/* ==========================================================================
   VARIANTS — loaded AFTER style.css by v1–v4
   Pure black ground + the image-panel vocabulary the variants are built from.
   style.css stays the base system; nothing here is variant-specific enough
   to need its own file.
   ========================================================================== */

/* ---------- 1. BLACK GROUND ---------------------------------------------- */
:root{
  --bg:        #000;
  --bg-2:      #000;
  /* Slightly lifted off pure black so empty placeholder slots stay readable
     while you are still exporting. Real artwork covers these entirely. */
  --surface:   #0d0d11;
  --surface-2: #16161c;
  --line:      #2a2a33;
  --line-soft: #141417;
  --gutter:    clamp(6px, .7vw, 12px);   /* dense gallery gutter */
}

/* ---------- 2. ASPECT HELPERS -------------------------------------------- */
/* Put these straight on .frame — they set the slot, the image fills it.
   Written as .frame.ar-* so they outrank contextual rules like
   `.shot .frame { aspect-ratio:16/9 }` in style.css.                       */
.frame.ar-219 { aspect-ratio: 21/9  }
.frame.ar-169 { aspect-ratio: 16/9  }
.frame.ar-32  { aspect-ratio: 3/2   }
.frame.ar-11  { aspect-ratio: 1     }
.frame.ar-45  { aspect-ratio: 4/5   }
.frame.ar-34  { aspect-ratio: 3/4   }
.frame.ar-916 { aspect-ratio: 9/16  }

/* Cut-out art (character PNGs, logos) inside a slot that is not its own
   ratio — letterbox it instead of cropping it. */
.frame--contain img{ object-fit:contain }

/* ---------- 2b. PORTRAIT STRIP ------------------------------------------- */
/* The game is authored 9:16. Three or four native captures set side by side
   become one horizontal band, which is how portrait art earns a full-width
   dark page. Child count drives the column count — no per-count classes.
   Give each child `.frame.ar-916` (or any ar-* if you want a mixed row).   */
.strip{ display:grid; grid-auto-flow:column; grid-auto-columns:minmax(0,1fr) }
@media (max-width:760px){
  .strip{ grid-auto-flow:row; grid-template-columns:repeat(2, minmax(0,1fr)) }

  /* Two columns and an odd number of captures leaves the last one alone in
     the left column with a hole beside it, which reads as a missing image
     rather than a deliberate end. Centre the orphan at the same width as its
     siblings instead. TR is the first game to hit this — it has three win
     tiers rather than four — and the selector only fires on an odd count, so
     every four-tier ladder is untouched. */
  .strip > :last-child:nth-child(odd){
    grid-column:1 / -1;
    max-width:calc(50% - var(--gap) / 2);
    margin-inline:auto;
  }
}

/* ---------- 3. RUNS ------------------------------------------------------- */
/* A vertical sequence of panels. --tight is the gallery pacing.             */
.run        { display:grid; gap:var(--gap) }
.run--tight { gap:var(--gutter) }
.pad        { padding-inline:var(--pad) }
.pad--thin  { padding-inline:var(--gutter) }
.run + .run { margin-top:var(--gap) }
.run--tight + .run--tight{ margin-top:var(--gutter) }

/* Vertical breathing for panel-only pages — much tighter than .section,
   because the panels themselves carry the rhythm. */
.band       { padding-block:clamp(40px, 5vw, 96px) }
.band--tight{ padding-block:clamp(20px, 2.4vw, 44px) }

/* ---------- 4. FULL-BLEED BAND ------------------------------------------- */
.bleed .frame{ border-radius:0 }

/* ---------- 5. DUO / TRIO ------------------------------------------------- */
/* These carry no gap of their own — always pair them with .run / .run--tight,
   which owns the spacing. Declaring gap here would beat .run on source order. */
.duo { display:grid; grid-template-columns:minmax(0,1fr) }
.trio{ display:grid; grid-template-columns:minmax(0,1fr) }
@media (min-width:820px){ .duo { grid-template-columns:repeat(2, minmax(0,1fr)) } }
@media (min-width:700px){ .trio{ grid-template-columns:repeat(3, minmax(0,1fr)) } }

/* Off-balance duo — one panel taller than the other, for v3/v4 */
@media (min-width:820px){
  .duo--offset > :last-child { margin-top:clamp(28px, 5vw, 90px) }
}

/* ---------- 6. MOSAIC ----------------------------------------------------- */
/* 4-column bed; children span with .m-2 / .m-3 / .m-4.                      */
.mosaic{ display:grid; grid-template-columns:repeat(2, minmax(0,1fr)) }
@media (min-width:900px){ .mosaic{ grid-template-columns:repeat(4, minmax(0,1fr)) } }
.mosaic > *{ min-width:0 }
.m-2{ grid-column:span 2 }
@media (min-width:900px){ .m-3{ grid-column:span 3 } .m-4{ grid-column:span 4 } }

/* ---------- 7. SPLIT ------------------------------------------------------ */
/* One tall panel beside a stacked pair, all flush at top and bottom.        */
.split{ display:grid; grid-template-columns:minmax(0,1fr) }
@media (min-width:900px){ .split{ grid-template-columns:repeat(2, minmax(0,1fr)) } }
/* nested, so it can legitimately inherit the computed gap from .split */
.split__stack{ display:grid; gap:inherit; grid-template-rows:repeat(2, minmax(0,1fr)) }
.split__stack .frame{ height:100%; aspect-ratio:auto; min-height:180px }

