/* ===================================================================
   GENUINE UNDEAD — NAV MENU + MODAL RUBRIQUES
   Kept in its own file on purpose: style.css / scene.js / events.js
   are the working cube engine and shouldn't be touched. Everything
   here layers on top using the same visual language already defined
   there (Courier New, neon green #39ff8f, dark glass panels).

   All content rubriques (OG Gallery, Timeline, 3 Statements, Art,
   Migration) share one generic modal system (.gu-modal / .gu-scrim)
   instead of five near-duplicate blocks, so a style tweak here
   applies everywhere at once.

   No dedicated top-left logo badge anymore, on either breakpoint —
   the GU logo now lives inside #reset-camera's own pulsing square
   instead (see the image-specific overrides further down), replacing
   the plain colored square that used to be there.
=================================================================== */

/* -----------------------------------------------------------------
   TOPBAR SPACE — make room for the fixed nav bar (top-center) that
   sits above #topbar's own content, so it doesn't overlap the
   tagline/status/search row. On touch devices the nav moves to a
   bottom bar instead (see the touch media query further down).
----------------------------------------------------------------- */
#topbar {
  /* Base fallback before nav-og.js measures the real banner height and
     pushes this down to clear the (now lower) nav bar exactly — see
     positionBannerAndNav() in nav-og.js. */
  padding-top: 58px !important;
  padding-left: 58px !important;
}

/* -----------------------------------------------------------------
   GU BANNER — the collection's own "Welcome to Genuine Undead" art,
   with real alpha transparency (no more near-black rectangle to
   blend away — this file is simply painted straight onto the site's
   own black background, no mix-blend-mode needed).

   Laptop: its own dedicated band across the top of the screen, above
   the rubrique nav — nav-og.js measures the banner's real rendered
   height and pushes #gu-nav (and #topbar's padding-top) down to clear
   it exactly, so nothing else on the page has to move. Width is
   measured live against the actual "OG Gallery"/"Official Links"
   button positions for an exact match; the CSS width below is only
   the pre-JS fallback.

   Mobile keeps its original spot entirely (see the touch media query)
   — same small strip between the logo and "live", not this dedicated
   band.
----------------------------------------------------------------- */
#gu-banner {
  position: fixed;
  /* Touches the very top edge of the screen, as if the illustration's
     own hanging cables carried on up past the edge rather than
     starting mid-air — env() only (no 10px minimum) so it's flush
     against the top on any device, notch or not. */
  top: env(safe-area-inset-top);
  left: 50%;
  transform: translateX(-50%);
  /* Enlarged again per feedback — a larger fraction of the OG
     Gallery/Official Links span (set in nav-og.js) and a much less
     transparent opacity, so it reads as a real presence rather than
     faint background scenery. */
  width: min(820px, calc(100vw - 300px));
  height: auto;
  max-height: 180px;
  z-index: 76;
  opacity: 0.9;
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
}

/* -----------------------------------------------------------------
   THE SINGLE NEON CIRCUIT (laptop only) — one continuous SVG path,
   not two separate animated elements. #gu-circuit-master is a plain,
   full-viewport overlay (1 unit = 1 real pixel, no viewBox scaling)
   so the coordinates nav-og.js computes for #gu-circuit-path (in
   positionBannerAndNav()) can be real screen pixels directly.

   The path itself is one unbroken line: down the short stem from the
   pulsing dot (under the banner), right along the nav bar's top edge,
   all the way around its rectangle, back to the stem, and up to the
   dot again — see the exact "d" string built in nav-og.js. A single
   pathLength="100" + stroke-dasharray/offset animation travels that
   entire shape as one dash, so there is only ever one traveling
   light, visiting the stem and the whole nav frame in one pass.

   #gu-circuit-dot is the small square at the stem's start — it pulses
   three times while the path's dash is idle/invisible, then goes dim
   while the dash makes its full pass, then pulses again right as the
   dash returns — both share the same 6s timeline (see the two
   keyframes below) so they read as one continuous circuit.

   Hidden entirely on mobile (see the touch override) — mobile's own
   single neon traces the tagline pill instead, never both at once.
----------------------------------------------------------------- */
#gu-circuit-master {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: 76;
  pointer-events: none;
  overflow: visible;
}

#gu-circuit-dot {
  fill: #39ff8f;
  filter: drop-shadow(0 0 5px #39ff8f) drop-shadow(0 0 10px rgba(57, 255, 143, 0.6));
  transform-box: fill-box;
  transform-origin: center;
  animation: gu-circuit-dot-pulse 1.4s ease-in-out infinite;
}

/* FIX: the dot no longer connects to the nav's circuit trace (the
   stem used to run all the way down to the nav bar, which - once the
   bar moved to the bottom of the screen - meant a neon line crossing
   straight through the cube and the whole screen top to bottom). It
   now beats continuously on its own, like a heartbeat, completely
   independent of the path below. */
@keyframes gu-circuit-dot-pulse {
  0%, 100% { opacity: 1;   transform: scale(1);    }
  50%      { opacity: 0.55; transform: scale(1.25); }
}

#gu-circuit-path {
  fill: none;
  stroke: #39ff8f;
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  filter: drop-shadow(0 0 4px #39ff8f) drop-shadow(0 0 8px rgba(57, 255, 143, 0.6));
  stroke-dasharray: 18 82;
  animation: gu-circuit-path-travel 30s linear infinite;
}

/* FIX: same 30s speed as before, but now a plain, continuous loop -
   the path itself is just the nav bar's own rectangle now (built in
   nav-og.js, no stem to the dot), so there's no separate "idle while
   the dot pulses" phase to synchronize with anymore. */
@keyframes gu-circuit-path-travel {
  0%   { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: -100; }
}

/* -----------------------------------------------------------------
   CIRCUIT TRACE (generic, reused elsewhere) — the plain, always-
   looping version used by the Art rubrique's gallery frame. Unrelated
   to the single nav circuit above.

   pathLength="100" on the rect means the stroke-dasharray/offset
   values are simple percentages of the perimeter, regardless of the
   actual pixel size of whatever it's tracing.
----------------------------------------------------------------- */
.gu-circuit-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.gu-circuit-svg rect,
.gu-circuit-svg path {
  vector-effect: non-scaling-stroke;
}

/* Small pulsing square at the trace's start point (top-center of the
   frame) — pulses 3 times, then dims while the path below travels the
   full loop, then brightens again for 3 more pulses right as the
   trace returns. Same timing shape as the nav's #gu-circuit-dot. Real
   pixel size now (positioned live by positionArtCircuit() in
   nav-og.js) — no viewBox stretching it out of shape anymore. */
#art-circuit-dot {
  fill: #39ff8f;
  filter: drop-shadow(0 0 4px #39ff8f);
  animation: art-circuit-dot-pulse 9s ease-in-out infinite;
}

@keyframes art-circuit-dot-pulse {
  0%, 3%   { opacity: 1;    }
  6%       { opacity: 0.2;  }
  9%, 12%  { opacity: 1;    }
  15%      { opacity: 0.2;  }
  18%, 21% { opacity: 1;    }
  26%      { opacity: 0.15; }
  90%      { opacity: 0.15; }
  96%, 100% { opacity: 1;   }
}

/* The single traveling dash — idle during the dot's pulses, then one
   pass around the entire frame, then fades right as the dot takes
   over again. pathLength="100" on the <path> is what makes "10 90"
   mean exactly one 10%-long dash for the whole loop, regardless of
   the frame's real pixel size. */
#art-circuit-path {
  fill: none;
  stroke: #39ff8f;
  stroke-width: 1;
  filter: drop-shadow(0 0 4px #39ff8f);
  stroke-dasharray: 10 90;
  animation: art-circuit-path-travel 9s linear infinite;
}

@keyframes art-circuit-path-travel {
  0%, 20%  { opacity: 0; stroke-dashoffset: 0; }
  24%      { opacity: 1; }
  90%      { opacity: 1; stroke-dashoffset: -100; }
  94%, 100% { opacity: 0; stroke-dashoffset: 0; }
}

/* -----------------------------------------------------------------
   TAGLINE + STATUS + SEARCH — pulled out of #topbar's own grid flow
   on laptop and pinned as fixed corner elements instead, per spec:
   the tagline next to the logo (top-left), "live" and the search
   cluster together in the top-right corner. Position:fixed removes
   them from the grid automatically, so #topbar's own grid-area rules
   for them (in style.css) simply go unused here — only #hud is still
   part of #topbar's actual flow. Untouched on mobile (see the touch
   media query), which still needs all of these inside #topbar's own
   layout for the row2 flex technique used there.

   The tagline deliberately has NO frame/background/border on laptop
   here (bare text only, per spec) — its original pill styling from
   style.css still applies on mobile, unaffected by this override.
----------------------------------------------------------------- */
#matrix-tagline {
  position: fixed;
  top: max(10px, env(safe-area-inset-top));
  left: 60px;
  z-index: 78;
  overflow: visible;
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
}

#status {
  position: fixed;
  top: max(10px, env(safe-area-inset-top));
  right: 20px;
  z-index: 78;
}

#search-column {
  position: fixed;
  top: 46px;
  right: 20px;
  z-index: 78;
}

/* -----------------------------------------------------------------
   HUD BADGES ("sales 24h" / "last sale") — harmonized to the same
   height as the search cluster's own icon buttons (26px), so the two
   groups read as one consistent control language instead of two
   different scales sitting side by side. Stacked vertically on
   laptop per spec ("last sale" under "sales 24h"); mobile keeps them
   side by side on one line (see the touch override, unaffected here).
----------------------------------------------------------------- */
#hud {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}

