/* The Cyvasse board at /play (games/show). Linked from that view only.
 *
 * The board keeps the legacy match screen's look in both themes: black hexes
 * with white edges, and a soft orange glow (.hex-glow) that pulses on the
 * selected unit and fades away over ten seconds on the last move. Each
 * unit's hex is shaded blue (yours) or red (the computer's) from the edge in,
 * in place of the legacy ring outlines. The ring ripple paints hex fills
 * inline from the controller (gradients it defines in the board's <defs>);
 * everything static lives here. */

.cyvasse-layout {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 1024px) {
  .cyvasse-layout {
    grid-template-columns: minmax(0, 1fr) 18rem;
    align-items: start;
  }
}

.cyvasse-board-wrap {
  position: relative;
  min-width: 0;
  /* The banner slides in from the left and out to the right of the board.
   * Clip it at the board's edges, or on a phone its slide-out pushes the page
   * wider than the screen and the browser scrolls sideways or zooms out. */
  overflow-x: clip;
}

.cyvasse-board {
  display: block;
  width: 100%;
  height: auto;
  max-width: 44rem;
  margin: 0 auto;
  user-select: none;
  -webkit-user-select: none;
}

/* A hex's resting fill is a gradient the controller sets as its fill
 * attribute (paintGround): slate board, dark setup rows, lit drop targets.
 * Every rule below that sets a fill draws over it. */
.cyvasse-board .hex-poly {
  stroke: white;
  stroke-width: 1.5;
  transition: fill 0.25s, stroke 0.25s;
}

.cyvasse-board .hex {
  cursor: default;
}

.cyvasse-board .hex:hover .hex-poly {
  stroke: yellow;
}

/* Setup: your five rows are near-black and hatched (the texture rule below),
 * darker than the slate board; a picked unit lights the empty ones (.is-drop)
 * with a brighter edge. */
.cyvasse-board .is-deploy .hex-poly {
  cursor: pointer;
}

.cyvasse-board .hex.is-drop:not(:focus-visible):not(:hover) .hex-poly {
  stroke: hsl(226 90% 76%);
}

/* The last move (the hex a unit left and the hex it reached): a soft orange
 * glow over the ground and the unit's team shade, fading to nothing over ten
 * seconds. The controller clears .is-last-move when the ten seconds are up
 * (cyvasse/last_move LAST_MOVE_MS) and sets --last-move-delay negative on a
 * redraw partway through, so the fade carries on rather than restarting. The
 * glow draws no edge (cyvasse/edges), so it never rings a hex. */
.cyvasse-board .hex-glow {
  display: none;
  pointer-events: none;
}

/* The selected unit: the same glow, breathing slowly for as long as it is
 * picked (opacity alone, so nothing moves or spills onto a neighbour). A
 * selection clears the last-move marks, so the two never share a hex. It also
 * lights a placed unit picked up again in setup, over the dark rows. */
.cyvasse-board .hex.is-selected .hex-glow {
  display: inline;
  animation: cyvasse-selected-pulse 1.8s ease-in-out infinite;
}

@keyframes cyvasse-selected-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.65; }
}

.cyvasse-board .is-last-move .hex-glow {
  display: inline;
  animation: cyvasse-last-move 10s ease-in forwards;
  animation-delay: var(--last-move-delay, 0ms);
}

@keyframes cyvasse-last-move {
  0% { opacity: 1; }
  25% { opacity: 0.9; }
  100% { opacity: 0; }
}

/* Less motion: a steady, fainter last-move glow, cleared at ten seconds all
 * the same; a steady selection glow. */
@media (prefers-reduced-motion: reduce) {
  .cyvasse-board .is-last-move .hex-glow {
    animation: none;
    opacity: 0.6;
  }

  .cyvasse-board .hex.is-selected .hex-glow {
    animation: none;
    opacity: 1;
  }
}

/* Keyboard play: the focused hex gets a thick yellow edge. */
.cyvasse-board .hex:focus {
  outline: none;
}

.cyvasse-board .hex:focus-visible .hex-poly {
  stroke: yellow;
  stroke-width: 4;
}

.cyvasse-board .is-move,
.cyvasse-board .is-attack,
.cyvasse-board .has-unit {
  cursor: pointer;
}

.cyvasse-board .unit-disc {
  fill: none;
  stroke: none;
  pointer-events: none;
}

/* The pencil skin is black linework on a transparent ground; a parchment disc
 * keeps it readable on the black board.
 *
 * The disc covers most of its hex, and with it the hex's team shade (blue
 * yours, red theirs, .unit-shade): so the disc draws its own thin rim in its
 * team's colour (task cyvasse-pencil-team-rim). The colours are the shade's
 * own (cyvasse_game_controller TEAM_SHADE; keep the two in step). The rim is
 * the disc's own edge, so nothing drawn over the hex (the last move's and the
 * selection's orange ring outside it, the danger pulse from the hex's edges
 * in) ever covers it. */
:root {
  --cyvasse-team-1: #3b82f6;
  --cyvasse-team-0: #dc2626;
}

[data-skin="pencil"] .cyvasse-board .has-unit .unit-disc {
  fill: #f7f4ec;
  stroke-width: 3;
}

[data-skin="pencil"] .cyvasse-board .has-unit[data-team="1"] .unit-disc { stroke: var(--cyvasse-team-1); }
[data-skin="pencil"] .cyvasse-board .has-unit[data-team="0"] .unit-disc { stroke: var(--cyvasse-team-0); }

/* The pencil skin's glow (task cyvasse-pencil-glow-visibility). Its parchment
 * disc covers most of the hex, so the hex glow above showed only as faint
 * orange corners, grey-brown over a blue team's rim. On a pencil piece the
 * glow also rings the disc: a warm orange ring with a soft halo, just outside
 * the disc's team rim (.unit-ring, a wide stroke under the disc, so only its
 * outer part shows), inside the piece's art clip (so it is cut where the disc
 * is) and under the art's linework. --rim-glow is the ring's strength, 0 to
 * 1: it fades with the last move over the same ten seconds and on the same
 * --last-move-delay clock, and breathes with the selection's pulse, never
 * below 0.65. The vector skin has no disc and keeps the hex glow alone. */
@property --rim-glow {
  syntax: "<number>";
  inherits: false;
  initial-value: 1;
}

.cyvasse-board .unit-ring {
  display: none;
  fill: none;
}

[data-skin="pencil"] .cyvasse-board .has-unit:is(.is-last-move, .is-selected) .unit-ring {
  display: inline;
  stroke: #ff9a1f;
  stroke-width: 10;
  stroke-opacity: var(--rim-glow);
  filter: drop-shadow(0 0 2.5px rgb(255 140 26 / calc(0.95 * var(--rim-glow))));
}

