/* ==========================================================================
   main.css — layout and components
   --------------------------------------------------------------------------
   Every colour, size, radius and duration here comes from css/tokens.css.
   There should be no raw hex values in this file. If you find one, it is a
   bug: add it to tokens.css and reference it instead.

   Written mobile-first — the plain rules describe a phone held upright, and
   the @media blocks near the bottom adapt for bigger screens and for a phone
   turned sideways.

   Sections:
      1. Page foundation
      2. Application root
      3. The scene (background layers, motes)
      4. HUD
      5. Screens
      6. Panels and typography
      7. Buttons
      8. Choice cards
      9. Result screens
     10. Utilities
     11. Tablet and desktop
     12. Landscape
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. PAGE FOUNDATION
   -------------------------------------------------------------------------- */
body {
  font-family: var(--font-body);
  font-weight: 400;
  color: var(--color-text);
  background-color: var(--color-ground-deep);

  /* Stop the whole page rubber-banding on mobile. Individual screens scroll
     inside themselves when their content is tall. */
  overflow: hidden;
  overscroll-behavior: none;
}

/* --------------------------------------------------------------------------
   2. APPLICATION ROOT
   #app holds the scene, the HUD and every screen.
   -------------------------------------------------------------------------- */
.app {
  position: relative;
  display: flex;
  flex-direction: column;

  width: 100%;
  height: 100%;
  margin: 0 auto;
  max-width: var(--app-max-width);
  overflow: hidden;

  /* Keeps content clear of phone notches and the iOS home bar. Zero on
     devices that do not have them. */
  padding:
    env(safe-area-inset-top)
    env(safe-area-inset-right)
    env(safe-area-inset-bottom)
    env(safe-area-inset-left);
}

/* Use the SMALL viewport height so a mobile address bar sliding in and out
   never cuts off the bottom of the screen. */
@supports (height: 100svh) {
  .app {
    height: 100svh;
  }
}

/* --------------------------------------------------------------------------
   3. THE SCENE
   Five stacked layers behind everything, plus drifting motes of light.
   Which images go in the layers is decided by css/worlds.css.
   -------------------------------------------------------------------------- */
.scene {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;   /* the scene is scenery — never catches a tap */
}

.scene__layer {
  position: absolute;
  /* Slightly larger than the box so the parallax shift never exposes an edge. */
  inset: -3%;
  background-repeat: no-repeat;
  background-size: cover;
  background-position: var(--layer-focus-x) var(--layer-focus-y);
  will-change: transform;
}

/* Layer 1 — the sky. Falls back to the world's gradient when there is no
   artwork yet, which is the case today. */
.scene__layer--sky {
  background-image: var(--layer-sky, none), var(--layer-sky-fallback);
}

.scene__layer--far      { background-image: var(--layer-far, none); }
.scene__layer--mid      { background-image: var(--layer-mid, none); }
.scene__layer--near     { background-image: var(--layer-near, none); }

/* Layer 5 — foreground darkening. The gradient part is lighting, not
   illustration, so it stays even once artwork arrives. */
.scene__layer--vignette {
  background-image: var(--layer-vignette, none);
  box-shadow: inset 0 0 180px 60px var(--color-ground-deep);
}

/* --- motes of light ------------------------------------------------------
   Eight small glowing dots drifting upward. Cheap: each is one element
   animating only transform and opacity. Switched off entirely for anyone who
   has asked their device for less motion (see css/reset.css). */
.scene__motes {
  position: absolute;
  inset: 0;
}

.mote {
  position: absolute;
  bottom: -6%;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--color-gold-bright);
  box-shadow: 0 0 8px 2px var(--tint-bright-45);
  opacity: 0;
  animation: mote-drift 18s linear infinite;
}

/* Each mote gets its own lane, size, speed and start time, so they never
   look like a row of identical dots. */
.mote:nth-child(1) { left:  8%; animation-duration: 17s; animation-delay:  0s; }
.mote:nth-child(2) { left: 21%; animation-duration: 22s; animation-delay: -4s; transform: scale(0.7); }
.mote:nth-child(3) { left: 35%; animation-duration: 15s; animation-delay: -9s; }
.mote:nth-child(4) { left: 48%; animation-duration: 20s; animation-delay: -2s; transform: scale(1.3); }
.mote:nth-child(5) { left: 62%; animation-duration: 24s; animation-delay: -13s; transform: scale(0.6); }
.mote:nth-child(6) { left: 74%; animation-duration: 16s; animation-delay: -6s; }
.mote:nth-child(7) { left: 86%; animation-duration: 21s; animation-delay: -16s; transform: scale(0.9); }
.mote:nth-child(8) { left: 94%; animation-duration: 19s; animation-delay: -11s; transform: scale(0.5); }

@keyframes mote-drift {
  0%   { transform: translateY(0) translateX(0);      opacity: 0; }
  12%  {                                              opacity: 0.65; }
  70%  {                                              opacity: 0.5; }
  100% { transform: translateY(-102vh) translateX(18px); opacity: 0; }
}

/* --------------------------------------------------------------------------
   4. HUD — health, XP and coins
   Three columns: MAP on the left, the numbers centred, an empty column on the
   right to balance them. Because the outer columns size to their content the
   numbers stay centred whether MAP is showing or not, and nothing can ever
   sit on top of anything else.
   -------------------------------------------------------------------------- */
.hud {
  position: relative;
  z-index: 2;

  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-2);

  padding: var(--space-2) var(--space-3);
  border-bottom: var(--border-gold);
  background: linear-gradient(180deg, var(--color-scrim), var(--tint-ground-25));
  backdrop-filter: blur(6px);
  flex-shrink: 0;
}

/* --- the ← MAP control --------------------------------------------------- */
.hud__nav {
  grid-column: 1;
  justify-self: start;

  display: flex;
  align-items: center;
  gap: var(--space-1);

  min-height: var(--tap-min);
  padding: 0 var(--space-3);
  border-radius: var(--radius-chip);

  font-family: var(--font-display);
  font-size: var(--text-meta);
  letter-spacing: var(--tracking-label);
  white-space: nowrap;

  color: var(--color-text-soft);
  border: var(--border-gold);
  background: var(--tint-light-04);

  transition:
    color var(--dur-quick) var(--ease),
    border-color var(--dur-quick) var(--ease),
    transform var(--dur-press) var(--ease);
}

.hud__nav .arrow {
  font-size: 1.05em;
  line-height: 1;
}

.hud__nav:active {
  transform: scale(0.96);
  color: var(--color-gold);
}

