/* ===========================================================================
   Hueward — HUD & overlay layer.
   Art direction: ink-wash on paper (GAME_DESIGN.md §7). Torn-paper tags, ink
   drops, brushed edges, lantern lamps. Every node in here is built by
   src/ui.js under <div id="hud">; this file only dresses it.

   Sizing: the HUD overlays a 960x540 internal-resolution canvas and must
   scale with it, so every dimension is expressed in `--u` (one internal
   pixel). ui.js keeps `--u` in sync with the mount's real width.
   =========================================================================== */

/* Zero-specificity defaults: if the page already positions #hud, its rules win. */
:where(#hud) {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

#hud {
  /* One internal (960x540) pixel, in CSS px. The page sets --ui-scale on the stage
     synchronously while fitting the letterbox, so the very first painted frame is
     already correct; ui.js then keeps an inline --u in step with the real width. */
  --u: calc(var(--ui-scale, 1) * 1px);

  /* Paper & ink */
  --paper: #ece6d6;
  --paper-hi: #f7f3ea;
  --paper-lo: #ded6c0;
  --ink: #15131a;
  --ink-soft: #3a3540;
  --wash: #6f6a72;
  --wash-lo: #b9b3b8;

  /* Hues (§7) */
  --blue: #2f7fe6;   --blue-d: #1b4fa0;   --blue-l: #8fc4ff;
  --red: #d8342b;    --red-d: #8f1d1b;    --red-l: #ff8a7a;
  --yellow: #f2c230; --yellow-d: #b88a12; --yellow-l: #fff2a8;
  --green: #3fae5a;  --green-d: #22703a;  --green-l: #a5f0b0;

  --serif: "Iowan Old Style", "Palatino Linotype", Georgia, serif;
  --mono: ui-monospace, Consolas, "SF Mono", Menlo, monospace;

  /* Torn-paper silhouettes */
  --torn-tag: polygon(
    0.8% 8%, 5% 1%, 19% 5%, 33% 0%, 48% 4%, 63% 0%, 79% 5%, 92% 1%, 99% 9%,
    96% 27%, 100% 46%, 97% 65%, 100% 84%, 93% 97%, 78% 100%, 62% 95%,
    46% 100%, 30% 96%, 15% 100%, 4% 94%, 2% 74%, 0% 55%, 3% 33%
  );
  --torn-band: polygon(
    0% 12%, 2% 0%, 12% 5%, 26% 1%, 40% 5%, 54% 1%, 68% 5%, 82% 1%, 96% 5%,
    100% 14%, 99% 50%, 100% 86%, 96% 99%, 82% 95%, 68% 99%, 54% 95%,
    40% 99%, 26% 95%, 12% 99%, 2% 100%, 0% 88%, 1% 50%
  );
  --torn-card: polygon(
    0.6% 4%, 4% 0.8%, 14% 3%, 26% 0.5%, 38% 3%, 50% 0%, 62% 3%, 74% 0.6%,
    86% 3%, 96% 0.8%, 99.4% 5%, 98% 18%, 100% 32%, 98.5% 46%, 100% 60%,
    98% 74%, 99.5% 88%, 96% 98%, 86% 99.5%, 74% 97%, 62% 100%, 50% 97.5%,
    38% 100%, 26% 97%, 14% 99.5%, 4% 98%, 0.6% 92%, 2% 78%, 0% 64%,
    1.5% 50%, 0% 36%, 2% 22%, 0.5% 10%
  );
  --torn-top: polygon(
    0% 15%, 4% 6%, 9% 13%, 14% 4%, 19% 12%, 25% 3%, 31% 11%, 37% 4%,
    43% 13%, 49% 5%, 55% 12%, 61% 3%, 67% 11%, 73% 5%, 79% 13%, 85% 4%,
    91% 12%, 96% 5%, 100% 13%, 100% 100%, 0% 100%
  );

  font-family: var(--serif);
  color: var(--ink);
  pointer-events: none;               /* gameplay input must reach the canvas */
  user-select: none;
  -webkit-user-select: none;
  -webkit-font-smoothing: antialiased;
  z-index: 5;
}

/* The `hidden` attribute must always beat the display rules below (§7). */
#hud[hidden],
#hud [hidden] { display: none !important; }

/* --------------------------------------------------------------------- */
/* Shared paper surfaces                                                  */
/* --------------------------------------------------------------------- */