[data-skin="pencil"] .cyvasse-board .has-unit.is-selected .unit-ring {
  animation: cyvasse-selected-rim 1.8s ease-in-out infinite;
}

[data-skin="pencil"] .cyvasse-board .has-unit.is-last-move:not(.is-selected) .unit-ring {
  animation: cyvasse-last-move-rim 10s ease-in forwards;
  animation-delay: var(--last-move-delay, 0ms);
}

@keyframes cyvasse-selected-rim {
  0%, 100% { --rim-glow: 1; }
  50% { --rim-glow: 0.65; }
}

@keyframes cyvasse-last-move-rim {
  0% { --rim-glow: 1; }
  25% { --rim-glow: 0.9; }
  100% { --rim-glow: 0; }
}

@media (prefers-reduced-motion: reduce) {
  [data-skin="pencil"] .cyvasse-board .has-unit.is-last-move:not(.is-selected) .unit-ring {
    animation: none;
    --rim-glow: 0.6;
  }

  [data-skin="pencil"] .cyvasse-board .has-unit.is-selected .unit-ring {
    animation: none;
    --rim-glow: 1;
  }
}

/* The art fills about four fifths of its hex (the box is 56 x 60 around the
 * centre; the vector art carries its own margin). The art never takes a
 * click: the hex polygon under it is the whole hit target, whatever the art's
 * size, and wherever a neighbour's art reaches over it.
 *
 * A piece stands on its tile (task cyvasse-piece-art-overlap): the controller
 * clips its art along the hex's right, lower-right and lower-left sides and
 * lets it rise over the hexes behind it, up and to the left (cyvasse/art_clip,
 * #hex-art-clip). So the art grows about a point low in its hex, --art-anchor
 * below the centre, where the figures stand: a larger piece rises, rather than
 * pushing its feet into the cut along the bottom.
 *
 * Size tiers (units.js sizeTier, set on the hex as data-tier): small pieces
 * (rabble, spearman, crossbowman) draw 1.1 times the art's own size, medium
 * ones (king, light and heavy horse) 1.2 times, large ones (trebuchet,
 * catapult, elephant, dragon, mountain) 1.35 times. No art is capped: the
 * clip that once trimmed a wide drawing at every side now frees the upper
 * ones, and the cut along the bottom is the tile's own edge. */
.cyvasse-board .unit-image,
.cyvasse-board .unit-disc,
.cyvasse-board .unit-ring {
  pointer-events: none;
  --tier-scale: 1.1;
  --art-anchor: 14px;
  --art-scale: var(--tier-scale);
  transform: translateY(var(--art-anchor)) scale(var(--art-scale)) translateY(calc(-1 * var(--art-anchor)));
}

.cyvasse-board .hex[data-tier="medium"] :is(.unit-image, .unit-disc, .unit-ring) { --tier-scale: 1.2; }
.cyvasse-board .hex[data-tier="large"] :is(.unit-image, .unit-disc, .unit-ring) { --tier-scale: 1.35; }

/* The pencil skin draws its piece as a token: black linework on a parchment
 * disc (radius 27), the art at 0.84 of the disc, the disc at 0.83 of the art
 * box. Disc and art grow together, by the same tiers, and take the same clip:
 * a large token rises over the hexes behind it and is cut flat along its
 * tile's right and lower sides. */
[data-skin="pencil"] .cyvasse-board :is(.unit-disc, .unit-ring) { --art-scale: calc(0.83 * var(--tier-scale)); }
[data-skin="pencil"] .cyvasse-board .unit-image { --art-scale: calc(0.84 * 0.83 * var(--tier-scale)); }

/* A hex the ripple has lit (.is-lit) carries a faint diagonal hatch over its
 * gradient, for texture. It sits under the unit shade and the art, and never
 * on the selected hex, so the orange stays a clean flat colour. */
.cyvasse-board .ring-texture {
  display: none;
  pointer-events: none;
}

.cyvasse-board .is-lit:not(.is-selected) .ring-texture,
.cyvasse-board .is-deploy:not(.is-selected) .ring-texture {
  display: inline;
}

/* The second-jump preview and the shooter's range (fills built in the
 * controller). A ghost takes a dashed edge in the colour of the live ring it
 * previews; the range field a thin crimson edge; a target a heavy bright red
 * one; a blocked hex a dashed slate edge. Each yields to the keyboard's
 * yellow focus edge. */
.cyvasse-board .hex.is-ghost:not(:focus-visible) .hex-poly {
  stroke-width: 1.75;
  stroke-dasharray: 5 3;
}

.cyvasse-board .hex.is-ghost[data-ghost="6"]:not(:focus-visible) .hex-poly {
  stroke: hsl(226 95% 70%);
}

.cyvasse-board .hex.is-ghost[data-ghost="7"]:not(:focus-visible) .hex-poly {
  stroke: hsl(0 90% 66%);
}

.cyvasse-board .hex.is-ghost[data-ghost="8"]:not(:focus-visible) .hex-poly {
  stroke: hsl(285 75% 72%);
}

.cyvasse-board .hex.is-field:not(:focus-visible) .hex-poly {
  stroke: hsl(354 60% 44%);
}

.cyvasse-board .hex.is-target:not(:focus-visible) .hex-poly {
  stroke: hsl(0 100% 66%);
  stroke-width: 2;
}

/* A hex the picked unit cannot stop on: sunken, a gray dish with a dim edge,
 * no hatch and no hue, so it never reads as a move, a capture or a threat. */
.cyvasse-board .hex.is-sunken:not(:focus-visible) .hex-poly {
  stroke: hsl(220 4% 34%);
}

.cyvasse-board .hex.is-blocked:not(:focus-visible) .hex-poly {
  stroke: hsl(214 16% 58%);
  stroke-dasharray: 3 3;
}

/* A unit's hex carries a faint rim of its team's colour from the edge in, the
 * same for every piece (one gradient per team, built in the controller). */
.cyvasse-board .unit-shade {
  display: none;
  pointer-events: none;
}

.cyvasse-board .has-unit .unit-shade {
  display: inline;
}

/* The shade would mute the orange of the selected unit and the red of a hex
 * under attack: those hexes let the cue through. The last move's glow sits
 * over the shade instead, so a moved unit keeps its team's rim. */
.cyvasse-board .is-selected .unit-shade,
.cyvasse-board .is-attack .unit-shade {
  opacity: 0.2;
}

/* Highlight borders (renderEdges, cyvasse/edges): one line per edge on the
 * line two hexes share, wide enough to cover both hexes' own edges, in the
 * colour of the highest highlight on either side. Highest first:
 *   selection > move rings > danger > threat perimeter (solid over dashed)
 * A plain move ring ("ring") draws nothing: its hexes keep their thin edges
 * and nothing beneath crosses them. The last move draws no edge: it is the
 * glow above, and its unit's team shade says whose it was. */
