/* Owned by the `ui` builder agent. */
:root {
  color-scheme: dark;

  /* the one interaction, in one place */
  --sg-y: 54%; /* vertical anchor: centre of the centre panel (see note below) */
  --sg-ms-out-mark: 140ms;
  --sg-ease-in: cubic-bezier(0.22, 0.61, 0.36, 1);
  --sg-ease-out: cubic-bezier(0.4, 0, 1, 1);
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html,
body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #000;
}

#view {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

#ui {
  position: fixed;
  inset: 0;
  pointer-events: none;
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-font-smoothing: antialiased;
  text-rendering: geometricPrecision;
}

#ui .layer {
  position: absolute;
  inset: 0;
}

/* ------------------------------------------------------------------ *
 * Play — a title card over footage, not a menu.                       *
 *                                                                     *
 * THE RULE THIS PIECE IS BUILT ON                                     *
 * The frame behind this overlay is meant to read as a photograph of   *
 * somebody's sim rig, and the one thing every rig photograph has is a *
 * huge brightness ratio between the panels and the room — in          *
 * refs/rig/01 the screens are blown out while the room floor is       *
 * nearly black. That ratio is the piece's most fragile asset and it   *
 * belongs to `rig` and `post`, not to the UI.                         *
 *                                                                     *
 * Round 1 spent it. A frame-wide grade plus a frame-wide grain veil   *
 * pulled the centre panel from mean 130 / max 255 down to 98 / 178    *
 * while lifting the darkest wall from 3.4 to 6.2 — a 38:1 contrast    *
 * ratio collapsed to 16:1. The overlay was quietly regrading the      *
 * photograph to make one word legible.                                *
 *                                                                     *
 * So: NOTHING here may touch a pixel outside the centre monitor. The  *
 * mark sits in the middle of the centre panel — which is where it     *
 * belongs anyway, because in the fiction the sim is what is waiting   *
 * on that screen — and as of round 9 there is no darkening term left  *
 * at all. Room, ceiling, wheel and side panels come through exactly   *
 * as `post` graded them, and so does the road under the word.         *
 * ------------------------------------------------------------------ */

#ui .sg {
  position: absolute;
  inset: 0;
}

/*
 * ROUND 9 — THE POOL IS GONE, AND NOTHING REPLACES IT.
 *
 * Rounds 1–8 all shipped some negative term under the word: first a frame-wide
 * grade, then an offset text-shadow, then a symmetric soft ellipse of shade.
 * Measured against a capture of the identical frame with the overlay hidden, the
 * round-8 ellipse was subtracting **48 luma on average 7 px out from the ink**,
 * peaking at −76, still −40 at 40 px, over 142 000 pixels — a tenth of the
 * picture. The road under the mark went from a clean sunlit surface to a brown
 * smear; side by side with the same frame minus the overlay it looks like
 * somebody breathed on the lens.
 *
 * That is the whole gap, and it does not have a tuning. A monitor emitting a
 * word cannot remove light from the picture around it: whatever the panel puts
 * out, the lens spreads OUTWARD. Any darkening authored on this side of the
 * camera is a scrim on a photograph, and the softer and wider you make it the
 * more it reads as dirt rather than as design.
 *
 * So the overlay is now **one-way**. The only pixels it paints are the two
 * bitmaps ui-lens.js builds, and every colour in them is held at luma ≥ 240 —
 * at or above the brightest pixel the sensor records anywhere in the frame — so
 * source-over compositing can essentially only lift. Measured across the whole
 * of both bitmaps, outside the glyph bodies: worst possible loss against the
 * frame's own white point −0.5 luma (a fifth of a display level, a fifth of the
 * grain's sd), against the mid-grey road it actually sits on a lift of +11.
 * Twenty times more able to brighten than to darken. That is what "it bleeds
 * outward" means as a number.
 */

/*
 * The mark. No box.
 *
 * A four-sided stroke around a word is the single most dialog-like shape in
 * interface design — it is the outline of a widget, and the frame it is sitting
 * in is supposed to be a photograph. What replaces it is a typographic device
 * instead of a chrome one: wide-tracked caps over a hairline that fades out at
 * both ends, i.e. a broadcast title, which has no corners for the eye to read as
 * a button and no enclosed area to read as a fill.
 *
 * ROUND 8 — the button no longer paints anything.
 *
 * It is the hit target, the position, the opacity and the transform, and that is
 * all. The two things you can actually see are bitmaps built by ui-lens.js and
 * appended by play.js, because a browser-rasterised glyph is a vector stroke
 * dropped onto a photograph: measured at 1600x900 the old one stepped from the
 * background to 249 in a single sample, held 249 for fifteen rows with zero
 * variance, out-rendered the brightest pixel anywhere else in the frame (245),
 * was the only perfectly neutral colour on screen, and — because that was the
 * only way to keep it legible over a blown-out road — sat in a 1–2 px dark
 * trench, which is the opposite of what a bright emitter does to its
 * surroundings. None of that is a styling problem, so none of it could be fixed
 * here. It is fixed by drawing the word as light instead of as type.
 *
 * The font properties below are NOT decorative leftovers: play.js reads them off
 * this rule with getComputedStyle and hands them to the rasteriser, so this is
 * still where the typography is specified.
 */
