/* Birdle M4 — WORDLE'S VISUAL SYSTEM (D-60, Brock 2026-10-27).
   Rev 16 was a teal/slate theme on a rounded system-UI font with chips and
   pills; the ask was to read as Wordle and Poople. So the palette, the type,
   the square hard-edged tiles, the bordered centre-titled header, the
   full-width grey keyboard and the black toast are Wordle's, taken from its
   published theme rather than approximated by eye.

   THREE OF WORDLE'S OWN COLOURS DO NOT MEET WCAG AA, measured, so three values
   are the nearest hue-preserving substitute rather than the original. (It was
   four: Wordle's yellow needed ink rather than white letters. The yellow tile
   no longer exists -- D-65 -- so that row went with the token.)

     light --dim    #787c7e -> #707476   4.21 -> 4.72 on white   (4% darker)
     light --absent #787c7e -> #707476   same colour, as a fill
     light --good   #6aaa64 -> #64a05e   2.78 -> 3.12 on white   (4% darker)
     dark --key     #818384 -> #6e7072   3.81 -> 4.97 with white  (15% darker)
                    This one is NOT a cosmetic nicety. The letter caps are 20px
                    bold and pass at 3.81 under the large-text allowance, but
                    `del`, `go`, Close and Sign in are 13px bold on the SAME
                    grey. Darkening the grey once fixes all of them and leaves
                    white text uniform, where exempting the small ones would
                    have meant a two-tone keyboard. At #6e7072 nothing here
                    depends on the large-text allowance at all -- and THAT is
                    the live claim, re-measured by axe on every run.
                    (SEVEN is a count of VIOLATION RECORDS across the 14 scans,
                    NOT of elements. Removing Undo -- D-71 -- deleted one of the
                    13px caps listed above and the count did NOT move: mutant MN
                    reverts this token and still reds SEVEN today. Worth stating
                    because the obvious reading of the sentence above is one
                    violation per control, and that reading would have had me
                    write `six` here off an element census. The number is
                    measured by the mutant, never typed.)

   Everything else is Wordle's literal value. The only pairs that lean on the
   WCAG large-text allowance (3:1) are white-on-green: the tile letters
   (0.54 x the 36px floor = 19.4px) and the two green buttons (20px), both at
   weight 700, which is the condition. `tools/test-a11y.mjs` re-measures all of
   it across both schemes and seven app states rather than trusting this
   comment -- it is what caught the 16px Copy button at 3.11:1 and the seven
   dark-mode key/button failures, neither of which the palette audit saw.

   Mobile-first and one-handed: everything interactive sits in the lower half
   of the viewport and the ladder grows upward from the input. */
:root {
  --bg: #ffffff;
  --panel: #ffffff;
  --edge: #d3d6da;          /* wordle tile border / key face */
  --ink: #1a1a1b;
  --dim: #707476;           /* wordle #787c7e, 4% darker for 4.5:1 */
  --good: #64a05e;          /* wordle #6aaa64, 4% darker for 3:1 on white */
  --absent: #707476;        /* wordle #787c7e, 4% darker */
  --key: #d3d6da;
  --on-key: #1a1a1b;
  --on-fill: #ffffff;       /* letters on --good / --absent */
  --toast: #1a1a1b;
  --on-toast: #ffffff;
  --link: #1f6f93;
  --radius: 4px;            /* wordle keys; TILES ARE SQUARE */
  --tap: 44px;
  --font: "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121213; --panel: #121213; --edge: #3a3a3c; --ink: #d7dadc; --dim: #878a8c;
    --good: #538d4e; --absent: #3a3a3c;
    --key: #6e7072; --on-key: #ffffff; --on-fill: #ffffff;
    --toast: #ffffff; --on-toast: #1a1a1b; --link: #85c9e8;
  }
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.4 var(--font);
  display: flex;
  flex-direction: column;
  padding-bottom: env(safe-area-inset-bottom);
  -webkit-text-size-adjust: 100%;
}
/* The sign-in popup renders one line of text and no game. */
body.bare { align-items: center; justify-content: center; text-align: center; padding: 24px; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; left: -999px; top: 0; z-index: 10;
  background: var(--ink); color: var(--bg); padding: 10px 14px;
}
.skip:focus { left: 0; }