#sales-24h-badge,
#last-sale-badge {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding: 0 10px;
  font-size: 11px;
  box-sizing: border-box;
}

/* style.css's own #last-sale-badge has margin-left:10px, meant for
   the original single-row layout — stacked vertically now, that
   margin would push it out of alignment with "sales 24h" above it. */
#last-sale-badge {
  margin-left: 0;
}

/* -----------------------------------------------------------------
   RESET-CAMERA'S LOGO — the GU logo now lives inside the pulsing
   "reset position" square (see the HTML comment there) rather than
   its own top-left corner badge. Sizing/the pulse animation both
   still come entirely from style.css's existing .reset-square rules
   (12px desktop, 10px mobile via its own touch override) — this only
   adds what's needed now that the square contains a real image
   instead of being an empty colored div.
----------------------------------------------------------------- */
#reset-camera .reset-square {
  overflow: hidden;
  background: #0a0a0c;
}

#reset-camera .reset-square img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  image-rendering: pixelated;
}

/* -----------------------------------------------------------------
   RESET-CAMERA POSITION (laptop only) — bottom-right corner, with a
   1.5x-larger logo (18px vs the original 12px square) per spec.
   Mobile keeps its own separate fixed-position + smaller-size rules
   in the touch media query further down, untouched by this.
----------------------------------------------------------------- */
#reset-camera {
  position: fixed;
  bottom: 18px;
  right: 22px;
  z-index: 30;
  /* Row, not the original column — "after the word position", not
     above it, per spec. Mobile keeps its own separate layout in the
     touch media query further down, untouched by this. */
  flex-direction: row;
  align-items: center;
  gap: 8px;
}

#reset-camera .reset-square {
  width: 18px;
  height: 18px;
  order: 2;
}

#reset-camera .reset-label {
  order: 1;
}

/* -----------------------------------------------------------------
   NAV MENU — "rubriques" that pulse gently to invite a click. Single
   row always (horizontal scroll if it doesn't fit) so it never grows
   downward into the topbar's own content, on any screen size.
   position:fixed already makes it a valid containing block for its
   own .gu-circuit-svg child (see further up) — deliberately no
   box-shadow/ambient glow here: exactly one neon effect (the animated
   traveling trace) traces this frame, per spec.
----------------------------------------------------------------- */
#gu-nav {
  position: fixed;
  top: auto;
  bottom: max(24px, env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  z-index: 75;
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-end;
  height: auto;
  max-height: 90px;
  gap: 8px;
  padding: 6px;
  /* Was capped at a flat 920px, far too narrow for 12 rubriques (needs
     roughly 1300px at this font size) — every laptop screen ended up
     scroll-clipped after "X", which read as the bar being cut off.
     Now sized from the actual viewport instead, with a generous cap
     only to stop it from looking absurd on ultra-wide monitors, and
     enough side margin (110px each) to clear the logo on the left and
     leave breathing room on the right. */
  max-width: min(1500px, calc(100vw - 220px));
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  background: rgba(10, 10, 12, 0.7);
  backdrop-filter: blur(6px);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  scrollbar-width: none;
}

/* FIX: scrollbar-width:none above only works in Firefox - Windows
   Chrome/Edge ignore it entirely and fall back to their default gray
   scrollbar, which is exactly what was reported as "ugly" (invisible
   on Mac only because its scrollbars auto-hide regardless of any CSS
   here). This is the WebKit/Blink equivalent, hiding it there too. */
#gu-nav::-webkit-scrollbar {
  display: none;
}

.gu-nav-btn {
  font-family: 'Courier New', monospace;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #39ff8f;
  background: rgba(57, 255, 143, 0.08);
  border: 1px solid rgba(57, 255, 143, 0.3);
  border-radius: 6px;
  padding: 8px 12px;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  display: inline-block;
  flex: none;
  align-self: center;
  animation: gu-nav-pulse 2.4s ease-in-out infinite;
}

/* FIX: the base align-self:center above was overriding #gu-nav's own
   align-items:flex-end for every individual button, which is exactly
   why that earlier fix had no visible effect - align-self on a child
   always wins over its parent's align-items. Scoped with the ">"
   child combinator so it only matches the regular standalone buttons
   (Marketplace, Art, etc.) that sit directly inside #gu-nav - GU
   Tools/Undead Defenders/Game are nested two levels deeper inside
   .gu-nav-stack-icons and are untouched by this, exactly as
   requested. Higher specificity than the plain .gu-nav-btn rule above
   (id + child combinator vs a single class) wins regardless of order. */
#gu-nav > .gu-nav-btn {
  align-self: flex-end;
}

/* stagger the pulse per item so the whole bar doesn't beat in unison */
.gu-nav-btn:nth-child(2) { animation-delay: 0.2s; }
.gu-nav-btn:nth-child(3) { animation-delay: 0.4s; }
.gu-nav-btn:nth-child(4) { animation-delay: 0.6s; }
.gu-nav-btn:nth-child(5) { animation-delay: 0.8s; }
.gu-nav-btn:nth-child(6) { animation-delay: 1.0s; }
.gu-nav-btn:nth-child(7) { animation-delay: 1.2s; }
.gu-nav-btn:nth-child(8) { animation-delay: 1.4s; }
.gu-nav-btn:nth-child(9) { animation-delay: 1.6s; }
.gu-nav-btn:nth-child(10) { animation-delay: 1.8s; }
.gu-nav-btn:nth-child(11) { animation-delay: 2.0s; }
.gu-nav-btn:nth-child(12) { animation-delay: 2.2s; }

@keyframes gu-nav-pulse {
  0%, 100% { box-shadow: 0 0 4px rgba(57, 255, 143, 0.25); border-color: rgba(57, 255, 143, 0.3); }
  50% { box-shadow: 0 0 14px rgba(57, 255, 143, 0.85); border-color: rgba(57, 255, 143, 0.8); }
}

/* Hidden on laptop — the nav bar there almost never needs to scroll,
   and if it does, the touch-only version further down is what
   actually shows and positions this. */
#gu-nav-scrollbar {
  display: none;
}

.gu-nav-btn:hover,
.gu-nav-btn.active {
  background: rgba(57, 255, 143, 0.22);
  color: #eafff2;
  animation-play-state: paused;
  box-shadow: 0 0 14px rgba(57, 255, 143, 0.85);
}

/* pink variant, used for external "leave the site" links (Merch,
   Discord, X, Marketplace) so they read visually distinct from the
   in-page rubriques (OG Gallery, Timeline, Statements, Art, Migration). */
.gu-nav-btn.accent-pink {
  color: #e0479f;
  background: rgba(224, 71, 159, 0.08);
  border-color: rgba(224, 71, 159, 0.3);
}
.gu-nav-btn.accent-pink:hover,
.gu-nav-btn.accent-pink.active {
  background: rgba(224, 71, 159, 0.22);
  box-shadow: 0 0 14px rgba(224, 71, 159, 0.85);
}

/* GAME rubrique button (Punk X Undead) — reuses .gu-nav-btn's exact
   size/padding/spacing so the bar's row height and gaps never shift,
   but deliberately opts out of the neon pulse used by every other
   rubrique: no animation, no box-shadow glow, ever (including hover/
   active) — per spec, this one button is the single non-electric
   item in the bar. Identified purely by the logogame.png icon inside
   it, no visible text label. */
.gu-nav-btn-game,
.gu-nav-btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.18);
  animation: none;
}

.gu-nav-btn-game:hover,
.gu-nav-btn-game.active,
.gu-nav-btn-icon:hover,
.gu-nav-btn-icon.active {
  background: rgba(255, 255, 255, 0.1);
  color: inherit;
  animation-play-state: initial;
  box-shadow: none;
}

.gu-nav-btn-game img,
.gu-nav-btn-icon img {
  display: block;
  height: 14px;
  width: auto;
}

@media (max-width: 900px) {
  .gu-nav-btn-game img,
  .gu-nav-btn-icon img { height: 13px; }
}

/* Hover label for the icon-only nav buttons (no visible text label
   otherwise) - laptop only, shown above the button since the nav bar
   itself now sits at the bottom of the screen, so "above" naturally
   means "above the cursor". */
/* Hover tooltip for icon-only nav buttons is now implemented in
   nav-og.js (a single shared element appended to <body>) instead of
   CSS ::after here - #gu-nav has overflow-x:auto for horizontal
   scrolling, which per spec forces overflow-y to clip too, silently
   hiding a ::after tooltip positioned above the button before it
   could ever be seen. See #gu-nav-tooltip below for the actual
   styling. */
#gu-nav-tooltip {
  position: fixed;
  transform: translate(-50%, -100%);
  margin-top: -8px;
  background: #1c1c20;
  color: #39ff8f;
  border: 1px solid rgba(57, 255, 143, 0.4);
  padding: 5px 10px;
  border-radius: 5px;
  font-family: 'Courier New', monospace;
  font-size: 12px;
  letter-spacing: 0.02em;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  z-index: 200;
  transition: opacity 0.15s ease;
}

#gu-nav-tooltip.visible {
  opacity: 1;
}

