/* The Raven Order — online game board. New component classes only; shared
   tokens, fonts, card/button/log styles come from ../styles.css (linked
   alongside this file) so this stays visually consistent with the rest of
   the site for free instead of re-deriving the palette. See
   planning/roadmap-online-game.md (Phase 4/5) and
   planning/roadmap-mobile-play.md (Phase 1-3) for the design history. */

.game-wrap {
  padding: 40px 0 80px;
}

/* .icon spans wrap inline SVGs. Width/height CSS has no layout effect on a
   plain inline (non-replaced) box, so without this an icon's rendered size
   depends on incidental context (e.g. whether its parent happens to be a
   flex container) instead of the size rules below. inline-flex makes every
   .icon a properly sizable box regardless of where it's used. */
.icon {
  display: inline-flex;
}

.icon svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* .hint was used all over this file's rendered markup (block reasons, small
   subtitles, "nothing here yet" placeholders) with no matching rule
   anywhere — it had been rendering as plain unstyled text everywhere since
   whichever pass introduced it. Noticed while building the sell-confirm
   dialog (F-023 follow-up), which needed it to actually look like a
   secondary caption; fixed globally rather than re-scoping yet another
   one-off class, since every existing use wants exactly this. */
.hint {
  font-size: 0.78rem;
  color: var(--muted);
}

/* --- Start screen (D-137: a minimal game-menu, not a website card at all) --- */

/* Before a match starts, html.in-game isn't set yet (render.js) — the one
   reliable hook for "this is the start screen," since it clears itself
   automatically the moment a match actually begins. The backdrop sits on
   <html> itself (fixed, behind everything, z-index -1) rather than scoped to
   .game-wrap, specifically so it also shows through the now-transparent
   .topbar below — the art reads as one continuous scene behind the whole
   screen, the way a game's own title screen would, instead of visibly
   starting underneath a solid header bar. Reuses the homepage's own
   hero-realm artwork rather than introducing a third image to the site.
   D-133/D-135 both still put the choices inside a boxed panel (first cream,
   then frosted glass) — user feedback after both was that it still read as
   a website dialog sitting on a photo. D-137 drops the box entirely: no
   background, no border, nothing but type directly over the art, closer to
   a Diablo/Hades-style main menu than a settings card. Darkened the
   gradient a little further than D-135's version since there's no longer a
   panel background doing any of the legibility work on its own — it's all
   on this gradient plus each line's own text-shadow now. */
html:not(.in-game)::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(180deg, rgba(20, 15, 30, 0.68) 0, rgba(20, 15, 30, 0.28) 220px, rgba(20, 15, 30, 0.6) 100%),
    url("../assets/hero-realm.jpg") center 32% / cover no-repeat;
}

/* The header has its own solid, blurred bar everywhere else (styles.css) —
   here it's fully transparent so the backdrop above shows through behind
   the brand mark/menu button too, instead of a visible seam where the art
   starts right below a dark bar. */
html:not(.in-game) .topbar {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: transparent;
}

html:not(.in-game) .game-wrap {
  min-height: calc(100vh - 72px);
  display: flex;
  align-items: stretch;
}

/* No background/border/shadow at all — title and the two choices sit
   directly on the backdrop. The big gap between the title block and
   .start-modes (not space-between's exact math) is what reads as "title up
   top, menu lower third" without needing the two to be pinned to hard
   edges. */
.start-screen {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 15vh;
  text-align: center;
  padding: 48px 24px;
}

.start-mark {
  font-size: 2.2rem;
  color: var(--accent);
  text-shadow: 0 0 22px rgba(217, 174, 97, 0.6), 0 2px 10px rgba(10, 6, 20, 0.6);
  margin-bottom: 8px;
}

.start-title {
  margin: 0;
  font-size: clamp(2.3rem, 5vw, 3.2rem);
  text-shadow: 0 4px 26px rgba(10, 6, 20, 0.7), 0 2px 8px rgba(10, 6, 20, 0.5);
}

.start-modes {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 34px;
}

/* Two explicit, always-offered choices (Tutorial / Full match) instead of
   Tutorial silently auto-activating the first visit — a real decision, not
   a settings checkbox. D-137: no card, no icon, no border — a plain label
   with a thin gold rule underneath is the entire affordance, the way a
   game's own title-screen menu items usually read (type first, chrome
   second). The underline is what carries "this is tappable, and here's
   which one we'd suggest," not a box around it. */
.start-mode {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  padding: 2px;
  color: var(--text);
  font: inherit;
  text-align: center;
  cursor: pointer;
}

.start-mode strong {
  position: relative;
  display: inline-block;
  font-family: "Cormorant Garamond", serif;
  font-size: 1.7rem;
  font-weight: 700;
  padding-bottom: 10px;
  text-shadow: 0 3px 16px rgba(10, 6, 20, 0.65);
}

.start-mode strong::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 34px;
  height: 1px;
  background: var(--accent);
  opacity: 0.4;
  transform: translateX(-50%);
  transition: width 0.25s ease, opacity 0.25s ease;
}

