/* ═══════════════════════════════════════════════
   Shovan Dhara — a side-scrolling portfolio
   The page is one long road. You walk east.
   ═══════════════════════════════════════════════ */

:root {
  --ink: #1c1722;
  --ink-soft: #2a2233;
  --cream: #f2e8d5;
  --cream-dim: #b9ad9a;
  --amber: #e8a33d;
  --rust: #c4633c;
  --teal: #4f7061;
  --moss: #6d7f45;
  --mauve: #7a6470;
  --line: rgba(242, 232, 213, 0.3);

  /* Height of the walkable ground band. Everything in the world sits on it. */
  --ground: clamp(88px, 13vh, 132px);
  --hud-top: 76px;
  --hud-bottom: 40px;

  --font-px: "Press Start 2P", "Courier New", monospace;
  --font-body: "VT323", "Courier New", monospace;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
  overflow: hidden;
}

body {
  background: var(--ink);
  color: var(--cream);
  font-family: var(--font-body);
  font-size: 21px;
  line-height: 1.45;
  -webkit-font-smoothing: none;
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}

a {
  color: var(--amber);
}

:focus-visible {
  outline: 3px solid var(--amber);
  outline-offset: 3px;
}

.skip-link {
  position: fixed;
  top: 8px;
  left: 8px;
  z-index: 60;
  padding: 10px 14px;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-px);
  font-size: 10px;
  text-decoration: none;
  transform: translateY(-180%);
  transition: transform 140ms ease;
}

.skip-link:focus {
  transform: none;
}

/* ── The sky ───────────────────────────────── */

.sky {
  position: fixed;
  inset: 0;
  z-index: 0;
  background: linear-gradient(
    180deg,
    #221b2e 0%,
    #2f2540 16%,
    #453450 32%,
    #654a5c 46%,
    #8c5f61 58%,
    #b0766a 68%,
    #c98a6b 76%,
    #7c5c5e 88%,
    #3d3038 100%
  );
  transition: background 1.4s ease;
}

/* Sun and moon ride an arc that px-app.js drives off scroll position. It
   feeds --x/--y/--size/--vis every frame, so nothing here may transition or
   the body would lag a frame behind the scroll. */

.sky__sun,
.sky__moon {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--size, 30px);
  height: var(--size, 30px);
  margin: calc(var(--size, 30px) / -2);
  border-radius: 50%;
  opacity: calc(var(--vis, 0) * var(--dim, 1));
  translate: var(--x, 80vw) var(--y, 40vh);
}

