/* Server-rendered reader styles (feature 22, ADR-0002) — dark redesign,
 * SINGLE dark theme (blue/cosmic/viridian accent trio).
 *
 * Self-contained: the reader surfaces (article reader, wiki home, public
 * landing) are plain Django SSR (no Vite bundle), so this stylesheet inlines
 * the design tokens, the page chrome (sticky top bar, landing rails,
 * footer), AND the live-article `.egg-*` surface that the canonical engine
 * output targets.
 *
 * ONE theme only (dark redesign, 2026-07-11): there is NO theme-switch
 * attribute selector and NO OS-preference media rule — the dark tokens ARE
 * the plain `:root`. Every chrome/article rule consumes `var(--…)`, so
 * changing the single `--accent` line below rebrands the whole surface.
 *
 * ACCENT EDIT-POINT 1 of 2: `--accent` in the `:root` below is one of the two
 * source-of-truth accent hexes (the other is
 * frontend/src/styles/tokens.css). See cms-reference/design/README.md.
 *
 * The `.egg-*` article rules are token-driven so a published article matches
 * the editor preview (feature 22's 1:1 guarantee).
 *
 * Fonts (Space Grotesk + IBM Plex Sans + JetBrains Mono) are SELF-HOSTED as
 * latin-subset variable woff2 under reader/fonts/ with font-display:swap, so
 * the cached reader hot path loads zero third-party origins (no Google Fonts
 * CDN round-trip / FOUT).
 */

/* ── Self-hosted fonts ────────────────────────────────────────────────────── */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/space-grotesk-latin.87c506d88b9f.woff2") format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 100 700;
  font-display: swap;
  src: url("fonts/ibm-plex-sans-latin.b2c9031d9fd6.woff2") format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url("fonts/jetbrains-mono-latin.570751c5f8b4.woff2") format('woff2');
}

/* ── Design tokens (dark redesign — single dark theme) ──────────────────────
 *
 * One `:root`, no theme-switch selector, no OS-preference media rule. The
 * accent is the single editable line; `--accent-deep`/`--tint` derive via
 * color-mix();
 * `--on-accent` is fixed white (blue fills keep white text). color-mix() is
 * evergreen-only by design (no fallback; documented in design/README.md).
 */
:root {
  --font-display: 'Space Grotesk', system-ui, sans-serif;
  --font-sans: 'IBM Plex Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, monospace;

  /* ── Canonical dark accent trio + neutrals (VERBATIM; mirrors tokens.css) ── */
  --accent: #1355e8;
  --accent-deep: color-mix(in srgb, var(--accent) 85%, #000);
  --accent-kicker: #4aa3d8;
  --accent-link: #5fff6e;
  --accent-link-hover: #8bffa0;
  --on-accent: #fff;
  --on-accent-link: #04170a;
  --tint: color-mix(in srgb, var(--accent) 14%, var(--bg));
  --bg: #1b1a1a;
  --bg-elev: #201f1f;
  --canvas: #262525;
  --surface: rgba(255, 255, 255, 0.035);
  --ink: #f0efee;
  --ink-2: #ffffff;
  --muted: #d8d6d4;
  --dim: #a3a1a0;
  --line: rgba(255, 255, 255, 0.12);
  --line-2: rgba(255, 255, 255, 0.18);

  /* ── Reader role tokens (dark-tuned) ─────────────────────────────────── */
  --good: #4ade80;
  --mid: #ffc94d;
  /* semantic [:red:]/[:green:] token text — brightened for dark bg; match the
   * admin --pill-red-ink / --pill-green-ink */
  --egg-red: #ff7a7a;
  --egg-green: #7dff8e;

  /* ── Typed [:annotation:VARIANT] callout accents ─────────────────────── */
  --callout-info: #5fa3e0;
  --callout-warning: var(--mid);
  --callout-danger: var(--egg-red);
  --callout-success: var(--egg-green);
  --embed-bg: #000;
  --embed-fg: #fff;
  --scrim: rgba(0, 0, 0, 0.55);
}

* {
  box-sizing: border-box;
}
/* Clears the sticky .top bar for #hl_N/#hm_N fragment jumps (On-This-Page /
   auto-TOC / deep links). `--reader-banner-h` adds the sandbox banner height on
   /sandbox/* so those jumps land below the banner-shifted header there too; the
   var is unset on the public reader, so the `0px` fallback keeps it at 84px. */
html {
  scroll-padding-top: calc(84px + var(--reader-banner-h, 0px));
}
html,
body {
  margin: 0;
  padding: 0;
}
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  /* Sticky-footer (Phase 3, A4): a short page's content is shorter than the
     viewport, leaving the fixed .egg-bg-art layer visible as a bright band
     below the .foot footer. Growing body to fill the viewport (min-height)
     and letting the content wrapper absorb the slack (flex:1 0 auto below on
     .egg-shell / .egg-landing-page) pins the footer to the viewport bottom
     instead, so the bg-art layer never shows past it. The sticky .top header
     and .foot footer stay ordinary (non-growing) flex items, as does the
     sticky .sandbox-banner (sticky works fine inside a flex column); only the
     fixed .egg-bg-art layer is truly out of flow. No position changes needed. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
.mono {
  font-family: var(--font-mono);
}
a {
  color: inherit;
  text-decoration: none;
}
::selection {
  background: rgba(19, 85, 232, 0.45);
  color: #fff;
}
::-webkit-scrollbar {
  height: 8px;
  width: 8px;
}
::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.06);
}
::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, var(--accent), var(--accent-kicker));
}
::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, var(--accent-kicker), var(--accent-link));
}

/* Visually-hidden but accessible/crawlable (document-outline H1s on the
   landing + wiki home, which have no visible page-title element). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* ── Editorial v2 chrome (designs/article-editorial.html .top/.top-in) ─────── */

/* Header — translucent dark + blur, wordmark logo (2026-07-11 dark retheme). */
.top {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(27, 26, 26, 0.92);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.top-in {
  max-width: 1280px;
  margin: 0 auto;
  padding: 11px 28px;
  display: flex;
  align-items: center;
  gap: 26px;
}
.logo {
  display: flex;
  align-items: center;
  gap: 10px;
}
.topnav {
  display: flex;
  gap: 2px;
  font-size: 13.5px;
  font-weight: 600;
}
.topnav a {
  padding: 7px 12px;
  border-radius: 4px;
  color: var(--ink);
}
.topnav a:hover {
  background: var(--canvas);
  color: var(--ink-2);
}
.top-search {
  margin-left: auto;
  flex: none;
  width: 240px;
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 8px;
  padding: 8px 12px;
  font-size: 13px;
  color: var(--muted);
}
.top-search-input {
  flex: 1;
  min-width: 0;
  border: none;
  background: transparent;
  outline: none;
  font: inherit;
  color: var(--ink);
  padding: 0;
}
.top-search-input::placeholder {
  color: var(--muted);
}
.top-search:focus-within {
  border-color: var(--line-2);
}
.btn-y {
  background: var(--accent);
  color: var(--on-accent);
  padding: 9px 16px;
  font-weight: 700;
  font-size: 13px;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}
.btn-y:hover {
  background: var(--accent-deep);
}

/* Landing content wrapper (Phase 3, A4 sticky-footer): the landing has no
   .egg-shell (it's cross-wiki, not a single-wiki reader surface), so its own
   top-level content block gets the same flex-grow to pin .foot to the
   viewport bottom on a short/sparsely-seeded landing page — see the body
   rule above. */
.egg-landing-page {
  flex: 1 0 auto;
}

.container {
  max-width: 1320px;
  margin: 0 auto;
  padding: 0 24px;
}
.section {
  padding: 56px 0 24px;
}
.section h2 {
  font-size: 23px;
  font-weight: 800;
  margin: 0 0 4px;
  letter-spacing: -0.01em;
  color: var(--ink-2);
}
.section .sub {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 20px;
}
.header-row {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin-bottom: 22px;
}
.header-row h2 {
  margin: 0;
}

/* ── Browse landing restyle: flat section panels + reviews carousel ─────────── */
/* Each landing rail's content sits in a flat elevated card (the Browse look).
   `--surface` is the translucent dark-elevated panel fill (mirrors `.rcard`,
   the right-aside card); flat 1px border, no shadow — consistent with the
   rest of the Editorial-v2 reader (dark retheme Task 11). */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 22px;
}
/* Reviews carousel: the existing scroll-snap `.reviews` track plus floating
   prev/next nav buttons. landing.js adds `.car-js` to reveal the buttons — with
   JS off the track stays swipeable and the (dead) buttons stay hidden. */
.car {
  position: relative;
}
.car-nav {
  display: none;
}
.car-js .car-nav {
  position: absolute;
  top: 50%;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  transform: translateY(-50%);
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: 50%;
  color: var(--ink-2);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.car-js .car-nav.prev {
  left: -6px;
}
.car-js .car-nav.next {
  right: -6px;
}
.car-js .car-nav:hover {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-color: var(--accent);
}
.car-js .car-nav[disabled] {
  opacity: 0.32;
  cursor: default;
}

/* Reviews row */
.reviews {
  display: flex;
  gap: 18px;
  overflow-x: auto;
  padding-bottom: 12px;
  scroll-snap-type: x mandatory;
}
.review-card {
  flex: 0 0 360px;
  scroll-snap-align: start;
  position: relative;
  cursor: pointer;
}
.review-card .img {
  aspect-ratio: 16/10;
  overflow: hidden;
  position: relative;
  border-radius: 8px;
  border: 1px solid var(--line-2);
}
.review-card .img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s;
}
.review-card:hover .img img {
  transform: scale(1.04);
}
.review-card .img::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 50%, var(--scrim));
}
.review-card .score {
  position: absolute;
  top: 12px;
  right: 12px;
  background: var(--accent);
  color: var(--on-accent);
  padding: 4px 10px;
  font-family: var(--font-mono);
  font-weight: 800;
  font-size: 16px;
  border-radius: 5px;
}
.review-card .score.bad {
  background: var(--accent-deep);
  color: var(--on-accent);
}
.review-card .score.mid {
  background: var(--mid);
  color: #fff;
}
.review-card .info {
  padding: 14px 0;
}
.review-card .badge {
  font-family: var(--font-mono);
  font-size: 10px;
  /* --accent is fill/border-only (dark retheme discipline); this small-caps
     kicker label uses --accent-kicker ink, matching .rcard .hd h4 / .rank .n. */
  color: var(--accent-kicker);
  margin-bottom: 6px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.review-card h3 {
  margin: 0 0 6px;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.25;
  color: var(--ink-2);
}
.review-card .by {
  font-size: 12px;
  color: var(--muted);
}
.review-card .by .author {
  color: var(--accent-kicker);
  font-weight: 600;
}

/* News list */
.news-list {
  display: flex;
  flex-direction: column;
}
.news-item {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: 14px;
  padding: 16px 0;
  border-top: 1px solid var(--line);
  cursor: pointer;
}
.news-item:first-child {
  border-top: none;
  padding-top: 0;
}
.news-item:hover h4 {
  color: var(--accent-link);
}
.news-item .img {
  width: 110px;
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: 8px;
  border: 1px solid var(--line-2);
}
.news-item .img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.news-item h4 {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--ink-2);
  transition: color 0.15s;
}
.news-item .meta {
  display: flex;
  gap: 8px;
  font-size: 11px;
  font-family: var(--font-mono);
  color: var(--muted);
  margin-bottom: 4px;
}
.news-item .author {
  color: var(--accent-kicker);
}
.news-item .desc {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.45;
}

