/* ================================================================
   STYLE.CSS — Empire of Dice Application Skin
   ================================================================
   This file provides all visual styling for the Empire of Dice
   digital board game application. It is built on the EOD brand
   design system defined in brand/tokens/eod.css and brand/DESIGN.md.

   Everything is scoped to the #app element so the game renders
   identically when running standalone in a browser and when
   embedded inside a host page (such as the claude.ai artifact
   viewer). This scoping ensures no style leakage in either
   direction.

   Key design rules enforced here:
   - Flat fills only, no gradients (print parity with punchboard)
   - Nightfall (#16211B) replaces pure black everywhere, including
     shadows and borders
   - Terrain colors stay muted; faction colors stay vivid
   - Faction color never appears without its glyph where ownership
     matters (enforced in the markup, not CSS)
   - Reduced-motion queries disable all animations for users who
     prefer less motion

   Tokens reference: brand/tokens/eod.css
   Design rules: brand/DESIGN.md
   ================================================================ */


/* ================================================================
   FONT FACE DECLARATIONS
   ================================================================
   Load the brand typefaces from the local TTF files in brand/fonts/.
   These are also available as base64 data URIs in fonts.css for
   fully self-contained (CSP-safe) embedding. Both are SIL OFL
   licensed, safe to bundle in apps and print on packaging.

   Chakra Petch: display font for headings, labels, UI chrome.
   Atkinson Hyperlegible: body font for maximum readability.
   ================================================================ */

/* Chakra Petch — display font (Regular, Medium, SemiBold, Bold) */
@font-face { font-family: "Chakra Petch"; src: url("brand/fonts/ChakraPetch-Regular.ttf"); font-weight: 400; }
@font-face { font-family: "Chakra Petch"; src: url("brand/fonts/ChakraPetch-Medium.ttf"); font-weight: 500; }
@font-face { font-family: "Chakra Petch"; src: url("brand/fonts/ChakraPetch-SemiBold.ttf"); font-weight: 600; }
@font-face { font-family: "Chakra Petch"; src: url("brand/fonts/ChakraPetch-Bold.ttf"); font-weight: 700; }

/* Atkinson Hyperlegible — body font (Regular, Bold, Italic) */
@font-face { font-family: "Atkinson Hyperlegible"; src: url("brand/fonts/AtkinsonHyperlegible-Regular.ttf"); font-weight: 400; }
@font-face { font-family: "Atkinson Hyperlegible"; src: url("brand/fonts/AtkinsonHyperlegible-Bold.ttf"); font-weight: 700; }
@font-face { font-family: "Atkinson Hyperlegible"; src: url("brand/fonts/AtkinsonHyperlegible-Italic.ttf"); font-weight: 400; font-style: italic; }


/* ================================================================
   SECTION 1: ROOT APP CONTAINER AND BRAND TOKENS
   ================================================================
   The #app element is the outermost container for the entire game.
   All CSS custom properties (design tokens) are declared here so
   every child element can reference them. The light "Chalk" theme
   is the default; the dark theme overrides are in Section 2.
   ================================================================ */

/* Ensure the page background matches the app surface color so no
   blank/white/black areas are ever visible behind the fixed #app */
html, body {
  margin: 0;
  padding: 0;
  background: #F5F1E6;   /* Chalk — matches #app --surface in light theme */
}

#app {

  /* ---- Brand color tokens: core neutrals ---- */
  /* These four colors form the neutral foundation of the design system */
  --nightfall: #16211B;  /* Green-black ink color; replaces pure black everywhere */
  --chalk: #F5F1E6;      /* Warm off-white paper color; primary light surface */
  --plytop: #D9C79E;     /* Light wood surface color; evokes the bar table origin */
  --barrel: #8A6238;     /* Dark wood accent color; used for borders and small highlights */

  /* ---- Brand color tokens: terrain colors (deliberately muted) ---- */
  /* Terrain colors recede visually so player pieces stand out on the map */
  --field: #4A7444;      /* Land hex fill; muted green */
  --deepwater: #27607A;  /* Water hex fill; muted teal-blue */
  --ridge: #7C7666;      /* Stone/neutral hex fill; also used for disabled UI states */

  /* ---- Brand color tokens: faction colors (deliberately vivid) ---- */
  /* These are the four player faction colors, chosen from the Okabe-Ito and */
  /* Paul Tol colorblind-safe research palettes. They are vivid so player */
  /* pieces visually advance over the muted terrain. */
  --ember: #CC3311;      /* Red faction (Tol vibrant red) */
  --gold: #E8B008;       /* Yellow faction (warm amber) */
  --cobalt: #0072B2;     /* Blue faction (Okabe-Ito blue) */
  --sedge: #009E73;      /* Green faction (Okabe-Ito bluish green, not pure green, for deuteranopia safety) */

  /* ---- Brand color tokens: faction ink variants ---- */
  /* These darker versions of the faction colors are safe for use as text */
  /* on light (Chalk) surfaces, meeting WCAG AA contrast ratio of 4.5:1 */
  --ember-ink: #B8300F;  /* Ember darkened for text use */
  --gold-ink: #876704;   /* Gold darkened for text use */
  --cobalt-ink: #005E94; /* Cobalt darkened for text use */
  --sedge-ink: #007C5A;  /* Sedge darkened for text use */

  /* ---- Light "Chalk" theme surface and text tokens (default) ---- */
  /* These semantic tokens map to the light theme values by default. */
  /* The dark theme (Section 2) overrides these same custom properties. */
  --surface: var(--chalk);    /* Main background color for screens and panels */
  --raised: #FFFCF4;          /* Slightly elevated surface (cards, buttons, topbar) */
  --sunken: #EAE4D4;          /* Slightly recessed surface (input fields, stat pills) */
  --text: var(--nightfall);   /* Primary text color */
  --muted: #4C574F;           /* Secondary/subdued text color */
  --border: #C9C2B0;          /* Border and divider color */
  --focus: var(--cobalt);     /* Focus ring color for keyboard/accessibility navigation */
  --map-bg: #CFC4A6;          /* Background color behind the hex map (muted plytop, like a tabletop) */
  --hex-line: #16211B;        /* Hex grid outline color (Nightfall) */
  --on-accent: var(--chalk);  /* Text color when placed on a dark or vivid fill */

  /* ---- Semantic status colors ---- */
  /* These alias the faction ink hues for UI feedback (success/warning/danger/info). */
  /* They always appear alongside an icon and text, never as color alone, so they */
  /* are never confused with faction ownership indicators. */
  --danger: var(--ember-ink);   /* Error and destructive action color */
  --warning: var(--gold-ink);   /* Warning and caution color */
  --success: var(--sedge-ink);  /* Success and confirmation color */
  --info: var(--cobalt-ink);    /* Informational and neutral status color */

  /* ---- Scrim (overlay backdrop) ---- */
  /* Semi-transparent Nightfall used behind modals and the pass-and-play screen */
  --scrim: rgba(22, 33, 27, .62);

  /* ---- Typography tokens ---- */
  /* Display font: Chakra Petch for headings, labels, and UI chrome */
  /* Body font: Atkinson Hyperlegible for readable body text at arm's length */
  --font-display: "Chakra Petch", "Segoe UI", sans-serif;
  --font-body: "Atkinson Hyperlegible", "Segoe UI", sans-serif;

  /* ---- Shape and motion tokens ---- */
  --radius: 6px;      /* Standard border radius for buttons, cards, pills */
  --radius-lg: 12px;  /* Larger border radius for modals, unit cards, buy cards */
  --dur-fast: 120ms;  /* Fast animation duration (button press feedback) */
  --dur-base: 200ms;  /* Base animation duration (transitions, fades) */
  --ease: cubic-bezier(.2, 0, 0, 1);  /* Ease curve: fast start, smooth deceleration */

  /* ---- App container layout ---- */
  /* The app fills the entire viewport as a fixed-position flex column. */
  /* This ensures it works as a full-screen web app on mobile. */
  position: fixed;
  inset: 0;
  z-index: 0;
  display: flex;
  flex-direction: column;

  /* ---- Base typography ---- */
  font-family: var(--font-body);
  font-size: 16px;     /* Minimum 16px per brand rules for body text readability */
  line-height: 1.5;

  /* ---- Base colors ---- */
  background: var(--surface);
  color: var(--text);

  /* ---- Touch and scroll behavior ---- */
  overscroll-behavior: none;    /* Prevent pull-to-refresh and scroll chaining */
  overflow: hidden;             /* The app manages its own scrolling per section */
  user-select: none;            /* Prevent accidental text selection during gameplay */
  -webkit-user-select: none;    /* Safari/iOS version of user-select */
  -webkit-text-size-adjust: 100%;  /* Prevent iOS from auto-enlarging text in landscape */
}


/* ================================================================
   SECTION 2: DARK THEME ("TABLE AT NIGHT")
   ================================================================
   When the .theme-dark class is added to #app, these overrides
   swap the light Chalk surfaces for deep green-black tones. The
   terrain and faction colors stay the same; only the surfaces, text,
   and a few semantic colors shift. The dark theme name "table at
   night" reflects the game's bar-table origin.
   ================================================================ */

#app.theme-dark {
  --surface: #101914;   /* Deep green-black main background */
  --raised: #182420;    /* Slightly lighter elevated surface */
  --sunken: #0B120E;    /* Darker recessed surface */
  --text: var(--chalk); /* Light text on dark backgrounds */
  --muted: #A9B3AB;     /* Subdued text, lighter for dark background readability */
  --border: #2C3A31;    /* Subtle green-tinted borders */
  --map-bg: #0C130F;    /* Very dark map background */
  --hex-line: #0B120E;  /* Hex outlines blend into the dark map */

  /* Semantic status colors lifted for visibility on dark surfaces */
  --danger: #E85C3A;          /* Ember lifted brighter for dark backgrounds */
  --warning: var(--gold);     /* Full Gold is readable on dark surfaces */
  --success: var(--sedge);    /* Full Sedge is readable on dark surfaces */
  --info: #3D9AD1;            /* Cobalt lifted brighter for dark backgrounds */

  /* Slightly more opaque scrim on dark theme for better modal contrast */
  --scrim: rgba(4, 8, 6, .68);
}


/* ================================================================
   SECTION 3: GLOBAL RESETS AND BASE ELEMENT STYLES
   ================================================================
   These rules reset default browser styles for all elements inside
   #app and set up consistent base styling for interactive elements.
   Everything is scoped to #app to avoid affecting the host page.
   ================================================================ */

/* Reset box-sizing, margin, and padding for all elements inside the app */
#app,
#app * {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  -webkit-tap-highlight-color: transparent;  /* Remove the blue/gray tap highlight on mobile */
}

/* Reset buttons to inherit font styles and remove default browser button appearance */
#app button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
  touch-action: manipulation;  /* Disable double-tap zoom on buttons for faster response */
}

/* Reset input and select elements to inherit font styles from the app */
#app input,
#app select {
  font: inherit;
}

