/* Ages of Myth — shared stylesheet for the GENERATED pages (the card browser at
 * /cards and the mythology hubs at /mythology). Written by hand; the pages that
 * link it are written by tools/site-build.ts.
 *
 * 🛑 The hand-written pages (index.html, how-to-play.html, support.html, the
 * legal pages) deliberately keep their own inline <style> and do NOT link this
 * file. That duplicates the design tokens, which is the cheap half of the
 * trade: 254 generated files must share one cacheable stylesheet, and pulling
 * the live pages onto it would put a shared-CSS regression in front of every
 * App Review / store-listing URL at once. If you change a token here, change it
 * in index.html's <style> too — they are meant to look identical.
 *
 * Rarity accents are copied from apps/mobile/src/match/CeremonyBeats.tsx
 * RARITY_COLOR so a card reads the same colour on the web as in the game.
 */

/* ---- Cinzel, self-hosted ------------------------------------------------- */
/* The app's display face (`fonts.displayHeavy` = Cinzel_900Black,
 * `fonts.display` = Cinzel_700Bold — apps/mobile/src/theme.ts), so a card name
 * on the web is the same typeface as in the game.
 *
 * 🛑 SELF-HOSTED, NOT fonts.googleapis.com. A Google Fonts <link> makes every
 * page load a third-party request that discloses the visitor's IP before any
 * consent is asked — and this site already gates its ad pixel behind an EU
 * consent prompt (oaiq.js), so reaching a third party for FONTS would walk
 * straight past the thing that prompt exists for. Two files, 40 KB, no
 * third-party origin, and the privacy policy needs no amendment.
 *
 * Cinzel is a VARIABLE font: Google serves the same woff2 for 700 and 900, so
 * one file per unicode subset covers both weights. Card names need latin-ext —
 * Ryūjin, Ōkuninushi and friends carry U+014C/U+016B/U+0113.
 *
 * Licence: SIL Open Font License 1.1. The licence text is redistributed beside
 * the binaries at /media/fonts/OFL.txt, which is what the OFL requires. */
