/**
 * landing.css — marketing landing page layout only. Requires site.css to be
 * loaded first (uses its custom properties, .framed, .btn-cta, [data-reveal]).
 * Mobile-first responsive.
 */

/* The transparent header overlays the top (position: absolute, no background);
 * on the landing page the hero fills the viewport behind it, so cancel the
 * default body offset site.css adds for the non-hero pages. */
body {
  padding-top: 0;
}

/* ── hero: full-bleed composition, title inside the art (pattern 1) ──────
 * hero-cinematic.png / hero-cinematic-mobile.png is the true full-bleed
 * background — a god descending into the summoning hall, spiral stair
 * falling away into the dark. The art's own lower band is already near
 * black, so the gradient overlay only needs to bridge the seam into the
 * page base and hold the CTA stack readable, not carve the whole scrim
 * itself (the old flat bg_summoning.png needed a heavier assist). */

.hero {
  position: relative;
  isolation: isolate;
  /* The header floats over the hero (position: fixed, frosted), so the hero
   * fills the whole viewport behind it. Content is bottom-aligned, so the nav
   * overlays the art at the top, never the CTA stack. */
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  overflow: hidden;
  padding: 96px 20px 64px;
  text-align: center;
  background-image: linear-gradient(
      to bottom,
      var(--bg) 0%,
      rgba(5, 6, 10, 0.55) 7%,
      rgba(5, 6, 10, 0) 17%,
      rgba(5, 6, 10, 0.15) 46%,
      rgba(6, 7, 11, 0.68) 68%,
      var(--bg) 100%
    ),
    /* Push the scene down ~header height so the god clears the menu; the top
     * band fades from the page base (above) into the art. */
    url('/web/art/hero-cinematic.png');
  background-size: cover, cover;
  background-position: center, center 92px;
  background-repeat: no-repeat, no-repeat;
}

@media (max-width: 820px), (orientation: portrait) {
  .hero {
    background-image: linear-gradient(
        to bottom,
        var(--bg) 0%,
        rgba(5, 6, 10, 0.6) 8%,
        rgba(5, 6, 10, 0) 18%,
        rgba(5, 6, 10, 0.15) 46%,
        rgba(6, 7, 11, 0.68) 66%,
        var(--bg) 100%
      ),
      url('/web/art/hero-cinematic-mobile.png');
    /* Match the desktop treatment: dark top band + scene pushed below the menu
     * so the hamburger/nav never sit unscrimmed on the god's bright wings. */
    background-position: center, center 72px;
  }
}

.hero-inner {
  position: relative;
  z-index: 2;
  max-width: 720px;
  margin: 0 auto;
}

/* Dark scrim BEHIND the whole text stack: the god art's wings are themselves
 * bright gold, so no fill color alone can separate the title from them.
 * Soft radial ellipse, no hard edges, sits under the text but over the art. */
.hero-inner::before {
  content: '';
  position: absolute;
  inset: -12% -18%;
  z-index: -1;
  background: radial-gradient(
    ellipse 70% 62% at 50% 42%,
    rgba(6, 7, 11, 0.68) 0%,
    rgba(6, 7, 11, 0.42) 55%,
    transparent 78%
  );
  pointer-events: none;
}
/* Mobile: the cinematic art's summoning circle sits right behind the title
 * lines — deepen the scrim there so the carved gold stays the brightest
 * element (desktop composition doesn't need it). */
@media (max-width: 820px) {
  .hero-inner::before {
    background: radial-gradient(
      ellipse 78% 66% at 50% 38%,
      rgba(6, 7, 11, 0.82) 0%,
      rgba(6, 7, 11, 0.55) 55%,
      transparent 80%
    );
  }
}


.thread-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 2px solid var(--filigree);
  background: radial-gradient(circle at 35% 30%, var(--metal-light), var(--metal-dark));
  color: var(--gold);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.85rem;
  margin: 0 auto 14px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
}

/* Carved-gold title (goosebump checklist #1): a gradient text-fill reads as
 * forged metal rather than flat yellow, with a stacked highlight/bevel/glow
 * text-shadow so it still holds up on a dark, busy background. Falls back to
 * flat --gold on the rare browser without background-clip: text. */