/* Show a visible focus ring on buttons and inputs when navigated via keyboard */
/* This uses :focus-visible so the ring only appears for keyboard navigation, */
/* not for mouse/touch clicks */
#app button:focus-visible,
#app input:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* Text selection highlight uses Gold background with Nightfall text */
#app ::selection {
  background: var(--gold);
  color: var(--nightfall);
}


/* ================================================================
   SECTION 4: BUTTON STYLES
   ================================================================
   The .btn class is the base button style used throughout the app.
   Variants (.primary, .danger, .ghost) modify the fill and border.
   Size variants (.small, .big) adjust padding and font size.
   All buttons have a minimum touch target of 44px height per
   mobile accessibility guidelines.
   ================================================================ */

/* Base button style: raised surface with border, centered content */
#app .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;                          /* Space between icon and label inside the button */
  background: var(--raised);
  color: var(--text);
  border-radius: var(--radius);
  padding: 10px 15px;
  font-weight: 700;
  border: 1.5px solid var(--border);
  min-height: 44px;                  /* Minimum touch target height for accessibility */
  transition: transform var(--dur-fast) var(--ease), background var(--dur-fast);
}

/* Button press feedback: scale down slightly when pressed */
#app .btn:active {
  transform: scale(.97);
}

/* Primary button: Nightfall fill with Chalk text, the main call-to-action style */
#app .btn.primary {
  background: var(--nightfall);
  border-color: var(--nightfall);
  color: var(--chalk);
}

/* Primary button in dark theme uses Field green instead of Nightfall */
/* because Nightfall is too close to the dark theme background */
#app.theme-dark .btn.primary {
  background: var(--field);
  border-color: var(--field);
  color: var(--chalk);
}

/* Danger button: Ember red fill for destructive actions like surrendering */
#app .btn.danger {
  background: var(--ember);
  border-color: var(--ember);
  color: #fff;
}

/* Ghost button: transparent background, border only, for secondary actions */
#app .btn.ghost {
  background: transparent;
}

/* Disabled button: reduced opacity and non-interactive */
#app .btn:disabled {
  opacity: .4;
  pointer-events: none;
}

/* Small button variant: reduced padding and font size for compact UI areas */
#app .btn.small {
  padding: 6px 11px;
  font-size: 13px;
  min-height: 34px;
}

/* Big button variant: full-width with larger padding, used in menu lists */
#app .btn.big {
  width: 100%;
  padding: 15px;
  font-size: 17px;
}


/* ================================================================
   SECTION 5: HEADINGS AND TEXT STYLES
   ================================================================
   Typography rules for headings (h2, h3) and helper text classes.
   Headings use the display font (Chakra Petch) while body text
   uses Atkinson Hyperlegible for maximum readability.
   ================================================================ */

/* Section headings: display font, bold, balanced wrapping */
#app h2 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 22px;
  letter-spacing: .02em;
  margin-bottom: 12px;
  color: var(--text);
  text-wrap: balance;  /* Prevent awkward single-word last lines in headings */
}

/* Sub-section labels: small uppercase display font, muted color */
/* Used for category labels like "RESOURCES", "UNITS", "ACTIONS" */
#app h3 {
  font-family: var(--font-display);
  font-size: 12.5px;
  margin: 16px 0 8px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .12em;  /* Wide tracking per brand rules for short uppercase labels */
  font-weight: 600;
}

/* Muted helper text: smaller, subdued color for descriptions and hints */
#app .muted {
  color: var(--muted);
  font-size: 13.5px;
}

/* Small text utility class */
#app .small {
  font-size: 12.5px;
}

/* Bold text emphasis */
#app b,
#app strong {
  font-weight: 700;
}


/* ================================================================
   SECTION 6: GENERIC SCREEN LAYOUT
   ================================================================
   The .screen class is used for full-page views like the main menu,
   setup screen, rules reference, and game log. Each screen scrolls
   vertically and constrains its content to a comfortable max-width.
   ================================================================ */

/* Scrollable full-height screen container */
#app .screen {
  flex: 1;
  overflow-y: auto;
  padding: 24px 18px 40px;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Constrain content within screens to a comfortable reading width */
#app .screen > * {
  max-width: 660px;
  width: 100%;
}

/* Vertically and horizontally centered layout for splash/menu screens.
   "safe center" keeps the content centered when it fits, but falls back to
   top-aligned (and therefore fully scrollable) when the content is taller than
   the screen — otherwise plain centering clips the top out of reach, which hid
   the share code and its Copy button on the lobby. */
#app .center {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: safe center;
  text-align: center;
  gap: 15px;
  min-height: 100%;
}

/* Brand mark image on the title screen: the hex/triangle logo */
#app .brandmark {
  width: 108px;
  height: 108px;
  filter: drop-shadow(0 4px 10px rgba(22, 33, 27, .28));  /* Subtle Nightfall shadow */
}

/* The remaining-characters count beside the chat box. Quiet until the limit is
   close, so it informs without nagging. */
#app .ochat-character-count {
  text-align: right;
  font-size: 11px;
  opacity: .55;
  padding: 2px 4px 0 0;
}

#app .ochat-character-count.is-nearly-full {
  opacity: 1;
  font-weight: 700;
  color: #d08a3a;
}

/* The training progress bar. A plain track and fill — the number beneath it is
   the precise answer; this is just the shape of the run so far. */
#app .training-progress-track {
  height: 12px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .22);
  overflow: hidden;
}

#app .training-progress-fill {
  height: 100%;
  background: var(--gold);
  transition: width 120ms linear;
}

/* The player's own creature, shown above the lobby title so they can see at a
   glance which team they are playing. */
#app .lobby-team-emblem {
  display: flex;
  justify-content: center;
  margin-bottom: 6px;
}


/* ------------------------------------------------------------
   FEEDBACK TO THE GAME'S CREATOR
   ------------------------------------------------------------ */

/* The quiet link under the in-game menu. Deliberately understated: always
   reachable, but not competing with the things a player opened the menu to do.
   Still a full-width tap target, so it is comfortable on a phone. */
#app .menu-footnote-row {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid rgba(128, 128, 128, .22);
  text-align: center;
}

#app .menu-footnote-link {
  background: none;
  border: 0;
  color: inherit;
  opacity: .6;
  font: inherit;
  font-size: 13px;
  text-decoration: underline;
  cursor: pointer;
  /* Roomy enough to tap accurately on a phone even though it reads as a link. */
  padding: 10px 12px;
  width: 100%;
}

#app .menu-footnote-link:hover,
#app .menu-footnote-link:focus-visible {
  opacity: 1;
}

/* The category chips wrap onto as many lines as they need, so a longer label
   never pushes the row off the side of a narrow phone. */
#app .seg.feedback-category-row {
  flex-wrap: wrap;
}

/* The message box. Sized in relative units and free to grow taller, so it holds a
   real paragraph on a desktop without overflowing a phone. */
#app .feedback-message-box {
  width: 100%;
  min-height: 7em;
  max-height: 40vh;
  resize: vertical;
  line-height: 1.4;
  /* A textarea defaults to the browser's monospace face, which would make this the
     one box in the game typed in a different font from every other. */
  font: inherit;
}

/* Matches the chat counter exactly — same role, so the same look. */
#app .feedback-character-count {
  text-align: right;
  font-size: 11px;
  opacity: .55;
  padding: 2px 4px 0 0;
}

#app .feedback-character-count.is-nearly-full {
  opacity: 1;
  font-weight: 700;
  color: #d08a3a;
}

/* The creator's inbox. Scrolls within itself so a long list never stretches the
   dialog past the screen. */
#app .feedback-inbox-list {
  max-height: 55vh;
  overflow-y: auto;
  text-align: left;
}

#app .feedback-entry {
  padding: 10px 0;
  border-bottom: 1px solid rgba(128, 128, 128, .22);
}

#app .feedback-entry-heading {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  align-items: baseline;
  margin-bottom: 4px;
}

/* A player's own words. `pre-wrap` keeps the line breaks they typed, and the
   break rules stop one very long unbroken word widening the whole dialog. */
#app .feedback-entry-message {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin-bottom: 4px;
}

/* The bottom line of one message: when it arrived on the left, the delete button on
   the right. They wrap onto separate lines rather than squashing on a narrow phone. */
#app .feedback-entry-footer {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 10px;
  align-items: center;
  justify-content: space-between;
}

/* The server password box above the list. Stacked label over field so the
   explanation is readable at any width. */
#app .feedback-delete-password {
  display: flex;
  flex-direction: column;
  gap: 3px;
  text-align: left;
  margin-bottom: 8px;
}

#app .feedback-delete-password input {
  width: 100%;
}

/* "Delete all" sits on its own line under the list, right-aligned and away from the
   harmless Refresh and Close buttons below it. */
#app .feedback-delete-all-row {
  display: flex;
  justify-content: flex-end;
  margin-top: 8px;
}

/* Its label stays on one line — "🗑 Delete / all" split over two rows reads as a
   broken button. */
#app .feedback-delete-all-button {
  white-space: nowrap;
}

/* The second half of "delete everything": the question and its two answers. Tinted
   so it is plainly a warning and not just another row of buttons. */
#app .feedback-delete-all-confirm {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  text-align: left;
  font-size: 13px;
  padding: 8px 10px;
  border-radius: 8px;
  background: rgba(208, 138, 58, .14);
}

/* Anything the feedback dialogs need to warn about — a refused send, a refused
   delete. One class rather than the same inline colour written out twice. */
#app .feedback-warning-text {
  font-size: 13px;
  color: #d08a3a;
  text-align: left;
  margin-bottom: 8px;
}

/* The trophy above the result screen. Deliberately large — this is the moment the
   game has been building to, and a small emoji read as an afterthought. */
#app .victory-trophy {
  font-size: clamp(96px, 30vw, 168px);
  line-height: 1;
  text-align: center;
  margin-bottom: 4px;
  /* A soft glow so it reads as a prize rather than a pasted-in character. */
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, .38));
}

/* One player's line in the final standings. Every player appears, so the winners
   are marked rather than being the only ones present. */
#app .final-standing {
  margin-bottom: 8px;
}

/* Winners get their card lifted out of the list with the gold edge. */
#app .final-standing.is-winner {
  border-color: var(--gold);
  border-width: 2px;
}

/* The finishing position, as a small numbered chip. */
#app .final-standing-place {
  min-width: 22px;
  font-weight: 800;
  opacity: .65;
}

#app .final-standing-crown {
  margin-left: 4px;
}

/* The score sits hard right so every player's total lines up in a column. */
#app .final-standing-score {
  margin-left: auto;
  font-weight: 800;
  white-space: nowrap;
}

#app .final-standing-breakdown {
  margin-top: 6px;
}

#app .title-hero {
  font-family: var(--font-display);
  font-size: 52px;
  letter-spacing: .06em;
  line-height: 1;
  font-weight: 700;
  color: var(--text);
  text-wrap: balance;
}

/* The settings gear pinned to the top-right corner of the hub screens. A small
   round button that stays clear of the centred content and out of the way.
   It floats well in from both edges so it never looks squashed against the side
   of the screen, and the inset grows a little on wider screens. */
