/* HUD. The same drawing language as the profile screen — flat fills, 2 px ink
   lines, hard unblurred shadows, mono for anything being measured — but the
   materials are the other way up, and they have to be.

   On the menu the ground is a ply wall I painted, so a chalk card on it is the
   loud thing. Here the ground is the gym itself: a pale blue-grey wall at the
   top of the frame and a yellow mat at the bottom, both of them light, both of
   them moving. A chalk panel over that is a light shape on a light shape, and
   the one thing a HUD may not do is need looking at twice.

   So the instruments are INK — solid, opaque, no blur behind them — and paper
   is spent on exactly one object: the problem tag, which is the printed card
   from the menu, on the wall, where a gym would have stapled it. That is the
   division. Anything the setter wrote is paper; anything the game is telling
   you about your own body is ink.

   The shadow means the same thing it means on the menu: this stands off the
   wall. Everything the player reads throws one; nothing else does. */

/* Self-hosted, like three and Rapier: the game runs off `npm start` with no
   network, and a HUD whose type arrives from a CDN — or does not — is a HUD
   that lays out differently on the frame the font lands. One variable file per
   subset, 200–1000, so every weight below is a real weight rather than a
   synthesised smear. latin-ext is a separate request the browser only makes if
   a glyph needs it, and the climber name is a text input, so someone will. */