.hero h1 {
  font-size: clamp(2.1rem, 6vw, 3.4rem);
  margin: 0 0 14px;
  color: var(--gold);
  /* LUMINOUS gold: the fill stays bright end to end (the earlier gradient
   * fell to #6b3907 dark bronze by the glyph baseline and the whole title
   * sank into the arch art). Darkness lives BEHIND the glyphs as a halo,
   * plus a faint warm glow so the title is the brightest thing on screen. */
  background: linear-gradient(180deg, #fffbe8 0%, #ffd968 32%, #f4b83a 58%, #d3941f 82%, #b97b1d 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  text-shadow: 0 2px 2px rgba(40, 20, 0, 0.65), 0 4px 14px rgba(0, 0, 0, 0.85),
    0 0 30px rgba(0, 0, 0, 0.8), 0 0 22px rgba(255, 190, 80, 0.28);
}

@supports not (-webkit-background-clip: text) {
  .hero h1 {
    -webkit-text-fill-color: initial;
    color: var(--gold);
  }
}

.hero .hook {
  font-size: 1.08rem;
  color: var(--ink);
  margin: 0 0 22px;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.85);
}

/* ── hero vision — a gold-framed, slightly tilted "vision" of real combat
 * (goosebump checklist #1, #5): keeps the god-summons composition intact and
 * adds a small, unmistakably real gameplay clip inside its flow, instead of
 * fighting the title for the full-bleed spot. */
.hero-vision {
  margin: 0 auto 22px;
  /* Wide cinematic action band (540x300 pure-combat crop) — the critic's
   * round-4 finding: a 190px phone of full game HUD read as clutter, not
   * combat. Big, legible, dramatic footage instead. */
  max-width: 420px;
}

.hero-vision-frame {
  overflow: hidden;
  transform: rotate(-3deg);
  box-shadow: 0 14px 30px rgba(0, 0, 0, 0.6);
}

.hero-vision-video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 540 / 300;
  object-fit: cover;
  background: var(--well);
}

.hero-vision-caption {
  margin: 8px 0 0;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gold);
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.85);
}

@media (max-width: 480px) {
  .hero-vision {
    /* 76%: at 92% the hero stack ran ~36px past a 390x844 fold and sliced
     * the trust pills; the video is the stack's tallest flexible element. */
    max-width: 76%;
  }
}

/* Foreground champion (critic round 4: the references lead with one huge
 * character-forward visual; the hero hall alone read as empty). Full-body
 * knight key art rising from the hero's lower left, behind the text stack. */
.hero-champion {
  position: absolute;
  bottom: 0;
  /* Right side: the animated walker already lives on the hero's left — two
   * knights stacked on one side read as a glitch, not a composition. */
  right: clamp(8px, calc(50% - 640px), 10vw);
  /* 576px = the 512x768 key art at a clean 0.75x. */
  height: min(62svh, 576px);
  width: auto;
  z-index: 1;
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
  filter: drop-shadow(0 18px 34px rgba(0, 0, 0, 0.75))
    drop-shadow(0 0 26px rgba(244, 184, 58, 0.18));
  pointer-events: none;
}

@media (max-width: 900px) {
  .hero-champion {
    /* 384px = clean 0.5x; a real presence on phones (the critic noted only
     * a sliver of hair peeked in), layered behind the scrimmed text stack. */
    height: 384px;
    right: -14%;
    opacity: 0.95;
  }
}

.hero .cta-row {
  display: flex;
  gap: 14px;
  justify-content: center;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.hero .trust-row {
  display: flex;
  gap: 12px;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
}

/* ── scroll nudge (pattern 9 / goosebump checklist #9) — a bouncing chevron,
 * never scroll-jacking, that fades out once the hero has been scrolled past. */
.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  z-index: 3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 2px solid var(--filigree);
  background: radial-gradient(circle at 35% 30%, var(--metal-light), var(--metal-dark));
  color: var(--gold);
  font-size: 1.1rem;
  font-weight: 800;
  text-decoration: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
  transition: opacity 0.5s ease;
}

.scroll-cue:hover {
  text-decoration: none;
}

.scroll-cue.is-hidden {
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .scroll-cue span {
    display: inline-block;
    animation: scroll-cue-bounce 1.6s ease-in-out infinite;
  }
}

@keyframes scroll-cue-bounce {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(5px);
  }
}

/* ── marching hero — live steps() sprite animation (pixel rule 3) ────────
 * hero_knight.png: 128x128 frames, 28-frame sheet (src/data/manifest.ts
 * HERO_ANIM_LAYOUT). Walk = frames 2-5 @ 7fps. Static (idle frame, no
 * motion) unless the user allows motion. */

