.review-root {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 8px 0 48px;
  max-width: 1360px;
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}

.review-kicker {
  margin: 0 0 8px;
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold);
}

.review-quota {
  margin: 10px 0 0;
  color: var(--muted);
  font-size: 0.88rem;
}

.review-quota a {
  color: var(--gold);
}

/* The status line is rewritten on EVERY ply during an analysis —
   "Analyzing move 12 of 64…". It sits above the board, and it was sized by
   its content, so a longer message wrapped to a second line, took the board's
   height, and re-squared the board. That is the constant resizing: the text
   grows a character at a time as the counts climb, and the board resizes with
   it.

   One fixed row, and the text is truncated inside it. A progress message is
   transient and the board is not, so the message is what gives.

   When there is no message the row collapses entirely: an empty <p> still
   occupies its 1.4rem of row height because the flex item has a fixed height,
   and on a height-locked page that is board that nothing can reach. The
   zero-height state must keep the text accessible tech honest — an empty
   status is nothing to announce, and removing it from the box tree when it
   has no text is precisely what :empty does. */
.review-status {
  flex: 0 0 auto;
  height: 1.4rem;
  line-height: 1.4rem;
  margin: 0;
  color: var(--muted);
  font-size: 0.92rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.review-status:empty {
  height: 0;
  line-height: 0;
}

.review-status.is-error {
  color: #f0a0a0;
}

/* The library is a <dialog> that covers the workspace, not a second page.

   It was a <section> that took over the whole document, which put the same
   games on screen twice: in the rail beside the board, and again here. As a
   modal dialog it sits in the top layer over everything, the rail underneath
   is the single real list, and "Pick another game" simply opens this over the
   board without unmounting anything.

   `showModal()` supplies the backdrop and the focus trap, so those are not
   re-implemented here; the ::backdrop rule below is the one piece the UA
   stylesheet does not provide. */
.review-picker {
  width: min(1080px, calc(100vw - 48px));
  max-width: none;
  max-height: calc(100dvh - 64px);
  margin: auto;
  border: 1px solid var(--line-strong);
  border-radius: 16px;
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--gold) 4%, transparent), transparent 32%),
    var(--panel);
  color: var(--cream);
  padding: 20px;
  display: grid;
  /* The list is the only column; the decorative board was removed. */
  grid-template-columns: minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}

.review-picker-main {
  min-width: 0;
}

/* `display: grid` on the dialog defeats the UA's
   `dialog:not([open]) { display: none }`, which would otherwise leave the
   closed dialog laid out on the page underneath the board. */
.review-picker:not([open]) {
  display: none;
}

.review-picker::backdrop {
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}

/* The count and the close button share the heading's right edge. */
.imported-picker-head-actions {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 10px;
}

#review-picker-close {
  width: 34px;
  height: 34px;
  padding: 0;
  font-size: 1.35rem;
  line-height: 1;
}

/* ——— The dialog's two faces ———
   One dialog, two jobs: the full games list, and the paste form on its own.
   `data-mode` on the dialog decides which is showing, and the elements that
   belong to the other face are hidden.

   The `hidden` attribute alone is not enough here for the same reason as
   everywhere else in this file: it is a UA-level `display: none` and any author
   `display` outranks it. The panels below are grids and flex containers, so
   without this the hidden face would still be laid out. */
.review-picker [hidden],
body.review-page .review-picker [hidden] {
  display: none !important;
}

/* The paste form is the only thing in its mode, so the dialog shrinks to it
   rather than reserving the width and height of a list that is not there.

   `.review-picker-main` becomes a block so the panel is a normal flow child
   rather than a grid item, and the panel itself is told to fill the dialog.
   Without that last part it kept the width it had in the two-column library
   layout — measured at 300px inside a 580px box, so the textarea you are
   meant to paste into was a third of the dialog with dead space either side. */
.review-picker[data-mode="pgn"] {
  width: min(620px, calc(100vw - 48px));
}

.review-picker[data-mode="pgn"] .review-picker-main {
  display: block;
  width: 100%;
}

.review-picker[data-mode="pgn"] .review-panel {
  margin: 0;
  width: 100%;
  max-width: none;
}

/* The game count belongs to the list. In the paste face it would be reporting
   on something that is not on screen. */
.review-picker[data-mode="pgn"] .imported-picker-count {
  display: none;
}

/* The box you are pasting into is the whole point of this face, so give it the
   room. 160px is the resting height everywhere else, where the panel sits
   beside a list and a tall textarea would push the dialog off a short screen. */
.review-picker[data-mode="pgn"] .review-pgn {
  min-height: 260px;
  width: 100%;
}

/* ——— `hidden` has to beat an author `display` ———
   The UA stylesheet hides a `[hidden]` element with `[hidden] { display:
   none }`. Every `display` in this file is an AUTHOR declaration and therefore
   outranks it, so the moment #review-workspace or .review-board-col was given
   a `display`, the attribute stopped working and the board view rendered on
   top of the picker.

   This is the standard counterpart, and it is deliberately UNCONDITIONAL and
   placed high in the file. Inside a media query it only protected the desktop
   layout, which left the two views overlapping at every narrower width where
   the unconditional `display: grid` on the board column still applied. */
body.review-page [hidden] {
  display: none !important;
}

/* On the picker, the imported-games list is a library surface that grows with
   the account — 173 games turned the panel into a single scrolling column and
   pushed the PGN textarea and its button off the bottom. Capped so the list
   scrolls inside its own frame and the paste-a-PGN control stays reachable
   without scrolling the page.

   The cap is generous and viewport-relative so a short window shrinks it
   rather than pushing the form below the fold. */
@media (min-width: 1181px) {
  body.review-page #review-picker .imported-games-host {
    max-height: min(52dvh, 560px);
    overflow-y: auto;
    overscroll-behavior-y: contain;
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--gold) 40%, transparent) transparent;
  }

  body.review-page #review-picker .imported-games-host::-webkit-scrollbar {
    width: 8px;
  }

  body.review-page #review-picker .imported-games-host::-webkit-scrollbar-track {
    background: transparent;
  }

  body.review-page #review-picker .imported-games-host::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--gold) 38%, transparent);
    border-radius: 999px;
  }

  /* Two columns on a wide window: the list beside the paste form, so neither
     has to be scrolled to reach the other. The split is on .review-picker-main
     — the heading, the games host and the PGN panel are all its children, so
     splitting #review-picker itself would leave them stacked in one cell. */
  body.review-page #review-picker .review-picker-main {
    display: grid;
    grid-template-columns: minmax(0, 1.35fr) minmax(300px, 0.65fr);
    grid-template-areas:
      "heading panel"
      "list    panel";
    align-items: start;
    gap: 16px 28px;
  }

  body.review-page #review-picker .imported-picker-heading {
    grid-area: heading;
    margin-bottom: 0;
  }

  body.review-page #review-picker .imported-games-host {
    grid-area: list;
  }

  /* …and none of that in the paste face.

     These rules are prefixed `body.review-page #review-picker`, which is more
     specific than the `.review-picker[data-mode="pgn"]` selectors above, so
     the paste dialog was still being laid out as a two-column list-plus-panel
     grid. That is why it came out at the full library width with the games
     list beside the paste box: the mode was set correctly, and the layout
     simply ignored it.

     Matching the attribute here is what actually honours the mode. */
  body.review-page #review-picker[data-mode="pgn"] .review-picker-main {
    display: block;
    width: 100%;
  }

  body.review-page #review-picker[data-mode="pgn"] .review-panel {
    grid-area: auto;
    position: static;
    margin: 0;
    width: 100%;
    max-width: none;
  }

  body.review-page #review-picker .review-panel {
    grid-area: panel;
    position: sticky;
    top: 12px;
  }
}

/* ——— Imported-games picker (Game Review + Blunder Override) ———
   The account's imported list is the centrepiece of the picker, so it is
   treated as a proper library surface: a framed panel, hairline-separated
   rows, a gold hover sweep, and a slim custom scrollbar. */
.imported-picker-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 14px;
}

.imported-picker-title {
  margin: 0;
  font-family: var(--serif, Georgia, serif);
  font-size: clamp(1.25rem, 2.4vw, 1.6rem);
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--cream);
}

.imported-picker-lead {
  margin: 7px 0 0;
  max-width: 52ch;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.55;
}

.imported-picker-count {
  flex: 0 0 auto;
  padding: 7px 14px;
  border: 1px solid color-mix(in srgb, var(--gold) 28%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--gold) 10%, transparent);
  color: var(--gold);
  font-size: 0.76rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.imported-games-host {
  margin-bottom: 18px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: color-mix(in srgb, var(--bg-2) 55%, transparent);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
}

.imported-games-list {
  display: flex;
  flex-direction: column;
  gap: 0;
  max-height: 430px;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior-y: contain;
  overscroll-behavior-x: none;
  /* Room for the custom scrollbar so it never overlaps a row. */
  padding: 6px;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--gold) 40%, transparent) transparent;
  -webkit-overflow-scrolling: touch;
}

/* Slim gold scrollbar, matching the rest of the product. */
.imported-games-list::-webkit-scrollbar {
  width: 8px;
}

.imported-games-list::-webkit-scrollbar-track {
  background: transparent;
}

.imported-games-list::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--gold) 35%, transparent);
  border-radius: 999px;
}

.imported-games-list::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--gold) 60%, transparent);
}

.imported-game-row {
  position: relative;
  display: grid;
  /* A fourth column for the review badges, so a reviewed game can show its
     numbers without pushing the action label off the row. */
  /* The badge column may shrink (minmax(0,…)) so the chips are never pushed
     past the row edge; the chips themselves wrap onto a second line inside it.
     Without the 0 minimum the column sized to max-content and the row
     overflowed, which clipped the figures instead of showing them. */
  grid-template-columns: 52px minmax(0, 1fr) minmax(0, auto) auto;
  align-items: center;
  gap: 10px 16px;
  width: 100%;
  /* Without this the flex column squashes rows instead of scrolling. */
  flex: 0 0 auto;
  padding: 14px 16px;
  border: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
  border-radius: 10px;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  overflow: hidden;
  transition: background-color 0.2s ease, box-shadow 0.2s ease;
}

/* Hairline separator: the last row has none, so the panel closes cleanly. */
.imported-game-row:last-child {
  border-bottom-color: transparent;
}

.imported-game-row:hover,
.imported-game-row:focus-visible {
  background: color-mix(in srgb, var(--gold) 7%, transparent);
  box-shadow: inset 2px 0 0 var(--gold);
}

.imported-game-row:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--gold) 70%, transparent);
  outline-offset: -2px;
  border-radius: 10px;
}

.imported-game-result {
  flex: 0 0 auto;
  min-width: 52px;
  padding: 5px 8px;
  border: 1px solid color-mix(in srgb, var(--gold) 26%, transparent);
  border-radius: 7px;
  background: color-mix(in srgb, var(--gold) 12%, transparent);
  color: var(--gold);
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.imported-game-copy {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 3px;
}

.imported-game-copy strong {
  overflow: hidden;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--cream);
}

.imported-game-copy small {
  overflow: hidden;
  font-size: 0.78rem;
  letter-spacing: 0.01em;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
}

/* Trailing affordance, revealed on hover so the resting row stays calm. */
.imported-game-action {
  flex: 0 0 auto;
  padding: 5px 12px;
  border: 1px solid color-mix(in srgb, var(--gold) 30%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--gold) 8%, transparent);
  color: var(--gold);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0.55;
  transition: opacity 0.2s ease, background-color 0.2s ease;
}

.imported-game-row:hover .imported-game-action,
.imported-game-row:focus-visible .imported-game-action {
  opacity: 1;
  background: color-mix(in srgb, var(--gold) 16%, transparent);
}

/* ——— Review badges ———
   Shown on a game that has already been analysed, so the picker answers "which
   of these have I looked at, and how did they go?" without re-running anything.

   Every badge is also spelled out in the row's aria-label, so the state is
   never carried by colour or position alone. */

.imported-game-badges {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: 0 0 auto;
  /* The chips carry words now, so a narrow column cannot hold them on one
     line. Wrapping keeps every figure readable; letting them clip would hide
     the accuracy and rating exactly when the column is tightest. */
  flex-wrap: wrap;
  justify-content: flex-end;
  max-width: 100%;
}

.imported-game-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: color-mix(in srgb, var(--bg-3) 70%, transparent);
  color: var(--cream-dim);
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Accuracy reads on a scale, so it gets the accent to make a strong or weak
   game findable at a glance in a long list. */
.imported-game-badge--accuracy {
  border-color: color-mix(in srgb, var(--gold) 32%, var(--line));
  background: color-mix(in srgb, var(--gold) 10%, transparent);
  color: var(--gold);
}

