/* ============================================================
   hub.css — kunzhub
   Aesthetic: engineering journal × mission-control terminal,
   DAY MODE. Field journal opened in the morning light. Warm
   cream paper, ivory cards that pop, deep ink type, sharper
   copper accents calibrated for daylight.

   Tokens are kept under their historical names (--hub-obsidian,
   --hub-vellum) but the *values* are the light-mode mapping.
   Read each as an opaque token: -obsidian = primary surface,
   -vellum = primary text, etc.
   ============================================================ */

:root {
  /* Surfaces — warm paper layer */
  --hub-obsidian:   #EFE6D0;   /* primary bg — warm cream */
  --hub-obsidian-2: #FBF6E8;   /* elevated card surface — ivory */
  --hub-obsidian-3: #F4ECD4;   /* sunken / inset paper */

  /* Type — deep ink */
  --hub-vellum:     #1A1410;   /* primary text — warm black */
  --hub-vellum-dim: #3D2F22;   /* secondary text */

  /* Accents — sharper for light bg */
  --hub-leather:    #8B7758;   /* warm tan, used in rules + secondary */
  --hub-copper:     #B85A2E;   /* punctuation accent — sharper for day */
  --hub-copper-hot: #D67340;   /* hover/focus accent */

  /* Ink (darkest available — used for headings, true blacks) */
  --hub-ink:        #0E0905;
  --hub-ink-dim:    #786650;   /* tertiary text / whisper */

  /* Hairlines */
  --hub-rule:       rgba(26, 20, 16, 0.10);
  --hub-rule-strong:rgba(26, 20, 16, 0.22);

  /* Type */
  --font-display: 'IM Fell English', 'EB Garamond', Georgia, serif;
  --font-body:    'Inter', system-ui, -apple-system, sans-serif;
  --font-mono:    'JetBrains Mono', ui-monospace, 'SF Mono', monospace;

  /* Geometry */
  --grid: 32px;
  --radius: 3px;
  --col-max: 1320px;

  /* Curves */
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);

  /* Doc-class palette (mirrors build.py CATEGORY_ACCENTS) */
  --c-career:     #B49A75;
  --c-creative:   #8b0000;
  --c-data:       #2a5a8f;
  --c-tools:      #4a7048;
  --c-explainers: #6f4a8f;
  --c-hardware:   #D17A3A;
  --c-family:     #e91e8c;
}

/* private volume: warmer paper + slightly more saturated rust accents */
body[data-visibility="private"] {
  --hub-obsidian:   #ECDFBE;   /* slightly warmer/redder cream */
  --hub-obsidian-2: #FAF3DF;
  --hub-obsidian-3: #F1E5BF;
  --hub-copper:     #A8421F;   /* deeper rust */
  --hub-copper-hot: #C5552C;
  --hub-rule:       rgba(168, 66, 31, 0.16);
  --hub-rule-strong:rgba(168, 66, 31, 0.34);
}

/* ── Reset ─────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  background: var(--hub-obsidian);
  color: var(--hub-vellum);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  min-height: 100vh;
  font-feature-settings: 'ss01' on, 'cv11' on;
  -webkit-font-smoothing: antialiased;
  /* push the page-top border down 32px so its 4px line is centered on
     the first hex row at y=34 */
  padding-top: 32px;
  /* honeycomb — ink-faint hexagons on warm paper. Single layer: major
     hexes (tile 60×102), 2px stroke, ~5.5% ink. The major-only pattern
     stays calm enough to be texture without the second-tier "noise"
     of a sub-grid. The thicker page-divider lines (4px) provide the
     contrasting structural weight. */
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 102' width='60' height='102'><g fill='none' stroke='%231a1410' stroke-opacity='0.055' stroke-width='2'><path d='M 30 0 L 60 17 L 60 51 L 30 68 L 0 51 L 0 17 Z'/><path d='M 60 51 L 90 68 L 90 102 L 60 119 L 30 102 L 30 68 Z'/></g></svg>");
  background-size: 60px 102px;
  /* hex-row centers occur every 51px. We want every page-structural
     horizontal line (page top, masthead-rule, status-bar borders, all
     section-dividers, footer bottom) to land on a hex-row center so
     each line visually 'cuts through' the middle of a hex row.
     Background scrolls with the page so the alignment holds at every
     scroll position. The bg-position-y is computed so that the very
     first hex row center sits at y=34 (matching the natural tile). */
  background-position: 0 0;
  background-attachment: scroll;
  overflow-x: hidden;
}

