/* ============================================================
   DCC COMPENDIUM — THEME
   Dark dungeon stone + cosmic game-show HUD.
   Phone-first: base styles are the ~360px layout; media
   queries scale UP.
   ============================================================ */

:root{
  --bg:#0c0a09;          /* dungeon black */
  --bg-1:#151210;        /* card stone */
  --bg-2:#1e1a16;        /* raised stone */
  --line:#2e2822;        /* hairline borders */
  --ink:#ece5da;         /* body text */
  --ink-dim:#a89d8e;     /* secondary text */
  --ink-faint:#6e6558;   /* meta text */
  --gold:#e8a33d;        /* ember gold — primary accent */
  --gold-soft:rgba(255,106,43,.14);
  --red:#d4483e;         /* blood red — danger/dead */
  --red-soft:rgba(255,59,48,.14);
  --sys:#5bc4f0;         /* system-message blue */
  --sys-soft:rgba(43,217,232,.12);
  --green:#69b86e;       /* alive */
  --green-soft:rgba(62,224,138,.14);
  --radius:12px;
  --font-brand:'Bungee',sans-serif;
  --font-head:'Rajdhani',sans-serif;
  --font-body:'Inter',system-ui,sans-serif;
  --font-mono:ui-monospace,'Cascadia Mono','Segoe UI Mono',Consolas,monospace;
}

*{margin:0;padding:0;box-sizing:border-box}
/* Vue mounts client-side with no build step, so without this the raw
   uncompiled template (every v-if branch, literal {{ }} bindings) flashes
   for a beat before Vue takes over. This is the entire fix for that. */
[v-cloak]{display:none}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;color-scheme:dark}
body{
  background:var(--bg);
  color:var(--ink);
  font-family:var(--font-body);
  font-size:15px;
  line-height:1.6;
  min-height:100vh;
}
img{width:100%;max-width:100%;display:block}
/* lazy images fade in once actually loaded instead of popping in mid-scroll */
img[loading="lazy"]{opacity:0;transition:opacity .35s ease}
img[loading="lazy"].loaded{opacity:1}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
/* Strip every browser's native focus/active/appearance chrome
   unconditionally, then redraw our own themed ring ourselves, gated on
   html.kb-nav (set by JS in app.js only from real Tab-key use, cleared on
   any mouse/touch). Browsers do NOT agree on when their own default ring
   should appear on a click -- Chrome, Firefox and Safari each use a
   different heuristic for buttons -- so trusting any of them was the bug.
   This sidesteps all of it: the ring is only ever driven by our own
   JS-tracked input modality, same result in every browser.
   - outline:none        Chrome/Edge/Safari default ring + Firefox's plain outline
   - appearance:none     native widget paint, incl. the inset "Windows button"
                          focus rectangle that ignores outline entirely
   - ::-moz-focus-inner  Firefox's separate inner dotted border on buttons
   - :-moz-focusring     Firefox's dotted line drawn around a button/select's
                          own text, unrelated to outline entirely
   - tap-highlight-color the gray flash Chrome/Safari draw on mobile tap */
button,input,select,textarea{appearance:none;-webkit-appearance:none;-moz-appearance:none}
a,button,select,summary,input,textarea,[tabindex]{
  outline:none!important;-webkit-tap-highlight-color:transparent;
}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
button:-moz-focusring,select:-moz-focusring,input:-moz-focusring{
  color:transparent;text-shadow:0 0 0 currentColor;
}
html.kb-nav a:focus,html.kb-nav button:focus,html.kb-nav summary:focus,html.kb-nav [tabindex]:focus{
  outline:2px solid var(--gold)!important;outline-offset:2px;
}
a{color:var(--gold);text-decoration:none}
::selection{background:var(--gold);color:#181103}
html{scrollbar-color:var(--bg-2) var(--bg);scrollbar-width:thin}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:var(--bg)}
::-webkit-scrollbar-thumb{background:var(--bg-2);border:2px solid var(--bg);border-radius:99px}
::-webkit-scrollbar-thumb:hover{background:var(--line)}

/* structural base for the ambient background layer -- content/position/inset/
   z-index/pointer-events live here since the pseudo-element needs `content`
   to exist at all; the actual paint (corner blooms + the brick coursing) is
   done by the second body::before further down, which redeclares every
   background-* longhand and fully overrides this one's paint. */
body::before{
  content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
}

/* ---------- archivist's disclosure ---------- */
/* shares the top bar's translucent bg + hairline bottom border so the header
   reads as one cohesive band instead of a stray note floating above it. */
.disclosure{
  display:none; /* archivist intro hidden per Jared 2026-07-26; delete this line to bring it back */
  padding:12px 14px;
  background:rgba(11,10,13,.92);
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  border-bottom:1px solid var(--line);
}
.disclosure-inner{max-width:1280px;margin:0 auto;text-align:left}
.disclosure-lead{
  font-family:var(--font-mono);font-size:11px;letter-spacing:.15em;text-transform:uppercase;
  color:var(--gold);display:block;margin-bottom:4px;
}
.disclosure-body{font-size:11px;line-height:1.35;color:var(--ink-faint)}
@media(min-width:1400px){ .disclosure-inner{ max-width:none } }

/* ---------- top bar ---------- */
.topbar{
  position:sticky;top:0;z-index:50;
  background:rgba(11,10,13,.92);
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  border-bottom:1px solid var(--line);
}
.topbar-inner{
  max-width:1280px;margin:0 auto;padding:10px 14px;
  display:flex;align-items:center;gap:10px;flex-wrap:nowrap;
}
.brand{display:flex;flex-direction:column;line-height:1.05;min-width:0;flex:0 1 auto}
.brand .eyebrow{
  font-family:var(--font-mono);font-size:9px;letter-spacing:.28em;white-space:normal;
  color:var(--sys);text-transform:uppercase;
}
.brand .title{
  font-family:var(--font-brand);font-size:17px;color:var(--gold);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  filter:drop-shadow(0 0 8px rgba(255,106,43,.5)) drop-shadow(0 0 20px rgba(43,217,232,.22));
}
/* red 'reset all progress' pill beside the title -- only rendered once there's
   progress to wipe (v-if hasProgress); reuses the footer confirm() flow */
.reset-top{
  display:inline-flex;align-items:center;gap:6px;flex:none;
  font-family:var(--font-mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;
  color:var(--red);background:var(--red-soft);border:1px solid var(--red);
  border-radius:99px;padding:7px 12px;cursor:pointer;
  transition:background .16s,color .16s,transform .16s;
}
.reset-top svg{width:13px;height:13px;flex:none}
.reset-top:hover{background:var(--red);color:#12100e}
.reset-top:active{transform:translateY(1px)}
/* bigger screens: shove the reset pill to the far right of the bar; on phones
   it stays snug next to the title (kept tight) */
@media(min-width:560px){ .reset-top{margin-left:auto} }
.searchbox{
  flex:1 1 240px;display:flex;align-items:center;gap:9px;
  background:var(--bg-2);border:1.5px solid rgba(91,196,240,.42);border-radius:99px;
  padding:10px 16px;min-width:160px;
  box-shadow:0 0 0 3px rgba(91,196,240,.08), 0 4px 14px -8px rgba(43,217,232,.35);
  transition:border-color .16s,box-shadow .16s;
}
.searchbox:hover{border-color:rgba(91,196,240,.6)}
.searchbox:focus-within{border-color:var(--sys);box-shadow:0 0 0 3px var(--sys-soft),0 6px 18px -8px rgba(43,217,232,.5)}
.searchbox svg{width:16px;height:16px;flex:none;stroke:var(--sys)}
.searchbox input{
  flex:1;background:none;border:0;outline:0;color:var(--ink);
  font:inherit;font-size:15px;min-width:0;
}
.searchbox input::placeholder{color:var(--ink-faint)}
.searchbox .clear{color:var(--ink-faint);font-size:16px;line-height:1;padding:0 2px}
.searchbox .clear:hover{color:var(--ink)}
.search-wrap{display:flex;flex-direction:column;gap:4px;flex:1 1 240px;min-width:150px}
.search-wrap .searchbox{flex:none;width:100%}
.search-scope{
  font-family:var(--font-mono);font-size:9px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--sys);padding-left:3px;line-height:1.3;
}

/* ---------- epub alert bar ---------- */
.alertbar{
  max-width:1280px;margin:12px 14px 0;padding:10px 14px;
  display:flex;gap:10px;align-items:flex-start;
  border:1px solid rgba(255,59,48,.5);border-radius:10px;
  background:linear-gradient(90deg, rgba(255,59,48,.16), rgba(255,106,43,.1));
}
@media(min-width:1308px){.alertbar{margin-left:auto;margin-right:auto}}
.alert-tag{
  flex:none;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--red);padding-top:2px;
  animation:alertblink 2.4s ease-in-out infinite;
}
@keyframes alertblink{0%,100%{opacity:1}50%{opacity:.45}}
.alert-msg{font-size:12.5px;color:var(--ink-dim);line-height:1.5}
.alert-msg b{color:var(--gold);font-weight:600}

/* ---------- swipeable rails (native overflow, no library) ---------- */
.rail{
  display:flex;gap:8px;overflow-x:auto;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
  padding:2px 14px;
  cursor:grab;
}
.rail::-webkit-scrollbar{display:none}
.rail.dragging{cursor:grabbing;scroll-behavior:auto}
.rail > *{flex:none}
/* covers are the same width but each keeps its own natural height (source art is
   consistent but not pixel-identical) -- bottom-align them so the rail reads as a shelf */
.bookrail-wrap .rail{align-items:flex-end}

/* ---------- book rail ---------- */
.bookrail-wrap{max-width:1280px;margin:14px auto 0}
.book-card{
  width:92px;border-radius:10px;overflow:hidden;position:relative;
  border:0;background:none;
  transition:box-shadow .18s ease;
  text-align:left;
}
.book-card .cover{display:block;min-height:60px;background:var(--bg-2);position:relative;overflow:hidden}
.book-card .cover img{width:100%;height:auto;display:block}
/* one-shot glossy sheen sweeping across the cover on hover -- transform only,
   no continuous animation, so it stays cheap and only fires on real intent */
.book-card .cover::after{
  content:"";position:absolute;inset:0;
  background:linear-gradient(115deg, transparent 42%, rgba(255,255,255,.22) 50%, transparent 58%);
  transform:translateX(-130%);transition:transform .75s ease;
}
.book-card:hover:not(.locked) .cover::after{transform:translateX(130%)}
/* ---- extra hover flourish: holographic foil drift + fairy-dust sparkle ---- */
/* iridescent foil that glows across the artwork (screen lights up dark covers),
   drifting under the one-shot sheen sweep */
