/*
 * Design tokens.
 *
 * `settings.allium`'s Appearance surface decides which of these apply: theme
 * follows the device until the player chooses, and high contrast — the setting
 * or the device, whichever asked — swaps the correct and present colours for
 * the orange and blue pair that `sharing.allium` also uses for its emoji grid.
 * Colour never carries meaning on its own: every mark has a shape and an
 * accessible description too.
 *
 * One shape governs all four combinations of theme and high contrast, and it
 * is what `AnUntriedKeyIsDistinguishableFromAScoredOne` costs. **A key nothing
 * is known about hugs the page and is drawn by its border; a scored key steps
 * away from the page** — dark on white, pale on near-black — **and its letter
 * is painted in the page's own extreme.** Nothing else spends the lightness
 * range on the distinction the player actually reads each turn, and the
 * distinction survives a screen dimmed far enough to take the hues with it.
 *
 * That inverts the marks between themes, which the palette could not do while
 * `sharing.allium` was read as tying them to the emoji. It no longer is: the
 * emoji "are whatever the reader's platform draws, and never were the board's
 * own colours". What follows high contrast is which palette is in use.
 *
 * Every figure in this file is enumerated by `tests/contrast.test.ts` over all
 * four combinations, against the four floors `game.allium` states and
 * `src/lib/config.ts` mirrors. None of them is a comment to be trusted: change
 * a colour and the gate recomputes the lot.
 */

:root {
  color-scheme: light dark;

  --background: #ffffff;
  --text: #1a1a1b;
  --muted: #5f6368;
  --focus: #0b57d0;

  /*
   * Dark marks, and white on them rather than black. On a white page the marks
   * are what steps away from it, and a mark dark enough for an untried key to
   * stand 3 to one off it is far too dark to carry black text. White measures
   * 10.7, 6.3 and 4.8 to one on the three; black would measure 1.9 on correct.
   */
  --mark-correct: #154725;
  --mark-present: #7a5a1a;
  --mark-absent: #6f7378;
  --mark-text: #ffffff;

  --tile-background: transparent;
  /*
   * The same tone as the key border, and for the same reason: an empty tile
   * outlined at 1.34 to one against white was a grid that had to be inferred.
   * A tile is not a control, so `EveryCombinationMeetsTheLegibilityFloor` does
   * not reach it — this is the floor applied where the guarantee does not
   * compel it, because the board is the other half of the same complaint.
   */
  --tile-border: #818a94;
  --tile-text: var(--text);

  /*
   * Nearly the page, and visible by its border. That is the whole trick: the
   * untried key gives up standing off the background — 1.16 to one, carried by
   * the border at 3.5 — and spends all of it standing off the marks instead,
   * at 4.1 on absent and better on the other two. The pair this exists for was
   * 2.89 before, and 1.23 in the dark theme.
   */
  --key-background: #edeef0;
  --key-border: #818a94;
  --key-text: var(--text);
}

/*
 * The dark theme is reached two ways — by the device while no choice is
 * recorded, and by an explicit choice — and both must carry the same values.
 * `tests/contrast.test.ts` can only drive the attribute; jsdom answers no media
 * query, so the block below is the one the gate never sees. Keep them in step
 * by hand, and change neither alone.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --background: #121213;
    --text: #f5f5f5;
    --muted: #a0a4a8;
    --focus: #a8c7fa;

    --mark-correct: #88ce96;
    --mark-present: #c59420;
    --mark-absent: #797f86;
    --mark-text: #000000;

    --tile-border: #6f7378;

    --key-background: #212326;
    --key-border: #6f7378;
  }

  :root:not([data-theme='light'])[data-high-contrast='true'] {
    --mark-correct: #f8af7d;
    --mark-present: #53a0ec;
  }
}

/*
 * `color-scheme` is set once on bare `:root` and never overridden, so an
 * explicit choice would flip the custom properties while the user agent kept
 * painting scrollbars, form controls and system colours the other way. These
 * two declarations make the choice reach the platform as well as the palette.
 */
