* { box-sizing: border-box; }

/* Roboto Condensed, vendored locally (see play/fonts/) so the page has no
 * external requests -- needed for a self-contained itch.io embed upload.
 * Each subset file is a variable font covering the whole weight axis, so
 * the same file is reused for both the 400 and 700 @font-face blocks
 * (exactly how Google Fonts itself served it) instead of needing 14
 * separate static files. */
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/roboto-condensed-cyrillic-ext.woff2") format("woff2");
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/roboto-condensed-cyrillic.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/roboto-condensed-greek-ext.woff2") format("woff2");
  unicode-range: U+1F00-1FFF;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/roboto-condensed-greek.woff2") format("woff2");
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/roboto-condensed-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/roboto-condensed-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/roboto-condensed-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/roboto-condensed-cyrillic-ext.woff2") format("woff2");
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/roboto-condensed-cyrillic.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/roboto-condensed-greek-ext.woff2") format("woff2");
  unicode-range: U+1F00-1FFF;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/roboto-condensed-greek.woff2") format("woff2");
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/roboto-condensed-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/roboto-condensed-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Roboto Condensed";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/roboto-condensed-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Single source of truth for #board's margin -- referenced by #board
 * itself, by the elongated-aspect-ratio override (which shrinks it), and
 * by the portrait top/bottom-bar positioning (which needs to know exactly
 * where the board's edge is to sit flush against it). Redefining this at
 * :root inside a media query updates every one of those consistently,
 * instead of three separate hardcoded values drifting out of sync. */
:root { --board-margin: 4vmin; }

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #1b1e24;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  color: #e8e8ec;
}

/* Margined on all sides so the hex grid itself never renders underneath
 * the top/bottom status text or the corner buttons -- those are separate
 * fixed-position DOM elements layered on top regardless of the canvas's
 * own size, so this margin is what actually keeps hex content clear of
 * them rather than just visually avoiding overlap by chance. Top/bottom
 * are generous (must clear both the corner buttons' box and the
 * top/bottom text); left/right are modest (corner buttons there are only
 * a corner-sized footprint, not a full-width bar). Width/height are
 * explicit calc()s, not a top+bottom+left+right auto-stretch -- <canvas>
 * is a replaced element, and CSS only auto-stretches a replaced element's
 * size from opposing inset properties in specific cases; in practice it
 * fell back to the intrinsic default (300x150) instead of filling the
 * margin. resize() reads the canvas's own post-margin clientWidth/
 * clientHeight rather than window.innerWidth/innerHeight, so this is the
 * single source of truth for the play area's size -- see resize()/
 * renderCache()/render() in app.mjs. */
#board {
  position: fixed;
  top: 3vmin;
  left: 3vmin;
  width: calc(100% - 6vmin);
  height: calc(100% - 6vmin);
  display: block;
  cursor: default;
}

/* Every font-size/padding/gap/position-offset below a button or menu is in
 * vmin, not px. vmin = min(1vw, 1vh) -- in landscape (width > height) that
 * min is always the height axis, so it behaves as %-of-viewport-HEIGHT; in
 * portrait (height > width) it's always the width axis, so it behaves as
 * %-of-viewport-WIDTH. That's exactly "vh in landscape, vw in portrait"
 * with no media query needed -- the browser picks the right axis per
 * frame as the window is resized/rotated. Hairline borders, shadow
 * blur/spread, and backdrop-filter blur radii are deliberately left in
 * px -- those are crisp decorative accents, not layout sizing, and
 * scaling a 1px border with viewport size would make it fuzzy/inconsistent
 * rather than crisp. */

/* Single-line status text, bottom-center -- bare text, no frame (see
 * .hbar below for the top-center equivalent). */
#status {
  position: fixed;
  bottom: 1.2vmin;
  left: 50%;
  transform: translateX(-50%);
  max-width: calc(100% - 2.4vmin);
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 3vmin;
  font-size: 3vmin;
  font-family: "Roboto Condensed", sans-serif;
  pointer-events: none;
}
#status .p0 { color: #6fb2ff; }
#status .p1 { color: #ff7d6f; }
#status .win { font-weight: 700; }