/* Latest games row */
.games-row {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 14px;
}
.game-tile {
  cursor: pointer;
}
.game-tile .cover {
  aspect-ratio: 3/4;
  overflow: hidden;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  position: relative;
  /* EgoGamer zero-state: a thumbnail-less tile reads as a deliberate colored
     well (with a centered initials glyph) rather than an empty bordered box. */
  background: var(--canvas);
}
.game-tile .cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s;
}
.game-tile:hover .cover img {
  transform: scale(1.05);
}
.game-tile h4 {
  margin: 10px 0 2px;
  font-size: 14px;
  font-weight: 700;
  color: var(--ink-2);
}
.game-tile .platform {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--muted);
}
/* EgoGamer extension: centered initials glyph for a thumbnail-less cover. */
.game-tile .cover .cover-initials {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-weight: 800;
  font-size: 40px;
  color: var(--line-2);
}

/* Wikis grid */
.wikis-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 14px;
}
.wiki-tile {
  cursor: pointer;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  overflow: hidden;
}
.wiki-tile .badge {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-weight: 800;
  font-size: 32px;
  /* Literal white, NOT var(--on-accent): the fill behind the initials is a
     fixed dark BADGE_PALETTE hex (wiki_tags.py), not the themable accent. */
  color: #fff;
  position: relative;
  overflow: hidden;
}
.wiki-tile .badge::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, transparent 60%, rgba(0, 0, 0, 0.45));
}
/* EgoGamer extension: when a wiki icon exists, render it edge-to-edge over the
   badge color fill (the color + initials remain the fallback). */
.wiki-tile .badge img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.wiki-tile .badge .articles {
  position: absolute;
  bottom: 8px;
  right: 8px;
  font-size: 10px;
  background: rgba(0, 0, 0, 0.62);
  color: #fff;
  padding: 2px 6px;
  font-weight: 600;
  letter-spacing: 0.05em;
  border-radius: 3px;
}
.wiki-tile .info {
  padding: 10px 12px;
  border-top: 1px solid var(--line-2);
  background: var(--bg);
}
.wiki-tile h4 {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink-2);
}
.wiki-tile .contributors {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--muted);
  margin-top: 2px;
}
.wiki-tile:hover {
  border-color: var(--accent);
}

/* Footer */
/* Footer — single-line brand + tagline + per-wiki copyright + build (the
   Browse mockup `.foot-in`; copyright relocated here from the aside/tail,
   2026-07-11 dark retheme Task 4). */
.foot {
  /* Flush against the shell above (its 48px bottom padding owns the spacing). */
  background: var(--bg-elev);
  border-top: 1px solid var(--line);
}
.foot-in {
  max-width: 1280px;
  margin: 0 auto;
  padding: 20px 28px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  font-size: 12px;
  color: var(--muted);
}
.foot .mk {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--accent), var(--accent-kicker));
  color: #fff;
  border-radius: 3px;
  font-weight: 900;
  font-size: 11px;
}
.foot b {
  color: var(--ink-2);
  font-weight: 700;
}
/* Per-wiki copyright card, relocated from the right aside (testid
   "copyright-card" — see reader_base.html footer block). */
.foot-rights {
  margin-left: auto;
  max-width: 520px;
  text-align: right;
  font-size: 11.5px;
  color: var(--dim);
}
.foot .r {
  /* margin-left:auto only right-hugs when no copyright block precedes it
     (landing/404 and wikis with no Copyright row). When the copyright block IS
     present, it owns the right-push (its own margin-left:auto), and the build
     string sits just after it — the adjacency rule below cancels this auto so
     the two don't split the free space and strand the copyright mid-footer. */
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--dim);
}
.foot-rights + .r {
  margin-left: 0; /* sits after the copyright block with the .foot-in gap */
}

/* Phones: stack the copyright + build below the brand row, full-width left. */
@media (max-width: 767px) {
  .foot-rights {
    flex-basis: 100%;
    margin-left: 0;
    max-width: none;
    text-align: left;
  }
  .foot .r {
    margin-left: 0;
  }
}

/* Landing-surface helpers (empty / placeholder states). */
.egg-landing-placeholder,
.egg-empty-state {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--muted);
  padding: 24px 0;
}

/* Styled 404 block (U12) — Editorial v2 chrome, no hero image. */
.egg-404 {
  padding: 64px 0;
  max-width: 640px;
}
.egg-404 .tag {
  display: inline-flex;
  gap: 8px;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--accent-kicker);
  margin-bottom: 14px;
}
.egg-404 .tag::before {
  content: '';
  width: 24px;
  height: 1px;
  background: var(--line-2);
}
.egg-404 h1 {
  font-size: 56px;
  font-weight: 900;
  line-height: 0.98;
  letter-spacing: -0.03em;
  margin: 0 0 16px;
  color: var(--ink-2);
  overflow-wrap: break-word;
}
.egg-404 p {
  font-size: 16px;
  color: var(--ink);
  margin: 0 0 24px;
  line-height: 1.55;
}
.egg-404 .actions {
  display: flex;
  gap: 10px;
}

/* ── Reader shell (Editorial v2) ───────────────────────────────────────────── */
/* Per-wiki background art (Phase 2): a fixed full-viewport layer behind the
   opaque .egg-shell. NOT background-attachment:fixed (janky on iOS Safari) — a
   real fixed element. body's --bg propagates to the canvas (behind, z-order 1);
   this z-index:-1 layer paints above that canvas but below in-flow content and
   the z-indexed chrome (header 100, scrim 250, drawers 255+, edge tabs 260), so
   art shows only in the margins around the opaque shell. ::after darkens it. */
.egg-bg-art {
  position: fixed;
  inset: 0;
  z-index: -1;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
/* Deepened + gradiated (Phase 3, A4): a flat 45% fill left the art visibly
   brighter wherever it wasn't already darkened by the .egg-shell glow halo
   below — most noticeably in the below-footer band on short pages. The
   vertical gradient darkens progressively toward the bottom edge, and the
   sticky-footer flex above (body/.egg-shell/.egg-landing-page) keeps that
   bottom edge pinned at the viewport bottom instead of mid-page. */
.egg-bg-art::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(27, 26, 26, 0.5), rgba(27, 26, 26, 0.66));
}
.egg-shell {
  max-width: 1320px;
  margin: 0 auto;
  /* Top padding (not child margins) owns the breathing room: child top margins
     would collapse through the padding-less shell edge and push the whole box
     below the sticky header, leaving a body-bg band between .top and the shell. */
  padding: 20px 28px 48px;
  background: var(--bg);
  /* Glow separates content from the bg-art layer (Phase 2); harmless over the
     plain dark canvas otherwise. */
  box-shadow: 0 0 80px rgba(0, 0, 0, 0.65);
  /* Sticky-footer (Phase 3, A4): grow to absorb the slack on short pages so
     .foot reaches the viewport bottom — see the body rule above. */
  flex: 1 0 auto;
}
.egg-announce {
  margin: 0 0 16px;
  padding: 11px 15px;
  background:
    linear-gradient(90deg, rgba(19, 85, 232, 0.22), rgba(95, 255, 110, 0.1)),
    var(--bg);
  border-bottom: 1px solid rgba(95, 255, 110, 0.2);
  border-left: none;
  border-radius: 0 6px 6px 0;
  color: var(--ink);
  font-size: 13px;
}
.egg-announce a {
  color: var(--accent-link);
  font-weight: 600;
}
.egg-announce a:hover {
  color: var(--accent-link-hover);
}
/* ── 3-column reader grid (mockup .wrap/.rail/.aside) ──────────────────────
   LEFT rail (Quick Access + Guide Index) │ MAIN │ RIGHT aside (On This Page /
   Popular / Copyright). DOM order is main → rail → aside (so the article's
   `hl_N` anchors precede the Sidebar render's duplicates in tree order);
   grid-template-areas pins the VISUAL rail-left layout regardless. */