.book-card .cover::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  opacity:0;transition:opacity .4s ease;mix-blend-mode:screen;
  background:linear-gradient(115deg, transparent 12%, rgba(255,55,150,.5) 28%, rgba(120,205,255,.5) 42%, rgba(120,255,190,.42) 52%, rgba(255,225,110,.55) 64%, rgba(210,120,255,.45) 74%, transparent 88%);
  background-size:240% 240%;
}
@media (hover:hover){.book-card:hover:not(.locked) .cover::before{opacity:.72}}
@media (prefers-reduced-motion:no-preference) and (hover:hover){
  .book-card:hover:not(.locked) .cover::before{animation:bc-foil 2.6s ease-in-out infinite}
}
@keyframes bc-foil{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
.book-card .cover .nocover{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-family:var(--font-head);font-weight:700;font-size:26px;color:var(--ink-faint);
}
/* every non-active cover (locked ones too) carries a light-grey 2px ring --
   SAME size as the teal hover/active ring, so it reads as the empty slot the
   highlight fills in */
.book-card:not(.active){box-shadow:0 0 0 2px rgba(255,255,255,.20)}
.book-card:hover:not(.locked){box-shadow:0 0 0 2px var(--sys),0 14px 28px -10px rgba(43,217,232,.4)}
html.kb-nav .book-card:focus:not(.locked){outline:none;box-shadow:0 0 0 2px var(--sys),0 14px 28px -10px rgba(43,217,232,.4)}
.book-card.active{box-shadow:0 0 0 2px var(--sys),0 14px 28px -10px rgba(43,217,232,.4)}
/* locked (completely unselected) covers: mostly grayscale with only a faint
   hint of colour left -- not fully desaturated -- so they recede hard behind
   the active book -- Jared 2026-08-02 (was saturate(.5)) */
.book-card.locked .cover img,.book-card.locked .cover .nocover{filter:brightness(.5) saturate(.25)}
.book-card.locked{cursor:not-allowed}
/* inactive (but available) covers sit back a touch -- softly dimmed and
   desaturated so the active book reads as the one in focus, the same way
   the character cards below recede until opened. Hover brings a cover back
   to full colour as a preview. */
.book-card .cover img{transition:filter .22s ease,opacity .22s ease}
.book-card:not(.active):not(.locked) .cover img{filter:saturate(.62) brightness(.84);opacity:.9}
.book-card:not(.active):not(.locked):hover .cover img{filter:none;opacity:1}
.book-pages{display:block;text-align:center;font-size:9px;line-height:1;letter-spacing:.03em;color:var(--ink-faint);padding:5px 4px 5px;background:#000;-webkit-user-select:none;user-select:none}
.lock-badge{
  position:absolute;inset:0;display:flex;flex-direction:column;gap:4px;
  align-items:center;justify-content:center;color:var(--ink-dim);
  font-family:var(--font-mono);font-size:8px;letter-spacing:.14em;text-transform:uppercase;
  background:rgba(12,10,9,.35);text-align:center;padding:4px;
}
.lock-badge svg{width:16px;height:16px;stroke:var(--ink-dim)}

/* ---------- view tabs ---------- */
.tabs{
  max-width:1280px;margin:14px auto 0;padding:0 14px;
  display:flex;gap:6px;border-bottom:1px solid var(--line);
}
.tab{
  font-family:var(--font-head);font-weight:600;font-size:14px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ink-dim);
  padding:8px 14px;border-bottom:2px solid transparent;margin-bottom:-1px;
  transition:color .2s;
}
.tab:hover{color:var(--ink)}
.tab.active{color:var(--gold);border-bottom-color:var(--gold)}
.tab .count{font-family:var(--font-mono);font-size:10px;color:var(--ink-faint);margin-left:4px}

/* ---------- category chips ---------- */
.chiprail-wrap{max-width:1280px;margin:12px auto 0}
.chip{
  display:flex;align-items:center;gap:6px;
  font-family:var(--font-head);font-weight:600;font-size:13px;letter-spacing:.04em;
  color:var(--ink-dim);background:var(--bg-1);
  border:1px solid var(--line);border-radius:99px;padding:6px 13px;
  transition:color .2s,border-color .2s,background .2s;
}
.chip:hover{color:var(--ink);border-color:var(--ink-faint)}
.chip.active{color:#181103;background:var(--gold);border-color:var(--gold)}
.chip .n{font-family:var(--font-mono);font-size:10px;opacity:.75}

/* ---------- toolbar ---------- */
.toolbar{
  max-width:1280px;margin:12px auto 0;padding:10px 14px;
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
}
.toolbar .result-count{
  font-family:var(--font-mono);font-size:10.5px;color:var(--ink-faint);
  letter-spacing:.14em;text-transform:uppercase;padding:0 2px;
}
/* every control on the bar shares one height + radius so the row reads as a set */
.select-wrap select,.toggle,.toolbar .searchbox{height:38px;border-radius:9px}
.select-wrap{position:relative}
.select-wrap select{
  appearance:none;-webkit-appearance:none;
  background:var(--bg-1);border:1px solid var(--line);
  color:var(--ink-dim);font-family:var(--font-head);font-weight:600;font-size:12.5px;
  letter-spacing:.04em;padding:0 32px 0 13px;outline:none;min-width:150px;cursor:pointer;
  transition:border-color .16s,box-shadow .16s,color .16s;
}
.select-wrap select:hover{color:var(--ink);border-color:var(--ink-faint)}
.select-wrap select:focus{border-color:var(--sys);box-shadow:0 0 0 3px var(--sys-soft);color:var(--ink)}
.select-wrap::after{
  content:"";position:absolute;right:13px;top:50%;translate:0 -30%;
  border:4px solid transparent;border-top-color:var(--ink-faint);pointer-events:none;
}
.toggle{
  display:inline-flex;align-items:center;
  font-family:var(--font-head);font-weight:600;font-size:12.5px;letter-spacing:.04em;
  color:var(--ink-dim);background:var(--bg-1);border:1px solid var(--line);
  padding:0 14px;cursor:pointer;transition:color .16s,border-color .16s,background .16s;
}
.toggle:hover{color:var(--ink);border-color:var(--ink-faint)}
.toggle.active{color:#0b0a0d;background:var(--sys);border-color:var(--sys)}
/* search pill sits inline on desktop, drops to its own full-width row on phones */
.toolbar .searchbox{flex:0 1 300px;min-width:200px;padding:0 14px}

/* ---------- codex grid ---------- */
.grid{
  max-width:1280px;margin:14px auto 0;padding:0 14px 20px;
  display:grid;grid-template-columns:repeat(2,1fr);gap:10px;
}
.card{
  position:relative;
  background:var(--bg-1);border:1px solid var(--line);border-radius:var(--radius);
  overflow:hidden;text-align:left;display:flex;flex-direction:column;content-visibility:auto;contain-intrinsic-size:auto 260px;
  transition:border-color .2s, box-shadow .2s;
}
.card:hover{box-shadow:0 0 0 2px var(--sys),0 10px 30px -12px rgba(0,0,0,.8)}
html.kb-nav .card:focus{outline:none;box-shadow:0 0 0 2px var(--sys),0 10px 30px -12px rgba(0,0,0,.8)}
/* cursor-tracking spotlight -- --mx/--my come from js/app.js on pointermove.
   Mouse/trackpad only: gated on hover:hover so it can never get stuck on a
   stale position the way :hover sometimes does after a tap on touch. No
   z-index, so plain DOM order does the layering -- it paints behind .thumb
   and .body (both position:relative, so they naturally paint after this
   first-child pseudo-element) without needing to fight for a stacking level. */
.card::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  opacity:0;transition:opacity .35s ease;
  background:radial-gradient(240px circle at var(--mx,50%) var(--my,50%), rgba(255,255,255,.10), transparent 72%);
}
@media (hover:hover) and (pointer:fine){
  .card:hover::before{opacity:1}
}
.card .thumb{aspect-ratio:1/1;position:relative;overflow:hidden;background-color:var(--bg-2);background-image:url("/.jared/DCCv2/images/placeholder.jpg");background-repeat:no-repeat;background-position:center;background-size:cover;background-blend-mode:screen}
/* category-tone accent -- just the label color, so every card gives an
   at-a-glance read of what kind of thing it is before you've opened it */
.card.tone-gold .cat{color:var(--gold)}
.card.tone-red .cat{color:var(--red)}
.card.tone-green .cat{color:var(--green)} 
.card .thumb img{
  height:100%;object-fit:cover;opacity:0;
  filter:brightness(.66) saturate(.45);transition:filter .25s ease,opacity .35s ease;
}
/* fade the thumb in over its colour placeholder once it actually loads
   (cards no longer use loading="lazy"; the lazyimg directive + .loaded
   class drive this so a card shows its avg colour, never a black box) */
.card .thumb img.loaded{opacity:1}
.card:hover .thumb img{filter:brightness(1.16) saturate(1.7) contrast(1.08)}
/* once an entry's been opened (or its book marked read), it's done being a
   spoiler for good -- full revealed brightness/saturation at rest, no hover required to prove it */
.card.seen .thumb img{filter:brightness(1.08) saturate(1.5) contrast(1.04)}
/* hovering an already-amplified (seen) card pushes it further still -- blowout is fine, it is just a hover treat */
.card.seen:hover .thumb img{filter:brightness(1.2) saturate(1.75) contrast(1.08)}
/* prismatic holographic foil -- the exact rainbow sweep the book covers get,
   now on every entity card's art. Screen-blend over the thumb, drifting via
   the shared bc-foil keyframes. Desktop-only (hover:hover) so it never sticks
   on a touch tap. z-index:2 keeps it above the dimmed/brightened image. */
.card .thumb::before{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  opacity:0;transition:opacity .4s ease;mix-blend-mode:screen;
  background:linear-gradient(115deg, transparent 12%, rgba(255,55,150,.5) 28%, rgba(120,205,255,.5) 42%, rgba(120,255,190,.42) 52%, rgba(255,225,110,.55) 64%, rgba(210,120,255,.45) 74%, transparent 88%);
  background-size:240% 240%;
}
@media (hover:hover){.card:hover .thumb::before{opacity:.72}}
@media (prefers-reduced-motion:no-preference) and (hover:hover){
  .card:hover .thumb::before{animation:bc-foil 2.6s ease-in-out infinite}
}
/* the brightness/saturation lift is easy to miss scanning a whole grid -- 
   a small corner check gives it an unmissable at-a-glance marker too.
   Same checkmark character the read-book button already uses. */
.card.seen .thumb::after{
  content:"✓";position:absolute;top:6px;right:6px;z-index:1;
  width:18px;height:18px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  font-family:var(--font-head);font-weight:700;font-size:11px;color:#180f08;
  background:var(--gold);box-shadow:0 2px 8px rgba(0,0,0,.55);
}
/* every entry is a spoiler until you choose to open it — the card stays dim,
   and hovering names the trade-off instead of hiding it. Clicking through is
   the consent. Solid dark-on-gold badge (not thin outlined text), a scrim
   behind it so it holds contrast against the image brightening underneath,
   and a hazard-stripe edge so it reads as caution tape, not a subtle tag. */
.spoiler-veil{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  opacity:0;transition:opacity .2s ease;pointer-events:none;
  background:rgba(10,8,7,.55);
}
.card:hover .spoiler-veil{opacity:1}
.spoiler-veil span{
  font-family:var(--font-mono);font-weight:700;font-size:11.5px;letter-spacing:.2em;text-transform:uppercase;
  color:#180f08;background:var(--gold);
  display:inline-flex;align-items:center;gap:6px;
  padding:6px 14px 6px 11px;border-radius:99px;
  box-shadow:0 0 0 1px rgba(0,0,0,.4),0 10px 22px -10px rgba(0,0,0,.85);
}
.spoiler-veil svg{width:13px;height:13px;flex:none;color:#180f08}
.spoiler-veil::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:6px;
  background:repeating-linear-gradient(135deg, var(--gold) 0 9px, #140d08 9px 18px);
}
/* touch devices have no :hover, so the dim-until-hover treatment would
   otherwise leave every thumb permanently dark with no way to reveal it --
   tapping already IS the consent (it opens the entry), so show the bright,
   labeled state by default instead of a hover state that can't happen. */
@media (hover:none){
  .card .thumb img{filter:brightness(1.16) saturate(1.7) contrast(1.08)}
  .spoiler-veil{opacity:1} 
} 
.card .body{padding:8px 10px 10px;display:flex;flex-direction:column;gap:4px;flex:1;position:relative}
.card .body::after{
  content:"";position:absolute;right:0;bottom:0;width:90px;height:90px;
  background:radial-gradient(circle at bottom right, rgba(255,255,255,.05), transparent 70%);
  pointer-events:none;
}
.card .cat{
  font-family:var(--font-mono);font-size:9px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--sys);
}
.card h3{
  font-family:var(--font-head);font-weight:700;font-size:15px;line-height:1.15;
  color:var(--ink);letter-spacing:.01em;
}
.card .hook{font-size:12px;color:var(--ink-dim);line-height:1.45}

/* placeholder art needed marker */
.needs-art{
  position:absolute;left:8px;bottom:8px;
  font-family:var(--font-mono);font-size:8px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-faint);background:rgba(12,10,9,.75);padding:3px 7px;border-radius:99px;
}

/* ---------- empty state ---------- */
.empty{
  max-width:1280px;margin:40px auto;padding:0 14px;text-align:center;color:var(--ink-faint);
  font-family:var(--font-mono);font-size:12px;letter-spacing:.08em;
}
.empty svg{width:32px;height:32px;stroke:var(--ink-faint);margin:0 auto 12px;display:block}
.empty-lead{margin:0}
.empty-clear{
  margin-top:16px;font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--sys);border:1px solid var(--sys);border-radius:99px;padding:7px 16px;
  transition:color .18s,border-color .18s,background .18s;
}
.empty-clear:hover{background:var(--sys-soft)}