@font-face {
  font-family: "Nunito";
  font-style: normal;
  font-weight: 200 1000;
  font-display: swap;
  src: url("../../assets/fonts/nunito-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Nunito";
  font-style: normal;
  font-weight: 200 1000;
  font-display: swap;
  src: url("../../assets/fonts/nunito-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* This block is the whole palette for both screens — profile.css loads second
   and names its surfaces from these rather than restating the values, for the
   reason config.js gives about arm length: two files holding the same number is
   two files that can disagree about it.

   Every hue is one COLORS in config.js already renders, so a chip of interface
   held against the game is the same colour as the game. */
:root {
  --ink: #2b2233;         /* COLORS.ink — the outline round everything drawn */
  --paper: #f4f1ea;       /* COLORS.chalk */
  --coral: #ff4d6d;       /* COLORS.problem — the holds this route lets you use */
  --good: #5fd97f;        /* COLORS.decoy green */
  --warn: #ffc53d;        /* COLORS.decoy amber */
  /* The one hue with no counterpart in COLORS, and it stays: `Hud#update`
     writes the same rgb into the warning vignette from JS, and the bar that
     says a hand is about to go has to be the colour of the screen edge that
     says it too. It is deliberately not --coral, which already means "this is
     the problem" in both screens and cannot also mean "you are about to fall". */
  --bad: #f87171;

  /* The one muted tone, shared. Two shades below COLORS.tnut because muted text
     has to clear 4.5:1 against the profile screen's ply as well as against
     paper, and at the T-nut's own value it reads 3.0 there. Muting is worth
     having only while the muted thing is still legible. */
  --pencil: #3f4b55;

  /* A local face for data — codes, counts, units, key legends. Whatever the
     machine already has, because of the paragraph above about the network, and
     because Nunito on its own cannot tell a number being read from a number
     being labelled. */
  --mono: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;

  /* The hard shadow every readable object throws, and how far. */
  --cast: rgba(27, 18, 30, .38);
  --lift: 3px;

  /* The one ink lighter than ink, for a groove cut into a slab. */
  --slot: #453b4f;

  --lh: #4cc9f0;
  --rh: #f72585;
  --lf: #80ed99;
  --rf: #ffb703;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: #d9e2e9;                /* COLORS.fog, so the first frame matches */
  font-family: "Nunito", "Segoe UI", system-ui, -apple-system, sans-serif;
  color: var(--paper);
  -webkit-font-smoothing: antialiased;
  /* Not `none`: the buttons still want their taps delivered. `manipulation`
     drops the double-tap zoom and the 300 ms click delay that comes with it,
     which is the only browser gesture this game has any use for losing. The
     canvas has its own `touch-action: none` below, because a drag across it is
     a reach and must never be a scroll. */
  touch-action: manipulation;
}

/* `100vh` on iOS Safari is the height of the page BEHIND the URL bar, so the
   canvas comes out taller than the window and the bottom of the wall — where
   the mat and the start jugs are — sits under the browser chrome. `100dvh` is
   the one that tracks the visible viewport; the `100vh` line above it is the
   fallback for anything that has not heard of it. `Game#resize` reads the
   canvas's own client size, so it follows either without being told. */
#viewport {
  display: block;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  touch-action: none;
}

#hud {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 100vh;
  height: 100dvh;
  pointer-events: none;
  /* `viewport-fit=cover` in the viewport meta is what puts the page under the
     notch, so it is also what makes these necessary. Held upright — which is
     how this game is meant to be held — the cutout lands on the row the problem
     tag and the stats share, and the home indicator lands on the core bar.
     Sideways it is the tag and the core bar again, one edge round. Four insets
     and the same two casualties either way up, so nothing here is per
     orientation. */
  padding:
    max(18px, env(safe-area-inset-top))
    max(18px, env(safe-area-inset-right))
    max(18px, env(safe-area-inset-bottom))
    max(18px, env(safe-area-inset-left));
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto 1fr auto;
  gap: 12px;
  opacity: 0;
  transition: opacity .5s ease;
}
#hud.on { opacity: 1; }
/* The touch controls are the only part of the HUD that takes pointer events, so
   they are the only part that can be pressed while it is faded out and the
   profile screen is up. Nothing there wants pressing then. */
#hud:not(.on) .tbtns, #hud:not(.on) .core { pointer-events: none; }

/* There used to be a vignette here, darkening the corners so translucent panels
   had something to sit on. The panels are opaque now and it was only ever
   propping them up — so it is gone, and the corners of the gym are the gym's
   own colour again. */

/* ---------------- problem tag ---------------- */

/* The one paper object on the screen: the route card from the menu, shrunk to
   what a gym staples beside the start hold. Same parts in the same order —
   coloured tape carrying the grade, then the name — so the thing the player
   picked and the thing now on the wall are recognisably one object. */
.tag {
  grid-column: 1; grid-row: 1;
  display: flex; align-items: stretch;
  align-self: start;
  background: var(--paper);
  color: var(--ink);
  border: 2px solid var(--ink);
  border-radius: 5px;
  overflow: hidden;
  box-shadow: var(--lift) var(--lift) 0 var(--cast);
  transition: opacity .8s ease;
}
/* And then it goes, once the climber leaves the deck — see `Hud#update`. This
   is the one panel on the screen that is READ ONCE: it says which problem is
   taped up, which is a question you have before you start and never again, and
   it is the largest opaque object in the frame. Gone rather than faded to a
   film, because the note at the head of this file is about a HUD that must not
   need looking at twice and a translucent card over a moving wall is exactly
   that. It comes back with the next visit to this screen, not with the next
   attempt: a card sliding back in every time the player falls is motion during
   a fall, which is the one moment they are already watching something. */
.tag.gone { opacity: 0; }
.tag-grade {
  display: flex; align-items: center;
  background: var(--coral);
  color: var(--ink);
  border-right: 2px solid var(--ink);
  font-weight: 1000;
  font-size: 22px;
  letter-spacing: -.03em;
  padding: 9px 13px;
}
.tag-meta { padding: 7px 13px 8px 11px; }
.tag-name {
  font-weight: 1000; font-size: 15px; letter-spacing: -.02em;
  text-transform: uppercase;
}
.tag-sub {
  margin-top: 2px;
  font-family: var(--mono); font-size: 9px;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--pencil);
}

/* ---------------- stats ---------------- */

/* One slab, three cells, value over label — the same shape as the route card's
   spec strip, because it is the same kind of reading. Three separate boxes
   would have said these were three unrelated numbers. */
.stats {
  grid-column: 3; grid-row: 1;
  align-self: start;
  display: flex; gap: 22px;
  background: var(--ink);
  border-radius: 5px;
  padding: 8px 15px 9px;
  box-shadow: var(--lift) var(--lift) 0 var(--cast);
}
.stat { text-align: right; min-width: 56px; }
/* `tabular-nums` is for the fallback face during the swap. Nunito needs it as
   much as it offers it — which is not at all: its ten digits are one width
   (.6em) at every weight, so a ticking clock sits still on its own. */