.hw-paper {
  background-color: var(--paper);
  background-image:
    repeating-linear-gradient(93deg, rgba(21, 19, 26, 0.035) 0 1px, rgba(0, 0, 0, 0) 1px 4px),
    repeating-linear-gradient(2deg, rgba(21, 19, 26, 0.022) 0 1px, rgba(0, 0, 0, 0) 1px 6px),
    radial-gradient(130% 150% at 18% 0%, #f7f2e4 0%, #ece5d2 55%, #dcd4bd 100%);
  color: var(--ink);
  /* Two shadows: a tight ink halo that hugs the torn silhouette (so the tag has a
     visible edge over pale sky as well as dark cave), then depth underneath. */
  filter:
    drop-shadow(0 0 calc(1.2 * var(--u)) rgba(21, 19, 26, 0.85))
    drop-shadow(0 calc(2 * var(--u)) calc(5 * var(--u)) rgba(12, 10, 16, 0.5));
}

.hw-tag {
  position: absolute;
  clip-path: var(--torn-tag);
  padding: calc(9 * var(--u)) calc(15 * var(--u)) calc(10 * var(--u));
}

.hw-label {
  font-family: var(--mono);
  font-size: calc(11 * var(--u));
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* --------------------------------------------------------------------- */
/* Hearts — ink drops, top-left                                           */
/* --------------------------------------------------------------------- */

.hw-hearts {
  left: calc(14 * var(--u));
  top: calc(12 * var(--u));
  transform: rotate(-0.7deg);
}

.hw-hearts .hw-drops {
  display: flex;
  gap: calc(4 * var(--u));
  margin-top: calc(3 * var(--u));
}

.hw-drop {
  width: calc(20 * var(--u));
  height: calc(24 * var(--u));
  display: block;
}

.hw-drop .fillpath { fill: var(--ink); stroke: var(--ink); stroke-width: 1.2; }
.hw-drop .shine { fill: rgba(247, 243, 234, 0.55); }

.hw-drop.is-empty .fillpath {
  fill: none;
  stroke: var(--wash);
  stroke-width: 1.6;
  stroke-dasharray: 3 2.6;
}
.hw-drop.is-empty .shine { display: none; }

.hw-drop.is-last {
  animation: hw-lastbeat 1.4s ease-in-out infinite;
  transform-origin: 50% 60%;
}

@keyframes hw-lastbeat {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.13); }
}

/* --------------------------------------------------------------------- */
/* Score tag — top-right                                                  */
/* --------------------------------------------------------------------- */

.hw-score {
  right: calc(14 * var(--u));
  top: calc(12 * var(--u));
  min-width: calc(126 * var(--u));
  text-align: right;
  transform: rotate(0.6deg);
}

.hw-score-value {
  font-size: calc(31 * var(--u));
  line-height: 1.02;
  font-weight: 600;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
}

.hw-score-sub {
  margin-top: calc(3 * var(--u));
  font-family: var(--mono);
  font-size: calc(12 * var(--u));
  color: var(--ink-soft);
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
}

.hw-muted {
  position: absolute;
  left: calc(16 * var(--u));
  top: calc(78 * var(--u));
  font-family: var(--mono);
  font-size: calc(11 * var(--u));
  letter-spacing: 0.2em;
  color: var(--paper-hi);
  background: rgba(21, 19, 26, 0.82);
  padding: calc(3 * var(--u)) calc(8 * var(--u));
  border-radius: calc(2 * var(--u));
}

/* --------------------------------------------------------------------- */
/* Region name — top-centre, under the status band, fades in on entry     */
/* --------------------------------------------------------------------- */

/* Sits BELOW the status band (which ends ~84u), not level with it. The longest region
   name — "Smoke Gallery", 13 uppercase characters at 19u with 0.19em tracking — is about
   244u wide, so centred it reaches ~602u; the palette rack now starts at ~588u. At the
   old top:16u the two plaques overlapped for the 2.6s the name is up. Dropping the name
   clear of the band fixes that for every name length rather than for these eight. */
.hw-region {
  position: absolute;
  left: 50%;
  top: calc(92 * var(--u));
  transform: translate(-50%, calc(-8 * var(--u)));
  text-align: center;
  opacity: 0;
  transition: opacity 0.55s ease, transform 0.55s ease;
}

