/* The controls: what the design system does not already give, and what a user
 * agent has to be told to stop doing.
 *
 * ===========================================================================
 * NOCTURNE OWNS THE CONTROL; THIS SHEET OWNS THE STATE THE PRODUCT ADDS
 * ===========================================================================
 *
 * "Every control in the product is one of Nocturne's classes with the state
 * values below. Set these once in the stylesheet, on the element and its
 * pseudo-classes, and never restyle per screen." So `.btn`, `.btn-primary`,
 * `.btn-secondary`, `.input`, `.seg`, `.seg-opt`, `.card`, `.tag` and `.dialog`
 * are the shipped bytes' and are not restated here. What is here is the four
 * things the product adds: the two retags, the states the design's own matrix
 * gives a product control, the status dot, and the slider.
 *
 * ===========================================================================
 * TWO RETAGS, AND THE FIRST ONE COST A DEFECT
 * ===========================================================================
 *
 * The deck draws the split control's halves as `<div>` and the slider as a
 * `<span>` with two `<i>` children. Neither can be pressed or dragged, because
 * an artboard has nothing to press — the design's own component table files the
 * slider as a "native range", and a half a person clicks is a button. So the
 * halves are `<button>` and the slider is `<input type="range">`.
 *
 * THE CARET CAME OUT STROKED IN BLACK. In the tree this is ported from, the
 * icon's `stroke: currentColor` resolved through a user agent's
 * `color: buttontext` on the retagged button, which beat inheritance, on a dark
 * ground. Three properties were taken back one at a time as each symptom bit,
 * and nobody had enumerated what else a user agent sets on a button. The owner
 * found it by looking at the app.
 *
 * So the reset below is the enumeration, done once: `color`, `font-family`,
 * `font-weight`, `line-height`, `border`, `background`, `margin`, `text-align`,
 * `text-transform`, `letter-spacing` and `appearance`.
 *
 * AND `font: inherit` IS NOT IN IT, THOUGH IT WAS. The shorthand is the tidy way
 * to take a font back from a user agent and it takes one property too many: it
 * resets `font-size` to the inherited value, which beats the shipped sheet's own
 * `.seg-opt { font-size: 13px }` -- same specificity, later in the cascade. So
 * both carets rendered at the shell's size where the design's table says `small`
 * 13 for seg labels, and the toggle halves were right only because they state a
 * size of their own.
 *
 * THIS IS THE BLACK-CARET INCIDENT'S SHAPE WITH THE VICTIM CHANGED. That one was
 * the user agent beating inheritance on a retagged button; this is our fix for it
 * beating the design system on the same element. A reset written as a shorthand
 * cannot say which of the properties it sets it meant, so this one is longhands
 * and `font-size` is not among them: `.seg-opt` takes the shipped 13px, and
 * `.st-device` states its own below because nothing shipped styles it. */

.seg-opt,
.st-device {
  appearance: none;
  color: inherit;
  font-family: inherit;
  font-weight: inherit;
  line-height: inherit;
  letter-spacing: inherit;
  text-align: inherit;
  text-transform: inherit;
  border: 0;
  background: transparent;
  margin: 0;
  cursor: pointer;
}

/* The picker row is a retagged button too, and the type table files it under the
 * same role: `small` 13, "Seg labels, device lines, captions". Stated here
 * because the shipped sheet has no rule for it to inherit one from. */
.st-device {
  font-size: var(--type-small);
}

/* ===========================================================================
 * ONE CONTROL HEIGHT, IN ONE PLACE
 * ===========================================================================
 *
 * "Control height is one number: 36px — buttons, fields, segs, combos, popup
 * rows and slider rows", and "Nocturne's own input height, no primary tier."
 *
 * Nocturne states it on `.input` (`min-height: 36px`) and nowhere else: its
 * `.btn` reaches 33.6px from padding and line-height, so the product has to say
 * the number. THIS IS THE ONLY PLACE IT SAYS IT. It was said in seven —
 * `.st-start .btn`, `.st-bar-right .btn`, `.st-hud .btn`, `#change-game`,
 * `.st-device`, `.dialog-actions .btn` and `.st-get` — which is one number
 * copied per screen, the same defect as a font-size typed off an artboard, and
 * six of the seven would have gone on being right by coincidence.
 *
 * `min-height` rather than `height`, because that is the form Nocturne's own
 * `.input` uses and the one the sentence calls "Nocturne's own input height".
 * The split control is the exception and the design states why: its halves are
 * 34px inside a 1px border, which is how it reaches the same 36.
 *
 * AND THE SLIDER ROW IS NOT IN THIS RULE, THOUGH THE SENTENCE NAMES IT. It was,
 * and the tree oracle measured the group at 36px against a board that draws it
 * 17.05 -- a label and a 6px track on one line inside a 52px bar, with nothing
 * around it to make 36 of. The sentence lists "slider rows" among the controls
 * that are one height and the deck does not draw one that way, which is a README
 * / deck disagreement of the kind Q11 already records; the standing rule is to
 * lift from the DECK, so the row takes the height the boards draw it at. */