.egg-grid {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr) 264px;
  grid-template-areas: 'rail main aside';
  gap: 40px;
  align-items: start;
  /* No top margin: the shell's padding-top (or the announce strip's bottom
     margin) owns the spacing above the grid. */
}
/* Empty-column collapse: the templates omit the rail when the wiki has no
   Global Navi / Sidebar, and the aside when there is no TOC / Popular /
   Copyright — these modifiers drop the matching reserved columns so a
   settings-less wiki shows no blank 232px/264px gutters. */
.egg-grid--no-rail {
  grid-template-columns: minmax(0, 1fr) 264px;
  grid-template-areas: 'main aside';
}
.egg-grid--no-aside {
  grid-template-columns: 232px minmax(0, 1fr);
  grid-template-areas: 'rail main';
}
.egg-grid--no-rail.egg-grid--no-aside {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: 'main';
}
.egg-grid > .main {
  grid-area: main;
}
.egg-grid > .rail {
  grid-area: rail;
}
.egg-grid > .aside {
  grid-area: aside;
}
.egg-grid .rail,
.egg-grid .aside {
  position: sticky;
  /* Clear the sticky `.top` header. On /sandbox/* the `.sandbox-banner` shifts
     `.top` down by its height (see `html:has(body.sandbox)` further down), so
     the sidebars must add the same `--reader-banner-h` — otherwise their tops
     slide under the header. The var is unset on the public reader (→ 0px), so
     this stays a plain 78px there. */
  top: calc(78px + var(--reader-banner-h, 0px));
}
.egg-main,
.egg-grid .main {
  min-width: 0;
  max-width: none;
}
/* Transparent container: the two .rcard boxes (Quick Access + Guide Index) are
   the visible cards — same icon-headed pattern as the On This Page / Trending
   aside cards. The mobile drawer re-styles `.rail` at <=1023px, so stripping the
   desktop card here does not affect the off-canvas drawer's own background. */
.rail {
  border: none;
  background: none;
  padding: 0;
}
.qa {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.qa a {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  padding: 7px 6px;
  margin: 0 -6px;
  border-radius: 6px;
  color: var(--ink);
  transition: background 0.12s, color 0.12s;
}
.qa a::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.22);
  flex-shrink: 0;
}
.qa a:hover:not([aria-current]) {
  color: var(--ink-2);
  background: rgba(255, 255, 255, 0.06);
}
.qa a[aria-current='page'] {
  color: var(--accent-link);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.qa a[aria-current='page']::before {
  background: var(--accent-link);
  box-shadow: 0 0 6px color-mix(in srgb, var(--accent-link) 55%, transparent);
}
/* Guide Index — the designs/article-editorial.html .idx accordion. The engine's
   flat <h2>+links output is reshaped to <details>/<summary>/.sub by
   wikis.sidebar_nav.sidebar_to_accordion before it reaches the template. */
.idx details {
  border-bottom: 1px solid var(--line);
}
.idx details summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 6px;
  margin: 0 -6px;
  border-radius: 6px;
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
  transition: color 0.12s, background 0.12s;
}
.idx details summary::-webkit-details-marker {
  display: none;
}
.idx details summary .chev {
  margin-left: auto;
  color: var(--dim);
  transition: transform 0.18s;
  font-size: 12px;
}
.idx details[open] summary .chev {
  transform: rotate(90deg);
  color: var(--ink);
}
.idx details summary:hover {
  color: var(--ink-2);
  background: rgba(255, 255, 255, 0.06);
}
.idx .sub {
  padding: 0 2px 10px 14px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.idx .sub a {
  font-size: 13px;
  color: var(--muted);
  padding: 6px 0 6px 12px;
  border-left: 2px solid var(--line);
  transition: color 0.12s, border-left-color 0.12s;
}
.idx .sub a:hover:not([aria-current]) {
  color: var(--ink-2);
}
.idx .sub a[aria-current='page'] {
  color: var(--accent-link);
  border-left-color: var(--accent-link);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.idx .flat[aria-current='page'] {
  color: var(--accent-link);
}
.idx .sub .subhead {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dim);
  padding: 8px 0 2px;
}
.idx .flat {
  display: block;
  padding: 10px 2px;
  font-size: 13.5px;
  font-weight: 600;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
}
.idx .flat:hover:not([aria-current]) {
  color: var(--ink-2);
}
/* ── Right-aside cards (On This Page / Popular / Copyright) ─────────────── */
.rcard {
  --rcard-pad: 18px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
  margin-bottom: 22px;
  padding: var(--rcard-pad);
}
/* Gradient cap (replaces the flat `border-top: 3px solid var(--accent)`) —
   negative-margin technique pulls the bar past `.rcard`'s own padding so it
   spans edge-to-edge under the rounded top corners. */
.rcard.toc::before {
  content: '';
  display: block;
  height: 5px;
  margin: calc(-1 * var(--rcard-pad, 18px)) calc(-1 * var(--rcard-pad, 18px)) 16px;
  background: linear-gradient(90deg, var(--accent), var(--accent-kicker), var(--accent-link));
  border-radius: 12px 12px 0 0;
}
.rcard .hd {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 0 0 12px;
  margin: 0 0 12px;
  border-bottom: 1px solid var(--line);
}
.rcard .hd .ic {
  width: 26px;
  height: 26px;
  border-radius: 6px;
  background: var(--tint);
  color: var(--accent-kicker);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.rcard .hd .ic svg {
  width: 15px;
  height: 15px;
  display: block;
}
.rcard .hd h4 {
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  margin: 0;
  color: var(--accent-kicker);
}
.rcard .bd {
  padding: 0;
}
.toc .bd a {
  display: flex;
  gap: 8px;
  align-items: center;
  font-size: 13px;
  color: var(--muted);
  padding: 7px 6px;
  margin: 0 -6px;
  border-radius: 6px;
  font-weight: 500;
  transition: color 0.12s, background 0.12s;
}
.toc .bd a::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent-kicker);
  flex-shrink: 0;
}
.toc .bd a:hover:not([aria-current]) {
  color: var(--ink-2);
  background: rgba(255, 255, 255, 0.06);
}
.toc .bd a[aria-current='location'] {
  color: var(--accent-link);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  font-weight: 600;
}
.toc .bd a[aria-current='location']::before {
  background: var(--accent-link);
  box-shadow: 0 0 6px color-mix(in srgb, var(--accent-link) 55%, transparent);
}
.toc .bd a.lvl-3 {
  padding-left: 19px;
  font-size: 12.5px;
}
.toc .bd a.lvl-3::before {
  width: 4px;
  height: 4px;
  background: rgba(255, 255, 255, 0.35);
}
.rank .bd a {
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 9px 0;
  border-top: 1px solid var(--line);
}
.rank .bd a:first-child {
  border-top: none;
}
.rank .n {
  font-weight: 800;
  font-size: 12px;
  color: var(--accent-kicker);
  width: 13px;
  flex-shrink: 0;
  text-align: center;
}
.rank h5 {
  font-size: 12.5px;
  font-weight: 600;
  margin: 0;
  line-height: 1.25;
  color: var(--ink);
  transition: color 0.12s;
}
.rank .bd a:hover:not([aria-current]) h5 {
  color: var(--ink-2);
}
.rank .bd a[aria-current='page'] .n {
  color: var(--accent-link);
}
.rank .bd a[aria-current='page'] h5 {
  color: var(--accent-link);
  text-decoration: underline;
  text-decoration-color: var(--accent-link);
  text-underline-offset: 2px;
}
/* Breadcrumb (Phase 2) — above the banner; cosmic links, muted separators. */
.egg-crumbs { font-family: var(--font-display); font-size: 12.5px; margin: 4px 0 14px; color: var(--dim); }
.egg-crumbs a { color: var(--accent-kicker); text-decoration: none; }
.egg-crumbs a:hover { color: var(--accent-link); }
.egg-crumbs-sep { margin: 0 8px; color: var(--dim); }
.egg-crumbs-plain { color: var(--dim); }

/* Framed title banner (Phase 2) — gradient frame + decorative plate over
   Article.cover; renders on every article (dark fill when coverless). */
.egg-banner { padding: 3px; border-radius: 16px; background: linear-gradient(135deg, var(--accent), var(--accent-link)); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4); margin: 0 0 20px; }
.egg-banner-inner { position: relative; aspect-ratio: 1600 / 686; border-radius: 14px; overflow: hidden; background: var(--bg); }
.egg-banner--nocover .egg-banner-inner {
  aspect-ratio: auto;
  background: linear-gradient(135deg, var(--bg-elev), var(--bg));
  padding: 26px 0 30px;
}
.egg-banner-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.egg-banner-plate { position: absolute; left: 50%; bottom: 4%; transform: translateX(-50%); width: 78%; max-width: 78%; padding: 2px; border-radius: 12px; background: linear-gradient(90deg, var(--accent), var(--accent-link)); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); }
.egg-banner--nocover .egg-banner-plate {
  position: static;
  transform: none;
  left: auto;
  bottom: auto;
  width: min(92%, 620px);
  margin: 0 auto;
}
.egg-banner-plate-in { display: block; border-radius: 11px; background: rgba(20, 20, 20, 0.93); padding: 1.4% 3%; text-align: center; font-family: var(--font-display); font-weight: 800; font-size: clamp(15px, 3vw, 33px); line-height: 1.2; letter-spacing: -0.01em; color: var(--ink); overflow-wrap: anywhere; }