/* --- the three stats ----------------------------------------------------- */
.hud__stats {
  grid-column: 2;
  justify-self: center;

  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  min-width: 0;

  /* The bar gained a fifth item in Phase 4B (magic). On a narrow phone the row
     no longer fits beside the MAP button, so it is allowed to wrap rather than
     slide underneath it. The media query below usually prevents the wrap. */
  flex-wrap: wrap;
  row-gap: var(--space-1);
}

.hud__stat {
  position: relative;   /* anchors the floating "+10" chip */
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--tint-ground-35);
  border: var(--border-soft);
  line-height: 1;
}

.hud__hearts {
  display: flex;
  align-items: center;
  gap: 3px;
}

.hud__icon {
  width: 18px;
  height: 18px;
  display: block;
  flex-shrink: 0;
}

/* Each stat carries its own colour AND its own icon shape, so the HUD is
   still readable to someone who cannot distinguish the hues. */
.icon-heart      { color: var(--color-health); }
.icon-heart-lost { color: var(--color-twilight-violet); }
.icon-xp         { color: var(--color-xp); }
.icon-coin       { color: var(--color-coin); }
.icon-magic      { color: var(--color-magic); }

/* A heart that has just been lost shrinks into its empty state. */
.hud__icon--fading {
  animation: heart-lost 300ms var(--ease);
}

@keyframes heart-lost {
  0%   { transform: scale(1.25); }
  60%  { transform: scale(0.82); }
  100% { transform: scale(1); }
}

.hud__value {
  font-variant-numeric: tabular-nums;   /* digits do not jiggle as they change */
  font-weight: 600;
  font-size: var(--text-meta);
  color: var(--color-text);
}

/* The "+10" that floats up when a reward lands. */
.hud__gain {
  position: absolute;
  left: 50%;
  top: 0;
  transform: translate(-50%, 0);
  font-size: 0.78rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: none;
  animation: gain-rise 900ms var(--ease) forwards;
}

@keyframes gain-rise {
  0%   { opacity: 0; transform: translate(-50%, 4px)   scale(0.9); }
  18%  { opacity: 1; transform: translate(-50%, -6px)  scale(1); }
  70%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -26px) scale(1); }
}

.hud__stat--pulse .hud__icon {
  animation: stat-pulse 400ms var(--ease);
}

@keyframes stat-pulse {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.28); }
  100% { transform: scale(1); }
}

/* --------------------------------------------------------------------------
   5. SCREENS
   One screen is visible at a time; js/screens.js swaps .screen--active.
   -------------------------------------------------------------------------- */
.screens {
  position: relative;
  z-index: 1;
  flex: 1;
  overflow: hidden;
}

.screen {
  position: absolute;
  inset: 0;
  display: none;
  flex-direction: column;

  /* Content taller than the window scrolls inside its own screen. */
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;

  padding: var(--space-5) var(--space-4);

  /* See the note on .panel below. */
  justify-content: safe center;
}

.screen--active {
  display: flex;
  animation: screen-in var(--dur-screen) var(--ease);
}

@keyframes screen-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* --------------------------------------------------------------------------
   6. PANELS AND TYPOGRAPHY
   .panel is the storybook card.

   Centring it vertically is done by the SCREEN, not by an auto margin. An
   auto margin centres beautifully while the panel fits, but when the panel is
   taller than the screen it pushes the top edge out of reach and no amount of
   scrolling brings it back. `justify-content: safe center` centres only while
   there is room and falls back to the top edge when there is not. Browsers
   too old for `safe` ignore the declaration and top-align, which is also
   fine — the one thing that must never happen is content becoming unreachable.
   -------------------------------------------------------------------------- */
.panel {
  position: relative;
  margin: 0 auto;
  width: 100%;
  max-width: var(--panel-max-width);

  display: flex;
  flex-direction: column;
  gap: var(--space-4);

  padding: var(--space-5) var(--space-4);
  border-radius: var(--radius-card);
  border: var(--border-gold);
  background: var(--color-surface-veil);
  box-shadow: var(--shadow-panel);
  backdrop-filter: blur(10px);
}

/* A hairline of gold light along the top edge — the one ornament the panel
   gets, and it is drawn rather than being an image file. */
.panel::before {
  content: "";
  position: absolute;
  top: 0;
  left: 12%;
  right: 12%;
  height: 1px;
  background: linear-gradient(90deg,
    transparent,
    var(--tint-bright-55) 50%,
    transparent);
}

.panel--center {
  text-align: center;
  align-items: center;
}

/* The title screen's panel is the one place with no frame — the wordmark
   should sit in the sky, not in a box. */
.panel--bare {
  background: none;
  border: 0;
  box-shadow: none;
  backdrop-filter: none;
}

.panel--bare::before {
  display: none;
}

.title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-display);
  line-height: 1.08;
  letter-spacing: var(--tracking-display);
  color: var(--color-gold);
  text-shadow:
    0 0 30px var(--tint-gold-45),
    0 2px 0 var(--tint-shadow-35);
  overflow-wrap: break-word;
  text-wrap: balance;
}

.subtitle {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(0.95rem, 4vmin, 1.2rem);
  color: var(--color-text-soft);
  max-width: var(--reading-max);
}

.heading {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-heading);
  line-height: 1.18;
  letter-spacing: 0.05em;
  color: var(--color-gold);
  text-shadow: 0 0 20px var(--tint-gold-30);
  text-wrap: balance;
}

.heading--warning {
  color: var(--color-failure);
  text-shadow: 0 0 20px var(--tint-failure-28);
}

.body-text {
  font-size: var(--text-body);
  line-height: 1.65;
  color: var(--color-text);
  max-width: var(--reading-max);
}

.text-muted {
  color: var(--color-text-soft);
}

/* The large emoji at the top of a screen. Decorative — always aria-hidden. */
.scene-icon {
  font-size: clamp(2.5rem, 12vmin, 3.5rem);
  line-height: 1;
  filter: drop-shadow(0 0 18px var(--tint-gold-35));
}

/* A short gold rule used to separate a heading from its story text. */
.flourish {
  width: 84px;
  height: 8px;
  color: var(--color-gold);
  opacity: 0.8;
}

.version {
  margin-top: var(--space-2);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--color-text-soft);
}

/* --------------------------------------------------------------------------
   7. BUTTONS
   -------------------------------------------------------------------------- */
.buttons {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  width: 100%;
  max-width: 20rem;
  margin-top: var(--space-2);
}

.btn {
  position: relative;
  min-height: var(--tap-comfort);
  padding: var(--space-2) var(--space-5);
  border-radius: var(--radius-control);

  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);

  border: var(--border-gold-firm);
  color: var(--color-gold);
  background: var(--tint-gold-08);

  transition:
    transform var(--dur-press) var(--ease),
    box-shadow var(--dur-quick) var(--ease),
    background-color var(--dur-quick) var(--ease),
    border-color var(--dur-quick) var(--ease);
}