/* Horizontal bar: currently just the victory/stall readout (top-center).
 * Bare text, no frame -- just the flex layout needed to lay the
 * victory/stall segments out in a row. */
.hbar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 1.8vmin;
  flex-wrap: wrap;
  max-width: calc(100% - 2.4vmin);
  line-height: 3.6vmin;
  font-family: "Roboto Condensed", sans-serif;
  pointer-events: none;
}

#victorybar {
  top: 1.2vmin;
  font-size: 3vmin;
  font-weight: 600;
  gap: 7vmin;
  width: 100%;
}

/* Corner icon buttons (Rules/home, Reset, Pass, Settings/Fullscreen) --
 * bare SVG icons, no background/border/frame, minimal edge margin. Reset
 * (bottom-left) and Pass (bottom-right) bookend the status text, living on
 * their own rather than inside the settings panel so they stay reachable
 * even in online mode, where the settings corner is replaced by the
 * fullscreen toggle (see app.mjs) and the whole online/QR panel moves to a
 * centered popup (.gui-panel below) instead of a corner. */
.icon-btn {
  position: fixed;
  width: 7vmin;
  height: 7vmin;
  padding: 0;
  margin: 0;
  display: block;
  background: none;
  border: none;
  cursor: pointer;
  pointer-events: auto;
}
.icon-btn img { display: block; width: 100%; height: 100%; }
.icon-btn:hover:not(:disabled) { opacity: 0.75; }
.icon-btn:disabled { opacity: 0.35; cursor: default; }

.icon-btn.top-left { top: 1vmin; left: 1vmin; }
.icon-btn.top-right { top: 1vmin; right: 1vmin; }
.icon-btn.bottom-left { bottom: 1vmin; left: 1vmin; }
.icon-btn.bottom-right { bottom: 1vmin; right: 1vmin; }

/* "Quite long" viewport -- very wide landscape or very tall portrait --
 * where vmin (tied to the SHORTER axis) would otherwise leave the corner
 * icons small relative to how far apart they actually sit on the long
 * axis. Bumped up from 5vmin to 7vmin in that case. */
@media (min-aspect-ratio: 16/9), (max-aspect-ratio: 9/16) {
  .icon-btn {
    width: 9vmin;
    height: 9vmin;
  }

  .icon-btn.top-left { top: 1.2vmin; left: 1.2vmin; }
.icon-btn.top-right { top: 1.2vmin; right: 1.2vmin; }
.icon-btn.bottom-left { bottom: 1.2vmin; left: 1.2vmin; }
.icon-btn.bottom-right { bottom: 1.2vmin; right: 1.2vmin; }

:root { --board-margin: 2vmin; }
}

.hidden { display: none; }

/* Mode-picker shown on a bare load (no ?vs=1/?host=/?bot=1) instead of
 * jumping straight into local-bot play -- same button look as the root
 * page's four corner links (font, glow border, dark fill), just stacked
 * vertically and centered instead of corner-anchored. vmin already
 * behaves like the root page's vh-in-landscape/vw-in-portrait split (see
 * the comment atop this file), so the root's landscape-block values carry
 * over as-is, unit-for-unit. */
@font-face {
  font-family: "MyFont";
  src: url("font.ttf") format("truetype");
  font-weight: normal;
  font-style: normal;
}

.start-menu {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 65;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8vmin;
}
.start-menu.hidden { display: none; }

.start-btn {
  font-family: "MyFont", sans-serif;
  display: inline-block;
  border: 1vmin solid #ff000066;
  background-color: #000000dd;
  color: red;
  cursor: pointer;
  height: 16vmin;
  width: 60vmin;
  transform: scale(0.9);
  transition: transform 0.2s ease, background-color 0.2s ease;
  border-radius: 16vmin;
  font-weight: 500;
  font-size: 12vmin;
  padding: 4vmin;
  text-align: center;
  line-height: 8vmin;
  letter-spacing: 1%;
  text-shadow: 0.1vh 0.2vh black;
  margin: 0;
}

