Cohesive styling pass: design tokens, shared primitives, responsive groundwork

The UI had a good terracotta-on-dark system that wasn't applied
consistently — magic-number spacing, ad-hoc page headers, ~6 copied
panel surfaces, ~8 drifting label variants, and scattered accent use.

- Tokens: warm the neutral ramp, add spacing/radius/type/motion scales,
  --accent-quiet + panel highlight. Terracotta hue unchanged.
- Primitives: .panel, .section-label, .page-header/.toolbar — collapse
  the copied surfaces and label variants onto shared classes.
- Unify each filter-heavy page's display controls (group/sort/view,
  select-all, count) into a workspace toolbar inside the filter panel,
  so they read as one surface instead of floating above it.
- Labels: sentence-case, higher-contrast form labels; uppercase section
  markers bumped faint -> muted.
- Accent hierarchy: reserve full terracotta for primary actions/active
  state; demote decorative labels and the group-header underline.
- Responsive groundwork: reflow classes for the deck two-pane and nav
  rail (stacks / icon-collapse at breakpoints), touch-target sizing.
  Desktop unchanged in kind.
This commit is contained in:
2026-07-22 23:44:21 -05:00
parent ecb946328a
commit bde9cf8a98
6 changed files with 361 additions and 142 deletions

View File

@@ -399,34 +399,12 @@ export function LibraryBrowser({
return (
<div>
<div className="row" style={{ justifyContent: "space-between", marginBottom: 12 }}>
<div className="row" style={{ gap: 14 }}>
<label className="row" style={{ gap: 6, cursor: "pointer" }} title="Select all filtered cards">
<input
ref={selectAllRef}
type="checkbox"
checked={rows.length > 0 && selected.size === rows.length}
onChange={toggleSelectAll}
disabled={rows.length === 0}
/>
<span className="muted" style={{ fontSize: 12 }}>
Select all
</span>
</label>
<h2 style={{ margin: 0 }}>
Library <span className="badge">{rows.length}</span>
</h2>
</div>
<div className="row" style={{ gap: 16 }}>
<div className="page-header">
<h2 className="page-header-title">
Library <span className="badge">{rows.length}</span>
</h2>
<div className="page-header-actions">
<button onClick={pickImportFile}>Import cards…</button>
<GroupBySelect value={groupBy} onChange={setGroupBy} />
<SortBySelect
value={sortBy}
dir={sortDir}
onChangeValue={setSortBy}
onToggleDir={() => setSortDir(sortDir === "asc" ? "desc" : "asc")}
/>
<ViewToggle mode={view} onChange={setView} />
</div>
</div>
@@ -538,7 +516,8 @@ export function LibraryBrowser({
/>
)}
<div className="filters">
<div className="panel workspace">
<div className="filters">
<div className="field">
<label>Name contains</label>
<input value={text} onChange={(e) => setText(e.target.value)} placeholder="e.g. bolt" />
@@ -620,6 +599,32 @@ export function LibraryBrowser({
placeholder="e.g. landfall"
/>
</div>
</div>
<div className="workspace-bar">
<label className="workspace-bar-check" title="Select all filtered cards">
<input
ref={selectAllRef}
type="checkbox"
checked={rows.length > 0 && selected.size === rows.length}
onChange={toggleSelectAll}
disabled={rows.length === 0}
/>
Select all
</label>
<span className="workspace-bar-count">
{rows.length} card{rows.length === 1 ? "" : "s"}
</span>
<span className="spacer" />
<GroupBySelect value={groupBy} onChange={setGroupBy} />
<SortBySelect
value={sortBy}
dir={sortDir}
onChangeValue={setSortBy}
onToggleDir={() => setSortDir(sortDir === "asc" ? "desc" : "asc")}
/>
<ViewToggle mode={view} onChange={setView} />
</div>
</div>
{rows.length === 0 ? (