.btn,
.st-device {
  min-height: 36px;
}

/* ===========================================================================
 * The split control
 * ===========================================================================
 *
 * "The control never changes width" and "opening must not move the toggle, so
 * the accent border replaces the hairline in place." Both are one rule: the
 * border is the same width open and closed, and only its colour moves. */

.seg {
  align-items: stretch;
  border-color: var(--line);
  transition: border-color 90ms linear;
}

.seg[data-open='true'] {
  border-color: var(--accent);
}

/* No `font-size` here either, and for the same reason: the shipped `.seg-opt` is
 * already 13px, which is what the table asks for. It was stated, and was correct,
 * and was hiding the caret's bug -- one half right by its own statement and the
 * other wrong by the reset above them both. */
.seg-opt[data-half='toggle'] {
  gap: var(--space-3);
  padding: 0 var(--space-4);
  height: 34px;
}

/* THE COLOUR IS ON THE WORD, NOT ON THE HALF, and the deck is why. Both boards
 * that draw this control draw the half with no colour at all and `color:
 * var(--calm)` on the span holding the word — so the half INHERITS its band, and
 * the two bands differ: the lobby bar is `--calm` and the in-game HUD is
 * `--muted`.
 *
 * Stated on the half, `--calm` was right on the lobby boards and overrode the
 * HUD's muted on all three in-game widths. The same defect as the overlay band's
 * "one recipe for its two leaders", one control further in: a property lifted
 * from the board it was read on, applied to the element above the one that
 * carries it. */
.st-seg-word {
  color: var(--calm);
}

/* The caret half is 32px wide, which with the 1px border either side is the
 * product's one control height. "36px total in both bars (34px options inside
 * the 1px border) — one recipe, never a hand-rolled wrapper." */
.seg-opt[data-half='caret'] {
  justify-content: center;
  width: 32px;
  height: 34px;
  padding: 0;
  color: var(--muted);
}

/* ===========================================================================
 * The chip, which the product had never overridden
 * ===========================================================================
 *
 * The type table's `tag` row is "10 | 400, uppercase `+.1em`" and Nocturne ships
 * `.tag` at `font-size: 11px; letter-spacing: 0.02em`. Nothing here said
 * otherwise, so every HOST chip in the product drew one pixel too large with a
 * fifth of the tracking -- 23.05px tall against the 21.5 every board draws, and
 * the same 1.55px on five boards.
 *
 * THE DECK SPELLS IT INLINE ON BOTH VARIANTS, which is how it was found and what
 * it is held against: the tile's chip is `font-size:10px;letter-spacing:.1em`
 * over Nocturne's padding, and the Ports row's adds `padding:1px 7px`, giving the
 * 47.91x19.5 the board draws there. Two recipes, so two rules. */

.tag {
  font-size: var(--type-tag);
  letter-spacing: var(--track-tag);
}

/* The Ports row's chip, all four declarations the board spells on it. Nocturne's
 * `.tag-outline` is a full-strength accent border and accent text; the board
 * mixes the border down to 55% and takes the ink to `--accent-act`.
 *
 * THE COLOURS WERE ALWAYS WRONG AND NOTHING COULD SEE THEM. At 11px the chip was
 * 53.58x25.05 against a drawn 47.91x19.5, too far out to pair, so the ink oracle
 * never had two elements to compare and reported nothing. Sizing it correctly is
 * what made the colour visible -- a fix uncovering what its own absence was
 * hiding, which is worth expecting whenever a geometry defect clears. */