.stat-v {
  display: block;
  font-size: 20px; font-weight: 1000; letter-spacing: -.035em;
  font-variant-numeric: tabular-nums;
}
.stat-v i { font-style: normal; font-size: 11px; opacity: .6; margin-left: 1px; }
.stat-k {
  display: block; margin-top: 3px;
  font-family: var(--mono); font-size: 8.5px;
  letter-spacing: .14em;
  color: rgba(244, 241, 234, .62);
}

/* ---------------- limb stamina ---------------- */

.limbs {
  grid-column: 1; grid-row: 3;
  display: flex; flex-direction: column; gap: 6px;
  align-self: end;
}
.limb {
  display: grid;
  grid-template-columns: 26px 132px auto;
  align-items: center; gap: 9px;
  background: var(--ink);
  border-radius: 5px;
  padding: 5px 11px 5px 6px;
  box-shadow: var(--lift) var(--lift) 0 var(--cast);
  transition: transform .12s ease, box-shadow .12s ease;
}
/* Slides out and takes a chalk ring — an outline round the thing being
   controlled, which is the same signal the ink line gives everywhere else. */
.limb.active {
  transform: translateX(7px);
  box-shadow: var(--lift) var(--lift) 0 var(--cast), inset 0 0 0 2px var(--paper);
}
.limb.off { opacity: .5; }
.limb.flash { animation: limbFlash .5s ease; }
@keyframes limbFlash {
  0% { background: var(--bad); transform: translateX(11px); }
  100% { background: var(--ink); transform: translateX(0); }
}
/* The badge IS the limb's colour with ink on it, the same keycap the profile
   screen teaches the scheme with. It used to be a grey chip with coloured type,
   which is the same information at a tenth of the glance value. */
.limb-key {
  width: 22px; height: 22px; border-radius: 4px;
  display: grid; place-items: center;
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  color: var(--ink);
}
.limb[data-limb="LH"] .limb-key { background: var(--lh); }
.limb[data-limb="RH"] .limb-key { background: var(--rh); }
.limb[data-limb="LF"] .limb-key { background: var(--lf); }
.limb[data-limb="RF"] .limb-key { background: var(--rf); }
/* A groove cut in the slab rather than a lighter film over it: the fill is a
   flat colour and it needs a flat colour behind it, or the empty part of the
   bar changes value with whatever the camera is pointing at. */
.limb-bar {
  height: 9px; border-radius: 3px;
  background: var(--slot);
  overflow: hidden;
  position: relative;
}
.limb-bar i {
  display: block; height: 100%; width: 100%;
  background: var(--good);
  transform-origin: left center;
  transition: background .18s linear;
}
.limb-name {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: .12em;
  color: rgba(244, 241, 234, .62);
}

/* ---------------- core / coil ---------------- */

/* The only readout with no slab under it: a box round the middle of the bottom
   of the screen is a box round the climber, and this bar is empty most of the
   time. So it is the bare instrument — the same groove-in-ink the limb bars
   are, which is what an empty one should look like, sitting straight on the
   mat. Type is ink for the same reason the tag's is: everything at this end of
   the frame is light, and chalk here would be light on light. */
.core {
  grid-column: 2; grid-row: 3;
  justify-self: center; align-self: end;
  text-align: center;
  width: 260px;
  color: var(--ink);
}
.core-label {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: .18em; margin-bottom: 5px;
}
.core-bar {
  height: 11px;
  background: var(--ink);
  border-radius: 4px;
  overflow: hidden;
  box-shadow: 2px 2px 0 var(--cast);
}
.core-bar i {
  display: block; height: 100%; width: 100%;
  /* Flat, not a ramp from amber to coral. The ramp was the only gradient left
     in the game's interface, and coral is spoken for — it is the colour of the
     holds this problem allows. Stored power is amber and stays amber; what
     changes is how much of the bar there is. */
  background: var(--warn);
  transform: scaleX(0); transform-origin: left center;
}
.core-hint {
  margin-top: 7px;
  font-family: var(--mono); font-size: 9px; letter-spacing: .1em;
  opacity: 0; transition: opacity .25s ease;
}
.core-hint.show { opacity: 1; }
/* The same paper keycap the cheatsheet and the profile screen use. It needs the
   ink line the others do without, because those sit on an ink slab and this one
   sits on the mat. */