.imported-game-badge--rating.is-up {
  border-color: color-mix(in srgb, #22c55e 40%, var(--line));
  background: color-mix(in srgb, #22c55e 12%, transparent);
  color: #4ade80;
}

.imported-game-badge--rating.is-down {
  border-color: color-mix(in srgb, #ef4444 40%, var(--line));
  background: color-mix(in srgb, #ef4444 12%, transparent);
  color: #fca5a5;
}

/* Issues are now worded ("2 blunders"), not a bare count, so the min-width
   that suited a single digit would only leave an awkward gap. */
.imported-game-badge--issues {
  border-color: color-mix(in srgb, #f59e0b 38%, var(--line));
  background: color-mix(in srgb, #f59e0b 11%, transparent);
  color: #fbbf24;
}

/* Accuracy and performance both carry a word, so they need room for it. The
   base min-width only existed to stop single-digit chips jittering. */
.imported-game-badge--accuracy,
.imported-game-badge--elo,
.imported-game-badge--issues {
  min-width: 0;
  padding: 4px 9px;
}

/* Performance rating. Deliberately cool-toned so it reads as a different
   measure from the warm accuracy chip beside it. */
.imported-game-badge--elo {
  border-color: color-mix(in srgb, var(--series-1) 34%, var(--line));
  background: color-mix(in srgb, var(--series-1) 11%, transparent);
  color: var(--series-1);
}

/* A scanned game with nothing to train. Deliberately quiet: it is an absence
   of problems, not a result worth drawing the eye. */
.imported-game-badge--clean {
  border-color: color-mix(in srgb, var(--data-win) 30%, var(--line));
  background: color-mix(in srgb, var(--data-win) 9%, transparent);
  color: var(--data-win);
}

/* The little word inside each chip. Set back and unbolded so the figure stays
   the thing being read, and so "Accuracy" / "Perf" is legible without
   competing with it. */
.imported-game-badge-key {
  margin-right: 4px;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.72;
}

/* Marks a rating derived from accuracy rather than reported by the platform. */
.review-elo-label.is-estimate {
  font-style: italic;
  opacity: 0.75;
}

/* A reviewed row is subtly marked, so the list can be skimmed for "which ones
   still need looking at" — the point of storing the summaries at all. */
.imported-game-row.is-reviewed {
  background: color-mix(in srgb, var(--gold) 3%, transparent);
}

.imported-game-row.is-reviewed:hover,
.imported-game-row.is-reviewed:focus-visible {
  background: color-mix(in srgb, var(--gold) 7%, transparent);
}

/* On a phone the row has room for the two figures that matter — accuracy and
   performance rating — but not for all four. The extras (rating movement and
   the worded issue counts) fold away rather than the accuracy disappearing
   entirely, which is what left the rows showing no useful figure at all. */
@media (max-width: 640px) {
  .imported-game-row {
    grid-template-columns: 44px minmax(0, 1fr);
    gap: 8px 10px;
    align-items: start;
  }

  /* The chips take their own full-width line under the title. */
  .imported-game-badges {
    grid-column: 1 / -1;
    justify-content: flex-start;
  }

  .imported-game-badge--rating,
  .imported-game-badge--issues {
    display: none;
  }
}

.imported-games-empty {
  margin: 0;
  padding: 34px 24px;
  border: 1px dashed var(--line);
  border-radius: 14px;
  background: color-mix(in srgb, var(--bg-2) 40%, transparent);
  color: var(--muted);
  font-size: 0.92rem;
  line-height: 1.6;
  text-align: center;
}

.review-tabs {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--fill-subtle);
  margin-bottom: 14px;
}

.review-tab {
  border: 0;
  background: transparent;
  color: var(--muted);
  font-family: var(--font);
  font-weight: 700;
  font-size: 0.82rem;
  padding: 8px 14px;
  border-radius: 8px;
  cursor: pointer;
}

.review-tab.is-active {
  background: var(--cream);
  color: var(--on-solid);
}

.review-label {
  display: block;
  margin: 0 0 6px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.review-pgn,
.review-input,
.review-select {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  background: var(--input-bg, var(--bg-2));
  color: var(--cream);
  font-family: var(--font);
  font-size: 0.92rem;
  padding: 10px 12px;
}

.review-pgn {
  resize: vertical;
  min-height: 160px;
  margin-bottom: 12px;
  line-height: 1.45;
}

.review-side-pick {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 16px;
  margin: -2px 0 12px;
}

.review-side-pick-label {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.review-side-opt {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.85rem;
  color: var(--cream);
  cursor: pointer;
}

.review-side-opt input {
  accent-color: var(--gold);
}

.review-scan-row {
  display: grid;
  grid-template-columns: 1fr 1.4fr auto;
  gap: 10px 12px;
  align-items: end;
}

.review-scan-row .review-label {
  grid-row: 1;
}

.review-scan-row .review-select {
  grid-column: 1;
  grid-row: 2;
}

.review-scan-row label[for="review-player"] {
  grid-column: 2;
  grid-row: 1;
}

.review-scan-row .review-input {
  grid-column: 2;
  grid-row: 2;
}

.review-scan-row #review-scan-go {
  grid-column: 3;
  grid-row: 2;
}

.review-game-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 14px;
  max-height: 420px;
  overflow: auto;
}

.review-game-item {
  display: grid;
  grid-template-columns: 3.2rem 1fr auto;
  gap: 12px;
  align-items: center;
  width: 100%;
  text-align: left;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: color-mix(in srgb, var(--bg-2) 55%, transparent);
  color: var(--cream);
  font-family: var(--font);
  cursor: pointer;
}

.review-game-item:hover {
  border-color: var(--gold);
}

.review-game-result {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.review-game-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.review-game-main small {
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.review-game-when {
  color: var(--muted);
  font-size: 0.8rem;
}

.review-workspace-top {
  display: flex;
  /* No wrap. The row holds the game's meta on the left and the view tabs on
     the right; `flex-wrap: wrap` dropped the tabs onto a second line whenever
     the meta ran long, which cost the 33px + 49px this row was merged to save.
     The meta is the part that may be long, so it is the part that gives: it
     truncates and the tabs stay put. */
  flex-wrap: nowrap;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
  min-width: 0;
}

/* The meta is the elastic half; the tabs and the outbound link are the fixed
   half.

   The link is `flex: 0 0 auto` and sits before the tabs, so the row's
   `space-between` puts it at the right end next to them — where a control that
   leaves the site belongs, and where it is not something the truncating meta
   can squeeze off the edge. `margin-left: auto` holds that when the meta is
   long, since space-between alone only splits the free space. */
.review-workspace-top .review-meta {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
}

.review-workspace-top .review-open-game {
  margin-left: auto;
}

.review-workspace-top .review-viewtabs {
  flex: 0 0 auto;
}

.review-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  color: var(--muted);
  font-size: 0.9rem;
}

.review-meta a {
  color: var(--gold);
}

/* ——— Open the game on its own site ———

   A small outbound control, not a word in a sentence. It sits in its own slot in
   the workspace's top row so a long game name cannot truncate it away, which is
   what happened when it was the last thing inside the meta.

   Shaped like a button because it leaves the page, which is the one thing the
   rest of this row does not do — the tabs swap a view, this opens a new tab on
   someone else's site. The arrow is the standard "leaves here" mark, and the
   whole thing is quiet: an accent hairline and a soft fill, so it reads as a
   control you may want without competing with the accuracy figure beside it. */
.review-open-game {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 34px;
  padding: 0 14px;
  border: 1px solid color-mix(in srgb, var(--gold) 34%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--gold) 8%, transparent);
  color: var(--cream);
  font-family: var(--font);
  font-size: 0.84rem;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color 0.16s ease,
    border-color 0.16s ease,
    color 0.16s ease;
}

.review-open-game[hidden] {
  display: none !important;
}

.review-open-game:hover {
  background: color-mix(in srgb, var(--gold) 18%, transparent);
  border-color: color-mix(in srgb, var(--gold) 60%, transparent);
  color: var(--cream);
}

.review-open-game:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

/* Dimmed, not gold, so the outbound arrow keeps the same weight as the label
   instead of becoming the loudest thing in a 34px pill. */
.review-open-game-icon {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  display: block;
  opacity: 0.85;
}

.review-open-game:hover .review-open-game-icon,
.review-open-game:focus-visible .review-open-game-icon {
  opacity: 1;
}

/* The analysis overlay covers the board square while Stockfish works. The
   strip that used to sit above the layout was six pixels tall and read as
   page chrome rather than as the review doing its job. The stage is the only
   element guaranteed square at every viewport (see .review-board-stage), so
   the overlay is anchored to it and the card scales with the board. */
.review-progress {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(10px, 4%, 22px);
  background: rgba(9, 9, 12, 0.55);
  -webkit-backdrop-filter: blur(7px) saturate(1.15);
  backdrop-filter: blur(7px) saturate(1.15);
}

/* Author display rules outrank the UA [hidden] rule, so hidden needs its own
   declaration or the overlay would sit over the board for the whole session. */
.review-progress[hidden] {
  display: none;
}

/* The completion fade: the bar has just reached 100% and a hard cut makes
   that invisible. review.js adds the class, then hides on its own timer. */
.review-progress.is-leaving {
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
}

.review-progress-card {
  width: min(80%, 400px);
  padding: 18px 20px 16px;
  border: 1px solid var(--line-strong);
  border-radius: 16px;
  background: rgba(16, 16, 20, 0.94);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
  text-align: center;
  font-family: var(--font);
}

.review-progress-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.review-progress-title {
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cream);
}

.review-progress-dots {
  display: inline-flex;
  gap: 3px;
  margin-left: 6px;
  vertical-align: baseline;
}

.review-progress-dots i {
  width: 4px;
  height: 4px;
  border-radius: 999px;
  background: var(--gold);
  animation: review-progress-dot 1.1s ease-in-out infinite;
}

.review-progress-dots i:nth-child(2) {
  animation-delay: 0.15s;
}

.review-progress-dots i:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes review-progress-dot {
  0%,
  100% {
    opacity: 0.25;
    transform: translateY(0);
  }
  40% {
    opacity: 1;
    transform: translateY(-2px);
  }
}

.review-progress-pct {
  font-size: 1.9rem;
  font-weight: 800;
  line-height: 1;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

.review-progress-track {
  position: relative;
  height: 10px;
  border-radius: 999px;
  background: var(--bg-3);
  border: 1px solid var(--line);
  overflow: hidden;
}

.review-progress-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 12px;
  min-height: 26px;
}

.review-progress-detail {
  flex: 1;
  min-width: 0;
  text-align: left;
  font-size: 0.78rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.review-cancel-btn {
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font-family: var(--font);
  font-size: 0.78rem;
  font-weight: 700;
  padding: 4px 12px;
  cursor: pointer;
}

.review-cancel-btn:hover:not(:disabled) {
  color: var(--cream);
  border-color: var(--gold);
}

.review-cancel-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

.review-progress-bar {
  position: relative;
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, #b8912a, var(--gold), #ffe08a);
  transition: width 0.3s ease-out;
  overflow: hidden;
}

/* Shimmer sweep: a light band crossing the fill, so a bar that is moving
   slowly still reads as working rather than stuck. */
.review-progress-bar::after {
  content: "";
  position: absolute;
  inset: 0;
  width: 40%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.35), transparent);
  animation: review-progress-sweep 1.4s ease-in-out infinite;
}

@keyframes review-progress-sweep {
  0% {
    transform: translateX(-120%);
  }
  100% {
    transform: translateX(320%);
  }
}

.review-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(260px, 0.85fr);
  gap: 18px;
  align-items: start;
  margin-top: 8px;
}

.review-board-col {
  display: grid;
  /* Column 1 is the eval bar, column 2 the board. The board track is 1fr: the
     outer grid has already reserved exactly --review-board for this column,
     so re-constraining it here is redundant at best and, where the two
     disagreed, the inner narrower value won. */
  grid-template-columns: var(--review-eval-bar) minmax(0, 1fr);
  gap: 10px var(--review-col-gap);
  align-items: start;
}

.review-eval-wrap {
  grid-row: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  /* Stretch with the board row so the bar always matches the board height. */
  height: auto;
  min-height: 0;
  align-self: stretch;
  position: relative;
}

.review-eval-bar {
  flex: 1;
  max-height: 50%;
  width: 14px;
  border-radius: 999px;
  background: var(--bg-3);
  border: 1px solid var(--line-strong);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.review-eval-fill {
  width: 100%;
  height: 50%;
  background: #f4f4f5;
  transition: height 0.25s ease;
}

.review-eval-label {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.65rem;
  font-weight: 700;
  color: var(--cream);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  background: var(--bg);
  padding: 1px 4px;
  border-radius: 4px;
  line-height: 1.2;
  white-space: nowrap;
}

.review-board-shell {
  grid-column: 2;
  width: 100%;
  /* The shell must never be wider than the square --review-board already
     computed, or the board would be laid out at one size and then clipped to
     a smaller box. The old fixed 720px was the reason the review board never
     grew past 640px on a 1080px-tall monitor while the trainer's did: the
     height budget said 716px, and this cap quietly took it back down.

     The desktop block below also pins width AND height to --review-board, so
     on desktop the shell is exactly the square. This keeps the cap
     non-binding everywhere rather than introducing a second number to keep in
     step with --review-board. */
  max-width: 100%;
  min-width: 0;
}

.review-board-stage {
  position: relative;
  /* The square, bounded by the cell on BOTH axes, and derived from the WIDTH.

     The board used to derive its width from the cell's HEIGHT
     (`height: 100%` + `aspect-ratio`). `max-width: 100%` was supposed to clamp
     it when the column was the tighter axis, and it does not: the clamp applies
     to the box, but the board inside is `width: 100%` of a box whose width came
     from the height in the first place. A cell taller than it is wide
     therefore produced a board wider than the box holding it.

     Measured: a 568px shell containing a 605px board, its right edge 6px past
     the start of the report panel — which is the overlap that was reported.

     Deriving from the width instead inverts the risk: a board can then only be
     as wide as its column allows, and `max-height: 100%` clamps the other axis
     by shrinking the square. It cannot spill sideways, which is the one way it
     is allowed to be wrong.

     `min(100%, 100cqh)` was tried for the other order and is not available
     here: cq units need a `container-type` ancestor and silently fall back to
     the small viewport without one. Depending on the width directly needs no
     container. */
  width: 100%;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: 1 / 1;
  margin-inline: auto;
}

.review-board {
  position: relative;
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  /* Fills the stage, which is already a square (see .review-board-stage).

     The board used to carry `aspect-ratio: 1 / 1` as well, and that is what
     made the board zoom without limit. A ratio inside a ratio: the stage takes
     its height from the cell and derives its width from that, the board then
     squares ITSELF against the stage's width and reports that back as content,
     and the next pass has a slightly different number to work from. Sub-pixel
     rounding compounds each time, so the board grew a little on every layout
     pass and never settled.

     One ratio, one box. The stage owns the square; the board just fills it. */
  width: 100%;
  height: 100%;
  /* The border is on the standalone page only. The review page removes it so
     the override controls sit visually below the board rather than inside its
     frame. */
  border: 12px solid var(--line-strong);
  /* Square-edged.

     A 24px radius on a 12px frame did not read as a rounded board so much as a
     rounded picture frame around one: the corners cut into the playing surface,
     the a-file and the 1st rank lost their corner squares to the curve, and the
     grid underneath kept going square while the frame clipped it. A chessboard
     is a grid, and the frame around it should not imply a different shape for
     the thing it contains.

     Zero, on both pages: this one rule is the whole board, since Game Review and
     Blunder Override both render `.review-board` and neither overrides it.

     The border width stays 12px. It is the frame the two boards measure by, and
     the parity tests compare it against the trainer's. */
  border-radius: 0;
  overflow: hidden;
  background: var(--felt, var(--bg-2));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.03), 0 6px 18px rgba(0, 0, 0, 0.12);
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
}

.review-board .square {
  position: relative;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.review-board .square.white {
  background: var(--board-light);
}

.review-board .square.black {
  background: var(--board-dark);
}

.review-board .square img {
  width: 88%;
  height: 88%;
  object-fit: contain;
  pointer-events: none;
}

/* Move classification badge on piece (like chess.com game review) */
.review-board .square .review-piece-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  font-size: 1.35em;
  line-height: 1;
  pointer-events: none;
  z-index: 4;
  width: 1.5em;
  height: 1.5em;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  border: 2px solid var(--gold);
  box-shadow: 0 0 0 2px var(--bg), 0 2px 6px rgba(0,0,0,0.4);
}

/* Ensure badge is visible on both light and dark squares */
.review-board .square.white .review-piece-badge,
.review-board .square.black .review-piece-badge {
  filter: drop-shadow(0 0 1px var(--ink));
}

/* Color the circle border by classification severity */
.review-board .square .review-piece-badge[data-class="brilliant"] { border-color: #a78bfa; }
.review-board .square .review-piece-badge[data-class="best"] { border-color: var(--gold-bright); }
.review-board .square .review-piece-badge[data-class="excellent"] { border-color: #86efac; }
.review-board .square .review-piece-badge[data-class="good"] { border-color: #22c55e; }
.review-board .square .review-piece-badge[data-class="inaccuracy"] { border-color: #fbbf24; }
.review-board .square .review-piece-badge[data-class="mistake"] { border-color: #fb923c; }
.review-board .square .review-piece-badge[data-class="blunder"] { border-color: #ef4444; }

/* Engine best-move arrow — polygon arrow matching the opening trainer's
   arrow-theory appearance, respecting the user's chosen arrow style. */
#board-arrows,
.board-arrows {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 4;
  overflow: visible;
}

#board-arrows .arrow-theory,
.board-arrows .arrow-theory {
  fill: var(--gold);
  stroke: rgba(237, 233, 254, 0.35);
  stroke-width: 0.016;
}

/* MultiPV alternative arrows — colors match Analysis tab rank badges */
.board-arrow-fill.arrow-alt-2 {
  fill: color-mix(in srgb, var(--gold) 35%, var(--muted) 25%);
  stroke: rgba(237, 233, 254, 0.2);
  stroke-width: 0.012;
  opacity: 0.6;
}

.board-arrow-fill.arrow-alt-3 {
  fill: color-mix(in srgb, var(--gold) 20%, var(--muted) 40%);
  stroke: rgba(237, 233, 254, 0.12);
  stroke-width: 0.008;
  opacity: 0.35;
}

/* Best move arrow (line 1) — blue like the Analysis tab rank-1 chip */
#board-arrows .board-arrow-fill.arrow-theory,
.board-arrows .board-arrow-fill.arrow-theory {
  fill: #38bdf8;
  stroke: rgba(56, 189, 248, 0.35);
  stroke-width: 0.016;
}

.review-board .square.review-best-sq::after {
  content: "";
  position: absolute;
  inset: 8%;
  border: 2px solid var(--gold);
  border-radius: 4px;
  pointer-events: none;
}

/* ——— Blunder Override, on this board ———

   Override is played on the review's own square rather than on a second one, so
   it has to bring its own board states. The standalone Blunder Override page
   gets these from style_mistakes.css; this page does not load that file, so
   every rule here was missing and the reader was left with an unstyled square:
   pieces could be picked but nothing showed that they had been, and a wrong move
   was rejected with no shake and no hint.

   Mirrors the mistakes-page values so the same trainer reads identically in both
   places. */

.review-board .square.selected-piece {
  box-shadow: inset 0 0 0 3px var(--gold);
  background: color-mix(in srgb, var(--gold) 24%, var(--felt));
}

.review-board .square.legal-dest {
  position: relative;
}

.review-board .square.legal-dest::after {
  content: "";
  position: absolute;
  width: 28%;
  height: 28%;
  border-radius: 50%;
  background: color-mix(in srgb, #22c55e 70%, transparent);
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  box-shadow: 0 0 8px color-mix(in srgb, #22c55e 50%, transparent);
}

.review-board .square.hint-piece {
  animation: review-hint-pulse 1.1s infinite alternate;
}

@keyframes review-hint-pulse {
  from { box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--gold) 85%, transparent); }
  to   { box-shadow: inset 0 0 0 7px color-mix(in srgb, var(--gold) 30%, transparent); }
}

.review-board.shake-board {
  animation: review-board-shake 0.35s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
}

@keyframes review-board-shake {
  10%, 90% { transform: translate3d(-2px, 0, 0); }
  20%, 80% { transform: translate3d(3px, 0, 0); }
  30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
  40%, 60% { transform: translate3d(4px, 0, 0); }
}

/* The dragged piece follows the cursor as a fixed-position ghost, so it must not
   be able to swallow the pointerup that drops it. Without this the drop lands on
   the ghost instead of the square underneath and the move never registers. */
.review-override-drag-ghost {
  position: fixed;
  z-index: 60;
  width: 46px;
  height: 46px;
  transform: translate(-50%, -50%);
  pointer-events: none;
  opacity: 0.9;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.5));
}

/* Promotion picker. Overlaid on the square rather than placed below it, because
   an inline form would take height from the board and resize it — the one thing
   the review layout cannot afford. `hidden` is beat explicitly for the same
   reason as everywhere else in this file: every `display` here is an author
   declaration and outranks the UA's `[hidden] { display: none }`. */
.review-override-promo {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  background: color-mix(in srgb, var(--overlay-bg) 82%, transparent);
  /* Square, to match the board it covers. It is inset to the stage, so a radius
     here would round the corners of the board's own edge while the board under
     it is square. */
  border-radius: 0;
  z-index: 30;
}

.review-override-promo[hidden] {
  display: none !important;
}

.review-override-promo-title {
  margin: 0;
  font-size: 0.76rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cream);
}

.review-override-promo-options {
  display: flex;
  gap: 12px;
}

.review-override-promo-btn {
  width: 64px;
  height: 64px;
  padding: 6px;
  border-radius: 14px;
  border: 1px solid var(--line-strong);
  background: var(--panel);
  cursor: pointer;
  transition: transform 0.18s ease, border-color 0.18s ease;
}

.review-override-promo-btn img {
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

.review-override-promo-btn:hover {
  border-color: var(--gold);
  transform: translateY(-3px) scale(1.04);
}

/* ——— Transport ———

   A row of buttons under the board, not a bar around them.

   The bar was a filled card with a border and a drop shadow, which made the
   transport look like a panel the board was sitting inside — and with the
   column's own border gone as well, the enclosure had become the loudest thing
   on a page whose subject is a chessboard. The buttons carry their own
   surfaces, so the group does not need one: what remains is a single aligned
   row, and the alignment is what makes it read as one instrument.

   Spacing is done with margin rather than the bar's old padding, because
   padding on a transparent container is invisible — it would have indented the
   row away from the board's edges for no reason. The top margin is the one
   value doing real work: it is the gap between the square and the controls, and
   this column is height-locked, so it is taken straight off the board.

   The row is `flex: 0 0 auto` and never wraps, because the column's rows are
   taller than the cell they are given and a wrapped transport is what pushed
   the controls off the bottom of a document that cannot scroll. */
.review-board-controls {
  grid-column: 2;
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  /* Buttons are centred, so the primary action no longer sits hard against the
     left edge under a wide board — it is part of the group. */
  justify-content: center;
  gap: 6px;
  margin-top: 10px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  flex: 0 0 auto;
}

/* The hairline between the primary action and the step controls. */
.review-ctl-divider {
  flex: 0 0 auto;
  width: 1px;
  height: 22px;
  margin: 0 2px;
  background: var(--line-strong);
}

.review-board-controls button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-width: 38px;
  height: 38px;
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
  color: var(--cream-dim);
  font-family: var(--font);
  font-size: 0.9rem;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color 0.16s ease,
    border-color 0.16s ease,
    color 0.16s ease,
    transform 0.16s ease;
}

.review-board-controls button:hover:not(:disabled) {
  background: var(--hover-fill);
  border-color: var(--line-strong);
  color: var(--cream);
}

.review-board-controls button:active:not(:disabled) {
  transform: translateY(1px);
}

.review-board-controls button:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

/* Disabled means "this would do nothing", which is a real state now: the
   transport is told where the reader is, so Previous on the starting position
   and Next at the end are dimmed instead of silently inert. */
.review-board-controls button:disabled {
  color: color-mix(in srgb, var(--muted) 42%, transparent);
  cursor: default;
  opacity: 0.55;
}

.review-ctl-glyph {
  font-size: 0.82em;
  line-height: 1;
  opacity: 0.9;
}

.review-ctl-label {
  font-size: 0.86rem;
  letter-spacing: 0.01em;
}

/* The primary action: the one button that is meant to be pressed first. */
.review-board-controls .review-ctl-primary {
  height: 40px;
  padding: 0 16px;
  border-color: color-mix(in srgb, var(--gold) 46%, transparent);
  border-radius: 10px;
  background: linear-gradient(
    180deg,
    var(--gold-bright),
    var(--gold)
  );
  color: var(--on-gold);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 4px 14px color-mix(in srgb, var(--gold) 26%, transparent);
}

.review-board-controls .review-ctl-primary:hover:not(:disabled) {
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--gold-bright) 88%, white),
    var(--gold-bright)
  );
  border-color: color-mix(in srgb, var(--gold) 70%, transparent);
  color: var(--on-gold);
}

.review-board-controls .review-ctl-primary:disabled {
  background: color-mix(in srgb, var(--bg-3) 80%, transparent);
  border-color: var(--line);
  color: color-mix(in srgb, var(--muted) 40%, transparent);
  box-shadow: none;
  opacity: 1;
}

/* The position readout is gone from the REVIEW transport.

   It sat at the end of the row reading the move number, next to a row of
   arrows. Once the redundant ply count was cut it was a single number floating
   there, competing with the controls and answering a question the board and the
   coach note already answer between them.

   The RULE is kept, not deleted: the Blunder Override toolbar reuses both
   `.review-board-controls` and `.review-ctl-readout`, and its readout — "Position
   2 / 5" — is a position out of a queue, which is a different fact from a move
   number and is not anywhere else on the page. Deleting the rule with the
   element would have left that readout as unstyled body text in the middle of
   the button row. Scoped away from the review transport, which no longer has
   one, rather than removed.

   Its `aria-live` region went with the review copy, which is deliberate: the
   move number is announced by the button that changed it and by the coach
   note's own live region, so a screen reader loses nothing. */

.review-ctl-readout {
  flex: 0 0 auto;
  margin-left: 4px;
  padding: 0 10px;
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* The best-line readout is gone.

   It was a principal variation in words — "Best: a8=Q# · a8=Q#" — which read as
   noise: it repeated the moves list, it was unbounded text sitting directly
   above the board, and every line of it came out of the square. The engine's
   suggestion is still shown where it carries meaning, as the green arrow on the
   board, drawn from the same `bestMove`. */

.review-coach {
  grid-column: 2;
  margin: 0;
  padding: 12px 14px;
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  background: color-mix(in srgb, var(--gold) 6%, var(--panel));
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  /* Hug the top of its track instead of stretching to the cap. A grid item
     stretches by default, which filled the whole track whatever the note said:
     a one-line note in a 220px box, with the empty band that is the gap in the
     screenshot. The track is the cap; the panel is the note. */
  align-self: start;
  /* Minimum height: the panel must never shrink below a readable box while
     the analysis runs or the user steps through the game, so the panel size
     stays stable and the board column never shifts. */
  min-height: 60px;
}

/* Now an inline SVG rather than a bitmap (see _coach_avatar.html), so this needs
   `display: block` to avoid the inline-element baseline gap an <img> did not
   have, and no `object-fit`, which only applied to replaced content. The plate
   and figure are painted by the SVG itself from the --coach-avatar-* tokens,
   which is what makes it theme-aware. */
.review-coach-avatar,
.coach-avatar {
  display: block;
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--gold) 35%, var(--line-strong));
  background: var(--bg-3);
}

/* Avatar palette. Defined here rather than on :root because the same tokens are
   consumed inside an SVG in the document body, and they follow the theme. */
:root,
[data-theme="dark"] {
  --coach-avatar-bg: #101c2a;
  --coach-avatar-bg-2: #0b141f;
  --coach-avatar-figure: #eef4fa;
  --coach-avatar-accent: var(--gold-bright, #7cff4a);
}

[data-theme="light"] {
  --coach-avatar-bg: #eaf3ea;
  --coach-avatar-bg-2: #dceadf;
  --coach-avatar-figure: #0b1a10;
  --coach-avatar-accent: var(--gold, #0f7d28);
}

/* The coach note shows in full, and never changes the board's size.

   The board's size is fixed by the grid row above, not by this panel, so the
   two are decoupled: the note scrolls inside a fixed-height box and the square
   stays the same on every move of every game.

   The cap is what that box is sized to. It is viewport-relative, because the
   board is the point of the page and a long note must not be able to crowd it
   out on a short screen — there the board wins and the note scrolls.
*/.review-coach-body {
  min-width: 0;
  max-height: none;
  /* The panel is as tall as its note, up to the cap.

     `height: 100%` here would stretch the panel to the full track whatever the
     note said, which is the gap in the screenshot: a one-line note in a 220px
     box. So the box is content-sized and the TRACK is the cap — a note that
     overruns is capped by the track and scrolls inside this frame, while a
     short note takes only what it needs and gives the rest to the board.

     `max-height: 100%` is the clamp: the frame cannot exceed its track, so a
     very long note scrolls rather than growing the column and pushing the
     transport controls off the bottom of the window. */
  overflow: visible;
}

.review-coach-body::-webkit-scrollbar {
  width: 6px;
}

.review-coach-body::-webkit-scrollbar-track {
  background: transparent;
}

.review-coach-body::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--gold) 28%, transparent);
  border-radius: 999px;
}


.review-coach-kicker {
  margin: 0 0 6px;
  font-size: 0.64rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gold);
}

.review-coach-text {
  margin: 0;
  color: var(--cream);
  font-size: 0.95rem;
  line-height: 1.55;
}

