feat(design): batch B recorded, batch C in the design lab (E28 S2c)
DESIGN.md: B1-B9 decided (compact density, the four-scheme set with the accent-vs-status rule, state layers, the sticky band, surface per role, the watch page, the slim NavRail with optional labels, loading by shape, the fade edges) with their decision-log rows. The lab keeps the chosen A and B renderings as reference on shared tokens (labTokens.ts) and adds batch C: the action-placement classification for review, side panels on touch and below 768, the Plex player on touch, and the dialog conventions.
This commit is contained in:
+72
-32
@@ -58,38 +58,48 @@ style guide.
|
||||
(A3, under Controls). [HIG, WCAG 2.5.8]
|
||||
- **Text inputs** use at least 1rem text on a coarse pointer, so a tablet browser doesn't zoom the page
|
||||
when an input gets focus.
|
||||
- **Below 768px** (a narrow window, split screen): what replaces the side rails is **Pending (C)**.
|
||||
- **Below 768px** (a narrow window, split screen): what replaces the side rails, and how a side panel opens on touch, is **Pending (C2)**. The Plex player on touch is **Pending (C3)**.
|
||||
|
||||
## Tokens
|
||||
|
||||
- **Colour:** only semantic tokens (`--bg --surface --card --fg --muted --accent --accent-fg --danger
|
||||
--warn --success --info --border`, each with a `-fg` partner where text sits on it). Six schemes × dark
|
||||
and light. Normal text ≥ 4.5:1 and a control's edge ≥ 3:1 in **every** scheme and mode. [WCAG 1.4.3,
|
||||
1.4.11] _The scheme set and the light-theme accent are decision B2._ The input edge token is set under
|
||||
Controls (A4).
|
||||
--warn --success --info --border`, each with a `-fg` partner where text sits on it). Four schemes × dark
|
||||
and light (B2): **Midnight** (blue), **Forest** (cyan), **Slate** (fuchsia), **Matrix** (lime). Each earns its
|
||||
place with its own hue family. Normal text ≥ 4.5:1 and a control's edge ≥ 3:1 in **every** scheme and mode.
|
||||
[WCAG 1.4.3, 1.4.11] The input edge token is set under Controls (A4).
|
||||
- **The accent never looks like a status.** A scheme's accent is at least 30° of OKLCH hue away from
|
||||
`--danger`, `--warn` and `--success`, and passes 4.5:1 both as text on `--bg` and under `--accent-fg` on
|
||||
the fill, in dark and light. A primary button must never read as a destructive one. A new scheme is
|
||||
measured against this before it ships (the lab's B2 tiles do the arithmetic). [WCAG 1.4.1, 1.4.3]
|
||||
- **State layers (B3):** hover and pressed are a layer, not a colour swap: the text colour mixed at **10%**
|
||||
(hover) / **16%** (pressed) over whatever the control sits on, so it reads on the page, a card and glass
|
||||
alike. A filled control mixes its own text colour into its fill at 14% / 24%. Opacity dimming and
|
||||
`bg-card` hovers are violations (invisible on a light card). Selected stays the solid accent.
|
||||
- **Type and size:** everything is rem-derived off `--font-scale` (the Settings text-size slider), so a
|
||||
px font size or a px-frozen box is an eslint error.
|
||||
- **Radius:** chips and segmented controls `rounded-full`; tabs `rounded-lg`. Buttons, icon buttons and
|
||||
form fields `rounded-lg`; toolbar triggers and search a pill (Controls). _Surfaces per role: decision B5._
|
||||
form fields `rounded-lg`; toolbar triggers and search a pill (Controls). Surfaces: see Surfaces.
|
||||
- **Layers:** the fixed z-index scale in `index.css` (`base < menu < chrome < paneltab < panel < rail <
|
||||
overlay < popover < tooltip`). No ad-hoc z-index. [maintainability]
|
||||
|
||||
## Controls — _Decided_ (batch A; density **Pending**, B1)
|
||||
## Controls — _Decided_ (batches A and B)
|
||||
|
||||
The chosen rendering of each rule is in the lab under "Decided". The primitives are built to it in S3.
|
||||
|
||||
- **Buttons (A1):** `rounded-lg` (r8): a control's corner is smaller than the surface it sits on (card r12,
|
||||
dialog r16). The rung follows the context: `md` in forms and dialog footers, `sm` in toolbars and panel
|
||||
headers, `xs` inside a table row. Glass is a `Button` variant, not a hand-built glass button. _The rung
|
||||
heights (standard 38/32/26 or compact 32/28/24) are decision B1._ [HIG, Material]
|
||||
- **Icon buttons (A2):** two sizes, r8: the smaller in rows and dense toolbars, the larger on its own. On a
|
||||
headers, `xs` inside a table row. Glass is a `Button` variant, not a hand-built glass button. [HIG, Material]
|
||||
- **Density (B1): compact, by explicit height.** `md` 2rem (32), `sm` 1.75rem (28), `xs` 1.5rem (24); icon
|
||||
buttons 1.75rem / 2rem. A toolbar's search pill, dropdown trigger, `sm` button and small icon button are
|
||||
the same 28, so the row lines up. Nothing goes under 24 (WCAG 2.5.8); touch grows it to 44 (A3).
|
||||
- **Icon buttons (A2):** two sizes (28 / 32), r8: the smaller in rows and dense toolbars, the larger on its own. On a
|
||||
picture, a round, dark translucent button, readable on any thumbnail. Round means "on media" only.
|
||||
- **Touch (A3):** on a coarse pointer every control primitive (`Button`, `SegmentedToggle`, `FilterSelect`,
|
||||
`Tabs`, `Switch`, chips, the nav rail) grows to 2.75rem (44px) through one token, as a real box. Mouse
|
||||
sizes don't change. [HIG, WCAG 2.5.8]
|
||||
- **Input (A4):** one bordered input primitive. The edge is `color-mix(in srgb, var(--fg) 55%, var(--bg))`,
|
||||
≥ 3:1 against the page in every scheme and mode. A form field is r8 at the `md` height; a toolbar search
|
||||
is a pill. Text is 1rem on a coarse pointer. [WCAG 1.4.11]
|
||||
≥ 3:1 against the page in every scheme and mode. A form field is r8 at the `md` height (32); a toolbar search
|
||||
is a pill at the `sm` height (28). Text is 1rem on a coarse pointer. [WCAG 1.4.11]
|
||||
- **Dropdown trigger (A5):** a pill in a toolbar (where the pill means "a filter or a choice"), r8 in a form
|
||||
(it lines up with the inputs).
|
||||
- **Checkbox, switch, slider (A6):** the browser's own controls tinted with `accent-color`; the whole
|
||||
@@ -105,13 +115,29 @@ Also in force:
|
||||
- A button that starts slow work shows `loading` (a spinner, `aria-busy`) and can't be pressed twice.
|
||||
[Nielsen #1]
|
||||
|
||||
## Surfaces and page chrome — **Pending (batch B)**
|
||||
## Surfaces and page chrome — _Decided_ (batch B)
|
||||
|
||||
Open in the lab: **B1** button density · **B2** the scheme set (and the light-theme accent) · **B3** state
|
||||
layers (hover, pressed) · **B4** the page-chrome model (a sticky band vs controls in the content, folding on
|
||||
a short screen, the page `h1`) · **B5** the surface and radius per role (card, dialog, menu, tooltip) · **B6**
|
||||
the public watch page · **B7** the nav rail · **B8** the loading form (spinner vs skeleton) · **B9** where
|
||||
the header + fade edge goes.
|
||||
- **Surface per role (B5):** a dialog or panel is `.glass` r16; a card or picker `.glass-card` r12; a menu
|
||||
`.glass-menu` r12 with 4px padding and r8 items (12 − 4, so the corners nest); a tooltip `.glass-menu` r8.
|
||||
Translucency comes only from the tiers (the app backdrop, Plex media, light); a per-site alpha override is a
|
||||
violation.
|
||||
- **Page chrome (B4):** a page's tabs and filters sit in ONE sticky band of the page colour, with the fade
|
||||
under it (below). Below ~600px of height the filter row folds into one "Filters · n" button. Every page has
|
||||
a visually hidden `h1`; the nav rail and the browser tab title are the visible identity. [WCAG 2.4.6]
|
||||
- **The header + fade edge (B9):** a crisp band and a 1rem gradient of the colour it sits on, so content
|
||||
dissolves as it slides under. It goes on sticky toolbars and table headers, tabs over a scrolling panel, a
|
||||
long dialog's header and footer, and the clipped ends of an overflowing chip row. Not on thumbnails (they
|
||||
are the content) and not on buttons (it becomes a dated gloss).
|
||||
- **Nav rail (B7):** a `NavRail` primitive, slim: 3.75rem wide. "You are here" is a soft accent tint plus a
|
||||
3px edge bar. Labels under the icons are optional, a per-user switch in Settings (off by default); they add
|
||||
height, never width, so each module has a short rail label that fits, and the full name stays in the
|
||||
tooltip. No wide padding around the icons.
|
||||
- **Loading (B8):** a first load of a grid, list or table shows its skeleton (a `Skeleton` primitive), so
|
||||
nothing jumps when the data lands; a control's own work shows its spinner (`loading`). Neither appears
|
||||
before ~300ms. [Nielsen #1]
|
||||
- **The public watch page (B6)** follows these same rules (glass, `Button`, the pressed-pill toggle, the
|
||||
scheme's backdrop); only the video keeps the browser's own controls, because a stranger's device is
|
||||
unknown.
|
||||
|
||||
Already in force:
|
||||
|
||||
@@ -134,7 +160,7 @@ Which actions are visible and which live in a menu.
|
||||
(watched, saved, downloading). **What goes into the menu:** rare, secondary and destructive actions.
|
||||
Destructive ones keep their confirmation. [Nielsen #8, #5]
|
||||
- **On touch, a card shows fewer actions and a "…" button.** The rest are in the menu. _Which actions stay
|
||||
visible per object is part of batch C._
|
||||
visible per object is the classification table, **Pending review (C1)**._
|
||||
- **Background menu:** a right-click on EMPTY content area (not over text, inputs, links or media) may open
|
||||
a page menu. It only mirrors page-scope actions that already exist in the toolbar (sort, view, quick
|
||||
filters, select all); it adds no new function. Shift+right-click falls through to the browser's own menu.
|
||||
@@ -167,12 +193,17 @@ Wherever there is text, the user can select it and copy it, and no click action
|
||||
An error sits next to what failed and says how to recover; a field error is under its field and linked
|
||||
with `aria-describedby`. Colour is never the only signal. [Nielsen #9, WCAG 3.3.1, 1.4.1]
|
||||
- **Loading:** anything slower than ~300ms shows a loading state; a near-instant load doesn't flash one.
|
||||
Space is reserved for late content so the layout doesn't jump. _Spinner vs skeleton: decision B8._
|
||||
Space is reserved for late content so the layout doesn't jump; the form is under Surfaces (B8).
|
||||
- **Reordering:** anything you can drag also has a button or menu way to move it (Top / Up / Down / Bottom),
|
||||
and a drag handle carries `touch-action: none` so a finger drags instead of scrolling. [WCAG 2.5.7]
|
||||
- **Motion:** 150–300ms micro-interactions, transform/opacity only; `prefers-reduced-motion` is respected.
|
||||
[WCAG 2.3.3]
|
||||
|
||||
## Dialogs — **Pending (C4)**
|
||||
|
||||
Where focus lands when a dialog opens (by purpose, APG), and whether the footer stays pinned while the body
|
||||
scrolls. Open in the lab.
|
||||
|
||||
## Copy
|
||||
|
||||
- Every user-facing string is in both Hungarian and English with zero key drift, and a user-facing error is
|
||||
@@ -190,15 +221,24 @@ Wherever there is text, the user can select it and copy it, and no click action
|
||||
|
||||
## Decision log
|
||||
|
||||
| Date | Decision | Chosen | Over | Why |
|
||||
| ---------- | --------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| 2026-09-24 | Device contract | Tablet portrait + landscape and up; phones out | A 1024px floor with a "too small" message | Portrait tablet use is real, and the app already renders at 820 portrait |
|
||||
| 2026-09-24 | Card actions on touch | Fewer visible, the rest in a "…" menu | 44px buttons that no longer fit the card | Six 44px targets need more width than a card has |
|
||||
| 2026-09-24 | Action placement | One menu with several triggers, never gesture-only, a desktop background menu that mirrors the toolbar | Visible buttons for everything | Fewer non-critical buttons on screen, without hiding any function |
|
||||
| 2026-09-24 | Text selection | Text stays selectable; a click that ends a selection does nothing | Click-to-play on titles | The user must be able to copy any text the app shows |
|
||||
| 2026-09-24 | A1 Button shape | r8 + a rung per context; glass is a Button variant | r12; a pill | Nested corners (r8 in r12 in r16); the pill already means "a filter or a choice" |
|
||||
| 2026-09-24 | A2 Icon buttons | Two sizes, r8; a round button only on media | One size; all round | A lone toolbar icon at the row size looks lost; round rows read as tags |
|
||||
| 2026-09-24 | A3 Touch size | 44 for every control primitive | 36 + gaps; Button only | One rule (HIG 44) instead of two sizes nobody remembers |
|
||||
| 2026-09-24 | A4 Input | Bordered, a 55% fg-into-bg edge (≥ 3:1) | Filled; today's look as a primitive | The only option that clears WCAG 1.4.11 without a heavy fill on glass |
|
||||
| 2026-09-24 | A5 Dropdown trigger | Pill in a toolbar, r8 in a form | Pill everywhere; r8 everywhere | Matches the neighbours in each context |
|
||||
| 2026-09-24 | A6 Checkbox, switch, slider | Native, accent-tinted, the row is the target | Drawn primitives | Free keyboard / screen-reader / forced-colours support; only 3 sites |
|
||||
| Date | Decision | Chosen | Over | Why |
|
||||
| ---------- | --------------------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 2026-09-24 | Device contract | Tablet portrait + landscape and up; phones out | A 1024px floor with a "too small" message | Portrait tablet use is real, and the app already renders at 820 portrait |
|
||||
| 2026-09-24 | Card actions on touch | Fewer visible, the rest in a "…" menu | 44px buttons that no longer fit the card | Six 44px targets need more width than a card has |
|
||||
| 2026-09-24 | Action placement | One menu with several triggers, never gesture-only, a desktop background menu that mirrors the toolbar | Visible buttons for everything | Fewer non-critical buttons on screen, without hiding any function |
|
||||
| 2026-09-24 | Text selection | Text stays selectable; a click that ends a selection does nothing | Click-to-play on titles | The user must be able to copy any text the app shows |
|
||||
| 2026-09-24 | A1 Button shape | r8 + a rung per context; glass is a Button variant | r12; a pill | Nested corners (r8 in r12 in r16); the pill already means "a filter or a choice" |
|
||||
| 2026-09-24 | A2 Icon buttons | Two sizes, r8; a round button only on media | One size; all round | A lone toolbar icon at the row size looks lost; round rows read as tags |
|
||||
| 2026-09-24 | A3 Touch size | 44 for every control primitive | 36 + gaps; Button only | One rule (HIG 44) instead of two sizes nobody remembers |
|
||||
| 2026-09-24 | A4 Input | Bordered, a 55% fg-into-bg edge (≥ 3:1) | Filled; today's look as a primitive | The only option that clears WCAG 1.4.11 without a heavy fill on glass |
|
||||
| 2026-09-24 | A5 Dropdown trigger | Pill in a toolbar, r8 in a form | Pill everywhere; r8 everywhere | Matches the neighbours in each context |
|
||||
| 2026-09-24 | A6 Checkbox, switch, slider | Native, accent-tinted, the row is the target | Drawn primitives | Free keyboard / screen-reader / forced-colours support; only 3 sites |
|
||||
| 2026-09-24 | B1 Density | Compact 32 / 28 / 24 by explicit height | Standard 38 / 32 / 26 | The owner's read of the lab; touch is covered separately (A3), and one toolbar row lines up at 28 |
|
||||
| 2026-09-24 | B2 Scheme set | Midnight, Forest→cyan, Slate→fuchsia, Matrix→lime | Today's six | The accent collided with danger (YouTube), warn (Slate) and success (Forest, Matrix); Midnight and Starship were one scheme twice; 4 of 6 light accents failed 4.5:1 |
|
||||
| 2026-09-24 | B3 State layers | Text colour at 10% / 16% over anything | Material's 8 / 12%; today's opacity + bg-card | Visible in every scheme and mode (≥ 1.2:1); today's row hover was 1.00:1 on a card |
|
||||
| 2026-09-24 | B4 Page chrome | One sticky band + fade; filters fold when short | A floating glass bar; everything scrolls | Tabs and filters always in reach, in one place; a keyboard-up tablet keeps its content |
|
||||
| 2026-09-24 | B5 Surfaces | One class + radius per role | Today's per-site mix | Nested corners and no per-site alpha drift |
|
||||
| 2026-09-24 | B6 /watch | The app's surfaces, native video controls | Flat and minimal | The page strangers see should look like the product; native controls work on any device |
|
||||
| 2026-09-24 | B7 Nav rail | Soft tint + edge bar, optional labels (a user setting), slim | Solid accent (recommended); a Material rail | The owner's combination: calmer than a solid fill, labels for who wants them, no width cost |
|
||||
| 2026-09-24 | B8 Loading | Skeleton by shape, spinner in a control | A spinner everywhere | Nothing jumps when data lands; a long wait doesn't look stuck |
|
||||
| 2026-09-24 | B9 Fade | Toolbars / headers, panel tabs, dialog edges, chip rows | Thumbnails; buttons | The north-star depth where content scrolls under chrome; not on content or small controls |
|
||||
|
||||
Reference in New Issue
Block a user