.core-hint b {
  display: inline-block;
  padding: 0 4px;
  background: var(--paper); color: var(--ink);
  border: 2px solid var(--ink);
  border-radius: 4px;
}

/* ---------------- controls ---------------- */

.keys {
  grid-column: 3; grid-row: 3;
  align-self: end;
  background: var(--ink);
  border-radius: 5px;
  padding: 10px 13px 11px;
  box-shadow: var(--lift) var(--lift) 0 var(--cast);
  transition: opacity .6s ease;
}
.keys-title {
  font-family: var(--mono); font-size: 8.5px; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase;
  color: rgba(244, 241, 234, .62);
  margin-bottom: 8px;
}
.keys-row { display: flex; align-items: center; gap: 4px; margin-top: 5px; }
/* Paper keycaps with ink on them, and Q E A D wearing their limb's colour —
   the same cap in the same colours as the profile screen's list, so the scheme
   is taught once and then only ever repeated. */
.keys-row b {
  display: inline-grid; place-items: center;
  min-width: 21px; height: 20px; padding: 0 5px;
  background: var(--paper); color: var(--ink);
  border-radius: 4px;
  font-family: var(--mono); font-size: 9px; font-weight: 700;
}
.keys-row b[data-limb="LH"] { background: var(--lh); }
.keys-row b[data-limb="RH"] { background: var(--rh); }
.keys-row b[data-limb="LF"] { background: var(--lf); }
.keys-row b[data-limb="RF"] { background: var(--rf); }
.keys-row span {
  margin-left: 5px; font-size: 11px;
  color: rgba(244, 241, 234, .62);
}
.keys.dim { opacity: .3; }

/* ---------------- banner ---------------- */

/* A stamp, like the one on the route card — so SENT lands in-game as the same
   green block the card wears afterwards, and the player has met it before it
   ever means anything. */
.banner {
  grid-column: 1 / -1; grid-row: 2;
  display: grid; place-items: center;
  opacity: 0; transition: opacity .3s ease;
}
.banner.show { opacity: 1; }
.banner-in {
  text-align: center;
  background: var(--ink); color: var(--paper);
  border: 3px solid var(--ink);
  border-radius: 7px;
  padding: 16px clamp(22px, 5vw, 54px) 20px;
  box-shadow: 6px 6px 0 var(--cast);
}
.banner.send .banner-in { background: var(--good); color: var(--ink); }
.banner.off .banner-in { background: var(--bad); color: var(--ink); }
.banner h1 {
  margin: 0;
  font-size: clamp(34px, 7vw, 76px);
  font-weight: 1000;
  letter-spacing: -.04em;
  line-height: .94;
  text-transform: uppercase;
}
.banner p {
  margin: 8px 0 0;
  font-family: var(--mono); font-size: 11px; letter-spacing: .08em;
  opacity: .72;
}

/* ---------------- warning strip ---------------- */

/* `Hud#update` writes this every frame — the only thing on the screen that is
   allowed to be soft, because it is not an object, it is the room going red. */
.warn-strip {
  position: fixed; inset: 0;
  pointer-events: none;
  box-shadow: inset 0 0 120px 0 rgba(248, 113, 113, 0);
  transition: box-shadow .18s linear;
}

/* ---------------- physics behind ---------------- */

/* Only ever on screen when it is true, and then it is worth the space: a
   starved world runs in slow motion and shortens every reach on a route tuned
   at full speed, which reads as the climber being weak rather than the device
   being slow. See RENDER.starveWarn. */
.perf {
  grid-column: 3; grid-row: 1;
  align-self: start; justify-self: end;
  margin-top: 66px;                 /* clear of the stats, which share the cell */
  padding: 6px 10px 5px;
  background: var(--bad);
  color: var(--ink);
  border: 2px solid var(--ink);
  border-radius: 4px;
  box-shadow: 2px 2px 0 var(--cast);
  font-family: var(--mono); font-size: 9px; font-weight: 700; letter-spacing: .1em;
  display: none;
}
.perf.show { display: block; }

