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

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:
2026-08-26 14:21:42 -04:00
co-authored by Claude Opus 5
parent fe18aaa956
commit 47f108c9c8
4 changed files with 147 additions and 43 deletions
+58 -26
View File
@@ -17,17 +17,43 @@ export const NOTE_COLOR_KEYS = [
export type NoteColor = (typeof NOTE_COLOR_KEYS)[number];
// The SUBDUED ramp — what a note wears when nothing chose a colour for it.
//
// NO BORDER, here or in the strong ramp below. A note card used to carry a 1px
// `border-{hue}-900`, and measured against its own fill that line was a 1.562.09
// contrast while the fill managed only 1.031.05 against the board. The loudest
// thing on every card was therefore an identical line in an identical place, and a
// board of them read as a grid of outlined rectangles no matter what colour was
// inside — "too samey even with the color differences". The card's boundary now
// comes from its fill in dark mode and from `shadow` in light (NoteCard.vue), both
// of which vary with the card instead of framing it.
//
// The dark values are COMPOSITED HEX rather than a Tailwind step, and that is the
// whole idea. `dark:bg-red-950/25` laid a hue over the near-black BOARD, which put
// the card at the board's own lightness (1.03) — invisible without the border it
// has just lost. These lay the same hue over the CARD SURFACE (`neutral-900`,
// #171717) at 18%, so an untagged card sits exactly where the default white card
// always sat (1.111.14 vs the board, against `bg-neutral-900`'s 1.10) while
// carrying LESS colour than before: chroma 717 where the old ramp had 1023.
//
// Subtler and more visible at once, which is only a contradiction if you assume
// subtlety has to come from lightness. Here it comes from chroma, and lightness is
// left to say "this is a card".
//
// Recipe, so these can be regenerated rather than guessed at: sRGB alpha
// compositing of `{hue}-950` at 0.18 over #171717 — `round(fg*0.18 + 23*0.82)` per
// channel. `gray` uses `neutral-800` as its 950, matching the strong ramp.
export const NOTE_CARD_CLASSES: Record<NoteColor, string> = {
default: "bg-white border-neutral-200 dark:bg-neutral-900 dark:border-neutral-700",
red: "bg-red-50 border-red-200 dark:bg-red-950/25 dark:border-red-900",
orange: "bg-orange-50 border-orange-200 dark:bg-orange-950/25 dark:border-orange-900",
yellow: "bg-amber-50 border-amber-200 dark:bg-amber-950/25 dark:border-amber-900",
green: "bg-green-50 border-green-200 dark:bg-green-950/25 dark:border-green-900",
teal: "bg-teal-50 border-teal-200 dark:bg-teal-950/25 dark:border-teal-900",
blue: "bg-blue-50 border-blue-200 dark:bg-blue-950/25 dark:border-blue-900",
purple: "bg-purple-50 border-purple-200 dark:bg-purple-950/25 dark:border-purple-900",
pink: "bg-pink-50 border-pink-200 dark:bg-pink-950/25 dark:border-pink-900",
gray: "bg-neutral-100 border-neutral-300 dark:bg-neutral-800/25 dark:border-neutral-700",
default: "bg-white dark:bg-neutral-900",
red: "bg-red-50 dark:bg-[#1f1515]",
orange: "bg-orange-50 dark:bg-[#1f1614]",
yellow: "bg-amber-50 dark:bg-[#1f1813]",
green: "bg-green-50 dark:bg-[#141b17]",
teal: "bg-teal-50 dark:bg-[#141b1b]",
blue: "bg-blue-50 dark:bg-[#171a22]",
purple: "bg-purple-50 dark:bg-[#1d1425]",
pink: "bg-pink-50 dark:bg-[#211419]",
gray: "bg-neutral-100 dark:bg-[#1a1a1a]",
};
export const NOTE_SWATCH_CLASSES: Record<NoteColor, string> = {
@@ -204,24 +230,30 @@ export function resolveLabelColor(label: { name: string; color?: string | null }
// (until step 5) by the picker. One Tailwind step deeper in light mode, and a much
// heavier fill in dark.
//
// The two ramps move in opposite directions per theme, because that is where each has
// headroom. Light mode cannot go lighter than `-50` without being white again, so the
// gap is opened by deepening the tagged end to `-100`. Dark mode CAN go lighter, so
// the untagged end drops to `/25` — closer to the board, which also gives the light
// body text MORE contrast rather than less.
// UNCHANGED by the border pass, deliberately. The operator's complaint was the edge
// line and the loudness of the DERIVED tint; the chosen colours were "pretty well"
// where they landed, and a ramp somebody has already signed off on is not something
// to redo while fixing something else.
//
// Borders stay put. The fill is the signal; moving both just muddies the edge.
// WHAT SEPARATES THE TWO RAMPS IS NOW CHROMA, NOT LIGHTNESS. Since the subdued ramp
// moved onto the card surface, the two sit at almost the same lightness in dark mode
// (red: 1.11 vs 1.12 against the board) and differ threefold in colour (chroma 10 vs
// 41). That is the better axis anyway: lightness is what says "this is a card", and
// spending it on emphasis is what left untagged cards flat against the board.
//
// Light mode was already built this way and needed no change — `-50` and `-100` are
// both essentially white plus a different amount of hue.
export const NOTE_CARD_CLASSES_STRONG: Record<NoteColor, string> = {
default: "bg-white border-neutral-200 dark:bg-neutral-900 dark:border-neutral-700",
red: "bg-red-100 border-red-200 dark:bg-red-950/70 dark:border-red-900",
orange: "bg-orange-100 border-orange-200 dark:bg-orange-950/70 dark:border-orange-900",
yellow: "bg-amber-100 border-amber-200 dark:bg-amber-950/70 dark:border-amber-900",
green: "bg-green-100 border-green-200 dark:bg-green-950/70 dark:border-green-900",
teal: "bg-teal-100 border-teal-200 dark:bg-teal-950/70 dark:border-teal-900",
blue: "bg-blue-100 border-blue-200 dark:bg-blue-950/70 dark:border-blue-900",
purple: "bg-purple-100 border-purple-200 dark:bg-purple-950/70 dark:border-purple-900",
pink: "bg-pink-100 border-pink-200 dark:bg-pink-950/70 dark:border-pink-900",
gray: "bg-neutral-200 border-neutral-300 dark:bg-neutral-800/70 dark:border-neutral-700",
default: "bg-white dark:bg-neutral-900",
red: "bg-red-100 dark:bg-red-950/70",
orange: "bg-orange-100 dark:bg-orange-950/70",
yellow: "bg-amber-100 dark:bg-amber-950/70",
green: "bg-green-100 dark:bg-green-950/70",
teal: "bg-teal-100 dark:bg-teal-950/70",
blue: "bg-blue-100 dark:bg-blue-950/70",
purple: "bg-purple-100 dark:bg-purple-950/70",
pink: "bg-pink-100 dark:bg-pink-950/70",
gray: "bg-neutral-200 dark:bg-neutral-800/70",
};
/**