/* =====================================================================
   GENERIC MODAL SYSTEM — reused for OG Gallery, Timeline, 3 Statements,
   Art and Migration. Same glass-panel language as #panel in style.css.
===================================================================== */
.gu-scrim {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: rgba(6, 6, 8, 0.82);
  backdrop-filter: blur(4px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}

.gu-scrim.open {
  opacity: 1;
  pointer-events: auto;
}

.gu-modal {
  position: fixed;
  top: 4vh;
  left: 50%;
  transform: translate(-50%, -12px);
  width: min(920px, calc(100vw - 24px));
  max-height: 92vh;
  z-index: 91;
  display: flex;
  flex-direction: column;
  background: rgba(14, 14, 17, 0.97);
  border: 1px solid rgba(57, 255, 143, 0.25);
  border-radius: 12px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 0 30px rgba(57, 255, 143, 0.08);
  color: #eee;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

/* Legendary detail popups are true full-screen overlays (not a
   floating card like every other rubrique) - both laptop and mobile,
   per spec: there's a full page of text to read, it gets the whole
   screen, with just the × top-right to get back to the grid.
   Giving it a DEFINITE height (not just max-height) is also the fix
   for a real bug: .leg-modal-scroll-wrap below is flex:1, but a
   flex:1 child only receives actual distributed space when its
   container has a definite height - with .gu-modal's own max-height-
   only sizing, the wrap (whose own child, .gu-modal-body, is
   position:absolute and so contributes nothing to its parent's auto
   height) was collapsing to near 0px, clipping the body down to just
   its first line. */
.gu-modal-legendary {
  top: 0 !important;
  left: 0 !important;
  transform: none !important;
  width: 100vw !important;
  height: 100vh !important;
  height: 100dvh !important;
  max-width: none !important;
  max-height: none !important;
  border-radius: 0 !important;
}

/* FIX: .gu-modal.open (further down) has TWO classes in its selector,
   giving it higher specificity than the single-class
   .gu-modal-legendary above - it was winning and re-applying
   transform: translate(-50%, 0) the instant the popup actually opened
   (that's what makes it visible at all), which combined with left:0
   here shifted the entire full-screen popup half its own width off
   to the left, cutting text off the left edge with the page visible
   through on the right. Matching specificity (two classes) and
   !important as a second, independent safeguard - same lesson as
   every other cascade-order bug already hit on this site. */
.gu-modal-legendary.open {
  transform: none !important;
  left: 0 !important;
  top: 0 !important;
}

/* Legendary thumbnail sitting at the top-left of the popup's text,
   above/beside the description. */
.leg-modal-thumb {
  display: block;
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid rgba(57, 255, 143, 0.3);
  box-shadow: 0 0 16px rgba(57, 255, 143, 0.15);
  margin-bottom: 18px;
}


/* Used only by the Art rubrique, whose on-chain gallery is meant to
   fill most of the screen for visibility, per spec — noticeably wider
   and taller than every other rubrique's modal. */
.gu-modal.wide {
  width: min(1300px, 96vw);
  max-height: 94vh;
  top: 3vh;
}

.gu-modal.open {
  opacity: 1;
  transform: translate(-50%, 0);
  pointer-events: auto;
}

.gu-modal-header {
  flex: none;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 20px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.gu-modal-header h2 {
  margin: 0;
  font-family: 'Courier New', monospace;
  font-size: 19px;
  font-weight: normal;
  color: #39ff8f;
  text-shadow: 0 0 6px rgba(57, 255, 143, 0.5);
  letter-spacing: 0.04em;
}

.gu-modal-header p {
  margin: 4px 0 0;
  font-size: 13.5px;
  color: #888;
  max-width: 60ch;
}

.gu-modal-close {
  flex: none;
  cursor: pointer;
  color: #999;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 6px;
  width: 30px;
  height: 30px;
  font-size: 17px;
  line-height: 1;
}

.gu-modal-close:hover {
  color: #eee;
  background: rgba(255, 255, 255, 0.12);
}

.gu-modal-body {
  overflow-y: auto;
  padding: 22px 24px;
  font-size: 16.5px;
  line-height: 1.8;
  color: #ccc;
}

.gu-modal-body p { margin: 0 0 14px; }
.gu-modal-body p:last-child { margin-bottom: 0; }

.gu-modal-footnote {
  flex: none;
  padding: 10px 20px 16px;
  font-size: 12px;
  color: #666;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  text-align: center;
}

.gu-modal-footnote a { color: #39ff8f; text-decoration: none; }
.gu-modal-footnote a:hover { text-decoration: underline; }

/* GAME rubrique (Punk X Undead) — outbound credits (collections,
   people) kept in plain bold white rather than the site's usual
   green/pink link color, per spec: this is a guest feature, not an
   official GU workstream. Applies both inside .gu-modal-body and
   inside this rubrique's own .gu-modal-footnote (overriding that
   element's default green above). */
.game-credit-link {
  color: #fff;
  font-weight: 700;
  text-decoration: none;
}
.game-credit-link:hover { text-decoration: underline; }

/* Small sub-headings inside the GU Tools rubrique's body, separating
   its two tool listings (GU Analytics / GU Explorer). */
.game-tool-heading {
  color: #39ff8f;
  font-weight: 700;
  margin-bottom: 0 !important;
}
.game-tool-heading-spaced {
  margin-top: 22px !important;
}

/* Creator credit row (small fixed pfp + title), used above each tool
   in GU Tools and above Undead Defenders' own h2. The pfp is a plain
   static image - deliberately NOT pulled live from the creator's X
   profile, so it can never change out from under them. */
.tool-title-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 4px;
}
.tool-title-row-spaced {
  margin-top: 44px;
}
.tool-title-row h2 {
  margin: 0;
}
.tool-pfp {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid rgba(57, 255, 143, 0.35);
  transition: box-shadow 0.2s ease;
}

.tool-title-row a:hover .tool-pfp {
  box-shadow: 0 0 10px rgba(57, 255, 143, 0.6);
}

/* Laptop only: slightly tighter buttons (less horizontal padding,
   marginally smaller text, tighter letter-spacing and gap between
   buttons) so the full row of rubriques fits within a typical laptop
   viewport width without needing horizontal scroll at all - the
   scrollbar itself is now hidden as a fallback too (see #gu-nav
   above), but fitting everything without ever needing to scroll is
   the real fix. */
@media (pointer: fine) {
  #gu-nav {
    gap: 5px;
  }

  .gu-nav-btn {
    padding: 7px 9px;
    font-size: 10.5px;
    letter-spacing: 0.04em;
  }
}

/* Laptop only: creator pfps shown even larger. */
@media (pointer: fine) {
  .tool-pfp {
    width: 60px;
    height: 60px;
  }
}

/* Groups GU Tools / Undead Defenders / Game with Official Links so
   they can sit stacked directly above it on desktop, rather than
   inline to its side in the same row.
   Mobile: display:contents makes this wrapper invisible to layout -
   its children act as if they were direct children of #gu-nav again,
   exactly like before this change, in the same single flat row.
   Desktop: a real flex column - the icons row on top, Official Links
   directly beneath it - which then sits as a single item within the
   main #gu-nav row, in the same horizontal position Official Links
   used to occupy alone. */
.gu-nav-stack {
  display: contents;
}

.gu-nav-stack-icons {
  display: contents;
}

@media (pointer: fine) {
  .gu-nav-stack {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
  }

  .gu-nav-stack-icons {
    display: flex;
    flex-direction: row;
    justify-content: center;
    gap: 6px;
  }
}

/* Centered preview illustration, sitting between a tool's description
   and its link - same treatment reused in GU Tools (both entries) and
   Undead Defenders. Green halo around each one, matching the site's
   neon accent. */
.tool-illustration {
  display: block;
  width: 100%;
  max-width: 340px;
  height: auto;
  margin: 16px auto 0;
  border-radius: 8px;
  border: 1px solid rgba(57, 255, 143, 0.3);
  box-shadow: 0 0 20px rgba(57, 255, 143, 0.35), 0 0 40px rgba(57, 255, 143, 0.15);
}

.tool-link-row {
  margin-top: 10px !important;
  text-align: center;
}

/* -----------------------------------------------------------------
   WHAT IS GENUINE UNDEAD? — styled like a specialized art-press
   feature: kicker + headline, a credited hero image, a lede
   paragraph, section sub-headings, pull-quotes, and a closing video
   section for @ekibtc's chapters.
----------------------------------------------------------------- */
.wig-kicker {
  color: #39ff8f;
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 0 0 6px !important;
}

.wig-hero {
  margin: 0 0 22px;
}

.wig-hero img {
  display: block;
  width: 100%;
  max-height: 420px;
  object-fit: cover;
  border-radius: 10px;
  border: 1px solid rgba(57, 255, 143, 0.25);
  box-shadow: 0 0 24px rgba(57, 255, 143, 0.12);
}

/* V3 features infographic - reference material meant to be read, not
   a decorative photo, so object-fit:contain and no aggressive height
   cap. Centered and reasonably sized on mobile, noticeably larger on
   laptop. */
.wig-v3-features {
  margin: 18px auto 24px;
  text-align: center;
}

.wig-v3-features img {
  width: 100%;
  max-width: 900px;
  height: auto;
  max-height: none;
  object-fit: contain;
  margin: 0 auto;
}

.wig-lightbox-trigger {
  cursor: zoom-in;
}

#gu-lightbox {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 300;
  background: rgba(5, 5, 6, 0.94);
  align-items: center;
  justify-content: center;
  padding: 24px;
}

#gu-lightbox.open {
  display: flex;
}

#gu-lightbox-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 6px;
  box-shadow: 0 0 40px rgba(57, 255, 143, 0.15);
}

#gu-lightbox-close {
  position: fixed;
  top: max(16px, env(safe-area-inset-top));
  right: max(16px, env(safe-area-inset-right));
  width: 36px;
  height: 36px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(57, 255, 143, 0.3);
  color: #eee;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  z-index: 301;
}