/* ---------- 8. SYMBOL WALL ------------------------------------------------ */
/* Every symbol in one dense block — no group labels, no captions.           */
.wall{ display:grid; gap:var(--gutter); grid-template-columns:repeat(7, minmax(0,1fr)) }
@media (max-width:980px){ .wall{ grid-template-columns:repeat(5, minmax(0,1fr)) } }
@media (max-width:560px){ .wall{ grid-template-columns:repeat(4, minmax(0,1fr)) } }
.wall .frame{
  aspect-ratio:1; border:1px solid var(--line-soft);
  background:radial-gradient(70% 70% at 50% 40%, var(--surface-2), var(--surface) 72%);
  transition:border-color .35s, background .35s;
}
.wall .frame img{ object-fit:contain; padding:13% }
.wall .frame:hover{ border-color:rgb(var(--accent-rgb) / .45) }

/* ---------- 8b. DEVICE SCREEN GROUND ------------------------------------- */
/* A 9:16 game canvas is WIDER than a modern phone screen (1179 × 2556 is
   19.5:9, not 9:16), so a native master letterboxes top and bottom inside
   the phone body — which is what the game actually looks like on device.
   Black, not --surface, so the bars read as the device and not as a slot. */
.phone .screen{ background:#000 }

/* ---------- 8c. CHARACTER COLUMN THAT ENDS LEVEL ------------------------- */
/* The reference panels get no fixed aspect ratio on purpose: they absorb
   whatever vertical space the copy leaves over, so the text column always
   finishes on exactly the same line as the art panel however long the
   paragraph runs. A fixed ratio would only line up for one length of copy. */
.char--tall{ align-items:stretch }
.char--tall .char__art{ align-self:start }   /* keeps its own 4:5, sets the row height */
.char--tall .char__body{ display:flex; flex-direction:column }
.char--tall .pair{
  margin-top:clamp(22px, 2.6vw, 40px);
  flex:1 1 auto; min-height:230px;
  display:grid; gap:var(--gap);
  grid-template-columns:repeat(2, minmax(0,1fr));
}
.char--tall .pair .frame{
  height:100%;
  border:1px solid var(--line-soft);
  background:radial-gradient(70% 70% at 50% 40%, var(--surface-2), var(--surface) 72%);
}
.char--tall .pair .frame img{ object-fit:contain; padding:7% }

/* ==========================================================================
   v6 — SPACING SCALE AND VERTICAL RHYTHM
   --------------------------------------------------------------------------
   The page ran two intervals for four different relationships: --section
   (130px) between every section without exception, and the within-grid gap
   (19px) also doing duty as the interval BETWEEN distinct groups. Two values
   cannot express four relationships, so nothing read as grouped and the page
   had no cadence.

   Four roles, each a clear step apart — bind < item < group < move:
     bind   heading to the content it introduces
     item   between siblings inside one grid
     group  between distinct sub-groups within a section
     move   between sections, where the subject changes

   Every clamp endpoint sits on the 4px scale below. Only the fluid middle
   interpolates off it, which is the honest trade for type that scales.
   ========================================================================== */
:is(.v6,.v7,.game){
  --s-3:12px;  --s-5:20px;  --s-6:24px;  --s-10:40px;
  --s-18:72px; --s-20:80px; --s-44:176px;

  --rhythm-item:  clamp(var(--s-3),  1.4vw, var(--s-5));
  --rhythm-bind:  clamp(var(--s-6),  2.8vw, var(--s-10));
  --rhythm-group: clamp(var(--s-10), 5vw,   var(--s-18));
  --rhythm-move:  clamp(var(--s-20), 12vw,  var(--s-44));

  --gap: var(--rhythm-item);          /* every grid gap comes off the scale */
}

:is(.v6,.v7,.game) .section{ padding-block:0 }
:is(.v6,.v7,.game) main > section + section{ margin-top:var(--rhythm-move) }
/* The statement reads as the caption to the key art, not as a separate
   movement, so it sits at the tightest interval on the page — tucked right
   under where the hero scrim resolves into the page ground. */
:is(.v6,.v7,.game) .hero + .section{ margin-top:var(--rhythm-item) }

:is(.v6,.v7,.game) .sec-head{ margin-bottom:var(--rhythm-bind) }
:is(.v6,.v7,.game) .symgroup + .symgroup{ margin-top:var(--rhythm-group) }
:is(.v6,.v7,.game) .symgroup__label{ margin-bottom:var(--rhythm-item) }
:is(.v6,.v7,.game) .group-break{ margin-top:var(--rhythm-group) }

/* The device band draws its own top and bottom rules, so the rule does the
   separating and the band's internal padding carries the movement interval.
   Previously it had zero top padding and its heading sat on the border. */
/* The device band used to draw its own rules and carry a movement interval of
   padding, because it held a heading and three CSS-built phones. It holds one
   full-bleed plate now: no heading, no rule, no padding. The plate's own top
   edge is the section boundary, and it is allowed to sit right under the win
   ladder — the top of the plate is travel headroom that nothing is composed
   into, so a tight join costs nothing and the 408px of dead black it replaces
   was the largest gap on the page. */
:is(.v6,.v7,.game) .mobile{
  margin-top:0;
  padding-block:0;
  border-block:none;
}
:is(.v6,.v7,.game) .mobile .stage--full{ margin-top:0 }

/* The plate already sits 20px under the win ladder. The distance you actually
   see is not that join — it is the 407px of plate ABOVE the phones, which is
   what a 16:9 frame leaves over a row of phones that are only 53% of its
   height and pinned to its floor.

   So the band slides up and that headroom goes behind the ladder rather than
   under it. Tied to viewport width because the stage's height is 56.25% of
   its width, so a fixed pixel pull would swallow the whole plate on a narrow
   screen and barely register on a wide one.

   The ladder has to be lifted out of the way explicitly: .mobile paints a
   gradient of its own, and without a stacking order it would cover the very
   screens it is supposed to slide behind. */
:is(.v6,.v7,.game) .mobile{
  margin-top:calc(-1 * clamp(70px, 12vw, 250px));
  position:relative;
  z-index:0;
}
:is(.v6,.v7,.game) #screens{ position:relative; z-index:1 }
:is(.v6,.v7,.game) .foot{ margin-top:0; border-top:none }   /* the band's own rule is the divider */

/* Three phones are a set; auto-fit collapsed them to one column under 560px,
   making each 759px tall and the section 2300px. Hold the row at three and
   let the phones scale. */
:is(.v6,.v7,.game) .phones{
  grid-template-columns:repeat(3, minmax(0,1fr));
  max-width:920px; margin-top:0;
}
@media (max-width:620px){
  :is(.v6,.v7,.game) .phone{ --pr:14px; --pb:4px }   /* corner radius has to scale with the body */
}

/* ---- Hero title -------------------------------------------------------
   Sits at the foot of the key art, where the scrim has resolved to solid
   ground, and hands straight into the description below it. Tracking is held
   at -0.035em; -0.04 is the floor and display type this large wants to sit
   just inside it rather than on it. */
:is(.v6,.v7,.game) .hero--titled .hero__art::before{
  /* The scrim has to carry a title now, not just hand off. The strong band
     reaches past the title's top edge (~20% up) — at the earlier 16% stop the
     brightest pixel behind the cap-line measured 2.47:1, under the 3:1 floor
     large text has to clear. */
  background:linear-gradient(to top, var(--bg) 2%, rgba(0,0,0,.82) 24%,
             rgba(0,0,0,.34) 40%, transparent 62%);
}
:is(.v6,.v7,.game) .hero__title{
  position:absolute; z-index:3; left:0; right:0;
  bottom:clamp(14px, 1.8vw, 34px);
  /* style.css sizes `.hero h1` for the logo lockup it used to hold —
     clamp(230px, 30vw, 470px) — which would box this title at 470px. */
  width:auto;
  margin-inline:auto; max-width:var(--max);
  padding-inline:var(--pad);
  font-size:clamp(46px, 8.6vw, 136px);
  font-weight:800; line-height:.9; letter-spacing:-0.035em;
  color:var(--text); text-wrap:balance;
}

/* ---- Hero height: leave it at the shared 64vh --------------------------
   Raising this to 82vh made the key art bigger but pushed the first panel
   below the fold, and that sliver of the next section showing under the hero
   is what tells you the page continues. The scroll cue is worth more than the
   extra height, so the hero keeps the base clamp(440px, 64vh, 780px). */

/* ---- The win ladder is the payoff, not a third portrait grid -----------
   Features, win ladder and device are three consecutive sections of 9:16
   panels; squinted, they read as one long field. Running the ladder past the
   container gives it a signature the contained sections do not have, and on
   wide screens it keeps growing after .wrap has capped at 1560.
   Breaking out to .wrap's border box rather than 100vw: vw includes the
   scrollbar, which pushed the document 8px wider than its client width even
   with body overflow-x:hidden. Negative padding has no scrollbar to trip on. */
:is(.game) .bleed-out{
  margin-inline:calc(var(--pad) * -1);
  padding-inline:var(--gutter);
}

/* ---- Density: symbols were the longest thing on a phone ---------------
   12 tiles at two columns ran 1669px — the longest section on mobile, for
   the page's most supporting content. Three columns brings it back in line
   with everything around it. */
@media (max-width:720px){
  :is(.game) .symgrid{ --sym-cols:3 }
}

/* ==========================================================================
   PARALLAX STAGE
   --------------------------------------------------------------------------
   Was one flat 2560x1440 composite with the phones baked in. Now three
   layers: a background taller than its frame, and two phone cut-outs pinned
   where they sat in the original. The phones are ordinary in-flow children of
   a scrolling page; only the background is driven, so the depth comes from
   one element moving against two that do not.

   Percentages below are measured off the original composite, so the
   arrangement survives the change: left phone 7.5% in, right at 38.5%, both
   ~23% of the frame width.
   ========================================================================== */
.stage{
  /* The one number that sets the effect. It is the background's overhang as a
     percentage of the frame height, and it equals the on-screen travel: at
     64% on a ~700px stage the image moves ~450px across a full pass. Two
     earlier passes undershot — 12.5%, because I sized it against the 2560px
     source rather than the ~1243px the panel actually renders at, then 32%,
     which read but stayed polite. page.js derives the travel from the
     rendered heights, so changing this value alone changes the effect.

     The plate has to be authored to match, or object-fit crops it:
     height = width × 0.5625 × (1 + this). At 2560 wide and 20.833% that is
     2560 × 1740, whose 2560 × 1440 window is the 16:9 frame on screen at
     rest — centred for the world stage, bottom-aligned for the anchored one.

     Three passes got here: 64% (2362), then 41.667% (2040) at −35%, now
     20.833% (1740) at half again. 300px of travel at source scale. Changing
     this alone changes the effect, but the plates have to be re-exported to
     the matching height or object-fit crops them — and for the anchored
     stage a centre crop silently moves the resting composition, which is the
     failure to watch for. */
  --stage-overhang:20.833%;

  position:relative;
  aspect-ratio:16/9;
  overflow:hidden;
  border-radius:var(--r);
  background:var(--surface);
}

/* Centred at rest, so half the overhang is available in each direction. */
.stage__bg{
  position:absolute; left:0; right:0; top:50%;
  height:calc(100% + var(--stage-overhang));
  transform:translate3d(0, -50%, 0);
}
/* will-change is a hint about what is ABOUT to happen, not a property to leave
   on. page.js adds .is-moving while a plate is easing and removes it when the
   plate settles, so a page at rest is not holding three full-size compositor
   layers for the life of the tab. */
.stage__bg.is-moving{ will-change:transform }
.stage__bg .frame{ height:100%; border-radius:0; background:none }
.stage__bg .frame img{ width:100%; height:100%; object-fit:cover }

.stage__phone{
  position:absolute; z-index:2;
  bottom:6%;
  width:23%; height:auto;
  filter:drop-shadow(0 32px 44px rgba(0,0,0,.55));
}
/* ---- World stage: the 40%-taller crop ----------------------------------
   16:9 stretched 40% in height, which is 160:126. The window it shows is
   therefore 2560 x 2016 rather than 2560 x 1440.

   Its overhang is NOT the shared 20.833%. bg-parallax is 2560 x 2362, and a
   2016 window inside it leaves 346px of travel — 17.163%. Written as its own
   value rather than re-cropping the plate, because cropping to hit a round
   percentage would throw away 74px of art that is perfectly usable as travel.
   Export the plate at 2560 x 2436 and this can go back to 20.833%. */
.stage--crop40{
  aspect-ratio:160/126;
  --stage-overhang:17.163%;
}

/* Two phones filling the taller frame, with an even margin all round.
   At 33.5% wide a 1064 x 2202 mockup stands 88.04% of this frame's height,
   so bottom:6% leaves 5.96% above it — balanced to within a twentieth of a
   percent without hardcoding a top value that would fight the bottom one.

   Centred as a pair: 14.5 + 33.5 + 4 + 33.5 + 14.5 = 100. They used to sit at
   7.5% and 38.5%, both inside the left half, because they were pinned to
   where they fell in a flat composite that no longer exists. */
.stage__phone--l{ left:14.5%; width:33.5% }
.stage__phone--r{ left:52%;   width:33.5% }

/* ---- One stage image instead of two --------------------------------------
   CPN ships a single stage picture, and it is not the usual phone cut-out: a
   2190 x 2298 close-up of a screen that is already cropped off at the bottom.
   Two rules that hold for the pair are wrong for it — pinning by width, and
   leaving an even margin all round.

   Sized by HEIGHT instead, to the frame less a margin at the top, so the
   picture's own bottom cut lands on the frame's bottom edge and the two become
   one line. Width follows from the aspect ratio, so a differently proportioned
   image stays correct without new numbers.

   `bottom:0` overrides the 6% the pair sits on, and `left:50%` with a -50%
   translate centres it independently of its width — which is not knowable here
   because it comes from the image itself. The translate is safe: .stage__phone
   only ever has `scale` written to it by page.js, never `transform`, and this
   stage is not one of the scaling ones. */
.stage__phone--solo{
  left:50%;
  transform:translateX(-50%);
  bottom:0;
  top:6%;
  width:auto;
  height:94%;
  max-width:none;
}

/* Grow from the base, not the middle. The phones are pinned by their bottom
   edge, so a centre-origin scale would lift them off the floor of the frame
   and the whole row would look like it was floating away. */
.stage__phone{ transform-origin:50% 100% }

/* ---- Hero as a stage ---------------------------------------------------
   The key art gets the same parallax machinery, with three things turned off.
   .stage brings an aspect-ratio, a radius and a surface colour, none of which
   the hero wants: its height is a clamp, its edges are the viewport, and the
   scrim on .hero__art::before has to keep painting over the plate.

   The hero opens vertically CENTRED rather than at either edge, so the
   overhang here is 20.833% but only the lower half of it is ever used —
   page load sits at the midpoint and scrolling carries it to the bottom.
   Effective drift is therefore 10.417% of the frame, half of the two stages
   below, which is what it was when it opened top-aligned. Same movement,
   different starting frame.

   Sizing it 20.833% rather than 10.417% also fixes the crop for free: at the
   smaller value the plate was WIDER than 2560 x 1300's ratio on a desktop, so
   cover trimmed height and needed object-position to stop it eating the top
   edge. At this value the plate is narrower than the art, cover fills the
   height exactly and trims the sides instead, and the art's vertical centre
   really is the centre. */
.stage--hero{
  --stage-overhang:20.833%;
  aspect-ratio:auto;          /* the height clamp on .hero__art wins */
  border-radius:0;
  background:none;
}

/* The frame is the point of the effect, so it holds its shape rather than
   collapsing — the phones just get smaller with it. */
@media (max-width:700px){
  .stage__phone{ filter:drop-shadow(0 14px 20px rgba(0,0,0,.55)) }
}

@media (prefers-reduced-motion:reduce){
  /* No drift. The composition still reads; it simply holds still. */
  .stage__bg{ transform:translate3d(0,-50%,0) !important; will-change:auto }

  /* Except this one. Centring is the right rest for the hero and the world
     stage, which both rest centred anyway — but the portrait-build plate is
     AUTHORED to rest bottom-aligned: its bottom window is the composition and
     the strip above it is travel nobody is meant to stop on. Centring put it
     103px out, so reduced-motion visitors saw a crop that was never framed.
     Pinned by position, because a percentage translate resolves against the
     plate's own height rather than the frame's. */
  .stage--anchor-bottom .stage__bg{
    top:auto; bottom:0; transform:none !important;
  }
}

/* ==========================================================================
   PORTRAIT BUILD STAGE
   The same machinery as the hero stage, with three differences that matter.

   1. It bleeds the full page width instead of sitting inside .wrap, so it
      reads as a band rather than a card. That is why the radius is zero: a
      rounded corner against the viewport edge looks like a mistake.

   2. Its drift is anchored, not centred. The hero stage centres its plate at
      the midpoint of its own pass, which is right for something you scroll
      past. This one is the last thing on the page, so it is driven by
      document progress instead: the plate's bottom edge lands exactly on the
      frame's bottom border at full scroll, and holds there. page.js reads
      .stage--anchor-bottom to pick that mapping.

   3. The phones scale as well as sit. Six percent across the whole pass —
      enough to feel like they are coming toward you, not enough to notice as
      an effect. page.js writes the standalone `scale` property, never
      `transform`, because .rise owns transform and would cancel it.
   ========================================================================== */
.stage--full{
  width:100%;                    /* outside .wrap, so this is the viewport */
  margin-top:var(--rhythm-group);
  border-radius:0;
  background:none;               /* the band's own gradient shows until the plate loads */
}

/* Three phones at 14.875% wide — 30% down from 21.25%. The gaps are scaled by
   the same 0.7 (6.6% to 4.62%) rather than left alone, so the row keeps the
   proportions it was composed with instead of drifting apart as the phones
   shrink.

   The group is 53.865% wide, which centres it on an equal 23.07% margin at
   each end. Those numbers are written out rather than computed because the
   arithmetic only has to hold at one size, and a reader checking the row is
   centred can add them up:  23.07 + 3(14.875) + 2(4.62) + 23.07 = 100. */
.stage__phone--b1{ left:23.07%;  width:14.875% }
.stage__phone--b2{ left:42.565%; width:14.875%; bottom:9% }   /* centre one stands taller */
.stage__phone--b3{ left:62.06%;  width:14.875% }

/* Three portrait phones inside a 16:9 band are about 80px wide at 375px, which
   reads as "there are phones here" rather than as artwork. The full fix is a
   second plate authored tall for narrow screens and swapped with <picture>;
   this is not that. What it does is stop the band being the wrong SHAPE for
   its contents — 4:5 gives the phones ~40% more width for free, and the plate
   is a soft near-transparent gradient, so cover trimming its sides costs
   nothing legible. Steps rather than a slide, so each is a shape someone
   chose. */
@media (max-width:700px){
  .stage--full{ aspect-ratio:4/5 }
  .stage__phone--b1{ left:5%;  width:28% }
  .stage__phone--b2{ left:36%; width:28% }
  .stage__phone--b3{ left:67%; width:28% }
}
@media (max-width:480px){
  .stage--full{ aspect-ratio:1/1 }
}

/* ---- Closing space on a page that has no portrait build ----------------
   Every game so far ended on the portrait-build plate, which is full-bleed and
   bottom-anchored: its own bottom edge closes the page. TR has no portrait
   build, so whatever section comes last ends flush against the bottom of the
   document and the page stops dead on an image.

   This gives the last section the same interval the band used to occupy, so
   the page still finishes on purpose rather than running out. Put it on
   whichever section is genuinely last; it is not automatic, because "last" is
   a decision about this game's content rather than something CSS can know.

   Scoped to :is(.v6,.v7,.game) to beat that block's own `.section{
   padding-block:0 }`, which is where the rhythm system strips the base
   padding before driving every interval with margins instead. An unscoped
   `.section--last` loses to it on specificity and silently does nothing —
   the padding computes to 0 and the page still ends flush.

   Padding rather than a margin because a bottom margin on the last child
   collapses out through <main> and <body>, and the space disappears again. */
:is(.v6,.v7,.game) .section--last{ padding-bottom:var(--rhythm-move) }

/* ---- Home mark ---------------------------------------------------------
   Replaces the top bar. Fixed rather than sticky so it never leaves, and
   pill-backed because it floats over artwork whose brightness we do not
   control — the pill is the affordance and the legibility in one. The
   wordmark shows until a logo.png exists, then the image takes over. */
.homemark{
  /* 44px is the touch-target floor. The label alone made a 32px box, which
     clears WCAG 2.5.8 but not 2.5.5, and this is the only interactive element
     on a game page. */
  min-height:44px;
  position:fixed; z-index:60;
  top:clamp(12px, 1.8vw, 24px); left:clamp(12px, 2.4vw, 40px);
  display:inline-flex; align-items:center;
  max-width:min(46vw, 260px);
  padding:9px 16px; border-radius:99px;
  /* .62 measured 3.03:1 worst-case over the key art; 12px text needs 4.5 */
  background:rgba(0,0,0,.82);
  border:1px solid rgba(255,255,255,.12);
  backdrop-filter:blur(10px) saturate(130%);
  color:var(--text); text-decoration:none;
  transition:background .18s ease, border-color .18s ease;
}
/* The hero's -64px pull existed so the art ran under the transparent top bar.
   With no bar it just crops 64px off the top of the key art. */
.game .hero{ margin-top:0 }

.homemark:hover{ background:rgba(0,0,0,.9); border-color:rgb(var(--accent-rgb) / .5) }
.homemark:focus-visible{ outline:2px solid var(--accent); outline-offset:3px }

.homemark__text{
  font:700 12px/1 var(--font);
  letter-spacing:.15em; text-transform:uppercase; white-space:nowrap;
}
/* The SpinPulse logo, sized to the pill. The link carries the accessible name. */
.homemark img.homemark__logo{ display:block; width:auto; height:24px }
/* The logo slot stays out of the way until a real file lands. */
.homemark__img{ display:none; width:clamp(84px, 8vw, 132px); background:none }
.homemark__img::after{ display:none }
.homemark:has(img.ok) .homemark__img{ display:block }
.homemark:has(img.ok) .homemark__text{ display:none }
.homemark:has(img.ok){ padding:8px 14px }
.homemark img{ width:100%; height:auto; object-fit:contain }

/* ---- Feature cards ----------------------------------------------------
   These slots hold full-bleed feature screens now, not alpha cut-outs, so
   the pedestal glow behind them has nothing to light and the 7% inset just
   framed the art. Each panel takes the shape of its own file. */
:is(.game) .char--cards .char__art::before{ display:none }

/* All three feature panels are 9:16, and the two small ones have to finish
   level with the tall one. That only works if the columns are proportioned
   for it, so the split is 0.7fr / 1fr rather than the 1.05/1 used elsewhere:

     hero    0.7fr of 1344 = 553 wide  -> 983 tall
     smalls  (791 - 20) / 2 = 385 wide ->  685 tall
     copy    ~200 + 40 gap + 685       =  925  <  983

   That slack only covers ~200px of copy. Past it the body column outgrew the
   hero and pushed the pair below its bottom edge, so the pair now takes
   whatever height the copy leaves instead (see the pair rules below). */
:is(.game) .char--cards{ grid-template-columns:minmax(0,0.7fr) minmax(0,1fr) }
:is(.game) .char--cards .char__body{ justify-content:space-between }

/* The 56ch cap belongs to the prose, not to the column. Left on the column it
   also shrank the image pair to 263px wide, which left a 183px hole between
   the copy and the panels. Cap the paragraph instead. */
:is(.game) .char--cards .char__body{ max-width:none }
:is(.game) .char--cards .char__body > p{ max-width:56ch }

/* These beat the `aspect-ratio:auto` rule below, which exists for pages whose
   character slot still holds a free-shaped cut-out. */
:is(.game) .char--cards .char__art .frame{ aspect-ratio:9/16; height:auto }
/* The pair fills the height the copy leaves, so it finishes level with the
   hero however long the copy runs. Size containment keeps it out of the row
   height, which the hero alone then sets. Each card fits both the half-column
   width and the leftover height at 9:16, and sits on the bottom edge. */
:is(.game) .char--cards .pair{
  flex:1 1 0; min-height:0;
  container-type:size;
  display:flex; align-items:flex-end; gap:var(--gap);
}
:is(.game) .char--cards .pair .frame{
  flex:none; aspect-ratio:9/16; height:auto;
  width:min(calc((100cqw - var(--gap)) / 2), calc(100cqh * 9 / 16));
  background:none; border-color:var(--line-soft);
}
:is(.game) .char--cards .pair .frame img{ height:100%; padding:0; object-fit:cover }

/* Stacked, the proportioning above is meaningless and the hero would run to
   ~600px tall on a phone. Let it share the row with nothing and cap it. */
@media (max-width:959px){
  :is(.game) .char--cards{ grid-template-columns:minmax(0,1fr) }
  :is(.game) .char--cards .char__art{ max-width:min(62vw, 380px) }
  /* Stacked, there is no hero beside the pair to match, so it sizes by width. */
  :is(.game) .char--cards .pair{ flex:none; container-type:normal }
  :is(.game) .char--cards .pair .frame{ width:calc((100% - var(--gap)) / 2) }
}

/* ---- Character panel keeps the artwork's own proportions ---------------
   The slot used to be pinned to 4:5, which silently letterboxes the moment
   an export changes shape. It now takes its height from the image, so
   whatever comes out of Photoshop is what shows. */
:is(.game) .char__art{ aspect-ratio:auto }
:is(.game) .char__art .frame{ height:auto; aspect-ratio:auto }
:is(.game) .char__art .frame img{ height:auto; object-fit:contain }

/* The bar floats over full-bleed key art. Against the dark placeholder that
   was fine; against real artwork the nav links measured 1.16:1 — invisible.
   The scrim now carries the bar rather than merely tinting it, and fades out
   by 132px so it still reads as a vignette and not a filled header.
   Worst case (a link over the brightest pixel in the strip) is 4.7:1. */
:is(.v6,.v7,.game) .topbar:not(.stuck){
  background:linear-gradient(to bottom,
              rgba(0,0,0,.88) 0,
              rgba(0,0,0,.86) 64px,
              rgba(0,0,0,0)  132px);
}
:is(.v6,.v7,.game) .topbar:not(.stuck) .navlinks a{ color:#e8e8ee }
:is(.v6,.v7,.game) .topbar:not(.stuck) .brand small{ color:#b9b9c4 }

/* Keyboard order is part of layout: it was inheriting the browser default. */
:is(.v6,.v7,.game) a:focus-visible, :is(.v6,.v7,.game) button:focus-visible{
  outline:2px solid var(--accent); outline-offset:3px; border-radius:2px;
}

/* ==========================================================================
   v6 — TYPE SYSTEM (Manrope)
   --------------------------------------------------------------------------
   One family. Manrope ships as a variable font, so 400..800 arrives in a
   single file — smaller than the four static cuts Inter was pulling, one of
   which (800) was downloaded and never used.

   The old page had three different jobs wearing one costume: a symbol's name,
   a screen's state, and a device caption were all 10px/500/mono/uppercase,
   separated only by colour. Six named roles replace that:

     display  section headings          lead   the opening statement
     body     running prose             label  group headings, state names
     meta     item names                spec   measurements (the only mono)

   Mono is now reserved for measurement, which on this page means the
   placeholder specs. Those vanish as artwork lands, so the finished page
   speaks entirely in Manrope and the scaffolding speaks in mono.
   ========================================================================== */

/* Manrope is served from this site, the same variable file the landing page uses, rather
   than from Google Fonts. Each game page preloads it. */
@font-face{
  font-family:"Manrope";
  src:url("assets/manrope-latin-wght-normal.woff2") format("woff2");
  font-weight:200 800;
  font-display:swap;
}

/* Metric-matched fallback so the swap does not reflow the page.
   Manrope: ascent 1020/1000, descent 280/1000, x-height ~0.525 vs Arial 0.519. */
@font-face{
  font-family:"Manrope Fallback";
  src:local("Arial"), local("Helvetica Neue"), local("Liberation Sans");
  size-adjust:101%;
  ascent-override:102%;
  descent-override:28%;
  line-gap-override:0%;
}

:is(.v6,.v7,.game){
  /* ---- GAME THEME — violet ----
     Every accent on the page runs through these three. The primary is picked
     for its contrast on pure black, because it carries 11-12px uppercase
     labels (specials, screen states) as well as glows: 7.7:1, well clear of
     the 4.5 floor a deeper violet like #8B5CF6 only just scrapes past.
     --accent-2 is the glow's far stop, so the character pedestal and the
     device band bloom violet→fuchsia rather than sitting on one flat hue. */
  --accent:     #a78bfa;
  --accent-2:   #e879f9;
  --accent-rgb: 167 139 250;

  --font:"Manrope", "Manrope Fallback", system-ui, sans-serif;

  --t-display: clamp(34px, 4.6vw, 64px);
  --t-lead:    clamp(19px, 1.85vw, 26px);
  --t-body:    clamp(16px, 1.15vw, 18px);   /* 16px floor, was 15px */
  --t-cap:     clamp(16px, 1.3vw,  19px);
  --t-ui:      14px;
  --t-label:   12px;
  --t-meta:    12px;   /* symbol names; 11px was below the 12px floor for any text */
  --t-spec:    11px;
}

/* Display — Manrope sets wide by default and needs pulling in at size.
   Held above the -0.04em tracking floor. */
:is(.v6,.v7,.game) .h2{
  font-size:var(--t-display); font-weight:700;
  line-height:1.04; letter-spacing:-0.035em;
}

/* Lead and body. Light type on near-black needs compensating on all three
   axes, so both run looser and fractionally tracked out versus a light page. */
:is(.v6,.v7,.game) .statement{
  font-size:var(--t-lead); font-weight:400;
  line-height:1.52; letter-spacing:-0.01em;
}
:is(.v6,.v7,.game) .char__body p{
  font-size:var(--t-body); font-weight:400;
  line-height:1.65; letter-spacing:0.005em;
}
/* Manrope has no true italic, so an <em> here would be synthesised and slanted
   by the browser. Emphasis is carried by colour instead. */
:is(.v6,.v7,.game) .statement em{ font-style:normal; font-weight:500; color:var(--text) }

/* Label — group headings and state names. */
:is(.v6,.v7,.game) .symgroup__label{
  font:700 var(--t-label)/1 var(--font);
  letter-spacing:0.16em; text-transform:uppercase; color:var(--muted);
}
:is(.v6,.v7,.game) .shot__cap span{
  font:600 var(--t-label)/1 var(--font);
  letter-spacing:0.14em; text-transform:uppercase; color:var(--accent);
}

/* Meta — item names. Same size as spec but a different family, so the two
   read apart instantly where they sit side by side. */
:is(.v6,.v7,.game) .sym figcaption,
:is(.v6,.v7,.game) .phone figcaption{
  font:500 var(--t-meta)/1.4 var(--font);
  letter-spacing:0.11em; text-transform:uppercase;
}
:is(.v6,.v7,.game) .sym figcaption{ color:var(--muted-2) }
:is(.v6,.v7,.game) .phone figcaption{ color:var(--muted-2) }

/* Specials carry no accent of their own — same border and same caption colour
   as the pays. The group heading above them is what marks them out, and the
   accent stays reserved for hover. */
:is(.v6,.v7,.game) .sym--special .frame{ border-color:var(--line-soft) }
:is(.v6,.v7,.game) .sym--special figcaption{ color:var(--muted-2) }

/* Caption title — was inheriting the 1.6 body leading on a two-word line. */
:is(.v6,.v7,.game) .shot__cap b{
  font-size:var(--t-cap); font-weight:600;
  line-height:1.25; letter-spacing:-0.02em;
}

/* Chrome. */
:is(.v6,.v7,.game) .navlinks a, :is(.v6,.v7,.game) .foot p, :is(.v6,.v7,.game) .foot nav{ font-size:var(--t-ui) }
:is(.v6,.v7,.game) .brand{ letter-spacing:-0.015em }
:is(.v6,.v7,.game) .brand small{ font-size:var(--t-ui) }

/* Spec — the production scaffolding voice. Tabular figures so the dimension
   pairs line up column-on-column across a grid of placeholders. */
:is(.v6,.v7,.game) .frame::after{
  font:500 var(--t-spec)/1.7 var(--mono);
  letter-spacing:0.1em;
  font-variant-numeric:tabular-nums;
}

/* ---------- 9. MICRO LABEL ------------------------------------------------ */
/* The only text allowed near a panel in v3. Never used in v4.               */
.tag{
  display:flex; justify-content:space-between; align-items:baseline; gap:16px;
  padding:11px 2px 0;
  font:500 10px/1 var(--mono); letter-spacing:.16em; text-transform:uppercase;
  color:var(--muted-2);
}
.tag i{ font-style:normal; color:var(--ghost) }

.index{
  display:flex; align-items:baseline; gap:14px; margin-bottom:14px;
  font:500 10px/1 var(--mono); letter-spacing:.18em; text-transform:uppercase;
  color:var(--muted-2);
}
.index b{ color:var(--accent); font-weight:500 }
.index::after{ content:""; flex:1; height:1px; background:var(--line-soft) }

/* ---------- 10. STATEMENT ------------------------------------------------- */
/* The description block. In v4 this is the only prose on the page.          */
.statement{
  max-width:60ch; text-wrap:pretty;
  font-size:clamp(17px, 1.7vw, 24px); line-height:1.6; letter-spacing:-.015em;
  color:#cdcdd6;
}
.statement--center{ max-width:52ch; margin-inline:auto; text-align:center; text-wrap:balance }
.statement em{ font-style:normal; color:var(--text) }

/* ---------- 11. HERO MODIFIERS ------------------------------------------- */
/* Hero carrying no overlaid logo, tagline or pills. Two things change:
   the band gets shorter, because its height no longer has to accommodate a
   type block; and the scrim gets much lighter, because it is no longer
   holding text up — it only needs to hand off into the page ground.
   The following section tightens its top padding to match, so the art and
   the description do not end up separated by a field of black. */
.hero--bare .hero__art{ height:clamp(440px, 64vh, 780px) }
.hero--bare .hero__art::before{
  background:linear-gradient(to top, var(--bg) 1%, rgba(8,8,11,.38) 20%, transparent 52%);
}
/* The interval below a bare hero is owned by the v6 rhythm scale, not here.
   A padding-top rule at this selector's weight silently outranked it. */

.hero--full .hero__art{ height:100svh; min-height:580px }
.hero--center .hero__body{
  inset:0; display:grid; place-items:center; padding:0 var(--pad);
  text-align:center;
}
.hero--center h1{ margin-inline:auto }

/* ---------- 12. QUIET CHROME --------------------------------------------- */
.topbar--quiet .navlinks{ display:none }
.foot--quiet .wrap{ justify-content:center; text-align:center }
.foot--mark{ border-top:none }
.foot--mark .wrap{ flex-direction:column; gap:20px; justify-content:center }
.foot--mark .markleft{ width:clamp(96px,10vw,150px); opacity:.5 }
.foot--mark .markleft .frame{ background:none }
.foot--mark .markleft .frame::after{ border-color:transparent }

/* A hyphenated name in a title breaks only between words where it fits, so it
   reads as one name ("Ultimate / Rock-Paper-Scissor", not "Rock- / Paper").
   Phones are too narrow to hold it on a line, so they keep the hyphen breaks. */
@media (min-width:768px){ .hero__title .keep{ white-space:nowrap } }

/* ---- Next mark -----------------------------------------------------------
   The page has no footer by choice, so this is the way onward: the next game in
   the index's order, opposite the home mark and in the same pill vocabulary, so
   the two read as one pair of controls rather than a bar. Same background and
   blur, so it carries its own legibility over whatever artwork sits under it. */
.nextmark{
  min-height:44px;                      /* the touch-target floor, as the home mark */
  position:fixed; z-index:60;
  top:clamp(12px, 1.8vw, 24px); right:clamp(12px, 2.4vw, 40px);
  display:inline-flex; align-items:center; gap:10px;
  max-width:min(52vw, 320px);
  padding:9px 16px; border-radius:99px;
  background:rgba(0,0,0,.82);
  border:1px solid rgba(255,255,255,.12);
  backdrop-filter:blur(10px) saturate(130%);
  color:var(--text); text-decoration:none;
  transition:background .18s ease, border-color .18s ease;
}
.nextmark:hover{ background:rgba(0,0,0,.9); border-color:rgb(var(--accent-rgb) / .5) }
.nextmark:focus-visible{ outline:2px solid var(--accent); outline-offset:3px }
.nextmark__label{
  font:700 var(--t-label)/1 var(--font);
  letter-spacing:.16em; text-transform:uppercase; color:var(--muted);
  flex:none;
}
/* One line, and an ellipsis rather than a second line if a title runs long. */
.nextmark__title{
  font:600 14px/1.2 var(--font); letter-spacing:-.01em;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.nextmark__arrow{ flex:none; color:var(--accent); transition:translate .25s ease }
.nextmark:hover .nextmark__arrow{ translate:3px 0 }

/* Two pills on a phone would crowd the top; the title and arrow carry it alone. */
@media (max-width:600px){
  .nextmark{ max-width:min(56vw, 240px); gap:8px }
  .nextmark__label{ display:none }
}
@media (prefers-reduced-motion:reduce){
  .nextmark, .nextmark__arrow{ transition:none }
}