.tag-outline {
  padding: 1px 7px;
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--accent-act);
}

/* ===========================================================================
 * The status dot: state is the dot, not the word
 * ===========================================================================
 *
 * "`--good` with a glow for live, an unlit `--faint-fill` disc for off,
 * `--waiting` when the chosen device vanished and we fell back to default."
 * Three states, one element, and the word beside it never changes. */

.st-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: 0 0 auto;
  transition:
    background 90ms linear,
    box-shadow 90ms linear;
}

.st-dot[data-state='live'] {
  background: var(--good);
  box-shadow: 0 0 8px 1px color-mix(in srgb, var(--good) 60%, transparent);
}

.st-dot[data-state='off'] {
  background: var(--faint-fill);
}

.st-dot[data-state='fallback'] {
  background: var(--waiting);
}

/* The join footer's dot, which is the one relay dot in the product: it is what
 * stops a bad code and an unreachable relay from ever looking alike. */
.st-dot-plain {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: 0 0 auto;
}

.st-dot-plain[data-tone='good'] {
  background: var(--good);
}

.st-dot-plain[data-tone='waiting'] {
  background: var(--waiting);
}

.st-dot-plain[data-tone='wrong'] {
  background: var(--wrong);
}

/* ===========================================================================
 * The slider
 * ===========================================================================
 *
 * "4–6px track `--faint-fill`, accent fill, 14px `--calm` handle with
 * `--handle-edge`." The fill is the one live value on the control and arrives as
 * a custom property the view sets, which is a value handed to the stylesheet
 * rather than styling written in the markup. */

/* THE SLIDER ROW IS TWO RECIPES, AND THIS RULE HAS NOW BEEN WRONG BOTH WAYS.
 *
 * The in-game HUD draws `display:flex;align-items:center;gap:8.4px` and nothing
 * else; the lobby's self bar draws `gap:11.2px;margin-left:16.8px`. The rule was
 * first the HUD's numbers with the margin missing, which put the group 16.8px
 * left on the self bar; then, correcting that from the self bar's board alone, it
 * became the self bar's numbers for everybody, which put the group 16.8px RIGHT
 * on all three in-game boards and accounted for 24 of the 28 differences left in
 * the tree.
 *
 * Twice, on one element, from the same mistake: reading the board where the
 * divergence was reported instead of every board that draws the thing. The base
 * is the plainer of the two and the self bar's margin is the exception, and both
 * are entries in the lifted table now, so a third correction from one board is
 * red rather than a round. */

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

.st-self-bar .st-slider-group {
  gap: var(--space-4);
  margin-left: var(--space-6);
}

.st-slider-label {
  font-size: var(--type-micro);
  font-weight: var(--weight-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--muted);
}

/* AND THE THIRD PLACE THIS ONE CONTROL IS TWO RECIPES. The self bar's label is
 * the type table's `micro` at `+.12em`; the in-game HUD's is drawn `+.1em`, which
 * over four uppercase characters at 11px is 0.88px -- the whole of the 0.89px the
 * second slider group still sat right of its board after the gap and the margin
 * were split. A tracking difference that moves a box, which is the kind the ink
 * oracle and the tree oracle each only half see.
 *
 * The table's value is the base and the HUD is the exception, the same way round
 * as the gap above it. Two boards draw `.12em` and three draw `.1em`, so this is
 * also a README-against-deck disagreement in the making if the type table is ever
 * read as covering both bars; the standing rule is the deck, per board. */
.st-hud .st-slider-label {
  letter-spacing: var(--track-tag);
}

/* 140px is the self bar's track, not 96, and the prose gives every number except
 * this one: "4-6px track `--faint-fill`, accent fill, 14px `--calm` handle".
 *
 * AND THERE ARE TWO TRACKS. The deck draws 140 in the self bar (`lobbyFull`,
 * `loadingDownload`) and 96 in the in-game HUD (`playing`, `laptop`, `floor`).
 * This said 96 until the round that read the lobby board, made it 140, fixed the
 * self bar and moved all three HUDs -- the "one recipe where the deck draws two"
 * defect committed one round after being written down as a rule, because the
 * board in front of me was checked and its siblings were not. The HUD's width is
 * scoped below and both are held against their own boards. */
