/* The anatomy inside a pane: tile layers, port rows, nameplates, the popup, the
 * overlay, the two dialogs, and the served refusal page.
 *
 * ===========================================================================
 * EVERY VALUE HERE IS A BOARD'S, AND THE ANATOMY IS THE BOARD'S TOO
 * ===========================================================================
 *
 * The markup this styles was lifted element by element from the artboards, so
 * this sheet is the other half of the same lift: the recipes the boards draw,
 * spelled once each, keyed off the data attributes the views write.
 *
 * WHICH MEANS THE TWO TILE FRAMES ARE TWO SETS OF VALUES. A lobby tile's badge
 * is 34px at a 16.8px inset with the name at `large`; an in-game tile's is 30px
 * at 12px with the name at `body`. That is what "tile anatomy is identical at
 * every size" means — identical at every WINDOW size, and different between the
 * two frames, which the boards draw and this keys on `data-where`. */

/* ===========================================================================
 * The tile
 * =========================================================================== */

.st-tile {
  position: relative;
  border-radius: var(--r-tile);
  overflow: hidden;
}

/* THE RING HAS FIVE STATES AND THE BADGE SAYS WHY. Solid for a seat that is
 * playing; the hue at half strength for one that is not; amber for a rough line,
 * which is the only place a status tone reaches a tile's edge; a hairline for an
 * open chair; and the hue at half strength inset for a seat idle in a running
 * match. "Waiting is the common case and must stay calm: amber on one tile,
 * never a room-wide treatment." */
.st-tile[data-ring='solid'] {
  box-shadow: 0 0 0 2px var(--tile-hue);
}

.st-tile[data-ring='outline'] {
  background: var(--well);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--tile-hue) 50%, transparent);
}

.st-tile[data-ring='rough'] {
  box-shadow: 0 0 0 2px var(--waiting);
}

.st-tile[data-ring='empty'] {
  background: var(--wash);
  box-shadow: inset 0 0 0 1px var(--faint);
}

.st-tile[data-ring='seat-idle'] {
  background: var(--wash);
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--tile-hue) 50%, transparent);
}

/* The camera ground: the port's own gradient, which the design calls "one
 * deliberate placeholder". It sits UNDER the picture rather than being replaced
 * by it — with no frame yet the canvas is transparent and the gradient shows
 * through, and a frame simply covers it. That is what makes the fallback true
 * with no loading state to manage, and it is a divergence the design intends:
 * the boards keep the gradient as the placeholder they are. */
.st-tile-cam {
  position: absolute;
  inset: 0;
}

.st-tile-cam[data-cam='1'] {
  background: var(--cam-1);
}

.st-tile-cam[data-cam='2'] {
  background: var(--cam-2);
}

.st-tile-cam[data-cam='3'] {
  background: var(--cam-3);
}

.st-tile-cam[data-cam='4'] {
  background: var(--cam-4);
}

/* A camera that is off: "the `--well` fill where the gradient was, `CAMERA OFF`
 * in `--dim`." */
.st-tile-cam[data-cam='off'] {
  background: var(--well);
}

.st-tile-off {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}

.st-tile-off span {
  font-size: var(--type-small);
  letter-spacing: 0.16em;
  color: var(--dim);
}

/* The picture. "In the lobby the faces are the product", so the video fills its
 * cell and crops at the sides rather than letterboxing — `cover`, centred, in
 * both frames. */
.st-tile-frame {
  position: absolute;
  inset: 0;
}

.st-tile-frame canvas {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* THE PLAYER'S OWN VIEW IS MIRRORED WHERE IT IS DISPLAYED, never the capture and
 * never a remote face: text on your shirt reads backwards to you and correctly
 * to everyone else. One rule, on the canvas inside the attribute the view
 * writes. */
.st-tile[data-self='true'] .st-tile-frame canvas,
.st-preview-frame canvas {
  transform: scaleX(-1);
}

.st-preview-frame canvas {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The port badge: three sizes, two fills. The glyph is 13px in the two smaller
 * badges and 15px in the largest, at weight 600, which the type table states as
 * an exception rather than a role. */
.st-port {
  display: grid;
  place-items: center;
  border-radius: var(--r-control);
  font-size: var(--type-port-glyph);
  font-weight: var(--weight-port-glyph);
  flex: 0 0 auto;
}

.st-port[data-size='34'] {
  width: 34px;
  height: 34px;
  font-size: var(--type-port-glyph-large);
}

.st-port[data-size='30'] {
  width: 30px;
  height: 30px;
}

.st-port[data-size='26'] {
  width: 26px;
  height: 26px;
  border-radius: var(--r-badge);
}

.st-port[data-fill='solid'] {
  background: var(--tile-hue);
  color: var(--ground);
}

/* The board's own percentages, and THERE ARE TWO OUTLINE RECIPES rather than one.
 * The open seat's 34px tile badge is `50%` inset over `70%` ink mixed with
 * transparent; the Ports row's 26px badge is `28%` over `52%` mixed with
 * `--ground`, which is the rule below `[data-empty='true']`. Authored as one they
 * were `65/75` -- a third recipe, matching neither, five percent from each and
 * therefore invisible to everything except an instrument that composites. */
.st-port[data-fill='outline'] {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--tile-hue) 50%, transparent);
  color: color-mix(in srgb, var(--tile-hue) 70%, transparent);
}