.start-mode span {
  font-size: 0.82rem;
  color: var(--muted);
  text-shadow: 0 2px 10px rgba(10, 6, 20, 0.6);
}

.start-mode:hover strong::after,
.start-mode:focus-visible strong::after {
  width: 60px;
  opacity: 0.9;
}

.start-mode:active {
  transform: scale(0.98);
}

/* Whichever mode tutorialSeen() (play/tutorial.js) suggests — a permanently
   wider, brighter underline plus gold-tinted text instead of D-133/D-135's
   glow-box, the same "this matters" language translated into the new
   box-free idiom. */
.start-mode.is-recommended strong {
  color: var(--accent-strong);
}

.start-mode.is-recommended strong::after {
  width: 60px;
  opacity: 0.85;
}

/* --- Shell (Phase 2) --- */

/* Applied at every width (D-015 / roadmap Phase 5): one deliberate,
   consistent behavior everywhere rather than a mobile-only exception. A
   centered max-width column (via the max()-based padding below) keeps the
   content from stretching edge-to-edge on wide screens, while the status
   bar / action bar backgrounds still span the full width like a toolbar. */

html.in-game {
  overflow: hidden;
}

html.in-game .topbar,
html.in-game .site-footer {
  display: none;
}

html.in-game .game-wrap {
  padding: 0;
}

.game-shell-fixed {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 50;
  /* Same starfield + glow gradient stack as body (styles.css) instead of a
     flat fill — entering a match dropped the whole site's atmosphere in
     favor of a bare violet rectangle before this. */
  background:
    radial-gradient(ellipse 900px 500px at 88% -8%, rgba(217, 174, 97, 0.14), transparent 60%),
    radial-gradient(1.5px 1.5px at 12% 8%, rgba(242, 236, 224, 0.6), transparent),
    radial-gradient(1px 1px at 60% 5%, rgba(242, 236, 224, 0.45), transparent),
    radial-gradient(1.5px 1.5px at 85% 15%, rgba(242, 236, 224, 0.5), transparent),
    radial-gradient(1px 1px at 25% 20%, rgba(242, 236, 224, 0.4), transparent),
    radial-gradient(1.5px 1.5px at 45% 3%, rgba(242, 236, 224, 0.45), transparent),
    radial-gradient(1px 1px at 75% 32%, rgba(242, 236, 224, 0.35), transparent),
    var(--bg);
  display: flex;
  flex-direction: column;
  /* F-025: this is a game, not a webpage — no stray text selection from a
     tap-drag, no browser double-tap-zoom hijacking a tap-heavy War-phase
     sequence. Previously only .chip opted out of both; applied here at the
     shell root so every screen (board, sheets — both render as descendants
     of this element, see render() in app.js) gets it for free. */
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
}

.shell-status-bar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px max(16px, calc((100% - 680px) / 2));
  background: rgba(60, 51, 80, 0.92);
  backdrop-filter: blur(14px);
}

.shell-status-bar .brand-mini {
  font-family: "Cormorant Garamond", serif;
  font-weight: 700;
  color: var(--text);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  text-transform: capitalize;
}

.shell-status-bar .brand-mini.stat-flash {
  animation: stat-flash 0.6s ease;
}

.status-gold {
  color: var(--accent);
  font-weight: 700;
  display: inline-block;
  transition: transform 0.2s ease;
}

.status-gold .icon {
  width: 18px;
  height: 18px;
  margin-right: 3px;
  vertical-align: -4px;
}

.status-gold.stat-flash {
  animation: stat-flash 0.6s ease;
}

.shell-content {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 16px max(16px, calc((100% - 680px) / 2));
}

/* --- Action bar (Phase 3) --- */

/* S-024: plain positioning container only — no background/backdrop-filter
   here (that's .shell-action-bar-inner below) specifically so this element
   never establishes its own stacking context, which would otherwise trap
   .tutorial-banner's z-index inside it regardless of how high it's set. */
.shell-action-bar {
  position: relative;
  flex: none;
}

.shell-action-bar-inner {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px max(16px, calc((100% - 680px) / 2)) calc(10px + env(safe-area-inset-bottom, 0px));
  background: rgba(60, 51, 80, 0.92);
  backdrop-filter: blur(14px);
}

.action-bar-primary {
  flex: 1 1 auto;
  display: flex;
  /* Stays a stable height even with nothing in it (Harvest's brief,
     content-free beat while the welcome tutorial banner has the floor —
     see renderActionBar) instead of collapsing to zero and visibly
     shrinking the action bar for that moment. */
  min-height: 3rem;
}

.action-bar-primary .button {
  width: 100%;
}

/* F-032: the full-screen turn announcement that replaced F-027's
   .your-turn-banner tap — see renderTurnAnnounceOverlay/announceTurn.
   Sits above everything (sheets, the sell-confirm dialog) since it can, in
   principle, land while either is open (a reload mid-flow, or the very
   start of a match). */
.turn-announce-overlay {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(20, 14, 30, 0.72);
  animation: turn-announce-backdrop-in 0.3s ease-out;
}