.egg-article-title {
  font-family: var(--font-display);
  font-size: 38px;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 24px 0 6px;
  color: var(--ink-2);
  text-wrap: balance;
}
.egg-article-meta {
  font-family: var(--font-display);
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 16px;
}

/* ── Category page (D1) — live auto-list of the category's articles + child
   cards. Bespoke light classes (NOT .news-item, whose 110px image column
   doesn't fit a text-only row); var() tokens only. ─────────────────────────── */
.egg-cat-section {
  margin: 30px 0 0;
}
.egg-cat-section-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.egg-cat-auto {
  font-size: 10px;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  /* Surface/kicker language (Task 6 → 11 routed fix): the light-era
     --tint/--accent-deep pill was near-invisible on dark. */
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--accent-kicker);
}
.egg-cat-list {
  display: flex;
  flex-direction: column;
}
.egg-cat-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 12px;
  padding: 12px 0;
  border-top: 1px solid var(--line);
  color: var(--ink-2);
  font-weight: 600;
  text-decoration: none;
}
.egg-cat-row:first-child {
  border-top: none;
}
.egg-cat-row:hover {
  /* Standard blue hover tint (Task 6 → 11 routed fix): light-era
     --accent-deep ink was nearly invisible on dark. */
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--accent-link);
}
.egg-cat-row .views {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted);
}
.egg-cat-empty {
  margin: 4px 0;
  padding: 8px 0;
  font-size: 14px;
  color: var(--muted);
}
.egg-cat-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 12px;
}
.egg-cat-card {
  display: block;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink-2);
  text-decoration: none;
  transition: border-color 0.15s, background 0.15s;
}
.egg-cat-card:hover {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.egg-cat-card .name {
  display: block;
  font-size: 15px;
  font-weight: 700;
}
.egg-cat-card .count {
  display: block;
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--muted);
}

/* ── Rendered article body (mirrors frontend/src/styles/egg.css) ──────────── */
.egg-article {
  font-size: 16.5px;
  line-height: 1.7;
  color: var(--ink);
}
/* Kept after .egg-infobox stopped floating: `display: table` still seals the
   article's bottom edge so the last child's margin cannot collapse out of
   .egg-article (which would tighten the gap to the related rail on every
   page), and `clear: both` keeps any future float contained. */
.egg-article::after {
  content: "";
  display: table;
  clear: both;
}
/* A `# ` header is MIXED inline content — the engine emits db_value/strong/
   link spans plus raw text (and the empty egg-anchor marker) as direct h2
   children — so the h2 must stay ONE inline formatting context. It shipped
   as `display: flex; gap: 12px` (solely to seat the ::before accent bar),
   but flex turns every inline child into its own item: on phones
   `# [db_value:…] Information` fragmented into a narrow internally-wrapping
   dbval box beside a separate vertically-centered "Information" box, the
   author's word space swallowed, and the empty anchor marker cost an extra
   12px gap (CU-86d3yuqc4). The bar is inline-block instead: margin-right
   stands in for the flex gap, and the negative vertical-align seats the
   22px bar on the 23px text line where flex centering used to put it
   (Chromium-probe-measured). Long headers now wrap full-width under the
   bar — accepted. Pinned by articles/tests/test_reader_css.py; mirrored in
   frontend/src/styles/egg.css. */
.egg-article h2 {
  font-family: var(--font-display);
  font-size: 23px;
  font-weight: 700;
  margin: 32px 0 10px;
  color: var(--ink-2);
  letter-spacing: -0.01em;
  border-bottom: none;
}
.egg-article h2::before {
  content: '';
  display: inline-block;
  width: 4px;
  height: 22px;
  margin-right: 12px;
  border-radius: 2px;
  vertical-align: -4px;
  background: linear-gradient(var(--accent), var(--accent-link));
}
.egg-article h3 {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 600;
  margin: 20px 0 6px;
  color: var(--ink-2);
}
.egg-article h4 {
  font-family: var(--font-display);
  font-weight: 600;
}
.egg-article p {
  margin: 8px 0;
}
.egg-article ul,
.egg-article ol {
  margin: 6px 0;
  padding-left: 22px;
}
.egg-article li {
  margin: 0;
  line-height: 1.55;
}
.egg-article li::marker {
  color: var(--accent-link);
}
/* Sub-list nesting (`** ` rows inside [list]/[order_list]) — tighten so the
   nested level reads as a child of its parent item. */
.egg-article li > ul,
.egg-article li > ol {
  margin: 4px 0 4px 0;
}
/* Engine-link ink — viridian (navigational/active role, spec 2026-07-11 §3); matches the admin egg.css `--link` (= --accent-link). */
.egg-link {
  color: var(--accent-link);
  font-weight: 600;
  text-decoration: none;
}
.egg-link:hover {
  color: var(--accent-link-hover);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.egg-red {
  color: var(--egg-red);
}
.egg-green {
  color: var(--egg-green);
}
/* Inline spoiler — blurred until revealed (Batch A). */
.egg-spoiler {
  background: #000;
  color: var(--ink);
  border-radius: 3px;
  padding: 0 3px;
  cursor: pointer;
  filter: blur(0.35em);
  transition: filter 0.12s ease;
  user-select: none;
}
.egg-spoiler.is-revealed {
  background: transparent;
  color: inherit;
  filter: none;
  user-select: auto;
}
.egg-spoiler:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Inline glossary term with tooltip (Batch A). */
.egg-term {
  border-bottom: 1px dotted var(--accent);
  cursor: help;
  position: relative;
}
.egg-term::after {
  content: attr(data-tip);
  position: absolute;
  left: 0;
  bottom: 125%;
  z-index: 5;
  min-width: 140px;
  max-width: 260px;
  padding: 6px 9px;
  background: var(--canvas);
  color: var(--ink);
  border: 1px solid var(--line-2);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  border-radius: 6px;
  font-size: 12px;
  line-height: 1.4;
  white-space: normal;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
}
.egg-term:hover::after,
.egg-term:focus-visible::after,
.egg-term.is-open::after {
  opacity: 1;
}
.egg-article del {
  color: var(--muted);
}
.egg-rule {
  border: none;
  border-top: 1px solid var(--line);
  margin: 18px 0;
}
.egg-btn {
  display: inline-block;
  padding: 9px 16px;
  background: var(--accent);
  color: var(--on-accent) !important;
  font-weight: 700;
  border-radius: 6px;
  text-decoration: none;
}
.egg-btn:hover {
  background: var(--accent-deep);
}
.egg-article blockquote {
  margin: 14px 0;
  padding: 10px 16px;
  border-left: 3px solid var(--accent);
  background: var(--canvas);
  border-radius: 0 6px 6px 0;
  color: var(--ink);
}
.egg-article blockquote cite {
  display: block;
  margin-top: 6px;
  font-size: 12px;
  font-style: normal;
  color: var(--muted);
}
.egg-callout {
  margin: 14px 0;
  padding: 12px 14px;
  font-size: 14px;
  background: var(--tint);
  border-left: 3px solid var(--accent);
  border-radius: 0 6px 6px 0;
  color: var(--ink);
}
/* Typed callout variants (Batch A). Bare .egg-callout keeps the 💡 tip look. */
.egg-callout-info,
.egg-callout-warning,
.egg-callout-danger,
.egg-callout-success {
  margin: 14px 0;
  padding: 12px 14px 12px 42px;
  position: relative;
  font-size: 14px;
  border: 1px solid var(--line-2);
  border-left-width: 3px;
  border-radius: 0 6px 6px 0;
  color: var(--ink);
}
.egg-callout-info::before,
.egg-callout-warning::before,
.egg-callout-danger::before,
.egg-callout-success::before {
  position: absolute;
  left: 14px;
  top: 12px;
  font-size: 15px;
  line-height: 1.4;
}
.egg-callout-info { background: color-mix(in srgb, var(--callout-info) 12%, var(--bg)); border-left-color: var(--callout-info); }
.egg-callout-info::before { content: "\2139\FE0F"; }        /* ℹ️ */
.egg-callout-warning { background: color-mix(in srgb, var(--callout-warning) 12%, var(--bg)); border-left-color: var(--callout-warning); }
.egg-callout-warning::before { content: "\26A0\FE0F"; }     /* ⚠️ */
.egg-callout-danger { background: color-mix(in srgb, var(--callout-danger) 12%, var(--bg)); border-left-color: var(--callout-danger); }
.egg-callout-danger::before { content: "\1F6D1"; }          /* 🛑 */
.egg-callout-success { background: color-mix(in srgb, var(--callout-success) 12%, var(--bg)); border-left-color: var(--callout-success); }
.egg-callout-success::before { content: "\2705"; }          /* ✅ */
.egg-toc {
  margin: 14px 0;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
}
.egg-toc-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--accent-kicker);
  margin-bottom: 8px;
}
.egg-toc ul {
  margin: 0;
  padding-left: 0;
  list-style: none;
}
.egg-toc a {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px;
  margin: 0 -6px;
  border-radius: 6px;
  color: var(--muted);
  font-weight: 500;
  transition: color 0.12s, background 0.12s;
}
.egg-toc a::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent-kicker);
  flex-shrink: 0;
}
.egg-toc a:hover {
  color: var(--accent-link);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.egg-embed {
  margin: 14px 0;
  aspect-ratio: 16 / 9;
  width: 100%;
  background: var(--embed-bg);
  color: var(--embed-fg);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
}
.egg-embed[data-embed='youtube']::after {
  content: '\25B6  YouTube embed';
}
.egg-embed[data-embed='twitter']::after {
  content: 'X / Twitter embed';
}
/* Hydrated state (reader/embeds.js): once a placeholder holds the real embed
   it drops the inert label/letterbox styling so the iframe / tweet fills it. */
.egg-embed[data-hydrated='1']::after {
  content: none;
}
.egg-embed[data-hydrated='1'] {
  background: transparent;
  display: block;
}
/* The X-Tweet blockquote sizes itself; only the YouTube frame keeps 16:9. */
.egg-embed[data-embed='twitter'][data-hydrated='1'] {
  aspect-ratio: auto;
}
.egg-embed-frame {
  width: 100%;
  height: 100%;
  border: 0;
}
.egg-article img.egg-img {
  max-width: 100%;
  height: auto;
}
.egg-article table {
  /* Fill the article column: a plain display:table + width:100% stretches the
     columns to the full width so short tables read like the wide ones instead
     of hugging their content (and no dead band). Full-width tables WRAP to fit
     rather than scroll, so display:block/overflow-x is intentionally dropped. */
  width: 100%;
  border-collapse: collapse;
  margin: 14px 0;
  font-size: 13px;
  border: 1px solid var(--line);
  /* border-radius is intentionally omitted: border-collapse tables ignore it,
     and the engine emits no wrapper element to round instead (P3/B5). */
}
.egg-article th,
.egg-article td {
  border: 1px solid rgba(255, 255, 255, 0.07);
  padding: 6px 10px;
  text-align: left;
}
.egg-article th {
  background: var(--canvas);
  color: var(--accent-kicker);
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  /* Keep header labels on one line (e.g. "2-PIECE" must not break at its
     hyphen); the column widens to fit instead of wrapping the heading. */
  white-space: nowrap;
}
.egg-article tr:hover td {
  background: rgba(19, 85, 232, 0.06);
}
/* QA wave 3 A2: table alignment via class= */
.egg-article td.center,
.egg-article th.center {
  text-align: center;
}
.egg-article td.left,
.egg-article th.left {
  text-align: left;
}
.egg-article td.right,
.egg-article th.right {
  text-align: right;
}
.egg-article table.center td,
.egg-article table.center th {
  text-align: center;
}
.egg-article table.left td,
.egg-article table.left th {
  text-align: left;
}
.egg-article table.right td,
.egg-article table.right th {
  text-align: right;
}
.egg-icon {
  display: inline-block;
  font: 700 11px/1 var(--font-mono, monospace);
  font-family: var(--font-display);
  letter-spacing: .04em;
  padding: 2px 5px;
  border: 1px solid var(--line-2);
  border-radius: 6px;
  vertical-align: middle;
}
.egg-icon-new,
.egg-icon-up,
.egg-icon-dyn {
  background: linear-gradient(135deg, rgba(19, 85, 232, 0.9), rgba(41, 135, 196, 0.9));
  color: var(--on-accent);
  border-color: var(--accent);
}
.egg-icon-hot {
  background: linear-gradient(135deg, #d13c3c, #ff7a7a);
  color: #fff;
  border-color: #d13c3c;
}

/* ── Bar graph ([:bar_graph:begin]…[:bar_graph:end]) ──────────────────────── */
.egg-bar-graph {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 14px 0;
}
.egg-bar {
  display: grid;
  grid-template-columns: 90px 1fr 48px;
  align-items: center;
  gap: 8px;
}
.egg-bar-label {
  font: 600 13px/1.3 var(--font-mono, monospace);
  color: var(--ink);
}
.egg-bar-track {
  background: rgba(255, 255, 255, 0.08);
  border-radius: 999px;
  height: 14px;
  overflow: hidden;
}
.egg-bar-fill {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--accent-kicker));
  border-radius: 999px;
}
.egg-bar-val {
  font: 600 12px/1 var(--font-mono, monospace);
  color: var(--callout-info);
  text-align: right;
}
.egg-w-0{width:0%}
.egg-w-5{width:5%}
.egg-w-10{width:10%}
.egg-w-15{width:15%}
.egg-w-20{width:20%}
.egg-w-25{width:25%}
.egg-w-30{width:30%}
.egg-w-35{width:35%}
.egg-w-40{width:40%}
.egg-w-45{width:45%}
.egg-w-50{width:50%}
.egg-w-55{width:55%}
.egg-w-60{width:60%}
.egg-w-65{width:65%}
.egg-w-70{width:70%}
.egg-w-75{width:75%}
.egg-w-80{width:80%}
.egg-w-85{width:85%}
.egg-w-90{width:90%}
.egg-w-95{width:95%}
.egg-w-100{width:100%}