.review-coach.is-critical {
  border-color: color-mix(in srgb, #f87171 45%, var(--line-strong));
  background: color-mix(in srgb, #f87171 8%, var(--panel));
}

.review-coach.is-critical .review-coach-kicker {
  color: #fca5a5;
}

.review-coach.is-strong {
  border-color: color-mix(in srgb, var(--gold) 40%, var(--line-strong));
}

.review-side {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.review-report {
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  background: var(--panel);
  padding: 16px;
}

.review-report-kicker {
  margin: 0;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.review-accuracy {
  margin: 6px 0 10px;
  font-size: 2.2rem;
  font-weight: 800;
  letter-spacing: -0.04em;
  color: var(--cream);
  line-height: 1;
}

/* Both figures side by side. A single accuracy number had nothing to be read
   against — not the opponent, and not the move counts listed below it. */
.review-accuracy-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin: 6px 0 10px;
}

.review-accuracy-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: color-mix(in srgb, var(--bg-2) 55%, transparent);
}

.review-accuracy-cell .review-accuracy {
  margin: 0;
  font-size: 1.7rem;
  font-variant-numeric: tabular-nums;
}

/* The opponent is context, not the headline, so it is set back a step. */
.review-accuracy-cell.is-opp .review-accuracy {
  font-size: 1.35rem;
  color: var(--cream-dim);
}

.review-accuracy-name {
  overflow: hidden;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* States the gap in words, so the pair is not left to be subtracted by hand. */
.review-accuracy-note {
  margin: 0 0 10px;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--muted);
}

.review-accuracy-note.is-ahead {
  color: #4ade80;
}

.review-accuracy-note.is-behind {
  color: #fbbf24;
}

.review-elo {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin: 0 0 12px;
}

.review-elo-card {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: color-mix(in srgb, var(--bg-2) 55%, transparent);
}

.review-elo-label {
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.review-elo-card strong {
  font-size: 1.15rem;
  font-weight: 800;
  color: var(--cream);
  font-variant-numeric: tabular-nums;
}

.review-class-pill {
  display: inline-flex;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: var(--bg-2);
  color: var(--cream);
}

.review-class-pill.brilliant { background: color-mix(in srgb, #22d3ee 22%, transparent); color: #67e8f9; }
.review-class-pill.best,
.review-class-pill.excellent { background: color-mix(in srgb, var(--gold) 28%, transparent); color: var(--gold-bright); }
.review-class-pill.good { background: color-mix(in srgb, #4ade80 22%, transparent); color: #86efac; }
.review-class-pill.inaccuracy { background: color-mix(in srgb, #fbbf24 22%, transparent); color: #fcd34d; }
.review-class-pill.mistake { background: color-mix(in srgb, #fb923c 22%, transparent); color: #fdba74; }
.review-class-pill.blunder { background: color-mix(in srgb, #f87171 22%, transparent); color: #fca5a5; }

.review-counts {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: grid;
  gap: 6px;
}

.review-counts li {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.86rem;
  color: var(--muted);
}

.review-counts strong {
  color: var(--cream);
  font-variant-numeric: tabular-nums;
}

/* review.js renders each count as a jump-to button; the static homepage
   showcase keeps plain <li> markup and is unaffected by these rules. */
.review-count-jump {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 3px 8px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font-family: var(--font);
  font-size: 0.86rem;
  text-align: left;
  cursor: pointer;
}

.review-count-jump > span {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.review-count-jump:hover:not(:disabled) {
  background: color-mix(in srgb, var(--bg-2) 80%, transparent);
}

.review-count-jump:disabled {
  cursor: default;
  opacity: 0.5;
}

/* Class dot symbols — chess-like icons replacing the old colored circles. */
.review-class-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: auto;
  height: auto;
  background: transparent;
  font-size: 0.7rem;
  line-height: 1;
}
.review-class-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  font-size: 0;
}
.review-class-dot::before { display: block; line-height: 1; font-size: 11px; }
.review-class-dot.brilliant { background: color-mix(in srgb, #8b5cf6 22%, transparent); }
.review-class-dot.brilliant::before { content: "❗"; color: #a78bfa; }
.review-class-dot.best { background: color-mix(in srgb, var(--gold) 22%, transparent); }
.review-class-dot.best::before      { content: "★";  color: var(--gold-bright); }
.review-class-dot.excellent { background: color-mix(in srgb, #4ade80 18%, transparent); }
.review-class-dot.excellent::before { content: "👍"; color: #86efac; }
.review-class-dot.good { background: color-mix(in srgb, #4ade80 14%, transparent); }
.review-class-dot.good::before      { content: "✓"; color: #4ade80; }
.review-class-dot.inaccuracy { background: color-mix(in srgb, #fbbf24 18%, transparent); }
.review-class-dot.inaccuracy::before { content: "?";   color: #fbbf24; }
.review-class-dot.mistake { background: color-mix(in srgb, #fb923c 18%, transparent); }
.review-class-dot.mistake::before   { content: "!";   color: #fb923c; }
.review-class-dot.blunder { background: color-mix(in srgb, #ef4444 18%, transparent); }
.review-class-dot.blunder::before   { content: "⁇"; color: #ef4444; }
.review-move .review-class-dot { width: auto; height: auto; font-size: 0.6rem; }

.review-moves-wrap {
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  background: var(--panel);
  padding: 14px;
  max-height: 520px;
  overflow: auto;
}

.review-moves-wrap h2 {
  margin: 0 0 10px;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Header row with the critical-moment jump buttons (review.js jumpToIssue). */
.review-moves-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin: 0 0 10px;
}

.review-moves-head h2 {
  margin: 0;
}

.review-moves-nav {
  display: flex;
  gap: 4px;
}

.review-moves-nav button {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--fill-subtle);
  color: var(--muted);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
}

.review-moves-nav button:hover {
  color: var(--cream);
  border-color: var(--gold);
}

.review-move-row {
  display: grid;
  grid-template-columns: 2rem 1fr 1fr;
  gap: 6px;
  margin-bottom: 4px;
  align-items: center;
}

.review-move-num {
  color: var(--muted);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}

.review-move {
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--cream);
  font-family: var(--font);
  font-size: 0.88rem;
  font-weight: 650;
  text-align: left;
  padding: 5px 8px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.review-move-emoji {
  font-size: 0.78rem;
  line-height: 1;
}

.review-move:hover,
.review-move.is-active {
  background: color-mix(in srgb, var(--bg-2) 80%, transparent);
}

.review-move.is-active {
  border-color: var(--line-strong);
}

.review-move.brilliant { box-shadow: inset 3px 0 0 #22d3ee; }
.review-move.best { box-shadow: inset 3px 0 0 var(--gold); }
.review-move.excellent { box-shadow: inset 3px 0 0 #86efac; }
.review-move.good { box-shadow: inset 3px 0 0 #4ade80; }
.review-move.inaccuracy { box-shadow: inset 3px 0 0 #fbbf24; }
.review-move.mistake { box-shadow: inset 3px 0 0 #fb923c; }
.review-move.blunder { box-shadow: inset 3px 0 0 #f87171; }

.review-move.is-empty {
  visibility: hidden;
  pointer-events: none;
}

.review-empty-moves {
  margin: 0;
  color: var(--muted);
}

@media (max-width: 900px) {
  .review-root {
    max-width: 100%;
    padding: 4px 0 32px;
    gap: 14px;
    overflow-x: hidden;
  }

  .review-picker {
    padding: 12px;
    border-radius: 12px;
  }

  .review-tabs {
    width: 100%;
    display: grid;
    grid-template-columns: 1fr 1fr;
  }

  .review-tab {
    width: 100%;
    min-height: 44px;
  }

  .review-pgn,
  .review-input,
  .review-select,
  .btn {
    font-size: 16px; /* prevent iOS Safari zoom on focus */
  }

  .review-layout {
    grid-template-columns: 1fr;
    gap: 14px;
  }

  .review-board-col {
    grid-template-columns: 22px minmax(0, 1fr);
    gap: 8px;
    width: 100%;
    max-width: 100%;
  }

  .review-board-shell {
    max-width: 100%;
  }

  .review-eval-wrap {
    height: auto;
    max-height: none;
    align-self: stretch;
  }

  .review-eval-wrap {
    /* Match board height via stretch against square board row */
    min-height: 0;
  }

  .review-board-col > .review-eval-wrap {
    height: auto;
    align-self: stretch;
  }

  .review-scan-row {
    grid-template-columns: 1fr;
  }

  .review-scan-row .review-label,
  .review-scan-row .review-select,
  .review-scan-row .review-input,
  .review-scan-row #review-scan-go,
  .review-scan-row label[for="review-player"] {
    grid-column: 1;
    grid-row: auto;
  }

  .review-scan-row #review-scan-go {
    width: 100%;
    min-height: 44px;
  }

  .review-board-controls {
    width: 100%;
  }

  .review-board-controls button {
    min-height: 44px;
    min-width: 44px;
  }

  .review-coach {
    grid-template-columns: 48px minmax(0, 1fr);
    gap: 10px;
    padding: 10px;
  }

  .review-coach-avatar {
    width: 48px;
    height: 48px;
  }

  .review-elo {
    grid-template-columns: 1fr 1fr;
  }

  .review-moves-wrap {
    max-height: none;
  }

  .review-workspace-top {
    flex-direction: column;
    align-items: stretch;
  }

  /* Stacked, the row is a full-width column, so the auto margin that pinned the
     link to the right edge on desktop would leave it floating against the
     right-hand wall with nothing beside it. It aligns to the text instead. */
  .review-workspace-top .review-open-game {
    margin-left: 0;
    align-self: flex-start;
  }
}

@supports (height: 100dvh) {
  @media (max-width: 900px) {
    .review-board-shell {
      width: min(100%, calc(100dvw - 56px));
    }
  }
}

/* ==========================================================================
   Game Review — enlarged workspace.

   style_review.css is also loaded by the Blunder Override page and by the home
   showcase, which reuse these same .review-* classes at their original sizes.
   Every override below is therefore anchored to body.review-page, so those two
   surfaces keep rendering exactly as before.
   ========================================================================== */

body.review-page .page-shell {
  width: min(1700px, calc(100% - 24px));
  max-width: none;
}

body.review-page .review-root {
  max-width: 1900px;
  /* No bottom padding, and no gap. The board view is locked to 100dvh and does
     not scroll, so anything reserved below the last row is not breathing room —
     it is height the square cannot have. The 56px bottom and the 24px gap were
     80px of the viewport that nothing could reach, on the normal case where the
     column fills the frame exactly.

     The top padding is kept at 18px: that is the one edge with a real edge to
     breathe against (the header), and losing it would make the meta row touch
     the header. The Insights view is the exception — it does scroll, and it
     re-adds its own bottom padding further down. */
  padding: 18px 0 0;
  gap: 0;
}

body.review-page .review-layout {
  grid-template-columns: minmax(0, 1.45fr) minmax(340px, 0.9fr);
  gap: 26px;
  margin-top: 10px;
}

body.review-page .review-board-col {
  /* The board track takes the WHOLE column rather than a second opinion on how
     wide the board may be. The outer grid has already reserved exactly
     --review-board for this column, so constraining it again here is redundant
     at best — and where the two disagreed the inner, narrower value won and
     the board stayed small inside a column sized for a bigger one. The board
     squares itself against this track (see .review-board-stage). */
  grid-template-columns: var(--review-eval-bar) minmax(0, 1fr);
  gap: 8px var(--review-col-gap);
}

/* The coach has left this column (it now leads the report rail), so the board
   cell IS row 1: the eval bar and the board shell are both pinned there, and
   the transport takes row 2 below. The shell still needs its explicit row —
   relying on auto-placement is what once left the bar stranded beside the
   coach instead of beside the board. */
body.review-page .review-eval-wrap {
  grid-row: 1;
}

body.review-page .review-eval-bar {
  width: 24px;
}

body.review-page .review-eval-label {
  /* The eval bar is 50% of the wrap height and centred, so its bottom
     edge sits at 75% of the wrap. Position the label right below it. */
  top: 75%;
  bottom: auto;
  font-size: 0.85rem;
}

/* The board's own box is set in the "Board sizing" block at the foot of this
   file, which replaces the max-width cap that used to live here. That cap was
   the reason the review board outgrew the trainer's on a large screen. */
body.review-page .review-board-shell {
  grid-row: 1;
}

/* The review page removes the 12px border so the override controls sit
   below the board rather than inside the frame. The stage's aspect-ratio
   already squares the board against its cell; the border was a second
   constraint that doubled as the visual frame. Without it the board uses
   the full stage area, which naturally includes the 12px the border
   claimed — so the square is the same size it was before. */
body.review-page .review-board {
  border: none;
}

body.review-page .review-board-controls {
  gap: 6px;
  padding: 7px;
  border-radius: 15px;
}

/* Override controls are now in the sidebar tab, not the board column.
   Remove old board-column positioning. */
body.review-page .review-override-controls {
  align-self: start;
}

body.review-page .review-board-controls button {
  min-width: 42px;
  height: 42px;
  padding: 0 12px;
  border-radius: 10px;
  font-size: 0.95rem;
}

body.review-page .review-board-controls .review-ctl-primary {
  height: 44px;
  padding: 0 20px;
  border-radius: 11px;
  font-size: 1rem;
}

/* Coach — now the first thing in the report rail, directly above the Accuracy
   panel: the explanation sits beside the square instead of consuming the
   height-locked column above it, and it is on screen before anything else in
   the rail is. The avatar keeps its full 76px; the rail is wide enough for it. */
body.review-page .review-coach {
  grid-template-columns: 76px minmax(0, 1fr);
  gap: 16px;
  padding: 18px 20px;
  border-radius: 16px;
  /* Same minimum height as the base rule: the panel never shrinks
     below a readable box, so the board column never shifts. */
  min-height: 60px;
}

body.review-page .review-coach-avatar {
  width: 76px;
  height: 76px;
  border-radius: 16px;
}

/* The body is the scrolling box. `min-height: 0` is what lets a grid item
   shrink below its content instead of being held at min-content — without it
   the note is the full height of the text and the article's `overflow: hidden`
   clips it, which is the overlap in a different guise: no scrollbar, no text.
   `minmax(0, 1fr)` on the column above is the same idea horizontally, so a
   long unbroken move cannot widen the panel either. */
body.review-page .review-coach-body {
  min-height: 0;
  align-self: stretch;
  /* The cap, carried here now that the board column's track is gone. The
     article is content-sized in the rail, so a percentage max-height would
     resolve against an indefinite height and be treated as none — the exact
     failure the old `height: 100%` on the article existed to prevent. Written
     in viewport units instead: the same min(176px, 19vh) the track enforced,
     minus the article's 32px of vertical padding, so the panel never grows
     past the cap. A longer note scrolls inside this frame rather than
     stretching the panel and pushing everything below it down the rail. */
  overflow: visible;
   max-height: none;
}

/* Inside the Analysis tab, the coach body has a fixed height so the engine
   lines, override button and transport arrows below it never shift as the
   reader steps through moves with different-length notes. The text font is
   kept at a readable size; long notes scroll inside the fixed box. */
body.review-page #review-side-analysis .review-coach-body {
  height: 150px;
  overflow-y: auto;
  align-self: auto;
}

/* Make the coach text slightly smaller inside the Analysis tab so more of
   a long note fits before scrolling. */
body.review-page #review-side-analysis .review-coach-text {
  font-size: 1rem;
  line-height: 1.5;
  min-height: 0;
}

body.review-page .review-coach-kicker {
  margin-bottom: 8px;
  font-size: 0.72rem;
}

body.review-page .review-coach-text {
  font-size: 1.22rem;
  line-height: 1.7;
  /* Floor only: without it a one-line note collapses the panel to a stub and
     the Accuracy card below jumps up a few lines on the next step. The floor
     holds short notes at a steady height; longer ones still grow to the
     frame's cap. Nothing in script measures this — the cap is the only bound,
     and the tests assert that stays true. */
  min-height: 4.2em;
}

/* Side panel scaled up so it doesn't look small next to the larger board. */
body.review-page .review-side {
  grid-column: 3;
  align-self: stretch;
  gap: 18px;
}

body.review-page .review-report {
  padding: 24px;
  border-radius: 16px;
  border: 1px solid var(--line-strong);
  background: var(--panel);
}

body.review-page .review-report-kicker {
  font-size: 0.74rem;
}

/* ——— Player avatars in the Report tab ——— */
body.review-page .review-player-avatars {
  display: flex;
  align-items: center;
  gap: 12px;
  justify-content: center;
  margin-bottom: 16px;
}

body.review-page .review-player-avatar {
  display: flex;
  align-items: center;
  gap: 8px;
}

body.review-page .review-player-avatar-letter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--on-solid, #fff);
  flex-shrink: 0;
}

body.review-page .review-player-avatar.is-white .review-player-avatar-letter {
  background: #eee;
  color: #222;
}

body.review-page .review-player-avatar.is-black .review-player-avatar-letter {
  background: #111;
  color: #eee;
}

body.review-page .review-player-avatar-name {
  font-size: 0.85rem;
  line-height: 1.3;
  color: var(--cream);
  max-width: 10ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.review-page .review-player-vs {
  font-size: 0.72rem;
  color: var(--muted);
  font-style: italic;
}

/* Eval chart in the Report tab sits above the accuracy row. */
body.review-page #review-report-evals-figure {
  margin-bottom: 18px;
}

body.review-page .review-accuracy {
  margin: 8px 0 12px;
  font-size: 2.7rem;
}

body.review-page .review-elo {
  gap: 10px;
  margin-bottom: 14px;
}

body.review-page .review-elo-card {
  padding: 10px 12px;
  border-radius: 12px;
}

body.review-page .review-elo-label {
  font-size: 0.68rem;
}

body.review-page .review-elo-card strong {
  font-size: 1.35rem;
}

body.review-page .review-class-pill {
  padding: 6px 13px;
  font-size: 0.8rem;
}

body.review-page .review-counts {
  gap: 8px;
  margin-top: 16px;
}

body.review-page .review-counts li,
body.review-page .review-count-jump {
  font-size: 0.95rem;
}

body.review-page .review-count-jump {
  padding: 5px 10px;
}

body.review-page .review-class-dot {
  width: auto;
  height: auto;
  font-size: 0.75rem;
}

body.review-page .review-move .review-class-dot {
  width: auto;
  height: auto;
  font-size: 0.65rem;
}

body.review-page .review-moves-wrap {
  padding: 18px;
  border-radius: 16px;
  max-height: 620px;
}

body.review-page .review-moves-wrap h2 {
  font-size: 0.78rem;
}

body.review-page .review-moves-nav button {
  width: 30px;
  height: 30px;
  font-size: 1.05rem;
}

body.review-page .review-move-row {
  grid-template-columns: 2.4rem 1fr 1fr;
  gap: 8px;
  margin-bottom: 6px;
}

body.review-page .review-move-num {
  font-size: 0.9rem;
}

body.review-page .review-move {
  padding: 7px 10px;
  border-radius: 8px;
  font-size: 1rem;
}

body.review-page .review-move-emoji {
  font-size: 0.9rem;
}

/* Responsive guards.
   These must restate the single-column/collapsed values explicitly: the rules
   above are body-scoped, so they outrank the shared 900px media query and would
   otherwise keep two columns and the wide eval gutter on a phone.

   The board's max-width caps that used to live here are GONE. They are what
   made the review board a different size from the trainer's: it was capped at
   880px, then 760px under 1400px, then 100% under 1100px, so the two boards
   matched only near 1100px. The board is now sized by --review-board in the
   "Board sizing" block at the foot of this file, on every breakpoint. */
@media (max-width: 1400px) {
  body.review-page .review-layout {
    /* Same three columns; only the board's own cap used to change here. */
    grid-template-columns: minmax(0, 1fr) var(--review-board) minmax(0, 1fr);
  }
}

@media (max-width: 1100px) {
  body.review-page .review-coach-text {
    font-size: 1.1rem;
  }
}

@media (max-width: 900px) {
  body.review-page .page-shell {
    width: calc(100% - 16px);
  }
  body.review-page .review-root {
    max-width: 100%;
    padding: 10px 0 40px;
  }
  body.review-page .review-layout {
    grid-template-columns: 1fr;
    gap: 14px;
  }
  body.review-page .review-board-col {
    grid-template-columns: 22px minmax(0, 1fr);
    gap: 8px;
  }
  body.review-page .review-coach {
    grid-template-columns: 56px minmax(0, 1fr);
    gap: 12px;
    padding: 14px 16px;
  }
  body.review-page .review-coach-avatar {
    width: 56px;
    height: 56px;
    border-radius: 12px;
  }
  body.review-page .review-coach-text {
    font-size: 1.04rem;
    line-height: 1.6;
  }
  body.review-page .review-accuracy {
    font-size: 2.2rem;
  }
  body.review-page .review-moves-wrap {
    max-height: 420px;
  }
}

/* ==========================================================================
   Insights view (Board / Insights tabs inside the workspace).

   Scoped to body.review-page for the same reason as the block above: the Blunder
   Override page and the home showcase share this stylesheet.
   ========================================================================== */

body.review-page .review-viewtabs {
  display: flex;
  gap: 6px;
  padding: 5px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--panel);
  align-self: flex-start;
}

body.review-page .review-viewtabs button {
  min-height: 40px;
  padding: 0 1.35rem;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-family: var(--font);
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  cursor: pointer;
  /* The padlock sits inside the pill, so the label and the state travel
     together and the tab does not change width when it is selected. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  transition: background 0.2s ease, color 0.2s ease;
}

/* The padlock on a locked tab. Muted rather than gold, so it does not read as
   an active state or as an invitation to click through to the report — the tab
   is still clickable, but what is behind it is not free. */
body.review-page .review-viewtab-lock {
  display: inline-flex;
  align-items: center;
  opacity: 0.7;
}

body.review-page .review-viewtab-lock svg {
  width: 11px;
  height: 11px;
  display: block;
}

body.review-page .review-viewtab-lock,
body.review-page .review-viewtabs button:hover .review-viewtab-lock {
  color: inherit;
}

/* Selected and locked: the pill still fills, so the tab does not look broken —
   it is the current view, and the content behind it is what is behind the
   paywall. */
body.review-page .review-viewtabs button[data-locked].is-active .review-viewtab-lock {
  opacity: 0.9;
}

body.review-page .review-viewtabs button:hover {
  color: var(--cream);
}

body.review-page .review-viewtabs button.is-active {
  background: linear-gradient(135deg, var(--gold), var(--gold-bright));
  color: var(--on-gold);
  box-shadow: 0 2px 10px color-mix(in srgb, var(--gold) 30%, transparent);
}

body.review-page .review-viewtabs button:focus-visible {
  outline: 2px solid var(--gold-bright);
  outline-offset: 2px;
}

body.review-page #review-view-insight {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 2px 26px 26px;
}

body.review-page #review-view-insight[hidden] {
  display: none;
}

/* ——— Insights: the locked state ————————————————————————————————————————
   Reviewing your own games is free and unlimited once signed in. Turning that
   review into a report is what Plus and Unlimited pay for, so for a free
   account the tab is present and clickable — the reader is not stopped at a
   wall they never saw — and the panel behind it is blurred with the upgrade
   card on top.

   Two things this deliberately does not do:

   · It does not hide the tab. A tab that vanishes reads as a bug, and a reader
     who cannot see the feature cannot decide whether they want it. Showing it
     greyed is the honest state: the feature exists, it costs something, and here
     is what it costs.

   · It does not remove the content from the DOM or the tab order. The panel is
     a lot of already-rendered markup and rendering it only for paid accounts
     would let the two versions drift apart. The blur sits on an inner wrapper
     that is `inert` and `aria-hidden` when locked, so the blurred sections
     cannot be tabbed into or read out — a screen reader reading a locked
     report is worse than one reading nothing, because it describes a page the
     reader does not have. */

body.review-page #review-view-insight[data-locked] {
  /* A column, so the blur layer and the card stack over the content. */
  position: relative;
}

body.review-page .review-insight-locked-body {
  display: contents;
}

/* The blur. Applied to the content rather than to the panel so the panel keeps
   its own background — blurring the panel itself would blur its own edge
   against the page and produce a grey rectangle with no sense of content under
   it. `-webkit-filter` alongside because Safari still needs the prefix for
   `blur()` on an element rather than a backdrop. */
body.review-page #review-view-insight[data-locked] .review-insight-locked-body {
  display: block;
  filter: blur(7px) saturate(0.7);
  opacity: 0.5;
  /* No pointer events: the blurred charts have hover tooltips, and a locked
     reader must not be able to pull figures out of a report they cannot read.
     `aria-hidden` and `inert` in the markup handle the same thing for a
     keyboard and a screen reader. */
  pointer-events: none;
  /* Capped, so a long locked report is a preview rather than a page of grey
     text the reader has to scroll past before reaching the card. */
  max-height: 460px;
  overflow: hidden;
  /* Fades the cut-off edge, so it reads as "there is more" rather than as a
     clipped box. */
  -webkit-mask-image: linear-gradient(180deg, #000 62%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 62%, transparent 100%);
}

body.review-page .review-insight-lock {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  /* A short inset so the blurred content stays visible around the edges. The
     card is the thing being read, and a full-bleed cover would hide the very
     shape of the thing being offered. */
  padding: 56px 24px 24px;
  text-align: center;
  /* Sits on top of the blur, so the content behind it is only ever seen
     through this card's own transparent edge. */
  background: radial-gradient(
    ellipse 70% 60% at 50% 42%,
    color-mix(in srgb, var(--bg) 92%, transparent),
    transparent 78%
  );
  animation: om-fade-in 0.2s ease-out both;
}

body.review-page .review-insight-lock-badge {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border: 1px solid color-mix(in srgb, var(--gold) 34%, var(--line));
  border-radius: 12px;
  color: var(--gold);
  background: color-mix(in srgb, var(--gold) 10%, transparent);
}

body.review-page .review-insight-lock-badge svg {
  width: 20px;
  height: 20px;
  display: block;
}

body.review-page .review-insight-lock-title {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.2rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--cream);
}

body.review-page .review-insight-lock-copy {
  margin: 0;
  max-width: 46ch;
  color: var(--cream-dim);
  font-size: 0.86rem;
  line-height: 1.55;
}

body.review-page .review-insight-lock-cta {
  margin-top: 4px;
  text-decoration: none;
}

body.review-page .review-insight-lock-note {
  margin: 0;
  color: var(--muted);
  font-size: 0.74rem;
}

@media (prefers-reduced-motion: reduce) {
  body.review-page .review-insight-lock {
    animation: none;
  }
}

/* One continuous surface: sections are separated by hairline rules rather than
   floating cards with gaps between them. */
body.review-page .review-insight-block {
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  padding: 26px 0;
}

body.review-page .review-insight-title {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--cream);
  letter-spacing: -0.01em;
}

body.review-page .review-insight-note {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 0.95rem;
  line-height: 1.55;
}

body.review-page .review-insight-headline {
  margin: 12px 0 0;
  font-size: 1.22rem;
  line-height: 1.7;
  color: var(--cream);
  max-width: 62ch;
}

body.review-page .review-insight-table {
  width: 100%;
  margin-top: 16px;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

body.review-page .review-insight-table th,
body.review-page .review-insight-table td {
  padding: 11px 12px;
  text-align: right;
  border-bottom: 1px solid var(--line);
  font-size: 1rem;
}

body.review-page .review-insight-table thead th {
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

body.review-page .review-insight-table tbody th[scope="row"] {
  text-align: left;
  font-weight: 700;
  color: var(--cream);
}

body.review-page .review-insight-table tbody tr:last-child th,
body.review-page .review-insight-table tbody tr:last-child td {
  border-bottom: 0;
}

body.review-page .review-insight-edge.is-ahead {
  color: var(--data-win);
  font-weight: 700;
}

/* Phase table: the bar and the figure sit together so the number can be read
   without mentally matching it to a length. */
body.review-page .review-insight-table tbody th[scope="row"] small {
  display: block;
  margin-top: 2px;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--muted);
}

body.review-page .review-insight-phasecell {
  display: flex;
  align-items: center;
  gap: 8px;
  font-variant-numeric: tabular-nums;
}

body.review-page .review-insight-phasecell > span {
  flex: 0 0 auto;
  min-width: 4.2em;
}

/* Bars share one scale across the table (set inline as --bar) so two rows can
   be compared directly. */
body.review-page .review-insight-bar {
  position: relative;
  flex: 1 1 auto;
  min-width: 40px;
  height: 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bg-3) 70%, transparent);
  overflow: hidden;
}

body.review-page .review-insight-bar::after {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--bar, 0%);
  border-radius: inherit;
  content: "";
}

body.review-page .review-insight-bar.is-you::after {
  background: linear-gradient(90deg, color-mix(in srgb, var(--gold) 55%, transparent), var(--gold-bright));
}

/* The opponent is the second series, so it takes the chart series colour
   rather than an invented blue — that keeps the pair on the same theme-aware
   ramp and stops the opponent reading as "the good result". */
body.review-page .review-insight-bar.is-opp::after {
  background: linear-gradient(90deg, color-mix(in srgb, var(--series-1) 45%, transparent), var(--series-1));
}

body.review-page .review-insight-bar.is-empty {
  opacity: 0.4;
}

body.review-page .review-insight-acc {
  display: block;
  font-size: 0.72rem;
  color: var(--muted);
}

/* Chart reference lines and axis labels. Kept faint: they orient the reading
   without competing with the data. */
body.review-page .review-insight-guide {
  stroke: var(--line);
  stroke-width: 1;
  stroke-dasharray: 2 4;
  vector-effect: non-scaling-stroke;
}

body.review-page .review-insight-axis {
  fill: var(--muted);
  font-size: 9px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

body.review-page .review-insight-trendchart {
  display: block;
  width: 100%;
  height: clamp(130px, 18vw, 168px);
  margin-top: 16px;
  overflow: visible;
}

body.review-page .review-insight-trenddot {
  fill: var(--gold-bright);
  stroke: var(--bg-2);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}

/* A missed chance is not an error — nothing was thrown away, a win was simply
   not cashed in — so it is set apart from the mistake palette rather than
   coloured as another warning. */
body.review-page .review-class-dot.missed::before {
  content: "△";
  color: var(--series-1);
}

body.review-page .review-insight-chip.is-missed {
  border-color: color-mix(in srgb, var(--series-1) 34%, var(--line));
  background: color-mix(in srgb, var(--series-1) 8%, transparent);
}

body.review-page .review-insight-openings {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

body.review-page .review-insight-openings-title {
  margin: 0 0 10px;
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

body.review-page .review-insight-openrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 90px 52px 58px;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  font-size: 0.88rem;
}

body.review-page .review-insight-openrow-name {
  overflow: hidden;
  color: var(--cream-dim);
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.review-page .review-insight-openrow-bar {
  display: block;
  height: 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bg-3) 70%, transparent);
  overflow: hidden;
}

body.review-page .review-insight-openrow-bar i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--barTone, var(--gold-bright));
}

body.review-page .review-insight-openrow-value {
  font-weight: 800;
  color: var(--cream);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

body.review-page .review-insight-openrow-games {
  color: var(--muted);
  font-size: 0.78rem;
  text-align: right;
}

/* Tile tone: a figure means nothing until you know which side of the
   comparison it fell on. Theme-aware, so these follow the data palette in
   both light and dark rather than staying pinned to the dark-theme hexes. */
body.review-page .review-insight-tile.is-up .review-insight-tile-value {
  color: var(--data-win);
}

body.review-page .review-insight-tile.is-down .review-insight-tile-value {
  color: var(--data-loss);
}

/* Per-phase accuracy, coloured on the cutoffs lichess uses for its own phase
   breakdown (85 / 70 / 55) so the table reads the same way it does there. */
body.review-page td.is-up {
  color: var(--data-win);
  font-weight: 700;
}

body.review-page td.is-ok {
  color: var(--data-win);
}

body.review-page td.is-warn {
  color: var(--data-warn);
  font-weight: 700;
}

body.review-page td.is-down {
  color: var(--data-loss);
  font-weight: 700;
}

body.review-page td.is-even {
  color: var(--data-draw);
}

body.review-page .review-insight-tile.is-warn .review-insight-tile-value {
  color: var(--data-warn);
}

body.review-page .review-insight-edge.is-behind {
  color: var(--data-loss);
  font-weight: 700;
}

body.review-page .review-insight-edge.is-even {
  color: var(--muted);
}

body.review-page .review-insight-figure {
  margin: 18px 0 0;
}

body.review-page .review-insight-chart {
  display: block;
  width: 100%;
  height: clamp(150px, 22vw, 220px);
  overflow: visible;
}

body.review-page .review-insight-caption {
  margin: 10px 0 0;
  color: var(--muted);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

/* ════════════════════════════════════════════════════════════════════
   PREMIUM INSIGHTS SECTIONS — Enhanced styling for detailed Insights tab
   ════════════════════════════════════════════════════════════════════ */

/* Block headers with emoji icons */
body.review-page .review-insight-block-head {
  margin-bottom: 16px;
}

body.review-page .review-insight-block-head h2 {
  margin: 0;
}

/* Summary metrics — upgraded with sub-text */
body.review-page .review-insight-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin-bottom: 8px;
  padding: 16px;
  background: linear-gradient(180deg, var(--gold-soft), transparent 60%),
              var(--surface-panel);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
}

body.review-page .review-insight-metric {
  text-align: center;
  padding: 8px 4px;
}

body.review-page .review-insight-metric-head {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 6px;
  margin-bottom: 6px;
}

body.review-page .review-insight-metric-head span {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--cream-dim);
}

body.review-page .review-insight-metric-head strong {
  font-size: var(--text-2xl);
  font-weight: var(--weight-extrabold);
  color: var(--cream);
  font-variant-numeric: tabular-nums;
}

body.review-page .review-insight-bar-track {
  height: 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bg-3) 70%, transparent);
  overflow: hidden;
  margin: 0 auto;
  max-width: 120px;
}

body.review-page .review-insight-bar-fill {
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--gold), var(--gold-bright));
  transition: width var(--transition-spring);
  box-shadow: 0 0 8px var(--gold-glow);
}

body.review-page .review-insight-bar-fill--warn {
  background: linear-gradient(90deg, var(--data-loss), #fb923c);
  box-shadow: 0 0 8px rgba(248, 113, 113, 0.4);
}

body.review-page .review-insight-metric-sub {
  display: block;
  margin-top: 6px;
  font-size: var(--text-xs);
  color: var(--muted);
  font-weight: var(--weight-medium);
}

/* Chart tabs */
body.review-page .review-insight-chart-tabs {
  display: flex;
  gap: 4px;
  margin-top: 12px;
  padding: 4px;
  background: var(--surface-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
}

body.review-page .review-insight-chart-tab {
  flex: 1;
  min-height: 36px;
  padding: 0 1rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  font-family: var(--font);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: all var(--transition-fast);
}

body.review-page .review-insight-chart-tab:hover {
  color: var(--cream);
  background: var(--gold-soft);
}

body.review-page .review-insight-chart-tab.is-active {
  color: var(--on-gold);
  background: linear-gradient(135deg, var(--gold), var(--gold-bright));
  box-shadow: 0 2px 8px var(--gold-glow);
}

body.review-page .review-insight-chart-tab:focus-visible {
  outline: 2px solid var(--gold-bright);
  outline-offset: 2px;
}

body.review-page .review-insight-chart-panel {
  animation: om-fade-in var(--motion-fast) var(--motion-ease-out);
}

@keyframes om-fade-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Table wrapper */
body.review-page .review-insight-table-wrap {
  overflow-x: auto;
  margin: 16px -4px 0;
  padding: 0 4px 8px;
}

/* Timeline — critical moments */
body.review-page .review-insight-timeline {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

body.review-page .review-insight-timeline-item {
  display: grid;
  grid-template-columns: 48px 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--surface-panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  transition: all var(--transition-fast);
  cursor: pointer;
}

body.review-page .review-insight-timeline-item:hover {
  border-color: var(--line-strong);
  box-shadow: var(--elev-1);
  transform: translateX(2px);
}

body.review-page .review-insight-timeline-move {
  font-family: var(--font);
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  color: var(--cream);
}

body.review-page .review-insight-timeline-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

body.review-page .review-insight-timeline-san {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--cream);
  font-family: var(--serif);
}

body.review-page .review-insight-timeline-class {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  width: fit-content;
}

body.review-page .review-insight-timeline-class.blunder {
  background: color-mix(in srgb, var(--data-loss) 18%, transparent);
  color: var(--data-loss);
  border: 1px solid color-mix(in srgb, var(--data-loss) 30%, transparent);
}

body.review-page .review-insight-timeline-class.mistake {
  background: color-mix(in srgb, #fb923c 18%, transparent);
  color: #fb923c;
  border: 1px solid color-mix(in srgb, #fb923c 30%, transparent);
}

body.review-page .review-insight-timeline-class.inaccuracy {
  background: color-mix(in srgb, var(--data-draw) 18%, transparent);
  color: var(--data-draw);
  border: 1px solid color-mix(in srgb, var(--data-draw) 30%, transparent);
}

body.review-page .review-insight-timeline-loss {
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  color: var(--data-loss);
  white-space: nowrap;
}

body.review-page .review-insight-empty {
  text-align: center;
  padding: 24px;
  color: var(--muted);
  font-size: var(--text-base);
}

/* Conversion section */
body.review-page .review-insight-conversion {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
  margin-top: 8px;
}

body.review-page .review-insight-conv-card {
  padding: 16px;
  background: var(--surface-panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  text-align: center;
  transition: all var(--transition-fast);
}

body.review-page .review-insight-conv-card:hover {
  border-color: var(--line-strong);
  box-shadow: var(--elev-1);
}

body.review-page .review-insight-conv-label {
  display: block;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  margin-bottom: 6px;
}

body.review-page .review-insight-conv-value {
  font-size: var(--text-3xl);
  font-weight: var(--weight-extrabold);
  font-variant-numeric: tabular-nums;
  color: var(--cream);
  line-height: 1.1;
}

body.review-page .review-insight-conv-sub {
  display: block;
  margin-top: 4px;
  font-size: var(--text-sm);
  color: var(--cream-dim);
}

/* Standout moves */
body.review-page .review-insight-standout {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

body.review-page .review-insight-standout-item {
  display: grid;
  grid-template-columns: 56px 1fr auto;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  background: var(--surface-panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  transition: all var(--transition-fast);
}

body.review-page .review-insight-standout-item:hover {
  border-color: var(--gold);
  box-shadow: var(--elev-1), 0 0 0 1px var(--gold-glow);
}

body.review-page .review-insight-standout-move {
  font-family: var(--serif);
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  color: var(--gold);
  text-align: center;
  padding: 8px;
  background: var(--gold-soft);
  border-radius: var(--radius-sm);
  min-width: 56px;
}

body.review-page .review-insight-standout-main {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

body.review-page .review-insight-standout-san {
  font-family: var(--serif);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--cream);
}

body.review-page .review-insight-standout-context {
  font-size: var(--text-sm);
  color: var(--cream-dim);
  line-height: 1.5;
}

body.review-page .review-insight-standout-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}

body.review-page .review-insight-standout-badge.brilliant {
  background: linear-gradient(135deg, var(--gold-soft), var(--gold-soft-hover));
  color: var(--gold);
  border: 1px solid var(--gold-glow);
}

body.review-page .review-insight-standout-badge.best {
  background: color-mix(in srgb, var(--data-info) 15%, transparent);
  color: var(--data-info);
  border: 1px solid color-mix(in srgb, var(--data-info) 30%, transparent);
}

body.review-page .review-insight-standout-badge.saved {
  background: color-mix(in srgb, var(--data-win) 15%, transparent);
  color: var(--data-win);
  border: 1px solid color-mix(in srgb, var(--data-win) 30%, transparent);
}

/* Time management stats */
body.review-page .review-insight-time-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
  margin-top: 16px;
  padding: 16px;
  background: var(--surface-panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}

body.review-page .review-insight-time-stat {
  display: flex;
  flex-direction: column;
  gap: 4px;
  text-align: center;
}

body.review-page .review-insight-time-stat-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
}

body.review-page .review-insight-time-stat-value {
  font-size: var(--text-xl);
  font-weight: var(--weight-extrabold);
  font-variant-numeric: tabular-nums;
  color: var(--cream);
}

/* Opening rows — enhanced */
body.review-page .review-insight-openings {
  margin-top: 16px;
}

body.review-page .review-insight-openrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 1fr 80px;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: var(--surface-panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  transition: all var(--transition-fast);
  cursor: pointer;
}

body.review-page .review-insight-openrow:hover {
  border-color: var(--line-strong);
  box-shadow: var(--elev-1);
  background: var(--surface-3);
}

body.review-page .review-insight-openrow-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--cream);
  font-weight: var(--weight-medium);
}

body.review-page .review-insight-openrow-bar {
  height: 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bg-3) 70%, transparent);
  overflow: hidden;
}

body.review-page .review-insight-openrow-bar i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--barTone, var(--gold-bright));
  transition: width var(--transition-spring);
}

body.review-page .review-insight-openrow-value {
  font-weight: var(--weight-bold);
  color: var(--cream);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

body.review-page .review-insight-openrow-games {
  color: var(--muted);
  font-size: var(--text-sm);
  text-align: right;
}

body.review-page .review-insight-verdict {
  margin: 12px 0 0;
  font-size: var(--text-sm);
  color: var(--cream-dim);
  line-height: 1.6;
}

/* Responsive adjustments for new sections */
@media (max-width: 900px) {
  body.review-page .review-insight-summary {
    grid-template-columns: repeat(2, 1fr);
    padding: 12px;
  }
  body.review-page .review-insight-metric-head strong {
    font-size: var(--text-xl);
  }
  body.review-page .review-insight-timeline-item {
    grid-template-columns: 40px 1fr;
    grid-template-rows: auto auto;
  }
  body.review-page .review-insight-timeline-loss {
    grid-column: 2;
    justify-self: end;
  }
  body.review-page .review-insight-standout-item {
    grid-template-columns: 48px 1fr;
    grid-template-rows: auto auto;
  }
  body.review-page .review-insight-standout-badge {
    grid-column: 2;
    justify-self: start;
  }
  body.review-page .review-insight-conversion {
    grid-template-columns: repeat(2, 1fr);
  }
  body.review-page .review-insight-time-stats {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 600px) {
  body.review-page .review-insight-summary {
    grid-template-columns: 1fr;
  }
  body.review-page .review-insight-conversion {
    grid-template-columns: 1fr;
  }
  body.review-page .review-insight-time-stats {
    grid-template-columns: 1fr;
  }
}

@media (prefers-reduced-motion: reduce) {
  body.review-page .review-insight-chart-panel {
    animation: none;
  }
  body.review-page .review-insight-bar-fill,
  body.review-page .review-insight-openrow-bar i {
    transition: none;
  }
}

/* ==========================================================================
   Unified workspace surface.

   The Board view previously mixed an unboxed board column against boxed side
   cards, with the coach card floating above an unboxed board — that read as
   boxes with stray edges between them. Everything below sits on one rounded
   surface, with hairline rules and flush joins instead of gaps.
   ========================================================================== */

/* The board view is no longer a card.

   It used to draw a 1px border, a 20px radius and an opaque panel background
   around the whole thing — a box inside the page inside a frame, with the
   games rail, the board and the report each carrying their own second border
   inside it. Three nested surfaces read as clutter, and the outer one bought
   nothing: the rails already carry their own edges.

   What is left is a flush layout. The page's own background is the surface,
   the rails are the only bordered things, and the top row is a hairline rather
   than a bar with a background of its own. */
body.review-page .review-workspace {
  gap: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  padding: 0;
}

body.review-page .review-workspace-top {
  /* Horizontal padding only. This row is directly above the board in a
     height-locked document, so its 10px of vertical padding was 10px of square
     on every game. The border-bottom is what separates it from the board; the
     padding was only ever air around that line. */
  padding: 0 4px 8px;
  border-bottom: 1px solid var(--line);
  gap: 14px;
}

/* The tabs sit at the right of the top row, so they are aligned to the same
   horizontal inset as the meta text beside them rather than starting at their
   own left padding. */
body.review-page .review-viewtabs {
  align-self: center;
  gap: 4px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  flex: 0 0 auto;
}

body.review-page .review-viewtabs button {
  min-height: 34px;
  padding: 0 1.1rem;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  box-shadow: none;
}

body.review-page .review-viewtabs button:hover {
  color: var(--cream);
  background: color-mix(in srgb, var(--gold) 8%, transparent);
}

body.review-page .review-viewtabs button.is-active {
  background: color-mix(in srgb, var(--gold) 14%, transparent);
  color: var(--gold);
  border-color: color-mix(in srgb, var(--gold) 34%, transparent);
  box-shadow: none;
}

/* The board view's own inset. Zero, so the three rails sit flush with the top
   row above them: a padding here is what made the rails look inset inside the
   page for no reason, since the rails carry their own borders. */
body.review-page #review-view-board {
  padding: 0;
}

body.review-page .review-layout {
  margin-top: 0;
  /* The column gap is --review-gap, owned by the board-sizing block so the
     three rails stay flush. A row gap would push the rails off the board's
     top edge, so it stays 0. */
  gap: 0 var(--review-gap);
}

/* The board column is no longer a panel.

   It was a bordered card with its own background, which put a visible frame
   around the coach, the board and the transport — and the frame was the thing
   the reader saw first, because it enclosed the largest object on the page.
   The board is the content and the square already carries a border of its own,
   so a second frame around it was decoration competing with the thing it
   enclosed. The three parts are now separated by the row gap and by the coach's
   own callout background, which is the only one of the three that needs to
   read as a panel. */
body.review-page .review-board-col {
  /* Horizontal padding removed: the layout grid's column-gap
     already separates the three columns, and 20px each side stole
     40px from the board track — making the board shorter than the
     eval bar beside it. Returning every pixel to the square. */
  padding: 0;
  row-gap: 0;
  border: 0;
  border-radius: 0;
  background: none;
}

body.review-page .review-report,
body.review-page .review-moves-wrap {
  border: 1px solid var(--line);
  border-radius: 18px;
  background: color-mix(in srgb, var(--bg-2) 45%, transparent);
  box-shadow: none;
}

/* The coach is a callout at the head of the report rail, not a competing card.

   It used to sit in a fixed track above the board with `height: 100%` pinned
   into that track — the definite height its note frame's `max-height: 100%`
   needed in order to resolve (a percentage against auto-height is treated as
   none, and the unclamped note then painted over the eval bar and the square).

   The article is out of the column now: the track is gone, and the frame
   clamps itself in viewport units instead (see .review-coach-body above). So
   the article is content-sized again — a rail child that hugs its note, up to
   that cap. `min-height: 0` keeps it shrinkable, and `overflow: hidden` keeps
   anything past the edge from painting outside the callout.

   The trade is the documented one: a one-line note and a five-line note take
   different heights here, so the figures below shift by a line between steps.
   The floor on .review-coach-text keeps that swing small, and the board never
   notices either way — none of this lives in the board's column any more. */
body.review-page .review-coach {
  min-height: 60px;
  overflow: visible;
  /* Two columns: avatar, then the scrolling body. Declared here rather than
     relied on from the base rule because without it the article is a block, the
     `grid-template-columns` above is inert, and the body is not a bounded box
     for the note to scroll inside. */
  display: grid;
  grid-template-columns: 76px minmax(0, 1fr);
  align-content: start;
  align-items: start;

  /* Gold rule down the left edge, tinted band, no second frame around it. */
  border: 0;
  border-left: 3px solid var(--gold);
  border-radius: 0 14px 14px 0;
  background: color-mix(in srgb, var(--gold) 6%, transparent);
  padding: 16px 18px;
}

/* Insight components */

body.review-page .review-insight-tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  margin: 0 -26px;
  background: var(--line);
  border-bottom: 1px solid var(--line);
}

body.review-page .review-insight-tile {
  margin: 0 -1px 0 0;
  padding: 20px 24px;
  background: var(--panel);
}

body.review-page .review-insight-tile-label {
  margin: 0;
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

body.review-page .review-insight-tile-value {
  margin: 8px 0 0;
  font-size: 1.85rem;
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--cream);
  font-variant-numeric: tabular-nums;
}

body.review-page .review-insight-tile-note {
  margin: 6px 0 0;
  font-size: 0.8rem;
  color: var(--muted);
}

body.review-page .review-insight-timeline {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}

body.review-page .review-insight-empty {
  margin: 16px 0 0;
  color: var(--muted);
  font-size: 0.95rem;
}

body.review-page .review-insight-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: color-mix(in srgb, var(--bg-2) 60%, transparent);
  color: var(--cream);
  font-family: var(--font);
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
  transition: border-color 0.18s ease, background 0.18s ease, transform 0.18s ease;
}

body.review-page .review-insight-chip:hover {
  border-color: var(--gold);
  background: color-mix(in srgb, var(--gold) 10%, transparent);
  transform: translateY(-1px);
}

body.review-page .review-insight-chip:focus-visible {
  outline: 2px solid var(--gold-bright);
  outline-offset: 2px;
}

body.review-page .review-insight-chip-move {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

body.review-page .review-insight-chip-cp {
  color: var(--muted);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
}

body.review-page .review-insight-book {
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

body.review-page .review-insight-booknote {
  margin: 0 0 4px;
  font-size: 1.05rem;
  line-height: 1.6;
  color: var(--cream);
}

body.review-page .review-insight-conversion {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

body.review-page .review-insight-conv-head {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.45;
  font-weight: 700;
  color: var(--cream);
  max-width: 60ch;
}

body.review-page .review-insight-conv-row {
  display: grid;
  grid-template-columns: 7rem auto minmax(0, 1fr);
  align-items: baseline;
  gap: 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}

body.review-page .review-insight-conv-row > span {
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

body.review-page .review-insight-conv-row strong {
  font-size: 1.35rem;
  font-weight: 800;
  color: var(--cream);
  font-variant-numeric: tabular-nums;
}

body.review-page .review-insight-conv-row small {
  font-size: 0.82rem;
  color: var(--muted);
}

body.review-page .review-insight-conv-note {
  margin: 4px 0 0;
  font-size: 0.98rem;
  line-height: 1.6;
  color: var(--cream-dim, var(--muted));
  max-width: 62ch;
}

/* Chips carry a second line now ("engine wanted …"), so stack them. */
body.review-page .review-insight-chip {
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  text-align: left;
}

body.review-page .review-insight-chip-top {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

body.review-page .review-insight-chip-want {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--muted);
  font-style: italic;
}

body.review-page .review-insight-chip.is-good {
  border-color: color-mix(in srgb, #4ade80 30%, var(--line));
}

body.review-page .review-insight-bookrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(120px, 1.4fr) auto;
  align-items: center;
  gap: 8px 16px;
}

body.review-page .review-insight-bookrow-label {
  font-size: 0.95rem;
  color: var(--cream);
}

body.review-page .review-insight-bookrow-bar {
  display: block;
  height: 8px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}

body.review-page .review-insight-bookrow-bar i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--gold-deep), var(--gold-bright));
}

body.review-page .review-insight-bookrow-value {
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
  color: var(--cream);
}

body.review-page .review-insight-bookrow-sub {
  grid-column: 1 / -1;
  font-size: 0.78rem;
  color: var(--muted);
}

body.review-page .review-insight-verdict {
  margin: 6px 0 0;
  font-size: 1rem;
  line-height: 1.6;
  color: var(--cream-dim, var(--muted));
}

/* Chart hover: crosshair drawn in the SVG, value card floating above it. */
body.review-page #review-view-insight {
  position: relative;
}

body.review-page .review-insight-crosshair {
  display: none;
  pointer-events: none;
}

body.review-page .review-insight-crosshair.is-on {
  display: block;
}

body.review-page .review-insight-crosshair line {
  stroke: var(--gold-bright);
  stroke-width: 1;
  stroke-dasharray: 3 3;
  opacity: 0.75;
}

body.review-page .review-insight-crosshair circle {
  fill: var(--gold-bright);
  stroke: var(--panel);
  stroke-width: 2;
}

body.review-page .review-insight-chart:focus-visible {
  outline: 2px solid var(--gold-bright);
  outline-offset: 4px;
  border-radius: 6px;
}

body.review-page .review-insight-chart {
  cursor: crosshair;
}

body.review-page .review-insight-tip {
  position: absolute;
  z-index: 5;
  min-width: 11rem;
  max-width: 16rem;
  padding: 10px 13px;
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  background: color-mix(in srgb, var(--bg-2) 92%, transparent);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}

body.review-page .review-insight-tip[hidden] {
  display: none;
}

body.review-page .review-insight-tip-head {
  margin: 0 0 6px;
  font-size: 0.92rem;
  font-weight: 800;
  color: var(--cream);
  font-variant-numeric: tabular-nums;
}

body.review-page .review-insight-tip-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  margin: 0;
  font-size: 0.8rem;
  color: var(--muted);
}

body.review-page .review-insight-tip-row strong {
  color: var(--cream);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 900px) {
  body.review-page .review-workspace-top,
  body.review-page #review-view-board {
    padding: 14px 16px;
  }
  body.review-page .review-insight-tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin: 0 -16px;
  }
  body.review-page #review-view-insight {
    padding: 2px 16px 20px;
  }
  body.review-page .review-insight-tile {
    padding: 16px 18px;
  }
  body.review-page .review-insight-tile-value {
    font-size: 1.5rem;
  }
  body.review-page .review-insight-bookrow {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  body.review-page .review-insight-bookrow-bar {
    grid-column: 1 / -1;
  }
}

/* ══════════════════════════════════════════════════════════════════
   Board sizing: shared with the trainer
   ══════════════════════════════════════════════════════════════════
   The review board is now the SAME BOX as the opening/endgame board, not
   merely a similar-looking one. Both are sized by viewport height through
   an identical formula, so the two pages cannot drift apart on resize.

   The formula is copied from style_training.css (main.opening-view) on
   purpose: a single shared custom property would be the tidier fix, but
   --trainer-rail is 280px (two side rails) while the review has a games
   rail plus a report rail, so the width term genuinely differs. What must
   match is the HEIGHT term, and that is the one the eye actually compares:
   the trainer board is 100dvh minus a 3.5rem header, less a 24px gutter. */
body.review-page {
  --review-header: 3.5rem;
  --review-gutter: 24px;
  --review-gap: 8px;

  /* Same WIDTH TERM as --trainer-board: 280px rails and 8px gaps, exactly as
     main.opening-view declares them, so the two boards are the same size for
     the same reason.

     The HEIGHT TERM is NOT the trainer's, and it must not be. The trainer's
     board column holds nothing but the board, so `100dvh - header - gutter`
     is all of it. The review board column is a stack — coach panel, eval bar,
     board, transport controls, best-line caption — and the coach panel is
     open-ended prose that can be six lines long. Budgeting the board for the
     full viewport height and then adding those rows on top of it is what put
     the bottom of the board under the fold: the document is locked to 100dvh,
     so whatever did not fit was clipped rather than scrolled to.

     --review-chrome is that fixed stack around the board: the intro and
     status rows, the top bar, the progress bar, the view tabs, the shell
     padding, the gaps, the transport row and the caption. It is measured, not
     guessed — see REVIEW_CHROME_MEASURED below — but it is a real cost the
     trainer simply does not have, and the board has to be told about it.

     MEASURED at a 1080px viewport with a long coach note: 344px, not the 268px
     first written here. The undercount left the board 32px taller than the
     space it had, and because the document is locked to 100dvh the overflow
     was clipped — the bottom two rows of the board simply disappeared.

     The board no longer takes its HEIGHT from this. It fills the cell the other
     rows leave behind and squares itself inside it (see .review-board-stage),
     so there is no constant left to get wrong. --review-board is now only the
     width ceiling, which is the term the trainer genuinely shares.

     --review-chrome is kept solely to stop the WIDTH term from proposing a
     board taller than the column can ever be; on a short, wide window the
     height term is what binds. It is deliberately generous — over-reserving
     here costs width, whereas under-reserving used to cost the bottom of the
     board.

     Reduced from 260px -> 180px since the coach panel moved to the report rail,
     the intro/status rows were removed, and the top bar is now leaner. This
     gives ~80px more viewport height to the board on desktop, letting it extend
     closer to the bottom of the screen. The height term binds on normal windows,
     so being generous here only affects the width ceiling on very wide screens. */
  --review-chrome: 180px;
  /* 48px: wider eval bar for better visibility, matches the larger board.
     Declared once here because the bar column, the inner grid and the outer
     grid all have to agree on it — when they each carried their own number
     the board was squeezed by the difference. */
  --review-eval-bar: 48px;
  --review-col-gap: 16px;
  /* The two rails. Wider to match the larger board and give more room for
     player info, accuracy chips, and the move list. The design stylesheet's
     fixed sidebar takes 340px off the left; rails at 320px min keep the
     layout balanced on wide screens. */
  --review-rail: 320px;
  /* Transport controls row height (arrow buttons below board).
     Added to layout grid row so side panels match full board column height. */
  --review-transport: 56px;
  /* The design stylesheet's fixed sidebar is not in the flow — it is
     position: fixed, and .site-frame merely has margin-left to clear it. So
     `100vw` is the whole window and every width budget written against it
     over-counts by the sidebar's width. Measured consequence: the board column
     asked for 1046px of a ~1580px frame, which left the two rails 0px each.

     `var(--sidebar-w, 0px)` reads the design's own value when one is active and
     falls back to zero when there is no sidebar, so this stays correct if the
     sidebar is ever resized or the design is switched off. */
  --review-sidebar: var(--sidebar-w, 0px);
  /* The board is no longer sized by this value; it sizes itself against the
     cell its column leaves (see .review-board-stage). This is now only the
     WIDTH ceiling for that column, and it is deliberately generous, because
     over-reserving costs nothing here while under-reserving shrinks the
     board. `--review-chrome` has been reduced to the chrome that is genuinely
     beside the board rather than above it — the top bar and the view tabs are
     now measured out of the width budget instead, since they no longer compete
     for vertical room with a height-derived board. */
  --review-board: min(
    calc(100dvh - var(--review-header) - var(--review-gutter)),
    calc(100vw - var(--review-sidebar) - (var(--review-gutter) * 2)
          - (var(--review-rail) * 2) - (var(--review-gap) * 2)
          - var(--review-eval-bar) - var(--review-col-gap))
  );
  /* The board column's WIDTH is the board plus the eval bar and the gap between
     them, and nothing more.

     It used to be the board's ceiling as well, which reserved a column twice
     the board's real width on any window where the board is height-bound — the
     normal case, because the board takes its size from the cell it is given and
     that cell is short. Measured at 1080px tall: a 486px board in a 1006px
     column, so 520px of dead space, the coach panel stretched across all of
     it, and the whole page pushed out to the right.

     The column's ceiling must therefore be derived from the board's HEIGHT, not
     from the width budget. A board is square, so its width is its height, and
     the height is what --review-chrome has already budgeted for.

     The eval bar and the column gap are grid SIBLINGS inside this ceiling, not
     children of it — subtracting them here made the column 64px narrower
     than the cell it actually holds. And the belt: if the height term ever
     collapses (a guessed-low --review-chrome on a tall, narrow window, for
     instance), the column must not strand a rail-sized hole, so the ceiling
     never drops below a 200px floor. */
  --review-board-col: max(
    min(
      calc(100dvh - var(--review-header) - var(--review-gutter) - var(--review-chrome)),
      calc(100vw - var(--review-sidebar) - (var(--review-gutter) * 2)
            - (var(--review-rail) * 2) - (var(--review-gap) * 2))
    ),
    200px
  );
  /* The actual board outer height (square). On every viewport the board is
     width-bound inside the 1fr track, so its height equals the track width. */
  --review-board-h: calc(var(--review-board-col) - var(--review-eval-bar) - var(--review-col-gap));
}

/* Board-sizing variables for Blunder Override, which uses the same
   .review-board-col grid but does not carry body.review-page. */
body.mistakes-page {
  --review-eval-bar: 48px;
  --review-col-gap: 16px;
  --review-transport: 56px;
}

/* The three rails. The board column is the --review-board square; the two
   rails take what is left and are capped so a wide monitor does not stretch
   the side panels into unreadable full-height columns of empty space. */
body.review-page .review-layout {
  display: grid;
  /* Three tracks: games rail, board column, report rail.
     Single grid row stretches to the layout's full height so the two side
     panels match the board height.

     The rails are 1fr so they share whatever the board leaves, and the board
     column is capped at --review-board-col rather than demanding it. The
     earlier `minmax(var(--review-board-col), auto)` treated that value as a
     MINIMUM, and on a wide monitor that is larger than the space the two rails
     need: the board column took 1046px and left the rails nothing at all
     (measured: `0px 1486px 0px`). Both rails collapsed to zero width — the
     games list and the moves list simply disappeared.

     Capping the middle track and giving the rails a real minimum is the
     arrangement the trainer already uses, where both rails are a fixed width
     and the board takes what is left. Here the board is height-driven, so it
     cannot be the one that grows: its size is whatever cell it is given. */
  grid-template-columns:
    minmax(var(--review-rail), 2fr)
    minmax(0, var(--review-board-col))
    minmax(420px, 1fr);
  /* Single row: the board's actual height. An empty second track used to add
     --review-transport (56px) of dead space here — a leftover from a
     board-column controls bar that no longer exists (the arrows live in the
     sidebar) — stretching the rails without growing the square. */
  grid-template-rows: var(--review-board-h);
  align-content: start;
  column-gap: var(--review-gap);
  row-gap: 0;
  align-items: stretch;
  justify-content: start;
  width: 100%;
  max-width: 100%;
}

/* The board shell is now exactly the square and nothing else. The old
   max-width chain (720px, then 880px on body.review-page, then 760px under
   1400px, then 100% under 1100px) is what let the review board outgrow the
   trainer on a large screen; removing the cap is the whole fix. */
body.review-page .review-board-shell {
  grid-row: 1;
  width: 100%;
  max-width: none;
  min-width: 0;
}

/* No `width: 100%` here. This rule used to set it, and because it is the last
   unscoped one in the file it outranked the height-derived sizing above — the
   stage took the full column width, ignored the 501px of height it actually
   had, and the board grew to 614px and ran under the fold.

   The stage's own rule derives the width from its height and clamps it to the
   column; repeating either half here only creates a second, conflicting
   answer. */
body.review-page .review-board-stage {
  max-width: 100%;
}

/* Border width is part of "the same size". box-sizing is border-box
   globally (style_dark.css), so the border comes out of --review-board and
   both boards keep the same 12px frame. */
body.review-page .review-board {
  border-width: 12px;
}

/* ——— Games rail ———
   The imported-games list, moved from a full-width band above the board to a
   rail beside it. It scrolls internally; the page does not.

   Explicitly placed in grid column 1 so it sits beside the board rather
   than below it when all three layout children auto-place into the same row. */
body.review-page .review-games-rail {
  grid-column: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 18px;
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--gold) 5%, transparent), transparent 180px),
    color-mix(in srgb, var(--bg-2) 45%, transparent);
}

body.review-page .review-games-rail-head {
  flex: 0 0 auto;
  padding: 16px 18px 12px;
  border-bottom: 1px solid var(--line);
}

/* The rail is a grid item of the same row as the board, so `stretch` makes it
   the height of that row — which is now the board cell, whatever the board
   inside it turned out to be. It used to carry its own `height:
   var(--review-board)`, and since the board is now sized from the space left
   over rather than from that constant, the two would disagree and the rail
   would end either taller than the board or clipped short.

   Scoped to the breakpoint because below it the rail is a normal stacked
   block (see the max-width: 1180px block) and must keep its own max-height. */
@media (min-width: 1181px) {
  body.review-page .review-games-rail {
    align-self: stretch;
  }
}

body.review-page .review-games-rail-title {
  margin: 0;
  font-size: 1.02rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--cream);
}

/* The scroll region. This is the only place the games list scrolls; the page
   around it does not.

   A flex column, so the list inside it can be given a definite height to fill.
   As a plain block it had the height of its content, and a list with a `flex`
   on it resolved against nothing. */
body.review-page .review-games-rail-scroll {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overscroll-behavior-y: contain;
  padding: 10px;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--gold) 40%, transparent) transparent;
}

body.review-page .review-games-rail-scroll::-webkit-scrollbar {
  width: 8px;
}

body.review-page .review-games-rail-scroll::-webkit-scrollbar-track {
  background: transparent;
}

body.review-page .review-games-rail-scroll::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--gold) 38%, transparent);
  border-radius: 999px;
}

body.review-page .review-games-rail-foot {
  flex: 0 0 auto;
  padding: 10px 12px;
  border-top: 1px solid var(--line);
}

body.review-page .review-games-rail-foot .btn {
  width: 100%;
  justify-content: center;
}

/* The rail hosts the standard imported-games rows, which were designed for a
   full-width list.

   Those rows are a FOUR-COLUMN GRID: result chip, title block, badge stack,
   action button. In a 200-300px rail that cannot fit, and the result chip's
   52px minimum plus the action button's own minimum left the title column
   almost nothing — which is what the narrow, truncated rows in the screenshot
   were.

   The earlier fix here set `flex-direction: column`, which did nothing at all:
   the row is `display: grid`, so the property is inert. It has to be a
   different GRID TEMPLATE.

   The rail stacks instead: result + action share the top line, the title spans
   beneath them, and the badges wrap under that. The action button is revealed
   on hover at full width, so the resting row is quiet and the row still reads
   as one clickable unit. */
body.review-page .review-games-rail-scroll .imported-game-row {
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "result action"
    "copy   copy"
    "badges badges";
  align-items: center;
  /* Compact. The rail is ~716px tall and the rows were 129px each, so only five
     games were visible before the reader had to scroll — which is what made the
     list look as though it stopped halfway. Five is not a list. */
  gap: 6px 10px;
  padding: 9px 12px;
}

/* The list is filled to the button, not to its own content.

   The rail's own scroll region is the flexible child and does reach the Paste
   PGN foot, so the gap in the screenshot was never the rail — it was this list
   inside it. `.imported-games-list` carries a shared `max-height: 430px` for
   the picker dialog, and that cap applied here too: three games occupied 430px
   of a ~716px rail and stopped, leaving a third of the rail empty above the
   button.

   So the list is given the rail's full height and its rows are distributed to
   fill it: the cap comes off so the list can grow, and each row below takes
   `flex-grow` so three games occupy the whole rail rather than 430px of it. With
   more games than fit, the rows stay their natural height and this container
   scrolls, which is the behaviour that was already correct. */
body.review-page .review-games-rail-scroll .imported-games-list {
  flex: 1 1 auto;
  max-height: none;
  min-height: 0;
  justify-content: space-between;
}

/* The rows grow to fill a rail that is taller than they are, but never shrink
   below their content.

   `flex-grow` only, with the shared `flex: 0 0 auto` basis kept. Plain
   `flex: 1 1 auto` would let a long list squash each row until the text
   overflowed its own box — which is the failure the shared rule's comment
   warns about, and the rows are only ever as tall as they need to be here. */
body.review-page .review-games-rail-scroll .imported-game-row {
  flex: 1 0 auto;
}

body.review-page .review-games-rail-scroll .imported-game-result {
  grid-area: result;
  min-width: 46px;
}

body.review-page .review-games-rail-scroll .imported-game-copy {
  grid-area: copy;
  min-width: 0;
}

body.review-page .review-games-rail-scroll .imported-game-badges {
  grid-area: badges;
  flex-wrap: wrap;
  justify-content: flex-start;
}

/* The action is a hover affordance in the rail: it would otherwise eat the
   full width of every row at rest. Kept in the DOM and focusable, so the row
   is still operable from the keyboard. */
body.review-page .review-games-rail-scroll .imported-game-action {
  grid-area: action;
  justify-self: end;
  opacity: 0;
  transition: opacity 0.18s ease;
}

body.review-page .review-games-rail-scroll .imported-game-row:hover .imported-game-action,
body.review-page .review-games-rail-scroll .imported-game-row:focus-within .imported-game-action {
  opacity: 1;
}

/* The title is allowed two lines in the rail. A single ellipsised line of a
   player name plus opponent is unreadable at this width. */
body.review-page .review-games-rail-scroll .imported-game-copy strong {
  white-space: normal;
  overflow-wrap: anywhere;
  font-size: 0.88rem;
  line-height: 1.3;
}

/* ——— Non-scrolling board view ———
   Above 1180px the board view is a fixed-height instrument: the document does
   not scroll, and only the games list and the moves list scroll inside their
   own frames. 1180px is the trainer's own breakpoint, so the two pages switch
   layout at the same width. */
@media (min-width: 1181px) {
  /* No picker state to unlock.

     These rules released the document when #review-workspace was hidden, for
     the days when the library was a page of its own and had to scroll. It is a
     <dialog> now: the workspace is never hidden, and the library scrolls inside
     its own box while the top layer holds the page still. So there is no
     second layout to restore, and the whole block is gone rather than left in
     place to match a state that can no longer occur. */

  /* Board view shown: lock the document.

     CRITICAL: this has to lock the element that is actually the flex column,
     and that is NOT always <body>.

     style_dark.css sets `body { display: flex; flex-direction: column }` and
     the whole non-scrolling layout was built on that. But the sigma design
     overrides it with `html[data-design="sigma"] body { display: block }` and
     moves the column to `.site-frame` instead, because sigma uses a fixed
     sidebar and the main content sits beside it.

     So under sigma, `body` is a block, `.page-shell { flex: 1 }` has no flex
     parent to divide, and it grows to whatever its content needs. Measured on
     the real page: body locked to 1080px while .page-shell reached 6924px. The
     board takes its width from the cell that is left over, so it grew to
     6531px — six times the viewport, clipped by the very lock that was meant
     to contain it.

     Every measurement until this point came from a hand-built harness that
     loaded only style_dark.css and style_review.css, so it never saw sigma's
     override and always reported a correct board. The harness was the reason
     this took so long to find. */
  body.review-page:has(#review-workspace:not([hidden])),
  html[data-design="sigma"] body.review-page:has(#review-workspace:not([hidden])) .site-frame {
    height: 100dvh;
    max-height: 100dvh;
    overflow: hidden;
  }

  /* From here down, every rule is scoped to the board view being SHOWN.
     Unscoped, they applied to the picker too: the footer disappeared, the
     shell was locked, and the long import form could not scroll. */
  body.review-page:has(#review-workspace:not([hidden])) .site-footer {
    display: none;
  }

  /* The header is a flex sibling of .page-shell, so the shell gets what is
     left of the viewport after it. The board's own sizing subtracts a 3.5rem
     header, which is this element's real height. */
  body.review-page:has(#review-workspace:not([hidden])) .site-header {
    flex: 0 0 auto;
  }

  body.review-page:has(#review-workspace:not([hidden])) .page-shell {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    /* No vertical padding. It is 24px of pure cost, and the board is what that
       space should be going to: the column stacks coach, board, transport
       controls and the best-line caption, and every pixel here comes straight
       off the square. */
    padding: 0;
  }

  /* Under a design stylesheet the frame is the flex column and the shell is its
     flex child, so the shell must be told to hold its share rather than grow to
     fit its content. `min-height: 0` alone is not enough: a flex item's
     automatic minimum size is its content's min-content height, so a tall
     subtree still pushes the box out. `height: 0` with `flex-grow` is the
     reliable way to say "take the space that is left over and no more". */
  html[data-design="sigma"] body.review-page:has(#review-workspace:not([hidden])) .page-shell {
    flex: 1 1 0;
    height: 0;
    min-height: 0;
  }

  /* .review-root sits between .page-shell and #review-workspace, and it was
     the missing link that made every height above it inert. Every element in
     the chain has to be a definite-height flex item. */
  body.review-page:has(#review-workspace:not([hidden])) .review-root {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    /* `height: 100%` AND a max-height. The percentage is what makes this a
       definite flex item so the chain below it has something to resolve
       against — without it the `1fr` board row grows to fit its content and the
       board runs to 3140px in a 640px window.

       The max-height is what stops the percentage from over-reporting: the
       shell is padded, so 100% of it is more than the space actually inside
       it, and the subtree overflowed by the padding. Clamping to 100% keeps
       the item inside its parent while the percentage keeps the chain
       definite. The two are not alternatives; each fixes what the other
       breaks.

       The 12px gap used to separate the workspace from the status row above
       it. Blank when the status is empty — but the gap is a fixed cost whether
       the row has anything in it or not, so the workspace owns the spacing
       instead (see #review-workspace below) and this is zero. */
    height: 100%;
    max-height: 100%;
    padding: 0;
    gap: 0;
  }

  body.review-page:has(#review-workspace:not([hidden])) #review-workspace {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    /* `height: 100%` with a matching max-height, for the same reason as
       .review-root: the percentage is what makes this a definite flex item so
       the board row has a bound to fill, and the max-height is what stops the
       padded shell's padding from being counted twice. */
    height: 100%;
    max-height: 100%;
    /* The breathing room between the status line and the workspace top row.
       It lives here, as a margin, rather than as a gap on .review-root: gap
       is a fixed cost even when the status is empty and collapsed, margin on
       a first child that is itself empty still costs nothing. When the status
       has text the 12px is there; when it does not, the board takes it. */
    margin-top: 12px;
  }

  body.review-page:has(#review-workspace:not([hidden])) .review-workspace-top,
  body.review-page:has(#review-workspace:not([hidden])) .review-viewtabs {
    flex: 0 0 auto;
  }

  /* The view tabs share a row with the game meta, and their 48px hit area was
     setting that row's height — the row is what the board starts below, so the
     tab height is board height.

     32px is still a comfortable target and stays a pill rather than a
     full-width bar, so the row drops from 82px to about 46px. Measured: that is
     36px back to the square. */
  body.review-page:has(#review-workspace:not([hidden])) .review-viewtabs {
    padding: 3px;
  }

  body.review-page:has(#review-workspace:not([hidden])) .review-viewtabs button {
    min-height: 32px;
    padding: 0 1.05rem;
    font-size: 0.88rem;
  }

  /* The tab panel is the flex child that owns the leftover viewport height.
     It must be a flex container itself, so the layout inside it stretches
     rather than being sized by its own content. */
  body.review-page:has(#review-workspace:not([hidden])) #review-view-board {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  /* The board column is a flex stack whose non-board rows are fixed, so the
     coach, eval bar, controls and caption all stay put while the board keeps
     the leftover height.

     `height: 100%` is required — it is what makes this a definite flex item so
     the board row has a bound to fill. Removing it collapsed the chain and the
     board grew to fit its content.

     `max-height: 100%` is equally required, and it is what the percentage
     alone gets wrong. The layout shares #review-view-board with whatever sits
     in flow beside it — historically the analysis progress strip, which was a
     flex sibling — so 100% is a hundred percent of the whole panel rather
     than of the space the layout actually gets. The overflow is clipped by
     the document lock (100dvh) rather than scrolled to, so the board's
     bottom rows would disappear below the fold.

     Percentage alone cannot express "take what is left". Flex can:
     `flex: 1 1 0` with a zero basis is exactly that, and max-height is the
     belt to its braces. */
  body.review-page .review-layout {
    flex: 1 1 0;
    height: 100%;
    max-height: 100%;
    min-height: 0;
  }

  /* The tab panel must actually own the leftover viewport height, or nothing
     below it can. It is a flex child of #review-workspace with
     `flex: 1 1 auto; min-height: 0`, so its height is definite — but only if
     the workspace itself is allowed to be the full height rather than being
     sized by its own content. */
  body.review-page:has(#review-workspace:not([hidden])) #review-view-board {
    display: flex;
    flex-direction: column;
  }

  body.review-page .review-board-col {
    grid-column: 2;
    /* Needed: this is the grid item that must FILL its row, and a grid item
       only reaches the row's full height if it stretches or is told the
       height. Without it the column collapsed to its content (285px) and the
       board inside it went to 26px. */
    height: 100%;
    min-height: 0;
    /* Row 2 is the board and must absorb everything the fixed rows do not use.
       `align-content: start` here defeated that: it packs the rows against the
       top and leaves the leftover height as dead space under the board, so the
       board was 78px smaller than the space it was given. `stretch` (the
       default, hence omitted) lets the 1fr row take it. */
    /* Two rows now: the board cell, then the transport controls. The coach
       note used to be a third row above the board, capped at min(176px,
       19vh); it leads the report rail instead, so the whole column's height
       belongs to the square.

       `fit-content` is NOT valid in `grid-template-rows` in this browser, and
       an unsupported value does not degrade — it invalidates the whole
       declaration (measured with CSS.supports: `minmax(0, fit-content(24em))`
       is false). That is why three formulations of a content-sized coach row
       produced byte-identical measurements: none of them applied. Keep it out
       of this declaration rather than rediscover that. The note's cap lives on
       the frame itself now, in viewport units.

       The controls row is `min-content`, not `auto`: an `auto` track absorbs
       whatever the board row leaves over, which is how it reached 249px with a
       192px board beside it. */
    grid-template-rows:
      minmax(0, 1fr)
      min-content;
    /* Rows stretch to fill the grid container. `stretch` (the default) lets the
       1fr board row absorb all available height. `start` packed rows at the top
       and left dead space under the transport controls. */
    align-content: stretch;
    /* Centre the board's own track in whatever width is left. Without this the
       1fr track is offset by the eval-bar column and the board sits hard
       against the bar, off-centre in the page. */
    justify-content: center;
  }

  /* Row 2 is the board. It takes ALL the height the coach, controls and
     caption did not use, and the board squares itself inside it.

     The board used to be pinned to a --review-board length computed by
     subtracting a hand-entered --review-chrome constant. That is fragile by
     construction: the constant has to equal the height of a column whose
     tallest part is a block of coach prose, and when it is even slightly out
     the document is locked to 100dvh, so the error cannot scroll into view —
     it is simply clipped off the bottom of the board. Two of those constants
     were wrong on the way here (a 720px cap, then 268px of chrome), and each
     one cost real board.

     Sizing the square from the cell that is actually left over removes the
     guessing: the board can never be larger than the space in front of it, so
     it is never clipped, and it never leaves dead space below. */
  /* The shell is the board CELL: it fills the column so the stage inside it can
     centre itself against the full width. It must not be sized by the square,
     which is what put the board 23px right of centre — the shell was pinned to
     --review-board (614px) inside a 702px column and the leftover sat on the
     right. */
  body.review-page .review-board-shell {
    align-self: stretch;
    justify-self: stretch;
    width: 100%;
    min-width: 0;
    min-height: 0;
    height: 100%;
    display: grid;
    place-items: center;
  }

  /* The eval bar spans the board cell exactly. It is a grid item of the same
     row as the shell, so `stretch` is what makes it match — pinning it to a
     separate --review-board length is what let the two drift apart. */
  body.review-page .review-eval-wrap {
    grid-row: 1;
    align-self: stretch;
    min-height: 0;
  }

  /* The report rail matches the board's row and scrolls inside itself. */
  body.review-page .review-side {
    align-self: stretch;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior-y: contain;
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--gold) 40%, transparent) transparent;
  }

  body.review-page .review-side::-webkit-scrollbar {
    width: 8px;
  }

  body.review-page .review-side::-webkit-scrollbar-track {
    background: transparent;
  }

  body.review-page .review-side::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--gold) 38%, transparent);
    border-radius: 999px;
  }

  /* Inside a height-locked rail the moves list should use the space it has
     rather than its own 520px cap. */
  body.review-page .review-moves-wrap {
    max-height: none;
  }
}

/* The Insights tab is a long read: charts, a phases table, timelines. It is
   deliberately allowed to scroll the document, because compressing it to fit
   a viewport would mean cutting content rather than fitting it. The board view
   above is the one that never scrolls.

   THE TRAP: the whole height-locked board chain keys on
   `:has(#review-workspace:not([hidden]))`, and that workspace element is NOT
   hidden when Insights is open. `setViewTab` only toggles #review-view-board
   and #review-view-insight — the workspace holding both stays visible. So every
   board rule below still matches underneath the Insights panel, and the two
   places that matter most are:

     · body and .site-frame at `height: 100dvh; overflow: hidden` — this is the
       actual clip. The document is one viewport tall and the panel's overflow
       has nowhere to go, so the last sections are simply unreachable.
     · .review-root and #review-workspace at `height: 100%` with
       `flex: 1 1 auto` — which stops the panel growing to fit its content even
       once the frame is released.

   Every link in that chain is released explicitly below. `.site-frame` was the
   one that had been missed, which is why the panel was cut off with no
   scrollbar: the one rule that actually clipped was the one not overridden. */
@media (min-width: 1181px) {
  body.review-page:has(#review-view-insight:not([hidden])) {
    height: auto;
    max-height: none;
    overflow: visible;
  }

  /* The clip itself. Under the sigma design the frame is the flex column that
     holds the header, the shell and the footer, so it is locked to the viewport
     and hidden independently of body — releasing body alone changes nothing. */
  body.review-page:has(#review-view-insight:not([hidden])) .site-frame,
  html[data-design="sigma"] body.review-page:has(#review-view-insight:not([hidden])) .site-frame {
    height: auto;
    max-height: none;
    overflow: visible;
  }

  body.review-page:has(#review-view-insight:not([hidden])) .page-shell {
    flex: none;
    min-height: 0;
    overflow: visible;
  }

  /* The heights the board chain imposed. `auto` plus the released flex basis is
     what lets the panel be as tall as its content, which is the whole point of
     the tab. */
  body.review-page:has(#review-view-insight:not([hidden])) .review-root {
    height: auto;
    max-height: none;
    flex: 0 0 auto;
    min-height: 0;
  }

  body.review-page:has(#review-view-insight:not([hidden])) #review-workspace {
    height: auto;
    max-height: none;
    flex: 0 0 auto;
  }

  body.review-page:has(#review-view-insight:not([hidden])) .review-layout {
    display: block;
    width: auto;
    height: auto;
    max-height: none;
  }

  /* The root gave up its bottom padding for the board, which cannot scroll and
     could not reach it anyway. Insights does scroll — it is the ordinary
     document flow this block restores — so it gets its own back, or the last
     section ends flush against the footer. */
  body.review-page:has(#review-view-insight:not([hidden])) .review-root {
    padding-bottom: 56px;
  }
}

/* Below the trainer's breakpoint the page returns to ordinary document flow
   and stacks. A fixed-height instrument does not fit a phone, and a board
   sized purely by viewport height would be unusable there. */

/* ——— Sidebar tabs and panels ———
     Two-tab UI: Report and Analysis. Toggled via role=tab. */
.review-side-tabs {
  display: flex;
  gap: 4px;
  margin: 0 0 12px;
  padding: 0;
  list-style: none;
  font-size: 0.78rem;
}
.review-side-tabs .review-side-tab {
  flex: 1;
  padding: 6px 12px;
  border-radius: 8px 8px 0 0;
  border: 1px solid var(--line);
  border-bottom: none;
  background: var(--bg);
  color: var(--muted);
  cursor: pointer;
}
/* Emoji icon inside the sidebar tabs */
.review-tab-emoji {
  font-size: 1rem;
  line-height: 1;
  vertical-align: middle;
  margin-right: 4px;
}
.review-side-tabs .review-side-tab.is-active {
  background: var(--panel);
  border-color: var(--line);
  border-bottom: 1px solid var(--panel);
  color: var(--cream);
}
.review-side-tabs .review-side-tab[aria-selected="false"] {
  background: transparent;
  color: var(--muted);
  border-color: transparent;
}
.review-side-tabs .review-side-tab[aria-selected="true"] {
  background: var(--panel);
  color: var(--cream);
}
.review-side-panel {
  display: none;
}
.review-side-panel.is-active {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* Analysis panel (Analysis tab) — flex column for proper stacking

   The panel itself does NOT scroll; only the embedded moves list
   (#review-analysis-moves) scrolls internally, keeping the coach,
   override button and transport arrows fixed in place. */
#review-side-analysis.is-active {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* Do not scroll the whole panel — let the moves list handle its own scroll */
  overflow: hidden;
  /* Keep the transport off the bottom edge */
  padding: 0 0 48px;
}

/* Moves list inside the Analysis tab — scrollable, fills remaining space,
   clickable per-move like chess.com game review.

   The list sits between the coach and the override/transport footer and
   scrolls independently so the coach note and transport arrows stay visible
   while the reader scans through long games. */
#review-side-analysis .review-analysis-moves {
  flex: 1;
  min-height: 80px;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: 0;
  background: none;
  max-height: none;
}
#review-side-analysis .review-analysis-moves .review-move-row {
  grid-template-columns: 2rem 1fr 1fr;
  gap: 4px;
  margin-bottom: 2px;
  align-items: center;
}
#review-side-analysis .review-analysis-moves .review-move-num {
  font-size: 0.8rem;
}
#review-side-analysis .review-analysis-moves .review-move {
  padding: 4px 8px;
  border-radius: 6px;
  font-size: 0.9rem;
  border: 1px solid transparent;
}
#review-side-analysis .review-analysis-moves .review-move:hover {
  background: color-mix(in srgb, var(--bg-2) 60%, transparent);
}
#review-side-analysis .review-analysis-moves .review-move.is-active {
  background: color-mix(in srgb, var(--bg-2) 80%, transparent);
  border-color: var(--line-strong);
}
#review-side-analysis .review-analysis-moves .review-move .review-class-dot {
  width: auto;
  height: auto;
  font-size: 0.6rem;
}

