/* v5 slicing steps 16-19 (design doc Section 8a): mobile-first layout --
   single column, larger tap targets, native input controls preferred
   over free text wherever the order shape allows it. Desktop gets a
   slightly roomier layout via the min-width media query at the bottom,
   but nothing here is DESIGNED desktop-first and shrunk down. */

* {
  box-sizing: border-box;
}

:root {
  --bg: #121316;
  --surface: #1c1e24;
  --surface-2: #262a33;
  --border: #383d48;
  --text: #e8e9ec;
  --text-dim: #9aa0ad;
  --accent: #7c9cff;
  --accent-text: #0b0d10;
  --danger: #ff6b6b;
  --ok: #5fd68a;
  --radius: 10px;
  --tap: 44px; /* minimum comfortable touch target height */
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #f5f6f8;
    --surface: #ffffff;
    --surface-2: #eef0f4;
    --border: #d7dbe3;
    --text: #1a1c20;
    --text-dim: #5b6270;
    --accent: #3b5bdb;
    --accent-text: #ffffff;
  }
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 16px; /* never smaller -- iOS Safari zooms in on focus if an input's font-size is under 16px */
  line-height: 1.4;
}

header {
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}

header h1 {
  margin: 0;
  font-size: 1.25rem;
}

.whoami {
  margin: 4px 0 0;
  color: var(--text-dim);
  font-size: 0.9rem;
}

.app-version {
  margin: 2px 0 0;
  color: var(--text-dim);
  font-size: 0.75rem;
}

main {
  padding: 12px;
  max-width: 640px;
  margin: 0 auto;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 16px;
}

.card h2 {
  margin-top: 0;
  font-size: 1.05rem;
}

.card h3 {
  font-size: 0.95rem;
  color: var(--text-dim);
  margin-bottom: 8px;
}

/* v6 (design doc Section 4.5): visibly distinct from a plain card,
   reinforcing the deliberate "not a checkbox bolted onto casting" GUI
   decision -- a purple accent border, same purple the diagram's trap dot
   uses, rather than the neutral border every other card gets. Was blue
   (#4d96ff) until real playtesting feedback (2026-07-23) found it too
   close to wizard A's own --accent blue to read as a distinct "trap"
   signal. */
.trap-card {
  border-color: #a855f7;
  border-width: 2px;
}

.list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 12px;
}

.list-empty {
  color: var(--text-dim);
  font-size: 0.9rem;
  font-style: italic;
}

.list-item {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  padding: 10px 12px;
  min-height: var(--tap);
  font-size: 1rem;
  cursor: pointer;
}

.list-item:active {
  transform: scale(0.99);
}

.list-item.selected {
  border-color: var(--accent);
  outline: 2px solid var(--accent);
}

.list-item .meta {
  display: block;
  color: var(--text-dim);
  font-size: 0.85rem;
  margin-top: 2px;
}

/* Shop & Inventory (design doc Section 6/11, 2026-08-01): a catalog row
   needs its name/effect, cost, and Buy button laid out on one line --
   .stat-row's own flex pattern applied on top of .list-item's existing
   background/border/padding, rather than .list-item's default block
   stacking. */
.shop-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.shop-row > span:first-child {
  flex: 1 1 auto;
}

.shop-item-cost {
  color: #ffd23f; /* gold/yellow, same shade the round-scrubber's attack-arrow already introduced */
  font-weight: 700;
  flex: 0 0 auto;
}

button.shop-buy-button {
  background: var(--ok);
  color: #0b0d10;
  border-color: var(--ok);
  font-weight: 600;
  flex: 0 0 auto;
  min-height: auto;
  padding: 6px 14px;
}

/* Order-submission indicator (roster screen, 2026-07-29): left dot is the
   viewer's own submission, right dot the opponent's -- see app.js's
   renderDuelList. Black/dim until submitted, then --ok green. */
.submission-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #000;
  border: 1px solid var(--border);
  margin-left: 4px;
  vertical-align: middle;
}

.submission-dot.submitted {
  background: var(--ok);
  border-color: var(--ok);
}

form {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

label {
  font-size: 0.9rem;
  color: var(--text-dim);
  margin-top: 4px;
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text);
}

.checkbox-label input {
  width: 20px;
  height: 20px;
}

input[type="text"],
input[type="number"],
select,
textarea {
  min-height: var(--tap);
  padding: 8px 10px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  font-size: 1rem;
  width: 100%;
}

button {
  min-height: var(--tap);
  padding: 8px 16px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  font-size: 1rem;
  cursor: pointer;
}

