/* THE CHESS BOARD (BRIEF-54b, DR-273) + THE POLISH LAYER (CJ-1604, CJ-1700).
 *
 * 🔒 WHY A SEPARATE FILE AND NOT AN ADDITION TO shell.css. `00 CLAIMS` names shell.css the single
 *    most collision-prone file in the project — "every layout defect this project has had lives here"
 *    — and `A-34` is what one stale read of it costs. Several sessions write to this tree at once.
 *    `TitleLayer.stylesheet` exists for exactly this.
 *
 * 🔒 EVERY CLASS IS PREFIXED `cj-chess`, AND NOTHING HERE OVERRIDES shell.css.
 *    `test/chessStyles.test.ts` fails the build on either half.
 *
 * 🔒 EVERY COLOUR IS A TOKEN, AND EVERY TOKEN IS ONE THAT EXISTS. Not one hex literal appears below.
 *    Translucent tones are built with `color-mix()` FROM tokens rather than written as `rgba(...)`
 *    literals, so a brand change or a dark-mode swap reaches them like everything else.
 *
 * ════════════════════════════════════════════════════════════════════════════════════════════════
 * THE POLISH LAYER — `CJ-1604`, the unblocked half of `00 HANDOFF BRIEF - the game look upgrade`.
 * ════════════════════════════════════════════════════════════════════════════════════════════════
 *
 * 🔒 THE HANDOFF BRIEF'S CENTRAL FINDING IS THAT THE ENGINE IS NOT WHAT MAKES A GAME LOOK EXPENSIVE.
 *    CrazyGames' own documentation lists nine different engines behind one look. What produces it, in
 *    order of leverage, is art direction, **a tween on every state change**, **feedback on every
 *    action**, and frame-rate discipline. **None of that requires WebGL**, and this file is the
 *    argument made in the language the board is already written in.
 *
 * 🔴 AND THE TRICK WORTH STEALING IS ONE LINE. The 3D prototype routes every duration in the game
 *    through `dur(ms) => CFG.polish ? ms : 0`, and its tween engine resolves a zero-duration tween on
 *    its first frame. One switch, whole polish layer, no branching anywhere else. **The CSS port is
 *    `--cj-polish`, an unitless 1 or 0 multiplied into every duration below.** Nothing in this file
 *    hard-codes a duration past it, and `test/chessPolish.test.ts` fails if anything ever does.
 *
 * 📌 That is what made the prototype persuasive: the before and after can be TOGGLED rather than
 *    argued about. The board carries a visible switch for the same reason.
 */

/* ---------------------------------------------------------------- the switch ---------------- */

.cj-chess {
  /* 1 = polished, 0 = every duration collapses to zero. The only knob. */
  --cj-polish: 1;

  /* The prototype's easing vocabulary. `outQuint` for travel, `outBack` for anything that should
     feel like it seats itself with a little weight. Nothing in this file eases linearly. */
  --cj-ease: cubic-bezier(0.22, 1, 0.36, 1);
  --cj-ease-back: cubic-bezier(0.34, 1.56, 0.64, 1);

  /* Every duration in the board, and every one of them goes through the switch. */
  --cj-t-fast: calc(110ms * var(--cj-polish));
  --cj-t-pop: calc(180ms * var(--cj-polish));
  --cj-t-move: calc(260ms * var(--cj-polish));
  --cj-t-nudge: calc(130ms * var(--cj-polish)); /* the prototype's own refusal nudge, to the ms */
  --cj-t-fade: calc(220ms * var(--cj-polish));
}

.cj-chess[data-cj-polish="off"] { --cj-polish: 0; }

/* 🔴 THE ACCESSIBILITY PREFERENCE BEATS THE TOGGLE, IN BOTH DIRECTIONS, AND THE ORDER HERE IS THE
   ONLY THING MAKING THAT TRUE. `.cj-chess[data-cj-polish="off"]` and `.cj-chess[data-cj-polish]`
   have identical specificity, so the later rule wins — which is why this block sits at the bottom of
   the switch and must stay there. A player who has asked their operating system for stillness gets
   stillness even with the switch showing "on", and no state is lost: every duration becomes zero, so
   every end state still arrives, instantly. */