/* The lobby frame groups the badge with the tag beside it; the in-game frame
 * draws the badge alone, so there is no group to position. */
.st-tile-topleft {
  position: absolute;
  left: var(--space-6);
  top: var(--space-6);
  display: flex;
  gap: var(--space-3);
  align-items: center;
}

/* THE ONE WIDTH THIS SHEET SET NAMES, AND THE DESIGN NAMES IT FIRST.
 *
 * D21: "the badge inset steps at 1440, not 1920, and the 1440 board now draws
 * 12px. It is 12px at 1440 and above, 10px at the floor."
 *
 * Everywhere else the layout derives from the space it is given -- the game takes
 * its height from the body, the rail is the clamped leftover, the tiles follow
 * the rail -- and no rule here asks how wide the window is. This is the stated
 * exception, so it is written as one: a threshold, at the value the design gives,
 * with the sentence that puts it there.
 *
 * The floor is the base and 1440 is the step up, so the narrowest window needs no
 * query to be right. It was a flat 12px at every width, which drew four badges
 * 2px in from where the floor board draws them and none anywhere else -- and I
 * read that as the floor being a rule sheet drawn tighter rather than as a step
 * the design had asked for. It is a lift, and D21 is where it was written down. */

.st-tile[data-where='ingame'] > .st-port {
  position: absolute;
  left: 10px;
  top: 10px;
}

@media (min-width: 1440px) {
  .st-tile[data-where='ingame'] > .st-port {
    left: 12px;
    top: 12px;
  }
}

/* ===========================================================================
 * THE PORT HUE, WHICH USED TO BE WRITTEN ON THE ELEMENT
 * ===========================================================================
 *
 * Six elements carried `style="--tile-hue:var(--pN)"`. The served policy's
 * `style-src 'self'` admits no inline style at all, so the attribute was the one
 * thing forcing it open — and it was never carrying a measurement: the hue is a
 * closed set of four, and every element that took it already says which port it
 * is. So the sheet reads the attribute the markup already had.
 *
 * TWO ATTRIBUTES, BECAUSE THE TWO COUNT DIFFERENTLY, and this is the conversion
 * stated once rather than papered over by giving one element a second name for
 * the same fact. A tile's `data-seat` is the roster's index and is 0-based; a
 * Ports row's and the band leader's `data-port` is the design's port number and
 * is 1-based. `--p1` is the first port in both. */

.st-tile[data-seat='0'],
[data-port='1'] {
  --tile-hue: var(--p1);
}

.st-tile[data-seat='1'],
[data-port='2'] {
  --tile-hue: var(--p2);
}

.st-tile[data-seat='2'],
[data-port='3'] {
  --tile-hue: var(--p3);
}

.st-tile[data-seat='3'],
[data-port='4'] {
  --tile-hue: var(--p4);
}

/* The exception badge's hue, which is `'port' | Tone`. The port case needs no
 * rule at all: the badge is drawn inside the tile, so it INHERITS the hue the
 * tile already resolved — the same reason the split control's word inherits its
 * band. The tones are the model's own five, and only the ones a badge can carry
 * are here, each one the token of the same name. */

.st-badge[data-hue='port'] {
  --st-hue: var(--tile-hue);
}

.st-badge[data-hue='calm'] {
  --st-hue: var(--calm);
}

.st-badge[data-hue='dim'] {
  --st-hue: var(--dim);
}

.st-badge[data-hue='good'] {
  --st-hue: var(--good);
}

.st-badge[data-hue='waiting'] {
  --st-hue: var(--waiting);
}

.st-badge[data-hue='wrong'] {
  --st-hue: var(--wrong);
}

/* The exception badge: "one recipe — `--plate`, 1px hairline in the hue at 55%,
 * text at 85%." At most one per tile, top-right, and its hue is the view's. */
.st-badge {
  position: absolute;
  right: var(--space-6);
  top: var(--space-6);
  padding: 4px 9px;
  border-radius: var(--r-badge);
  font-size: var(--type-micro);
  letter-spacing: var(--track-tag);
  background: var(--plate);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--st-hue) 55%, transparent);
  color: color-mix(in srgb, var(--st-hue) 85%, transparent);
}

.st-tile[data-where='ingame'] > .st-badge {
  right: 12px;
  top: 12px;
}

/* The nameplate. The lobby frame nests the name and the device line in a column
 * inside a `flex-end` plate and wraps the meter; the in-game frame holds the
 * name and the meter directly, centred. */
