/* ── Pee in the Bowl — UI shell ─────────────────────────────────────── */

:root{
  --ink:      #0e1116;
  --paper:    #f2f6f8;
  --tile:     #cfe0e6;
  --pee:      #e9d97a;
  --pee-hot:  #f6e98f;
  --water:    #7fc6d8;
  --bad:      #e2705a;
  --good:     #86d3a0;
  --panel:    rgba(12,16,22,.62);
  --stroke:   rgba(226,240,246,.16);
  /* ── Type ────────────────────────────────────────────────────────────
   * Nothing is fetched: three.min.js and the flush are both vendored and the
   * game has to work off a file:// URL with the wifi off, so a Google Fonts
   * <link> would be a blank first frame at best. These are stacks of faces
   * that actually ship with the OS, best-first.
   *
   * --display is the HUD voice: a condensed technical grotesque (Bahnschrift is
   * Windows' DIN, and DIN is what gauges and instrument panels are set in).
   * The narrow figures matter twice over — the rail is 222px and every label on
   * it is uppercase with .16em of tracking, which is where a normal-width face
   * runs out of room and starts wrapping.
   *
   * --font stays a plain UI grotesque for the running prose (taglines,
   * verdicts, the key list) — condensed is for glancing at, not for reading.
   */
  --display: "Bahnschrift", "Segoe UI Variable Display", "SF Compact Display",
             "Avenir Next Condensed", "Roboto Condensed", "Liberation Sans Narrow",
             "DejaVu Sans Condensed", "Arial Narrow", var(--font);
  --font: "Segoe UI Variable Text", ui-sans-serif, Inter, "Segoe UI",
          system-ui, -apple-system, "Helvetica Neue", sans-serif;
  --mono: ui-monospace, "Cascadia Mono", "SF Mono", "JetBrains Mono",
          Menlo, Consolas, monospace;

  /* ── Making unboxed text survive the room ────────────────────────────
   *
   * The HUD has no panels behind it any more, which means every glyph in it
   * has to hold up over BOTH backgrounds the game has: a bathroom lit white,
   * where the top of the frame is bright tile and the middle is bright
   * porcelain, and the same room under a blacklight with the lamps off. Those
   * are opposite problems and a single colour cannot solve them — light text
   * disappears into the first, dark text into the second.
   *
   * So the text is light and it carries its own dark edge. Eight one-pixel
   * offsets is a genuine OUTLINE rather than a drop shadow: it closes all the
   * way round the glyph, so what sits between the letterforms and whatever is
   * behind them is always the same near-black regardless of what that is. Two
   * soft blurs go under it to lift the whole word off a busy surface.
   *
   * Not -webkit-text-stroke, which is the obvious way to do this: without
   * `paint-order` the stroke paints OVER the fill and eats half the weight of
   * a 10px condensed face, and paint-order on HTML text is not something this
   * can rely on. Shadows paint under the fill by definition.
   *
   * It is one variable so that the readouts with a coloured glow of their own
   * can compose the two — glow first, outline after, so the outline stays the
   * innermost thing and the glow spreads outside it.
   */
  --edge: rgba(2,4,7,.94);
  --hud-out:
     1px 0 0 var(--edge),  -1px 0 0 var(--edge),
     0 1px 0 var(--edge),   0 -1px 0 var(--edge),
     1px 1px 0 var(--edge), -1px 1px 0 var(--edge),
     1px -1px 0 var(--edge), -1px -1px 0 var(--edge),
     0 2px 5px rgba(0,0,0,.80), 0 0 14px rgba(0,0,0,.55);
  /* The same thing at twice the offset, for the one readout set big enough
     that a one-pixel edge is a hairline rather than an outline. */
  --hud-out-lg:
     2px 0 0 var(--edge),  -2px 0 0 var(--edge),
     0 2px 0 var(--edge),   0 -2px 0 var(--edge),
     2px 2px 0 var(--edge), -2px 2px 0 var(--edge),
     2px -2px 0 var(--edge), -2px -2px 0 var(--edge),
     0 2px 6px rgba(0,0,0,.85), 0 0 16px rgba(0,0,0,.6);

  /* The three weights the HUD speaks in. Every one of them was between .30 and
   * .55 alpha when the readouts still had a blurred panel under them to sit
   * against; with the panels gone that is a grey on white tile. Raised until
   * the DIMMEST of them is still legible over lit porcelain, and the hierarchy
   * is now carried by the steps between them rather than by fading the bottom
   * one out until it is barely there. Raised again since: the outline below is
   * what buys the contrast, so the fill can sit near-white and let the dark
   * edge do the separating instead of splitting the difference with it. */
  --hud-dim: rgba(255,255,255,.96);   /* captions, units, the small print */
  --hud-mid: rgba(255,255,255,1);   /* values that are read, not scanned */
  --hud-hi:  #ffffff;                 /* the ones that decide something */
}

*{ box-sizing:border-box; }
html,body{ height:100%; }
body{
  margin:0; background:#05070a; color:var(--paper);
  font-family:var(--font); overflow:hidden;
  -webkit-user-select:none; user-select:none;
  -webkit-font-smoothing:antialiased;
}

#stage{ position:fixed; inset:0; }
/* The system cursor is hidden ONLY while the pointer is genuinely captured.
   Hiding it unconditionally was a trap: whenever the lock dropped — Escape,
   lost focus, or Chrome declining to give it back — the player was left with an
   invisible cursor they could neither see nor find, and it would wander out of
   the window taking the aim with it. */
#view{ display:block; width:100%; height:100%; cursor:default; }
body.locked #view{ cursor:none; }

/* Shown while a session is live but the pointer is not captured. */
#lockhint{
  position:fixed; left:50%; bottom:11%; transform:translateX(-50%);
  padding:9px 16px; border-radius:10px; z-index:12;
  background:rgba(12,16,22,.78); border:1px solid var(--stroke);
  backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  font-family:var(--display);
  font-size:11.5px; letter-spacing:.10em; text-transform:uppercase; font-weight:700;
  color:var(--pee-hot); pointer-events:none; opacity:0; transition:opacity .25s ease;
}
body.playing:not(.locked) #lockhint{ opacity:1; }

/* soft lens vignette + faint chromatic warmth, pure CSS */
#vignette{
  position:fixed; inset:0; pointer-events:none;
  background:
    radial-gradient(120% 95% at 50% 45%, transparent 42%, rgba(3,6,10,.30) 78%, rgba(2,4,7,.68) 100%),
    radial-gradient(60% 45% at 50% 18%, rgba(255,246,214,.055), transparent 70%);
  mix-blend-mode:normal;
  transition:background 1.4s ease;
}
/* lights out: the warm ceiling bloom goes cold moonlight blue and the falloff
   clamps down hard, so the only bright thing left on screen is the glowing mess */