/* The one glowing control on a screen. If two things glow, neither reads as
   the thing to press. */
.btn--primary {
  background:
    linear-gradient(180deg, var(--tint-bright-34), var(--tint-gold-14));
  box-shadow: var(--glow-gold);
  color: var(--color-on-gold);
  border-color: var(--color-gold);
}

/* A slow sheen crossing the primary button — the only ambient motion in the
   interface itself. */
.btn--primary::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(105deg,
    transparent 38%,
    var(--tint-light-16) 50%,
    transparent 62%);
  background-size: 260% 100%;
  animation: sheen 5.5s ease-in-out infinite;
  pointer-events: none;
}

@keyframes sheen {
  0%, 62% { background-position: 190% 0; }
  100%    { background-position: -60% 0; }
}

.btn--secondary {
  border-color: var(--tint-gold-40);
  color: var(--color-text-soft);
  background: var(--tint-light-04);
}

/* :active fires on a tap as well as a click, so this works on phones.
   Nothing important ever depends on :hover. */
.btn:active {
  transform: scale(0.97);
}

@media (hover: hover) {
  .btn:hover {
    background-color: var(--tint-gold-20);
    border-color: var(--color-gold-bright);
  }
  .btn--primary:hover {
    box-shadow: var(--glow-strong);
  }
  .hud__nav:hover {
    color: var(--color-gold);
    border-color: var(--color-gold);
  }
}

/* --------------------------------------------------------------------------
   FOCUS
   A thick white ring, so it stays visible against both the interface and any
   artwork behind it. This is the one style that must never be weakened.
   -------------------------------------------------------------------------- */
.btn:focus-visible,
.choice:focus-visible,
.node:focus-visible,
.hud__nav:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 3px;
}

/* --------------------------------------------------------------------------
   8. CHOICE CARDS
   The whole card is the button. The artwork, the label and the glow all live
   inside the same <button>, so there is never a hit target that misses.
   -------------------------------------------------------------------------- */
.choices {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  width: 100%;
  margin-top: var(--space-1);
}

.choice {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-4);

  min-height: var(--tap-choice);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-control);
  overflow: hidden;

  /* Each card is tinted by its own path colour, set inline by js/ui.js as
     --path-hue. That is the only per-choice value the CSS needs.
     These two are the plain fallback; the @supports block below tints them
     with the path colour on browsers that can mix colours. */
  border: 2px solid var(--tint-gold-40);
  background: var(--color-surface-raised);
  color: var(--color-text);
  text-align: left;

  transition:
    transform var(--dur-press) var(--ease),
    border-color var(--dur-quick) var(--ease),
    box-shadow var(--dur-quick) var(--ease),
    opacity var(--dur-quick) var(--ease);
}

/* A soft pool of the path's own light behind its emblem. */
.choice::before {
  content: "";
  position: absolute;
  left: -10%;
  top: 50%;
  width: 46%;
  height: 200%;
  transform: translateY(-50%);
  pointer-events: none;
}

/* color-mix() lets one path colour produce the border, the fill and the glow,
   so adding a path later means adding one colour and nothing else.
   Everything inside is an enhancement — the cards are complete without it. */
@supports (color: color-mix(in srgb, red 50%, blue)) {
  .choice {
    border-color: color-mix(in srgb, var(--path-hue) 45%, transparent);
    background:
      linear-gradient(135deg,
        color-mix(in srgb, var(--path-hue) 14%, var(--color-surface-raised)),
        var(--color-surface));
  }

  .choice::before {
    background: radial-gradient(closest-side,
      color-mix(in srgb, var(--path-hue) 30%, transparent),
      transparent);
  }

  .choice__art {
    filter: drop-shadow(0 0 10px color-mix(in srgb, var(--path-hue) 55%, transparent));
  }
}

.choice__art {
  position: relative;
  width: 46px;
  height: 46px;
  flex-shrink: 0;
  color: var(--path-hue);
}

.choice__art svg {
  width: 100%;
  height: 100%;
  display: block;
}

.choice__label {
  position: relative;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.15rem;
  letter-spacing: var(--tracking-label);
}

.choice:active {
  transform: scale(0.98);
}

@media (hover: hover) {
  .choice:hover {
    border-color: var(--path-hue);
    box-shadow: var(--glow-gold);
  }
}

/* --- the moment after a choice is made ----------------------------------- */

/* The card that was picked, and turned out right. */
.choice--correct {
  border-color: var(--color-gold-bright);
  box-shadow: var(--glow-strong);
  animation: choice-lift var(--dur-quick) var(--ease);
}

@keyframes choice-lift {
  0%   { transform: scale(0.98); }
  60%  { transform: scale(1.03); }
  100% { transform: scale(1); }
}

/* The card that was picked, and turned out wrong. Shakes briefly — this
   animation in particular must stay behind the reduced-motion guard. */
.choice--wrong {
  border-color: var(--color-failure);
  animation: choice-refuse 420ms var(--ease);
}

@keyframes choice-refuse {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(-6px); }
  45%      { transform: translateX(5px); }
  70%      { transform: translateX(-3px); }
}

/* The two cards that were not picked fade back while the result resolves. */
.choice--dimmed {
  opacity: 0.35;
}

/* --------------------------------------------------------------------------
   9. RESULT SCREENS
   Colour is a bonus signal. The icon and the words are what actually tell the
   player what happened.
   -------------------------------------------------------------------------- */
.rewards {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: 1.05rem;
  font-weight: 600;
}

.rewards__item {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--tint-ground-40);
  border: var(--border-soft);
  font-variant-numeric: tabular-nums;

  /* Each reward arrives just after the one above it. js/ui.js sets the delay. */
  opacity: 0;
  animation: reward-in 380ms var(--ease) forwards;
  animation-delay: var(--reward-delay, 0ms);
}

@keyframes reward-in {
  from { opacity: 0; transform: translateY(8px) scale(0.96); }
  to   { opacity: 1; transform: translateY(0)   scale(1); }
}

.result--success .heading {
  color: var(--color-success);
  text-shadow: 0 0 22px var(--tint-success-30);
}
.result--success .rewards__item {
  color: var(--color-success);
  border-color: var(--tint-success-30);
}
.result--success {
  box-shadow: var(--shadow-panel), var(--glow-success);
}

.result--failure .heading {
  color: var(--color-failure);
  text-shadow: 0 0 22px var(--tint-failure-28);
}
.result--failure .rewards__item {
  color: var(--color-failure);
  border-color: var(--tint-failure-30);
}
.result--failure {
  box-shadow: var(--shadow-panel), var(--glow-failure);
}