#app .settings-gear {
  position: fixed;
  top: clamp(16px, 2.5vw, 28px);
  right: clamp(16px, 2.5vw, 28px);
  z-index: 40;
  width: 42px;
  height: 42px;
  padding: 0;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  line-height: 1;
  background: var(--surface);
  border: 1.5px solid var(--border);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
  cursor: pointer;
}
#app .settings-gear:hover { background: var(--sunken); }

/* The brand logo (the same mark used as the browser-tab icon), shown on the
   entry screens. It scales with the viewport and never overflows its column. */
/* The logo on a screen. This is the BARE mark — transparent, no tile behind it —
   so it sits on the page rather than looking like an app icon pasted onto it. The
   app-icon files keep their opaque dark tile, which is right in a launcher grid
   and wrong here.

   The mark's hexagon is drawn in ink, which would vanish against the dark theme,
   so the dark theme swaps to the chalk-framed copy. Both keep the team-coloured
   pips; only the frame changes. */
#app .brand-logo {
  display: block;
  width: clamp(84px, 26vw, 132px);
  height: auto;
  max-width: 100%;
  margin: 0 auto 10px;
}

#app.theme-dark .brand-logo {
  content: url("brand/logo/eod-mark-on-dark.svg");
}

/* A smaller inline variant for section headers (e.g. the Multiplayer landing) */
#app .brand-logo.compact {
  width: clamp(60px, 18vw, 84px);
  margin-bottom: 6px;
}

/* Tagline text below the title: uppercase display font in Gold ink */
#app .tagline {
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--gold-ink);  /* Gold ink variant is text-safe on Chalk background */
  font-size: 14px;
}

/* In dark theme, use full Gold color since it is readable on dark surfaces */
#app.theme-dark .tagline {
  color: var(--gold);
}

/* Subtitle text: smaller muted description below the tagline */
#app .subtitle {
  color: var(--muted);
  font-size: 14.5px;
  max-width: 380px;
  line-height: 1.5;
}

/* Vertical list of menu buttons on the title screen */
#app .menu-list {
  display: flex;
  flex-direction: column;
  gap: 11px;
  width: min(330px, 88vw);  /* Constrained width that fits phone screens */
  margin-top: 12px;
}


/* ================================================================
   SECTION 7: CARDS, ROWS, AND FORM FIELDS
   ================================================================
   Reusable layout components: .card for content boxes, .row for
   horizontal flex layouts, .field for form input groups, and .seg
   for segmented button pickers (like player count selection).
   ================================================================ */

/* Card: elevated content container with rounded corners and subtle shadow */
#app .card {
  background: var(--raised);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 15px;
  margin-bottom: 12px;
  box-shadow: 0 1px 3px rgba(22, 33, 27, .10);  /* Very subtle Nightfall shadow */
}

/* Horizontal flex row for inline layouts */
#app .row {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Allow row items to wrap onto multiple lines */
#app .row.wrap {
  flex-wrap: wrap;
}

/* Spread row items to opposite ends (space-between) */
#app .spread {
  justify-content: space-between;
}

/* Form field container: label on top, input below */
#app .field {
  margin-bottom: 13px;
}

/* ---- Team selection row on the New Game screen ----
   One row per seat: a tappable creature emblem on the left that chooses the team,
   and that seat's team name plus player-name box on the right. */
#app .team-setup-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* The emblem button. Sized as a comfortable tap target on a phone, and filled with
   the team's own colour so the choice is obvious at a glance. */
#app .team-choice-button {
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 2px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}
#app .team-choice-button:hover { filter: brightness(1.08); }

/* The name column grows to fill whatever space is left beside the emblem. */
#app .team-choice-name {
  flex: 1 1 auto;
  min-width: 0;
}

/* The team's creature name, sitting just above that seat's player-name box. */
#app .team-choice-label {
  display: block;
  font-family: var(--font-display);
  font-size: 11.5px;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .08em;
  margin-bottom: 3px;
}

/* Form field labels: small uppercase display font */
#app .field label {
  display: block;
  font-family: var(--font-display);
  font-size: 11.5px;
  color: var(--muted);
  margin-bottom: 6px;
  text-transform: uppercase;
  letter-spacing: .1em;
  font-weight: 600;
}

/* Text input fields: surface-colored background with border */
#app .field input[type=text] {
  width: 100%;
  background: var(--surface);
  color: var(--text);
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  padding: 11px 12px;
}

/* Text inputs on the multiplayer sign-in and lobby screens */
#app .textin {
  background: var(--surface);
  color: var(--text);
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  padding: 11px 12px;
  font-size: 15px;
}

/* Segmented button group: a row of toggle buttons for multi-choice options */
/* Used for player count picker, map size selector, and similar controls */
#app .seg {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

/* Individual segment button: surface-colored with border */
#app .seg button {
  background: var(--surface);
  border: 1.5px solid var(--border);
  color: var(--text);
  border-radius: var(--radius);
  padding: 9px 13px;
  font-weight: 700;
  min-height: 40px;
  font-family: var(--font-display);
  letter-spacing: .01em;
}

/* Active/selected segment button: Nightfall fill with Chalk text */
#app .seg button.on {
  background: var(--nightfall);
  border-color: var(--nightfall);
  color: var(--chalk);
}

/* In dark theme, active segment uses Field green instead of Nightfall */
#app.theme-dark .seg button.on {
  background: var(--field);
  border-color: var(--field);
}


/* ================================================================
   SECTION 8: FACTION CHIPS (COLOR + GLYPH INDICATORS)
   ================================================================
   Faction chips are small colored squares containing a faction glyph
   (spear, sun, wave, or sprout SVG). Per the CVD (Color Vision
   Deficiency) accessibility rule, faction color never appears as a
   bare dot — it always includes the glyph so colorblind players can
   distinguish factions by shape as well as color.

   Three sizes: default (20px), .sm small (16px), .lg large (26px).
   ================================================================ */

/* Base faction chip: small colored square with the faction glyph SVG inside */
#app .chip {
  width: 20px;
  height: 20px;
  border-radius: 5px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid rgba(22, 33, 27, .35);  /* Semi-transparent Nightfall border */
  flex: 0 0 auto;                              /* Prevent chip from shrinking in flex layouts */
}

/* Faction glyph SVG inside the chip */
#app .chip svg {
  width: 13px;
  height: 13px;
  display: block;
}

/* Small chip variant: used in turn order dots and compact layouts */
#app .chip.sm {
  width: 16px;
  height: 16px;
  border-radius: 4px;
}

#app .chip.sm svg {
  width: 10px;
  height: 10px;
}

/* Large chip variant: used in setup screen faction picker */
#app .chip.lg {
  width: 26px;
  height: 26px;
  border-radius: 6px;
}

#app .chip.lg svg {
  width: 17px;
  height: 17px;
}


/* ================================================================
   SECTION 9: IN-GAME LAYOUT GRID
   ================================================================
   During gameplay, the screen is divided into a CSS Grid with three
   areas: the top bar (player info, resources, phases), the map
   (SVG hex grid with pan/zoom), and the side panel (tile details,
   unit actions, build options).

   On phones (< 940px): vertical stack with bottom sheet.
   On desktop (>= 940px): map fills the left, side panel on the right.
   ================================================================ */

/* Main game layout grid: fills the entire viewport */
#app .gamegrid {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;  /* Top bar, map (fills remaining space), side panel */
  grid-template-columns: 100%;
  grid-template-areas: "top" "map" "side";
}

/* ---- Top bar: player info, resources, and phase indicators ---- */

/* Top bar container: raised surface above the map */
#app .topbar {
  grid-area: top;
  background: var(--raised);
  border-bottom: 1.5px solid var(--border);
  z-index: 5;  /* Sits above the map SVG */
}

/* Player color band: a thin 4px-tall bar showing the current player's faction color */
#app .pcband {
  height: 6px;
  width: 100%;
}

/* Inner padding container for the top bar content */
#app .tb-inner {
  padding: 7px 10px 6px;
  display: flex;
  flex-direction: column;  /* Stack vertically on mobile; overridden to row on desktop */
  gap: 6px;
}

/* First line of the top bar: round indicator, player name, turn order dots, menu button */
#app .tb-line1 {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;  /* Allow flex children to shrink below their content size */
}

/* Round number pill: "ROUND 3" indicator styled as a compact pill */
#app .roundpill {
  font-family: var(--font-display);
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text);
  background: var(--sunken);
  border: 1.5px solid var(--border);
  padding: 5px 11px;
  border-radius: 999px;          /* Fully rounded pill shape */
  white-space: nowrap;
  font-variant-numeric: tabular-nums;  /* Fixed-width digits so the pill does not jump as round numbers change */
  letter-spacing: .04em;
}

/* Turn timer pill: the per-turn countdown, shown only when a turn timer is on.
   It sits beside the round pill and turns red as the seconds run low. */
#app .turntimer {
  font-family: var(--font-display);
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text);
  background: var(--sunken);
  border: 1.5px solid var(--border);
  padding: 5px 11px;
  border-radius: 999px;          /* Fully rounded pill shape, matching the round pill */
  white-space: nowrap;
  font-variant-numeric: tabular-nums;  /* Fixed-width digits so the countdown does not jump each second */
  letter-spacing: .04em;
}

/* The final seconds: flip the timer to an alarming red so time pressure is clear */
#app .turntimer.urgent {
  color: #fff;
  background: #c0392b;
  border-color: #c0392b;
}

/* Turn banner: shows current player name and phase subtitle */
#app .turnbanner {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 17px;
}

/* Faction color dot next to the player name — a bold circle that makes the
   active player instantly recognizable when passing the device */
#app .turnbanner .dot {
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  flex-shrink: 0;
  border: 2px solid var(--border);
}

/* Player name inside the turn banner: truncates with ellipsis if too long */
#app .turnbanner .nm {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Phase subtitle text inside the turn banner */
#app .turnbanner .sub {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 13px;
  color: var(--muted);
  white-space: nowrap;
}

/* Turn order indicator: a row of small faction chips showing who plays in what order */
#app .orderdots {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* Non-current players in the turn order are dimmed */
#app .orderdots .chip {
  opacity: .5;
}

/* The current player's chip in the turn order has full opacity and a focus ring */
#app .orderdots .chip.now {
  opacity: 1;
  box-shadow: 0 0 0 2px var(--focus);
}

/* Players who have already taken their turn are heavily dimmed */
#app .orderdots .chip.done {
  opacity: .25;
}

/* Icon button: small square button used for the menu hamburger and zoom controls */
#app .iconbtn {
  background: var(--sunken);
  border: 1.5px solid var(--border);
  color: var(--text);
  border-radius: var(--radius);
  width: 40px;
  height: 36px;
  font-size: 16px;
  line-height: 1;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* SVG icon inside icon buttons */
#app .iconbtn svg {
  width: 20px;
  height: 20px;
}

/* ---- Resource bar: horizontal scrolling row of resource counts ---- */

/* Container for the resource pills; wraps to multiple lines on narrow screens */
#app .resbar {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