@media (pointer: fine) {
  .wig-v3-features img {
    max-width: 1100px;
  }
}

.wig-hero figcaption {
  margin-top: 8px;
  font-size: 12px;
  color: #777;
  text-align: right;
}

.wig-lede {
  font-size: 1.2em;
  line-height: 1.6;
  color: #eee;
  font-weight: 600;
  border-left: 3px solid #39ff8f;
  padding-left: 16px;
  margin-bottom: 22px !important;
}

.wig-pullquote {
  font-size: 1.25em;
  font-style: italic;
  color: #39ff8f;
  text-align: center;
  margin: 30px auto !important;
  max-width: 80%;
  line-height: 1.6;
}

.wig-section-heading {
  color: #fff;
  font-weight: 700;
  font-size: 1.1em;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-bottom: 1px solid rgba(57, 255, 143, 0.25);
  padding-bottom: 8px;
  margin-top: 36px !important;
  margin-bottom: 14px !important;
}

.wig-signoff {
  text-align: center;
  font-weight: 700;
  font-size: 1.1em;
  color: #fff;
  margin-top: 30px !important;
  letter-spacing: 0.03em;
}

/* Marks the start of a genuinely new chapter within the same article
   (as opposed to .wig-section-heading, used for subsections within a
   chapter) - noticeably bigger, with a strong top margin and a
   divider line above it, so it reads as a real break rather than just
   another subsection. */
.wig-chapter-title {
  color: #39ff8f;
  font-weight: 700;
  font-size: 1.6em;
  letter-spacing: 0.02em;
  text-align: center;
  margin-top: 64px !important;
  padding-top: 40px;
  border-top: 1px solid rgba(57, 255, 143, 0.25);
}

/* Per-artist mini sub-heading, one step down from .wig-section-heading
   visually (no underline) since these are shorter individual entries
   within the "From community member to creator" section. */
.wig-artist-heading {
  color: #fff;
  font-weight: 700;
  font-size: 1.05em;
  margin-top: 60px !important;
  margin-bottom: 4px !important;
  clear: both;
}

.wig-artist-figure {
  margin: 10px 0 16px;
}

.wig-artist-figure img {
  display: block;
  width: 100%;
  max-width: 360px;
  max-height: 320px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid rgba(57, 255, 143, 0.2);
}

.wig-artist-figure figcaption {
  margin-top: 6px;
  font-size: 11.5px;
  color: #777;
}

.wig-artist-figure-centered {
  max-width: 480px;
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

/* FIX: centered illustrations were inheriting the base rule's
   object-fit:cover + max-height:320px, which cropped any image taller
   than that ratio instead of showing it in full. Width stays
   constrained to the figure's own max-width; height is left to follow
   naturally from the image's real aspect ratio. */
.wig-artist-figure-centered img {
  max-width: 100%;
  max-height: none;
  height: auto;
  object-fit: contain;
}

/* Markus Manson's two Undeadz illustrations. Mobile (base): stacked in
   a centered column - a cramped side-by-side row didn't leave enough
   room to read either image clearly, and the banner is sized down
   here specifically so it visually matches how LayneTNW's centered
   illustration reads elsewhere in the same article. Desktop switches
   this to a bottom-aligned side-by-side row instead (see the
   pointer:fine override below). */
.wig-undeadz-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  clear: both;
  margin: 16px 0;
}

.wig-undeadz-row .wig-artist-figure {
  float: none;
  margin: 0;
  width: 100%;
  max-width: 300px;
  text-align: center;
}

.wig-undeadz-tk img {
  width: 100%;
  max-width: 300px;
  max-height: 300px;
}

.wig-undeadz-banner img {
  width: 100%;
  max-width: 300px;
  max-height: 100px;
}

/* Laptop only: press-article treatment - the illustration floats
   beside its artist's text instead of sitting full-width above it,
   so paragraphs wrap around it the way a real magazine feature would
   lay out a portrait next to a profile. Mobile keeps the simpler
   full-width stacked version above (image, then text, one column). */
@media (pointer: fine) {
  .wig-artist-figure {
    float: right;
    width: 240px;
    max-width: 240px;
    margin: 4px 0 16px 24px;
  }

  .wig-artist-figure img {
    max-width: 240px;
    max-height: 240px;
  }

  /* Overrides the float above - this one stays centered under its
     text, wider than the usual floated portrait since it's a full
     landscape illustration. */
  .wig-artist-figure-centered {
    float: none;
    width: 100%;
    max-width: 480px;
    margin: 16px auto;
  }

  .wig-artist-figure-centered img {
    max-width: 100%;
    max-height: none;
    height: auto;
    object-fit: contain;
  }

  /* Restores the side-by-side, bottom-aligned layout on desktop -
     mobile (base rules above) stacks these two images in a centered
     column instead. */
  .wig-undeadz-row {
    flex-direction: row;
    align-items: flex-end;
    justify-content: center;
    flex-wrap: wrap;
  }

  .wig-undeadz-row .wig-artist-figure {
    width: auto;
    max-width: 260px;
  }

  .wig-undeadz-tk img {
    width: auto;
    max-height: 200px;
  }

  .wig-undeadz-banner img {
    width: auto;
    max-height: 130px;
  }
}

.wig-videos {
  margin-top: 10px;
}

.wig-video-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 18px;
  margin-top: 16px;
}

.wig-video {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.wig-video-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid rgba(57, 255, 143, 0.25);
  box-shadow: 0 0 16px rgba(57, 255, 143, 0.1);
}

.wig-video-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.wig-video span {
  font-family: 'Courier New', monospace;
  font-size: 12px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #999;
  text-align: center;
}

.wig-video-more {
  align-items: center;
  justify-content: center;
  aspect-ratio: 16 / 9;
  border: 1px dashed rgba(255, 255, 255, 0.2);
  border-radius: 8px;
}

.wig-video-more span {
  color: #666;
  font-style: italic;
  text-transform: none;
}

/* Short neon scroll indicator on the right edge of the article -
   signals there's more to read below without pretending to be an
   exact proportional scrollbar (deliberately short and fixed-length,
   per spec, not sized to content). Uses a nested flex wrapper rather
   than the position:absolute-body trick used for the legendary
   popups: .gu-modal.wide only has a max-height (not a definite
   height), so a flex:1 child whose own content is position:absolute
   would collapse to 0 - already hit and fixed once for the
   legendaries, avoided here entirely by keeping .gu-modal-body a
   normal, still-scrolling flex child and only overlaying the
   indicator on top of it. */
.wig-scroll-wrap {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.wig-scrollbar {
  position: absolute;
  top: 8px;
  bottom: 8px;
  right: 4px;
  width: 3px;
  background: rgba(255, 255, 255, 0.12);
  border-radius: 2px;
  pointer-events: none;
  z-index: 2;
}

.wig-scrollbar-thumb {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 70px;
  background: #39ff8f;
  border-radius: 2px;
  box-shadow: 0 0 6px rgba(57, 255, 143, 0.8);
}

@media (max-width: 640px) {
  .wig-video-grid { grid-template-columns: 1fr; }
  .wig-lede { font-size: 1.08em; }
}

/* The GAME rubrique's footnote credit (@TaoKoltes) needs its own
   override: .gu-modal-footnote a (above) is a class+element selector,
   more specific than the plain .game-credit-link class, so it was
   winning and rendering this link neon green like every other
   footnote link on the site - not what's wanted here. A light gray
   (rather than the white/bold used for this same class elsewhere in
   the rubrique's body text) keeps this specific mention low-key,
   matching the footnote's own muted tone instead of standing out. */
.gu-modal-footnote .game-credit-link {
  color: #999;
  font-weight: 400;
}

/* Game icon shown at the very top of the GAME rubrique's body, before
   any text - a quick visual anchor before reading the description,
   and clickable straight through to the game itself. */
#game-icon-top-link {
  display: block;
  width: 88px;
  margin: 0 auto 18px;
}

#game-icon-top {
  display: block;
  width: 88px;
  height: 88px;
  border-radius: 16px;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

#game-icon-top-link:hover #game-icon-top {
  transform: translateY(-2px);
  box-shadow: 0 0 18px rgba(57, 255, 143, 0.35);
}

/* Clickable arcade-cabinet image at the end of the GAME rubrique,
   linking out to the game itself. Same soft neon-green frame
   treatment already used for .leg-card img elsewhere, so it reads as
   part of the same site rather than a foreign drop-in, even though
   this particular rubrique's text links above don't use the accent
   color. */
#game-arcade-link {
  display: block;
  max-width: 300px;
  margin: 20px auto 0;
}

#game-arcade-link img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
  border: 1px solid rgba(57, 255, 143, 0.25);
  box-shadow: 0 0 18px rgba(57, 255, 143, 0.12);
  transition: box-shadow 0.2s ease, transform 0.2s ease;
}

#game-arcade-link:hover img {
  box-shadow: 0 0 24px rgba(57, 255, 143, 0.4);
  transform: translateY(-2px);
}

/* A more legible variant of the plain footnote — still sits below a
   divider like the others, but readable at a glance rather than
   easy-to-miss muted gray. The email itself is bold (only text on the
   whole site set that way, deliberately, since it's the one thing
   here someone might actually need to act on). */
.og-missing-note {
  color: #aaa;
  font-size: 13px;
}

.og-missing-note strong {
  color: #39ff8f;
  font-weight: bold;
}