/* ---- header: Wordle's centred, letterspaced, bordered bar ---- */
header {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  height: 50px; padding: 0 8px;
  border-bottom: 1px solid var(--edge);
  background: var(--panel);
  flex: none;
}
h1 {
  margin: 0;
  font-size: clamp(1.5rem, 7vw, 2.0625rem);   /* wordle 33px */
  font-weight: 700;
  letter-spacing: .01em;
  line-height: 1;
  text-transform: uppercase;
}
.hdr-right { position: absolute; right: 8px; top: 0; height: 100%; display: flex; align-items: center; gap: 2px; }
.icon {
  border: 0; background: transparent; color: var(--ink);
  min-width: var(--tap); min-height: var(--tap); padding: 0;
  cursor: pointer; font-size: 1.25rem; line-height: 1;
}
.icon:hover { color: var(--dim); }

main {
  flex: 1; display: flex; flex-direction: column; min-height: 0;
  width: 100%; max-width: 500px; margin: 0 auto; padding: 6px 8px 0; gap: 6px;
}

/* ---- mode switch: Wordle has none, Poople does. Kept minimal. ---- */
.modes { display: flex; justify-content: center; gap: 4px; flex: none; }
.mode {
  border: 1px solid transparent; background: transparent; color: var(--dim);
  font: inherit; font-size: .8125rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; padding: 5px 12px; border-radius: var(--radius);
  cursor: pointer; min-height: 32px;
}
.mode.on { background: var(--ink); color: var(--bg); }

/* ---- the goal line ---- */
.goal {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin: 0; font-size: .8125rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--dim); flex: none; flex-wrap: wrap;
}
.goal b { color: var(--ink); }
.goal .arrow { color: var(--dim); }

/* ---- the ladder. Square hard-edged tiles, 2px borders: Wordle's board.
   THE BOARD IS FITTED, NOT FIXED (D-61). Wordle sizes its board to the space it
   has; a fixed tile does not survive a ladder that grows. At the Wordle tile
   size twelve rungs need 684px and the ladder area measures 488 on a 390x844
   phone, 302 at 360x640 and 544 on a 1280x900 desktop -- so the collapse
   control sat at viewport top -110 / -224 / -54 and was unclickable at ALL
   THREE, desktop included. `fitLadder()` in app.js sets --cell from the
   measured space and the rung count; this is the fallback for the one frame
   before it runs. `overflow: hidden` is what keeps a clipped rung from painting
   over the goal line, which it was doing (first rung top: -69). */
/* THE BOARD IS TOP-ANCHORED (D-66, Brock 2026-10-27). It used to be
   `justify-content: flex-end`, which pinned the rungs to the BOTTOM of a
   flex: 1 region -- so a two-rung ladder left the whole board area empty above
   it and the game opened on a void under the goal line. Wordle and Poople both
   start their board directly under the header and grow DOWNWARD; the slack
   belongs between the board and the keyboard, where it reads as Wordle's
   unplayed rows rather than as a missing element. The one-handed rationale the
   old value carried does not survive it: the input and keyboard are still
   bottom-anchored by `main`'s own flex, which is what actually puts them under
   the thumb. */
.ladder-wrap {
  flex: 1; display: flex; flex-direction: column; justify-content: flex-start;
  min-height: 0; gap: 5px; overflow: hidden;
}
/* EXPANDING MUST BE ABLE TO SHOW WHAT IT REVEALS. `overflow: hidden` is right
   for the collapsed board -- it is what stops a clipped rung painting over the
   goal line -- but applied to the expanded one it would hide the rungs the
   player just asked to see, with no way to reach them. Scrolling is only ever
   offered on an explicit expand. */
.ladder-wrap.expanded { overflow-y: auto; }
/* THE FALLBACK LIVES ON THE ANCESTOR, NOT THE CELL. Declaring `--cell` on
   `.cell` shadows the inherited value -- a custom property set on an element
   beats inheritance -- so `fitLadder()`'s value on #ladder was silently ignored
   and the tiles stayed 52px while the row count shrank around them. Measured:
   rows fell to 11/6/12 and `cell` never moved. */