@media (max-width: 900px) {
  #review-side-analysis .review-analysis-moves .review-move-row {
    grid-template-columns: 1.8rem 1fr 1fr;
    gap: 3px;
  }
  #review-side-analysis .review-analysis-moves .review-move {
    font-size: 0.82rem;
    padding: 3px 6px;
  }
}

.review-engine-lines {
  margin-top: 16px;
}
.review-engine-lines h3 {
  margin: 0 0 12px;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.review-engine-lines-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.review-engine-line {
  display: grid;
  grid-template-columns: 28px 1fr auto auto;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: 8px;
  background: var(--panel);
  border: 1px solid var(--line);
  font-size: 0.9rem;
  line-height: 1.3;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.review-engine-line:hover {
  background: var(--panel-hover);
  border-color: var(--line-strong);
}
.review-engine-rank {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 6px;
  font-size: 0.75rem;
  font-weight: 800;
  color: var(--muted);
  background: var(--bg);
}
/* The best line is the one row that carries the engine's verdict, and it was
   dressed in the brand accent (--gold) — neon green in the sigma theme, the
   same hue the board paints "good" moves and the same green every other UI
   accent uses. A full green bar read as decoration rather than as data, and
   the "1" badge inside it was a dark square that all but vanished against it.
   This gives the row its own engine-blue identity instead: a cool tint behind
   it, a solid accent edge down the left, and a filled rank chip — so the best
   line is the most legible thing in the box, and green is left to mean what it
   means everywhere else. Scoped to the rank-best row so it survives hover. */
.review-engine-line.review-engine-rank-best,
.review-engine-line.review-engine-rank-best:hover {
  background:
    linear-gradient(90deg,
      color-mix(in srgb, #38bdf8 18%, transparent) 0%,
      color-mix(in srgb, #38bdf8 7%, transparent) 100%),
    var(--panel);
  border-color: color-mix(in srgb, #38bdf8 50%, var(--line));
  box-shadow: inset 3px 0 0 #38bdf8;
}
/* The rank chip inside the best row: filled blue with near-black type, so the
   "1" reads at a glance instead of dissolving into the tint. */
.review-engine-line.review-engine-rank-best .review-engine-rank {
  background: #38bdf8;
  color: #04121b;
  font-weight: 800;
  box-shadow: 0 0 10px color-mix(in srgb, #38bdf8 50%, transparent);
}
/* The engine's chosen move is the point of the row — set it a step heavier. */
.review-engine-line.review-engine-rank-best .review-engine-move strong {
  font-weight: 800;
  color: var(--text);
}
.review-engine-move {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.review-engine-move strong {
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}

/* Alternative lines (2nd, 3rd best) — progressively muted */
.review-engine-line.review-engine-rank-alt {
  background: var(--panel);
  border-color: var(--line);
}
.review-engine-line.review-engine-rank-alt:hover {
  background: var(--panel-hover);
  border-color: var(--line-strong);
}
.review-engine-line.review-engine-rank-alt .review-engine-rank {
  background: color-mix(in srgb, var(--muted) 40%, var(--panel));
  color: var(--muted);
  font-weight: 600;
}
.review-engine-line.review-engine-rank-2 .review-engine-rank {
  background: color-mix(in srgb, var(--gold) 35%, var(--muted) 25%);
  color: var(--text);
}
.review-engine-line.review-engine-rank-3 .review-engine-rank {
  background: color-mix(in srgb, var(--gold) 20%, var(--muted) 40%);
  color: var(--muted);
}
.review-engine-line.review-engine-rank-alt .review-engine-move strong {
  color: color-mix(in srgb, var(--text) 75%, var(--muted));
  font-weight: 500;
}
.review-engine-line.review-engine-rank-alt .review-engine-eval {
  color: color-mix(in srgb, var(--text) 60%, var(--muted));
}
.review-engine-line.review-engine-rank-alt .review-engine-pv {
  color: var(--muted);
  opacity: 0.7;
}
.review-engine-pv {
  color: var(--muted);
  font-size: 0.85rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.review-engine-eval {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  padding-left: 8px;
}

/* Engine line visibility switch (on/off toggle at end of line) */
.review-engine-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  margin-left: 12px;
  flex-shrink: 0;
}
.review-engine-switch input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
.review-engine-switch-slider {
  position: relative;
  width: 40px;
  height: 22px;
  border-radius: 11px;
  background: var(--line);
  border: 1px solid var(--line-strong);
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
  display: flex;
  align-items: center;
  padding: 2px;
}
.review-engine-switch-slider::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--text);
  box-shadow: 0 1px 3px rgba(0,0,0,0.3);
  transition: left 0.2s ease, background 0.2s ease;
}
.review-engine-switch input[type="checkbox"]:checked + .review-engine-switch-slider {
  background: var(--gold);
  border-color: var(--gold-bright);
}
.review-engine-switch input[type="checkbox"]:checked + .review-engine-switch-slider::before {
  left: calc(100% - 18px);
  background: var(--bg);
}
.review-engine-switch input[type="checkbox"]:focus-visible + .review-engine-switch-slider {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}
.review-engine-switch:hover .review-engine-switch-slider {
  box-shadow: 0 0 8px color-mix(in srgb, var(--gold) 40%, transparent);
}

/* Dimmed line when toggled off */
.review-engine-line[data-rank]:has(input[type="checkbox"]:not(:checked)) {
  opacity: 0.5;
}
.review-engine-line[data-rank]:has(input[type="checkbox"]:not(:checked)) .review-engine-rank {
  opacity: 0.4;
}

/* Phase bars for Report tab — Opening / Middlegame / Endgame */
.review-report-phases {
  margin: 24px 0 16px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
}
.review-report-phases-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
@media (max-width: 720px) {
  .review-report-phases-grid {
    grid-template-columns: 1fr;
  }
}
.review-report-phase {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.review-report-phase-bar-wrap {
  position: relative;
  width: 100%;
  height: 22px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bg-3) 70%, transparent);
  overflow: hidden;
  display: flex;
  align-items: center;
  padding: 0 4px;
}
.review-report-phase-bar {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--bar, 0%);
  border-radius: inherit;
  background: linear-gradient(90deg, color-mix(in srgb, var(--gold) 55%, transparent), var(--gold-bright));
}
.review-report-phase-label {
  position: absolute;
  z-index: 1;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-weight: 700;
  font-size: 0.75rem;
  color: var(--text);
  text-shadow: 0 0 3px var(--bg), 0 0 6px var(--bg);
  white-space: nowrap;
  pointer-events: none;
}
.review-report-phase-acc {
  font-weight: 700;
  font-size: 0.8rem;
  padding: 2px 8px;
  border-radius: 6px;
  white-space: nowrap;
}
.review-report-phase-acc[data-tone="is-up"] {
  background: color-mix(in srgb, var(--data-win) 18%, transparent);
  color: var(--data-win);
}
.review-report-phase-acc[data-tone="is-even"] {
  background: color-mix(in srgb, var(--data-draw) 18%, transparent);
  color: var(--data-draw);
}
.review-report-phase-acc[data-tone="is-down"] {
  background: color-mix(in srgb, var(--data-loss) 18%, transparent);
  color: var(--data-loss);
}
.review-report-phase-acc:not([data-tone]) {
  background: color-mix(in srgb, var(--muted) 18%, transparent);
  color: var(--muted);
}
.review-start-review-launch {
  margin-top: 24px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
  text-align: center;
}
.review-start-review-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px 36px;
  border-radius: 14px;
  background: linear-gradient(135deg, var(--gold) 0%, var(--gold-bright) 100%);
  border: 1px solid color-mix(in srgb, var(--gold) 60%, transparent);
  color: var(--bg);
  font-size: 1.18rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  cursor: pointer;
  box-shadow:
    0 2px 8px color-mix(in srgb, var(--gold) 30%, transparent),
    inset 0 1px 0 color-mix(in srgb, #fff 12%, transparent);
  transition: background 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
  width: 100%;
  line-height: 1.4;
}
.review-start-review-btn:hover {
  background: linear-gradient(135deg, var(--gold-bright) 0%, #ffe066 100%);
  transform: translateY(-2px);
  box-shadow:
    0 6px 20px color-mix(in srgb, var(--gold) 35%, transparent),
    inset 0 1px 0 color-mix(in srgb, #fff 18%, transparent);
}
.review-start-review-btn:focus-visible {
  outline: 2px solid var(--gold-bright);
  outline-offset: 2px;
}
.review-start-review-btn:active {
  transform: translateY(0);
  box-shadow:
    0 1px 4px color-mix(in srgb, var(--gold) 25%, transparent),
    inset 0 1px 0 color-mix(in srgb, #fff 8%, transparent);
}
.review-start-review-glyph {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  opacity: 0.85;
  transition: opacity 0.15s ease;
}
.review-start-review-btn:hover .review-start-review-glyph {
  opacity: 1;
}
.review-start-review-hintline {
  margin: 10px 0 0;
  font-size: 0.8rem;
  color: var(--muted);
  letter-spacing: 0.02em;
}

/* Label inside the start-review button */
.review-start-review-label {
  font-size: 1.08rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* Analysis tab transport — clean, refined arrow buttons

   The transport sits at the bottom of the Analysis panel (with padding below).
   The buttons are compact and clean — flat with a subtle rounded frame, no
   heavy shadows. The active/disabled states are kept simple. */
.review-analysis-transport {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin: 0;
  padding: 0;
}
.review-analysis-transport .review-ctl {
  width: 38px;
  height: 38px;
  border-radius: 12px;
  background: linear-gradient(135deg, var(--gold) 0%, var(--gold-bright) 100%);
  border: 1px solid color-mix(in srgb, var(--gold) 60%, transparent);
  color: var(--bg);
  cursor: pointer;
  display: grid;
  place-items: center;
  font-size: 1rem;
  transition:
    background 0.12s ease,
    border-color 0.12s ease,
    color 0.12s ease;
}
.review-analysis-transport .review-ctl:hover {
  background: linear-gradient(135deg, var(--gold-bright) 0%, #86efac 100%);
  border-color: color-mix(in srgb, var(--gold) 70%, transparent);
  color: var(--bg);
}
.review-analysis-transport .review-ctl:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}
.review-analysis-transport .review-ctl:disabled {
  opacity: 0.3;
  cursor: not-allowed;
  color: var(--muted);
  border-color: transparent;
}
.review-analysis-transport .review-ctl-glyph {
  font-size: 1.2rem;
  line-height: 1;
  font-weight: 600;
}

/* Hint shown in the Analysis tab before the reader starts the review */
.review-analysis-start-hint {
  margin: 20px 0 0;
  padding: 16px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--gold) 7%, var(--panel));
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.5;
  text-align: center;
}

@media (max-width: 1180px) {
  body.review-page .review-layout {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto; /* let each section size to its content instead of the tiny desktop-derived --review-board-h */
    width: auto;
  }

  body.review-page .review-games-rail,
  body.review-page .review-board-shell,
  body.review-page .review-side {
    grid-row: auto;
    height: auto;
    max-height: none;
  }

  body.review-page .review-games-rail {
    max-height: 320px;
  }

  /* The board is still height-capped so it never outruns the screen, but it
     is allowed to use the full column width. */
  body.review-page .review-board-shell {
    width: min(100%, calc(100dvh - var(--review-gutter) * 2));
    margin-inline: auto;
  }

  body.review-page .review-eval-wrap {
    height: auto;
  }
}

/* ==========================================================================
   Blunder Override — the panel, the queue, and the launch button.

   The board states and the promotion picker are further up, next to the other
   board rules. These are the parts of the session that live outside the square:
   the entry point under the moves list, and the queue beside it. None of them
   had any styling on this page, so the session ran and moved pieces correctly
   while reading as unstyled text.

   All scoped to body.review-page, because the Blunder Override page and the
   home showcase reuse these .review-* class names at their own sizes.
   ========================================================================== */

body.review-page .review-override-launch {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  margin-top: 16px;
  padding: 14px 16px;
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  background: color-mix(in srgb, var(--gold) 7%, var(--panel));
}

body.review-page .review-override-launch[hidden] {
  display: none !important;
}

body.review-page .review-override-btn {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 9px 14px;
  border: 1px solid var(--gold);
  border-radius: 10px;
  background: color-mix(in srgb, var(--gold) 16%, transparent);
  color: var(--cream);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.18s ease, transform 0.18s ease;
}

body.review-page .review-override-btn:hover {
  background: color-mix(in srgb, var(--gold) 26%, transparent);
  transform: translateY(-1px);
}

/* An SVG icon, so it is sized as a box rather than as a font glyph. The old
   `font-size: 1.1rem` sized the glyph character; an SVG ignores that and would
   otherwise fall back to its own intrinsic size, which is not what the button's
   type scale assumes. */
body.review-page .review-override-btn-glyph {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  display: block;
}

body.review-page .review-override-btn-count {
  display: inline-grid;
  place-items: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--gold);
  color: var(--ink, #0d0e10);
  font-size: 0.78rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

body.review-page .review-override-hintline {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}

/* The queue sits under the moves list in the same rail, and takes the leftover
   height rather than growing: a long queue scrolls inside itself so it cannot
   push the board's column wider or taller. */
body.review-page .review-override-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

body.review-page .review-override-panel[hidden] {
  display: none !important;
}

body.review-page .review-override-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex: 0 0 auto;
}

body.review-page .review-override-panel-head h2 {
  margin: 0;
  font-size: 0.8rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

body.review-page #review-override-exit {
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 1.4rem;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}

body.review-page #review-override-exit:hover {
  color: var(--cream);
}

body.review-page .review-override-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  padding: 10px 0;
  font-size: 0.82rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

body.review-page .review-override-stats strong {
  color: var(--cream);
}

body.review-page .review-override-queue {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 0;
  max-height: 34vh;
  overflow-y: auto;
}

body.review-page .review-override-queue-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: color-mix(in srgb, var(--bg-3) 70%, transparent);
  color: var(--cream);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.18s ease, background 0.18s ease;
}

body.review-page .review-override-queue-item:hover {
  border-color: var(--gold);
}

body.review-page .review-override-queue-item.is-active {
  border-color: var(--gold);
  background: color-mix(in srgb, var(--gold) 12%, transparent);
}

body.review-page .review-override-queue-item.is-solved {
  opacity: 0.62;
}

body.review-page .review-override-queue-idx {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--gold) 20%, transparent);
  font-size: 0.76rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

body.review-page .review-override-queue-info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

body.review-page .review-override-queue-title {
  font-size: 0.88rem;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

body.review-page .review-override-queue-tier {
  font-size: 0.76rem;
  color: var(--muted);
}

body.review-page .review-override-queue-tier.is-clear {
  color: var(--danger-text);
}

/* The missed-win tier. Distinct from "is-clear" because a thrown won position
   is a decision error rather than a dropped piece, and the queue should not
   read as though something was hanging. */
body.review-page .review-override-queue-tier.is-thrown {
  color: var(--gold);
}

/* Empty state shown when a game has no blunders to override. */
body.review-page .review-override-empty {
  text-align: center;
  padding: 28px 16px;
}
body.review-page .review-override-empty-title {
  margin: 0 0 6px;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--cream);
}
body.review-page .review-override-empty-note {
  margin: 0;
  max-width: 40ch;
  color: var(--muted);
  font-size: 0.82rem;
  line-height: 1.5;
}

/* Skipped positions have reduced opacity */
body.review-page .review-override-queue-item.is-skipped {
  opacity: 0.55;
  pointer-events: none;
}

/* ——— Override filter buttons ——— */
body.review-page .review-override-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 6px 0;
}

body.review-page .review-override-filter-btn {
  padding: 4px 12px;
  border: 1px solid var(--line);
  border-radius: 20px;
  background: color-mix(in srgb, var(--bg-3) 50%, transparent);
  color: var(--muted);
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
  transition: border-color 0.18s ease, background 0.18s ease;
}

body.review-page .review-override-filter-btn.is-active {
  border-color: var(--gold);
  background: color-mix(in srgb, var(--gold) 16%, transparent);
  color: var(--cream);
}

body.review-page .review-override-filter-btn:hover {
  border-color: var(--gold);
}



/* ——— Build from all games button ——— */
body.review-page .review-override-build-all {
  display: block;
  width: 100%;
  margin-top: 8px;
  font-size: 0.78rem;
}

/* ——— Override summary overlay ——— */
body.review-page .review-override-summary {
  padding: 14px;
  border: 1px solid var(--gold);
  border-radius: 12px;
  background: color-mix(in srgb, var(--bg-3) 70%, transparent);
  margin-top: 12px;
}

body.review-page .review-override-summary[hidden] {
  display: none !important;
}

body.review-page .review-override-summary-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

body.review-page .review-override-summary-head h2 {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 800;
}

body.review-page .review-override-summary-close {
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 1.3rem;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}

body.review-page .review-override-summary-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  padding: 8px 0;
  font-size: 0.82rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

body.review-page .review-override-summary-stats strong {
  color: var(--cream);
}

body.review-page .review-override-summary-positions {
  padding: 6px 0;
  font-size: 0.8rem;
  color: var(--muted);
  max-height: 20vh;
  overflow-y: auto;
}

body.review-page .review-summary-stat {
  white-space: nowrap;
}

body.review-page .review-summary-pos {
  display: block;
  line-height: 1.6;
}

/* ——— Skip and flag buttons in the override toolbar ——— */
body.review-page .review-override-skip-btn,
body.review-page .review-override-flag-btn {
  font-size: 0.75rem;
}

/* ——— Override tab sidebar ——— */
body.review-page #review-side-override {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
}

body.review-page #review-side-override[hidden] {
  display: none !important;
}

