/* ============================================================
   ASHEN THRONE — Component Library v2
   Production-ready component classes built on tokens.css.
   Every screen in the app should compose from these primitives.
   ============================================================ */

@import url('./tokens.css');

*, *::before, *::after { box-sizing: border-box; }

/* ============================================================
   1. BUTTONS   .btn + variant modifiers
   ============================================================ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  height: 32px;
  padding: 0 14px;
  font-family: var(--font-mono);
  font-size: var(--font-md);
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-primary);
  background: var(--bg-panel);
  border: 1px solid var(--border-steel);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background .15s, border-color .15s, color .15s, box-shadow .15s, transform .08s;
  box-shadow: var(--shadow-btn-inset);
  white-space: nowrap;
}
.btn:hover { background: var(--bg-panel-hover); border-color: #3a4258; }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--accent-cyan); }
.btn:disabled, .btn.is-disabled {
  opacity: 0.4; cursor: not-allowed; transform: none !important;
}

/* Variants — gradient fill + outline + ghost */
.btn-primary {
  color: var(--accent-cyan);
  background: linear-gradient(180deg, rgba(79,195,247,0.22), rgba(79,195,247,0.06));
  border-color: var(--accent-cyan);
}
.btn-primary:hover:not(:disabled) {
  color: #fff;
  background: linear-gradient(180deg, rgba(79,195,247,0.38), rgba(79,195,247,0.12));
  box-shadow: var(--glow-cyan);
}

/* ==============================================================
   PRESET SLOT BUTTONS (v2)
   --------------------------------------------------------------
   Compact buttons for the deploy-modal preset column (1/2/3 + S)
   and the larger picker grid inside the Save-to-Preset dialog.
   The legacy .btn opacity-trick was unreadable — these split into
   filled/empty/save variants so contrast tracks slot state.

   Selectors are scoped under .modal-box to outweigh the legacy
   .modal-box button rule in client/index.html (margin-top:4px,
   padding:8px 16px, font-size:13px) which would otherwise clobber
   the size, padding, and stacking of these compact buttons.
   ============================================================== */
.modal-box .preset-slot-btn,
.preset-slot-btn {
  display: block;
  width: 100%;
  height: 28px;
  min-height: 28px;
  padding: 0;
  margin: 0 0 4px 0;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  border-radius: 3px;
  cursor: pointer;
  transition: background .15s, border-color .15s, color .15s, box-shadow .15s;
  line-height: 1;
  text-align: center;
}
.modal-box .preset-slot-btn:last-child,
.preset-slot-btn:last-child { margin-bottom: 0; }
.modal-box .preset-slot-btn--filled,
.preset-slot-btn--filled {
  color: var(--accent-cyan);
  background: linear-gradient(180deg, rgba(79,195,247,0.22), rgba(79,195,247,0.06));
  border: 1px solid var(--accent-cyan);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05), 0 0 6px rgba(79,195,247,0.18);
}
.modal-box .preset-slot-btn--filled:hover,
.preset-slot-btn--filled:hover {
  color: #fff;
  background: linear-gradient(180deg, rgba(79,195,247,0.38), rgba(79,195,247,0.12));
  box-shadow: var(--glow-cyan);
}
.modal-box .preset-slot-btn--empty,
.preset-slot-btn--empty {
  color: var(--text-secondary);
  background: rgba(13,21,32,0.5);
  border: 1px solid var(--border-steel);
}
.modal-box .preset-slot-btn--empty:hover,
.preset-slot-btn--empty:hover {
  color: var(--accent-cyan);
  border-color: var(--accent-cyan);
  background: rgba(26,34,53,0.7);
}
/* "S" save button — gold accent, separated from the load slots so the
   action reads as distinct from "load slot N". */
.modal-box .preset-slot-btn--save,
.preset-slot-btn--save {
  color: var(--accent-gold);
  background: linear-gradient(180deg, rgba(255,215,0,0.18), rgba(255,215,0,0.04));
  border: 1px solid rgba(255,215,0,0.55);
  margin-top: 6px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
}
.modal-box .preset-slot-btn--save:hover,
.preset-slot-btn--save:hover {
  color: #fff;
  background: linear-gradient(180deg, rgba(255,215,0,0.34), rgba(255,215,0,0.08));
  border-color: var(--accent-gold);
  box-shadow: 0 0 10px rgba(255,215,0,0.35);
}

/* Picker buttons inside the Save-to-Preset dialog body — bigger
   tap-targets that show "filled" vs "empty" so users know they're
   about to overwrite. */
.modal-box .preset-pick-btn,
.preset-pick-btn {
  flex: 1;
  height: 64px;
  min-height: 64px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  margin: 0;
  padding: 0;
  font-family: var(--font-mono);
  font-weight: 700;
  border-radius: 4px;
  cursor: pointer;
  transition: background .15s, border-color .15s, color .15s, box-shadow .15s;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
}
.preset-pick-btn-num {
  font-size: 22px;
  letter-spacing: 2px;
  line-height: 1;
}
.preset-pick-btn-tag {
  font-size: 8px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  font-weight: 600;
  opacity: 0.85;
}
.modal-box .preset-pick-btn--filled,
.preset-pick-btn--filled {
  color: var(--accent-cyan);
  background: linear-gradient(180deg, rgba(79,195,247,0.22), rgba(79,195,247,0.06));
  border: 1px solid var(--accent-cyan);
}
.modal-box .preset-pick-btn--filled:hover:not(:disabled),
.preset-pick-btn--filled:hover:not(:disabled) {
  color: #fff;
  background: linear-gradient(180deg, rgba(79,195,247,0.4), rgba(79,195,247,0.12));
  box-shadow: var(--glow-cyan);
}
.modal-box .preset-pick-btn--empty,
.preset-pick-btn--empty {
  color: var(--text-secondary);
  background: rgba(13,21,32,0.5);
  border: 1px solid var(--border-steel);
}
.modal-box .preset-pick-btn--empty:hover:not(:disabled),
.preset-pick-btn--empty:hover:not(:disabled) {
  color: var(--accent-cyan);
  border-color: var(--accent-cyan);
  background: rgba(26,34,53,0.7);
}
.preset-pick-btn:disabled {
  opacity: 0.32;
  cursor: not-allowed;
  pointer-events: none;
}

.btn-danger {
  color: var(--accent-red);
  background: linear-gradient(180deg, rgba(239,83,80,0.22), rgba(239,83,80,0.06));
  border-color: var(--accent-red);
}
.btn-danger:hover:not(:disabled) {
  color: #fff;
  background: linear-gradient(180deg, rgba(239,83,80,0.38), rgba(239,83,80,0.12));
  box-shadow: var(--glow-red);
}

.btn-success {
  color: var(--accent-green);
  background: linear-gradient(180deg, rgba(102,187,106,0.22), rgba(102,187,106,0.06));
  border-color: var(--accent-green);
}
.btn-success:hover:not(:disabled) {
  color: #fff;
  background: linear-gradient(180deg, rgba(102,187,106,0.38), rgba(102,187,106,0.12));
}

.btn-warning {
  color: var(--accent-orange);
  background: linear-gradient(180deg, rgba(255,152,0,0.22), rgba(255,152,0,0.06));
  border-color: var(--accent-orange);
}
.btn-warning:hover:not(:disabled) {
  color: #fff;
  background: linear-gradient(180deg, rgba(255,152,0,0.38), rgba(255,152,0,0.12));
}

.btn-gold {
  color: var(--accent-gold);
  background: linear-gradient(180deg, rgba(255,215,0,0.22), rgba(255,215,0,0.06));
  border-color: var(--accent-gold);
}
.btn-gold:hover:not(:disabled) {
  color: #fff;
  background: linear-gradient(180deg, rgba(255,215,0,0.38), rgba(255,215,0,0.12));
  box-shadow: var(--glow-gold);
}

.btn-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-dim);
  box-shadow: none;
}
.btn-ghost:hover:not(:disabled) {
  color: var(--text-primary);
  background: rgba(79,195,247,0.06);
  border-color: var(--border-subtle);
}

/* Sizes */
.btn-sm { height: 24px; padding: 0 10px; font-size: var(--font-sm); letter-spacing: 1.2px; }
.btn-lg { height: 40px; padding: 0 20px; font-size: var(--font-lg); letter-spacing: 2px; }
.btn-block { display: flex; width: 100%; }

/* Icon-only button — square */
.btn-icon {
  width: 28px; height: 28px; padding: 0;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-dim);
  border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: all .15s;
  position: relative;
}
.btn-icon:hover {
  color: var(--accent-cyan);
  border-color: var(--border-steel);
  background: rgba(79,195,247,0.05);
}
.btn-icon .notif-dot {
  position: absolute; top: 3px; right: 3px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent-red);
  box-shadow: 0 0 6px rgba(239,83,80,0.7);
}
.btn-icon .notif-dot.cyan { background: var(--accent-cyan); box-shadow: 0 0 6px rgba(79,195,247,0.7); }

/* ============================================================
   2. PANELS  .panel — framed content container
   ============================================================ */
.panel {
  background: linear-gradient(180deg, var(--bg-panel) 0%, var(--bg-dark) 100%);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-panel);
  padding: var(--space-lg);
}
.panel-glass {
  background: linear-gradient(180deg, rgba(17,24,39,0.55) 0%, rgba(8,10,22,0.55) 100%);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(42,48,64,0.7);
  border-radius: var(--radius-md);
  box-shadow: 0 2px 8px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.05);
}
.panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding-bottom: var(--space-md);
  margin-bottom: var(--space-md);
  border-bottom: 1px solid var(--border-subtle);
}
.panel-head .panel-title {
  font-size: var(--font-md);
  letter-spacing: 2px;
  color: var(--accent-cyan);
  text-transform: uppercase;
  font-weight: 700;
}

/* Tactical corner brackets — optional overlay on any relative container */
.tactical-corners { position: relative; }
.tactical-corners::before, .tactical-corners::after,
.tactical-corners > .tc-br, .tactical-corners > .tc-bl {
  content: ''; position: absolute; width: 12px; height: 12px;
  border: 1.5px solid var(--accent-cyan);
  pointer-events: none;
}
.tactical-corners::before { top: -1px; left: -1px; border-right: 0; border-bottom: 0; }
.tactical-corners::after  { top: -1px; right: -1px; border-left: 0; border-bottom: 0; }
.tactical-corners > .tc-bl { bottom: -1px; left: -1px; border-right: 0; border-top: 0; }
.tactical-corners > .tc-br { bottom: -1px; right: -1px; border-left: 0; border-top: 0; }

/* ============================================================
   3. BADGES  .badge — small tag chip
   ============================================================ */
.badge {
  display: inline-flex; align-items: center; gap: 4px;
  height: 16px;
  padding: 0 6px;
  font-family: var(--font-mono);
  font-size: var(--font-xs);
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  line-height: 1;
}
.badge-kinetic  { color: var(--class-kinetic);  background: rgba(239,83,80,0.12);  border-color: rgba(239,83,80,0.3); }
.badge-electric { color: var(--class-electric); background: rgba(255,152,0,0.12);  border-color: rgba(255,152,0,0.3); }
.badge-psionic  { color: var(--class-psionic);  background: rgba(38,198,218,0.12); border-color: rgba(38,198,218,0.3); }
.badge-t1 { color: var(--tier-1); background: rgba(136,136,136,0.15);    border-color: rgba(136,136,136,0.35); }
.badge-t2 { color: var(--tier-2); background: rgba(79,195,247,0.12);     border-color: rgba(79,195,247,0.3); }
.badge-t3 { color: var(--tier-3); background: rgba(255,215,0,0.12);      border-color: rgba(255,215,0,0.3); }
.badge-uncommon  { color: var(--rarity-uncommon);  background: rgba(102,187,106,0.12); border-color: rgba(102,187,106,0.3); }
.badge-rare      { color: var(--rarity-rare);      background: rgba(66,165,245,0.12); border-color: rgba(66,165,245,0.3); }
.badge-epic      { color: var(--rarity-epic);      background: rgba(224,64,251,0.12); border-color: rgba(224,64,251,0.3); }
.badge-legendary { color: var(--rarity-legendary); background: rgba(255,215,0,0.12);  border-color: rgba(255,215,0,0.3); }
.badge-equipped { color: var(--accent-cyan); background: rgba(79,195,247,0.12); border-color: rgba(79,195,247,0.3); }
.badge-new  { color: var(--accent-gold); background: rgba(255,215,0,0.15);  border-color: rgba(255,215,0,0.4); }
.badge-dim { color: var(--text-dim); background: rgba(136,136,136,0.1); border-color: var(--border-subtle); }

/* ============================================================
   4. PILLS  .pill — status indicator with colored dot
   ============================================================ */
.pill {
  display: inline-flex; align-items: center; gap: 5px;
  height: 18px;
  padding: 0 8px;
  border-radius: var(--radius-pill);
  font-size: var(--font-xs);
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  background: rgba(8,10,22,0.6);
  border: 1px solid;
  line-height: 1;
}
.pill .pill-dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.pill-green  { color: var(--accent-green);  border-color: rgba(102,187,106,0.35); }
.pill-cyan   { color: var(--accent-cyan);   border-color: rgba(79,195,247,0.35); }
.pill-orange { color: var(--accent-orange); border-color: rgba(255,152,0,0.35); }
.pill-red    { color: var(--accent-red);    border-color: rgba(239,83,80,0.35); }
.pill-gold   { color: var(--accent-gold);   border-color: rgba(255,215,0,0.35); }
.pill-purple { color: var(--accent-purple); border-color: rgba(224,64,251,0.35); }

