/* ============================================================
   gv-world.css — the shared half of every game world.

   Each game ships a <game>/world.css that declares the world's tokens
   (--w-*) for both themes, maps the game's own --xx-<role> tokens onto
   them, and adds whatever that world needs by hand. This file is the part
   they all share: the page backdrop, the game card, the marquee line, the
   mode pills, the board rim, the cells, the readouts, the buttons and the
   sound toggle — all drawn purely from the tokens, so one stylesheet gives
   sixteen different rooms.

   Layout is never touched here: no padding, sizing or display changes on
   anything a game lays out itself. Selectors are prefixed with :root so
   they outrank the site's own `:root[data-theme="light"] .x` rules.

   Token contract (declared per world, per theme):
     --w-font                          display face
     --w-bg-0 .. --w-bg-3              surfaces, deepest to highest
     --w-edge-hi, --w-edge-lo          bevel light and shade
     --w-ink, --w-dim                  text
     --w-accent, --w-accent-hi, --w-accent-lo, --w-accent-ink, --w-accent-text
     --w-fill-tint                     lighter stop for filled gradients
     --w-good, --w-good-text, --w-bad, --w-bad-text, --w-warn, --w-warn-text
     --w-blue, --w-violet              secondary hues for the game's own roles
     --w-line, --w-line-soft, --w-glow, --w-glow-soft, --w-drop, --w-glow-scale
     --w-cell, --w-cell-hi, --w-cell-lo, --w-cell-br, --w-cell-ink, --w-hole
     --w-texture                       optional background-image layers on the card
     --w-page                          page backdrop
     --w-radius                        card corner
   ============================================================ */

:root body.gv-world{
  color: var(--w-ink);
  background: var(--w-page, var(--bg));
}

/* Page backdrop: one soft bloom of the world's accent, no grid, no aurora. */
:root body.gv-world::before{
  content: "";
  position: fixed; inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(70vmax 40vmax at 50% -10%, var(--w-glow-soft), transparent 70%);
}

/* ---------- the game card ---------- */
:root body.gv-world main > .card:first-of-type{
  position: relative;
  isolation: isolate;
  border: 1px solid var(--w-line);
  border-radius: var(--w-radius, 20px);
  color: var(--w-ink);
  background:
    var(--w-texture, none),
    linear-gradient(180deg, var(--w-bg-2), var(--w-bg-1) 45%, var(--w-bg-0));
  box-shadow:
    inset 0 1px 0 var(--w-edge-hi),
    inset 0 0 0 1px rgba(0,0,0,.18),
    0 24px 60px var(--w-drop);
  backdrop-filter: none;
}
:root body.gv-world main > .card:first-of-type::before{
  content: "";
  position: absolute; inset: 7px;
  border-radius: calc(var(--w-radius, 20px) - 6px);
  border: 1px solid var(--w-line-soft);
  pointer-events: none;
  z-index: 0;
}
:root body.gv-world main > .card:first-of-type > *{ position: relative; z-index: 1; }
:root body.gv-world main > .card:first-of-type .muted{ color: var(--w-dim); }
:root body.gv-world main > .card:first-of-type .mono{ color: var(--w-ink); }

/* Other cards on the page (SEO, FAQ) keep the site look but pick up the
   world's line colour so the page reads as one. */
:root body.gv-world main > .card:not(:first-of-type){ border-color: var(--w-line-soft); }

/* ---------- marquee line under the title ---------- */
:root body.gv-world header p.sub{
  display: block;
  margin-top: 10px;
  padding: 10px 14px;
  border-radius: 14px;
  border: 1px solid var(--w-line);
  background: linear-gradient(180deg, var(--w-bg-3), var(--w-bg-2));
  color: var(--w-ink);
  box-shadow: inset 0 1px 0 var(--w-edge-hi), 0 10px 26px var(--w-drop);
}