.turn-announce-text {
  text-align: center;
  font-family: "Cormorant Garamond", serif;
  font-weight: 700;
  font-size: 2.4rem;
  color: var(--accent-strong, var(--accent));
  text-shadow: 0 0 30px rgba(217, 174, 97, 0.4);
  animation: turn-announce-text-in 0.4s ease-out;
}

.turn-announce-mark {
  display: block;
  font-size: 1.6rem;
  margin-bottom: 8px;
}

@keyframes turn-announce-backdrop-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes turn-announce-text-in {
  from { opacity: 0; transform: scale(0.9) translateY(10px); }
  to { opacity: 1; transform: scale(1) translateY(0); }
}

.chip-badge {
  flex: none;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--text);
  border-radius: var(--radius);
  padding: 0.7rem 0.8rem;
  font-size: 0.85rem;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease, transform 0.1s ease;
}

.chip-badge:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.chip-badge .icon {
  width: 15px;
  height: 15px;
  margin-right: 2px;
  vertical-align: -3px;
}

/* --- Sections & chips (Phase 3) --- */

.section-label {
  display: block;
  color: var(--muted);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin: 14px 0 8px;
}

/* Separates the always-visible enemy realm (buildings/units, top) from the
   player's own (bottom) — position plus the D-022 danger/accent tint is the
   only ownership signal now, no "Your buildings/units" text labels. */
.realm-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 16px 0;
  color: var(--muted);
}

.realm-divider::before,
.realm-divider::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: var(--line);
}

.realm-divider span {
  flex: none;
  color: var(--accent);
  font-size: 0.8rem;
}

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}

.chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 6px 12px 6px 8px;
  font-size: 0.82rem;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: default;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation;
  transition: border-color 0.2s ease, transform 0.1s ease;
}

.chip .icon {
  width: 20px;
  height: 20px;
  color: var(--accent);
  flex: none;
}

/* D-114: a building chip with illustrated art gets a round portrait here
   instead of its line icon, bled out to the chip's own top/bottom/left
   edges — negative margins matching .chip's own padding (6px vertical, 8px
   on the left) pull it flush against those edges. 34px matches .chip's own
   rendered height (padding 6+6 plus its ~22px content row) — a fixed size
   rather than deriving it from align-self: stretch + aspect-ratio, which
   turned out to fall back to the source image's raw intrinsic size
   (several hundred px) whenever the stretch height wasn't yet definite at
   the point aspect-ratio tried to transfer it to a width. */
.chip-art {
  display: inline-flex;
  width: 34px;
  height: 34px;
  margin: -6px 0 -6px -8px;
  border-radius: 50%;
  overflow: hidden;
  flex: none;
}

.chip-art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

button.chip {
  cursor: pointer;
}

button.chip:hover {
  border-color: var(--accent);
}

.chip-building {
  border-color: var(--accent);
}

/* Enemy-owned buildings/units (F-021): a distinct danger tint instead of the
   player's own accent gold, applied everywhere the AI's stuff renders (the
   enemy summary/sheet, and combat's attacker tiles/target chips below) —
   not just the War-phase combat UI, since the enemy sheet had the same
   readability problem using the same neutral chip styling as the player's
   own side. */
.chip.enemy {
  border-color: var(--danger);
  background: rgba(164, 66, 90, 0.08);
}

.chip.enemy .icon {
  color: var(--danger);
}

.chip.enemy .chip-tag {
  color: var(--danger);
}

/* Beats the generic button.chip:hover accent-border rule (three classes vs.
   one element + one class + a pseudo-class) so an enemy chip's hover state
   stays in its own danger tint instead of flipping to the player's gold. */
.chip.enemy:hover {
  border-color: var(--danger);
}

/* War phase, on-board pick/place (F-021 follow-up): .chip-pickable marks a
   chip as a valid tap target right now (a dashed accent outline — "you can
   tap this"), .chip-picked marks the one currently held/committed (solid
   accent + glow). Enemy chips default to the danger tint (D-022), so both
   states get an explicit override there — plain specificity (.chip.enemy is
   two classes) would otherwise win over the one-class
   .chip-pickable/.chip-picked. */
.chip-pickable {
  border-style: dashed;
  border-color: var(--accent);
}

.chip-picked {
  border-color: var(--accent);
  background: rgba(169, 116, 31, 0.16);
  box-shadow: 0 0 0 2px rgba(169, 116, 31, 0.35);
}

.chip.enemy.chip-pickable {
  border-style: dashed;
  border-color: var(--accent);
}

.chip.enemy.chip-picked {
  border-color: var(--accent);
  background: rgba(169, 116, 31, 0.16);
  box-shadow: 0 0 0 2px rgba(169, 116, 31, 0.35);
}

.chip.enemy.chip-pickable .icon,
.chip.enemy.chip-picked .icon {
  color: var(--accent);
}