/* ============================================================
   5. STAT ROW  .stat-row — label / value line
   ============================================================ */
.stat-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-sm) 0;
  font-size: var(--font-md);
}
.stat-row + .stat-row { border-top: 1px solid var(--border-subtle); }
.stat-row .stat-label {
  font-size: var(--font-sm);
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: var(--text-dim);
}
.stat-row .stat-value {
  font-weight: 700;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   6. PROGRESS BAR  .bar
   ============================================================ */
.bar {
  height: 3px;
  background: var(--bg-darker);
  border-radius: 2px;
  overflow: hidden;
}
.bar-fill {
  display: block; height: 100%;
  background: var(--accent-cyan);
  box-shadow: 0 0 4px rgba(79,195,247,0.5);
  transition: width .3s ease;
}
.bar-fill.gold    { background: linear-gradient(90deg, var(--accent-orange), var(--accent-gold)); box-shadow: 0 0 6px rgba(255,215,0,0.5); }
.bar-fill.red     { background: var(--accent-red);    box-shadow: 0 0 4px rgba(239,83,80,0.5); }
.bar-fill.green   { background: var(--accent-green);  box-shadow: 0 0 4px rgba(102,187,106,0.5); }
.bar-fill.purple  { background: var(--accent-purple); box-shadow: 0 0 4px rgba(224,64,251,0.5); }
.bar-thick { height: 6px; }
.bar-thin  { height: 2px; }

/* ============================================================
   7. TABS  .tab-bar + .tab
   ============================================================ */
.tab-bar {
  display: flex;
  border-bottom: 1px solid var(--border-subtle);
}
.tab {
  flex: 1;
  height: 36px;
  background: transparent;
  border: 0;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: var(--font-sm);
  letter-spacing: 1.8px;
  font-weight: 700;
  text-transform: uppercase;
  cursor: pointer;
  position: relative;
  transition: all .15s;
}
.tab:hover { color: var(--text-primary); background: rgba(79,195,247,0.05); }
.tab.is-active { color: var(--accent-cyan); }
.tab.is-active::before {
  content: ''; position: absolute; top: 0; left: 20%; right: 20%; height: 2px;
  background: var(--accent-cyan);
  box-shadow: 0 0 8px var(--accent-cyan);
}
.tab-gold { color: var(--accent-gold); }
.tab-gold:hover { background: rgba(255,215,0,0.08); color: var(--accent-gold); }
.tab .tab-badge {
  position: absolute; top: 5px; right: 18%;
  min-width: 14px; height: 14px; padding: 0 4px;
  background: var(--accent-red);
  border-radius: 7px;
  color: #fff; font-size: 10px; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 6px rgba(239,83,80,0.5);
}

/* ============================================================
   8. FILTER PILLS — segmented selector
   ============================================================ */
.filter-bar {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  /* Transparent — lets translucent panel backgrounds (history-panel,
     squad modal, etc.) show through. Solid `--bg-darker` was reading as
     a black block against panels with rgba backgrounds. The subtle steel
     border + the active pill's cyan tint carry the segmented-control
     visual without needing a container fill. */
  background: transparent;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}
.filter-pill {
  padding: 4px 10px;
  font-size: var(--font-sm);
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--text-dim);
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: all .15s;
}
.filter-pill:hover { color: var(--text-primary); }
.filter-pill.is-active { color: var(--accent-cyan); background: rgba(79,195,247,0.12); }

/* ============================================================
   9. CARD  .ds-card — icon + title/subtitle + count row
   ============================================================ */
.ds-card {
  display: flex; align-items: center; gap: var(--space-md);
  padding: var(--space-md);
  background: var(--bg-panel);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all .15s;
  color: var(--text-primary);
  text-align: left;
}
.ds-card:hover { border-color: var(--border-steel); background: var(--bg-panel-hover); transform: translateY(-1px); }
.ds-card .ds-card-icon {
  width: 36px; height: 36px;
  border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
  font-size: 18px;
  background: var(--bg-slot);
  border: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.ds-card .ds-card-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ds-card .ds-card-title { font-size: var(--font-lg); font-weight: 700; color: var(--text-bright); text-transform: uppercase; letter-spacing: 1px; }
.ds-card .ds-card-subtitle { font-size: var(--font-sm); color: var(--text-dim); letter-spacing: 0.5px; }
.ds-card .ds-card-count { font-size: var(--font-xl); font-weight: 800; color: var(--text-bright); font-variant-numeric: tabular-nums; }

/* Compact variant (building rail, unit list) */
.ds-card-compact {
  display: grid;
  grid-template-columns: 32px 1fr auto;
  gap: var(--space-md);
  padding: var(--space-sm);
  align-items: center;
  background: linear-gradient(180deg, rgba(17,24,39,0.55) 0%, rgba(8,10,22,0.55) 100%);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(42,48,64,0.7);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all .15s;
}
.ds-card-compact:hover {
  border-color: var(--accent-cyan);
  transform: translateY(-1px);
  box-shadow: 0 4px 14px rgba(0,0,0,0.5), 0 0 10px rgba(79,195,247,0.2);
}
.ds-card-compact.is-ready { border-color: rgba(102,187,106,0.45); }
.ds-card-compact.is-upgrading { border-color: rgba(255,215,0,0.5); }

/* ============================================================
   10. MODAL  .modal-backdrop + .modal + variants
   ============================================================ */
.modal-backdrop {
  position: absolute; inset: 0;
  background: rgba(3,5,10,0.72);
  backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center;
  z-index: 1000;
  padding: var(--space-lg);
  animation: mbd-in .18s ease-out both;
}
@keyframes mbd-in { from { opacity: 0; } to { opacity: 1; } }

.modal {
  background: linear-gradient(180deg, rgba(17,24,39,0.92) 0%, rgba(8,10,22,0.95) 100%);
  backdrop-filter: blur(14px);
  border: 1px solid var(--border-steel);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font-family: var(--font-mono);
  position: relative;
  display: flex; flex-direction: column;
  box-shadow: var(--shadow-modal);
  animation: m-in .2s cubic-bezier(.2,.8,.2,1) both;
  overflow: hidden;
  max-width: 100%;
}
@keyframes m-in { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: none; } }

.modal-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: var(--space-md) var(--space-lg);
  height: 36px;
  border-bottom: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.modal-title {
  font-size: var(--font-md);
  letter-spacing: 1.5px;
  color: var(--accent-cyan);
  text-transform: uppercase;
  font-weight: 700;
}
.modal-close {
  width: 22px; height: 22px;
  background: transparent; border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-family: inherit; font-size: var(--font-md);
  cursor: pointer; transition: all .15s;
  display: flex; align-items: center; justify-content: center;
}
.modal-close:hover { border-color: var(--accent-red); color: var(--accent-red); background: rgba(239,83,80,0.08); }
.modal-body { padding: var(--space-lg); overflow: auto; flex: 1; }
.modal-footer {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  border-top: 1px solid var(--border-subtle);
  background: rgba(6,6,16,0.4);
  flex-shrink: 0;
}

/* Modal variants */
.modal-tactical {
  border-color: rgba(79,195,247,0.4);
  box-shadow: 0 30px 80px rgba(0,0,0,0.8), 0 0 40px rgba(79,195,247,0.2);
}

/* ============================================================
   11. FORM CONTROLS
   ============================================================ */
.field { display: flex; flex-direction: column; gap: var(--space-xs); }
.field-label {
  font-size: var(--font-xs);
  letter-spacing: 1.5px;
  color: var(--text-dim);
  text-transform: uppercase;
  font-weight: 600;
  display: flex; justify-content: space-between; align-items: baseline;
}
.field-required { color: var(--accent-red); margin-left: 3px; }
.input, .select, .textarea {
  width: 100%;
  height: 28px;
  padding: 0 8px;
  background: var(--bg-slot);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--font-sm);
  color: var(--text-primary);
  transition: all .15s;
}
.textarea { height: auto; padding: 8px; resize: vertical; min-height: 60px; }
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--accent-cyan);
  box-shadow: 0 0 0 2px rgba(79,195,247,0.2);
}
.input:disabled { opacity: 0.5; cursor: not-allowed; }

/* ============================================================
   11b. FORM PATTERNS — small layout primitives shared across
   login, modal forms, and any future credential surfaces.

   IMPORTANT: these rules live here (not in client/index.html's
   inline <style> block) so the mobile media queries in styles.css
   can override them. Inline <style> in <head> is loaded AFTER
   styles.css and would otherwise shadow the responsive rules.
   ============================================================ */
.form-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-md);
}
.wordmark {
  font-family: var(--font-mono);
  font-size: var(--font-lg);
  font-weight: 700;
  letter-spacing: 3px;
  color: var(--text-bright);
  text-transform: uppercase;
}
.or-rule {
  display: flex; align-items: center; gap: var(--space-md);
  margin: var(--space-sm) 0 var(--space-xs);
}
.or-rule .rule {
  flex: 1; height: 1px;
  background: linear-gradient(90deg, transparent, var(--border-steel), transparent);
}
.or-rule span {
  font-family: var(--font-mono);
  font-size: 9px; letter-spacing: 2px;
  color: var(--text-dim);
  text-transform: uppercase;
}

/* Login form panel — sized for desktop. Mobile widths/paddings live
   in styles.css media queries (loaded later, so they win on cascade). */
.login-box {
  width: 308px;
  padding: 18px 20px 20px;
  display: flex; flex-direction: column; gap: 10px;
}
.login-error { min-height: 14px; margin: 0; }

/* ============================================================
   12. RESOURCE BAR CHIP
   ============================================================ */
.res-chip {
  display: inline-flex; align-items: center; gap: var(--space-sm);
  padding: 3px 10px 3px 4px;
  background: rgba(13,21,32,0.35);
  border: 1px solid rgba(26,32,48,0.7);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all .15s;
  position: relative;
}
.res-chip:hover { border-color: var(--border-steel); background: rgba(26,34,53,0.5); }
.res-chip .res-ico { width: 22px; height: 22px; flex-shrink: 0; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5)); }
.res-chip .res-body { display: flex; flex-direction: column; gap: 2px; min-width: 48px; }
.res-chip .res-value { font-size: var(--font-md); font-weight: 700; color: #fff; letter-spacing: 0.3px; }
.res-chip .res-bar { height: 2px; background: var(--bg-darker); border-radius: 1px; overflow: hidden; }
.res-chip .res-bar-fill { display: block; height: 100%; transition: width .3s ease; }
.res-chip.is-warn { border-color: rgba(255,152,0,0.4); box-shadow: 0 0 8px rgba(255,152,0,0.15); }
.res-chip.is-warn .res-value { color: var(--accent-orange); }
.res-chip.is-premium .res-value { color: var(--accent-gold); }
.res-chip .res-plus {
  position: absolute; right: 3px; top: 50%; transform: translateY(-50%);
  width: 14px; height: 14px;
  background: var(--accent-gold); color: #0a0c14;
  border-radius: 3px;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 800; line-height: 1;
}

/* ============================================================
   13. LAYOUT PRIMITIVES
   ============================================================ */
.flex         { display: flex; }
.flex-col     { display: flex; flex-direction: column; }
.flex-between { display: flex; justify-content: space-between; align-items: center; }
.flex-center  { display: flex; align-items: center; justify-content: center; }
.flex-1       { flex: 1; min-width: 0; }

.gap-xs { gap: var(--space-xs); }
.gap-sm { gap: var(--space-sm); }
.gap-md { gap: var(--space-md); }
.gap-lg { gap: var(--space-lg); }
.gap-xl { gap: var(--space-xl); }

.mb-sm { margin-bottom: var(--space-sm); }
.mb-md { margin-bottom: var(--space-md); }
.mb-lg { margin-bottom: var(--space-lg); }
.mb-xl { margin-bottom: var(--space-xl); }

.p-sm { padding: var(--space-sm); }
.p-md { padding: var(--space-md); }
.p-lg { padding: var(--space-lg); }
.p-xl { padding: var(--space-xl); }

.text-center { text-align: center; }
.text-right  { text-align: right; }
.text-dim       { color: var(--text-dim); }
.text-secondary { color: var(--text-secondary); }
.text-bright    { color: var(--text-bright); }
.text-cyan      { color: var(--accent-cyan); }
.text-red       { color: var(--accent-red); }
.text-green     { color: var(--accent-green); }
.text-gold      { color: var(--accent-gold); }
.text-purple    { color: var(--accent-purple); }
.text-bold      { font-weight: 700; }
.text-uppercase { text-transform: uppercase; letter-spacing: 1.2px; }
.mono           { font-family: var(--font-mono); }

.text-xs  { font-size: var(--font-xs); }
.text-sm  { font-size: var(--font-sm); }
.text-md  { font-size: var(--font-md); }
.text-lg  { font-size: var(--font-lg); }
.text-xl  { font-size: var(--font-xl); }
.text-2xl { font-size: var(--font-2xl); }

/* ============================================================
   14. MOTION — standard keyframes (use sparingly)
   ============================================================ */
@keyframes at-pulse {
  0%, 100% { opacity: 0.75; }
  50% { opacity: 1; }
}
@keyframes at-glow-line {
  0%, 100% { opacity: 0.3; box-shadow: 0 0 4px currentColor; }
  50%      { opacity: 1;   box-shadow: 0 0 12px currentColor; }
}
@keyframes at-scan {
  0%   { transform: translateY(0); }
  100% { transform: translateY(100%); }
}
@keyframes at-notif-pop {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.2); }
}
.anim-pulse      { animation: at-pulse 1.6s ease-in-out infinite; }
.anim-glow-line  { animation: at-glow-line 1.6s ease-in-out infinite; }
.anim-scan       { animation: at-scan 3s linear infinite; }
.anim-notif-pop  { animation: at-notif-pop .8s ease-in-out infinite; }