:root[data-theme='light'] {
  color-scheme: light;
}

:root[data-theme='dark'] {
  color-scheme: dark;

  --background: #121213;
  --text: #f5f5f5;
  --muted: #a0a4a8;
  --focus: #a8c7fa;

  /*
   * Pale marks and black on them, the light theme's arrangement turned over.
   * The untried key is the dark one here, at #212326 against a #121213 page,
   * so what a scored key does is step out into the light: absent at 3.9 to one
   * from an untried key, correct at 7.9, and 2.2 between those two.
   */
  --mark-correct: #88ce96;
  --mark-present: #c59420;
  --mark-absent: #797f86;
  --mark-text: #000000;

  --tile-border: #6f7378;

  --key-background: #212326;
  --key-border: #6f7378;
}

/*
 * High contrast is a second palette that has to clear the same bar, and it is
 * two tokens because absent and the untried key already clear it in both. The
 * theme decides the lightness the pair has to hit and this decides the hue, so
 * there is a block per theme rather than one that spans both — the orange that
 * a light theme needs is not the orange a dark one does.
 *
 * Order matters and is deliberate. This block is `(0,2,0)`, the same as
 * `[data-theme='dark']`, so it sits after it to win at equal weight; the dark
 * pair below is `(0,3,0)` and wins outright. The two dark paths above and here
 * both carry their own high-contrast block for exactly that reason.
 */
:root[data-high-contrast='true'] {
  --mark-correct: #682d0f;
  --mark-present: #265eb1;
}