/* The success bloom — a ring of light expanding once behind the icon. */
.result__bloom {
  position: absolute;
  left: 50%;
  top: 12%;
  width: 180px;
  height: 180px;
  margin: -90px 0 0 -90px;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(closest-side,
    var(--tint-bright-50),
    var(--tint-bright-12) 55%,
    transparent 72%);
  opacity: 0;
}

.result--success .result__bloom {
  animation: bloom var(--dur-reward) var(--ease);
}

@keyframes bloom {
  0%   { opacity: 0;    transform: scale(0.3); }
  35%  { opacity: 0.95; }
  100% { opacity: 0;    transform: scale(1.5); }
}

/* Sparkles rising through the success panel. Built by js/ui.js. */
.sparkle {
  position: absolute;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--color-gold-bright);
  box-shadow: 0 0 10px 3px var(--tint-bright-55);
  opacity: 0;
  pointer-events: none;
  animation: sparkle-rise 900ms var(--ease) forwards;
}

@keyframes sparkle-rise {
  0%   { opacity: 0; transform: translateY(10px) scale(0.4); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(-70px) scale(1); }
}

.result__icon {
  font-size: clamp(2.5rem, 12vmin, 3.5rem);
  line-height: 1;
  position: relative;
}

/* --------------------------------------------------------------------------
   10. UTILITIES
   -------------------------------------------------------------------------- */

/* Read by screen readers, invisible on screen. Used to say in words what the
   icons show as pictures. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The SVG sprite that holds every icon shape. */
.icon-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   11. TABLET AND DESKTOP
   The mobile styles above are the default; these add room.
   -------------------------------------------------------------------------- */
@media (min-width: 700px) {
  .screen {
    padding: var(--space-6) var(--space-5);
  }

  .panel {
    max-width: 40rem;
    padding: var(--space-6);
    gap: var(--space-5);
    box-shadow: var(--shadow-card);
  }

  /* Three paths side by side once there is room for them. */
  .choices {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-4);
  }

  .choice {
    flex-direction: column;
    justify-content: center;
    text-align: center;
    min-height: 150px;
    gap: var(--space-3);
    padding: var(--space-4);
  }

  .choice::before {
    left: 50%;
    top: 34%;
    width: 78%;
    height: 78%;
    transform: translate(-50%, -50%);
  }

  .choice__art {
    width: 58px;
    height: 58px;
  }

  .hud {
    padding: var(--space-2) var(--space-4);
  }

  .hud__icon {
    width: 20px;
    height: 20px;
  }
}

/* --------------------------------------------------------------------------
   12. LANDSCAPE — a phone or small tablet turned sideways
   --------------------------------------------------------------------------
   Sideways there is width to spare and very little height, so the content
   reflows into two columns instead of shrinking. The max-height condition is
   what makes this a phone rule: a desktop monitor is also landscape but has
   plenty of height, so it keeps the roomy layout above.
   -------------------------------------------------------------------------- */
@media (orientation: landscape) and (max-height: 600px) {

  [data-world] {
    --layer-focus-y: 46%;
  }

  .hud {
    padding: var(--space-1) var(--space-3);
  }

  .screen {
    padding: var(--space-3) var(--space-4);
  }

  .panel {
    max-width: 52rem;
    padding: var(--space-4);
    gap: var(--space-3);
  }

  /* Story on the left, buttons on the right. Everything inside the panel goes
     to column 1 by default; only .buttons is moved across. */
  .panel--center {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(10rem, auto);
    column-gap: var(--space-5);
    row-gap: var(--space-2);
    align-content: center;
    align-items: center;
    text-align: left;
  }

  .panel--center > * {
    grid-column: 1;
    justify-self: start;
  }

  .panel--center > .buttons {
    grid-column: 2;
    grid-row: 1 / -1;
    align-self: center;
    margin-top: 0;
    max-width: none;
  }

  .scene-icon,
  .result__icon {
    font-size: clamp(1.75rem, 9vmin, 2.5rem);
  }

  .version {
    margin-top: 0;
  }

  .choices {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3);
    margin-top: 0;
  }

  .choice {
    flex-direction: column;
    justify-content: center;
    text-align: center;
    min-height: var(--tap-choice-landscape);
    gap: var(--space-1);
    padding: var(--space-2);
  }

  .choice::before {
    left: 50%;
    top: 36%;
    width: 76%;
    height: 76%;
    transform: translate(-50%, -50%);
  }

  .choice__art {
    width: 34px;
    height: 34px;
  }

  .choice__label {
    font-size: 0.95rem;
  }

  .btn {
    min-height: 46px;
    font-size: 0.95rem;
  }

  .rewards {
    font-size: 0.95rem;
  }

  .rewards__item {
    padding: var(--space-1) var(--space-3);
  }
}

/* Very short landscape — a small phone sideways. Squeeze out the last of the
   vertical spacing, but never the tap targets. */
@media (orientation: landscape) and (max-height: 380px) {
  /* Text one step smaller and set tighter — the story still has to be
     comfortable to read, so this is as far as it goes. */
  [data-world] {
    --text-body: clamp(0.85rem, 3.6vmin, 0.95rem);
  }

  .panel {
    gap: var(--space-1);
    padding: var(--space-2) var(--space-3);
  }

  .body-text {
    line-height: 1.45;
  }

  /* The gold rule is pure ornament, and ornament is the first thing to go
     when there is no room for it. */
  .flourish {
    display: none;
  }

  .scene-icon,
  .result__icon {
    font-size: 1.6rem;
  }

  .choice {
    min-height: 58px;
  }

  .choice__art {
    width: 26px;
    height: 26px;
  }

  .btn {
    min-height: var(--tap-min);
  }
}


/* ==========================================================================
   12. WORLD 1 — MAP, GATES AND THE NEW CHALLENGE TYPES
   --------------------------------------------------------------------------
   Everything added in Phase 4B. Built from the same tokens as the rest of the
   game: there is not a single raw colour below.
   ========================================================================== */

/* --------------------------------------------------------------------------
   THE WORLD MAP
   A trail of five nodes. State is never colour alone — every node also
   carries an icon and a word.
   -------------------------------------------------------------------------- */
.panel--map {
  max-width: 30rem;
}

.map__progress {
  font-size: var(--text-meta);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-gold);
  margin-top: calc(var(--space-2) * -1);
  margin-bottom: var(--space-4);
}

.map__trail {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 100%;
  margin: 0 0 var(--space-4);
  padding: 0;
}

/* The dotted line joining one node to the next. It is decoration, so it is
   drawn on the list item rather than on the button. */