.hw-region.is-in {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Sits over both the pale sky and the dark caves, so it carries its own ground:
   near-opaque ink with paper type is ~8:1 over the worst-case light background and
   better over dark. Blurred shadows alone were ~1:1 against pale paper. */
.hw-region-name {
  display: inline-block;
  font-size: calc(19 * var(--u));
  letter-spacing: 0.19em;
  text-indent: 0.19em;
  text-transform: uppercase;
  color: var(--paper-hi);
  background: rgba(21, 19, 26, 0.92);
  padding: calc(4 * var(--u)) calc(14 * var(--u)) calc(5 * var(--u));
  box-shadow: 0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(12, 10, 16, 0.5);
}

.hw-region-rule {
  height: calc(2 * var(--u));
  margin: calc(4 * var(--u)) auto 0;
  width: 52%;
  background: linear-gradient(90deg,
    rgba(236, 230, 214, 0) 0%,
    rgba(236, 230, 214, 0.85) 30%,
    rgba(236, 230, 214, 0.85) 70%,
    rgba(236, 230, 214, 0) 100%);
  filter: drop-shadow(0 calc(1 * var(--u)) 0 rgba(12, 10, 16, 0.8));
}

/* --------------------------------------------------------------------- */
/* Palette — four lantern lamps, bottom-centre                            */
/* --------------------------------------------------------------------- */

/* The rack is a torn-paper plaque, like the hearts and score tags. Without it the
   lantern ironwork is ink over whatever the world happens to be, which disappears
   entirely against near-black stone. With it, all three states read on a constant
   ground everywhere in the map.

   POSITION: the top bar, immediately left of the score tag — NOT the bottom centre it
   used to occupy. Hueward is a platformer with a low camera, so the space under the
   player is where the next ledge, the next spike row and the next drop live; a plaque
   parked there hid the one region the player is steering into. The top band already
   belongs to status (hearts left, score right), the palette IS status, and nothing is
   ever platformed through it.

   `right` is measured to clear the score tag: it sits at right:14u and is 156u wide
   (126u min-width + 2x15u padding), so 190u leaves ~20u of paper between the two even
   when a long score pushes the tag wider. */
.hw-palette {
  position: absolute;
  top: calc(12 * var(--u));
  right: calc(190 * var(--u));
  display: flex;
  gap: calc(6 * var(--u));
  align-items: flex-end;
  padding: calc(5 * var(--u)) calc(10 * var(--u)) calc(6 * var(--u));
  clip-path: var(--torn-band);
  transform: rotate(-0.4deg);      /* between the hearts' -0.7 and the score's +0.6 */
}

/* Two-thirds of the old bottom-bar size. The lamp box stays wider than the artwork
   inside it so the click target does not shrink as fast as the silhouette does. */
.hw-lamp {
  position: relative;
  width: calc(36 * var(--u));
  padding: calc(3 * var(--u)) 0 0;
  text-align: center;
  cursor: pointer;
  pointer-events: auto;                 /* the one HUD element that takes clicks */
  transition: transform 0.18s ease;
}

.hw-lamp.hue-b { --hue: var(--blue);   --hue-d: var(--blue-d);   --hue-l: var(--blue-l); }
.hw-lamp.hue-r { --hue: var(--red);    --hue-d: var(--red-d);    --hue-l: var(--red-l); }
.hw-lamp.hue-y { --hue: var(--yellow); --hue-d: var(--yellow-d); --hue-l: var(--yellow-l); }
.hw-lamp.hue-g { --hue: var(--green);  --hue-d: var(--green-d);  --hue-l: var(--green-l); }

.hw-lamp.is-locked { cursor: default; }
.hw-lamp.is-owned:hover { transform: translateY(calc(-1.5 * var(--u))); }
.hw-lamp.is-lit { transform: translateY(calc(-3 * var(--u))); }

.hw-lamp-halo {
  position: absolute;
  left: 50%;
  top: calc(11 * var(--u));
  width: calc(56 * var(--u));
  height: calc(56 * var(--u));
  margin: calc(-28 * var(--u)) 0 0 calc(-28 * var(--u));
  border-radius: 50%;
  background: radial-gradient(circle, var(--hue-l) 0%, var(--hue) 26%, rgba(0, 0, 0, 0) 68%);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.hw-lamp.is-lit .hw-lamp-halo {
  opacity: 0.5;
  animation: hw-halo 1.9s ease-in-out infinite;
}

@keyframes hw-halo {
  0%, 100% { opacity: 0.38; transform: scale(0.94); }
  50%      { opacity: 0.58; transform: scale(1.06); }
}

.hw-lamp-svg {
  position: relative;
  display: block;
  width: calc(26 * var(--u));
  height: calc(34 * var(--u));
  margin: 0 auto;
  overflow: visible;
}

/* ------------------------------------------------------------------------
   Three states, ONE silhouette. Every part is drawn in every state; only the
   fill, the outline and the glow change:

     locked  hollow ghosted ink, dashed, no colour  -> "absent, go find it"
     owned   solid ink linework, hue in the glass,
             no flame and no halo                   -> "present, but dark"
     lit     the same, glass bright, flame + halo    -> "burning"

   Locked vs owned is a hollow/solid difference, not an opacity difference, so
   the two are separable at a glance in the first minutes of the game.
   ------------------------------------------------------------------------ */

/* --- base state = LOCKED: outline only, nothing filled, no hue anywhere --- */
.hw-l-ring,
.hw-l-iron {
  fill: none;
  stroke: var(--wash);
  stroke-width: 1.9;
  stroke-dasharray: 3.4 2.8;
  stroke-linejoin: round;
}
.hw-l-hook {
  fill: none;
  stroke: var(--wash);
  stroke-width: 2;
  stroke-dasharray: 3.4 2.8;
  stroke-linecap: round;
}
.hw-l-glass {
  fill: none;
  stroke: var(--wash);
  stroke-width: 2.2;
  stroke-dasharray: 4.5 3.5;
  stroke-linejoin: round;
}
.hw-l-rib {
  stroke: var(--wash);
  stroke-width: 1.2;
  stroke-dasharray: 3 3;
  opacity: 0.5;
}
.hw-l-shade { fill: rgba(21, 19, 26, 0.28); opacity: 0; }
.hw-l-shine { fill: none; opacity: 0; }
.hw-l-flame { fill: var(--hue-l); opacity: 0; transform-origin: 50% 70%; }
.hw-l-lock {
  fill: var(--wash);
  font-family: var(--serif);
  font-size: 15px;
  font-weight: 700;
  text-anchor: middle;
}
.hw-lamp.is-locked { opacity: 0.82; }
.hw-lamp.is-locked .hw-lamp-svg { filter: none; }

/* --- OWNED and LIT share the full ink linework --- */
.hw-lamp.is-owned .hw-l-ring,
.hw-lamp.is-lit   .hw-l-ring,
.hw-lamp.is-owned .hw-l-iron,
.hw-lamp.is-lit   .hw-l-iron {
  fill: var(--ink);
  stroke: var(--ink);
  stroke-width: 1.5;
  stroke-dasharray: none;
}
.hw-lamp.is-owned .hw-l-hook,
.hw-lamp.is-lit   .hw-l-hook {
  stroke: var(--ink);
  stroke-width: 2.2;
  stroke-dasharray: none;
}
.hw-lamp.is-owned .hw-l-rib,
.hw-lamp.is-lit   .hw-l-rib {
  stroke: rgba(247, 243, 234, 0.55);
  stroke-width: 1.3;
  stroke-dasharray: none;
  opacity: 0.85;
}
.hw-lamp.is-owned .hw-l-shine,
.hw-lamp.is-lit   .hw-l-shine {
  fill: rgba(247, 243, 234, 0.4);
  opacity: 1;
}
.hw-lamp.is-owned .hw-l-glass,
.hw-lamp.is-lit   .hw-l-glass {
  stroke: var(--ink);
  stroke-width: 2.2;
  stroke-dasharray: none;
}

/* --- OWNED: hue in the glass, but the wick is out --- */
.hw-lamp.is-owned .hw-l-glass { fill: var(--hue-d); }
/* An unlit glass is the hue in shadow: it darkens the paler hues (Yellow reaches
   ~4:1 on paper instead of ~2.5:1) and reads as "cold" beside the lit lamp. */
.hw-lamp.is-owned .hw-l-shade { opacity: 1; }
.hw-lamp.is-owned .hw-l-flame { opacity: 0; }   /* no flame when unlit */

/* --- LIT: bright glass, flame, halo --- */
.hw-lamp.is-lit .hw-l-glass { fill: var(--hue); }
.hw-lamp.is-lit .hw-lamp-svg {
  filter: drop-shadow(0 0 calc(3 * var(--u)) var(--hue-l));
}
.hw-lamp.is-lit .hw-l-flame {
  opacity: 1;
  animation: hw-flame 1.5s ease-in-out infinite;
}

@keyframes hw-flame {
  0%, 100% { transform: scaleY(0.9); opacity: 0.85; }
  50%      { transform: scaleY(1.1); opacity: 1; }
}

.hw-lamp.is-owned .hw-l-lock,
.hw-lamp.is-lit   .hw-l-lock { display: none; }

/* Side by side, "3" + "YELLOW" needs ~41u and the column is 36u — so the compact rack
   stacks them instead of dropping the name. The name is what teaches which key holds
   which hue while all four are still locked and identically grey. */
.hw-lamp-foot {
  margin-top: calc(1 * var(--u));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(2 * var(--u));
}

.hw-lamp-key {
  font-family: var(--mono);
  font-size: calc(9 * var(--u));
  font-weight: 700;
  line-height: 1;
  color: var(--paper-hi);
  background: rgba(21, 19, 26, 0.9);
  padding: calc(2 * var(--u)) calc(4.5 * var(--u));
  border-radius: calc(2 * var(--u));
}

/* On the plaque the ground is known, so plain ink type reads at ~15:1. */
.hw-lamp-name {
  font-family: var(--mono);
  font-size: calc(7.5 * var(--u));
  letter-spacing: 0.04em;
  line-height: 1;
  color: var(--ink);
}

.hw-lamp.is-locked .hw-lamp-name { color: var(--wash); }
.hw-lamp.is-locked .hw-lamp-key { opacity: 0.62; }
.hw-lamp.is-lit .hw-lamp-name { font-weight: 700; }

/* --------------------------------------------------------------------- */
/* Wayfinder — a far-off lantern at the frame edge, in the direction of    */
/* the next objective. game.js does the projection; this only dresses it.  */
/* --------------------------------------------------------------------- */

/* Positioned by its CENTRE: game.js hands over a point on an inset frame, and the
   lamp has to sit ON that point rather than hang off it. */
.hw-way {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(2 * var(--u));
  /* Present, never louder than the world. is-urgent lifts it — see below. */
  opacity: 0.62;
  transition: opacity 0.5s ease;
}

/* The lamp itself is a palette lamp (.hw-lamp.is-lit.hue-*) and inherits every rule
   from the rack — silhouette, ironwork, hue glass, halo. Only three things differ,
   and each is listed here rather than duplicated into a second lantern. */
.hw-way-lamp {
  width: calc(30 * var(--u));
  padding: 0;
  pointer-events: none;     /* the rack takes clicks; a distant light does not */
  cursor: default;
}

.hw-way-lamp .hw-lamp-svg {
  width: calc(30 * var(--u));
  height: calc(39 * var(--u));
}

.hw-way-lamp .hw-lamp-halo {
  top: calc(13 * var(--u));
  width: calc(66 * var(--u));
  height: calc(66 * var(--u));
  margin: calc(-33 * var(--u)) 0 0 calc(-33 * var(--u));
}

/* No paper card. A tag out here would be a third rectangle floating over the
   playfield; ink on a paper halo sits in the world the way the thought line does. */
/* In the lower half of the frame the label sits ABOVE the lamp (ui.js sets is-low).
   Below it, the label would be stacked on top of the outward-pointing trail AND on
   the thought line, which both live down there. */
.hw-way.is-low { flex-direction: column-reverse; }

.hw-way-label {
  text-align: center;
  white-space: nowrap;
  /* Four stacked halos, not two: this is bare type with no card under it, and it
     crosses pale paper, mid-grey silhouettes and near-black stone within one screen. */
  text-shadow:
    0 0 calc(2 * var(--u)) var(--paper-hi),
    0 0 calc(4 * var(--u)) var(--paper-hi),
    0 0 calc(8 * var(--u)) var(--paper-hi),
    0 0 calc(14 * var(--u)) var(--paper);
}

.hw-way-what {
  font-family: var(--mono);
  font-size: calc(9.5 * var(--u));
  font-weight: 700;
  letter-spacing: 0.14em;
  line-height: 1.15;
  color: var(--ink);
}

.hw-way-dist {
  font-family: var(--mono);
  font-size: calc(8 * var(--u));
  letter-spacing: 0.04em;
  line-height: 1.2;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

/* Lost, not exploring. game.js sets this after a stretch with no core, no gem, no
   checkpoint and no new region. Until then the lamp stays quiet, because a waypoint
   that shouts from the first second turns a metroidvania into a corridor. */
.hw-way.is-urgent { opacity: 1; }

/* --------------------------------------------------------------------- */
/* The Wick's thoughts — prose, bottom-centre above the control strip      */
/* --------------------------------------------------------------------- */

/* NOT a toast. A toast is a paper tag with an ink rule and it teaches a control; a
   thought is the character's own voice and it carries the story, so it is bare
   italic ink with only a paper glow to hold it off the background. Sharing one
   visual language would teach the player to skim both. */
.hw-thought {
  position: absolute;
  left: 50%;
  bottom: calc(52 * var(--u));
  width: calc(560 * var(--u));
  margin-left: calc(-280 * var(--u));
  text-align: center;
  font-family: var(--serif);
  font-style: italic;
  font-size: calc(16 * var(--u));
  line-height: 1.38;
  color: var(--ink);
  /* The world behind this swings from near-white paper to near-black stone, and
     italic serif is thin. A paper halo (not a card) keeps it legible on both without
     putting another rectangle over the playfield. */
  text-shadow:
    0 0 calc(3 * var(--u)) var(--paper-hi),
    0 0 calc(7 * var(--u)) var(--paper-hi),
    0 0 calc(14 * var(--u)) var(--paper),
    0 calc(1 * var(--u)) calc(2 * var(--u)) rgba(236, 230, 214, 0.9);
  opacity: 0;
  transform: translateY(calc(6 * var(--u)));
  /* Slow in, slower out: a thought should arrive at reading pace, not snap. */
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.hw-thought.is-in {
  opacity: 1;
  transform: translateY(0);
}

/* The toast stack (bottom-left, x 14-334) and the centred thought (x 200-760) share
   a band, so they can physically overlap. Rather than shunt the prose off-centre for
   a case that only sometimes happens, the thought gives way: a control hint the
   player just asked for outranks a musing, every time. */
.hw-thought.is-in.is-muted {
  opacity: 0.16;
  transition: opacity 0.25s ease;
}

/* --------------------------------------------------------------------- */
/* Control strip — permanent, along the bottom                            */
/* --------------------------------------------------------------------- */

.hw-strip {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: calc(36 * var(--u));
  padding-top: calc(6 * var(--u));
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: nowrap;
  gap: calc(9 * var(--u));
  background:
    linear-gradient(180deg, rgba(21, 19, 26, 0.86) 0%, rgba(21, 19, 26, 0.96) 100%);
  clip-path: var(--torn-top);
}

.hw-ctl {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  gap: calc(4 * var(--u));
  font-family: var(--mono);
  font-size: calc(12 * var(--u));
  letter-spacing: 0.04em;
  color: #e6dfcd;
  white-space: nowrap;
}

.hw-ctl b {
  font-weight: 700;
  color: var(--ink);
  background: var(--paper-hi);
  padding: calc(2 * var(--u)) calc(5 * var(--u));
  border-radius: calc(2 * var(--u));
  font-size: calc(11.5 * var(--u));
  line-height: 1.25;
}

.hw-ctl b.hw-ctl-btn {
  pointer-events: auto;
  cursor: pointer;
}

.hw-ctl b.hw-ctl-btn:hover {
  background: #fff;
  box-shadow: 0 0 0 calc(1.5 * var(--u)) rgba(247, 243, 234, 0.55);
}

.hw-ctl-sep {
  width: calc(1 * var(--u));
  height: calc(13 * var(--u));
  background: rgba(230, 223, 205, 0.28);
}

/* --------------------------------------------------------------------- */
/* Coach card — top-right, non-blocking (§8, engine rule C)               */
/* --------------------------------------------------------------------- */

.hw-coach {
  position: absolute;
  right: calc(18 * var(--u));
  top: calc(132 * var(--u));       /* clear of the score tag (ends ~94u) by ~38u */
  width: calc(292 * var(--u));
  padding: calc(14 * var(--u)) calc(18 * var(--u)) calc(16 * var(--u));
  clip-path: var(--torn-card);
  transform: rotate(-0.9deg);      /* counter to the score tag's +0.6deg */
  transition: opacity 0.35s ease;
}

.hw-coach-title {
  font-size: calc(26 * var(--u));
  letter-spacing: 0.16em;
  line-height: 1.1;
}

.hw-coach-rule {
  height: calc(2 * var(--u));
  margin: calc(6 * var(--u)) 0 calc(8 * var(--u));
  background: linear-gradient(90deg, var(--ink) 0%, rgba(21, 19, 26, 0.15) 100%);
}

.hw-coach-obj {
  font-size: calc(14.5 * var(--u));
  line-height: 1.35;
  margin-bottom: calc(9 * var(--u));
}

.hw-coach-legend {
  display: flex;
  flex-wrap: wrap;
  gap: calc(5 * var(--u)) calc(8 * var(--u));
  margin-bottom: calc(9 * var(--u));
}

.hw-chip {
  display: flex;
  align-items: center;
  gap: calc(4 * var(--u));
  font-family: var(--mono);
  font-size: calc(11 * var(--u));
  letter-spacing: 0.06em;
  color: var(--ink);
}

.hw-chip i {
  display: block;
  width: calc(11 * var(--u));
  height: calc(11 * var(--u));
  border-radius: 50%;
  border: calc(1.5 * var(--u)) solid var(--ink);
  background: var(--chip, var(--wash));
}

.hw-coach-keys {
  font-family: var(--mono);
  font-size: calc(11.5 * var(--u));
  line-height: 1.5;
  color: var(--ink-soft);
}

.hw-coach-skip {
  margin-top: calc(8 * var(--u));
  font-family: var(--mono);
  font-size: calc(11 * var(--u));
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--wash);
}

/* --------------------------------------------------------------------- */
/* Banner — colour-core teaching (§8)                                     */
/* --------------------------------------------------------------------- */

/* The container is pure geometry and must NEVER paint: while no message is up it
   holds no card at all and is `hidden` besides, so nothing cream sits over the
   playfield. All paper styling lives on .hw-banner-card. Because the container is
   absolutely positioned and fixed-size, adding or removing the card shifts nothing. */
/* TOP of the frame, under the region name — NOT the bottom it used to sit at.
   The camera clamps at the map edges, so deep rooms push the player BELOW screen
   centre: in the Warden's Den the Wick stands at y~348 and the old bottom:118u card
   covered y 349-422 — exactly the player, at exactly the moment the Blue core is
   absorbed. Every core banner fires in a room where the camera is unclamped (player
   at y~310) or clamped DOWN (player lower still), so a card that ends by y~200 can
   never cover the Wick. */
.hw-banner {
  position: absolute;
  left: 50%;
  top: calc(128 * var(--u));
  width: calc(600 * var(--u));
  margin-left: calc(-300 * var(--u));
  display: flex;
  justify-content: center;
  background: none;
  border: 0;
  box-shadow: none;
  filter: none;
  clip-path: none;
  padding: 0;
}

.hw-banner-card {
  width: 100%;
  padding: calc(13 * var(--u)) calc(24 * var(--u)) calc(14 * var(--u));
  clip-path: var(--torn-band);
  text-align: center;
  border-left: calc(6 * var(--u)) solid var(--accent, var(--ink));
  opacity: 0;
  transform: translateY(calc(-10 * var(--u))) scale(0.98);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.hw-banner-card.is-in {
  opacity: 1;
  transform: translateY(0) scale(1);
}

.hw-banner-text {
  color: var(--ink);
  font-size: calc(17 * var(--u));
  line-height: 1.34;
  letter-spacing: 0.015em;
}

/* The hue name stays INK (15:1 on paper). Saturated Yellow on paper is ~1.4:1 and
   Red ~3.8:1 — unreadable as body type — so the colour identity moves to an
   underline, which also keeps the meaning from resting on colour alone. */
.hw-banner-text .hw-hue-word {
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--ink);
  border-bottom: calc(3 * var(--u)) solid var(--accent, var(--ink));
  padding-bottom: calc(1 * var(--u));
}

/* --------------------------------------------------------------------- */
/* Toasts — situational hints, bottom-left                                */
/* --------------------------------------------------------------------- */

/* Same rule as the banner: the stack is geometry only. Empty -> no children and
   `hidden`, so it cannot leave a blank tag on screen. */
.hw-toasts {
  position: absolute;
  left: calc(14 * var(--u));
  bottom: calc(46 * var(--u));
  width: calc(320 * var(--u));
  display: flex;
  flex-direction: column-reverse;
  gap: calc(7 * var(--u));
  background: none;
  border: 0;
  box-shadow: none;
  filter: none;
  clip-path: none;
  padding: 0;
}

.hw-toast {
  /* A toast is text the player MUST read, over unpredictable background values, so it
     is fully opaque warm paper with ink text (15:1) — never a translucent wash. */
  padding: calc(8 * var(--u)) calc(12 * var(--u)) calc(9 * var(--u));
  border-left: calc(4 * var(--u)) solid var(--ink);
  clip-path: var(--torn-tag);
  color: var(--ink);
  font-size: calc(13.5 * var(--u));
  line-height: 1.32;
  opacity: 0;
  transform: translateX(calc(-12 * var(--u)));
  /* in fast, out slower: the readable state is reached almost immediately */
  transition: opacity 0.16s ease-out, transform 0.16s ease-out;
}

.hw-toast.is-in { opacity: 1; transform: translateX(0); }
.hw-toast.is-out {
  opacity: 0;
  transform: translateX(calc(-12 * var(--u)));
  transition: opacity 0.3s ease-in, transform 0.3s ease-in;
}

/* --------------------------------------------------------------------- */
/* Floaters — rising score pops                                           */
/* --------------------------------------------------------------------- */

.hw-floaters {
  position: absolute;
  inset: 0;
  background: none;
  border: 0;
  box-shadow: none;
  filter: none;
}

.hw-floater {
  position: absolute;
  font-family: var(--serif);
  font-size: calc(16 * var(--u));
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--paper-hi);
  /* A crisp 1px ink outline rather than a blur: legible on the pale paper ground and
     in the dark caves alike, where a soft glow only worked against one of them. */
  text-shadow:
    calc(1 * var(--u)) 0 0 var(--ink), calc(-1 * var(--u)) 0 0 var(--ink),
    0 calc(1 * var(--u)) 0 var(--ink), 0 calc(-1 * var(--u)) 0 var(--ink),
    calc(1 * var(--u)) calc(1 * var(--u)) 0 var(--ink),
    calc(-1 * var(--u)) calc(1 * var(--u)) 0 var(--ink),
    calc(1 * var(--u)) calc(-1 * var(--u)) 0 var(--ink),
    calc(-1 * var(--u)) calc(-1 * var(--u)) 0 var(--ink),
    0 calc(2 * var(--u)) calc(6 * var(--u)) rgba(12, 10, 16, 0.6);
  transform: translate(-50%, -50%);
  animation: hw-float 0.95s cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
  white-space: nowrap;
}

/* Word pops (CHECKPOINT, ...) arrive at the player, i.e. at screen centre. Keep them
   small, plaque-style and lifted clear of the sprite so they inform without covering
   the thing the player is looking at. */
.hw-floater.is-label {
  font-family: var(--mono);
  font-size: calc(11 * var(--u));
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  text-transform: uppercase;
  color: var(--paper-hi);
  background: rgba(21, 19, 26, 0.92);
  padding: calc(3 * var(--u)) calc(9 * var(--u)) calc(4 * var(--u));
  text-shadow: none;
  margin-top: calc(-30 * var(--u));
}

@keyframes hw-float {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.7); }
  18%  { opacity: 1; transform: translate(-50%, calc(-50% - 7 * var(--u))) scale(1.1); }
  40%  { transform: translate(-50%, calc(-50% - 17 * var(--u))) scale(1); }
  100% { opacity: 0; transform: translate(-50%, calc(-50% - 44 * var(--u))) scale(0.96); }
}