@media (prefers-reduced-motion: reduce) {
  .cj-chess,
  .cj-chess[data-cj-polish] { --cj-polish: 0; }
}

.cj-chess {
  display: flex;
  flex-direction: column;
  gap: var(--cj-space-4, 12px);
  align-items: center;
  width: 100%;
}

/* --- the setup row: opponent, colour, new game, polish ---------------------------------------- */

.cj-chess__setup {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cj-space-4, 12px);
  align-items: flex-end;
  justify-content: center;
  width: 100%;
  margin: 0;
}

.cj-chess__field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0;
  min-width: 0;
}

.cj-chess__label {
  font-family: var(--font-ui);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.cj-chess__select {
  font-family: var(--font-ui);
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  /* 🔒 48px is the project's touch floor (`--touch-min`), and `CJ-930` found fifteen live controls
     under it — three of them under WCAG 2.2's 24x24 absolute minimum. */
  min-height: var(--touch-min);
  padding: 0 12px;
  max-width: 22rem;
}

.cj-chess__select:focus-visible,
.cj-chess__sq:focus-visible,
.cj-chess__input:focus-visible,
.cj-chess__polish:focus-visible {
  outline: var(--focus);
  outline-offset: 2px;
  box-shadow: var(--focus-glow);
}

/* The visible switch. The prototype's header carries the same control for the same reason: a polish
   budget you can turn off is an argument; one you cannot is a preference. */
.cj-chess__polish {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: var(--touch-min);
  padding: 0 12px;
  font-family: var(--font-ui);
  font-size: 0.85rem;
  color: var(--ink-muted);
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  cursor: pointer;
}

.cj-chess__polishdot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--sage);
  transition: background var(--cj-t-fast) var(--cj-ease);
}

/* Never colour alone: the button's own text says "Motion: on" / "Motion: off", and it carries
   `aria-pressed`. The dot is the fast path, not the meaning. */
.cj-chess[data-cj-polish="off"] .cj-chess__polishdot { background: var(--n-300); }

/* --- the status line --------------------------------------------------------------------------- */

.cj-chess__status {
  font-family: var(--font-ui);
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink);
  background: var(--accent-tint);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 10px 16px;
  margin: 0;
  text-align: center;
  min-height: 1.5em;
  width: 100%;
  max-width: 34rem;
  box-sizing: border-box;
  transition: background var(--cj-t-fade) var(--cj-ease),
              border-color var(--cj-t-fade) var(--cj-ease);
}

/* 🔒 THE RESULT IS STATED IN PLACE, NOT IN A MODAL, AND THAT IS A DELIBERATE DEPARTURE FROM
   `BRIEF-54` Step 3's "use the shell, do not hand-roll another outcome card". Two reasons, both
   specific. (1) The shell's card carries the `DR-133` signup offer — "Want that streak to follow you
   to your phone?" — and chess records NO streak, so on this page that sentence is false. (2) A modal
   covers the final position, which is the one thing a chess player wants to look at when a game
   ends. `CJ-1531` is the row to give the shell card an option to suppress the offer. */
.cj-chess[data-cj-chess-over] .cj-chess__status {
  background: var(--surface);
  border-color: var(--toast);
  border-width: 2px;
  font-size: 1.1rem;
}

/* The thinking state. A three-dot pulse, and the status line says "CrumbJar is thinking…" in words —
   the animation is decoration on a sentence that is already complete without it. */
.cj-chess__think {
  display: none;
  margin-left: 6px;
}
.cj-chess[data-cj-chess-busy] .cj-chess__think { display: inline; }
.cj-chess__think span {
  display: inline-block;
  width: 5px;
  height: 5px;
  margin-left: 3px;
  border-radius: 50%;
  background: var(--accent-deep);
  animation: cj-chess-think calc(1200ms * var(--cj-polish)) ease-in-out infinite;
}
.cj-chess__think span:nth-child(2) { animation-delay: calc(150ms * var(--cj-polish)); }
.cj-chess__think span:nth-child(3) { animation-delay: calc(300ms * var(--cj-polish)); }