@font-face {
  font-family: "Cinzel";
  font-style: normal;
  font-weight: 700 900;
  font-display: swap;
  src: url("/media/fonts/cinzel-latin.woff2") format("woff2");
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
    U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Cinzel";
  font-style: normal;
  font-weight: 700 900;
  font-display: swap;
  src: url("/media/fonts/cinzel-latin-ext.woff2") format("woff2");
  unicode-range:
    U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

:root {
  --sand-bg: #1a140e;
  --sand-bg-2: #120d08;
  --sand-panel: #221912;
  --sand-panel-2: #2b2016;
  --sand-text: #e9ddc7;
  --sand-text-muted: #b8a98e;
  --gold: #d4a04a;
  --gold-bright: #f0c66b;
  --turquoise: #3fb6a8;
  --rule: #3a2c1f;
  --link: #f0c66b;

  --r-common: #9aa0a6;
  --r-uncommon: #5fb97b;
  --r-rare: #4fa3d1;
  --r-epic: #b07ad1;
  --r-legendary: #e0b25a;

  /* The hero composition reproduces apps/mobile/src/cards/detail/CardDetailHero.tsx
     layer for layer, so these are the app's own values, not web approximations.
     `--ink` is `#0C0704`, the colour every scrim on that hero is mixed from;
     `--bg-deep` is theme.ts `colors.bgDeep`; `--band` is the flavour band the
     foot scrim resolves into, so the art appears to fade into the panel. */
  --font-display: "Cinzel", Georgia, "Iowan Old Style", "Times New Roman", serif;
  --ink: 12, 7, 4;
  --bg-deep: #100a06;
  --band: #140d08;
  --gold-app: #e8b964;
  --gold-name: #f7dda0;
  --text-app: #f6efe3;
  --text-muted-app: #cdb89a;

  /* Pantheon accents — theme.ts lines 29-37. The power value and the pantheon
     line on the hero are drawn in the figure's own accent. */
  --p-norse: #7fb2e5;
  --p-greek: #e0855a;
  --p-egyptian: #e0b65a;
  --p-persian: #c77dd6;
  --p-japanese: #e0607a;
  --p-aztec: #3dbd8e;
  --p-bharat: #8f86e8;

  /* Rarity accents as the CHIP uses them — rarityMeta.ts RARITY_ACCENT, which is
     a different set from the CeremonyBeats palette above (that one tints reveal
     beats). The chip fill is always the opaque #140D08, never an alpha wash. */
  --ra-common: #9a8e78;
  --ra-uncommon: #6fcfa3;
  --ra-rare: #5a8bff;
  --ra-epic: #b16fe0;
  --ra-legendary: #ffb84d;
}

* {
  box-sizing: border-box;
}
html {
  -webkit-text-size-adjust: 100%;
}
body {
  margin: 0;
  background: var(--sand-bg);
  color: var(--sand-text);
  font-family:
    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 16px;
  line-height: 1.6;
}
a {
  color: var(--link);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
a:hover {
  color: var(--gold-bright);
}
img {
  max-width: 100%;
}

/* ---- Chrome (mirrors index.html) ---------------------------------------- */
.topnav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  max-width: 1040px;
  margin: 0 auto;
  padding: 0.85rem 1.25rem;
}
.topnav .brand-logo {
  height: 44px;
  width: auto;
  display: block;
}
.topnav .nav-links {
  display: flex;
  align-items: center;
  gap: 1.15rem;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.topnav .nav-links a {
  color: var(--sand-text-muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.95rem;
}
.topnav .nav-links a:hover,
.topnav .nav-links a[aria-current="page"] {
  color: var(--gold-bright);
}
.topnav .nav-links .nav-cta {
  background: linear-gradient(180deg, var(--gold-bright), var(--gold));
  color: #1a140e;
  padding: 0.5rem 1rem;
  border-radius: 9px;
  font-weight: 700;
  font-size: 0.9rem;
}
.topnav .nav-links .nav-cta:hover {
  color: #1a140e;
  filter: brightness(1.06);
}
@media (max-width: 560px) {
  .topnav {
    flex-direction: column;
    gap: 0.6rem;
  }
  .topnav .nav-links {
    justify-content: center;
  }
}

main {
  max-width: 1040px;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 2rem;
}
main.narrow {
  max-width: 760px;
}

.crumbs {
  font-size: 0.85rem;
  color: var(--sand-text-muted);
  margin: 0 0 1.25rem;
}
.crumbs a {
  color: var(--sand-text-muted);
}
.crumbs span[aria-hidden="true"] {
  opacity: 0.55;
  margin: 0 0.35rem;
}

h1 {
  font-size: 2.1rem;
  color: var(--gold-bright);
  margin: 0 0 0.4rem;
  line-height: 1.15;
}
.page-lead {
  color: var(--sand-text-muted);
  margin: 0 0 1.75rem;
  max-width: 62ch;
}

/* ---- Filter bar (card index) -------------------------------------------- */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin: 0 0 1.5rem;
  padding: 0 0 1.25rem;
  border-bottom: 1px solid var(--rule);
}
.filters input[type="search"] {
  flex: 1 1 200px;
  min-width: 170px;
  background: var(--sand-bg-2);
  border: 1px solid var(--rule);
  border-radius: 9px;
  color: var(--sand-text);
  padding: 0.55rem 0.8rem;
  font-size: 0.95rem;
  font-family: inherit;
}
.filters input[type="search"]:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 1px;
}
.filters select {
  background: var(--sand-bg-2);
  border: 1px solid var(--rule);
  border-radius: 9px;
  color: var(--sand-text);
  padding: 0.55rem 0.7rem;
  font-size: 0.95rem;
  font-family: inherit;
}
.filter-count {
  font-size: 0.85rem;
  color: var(--sand-text-muted);
  flex: 0 0 auto;
}

/* ---- Card grid ---------------------------------------------------------- */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 0.9rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.card-grid > li {
  margin: 0;
}
.card-tile {
  display: block;
  background: var(--sand-panel);
  border: 1px solid var(--rule);
  border-top: 3px solid var(--tile-accent, var(--rule));
  border-radius: 11px;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  height: 100%;
}
.card-tile:hover {
  border-color: var(--gold);
  color: inherit;
}
.card-tile .thumb {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: var(--sand-bg-2);
}
.card-tile .tile-body {
  padding: 0.55rem 0.7rem 0.7rem;
}
.card-tile .tile-name {
  display: block;
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--sand-text);
  line-height: 1.25;
}
.card-tile .tile-meta {
  display: block;
  font-size: 0.78rem;
  color: var(--sand-text-muted);
  margin-top: 0.15rem;
  text-transform: capitalize;
}
.card-grid > li[hidden] {
  display: none;
}
.empty-note {
  color: var(--sand-text-muted);
  padding: 2rem 0;
  text-align: center;
}

