The border was the thing making every note look the same
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 9s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Successful in 18s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m8s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m23s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m4s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 9s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Successful in 18s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m8s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m23s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m4s
A note card carried a 1px tint border. Measured against its own fill, that
line was a 1.56-2.09 contrast in dark mode while the fill managed only
1.03-1.05 against the board — so the loudest thing on every card was an
identical line in an identical place, and a field of them read as a grid of
outlined rectangles however different the colours inside were.
Removed from the note card on both surfaces. `border` survives for panels,
banners, the update card and the pickers: those are single elements, not a
field of them.
What replaces it differs by theme, because elevation does.
Light leans on a shadow. An untagged card is `bg-red-50` on a `neutral-50`
board — a 1.04 contrast that can only read as a card by sitting above one.
The web goes `shadow-sm` -> `shadow`; Android had no shadow at all and gets
2dp.
Dark cannot use one, black on near-black. So the subdued fills moved onto
the card surface instead: `{hue}-950` composited at 0.18 over #171717 and
baked, rather than the same hue at 0.25 over the near-black board. An
untagged card now sits where the plain white card always sat (1.11-1.14
against the board, against `bg-neutral-900`'s 1.10) while carrying LESS hue
than before — chroma 7-17 where the old ramp had 10-23.
Subtler and more visible at once, which is only a contradiction if subtlety
has to come from lightness. Here it comes from chroma, and lightness is left
to say "this is a card". Which also reframes the two weights: in dark they
now sit within a hair of each other (red: 1.11 vs 1.12) and differ threefold
in colour (chroma 10 vs 41).
The chosen ramp is untouched — the operator signed those colours off, and a
ramp somebody likes is not something to redo while fixing something else.
Light was already built this way: `-50` and `-100` are both white plus a
different amount of hue.
Body text still measures 14.3-16.4 against the 4.5 it needs, meta 6.9-7.1
against 3.0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
@@ -44,12 +45,23 @@ fun NoteCard(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
// Elevation instead of an edge line. The card used to be framed by a
|
||||
// 1px tint border, which measured a higher contrast against its own
|
||||
// fill (1.56-2.09) than the fill managed against the board
|
||||
// (1.03-1.05) — so the loudest thing on every card was the same line
|
||||
// in the same place, and a board of them read as a grid of outlines
|
||||
// however different the colours inside. See NoteTint.cardBackground.
|
||||
//
|
||||
// It matters most in LIGHT mode, where an untagged card is a 1.04
|
||||
// contrast against the board and nothing but a shadow can lift it
|
||||
// off. In dark the shadow is black on near-black and invisible, which
|
||||
// is why the subdued fills were moved onto the card surface instead.
|
||||
.shadow(CARD_ELEVATION, RoundedCornerShape(CARD_RADIUS))
|
||||
// Clipped BEFORE clickable, so the ripple is bounded by the card's
|
||||
// rounded corners instead of a rectangle overhanging them.
|
||||
.clip(RoundedCornerShape(CARD_RADIUS))
|
||||
.clickable(onClickLabel = stringResource(R.string.board_open_note), onClick = onOpen)
|
||||
.background(tint.cardBackground(dark, strong))
|
||||
.border(1.dp, tint.border(dark), RoundedCornerShape(CARD_RADIUS))
|
||||
.padding(12.dp),
|
||||
) {
|
||||
// Body then checklist, in order — a note can carry both (M13 step 2), and
|
||||
@@ -236,4 +248,10 @@ private const val MAX_PREVIEW_LINES = 8
|
||||
private const val MAX_WRAPPED_LINES = 2
|
||||
private const val MAX_LABEL_CHIPS = 3
|
||||
private val CARD_RADIUS = 12.dp
|
||||
|
||||
// Enough to lift a white card off a #fafafa board and no more. Material's own
|
||||
// resting card elevation is 1dp; this is a shade above it because the board is a
|
||||
// masonry field of cards rather than one card on a page, and at 1dp the whole field
|
||||
// reads flat again — which is what the removed border was compensating for.
|
||||
private val CARD_ELEVATION = 2.dp
|
||||
private val CHIP_RADIUS = 6.dp
|
||||
|
||||
@@ -14,9 +14,10 @@ import com.fabledsword.thoughtsync.core.Note
|
||||
* is amber on the desktop is the same amber on the phone rather than a near-miss.
|
||||
* Generated from tailwindcss 3.4's palette rather than transcribed by eye.
|
||||
*
|
||||
* Dark tints keep the web's ALPHA (`dark:bg-red-950/40`) instead of a
|
||||
* Most dark tints keep the web's ALPHA (`dark:bg-red-950/70`) instead of a
|
||||
* precomputed blend — Compose composites a translucent colour over what's beneath
|
||||
* exactly as CSS does, so the card sits on the background the same way in both.
|
||||
* exactly as CSS does, so the card sits on the background the same way in both. The
|
||||
* one exception is [NoteTint.darkCardSubdued], which is baked; see its own note.
|
||||
*
|
||||
* `yellow` maps to Tailwind's *amber*, matching colors.ts; plain yellow is too
|
||||
* acid against the neutral surfaces.
|
||||
@@ -31,6 +32,28 @@ data class NoteTint(
|
||||
val lightChipForeground: Color,
|
||||
val darkChipBackground: Color,
|
||||
val darkChipForeground: Color,
|
||||
/**
|
||||
* The DARK subdued card fill, opaque, as a colour rather than an alpha.
|
||||
*
|
||||
* Every other dark value in this table is `{hue}-950` at an alpha, composited by
|
||||
* whoever draws it over the near-black BOARD — which put an untagged card at the
|
||||
* board's own lightness (a 1.03 contrast) and left the border doing all the work
|
||||
* of saying "card". The border is gone (see [cardBackground]), so these are
|
||||
* `{hue}-950` composited at 0.18 over the CARD SURFACE, #171717, and baked: the
|
||||
* card now sits where the plain white card always sat, 1.11–1.14 against the
|
||||
* board, while carrying LESS hue than the old ramp did — chroma 7–17 against
|
||||
* 10–23.
|
||||
*
|
||||
* Opaque rather than another alpha because the backdrop is no longer the board
|
||||
* alone: the editor draws the same fill on a sheet. A colour that means one thing
|
||||
* on the board and another in the editor is the bug this whole milestone exists
|
||||
* to stop.
|
||||
*
|
||||
* Baked rather than lerped at draw time: Compose's `lerp(Color, Color, Float)`
|
||||
* interpolates in Oklab, CSS alpha-composites in sRGB, and the mirror in
|
||||
* colors.ts is only worth having if both sides land on the same byte.
|
||||
*/
|
||||
val darkCardSubdued: Color,
|
||||
/**
|
||||
* False only for `default`, which is the ABSENCE of a colour rather than one of
|
||||
* them. Everything else has two weights (see [cardBackground]); `default` has one,
|
||||
@@ -42,21 +65,34 @@ data class NoteTint(
|
||||
fun background(dark: Boolean): Color = if (dark) darkBackground else lightBackground
|
||||
|
||||
/**
|
||||
* A NOTE card's fill, at one of two weights.
|
||||
* A NOTE card's fill, at one of two weights, and the card's ONLY boundary.
|
||||
*
|
||||
* `strong` means the colour was CHOSEN — by a tag, or (until step 5) by the
|
||||
* picker. Subdued means it was derived from the note's id purely so the board is
|
||||
* not a wall of white. Drawing those at the same weight is what prompted the
|
||||
* operator's "the tints look the same as the chosen colors".
|
||||
*
|
||||
* The two weights move in OPPOSITE directions per theme, because that is where
|
||||
* each has headroom. Light cannot go lighter than `-50` without being white again,
|
||||
* so the gap opens by deepening the chosen end to `-100` — which is exactly
|
||||
* [lightChipBackground], already in this table, so no new hex is transcribed.
|
||||
* Dark CAN go lighter, so the derived end drops to a quarter opacity: closer to
|
||||
* the board, which gives the light body text MORE contrast, not less.
|
||||
* THE CARD NO LONGER HAS A BORDER. It used to carry a 1px `{hue}-900` edge, and
|
||||
* measured against its own fill that line was a 1.56–2.09 contrast where the fill
|
||||
* managed 1.03–1.05 against the board. The loudest thing on every card was an
|
||||
* identical line in an identical place, so a board of them read as a grid of
|
||||
* outlined rectangles whatever colour was inside — the operator's "too samey even
|
||||
* with the color differences". [border] survives for panels, banners and pickers,
|
||||
* which are single elements rather than a field of them; a note card gets none.
|
||||
*
|
||||
* Borders are untouched. The fill is the signal; moving both muddies the edge.
|
||||
* What replaces it differs by theme, because elevation does. Dark leans on
|
||||
* [darkCardSubdued] sitting above the near-black board. Light leans on a real
|
||||
* shadow (`NoteCard.kt`), since white-on-#fafafa is a 1.04 contrast and no fill
|
||||
* is going to rescue it.
|
||||
*
|
||||
* WHAT SEPARATES THE TWO WEIGHTS IS CHROMA, NOT LIGHTNESS. In dark they now sit
|
||||
* within a hair of each other (red: 1.11 against the board versus 1.12) and
|
||||
* differ threefold in colour (chroma 10 versus 41). Lightness is what says "this
|
||||
* is a card"; spending it on emphasis is what left untagged cards flat.
|
||||
*
|
||||
* Light was already built this way and is untouched: `-50` and `-100` are both
|
||||
* white plus a different amount of hue, and `-100` is exactly
|
||||
* [lightChipBackground], already in this table, so no new hex is transcribed.
|
||||
*/
|
||||
fun cardBackground(
|
||||
dark: Boolean,
|
||||
@@ -64,7 +100,8 @@ data class NoteTint(
|
||||
): Color =
|
||||
when {
|
||||
!tintable -> background(dark)
|
||||
dark -> darkBackground.copy(alpha = if (strong) STRONG_DARK_ALPHA else SUBDUED_DARK_ALPHA)
|
||||
dark && strong -> darkBackground.copy(alpha = STRONG_DARK_ALPHA)
|
||||
dark -> darkCardSubdued
|
||||
strong -> lightChipBackground
|
||||
else -> lightBackground
|
||||
}
|
||||
@@ -103,10 +140,9 @@ data class NoteTint(
|
||||
// it does not carry the meaning. Raise to 0.80 if that judgment is ever overruled.
|
||||
private const val CHIP_EDGE_ALPHA = 0.60f
|
||||
|
||||
// The two dark-theme weights, as fractions. Mirror `dark:bg-{hue}-950/25` and `/70`
|
||||
// in colors.ts — these are the only two numbers that have to agree by hand, since the
|
||||
// light weights are both already columns of the table below.
|
||||
private const val SUBDUED_DARK_ALPHA = 0.25f
|
||||
// The chosen weight in dark, as a fraction. Mirrors `dark:bg-{hue}-950/70` in
|
||||
// colors.ts. Its counterpart is no longer an alpha at all — the subdued weight is a
|
||||
// baked colour, [NoteTint.darkCardSubdued], for the reasons recorded there.
|
||||
private const val STRONG_DARK_ALPHA = 0.70f
|
||||
|
||||
/** Keyed by the core's colour vocabulary. Order matches the web's picker. */
|
||||
@@ -123,6 +159,7 @@ val NOTE_TINTS: Map<String, NoteTint> =
|
||||
lightChipForeground = Color(0xFF525252),
|
||||
darkChipBackground = Color(0x1AFFFFFF),
|
||||
darkChipForeground = Color(0xFFD4D4D4),
|
||||
darkCardSubdued = Color(0xFF171717),
|
||||
tintable = false,
|
||||
),
|
||||
"red" to
|
||||
@@ -136,6 +173,7 @@ val NOTE_TINTS: Map<String, NoteTint> =
|
||||
lightChipForeground = Color(0xFFB91C1C),
|
||||
darkChipBackground = Color(0x80450A0A),
|
||||
darkChipForeground = Color(0xFFFCA5A5),
|
||||
darkCardSubdued = Color(0xFF1F1515),
|
||||
),
|
||||
"orange" to
|
||||
NoteTint(
|
||||
@@ -148,6 +186,7 @@ val NOTE_TINTS: Map<String, NoteTint> =
|
||||
lightChipForeground = Color(0xFFC2410C),
|
||||
darkChipBackground = Color(0x80431407),
|
||||
darkChipForeground = Color(0xFFFDBA74),
|
||||
darkCardSubdued = Color(0xFF1F1614),
|
||||
),
|
||||
"yellow" to
|
||||
NoteTint(
|
||||
@@ -160,6 +199,7 @@ val NOTE_TINTS: Map<String, NoteTint> =
|
||||
lightChipForeground = Color(0xFF92400E),
|
||||
darkChipBackground = Color(0x80451A03),
|
||||
darkChipForeground = Color(0xFFFCD34D),
|
||||
darkCardSubdued = Color(0xFF1F1813),
|
||||
),
|
||||
"green" to
|
||||
NoteTint(
|
||||
@@ -172,6 +212,7 @@ val NOTE_TINTS: Map<String, NoteTint> =
|
||||
lightChipForeground = Color(0xFF15803D),
|
||||
darkChipBackground = Color(0x80052E16),
|
||||
darkChipForeground = Color(0xFF86EFAC),
|
||||
darkCardSubdued = Color(0xFF141B17),
|
||||
),
|
||||
"teal" to
|
||||
NoteTint(
|
||||
@@ -184,6 +225,7 @@ val NOTE_TINTS: Map<String, NoteTint> =
|
||||
lightChipForeground = Color(0xFF0F766E),
|
||||
darkChipBackground = Color(0x80042F2E),
|
||||
darkChipForeground = Color(0xFF5EEAD4),
|
||||
darkCardSubdued = Color(0xFF141B1B),
|
||||
),
|
||||
"blue" to
|
||||
NoteTint(
|
||||
@@ -196,6 +238,7 @@ val NOTE_TINTS: Map<String, NoteTint> =
|
||||
lightChipForeground = Color(0xFF1D4ED8),
|
||||
darkChipBackground = Color(0x80172554),
|
||||
darkChipForeground = Color(0xFF93C5FD),
|
||||
darkCardSubdued = Color(0xFF171A22),
|
||||
),
|
||||
"purple" to
|
||||
NoteTint(
|
||||
@@ -208,6 +251,7 @@ val NOTE_TINTS: Map<String, NoteTint> =
|
||||
lightChipForeground = Color(0xFF7E22CE),
|
||||
darkChipBackground = Color(0x803B0764),
|
||||
darkChipForeground = Color(0xFFD8B4FE),
|
||||
darkCardSubdued = Color(0xFF1D1425),
|
||||
),
|
||||
"pink" to
|
||||
NoteTint(
|
||||
@@ -220,6 +264,7 @@ val NOTE_TINTS: Map<String, NoteTint> =
|
||||
lightChipForeground = Color(0xFFBE185D),
|
||||
darkChipBackground = Color(0x80500724),
|
||||
darkChipForeground = Color(0xFFF9A8D4),
|
||||
darkCardSubdued = Color(0xFF211419),
|
||||
),
|
||||
"gray" to
|
||||
NoteTint(
|
||||
@@ -232,6 +277,7 @@ val NOTE_TINTS: Map<String, NoteTint> =
|
||||
lightChipForeground = Color(0xFF404040),
|
||||
darkChipBackground = Color(0xFF404040),
|
||||
darkChipForeground = Color(0xFFE5E5E5),
|
||||
darkCardSubdued = Color(0xFF1A1A1A),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user