@keyframes cj-chess-think {
  0%, 60%, 100% { opacity: 0.28; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}

/* --- the board -------------------------------------------------------------------------------- */

.cj-chess__boardwrap {
  position: relative;
  display: grid;
  grid-template-columns: 1.25rem minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) 1.25rem;
  gap: 2px;
  width: 100%;
  /* 🔒 `min(...)` rather than a fixed pixel size: `test/boardWithinColumn.test.ts` exists because a
     board that breaks out of the centre column is this project's most-repeated layout defect. */
  max-width: min(34rem, 100%, 70vh);
  margin: 0 auto;
}

/* LAYER 1, ART DIRECTION — the board reads as an OBJECT ON A TABLE rather than a grid in a page.
 *
 * 🔴 THE `A-564` LIGHT BUDGET APPLIES HERE AND IT IS NOT ONLY A 3D RULE. The owner found a board
 *    where three lights summing to ~1.9x turned a cream tile on a cream surface into one white
 *    shape: it rendered perfectly and looked empty. **A CSS filter and blend stack is the same
 *    arithmetic.** Everything below is a SHADOW (subtractive) or a token colour. There is no
 *    `brightness()`, no `filter` on the board, and no stacked translucent white — so the budget
 *    cannot be exceeded by construction rather than by care.
 */
.cj-chess__board {
  grid-column: 2;
  grid-row: 1;
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(8, 1fr);
  aspect-ratio: 1 / 1;
  border: 2px solid var(--ink);
  border-radius: var(--cj-radius-1, 8px);
  overflow: hidden;
  width: 100%;
  box-shadow:
    0 1px 0 color-mix(in oklab, var(--surface) 60%, transparent),
    0 10px 22px -12px color-mix(in oklab, var(--ink) 45%, transparent);
}

.cj-chess__sq {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  margin: 0;
  border: 0;
  cursor: pointer;
  position: relative;
  font-size: clamp(1.2rem, 6.5vw, 2.4rem);
  line-height: 1;
  min-width: 0;
  min-height: 0;
  /* The board's own max-width keeps every square above the touch floor at every supported width. */
  transition: background-color var(--cj-t-fast) var(--cj-ease);
}

.cj-chess__sq--light { background: var(--n-050); }
.cj-chess__sq--dark { background: var(--sand); }

/* LAYER 3, FEEDBACK — the hover lift. Only on a square a legal move STARTS from, so the board tells
   you what is holdable before you commit to holding it. That is the prototype's hover lift, and it
   is the cheapest single thing that makes a board feel alive. Pointer devices only: on a touch
   screen `:hover` sticks after a tap and would leave a piece lifted for ever. */
@media (hover: hover) and (pointer: fine) {
  .cj-chess__sq--from:hover .cj-chess__glyph {
    transform: translateY(-3px) scale(1.06);
  }
}

/* Selected: a ring, an inset mark, and `aria-pressed` in the markup. The ring survives a greyscale
   render; `aria-pressed` survives having no sight of the screen at all. */
.cj-chess__sq--sel { box-shadow: inset 0 0 0 4px var(--toast); }
.cj-chess__sq--sel .cj-chess__glyph { transform: translateY(-2px) scale(1.1); }

/* The last move played, both squares. Not decoration — in chess this is the single most useful piece
   of board state there is, and without it a correspondence player returning to a board cannot see
   what just happened. It is also the honest place for a tint, because it says something true. */
.cj-chess__sq--last { background-color: color-mix(in oklab, var(--accent-tint) 72%, var(--n-050)); }
.cj-chess__sq--dark.cj-chess__sq--last { background-color: color-mix(in oklab, var(--accent) 30%, var(--sand)); }

/* Legal destinations. A dot for an empty square, a ring for a capture — two different marks for two
   different meanings, so the board is not saying "you may move here" with one shape and hoping. */
.cj-chess__sq--target::after {
  content: "";
  position: absolute;
  inset: 38%;
  border-radius: 50%;
  background: var(--toast);
  opacity: 0.55;
  transform: scale(0);
  transition: transform var(--cj-t-pop) var(--cj-ease-back);
  /* Staggered by distance from the selected square, so the options bloom outwards rather than all
     appearing at once. `--i` is set by chess.js per square. */
  transition-delay: calc(var(--i, 0) * 14ms * var(--cj-polish));
}
.cj-chess__sq--target.cj-chess__sq--in::after { transform: scale(1); }