/* paper grain + soft warm vignette (day light through a window) */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  background-image:
    radial-gradient(ellipse at 50% 0%, rgba(184, 90, 46, 0.05), transparent 55%),
    radial-gradient(ellipse at 100% 100%, rgba(139, 119, 88, 0.06), transparent 60%),
    url("data:image/svg+xml;utf8,<svg viewBox='0 0 220 220' xmlns='http://www.w3.org/2000/svg'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.4   0 0 0 0 0.32   0 0 0 0 0.22   0 0 0 0.35 0'/></filter><rect width='220' height='220' filter='url(%23n)' opacity='0.55'/></svg>");
  background-blend-mode: multiply;
  opacity: 0.5;
  mix-blend-mode: multiply;
}

a { color: inherit; text-decoration: none; }
em { font-style: italic; }
.mono { font-family: var(--font-mono); }
kbd {
  font-family: var(--font-mono);
  font-size: 0.78em;
  padding: 1px 5px;
  border: 1px solid var(--hub-rule);
  border-bottom-width: 2px;
  border-radius: 3px;
  color: var(--hub-vellum);
  background: var(--hub-obsidian-2);
}

/* ── Layout container ──────────────────────────────────────── */
body > * { position: relative; z-index: 2; }
.masthead, .section-divider, .cards, .empty, .footer {
  max-width: var(--col-max);
  margin-left: auto;
  margin-right: auto;
  padding-left: clamp(1.25rem, 4vw, 3rem);
  padding-right: clamp(1.25rem, 4vw, 3rem);
}

/* ── Masthead ──────────────────────────────────────────────── */
.masthead {
  /* 19px lifts masthead-rule border-bottom center to y=85 (= 34 + 51) */
  padding-top: 19px;
  padding-bottom: 2rem;
  border-top: 4px solid rgba(26, 20, 16, 0.18);  /* page-top edge, center y=34 */
}

.masthead-rule {
  display: flex;
  height: 32px;            /* lock to grid: bottom border lands at y=64 */
  box-sizing: border-box;
  align-items: center;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--hub-ink-dim);
  padding-bottom: 0.75rem; /* 12px */
  border-bottom: 4px solid rgba(26, 20, 16, 0.18); /* same weight as section dividers */
  margin-bottom: 1rem; /* 16px */
  opacity: 0;
  animation: fadeIn 0.45s var(--ease) 0.05s forwards;
}
.masthead-host::before { content: "❯ "; color: var(--hub-copper); }
.masthead-build { color: var(--hub-leather); }

.wordmark {
  font-family: var(--font-display);
  font-size: clamp(3rem, 9vw, 6.5rem);
  font-weight: 400;
  line-height: 0.92;
  letter-spacing: 0.04em;
  color: var(--hub-vellum);
  text-align: center;
  margin: 0.25rem 0 1rem;
}
.wm-ch {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.4em);
  animation: wmIn 0.55s var(--ease) calc(var(--i) * 0.045s + 0.1s) forwards;
}
@keyframes wmIn {
  to { opacity: 1; transform: translateY(0); }
}

.masthead-sub {
  font-family: var(--font-body);
  font-size: 0.95rem;
  text-align: center;
  color: var(--hub-vellum-dim);
  margin-bottom: 0;     /* tightened from 16px → 0 to shift status-bar up
                            onto hex-row center y=289 */
  opacity: 0;
  animation: fadeIn 0.5s var(--ease) 0.45s forwards;
}
.masthead-sub .vol {
  font-family: var(--font-display);
  font-style: italic;
  color: var(--hub-leather);
  font-size: 1.05rem;
}
.masthead-sub .vol-suffix { color: var(--hub-copper-hot); }
.masthead-sub .sep {
  margin: 0 0.6rem;
  color: var(--hub-ink-dim);
}
.masthead-sub em { color: var(--hub-vellum); }
.status-live {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.15em;
  color: var(--hub-copper-hot);
}
.status-live .dot {
  display: inline-block;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #44FF88;
  box-shadow: 0 0 0 0 rgba(68, 255, 136, 0.7);
  margin-right: 0.4rem;
  vertical-align: 1px;
  animation: pulseDot 1.6s ease-out infinite;
}
@keyframes pulseDot {
  0%   { box-shadow: 0 0 0 0 rgba(68, 255, 136, 0.55); }
  70%  { box-shadow: 0 0 0 9px rgba(68, 255, 136, 0); }
  100% { box-shadow: 0 0 0 0 rgba(68, 255, 136, 0); }
}