.gu-external-btn {
  display: inline-block;
  margin-top: 6px;
  font-family: 'Courier New', monospace;
  font-size: 11px;
  color: #39ff8f;
  border: 1px solid rgba(57, 255, 143, 0.4);
  border-radius: 6px;
  padding: 8px 14px;
  text-decoration: none;
  letter-spacing: 0.04em;
}

.gu-external-btn:hover {
  background: rgba(57, 255, 143, 0.12);
}

/* -----------------------------------------------------------------
   OG GALLERY — two columns: "Community is Utility" text on the left
   (sticky, stays put), avatar grid on the right. The whole body
   scrolls vertically as a unit — the OG list is meant to keep
   growing, so this is a real scroll area, not a fixed-height grid.
   Stacks vertically on narrow screens (see the touch media query).
----------------------------------------------------------------- */
#og-body {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 26px;
  align-items: start;
  padding: 22px 24px;
  overflow-y: auto;
  max-height: calc(92vh - 96px - 40px);
}

#og-intro {
  position: sticky;
  top: 0;
  font-size: 15px;
  line-height: 1.8;
  color: #bbb;
}

#og-intro h3 {
  margin: 0 0 10px;
  font-family: 'Courier New', monospace;
  font-size: 15px;
  letter-spacing: 0.08em;
  color: #39ff8f;
  text-shadow: 0 0 6px rgba(57, 255, 143, 0.4);
}

#og-intro p { margin: 0 0 12px; }
#og-intro p:last-child { margin-bottom: 0; font-style: italic; color: #888; }

#og-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
  gap: 14px;
}

.og-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 12px 6px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  text-decoration: none;
  transition: border-color 0.15s ease, transform 0.15s ease, background 0.15s ease;
}

.og-card:hover {
  border-color: rgba(57, 255, 143, 0.5);
  background: rgba(57, 255, 143, 0.06);
  transform: translateY(-2px);
}

.og-avatar {
  width: 76px;
  height: 76px;
  border-radius: 50%;
  overflow: hidden;
  background: #1c1c20;
  border: 2px solid rgba(57, 255, 143, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.og-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.og-handle {
  font-family: 'Courier New', monospace;
  font-size: 12px;
  color: #eee;
  text-align: center;
  overflow-wrap: anywhere;
}

.og-card:hover .og-handle { color: #39ff8f; }

/* -----------------------------------------------------------------
   8 LEGENDARIES — a simple, even grid of the eight 1-of-1 portraits.
----------------------------------------------------------------- */
.leg-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  margin-top: 18px;
}

.leg-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.leg-card img {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid rgba(57, 255, 143, 0.25);
  box-shadow: 0 0 16px rgba(57, 255, 143, 0.1);
}

.leg-card span {
  font-family: 'Courier New', monospace;
  font-size: 13px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #eee;
}

/* Clickable/tappable - opens the matching legendary's full popup (see
   nav-og.js). The image plays a quick flip (a 2D scaleX squeeze/
   release rather than a true 3D rotateY - deliberately: a real 3D
   flip needing perspective/backface-visibility caused real, hard-to-
   diagnose problems elsewhere on this site, so this stays simple and
   robust) the instant it's tapped, then the popup opens over it. */
.leg-card {
  cursor: pointer;
}

.leg-card img.leg-card-flipping {
  animation: leg-card-flip 0.5s ease;
}

@keyframes leg-card-flip {
  0%   { transform: scaleX(1); }
  50%  { transform: scaleX(0); }
  100% { transform: scaleX(1); }
}

/* "THE EIGHT" introduction text at the top of the 8 Legendaries body. */
.leg-intro-title {
  color: #39ff8f;
  font-weight: 700;
  font-size: 19px;
  letter-spacing: 0.04em;
  margin-bottom: 10px !important;
}

.leg-intro-title-spaced {
  margin-top: 24px !important;
}

.leg-tap-hint {
  color: #888;
  font-size: 13px;
  font-style: italic;
  margin-top: 20px !important;
  margin-bottom: 0 !important;
}

/* Sub-heading introducing the War burn story, inside the War popup -
   same treatment as GU Tools' own sub-headings. */
.leg-story-heading {
  color: #39ff8f;
  font-weight: 700;
  margin-top: 26px !important;
  margin-bottom: 4px !important;
}

/* Legendary detail popups are now full-screen (see .gu-modal-legendary
   near the top of the generic modal system section, right after the
   base .gu-modal rule) - this used to be a smaller floating card,
   superseded per updated spec. */

/* Wraps .gu-modal-body so a custom neon scroll thumb can be
   positioned over it without scrolling away with the content itself
   (a child of the scrolling element would scroll off-screen with the
   text - this sibling approach, sized to fill the same box, doesn't). */
.leg-modal-scroll-wrap {
  position: relative;
  flex: 1;
  min-height: 0;
}

.gu-modal-legendary .gu-modal-body {
  position: absolute;
  inset: 0;
}

/* Mobile only, matching the site's existing scroll-hint language
   (see #art-gallery-scrollbar) - laptop already shows a native
   scrollbar on hover/scroll, no need to duplicate it there. */
.leg-modal-scrollbar {
  display: none;
}

@media (pointer: coarse) {
  .gu-modal-legendary .gu-modal-body {
    right: 10px;
  }

  .leg-modal-scrollbar {
    display: block;
    position: absolute;
    top: 4px;
    bottom: 4px;
    right: 0;
    width: 3px;
    background: rgba(255, 255, 255, 0.14);
    border-radius: 2px;
    overflow: hidden;
  }

  .leg-modal-scrollbar-thumb {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    background: #39ff8f;
    border-radius: 2px;
    box-shadow: 0 0 5px rgba(57, 255, 143, 0.7);
  }
}

/* -----------------------------------------------------------------
   ART GALLERY — a horizontally scrollable strip of curated Genuine
   Undead illustrations (static images, no smart contract connection
   — removed per request; the on-chain reads weren't reliably
   resolving). #art-gallery-wrap holds the neon frame — a single
   traveling-light neon trace via the same .gu-circuit-svg technique
   used elsewhere (the wrap's own border is now a plain neutral line,
   not a second neon); #art-gallery-track is the actual scrolling row, clipped
   separately so the frame's own glow (which extends past its edges)
   never gets cut off by the track's own overflow:hidden.

   Desktop shows ~4 tiles at once with the rest scrollable; mobile
   shows ~2 — same 17 images, sized differently per breakpoint via
   flex-basis.
----------------------------------------------------------------- */
#art-gallery-wrap {
  position: relative;
  margin-top: 22px;
  padding: 10px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  background: transparent;
}

/* -----------------------------------------------------------------
   ART GALLERY SCROLLBAR — same thin track+thumb technique as the
   bottom rubrique nav's own #gu-nav-scrollbar (replacing this
   gallery's previous pulsing arrow-glyph hint, per spec: that
   scrollbar design is the clearer, already-proven way to show a strip
   is scrollable). Sits in normal flow right under the track — no
   fixed positioning/JS-measured rect needed here, since this bar
   lives inside the gallery's own relatively-positioned wrap instead
   of floating over the whole page. Touch only now — laptop uses the
   hover arrows below instead (see the "not coarse pointer" rule
   further down). Width/position of the thumb are computed live in
   updateArtGalleryScrollbar() (nav-og.js).
----------------------------------------------------------------- */
#art-gallery-scrollbar {
  position: relative;
  margin-top: 8px;
  height: 3px;
  background: rgba(255, 255, 255, 0.14);
  border-radius: 2px;
  overflow: hidden;
}

#art-gallery-scrollbar-thumb {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 30%;
  background: #39ff8f;
  border-radius: 2px;
  box-shadow: 0 0 5px rgba(57, 255, 143, 0.7);
}

/* -----------------------------------------------------------------
   ART GALLERY HOVER ARROWS — laptop/mouse only. Sit over the
   leftmost/rightmost tiles, hidden until the pointer is over the
   gallery, and scroll the track on click (see nav-og.js). Replaces
   the scrollbar as the "you can scroll" hint on a fine pointer; the
   scrollbar itself is hidden here and stays touch-only (see the
   pointer:coarse rule below).
----------------------------------------------------------------- */
@media not (pointer: coarse) {
  #art-gallery-scrollbar { display: none; }

  .art-gallery-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 5;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.55);
    border: 1px solid rgba(57, 255, 143, 0.4);
    color: #39ff8f;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease;
  }

  #art-gallery-wrap:hover .art-gallery-arrow {
    opacity: 1;
    pointer-events: auto;
  }

  .art-gallery-arrow:hover { background: rgba(57, 255, 143, 0.18); }
  .art-gallery-arrow-left { left: 18px; }
  .art-gallery-arrow-right { right: 18px; }
}

/* Touch: no hover to reveal the arrows, so hide them entirely and
   keep the scrollbar (already visible by default) as the hint. */
@media (pointer: coarse) {
  .art-gallery-arrow { display: none; }
}

#art-gallery-track {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  border-radius: 6px;
  height: 240px;
}

.art-gallery-item {
  position: relative;
  flex: 0 0 calc(25% - 7.5px);
  scroll-snap-align: start;
  aspect-ratio: 1;
  border-radius: 6px;
  overflow: hidden;
  background: #111;
  border: 1px solid rgba(255, 255, 255, 0.1);
  transition: border-color 0.15s ease, transform 0.15s ease;
}

.art-gallery-item:hover {
  border-color: rgba(255, 255, 255, 0.25);
  transform: translateY(-2px);
}

.art-gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* -----------------------------------------------------------------
   ART CREDIT LINE — the short attribution to @Liquidity_Art's thread,
   between the gallery and the long educational essay below it.