/* Override control buttons use the same base styles as the review board
   controls — gold primary, cream-dim quiet, hover states, the full theme.
   The selectors are duplicated here because the buttons live in a sidebar
   panel, not inside .review-board-controls. */
body.review-page .review-override-ctl-group[hidden] {
  display: none !important;
}

body.review-page .review-override-ctl-bar {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  align-items: center;
  min-height: 0;
}

body.review-page .review-override-ctl-bar .review-ctl,
body.review-page .review-override-ctl-bar .review-ctl-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 34px;
  padding-inline: 10px;
  font-family: var(--font);
  font-size: 0.82rem;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color 0.16s ease,
    border-color 0.16s ease,
    color 0.16s ease,
    transform 0.16s ease;
  border-radius: 9px;
}

body.review-page .review-override-ctl-bar .review-ctl {
  border: 1px solid transparent;
  background: transparent;
  color: var(--cream-dim);
}

body.review-page .review-override-ctl-bar .review-ctl:hover:not(:disabled) {
  background: var(--hover-fill);
  border-color: var(--line-strong);
  color: var(--cream);
}

body.review-page .review-override-ctl-bar .review-ctl:active:not(:disabled) {
  transform: translateY(1px);
}

body.review-page .review-override-ctl-bar .review-ctl:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