/* ---------- mode pills ---------- */
:root body.gv-world .seg{
  font-family: var(--w-font);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  font-size: 12px;
  border: 1px solid var(--w-line);
  background: var(--w-bg-0);
  color: var(--w-dim);
  box-shadow: inset 0 1px 0 var(--w-edge-hi);
  transition: background .15s, color .15s, transform .12s;
}
:root body.gv-world .seg:hover{ color: var(--w-ink); border-color: var(--w-accent); background: var(--w-bg-2); }
:root body.gv-world .seg.on,
:root body.gv-world .seg[aria-pressed="true"],
:root body.gv-world .seg[aria-selected="true"]{
  background: linear-gradient(180deg, var(--w-fill-a), var(--w-fill-b));
  color: var(--w-accent-ink);
  border-color: var(--w-accent-lo);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.35), 0 0 16px var(--w-glow-soft);
}

/* ---------- board ---------- */
:root body.gv-world .gvArcBoard{
  position: relative;
  border-radius: 14px;
  box-shadow:
    inset 0 0 40px var(--w-glow-soft),
    0 16px 40px var(--w-drop);
}
:root body.gv-world .gvArcBoard::after{
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: inset 0 1px 0 var(--w-edge-hi), inset 0 0 0 1px var(--w-line);
}
:root body.gv-world .gvArcBoard.gvArcEnter{ animation: gvWorldBoardIn .52s cubic-bezier(.2,.8,.25,1) both; }
@keyframes gvWorldBoardIn{ 0%{ opacity: 0; transform: translateY(10px) scale(.985); } 100%{ opacity: 1; transform: none; } }

/* ---------- cells ---------- */
/* Transition, hover, press. Background and border stay with the game's own
   tokens (which the world file has re-pointed), because a cell's colour
   carries state. */
:root body.gv-world .gvArcCell{
  transition:
    transform .16s cubic-bezier(.34,1.56,.64,1),
    box-shadow .22s ease,
    border-color .2s ease,
    background .16s ease,
    filter .2s ease;
}
:root body.gv-world .gvArcCell:hover:not([disabled]){
  border-color: var(--w-accent);
  box-shadow: 0 0 14px var(--w-glow-soft);
}
:root body.gv-world .gvArcCell:active:not([disabled]){ transform: scale(.94); }
/* A cell that the win choreography has lit. */
:root body.gv-world .gvArcCell.gvWorldLit{
  filter: brightness(1.35) saturate(1.2);
  box-shadow: 0 0 calc(14px * var(--w-glow-scale, 1)) var(--w-glow);
}
/* Nonogram's own effect classes, so its hand-built integration still fires. */
@keyframes gvWorldPop{ 0%{ transform: scale(.86) } 55%{ transform: scale(1.08) } 100%{ transform: scale(1) } }
@keyframes gvWorldZap{ 0%,100%{ filter: brightness(1) } 40%{ filter: brightness(2); box-shadow: 0 0 16px var(--w-glow) } }
:root body.gv-world .nxPop{ animation: gvWorldPop .36s cubic-bezier(.34,1.56,.64,1); }
:root body.gv-world .nxZap{ animation: gvWorldZap .48s ease; }
:root body.gv-world .nxLineHit{ color: var(--w-accent-text) !important; animation: gvWorldPop .6s ease; }

/* ---------- readouts ---------- */
:root body.gv-world .gvArcVal{
  font-family: var(--w-font);
  font-weight: 700;
  color: var(--w-accent-text);
  text-shadow: 0 0 calc(14px * var(--w-glow-scale, 1)) var(--w-glow-soft);
  transition: color .25s ease, text-shadow .25s ease;
}