----------------------------------------------------------------- */
.art-credit {
  margin-top: 14px !important;
  font-size: 12px !important;
  color: #888;
  font-style: italic;
}

.art-credit a {
  color: #39ff8f;
  text-decoration: none;
}

.art-credit a:hover {
  text-decoration: underline;
}

/* -----------------------------------------------------------------
   ART ESSAY — the long, fixed-content "anatomy of a collection" text.
   A visually distinct block (subtle top border/spacing) from the
   short intro paragraph and gallery above it, since it's a much
   longer, denser read — headings and lists get their own treatment
   for scannability rather than reading as one undifferentiated wall
   of text.
----------------------------------------------------------------- */
#art-essay {
  margin-top: 28px;
  padding-top: 24px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

#art-essay h2 {
  margin: 0 0 18px;
  font-family: 'Courier New', monospace;
  font-size: 23px;
  color: #39ff8f;
  text-shadow: 0 0 6px rgba(57, 255, 143, 0.4);
  letter-spacing: 0.02em;
}

#art-essay h3 {
  margin: 26px 0 10px;
  font-family: 'Courier New', monospace;
  font-size: 17px;
  color: #eee;
  border-left: 3px solid rgba(57, 255, 143, 0.5);
  padding-left: 10px;
}

#art-essay p {
  margin: 0 0 14px;
  font-size: 16.5px;
  line-height: 1.8;
  color: #bbb;
}

#art-essay ul {
  margin: 0 0 14px;
  padding-left: 0;
  list-style: none;
  columns: 2;
  column-gap: 24px;
}

#art-essay li {
  font-size: 15px;
  line-height: 1.95;
  color: #ccc;
  break-inside: avoid;
}

#art-essay li::before {
  content: "› ";
  color: #39ff8f;
}

/* -----------------------------------------------------------------
   TIMELINE MODAL — vertical scrolling commit-log, reusing the same
   generic modal shell but with its own year/entry markup.
----------------------------------------------------------------- */
#timeline-body { padding: 24px 28px 24px 44px; }

.tl-year {
  font-family: 'Courier New', monospace;
  color: #39ff8f;
  font-size: 21px;
  font-weight: bold;
  margin: 34px 0 16px -24px;
  padding-left: 24px;
  position: relative;
  text-shadow: 0 0 6px rgba(57, 255, 143, 0.4);
}

.tl-year:first-child { margin-top: 0; }

.tl-year::before {
  content: "";
  position: absolute;
  left: 0;
  top: 5px;
  width: 10px;
  height: 10px;
  background: #0d0d10;
  border: 2px solid #39ff8f;
  border-radius: 50%;
}

.timeline-list {
  border-left: 2px solid rgba(57, 255, 143, 0.25);
  padding-left: 20px;
}

.tl-item {
  margin-bottom: 16px;
  position: relative;
}

.tl-item::before {
  content: "";
  position: absolute;
  left: -25px;
  top: 6px;
  width: 6px;
  height: 6px;
  background: #666;
  border-radius: 50%;
}

.tl-item .tl-date {
  display: block;
  color: #39ff8f;
  font-size: 15px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: bold;
  margin-bottom: 5px;
}

.tl-item p {
  margin: 0;
  font-size: 17px;
  line-height: 1.8;
  color: #ccc;
}

.tl-item.new .tl-date { color: #e0479f; }
.tl-item.new::before { background: #e0479f; }

.tl-tag-new {
  display: inline-block;
  font-size: 8px;
  letter-spacing: 0.05em;
  color: #e0479f;
  border: 1px solid #e0479f;
  padding: 1px 5px;
  margin-left: 6px;
  vertical-align: middle;
  border-radius: 3px;
}

/* -----------------------------------------------------------------
   MIGRATION MODAL — larger, easier-to-read prose (this rubrique
   carries the most important explanation on the site), plus a
   contract table and a prominent link to the official migration DAPP.
----------------------------------------------------------------- */
#migration-body {
  font-size: 19px;
  line-height: 1.9;
}

#migration-body strong { font-size: 1em; }

.mig-contracts {
  display: grid;
  gap: 10px;
  margin: 16px 0 20px;
}

.mig-contract {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 8px;
  padding: 12px 16px;
  font-size: 16px;
}

.mig-contract .mig-label {
  color: #39ff8f;
  font-weight: bold;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 13px;
  display: block;
  margin-bottom: 4px;
}

.mig-contract .mig-addr {
  color: #ccc;
  overflow-wrap: anywhere;
}

.mig-contract.legacy { opacity: 0.6; }

.mig-dapp-cta {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin: 22px 0 6px;
  padding: 16px 18px;
  background: rgba(224, 71, 159, 0.08);
  border: 1px solid rgba(224, 71, 159, 0.35);
  border-radius: 10px;
}

.mig-dapp-cta p {
  margin: 0;
  font-size: 15px;
  color: #ddd;
  flex: 1 1 220px;
}

.mig-dapp-btn {
  flex: none;
  display: inline-block;
  font-family: 'Courier New', monospace;
  font-size: 14px;
  font-weight: bold;
  color: #0a0a0c;
  background: #e0479f;
  border-radius: 8px;
  padding: 12px 18px;
  text-decoration: none;
  letter-spacing: 0.03em;
  box-shadow: 0 0 16px rgba(224, 71, 159, 0.5);
}

.mig-dapp-btn:hover { background: #ff5cb0; }

/* -----------------------------------------------------------------
   3 STATEMENTS — horizontal, page-by-page carousel over the three
   original founder image scans, in chronological order. Scroll-snap
   makes each one land square in the viewport; arrows + dots are a
   convenience layered on top (see nav-og.js), not a requirement —
   trackpad/touch swipe or arrow keys work too.
----------------------------------------------------------------- */
@media not (pointer: coarse) {
  /* Laptop only: a taller, wider, near-full-height modal so the
     images read bigger — mobile keeps its own auto-height sizing,
     untouched (specificity here is scoped so it can never leak over
     via the cascade). */
  .gu-modal[data-modal="statements"] {
    width: min(1000px, calc(100vw - 24px));
    height: 95vh;
    max-height: 95vh;
    top: 2.5vh;
  }

  .gu-modal[data-modal="statements"] .statement-page {
    padding: 16px 50px;
  }

  /* Computed, not guessed: 95vh modal minus the header (~81px) and
     the dots footer (~40px) below the track, minus this page's own
     vertical padding (32px) and its image/caption gap + caption line
     (~25px) — so the date caption is always fully in view and never
     requires the scroll the old fixed 76vh value could cause. */
  .gu-modal[data-modal="statements"] .statement-page img {
    max-height: calc(95vh - 185px);
  }
}

#statements-viewport {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

#statements-track {
  flex: 1 1 auto;
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

#statements-track::-webkit-scrollbar { display: none; }

.statement-page {
  flex: 0 0 100%;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 20px 60px;
  box-sizing: border-box;
}

.statement-page img {
  max-width: 100%;
  max-height: 62vh;
  object-fit: contain;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}

.statement-caption {
  font-family: 'Courier New', monospace;
  font-size: 11px;
  color: #888;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.statements-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 5;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.5);
  border: 1px solid rgba(57, 255, 143, 0.4);
  color: #39ff8f;
  font-size: 16px;
  cursor: pointer;
}

.statements-arrow:hover { background: rgba(57, 255, 143, 0.18); }
#statements-prev { left: 12px; }
#statements-next { right: 12px; }

/* Laptop: invisible wrapper — its children (the arrows + the shared
   caption) keep behaving exactly as if they were direct children of
   #statements-viewport, i.e. the arrows stay absolutely centered over
   the image via the rules just above, and the shared caption stays
   hidden (each page's own .statement-caption is what shows there
   instead). Mobile turns this into a real flex row — see the touch
   override further down. */
#statements-footer {
  display: contents;
}

#statements-caption-shared {
  display: none;
}

#statements-dots {
  flex: none;
  display: flex;
  justify-content: center;
  gap: 8px;
  padding: 14px 0 18px;
}

.statement-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.2);
  border: none;
  cursor: pointer;
  padding: 0;
}

.statement-dot.active {
  background: #39ff8f;
  box-shadow: 0 0 8px rgba(57, 255, 143, 0.7);
}

/* -----------------------------------------------------------------
   OFFICIAL LINKS — a categorized resource list: DAPP, marketplaces,
   official accounts, and wallet/contract addresses. Link rows for
   anything with a URL; plain label/value rows for raw addresses.
----------------------------------------------------------------- */
.ol-section { margin-bottom: 26px; }
.ol-section:last-child { margin-bottom: 0; }

.ol-heading {
  margin: 0 0 10px;
  font-family: 'Courier New', monospace;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #39ff8f;
  text-shadow: 0 0 6px rgba(57, 255, 143, 0.4);
  border-bottom: 1px solid rgba(57, 255, 143, 0.2);
  padding-bottom: 6px;
}

.ol-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px;
  margin-bottom: 6px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 8px;
  color: #eee;
  text-decoration: none;
  font-size: 17px;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.ol-link:hover {
  border-color: rgba(57, 255, 143, 0.5);
  background: rgba(57, 255, 143, 0.06);
  color: #39ff8f;
}

.ol-link .ol-arrow {
  flex: none;
  color: #39ff8f;
  font-size: 17px;
}

.ol-item {
  padding: 12px 14px;
  margin-bottom: 6px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 8px;
  font-size: 16px;
}

.ol-item .ol-label {
  display: block;
  color: #888;
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 4px;
}