/* ---------------- touch ---------------- */

/* Everything below is off until a finger actually touches the screen — `.touch`
   goes on the body from the pointer type of the last press, not from a capability
   check, because a laptop with a touchscreen is both machines and the controls
   should follow the one in use. */
.touch-keys, .tbtns { display: none; }

body.touch .keys { display: none; }
body.touch .touch-keys { display: block; }

/* The key badge advertises a key that does not exist on a phone. The row still
   carries the limb's colour and its name, which is what it was for. */
body.touch .limb-key { display: none; }
body.touch .limb { grid-template-columns: 132px auto; }

.tbtns {
  grid-column: 3; grid-row: 2;
  align-self: start; justify-self: end;
  flex-direction: column; gap: 8px;
  pointer-events: auto;
}
body.touch .tbtns { display: flex; }

/* Pressable, so it presses: over by the width of its own shadow, the same move
   CLIMB makes on the menu. */
.tbtn {
  appearance: none;
  background: var(--ink);
  border: 0;
  border-radius: 5px;
  color: var(--paper);
  font: inherit; font-family: var(--mono);
  font-size: 10px; font-weight: 700; letter-spacing: .12em;
  /* 44 px is the smallest thing a thumb hits reliably; below that the button is
     decoration. */
  min-width: 96px; min-height: 44px;
  padding: 0 12px;
  box-shadow: var(--lift) var(--lift) 0 var(--cast);
  touch-action: manipulation;
  transition: translate .1s ease, box-shadow .1s ease;
}
.tbtn:active {
  translate: var(--lift) var(--lift);
  box-shadow: 0 0 0 var(--cast);
}
/* Long by default; the narrow queries at the foot of this file swap them. Only
   SHAKE OUT actually loses a word — the other two are their own short form, and
   are written twice so the rule that swaps them can be one rule. */
.t-short { display: none; }
/* SHAKE OUT latches rather than being held: the hand that would hold it down is
   the hand doing the dragging. See the header of game/input.js. */
.tbtn.on { background: var(--warn); color: var(--ink); }

/* The core bar becomes the coil control. It needs a real hit area rather than
   the 12 px of bar it draws, and it needs to stop being transparent to pointers
   — the rest of the HUD stays `pointer-events: none` so a drag across it still
   reaches the wall behind. It gets the slab the desktop version does without,
   because here it is a button and a button has to look pressable. */
body.touch .core {
  pointer-events: auto;
  padding: 9px 16px 11px;
  border-radius: 6px;
  background: var(--ink);
  color: var(--paper);
  box-shadow: var(--lift) var(--lift) 0 var(--cast);
  touch-action: none;
  transition: translate .1s ease, box-shadow .1s ease;
}
/* Inside a slab now rather than sitting on the mat, so it becomes the groove
   the limb bars are — full ink here would be ink on ink — and it stops throwing
   a shadow, because the slab around it is already throwing one. */
body.touch .core-bar {
  height: 14px;
  background: var(--slot);
  box-shadow: none;
}
body.touch .core.held {
  translate: var(--lift) var(--lift);
  box-shadow: 0 0 0 var(--cast);
}
body.touch .core-hint { opacity: .72; }     /* it is the label, so it always shows */

/* ---------------- rotate prompt ---------------- */

#rotate { display: none; }

/* Three gates, and each one is load-bearing.

   `orientation: landscape` is also every desktop window ever opened, so
   `body.touch` is the whole reason a mouse is never nagged — and `in-game`
   keeps it off the profile screen, which reads fine either way up.

   `max-height` is what separates a landscape PHONE, 390 px tall and genuinely
   cramped, from a landscape TABLET at 820, which has room for the full HUD and
   plays perfectly well. Without it the prompt would block an iPad held the way
   most people hold one, and it would be blocking a screen that works. */