/* ---------- detail overlay ---------- */
.dcc-overlay{
  position:fixed;inset:0;z-index:100;overflow-y:auto;overscroll-behavior:contain;
  background:rgba(8,7,6,.82);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
}
.sheet{
  position:relative;overflow:hidden;
  max-width:860px;margin:0 auto;min-height:100%;
  background:var(--bg-1);   /* flat, no bloom -- matches the hero image's fade at every scroll position */
  border-left:1px solid var(--line);border-right:1px solid var(--line);
}
.sheet-hero{position:relative;aspect-ratio:1/1;background:var(--bg-2);overflow:hidden}
.sheet-hero img{width:100%;height:100%;object-fit:cover;object-position:center}
/* two stacked layers: the already-cached thumb paints instantly, the full-res
   fades in on top once it loads so the drawer never opens on a blank box */
.sheet-hero .hero-thumb,.sheet-hero .hero-full{position:absolute;inset:0;transform:translateZ(0);backface-visibility:hidden}
.sheet-hero .hero-thumb{filter:blur(14px) brightness(.82);transform:scale(1.06) translateZ(0)}
/* full-res is colour-graded at all times and only OPACITY animates in, so the
   fade is compositor-only (no per-frame filter repaint = no jank), and the
   grade is gentler than the thumb so the colours don't pop as it arrives */
.sheet-hero .hero-full{opacity:0;filter:brightness(1.07) saturate(1.4) contrast(1.03);transition:opacity .5s ease;will-change:opacity}
.sheet-hero .hero-full.ready{opacity:1}
/* category-tone glow layered into the SAME ::after as the existing
   bottom-fade (rather than a separate pseudo-element) so it inherits the
   exact working stacking order for free -- it already paints above the
   opaque hero photos and below .hero-nav's z-index:3 arrows. "major"
   categories (boss/god/admin) get a bigger, hotter version so opening one
   actually reads as a moment, not just a recolor. */
.sheet-hero::after{
  content:"";position:absolute;inset:0;
  background:
    radial-gradient(ellipse 90% 70% at 50% 28%, var(--sys-soft), transparent 68%),
    linear-gradient(to top, var(--bg-1) 0%, transparent 45%);
}
.sheet-hero.tone-gold::after{background:
    radial-gradient(ellipse 90% 70% at 50% 28%, var(--gold-soft), transparent 68%),
    linear-gradient(to top, var(--bg-1) 0%, transparent 45%);
}
.sheet-hero.tone-red::after{background:
    radial-gradient(ellipse 90% 70% at 50% 28%, var(--red-soft), transparent 68%),
    linear-gradient(to top, var(--bg-1) 0%, transparent 45%);
}
.sheet-hero.tone-green::after{background:
    radial-gradient(ellipse 90% 70% at 50% 28%, var(--green-soft), transparent 68%),
    linear-gradient(to top, var(--bg-1) 0%, transparent 45%);
}
.sheet-hero.major::after{background:
    radial-gradient(ellipse 110% 90% at 50% 24%, var(--red-soft), transparent 72%),
    linear-gradient(to top, var(--bg-1) 0%, transparent 45%);
}
.sheet-hero.major.tone-sys::after{background:
    radial-gradient(ellipse 110% 90% at 50% 24%, var(--sys-soft), transparent 72%),
    linear-gradient(to top, var(--bg-1) 0%, transparent 45%);
}
/* (a filter-animation flash for major entries used to live here; removed --
   animating filter on the full-res photo was the source of the drawer-open jank) */
/* prev/next entry arrows, centered vertically on the hero image */
.hero-nav{
  position:absolute;top:50%;translate:0 -50%;z-index:3;
  width:44px;height:44px;border-radius:99px;
  display:flex;align-items:center;justify-content:center;
  background:rgba(12,10,9,.6);border:1px solid var(--line);color:var(--ink);
  -webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
  transition:background .2s,border-color .2s,color .2s;
}
.hero-nav svg{width:20px;height:20px;stroke:currentColor}
.hero-nav.prev{left:12px}
.hero-nav.next{right:12px}
.hero-nav:hover{background:rgba(12,10,9,.85);border-color:var(--gold);color:var(--gold)}
.hero-nav:active{transform:scale(.93)}
.sheet-close{
  position:fixed;top:12px;right:12px;z-index:110;
  width:38px;height:38px;border-radius:99px;display:flex;align-items:center;justify-content:center;
  background:rgba(12,10,9,.8);border:1px solid var(--line);color:var(--ink);
  font-size:17px;line-height:1;
}
.sheet-close:hover{border-color:var(--gold);color:var(--gold)}
.sheet-body{padding:0 18px 30px;margin-top:-34px;position:relative}
.sheet-body .cat{
  font-family:var(--font-mono);font-size:10px;letter-spacing:.24em;text-transform:uppercase;color:var(--sys);
}
.sheet-hero.tone-gold + .sheet-body .cat{color:var(--gold)}
.sheet-hero.tone-red + .sheet-body .cat{color:var(--red)}
.sheet-hero.tone-green + .sheet-body .cat{color:var(--green)}
.sheet-body h2{
  font-family:var(--font-head);font-weight:700;font-size:clamp(36px,6.5vw,46px);line-height:1.0;color:var(--ink);
  margin:4px 0 2px;
}
.sheet-body .aka{font-size:12.5px;color:var(--ink-faint);font-style:italic}
.badge-row{display:flex;flex-wrap:wrap;gap:6px;margin:12px 0 4px}
/* copy-link ("share this entry") button under the drawer title */
.sheet-share{
  display:inline-flex;align-items:center;gap:6px;margin-top:10px;
  font-family:var(--font-mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--ink-dim);background:var(--bg-1);border:1px solid var(--line);border-radius:99px;
  padding:6px 13px;transition:color .16s,border-color .16s,background .16s;
}
.sheet-share:hover{color:var(--ink);border-color:var(--sys)}
.sheet-share.done{color:var(--sys);border-color:var(--sys);background:var(--sys-soft)}
.sheet-share svg{width:13px;height:13px;flex:none}
.badge{
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;
  border:1px solid var(--line);border-radius:99px;padding:4px 10px;color:var(--ink-dim);
  background:var(--bg-2);
}
.badge.gold{color:var(--gold);border-color:rgba(255,106,43,.4);background:var(--gold-soft)}
.badge.good{color:var(--green);border-color:rgba(62,224,138,.4);background:var(--green-soft)}
.badge.bad{color:var(--red);border-color:rgba(255,59,48,.4);background:var(--red-soft)}
.sheet-desc{margin-top:14px;display:flex;flex-direction:column;gap:10px;color:var(--ink);font-size:14.5px}
.sheet-desc p{color:var(--ink)}
.sheet-desc p+p{color:var(--ink-dim)}

/* system-style stat box */
.sysbox{
  margin-top:18px;border:1px solid rgba(43,217,232,.28);border-radius:10px;
  background:var(--sys-soft);overflow:hidden;
}
.sysbox .sysbox-head{
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:.24em;text-transform:uppercase;
  color:var(--sys);padding:8px 13px;border-bottom:1px solid rgba(43,217,232,.2);
}
.sysbox table{width:100%;border-collapse:collapse}
.sysbox td{
  padding:7px 13px;font-size:13px;vertical-align:top;
  border-bottom:1px solid rgba(43,217,232,.1);
}
.sysbox tr:last-child td{border-bottom:0}
.sysbox td:first-child{
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-faint);white-space:nowrap;width:1%;padding-right:18px;
}
.sysbox td:last-child{color:var(--ink)}

/* quote */
#app .sheet-quote{
  margin-top:1rem;padding:13px 16px;border-left:3px solid var(--gold);
  background:var(--gold-soft);border-radius:0 10px 10px 0;
  font-style:italic;color:var(--ink);font-size:14px;
}
.sheet-quote .who{
  display:block;margin-top:6px;font-style:normal;font-family:var(--font-mono);
  font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-faint);
}

/* spoiler bar (fate) */
.spoiler-row{margin-top:18px}
.spoiler-label{
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:.24em;text-transform:uppercase;
  color:var(--red);display:flex;align-items:center;gap:5px;margin-bottom:6px;
}
.spoiler-label svg{width:11px;height:11px;flex:none;stroke:currentColor}
/* deliberate reveal only (no hover-peek): a hard [REDACTED] bar you tap to declassify */
.spoiler{display:inline-block;max-width:100%;cursor:pointer;outline:none}
.spoiler-body{
  background:#050506;
  background-image:repeating-linear-gradient(45deg, rgba(255,255,255,.05) 0 7px, transparent 7px 14px);
  box-shadow:0 0 0 1px rgba(212,72,62,.5) inset;
  color:transparent;border-radius:3px;padding:.12em .4em;font-size:14px;
  -webkit-box-decoration-break:clone;box-decoration-break:clone;
  transition:background .3s,box-shadow .3s,color .3s;
}
.spoiler-body b,.spoiler-body em{color:inherit}
html.kb-nav .spoiler:focus .spoiler-body,
.spoiler.revealed .spoiler-body{
  background:transparent;background-image:none;box-shadow:none;color:var(--ink-dim);
}
html.kb-nav .spoiler:focus .spoiler-body b,
.spoiler.revealed .spoiler-body b{color:var(--gold)}
@media (prefers-reduced-motion:no-preference){
  .spoiler.revealed{animation:redact-declass .5s ease both}
}
@keyframes redact-declass{
  0%{filter:contrast(1.6) brightness(1.4)}
  12%{transform:translateX(-3px) skewX(-1.2deg)}
  26%{transform:translateX(3px) skewX(1.2deg)}
  40%{transform:translateX(-2px)}
  55%{transform:translateX(1px)}
  100%{transform:none;filter:none}
}

/* related */
.related-row{margin-top:20px}
.related-row .lbl{
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:.24em;text-transform:uppercase;
  color:var(--ink-faint);display:block;margin-bottom:8px;
}
.related-chips{display:flex;flex-wrap:wrap;gap:6px}
.rchip{
  display:flex;align-items:center;gap:7px;
  background:var(--bg-2);border:1px solid var(--line);border-radius:99px;
  padding:4px 12px 4px 4px;font-family:var(--font-head);font-weight:600;font-size:13px;
  color:var(--ink-dim);transition:color .2s,border-color .2s;
}
.rchip:hover{color:var(--gold);border-color:var(--gold)}
.rchip img{width:24px;height:24px;border-radius:99px;object-fit:cover;flex:none}

/* wiki footer note inside sheet */
.sheet-meta{
  margin-top:26px;padding-top:14px;border-top:1px dashed var(--line);
  font-family:var(--font-mono);font-size:10px;letter-spacing:.08em;color:var(--ink-faint);
  display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;
}