/* --------------------------------------------------------------------- */
/* Pause overlay                                                          */
/* --------------------------------------------------------------------- */

.hw-pause {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: calc(8 * var(--u));
  background:
    radial-gradient(120% 120% at 50% 45%, rgba(21, 19, 26, 0.42) 0%, rgba(21, 19, 26, 0.74) 100%);
}

.hw-pause-title {
  font-size: calc(56 * var(--u));
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  color: var(--paper-hi);
  text-shadow: 0 calc(3 * var(--u)) calc(10 * var(--u)) rgba(0, 0, 0, 0.9);
}

.hw-pause-sub {
  font-family: var(--mono);
  font-size: calc(14 * var(--u));
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #d8d1bf;
}

/* The secondary line is an option, not the instruction: quieter than "resume". */
.hw-pause-restart {
  margin-top: calc(6 * var(--u));
  font-size: calc(11 * var(--u));
  color: #9d968a;
}

/* --------------------------------------------------------------------- */
/* End cards — GAME_OVER / VICTORY (§6)                                   */
/* --------------------------------------------------------------------- */

.hw-end {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;                 /* a click anywhere restarts */
  cursor: pointer;
  background:
    radial-gradient(110% 110% at 50% 45%, rgba(21, 19, 26, 0.55) 0%, rgba(12, 10, 16, 0.88) 100%);
  animation: hw-fadein 0.4s ease both;
}