.ladder {
  --cell: 52px;
  list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px;
}
.rung { display: flex; gap: 5px; justify-content: center; }
.cell {
  width: var(--cell); height: var(--cell); display: grid; place-items: center;
  /* LARGE TEXT, AND THE RATIO IS WHAT GUARANTEES IT. 0.54 x the 36px floor
     app.js enforces is 19.4px at weight 700, which clears the 18.66px-bold bar
     -- the condition that lets --on-fill sit on --good at 3.12:1 (light) /
     3.97:1 (dark). A smaller floor would quietly forfeit that allowance, which
     is why MIN_CELL and this ratio are a pair and neither moves alone. */
  font-size: calc(var(--cell) * .54); font-weight: 700; line-height: 1;
  text-transform: uppercase;
  border: 2px solid var(--edge); background: transparent; color: var(--ink);
  user-select: none;
}
/* The START rung is GIVEN, not played, so it keeps Wordle's empty-tile look:
   outline only. Filling it green where it already matches would read as a move
   the player made. */
.rung.start .cell { border-color: var(--edge); background: transparent; color: var(--ink); }
/* TWO TILE STATES (D-65). Yellow -- "the letter you changed, and it missed" --
   was a mark about the player's action rather than about the puzzle, and the
   rule it encoded is unreadable in a four-letter ladder with a fixed target.
   The `--near` / `--on-near` tokens went with it rather than being left
   declared and unused, which is the only way the contrast audit's token sweep
   stays a census of what actually ships. */
.cell.hit { background: var(--good); border-color: var(--good); color: var(--on-fill); }
.cell.none { background: var(--absent); border-color: var(--absent); color: var(--on-fill); }
.collapsed {
  align-self: center; background: transparent; border: 0;
  color: var(--dim); font: inherit; font-size: .75rem; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; padding: 4px 10px;
  cursor: pointer; min-height: 32px; text-decoration: underline;
}

/* ---- the toast: Wordle's inverted pill, centred over the board ---- */
.message {
  position: fixed; left: 50%; top: 68px; transform: translateX(-50%);
  z-index: 20; max-width: min(90vw, 420px);
  margin: 0; padding: 14px 18px; border-radius: var(--radius);
  background: var(--toast); color: var(--on-toast);
  font-size: .875rem; font-weight: 700; text-align: center;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .25);
}
.message:empty { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .message { animation: pop .1s ease-out; }
  @keyframes pop { from { opacity: 0; transform: translateX(-50%) scale(.94); } }
}
/* In the dialog the status line is inline copy, not a floating toast. */
dialog .message {
  position: static; transform: none; max-width: none; box-shadow: none;
  background: transparent; color: var(--dim); font-weight: 400;
  padding: 6px 0 0; animation: none; min-height: 1.3em;
}
dialog .message:empty { display: block; }

#input-form { display: flex; gap: 5px; flex: none; }
#input {
  flex: 1; min-width: 0; min-height: var(--tap);
  font-family: var(--font); font-size: 1.25rem; font-weight: 700;
  letter-spacing: .3em; text-align: center; text-transform: uppercase;
  background: var(--panel); color: var(--ink);
  border: 2px solid var(--edge); border-radius: var(--radius);
}
#input::placeholder { letter-spacing: .3em; color: var(--edge); text-transform: none; }
button { font: inherit; }
#input-form button, .row button {
  min-height: var(--tap); padding: 0 14px; border-radius: var(--radius);
  border: 0; background: var(--key); color: var(--on-key);
  font-size: .8125rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  cursor: pointer;
}
/* 1.1875rem = 19px at weight 700, which IS large text (>= 18.66px bold) and is
   what lets white sit on --good here. At 1rem axe measured #copy at 3.11:1 and
   failed it against the 4.5:1 normal-text threshold -- correctly. Wordle's own
   button type is 20px bold uppercase, so the accessible size is also the
   closer-to-Wordle size. */
/* `#input-form #submit`, NOT `#submit` (D-73). The two green buttons are
   declared IDENTICALLY -- this line and `#copy` below are the same three
   declarations -- and only one of them ever applied: the shared rule reaches
   the form by ID (`#input-form button`, 0-1-0-1) and the row by CLASS
   (`.row button`, 0-0-1-1), so it OUTRANKS `#submit` (0-1-0-0) and LOSES to
   `#copy`. Enter shipped grey at 13px from Rev 17 while the comment above it
   explained its green. An accident of selector form, not a decision; the e2e
   now asserts the two buttons render the same fill so it cannot die silently
   again. */
#input-form #submit { background: var(--good); color: var(--on-fill); font-size: 1.25rem; }
/* No `button:disabled` rule. `#undo` was the only control the app ever
   disabled, and with it gone (D-71) nothing in the DOM can match -- an
   unreachable declaration is dead code on the same ground this project deletes
   an unreachable guard. `verify-figures` legs the absence. */