.sky__sun {
  background: var(--sun-color, #f8dda4);
  box-shadow:
    0 0 0 calc(var(--size, 30px) * 0.22) rgba(244, 208, 138, 0.16),
    0 0 calc(var(--size, 30px) * 2.6) calc(var(--size, 30px) * 0.85)
      rgba(232, 163, 61, var(--halo, 0.4));
}

.sky__moon {
  /* Off-centre highlight so it reads as a sphere, not a disc. */
  background: radial-gradient(circle at 36% 32%, #f2f5fb 0%, #dde3ef 52%, #b9c3d8 100%);
  box-shadow:
    0 0 0 calc(var(--size, 30px) * 0.16) rgba(206, 218, 240, 0.12),
    0 0 calc(var(--size, 30px) * 2.2) calc(var(--size, 30px) * 0.6)
      rgba(176, 196, 235, var(--halo, 0.35));
}

/* Craters, so it isn't just a white dot. */
.sky__moon::before,
.sky__moon::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  background: rgba(158, 170, 196, 0.5);
}

.sky__moon::before {
  top: 22%;
  left: 24%;
  width: 26%;
  height: 26%;
}

.sky__moon::after {
  top: 55%;
  left: 52%;
  width: 18%;
  height: 18%;
  opacity: 0.7;
}

/* The glow spilled across the horizon at the point the body is rising or
   setting — this is what actually sells sunrise and sunset. */
.sky__wash {
  position: absolute;
  inset: 0;
  background: radial-gradient(
    70% 44% at var(--wash-x, 70%) 76%,
    var(--wash-color, rgba(236, 138, 62, 0.62)),
    transparent 70%
  );
  opacity: var(--wash, 0);
}

body[data-season="autumn"] .sky {
  background: linear-gradient(
    180deg,
    #2a1c22 0%,
    #4a2a28 28%,
    #8a4a32 52%,
    #c46a3a 70%,
    #5a3830 100%
  );
}

body[data-season="rain"] .sky {
  background: linear-gradient(180deg, #161820 0%, #232833 40%, #3a424c 70%, #2a3036 100%);
}

body[data-season="storm"] .sky {
  background: linear-gradient(180deg, #101218 0%, #1a1e28 38%, #2c3340 72%, #1c2228 100%);
}

body[data-season="winter"] .sky {
  background: linear-gradient(180deg, #6d7c8c 0%, #a8b6c2 36%, #d5dde4 68%, #8b97a3 100%);
}

body[data-season="forge"] .sky {
  background: linear-gradient(180deg, #2a1410 0%, #5a2418 36%, #c45a28 62%, #3a2018 100%);
}

body[data-season="hearth"] .sky {
  background: linear-gradient(180deg, #0c0a14 0%, #1a1424 40%, #2a1c28 70%, #140e16 100%);
}

/* Cloud cover dims whatever is in the sky rather than repositioning it. */

body[data-season="rain"] .sky__sun,
body[data-season="rain"] .sky__moon,
body[data-season="storm"] .sky__sun,
body[data-season="storm"] .sky__moon {
  --dim: 0.14;
  box-shadow: none;
}

body[data-season="winter"] .sky__sun {
  --dim: 0.62;
}

body[data-season="rain"] .sky__wash,
body[data-season="storm"] .sky__wash {
  opacity: calc(var(--wash, 0) * 0.25);
}

.layer--far,
.layer--mid,
.layer--water,
.layer--trees,
.layer--ground,
.layer--clouds {
  transition: filter 1.4s ease, opacity 1.4s ease;
}

body[data-season="autumn"] .layer--trees {
  filter: saturate(0.7) brightness(0.62) hue-rotate(-18deg);
}

body[data-season="rain"] .layer--clouds,
body[data-season="storm"] .layer--clouds {
  opacity: 0.72;
  filter: brightness(0.45) saturate(0.2);
}

body[data-season="rain"] .layer--far,
body[data-season="storm"] .layer--far,
body[data-season="rain"] .layer--mid,
body[data-season="storm"] .layer--mid {
  filter: saturate(0.2) brightness(0.42);
}

/* The treeline keeps more of its colour than the ridges behind it, or the
   monsoon canopy collapses into one black mass. */
body[data-season="rain"] .layer--trees,
body[data-season="storm"] .layer--trees {
  filter: saturate(0.45) brightness(0.56);
}

/* Water takes the weather too: churned and grey under rain, iced over in the
   cold snap, catching the furnace light during the thaw. */
body[data-season="rain"] .layer--water,
body[data-season="storm"] .layer--water {
  filter: saturate(0.3) brightness(0.48);
}

body[data-season="winter"] .layer--water {
  filter: saturate(0.08) brightness(1.06) contrast(0.88);
}

body[data-season="forge"] .layer--water {
  filter: saturate(0.75) brightness(0.85) hue-rotate(-14deg);
}

body[data-season="hearth"] .layer--water {
  filter: saturate(0.5) brightness(0.4);
}

body[data-season="winter"] .layer--clouds {
  opacity: 0.55;
  filter: brightness(1.2) saturate(0);
}

body[data-season="winter"] .layer--far,
body[data-season="winter"] .layer--mid,
body[data-season="winter"] .layer--ground {
  filter: saturate(0.15) brightness(0.95) contrast(0.9);
}

/* Snow-laden pines need to stay dark against the pale peaks, otherwise the
   whole cold snap flattens into one sheet of grey. */
body[data-season="winter"] .layer--trees {
  filter: saturate(0.22) brightness(0.5) contrast(1.08);
}

body[data-season="forge"] .layer--trees,
body[data-season="forge"] .layer--mid {
  filter: saturate(0.55) brightness(0.55) hue-rotate(8deg);
}

body[data-season="hearth"] .layer--clouds {
  opacity: 0.22;
}

body[data-season="rain"] .flock,
body[data-season="storm"] .flock,
body[data-season="winter"] .flock,
body[data-season="hearth"] .flock {
  opacity: 0;
}

/* ── Parallax scenery ──────────────────────── */

/* ── Cumulus ───────────────────────────────── */

/* Big volumetric clouds that cross on their own timeline, in front of the
   sky but behind the ridgelines. Overlapping lobes over a flat base is what
   separates a cumulus from a blob. */

.clouds {
  position: fixed;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
  transition: opacity 1.4s ease, filter 1.4s ease;
}

.cloud {
  position: absolute;
  top: var(--lane);
  left: 0;
  width: var(--w);
  height: calc(var(--w) * 0.4);
  opacity: var(--tone, 0.85);
  animation: cloud-drift var(--time) linear var(--wait) infinite;
}

.cloud i {
  position: absolute;
  bottom: 14%;
  border-radius: 50%;
  background: linear-gradient(180deg, #f0e2ce 0%, #d6c2ba 44%, #a08e98 100%);
}

.cloud i:nth-child(1) {
  left: 0;
  width: 42%;
  height: 62%;
}

.cloud i:nth-child(2) {
  left: 17%;
  width: 46%;
  height: 100%;
}

.cloud i:nth-child(3) {
  left: 47%;
  width: 38%;
  height: 78%;
}

.cloud i:nth-child(4) {
  left: 66%;
  width: 34%;
  height: 50%;
}

/* Flat, slightly darker underside — cumulus sit on a level base. */
.cloud::after {
  content: "";
  position: absolute;
  right: 1%;
  bottom: 0;
  left: 1%;
  height: 30%;
  border-radius: 999px;
  background: linear-gradient(180deg, #cdb8b6 0%, #8e7d8a 100%);
}

@keyframes cloud-drift {
  from {
    translate: -45vw 0;
  }
  to {
    translate: 135vw 0;
  }
}

body[data-season="rain"] .clouds,
body[data-season="storm"] .clouds {
  filter: brightness(0.42) saturate(0.2);
}

body[data-season="winter"] .clouds {
  filter: brightness(1.12) saturate(0.08);
}

body[data-season="forge"] .clouds {
  filter: brightness(0.78) sepia(0.35) hue-rotate(-14deg);
}

body[data-season="hearth"] .clouds {
  opacity: 0.32;
  filter: brightness(0.38) saturate(0.4);
}

.scenery {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

.layer {
  position: absolute;
  left: 0;
  right: 0;
  background-repeat: repeat-x;
  background-position: 0 bottom;
  background-size: auto 100%;
  image-rendering: pixelated;
  will-change: background-position;
}

.layer--clouds {
  top: 3vh;
  height: 36vh;
  background-image: url("art/layer-clouds.png");
  background-position: 0 top;
  opacity: 0.4;
}

/* ── Birds ─────────────────────────────────── */

.flock {
  position: fixed;
  inset: 0;
  z-index: 6;
  overflow: hidden;
  pointer-events: none;
}

/* A bird is a 18x12 pixel sprite drawn with box-shadow, three flap frames
   deep. Travel, bob and flap are separate animations on separate properties
   so they compose instead of fighting over `transform`. */

.bird {
  --bird: #241f24;
  --size: 1;
  --flap: 0.42s;
  position: absolute;
  top: var(--lane);
  left: 0;
  width: 36px;
  height: 24px;
  transform-origin: center;
  filter: drop-shadow(0 0 5px rgba(250, 238, 214, 0.4));
  animation:
    cross var(--time) linear var(--wait) infinite,
    bird-glide calc(var(--time) / 11) ease-in-out var(--wait) infinite alternate;
}

.bird__f {
  position: absolute;
  top: 0;
  left: 0;
  width: 2px;
  height: 2px;
  opacity: 0;
  animation-duration: var(--flap);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-delay: var(--wait);
}

/* The body pixels are identical in all three frames — only the wings move,
   the way a hand-drawn sprite sheet would do it. */

.bird__f--up {
  animation-name: flap-up;
  box-shadow:
    4px 4px 0 0 var(--bird), 30px 4px 0 0 var(--bird),
    6px 6px 0 0 var(--bird), 28px 6px 0 0 var(--bird),
    8px 8px 0 0 var(--bird), 26px 8px 0 0 var(--bird),
    10px 10px 0 0 var(--bird), 24px 10px 0 0 var(--bird),
    12px 12px 0 0 var(--bird), 14px 12px 0 0 var(--bird), 16px 12px 0 0 var(--bird),
    18px 12px 0 0 var(--bird), 20px 12px 0 0 var(--bird), 22px 12px 0 0 var(--bird),
    16px 14px 0 0 var(--bird), 18px 14px 0 0 var(--bird);
}

.bird__f--mid {
  animation-name: flap-mid;
  box-shadow:
    4px 10px 0 0 var(--bird), 6px 10px 0 0 var(--bird),
    28px 10px 0 0 var(--bird), 30px 10px 0 0 var(--bird),
    8px 12px 0 0 var(--bird), 10px 12px 0 0 var(--bird),
    12px 12px 0 0 var(--bird), 14px 12px 0 0 var(--bird), 16px 12px 0 0 var(--bird),
    18px 12px 0 0 var(--bird), 20px 12px 0 0 var(--bird), 22px 12px 0 0 var(--bird),
    24px 12px 0 0 var(--bird), 26px 12px 0 0 var(--bird),
    16px 14px 0 0 var(--bird), 18px 14px 0 0 var(--bird);
}

.bird__f--down {
  animation-name: flap-down;
  box-shadow:
    12px 12px 0 0 var(--bird), 14px 12px 0 0 var(--bird), 16px 12px 0 0 var(--bird),
    18px 12px 0 0 var(--bird), 20px 12px 0 0 var(--bird), 22px 12px 0 0 var(--bird),
    16px 14px 0 0 var(--bird), 18px 14px 0 0 var(--bird),
    10px 14px 0 0 var(--bird), 24px 14px 0 0 var(--bird),
    8px 16px 0 0 var(--bird), 26px 16px 0 0 var(--bird),
    6px 18px 0 0 var(--bird), 28px 18px 0 0 var(--bird),
    4px 20px 0 0 var(--bird), 30px 20px 0 0 var(--bird);
}

/* up → mid → down → mid, held flat so the frames snap like real pixel art. */

@keyframes flap-up {
  0%,
  24.9% {
    opacity: 1;
  }
  25%,
  100% {
    opacity: 0;
  }
}

@keyframes flap-mid {
  0%,
  24.9% {
    opacity: 0;
  }
  25%,
  49.9% {
    opacity: 1;
  }
  50%,
  74.9% {
    opacity: 0;
  }
  75%,
  100% {
    opacity: 1;
  }
}

@keyframes flap-down {
  0%,
  49.9% {
    opacity: 0;
  }
  50%,
  74.9% {
    opacity: 1;
  }
  75%,
  100% {
    opacity: 0;
  }
}

@keyframes cross {
  from {
    translate: -10vw 0;
  }
  to {
    translate: 112vw 0;
  }
}

@keyframes bird-glide {
  from {
    transform: scale(var(--size)) translateY(6px);
  }
  to {
    transform: scale(var(--size)) translateY(-10px);
  }
}

.bird--west {
  animation-name: cross-west, bird-glide-west;
}

/* Atmospheric perspective: the further out a bird is, the smaller, hazier
   and slower-beating it reads. */

.bird--far {
  --size: 1;
  --bird: #171320;
  --flap: 0.6s;
  opacity: 0.82;
}

.bird--mid {
  --size: 1.5;
  --bird: #120e18;
  --flap: 0.48s;
  opacity: 0.94;
}

.bird--near {
  --size: 2.1;
  --bird: #0c0910;
  --flap: 0.34s;
  filter: drop-shadow(0 0 8px rgba(250, 238, 214, 0.5));
}

@keyframes cross-west {
  from {
    translate: 112vw 0;
  }
  to {
    translate: -10vw 0;
  }
}

@keyframes bird-glide-west {
  from {
    transform: scale(var(--size)) scaleX(-1) translateY(6px);
  }
  to {
    transform: scale(var(--size)) scaleX(-1) translateY(-10px);
  }
}

.flock {
  transition: opacity 1s ease;
}

/* ── Weather + fire ────────────────────────── */

.weather {
  position: fixed;
  inset: 0;
  z-index: 6;
  overflow: hidden;
  pointer-events: none;
}

.fall,
.mist,
.flash {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.9s ease;
}

/* Each particle composes a fall on `translate` with a sway on `transform` and
   a tumble on `rotate`, so nothing has to share a single property. Depth
   comes from the per-particle size, speed and alpha set in px-app.js. */

.fall .bit {
  position: absolute;
  left: var(--x);
}

/* Only the field the current season needs is allowed to run; the rest sit
   parked instead of burning frames behind opacity: 0. */
.fall .bit,
.fall::after {
  animation-play-state: paused;
}

.fall--rain .bit {
  top: -14%;
  width: var(--w, 2px);
  height: var(--len, 26px);
  background: linear-gradient(180deg, transparent, rgba(214, 230, 244, var(--alpha, 0.7)));
  rotate: -12deg; /* leans into the same wind the --drift carries it on */
  animation: rain-fall var(--dur) linear var(--delay) infinite;
}

/* Spray where the rain lands, rather than streaks that vanish mid-air. */
.fall--rain::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  height: calc(var(--ground) + 26px);
  background: linear-gradient(180deg, transparent, rgba(206, 222, 238, 0.16));
  animation: spray 1.1s ease-in-out infinite alternate;
}

.fall--snow .bit {
  top: -8%;
  width: var(--w, 5px);
  height: var(--w, 5px);
  background: rgba(244, 248, 252, var(--alpha, 0.9));
  animation:
    snow-fall var(--dur) linear var(--delay) infinite,
    sway calc(var(--dur) / 4) ease-in-out var(--delay) infinite alternate;
}

.fall--leaves .bit {
  top: -8%;
  width: var(--w, 10px);
  height: calc(var(--w, 10px) * 0.6);
  background: var(--leaf, #c4633c);
  box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.35);
  opacity: var(--alpha, 1);
  animation:
    leaf-fall var(--dur) linear var(--delay) infinite,
    sway calc(var(--dur) / 3) ease-in-out var(--delay) infinite alternate,
    tumble calc(var(--dur) / 5) linear var(--delay) infinite;
}

.fall--embers .bit {
  bottom: var(--ground);
  width: var(--w, 3px);
  height: var(--w, 3px);
  background: #ffc266;
  box-shadow: 0 0 7px 1px rgba(232, 106, 42, 0.8);
  animation:
    ember-rise var(--dur) ease-out var(--delay) infinite,
    sway calc(var(--dur) / 3) ease-in-out var(--delay) infinite alternate,
    flicker 0.16s steps(2) infinite;
}

.mist {
  background: linear-gradient(180deg, transparent 40%, rgba(210, 220, 230, 0.28) 100%);
}

/* Light thrown from a point off the top-right rather than a flat white wash. */
.flash {
  background: radial-gradient(
    130% 95% at 70% -12%,
    rgba(242, 247, 255, 0.95),
    rgba(206, 220, 242, 0.42) 44%,
    transparent 76%
  );
}

body[data-season="autumn"] .fall--leaves,
body[data-season="harvest"] .fall--leaves {
  opacity: 1;
}

body[data-season="rain"] .fall--rain,
body[data-season="storm"] .fall--rain {
  opacity: 1;
}

body[data-season="rain"] .mist,
body[data-season="storm"] .mist,
body[data-season="winter"] .mist {
  opacity: 1;
}

/* A real strike stutters: leader flash, gap, restrike, then a slow bleed-off.
   One flat blink is what makes it read as a light switch. */
body[data-season="storm"] .flash.is-on {
  transition: none;
  animation: strike 640ms steps(1) 1;
}

@keyframes strike {
  0% {
    opacity: 0.95;
  }
  9% {
    opacity: 0.12;
  }
  15% {
    opacity: 0.72;
  }
  23% {
    opacity: 0;
  }
  31% {
    opacity: 0.88;
  }
  44% {
    opacity: 0.24;
  }
  100% {
    opacity: 0;
  }
}

body[data-season="winter"] .fall--snow {
  opacity: 1;
}

body[data-season="forge"] .fall--embers,
body[data-season="hearth"] .fall--embers {
  opacity: 1;
}

body[data-season="autumn"] .fall--leaves .bit,
body[data-season="harvest"] .fall--leaves .bit,
body[data-season="rain"] .fall--rain .bit,
body[data-season="rain"] .fall--rain::after,
body[data-season="storm"] .fall--rain .bit,
body[data-season="storm"] .fall--rain::after,
body[data-season="winter"] .fall--snow .bit,
body[data-season="forge"] .fall--embers .bit,
body[data-season="hearth"] .fall--embers .bit {
  animation-play-state: running;
}

@keyframes rain-fall {
  to {
    translate: var(--drift) 128vh;
  }
}

@keyframes snow-fall {
  to {
    translate: var(--drift) 122vh;
  }
}

@keyframes leaf-fall {
  to {
    translate: var(--drift) 122vh;
  }
}

@keyframes ember-rise {
  from {
    translate: 0 0;
    opacity: 1;
  }
  to {
    translate: var(--drift) -52vh;
    opacity: 0;
  }
}

@keyframes sway {
  from {
    transform: translateX(calc(var(--sway, 16px) * -1));
  }
  to {
    transform: translateX(var(--sway, 16px));
  }
}

@keyframes tumble {
  to {
    rotate: var(--spin, 360deg);
  }
}

@keyframes flicker {
  50% {
    opacity: 0.55;
  }
}

@keyframes spray {
  to {
    opacity: 0.45;
  }
}

.hearth {
  position: fixed;
  bottom: calc(var(--ground) - 8px);
  left: calc(12% + 78px);
  z-index: 6;
  width: 52px;
  height: 48px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.8s ease;
}

body[data-season="forge"] .hearth,
body[data-season="hearth"] .hearth {
  opacity: 1;
}

.hearth__glow {
  position: absolute;
  inset: -28px -32px -10px;
  background: radial-gradient(circle, rgba(232, 163, 61, 0.55), rgba(232, 163, 61, 0.12) 42%, transparent 72%);
  animation: fire-glow 0.7s steps(2) infinite alternate;
}

.hearth__logs {
  position: absolute;
  right: 8px;
  bottom: 4px;
  left: 8px;
  height: 8px;
  background: #3a2a22;
  box-shadow: 0 3px 0 #241810;
}

/* Three nested flames flickering out of phase — a single shape reads as a
   triangle, three overlapping ones read as fire. */

.hearth__flame {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform-origin: bottom center;
  clip-path: polygon(50% 0, 66% 26%, 76% 58%, 62% 100%, 38% 100%, 24% 58%, 34% 26%);
  animation-timing-function: steps(2);
  animation-iteration-count: infinite;
}

.hearth__flame--outer {
  width: 20px;
  height: 30px;
  margin-left: -10px;
  background: linear-gradient(180deg, #e8873a 0%, #d1451f 62%, #6d1c0e 100%);
  opacity: 0.85;
  animation-name: fire-lick;
  animation-duration: 0.33s;
}

.hearth__flame--inner {
  width: 13px;
  height: 21px;
  margin-left: -6.5px;
  background: linear-gradient(180deg, #ffd166 0%, #f08b2c 66%, #b8391a 100%);
  animation-name: fire-lick;
  animation-duration: 0.23s;
  animation-delay: -0.11s;
}

.hearth__flame--core {
  bottom: 9px;
  width: 6px;
  height: 10px;
  margin-left: -3px;
  background: linear-gradient(180deg, #fff6d8, #ffc74d);
  animation-name: fire-core;
  animation-duration: 0.17s;
}

@keyframes fire-lick {
  0% {
    scale: 1 1;
    translate: 0 0;
  }
  50% {
    scale: 1.14 0.82;
    translate: -1px 0;
  }
  100% {
    scale: 0.9 1.1;
    translate: 1px 0;
  }
}

@keyframes fire-core {
  50% {
    scale: 1.2 0.78;
    opacity: 0.75;
  }
}

@keyframes fire-glow {
  to {
    opacity: 0.55;
  }
}

body[data-season="hearth"] .prop--campfire,
body[data-season="forge"] .prop--campfire {
  filter: drop-shadow(0 0 16px rgba(232, 163, 61, 0.7)) saturate(1.1) brightness(1);
}

/* The scenery strips are vector, cut long enough that the parallax never runs
   off the end of them, so the terrain keeps changing the whole way east.
   Regenerate with `node tools-landscape.js`. */
.layer--far,
.layer--mid,
.layer--water,
.layer--trees {
  image-rendering: auto;
}

.layer--far {
  bottom: calc(var(--ground) + 7vh);
  height: 42vh;
  background-image: url("art/layer-far.svg");
  filter: saturate(0.7) brightness(0.92);
  opacity: 0.9;
}

.layer--mid {
  bottom: calc(var(--ground) + 1.5vh);
  height: 34vh;
  background-image: url("art/layer-mid.svg?v=trees4");
  filter: saturate(0.62) brightness(0.72);
}

/* Lakes and river bends, sitting behind the treeline. */
.layer--water {
  bottom: calc(var(--ground) - 2px);
  height: 22vh;
  background-image: url("art/layer-water.svg");
  filter: saturate(0.7) brightness(0.72);
}

.layer--trees {
  bottom: calc(var(--ground) - 12px);
  height: 30vh;
  background-image: url("art/layer-near.svg?v=trees4");
  filter: saturate(0.58) brightness(0.74) hue-rotate(-4deg);
}

.layer--ground {
  bottom: 0;
  height: calc(var(--ground) * 2.7);
  background-image: url("art/layer-ground.png");
  filter: saturate(0.5) brightness(0.62);
}

/* ── Dither + dusk wash over the whole world ─ */

.dither {
  position: fixed;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background-image:
    linear-gradient(180deg, rgba(52, 36, 60, 0.42), rgba(28, 23, 34, 0.12) 55%, rgba(28, 23, 34, 0.5)),
    repeating-linear-gradient(180deg, rgba(0, 0, 0, 0.16) 0 1px, transparent 1px 3px);
}

/* ── The walker ────────────────────────────── */

.walker {
  position: fixed;
  bottom: calc(var(--ground) - 14px);
  left: 12%;
  z-index: 4;
  pointer-events: none;
}

.walker::after {
  content: "";
  position: absolute;
  right: 4%;
  bottom: 18%;
  width: 42%;
  height: 38%;
  background: radial-gradient(circle, rgba(255, 196, 140, 0.42), transparent 70%);
  pointer-events: none;
}

.walker img {
  width: auto;
  height: clamp(84px, 13vh, 128px);
  image-rendering: pixelated;
  filter: drop-shadow(0 6px 0 rgba(0, 0, 0, 0.35));
  animation: bob 1.6s steps(2) infinite alternate;
}

.walker.is-walking img {
  animation-duration: 0.34s;
}

.walker.is-back img {
  transform: scaleX(-1);
}

@keyframes bob {
  to {
    transform: translateY(-5px);
  }
}

.walker.is-back img {
  animation-name: bob-back;
}

@keyframes bob-back {
  from {
    transform: scaleX(-1) translateY(0);
  }
  to {
    transform: scaleX(-1) translateY(-5px);
  }
}

/* ── HUD: brand, track, CTA, hints ─────────── */

.brand {
  position: fixed;
  top: 18px;
  left: clamp(14px, 2.4vw, 30px);
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 11px;
}

.brand__mark {
  width: 26px;
  height: 26px;
  background:
    linear-gradient(var(--amber), var(--amber)) 50% 50% / 10px 10px no-repeat,
    var(--ink);
  outline: 3px solid var(--cream);
  box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.55);
}

.brand__text b {
  display: block;
  color: var(--cream);
  font-family: var(--font-px);
  font-size: 13px;
  letter-spacing: 1px;
  text-transform: uppercase;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.75);
}

.brand__text em {
  display: block;
  margin-top: 5px;
  color: var(--cream-dim);
  font-size: 0.9rem;
  font-style: normal;
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.7);
}

.track {
  position: fixed;
  top: 16px;
  left: 50%;
  z-index: 50;
  transform: translateX(-50%);
  text-align: center;
}

.track__list {
  position: relative;
  display: flex;
  align-items: center;
  margin: 0;
  padding: 0;
  list-style: none;
}

.track__list::before {
  content: "";
  position: absolute;
  top: 50%;
  right: 14px;
  left: 14px;
  height: 3px;
  background: repeating-linear-gradient(
    90deg,
    rgba(242, 232, 213, 0.5) 0 6px,
    transparent 6px 11px
  );
  transform: translateY(-1px);
}

.ckpt {
  position: relative;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  text-decoration: none;
}

.ckpt__box {
  width: 11px;
  height: 11px;
  background: rgba(242, 232, 213, 0.55);
  outline: 3px solid rgba(0, 0, 0, 0.6);
  transition:
    width 120ms steps(2),
    height 120ms steps(2),
    background 120ms steps(2);
}

.ckpt:hover .ckpt__box,
.ckpt:focus-visible .ckpt__box {
  background: var(--cream);
}

.ckpt.is-done .ckpt__box {
  background: var(--cream);
}

.ckpt.is-active .ckpt__box {
  width: 17px;
  height: 17px;
  background: var(--amber);
  box-shadow: 0 0 14px 3px rgba(232, 163, 61, 0.6);
}

.ckpt__name {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.climate {
  margin: 6px 0 0;
  color: var(--amber);
  font-family: var(--font-px);
  font-size: 8px;
  letter-spacing: 1px;
  text-transform: uppercase;
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.8);
}

.track__label {
  margin: 6px 0 0;
  color: var(--cream-dim);
  font-family: var(--font-px);
  font-size: 8px;
  letter-spacing: 1px;
  text-transform: uppercase;
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.8);
}

/* One line of narration per region, wiped on like a caption when the walker
   crosses into a new stretch of road. */
.story {
  max-width: 30ch;
  margin: 10px 0 0;
  padding-left: 10px;
  color: var(--cream);
  font-size: 0.95rem;
  line-height: 1.45;
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.85);
  border-left: 2px solid rgba(232, 163, 61, 0.55);
}

.story.is-new {
  animation: story-in 620ms ease-out 1;
}

@keyframes story-in {
  from {
    opacity: 0;
    translate: 0 -5px;
    clip-path: inset(0 100% 0 0);
  }
  to {
    opacity: 1;
    translate: 0 0;
    clip-path: inset(0 0 0 0);
  }
}

.cta {
  position: fixed;
  top: 18px;
  right: clamp(14px, 2.4vw, 30px);
  z-index: 50;
  padding: 13px 20px;
  color: var(--ink);
  background: var(--cream);
  outline: 3px solid rgba(0, 0, 0, 0.6);
  box-shadow: 0 5px 0 rgba(0, 0, 0, 0.45);
  font-family: var(--font-px);
  font-size: 11px;
  letter-spacing: 2px;
  text-decoration: none;
  text-transform: uppercase;
  transition: transform 110ms steps(2), background 110ms steps(2);
}

.cta:hover,
.cta:focus-visible {
  background: var(--amber);
  transform: translateY(-2px);
}

.hints {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 9px clamp(14px, 2.4vw, 30px) calc(9px + env(safe-area-inset-bottom));
  background: linear-gradient(180deg, rgba(20, 16, 25, 0), rgba(20, 16, 25, 0.86) 45%);
  color: var(--cream-dim);
  font-size: 0.95rem;
}

.hints p {
  margin: 0;
}

.hints__keys span {
  color: var(--cream);
  text-transform: uppercase;
}

.hints kbd {
  display: inline-block;
  min-width: 22px;
  padding: 2px 5px;
  color: var(--cream);
  background: rgba(242, 232, 213, 0.12);
  border: 2px solid rgba(242, 232, 213, 0.4);
  font-family: var(--font-body);
  font-size: 0.9rem;
  text-align: center;
}

.hints__east {
  color: var(--cream-dim);
  font-size: 0.9rem;
  letter-spacing: 2px;
  text-transform: uppercase;
  white-space: nowrap;
}

/* ── The road: the horizontal scroller ─────── */

.road {
  position: fixed;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: stretch;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  cursor: grab;
}

.road::-webkit-scrollbar {
  display: none;
}

.road.is-dragging {
  cursor: grabbing;
}

.stage {
  position: relative;
  flex: 0 0 auto;
  width: 100vw;
  display: grid;
  align-content: center;
  justify-items: center;
  /* The extra bottom padding reserves a band along the ground line for the
     set pieces, so panels stop above them instead of covering them. */
  padding: calc(var(--hud-top) + 18px) clamp(18px, 4vw, 60px)
    calc(var(--ground) + var(--hud-bottom) + 143px);
}

.stage__inner {
  width: min(100%, 1180px);
}

/* Loadout towers: a Quidditch pillar that rises from the ground as you
   ride in. Each flies a different house flag. --rise is 0 below the pitch
   and 1 when the stage is under the walker. */
.stage--pitch {
  overflow: hidden;
}

.tower {
  --house: #b01c24;
  --house-l: #e8a33d;
  position: absolute;
  bottom: calc(var(--ground) - 18px);
  z-index: 3;
  width: 72px;
  height: min(54vh, 440px);
  pointer-events: none;
  transform: translateY(calc((1 - var(--rise, 0)) * 115%));
  will-change: transform;
  filter: drop-shadow(0 8px 0 rgba(0, 0, 0, 0.45));
}

.tower--west {
  left: 3.5%;
}

.tower--east {
  right: 3.5%;
  left: auto;
}

.tower--high {
  height: min(66vh, 540px);
}

.tower--low {
  height: min(40vh, 320px);
}

.tower--gryffindor {
  --house: #b01c24;
  --house-l: #e8a33d;
}

.tower--slytherin {
  --house: #2f5a3c;
  --house-l: #c8d4cf;
}

.tower--hufflepuff {
  --house: #d4a017;
  --house-l: #2b2418;
}

.tower--ravenclaw {
  --house: #1e3a6e;
  --house-l: #c49a4a;
}

.tower--hogwarts {
  --house: #6e1218;
  --house-l: #d4b056;
}

.tower--seeker {
  --house: #1c1a16;
  --house-l: #f0c75e;
}

.tower__shaft {
  position: absolute;
  inset: 18px 8px 0;
  background:
    linear-gradient(90deg, rgba(0, 0, 0, 0.35) 0 4px, transparent 4px),
    repeating-linear-gradient(
      180deg,
      #4a4454 0 10px,
      #3a3544 10px 11px,
      #5c5568 11px 20px
    );
  box-shadow: 5px 0 0 #1c1822;
}

.tower__shaft::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 16%;
  width: 10px;
  height: 14px;
  background: #1a161c;
  box-shadow: 0 28px 0 #1a161c, 0 56px 0 #1a161c;
}

.tower__lip {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 20px;
  background:
    repeating-linear-gradient(
      90deg,
      #3a3544 0 9px,
      transparent 9px 12px,
      #5c5568 12px 20px
    ),
    #2c2834;
  box-shadow: 0 4px 0 #1c1822;
}

.tower__flag {
  position: absolute;
  top: 26px;
  left: 100%;
  width: 78px;
  height: 48px;
  transform-origin: 0 8px;
  animation: flutter 1.7s steps(3) infinite alternate;
}

.tower--east .tower__flag {
  left: auto;
  right: 100%;
  transform-origin: 100% 8px;
  animation-name: flutter-west;
}

.tower__cloth {
  display: block;
  width: 100%;
  height: 100%;
  background:
    linear-gradient(180deg, var(--house-l) 0 5px, transparent 5px 33px, var(--house-l) 33px),
    var(--house);
  clip-path: polygon(0 0, 100% 10%, 84% 50%, 100% 90%, 0 100%);
  box-shadow: 2px 3px 0 rgba(0, 0, 0, 0.35);
}

.tower--east .tower__cloth {
  clip-path: polygon(0 10%, 100% 0, 100% 100%, 0 90%, 16% 50%);
}

.tower__mark {
  position: absolute;
  top: 12px;
  left: 14px;
  width: 12px;
  height: 12px;
  background: var(--house-l);
}

.tower--gryffindor .tower__mark {
  clip-path: polygon(50% 0, 100% 38%, 82% 100%, 18% 100%, 0 38%);
}

.tower--slytherin .tower__mark {
  left: 16px;
  width: 16px;
  height: 8px;
  border-radius: 0 8px 8px 0;
}

.tower--hufflepuff .tower__mark {
  border-radius: 50%;
}

.tower--ravenclaw .tower__mark {
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}

.tower--hogwarts .tower__mark {
  width: 10px;
  height: 14px;
  box-shadow: 8px 0 0 var(--house-l);
}

.tower--seeker .tower__mark {
  left: 18px;
  top: 11px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  box-shadow: 0 0 6px var(--house-l);
}

@keyframes flutter-west {
  from {
    transform: skewY(0deg) scaleX(1);
  }
  to {
    transform: skewY(8deg) scaleX(0.9);
  }
}

.stage__inner--pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 22px;
  align-items: start;
}

/* Props stand on the ground line, and travel with the road. */

.prop {
  position: absolute;
  bottom: calc(var(--ground) - 18px);
  z-index: -1;
  width: auto;
  image-rendering: pixelated;
  filter: drop-shadow(0 6px 0 rgba(0, 0, 0, 0.3)) saturate(0.75) brightness(0.85);
  pointer-events: none;
}

.prop--signpost {
  right: 9%;
  height: clamp(128px, 22vh, 210px);
}

.prop--barrel {
  right: 22%;
  height: clamp(42px, 7vh, 68px);
}

.prop--banner {
  right: 7%;
  height: clamp(140px, 24vh, 230px);
}

.prop--campfire {
  right: 12%;
  bottom: calc(var(--ground) - 22px);
  height: clamp(72px, 13vh, 120px);
}

/* ── Set pieces ────────────────────────────── */

/* One animated diorama per checkpoint. Each lives inside its own stage so it
   travels past as you walk, and stands in the clear band below the panel on
   the ground line. Everything is flat shapes in one shared palette so the
   dioramas sit alongside the pixel art rather than on top of it. */

.set {
  --wood-d: #2b1f18;
  --wood: #4a3425;
  --wood-l: #6d5036;
  --stone-d: #322e39;
  --stone: #494353;
  --stone-l: #676173;
  --cloth: #9c4a36;
  --cloth-l: #c4633c;
  --metal: #6f7684;
  --metal-l: #9aa2b0;
  --skin: #b97f5a;
  --glint: #e8c88a;

  position: absolute;
  bottom: calc(var(--ground) - 18px);
  left: 15%;
  z-index: -1;
  width: auto;
  height: clamp(152px, 24vh, 220px);
  pointer-events: none;
  filter: drop-shadow(0 7px 0 rgba(0, 0, 0, 0.45));
}

.set img {
  image-rendering: pixelated;
}

.set__sprite {
  display: block;
  height: 100%;
  width: auto;
}

.set :is(span, i, b) {
  position: absolute;
}

.stage--title .set {
  height: clamp(188px, 30vh, 268px);
}

.set--mill {
  height: clamp(176px, 28vh, 250px);
}

.set--fort,
.set--yard {
  height: clamp(148px, 23vh, 210px);
}

.set--camp {
  height: clamp(132px, 21vh, 196px);
}

/* Shared beats, so a hammer and a hoist can run off the same clock. */

@keyframes flutter {
  from {
    transform: skewY(0deg) scaleX(1);
  }
  to {
    transform: skewY(-8deg) scaleX(0.9);
  }
}

@keyframes strike-swing {
  0%,
  40% {
    rotate: -118deg;
  }
  55%,
  70% {
    rotate: -18deg;
  }
  100% {
    rotate: -118deg;
  }
}

@keyframes heave {
  0%,
  10% {
    translate: 0 0;
  }
  55%,
  86% {
    translate: 0 -3px;
  }
  100% {
    translate: 0 0;
  }
}

/* ── Start: a windmill turning on the ridge ── */

/* The wrapper is pinned to the mill pin at (78, 36) of the 160×124 sheet.
   Only the image spins — putting translate and rotate on the same element
   made the sails orbit the tower instead of turning on the axle. */
.mill__hub {
  left: 48.75%;
  top: 29.03%;
  width: 55%;
  aspect-ratio: 1;
  translate: -50% -50%;
}

.mill__hub img {
  display: block;
  width: 100%;
  height: auto;
  transform-origin: 50% 50%;
  animation: mill-spin 11s linear infinite;
}

@keyframes mill-spin {
  to {
    rotate: 360deg;
  }
}

/* ── Profile: a well with a dipping bucket ── */

.well__rope {
  bottom: 48%;
  left: 50%;
  width: 2px;
  height: 12%;
  margin-left: -1px;
  background: var(--glint);
  opacity: 0.55;
  transform-origin: 50% 0;
  animation: rope-out 5.2s ease-in-out infinite alternate;
}

.well__bucket {
  bottom: 42%;
  left: 50%;
  width: 16%;
  margin-left: -8%;
  animation: dip 5.2s ease-in-out infinite alternate;
}

.well__bucket img {
  display: block;
  width: 100%;
  height: auto;
  image-rendering: pixelated;
}

@keyframes rope-out {
  to {
    scale: 1 3.2;
  }
}

@keyframes dip {
  to {
    translate: 0 42%;
  }
}

/* ── Loadout: the quartermaster's fort ────── */

.fort__lift {
  bottom: 18%;
  left: 28%;
  width: 9%;
  animation: stack 2.8s steps(5) infinite;
}

.fort__lift img {
  display: block;
  width: 100%;
  height: auto;
  image-rendering: pixelated;
}

.keeper {
  bottom: 16%;
  left: 42%;
  width: 14%;
  height: 42%;
}

.keeper__body {
  bottom: 0;
  left: 18%;
  width: 58%;
  height: 52%;
  background: linear-gradient(180deg, var(--cloth) 0 62%, var(--wood-d) 62% 100%);
  box-shadow: 1px 0 0 var(--wood-d), -1px 0 0 var(--wood-d);
  animation: heave 2.8s steps(5) infinite;
}

.keeper__head {
  bottom: 52%;
  left: 24%;
  width: 46%;
  height: 22%;
  background: var(--skin);
  box-shadow: 0 -5px 0 0 var(--wood-d), 1px 0 0 var(--wood-d);
  animation: heave 2.8s steps(5) infinite;
}

.keeper__arm {
  bottom: 38%;
  width: 18%;
  height: 32%;
  background: var(--skin);
  box-shadow: 1px 0 0 var(--wood-d);
  transform-origin: 50% 0;
}

.keeper__arm--back {
  left: 4%;
  rotate: 14deg;
  opacity: 0.72;
}

.keeper__arm--front {
  left: 72%;
  animation: reach 2.8s steps(5) infinite;
}

@keyframes reach {
  0%,
  10% {
    rotate: 12deg;
  }
  55%,
  86% {
    rotate: -70deg;
  }
  100% {
    rotate: 12deg;
  }
}

/* Appears at his feet, gets lifted onto the stack, then he starts again. */
@keyframes stack {
  0% {
    translate: 0 22px;
    opacity: 0;
  }
  10% {
    translate: 0 22px;
    opacity: 1;
  }
  55%,
  86% {
    translate: 0 0;
    opacity: 1;
  }
  92%,
  100% {
    translate: 0 0;
    opacity: 0;
  }
}

/* ── Skills: a smith working the anvil ────── */

.smith {
  bottom: 4%;
  left: 72%;
  width: 16%;
  height: 48%;
}

.smith__body {
  bottom: 0;
  left: 22%;
  width: 56%;
  height: 54%;
  background: linear-gradient(180deg, var(--wood-l), var(--wood-d));
  box-shadow: 1px 0 0 var(--wood-d), -1px 0 0 var(--wood-d);
}

.smith__head {
  bottom: 54%;
  left: 28%;
  width: 44%;
  height: 22%;
  background: var(--skin);
  box-shadow: 0 -4px 0 0 var(--wood-d), 1px 0 0 var(--wood-d);
}

.smith__arm {
  bottom: 42%;
  left: 4%;
  width: 18%;
  height: 30%;
  background: var(--skin);
  transform-origin: 50% 0;
  animation: strike-swing 1.2s steps(4) infinite;
}

.smith__hammer {
  top: 70%;
  left: -90%;
  width: 180%;
  height: 28%;
  background: var(--metal-l);
  box-shadow: inset -40% 0 0 var(--metal);
}

.forge__sparks {
  bottom: 18%;
  left: 70%;
  width: 2px;
  height: 2px;
}

/* Sparks fire at 56%, the frame the hammer lands on. */
.forge__sparks i {
  width: 3px;
  height: 3px;
  background: var(--glint);
  box-shadow: 0 0 5px 1px rgba(232, 163, 61, 0.9);
  animation: spark 1.2s steps(4) infinite;
}

.forge__sparks i:nth-child(1) {
  --sx: -16px;
  --sy: -20px;
}

.forge__sparks i:nth-child(2) {
  --sx: -7px;
  --sy: -26px;
}

.forge__sparks i:nth-child(3) {
  --sx: 6px;
  --sy: -22px;
}

.forge__sparks i:nth-child(4) {
  --sx: 15px;
  --sy: -13px;
}

.forge__sparks i:nth-child(5) {
  --sx: -22px;
  --sy: -8px;
}

@keyframes spark {
  0%,
  52% {
    translate: 0 0;
    opacity: 0;
  }
  56% {
    translate: 0 0;
    opacity: 1;
  }
  100% {
    translate: var(--sx) var(--sy);
    opacity: 0;
  }
}

/* ── Campaigns: a watchtower sweeping ─────── */

.tower__beam {
  top: 28%;
  left: 50%;
  width: 96%;
  height: 72%;
  margin-left: -48%;
  background: linear-gradient(180deg, rgba(240, 216, 150, 0.34), transparent 78%);
  clip-path: polygon(46% 0, 54% 0, 100% 100%, 0 100%);
  transform-origin: 50% 0;
  animation: sweep 6.5s ease-in-out infinite alternate;
}

.sentry {
  bottom: 33%;
  left: 36%;
  width: 10%;
  height: 22%;
}

.sentry i:nth-child(1) {
  bottom: 52%;
  left: 8%;
  width: 72%;
  height: 40%;
  background: var(--skin);
  box-shadow: 0 -4px 0 var(--wood-d);
}

.sentry i:nth-child(2) {
  bottom: 0;
  left: 0;
  width: 86%;
  height: 58%;
  background: var(--cloth);
  box-shadow: 1px 0 0 var(--wood-d);
  animation: heave 3.4s steps(3) infinite;
}

@keyframes sweep {
  from {
    rotate: -42deg;
  }
  to {
    rotate: 42deg;
  }
}

@keyframes beacon-pulse {
  to {
    opacity: 0.55;
  }
}

/* ── Builds: a crane hoisting onto scaffold ─ */

.yard__cable {
  bottom: 38%;
  left: 70%;
  width: 2px;
  height: 42%;
  background: var(--metal-l);
  transform-origin: 50% 0;
  animation: hoist-cable 4.4s ease-in-out infinite alternate;
}

.yard__load {
  bottom: 30%;
  left: 64%;
  width: 10%;
  animation: hoist 4.4s ease-in-out infinite alternate;
}

.yard__load img {
  display: block;
  width: 100%;
  height: auto;
  image-rendering: pixelated;
}

.builder {
  bottom: 2%;
  left: 82%;
  width: 12%;
  height: 40%;
}

.builder i:nth-child(1) {
  bottom: 52%;
  left: 18%;
  width: 52%;
  height: 24%;
  background: var(--skin);
  box-shadow: 0 -4px 0 var(--glint);
}

.builder i:nth-child(2) {
  bottom: 0;
  left: 12%;
  width: 64%;
  height: 54%;
  background: linear-gradient(180deg, var(--metal-l), var(--metal));
  box-shadow: 1px 0 0 var(--wood-d);
}

.builder i:nth-child(3) {
  bottom: 44%;
  left: 68%;
  width: 18%;
  height: 28%;
  background: var(--skin);
  transform-origin: 50% 0;
  animation: strike-swing 1.5s steps(4) infinite;
}

@keyframes hoist {
  from {
    translate: 0 70%;
  }
  to {
    translate: 0 0;
  }
}

@keyframes hoist-cable {
  from {
    scale: 1 1.72;
  }
  to {
    scale: 1 1;
  }
}

/* ── Save: a tent and fireflies ───────────── */

.camp__pennant {
  bottom: 78%;
  left: 39%;
  width: 10%;
  height: 8%;
  background: var(--glint);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
  transform-origin: 0 50%;
  animation: flutter 1.9s steps(3) infinite alternate;
}

.fireflies {
  bottom: 16px;
  left: 0;
  width: 100%;
  height: 96px;
}

.fireflies i {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #ffe9a8;
  box-shadow: 0 0 7px 2px rgba(255, 216, 128, 0.65);
  animation:
    firefly-drift var(--t) ease-in-out infinite alternate,
    glimmer calc(var(--t) / 3) steps(2) infinite alternate;
}

@keyframes firefly-drift {
  to {
    translate: var(--dx) var(--dy);
  }
}

@keyframes glimmer {
  to {
    opacity: 0.12;
  }
}

/* ── Shared type ───────────────────────────── */

.eyebrow {
  margin: 0 0 16px;
  color: var(--amber);
  font-family: var(--font-px);
  font-size: 10px;
  letter-spacing: 2px;
  text-transform: uppercase;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.7);
}

.stage__title {
  margin: 0;
  color: var(--cream);
  font-family: var(--font-px);
  font-size: clamp(20px, 3.4vw, 36px);
  line-height: 1.35;
  text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.8);
}

.stage__sub {
  margin: 16px 0 30px;
  color: var(--cream-dim);
  font-size: 1.1rem;
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.7);
}

.prose {
  margin: 0;
  color: #e2d8c6;
  font-size: 1.08rem;
}

/* ── Panels ────────────────────────────────── */

/* ── Ruined castle walls ───────────────────── */

/* Every entry is inscribed on a standing section of broken masonry. The top
   edge is bitten away where the wall has come down, a fissure runs through
   the stone, torchlight falls from the upper left and damp gathers at the
   base. No border: broken walls don't have trim. */

.panel,
.build {
  position: relative;
  background-color: #171320;
  background-image:
    /* torchlight from off the upper left */
    radial-gradient(120% 80% at 10% -6%, rgba(232, 163, 61, 0.16), transparent 58%),
    /* damp and lichen creeping up from the ground */
    radial-gradient(58% 34% at 16% 104%, rgba(104, 132, 84, 0.22), transparent 72%),
    radial-gradient(46% 26% at 84% 104%, rgba(104, 132, 84, 0.14), transparent 72%),
    /* the wall falls into shadow at its edges */
    radial-gradient(130% 105% at 46% 34%, transparent 34%, rgba(0, 0, 0, 0.62) 100%),
    /* enough wash to keep text legible over the masonry */
    linear-gradient(180deg, rgba(13, 11, 18, 0.52), rgba(13, 11, 18, 0.68)),
    url("art/wall-crack.svg"),
    url("art/wall-brick.svg");
  background-repeat: no-repeat, no-repeat, no-repeat, no-repeat, no-repeat, no-repeat, repeat;
  background-position: 0 0, 0 0, 0 0, 0 0, 0 0, right 14% top -24px, 0 0;

  /* Crumbled along the top, with a chunk gone from the lower right corner. */
  clip-path: polygon(
    0% 4.4%, 2.4% 1.4%, 5.6% 3.6%, 9.2% 0.7%, 12.8% 3%, 16.6% 0.5%, 20.8% 2.9%,
    24.6% 1%, 28.8% 4.1%, 33.4% 1.3%, 38.2% 3.3%, 42.6% 0.6%, 47.4% 2.7%,
    52.6% 0.9%, 57.4% 3.7%, 61.8% 1.2%, 66.6% 3%, 71.2% 0.4%, 75.8% 2.5%,
    80.6% 1.1%, 85.4% 3.9%, 89.8% 1.6%, 94.6% 3.2%, 100% 1%,
    100% 93%, 97.4% 96.4%, 96.2% 100%, 0% 100%
  );
  filter:
    drop-shadow(0 3px 0 rgba(0, 0, 0, 0.85))
    drop-shadow(0 16px 24px rgba(0, 0, 0, 0.55));
}

.panel {
  width: 100%;
  max-height: calc(100svh - var(--hud-top) - var(--ground) - var(--hud-bottom) - 169px);
  overflow-y: auto;
  /* Extra headroom so the crumbled top edge bites into stone, not text. */
  padding: clamp(36px, 4vw, 54px) clamp(20px, 2.4vw, 32px) clamp(22px, 2.4vw, 32px);
  scrollbar-width: thin;
  scrollbar-color: rgba(242, 232, 213, 0.35) transparent;
}

/* The blinking advance marker every dialogue box in the genre has. Sticky so
   it rides the bottom edge while the entry scrolls underneath. */
.panel::after {
  content: "▼";
  position: sticky;
  bottom: 0;
  display: block;
  margin-top: 12px;
  padding-right: 20px; /* clear of the broken corner */
  color: var(--amber);
  font-family: var(--font-px);
  font-size: 10px;
  text-align: right;
  animation: advance 1.1s steps(2) infinite;
}

@keyframes advance {
  50% {
    opacity: 0.2;
    translate: 0 2px;
  }
}

/* A bronze plaque bolted to the stone, its bolt head cast at the left. */
.panel__code {
  display: inline-block;
  margin: 0 0 16px;
  padding: 7px 22px 7px 12px;
  color: #24190f;
  background:
    radial-gradient(circle at 6px 50%, rgba(0, 0, 0, 0.55) 0 1.6px, transparent 2.4px),
    linear-gradient(180deg, #e9b463 0%, #c8862f 58%, #9a6220 100%);
  font-family: var(--font-px);
  font-size: 9px;
  letter-spacing: 2px;
  text-transform: uppercase;
  clip-path: polygon(0 0, 100% 0, calc(100% - 11px) 100%, 0 100%);
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.6);
}

/* On a panel it hangs off the broken top edge; inside a build card it stays
   in flow so it can't land on top of the artwork. */
.panel > .panel__code {
  position: absolute;
  top: clamp(12px, 1.6vw, 20px);
  left: clamp(20px, 2.4vw, 32px);
  margin: 0;
}

.panel__code::before {
  content: "◈ ";
}

/* Each region of the road gets its own survey mark. */
.stage[data-group="profile"] .panel__code::after {
  content: " · surveyed";
}

.stage[data-group="loadout"] .panel__code::after {
  content: " · catalogued";
}

.stage[data-group="skills"] .panel__code::after {
  content: " · appraised";
}

.stage[data-group="xp"] .panel__code::after {
  content: " · charted";
}

.stage[data-group="builds"] .panel__code::after {
  content: " · recovered";
}

/* Chiselled: a hard shadow below the strokes and a faint lit lip above, as if
   the letters were cut into the block and catching the torchlight. */
.panel__title {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin: 0 0 18px;
  padding-bottom: 14px;
  color: var(--cream);
  font-family: var(--font-px);
  font-size: clamp(13px, 1.6vw, 18px);
  line-height: 1.5;
  text-shadow:
    0 2px 0 rgba(0, 0, 0, 0.9),
    0 -1px 0 rgba(242, 232, 213, 0.1);
  border-bottom: 2px solid rgba(232, 163, 61, 0.26);
}

/* Struck into the page like a stamp in a logbook, slightly off-square. */
.panel__tag {
  padding: 6px 10px;
  color: var(--ink);
  background: var(--amber);
  border: 2px solid rgba(0, 0, 0, 0.45);
  font-size: 8px;
  letter-spacing: 1px;
  text-transform: uppercase;
  rotate: -3deg;
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.4);
}