@keyframes hw-fadein { from { opacity: 0; } to { opacity: 1; } }

.hw-card {
  position: relative;
  width: calc(520 * var(--u));
  padding: calc(24 * var(--u)) calc(34 * var(--u)) calc(22 * var(--u));
  clip-path: var(--torn-card);
  text-align: center;
  animation: hw-cardin 0.45s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes hw-cardin {
  from { transform: translateY(calc(16 * var(--u))) scale(0.97); }
  to   { transform: translateY(0) scale(1); }
}

.hw-card-eyebrow {
  font-family: var(--mono);
  font-size: calc(11 * var(--u));
  letter-spacing: 0.42em;
  text-indent: 0.42em;
  text-transform: uppercase;
  color: var(--wash);
}

.hw-card-title {
  font-size: calc(38 * var(--u));
  line-height: 1.1;
  letter-spacing: 0.09em;
  margin-top: calc(2 * var(--u));
}

.hw-end.is-victory .hw-card-title {
  background: linear-gradient(90deg, var(--blue-d), var(--green-d) 34%, var(--yellow-d) 66%, var(--red-d));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hw-card-rule {
  height: calc(2 * var(--u));
  margin: calc(10 * var(--u)) auto calc(12 * var(--u));
  width: 72%;
  background: linear-gradient(90deg,
    rgba(21, 19, 26, 0) 0%, rgba(21, 19, 26, 0.85) 22%,
    rgba(21, 19, 26, 0.85) 78%, rgba(21, 19, 26, 0) 100%);
}

.hw-card-score {
  font-size: calc(52 * var(--u));
  line-height: 1;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.hw-card-scorelabel {
  font-family: var(--mono);
  font-size: calc(11 * var(--u));
  letter-spacing: 0.34em;
  text-indent: 0.34em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: calc(2 * var(--u));
}

.hw-best {
  margin: calc(9 * var(--u)) auto 0;
  display: inline-block;
  font-family: var(--mono);
  font-size: calc(12 * var(--u));
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--paper-hi);
  background: var(--ink);
  padding: calc(4 * var(--u)) calc(12 * var(--u));
  transform: rotate(-1.4deg);
}

.hw-hues {
  display: flex;
  justify-content: center;
  gap: calc(9 * var(--u));
  margin: calc(14 * var(--u)) 0 calc(4 * var(--u));
}

.hw-hue {
  width: calc(30 * var(--u));
  height: calc(30 * var(--u));
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono);
  font-size: calc(13 * var(--u));
  font-weight: 700;
  color: var(--wash);
  border: calc(2 * var(--u)) dashed var(--wash);
  background: rgba(0, 0, 0, 0);
}

.hw-hue.is-found {
  color: var(--paper-hi);
  border: calc(2 * var(--u)) solid var(--ink);
  background: var(--chip);
  box-shadow: 0 0 calc(10 * var(--u)) var(--chip);
  text-shadow: 0 calc(1 * var(--u)) calc(2 * var(--u)) rgba(12, 10, 16, 0.8);
}

.hw-stats {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: calc(6 * var(--u)) calc(8 * var(--u));
  margin-top: calc(14 * var(--u));
}

.hw-stat { text-align: center; }

.hw-stat-k {
  font-family: var(--mono);
  font-size: calc(10.5 * var(--u));
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--wash);
}