.st-tile-plate {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  background: var(--scrim);
}

/* THE BOTTOM PAD IS 14px AND IS NOT A SPACING STEP. The board draws
 * `padding:11.2px 16.8px 14px` on every lobby and loading plate -- 27 of them --
 * and there is no token between --space-4 (11.2) and --space-6 (16.8), so the
 * third value is written out rather than rounded to one.
 *
 * THE DECK MOVED IT, AND IT IS PART OF THE SAME CHANGE AS THE DEVICE LINE
 * (D114). The revision before it drew `padding:11.2px 16.8px` on all 27 -- bottom 11.2 --
 * which is what two values here said, so the product agreed with the deck it was
 * graded against and the structural oracle was green. The designer took the line
 * out of the plate and deepened the plate's bottom in one revision.
 *
 * IT IS WRITTEN DOWN BECAUSE IT WAS NEARLY MISREAD. Following the line and not
 * the padding left the plate 2.8px short, which unpaired it from the board's --
 * the box bound is ONE PIXEL PER EDGE (`verify/gates/design/tree.ts`) -- and an
 * unpaired plate cascades into 20 reported differences per lobby board, none of
 * them about the padding and none about the device line either. A structural
 * skeleton diff of tags and nesting says the two revisions differ by one <span>;
 * it is blind to a style attribute, so "nothing else moved" was a claim about the
 * tree shape and not about the board. */
.st-tile[data-where='lobby'] > .st-tile-plate,
.st-tile[data-where='loading'] > .st-tile-plate {
  padding: var(--space-4) var(--space-6) 14px;
  align-items: flex-end;
  gap: var(--space-4);
}

.st-tile[data-where='ingame'] > .st-tile-plate {
  padding: 6px 12px;
  align-items: center;
  gap: var(--space-3);
}

.st-tile-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.st-tile-name {
  font-weight: var(--weight-large);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.st-tile[data-where='lobby'] .st-tile-name,
.st-tile[data-where='loading'] .st-tile-name {
  font-size: var(--type-large);
  line-height: 1.15;
}

.st-tile[data-where='ingame'] .st-tile-name {
  font-size: var(--type-body);
}

/* The signal meter: three bars, and its tone says whether the line is rough
 * while its lit count says how good it is. "It turns amber at one bar, matching
 * the tile ring, and is never red: a link that bad has already become a hold,
 * which is its own screen." An unmeasured line reads the same as an absent one,
 * so arithmetic over nothing cannot read as a feature. */
.st-tile-signal {
  margin-left: auto;
  display: flex;
  gap: var(--space-3);
  align-items: center;
}

.st-tile-meter {
  display: flex;
  align-items: end;
  gap: 2px;
}

.st-tile[data-where='ingame'] .st-tile-meter {
  margin-left: auto;
  height: 12px;
}

.st-tile-signal .st-tile-meter {
  height: 14px;
}

.st-tile-meter i {
  display: block;
  width: 3px;
  background: var(--faint);
}

.st-tile-signal .st-tile-meter i:nth-child(1) {
  height: 6px;
}

.st-tile-signal .st-tile-meter i:nth-child(2) {
  height: 10px;
}

.st-tile-signal .st-tile-meter i:nth-child(3) {
  height: 14px;
}

.st-tile[data-where='ingame'] .st-tile-meter i:nth-child(1) {
  height: 5px;
}

.st-tile[data-where='ingame'] .st-tile-meter i:nth-child(2) {
  height: 8px;
}

.st-tile[data-where='ingame'] .st-tile-meter i:nth-child(3) {
  height: 12px;
}

.st-tile-meter[data-tone='good'] i:nth-child(-n + 3):nth-child(-n + 1),
.st-tile-meter[data-lit='2'] i:nth-child(-n + 2),
.st-tile-meter[data-lit='3'] i {
  background: var(--good);
}

.st-tile-meter[data-lit='1'] i:nth-child(1) {
  background: var(--waiting);
}

/* A chair, which is the deck's own anatomy: one centred column holding the
 * outline badge and a caption, and none of the five layers a face has. */
.st-tile-open {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
}

.st-tile[data-ring='empty'],
.st-tile[data-ring='seat-idle'] {
  display: grid;
  place-items: center;
}

.st-tile-seat {
  font-size: var(--type-lead);
  letter-spacing: 0.14em;
  color: var(--dim);
}

/* ===========================================================================
 * The rail's two cards
 * =========================================================================== */

.st-card {
  padding: var(--space-4);
  gap: var(--space-3);
  box-shadow: var(--shadow-sm);
  display: flex;
  flex-direction: column;
}

/* "The Ports header is label + hairline, the same rule as every other panel
 * header." The loading screen's head is its one sentence and no hairline, which
 * is why the rule is on the element rather than on the head. */
.st-card-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.st-rule {
  flex: 1;
  height: 1px;
  background: var(--faint);
}

.st-game-row {
  display: flex;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-3);
  border-radius: var(--r-control);
  background: var(--ground);
  box-shadow: inset 0 0 0 1px var(--faint);
}