.cj-chess__sq--capture::after {
  inset: 6%;
  border-radius: 50%;
  background: transparent;
  border: 3px solid var(--toast);
  opacity: 0.7;
}

/* The king, when it is in check. A pulse AND the status line says "You are in check." in words.
 *
 * ⚠️ THIS WAS A `radial-gradient` FOR ABOUT A MINUTE AND THIS FILE'S OWN GUARD BANS ONE. The ban
 *    exists because a glossy gradient is the house's named "generated tell" (`BRIEF-19`), and a soft
 *    glow around a king is exactly the sort of thing that arrives calling itself an exception. A flat
 *    tinted inset does the same job, costs less to paint, and keeps the rule intact — which is worth
 *    more than the glow was.
 */
.cj-chess__sq--check::before {
  content: "";
  position: absolute;
  inset: 0;
  background: color-mix(in oklab, var(--jam) 42%, transparent);
  opacity: 0.35;
  animation: cj-chess-check calc(900ms * var(--cj-polish)) ease-in-out 3;
}

@keyframes cj-chess-check {
  0%, 100% { opacity: 0.3; }
  50% { opacity: 0.85; }
}

/* 🔒 THE REFUSAL NUDGE — 130ms, the prototype's own number. It exists because a refused input that
   does nothing is indistinguishable from a broken game: the owner met exactly that on Skewer
   (`A-530`), tried an illegal pair, nothing happened, and nothing on the board explained why. The
   status line says why in words; this says "not that one" in the half-second before anyone reads it. */
.cj-chess__sq--nudge {
  animation: cj-chess-nudge var(--cj-t-nudge) linear;
}

@keyframes cj-chess-nudge {
  0% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  50% { transform: translateX(4px); }
  75% { transform: translateX(-2px); }
  100% { transform: translateX(0); }
}

.cj-chess__glyph {
  pointer-events: none;
  /* 🔒 BOTH COLOURS USE THE SOLID GLYPH AND ARE TOLD APART BY FILL PLUS OUTLINE. The Unicode outline
     series (U+2654..) is close to invisible on a pale square in several common fonts, and a piece
     identified by outline-versus-solid is a meaning carried by shape alone. The text-shadow ring is
     what keeps a white piece legible on `--n-050`; it is drawn in four directions because
     `-webkit-text-stroke` is not universal. */
  font-size: 1em;
  display: block;
  transform: translateY(0) scale(1);
  transition: transform var(--cj-t-pop) var(--cj-ease-back);
}

/* 🔴 `will-change` IS ADDED FOR THE DURATION OF A MOVE AND THEN REMOVED, NOT DECLARED ON ALL 64
 *    SQUARES. The first draft put it on `.cj-chess__glyph`, which asks the browser to promote
 *    **sixty-four elements** to their own compositor layers and hold them there for the whole game —
 *    on a cheap phone that is memory spent to make one moving piece smoother. **That is layer 4 of
 *    the handoff brief, frame-rate discipline, failed in the act of implementing layer 2.**
 *    `chess.js` adds `--flip` for one move and takes it off on `transitionend`.
 */
.cj-chess__glyph--flip { will-change: transform; }

.cj-chess__glyph--white {
  color: var(--surface);
  text-shadow: 0 1px 0 var(--ink), 0 -1px 0 var(--ink), 1px 0 0 var(--ink), -1px 0 0 var(--ink);
}
.cj-chess__glyph--black {
  color: var(--ink);
  text-shadow: 0 1px 0 var(--n-100), 0 -1px 0 var(--n-100), 1px 0 0 var(--n-100), -1px 0 0 var(--n-100);
}

/* LAYER 2, MOTION — the piece that just moved travels from where it was.
 *
 * 🔒 THE BOARD IS REDRAWN FIRST AND ANIMATED SECOND, which is the FLIP technique and is chosen for a
 *    correctness reason rather than a performance one: the position is already correct before a
 *    single frame plays, so a dropped animation, a mid-move resize or a thrown exception can leave
 *    the board looking wrong for 260ms and never leave it WRONG. Animating into place first would
 *    make the rendered board a claim about the position; this way it is a report of it.
 */