.st-slider {
  appearance: none;
  width: 140px;
  height: 6px;
  margin: 0;
  border-radius: 3px;
  background: linear-gradient(
    to right,
    var(--accent) 0 var(--st-fill),
    var(--faint) var(--st-fill) 100%
  );
  cursor: pointer;
}

.st-slider::-webkit-slider-thumb {
  appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--calm);
  box-shadow: var(--handle-edge);
  border: 0;
}

.st-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--calm);
  box-shadow: var(--handle-edge);
  border: 0;
}

/* The in-game HUD's track: 96px on all three boards that draw it. */
.st-hud .st-slider {
  width: 96px;
}

.st-slider:hover {
  background: linear-gradient(
    to right,
    var(--accent-hov) 0 var(--st-fill),
    var(--faint) var(--st-fill) 100%
  );
}

/* ===========================================================================
 * The two red controls, and the one opacity
 * ===========================================================================
 *
 * "Leave match, End match — the only red controls in the product, the two
 * commits that take something from the other players. Outline, never fill." And
 * the HUD control behind an open dialog carries the same hairline, "so the
 * control and its consequence read as one object" — which is why this is an
 * attribute on the control rather than a class the dialog adds. */

.btn[data-commit='destructive'],
#leave-match[data-dialog-open],
#quit[data-dialog-open] {
  border-color: color-mix(in srgb, var(--wrong) 55%, transparent);
  color: var(--wrong);
}

/* The dialog's own commit is the full-strength hairline: the board draws the
 * originating control mixed down and the commit at the token. */
.dialog-actions .btn[data-commit='destructive'] {
  border-color: var(--wrong);
}

/* "One opacity exists in the product: .45, and only on a disabled control."
 * Nocturne already paints `.btn:disabled`; this is the rest of what the product
 * disables, so there is one number and one place. */
/* AND THE LAST SELECTOR IS NOT A CONTROL. The Map controller dialog's panel is
 * dimmed while no pad is reporting, and the design names the treatment rather than
 * describing it: "No pad found. Panel at 45% opacity -- the disabled treatment."
 * So it takes the one number from the one place instead of writing a second .45
 * into the anatomy sheet. It is the only dimmed panel there is -- a refused gesture
 * has no treatment, and storage-refused is a line rather than a dimming. */
.input:disabled,
.st-device:disabled,
.st-slider:disabled,
.st-map-plate[data-no-pad] {
  opacity: 0.45;
  cursor: default;
}

/* ===========================================================================
 * The field's own states, from the design's state matrix
 * ===========================================================================
 *
 * "Code valid: border `--accent` + 3px accent glow @18%." The matrix says the
 * field is otherwise "`.input` as it comes — no override", so nothing here
 * restates its rest state. */

.input[data-state='found'] {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}

/* "the room-code field is 18px `+.3em` tabular, **inside an ordinary 36px
 * `.input`**", and "`.input` at its native 36px, text 18px `+.3em` tabular".
 *
 * THE LEADING IS WHY IT WAS NOT 36. The shipped `.input` is `min-height: 36px`
 * with 6px of padding, which reaches 36 at the body's 14px text -- and 18px text
 * at the body's 1.55 leading is 27.9, so the field grew to 41.89 and took the
 * whole join column 5.89px with it. Every child below it moved, and the board's
 * own recipe for the same box is `height:36px` flat.
 *
 * `line-height: 1` rather than a second `height: 36px`: the growth is the leading
 * and not the height, the design says one control height and means one statement
 * of it, and a field of tabular digits on one line has no use for prose leading.
 * The shipped `min-height` then does exactly what it is for. */
.input[data-field='roomcode'] {
  font-size: var(--type-roomcode);
  line-height: 1;
  letter-spacing: var(--track-roomcode);
}

.input::placeholder {
  color: var(--dim);
}

/* ===========================================================================
 * The spinner, which is an animated asset rather than a drawn ring
 * ===========================================================================
 *
 * The deck marks it animated, and a document being measured winds every
 * animation to time zero — which shows a spinner's FIRST frame, an honest
 * picture of it. A transition wound to zero would show the value it is leaving,
 * which is why the transitions above are on colour alone and short. */

@keyframes st-spin {
  to {
    transform: rotate(1turn);
  }
}

.st-spinner {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--accent);
  border-top-color: transparent;
  flex: 0 0 auto;
  animation: st-spin 1s linear infinite;
}

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