/* Individual resource pill: icon + count, like "🌾 12".
   Sized generously so the resource totals are easy to read at a glance on both
   desktop and phone screens. */
#app .res {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--sunken);
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  padding: 4px 10px;
  font-size: 16px;
  font-weight: 700;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;  /* Fixed-width digits so pills do not jump when numbers change */
}

/* Resource icon inside the pill */
#app .res .ic {
  width: 22px;
  height: 22px;
  display: inline-block;
}

/* Goal resource pill (victory condition tracker): highlighted with Barrel border */
#app .res.goal {
  border-color: var(--barrel);
  color: var(--barrel);
}

/* Goal pill in dark theme uses Plytop (lighter wood) for visibility */
#app.theme-dark .res.goal {
  color: var(--plytop);
  border-color: #5a4a30;
}

/* ---- Phase bar: shows the four turn phases with active/done states ---- */

/* Container for the phase indicator pills */
#app .phasebar {
  display: flex;
  gap: 5px;
}

/* Individual phase pill: small uppercase label like "PRODUCE", "BATTLE", etc. */
#app .phasebar .ph {
  flex: 1;
  text-align: center;
  font-family: var(--font-display);
  font-size: 12px;
  padding: 8px 2px 7px;
  border-radius: var(--radius);
  background: var(--sunken);
  color: var(--muted);
  border: 1.5px solid var(--border);
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
}

/* Phase number circle inside the phase pill */
/* The little numbered circle before each phase label. box-sizing keeps the border
   inside the 18px so the ring is truly round, and line-height:1 stops the display
   font's own tall line box from pushing the digit off-centre inside it — flex
   centring alone cannot correct for that. */
#app .phasebar .ph .n {
  display: inline-flex;
  box-sizing: border-box;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-size: 10px;
  line-height: 1;
  font-variant-numeric: tabular-nums;   /* 1 and 2 occupy the same width */
}

/* Active phase: Nightfall fill with Chalk text (highlighted as current) */
#app .phasebar .ph.on {
  background: var(--nightfall);
  color: var(--chalk);
  border-color: var(--nightfall);
}

/* Active phase in dark theme uses Field green */
#app.theme-dark .phasebar .ph.on {
  background: var(--field);
  border-color: var(--field);
}

/* Completed phase: Field-colored text and border to indicate done */
#app .phasebar .ph.done {
  color: var(--field);
  border-color: var(--field);
}

/* Completed phase in dark theme uses Sedge green */
#app.theme-dark .phasebar .ph.done {
  color: var(--sedge);
  border-color: #2f5a3d;
}


/* ================================================================
   SECTION 10: MAP AREA (SVG HEX GRID VIEWPORT)
   ================================================================
   The map area contains the SVG hex grid and supports touch
   pan/zoom via pointer events (handled in game.js). A subtle
   hex-pattern background texture sits behind the SVG.
   ================================================================ */

/* Map viewport: fills the middle grid area, clips overflow for pan/zoom */
#app .mapwrap {
  grid-area: map;
  position: relative;
  overflow: hidden;
  touch-action: none;  /* Disable browser touch gestures; pan/zoom handled by game.js */
  background: var(--map-bg);
}

/* Subtle repeating hex pattern overlay behind the map SVG */
/* This creates the faint tabletop texture visible around the edges of the map */
#app .mapwrap::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: .5;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='103.92'%3E%3Cg fill='none' stroke='%2316211B' stroke-opacity='0.07' stroke-width='2'%3E%3Cpath d='M0 0 L30 51.96 L0 103.92'/%3E%3Cpath d='M60 0 L30 51.96 L60 103.92'/%3E%3Cpath d='M0 0 H60'/%3E%3Cpath d='M0 51.96 H60'/%3E%3Cpath d='M0 103.92 H60'/%3E%3C/g%3E%3C/svg%3E");
}

/* ---- Founding allocation: board preview + resource controls ----
   Side by side on a wide browser, stacked (and scrollable) on a narrow phone, so
   the map preview never collapses into a thin sliver. */

/* Let this layout use more width than the usual centred column so a real
   two-pane view fits comfortably on a browser. */
#app .screen > .alloc-layout {
  max-width: min(1040px, 100%);
}

/* Default (narrow / phone): a simple vertical stack. The parent .screen scrolls. */
#app .alloc-layout {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
}

/* The read-only board preview. A firm minimum height keeps it usable no matter
   how short the window gets. */
#app .alloc-map {
  width: 100%;
  height: min(42vh, 340px);
  min-height: 240px;
  border: 1.5px solid var(--border);
  border-radius: 12px;
}

/* The resource-allocation controls (the card plus the continue button). */
#app .alloc-controls {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Wide browser: put the map and the controls side by side. The map fills the
   left, the controls take a comfortable fixed-ish column on the right. */
@media (min-width: 760px) {
  #app .alloc-layout {
    flex-direction: row;
    align-items: stretch;
  }
  #app .alloc-map {
    flex: 1 1 58%;
    min-width: 0;
    height: auto;          /* stretch to match the controls' height */
    min-height: 380px;
  }
  #app .alloc-controls {
    flex: 1 1 42%;
    min-width: 280px;
    max-width: 460px;
  }
}

/* The main map SVG element: positioned absolutely, fills the entire map wrapper */
#app .mapwrap svg#mapsvg {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  max-width: none;  /* Override any max-width inherited from parent */
  display: block;
}

/* ---- Zoom controls: floating buttons in the top-right corner of the map ---- */

/* Zoom control container */
#app .zoomctl {
  position: absolute;
  right: 10px;
  top: 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 4;  /* Above the map SVG but below the top bar */
}

/* Individual zoom button (+ and -) */
#app .zoomctl button {
  width: 40px;
  height: 40px;
  border-radius: var(--radius);
  background: var(--raised);
  border: 1.5px solid var(--border);
  color: var(--text);
  font-size: 18px;
  font-weight: 800;
  box-shadow: 0 1px 3px rgba(22, 33, 27, .2);  /* Subtle shadow for floating appearance */
  display: flex;
  align-items: center;
  justify-content: center;
}

/* SVG icon inside zoom buttons */
#app .zoomctl button svg {
  width: 20px;
  height: 20px;
}


/* ================================================================
   SECTION 11: SIDE PANEL (TILE DETAILS, ACTIONS, BUILD OPTIONS)
   ================================================================
   The side panel displays context-sensitive information about the
   selected tile or unit, available actions, and the build menu.
   On mobile it appears as a bottom sheet; on desktop it becomes a
   fixed sidebar on the right side of the screen.
   ================================================================ */

/* Side panel container: anchored to the bottom of the grid on mobile */
#app .sidepanel {
  grid-area: side;
  background: var(--raised);
  border-top: 1.5px solid var(--border);
  z-index: 5;
  display: flex;
  flex-direction: column;
}

/* Scrollable content area inside the side panel */
#app .panelscroll {
  overflow-y: auto;
  padding: 2px 13px 13px;
}

/* ---- The bottom sheet's resize handle ----
   The bar across the top of the panel on a phone. It replaced a pseudo-element that
   only LOOKED like a handle and could not be touched, which is why the panel could
   not be moved at all. Now it is a real button: tap it to step through the sizes,
   or drag it up and down (the drag is wired up in main.js).

   It is deliberately taller than the little grey bar it draws, so there is a
   comfortable thumb-sized target rather than a four-pixel line. */
#app .sheet-handle {
  flex: 0 0 auto;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 26px;
  padding: 0;
  background: none;
  border: 0;
  cursor: grab;
  /* Stops the browser claiming the gesture as a page scroll, so the drag handler
     below actually receives the pointermove events on a phone. */
  touch-action: none;
}

/* While the handle is being held, the cursor says so. */
#app .sheet-handle:active {
  cursor: grabbing;
}

/* The little grey bar people recognise as "you can pull this". */
#app .sheet-handle-grip {
  display: block;
  width: 44px;
  height: 4px;
  border-radius: 2px;
  background: var(--border);
}

/* Bigger and darker while the handle is hovered, held or keyboard-focused, so it is
   obvious that this is something you can use rather than a piece of decoration. */
#app .sheet-handle:hover .sheet-handle-grip,
#app .sheet-handle:focus-visible .sheet-handle-grip,
#app .sheet-handle:active .sheet-handle-grip {
  background: var(--muted);
  width: 56px;
}

/* Remove top margin from the first heading inside the panel */
#app .panelscroll h3:first-child {
  margin-top: 2px;
}

/* ---- How tall the bottom sheet is (phones and narrow windows only) ----
   render.js puts exactly one of these three classes on the panel, and the handle
   above swaps between them. They are inside a max-width query on purpose: from
   940px up the panel is a full-height sidebar with nothing to resize, and an
   unscoped `.panel-height-normal` would out-specify the desktop rule that removes
   the height limit, leaving the sidebar stuck at half a screen. */
@media (max-width: 939px) {

  /* Collapsed: the handle alone. The map then gets nearly the whole screen, which
     is the point of the whole control — being able to actually look at the board on
     a phone without the panel eating half of it. */
  #app .sidepanel.panel-height-collapsed .panelscroll,
  #app .sidepanel.panel-height-collapsed .logpeek {
    display: none;
  }

  /* Normal: the size the sheet has always opened at, a little under half the
     screen, so the map still has the larger share. */
  #app .sidepanel.panel-height-normal {
    max-height: 47vh;
  }

  /* Tall: most of the screen, for reading a long build list or the game log
     without scrolling a small window. A strip of map is deliberately left showing
     so it never feels like the board has gone away. */
  #app .sidepanel.panel-height-tall {
    max-height: 82vh;
  }
}

/* Log peek area: hidden on mobile, shown on desktop (see desktop media query below) */
/* ---- Game log peek (bottom of the side panel) ----
   Shown on every screen size. The header is the only click target that opens the
   full-log modal; the entry list underneath scrolls independently so the player can
   read back through recent events in place, on desktop and on a phone alike. */
#app .logpeek {
  display: flex;
  flex-direction: column;
  min-height: 0;                 /* Lets the entry list shrink so it can scroll */
  border-top: 1.5px solid var(--border);
  font-size: 12.5px;
  color: var(--muted);
  line-height: 1.55;
}

/* The clickable header. A plain button so it is keyboard reachable. */
#app .logpeek-header {
  flex: 0 0 auto;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: 9px 16px 4px;
  font-family: var(--font-display);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--muted);
  cursor: pointer;
}
#app .logpeek-header:hover { color: var(--text); }

/* The scrollable list of recent entries. overscroll-behavior stops a scroll that
   reaches the end of the list from continuing on and scrolling the page behind it. */
#app .logpeek-entries {
  flex: 1 1 auto;
  min-height: 0;
  max-height: 150px;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;   /* Momentum scrolling on iOS */
  padding: 0 16px 10px;
}

/* Individual log entry lines */
#app .logpeek-entry {
  padding: 1.5px 0;
}