/* ---------- chapters view ---------- */
.chapters{max-width:1280px;margin:14px auto 0;padding:0 14px 20px;display:flex;flex-direction:column;gap:10px}
.chapter{
  background:var(--bg-1);border:1px solid var(--line);border-radius:var(--radius);
  overflow:hidden;
}
/* a recap expanded at least once keeps a subtly warmer border, so the list
   shows at a glance what you've already opened -- no label, just the tint */
.chapter.viewed{border-color:rgba(91,196,240,.42)}
.chapter summary{
  list-style:none;display:flex;align-items:baseline;gap:12px;padding:12px 15px;cursor:pointer;
  transition:background .18s;
}
.chapter summary::-webkit-details-marker{display:none}
/* hover lifts the whole header with a whisper of the bright cyan --sys,
   toned way down (a 6% wash over the card) -- was --bg-2 which read purple */
.chapter summary:hover{background:linear-gradient(rgba(43,217,232,.06), rgba(43,217,232,.06)), var(--bg-1)}
.chapter summary:hover h3,
.chapter summary:hover .chev{color:var(--gold)}
/* cursor-tracking spotlight on the chapter header -- same feel as the codex
   cards; js/app.js sets --mx/--my on the hovered summary. Direct children are
   made position:relative so they paint above the ::before (DOM order, the same
   trick .card uses). Mouse / fine-pointer only. */
.chapter summary{position:relative}
.chapter summary > *{position:relative}
.chapter summary::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  opacity:0;transition:opacity .35s ease;
  background:radial-gradient(220px circle at var(--mx,50%) var(--my,50%), rgba(255,106,43,.11), transparent 72%);
}
@media (hover:hover) and (pointer:fine){
  .chapter summary:hover::before{opacity:1}
}
.chapter summary .num{
  font-family:var(--font-mono);font-size:11px;color:var(--gold);letter-spacing:.08em;
  letter-spacing:.02em;
}
.chapter summary h3{
  font-family:var(--font-head);font-weight:600;font-size:15.5px;color:var(--ink);line-height:1.25;
}
.chapter summary .chev{color:var(--ink-faint);flex:none;transition:rotate .2s;font-size:11px}
.chapter[open] summary .chev{rotate:90deg}
.chapter .chapter-body{
  padding:8px 15px 14px 15px;color:var(--ink-dim);font-size:13.5px;
  display:flex;flex-direction:column;gap:8px;
}
.chapter .chapter-body .events{
  display:flex;flex-wrap:wrap;gap:5px;margin-top:4px;
}
.chapter .chapter-body .events span{
  font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;
  border:1px solid var(--line);border-radius:99px;padding:3px 8px;color:var(--ink-faint);
}

/* ---------- achievement toast ---------- */
.dcc-toast{
  position:fixed;left:50%;bottom:18px;translate:-50% 0;z-index:200;
  width:min(92vw,420px);
  background:var(--bg-2);border:1px solid rgba(255,106,43,.45);border-radius:12px;
  padding:12px 15px;box-shadow:0 18px 50px -12px rgba(0,0,0,.9);
  display:flex;gap:12px;align-items:flex-start;
}
.dcc-toast .tag{
  font-family:var(--font-mono);font-size:9px;letter-spacing:.24em;text-transform:uppercase;
  color:var(--gold);display:block;
}
.dcc-toast .msg{font-size:13px;color:var(--ink);line-height:1.45;margin-top:3px}
.dcc-toast-enter-active{transition:opacity .35s, translate .35s}
.dcc-toast-leave-active{transition:opacity .3s, translate .3s}
.dcc-toast-enter-from,.dcc-toast-leave-to{opacity:0;translate:-50% 14px}

/* ---------- footer ---------- */
.footer{
  border-top:1px solid var(--line);margin-top:30px;padding:22px 14px 34px;
  text-align:center;
}
/* the "whoopsie" button -- deliberately quiet so it doesn't compete with
   real navigation, but still clearly its own clickable thing, not decoration */
.reset-btn{
  margin-top:18px;font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--red);
  border:1px solid var(--red);border-radius:99px;padding:7px 16px;
  transition:color .18s,border-color .18s;
}
.reset-btn:hover{color:var(--sys);border-color:var(--sys)}

/* ---------- read progress row ---------- */
.readrow{
  max-width:1280px;margin:12px auto 0;padding:0 14px;
  display:flex;align-items:center;gap:8px 10px;flex-wrap:wrap;
}
.readbtn{
  display:inline-flex;align-items:center;gap:8px;flex:none;
  font-family:var(--font-mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-dim);background:var(--bg-1);
  border:1px solid var(--line);border-radius:99px;padding:7px 14px 7px 9px;
  transition:border-color .16s,color .16s,background .16s,box-shadow .16s;
}
/* the "finished the book" control is what unboxes the variant-cover reward,
   so the un-marked state gets a warm gold call-to-action glow -- people skip
   plain buttons. Marking it read flips it to the teal "complete" look below. */
.readbtn:not(.done){border-color:rgba(232,163,61,.6);color:var(--ink);box-shadow:0 0 0 3px var(--gold-soft)}
.readbtn:not(.done) .box{border-color:rgba(232,163,61,.75)}
@media (prefers-reduced-motion:no-preference) and (hover:hover){
  .readbtn:not(.done){animation:readpulse 2.6s ease-in-out infinite}
}
@keyframes readpulse{
  0%,100%{box-shadow:0 0 0 3px var(--gold-soft)}
  50%{box-shadow:0 0 0 4px rgba(232,163,61,.10),0 0 16px rgba(232,163,61,.20)}
}
.readbtn .box{
  width:16px;height:16px;flex:none;border-radius:4px;
  border:1px solid var(--ink-faint);background:var(--bg);
  display:flex;align-items:center;justify-content:center;
  font-size:11px;line-height:1;color:var(--bg);
}
.readbtn:hover{border-color:var(--sys);color:var(--ink)}
.readbtn.done{color:var(--sys);border-color:var(--sys);background:var(--sys-soft)}
.readbtn.done .box{background:var(--sys);border-color:var(--sys)}

.readnote{
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;
  color:var(--ink-faint);line-height:1.4;
}

/* "unlock all" shortcut icon -- sits before the finished-book button, shown
   only while locked books remain (v-if). Cyan like the unlock/complete state;
   the hover tooltip names it, the click fires a confirm() warning. */
.unlockbtn{
  position:relative;flex:none;
  display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;border-radius:99px;
  color:var(--sys);background:var(--bg-1);
  border:1px solid var(--line);
  transition:border-color .16s,color .16s,background .16s,box-shadow .16s;
}
.unlockbtn:hover,.unlockbtn:focus-visible{
  border-color:var(--sys);color:#fff;background:var(--sys-soft);
  box-shadow:0 0 0 3px var(--sys-soft);outline:none;
}
.unlockbtn svg{width:17px;height:17px}
.unlockbtn::after{
  content:attr(data-tip);position:absolute;bottom:calc(100% + 8px);left:0;
  transform:translateY(4px);
  white-space:nowrap;font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink);background:var(--bg-2);border:1px solid var(--line);border-radius:6px;padding:5px 9px;
  box-shadow:0 8px 20px -10px rgba(0,0,0,.8);
  opacity:0;pointer-events:none;transition:opacity .16s,transform .16s;z-index:6;
}
.unlockbtn:hover::after,.unlockbtn:focus-visible::after{opacity:1;transform:translateY(0)}

/* ---------- fade transitions ---------- */
.fade-enter-active,.fade-leave-active{transition:opacity .18s}
.fade-enter-from,.fade-leave-to{opacity:0}

/* ---------- desktop scale-up ---------- */
@media(min-width:500px){
  /* Detail drawer docks to the left edge (codex stays visible behind it) from
     500px up; full-width only on true phones below 500px. Capped at 480px wide. */
  .dcc-overlay{padding:0;background:rgba(8,7,6,.66)}
  .sheet{
    position:fixed;left:0;top:0;bottom:0;
    width:clamp(360px,34vw,480px);max-width:480px;margin:0;min-height:0;
    overflow-y:auto;overscroll-behavior:contain;
    border:1px solid var(--line);border-left:0;border-radius:0 16px 16px 0;
    box-shadow:0 0 60px rgba(0,0,0,.65);
    transition:transform .22s ease;
  }
  .sheet-hero{aspect-ratio:1/1}
  .sheet-hero img{object-position:center}
  .sheet-body h2{font-size:clamp(44px,4.4vw,58px)}
  .sheet-close{position:absolute}
  .fade-enter-from .sheet,.fade-leave-to .sheet{transform:translateX(-28px)}
}
@media(min-width:560px){
  .grid{grid-template-columns:repeat(3,1fr);gap:12px}
  .brand .title{font-size:19px}
  .book-card{width:108px}
}
@media(min-width:820px){
  .grid{grid-template-columns:repeat(4,1fr)}
  .book-card{width:122px}
  body{font-size:16px}
}
@media(min-width:1100px){
  .grid{grid-template-columns:repeat(5,1fr)}
}
/* BS4 xl */
@media(min-width:1200px){
  .book-card{width:140px}
}

@media(prefers-reduced-motion:reduce){
  *{transition:none!important;animation:none!important;scroll-behavior:auto!important}
}

/* ---------- edge to edge on big screens ----------
   Everything is capped at 1280px by default. Past that the cap just wastes
   glass, so release it and add columns instead. Chapter recaps keep a reading
   measure — full-width prose is unreadable no matter how big the monitor. */
@media(min-width:1400px){
  .topbar-inner,.alertbar,.bookrail-wrap,.readrow,.progressrow,.tabs,
  .chiprail-wrap,.toolbar,.grid,.empty,.chapters{ max-width:none }
  .topbar-inner,.readrow,.progressrow,.tabs,.toolbar,.grid,.empty,.chapters{ padding-left:28px;padding-right:28px }
  .rail{ padding-left:28px;padding-right:28px }
  .alertbar{ margin-left:28px;margin-right:28px }
  .grid{ grid-template-columns:repeat(6,1fr) }
  .book-card{width:150px}
}
@media(min-width:1750px){ .grid{ grid-template-columns:repeat(7,1fr) } .book-card{width:162px} }
@media(min-width:2150px){ .grid{ grid-template-columns:repeat(8,1fr) } }
@media(min-width:2600px){ .grid{ grid-template-columns:repeat(9,1fr) } }

/* ---------- chapter art ---------- */
.chapter-art{
  margin:0 0 12px;border:1px solid var(--line);border-radius:10px;overflow:hidden;
  background:var(--bg-2);
}
.chapter-art img{width:100%;aspect-ratio:1/1;object-fit:cover;display:block}
@media(min-width:820px){
  .chapter-art{float:right;width:300px;margin:0 0 12px 16px}
}

