/**
 * Leyline — tool styles
 *
 * Cards, tabs, tables, buttons and dialogs all come from frikwork untouched.
 * What is here is the board: the SVG bed, the two edge states, the four node
 * states, and the page-scoped text colours the shared layer gets wrong.
 *
 * WHY THE BOARD IS SVG AND WHAT THAT BUYS THE CSS
 * -----------------------------------------------
 * The viewBox is the engine's own 0..1000 square, so every coordinate in
 * script.js is a board unit and the browser does the pixel mapping. Nothing
 * here converts anything. Two consequences worth knowing before editing:
 *
 *   - `r` on a node is in BOARD UNITS and therefore scales with the board. That
 *     is what you want for a shape: a node is 4% of the board on a phone and on
 *     a 34rem panel alike.
 *   - `stroke-width` on a line is NOT scaled, because every stroke below sets
 *     `vector-effect: non-scaling-stroke` and its width in CSS px. A 4-unit
 *     stroke would be 2.2px on a desktop board and 1.3px on a 320px phone,
 *     which is the difference between a line and a hairline. The widths are the
 *     one thing on this board that must not shrink, because the WIDTH is how a
 *     crossing line is marked for a player who cannot see the colour.
 *
 * COLOUR IS NEVER THE ONLY SIGNAL
 * -------------------------------
 * A crossing line is thicker AND redder; the difference between "in the way"
 * and "fine" is the whole game, and red-against-grey is exactly the distinction
 * a colourblind player cannot make. Same reasoning as Cascade's six
 * silhouettes, arriving at a different answer because this board has one
 * property to vary rather than six identities.
 *
 * WHY THERE IS NO THEME-CONDITIONAL BLOCK IN THIS FILE
 * ----------------------------------------------------
 * tools/wordle/style.css needs one and its comments are worth reading before
 * you assume this file is missing something. frikwork ships dark on bare :root
 * and declares light TWICE — once from [data-fw-theme="light"] and again from
 * `@media (prefers-color-scheme: light) { :root:not([data-fw-theme]) }`, which
 * re-declares the whole palette — so a variable defined once at :root does NOT
 * follow the theme. That is what bit Wordle: its ink was --fw-on-primary, which
 * frikwork does NOT re-declare and which is near-black in every state.
 *
 * Nothing here has that problem, because every colour this file invents is a
 * color-mix() against --fw-text and a token frikwork re-declares in BOTH light
 * blocks (--fw-text-muted, --fw-brand-text, --fw-brand-2, --fw-danger). The mix
 * is Wordle's trick: --fw-text is by definition the pole furthest from the
 * surface, so `70% hue + 30% --fw-text` deepens a dark-theme colour toward white
 * and a light-theme colour toward black, raising contrast either way with one
 * rule. Adding an empty two-selector block to look like Wordle would be cargo
 * cult; what matters is that all four states were MEASURED.
 *
 * Measured with a probe over frikwork 0.0.26's own token values in all four
 * states (explicit light, explicit dark, attribute-absent + OS light,
 * attribute-absent + OS dark), against the .fw-card glass surface actually
 * composited over --fw-bg rather than against the flat token. Worst text ratio
 * in use 4.84:1, worst graphic ratio 4.20:1 — the numbers are in the task
 * report. Two raw tokens were measured and REJECTED for text on a card:
 * --fw-text-muted at 3.60:1 and --fw-brand-text at 3.94:1, both in the light
 * palette. Hence --ley-muted and --ley-accent below, and hence .ley-help /
 * .ley-note rather than .fw-help.
 *
 * WHERE THAT LEAVES TEXT THIS FILE DOES NOT AUTHOR
 * ------------------------------------------------
 * Five places on this page are painted by shared code — assets/css/app.css's
 * .app-stat__label and .app-empty p, frikwork's .fw-table td/th, and the
 * .fw-help that assets/js/games.js and includes/games.php both emit — and every
 * one is raw --fw-text-muted at 3.60:1 on a light card. That is a shared-CSS
 * problem affecting five games, and fixing it in app.css would restyle four
 * tools this task has no business touching. So each is out-specified HERE,
 * scoped to Leyline, and each override names the shared rule it is beating.
 * The requirement is every text the PAGE paints, not every text this file
 * styles.
 *
 * --fw-bg-2 is deliberately NOT used to separate the board from the panel it
 * sits on: it resolves to the same #ffffff as --fw-bg-card in the light theme.
 * The board bed is --fw-bg-hover with a --fw-border rule around it, the same
 * reasoning Wordle's keyboard and Cascade's board bed use.
 */