body.review-page .review-override-ctl-bar .review-ctl-primary {
  border-color: color-mix(in srgb, var(--gold) 46%, transparent);
  background: linear-gradient(180deg, var(--gold-bright), var(--gold));
  color: var(--on-gold);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 4px 14px color-mix(in srgb, var(--gold) 26%, transparent);
}

body.review-page .review-override-ctl-bar .review-ctl-primary:hover:not(:disabled) {
  background: linear-gradient(180deg, color-mix(in srgb, var(--gold-bright) 88%, white), var(--gold-bright));
  border-color: color-mix(in srgb, var(--gold) 70%, transparent);
  color: var(--on-gold);
}

body.review-page .review-override-ctl-bar .review-ctl-primary:active:not(:disabled) {
  transform: translateY(1px);
}

/* Prominent Next button sits at the top of the panel, always visible without
   scrolling. Full-width gold gradient button with a right arrow. */
body.review-page .review-override-next-prime {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  height: 44px;
  margin: 0;
  padding: 0 20px;
  border: 0;
  border-radius: 12px;
  background: linear-gradient(180deg, var(--gold-bright), var(--gold));
  color: var(--on-gold);
  font-family: var(--font);
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 4px 18px color-mix(in srgb, var(--gold) 28%, transparent);
  transition:
    background 0.16s ease,
    transform 0.16s ease;
  flex: 0 0 auto;
}