.status-bar {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem 0.85rem;
  align-items: baseline;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--hub-vellum-dim);
  letter-spacing: 0.05em;
  /* 0.78rem padding × 2 + 8px borders + ~14px text-line ≈ 51px total
     → status-bar height = one hex-step. Top border center lands on a
     hex-row center; bottom border center lands on the next one. */
  padding: 0.78rem 0;
  border-top: 4px solid rgba(26, 20, 16, 0.18);     /* center on hex-row */
  border-bottom: 4px solid rgba(26, 20, 16, 0.18);  /* center on next hex-row */
  opacity: 0;
  animation: fadeIn 0.5s var(--ease) 0.65s forwards;
}
.status-key { color: var(--hub-ink-dim); text-transform: uppercase; letter-spacing: 0.18em; }
.status-val { color: var(--hub-vellum); font-variant-numeric: tabular-nums; }
.status-val a { color: var(--hub-copper); }
.status-val a:hover { color: var(--hub-copper-hot); border-bottom: 1px solid var(--hub-copper-hot); }
.status-sep { color: var(--hub-rule-strong); }

/* ── Command line ──────────────────────────────────────────── */
.commandline {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 1.75rem auto 1rem;
  max-width: 720px;
  padding: 0.85rem 1rem;
  background: linear-gradient(180deg, var(--hub-obsidian-3) 0%, var(--hub-obsidian-2) 100%);
  border: 1px solid var(--hub-rule);
  border-radius: var(--radius);
  box-shadow:
    0 1px 0 rgba(255, 248, 230, 0.6) inset,
    0 8px 24px rgba(80, 60, 40, 0.14);
  position: relative;
  opacity: 0;
  animation: fadeIn 0.5s var(--ease) 0.55s forwards;
}
.commandline::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  background: linear-gradient(90deg, transparent, var(--hub-copper) 50%, transparent);
  opacity: 0;
  z-index: -1;
  transition: opacity 0.2s var(--ease);
}
.commandline:focus-within::before { opacity: 0.5; }
.commandline:focus-within {
  border-color: var(--hub-copper);
  box-shadow:
    0 0 0 3px rgba(209, 122, 58, 0.18),
    0 8px 24px rgba(80, 60, 40, 0.14);
}