/* ---------- author + narrator credit ---------- */
/* ---------- creator credit: the real showrunners, deliberately the loudest thing down here ---------- */
.creators{max-width:1280px;margin:0 auto}
.creators-head{
  font-family:var(--font-mono);font-size:11px;letter-spacing:.24em;text-transform:uppercase;
  color:var(--gold);text-align:center;
}
.creators-sub{
  font-size:12.5px;line-height:1.5;color:var(--ink-faint);text-align:center;
  max-width:540px;margin:6px auto 18px;
}
.creator-grid{display:grid;gap:14px;grid-template-columns:1fr}
@media(min-width:640px){ .creator-grid{grid-template-columns:1fr 1fr} }
.creator{
  --accent:var(--gold);
  position:relative;display:flex;flex-direction:column;align-items:flex-start;text-align:left;
  background:linear-gradient(180deg, var(--bg-1), var(--bg));
  border:1px solid var(--line);border-radius:14px;padding:22px 20px;overflow:hidden;
  transition:border-color .2s, box-shadow .2s;
}
.creator.c-cyan{--accent:var(--sys)}
.creator::before{content:"";position:absolute;left:0;right:0;top:0;height:2px;background:var(--accent)}
.creator::after{
  content:"";position:absolute;left:0;right:0;top:0;height:130px;pointer-events:none;
  background:radial-gradient(120% 100% at 50% 0, color-mix(in srgb, var(--accent) 24%, transparent), transparent 70%);
  opacity:.45;transition:opacity .25s ease;
}
.creator:hover{border-color:var(--accent);box-shadow:0 18px 46px -22px var(--accent)}
.creator:hover::after{opacity:.9}
.creator .role{
  position:relative;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--accent);
}
.creator .cname{
  position:relative;font-family:var(--font-head);font-weight:700;font-size:clamp(22px,3.4vw,28px);
  line-height:1.05;color:var(--ink);margin:7px 0 10px;
  text-shadow:0 0 20px color-mix(in srgb, var(--accent) 32%, transparent);
}
.creator .blurb{position:relative;font-size:13.5px;line-height:1.55;color:var(--ink-dim);margin-bottom:16px}
.creator .go{
  position:relative;margin-top:auto;display:inline-flex;align-items:center;gap:7px;
  font-family:var(--font-head);font-weight:700;font-size:12.5px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--accent);
}
.creator .go svg{width:14px;height:14px;stroke:currentColor;transition:transform .2s ease}
.creator:hover .go svg{transform:translateX(3px)}
.creators-foot{text-align:center;font-size:12px;line-height:1.5;color:var(--ink-faint);margin-top:16px}
.creators-foot b{color:var(--ink-dim);font-family:var(--font-mono);letter-spacing:.12em;font-weight:600}
/* ---------- archivist note: small, in character, deliberately lesser than the credit above ---------- */
.archivist{
  max-width:1280px;margin:22px auto 0;padding:12px 15px;text-align:left;
  border:1px dashed var(--line);border-radius:10px;background:rgba(0,0,0,.35);
}
.archivist .lead{
  font-family:var(--font-mono);font-size:12px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink);margin-bottom:6px;
}
.archivist p{font-size:11.5px;line-height:1.5;color:var(--ink-faint)}
@media(min-width:1400px){ .footer{padding-left:28px;padding-right:28px} .creators,.archivist{ max-width:none } }

/* ---------- chapter row thumbnail ----------
   220 chapter illustrations were invisible behind collapsed accordions.
   A small square in the summary row makes the list scannable and shows the art. */
.chapter summary{align-items:center}
.ch-meta{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0}
.ch-thumb{
  flex:none;width:34px;height:34px;overflow:hidden;
  transition:width .32s cubic-bezier(.34,1.56,.64,1), height .32s cubic-bezier(.34,1.56,.64,1);
}
.ch-thumb img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .5s ease,opacity .35s ease}
.chapter[open] .ch-thumb img{transform:scale(1.05)}
.chapter[open] .ch-thumb{width:68px;height:68px}
.chapter{content-visibility:auto;contain-intrinsic-size:auto 62px}
@media(min-width:820px){
  .ch-thumb{width:70px;height:70px}
  .chapter[open] .ch-thumb{width:140px;height:140px}
}

/* ============================================================
   PHONE PASS — real-device usability
   ============================================================ */

/* iOS Safari zooms the whole page when you focus a control smaller than 16px.
   That was happening on every search tap. Never go below 16px on touch. */
@media(max-width:819px){
  .searchbox input,
  .select-wrap select{font-size:16px}
}

/* Notched phones: keep content out from under the camera cutout and the
   home indicator, in both orientations. */
@supports(padding:max(0px)){
  .topbar-inner{
    padding-left:max(14px, env(safe-area-inset-left));
    padding-right:max(14px, env(safe-area-inset-right));
  }
  .rail{
    padding-left:max(14px, env(safe-area-inset-left));
    padding-right:max(14px, env(safe-area-inset-right));
  }
  .grid,.readrow,.progressrow,.tabs,.toolbar,.chapters,.footer,.alertbar{
    padding-left:max(14px, env(safe-area-inset-left));
    padding-right:max(14px, env(safe-area-inset-right));
  }
  .sheet-body{padding-bottom:max(30px, env(safe-area-inset-bottom))}
}

@media(max-width:819px){
  /* Comfortable thumb targets. Anything tappable clears 44px. */
  .chip{min-height:44px;padding:8px 15px}
  .tab{min-height:44px;display:flex;align-items:center}
  .readbtn{min-height:44px}
  /* the finished-book hint drops to its own full-width line under the button
     instead of being squished into a narrow column beside it -- tight on phones */
  .readnote{flex:1 0 100%;line-height:1.35}
  .select-wrap select,.toggle,.toolbar .searchbox{height:44px}
  /* push the search pill to the end of the flex-wrap flow so it drops to
     its own full-width row, and the Hide Spoilers toggle (which otherwise
     sits after it in source order) rides up onto the row above with the
     sort dropdown instead of stranding alone next to a full-width search */
  .toolbar .searchbox{flex:1 1 100%;order:5}
  .sheet-close{width:44px;height:44px;font-size:20px}
  .chapter summary{padding:12px 14px;min-height:64px}

  /* The alert bar was eating half the first screen: the tag sat in its own
     column and squeezed the message into a narrow ribbon. Stack it instead. */
  .alertbar{flex-direction:column;gap:4px;padding:9px 13px;margin-top:10px}
  .alert-tag{padding-top:0}

  /* Tighten the run of chrome above the grid so cards appear sooner. */
  .bookrail-wrap{margin-top:10px}
  .readrow{margin-top:9px}
  .tabs{margin-top:10px}
  .chiprail-wrap{margin-top:9px}
  .toolbar{margin-top:8px}
  .grid{margin-top:10px;gap:10px}

  /* Grey flash on tap looks broken on a dark theme. */
  a,button,summary,.chip,.tab,.card{-webkit-tap-highlight-color:rgba(255,106,43,.18)}

  /* Momentum scrolling inside the open entry sheet, and never let a rubber-band
     scroll leak through to the page behind it. */
  .dcc-overlay{-webkit-overflow-scrolling:touch;overscroll-behavior:contain}
}

/* Very small phones: one column beats two cramped ones. */
@media(max-width:359px){
  .grid{grid-template-columns:1fr}
}

/* Landscape phone: the sticky topbar ate most of a short viewport. */
@media(max-height:450px) and (orientation:landscape){
  .topbar{position:static}
  .alertbar{display:none}
}

/* ============================================================
   PALETTE — retuned to the card art (sampled from the cards themselves)
   The art prints in three inks on aged paper: dense black, a muted ember
   terracotta (#c09070–#d0a080), and an oxidized sea-green teal (#70a0a0).
   The old theme fought it — a bright modern cyan (#5bc4f0) against art that
   has no cyan anywhere, and a saturated marigold against a softer ember.
   These override the :root above by cascade order.
   ============================================================ */
:root{
  --bg:#0b0a0d;          /* warm ink-black */
  --bg-1:#161419;        /* card stone */
  --bg-2:#211c26;        /* raised stone */
  --line:#332d39;        /* hairline, warmer */

  --ink:#f1ece3;         /* the paper itself */
  --ink-dim:#aca392;
  --ink-faint:#746a5f;

  --gold:#ff6a2b;        /* ember plate, richer — accents punch even though art stays muted */
  --gold-soft:rgba(255,106,43,.17);

  --red:#ff3b30;         /* the deep red of the art's warm plate, a touch hotter */
  --red-soft:rgba(255,59,48,.17);

  --sys:#2bd9e8;         /* oxidized teal plate, saturated up from a grey-teal */
  --sys-soft:rgba(43,217,232,.16);

  --green:#3ee08a;       /* mossy, taken from the art's green-greys */
  --green-soft:rgba(62,224,138,.15);

  --mag:#ff3d9a;        /* hot magenta - rare book-cover pop */
  --mag-soft:rgba(255,61,154,.14);
}

/* The page's ambient light is deliberately colourless. Colour here belongs to
   the accents and the art; the dungeon itself is just cold grey light falling
   on stone, so the four corner blooms are neutral greys rather than any hue.
   Four of them rather than two so the atmosphere holds up on a long scroll,
   not just the first viewport. Under those: actual brickwork -- 72x24 courses
   in a running bond, drawn as a tiling SVG rather than stacked gradients,
   because a staggered bond needs a real 2D tile (a gradient runs in one
   direction only, so pure CSS gives you a grid, not a bond). Every brick face
   carries its own tint -- most a shade paler than the wall, roughly a quarter
   of them darker -- so it reads as mottled masonry instead of a repeating
   stamp, and each joint is a dark groove with a 1px lit edge below and right
   of it, so the light comes from the top-left like everything else on the
   page. All of it at single-digit-percent opacities: set dressing behind the
   UI, not wallpaper. The tile carries its own explicit position/size/repeat
   inline, because this stack mixes auto-sized full-bleed gradients with one
   fixed-size repeating tile and the shorthand can't infer both. */