.map__step {
  position: relative;
}

.map__step + .map__step::before {
  content: "";
  position: absolute;
  left: 34px;
  top: calc(var(--space-2) * -1);
  width: 2px;
  height: var(--space-2);
  background-image: linear-gradient(var(--color-twilight-violet) 60%, transparent 0);
  background-size: 2px 5px;
}

.node {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  min-height: var(--tap-comfort);
  padding: var(--space-3) var(--space-4);
  border: var(--border-soft);
  border-radius: var(--radius-control);
  background: var(--color-surface);
  color: var(--color-text);
  text-align: left;
  transition:
    transform var(--dur-press) var(--ease),
    border-color var(--dur-quick) var(--ease),
    box-shadow var(--dur-quick) var(--ease);
}

.node__dot {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--color-twilight-violet);
  background: var(--color-ground);
  color: var(--color-twilight-haze);
}

.node__icon {
  width: 20px;
  height: 20px;
}

.node__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.node__name {
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 600;
}

.node__state {
  font-size: var(--text-meta);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-text-soft);
}

/* --- locked ---------------------------------------------------------------
   Still a real button, so it can be reached by keyboard and still explains
   itself. It simply refuses to open. */
.node--locked {
  opacity: 0.55;
  cursor: not-allowed;
}

.node--locked .node__name {
  color: var(--color-text-soft);
}

/* --- open, and the one to play next --------------------------------------- */
.node--available .node__dot,
.node--current .node__dot {
  border-color: var(--color-gold);
  color: var(--color-gold);
}

.node--current {
  border-color: var(--color-gold);
  box-shadow: var(--glow-gold);
}

.node--current .node__dot {
  border-color: var(--color-gold-bright);
  color: var(--color-gold-bright);
  animation: node-pulse 2.6s var(--ease) infinite;
}

.node--current .node__state {
  color: var(--color-gold-bright);
}

@keyframes node-pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--tint-gold-35); }
  50%      { box-shadow: 0 0 0 8px transparent; }
}

/* --- completed ------------------------------------------------------------ */
.node--completed .node__dot {
  border-color: var(--color-gold);
  background: var(--color-gold);
  color: var(--color-ground-deep);
}

.node--completed .node__state {
  color: var(--color-gold);
}

/* A stage finished without losing a single heart keeps a thin outer ring. */
.node--perfect .node__dot {
  box-shadow: 0 0 0 3px var(--color-ground), 0 0 0 5px var(--color-gold-bright);
}

.node:active {
  transform: scale(0.99);
}

@media (hover: hover) {
  .node:not(.node--locked):hover {
    border-color: var(--color-gold);
    box-shadow: var(--glow-gold);
  }
}

/* A tap on a locked node does nothing — but it answers, so the tap never
   feels like a broken button. */
.node--refused {
  animation: node-refuse 380ms var(--ease);
}

@keyframes node-refuse {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-5px); }
  60%      { transform: translateX(4px); }
}

/* --------------------------------------------------------------------------
   RUNE SHARDS — five sockets, filled as they are found
   -------------------------------------------------------------------------- */
.shards {
  list-style: none;
  display: flex;
  justify-content: center;
  gap: var(--space-2);
  margin: var(--space-3) 0;
  padding: 0;
}

.shard {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-chip);
  border: 1px dashed var(--color-twilight-violet);
  color: var(--color-twilight-violet);
  opacity: 0.6;
}

.shard__icon {
  width: 18px;
  height: 18px;
}

.shard--found {
  border: 1px solid var(--color-gold);
  background: var(--tint-gold-14);
  color: var(--color-gold-bright);
  opacity: 1;
}

/* --------------------------------------------------------------------------
   THE REFERENCE MARK — pinned above a 'find' challenge
   The answer stays on screen the whole time: this tests looking, not memory.
   -------------------------------------------------------------------------- */
.reference {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  align-self: stretch;
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-1);
  border: var(--border-gold);
  border-radius: var(--radius-control);
  background: var(--tint-gold-08);
}

.reference__art {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  color: var(--color-gold-bright);
}

.reference__icon {
  width: 100%;
  height: 100%;
}

.reference__label {
  font-size: var(--text-meta);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-text-soft);
}

/* --------------------------------------------------------------------------
   CHOICE CARDS — additions for the new challenge types
   -------------------------------------------------------------------------- */
.choice__text {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.choice__detail {
  font-size: var(--text-meta);
  color: var(--color-text-soft);
  letter-spacing: 0;
  text-transform: none;
  font-family: var(--font-body);
}

.choice__reason {
  font-size: var(--text-meta);
  color: var(--color-failure);
  font-family: var(--font-body);
}

/* A route the player cannot safely take. Shown, disabled, with the reason on
   it — never silently missing. */
.choice--blocked {
  opacity: 0.5;
  cursor: not-allowed;
}

/* The cost chip on a route card. Every price is on the card BEFORE it is
   chosen: there are no hidden dice anywhere in this game. */
.cost {
  /* A flex item, NOT absolutely positioned: an overlaid chip sat on top of
     the longer route names and made them unreadable. Sitting in the layout,
     it can never collide with the label however long the label gets. */
  flex-shrink: 0;
  margin-left: auto;
  align-self: center;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  white-space: nowrap;
  border: 1px solid currentColor;
}

/* The label takes the space that is left, and wraps rather than pushing the
   chip off the card. */
.choices--routes .choice__text {
  flex: 1 1 auto;
  min-width: 0;
}

.cost--safe   { color: var(--color-success); }
.cost--health { color: var(--color-health); }
.cost--magic  { color: var(--color-magic); }

/* Crystals sit in a row rather than a stack: the sequence reads left to
   right, and three or four of them fit comfortably. */
.choices--crystals {
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
}

.choices--crystals .choice {
  flex-direction: column;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap-choice);
  text-align: center;
}

.choices--crystals .choice__label {
  font-size: 0.78rem;
}

/* While the lights are playing the crystals cannot be pressed. */
.choices--playing .choice {
  pointer-events: none;
}

/* One crystal lighting up. A slow bloom, never a flash — nothing in this
   game pulses faster than about 1.6Hz. */
.choice--lit {
  border-color: var(--color-gold-bright);
  box-shadow: var(--glow-strong);
  transform: scale(1.04);
}