.panel__role {
  margin: 0 0 6px;
  color: var(--teal);
  font-size: 1.05rem;
  text-transform: uppercase;
}

.panel__role span {
  color: var(--cream-dim);
}

.panel__role span::before {
  content: " · ";
}

.bullets {
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
}

.bullets--two {
  columns: 2;
  column-gap: 34px;
}

.bullets li {
  position: relative;
  padding: 0 0 12px 22px;
  color: #ded3c0;
  font-size: 1.05rem;
  break-inside: avoid;
}

.bullets li::before {
  content: "▪";
  position: absolute;
  left: 2px;
  color: var(--amber);
}

.bullets b {
  color: var(--cream);
  font-weight: 400;
}

/* ── Stage 00 · title ──────────────────────── */

.stage--title .stage__inner {
  max-width: 860px;
}

.title {
  margin: 0;
  color: var(--cream);
  font-family: var(--font-px);
  font-size: clamp(30px, 6.4vw, 68px);
  line-height: 1.18;
  text-transform: uppercase;
  text-shadow:
    4px 4px 0 rgba(0, 0, 0, 0.85),
    8px 8px 0 rgba(232, 163, 61, 0.35);
}

.title__class {
  min-height: 1.4em;
  margin: 24px 0 0;
  color: var(--cream);
  font-size: 1.3rem;
  letter-spacing: 1px;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.75);
}

