Desktop library pass: alignment, card weight, sidebar and theme

Layout (desktop / lg+ only):
- 40px top padding above the page title.
- Deck grid uses minmax(320px, 1fr), so two cards fill the content width
  and share a right edge with the header (same max-width container).
- Group headings only render once real folders exist; with none, a single
  quiet "All decks" label replaces "Unorganized".
- Import JSON becomes a plain text button (mobile keeps the pill).

Deck cards (desktop):
- The whole card is a link; Study is a compact secondary button instead
  of a full-width solid pill.
- The ... control is a 32px ghost button with a real ellipsis icon,
  aligned to the title's centre.
- Meta line always shows the same fields (activities · runs / Not played
  yet); the score is a separate quiet "Best 53%" stat.
- 12px rhythm between title/meta/tags/summary, 16px before the actions.
- Hover: lighter surface, stronger border, soft shadow and a 2px lift in
  light mode (pointer devices only, so touch has no sticky hover).

Sidebar (desktop rail):
- All decks + Unorganized are views above the FOLDERS label; only
  user-created folders sit under it.
- Count only on All decks. The mobile drawer is unchanged.

Theme:
- Dark: sidebar is a step darker (#181818 to #141414 vs #212121 page).
- Light: cards get a slightly stronger border (#e2e2e2) and a soft
  shadow; the sidebar divider and edge shadow are stronger too.
- Medium difficulty darkened to #7a5300 (6.2:1 on the light chip).

Mobile is untouched: layout changes are behind lg: (or the desktop prop);
theme tweaks are shared tokens.

Verified: key-gate 49/49, install sheet 64/64, retry 14/14, zoom 6/6,
deck chrome 15/15, deck fixes 15/15, final ux 12/12, contrast 20/20,
security 8/8, pwa 16/16.
This commit is contained in:
Martin Dvořák 2026-10-07 22:37:29 +00:00
commit 3aae6f3199
5 changed files with 196 additions and 67 deletions

View file

@ -85,8 +85,11 @@
--sidebar: #f9f9f9;
--surface: #ffffff;
--surface-raised: #ffffff;
--card-hover-bg: #fafafa;
--surface-sunken: rgb(13 13 13 / 0.05);
--hairline: #e5e5e5;
--card-border: #e2e2e2;
--sidebar-divider: #e2e2e2;
--hairline-soft: rgb(13 13 13 / 0.07);
--glass-tint: rgb(255 255 255 / 0.6);
--text: #0d0d0d; /* 17.6:1 on #ffffff */
@ -95,7 +98,7 @@
--hover: rgb(13 13 13 / 0.05);
--sidebar-top: #fbfbfc;
--sidebar-bottom: #f6f6f7;
--sidebar-edge: 0 1px 0 var(--hairline), 6px 0 16px -14px rgb(13 13 13 / 0.28);
--sidebar-edge: 0 1px 0 var(--hairline), 6px 0 18px -12px rgb(13 13 13 / 0.38);
/* API-key gate: near-white stage with soft blank glows, tinted input. */
--gate-base: #f6f6f8;
--gate-glow-a: rgb(84 102 130 / 0.22);
@ -113,7 +116,8 @@
--install-chip: #f2f2f2;
--install-border: #e0e0e0;
--shadow-card: 0 1px 2px rgb(13 13 13 / 0.06), 0 1px 3px rgb(13 13 13 / 0.04);
--shadow-card-hover: 0 10px 28px -14px rgb(13 13 13 / 0.22), 0 1px 2px rgb(13 13 13 / 0.06);
--card-shadow: 0 1px 2px rgb(13 13 13 / 0.05), 0 5px 14px -6px rgb(13 13 13 / 0.12);
--shadow-card-hover: 0 12px 30px -12px rgb(13 13 13 / 0.2), 0 2px 6px rgb(13 13 13 / 0.07);
}
.dark {
@ -122,16 +126,19 @@
--sidebar: #171717;
--surface: #2f2f2f;
--surface-raised: #383838;
--card-hover-bg: #383838;
--surface-sunken: rgb(255 255 255 / 0.08);
--hairline: rgb(255 255 255 / 0.14);
--card-border: var(--hairline);
--sidebar-divider: var(--hairline);
--hairline-soft: rgb(255 255 255 / 0.08);
--glass-tint: rgb(255 255 255 / 0.08);
--text: #ececec; /* 12.9:1 on #212121 */
--text-muted: #b4b4b4; /* 7.6:1 */
--text-subtle: #a0a0a0; /* 6.0:1 */
--hover: rgb(255 255 255 / 0.07);
--sidebar-top: #1a1a1a;
--sidebar-bottom: #151515;
--sidebar-top: #181818;
--sidebar-bottom: #141414;
--sidebar-edge: 0 1px 0 var(--hairline), 6px 0 16px -14px rgb(0 0 0 / 0.75);
/* API-key gate: near-black stage, monochrome glows, raised input. */
--gate-base: #0f0f0f;
@ -150,6 +157,7 @@
--install-chip: #3a3a3c;
--install-border: rgb(255 255 255 / 0.08);
--shadow-card: 0 1px 2px rgb(0 0 0 / 0.25);
--card-shadow: var(--shadow-card);
--shadow-card-hover: 0 10px 28px -16px rgb(0 0 0 / 0.55), 0 1px 2px rgb(0 0 0 / 0.3);
}
@ -316,18 +324,24 @@ body {
.card {
border-radius: 1.125rem; /* 18px */
background: var(--surface);
border: 1px solid var(--hairline);
box-shadow: var(--shadow-card);
border: 1px solid var(--card-border);
box-shadow: var(--card-shadow);
}
/* Opt-in hover lift for interactive cards (deck cards) */
/* Opt-in hover lift for interactive cards (deck cards). Pointer devices only,
so touch screens never get sticky hover states. */
.card-hover {
transition: box-shadow 180ms ease, border-color 180ms ease, transform 180ms ease;
transition: background-color 180ms ease, box-shadow 180ms ease, border-color 180ms ease, transform 180ms ease;
}
.card-hover:hover {
background: var(--surface-raised);
border-color: color-mix(in oklab, var(--hairline) 55%, var(--text) 18%);
box-shadow: var(--shadow-card-hover);
@media (hover: hover) and (pointer: fine) {
.card-hover:hover {
background: var(--card-hover-bg);
border-color: color-mix(in oklab, var(--hairline) 45%, var(--text) 20%);
box-shadow: var(--shadow-card-hover);
}
:root:not(.dark) .card-hover:hover {
transform: translateY(-2px);
}
}
.card-hover:active {
transform: translateY(0);
@ -409,7 +423,7 @@ body {
/* Sidebar surface: a touch darker than the page, with a real edge. */
.sidebar-surface {
background: linear-gradient(180deg, var(--sidebar-top), var(--sidebar-bottom));
border-right: 1px solid var(--hairline);
border-right: 1px solid var(--sidebar-divider);
box-shadow: var(--sidebar-edge);
}
@ -488,7 +502,7 @@ body {
/* Difficulty: neutral pill, colour lives in the text only. */
.diff-easy { color: #16714f; }
.diff-medium { color: #8a5e00; }
.diff-medium { color: #7a5300; }
.diff-hard { color: #b4342f; }
.dark .diff-easy { color: #6fd3a8; }
.dark .diff-medium { color: #e3b341; }