/* style.css -- Sinking City.
   Sunlit toy diorama on a warm tabletop. Every panel on screen is a physical
   object: a red swallowtail ribbon, or a cream wooden token with a warm brown
   edge and a soft shadow under it. No glass, no blur, no translucency. */

:root {
  --table:      #E9BE84;
  --table-edge: #DCA463;
  --cream:      #FFF0D2;
  --cream-lit:  #FFF8E8;
  --cream-dip:  #F3DEB4;
  --brown:      #7A4E2C;
  --brown-dp:   #4E2F18;
  --red:        #E03A2F;
  --red-dk:     #BE2A21;
  --yellow:     #FFC93C;
  --grass:      #96D94E;
  --blue:       #4FA8D8;
  --ink:        #3A2418;

  --cond: 'Arial Narrow', 'Liberation Sans Narrow', 'Nimbus Sans Narrow',
          'DejaVu Sans Condensed', 'Helvetica Neue', Impact, system-ui, sans-serif;
  --body: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;

  --shadow-soft: 0 3px 0 rgba(78, 47, 24, .18), 0 8px 18px rgba(78, 47, 24, .22);
  --shadow-hard: 0 4px 0 rgba(78, 47, 24, .26), 0 12px 26px rgba(78, 47, 24, .30);
}

* { box-sizing: border-box; }

/* the hidden attribute has to beat a display rule on the same element, which
   it does not do on its own */
[hidden] { display: none !important; }

html, body {
  margin: 0; padding: 0; height: 100%; width: 100%;
  overflow: hidden;
  background: var(--table);
  color: var(--ink);
  font-family: var(--body);
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  touch-action: none;
  overscroll-behavior: none;
}

canvas#c {
  position: fixed; inset: 0; display: block;
  width: 100%; height: 100%;
  z-index: 0;
}

#fx { position: fixed; inset: 0; z-index: 2; pointer-events: none; overflow: hidden; }

#err {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 99;
  max-height: 38%; overflow: auto; display: none;
  background: var(--red); color: #fff;
  font: 12px/1.45 ui-monospace, monospace; padding: 8px 10px; white-space: pre-wrap;
}

/* ======================================================================= type */
.cond {
  font-family: var(--cond);
  text-transform: uppercase;
  letter-spacing: .10em;
  font-weight: 700;
}