/* ── Steps ([:steps:begin]…[:steps:end]) walkthrough cards ────────────────── */
.egg-steps {
  list-style: none;
  counter-reset: egg-step;
  margin: 14px 0;
  padding: 0;
}
.egg-steps > .egg-step {
  counter-increment: egg-step;
  position: relative;
  padding: 4px 0 14px 44px;
  border-left: 2px solid var(--line-2);
  margin-left: 15px;
}
.egg-steps > .egg-step:last-child {
  border-left-color: transparent;
  padding-bottom: 0;
}
.egg-steps > .egg-step::before {
  content: counter(egg-step);
  position: absolute;
  left: -16px;
  top: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font: 700 14px/30px var(--font-mono, monospace);
  text-align: center;
}

/* ── Pros / cons ([:pros_cons:begin]…[:pros_cons:end]) ────────────────────── */
.egg-proscons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin: 14px 0;
}
/* A one-sided block still emits the other column's <div> (the empty <ul> is
   pinned by test_pros_cons_empty_side_renders_empty_ul_not_broken), which
   painted as a tinted, coloured-rule box around nothing — chrome with no
   content, reading as "the Cons failed to load" rather than "there are no
   cons". Hide the empty side and let the populated one take the row.

   Keyed on `ul:empty` rather than `:not(:has(li))` on purpose: `:has()` may
   not be nested inside another `:has()`, so the parent rule would be an
   invalid selector and get dropped silently — the columns stayed 1fr 1fr with
   nothing to show for it. Both rules below use a single level of `:has()`. */
.egg-proscons:has(ul:empty) {
  grid-template-columns: 1fr;
}
.egg-proscons > div:has(> ul:empty) {
  display: none;
}
.egg-pros,
.egg-cons {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 12px;
}
.egg-pros {
  border-top: 3px solid var(--good);
  background: rgba(74, 222, 128, 0.08);
}
.egg-cons {
  border-top: 3px solid var(--egg-red);
  background: rgba(255, 122, 122, 0.08);
}
/* Column identity in words, not just tint + glyph. Uses --good / --egg-red
   (the same pair the ✓/✗ and the top rules use) rather than --accent-link:
   a viridian heading would read as a link the reader can tap. */
.egg-proscons-title {
  margin: 0 0 6px;
  font: 700 13px/1.2 var(--font-display);
  letter-spacing: 0.02em;
}
.egg-pros .egg-proscons-title {
  color: var(--good);
}
.egg-cons .egg-proscons-title {
  color: var(--egg-red);
}
.egg-proscons ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.egg-proscons li {
  position: relative;
  padding-left: 22px;
  margin: 6px 0;
}
.egg-pros li::before {
  content: "✓";
  color: var(--good);
  position: absolute;
  left: 0;
  font-weight: 700;
}
.egg-cons li::before {
  content: "✗";
  color: var(--egg-red);
  position: absolute;
  left: 0;
  font-weight: 700;
}
@media (max-width: 720px) {
  .egg-proscons {
    grid-template-columns: 1fr;
  }
}

/* ── Compare ([:compare:begin]…[:compare:vs]…[:compare:end]) VS two-column ── */
.egg-compare {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: start;
  gap: 12px;
  margin: 14px 0;
}
.egg-compare-col {
  border: 1px solid var(--line-2);
  border-radius: 8px;
  padding: 10px 12px;
  min-width: 0;
}
.egg-compare-title {
  margin: 0 0 8px;
  font: 700 15px/1.3 var(--font-mono, monospace);
  color: var(--ink);
}
.egg-compare-vs {
  align-self: center;
  background: var(--accent);
  color: #fff;
  font: 700 12px/1 var(--font-mono, monospace);
  padding: 6px 8px;
  border-radius: 999px;
}
@media (max-width: 720px) {
  .egg-compare {
    grid-template-columns: 1fr;
  }
  .egg-compare-vs {
    justify-self: center;
  }
}

/* ── db_value: an image-typed column renders an inline icon ─────────────────
   [db_value:] is otherwise plain body text in the reader. An image-typed cell
   (and the system image_url field) emits <img class="egg-img"> instead, so cap
   it to the line box — the column type means "thumbnail / icon", and an
   uncapped full-size upload would break the paragraph it sits in.
   Mirrored in frontend/src/styles/egg.css. */
.egg-article .egg-dbval img.egg-img {
  width: auto;
  max-height: 1.6em;
  vertical-align: text-bottom;
}

/* ── db_infobox: full-width fact card, stacked in the article flow ─────────
   Was `float: right; width: 260px` (46fb003). A float only reads correctly
   when the section HAS prose for the card to sit beside; on a heading-only
   guide page one card floats down past several sections. (At diagnosis time
   `.egg-article h2` was still `display: flex`, so those headings shrank
   beside the card instead of wrapping; the h2 is a plain block again since
   CU-86d3yuqc4 — that changes the failure's look, not the verdict: the card
   still detaches from the section it documents.) Mirrored in
   frontend/src/styles/egg.css; pinned by
   articles/tests/test_reader_css.py. */