/* Commander XP result state inside rankUpModal. */
@keyframes rankup-xp-pop {
  0%   { opacity: 0; transform: translateY(8px) scale(0.94); }
  70%  { opacity: 1; transform: translateY(-2px) scale(1.03); }
  100% { opacity: 1; transform: none; }
}
@keyframes rankup-xp-ring {
  0%   { opacity: 0.95; transform: translate(-50%, -50%) scale(0.62); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.62); }
}
@keyframes rankup-xp-fill {
  0%   { width: 0; }
  100% { width: var(--rankup-xp-progress, 0%); }
}
.rankup-xp-result {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
  padding: 2px 0 4px;
  font-family: var(--font-mono);
  animation: rankup-xp-pop 420ms ease-out both;
}
.rankup-xp-result__burst {
  position: relative;
  width: 86px;
  height: 86px;
  display: grid;
  place-items: center;
  margin: 2px 0 4px;
  border: 1px solid rgba(79,195,247,0.45);
  border-radius: 50%;
  background:
    radial-gradient(circle, rgba(79,195,247,0.26) 0%, rgba(79,195,247,0.08) 48%, rgba(79,195,247,0) 72%),
    rgba(5,8,14,0.85);
  box-shadow: 0 0 22px rgba(79,195,247,0.36);
}
.rankup-xp-result--levelup .rankup-xp-result__burst {
  border-color: rgba(255,215,0,0.62);
  background:
    radial-gradient(circle, rgba(255,215,0,0.30) 0%, rgba(79,195,247,0.10) 50%, rgba(255,215,0,0) 74%),
    rgba(8,7,2,0.88);
  box-shadow: 0 0 24px rgba(255,215,0,0.42), 0 0 48px rgba(79,195,247,0.18);
}
.rankup-xp-result--levelup .rankup-xp-result__burst::before,
.rankup-xp-result--levelup .rankup-xp-result__burst::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 92px;
  height: 92px;
  border: 1px solid rgba(255,215,0,0.58);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  animation: rankup-xp-ring 1.15s ease-out infinite;
}
.rankup-xp-result--levelup .rankup-xp-result__burst::after {
  animation-delay: 220ms;
  border-color: rgba(79,195,247,0.38);
}
.rankup-xp-result__level {
  color: var(--text-bright);
  font-size: 32px;
  font-weight: 900;
  line-height: 1;
  text-shadow: 0 0 14px currentColor;
}
.rankup-xp-result__kicker {
  color: var(--accent-cyan);
  font-size: 10px;
  font-weight: 900;
  letter-spacing: 2.4px;
  text-transform: uppercase;
}
.rankup-xp-result--levelup .rankup-xp-result__kicker {
  color: var(--accent-gold);
}
.rankup-xp-result__name {
  color: var(--text-bright);
  font-size: 20px;
  font-weight: 900;
  letter-spacing: 1.4px;
}
.rankup-xp-result__levels {
  color: var(--text-secondary);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1.4px;
  text-transform: uppercase;
}
.rankup-xp-result__levels span {
  color: var(--accent-cyan);
  padding: 0 5px;
}
.rankup-xp-result__chips {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
  width: 100%;
  margin-top: 4px;
}
.rankup-xp-result__chip {
  padding: 4px 8px;
  border: 1px solid rgba(79,195,247,0.34);
  border-radius: 3px;
  background: rgba(79,195,247,0.08);
  color: var(--text-primary);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.9px;
  text-transform: uppercase;
}
.rankup-xp-result--levelup .rankup-xp-result__chip {
  border-color: rgba(255,215,0,0.34);
  background: rgba(255,215,0,0.08);
}
.rankup-xp-result__meter {
  width: 100%;
  height: 8px;
  margin-top: 8px;
  overflow: hidden;
  border: 1px solid rgba(79,195,247,0.32);
  border-radius: 999px;
  background: rgba(255,255,255,0.08);
}
.rankup-xp-result__meter-fill {
  height: 100%;
  width: var(--rankup-xp-progress, 0%);
  border-radius: inherit;
  background: linear-gradient(90deg, var(--accent-purple), var(--accent-cyan));
  box-shadow: 0 0 12px rgba(79,195,247,0.42);
  animation: rankup-xp-fill 720ms ease-out 160ms both;
}
.rankup-xp-result--levelup .rankup-xp-result__meter-fill {
  background: linear-gradient(90deg, var(--accent-cyan), var(--accent-gold));
  box-shadow: 0 0 14px rgba(255,215,0,0.45);
}
.rankup-xp-result__progress {
  color: var(--text-dim);
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 1.2px;
  text-transform: uppercase;
}
@media (prefers-reduced-motion: reduce) {
  .rankup-xp-result,
  .rankup-xp-result--levelup .rankup-xp-result__burst::before,
  .rankup-xp-result--levelup .rankup-xp-result__burst::after,
  .rankup-xp-result__meter-fill {
    animation: none;
  }
}

/* ============================================================
   15. RUN SYSTEM — squad rail + detail modal
   Pixel-perfect from design handoff (Phase 5).
   Mobile landscape priority — modal uses clamp() to scale to viewport.
   ============================================================ */

/* ── keyframes (referenced by both rail tiles and modal critical states) ── */
@keyframes heat-pulse {
  0%, 100% { filter: brightness(1); }
  50%      { filter: brightness(1.3); }
}
@keyframes bang-pulse {
  0%, 100% { transform: scale(1);   box-shadow: 0 0 0 1.5px #1a0e00, 0 0 6px rgba(255,152,0,0.6); }
  50%      { transform: scale(1.15); box-shadow: 0 0 0 1.5px #1a0e00, 0 0 14px rgba(255,152,0,0.95); }
}
@keyframes critical-edge {
  0%, 100% { box-shadow: 0 0 8px rgba(239,83,80,0.35); }
  50%      { box-shadow: 0 0 18px rgba(239,83,80,0.7); }
}
@keyframes sweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

/* ── rail container (anchored to LEFT edge of world map screen) ── */
.squad-rail {
  position: fixed;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 60px;
  padding: 8px 6px;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  align-self: flex-start;
  border: 1px solid var(--border-steel);
  border-left: none;
  border-top-right-radius: 6px;
  border-bottom-right-radius: 6px;
  backdrop-filter: blur(6px);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.04), 2px 0 8px rgba(0,0,0,0.4);
  background: linear-gradient(180deg, rgba(8,10,22,0.72), rgba(6,8,18,0.78));
  z-index: 30;
  pointer-events: auto;
}

/* ── rail slot (48×48 chunky portrait tile) ── */
.rail-slot {
  width: 48px;
  height: 48px;
  padding: 0;
  position: relative;
  border: none;
  background: transparent;
  cursor: pointer;
}
.rail-slot.is-idle { filter: saturate(0.5) brightness(0.75); }
.rail-slot__bezel {
  position: absolute;
  inset: 0;
  border-radius: 6px;
  background: linear-gradient(180deg, #2a3040, #11151c);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), 0 1px 2px rgba(0,0,0,0.6);
}
.rail-slot__ring {
  position: absolute;
  inset: 2px;
  border-radius: 4px;
  border: 2px solid var(--ring-color, var(--accent-cyan));
  box-shadow: inset 0 0 4px rgba(255,255,255,0.06);
}
.rail-slot__ring.is-event {
  box-shadow: 0 0 8px var(--ring-color);
}
.rail-slot__ring.is-critical {
  box-shadow: 0 0 8px var(--ring-color), inset 0 0 6px rgba(239,83,80,0.33);
  animation: critical-edge 1.4s ease-in-out infinite;
}
.rail-slot__ring.is-hot {
  animation: heat-pulse 1.4s ease-in-out infinite;
}
.rail-slot__portrait {
  position: absolute;
  inset: 5px;
  border-radius: 3px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--portrait-bg, linear-gradient(135deg, rgba(79,195,247,0.13), var(--bg-panel)));
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 13px;
  color: #fff;
  letter-spacing: 1px;
}
.rail-slot__portrait::after {
  /* class-tinted edge wash for cohesion */
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, transparent 60%, var(--cls-color, transparent) 33%);
  background: linear-gradient(180deg, transparent 60%, var(--cls-wash, transparent));
}
.rail-slot__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.rail-slot__heatfill {
  position: absolute;
  left: 3px;
  top: 3px;
  bottom: 3px;
  width: 3px;
  background: rgba(0,0,0,0.55);
  border-radius: 2px;
  overflow: hidden;
  border: 1px solid rgba(0,0,0,0.6);
}
.rail-slot__heatfill > div {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--heat-color, var(--heat-cold));
  transition: height 0.3s ease;
}
/* Status corner badge (top-right) */
.rail-slot__status {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--status-bg, #101018);
  border: 1px solid var(--status-color, var(--text-dim));
  color: var(--status-color, var(--text-dim));
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 1;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 2px rgba(0,0,0,0.6);
}
/* Event ! badge (top-left) */
.rail-slot__bang {
  position: absolute;
  top: -5px;
  left: -5px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent-orange);
  color: #1a0e00;
  font-family: var(--font-mono);
  font-weight: 900;
  font-size: 11px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 1.5px #1a0e00, 0 0 10px rgba(255,152,0,0.85);
  animation: bang-pulse 0.8s ease-in-out infinite;
}
/* Urgent variant — red badge for urgency:'high' cards. The card's
   safe_default is a run-ender (squad recall / commander injury), so
   ignoring this one materially hurts. Bigger glow, redder color. */
.rail-slot__bang--urgent {
  background: var(--accent-red);
  color: #1a0a08;
  box-shadow: 0 0 0 1.5px #1a0a08, 0 0 14px rgba(239, 83, 80, 0.95);
}
/* Rank pip (bottom-right) */
.rail-slot__rank {
  position: absolute;
  bottom: -3px;
  right: -3px;
  min-width: 14px;
  height: 12px;
  padding: 0 3px;
  border-radius: 2px;
  background: #0a0a14;
  border: 1px solid var(--border-steel);
  color: var(--accent-cyan);
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Empty slot (no commander) */
.rail-slot--empty {
  border: 1px dashed var(--border-steel);
  border-radius: 6px;
  background: rgba(8,10,22,0.55);
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 18px;
  line-height: 1;
  font-weight: 300;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ── Squad detail modal (landscape) ──
   Stretches between the top resource bar and the bottom nav bar so the
   player can still see (and interact with) the chrome. Scrim is fully
   transparent and pointer-events: none so the rail, chat, HUD buttons,
   and canvas underneath remain clickable. The modal itself owns its
   own pointer-events; close via ✕ or Esc only. */
.squad-modal-scrim {
  position: fixed;
  /* Sit between the top resource bar (36px) and bottom nav bar (32–36px),
     with a small breathing pad so the modal border is visible. */
  top: 40px;
  bottom: 40px;
  left: 0;
  right: 0;
  z-index: 50;
  display: flex;
  align-items: stretch;
  justify-content: center;
  pointer-events: none;        /* clicks fall through to underlying UI */
  background: transparent;     /* unblocked, undimmed */
}
/* The TASKS badge is "background tier" — stays visible above the squad
   modal. Only the tutorial panel hides for modal focus. */
body.squad-modal-open .tutorial-panel {
  display: none !important;
}
.squad-modal {
  pointer-events: auto;        /* modal itself catches clicks */
  width: clamp(320px, 92vw, 720px);
  /* Fill the available vertical space between top/bottom HUD chrome
     instead of a fixed height. The flex parent uses align-items: stretch
     so this works automatically. */
  align-self: stretch;
  background: linear-gradient(180deg, rgba(11,15,30,0.97), rgba(6,8,18,0.97));
  border: 1px solid var(--border-steel);
  border-radius: 6px;
  box-shadow: 0 4px 24px rgba(0,0,0,0.7), 0 0 16px rgba(79,195,247,0.18);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  font-family: var(--font-mono);
  color: var(--text-primary);
  /* Anchors children with position:absolute (e.g. .squad-popup overlay). */
  position: relative;
}

/* Squad popup — replaces the sidebar SQUAD block during extracting/critical
   scenes. Triggered by the footer "Squad" button; overlays the modal
   interior, click backdrop or × to close. The card centers; popup-card
   reuses the standard .sm-block chrome since squadBlock is reused as-is. */
.squad-popup {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
}
.squad-popup__scrim {
  position: absolute;
  inset: 0;
  background: rgba(6, 8, 18, 0.7);
  backdrop-filter: blur(2px);
  cursor: pointer;
}
.squad-popup__card {
  position: relative;
  /* Vertical-leaning card — the formation grid is column-stacked
     (3 columns rendered top-to-bottom with their front+back slots),
     so a narrow card reads cleanly without wasted side margin. Tracks
     between 240px (mobile-landscape min) and 300px (modal-friendly max). */
  width: clamp(240px, 60%, 300px);
  background: var(--bg-panel);
  border: 1px solid var(--accent-cyan);
  border-radius: 6px;
  box-shadow:
    0 8px 28px rgba(0,0,0,0.75),
    0 0 18px rgba(79,195,247,0.30),
    inset 0 1px 0 rgba(255,255,255,0.06);
  padding: 12px 16px 18px;
  animation: squad-popup-in 220ms cubic-bezier(.2,1.4,.3,1);
}
/* Popup-scoped formation-grid sizing — the squad-modal sidebar's 44×44
   slots are constrained to share column space with HEAT and HAUL. Inside
   the popup the grid has the whole card to itself, so we bump slots up
   to 60×60 for legibility. Bigger thumb, bigger count overlay, more
   breathing room between rows. */
.squad-popup .fg-grid { gap: 6px; }
.squad-popup .fg-row  { gap: 6px; }
.squad-popup .fg-slot,
.squad-popup .fg-slot.fg-static {
  width: 60px;
  height: 60px;
  flex: 0 0 60px;
  padding: 3px;
}
.squad-popup .fg-thumb { width: 52px; height: 52px; }
.squad-popup .fg-count-overlay {
  font-size: 12px;
  font-weight: 800;
  min-width: 18px;
}
.squad-popup .fg-tier-pip {
  font-size: 9px;
}
.squad-popup__card .squad-modal-fg-host {
  padding-top: 8px;
}
.squad-popup__close {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 24px;
  height: 24px;
  background: transparent;
  border: 1px solid var(--border-steel);
  border-radius: 3px;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
}
.squad-popup__close:hover {
  color: var(--text-bright);
  border-color: var(--accent-cyan);
}
.squad-popup__card .sm-block {
  margin: 0;
  border: 0;
  background: transparent;
  padding: 0;
}
@keyframes squad-popup-in {
  0%   { opacity: 0; transform: scale(0.92) translateY(8px); }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}
.squad-modal--idle {
  width: clamp(320px, 78vw, 560px);
}
@media (max-height: 480px) {
  /* Mobile-landscape: tighter chrome → bring the modal closer to the bars. */
  .squad-modal-scrim { top: 32px; bottom: 32px; }
}
.squad-modal__header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border-steel);
  background: linear-gradient(180deg, var(--cls-tint, transparent), transparent);
  flex-shrink: 0;
  height: 48px;
}
.squad-modal__name {
  font-size: 12px;
  color: #fff;
  letter-spacing: 1.5px;
  margin: 0;
}
.squad-modal__close {
  margin-left: auto;
  width: 22px;
  height: 22px;
  border: 1px solid var(--border-steel);
  border-radius: 3px;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  font: inherit;
  font-size: 11px;
  line-height: 1;
  padding: 0;
}
.squad-modal__close:hover { color: var(--text-primary); border-color: var(--border-glow); }
.squad-modal__body {
  flex: 1;
  padding: 8px 10px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  overflow: hidden;
  min-height: 0;
}
.squad-modal__body--two-col   { grid-template-columns: repeat(2, 1fr); }
.squad-modal__body--event     { grid-template-columns: 1.7fr 1fr; }
/* Extracting scene: wide LEFT for the slot-machine reveal area, narrow
   RIGHT for the heat / site / squad / haul stack. Same ratio as --event
   so the chrome reads consistently across scenes. */