/* F-026: a brief "hit" pulse on a unit-group chip the instant its step in
   the paced battle reveal lands, right before its count actually drops (see
   revealBattleAndAdvance/renderUnitChips in app.js). A box-shadow rather
   than a border-color override so it reads the same way on a plain chip and
   on an .enemy one (which already owns its own border color) without
   needing a separate .chip.enemy.chip-dying rule. */
.chip-dying {
  animation: chip-dying 0.3s ease;
}

@keyframes chip-dying {
  0% { transform: scale(1); box-shadow: none; }
  35% { transform: scale(1.1); box-shadow: 0 0 0 4px rgba(240, 198, 126, 0.45); }
  100% { transform: scale(1); box-shadow: none; }
}

/* F-030: the equivalent pulse for a building's own step in the reveal —
   a danger-red tint and a slight shake instead of the unit-death gold
   pulse, so destruction reads distinctly from a unit dying. */
.chip-destroying {
  animation: chip-destroying 0.4s ease;
}

@keyframes chip-destroying {
  0% { transform: scale(1) rotate(0deg); box-shadow: none; }
  30% { transform: scale(1.08) rotate(-2deg); box-shadow: 0 0 0 4px rgba(164, 66, 90, 0.4); }
  60% { transform: scale(1.05) rotate(2deg); box-shadow: 0 0 0 4px rgba(164, 66, 90, 0.4); }
  100% { transform: scale(1) rotate(0deg); box-shadow: none; }
}

/* The creation-side mirror of chip-dying/chip-destroying: a freshly built
   building grows/glows in instead of just appearing on the next render. */
.chip-appearing {
  animation: chip-appear 0.5s ease;
}

@keyframes chip-appear {
  0% { transform: scale(0.5); opacity: 0; box-shadow: none; }
  60% { transform: scale(1.08); opacity: 1; box-shadow: 0 0 0 4px rgba(169, 116, 31, 0.35); }
  100% { transform: scale(1); opacity: 1; box-shadow: none; }
}

/* Slim, non-modal instruction line for the current War sub-step — replaces
   the declare/target/assign sheets' framing text now that the whole flow
   lives inline on the board instead of in a popup. */
.war-status {
  margin: 0 0 12px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
}

/* Connector lines between a defender's board chip and the attacker tile(s)
   it's blocking (defend War sub-step only — drawWarLines() in app.js).
   position:fixed covering the real viewport, not just .shell-content, so
   getBoundingClientRect() coordinates (already viewport-relative) can be
   used directly with no manual scroll-offset math — see D-034. Below the
   sheet backdrop/panel z-index (70/71) so an open sheet always wins;
   pointer-events:none so the lines never intercept taps on the chips
   underneath — this is a read-only visual layer over warAssign.defenses,
   never a second, competing interaction surface. */
.war-lines-overlay {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 65;
  pointer-events: none;
}

.war-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linecap: round;
  opacity: 0.55;
}

.war-line-new {
  animation: war-line-in 0.35s ease;
}

@keyframes war-line-in {
  from { opacity: 0; }
  to { opacity: 0.55; }
}

/* Hover (desktop) / focus (keyboard) preview of a chip's full info card —
   the same content the click-through info sheet shows, just a lighter
   glance that doesn't require opening anything (F-021 follow-up). Touch
   devices have no hover, so they only ever get this via the info sheet. */
.chip-tooltip {
  display: none;
  position: absolute;
  left: 0;
  top: calc(100% + 6px);
  z-index: 45;
  width: max-content;
  max-width: 240px;
  padding: 8px 10px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--bg-deep);
  color: var(--text);
  font-size: 0.78rem;
  line-height: 1.4;
  text-align: left;
  white-space: normal;
  pointer-events: none;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
}

@media (hover: hover) {
  .chip:hover .chip-tooltip,
  .chip:focus-visible .chip-tooltip {
    display: block;
  }
}

/* The fuller card shown in the unit-info/building-info sheets themselves
   (opened by tap/click) — a bigger icon plus a short rules blurb, reusing
   the engine's own BUILDINGS[type].blurb / UNIT_STATS[tier].blurb text. */
.card-info-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 6px;
}

/* D-116: the building-info sheet's own art banner — capped height (not
   edge-to-edge like .build-card-art, which only works at a small grid-tile
   size) so a wide sheet doesn't turn the image into a huge square with the
   name/blurb squeezed into a thin overlay band. Name/blurb render at their
   normal .card-info-head/.card-info-text size below it instead. */
.building-info-art {
  position: relative;
  width: 100%;
  height: 200px;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--line);
  margin-bottom: 10px;
}

.building-info-art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.icon-lg {
  width: 34px;
  height: 34px;
  color: var(--accent);
  flex: none;
}

.icon-lg.enemy {
  color: var(--danger);
}

.card-info-text {
  margin: 6px 0;
  font-size: 0.88rem;
  line-height: 1.5;
}

/* The attached evolution card shown inside a building's info sheet (F-024)
   — set off from the building's own info with a hairline, so it still reads
   as "a card, attached here" rather than more building copy. */
.card-info-attached {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  font-size: 0.85rem;
}

