@charset "UTF-8";
/* =====================================================================
   SECTIONS — Phase 5 mobile-first advertising landing page.

   Composed at 390px first; every breakpoint above widens the SAME
   composition. There is no viewport at which the page becomes an
   application layout.

   Section rhythm: each band owns one persuasion job, sits on the TrustDice
   purple foundation, and is separated from its neighbours by a change of
   ground rather than by a boxed container.
   ===================================================================== */

/* ---------------------------------------------------------------------
   Section rhythm
   Every band uses the same vertical scale, so mobile compaction is four
   numbers in tokens.css rather than a hunt through this file.
   --------------------------------------------------------------------- */
.hero, .gameplay, .lineup, .howto, .features, .voices, .trust, .faq, .final-cta {
  padding-top: var(--section-padding-y);
  padding-bottom: var(--section-padding-y);
}

/* --- Section separators -------------------------------------------------
   A thin decorative band between environments so a colour change reads as a
   deliberate scene change rather than an accident. Purely presentational. */
.sep {
  position: relative;
  height: 14px;
  margin-top: -14px;      /* sits ON the section above, not between two grounds */
  overflow: hidden;
  z-index: 2;
}
/* Pachinko-ball row. */
.sep--balls {
  background-image: radial-gradient(circle at 7px 7px, rgba(255, 255, 255, .85) 0 3px, transparent 3.5px);
  background-size: 22px 14px;
  opacity: .5;
}
/* Marquee bulbs. */
.sep--bulbs {
  background-image: radial-gradient(circle at 9px 7px, var(--pachi-yellow) 0 3px, rgba(255, 227, 61, .25) 3.5px 5px, transparent 5.5px);
  background-size: 26px 14px;
}
/* Diagonal chrome stripes. */
.sep--stripes {
  background-image: repeating-linear-gradient(-45deg, rgba(28, 16, 36, .16) 0 6px, transparent 6px 14px);
}

/* ---------------------------------------------------------------------
   Shared section heading
   One heading component for every band, so section rhythm is consistent
   and no section invents its own type scale.
   --------------------------------------------------------------------- */
/* Phase 5.8H: the shared heading-to-content gap, one token for the page. */
.section-head { margin-bottom: var(--head-gap); }
.section-head__title {
  position: relative;
  padding-left: 14px;
  /* Phase 5.8H density pass: was clamp(1.35rem, 6.2vw, 2.1rem) (-9%). */
  /* Phase 5.8I: -9% again (5.8H was clamp(1.24rem, 5.7vw, 1.91rem)). */
  font-size: clamp(1.13rem, 5.2vw, 1.74rem);
  font-weight: 900;
  line-height: 1.34;
  color: #fff;
}
/* A section on a LIGHT ground flips the heading and sub to deep plum. One
   rule, set by the section wrapper, rather than nine per-section overrides. */