body::before{
  background:
    radial-gradient(1200px 500px at 85% -5%, rgba(214,214,214,.05), transparent 60%),
    radial-gradient(900px 420px at -10% 15%, rgba(186,186,186,.055), transparent 55%),
    radial-gradient(800px 500px at -5% 100%, rgba(160,160,160,.05), transparent 55%),
    radial-gradient(900px 550px at 105% 90%, rgba(198,198,198,.04), transparent 55%),
    url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='432'%20height='192'%3E%3Cg%20fill='white'%3E%3Crect%20x='2'%20y='2'%20width='70'%20height='22'%20fill-opacity='.011'/%3E%3Crect%20x='74'%20y='2'%20width='70'%20height='22'%20fill-opacity='.008'/%3E%3Crect%20x='146'%20y='2'%20width='70'%20height='22'%20fill-opacity='.029'/%3E%3Crect%20x='218'%20y='2'%20width='70'%20height='22'%20fill-opacity='.01'/%3E%3Crect%20x='290'%20y='2'%20width='70'%20height='22'%20fill-opacity='.031'/%3E%3Crect%20x='38'%20y='26'%20width='70'%20height='22'%20fill-opacity='.012'/%3E%3Crect%20x='110'%20y='26'%20width='70'%20height='22'%20fill-opacity='.026'/%3E%3Crect%20x='182'%20y='26'%20width='70'%20height='22'%20fill-opacity='.03'/%3E%3Crect%20x='254'%20y='26'%20width='70'%20height='22'%20fill-opacity='.014'/%3E%3Crect%20x='326'%20y='26'%20width='70'%20height='22'%20fill-opacity='.01'/%3E%3Crect%20x='398'%20y='26'%20width='34'%20height='22'%20fill-opacity='.008'/%3E%3Crect%20x='0'%20y='26'%20width='36'%20height='22'%20fill-opacity='.008'/%3E%3Crect%20x='2'%20y='50'%20width='70'%20height='22'%20fill-opacity='.027'/%3E%3Crect%20x='146'%20y='50'%20width='70'%20height='22'%20fill-opacity='.022'/%3E%3Crect%20x='218'%20y='50'%20width='70'%20height='22'%20fill-opacity='.023'/%3E%3Crect%20x='362'%20y='50'%20width='70'%20height='22'%20fill-opacity='.022'/%3E%3Crect%20x='110'%20y='74'%20width='70'%20height='22'%20fill-opacity='.005'/%3E%3Crect%20x='182'%20y='74'%20width='70'%20height='22'%20fill-opacity='.015'/%3E%3Crect%20x='254'%20y='74'%20width='70'%20height='22'%20fill-opacity='.006'/%3E%3Crect%20x='326'%20y='74'%20width='70'%20height='22'%20fill-opacity='.024'/%3E%3Crect%20x='398'%20y='74'%20width='34'%20height='22'%20fill-opacity='.015'/%3E%3Crect%20x='0'%20y='74'%20width='36'%20height='22'%20fill-opacity='.015'/%3E%3Crect%20x='2'%20y='98'%20width='70'%20height='22'%20fill-opacity='.016'/%3E%3Crect%20x='74'%20y='98'%20width='70'%20height='22'%20fill-opacity='.007'/%3E%3Crect%20x='146'%20y='98'%20width='70'%20height='22'%20fill-opacity='.015'/%3E%3Crect%20x='218'%20y='98'%20width='70'%20height='22'%20fill-opacity='.011'/%3E%3Crect%20x='290'%20y='98'%20width='70'%20height='22'%20fill-opacity='.026'/%3E%3Crect%20x='38'%20y='122'%20width='70'%20height='22'%20fill-opacity='.012'/%3E%3Crect%20x='110'%20y='122'%20width='70'%20height='22'%20fill-opacity='.011'/%3E%3Crect%20x='182'%20y='122'%20width='70'%20height='22'%20fill-opacity='.005'/%3E%3Crect%20x='326'%20y='122'%20width='70'%20height='22'%20fill-opacity='.029'/%3E%3Crect%20x='398'%20y='122'%20width='34'%20height='22'%20fill-opacity='.023'/%3E%3Crect%20x='0'%20y='122'%20width='36'%20height='22'%20fill-opacity='.023'/%3E%3Crect%20x='2'%20y='146'%20width='70'%20height='22'%20fill-opacity='.012'/%3E%3Crect%20x='74'%20y='146'%20width='70'%20height='22'%20fill-opacity='.004'/%3E%3Crect%20x='218'%20y='146'%20width='70'%20height='22'%20fill-opacity='.011'/%3E%3Crect%20x='290'%20y='146'%20width='70'%20height='22'%20fill-opacity='.022'/%3E%3Crect%20x='362'%20y='146'%20width='70'%20height='22'%20fill-opacity='.026'/%3E%3Crect%20x='110'%20y='170'%20width='70'%20height='22'%20fill-opacity='.012'/%3E%3Crect%20x='182'%20y='170'%20width='70'%20height='22'%20fill-opacity='.013'/%3E%3Crect%20x='326'%20y='170'%20width='70'%20height='22'%20fill-opacity='.007'/%3E%3Crect%20x='398'%20y='170'%20width='34'%20height='22'%20fill-opacity='.009'/%3E%3Crect%20x='0'%20y='170'%20width='36'%20height='22'%20fill-opacity='.009'/%3E%3C/g%3E%3Cg%20fill='black'%3E%3Crect%20x='362'%20y='2'%20width='70'%20height='22'%20fill-opacity='.109'/%3E%3Crect%20x='74'%20y='50'%20width='70'%20height='22'%20fill-opacity='.138'/%3E%3Crect%20x='290'%20y='50'%20width='70'%20height='22'%20fill-opacity='.155'/%3E%3Crect%20x='38'%20y='74'%20width='70'%20height='22'%20fill-opacity='.148'/%3E%3Crect%20x='362'%20y='98'%20width='70'%20height='22'%20fill-opacity='.116'/%3E%3Crect%20x='254'%20y='122'%20width='70'%20height='22'%20fill-opacity='.157'/%3E%3Crect%20x='146'%20y='146'%20width='70'%20height='22'%20fill-opacity='.237'/%3E%3Crect%20x='38'%20y='170'%20width='70'%20height='22'%20fill-opacity='.11'/%3E%3Crect%20x='254'%20y='170'%20width='70'%20height='22'%20fill-opacity='.279'/%3E%3C/g%3E%3Cg%20stroke='black'%20stroke-opacity='.5'%20stroke-width='2'%3E%3Cpath%20d='M0%201H432M0%2025H432M0%2049H432M0%2073H432M0%2097H432M0%20121H432M0%20145H432M0%20169H432'/%3E%3Cpath%20d='M1%200V24M73%200V24M145%200V24M217%200V24M289%200V24M361%200V24M37%2024V48M109%2024V48M181%2024V48M253%2024V48M325%2024V48M397%2024V48M1%2048V72M73%2048V72M145%2048V72M217%2048V72M289%2048V72M361%2048V72M37%2072V96M109%2072V96M181%2072V96M253%2072V96M325%2072V96M397%2072V96M1%2096V120M73%2096V120M145%2096V120M217%2096V120M289%2096V120M361%2096V120M37%20120V144M109%20120V144M181%20120V144M253%20120V144M325%20120V144M397%20120V144M1%20144V168M73%20144V168M145%20144V168M217%20144V168M289%20144V168M361%20144V168M37%20168V192M109%20168V192M181%20168V192M253%20168V192M325%20168V192M397%20168V192'/%3E%3C/g%3E%3Cg%20stroke='white'%20stroke-width='1'%3E%3Cpath%20stroke-opacity='.03'%20d='M0%202.5H432M0%2026.5H432M0%2050.5H432M0%2074.5H432M0%2098.5H432M0%20122.5H432M0%20146.5H432M0%20170.5H432'/%3E%3Cpath%20stroke-opacity='.018'%20d='M2.5%200V24M74.5%200V24M146.5%200V24M218.5%200V24M290.5%200V24M362.5%200V24M38.5%2024V48M110.5%2024V48M182.5%2024V48M254.5%2024V48M326.5%2024V48M398.5%2024V48M2.5%2048V72M74.5%2048V72M146.5%2048V72M218.5%2048V72M290.5%2048V72M362.5%2048V72M38.5%2072V96M110.5%2072V96M182.5%2072V96M254.5%2072V96M326.5%2072V96M398.5%2072V96M2.5%2096V120M74.5%2096V120M146.5%2096V120M218.5%2096V120M290.5%2096V120M362.5%2096V120M38.5%20120V144M110.5%20120V144M182.5%20120V144M254.5%20120V144M326.5%20120V144M398.5%20120V144M2.5%20144V168M74.5%20144V168M146.5%20144V168M218.5%20144V168M290.5%20144V168M362.5%20144V168M38.5%20168V192M110.5%20168V192M182.5%20168V192M254.5%20168V192M326.5%20168V192M398.5%20168V192'/%3E%3C/g%3E%3C/svg%3E")
      0 0/432px 192px repeat,
    var(--bg);
}
/* three slow-drifting light sources behind everything -- the corner blooms
   above are static mood lighting, these actually move. Grey, not green: this
   is pale light shifting over wet stone, not firelight (the .ember class name
   predates the palette). Transform + opacity only (compositor-friendly), and
   the sitewide prefers-reduced-motion rule below already kills all animation
   for anyone who's asked for that. */
/* background repaint: bricks removed 2026-07-26. Dark + dungeony on purpose.
   Two corners now carry a faint themed wash -- DCC orange top-left, system
   teal bottom-right -- as a soft warm/cool diagonal; the other two stay
   near-neutral so they don't compete. This redeclares background so it fully
   overrides the earlier brick paint above. */
body::before{
  /* corner-bloom radii are vars so the small-screen rule below can shrink them
     without re-listing the whole background stack (incl. the big tile) */
  --bloom-tr:1250px 560px;
  --bloom-tl:1360px 700px;
  --bloom-bl:900px 560px;
  --bloom-br:1320px 760px;
  background:
    radial-gradient(var(--bloom-tr) at 84% -6%, rgba(236,188,128,.118), transparent 60%),
    radial-gradient(var(--bloom-tl) at -12% 7%, rgba(250,126,38,.48), transparent 60%),
    radial-gradient(var(--bloom-bl) at -6% 104%, rgba(212,208,204,.100), transparent 58%),
    radial-gradient(var(--bloom-br) at 108% 94%, rgba(70,190,205,.38), transparent 62%),
    /* skibbity: a faint cosmic nebula drifting UNDER the corner blooms. The
       near-opaque veil directly above it in this stack knocks it back to a
       whisper, so it reads as deep-space haze behind the stone rather than
       wallpaper -- visible mostly through the gutters between cards. Static
       layer, no animation = mobile-safe. Root-relative URL resolves no matter
       where Omnia serves this stylesheet from (matches app.js BASE). */
    linear-gradient(rgba(12,10,9,.80), rgba(12,10,9,.80)),
    url("/.jared/DCCv2/images/skibbity2.jpg") center center/cover no-repeat,
    var(--bg);
}
/* Desktop only: drift the nebula on a slow ~84s breath so deep space feels
   alive. ONLY the nebula moves -- the blooms + veil are full-bleed gradients
   (background-position is a no-op on them) and var(--bg) is a solid colour,
   so the single animated background-position pans nothing but the cover image.
   Gated hover:hover + no-preference; mobile keeps the static frame (frozen-
   embers policy). Proven via headless: blooms pixel-identical across endpoints. */
@media (prefers-reduced-motion:no-preference) and (hover:hover){
  body::before{animation:nebulaDrift 84s ease-in-out infinite}
}
@keyframes nebulaDrift{
  0%,100%{background-position:center 43%}
  50%{background-position:center 57%}
}
/* phones: desktop blooms are wider than the viewport and flatten into a wash,
   eating contrast -- shrink them a good amount so they read as corner glows. */
@media(max-width:640px){
  body::before{
    --bloom-tr:560px 300px;
    --bloom-tl:600px 360px;
    --bloom-bl:460px 300px;
    --bloom-br:580px 400px;
  }
}
/* xl screens: nudge the two hero blooms (orange + teal) up a bit so they stay
   proportional on very wide viewports instead of hugging the corners. */
@media(min-width:1600px){
  body::before{
    --bloom-tl:1780px 920px;
    --bloom-br:1720px 1000px;
  }
}
.ambient{position:fixed;inset:0;z-index:-1;overflow:hidden;pointer-events:none}
.ember{
  position:absolute;border-radius:50%;filter:blur(80px);
  mix-blend-mode:screen;opacity:.44;will-change:transform;
}
.ember.e1{
  width:600px;height:600px;top:-16%;left:4%;
  background:radial-gradient(circle, rgba(208,208,208,.42), transparent 70%);
  animation:emberDrift1 46s ease-in-out infinite;
}
.ember.e2{
  width:520px;height:520px;bottom:-18%;right:0%;
  background:radial-gradient(circle, rgba(148,148,148,.34), transparent 70%);
  animation:emberDrift2 38s ease-in-out infinite;
}
.ember.e3{
  width:560px;height:560px;top:28%;right:-16%;
  background:radial-gradient(circle, rgba(180,180,180,.38), transparent 70%);
  animation:emberDrift3 54s ease-in-out infinite;
}
@keyframes emberDrift1{
  0%,100%{transform:translate(0,0) scale(1)}
  50%{transform:translate(9%,10%)}
}
@keyframes emberDrift2{
  0%,100%{transform:translate(0,0) scale(1)}
  50%{transform:translate(-11%,-8%)}
}
@keyframes emberDrift3{
  0%,100%{transform:translate(0,0) scale(1)}
  50%{transform:translate(-8%,9%)}
}
/* fine stone grain -- pure CSS noise (SVG turbulence), tiled small and kept
   nearly invisible via low opacity + overlay blend, not a flat grey wash */
body::after{
  content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
  opacity:.085;mix-blend-mode:overlay;background-repeat:repeat;
  background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20width%3D%27180%27%20height%3D%27180%27%3E%3Cfilter%20id%3D%27n%27%3E%3CfeTurbulence%20type%3D%27fractalNoise%27%20baseFrequency%3D%270.85%27%20numOctaves%3D%272%27%20stitchTiles%3D%27stitch%27%20result%3D%27t%27/%3E%3CfeColorMatrix%20in%3D%27t%27%20type%3D%27matrix%27%20values%3D%270%200%200%200%201%20%200%200%200%200%201%20%200%200%200%200%201%20%200%200%200%201%200%27/%3E%3C/filter%3E%3Crect%20width%3D%27100%25%27%20height%3D%27100%25%27%20filter%3D%27url%28%2523n%29%27/%3E%3C/svg%3E");
  background-size:180px 180px;
}
/* mobile / touch: kill the always-on GPU load that heats phones AND janks
   scrolling. The blurred screen-blend embers re-composite every scroll
   frame, so HIDE them outright (the corner blooms stay for ambiance). Also
   drop the sticky-header + reveal backdrop blur, the blend grain, and the
   spinning god-rays. Desktop keeps all of it. */