.hero-walk-lane {
  /* Fixed to the upper architecture band (well clear of the h1/hook/CTA
   * text block at every breakpoint, since that stack bottom-anchors and
   * varies in height) rather than a "gap" that shifts with viewport. */
  position: absolute;
  left: 0;
  right: 0;
  /* 26%: keeps the 256px walker clear of the sticky header when the hero
   * scrolls (at 10% its head clipped behind the nav at y~200). */
  top: 26%;
  z-index: 1;
  pointer-events: none;
}

.hero-walker-mover {
  /* Reduced-motion resting state: a single still figure centered in the
   * composition's clear gap, no animation, no off-canvas positioning. */
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
}

/* Compositor-only sprite stepping: the clip box (.hero-walker) shows one
 * 128x128 frame of the 3584px strip; the STRIP is translated with steps()
 * (transform, no repaint) instead of animating background-position, and the
 * traversal + 2x zoom are transforms on their own wrappers. */
.hero-walker {
  width: 128px;
  height: 128px;
  overflow: hidden;
}

.hero-walker-strip {
  width: 3584px;
  height: 128px;
  background-image: url('/assets/heroes/knight/hero_knight.png');
  background-repeat: no-repeat;
  background-size: 3584px 128px;
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
  transform: translateX(-256px);
}

@media (prefers-reduced-motion: no-preference) {
  .hero-walker-mover {
    left: -20%;
    transform: none;
    animation: hero-walk-across 15s linear infinite;
  }

  /* 2x in PAINT (doubled box + background-size), not transform scale —
   * transforms bilinear-resample the raster and ignore image-rendering:
   * pixelated, softening the pixels. translateX stepping stays compositor. */
  .hero-walker {
    width: 256px;
    height: 256px;
  }

  .hero-walker-strip {
    width: 7168px;
    height: 256px;
    background-size: 7168px 256px;
    /* Walk = frames 2-5 @ ~7fps (manifest HERO_ANIM_LAYOUT), 2x frames. */
    animation: hero-walk-frames 0.5714s steps(4) infinite;
  }
}

@keyframes hero-walk-frames {
  from {
    transform: translateX(-512px);
  }
  to {
    transform: translateX(-1536px);
  }
}

@keyframes hero-walk-across {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(140vw);
  }
}

@media (max-width: 640px) {
  /* The walker's lane crosses the hero paragraph on phones (critic round 4
   * screenshots) — hide it; the action video carries motion on mobile. */
  .hero-walk-lane {
    display: none;
  }

  /* The bouncing chevron lands on the trust pills once the whole stack sits
   * above the fold; phones scroll without a hint anyway. */
  .scroll-cue {
    display: none;
  }
}

/* ── connective thread (pattern 2) — a gold ornament line runs down each
 * section on wide viewports, tying the scroll into one guided descent. */

.section {
  position: relative;
  padding: 52px 20px;
  max-width: 1120px;
  margin: 0 auto;
}

@media (min-width: 900px) {
  .section::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 3px;
    transform: translateX(-50%);
    background: repeating-linear-gradient(
      to bottom,
      var(--filigree) 0 6px,
      transparent 6px 16px
    );
    opacity: 0.5;
    z-index: 0;
    pointer-events: none;
  }
}

.section > * {
  position: relative;
  z-index: 1;
}

.section h2 {
  text-align: center;
  color: var(--gold);
  font-size: clamp(1.4rem, 3vw, 1.75rem);
  margin-bottom: 8px;
}

.section .lede {
  text-align: center;
  color: var(--ink-dim);
  max-width: 620px;
  margin: 0 auto 32px;
}

/* ── biome descent (pattern 4) — each section below the hero carries its
 * own biome backdrop in act order: forest -> caverns -> crypt -> frost ->
 * volcano -> abyss, darkened + faded to --bg at each seam. */

.biome-section {
  max-width: none;
  padding-left: 0;
  padding-right: 0;
}

.biome-section .section-inner {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 20px;
}

.biome-bg {
  position: absolute;
  inset: 0;
  z-index: -3;
  background-size: cover;
  background-position: center;
}

.biome-bg.band-a {
  clip-path: polygon(0 0, 100% 0, 100% 62%, 0 82%);
}