.is-light .section-head__title { color: var(--text-plum); }
.is-light .section-head__sub { color: #5b4a68; }
.is-light .section-head__lead { color: #b0430e; }
.section-head__title::before {
  content: "";
  position: absolute;
  left: 0;
  top: .18em;
  bottom: .18em;
  width: 5px;
  border-radius: 3px;
  background: linear-gradient(180deg, var(--pachi-gold), var(--pachi-red));
}
.section-head__sub {
  margin-top: 6px;
  padding-left: 14px;
  font-size: .88rem;
  line-height: 1.7;
  color: #dccfe9;
}
/* Centred campaign variant. Used for the campaign bands; the FAQ, legal text
   and long explanatory copy stay left-aligned and conventionally readable. */
.section-head--center { text-align: center; }
.section-head--center .section-head__title {
  padding-left: 0;
  padding-bottom: 12px;
  letter-spacing: .01em;
  line-height: 1.34;
  text-wrap: balance;
}
/* The rail becomes a short centred underline plate instead of a side bar. */
.section-head--center .section-head__title::before {
  left: 50%;
  top: auto;
  bottom: 0;
  width: 56px;
  height: 4px;
  transform: translateX(-50%);
  background: linear-gradient(90deg, var(--color-pachi-yellow), var(--color-pachi-orange));
}
.section-head--center .section-head__sub { padding-left: 0; }
/* A single relocated benefit line under a section heading. */
.section-head__lead {
  margin-top: 8px;
  font-size: .82rem;
  font-weight: 900;
  letter-spacing: .01em;
  color: var(--color-pachi-yellow);
}

@media (min-width: 768px) {
  .section-head { margin-bottom: 22px; }
  .section-head__title { padding-left: 16px; }
  .section-head__sub { padding-left: 16px; font-size: .95rem; }
  .section-head--center .section-head__title,
  .section-head--center .section-head__sub { padding-left: 0; }
/* A single relocated benefit line under a section heading. */
.section-head__lead {
  margin-top: 8px;
  font-size: .82rem;
  font-weight: 900;
  letter-spacing: .01em;
  color: var(--color-pachi-yellow);
}
}

/* ---------------------------------------------------------------------
   HERO — condensed, centred campaign hero (Phase 5.1)

   Phase 5's hero was left-aligned and tall: at 390x844 the machine fan ran off
   the fold and nothing of the next section showed. This version is centred at
   every width and every vertical measurement was cut until the whole
   composition — eyebrow, H1, sub, credibility badge, proof row, CTA, bonus
   note and the complete fan — fits inside the first viewport with a deliberate
   sliver of the next section visible beneath it.
   --------------------------------------------------------------------- */
.hero {
  position: relative;
  overflow: hidden;
  padding: 18px 0 26px;
  text-align: center;
  background:
    radial-gradient(112% 70% at 50% 0%, rgba(169, 104, 255, .60) 0%, transparent 62%),
    radial-gradient(80% 56% at 88% 96%, rgba(24, 207, 250, .22) 0%, transparent 64%),
    radial-gradient(72% 50% at 8% 96%, rgba(236, 43, 145, .34) 0%, transparent 66%),
    linear-gradient(168deg, var(--campaign-purple-bright) 0%, var(--campaign-purple) 44%, var(--campaign-magenta) 118%);
}
.hero__inner { position: relative; z-index: 3; }
/* Phase 5.8D: the hero copy uses the shared centred-copy measure at every
   width, so it lines up with every other section's inner content. */
.hero__copy { max-width: var(--content-copy); margin-inline: auto; }

/* --- Eyebrow ------------------------------------------------------------- */
.hero__eyebrow {
  display: inline-block;
  padding: 6px 16px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .10);
  border: 1px solid rgba(255, 227, 61, .40);
  font-size: .72rem;
  font-weight: 900;
  letter-spacing: .09em;
  color: var(--color-pachi-yellow);
}

/* --- Headline ------------------------------------------------------------
   Japanese display treatment: tight display line-height, a little tracking,
   balanced wrapping, and a restrained plum edge — not a fake 3D bevel.
   Phase 5.8: `.hero__title-wrap` groups the H1 with the credibility badge in
   the DOM (so the badge always immediately follows the title it's visually
   attached to). Below 1024px the badge is a normal-flow sibling of the H1
   inside it; from 1024px `.hero__title-wrap` becomes the positioning
   context and the badge hangs off the BOTTOM-right corner of the title,
   beside the H1's second (shorter, gold) line — see `.stat-badge` below for
   why that corner, not the top, is the one with reliable clear space at
   every tested width. */
.hero__title-wrap { position: relative; }
.hero__title {
  margin-top: 9px;
  /* Phase 5.8H density pass: was clamp(1.5rem, 7.1vw, 2.6rem) (-9%). */
  /* Phase 5.8I: -9% again (5.8H was clamp(1.4rem, 6.5vw, 2.36rem)). */
  font-size: clamp(1.28rem, 5.9vw, 2.14rem);
  font-weight: 900;
  line-height: 1.3;
  letter-spacing: .005em;
  text-wrap: balance;
  text-shadow: 0 2px 0 rgba(24, 0, 36, .55), 0 0 26px rgba(169, 104, 255, .38);
}
.hero__sub {
  margin-top: 8px;
  font-size: clamp(.92rem, 3.9vw, 1.05rem);
  font-weight: 700;
  line-height: 1.6;
  color: #efe3f9;
}
.hero__sub span { display: block; }

/* --- Registered-account credibility sticker ------------------------------
   Phase 5.7 made this a compact stamp; 5.8 floated it; 5.8B turned it into a
   circular gold-sunburst medal. Phase 5.8C corrects two things about that
   medal:

   1. STRUCTURE. It was `float: right` as the first child of
      `.hero__title-wrap`, so the headline's own line boxes wrapped around it —
      which means the H1's width, wrapping and optical centring all CHANGED
      depending on whether a verified snapshot existed. The headline must be
      identical with or without the badge. It is now `position: absolute`, so
      it is out of flow entirely and contributes nothing to the H1's layout;
      scripts/verify.mjs measures the H1 with the badge shown and hidden and
      requires <=1px difference in x, width, height and line count.
   2. VISUAL. The `repeating-conic-gradient` sunburst plus concentric rings
      was dense and noisy at 94-132px. It is now a clean Japanese campaign
      sticker: a compact tilted OVAL, a flat magenta-to-coral plate, one 3px
      gold outline, one small sparkle and a deep-plum shadow. No conic
      gradient, no concentric rings.

   Non-interactive by construction: a <div> with `pointer-events: none`, no
   hover/focus/active rule anywhere, and no button semantics.

   Copy is unchanged and is exactly three parts: the small `登録者数` label, the
   dominant dynamic `[value]万`, and the small `人突破！` suffix. The number is
   never hard-coded — see hero.njk and scripts/verify.mjs. The oval is wider
   than it is tall specifically so a five-character value (1000万) still fits
   on one line without clipping. */
.stat-badge {
  position: absolute;
  /* Anchored just BELOW the headline block's bottom-right corner
     (`top: 100%`), lapping its boundary by 2px.

     This position was chosen by measurement, not taste. The H1 is centred and
     its lines fill their box at every width — at 390px the first line ends 5px
     from the box edge, at 1024px 11px — so ANY box large enough to hold the
     three copy lines and placed beside the headline necessarily covers
     headline glyphs. The band immediately below it does not: the sub line is
     much shorter than its paragraph box, leaving 56-106px of clear right-hand
     whitespace. So the sticker sits there, touching no headline glyph at all,
     and only laps the tail of the sub's first line by ~12px at 320-360px
     (nothing at all from 390px up). verify.mjs asserts every one of those
     clearances. */
  top: 100%;
  right: -12px;
  margin-top: 2px;
  z-index: 5;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 0;
  width: clamp(78px, 23vw, 92px);
  /* Wider than tall on purpose: a five-character value (1000万) has to fit on
     one line inside the plate, which a circle at this size cannot do. */
  aspect-ratio: 1 / .66;
  padding: 0;
  border: 3px solid var(--lineup-gold);
  border-radius: 50%;
  background: linear-gradient(158deg, var(--badge-magenta) 0%, #f0356f 52%, var(--badge-coral) 100%);
  box-shadow: 0 4px 10px rgba(20, 0, 34, .42);
  text-align: center;
  transform: rotate(var(--badge-rotate));
  /* Decoration: it must never intercept a tap meant for the headline or a CTA. */
  pointer-events: none;
}
/* One restrained sparkle on the upper-right of the plate. Never animated, so
   reduced motion has nothing to switch off. */
.stat-badge__spark {
  position: absolute;
  top: -7px;
  right: -4px;
  width: 13px;
  height: 13px;
  display: grid;
  place-items: center;
  font-size: .78rem;
  line-height: 1;
  color: var(--color-pachi-yellow);
  text-shadow: 0 1px 0 var(--badge-plum-edge);
}
.stat-badge__spark::before { content: "✦"; }
/* The small label above the figure (登録者数). Deliberately well under the
   number's size/weight so the 万-figure reads as the dominant element. */
.stat-badge__label {
  font-size: clamp(.48rem, 1.8vw, .55rem);
  font-weight: 800;
  letter-spacing: .03em;
  line-height: 1.1;
  color: #ffeed6;
  text-shadow: 0 1px 1px rgba(58, 10, 74, .55);
}
/* The statement STACKS (number over suffix): three short CJK lines fit an oval,
   one long line does not. Only where the line breaks changed — the text is
   byte-identical, and `aria-live` stays on this <p>, so a runtime refresh is
   still announced as one whole statement. */
.stat-badge__figure {
  display: block;
  line-height: 1;
}
/* White, so the sticker reads as a different object from the solid-yellow H1
   right beside it. Sized to hold FIVE characters (1000万) without clipping —
   verify.mjs renders 99万 / 232万 / 999万 / 1000万 and asserts the figure stays
   inside the plate for each. */
.stat-badge__number,
.stat-badge__value {
  display: block;
  /* The floor is set by the FIVE-character case: at 320px the plate's inner
     width is 72px, so 1000万 needs <=14.4px per character. */
  font-size: clamp(.86rem, 2.4vw, 1.12rem);
  font-weight: 900;
  letter-spacing: 0;
  color: #fff;
  text-shadow: 0 2px 0 rgba(88, 4, 52, .8);
  white-space: nowrap;
}
.stat-badge__suffix {
  display: block;
  margin-top: 1px;
  font-size: clamp(.48rem, 1.9vw, .56rem);
  font-weight: 800;
  color: #fff4e4;
  text-shadow: 0 1px 1px rgba(58, 10, 74, .6);
  white-space: nowrap;
}
/* 320-359px only. Here the viewport is too narrow for the sticker to clear
   the sub line's tail below the headline (it would nick the last character by
   ~12px), so it moves UP beside the headline's FIRST line instead. That line
   ends in the ideographic comma 「、」, whose ink sits in the lower-left of its
   em box, so the ~14px lap covers punctuation whitespace rather than a
   character — measurably the least destructive overlap available at this
   width. Every other line, the eyebrow, the sub, the proof row and the CTA
   stay fully clear; verify.mjs asserts each. */
@media (max-width: 359px) {
  .stat-badge {
    top: 8px;
    margin-top: 0;
  }
}
@media (min-width: 768px) {
  /* Phase 5.8F: `right: -14px` put the sticker 17px past the shared content
     rail at 768px, because the copy column IS the container width there. It
     sits flush with the rail instead; the 14px shift keeps ample clearance
     from the sub line's tail either way. */
  .stat-badge { width: 100px; right: 5px; }
}
/* Desktop: same sticker, larger, still anchored to the H1's bottom-right
   outer edge. No reserved column, no H1 padding, no flow participation. */
/* Desktop: the same sticker, larger. `right` stays tight enough that the
   rotated box never reaches the machine artwork — at 1024px the copy column
   ends at x506 and the nearest cabinet begins at x525. */
@media (min-width: 1024px) {
  .stat-badge {
    width: clamp(100px, 8vw, 112px);
    right: -12px;
  }
}
/* Preview-build QA chip only (LP_PREVIEW_CONTENT=true) - never renders in a
   production build. */
.preview-tag {
  display: inline-block;
  margin-left: .4em;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(58, 10, 74, .4);
  font-size: .58rem;
  font-weight: 800;
  letter-spacing: .02em;
  vertical-align: middle;
}
.hero__stat-note {
  margin-top: 6px;
  font-size: .64rem;
  line-height: 1.5;
  color: #b79ec9;
}
/* Forced-colours (e.g. Windows High Contrast): decorative gradient/shadow are
   stripped by the user agent anyway, so make the fallback intentional - a
   solid system-colour card that stays fully legible. */
@media (forced-colors: active) {
  .stat-badge { background: Canvas; border: 2px solid CanvasText; box-shadow: none; }
  .stat-badge__label,
  .stat-badge__number,
  .stat-badge__value,
  .stat-badge__suffix { color: CanvasText; text-shadow: none; }
}

/* --- Inline proof row ----------------------------------------------------
   The full-width spaces between the three approved items stay in the DOM so
   the line is byte-identical; font-size:0 collapses them. */
.hero__trust {
  margin-top: 12px;
  font-size: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 3px 13px;
}
.hero__trust span {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: .78rem;
  font-weight: 700;
  line-height: 1.5;
  color: #fff;
}
.hero__trust span::before {
  content: "";
  width: .44em;
  height: .76em;
  border-right: 2.5px solid #8dffb5;
  border-bottom: 2.5px solid #8dffb5;
  transform: rotate(45deg) translateY(-.1em);
  flex: none;
}

.hero__actions { margin-top: 18px; }
/* Phase 5.8D: the hero CTA gets the SAME 520px cap the lineup and final CTAs
   already use, from the width where `btn--block` stops being full-bleed
   (components.css). Without it the button stretched across the whole 820px
   copy measure at desktop, reading as a bar rather than a button — and the
   page's three in-section CTAs looked like three different components. */
@media (min-width: 600px) {
  .hero__actions { max-width: 470px; margin-inline: auto; }
}

/* --- Signup-bonus microcopy ---------------------------------------------
   States that a benefit exists. No amount, no spin count, no guarantee. The
   disclaimer link is a plain text link and must never read as a second CTA. */
.hero__bonus {
  margin-top: 12px;
  font-size: .92rem;
  font-weight: 900;
  letter-spacing: .02em;
  color: var(--color-pachi-yellow);
}
.hero__bonus::before {
  content: "";
  display: inline-block;
  width: .5em;
  height: .5em;
  margin-right: .5em;
  border-radius: 50%;
  background: var(--color-pachi-orange);
  vertical-align: .1em;
}
.hero__note {
  margin-top: 9px;
  font-size: .66rem;
  line-height: 1.55;
  color: #a98fbd;
}

/* --- Machine fan ---------------------------------------------------------
   Three separately positioned framed portrait panels. `contain` guarantees a
   cabinet and its title artwork are never cropped. Phase 5.1 cut the mobile
   stage to ~178px so the fan finishes inside the first viewport. */
/* Phase 5.8K — the hero's artwork band, in ONE place.
   `--stage-h` used to live on `.hero__stage`, which meant only the stage could
   read it. The hero guide is capped by that same band (5.8I's intent: she must
   never grow past the machine artwork into the copy), so both now read
   `--hero-band` from `.hero`. When the roller shortened the stage by
   --roller-fit, the guide followed automatically instead of keeping a stale
   `100vh - 568px` guess and drifting up over the CTA. */
.hero {
  --stage-h: 172px;
  --roller-fit: .8126;
  --hero-band: calc(var(--stage-h) * var(--roller-fit));
}
.hero__stage {
  position: relative;
  z-index: 2;
  height: var(--stage-h);
  margin-top: 20px;
  display: grid;
  align-items: end;
  justify-items: center;
}
.hero__halo {
  position: absolute;
  inset: -30% -8% -8%;
  background:
    radial-gradient(58% 60% at 50% 64%, rgba(143, 70, 255, .58), transparent 72%),
    radial-gradient(42% 46% at 18% 74%, rgba(255, 46, 136, .30), transparent 74%),
    radial-gradient(42% 46% at 82% 74%, rgba(35, 215, 255, .26), transparent 74%);
  pointer-events: none;
}
.hero__machine {
  --tilt: 0deg;
  position: absolute;
  bottom: 0;
  margin: 0;
  height: 88%;
  aspect-ratio: 0.64;
  transform: rotate(var(--tilt));
  overflow: hidden;
  border-radius: 11px;
  border: 2px solid rgba(255, 227, 61, .40);
  background: #1a0126;
  box-shadow: 0 12px 26px rgba(0, 0, 0, .55);
}
.hero__machine img { width: 100%; height: 100%; object-fit: contain; }
/* The centre machine is the stage's only in-flow child, so the grid centres it
   without a transform — leaving `transform` free for the tilt + reveal. */
.hero__machine--center {
  position: relative;
  height: 100%;
  z-index: 3;
  border-color: rgba(255, 227, 61, .66);
  box-shadow: 0 16px 32px rgba(0, 0, 0, .6), 0 0 24px rgba(255, 176, 34, .24);
}
/* 3%, not 2%: the +/-5deg tilt widens each cabinet's bounding box by ~2.3px,
   which at 2% put the outer two 2.3px past the shared content rail below
   1024px (Phase 5.8F). */
.hero__machine--left  { left: 3%;  --tilt: -5deg; z-index: 1; }
.hero__machine--right { right: 3%; --tilt: 5deg;  z-index: 1; }

/* =====================================================================
   PHASE 5.8J — the hero fan is a slow 18-machine roller.

   The three visible slots keep their EXISTING geometry rules above (and every
   per-breakpoint override below them), so the approved composition — smaller
   slanted machine left, larger upright machine centre, smaller slanted machine
   right — is byte-for-byte the same fan it was. Nothing here changes where a
   machine sits; it adds the two off-stage roles the rotation needs and the
   transition that carries an element from one role to the next.

   Phase 5.8K removed the pause/play control entirely and with it the
   hover-pause that made the rotation appear frozen. See docs/DESIGN-SYSTEM.md
   §28 for the measured cause and the accessibility consequence.

   ONLY `transform` and `opacity` are animated. main.js measures the slot boxes
   and writes a complete `translate3d(...) rotate(...) scale(...)` per element,
   so a machine changing position can never alter layout — the H1, the account
   badge, the CTA and the hero copy cannot move. See docs/DESIGN-SYSTEM.md §27.
   ===================================================================== */
:root {
  /* Must match heroMachines.json -> roller.transitionMs. */
  --hero-roll-ms: 1000ms;
  --hero-roll-ease: cubic-bezier(.33, .02, .28, 1);   /* no overshoot, no bounce */
}

/* In roller mode every slot is absolutely positioned, INCLUDING the centre.
   `left: 0; right: 0; margin-inline: auto` centres it without a transform —
   which matters twice: the base transform stays free for the tilt, and the
   stage stops depending on which element happens to be its in-flow child, so
   committing a role swap cannot reflow anything. */
.hero__stage--roller { position: relative; }
.hero__stage--roller .hero__machine--center {
  position: absolute;
  left: 0;
  right: 0;
  margin-inline: auto;
  height: 100%;
}
/* The two staging roles. Off-stage and transparent, so a machine always slides
   in from somewhere real rather than fading up out of nothing. */
.hero__stage--roller .hero__machine--in,
.hero__stage--roller .hero__machine--out {
  height: 88%;
  z-index: 0;
}
/* `!important` earns its place here: motion.css's reveal system sets
   `opacity: 1` on `.js [data-reveal="machine"].is-revealed`, which is a more
   specific selector than any stage-scoped class and would otherwise leave the
   two staging slots fully visible outside the fan. */
/* The attribute selector is what makes this WIN: motion.css's
   `.js [data-reveal="machine"].is-revealed { opacity: 1 }` is also !important
   and loads later, so equal-!important source order would hand it the
   declaration. Adding [data-pos] raises specificity above it, and the staging
   slots then stay invisible in every state — scripted, unscripted and under
   reduced motion. */
.hero__stage--roller .hero__machine--in[data-pos="in"],
.hero__stage--roller .hero__machine--out[data-pos="out"] { opacity: 0 !important; }
.hero__stage--roller .hero__machine--in  { left: 100%;  --tilt: 5deg; }
.hero__stage--roller .hero__machine--out { right: 100%; --tilt: -5deg; }

/* The transition itself. Applied only while the controller is running, so a
   first paint and every committed state are instant. */
.hero__stage--roller.is-rolling .hero__machine {
  transition: transform var(--hero-roll-ms) var(--hero-roll-ease),
              opacity var(--hero-roll-ms) linear;
}
/* Committing a role swap must never animate — the element's inline transform is
   cleared and its new role class applied in the same frame. */
.hero__stage--roller .hero__machine[data-commit] { transition: none !important; }

/* --- the roller frame fits the ARTWORK, not the old cutout ratio ---------
   Phase 5.8K. The original three hero machines are 0.64-ratio cabinet cutouts,
   so `.hero__machine` was built as a 0.64 frame. The roller's 18 approved
   provider images are 816x1036 (0.7876) — a *wider* picture in a *narrower*
   frame, which `object-fit: contain` resolved the only way it could: by
   letterboxing. Measured at 1440 that was 43.7px of `#1a0126`, 18.9% of the
   slot height, top and bottom of every machine. The images themselves are
   edge-to-edge artwork with no black border and no alpha padding (verified
   pixel-by-pixel on all 18 sources), so nothing needed cropping or retouching:
   the frame was simply the wrong shape.

   Giving the roller frame the artwork's own ratio removes the bars completely.
   The catch is that a taller-per-width frame would make every machine 23%
   WIDER, which would move the slot centres and break the composition 5.8J
   locked. So the stage — and the sub-600px vw cap that governs there — give
   back exactly the height the correct ratio no longer needs:

       --roller-fit = 0.64 / (816/1036) = 0.8126

   width = height x ratio, and both heights scale by the same factor, so every
   machine keeps its EXACT previous width and therefore its exact slot centre,
   while the fan gets ~19% of its height back. That reclaimed height is what
   lets the hero close up now that the pause control is gone. */
/* The STAGE keeps its full height; the machines inside it take the reduction.
   Compacting the hero was not asked for in this phase, and letting it shrink
   moved everything the 5.8I browser pass had measured — the next-section peek
   went past its ceiling at 414x896 and the hero guide, capped against the
   viewport rather than the band, ended up over the bonus note and the
   registration CTA. Scaling the machines instead of the stage keeps the hero's
   own geometry identical, and because `.hero__stage` is `align-items: end` the
   reclaimed height simply sits above the fan where the halo already is.

   Doing it here rather than with a `margin-top` give-back also avoids a
   specificity fight: `.hero__stage` has per-breakpoint `margin-top` values that
   would each have had to be rewritten. `calc(<percentage> * <number>)` is
   exactly what is needed and is resolved against the same containing block the
   plain percentage was. */
.hero__stage--roller .hero__machine {
  aspect-ratio: 816 / 1036;
  height: calc(88% * var(--roller-fit));
}
.hero__stage--roller .hero__machine--center { height: calc(100% * var(--roller-fit)); }
.hero__stage--roller .hero__machine--in,
.hero__stage--roller .hero__machine--out { height: calc(88% * var(--roller-fit)); }

/* Reduced motion: the roller never starts, so there is nothing to pause and no
   control is shown. The initial three-machine fan stays exactly as approved. */
@media (prefers-reduced-motion: reduce) {
  .hero__stage--roller.is-rolling .hero__machine { transition: none !important; }
}
/* Phase 5.8D — bound the fan's SCALE below 600px.
   A cabinet's width is its height x 0.64, and its height is a percentage of the
   stage, whose height tracks the viewport height (the peek formula below). That
   meant the same three machines rendered 68px wide at 320x568 and 250px wide at
   414x896 — a 3.6x swing, with the wide end overlapping so heavily that only
   the centre cabinet was really visible. Capping the height against the
   viewport WIDTH keeps the fan's proportions constant instead: the cap is
   ~46% of the stage width for the centre cabinet, so three of them always
   compose the same way. The stage's own height is untouched, so the
   next-section peek is unaffected by construction. */
@media (max-width: 599px) {
  .hero__machine { max-height: calc(63vw - 24px); }
  .hero__machine--center { max-height: calc(72vw - 27px); }
  /* Below 600px this cap, not the stage, decides the machine size — so it takes
     the same --roller-fit reduction, otherwise the corrected frame ratio would
     widen the fan by 23% exactly where there is least room for it. */
  .hero__stage--roller .hero__machine { max-height: calc((63vw - 24px) * var(--roller-fit)); }
  .hero__stage--roller .hero__machine--center { max-height: calc((72vw - 27px) * var(--roller-fit)); }
}

/* Phase 5.8: the credibility badge moved beside the H1 (a compact overlay,
   not a stacked row — see .stat-badge above), so it costs far less vertical
   space than the Phase 5.7 three-row-then-one-row panels did; Phase 5.8A
   shrank it again (104-120px mobile). This compaction keeps the whole
   composition inside a 390x844 viewport with the next section peeking under
   the fold. Browsers without :has() simply scroll a little further; nothing
   breaks. */
@media (max-width: 1023px) {
  /* Phase 5.6: tightened further so more of the next section peeks under
     the fold at 360-414px (target ~24-72px) — reduced spacing throughout,
     never a page-level zoom/transform, and every element still keeps a
     sane minimum gap. */
  .hero:has(.stat-badge) .hero__title   { margin-top: 11px; }
  .hero:has(.stat-badge) .hero__sub     { margin-top: 8px; }
  .hero:has(.stat-badge) .hero__trust   { margin-top: 10px; }
  .hero:has(.stat-badge) .hero__actions { margin-top: 16px; }
  .hero:has(.stat-badge) .hero__bonus   { margin-top: 10px; }
  .hero:has(.stat-badge) .hero__note    { margin-top: 7px; }
  .hero:has(.stat-badge) .hero__stage   { margin-top: 14px; }
}
/* Shorter phones (360x740, 320x720) have less height to spend than a 390x844
   iPhone, so the fan gives some back rather than running past the fold. */
@media (max-width: 389px) {
  .hero { --stage-h: 108px; }
  .hero:has(.stat-badge) { --stage-h: 100px; }
}
@media (max-width: 359px) {
  .hero { padding-top: 14px; padding-bottom: 22px; }
  .hero { --stage-h: 101px; }
  .hero__stage { margin-top: 7px; }
  .hero:has(.stat-badge) { --stage-h: 84px; }
  .hero__title { font-size: 1.5rem; }
}
/* 360-599px: the 24-72px peek target is fundamentally a viewport-HEIGHT
   relationship — a taller screen naturally shows more of the next section
   unless the hero grows to match — so the stage height here tracks 100vh
   directly instead of stepping at fixed breakpoints (a fixed px value that
   hit the target at 390x844 measured ~130-190px over target at 414x896,
   simply because that viewport is taller). Formula fitted empirically
   against chrome-headless-shell at 360x800/390x844/414x896: stage height =
   viewport height - 595px, clamped to a sane range. Placed after the
   max-width:389px block (same selector, same specificity) so it wins for
   360-389px too, replacing that block's static value with the more accurate
   continuous one; 320-359px is untouched — the spec does not require
   next-section visibility there.

   Phase 5.7: the constant moved from -600 to -595 (+5px of stage height) to
   re-fit the same 24-72px target after the compact stamp badge (shorter
   than the Phase 5.5 three-line panel it replaced) shrank the hero by a few
   px at every mobile width.

   Phase 5.8 moved the constant to -670 (-75px of stage height) to offset the
   hero-bottom machine parade it introduced (~90-110px including its own
   margin/padding). Phase 5.8A removed that parade from the site entirely and
   shrank the badge, landing on -555.

   Phase 5.8B: -535, ceiling 360px, after floating the medal took ~20px off
   the hero.

   Phase 5.8C: -493, ceiling 390px. Taking the medal OUT OF FLOW entirely
   (`position: absolute`, so the headline no longer wraps around it) removed
   the last of the height it used to contribute, pushing the peek to 78-92px.
   The stage absorbs that; the higher ceiling is again what fixes 414x896,
   where the formula was pinned at the old cap. Re-measured against
   chrome-headless-shell — see the browser pass for the asserted values. */
@media (min-width: 360px) and (max-width: 599px) {
  .hero:has(.stat-badge) { --stage-h: clamp(100px, calc(100vh - 545px), 300px); }
}
/* Tablet: cap the stage width, otherwise the three cabinets drift apart into
   separate pictures instead of holding together as one fan. */
@media (min-width: 600px) and (max-width: 1023px) {
  .hero { padding: var(--section-pad-y-compact) 0 var(--section-pad-y-compact); }
  .hero { --stage-h: clamp(190px, 24.5vw, 236px); }
  .hero__stage { max-width: 560px; margin-inline: auto; width: 100%; }
}

/* Desktop: the SAME centred stacked narrative as mobile, just wider.
   Phase 5.8D removed the two-column split (left copy / right machine fan)
   that used to take over at 1024px. That split made desktop a different
   composition from mobile — a separate app-style layout rather than the same
   Japanese campaign page at a larger size — and it broke the reading order:
   the machine artwork sat BESIDE the message instead of following it.

   The order is now identical at 320px and 1440px: eyebrow -> H1 -> sub ->
   proof -> CTA -> bonus/note -> machine fan. Only the widths and the type
   scale grow. Nothing here uses `zoom` or a page-level transform; the
   "zoomed-out" balance comes from the shared content widths and type scale. */
@media (min-width: 1024px) {
  .hero { padding: var(--section-pad-y-compact) 0 var(--section-pad-y-compact); }
  /* Explicitly NOT a grid: the copy and the stage stay in normal flow, in
     source order, both centred. */
  .hero__inner { display: block; }
  .hero__copy {
    max-width: var(--content-copy);
    margin-inline: auto;
  }
  /* The fan follows the copy and grows into the page's wide edge instead of
     being confined to a right-hand column. */
  .hero { --stage-h: clamp(250px, 26vw, 340px); }
  .hero__stage {
    width: 100%;
    max-width: 760px;
    /* Sized so the whole hero — message, CTA and substantial machine artwork —
       still finishes inside a 900px-tall desktop viewport with a real
       next-section peek. Measured, see the browser pass. */
    margin-inline: auto;
    margin-top: var(--content-gap);
  }
  .hero__machine { height: 86%; }
  .hero__machine--center { height: 100%; }
  /* Roller counterparts of the two rules above — same --roller-fit reduction,
     so the corrected frame keeps the machines' widths and the desktop fan's
     slot centres unchanged. */
  .hero__stage--roller .hero__machine { height: calc(86% * var(--roller-fit)); }
  .hero__stage--roller .hero__machine--center { height: calc(100% * var(--roller-fit)); }
  .hero__machine--left  { left: 4%; }
  .hero__machine--right { right: 4%; }
}
@media (min-width: 1200px) {
  /* Wider but not taller: the fan spreads across 840px rather than growing
     vertically, which is what keeps the whole hero inside a 900px viewport. */
  .hero { --stage-h: clamp(212px, 16.2vw, 231px); }
  .hero__stage { max-width: 840px; }
  /* A wider stage can afford a little more spread between the cabinets. */
  .hero__machine--left  { left: 2%; }
  .hero__machine--right { right: 2%; }
}

/* ---------------------------------------------------------------------
   GAMEPLAY SHOWCASE (Phase 5.1)

   Replaces the standalone benefit band. Media on the left/top, the approved
   benefit sentences beside it — so the first thing a visitor sees is what the
   product actually looks like, with the claims attached to it rather than
   floating in a separate strip.
   --------------------------------------------------------------------- */
.gameplay {
  position: relative;
  overflow: hidden;
  /* Phase 5.8H: was 28/32. Now a shared density step. */
  padding: var(--section-pad-y-tight) 0 calc(var(--section-pad-y-tight) + 4px);
  background:
    radial-gradient(88% 58% at 50% 0%, rgba(24, 207, 250, .42), transparent 66%),
    radial-gradient(70% 44% at 88% 100%, rgba(255, 227, 61, .16), transparent 64%),
    linear-gradient(176deg, var(--digital-blue) 0%, #1747c9 46%, #123a9e 100%);
}
.gameplay__inner { position: relative; z-index: 2; }

/* Wraps the stage (and only the stage) so the guide character's bottom:0
   anchor tracks the stage's box, never the taller benefit grid below it —
   see the Phase 5.4 comment in gameplay.njk. */
.gameplay__media { position: relative; }
/* Phase 5.8K: the guide no longer reserves a lane.
   5.8I solved "the proportional mobile guide covers the machine demo" by giving
   `.gameplay__media` a `padding-right` the width of the character. That worked
   visually but it made a decoration decide where the primary content sits: the
   stage centred inside the *remaining* box, so it rendered 84.8px left of the
   rail centre at 390px (75.6px at 320px) and the section read as lopsided.

   Content geometry now ignores her completely — the stage is centred on the
   full rail at every width — and she is a pure overlay: anchored past the rail
   into the page gutter so she uses the widest empty space available, and
   stacked BEHIND the stage so the part of her that reaches it is occluded
   rather than covering the machine. She stands behind the cabinet instead of
   in front of it, which is also the more natural reading of the scene. */

/* The stage height is fixed in CSS and the media carries width/height, so the
   box is reserved before the video or image loads — no layout shift. */
.gameplay__stage {
  max-width: 260px;
  margin: 0 auto;
  /* Above the gameplay guide (z-index 1 on mobile), so she reads as standing
     behind the cabinet and can never cover the machine view. */
  position: relative;
  z-index: 2;
  border-radius: 16px;
  overflow: hidden;
  border: 2px solid rgba(255, 227, 61, .34);
  background: #1a0126;
  box-shadow: 0 14px 32px rgba(12, 0, 22, .55);
  /* Phase 5.8F: the stage now matches the SOURCE's own ratio (480x699), so a
     `contain` fit fills it exactly — no crop and no letterbox. It used to be
     9/16, which is narrower than the source, so `cover` was silently cutting
     the left and right off the machine. */
  aspect-ratio: 480 / 699;
}
/* Below 768px the stage takes an explicit cap instead of inheriting the 260px
   base. Until Phase 5.8K the character's `padding-right` lane happened to
   squeeze it to ~183px at 390; with the lane gone the stage would jump to the
   full 260px and swallow the background band she stands in. 200px keeps the
   machine slightly LARGER than it has been while leaving her a real place to
   stand — and, unlike the lane, it is symmetric, so the stage is centred. */
@media (max-width: 767px) {
  .gameplay__stage { max-width: 200px; }
}
/* Phase 5.8F: `contain`, so the complete machine view is always visible. With
   the stage set to the source's own ratio this is a pixel-exact fill. */
.gameplay__video,
.gameplay__still {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}

/* Phase 5.4: a compact, energetic grid — not a stacked sentence list.
   Phase 5.8B: `auto-fit` + a 128px floor replaces the hard `repeat(2, ...)`,
   so the same one declaration gives a compact 2x2 on a phone and a single
   four-across row wherever there is room (768px up, and the desktop layout's
   own right-hand column), with no per-breakpoint column count to maintain.
   `grid-auto-rows: 1fr` is what keeps all four tiles the SAME height whether
   they land on one row or two. */
.gameplay__points {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  grid-auto-rows: 1fr;
  justify-content: center;
  gap: 6px;
  margin-top: 10px;
}
/* Centred, so the oversized keyword is the tile's own axis rather than a
   left-aligned line with a ragged gap beside it. */
.gameplay__point {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 4px;
  padding: 7px 7px 8px;
  border-radius: 12px;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .14);
  text-align: center;
}
/* A thin angled illuminated trim along the top edge — subtle, not a full
   frame, so four of them read as one coherent component. */
.gameplay__point::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: linear-gradient(90deg, transparent, var(--tile-tone, var(--pachi-gold)), transparent);
  opacity: .85;
}
.gameplay__point--gold   { --tile-tone: var(--pachi-gold); }
.gameplay__point--cyan   { --tile-tone: var(--pachi-cyan); }
.gameplay__point--pink   { --tile-tone: var(--pachi-magenta); }
.gameplay__point--white  { --tile-tone: #fff; }
/* The icon SUPPORTS the keyword — it is deliberately small enough that it can
   never compete with it for the tile's attention. */
.gameplay__point-icon {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .12);
  color: var(--tile-tone, #fff);
  flex: none;
}
.gameplay__point-icon .icon { width: 15px; height: 15px; }
.gameplay__point-copy { line-height: 1.3; }
/* Small contextual line above the keyword. Light blue-white, not the old
   lavender — this section's background is blue. */
.gameplay__lead {
  display: block;
  margin-bottom: 1px;
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: #d7e7ff;
}
/* The keyword is the tile's primary visual (Phase 5.8B: roughly 45% larger
   than Phase 5.4's, so it is scannable at arm's length).

   CONTRAST: it used to inherit the tile's own decorative `--tile-tone`, which
   put magenta text (#ec2b91) on this section's blue — about 2.2:1, well under
   AA even for large text, and the `サクサク` tile was the worst case. Every
   keyword is now the same bright pachinko yellow with a deep-plum edge:
   ~8:1 on the section's mid-blue, and legibility comes from the FILL, never
   from the glow. The per-tile tone survives on the icon chip and the top
   accent bar, where it is decoration rather than text — so the tiles keep
   their variety and none of them carries meaning in colour alone (each
   sentence is fully spelled out either way). scripts/verify.mjs measures the
   rendered ratio in the browser pass rather than trusting this comment. */
.gameplay__impact {
  display: block;
  font-family: 'Dela Gothic One', var(--font-jp);
  font-size: clamp(1.45rem, 6.4vw, 1.85rem);
  font-weight: 400;
  line-height: 1.06;
  letter-spacing: .005em;
  color: var(--pachi-headline-yellow);
  -webkit-text-fill-color: var(--pachi-headline-yellow);
  text-shadow: 0 2px 0 rgba(20, 0, 44, .72);
}
.gameplay__trail {
  display: block;
  margin-top: 3px;
  font-size: .74rem;
  font-weight: 700;
  line-height: 1.45;
  color: #f2e8ff;
}

@media (min-width: 768px) {
  .gameplay { padding: var(--section-pad-y-mid) 0 calc(var(--section-pad-y-mid) + 5px); }
  .gameplay__points { gap: 10px; }
  .gameplay__point { padding: 10px 9px 10px; gap: 5px; }
  .gameplay__point-icon { width: 28px; height: 28px; }
  .gameplay__point-icon .icon { width: 17px; height: 17px; }
  .gameplay__impact { font-size: clamp(1.55rem, 2.6vw, 1.95rem); }
  .gameplay__trail { font-size: .78rem; }
  .gameplay__stage { max-width: 199px; }
}
/* Phase 5.8E: the MOBILE composition is canonical at every width.
   Through 5.8D this section became a desktop-specific horizontal scene from
   1024px — gameplay media in a left column, the guide standing in the middle,
   and the heading plus all four benefit tiles stacked in a right column. That
   is a different story order from the phone (heading -> callout -> media ->
   benefits), and it put the media and the benefit grid in competing columns.

   The grid is gone. Desktop keeps the same single-flow, centred order and only
   grows: the media wrapper and the stage each take a capped centred width so
   the composition scales instead of stretching, and the benefit tiles stay
   BELOW the media rather than beside it. */
@media (min-width: 1024px) {
  /* Explicitly single-flow — no columns, no reordering. */
  .gameplay__inner { display: block; }
  /* Phase 5.8F desktop scale. The stage was 400x711 at 1024 and 440x782 at
     1440 — it consumed most of a 900px viewport and the image inside was being
     upscaled past its 480px source. The cap is now on the STAGE (and therefore
     on the real image box), sized so the rendered height lands in the intended
     300-350px band at 1024. The media wrapper is tightened to match, which
     also opens a clean lane for the guide beside the machine instead of over
     its controls. */
  .gameplay__media {
    max-width: 344px;
    margin-inline: auto;
  }
  .gameplay__stage { max-width: 182px; margin-inline: auto; }
  /* The four tiles follow the media and sit inside the shared copy measure so
     they read as one row under the visual, never as a side column. */
  .gameplay__points {
    max-width: var(--content-copy);
    margin-inline: auto;
    margin-top: var(--content-gap);
  }
}
@media (min-width: 1280px) {
  .gameplay__media { max-width: 372px; }
  .gameplay__stage { max-width: 199px; }
}

/* ---------------------------------------------------------------------
   E. MACHINE LINEUP — Phase 5.8: a clean heading -> tabs -> grid -> CTA
   (the Phase 5.7 marquee "showcase" was moved to the hero in 5.8 and then
   removed from the site entirely in 5.8A, so this section no longer needs a
   stage to visually attach to). Still one bright violet/magenta/cyan/gold
   environment, not the earlier near-black plum.
   --------------------------------------------------------------------- */
.lineup {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(85% 46% at 12% 0%, rgba(236, 43, 145, .38), transparent 55%),
    radial-gradient(65% 50% at 88% 12%, rgba(34, 224, 255, .22), transparent 60%),
    linear-gradient(180deg, var(--lineup-violet-bright) 0%, var(--lineup-violet) 55%, #4a1690 100%);
  color: #fff;
}
/* Marquee bulb rail across the top of the show floor. */
.lineup::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 8px;
  background-image: radial-gradient(circle at 10px 4px, var(--lineup-gold) 0 2.6px, rgba(255, 159, 28, .35) 3px 4.4px, transparent 4.8px);
  background-size: 26px 8px;
  pointer-events: none;
}
.lineup .container { position: relative; z-index: 2; }
.lineup .section-head__sub { color: #ede2fb; }
.lineup .section-head__lead { color: var(--lineup-gold); }

/* A short gold->magenta light rail under the heading (Phase 5.7). Real text
   stays real text — this is a decorative underline, not part of the H2. */
.lineup__title-rail {
  display: block;
  width: 88px;
  height: 4px;
  margin: 10px auto 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--lineup-gold), var(--lineup-magenta));
  box-shadow: 0 0 10px rgba(255, 199, 44, .55);
}

/* --- Tabs ------------------------------------------------------------------
   The tablist is BUILT BY JAVASCRIPT (see main.js → initLineupTabs), so these
   rules only ever apply once the enhancement has run. Without JS the page
   shows both group headings and both grids, and none of this matters.
   Phase 5.8: sits directly above the grid as its own component — no stage
   above it to visually attach to any more, so a plain rounded tab. */
.lineup__tablist {
  display: flex;
  gap: 8px;
  margin: 20px 0 16px;
  justify-content: center;
  flex-wrap: wrap;
}
.lineup__tab {
  min-height: 44px;
  padding: 9px 22px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, .22);
  background: #fff;
  color: var(--text-plum);
  font-size: .88rem;
  font-weight: 900;
  letter-spacing: .02em;
  cursor: pointer;
  box-shadow: 0 3px 0 rgba(28, 16, 36, .18);
  transition: background-color .18s var(--motion-ease-out),
              border-color .18s var(--motion-ease-out),
              color .18s var(--motion-ease-out);
}
.lineup__tab:hover { background: #fff4e8; }
/* Selected state is carried by aria-selected, so the styling and the
   accessibility semantics can never drift apart. Coral/hot-pink active,
   white/pale-lilac inactive — no colour-only distinction, the label text
   (人気機種/新着機種) always carries the meaning too. */
.lineup__tab[aria-selected="true"] {
  background: linear-gradient(180deg, var(--pachi-red-hot), #cf1440);
  border-color: rgba(243, 40, 84, .9);
  color: #fff;
  box-shadow: 0 4px 0 rgba(120, 8, 38, .55);
}
.lineup__tab:last-child[aria-selected="true"] {
  background: linear-gradient(180deg, var(--digital-cyan), var(--digital-blue));
  border-color: rgba(34, 87, 230, .9);
  box-shadow: 0 4px 0 rgba(18, 58, 158, .5);
}
/* Once tabbed, the heading is redundant on screen — its label is on the tab —
   but it stays in the DOM as the panel's accessible name. */
.is-tabbed .lineup__group--tabbed {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.lineup__group-wrap[hidden] { display: none; }
.lineup__group-wrap { padding: 0 14px; }

.lineup__group {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 22px 0 10px;
  font-size: .9rem;
  font-weight: 900;
  color: #f0e6f8;
}
.lineup__group:first-of-type { margin-top: 4px; }
.lineup__group-caption { min-width: 0; }
.lineup__group-badge {
  flex: none;
  padding: 3px 9px;
  border-radius: 5px;
  font-size: .7rem;
  font-weight: 900;
  line-height: 1.4;
  color: #fff;
  box-shadow: 0 2px 0 rgba(0, 0, 0, .45);
}
.lineup__group-badge--new { background: linear-gradient(180deg, #3ee3ff, #0090d0); color: #05202b; }
.lineup__group-badge--hot { background: linear-gradient(180deg, #ff5b8f, #d5003f); }
.lineup__group-badge--std { background: linear-gradient(180deg, #ffe08a, #e0a000); color: #3a2200; }

/* Phase 5.8B — ONE declaration that handles every width and any number of
   machines. `minmax(136px, 250px)` is the whole mechanism:

     · the 136px floor is what yields 2 compact columns at 320-479px
       (2 x 136 + 10 gap = 282, inside a 288px container at 320px) and refuses
       to fit a third there;
     · the 250px CEILING is what stops three machines from being stretched
       into oversized cards — at 1024px+ a three-machine tab renders three
       250px cards, centred by `justify-content`, instead of three 300px+
       ones spanning the whole 1200px section;
     · `auto-fit` means a fourth, fifth or tenth entry added to machines.json
       simply fills the row and then wraps onto the next one. No template
       edit, no CSS edit, no new breakpoint.

   Measured column counts: 2 at 320-479, 3 at 480-767, 3 at 768-1099 (~230px
   cards), 3 at 1100px+ (250px cards). scripts/verify.mjs asserts both the
   counts and the card width, and additionally re-renders with a synthetic
   7-machine group to prove the wrap is data-driven. */
.lineup__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  justify-content: center;
  align-items: stretch;
  gap: 10px;
}
/* 480px+: three columns, with the GRID capping its own width so the cards stay
   compact instead of stretching with the 1200px section. 760px = 3 x ~244 + 2
   gaps, which is what keeps a three-machine tab inside the 230-250px card
   target at 768-1099px instead of rendering three 300px+ cards. */
@media (min-width: 480px) {
  .lineup__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    max-width: 760px;
    margin-inline: auto;
  }
}
@media (min-width: 768px) { .lineup__grid { gap: var(--card-gap); } }
/* 1100px+: fixed 250px tracks, centred.
   Phase 6.6 pinned this to THREE. `auto-fit` used to be right: with three
   machines per tab it simply centred them, and a fourth entry would start
   filling the row. Now each tab holds five or six, and auto-fit counts four
   tracks at 1440 — so six cards came out as 4 + 2, with a ragged short second
   row, instead of the two clean rows of three the section is designed around.
   A fixed three keeps every desktop width on the same 3-up rhythm as the
   768-1099 band, and a seventh machine wraps onto a third row rather than
   changing the column count under the reader. */
@media (min-width: 1100px) {
  .lineup__grid {
    grid-template-columns: repeat(3, 250px);
    max-width: none;
    gap: 16px;
  }
}
/* The <li> stretches to the grid row's height and the card fills it, so every
   card in a row is exactly as tall as the tallest — at any column count. */
.lineup__item { min-width: 0; display: grid; }
.lineup__item .machine-card { height: 100%; }

/* Phase 5.8: the media stage now matches the 816x1036 master canvas exactly
   (see build-images.mjs LINEUP_CANVAS / machines.json canvasWidth/Height),
   so the column's own width drives the height proportionally — equal card
   heights and identical image-stage ratios fall out of CSS Grid row-sizing
   for free, at any column count, with no per-breakpoint height to tune. */
.machine-card__media {
  /* Phase 5.8H density pass: 816/1036 -> 816/940 (-9%). */
  /* Phase 5.8I: 816/900 -> 816/800 (-11%). */
  aspect-ratio: 816 / 690;
  display: grid;
  place-items: center;
  padding: 6px;
  background: linear-gradient(180deg, rgba(255, 255, 255, .06), transparent 70%), #1a0126;
}
.machine-card__media img { width: 100%; height: 100%; object-fit: contain; object-position: center; }

.machine-card__body { padding: 8px 10px 10px; }
.machine-card__cat { margin-bottom: 3px; font-size: .62rem; padding: 1px 7px; }
.machine-card__title {
  font-size: .8rem;
  line-height: 1.4;
  min-height: 2.8em;
  -webkit-line-clamp: 2;
}
@media (min-width: 768px) { .machine-card__title { font-size: .88rem; } }
.machine-card__badge { top: 6px; left: 6px; padding: 3px 8px; font-size: .66rem; }
.machine-card__corner { width: 40px; height: 40px; }

.lineup__actions { margin-top: 24px; text-align: center; }
/* Cap the CTA so it reads as a centred call-to-action, not a bar spanning the
   whole 1200px section — same pattern already proven on `.final-cta__action`. */
@media (min-width: 768px) {
  .lineup__actions { max-width: 470px; margin-inline: auto; }
}

/* ---------------------------------------------------------------------
   F. HOW TO START — numbered campaign sequence
   --------------------------------------------------------------------- */
.howto {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(80% 50% at 50% 0%, rgba(255, 255, 255, .5), transparent 62%),
    linear-gradient(168deg, var(--pachi-yellow) 0%, #ffc42a 52%, var(--pachi-orange) 100%);
  color: var(--text-dark);
}

/* ---------------------------------------------------------------------
   G. 選ばれる理由
   --------------------------------------------------------------------- */
.features {
  position: relative;
  background: linear-gradient(180deg, var(--surface-lavender) 0%, var(--surface-lilac) 100%);
  color: var(--text-dark);
}
/* --- Phase 5.8C: visual-first reason cards -------------------------------
   The section used to be a wall of text: a small title plus two or three
   bullet sentences per card, competing with the screenshot, with a red
   ::first-letter accent on every bullet. It read slowly and the red-on-light
   accent added noise without adding meaning.

   Each card now renders EXACTLY two strings — one large impact phrase and one
   short support line (content.json -> features.cards[].impact/.support) — under
   the screenshot, which leads because it is the fastest thing to read. Target
   is a 3-5 second scan of all six.

   Layout: one compact HORIZONTAL card per row below 768px (picture ~42%, copy
   ~58%), two columns on tablet, three columns x two rows from 1024px. Equal
   heights everywhere via `grid-auto-rows: 1fr` plus `height: 100%` on the card
   itself, so a longer support line can never make one card taller than its
   neighbours. */
.features__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-auto-rows: 1fr;
  gap: var(--card-gap);
}
/* Tone is a decorative accent only. Phase 5.8C removed it from every TEXT
   colour: several tones (gold, cyan) have no reliable contrast as text on this
   card's light background, so tone now lives solely in the icon and the top
   accent line. */
.feature-card--gold   { --tone: var(--pachi-gold); }
.feature-card--cyan   { --tone: var(--pachi-cyan); }
.feature-card--pink   { --tone: var(--pachi-magenta); }
.feature-card--blue   { --tone: var(--pachi-blue); }
.feature-card--red    { --tone: var(--pachi-red); }
.feature-card--violet { --tone: var(--campaign-violet); }

/* Mobile: one horizontal card per row. `46%` of the card's CONTENT box lands
   the picture at ~42% of the card's outer width (the spec's 40-44% visual
   share) once padding and the column gap are taken out, while leaving the copy
   column wide enough that the impact phrase never wraps to three lines at
   320px — which is what makes the six cards scannable rather than a tall
   stack. */
.feature-card {
  display: grid;
  grid-template-columns: 46% minmax(0, 1fr);
  align-items: center;
  /* Phase 5.8H density pass: was gap 11 / padding 11 13 11 11. */
  gap: 8px;
  height: 100%;
  padding: 7px 9px 7px 7px;
  border-radius: var(--radius-md);
  background: var(--card-bg);
  /* Purple frame + one restrained tone-coloured accent line along the top. No
     gradient inside the card. */
  border: 1px solid rgba(83, 24, 128, .20);
  border-top: 4px solid var(--tone, var(--campaign-violet));
  box-shadow: var(--shadow-card);
}
.feature-card__body { min-width: 0; }
/* The icon supports the phrase and must never compete with it. */
.feature-card__kicker { display: flex; align-items: center; margin-bottom: 3px; }
.feature-card__icon {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--tone, var(--campaign-violet)) 20%, #fff);
  color: var(--text-plum);
}
.feature-card__icon .icon { width: 14px; height: 14px; }
/* The impact phrase is the card's primary visual. ONE fixed dark plum for all
   six (16.9:1 on the white card) — never the per-card tone, which is why the
   old red first-letter accent is gone too. */
.feature-card__impact {
  /* Phase 5.8D scale correction: 5.8C set this at 19-23px mobile / 24-30px
     desktop, which made a card's phrase compete with the section heading and
     dwarf the screenshot beside it. Retuned into the page-wide hierarchy —
     strong, but clearly below a section heading. */
  font-size: clamp(1.08rem, 4.6vw, 1.2rem);
  font-weight: 900;
  line-height: 1.3;
  letter-spacing: .005em;
  color: var(--text-plum);
  text-wrap: balance;
}
.feature-card__support {
  margin-top: 4px;
  font-size: clamp(.85rem, 3.3vw, .94rem);
  font-weight: 600;
  line-height: 1.5;
  color: var(--card-text-muted);
}

/* --- Reason media stage --------------------------------------------------
   ONE stage shared by all six cards: identical height, radius, border and
   backdrop, so the grid reads as a single component instead of six variants.
   Only the fit mode and the focal point vary, and both are data — no card is
   named in this stylesheet. */
.feature-card__media {
  /* The stage takes its height from its own width at the SOURCES' median
     aspect ratio (~1.77 across the six, which run 0.69 to 4.17). That is what
     keeps `contain` honest: the two widest screenshots fill the stage almost
     exactly, and the tall ones letterbox by a small absolute amount instead of
     leaving the large empty areas a squarer stage produced. */
  /* Phase 5.8H density pass: 16/9 -> 16/8.3 (-8% stage height). Still
     `contain`, so the six screenshots letterbox slightly more rather than
     being cropped, and the lightbox still shows every one full size. */
  aspect-ratio: 16 / 7.3;
  align-self: center;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--card-border);
  background: #1a0126;
}
/* Phase 5.8C: `contain` is the DEFAULT now, not the exception. The six sources
   run from 0.69 to 4.17 in aspect ratio, so the previous `cover` + per-card
   focal point cropped real product UI out of the frame to fill the stage. The
   stage keeps its neutral dark plum backdrop, so the letterboxing a 4.17-ratio
   source needs reads as a deliberate frame — the same pattern the testimonial
   carousel already uses for its 1.37-2.94 spread. Nothing is ever cropped or
   stretched. */
.feature-card__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}
/* content.json → media.fit. `cover` remains available for a future source that
   genuinely wants edge-to-edge framing. */
.feature-card__media[data-fit="cover"] img { object-fit: cover; }
/* --- Phase 5.8E: the media stage is a zoom trigger ----------------------
   The six sources run 0.69 to 4.17 in aspect ratio, so the card shows them
   `contain`-fitted in one shared stage and the full view lives in a dialog.
   The stage is a <button>, which is what makes that work by tap and by
   keyboard rather than on hover only. Every button default is reset here so
   the card looks exactly as it did before it became interactive. */
/* --- Phase 5.8I: THE shared image-zoom system --------------------------
   One trigger presentation and one dialog for every zoomable image on the
   page. Replaces `.feature-card__zoom*` (which carried a large expanding
   画像を拡大 text chip) and `.voice-shot__zoom` / `.voice-dialog`. The
   ユーザーの声 presentation is the source of truth: a small magnifier disc in
   the bottom-right corner, no text overlay, so the thumbnail stays the content.

   Any section opts in with `class="zoom-trigger" data-zoom-open` — see
   components/zoom-dialog.njk. */
.zoom-trigger {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: zoom-in;
  -webkit-appearance: none;
  appearance: none;
}
.zoom-trigger:focus-visible { outline: var(--focus-ring); outline-offset: 3px; }

/* The affordance: one magnifier drawn in CSS — no new asset, and identical in
   both sections. Always visible, because touch devices get no hover. */
.zoom-trigger__icon {
  position: absolute;
  right: 8px;
  bottom: 8px;
  z-index: 2;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(26, 1, 38, .74);
  border: 1px solid rgba(255, 255, 255, .3);
  transition: background-color .16s var(--motion-ease-out);
  pointer-events: none;
}
.zoom-trigger:hover .zoom-trigger__icon,
.zoom-trigger:focus-visible .zoom-trigger__icon { background: rgba(60, 8, 84, .95); }
/* Lens + handle. */
.zoom-trigger__icon::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 5px;
  width: 9px;
  height: 9px;
  border: 2px solid #fff;
  border-radius: 50%;
}
.zoom-trigger__icon::after {
  content: "";
  position: absolute;
  right: 5px;
  bottom: 5px;
  width: 6px;
  height: 2px;
  border-radius: 1px;
  background: #fff;
  transform: rotate(45deg);
}

/* The one dialog. `display: flex` only while open, so the closed dialog stays
   display:none and its image is never fetched. */
.zoom-dialog {
  width: 100vw;
  max-width: 100vw;
  height: 100dvh;
  max-height: 100dvh;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: hidden;
}
.zoom-dialog[open] {
  display: flex;
  align-items: center;
  justify-content: center;
}
.zoom-dialog::backdrop { background: rgba(8, 6, 20, .86); }
.zoom-dialog__inner { position: relative; max-width: 96vw; max-height: 88dvh; }
/* NO UPSCALING: `width`/`height` are auto under viewport caps, and main.js
   writes the source's real intrinsic size onto the element. A 620px capture
   therefore renders at 620px; only an image larger than the cap shrinks. */
.zoom-dialog img {
  display: block;
  max-width: 96vw;
  max-height: 88dvh;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 10px;
  background: #1a0126;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .6);
}
/* Close control: just outside the image's top-right on roomy screens, pinned
   inside the safe area at 320px, so it is always reachable. */
.zoom-dialog__close {
  position: absolute;
  top: -46px;
  right: 0;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 44px;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, .34);
  background: rgba(26, 1, 38, .92);
  color: #fff;
  font-size: .84rem;
  font-weight: 800;
  cursor: pointer;
}
.zoom-dialog__close:hover { background: rgba(60, 8, 84, .96); }
.zoom-dialog__close:focus-visible { outline: var(--focus-ring); outline-offset: 3px; }
.zoom-dialog__close-x {
  position: relative;
  width: 13px;
  height: 13px;
  flex: none;
}
.zoom-dialog__close-x::before,
.zoom-dialog__close-x::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
}
.zoom-dialog__close-x::before { transform: rotate(45deg); }
.zoom-dialog__close-x::after  { transform: rotate(-45deg); }
/* Below 480px there is no room above the image for the button, so it moves
   inside the top-right corner and the image gives it clearance. */
@media (max-width: 479px) {
  .zoom-dialog__inner { max-height: 80dvh; }
  .zoom-dialog img { max-height: 80dvh; }
  .zoom-dialog__close { top: 8px; right: 8px; }
}
/* Reduced motion: no zoom or fade — the dialog simply appears. */
@media (prefers-reduced-motion: reduce) {
  .zoom-dialog,
  .zoom-dialog__inner,
  .zoom-dialog img,
  .zoom-trigger__icon {
    transition: none !important;
    animation: none !important;
  }
}

/* content.json → media.focus: which part survives a cover crop. Vertical for
   tall sources, horizontal for panoramic ones. */
.feature-card__media[data-focus="top"] img    { object-position: top; }
.feature-card__media[data-focus="bottom"] img { object-position: bottom; }
.feature-card__media[data-focus="left"] img   { object-position: left center; }
.feature-card__media[data-focus="right"] img  { object-position: right center; }

/* Tablet: two columns, and the card turns VERTICAL (picture on top) so each
   half gets its full width instead of two cramped columns inside a narrow
   card. Restored in Phase 5.8I — the zoom-CSS consolidation had removed this
   whole responsive tail with it, which collapsed the reason grid to a single
   column on desktop (the section measured 1777px instead of 775px). */
@media (min-width: 600px) {
  .features__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--card-gap); }
  .feature-card {
    grid-template-columns: minmax(0, 1fr);
    align-items: stretch;
    gap: 6px;
    padding: var(--card-padding);
  }
  /* Phase 5.8H 16/10 -> 16/8.8 -> 16/8.2; Phase 5.8I -> 16/7.2. THIS is the
     rule that drives the reason grid's height from 600px up — the card is
     vertical here, so the stage is a row rather than a column. Still
     `contain`, so no screenshot is cropped. */
  .feature-card__media { aspect-ratio: 16 / 7.2; align-self: stretch; }
}
@media (min-width: 768px) {
  .features { padding: var(--section-pad-y-mid) 0 calc(var(--section-pad-y-mid) + 7px); }
  .features__grid { gap: var(--card-gap); }
  .feature-card { padding: var(--card-padding); }
  .feature-card__impact { font-size: clamp(1.06rem, 1.55vw, 1.22rem); }
}
/* Desktop: 3 x 2 on the shared COPY measure, not the wide rail.
   Phase 6.5 — measured at 1440, every comparable content block on the page sits
   at 782-820px (gameplay 820, testimonials 820, FAQ 820, lineup cards 782)
   while this grid alone spanned the full 1160px rail. It was not off-centre —
   left and right margins were already equal to the pixel — it was simply the
   only section using the widest measure, which is what made it dominate.
   `--content-copy` is the SAME token gameplay, the testimonial carousel and the
   FAQ already resolve to, so this is an existing shared width rather than a new
   one-off maximum. Cards land at ~267px, close to the lineup's ~250px. */