body.night #vignette{
  background:
    radial-gradient(120% 95% at 50% 45%, transparent 30%, rgba(2,6,14,.50) 70%, rgba(1,3,8,.90) 100%),
    radial-gradient(70% 50% at 50% 12%, rgba(84,138,230,.085), transparent 74%);
}

/* ── The red ───────────────────────────────────────────────────────────
 *
 * Comes up around the edges of the frame whenever damage is done, and it is
 * deliberately a VIGNETTE rather than a full-screen wash: the middle of the
 * screen is where the stream and the bowl are, and the one moment the player
 * must not lose sight of them is the moment they are hitting the wrong thing.
 *
 * No transition. The opacity is written per frame by game.js against its own
 * decay, and a CSS ease on top of that would smear the attack — a flash that
 * fades IN is a light coming on, not an impact.
 */
#dmgflash{
  position:fixed; inset:0; pointer-events:none; opacity:0;
  background:
    radial-gradient(120% 90% at 50% 50%, transparent 38%, rgba(226,60,48,.42) 82%, rgba(255,70,52,.72) 100%);
  mix-blend-mode:screen;
}

/* ── HUD ───────────────────────────────────────────────────────────── */
#hud{ position:fixed; inset:0; pointer-events:none; transition:opacity .45s ease; }
/* beats the generic .hidden{display:none} so the HUD can fade instead of pop */
#hud.hidden{ display:block; opacity:0; }
#hud.hidden #controls{ pointer-events:none; }

/* Every uppercase, tracked-out caption in the interface goes through here or
   through .ctl label — one place to change the HUD's voice. */
.label{
  font-family:var(--display);
  font-size:10px; letter-spacing:.18em; text-transform:uppercase;
  color:var(--hud-dim); font-weight:600;
}
.value{ font-family:var(--mono); font-variant-numeric:tabular-nums; }

/* The outline, on everything in both rails — see --hud-out. Set once here and
   inherited, so a readout only names a text-shadow of its own when it wants a
   coloured glow, and then composes it with this rather than replacing it. */
#topbar, #botrail{ text-shadow:var(--hud-out); }

/* ── The strip ─────────────────────────────────────────────────────────
 *
 * Across the top, because the top is the only band of the frame with nothing
 * in it: the toilet stands in the middle and low, and the reveal at the end of
 * a night wants the floor and the skirting clear as well.
 *
 * Three cells and the middle one is the score. The scrim is on the strip
 * rather than behind each readout — one gradient that fades out before it
 * reaches anything the player is aiming at, instead of three rectangles that
 * announce themselves as interface.
 *
 * It is a floor under the contrast, not the contrast itself: the outline on
 * the glyphs is what actually makes them readable, and the scrim's job is only
 * to stop the brightest part of a lit bathroom — the tile at the top of the
 * frame — sitting immediately behind a row of small type. Which is why it
 * holds most of its weight through the band the text occupies and then goes
 * to nothing well before it reaches the bowl. */
#topbar{
  position:absolute; top:0; left:0; right:0;
  display:grid; grid-template-columns:1fr auto 1fr; align-items:start;
  gap:18px; padding:12px clamp(14px, 2.4vw, 30px) 30px;
  background:linear-gradient(180deg,
    rgba(4,7,11,.82) 0%, rgba(4,7,11,.66) 42%, rgba(4,7,11,.34) 72%, transparent 100%);
}

/* ── Which night this is ───────────────────────────────────────────── */
#levelchip{ justify-self:start; }
.lvline{ display:flex; align-items:baseline; gap:7px; }
.nno{
  font-family:var(--display); font-weight:700; font-size:13px;
  letter-spacing:.20em; color:var(--hud-dim); text-transform:uppercase;
}
.nno b{
  font-family:var(--mono); font-size:21px; font-weight:700; letter-spacing:0;
  color:var(--pee-hot); margin-left:1px;
  text-shadow:0 0 16px rgba(246,233,143,.30), var(--hud-out-lg);
}
.nof{
  font-family:var(--display); font-size:9.5px; font-weight:600; letter-spacing:.16em;
  color:var(--hud-dim);
}
.nof b{ font-family:var(--mono); color:var(--hud-mid); font-weight:600; }
#levelName{
  margin-top:1px; max-width:26ch;
  font-family:var(--display); font-size:11px; letter-spacing:.10em;
  text-transform:uppercase; color:var(--hud-mid);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}

/* ── The score ─────────────────────────────────────────────────────────
 * Middle of the strip, and the only thing in the interface allowed to be big.
 * It is the number the night is decided on; everything else is commentary. */
#dmgmeter{ justify-self:center; width:min(46vw, 340px); text-align:center; }
#dmgmeter .head{
  display:flex; align-items:baseline; justify-content:center; gap:9px;
  margin-bottom:5px;
}
#dmgmeter .head .label{ color:var(--hud-dim); }
/* Glow FIRST and the outline after it, so the halo spreads outside a hard dark
   edge instead of being sandwiched between the fill and the room. Yellow on
   white porcelain is the worst pairing in the game and this is the number that
   must never be unreadable. */
#stray{
  font-size:31px; line-height:.9; font-weight:700; letter-spacing:-.02em;
  color:var(--pee-hot);
  text-shadow:0 0 22px rgba(246,233,143,.40), var(--hud-out-lg);
  transition:color .25s ease, text-shadow .25s ease;
}
/* The big readout goes red once the budget is gone. There is nothing to be
   done about it by then, which is the point — the night is lost and the rest
   of it is watching it happen. */
#stray.over{ color:#ff8168; text-shadow:0 0 24px rgba(226,112,90,.6), var(--hud-out-lg); }
#dmgmeter .cap{
  font-family:var(--display); font-size:9.5px; font-weight:600; letter-spacing:.16em;
  color:var(--hud-dim); text-transform:uppercase;
}
#dmgmeter .cap b{ font-family:var(--mono); color:var(--hud-mid); font-weight:600; }

/* ── Meters ────────────────────────────────────────────────────────────
 * SEGMENTED, and that is the whole of what makes these read as an arcade
 * cabinet rather than as a settings page. A smooth fill is a value being
 * reported; a row of cells going out one at a time is a countdown, and the eye
 * reads a count of lit cells faster than it reads a length.
 *
 * The segmentation is a repeating gradient painted OVER the track and its
 * fill, so nothing has to be built out of elements and the JS still just sets
 * a width.
 *
 * THE EMPTY TRACK IS DARK, not a white wash. At 9% white it was a meter you
 * could only find in a dark room: over lit tile the unfilled part of the bar
 * was the same value as the wall behind it, so a nearly-empty meter had no
 * visible extent and there was nothing for the fill to be read against. A dark
 * trough with a light hairline round it reads as a channel on both — the same
 * reason a real gauge is a hole in the panel rather than a paler patch of it. */