/* ---- The living-hero card banner ---------------------------------------- */
/* A layer-for-layer port of the in-game card-detail hero
 * (apps/mobile/src/cards/detail/CardDetailHero.tsx). The clip alone is just a
 * rectangle of video; what makes it read as a CARD is the stack over it — a flat
 * ground wash, a left-to-right readability scrim, a 92px foot scrim that
 * resolves into the panel colour, the identity column (rarity chip, name in
 * Cinzel, pantheon line, tribe chips) and the Favor/Power plaque.
 *
 * Geometry: the app computes hero height as 38% of the panel's max height,
 * clamped 176–320pt on phones, over a panel up to 430pt wide — i.e. ~430:262 on
 * a typical phone. That ratio is used here rather than a round 16:10 so the clip
 * is cropped the same way the game crops it.
 *
 * 🛑 `cover`, not `contain` — this is the one place the earlier note in this
 * file is overruled, and deliberately: the game itself covers (the clip layer
 * passes contentFit="cover", contentPosition="center"), the framing was approved
 * against that crop, and letterboxing inside a composed card frame looks like a
 * bug rather than a choice. */
.hero-clip {
  position: relative;
  margin: 0 0 1.75rem;
  max-width: 720px;
  aspect-ratio: 430 / 262;
  overflow: hidden;
  border-radius: 16px;
  border: 1px solid rgba(232, 185, 100, 0.33);
  background: var(--bg-deep);
}
.hero-clip video,
.hero-clip > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  /* heroIdle.ts rest pose. The app breathes between 1.012 and 1.02 over 4.2s;
     a still page takes the rest value so the crop matches. */
  transform: scale(1.012);
}

/* L4 — the ground wash. Flat, full-bleed, NOT a gradient (0.28 since
   2026-09-18; it was 0.42 and read as muddy). */
.hero-clip .ground {
  position: absolute;
  inset: 0;
  background: rgba(var(--ink), 0.28);
}
/* L5 — the horizontal readability scrim. This is what lets gold text sit on top
   of an arbitrary illustration. Mirrored under [dir="rtl"]. */
.hero-clip .scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to right,
    rgba(var(--ink), 0.949) 0%,
    rgba(var(--ink), 0.851) 34%,
    rgba(var(--ink), 0.4) 68%,
    rgba(var(--ink), 0) 100%
  );
}
/* L6 — the foot scrim: a fixed 92px band whose last stop is the panel colour, so
   the art appears to continue past the bottom edge rather than stop at it. */
.hero-clip .foot {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 92px;
  background: linear-gradient(
    to bottom,
    rgba(var(--ink), 0) 0%,
    rgba(var(--ink), 0.6) 62%,
    var(--band) 100%
  );
}

/* L7 — the identity column. 46% of the width is a CAP in the app, not a width. */
.hero-clip .identity {
  position: absolute;
  top: 12px;
  left: 12px;
  max-width: 46%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}
.hero-clip .rarity-chip {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 10px;
  letter-spacing: 1.6px;
  padding: 2px 8px;
  border: 1px solid var(--chip, var(--gold-app));
  border-radius: 999px;
  /* Opaque by construction — a translucent fill reads as the pill having fallen
     behind the illustration. */
  background: var(--band);
  color: var(--chip, var(--gold-app));
  text-transform: none;
}
/* 🛑 No `text-transform: uppercase` on the name. Cinzel's lowercase glyphs ARE
   small capitals, so the large-cap/small-cap contrast is the design — forcing
   uppercase flattens it. Catalog names are title case for exactly this. */