/* ---- Desktop layout override (screens 940px and wider) ---- */
/* On wider screens, the side panel moves from a bottom sheet to a right sidebar, */
/* the top bar becomes a horizontal row, and a log peek area becomes visible. */
@media (min-width: 940px) {

  /* Switch to a two-column grid: map on the left, 410px sidebar on the right */
  #app .gamegrid {
    grid-template-columns: minmax(0, 1fr) 410px;
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-areas: "top top" "map side";
  }

  /* Side panel becomes a full-height right sidebar instead of a bottom sheet */
  #app .sidepanel {
    max-height: none;             /* Remove the mobile height limit */
    border-top: 0;                /* Remove top border (was the bottom-sheet edge) */
    border-left: 1.5px solid var(--border);  /* Add left border to separate from map */
  }

  /* More generous padding inside the sidebar on desktop */
  #app .panelscroll {
    flex: 1;
    padding: 16px 18px;
  }

  /* Hide the resize handle on desktop, where the panel is a full-height sidebar and
     there is no bottom sheet to pull up or down. */
  #app .sheet-handle {
    display: none;
  }

  /* The sidebar is taller on desktop, so the log can show more history at once. */
  #app .logpeek-entries {
    max-height: 190px;
  }

  /* On desktop, the top bar inner layout becomes a horizontal row */
  #app .tb-inner {
    flex-direction: row;
    align-items: center;
    gap: 16px;
    padding: 9px 16px;
  }

  /* First line of the top bar gets a fixed width on desktop */
  #app .tb-line1 {
    flex: 0 0 auto;
    min-width: 300px;
  }

  /* Resource bar fills remaining space between player info and phase bar */
  #app .resbar {
    flex: 1;
  }

  /* Phase bar gets a fixed width on desktop for consistent layout */
  #app .phasebar {
    flex: 0 0 360px;
  }
}


/* ================================================================
   SECTION 12: ACTION ROW (CONTEXTUAL BUTTONS BELOW TILE/UNIT INFO)
   ================================================================
   A horizontal row of action buttons shown in the side panel, such
   as "Move", "Attack", "Repair", "End Turn".
   ================================================================ */

/* Action button row: horizontal flex with equal-width buttons */
#app .actionrow {
  display: flex;
  gap: 8px;
  margin-top: 11px;

  /* Three buttons will not fit side by side on a narrow phone, so they wrap onto a
     second line instead of shrinking into unreadable slivers. */
  flex-wrap: wrap;
}

/* The plain-English note under an action row explaining why a button is greyed out
   (for example a unit that already moved this turn cannot be repaired). It spans the
   full width so a whole sentence reads normally rather than wrapping every word. */
#app .repair-blocked-reason {
  margin-top: 7px;
  line-height: 1.4;
}

/* Each action button fills equal width */
#app .actionrow .btn {
  flex: 1;
}


/* ================================================================
   SECTION 13: MAP SELECTION AND HIGHLIGHT STROKES (SVG)
   ================================================================
   Visual indicators drawn on the hex map SVG to show which tile is
   selected and which tiles are valid targets for movement or attack.
   Also includes the "ready dot" pulsing animation for units that
   have not yet acted this turn.
   ================================================================ */

/* Selected hex outline: bright Chalk stroke, drawn on top of the hex fill */
#app .hexsel {
  stroke: var(--chalk);
  stroke-width: 3;
  fill: none;
  pointer-events: none;  /* Selection outlines should not intercept clicks */
}

/* Highlighted hex outline: used for valid move/attack targets */
#app .hex-highlight {
  stroke-width: 2.4;
  pointer-events: none;
}

/* ---- Cursor hover highlight on the board ----
   Every tile lifts slightly and gains a gold outline while the cursor is over it,
   for a livelier, more interactive board. This is purely a mouse affordance, so it
   is gated to devices that actually have a hover-capable pointer — touch screens
   (which fake a lingering :hover after a tap) are left untouched. Because every
   decoration on the board carries pointer-events:none, the base tile polygon is
   what the cursor lands on, so this reliably highlights the WHOLE tile even under
   a unit or a flag. */
@media (hover: hover) {
  #app #mapsvg [data-act="map-tile-tap"] {
    transition: filter 0.12s ease, stroke 0.12s ease, stroke-width 0.12s ease;
  }
  #app #mapsvg [data-act="map-tile-tap"]:hover {
    filter: brightness(1.12) saturate(1.06);
    stroke: var(--gold);
    stroke-width: 3;
    cursor: pointer;
  }
}

/* Ready dot: pulsing animation on units that have not been used this turn */
/* The dot fades in and out to draw the player's attention to available units */
#app .readydot {
  animation: readypulse 1.3s ease-in-out infinite;
}

/* Ready dot pulsing keyframes: fades to 35% opacity and back */
@keyframes readypulse {
  50% { opacity: .35; }
}

/* ---- Team picker (choosing which creature a seat plays as) ----
   A grid of all six teams. Two columns on a phone, three once there is room. */
#app .team-option-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 6px;
}
@media (min-width: 460px) {
  #app .team-option-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* One selectable team. */
#app .team-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 10px 6px;
  background: var(--sunken);
  border: 2px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  font: inherit;
  color: inherit;
}
#app .team-option:hover { border-color: var(--muted); }

/* The team currently played by this seat. */
#app .team-option.chosen {
  border-color: var(--gold);
  background: color-mix(in srgb, var(--gold) 14%, transparent);
}

/* The coloured square holding the creature emblem. */
#app .team-option-emblem {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: var(--radius);
}

#app .team-option-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12.5px;
  text-align: center;
  line-height: 1.2;
}

/* Note shown on a team another seat already plays — picking it swaps the two. */
#app .team-option-note {
  font-size: 10px;
  color: var(--muted);
  text-align: center;
  line-height: 1.2;
}

/* ---- Terms of Service agreement on the create-account form ----
   A checkbox and its wrapping label, sized so the whole line is comfortably
   tappable on a phone rather than just the small box itself. */
#app .terms-agreement {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  margin: 4px 0 2px;
  cursor: pointer;
  line-height: 1.45;
}

/* The box itself, enlarged from the browser default to a fair touch target. */
#app .terms-agreement input[type="checkbox"] {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  cursor: pointer;
}

/* An inline button styled as a link, for opening the terms from inside a sentence. */
#app .link-button {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--info);
  text-decoration: underline;
  cursor: pointer;
}

/* ---- Unit shapes inside the Rules reference table ----
   The rules show each unit as the shape it actually is on the board, with its full
   strength inside, rather than a die notation like "d8". */
#app .rules-unit-shape {
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
}

/* Shape and name sit together in the first column of each unit row. */
#app .rules-unit-cell {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* ---- Player-to-player trade grid ----
   Three columns shared by the headings and every resource row, so the "you give"
   and "you get" captions sit exactly above the steppers they describe. */
#app .trade-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 7px 10px;
  margin-bottom: 4px;
}

/* Column captions sit centred over their stepper. */
#app .trade-column-heading {
  text-align: center;
}

/* Resource name and icon; truncates rather than pushing the steppers off a phone. */
#app .trade-resource-label {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The running "what each side would hold afterwards" figure beside each stepper. */
#app .trade-projected-total {
  min-width: 22px;
  text-align: right;
  font-variant-numeric: tabular-nums;   /* so the preview does not jitter */
}

/* ---- Initiative rolling interstitial ----
   The step every player passes through before the turn order is revealed. Each
   player's real six-sided die is thrown here, using the same die component the
   battles use, so a die looks and lands the same everywhere in the game. This is
   also the slot a rewarded video advert will occupy. */
#app .initiative-roll-stage {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 18px 0 12px;
  width: min(340px, 90vw);
}

/* One player's line: their colour, their name, and their die at the end. The name
   takes the slack so every die lines up in a column however long the names are. */
#app .initiative-roll-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 12px;
  background: var(--surface);
  border: 1px solid rgba(40, 35, 25, .18);
  border-radius: var(--radius);
}

#app .initiative-roll-name {
  flex: 1;
  font-weight: 700;
  /* A long name shortens rather than pushing the die off the row. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A player's dice: their first throw, then one more for each round they had to
   roll again to be separated from somebody. Kept on one line and never allowed to
   shrink, so a tie-break stays readable on a phone. */
#app .initiative-roll-dice {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

/* The arrow between a die and the re-roll that followed it: "…and then rolled
   this". Quiet, because the dice are the point. */
#app .initiative-reroll-arrow {
  opacity: .45;
  font-size: 12px;
}

/* The row for a player who took initiative on the BID, before anyone rolled. It is
   given the gold edge so it reads as the settled result at the top of the list,
   rather than as one more competitor waiting on a die. */
#app .initiative-roll-row.is-bid-winner {
  border-color: var(--gold);
  border-width: 2px;
}

/* What that player won with, in place of a die. */
#app .initiative-bid-won {
  flex-shrink: 0;
  font-weight: 700;
  font-size: 13px;
  color: var(--gold-ink);
  text-align: right;
}

/* The line between the settled winner and the players still rolling, so the two
   parts of the screen are not read as one list. */
#app .initiative-roll-divider {
  text-align: center;
  font-size: 12px;
  opacity: .6;
  margin: 2px 0;
}

/* Active player's Capital: a soft halo in that team's colour, breathing gently so
   it is obvious whose turn it is without the board looking busy. The ring is drawn
   in the team colour by the renderer; this only animates its glow. */
#app .capital-active-glow {
  animation: capitalglow 2.1s ease-in-out infinite;
}

/* The halo swells and fades between a tight bright ring and a wider soft one. */
@keyframes capitalglow {
  0%, 100% { opacity: 0.95; filter: drop-shadow(0 0 2px currentColor); }
  50%      { opacity: 0.45; filter: drop-shadow(0 0 7px currentColor); }
}

/* Disable the looping board animations for users who prefer reduced motion.
   The ready dot and the capital halo both stay visible, just perfectly still. */
@media (prefers-reduced-motion: reduce) {
  #app .readydot,
  #app .capital-active-glow {
    animation: none;
  }
}


/* ================================================================
   SECTION 14: UNIT CARD AND TILE INFO DISPLAY
   ================================================================
   The unit card shows details about a selected unit (icon, type,
   divisions, stats). The list and listitem classes are used for
   various scrollable lists in the side panel.
   ================================================================ */

/* Unit card: horizontal layout with unit glyph icon and stat text */
#app .unitcard {
  display: flex;
  gap: 12px;
  align-items: center;
  background: var(--sunken);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 11px;
}

/* Unit glyph icon: the faction-colored unit type SVG */
#app .unitglyph {
  width: 50px;
  height: 50px;
  flex: 0 0 auto;  /* Prevent the icon from shrinking */
}

/* Stat line text: small muted text for unit statistics like "ATK 2 / DEF 2" */
#app .statline {
  font-size: 12.5px;
  color: var(--muted);
}

/* Bold values within stat lines use the primary text color for emphasis */
#app .statline b {
  color: var(--text);
}

/* Generic vertical list container */
#app .list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Individual list item: horizontal layout with icon, text, and optional action */
#app .listitem {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--sunken);
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  padding: 9px 11px;
  font-size: 13.5px;
}