.egg-infobox {
  margin: 14px 0;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
}
.egg-infobox-img {
  display: block;
  /* Capped rather than `width: 100%`: spanning the full content column would
     stretch the record image into a banner AND upscale a small upload into a
     blurry one. 260px is the size it rendered at inside the old floated card.
     The `100%` term is load-bearing — this <img> carries no `egg-img` class,
     so the `.egg-article img.egg-img` clamp does NOT catch it, and a bare
     260px cap overflows the card once its content box is narrower than that
     (measured: 234px at a 320px viewport). */
  width: auto;
  max-width: min(260px, 100%);
  border-radius: 6px;
  margin-bottom: 8px;
}
.egg-infobox-title {
  margin: 0 0 8px;
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--ink);
}
.egg-infobox-rows {
  margin: 0;
}
.egg-infobox-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 4px 0;
  border-top: 1px solid var(--line);
  font-size: 13px;
}
.egg-infobox-row dt {
  color: var(--muted);
  font-weight: 500;
}
.egg-infobox-row dd {
  margin: 0;
  text-align: right;
}
/* An image-typed DB column renders a real <img class="egg-img"> in its row.
   The DB Admin grid renders the same cell as a thumb, so clamp it here too —
   `.egg-article img.egg-img { max-width: 100% }` alone resolves against a
   content-sized flex <dd>, so the 64px height cap is what actually keeps the
   row compact. Mirrored in frontend/src/styles/egg.css. */
.egg-infobox-row dd img.egg-img {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: 64px;
  margin-left: auto;
}

/* ── card_grid: responsive card grid ─────────────────────────────
   div-qualified on purpose: the legacy roster_card_grid DB template emits
   <table class='egg-card-grid'>; grid display on a table would break it. */
div.egg-card-grid {
  display: grid;
  gap: 10px;
  margin: 14px 0;
}
div.egg-card-grid--c2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
div.egg-card-grid--c3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
div.egg-card-grid--c4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
div.egg-card-grid--c5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
div.egg-card-grid--c6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.egg-card {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px;
  min-width: 0;
  font-size: 14px;
  background: var(--surface);
}
@media (max-width: 720px) {
  div.egg-card-grid--c3,
  div.egg-card-grid--c4,
  div.egg-card-grid--c5,
  div.egg-card-grid--c6 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ── Batch D: article cross-reference chip (inline pill; survives cells).
   Class is egg-art-chip, NOT egg-chip — the admin SPA already owns .egg-chip
   as a UI chip class (VisualBlock etc.). ─────────────────────────────── */
.egg-art-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--accent-link);
  font-weight: 600;
  font-size: 0.85em;
  text-decoration: none;
  vertical-align: middle;
}
.egg-art-chip:hover {
  border-color: var(--accent);
}
.egg-art-chip-img {
  width: 22px;
  height: 22px;
  margin-left: -6px;
  border-radius: 50%;
  object-fit: cover;
}
.egg-art-chip > span {
  white-space: nowrap;
}

/* ── Batch E: review furniture — [:score:] verdict medallion +
   [:score_card:] overall/axes card. Axis rows reuse the egg-bar markup
   above verbatim (same classes, same egg-w-N width ladder). ──────────── */
/* A 2px --accent capsule is this system's BUTTON language; on a static div it
   reads as tappable. The 1px --line-2 edge is sub-3:1 on its own, so the
   --surface fill and the mono `/ N` suffix carry containment, not the border. */
.egg-score {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 10px 18px;
  border: 1px solid var(--line-2);
  border-radius: 12px;
  background: var(--surface);
  margin: 14px 0;
}
.egg-score-val {
  font: 700 1.6em var(--font-mono);
  color: var(--ink);
}
.egg-score-max {
  font: 600 0.95em var(--font-mono);
  color: var(--muted);
}
.egg-score-lg .egg-score-val {
  font-size: 2.2em;
}
.egg-score-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 14px 16px;
  margin: 14px 0;
  background: var(--surface);
}
.egg-score-card .egg-score {
  margin: 0;
  align-self: flex-start;
}

/* ── Batch F: interactive & freshness — [:checklist:] ticks, [:updated:]
   banner, [:map:] static pins. Checklist/map are reader-JS enhanced
   (checklist.js persists ticks; map.js adds .egg-map--live). The base
   (class-free) map state IS the no-JS fallback: a readable pin list with
   visible popups. ─────────────────────────────────────────────────── */
ul.egg-checklist { list-style: none; margin: 14px 0; padding-left: 0; }
.egg-checklist li { margin: 6px 0; }
.egg-checklist label { display: inline-flex; align-items: baseline; gap: 8px; cursor: pointer; }
/* The box is drawn on the NATIVE input (appearance:none), never a proxy span —
   checklist.js keys persistence off this element and the e2e spec .check()s it.
   The tick must be a background image: <input> is a replaced element, so
   ::before/::after never render on it. Unchecked border is .35 alpha, not
   --line (.12) — the latter composites to 1.44:1 on --bg and fails SC 1.4.11. */
.egg-checklist input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 16px; height: 16px; flex: none; transform: translateY(2px); cursor: pointer;
  border: 1px solid rgba(255, 255, 255, 0.35); border-radius: 4px; background: transparent;
}
.egg-checklist input[type="checkbox"]:checked {
  border-color: var(--accent-link);
  background: rgba(95, 255, 110, 0.07)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2 6.4L4.6 9 10 3.2' fill='none' stroke='%235fff6e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
    center / 11px 11px no-repeat;
}
/* appearance:none drops the UA focus ring — restore it or this control becomes
   a WCAG 2.4.7 failure. forced-colors hands the box back to the UA, since a
   background-image tick is dropped in High Contrast Mode. */
.egg-checklist input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--accent-link); outline-offset: 2px;
}
@media (forced-colors: active) {
  .egg-checklist input[type="checkbox"] { appearance: auto; }
}
.egg-checklist li:has(input:checked) span { color: var(--muted); text-decoration: line-through; }

/* Provenance metadata, so it sits in the chip family (.egg-map-cats > li,
   .egg-art-chip) rather than cloning the authored-emphasis chrome that
   blockquote and .egg-callout share. The glyph is CSS, not emitted text, so
   it stays decorative and out of the accessibility tree. */
.egg-updated {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 10px 0; padding: 3px 10px;
  border: 1px solid var(--line-2); border-radius: 999px;
  background: var(--surface); color: var(--muted);
  font: 600 12px/1.6 var(--font-mono);
}
.egg-updated::before { content: "\21BB"; }

.egg-map { margin: 14px 0; }
.egg-map img { display: block; max-width: 100%; border: 1px solid var(--line-2); border-radius: 8px; }
ul.egg-map-pins { list-style: none; margin: 8px 0 0; padding-left: 0; }
.egg-map-pins li { margin: 4px 0; }
.egg-map-pin {
  background: none; border: 0; padding: 0; text-align: left;
  font: 600 14px/1.5 var(--font-sans); color: var(--ink);
}
.egg-map-pop { color: var(--muted); font-size: 0.9em; margin: 2px 0 8px; }

/* ── Map viewer (Phase A, Leaflet). Fallback rules above stay for no-JS;
   .egg-map--live hides the fallback and shows the viewer. ─────────────── */