/* The art slot. The gradient is the design's placeholder and the real picture is
 * a second layer over it, which is why this is one element and not an image
 * child: a tree oracle pairs our node against the board's, and a child the board
 * does not draw is a divergence. */
.st-game-art {
  width: 64px;
  height: 48px;
  flex: 0 0 64px;
  border-radius: var(--radius-sm);
  background: var(--art);
}

.st-game-art[data-art='true'] {
  background:
    var(--game-art) center / cover no-repeat,
    var(--art);
}

.st-game-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.st-game-title {
  font-size: var(--type-body);
  font-weight: var(--weight-body);
}

.st-game-text .st-game-title {
  font-weight: 500;
}

.st-game-meta {
  font-size: 12px;
  color: var(--muted);
}

.st-game-play {
  color: var(--good);
}

#change-game {
  justify-content: space-between;
}

.st-count {
  color: var(--muted);
  font-size: 12px;
}

/* The download block, which the loading screen alone draws. The fill's width is
 * the one live value and rides as an inline style; the `<i>` is the board's own
 * tag rather than a choice. */
.st-download {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 0 var(--space-3) var(--space-1);
}

.st-download-track {
  display: block;
  height: 2px;
  border-radius: 1px;
  background: var(--faint);
  position: relative;
  overflow: hidden;
}

.st-download-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  display: block;
  background: var(--accent);
}

.st-download-tag {
  font-size: var(--type-tag);
  letter-spacing: var(--track-tag);
  text-transform: uppercase;
  color: var(--muted);
}

/* ===========================================================================
 * The Ports rows
 * =========================================================================== */

.st-port-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.st-port-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--r-control);
  background: var(--ground);
}

.st-port-row[draggable='true'] {
  cursor: grab;
}

/* "An empty port row is dimmed but not disabled: a `--faint` inset, Open in
 * `--dim`, and the port badge mixed down. It keeps its hue so the list still
 * reads as a four-port cabinet, but recedes behind the occupied rows. NOT
 * `opacity: .45` — that means disabled, and an open port is a valid drop
 * target." */
/* AND IT KEEPS ITS FILL. The sentence above names the inset and says nothing
 * about the fill, so the fill was dropped -- and the board draws the empty row
 * `background:var(--ground);box-shadow:inset 0 0 0 1px var(--faint)`, the same
 * fill as an occupied row with a hairline added. Read from the prose where the
 * board states it, which is the join column's missing width one card over. */
.st-port-row[data-empty='true'] {
  box-shadow: inset 0 0 0 1px var(--faint);
}

.st-port-row[data-empty='true'] .st-port {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--tile-hue) 28%, transparent);
  color: color-mix(in srgb, var(--tile-hue) 52%, var(--ground));
}

.st-port-name {
  font-size: var(--type-default);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.st-port-row[data-empty='true'] .st-port-name,
.st-port-row[data-idle='true'] .st-port-name {
  color: var(--dim);
}

/* Version skew: "the mismatched build number in `--waiting` at the row's right,
 * and nothing else — no ring on the row, no ring on the tile. The number is the
 * fact; the disabled Start is the consequence." */
.st-port-build {
  margin-left: auto;
  font-size: 12px;
  color: var(--waiting);
}

/* The handle is "the row's trailing element after any status readout, so handles
 * line up in one column and the row still reads identity-first". */
.st-drag {
  margin-left: auto;
  display: grid;
  place-items: center;
  color: var(--dim);
  cursor: grab;
}

/* The load slot, which the same recipe serves for four load words and two
 * running-match words. The idle word has NO marker element at all, which is
 * lifted rather than simplified: nothing is waiting on that seat. */
.st-load {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--type-tag);
  letter-spacing: var(--track-tag);
  text-transform: uppercase;
}

.st-load-mark {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: 0 0 auto;
}

.st-load[data-load='ready'],
.st-load[data-load='in-play'] {
  color: var(--muted);
}

.st-load[data-load='ready'] .st-load-mark,
.st-load[data-load='in-play'] .st-load-mark {
  background: var(--good);
}

.st-load[data-load='loading'] {
  color: var(--dim);
}

.st-load[data-load='loading'] .st-load-mark {
  width: 11px;
  height: 11px;
  border: 2px solid var(--accent);
  border-top-color: transparent;
  animation: st-spin 1s linear infinite;
}

.st-load[data-load='queued'] {
  color: var(--dim);
}

.st-load[data-load='queued'] .st-load-mark {
  box-shadow: inset 0 0 0 1px currentcolor;
}

.st-load[data-load='failed'] {
  color: var(--wrong);
}