.bar{
  position:relative; height:7px; border-radius:2px;
  background:rgba(4,7,11,.66);
  box-shadow:inset 0 0 0 1px rgba(232,244,250,.30), 0 1px 3px rgba(0,0,0,.55);
  overflow:hidden; margin-bottom:7px;
}
.bar > div{ height:100%; width:0%; border-radius:1px; transition:width .18s ease, background-color .3s ease; }
.bar::after{
  content:''; position:absolute; inset:0; pointer-events:none; z-index:1;
  background:repeating-linear-gradient(90deg,
    transparent 0 5px, rgba(5,8,12,.92) 5px 7px);
}
/* Spent budget, not progress: it starts empty and a full one is a failed
   night, so it warms toward the bad colour as it goes rather than staying the
   same cheerful yellow all the way to the end. */
#straybar{ background:linear-gradient(90deg,var(--pee),var(--pee-hot)); box-shadow:0 0 12px rgba(233,217,122,.30); }
#straybar.warn{ background:linear-gradient(90deg,var(--pee-hot),#e2a05a); box-shadow:0 0 12px rgba(226,160,90,.35); }
#straybar.over{ background:linear-gradient(90deg,#e2a05a,var(--bad)); box-shadow:0 0 14px rgba(226,112,90,.45); }

/* ── The borderline ────────────────────────────────────────────────────
 *
 * The track is 1.5x tonight's limit and this is the limit itself, drawn ON the
 * bar rather than implied by it being full. That is the difference between a
 * meter that answers "have you failed" and one that answers "how close are
 * you, and if you have, by how much" — the second question is the one that is
 * still worth asking with two seconds of bladder left.
 *
 * Left is set from JS (OVER_SCALE in game.js) so the mark and the fill can
 * never disagree about where the line is. Overflows the 5px track on purpose:
 * a notch confined to the bar's height is invisible next to the fill.
 */
/* The track clips its fill so the rounded ends stay rounded; the notch has to
   stand proud of it, so this one bar opts out. The fill carries the same
   border-radius itself, so nothing about it changes. */
#dmgmeter .bar{ overflow:visible; }
/* Above the segment overlay, which is a later sibling and would otherwise
   paint the pass mark out at every second segment. */
#straynotch{
  position:absolute; top:-4px; bottom:-4px; width:2px; left:66.667%;
  margin-left:-1px; border-radius:2px; z-index:2;
  background:var(--paper); opacity:.9; pointer-events:none;
  box-shadow:0 0 0 1px rgba(0,0,0,.55), 0 0 8px rgba(255,255,255,.35);
}

/* The room's condition in words, under the meter.
 *
 * The number is exact and the bar is spatial; this is the one that is actually
 * read at a glance mid-stream, when there is no attention spare for either. It
 * tracks the same three colour states as the fill so the whole block changes
 * character together rather than the player having to cross-reference it. */
.straystate{
  font:600 10px/1 var(--display); letter-spacing:.22em; text-transform:uppercase;
  color:var(--hud-mid); margin:0;
  transition:color .3s ease;
}
.straystate.warn{ color:#f0b571; }
.straystate.over{ color:#f08a72; text-shadow:0 0 14px rgba(226,112,90,.6), var(--hud-out); }

/* ── The bottom rail ───────────────────────────────────────────────────
 * Reference, not score. It sits in the dead corner at a fraction of the
 * strip's weight, and the whole cluster lifts on hover — the buttons are in
 * here and they have to be findable, without any of it competing with the
 * bowl while a session is running. */
/* Held back by a little transparency rather than by dimming the colours, so
   the corner stays quiet without any of it dropping below the contrast the
   outline was chosen to guarantee. .82 is as far down as it can go and still
   be read over lit tile at a glance. */
#botrail{
  position:absolute; left:clamp(14px, 2.4vw, 30px); bottom:16px;
  display:flex; flex-direction:column; align-items:flex-start; gap:9px;
  opacity:.82; transition:opacity .25s ease;
}
#botrail:hover{ opacity:1; }

/* The live running order of what is getting hit.
 *
 * Dim by default — it is reference, not the score — with the worst offender
 * brought up to full so the eye lands on the name that matters without having
 * to read three rows and compare numbers. */
.zones{ display:flex; flex-direction:column; gap:1px; }
.zrow{ display:flex; align-items:baseline; gap:8px; min-width:132px; }
.zrow .label{ font-size:10px; letter-spacing:.10em; }
.zrow .value.sm{
  margin-left:auto; font-size:11px; color:var(--hud-mid);
}
/* Lifted off --bad, which is a fill colour rather than a text one: at 11px on
   white porcelain the original red read as brown. */
.zrow.worst .label, .zrow.worst .value.sm{ color:#f08a72; }
/* A slot with nothing in it yet still has to hold its height, or the rail
   jumps every time a new surface gets hit for the first time. */
.zrow.empty{ opacity:.40; }

/* Trivia, on one line. Accuracy stopped deciding anything the day the weighted
   damage score replaced it, and it has been sized accordingly ever since. */
#stats{ display:flex; gap:12px; align-items:baseline; }
.stat{ display:flex; align-items:baseline; gap:5px; }
.stat .label{ font-size:9px; letter-spacing:.14em; color:var(--hud-dim); }
.stat .value.sm{ font-size:11px; color:var(--hud-mid); }
.stat.good .value.sm{ color:#9ee0b4; }

/* ── The clock ─────────────────────────────────────────────────────────
 * Right-hand end of the strip. Segmented like the damage meter — see .bar —
 * so what the player reads is cells going out rather than a bar shrinking. */
#bladderwrap{ justify-self:end; width:min(24vw, 190px); text-align:right; }
#bladderwrap .head{ margin-bottom:5px; }
#bladderwrap .label{ color:var(--hud-dim); }
.bar.tall{ height:11px; margin:0; }
#bladderbar{
  width:100%; background:linear-gradient(90deg,#f2e79a,#e9d97a);
  box-shadow:0 0 14px rgba(233,217,122,.32);
}
/* The last quarter of the tank is where the flow curve falls away, the stoop
   engages and the aim starts going — it gets a colour of its own and a pulse,
   because it is the one state change in the session the player has to act on. */
#bladderbar.low{
  background:linear-gradient(90deg,#e2a05a,#e2705a);
  box-shadow:0 0 16px rgba(226,112,90,.5);
  animation:tanklow .7s steps(1,end) infinite;
}
@keyframes tanklow{ 0%,60%{ opacity:1; } 61%,100%{ opacity:.55; } }

/* Control panel — the bottom of the rail. The frosted card it used to live in
   is gone with the rest of them; what is left is the row of buttons and the
   (hidden) tuning sliders, which need no surface of their own. */
/* An ID selector outranks a class one, so `#controls{display:flex}` was quietly
   beating `.hidden{display:none}` and the sliders stayed on screen through the
   whole blacklight reveal — and then through the ending — despite the JS having
   correctly asked for them to go. Same specificity, so this wins on order. */
#controls.hidden{ display:none; }
#controls{
  display:flex; flex-direction:column; align-items:stretch; gap:11px;
  pointer-events:auto;
}
.ctl{ width:100%; min-width:186px; }
.ctl.narrow{ display:flex; gap:6px; padding-bottom:0; }
.ctl.narrow button{ flex:1; }

/* Lean is a continuous axis, not a toggle, so it reads as a slider like the
   other analogue controls. The label lights up whenever the player is off the
   standing pose, however the lean was driven — key, drag or the slider itself. */
body.leaning #leanVal{ text-shadow:0 0 12px rgba(233,217,122,.65); }

.ctl label{
  display:block; font-family:var(--display);
  font-size:10.5px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--hud-dim); font-weight:600; margin-bottom:7px;
}
.ctl label b{ color:var(--pee-hot); font-family:var(--mono); letter-spacing:0; text-transform:none; font-size:11px; }
.hint{ font-size:9.5px; letter-spacing:.08em; color:var(--hud-dim); opacity:.75; margin-top:5px; }

input[type=range]{
  -webkit-appearance:none; appearance:none; width:100%; height:4px; border-radius:99px;
  background:rgba(255,255,255,.14); outline:none; cursor:pointer;
}
input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none; width:13px; height:13px; border-radius:50%;
  background:var(--pee-hot); border:2px solid #10151b; cursor:grab;
  box-shadow:0 0 0 1px rgba(246,233,143,.35), 0 0 12px rgba(246,233,143,.45);
}
input[type=range]::-moz-range-thumb{
  width:11px; height:11px; border-radius:50%; background:var(--pee-hot);
  border:2px solid #10151b; cursor:grab;
}