.egg-map--live > .egg-map-img,
.egg-map--live > .egg-map-pins { display: none; }
.egg-map-viewport {
  height: min(70vh, 540px);
  background: #101010;
  border: 1px solid var(--line-2);
  border-radius: 8px;
  overflow: hidden;
}
.egg-map-cats {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 8px;
  padding: 0;
}
.egg-map-cats > li {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  font: 600 12px/1.6 var(--font-sans);
  background: rgba(255, 255, 255, 0.035);
}
.egg-map--live .egg-map-cats > li { cursor: pointer; user-select: none; }
.egg-map--live .egg-map-cats > li.is-off { opacity: 0.4; }
.egg-map-marker {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  color: #fff;
  background: var(--pin, #1355e8);
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: 50%;
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.55);
}
.egg-map-arrow { display: block; color: var(--pin, #4aa3d8); filter: drop-shadow(0 0 2px rgba(0,0,0,.6)); }
.egg-map-shape-label { font-weight: 600; }
/* Leaflet dark skin — scope everything under .egg-map-viewport. */
.egg-map-viewport .leaflet-bar a {
  background: #232222;
  color: #e8e6e3;
  border-color: rgba(255, 255, 255, 0.14);
}
.egg-map-viewport .leaflet-bar a:hover { background: #2c2b2b; }
.egg-map-popup .leaflet-popup-content-wrapper,
.egg-map-popup .leaflet-popup-tip {
  background: #232222;
  color: var(--ink);
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
}
.egg-map-popup .leaflet-popup-content { margin: 10px 12px; font: 13px/1.55 var(--font-sans); }

/* ── Accordion block ─────────────────────────────────────────────────────── */
.egg-accordion-block {
  border: 1px solid var(--line);
  border-radius: 10px;
  margin: 14px 0;
  padding: 0 12px;
  background: rgba(0, 0, 0, 0.18);
}
.egg-accordion-block > summary {
  font: 600 14px var(--font-display);
  color: var(--ink);
  cursor: pointer;
  padding: 10px 0;
  list-style-position: inside;
}
.egg-accordion-block > summary::marker {
  color: var(--accent-link);
}
.egg-accordion-block[open] > summary {
  border-bottom: 1px solid var(--line);
}

/* ── Image grid (parallel / center / evenly_space enlarged images) ─────────── */
.egg-img-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 14px 0;
}
.egg-article .egg-img-grid img {
  max-width: calc(50% - 5px);
  height: auto;
  border: 1px solid var(--line-2);
}
.egg-img-grid--center {
  justify-content: center;
}
.egg-img-grid--evenly_space {
  justify-content: space-between;
}

/* ── Tab groups (CSS-only radio-hack) ─────────────────────────────────────── */
.egg-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin: 14px 0;
  /* Containing block for the abspos radio below, so it anchors to .egg-tabs
     rather than the initial containing block. Harmless in the reader (its <html>
     scrolls the article), but the correct containing block, and it mirrors the
     admin .egg-tabs where the missing anchor caused a phantom window scrollbar. */
  position: relative;
}
.egg-tab-radio {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
/* Underline tabs. `--accent` is NOT available for the active edge here: once
   the pill is gone the underline is the sole selected-state indicator, and
   #1355e8 on --bg is 2.89:1, under SC 1.4.11's 3:1. Theme law already assigns
   active states to --accent-link (13.2:1). Padding is 12px 14px so the label
   clears the 24px SC 2.5.8 target minimum. */
.egg-tab-label {
  font: 500 13px var(--font-display);
  padding: 12px 14px;
  cursor: pointer;
  color: var(--muted);
  border: 0;
  background: none;
  border-radius: 0;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
  /* Labels are arbitrary inline markup (e.g. [:img:] + [nl] + text renders as
     <img><br>Text): center every line box so the text isn't flush-left under
     the image, and grow so the buttons share the full tab-bar width. shrink 0
     is safe — .egg-tabs is flex-wrap: wrap, so overlong rows wrap instead of
     overflowing. */
  text-align: center;
  flex: 1 0 auto;
  transition: border-color 0.15s, color 0.15s;
}
.egg-tab-label:hover {
  border-bottom-color: var(--line-2);
  color: var(--ink);
}
/* The strip rule lives on the panel's top edge, not the container's bottom:
   the panel is an order:99 flex child INSIDE .egg-tabs, so a container
   border-bottom would paint under it. .egg-tabs keeps flex-wrap:wrap — the
   panel is flex:1 0 100% and relies on wrapping to reach its own line. */
.egg-tab-panel {
  display: none;
  order: 99;
  flex: 1 0 100%;
  padding: 12px 2px;
  border-top: 1px solid var(--line);
}
.egg-tab-radio:checked + .egg-tab-label {
  color: var(--ink-2);
  font-weight: 600;
  border-bottom-color: var(--accent-link);
}
/* .egg-tab-radio is opacity:0 but still keyboard-focusable; without this the
   group is unnavigable by sight for keyboard users (WCAG 2.4.7). */
.egg-tab-radio:focus-visible + .egg-tab-label {
  outline: 2px solid var(--accent-link);
  outline-offset: -2px;
}
/* Supports up to 8 tabs per group; beyond the last pair a panel silently never
   displays, so extend this ladder rather than letting content vanish. */
.egg-tabs > .egg-tab-radio:nth-of-type(1):checked ~ .egg-tab-panel:nth-of-type(1),
.egg-tabs > .egg-tab-radio:nth-of-type(2):checked ~ .egg-tab-panel:nth-of-type(2),
.egg-tabs > .egg-tab-radio:nth-of-type(3):checked ~ .egg-tab-panel:nth-of-type(3),
.egg-tabs > .egg-tab-radio:nth-of-type(4):checked ~ .egg-tab-panel:nth-of-type(4),
.egg-tabs > .egg-tab-radio:nth-of-type(5):checked ~ .egg-tab-panel:nth-of-type(5),
.egg-tabs > .egg-tab-radio:nth-of-type(6):checked ~ .egg-tab-panel:nth-of-type(6),
.egg-tabs > .egg-tab-radio:nth-of-type(7):checked ~ .egg-tab-panel:nth-of-type(7),
.egg-tabs > .egg-tab-radio:nth-of-type(8):checked ~ .egg-tab-panel:nth-of-type(8) {
  display: block;
}

/* Block alignment — emitted by the [:align:] markup token (class-only;
   the engine never bakes an inline style=). Aligns text + inline images. */
.egg-align-left {
  text-align: left;
}
.egg-align-center {
  text-align: center;
}
.egg-align-right {
  text-align: right;
}

/* ── Responsive ───────────────────────────────────────────────────────────── */
@media (max-width: 1319px) {
  /* ≥1320 = full repeat(6,1fr) */
  .games-row,
  .wikis-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}
@media (max-width: 1023px) {
  .games-row,
  .wikis-grid {
    grid-template-columns: repeat(3, 1fr);
  }
  .review-card {
    flex: 0 0 300px;
  }
  .top-search {
    display: none;
  }
  /* Single column: main stacks FIRST (matching DOM order — the article body
     precedes the full Guide Index on mobile), then rail, then aside. */
  .egg-grid {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: 'main' 'rail' 'aside';
  }
  /* Collapsed grids drop the missing rows too — an empty named row would
     otherwise keep its 40px row gap(s) between main and the next block.
     Main stays first in every variant. */
  .egg-grid--no-rail {
    grid-template-areas: 'main' 'aside';
  }
  .egg-grid--no-aside {
    grid-template-areas: 'main' 'rail';
  }
  .egg-grid--no-rail.egg-grid--no-aside {
    grid-template-areas: 'main';
  }
  .egg-grid .rail,
  .egg-grid .aside {
    position: static;
  }
}
@media (max-width: 767px) {
  .games-row,
  .wikis-grid {
    grid-template-columns: repeat(2, 1fr);
  }
  /* Mobile top bar (landing, and the no-JS wiki-home/article fallback): keep the
     section links reachable — shrink and wrap, do not strip. */
  .top-in {
    gap: 10px;
    flex-wrap: wrap;
  }
  .topnav {
    font-size: 12px;
  }
}

/* ── Off-canvas reader drawers + fixed edge tabs ──────────────────────────────
   At <=1023px the 3-column reader grid collapses to one column (the rule
   above). With JS (drawers.js adds `html.reader-js`) the left rail / right
   aside become slide-in drawers opened by a pair of fixed edge tabs; with JS
   off they keep the <=1023px reflow-below-main fallback and the desktop nav
   stays. The drawers ARE the existing .rail / .aside moved off-canvas with a
   transform — never duplicated — so the main-first DOM order and the
   quick-access / guide-index / on-this-page / popular testids are all
   unchanged. */
.reader-scrim,
.reader-drawer-head {
  display: none;
}

/* Fixed edge tabs — the always-visible <=1023px drawer triggers. Slim vertical
   labels flush to the viewport edges, sitting over the `.egg-shell` 28px side
   gutter (`padding: 20px 28px 48px`, no mobile override) rather than the
   content column — so they are always reachable and never cover a word. See
   docs/superpowers/specs/2026-08-04-reader-edge-tabs-design.md; the geometry is
   pinned by frontend/e2e/reader-structures.spec.ts. Hidden by default; shown
   only under `.reader-js` at <=1023px. */
.reader-edgetab {
  display: none;
  position: fixed;
  /* Low on the screen: within thumb reach, and clear of both the sticky header
     and the article's first heading. Anchored to the BOTTOM, so — unlike the
     old top-band placement — the /sandbox/* banner (which pushes `.top` down by
     `--reader-banner-h`) cannot displace it. No banner term needed. */
  bottom: 18%;
  z-index: 260;
  /* Vertical label. `line-height: 1` is required, not cosmetic: in vertical-rl
     the line box is the tab's WIDTH, and the UA default `normal` (~1.3) would
     take the tab to ~28.6px — over the 28px gutter and onto the text. At 1 the
     tab measures ~25.5px (6+6 padding, 1+2 borders, 10.5px line box). */
  writing-mode: vertical-rl;
  line-height: 1;
  padding: 14px 6px;
  align-items: center;
  justify-content: center;
  /* #262525 — the base colour the design specifies as rgba(38,37,37,.94),
     as a token. The .94 alpha is deliberately dropped: the tab sits over the
     page margin with no text beneath it, so opaque costs nothing and keeps the
     "page content never shows through a tab" property. */
  background: var(--canvas);
  color: var(--muted);
  font-family: var(--font-sans);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 1.8px;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
  transition: color 0.2s ease;
}
/* Quiet hover — the label brightens, the tab does not fill. These are ambient
   controls parked in the margin, not primary actions. */
.reader-edgetab:hover {
  color: var(--ink);
}
/* State the focus ring explicitly (same convention as the checklist checkbox
   further up this file): at `left`/`right: -1px` the UA ring's outer edge would
   be clipped by the viewport boundary, so pin a ring we control. That outer
   edge still clips; the other three sides stay visible, which is enough to
   show focus. */
.reader-edgetab:focus-visible {
  outline: 2px solid var(--accent-link);
  outline-offset: 2px;
}
/* Flush to the edge (-1px hides the outer border off-screen). The inner-edge
   accent carries the left=menu / right=contents wayfinding. */
.reader-edgetab-rail {
  left: -1px;
  border: 1px solid var(--line);
  border-right: 2px solid var(--accent-kicker);
  border-radius: 0 8px 8px 0;
}
.reader-edgetab-aside {
  right: -1px;
  border: 1px solid var(--line);
  border-left: 2px solid var(--accent-link);
  border-radius: 8px 0 0 8px;
}
/* drawers.js sets `el.hidden = true` on an edge tab whose drawer is absent (a
   dead control — no rail/aside to open, or an empty aside). The UA
   `[hidden] { display: none }` rule is user-agent-origin, so it loses to the
   author-origin `display: flex` below regardless of specificity — force
   `hidden` to always win for these controls. */
.reader-edgetab[hidden] {
  display: none !important;
}
@media (max-width: 1023px) {
  .reader-js .reader-edgetab {
    display: flex;
  }
  /* An open drawer sets `reader-drawer-open` on <html> (drawers.js). Hide BOTH
     edge tabs while a drawer is open so they don't float over the scrim — the
     drawer's own close paths (scrim tap, header ✕, Escape, a TOC tap) dismiss
     it. Higher specificity than the `display: flex` above, so it wins without
     !important; the dead-control `[hidden]` override still takes precedence. */
  .reader-js.reader-drawer-open .reader-edgetab {
    display: none;
  }
}

@media (max-width: 1023px) {
  /* Off-canvas drawers — re-purpose the existing .rail (left) + .aside (right).
     Gated on `.reader-js` so JS-off keeps the reflow-below-main fallback. The
     closed drawers are `visibility:hidden` so their links aren't tab-focusable
     while off-screen. */
  .reader-js .egg-grid .rail,
  .reader-js .egg-grid .aside {
    position: fixed;
    top: 0;
    bottom: 0;
    /* Above the sticky `.top` header (z-index 100) — the header now stays
       visible at this width, so an open drawer must overlay it (and its own
       sticky close bar must clear it), below the edge tabs (z-index 260). */
    z-index: 255;
    width: 86%;
    max-width: 330px;
    margin: 0;
    padding: 0 16px 28px;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--bg);
    visibility: hidden;
    transition:
      transform 0.26s cubic-bezier(0.4, 0, 0.2, 1),
      visibility 0.26s;
  }
  .reader-js .egg-grid .rail {
    left: 0;
    border-right: 1px solid var(--line-2);
    transform: translateX(-104%);
  }
  .reader-js .egg-grid .aside {
    right: 0;
    border-left: 1px solid var(--line-2);
    transform: translateX(104%);
  }
  .reader-js .egg-grid .rail.reader-open,
  .reader-js .egg-grid .aside.reader-open {
    transform: translateX(0);
    visibility: visible;
  }

  /* Dimming scrim behind an open drawer — above the sticky `.top` header
     (z-index 100) so the header dims with the rest of the page, below the
     drawer itself (z-index 255). */
  .reader-js .reader-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 250;
    background: var(--scrim);
    opacity: 0;
    visibility: hidden;
    transition:
      opacity 0.22s,
      visibility 0.22s;
  }
  .reader-js .reader-scrim.show {
    opacity: 1;
    visibility: visible;
  }

  /* Drawer header (sticky close bar) — shown only in drawer mode. */
  .reader-js .reader-drawer-head {
    display: flex;
    align-items: center;
    gap: 9px;
    position: sticky;
    top: 0;
    margin: 0 -16px 8px;
    padding: 14px 16px;
    background: var(--bg);
    border-bottom: 1px solid var(--line-2);
  }
  .reader-drawer-head h3 {
    margin: 0;
    color: var(--ink-2);
    font-size: 15px;
    font-weight: 800;
  }
  .reader-drawer-close {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    background: none;
    border: 1px solid var(--line-2);
    border-radius: 7px;
    color: var(--muted);
    font-size: 14px;
    cursor: pointer;
  }
}