.prompt {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--hub-copper);
  letter-spacing: 0.05em;
  user-select: none;
}
.search {
  flex: 1;
  background: transparent;
  border: 0;
  outline: 0;
  font-family: var(--font-mono);
  font-size: 0.95rem;
  color: var(--hub-vellum);
  caret-color: transparent; /* custom caret element handles it */
  padding: 0;
}
.search::placeholder { color: var(--hub-ink-dim); font-style: italic; }
.caret {
  font-family: var(--font-mono);
  color: var(--hub-copper);
  /* Slow, human-paced breathe (~3s/breath). Each inhale reveals the next
     category color; the color morph happens during the dim exhale so the
     swap is never a hard flash. 8 colors → 24s full loop. */
  animation: caretBreathe 24s ease-in-out infinite;
  will-change: opacity, color;
  user-select: none;
}
.commandline:focus-within .caret { display: none; }
.search:not(:placeholder-shown) + .caret { display: none; }
@keyframes caretBreathe {
  0%     { opacity: 0.28; color: #6F5A3A; } /* Career   */
  6.25%  { opacity: 1;    color: #6F5A3A; }
  12.5%  { opacity: 0.28; color: #8b0000; } /* Creative */
  18.75% { opacity: 1;    color: #8b0000; }
  25%    { opacity: 0.28; color: #2a5a8f; } /* Data     */
  31.25% { opacity: 1;    color: #2a5a8f; }
  37.5%  { opacity: 0.28; color: #4a7048; } /* Tools    */
  43.75% { opacity: 1;    color: #4a7048; }
  50%    { opacity: 0.28; color: #6f4a8f; } /* Explainers */
  56.25% { opacity: 1;    color: #6f4a8f; }
  62.5%  { opacity: 0.28; color: #A85A1F; } /* Hardware */
  68.75% { opacity: 1;    color: #A85A1F; }
  75%    { opacity: 0.28; color: #c01875; } /* Family   */
  81.25% { opacity: 1;    color: #c01875; }
  87.5%  { opacity: 0.28; color: #1a7a6e; } /* Kenia    */
  93.75% { opacity: 1;    color: #1a7a6e; }
  100%   { opacity: 0.28; color: #6F5A3A; } /* back to Career */
}
.commandline-hint {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  color: var(--hub-ink-dim);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  display: none;
  align-items: center;
  gap: 0.25rem;
  white-space: nowrap;
}
@media (min-width: 760px) {
  .commandline-hint { display: inline-flex; }
}
.hint-key {
  cursor: help;
  border-bottom: 1px dotted var(--hub-rule);
  padding: 1px 2px;
  border-radius: 2px;
  outline: none;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease), background 0.15s var(--ease);
}
.hint-key:hover,
.hint-key:focus-visible {
  color: var(--hub-copper);
  border-bottom-color: var(--hub-copper);
  background: rgba(209, 122, 58, 0.08);
}
.hint-sep {
  color: var(--hub-rule-strong);
  user-select: none;
}

/* ── Lock badge: prominent public/private indicator ───────── */
.lock-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0 auto 0;                          /* lock-badge sits flush — needed
                                                to drop status-bar onto hex y=289 */
  padding: 0.45rem 1.1rem 0.5rem 0.85rem;
  border: 1.5px solid var(--hub-rule-strong);
  border-radius: 999px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--hub-vellum) 4%, transparent),
    color-mix(in srgb, var(--hub-vellum) 0%, transparent));
  width: fit-content;
  position: relative;
  /* Animate the masthead fade-in stack — appears with the sub-tagline */
  opacity: 0;
  animation: fadeIn 0.5s var(--ease) 0.55s forwards;
  transition: transform 0.2s var(--ease), border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.lock-badge:hover {
  transform: translateY(-1px);
  border-color: currentColor;
  box-shadow: 0 6px 18px color-mix(in srgb, currentColor 18%, transparent);
}
/* Center the badge in the masthead column */
.masthead .lock-badge {
  display: flex;
  margin-left: auto;
  margin-right: auto;
}

.lock-badge.is-open    { color: var(--hub-tools, #4a7048); }     /* green-ish accent */
.lock-badge.is-locked  { color: var(--hub-copper); }              /* warm warning accent */

.lock-icon {
  width: 1.65rem;
  height: 2rem;
  flex-shrink: 0;
  filter: drop-shadow(0 1px 0 rgba(0,0,0,0.05));
}

.lock-label {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: currentColor;
}

/* Soft pulse on the LOCKED variant so it reads as live-restricted */
.lock-badge.is-locked .lock-icon {
  animation: lockPulse 2.4s ease-in-out infinite;
}
@keyframes lockPulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50%      { transform: scale(1.04); opacity: 0.88; }
}

@media (max-width: 640px) {
  .lock-badge {
    padding: 0.4rem 0.9rem 0.45rem 0.7rem;
    gap: 0.45rem;
    margin: 0.15rem auto 0;        /* tightened to drop status-bar onto hex-row center y=289 */
  }
  .lock-icon { width: 1.4rem; height: 1.75rem; }
  .lock-label { font-size: 0.7rem; letter-spacing: 0.14em; }
}

/* ── Category chips ────────────────────────────────────────── */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  justify-content: center;
  margin-top: 0.5rem;
  opacity: 0;
  animation: fadeIn 0.45s var(--ease) 0.75s forwards;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.35rem 0.85rem 0.4rem;
  background: transparent;
  border: 1px solid var(--hub-rule);
  border-radius: 999px;
  color: var(--hub-vellum-dim);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  cursor: pointer;
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease), background 0.18s var(--ease);
}
.chip-dot {
  display: inline-block;
  width: 8px; height: 8px;
  background: var(--c);
  border-radius: 1px;
  transform: rotate(45deg);
  box-shadow: 0 0 0 1px rgba(26, 20, 16, 0.20) inset;
}
.chip:hover {
  color: var(--hub-vellum);
  border-color: var(--hub-leather);
}
.chip.is-active {
  color: var(--hub-obsidian-2);
  background: var(--hub-vellum);
  border-color: var(--hub-vellum);
}

/* ── Decorative section divider ───────────────────────────── */
.section-divider {
  position: relative;
  border: 0;
  height: 4px;           /* 2× the major hex stroke — clear "page divider" weight */
  background: rgba(26, 20, 16, 0.18);  /* slightly less alpha than 1px @ 0.10 perception */
  margin: 2.75rem auto 2rem; /* tuned so sd_0 lands on hex-row center y=544 */
  max-width: var(--col-max);
  border-radius: 2px;    /* soft pill caps on the bar itself */
}
.section-divider {
  overflow: visible; /* defend against UA default overflow:hidden on <hr> */
}
.section-divider::after {
  content: attr(data-label);
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 7px 1.25rem;
  background: var(--hub-obsidian);   /* day-mode = paper */
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--hub-ink);
  border: 1px solid var(--hub-leather);
  border-radius: 999px;
  box-shadow:
    0 1px 0 rgba(255, 248, 230, 0.5) inset,
    0 2px 6px rgba(80, 60, 40, 0.08);
}

/* ── Recently Added strip ─────────────────────────────────── */
.recents {
  max-width: var(--col-max);
  margin-left: auto;
  margin-right: auto;
  padding-left: clamp(1.25rem, 4vw, 3rem);
  padding-right: clamp(1.25rem, 4vw, 3rem);
  padding-bottom: 0.5rem;
}

/* Recents is hidden when a filter/search is active (managed via JS) */
.recents.is-hidden { display: none; }

.recents-divider {
  /* Each pill is ~32px tall and centered on its 1px line, so the pill
     spans ~16px above and below the line. To get clear visual separation
     between the 'REGISTRY · CONTENTS' and 'RECENTLY ADDED' pills, we
     need >=48px of breathing room between the two lines (16+16=32 for
     the pill heights alone, plus visual gap). 3.25rem = 52px. */
  /* tuned so recents-divider lands on hex-row center y=595 = next step
     after sd_0 at y=544 */
  margin-top: 2.95rem !important;
  margin-bottom: 1.25rem !important;
}

.recents-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem; /* 16 */
  padding-bottom: 1.5rem; /* 24 */
  border-bottom: 1px solid var(--hub-rule);
  margin-bottom: 1rem; /* 16 */
}

.recent-tile {
  --accent: var(--hub-leather);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.25rem; /* 4 */
  padding: 0.75rem 1rem; /* 12 / 16 — uniform, no top/bottom asymmetry */
  flex: 1 1 220px;
  min-width: 180px;
  max-width: 360px;
  background:
    linear-gradient(180deg, rgba(255, 250, 232, 0.5), rgba(80, 60, 40, 0.025)),
    var(--hub-obsidian-2);
  border: 1px solid var(--hub-rule);
  border-radius: var(--radius);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transform: scale(0.97);
  transition:
    transform 0.22s var(--ease),
    border-color 0.22s var(--ease),
    box-shadow 0.22s var(--ease);
  box-shadow: 0 1px 0 rgba(255, 248, 230, 0.6) inset, 0 2px 6px rgba(80, 60, 40, 0.10);
}

.recent-stripe {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: var(--accent);
  opacity: 0.85;
}

.recent-tile:hover {
  transform: scale(1) translateY(-2px);
  border-color: var(--accent);
  box-shadow:
    0 1px 0 rgba(255, 248, 230, 0.7) inset,
    0 10px 28px rgba(80, 60, 40, 0.18),
    0 0 0 1px color-mix(in srgb, var(--accent) 20%, transparent);
}

.recent-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding-top: 0.2rem;
}

.recent-serial {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  font-weight: 500;
  color: var(--hub-vellum-dim);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  transition: color 0.18s var(--ease);
}
.recent-tile:hover .recent-serial { color: var(--hub-copper); }

.recent-class {
  font-family: var(--font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  opacity: 0.8;
}

.recent-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.1rem;
  line-height: 1.2;
  color: var(--hub-vellum);
  letter-spacing: -0.005em;
  font-feature-settings: 'liga' on, 'kern' on;
  text-wrap: balance;
}

.recent-stamp {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  color: var(--hub-leather);
  text-transform: uppercase;
  margin-top: 0.15rem;
}

/* ── Keyboard nav: focus-visible ring on cards ─────────────── */
.card:focus-visible,
.recent-tile:focus-visible {
  outline: 2px solid var(--hub-copper);
  outline-offset: 3px;
  border-color: var(--hub-copper);
}

/* ── Cards (specimen entries) ─────────────────────────────── */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1rem; /* 16 — grid-aligned card gutters */
  padding-bottom: 1.25rem; /* 20px — tuned so COLOPHON divider lands on hex-row center */
}

.card {
  --accent: var(--hub-leather);
  position: relative;
  display: grid;
  grid-template-rows: auto auto auto 1fr auto;
  gap: 0.45rem;
  padding: 1rem 1.1rem 0.9rem;
  background:
    linear-gradient(180deg, rgba(255, 250, 232, 0.6), rgba(80, 60, 40, 0.03)),
    var(--hub-obsidian-2);
  border: 1px solid var(--hub-rule);
  border-radius: var(--radius);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  opacity: 0;
  transform: translateY(8px);
  /* clamp stagger: when registry grows past ~25 entries, cap the per-card
     delay so the last card still appears within ~1.6s of page load */
  animation: cardIn 0.5s var(--ease) calc(min(var(--i, 0), 24) * 0.04s + 0.85s) forwards;
  transition:
    transform 0.25s var(--ease),
    border-color 0.25s var(--ease),
    box-shadow 0.25s var(--ease);
  box-shadow: 0 1px 0 rgba(255, 248, 230, 0.6) inset, 0 2px 8px rgba(80, 60, 40, 0.12);
}
@keyframes cardIn {
  to { opacity: 1; transform: translateY(0); }
}

.card-stripe {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--accent);
  opacity: 0.85;
}
.card::after { /* scan-line sweep on hover */
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(110deg,
    transparent 30%,
    color-mix(in srgb, var(--accent) 18%, transparent) 50%,
    transparent 70%);
  transform: translateX(-110%);
  opacity: 0;
  transition: transform 0.6s var(--ease), opacity 0.4s var(--ease);
}
.card:hover {
  transform: translateY(-3px);
  border-color: var(--accent);
  box-shadow:
    0 1px 0 rgba(255, 248, 230, 0.7) inset,
    0 14px 36px rgba(80, 60, 40, 0.22),
    0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent);
}
.card:hover::after {
  transform: translateX(110%);
  opacity: 1;
}
.card:hover .card-serial { color: var(--hub-copper); }
.card:hover .card-title { color: var(--hub-vellum); }
.card.is-hidden { display: none !important; }

/* Card head: serial + class */
.card-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding-top: 0.35rem;
  margin-bottom: 0.1rem;
}
.card-serial {
  font-family: var(--font-mono);
  font-size: 1.15rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--hub-vellum-dim);
  font-variant-numeric: tabular-nums;
  transition: color 0.2s var(--ease);
}
.card-class {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--hub-ink-dim);
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.card-class-mark {
  width: 6px; height: 6px;
  background: var(--accent);
  transform: rotate(45deg);
  box-shadow: 0 0 0 1px rgba(26, 20, 16, 0.18) inset;
}

.card-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.4rem;
  line-height: 1.15;
  color: var(--hub-vellum);
  margin-top: 0.35rem;
  letter-spacing: -0.005em;
  font-feature-settings: 'liga' on, 'dlig' on, 'kern' on;
  transition: color 0.2s var(--ease);
  text-wrap: balance; /* graceful degradation on older engines */
}
.card.is-external .card-title::after {
  content: "↗";
  display: inline-block;
  margin-left: 0.35rem;
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 0.75em;
  vertical-align: 0.15em;
  transition: transform 0.2s var(--ease);
}
.card.is-external:hover .card-title::after { transform: translate(3px, -3px); }

.card-desc {
  color: var(--hub-vellum-dim);
  font-size: 0.88rem;
  line-height: 1.5;
}

.card-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.6rem;
  padding-top: 0.55rem;
  border-top: 1px dashed var(--hub-rule);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  color: var(--hub-ink-dim);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.card-stamp { color: var(--hub-leather); }
.card-stamp .glyph-doc      { color: var(--accent); margin-right: 0.25rem; }
.card-stamp .glyph-external { color: var(--accent); margin-right: 0.25rem; }
.card-url {
  color: var(--hub-ink-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 60%;
  text-align: right;
}

/* private pulse — small live indicator dot */
.card-pulse {
  position: absolute;
  top: 0.7rem;
  right: 0.65rem;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: #44FF88;
  box-shadow: 0 0 8px rgba(68,255,136,0.7);
  animation: pulseDot 2.2s ease-out infinite;
}

/* Featured (first) card spans 2 columns and gets extra ornament */
.card.is-featured {
  --serial-size: 1.55rem;
}
.card.is-featured .card-serial { font-size: var(--serial-size); }
.card.is-featured .card-title { font-size: 1.85rem; }
.card.is-featured .card-stripe { height: 4px; }
.card.is-featured::before {
  content: "★ featured";
  position: absolute;
  top: 0.7rem;
  right: 0.85rem;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
}
.card.is-featured .card-head { padding-top: 0.6rem; }
@media (min-width: 920px) {
  .card.is-featured {
    grid-column: span 2;
  }
}

/* ── Empty state ──────────────────────────────────────────── */
.empty {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--hub-ink-dim);
  padding: 4rem 1rem;
}
.empty-line { margin: 0.4rem 0; }
.empty code {
  background: var(--hub-obsidian-2);
  padding: 0.2rem 0.45rem;
  border-radius: 3px;
  color: var(--hub-copper);
  border: 1px solid var(--hub-rule);
}

/* ── Footer / Colophon ────────────────────────────────────── */
.footer {
  /* tuned so COLOPHON divider lands on hex-row center y=2737 and the
     bottom page border center on y=2839. Both shift together with
     footer padding-top, so one knob tunes both. */
  padding-bottom: 2.75rem;
  padding-top: 0.45rem;
  border-bottom: 4px solid rgba(26, 20, 16, 0.18);  /* page-bottom edge */
}
.colophon {
  text-align: center;
  font-family: var(--font-body);
  font-size: 0.78rem;
  color: var(--hub-ink-dim);
}
.colophon em {
  font-family: var(--font-display);
  font-style: italic;
  color: var(--hub-leather);
}
.colophon .mono {
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  color: var(--hub-vellum-dim);
}

/* ── Global fade helper ───────────────────────────────────── */
@keyframes fadeIn { to { opacity: 1; } }

/* ── Reduced motion ───────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001s !important;
    animation-delay: 0s !important;
    /* Without this, `infinite` animations loop ~1000x/sec at the crushed
       duration above — a rapid flicker. Run them once and hold. */
    animation-iteration-count: 1 !important;
    transition-duration: 0.001s !important;
  }
  /* Steady, calm caret when motion is reduced — no breathe, no flicker. */
  .caret {
    animation: none !important;
    opacity: 1;
    color: var(--hub-copper);
  }
}

/* ── Mobile (≤640px — covers Samsung S24+ at 412dp) ───────── */
@media (max-width: 640px) {
  /* Tighter side gutters so 412dp screens don't feel cramped */
  .masthead, .section-divider, .cards, .empty, .footer, .recents {
    padding-left: 1rem;
    padding-right: 1rem;
  }

  /* Recents: side-scroll strip — keeps 3 tiles visible as a peek row */
  .recents-row {
    gap: 0.65rem;
    flex-wrap: nowrap;
    overflow-x: auto;
    /* hide scrollbar chrome but keep functional */
    scrollbar-width: none;
    -ms-overflow-style: none;
    /* add a touch of right padding so last tile doesn't bump the edge */
    padding-right: 0.5rem;
  }
  .recents-row::-webkit-scrollbar { display: none; }
  .recent-tile {
    flex: 0 0 auto;
    width: 180px;
    min-width: unset;
    padding: 0.6rem 0.9rem 0.55rem;
    transform: none; /* remove scale on mobile for sharper renders */
  }
  .recent-title { font-size: 1rem; }
  .recent-serial { font-size: 0.95rem; }

  /* Sub-tagline last plain span: also block like em, for consistent stacking */
  .masthead-sub > span:not(.vol):not(.sep):not(.status-live) { display: block; }

  .masthead { padding-top: 1.4rem; padding-bottom: 1.5rem; }
  .wordmark {
    font-size: clamp(2.4rem, 11vw, 4rem);
    letter-spacing: 0.03em;
  }

  /* Stack host + build vertically — they overrun the line on narrow screens */
  .masthead-rule {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.15rem;
    font-size: 0.6rem;
    padding-bottom: 0.65rem;
    margin-bottom: 1.1rem;
  }

  /* Sub-tagline: hide decorative slashes; let pieces wrap */
  .masthead-sub { line-height: 1.7; font-size: 0.88rem; }
  .masthead-sub .sep { display: none; }
  .masthead-sub em { display: block; margin: 0.1rem 0; }
  .masthead-sub .status-live { display: block; margin-top: 0.2rem; }

  /* Status bar: separators dangle when items wrap → hide them, use gaps */
  .status-bar {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 0.3rem 0.85rem;
    font-size: 0.68rem;
    padding: 0.55rem 0;
  }
  .status-bar .status-sep { display: none; }

  /* Command line */
  .commandline { padding: 0.7rem 0.85rem; margin-top: 1.25rem; }
  .commandline .prompt { font-size: 0.8rem; }
  .search { font-size: 0.9rem; }
  .commandline-hint { display: none; }

  /* Chips */
  .chips { gap: 0.35rem; margin-top: 0.4rem; }
  .chip {
    font-size: 0.66rem;
    padding: 0.3rem 0.7rem 0.32rem;
    gap: 0.35rem;
  }

  /* Section divider label — keep readable but slightly smaller */
  .section-divider::after {
    font-size: 0.6rem;
    padding: 0 0.6rem;
    letter-spacing: 0.18em;
  }

  /* Cards: single column, tighter padding, slightly smaller display type */
  .cards { grid-template-columns: 1fr; gap: 0.85rem; padding-bottom: 2rem; }
  .card { padding: 0.95rem 1rem 0.85rem; }
  .card.is-featured { grid-column: span 1; }
  .card.is-featured .card-title { font-size: 1.55rem; }
  .card-title { font-size: 1.25rem; }
  .card-serial { font-size: 1.05rem; }
  .card-class { font-size: 0.58rem; letter-spacing: 0.14em; }
  .card-foot { font-size: 0.62rem; letter-spacing: 0.06em; }
  .card-url { max-width: 55%; }

  /* Footer / colophon: wraps cleanly on small screens */
  .footer { padding-bottom: 2rem; }
  .colophon {
    font-size: 0.72rem;
    line-height: 1.8;
  }
}

/* ── S24+ / narrow phones (≤480px) — Samsung Galaxy S24+ is 412dp ──────── */
@media (max-width: 480px) {
  /* Side gutters: shave to 0.85rem so 412dp has breathing room */
  .masthead, .section-divider, .cards, .empty, .footer, .recents {
    padding-left: 0.85rem;
    padding-right: 0.85rem;
  }

  /* Masthead-rule: hide the build string entirely — the host
     name alone already fills the 412dp line */
  .masthead-build { display: none; }

  /* Status bar: further reduce font and row gap */
  .status-bar {
    font-size: 0.62rem;
    gap: 0.25rem 0.65rem;
    padding: 0.45rem 0;
  }

  /* Masthead-sub: slightly smaller on very narrow screens */
  .masthead-sub { font-size: 0.84rem; }

  /* Command line: just a touch tighter */
  .commandline { padding: 0.65rem 0.8rem; gap: 0.5rem; }

  /* Chips: slightly smaller text + tighter gaps at 412dp */
  .chips { gap: 0.28rem; }
  .chip {
    font-size: 0.63rem;
    padding: 0.28rem 0.6rem 0.3rem;
    gap: 0.3rem;
  }
  .chip-dot { width: 7px; height: 7px; }

  /* Section divider text: prevent overflow on 412dp */
  .section-divider::after {
    font-size: 0.56rem;
    letter-spacing: 0.14em;
    padding: 0 0.5rem;
    /* allow overflow clip rather than breaking the line */
    white-space: nowrap;
    overflow: hidden;
    max-width: calc(100vw - 2rem);
  }

  /* Cards: tighter padding */
  .card { padding: 0.85rem 1rem 0.8rem; }

  /* Card titles: slightly smaller at 412dp; balance for multi-line heads */
  .card-title {
    font-size: 1.2rem;
    text-wrap: balance;
  }
  .card.is-featured .card-title {
    font-size: 1.4rem;
    text-wrap: balance;
  }

  /* Card foot: url gets a bit more room on 412dp narrow width */
  .card-url { max-width: 52%; }

  /* Footer: minimal bottom padding */
  .footer { padding-bottom: 1.75rem; }
}

/* ── Ultra-narrow (≤380px) — older or compact phones ──────── */
@media (max-width: 380px) {
  .wordmark { font-size: 2.3rem; letter-spacing: 0.02em; }
  .masthead-sub { font-size: 0.82rem; }
  .card-title { font-size: 1.15rem; }
  .card.is-featured .card-title { font-size: 1.35rem; }
}