@media (min-width: 1024px) {
  .features__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--card-gap);
    max-width: var(--content-copy);
    margin-inline: auto;
  }
  .feature-card { padding: var(--card-padding); }
  .feature-card__icon { width: 24px; height: 24px; border-radius: 7px; }
  .feature-card__icon .icon { width: 15px; height: 15px; }
  .feature-card__impact { font-size: clamp(1.15rem, 1.36vw, 1.33rem); }
  .feature-card__support { font-size: clamp(.88rem, .95vw, .97rem); margin-top: 4px; }
}

/* ---------------------------------------------------------------------
   H. TRUST, LICENCE & RESPONSIBLE PLAY
   --------------------------------------------------------------------- */
.trust {
  position: relative;
  background: linear-gradient(180deg, var(--surface-lilac) 0%, var(--surface-white) 100%);
  color: var(--text-dark);
}
.trust__points { display: grid; gap: 7px; margin-bottom: 18px; }
.trust__point {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  background: var(--td-surface-1);
  border: 1px solid var(--td-border-subtle);
}
.trust__check { flex: none; display: grid; place-items: center; color: #8dffb5; }
.trust__check .icon { width: 19px; height: 19px; }
.trust__point-text { font-size: .85rem; line-height: 1.6; color: #ece0f5; }
@media (min-width: 768px) {
  .trust { padding: var(--section-pad-y-mid) 0 calc(var(--section-pad-y-mid) + 5px); }
  .trust__points { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* The licence panel is nested inside this section now, so it drops the
   page-level padding and top border it carried as a standalone block. */
.trust .license {
  padding: 0;
  background: none;
  border-top: 0;
}

.trust__links { margin-top: 24px; }
.trust__links-title {
  margin-bottom: 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--td-border-subtle);
  font-size: .82rem;
  font-weight: 900;
  color: #fff;
}
.trust__links-title--social { margin-top: 20px; }
.trust__link-list { display: grid; gap: 8px; }
.trust__link-list a {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 46px;
  padding: 8px 12px;
  border-radius: 10px;
  background: var(--td-surface-1);
  border: 1px solid var(--td-border-subtle);
  color: #ece0f5;
  font-size: .88rem;
  font-weight: 700;
  text-decoration: none;
}
.trust__link-list a:hover { border-color: var(--pachi-gold); color: #fff; }
.trust__link-icon { display: grid; place-items: center; color: var(--pachi-gold-light); }
.trust__link-icon .icon { width: 19px; height: 19px; }
.trust__link-ext { margin-left: auto; display: grid; place-items: center; opacity: .5; }
.trust__link-ext .icon { width: 13px; height: 13px; }

.trust__socials { display: flex; flex-wrap: wrap; gap: 10px; }
.trust__socials a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 8px 14px;
  border-radius: 10px;
  background: var(--td-surface-1);
  border: 1px solid var(--td-border-subtle);
  color: #ece0f5;
  font-size: .85rem;
  font-weight: 700;
  text-decoration: none;
}
.trust__socials a:hover { border-color: var(--td-brand-accent-2); color: #fff; }
.trust__socials .icon { width: 19px; height: 19px; }
.trust__contact { margin-top: 14px; font-size: .82rem; color: #cbb8da; }
.trust__contact a { color: var(--pachi-gold-light); }

@media (min-width: 768px) {
  .trust__link-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  .trust__link-list { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ---------------------------------------------------------------------
   USER TESTIMONIALS (Phase 5.1)
   Renders only when a permissioned, visible entry exists — see
   sections/testimonials.njk. No star scores, no avatars, no review schema.
   --------------------------------------------------------------------- */
.voices {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(80% 52% at 50% 0%, rgba(255, 255, 255, .46), transparent 64%),
    linear-gradient(172deg, var(--sky-blue) 0%, #2a92e4 58%, var(--digital-blue) 100%);
}
/* Screenshot carousel. Native scroll-snap does the scrolling, so touch swipe
   works with no JavaScript; main.js only adds the buttons' state. Slide
   sizing lives with the fixed-stage rules below (`.voices__item`,
   `.voice-shot`) — exactly one slide fills the track at every width, with no
   peek of the next, since this is a single centred screenshot, not a card
   grid. */
/* Phase 5.8B: the whole carousel is capped and centred, so this reads as
   supporting social proof rather than a second conversion section competing
   with the CTAs. */
/* Phase 5.8F: the testimonial mascot is anchored to this container, so it must
   be the positioning context. */
.voices__inner { position: relative; }
.voices__carousel { position: relative; max-width: 820px; margin-inline: auto; }
/* `gap: 0` (Phase 5.8B, was 12px): with `flex: 0 0 100%` items, any gap makes
   a slide's snap position differ from its own offset by the accumulated gap,
   so slides drifted slightly off their intended boundary the further you
   paged. At zero, every slide's boundary is exactly its offsetLeft — which is
   also what main.js scrolls to. `scroll-snap-stop: always` keeps a fast swipe
   from skipping past a review. */
.voices__track {
  display: flex;
  align-items: stretch;
  gap: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  padding-bottom: 4px;
  margin: 0;
  list-style: none;
}
.voices__track::-webkit-scrollbar { display: none; }

.voices__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 11px;
  margin-top: 8px;
}
.voices__nav {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #fff;
  border: 1px solid rgba(8, 40, 80, .16);
  box-shadow: 0 3px 8px rgba(8, 40, 80, .2);
  cursor: pointer;
  transition: background-color .16s var(--motion-ease-out), opacity .16s var(--motion-ease-out);
}
.voices__nav:hover:not(:disabled) { background: #eaf5ff; }
.voices__nav:disabled { opacity: .38; cursor: default; }

/* Small decorative pagination dots (Phase 5.6) — aria-hidden, no visible
   "3/9" count anywhere. Purely a glance-able position cue between the arrows. */
.voices__dots { display: flex; align-items: center; gap: 6px; margin: 0; padding: 0; list-style: none; }
.voices__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .55);
  transition: background-color .2s var(--motion-ease-out), transform .2s var(--motion-ease-out);
}
.voices__dot.is-active { background: #fff; transform: scale(1.35); }
.voices__chev {
  width: 10px;
  height: 10px;
  border-left: 3px solid var(--digital-blue);
  border-bottom: 3px solid var(--digital-blue);
  transform: rotate(45deg) translate(1px, -1px);
}
.voices__chev--next { transform: rotate(-135deg) translate(1px, -1px); }

/* Phase 5.4: the outcome disclaimer render path is gone; this class now only
   ever styles the preview-fixture note (which never ships in production). */
.voices__disclaimer { margin-top: 14px; font-size: .68rem; line-height: 1.7; color: #e2f1ff; text-align: center; }
.voices__disclaimer--preview { font-weight: 700; color: #fff; }

/* Original coin motifs — decorative, aria-hidden. */
.voices__coin {
  position: absolute;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 36% 32%, #fff8d2, #ffd54a 52%, #e8a200 78%, #b97b00);
  box-shadow: 0 4px 10px rgba(8, 40, 80, .3);
  opacity: .9;
  animation: amb-float 15s ease-in-out infinite;
  pointer-events: none;
}
.voices__coin--a { left: 5%;  top: 12%; }
.voices__coin--b { right: 7%; top: 26%; width: 24px; height: 24px; animation-duration: 19s; animation-delay: -5s; }
.voices__coin--c { left: 12%; bottom: 12%; width: 28px; height: 28px; animation-duration: 22s; animation-delay: -11s; }

@media (min-width: 768px) { .voices__coin--a { left: 8%; } }

/* ---------------------------------------------------------------------
   I. FAQ
   --------------------------------------------------------------------- */
.faq {
  position: relative;
  background: linear-gradient(180deg, var(--surface-white) 0%, var(--surface-lilac) 100%);
  color: var(--text-dark);
}
.faq__list { display: grid; gap: clamp(7px, 1vw, 8px); }
.faq__item {
  border-radius: var(--radius-md);
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.faq__trigger {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  /* Phase 5.8H: 52 -> 46px, still well over the 44px touch minimum. */
  min-height: 44px;
  padding: 8px 12px;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
}
.faq__q-mark {
  flex: none;
  font-size: 1rem;
  font-weight: 900;
  line-height: 1.5;
  color: var(--pachi-red);
}
.faq__q-text {
  flex: 1 1 auto;
  min-width: 0;
  font-size: .95rem;
  font-weight: 700;
  line-height: 1.45;
  color: var(--card-text);
}
/* Chevron rotates to show state; `aria-expanded` carries the real semantics,
   so the rotation is decoration and never the only cue. */
.faq__chev {
  flex: none;
  width: .55em;
  height: .55em;
  margin-top: .42em;
  border-right: 2.5px solid var(--card-text-muted);
  border-bottom: 2.5px solid var(--card-text-muted);
  transform: rotate(45deg);
  transition: transform .22s var(--motion-ease-out);
}
.faq__item.is-open .faq__chev { transform: rotate(-135deg); }
.faq__item.is-open .faq__trigger { background: var(--card-bg-2); }
.faq__a {
  padding: 0 12px 10px;
  font-size: .9rem;
  line-height: 1.6;
  color: var(--card-text-muted);
}
@media (min-width: 768px) {
  .faq { padding: var(--section-pad-y-mid) 0 calc(var(--section-pad-y-mid) + 7px); }
  /* Phase 5.8D: the FAQ keeps a narrower measure than the page's wide edge —
     Q&A prose across 1160px is uncomfortable — but it now uses the SHARED
     `--content-copy` token, the same narrow edge the hero copy uses, instead
     of its own 840px value. Deliberate and documented, not an arbitrary jump. */
  .faq__inner { max-width: var(--content-copy); margin-inline: auto; }
  .faq__q-text { font-size: 1rem; }
}

/* ---------------------------------------------------------------------
   J. FINAL CONVERSION
   --------------------------------------------------------------------- */
.final-cta {
  position: relative;
  overflow: hidden;
  text-align: center;
  background:
    radial-gradient(100% 76% at 50% 12%, rgba(255, 227, 61, .30), transparent 60%),
    radial-gradient(120% 90% at 50% 100%, rgba(107, 31, 214, .8), transparent 70%),
    linear-gradient(168deg, var(--campaign-magenta) 0%, #a01a8c 44%, var(--campaign-purple) 100%);
}
/* Phase 5.8F: the positioning context for the final-CTA guide — and nothing
   else. The reserved strip below the CTA (`padding-bottom` up to 190px) and
   the `min-height` of 300/340px existed only to make room for the old
   bottom-anchored placement, and together they left 186-189px of empty section
   below the button on a phone and 128px on desktop. Both are gone: the section
   now ends shortly after its last meaningful content, and the guide is placed
   in a side lane instead (components.css). */
.final-cta__inner { position: relative; z-index: 2; }
/* Phase 5.8I — the mobile final CTA is RECOMPOSED, not padded.

   Phases 5.8F/5.8H concluded there was "no side lane at all" on a phone and
   parked the guide in a band BELOW the button (`top: 100%`), sized to contain
   her. That is why she read as a small sticker: 0.36x her desktop
   character/section prominence, the worst ratio on the page.

   There IS a lane — it just has to be created rather than found. The proof
   circles, the heading and the supporting line now sit in a right-hand column
   and she stands in the left one, full height; the registration button spans
   the complete row beneath both. The lane is derived from her own height x her
   intrinsic 0.467 aspect, so it cannot drift from a stale number, and the
   button cancels the lane with a negative margin so it stays full-width and
   centred ON THE SECTION rather than on the padded box. */
/* PHASE 5.8L — the mascot-independence rule reaches the final CTA.
   5.8I's recomposition above was still a CONTENT LANE: `padding-left` on
   `.final-cta__inner` sized from the character, with the button cancelling it
   again via a negative margin. Measured, that put the proof circles, the
   heading and the supporting line off the rail centre, and made the copy column
   narrower than the section — a decoration deciding where content sits, which
   is exactly what gameplay and はじめ方 stopped doing in 5.8K.

   The lane is gone. Content is centred on the full rail; the characters are
   overlays in the decorative band the section already reserves at its foot. */
@media (max-width: 767px) {
  .final-cta { padding-bottom: calc(var(--section-pad-y-mid) + 10px + var(--fc-band-mobile)); }
}
.final-cta .proof-circles { margin-bottom: 10px; }
.final-cta__title {
  font-size: clamp(1.31rem, 6vw, 2.26rem);
  font-weight: 900;
  line-height: 1.34;
  color: #fff;
  text-shadow: 0 3px 0 rgba(0, 0, 0, .45), 0 0 30px rgba(255, 176, 34, .3);
}
.final-cta__sub {
  margin-top: 10px;
  font-size: clamp(.92rem, 3.9vw, 1.08rem);
  font-weight: 700;
  color: #f0dff8;
}
.final-cta__action { margin-top: 13px; }
/* Cap the closing button so the celebrating guide has a clear column beside it
   instead of standing on top of a 1120px-wide bar. */
@media (min-width: 768px) {
  .final-cta__action { max-width: 470px; margin-inline: auto; }
}
@media (min-width: 768px) {
  /* Trailing space is deliberate and small: the button-to-section-bottom gap
     lands inside the 48-80px desktop / 28-64px tablet target. */
  /* The bottom keeps a slightly deeper band than the top: it is the guide's
     side lane and the page's last breath before the footer. 60px -> 48px. */
  .final-cta { padding: calc(var(--section-pad-y-mid) + 5px) 0 calc(var(--section-pad-y-mid) + 10px); }
}


/* =====================================================================
   LIGHT SECTIONS — Phase 5.2

   The lineup, how-to, reasons, trust and FAQ sections now sit on light
   grounds. Everything that was written for a dark ground is inverted here,
   in one place, rather than by editing each component.
   ===================================================================== */

/* Lineup no longer carries the shared `is-light` utility (Phase 5.7 — the
   section's background/tabs are fully authored in its own base rule now,
   see "E. MACHINE LINEUP" above), so no `.is-light .lineup__*` rules live
   here any more. */

/* --- How-to on the yellow band ------------------------------------------ */
.is-light .step {
  background: #fff;
  border-color: rgba(28, 16, 36, .12);
  border-left-color: var(--pachi-red-hot);
}
.is-light .step__num {
  background: linear-gradient(160deg, var(--campaign-magenta), #b8106a);
  color: #fff;
  box-shadow: 0 3px 0 rgba(120, 8, 60, .4);
}

/* --- Reason cards on lavender ------------------------------------------- */
.is-light .feature-card {
  background: #fff;
  border-color: rgba(107, 31, 214, .16);
  border-top-color: var(--campaign-purple-bright);
}
.is-light .icon-chip {
  background: linear-gradient(160deg, var(--campaign-purple-bright), var(--campaign-purple));
  border-color: rgba(255, 227, 61, .5);
}

/* --- Trust on pale lavender --------------------------------------------- */
.is-light .trust__point {
  background: #fff;
  border-color: rgba(107, 31, 214, .16);
}
.is-light .trust__point-text { color: var(--text-dark); }
.is-light .trust__check { color: #0f9d58; }

/* --- FAQ on off-white --------------------------------------------------- */
.is-light .faq__item {
  background: #fff;
  border-color: rgba(107, 31, 214, .16);
}
.is-light .faq__q-mark { color: var(--campaign-magenta); }
.is-light .faq__item.is-open .faq__trigger { background: var(--surface-lilac); }

/* The "licence panel on a light ground" variant was removed in Phase 5.4:
   the panel only ever renders inside the plain (dark) footer now that the
   standalone trust section is gone, so an `.is-light` context for it no
   longer exists — see components.css for the panel's real, dark-ground
   colours and their contrast rationale. */

/* --- Gameplay on electric blue ------------------------------------------ */
.gameplay .section-head__sub { color: #d8ecff; }
.gameplay__point {
  background: rgba(255, 255, 255, .12);
  border-color: rgba(255, 255, 255, .22);
}
.gameplay__stage { border-color: rgba(24, 207, 250, .55); }

/* --- Voices on sky blue -------------------------------------------------- */
.voices .section-head__title { color: #fff; }
.voices__disclaimer { color: #e2f1ff; }

/* =====================================================================
   PHASE 5.3 — illustrated how-to, testimonial carousel
   (the lineup show-floor rules that used to live here were consolidated
   into "E. MACHINE LINEUP" above in Phase 5.7 — one definition, not a
   base layer plus an override layer.)
   ===================================================================== */

/* --- How-to: illustrated sequence (Phase 5.4: significantly more compact) -
   The media stage is now a FIXED height at every breakpoint, with
   `object-fit: contain` doing the same job it does for testimonials: three
   screenshots of different native sizes (step 1 is a short, wide crop; 2/3
   are much taller) all sit inside the same compact box with no cropping and
   no stretching, so the section's overall height stays predictable and the
   cards can be reduced without touching the images' aspect ratios. */
.howto { overflow: hidden; }
/* Phase 5.8K: `position: relative` because the guide is anchored to this band
   now, not to the introduction copy. `margin-bottom` tightened 14px -> 8px as
   part of closing the upper gap. */
.howto__head { position: relative; text-align: center; margin-bottom: 14px; }
.howto__title {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Phase 5.8G: fluid, so the bigger lockup clears the ribbon without ever
     touching it and without opening a hole between the two. */
  gap: clamp(5px, 1.4vw, 12px);
  margin: 0;
}
/* Phase 5.8G — the 3ステップ lockup.
   Until 5.8F the numeral rendered at ~35px on a phone: smaller than the
   ribbon above it and barely larger than the sentence below, so the section's
   central promise was not the focal point. It is now a dominant oversized
   numeral with a compact word locked to its baseline — the structural
   principle borrowed from the 777town reference (one big figure + one small
   label as a single centred unit), expressed entirely in this project's own
   yellow/gold/plum system. No colour, graphic, wording or button was copied.

   Rendered numeral: ~70px at 320 to ~96px at 1440. Rendered ステップ: ~26px to
   ~36px. Both stay inside the phase's suggested bands at all seven verified
   widths, and both are fluid rather than stepped per breakpoint. */
.howto__count {
  display: inline-flex;
  align-items: baseline;
  justify-content: center;
  gap: clamp(2px, .9vw, 8px);
}
.howto__count-num {
  font-size: clamp(3.95rem, 3.5rem + 2.1vw, 5.4rem);
  /* Tight so the taller numeral does not open a gap before the supporting
     sentence — the lockup grew in weight, not in vertical footprint. */
  line-height: .82;
  /* The numeral carries `tx-gold` (utilities.css): bright gold core with a
     deep-plum stroke. That stroke is what separates it from the yellow rays
     behind it — the previous `tx-yellow` had only a 55%-alpha edge and sank
     into the background. Scaled up here because 1.5px reads as a hairline on
     a 96px glyph; the shared utility is untouched, so the lineup heading that
     also uses `tx-gold` is unaffected. */
  -webkit-text-stroke-width: 3px;
}
@media (max-width: 389px) { .howto__count-num { -webkit-text-stroke-width: 2.5px; } }
.howto__count-unit {
  font-size: clamp(1.47rem, 1.3rem + .8vw, 2.03rem);
  line-height: 1.1;
  color: var(--text-plum);
}
/* Phase 5.8D — the intro scene: guide at the left, introduction copy beside
   her. Superseded by the Phase 5.8E rule below. */
/* Phase 5.8E: the introduction is CENTRED and the guide is an absolutely
   positioned edge decoration inside it. 5.8D made this a flex row with her as
   a flow item, which pushed the copy off centre and opened a dead band before
   Step 1. She now costs no flow height, so Step 1 follows the introduction
   directly. The left inset is what keeps the centred copy clear of her at the
   narrowest widths; from 1024px the container's own slack is wide enough that
   no inset is needed. */
.howto__intro {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Phase 5.8K: the reserved min-height is gone, so this gap is now the ONLY
     thing separating the lockup from the supporting copy — it needs to read as
     deliberate spacing rather than the leftover of a collapsed box. */
  margin-top: 8px;
  /* Phase 5.8G retuned this lane. It reserves horizontal room for the guide,
     and it was set in 5.8E against a figure that no longer exists: 96px at
     320px for a girl who is now ~54px wide there. Over-reserving squeezed the
     copy into 175px, which broke 初めてでも迷いにくい操作設計 onto a second line
     with a single orphaned 計 character.

     The lane now tracks her actual measured width plus ~7-19px of breathing
     room (54/62 at 320, 65/72 at 390, 111/130 at 768, 121/150 at 1023), so the
     copy gains 34px at 320 and 28px at 390 and both sentences fit their lines.
     The cap is unchanged at 150px, and 1024px+ still clears the lane entirely
     via the rule below. */
  /* Phase 5.8I: widened again because the guide is now proportional to her
     desktop presence (~86px wide at 390 instead of ~58px). The lane still
     tracks her MEASURED width plus ~8px, never a guessed value. */
  /* Phase 5.8K: NO padding-left lane and NO min-height. Both existed only to
     accommodate the guide, and both are exactly what this phase removes:
       · the lane put the supporting copy 30.8-37.5px off the rail centre;
       · the min-height reserved var(--howto-guide-h) + 6px (201px at 390) of
         vertical space for ~50px of text, which is where the section's empty
         upper area came from.
     The copy is centred on the full rail and the box is only as tall as its
     own content. The guide is an overlay on `.howto__head` (components.css). */
  text-align: center;
}
.howto__intro-copy { min-width: 0; }
.howto__sub { font-size: clamp(.86rem, 3.5vw, 1.02rem); line-height: 1.5; color: #6b4a10; }
.howto__ray {
  position: absolute;
  inset: -30% -20% auto;
  height: 90%;
  background: repeating-conic-gradient(from 0deg, rgba(255, 255, 255, .30) 0deg 4deg, transparent 4deg 13deg);
  -webkit-mask-image: radial-gradient(closest-side, transparent 10%, #000 34%, transparent 76%);
  mask-image: radial-gradient(closest-side, transparent 10%, #000 34%, transparent 76%);
  pointer-events: none;
}

/* Phase 5.8C: this wrapper is ONLY the mascot's positioning context. It is
   never a grid and never reserves a column, so the step sequence inside it
   centres itself against the shared container regardless of whether the
   mascot renders.

   `padding-top` is the short band the mobile mascot stands in — she is
   absolutely positioned inside it (components.css). It is deliberately modest
   (~112px, under the 120-150px budget) so Step 1 still begins soon after the
   section intro; she occupies that gap rather than creating a tall reserved
   block of her own, which is what Phase 5.8B's in-flow centred figure did. */
/* Phase 5.8D: the reserved top band is GONE. It existed only to give the 5.8C
   absolute mascot somewhere to stand; now that she is a flow child of the intro
   scene above, the band was ~112px of dead yellow space between the
   introduction and Step 1 — exactly the kind of character-induced gap this
   phase set out to remove. Step 1 now begins immediately after the intro. */
.howto__flow { position: relative; z-index: 2; }
/* The step grid centres ITSELF — `margin-inline: auto` here, not a side effect
   of any mascot lane. This is the rule that makes hiding the mascot a no-op
   for the cards. */
/* Phase 5.8H — THREE COLUMNS AND TWO ARROWS AT EVERY WIDTH, 320px included.
   Phase 5.8G unified the order INSIDE each card but left this rule as a
   single implicit column with `grid-template-columns` only declared from
   768px, so mobile still stacked. The row template is now the base rule and
   the breakpoint only adjusts the outer cap. Nothing wraps (an explicit
   five-track template cannot), nothing scrolls, nothing is a carousel, and
   both arrows always sit between the cards they join.

   `minmax(0, 1fr)` rather than `1fr` so a long Japanese description can never
   push a track past its share and force horizontal overflow. */
.steps {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: stretch;          /* the three cards share one row height */
  gap: 0 var(--step-gap);
  counter-reset: none;
  position: relative;
  z-index: 2;
  margin-inline: auto;
}
/* Phase 5.8G — ONE canonical card composition at every width.
   Through 5.8F the mobile card was a DIFFERENT design from the desktop card.
   Mobile used `grid-template-areas: "badge body" "shot shot"` — badge inline
   beside the title, description above the screenshot — while 768px+ used
   "badge" / "shot" / "body". Same markup, two compositions, so the section
   read as two designs.

   The grid is now a plain single column that simply FOLLOWS DOM ORDER
   (badge → screenshot → title → description, see how-to.njk). There is no
   `grid-template-areas` and no `grid-area` anywhere in this component any
   more, so no breakpoint can reorder it: the mobile card is the desktop card
   in one column, and the only intentional structural difference left is
   three-across (desktop) versus stacked (mobile), which `.steps` owns. */
.step {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* Phase 5.8H: every internal measure is now fluid, because at 320px this
     card is ~83px wide instead of ~284px. Nothing is hidden, clamped or
     rewritten — the module is SIZED down, not restructured. */
  gap: var(--step-inner-gap);
  padding: var(--step-pad);
  text-align: center;
  border-radius: var(--step-radius);
  background: #fff;
  border: var(--step-border) solid rgba(28, 16, 36, .10);
  box-shadow: 0 var(--step-border) 0 rgba(163, 16, 106, .18);
}
/* Centred at every width — the badge's position and visual role never change
   between mobile and desktop, so it is never inline beside the title. */
.step__badge { margin: 0; justify-self: center; }
.step__num {
  display: grid;
  place-items: center;
  /* Phase 5.8G: one fluid diameter, `aspect-ratio` guaranteeing a true circle,
     so all three badges are identical by construction rather than by matching
     numbers, and always subordinate to the `3` in the lockup above.
     Phase 5.8H rescaled it for a three-across mobile row: ~28px at 320 to
     32px by 640px, then 46px+ once the cards are wide again. */
  width: var(--step-badge);
  aspect-ratio: 1;
  border-radius: 50%;
  background: linear-gradient(160deg, var(--campaign-magenta), #a3106a);
  color: #fff;
  font-size: var(--step-badge-fs);
  line-height: 1;
  box-shadow: 0 2px 0 rgba(90, 6, 55, .5);
}
.step__body { min-width: 0; }
/* Phase 5.8D typography (spec §8). The step copy was ~15px/12px against a
   section heading several times its size, so the cards read as captions bolted
   under oversized screenshots. Titles now clearly outrank descriptions, and
   descriptions sit at a comfortable Japanese reading size on the bright yellow
   ground. Step 3's longer sentence simply wraps — its font is NOT reduced
   independently to force matching line counts. */
.step__title {
  font-size: var(--step-title-fs);
  line-height: 1.3;
  color: var(--text-plum);
}
.step__text {
  margin-top: clamp(2px, .8vw, 4px);
  font-size: var(--step-text-fs);
  line-height: 1.45;
  color: #4a3a58;
}
/* Fixed-height stage — mobile target ~115-145px. Never cropped, never
   stretched: `contain` lets a short wide crop (step 1) and a tall portrait
   (steps 2/3) share one box. */
.step__shot {
  /* Phase 5.5: raised from 128px now that steps 2/3 carry a much less
     extreme mobile crop (see content.json howTo.steps[].shot) — the action
     reads at a legible size without the stage becoming several screens
     tall. */
  /* Phase 5.8H: fluid, because the card is now one of three columns at every
     width. `contain` is unchanged, so the screenshots are still never cropped
     or stretched — only the stage they letterbox inside got smaller. */
  height: var(--step-shot-h);
  border-radius: clamp(5px, 1.6vw, 10px);
  overflow: hidden;
  border: var(--step-shot-border) solid #2a0038;
  background: #1a0126;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .5);
}
.step__shot picture { display: block; width: 100%; height: 100%; }
.step__shot img { display: block; width: 100%; height: 100%; object-fit: contain; }
/* Phase 5.8L: the shot is now a `.zoom-trigger` button. `.zoom-trigger` already
   supplies `padding: 0; border: 0; background: none; cursor: zoom-in` and the
   focus ring, so only the two properties that would fight the card's own box
   need restating — the shot keeps its exact previous geometry, and the card
   composition (badge -> screenshot -> title -> description) is unchanged. */
.step__shot.zoom-trigger { width: 100%; display: block; }
/* The magnifier is scaled down a little inside the small step card: the shot
   box is ~86-140px tall here against ~200px+ in 選ばれる理由, so the shared 24px
   disc would read as a badge rather than an affordance. Same disc, same colours,
   same inset ratio, same `cursor: zoom-in` — only the diameter follows the box. */
.step__shot .zoom-trigger__icon {
  width: clamp(16px, 5.2vw, 22px);
  height: clamp(16px, 5.2vw, 22px);
  right: clamp(3px, 1vw, 6px);
  bottom: clamp(3px, 1vw, 6px);
}

/* --- Phase 5.8J: the how-to registration CTA -----------------------------
   The conclusion of the three-step explanation, not a competitor to the hero or
   the final CTA: narrower than both (≈300-360px against their 470px), one line,
   centred, and separated from the row by the shared band spacing rather than a
   new magic number. */
/* Phase 5.8K: the how-to CTA uses the SHARED in-page CTA sizing, not its own.
   5.8J gave it `btn--lg` plus a bespoke `max-width: 360px` / `min-width: 300px`
   pair, which rendered it 300x40 against the hero/lineup/final 470x50 — a
   different component to the eye. It now takes the same `btn--xl btn--block`
   size and the same 470px capped-and-centred wrapper the other three use
   (components.css lists all four selectors together, so there is exactly one
   place where in-page CTA width is decided). No width value is repeated here. */
.howto__cta {
  margin-top: clamp(12px, 2.4vw, 20px);
  text-align: center;
}
.howto__cta .btn {
  white-space: nowrap;      /* the label stays on one line at every width */
}
@media (min-width: 768px) {
  .howto__cta { max-width: 470px; margin-inline: auto; }
}

/* Chevrons between steps. Phase 5.8H: ONE direction — right — at every width,
   because the cards are a row at every width. There is no longer a rotated
   base rule, no breakpoint override and no second `@keyframes arrow-bob`; the
   bob travels along the same axis the row reads in. */
.step-arrow {
  display: grid;
  place-items: center;
  /* Phase 5.8H: `align-content: center` groups the two chevrons in the middle
     as ONE double arrow. Without it the arrow cell is stretched to the row
     height (the cards are `align-items: stretch`), the two implicit auto rows
     stretch with it, and the pair ended up ~115px apart reading as two
     unrelated arrows. */
  align-content: center;
  gap: clamp(1px, .4vw, 2px);
  padding: 0;
  list-style: none;
}
.step-arrow span {
  width: var(--step-arrow);
  height: var(--step-arrow);
  border-right: var(--step-arrow-w) solid var(--campaign-magenta);
  border-bottom: var(--step-arrow-w) solid var(--campaign-magenta);
  transform: rotate(-45deg);
  filter: drop-shadow(0 1px 0 rgba(163, 16, 106, .5));
  animation: arrow-bob 1.8s ease-in-out infinite;
}
.step-arrow span:nth-child(2) { animation-delay: .18s; opacity: .55; }
@keyframes arrow-bob {
  0%, 100% { transform: rotate(-45deg) translate3d(0, 0, 0); }
  50%      { transform: rotate(-45deg) translate3d(3px, -3px, 0); }
}

@media (min-width: 768px) {
  .howto { padding: var(--section-pad-y-tight) 0 calc(var(--section-pad-y-tight) + 5px); }
  /* Kept relative from Phase 5.5. The mascot's own positioning context is
     `.howto__flow` (Phase 5.8C), so this no longer anchors her — it stays
     because `.howto__ray` and the section's decorative layers rely on it. */
  .howto .container { position: relative; }
  .steps {
    /* Phase 5.8H: the row template, the stretch and the gap are base rules now
       — this breakpoint only caps the row's outer width.
       An explicit max-width that centres ITSELF, never a reserved lane that
       eats into this box's content area. Phase 5.8C narrows it to 800px for
       1024-1279px (see below) purely to open leftover space for the mascot —
       the cap is unconditional, so the cards stay centred whether or not she
       renders. */
    max-width: 900px;
    margin-inline: auto;
  }
  /* Phase 5.8G: composition is NOT restated here — the single-column DOM-order
     grid and the centred badge are base rules.
     Phase 5.8H: neither is the ROW or the arrow direction, which are also base
     rules now. Every value below is a `--step-*` token override, i.e. pure
     scale: the cards are wide again from 768px, so the type and the stage step
     back up. There is deliberately no `transform` and no second
     `@keyframes arrow-bob` here any more. */
  :root {
    --step-gap:          6px;
    --step-inner-gap:    7px;
    --step-pad:          13px;
    --step-radius:       14px;
    --step-border:       2px;
    --step-badge:        clamp(2.6rem, 2.3rem + 1.2vw, 2.9rem);
    --step-badge-fs:     clamp(1.18rem, 1.1rem + .4vw, 1.4rem);
    --step-title-fs:     clamp(1.16rem, 1.5vw, 1.32rem);
    --step-text-fs:      clamp(.92rem, 1.15vw, 1.02rem);
    --step-shot-h:       clamp(150px, 19vw, 178px);
    --step-shot-border:  3px;
    --step-arrow:        clamp(13px, 1.4vw, 15px);
    --step-arrow-w:      4px;
  }
}

/* Phase 5.8C — the mascot's lane is leftover space, not a grid track. `.steps`
   caps its own width and stays centred; whatever remains either side is where
   she stands (components.css positions her absolutely at `left: 0`). Capping
   at 800px between 1024 and 1279px is what CREATES a wide enough lane at
   1024px (~79px each side) without ever shifting the cards off centre — the
   cap is unconditional, so it does not depend on her being rendered. */
@media (min-width: 1024px) {
  .steps { max-width: 800px; }
}
@media (min-width: 1280px) {
  .steps { max-width: 900px; }
}

/* --- Testimonials: one stable stage, variable-height screenshots ---------
   Every slide is the SAME box (Phase 5.8B: 340x260 mobile, 800x360 from
   768px) and the image is `contain` inside it. That is what stops a 418px-tall
   review and an 898px-tall review from changing the page height between
   slides. Nothing is cropped or stretched.

   `.voices__item` always fills 100% of the track's visible width — at every
   breakpoint, not just mobile — so exactly one slide is ever in view; the
   inner `.voice-shot` is what actually caps out at 620px and centres itself.
   An item narrower than 100% left the next slide peeking in at the container
   edge on anything wider than a phone, which read as a card grid instead of
   "one screenshot at a time". */
.voices__item { flex: 0 0 100%; scroll-snap-align: center; scroll-snap-stop: always; min-width: 0; }

/* Phase 5.5: back to ONE fixed stage size, shared by all nine screenshots —
   Phase 5.4's per-slide `width/height: auto` + JS height-sync made the
   section's own height (and everything below it, including FAQ) move by as
   much as the tallest-vs-shortest review's difference every time the slide
   changed. `aspect-ratio` + a capped width gives a box whose rendered size
   depends only on the viewport, never on which of the nine is showing, so
   main.js no longer needs to measure or transition anything (see
   initVoices — `syncHeight` was removed). Nothing is cropped or stretched:
   the image inside still only ever shows via `object-fit: contain`. */
.voice-shot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  /* Phase 5.8B: 340x260 mobile (was 360x270 at 4/3). Inside the ~220-300px
     stage band — a review is proof, not the whole screen.
     Phase 5.8H density pass: 340x260 -> 306x234 (-10%). */
  max-width: 270px;
  aspect-ratio: 340 / 260;
  margin: 0 auto;
  padding: 10px;
  border: 0;
  border-radius: 16px;
  /* Neutral, slightly tinted stage — the screenshots themselves are white,
     so a white stage let them blend into the section instead of reading as
     a contained "frame". */
  background: #eae3f5;
  box-shadow: 0 10px 26px rgba(8, 40, 80, .28);
  cursor: zoom-in;
  position: relative;
}
@media (min-width: 768px) {
  /* Phase 5.8B: 800x360, down from Phase 5.5's 880x500. The old box was tall
     enough that one review filled most of a laptop viewport, which read as a
     primary section; 360px of stage height keeps the nine screenshots legible
     while the section stays supporting proof (closer to the VeraJohn
     reference). Still ONE stage shared by all nine, so nothing below can move
     between slides, and still `contain`, so nothing is ever cropped — a
     shorter or taller review just letterboxes into the neutral plate. */
  .voice-shot { max-width: 640px; aspect-ratio: 720 / 288; padding: 10px; }
}
.voice-shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #fff;
  border-radius: 9px;
}

body.is-dialog-open { overflow: hidden; }
.howto__lead {
  /* Phase 5.8D: scaled with the shared lead step, not its own value. */
  margin-top: 6px;
  font-size: .82rem;
  font-weight: 900;
  color: #b0430e;
}