:root {
  /* Muted text and accent text, both deepened past the raw tokens — see the
     rejected measurements in the header. */
  --ley-muted: color-mix(in srgb, var(--fw-text-muted) 66%, var(--fw-text));
  --ley-accent: color-mix(in srgb, var(--fw-brand-text) 86%, var(--fw-text));

  /* The board's four colours. Every one is a mix against --fw-text so it
     deepens away from whichever surface is live. */
  --ley-edge: color-mix(in srgb, var(--fw-text-muted) 70%, var(--fw-text));
  --ley-cross: color-mix(in srgb, var(--fw-danger) 86%, var(--fw-text));
  --ley-node: color-mix(in srgb, var(--fw-brand-2) 86%, var(--fw-text));

  /* The two stroke widths, in CSS px because of non-scaling-stroke. Named
     because the rules-dialog legend draws the same two and they must not drift
     apart from the board. */
  --ley-edge-w: 2.5px;
  --ley-cross-w: 5px;
}

.ley-layout {
  display: grid;
  grid-template-columns: minmax(280px, 340px) 1fr;
  gap: var(--fw-space-lg);
  align-items: start;
}

.ley-play {
  display: flex;
  flex-direction: column;
  gap: var(--fw-space-lg);
  min-width: 0;
}

/* Replacements for .fw-help, which measures 3.60:1 on a card in the light
   palette. Same role, same size, a colour that clears AA. */
.ley-help,
.ley-note {
  color: var(--ley-muted);
  font-size: 0.85rem;
  line-height: 1.6;
}
.ley-note { margin: var(--fw-space-md) 0 0; }
.ley-help i,
.ley-note i { color: var(--ley-accent); }

/* --- Settings --------------------------------------------------------------
   .fw-tabs--segmented is an inline-flex track that scrolls when it runs out of
   room. In a 340px sidebar that would become a scroll strip, so the track is
   re-laid as a full-width grid and keeps its own chrome. */

.ley-seg {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  overflow-x: visible;
  width: 100%;
}

.ley-seg--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.ley-sizefield { margin-top: var(--fw-space-md); }

.ley-actions {
  display: grid;
  gap: var(--fw-space-sm);
}

.ley-keys {
  margin-top: var(--fw-space-md);
}

/* --- Stats ----------------------------------------------------------------- */

.ley-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: var(--fw-space-md);
  margin-bottom: var(--fw-space-md);
}

/* Each tile is wrapped so script.js has an id to hang off; the wrapper must not
   shrink the ccm_stat() card it holds. */
.ley-stat { display: flex; }
.ley-stat > .fw-card { flex: 1 1 auto; }

/* ccm_stat()'s label is --fw-text-muted (assets/css/app.css), which measures
   3.60:1 on a light-theme card — the same 3.60:1-token problem .fw-help has,
   arriving through a different component. Deepened here rather than in app.css,
   because app.css is shared with six other tools and this task is not the place
   to restyle them. Specificity (0,2,0) beats app.css's (0,1,0). */
.ley-stats .app-stat__label { color: var(--ley-muted); }

/* The live crossing count carries its state in the number's colour as well as
   in the number. Colour is the REINFORCEMENT here and never the signal: "0" and
   "7" are already distinguishable without it, which is the test this passes and
   the board's edge colours would not. */