.start-btn:hover { transform: scale(1); background: black; }

#victorybar .p0 { color: #6fb2ff; }
#victorybar .p1 { color: #ff7d6f; }
#victorybar .stall { color: #d8d8dc; }
/* At its max face, the die is one tick from wrapping back to 1 -- a wrap
 * being one of the only two ways a point moves (see updateVictoryBar). */
#victorybar .stall.warn { color: #ffd23f; font-weight: 700; }
#victorybar.hidden { display: none; }

/* Panel chrome for .online-panel (see near the bottom of this file -- the
 * "Play Online" role-picker/QR/share-link/connection-status panel).
 * Centered like .choice-menu (same treatment as the attack/charge and
 * hold/recuperate popups) rather than corner-anchored -- the settings
 * corner shows a fullscreen toggle instead during online play (see
 * app.mjs), so this panel isn't competing with anything for that spot
 * anymore. No padding on the outer box itself: .gui-header/.gui-body
 * below provide their own, so adding padding here would double it up. */
.gui-panel {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: rgba(24, 26, 32, 0.92);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 0.8vmin;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
  overflow: hidden;
  pointer-events: auto;
  backdrop-filter: blur(4px);
}

.gui-header {
  box-sizing: border-box;
  padding: 1.2vmin 1.4vmin;
  line-height: 3.4vmin;
  background: rgba(255, 255, 255, 0.05);
  font-size: 3vmin;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: #e8e8ec;
  user-select: none;
}
.gui-header::before {
  content: "\25B8";
  display: inline-block;
  width: 1.6vmin;
  font-size: 1.4vmin;
  color: #9aa0ab;
}

.gui-body {
  display: flex;
  flex-direction: column;
  gap: 1.2vmin;
  padding: 1.6vmin 2vmin 2vmin;
}

/* Settings modal (#settingsMenu, a .choice-menu) content -- label-left/
 * control-right rows, plus the label-on-top/full-width .slider-row
 * layout below for the two sliders. */
.settings-panel {
  display: flex;
  flex-direction: column;
  gap: 0.8vmin;
  min-width: 32vmin;
  text-align: left;
}

.settings-panel label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.2vmin;
  font-size: 3vmin;
  color: #b9bcc4;
  padding: 0.3vmin 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
/* A label's own `display: flex` outranks the generic .hidden class on
 * specificity (class+class > class alone), so it needs its own override
 * here to actually hide (e.g. the Placement rollouts row -- still
 * present in the DOM with its default value, just not shown). */
.settings-panel label.hidden { display: none; }

.settings-panel select, .settings-panel input {
  background: #262a33;
  color: #e8e8ec;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 0.5vmin;
  padding: 0.6vmin 1vmin;
  font-size: 3vmin;
  max-width: 22vmin;
}

/* Skill level / Time to think: label (with live value) stacked on top of
 * a slider that spans the full modal width, instead of the label-left/
 * control-right row every other setting uses. */