button{
  font-family:var(--display); font-size:11px; font-weight:700; letter-spacing:.12em;
  color:var(--paper); background:rgba(255,255,255,.07);
  border:1px solid var(--stroke); border-radius:9px; padding:9px 12px; cursor:pointer;
  transition:background .18s ease, transform .12s ease, border-color .18s ease;
}
/* The rail's buttons are smaller and quieter than the overlay's — they are
   escape hatches with keys of their own, not things to be pressed.
   Filled DARK rather than with a wash of white: a 5%-white button over lit
   floor tile is a button with no edges, and these are the one part of the HUD
   that has to look like something you can hit. */
#controls button{
  font-size:9.5px; letter-spacing:.16em; padding:6px 9px; border-radius:7px;
  background:rgba(4,7,11,.62); color:var(--hud-mid);
  border-color:rgba(232,244,250,.28); text-shadow:none;
}
#controls button:hover{ background:rgba(16,24,34,.86); color:var(--hud-hi); }
button:hover{ background:rgba(255,255,255,.14); border-color:rgba(226,240,246,.34); }
button:active{ transform:translateY(1px); }

/* Was at 50%/52% — dead centre, directly over the bowl, for a second and a half
   every time anything happened. Moved to the top strip, which is the only band
   of the frame with nothing in it: the level chip is top-left and the bladder
   meter is top-right, and this sits in the gap between them.
 *
 * It drifts DOWN into place now rather than up. Coming from above is what makes
 * a thing at the top of the screen read as arriving rather than as leaving. */
/* …and then moved again, down under the strip, once the strip took the top of
   the frame. It still arrives from above; it just now clears the score rather
   than landing on top of it. */
#toast{
  position:absolute; left:50%; top:92px; transform:translate(-50%,-12px);
  font-family:var(--display);
  font-size:16px; font-weight:700; letter-spacing:.05em;
  /* Outside both rails, so it needs the outline named explicitly — and it lands
     over the middle of the room, which is the brightest thing on screen. */
  color:var(--pee-hot); text-shadow:0 2px 18px rgba(0,0,0,.9), var(--hud-out);
  opacity:0; pointer-events:none; transition:opacity .3s ease, transform .3s ease;
}
#toast.show{ opacity:1; transform:translate(-50%,0); }

/* ── Crosshair — THE OLD SIGHT, off unless C is pressed ────────────────
 *
 * Everything below still works and is still driven by updateReticleDom, but it
 * is no longer what the player sees. The live sight is a ring drawn in the room,
 * lying flat on whatever the stream is about to hit — see P.makeSightMaterial in
 * js/shaders.js.
 *
 * It lost to that ring on the one point this comment was already worrying
 * about. A mark positioned at the projected landing point is drawn IN FRONT OF
 * the landing, so the moment liquid arrives on porcelain is the moment something
 * is sitting on top of it. The gap in the middle was the mitigation and it was
 * not enough: the stream comes down through the gap and the splash spreads out
 * through the ticks. A ring on the surface has the impact happening *inside* it,
 * on ground it is drawn around rather than over, and it foreshortens with the
 * surface into the bargain.
 *
 * Kept, in the markup and here, so the two can be put side by side rather than
 * argued about. What follows is unchanged.
 *
 * A readout, not a sight. It is drawn at the traced landing point of the
 * stream — the centroid of the two rods, once a night forks — so it moves
 * whenever the lean, the pressure, the surge or the hand's drift moves where
 * the liquid is going to come down. Four ticks around a gap rather than a
 * solid cross, because the centre is precisely where the player needs to see
 * the water and a mark that covers its own target is worse than no mark. Thin
 * and half-transparent: findable without competing with what it is about. */
/* Anchored at the origin, positioned ENTIRELY by the transform that game.js
   writes each frame. It used to sit at left:50%/top:50% with a centring
   transform, and then the per-frame transform overwrote that centring — so the
   reticle rendered half a viewport down and right of where it claimed to be,
   which is to say off the screen. With nothing visible tracking the drift, the
   stream looked like it was simply following the mouse. */
/* ── …and the bladder gauge ────────────────────────────────────────────
 *
 * The four ticks are also four little progress bars. `--fill` is how much is
 * left in the tank and `--pc` is the colour to draw it in, both written by
 * updateReticle in game.js, which explains why the gauge lives out here on the
 * sight instead of only in the corner of the frame.
 *
 * Each tick fills from the END NEAREST THE CENTRE outward, so the mark
 * visibly closes in on the aim point as the session runs out — the shape of
 * the reticle is itself the readout, before any colour is read off it.
 *
 * TWO COLOURS, not one colour that ramps. The sight is red — all of it, at the
 * top of the tank — and it DRAINS INTO WHITE from the outside in as the session
 * runs down, so the readout is a boundary travelling along a tick rather than a
 * hue the player has to have memorised a scale for. Red against white is the
 * one pairing that survives both rooms this game has, and the moving edge
 * between them is legible peripherally in a way an amber-vs-yellow midpoint
 * never was. The drained part stays fully drawn rather than fading out, so the
 * crosshair never stops being a crosshair — it just changes colour. */
#crosshair{
  position:fixed; left:0; top:0; width:34px; height:34px;
  pointer-events:none; opacity:.85; will-change:transform;
  /* --fill is how much is left, written per frame by game.js. --pc is the
     remaining tank, --pd is what has already gone; both are constant, which is
     why the JS only has a number to write now. */
  --fill:1; --pc:#ff4234; --pd:rgba(255,255,255,.92);
}
/* White ticks with a dark halo, rather than a blend mode. `difference` looked
   elegant and vanished completely over the bright porcelain the player spends
   the whole game aiming at — which is the one background it had to survive.
   An explicit outline works on white tile and in a blacked-out room alike. */