@media (prefers-reduced-motion: reduce) {
  .choice--lit {
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   GATE PIPS — the Ancient Ruins only
   -------------------------------------------------------------------------- */
.gates {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  padding: 0;
}

.gate {
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-twilight-violet);
  color: var(--color-twilight-haze);
  font-size: 0.66rem;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}

.gate--done {
  border-color: var(--color-gold);
  color: var(--color-gold);
  background: var(--tint-gold-14);
}

.gate--active {
  border-color: var(--color-gold-bright);
  color: var(--color-gold-bright);
  box-shadow: var(--glow-gold);
}

/* --------------------------------------------------------------------------
   THE SPOKEN STATUS LINE  ("Watch the crystals", then "Your turn")
   -------------------------------------------------------------------------- */
.challenge__status {
  min-height: 1.4em;
  font-size: var(--text-meta);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-gold);
  text-align: center;
}

/* --------------------------------------------------------------------------
   THE RESET CONFIRMATION
   Deleting progress is the only thing in this game that cannot be undone,
   so it is the only thing that asks first.
   -------------------------------------------------------------------------- */
.confirm {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-4);
  border: 2px solid var(--tint-failure-30);
  border-radius: var(--radius-control);
  background: var(--color-surface);
}

.confirm__title {
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  color: var(--color-failure);
}

.btn--danger {
  border: 2px solid var(--color-failure);
  background: var(--tint-failure-28);
  color: var(--color-text);
}

@media (hover: hover) {
  .btn--danger:hover {
    background: var(--tint-failure-30);
    box-shadow: var(--glow-failure);
  }
}

/* --------------------------------------------------------------------------
   LANDSCAPE — the same rules as Phase 2.1: keep height for content and
   never shrink a tap target below the 44px floor.
   -------------------------------------------------------------------------- */
@media (orientation: landscape) and (max-height: 600px) {
  .map__trail {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-2);
  }

  /* The joining line only makes sense in a single column. */
  .map__step + .map__step::before {
    display: none;
  }

  .node {
    min-height: var(--tap-min);
    padding: var(--space-2) var(--space-3);
  }

  .node__dot {
    width: 32px;
    height: 32px;
  }

  .choices--crystals {
    grid-template-columns: repeat(auto-fit, minmax(72px, 1fr));
  }

  .choices--crystals .choice {
    min-height: var(--tap-choice-landscape);
  }

  .reference {
    padding: var(--space-2) var(--space-3);
  }

  .reference__art {
    width: 34px;
    height: 34px;
  }
}


/* --------------------------------------------------------------------------
   NARROW PHONES — keeping the five-item status bar on one line
   Everything here only tightens spacing. No tap target drops below 44px.
   -------------------------------------------------------------------------- */
@media (max-width: 380px) {
  .hud {
    gap: var(--space-1);
    padding: var(--space-2);
  }

  .hud__stats {
    gap: var(--space-1);
  }

  .hud__stat {
    padding: var(--space-1);
  }

  .hud__nav {
    padding: 0 var(--space-3);
  }

  /* The arrow alone carries the meaning at this width. The button keeps its
     aria-label ("Back to the map"), so nothing is lost to a screen reader. */
  .hud__nav-label {
    display: none;
  }
}


/* ==========================================================================
   13. PHASE 4C — MAP POLISH, KEEPSAKES AND THE PERFECT MARK
   ========================================================================== */

/* --------------------------------------------------------------------------
   "NEW PATH UNLOCKED" — shown once, on returning to the map
   -------------------------------------------------------------------------- */
.map__notice {
  align-self: stretch;
  margin-bottom: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-gold);
  border-radius: var(--radius-chip);
  background: var(--tint-gold-14);

  font-size: var(--text-meta);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--color-gold-bright);
  text-align: center;

  animation: notice-in var(--dur-screen) var(--ease);
}

@keyframes notice-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* --------------------------------------------------------------------------
   YOU ARE HERE
   Sits at the end of the node's own row, so it can never cover the name or
   eat into the tap target.
   -------------------------------------------------------------------------- */
.node__here {
  margin-left: auto;
  flex-shrink: 0;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-gold-bright);
  background: var(--tint-gold-14);

  font-family: var(--font-body);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--color-gold-bright);
  white-space: nowrap;
}

/* A stage that has just opened. The glow fades on its own after a moment;
   js/ui.js then removes the class. */
.node--opened {
  animation: node-opened 1.6s var(--ease) 2;
}

@keyframes node-opened {
  0%, 100% { box-shadow: var(--glow-gold); }
  50%      { box-shadow: var(--glow-strong); }
}

/* A perfectly cleared stage keeps the completed look plus its outer ring. */
.node--perfect .node__dot {
  border-color: var(--color-gold);
  background: var(--color-gold);
  color: var(--color-ground-deep);
  box-shadow: 0 0 0 3px var(--color-ground), 0 0 0 5px var(--color-gold-bright);
}

.node--perfect .node__state {
  color: var(--color-gold-bright);
}

/* --------------------------------------------------------------------------
   KEEPSAKES
   A list, not an inventory. Nothing here is used, equipped or spent.
   -------------------------------------------------------------------------- */
.keepsakes {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 100%;
  margin: 0 0 var(--space-4);
  padding: 0;
}

.keepsake {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: var(--border-soft);
  border-radius: var(--radius-control);
  background: var(--color-surface);
  text-align: left;
}

.keepsake__art {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-pill);
  border: 1px dashed var(--color-twilight-violet);
  color: var(--color-twilight-haze);
  font-family: var(--font-display);
  font-size: 1.2rem;
}

.keepsake__icon {
  width: 22px;
  height: 22px;
}

.keepsake__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.keepsake__name {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-text-soft);
}

.keepsake__note {
  font-size: var(--text-meta);
  color: var(--color-text-soft);
  opacity: 0.85;
}

/* Found: the socket fills with gold and the name brightens. Undiscovered
   entries stay dashed and show a hint instead of a description. */
.keepsake--found .keepsake__art {
  border: 1px solid var(--color-gold);
  background: var(--tint-gold-14);
  color: var(--color-gold-bright);
}

.keepsake--found .keepsake__name {
  color: var(--color-gold-bright);
}

/* The compact row used on the world-complete screen. */
.keepsakes--compact {
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
}

.keepsakes--compact .keepsake {
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  text-align: center;
  min-width: 6.5rem;
}

.keepsakes--compact .keepsake__name {
  font-size: var(--text-meta);
}

/* --------------------------------------------------------------------------
   THE PERFECT MARK on the result screen
   Shown only when the run genuinely earned it.
   -------------------------------------------------------------------------- */
.perfect {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  align-self: center;
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-4);

  border: 2px solid var(--color-gold-bright);
  border-radius: var(--radius-pill);
  background: var(--tint-gold-14);
  box-shadow: var(--glow-gold);

  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  color: var(--color-gold-bright);

  animation: perfect-in var(--dur-reward) var(--ease) both;
  animation-delay: 520ms;
}

.perfect__icon {
  width: 18px;
  height: 18px;
  color: var(--color-gold-bright);
}