@media (hover:none){
  .ambient{display:none}
  .topbar{-webkit-backdrop-filter:none;backdrop-filter:none}
  body::after{display:none}
  .variant-reveal{-webkit-backdrop-filter:none;backdrop-filter:none}
  .vr-rays{animation:none}
}

/* achievement variant of the notice bar */
.alertbar.achievement{
  border-color:rgba(255,106,43,.5);
  background:linear-gradient(90deg, rgba(255,106,43,.16), rgba(43,217,232,.09));
}
.alertbar.achievement .alert-tag{color:var(--gold)}

/* ---------- topbar without the search field ----------
   The search input was removed; the brand alone shouldn't leave a dead bar. */
.topbar-inner{padding-top:12px;padding-bottom:12px}
.brand .title{font-size:20px}
@media(min-width:820px){ .brand .title{font-size:23px} }

/* ============================================================
   VARIANT-COVER REVEAL  (easter egg: click the book you're on)
   A "legendary loot" unboxing: dim the room, fire god-rays behind
   the alternate cover, slam it in, sweep a gleam across it, rain
   confetti, and hold until dismissed. Screen shake lives on
   #app.shaking; confetti pieces are injected by burstConfetti().
   ============================================================ */
.variant-reveal{
  position:fixed;inset:0;z-index:300;
  display:flex;padding:18px 12px;
  background:radial-gradient(circle at 50% 42%, rgba(232,163,61,.20), rgba(8,7,6,.85) 58%, rgba(8,7,6,.94));
  -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);
  /* clip everything -- especially the oversized spinning .vr-rays, which
     backdrop-filter traps inside this box; a scrollable overlay let its
     rotating bounding box grow/shrink the page height in a loop. The card
     itself scrolls (below) when it's taller than the screen. */
  overflow:hidden;
}
/* the card gets its own full-viewport scroll layer so its glow/ring/shake are
   never clipped anywhere visible -- the overlay's overflow:hidden above only
   has to keep trapping the oversized spinning .vr-rays */