/* The hero name IS the page <h1>, so it must override the generic h1 rule. */
.hero-clip h1.hero-name {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(22px, 5.2vw, 38px);
  line-height: 1.105;
  letter-spacing: 1px;
  color: var(--gold-name);
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.6);
}
.hero-clip .pantheon-line {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 11px;
  letter-spacing: 1.8px;
  color: var(--accent, var(--gold-app));
}
.hero-clip .pantheon-line i {
  width: 9px;
  height: 9px;
  border-radius: 5px;
  border: 1px solid var(--bg-deep);
  background: var(--accent, var(--gold-app));
  flex: 0 0 auto;
}
/* Tribe chips are STACKED, one per line — side by side they were the widest
   element in the column after the name. */
.hero-clip .tribes {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 5px;
  margin-top: 2px;
  list-style: none;
  padding: 0;
}
.hero-clip .tribes li {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 13px;
  letter-spacing: 0.9px;
  color: var(--text-app);
  padding: 3px 7px;
  border: 1px solid rgba(232, 185, 100, 0.4);
  border-radius: 999px;
  background: rgba(20, 13, 8, 0.9);
  text-transform: capitalize;
}

/* L8 — the Favor / Power plaque, bottom-left, with a hairline centre divider. */
.hero-clip .plaque {
  position: absolute;
  left: 12px;
  bottom: 8px;
  display: flex;
  align-items: stretch;
  border: 1px solid rgba(232, 185, 100, 0.33);
  border-radius: 12px;
  background: rgba(var(--ink), 0.9);
  padding: 4px 8px;
}
.hero-clip .plaque > div {
  min-width: 52px;
  text-align: center;
}
.hero-clip .plaque > div + div {
  border-left: 1px solid rgba(232, 185, 100, 0.27);
  margin-left: 8px;
  padding-left: 8px;
}
.hero-clip .plaque dt {
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--text-muted-app);
  margin: 0;
}
.hero-clip .plaque dd {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 22px;
  line-height: 1.18;
}
.hero-clip .plaque .favor dd {
  color: var(--gold-app);
}
.hero-clip .plaque .power dd {
  color: var(--accent, var(--gold-app));
}
/* The Swift keyword is the one trait that changes how a figure moves, so it gets
   the bottom-right corner the app gives the mastery badge. */
.hero-clip .swift-badge {
  position: absolute;
  right: 12px;
  bottom: 8px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gold-app);
  border: 1px solid var(--gold-app);
  border-radius: 999px;
  background: rgba(var(--ink), 0.9);
  padding: 2px 9px;
}
.hero-clip figcaption {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
@media (max-width: 420px) {
  .hero-clip .tribes,
  .hero-clip .rarity-chip {
    display: none;
  }
}

/* A handful of clips on the index, so the browse page has motion without
   fetching 253 videos. */
.featured {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.9rem;
  margin: 0 0 2rem;
  list-style: none;
  padding: 0;
}
@media (max-width: 620px) {
  .featured {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
.featured a {
  display: block;
  text-decoration: none;
  color: inherit;
}
.featured video {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  background: var(--sand-bg-2);
  border: 1px solid var(--rule);
  border-top: 3px solid var(--tile-accent, var(--gold));
  border-radius: 11px;
}
.featured a:hover video {
  border-color: var(--gold);
}
.featured span {
  display: block;
  margin-top: 0.4rem;
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--sand-text);
}

/* ---- Card detail -------------------------------------------------------- */
.card-detail {
  display: grid;
  grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
  gap: 2rem;
  align-items: start;
}
@media (max-width: 680px) {
  .card-detail {
    grid-template-columns: 1fr;
    gap: 1.25rem;
  }
}
.card-detail .art {
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--rule);
  border-top: 4px solid var(--tile-accent, var(--gold));
  background: var(--sand-bg-2);
}
.card-detail .art img {
  display: block;
  width: 100%;
  height: auto;
}
.rarity-line {
  display: inline-block;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--tile-accent, var(--gold));
  margin: 0 0 0.3rem;
}
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 0.6rem;
  margin: 0 0 1.5rem;
}
.stats div {
  background: var(--sand-panel);
  border: 1px solid var(--rule);
  border-radius: 9px;
  padding: 0.6rem 0.75rem;
}
.stats dt {
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sand-text-muted);
  margin: 0 0 0.15rem;
}
.stats dd {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--sand-text);
  text-transform: capitalize;
}
.block {
  background: var(--sand-panel);
  border: 1px solid var(--rule);
  border-radius: 11px;
  padding: 1rem 1.15rem;
  margin: 0 0 1rem;
}
.block h2 {
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 0.45rem;
}
.block p {
  margin: 0;
}
.block p.flavor {
  font-style: italic;
  color: var(--sand-text-muted);
}
.keywords {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}
.keywords li {
  font-size: 0.8rem;
  color: var(--turquoise);
  background: rgba(63, 182, 168, 0.1);
  border: 1px solid rgba(63, 182, 168, 0.3);
  border-radius: 999px;
  padding: 0.15rem 0.6rem;
  text-transform: capitalize;
}