.squad-modal__body--extracting { grid-template-columns: 1.7fr 1fr; }

/* The reveal column lets its single block fill the column height so the
   crate row + soak card have room. Inside that block, the .sm-block__body
   wrapper grows to fill — see [data-block="reveal"] rules below. */
.squad-modal__col--reveal { gap: 0; }
.squad-modal__col--reveal > .sm-block[data-block="reveal"] {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.squad-modal__col--reveal > .sm-block[data-block="reveal"] > .sm-block__body,
.squad-modal__col--reveal > .sm-block[data-block="reveal"] > div:last-child {
  flex: 1;
  min-height: 0;
}
.squad-modal__col--reveal .reveal-area {
  height: 100%;
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.squad-modal__col--reveal .reveal-crates {
  flex: 1;
  align-items: center;
  justify-content: center;
  min-height: 0;
  gap: 12px;
}
/* When the reveal is the centerpiece, the crates can be larger. */
.squad-modal__col--reveal .reveal-crate {
  width: 72px;
  height: 88px;
  font-size: 28px;
}
.squad-modal__col {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 0;
  overflow: hidden;
}
.squad-modal__footer {
  display: flex;
  gap: 6px;
  padding: 8px 12px;
  border-top: 1px solid var(--border-steel);
  background: rgba(6,8,18,0.5);
  align-items: center;
  flex-shrink: 0;
}
.squad-modal__footer .footer-caption {
  flex: 1;
  font-size: 9px;
  color: var(--text-dim);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  text-align: center;
}

/* Block — sub-panel inside the modal grid */
.sm-block {
  border: 1px solid var(--border-steel);
  border-radius: 4px;
  background: rgba(8,10,22,0.7);
  padding: 6px 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-height: 0;
  overflow: hidden;
}
.sm-block.is-dim   { background: rgba(8,10,22,0.4); opacity: 0.65; }
.sm-block.is-accent-red { border-color: var(--heat-critical); box-shadow: 0 0 8px rgba(239,83,80,0.2); }
.sm-block__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 4px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border-subtle);
  flex-shrink: 0;
}
.sm-block__title {
  font-size: 9px;
  color: var(--text-dim);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  margin: 0;
  font-weight: 700;
}
.sm-block__right {
  font-size: 9px;
  color: var(--text-dim);
  letter-spacing: 0.5px;
}

/* Heat meter inside HeatBlock */
.heat-meter {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.heat-meter__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.heat-meter__label {
  font-size: 9px;
  letter-spacing: 1px;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--heat-color, var(--heat-cold));
}
.heat-meter__value {
  font-size: 11px;
  font-weight: 700;
  color: #fff;
}
.heat-meter__value small { color: #777; font-size: 9px; }
.heat-meter__rate {
  margin-left: 8px;
  color: var(--text-secondary);
  font-size: 9px;
  font-weight: 700;
  font-family: var(--font-mono);
  letter-spacing: 0.5px;
}
.heat-meter__bar {
  height: 10px;
  background: var(--bg-darker);
  border: 1px solid var(--border-subtle);
  border-radius: 2px;
  overflow: hidden;
  position: relative;
}
.heat-meter__bar.is-mini { height: 4px; }
.heat-meter__fill {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--heat-color, var(--heat-cold));
  transition: width 0.3s ease, background 0.3s;
}
.heat-meter__fill.is-pulse { animation: heat-pulse 1.4s ease-in-out infinite; }
.heat-meter__fill.is-critical-pulse { animation: heat-pulse 0.8s ease-in-out infinite; box-shadow: 0 0 8px var(--heat-color); }
.heat-meter__tick {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: rgba(255,255,255,0.18);
}
/* Status pip / chip (used in modal header) */
.status-pip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 8px;
  letter-spacing: 1px;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--pip-color, var(--text-dim));
}
.class-chip {
  display: inline-flex;
  padding: 1px 6px;
  font-size: 9px;
  letter-spacing: 1px;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--cls-color, var(--accent-cyan));
  border: 1px solid color-mix(in srgb, var(--cls-color, var(--accent-cyan)) 33%, transparent);
  background: color-mix(in srgb, var(--cls-color, var(--accent-cyan)) 12%, transparent);
  border-radius: 3px;
}

/* Small badge (used in block "right" and header chips) */
.sm-badge {
  display: inline-flex;
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--badge-color, var(--accent-cyan));
  border: 1px solid color-mix(in srgb, var(--badge-color, var(--accent-cyan)) 40%, transparent);
  background: color-mix(in srgb, var(--badge-color, var(--accent-cyan)) 12%, transparent);
}

/* HAUL block — prominent carry bar + cargo readout + chip grid.
   Each chip is a uniform PNG-icon + value capsule. Mirrors the canvas
   haul panel in worldMapScreen._drawCargoTracker (rarity-tinted border,
   bold tabular value, no labels, empties hidden). */
.haul-bar {
  height: 8px;
  background: var(--bg-darker);
  border: 1px solid var(--border-subtle);
  border-radius: 3px;
  overflow: hidden;
  position: relative;
  flex-shrink: 0;
}
.haul-bar__fill {
  height: 100%;
  transition: width 0.3s ease, background 0.3s;
  box-shadow: 0 0 6px rgba(79,195,247,0.4);
}
.haul-cargo {
  font-size: 10px;
  font-weight: 700;
  color: #fff;
  letter-spacing: 0.5px;
  margin-top: 4px;
  font-variant-numeric: tabular-nums;
}
.haul-chips {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 4px;
  margin-top: 6px;
  align-content: start;
  flex: 1;
  min-height: 0;
}
.haul-empty {
  font-size: 10px;
  color: var(--text-dim);
  padding: 8px 0;
  text-align: center;
  letter-spacing: 0.5px;
}

/* Value-only haul container (Phase 2 reveal pipeline). Replaces the
   itemized chip list with one big $ato number. Rarity tiers glow at
   threshold values — see _haulRarityTier in squadDetailModal.js.
   The bank reveal modal is where individual items get itemized. */
.haul-value {
  position: relative;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 4px;
  padding: 10px 8px;
  margin-bottom: 6px;
  border: 1px solid var(--accent-cyan);
  border-radius: 4px;
  background:
    radial-gradient(ellipse at 50% 0%, rgba(79,195,247,0.15), transparent 70%),
    linear-gradient(180deg, rgba(8,10,22,0.6), rgba(11,15,30,0.85));
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.05),
    0 0 12px rgba(79,195,247,0.18);
  transition: box-shadow 280ms ease, border-color 280ms ease;
  overflow: hidden;
}
.haul-value__num {
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 700;
  color: var(--text-bright);
  letter-spacing: 0.5px;
  line-height: 1;
}
.haul-value__suffix {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 1.5px;
  color: var(--accent-cyan);
  text-transform: uppercase;
}
.haul-value--empty {
  font-size: 10px;
  letter-spacing: 2px;
  color: var(--text-dim);
  text-transform: uppercase;
  font-weight: 500;
  padding: 14px 8px;
  background: none;
  border-color: var(--border-steel);
  box-shadow: none;
}
.haul-value--rare {
  border-color: var(--rarity-rare);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.06),
    0 0 14px rgba(66,165,245,0.30);
}
.haul-value--epic {
  border-color: var(--rarity-epic);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.08),
    0 0 16px rgba(224,64,251,0.40),
    0 0 32px rgba(224,64,251,0.18);
}
.haul-value--epic .haul-value__suffix { color: var(--rarity-epic); }
.haul-value--epic::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg,
    transparent 30%,
    rgba(224,64,251,0.18) 50%,
    transparent 70%);
  background-size: 220% 100%;
  animation: haul-value-shimmer 2.8s linear infinite;
  pointer-events: none;
}
.haul-value--legendary {
  border-color: var(--rarity-legendary);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.12),
    0 0 22px rgba(255,215,0,0.50),
    0 0 44px rgba(255,215,0,0.22);
}
.haul-value--legendary .haul-value__suffix { color: var(--rarity-legendary); }
.haul-value--legendary .haul-value__num {
  /* Dual drop-shadow halo per design_handoff README "Text Glow":
     traces the character outline, doesn't fog inside the shape. */
  filter:
    drop-shadow(0 0 6px rgba(255,215,0,0.60))
    drop-shadow(0 0 12px rgba(255,215,0,0.30));
}
.haul-value--legendary::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg,
    transparent 30%,
    rgba(255,215,0,0.22) 50%,
    transparent 70%);
  background-size: 220% 100%;
  animation: haul-value-shimmer 2.4s linear infinite;
  pointer-events: none;
}
@keyframes haul-value-shimmer {
  0%   { background-position:  100% 0; }
  100% { background-position: -100% 0; }
}

/* Reveal area — the per-cycle slot-machine reveal beneath the haul
   value. Cycle progress bar + stage marquee + crate row. Driven by
   the squadDetailModal._onExtractionCycle handler. */
.reveal-area {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed var(--border-subtle);
  display: flex;
  flex-direction: column;
  gap: 6px;
  position: relative;  /* anchors .reveal-panel-wash absolute child */
  transition: box-shadow 320ms ease, border-color 320ms ease;
}
/* Inner gradient wash that tints the reveal-area during soak. Always
   in the DOM; opacity 0 by default. Sits behind crates/marquee/bar via
   z-index, doesn't intercept clicks. Rarity colour and visibility are
   driven by .soak-rare / .soak-epic / .soak-legendary on the parent. */