.ley-stats .app-stat__value.is-crossing { color: var(--ley-cross); }
.ley-stats .app-stat__value.is-clear { color: var(--ley-accent); }

/* --- The board ------------------------------------------------------------- */

.ley-board-wrap {
  display: flex;
  justify-content: center;
}

.ley-board {
  display: block;
  width: 100%;
  /* Square, and never taller than the viewport on a short screen. Cosmetic, and
     it is worth saying so: the viewBox is square and preserveAspectRatio is the
     default xMidYMid meet, so a non-square box would letterbox rather than
     distort, and script.js maps a pointer through getScreenCTM() and does not
     care about the box at all. The 78vh cap bounds the board against the
     VIEWPORT, not against the page — on a short window the panels above it can
     still push its bottom edge below the fold and the player scrolls, which is
     ordinary. */
  aspect-ratio: 1;
  max-width: min(34rem, 78vh);
  border: 1px solid var(--fw-border);
  border-radius: var(--fw-radius-md);
  /* --fw-bg-2 is the same white as the card in the light palette; the bed has
     to differ from the panel or the board has no edge. */
  background: var(--fw-bg-hover);
  /* A drag must not also scroll the page on a touch screen. */
  touch-action: none;
}

/* Once it is solved there is nothing left to drag, and a stray drag afterwards
   would move a node on a finished board while the result dialog is open. */
.ley-board.is-done { pointer-events: none; }

/* --- Edges ----------------------------------------------------------------- */

.ley-edge {
  stroke: var(--ley-edge);
  stroke-width: var(--ley-edge-w);
  stroke-linecap: round;
  /* Widths stay in CSS px at every board size — see the header. */
  vector-effect: non-scaling-stroke;
  transition: stroke var(--fw-dur-fast) ease, stroke-width var(--fw-dur-fast) ease;
}

/* In the way. Thicker AND redder: the width is what a colourblind player reads,
   the colour is what everyone else reads first. */
.ley-edge.is-crossing {
  stroke: var(--ley-cross);
  stroke-width: var(--ley-cross-w);
}

/* --- Nodes ----------------------------------------------------------------- */

/* The group is the focusable thing; the three circles inside it are the hit
   target, the focus ring and the visible dot. cursor: grab on the group so the
   whole hit area says "drag me", not just the dot. */
.ley-node {
  cursor: grab;
  outline: none;   /* replaced, not removed — see .ley-node__ring below */
}

.ley-node.is-dragging { cursor: grabbing; }

/* Invisible, and much bigger than the dot. A 20-unit dot is about 11px across
   on a desktop board and 6px on a phone, which is not a touch target; this is
   about 50px and 29px respectively. `fill: none` would make it unhittable, so
   it is transparent with pointer-events forced on.

   THIS CIRCLE IS THE AFFORDANCE, NOT THE GRAB. At this size the circles overlap
   each other in any tight part of a solved layout, and a browser resolves an
   overlap by paint order — so the node you would GET from a hit test is the one
   latest in the DOM, not the one under your finger. script.js grabs the NEAREST
   node within the same radius instead (see nearestNode()); all this rule does
   is make the cursor and the hover state cover the area the grab covers. In a
   cluster the hover can still land on the wrong one of two overlapping nodes,
   which is cosmetic — the drag takes the nearest either way. */
.ley-node__hit {
  fill: transparent;
  pointer-events: all;
}

/* A line is something to look at, not something to grab. Left hittable it would
   take the cursor and the hover state off a node whose hit circle it crosses,
   for no gain — the pointer handler works off distance and never reads the
   event target. */
.ley-edge { pointer-events: none; }

.ley-node__dot {
  fill: var(--ley-node);
  transition: fill var(--fw-dur-fast) ease, r var(--fw-dur-fast) ease;
}