@media (orientation: landscape) and (max-height: 560px) {
  body.touch.in-game #rotate {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 100vh;
    height: 100dvh;
    z-index: 30;
    display: grid; place-content: center; gap: 12px;
    text-align: center;
    background: var(--ink);
    padding: 24px;
  }
}
.rot-icon { font-size: 46px; }
.rot-title { font-size: 17px; font-weight: 1000; letter-spacing: -.02em; text-transform: uppercase; }
.rot-sub {
  font-family: var(--mono); font-size: 10px; letter-spacing: .12em;
  color: rgba(244, 241, 234, .62);
}

/* The start card that used to live here is now the profile screen — see
   ui/profile.css. */

@media (max-width: 720px) {
  .keys, .stats .stat:nth-child(3) { display: none; }
  body.touch .touch-keys { display: none; }
  .limb { grid-template-columns: 24px 88px auto; }
  body.touch .limb { grid-template-columns: 88px auto; }
  .limb-name { display: none; }
  .stats { gap: 16px; }

  /* The setter's one-line character note. It is the smallest type on the card
     and the first thing to wrap, and at 390 px it wraps to three lines and
     makes the tag the tallest object on the screen — 91 px of paper over the
     wall to carry a sentence nobody reads twice. The grade and the name are
     what identify the problem; those stay. */
  .tag-sub { display: none; }
  .tag-grade { font-size: 19px; padding: 7px 11px; }
  .tag-meta { padding: 7px 11px 8px 10px; }

  /* Short labels, and the width they buy. 96 px was the 44 px touch floor with
     52 px of margin on top of it; 44 is the floor, so 44 is what it says, and
     the shortest label — MENU — is the only one that ever reaches it.

     They have to be this tight. The row sits in the same grid column as the
     stats, so the column is as wide as whichever is wider, and the tag in the
     other column cannot go below the width of the word DRAGON. At 360 px those
     two plus the gap came to 382, and a fixed grid on a body that hides its
     overflow does not wrap or scroll — it is cut off, so MENU lost its right
     half and the stats slab ran off the edge under it. Three buttons at ~50 px
     leave about 20 px of slack; `tools/_scratch-phone.mjs` prints CLIPPED
     against any panel that crosses an edge, which is the check to run after
     touching any of these widths. */
  .t-long { display: none; }
  .t-short { display: inline; }
  .tbtn { min-width: 44px; padding: 0 10px; font-size: 9.5px; letter-spacing: .06em; }
}

/* A landscape phone is wide and SHORT — 844 x 390 is typical — so it never
   trips the width query above, and at full size the limb list, the core bar and
   the stats meet in the middle. Height is the axis that is actually scarce. */
@media (max-height: 460px) {
  #hud {
    padding:
      max(10px, env(safe-area-inset-top))
      max(12px, env(safe-area-inset-right))
      max(10px, env(safe-area-inset-bottom))
      max(12px, env(safe-area-inset-left));
    gap: 8px;
  }
  .keys { display: none; }
  body.touch .touch-keys { display: none; }
  .tag-grade { font-size: 17px; padding: 6px 10px; }
  .tag-name { font-size: 13px; }
  .tag-sub { display: none; }
  .stats { padding: 5px 12px 6px; gap: 14px; }
  .stat { min-width: 46px; }
  .stat-v { font-size: 15px; }
  .stat-k { font-size: 8px; }
  .limbs { gap: 4px; }
  .limb { grid-template-columns: 22px 76px auto; padding: 3px 9px 3px 5px; }
  body.touch .limb { grid-template-columns: 76px auto; }
  .limb-name { display: none; }
  .limb-bar { height: 7px; }
  .core { width: 190px; }
  /* The middle grid column is only the middle of the screen while there is
     something in the right-hand one, and on touch there never is — the keyboard
     cheatsheet is gone. Span the row instead, so the bar a thumb is aiming for
     is actually in the middle and either thumb can reach it. */
  body.touch .core { grid-column: 1 / -1; padding: 6px 14px 8px; }
  .core-label { display: none; }
  .core-hint { margin-top: 5px; font-size: 8.5px; }
  .tbtns { gap: 6px; }
  .tbtn { min-width: 78px; min-height: 38px; font-size: 9px; letter-spacing: .08em; }
  .perf { margin-top: 48px; }
  .banner-in { padding: 12px 26px 15px; }
  .banner h1 { font-size: clamp(26px, 6vw, 48px); }
  .banner p { font-size: 10px; }
}