.biome-bg.band-b {
  clip-path: polygon(0 82%, 100% 62%, 100% 100%, 0 100%);
}

.biome-fade {
  position: absolute;
  inset: 0;
  z-index: -2;
  background: linear-gradient(
    to bottom,
    var(--bg) 0%,
    rgba(8, 9, 13, 0.72) 14%,
    rgba(8, 9, 13, 0.72) 86%,
    var(--bg) 100%
  );
}

/* ── feature grid ─────────────────────────────────────────────────────── */

.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 18px;
}

.feature-card {
  padding: 18px;
}

.feature-card h3 {
  margin: 0 0 6px;
  color: var(--gold);
  font-size: 1rem;
}

.feature-card p {
  margin: 0;
  color: var(--ink-dim);
  font-size: 0.9rem;
}

/* ── dev log strip (goosebump checklist #6) — dated, real, diegetic panels ── */

.devlog-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}

/* "Read the full dev log" affordance under the card grid — centered so it
   relates to the section's centered heading, not the leftmost card. */
.devlog-more {
  text-align: center;
  margin-top: 24px;
}

.devlog-card {
  padding: 16px 18px;
}

.devlog-date {
  display: block;
  color: var(--filigree);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 6px;
}

.devlog-card p {
  margin: 0;
  color: var(--ink);
  font-size: 0.92rem;
  line-height: 1.4;
}

/* ── character select screen (goosebump checklist #3, #10) — one giant
 * full-body render + structured metadata rows, switched by a filmstrip of
 * portraits. All six .select-panel articles are present in the markup, so
 * with JS disabled every hero renders stacked; the inline script hides all
 * but the active one and drives the filmstrip. */

.select-screen {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.select-panel {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
}

/* The `display: grid` above otherwise wins over the browser's UA-level
 * `[hidden] { display: none }` (author rules beat UA rules regardless of
 * specificity), so the JS-driven single-panel view needs this explicit. */
.select-panel[hidden] {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  .select-panel {
    transition: opacity 0.35s ease, transform 0.35s ease;
  }

  .select-panel.is-entering {
    opacity: 0;
    transform: translateY(10px);
  }
}

@media (min-width: 760px) {
  .select-panel {
    grid-template-columns: minmax(220px, 38%) 1fr;
    align-items: stretch;
  }
}

.select-render {
  position: relative;
  border-radius: 10px;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  min-height: 260px;
  padding-top: 20px;
}

.select-render img {
  width: 100%;
  /* 400px: the design chibi renders are square (800x800) with generous
   * transparent padding, unlike the old 512x768 portraits the 300px cap
   * was tuned for. */
  max-width: 400px;
  height: auto;
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
  filter: drop-shadow(0 12px 18px rgba(0, 0, 0, 0.55));
}

/* Per-class backdrop: select-bg-<class>.png (goosebump checklist #3 render
 * stage) darkened by a class-tinted overlay so the metadata panel and the
 * champion art on top both stay readable. */
/* The backdrop art lives on its own ::before layer so it can be
 * brightness-boosted independently of the character sprite above it
 * (CSS gradients alone could not rescue the darker source scenes,
 * e.g. the samurai's moonlit night). */
.select-render::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  background-image: linear-gradient(to bottom, transparent 0%, rgba(10, 11, 16, 0.38) 100%),
    radial-gradient(circle at 50% 30%, var(--select-tint, transparent) 0%, transparent 78%),
    var(--select-bg, none);
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  filter: brightness(1.45) saturate(1.08);
}

.select-render img {
  position: relative;
  z-index: 1;
}
.select-render[data-tint='knight'] {
  --select-bg: url('/web/art/select-bg-knight.png');
  --select-tint: rgba(44, 58, 82, 0.14);
}
.select-render[data-tint='mage'] {
  --select-bg: url('/web/art/select-bg-mage.png');
  --select-tint: rgba(58, 44, 82, 0.14);
}
.select-render[data-tint='healer'] {
  --select-bg: url('/web/art/select-bg-healer.png');
  --select-tint: rgba(74, 60, 34, 0.14);
}
.select-render[data-tint='archer'] {
  --select-bg: url('/web/art/select-bg-archer.png');
  --select-tint: rgba(39, 74, 46, 0.14);
}
.select-render[data-tint='samurai'] {
  --select-bg: url('/web/art/select-bg-samurai.png');
  --select-tint: rgba(74, 34, 34, 0.14);
}
.select-render[data-tint='berserker'] {
  --select-bg: url('/web/art/select-bg-berserker.png');
  --select-tint: rgba(58, 20, 20, 0.14);
}