:focus-visible { outline: 3px solid var(--link); outline-offset: 2px; }

/* ---- keyboard: Wordle's grey caps, 4px radius, half-key row inset ---- */
.keyboard { display: flex; flex-direction: column; gap: 6px; padding: 6px 0 8px; flex: none; }
.krow { display: flex; gap: 5px; justify-content: center; }
.krow.inset { padding: 0 5%; }
.key {
  flex: 1; min-width: 0; height: 52px;
  border: 0; background: var(--key); color: var(--on-key);
  border-radius: var(--radius); cursor: pointer;
  /* LARGE TEXT: 1.25rem (20px) at weight 700, which is what lets --on-key sit
     on the dark-scheme --key (#818384) at 3.81:1. */
  font-size: 1.25rem; font-weight: 700; text-transform: uppercase;
  user-select: none;
}
.key.wide { flex: 1.5; font-size: .8125rem; letter-spacing: .02em; padding: 0 2px; }

dialog {
  border: 1px solid var(--edge); border-radius: 8px; background: var(--panel); color: var(--ink);
  padding: 22px; width: min(92vw, 360px);
}
dialog::backdrop { background: rgba(0, 0, 0, .5); }
dialog h2 {
  margin: 0 0 4px; font-size: 1.25rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .02em; text-align: center;
}
.score { font-size: .875rem; margin: .3em 0; text-align: center; color: var(--dim); font-weight: 700; }
.score b { color: var(--ink); }
.vspar {
  font-size: 1.75rem; font-weight: 700; margin: .1em 0 .4em;
  text-align: center; text-transform: uppercase; letter-spacing: .02em; color: var(--good);
}
.vspar.over { color: var(--dim); }
.stats { display: flex; gap: 6px; margin: 14px 0; padding: 0; }
.stats div { flex: 1; text-align: center; }
.stats dt { color: var(--dim); font-size: .625rem; text-transform: uppercase; letter-spacing: .04em; }
.stats dd { margin: 2px 0 0; font-size: 1.75rem; font-weight: 400; }
.row { display: flex; gap: 8px; align-items: stretch; }
.row button { flex: 1; }
#copy { background: var(--good); color: var(--on-fill); font-size: 1.25rem; }
.fine { color: var(--dim); font-size: .75rem; text-align: center; }
.fine a, .report a { color: var(--link); }
hr { border: 0; border-top: 1px solid var(--edge); margin: 16px 0 12px; }

/* ---- account block (M7). Hidden entirely until a client id is configured. ---- */
.account { display: flex; flex-direction: column; gap: 8px; }
.account[hidden] { display: none; }
.who { font-size: .8125rem; color: var(--dim); text-align: center; word-break: break-all; }
.who b { color: var(--ink); font-weight: 700; }

/* The CELL rules that used to live here are gone: `fitLadder()` measures the
   real space, which a media query only approximates. The KEY rules stay --
   the keyboard is a fixed three rows and genuinely is a viewport question. */
@media (max-height: 700px) { .key { height: 46px; } }
@media (max-width: 360px) { .key { font-size: 1.0625rem; } }
@media (prefers-reduced-motion: no-preference) {
  .rung { animation: rise .14s ease-out; }
  @keyframes rise { from { opacity: 0; transform: translateY(4px); } }
}

/* Gate 3 word-report line. Revealed only on a NOT_A_WORD refusal. */
.report { margin: 0; text-align: center; color: var(--dim); font-size: .75rem; }

/* THE LAST-RESORT COPY REVEAL (D-56). The copy button has two tiers above this
   -- the async Clipboard API and `execCommand` over an off-screen textarea --
   so a player sees the payload only when BOTH are unavailable, which no
   mainstream browser does. It is kept because the alternative terminal arm is
   a button that apologises, and `hidden` needs its own `display:none` here
   because any author `display` rule outranks the UA declaration. */
.manual { display: flex; flex-direction: column; gap: 4px; margin: 8px 0 0; font-size: .8125rem; color: var(--dim); }
.manual[hidden] { display: none; }
.manual textarea {
  width: 100%; box-sizing: border-box; resize: vertical;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .8125rem; line-height: 1.35;
  padding: 8px; border-radius: var(--radius);
  border: 1px solid var(--edge); background: var(--bg); color: var(--ink);
}