/* ---------- buttons inside the game card ---------- */
:root body.gv-world main > .card:first-of-type button:not(.seg):not(.gvArcCell):not(.iconbtn):not(.nxSound):not([class*='Cell']):not([class*='Tile']):not([class*='Hex']):not([class*='Key']){
  font-family: var(--w-font);
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--w-ink);
  border: 1px solid rgba(0,0,0,.3);
  background: linear-gradient(180deg, var(--w-bg-3), var(--w-bg-1) 55%, var(--w-bg-0));
  box-shadow: inset 0 2px 0 var(--w-edge-hi), inset 0 -3px 0 var(--w-edge-lo), 0 4px 10px var(--w-drop);
  text-shadow: none;
  transition: transform .08s ease, box-shadow .08s ease, filter .15s;
}
:root body.gv-world main > .card:first-of-type button:not(.seg):not(.gvArcCell):not(.iconbtn):not(.nxSound):not([class*='Cell']):not([class*='Tile']):not([class*='Hex']):not([class*='Key']):hover{ filter: brightness(1.08); }
:root body.gv-world main > .card:first-of-type button:not(.seg):not(.gvArcCell):not(.iconbtn):not(.nxSound):not([class*='Cell']):not([class*='Tile']):not([class*='Hex']):not([class*='Key']):active{
  transform: translateY(3px);
  box-shadow: inset 0 2px 4px rgba(0,0,0,.45);
}
/* Primary actions: the world's accent as a fill. */
:root body.gv-world main > .card:first-of-type button.primary,
:root body.gv-world main > .card:first-of-type button[class*='Primary'],
:root body.gv-world main > .card:first-of-type button[class*='primary'],
:root body.gv-world main > .card:first-of-type button[class*='Check'],
:root body.gv-world main > .card:first-of-type button[class*='Submit'],
:root body.gv-world main > .card:first-of-type button[id*='check'],
:root body.gv-world main > .card:first-of-type button[id*='submit'],
:root body.gv-world main > .card:first-of-type button[id*='Check'],
:root body.gv-world main > .card:first-of-type button[id*='Submit']{
  color: var(--w-accent-ink);
  border-color: var(--w-accent-lo);
  background: linear-gradient(180deg, var(--w-fill-a), var(--w-fill-b));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.4), inset 0 -3px 0 rgba(0,0,0,.2), 0 6px 14px var(--w-drop);
}

/* Links dressed as buttons inside the card (some games use <a class="...Btn">). */
:root body.gv-world main > .card:first-of-type a[class*='Btn'],
:root body.gv-world main > .card:first-of-type a.seg{
  font-family: var(--w-font);
  font-weight: 700;
  text-decoration: none;
  color: var(--w-ink);
}
:root body.gv-world main > .card:first-of-type a[class*='Btn'].primary,
:root body.gv-world main > .card:first-of-type a[class*='Btn'][class*='rimary']{
  color: var(--w-accent-ink);
  background: linear-gradient(180deg, var(--w-fill-a), var(--w-fill-b));
  border: 1px solid var(--w-accent-lo);
}

/* CTA links in the hub strip below the card (Play Daily / Quick Play). */
:root body.gv-world main a[class*='CtaBtn']{
  font-family: var(--w-font);
  font-weight: 700;
  text-decoration: none;
  color: var(--w-accent-text);
  border: 1px solid var(--w-line);
  background: var(--w-bg-2);
}
:root body.gv-world main a[class*='CtaBtn'].primary{
  color: var(--w-accent-ink);
  background: linear-gradient(180deg, var(--w-fill-a), var(--w-fill-b));
  border-color: var(--w-accent-lo);
}

/* ---------- the world's nameplate ---------- */
/* A small plate on the top edge of the game card. It is the element right
   before the card, pulled down over the card's border. */
:root body.gv-world .gvWorldTag{
  position: relative;
  z-index: 3;
  display: block;
  width: fit-content;
  margin: 0 14px -14px auto;
  font-family: var(--w-font);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  padding: 5px 12px 4px;
  border-radius: 999px;
  color: var(--w-accent-ink);
  background: linear-gradient(180deg, var(--w-fill-a), var(--w-fill-b));
  border: 1px solid var(--w-accent-lo);
  box-shadow: 0 4px 12px var(--w-drop), 0 0 14px var(--w-glow-soft);
  pointer-events: none;
  white-space: nowrap;
}
@media (max-width: 520px){ :root body.gv-world .gvWorldTag{ margin-right: 10px; font-size: 10px; } }