.select-panel.is-locked .select-render img {
  filter: grayscale(0.75) brightness(0.5) drop-shadow(0 12px 18px rgba(0, 0, 0, 0.55));
}

/* Brakkar reads locked even in the backdrop — extra dark + desaturated. */
.select-panel.is-locked .select-render {
  filter: grayscale(0.55) brightness(0.6);
}

.select-panel.is-locked .select-idle-strip {
  filter: grayscale(0.7) brightness(0.6);
}

.select-lock-badge {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 2;
  background: linear-gradient(to bottom, var(--gold), var(--filigree));
  color: var(--bg-deep);
  font-size: 0.68rem;
  font-weight: 800;
  padding: 4px 10px;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.select-meta {
  padding: 20px;
}

.select-epithet {
  margin: 0 0 2px;
  color: var(--ink-dim);
  font-size: 0.82rem;
  font-style: italic;
}

.select-meta h3 {
  margin: 0 0 14px;
  color: var(--gold);
  font-family: var(--font-display);
  font-size: 1.4rem;
}

.select-stats {
  display: grid;
  gap: 8px;
  margin: 0 0 14px;
}

.select-stat {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border);
}

.select-stat dt {
  margin: 0;
  /* --ink-dim (not --ink-faint) clears WCAG AA for these small bold labels
   * over the .framed card's metal gradient. */
  color: var(--ink-dim);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.72rem;
}

.select-stat dd {
  margin: 0;
  color: var(--ink);
  font-weight: 700;
  font-size: 0.92rem;
  text-align: right;
}

.select-stat .stars {
  color: var(--gold);
  letter-spacing: 1px;
  margin-right: 6px;
}

.select-desc {
  margin: 0;
  color: var(--ink-dim);
  font-size: 0.9rem;
}

.select-panel.is-locked .select-desc {
  color: var(--gold);
  font-weight: 600;
}

/* ── select idle sprite — a small LIVE clip of the class's idle animation
 * beside the metadata (pixel rule 3: motion proves this is a real game
 * character, not a static render). Same compositor-only clip+strip pattern
 * as .hero-walker: a fixed-size clip box shows one frame of the sheet, the
 * STRIP is translated with steps() (transform only). Frame geometry comes
 * from src/data/manifest.ts HERO_SHEETS via --frame-w/--sheet-w set inline
 * per instance (128x128 for every class except berserker's 224x128).
 * Idle = frames 0-1 @ 3fps (HERO_ANIM_LAYOUT). Static frame 0, no scale,
 * under prefers-reduced-motion; the outer wrapper clamps to 100% width so a
 * 2x-scaled berserker clip (448px) never causes horizontal overflow on
 * narrow viewports. */
.select-idle {
  display: flex;
  justify-content: center;
  overflow: hidden;
  width: var(--frame-w);
  max-width: 100%;
  margin: 14px auto 0;
}

.select-idle-clip {
  width: var(--frame-w);
  height: 128px;
  overflow: hidden;
  flex: 0 0 auto;
  /* The 3584-7168px strip descendant (scaled 2x by the parent) otherwise
   * inflates document.scrollWidth on narrow viewports even though nothing
   * outside this box ever paints — overflow: hidden alone isn't enough
   * once a transform is involved; contain: paint is the documented fix. */
  contain: paint;
}

.select-idle-strip {
  width: var(--sheet-w);
  height: 128px;
  background-image: var(--sprite-url);
  background-repeat: no-repeat;
  background-size: var(--sheet-w) 128px;
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
}

@media (prefers-reduced-motion: no-preference) {
  /* NATIVE 1x, deliberately: the sheet's frames are softly rendered at
   * 128px (anti-aliased edges in the source art), so ANY upscale — even a
   * crisp nearest-neighbor one — magnifies mush. Small and pixel-perfect
   * beats big and muddy; the large design render above carries the panel.
   * Only the frame-stepping translateX animates (compositor-only). */
  .select-idle {
    justify-content: flex-start;
    contain: paint;
  }

  .select-idle-strip {
    animation: select-idle-frames 0.6667s steps(2) infinite;
  }
}

@keyframes select-idle-frames {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(calc(var(--frame-w) * -2));
  }
}