.reveal-panel-wash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 320ms ease;
  z-index: 0;
  background: radial-gradient(
    ellipse at 50% 35%,
    color-mix(in srgb, var(--rarity-color, var(--accent-cyan)) 24%, transparent) 0%,
    color-mix(in srgb, var(--rarity-color, var(--accent-cyan)) 10%, transparent) 45%,
    transparent 80%
  );
}
.reveal-area.soak-rare      .reveal-panel-wash { opacity: 0.18; --rarity-color: var(--rarity-rare); }
.reveal-area.soak-epic      .reveal-panel-wash { opacity: 0.22; --rarity-color: var(--rarity-epic); }
.reveal-area.soak-legendary .reveal-panel-wash { opacity: 0.28; --rarity-color: var(--rarity-legendary); }
/* Lift bar/marquee/crates above the wash. */
.reveal-area > :not(.reveal-panel-wash) { position: relative; z-index: 1; }
.reveal-bar {
  position: relative;
  height: 3px;
  background: rgba(13, 21, 32, 0.7);
  border-radius: 2px;
  overflow: hidden;
}
.reveal-bar__fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0%;
  background: linear-gradient(90deg, var(--accent-cyan), #fff);
  box-shadow: 0 0 4px rgba(79,195,247,0.7);
  transition: width 240ms linear;
}
.reveal-bar.paused .reveal-bar__fill {
  /* During soak the bar visibly freezes mid-cycle. */
  transition: none;
}
.reveal-marquee {
  font-size: 9px;
  letter-spacing: 1.3px;
  color: var(--text-dim);
  text-transform: uppercase;
  font-weight: 600;
  display: flex;
  justify-content: space-between;
  gap: 8px;
}
.reveal-marquee .reveal-marquee__detail {
  color: var(--text-secondary);
  font-weight: 500;
  letter-spacing: 1px;
}
.reveal-crates {
  display: flex;
  gap: 6px;
  min-height: 56px;
  align-items: center;
  flex-wrap: wrap;
  position: relative;
}
.reveal-crate {
  --rarity-color: var(--rarity-common);
  position: relative;
  width: 44px;
  height: 54px;
  background:
    linear-gradient(180deg, #1a2235 0%, #0d1320 70%, #060914 100%);
  border: 1px solid var(--border-steel);
  border-radius: 3px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  font-size: 18px;
  color: var(--text-dim);
  transition: border-color 350ms ease, box-shadow 350ms ease, transform 350ms cubic-bezier(.2,1.4,.3,1);
  /* `backwards` fill-mode applies the keyframe's 0% state (opacity 0,
     translated up + scaled) BEFORE the animation starts. With zero
     delay that's effectively the first painted frame, so the crate
     still drops in cleanly. We avoid the static `opacity: 0` /
     `transform: translateY(...)` declarations a previous revision used,
     because once a downstream class (.flashing, .spinning) overrides
     the `animation` property the drop's `forwards` fill is dropped —
     the element then falls back to whatever's in the cascade. With
     the static opacity:0 the crate would snap invisible whenever
     stage 2's flash kicked in. Now the cascade defaults to opacity 1
     and there's no invisible base to fall back to. */
  animation: reveal-crate-drop 460ms cubic-bezier(.22,1.32,.36,1) backwards;
  overflow: hidden;
}

/* Label sits at the bottom of the crate, cycles SCAN → SCANNING →
   <RARITY> → <ITEM NAME> across the 3 stages. */
.reveal-crate__label {
  position: absolute;
  bottom: 4px;
  left: 0; right: 0;
  text-align: center;
  font-size: 7px;
  letter-spacing: 1.5px;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  pointer-events: none;
  z-index: 3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 0 2px;
  transition: color 240ms ease;
}
.reveal-crate.locked .reveal-crate__label { color: var(--rarity-color); }
.reveal-crate.scanning .reveal-crate__label {
  color: var(--accent-cyan);
  animation: reveal-crate-label-blink 0.55s ease-in-out infinite;
}
@keyframes reveal-crate-label-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}

/* Placeholder '?' shown during stages 1-2 (closed crate). Hidden when
   stage 3 starts spinning the icon. */
.reveal-crate__qmark {
  position: relative;
  z-index: 2;
  font-size: 26px;
  font-weight: 700;
  color: var(--text-dim);
  filter: drop-shadow(0 0 3px rgba(0,0,0,0.4));
  transition: color 240ms ease, opacity 240ms ease;
}
.reveal-crate.locked .reveal-crate__qmark {
  color: var(--rarity-color);
  opacity: 1;
  /* Drop-shadow halo in rarity color so the placeholder stays
     legible during the stage-2 → stage-3 gap, including on common
     drops where the rarity color matches the crate steel tones. */
  filter: drop-shadow(0 0 4px color-mix(in srgb, var(--rarity-color) 70%, transparent));
}
.reveal-crate.spinning .reveal-crate__qmark,
.reveal-crate.revealed .reveal-crate__qmark { display: none; }

/* Real loot icon — hidden until stage 3 spin begins. Sized to fit
   inside the crate with room for the label below. The slot-machine
   spin swaps the img src rapidly; .c-just is a brief settle bump. */
.reveal-crate__icon {
  position: relative;
  z-index: 2;
  display: none;
  width: 60%;
  height: auto;
  max-height: 60%;
  object-fit: contain;
  filter: drop-shadow(0 0 4px color-mix(in srgb, var(--rarity-color) 60%, transparent));
  transition: transform 240ms cubic-bezier(.2,1.4,.3,1);
}
.reveal-crate.spinning .reveal-crate__icon {
  /* Subtle vertical wobble during the slot-machine spin so swapping
     icons reads as motion, not just frame-flicker. */
  animation: reveal-crate-spin-wobble 0.12s ease-in-out infinite alternate;
}
@keyframes reveal-crate-spin-wobble {
  0%   { transform: translateY(-2px) scale(0.96); }
  100% { transform: translateY(2px)  scale(1.02); }
}
.reveal-crate.c-just .reveal-crate__icon {
  animation: reveal-crate-settle 420ms cubic-bezier(.2,1.4,.3,1);
}
@keyframes reveal-crate-settle {
  0%   { transform: scale(1.6); }
  60%  { transform: scale(0.92); }
  100% { transform: scale(1); }
}
@keyframes reveal-crate-drop {
  0%   { opacity: 0; transform: translateY(-14px) scale(0.85); }
  60%  { opacity: 1; transform: translateY(2px) scale(1.05); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
.reveal-crate__glyph {
  position: relative;
  z-index: 2;
  filter: drop-shadow(0 0 3px color-mix(in srgb, var(--rarity-color) 60%, transparent));
  transition: color 240ms ease, transform 240ms ease;
}
.reveal-crate.scanning::before {
  /* Vertical scan beam sweeping the crate during stage 2. */
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    transparent 0%,
    rgba(79,195,247,0.5) 45%,
    rgba(79,195,247,0.95) 50%,
    rgba(79,195,247,0.5) 55%,
    transparent 100%);
  background-size: 100% 200%;
  animation: reveal-crate-scan 700ms ease-in-out infinite alternate;
  pointer-events: none;
  mix-blend-mode: screen;
}
@keyframes reveal-crate-scan {
  0%   { background-position: 0 -50%; }
  100% { background-position: 0 100%; }
}
.reveal-crate.locked {
  border-color: var(--rarity-color);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.05),
    0 0 8px color-mix(in srgb, var(--rarity-color) 45%, transparent);
}
/* Continuous shimmer sweep on locked rare+ crates. Per-rarity duration
   matches prototype: rare 3.6s / epic 2.8s / legendary 2.4s. */
.reveal-crate.locked.reveal-crate--rarity-rare::after,
.reveal-crate.locked.reveal-crate--rarity-epic::after,
.reveal-crate.locked.reveal-crate--rarity-legendary::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(120deg,
    transparent 30%,
    color-mix(in srgb, var(--rarity-color) 35%, transparent) 50%,
    transparent 70%);
  background-size: 220% 100%;
  animation: reveal-crate-shimmer var(--reveal-crate-shimmer-dur, 3s) linear infinite;
  mix-blend-mode: screen;
  z-index: 1;
}
.reveal-crate.reveal-crate--rarity-rare      { --reveal-crate-shimmer-dur: 3.6s; }
.reveal-crate.reveal-crate--rarity-epic      { --reveal-crate-shimmer-dur: 2.8s; }
.reveal-crate.reveal-crate--rarity-legendary { --reveal-crate-shimmer-dur: 2.4s; }
@keyframes reveal-crate-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}
.reveal-crate.flashing {
  /* Brief flash burst when rarity locks in. */
  animation: reveal-crate-flash 550ms ease-out;
}
@keyframes reveal-crate-flash {
  0%   { transform: scale(1);    filter: brightness(1); }
  35%  { transform: scale(1.18); filter: brightness(1.8); }
  100% { transform: scale(1);    filter: brightness(1); }
}
.reveal-crate.revealed .reveal-crate__glyph {
  color: var(--rarity-color);
}
.reveal-crate--rarity-rare      { --rarity-color: var(--rarity-rare); }
.reveal-crate--rarity-epic      { --rarity-color: var(--rarity-epic); }
.reveal-crate--rarity-uncommon  { --rarity-color: var(--accent-green); }
.reveal-crate--rarity-legendary { --rarity-color: var(--rarity-legendary); }

/* Quantity chip — appended at stage-3 settle by _playCycleReveal (and by
   _seedRevealFromSquad's static render). Mirrors the bank-reveal modal's
   item-card chip (.bank-item-qty-chip) so the "×N" badge reads the same
   across the two reveal surfaces. Positioned in the upper-right of the
   crate; rarity-tinted border. Resources show their amount (1,200+),
   items show their count only when >1 — see the qtyN gate in JS. */
.reveal-crate__qty-chip {
  /* The crate has overflow:hidden (clips the scan beam, shimmer, and
     the .flashing scale(1.18) pulse to within its box), so the chip
     can't protrude outside the corner — sits flush inside the top-right
     instead. Rarity-tinted border + dark fill so it reads on any icon
     behind it. */
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 18px;
  height: 13px;
  padding: 0 4px;
  border: 1px solid var(--rarity-color, var(--text-dim));
  border-radius: 7px;
  background: var(--bg-darker);
  color: var(--text-bright);
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.4px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  box-shadow:
    0 1px 3px rgba(0,0,0,0.7),
    0 0 4px color-mix(in srgb, var(--rarity-color, var(--text-dim)) 40%, transparent);
  z-index: 4;
  pointer-events: none;
}

/* Soak phase — the active crate scales up, siblings dim, panel border
   tints to the rarity. Triggered by .reveal-area.soak-{rarity}. */
.reveal-area.soaking .reveal-crate { opacity: 0.28; filter: blur(0.5px); transition: opacity 350ms ease, filter 350ms ease, transform 350ms cubic-bezier(.2,1.4,.3,1); }
.reveal-area.soaking .reveal-crate.active {
  opacity: 1;
  filter: none;
  z-index: 3;
  box-shadow:
    0 0 16px color-mix(in srgb, var(--rarity-color) 60%, transparent),
    0 0 32px color-mix(in srgb, var(--rarity-color) 30%, transparent);
}
/* Per-rarity active-crate scale (additive escalation: rare 1.22 / epic 1.32 / legendary 1.50 + bob).
   Matches the prototype's per-rarity scale curve from design_handoff/README. */
.reveal-area.soak-rare      .reveal-crate.active { transform: scale(1.22); }
.reveal-area.soak-epic      .reveal-crate.active { transform: scale(1.32); }
.reveal-area.soak-legendary .reveal-crate.active {
  transform: scale(1.50);
  animation: reveal-crate-bob 850ms ease-in-out infinite alternate;
}
@keyframes reveal-crate-bob {
  0%   { transform: scale(1.50) translateY(0); }
  100% { transform: scale(1.54) translateY(-3px); }
}

/* Panel border + outer glow shift on soak — rarity-tinted halo that
   makes the whole reveal area react to the rarity. */
.reveal-area.soak-rare {
  box-shadow: 0 0 14px color-mix(in srgb, var(--rarity-rare) 35%, transparent);
}
.reveal-area.soak-epic {
  box-shadow:
    0 0 18px color-mix(in srgb, var(--rarity-epic) 45%, transparent),
    0 0 38px color-mix(in srgb, var(--rarity-epic) 18%, transparent);
}
.reveal-area.soak-legendary {
  box-shadow:
    0 0 22px color-mix(in srgb, var(--rarity-legendary) 55%, transparent),
    0 0 48px color-mix(in srgb, var(--rarity-legendary) 25%, transparent);
}

/* Legendary soak-card name — gold drop-shadow halo (NOT text-shadow:
   text-shadow blurs inside character shapes; drop-shadow halos the
   actual outline). See design_handoff_extraction_reveal/README.md
   "Implementation Notes — Text Glow". */
.reveal-area.soak-legendary .reveal-soak-card {
  border-color: var(--rarity-legendary);
  box-shadow:
    0 6px 18px rgba(0,0,0,0.6),
    0 0 16px color-mix(in srgb, var(--rarity-legendary) 45%, transparent);
}
.reveal-area.soak-legendary .reveal-soak-card__name {
  color: #fff;
  filter:
    drop-shadow(0 0 3px rgba(255,215,0,0.85))
    drop-shadow(0 0 10px rgba(255,215,0,0.35));
}

/* Screen-wide rarity tint for epic+ soak. Element appended to <body>
   on soak entry, tagged .reveal-transient so _clearTransientFX wipes
   it on cycle overlap / Recall / close. Legendary adds a shimmer sweep. */
.screen-soak-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9998;
  opacity: 0;
  animation: screen-soak-fade-in 320ms ease forwards;
}
.screen-soak-overlay--epic {
  background: radial-gradient(
    ellipse at 50% 45%,
    color-mix(in srgb, var(--rarity-epic) 38%, transparent) 0%,
    color-mix(in srgb, var(--rarity-epic) 14%, transparent) 38%,
    transparent 78%
  );
}
.screen-soak-overlay--legendary {
  background: radial-gradient(
    ellipse at 50% 45%,
    color-mix(in srgb, var(--rarity-legendary) 42%, transparent) 0%,
    color-mix(in srgb, var(--rarity-legendary) 18%, transparent) 38%,
    transparent 78%
  );
}
.screen-soak-overlay--legendary::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    120deg,
    transparent 0%,
    color-mix(in srgb, var(--rarity-legendary) 28%, transparent) 50%,
    transparent 100%
  );
  background-size: 220% 100%;
  animation: legendary-screen-shimmer 3s linear infinite;
  mix-blend-mode: screen;
}
.screen-soak-overlay.fading-out {
  animation: screen-soak-fade-out 420ms ease forwards;
}
@keyframes screen-soak-fade-in {
  0%   { opacity: 0; }
  100% { opacity: 0.55; }
}
@keyframes screen-soak-fade-out {
  0%   { opacity: 0.55; }
  100% { opacity: 0; }
}
@keyframes legendary-screen-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}