.section {
  margin: 3rem 0 0;
}
.section > h2 {
  font-size: 1.35rem;
  color: var(--gold-bright);
  margin: 0 0 1rem;
}

/* ---- Pantheon hub links ------------------------------------------------- */
.hub-links {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.7rem;
  list-style: none;
  margin: 0 0 2rem;
  padding: 0;
}
.hub-links a {
  display: block;
  background: var(--sand-panel);
  border: 1px solid var(--rule);
  border-radius: 10px;
  padding: 0.8rem 1rem;
  text-decoration: none;
  font-weight: 700;
  color: var(--sand-text);
}
.hub-links a:hover {
  border-color: var(--gold);
  color: var(--gold-bright);
}
.hub-links a span {
  display: block;
  font-weight: 400;
  font-size: 0.82rem;
  color: var(--sand-text-muted);
}

/* ---- CTA + footer ------------------------------------------------------- */
.get-game {
  margin: 3rem 0 0;
  background: linear-gradient(180deg, var(--sand-panel-2), var(--sand-panel));
  border: 1px solid var(--rule);
  border-radius: 12px;
  padding: 1.5rem 1.35rem;
  text-align: center;
}
.get-game h2 {
  margin: 0 0 0.3rem;
  font-size: 1.2rem;
  color: var(--gold-bright);
}
.get-game p {
  margin: 0 0 1.1rem;
  color: var(--sand-text-muted);
  font-size: 0.95rem;
}
.store-badges {
  display: flex;
  gap: 0.75rem;
  justify-content: center;
  flex-wrap: wrap;
}
.store-badge svg {
  height: 48px;
  width: auto;
  display: block;
}
.store-badge:hover {
  filter: brightness(1.08);
}
/* oaiq.js stamps data-platform on <html>; on a phone the other store's badge is
   dead weight. Desktop and no-JS keep both. */
html[data-platform="ios"] .store-badge-android,
html[data-platform="android"] .store-badge-ios {
  display: none;
}

footer.fineprint {
  margin: 2.5rem auto 0;
  max-width: 1040px;
  padding: 1.5rem 1.25rem 3rem;
  border-top: 1px solid var(--rule);
  font-size: 0.85rem;
  color: var(--sand-text-muted);
  text-align: center;
}
footer.fineprint a {
  color: var(--sand-text-muted);
}
footer.fineprint .links {
  margin-bottom: 0.75rem;
}
footer.fineprint .links a {
  margin: 0 0.5rem;
}

/* ---- Image skeletons ---------------------------------------------------- */
/* Every art slot already reserves its box (aspect-ratio / width+height) so the
 * page does not shift; this is what the box LOOKS like until the art arrives —
 * a slow highlight sweep instead of a flat dark rectangle, then a short fade-in.
 *
 * The sweep is the <img>'s own background, so the loaded image simply paints
 * over it; `.ld` (set by the inline head script in site-build.ts) switches the
 * animation off so nothing keeps repainting behind finished art. Gated on the
 * `.sk` root class the same script adds: without JS nothing here applies and the
 * slot keeps its flat `--sand-bg-2` fill — never an image stuck at opacity 0. */