button[type="submit"],
button.primary {
  background: var(--accent);
  color: var(--accent-text);
  border-color: var(--accent);
  font-weight: 600;
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

button.danger {
  background: transparent;
  color: var(--danger);
  border-color: var(--danger);
}

.link-button {
  background: none;
  border: none;
  color: var(--accent);
  padding: 8px 0;
  min-height: auto;
  font-size: 0.95rem;
}

/* v5 slicing steps 17-19: the duel screen */

.turn-picker {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: center;
}

.turn-picker button {
  flex: 0 0 auto;
}

#turn-picker-label,
#round-scrubber-label {
  flex: 1 1 auto;
  text-align: center;
  font-weight: 600;
}

.ring-segment {
  fill: var(--surface-2);
  stroke: var(--border);
  stroke-width: 1;
}

.ring-segment.hole {
  fill: var(--bg);
  stroke: var(--danger);
  stroke-dasharray: 3 2;
}

.entity-dot {
  stroke: var(--surface);
  stroke-width: 1.5;
}

.entity-dot.owner-A {
  fill: var(--accent);
}

.entity-dot.owner-B {
  fill: #ff9f5a;
}

/* v6 (design doc Section 4.5): a still-invisible trap always renders
   purple on its OWNER's own diagram, regardless of which wizard placed
   it -- overrides owner-A/owner-B's color (same specificity, declared
   after) so it reads as "trap," not "my color." Was blue (#4d96ff) until
   real playtesting feedback (2026-07-23) found it too close to wizard
   A's own --accent blue to read as a distinct signal. */
.entity-dot.trap {
  fill: #a855f7;
}

/* Round scrubber eye-candy follow-up (2026-07-31): fades the dot for the
   one step a conjure/sphere is drawn WHILE dead (see app.js's justDied) --
   still readable as "this used to be here," but visibly not the same as
   a live entity. Paired with .death-mark's big X drawn over top. */
.entity-dot.just-died {
  opacity: 0.55;
}

.death-mark {
  font-size: 22px;
  font-weight: 900;
  fill: var(--danger);
  text-anchor: middle;
  pointer-events: none;
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: 3px;
}

/* Appear glow (same follow-up): an owner-colored ring that pulses
   outward and fades behind a freshly-appeared entity's dot, for the one
   step it's new. Answers "what just appeared" the same way the existing
   move-arrows already answer "what moved" -- neither the dots nor the
   arrows previously said anything about summons/deaths on their own. */
.entity-appear-glow {
  fill: none;
  stroke-width: 3;
  pointer-events: none;
  animation: entity-appear-pulse 1.2s ease-out infinite;
}

.entity-appear-glow.owner-A {
  stroke: var(--accent);
}

.entity-appear-glow.owner-B {
  stroke: #ff9f5a;
}

@keyframes entity-appear-pulse {
  from {
    r: 11;
    opacity: 0.9;
  }
  to {
    r: 19;
    opacity: 0;
  }
}

.entity-label {
  font-size: 8px;
  fill: var(--text);
  text-anchor: middle;
  pointer-events: none;
  font-weight: 600;
}

/* Real playtesting request, 2026-07-28: a light watermark of each
   segment's own index, so a player can tell ring/segment apart at a
   glance without hovering. Reworked 2026-08-01 per real user feedback
   ("still cannot see the numbers... too small or faint") -- the previous
   fix chased a THEME token (--text) at a tuned opacity and still wasn't
   legible enough. Replaced with one fixed mid-grey instead of a
   theme-relative value: a mid-grey sits roughly equidistant in contrast
   from both a light and a dark background, so it reads reasonably well
   against EITHER ring-segment fill without needing separate light/dark
   tuning at all. Bumped from 7px to 9px at full opacity (no longer
   faded) -- app.js now also skips drawing this entirely for any segment
   that already holds an entity dot or event icon, so it no longer needs
   to stay deliberately faint to avoid competing with them there. */
.segment-watermark {
  font-size: 9px;
  fill: #8a8a8a;
  text-anchor: middle;
  pointer-events: none;
}

.power-source {
  fill: var(--text);
  stroke: var(--surface-2);
  stroke-width: 2;
}

/* Anger the Source escalation (design doc Section 6, resolved 2026-08-06):
   yellow while Angry, red while Very Angry, black while a Berserk burst
   fired this round -- cosmetic only, driven by view.reservoirEffects +
   this turn/round's own reservoir_attack events (app.js's powerSourceTierClass). */
.power-source.tier-angry {
  fill: #d4b106;
}
.power-source.tier-very-angry {
  fill: #c0392b;
}
.power-source.tier-berserk {
  fill: #111;
  stroke: #c0392b;
}

.event-icon-combat {
  fill: var(--danger);
  font-size: 12px;
  font-weight: 800;
  pointer-events: none;
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: 3px;
}

/* Detonation glyph (round scrubber eye-candy follow-up, 2026-07-31):
   upgraded from a plain letter 'D' to an actual splat/explosion emoji per
   real user feedback -- sized up from the combat 'X' since an emoji
   reads smaller than a bold letter at the same font-size. No fill/stroke
   needed (emoji glyphs render their own color, ignoring `fill`). */