.ol-item .ol-value {
  color: #ccc;
  font-family: 'Courier New', monospace;
  overflow-wrap: anywhere;
}

/* -----------------------------------------------------------------
   TOPBAR-ROW2 — see the HTML comment right above it in index.html.
   Invisible wrapper by default (laptop): #hud and #search-column keep
   using their own original grid-area rules from style.css, exactly as
   before. Only the touch override further down turns this into a
   real flex row.
----------------------------------------------------------------- */
#topbar-row2 {
  display: contents;
}

/* ===================================================================
   TOUCH DEVICES — nav collapses to a compact bottom bar, logo stays
   top-left but shrinks slightly, modals go near-full-screen and the
   OG two-column layout stacks into one column.
=================================================================== */
@media (pointer: coarse) {
  /* Desktop just got noticeably bigger modal text (see the base rules
     above) — these restore mobile's original, already-tuned sizes for
     every element that didn't already have its own touch override
     (timeline text, further down, already had one). */
  .gu-modal-header h2 { font-size: 15px; }
  .gu-modal-header p { font-size: 11px; }
  .gu-modal-body { font-size: 13px; line-height: 1.75; }
  .gu-modal-footnote { font-size: 10px; }
  .og-missing-note { font-size: 12px; }
  #og-intro { font-size: 12.5px; line-height: 1.75; }
  #og-intro h3 { font-size: 13px; }
  .leg-card span { font-size: 11px; }
  #migration-body { font-size: 15px; }
  .mig-contract { font-size: 11px; }
  .mig-contract .mig-label { font-size: 10px; }
  .mig-dapp-cta p { font-size: 12px; }
  .mig-dapp-btn { font-size: 12px; }
  .ol-link { font-size: 14px; }
  .ol-link .ol-arrow { font-size: 12px; }
  .ol-item { font-size: 13.5px; }
  .ol-item .ol-label { font-size: 10px; }

  #topbar {
    /* padding-top is intentionally NOT !important here anymore — it
       used to be a static calc() guessing the banner's height, marked
       !important, which silently defeated nav-og.js's own JS-computed
       value (an inline style can never beat a stylesheet's
       !important, no matter how accurate that JS measurement is).
       That mismatch is exactly what caused the tagline to overlap the
       banner instead of sitting below it on at least one real device.
       This starting value is just the pre-JS fallback now; the real,
       always-correct value is set live in positionBannerAndNav(). */
    padding-top: 60px;
    padding-left: max(8px, env(safe-area-inset-left)) !important;
    grid-template-columns: 1fr !important;
    grid-template-areas: "tagline" "row2" !important;
  }

  /* Same spot as before: the already-empty top-left area (left) and
     the "live" status badge (right), at the same height as both.
     Re-sized for the new banner's own aspect ratio (much wider/
     shorter than the previous image) so it still fits safely in that
     gap without touching either corner. Width AND height are both
     hardcoded on purpose (never "auto") — a previous version only set
     height and left width to auto-compute from the image's intrinsic
     ratio, which on at least one real device fell back to a much
     larger default box, covering a big chunk of the cube underneath
     instead of the small strip intended. Fixed pixel values remove
     that ambiguity. */
  #gu-banner {
    position: fixed !important;
    top: max(8px, env(safe-area-inset-top)) !important;
    left: 50% !important;
    transform: translateX(-50%) !important;
    right: auto !important;
    width: 200px !important;
    height: 35px !important;
    max-width: 200px !important;
    max-height: 35px !important;
    margin: 0 !important;
    z-index: 60 !important;
    opacity: 0.85;
  }

  /* Framed with the neon trace now, per spec — same rule as laptop's
     rubrique nav band, just applied to this pill instead (mobile has
     no nav band up here; it's a bottom bar). The pill's own
     background/border from style.css is what the neon actually
     traces around. */
  #matrix-tagline {
    /* Undo the laptop base rule's position:fixed/top/left (it pins the
       tagline near the top-left corner, over the banner) so it goes
       back into #topbar's own normal flow — its own grid-area:tagline
       rule (style.css) then places it right under the banner, exactly
       filling the padding-top space nav-og.js computes for it. */
    position: static;
    top: auto;
    left: auto;
    justify-self: center;
    text-align: center;
    white-space: normal;
  }

  /* Same single circuit as laptop, now tracing the tagline pill
     instead of the nav bar — see positionBannerAndNav() in nav-og.js,
     which measures whichever target applies per breakpoint. No
     display:none override here anymore: this shows on both. */

  /* Restructured as a real CSS grid now: "sales 24h" pinned to the
     left edge with the sound toggle pinned to the right edge on that
     same top row; "last sale" on its own row underneath, left-aligned
     with "sales 24h", with the search form (input/arrow/clear) on
     that SAME bottom row, right-aligned directly under the sound
     toggle — per spec.

     #hud itself is set to display:contents below so its two badges
     (injected at runtime by events.js — not static markup, so this
     can't be fixed by simply reordering HTML) become direct grid
     items of THIS container instead, placeable individually by their
     own #sales-24h-badge / #last-sale-badge rules regardless of
     events.js's internal DOM nesting or how often it re-renders them.
     #search-column / #search-cluster get the same display:contents
     treatment so the search form and the sound button — normally
     nested two levels deep inside them — can each be placed into
     their own grid column independently instead of moving as one
     glued-together unit. */
  #topbar-row2 {
    display: grid;
    grid-area: row2;
    grid-template-columns: auto 1fr auto;
    grid-template-rows: auto auto;
    grid-template-areas:
      "sales  .      sound"
      "last   .      search";
    column-gap: 10px;
    row-gap: 6px;
    align-items: center;
    width: 100%;
    overflow: hidden;
  }

  /* Transparent to the grid above — its children (the two badge
     spans) participate directly in #topbar-row2's own grid, as if
     #hud itself didn't exist as a wrapping box. */
  #hud {
    display: contents;
  }

  #hud span {
    white-space: nowrap;
  }

  /* These are legends, not primary actions, so they stay the most
     compact element on this row. */
  #sales-24h-badge,
  #last-sale-badge {
    justify-self: start;
    align-self: start;
    margin-left: 0 !important;
    padding: 4px 6px;
    font-size: 8.5px;
    line-height: 1.1;
    white-space: nowrap;
  }

  #sales-24h-badge { grid-area: sales; }
  #last-sale-badge { grid-area: last; }

  #sales-24h-badge .dot,
  #last-sale-badge .dot {
    width: 6px;
    height: 6px;
  }

  /* Both wrappers dissolve into the grid so the form and the sound
     button — glued together before via #search-cluster's own flex
     row — can be placed independently: the form centered in its own
     column, the sound button pinned to the far right column. */
  #search-column,
  #search-cluster {
    display: contents;
  }

  /* The form (search GU# / arrow / clear) — right-hand column, flush
     to the screen edge (same edge as the sound toggle above it), on
     the same row as "last sale". */
  #search-gu {
    grid-area: search;
    justify-self: end;
    align-self: center;
  }

  /* The sound toggle — its own right-hand column, flush to the
     screen edge, on the "sales 24h" row, directly above the search
     form. */
  #sound-toggle {
    grid-area: sound;
    justify-self: end;
    align-self: center;
  }

  /* Now that the tagline no longer shares this row's vertical budget
     (it moved below — see #topbar's grid-template-areas above), there
     is a bit more room to work with: centered internally and sized up
     slightly for a cleaner, easier-to-hit target. */
  #search-gu {
    padding: 5px 8px;
    gap: 4px;
    height: 30px;
    justify-content: center;
  }

  #search-gu label {
    font-size: 8px;
  }

  #search-gu input {
    width: 38px;
    font-size: 11.5px;
    padding: 0;
    height: 100%;
    text-align: center;
  }

  /* Small enough that the whole row — badges, input, arrow, clear and
     sound — fits on one line without scrolling, and the sound button
     in particular is never pushed off-screen or truncated. */
  #search-gu button,
  #search-gu-reset,
  #sound-toggle {
    width: 27px;
    height: 27px;
    font-size: 12px;
    flex: none;
  }

  /* "live" gets the entire top-right corner to itself, independent of
     the grid, so it's never crowded by anything else in the topbar. */
  #status {
    position: fixed;
    top: max(10px, env(safe-area-inset-top));
    right: max(10px, env(safe-area-inset-right));
    z-index: 30;
    font-size: 10px;
    padding: 5px 8px;
  }

  /* ---------------------------------------------------------------
     COMPACT PHONES — iPhone SE/mini and most compact Android widths
     (roughly ≤399px) genuinely don't fit the sizes above without
     "last sale" and "search GU#" overlapping — this tier shrinks
     everything on row2 further specifically for that width range.
     Tablets (see the tier below) go the other way and get MORE
     breathing room, since the baseline sizes above were tuned as a
     phone-first compromise, not for a tablet's actual available space.
     --------------------------------------------------------------- */
  @media (max-width: 399px) {
    #topbar-row2 { gap: 3px; }
    #hud { gap: 3px; font-size: 7px; }

    #sales-24h-badge,
    #last-sale-badge {
      padding: 3px 4px;
      font-size: 7px;
    }

    #sales-24h-badge .dot,
    #last-sale-badge .dot {
      width: 5px;
      height: 5px;
    }

    #search-cluster { gap: 3px; }

    #search-gu {
      padding: 3px 5px;
      gap: 3px;
      height: 26px;
    }

    #search-gu label { font-size: 7px; }

    #search-gu input {
      width: 28px;
      font-size: 10px;
    }

    #search-gu button,
    #search-gu-reset,
    #sound-toggle {
      width: 23px;
      height: 23px;
      font-size: 10.5px;
    }
  }

  /* ---------------------------------------------------------------
     TABLETS — plenty of real width available (iPad and similar,
     roughly ≥700px in portrait), so row2's controls can be sized up
     for comfort rather than staying at the phone-cramped baseline.
     --------------------------------------------------------------- */
  @media (min-width: 700px) {
    #topbar-row2 { gap: 10px; }
    #hud { gap: 10px; font-size: 12px; }

    #sales-24h-badge,
    #last-sale-badge {
      padding: 6px 12px;
      font-size: 12px;
    }

    #sales-24h-badge .dot,
    #last-sale-badge .dot {
      width: 8px;
      height: 8px;
    }

    #search-cluster { gap: 8px; }

    #search-gu {
      padding: 8px 14px;
      gap: 8px;
      height: 40px;
    }

    #search-gu label { font-size: 12px; }

    #search-gu input {
      width: 54px;
      font-size: 14px;
    }

    #search-gu button,
    #search-gu-reset,
    #sound-toggle {
      width: 36px;
      height: 36px;
      font-size: 15px;
    }
  }

  /* ---------------------------------------------------------------
     BOTTOM STACK, back to front: the code-scroll strip (top), then
     the reset-camera badge, then the rubrique nav bar at the very
     bottom — each in its own clear band so nothing overlaps.
     --------------------------------------------------------------- */
  /* Taller bar per feedback — more comfortable tap targets at the
     very bottom of the screen. The code-scroll/reset-camera offsets
     just below are recalculated to match this new real height. */
  #gu-nav {
    top: auto;
    bottom: max(8px, env(safe-area-inset-bottom));
    left: 50%;
    transform: translateX(-50%);
    max-width: calc(100vw - 16px);
    gap: 6px;
    padding: 8px;
  }

  .gu-nav-btn {
    font-size: 10px;
    padding: 10px 11px;
  }

  /* Replaced the small arrow glyph (per feedback: not a clear enough
     hint) with a thin scrollbar-style indicator instead — a track
     with a highlighted thumb showing both that it scrolls AND how
     much more there is, updated live in nav-og.js on every scroll. A
     mini scrollbar is about as unambiguous as a "there's more"
     affordance gets. It's a separate fixed element (not a child of
     #gu-nav itself) positioned to match the nav's own rect in JS —
     being a child of the scrolling nav would have it scroll away
     with the content instead of staying put as a fixed reference. */
  #gu-nav-scrollbar {
    display: block;
    position: fixed;
    height: 3px;
    background: rgba(255, 255, 255, 0.14);
    border-radius: 2px;
    overflow: hidden;
    z-index: 75;
    pointer-events: none;
  }

  #gu-nav-scrollbar-thumb {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    width: 30%;
    background: #39ff8f;
    border-radius: 2px;
    box-shadow: 0 0 5px rgba(57, 255, 143, 0.7);
  }

  /* Full width, 3 lines, sitting a bit further above the (now taller)
     nav bar than before — never hidden underneath it. */
  #code-scroll {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(66px + max(8px, env(safe-area-inset-bottom)));
    top: auto;
    width: 100%;
    height: 42px;
    padding: 0 14px;
    flex: none;
    background: linear-gradient(0deg, rgba(0,0,0,0.55), rgba(0,0,0,0.15));
    -webkit-mask-image: none;
    mask-image: none;
  }

  #code-scroll .code-scroll-line {
    font-size: 9px;
    line-height: 1.4em;
  }

  /* Floats in its own band above the code-scroll strip and the (now
     taller) nav bar, so it never collides with either. */
  #reset-camera {
    position: fixed;
    bottom: calc(66px + 42px + 6px + max(8px, env(safe-area-inset-bottom)));
    right: max(8px, env(safe-area-inset-right));
    z-index: 30;
    flex-direction: row;
    gap: 6px;
    padding: 6px 8px;
  }

  #reset-camera .reset-label {
    font-size: 8px;
    /* Default order (0) — renders first, so the whole "reset position"
       label always comes before the logo below, explicit rather than
       relying on flex-direction:row-reverse to flip it. */
    order: 1;
  }

  #reset-camera .reset-square {
    width: 10px;
    height: 10px;
    /* Placed after the label — the logo now sits past the word
       "position", never in front of "reset". */
    order: 2;
  }

  .gu-modal {
    top: 3vh;
    max-height: 94vh;
    width: calc(100vw - 16px);
  }

  /* 2 tiles visible at once instead of 4, the rest still scrollable
     to the right — same fetched set, just sized for a phone screen. */
  #art-gallery-track {
    height: 150px;
  }

  .art-gallery-item {
    flex-basis: calc(50% - 5px);
  }

  #art-essay h2 { font-size: 17px; }
  #art-essay h3 { font-size: 13.5px; margin: 22px 0 8px; }
  #art-essay p { font-size: 15px; line-height: 1.75; }
  #art-essay ul { columns: 1; }
  #art-essay li { font-size: 14px; }

  #og-body {
    grid-template-columns: 1fr;
    padding: 18px;
    max-height: calc(94vh - 90px - 34px);
  }

  #og-intro { position: static; }

  .leg-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  /* A taller modal just for this rubrique so the enlarged image below
     always has room, without touching every other rubrique's modal
     sizing on mobile. */
  .gu-modal[data-modal="statements"] {
    max-height: 96vh;
    top: 2vh;
  }

  /* True edge to edge, per spec — zero horizontal padding, and the
     image's own border is the green frame (touching the image
     directly, no gap), rather than a separate wrapping frame with
     margin around it. The old 40vh cap was well under what a
     full-width square image actually needs, which is what forced it
     to shrink and left black bars on both sides; 75vh is comfortably
     above that on every real phone/tablet size while still leaving
     guaranteed room below for #statements-footer and the dots — the
     previous version guessed a fixed pixel position for the
     caption/arrows instead, which overlapped the image once it
     rendered taller than assumed on a real device. */
  .statement-page {
    padding: 6px 0 0;
    justify-content: flex-start;
  }
  .statement-page img {
    width: 100%;
    max-width: 100%;
    max-height: 75vh;
    border-radius: 0;
    border: 1px solid #39ff8f;
    box-shadow: 0 0 10px rgba(57, 255, 143, 0.35);
  }

  /* Each page's own caption is hidden on mobile — the shared one in
     #statements-footer (kept in sync by initStatementsCarousel() in
     nav-og.js) is what shows here instead, so it can sit on the same
     line as the arrows regardless of which page is scrolled to. */
  .statement-page > .statement-caption {
    display: none;
  }

  /* The date caption keeps its own centered line above the dots —
     the arrows below no longer live in this flex row (see the
     .statements-arrow rule further down: they now overlay near the
     dots' own row instead, per spec — "same line as the dots", not
     the date). */
  #statements-footer {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    padding: 10px 14px 2px;
  }

  #statements-caption-shared {
    display: block;
    text-align: center;
  }

  /* Moved to overlay near #statements-dots' own row instead of
     sitting in #statements-footer with the date text — "on the same
     line as the dots", per spec. #statements-viewport's position:
     relative is turned off for this breakpoint (see its own rule)
     specifically so these bubble up to use the modal itself as their
     containing block, since the dots live outside the viewport as its
     sibling; bottom:5px lines their center up with the dots' own row
     (18px bottom padding + half its 8px dot height ≈ 22px from the
     modal's bottom edge — a 34px arrow centers there at bottom:5px). */
  .statements-arrow {
    position: absolute;
    top: auto;
    bottom: 5px;
    transform: none;
    width: 34px;
    height: 34px;
    font-size: 15px;
    z-index: 6;
  }

  #statements-viewport {
    position: static;
  }

  #og-grid {
    grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
    gap: 10px;
  }

  .og-avatar { width: 62px; height: 62px; }
  .og-handle { font-size: 9px; }

  #timeline-body { padding: 18px 16px 18px 32px; }
  .tl-year { font-size: 17px; }
  .tl-item .tl-date { font-size: 13px; }
  .tl-item p { font-size: 15px; line-height: 1.7; }
}