body.review-page .review-override-next-prime:hover {
  background: linear-gradient(180deg, color-mix(in srgb, var(--gold-bright) 88%, white), var(--gold-bright));
  transform: translateY(-1px);
}

body.review-page .review-override-next-prime:active {
  transform: translateY(1px);
}

body.review-page .review-override-next-prime:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

body.review-page .review-override-next-prime .review-ctl-glyph {
  font-size: 1.1rem;
  line-height: 1;
}

body.review-page .review-override-next-prime .review-override-next-label {
  font-size: inherit;
}

body.review-page .review-override-exit-side {
  display: block;
  width: 100%;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: color-mix(in srgb, var(--bg-3) 50%, transparent);
  color: var(--muted);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  text-align: center;
  transition: border-color 0.18s ease;
}

body.review-page .review-override-exit-side:hover {
  border-color: var(--gold);
  color: var(--cream);
}

body.review-page .review-override-exit-side[hidden] {
  display: none !important;
}

/* ——— Insights sidebar: progress bars ——— */
body.review-page .review-insight-summary {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px;
}

body.review-page .review-insight-metric {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

body.review-page .review-insight-metric-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 0.85rem;
  color: var(--muted);
}

body.review-page .review-insight-metric-head strong {
  color: var(--cream);
  font-size: 0.95rem;
}

body.review-page .review-insight-bar-track {
  width: 100%;
  height: 8px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}

body.review-page .review-insight-bar-fill {
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--gold), var(--gold-bright));
  transition: width 0.4s ease;
}

body.review-page .review-insight-bar-fill--warn {
  background: linear-gradient(90deg, #f87171, #ef4444);
}

/* ——— Insights: fit the whole read on one screen ————————————————————————
   The critical-moments list — the section people most want — was falling off
   the bottom of the page, with nothing to scroll to. Two causes, and the fonts
   were the smaller one.

   1. TYPE AND SPACING. Every figure here was set for a page that scrolls, so
      it could afford generous sizes. On a 1080p screen the panel needed roughly
      two viewports: eleven sections, three charts, and a pair of critical-moment
      timelines that each grew with the number of mistakes in the game. A bad
      game is the worst case, and that is exactly when the list matters most.

   2. THE CHIPS. Each critical moment is a two-line card, so a game with twelve
      mistakes spent twelve of them as ~56px blocks. They are now a single
      inline line each, which is both shorter and easier to scan in order.

   The scale is applied as one block of overrides rather than by editing every
   original declaration, because the originals are the readable reference and
   the compaction is a separate, deliberate decision about density. A test pins
   the relationship between the two so the pair cannot drift.

   Nothing here drops content: same sections, same charts, same chips. Only the
   space each one takes is reduced. */

@media (min-width: 1181px) {
  /* Section rhythm. The single biggest saving: eleven sections at 26px of
     padding top and bottom is 570px of the panel before a single word. */
  body.review-page .review-insight-block {
    padding: 13px 0;
  }

  body.review-page .review-insight-tile {
    padding: 11px 16px;
  }

  body.review-page .review-insight-tile-value {
    margin-top: 3px;
    font-size: 1.3rem;
  }

  body.review-page .review-insight-tile-note {
    margin-top: 2px;
    font-size: 0.7rem;
  }

  body.review-page .review-insight-title {
    font-size: 1.02rem;
  }

  body.review-page .review-insight-note {
    margin-top: 3px;
    font-size: 0.78rem;
    line-height: 1.4;
  }

  body.review-page .review-insight-headline {
    margin-top: 5px;
    font-size: 0.95rem;
    line-height: 1.5;
  }

  /* The two big charts. These are the other large fixed cost, and they are the
     easiest to shrink without losing anything — a trendline does not need
     220px to show a trend. */
  body.review-page .review-insight-chart {
    height: clamp(112px, 13vh, 150px);
  }

  body.review-page .review-insight-trendchart {
    height: clamp(96px, 11vh, 124px);
    margin-top: 8px;
  }

  body.review-page .review-insight-figure {
    margin-top: 8px;
  }

  body.review-page .review-insight-caption {
    margin-top: 4px;
    font-size: 0.72rem;
  }

  body.review-page .review-insight-table {
    margin-top: 8px;
  }

  body.review-page .review-insight-table th,
  body.review-page .review-insight-table td {
    padding: 5px 10px;
    font-size: 0.8rem;
  }

  body.review-page .review-insight-table thead th {
    font-size: 0.6rem;
  }

  /* ——— The chips ————
     One line each instead of a stacked card. The engine line ("engine wanted
     Nf3") was the second row that made every chip two rows tall; it is dropped
     from the chip and kept in the title attribute, which already carried the
     move, the SAN and the centipawn loss. Nothing is lost — the detail is still
     there, it is just not competing for vertical space with the move itself. */
  body.review-page .review-insight-chip {
    flex-direction: row;
    align-items: baseline;
    gap: 6px;
    padding: 3px 9px;
    font-size: 0.78rem;
    border-radius: 999px;
  }

  body.review-page .review-insight-chip-want {
    display: none;
  }

  body.review-page .review-insight-chip-cp {
    font-size: 0.7rem;
  }

  body.review-page .review-insight-timeline {
    gap: 5px;
    margin-top: 8px;
  }

  body.review-page .review-insight-empty {
    margin-top: 8px;
    font-size: 0.8rem;
  }

  /* ——— The prose blocks ——— */
  body.review-page .review-insight-conversion {
    gap: 6px;
  }

  body.review-page .review-insight-conv-head {
    font-size: 1rem;
    line-height: 1.35;
  }

  body.review-page .review-insight-conv-row {
    grid-template-columns: 6rem auto minmax(0, 1fr);
  }

  body.review-page .review-insight-conv-row strong {
    font-size: 1.05rem;
  }

  body.review-page .review-insight-conv-row small {
    font-size: 0.7rem;
  }

  body.review-page .review-insight-conv-note {
    margin-top: 2px;
    font-size: 0.8rem;
    line-height: 1.45;
  }

  body.review-page .review-insight-book {
    margin-top: 8px;
    gap: 6px;
  }

  body.review-page .review-insight-booknote {
    font-size: 0.86rem;
    line-height: 1.45;
  }

  body.review-page .review-insight-bookrow {
    gap: 4px 12px;
  }

  body.review-page .review-insight-bookrow-label {
    font-size: 0.8rem;
  }

  body.review-page .review-insight-bookrow-bar {
    height: 5px;
  }

  body.review-page .review-insight-bookrow-value {
    font-size: 0.86rem;
  }

  body.review-page .review-insight-bookrow-sub {
    font-size: 0.68rem;
  }

  body.review-page .review-insight-verdict {
    margin-top: 3px;
    font-size: 0.85rem;
    line-height: 1.45;
  }

  body.review-page .review-insight-openings {
    margin-top: 8px;
    padding-top: 8px;
  }

  body.review-page .review-insight-openrow {
    font-size: 0.76rem;
  }

  body.review-page .review-insight-openrow-bar {
    height: 4px;
  }

  body.review-page .review-insight-openrow-games {
    font-size: 0.68rem;
  }
}

/* ——— Guest lock: blur + overlay for non-logged-in visitors ————————

   When `data-guest-locked` is on #review-root, the page content is
   blurred behind a sign-in prompt. The exception is when a guest arrives
   from the homepage scan deep-link (source + player in the query-string),
   which grants one unlocked session.                                     */

body.review-page #review-root[data-guest-locked] .review-guest-lock {
  /* Establish a positioning context for the absolutely-positioned card. */
  position: relative;
  /* The blurred body holds only an empty status line + hidden workspace for a
     guest, so without this the container collapses and the centred card
     squeezes against the top edge. Evaluation's body wraps full page content
     so it is naturally tall — this floor makes review match its position. */
  min-height: min(78vh, 620px);
}

body.review-page #review-root[data-guest-locked] .review-guest-lock-body {
  filter: blur(7px) saturate(0.7);
  opacity: 0.5;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
  /* Same cap as the evaluation page so the lock card centres at the same
     height there. */
  max-height: 620px;
  /* Mirror the container floor so the fade mask has room to breathe even
     with no visible content behind the card. */
  min-height: min(78vh, 620px);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, #000 55%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 55%, transparent 100%);
}

body.review-page .review-guest-lock-card {
  /* Centred small box overlay inside the guest-lock container. */
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 340px;
  padding: 20px 24px 22px;
  text-align: center;
  border-radius: 16px;
  /* Premium surface depth: warm top-light + soft gold centre glow + rich base. */
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--cream) 4%, transparent) 0%, transparent 40%),
    radial-gradient(ellipse 70% 60% at 50% 42%, color-mix(in srgb, var(--gold) 6%, transparent), transparent 60%),
    color-mix(in srgb, var(--bg) 96%, var(--bg-2));
  /* Frosted layer so the sign-in prompt reads as its own surface. */
  -webkit-backdrop-filter: blur(5px) saturate(1.05);
  backdrop-filter: blur(5px) saturate(1.05);
  /* Premium glass border with neon green cast. */
  border: 1px solid color-mix(in srgb, var(--gold) 24%, var(--glass-border));
  /* Elevation + neon green glow stack — matches tracking page. */
  box-shadow:
    var(--panel-glow),
    0 0 0 1px var(--gold-glow),
    0 0 18px var(--glow-line),
    0 0 48px color-mix(in srgb, var(--glow-line) 35%, transparent);
  animation: om-fade-in 0.2s ease-out both;
}

/* Safety net: the picker <dialog> now lives outside the guest-lock wrapper
   and review.js never calls showModal() for locked guests, so this should
   never fire — kept so a future auto-open cannot freeze the viewport behind
   an invisible modal again. */
body.review-page #review-root[data-guest-locked] .review-picker {
  display: none !important;
}

body.review-page .review-guest-lock-badge {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border: 1px solid color-mix(in srgb, #a855f7 34%, var(--line));
  border-radius: 12px;
  color: #a855f7;
  background: color-mix(in srgb, #a855f7 10%, transparent);
  font-size: 1.5rem;
}

body.review-page .review-guest-lock-badge svg {
  width: 24px;
  height: 24px;
  display: block;
}

body.review-page .review-guest-lock-title {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.2rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--cream);
}

body.review-page .review-guest-lock-copy {
  margin: 0;
  max-width: 46ch;
  color: var(--cream-dim);
  font-size: 0.86rem;
  line-height: 1.55;
}

body.review-page .review-guest-lock-cta {
  margin-top: 4px;
  text-decoration: none;
  pointer-events: auto;
}

body.review-page .review-guest-lock-secondary {
  display: inline-block;
  margin: 0;
  color: var(--muted);
  font-size: 0.74rem;
  pointer-events: auto;
}

@media (prefers-reduced-motion: reduce) {
  body.review-page .review-guest-lock-card {
    animation: none;
  }
}

@media (max-width: 640px) {
  body.review-page .page-shell {
    width: calc(100% - 8px);
  }
  body.review-page .review-root {
    padding: 6px 0 24px;
  }
  body.review-page .review-board-controls {
    gap: 4px;
    padding: 6px;
    overflow-x: auto;
  }
  body.review-page .review-ctl-primary {
    padding: 0 12px;
    font-size: 0.85rem;
  }
  body.review-page .review-games-rail {
    display: none;
  }
  body.review-page .review-layout {
    /* Flex column: board at top, scrollable panels below */
    display: flex;
    flex-direction: column;
    gap: 14px;
  }
  body.review-page .review-board-col {
    flex: 0 0 auto;
    width: 100%;
  }
  body.review-page .review-board-shell {
    width: 100%;
  }
  body.review-page .review-side {
    flex: 0 0 auto;
    overflow: visible;
    max-height: none;
    grid-column: auto;
    align-self: auto;
    padding: 0;
  }
  body.review-page .review-side-tabs {
    margin: 0 0 6px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  body.review-page .review-side-tabs::-webkit-scrollbar {
    display: none;
  }
  body.review-page .review-side-tab {
    padding: 5px 6px;
    white-space: nowrap;
    font-size: 0.72rem;
  }
  body.review-page .review-tab-emoji {
    font-size: 0.8rem;
    margin-right: 2px;
  }
  body.review-page .review-side-panel.is-active {
    flex: 0 0 auto;
  }
  body.review-page .review-report {
    padding: 14px;
    border-radius: 10px;
  }
  body.review-page .review-workspace-top,
  body.review-page #review-view-board {
    padding: 10px 12px;
  }
}

@media (max-width: 640px) {
  body.mistakes-page .mistakes-controls {
    gap: 4px;
    padding: 5px;
    overflow-x: auto;
  }
  body.mistakes-page .review-ctl-primary {
    padding: 0 10px;
    font-size: 0.8rem;
  }
  body.mistakes-page .mistakes-context-card {
    padding: 12px 14px;
  }
}
/* ════════════════════════════════════════════════════════════════════
   PREMIUM VISUAL POLISH — Overall page enhancements
   ════════════════════════════════════════════════════════════════════ */

/* Board column — perfect square with premium matte finish */
body.review-page .review-board-col {
  position: relative;
}

body.review-page .review-board-shell {
  border-radius: var(--radius-lg);
  background: 
    radial-gradient(ellipse 80% 80% at 50% 0%, color-mix(in srgb, var(--gold) 3%, transparent), transparent 50%),
    radial-gradient(ellipse 60% 60% at 100% 100%, color-mix(in srgb, var(--gold) 2%, transparent), transparent 40%),
    var(--board-mat);
  border: 1px solid var(--line-strong);
  box-shadow: 
    var(--elev-2),
    inset 0 1px 0 color-mix(in srgb, var(--cream) 4%, transparent);
  overflow: hidden;
}

body.review-page .review-board-shell::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: 
    radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--cream) 3%, transparent) 1px, transparent 0);
  background-size: 24px 24px;
  pointer-events: none;
  opacity: 0.03;
}

/* Eval bar — dark gray track with neon green fill */
body.review-page .review-eval-bar {
  border-radius: var(--radius-pill);
  background: #2a2a2a;
  box-shadow: 
    inset 0 1px 0 rgba(255,255,255,0.05),
    0 2px 8px rgba(0,0,0,0.3),
    0 0 0 1px var(--line-strong);
  overflow: hidden;
}

body.review-page .review-eval-fill {
  border-radius: inherit;
  background: 
    linear-gradient(90deg, #00ff88, #00ff66, #00e655);
  box-shadow: 
    0 0 16px rgba(0, 255, 136, 0.6),
    0 0 32px rgba(0, 255, 136, 0.4),
    inset 0 1px 0 rgba(255,255,255,0.25);
  transition: width var(--transition-spring), transform var(--transition-fast);
}

body.review-page .review-eval-label {
  text-shadow: 0 1px 2px rgba(0,0,0,0.5);
  font-variant-numeric: tabular-nums;
}

/* Side panels — glassmorphism cards */
body.review-page .review-side-panel {
  background: var(--surface-panel);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

body.review-page .review-side-panel:hover {
  box-shadow: var(--elev-2);
  border-color: var(--line-strong);
}

/* Side panel tabs — premium styling */
body.review-page .review-viewtabs {
  background: var(--surface-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 4px;
  gap: 4px;
}

body.review-page .review-viewtabs button {
  border-radius: var(--radius-sm);
  font-weight: var(--weight-semibold);
  font-size: var(--text-sm);
  transition: all var(--transition-fast);
  position: relative;
  overflow: hidden;
}

body.review-page .review-viewtabs button::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, var(--gold), var(--gold-bright));
  opacity: 0;
  transition: opacity var(--transition-fast);
  border-radius: inherit;
}

body.review-page .review-viewtabs button.is-active {
  color: var(--on-gold);
  box-shadow: 0 2px 8px var(--gold-glow);
}

body.review-page .review-viewtabs button.is-active::before {
  opacity: 1;
}

/* Coach panel — animated avatar, premium styling */
body.review-page .review-coach {
  background: var(--surface-panel);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-1);
  padding: 16px;
  transition: all var(--transition-base);
}