.event-icon-detonation {
  font-size: 18px;
  pointer-events: none;
}

/* Ranged-attack arrow (same follow-up): bold and yellow specifically to
   read as distinct from the thin dashed move-arrows -- an attack, not a
   step. */
.attack-arrow {
  stroke: #ffd23f;
  stroke-width: 3;
  opacity: 0.9;
}

.attack-arrow-head {
  fill: #ffd23f;
}

/* v6 Walls (design doc Section 4.3): a wall piece renders as an extra,
   bolder line just inside its segment's inner-ring boundary -- fill:none
   since arcPath() draws an open arc, not a closed wedge; stroke-width 4
   is noticeably thicker than .ring-segment's own boundary (1) so it reads
   as a real obstruction, not just a slightly bolder grid line. */
.wall-boundary {
  fill: none;
  stroke-width: 4;
  stroke-linecap: round;
}

.wall-boundary.owner-A {
  stroke: var(--accent);
}

.wall-boundary.owner-B {
  stroke: #ff9f5a;
}

/* Sector effects (design doc Section 4.4, v7 Pass 8) -- real playtesting
   request, 2026-07-24: these never rendered on the map before. A tinted
   wedge fill per footprint segment, color keyed off spell name; pointer-
   events disabled so a zone never intercepts clicks meant for the
   segment/entity underneath it. Airborne (Darkness/Fog, fixed in place)
   gets a solid border; grounded (Snare/Oil Slick/Poison Gas, dragged by
   ring rotation) gets a dashed one -- a real tactical difference (whether
   this zone will still be here next turn without you doing anything)
   worth a visual cue of its own, not just color. */
.sector-effect {
  stroke-width: 1.5;
  pointer-events: none;
}

.sector-effect.airborne {
  stroke-dasharray: none;
}

.sector-effect.grounded {
  stroke-dasharray: 5 3;
}

.sector-effect.spell-darkness {
  fill: rgba(20, 16, 36, 0.6);
  stroke: rgba(120, 90, 200, 0.7);
}

.sector-effect.spell-fog {
  fill: rgba(200, 205, 215, 0.4);
  stroke: rgba(150, 160, 175, 0.7);
}

.sector-effect.spell-snare {
  fill: rgba(107, 79, 40, 0.4);
  stroke: rgba(140, 105, 60, 0.8);
}

.sector-effect.spell-oil-slick {
  fill: rgba(35, 30, 12, 0.55);
  stroke: rgba(90, 75, 20, 0.8);
}

.sector-effect.spell-poison-gas {
  fill: rgba(96, 168, 64, 0.4);
  stroke: rgba(80, 140, 50, 0.8);
}

.move-arrow {
  stroke-width: 1.5;
  stroke-dasharray: 4 2;
  opacity: 0.75;
}

.move-arrow.owner-A {
  stroke: var(--accent);
}

.move-arrow.owner-B {
  stroke: #ff9f5a;
}

/* Ring-rotation-caused movement (real user request, 2026-08-02): grey
   instead of the mover's own owner color, so a rotation-carried figure
   reads as distinct from one that moved under its own power -- mutually
   exclusive with owner-A/owner-B (app.js picks one class or the other,
   never both), so no specificity fight with the rules above. */
.move-arrow.rotation {
  stroke: var(--text-dim);
}

/* SVG <marker> content isn't a DOM descendant of the <line> that
   references it via marker-end (it's pulled from <defs> by id), so a
   per-owner CSS selector can't reach it the way it can the line itself
   -- kept a single neutral color rather than fighting that with
   context-fill (inconsistent browser support). */
.move-arrow-head {
  fill: var(--text-dim);
}

/* Diagram container: intentionally does NOT set overflow:hidden or fight
   the page's own pinch-zoom (Section 8a's mobile/device decision -- lean
   on native zoom first, since the diagram is SVG and stays crisp at any
   zoom level, rather than building dedicated zoom controls up front). A
   min-height keeps the card from collapsing before the SVG loads. */
.diagram-wrap {
  min-height: 260px;
  display: flex;
  justify-content: center;
}

.diagram-wrap svg {
  max-width: 100%;
  height: auto;
}

/* Real user feedback (2026-07-19): nothing in the UI showed entity
   health anywhere -- only inferable by parsing the event log's damage
   text. A plain list, own units showing the exact health the filtered
   view already gives them (Section 2a full transparency), everyone
   else showing the qualitative bucket. */
.status-list {
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.88rem;
}

.status-row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  padding: 2px 0;
}

.status-row .label {
  color: var(--text-dim);
}

.status-row.dead .value {
  color: var(--danger);
}

