/* 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', 'Courier New', 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);
}
/* Clearfix: .egg-infobox floats right, so without this an infobox taller
   than adjacent prose underflows .egg-article (footer/related-rail overlap). */
.egg-article::after {
  content: "";
  display: table;
  clear: both;
}
.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;
  display: flex;
  align-items: center;
  gap: 12px;
}
.egg-article h2::before {
  content: '';
  flex: none;
  width: 4px;
  height: 22px;
  border-radius: 2px;
  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: floating fact card (float right desktop, full-width mobile) ── */
.egg-infobox {
  float: right;
  width: 260px;
  margin: 4px 0 14px 16px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
}
.egg-infobox-img {
  display: block;
  width: 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>, which lets a full-size upload blow the 260px card
   open. 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;
}
@media (max-width: 720px) {
  .egg-infobox {
    float: none;
    width: auto;
    margin: 14px 0;
  }
}

/* ── 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;
  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. The sticky
   .top header serves every width now, so there is no mobile top-bar and no
   scroll-revealed floating buttons. Hidden by default; shown only under
   `.reader-js` at <=1023px. */
.reader-edgetab {
  display: none;
  position: fixed;
  /* Sit 12px below the sticky 55px `.top` header — the same 12px inset the tabs
     keep from the left/right viewport edges (`left/right: 12px`, below) so the
     gap reads even on every side. On /sandbox/* the banner pushes `.top` down by
     `--reader-banner-h`, so add it (mirrors the sticky rail/aside offset at
     `.egg-grid .rail,.aside`) to preserve the 12px gap there too; 0px elsewhere. */
  top: calc(67px + var(--reader-banner-h, 0px));
  z-index: 260;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
  transition: background 0.2s ease;
}
.reader-edgetab svg {
  display: block;
  width: 18px;
  height: 18px;
}
/* Both tabs are OPAQUE (solid `--bg-elev`, not an alpha tint) so page content
   never shows through them; the accent border + icon carry the left=menu (blue)
   / right=contents (green) wayfinding. */
.reader-edgetab-rail {
  left: 12px;
  border: 1.5px solid var(--accent);
  background: var(--bg-elev);
  color: var(--ink-2);
}
.reader-edgetab-rail:hover {
  /* Blue is the sanctioned fill/interaction accent, so the menu tab fills. */
  background: var(--accent);
  color: var(--on-accent);
}
.reader-edgetab-aside {
  right: 12px;
  border: 1.5px solid var(--accent-link);
  background: var(--bg-elev);
  color: var(--accent-link);
}
.reader-edgetab-aside:hover {
  /* --accent-link is a link/active accent, never a fill (dark-theme
     discipline), so the contents tab tints + brightens its edge instead. */
  background: color-mix(in srgb, var(--accent-link) 18%, var(--bg-elev));
  border-color: var(--accent-link-hover);
}
/* 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;
  }
}

/* Clear the fixed edge-tab band (top:67px + 44px tall = ~67-111px, plus the
   sandbox banner where present) so the
   P2 breadcrumb + banner (article) / top-of-main content (category, wiki
   home — all three templates share the .egg-main wrapper) start below the
   always-visible drawer triggers instead of under them. JS-off reflows the
   rail/aside below main and never shows the tabs, so gate on .reader-js.
   Does not touch `.egg-grid .rail,.aside { top: 78px }` (a separate sticky
   offset for the desktop-collapsed drawers, unaffected by this padding). */
@media (max-width: 1023px) {
  .reader-js .egg-main {
    padding-top: 56px;
  }
}

@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);
}