@keyframes perfect-in {
  from { opacity: 0; transform: scale(0.86); }
  60%  { opacity: 1; transform: scale(1.06); }
  to   { opacity: 1; transform: scale(1); }
}

/* --------------------------------------------------------------------------
   THE LINE THAT CARRIES THE PLAYER ONWARD
   -------------------------------------------------------------------------- */
.leads-to {
  max-width: var(--reading-max);
  margin-top: var(--space-2);
  padding-top: var(--space-3);
  border-top: var(--border-gold);

  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--text-body);
  color: var(--color-twilight-warm);
}

/* --------------------------------------------------------------------------
   LANDSCAPE
   -------------------------------------------------------------------------- */
@media (orientation: landscape) and (max-height: 600px) {
  .keepsakes {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-2);
  }

  .keepsake {
    padding: var(--space-2) var(--space-3);
  }

  .keepsake__art {
    width: 32px;
    height: 32px;
  }
}

/* On a narrow phone the "YOU ARE HERE" tag would squeeze the stage name, so
   it drops to its own line under it instead of competing for the row. */
@media (max-width: 420px) {
  .node {
    flex-wrap: wrap;
  }

  .node__here {
    margin-left: 56px;
    margin-top: var(--space-1);
  }
}


/* --------------------------------------------------------------------------
   SHORT SCREENS
   The result screen can carry four reward lines and the PERFECT mark at once.
   On a 568px-tall phone that is a lot of card, so it breathes a little less.
   Nothing shrinks below the 44px tap floor.
   -------------------------------------------------------------------------- */
@media (max-height: 640px) {
  .panel {
    gap: var(--space-3);
    padding: var(--space-4);
  }

  .rewards {
    gap: var(--space-1);
  }

  .rewards__item {
    padding: var(--space-1) var(--space-3);
  }

  .perfect {
    margin-top: 0;
    padding: var(--space-1) var(--space-3);
  }
}

/* ==========================================================================
   PRODUCTION ARTWORK
   --------------------------------------------------------------------------
   Added when the approved World 1 art landed. Every rule here styles a real
   painted image; the vector sprite and the emoji fallbacks underneath are all
   still in place, so a missing file degrades to the old look rather than to a
   broken layout.

   The image FILES are named in exactly two places — css/worlds.css for the
   background layers, and the ARTWORK block at the top of js/ui.js for
   everything else. Nothing is named here.
   ========================================================================== */

/* --------------------------------------------------------------------------
   ARI
   One rule for every pose: they are all one canvas at one figure scale, so
   they can share a size and never jump between screens.
   -------------------------------------------------------------------------- */
.ari {
  display: block;
  height: clamp(7rem, 26vmin, 11rem);
  /* Every pose ships on the same 683x1024 canvas. Declaring the ratio means
     the space is the right width BEFORE the picture arrives, so the buttons
     underneath never jump as it loads. */
  aspect-ratio: 683 / 1024;
  width: auto;
  margin: 0 auto;
  filter: drop-shadow(0 6px 18px var(--tint-ink-45));
}

/* The scene-icon slot was built for an emoji, so it carries a font size and a
   gold glow. A picture of Ari needs neither. */
.scene-icon:has(.ari) {
  font-size: 0;
  filter: none;
}

@media (orientation: landscape) and (max-height: 600px) {
  .ari {
    height: clamp(5rem, 22vmin, 7.5rem);
  }
}

/* --------------------------------------------------------------------------
   PATH VIGNETTES — Stage 1
   The painting replaces the vector emblem inside the same 46px slot, so the
   card layout is unchanged. It is a wide picture in a square hole, so it is
   cropped to the middle rather than squashed.
   -------------------------------------------------------------------------- */
.choice__art--painted {
  border-radius: var(--radius-chip);
  overflow: hidden;
}

.choice__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* --------------------------------------------------------------------------
   SHARDS
   A found socket shows its own wedge of the broken disc. An empty socket keeps
   the dashed outline and the vector shape, so the row still reads as progress.
   -------------------------------------------------------------------------- */
.shard__image {
  width: 26px;
  height: 26px;
  object-fit: contain;
  display: block;
}

/* The painting carries its own gold, so the tinted fill would only muddy it. */
.shard--found {
  background: none;
}

/* --------------------------------------------------------------------------
   KEEPSAKES
   -------------------------------------------------------------------------- */
.keepsake__image {
  width: 30px;
  height: 30px;
  object-fit: contain;
  display: block;
}

.keepsake--found .keepsake__art {
  background: none;
}

/* --------------------------------------------------------------------------
   THE ISLAND MAP PLATE
   The map is still a list of nodes, not a painted island — the plate sits
   behind the list as a backdrop. Moving the nodes onto coordinates of the
   plate is a design change, not an art change, and is deliberately not done
   here. The plate is decorative, so it is drawn in CSS rather than as an
   <img>: nothing in the list depends on it and nothing announces it.
   -------------------------------------------------------------------------- */
.panel--map {
  position: relative;
  isolation: isolate;
}

.panel--map::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background-image: url("../assets/art/ui/island-map-plate.webp");
  background-size: cover;
  background-position: center;
  opacity: 0.22;
  /* Fades out under the buttons so no label ever sits on busy artwork. */
  -webkit-mask-image: radial-gradient(120% 78% at 50% 34%, #000 0%, #000 42%, transparent 88%);
          mask-image: radial-gradient(120% 78% at 50% 34%, #000 0%, #000 42%, transparent 88%);
}

/* Reduced motion is also a signal for "less visual noise". */
@media (prefers-reduced-motion: reduce) {
  .panel--map::before {
    opacity: 0.14;
  }
}

/* Ari on the world map sits smaller: the map is the busiest screen in the
   game and the nodes are what the player came to read. */
.ari--small {
  height: clamp(4rem, 15vmin, 6rem);
}

.scene-icon--small {
  margin-bottom: var(--space-2);
}


/* ==========================================================================
   THE ASSEMBLED SHARD DISC — world-complete screen only
   --------------------------------------------------------------------------
   The five shards are fragments of one broken circular artifact. Everywhere
   else they are a progress row of sockets; here they are laid back into the
   disc they came from, so finishing the island shows the thing the player
   spent five stages putting together.

   No new artwork: this is the same five WebP files, rotated and pushed out
   from a common centre. The angles are measured from the paintings, not
   guessed — each shard's gold rim is its original outer edge, so every piece
   is turned until its rim faces outward and the rims form the circle.

   The ragged gap left in the middle is the point: recovered, not repaired.
   ========================================================================== */
.shards--disc {
  position: relative;
  display: block;
  width: min(62vw, 220px);
  aspect-ratio: 1;
  margin: var(--space-4) auto;
  gap: 0;
}

.shards--disc .shard {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 54%;
  height: 54%;

  /* The socket chrome belongs to the progress row, not to the artifact. */
  border: 0;
  border-radius: 0;
  background: none;
  opacity: 1;
}

.shards--disc .shard__image {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 2px 6px var(--tint-ink-45));
}