/* LEGENDARY banner — full-screen centered word with gold drop-shadow
   halo. Only appears for legendary soak. Tagged .reveal-transient. */
.legendary-banner {
  position: fixed;
  top: 28%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 10000;
  font-family: var(--font-mono);
  font-size: 44px;
  font-weight: 800;
  letter-spacing: 8px;
  color: #fff;
  text-transform: uppercase;
  pointer-events: none;
  white-space: nowrap;
  filter:
    drop-shadow(0 0 6px rgba(255,215,0,0.95))
    drop-shadow(0 0 18px rgba(255,215,0,0.55))
    drop-shadow(0 0 36px rgba(255,215,0,0.30));
  animation: legendary-banner-in 380ms cubic-bezier(.2,1.4,.3,1) forwards;
}
.legendary-banner.fading-out {
  animation: legendary-banner-out 480ms ease forwards;
}
@keyframes legendary-banner-in {
  0%   { opacity: 0; transform: translate(-50%, calc(-50% + 14px)) scale(0.9); }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@keyframes legendary-banner-out {
  0%   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, calc(-50% - 10px)) scale(1.04); }
}

/* Debug reveal panel — floating tool for triggering reveal-pipeline states
   without waiting for real extractions. Sits in the bottom-right corner;
   collapsed by default (just a "DEBUG" pill). Click to expand. State
   persists in localStorage. */
.debug-reveal-panel {
  position: fixed;
  right: 12px;
  bottom: 12px;
  z-index: 10050;
  font-family: var(--font-mono);
  color: var(--text-primary);
}
.debug-reveal-toggle {
  display: none;
  padding: 4px 10px;
  background: rgba(11, 15, 30, 0.92);
  border: 1px solid var(--border-steel);
  border-radius: 3px;
  color: var(--accent-cyan);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 1.8px;
  font-weight: 700;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(0,0,0,0.5);
}
.debug-reveal-panel--collapsed .debug-reveal-toggle { display: inline-block; }
.debug-reveal-panel--collapsed .debug-reveal-body { display: none; }
.debug-reveal-body {
  width: 320px;
  background: rgba(11, 15, 30, 0.96);
  border: 1px solid var(--border-steel);
  border-radius: 4px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.6), 0 0 12px rgba(79,195,247,0.10);
  padding: 10px 12px 12px;
}
.debug-reveal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--border-subtle);
  padding-bottom: 6px;
}
.debug-reveal-title {
  font-size: 10px;
  letter-spacing: 1.8px;
  font-weight: 700;
  color: var(--accent-cyan);
  text-transform: uppercase;
}
.debug-reveal-close {
  background: transparent;
  border: 1px solid var(--border-steel);
  border-radius: 3px;
  color: var(--text-dim);
  font-size: 14px;
  width: 22px;
  height: 22px;
  line-height: 1;
  cursor: pointer;
}
.debug-reveal-close:hover { color: var(--text-bright); border-color: var(--accent-cyan); }
.debug-reveal-section { margin-bottom: 8px; }
.debug-reveal-section:last-child { margin-bottom: 0; }
.debug-reveal-label {
  font-size: 9px;
  letter-spacing: 1.4px;
  font-weight: 700;
  color: var(--text-secondary);
  text-transform: uppercase;
  margin-bottom: 4px;
}
.debug-reveal-hint {
  font-size: 8px;
  color: var(--text-dim);
  font-weight: 500;
  letter-spacing: 0.5px;
  text-transform: none;
  margin-left: 4px;
}
.debug-reveal-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.debug-reveal-row > button {
  flex: 0 0 auto;
  padding: 5px 8px;
  background: rgba(13, 21, 32, 0.7);
  border: 1px solid var(--border-steel);
  border-radius: 3px;
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 1px;
  font-weight: 600;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}
.debug-reveal-row > button:hover {
  background: rgba(79,195,247,0.10);
  border-color: var(--accent-cyan);
  color: var(--text-bright);
}

/* Particle bursts from active crate during soak. Two element shapes:
   `.reveal-particle` (dot) and `.reveal-particle--spark` (sliver, used
   for epic/legendary additional sparks). Position is set inline via
   left/top (fixed viewport coords); drift target via --dx / --dy CSS
   custom props. Animation duration set inline so spawn-time RNG can
   stagger lifetimes. Tagged .reveal-transient for _clearTransientFX. */
.reveal-particle {
  position: fixed;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 10001;
  background: var(--rarity-color, var(--accent-cyan));
  box-shadow: 0 0 8px color-mix(in srgb, var(--rarity-color, var(--accent-cyan)) 80%, transparent);
  animation: reveal-particle-drift 1000ms ease-out forwards;
  --dx: 0px;
  --dy: 0px;
}
.reveal-particle--spark {
  width: 2px;
  height: 14px;
  border-radius: 1px;
  filter: blur(0.4px);
}
.reveal-particle--rare       { --rarity-color: var(--rarity-rare); }
.reveal-particle--epic       { --rarity-color: var(--rarity-epic); }
.reveal-particle--legendary  { --rarity-color: var(--rarity-legendary); }
@keyframes reveal-particle-drift {
  0%   {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
  60%  {
    opacity: 0.8;
    transform: translate(calc(-50% + (var(--dx) * 0.65)), calc(-50% + (var(--dy) * 0.65))) scale(0.85);
  }
  100% {
    opacity: 0;
    transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(0.4);
  }
}

/* Flying crate — viewport-fixed clone that animates from a reveal-crate's
   live position to the haul-value tile's center. Built per-launch by
   _animateCycleFlight; position/size set inline, transform/opacity
   transition handles the flight. Rarity border/glow inherited via the
   .reveal-crate--rarity-* class. Tagged .reveal-transient. */
.reveal-crate-flying {
  position: fixed;
  z-index: 9999;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    linear-gradient(180deg, #1a2235 0%, #0d1320 70%, #060914 100%);
  border: 1px solid var(--rarity-color, var(--accent-cyan));
  border-radius: 3px;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.06),
    0 0 12px color-mix(in srgb, var(--rarity-color, var(--accent-cyan)) 60%, transparent);
  transition: transform 520ms cubic-bezier(.55,-0.15,.3,1.08),
              opacity 320ms ease 240ms;
}
.reveal-crate-flying__icon {
  display: block;
  width: 60%;
  height: auto;
  max-height: 60%;
  object-fit: contain;
  filter: drop-shadow(0 0 4px color-mix(in srgb, var(--rarity-color, var(--accent-cyan)) 60%, transparent));
}

/* Haul-value pulse — brief brightness+saturate flash when an item lands.
   Duration set inline via --pulse-dur so the JS can scale per rarity
   (rare 550ms / epic 800ms / legendary 1100ms). */
.haul-value.pulsing {
  animation: haul-value-pulse var(--pulse-dur, 550ms) ease-out;
}
@keyframes haul-value-pulse {
  0%   { filter: brightness(1.6) saturate(1.35); transform: scale(1.03); }
  60%  { filter: brightness(1.2) saturate(1.15); transform: scale(1.01); }
  100% { filter: brightness(1)   saturate(1);    transform: scale(1); }
}

/* Soak card — pops above the active crate during rare+ soak. */
.reveal-soak-card {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 8px);
  transform: translateX(-50%);
  min-width: 160px;
  max-width: 260px;
  padding: 6px 10px;
  border: 1px solid var(--rarity-color, var(--accent-cyan));
  background:
    radial-gradient(ellipse at 50% 0%, color-mix(in srgb, var(--rarity-color, var(--accent-cyan)) 20%, transparent), transparent 70%),
    linear-gradient(180deg, rgba(8,10,22,0.92), rgba(11,15,30,0.96));
  border-radius: 4px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.6),
              0 0 12px color-mix(in srgb, var(--rarity-color, var(--accent-cyan)) 30%, transparent);
  text-align: center;
  z-index: 4;
  animation: reveal-soak-pop 320ms cubic-bezier(.2,1.4,.3,1);
  pointer-events: none;
}
@keyframes reveal-soak-pop {
  0%   { opacity: 0; transform: translate(-50%, 4px) scale(0.85); }
  100% { opacity: 1; transform: translate(-50%, 0) scale(1); }
}
.reveal-soak-card__rarity {
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 1.8px;
  color: var(--rarity-color, var(--accent-cyan));
  text-transform: uppercase;
}
.reveal-soak-card__name {
  font-size: 12px;
  font-weight: 700;
  color: #fff;
  letter-spacing: 1px;
  text-transform: uppercase;
  margin: 2px 0;
}
.reveal-soak-card__line {
  font-size: 8px;
  letter-spacing: 1.2px;
  color: var(--text-secondary);
  text-transform: uppercase;
}
.haul-chip {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 6px;
  height: 22px;
  border-radius: 3px;
  background: rgba(13,21,32,0.55);
  border: 1px solid var(--border-steel);
  box-sizing: border-box;
  /* Was overflow:hidden — but we need the floating "+N" pop badge to
     escape the chip bounds (rises above). Visible overflow is fine
     since the chip dimensions are still constrained by flex sizing. */
  overflow: visible;
  /* Anchor for the absolute-positioned haul-chip__pop child. */
  position: relative;
  transition: transform 0.15s ease-out;
}
/* Brief border + scale pulse when a haul chip is "just gained" via an
   event reward. Triggered from squadDetailModal.animateHaulPops by
   adding the modifier class for ~800ms. */
.haul-chip--just-gained {
  animation: haul-chip-pulse 0.8s ease-out;
}
@keyframes haul-chip-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(102,187,106, 0.7); transform: scale(1); }
  30%  { box-shadow: 0 0 12px 4px rgba(102,187,106, 0.55); transform: scale(1.08); }
  100% { box-shadow: 0 0 0 0 rgba(102,187,106, 0); transform: scale(1); }
}
/* Floating "+N" pop badge that rises out of the chip and fades. Sized
   to sit just above the chip top edge; long values (e.g. +1.2K) stay
   right-aligned with the chip's value column. */
.haul-chip__pop {
  position: absolute;
  top: -4px;
  right: 4px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 800;
  color: var(--accent-green);
  text-shadow: 0 0 6px rgba(102,187,106,0.8), 0 1px 0 #000;
  letter-spacing: 0.3px;
  pointer-events: none;
  white-space: nowrap;
  animation: haul-chip-pop 1.5s ease-out forwards;
  z-index: 5;
}
@keyframes haul-chip-pop {
  0%   { opacity: 0; transform: translateY(0); }
  15%  { opacity: 1; transform: translateY(-3px); }
  70%  { opacity: 1; transform: translateY(-18px); }
  100% { opacity: 0; transform: translateY(-26px); }
}
.haul-chip--premium {
  border-color: rgba(255,215,0,0.55);
  background: rgba(255,215,0,0.06);
}
.haul-chip--rare {
  border-color: rgba(224,64,251,0.55);
  background: rgba(224,64,251,0.06);
}
.haul-chip__icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  object-fit: contain;
  display: block;
}
.haul-chip__val {
  flex: 1;
  text-align: right;
  font-size: 11px;
  font-weight: 800;
  color: #fff;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.3px;
}
.haul-chip--premium .haul-chip__val { color: var(--accent-gold); }
.haul-chip--rare    .haul-chip__val { color: var(--accent-purple); }

/* ────────────────────────────────────────────────────────────────
   Corner-floating "loot received" chips. Visual parity with the
   haul-block .haul-chip — same chrome, same rarity tiers — so a
   gain reads as one celebration system regardless of where it
   surfaces. Used by lootCelebration for non-haul items (catalysts,
   beacon keys, signal shards, etc.); haul-tracked resources skip
   this surface and animate inline on their existing haul chip.
   ──────────────────────────────────────────────────────────────── */
#loot-celebration {
  /* Wider gap than the haul block's grid so the slide-in animation
     reads as discrete "incoming" rows rather than a stacked panel. */
  gap: 6px !important;
}
.loot-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 10px;
  height: 28px;
  border-radius: 4px;
  background: rgba(13,21,32,0.85);
  border: 1px solid var(--border-steel);
  box-sizing: border-box;
  font-family: var(--font-mono);
  /* Slide-in baseline — JS toggles the visible class to animate. */
  opacity: 0;
  transform: translateX(-24px);
  transition: opacity 0.3s ease-out, transform 0.3s ease-out;
  pointer-events: none;
  /* Subtle backdrop so chips read against busy map terrain. */
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 2px 8px rgba(0,0,0,0.5);
  /* Force a fixed width so multiple incoming chips stack cleanly. */
  min-width: 130px;
}
.loot-chip--visible {
  opacity: 1;
  transform: translateX(0);
}
.loot-chip--leaving {
  /* Float-up + fade-out exit, replaces the original "fade left" exit
     so it reads as "loot collected" rather than "row dismissed". */
  transition: opacity 0.6s ease-in, transform 0.6s ease-in;
  opacity: 0;
  transform: translateY(-12px);
}
.loot-chip__icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  object-fit: contain;
  display: block;
}
.loot-chip__val {
  flex: 1;
  text-align: right;
  font-size: 13px;
  font-weight: 800;
  color: var(--accent-green);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.4px;
  text-shadow: 0 0 6px rgba(102,187,106,0.5);
}
/* Rarity tiers — mirror haul-chip's palette so identical resources
   read the same color across the haul block and the corner. */