.st-load[data-load='failed'] .st-load-mark {
  background: var(--wrong);
}

.st-load[data-load='idle'] {
  color: var(--dim);
}

/* ===========================================================================
 * The device popup, the only popover in the product
 * =========================================================================== */

.st-picker {
  position: absolute;
  width: 340px;
  padding: var(--space-6);
  border-radius: var(--radius-md);
  background: var(--panel);
  box-shadow: var(--pop);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.st-picker-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.st-picker-rule {
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, var(--faint), transparent);
}

.st-picker-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.st-device {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0 var(--space-4);
  border-radius: var(--r-control);
}

.st-device[data-selected='true'] {
  background: var(--ground);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
}

.st-device-tick {
  width: 18px;
  display: grid;
  place-items: center;
  color: var(--accent);
  flex: 0 0 18px;
}

.st-device-name {
  font-size: var(--type-default);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.st-device[data-selected='false'] .st-device-name {
  color: var(--muted);
}

/* The divider's fade is 24px each end where the shared rule token fades over 48:
 * this is the popup's own width and the token is the full-width rule's. */
.st-picker-divider {
  height: 1px;
  background: linear-gradient(
    to right,
    transparent,
    var(--faint) 24px,
    var(--faint) calc(100% - 24px),
    transparent
  );
}

.st-picker-level {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

/* "The level meter is why this is worth building — picking a device you cannot
 * verify is a guess, and the bars answer 'is this the one that hears me' without
 * a word of instruction." The lit count is this machine's own measurement and no
 * message carries it, so zero draws unlit. */
.st-level {
  flex: 1;
  display: flex;
  gap: 3px;
  align-items: center;
}

.st-level i {
  flex: 1;
  height: 6px;
  border-radius: 1px;
  display: block;
  background: var(--faint-fill);
}

.st-level[data-lit='1'] i:nth-child(-n + 1),
.st-level[data-lit='2'] i:nth-child(-n + 2),
.st-level[data-lit='3'] i:nth-child(-n + 3),
.st-level[data-lit='4'] i:nth-child(-n + 4),
.st-level[data-lit='5'] i:nth-child(-n + 5),
.st-level[data-lit='6'] i:nth-child(-n + 6),
.st-level[data-lit='7'] i:nth-child(-n + 7),
.st-level[data-lit='8'] i:nth-child(-n + 8),
.st-level[data-lit='9'] i:nth-child(-n + 9),
.st-level[data-lit='10'] i {
  background: var(--good);
}

/* "The notch points at the chevron, not the middle of the popup." Where it lands
 * is measured at mount, so the offset is a custom property the wiring sets. */
.st-picker-notch {
  position: absolute;
  left: var(--st-notch, 86px);
  bottom: -6px;
  width: 12px;
  height: 12px;
  transform: rotate(45deg);
  background: var(--panel);
  box-shadow: 1px 1px 0 0 var(--line);
}

/* The preview well: "a fixed 160px well … its base is `--deep` with a `--faint`
 * inset — flat, not the camera gradient — and all three empty states are the same
 * box so the popup never changes height." */
.st-preview {
  height: 160px;
  border-radius: var(--r-control);
  overflow: hidden;
  position: relative;
  background: var(--deep);
  box-shadow: inset 0 0 0 1px var(--faint);
  display: grid;
  place-items: center;
}

.st-preview-cam {
  position: absolute;
  inset: 0;
}

.st-preview-cam[data-cam='1'] {
  background: var(--cam-1);
}

.st-preview-cam[data-cam='2'] {
  background: var(--cam-2);
}

.st-preview-cam[data-cam='3'] {
  background: var(--cam-3);
}

.st-preview-cam[data-cam='4'] {
  background: var(--cam-4);
}

.st-preview-frame {
  position: absolute;
  inset: 0;
}

/* "ONLY YOU SEE THIS, and the badge says so." It is the one surface in the
 * product that shows a camera to nobody else. */
.st-preview-badge {
  position: absolute;
  left: 10px;
  top: 10px;
  padding: 4px 9px;
  border-radius: var(--r-badge);
  font-size: var(--type-tag);
  letter-spacing: var(--track-tag);
  background: var(--plate);
  box-shadow: inset 0 0 0 1px var(--faint);
  color: var(--muted);
}

.st-preview-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  color: var(--muted);
}

.st-preview-empty span:last-child {
  font-size: 12px;
  color: var(--muted);
}

.st-preview-icon {
  color: var(--color-neutral-700, var(--dim));
}

.st-preview-icon[data-tone='waiting'] {
  color: var(--waiting);
}

/* ===========================================================================
 * The overlay over a stopped picture
 * =========================================================================== */

.st-overlay {
  position: absolute;
  inset: 0;
  background: var(--overlay);
}

/* "`--overlay` scrim + 96px PAUSED, nothing else." It is relatively positioned
 * so the rectangle's own centring places it, which is the recipe three of the
 * five stopped boards draw. */
.st-paused {
  position: relative;
  font-size: var(--type-paused);
  font-weight: 500;
  letter-spacing: var(--track-paused);
}

/* The band. "Held screens never dim the uninvolved tiles. All overlay text is
 * full strength." */
.st-overlay-band {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 44px;
  display: flex;
  align-items: center;
  gap: var(--space-8);
  background: linear-gradient(to top, var(--overlay-band), transparent);
}

/* AND ITS TWO LEADERS ARE NOT ONE RECIPE. This was authored as one and the deck
 * disagrees: board 19 draws the band `align-items: center` around its 40px
 * spinner, board 17 draws it `flex-start` around the 56px port square, so the
 * one line beside a square taller than itself sits at the square's top. Written
 * as one rule it is wrong on one of the two boards, and neither the tree
 * comparison nor the copy census can see it — both elements are present, in
 * order, with the board's own text. */
.st-overlay-band:has(.st-band-leader[data-leader='port']) {
  align-items: flex-start;
}

.st-band-leader[data-leader='port'] {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  flex: 0 0 56px;
  border-radius: var(--radius-md);
  background: var(--tile-hue);
  color: var(--ground);
  font-size: var(--type-port-glyph-band);
  font-weight: var(--weight-port-glyph);
}

.st-band-leader[data-leader='spinner'] {
  width: 40px;
  height: 40px;
  flex: 0 0 40px;
  border-radius: 50%;
  border: 3px solid var(--accent);
  border-top-color: transparent;
  animation: st-spin 1s linear infinite;
}

.st-band-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  flex: 1;
  min-width: 0;
}