/* A shard that was never found keeps its vector outline, in place, so the
   gap in the circle is visible rather than silently closed. */
.shards--disc .shard:not(.shard--found) {
  opacity: 0.28;
}

.shards--disc .shard__icon {
  width: 42%;
  height: 42%;
}

/* Each piece: rotate about its own centre so the gold rim faces out, then
   push it away from the middle along its slot. Read right to left. */
.shards--disc .shard:nth-child(1) {
  transform: translate(-50%, -50%) translate(0, -33.3%) rotate(-66.8deg);
}
.shards--disc .shard:nth-child(2) {
  transform: translate(-50%, -50%) translate(31.7%, -10.3%) rotate(138.3deg);
}
.shards--disc .shard:nth-child(3) {
  transform: translate(-50%, -50%) translate(19.6%, 27.0%) rotate(85.5deg);
}
.shards--disc .shard:nth-child(4) {
  transform: translate(-50%, -50%) translate(-19.6%, 27.0%) rotate(-154.2deg);
}
.shards--disc .shard:nth-child(5) {
  transform: translate(-50%, -50%) translate(-31.7%, 10.3%) rotate(-128.8deg);
}

/* A soft bloom behind the disc, the same gold the rest of the game glows
   with. Decorative only — it carries no information. */
.shards--disc::before {
  content: "";
  position: absolute;
  inset: -6%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%,
    var(--tint-gold-20) 0%,
    var(--tint-gold-08) 45%,
    transparent 70%);
  pointer-events: none;
}

/* The pieces settle into the circle once, on arrival. Nothing is communicated
   by the movement — the disc is already correct when it stops. */
.shards--disc .shard {
  animation: shard-settle var(--dur-slow, 520ms) var(--ease, ease-out) both;
}
.shards--disc .shard:nth-child(1) { animation-delay: 60ms; }
.shards--disc .shard:nth-child(2) { animation-delay: 140ms; }
.shards--disc .shard:nth-child(3) { animation-delay: 220ms; }
.shards--disc .shard:nth-child(4) { animation-delay: 300ms; }
.shards--disc .shard:nth-child(5) { animation-delay: 380ms; }

@keyframes shard-settle {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Small phones: keep the disc clear of the summary beneath it. */
@media (max-width: 360px) {
  .shards--disc { width: min(58vw, 180px); }
}

/* Landscape phones have almost no height to spare. */
@media (orientation: landscape) and (max-height: 600px) {
  .shards--disc {
    width: min(30vw, 150px);
    margin: var(--space-2) auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .shards--disc .shard {
    animation: none;
  }
}


/* ==========================================================================
   MAP NODE IDENTITY
   --------------------------------------------------------------------------
   Each stop on the map shows a thumbnail of its own location, cropped out of
   the background painting that location already ships. No new artwork, no new
   files, nothing added to CORE_FILES — these are the same WebPs the scene
   uses, and they are already precached.

   The thumbnail is decoration underneath the state, never the state itself.
   Locked / open / done / perfect are still carried by the ring colour, the
   icon and the word beside the name, so nothing here depends on the picture
   loading or on the player telling five paintings apart.
   ========================================================================== */
.node__dot {
  position: relative;
  overflow: hidden;
  background-size: cover;
  background-position: center 60%;
}

/* Darkens whatever is behind the icon so the tick or lock always reads. */
.node__dot::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--tint-ground-40);
}

.node__icon {
  position: relative;
  z-index: 1;
}

.node[data-world="shore"] .node__dot {
  background-image: url("../assets/art/worlds/shore/shore-layer-3-mid.webp");
}
.node[data-world="forest"] .node__dot {
  background-image: url("../assets/art/worlds/forest/forest-layer-1-sky.webp");
}
.node[data-world="cave"] .node__dot {
  background-image: url("../assets/art/worlds/cave/cave-layer-3-mid.webp");
}
.node[data-world="mountain"] .node__dot {
  background-image: url("../assets/art/worlds/mountain/mountain-layer-3-mid.webp");
}
.node[data-world="ruins"] .node__dot {
  background-image: url("../assets/art/worlds/ruins/ruins-layer-3-mid.webp");
}

/* A place not yet reached is dimmed and drained of colour — the shape of the
   location is there, but it reads as somewhere you have not been. */
.node--locked .node__dot {
  filter: grayscale(0.85);
}
.node--locked .node__dot::after {
  background: var(--tint-ground-40), var(--color-scrim);
}

/* Finished places keep the gold the map has always used to mean "done", now
   as a veil rather than a flat fill, so the location still shows through and
   a completed island is five different places rather than five gold discs.
   The gold sits over the standard darkening, and the tick flips to bright
   gold so it reads on the painting instead of on a flat fill. */
.node--completed .node__dot,
.node--perfect .node__dot {
  background-color: transparent;
  color: var(--color-gold-bright);
}
.node--completed .node__dot::after,
.node--perfect .node__dot::after {
  background:
    linear-gradient(0deg, var(--tint-gold-14), var(--tint-gold-14)),
    var(--tint-ground-40);
}

/* Where the player is standing gets the clearest view of its own place. */
.node--current .node__dot::after {
  background: var(--tint-ground-25);
}


/* ==========================================================================
   ARI ON THE TITLE SCREEN
   --------------------------------------------------------------------------
   The menu was the only screen in the game with no artwork on it. It still
   gets no world painting — a title screen is not a place, and the twilight
   gradient behind it is the point — but the character the player is about to
   be now stands above the title.

   Deliberately smaller than the in-game poses: this is a title screen, so the
   title has to stay the loudest thing on it.
   ========================================================================== */
.ari--title {
  height: clamp(5.5rem, 19vmin, 8.5rem);
}

.scene-icon--title {
  margin-bottom: var(--space-2);
}

/* Sideways on a phone there is almost no height; the title and the two
   buttons matter more than the picture. */
@media (orientation: landscape) and (max-height: 600px) {
  .ari--title {
    height: clamp(3.5rem, 13vmin, 5rem);
  }
  .scene-icon--title {
    margin-bottom: 0;
  }
}

/* Very short screens: drop the picture rather than push START QUEST under
   the fold. Nothing is lost — it is decoration. */
@media (max-height: 460px) {
  .scene-icon--title {
    display: none;
  }
}