/* ── filmstrip — the switching control (enhancement only, see JS) ───────── */

.filmstrip {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}

.filmstrip-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 76px;
  padding: 4px;
  background: var(--panel);
  border: 2px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  color: var(--ink-dim);
  font-size: 0.66rem;
  font-weight: 600;
}

.filmstrip-item img {
  width: 100%;
  /* The tall trading-card sliver crop is the INTENDED design (owner call,
   * 2026-07-14) — height locked explicitly so it can't drift; faces are
   * kept in frame with per-class object-position below. */
  height: 256px;
  object-fit: cover;
  border-radius: 4px;
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
}

/* Liria's face sits right-of-center in her square portrait; the default
 * 50% crop window showed only hair. */
.filmstrip-item[data-select='healer'] img {
  object-position: 68% 50%;
}

.filmstrip-item.is-active {
  border-color: var(--gold);
  color: var(--gold);
  box-shadow: 0 0 0 2px rgba(244, 197, 66, 0.35);
}

.filmstrip-item.is-locked img {
  filter: grayscale(0.85) brightness(0.5);
}

.filmstrip-item.is-locked span {
  color: var(--gold);
}

/* ── screenshots — framed carousel of real in-engine video (goosebump
 * checklist #4, #5), captioned. Posters (still frames) show until the clip
 * scrolls into view; the inline script starts/stops playback. ─────────── */

.shots-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 22px;
  justify-items: center;
}

.phone-frame {
  overflow: hidden;
  background: var(--well);
  max-width: 220px;
  width: 100%;
  border-radius: 4px;
}

.phone-frame img {
  width: 100%;
  display: block;
}

.shot-video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 540 / 960;
  object-fit: cover;
  background: var(--bg-deep);
}

.shot-caption {
  margin: 0;
  padding: 8px 10px 10px;
  color: var(--ink-dim);
  font-size: 0.78rem;
  text-align: center;
}

/* ── closing band (pattern 12) — distinct, breaks the dominant palette ──── */

.closing {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 36px;
}

.closing-art {
  /* A step larger (owner: "increase a bit"), growing into the left side
   * of the closing row. */
  flex: 0 1 400px;
  max-width: 420px;
  padding: 8px;
}

.closing-art img {
  width: 100%;
  /* Square cover-crop, owner's pick (2026-07-14): the tight crop on the
   * gate reads stronger in the frame than the full wide banner. */
  aspect-ratio: 1 / 1;
  object-fit: cover;
  /* window shifted right so the hooded vessel stands fully inside the crop */
  object-position: 62% 50%;
  display: block;
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
}

.closing-text {
  flex: 1 1 360px;
  max-width: 480px;
  text-align: center;
}

.closing-text h2 {
  text-align: center;
}

.closing-text .lede {
  margin-bottom: 24px;
}

/* ── footer small print ──────────────────────────────────────────────── */

.footer-note {
  color: var(--ink-dim);
  font-size: 0.8rem;
}

/* ── floating reward badge (goosebump checklist #7) — points at the real
 * in-game daily reward, never a fake/invented promo. ───────────────────── */

.floating-badge {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px 6px 6px;
  border-radius: 999px;
  background: linear-gradient(to bottom, var(--metal-light), var(--metal));
  border: 2px solid var(--filigree);
  box-shadow: var(--shadow-deep);
  color: var(--gold);
  font-size: 0.78rem;
  font-weight: 700;
  text-decoration: none;
  max-width: 240px;
}

.floating-badge:hover {
  border-color: var(--gold);
  text-decoration: none;
}

.floating-badge img {
  width: 32px;
  height: 32px;
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
}

@media (prefers-reduced-motion: no-preference) {
  .floating-badge img {
    animation: badge-pulse 2.4s ease-in-out infinite;
  }
}

@keyframes badge-pulse {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.1);
  }
}

/* Fixed + bottom-right collides with the hero's own bottom-anchored CTA
 * stack on phones (there is no room to relocate it without covering other
 * content) — hidden below tablet width rather than crowding the primary
 * conversion path. */
@media (max-width: 720px) {
  .floating-badge {
    display: none;
  }
}