.vr-scroll{
  position:absolute;inset:0;z-index:1;
  display:flex;overflow-y:auto;overscroll-behavior:contain;
  padding:18px 12px;
  /* hide the scrollbar: a classic Windows bar steals ~17px from the right and
     shifts the card off the rays' fixed center -- still scrolls by wheel/touch */
  scrollbar-width:none;
}
.vr-scroll::-webkit-scrollbar{display:none}
/* slow-spinning god-rays behind the card, faded out toward the edges */
.vr-rays{
  position:fixed;left:50%;top:44%;width:75vmax;height:75vmax;
  transform:translate(-50%,-50%) scale(2);pointer-events:none;
  background:repeating-conic-gradient(from 0deg, rgba(232,163,61,.13) 0deg 5deg, transparent 5deg 12deg);
  -webkit-mask:radial-gradient(circle, #000 0, transparent 60%);
          mask:radial-gradient(circle, #000 0, transparent 60%);
  will-change:transform;
  animation:vr-spin 26s linear infinite, vr-fadein .9s ease-out both;
}
.vr-card{
  position:relative;z-index:1;margin:auto;
  display:flex;flex-direction:column;align-items:center;text-align:center;
  gap:13px;max-width:340px;width:100%;
}
.vr-frame{
  position:relative;width:min(62vw,232px);border-radius:10px;overflow:hidden;
  box-shadow:0 0 0 3px var(--gold), 0 0 46px -4px rgba(232,163,61,.8), 0 30px 60px -22px #000;
  animation:vr-shake .5s .56s ease-in-out both;
}
/* reserve the cover's height (2:3 fallback until it loads, then its true ratio)
   so it can't collapse-to-zero then reflow mid-pop -- that was the iOS hitch */
.vr-frame img{width:100%;height:auto;display:block;aspect-ratio:auto 2 / 3}
/* one-shot diagonal shine sweeping across the cover after it lands */
.vr-gleam{
  position:absolute;top:0;left:0;width:55%;height:100%;pointer-events:none;
  background:linear-gradient(100deg, transparent, rgba(255,255,255,.5), transparent);
  transform:translateX(-160%) skewX(-16deg);
}
.vr-msg{font-size:14px;line-height:1.5;color:var(--ink);margin:2px 0 0}
.vr-ed{
  font-family:var(--font-head);font-weight:600;
  font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-dim);text-shadow:0 1px 3px rgba(0,0,0,.5);margin:0;
}
.vr-ed .vr-title{display:block;color:var(--gold);letter-spacing:.06em;font-size:14px;margin-bottom:2px}
.vr-close{
  margin-top:5px;padding:11px 26px;border-radius:99px;
  background:var(--gold);color:#181103;font-family:var(--font-head);font-weight:700;
  font-size:14px;letter-spacing:.06em;
  box-shadow:0 10px 26px -12px rgba(232,163,61,.9);
  transition:filter .16s, transform .16s;
}
.vr-close:hover{filter:brightness(1.08)}
.vr-close:active{transform:translateY(1px)}
/* stats readout styled after the System boot terminal (.sb-card): cyan mono,
   faint scanlines, glowing ring -- the same "AI interface" voice as the intro */
.vr-stats{
  display:flex;flex-direction:column;gap:7px;margin:1px 0 0;width:100%;max-width:300px;
  font-family:var(--font-mono);text-align:left;
  padding:12px 14px 11px;border-radius:10px;
  background:
    repeating-linear-gradient(0deg, rgba(91,196,240,.045) 0 1px, transparent 1px 3px),
    linear-gradient(180deg, rgba(10,20,28,.82), rgba(6,10,14,.82));
  box-shadow:0 0 0 1px rgba(91,196,240,.35), 0 0 40px -10px rgba(43,217,232,.4), inset 0 0 30px -16px rgba(91,196,240,.5);
}
.vr-stats::before{
  content:"// FLOOR TELEMETRY";
  font-size:9.5px;letter-spacing:.24em;text-transform:uppercase;
  color:rgba(91,196,240,.7);text-shadow:0 0 10px rgba(91,196,240,.4);margin-bottom:2px;
}
.vr-stat{display:flex;justify-content:space-between;align-items:baseline;gap:12px}
.vr-stat dt{margin:0;font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:rgba(91,196,240,.78);white-space:nowrap;text-shadow:0 0 9px rgba(91,196,240,.35)}
.vr-stat dt::before{content:"> ";color:#ff7a2e;text-shadow:0 0 8px rgba(255,122,46,.6)}
.vr-stat dd{margin:0;font-size:13px;color:#eaf7fb;text-align:right;font-variant-numeric:tabular-nums;text-shadow:0 0 8px rgba(91,196,240,.35)}
.vr-num{color:#ff9a4d;text-shadow:0 0 10px rgba(255,122,46,.75);font-variant-numeric:tabular-nums;font-size:15px}
/* one-off lore subnote (Book 6: the Floor 7 incident) -- the glyph is a tiny
   Prince Rupert's drop, for those who know. v1 of the glyph looked like a
   sperm (client confirmed), hence the v1.49 redraw: bulb hangs low-left, tail
   crooks up and over, wavy taper down-right, like the real glass thing */
.vr-note{
  display:flex;align-items:center;gap:8px;margin-top:3px;padding-top:8px;
  border-top:1px dashed rgba(91,196,240,.22);
  font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;
  color:rgba(91,196,240,.62);text-shadow:0 0 10px rgba(91,196,240,.3);
}
.vr-drop{width:23px;height:13px;flex:none;color:#5bc4f0;opacity:.62;filter:drop-shadow(0 0 4px rgba(91,196,240,.4))}
/* the System's single-use disclaimer -- warning-orange counterpart to the cyan notes */
.vr-warn{
  margin:2px 0 0;padding-top:7px;border-top:1px dashed rgba(255,122,46,.25);
  font-size:9px;letter-spacing:.16em;text-transform:uppercase;line-height:1.75;
  color:rgba(255,154,77,.55);text-shadow:0 0 8px rgba(255,122,46,.22);
}
.vr-warn b{color:rgba(255,154,77,.95);font-weight:700;text-shadow:0 0 9px rgba(255,122,46,.5)}
@media (max-width:430px){
  .vr-scroll{padding:14px 10px}
  .vr-card{gap:10px}
  .vr-frame{width:min(58vw,196px)}
  .vr-msg{font-size:13px;line-height:1.45}
  .vr-ed{font-size:11px}
  .vr-ed .vr-title{font-size:13px}
  .vr-close{padding:10px 22px;font-size:13px}
  .vr-stats{padding:10px 12px 9px}
  .vr-stat dt{font-size:9.5px}
  .vr-stat dd{font-size:12.5px}
  .vr-num{font-size:14px}
}
/* short viewports: shrink the cover so the fuller telemetry panel + warning
   still fit without the popup needing to scroll */
@media (max-height:640px){
  .vr-scroll{padding:10px 12px}
  .vr-frame{width:min(40vw,142px)}
  .vr-card{gap:8px}
  .vr-stats{gap:5px;padding:9px 12px 8px}
  .vr-warn{font-size:8.5px;letter-spacing:.12em;line-height:1.6}
  .vr-close{padding:9px 20px;font-size:12.5px}
}
.vr-progress{
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;
  color:var(--ink-dim);text-shadow:0 1px 3px rgba(0,0,0,.5);margin:3px 0 0;
}

/* entrance: backdrop fades via the Vue transition on the root; the card pop +
   gleam run as their own animations on the elements themselves. Because v-if
   remounts them fresh each reveal, they replay every time -- and Vue's short
   opacity transition on the root can't truncate the longer card bounce. */
.variant-enter-active{transition:opacity .28s ease}
.variant-leave-active{transition:opacity .32s ease}
.variant-enter-from,.variant-leave-to{opacity:0}
.vr-card{animation:vr-pop .62s cubic-bezier(.2,1.35,.4,1) both}
.vr-gleam{animation:vr-gleam 1.05s ease-out .34s both}

@keyframes vr-spin{to{transform:translate(-50%,-50%) scale(2) rotate(360deg)}}
@keyframes vr-fadein{from{opacity:0}to{opacity:1}}
@keyframes vr-pop{
  0%{transform:scale(.4) rotate(-7deg);opacity:0}
  60%{transform:scale(1.06) rotate(2deg);opacity:1}
  100%{transform:scale(1) rotate(0)}
}
@keyframes vr-gleam{
  0%{transform:translateX(-160%) skewX(-16deg)}
  100%{transform:translateX(320%) skewX(-16deg)}
}
/* the "boof" impact -- the freshly-landed cover jolts, then settles */
@keyframes vr-shake{
  0%,100%{transform:translate3d(0,0,0)}
  14%{transform:translate3d(-6px,2px,0)}
  28%{transform:translate3d(7px,-3px,0)}
  42%{transform:translate3d(-7px,-2px,0)}
  56%{transform:translate3d(5px,3px,0)}
  70%{transform:translate3d(-4px,-2px,0)}
  84%{transform:translate3d(2px,1px,0)}
}

/* confetti: fixed full-screen layer, pieces injected by burstConfetti() */
.confetti-layer{position:fixed;inset:0;z-index:340;pointer-events:none;overflow:hidden}
/* floating back-to-top -- appears after a little scroll; hidden while a modal
   (entry drawer / variant reveal) is open via the :class gate in the markup */
.to-top{
  position:fixed;right:14px;z-index:95;
  bottom:calc(14px + env(safe-area-inset-bottom, 0px));
  width:46px;height:46px;border-radius:50%;flex:none;
  display:flex;align-items:center;justify-content:center;
  color:#181103;background:var(--gold);
  box-shadow:0 10px 24px -8px rgba(0,0,0,.85),0 0 0 1px rgba(232,163,61,.5);
  opacity:0;transform:translateY(14px) scale(.92);pointer-events:none;
  transition:opacity .25s ease,transform .25s ease,filter .16s ease;
}
.to-top.show{opacity:1;transform:translateY(0) scale(1);pointer-events:auto}
.to-top:hover{filter:brightness(1.08)}
.to-top:active{transform:translateY(1px) scale(.97)}
.to-top svg{width:22px;height:22px}
.cfetti{
  position:absolute;top:-8vh;opacity:.96;will-change:transform;
  animation-name:cfetti-fall;animation-timing-function:cubic-bezier(.35,.12,.5,1);animation-fill-mode:both;
}
@keyframes cfetti-fall{
  0%{transform:translate3d(0,0,0) rotate(0);opacity:0}
  8%{opacity:.96}
  100%{transform:translate3d(var(--drift,0),112vh,0) rotate(calc(var(--rot,180deg) + 900deg));opacity:.9}
}

/* screen shake, fired on reveal by toggling #app.shaking. Transforming #app
   also carries the fixed reveal + confetti with it, so the whole frame jolts
   together for ~0.6s and then settles. */
@keyframes screenshake{
  0%,100%{transform:translate3d(0,0,0)}
  10%{transform:translate3d(-6px,3px,0)}
  20%{transform:translate3d(7px,-4px,0)}
  30%{transform:translate3d(-8px,-2px,0)}
  40%{transform:translate3d(6px,5px,0)}
  50%{transform:translate3d(-5px,-3px,0)}
  60%{transform:translate3d(6px,3px,0)}
  70%{transform:translate3d(-4px,-3px,0)}
  80%{transform:translate3d(3px,2px,0)}
  90%{transform:translate3d(-2px,-1px,0)}
}
/* screen-shake retired: it transformed #app and broke fixed positioning of
   the reveal/confetti when scrolled. The cover now shakes via vr-shake. */

/* ============================================================
   OMNIA / BOOTSTRAP 4 COMPATIBILITY  — keep this LAST in the file
   This page is CF-included into an Omnia (Bootstrap 4) shell whose global
   stylesheet fights the compendium. Everything here is scoped to #app so it
   undoes that interference without leaking back onto Omnia's own chrome.
   Confirmed collisions (omnia.css / 1984.css):
     .overlay{position:absolute!important;opacity:.5}   <- their own scrim class
     .h1..h6,h1..h6{margin-bottom:1rem!important}
     h1..h6{color:#333!important}
     BS4 reboot: p, ul, ol, dl, blockquote get margin-bottom:1rem
     BS4 .toast defaults to opacity:0 — sidestepped by renaming our class to .dcc-toast
   ============================================================ */

/* Kill the forced heading margin and the dark #333 heading colour. We only
   touch spacing + colour here; font/size/weight stay with our own type rules
   (a broader reset would out-specify and clobber them). */
#app h1,#app h2,#app h3,#app h4,#app h5,#app h6{
  margin-top:0 !important;
  margin-bottom:0 !important;
  color:var(--ink) !important;   /* beat 1984.css h1..h6{color:#333!important}; every heading here is --ink */
}
/* the one heading that intentionally carried a little breathing room */
#app .sheet-body h2{ margin:4px 0 2px !important; }

/* BS4's reboot adds margin-bottom:1rem to flow content; the compendium spaces
   everything with flex gaps (matching our own `*{margin:0}` reset), so zero it
   back out. #app scoping out-specifies the bare-element rules, no !important needed. */
#app p,
#app ul,#app ol,#app dl,#app dd,
#app blockquote,#app figure,#app table{ margin:0; }
#app ul,#app ol{ padding-left:0; list-style:none; }

/* ================= System "boot" intro overlay ================= */
.system-boot{
  position:fixed;inset:0;z-index:400;
  display:flex;align-items:center;justify-content:center;padding:24px;
  background:radial-gradient(circle at 50% 40%, rgba(20,40,54,.5), #050607 62%, #000);
  cursor:pointer;overflow:hidden;
}
.sb-scan{position:absolute;inset:0;pointer-events:none;opacity:.6;
  background:repeating-linear-gradient(0deg, rgba(91,196,240,.05) 0 1px, transparent 1px 3px);
  mix-blend-mode:screen;}
.sb-card{
  position:relative;max-width:min(92vw,540px);width:100%;
  font-family:var(--font-mono);color:var(--sys);
  text-shadow:0 0 12px rgba(91,196,240,.5);
  padding:22px 20px;border-radius:10px;
  background:linear-gradient(180deg, rgba(10,20,28,.72), rgba(6,10,14,.72));
  box-shadow:0 0 0 1px rgba(91,196,240,.35), 0 0 60px -12px rgba(43,217,232,.5), inset 0 0 40px -20px rgba(91,196,240,.6);
}
.sb-head{font-size:11px;letter-spacing:.26em;text-transform:uppercase;
  color:rgba(91,196,240,.75);display:flex;align-items:center;gap:8px;margin-bottom:14px;}
.sb-dot{width:8px;height:8px;border-radius:50%;background:#ff7a2e;flex:none;
  box-shadow:0 0 12px rgba(255,122,46,.9);animation:sb-pulse 1.1s ease-in-out infinite}
.sb-line{font-size:clamp(13px,3.6vw,16px);line-height:1.5;margin:0 0 8px;opacity:0}
.sb-line b{color:#fff;font-weight:700}
.sb-l1{color:#fff;font-weight:700;letter-spacing:.02em}
/* orange system accents: the prompt caret that 'writes' each line + the live
   entity tally, against the teal card -- DCC's warm/cool pairing */
.sb-p{color:#ff7a2e;font-weight:700;margin-right:2px;text-shadow:0 0 8px rgba(255,122,46,.6)}
.sb-line b.sb-num{color:#ff9a4d;text-shadow:0 0 10px rgba(255,122,46,.75);font-variant-numeric:tabular-nums}
.sb-line b.sb-kill{color:#ff5a4a;text-shadow:0 0 9px rgba(212,72,62,.6)}
.sb-snark{color:var(--ink-dim);font-style:italic;text-shadow:none;opacity:.9;
  font-size:clamp(12px,3.2vw,14px);margin-top:6px}
.sb-sign{color:#fff}
/* 'of 13,000,000' -- smaller/dimmer so the serial reads first */
.sb-of{font-size:.78em;color:var(--ink-dim);opacity:.85;letter-spacing:.01em;font-variant-numeric:tabular-nums}
.sb-l6{margin-bottom:0}
.sb-cursor{display:inline-block;width:.55em;height:1.05em;margin-left:3px;vertical-align:-.16em;
  background:#ff7a2e;box-shadow:0 0 10px rgba(255,122,46,.9);animation:sb-blink 1s steps(1) infinite}
.sb-skip{position:absolute;top:12px;right:12px;
  font-family:var(--font-mono);font-size:10px;letter-spacing:.16em;
  color:rgba(91,196,240,.7);background:transparent;
  display:flex;align-items:center;gap:3px;padding:4px 6px;border-radius:6px;
  transition:color .15s,background .15s;}
.sb-skip:hover{color:#fff;background:rgba(91,196,240,.12)}
.sb-skip svg{width:12px;height:12px}
@keyframes sb-pulse{0%,100%{opacity:1}50%{opacity:.35}}
@keyframes sb-blink{0%,49%{opacity:1}50%,100%{opacity:0}}
@keyframes sb-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:no-preference){
  .sb-l1{animation:sb-in .4s .15s ease both}
  .sb-l2{animation:sb-in .4s .50s ease both}
  .sb-l3{animation:sb-in .4s .95s ease both}
  .sb-l4{animation:sb-in .4s 1.35s ease both}
  .sb-l6{animation:sb-in .4s 1.80s ease both}
}
@media (prefers-reduced-motion:reduce){
  .sb-line{opacity:1}
  .sb-dot,.sb-cursor{animation:none}
}
.boot-enter-active{transition:opacity .3s}
.boot-leave-active{transition:opacity .5s ease, transform .5s ease}
.boot-enter-from{opacity:0}
.boot-leave-to{opacity:0;transform:scale(1.04)}

/* ============================================================
   TACTILE POLISH -- instant taps + press feedback everywhere
   ============================================================ */
/* kill the ~300ms tap delay / double-tap-zoom lag so every tap fires now */
button,a,summary,select,input,label,.card,.chip,.tab,.book-card,.to-top,.readbtn,.unlockbtn,.sheet-close,.reset-top{touch-action:manipulation}
/* a quick press-in on tap = the UI acknowledges every decision instantly (no
   transition on purpose -- the snap is what reads as 'instant') */
.card:active{transform:scale(.985)}
.chip:active,.tab:active{transform:scale(.955)}
.book-card:active:not(.locked){transform:scale(.965)}
.readbtn:active,.unlockbtn:active,.empty-clear:active,.sheet-close:active{transform:scale(.94)}

/* subtle release tag in the footer -- reads like a maintained product */
.version{
  margin:16px 0 0;text-align:center;
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--ink-faint);opacity:.7;
}
.version b{color:var(--ink-dim);font-weight:700;letter-spacing:.12em}

/* ============================================================
   RESET CONFIRM DIALOG -- on-brand replacement for native confirm()
   ============================================================ */
.confirm-scrim{
  position:fixed;inset:0;z-index:320;display:flex;align-items:center;justify-content:center;
  padding:20px;background:rgba(8,7,6,.82);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
}
.confirm-box{
  width:100%;max-width:360px;text-align:left;
  background:linear-gradient(180deg,#1b1512,#141010);
  border:1px solid var(--line);border-radius:14px;padding:20px 20px 16px;
  box-shadow:0 30px 70px -24px #000,0 0 0 1px rgba(0,0,0,.5);
}
.confirm-title{
  margin:0 0 9px;font-family:var(--font-head);font-weight:700;
  font-size:17px;letter-spacing:.05em;text-transform:uppercase;color:var(--red);
}
.confirm-msg{margin:0 0 18px;font-size:13.5px;line-height:1.5;color:var(--ink-dim)}
.confirm-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:8px}
.confirm-cancel,.confirm-go{
  font-family:var(--font-head);font-weight:700;font-size:13px;letter-spacing:.04em;
  border-radius:99px;padding:10px 18px;cursor:pointer;
  transition:filter .16s,background .16s,color .16s,border-color .16s,transform .12s;
}
.confirm-cancel{background:transparent;border:1px solid var(--line);color:var(--ink-dim)}
.confirm-cancel:hover{border-color:var(--ink-faint);color:var(--ink)}
.confirm-go{background:var(--red);border:1px solid var(--red);color:#160b0a}
.confirm-go:hover{filter:brightness(1.08)}
.confirm-go:active,.confirm-cancel:active{transform:translateY(1px)}
/* cyan variant for the unlock-all dialog -- a System deal, not a destructive act */
.confirm-box.cyan{
  background:linear-gradient(180deg,#101a20,#0c1216);
  border-color:rgba(91,196,240,.35);
  box-shadow:0 30px 70px -24px #000,0 0 0 1px rgba(91,196,240,.22),0 0 50px -14px rgba(43,217,232,.4);
}
.confirm-box.cyan .confirm-title{color:var(--sys);text-shadow:0 0 14px rgba(43,217,232,.4)}
.confirm-box.cyan .confirm-go{background:var(--sys);border-color:var(--sys);color:#04161a}
.confirm-enter-active,.confirm-leave-active{transition:opacity .2s ease}
.confirm-enter-from,.confirm-leave-to{opacity:0}
.confirm-enter-active .confirm-box{transition:transform .22s cubic-bezier(.2,1.2,.4,1)}
.confirm-enter-from .confirm-box{transform:scale(.94)}
