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
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:
@@ -20,6 +20,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
@@ -45,23 +46,18 @@ fun NoteCard(
|
|||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
// Elevation instead of an edge line. The card used to be framed by a
|
// Depth, not the boundary — the edge below is that. 1dp: enough to
|
||||||
// 1px tint border, which measured a higher contrast against its own
|
// separate a white card from a #fafafa board, and the web's own
|
||||||
// fill (1.56-2.09) than the fill managed against the board
|
// `shadow-sm` is the value it is matching.
|
||||||
// (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))
|
.shadow(CARD_ELEVATION, RoundedCornerShape(CARD_RADIUS))
|
||||||
// Clipped BEFORE clickable, so the ripple is bounded by the card's
|
// Clipped BEFORE clickable, so the ripple is bounded by the card's
|
||||||
// rounded corners instead of a rectangle overhanging them.
|
// rounded corners instead of a rectangle overhanging them.
|
||||||
.clip(RoundedCornerShape(CARD_RADIUS))
|
.clip(RoundedCornerShape(CARD_RADIUS))
|
||||||
.clickable(onClickLabel = stringResource(R.string.board_open_note), onClick = onOpen)
|
.clickable(onClickLabel = stringResource(R.string.board_open_note), onClick = onOpen)
|
||||||
.background(tint.cardBackground(dark, strong))
|
.background(tint.cardBackground(dark, strong))
|
||||||
|
// ONE grey edge on every card, regardless of its colour — the tint is
|
||||||
|
// deliberately not consulted here. See CARD_EDGE_DARK.
|
||||||
|
.border(1.dp, if (dark) CARD_EDGE_DARK else CARD_EDGE_LIGHT, RoundedCornerShape(CARD_RADIUS))
|
||||||
.padding(12.dp),
|
.padding(12.dp),
|
||||||
) {
|
) {
|
||||||
// Body then checklist, in order — a note can carry both (M13 step 2), and
|
// Body then checklist, in order — a note can carry both (M13 step 2), and
|
||||||
@@ -248,10 +244,31 @@ private const val MAX_PREVIEW_LINES = 8
|
|||||||
private const val MAX_WRAPPED_LINES = 2
|
private const val MAX_WRAPPED_LINES = 2
|
||||||
private const val MAX_LABEL_CHIPS = 3
|
private const val MAX_LABEL_CHIPS = 3
|
||||||
private val CARD_RADIUS = 12.dp
|
private val CARD_RADIUS = 12.dp
|
||||||
|
private val CARD_ELEVATION = 1.dp
|
||||||
|
|
||||||
// Enough to lift a white card off a #fafafa board and no more. Material's own
|
// THE CARD'S EDGE — one grey, every card, both weights, all ten colour keys. It is a
|
||||||
// resting card elevation is 1dp; this is a shade above it because the board is a
|
// constant here rather than a column in NoteTint precisely so the palette CANNOT vary
|
||||||
// masonry field of cards rather than one card on a page, and at 1dp the whole field
|
// it; uniformity is the feature.
|
||||||
// reads flat again — which is what the removed border was compensating for.
|
//
|
||||||
private val CARD_ELEVATION = 2.dp
|
// The version of this that came from the palette was a `{hue}-900` border, and it
|
||||||
|
// failed twice over: the line measured 1.56-2.09 against its own fill while the fill
|
||||||
|
// managed only 1.03-1.05 against the board, so it was the loudest thing on the card —
|
||||||
|
// and it carried the same information the fill did. A field of cards read as a grid of
|
||||||
|
// outlines however different the colours inside were. A neutral line carries no
|
||||||
|
// information at all, which is exactly what lets it be structure instead of content.
|
||||||
|
//
|
||||||
|
// The two values are MATCHED rather than chosen by eye: each measures ~1.6-1.7 against
|
||||||
|
// the card it edges (light 1.57-1.98 across all twenty fills, dark 1.58-1.73), so the
|
||||||
|
// edge reads with the same authority in either theme. Dark is `neutral-700`, which is
|
||||||
|
// 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`, neither of which
|
||||||
|
// lands in range: 300 fades to 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 way to write this and was
|
||||||
|
// measured and rejected: a border composites over the card's own fill, so `White` at
|
||||||
|
// 20% comes out #56396D on a purple card and #A3C9C1 on a teal one. Hue-coded edges are
|
||||||
|
// the thing being removed.
|
||||||
|
private val CARD_EDGE_LIGHT = Color(0xFFB8B8B8)
|
||||||
|
private val CARD_EDGE_DARK = Color(0xFF404040)
|
||||||
private val CHIP_RADIUS = 6.dp
|
private val CHIP_RADIUS = 6.dp
|
||||||
|
|||||||
@@ -72,18 +72,14 @@ data class NoteTint(
|
|||||||
* not a wall of white. Drawing those at the same weight is what prompted the
|
* 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".
|
* operator's "the tints look the same as the chosen colors".
|
||||||
*
|
*
|
||||||
* THE CARD NO LONGER HAS A BORDER. It used to carry a 1px `{hue}-900` edge, and
|
* THE CARD'S EDGE IS NO LONGER A TINT. It used to carry a 1px `{hue}-900` border,
|
||||||
* measured against its own fill that line was a 1.56–2.09 contrast where the fill
|
* and measured against its own fill that line was a 1.56–2.09 contrast where the
|
||||||
* managed 1.03–1.05 against the board. The loudest thing on every card was an
|
* fill managed 1.03–1.05 against the board — so the loudest thing on every card
|
||||||
* identical line in an identical place, so a board of them read as a grid of
|
* was a line saying exactly what the fill already said, and a field of them read
|
||||||
* outlined rectangles whatever colour was inside — the operator's "too samey even
|
* as a grid of outlines whatever colour was inside. The card still has an edge;
|
||||||
* with the color differences". [border] survives for panels, banners and pickers,
|
* it is one grey for all ten keys and it lives in `NoteCard.kt` as a constant, so
|
||||||
* which are single elements rather than a field of them; a note card gets none.
|
* the palette cannot vary it. [border] is untouched and still serves panels,
|
||||||
*
|
* banners, the update card and the pickers — single elements, not a field.
|
||||||
* 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
|
* 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
|
* within a hair of each other (red: 1.11 against the board versus 1.12) and
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown))
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
ref="root"
|
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="[
|
:class="[
|
||||||
noteCardClasses(note),
|
noteCardClasses(note),
|
||||||
dragging ? 'opacity-40' : '',
|
dragging ? 'opacity-40' : '',
|
||||||
@@ -233,14 +233,30 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown))
|
|||||||
]"
|
]"
|
||||||
:data-note-id="note.id"
|
:data-note-id="note.id"
|
||||||
>
|
>
|
||||||
<!-- No `border`. The hue border that used to frame every card outshouted the fill
|
<!-- THE EDGE LIVES HERE, NOT IN THE PALETTE, and that is the whole point of it.
|
||||||
it framed and turned the board into a grid of identical outlines — see the
|
Every card gets the same grey hairline whatever colour it is; the fill is the
|
||||||
measurements above NOTE_CARD_CLASSES. `shadow` rather than `shadow-sm` because
|
only thing that varies. The version of this that was a `{hue}-900` border
|
||||||
it is now the ONLY thing separating a light-mode card from the board: an
|
failed because the line was both the loudest element on the card (1.56-2.09
|
||||||
untagged card is `bg-red-50` on `bg-neutral-50`, a 1.05 contrast that reads as
|
against its own fill, where the fill managed 1.03-1.05 against the board) AND
|
||||||
a card purely by sitting above one. Dark mode gets no shadow worth the name
|
carried the same information the fill did, so a board of them read as a grid
|
||||||
against a near-black board, which is why the subdued fills moved onto the card
|
of outlines. A neutral line carries no information at all, which is exactly
|
||||||
surface instead. -->
|
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
|
<img
|
||||||
v-if="firstImage"
|
v-if="firstImage"
|
||||||
:src="firstImage.url"
|
:src="firstImage.url"
|
||||||
|
|||||||
@@ -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.
|
// 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
|
// NO BORDER IN EITHER RAMP, here or below — the note card still HAS an edge, but it
|
||||||
// `border-{hue}-900`, and measured against its own fill that line was a 1.56–2.09
|
// is one grey for every card and it lives in NoteCard.vue, not in the palette. That
|
||||||
// contrast while the fill managed only 1.03–1.05 against the board. The loudest
|
// separation is the fix. The border used to be `border-{hue}-900` and measured
|
||||||
// thing on every card was therefore an identical line in an identical place, and a
|
// 1.56–2.09 against its own fill while the fill managed only 1.03–1.05 against the
|
||||||
// board of them read as a grid of outlined rectangles no matter what colour was
|
// board, so the loudest thing on every card was a line that said exactly what the
|
||||||
// inside — "too samey even with the color differences". The card's boundary now
|
// fill already said — and a field of them read as a grid of outlines however
|
||||||
// comes from its fill in dark mode and from `shadow` in light (NoteCard.vue), both
|
// different the colours inside were. A line that varies by colour is content; a line
|
||||||
// of which vary with the card instead of framing it.
|
// 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
|
// 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
|
// 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
|
// the card at the board's own lightness (1.03), which is why the old hue border had
|
||||||
// has just lost. These lay the same hue over the CARD SURFACE (`neutral-900`,
|
// 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
|
// #171717) at 18%, so an untagged card sits exactly where the default white card
|
||||||
// always sat (1.11–1.14 vs the board, against `bg-neutral-900`'s 1.10) while
|
// always sat (1.11–1.14 vs the board, against `bg-neutral-900`'s 1.10) while
|
||||||
// carrying LESS colour than before: chroma 7–17 where the old ramp had 10–23.
|
// carrying LESS colour than before: chroma 7–17 where the old ramp had 10–23.
|
||||||
|
|||||||
Reference in New Issue
Block a user