.title__class::after {
  content: "_";
  animation: blink 1s steps(1) infinite;
}

@keyframes blink {
  50% {
    opacity: 0;
  }
}

.pips {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 30px 0 0;
  padding: 0;
  list-style: none;
}

.pips li {
  min-width: 140px;
  padding: 11px 14px;
  background: rgba(24, 19, 30, 0.82);
  border: 3px solid var(--line);
  box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.5);
}

.pips b {
  display: block;
  color: var(--amber);
  font-family: var(--font-px);
  font-size: 14px;
}

.pips span {
  display: block;
  margin-top: 8px;
  color: var(--cream-dim);
  font-size: 0.92rem;
}

.title__go {
  margin: 32px 0 0;
}

.btn {
  display: inline-block;
  padding: 15px 22px;
  color: var(--ink);
  background: var(--amber);
  outline: 3px solid rgba(0, 0, 0, 0.6);
  box-shadow: 0 6px 0 rgba(0, 0, 0, 0.45);
  font-family: var(--font-px);
  font-size: 11px;
  letter-spacing: 1px;
  text-decoration: none;
  text-transform: uppercase;
  transition: transform 110ms steps(2), background 110ms steps(2);
}

.btn:hover,
.btn:focus-visible {
  background: var(--cream);
  transform: translateY(-3px);
}