/* ---------- sound toggle, chain badge, banner ---------- */
:root body.gv-world .nxSound{
  width: 36px; height: 36px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--w-line);
  background: var(--w-bg-0);
  color: var(--w-accent-text);
  font-size: 15px; line-height: 1; cursor: pointer;
  box-shadow: inset 0 1px 0 var(--w-edge-hi);
  transition: transform .12s ease, opacity .2s;
}
:root body.gv-world .nxSound:active{ transform: scale(.93); }
:root body.gv-world .nxSound[aria-pressed="false"]{ opacity: .6; }
:root body.gv-world nav .nxSound{ position: static; }

:root body.gv-world .nxCombo{
  display: inline-flex; align-items: baseline; gap: 8px;
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid var(--w-accent-lo);
  background: linear-gradient(180deg, var(--w-fill-a), var(--w-fill-b));
  color: var(--w-accent-ink);
  font: 700 14px/1 var(--w-font);
  box-shadow: 0 0 18px var(--w-glow-soft);
  opacity: 0; transform: translateY(6px) scale(.9);
  transition: opacity .2s ease, transform .28s cubic-bezier(.34,1.56,.64,1);
  pointer-events: none;
}
:root body.gv-world .nxCombo.on{ opacity: 1; transform: none; }
:root body.gv-world .nxComboX{ font-size: 18px; }

:root body.gv-world .nxBanner{
  position: fixed; left: 50%; top: 34%;
  transform: translate(-50%, 0);
  z-index: 10058;
  pointer-events: none;
  text-align: center;
  font: 700 clamp(24px, 7vw, 40px)/1.1 var(--w-font);
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 12px 28px 10px;
  border-radius: 18px;
  color: var(--w-accent-ink);
  background: linear-gradient(180deg, var(--w-fill-a), var(--w-fill-b));
  border: 2px solid var(--w-accent-lo);
  box-shadow: 0 0 40px var(--w-glow), 0 20px 50px rgba(0,0,0,.4);
  opacity: 0;
}
@keyframes gvWorldBannerIn{
  0%   { opacity: 0; transform: translate(-50%, 20px) scale(.8); }
  18%  { opacity: 1; transform: translate(-50%, 0) scale(1.06); }
  30%  { transform: translate(-50%, 0) scale(1); }
  80%  { opacity: 1; transform: translate(-50%, 0) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -24px) scale(.96); }
}
:root body.gv-world .nxBanner.on{ animation: gvWorldBannerIn 1.5s cubic-bezier(.2,.8,.25,1) forwards; }

/* ---------- the win ---------- */
:root body.gv-world main > .card:first-of-type.gvWorldWon{
  border-color: var(--w-accent);
  box-shadow: inset 0 1px 0 var(--w-edge-hi), 0 0 40px var(--w-glow-soft), 0 24px 60px var(--w-drop);
}

@media (prefers-reduced-motion: reduce){
  :root body.gv-world .gvArcCell{ transition: none; }
  :root body.gv-world .gvArcBoard.gvArcEnter,
  :root body.gv-world .nxPop, :root body.gv-world .nxZap, :root body.gv-world .nxLineHit{ animation: none; }
  :root body.gv-world .nxBanner.on{ animation-duration: .01ms; }
}

/* ---------- ambient life ---------- */
/* One fixed canvas behind everything (gv-world.js draws the world's
   particles on it). z-index -1 keeps it under the page's own content and
   above the body backdrop. */
:root body .gvWorldAmbient{
  position: fixed; inset: 0; width: 100%; height: 100%;
  z-index: -1; pointer-events: none;
  opacity: .85;
}
:root[data-theme="light"] body .gvWorldAmbient{ opacity: .55; }

/* ---------- entrance ---------- */
/* The room lights up: the marquee line, then the card, then the nameplate
   stamps on. Cheap, once per load, off for reduced motion. */