.ley-node:hover .ley-node__dot,
.ley-node.is-dragging .ley-node__dot {
  fill: var(--ley-accent);
}

/* THE FOCUS RING, WHICH IS HOW A KEYBOARD PLAYER KNOWS WHERE THEY ARE.
   Drawn rather than left to `outline` on purpose: an outline on an SVG element
   is painted around the bounding box, which for a node group is the 92-unit hit
   circle — a big square hovering over a small dot, in a colour the UA picks. A
   stroked circle is concentric with the node, sits in --fw-text (15:1 against
   the bed in both themes) and is drawn at a fixed 3px like every other focus
   ring in the suite. `outline: none` above is the replacement half of that
   bargain and is only sound because this rule is unconditional CSS with no
   media query and no token that can resolve to transparent. */
.ley-node__ring {
  fill: none;
  stroke: transparent;
  stroke-width: 3px;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

.ley-node:focus-visible .ley-node__ring { stroke: var(--fw-text); }

/* :focus-visible alone leaves a node the player CLICKED without a ring, which
   is right for a mouse and wrong here: the click is also what puts the arrow
   keys on that node, so a mouse player who then reaches for the keyboard has no
   idea which one they are about to move. The class is set by script.js on
   whichever node last took focus by any route. */
.ley-node.is-focus .ley-node__ring { stroke: var(--fw-text); }

/* --- Status ---------------------------------------------------------------- */

.ley-status {
  margin: var(--fw-space-lg) 0 0;
  padding: var(--fw-space-sm) var(--fw-space-md);
  border: 1px solid var(--fw-border);
  border-radius: var(--fw-radius-md);
  /* --fw-bg-2 is the same white as the card in the light palette; the border is
     what separates this strip, not the fill. */
  background: var(--fw-bg-2);
  color: var(--fw-text-2);
  text-align: center;
  min-height: 2.75rem;
}

/* --- Leaderboard ----------------------------------------------------------- */

.ley-board__scope {
  margin: 0 0 var(--fw-space-sm);
  font-weight: 600;
  color: var(--ley-accent);
}

/* ccmGames.renderBoard() puts classPrefix on the rank and value columns and on
   the <td> holding the initials — never on the "Initials" <th>, which carries no
   class in any game. These rules are written to be correct either way.

   The rank colour is qualified with .ley-board-table on purpose. frikwork's own
   `.fw-table td` and `.fw-table th` rules are (0,1,1) and would otherwise beat a
   bare `.ley-board__rank` at (0,1,0) — which is how the rank column ends up
   rendering at raw --fw-text-muted (3.60:1 on a light card) while a stylesheet
   that plainly says --ley-muted sits three lines above it. Measured, not
   guessed; see the header. */
.ley-board__rank { width: 3rem; }
.ley-board-table .ley-board__rank { color: var(--ley-muted); }
.ley-board__initials { font-weight: 700; letter-spacing: 0.06em; }
.ley-board__num { text-align: right; font-variant-numeric: tabular-nums; }
.ley-board__note { margin-bottom: 0; }

/* THE TWO STATES ccmGames.renderBoard() PAINTS, WHICH THIS FILE DOES NOT OWN.
   The empty state is `.app-empty p` (assets/css/app.css) and the error state is
   `<p class="fw-help">` (assets/js/games.js) — both raw --fw-text-muted, both
   3.60:1 on a light card, and between them they are what a first-time visitor
   sees: the empty state is the default rendering of this panel on day one and
   every morning a fresh daily scope opens. Scoped by id so the fix stays inside
   Leyline and the shared problem is left where it is; an id beats app.css's
   (0,2,0) and frikwork's (0,1,0) alike. Measured, like everything else here. */
#leyLeaderboard .app-empty p,
#leyLeaderboard .fw-help { color: var(--ley-muted); }

/* --- Dialog content -------------------------------------------------------- */

.ley-verdict {
  margin: 0 0 var(--fw-space-md);
  padding: var(--fw-space-md);
  border: 1px solid var(--fw-border);
  border-radius: var(--fw-radius-md);
  background: var(--fw-bg-2);
  line-height: 1.5;
}

.ley-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(5rem, 1fr));
  gap: var(--fw-space-sm);
  margin: 0 0 var(--fw-space-md);
  padding: var(--fw-space-md);
  border: 1px solid var(--fw-border);
  border-radius: var(--fw-radius-md);
  background: var(--fw-bg-2);
}