/* Grow class: fills remaining space in a flex row (used for list item text) */
#app .listitem .grow {
  flex: 1;
  min-width: 0;  /* Allow text to shrink and truncate if needed */
}

/* Cost indicator text: Barrel-colored bold text showing resource costs */
#app .cost {
  color: var(--barrel);
  font-size: 12.5px;
  white-space: nowrap;
  font-weight: 700;
}

/* Cost text in dark theme uses lighter Plytop color for visibility */
#app.theme-dark .cost {
  color: var(--plytop);
}


/* ================================================================
   SECTION 15: BUILD PHASE BUY GRID
   ================================================================
   During the Build phase, purchasable items (units, roads, workers)
   are displayed in a 2-column grid of "buy cards". Each card shows
   the item name, cost, and current stock (division pips).
   ================================================================ */

/* Two-column grid for buy cards */
#app .buygrid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

/* Individual buy card: vertical layout showing item info */
#app .buycard {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  text-align: left;
  background: var(--sunken);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 11px;
  min-height: 78px;
  transition: transform var(--dur-fast) var(--ease);
}

/* Press feedback on enabled buy cards */
#app .buycard:not(:disabled):active {
  transform: scale(.97);
}

/* Enabled buy cards get a Barrel border to indicate they are purchasable */
#app .buycard:not(:disabled) {
  border-color: var(--barrel);
}

/* Disabled buy cards (cannot afford or inventory full) are dimmed */
#app .buycard:disabled {
  opacity: .5;
}

/* Buy card item name in display font */
#app .buycard b {
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 700;
}

/* Division pips row: small dots showing how many of this unit type you own */
#app .buycard .pips {
  display: flex;
  gap: 3px;
  margin-left: auto;
}

/* Individual pip dot: gray by default */
#app .buycard .pip {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--border);
}

/* Filled pip: green dot indicating an owned unit of this type */
#app .buycard .pip.f {
  background: var(--field);
}

/* Lost pip: under the graveyard rule this slot holds a unit destroyed earlier and
   can never be rebuilt, so it shows a dimmed skull instead of a dot. Sized a little
   larger than a dot because it carries a glyph rather than a plain fill. */
#app .buycard .pip.lost {
  width: auto;
  height: auto;
  border-radius: 0;
  background: none;
  font-size: 10px;
  line-height: 1;
  opacity: 0.55;
  filter: grayscale(1);
}

/* Explanatory text on disabled buy cards (e.g., "No gold" or "Inventory full") */
#app .buycard .why {
  font-size: 11px;
  color: var(--muted);
}

/* Top line of the buy card: name and pip dots side by side */
#app .buycard .topline {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 6px;
}


/* ================================================================
   SECTION 16: STEPPER CONTROLS
   ================================================================
   Stepper controls are +/- button pairs with a numeric value between
   them. Used for resource allocation during founding (splitting 24
   starting resources), capital gold/food split, and trade amounts.
   ================================================================ */

/* Stepper container: horizontal layout with minus button, value, plus button */
#app .stepper {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* Stepper increment/decrement buttons */
#app .stepper button {
  width: 34px;
  height: 34px;
  border-radius: var(--radius);
  background: var(--raised);
  color: var(--text);
  border: 1.5px solid var(--border);
  font-size: 17px;
  font-weight: 800;
}

/* Stepper current value display */
#app .stepper .val {
  min-width: 30px;
  text-align: center;
  font-weight: 800;
  font-size: 16px;
  font-variant-numeric: tabular-nums;  /* Fixed-width digits prevent layout jumps */
}

/* Mini stepper variant: smaller buttons and value for compact layouts */
#app .stepper.mini button {
  width: 28px;
  height: 28px;
  font-size: 14px;
}

#app .stepper.mini .val {
  min-width: 22px;
  font-size: 14px;
}


/* ================================================================
   SECTION 17: MODAL DIALOGS
   ================================================================
   Modal dialogs appear on top of a semi-transparent scrim for
   confirmations, battle results, trade proposals, and the in-game
   menu. The .fresh class triggers a one-time entrance animation.
   ================================================================ */

/* Modal scrim: full-screen semi-transparent overlay behind the modal */
#app .modal-scrim {
  position: absolute;
  inset: 0;
  background: var(--scrim);
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

/* Modal content box: elevated card centered on the screen */
#app .modal {
  background: var(--raised);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 19px;
  width: min(440px, 94vw);   /* Constrained to 440px or 94% of viewport width */
  max-height: 85vh;           /* Prevent the modal from overflowing the viewport */
  overflow-y: auto;           /* Scroll inside the modal if content is tall */
  box-shadow: 0 12px 40px rgba(22, 33, 27, .4);  /* Deep Nightfall shadow for elevation */
}

/* Entrance animation: slides up slightly and fades in */
/* Only plays once when the modal first appears (via .fresh class) */
/* Subsequent re-renders of the same modal do not re-trigger the animation */
#app .modal-scrim.fresh .modal {
  animation: modalin .18s var(--ease);
}

/* Modal entrance keyframes: starts slightly below and transparent, ends in place */
@keyframes modalin {
  from {
    transform: translateY(8px) scale(.985);
    opacity: 0;
  }
}

/* Disable the modal entrance animation for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
  #app .modal-scrim.fresh .modal {
    animation: none;
  }
}

/* Modal heading gets slightly less bottom margin than the standard h2 */
#app .modal h2 {
  margin-bottom: 10px;
}


/* ================================================================
   SECTION 18: PASS-AND-PLAY SCREEN
   ================================================================
   Between player turns in hot-seat mode, a full-screen "pass the
   phone" screen covers the game to prevent the next player from
   seeing the previous player's board state. Shows the brand mark
   and the next player's name.
   ================================================================ */

/* Full-screen pass-and-play privacy cover */
#app .pass-screen {
  position: absolute;
  inset: 0;
  z-index: 44;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  background: var(--surface);
  text-align: center;
  padding: 24px;
}

/* Smaller brand mark on the pass screen */
#app .pass-screen .brandmark {
  width: 68px;
  height: 68px;
}


/* ================================================================
   SECTION 19: DICE AND BATTLE RESULTS
   ================================================================
   Combat in Empire of Dice uses physical dice rolls. Each die is
   rendered as a small square showing the roll value. Hits (rolls
   that scored a casualty) are highlighted in Ember red with a
   pop animation.
   ================================================================ */

/* Container for a set of dice: wraps if there are many */
/* ============================================================
   BATTLE DICE — a thrown handful that tumbles and settles
   ============================================================
   The game is called Empire of Dice, so a battle shows real dice with real pips,
   one per division, and they land like dice rather than appearing as finished
   numbers.

   Each die runs the same two-part animation: a tumble that scales it up, spins it
   and blurs the face, then a settle where it drops onto the table with a small
   bounce. The stagger comes from an animation-delay set per die in render.js, so a
   ten-die roll lands as a scatter rather than all at once.

   It is deliberately CSS-only. A JavaScript timer driving this would be left half
   finished whenever a re-render arrived mid-roll — which, in an online game
   receiving polls every couple of seconds, is most of the time.
   ============================================================ */

#app .diceset {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  justify-content: center;
  /* Room for a die at the top of its tumble, so it is not clipped mid-throw. */
  padding: 8px 0 4px;
}

#app .die-roll {
  --die-size: 34px;
  width: var(--die-size);
  height: var(--die-size);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 7px;
  background: linear-gradient(155deg, #fdfcf7 0%, #ece7d9 62%, #d9d2c0 100%);
  border: 1px solid rgba(40, 35, 25, .28);
  /* A contact shadow plus a soft inner highlight, so it reads as a solid object
     resting on the card rather than a flat square drawn on it. */
  box-shadow:
    0 3px 6px rgba(24, 20, 12, .34),
    inset 0 1px 0 rgba(255, 255, 255, .85),
    inset 0 -2px 3px rgba(90, 78, 52, .22);
  animation: die-tumble var(--die-tumble-duration, 620ms) cubic-bezier(.22, .9, .28, 1.02) both;
  will-change: transform;
}

/* The pip grid. Three by three, with each pip placed by row and column from
   render.js, which is how the six faces are described in one small table. */
#app .die-face {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  gap: 2px;
  width: 74%;
  height: 74%;
}

#app .die-pip {
  align-self: center;
  justify-self: center;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #2a2620;
  /* Pips are drilled into the face, so they carry a shadow on the inside. */
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, .55), 0 1px 0 rgba(255, 255, 255, .5);
}

/* A hit. The die is stained the alarm colour and its pips go pale, so hits are
   readable at a glance without the count depending on colour alone — the tally
   under each side states the number in words as well. */
#app .die-roll.is-hit {
  background: linear-gradient(155deg, #c8452f 0%, #a5301d 62%, #7d2314 100%);
  border-color: rgba(60, 12, 6, .5);
}
#app .die-roll.is-hit .die-pip {
  background: #ffeae4;
  box-shadow: inset 0 1px 1px rgba(90, 20, 10, .5);
}

/* A hit gets a brief flare exactly as it lands — the delay is set per die so it
   fires on contact, not while the die is still in the air. */
#app .die-roll.is-hit::after {
  content: '';
  position: absolute;
  inset: -5px;
  border-radius: 11px;
  border: 2px solid var(--danger, #c8452f);
  opacity: 0;
  animation: die-hit-flare 460ms ease-out both;
  animation-delay: var(--die-settle-delay, 620ms);
  pointer-events: none;
}
#app .die-roll { position: relative; }

/* The throw: the die comes in high, small and spinning, then drops and overshoots
   very slightly before settling — the little bounce is what sells it as landing. */
@keyframes die-tumble {
  0%   { transform: translateY(-38px) scale(.55) rotate(-160deg); opacity: 0; }
  35%  { opacity: 1; }
  62%  { transform: translateY(3px) scale(1.06) rotate(12deg); }
  80%  { transform: translateY(-2px) scale(.99) rotate(-3deg); }
  100% { transform: translateY(0) scale(1) rotate(0deg); opacity: 1; }
}

@keyframes die-hit-flare {
  0%   { opacity: 0; transform: scale(.7); }
  40%  { opacity: .95; transform: scale(1.12); }
  100% { opacity: 0; transform: scale(1.3); }
}

/* Anyone who has asked their system to reduce motion gets the result without the
   throw: the dice simply fade in, still staggered so the reading order holds. */
@media (prefers-reduced-motion: reduce) {
  #app .die-roll { animation: die-fade-in 200ms ease-out both; }
  #app .die-roll.is-hit::after { animation: none; opacity: 0; }
  @keyframes die-fade-in { from { opacity: 0; } to { opacity: 1; } }
}

/* The running count under each side, so the outcome can be checked at a glance
   without recounting the dice. */
/* Held back until the dice have landed, so the count arrives as the answer to the
   roll rather than alongside it. The delay is set per battle in render.js, since it
   depends on how many dice were thrown. */
#app .battle-side-tally,
#app .outcome {
  animation: result-arrives 340ms ease-out both;
}