:root body.gv-world header p.sub{ animation: gvWorldSubIn .5s cubic-bezier(.2,.8,.25,1) both; }
:root body.gv-world main > .card:first-of-type{ animation: gvWorldCardIn .6s .08s cubic-bezier(.2,.8,.25,1) both; }
:root body.gv-world .gvWorldTag{ animation: gvWorldStamp .55s .38s cubic-bezier(.2,.9,.3,1.4) both; }
@keyframes gvWorldSubIn{ 0%{ opacity: 0; transform: translateY(-6px) } 100%{ opacity: 1; transform: none } }
@keyframes gvWorldCardIn{ 0%{ opacity: 0; transform: translateY(14px) scale(.985) } 100%{ opacity: 1; transform: none } }
@keyframes gvWorldStamp{ 0%{ opacity: 0; transform: scale(1.6) rotate(-6deg) } 100%{ opacity: 1; transform: none } }

/* ---------- the post-win dialog wears the world ---------- */
:root body.gv-world .gvWinDlg::backdrop{ background: rgba(0,0,0,.6); backdrop-filter: blur(3px); }
:root body.gv-world .gvWin{
  background: linear-gradient(180deg, var(--w-bg-2), var(--w-bg-1) 60%, var(--w-bg-0));
  color: var(--w-ink);
  border: 1px solid var(--w-line);
  box-shadow: inset 0 1px 0 var(--w-edge-hi), 0 30px 70px rgba(0,0,0,.5), 0 0 40px var(--w-glow-soft);
  animation: gvWorldDlgIn .45s cubic-bezier(.2,.9,.3,1.25) both;
}
@keyframes gvWorldDlgIn{ 0%{ opacity: 0; transform: translateY(18px) scale(.94) } 100%{ opacity: 1; transform: none } }
:root body.gv-world .gvWin h2{ font-family: var(--w-font); font-weight: 700; letter-spacing: .02em; font-size: 26px; color: var(--w-accent-text); text-shadow: 0 0 calc(18px * var(--w-glow-scale,1)) var(--w-glow-soft); }
:root body.gv-world .gvWin .gvWinSub{ color: var(--w-dim); opacity: 1; }
:root body.gv-world .gvWinNext{ background: var(--w-bg-0); border-color: var(--w-line); box-shadow: inset 0 1px 0 var(--w-edge-hi); }
:root body.gv-world .gvWinNext:hover{ background: var(--w-bg-3); border-color: var(--w-accent); }
:root body.gv-world .gvWinNextName{ font-family: var(--w-font); font-weight: 700; letter-spacing: .03em; color: var(--w-ink); }
:root body.gv-world .gvWinNextDesc{ color: var(--w-dim); opacity: 1; }
:root body.gv-world .gvWinNextArr{ color: var(--w-accent-text); opacity: 1; }
:root body.gv-world .gvWinRow .gvWinBtn{
  font-family: var(--w-font); font-weight: 700; letter-spacing: .04em;
  background: var(--w-bg-0); color: var(--w-ink); border-color: var(--w-line);
  box-shadow: inset 0 1px 0 var(--w-edge-hi);
}
:root body.gv-world .gvWinRow .gvWinBtn:hover{ border-color: var(--w-accent); filter: none; background: var(--w-bg-3); }
:root body.gv-world .gvWinRow .gvWinBtn.primary{
  background: linear-gradient(180deg, var(--w-fill-a), var(--w-fill-b)); color: var(--w-accent-ink); border-color: var(--w-accent-lo);
}
:root body.gv-world .gvWin .gvWinX{ color: var(--w-dim); }
:root body.gv-world .gvWin .gvWinSmall{ color: var(--w-dim); }
:root body.gv-world .gvWinRemind{ border-color: var(--w-line); }

@media (prefers-reduced-motion: reduce){
  :root body.gv-world header p.sub, :root body.gv-world main > .card:first-of-type, :root body.gv-world .gvWorldTag, :root body.gv-world .gvWin{ animation: none; }
}