/* ── Stage 01 · profile ────────────────────── */

.profile {
  display: grid;
  grid-template-columns: 250px minmax(0, 1fr);
  gap: 26px;
  align-items: start;
}

.profile__avatar {
  width: 100%;
  image-rendering: pixelated;
  outline: 3px solid rgba(0, 0, 0, 0.5);
}

.profile__name {
  margin: 16px 0 0;
  color: var(--amber);
  font-family: var(--font-px);
  font-size: 12px;
}

.profile__role {
  margin: 10px 0 0;
  color: var(--teal);
  font-size: 1rem;
  text-transform: uppercase;
}

.profile__main {
  display: grid;
  gap: 20px;
}

.stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px 24px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.stat__name {
  display: block;
  margin-bottom: 5px;
  color: var(--cream-dim);
  font-size: 0.98rem;
  text-transform: uppercase;
}

.stat__bar {
  position: relative;
  display: block;
  height: 14px;
  background: rgba(0, 0, 0, 0.5);
  border: 2px solid rgba(242, 232, 213, 0.25);
  overflow: hidden;
}

.stat__bar i {
  display: block;
  width: 0;
  height: 100%;
  background: linear-gradient(90deg, var(--teal), var(--amber));
  transition: width 900ms cubic-bezier(0.2, 0.85, 0.25, 1);
}