@media (max-width: 600px) {
  .hero {
    /* Tightened (was 76px/48px): the bottom-anchored hero stack overflowed
     * 100svh by ~16px on a 390x844 phone, slicing the trust pills at the
     * fold — the whole conversion stack must sit above it. */
    padding: 60px 16px 28px;
  }

  .hero-vision {
    margin-bottom: 14px;
  }

  .hero .hook {
    margin-bottom: 14px;
  }

  .hero .cta-row {
    margin-bottom: 10px;
  }

  .section {
    padding: 40px 16px;
  }
}

/* ── floor 100 tease — menace without spoilers: boss-tease.png is already a
 * dark silhouette with glowing eyes, so brightness only needs to lift it
 * enough for the eyes/red glow to read, not crush it further. The eyes sit
 * at the very top of the source frame, so anchor there. */
#floor-100 {
  position: relative;
  /* .boss-tease::before below uses a negative inset (same trick as
   * .hero-inner::before) — .hero clips that via its own overflow: hidden,
   * so this section needs the same or the scrim bleeds past the viewport
   * edge and inflates horizontal scroll on narrow screens. */
  overflow: hidden;
  min-height: 480px;
  display: flex;
  align-items: center;
}

.boss-tease-bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  background-size: cover;
  background-position: center top;
  filter: brightness(0.82) saturate(1.15);
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
}

.boss-tease-veil {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: radial-gradient(
      ellipse 55% 60% at 50% 45%,
      rgba(160, 30, 60, 0.22) 0%,
      rgba(80, 20, 90, 0.16) 45%,
      transparent 75%
    ),
    linear-gradient(to bottom, var(--bg) 0%, transparent 18%, transparent 82%, var(--bg) 100%);
}

.boss-tease {
  position: relative;
  text-align: center;
}

/* Local scrim behind the headline only (same trick as .hero-inner::before) —
 * keeps text legible now that the art underneath reads brighter. */
.boss-tease::before {
  content: '';
  position: absolute;
  inset: -10% -14%;
  z-index: -1;
  background: radial-gradient(
    ellipse 62% 58% at 50% 50%,
    rgba(6, 7, 11, 0.55) 0%,
    rgba(6, 7, 11, 0.3) 55%,
    transparent 78%
  );
  pointer-events: none;
}

.boss-tease h2 {
  color: #f0d8da;
  text-shadow: 0 0 24px rgba(160, 30, 60, 0.55), 0 4px 18px rgba(0, 0, 0, 0.9);
}

.boss-tease-note {
  color: var(--gold);
  font-size: 0.86rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.9);
}

/* ── ambient particles — subtle, compositor-only (transform + opacity) atmo:
 * golden dust in the hero, snow on the frost band, embers on the closing
 * volcano/abyss band. Positions/timing come from --x/--delay/--dur set
 * inline per span so one small ruleset covers all instances. Kept off text
 * by biasing hero motes to the left/right edges. Fully removed under
 * prefers-reduced-motion: reduce (display: none, zero cost). */
.ambient-particles {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
  pointer-events: none;
}

.ambient-particles span {
  position: absolute;
  left: var(--x, 50%);
  width: 3px;
  height: 3px;
  border-radius: 50%;
  opacity: 0;
  animation-delay: var(--delay, 0s);
}

.hero-motes {
  z-index: 1;
}

.hero-motes span {
  bottom: 8%;
  background: var(--gold);
  box-shadow: 0 0 6px rgba(244, 197, 66, 0.75);
}

.snow-particles span {
  top: -4%;
  width: 2px;
  height: 2px;
  background: #eef4ff;
  box-shadow: 0 0 4px rgba(230, 240, 255, 0.7);
}

.ember-particles span {
  bottom: 2%;
  background: #ff8a3d;
  box-shadow: 0 0 6px rgba(255, 120, 40, 0.8);
}