/* ===========================================================================
 * The two confirmations
 * =========================================================================== */

/* THE DIALOG IS NOCTURNE'S, AND THIS IS ONLY WHAT THE DESIGN MOVES.
 *
 * "**Nocturne's own size**: 440px, `--space-6` padding, 20px title, 14px body,
 * `--r-window`, `--modal`, over a 62% `--board` scrim." Held against the shipped
 * sheet, five of those seven are already what it sets — 440px, the `--space-3`
 * gap, `--r-window` (its own `--radius-lg`), `--panel` (its `--color-surface`),
 * and the whole title row at 20/500. Three are not: the padding, the shadow and
 * the scrim.
 *
 * THE BOARD RESTATES ALL SEVEN INLINE and that is not licence to. An artboard
 * draws by writing computed values on the element; the design's instruction for
 * a Nocturne class is "set these once in the stylesheet … and never restyle per
 * screen", and its word for this row is "unchanged". So the restatements went,
 * and `../view/stylesheets.test.ts` measures the shipped rule against the
 * table's row instead — which keeps every one of the seven measured, including
 * the four nothing here declares. */

.dialog-backdrop {
  background: color-mix(in srgb, var(--board) 62%, transparent);
}

.dialog {
  padding: var(--space-6);
  box-shadow: var(--modal);
}

/* The body is the one place the product refuses a shipped value outright:
 * Nocturne dims it with `opacity: 0.85`, and "one opacity exists in the product:
 * .45, and only on a disabled control". The board draws `opacity: 1` with the
 * de-emphasis carried by `--muted`, which is the same reading a person gets and
 * the only one that leaves the opacity rule true. */
.dialog-body {
  line-height: 1.55;
  opacity: 1;
  color: var(--muted);
  text-wrap: pretty;
}

.dialog-actions {
  gap: var(--space-3);
}

.dialog-actions .btn {
  white-space: nowrap;
}

/* ===========================================================================
 * The served refusal page
 * ===========================================================================
 *
 * A page of its own, with no script — so it cannot be told which capability is
 * missing by anything that runs in it. It declares one line per capability the
 * product tests and the application navigates to the fragment naming the real
 * one, which `:target` reveals. No script, no template, and no plausible fake:
 * with no fragment nothing is shown, because nothing has said which. */

.st-missing {
  display: none;
  font-size: var(--type-small);
  line-height: 1.5;
  color: var(--muted);
}

.st-missing:target {
  display: block;
}

.st-refusal {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
}

.st-refusal-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.st-refusal-text > .t-lead {
  font-weight: 500;
}

.st-get {
  text-decoration: none;
}