.stat__bar::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(90deg, transparent 0 7px, rgba(0, 0, 0, 0.8) 7px 10px);
}

.is-visible .stat__bar i {
  width: var(--fill);
}

.codex {
  margin: 0;
  padding: 16px 18px;
  background: rgba(232, 163, 61, 0.08);
  border-left: 4px solid var(--amber);
  color: #e2d8c6;
  font-size: 1.05rem;
  font-style: italic;
}

.codex p {
  margin: 0 0 10px;
}

.codex cite {
  color: var(--amber);
  font-size: 0.92rem;
  font-style: normal;
  text-transform: uppercase;
}

/* ── Stage 05 · builds (also used by loadout) ─ */

.build {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: 26px;
  align-items: center;
  padding: clamp(34px, 3.6vw, 48px) clamp(18px, 2.2vw, 28px) clamp(20px, 2.2vw, 28px);
}

.build__art {
  display: grid;
  place-items: center;
  min-height: 0;
  background: rgba(0, 0, 0, 0.28);
  outline: 3px solid rgba(0, 0, 0, 0.5);
}

.build__art img {
  width: 100%;
  image-rendering: pixelated;
}

.build__text .prose {
  font-size: 1.38rem;
  line-height: 1.4;
}

/* Loadout reads as inspected gear, not a caption under a picture. */
.kit__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  margin: 0 0 12px;
}