#ui .sg-play {
  --rise: 12px;
  --scale: 1;
  /* the box the mark is centred in. --word-w is measured by play.js from the
     font this rule resolves to, so the rule's overhang (--rule-x) is still
     stated relative to the word rather than as an absolute number that would
     only be right on the machine it was tuned on. */
  --pad-x: 30px;
  --word-w: 84px;
  --rule-x: 24px;
  --rule-a: 0.9;

  position: absolute;
  left: 50%;
  top: var(--sg-y);
  transform: translate(-50%, calc(-50% + var(--rise))) scale(var(--scale));

  display: block;
  width: calc(var(--word-w) + 2 * var(--pad-x));
  height: 40px;
  /* The mark's scatter is baked into the bitmaps and now reaches ~90 px past the
     ink in every direction — nine times the button box. Stated rather than
     inherited, because a UA stylesheet clipping a button's overflow would saw
     the halo off at a rectangle and put back the hard edge round 8 removed. */
  overflow: visible;

  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background-color: transparent;
  background-image: none;

  font-family: inherit;
  font-size: 21px;
  font-weight: 500;
  line-height: 1;

  cursor: pointer;
  pointer-events: auto;
  opacity: 0;

  transition:
    opacity 720ms var(--sg-ease-in) 170ms,
    transform 720ms var(--sg-ease-in) 170ms;
}

/* The word. Its own scatter is baked into the bitmap, so it overflows the
   button box by ~60 px in every direction and must not eat the pointer. */
#ui .sg-mark {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  pointer-events: none;
}

/* The hairline. Fades to nothing at both ends, so it never closes a shape.
   Brighter in the middle, the way a thin lit edge photographs.
 *
 * Round 7 sized it off the word instead of off a literal number, and shortened
 * the end fades so the solid core is about the width of the word; that was
 * right and is unchanged. Round 8 only changed what it is made of. A 1 px CSS
 * box is exactly as unphotographic as a glyph — worse, in fact, because it is a
 * single sample wide — so it is now the same kind of bitmap as the word, built
 * once at its widest (:hover) and scaled horizontally by CSS. Horizontal scaling
 * is what the old percentage-stop gradient did too, so the end fades stay
 * proportional; the vertical profile, which is the part carrying the optics,
 * never stretches.
 *
 * --rule-a survives as an opacity, so the brightness step on press is still
 * discrete: the mark should answer like a switch, not ease like a web widget. */
#ui .sg-rule {
  position: absolute;
  left: 50%;
  width: calc(var(--word-w) + var(--rule-x));
  transform: translateX(-50%);
  opacity: var(--rule-a);
  pointer-events: none;
  transition: width 260ms var(--sg-ease-in);
}

#ui .sg.is-in .sg-play {
  --rise: 0px;
  opacity: 1;
}

/* Affordance without chrome: the hairline runs out under the word. Nothing
   grows a fill, nothing gains an outline. 44px is RULE_MAX_X in play.js — the
   width the rule bitmap is built at, so hover is the one state that is not a
   downscale. */
#ui .sg-play:hover {
  --rule-x: 44px;
  --rule-a: 1;
}

#ui .sg-play:active,
#ui .sg-play.is-press {
  --rule-x: 33px;
  --rule-a: 1;
  --scale: 0.992;
  transition:
    opacity 720ms var(--sg-ease-in) 170ms,
    transform 90ms ease-out;
}

#ui .sg-play:focus-visible {
  outline: 1px solid rgba(255, 255, 255, 0.9);
  outline-offset: 6px;
}

/* Leaving. The mark fades and opens outward — the same direction the morph
   pushes the frame. Everything is done inside 200 ms, i.e. 4% of the 4.6 s
   morph, and play.js deletes the node right after. */
#ui .sg.is-out {
  pointer-events: none;
}

#ui .sg.is-out .sg-play {
  --scale: 1.04;
  --rule-x: 88px;
  --rule-a: 0;
  opacity: 0;
  transition:
    opacity var(--sg-ms-out-mark) var(--sg-ease-out),
    transform 200ms cubic-bezier(0.3, 0, 0.6, 1);
}

#ui .sg.is-out .sg-rule {
  transition:
    width 200ms cubic-bezier(0.3, 0, 0.6, 1),
    opacity var(--sg-ms-out-mark) var(--sg-ease-out);
}

/* capture mode and prefers-reduced-motion: no in-between states at all */
#ui .sg.sg-no-anim,
#ui .sg.sg-no-anim *,
#ui .sg.sg-no-anim *::before,
#ui .sg.sg-no-anim *::after {
  transition: none !important;
  animation: none !important;
}