/* Respect reduced-motion preference: kill the pulsing, keep the rest */
@media (prefers-reduced-motion: reduce) {
  .gu-nav-btn { animation: none; }
}

/* ===================================================================
   STRICT PORTRAIT LOCK (mobile/tablet only) — if the device physically
   rotates to landscape, the page is rotated back to portrait via CSS
   transform rather than showing any "please rotate" message, per
   spec: from the visitor's point of view, nothing happens and the
   site simply stays in portrait no matter which way the device is
   held. This is the standard CSS-only orientation-lock technique
   (there's no real cross-browser Screen Orientation lock available to
   a plain website — iOS Safari in particular never implemented it),
   scoped to touch devices only so it never affects a desktop browser
   window that happens to be wide.

   Known trade-off, worth testing for real: this only rotates what's
   drawn on screen — touch coordinates are still reported by the OS in
   the device's own (unrotated) landscape space, so precise drag
   interactions (the cube itself) may track slightly off from where a
   finger visually appears to be while the phone is actually held in
   landscape. Everything else (taps, scrolling, the rubrique menus,
   reading content) is unaffected.
=================================================================== */
@media (pointer: coarse) and (orientation: landscape) {
  html {
    transform: rotate(-90deg);
    transform-origin: left top;
    width: 100vh;
    height: 100vw;
    overflow-x: hidden;
    position: absolute;
    top: 100%;
    left: 0;
  }
}
