/* ============================================================
   enrapt DESIGN SYSTEM — the single source of truth for brand surfaces,
   rhythm, motion and the reusable UI primitives every screen is built from.
   Loaded AFTER foundation.css (tokens) + shell.css, BEFORE the per-screen
   stylesheets, which should compose THESE classes instead of re-inventing
   cards/shadows/controls. DRY: change a primitive here, every screen follows.
   ============================================================ */

:root {
  /* Spacing scale (8pt-ish) — one rhythm everywhere. */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px; --s7: 48px;
  /* Type scale. */
  --fs-display: 30px; --fs-h2: 22px; --fs-h3: 16px; --fs-body: 14px; --fs-cap: 12px;
  --fs-figure: 30px; --fs-hero-figure: 46px;
  /* Brand gradients (one definition each). */
  --grad-accent: linear-gradient(180deg, var(--accent-2-strong), var(--accent-2));
  --grad-card: linear-gradient(180deg, color-mix(in srgb, var(--panel) 96%, var(--accent-2) 4%), var(--panel));
  --grad-card-lit: linear-gradient(180deg, var(--panel-2), var(--panel));
  /* One focus ring everywhere. */
  --focus-ring: 0 0 0 3px var(--accent-2-soft);
  /* Motion: --spring/--tap and the --dur-*/--ease-* ramp are defined ONCE in
     foundation.css (loaded first). Do NOT re-declare --spring here: a
     `--spring: var(--spring, ...)` self-reference forms a var() cycle, which the
     CSS spec makes invalid-at-computed-value-time, so --spring would resolve to
     EMPTY and every `transition: ... var(--spring)` would silently break. */
}

/* ---- Surfaces ---------------------------------------------------------- */
/* The one card. Soft elevation, a 1px top glass highlight, --radius rounding. */
.ds-card {
  position: relative;
  background: var(--grad-card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: var(--s5);
}
.ds-card::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 1px;
  border-radius: var(--radius) var(--radius) 0 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.07), transparent);
  pointer-events: none;
}
.ds-card-hero { box-shadow: var(--shadow-panel); }
/* A tappable card (resume / list item): lifts + accents on hover, presses down. */
.ds-tappable {
  cursor: pointer;
  transition: transform var(--spring), border-color var(--tap), box-shadow var(--tap), background var(--tap);
}
.ds-tappable:hover { transform: translateY(-2px); border-color: var(--accent-2); box-shadow: var(--shadow-panel); }
.ds-tappable:active { transform: translateY(0) scale(0.992); }
.ds-tappable:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* ---- Labels & figures -------------------------------------------------- */
.ds-eyebrow {
  font-size: var(--fs-cap); font-weight: 700; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--faint);
}
.ds-figure { font-size: var(--fs-figure); font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; line-height: 1; }
.ds-figure-accent { color: var(--accent-2-strong); }
.ds-figure-hero { font-size: var(--fs-hero-figure); font-weight: 800; letter-spacing: -0.02em; }
.ds-cap { font-size: var(--fs-cap); color: var(--muted); }

/* ---- Chip / badge ----------------------------------------------------- */
.ds-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px; border-radius: var(--radius-pill);
  background: var(--panel-2); border: 1px solid var(--border-soft);
  color: var(--muted); font-size: var(--fs-cap); font-weight: 600;
}
.ds-chip-accent { background: var(--accent-2-soft); border-color: transparent; color: var(--accent-2-strong); }

/* ---- Segmented control (Day/Week/Month, By day/By week …) -------------- */
.ds-seg {
  position: relative;
  display: inline-flex; padding: 3px; gap: 2px;
  background: var(--bg); border: 1px solid var(--border-soft);
  border-radius: var(--radius-pill);
}
/* The sliding indicator pill (js/screens/segmented.js positions it under the
   active segment; CSS animates the slide). Works for any segmented control —
   .ds-seg here, the stats Day/Week/Month + By day/week in panels.css. The active
   segment itself goes colour-only; the pill is the moving background. */