.hw-stat-v {
  font-size: calc(21 * var(--u));
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

.hw-card-prompt {
  margin-top: calc(16 * var(--u));
  font-family: var(--mono);
  font-size: calc(13 * var(--u));
  letter-spacing: 0.13em;
  color: var(--ink);
  animation: hw-breathe 2.2s ease-in-out infinite;
}

.hw-card-prompt b {
  background: var(--ink);
  color: var(--paper-hi);
  padding: calc(2 * var(--u)) calc(6 * var(--u));
  border-radius: calc(2 * var(--u));
  font-weight: 700;
}

@keyframes hw-breathe {
  0%, 100% { opacity: 0.62; }
  50%      { opacity: 1; }
}

/* --------------------------------------------------------------------- */
/* Reduced motion — kill the decorative loops, keep every state legible.   */
/* --------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  #hud *,
  #hud *::before,
  #hud *::after {
    animation-duration: 0.001s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001s !important;
  }
  .hw-lamp.is-lit .hw-lamp-halo { opacity: 0.5; }
  .hw-lamp.is-lit .hw-l-flame { opacity: 1; }
  .hw-card-prompt { opacity: 1; }
  /* Floaters carry information (score), not decoration — keep them readable. */
  .hw-floater { animation-duration: 0.95s !important; }
}