/* ===========================================================================
 * The Map controller dialog
 * ===========================================================================
 *
 * NOCTURNE'S DIALOG WITH THREE THINGS CHANGED, and those three are the design's
 * own sentence: "`.dialog`, 590px wide (wider than Nocturne's 440 because the
 * panel is the content), surface `--well`, padding 16.8px, gap 8.4px with 5.6px
 * more before the panel and before the actions (the Leave/Quit dialog rhythm),
 * over the 62% `--board` scrim -- the same scrim as Leave."
 *
 * The padding, the gap, the radius, the shadow and the scrim are already what
 * `.dialog` and `.dialog-backdrop` set for the two confirmations, so the width
 * and the surface are the whole of what this adds. The surface is the one place
 * the three in-game dialogs differ, and the design says why: `--well` is "a
 * deliberate step darker so the light caps read as caps".
 *
 * EVERY FIGURE BELOW IS EITHER A TOKEN OR A MEASUREMENT OFF THE BOARD. The plate
 * is a drawn picture of a cabinet at one size -- the design states the 270px
 * plate, the 76px well, the 24px keys at radius 6, the 36px Start cap, the 56px
 * caps on a 90px pitch and the 48px ball outright -- so these are the design's
 * own numbers rather than one artboard's output at one window size. What is NOT
 * stated, and is derived instead, is where each control sits: the view works that
 * out from the manifest's kind and order and hands this the index and the arc. */

.dialog.st-map {
  width: min(590px, 100%);
  background: var(--well);
}

/* The title's own row. The board draws the title inside a flex line of its own --
 * a baseline row with the body's gap -- rather than as a bare child of the dialog,
 * which is what keeps a lift of this dialog matching the board element for element.
 * It is the same row recipe as the body's, one line up. */
.st-map-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
}

/* The body row: the line, and the count pushed to the end of it. The no-pad
 * board is the same row with a dot before the line, and it aligns to the top
 * rather than the middle because the dot belongs to the line's first line. */
.st-map-line {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.st-map-line[data-dot='waiting'] {
  align-items: flex-start;
}

/* THE ONE DOT IN THIS DIALOG, and it is `--waiting` rather than an alarm: a pad
 * that has not reported yet is not a failure, and the design withholds a dot
 * everywhere else here -- including from the browser that keeps nothing, where
 * "the player cannot fix it, so it is a fact, not an alarm". */
.st-map-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--waiting);
  margin-top: 7px;
}

/* `micro` at the HUD's tracking, which is the recipe the in-game slider labels
 * already take -- see the note beside `.st-hud .st-slider-label`. Tabular,
 * because the count changes in place as the walk moves. */
.st-map-count {
  margin-left: auto;
  font-size: var(--type-micro);
  font-weight: var(--weight-micro);
  letter-spacing: var(--track-tag);
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The control being asked for, inside the body line. The sentence is `--muted`
 * and the control's own word steps up to `--calm`, which is the only emphasis in
 * the dialog that is not a control lighting. */
.st-map-asked {
  font-weight: 500;
  color: var(--calm);
}

/* ---------------------------------------------------------------- the plate */

/* `--cabinet` AND NOT `--plate`. The design's own audit is explicit: `--plate` is
 * a scrim and the cabinet plate is its own name. Both resolve near enough that a
 * screen would not show the mistake, which is exactly why it is written down. */
.st-map-plate {
  /* THE TWO CAP DIAMETERS, STATED ONCE. They are the cabinet's own -- "a 36px
   * Start cap" and "three 56px caps" -- and they are properties rather than
   * literals because a `height: 36px` here is not the product's control height,
   * which is one number with one owner (`./controls.css`). Two figures that happen
   * to agree are exactly what a scan for that number cannot tell apart. */
  --st-map-cap-start: 36px;
  --st-map-cap-button: 56px;
  position: relative;
  height: 270px;
  margin-top: var(--space-2);
  border-radius: var(--r-window);
  background: var(--cabinet);
  box-shadow: 0 0 0 1px var(--line);
}

/* The stick: four switches around a 76px well, with the ball and the shaft over
 * them. NEITHER THE BALL NOR THE SHAFT EVER CHANGES STATE -- what a walk binds is
 * the switch, and a ball that lit would be claiming the stick was mapped. */
.st-map-stick {
  position: absolute;
  left: 44px;
  top: 78px;
  width: 128px;
  height: 128px;
}

.st-map-well {
  position: absolute;
  inset: 26px;
  border-radius: 50%;
  background: var(--cap);
  box-shadow: 0 0 0 1px var(--line-act);
}

.st-map-shaft {
  position: absolute;
  left: 60px;
  top: 52px;
  width: 8px;
  height: 24px;
  border-radius: 4px;
  background: var(--detail);
}

.st-map-ball {
  position: absolute;
  left: 40px;
  top: 40px;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--raised);
  box-shadow: 0 0 0 1px var(--detail);
}

/* A switch straddles the cluster's edge: 24px square, centred on its own axis,
 * with 10px of it outside the 128px box. That is why the four rules below are
 * two numbers and not eight coordinates. */
.st-map-key {
  position: absolute;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: var(--r-control);
}

.st-map-key[data-side='up'],
.st-map-key[data-side='down'] {
  left: calc(50% - 12px);
}

.st-map-key[data-side='left'],
.st-map-key[data-side='right'] {
  top: calc(50% - 12px);
}

.st-map-key[data-side='up'] {
  top: -10px;
}

.st-map-key[data-side='down'] {
  bottom: -10px;
}