.seg-ind {
  position: absolute; top: 3px; bottom: 3px; left: 0; width: 0; z-index: 0;
  border-radius: var(--radius-pill);
  background: var(--elevate); box-shadow: var(--shadow-sm);
  transition: transform var(--spring), width var(--spring), opacity var(--tap);
  pointer-events: none;
}
.ds-seg > button, .ds-seg > .seg-item {
  position: relative; z-index: 1;
  padding: 6px 14px; border-radius: var(--radius-pill);
  color: var(--muted); font-size: 13px; font-weight: 600;
  transition: color var(--tap), transform var(--tap);
}
.ds-seg > button:hover { color: var(--text); }
.ds-seg > button:active { transform: scale(0.93); } /* tactile press */
.ds-seg > .active, .ds-seg > button.active { color: var(--accent-2-strong); }

/* ---- DsSheet — the one reusable summoned bottom sheet (js/ds/sheet.js)
   Same surface language as the Add / reader-settings sheets, content-agnostic, so
   any caller can summon it (the single-choice list is the first consumer). One
   dismiss model: backdrop · close · grip drag · Esc. --------------------------- */
.ds-sheet-backdrop {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0, 0, 0, 0.5);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-base, 240ms) var(--ease-standard, ease);
}
.ds-sheet-backdrop.open { opacity: 1; pointer-events: auto; }
.ds-sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 1001;
  max-height: 80dvh; overflow-y: auto; -webkit-overflow-scrolling: touch;
  background: var(--panel);
  border-top-left-radius: 22px; border-top-right-radius: 22px;
  border-top: 1px solid var(--border);
  box-shadow: 0 -18px 50px rgba(0, 0, 0, 0.55);
  padding: 10px 20px calc(22px + env(safe-area-inset-bottom));
  transform: translateY(110%); transition: transform var(--spring);
}
.ds-sheet.open { transform: translateY(0); }
/* Full-viewport cover: a drill-in summoned from inside another surface (reader
   font picker) — covers everything behind so nothing else shows or scrolls. */
.ds-sheet--full {
  top: 0; max-height: 100dvh; height: 100dvh;
  border-radius: 0; border-top: 0;
}
.ds-sheet .sheet-grip {
  position: relative; height: 26px; margin: -4px auto 4px;
  display: block; width: 100%; cursor: grab; touch-action: none;
  -webkit-tap-highlight-color: transparent;
}
.ds-sheet .sheet-grip:active { cursor: grabbing; }
.ds-sheet .sheet-grip::before {
  content: ""; position: absolute; left: 50%; top: 11px; transform: translateX(-50%);
  width: 40px; height: 4px; border-radius: 999px; background: var(--border);
}
.ds-sheet-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 10px;
}
.ds-sheet-title { margin: 0; font-size: 16px; font-weight: 650; color: var(--text); }

/* ---- DsChoice — single-choice list inside the sheet (js/ds/choice.js).
   Selectable rows with optional leading swatch + secondary description; the
   current value carries the check. Replaces the old enum segmented control. ---- */