.kit__slot {
  color: var(--cream-dim);
  font-family: var(--font-px);
  font-size: 8px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}

.kit__rarity {
  font-family: var(--font-px);
  font-size: 8px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}

.kit__rarity--rare {
  color: #8ec3c9;
}

.kit__rarity--epic {
  color: #c9a0de;
}

.kit__rarity--legendary {
  color: var(--amber);
}

.kit .panel__tag {
  margin-left: auto;
}

.kit__flavor {
  margin: 0 0 14px;
  color: var(--cream-dim);
  font-size: 1.2rem;
  font-style: italic;
  line-height: 1.35;
}

.kit__flavor::before {
  content: "“";
}

.kit__flavor::after {
  content: "”";
}

.kit__affix {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  margin: 16px 0 0;
  padding: 0;
  list-style: none;
}

.kit__affix li {
  color: var(--amber);
  font-size: 1.05rem;
  text-transform: uppercase;
}

.kit__affix li::before {
  content: "+ ";
}

.kit--rare .build__art {
  box-shadow: inset 0 0 0 3px rgba(142, 195, 201, 0.28);
}

.kit--epic .build__art {
  box-shadow: inset 0 0 0 3px rgba(201, 160, 222, 0.32);
}

.kit--legendary .build__art {
  box-shadow:
    inset 0 0 0 3px rgba(232, 163, 61, 0.45),
    inset 0 0 28px rgba(232, 163, 61, 0.12);
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
}