/* A battle that has already been thrown on this screen is drawn at rest: no
   tumble, no delayed reveal, just the result. Without this, every re-render
   restarted the throw — and an online board re-renders every couple of seconds,
   so the dice never stopped rolling and the outcome never stayed put. */
#app .die-roll.has-settled,
#app .battle-side-tally.has-settled,
#app .outcome.has-settled {
  animation: none;
  opacity: 1;
  transform: none;
}

/* The hit flash is part of the throw, so it does not replay either. */
#app .die-roll.has-settled.is-hit::after {
  animation: none;
  opacity: 0;
}
@keyframes result-arrives {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Reduced motion still gets the delay — the point is not spoiling the roll — but
   without the movement. */
@media (prefers-reduced-motion: reduce) {
  @keyframes result-arrives { from { opacity: 0; } to { opacity: 1; } }
}

#app .battle-side-tally {
  margin-top: 4px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Phones get slightly smaller dice so a ten-division roll still fits on one or two
   rows instead of pushing the outcome off the bottom of the card. */
@media (max-width: 520px) {
  #app .die-roll { --die-size: 29px; }
  #app .die-pip { width: 5px; height: 5px; }
}

#app .battle-side {
  margin-bottom: 11px;
}

/* Label for each battle side (e.g., "ATTACKER (Infantry, 10 divisions)") */
#app .battle-side .lbl {
  font-size: 13px;
  font-weight: 700;
  margin-bottom: 3px;
}

/* Battle outcome summary text (e.g., "Attacker conquers the tile!") */
#app .outcome {
  font-size: 15px;
  font-weight: 600;
  margin: 11px 0;
  padding: 11px 13px;
  border-radius: var(--radius);
  background: var(--sunken);
  border: 1.5px solid var(--border);
}


/* ================================================================
   SECTION 20: GAME LOG
   ================================================================
   The full game log displays a chronological list of all events
   that have happened during the game. Each entry is a single line
   separated by a thin border.
   ================================================================ */

/* Game log container: scrollable column of log entries */
#app .logbox {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text);
  max-height: 55vh;
  overflow-y: auto;
}

/* Individual log entry line */
#app .logbox div {
  padding: 3px 0;
  border-bottom: 1px solid var(--border);
  color: var(--muted);
}


/* ================================================================
   SECTION 21: TOAST NOTIFICATIONS
   ================================================================
   Toast messages appear briefly at the bottom of the screen to
   communicate quick feedback like "Not enough resources" or
   "Unit moved". They fade in and out via opacity transitions.
   ================================================================ */

/* Toast notification: centered at the bottom of the map area */
#app #toast {
  position: absolute;
  left: 50%;
  bottom: calc(47vh + 14px);         /* Positioned just above the side panel on mobile */
  transform: translateX(-50%);        /* Center horizontally */
  background: var(--nightfall);
  color: var(--chalk);
  border: 1.5px solid var(--field);   /* Green border for visibility */
  padding: 11px 17px;
  border-radius: var(--radius);
  font-size: 14px;
  font-weight: 700;
  z-index: 80;                        /* Above modals and side panels */
  max-width: 88vw;
  text-align: center;
  opacity: 0;                         /* Hidden by default; .show class makes it visible */
  transition: opacity .2s;
  pointer-events: none;               /* Toast does not block interaction */
  box-shadow: 0 6px 20px rgba(22, 33, 27, .4);
}

/* Show state: make the toast visible */
#app #toast.show {
  opacity: 1;
}

/* On desktop, reposition the toast to account for the sidebar width */
@media (min-width: 940px) {
  #app #toast {
    left: calc((100% - 410px) / 2);   /* Center within the map column, not the full viewport */
    bottom: 26px;
  }
}


/* ================================================================
   SECTION 22: RULES REFERENCE TABLE
   ================================================================
   The in-game rules reference shows unit stats in a table format
   with bordered cells and uppercase column headers.
   ================================================================ */

/* Stats table: full-width with collapsed borders */
#app table.stats {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}

/* Table cells: centered text with consistent padding and tabular numbers */
#app table.stats th,
#app table.stats td {
  border: 1.5px solid var(--border);
  padding: 5px 6px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Table headers: sunken background with small uppercase display font */
#app table.stats th {
  background: var(--sunken);
  color: var(--muted);
  font-family: var(--font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
}


/* ================================================================
   SECTION 23: VICTORY SCREEN
   ================================================================
   Displayed when a player wins the game. Shows the winner's name
   in large display text with game statistics (tiles controlled,
   units remaining, resources saved) in a row of stat cards.
   ================================================================ */

/* Victory heading: large display font announcing the winner */
#app .victory {
  font-family: var(--font-display);
  font-size: 34px;
  font-weight: 700;
  letter-spacing: .03em;
  color: var(--text);
  text-wrap: balance;
}

/* Row of stat cards on the victory screen */
#app .statrow {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}

/* Individual stat card: shows a number and a label */
#app .stat {
  background: var(--raised);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 9px 17px;
  text-align: center;
}

/* Stat value: large Field-colored number */
#app .stat b {
  display: block;
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--field);
  font-variant-numeric: tabular-nums;
}

/* Stat value in dark theme uses Sedge green */
#app.theme-dark .stat b {
  color: var(--sedge);
}

/* Stat label: small uppercase text below the number */
#app .stat span {
  font-family: var(--font-display);
  font-size: 10.5px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .1em;
}


/* ================================================================
   SECTION 24: TUTORIAL SPOTLIGHT AND COACH POPOVER
   ================================================================
   The tutorial system highlights specific UI elements with a golden
   spotlight border and displays instructional text in a "coach"
   popover. The spotlight creates a visual cutout effect using a
   massive box-shadow that covers the rest of the screen.
   ================================================================ */

/* Tutorial spotlight: a positioned rectangle with a Gold border */
/* The 9999px box-shadow covers everything outside the spotlight area */
#app #coachspot {
  /* Fixed (viewport-relative) so the spotlight lines up with the target's
     on-screen rectangle even when the page has scrolled — getBoundingClientRect
     returns viewport coordinates, which only match a fixed element. */
  position: fixed;
  z-index: 47;
  pointer-events: none;
  border: 2.5px solid var(--gold);
  border-radius: var(--radius);
  box-shadow:
    0 0 0 9999px var(--scrim),                /* Scrim covers everything outside the spotlight */
    0 0 18px rgba(232, 176, 8, .55);           /* Gold glow around the spotlight edge */
  transition: left .25s var(--ease), top .25s var(--ease), width .25s var(--ease), height .25s var(--ease);
}

/* Disable spotlight movement animation for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
  #app #coachspot {
    transition: none;
  }
}

/* Coach popover: the instructional text box shown during the tutorial */
/* Positioned at the bottom of the map area by default */
#app .coach {
  /* Fixed (viewport-relative) to match the spotlight — see #coachspot above.
     The JS positioner overrides top/left inline; this is the resting default. */
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(47vh + 12px);
  z-index: 48;
  width: min(440px, 94vw);
  /* Slightly see-through, with the backdrop blurred behind it, so the board is
     never fully hidden by the coaching text. The blur keeps the words crisp while
     the shapes behind stay legible — a flat opaque panel blocked the very thing
     the step was describing. */
  background: color-mix(in srgb, var(--raised) 88%, transparent);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  border: 2px solid var(--gold);      /* Gold border to match the spotlight */
  border-radius: var(--radius-lg);
  padding: 13px 15px;
  box-shadow: 0 12px 34px rgba(22, 33, 27, .45);
  pointer-events: none;               /* Only the buttons inside are interactive */
  transition: opacity .15s ease;
}

/* Holding the pointer over the card fades it right down, so the player can peek at
   whatever it is sitting in front of without having to dismiss the step. */
#app .coach:hover {
  opacity: 0.25;
}

/* The Skip button stays fully solid even while the card is faded, so it never
   becomes hard to find or hit. */
#app .coach:hover button {
  opacity: 1;
}

/* Buttons inside the coach popover should be clickable */
#app .coach button {
  pointer-events: auto;
}

/* Dock-top variant: positions the coach at the top of the screen when the */
/* spotlight is near the bottom, to avoid covering the highlighted element */
#app .coach.dock-top {
  top: 74px;
  bottom: auto;
}

/* On desktop, position the coach to the left of the sidebar */
@media (min-width: 940px) {
  #app .coach:not(.dock-top) {
    left: auto;
    right: 426px;                      /* Right edge of map area + offset */
    transform: none;
    bottom: 18px;
  }
}

/* Step label: "STEP 3 OF 12" indicator at the top of the coach */
#app .coach .steplbl {
  font-family: var(--font-display);
  font-size: 10.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--gold-ink);
  font-weight: 700;
  margin-bottom: 6px;
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Step label in dark theme uses full Gold color */
#app.theme-dark .coach .steplbl {
  color: var(--gold);
}

/* Hex progress indicator: a row of small hex-shaped dots showing tutorial progress */
#app .coach .hexprog {
  display: inline-flex;
  gap: 3px;
  margin-left: auto;
}

/* Individual hex progress dot: clipped into a hexagon shape */
#app .coach .hexprog i {
  width: 9px;
  height: 10px;
  display: inline-block;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  background: var(--border);          /* Unfilled step: gray */
}

/* Filled hex progress dot: green to indicate a completed step */
#app .coach .hexprog i.f {
  background: var(--field);
}

/* Coach body text: the instructional message */
#app .coach .body {
  font-size: 15px;
  line-height: 1.5;
}

/* Bold text within coach instructions uses Field green for emphasis */
#app .coach .body b {
  color: var(--field);
  font-weight: 700;
}

/* Bold text in dark theme uses Sedge green */
#app.theme-dark .coach .body b {
  color: var(--sedge);
}

/* Button row at the bottom of the coach: spread to opposite ends */
#app .coach .row {
  margin-top: 11px;
  justify-content: space-between;
}


/* ================================================================
   SECTION 25: INLINE ICONS AND RESOURCE QUANTITIES
   ================================================================
   Small inline SVG icons used within text spans to show resource
   types, unit types, and other game concepts. These icons align
   with the surrounding text baseline.
   ================================================================ */

/* Resource icon: inline icon that sits within body text (side-panel costs, build
   menus, upgrade rows). Sized well above the text beside it so every resource is
   easy to read at a glance. */
#app .ri {
  width: 1.55em;
  height: 1.55em;
  display: inline-block;
  vertical-align: -0.34em;  /* Sit the taller icon centred on the text baseline */
}

/* Unit icon: slightly larger inline icon for unit type indicators */
#app .uicon {
  width: 24px;
  height: 24px;
  display: inline-block;
  vertical-align: -0.32em;
}

/* Tiny unit icon variant: used in compact list displays */
#app .uicon.tiny {
  width: 17px;
  height: 17px;
  vertical-align: -0.18em;
}

/* Cost quantity display: inline flex container for "icon + number" pairs */
#app .cq {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  white-space: nowrap;
}

/* Victory star icon: Gold colored (used in victory condition displays) */
#app .winstar {
  color: var(--gold);
}