.sk :is(.banner, .scene, .codex-grid img, .card-tile .thumb, .chapter-list img):not(.ld) {
  background: linear-gradient(
      100deg,
      transparent 35%,
      rgba(232, 185, 100, 0.07) 50%,
      transparent 65%
    )
    var(--sand-bg-2);
  background-size: 250% 100%;
  animation: sk-sweep 1.5s ease-in-out infinite;
}
.sk :is(.banner, .scene, .codex-grid img, .card-tile .thumb, .chapter-list img).ld {
  animation: sk-in 0.35s ease-out;
}
@keyframes sk-sweep {
  from {
    background-position: 100% 0;
  }
  to {
    background-position: 0 0;
  }
}
@keyframes sk-in {
  from {
    opacity: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .sk :is(.banner, .scene, .codex-grid img, .card-tile .thumb, .chapter-list img) {
    animation: none !important;
  }
}

/* ---- The Myth Codex ------------------------------------------------------ */
/* 🛑 `object-position: top` on every banner. A codex banner is a WIDE
 * illustration and a centred cover-crop lands on the torso — it cuts the head
 * off. Every in-app call site passes `contentPosition="top"` for this reason;
 * the web must too. */
.banner {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 1;
  object-fit: cover;
  object-position: top;
  border-radius: 14px;
  border: 1px solid var(--rule);
  background: var(--sand-bg-2);
  margin: 0 0 1.25rem;
}
.scene {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  object-position: top;
  border-radius: 14px;
  border: 1px solid var(--rule);
  background: var(--sand-bg-2);
  margin: 0 0 1.5rem;
}

.kicker {
  margin: 0 0 0.3rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--turquoise);
}
.kicker a {
  color: inherit;
  text-decoration: none;
}
.kicker a:hover {
  text-decoration: underline;
}

.chapter .subtitle {
  margin: 0 0 1rem;
  color: var(--gold);
  font-size: 0.95rem;
  letter-spacing: 0.02em;
}

/* An epigraph is a line THIS PROJECT WROTE, not a quotation from a source.
 * Deliberately NOT a <blockquote> and deliberately carrying no attribution —
 * see the note in tools/site-build.ts. It is typographic display text. */
.epigraph {
  margin: 0 0 1.5rem;
  padding: 0 0 0 1rem;
  border-left: 2px solid var(--gold);
  color: var(--sand-text-muted);
  font-style: italic;
  font-size: 1.05rem;
  line-height: 1.5;
}

.prose {
  font-size: 1.06rem;
  line-height: 1.75;
}
.prose p {
  margin: 0 0 1.15rem;
}
.prose p:last-child {
  margin-bottom: 0;
}

.themes {
  margin: 1.5rem 0 0;
}

/* The source note is the Codex speaking about where an episode comes from —
 * again not a quotation. `attribution` names a tradition or a surviving text,
 * never a speaker, so it is safe as a label. */
.source-note {
  margin: 1.75rem 0 0;
  background: var(--sand-panel);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--turquoise);
  border-radius: 10px;
  padding: 1rem 1.15rem;
}
.source-note h2 {
  margin: 0 0 0.45rem;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--turquoise);
}
.source-note p {
  margin: 0;
  font-size: 0.95rem;
}
.source-note .attribution {
  margin-top: 0.5rem;
  font-size: 0.85rem;
  color: var(--sand-text-muted);
}

.unlock-note {
  margin: 1.5rem 0 0;
  font-size: 0.82rem;
  color: var(--sand-text-muted);
}

