Give the cards an edge again — one grey, not ten hues
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 9s
CI & Build / Python lint (push) Successful in 10s
CI & Build / Python tests (push) Successful in 15s
CI & Build / integration (push) Successful in 20s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m59s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m39s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m20s

The border was never the problem; a border that carried COLOUR was. It said
exactly what the fill already said, at 1.56-2.09 against that fill where the
fill managed 1.03-1.05 against the board — the loudest element on every card
was redundant with the quietest. A line that varies by colour is content and
competes with the fill. A line that never varies is structure and does not.

So the edge comes back, and it comes back as a constant in NoteCard rather
than a column in the palette. Uniformity is the feature, and putting it where
the palette cannot reach it is how that stays true.

  light  #b8b8b8    1.57-1.98 against all twenty card fills
  dark   #404040    1.58-1.73

Matched, not eyeballed: both land at ~1.6-1.7 against the card they edge, so
the edge reads with the same authority in either theme. Dark is `neutral-700`
— what the `default` card's border always was, one entry's value promoted to
the rule for all of them. Light sits between `neutral-300` and `neutral-400`
because neither lands in range: 300 fades to 1.18 on a gray-tagged card, 400
jumps to 2.52 and reads as a wireframe.

Rejected on measurement: a translucent black/white edge, which is the tidier
way to write it and self-adjusts per card. A border composites over the
card's own fill, so `border-white/20` comes out #56396d on a purple card and
#a3c9c1 on a teal one. Hue-coded edges are the thing being removed.

The shadow steps back to what it was for — depth, not the boundary. Web
returns to `shadow-sm`; Android's 2dp drops to 1dp, matching it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 15:00:22 -04:00
co-authored by Claude Opus 5
parent 47f108c9c8
commit ae2053d2ed
4 changed files with 77 additions and 47 deletions
+25 -9
View File
@@ -222,7 +222,7 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown))
<template>
<div
ref="root"
class="group relative mb-4 break-inside-avoid rounded-xl p-3 shadow transition hover:shadow-md"
class="group relative mb-4 break-inside-avoid rounded-xl border border-[#b8b8b8] p-3 shadow-sm transition hover:shadow-md dark:border-[#404040]"
:class="[
noteCardClasses(note),
dragging ? 'opacity-40' : '',
@@ -233,14 +233,30 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown))
]"
:data-note-id="note.id"
>
<!-- No `border`. The hue border that used to frame every card outshouted the fill
it framed and turned the board into a grid of identical outlines see the
measurements above NOTE_CARD_CLASSES. `shadow` rather than `shadow-sm` because
it is now the ONLY thing separating a light-mode card from the board: an
untagged card is `bg-red-50` on `bg-neutral-50`, a 1.05 contrast that reads as
a card purely by sitting above one. Dark mode gets no shadow worth the name
against a near-black board, which is why the subdued fills moved onto the card
surface instead. -->
<!-- THE EDGE LIVES HERE, NOT IN THE PALETTE, and that is the whole point of it.
Every card gets the same grey hairline whatever colour it is; the fill is the
only thing that varies. The version of this that was a `{hue}-900` border
failed because the line was both the loudest element on the card (1.56-2.09
against its own fill, where the fill managed 1.03-1.05 against the board) AND
carried the same information the fill did, so a board of them read as a grid
of outlines. A neutral line carries no information at all, which is exactly
what lets it be structure.
The two values are MATCHED, not picked by eye: each measures ~1.6-1.7 against
the card it edges (light 1.57-1.98, dark 1.58-1.73), so the edge has the same
authority in either theme. #404040 is `neutral-700`, which is what the default
card's border always was — promoted from one entry in the palette to the rule
for all of them. #b8b8b8 sits between `neutral-300` and `neutral-400`, neither
of which lands in range: 300 fades out at 1.18 on a gray-tagged card, 400
jumps to 2.52 and reads as a wireframe.
NOT a translucent black/white edge, which is the tidier-looking way to do this
and was measured and rejected: a border composites over the card's own fill,
so `border-white/20` comes out #56396d on a purple card and #a3c9c1 on a teal
one. Hue-coded edges are the thing being removed.
`shadow-sm`, back down from `shadow`: the border is the boundary again, so the
shadow is only depth. -->
<img
v-if="firstImage"
:src="firstImage.url"
+11 -10
View File
@@ -19,19 +19,20 @@ 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.
// NO BORDER IN EITHER RAMP, here or below — the note card still HAS an edge, but it
// is one grey for every card and it lives in NoteCard.vue, not in the palette. That
// separation is the fix. The border used to be `border-{hue}-900` and measured
// 1.562.09 against its own fill while the fill managed only 1.031.05 against the
// board, so the loudest thing on every card was a line that said exactly what the
// fill already said — and a field of them read as a grid of outlines however
// different the colours inside were. A line that varies by colour is content; a line
// that never varies is structure. Only one of those competes with the fill.
//
// 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`,
// the card at the board's own lightness (1.03), which is why the old hue border had
// to shout to be seen at all. 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.