/* ── Sandbox mode (admin-only preview of unpublished content) ──────────────
 * Shown ONLY on /sandbox/* pages (the `sandbox` context flag). The public
 * reader never emits these. Colors come from the existing reader tokens — no
 * hardcoded hex. The banner is a sticky bar above the `.top` header; the
 * `body.sandbox .top` offset keeps the header clear of it. */
.sandbox-banner {
  position: sticky;
  top: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 34px;
  padding: 0 16px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
/* Expose the banner height as an inherited custom property so the header
   offset (below), the sticky rail/aside `top`, and the root `scroll-padding-top`
   all derive from ONE number — the `.sandbox-banner { height: 34px }` above.
   Declared on <html> (not `body.sandbox`) so the root scroll container can read
   it for `scroll-padding-top`; it inherits down to the body-nested sidebars.
   `:has()` is already used in this file (see `.egg-checklist li:has(...)`). */
html:has(body.sandbox) {
  --reader-banner-h: 34px;
}
body.sandbox .top {
  top: var(--reader-banner-h, 34px);
}
.sandbox-pill {
  display: inline-block;
  vertical-align: middle;
  padding: 1px 8px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--tint);
  color: var(--accent-kicker);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* ── Click-to-enlarge image lightbox (D1) ─────────────────────────────────────
 * The markup engine flags an enlargeable image with the `egg-enlargeable` class
 * (renderer.py). lightbox.js adds `html.lightbox-js` and builds the overlay on
 * click. The zoom-in affordance is gated on `.lightbox-js` so JS-off shows no
 * misleading cursor; colors use existing tokens + the shared --scrim (no hex). */
html.lightbox-js .egg-article img.egg-enlargeable {
  cursor: zoom-in;
  transition: opacity 0.15s ease;
}
html.lightbox-js .egg-article img.egg-enlargeable:hover {
  opacity: 0.9;
}
.egg-lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--scrim);
}
.egg-lightbox[hidden] {
  display: none;
}
.egg-lightbox-fig {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  max-width: 100%;
  max-height: 100%;
}
.egg-lightbox-img {
  max-width: 92vw;
  max-height: 84vh;
  object-fit: contain;
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.7);
}
.egg-lightbox-cap {
  max-width: 92vw;
  color: var(--on-accent);
  font-size: 13px;
  text-align: center;
}
.egg-lightbox-close {
  position: fixed;
  top: 14px;
  right: 16px;
  width: 40px;
  height: 40px;
  padding: 0;
  font-size: 26px;
  line-height: 1;
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: 999px;
  color: var(--ink-2);
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.5);
}
.egg-lightbox-close:hover {
  background: var(--tint);
}

/* Related-pages sections (Phase 2) — article foot; page chrome, no egg.css mirror. */
.egg-related {
  margin-top: 40px;
}
.egg-related-head {
  font-family: var(--font-display);
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--accent-kicker);
  margin: 0 0 14px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}
.egg-related-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 14px;
}
.egg-related-card {
  display: block;
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
  background: var(--surface);
  text-decoration: none;
  color: var(--ink);
}
.egg-related-card:hover {
  border-color: var(--accent);
}
.egg-related-card-img {
  display: block;
  aspect-ratio: 16 / 9;
  background: var(--canvas);
}
.egg-related-card-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.egg-related-card-ttl {
  display: block;
  padding: 10px 12px;
  font-family: var(--font-display);
  font-size: 13.5px;
  font-weight: 600;
}
.egg-related-table {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.egg-related-cell {
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--accent-link);
  text-decoration: none;
  font-size: 14px;
}
.egg-related-cell:hover {
  border-color: var(--accent);
  color: var(--accent-link-hover);
}
@media (max-width: 1023px) {
  .egg-related-table {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 640px) {
  .egg-related-table {
    grid-template-columns: 1fr;
  }
}

/* ── Global search results page (spec 2026-07-15) ─────────────────────────
 * Cross-wiki /search/ + /sandbox/search/. Result rows follow the
 * .egg-related-card surface language (translucent card, 1px --line border,
 * accent hover border); wiki/category/date metadata is kicker text. */
.egg-search-page {
  max-width: 760px;
  margin: 0 auto;
  padding: 36px 28px 64px;
}
.egg-search-head {
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--ink-2);
  margin: 0 0 20px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.egg-search-note {
  color: var(--muted);
  font-size: 14.5px;
}
.egg-search-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.egg-search-link {
  display: block;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  text-decoration: none;
  color: var(--ink);
}
.egg-search-link:hover {
  border-color: var(--accent);
}
.egg-search-title {
  display: block;
  font-family: var(--font-display);
  font-size: 15.5px;
  font-weight: 600;
  color: var(--accent-link);
}
.egg-search-link:hover .egg-search-title {
  color: var(--accent-link-hover);
}
.egg-search-meta {
  display: block;
  margin-top: 4px;
  font-size: 11.5px;
  color: var(--accent-kicker);
}
.egg-search-snippet {
  display: block;
  margin-top: 6px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted);
}
.egg-search-pager {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 24px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: 13.5px;
}
.egg-search-pager a {
  color: var(--accent-link);
}
.egg-search-pager a:hover {
  color: var(--accent-link-hover);
}
.egg-search-pager-status {
  color: var(--dim);
  font-size: 11.5px;
}

/* Header-search suggestions dropdown (search.js). Anchored inside the
 * .top-search form (position:relative) — NOT the viewport — so the sandbox
 * banner's header offset never needs compensating. Sits above page content
 * via the sticky header's own stacking context (.top is z-index 100). */
.top-search {
  position: relative;
}
.top-search-suggest {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 6px;
  list-style: none;
  background: var(--bg-elev);
  border: 1px solid var(--line-2);
  border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
  max-height: 60vh;
  overflow-y: auto;
}
.top-search-suggest li {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border-radius: 7px;
  cursor: pointer;
}
.top-search-suggest li:hover,
.top-search-suggest li[aria-selected='true'] {
  background: var(--canvas);
}
.top-search-suggest-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}
.top-search-suggest li[aria-selected='true'] .top-search-suggest-title {
  color: var(--accent-link);
}
.top-search-suggest-wiki {
  font-size: 10.5px;
  color: var(--accent-kicker);
}