/* ================================================================== the ribbon */
.ribbon {
  position: relative;
  background: linear-gradient(#C93227 0 16%, var(--red) 16% 100%);
  color: #fff;
  padding: .42em 1.5em .40em;
  clip-path: polygon(0 0, calc(100% - .85em) 0, 100% 50%,
                     calc(100% - .85em) 100%, 0 100%, .85em 50%);
  box-shadow: 0 .12em 0 rgba(120, 30, 24, .55), 0 .3em .5em rgba(78, 47, 24, .28);
}
.ribbon-text {
  position: relative; z-index: 2;
  display: block; text-align: center;
  font-family: var(--cond); text-transform: uppercase;
  letter-spacing: .16em; font-weight: 700;
  text-shadow: 0 .09em 0 rgba(120, 30, 24, .55);
  white-space: nowrap;
}

.ribbon.hero {
  font-size: clamp(1.5rem, 8.6vmin, 5.4rem);
}

/* THE BAND IS THE SCREEN'S HEADER, AND IT IS THE FRAME'S AND NOT THE PANEL'S.
   The red runs the whole width of the frame and sits flat against the top of
   it: a band that stops short of both edges is a panel that happens to be red,
   and one that floats half way down the screen is not a header at all.

   It gets there by taking the screen's own gutter back with a negative margin,
   so the content below keeps that gutter and the band does not. The width is
   the same sum in the other direction, which leaves the band's margin box
   exactly the width of the content box: no free space is left over, so nothing
   centres it, aligns it or stretches it by even a pixel and the same rule holds
   whatever the screen's flex direction is.

   NO SWALLOWTAIL HERE. A notch is cut into an end, and this band has no ends,
   it has the edges of the frame. The message ribbons on the other screens are
   the same band, so they carry the same flat ends. */
.screen > .ribbon.hero {
  align-self: flex-start;
  width: calc(100% + var(--sx) + var(--sxr));
  margin: calc(env(safe-area-inset-top) - var(--sy))
          calc(-1 * var(--sxr)) 0 calc(-1 * var(--sx));
  clip-path: none;
  /* THE RED IS THE FRAME'S, THE WORDS ARE THE ROOM'S. The band paints edge to
     edge, and what it says is centred on the room the platform's own sheet has
     left, which is the same axis the menu and the board are centred on: a title
     centred on the frame while the panels are centred beside the sheet is a
     title that looks half a sheet off, and with a sheet down the right of a
     wide window it is a title half under it. */
  padding-right: calc(1.5em + var(--room-right, 0px));
}
.ribbon.hero.small { font-size: clamp(1.2rem, 4.6vmin, 2.4rem); }
.ribbon.hero.brandplate { container-type: inline-size; }

/* THE TWO MESSAGE BANDS CARRY THE MARK AS WELL, and this rule is what makes
   that fit. They used to hold a line of type and nothing else, so the upgrade
   choice and the end of a run -- the two screens a person is most likely to
   photograph, and the two a picture of the page is most likely to be taken
   from -- were pictures of this page with no mark anywhere in them. The HUD's
   own mark is up in both states, and the band lies over it: put a probe at the
   centre of the HUD's drawing and what is hit is the band's own type. So the
   one mark on the page was behind the band and nobody was seeing it, which is
   why a photograph of either screen could not settle the logo question. A logo
   that is absent from exactly the frames somebody keeps is not a logo the work
   can be judged on.

   THE MARK IS SIZED BY THE BAND'S OWN SMALL FONT, not by the title band's. The
   message band is `clamp(1.2rem, 4.6vmin, 2.4rem)`, which holds at 19.2px at
   the narrow end, and the mark drawn at that cap is 143px of ink: wide enough
   to read, and 24px tall inside a band of 53. Taking the title band's larger
   `clamp(1.7rem, 7vmin, 2.8rem)` instead -- which the `brandplate` class asks
   for in a short window -- would put a 34px mark in a 76px band, and the end
   screen at 568x320 has 320px of height and no more. So the small size wins
   here, on purpose, and it is stated rather than inherited.

   THE MARK IS ALONE IN THE BAND, exactly as the title screen's is. The line of
   type that used to be in the band is not put under the mark: that was tried,
   and it read as a wordmark with a tagline beneath it, which the brief names
   and forbids -- the mark is the name and nothing hangs off it. So the band
   holds the mark and nothing else, and the message goes below it as the
   screen's own headline. */
.ribbon.hero.small.brandplate { font-size: clamp(1.2rem, 4.6vmin, 2.4rem); }

/* THE MESSAGE IS A HEADLINE UNDER THE BAND, and it takes a backing only where
   the background makes one necessary. The end screen is solid: a line of type
   on it is legible as it stands, and a pill there would be height spent on a
   surface that is already there -- the end screen is the one screen with four
   chips, three buttons and a board on it, and 720px of window at a laptop. The
   choices screen is NOT solid: the city is behind it, and this line was tried
   as plain type over that and came out red on sand, unreadable, under a tree.
   So the choices screen gets the pill, in the same cream and brown the clear
   callout on the end screen already uses for exactly the same reason, and the
   end screen gets the line.

   Which screen is solid is what decides it, rather than a second class to keep
   in step: if one of them stops being solid it takes the backing with it. */
.bandnote {
  margin: 0; text-align: center;
  font-family: var(--cond); text-transform: uppercase;
  letter-spacing: .16em; font-weight: 700;
  font-size: clamp(.95rem, 3.4vmin, 1.5rem);
  color: var(--red);
}
.screen:not(.solid) .bandnote {
  justify-self: center; align-self: center;
  color: var(--brown-dp);
  background: linear-gradient(var(--cream-lit), var(--cream-dip));
  border: .16em solid var(--brown); border-radius: 999px;
  padding: .34em 1.1em .38em;
  box-shadow: var(--shadow-soft);
  /* AND IT IS NOT GLUED TO THE LINE UNDER IT. Measured with a Range on the
     headline's own glyph run, the pill's bottom edge and the cap tops were at
     the same pixel -- gap 0.0 at 320x568, 568x320, 844x390 and 1280x720 -- so
     at the short landscape the border and its shadow met the top of the letters
     and the word read as cut in half by the band. Half an em of clear air is
     what stops that, and it costs nothing: the choices screen has 124px of
     window under its lowest element at the tightest shape. */
  margin-bottom: .5em;
}

/* ==================================================================== the mark
   One mark, drawn once in tools/logo/mark.py and written out by
   tools/logo/emit.py: the mark on the page, the symbol on its own, the light
   and dark colourways and the app icon all come off the same grid, so none of
   them can drift from the others. Nothing here re-draws it by hand.

   THE MARK IS THE NAME. It was drawn as a lockup -- the disc, then SINKING CITY
   -- and the person looked straight at the disc and said to take it off and
   leave the name. So the banner is the wordmark alone, and the symbol lives
   where it reads best and where it always was: the app icon, the favicon and
   the symbol-only file beside this one.

   That is also why there is no container query here any more. The old rule
   dropped the wordmark below its minimum content width and let the symbol carry
   the mark alone; with the symbol no longer in the banner that rule would hide
   the entire mark on a narrow screen. One mark, one regime, no swap.

   The wordmark's cap height is the banner's font size, so the mark scales with
   the banner and with nothing else. It is one colour, --mark, and it never
   takes a shadow, a glow, an outline, a gradient or a section's accent.

   Clear space is three times the mark's stroke weight, which is 0.66 of its cap
   height, held on all four sides: --brand-clear. The band's own padding is
   exactly that unit, so the mark stands the same distance off the top, the
   bottom and both ends, and there is no notch left on an end to clear.

   THE NUMBER BELOW IS DERIVED FROM THE DRAWING, NOT CHOSEN. emit.py prints it
   on every run, so it can be checked against the mark rather than remembered:
     wordmark ink width   7.455 cap heights   (mark.py: 745.46 over a 100 cap)
     its clear space on both sides  2 x 0.66 = 1.32 cap
     so the band has to be 7.455 + 1.32 = 8.775 cap wide, and the cap is taken
     as min(the size the band asks for, what the band can hold). The mark is
     therefore never squeezed by a flex shrink, which would scale it by a factor
     its own clear space did not get. */
.ribbon.hero.brandplate {
  /* The band is the width of the frame, and the frame less the room the
     platform's sheet has taken is what the mark may actually stand on: the
     clear space at the ends is held inside that, so a mark measured against
     the whole frame is a mark whose last letter sits under the sheet. */
  --brand-fit: calc(100vw - var(--room-right, 0px));
  /* 8.775 is 7.455 of ink plus 1.32 of clear space, both measured off the
     drawing and printed by emit.py. The 1em ceiling is the band's own font
     size, which is where the sizes asked for top out. */
  --brand-cap: min(1em, calc(var(--brand-fit) / 8.775));
  /* OPTICAL, NOT MATHEMATICAL: the drawing's band runs from the cap overshoot
     to the descender, so its own middle sits at 41.9% of the ink box and a
     centred box lifts the letters by a tenth of the mark. The eye centres the
     cap band, not the descender, and the cap band is what is put on the
     banner's middle. 0.16129 is the descender, 20 of the band's 124 units. */
  --brand-opt: calc(var(--brand-h) * .16129);
  padding: calc(var(--brand-clear) + var(--brand-opt))
           var(--brand-clear)
           var(--brand-clear);
  /* the clear space, plus the room the platform's sheet has taken, so the mark
     is centred on the same axis as the panel under it */
  padding-right: calc(var(--brand-clear) + var(--room-right, 0px));
}
.brand, .ribbon.hero.brandplate {
  /* the fallback is for a .brand used outside a banner; --brand-cap, when it is
     there, always wins, because it is declared on the banner and inherited */
  --brand-h: calc(var(--brand-cap, 1em) * 1.24);      /* the drawing's band */
  --brand-clear: calc(var(--brand-cap, 1em) * .66);   /* stroke weight x 3 */
  --brand-gap: calc(var(--brand-cap, 1em) * .13);     /* one letter-space */
}
.brand {
  --mark: var(--brown-dp);          /* on the page's light surfaces */
  /* it is an <h1>, and the reader's own sheet makes an h1 twice its parent.
     The cap is the banner's font size and nothing else, so say so. */
  font-size: 1em; font-weight: inherit;
  display: flex; align-items: flex-start; justify-content: center;
  gap: var(--brand-gap);
  margin: 0; padding: 0; line-height: 0;
  color: var(--mark);
}
.brand.on-dark { --mark: var(--cream-lit); }   /* on the red ribbon, and the pit */

.brand-lock {
  display: flex; align-items: flex-start; justify-content: center;
  gap: var(--brand-gap);
}
.brand-mark, .brand-word {
  display: block; flex: none;                    /* never squeezed, never scaled
                                                    apart from its partner */
  height: var(--brand-h); width: auto;
}

/* NO CONTAINER QUERY HERE ON PURPOSE. A `@container (max-width: ...)` that hid
   .brand-word used to swap the lockup for the symbol alone. The banner is the
   wordmark now, so the same rule would blank the title on a narrow screen: the
   one thing this page cannot do. The symbol-only variant is still shipped, as
   site/img/logo-mark.svg and as the app icon; it just is not a state of this
   banner. */

/* the mark is a link on no screen, so it has no hover and no focus ring: it is
   the page's title, not a control. If that changes it takes a colour-only
   hover and a ring drawn outside --brand-clear, and never a scale. */

/* ================================================================ the counters */
.chip {
  display: inline-flex; align-items: baseline; gap: .55em;
  background: linear-gradient(var(--cream-lit), var(--cream-dip));
  border: .14em solid var(--brown);
  border-radius: 999px;
  padding: .30em .95em .34em;
  box-shadow: var(--shadow-soft);
}
.chipk {
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .14em;
  font-weight: 700; font-size: .72em; color: var(--brown);
}
.chipv {
  font-family: var(--cond); font-weight: 700; font-size: 1.15em;
  color: var(--ink); font-variant-numeric: tabular-nums;
  letter-spacing: .02em; white-space: nowrap;
}

.scorepill {
  display: inline-flex; flex-direction: column; align-items: center;
  background: linear-gradient(var(--cream-lit), var(--cream-dip));
  border: .22em solid var(--brown);
  border-radius: 1.1em;
  padding: .26em .85em .34em;
  box-shadow: var(--shadow-hard);
}
.scorelabel {
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .22em;
  font-weight: 700; font-size: .52em; color: var(--brown); line-height: 1.25;
}
.scorevalue {
  font-family: var(--cond); font-weight: 700; line-height: .95;
  font-size: 2.35em; color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: .01em;
}

.mult {
  margin-top: .35em;
  display: inline-block;
  background: var(--yellow);
  border: .14em solid var(--brown);
  border-radius: 999px;
  padding: .12em .7em .16em;
  font-family: var(--cond); font-weight: 700; font-size: .8em;
  letter-spacing: .10em; color: var(--brown-dp);
  font-variant-numeric: tabular-nums;
  box-shadow: var(--shadow-soft);
}

/* =================================================================== the timer */
.timer-ribbon {
  min-width: 7.4em; font-size: clamp(1rem, 2.6vmin, 1.55rem);
  overflow: hidden;
}
.timer-ribbon .ribbon-text { color: #fff; }
.timer-ribbon .ribbon-text:empty { display: none; }
.ribbon-fill {
  position: absolute; top: 0; bottom: 0; right: 0; width: 0;
  background: linear-gradient(#8E1E16, #C1251B);
  z-index: 1;
}
.timer-ribbon.urgent { background: linear-gradient(#A82A20 0 16%, #C93227 16% 100%); }
.timer-ribbon.urgent .ribbon-text { animation: tick .9s steps(2, end) infinite; }
@keyframes tick { 50% { opacity: .72; } }

/* ============================================================== the hole glyph */
.sizebox {
  display: flex; align-items: center; gap: .7em;
  background: linear-gradient(var(--cream-lit), var(--cream-dip));
  border: .18em solid var(--brown);
  border-radius: 1.4em;
  padding: .55em .95em .55em .7em;
  box-shadow: var(--shadow-hard);
}
.holeglyph {
  position: relative; width: 3.4em; height: 3.4em; flex: none;
}
.glyph-ring {
  position: absolute; inset: 0; border-radius: 50%;
  background: var(--cream-dip);
  border: .16em solid var(--brown);
  overflow: hidden;
}
.glyph-meter {
  position: absolute; left: 0; right: 0; bottom: 0; height: 0%;
  background: linear-gradient(var(--yellow), #F0A81E);
  transition: height .12s linear;
}
.glyph-hole {
  position: absolute; left: 50%; top: 50%;
  width: 78%; height: 78%; border-radius: 50%;
  transform: translate(-50%, -50%) scale(.30);
  background: radial-gradient(circle at 50% 34%, #3A2418 0 34%, #2B1A22 62%, #150C12 100%);
  box-shadow: 0 0 0 .10em #6B4A2E, inset 0 -.18em .3em rgba(107, 58, 42, .55);
  transition: transform .3s cubic-bezier(.3, 1.6, .5, 1);
}
.glyph-shards i {
  position: absolute; left: 50%; top: 50%;
  width: .42em; height: .42em; margin: -.21em 0 0 -.21em;
  background: #6B4A2E;
  border: .05em solid #4E2F18;
  border-radius: .08em;
  transition: opacity .2s, transform .3s cubic-bezier(.3, 1.6, .5, 1);
}
.sizelabel { display: flex; flex-direction: column; gap: .1em; }
.sizecap {
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .2em;
  font-size: .58rem; font-weight: 700; color: var(--brown); line-height: 1;
}
.sizestage {
  font-family: var(--cond); font-weight: 700; line-height: 1;
  font-size: 1.6rem; color: var(--ink); font-variant-numeric: tabular-nums;
}
.sizenext {
  font-family: var(--body); font-size: .6rem; color: var(--brown);
  white-space: nowrap; line-height: 1;
}

/* ================================================================= the buttons */
.actionbtn {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: clamp(4.4rem, 13vmin, 6rem); height: clamp(4.4rem, 13vmin, 6rem);
  border-radius: 1.2em;
  border: .18em solid var(--brown);
  background: linear-gradient(var(--yellow), #F0A81E);
  box-shadow: var(--shadow-hard);
  cursor: pointer; padding: 0;
  font-family: var(--cond); color: var(--brown-dp);
  -webkit-user-select: none; user-select: none;
}
.actionbtn .actlabel { font-size: 1.02rem; font-weight: 700; letter-spacing: .1em; z-index: 2; }
.actionbtn .actkey {
  font-size: .56rem; letter-spacing: .18em; opacity: .72; z-index: 2;
}
.actionbtn .cd {
  position: absolute; left: 0; right: 0; bottom: 0; height: 0;
  background: rgba(78, 47, 24, .40); z-index: 1;
}
.actionbtn.spent { background: linear-gradient(var(--cream-lit), var(--cream-dip)); }
.actionbtn:active { transform: translateY(.12em); box-shadow: var(--shadow-soft); }

.helpbtn {
  width: clamp(2.4rem, 8vmin, 3rem); height: clamp(2.4rem, 8vmin, 3rem);
  border-radius: 50%;
  border: .16em solid var(--brown);
  background: linear-gradient(var(--cream-lit), var(--cream-dip));
  box-shadow: var(--shadow-soft);
  font-family: var(--cond); font-weight: 700; font-size: 1.25rem;
  color: var(--brown); cursor: pointer; padding: 0;
}

/* ================================================================== the banner */
.combobanner {
  position: absolute; left: 50%; top: 34%;
  transform: translate(-50%, -50%);
  background: linear-gradient(var(--cream-lit), var(--yellow));
  border: .14em solid var(--brown);
  border-radius: 999px;
  padding: .30em 1.1em .36em;
  box-shadow: var(--shadow-hard);
  font-family: var(--cond); font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; font-size: clamp(.78rem, 2.4vmin, 1.15rem);
  color: var(--brown-dp);
  white-space: nowrap; max-width: 88vw; overflow: hidden; text-overflow: ellipsis;
  pointer-events: none;
}
.combobanner.pop2 { animation: banner .3s cubic-bezier(.28, 1.7, .5, 1); }
.combobanner.c { background: linear-gradient(var(--cream-lit), var(--yellow)); }
@keyframes banner {
  0% { transform: translate(-50%, -50%) scale(.55); opacity: 0; }
  100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
}

/* ================================================================ the score pop */
.pop {
  position: absolute; left: 0; top: 0;
  font-family: var(--cond); font-weight: 700;
  font-size: clamp(1.05rem, 3.2vmin, 1.9rem);
  color: var(--cream);
  font-variant-numeric: tabular-nums; letter-spacing: .04em;
  white-space: nowrap; pointer-events: none;
  transform-origin: 50% 50%;
  text-shadow:
    -.075em 0 0 var(--yellow), .075em 0 0 var(--yellow),
    0 -.075em 0 var(--yellow), 0 .075em 0 var(--yellow),
    -.055em -.055em 0 var(--yellow), .055em -.055em 0 var(--yellow),
    -.055em .055em 0 var(--yellow), .055em .055em 0 var(--yellow),
    0 .13em 0 rgba(78, 47, 24, .38);
  will-change: transform, opacity;
}
.pop.big { font-size: clamp(1.6rem, 5.2vmin, 3.1rem); color: #fff; }

/* ================================================================== the layout */
#hud {
  position: fixed; inset: 0; z-index: 3;
  pointer-events: none;
  font-size: clamp(13px, 2.1vmin, 19px);
  padding: max(.7em, env(safe-area-inset-top)) max(.8em, env(safe-area-inset-right))
           max(.7em, env(safe-area-inset-bottom)) max(.8em, env(safe-area-inset-left));
  /* ONE NUMBER FOR THE BAND'S HEIGHT, USED BY THE BAND AND BY THE THREE THINGS
     STANDING ON IT, so the paint and the contents cannot drift apart. The
     3.9em is the score pill, which is the tallest of the three: two insets plus
     3.9em is what holds it whole at every shape, and it is measured rather than
     guessed (`.podbot/hudmeasure.py` prints all three heights). */
  --sy: max(.7em, env(safe-area-inset-top));
  --hud-band-h: calc(var(--sy) * 2 + 3.9em);
}
#hud.hidden { display: none; }
#hud > * { pointer-events: auto; }
/* THE BAND ACROSS THE TOP OF THE RUN. Same red, same edge-to-edge run and the
   same flat top edge as the banner on the screens between runs, so the page has
   one header rather than a different one per screen. Its height is the inset
   plus the tallest thing standing on it, which is the score pill.

   IT IS PAINT AND NOT A TARGET: `pointer-events: none`, because a full-width
   element that took pointers would swallow every drag aimed at the city under
   it, and dragging at the city is the one thing this screen is for. */
#hud .hud-band {
  pointer-events: none;
  position: absolute; top: 0; left: 0; right: 0;
  height: var(--hud-band-h);
  background: linear-gradient(#C93227 0 16%, var(--red) 16% 100%);
  box-shadow: 0 .12em 0 rgba(120, 30, 24, .55), 0 .3em .5em rgba(78, 47, 24, .28);
}

/* THE TIMER ON THE BAND IS A CREAM PLATE. It was the red flag, and red on red
   has no edge: the band is what is behind it now. It keeps its shape, its
   drain as the clock runs down and its tick when the clock is nearly out -- the
   text turns red instead of the plate. */
#hud .timer-ribbon {
  background: linear-gradient(var(--cream-lit), var(--cream-dip));
  border: .14em solid var(--brown);
  border-radius: 999px;
  clip-path: none;
  box-shadow: var(--shadow-soft);
  padding: .30em 1.15em .34em;
}
#hud .timer-ribbon.urgent { background: linear-gradient(var(--cream-lit), var(--cream-dip)); }
#hud .timer-ribbon .ribbon-text {
  color: var(--brown-dp); text-shadow: none;
}
#hud .timer-ribbon.urgent .ribbon-text { color: var(--red-dk); }
/* the drain reads as a shade going across the plate rather than as a red flag
   half eaten */
#hud .timer-ribbon .ribbon-fill {
  background: linear-gradient(#8E1E16, #C1251B); opacity: .22;
  border-radius: 0 999px 999px 0;
}

/* the score multiplier hangs under the band rather than standing on it, so the
   pill keeps the band's middle when it appears and the band never resizes
   mid-run */
#hud .hud-topleft .mult { position: absolute; top: calc(100% + .25em); left: 0; }

/* the three things on the band stand on its middle */
#hud .hud-topleft, #hud .hud-topright, #hud .hud-top {
  top: 0; height: var(--hud-band-h);
  display: flex; align-items: center;
}
#hud .hud-topleft  { position: absolute; left: max(.8em, env(safe-area-inset-left)); flex-direction: column; align-items: flex-start; justify-content: center; }
#hud .hud-topright { position: absolute; right: max(.8em, env(safe-area-inset-right)); }
#hud .hud-bottomleft{ position: absolute; left: max(.8em, env(safe-area-inset-left)); bottom: max(.7em, env(safe-area-inset-bottom)); }
#hud .hud-bottomright{
  position: absolute; right: max(.8em, env(safe-area-inset-right)); bottom: max(.7em, env(safe-area-inset-bottom));
  display: flex; align-items: flex-end; gap: .55em;
}
.actions { display: flex; gap: .55em; order: 1; }
.helpbtn { order: 2; flex: none; }

/* A TAP ON ? IS NOT A HOLD. The card holds the clock while it is up, and this
   button sits in the corner of the HUD where a thumb rests and where anything
   that taps whatever it finds will land. So the card opens on a HELD press and
   a tap only says how. This is that saying: small, tied to the button it is
   about, over the corner of the city rather than across it, and gone on its
   own. It is absolutely placed, so showing it never moves the buttons. */
.holdhint {
  position: absolute; right: 0; bottom: calc(100% + .5em);
  pointer-events: none; white-space: nowrap;
  background: linear-gradient(var(--cream-lit), var(--cream-dip));
  border: .12em solid var(--brown);
  border-radius: 999px; padding: .22em .68em .28em;
  box-shadow: var(--shadow-soft);
  font-family: var(--cond); font-weight: 700; letter-spacing: .1em;
  /* the floor is what a 320-wide phone gets, and it is set by legibility
     rather than by fit: the pill has room to spare at that width */
  text-transform: uppercase; font-size: clamp(.68rem, 2vmin, .82rem);
  color: var(--brown-dp);
}
.holdhint b { color: var(--brown); }

/* ====================================================== the mark while playing */
/* THE START SCREEN IS NOT THE PAGE. The banner carries the mark on the menu,
   and then the menu goes away and a run is four minutes with the HUD and
   nothing else: a frame taken mid-run showed score, timer, size, sound and
   help, and no mark anywhere on it. So the same drawing stands at the top
   middle of the run. It is mark.py's wordmark, injected by the same emit.py
   that fills the banner, into markers of its own, so the two cannot drift.

   Same rules the banner keeps, restated here rather than inherited, because
   the banner's are scoped to its own width arithmetic and inheriting a broken
   half of them would be worse than saying them twice:
     one colour token, --mark, and no shadow, glow, outline or gradient on the
     drawing itself; clear space of three times the stroke weight held on all
     four sides, which is .66 of the cap; and no swap to a shorter form at any
     size, because the mark is the name and there is nothing to drop.

   IT STANDS BARE ON THE BAND, in cream, exactly as the banner draws it, and it
   got there by looking rather than by reasoning. It used to sit on a solid
   cream plate, and the plate was right when the mark stood over the city: a
   busy area earns a backing and never a halo. Then the band went in behind it,
   and the plate became the one thing on this page that did NOT match the person's
   own picture: their band, and this page's banner, both put the wordmark
   straight on the red, and a plate is a second panel on a band that is meant to
   be one shape. Three bands at one width, `.podbot/proof/the-three-bands.png`,
   is what settled it: on the plate the run's mark read as a chip between two
   other chips, and bare it reads as the page's name. --mark is the cream here,
   the same token the banner's `.on-dark` uses.

   THE CAP IS THE BAND'S, NOT A NUMBER PICKED FOR A PHONE: as tall as the band
   holds with the mark's clear space kept, and no wider than the room between
   the score pill and the clock, which is what binds on a narrow phone. Its
   clear space and its optical lift are the banner's own fractions of the cap,
   so the two bands centre the mark the same way. */
/* centred by its own box rather than by a transform, so the band's height rule
   above can put it on the band's middle without fighting a translate */
#hud .hud-top {
  position: absolute; left: 0; right: 0;
  justify-content: center;
  pointer-events: none;         /* the band is not a target either */
}
.hud-brand {
  --mark: var(--cream-lit);                 /* straight on the band's red */
  /* TWO CEILINGS, WHICHEVER IS LOWER. The height one: the band is the mark's
     to stand up in, and what stops it is its own clear space, ink 1.24 + clear
     .66 + clear .66 + the optical lift .20 = 2.76 caps on a band 5.3em tall,
     which is 1.92; 1.75 leaves the letters a little air off the band's edges
     rather than setting them against it. The width one: the mark is centred on
     the frame, so what it may have is twice the distance from the middle to the
     nearer of the score pill and the clock, both of which sit in the band's
     corners. 8.775 is the banner's own divisor, 7.455 of ink plus 1.32 of clear
     space, and 18em is that pair of neighbours plus the insets and a gap each
     side, measured at 320 wide (`.podbot/bandcheck.py` prints the two gaps at
     seven shapes). The width ceiling is what binds on a phone and the height
     one on a laptop, which is the ordering the picture wants: a phone band with
     a score pill and a clock on it has no width to spare. */
  --hud-cap: min(1.75em, calc((100vw - 18em) / 8.775));
  --hud-clear: calc(var(--hud-cap) * .66);  /* stroke weight x 3 */
  /* OPTICAL, NOT MATHEMATICAL, and the banner's own fraction: the drawing's
     band runs from the cap overshoot to the descender, so its box middle sits
     below the cap band's and a box that is centred puts the letters high. The
     lift is .16129 of the ink, which is .2 of the cap, added above and not
     below. */
  --hud-opt: calc(var(--hud-cap) * .20);
  display: block;
  padding: calc(var(--hud-clear) + var(--hud-opt))
           var(--hud-clear)
           var(--hud-clear);
  color: var(--mark);
  line-height: 0;
}
/* the drawing's band is 1.24 of its cap: 124 units over a 100 unit cap */
.hud-word { display: block; height: calc(var(--hud-cap) * 1.24); width: auto; }

/* a narrow phone: shrink the furniture and pull the corners in */
@media (max-width: 640px), (max-height: 460px) {
  #hud { font-size: clamp(12px, 3.4vw, 16px); }
  .scorevalue { font-size: 1.95em; }
  .holeglyph { width: 2.7em; height: 2.7em; }
  .sizestage { font-size: 1.25rem; }
  .sizenext { display: none; }
  /* THE CLOCK IS AS WIDE AS THE LONGEST CLOCK AND NO WIDER. 6.2em was 99px of
     plate around 58px of text, and on a 320-wide band that 12px of nothing was
     the whole gap between the clock and the mark: the two cream plates met, and
     two plates that meet read as one plate with a seam in it. Every clock this
     page draws is four characters, and every one of them measures 87px (the
     only five-character time, 10:00, is never shown -- the run is four
     minutes), so 5.6em is 89.6px: still above the widest clock, so the plate
     cannot jump when the digits change, and the 9.6px it gives back is a real
     gap. */
  .timer-ribbon { min-width: 5.6em; }
}

/* ============================================================== the help card */
.helpcard {
  position: fixed; left: 50%; transform: translate(-50%, -50%);
  z-index: 6;
  /* THE CARD STANDS IN THE ROOM BELOW THE BAND, NEVER OVER IT. The card is cream
     and the wordmark on the band is cream; the card is fixed at z-index 6 and
     the band carries no z-index at all, so a card centred on the screen cut the
     mark in half. At 844x390 its top edge landed at y 48 while the wordmark runs
     28 to 63, and the letters were sliced across the baseline (shot:
     `.podbot/proof/what-the-help-card-covers.png`). Centring it inside what is
     left over under the band, and capping its height by that same room, means no
     card at any shape can reach up into the mark.
     `--card-roof` is the band's own height, the same number the band and the
     three things standing on it use, so the two cannot drift apart. */
  --card-roof: var(--hud-band-h, 4.5em);
  top: calc(var(--card-roof) + (100% - var(--card-roof)) / 2);
  max-height: calc(100% - var(--card-roof) - 1.2em);
  /* It still covers as little of the city as it can and stay readable: the
     person can see the game they are about to go back to, and it scrolls rather
     than ever growing into the band. */
  width: min(88vw, 26rem); overflow-y: auto;
  background: linear-gradient(var(--cream-lit), var(--cream-dip));
  border: .2em solid var(--brown);
  border-radius: 1.2em;
  box-shadow: var(--shadow-hard);
  padding: .8rem .95rem .95rem;
  text-align: left;
}
.helpcard h2 {
  margin: .05rem 0 .4rem;
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .16em;
  font-size: 1.1rem; color: var(--brown-dp);
}
.helpcard p { margin: .34rem 0; font-size: .86rem; line-height: 1.4; color: var(--ink); }
.helpcard b { color: var(--brown-dp); }
/* the second way out, said plainly, because the card is dismissible by any
   press outside it and that is not a thing anyone can guess */
.helpcard .wayout {
  margin: .5rem 0 .1rem; font-size: .78rem; color: var(--brown);
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .1em;
  text-align: center;
}
.helpgrip {
  position: absolute; left: 50%; top: .42rem; transform: translateX(-50%);
  width: 3.4rem; height: .32rem; border-radius: 999px; background: rgba(122, 78, 44, .35);
}
/* A SHORT WINDOW GETS A TIGHTER CARD. At 568x320 the room under the band is
   235px and the card's full dress is 293px, which pushed BACK TO IT below the
   fold: the way out of the card became something to go looking for. This is
   spacing and leading only, nothing is dropped, so the card still says all of
   it and the run is still held the whole time it is up. Measured by
   `.podbot/helpgap.py` at eight shapes, both orientations. */
@media (max-height: 430px) {
  .helpcard { padding: .5rem .85rem .55rem; border-radius: .9em; }
  .helpcard h2 { font-size: .95rem; margin: 0 0 .18rem; }
  .helpcard p { font-size: .78rem; line-height: 1.26; margin: .18rem 0; }
  .helpcard .wayout { margin: .25rem 0 0; font-size: .66rem; }
}

/* ================================================================= the screens */
.screen {
  position: fixed; inset: 0;
  /* The platform's own sheet is drawn over this page when a run ends: across
     the bottom of a narrow window, down the right of a wide one. It is on top
     of everything here and it is not ours to move, so the content is laid out
     in the room it leaves instead of under it. The two lengths are written by
     the page from a measurement of the shell around it (see measureRoom in
     game.js) and are zero whenever there is no sheet, which is the case before
     the first run and whenever the page is opened on its own.

     THE ROOM IS TAKEN OUT OF THE CONTENT, NOT OUT OF THE PAINT. It is padding
     and it used to be an inset, and an inset shrinks the box: the haze stopped
     102px short of the right edge and left a strip of bare city with a straight
     edge down it, which is the cut the person pointed at. Padding moves the
     content off the sheet and leaves the vignette covering the whole frame. */
  --sx: .9rem;                                   /* the frame's own gutter */
  --sxr: calc(.9rem + var(--room-right, 0px));   /* ... and the room on the right */
  --sy: max(1rem, env(safe-area-inset-top));
  --syb: calc(max(1.4rem, env(safe-area-inset-bottom)) + var(--room-bottom, 0px));
  z-index: 4;
  display: flex; flex-direction: column; align-items: center;
  overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
  padding: var(--sy) var(--sxr) var(--syb) var(--sx);
  /* a warm vignette, not a wall: the city stays visible behind the panels,
     because the city is the best thing on this screen */
  background:
    radial-gradient(115% 78% at 50% 38%, rgba(233, 190, 132, 0) 26%, rgba(201, 145, 88, .62) 88%,
                    rgba(176, 120, 70, .80) 100%),
    linear-gradient(180deg, rgba(220, 164, 99, .72) 0, rgba(233, 190, 132, 0) 22%,
                    rgba(233, 190, 132, 0) 62%, rgba(220, 164, 99, .70) 100%);
}
.screen[hidden] { display: none; }
.screen > .ribbon { flex: none; }

/* THE PANEL TAKES THE MIDDLE OF THE FRAME, not the top of it. The menu, the
   picker and the finish screen are what the person is looking at, and a table
   set against the ceiling of the room is not where anybody looks for it.

   THE BAND IS THE ONE THING THAT DOES NOT. It is the header and it is pinned to
   the top; everything under it takes what is left, which is why this counts
   from the band's next sibling rather than from the first child.

   AUTO MARGINS AND NOT justify-content: center. A centred flex line that is
   taller than its container overflows both ends, and the top of it cannot be
   scrolled to: the first line of a tall menu would simply be gone on a small
   phone. An auto margin absorbs free space when there is some and falls back to
   zero when there is not, so one rule centres a screen that fits and still
   scrolls, from the very top, a screen that does not. */
.screen > .ribbon.hero + * { margin-top: auto; }
.screen > :last-child { margin-bottom: auto; }

/* the end screen is the market stall: it becomes the tabletop rather than
   sitting on top of the city */
.screen.solid {
  background:
    radial-gradient(115% 78% at 50% 34%, rgba(255, 248, 232, .70) 0, rgba(255, 248, 232, 0) 58%),
    linear-gradient(180deg, #E9BE84 0%, #E6B67C 46%, #DCA463 100%);
}

/* The menu and the board, as one thing, so that they can be centred under the
   band as a pair and set side by side on a window wide enough for both. The
   band is not in here: it is the screen's header. */
.pair {
  display: flex; flex-direction: column; align-items: center;
  width: 100%; gap: .85rem;
}
/* The menu card centres itself with `margin: ... auto`, and an auto margin on a
   flex item eats the free space before the line's own centring ever sees it:
   side by side that left the menu in the middle of the gap and jammed the board
   against the right edge of the frame, so the pair read as two cards with an
   accident between them rather than as one thing in the middle. The pair is
   what is centred here, so its two cards carry no side margins of their own. */
.pair > .startbody, .pair > .menuboard, .pair > .board {
  margin-left: 0; margin-right: 0;
}

.startbody, .overbody {
  width: min(calc(94vw - var(--room-right, 0px)), 54rem);
  display: flex; flex-direction: column; align-items: center;
  gap: .85rem; margin: .9rem auto 0;
}
/* the sign the title sits on: a chunky painted board, not a glass panel */
.sign {
  width: min(92vw, 30rem);
  background: linear-gradient(var(--cream-lit), var(--cream-dip));
  border: .22em solid var(--brown);
  border-radius: 1.3em;
  box-shadow: var(--shadow-hard);
  padding: 1.15rem 1.2rem 1.25rem;
  gap: .7rem;
  position: relative;
}
.sign::before {
  content: ''; position: absolute; inset: .35rem .35rem auto .35rem; height: .18rem;
  background: rgba(122, 78, 44, .18); border-radius: 999px;
}
.sign .tagline { font-size: clamp(.85rem, 2.4vmin, 1.15rem); }
.sign .hint { font-size: .78rem; max-width: 24em; }
.sign .loadtext { margin: 0; }
.tagline {
  margin: 0; font-family: var(--cond); text-transform: uppercase;
  letter-spacing: .14em; font-weight: 700;
  font-size: clamp(.95rem, 3vmin, 1.5rem); color: var(--brown-dp);
  text-align: center;
}
.startrow { display: flex; flex-wrap: wrap; gap: .55rem; justify-content: center; }
.hint { margin: 0; font-size: .82rem; color: var(--brown); text-align: center; max-width: 30em; }

.loadbar {
  width: min(78vw, 22rem); height: .7rem; border-radius: 999px;
  background: var(--cream-dip); border: .14em solid var(--brown); overflow: hidden;
  box-shadow: inset 0 .1em .2em rgba(78, 47, 24, .18);
}
.loadfill {
  height: 100%; width: 0%;
  background: linear-gradient(var(--grass), #6FBE38);
  transition: width .2s linear;
}
.loadtext {
  margin: -.5rem 0 0; font-family: var(--cond); text-transform: uppercase;
  letter-spacing: .18em; font-size: .62rem; color: var(--brown);
}
/* the loading row is for the wait and only for the wait */
.loadbar.done, .loadtext.done {
  opacity: 0; pointer-events: none; transition: opacity .5s ease;
}

.btn {
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .14em;
  font-weight: 700; cursor: pointer;
  border-radius: 999px; border: .18em solid var(--brown);
  background: linear-gradient(var(--cream-lit), var(--yellow));
  color: var(--brown-dp);
  box-shadow: var(--shadow-hard);
  padding: .5em 1.6em .56em;
  transition: transform .08s, box-shadow .08s;
}
.btn.big { font-size: clamp(1.15rem, 3.6vmin, 1.9rem); padding: .5em 2.1em .56em; }
.btn.small { font-size: clamp(.85rem, 2.4vmin, 1.1rem); }
.btn:hover { filter: brightness(1.04); }
.btn:active { transform: translateY(.14em); box-shadow: var(--shadow-soft); }
.btn[disabled] { opacity: .55; cursor: default; transform: none; }

/* =========================================================== the upgrade cards */
/* The picker is the one thing on screen while it is up, so it sits in the
   middle of the screen rather than at the top of it. The wrapper centres with
   auto block margins rather than justify-content, because auto margins fall
   back to zero when the content is taller than the screen and the top of the
   picker stays reachable on a short landscape phone; centred flex content
   would overflow off the top and never scroll back. */
#choices { justify-content: center; }
.choicewrap {
  display: flex; flex-direction: column; align-items: center;
  width: 100%; margin: auto 0;
}
.cardrow {
  display: flex; flex-wrap: wrap; gap: 1.1rem; justify-content: center;
  margin-top: 1.4rem; width: min(96vw, 58rem);
}
.card {
  position: relative; text-align: left; cursor: pointer;
  width: min(43vw, 25rem);
  background: linear-gradient(var(--cream-lit), var(--cream-dip));
  border: .2em solid var(--brown);
  border-radius: 1.2em;
  box-shadow: var(--shadow-hard);
  padding: 2rem 1.5rem 1.35rem;
  font-family: var(--body); color: var(--ink);
  display: flex; flex-direction: column; gap: .6rem;
  animation: cardin .3s cubic-bezier(.24, 1.6, .5, 1) both;
}
.card:nth-child(2) { background: linear-gradient(var(--cream-lit), #FFE7B4); }
@keyframes cardin {
  0% { transform: translateY(1.2rem) scale(.94); opacity: 0; }
  100% { transform: none; opacity: 1; }
}
.card .cardtag {
  position: absolute; left: 50%; top: -.95rem; transform: translateX(-50%);
  background: var(--red); color: #fff;
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .16em;
  font-weight: 700; font-size: .7rem;
  padding: .24em 1em .28em; border-radius: 999px;
  border: .13em solid var(--red-dk);
  box-shadow: 0 .12em 0 rgba(120, 30, 24, .4);
  white-space: nowrap;
}
.card .cardname {
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .12em;
  font-weight: 700; font-size: clamp(1.4rem, 4.4vmin, 2.15rem);
  color: var(--brown-dp); text-align: center;
}
.card .cardblurb { font-size: 1rem; line-height: 1.45; text-align: center; }
.card .cardpick {
  margin-top: .45rem; text-align: center;
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .18em;
  font-weight: 700; font-size: .8rem; color: var(--brown);
}
.card:hover { filter: brightness(1.03); }
.card:active { transform: translateY(.14em); }
.card.taken { outline: .3em solid var(--grass); outline-offset: -.05em; }
.card[disabled] { cursor: default; }
.card[disabled]:not(.taken) { opacity: .45; filter: grayscale(.4); }

/* ============================================================ the market stall */
.sectionhead {
  margin: .3rem 0 -.1rem;
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .18em;
  font-weight: 700; font-size: clamp(.85rem, 2.4vmin, 1.1rem); color: var(--brown-dp);
  text-align: center;
}
.loadout {
  width: 100%;
  display: grid; gap: .45rem;
  grid-template-columns: repeat(auto-fill, minmax(clamp(4.4rem, 11vmin, 7rem), 1fr));
}
.tierhead {
  grid-column: 1 / -1;
  margin-top: .5rem;
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .2em;
  font-weight: 700; font-size: .66rem; color: var(--brown);
  border-bottom: .12em solid rgba(122, 78, 44, .35);
  padding-bottom: .16rem;
}
.loot {
  position: relative;
  background: linear-gradient(var(--cream-lit), var(--cream-dip));
  border: .13em solid var(--brown);
  border-radius: .8em;
  box-shadow: var(--shadow-soft);
  padding: .35rem .3rem .3rem;
  display: flex; flex-direction: column; align-items: center; gap: .12rem;
  animation: cardin .28s cubic-bezier(.24, 1.6, .5, 1) both;
}
.loot img {
  width: 100%; aspect-ratio: 1; object-fit: contain; display: block;
  filter: drop-shadow(0 .12rem .12rem rgba(78, 47, 24, .28));
}
.lootname {
  font-size: .6rem; line-height: 1.12; text-align: center; color: var(--brown-dp);
  /* two lines and no ellipsis: every prop gets its whole name on the stall,
     and the reserved height keeps the counts on one baseline across a row */
  max-width: 100%; min-height: 2.24em;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: break-word;
}
.lootn {
  font-family: var(--cond); font-weight: 700; font-size: .78rem;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.overrow { display: flex; gap: .6rem; flex-wrap: wrap; justify-content: center; margin-top: .4rem; }

.board {
  width: min(calc(94vw - var(--room-right, 0px)), 32rem);
  margin-top: .5rem; padding-bottom: .5rem;
}
.board .row {
  display: flex; align-items: center; gap: .6rem;
  padding: 0 .7rem;
  background: linear-gradient(var(--cream-lit), var(--cream-dip));
  border: .12em solid var(--brown); border-radius: 999px;
  box-shadow: var(--shadow-soft);
  font-size: .88rem;
}
.board .row.mine { background: linear-gradient(var(--cream-lit), var(--yellow)); }
/* the tag on a run of your own. It keeps its own width: the name beside it is
   the thing that gives way, and a squeezed row would drop the score off the
   end of a phone */
.board .row .youtag {
  flex: 0 0 auto; font-style: normal;
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .08em;
  font-size: .72em; line-height: 1.6;
  color: var(--cream-lit); background: var(--brown);
  border-radius: 999px; padding: 0 .6em;
}
.board .row .rank {
  font-family: var(--cond); font-weight: 700; color: var(--brown);
  font-variant-numeric: tabular-nums; min-width: 1.8em;
  /* the number is a rank, not a column: left to grow it took as much of the
     row as the name beside it and pushed the name into an ellipsis */
  flex: 0 0 auto;
}
.board .row a, .board .row span { flex: 1; color: var(--ink); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board .row a { text-decoration: underline; text-decoration-thickness: .08em; }
.board .row b { font-family: var(--cond); font-variant-numeric: tabular-nums; }
.boardempty { font-size: .85rem; color: var(--brown); text-align: center; }

/* The boards live on the menu as well as at the stall. It is one list drawn in
   two places from one call, so the two can never disagree about who is on top.
   The menu copy is the tighter of the two: the menu has a start button and a
   phone held upright is not a tall screen, so the rows lose a little padding
   and the menu shows fewer of them. */
.menuboard {
  width: min(92vw, 30rem);
  margin-top: .1rem; padding: .9rem 1rem 1rem;
}
.menuboard .row { font-size: .8rem; padding: 0 .62rem; }
.menuboard .sectionhead { margin: .1rem 0 .15rem; font-size: clamp(.72rem, 2.1vmin, .95rem); }
.menuboard .boardempty { font-size: .78rem; }
/* the same box as the stall's, in the menu's smaller row: five rows tall, ten
   before it scrolls. This used to be a shorter window with the list cut off at
   the fifth row, which is a board that hides whoever is sixth and says nothing
   about having done it. */
.menuboard .runlist { --rowh: 1.6rem; --rowgap: .26rem; }

/* THE BOX HOLDS FIVE, GROWS TO TEN, AND SCROLLS PAST THAT. It is the pane that
   is bounded and not the list: every row there is gets drawn, so nobody is
   quietly off the end of a board, and what gives is how much of it fits on the
   screen at once.

   A WHOLE NUMBER OF ROWS, which is why the row height is a named length and the
   pane's is built out of it: a row sliced through the middle by the pane's own
   edge reads as a broken row rather than as a list with more below it. The row
   takes its height from the same variable, so the two cannot drift apart.

   AND A COUNTED NUMBER OF ROWS, NOT A HEIGHT IN vh. A cap written as 72vh is a
   cap that lands in the middle of a row on most windows, and a row sliced by
   the pane's edge reads as a broken row rather than as a list with more below
   it. So the bound is a COUNT -- --rows -- and the height is built from it, so
   it is whole rows at every window size. Short windows get shorter rows, and,
   where even those do not fit, a smaller count (see the media queries). */
.runlist {
  --rows: 10;
  --rowh: 2rem;
  --rowgap: .3rem;
  display: flex; flex-direction: column;
  gap: var(--rowgap);
  min-height: calc(5 * var(--rowh) + 4 * var(--rowgap));
  max-height: calc(var(--rows) * (var(--rowh) + var(--rowgap)) - var(--rowgap));
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding-right: .15rem;
}
.runlist .row { height: var(--rowh); box-sizing: border-box; flex: none; }

/* The break between the city's top thirty and your own run, when yours did not
   reach it. It is a mark and not a row: no pill, no border, no background, so
   nothing about it looks like an entry on the board. Dropping the flex gap
   above it is what makes it read as a break in the list rather than as one
   more thing in it. */
.runlist .rowsep {
  flex: none; margin-top: calc(var(--rowgap) * -.45);
  text-align: center; font-family: var(--cond);
  text-transform: uppercase; letter-spacing: .12em;
  font-size: .62rem; line-height: 1; color: var(--brown);
  opacity: .75;
}

/* the one line that says the whole city is gone, on the results screen */
.clearrow {
  margin: .1rem 0 -.2rem; text-align: center; max-width: 34em;
  font-size: clamp(.82rem, 2.4vmin, 1rem); color: var(--brown-dp);
  background: linear-gradient(var(--cream-lit), var(--yellow));
  border: .16em solid var(--brown); border-radius: 999px;
  padding: .45em 1.1em .5em;
  box-shadow: var(--shadow-soft);
}
.clearrow[hidden] { display: none; }
.clearrow b { font-family: var(--cond); text-transform: uppercase; letter-spacing: .1em; }

/* ==================================================== very short viewports */
@media (max-height: 430px) {
  /* the two lengths the screen's own padding is built from, so the band's
     negative margin still cancels the top one exactly and the room the
     platform's sheet takes is still kept at the bottom */
  .screen { --sy: .6rem; --syb: calc(.8rem + var(--room-bottom, 0px)); }
  .startbody, .overbody { gap: .5rem; margin-top: .5rem; }
  .pair { gap: .5rem; }
  .ribbon.hero { font-size: clamp(1.2rem, 5.4vmin, 2rem); }
  /* the mark is a drawing and not a line of type, and landscape is the shape
     this page is judged in first, so it keeps its size on a short window */
  .ribbon.hero.brandplate { font-size: clamp(1.7rem, 7vmin, 2.8rem); }
  /* the picker still wants to be the biggest thing on the screen, so the space
     comes out of the padding and the gaps rather than out of the cards. The
     band above it is the header now, so there is no margin to take off it:
     the picker is centred in what the band leaves. */
  .cardrow { gap: .7rem; margin-top: .9rem; }
  .card { padding: 1.35rem 1rem .85rem; }
  .card .cardblurb { font-size: .85rem; }
  .card .cardpick { margin-top: .2rem; }
  /* on a short landscape window the menu board takes the least of it: the rows
     are still rows, but they are the first thing to give up space */
  .menuboard { padding: .55rem .7rem .6rem; }
  .menuboard .row { font-size: .72rem; padding: 0 .55rem; }
  .menuboard .sectionhead { margin: 0 0 .08rem; }
  /* THE ROWS GET SHORTER, NOT FEWER. The board is the first thing here to give
     up space, and it gives up the height of its rows rather than the number of
     them: ten short rows still fit ten names, and the pane's own arithmetic
     goes on holding at five and at ten. Six-and-a-bit comfortable rows is all
     this window has, and a board that stopped at five said nothing about the
     sixth person on it. */
  .menuboard .runlist { --rowh: 1.35rem; --rowgap: .2rem; }
  /* the stall's board is the last thing on a screen that already has a title,
     four chips, three buttons and a heading above it, so on a window this short
     it is seven whole rows and the screen itself scrolls to the rest */
  .board .runlist { --rows: 7; }
  #start { --syb: calc(.5rem + var(--room-bottom, 0px)); }
}

/* A window that is not short at all, whose room is: the platform's sheet takes
   the bottom half of the frame, so a phone held upright is left with 391px of
   room on a 800px frame. That is the short-window problem arriving from the
   other side, and the compact layout above is what fits it. The class is put on
   <html> by measureRoom in game.js, and only while the sheet is actually there,
   so a page opened on its own is laid out exactly as it was before. */
html.room-short .screen { --sy: .5rem; --syb: calc(.6rem + var(--room-bottom, 0px)); }
html.room-short .startbody { gap: .45rem; margin-top: .45rem; }
html.room-short .startbody .hint { font-size: .74rem; line-height: 1.3; }
html.room-short .ribbon.hero { font-size: clamp(1.2rem, 5.4vmin, 2rem); }
html.room-short .menuboard { padding: .5rem .7rem .55rem; margin-top: .1rem; }
html.room-short .menuboard .row { font-size: .72rem; padding: 0 .55rem; }
html.room-short .menuboard .sectionhead { margin: 0 0 .08rem; }
/* Short rows again, and whole ones: the arithmetic that keeps a row from being
   sliced by the pane's edge is the same arithmetic, whatever the height is. */
html.room-short .menuboard .runlist { --rowh: 1.35rem; --rowgap: .2rem; }
html.room-short .board .runlist { --rowh: 1.7rem; --rowgap: .24rem; }
/* the finish screen keeps its own controls first: the board is under a button
   there, and a button under a list is a button nobody finds */
html.room-short .overbody { gap: .45rem; }

/* A window that is wide and short cannot stack a start panel and a board: the
   board lands under the fold and the menu becomes a scroll for the one thing
   the person asked to find on it. Side by side both are on the screen at once.

   THE PAIR IS A BOX OF ITS OWN AND THE BAND IS NOT IN IT. The band is the
   screen's header and it is pinned to the top of the screen whatever the pair
   under it is doing; the pair is what goes side by side. Keeping them in one
   flex line was what put the band half way down the frame on every laptop,
   because centring the line centred the header with it. */
@media (max-height: 470px) and (min-width: 560px) {
  .pair {
    flex-direction: row; flex-wrap: wrap;
    align-items: flex-start;
    justify-content: center; gap: .4rem .95rem;
  }
  /* Both columns are narrowed, not just the board. The start panel is the
     wider of the two because it is the one with a sentence to read; the pair
     still has to come in under the line width or they wrap and it is the old
     stacked menu again. Measured at 740x360: 336 and 303 against 711 of line,
     no scroll, and the board's bottom edge 53px clear of the window. */
  /* The columns are measured against the room the screens actually have, not
     against the window: when the platform's sheet is down the right of a wide
     window, the menu has less than the window to put two columns in, and a
     width taken from the window puts the board back under the sheet. */
  /* side by side the two cards start on the same line, so the menu's own top
     margin goes: it is the stack that needs that gap, not the row */
  .pair > .startbody { margin-top: 0; width: min(47%, 21rem); }
  .pair > .menuboard { width: min(41%, 20rem); }
  /* the board column is narrow here whatever the window's height, so it takes
     the compact rows by width and not by height: a row left at the roomy size
     in a 230px column wraps its score onto a second line and the board reads
     as ragged. */
  .pair > .menuboard { padding: .6rem .7rem .7rem; }
  .pair > .menuboard .row { font-size: .72rem; padding: 0 .55rem; }
  .pair > .menuboard .sectionhead { margin: 0 0 .1rem; }
  .pair > .menuboard .runlist { --rowh: 1.4rem; --rowgap: .2rem; }
}

/* A laptop window is wide enough for the start panel and the board to stand
   beside each other, and stacking them puts the board under the fold on the
   very screen where there is room for both. The band keeps its own line.
   Measured at 1280x800: the pair comes in at 854 against 1280 of line and the
   menu does not scroll. */
@media (min-width: 900px) {
  .pair {
    flex-direction: row; flex-wrap: wrap;
    align-items: flex-start;
    justify-content: center; gap: .9rem 1.4rem;
  }
  .pair > .startbody { margin-top: 0; width: min(46vw, 28rem); }
  .pair > .menuboard { width: min(40vw, 25rem); }
}

/* a mouse or trackpad pointer can hover; touch cannot, so the hint shortens */
@media (pointer: coarse) {
  .actionbtn .actkey { opacity: .55; }
}

/* On a phone the two upgrade cards stack, and each one takes the width it
   needs for its blurb to read as a sentence rather than a column of words.
   This lives here, after the card rules, so it is the one that wins. */
@media (max-width: 640px) {
  .cardrow { flex-direction: column; align-items: center; gap: 1rem; margin-top: 1.7rem; }
  .card { width: min(90vw, 30rem); padding: 1.9rem 1.35rem 1.15rem; }
  .card .cardblurb { font-size: 1rem; }
}
@media (max-width: 400px) {
  .card { width: 93vw; padding: 1.7rem 1.1rem 1rem; }
  .card .cardname { font-size: 1.55rem; }
}