@media (prefers-reduced-motion: no-preference) {
  .hero-motes span {
    animation-name: mote-rise;
    animation-duration: var(--dur, 16s);
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
  }

  .snow-particles span {
    animation-name: snow-fall;
    animation-duration: var(--dur, 8s);
    animation-timing-function: linear;
    animation-iteration-count: infinite;
  }

  .ember-particles span {
    animation-name: ember-rise;
    animation-duration: var(--dur, 12s);
    animation-timing-function: ease-in;
    animation-iteration-count: infinite;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ambient-particles {
    display: none;
  }
}

@keyframes mote-rise {
  0% {
    transform: translateY(0) scale(1);
    opacity: 0;
  }
  12% {
    opacity: 0.55;
  }
  88% {
    opacity: 0.3;
  }
  100% {
    transform: translateY(-65vh) scale(1.15);
    opacity: 0;
  }
}

@keyframes snow-fall {
  0% {
    transform: translate3d(0, -10%, 0);
    opacity: 0;
  }
  10% {
    opacity: 0.8;
  }
  90% {
    opacity: 0.55;
  }
  100% {
    transform: translate3d(8px, 92vh, 0);
    opacity: 0;
  }
}

@keyframes ember-rise {
  0% {
    transform: translate(0, 0);
    opacity: 0;
  }
  14% {
    opacity: 0.75;
  }
  85% {
    opacity: 0.35;
  }
  100% {
    transform: translate(10px, -55vh);
    opacity: 0;
  }
}

/* ── leaderboard popup ──────────────────────────────────────────────────
 * Enhancement only: the trigger is meaningless without JS, so it stays
 * hidden until the html.js bootstrap class lands. */
html:not(.js) .lb-trigger {
  display: none;
}

/* Author display:flex would otherwise override the hidden attribute's UA
 * display:none and the invisible overlay would swallow every click on the
 * page (same [hidden] trap as the select panels). */
.lb-overlay[hidden] {
  display: none;
}

.lb-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(6, 7, 11, 0.78);
  backdrop-filter: blur(6px);
}

body.lb-locked {
  overflow: hidden;
}

.lb-modal {
  position: relative;
  width: min(480px, 100%);
  max-height: min(70svh, 640px);
  overflow-y: auto;
  padding: 26px 26px 22px;
  background: linear-gradient(to bottom, var(--metal-light), var(--metal));
}

.lb-modal h2 {
  margin: 0 0 4px;
  color: var(--gold);
  font-size: 1.4rem;
}

.lb-sub {
  margin: 0 0 14px;
  color: var(--ink-dim);
  font-size: 0.88rem;
}

/* Per-difficulty board tabs (Normal / Hard / Nightmare), matching the game's
 * three tiers. Active tab reads as the site's gold "material". */
.lb-tabs {
  display: flex;
  gap: 8px;
  margin: 0 0 16px;
}

.lb-tab {
  flex: 1;
  padding: 8px 6px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--well);
  color: var(--ink-dim);
  font-weight: 700;
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.lb-tab:hover {
  color: var(--ink);
  border-color: var(--border-hi);
}

.lb-tab.is-active {
  color: var(--metal-dark);
  background: linear-gradient(to bottom, #ffd968, var(--gold));
  border-color: var(--filigree);
}

.lb-status {
  color: var(--ink-dim);
  font-size: 0.9rem;
  margin: 12px 0;
}

.lb-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--border-hi);
  background: var(--well);
  color: var(--gold);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.lb-close:hover {
  border-color: var(--gold);
}

.lb-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.lb-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 10px;
  border-bottom: 1px solid rgba(184, 146, 58, 0.16);
  color: var(--ink);
  font-size: 0.94rem;
}

.lb-row:last-child {
  border-bottom: none;
}

.lb-rank {
  flex: 0 0 34px;
  text-align: center;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

.lb-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lb-floor {
  flex: 0 0 auto;
  color: var(--gold);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.lb-rank-1 {
  background: rgba(244, 197, 66, 0.1);
}

.lb-rank-2 {
  background: rgba(200, 205, 215, 0.07);
}

.lb-rank-3 {
  background: rgba(205, 127, 50, 0.08);
}

/* ── select stage — the WHOLE section wears the active hero's backdrop and
 * crossfades on switch (owner request: the section should transform with
 * the hero, and the art deserves to be seen). Sits above the crypt biome
 * band (-3) and below the fade veil content. */
.select-stage {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
}

.select-stage-layer {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transition: opacity 0.7s ease;
  filter: brightness(0.74) saturate(1.05);
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
}

#heroes[data-active-class='knight'] .select-stage-layer[data-stage='knight'],
#heroes[data-active-class='mage'] .select-stage-layer[data-stage='mage'],
#heroes[data-active-class='healer'] .select-stage-layer[data-stage='healer'],
#heroes[data-active-class='archer'] .select-stage-layer[data-stage='archer'],
#heroes[data-active-class='samurai'] .select-stage-layer[data-stage='samurai'],
#heroes[data-active-class='berserker'] .select-stage-layer[data-stage='berserker'] {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .select-stage-layer {
    transition: none;
  }
}