.ds-choice-list { display: flex; flex-direction: column; gap: 2px; }
/* Group header for a sectioned choice list (e.g. font Serif / Sans-serif). */
.ds-choice-section {
  font-size: var(--fs-cap); font-weight: 700; letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--faint);
  padding: 12px 12px 4px;
}
.ds-choice-section:first-child { padding-top: 2px; }
.ds-choice-item {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 12px 12px; border-radius: var(--radius-sm);
  background: transparent; border: 1px solid transparent;
  color: var(--text); font: inherit; cursor: pointer;
  transition: background var(--tap), border-color var(--tap);
}
.ds-choice-item:hover { background: var(--elevate); }
.ds-choice-item:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-choice-item.selected { background: var(--panel-2); border-color: var(--border-soft); }
.ds-choice-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ds-choice-label { font-size: 15px; font-weight: 600; color: var(--text); }
.ds-choice-help { font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.ds-choice-check {
  flex: 0 0 auto; width: 18px; height: 18px; margin-right: 2px;
  opacity: 0; transition: opacity var(--tap);
}
.ds-choice-item.selected .ds-choice-check { opacity: 1; }
/* A drawn checkmark (no glyph dependency): rotated open corner of a box. */
.ds-choice-check::after {
  content: ""; display: block; width: 6px; height: 11px; margin: 1px auto 0;
  border-right: 2px solid var(--accent-2-strong); border-bottom: 2px solid var(--accent-2-strong);
  transform: rotate(45deg);
}
.ds-choice-extra { margin-top: 8px; padding-top: 10px; border-top: 1px solid var(--border-soft); }

/* ---- Theme swatch (js/theme/theme.js buildThemeSwatch): three stacked bands of
   a theme's real surface / panel / accent colours, read live from the cascade. -- */
.theme-swatch {
  flex: 0 0 auto; display: flex; width: 30px; height: 22px;
  border-radius: 6px; overflow: hidden; border: 1px solid var(--border-soft);
}
.ds-choice-swatch.theme-swatch { width: 34px; height: 26px; }
.theme-swatch-band { flex: 1 1 0; }

/* ---- DsStepper — the one numeric control (js/ds/stepper.js): a label + a
   −/value/＋ cluster on the header line, a full-width slider below. Wide-range
   values get all three inputs (drag · nudge · type). ------------------------- */
.ds-stepper { display: flex; flex-direction: column; gap: 6px; }
.ds-stepper-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.ds-stepper-labels { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ds-stepper-label { font-size: 14px; font-weight: 600; color: var(--text); }
/* Plain-language description so a number is never shown without saying what it does. */
.ds-stepper-hint { font-size: 12px; font-weight: 400; line-height: 1.3; color: var(--muted); }
.ds-stepper-cluster {
  display: inline-flex; align-items: center; gap: 2px;
  background: var(--bg); border: 1px solid var(--border-soft);
  border-radius: var(--radius-pill); padding: 2px;
}
.ds-stepper-btn {
  width: 36px; height: 36px; flex: 0 0 auto; border-radius: 50%;
  color: var(--accent-2-strong); background: transparent; border: 0;
  font-size: 19px; font-weight: 700; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  -webkit-tap-highlight-color: transparent; touch-action: none;
  transition: background var(--tap), transform var(--tap);
}
.ds-stepper-btn:hover { background: var(--elevate); }
.ds-stepper-btn:active { transform: scale(0.9); }
.ds-stepper-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-stepper-value {
  width: 58px; text-align: center; background: transparent; border: 0;
  color: var(--text); font: inherit; font-weight: 700; font-variant-numeric: tabular-nums;
  -moz-appearance: textfield; appearance: textfield;
}
.ds-stepper-value::-webkit-outer-spin-button,
.ds-stepper-value::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.ds-stepper-value:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 8px; }
.ds-stepper-slider { width: 100%; }

/* ============================================================
   DsButton — THE one canonical button (DESIGN-SYSTEM.md §3 / §5 P1).
   Variants: primary / secondary / quiet / destructive. Width: inline by
   default, full-width with .ds-btn--full. Every value is a token, so a control
   can't invent its own rhythm. This collapses the old near-duplicate primaries
   (.primary in foundation.css + .ds-btn-primary here) into ONE component; the
   legacy class names below are kept only as thin ALIASES of these rules so no
   call site visually regresses.

   The distinction between variants is STRUCTURAL (fill vs filled-pill vs text vs
   red), not just colour, so a screen reads as a designed hierarchy. Use ONE
   primary per view. Destructive uses the red `--accent` token (spec rule 5) so a
   delete/reset confirm is never visually identical to Cancel/secondary. ------ */
.ds-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  flex: 0 0 auto; min-height: 40px; padding: 10px 18px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  font: inherit; font-weight: 650; letter-spacing: 0.005em; cursor: pointer;
  transition: background var(--tap), border-color var(--tap), color var(--tap),
              transform var(--tap), box-shadow var(--tap);
}
.ds-btn:active { transform: translateY(1px); }
.ds-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-btn:disabled { cursor: not-allowed; opacity: .55; }
/* Width: full-width CTA (the old .ds-btn-primary default was 100%). */
.ds-btn--full { width: 100%; padding: 13px 18px; }