.loot-chip--premium {
  border-color: rgba(255,215,0,0.55);
  background: rgba(35,28,8,0.85);
}
.loot-chip--premium .loot-chip__val {
  color: var(--accent-gold);
  text-shadow: 0 0 6px rgba(255,215,0,0.55);
}
.loot-chip--rare {
  border-color: rgba(224,64,251,0.55);
  background: rgba(28,12,38,0.85);
}
.loot-chip--rare .loot-chip__val {
  color: var(--accent-purple);
  text-shadow: 0 0 6px rgba(224,64,251,0.55);
}
.loot-chip--legendary {
  border-color: rgba(255,140,0,0.65);
  background: rgba(40,22,8,0.9);
  /* Faint shimmer for top-tier loot — slow + low-opacity so it's a
     "this is special" cue without distracting from gameplay. */
  animation: loot-chip-shimmer 2.5s ease-in-out infinite;
}
.loot-chip--legendary .loot-chip__val {
  color: var(--accent-orange);
  text-shadow: 0 0 8px rgba(255,140,0,0.7);
}
@keyframes loot-chip-shimmer {
  0%, 100% { box-shadow: 0 2px 8px rgba(0,0,0,0.5), 0 0 0 0 rgba(255,140,0,0); }
  50%      { box-shadow: 0 2px 8px rgba(0,0,0,0.5), 0 0 12px 2px rgba(255,140,0,0.4); }
}
/* Loss variant — fired by event-card outcomes that SPEND resources or
   items (resourceCost / itemCost / fuelCost). Red text + red shadow,
   red border + faint red wash. Overrides the rarity-tier color so a
   "−1 Beacon Key" loss reads as a loss first, premium item second.
   The selectors are deliberately listed after the rarity blocks above
   so cascade order resolves to red without `!important`. */
.loot-chip--loss {
  border-color: rgba(239, 83, 80, 0.55);
  background: rgba(38, 12, 12, 0.85);
}
.loot-chip--loss .loot-chip__val,
.loot-chip--loss.loot-chip--premium .loot-chip__val,
.loot-chip--loss.loot-chip--rare .loot-chip__val,
.loot-chip--loss.loot-chip--legendary .loot-chip__val {
  color: var(--accent-red);
  text-shadow: 0 0 6px rgba(239, 83, 80, 0.55);
}
/* Suppress legendary shimmer on loss chips — the celebration vocabulary
   says "this is special" for shimmer, which doesn't fit a spent
   resource. Keeps loss-of-prototype readable as "you spent it". */
.loot-chip--loss.loot-chip--legendary {
  animation: none;
}

/* SQUAD block formation-grid host — anchors the 2×3 .fg-grid at the top
   of the body and gives it room to grow without scrolling. The slot
   sizing below is scoped to .squad-modal so the in-game CC / Megatower
   versions keep their own dimensions. */
.squad-modal-fg-host {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 4px;
}
/* Compact fg-slot sizing inside the squad-modal SQUAD block so the full
   2×3 grid fits inside the block without scrolling, even at mobile-landscape
   heights. Mirrors the precedent set by .mt-form-col in styles.css. */
.squad-modal .fg-grid { gap: 3px; align-items: center; }
.squad-modal .fg-row { gap: 3px; justify-content: center; }
.squad-modal .fg-slot,
.squad-modal .fg-slot.fg-static {
  width: 44px;
  height: 44px;
  flex: 0 0 44px;
  padding: 2px;
}
.squad-modal .fg-thumb { width: 38px; height: 38px; }
.squad-modal .fg-count-overlay {
  font-size: 10px;
  font-weight: 800;
  min-width: 14px;
  padding: 1px 3px;
  bottom: 2px;
  right: 2px;
}
.squad-modal .fg-tier-pip {
  font-size: 8px;
  min-width: 12px;
  padding: 1px 3px;
  top: 2px;
  left: 2px;
}

@media (max-height: 480px) {
  .squad-modal-scrim { top: 30px; bottom: 30px; }
  .squad-modal__header {
    height: 44px;
    padding: 6px 10px;
  }
  .squad-modal__body {
    padding: 6px 8px;
    gap: 6px;
  }
  .squad-modal__col { gap: 5px; }
  .sm-block {
    padding: 5px 7px;
    gap: 3px;
  }
  .sm-block__head {
    margin-bottom: 2px;
    padding-bottom: 3px;
  }
  .squad-modal__body--event [data-block="squad"] .sm-block__head {
    display: none;
  }
  .squad-modal__body--event [data-block="haul"] {
    flex: 1 1 0;
  }
  .squad-modal__body--event [data-block="haul"] .haul-chips {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 3px;
    margin-top: 4px;
    flex: 0 0 auto;
  }
  .squad-modal__body--event [data-block="haul"] .haul-chip {
    height: 18px;
    padding: 2px 4px;
    gap: 3px;
  }
  .squad-modal__body--event [data-block="haul"] .haul-chip__icon {
    width: 13px;
    height: 13px;
  }
  .squad-modal__body--event [data-block="haul"] .haul-chip__val {
    font-size: 9px;
  }
  .squad-modal__body--event [data-block="squad"] {
    flex: 0 0 96px;
  }
  .squad-modal-fg-host {
    align-items: flex-start;
    padding-top: 0;
  }
  .squad-modal .fg-grid,
  .squad-modal .fg-row { gap: 1px; }
  .squad-modal .fg-slot,
  .squad-modal .fg-slot.fg-static {
    width: 28px;
    height: 28px;
    flex-basis: 28px;
    border-width: 1px;
    padding: 1px;
  }
  .squad-modal .fg-thumb {
    width: 26px;
    height: 26px;
  }
  .squad-modal .fg-count-overlay {
    font-size: 8px;
    min-width: 11px;
    padding: 1px 2px;
    bottom: 1px;
    right: 1px;
  }
  .squad-modal .fg-tier-pip {
    font-size: 7px;
    min-width: 10px;
    top: 1px;
    left: 1px;
  }
}

/* Stat row reused in CrawlerBlock / TravelBlock / HeatBlock */
.stat-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 0;
}
.stat-row__label {
  width: 62px;
  font-size: 9px;
  color: var(--text-dim);
  letter-spacing: 1px;
  text-transform: uppercase;
  flex-shrink: 0;
}
.stat-row__value {
  margin-left: auto;
  font-size: 11px;
  font-weight: 700;
  color: #fff;
}
.stat-row__value.is-dim { color: var(--text-dim); }
.stat-row__bar {
  flex: 1;
  height: 4px;
  background: var(--bg-darker);
  border: 1px solid var(--border-subtle);
  border-radius: 2px;
  overflow: hidden;
}
.stat-row__bar > div {
  height: 100%;
  background: var(--accent-cyan);
  transition: width 0.3s ease;
}

/* Event card (60/40 left column when active) */
.event-card {
  position: relative;
  border: 1px solid var(--accent-orange);
  border-radius: 4px;
  padding: 10px 12px;
  background: linear-gradient(180deg, rgba(255,152,0,0.10), rgba(8,10,22,0.85));
  box-shadow: 0 0 14px rgba(255,152,0,0.35), inset 0 0 30px rgba(255,152,0,0.04);
  display: flex;
  flex-direction: column;
  gap: 6px;
  overflow: hidden;
  min-height: 0;
}
.event-card__sweep {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent-orange), transparent);
  animation: sweep 2.4s linear infinite;
}
.event-card__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.event-card__tag {
  font-size: 9px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--accent-orange);
}
.event-card__timer {
  font-size: 11px;
  font-family: var(--font-mono);
  color: var(--accent-orange);
  font-weight: 700;
}
.event-card__timer.is-urgent { color: var(--accent-red); animation: heat-pulse 0.8s ease-in-out infinite; }
.event-card__title {
  font-size: 13px;
  color: #fff;
  letter-spacing: 1.2px;
  margin: 0;
  font-weight: 700;
}
.event-card__flavor {
  font-size: 10px;
  line-height: 1.45;
  color: var(--text-secondary);
  text-wrap: pretty;
  margin: 0;
  flex: 1;
  overflow: hidden;
}
.event-card__progress {
  height: 2px;
  background: rgba(0,0,0,0.6);
  border-radius: 1px;
  overflow: hidden;
}
.event-card__progress > div {
  height: 100%;
  background: var(--accent-orange);
  transition: width 0.5s linear;
}
.event-card__progress > div.is-urgent { background: var(--accent-red); }
.event-card__choices {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: auto;
}

/* "Doing the action" panel — replaces the choice list for a brief moment
   after the player clicks a choice, so the resolution feels like an
   action they're performing rather than an instant state-flip. The
   countdown timer is paused (no updates) while this is visible; the
   result card swaps in once the bar fills AND the server response has
   landed (whichever is later). */
.event-card__executing {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: auto;
  padding: 10px;
  border: 1px solid var(--border-glow);
  background: rgba(79, 195, 247, 0.06);
  border-radius: 4px;
}
.event-card__executing-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--accent-cyan);
}
.event-card__executing-tag {
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.event-card__executing-tag::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent-cyan);
  animation: exec-pulse 0.9s ease-in-out infinite;
}
.event-card__executing-label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  color: #fff;
  text-transform: uppercase;
}
.event-card__executing-bar {
  height: 4px;
  background: rgba(0, 0, 0, 0.6);
  border-radius: 2px;
  overflow: hidden;
}
.event-card__executing-bar > div {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--accent-cyan) 0%, #82e9ff 100%);
  /* animation-duration is set inline so the JS owner can tweak it without
     a CSS edit. The `forwards` fill keeps the bar at 100% if the server
     response is slow — the reveal still waits on the result actually
     arriving, so a "stuck at 100%" bar is the correct visual signal. */
  animation: exec-fill linear forwards;
}
@keyframes exec-fill {
  from { width: 0%; }
  to   { width: 100%; }
}
@keyframes exec-pulse {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.4; transform: scale(0.7); }
}
.event-card__choice {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border: 1px solid var(--border-steel);
  background: rgba(8,10,22,0.6);
  border-radius: 3px;
  cursor: pointer;
  font-family: var(--font-mono);
  text-align: left;
  color: #fff;
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
}
.event-card__choice:hover { background: rgba(8,10,22,0.8); border-color: var(--border-glow); }
.event-card__choice-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 2px;
  background: var(--bg-panel);
  border: 1px solid var(--border-steel);
  color: var(--text-secondary);
  font-size: 9px;
  flex-shrink: 0;
}
.event-card__choice-label { flex: 1; }
.event-card__choice-chips {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 3px;
  flex-shrink: 0;
  max-width: 55%;
}

/* Outcome chip — used on choice buttons (preview) and result card (confirm).
   Five kinds: gain (green), loss/cost (red), heat (orange), meta (dim).
   `.is-dim` greys out chips when the choice was rejected (e.g. fuel cost
   not met). */