.card-info-attached .icon {
  width: 16px;
  height: 16px;
  color: var(--accent);
  flex: none;
}

.chip-tag {
  color: var(--accent);
  font-size: 0.72rem;
}

.chip-count {
  font-weight: 700;
}

.chip-add {
  border-style: dashed;
  color: var(--muted);
}

/* --- On-demand sheets (Phase 3): hand, build picker, unit-group menu,
   declare/assign war actions, enemy summary, battle log. All rendered the
   same way regardless of viewport — this is just better UI at any width,
   independent of the still-open F-015 question about the outer fixed
   shell. position:fixed on the panel/backdrop themselves (not relying on
   an ancestor being fixed) is what makes them work identically on desktop,
   where .game-shell-fixed itself is normal-flow. */

.sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 14, 30, 0.55);
  z-index: 70;
}

.sheet-panel {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 82vh;
  margin: 0 auto;
  max-width: 640px;
  background: var(--panel);
  color: var(--card-text, #2b2130);
  border-radius: 14px 14px 0 0;
  z-index: 71;
  display: flex;
  flex-direction: column;
  box-shadow: 0 -12px 34px rgba(20, 14, 30, 0.4);
  --text: var(--card-text, #2b2130);
  --muted: var(--card-muted, #6b5f60);
  --accent: var(--card-accent, #a9741f);
  --line: var(--card-line, rgba(43, 33, 48, 0.14));
}

.sheet-header {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}

.sheet-header h3 {
  margin: 0;
}

.sheet-body {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 16px;
  padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
}

/* Confirm dialog (F-023 follow-up: sell-and-build; also reused for the
   exit-match confirmation, replacing a bare window.confirm): a small
   centered card, not a sheet — floats above whatever sheet is open (higher
   z-index than .sheet-backdrop/.sheet-panel) and above .turn-announce-
   overlay (90) too, since either confirm can legitimately be triggered
   right as a turn starts (a native window.confirm was always topmost
   regardless of in-page z-index, so this has to out-rank everything this
   page draws to actually replace it). Same panel-variable remap as
   .sheet-panel so the reused .card-info-head/.card-info-text classes read
   correctly against its light surface. */
.confirm-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(20, 14, 30, 0.6);
  z-index: 95;
}

.confirm-dialog {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: min(340px, calc(100% - 32px));
  background: var(--panel);
  color: var(--card-text, #2b2130);
  border-radius: 14px;
  padding: 18px;
  z-index: 96;
  box-shadow: 0 20px 44px rgba(20, 14, 30, 0.45);
  animation: confirm-dialog-in 0.18s ease-out;
  --text: var(--card-text, #2b2130);
  --muted: var(--card-muted, #6b5f60);
  --accent: var(--card-accent, #a9741f);
  --line: var(--card-line, rgba(43, 33, 48, 0.14));
}

@keyframes confirm-dialog-in {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.96); }
  to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

.confirm-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
}

/* Build sheet (F-022/F-028/F-029). */

/* F-028: a uniform 2-column grid — built and not-yet-built buildings are
   the same card shape in the same grid, told apart only by state color, not
   by living in separate components. */
.build-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}

/* D-114: 3 columns once there's room (desktop) — the sheet panel itself
   caps at 640px (.sheet-panel's own max-width) regardless of viewport, so
   this reflects "the sheet got wide enough," not raw screen width. */
@media (min-width: 640px) {
  .build-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Base card: works as either a plain (non-interactive) <div> or, when the
   markup makes it a <button>, the entire card is the tap target — no
   button lives inside it. The reset properties below undo native <button>
   chrome so both tags render identically. */
.build-card {
  display: block;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: default;
  overflow: hidden;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease, transform 0.1s ease;
  /* The real cause of the build-card-art gap bug: .build-grid's rows stretch
     every card in a row to match its tallest sibling (e.g. one with a
     longer status line like "Needs Armory") via the grid default
     (align-items: normal = stretch) — confirmed directly by forcing
     align-self: start on a gapped card and watching it shrink back to its
     natural, gap-free height. A <button> centers the resulting spare height
     around its content while a plain <div> doesn't, which is why only
     buildable (button) cards ever showed it — but the stretch itself was
     always the actual bug: a card has no reason to be taller than its own
     content just because a neighbor's status line is longer. Opting every
     card out of the stretch is the fix, not fighting how each tag happens
     to render the leftover space. */
  align-self: start;
  appearance: none;
  -webkit-appearance: none;
}

button.build-card {
  cursor: pointer;
}

/* Built: a filled, muted "done" tint plus a small check badge instead of
   the cost badge — reuses the --success token (defined site-wide, unused
   until now) rather than inventing a new color for this one state. */
.build-card.is-built {
  background: rgba(63, 125, 92, 0.08);
  border-color: rgba(63, 125, 92, 0.35);
}

/* Buildable right now — the one deliberately eye-catching state, reusing
   the same sparing gold-glow language as the site's hero panel (D-009)
   rather than a new visual idiom. */
.build-card.is-buildable {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(169, 116, 31, 0.12), 0 4px 14px rgba(169, 116, 31, 0.18);
}

/* Buildable, but only by selling Workers first (F-023) — still the
   .is-buildable glow (it IS actionable, one tap away), plus a dashed border
   as the "one extra step" cue (reusing .chip-add's dashed idiom). */
.build-card.is-sellable {
  border-style: dashed;
}

/* Not built and not currently possible (missing prerequisite, gold, or the
   turn limit) — dimmed and inert, with a one-line reason (color alone isn't
   an accessible-enough signal on its own). */
.build-card.is-locked {
  opacity: 0.55;
}

/* F-041: points at the specific chip/card the guided tutorial's current
   step wants tapped (the "+ Build" chip, then the Armory card once the
   build sheet is open). Deliberately stronger at every point of the pulse
   than .build-card.is-buildable's own static glow (3px/14px, 0.12/0.18
   alpha) — a same-strength pulse reads identically to "merely buildable"
   whenever another affordable card happens to be on screen at the same
   time (e.g. Armory next to an equally-affordable Academy Lv.1), which is
   exactly the ambiguity this exists to avoid. The solid border-color + tint
   (not just a box-shadow ring) is what actually survives a single frozen
   video frame, the same "committed" language .chip-picked already uses. */
.tutorial-target {
  border-color: var(--accent) !important;
  background: rgba(169, 116, 31, 0.1);
  animation: tutorial-target-pulse 1.4s ease-in-out infinite;
}

@keyframes tutorial-target-pulse {
  0%, 100% { box-shadow: 0 0 0 4px rgba(169, 116, 31, 0.3), 0 4px 20px rgba(169, 116, 31, 0.32); }
  50% { box-shadow: 0 0 0 8px rgba(169, 116, 31, 0.45), 0 4px 28px rgba(169, 116, 31, 0.5); }
}

/* D-112: the build-card art box — image (or placeholder glyph) fills it
   edge to edge, with the name/blurb overlaid directly on its lower portion
   (inside a gradient fading into the sheet's own panel color) and a round
   badge (cost, or a check once built) floating over the top-right corner.
   Replaces the old separate icon+name+cost header row and text block below
   a plain image — this is what D-110/D-111's art slot grows into once
   there's room to actually show the art, not just a small thumbnail. */
.build-card-art {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  background: var(--line);
}

.build-card-art img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.build-card-badge {
  position: absolute;
  top: 8px;
  right: 8px;
  display: flex;
  align-items: center;
  gap: 2px;
  min-width: 26px;
  height: 26px;
  padding: 0 7px;
  border-radius: 13px;
  background: var(--accent);
  color: #2b2130;
  font-size: 0.72rem;
  font-weight: 700;
  box-shadow: 0 2px 6px rgba(20, 14, 30, 0.3);
}

/* Specificity note: a badge's icon also matches .card-art-placeholder
   .icon (same specificity, declared later) whenever the badge sits inside
   an unillustrated card's placeholder art box — the extra .build-card-art
   qualifier here breaks that tie deliberately rather than by source order. */
.build-card-art .build-card-badge .icon {
  width: 13px;
  height: 13px;
  color: inherit;
  opacity: 1;
}

.build-card-badge-check {
  background: var(--success);
  color: #fff;
  justify-content: center;
}

/* The green "built" wash as its own layer directly over the art photo —
   .build-card-art's own background (below) is invisible once the image is
   full-bleed (D-112), so this has to sit in front of the <img>, not behind
   it. DOM order (inserted right after the <img>, before the badge/name
   overlay) is what keeps it under those without needing z-index. */
.build-card-built-tint {
  position: absolute;
  inset: 0;
  background: rgba(63, 125, 92, 0.2);
  pointer-events: none;
}

/* Same idea, same strength, for a locked card (missing prereq, gold, or the
   turn limit) — the rgb triplet is --danger (#a4425a) by eye, hardcoded
   here the same way the green tint above hardcodes --success's, since
   rgba() can't take a CSS custom property directly without a separate
   r,g,b-triplet variable this file doesn't otherwise need. */
.build-card-locked-tint {
  position: absolute;
  inset: 0;
  background: rgba(164, 66, 90, 0.2);
  pointer-events: none;
}

/* Buildable right now (not yet owned) — same green wash as .is-built,
   reusing "green = good" rather than inventing a third color; the cost
   badge vs. the owned state's check badge is what still tells them apart
   at a glance. Kept as its own class (not shared with .build-card-built-tint)
   since the two states are semantically distinct even though they render
   identically today. */
.build-card-buildable-tint {
  position: absolute;
  inset: 0;
  background: rgba(63, 125, 92, 0.2);
  pointer-events: none;
}

.build-card-overlay {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 22px 8px 7px;
  background: linear-gradient(to bottom, transparent, var(--panel) 55%);
}

.build-card-name {
  font-weight: 700;
  font-size: 0.85rem;
}

.build-card-blurb {
  font-size: 0.68rem;
  line-height: 1.35;
  color: var(--text);
  margin-top: 2px;
}

.build-card-status .icon,
.card-info-head .hint .icon {
  width: 15px;
  height: 15px;
  margin-right: 2px;
  vertical-align: -3px;
}

.build-card-status {
  font-size: 0.68rem;
  color: var(--muted);
  padding: 5px 8px 7px;
}

/* The actual blocking reason (missing prerequisite, gold, turn limit) reads
   noticeably stronger than ordinary status/flavor text — the point is to
   answer "why can't I build this" at a glance, not blend into the card's
   description paragraph. */
.build-card-status-locked {
  display: inline-flex;
  align-items: center;
  font-weight: 600;
  color: var(--text);
}

.icon-button {
  border: 1px solid var(--border-strong);
  background: transparent;
  color: inherit;
  border-radius: var(--radius);
  width: 24px;
  height: 24px;
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1;
  transition: border-color 0.2s ease, color 0.2s ease, transform 0.1s ease;
}

.icon-button:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.evo-hand {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
}

/* D-140: shaped like .build-card now (full-bleed art, padding:0, the text
   block below living in its own padded wrapper) instead of a uniformly
   padded card with a small image — see evoCardArtHtml (render.js) for the
   matching markup change. */
.evo-card {
  display: block;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: default;
  overflow: hidden;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

.evo-card-body {
  padding: 8px;
}

/* Tapping the card itself plays it — no separate Play button (F-035). Same
   "buildable" glow language .build-card.is-buildable already uses for "one
   tap away", so a playable card reads the same way a buildable one does. */
button.evo-card {
  cursor: pointer;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(169, 116, 31, 0.12), 0 4px 14px rgba(169, 116, 31, 0.18);
}

button.evo-card:active {
  transform: scale(0.98);
}

/* Not playable right now for a real reason (missing prereq, target, etc.) —
   dimmed, same idiom .build-card.is-locked uses. A card shown outside the
   human's own construction turn (nothing to act on at all) stays undimmed;
   see the evo-card-locked class in renderHandSheet. */
.evo-card-locked {
  opacity: 0.6;
}

.evo-card-status {
  font-size: 0.68rem;
  margin-top: 5px;
}

/* A subtle theme tint by card group (D-126): violet for druid tattoos,
   yellow for "tech" (Medicine/Shields/Steel craft/Cloaking), green for
   everything else ("tradition"). D-140: moved from a wash on the card's own
   background to its own layer directly over the art (same fix D-112
   already applied once for build-card's built/locked tints — a background
   wash goes invisible the moment the art covers the whole card) — the
   small themed icon stays, now recolored inside the name overlay instead
   of a separate head row. */
.evo-card-tradition-tint {
  position: absolute;
  inset: 0;
  background: rgba(63, 125, 92, 0.18);
  pointer-events: none;
}

.evo-card-tradition .build-card-name .icon {
  color: var(--success);
}

.evo-card-tattoo-tint {
  position: absolute;
  inset: 0;
  background: rgba(124, 94, 163, 0.18);
  pointer-events: none;
}

.evo-card-tattoo .build-card-name .icon {
  color: #7c5ea3;
}

.evo-card-tech-tint {
  position: absolute;
  inset: 0;
  background: rgba(184, 134, 11, 0.18);
  pointer-events: none;
}

.evo-card-tech .build-card-name .icon {
  color: #b8860b;
}

.evo-card .build-card-name .icon {
  width: 13px;
  height: 13px;
  margin-right: 4px;
  vertical-align: -2px;
}

.card-art-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
}

.card-art-placeholder .icon {
  width: 36%;
  height: 36%;
  color: var(--accent);
  opacity: 0.3;
}

.evo-card-target {
  font-size: 0.72rem;
  color: var(--muted);
  margin-bottom: 6px;
}

/* The card's effect text — always visible (F-024; previously revealed only
   on hover/tap, which hid exactly the information needed to judge whether
   to play the card at all). */
.evo-card-text {
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--text);
  margin-bottom: 6px;
}

/* A currently-held defender that can't legally stop this attacker (Shadow-rune
   tattoo / Cloaking card restrict it to druids only) — falls back to the
   plain info-card tap instead of accepting an assignment that would just be
   silently dropped at resolve time. Applies to any .chip, not just the
   attacker row, since it's the same underlying "held unit doesn't fit here"
   state renderAttackerTile expresses. */
.chip.chip-disabled {
  opacity: 0.55;
  cursor: default;
}

/* Floats above the action bar (position:absolute against the action bar's
   own position:relative) instead of sitting inline in .shell-content — a
   guided-tutorial line was found to push the enemy summary/buildings/units
   down, eating into the exact fixed-viewport space the no-scroll shell is
   built to protect. As an overlay it never affects .shell-content's scroll
   height, at any viewport. */
.tutorial-banner {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  /* S-024: was 60 — lower than .sheet-backdrop/.sheet-panel (70/71), so the
     banner was hidden behind any open sheet, including the exact moments
     (e.g. "Tap Armory to build it") where it's pointing at something the
     open sheet itself contains. 75 clears both while staying below the
     sell-confirm dialog (80/81) and the turn-announce overlay (90). */
  z-index: 75;
  margin: 0 max(16px, calc((100% - 680px) / 2)) 10px;
  /* background/color/border-strong/box-shadow and the --text/--muted/
     --accent/--line token overrides come from the shared card-context rule
     in ../styles.css (.hero-panel, .feature-card, ... .tutorial-banner) —
     without it, .button-secondary's border/text read the dark shell's
     light-on-dark tokens and go near-invisible against this cream toast. */
  border: 1px solid var(--accent);
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

/* tutorial.js adds this whenever a sheet is open — .sheet-panel covers the
   bottom ~82vh of the viewport, which swallows the banner's usual
   just-above-the-action-bar spot, so it ends up floating mid-sheet,
   overlapping whatever cards happen to sit there (including the one it's
   pointing at). Docked under the status bar instead — the one strip a sheet
   never covers — so it never competes with the sheet's own content. */
.tutorial-banner-top {
  position: fixed;
  top: 58px;
  bottom: auto;
  left: 0;
  right: 0;
  margin: 0 max(16px, calc((100% - 680px) / 2));
}

/* F-041 follow-up: the entrance animation used to sit directly on
   .tutorial-banner, unconditionally — but render() fully rebuilds this
   element on every single render(), including several times a second during
   the AI's paced turn, replaying the animation each time even though the
   text hadn't changed (read as a flicker for the whole AI turn). Applied
   only when tutorial.js's renderTutorialBanner detects the step actually
   changed since the last render, same guard state.js's lastGold/
   lastPhaseLabel already use for the status bar's flash cues. */
.tutorial-banner-enter {
  animation: log-line-in 0.35s ease;
}

.tutorial-banner p {
  margin: 0;
  line-height: 1.5;
}

/* D-108: the only tap offered during a paced reveal beat (block-reveal,
   each battle event) — lets someone who's already read this skip the rest
   of that one wait, not a general "skip the tutorial" escape hatch, so it's
   small/quiet rather than styled like a real decision. */
.tutorial-skip {
  align-self: flex-end;
  background: none;
  border: none;
  padding: 2px 0;
  color: var(--accent);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}

.tutorial-skip:hover {
  text-decoration: underline;
}

.winner-banner {
  text-align: center;
  padding: 40px 20px;
  animation: winner-banner-in 0.4s ease-out;
}

.winner-banner h2 {
  font-size: 1.8rem;
  margin-bottom: 6px;
}

.winner-summary {
  color: var(--muted);
  font-size: 0.9rem;
}

/* A win gets the same sparing gold-glow language used everywhere else on
   the site (hero panel, .turn-announce-text, build-sheet buildable state) —
   not a new visual idiom, just this moment's turn to use it. A loss stays
   quiet and desaturated rather than a punishing "GAME OVER" treatment,
   matching the boutique-board-game warmth the rest of the site keeps even
   here. */
.winner-banner.is-win h2 {
  color: var(--accent);
  text-shadow: 0 0 24px rgba(169, 116, 31, 0.35);
}

.winner-banner.is-loss {
  filter: grayscale(0.35);
  opacity: 0.92;
}

.winner-mark {
  font-size: 2.2rem;
  color: var(--accent);
  text-shadow: 0 0 20px rgba(169, 116, 31, 0.45);
  margin-bottom: 6px;
}

@keyframes winner-banner-in {
  from { opacity: 0; transform: translateY(10px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

/* --- Feedback animations (Phase 5 polish) — deliberately restrained per
   D-010 ("dezent"): brief transient cues, not showy motion, and no sound
   (roadmap Phase 5: "sound-free animations"). --- */

@keyframes stat-flash {
  0% { transform: scale(1); }
  30% { transform: scale(1.25); color: var(--accent-strong, var(--accent)); }
  100% { transform: scale(1); }
}

.battle-log li {
  transition: opacity 0.3s ease;
}

.battle-log li.log-line-new {
  animation: log-line-in 0.35s ease;
}

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

button.chip:focus-visible,
.icon-button:focus-visible,
.chip-badge:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Tap/press feedback (game-feel pass): every interactive surface dips
   slightly on press instead of giving no acknowledgment at all until
   whatever the tap triggers finishes rendering — the cheapest, most
   standard "this is a game, not a webpage" signal, and the only
   interaction states this file had before were :hover (desktop-only) and
   :focus-visible. .button's own :active lives in styles.css since that
   class is shared site-wide, not just in the game. */
button.chip:active,
.chip-badge:active,
.icon-button:active,
button.build-card:active {
  transform: scale(0.96);
}

@media (prefers-reduced-motion: reduce) {
  .chip,
  .chip-badge,
  .icon-button,
  .build-card,
  .status-gold,
  .brand-mini,
  .tutorial-banner-enter,
  .battle-log li,
  .war-line-new,
  .winner-banner,
  .turn-announce-overlay,
  .turn-announce-text,
  .start-mode,
  .tutorial-target {
    transition: none;
    animation: none !important;
  }
}