/* PRIMARY — the one gold action you want taken. */
.ds-btn--primary {
  background: var(--accent-2); color: var(--on-accent);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
.ds-btn--primary:hover { background: var(--accent-2-strong); }
/* SECONDARY — a quiet filled pill (subtle surface + hairline; lifts on hover).
   Amber is reserved for the primary action only. */
.ds-btn--secondary {
  background: var(--panel-2); color: var(--text); border-color: var(--border-soft);
  font-weight: 600;
}
.ds-btn--secondary:hover { background: var(--elevate); border-color: var(--border); }
/* QUIET — text-only, low emphasis; amber ink signals it's tappable. */
.ds-btn--quiet {
  background: none; color: var(--accent-2); font-weight: 650;
}
.ds-btn--quiet:hover { color: var(--accent-2-strong); background: var(--accent-2-soft); }
/* DESTRUCTIVE — the red tier (spec rule 5). For irreversible confirms (sign out,
   data reset). Reversible removes use execute+undo, not this. */
.ds-btn--destructive {
  background: var(--accent); color: var(--on-accent);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
.ds-btn--destructive:hover { filter: brightness(1.08); }
.ds-btn--destructive:focus-visible { box-shadow: 0 0 0 3px var(--danger-soft); }

@keyframes enrapt-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .62; } }
.ds-btn--primary:disabled { opacity: .9; cursor: progress; animation: enrapt-pulse 1.1s ease-in-out infinite; }

/* ---- Legacy aliases — same rules as the variants above, kept so existing
   call sites (#loadUrlBtn .primary, .ghost, .ds-btn-primary CTAs) keep their
   exact look while migration completes. New code uses .ds-btn .ds-btn--*. ---- */
.primary,
.ds-btn-primary {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  flex: 0 0 auto; min-height: 40px; padding: 10px 18px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  font: inherit; font-weight: 650; letter-spacing: 0.005em; cursor: pointer;
  background: var(--accent-2); color: var(--on-accent);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  transition: background var(--tap), border-color var(--tap), color var(--tap),
              transform var(--tap), box-shadow var(--tap);
}
.ds-btn-primary { width: 100%; padding: 13px 18px; }
.primary:hover, .ds-btn-primary:hover { background: var(--accent-2-strong); }
.primary:active, .ds-btn-primary:active { transform: translateY(1px); }
.ds-btn-ghost {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  flex: 0 0 auto; padding: 12px 16px; border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm); background: var(--panel-2); color: var(--text);
  font: inherit; font-weight: 600; cursor: pointer;
  transition: background var(--tap), border-color var(--tap);
}
.ds-btn-ghost:hover { background: var(--elevate); border-color: var(--border); }
.ds-btn-quiet {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  flex: 0 0 auto; padding: 10px 12px; border: none; background: none;
  border-radius: var(--radius-sm); color: var(--accent-2);
  font: inherit; font-weight: 650; cursor: pointer;
  transition: color var(--tap), background var(--tap);
}
.ds-btn-quiet:hover { color: var(--accent-2-strong); background: var(--accent-2-soft); }
.primary:focus-visible, .ds-btn-primary:focus-visible,
.ds-btn-ghost:focus-visible, .ds-btn-quiet:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-btn-primary:disabled { opacity: .9; cursor: progress; animation: enrapt-pulse 1.1s ease-in-out infinite; }

/* ============================================================
   DsIconButton — THE one canonical icon button (DESIGN-SYSTEM.md §3 / §5 P1).
   Consolidates the 5+ web variants the audit found (.cat-remove, .rd-del,
   .small-btn icon use, .lib-tile-remove, .panel-close, .lib-rail-heart) onto a
   single class with a >=44px HIT TARGET — the old 26-32px controls failed touch
   minimums. The visible glyph stays small (token --control-icon-sm); the 44px
   target is the tappable box, expanded past a small visual via padding so a tile
   corner badge still LOOKS 26-28px while being fully tappable.

   Aligns with the Android DsRemoveButton/EnraptCloseIcon model: stroked enrapt
   glyphs (ICONS.close / ICONS.check), token tint, red wash only on
   hover/press for destructive actions. The remove/Undo BEHAVIOUR (P0) is
   untouched; this is the button's visual/structural shell only. -------------- */
.ds-icon-btn {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  /* 44px minimum touch target (WCAG 2.5.5 / Apple HIG / Material). */
  min-width: 44px; min-height: 44px;
  padding: 0; border: none; background: transparent;
  border-radius: var(--radius-pill);
  color: var(--muted); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--tap), color var(--tap), opacity var(--tap), transform var(--tap);
}
.ds-icon-btn > svg { width: var(--control-icon-sm); height: var(--control-icon-sm); flex: 0 0 auto; }
.ds-icon-btn:hover { background: var(--elevate); color: var(--text); }
.ds-icon-btn:active { transform: scale(0.92); }
.ds-icon-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.ds-icon-btn:disabled { cursor: not-allowed; opacity: .45; }
/* Destructive icon action (remove / delete): red wash + red glyph on hover/press
   only — calm (muted) at rest. */