/* Held upright — which is how this game is meant to be held on a phone. The
   width query above has already done the compaction (the TIME stat, the keys
   panel and the limb names are gone at 390 px); what is left is that the grid
   is a three-column one and there are not three columns' worth of width. This
   block has to come AFTER it: same specificity, so source order is the only
   thing deciding which column index wins.

   Two columns and a fourth row, and then almost everything stays where it was.
   The middle of the screen is what the change protects: in landscape the right
   edge is 3.2 m of wall away from the climber, and in a 2.7 m wide frame it is
   inside their reach, so anything a thumb can hit has to be out of the band a
   drag crosses. */
@media (orientation: portrait) {
  #hud {
    grid-template-columns: 1fr auto;
    grid-template-rows: auto 1fr auto auto;
  }
  /* `end`, because this column is `auto` and the button row below is now the
     widest thing in it. Without it the stats slab is stretched to the buttons'
     width and the three numbers drift apart inside a box bigger than they are
     — the slab is meant to shrink-wrap them, which is what makes it one
     reading rather than three. */
  .stats { grid-column: 2; justify-self: end; }
  /* Already `align-self: start` in row 2, so they sit straight under the stats
     — top of the screen, clear of the wall a finger drags across. Putting them
     in the bottom stack instead is what index.html warns about: a thumb dragging
     a limb must not clip RESET.

     A ROW, though, and that is the whole point. Stacked they are 96 x 148 —
     ink hanging a fifth of the way down the frame at the right-hand edge, which
     in a 2.7 m wide portrait shot is wall the climber is inside the reach of.
     Lying down they are 44 px tall, they finish inside the band the stats
     already occupy, and the middle of the screen is empty again. */
  .tbtns { grid-column: 2; flex-direction: row; gap: 6px; }
  .keys { grid-column: 2; }
  /* Off the right column, where it would otherwise land on those buttons. */
  .perf { grid-column: 1; grid-row: 2; justify-self: start; margin-top: 0; }

  /* The first column is `auto` in landscape, so everything in it shrink-wraps
     without being asked; it is `1fr` here, so nothing does. Both of these are
     objects with their own size — a card stapled to the wall, and a list of
     four bars — and stretched to the column they stop being either. The limb
     list is the one that matters: spread across 390 px it is four ink slabs
     lying exactly where the climber's feet are at this end of a portrait frame,
     and the corner is the one place down here they never reach. */
  .tag, .limbs { justify-self: start; }

  /* The core bar is the exception, because on touch it IS the coil control and
     a full-width slab on the bottom edge is the one target a thumb should never
     have to aim for. 260 px is a landscape number. */
  .core {
    grid-column: 1 / -1; grid-row: 4;
    width: auto;
    justify-self: stretch;
  }

  /* The word CORE, over a bar, over a line that already says what holding the
     bar does. Three rows for one control, and the one in the middle is the
     control — so the label goes and the hint stays, because the hint is the
     only thing on a phone that says this rectangle is a button at all. Worth
     about 17 px of the bottom edge. */
  body.touch .core-label { display: none; }
  body.touch .core { padding: 7px 16px 9px; }

  /* Four stamina bars as a BODY rather than a list: left column left limbs,
     right column right, hands over feet. The DOM order is already LH RH LF RF,
     so row-major lands each one where its own limb is.

     It is not only smaller — 145 x 39 against 114 x 94, and out of the corner
     the climber's feet occupy at this end of a portrait frame. It is the only
     arrangement that is legible at all down here: the key badge is hidden on
     touch (there is no Q to press) and the name is hidden under 720 px, so a
     COLUMN of four is four anonymous green bars whose order the player has to
     have been told. A 2x2 has to be told nothing. */
  body.touch .limbs {
    display: grid;
    grid-template-columns: auto auto;
    gap: 5px;
  }
  body.touch .limb { grid-template-columns: 54px; padding: 4px 8px; }
}