/* Taken up to match the outline the rest of the HUD now carries: the ring is
   what draws the sight over bright porcelain, and it is doing that for the
   UNFILLED part of each tick as well, which has no colour of its own left. */
#crosshair i, #crosshair b{
  box-shadow:0 0 0 1px rgba(2,4,7,.80), 0 0 5px rgba(0,0,0,.55);
}
/* Wider and longer than the plain sight was: at 1.5px there was no room for a
   fill to be legible as one. Still a gap in the middle, which is where the
   water the player is aiming at has to stay visible. */
#crosshair i{ position:absolute; background:var(--pd); }
#crosshair i:nth-child(1){
  left:50%; top:0; width:2.5px; height:11px; margin-left:-1.25px;
  background:linear-gradient(to top, var(--pc) 0 calc(var(--fill) * 100%),
                                     var(--pd) calc(var(--fill) * 100%) 100%);
}
#crosshair i:nth-child(2){
  left:50%; bottom:0; width:2.5px; height:11px; margin-left:-1.25px;
  background:linear-gradient(to bottom, var(--pc) 0 calc(var(--fill) * 100%),
                                        var(--pd) calc(var(--fill) * 100%) 100%);
}
#crosshair i:nth-child(3){
  top:50%; left:0; height:2.5px; width:11px; margin-top:-1.25px;
  background:linear-gradient(to left, var(--pc) 0 calc(var(--fill) * 100%),
                                      var(--pd) calc(var(--fill) * 100%) 100%);
}
#crosshair i:nth-child(4){
  top:50%; right:0; height:2.5px; width:11px; margin-top:-1.25px;
  background:linear-gradient(to right, var(--pc) 0 calc(var(--fill) * 100%),
                                       var(--pd) calc(var(--fill) * 100%) 100%);
}
/* The centre dot stays red for the whole session. It is the innermost thing on
   the sight, so by the drain's own geometry it would be the last to turn — and
   holding it red outright keeps the mark identifiable as the reticle at the
   moment the four ticks have gone white. */
#crosshair b{
  position:absolute; left:50%; top:50%; width:3px; height:3px;
  margin:-1.5px 0 0 -1.5px; border-radius:50%; background:var(--pc);
}

/* The night badge, the score and the tank are all up in the strip now — see
   #topbar above, which is where their layout and their type live. */

/* ── The ending's overlays ─────────────────────────────────────────────
 *
 * Layered deliberately between the HUD and the result card. The HUD has no
 * z-index and so paints at 0; the card is at 20. Sitting these at 10 is what
 * lets the blackout swallow the scorecard, the bladder meter and the toast
 * while still having the result fade up on top of it afterwards.
 *
 * No transition on either. Both are driven per frame from ending.js and a CSS
 * ease would fight the schedule — the flash in particular has to be gone in
 * about a fifth of a second or it stops reading as an impact and starts
 * reading as a light being switched on. The one exception is the blackout's
 * exit, which is a fade because the card has to arrive out of nothing.
 */
#scareflash, #scareblack{
  position:fixed; inset:0; z-index:10; pointer-events:none; opacity:0;
}
#scareflash{ background:rgba(255,250,250,1); mix-blend-mode:screen; }
#scareblack{ background:#000; transition:opacity .9s ease; }

/* ── The title ─────────────────────────────────────────────────────────
 *
 * NO SCRIM, NO PANEL, NO BLUR. That is the whole of what changed here and it
 * is worth being explicit about why, because the frosted card this replaced
 * was not badly made — it was in the way. The bathroom is the game's best
 * asset and the opening screen is the one a player looks at longest, so
 * putting a blurred sheet over the room until the moment they have to aim at
 * it meant the thing they were deciding whether to play was never visible.
 *
 * What holds the type against a lit room instead is the same thing that holds
 * the HUD against it: an outline on the glyphs, plus a gradient that only
 * darkens the band the text is actually sitting in. Nothing covers the bowl.
 *
 * It is also the button. The entire surface takes the click — see bindInput —
 * which is what lets the prompt be a line of text rather than a control.
 */
#title{
  position:fixed; inset:0; z-index:22;
  display:grid; place-items:center; cursor:pointer;
  /* Top and bottom only. The middle of the frame — the bowl — is untouched. */
  background:
    linear-gradient(180deg, rgba(4,7,11,.55) 0%, transparent 34%),
    linear-gradient(0deg,   rgba(4,7,11,.62) 0%, transparent 30%);
  animation:titlein .8s ease both;
}
@keyframes titlein{ from{ opacity:0; } to{ opacity:1; } }

/* THE TITLE ITSELF, and it leaves.
 *
 * A film title card is on screen for about four seconds and then it is not,
 * and the reason the same shape works here is that it hands the frame back:
 * what is left after it goes is the room, one line of prompt, and nothing
 * else. Held at full opacity for three seconds — long enough to be read
 * twice — and then eased out over one and a half.
 *
 * `forwards` matters. Without it the fill snaps back to the first keyframe on
 * the last frame of the animation, which is a title that vanishes and then
 * flashes back on.
 */
.titlewrap{
  text-align:center; padding:0 6vw;
  animation:titlecard 5.4s cubic-bezier(.4,0,.2,1) forwards;
}
@keyframes titlecard{
  0%   { opacity:0; transform:translateY(14px) scale(.985); letter-spacing:.06em; }
  16%  { opacity:1; transform:none; letter-spacing:0; }
  62%  { opacity:1; transform:none; }
  100% { opacity:0; transform:translateY(-10px) scale(1.02); }
}
/* The one thing on the title card that has to survive a lit bathroom behind
   it, so it carries the HUD's outline under its gold gradient fill. */
#title h1{
  font-size:clamp(38px, 9vw, 96px); margin:0;
  filter:drop-shadow(0 2px 18px rgba(0,0,0,.85)) drop-shadow(0 0 42px rgba(0,0,0,.6));
}
#title .tag{
  margin:14px 0 0; font-size:clamp(11px, 1.5vw, 14px);
  color:rgba(226,240,246,.72); letter-spacing:.04em;
  text-shadow:0 2px 12px rgba(0,0,0,.9), var(--hud-out);
}

/* …and the prompt, which arrives as the title is leaving and then stays. It
   is deliberately at the bottom of the frame and not under the title: by the
   time it is readable the title is halfway gone, and two pieces of type
   fading past each other in the same place is a mess. */
