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:
@@ -78,9 +78,11 @@ export function CardEntry() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Add Cards</h2>
|
||||
<div className="page-header">
|
||||
<h2 className="page-header-title">Add Cards</h2>
|
||||
</div>
|
||||
|
||||
<div className="field" style={{ maxWidth: 260, marginBottom: 12 }}>
|
||||
<div className="field" style={{ maxWidth: 260, marginBottom: "var(--space-3)" }}>
|
||||
<label>Location for added cards (optional)</label>
|
||||
<input
|
||||
placeholder="e.g. Box 1 — prefilled onto each card"
|
||||
@@ -102,7 +104,7 @@ export function CardEntry() {
|
||||
|
||||
{recent.length > 0 && (
|
||||
<>
|
||||
<h2 style={{ marginTop: 24 }}>Just added</h2>
|
||||
<h2 style={{ marginTop: "var(--space-6)" }}>Just added</h2>
|
||||
<ul className="result-list">
|
||||
{recent.map((r, i) => (
|
||||
<li key={i} style={{ cursor: "default" }}>
|
||||
|
||||
@@ -267,11 +267,11 @@ export function CardSearch({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="row" style={{ justifyContent: "space-between", marginBottom: 12 }}>
|
||||
<h2 style={{ margin: 0 }}>
|
||||
<div className="page-header">
|
||||
<h2 className="page-header-title">
|
||||
Card Search {hasActiveFilter && <span className="badge">{rows.length}</span>}
|
||||
</h2>
|
||||
<div className="row" style={{ gap: 16 }}>
|
||||
<div className="page-header-actions">
|
||||
<select
|
||||
value={deckId}
|
||||
onChange={(e) => setDeckId(e.target.value === "" ? "" : Number(e.target.value))}
|
||||
@@ -283,17 +283,10 @@ export function CardSearch({
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<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>
|
||||
|
||||
<div className="panel workspace">
|
||||
<div className="filters">
|
||||
<div className="field">
|
||||
<label>Name contains</label>
|
||||
@@ -357,6 +350,24 @@ export function CardSearch({
|
||||
placeholder="e.g. landfall"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="workspace-bar">
|
||||
{hasActiveFilter && (
|
||||
<span className="workspace-bar-count">
|
||||
{rows.length} result{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>
|
||||
|
||||
{!hasActiveFilter ? (
|
||||
|
||||
@@ -159,7 +159,7 @@ function CommanderHero({ card, onClear }: { card: DeckEntry; onClear: () => void
|
||||
const { handlers } = useHoverPreview();
|
||||
const { open } = useClickPreview();
|
||||
return (
|
||||
<div className="commander-hero">
|
||||
<div className="panel commander-hero">
|
||||
{card.image_uri ? (
|
||||
<img
|
||||
className="commander-hero-img"
|
||||
@@ -818,8 +818,8 @@ export function DeckView({ refreshSignal }: { refreshSignal?: number }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", gap: 20 }}>
|
||||
<div style={{ width: 240, flexShrink: 0 }}>
|
||||
<div className="deck-layout">
|
||||
<div className="deck-sidebar">
|
||||
<div className="row" style={{ justifyContent: "space-between" }}>
|
||||
<h2>Decks</h2>
|
||||
<IconButton icon={Icons.upload} label="Import deck…" onClick={pickImportFile} />
|
||||
@@ -925,7 +925,7 @@ export function DeckView({ refreshSignal }: { refreshSignal?: number }) {
|
||||
|
||||
{/* Deck metadata: format + (for Commander/Brawl) the commander card. */}
|
||||
<div className="deck-top-meta">
|
||||
<div className="deck-meta">
|
||||
<div className="panel deck-meta">
|
||||
<label className="row" style={{ gap: 6 }}>
|
||||
<span className="muted">Format</span>
|
||||
<select value={deck.format} onChange={(e) => changeFormat(e.target.value)}>
|
||||
@@ -958,8 +958,8 @@ export function DeckView({ refreshSignal }: { refreshSignal?: number }) {
|
||||
|
||||
{contents.length > 0 && (
|
||||
<div className="deck-top-stats">
|
||||
<div className="deck-stats">
|
||||
<div className="deck-stats-head">Deck analysis</div>
|
||||
<div className="panel deck-stats">
|
||||
<div className="deck-stats-head section-label">Deck analysis</div>
|
||||
<div className="deck-stats-body">
|
||||
<div className="deck-stats-numbers">
|
||||
<div className="stat-tiles-row">
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
Reference in New Issue
Block a user