:root[data-theme='dark'][data-high-contrast='true'] {
  --mark-correct: #f8af7d;
  --mark-present: #53a0ec;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--background);
  color: var(--text);
  font-family:
    system-ui,
    -apple-system,
    'Segoe UI',
    sans-serif;
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/*
 * `game.allium` — contract DirectManipulation. Ten surfaces fulfil it and not
 * one of them owns it, which is why the rules sit here rather than in any one
 * component. Everything below is measured by `tests/directManipulation.test.ts`
 * and by the width stories in `stories/Keyboard.stories.svelte`.
 *
 * ATapDoesOnlyWhatTheControlDoes. `manipulation` declines the platform's guess
 * at what a second tap meant, which is what makes fast repeated tapping on the
 * keyboard ordinary play rather than a gesture; the rest stop a held finger
 * selecting a key's label or raising a callout over it. It is deliberately not
 * `touch-action: none`, which would take the pinch as well —
 * DeliberateZoomIsNeverTakenAway says the first invariant is not licence for
 * that, and `app.html` leaves the viewport scalable for the same reason.
 */
button,
label,
input[type='checkbox'],
input[type='radio'] {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/*
 * The same invariant, and the one declaration of it a text control can take. A
 * second fast tap in the link field or the shared grid is a caret placed twice,
 * never a zoom, so `manipulation` belongs here as much as it does on a key.
 *
 * The other four are deliberately absent rather than forgotten. `sharing.allium`
 * asks in TheGridIsAvailableAsText for the grid to be "selected by hand before
 * it is sent", and on a phone the callout is how a selection is copied — so
 * suppressing either would satisfy this contract by spending that guarantee.
 * Leaving the tap highlight alone is the same decision seen from the other side:
 * nothing here removes the platform's acknowledgement, so nothing here owes a
 * replacement, and the caret and the focus outline arrive on contact anyway.
 */
input[type='text'],
textarea {
  touch-action: manipulation;
}

/*
 * DeliberateZoomIsNeverTakenAway, from the other direction: below 16px iOS
 * Safari magnifies the page when an input takes focus, which is the platform
 * zooming on its own initiative rather than the player asking. Every text
 * control in the app already says this in its own scoped styles, so this rule
 * changes nothing on the screen today; what it changes is that a control which
 * arrives without them inherits the body's figure instead of the user agent's
 * smaller one. Measured in Chromium by `stories/LinkReady.stories.svelte`,
 * because jsdom's default input font is already 16px and an assertion there
 * would pass whether or not this rule existed.
 */
input,
textarea {
  font: inherit;
}

/*
 * EveryControlIsAComfortableTarget, top to bottom, where every control meets the
 * figure outright. 44px is `game.allium`'s `config.minimum_touch_target`;
 * `MINIMUM_TOUCH_TARGET` in `src/lib/config.ts` is the same number, and the test
 * compares this resolved value against it so the two cannot drift apart.
 *
 * Across is deliberately not declared. The on-screen keyboard is the one place
 * the figure cannot be met in that direction, so a floor here would be wrong for
 * a key and would have to be fought back inside `Keyboard.svelte` — the fight
 * that, lost, is the sideways scroll this rule exists alongside. Every other
 * control carries a label that takes it past the figure on its own, and the
 * story run measures that rather than assuming it.
 *
 * A text control is included on purpose: a 37px input is as much a target as a
 * 37px button. It takes only part of the first rule above, and for the opposite
 * reason — `ShareResults` has to stay selectable by hand.
 */
button,
input[type='text'],
textarea {
  min-block-size: 44px;
}

/*
 * ATouchIsAcknowledged. The rule above removes the platform's own tap flash, and
 * removing it without replacing it leaves a control that reads as dead under
 * exactly the finger this contract exists for.
 *
 * The page's own ink drawn inside the key, backed by its own paper. Two tones
 * rather than one because a key is not one colour: a plain key, three marks and
 * the two the high-contrast palette replaces make six backgrounds per theme and
 * twelve in all, and no single tone stands off every one of them. Ink and paper
 * between them always do, because the marks are placed by how far they sit from
 * the page and one of the two is therefore always at the far end. The narrowest
 * of the twelve edges is 4.63 to one, on the dark theme's absent mark, well past
 * the 3.0 a control's boundary answers to — and `tests/contrast.test.ts`
 * recomputes all twelve rather than trusting this sentence.
 *
 * A ring rather than a filter, which was the first thing tried. `filter` dims
 * the letter along with the key, which costs
 * ResultsAreNeverConveyedByColourAlone the contrast that clause depends on —
 * not a trade that was on offer. A shadow paints over the background and under
 * the content, so the letter is untouched at every value.
 *
 * No transition and no animation: the acknowledgement "owes nothing to whether
 * animations are running", so `data-animations` must not reach it the way it
 * reaches the tile reveal. `box-shadow` is set by no component, so the cue is
 * not something a key can override by accident.
 *
 * Two selectors, because the first rule in this block suppresses the platform's
 * flash on four kinds of control and a replacement owed to one of them is owed
 * to all. The row rather than the box: `SettingsPanel` makes the `<label>` the
 * 44px target precisely because a label bound to its control activates it across
 * its whole area, so the row is what the finger is aimed at and the row is what
 * has to answer. A ring drawn on the native box instead would be a cue on the
 * 13px thing nobody is aiming at, and inset shadow on a native checkbox is the
 * platform's to paint or ignore.
 *
 * `:has(input:not(:disabled))` is the same carve-out `:not(:disabled)` makes on
 * the button half: the disabled hard-mode row must not report a touch that does
 * nothing. It also settles which labels are in scope. A label bound by `for` —
 * `CustomGameForm`, `LinkReady`, `ResultsReady` — matches neither selector and
 * needs no ring, because pressing it moves focus into the field and the focus
 * outline above is the acknowledgement, arriving on the control that took the
 * tap rather than on the words pointing at it.
 */
button:active:not(:disabled),
label:has(input:not(:disabled)):active {
  box-shadow:
    inset 0 0 0 2px var(--text),
    inset 0 0 0 4px var(--background);
}

/*
 * Present to assistive technology, absent to the eye. The live region uses it,
 * because what it says is already on the screen in another form: the marks are
 * on the board and the conclusion is in the modal.
 */
.visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