.ds-icon-btn--danger:hover, .ds-icon-btn--danger:active { background: var(--danger-soft); color: var(--accent); }

/* ---- Progress bar — SILVER (polished metal). Keeping progress off the gold
       accent means a screen isn't a wall of gold: gold = actions, silver =
       progress, white/silver = text, blue = surface. A real multi-tone palette
       (blue · gold · silver · black · white), not monochrome. -------------- */
.ds-track { height: 10px; border-radius: var(--radius-pill); background: var(--panel-2); overflow: hidden; box-shadow: inset 0 1px 2px rgba(0,0,0,0.45); }
.ds-track > .fill { height: 100%; border-radius: var(--radius-pill); background: linear-gradient(90deg, var(--progress-dim), var(--progress-light)); box-shadow: 0 0 10px rgba(182, 192, 208, 0.22); transition: width var(--spring); }

/* ---- Empty state (every screen needs one) ----------------------------- */
.ds-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--s3); padding: var(--s7) var(--s5);
  color: var(--muted);
}
.ds-empty .ds-empty-icon {
  width: 64px; height: 64px; border-radius: 20px;
  display: grid; place-items: center;
  background: var(--panel); border: 1px solid var(--border-soft);
  color: var(--accent-2); box-shadow: var(--shadow-sm);
}
.ds-empty .ds-empty-icon svg { width: 30px; height: 30px; }
.ds-empty .ds-empty-title { font-size: var(--fs-h3); font-weight: 650; color: var(--text); }
.ds-empty .ds-empty-sub { font-size: var(--fs-body); line-height: 1.5; max-width: 30ch; }

/* ---- Staggered entrance for lists/cards on screen show ----------------- */
@keyframes ds-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.screen.active .ds-rise { animation: ds-rise var(--spring) both; }

/* ---- DsSkeleton — the shared loading placeholder (DESIGN-SYSTEM.md §3) ---
   A quiet pulsing surface block that holds the space the real content will
   fill (no layout shift), instead of a spinner or a blank gap. Token-driven:
   the surface is a panel tier, the pulse rides the motion ramp. Subtle by
   design — it breathes between two near-identical dark surfaces, never a bright
   flash. prefers-reduced-motion (handled globally in foundation.css) stills the
   pulse to a static block. The shimmer is one keyframe so web + Android read
   the same. */
@keyframes ds-skeleton-pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
.ds-skeleton {
  background: var(--panel-2);
  border-radius: var(--radius-sm);
  /* Pulse is a multiple of the slow duration so it reads as a calm breath, not
     a flicker; both ends derive from the motion ramp (no magic seconds). */
  animation: ds-skeleton-pulse calc(var(--dur-slow) * 3) var(--ease-standard) infinite;
}
.ds-skeleton--line { height: 0.9em; }
.ds-skeleton--block { height: var(--control-size); }
.ds-skeleton--cover { aspect-ratio: 2 / 3; height: auto; border-radius: var(--radius); }
.ds-skeleton-group { display: flex; flex-direction: column; gap: var(--s3); width: 100%; }

/* ============================================================
   COVER ART (real <img> or a tasteful typographic fallback — never a rainbow
   "image of nothing") + the rich CATALOGUE ROW: a cover thumbnail beside the
   FULL written detail (title · author · progress · pct/words/time). cv-* markup
   comes from js/screens/cover.js; .cat-row from home.js / library.js.
   ============================================================ */
/* Real covers vary in aspect ratio, so show them at their NATURAL ratio — never
   crop (cropping cuts off the title). The container sizes to the image. */
.cv-img { width: 100%; height: auto; display: block; }
.cv-art.cv-has-img { aspect-ratio: auto; height: auto; }
.cv-art.cv-has-img::before { display: none; } /* no fake sheen over real art */
/* The DESIGNED fallback spine (no art): a series-tinted blue-black surface with
   a hairline accent band at the head, a quiet kind eyebrow, a typeset serif
   title and an optional series line at the foot. --cv-hue (set inline by
   cover.js, hashed off the series or title) only shifts the tint, so the spine
   stays on the brand's dark/silver/gold language — never a rainbow box. */