.event-card__chip {
  display: inline-flex;
  align-items: center;
  padding: 1px 5px;
  border-radius: 2px;
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  border: 1px solid transparent;
  white-space: nowrap;
  font-family: var(--font-mono);
}
.event-card__chip--gain {
  background: rgba(76, 175, 80, 0.14);
  border-color: rgba(76, 175, 80, 0.45);
  color: var(--accent-green);
}
.event-card__chip--loss,
.event-card__chip--cost {
  background: rgba(239, 83, 80, 0.14);
  border-color: rgba(239, 83, 80, 0.45);
  color: var(--accent-red);
}
.event-card__chip--heat {
  background: rgba(255, 152, 0, 0.14);
  border-color: rgba(255, 152, 0, 0.45);
  color: var(--heat-warm, #ff9800);
}
.event-card__chip--meta {
  background: rgba(255,255,255,0.04);
  border-color: var(--border-steel);
  color: var(--text-dim);
}
.event-card__chip.is-dim { opacity: 0.45; }

/* Icon + amount inside cost chips. Icon sits flush left of the formatted
   amount; height matches the chip's text line so it doesn't push the chip
   taller. Used by resourceCost preview chips (server-supplied costChips
   render an icon for the resource type instead of a "−Metal" text label). */
.event-card__chip-icon {
  width: 10px;
  height: 10px;
  margin-right: 3px;
  vertical-align: middle;
  object-fit: contain;
  /* Tinted by the chip's color via CSS filter — keeps the metal/energy/
     credits/etc. icons readable inside the red cost-chip background. */
  filter: brightness(1.1) saturate(0.9);
}
.event-card__chip-glyph {
  margin-right: 2px;
  opacity: 0.8;
}
.event-card__chip-amount {
  font-variant-numeric: tabular-nums;
}

/* "Blue option" — any choice carrying a `requires` predicate (commander
   class, item, troop power). Rendered with the FTL-blue treatment so
   specialty plays are visually distinct from baseline options. Two sub-
   states stack via the `--gated` modifier:
     - Active blue: cyan border + cyan label, the player meets the gate
                    and can pick it. Reads as "specialist option."
     - Locked blue (`--blue.--gated`): dimmed cyan, dashed border,
                    disabled cursor, requirement label visible. Reads
                    as "specialist option you don't qualify for."
   The cyan tint persists in both states (vs. the standard steel-grey
   border on regular gated options) so the player can scan the choice
   list and spot blue options at a glance regardless of availability. */
.event-card__choice--blue {
  border-color: var(--accent-cyan);
  background: rgba(79, 195, 247, 0.08);
}
.event-card__choice--blue .event-card__choice-label {
  color: var(--accent-cyan);
  text-shadow: 0 0 8px rgba(79, 195, 247, 0.35);
}
.event-card__choice--blue:hover {
  background: rgba(79, 195, 247, 0.14);
  border-color: var(--accent-cyan);
}
.event-card__choice--blue .event-card__choice-num {
  border-color: var(--accent-cyan);
  color: var(--accent-cyan);
}

/* Gated option — choice whose `requires` predicate failed. The button
   is disabled and dimmed so the player understands this option exists
   but is currently locked. The inline `event-card__choice-gate` chip
   explains why (e.g. "Requires Psionic commander"). Stacks with --blue
   for the gated-blue specialty look. */
.event-card__choice--gated {
  opacity: 0.55;
  cursor: not-allowed;
  border-style: dashed;
}
.event-card__choice--gated:hover {
  /* Suppress hover affordance — gated buttons aren't clickable. */
  background: rgba(8,10,22,0.6);
  border-color: var(--border-steel);
}
/* Gated blue specifically: keep the cyan accent in the dashed border
   even while dimmed, so it still reads as "blue option, but locked"
   rather than just "any greyed option." */
.event-card__choice--blue.event-card__choice--gated {
  border-color: var(--accent-cyan);
}
.event-card__choice--blue.event-card__choice--gated:hover {
  background: rgba(79, 195, 247, 0.06);
  border-color: var(--accent-cyan);
}
.event-card__choice-gate {
  display: inline-flex;
  align-items: center;
  padding: 1px 5px;
  border-radius: 2px;
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  border: 1px solid var(--border-steel);
  background: rgba(255,255,255,0.04);
  color: var(--text-dim);
  white-space: nowrap;
  font-family: var(--font-mono);
  margin-right: 6px;
}
/* Requirement chip on a blue-gated option uses the cyan accent so the
   "Requires X" text matches the option's specialty theme. */
.event-card__choice--blue .event-card__choice-gate {
  border-color: var(--accent-cyan);
  background: rgba(79, 195, 247, 0.06);
  color: var(--accent-cyan);
}

/* Red high-risk option — visual opposite of blue specialty. Flags
   choices whose outcome set contains real combat (encounter) or a
   meaningful troop/commander loss. The red is a stakes warning ONLY:
   it doesn't reveal which specific outcome will fire (heat, exact loss
   counts, rewards stay hidden — see _previewChips policy comment).
   Two states mirror blue:
     - Live red (`--red`): red border, dim red wash, red label/number.
                Click as normal; the warning is read as "this might
                cost you" without a hard stop.
     - Locked red (`--red.--gated`): dimmed, dashed border, red retained
                so the player can still scan "risky option I can't take".
   The red tint persists across both so risky options stand out at a
   glance regardless of availability. */
.event-card__choice--red {
  border-color: var(--accent-red);
  background: rgba(239, 83, 80, 0.08);
}
.event-card__choice--red .event-card__choice-label {
  color: var(--accent-red);
  text-shadow: 0 0 8px rgba(239, 83, 80, 0.35);
}
.event-card__choice--red:hover {
  background: rgba(239, 83, 80, 0.14);
  border-color: var(--accent-red);
}
.event-card__choice--red .event-card__choice-num {
  border-color: var(--accent-red);
  color: var(--accent-red);
}
/* Gated red parity with gated blue — keep the red accent in the dashed
   border while dimmed. */
.event-card__choice--red.event-card__choice--gated {
  border-color: var(--accent-red);
}
.event-card__choice--red.event-card__choice--gated:hover {
  background: rgba(239, 83, 80, 0.06);
  border-color: var(--accent-red);
}
.event-card__choice--red .event-card__choice-gate {
  border-color: var(--accent-red);
  background: rgba(239, 83, 80, 0.06);
  color: var(--accent-red);
}

/* FTL-style multi-paragraph flavor: stack <p> tags with a small gap, no
   extra vertical scroll growth. Earlier rule clamps `flex:1; overflow:hidden`
   so paragraphs collapse cleanly when the modal is short. */
.event-card__flavor + .event-card__flavor {
  margin-top: 6px;
}

/* Outcome narrative — the rolled-outcome prose paragraph shown in the
   result card. Sits between the choice label and the detail rows. */
.event-card__result-narrative {
  font-size: 11px;
  line-height: 1.45;
  color: var(--text-secondary);
  margin: 6px 0 8px 0;
  text-wrap: pretty;
}

/* Event-card prose markup — emitted by the _renderProse parser in
   squadDetailModal.js for flavor + result narratives. Author markup:
     **bold**         → <strong>
     *italic*         → <em>
     {type:text}      → <span class="evp-<type>">
   Color tokens stay in sync with the design system (--accent-*).
   Bold uses --text-primary so it reads as "look here" without color
   competition; italic uses --text-tertiary so it reads as a half-step
   back from regular body. Typed spans carry their categorical color. */
.event-card__flavor strong,
.event-card__result-narrative strong {
  color: var(--text-primary);
  font-weight: 700;
}
/* Italic prose — inherits body color so the emphasis carries through
   the slant alone (NOT a dimming/de-emphasis treatment, which read as
   "this is unimportant"). Bold remains the stronger emphasis above. */
.event-card__flavor em,
.event-card__result-narrative em {
  color: inherit;
  font-style: italic;
}
.evp-lore { color: var(--accent-gold);   font-weight: 600; }
.evp-kin  { color: var(--class-kinetic); font-weight: 600; }
.evp-elc  { color: var(--class-electric);font-weight: 600; }
.evp-psi  { color: var(--class-psionic); font-weight: 600; }
.evp-gain { color: var(--accent-green);  font-weight: 600; }
.evp-loss { color: var(--accent-red);    font-weight: 600; }
.evp-item { color: var(--rarity-epic, #e040fb); font-weight: 600; }

/* Result card — shown after the player chooses (or after server applies
   safe-default on timeout). Same outer .event-card frame so the slot
   transitions smoothly; result-specific bits live under .event-card--result. */
/* Urgent event tag — red, stronger weight than the standard yellow
   EVENT · DECISION tag. Pairs with the .event-card--urgent outer class
   which adds a subtle red border accent. The card author marks
   urgency:'high' for events whose safe_default is a run-ender (squad
   recall, commander injury). */
.event-card__tag--urgent {
  color: var(--accent-red);
  border-color: rgba(239, 83, 80, 0.55);
  background: rgba(239, 83, 80, 0.12);
  font-weight: 800;
  letter-spacing: 1.2px;
  animation: urgent-pulse 1.6s ease-in-out infinite;
}
/* Quest-objective tag (yellow) — destination cards skip the countdown
   timer + progress bar entirely; the tag color signals "no timer" by
   matching the yellow tile + objectives-panel accent. */
.event-card__tag--objective {
  color: #ffd700;
  border-color: rgba(255, 215, 0, 0.55);
  background: rgba(255, 215, 0, 0.10);
  font-weight: 800;
  letter-spacing: 1.2px;
}
/* Quiet timer slot — left in the DOM as an empty span when the event
   is quest-only (questOnly: true), so future surgical patches can
   still find [data-timer]. CSS hides it. */
.event-card__timer--quiet { display: none; }
@keyframes urgent-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 83, 80, 0); }
  50%      { box-shadow: 0 0 8px 2px rgba(239, 83, 80, 0.35); }
}
.event-card--urgent {
  border-color: rgba(239, 83, 80, 0.45);
}

.event-card__tag--success {
  color: var(--accent-green);
  border-color: rgba(76, 175, 80, 0.45);
  background: rgba(76, 175, 80, 0.10);
}
.event-card__tag--aborted {
  color: var(--accent-red);
  border-color: rgba(239, 83, 80, 0.45);
  background: rgba(239, 83, 80, 0.10);
}
/* Encounter-loss variant — choice DID apply (battle ran, troops lost,
   retreat triggered) but resolved as a defeat. Distinct from --aborted
   which means "choice rejected, no effects ran". Orange to visually
   pair with the RETURNING status the squad enters on loss. */
.event-card__tag--defeat {
  color: #ff9800;
  border-color: rgba(255, 152, 0, 0.55);
  background: rgba(255, 152, 0, 0.10);
}
.event-card__result-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 6px;
  flex: 1;
  /* Internal scroll for crowded result cards (encounter results stack
     fight-summary + per-unit losses + XP + loot + heat = 7+ rows). The
     head row + CONTINUE button stay fixed; only the narrative + detail
     rows scroll within their own viewport. min-height:0 is required for
     a flex child to shrink below its content size. */
  min-height: 0;
  overflow-y: auto;
}
.event-card__result-label {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.8px;
  color: #fff;
  text-transform: uppercase;
}
.event-card__result-label.is-strike {
  text-decoration: line-through;
  text-decoration-color: var(--accent-red);
  color: var(--text-dim);
}
.event-card__result-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

/* Detail rows — itemize server-resolved actuals (resource grants, fuel
   costs, troop losses, buff durations). One row per atomic outcome so
   the player can see exactly what changed. Stacked rather than chip-flow
   because the rows have variable-length text + an icon. */
/* 2-column grid — pairs detail rows side-by-side so the result card
   stays compact in landscape. Falls back to single column at very
   narrow widths via media-query below. Each row is icon + text so two
   columns is the readability ceiling. */
.event-card__result-details {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px;
  margin-top: 2px;
}
@media (max-width: 480px) {
  .event-card__result-details { grid-template-columns: 1fr; }
}
.event-card__detail {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px;
  border-radius: 3px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.4px;
  border: 1px solid transparent;
  background: rgba(255,255,255,0.02);
}
.event-card__detail--gain {
  color: var(--accent-green);
  border-color: rgba(76, 175, 80, 0.35);
  background: rgba(76, 175, 80, 0.08);
}
.event-card__detail--loss,
.event-card__detail--cost {
  color: var(--accent-red);
  border-color: rgba(239, 83, 80, 0.35);
  background: rgba(239, 83, 80, 0.08);
}
.event-card__detail--heat {
  color: var(--heat-warm, #ff9800);
  border-color: rgba(255, 152, 0, 0.35);
  background: rgba(255, 152, 0, 0.08);
}
.event-card__detail-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  object-fit: contain;
}
.event-card__detail-icon--asset {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.event-card__detail-icon--asset > img {
  width: 16px !important;
  height: 16px !important;
  object-fit: contain;
}
.event-card__detail-icon--asset > span {
  line-height: 1;
}
.event-card__detail-icon--troop {
  border-radius: 2px;
  object-fit: cover;
}
.event-card__detail-icon--text {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1;
}
.event-card__detail-text {
  text-transform: capitalize;
  /* min-width:0 allows the span to shrink within the 2-col grid cell
     when text would overflow. overflow ellipsis prevents one long
     loss row from pushing its sibling cell out of frame. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.event-card__result-heat {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 1px;
  color: var(--text-secondary);
  text-transform: uppercase;
}
.event-card__result-heat strong {
  color: var(--heat-warm, #ff9800);
  font-weight: 700;
}
.event-card__result-cta {
  margin-top: auto;
  display: flex;
  justify-content: flex-end;
}

/* Event log entry */
.event-log {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 10px;
  overflow-y: auto;
}
.event-log__entry {
  display: flex;
  gap: 6px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.event-log__t {
  width: 26px;
  flex-shrink: 0;
  color: var(--text-dim);
  font-size: 9px;
}
.event-log__text {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}
.event-log__entry.is-loss   { color: var(--accent-red); }
.event-log__entry.is-gain   { color: var(--accent-green); }
.event-log__entry.is-choice { color: var(--accent-cyan); }
.event-log__entry.is-event  { color: var(--text-secondary); }

/* Hunter banner (modal header extra slot, critical state) */
/* Hunter banner — appears in modal header from spawn (Phase 9). Default
   state is orange (warm/hot heat bands, T1-T3 Hunter); is-critical flips
   red + pulses for T4+ or heat >= 80 to escalate visual urgency. */
.hunter-banner {
  padding: 2px 8px;
  border: 1px solid var(--accent-orange);
  border-radius: 3px;
  background: rgba(255,152,0,0.15);
  font-size: 10px;
  color: var(--accent-orange);
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
}
.hunter-banner.is-critical {
  border-color: var(--accent-red);
  background: rgba(239,83,80,0.15);
  color: var(--accent-red);
  animation: critical-edge 1s ease-in-out infinite;
}
.event-decision-tag {
  padding: 2px 8px;
  border: 1px solid var(--accent-orange);
  border-radius: 3px;
  background: rgba(255,152,0,0.15);
  font-size: 9px;
  color: var(--accent-orange);
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}
/* Clickable header badge — promotes the scene from extracting →
   event-decision when clicked. See squadDetailModal._handleClick
   data-action="open-event". */
.event-decision-tag--clickable {
  cursor: pointer;
  font-family: inherit;
  transition: background 0.12s ease-out, transform 0.12s ease-out, box-shadow 0.12s ease-out;
  animation: decision-pulse 1.4s ease-in-out infinite;
}
.event-decision-tag--clickable:hover {
  background: rgba(255,152,0,0.28);
  transform: translateY(-1px);
  box-shadow: 0 0 8px rgba(255,152,0,0.55);
}
.event-decision-tag--clickable:active {
  transform: translateY(0);
}
@keyframes decision-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255,152,0,0); }
  50%      { box-shadow: 0 0 6px 0 rgba(255,152,0,0.45); }
}