#startprompt{
  position:absolute; left:0; right:0; bottom:12%;
  text-align:center; animation:promptin 1.2s 2.6s ease both;
}
@keyframes promptin{ from{ opacity:0; transform:translateY(10px); } to{ opacity:1; transform:none; } }
#startprompt .prompt{
  display:inline-block; font-family:var(--display);
  font-size:13px; font-weight:700; letter-spacing:.28em; text-transform:uppercase;
  color:var(--pee-hot); text-shadow:0 2px 16px rgba(0,0,0,.9), var(--hud-out);
  animation:promptpulse 2.4s ease-in-out infinite;
}
/* Breathing, not blinking. A prompt that goes fully out reads as a fault. */
@keyframes promptpulse{ 0%,100%{ opacity:1; } 50%{ opacity:.48; } }
/* Brighter than the .fine it inherits from, and carrying the HUD's outline.
   That class was written for a line sitting on a dark frosted card; this one
   is over a lit bath mat, where 42% white is invisible. */
#startprompt .fine{
  margin:10px 0 0; font-size:10.5px; color:rgba(240,248,252,.80);
  text-shadow:var(--hud-out);
}
#startprompt a{ color:rgba(246,233,143,.72); text-decoration:underline; cursor:pointer; }
/* Touch devices are told to tap, not to click. Set from JS on <body>. */
body.touch #startprompt .prompt::after{ content:''; }

/* ── The two permanent buttons ─────────────────────────────────────────
 *
 * BOTTOM-right, outside #hud, above everything. See the note in the markup
 * for why they cannot live in the HUD: RESET has to work during the ending,
 * and the ending hides the HUD and then blacks the screen out over it.
 *
 * The corner is the only empty one left. The top strip is the live readout end
 * to end — the night at one end, the score in the middle, the bladder meter at
 * the other — and the bottom LEFT is the diagnostics rail, so a pair of
 * buttons anywhere but here lands on something that is being read mid-stream.
 *
 * Quiet by default and legible on hover: they are escape hatches, not part of
 * the read, and nothing in this corner should compete with the bowl.
 */
#quickbar{
  position:fixed; bottom:16px; right:clamp(12px, 2.4vw, 26px); z-index:30;
  display:flex; gap:7px; opacity:.62; transition:opacity .22s ease;
}
#quickbar:hover{ opacity:1; }
#quickbar button{
  display:flex; align-items:center; gap:6px;
  font-size:12px; letter-spacing:.14em; padding:6px 10px; border-radius:8px;
  background:rgba(4,7,11,.72); color:var(--hud-mid);
  border:1px solid rgba(232,244,250,.24); text-shadow:none; line-height:1;
}
#quickbar button span{ font-size:9px; letter-spacing:.16em; }
#quickbar button:hover{ background:rgba(16,24,34,.92); color:var(--hud-hi); border-color:rgba(232,244,250,.42); }
#resetBtn:hover{ border-color:rgba(226,112,90,.6); color:#ffb9a6; }
/* WHILE THE POINTER IS CAPTURED THESE ARE NOT CLICKABLE, and pretending
   otherwise would be the worst version of this. A locked pointer has no cursor
   and the browser routes every event to the canvas, so the honest thing is for
   the buttons to stop offering a click and start offering the key that does
   the same job — R restarts the night, Escape opens the panel (and drops the
   lock on the way, which is the same keystroke either way). */
body.locked #quickbar{ opacity:.5; }
body.locked #quickbar button{ cursor:default; }
/* On the BUTTON, not on the label span — `attr()` reads the element the
   pseudo belongs to, and the attribute is on the button. It also means the
   key survives the narrow breakpoint below, where the label does not. */
body.locked #quickbar button::after{
  content:attr(data-key); color:var(--pee-hot);
  font-size:9px; letter-spacing:.14em;
}
/* Under 620px the words go and the glyphs stay — two icons is still a legible
   pair, and the strip below them needs the width more than they do. */
@media (max-width:620px){ #quickbar button span{ display:none; } }

/* ── Settings ──────────────────────────────────────────────────────────
 * The one place in the interface that IS allowed a scrim and a blur, because
 * it is a modal the player opened on purpose and nothing is happening behind
 * it that they need to see. */
#settings{
  position:fixed; inset:0; z-index:32; display:grid; place-items:center;
  padding:3vh 16px;
  background:rgba(3,5,8,.72);
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
}
#settings .panel{
  position:relative; text-align:left; max-height:94vh; overflow-y:auto;
  padding:26px 28px 24px;
}
#settings h2{
  margin:0 0 18px; font-family:var(--display);
  font-size:13px; font-weight:700; letter-spacing:.28em; text-transform:uppercase;
  color:var(--pee-hot);
}
.closeX{
  position:absolute; top:14px; right:14px; padding:4px 9px; font-size:12px;
  background:transparent; border-color:transparent; color:rgba(226,240,246,.5);
}
.closeX:hover{ background:rgba(255,255,255,.08); color:var(--paper); }

/* One rule per block rather than a border per row: the groups are already
   separated by a run of white space and a caption, and a hairline between each
   of them turns a short panel into a form. */
.sgroup{ margin:0 0 22px; }
.sgroup:last-child{ margin-bottom:0; }
.srow{ display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:8px; }
.srow label{
  display:block; margin:0; font-family:var(--display);
  font-size:10.5px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--hud-dim); font-weight:600;
}
.srow label b{ color:var(--pee-hot); font-family:var(--mono); letter-spacing:0; font-size:11px; }
#mute{ padding:5px 10px; font-size:13px; line-height:1; }

/* The night picker. A locked night is drawn rather than hidden — the row is
   how the player learns there are five of them and how far along they are,
   and a picker that grows an item every twenty minutes teaches neither. */
#nightpick{ display:flex; gap:6px; }
#nightpick button{
  flex:1; padding:10px 0; font-size:13px; letter-spacing:0;
  font-family:var(--mono); font-weight:700;
}
#nightpick button[disabled]{
  opacity:.28; cursor:not-allowed; background:rgba(255,255,255,.03);
}
#nightpick button[disabled]:hover{ background:rgba(255,255,255,.03); border-color:var(--stroke); }
#nightpick button.now{
  border-color:rgba(246,233,143,.55); color:var(--pee-hot);
  box-shadow:0 0 0 1px rgba(246,233,143,.18), 0 0 18px rgba(246,233,143,.14);
}

#settings #howto{ margin:0; }
#settings .ctl{ margin-bottom:14px; }
#settings .ctl.narrow{ margin-bottom:0; }

/* ── The end of the run ────────────────────────────────────────────────
 *
 * Its own screen rather than a variant of the scorecard, because it is not a
 * verdict on a night — it is the credits. It lands on top of the last frame of
 * whichever finale played, which is why the background is a warm wash rather
 * than a blackout: both endings that can reach here go out through the light.
 */