.banner {
  margin: 4px 0 0;
  padding: 10px 14px;
  border-radius: var(--radius);
  font-weight: 600;
  text-align: center;
}

.banner.win {
  background: color-mix(in srgb, var(--ok) 20%, transparent);
  color: var(--ok);
  border: 1px solid var(--ok);
}

.banner.loss {
  background: color-mix(in srgb, var(--danger) 20%, transparent);
  color: var(--danger);
  border: 1px solid var(--danger);
}

.event-log {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 0.92rem;
  max-height: 260px;
  overflow-y: auto;
}

.event-log .event-line {
  padding: 4px 0;
  border-bottom: 1px solid var(--border);
}

/* Round scrubber follow-up (2026-07-31): marks the event lines that
   belong to the currently-viewed round (see renderEventLog's
   highlightRound param) so they stand out from the earlier rounds'
   events still shown above them in the same cumulative list. */
.event-log .event-line-current-round {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-left: 3px solid var(--accent);
  padding-left: 8px;
  font-weight: 600;
}

/* User Manual viewer: renders the real USER_MANUAL.md via the CDN-loaded
   `marked` parser. Tables (stat blocks, the spell roster) routinely have
   more columns than a 640px mobile-first `main` can show -- scrolling the
   TABLE itself horizontally (rather than letting it overflow the page)
   matches this project's existing wide-content convention elsewhere. */
.manual-content h1,
.manual-content h2,
.manual-content h3,
.manual-content h4 {
  margin-top: 1.4em;
  margin-bottom: 0.4em;
}

.manual-content h1:first-child,
.manual-content h2:first-child {
  margin-top: 0;
}

.manual-content p,
.manual-content ul,
.manual-content ol {
  line-height: 1.5;
}

.manual-content code {
  background: var(--surface-2);
  border-radius: 4px;
  padding: 0.1em 0.35em;
  font-size: 0.9em;
}

.manual-content hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 1.5em 0;
}

.manual-content table {
  display: block;
  overflow-x: auto;
  border-collapse: collapse;
  width: max-content;
  max-width: 100%;
  font-size: 0.9rem;
}

.manual-content th,
.manual-content td {
  border: 1px solid var(--border);
  padding: 6px 10px;
  text-align: left;
  white-space: nowrap;
}

.manual-content th {
  background: var(--surface-2);
}

.event-log .event-line:last-child {
  border-bottom: none;
}

.event-log .event-empty {
  color: var(--text-dim);
  font-style: italic;
}

.order-block {
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.order-block:last-of-type {
  border-bottom: none;
}

.order-block h4 {
  margin: 0 0 8px;
  font-size: 0.95rem;
}

/* A single cast or retarget's own fields, nested inside an .order-block
   (which can now hold several -- multiple casts, one block per
   retargetable conjure/sphere). */
.sub-block {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  margin-bottom: 10px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.sub-block-header {
  display: flex;
  justify-content: flex-end;
}

.token-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.token-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 6px 4px 12px;
  font-size: 0.9rem;
}

.token-chip button {
  min-height: 28px;
  min-width: 28px;
  padding: 0;
  border-radius: 50%;
  line-height: 1;
}

.token-add {
  display: flex;
  gap: 8px;
}

.token-add select {
  flex: 1 1 auto;
}

.token-add button {
  flex: 0 0 auto;
}

.order-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.order-actions button {
  flex: 1 1 auto;
}

.hint {
  color: var(--text-dim);
  font-size: 0.85rem;
  margin: 8px 0 0;
}

#cast-fields {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
}

#resolve-card button {
  width: 100%;
}

.status-bar {
  position: sticky;
  bottom: 0;
  margin: 0;
  padding: 10px 16px;
  background: var(--surface-2);
  border-top: 1px solid var(--border);
  font-size: 0.9rem;
}

.status-bar.error {
  color: var(--danger);
}

.status-bar.ok {
  color: var(--ok);
}

@media (min-width: 700px) {
  main {
    padding: 24px;
  }
}

/* Profile screen (design doc Section 6, 2026-07-28 session). */

.xp-bar-track {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  height: 14px;
  overflow: hidden;
  margin: 4px 0 8px;
}

.xp-bar-fill {
  background: var(--accent);
  height: 100%;
  width: 0%;
}

.stat-row {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px 12px;
}

.stat-row .stat-name {
  color: var(--text-dim);
  font-size: 0.9rem;
  flex: 0 0 auto;
}

.stat-row .stat-value {
  font-weight: 600;
  flex: 1 1 auto;
  text-align: right;
}

.stat-row button {
  padding: 4px 12px;
  min-height: auto;
}

.reward-card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 12px;
  margin-bottom: 8px;
}

.reward-card h4 {
  margin: 0 0 8px;
  font-size: 0.9rem;
  color: var(--text-dim);
}

.reward-card .reward-options {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