.st-map-key[data-side='left'] {
  left: -10px;
}

.st-map-key[data-side='right'] {
  right: -10px;
}

/* One cap and its captions. The column is the same for Start and for a face
 * button; what differs is which slot the word is in, which the view decides, and
 * the two sizes and placements below. */
.st-map-seat {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.st-map-seat[data-cap='start'] {
  left: 190px;
  top: 24px;
}

/* THE ROW IS A PITCH, NOT THREE TYPED POSITIONS. One flex row at the crown's
 * height, 84px seats with a 6px gap, which is the 90px pitch the design states --
 * so a manifest with a fourth button gets a fourth cap and nothing here changes.
 *
 * AND THE ARC IS TWO PLACES RATHER THAN A LIFT PER CAP. The ends of the row sit
 * one step below its crown; 16px is that step, and it is the one figure in the row
 * that is still the board's. A per-cap lift would have to reach the element as a
 * custom property in a `style` attribute, which the served policy refuses. */
.st-map-row {
  position: absolute;
  left: 248px;
  top: 92px;
  display: flex;
  align-items: flex-start;
  gap: 6px;
}

.st-map-seat[data-cap='button'] {
  position: static;
  width: 84px;
}

.st-map-seat[data-arc='end'] {
  margin-top: 16px;
}

.st-map-cap {
  display: block;
  border-radius: 50%;
}

.st-map-seat[data-cap='start'] .st-map-cap {
  width: var(--st-map-cap-start);
  height: var(--st-map-cap-start);
}

.st-map-seat[data-cap='button'] .st-map-cap {
  width: var(--st-map-cap-button);
  height: var(--st-map-cap-button);
}

.st-map-letter {
  font-size: var(--type-small);
  font-weight: 500;
  letter-spacing: var(--track-tag);
  color: var(--muted);
}

.st-map-verbs {
  font-size: var(--type-micro);
  color: var(--muted);
  white-space: nowrap;
}

/* -------------------------------------------------- the five control states */

/* FIVE STATES AND ONE TABLE, keyed on the control's own attribute. The key and
 * the cap differ in exactly one state -- Pending, where a key has no fill and a
 * cap is `--cap` -- so every other row is one rule over both.
 *
 * ASKING IS THE ONLY LIT THING IN THE DIALOG, and its halo and glow are one
 * shadow in two parts: a 4px ring in `--accent-fill` and a 20px bloom of the
 * accent at 45%. ASKING-HELD DROPS BOTH: a binding commits on RELEASE, so the
 * control steps to `--accent-act` while the input is down and turns Assigned when
 * the player lets go. Without that step the press a player is making is the one
 * thing the panel does not show. */

.st-map-key[data-map-state='pending'] {
  color: var(--muted);
  box-shadow: 0 0 0 1px var(--line-hov);
}

.st-map-cap[data-map-state='pending'] {
  background: var(--cap);
  box-shadow: 0 0 0 1px var(--line-act);
}

.st-map-key[data-map-state='asking'],
.st-map-cap[data-map-state='asking'] {
  background: var(--accent);
  color: var(--ground);
  box-shadow:
    0 0 0 4px var(--accent-fill),
    0 0 20px color-mix(in srgb, var(--accent) 45%, transparent);
}

.st-map-key[data-map-state='asking-held'],
.st-map-cap[data-map-state='asking-held'] {
  background: var(--accent-act);
  color: var(--ground);
  box-shadow: none;
}

.st-map-key[data-map-state='assigned'],
.st-map-cap[data-map-state='assigned'] {
  background: var(--map-assigned);
  color: var(--calm);
  box-shadow: 0 0 0 1px var(--map-ring);
}

/* GREEN IS STATUS, NEVER IDENTITY. Actuated is Assigned with the mapped input
 * held, at any time the dialog is open -- so it is one step of the same green and
 * not a colour of its own. */
.st-map-key[data-map-state='actuated'],
.st-map-cap[data-map-state='actuated'] {
  background: var(--map-actuated);
  color: var(--calm);
  box-shadow: 0 0 0 1px var(--map-ring);
}

/* ----------------------------------------------------- the pips and the row */

/* The actions row is the pips at one end and the buttons at the other, which is
 * why it centres rather than taking `.dialog-actions`' own end alignment: the
 * spacer between them is what pushes the buttons right. */
.st-map .dialog-actions {
  align-items: center;
}

.st-map-gap {
  flex: 1;
}

.st-map-pips {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* ONE PIP PER CONTROL, LIT ONE PER ASSIGNED CONTROL. The radius is half the
 * height, which is a stadium rather than a corner, so it is the shape and not a
 * radius the design names. */
.st-map-pip {
  display: block;
  width: 16px;
  height: 4px;
  border-radius: 2px;
  background: var(--faint);
}

.st-map-pip[data-lit] {
  background: var(--accent);
}
