diff --git a/docs/DESIGN.md b/docs/DESIGN.md index e0754d9e..f8774b57 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -130,7 +130,10 @@ style guide. to name one of the two. Black and white are the media scrims and their text. Five schemes × dark and light (B2): **Midnight** (blue), **Forest** (cyan), **Slate** (fuchsia), **Matrix** (moss) each earn their place with their own hue family; **Fjord** (Midnight's navy with a calm steel-blue accent) is the one kept - exception, the calm twin of the vivid Midnight. A new scheme still needs its own hue family. Normal text ≥ 4.5:1 and a control's edge ≥ 3:1 in **every** scheme and mode. + exception, the calm twin of the vivid Midnight. A new scheme still needs its own hue family. Normal text ≥ 4.5:1 and a control's edge ≥ 3:1 in **every** scheme and mode; + `--muted` keeps ≥ 6:1 on the bare `--bg`, because it mostly sits on glass, which costs up to ~1.5 of the ratio. + A tonal fill under text (a badge, the item count, the current rail row) is an opaque `bg-tint*` (its colour + mixed into `--surface`), never a translucent `bg-fg/8`: the glass and what scrolls under it show through. [WCAG 1.4.3, 1.4.11] The input edge token is set under Controls (A4). The ≥ 3:1 edge is the field family's (a field, the form's dropdown trigger), whose edge is the only thing that says "type here". A labelled pill (a chip, a toolbar trigger, a tab, a segment) keeps the faint `--border`: its text names it, @@ -788,3 +791,4 @@ of a full-width panel and nothing on the right. | 2026-10-14 | API tokens (owner A/A/A) | Settings → API tokens: a row per token in three lines (name + prefix, its folders and rights, last use and expiry), revoked from its ⋯; a new token asks the kind first (Hub exchange or Custom: rights as chips per folder), expiry 1 year by default; the token shows once in the same dialog, with Copy and a curl line | A table; one form with a preset link; the token in a banner above the list | A table truncates the rights at this width; the common token never shows a matrix to get wrong; closing the dialog is plainly when the token is gone | | 2026-10-15 | Folder links (owner A/A/A) | An own folder's menu has Copy link and Share with link… as a file row does (the dialog is the link half of the file's); the link opens a list of the folder's files as it is now, Play opening the watch player above it with the playing row lit, a document only downloading; the rail marks a folder with a live link beside its count | Share with link… alone; cards; a download-only list; no mark | Mostly videos from a phone, read on a phone: a list gives a long name its line and the next one is a tap away; the folder link is the one place where moving a file in publishes it, so where it is must show | | 2026-10-17 | Copy-only changes (owner ★) | Step 4 of the Conformance check alone (not 1–3 or 5): every state the changed text renders in, seen in both languages | The whole five-step check; no screenshot at all (DOM test + key parity) | Steps 1–3 and 5 judge controls, patterns and composition, which a text change does not touch; the one risk left is a longer Hungarian string wrapping or clipping, and only a screenshot shows that | +| 2026-10-17 | Text on glass (owner ★) | Light `--muted` 15% toward `--fg` in every scheme (≥ 6:1 on the bare page); the item count, the badges and the current Files row on opaque tints mixed into the surface | Only the two failing components; the item count's number in `--fg` | Pixel-measured over every scheme × mode × backdrop, the count fell to 3.8:1 (dark too: thumbnails scroll under the toolbar's glass) and Channels' tabs to 4.1:1; an `--fg` count outshouts the filters beside it | diff --git a/frontend/src/components/FilesRail.tsx b/frontend/src/components/FilesRail.tsx index c26ba92d..79471ae9 100644 --- a/frontend/src/components/FilesRail.tsx +++ b/frontend/src/components/FilesRail.tsx @@ -63,7 +63,7 @@ function Entry({ onClick={() => setView(view)} className={clsx( "flex w-full min-h-8 items-center gap-2 rounded-lg px-2 text-sm", - active ? "bg-accent/12 font-medium text-accent" : "text-fg", + active ? "bg-tint-current font-medium text-accent" : "text-fg", )} > diff --git a/frontend/src/components/ui/Badge.tsx b/frontend/src/components/ui/Badge.tsx index be4b8b60..4619c18f 100644 --- a/frontend/src/components/ui/Badge.tsx +++ b/frontend/src/components/ui/Badge.tsx @@ -7,10 +7,12 @@ import type { ReactNode } from "react"; // at 8%, so it reads on the dark schemes and on white alike; `onDark` is the always-dark player's. A // `tone` says which kind of label it is (a role, a state to notice): its own colour at 6%, the most // that keeps its text at 4.5:1 on light (theme.contract.test.ts measures all three in every scheme). +// Each tint is mixed into the surface (`bg-tint*`), opaque: a translucent one lets the glass and what +// scrolls under it through, and the text's contrast goes with them. const TONES = { - neutral: "bg-fg/8 text-muted", - accent: "bg-accent/6 text-accent", - warn: "bg-warn/6 text-warn", + neutral: "bg-tint text-muted", + accent: "bg-tint-accent text-accent", + warn: "bg-tint-warn text-warn", } as const; export function Badge({ diff --git a/frontend/src/components/ui/ItemCount.tsx b/frontend/src/components/ui/ItemCount.tsx index 2d7c7ce2..61761f36 100644 --- a/frontend/src/components/ui/ItemCount.tsx +++ b/frontend/src/components/ui/ItemCount.tsx @@ -8,7 +8,8 @@ import { formatNumber } from "../../lib/format"; const OVERFLOW_AT = 1_000_000; // A compact, FIXED-WIDTH count pill for a toolbar: the filtered item count at the toolbar's 28, flat -// and tonal like a `Badge` — no edge, because it does nothing (edged = pressable, flat = just +// and tonal like a `Badge` (the same opaque `bg-tint`: the toolbar is glass over scrolling thumbnails) +// — no edge, because it does nothing (edged = pressable, flat = just // says; it used to wear the filter dropdowns' glass pill and read as one more). The fixed width is the point — a // changing number must never shove the controls beside it, so the box is sized to the widest value // ("999,999 items") and the content is centred within it. A count of a million or more collapses to @@ -46,7 +47,7 @@ export default function ItemCount({ failed ? t("common.countFailed") : count == null ? undefined : formatNumber(count, lang) } aria-busy={stale || undefined} - className="inline-flex h-7 min-w-[9rem] shrink-0 items-center justify-center gap-1.5 rounded-full bg-fg/8 px-3 text-sm whitespace-nowrap text-muted" + className="inline-flex h-7 min-w-[9rem] shrink-0 items-center justify-center gap-1.5 rounded-full bg-tint px-3 text-sm whitespace-nowrap text-muted" >