#winscreen{
  position:fixed; inset:0; z-index:26; display:grid; place-items:center;
  padding:4vh 20px; text-align:center;
  background:
    radial-gradient(75% 60% at 50% 44%, rgba(58,36,18,.42), rgba(3,5,8,.92));
  backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
  animation:titlein 1.1s ease both;
}
.winwrap{ width:min(92vw, 520px); }
#winKicker{
  font-family:var(--display); font-size:10px; font-weight:700;
  letter-spacing:.42em; text-transform:uppercase; color:rgba(226,240,246,.42);
  margin-bottom:10px;
}
#winscreen h1{ font-size:clamp(30px, 6.4vw, 58px); margin:0 0 14px; }
/* The perfect run's title is the one thing in the game that gets the warm
   gradient the ✦ grade uses — it is the same achievement, five nights wide. */
#winscreen.triumph h1{ background:linear-gradient(180deg,#fff2d8,#ffb066); -webkit-background-clip:text; background-clip:text; }
#winSub{ margin:0 0 22px; font-size:13px; line-height:1.6; color:rgba(226,240,246,.66); }
#winStats{
  display:flex; justify-content:center; gap:26px; flex-wrap:wrap;
  margin:0 0 22px; padding:16px 0; border-top:1px solid rgba(255,255,255,.07);
  border-bottom:1px solid rgba(255,255,255,.07);
}
#winStats .ws{ display:flex; flex-direction:column; align-items:center; gap:5px; }
#winStats .ws b{ font-family:var(--mono); font-size:22px; color:var(--paper); font-weight:600; }
#winStats .ws span{
  font-family:var(--display); font-size:9px; letter-spacing:.18em;
  text-transform:uppercase; color:rgba(226,240,246,.45);
}
#winLine{
  margin:0 0 24px; font-size:12.5px; font-style:italic; line-height:1.6;
  color:var(--pee-hot);
}
#winscreen.triumph #winLine{ color:#ffcf9a; }
#winAgain{
  width:min(100%, 300px); padding:14px; font-size:11.5px; letter-spacing:.2em;
  border-radius:12px; border:none; color:#1a1608;
  background:linear-gradient(180deg,var(--pee-hot),#dcc95f);
  box-shadow:0 8px 26px rgba(233,217,122,.22);
}
#winAgain:hover{ background:linear-gradient(180deg,#fff3b0,#e6d370); }

/* ── Overlay ───────────────────────────────────────────────────────── */
/* Anchored to the left rail's side of the frame, never the middle.
 *
 * Nothing in this interface is allowed to sit over the centre of the screen —
 * that is where the bowl is, and the opening card is the screen the player
 * looks at longest, so it was the worst offender: the room they are about to
 * aim at was behind a blurred panel right up until the moment they had to aim
 * at it. Left-anchored, it lines up with the column the scorecard and controls
 * will occupy the second it goes away, so the interface arrives where it lives.
 *
 * The scrim's hotspot moves with it, biased left and down, so the darkest part
 * of the fade is behind the card rather than behind the toilet.
 *
 * Under 900px there is no "beside" left to anchor to, so it falls back to the
 * bottom of the frame — the same place the night card already uses.
 */
#overlay{
  position:fixed; inset:0; display:grid; z-index:20;
  place-items:center start; padding:3vh 0 3vh clamp(16px, 4vw, 64px);
  background:radial-gradient(95% 75% at 26% 52%, rgba(10,16,22,.72), rgba(3,5,8,.94));
  backdrop-filter:blur(7px); -webkit-backdrop-filter:blur(7px);
  transition:opacity .5s ease;
}
/* A card taller than the window has to scroll rather than run off both ends of
   it — the same guarantee the night card already carries. */
#overlay > .panel{ max-height:94vh; overflow-y:auto; }
#overlay.hidden{ opacity:0; pointer-events:none; }
/* …and the card inside it, which asks for its events back — see the rule on
   `#overlay.night .panel`. Without this the invisible scorecard keeps its
   click target for the whole session: the panel is anchored to the bottom of
   the frame, so the bottom fifth of the title screen silently refuses to
   start the game and nothing on screen explains why. */
#overlay.hidden .panel{ pointer-events:none; }
/* The scorecard over the blacklight reveal.
   No scrim, no blur, no full-screen panel: the whole point of the reveal is
   that you can look at the room, so the result shrinks to a card at the bottom
   and everything above it stays clear. */
/* Longhand `padding` rather than `padding-bottom`, or the opening card's left
   inset survives into this state and shunts the bottom card off to the right
   of the frame. */
#overlay.night{
  background:none; backdrop-filter:none; -webkit-backdrop-filter:none;
  place-items:end center; padding:0 0 24px; pointer-events:none;
}
#overlay.night .panel{
  pointer-events:auto; width:min(94vw,420px); padding:14px 18px 14px; border-radius:16px;
  background:linear-gradient(180deg, rgba(7,14,26,.80), rgba(3,7,15,.88));
  backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  box-shadow:0 18px 50px rgba(0,0,0,.5), inset 0 1px 0 rgba(150,190,255,.07);
  text-align:left;
}
/* The end card is the result and a button, full stop. The key list belongs to
   the opening screen only, and this is a guarantee rather than a hope: the JS
   hides it when play starts, and the end state cannot show it even if some
   future path forgets to. */
#overlay.night h1, #overlay.night .tag, #overlay.night .fine,
#overlay.night #howto{ display:none; }

/* The end card, laid out as a grid so the grade can sit beside the whole run
   of counts. Rows, in order: the pass/fail banner across the top, seven count
   lines beside the grade, then the two verdicts and the streak note. Explicit
   columns with auto row placement — the row numbers are not written down
   anywhere, so adding a count line does not mean renumbering everything under
   it, which is exactly what went wrong the first time this grew. */
#overlay.night #resultBlock{
  margin:0 0 10px;
  display:grid; grid-template-columns:auto 1fr; gap:0 18px; align-items:center;
}
/* Two ID selectors outrank `.hidden`, so the block above has to say what
   hidden means for itself — otherwise the scorecard is laid out and taking
   clicks from the moment the page loads, which the overlay's own opacity:0
   hides just well enough that the only symptom is a dead strip across the
   bottom of the title screen. The third time this file has been bitten by an
   ID beating `.hidden`; see `#controls.hidden` and `#fallback.hidden`. */
#overlay.night #resultBlock.hidden{ display:none; }
#overlay.night .levelResult{ grid-column:1 / -1; }
#overlay.night .grade{
  grid-column:1; grid-row:2 / span 7; align-self:center;
  font-size:46px; margin:0 0 0 2px;
}
#overlay.night .resultLine{ grid-column:2; font-size:11px; padding:2.5px 0; }
#overlay.night .resultLine b{ font-size:11.5px; }
#overlay.night .verdict,
#overlay.night .streak{
  grid-column:1 / -1; margin-top:9px; font-size:11.5px; text-align:center;
}
/* Her line reads as a different voice: not italic-yellow like the scorecard's
   quip but plain and cooler, because it is not a joke about the toilet. */
#overlay.night .verdict.her{ margin-top:6px; }
#overlay.night .streak{ margin-top:8px; }
/* A tall card on a short window has to be able to scroll rather than run off
   the bottom of the screen with the button on it. */