.slider-row {
  display: flex;
  flex-direction: column;
  gap: 0.6vmin;
  padding: 0.6vmin 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.slider-row label {
  font-size: 3vmin;
  color: #b9bcc4;
}
/* The "Players" select gets the same label-on-top/full-width treatment
 * as the sliders below (its option text is too long for the label-left/
 * control-right layout every other row uses), but keeps its own visible
 * box rather than bleeding into the modal's padding like the sliders. */
.settings-panel .slider-row select {
  width: 100%;
  max-width: none;
}
/* Bleeds past .choice-menu's side padding so the track itself spans
 * the full width of the settings popup (flush with its border), not just
 * the padded content column every other row sits in. */
.settings-panel input[type="range"] {
 display: block;
    width: calc(100%);
    max-width: none;
    margin: 0;
    padding: 0;
    accent-color: #3a6ea8;
    background: transparent;
    border: none;
}

/* Plain in-panel buttons -- Fullscreen and Close. Block-level/full-width
 * to sit naturally in the panel's column instead of floating fixed in a
 * screen corner like .icon-btn. */
.panel-btn {
  display: block;
  width: 100%;
  box-sizing: border-box;
  background: #262a33;
  color: #e8e8ec;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 0.5vmin;
  padding: 1.2vmin 1vmin;
  font-size: 3vmin;
  text-align: center;
  cursor: pointer;
  /* On top of .settings-panel's own row gap -- buttons (Fullscreen, Back
     to mode selection) get visibly more breathing room around them than
     the plain label/select/slider rows they sit next to. */
  margin: 0.8vmin 0;
}
.panel-btn:hover { background: #33394a; }
/* .panel-btn's own `display: block` is declared after the generic .hidden
 * class above -- equal specificity, so source order alone would let it win
 * and keep #backToMenuBtn visible even with .hidden applied (same issue
 * .settings-panel label.hidden works around below). */
.panel-btn.hidden { display: none; }

/* Dismisses #settingsMenu -- same box as .panel-btn, just muted and set
 * apart from the actual settings above it. */
#settingsCloseBtn { margin-top: 1.4vmin; color: #8a8d96; }
.hint {
    font-size: 3.6vmin;
    color: #8a8d96;
    min-height: 2vmin;
    padding-top: 0.3vmin;
}
#popups {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 50;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1vmin;
  pointer-events: none;
}

.popup {
  font-size: 2.6vmin;
  font-weight: 700;
  font-family: "Roboto Condensed", sans-serif;
  padding: 1vmin 2.6vmin;
  border-radius: 1vmin;
  background: rgba(15, 17, 22, 0.82);
  border: 1px solid rgba(255, 255, 255, 0.18);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
  text-align: center;
  white-space: nowrap;
  opacity: 0;
  animation-name: popup-fade;
  animation-timing-function: ease-out;
  animation-fill-mode: forwards;
}

.popup.p0 { color: #9dc9ff; border-color: rgba(111, 178, 255, 0.5); }
.popup.p1 { color: #ffb3a8; border-color: rgba(255, 125, 111, 0.5); }
.popup.win { font-size: 3.8vmin; }

/* Small per-unit flourish popups (charge/traverse/recuperate/first blood)
 * -- same .popup base and popup-fade animation as the center-screen ones
 * (#popups), but positioned directly over the acting unit's hex instead
 * of screen-center, via inline left/top set from hexCenter() in app.mjs.
 * popup-fade animates the standalone `scale` property rather than
 * `transform: scale(...)` specifically so it can coexist with the
 * `translate` set here for positioning -- two separate CSS properties,
 * instead of one clobbering the other every keyframe. */
.popup-unit {
  position: fixed;
  z-index: 55;
  translate: -50% -50%;
  font-size: 3vmin;
  font-weight: 700;
  padding: 0.6vmin 1.4vmin;
  border-radius: 0.6vmin;
  pointer-events: none;
}

@keyframes popup-fade {
  0%   { opacity: 0; scale: 0.85; }
  12%  { opacity: 1; scale: 1; }
  72%  { opacity: 1; scale: 1; }
  100% { opacity: 0; scale: 1.04; }
}

/* Tap-friendly choice menu (attack-vs-charge, hold-vs-recuperate, ...) --
 * replaces the old Shift-click modifier, which has no equivalent on a
 * touchscreen. */
.choice-menu {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 60;
  background: rgba(15, 17, 22, 0.95);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 1.2vmin;
  padding: 2.2vmin 2.8vmin;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
  text-align: center;
}

.choice-menu.hidden { display: none; }

.choice-title {
  font-size: 3vmin;
  font-weight: 700;
  margin-bottom: 1.6vmin;
}

.choice-options {
  display: flex;
  flex-direction: column;
  gap: 1.6vmin;
  min-width: 30vmin;
}

.choice-options button {
  background: #262a33;
  color: #e8e8ec;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 0.8vmin;
  padding: 1.4vmin 2.6vmin;
  font-size: 3vmin;
  cursor: pointer;
}

.choice-options button:hover { background: #33394a; }
.choice-options button.cancel { color: #8a8d96; margin-top: 0.6vmin; }

/* Online-play panel (centered, see .gui-panel above) -- shown in ?vs=1 /
 * ?host=... mode: a role picker + share link/QR while waiting for a peer,
 * then a brief connected status before auto-hiding so it doesn't sit over
 * the board during play (see renderHostConnectedUI/renderClientConnectedUI
 * in app.mjs). The Reset button that appears for the host once connected
 * lives bottom-left instead, see .icon-btn. */
.online-panel { width: 42vmin; }

/* Seat picker (#roleSelect) -- same look as the settings menu's Players
 * dropdown (#modeSelect, see .settings-panel select/.slider-row above),
 * just re-scoped to .online-panel instead of .settings-panel since this
 * sits in the pre-connection online panel, not the settings modal. */
.online-panel select {
  background: #262a33;
  color: #e8e8ec;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 0.5vmin;
  padding: 0.6vmin 1vmin;
  font-size: 3vmin;
  width: 100%;
}

.online-status {
    font-size: 2.4vmin;
    color: #b9bcc4;
    line-height: 2vmin;
    margin: 0.5vmin 0vmin;
}

.share-row { display: flex; gap: 1vmin; margin: 0.6vmin 0; }
.share-row input {
  flex: 1 1 auto;
  min-width: 0;
  background: #262a33;
  color: #e8e8ec;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 0.5vmin;
  padding: 0.6vmin 1vmin;
  font-size: 3vmin;
}
.share-row button {
  flex: 0 0 auto;
  width: auto;
  background: #262a33;
  color: #e8e8ec;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 0.5vmin;
  padding: 0.6vmin 1.8vmin;
  font-size: 3vmin;
  cursor: pointer;
}
.share-row button:hover { background: #33394a; }

.qr-holder {
  display: flex;
  justify-content: center;
  background: #fff;
  border-radius: 0.5vmin;
  padding: 0.8vmin;
  margin-top: 0.6vmin;
}
.qr-holder svg { display: block; width: 100%; height: auto; }

/* Portrait override -- placed last so it wins the cascade against the
 * targeted rules above regardless of specificity ties (a media query adds
 * no specificity of its own; source order is what decides a tie). Board
 * goes full-width (no left/right margin) since portrait has width to
 * spare against a narrow screen's real constraint, height.
 *
 * --board-margin is reset to a flat 4vmin here (NOT a self-referencing
 * max()/calc() against its own current value -- custom properties treat
 * that as a cycle and it resolves to nothing, breaking every rule that
 * reads it). Most real phones in portrait are narrower than 9:16, so the
 * elongated-aspect-ratio query above almost always also matches and would
 * otherwise leave this at 2vmin -- too little to fit the bars below
 * without clipping them against the viewport edge, since they sit flush
 * against the board and grow OUTWARD past it, away from the board's own
 * (now nearly edge-to-edge) margin.
 *
 * The top/bottom bars (.hbar/#status) are repositioned to sit flush
 * against the board's own top/bottom edge instead of floating at their
 * own fixed offset from the viewport, which would otherwise leave a gap
 * (or an overlap) between the bar and the board any time --board-margin
 * changes. bottom/top: calc(100% - var(--board-margin)) pins the bar's
 * OUTER edge exactly where the board's edge is, regardless of the bar's
 * own height -- the bar then grows inward (up for the status line, down
 * for the victory bar) from that shared edge. The opposite inset has to
 * be forced to auto, or a non-replaced fixed-position element with both
 * top and bottom set stretches to fill the gap between them instead of
 * keeping its natural single-line height. */
@media (orientation: portrait) {
  @property --_w {
  syntax: "<length>";
  inherits: true;
  initial-value: 100vw;
}
@property --_h {
  syntax: "<length>";
  inherits: true;
  initial-value: 100vh;
}

:root {
  /* Calculates height / width aspect ratio scalar */
  --vp-ratio: tan(atan2(var(--_h), var(--_w))); 
}

  #victorybar {
    font-size: 4vmin;
   top: calc(13vmax  * var(--vp-ratio));
  }

  #status {
    bottom:  calc(13vmax  * var(--vp-ratio));
  }

  #board {
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;}
}