.cj-chess__glyph--flip {
  transition: transform var(--cj-t-move) var(--cj-ease);
}

/* The effects layer: captured pieces fade out here, over the board, after the board itself has
   already moved on. 🔴 IT IS `pointer-events: none` AND SITS UNDER NOTHING — `A-564`'s second trap
   is a positioned layer that paints over the HUD, and the fix is that this one covers the BOARD
   only, never the status line, the controls or an ad rail. */
.cj-chess__fx {
  position: absolute;
  grid-column: 2;
  grid-row: 1;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  border-radius: var(--cj-radius-1, 8px);
}

.cj-chess__ghost {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: clamp(1.2rem, 6.5vw, 2.4rem);
  line-height: 1;
  transform: scale(1);
  opacity: 1;
  transition: transform var(--cj-t-fade) var(--cj-ease),
              opacity var(--cj-t-fade) var(--cj-ease);
}
.cj-chess__ghost--gone {
  transform: scale(0.4) rotate(12deg);
  opacity: 0;
}

/* --- coordinates ------------------------------------------------------------------------------ */

.cj-chess__files,
.cj-chess__ranks {
  display: grid;
  font-family: var(--font-ui);
  font-size: 0.7rem;
  font-variant-numeric: var(--num);
  color: var(--ink-muted);
}
.cj-chess__files {
  grid-column: 2;
  grid-row: 2;
  grid-template-columns: repeat(8, 1fr);
  text-align: center;
}
.cj-chess__ranks {
  grid-column: 1;
  grid-row: 1;
  grid-template-rows: repeat(8, 1fr);
  align-items: center;
  justify-items: center;
}

/* --- the typed-move path ----------------------------------------------------------------------- */

.cj-chess__typed {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
  width: 100%;
  max-width: 34rem;
}
.cj-chess__typed .cj-chess__label { grid-column: 1; }

.cj-chess__input {
  font-family: var(--font-ui);
  font-size: 1rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  min-height: var(--touch-min);
  padding: 0 12px;
  width: 100%;
  box-sizing: border-box;
  transition: border-color var(--cj-t-fast) var(--cj-ease);
}

.cj-chess__help {
  grid-column: 1 / -1;
  font-family: var(--font-ui);
  font-size: 0.8rem;
  color: var(--ink-muted);
}

/* --- the move record --------------------------------------------------------------------------- */

.cj-chess__record {
  width: 100%;
  max-width: 34rem;
}

.cj-chess__recordhead {
  font-family: var(--font-ui);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin: 0 0 6px;
}

.cj-chess__moves {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 2px 10px;
  margin: 0;
  padding: 0 0 0 2.2em;
  max-height: 9rem;
  overflow-y: auto;
  font-family: var(--font-ui);
  font-size: 0.9rem;
  font-variant-numeric: var(--num);
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding-block: 8px;
}

.cj-chess__move { padding: 1px 0; }

.cj-chess__ply {
  display: inline-block;
  min-width: 3.6em;
}

/* The newest ply arrives rather than appearing. One frame of difference, and it is the difference
   between a list that updates and a list that is being written. */
.cj-chess__ply--new {
  animation: cj-chess-ply var(--cj-t-pop) var(--cj-ease);
}

@keyframes cj-chess-ply {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

.cj-chess__nojs {
  font-family: var(--font-ui);
  font-size: 0.9rem;
  color: var(--ink-muted);
  background: var(--n-050);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 10px 14px;
  margin: 0;
}

/* --- narrow screens ---------------------------------------------------------------------------- */

@media (max-width: 480px) {
  .cj-chess__setup { flex-direction: column; align-items: stretch; }
  .cj-chess__select { max-width: none; }
  .cj-chess__polish { justify-content: center; }
  .cj-chess__typed { grid-template-columns: minmax(0, 1fr) auto; }
  .cj-chess__typed .cj-chess__label { grid-column: 1 / -1; }
}