/* ---- Chapter list (a figure's Codex) ------------------------------------ */
.chapter-list {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: ch;
}
.chapter-list > li {
  counter-increment: ch;
  margin: 0 0 0.7rem;
}
.chapter-list a,
.chapter-list .locked {
  display: grid;
  grid-template-columns: 116px minmax(0, 1fr);
  gap: 1rem;
  align-items: stretch;
  background: var(--sand-panel);
  border: 1px solid var(--rule);
  border-radius: 11px;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
}
.chapter-list a:hover {
  border-color: var(--gold);
  color: inherit;
}
.chapter-list img {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 88px;
  object-fit: cover;
  object-position: top;
  background: var(--sand-bg-2);
}
.chapter-list .locked img {
  filter: grayscale(0.7) brightness(0.55);
}
.chapter-list .ch-body {
  display: block;
  padding: 0.75rem 1rem 0.8rem 0;
}
.chapter-list .ch-title {
  display: block;
  font-weight: 700;
  color: var(--gold-bright);
  line-height: 1.3;
}
.chapter-list .ch-title::before {
  content: counter(ch) ". ";
  color: var(--sand-text-muted);
  font-weight: 400;
}
.chapter-list .locked .ch-title {
  color: var(--sand-text-muted);
}
.chapter-list .ch-teaser {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.92rem;
  color: var(--sand-text-muted);
}
.chapter-list .ch-lock {
  display: block;
  margin-top: 0.4rem;
  font-size: 0.78rem;
  color: var(--turquoise);
}
@media (max-width: 520px) {
  .chapter-list a,
  .chapter-list .locked {
    grid-template-columns: 78px minmax(0, 1fr);
    gap: 0.75rem;
  }
}

/* ---- Codex index grid --------------------------------------------------- */
.codex-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 0.9rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.codex-grid a {
  display: block;
  background: var(--sand-panel);
  border: 1px solid var(--rule);
  border-radius: 11px;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
}
.codex-grid a:hover {
  border-color: var(--gold);
  color: inherit;
}
.codex-grid img {
  display: block;
  width: 100%;
  aspect-ratio: 5 / 2;
  object-fit: cover;
  object-position: top;
  background: var(--sand-bg-2);
}
.codex-grid .cg-name {
  display: block;
  padding: 0.6rem 0.8rem 0;
  font-weight: 700;
  color: var(--sand-text);
}
.codex-grid .cg-meta {
  display: block;
  padding: 0 0.8rem 0.7rem;
  font-size: 0.8rem;
  color: var(--sand-text-muted);
}
.section > h2 a {
  color: inherit;
  text-decoration: none;
}
.section > h2 a:hover {
  color: var(--gold-bright);
  text-decoration: underline;
}

/* ---- Prev / next pager -------------------------------------------------- */
.pager {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.8rem;
  margin: 2.5rem 0 0;
}
.pager a {
  display: block;
  background: var(--sand-panel);
  border: 1px solid var(--rule);
  border-radius: 10px;
  padding: 0.8rem 1rem;
  text-decoration: none;
  color: var(--gold-bright);
  font-weight: 700;
  font-size: 0.95rem;
}
.pager a:hover {
  border-color: var(--gold);
}
.pager a.next {
  text-align: right;
}
.pager a span {
  display: block;
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sand-text-muted);
  margin-bottom: 0.15rem;
}
@media (max-width: 480px) {
  .pager {
    grid-template-columns: 1fr;
  }
  .pager a.next {
    text-align: left;
  }
}

/* ---- Legend Saga callout (card + Codex pages of a Legend with a live Saga) ---
 * Written by sagaCallout() in tools/site-build.ts. Same panel language as the
 * Saga feature block on the home page. */
.saga-callout {
  margin: 2.5rem 0 0;
  padding: 1.25rem 1.4rem;
  border: 1px solid var(--rule);
  border-radius: 14px;
  background:
    radial-gradient(ellipse at 10% 0%, rgba(127, 180, 255, 0.14), transparent 60%),
    var(--sand-panel);
}
.saga-callout .kicker {
  margin: 0 0 0.3rem;
  color: var(--turquoise);
}
.saga-callout h2 {
  margin: 0 0 0.45rem;
  color: var(--gold-bright);
}
.saga-callout p {
  margin: 0 0 0.5rem;
}
.saga-callout p:last-child {
  margin: 0;
}