#overlay.night .panel{ max-height:88vh; overflow-y:auto; }

/* ── Pass / fail ───────────────────────────────────────────────────────
 * The banner is the only thing on this card most players will read, so it says
 * the one thing that decides what happens next and says it in one word. */
.levelResult{
  font-family:var(--display);
  font-size:11.5px; font-weight:700; letter-spacing:.22em;
  padding:7px 0 9px; margin:0 0 6px;
}
.levelResult.pass{ color:var(--good); }
.levelResult.fail{ color:var(--bad); }
.streak{ font-size:11px; color:rgba(226,240,246,.42); letter-spacing:.02em; }
/* A failed night's button is not a reward, so it does not get the gold. */
#startBtn.retry{
  background:linear-gradient(180deg,#3a4450,#2a323c); color:var(--paper);
  box-shadow:0 8px 26px rgba(0,0,0,.3);
}
#startBtn.retry:hover{ background:linear-gradient(180deg,#46525f,#333c47); }
#runline{ margin-top:0; }
#runline a{ color:rgba(226,240,246,.55); text-decoration:underline; cursor:pointer; }
#overlay.night #startBtn{ padding:10px; font-size:11px; letter-spacing:.16em; }

.panel{
  width:min(92vw,470px); padding:34px 38px 30px; text-align:center;
  border-radius:22px; border:1px solid var(--stroke);
  background:linear-gradient(180deg, rgba(20,27,35,.82), rgba(10,14,19,.86));
  box-shadow:0 30px 90px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.06);
}
h1{
  margin:0 0 6px; font-family:var(--display);
  font-size:40px; line-height:1; letter-spacing:-.005em; font-weight:700;
  background:linear-gradient(180deg,#fdf7d8,var(--pee));
  -webkit-background-clip:text; background-clip:text; color:transparent;
}
h1 .thin{ font-weight:300; opacity:.72; }
.tag{ margin:0 0 22px; font-size:12.5px; color:rgba(226,240,246,.55); line-height:1.5; }

#howto{ display:grid; grid-template-columns:1fr; gap:7px; margin:0 0 22px; text-align:left; }
.k{ display:flex; align-items:center; gap:8px; font-size:11.5px; color:rgba(226,240,246,.5); }
.k span{ margin-left:auto; color:rgba(226,240,246,.78); font-size:11px; letter-spacing:.04em; }
kbd{
  font-family:var(--mono); font-size:10px; padding:3px 7px; border-radius:6px;
  background:rgba(255,255,255,.07); border:1px solid var(--stroke);
  border-bottom-width:2px; color:var(--paper);
}

#startBtn{
  width:100%; padding:14px; font-size:12px; letter-spacing:.2em; border-radius:12px;
  background:linear-gradient(180deg,var(--pee-hot),#dcc95f); color:#1a1608; border:none;
  box-shadow:0 8px 26px rgba(233,217,122,.22);
}
#startBtn:hover{ background:linear-gradient(180deg,#fff3b0,#e6d370); }
.fine{ margin:14px 0 0; font-size:10px; color:rgba(226,240,246,.28); line-height:1.5; }

#resultBlock{ margin:0 0 22px; }
/* The letter grade in the display face, at 700 rather than 800: Bahnschrift's
   variable weight axis tops out at 700, and asking for more only gets a
   synthesised smear of the same outline. */
.grade{
  font-family:var(--display);
  font-size:74px; line-height:.9; font-weight:700; letter-spacing:-.02em; margin-bottom:14px;
  background:linear-gradient(180deg,#fff8dc,var(--pee)); -webkit-background-clip:text; background-clip:text; color:transparent;
}
.resultLine{
  display:flex; justify-content:space-between; align-items:baseline;
  font-size:12px; color:rgba(226,240,246,.55); padding:6px 2px;
  border-bottom:1px solid rgba(255,255,255,.05);
}
.resultLine b{ font-family:var(--mono); font-size:13px; color:var(--paper); font-weight:600; }
.resultLine.bad b{ color:var(--bad); }
/* The worst-hit line is the explanation, not another statistic, so it is
   allowed to be the one warm row in the column of grey ones. */
.resultLine.worst b{ color:var(--pee-hot); font-size:12px; }
.verdict{ margin-top:14px; font-size:12.5px; line-height:1.5; color:var(--pee-hot); font-style:italic; }
/* The second verdict — hers. Not italic and not yellow: the scorecard's line
   is the game being funny about the porcelain, and this one is not the game
   talking. It goes warm only on the run that earned it. */
.verdict.her{
  margin-top:8px; font-style:normal; letter-spacing:.01em;
  color:rgba(214,226,236,.62);
}
.verdict.her.warm{ color:#ffcf9a; }
/* The perfect grade is a mark, not a letter, so it does not want the letter's
   tight tracking or its cold yellow. */
.grade.perfect{
  background:linear-gradient(180deg,#fff2d8,#ffb066);
  -webkit-background-clip:text; background-clip:text;
  letter-spacing:0;
}

.hidden{ display:none; }
#overlay.hidden{ display:grid; }
/* An ID selector outranks a class one, so every block below that sets its own
   `display` on an ID has to say what `.hidden` means for it — otherwise the
   title card, the settings panel and the win screen are all permanently on
   screen no matter what the JS asks for. Same trap `#controls.hidden` and
   `#fallback.hidden` above already had to be dug out of. */
#title.hidden, #settings.hidden, #winscreen.hidden, #quickbar.hidden{ display:none; }

#fallback{
  position:fixed; inset:0; display:grid; place-items:center; padding:40px; z-index:50;
  background:#05070a; color:rgba(226,240,246,.7); font-size:14px; text-align:center; line-height:1.6;
}
/* the id selector above outranks .hidden{display:none}, so the error screen
   would otherwise cover the game on load */
#fallback.hidden{ display:none; }

/* Below this there is no room to sit the card beside the bowl, so it goes to
   the bottom of the frame instead — off-centre vertically, which is the part
   that matters, and the same corner the night card already lives in. */
@media (max-width:900px){
  #overlay{ place-items:end center; padding:0 0 max(18px, 3vh); }
  #overlay > .panel{ max-height:82vh; }
}

/* On a narrow window the strip runs out of room for three cells side by side
   long before it runs out of things to say, so the night name — the one item
   that is decoration rather than data — is the first thing to go, and the two
   meters give up their fixed widths. */
@media (max-width:760px){
  #topbar{ gap:10px; padding:9px 12px 20px; }
  #levelName{ display:none; }
  #dmgmeter{ width:min(52vw, 240px); }
  #stray{ font-size:25px; }
  #bladderwrap{ width:min(28vw, 130px); }
  #botrail{ left:12px; bottom:12px; gap:7px; }
  #toast{ top:76px; font-size:14px; }
  h1{ font-size:30px; }
  #overlay.night .panel{ width:min(94vw,360px); }
}