.cv-art.cv-fallback {
  --cv-hue: 220;
  display: flex; flex-direction: column;
  padding: 9px 9px 10px;
  background:
    linear-gradient(160deg,
      hsl(var(--cv-hue) 26% 20%),
      hsl(var(--cv-hue) 30% 12%) 62%,
      var(--panel));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}
/* A thin tinted band at the head of the spine — the "designed cover" cue. */
.cv-art.cv-fallback::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 4px; z-index: 1;
  background: linear-gradient(90deg,
    hsl(var(--cv-hue) 55% 58%), hsl(var(--cv-hue) 45% 42%));
  opacity: 0.85;
}
.cv-fb-kind {
  position: relative; z-index: 2;
  font-size: 7px; font-weight: 700; letter-spacing: 0.12em;
  color: rgba(255, 255, 255, 0.5); text-transform: uppercase;
}
.cv-fb-title {
  position: relative; z-index: 2;
  margin-top: auto;
  font-family: "Literata", Georgia, serif; font-size: 11px; font-weight: 600;
  color: rgba(246, 248, 252, 0.92); line-height: 1.16;
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.5);
}
.cv-fb-series {
  position: relative; z-index: 2;
  margin-top: 4px;
  font-size: 8.5px; font-weight: 600; letter-spacing: 0.02em;
  color: rgba(255, 255, 255, 0.52);
  font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* The rich row — cover thumbnail + the detail block. */
.cat-row {
  display: flex; align-items: center; gap: 13px;
  width: 100%; text-align: left; cursor: pointer;
  padding: 10px 12px; border-radius: var(--radius);
  background: var(--panel); border: 1px solid var(--border-soft);
  transition: background var(--tap), border-color var(--tap), transform var(--tap);
  -webkit-tap-highlight-color: transparent;
}
.cat-row:hover { background: var(--panel-2); border-color: var(--border); }
.cat-row:active { transform: scale(0.992); }
.cat-row:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.cat-row .cv-art { flex: 0 0 46px; width: 46px; border-radius: 7px; box-shadow: 0 3px 9px rgba(0, 0, 0, 0.42); }
.cat-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.cat-row-head { display: flex; align-items: baseline; gap: 8px; }
.cat-title { flex: 1; min-width: 0; font-size: 15px; font-weight: 600; color: var(--text); line-height: 1.25; letter-spacing: -0.005em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-kind { flex: 0 0 auto; font-size: 9px; font-weight: 700; letter-spacing: 0.1em; color: var(--faint); }
.cat-author { font-size: 12.5px; font-weight: 500; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 1px; }
.cat-track { height: 5px; margin-top: 7px; }
.cat-meta { font-size: 11.5px; font-weight: 500; color: var(--faint); font-variant-numeric: tabular-nums; letter-spacing: 0.01em; margin-top: 5px; }

/* Cloud mark on a catalogue row / hero: this book is stored in the cloud and
   opens on any device (restored from the old recently-read list). */
.cat-cloud { flex: 0 0 auto; align-self: center; display: inline-flex; align-items: center; color: var(--muted); opacity: 0.85; margin-left: 2px; }
.cat-cloud svg { width: 13px; height: 13px; }
/* Slashed variant: the file is NOT in the cloud yet. Quieter than the in-cloud
   mark (faint gray, never red — it's information, not an alarm). */
.cat-cloud-off { color: var(--faint); opacity: 0.7; }

/* Remove-from-library control on a catalogue row — now a DsIconButton
   (.ds-icon-btn .ds-icon-btn--danger in markup). Visible on touch (no hover);
   hover-revealed on desktop. Only the context-specific bits (alignment, the
   hover-reveal opacity, rest tint) live here; size/box/44px come from
   .ds-icon-btn. */
.cat-remove { align-self: center; margin-left: 2px; color: var(--faint); opacity: 0.5; }
.cat-remove:hover, .cat-remove:active { opacity: 1; }
@media (hover: hover) {
  .cat-remove { opacity: 0; }
  .cat-row:hover .cat-remove, .cat-row:focus-within .cat-remove { opacity: 0.7; }
  .cat-row .cat-remove:hover { opacity: 1; }
}