.tags li {
  padding: 6px 10px;
  color: var(--cream);
  background: rgba(79, 112, 97, 0.3);
  border: 2px solid rgba(242, 232, 213, 0.28);
  font-size: 0.92rem;
  text-transform: uppercase;
}

/* ── Stage 06 · save ───────────────────────── */

.stage--save .stage__inner {
  max-width: 900px;
}

.comms {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.comm {
  display: block;
  padding: 14px 16px;
  color: var(--cream);
  text-decoration: none;
  background: rgba(24, 19, 30, 0.88);
  border: 3px solid var(--line);
  box-shadow: 0 5px 0 rgba(0, 0, 0, 0.45);
  transition: transform 110ms steps(2), border-color 110ms steps(2);
}

.comm:hover,
.comm:focus-visible {
  border-color: var(--amber);
  transform: translateY(-4px);
}

.comm b {
  display: block;
  color: var(--amber);
  font-family: var(--font-px);
  font-size: 9px;
  letter-spacing: 1px;
  text-transform: uppercase;
}

.comm em {
  display: block;
  margin-top: 8px;
  overflow: hidden;
  color: var(--cream-dim);
  font-size: 0.98rem;
  font-style: normal;
  text-overflow: ellipsis;
}

.save__legacy {
  margin: 28px 0 0;
}

.save__legacy a {
  color: var(--cream-dim);
  font-family: var(--font-px);
  font-size: 9px;
  letter-spacing: 1px;
  text-decoration: none;
  text-transform: uppercase;
}

.save__legacy a:hover,
.save__legacy a:focus-visible {
  color: var(--amber);
}

/* ── Narrow screens: the road stands up ────── */

@media (max-width: 900px) {
  html,
  body {
    height: auto;
    overflow: visible;
  }

  body {
    padding-bottom: 56px;
  }

  .road {
    position: static;
    display: block;
    overflow: visible;
    scroll-snap-type: none;
    cursor: auto;
  }

  .stage {
    width: auto;
    min-height: auto;
    padding: 56px clamp(16px, 5vw, 32px);
    scroll-snap-align: none;
  }

  .stage:first-child {
    padding-top: calc(var(--hud-top) + 46px);
  }

  .stage:last-child {
    padding-bottom: calc(var(--ground) + 60px);
  }

  .panel {
    max-height: none;
    overflow-y: visible;
  }

  .bullets--two,
  .stage__inner--pair,
  .stats,
  .profile,
  .build {
    grid-template-columns: minmax(0, 1fr);
    columns: 1;
  }

  .profile__side {
    max-width: 230px;
  }

  .walker,
  .prop,
  .set,
  .hearth,
  .tower {
    display: none;
  }

  .stage--pitch {
    overflow: visible;
  }

  .layer--clouds {
    opacity: 0.4;
  }

  .track {
    left: auto;
    right: clamp(14px, 2.4vw, 30px);
    top: auto;
    bottom: 46px;
    transform: none;
  }

  .cta {
    top: auto;
    bottom: 44px;
    left: clamp(14px, 2.4vw, 30px);
    right: auto;
    padding: 11px 16px;
    font-size: 10px;
  }

  .hints__keys {
    display: none;
  }

  .hints {
    justify-content: center;
  }
}

@media (max-width: 560px) {
  .title {
    font-size: clamp(26px, 9vw, 40px);
  }

  .stage__inner,
  .stage--title .stage__inner,
  .stage--save .stage__inner {
    max-width: none;
  }
}

/* ── Motion ────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition-duration: 1ms !important;
  }

  .road {
    scroll-behavior: auto;
    scroll-snap-type: none;
  }

  .fall,
  .flash,
  .hearth__flame,
  .hearth__glow {
    animation: none !important;
    opacity: 0 !important;
  }
}