.ley-facts__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  text-align: center;
}

.ley-facts dt {
  font-size: 0.8rem;
  color: var(--ley-muted);
}

.ley-facts dd {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--ley-accent);
}

.ley-rules {
  display: grid;
  gap: var(--fw-space-sm);
  margin: 0 0 var(--fw-space-md);
  padding-left: var(--fw-space-lg);
  color: var(--fw-text-2);
}

.ley-legend__title {
  margin: var(--fw-space-lg) 0 var(--fw-space-sm);
  font-size: 1rem;
}

.ley-legend {
  display: grid;
  gap: var(--fw-space-sm);
  margin: 0 0 var(--fw-space-md);
  padding: 0;
  list-style: none;
}

.ley-legend li {
  display: flex;
  align-items: center;
  gap: var(--fw-space-md);
  color: var(--fw-text-2);
}

/* The legend swatches are the board's own strokes at legend size: the two edge
   states are drawn as a border of exactly --ley-edge-w / --ley-cross-w, so the
   thing the dialog calls "thicker" is measurably the thicker one, and the node
   swatch is the node's own fill. Change a width above and the legend follows. */
.ley-swatch {
  flex: 0 0 auto;
  width: 2.5rem;
  height: 0;
  align-self: center;
}

.ley-swatch--edge {
  border-top: var(--ley-edge-w) solid var(--ley-edge);
}

.ley-swatch--cross {
  border-top: var(--ley-cross-w) solid var(--ley-cross);
}

.ley-swatch--node {
  height: 1.1rem;
  border-radius: var(--fw-radius-pill);
  background: var(--ley-node);
  width: 1.1rem;
  margin-left: 0.7rem;
  margin-right: 0.7rem;
}

.ley-sizetable { margin-top: var(--fw-space-md); }
.ley-sizetable__caption {
  caption-side: top;
  padding-bottom: var(--fw-space-xs);
  color: var(--ley-muted);
  font-size: 0.85rem;
  text-align: left;
}
.ley-sizetable__num { text-align: right; font-variant-numeric: tabular-nums; }

/* The initials prompt's help line, which ccm_game_initials_modal() renders as
   `<small class="fw-help">` inside a dialog this page owns. Same raw
   --fw-text-muted, same 3.60:1, and it is load-bearing copy — askInitials()
   rewrites it to say WHY a submission was refused. Scoped by the modal's id so
   the other five games' prompts are untouched. */
#leyInitialsModal .fw-help { color: var(--ley-muted); }

.ley-initials {
  text-transform: uppercase;
  letter-spacing: 0.3em;
  text-align: center;
  font-size: 1.4rem;
  font-weight: 700;
}

/* --- Responsive ------------------------------------------------------------ */

@media (max-width: 960px) {
  .ley-layout { grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
  .ley-board { max-width: 100%; }
  .ley-keys { display: none; }
  .ley-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--fw-space-sm); }
}

/* Same contract frikwork honours for its own animations: no motion, but nothing
   below costs a player information. Every state that matters — the crossing
   colour, the crossing WIDTH, the node fill, the focus ring — is a property
   with a value, not a keyframe, so switching the transitions off changes only
   how fast the board arrives at the same picture. There is deliberately nothing
   else to disable: the board has no keyframes, because a node follows your
   finger and an edge follows its nodes. */
@media (prefers-reduced-motion: reduce) {
  .ley-edge,
  .ley-node__dot { transition: none; }
}