/* Victory star stays Gold in dark theme as well */
#app.theme-dark .winstar {
  color: var(--gold);
}

/* Override auto-sizing of resource icons inside resource pills */
/* This allows the icon to size naturally based on its SVG viewBox */
#app .res .ic {
  width: auto;
  height: auto;
}

/* Resource icon SVG inside a resource pill in the top bar: sized large so the six
   resources are legible at a glance on both desktop and phone screens. */
#app .res .ic .ri {
  width: 24px;
  height: 24px;
}


/* ============================================================
   ONLINE PLAY OVERLAY
   The online game's controls live in a slim bar docked to the
   BOTTOM of the screen (status, turn timer, order, chat, leave).
   Docking it as a real bottom bar — and reserving space for it —
   means it never covers the game's own action button the way a
   floating overlay did.
   ============================================================ */

/* The bar is pinned across the bottom of the screen */
#app .online-hud {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  pointer-events: none;   /* gaps pass taps through; children re-enable below */
}

/* Reserve space at the bottom of scrollable game areas so the bar never hides
   the last control (e.g. End Turn / Continue). :has() lets us do this only when
   the bar is actually present. */
#app:has(.online-hud) .panelscroll,
#app:has(.online-hud) .sidepanel .logpeek-entries,
#app:has(.online-hud) .screen {
  padding-bottom: 84px;
}

/* The bar itself. The three parts share one row: the status text takes the
   leftover space and ellipsises, while the timer and the action buttons keep
   their natural size and never shrink — so nothing is ever squeezed onto a
   second line on a narrow phone. */
#app .online-bar {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--surface);
  border-top: 1.5px solid var(--border);
  padding: 8px 12px;
  box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.12);
}

/* When it is YOUR move, tint the whole bar gold so it is unmistakable */
#app .online-bar.myturn {
  background: var(--gold);
  border-top-color: var(--gold);
  color: #16211B;
}

#app .online-bar-status {
  font-weight: 700;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Take the leftover width and be the ONLY part allowed to shrink, so a long
     player name ellipsises instead of pushing the timer onto a new line. */
  flex: 1 1 auto;
  min-width: 0;
}

#app .online-bar-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;   /* the buttons keep their size */
}

/* The prominent "you're on the clock" timer bubble shown in the bar on your turn.
   It never shrinks and never wraps, and its size scales gently with the viewport
   so it stays on one line from the narrowest phone up to desktop. */
#app .online-timer-big {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(15px, 4.4vw, 20px);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  background: #16211B;
  color: #fff;
  border-radius: 999px;
  padding: 4px clamp(9px, 3vw, 14px);
  white-space: nowrap;
  flex-shrink: 0;
}
#app .online-timer-big.urgent { background: #c0392b; }

/* The chat panel floats just above the bar when opened */
#app .online-chat-panel {
  pointer-events: auto;
  background: var(--surface);
  border-top: 1.5px solid var(--border);
  box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.12);
}

/* The scrolling message log */
#app .ochat-log {
  max-height: 34vh;
  overflow-y: auto;
  padding: 8px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* One chat message; my own messages are tinted so they stand out */
#app .ochat-msg {
  font-size: 13px;
  line-height: 1.35;
  word-break: break-word;
}
#app .ochat-msg.mine { color: var(--gold-ink); }

/* The message entry row: a text box and a Send button */
#app .ochat-entry {
  display: flex;
  gap: 6px;
  padding: 8px 10px;
  border-top: 1px solid var(--border);
}
#app .ochat-entry .textin { flex: 1; min-width: 0; }


/* ---- Cross-player proposal card (peer trade / peace offer) ---- */

/* Floats just above the online bar. Full width of the bar, but its inner content
   is capped and centred so it reads well from phone to desktop. */
#app .online-proposal {
  pointer-events: auto;
  box-sizing: border-box;
  width: 100%;
  max-width: 100%;
  background: var(--surface);
  border-top: 1.5px solid var(--gold);
  box-shadow: 0 -4px 14px rgba(0, 0, 0, 0.16);
  padding: 12px clamp(12px, 4vw, 18px);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* The waiting card (my own offer) reads calmer than an offer needing my answer */
#app .online-proposal.waiting { border-top-color: var(--border); }

#app .oprop-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(14px, 4vw, 17px);
  line-height: 1.2;
}

#app .oprop-body { line-height: 1.4; }

/* One "you receive / you give" line in a trade offer */
#app .oprop-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;   /* the icons wrap under the label on a very narrow screen */
}

/* The accept / decline (or cancel) buttons: wrap gracefully if they cannot fit */
#app .oprop-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
#app .oprop-actions .btn { flex: 0 1 auto; }


/* ---- Incoming proposal popup (trade offer / peace offer addressed to me) ----
   A full-screen dimmed backdrop with a centered card, so an offer sent to this
   player is impossible to miss on any device and clearly demands an answer. */
#app .proposal-overlay {
  position: fixed;
  inset: 0;
  z-index: 75;                 /* above the turn-order overlay and the online bar */
  background: var(--scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

/* The centered offer card itself */
#app .proposal-modal {
  box-sizing: border-box;
  width: min(380px, 92vw);
  background: var(--surface);
  border: 1.5px solid var(--gold);
  border-radius: var(--radius-lg);
  box-shadow: 0 12px 44px rgba(0, 0, 0, 0.34);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  text-align: left;
}

/* The accept button in the popup carries the full weight of the decision */
#app .proposal-modal .oprop-actions .btn { flex: 1 1 auto; }


/* ---- Turn-order panel (post-bidding reveal + on-demand view) ---- */

/* Full-screen dimmed backdrop; tapping it dismisses the panel */
#app .turnorder-overlay {
  position: fixed;
  inset: 0;
  z-index: 70;
  background: var(--scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

/* The centred card listing the play order */
#app .turnorder-card {
  background: var(--surface);
  border: 1.5px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 18px;
  width: min(340px, 92vw);
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
  text-align: left;
}

/* One row in the order list */
/* Fixed columns rather than a flex row, so the same thing always sits in the same
   place on every line: position, team dot, name, bid, then any tags. With flex the
   tags on the winning row shoved its bid into the middle of the line while every
   other row's bid sat at the far right, and the numbers never lined up. */
#app .turnorder-row {
  display: grid;
  grid-template-columns: 20px 12px 1fr auto minmax(0, 96px);
  align-items: center;
  gap: 8px;
  padding: 8px 6px;
  border-top: 1px solid var(--border);
  font-weight: 600;
}
#app .turnorder-row.now { background: color-mix(in srgb, var(--gold) 18%, transparent); border-radius: 6px; }
#app .turnorder-pos {
  font-family: var(--font-display);
  font-weight: 800;
  width: 20px;
  text-align: center;
  color: var(--muted);
}
#app .turnorder-name { flex: 1; }
#app .turnorder-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--gold-ink);
}
#app .turnorder-tag.now { color: var(--info); }

/* What a player bid for initiative, shown once the sealed bids are opened. Pushed
   to the right of the name so the numbers line up in a readable column down the
   panel, whatever length the names are. */
#app .turnorder-bid {
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;   /* keeps the column straight */
  color: var(--muted);
}
#app .turnorder-bid .icon { width: 14px; height: 14px; }

/* Both tags share ONE grid cell, so a row with two of them does not push the bid
   column out of line on the rows that have none. */
#app .turnorder-tags {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  text-align: right;
}

/* The creature a player has claimed in the online lobby, shown beside their name.
   Pushed right so the column of team names stays readable however long the
   usernames are. */
#app .lobby-team-name {
  margin-left: auto;
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
}

/* ------------------------------------------------------------
   THE BOT TRAINING PANEL
   ------------------------------------------------------------
   Training is a long, invisible process. These styles exist so a
   run can be READ — what it is doing, what it has learned, and
   whether it is still getting anywhere — rather than watched.
   ------------------------------------------------------------ */

/* The line under the progress bar naming the generation. */
#app .training-headline {
  font-size: 13px;
  margin-top: 6px;
}

/* How many of a resource are in the basket going to the world bank, shown on the
   resource's own button so the basket needs no separate list. */
#app .trade-chosen-count {
  display: inline-block;
  margin-left: 4px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--ember);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
}

/* The row holding the basket's Clear button, pushed to the right so it reads as an
   undo for the row of resources above it rather than another choice. */
#app .trade-basket-row {
  display: flex;
  justify-content: flex-end;
  margin-top: 6px;
}

/* The self-training status block in the developer panel: what the bots are playing
   with, and whether they are improving right now. Boxed and left-aligned so it reads
   as a report on its own rather than more button labels. */
#app .training-background-status {
  text-align: left;
  padding: 8px 10px;
  margin-top: 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  line-height: 1.45;
}

/* The block of headline numbers. Wraps to one column on a narrow phone. */
#app .training-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 6px 12px;
  margin: 10px 0;
}

#app .training-stat {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  padding: 4px 0;
  border-bottom: 1px solid rgba(128, 128, 128, .18);
  font-size: 13px;
}

#app .training-stat-label { opacity: .65; }
#app .training-stat-value { font-weight: 700; }

/* The generation log. Scrolls within itself so a long run never stretches the
   dialog past the screen. */
#app .training-log {
  max-height: 30vh;
  overflow-y: auto;
  margin-bottom: 8px;
  font-variant-numeric: tabular-nums;
}

#app .training-log-row {
  display: grid;
  grid-template-columns: 2.6em 1fr 1fr 1fr 1fr;
  gap: 6px;
  padding: 3px 0;
  font-size: 12px;
  border-bottom: 1px solid rgba(128, 128, 128, .12);
}

#app .training-log-heading {
  opacity: .6;
  font-weight: 700;
  position: sticky;
  top: 0;
  background: var(--surface);
}

/* The "how the games ended" table has three columns rather than five: a wide one for
   the ending's name and two narrow ones for the count and the share. It must come
   AFTER the rule above, which sets the five-column layout at the same specificity —
   the later rule is the one that wins. */
#app .training-victory-table .training-log-row {
  grid-template-columns: 2.6fr 1fr 1fr;
}

/* The ending's name is a sentence, so it reads from the left like one. */
#app .training-victory-name {
  text-align: left;
}

/* A generation that beat everything before it — the moments the run actually
   learned something, so they can be picked out at a glance. */
#app .training-log-row.is-improvement {
  color: var(--gold-ink);
  font-weight: 700;
}

/* The learned weights, so the judgement can be read rather than guessed at. */
#app .training-weight-list {
  max-height: 30vh;
  overflow-y: auto;
  font-variant-numeric: tabular-nums;
}

#app .training-weight-row {
  display: grid;
  grid-template-columns: 1fr 4.5em 4.5em;
  gap: 6px;
  padding: 3px 0;
  font-size: 12px;
  border-bottom: 1px solid rgba(128, 128, 128, .12);
}

/* A long feature name shortens rather than pushing its numbers off the row. */
#app .training-weight-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  opacity: .75;
}

#app .training-weight-value { font-weight: 700; text-align: right; }
#app .training-weight-spread { opacity: .55; text-align: right; }