body.review-page .review-coach:hover {
  box-shadow: var(--elev-2);
  border-color: var(--line-strong);
}

body.review-page .review-coach-avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--gold), var(--gold-bright));
  box-shadow: 0 0 20px var(--gold-glow);
  animation: om-pulse 3s ease-in-out infinite;
}

@keyframes om-pulse {
  0%, 100% { box-shadow: 0 0 20px var(--gold-glow); }
  50% { box-shadow: 0 0 30px var(--gold-glow-strong); }
}

body.review-page .review-coach-text {
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--cream);
  animation: om-slide-up var(--motion-base) var(--motion-ease-out);
}

@keyframes om-slide-up {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Coach expand button & detail panel — matches site .btn-gold theme */
body.review-page .review-coach-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}

body.review-page .review-coach-expand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  border: 1px solid var(--gold-btn-border);
  border-radius: var(--radius-pill);
  background: linear-gradient(180deg, var(--gold-btn-from), var(--gold-btn-to));
  color: var(--on-solid);
  font-family: var(--font);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  cursor: pointer;
  transition: all var(--transition-base);
  box-shadow: var(--elev-1);
}

body.review-page .review-coach-expand:hover {
  border-color: var(--gold-btn-to-hover);
  background: linear-gradient(180deg, var(--gold-btn-from-hover), var(--gold-btn-to-hover));
  color: var(--on-solid);
  transform: translateY(-1px);
  box-shadow: var(--elev-2);
}

body.review-page .review-coach-expand:active {
  transform: translateY(0) scale(0.98);
}

body.review-page .review-coach-expand:focus-visible {
  outline: 2px solid var(--gold-bright);
  outline-offset: 2px;
}

body.review-page .review-coach-expand[aria-expanded="true"] {
  border-color: var(--gold-btn-border);
  background: linear-gradient(180deg, var(--gold-btn-from), var(--gold-btn-to));
  color: var(--on-solid);
  box-shadow: var(--elev-1), 0 0 0 1px var(--gold-glow);
}

body.review-page .review-coach-expand[aria-expanded="true"] .review-coach-expand-icon {
  transform: rotate(180deg);
}

body.review-page .review-coach-expand-icon {
  transition: transform var(--transition-spring);
  font-size: 0.65rem;
  color: var(--on-solid);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
}

body.review-page .review-coach-expand-label {
  font-variant-numeric: tabular-nums;
}

body.review-page .review-coach-detail {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  animation: om-slide-down var(--motion-base) var(--motion-ease-out);
}

@keyframes om-slide-down {
  from { opacity: 0; transform: translateY(-8px); max-height: 0; }
  to { opacity: 1; transform: translateY(0); max-height: 500px; }
}

body.review-page .review-coach-detail-section {
  line-height: var(--leading-relaxed);
}

body.review-page .review-coach-detail-text {
  margin: 0 0 10px;
  color: var(--cream);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
}

body.review-page .review-coach-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

body.review-page .review-coach-tag {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  background: var(--gold-soft);
  color: var(--gold);
  border: 1px solid var(--gold-glow);
  box-shadow: 0 1px 2px rgba(57, 255, 20, 0.1);
  transition: all var(--transition-fast);
}

body.review-page .review-coach-tag:hover {
  transform: translateY(-1px);
  box-shadow: 0 2px 8px var(--gold-glow);
}

body.review-page .review-coach-tag.eval-swing {
  background: color-mix(in srgb, var(--gold) 15%, transparent);
  color: var(--gold);
  border: 1px solid var(--gold-glow);
}

body.review-page .review-coach-tag.eval-swing:hover {
  background: color-mix(in srgb, var(--gold) 25%, transparent);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  body.review-page .review-coach-detail { animation: none; }
  body.review-page .review-coach-expand-icon,
  body.review-page .review-coach-expand,
  body.review-page .review-coach-tag { transition: none; }
}

/* ——— Premium neon finish ———————————————————————————————————————————————

   One restrained glow pass over the review page, built only on the site's
   own accent channels (--gold-soft, --gold-glow, --glow-line from the theme
   stylesheets), so light and dark mode both read as neon without a second
   palette. Everything below is additive — no existing declaration is touched
   — and nothing paints the explicitly un-surfaced areas: .review-workspace
   keeps its no-shadow rule, .review-board-col keeps no panel, and the board
   and promotion picker keep border-radius: 0 (see the square-edge and
   no-panel tests). Hero buttons fold their existing base shadows in rather
   than replacing them, and the games rail rows keep the inset accent edge a
   test pins. The existing bottom-of-file reduced-motion block silences every
   animation added here. */

body.review-page {
  --om-neon-ring: 0 0 0 1px var(--gold-glow), 0 0 18px var(--glow-line);
  --om-neon-ring-strong:
    0 0 0 1px var(--gold-glow),
    0 0 10px var(--glow-line),
    0 0 32px color-mix(in srgb, var(--glow-line) 70%, transparent);
}

/* 1. The board carries the room's light source: a live green ring plus a
   slow breathing halo, the page's single strongest glow. The border itself
   is the frame the parity tests measure, so it is not redeclared — only the
   shadow around it. */
body.review-page .review-board {
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.03),
    0 6px 18px rgba(0, 0, 0, 0.12),
    0 0 0 1px var(--gold-glow),
    0 0 22px var(--glow-line);
}

@media (prefers-reduced-motion: no-preference) {
  body.review-page .review-board {
    animation: om-board-breathe 4.5s ease-in-out infinite;
  }
}

@keyframes om-board-breathe {
  0%, 100% {
    box-shadow:
      inset 0 0 0 1px rgba(255, 255, 255, 0.03),
      0 6px 18px rgba(0, 0, 0, 0.12),
      0 0 0 1px var(--gold-glow),
      0 0 22px var(--glow-line);
  }
  50% {
    box-shadow:
      inset 0 0 0 1px rgba(255, 255, 255, 0.03),
      0 6px 18px rgba(0, 0, 0, 0.12),
      0 0 0 1px var(--gold-glow),
      0 0 34px var(--glow-line);
  }
}

/* 2. The hero calls-to-action glow at rest and flare on hover. Start Review is
   the brightest thing on the page once a game loads, and Override blunders is
   the second door into training, so the neon makes both read as the obvious
   next press. Each button's existing base shadow is folded in, not replaced —
   a second box-shadow declaration here would otherwise drop it. The transport
   arrows keep the site's wash-only hover and gain only a text glow. */
body.review-page .review-start-review-btn {
  box-shadow:
    0 2px 8px color-mix(in srgb, var(--gold) 30%, transparent),
    inset 0 1px 0 color-mix(in srgb, #fff 12%, transparent),
    0 0 18px var(--glow-line);
}

body.review-page .review-start-review-btn:hover {
  box-shadow:
    0 6px 20px color-mix(in srgb, var(--gold) 35%, transparent),
    inset 0 1px 0 color-mix(in srgb, #fff 18%, transparent),
    0 0 30px var(--glow-line);
}

body.review-page .review-override-btn {
  box-shadow: 0 0 14px color-mix(in srgb, var(--glow-line) 60%, transparent);
}

body.review-page .review-override-btn:hover {
  box-shadow: 0 0 24px var(--glow-line);
}

body.review-page .review-analysis-transport .review-ctl:hover:not(:disabled) {
  text-shadow: 0 0 8px var(--glow-line);
}

/* 3. Sidebar tabs: the active tab gets the neon underline treatment, with a
   soft text glow so the label reads as lit rather than painted. */
body.review-page .review-side-tabs .review-side-tab.is-active,
body.review-page .review-side-tabs .review-side-tab[aria-selected="true"] {
  box-shadow:
    inset 0 -2px 0 var(--gold),
    0 4px 14px color-mix(in srgb, var(--glow-line) 55%, transparent);
  text-shadow: 0 0 10px var(--glow-line);
}

/* 4. Move list: the current move is the one lit marker on the page. Hover
   stays a quiet wash; only .is-active glows. */
body.review-page .review-move.is-active {
  box-shadow:
    inset 0 0 0 1px var(--gold-glow),
    0 0 12px color-mix(in srgb, var(--glow-line) 60%, transparent);
  text-shadow: 0 0 8px var(--glow-line);
}

/* 5. The analysis overlay card gets a gold ring so the modal moment reads as
   one: it already has a deep shadow, this just ties it to the same light. */
body.review-page .review-progress-card {
  box-shadow:
    0 24px 60px rgba(0, 0, 0, 0.5),
    var(--om-neon-ring);
}

/* 6. Rails: a hairline of light on the games rail title and the eval readouts
   so the chrome around the board carries the theme without new surfaces. */
body.review-page .review-games-rail-title {
  text-shadow: 0 0 12px var(--glow-line);
}

body.review-page .review-eval-label {
  text-shadow: 0 0 8px var(--glow-line);
}

/* 7. Games rail rows pick up a green halo on hover, over the left accent edge
   they already carry. No transform: the row shifts its own background and edge
   on hover, and a second motion would fight it. */
body.review-page .review-games-rail-scroll .imported-game-row:hover,
body.review-page .review-games-rail-scroll .imported-game-row:focus-visible {
  box-shadow:
    inset 2px 0 0 var(--gold),
    0 0 14px color-mix(in srgb, var(--glow-line) 55%, transparent);
}

/* 8. Reduced motion: shimmer, pulse, and glow loops go static. The resting
   glows above stay — they are paint, not motion. */
@media (prefers-reduced-motion: reduce) {
  body.review-page .review-board,
  body.review-page .review-progress-dots i,
  body.review-page .review-progress-bar::after {
    animation: none;
  }
}

/* Move list — virtualized, phase-grouped, premium styling */
body.review-page .review-move-row {
  display: grid;
  grid-template-columns: 2rem 1fr 1fr;
  gap: 8px;
  align-items: center;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  transition: all var(--transition-fast);
}

body.review-page .review-move-row:hover {
  background: var(--hover-fill);
}

body.review-page .review-move {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--cream);
  font-family: var(--serif);
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  cursor: pointer;
  transition: all var(--transition-fast);
}

body.review-page .review-move:hover {
  background: var(--gold-soft);
  border-color: var(--gold-glow);
}

body.review-page .review-move.is-active {
  background: linear-gradient(135deg, var(--gold-soft), var(--gold-soft-hover));
  border-color: var(--gold);
  box-shadow: 0 0 0 1px var(--gold-glow);
  color: var(--cream);
}

body.review-page .review-class-dot {
  width: auto;
  height: auto;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  flex-shrink: 0;
}

body.review-page .review-class-dot.brilliant::before { content: "❗"; color: #a78bfa; font-weight: bold; }
body.review-page .review-class-dot.best::before { content: "★"; color: var(--gold-bright); font-weight: bold; }
body.review-page .review-class-dot.excellent::before { content: "👍"; color: #86efac; font-weight: bold; }
body.review-page .review-class-dot.good::before { content: "✓"; color: #22c55e; font-weight: bold; }
body.review-page .review-class-dot.inaccuracy::before { content: "?"; color: #fbbf24; font-weight: bold; }
body.review-page .review-class-dot.mistake::before { content: "!"; color: #fb923c; font-weight: bold; }
body.review-page .review-class-dot.blunder::before { content: "⁇"; color: #ef4444; font-weight: bold; }

/* Phase badges in move list */
body.review-page .review-move-phase {
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-3);
}

body.review-page .review-viewtabs button:not(.is-active) {
  color: var(--muted);
}

body.review-page .review-viewtabs button:not(.is-active):hover {
  color: var(--cream);
  background: var(--gold-soft);
}

body.review-page .review-viewtabs button span {
  position: relative;
  z-index: 1;
}
/* Game rail — premium cards with avatars */
body.review-page .review-games-rail {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

body.review-page .review-game-item {
  display: grid;
  grid-template-columns: 40px 1fr auto;
  gap: 12px;
  align-items: center;
  padding: 10px 12px;
  background: var(--surface-panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all var(--transition-fast);
  animation: om-fade-in var(--motion-base) var(--motion-ease-out) backwards;
}

body.review-page .review-game-item:hover {
  border-color: var(--line-strong);
  box-shadow: var(--elev-1);
  transform: translateX(4px);
  background: var(--surface-3);
}

body.review-page .review-game-item.is-active {
  border-color: var(--gold);
  box-shadow: var(--elev-1), 0 0 0 1px var(--gold-glow);
  background: var(--gold-soft);
}

body.review-page .review-game-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--gold), var(--gold-bright));
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: var(--weight-bold);
  color: var(--on-gold);
  font-size: var(--text-sm);
}

body.review-page .review-game-info {
  min-width: 0;
}

body.review-page .review-game-players {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--cream);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

body.review-page .review-game-meta {
  font-size: var(--text-xs);
  color: var(--muted);
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

body.review-page .review-game-result {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
}

body.review-page .review-game-result.win {
  background: color-mix(in srgb, var(--data-win) 15%, transparent);
  color: var(--data-win);
  border: 1px solid color-mix(in srgb, var(--data-win) 30%, transparent);
}

body.review-page .review-game-result.loss {
  background: color-mix(in srgb, var(--data-loss) 15%, transparent);
  color: var(--data-loss);
  border: 1px solid color-mix(in srgb, var(--data-loss) 30%, transparent);
}

body.review-page .review-game-result.draw {
  background: color-mix(in srgb, var(--data-draw) 15%, transparent);
  color: var(--data-draw);
  border: 1px solid color-mix(in srgb, var(--data-draw) 30%, transparent);
}

body.review-page .review-game-opening {
  font-size: var(--text-xs);
  color: var(--cream-dim);
  font-style: italic;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Transport controls — tactile buttons */
body.review-page .review-board-controls {
  display: flex;
  gap: 8px;
  padding: 12px;
  background: var(--surface-panel);
  border-top: 1px solid var(--line);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

body.review-page .review-ctl {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 44px;
  height: 40px;
  padding: 0 14px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--cream-dim);
  font-family: var(--font);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: all var(--transition-fast);
}

body.review-page .review-ctl:hover:not(:disabled) {
  background: var(--hover-fill);
  border-color: var(--line-strong);
  color: var(--cream);
  transform: translateY(-1px);
}

body.review-page .review-ctl:active:not(:disabled) {
  transform: translateY(1px) scale(0.98);
}

body.review-page .review-ctl:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

body.review-page .review-ctl-primary {
  background: linear-gradient(180deg, var(--gold-bright), var(--gold));
  border-color: color-mix(in srgb, var(--gold) 46%, transparent);
  color: var(--on-gold);
  box-shadow: 
    inset 0 1px 0 rgba(255,255,255,0.22),
    0 4px 14px color-mix(in srgb, var(--gold) 26%, transparent);
}

body.review-page .review-ctl-primary:hover:not(:disabled) {
  background: linear-gradient(180deg, color-mix(in srgb, var(--gold-bright) 88%, white), var(--gold-bright));
  border-color: color-mix(in srgb, var(--gold) 70%, transparent);
  transform: translateY(-2px);
  box-shadow: 
    inset 0 1px 0 rgba(255,255,255,0.22),
    0 6px 20px color-mix(in srgb, var(--gold) 36%, transparent);
}

body.review-page .review-ctl-primary:active:not(:disabled) {
  transform: translateY(1px) scale(0.98);
}

body.review-page .review-ctl-divider {
  width: 1px;
  height: 24px;
  background: var(--line);
  margin: 0 4px;
}

/* Entrance animations for page sections */
body.review-page .review-board-col {
  animation: om-slide-up var(--motion-slow) var(--motion-spring) backwards;
}

body.review-page .review-games-rail .review-game-item:nth-child(n) {
  animation-delay: calc(var(--stagger-base) * var(--item-index, 0));
}

body.review-page .review-side-panel {
  animation: om-fade-in var(--motion-base) var(--motion-ease-out) backwards;
}

body.review-page .review-side-panel:nth-child(1) { animation-delay: calc(var(--stagger-base) * 0); }
body.review-page .review-side-panel:nth-child(2) { animation-delay: calc(var(--stagger-base) * 1); }
body.review-page .review-side-panel:nth-child(3) { animation-delay: calc(var(--stagger-base) * 2); }

/* Focus visible — branded focus rings */
body.review-page *:focus-visible {
  outline: 2px solid var(--gold-bright);
  outline-offset: 2px;
}

/* Scrollbar styling */
body.review-page ::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

body.review-page ::-webkit-scrollbar-track {
  background: var(--surface-2);
  border-radius: var(--radius-pill);
}

body.review-page ::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: var(--radius-pill);
  border: 2px solid var(--surface-2);
}

body.review-page ::-webkit-scrollbar-thumb:hover {
  background: var(--gold);
}

/* Board Synchronization — Coach highlights & ghost arrows */
/* Square highlights for coach note sync */
body.review-page .square.coach-highlight {
  box-shadow: inset 0 0 0 3px var(--gold), 0 0 12px var(--gold-glow);
  z-index: 5;
}

body.review-page .square.coach-highlight-from {
  box-shadow: inset 0 0 0 3px var(--gold), 0 0 12px var(--gold-glow);
  background: linear-gradient(135deg, var(--gold-soft), transparent);
}

body.review-page .square.coach-highlight-to {
  box-shadow: inset 0 0 0 3px var(--gold), 0 0 12px var(--gold-glow);
  background: linear-gradient(225deg, var(--gold-soft), transparent);
}

/* Ghost arrows for alternative moves */
body.review-page .board-arrow-fill.arrow-ghost {
  stroke: var(--gold);
  stroke-width: 0.02;
  fill: var(--gold-soft);
  opacity: 0.6;
  stroke-dasharray: 0.08 0.04;
  filter: drop-shadow(0 0 4px var(--gold-glow));
}

body.review-page .board-arrow-fill.arrow-ghost:hover {
  opacity: 1;
  stroke-width: 0.03;
  filter: drop-shadow(0 0 8px var(--gold-glow));
}

/* Highlighted squares for coach sync */
body.review-page .square.coach-highlight {
  transition: box-shadow var(--transition-fast), background var(--transition-fast);
}

/* Pulse animation for active highlight */
@keyframes coach-pulse {
  0%, 100% { box-shadow: inset 0 0 0 3px var(--gold), 0 0 12px var(--gold-glow); }
  50% { box-shadow: inset 0 0 0 3px var(--gold), 0 0 24px var(--gold-glow-strong); }
}

body.review-page .square.coach-highlight-active {
  animation: coach-pulse 1.5s ease-in-out infinite;
}

/* Clickable tags in coach detail */
body.review-page .review-coach-tag-clickable {
  cursor: pointer;
  position: relative;
}

body.review-page .review-coach-tag-clickable::after {
  content: " 👁";
  font-size: 0.7em;
  opacity: 0.6;
  margin-left: 4px;
}

body.review-page .review-coach-tag-clickable:hover::after {
  opacity: 1;
}

body.review-page .review-coach-tag-clickable:hover {
  transform: translateY(-1px);
  box-shadow: 0 2px 8px var(--gold-glow);
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  body.review-page *,
  body.review-page *::before,
  body.review-page *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ——— "AI startup" panel treatment ———
   The side panels already had a glassmorphism section, but it leaned on the
   weak --elev-1/2 shadows while the rest of the site (blog, puzzles, mistakes,
   tracking, training) uses the richer --panel-glow / --panel-glow-hover stacks
   the theme defines. And the games rail — the leftmost column — carried no
   premium surface at all. This brings the review panels up to the site's
   premium standard and gives them a modern-dashboard character:
     · a cool indigo/teal "aurora" wash over the existing surface, so the
       panels read as a contemporary AI product without a second palette;
     · the theme's premium lift shadow in place of the thin elevation;
     · a bright hairline along the top edge — the classic glass-card top-light
       that makes a flat panel feel lit from above.
   Everything is additive and scoped to body.review-page, uses theme variables
   so light and dark both work, and leaves the outer .review-workspace flat
   (its borderless/shadowless rule is test-pinned) and the board square. The
   aurora sits UNDER the content (::before, behind the card's text) and the
   top-light is a ::after, so neither intercepts clicks.

   Scoped to .review-side-panel alone — the outer card. .review-report is the
   plain content wrapper nested inside every Report/Insights panel and
   .review-coach is the gold-edged callout in the Analysis tab; giving either
   the full card skin stacked a second raised card inside the first, which is
   the "double panel" this replaces. There is deliberately no `overflow:
   hidden`: on desktop the rail (.review-side) is the scroll container and the
   panel is its flex child, so clipping the panel trapped Insights' overflow
   and made the tab unscrollable. The aurora is instead rounded to the card
   with `border-radius: inherit`, which needs no clip. */
body.review-page .review-side-panel {
  position: relative;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--gold) 8%, transparent) 0%,
      transparent 55%),
    var(--surface-panel);
  border: 1px solid color-mix(in srgb, var(--gold) 16%, var(--glass-border));
  box-shadow: var(--panel-glow);
}

/* The aurora wash: two soft cool blooms (indigo + teal) in the top corners,
   over the brand's green already added above. Kept low-alpha so it tints the
   surface rather than repainting it, and behind the content. */
body.review-page .review-side-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background:
    radial-gradient(120% 90% at 0% 0%,
      color-mix(in srgb, #6366f1 12%, transparent) 0%,
      transparent 60%),
    radial-gradient(120% 90% at 100% 0%,
      color-mix(in srgb, #14b8a6 9%, transparent) 0%,
      transparent 55%);
  z-index: 0;
}

/* The glass top-light: a 1px bright edge along the top, brightest in the
   centre where a raised surface would catch the room's light. */
body.review-page .review-side-panel::after {
  content: "";
  position: absolute;
  top: 0;
  left: 12px;
  right: 12px;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent,
    color-mix(in srgb, var(--gold) 55%, transparent),
    transparent);
  z-index: 1;
}

/* Keep the real content above the aurora/top-light layers. */
body.review-page .review-side-panel > * {
  position: relative;
  z-index: 2;
}

body.review-page .review-side-panel:hover {
  border-color: color-mix(in srgb, var(--gold) 32%, var(--glass-border));
  box-shadow: var(--panel-glow-hover);
}

/* The games rail finally gets the same glass treatment it never had, so the
   leftmost column matches the others. Its own gradient surface is folded in
   under the panel glow rather than replaced. */
body.review-page .review-games-rail {
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--gold) 5%, transparent), transparent 180px),
    radial-gradient(120% 90% at 0% 0%, color-mix(in srgb, #6366f1 10%, transparent), transparent 60%),
    color-mix(in srgb, var(--bg-2) 45%, transparent);
  border: 1px solid color-mix(in srgb, var(--gold) 14%, var(--glass-border));
  box-shadow: var(--panel-glow);
  position: relative;
}

body.review-page .review-games-rail::after {
  content: "";
  position: absolute;
  top: 0;
  left: 14px;
  right: 14px;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent,
    color-mix(in srgb, var(--gold) 45%, transparent),
    transparent);
}

body.review-page .review-games-rail:hover {
  border-color: color-mix(in srgb, var(--gold) 28%, var(--glass-border));
  box-shadow: var(--panel-glow-hover);
}