.cyvasse-board .hex-edge {
  display: none;
  fill: none;
  stroke-width: 3.5;
  stroke-linecap: round;
  pointer-events: none;
}

.cyvasse-board .hex-edge[data-kind]:not([data-kind="ring"]) { display: inline; }
.cyvasse-board .hex-edge[data-kind="selected"] { stroke: orange; }
.cyvasse-board .hex-edge[data-kind="target"] { stroke: hsl(0 100% 66%); stroke-width: 4.5; }
.cyvasse-board .hex-edge[data-kind="field"] { stroke: hsl(354 60% 44%); }
.cyvasse-board .hex-edge[data-kind^="ghost-"],
.cyvasse-board .hex-edge[data-kind="blocked"] { stroke-linecap: butt; stroke-dasharray: 5 3; }
.cyvasse-board .hex-edge[data-kind="ghost-6"] { stroke: hsl(226 95% 70%); }
.cyvasse-board .hex-edge[data-kind="ghost-7"] { stroke: hsl(0 90% 66%); }
.cyvasse-board .hex-edge[data-kind="ghost-8"] { stroke: hsl(285 75% 72%); }
.cyvasse-board .hex-edge[data-kind="blocked"] { stroke: hsl(214 16% 58%); stroke-dasharray: 3 3; }
.cyvasse-board .hex-edge[data-kind="perimeter"] { stroke: #ef4444; }
.cyvasse-board .hex-edge[data-kind="perimeter-ranged"] { stroke: #ef4444; stroke-linecap: butt; stroke-dasharray: 5 4; }

.cyvasse-board .hex-edge[data-kind="danger"] {
  stroke: #f97316;
  animation: cyvasse-danger 1.8s ease-in-out infinite;
}

/* Hover and the keyboard's focus edge, over every border (moveCursor). */
.cyvasse-board .hex-cursor {
  display: none;
  fill: none;
  stroke: yellow;
  stroke-width: 1.5;
  pointer-events: none;
}

.cyvasse-board .hex-cursor.is-on { display: inline; }
.cyvasse-board .hex-cursor.is-focus { stroke-width: 4; }

/* The rotator banner: slides in from the left, out to the right, in its
 * own slot above the board. It never sits over the board, where it hid the
 * computer's selected unit and its move (UX audit #2). The slot keeps its
 * height while the banner is hidden, so the board never jumps; a line that
 * wraps on a phone grows upward, away from the board. */
.cyvasse-banner-slot {
  position: relative;
  min-height: 2.75rem;
  margin-bottom: 0.25rem;
}

.cyvasse-banner {
  position: absolute;
  bottom: 0;
  left: 50%;
  padding: 0.5rem 1.25rem;
  border-radius: 0.5rem;
  background: rgb(0 0 0 / 0.8);
  border: 1px solid rgb(255 255 255 / 0.6);
  color: white;
  font-weight: 700;
  font-size: 1rem;
  line-height: 1.5rem;
  /* One line where it fits; a long line ("Opponent passes · Turn 6 · …")
   * wraps on a phone rather than running past the board. */
  width: max-content;
  max-width: calc(100% - 1rem);
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transform: translateX(-150%);
  transition: transform 0.6s ease, opacity 0.6s ease;
}

.cyvasse-banner.is-showing {
  opacity: 1;
  transform: translateX(-50%);
}

.cyvasse-banner.is-leaving {
  opacity: 0;
  transform: translateX(50%);
}

@media (prefers-reduced-motion: reduce) {
  .cyvasse-banner,
  .cyvasse-board .hex-poly {
    transition: none;
  }
}

/* The units still to place, on see-through tiles; a placed unit leaves a
 * faint dashed slot (.dock-slot) so the grid never changes size. */
.cyvasse-dock {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(3.5rem, 1fr));
  gap: 0.25rem;
}

.dock-unit,
.dock-slot {
  aspect-ratio: 1;
  border-radius: 0.5rem;
}

.dock-unit {
  display: grid;
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  padding: 0.125rem;
  border: 1px solid transparent;
  background: transparent;
  cursor: pointer;
  transition: background-color 120ms, border-color 120ms;
}

/* A phone has no hover: scoped, so a tapped tile does not keep the tint. */
@media (hover: hover) {
  .dock-unit:hover {
    background-color: rgb(139 92 246 / 0.12);
    border-color: rgb(139 92 246 / 0.45);
  }
}

.dock-unit:focus-visible {
  background-color: rgb(139 92 246 / 0.12);
  border-color: rgb(139 92 246 / 0.45);
}

.dock-unit[aria-pressed="true"] {
  background-color: rgb(255 165 0 / 0.14);
  border-color: orange;
  box-shadow: 0 0 0 1px orange;
}

.dock-slot {
  border: 1px dashed rgb(139 92 246 / 0.25);
}

/* Both skins draw in dark ink (black horses, iron, pencil linework), which
 * sinks into the dark card. In the dark theme the tile stays see-through,
 * with no opaque fill: a soft light glow sits behind the art only, and the
 * art carries a light rim. The pencil art, all linework, takes a firmer rim.
 * The light theme keeps the bare tile. */
html.dark .dock-unit {
  background-image: radial-gradient(closest-side, rgb(255 255 255 / 0.2), rgb(255 255 255 / 0.07) 65%, rgb(255 255 255 / 0));
}

html.dark .dock-unit img {
  filter: drop-shadow(0 0 1px rgb(255 255 255 / 0.45)) drop-shadow(0 0 3px rgb(255 255 255 / 0.2));
}

html.dark [data-skin="pencil"] .dock-unit img {
  filter: drop-shadow(0 0 1px rgb(255 255 255 / 0.6)) drop-shadow(0 0 4px rgb(255 255 255 / 0.25));
}

.dock-unit img,
.cyvasse-info-art img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.cyvasse-info-art {
  width: 3.5rem;
  height: 3.5rem;
  flex: none;
  display: grid;
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  padding: 0.25rem;
  border-radius: 0.5rem;
  background: #f7f4ec;
}

.cyvasse-stats {
  display: grid;
  gap: 0.25rem;
}

.cyvasse-stats div {
  display: flex;
  justify-content: space-between;
  font-size: 0.875rem;
}

.cyvasse-stats dt {
  color: var(--color-text-muted, inherit);
}

.cyvasse-stats dd {
  font-weight: 600;
}

.cyvasse-trump img,
.cyvasse-graveyard img {
  display: inline-block;
  width: 1.75rem;
  height: 1.75rem;
  object-fit: contain;
  vertical-align: middle;
  border-radius: 0.25rem;
  background: #f7f4ec;
  margin: 0 0.125rem 0.125rem 0;
}

.cyvasse-graveyard {
  min-height: 1.75rem;
}

/* Online matches (matches/show): a refused turn or setup, in the engine's
 * danger tone (a hex token, so mixed rather than given an alpha). */
.cyvasse-alert {
  padding: 0.75rem 1rem;
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--color-danger, #EF4444) 45%, transparent);
  background: color-mix(in srgb, var(--color-danger, #EF4444) 12%, transparent);
  color: var(--color-danger-ink, var(--color-danger, #EF4444));
}

/* ---- Live matches (LiveMatch, cyvasse_match_controller renderClock) ---- */

/* The clock both players see: a bar that empties, red for the last seconds,
 * and a shimmer while a computer player "thinks". */
.live-clock {
  max-width: 26rem;
}

.live-clock-track {
  height: 6px;
  margin-top: 0.35rem;
  border-radius: 9999px;
  background: color-mix(in srgb, var(--color-cta, #8E82FE) 18%, transparent);
  overflow: hidden;
}

.live-clock-track span {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: 9999px;
  background: var(--color-cta, #8E82FE);
  transition: width 0.2s linear, background-color 0.3s;
}

.live-clock-seconds {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--color-text-heading, inherit);
}

.live-clock.is-warning .live-clock-track span {
  background: #ef4444;
}

.live-clock.is-warning .live-clock-seconds,
.live-clock.is-warning [data-cyvasse-match-target="clockLabel"] {
  color: #f87171;
}

.live-clock.is-thinking .live-clock-track span {
  background: linear-gradient(90deg, transparent, var(--color-cta, #8E82FE), transparent);
  background-size: 50% 100%;
  background-repeat: no-repeat;
  animation: live-clock-think 1.2s ease-in-out infinite;
}

@keyframes live-clock-think {
  from { background-position: -50% 0; }
  to { background-position: 150% 0; }
}

.live-notice {
  padding: 0.6rem 0.9rem;
  border-radius: 0.5rem;
  font-size: 0.9rem;
  border: 1px solid color-mix(in srgb, var(--color-cta, #8E82FE) 40%, transparent);
  background: color-mix(in srgb, var(--color-cta, #8E82FE) 12%, transparent);
  color: var(--color-text-heading, inherit);
}

.live-notice:not([hidden]) {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
}

.live-notice > span {
  flex: 1;
}

.live-notice-dismiss {
  flex: none;
  line-height: 1;
  font-size: 1.1rem;
  padding: 0 0.2rem;
  opacity: 0.7;
  cursor: pointer;
}

.live-notice-dismiss:hover,
.live-notice-dismiss:focus-visible {
  opacity: 1;
}

/* The match page (matches/show): the board (about two thirds) beside a
 * sidebar of the versus card over the panel. A phone stacks versus, board,
 * then panel. */
.match-layout {
  grid-template-areas: "versus" "board" "panel";
}

.match-layout > .match-versus-card { grid-area: versus; }
.match-layout > .cyvasse-board-wrap { grid-area: board; }
.match-layout > .match-panel { grid-area: panel; }

@media (min-width: 1024px) {
  .match-layout {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    grid-template-areas: "board versus" "board panel";
    row-gap: 1rem;
  }

  /* The board sits high, its top near the versus card's (task
   * cyvasse-sidebar-reorder). The banner keeps its own row above the board,
   * never over it; the start-over strip, which on a match holds only the
   * hint, shares that row instead of adding one: the hint at the left, the
   * banner centred and drawn over it (it takes no clicks), and the pair
   * lifted into the page's top padding (main's py-6). */
  .match-layout > .cyvasse-board-wrap {
    margin-top: -1.5rem;
  }

  .match-layout .cyvasse-board-bar {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2.75rem; /* the banner slot's height */
    margin: 0;
  }

  /* Drawn over the hint's row, so it must let clicks through to the hint. */
  .match-layout .cyvasse-banner-slot {
    z-index: 1;
    pointer-events: none;
  }
}

.match-panel .live-clock {
  max-width: none;
}

.match-panel-status > [hidden] {
  display: none;
}

/* The timer card holds only what is on: with no clock, switches, deadline
 * or notice (a correspondence match in setup), it is not drawn at all. */
.match-panel > .match-panel-status:not(:has(> :not([hidden]))) {
  display: none;
}

.match-panel-status .cyvasse-threat-toggles {
  margin-left: 0;
  font-size: 0.875rem;
}

/* The status line, the board's one live region, sits under the army or unit
 * card. While the army card is up it shows the line's copy (armyStatus), so
 * the line is read, not shown: it never moves, so nothing is announced twice
 * or lost (task cyvasse-sidebar-reorder). */
.match-panel:has(> .cyvasse-setup-controls:not([hidden]) .cyvasse-army-status) > .match-status-line {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.match-actions {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.match-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
}

/* One forfeit shows at a time, by the status the board keeps current. It
 * ends the game, so it sits apart from the links, below a rule. */
.match-forfeit {
  display: none;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-border);
}

[data-match-status="pending"] .match-forfeit[data-forfeit="pregame"],
[data-match-status="new"] .match-forfeit[data-forfeit="pregame"],
[data-match-status="in progress"] .match-forfeit[data-forfeit="play"] {
  display: flex;
}

/* A danger outline, never the green fill of a Play button. The ink is the
 * engine's contrast-safe text red (--color-danger-ink), not the fill red. */
.match-forfeit-button {
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--color-danger, #EF4444) 60%, transparent);
  color: var(--color-danger-ink, var(--color-danger, #EF4444));
  font-weight: 600;
}

.match-forfeit-button:hover,
.match-forfeit-button:focus-visible {
  background: color-mix(in srgb, var(--color-danger, #EF4444) 12%, transparent);
}

/* The versus card (matches/_versus): you over them, each an avatar beside a
 * name, a quiet "vs" rule between. A long name wraps only at a space and
 * shrinks to fit rather than break mid-word (.player-name, name-fit). */
.match-versus-card {
  padding: 0.875rem 1rem;
}

.match-versus {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0;
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

.match-versus-side {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}

.match-versus-who {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-width: 0;
}

.match-versus-name {
  flex: 0 1 auto;
  min-width: 0;
}

.match-versus-vs {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding-left: 3.5rem;
  font-size: 0.7rem;
  font-weight: 500;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.12em;
}

.match-versus-vs::after {
  content: "";
  flex: 1;
  border-top: 1px solid currentColor;
  opacity: 0.35;
}

/* An army the clock placed arrives one piece at a time. The fade applies only
 * while it arrives (.is-arrival on the board), so other shade changes stay
 * instant. */
.cyvasse-board.is-arrival .hex .unit-image,
.cyvasse-board.is-arrival .hex .unit-shade {
  transition: opacity 0.25s ease-out;
}

.cyvasse-board .hex.is-arriving .unit-image,
.cyvasse-board .hex.is-arriving .unit-shade {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .live-clock.is-thinking .live-clock-track span { animation: none; background: var(--color-cta, #8E82FE); }
  .live-clock-track span,
  .cyvasse-board.is-arrival .hex .unit-image,
  .cyvasse-board.is-arrival .hex .unit-shade { transition: none; }
}

/* The "Your army" card (games/_army_card). One grid: the heading, a
 * match's status (two lines kept, so a longer line never pushes what is
 * under it), Smart Setup beside Ready, then the dock (task
 * cyvasse-sidebar-reorder: Ready leads the card rather than trailing the
 * dock). Every slot keeps its size and Smart Setup keeps one place in every
 * mode, so no state change moves Ready, the dock or anything under the
 * card. With nothing placed Smart Setup is filled; after that it is hollow
 * and reads Place All, then New Setup. Ready stays muted until the army is
 * placed. The status says the count, so the count shows only in a docked
 * sheet. */
.cyvasse-army {
  --army-slot: 2.75rem;
  display: grid;
  gap: 0.75rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: auto auto var(--army-slot) auto;
  grid-template-areas: "heading heading" "status status" "smart ready" "dock dock";
}

.cyvasse-army:not(:has(.cyvasse-army-status)) {
  grid-template-rows: auto var(--army-slot) auto;
  grid-template-areas: "heading heading" "smart ready" "dock dock";
}

.cyvasse-army > h2 { grid-area: heading; }
.cyvasse-army .cyvasse-dock { grid-area: dock; }
.cyvasse-army .cyvasse-ready { grid-area: ready; }
.cyvasse-army .cyvasse-smart { grid-area: smart; }

.cyvasse-army-status {
  grid-area: status;
  min-height: 2lh;
  margin: 0;
  text-align: center;
}

.cyvasse-army .btn {
  width: 100%;
  height: var(--army-slot);
  padding-inline: 0.5rem;
  white-space: nowrap;
}

.cyvasse-army-count {
  display: none;
  margin: 0;
}

.cyvasse-ready,
.cyvasse-army[data-army-mode="smart"] .cyvasse-smart {
  color: #fff;
  background-color: #7c3aed;
  border: 1px solid transparent;
  box-shadow: 0 0 0 1px rgb(167 139 250 / 0.5), 0 6px 18px -6px rgb(124 58 237 / 0.7);
}

@media (hover: hover) {
  .cyvasse-ready:hover:not(:disabled),
  .cyvasse-army[data-army-mode="smart"] .cyvasse-smart:hover {
    background-color: #6d28d9;
  }
}

.cyvasse-ready:disabled {
  opacity: 0.4;
  box-shadow: none;
  filter: saturate(0.5);
}

.cyvasse-smart {
  color: inherit;
  background-color: transparent;
  border: 1px solid #8b5cf6;
  box-shadow: none;
}

@media (hover: hover) {
  .cyvasse-smart:hover {
    background-color: rgb(139 92 246 / 0.15);
  }
}

/* The strip above the board: the start-over hint while a piece is picked,
 * and the threat switch. A fixed height, so the board never jumps when the
 * hint comes and goes. */
.cyvasse-board-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-height: 2.125rem; /* the touch hint: 19.5px line + 0.35rem padding + border = 32.7px */
  margin-bottom: 0.375rem;
  /* The board wrap clips sideways (the banner, above), so inset the strip
   * enough that a focused hint's ring is not shaved at the wrap's edge. */
  padding-inline: 0.25rem;
  font-size: 0.8125rem;
}

.cyvasse-hint {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.2rem 0.65rem;
  border-radius: 9999px;
  border: 1px solid var(--color-border);
  background: var(--color-surface-alt);
  color: var(--color-text-secondary, inherit);
  font-weight: 500;
  cursor: pointer;
}

.cyvasse-hint[hidden] {
  display: none;
}

.cyvasse-hint kbd {
  padding: 0 0.3rem;
  border-radius: 0.25rem;
  border: 1px solid var(--color-border);
  border-bottom-width: 2px;
  font: inherit;
  font-weight: 700;
  font-size: 0.75rem;
}

/* A touch screen has no Esc key: the hint becomes a tap target. */
.cyvasse-hint-touch {
  display: none;
}

@media (hover: none) and (pointer: coarse) {
  .cyvasse-hint-keys {
    display: none;
  }

  .cyvasse-hint-touch {
    display: inline;
  }

  .cyvasse-hint {
    padding: 0.35rem 0.9rem;
  }
}

.cyvasse-threat-toggles {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  margin-left: auto;
}

.cyvasse-threat-toggles[hidden] {
  display: none;
}

.cyvasse-threat-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  color: var(--color-text-secondary, inherit);
  cursor: pointer;
  user-select: none;
}

.cyvasse-threat-toggle input {
  accent-color: #e25c3c;
}

/* The board key (games/_threat_toggle, BoardLegendHelper): a small "?" by the
 * threat switches that opens a panel over the board, one swatch per look a
 * hex takes in play. The swatches copy the board rules above; the board looks
 * the same in both themes, so they do too, and only the panel follows it. */
.cyvasse-legend {
  position: relative;
}

.cyvasse-legend-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  color: var(--color-text-secondary, inherit);
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.cyvasse-legend-toggle::-webkit-details-marker {
  display: none;
}

.cyvasse-legend-mark {
  display: inline-grid;
  place-items: center;
  width: 1.125rem;
  height: 1.125rem;
  border-radius: 9999px;
  border: 1px solid var(--color-border-strong, var(--color-border));
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1;
}

.cyvasse-legend[open] .cyvasse-legend-mark {
  border-color: currentColor;
}

.cyvasse-legend-panel {
  position: absolute;
  z-index: 20;
  top: calc(100% + 0.375rem);
  right: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.375rem 0.75rem;
  width: min(22rem, calc(100vw - 2rem));
  margin: 0;
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  background: var(--color-surface);
  color: var(--color-text-body, inherit);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.25);
  list-style: none;
  font-size: 0.75rem;
  line-height: 1.25;
}

/* With no room below its "?", the key opens above it (board_key controller),
 * which sets a max-height when neither side has room for all of it; the
 * panel then scrolls inside itself, never off the screen. */
.cyvasse-legend[data-placement="above"] .cyvasse-legend-panel {
  top: auto;
  bottom: calc(100% + 0.375rem);
}

.cyvasse-legend-panel {
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* In the match's timer card the key sits at the left of a wrapped row. */
.match-panel-status .cyvasse-legend-panel {
  right: auto;
  left: 0;
  width: min(17rem, calc(100vw - 2rem));
  grid-template-columns: minmax(0, 1fr);
}

.cyvasse-legend-entry {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.cyvasse-legend-swatch {
  flex: none;
  width: 0.875rem;
  height: 1rem;
  overflow: visible;
}

.cyvasse-legend-swatch polygon {
  fill: hsl(220 11% 17%);
  stroke: white;
  stroke-width: 1;
}

/* The selection's swatch glows and pulses like the board's; the last move's
 * is the same glow, dimmer and still. */
.cyvasse-legend-swatch[data-swatch="selected"] polygon {
  fill: url(#cyvasse-legend-glow-selected);
  stroke: orange;
  animation: cyvasse-selected-pulse 1.8s ease-in-out infinite;
}
.cyvasse-legend-swatch[data-swatch="last-move"] polygon { fill: url(#cyvasse-legend-glow-last-move); opacity: 0.7; }

/* In the pencil skin the two swatches also show the parchment disc the glow
 * rings on the board; the vector skin has no disc. */
.cyvasse-legend-disc { display: none; }
[data-skin="pencil"] .cyvasse-legend-disc {
  display: inline;
  fill: #f7f4ec;
  stroke: #ff9a1f;
  stroke-width: 1.25;
  filter: drop-shadow(0 0 1px rgb(255 140 26 / 0.95));
}
[data-skin="pencil"] .cyvasse-legend-swatch[data-swatch="selected"] .cyvasse-legend-disc {
  animation: cyvasse-selected-rim 1.8s ease-in-out infinite;
  stroke-opacity: var(--rim-glow);
}

/* Your piece and the enemy's show the disc's team rim, as on the board. */
[data-skin="pencil"] .cyvasse-legend-swatch:is([data-swatch="yours"], [data-swatch="enemy"]) .cyvasse-legend-disc {
  stroke-width: 1;
  filter: none;
}
[data-skin="pencil"] .cyvasse-legend-swatch[data-swatch="yours"] .cyvasse-legend-disc { stroke: var(--cyvasse-team-1); }
[data-skin="pencil"] .cyvasse-legend-swatch[data-swatch="enemy"] .cyvasse-legend-disc { stroke: var(--cyvasse-team-0); }

@media (prefers-reduced-motion: reduce) {
  .cyvasse-legend-swatch[data-swatch="selected"] polygon { animation: none; }
  [data-skin="pencil"] .cyvasse-legend-swatch[data-swatch="selected"] .cyvasse-legend-disc { animation: none; }
}
.cyvasse-legend-swatch[data-swatch="yours"] polygon { fill: hsl(217 60% 30%); }
.cyvasse-legend-swatch[data-swatch="enemy"] polygon { fill: hsl(0 55% 28%); }
.cyvasse-legend-swatch[data-swatch="move"] polygon { fill: hsl(240 44% 47%); }
.cyvasse-legend-swatch[data-swatch="attack"] polygon { fill: hsl(10 44% 47%); stroke: hsl(0 100% 66%); stroke-width: 2; }
.cyvasse-legend-swatch[data-swatch="sunken"] polygon { fill: hsl(220 4% 11%); stroke: hsl(220 4% 34%); }
.cyvasse-legend-swatch[data-swatch="ghost"] polygon { fill: hsl(226 30% 22%); stroke: hsl(226 95% 70%); stroke-dasharray: 2 1.5; }
.cyvasse-legend-swatch[data-swatch="field"] polygon { fill: hsl(4 45% 26%); stroke: hsl(354 60% 44%); }
.cyvasse-legend-swatch[data-swatch="blocked"] polygon { fill: hsl(214 12% 20%); stroke: hsl(214 16% 58%); stroke-dasharray: 1.5 1.5; }
.cyvasse-legend-swatch[data-swatch="reach"] polygon { stroke: #ef4444; stroke-width: 2; }
.cyvasse-legend-swatch[data-swatch="danger"] polygon { fill: hsl(25 70% 30%); stroke: #f97316; stroke-width: 2; }
.cyvasse-legend-swatch[data-swatch="focus"] polygon { stroke: yellow; stroke-width: 2; }

/* A phone keeps only the "?"; the name stays for screen readers. */
@media (max-width: 639px) {
  .cyvasse-legend-name {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* The switches wrap on a phone and the "?" can land at the left of its
   * own row, so the panel hangs from the strip's right edge, not the "?". */
  .cyvasse-board-bar,
  .match-panel-status {
    position: relative;
  }

  .cyvasse-legend {
    position: static;
  }

  .cyvasse-legend-panel,
  .match-panel-status .cyvasse-legend-panel {
    left: auto;
    right: 0;
    grid-template-columns: minmax(0, 1fr);
    width: min(15rem, calc(100vw - 2rem));
  }
}

/* The opponent's reach (.is-threatened, from renderThreats) is outlined only
 * round its rim: a solid red "perimeter" edge (above) where the area meets
 * the rest of the board or the board's edge. In cyvasse/edges' "dual"
 * PERIMETER_STYLE, melee's rim is solid and ranged's a dashed
 * "perimeter-ranged" edge. */

/* A unit of yours the opponent could kill next turn: an orange glow from its
 * hex's edges in, like the team shade, and an orange edge, both breathing
 * (steady for a player who asked for less motion). They breathe by opacity
 * alone, so no frame rebuilds geometry (still repainted: SVG children get no
 * layer of their own). */
.cyvasse-board .danger-edge {
  display: none;
  pointer-events: none;
}

.cyvasse-board .is-danger .danger-edge {
  display: inline;
  animation: cyvasse-danger 1.8s ease-in-out infinite;
  will-change: opacity;
}

/* The team shade steps back, as for a hex under attack, so the orange reads. */
.cyvasse-board .is-danger .unit-shade {
  opacity: 0.3;
}

@keyframes cyvasse-danger {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

@media (prefers-reduced-motion: reduce) {
  .cyvasse-board .is-danger .danger-edge,
  .cyvasse-board .hex-edge[data-kind="danger"] {
    animation: none;
    opacity: 0.9;
  }
}

/* The setup clock's copy in the army card: shown only in a docked sheet. */
.cyvasse-army-clock {
  display: none;
}

/* Setup layouts (tasks cyvasse-phone-setup-dock, cyvasse-dock-landscape-tablet).
 * Placing 19 units means picking one from the army card and placing it on
 * the board, 19 times, on a running clock, so the board and the whole army
 * card (Smart Setup and Ready with it) are on the screen together for the
 * whole setup, with no page scroll. Each screen gets the layout that fits:
 *
 * - under 1024px, held upright (phones, tablets in portrait): the card docks
 *   as a sheet fixed to the bottom of the screen, under the board;
 * - under 1024px, on its side (phones in landscape): the card docks as a
 *   sheet fixed to the right of the screen, beside a board sized to the
 *   screen's height;
 * - 1024px and up on a touch screen (tablets in landscape): the desktop two
 *   columns, the board capped to the screen's height;
 * - desktop (1024px and up with a mouse) is untouched.
 *
 * --setup-fit names the fit to the controller (showBoardForSetup), which
 * scrolls the board into view once, as setup opens and at a pick: "bottom"
 * keeps the board between the pinned navbar and the sheet, "side" between
 * the navbar and the screen's foot, and "page" keeps the board and the army
 * card, both in the page, on the screen together. Nothing in a sheet moves
 * as the army is placed: every tile keeps its slot and Smart Setup keeps one
 * place, only its label and fill change. The rest of the panel (openings,
 * saved lineups) stays in the page, clear of the sheet. */

/* Both sheets: the heading over the count (and a live match's clock), every
 * unit, then Smart Setup beside Ready at the sheet's foot, in thumb reach. */
@media (max-width: 1023px) {
  .cyvasse-setup-controls .cyvasse-army {
    --army-slot: 2.75rem;
    position: fixed;
    z-index: 40;
    margin: 0;
    gap: 0.5rem 0.75rem;
    box-shadow: 0 -10px 24px -14px rgb(0 0 0 / 0.45);
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: 1.25rem auto var(--army-slot);
    grid-template-areas: "heading meta" "dock dock" "smart ready";
  }

  /* The sheet keeps to the count and the clock; the status copy stays out,
   * and the status line itself is read from the page as ever. */
  .cyvasse-setup-controls .cyvasse-army .cyvasse-army-status {
    display: none;
  }

  .cyvasse-setup-controls .cyvasse-army > h2 {
    align-self: center;
    text-align: left;
    line-height: 1.25rem;
  }

  .cyvasse-setup-controls .cyvasse-army .cyvasse-army-count {
    display: block;
    grid-area: meta;
    justify-self: start;
    line-height: 1.25rem;
    white-space: nowrap;
  }

  .cyvasse-setup-controls .cyvasse-army .cyvasse-army-clock:not([hidden]) {
    display: block;
    grid-area: meta;
    justify-self: end;
    align-self: center;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1.25rem;
    color: var(--color-heading, inherit);
  }

  .cyvasse-setup-controls .cyvasse-army .cyvasse-army-clock.is-warning {
    color: #dc2626;
  }

  /* On the dark dock sheet (a bg-surface card) #dc2626 is 2.31:1. The dark
   * theme takes the engine's danger ink, the red derived to clear WCAG AA
   * 4.5:1 on every dark surface (task cyvasse-contrast-and-names). Light mode
   * keeps #dc2626, 4.83:1 on the white card. */
  html.dark .cyvasse-setup-controls .cyvasse-army .cyvasse-army-clock.is-warning {
    color: var(--color-danger-ink);
  }
}

/* The bottom sheet, clear of the home indicator: every unit in three rows of
 * seven on a phone. The controller measures the sheet into --dock-sheet; the
 * fallback is its usual height. The page is padded clear of it, and the
 * board is capped to the screen left above the sheet and the pinned navbar
 * (about 6.5rem once collapsed) with the board's bar (2.5rem). */
@media (max-width: 1023px) and (orientation: portrait) {
  .cyvasse-game:has(.cyvasse-setup-controls:not([hidden])) {
    --setup-fit: bottom;
    --dock-sheet-h: var(--dock-sheet, calc(15.5rem + env(safe-area-inset-bottom, 0px)));
    padding-bottom: calc(var(--dock-sheet-h) + 0.5rem);
  }

  .cyvasse-game:has(.cyvasse-setup-controls:not([hidden])) .cyvasse-board {
    max-height: calc(100svh - var(--dock-sheet-h) - 9.5rem);
  }

  .cyvasse-setup-controls .cyvasse-army {
    inset: auto 0 0 0;
    padding: 0.625rem max(1rem, env(safe-area-inset-right, 0px)) calc(0.75rem + env(safe-area-inset-bottom, 0px)) max(1rem, env(safe-area-inset-left, 0px));
    border-radius: 1rem 1rem 0 0;
    border-bottom-width: 0;
  }

  .cyvasse-setup-controls .cyvasse-army .cyvasse-dock {
    grid-template-columns: repeat(7, minmax(0, 3.25rem));
    justify-content: center;
  }
}

/* A tablet held upright: wider tiles, in two rows of ten, so the sheet stays
 * short and the board keeps its full size above it. */
@media (min-width: 641px) and (max-width: 1023px) and (orientation: portrait) {
  .cyvasse-setup-controls .cyvasse-army {
    padding-inline: max(1.5rem, env(safe-area-inset-left, 0px)) max(1.5rem, env(safe-area-inset-right, 0px));
  }

  .cyvasse-setup-controls .cyvasse-army .cyvasse-dock {
    grid-template-columns: repeat(10, minmax(0, 4.5rem));
  }
}

/* The side sheet: a phone on its side has too little height for a board over
 * a sheet, so the sheet takes the screen's right side, from under the pinned
 * navbar to the foot, clear of the notch and the home indicator. The page is
 * padded clear of it (main's own gutter is 1rem), and the board, left of it,
 * is capped to the screen's height under the navbar (about 3.5rem once
 * collapsed), the board's bar tucked under the navbar. The tiles are compact,
 * three rows of seven; a sheet taller than the screen scrolls on its own. */
@media (max-width: 1023px) and (orientation: landscape) {
  .cyvasse-game:has(.cyvasse-setup-controls:not([hidden])) {
    --setup-fit: side;
    --dock-side-w: clamp(16rem, 46vw, 24rem);
    padding-right: calc(var(--dock-side-w) + env(safe-area-inset-right, 0px) - 0.25rem);
    padding-left: env(safe-area-inset-left, 0px);
  }

  .cyvasse-game:has(.cyvasse-setup-controls:not([hidden])) .cyvasse-board {
    max-height: calc(100svh - 4.25rem);
    max-width: min(44rem, calc((100svh - 4.25rem) * 1.12));
  }

  .cyvasse-setup-controls .cyvasse-army {
    top: var(--pin-stack-bottom, 0px);
    right: 0;
    bottom: 0;
    width: calc(var(--dock-side-w) + env(safe-area-inset-right, 0px));
    overflow-y: auto;
    overscroll-behavior: contain;
    align-content: start;
    padding: 0.625rem max(1rem, env(safe-area-inset-right, 0px)) calc(0.75rem + env(safe-area-inset-bottom, 0px)) 1rem;
    border-radius: 1rem 0 0 0;
    border-right-width: 0;
    border-bottom-width: 0;
    box-shadow: -10px 0 24px -14px rgb(0 0 0 / 0.45);
  }

  .cyvasse-setup-controls .cyvasse-army .cyvasse-dock {
    grid-template-columns: repeat(7, minmax(0, 1fr));
  }
}

/* A tablet on its side keeps the desktop's two columns; its screen is short,
 * so the board is capped to leave the army card beside it on the screen
 * (the navbar, about 3.5rem collapsed, and the card's lead over the board,
 * about 5.5rem). A mouse (hover) marks a desktop, which is left as it was. */
@media (min-width: 1024px) and (hover: none) and (pointer: coarse) {
  .cyvasse-game:has(.cyvasse-setup-controls:not([hidden])) {
    --setup-fit: page;
  }

  .cyvasse-game:has(.cyvasse-setup-controls:not([hidden])) .cyvasse-board {
    max-height: calc(100svh - 10rem);
    max-width: min(44rem, calc((100svh - 10rem) * 1.12));
  }
}

/* Play layouts (task cyvasse-play-layout-fit). The setup layouts above keep
 * the board on the screen while an army is placed; these keep the whole board
 * on the screen for the game itself, all 91 hexes, on the screens where it
 * once ran off: phones on their side, short laptops, and the empty band a
 * phone showed above the board on a match. */

/* Short desktop screens (a 13" laptop, 1280x800 or 1133x744): the board is
 * capped to the screen's height left under what sits above it at the top of
 * the page, so it is whole on the screen without a scroll. On /play that lead
 * is the expanded navbar (about 5.75rem), the page's padding, the title and
 * the board's banner and hint rows, about 17.5rem; on a match, whose board is
 * lifted beside the versus card, about 8.75rem; each with a 1rem margin. The
 * environment bar dev, test and QA show above the navbar (.studio-bar-stack,
 * 2.5rem) scrolls away with the page but is on the screen at the top, so it
 * counts. A screen tall enough for the lead and the board both keeps the
 * full 44rem: about 925px on /play (965px under the bar), 785px on a match. */
@media (min-width: 1024px) {
  .cyvasse-game {
    --board-lead: 18.5rem;
    --board-room: max(18rem, calc(100svh - var(--board-lead)));
  }

  .cyvasse-game:has(.match-layout) {
    --board-lead: 9.75rem;
  }

  :root:has(.studio-bar-stack) .cyvasse-game {
    --board-lead: 21rem;
  }

  :root:has(.studio-bar-stack) .cyvasse-game:has(.match-layout) {
    --board-lead: 12.25rem;
  }

  .cyvasse-game .cyvasse-board {
    max-height: var(--board-room);
    max-width: min(44rem, calc(var(--board-room) * 1.12));
  }
}

/* A phone on its side, once setup is over (the side sheet has gone): the
 * board beside the sidebar, as in setup, and sized to the screen's height
 * under the pinned navbar (about 3.5rem once collapsed) with the banner's row
 * over it (3rem): the start-over strip shares that row, as on a desktop
 * match, the banner drawn over it. The board's column sticks under the
 * navbar, so the sidebar (the unit card, the fallen, the chat) scrolls past
 * it and the board never leaves the screen. --play-fit names the fit to the
 * controller (showBoardForPlay), which scrolls the board into view once, as
 * the game opens. */
@media (max-width: 1023px) and (orientation: landscape) {
  .cyvasse-game:not(:has(.cyvasse-setup-controls:not([hidden]))) {
    --play-fit: side;
    padding-inline: env(safe-area-inset-left, 0px) env(safe-area-inset-right, 0px);
  }

  .cyvasse-game:not(:has(.cyvasse-setup-controls:not([hidden]))) .cyvasse-layout {
    grid-template-columns: minmax(0, 1fr) clamp(14rem, 34vw, 20rem);
    column-gap: 1rem;
    align-items: start;
  }

  .cyvasse-game:not(:has(.cyvasse-setup-controls:not([hidden]))) .match-layout {
    grid-template-rows: auto 1fr;
    grid-template-areas: "board versus" "board panel";
    row-gap: 1rem;
  }

  .cyvasse-game:not(:has(.cyvasse-setup-controls:not([hidden]))) .cyvasse-board-wrap {
    position: sticky;
    top: var(--pin-stack-bottom, 0px);
  }

  .cyvasse-game:not(:has(.cyvasse-setup-controls:not([hidden]))) .cyvasse-board-bar {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2.75rem; /* the banner slot's height */
    margin: 0;
  }

  /* Drawn over the hint's row, so it must let clicks through to the hint. */
  .cyvasse-game:not(:has(.cyvasse-setup-controls:not([hidden]))) .cyvasse-banner-slot {
    z-index: 1;
    pointer-events: none;
  }

  .cyvasse-game:not(:has(.cyvasse-setup-controls:not([hidden]))) .cyvasse-board {
    max-height: calc(100svh - 7rem);
    max-width: min(44rem, calc((100svh - 7rem) * 1.12));
  }
}

/* A phone held upright, on a match: the board is as wide as the screen
 * allows, so the height above it is all that can be given back. The banner's
 * row, held open for a banner that shows two seconds a turn, left a band of
 * about 100px between the versus card and the board. Now the row is a 1rem
 * strip under a tighter gap; the banner and the start-over hint, which shares
 * the strip, rise from its foot over the gap and the versus card's foot while
 * they show (the hint within the card's bottom padding; the taller banner, for
 * its two seconds, a little into the card). Nothing is laid out anew when
 * either comes or goes, so the board never jumps, and neither is ever drawn
 * over the board. */
@media (max-width: 1023px) and (orientation: portrait) {
  .match-layout > .cyvasse-board-wrap {
    margin-top: -1.125rem; /* the grid's 1.5rem gap, less 0.375rem */
  }

  .match-layout .cyvasse-banner-slot {
    min-height: 1rem;
    margin-bottom: 0;
    z-index: 1;
    pointer-events: none;
  }

  .match-layout .cyvasse-board-bar {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 0.875rem;
    min-height: 0;
    margin: 0;
    align-items: flex-end;
  }
}

/* A docked sheet on a live match carries the setup clock (.cyvasse-army-clock),
 * so the timer card's own clock would be a second one, peeking above a bottom
 * sheet. It goes while the sheet is up, and the card with it when the clock
 * was all it held; a notice or an error keeps the card. */
@media (max-width: 1023px) {
  .match-panel:has(> .cyvasse-setup-controls:not([hidden]) .cyvasse-army-clock:not([hidden])) > .match-panel-status > .live-clock {
    display: none;
  }

  .match-panel:has(> .cyvasse-setup-controls:not([hidden]) .cyvasse-army-clock:not([hidden])) > .match-panel-status:not(:has(> :not([hidden]):not(.live-clock))) {
    display: none;
  }
}
