/* ============================================
   SOL WILDS — UI Styles
   HUD, action bar, frames, panels, overlays
   ============================================ */

/* Register --cooldown-angle as an animatable <angle> so CSS @keyframes
   can interpolate conic-gradient at native framerate (60fps). Without this,
   custom properties are opaque strings and can't be animated. */
@property --cooldown-angle {
  syntax: '<angle>';
  initial-value: 360deg;
  inherits: false;
}

@keyframes cooldown-sweep {
  from { --cooldown-angle: 0deg; }
  to { --cooldown-angle: 360deg; }
}

/* ============================================
   GUI LAYER
   Single wrapper for all UI - enables clean toggle
   and establishes stacking context for all GUI elements
   ============================================ */
#gui-layer {
  position: fixed;
  inset: 0;
  z-index: 100;
  pointer-events: none;
  transform: translate3d(0, 0, 0);
  transition: opacity var(--fade-snappy) ease-out;
  contain: layout style;
}

/* 
   POINTER-EVENTS: Direct children of #gui-layer manage their own.
   
   Click-through containers (children opt-in):
   - #left-panel-column: none (map.css) → children: auto
   - #right-panel-column: none (map.css) → children: auto
   - #bottom-ui: none → complex hierarchy
   - #resize-overlay: none (only visible during resize)
   - #tab-resume-overlay: none (sibling of #gui-layer under #game; only visible briefly after tab return)
   
   Interactive elements (explicitly set auto):
   - #fps-counter: auto (map.css)
   - #dialogue-panel: auto
   - #dialogue-shroud.visible: auto
   - #settings-menu: auto (explicit — show() does not override inherited none)
   - #settings-shroud.visible: auto
   - #worldmap-overlay: auto (explicit — show() does not override inherited none)
   - #toast-debug-panel: auto
   - .gui-modal: auto
   - #welcome-shroud.visible: auto
   
   Do NOT add a blanket #gui-layer > * rule - it overrides individual rules.
*/

/* GUI hidden state (Cmd/Ctrl + \) */
#gui-layer.hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none !important;
}

#gui-layer.hidden > * {
  pointer-events: none !important;
}

/* ============================================
   GUI TOOLTIP
   Unified tooltip style for all GUI elements
   Usage: Add .gui-tooltip class, position with CSS,
   toggle visibility on parent :hover
   ============================================ */
.gui-tooltip {
  position: absolute;
  background: var(--bg-panel);
  padding: var(--gui-padding-sm) var(--gui-padding);
  font-family: var(--font-display);
  font-size: var(--font-size-sm);
  color: var(--text-primary);
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--fade-snappy) ease, visibility var(--fade-snappy) ease;
  pointer-events: auto;
  z-index: 100;
}

.tooltip-dismissed > .gui-tooltip {
  opacity: 0 !important;
  visibility: hidden !important;
}

/* Common tooltip positions */
.gui-tooltip.above {
  bottom: calc(100% + var(--gui-padding-sm));
  left: 50%;
  transform: translate3d(-50%, 0, 0);
}

.gui-tooltip.below {
  top: calc(100% + var(--gui-padding-sm));
  left: 50%;
  transform: translate3d(-50%, 0, 0);
}

.gui-tooltip.left {
  right: calc(100% + var(--gui-padding-sm));
  top: 50%;
  transform: translate3d(0, -50%, 0);
}

.gui-tooltip.right {
  left: calc(100% + var(--gui-padding-sm));
  top: 50%;
  transform: translate3d(0, -50%, 0);
}

/* ============================================
   GUI PANEL - UNIFIED DRAGGABLE PANEL SYSTEM
   All draggable panels use .gui-panel class
   ============================================ */

/* Base behavior for all GUI panels (drag/resize/dock) */
.gui-panel {
  position: relative; /* Docked panels stay in flow */
  /* GPU layer via 3D transform context */
  backface-visibility: hidden;
  transform: translate3d(0, 0, 0);
}

/* Visual styles for column panels (not frame wrappers) */
.gui-panel:not(.frame-wrapper) {
  background: var(--bg-panel-alpha);
  font-family: var(--font-display);
  min-width: 240px;
}

.gui-panel.hidden {
  display: none;
}

/* Undocked (floating) state */
.gui-panel.undocked {
  position: fixed;
  z-index: 30;
  pointer-events: auto;
}

.gui-panel.undocked:not(.frame-wrapper) {
  box-shadow: var(--shadow-md);
}

/* Actively dragging */
.gui-panel.dragging {
  cursor: grabbing;
  z-index: 40;
}

.gui-panel.dragging:not(.frame-wrapper) {
  box-shadow: var(--shadow-xl);
}

/* Resizing state */
.gui-panel.resizing {
  cursor: se-resize;
}

/* Near dock zone indicator */
.gui-panel.near-dock .redock-indicator {
  opacity: 1;
}

/* Panel header (drag handle) */
.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--gui-padding);
  background: var(--alpha-white-03);
  border-bottom: 1px solid var(--frame-border);
  cursor: grab;
  user-select: none;
}

.panel-header:active {
  cursor: grabbing;
}

.panel-footer {
  padding: var(--gui-padding);
  background: var(--alpha-white-03);
  border-top: 1px solid var(--frame-border);
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  text-align: center;
}

#minimap-label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  text-align: left;
}

#currency-display {
  color: var(--color-gold);
  font-variant-numeric: tabular-nums;
}
#currency-display::before {
  content: '⬡ ';
}

.panel-title {
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.panel-controls {
  display: flex;
  gap: 0.25rem;
}

.panel-btn {
  width: 24px;
  height: 24px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-muted);
  font-size: var(--font-size-md);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity var(--fade-snappy) ease, background-color var(--fade-snappy) ease;
}

.gui-panel.undocked .panel-btn {
  opacity: 0.6;
}

.panel-btn:focus {
  opacity: 0.6;
}

.panel-btn:hover,
.panel-btn:focus-visible {
  opacity: 1;
  background: var(--alpha-white-10);
}

/* Resize handles - corners */
.resize-handle-se,
.resize-handle-sw,
.resize-handle-ne,
.resize-handle-nw {
  position: absolute;
  width: 14px;
  height: 14px;
  opacity: 0;
  transition: opacity var(--fade-snappy) ease;
  z-index: 10;
}

.resize-handle-se::after,
.resize-handle-sw::after,
.resize-handle-ne::after,
.resize-handle-nw::after {
  content: '';
  position: absolute;
  width: 6px;
  height: 6px;
  opacity: 0.5;
}

/* Corner positions and cursors */
.resize-handle-se {
  bottom: 2px;
  right: 2px;
  cursor: se-resize;
}
.resize-handle-se::after {
  bottom: 0;
  right: 0;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
}

.resize-handle-sw {
  bottom: 2px;
  left: 2px;
  cursor: sw-resize;
}
.resize-handle-sw::after {
  bottom: 0;
  left: 0;
  border-left: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
}

.resize-handle-ne {
  top: 2px;
  right: 2px;
  cursor: ne-resize;
}
.resize-handle-ne::after {
  top: 0;
  right: 0;
  border-right: 2px solid var(--text-muted);
  border-top: 2px solid var(--text-muted);
}

.resize-handle-nw {
  top: 2px;
  left: 2px;
  cursor: nw-resize;
}
.resize-handle-nw::after {
  top: 0;
  left: 0;
  border-left: 2px solid var(--text-muted);
  border-top: 2px solid var(--text-muted);
}

/* Resize handles - edge centers */
.resize-handle-n,
.resize-handle-s,
.resize-handle-e,
.resize-handle-w {
  position: absolute;
  opacity: 0;
  transition: opacity var(--fade-snappy) ease;
  z-index: 10;
}

/* Horizontal handles (top/bottom) */
.resize-handle-n,
.resize-handle-s {
  left: 50%;
  transform: translate3d(-50%, 0, 0);
  width: 40px;
  height: 10px;
  cursor: ns-resize;
}

/* Vertical handles (left/right) */
.resize-handle-e,
.resize-handle-w {
  top: 50%;
  transform: translate3d(0, -50%, 0);
  width: 10px;
  height: 40px;
  cursor: ew-resize;
}

/* Edge handle visual indicator (bar) */
.resize-handle-n::after,
.resize-handle-s::after,
.resize-handle-e::after,
.resize-handle-w::after {
  content: '';
  position: absolute;
  background: var(--text-muted);
  opacity: 0.5;
}

/* Horizontal bar for top/bottom */
.resize-handle-n::after,
.resize-handle-s::after {
  left: 50%;
  transform: translate3d(-50%, 0, 0);
  width: 24px;
  height: 2px;
}

/* Vertical bar for left/right */
.resize-handle-e::after,
.resize-handle-w::after {
  top: 50%;
  transform: translate3d(0, -50%, 0);
  width: 2px;
  height: 24px;
}

/* Edge handle positions */
.resize-handle-n {
  top: 0;
}
.resize-handle-n::after {
  top: 2px;
}

.resize-handle-s {
  bottom: 0;
}
.resize-handle-s::after {
  bottom: 2px;
}

.resize-handle-e {
  right: 0;
}
.resize-handle-e::after {
  right: 2px;
}

.resize-handle-w {
  left: 0;
}
.resize-handle-w::after {
  left: 2px;
}

/* Show resize handles on hover or while actively dragging */
.gui-panel:hover .resize-handle-se,
.gui-panel:hover .resize-handle-sw,
.gui-panel:hover .resize-handle-ne,
.gui-panel:hover .resize-handle-nw,
.gui-panel:hover .resize-handle-n,
.gui-panel:hover .resize-handle-s,
.gui-panel:hover .resize-handle-e,
.gui-panel:hover .resize-handle-w,
.gui-panel.dragging .resize-handle-se,
.gui-panel.dragging .resize-handle-sw,
.gui-panel.dragging .resize-handle-ne,
.gui-panel.dragging .resize-handle-nw,
.gui-panel.dragging .resize-handle-n,
.gui-panel.dragging .resize-handle-s,
.gui-panel.dragging .resize-handle-e,
.gui-panel.dragging .resize-handle-w {
  opacity: 1;
}

/* Redock indicator */
.redock-indicator {
  position: absolute;
  inset: -2px;
  border: 2px dashed var(--accent);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--fade-snappy) ease;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--redock-bg);
}

.redock-indicator span {
  background: var(--bg-panel);
  padding: 0.25rem 0.5rem;
  font-size: var(--font-size-xs);
  color: var(--accent);
}

/* Character sheet CSS removed — replaced by characterpanel.css */

/* Charge bar background (shared with unit frames) */
.charge-bar { background: var(--player-charge-bg); }

/* ============================================
   QUEST TRACKER
   ============================================ */
#quest-tracker {
  position: relative; /* Stay in flex flow when docked */
  background: var(--bg-panel-alpha);
  min-width: 240px;
  max-width: 400px;
  /* GPU layer for smooth drag/resize */
  backface-visibility: hidden;
  transform: translate3d(0, 0, 0);
  font-family: var(--font-display);
}

/* When undocked (dragged), take out of flex flow */
#quest-tracker.undocked {
  position: fixed;
}

#quest-list {
  display: flex;
  flex-direction: column;
  gap: var(--gui-gap);
  max-height: 200px;
  overflow-y: auto;
  padding: var(--gui-padding);
}

.quest-item {
  padding: var(--gui-padding) 0;
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
}

.quest-item.active {
  color: var(--warning);
}

.quest-item .quest-name {
  display: block;
  font-weight: 600;
  margin-bottom: 0.15rem;
}

.quest-item .quest-objective {
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  padding-left: 0.5rem;
}

.quest-item .quest-objective::before { content: '◦ '; }
.quest-item .quest-objective.complete::before {
  content: '✓ ';
  color: var(--success);
}

.quest-item.active {
  cursor: pointer;
  border-left: 2px solid transparent;
  padding-left: calc(var(--gui-padding) - 2px);
}
.quest-item.tracked {
  border-left-color: var(--accent);
}
.quest-item.tracked .quest-name {
  color: var(--accent);
}

/* ============================================
   COMBAT LOG
   ============================================ */
#combat-log-container {
  position: relative;
  min-width: 240px;
  background: var(--bg-panel-alpha);
}

#combat-log-container.hidden {
  display: none;
}

/* When undocked, use fixed positioning */
#combat-log-container.undocked {
  position: fixed;
}

#combat-log {
  padding: var(--gui-padding);
  max-height: 150px;
  overflow-y: auto;
  font-family: var(--font-display);
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
}

.combat-log-line {
  padding: var(--gui-padding) 0;
  border-bottom: 1px solid var(--alpha-white-03);
}

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

.combat-log-damage-dealt { color: oklch(0.78 0.14 50); }
.combat-log-damage-taken { color: oklch(0.72 0.18 25); }
.combat-log-debuff { color: oklch(0.72 0.15 330); }
.combat-log-healing { color: oklch(0.78 0.16 145); }
.combat-log-death { color: oklch(0.60 0.10 25); }
.combat-log-xp { color: oklch(0.82 0.12 85); }
.combat-log-loot { color: oklch(0.78 0.10 200); }
.combat-log-ability { color: var(--text-secondary); }

.combat-log-filter-bar {
  display: flex;
  gap: 2px;
  padding: 3px var(--gui-padding);
  border-bottom: 1px solid var(--alpha-white-05);
}

.combat-log-filter {
  position: relative;
  font: inherit;
  font-size: var(--font-size-xs);
  background: none;
  border: 1px solid var(--alpha-white-10);
  border-radius: 3px;
  color: var(--text-muted);
  padding: 1px 6px;
  cursor: pointer;
}

.combat-log-filter::after {
  content: '';
  position: absolute;
  inset: -10px -6px;
  min-height: 44px;
  min-width: 44px;
}

.combat-log-filter.active {
  color: var(--text-primary);
  border-color: var(--alpha-white-20);
  background: var(--alpha-white-05);
}

#combat-log-container.hide-damage .combat-log-damage-dealt,
#combat-log-container.hide-damage .combat-log-damage-taken,
#combat-log-container.hide-damage .combat-log-debuff { display: none; }
#combat-log-container.hide-healing .combat-log-healing { display: none; }
#combat-log-container.hide-xp .combat-log-xp,
#combat-log-container.hide-xp .combat-log-loot { display: none; }
#combat-log-container.hide-system .combat-log-system,
#combat-log-container.hide-system .combat-log-ability,
#combat-log-container.hide-system .combat-log-death { display: none; }

/* ============================================
   PARTY FRAMES (wireframe stub)
   ============================================ */

#party-frames {
  position: relative;
  min-width: 180px;
  max-width: 200px;
  pointer-events: auto;
}

#party-frames.hidden {
  display: none;
}

.party-frame-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.party-member {
  display: flex;
  align-items: center;
  gap: var(--gui-gap);
  padding: var(--gui-padding-sm) var(--gui-padding);
  background: var(--bg-panel-alpha);
  border: 1px solid var(--alpha-white-05);
  border-radius: 3px;
  cursor: pointer;
  transition: border-color var(--ui-transition);
}

.party-member:hover {
  border-color: var(--accent);
}

.party-member-portrait {
  width: 28px;
  height: 28px;
  border: 1px dashed var(--border-default);
  border-radius: 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  flex-shrink: 0;
  background: var(--frame-inner);
}

.party-member-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.party-member-name {
  font-family: var(--font-sans);
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.party-member-hp {
  height: 4px;
  background: var(--player-hp-bg);
  border-radius: 2px;
  overflow: hidden;
}

.party-member-hp-fill {
  height: 100%;
  width: 100%;
  background: var(--player-hp-color);
  border-radius: 2px;
  /* scaleX (not width) so HP changes animate on the compositor, not via per-frame
     layout. --hp-scale (0-1) is set by partyFrames.js. */
  transform: scaleX(var(--hp-scale, 1));
  transform-origin: left center;
  transition: transform var(--ui-transition);
}

/* ============================================
   CHAT WINDOW (wireframe stub)
   ============================================ */

#chat-window {
  position: relative;
  min-width: 240px;
  max-width: 320px;
  background: var(--bg-panel-alpha);
  border: 1px solid var(--alpha-white-05);
  border-radius: 4px;
  display: flex;
  flex-direction: column;
}

.chat-tabs {
  display: flex;
  border-bottom: 1px solid var(--alpha-white-05);
  flex-shrink: 0;
}

.chat-tab {
  flex: 1;
  background: none;
  border: none;
  padding: var(--gui-padding-sm) var(--gui-padding);
  font-family: var(--font-sans);
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--ui-transition);
  border-bottom: 2px solid transparent;
}

.chat-tab:hover {
  color: var(--text-secondary);
}

.chat-tab.active {
  color: var(--text-primary);
  border-bottom-color: var(--accent);
}

.chat-messages {
  flex: 1;
  min-height: 100px;
  max-height: 160px;
  overflow-y: auto;
  padding: var(--gui-padding);
  font-family: var(--font-sans);
  font-size: var(--font-size-xs);
  color: var(--text-secondary);
  line-height: 1.5;
  contain: layout paint;
}


.chat-input-row {
  border-top: 1px solid var(--alpha-white-05);
  padding: var(--gui-padding-sm);
  display: flex;
  align-items: center;
}

.chat-channel-badge {
  background: none;
  border: none;
  padding: 0 var(--gui-padding-sm);
  font-family: var(--font-sans);
  font-size: var(--font-size-xs);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
  line-height: 1;
  max-width: 8em;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--text-primary);
}
.chat-channel-badge:disabled { opacity: 0.5; cursor: not-allowed; }
.chat-channel-badge[data-channel="say"]     { color: var(--text-primary); }
.chat-channel-badge[data-channel="zone"]    { color: var(--accent); }
.chat-channel-badge[data-channel="whisper"] { color: var(--punch-plum); }
.chat-channel-badge[data-channel="emote"]   { color: var(--punch-amber); }
.chat-channel-badge[data-channel="party"]   { color: var(--color-jade); }

.chat-input {
  flex: 1;
  min-width: 0;
  padding: var(--gui-padding-sm) var(--gui-padding);
  background: var(--frame-inner);
  border: 1px solid var(--alpha-white-08);
  border-radius: 3px;
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--font-size-xs);
  outline: none;
  transition: border-color var(--ui-transition);
}

.chat-input::placeholder {
  color: var(--text-muted);
}

.chat-input:focus {
  border-color: var(--accent);
}

.chat-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.chat-input:focus[data-chat-mode="whisper"] { border-color: var(--punch-plum); }
.chat-input:focus[data-chat-mode="emote"]   { border-color: var(--punch-amber); }

.chat-input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ─── Chat messages: scrollbar ─── */

.chat-messages {
  scrollbar-width: thin;
  scrollbar-color: var(--alpha-white-08) transparent;
}

/* ─── Chat messages: per-message layout ─── */

.chat-message {
  padding: 1px 0;
  line-height: 1.5;
  word-break: break-word;
  transition: opacity var(--fade-slow) ease;
}

.chat-messages.chat-faded .chat-message {
  opacity: 0.25;
}

#chat-window:hover .chat-messages.chat-faded .chat-message,
#chat-window:focus-within .chat-messages.chat-faded .chat-message {
  opacity: 1;
}

.chat-timestamp {
  color: var(--text-disabled);
  font-size: var(--font-size-xs);
}

.chat-channel {
  font-size: var(--font-size-xs);
  font-weight: 600;
}

.chat-channel--say { color: var(--text-primary); }
.chat-channel--zone { color: var(--accent); }
.chat-channel--whisper { color: var(--punch-plum); }
.chat-channel--party { color: var(--color-jade); }
.chat-channel--system { color: var(--text-muted); font-style: italic; font-weight: 400; }
.chat-channel--emote { color: var(--punch-amber); font-style: italic; }

/* ─── Player Context Menu ─── */

.player-context-menu {
  position: fixed;
  z-index: 9998;
  min-width: 120px;
  background: var(--bg-panel);
  border: 1px solid var(--alpha-white-08);
  border-radius: 4px;
  box-shadow: var(--shadow-md);
  padding: 2px 0;
  display: none;
}

.player-context-menu.visible {
  display: block;
}

.context-menu-item {
  display: block;
  width: 100%;
  padding: 6px 12px;
  background: none;
  border: none;
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--font-size-xs);
  text-align: left;
  cursor: pointer;
  transition: background var(--ui-transition);
}

.context-menu-item:hover {
  background: var(--alpha-white-05);
}

.chat-sender {
  font-weight: 600;
  cursor: pointer;
}

.chat-sender:hover,
.chat-sender:focus-visible {
  text-decoration: underline;
}

.chat-text {
  font-size: var(--font-size-xs);
}

/* ─── Chat font size overrides (settings) ─── */

.chat-font-medium .chat-tab,
.chat-font-medium .chat-messages,
.chat-font-medium .chat-input,
.chat-font-medium .chat-channel-badge,
.chat-font-medium .chat-timestamp,
.chat-font-medium .chat-channel,
.chat-font-medium .chat-text,
.chat-font-medium .context-menu-item {
  font-size: var(--font-size-sm);
}

.chat-font-large .chat-tab,
.chat-font-large .chat-messages,
.chat-font-large .chat-input,
.chat-font-large .chat-channel-badge,
.chat-font-large .chat-timestamp,
.chat-font-large .chat-channel,
.chat-font-large .chat-text,
.chat-font-large .context-menu-item {
  font-size: var(--font-size-md);
}

/* ─── Large Text (Accessibility) ─── */

#game.ui-text-large {
  --font-size-2xs: 0.65rem;
  --font-size-xs:  0.75rem;
  --font-size-sm:  0.85rem;
  --font-size-md:  0.95rem;
  --font-size-lg:  1.05rem;
  --font-size-xl:  1.15rem;
  --font-size-2xl: 1.4rem;
  --font-size-3xl: 1.75rem;
  --font-size-4xl: 2.3rem;
}

#game.ui-text-large .xp-level-box {
  width: 28px;
  min-width: 28px;
  height: 28px;
}

#game.ui-text-large .inventory-item-info {
  min-width: 0;
}

/* ─── Reduced Motion (Accessibility) ─── */

.reduced-motion #sandstorm-layer,
.reduced-motion #thunderstorm-layer,
.reduced-motion #rain-layer,
.reduced-motion #sand-gust-layer { content-visibility: hidden; }

.reduced-motion .dust-devil-pixel,
.reduced-motion .ambient-dust-pixel { content-visibility: hidden; }

.reduced-motion .storm-shake,
.reduced-motion .gust-shake { animation: none !important; }

.reduced-motion .damage-number { animation-duration: 0.01ms !important; }
.reduced-motion .projectile { animation-duration: 0.01ms !important; }

.reduced-motion #zone-banner.zone-banner-visible,
.reduced-motion #levelup-display.levelup-visible { animation-duration: 0.01ms !important; }
.reduced-motion .achievement-alert { transition-duration: 0.01ms !important; }

.reduced-motion .ember-particle,
.reduced-motion .explosion-particle,
.reduced-motion .dice-particle,
.reduced-motion .vaporize-particle,
.reduced-motion .crush-particle { animation: none !important; content-visibility: hidden; }

.reduced-motion .aoe-effect { animation: none !important; }

.reduced-motion .weather-indicator.thunder { animation: none; }
.reduced-motion .action-slot.new-ability,
.reduced-motion .action-slot.new-ability > .new-glow { animation: none; }
.reduced-motion .effect-cd-fill { animation: none !important; stroke-dashoffset: 0 !important; }

/* Decorative actor animations — suppress continuous motion, preserve functional ones.
   Walk/run use composite animations (e.g. sheet-walk-cycle, walk-bob). Override to
   keep the sprite cycle but drop walk-bob/shadow-bounce (decorative bob/bounce).
   :not() exclusions match actors.css — jump, leap, knockback, sit all have their
   own functional animations that must not be clobbered. */
.reduced-motion .actor.moving:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.sitting-down):not(.sitting):not(.standing-up):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .sprite,
.reduced-motion .actor.moving:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.sitting-down):not(.sitting):not(.standing-up):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .sprite-hair,
.reduced-motion .actor.moving:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.sitting-down):not(.sitting):not(.standing-up):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .equip-head,
.reduced-motion .actor.moving:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.sitting-down):not(.sitting):not(.standing-up):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .equip-chest,
.reduced-motion .actor.moving:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.sitting-down):not(.sitting):not(.standing-up):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .equip-weapon { animation-name: sheet-walk-cycle !important; }
.reduced-motion .actor.moving.sprinting:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .sprite,
.reduced-motion .actor.moving.sprinting:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .sprite-hair,
.reduced-motion .actor.moving.sprinting:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .equip-head,
.reduced-motion .actor.moving.sprinting:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .equip-chest,
.reduced-motion .actor.moving.sprinting:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .equip-weapon { animation-name: sheet-run-cycle !important; }
.reduced-motion .character.moving:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.sitting-down):not(.sitting):not(.standing-up):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .shadow,
.reduced-motion .network-player.moving:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.sitting-down):not(.sitting):not(.standing-up):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .shadow { animation-name: sheet-walk-cycle !important; }
.reduced-motion .character.moving.sprinting:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .shadow,
.reduced-motion .network-player.moving.sprinting:not(.jumping):not(.leaping):not(.vehicle-knockback):not(.vehicle-knockback-heavy):not(.dead):not(.despawning):not(.downed):not([class*="death-"]) .shadow { animation-name: sheet-run-cycle !important; }
.reduced-motion .actor.moving:not(.character):not(.network-player) .shadow { animation: none !important; }
.reduced-motion #select-ring.active.combat-active { animation: none !important; }
.reduced-motion .telegraph-zone { animation: none !important; }
.reduced-motion .ground-effect { animation: none !important; }
.reduced-motion .character.ghost,
.reduced-motion .network-player.ghost { animation: none !important; opacity: 0.5; }
.reduced-motion .character.downed { animation: none !important; opacity: 0.5; }
.reduced-motion .character.immune { animation: none !important; filter: brightness(1.4) saturate(1.2); }
.reduced-motion .enemy.enraged { animation: none !important; filter: brightness(1.1) saturate(1.15); }
.reduced-motion .nameplate-quest { animation: none !important; }
.reduced-motion .nameplate .nameplate-quest { animation: none !important; }

/* Additional in-game toggle coverage for animations in other stylesheets */
.reduced-motion .cooldown-overlay { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
.reduced-motion .toast { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
.reduced-motion .comms-wave span { animation: none !important; }
.reduced-motion .worldmap-waypoint-dot { animation: none !important; }
.reduced-motion .ability-tile.new-tile { animation: none !important; }
.reduced-motion .ability-tile.assigning { animation: none !important; }
.reduced-motion .poof-effect { animation: none !important; }
.reduced-motion .aoe-pulse { animation: none !important; }

/* ─── Chat tab filtering ─── */

.chat-messages.filter-party .chat-message:not([data-chat-channel="party"]):not([data-chat-channel="system"]) { display: none; }
.chat-messages.filter-system .chat-message:not([data-chat-channel="system"]) { display: none; }

/* ============================================
   BOTTOM UI
   Main container for portraits and abilities rows
   
   POINTER-EVENTS HIERARCHY:
   - #bottom-ui: none (click-through container)
     - #interact-prompt: none (visual only)
     - .bottom-dock-row: none (click-through)
   (#toast-container moved to body level — see its rule below — so it's no longer here)
       - .portrait-dock-zone: none (click-through)
         - .frame-wrapper: none (click-through)
           - .unit-frame: auto (interactive)
           - .frame-status-container: none
             - .effect-box: auto (hoverable)
             - .ability-box.on-cooldown: auto (hoverable)
       - #action-block: auto (interactive)
   ============================================ */
#bottom-ui {
  position: fixed;
  bottom: var(--gui-margin);
  left: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gui-margin);
  z-index: 10;
  pointer-events: none;
}

/* Bottom dock rows - interactive areas */
.bottom-dock-row {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  gap: var(--gui-margin);
  pointer-events: none; /* Container click-through, children opt-in */
}

/* Combat HUD dock - shared width for portrait + abilities rows */
#combat-hud-dock {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: fit-content;
  gap: var(--gui-margin);
  pointer-events: none;
}

/* Portrait Row - fixed left and right dock zones */
#portrait-row {
  display: flex;
  justify-content: space-between;
  width: 100%;
  z-index: 2; /* Above abilities row */
  pointer-events: none; /* Don't block world clicks - children re-enable */
}

/* Fixed dock zones - maintain position even when empty */
.portrait-dock-zone {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  gap: var(--gui-margin);
  min-width: 240px;
  pointer-events: none; /* Don't block clicks when empty - children re-enable */
}

#portrait-dock-left {
  align-items: flex-start;
}

#portrait-dock-right {
  align-items: flex-end;
}

/* Abilities Row - centered */
#abilities-row {
  justify-content: center;
  /* Width determined by content */
  width: fit-content;
  z-index: 1; /* Below portrait row */
  /* Maintain dock zone even when action-block is undocked */
  min-height: 40px;
}

/* ============================================
   ACTION BLOCK
   Draggable action bar container
   ============================================ */
#action-block {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--gui-margin-sm);
  /* GPU layer */
  backface-visibility: hidden;
  transform: translate3d(0, 0, 0);
  cursor: grab;
  pointer-events: auto; /* Interactive UI element */
}

#action-block:active {
  cursor: grabbing;
}

/* Only the action slot buttons should be clickable, not draggable */
#action-block .action-slot {
  cursor: pointer;
}

/* Undocked state */
#action-block.undocked {
  position: fixed;
  z-index: 30;
}

#action-block.dragging {
  cursor: grabbing;
  z-index: 40;
  box-shadow: var(--shadow-xl);
}

#action-block.near-dock .redock-indicator {
  opacity: 1;
}

/* ============================================
   UNIT FRAMES (Player & Target)
   Inside portrait row when docked
   ============================================ */
.unit-frame {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--bg-panel-alpha);
  min-width: 240px;
  /* GPU layer */
  backface-visibility: hidden;
  transform: translate3d(0, 0, 0);
  z-index: 10;
  cursor: grab;
  pointer-events: auto; /* Re-enable clicks (parent dock zone has none) */
}

.unit-frame:active {
  cursor: grabbing;
}

/* Unit frame content row (portrait + info) */
.unit-frame-content {
  display: flex;
  align-items: center;
  gap: var(--gui-gap);
  padding: var(--gui-padding);
}

/* Resize handles inherit from .gui-panel resize handles */
.unit-frame .resize-handle-se,
.unit-frame .resize-handle-sw,
.unit-frame .resize-handle-ne,
.unit-frame .resize-handle-nw,
.unit-frame .resize-handle-n,
.unit-frame .resize-handle-s,
.unit-frame .resize-handle-e,
.unit-frame .resize-handle-w {
  position: absolute;
  z-index: 10;
  opacity: 0;
}

/* Show resize handles on hover (unit-frame only) or while actively dragging */
.unit-frame:hover .resize-handle-se,
.unit-frame:hover .resize-handle-sw,
.unit-frame:hover .resize-handle-ne,
.unit-frame:hover .resize-handle-nw,
.unit-frame:hover .resize-handle-n,
.unit-frame:hover .resize-handle-s,
.unit-frame:hover .resize-handle-e,
.unit-frame:hover .resize-handle-w,
.frame-wrapper.dragging .unit-frame .resize-handle-se,
.frame-wrapper.dragging .unit-frame .resize-handle-sw,
.frame-wrapper.dragging .unit-frame .resize-handle-ne,
.frame-wrapper.dragging .unit-frame .resize-handle-nw,
.frame-wrapper.dragging .unit-frame .resize-handle-n,
.frame-wrapper.dragging .unit-frame .resize-handle-s,
.frame-wrapper.dragging .unit-frame .resize-handle-e,
.frame-wrapper.dragging .unit-frame .resize-handle-w {
  opacity: 1;
}

/* Dock button for unit frames */
.unit-frame .dock-btn {
  position: absolute;
  top: var(--gui-padding-sm);
  right: var(--gui-padding-sm);
  opacity: 0;
  z-index: 10;
}

/* Show dock button when undocked (but not actively dragging) */
.frame-wrapper.undocked:not(.dragging) .unit-frame .dock-btn {
  opacity: 0.6;
}

.unit-frame .dock-btn:hover {
  opacity: 1;
}

.unit-frame.resizing {
  cursor: nwse-resize;
}

/* Frame wrapper undocked states (position/z-index handled by .gui-panel) */
.frame-wrapper.undocked .unit-frame {
  box-shadow: var(--shadow-md);
}

.frame-wrapper.dragging .unit-frame {
  box-shadow: var(--shadow-xl);
}

/* Legacy: unit-frame without wrapper (backwards compatibility) */
.unit-frame.undocked {
  position: fixed;
  z-index: 30;
  pointer-events: auto;
  box-shadow: var(--shadow-md);
}

.unit-frame.dragging {
  cursor: grabbing;
  z-index: 40;
  box-shadow: var(--shadow-xl);
}

.unit-frame.near-dock .redock-indicator {
  opacity: 1;
}

/* When docked to left/right columns, adjust styling */
.frame-wrapper.column-docked {
  width: 100%;
}

.frame-wrapper.column-docked .unit-frame {
  min-width: unset;
}

#player-frame {
  /* In portrait row: sits on left */
}

#target-frame {
  /* In portrait row: mirrored layout (details left, icon right) to face inward */
}

#target-frame.hidden {
  display: none;
}

/* Portrait wrapper - contains frame-portrait */
.portrait-wrapper {
  width: var(--size-portrait-md);
  height: var(--size-portrait-md);
}

.frame-portrait {
  position: relative;
  width: 100%;
  height: 100%;
  background: var(--frame-inner);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--portrait-icon-size);
  box-sizing: border-box;
}

/* Portrait level badge - small square showing level number */
.portrait-level {
  position: absolute;
  bottom: var(--gui-padding-sm);
  font-family: var(--font-numbers);
  font-size: var(--font-size-sm);
  color: var(--text-primary);
  background: var(--alpha-black-50);
  padding: var(--gui-padding-sm);
  line-height: 1;
  z-index: 5;
}

/* Player portrait: level hidden (shown in XP bar level boxes instead) */
.player-portrait .portrait-level {
  display: none;
}

/* Enemy/target portrait: level at bottom-right */
.enemy-portrait .portrait-level {
  right: var(--gui-padding-sm);
}

/* Swing timer SVG — inside frame-portrait, stretches to fill */
.frame-portrait .swing-timer-border {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--fade-standard) ease;
  transform: translate3d(0, 0, 0);
}

/* Show when autoattacking — class toggled by JS */
#player-frame.autoattacking .swing-timer-border {
  opacity: 1;
}

/* Portrait swing timer stroke styles (same path as cast timer — starts at 12 o'clock) */
.swing-timer-track {
  fill: none;
  stroke: var(--alpha-white-08);
  stroke-width: 2;
  stroke-linecap: square;
  stroke-linejoin: miter;
}

.swing-timer-fill {
  fill: none;
  stroke: var(--swing-color, var(--accent));
  stroke-width: 2.5;
  stroke-linecap: square;
  stroke-linejoin: miter;
  transform: translate3d(0, 0, 0);
}

/* Cooldown state: ring drains as cooldown progresses */
#player-frame.swing-cooldown .swing-timer-fill {
  --swing-color: var(--accent);
}

/* Target frame swing timer — danger color for enemy attacks */
#target-frame.autoattacking .swing-timer-border {
  opacity: 1;
}

#target-frame.swing-cooldown .swing-timer-fill {
  --swing-color: var(--danger);
}


.frame-info {
  display: flex;
  flex-direction: column;
  gap: var(--gui-padding);
  flex: 1;
  min-width: 0; /* Allow scaling with container */
  width: 100%;
}

.frame-name-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.frame-name {
  font-family: var(--font-nameplate);
  font-size: var(--frame-name-size);
  font-weight: 600;
  color: var(--text-primary);
}

.frame-family {
  font-family: var(--font-nameplate);
  font-size: 0.65rem;
  font-weight: 400;
  color: var(--text-secondary);
  margin-left: 6px;
}
.frame-family:empty { display: none; }

.frame-level {
  font-family: var(--font-numbers);
  font-size: var(--frame-level-size);
  color: var(--color-rarity-legendary);
}

/* Container for HP/charge bars */
.frame-bars {
  display: flex;
  flex-direction: column;
  gap: var(--gui-padding-sm);
  width: 100%;
}

.frame-bar {
  position: relative;
  width: 100%;
  height: var(--size-bar-lg);
  background: var(--player-hp-bg);
  overflow: hidden;
}

/* Hide text in portrait bars - visual only */
.frame-bar-text {
  display: none;
}

/* Player charge bar - scoped to player-frame */
#player-frame .frame-bar.charge-bar {
  height: var(--size-bar-md);
  background: var(--player-charge-bg);
}

.frame-hp-fill {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  background: var(--player-hp-color);
  /* GPU-accelerated: scale3d instead of width */
  transform: scale3d(calc(var(--hp-pct, 100) / 100), 1, 1);
  transform-origin: left center;
  transition: transform 0.2s ease;
}

/* Charge fill - base styles (mirrors .frame-hp-fill pattern) */
.frame-charge-fill {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  background: var(--player-charge-color);
  /* Use scaleX with width:100% instead of scale3d with calc - more reliable */
  transform: scaleX(var(--charge-pct, 1));
  transform-origin: left center;
  transition: transform 0.2s ease;
}

/* Target frame (enemies) - uses enemy colors */
#target-frame .frame-hp-fill {
  background: var(--enemy-hp-color);
}

/* Friendly NPC target - uses NPC colors */
#target-frame.friendly .frame-hp-fill {
  background: var(--npc-hp-color);
}


#target-frame.friendly .frame-level {
  color: var(--success);
}

/* Vehicle target - friendly green (same as NPC) */
#target-frame.frame-vehicle .frame-hp-fill {
  background: var(--npc-hp-color);
}


#target-frame.frame-vehicle .frame-level {
  color: var(--warning);
}

/* ============================================
   CHARGE BAR (for actors)
   ============================================ */
.frame-bar.charge-bar {
  height: var(--size-bar-md);
  background: var(--charge-bg);
}

/* Hide charge bar for objects and vehicles (non-actors) */
#target-frame.frame-object .frame-bar.charge-bar,
#target-frame.frame-vehicle .frame-bar.charge-bar {
  display: none;
}

/* ============================================
   FUEL BAR (for vehicles)
   ============================================ */
.frame-bar.fuel-bar {
  height: var(--size-bar-md);
  background: var(--charge-bg); /* Same base as charge */
  display: none; /* Hidden by default */
}

/* Show fuel bar only for vehicles */
#target-frame.frame-vehicle .frame-bar.fuel-bar {
  display: block;
}

.frame-fuel-fill {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  background: var(--player-charge-color);
  transform: scale3d(calc(var(--fuel-pct, 100) / 100), 1, 1);
  transform-origin: left center;
  transition: transform 0.2s ease;
}


/* Target frame charge fill (enemies by default) */
#target-frame .frame-charge-fill {
  background: var(--enemy-charge-color);
}

/* Friendly NPC target uses NPC charge color */
#target-frame.friendly .frame-charge-fill {
  background: var(--npc-charge-color);
}

/* ============================================
   TARGET-OF-TARGET SUB-FRAME
   Compact row showing who the targeted enemy is attacking
   ============================================ */

.tot-frame {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 2px var(--gui-padding);
  border-top: 1px solid var(--alpha-white-05);
  font-size: var(--font-size-xs);
}

.tot-label {
  color: var(--text-muted);
  flex-shrink: 0;
}

.tot-name {
  color: var(--text-secondary);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.tot-hp-bar {
  flex-shrink: 0;
  width: 40px;
  height: 3px;
  background: var(--player-hp-bg);
  border-radius: 1px;
  overflow: hidden;
}

.tot-hp-fill {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--player-hp-color);
  border-radius: 1px;
  /* scaleX (not width) so HP changes animate on the compositor, not via per-frame
     layout. --hp-scale (0-1) is set by combatUI.js. */
  transform: scaleX(var(--hp-scale, 1));
  transform-origin: left center;
  transition: transform 0.2s ease;
}

/* ============================================
   FRAME WRAPPER (contains effects/abilities + unit-frame)
   Container is click-through, only unit-frame is interactive
   ============================================ */
.frame-wrapper {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--gui-margin-sm);
  width: 100%;
  min-width: 240px;
  pointer-events: none; /* Click-through - unit-frame opts in */
}

.frame-wrapper .unit-frame {
  cursor: grab;
}

.frame-wrapper .unit-frame:active {
  cursor: grabbing;
}

/* ============================================
   FRAME EFFECTS (Buffs/Debuffs Row)
   ============================================ */
/* Container for effects + abilities rows */
.frame-status-container {
  display: flex;
  flex-direction: column-reverse;
  gap: var(--gui-margin-sm);
  min-width: 240px;
  pointer-events: none; /* Click-through - children opt-in */
}

.frame-effects {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--gui-gap-sm);
  min-height: 24px; /* Reserve space even when empty */
}

.frame-buffs,
.frame-debuffs {
  display: flex;
  gap: var(--space-1);
  flex-wrap: wrap;
  flex: 1; /* Each takes half the space */
}

.frame-buffs {
  justify-content: flex-start;
}

.frame-debuffs {
  justify-content: flex-end;
}

/* Individual buff/debuff box */
.effect-box {
  position: relative;
  width: var(--size-icon-md);
  height: var(--size-icon-md);
  background: var(--alpha-black-60);
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--effect-icon-size);
  cursor: help;
  pointer-events: auto; /* Hoverable for tooltip */
}

.effect-box:hover .gui-tooltip,
.effect-box:focus-within .gui-tooltip {
  opacity: 1;
  visibility: visible;
}

/* Duration timer text overlay */
.effect-box .effect-timer {
  position: absolute;
  bottom: calc(-1 * var(--gui-padding-sm));
  right: calc(-1 * var(--gui-padding-sm));
  font-size: var(--effect-timer-size);
  font-family: var(--font-numbers);
  color: var(--text-primary);
  background: var(--alpha-black-70);
  padding: var(--gui-padding-sm);
  line-height: 1;
  z-index: 3;
}

.effect-box .stack-count {
  position: absolute;
  bottom: calc(-1 * var(--gui-padding-sm));
  right: calc(-1 * var(--gui-padding-sm));
  font-size: var(--effect-timer-size);
  font-family: var(--font-numbers);
  font-weight: bold;
  color: var(--text-primary);
  background: var(--alpha-black-70);
  padding: var(--gui-padding-sm);
  line-height: 1;
  z-index: 3;
}

/* SVG stroke cooldown indicator for timed effects */
.effect-box .effect-cd-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 2;
}

.effect-cd-track {
  fill: none;
  stroke: var(--alpha-white-08);
  stroke-width: 2;
}

.effect-cd-fill {
  fill: none;
  stroke-width: 2;
  stroke-linecap: square;
}

@keyframes effect-sweep {
  from { stroke-dashoffset: 88; }
  to   { stroke-dashoffset: 0; }
}

/* Dim icon when effect has duration (timed) */
.effect-box:not(.permanent) .effect-icon {
  opacity: 0.5;
}

/* Permanent effects - full brightness, no timer/stroke */
.effect-box.permanent .effect-timer,
.effect-box.permanent .effect-cd-svg {
  display: none;
}

/* ============================================
   FRAME ABILITIES (Cooldown Row)
   ============================================ */
.frame-abilities {
  display: flex;
  justify-content: flex-start;
  gap: var(--gui-gap-sm);
  min-height: 24px; /* Match effect row height */
  pointer-events: none; /* Click-through - children opt-in */
}

/* Mirror alignment for right dock (target frame) */
/* Right-side frames: mirrored layout (persists when undocked) */
.frame-wrapper:has([data-dock-side="right"]) .frame-effects {
  flex-direction: row-reverse;
}

.frame-wrapper:has([data-dock-side="right"]) .frame-buffs {
  justify-content: flex-end;
}

.frame-wrapper:has([data-dock-side="right"]) .frame-debuffs {
  justify-content: flex-start;
}

.frame-wrapper:has([data-dock-side="right"]) .frame-abilities {
  justify-content: flex-end;
}

/* Hide abilities row for non-hostile targets */
.frame-wrapper:has(#target-frame:not(.hostile)) .frame-abilities,
.frame-wrapper:has(#target-frame.hidden) .frame-abilities {
  display: none;
}


/* Individual ability cooldown box */
.ability-box {
  position: relative;
  width: var(--size-icon-md);
  height: var(--size-icon-md);
  background: var(--alpha-black-60);
  border: none;
  display: none; /* Hidden by default - only show when on cooldown */
  align-items: center;
  justify-content: center;
  font-size: var(--ability-icon-size);
  cursor: help;
}

.ability-box::after {
  content: '';
  position: absolute;
  inset: -10px;
  min-height: 44px;
  min-width: 44px;
}

/* Only show ability icons when on cooldown (active) */
.ability-box.on-cooldown {
  display: flex;
  pointer-events: auto; /* Hoverable for tooltip when visible */
}

.ability-box:hover .gui-tooltip,
.ability-box:focus-within .gui-tooltip {
  opacity: 1;
  visibility: visible;
}

/* Cooldown overlay (fills from bottom) */
.ability-box .ability-cd-overlay {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 100%;
  background: var(--alpha-black-70);
  transform: scale3d(1, calc(var(--cd-pct, 0) / 100), 1);
  transform-origin: bottom center;
  pointer-events: none;
  z-index: 1;
}

/* SVG stroke cooldown indicator (like Sprint) */
.ability-box .ability-cd-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 2;
  opacity: 0;
  transition: opacity var(--fade-snappy) ease;
}

.ability-box.on-cooldown .ability-cd-svg {
  opacity: 1;
}

.ability-cd-track {
  fill: none;
  stroke: var(--alpha-white-08);
  stroke-width: 2;
}

.ability-cd-fill {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: square;
}

/* Cooldown timer text */
.ability-box .ability-cd-timer {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--ability-timer-size);
  font-family: var(--font-numbers);
  color: var(--text-primary);
  z-index: 3;
  opacity: 0;
}

.ability-box.on-cooldown .ability-cd-timer {
  opacity: 1;
}

/* Icon dimmed when on cooldown */
.ability-box.on-cooldown .ability-icon {
  opacity: 0.4;
}

.ability-icon {
  position: relative;
  z-index: 0;
  transition: opacity var(--fade-snappy) ease;
}

@media (forced-colors: active) {
  .effect-box,
  .ability-box {
    border: 1px solid CanvasText;
  }
}

/* ============================================
   RESPONSIVE: Side column docking
   ============================================ */
/* When frame is docked to side columns, stack vertically */
.left-column .unit-frame,
.right-column .unit-frame {
  flex-direction: column;
}

/* Side column adjustments - keep left/right alignment */
.left-column .frame-effects,
.right-column .frame-effects {
  flex-wrap: wrap;
}

.left-column .frame-abilities,
.right-column .frame-abilities {
  flex-wrap: wrap;
}

/* Weakness display removed - combat simplification */

/* ============================================
   ACTION BAR
   ============================================ */
#action-bar,
#action-bar-2 {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--gui-padding-sm);
  background: var(--bg-panel-alpha);
  padding: var(--gui-padding);
  z-index: 20;
}

#action-bar-2 {
  display: none;
}

#game.action-bar-2-visible #action-bar-2 {
  display: flex;
}

/* Old weapon section styles removed - replaced with weapon toggle slot */

.action-slot {
  position: relative;
  width: var(--size-slot);
  height: var(--size-slot);
  background: var(--frame-inner);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  /* GPU-friendly transitions */
  transition: opacity var(--fade-snappy) ease, background-color var(--fade-snappy) ease;
  outline: none;
  -webkit-tap-highlight-color: transparent;
  /* Prevent native text selection/drag on emoji icons and labels */
  user-select: none;
  -webkit-user-drag: none;
}

.action-slot.queued {
  box-shadow: inset 0 0 0 2px oklch(1 0 0 / 0.6);
}

.action-slot:hover:not(.on-cooldown):not(.locked-slot):not(.empty-slot):not(.disabled) {
  background: var(--alpha-white-05);
}

.action-slot:focus-visible {
  outline: 2px solid var(--alpha-white-40);
  outline-offset: -2px;
}

/* Action slot tooltips now use the fixed-position singleton (.ability-tooltip)
   from abilityBook.js — inline .gui-tooltip is kept as fallback content but hidden.
   The fixed tooltip escapes the bottom-ui stacking context (z-index issue with portrait frames). */
.action-slot > .gui-tooltip {
  display: none;
}

.action-slot.active {
  /* No special styling - swing timer SVG handles visual feedback */
}

/* Disabled state - when targeting friendly NPC.
   Deliberately NO pointer-events:none (session 667, the beacon dead-click
   report): killing hit-testing made click delivery depend on this throttled,
   multi-writer class — a slot wearing a stale `disabled` ate every click
   while its keybind worked (input.js binds clicks per-slot; keyboard skips
   hit-testing). Presses must ALWAYS reach the handler; useAbility's gates
   refuse loudly (flash + toast, the P5b contract). The visual dim + cursor
   carry the affordance. Pinned by tests/hotbar-click-integrity.test.js. */
.action-slot.disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.action-slot.disabled::after {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    45deg,
    transparent,
    transparent 3px,
    var(--alpha-black-30) 3px,
    var(--alpha-black-30) 6px
  );
}

/* Press-refusal flash — per-press red pulse when a hotbar press is refused
   (out of range, GCD-blocked, no target, ...). The persistent .out-of-range
   tint below is the passive affordance; this flash is the ACTIVE per-press
   signal (WoW's red-flash contract, P5 strike). Animates opacity only
   (compositor-only property); the red fill is static paint on the overlay.
   Restarted per press via queueAnimRestart in combat.js flashPressRefusal. */
.action-slot > .refusal-flash {
  position: absolute;
  inset: 0;
  background: oklch(from var(--danger) l c h / 0.45);
  opacity: 0;
  pointer-events: none;
  z-index: 7;
}

@keyframes press-refused-flash {
  0%   { opacity: 1; }
  100% { opacity: 0; }
}

/* Out of range — WoW-style red tint when target is beyond ability range */
.action-slot.out-of-range {
  filter: saturate(0.3) brightness(0.7);
  opacity: 0.6;
}

.action-slot.out-of-range::before {
  content: '';
  position: absolute;
  inset: 0;
  background: oklch(from var(--danger) l c h / 0.2);
  pointer-events: none;
  z-index: 6;
}

/* Not enough Charge — WoW's proactive unusable affordance (P5b): a plain dim,
   deliberately weaker than .execute-locked (hard gate) and colorless unlike
   .out-of-range (positional). Same specificity as its siblings (one class);
   declared after .out-of-range so a slot that is both reads as unaffordable,
   matching useAbility's gate order (charge is checked before range). Static
   state class toggled by the 200ms action-bar sweep — no animation. */
.action-slot.no-charge {
  filter: saturate(0.2) brightness(0.6);
  opacity: 0.55;
}

.action-slot.on-cooldown {
  cursor: not-allowed;
}

/* Cooldown overlay — clockwise radial sweep from 12 o'clock.
   Hidden by default to prevent sub-pixel rendering artifacts from the
   conic-gradient within the GPU-composited #action-block layer. */
.cooldown-overlay {
  position: absolute;
  inset: 0;
  background: conic-gradient(
    transparent var(--cooldown-angle, 360deg),
    var(--alpha-black-70) var(--cooldown-angle, 360deg)
  );
  pointer-events: none;
  z-index: 5;
  display: none;
}

.action-slot.on-cooldown .cooldown-overlay {
  display: block;
}

/* Cooldown timer text — centered, large for readability over dark sweep overlay */
.cooldown-timer {
  position: absolute;
  inset: 0;
  align-items: center;
  justify-content: center;
  font-family: var(--font-numbers);
  font-size: 1.1rem;
  font-weight: bold;
  color: var(--text-primary);
  text-shadow: 0 0 3px var(--alpha-black-90), 0 1px 2px var(--alpha-black-70);
  pointer-events: none;
  z-index: 6;
  display: none;
}

.action-slot.on-cooldown .cooldown-timer {
  display: flex;
}

.slot-icon {
  font-size: var(--action-icon-size);
}

/* Labels hidden - icons only */
.slot-label {
  display: none;
  font-size: var(--action-label-size);
}

.slot-key {
  position: absolute;
  bottom: var(--gui-padding-sm);
  right: var(--gui-padding-sm);
  font-family: var(--font-numbers);
  font-size: var(--action-key-size);
  color: var(--text-muted);
  background: var(--alpha-black-50);
  padding: var(--gui-padding-sm);
}

/* Special slot - no unique border styling */
.special-slot {
  /* Uses default frame-border */
}

/* (removed .action-group — 12-slot bar is flat) */

/* Melee and ranged slots - no special styling, use default frame-border */
.melee-slot,
.ranged-slot {
  /* Uses default frame-border */
}

/* Execute-locked: execute-only abilities (Last Word + Skyfall) when target is above 25% HP (hard gate) */
.action-slot.execute-locked {
  filter: saturate(0.15) brightness(0.5);
  opacity: 0.5;
  cursor: not-allowed;
}

.action-slot.execute-locked::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--alpha-black-50);
  pointer-events: none;
  z-index: 6;
}

/* ============================================
   PROC BORDER — WoW-style sweeping glow on empowered abilities
   Compositor-only: the .proc-border wrapper holds a fixed CSS mask
   (hollow border ring) + overflow:hidden. The ::before pseudo-element
   carries the conic gradient and rotates via transform (GPU layer).
   Only transform + opacity are animated — zero repaints.
   ============================================ */

/* Empowered slot: proc-border animation only, no ambient glow */

/* Non-rotating wrapper: mask + clip.
   Base state: no animation (the implicit default). (Note: a paused animation is
   also dormant — measured session 511 — so the old "paused still costs per-frame
   bookkeeping, Chromium bug 40728212" claim was disproven.) */
.proc-border {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 8;
  opacity: 0;
  overflow: hidden;
  transition: opacity var(--fade-standard) ease;
  /* Hollow rectangle: mask creates 2px border ring inside slot edge,
     matching the SVG swingtimer stroke-width for visual consistency. */
  -webkit-mask:
    linear-gradient(oklch(0 0 0) 0 0) content-box,
    linear-gradient(oklch(0 0 0) 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(oklch(0 0 0) 0 0) content-box,
    linear-gradient(oklch(0 0 0) 0 0);
  mask-composite: exclude;
  padding: 2px;
}

/* Rotating gradient layer — oversized so the ring stays filled
   at every rotation angle. Only transform is animated (compositor). */
.proc-border::before {
  content: '';
  position: absolute;
  inset: -50%;
  background: conic-gradient(
    from 0deg,
    transparent 0%,
    var(--proc-glow) 10%,
    transparent 22%,
    transparent 50%,
    var(--proc-glow-dim) 60%,
    transparent 72%
  );
}

/* Show border ring when parent is empowered */
.action-slot.empowered > .proc-border {
  opacity: 1;
}

/* GPU-accelerated spin on the gradient layer only */
.action-slot.empowered > .proc-border::before {
  animation: proc-spin 2s linear infinite;
}

@keyframes proc-spin {
  to { transform: rotate3d(0, 0, 1, 360deg); }
}

/* Trigger flash: scale3d burst on the wrapper when proc first fires.
   Specificity (0,4,0) must beat empowered rule (0,3,0) above. */
.action-slot.empowered > .proc-border.proc-trigger {
  animation: proc-flash 0.35s ease-out;
}

@keyframes proc-flash {
  0% {
    opacity: 0;
    transform: scale3d(1.2, 1.2, 1);
  }
  40% {
    opacity: 1;
    transform: scale3d(1.06, 1.06, 1);
  }
  100% {
    opacity: 1;
    transform: scale3d(1, 1, 1);
  }
}

/* Swing timer styles moved to player portrait section above */

/* ============================================
   NEW ABILITY GLOW — soft gold backlight for freshly unlocked abilities
   Compositor-only: only opacity is animated. Sits behind slot content
   with negative inset to bleed beyond tile edges as a backlight.
   Suppressed when .empowered (proc border takes visual priority).
   ============================================ */
.new-glow {
  position: absolute;
  inset: -6px;
  pointer-events: none;
  z-index: 0;
  opacity: 0;
  background: radial-gradient(
    ellipse at center,
    var(--proc-glow-ambient) 0%,
    transparent 70%
  );
  transition: opacity var(--fade-snappy) ease;
}

.action-slot.new-ability > .new-glow {
  opacity: 1;
  animation: new-ability-glow-div 2s ease-in-out infinite;
}

.action-slot.empowered > .new-glow {
  opacity: 0;
  animation: none;
}

/* Gold breathing pulse on the slot itself (belt-and-suspenders with .new-glow div) */
.action-slot.new-ability {
  animation: new-ability-pulse 2s ease-in-out infinite;
}

.action-slot.new-ability.empowered {
  animation: none;
}

@keyframes new-ability-glow-div {
  0%, 100% { opacity: 0.7; }
  50% { opacity: 0.25; }
}

@keyframes new-ability-pulse {
  0%, 100% { box-shadow: 0 0 10px 3px var(--proc-glow-ambient); }
  50% { box-shadow: 0 0 4px 1px var(--proc-glow-subtle); }
}

/* ============================================
   CAST/CHANNEL TIMER (SVG stroke - GPU accelerated)
   ============================================ */
.cast-slot-wrapper {
  position: relative;
  display: inline-flex;
}

.cast-timer-border {
  position: absolute;
  inset: -1px;
  width: calc(100% + 2px);
  height: calc(100% + 2px);
  pointer-events: none;
  z-index: 10;
  opacity: 0;
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
}

/* Show when casting/channeling */
.cast-slot-wrapper.casting .cast-timer-border,
.cast-slot-wrapper.channeling .cast-timer-border {
  opacity: 1;
}

/* Cast/channel get a soft fade; GCD is instant (default transition: none
   on base rule) to prevent the track stroke hairline during opacity change */
.cast-slot-wrapper.casting .cast-timer-border,
.cast-slot-wrapper.channeling .cast-timer-border {
  transition: opacity var(--fade-standard) ease;
}

.cast-timer-track {
  fill: none;
  stroke: var(--alpha-white-08);
  stroke-width: 2;
  stroke-linecap: square;
  stroke-linejoin: miter;
}

.cast-timer-fill {
  fill: none;
  stroke: var(--cast-color, var(--accent));
  stroke-width: 2.5;
  stroke-linecap: square;
  stroke-linejoin: miter;
  transform: translate3d(0, 0, 0);
  /* PERF: Removed drop-shadow filter and will-change */
}

/* Channel state: same player blue as casts (both are positive player wind-ups) */
.cast-slot-wrapper.channeling .cast-timer-fill {
  --cast-color: var(--cast-player);
}

/* Cast state: player color — charged shot */
.cast-slot-wrapper.casting .cast-timer-fill {
  --cast-color: var(--cast-player);
}

/* Charge ability slots - same style as weapon abilities */

/* Locked slot — ability slotted but level not met (WoW-style pre-slotting)
   Compound selector (.action-slot.locked-slot) ensures specificity matches
   .action-slot.disabled / .action-slot.execute-locked so lock styles win
   via source order (this rule appears later in the stylesheet). */
.action-slot.locked-slot {
  cursor: not-allowed;
  opacity: 0.5;
  filter: grayscale(0.6);
}

.action-slot.locked-slot .slot-label {
  font-size: 0.55rem;
  color: var(--text-disabled);
}

.action-slot.locked-slot::after {
  content: '🔒';
  position: absolute;
  top: 2px;
  right: 2px;
  font-size: 0.55rem;
  opacity: 0.6;
  pointer-events: none;
}

/* Empty slot — no ability equipped (valid drop target) */
.empty-slot {
  cursor: default;
  border: 1px dashed var(--alpha-white-10);
  background: var(--alpha-black-20);
}

/* Hide all content in empty slots */
.empty-slot .slot-icon,
.empty-slot .slot-label,
.empty-slot .slot-key,
.empty-slot .cooldown-overlay,
.empty-slot .cooldown-timer {
  visibility: hidden;
}

/* Dragging state — bar slot being picked up */
.action-slot.dragging {
  opacity: 0.3;
  filter: grayscale(0.5);
}

/* Drop hover — valid drop target highlight during drag */
.action-slot.drop-hover {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  background: var(--alpha-white-05);
}

/* Hidden ability slots (MSQ not yet revealed) */
/* Charge abilities are completely hidden until the MSQ reveals them */
#charge-abilities.hidden-abilities {
  display: none !important;
}

/* Utility slots - same style as weapon abilities */

/* ============================================
   SPRINT TIMER (SVG stroke - GPU accelerated)
   ============================================ */
/* (removed .sprint-slot-wrapper — sprint is now a regular slot) */

.sprint-timer-border {
  position: absolute;
  inset: -1px;
  width: calc(100% + 2px);
  height: calc(100% + 2px);
  pointer-events: none;
  z-index: 10;
  opacity: 0;
  transition: opacity var(--fade-standard) ease;
  /* GPU layer via 3D transform */
  transform: translate3d(0, 0, 0);
}

/* Show when sprint buff is active */
.action-slot.sprint-active .sprint-timer-border {
  opacity: 1;
}

/* Sprint duration reuses cooldown overlay/timer — show during buff */
.action-slot.sprint-active .cooldown-overlay {
  display: block;
}

.action-slot.sprint-active .cooldown-timer {
  display: flex;
}

/* Background track (subtle) */
.sprint-timer-track {
  fill: none;
  stroke: var(--alpha-white-08);
  stroke-width: 2;
  stroke-linecap: square;
  stroke-linejoin: miter;
}

/* Progress fill stroke */
.sprint-timer-fill {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.5;
  stroke-linecap: square;
  stroke-linejoin: miter;
  /* GPU layer for performance */
  transform: translate3d(0, 0, 0);
  /* PERF: Removed drop-shadow filter and will-change */
  /* Transition set dynamically by JS for precise timing */
}

/* Buff active state - uses same style as cooldown */

/* ============================================
   XP ROW (anchored to bottom of screen)
   Level boxes flanking the XP bar with gap
   Width synced to action bar via JS (--xp-row-width)
   ============================================ */
#xp-row {
  display: flex;
  align-items: flex-end; /* Level boxes taller, XP bar aligns to bottom */
  justify-content: center;
  gap: var(--gui-gap); /* Gap between boxes and bar */
  pointer-events: auto;
  /* Reduce gap from abilities row above */
  margin-top: calc(-1.5 * var(--gui-margin));
  /* Extend to bottom edge by compensating for bottom-ui margin */
  margin-bottom: calc(-1 * var(--gui-margin));
}

/* Level boxes (current and next) - outside the bar */
.xp-level-box {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  min-width: 24px;
  height: 24px;
  background: var(--bg-panel-alpha);
  font-family: var(--font-numbers);
  font-size: var(--font-size-xs);
  font-weight: 600;
  color: var(--text-primary);
}

/* ============================================
   XP BAR (inside xp-row)
   Width synced to action bar via JS
   ============================================ */
#xp-bar-container {
  position: relative;
  z-index: 25;
  width: var(--xp-row-width, 300px); /* Synced to action bar width via JS */
  min-width: 100px;
}

#xp-bar-track {
  position: relative;
  height: 8px;
  background: var(--bg-panel-alpha);
  overflow: hidden;
}

/* XP bar tooltip */
#xp-bar-container:focus-visible {
  outline: 2px solid var(--alpha-white-40);
  outline-offset: -1px;
}

#xp-bar-container:hover .gui-tooltip,
#xp-bar-container:focus-within .gui-tooltip {
  opacity: 1;
  visibility: visible;
}

#xp-bar-level {
  color: var(--color-rarity-legendary);
  font-weight: 600;
}

#xp-bar-progress {
  color: var(--text-secondary);
}

#xp-bar-fill {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  background: var(--xp-color);
  /* GPU-accelerated: scale3d instead of width */
  transform: scale3d(calc(var(--xp-pct, 0) / 100), 1, 1);
  transform-origin: left center;
  transition: transform 0.4s ease-out;
}

#xp-bar-notches {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.xp-notch {
  position: absolute;
  width: 1px;
  height: 100%;
  background: var(--alpha-white-15);
}

.xp-notch:nth-child(1) { left: 25%; }
.xp-notch:nth-child(2) { left: 50%; }
.xp-notch:nth-child(3) { left: 75%; }

/* XP bar text - inside the bar */
/* XP bar text hidden - using compact bar */

/* Rested XP bonus indicator (optional enhancement) */
#xp-bar-fill.rested::after {
  content: '';
  position: absolute;
  top: 0;
  left: 100%;
  height: 100%;
  width: 20%;
  background: linear-gradient(180deg, var(--xp-rested) 0%, var(--xp-rested-mid) 50%, var(--xp-rested-dark) 100%);
  opacity: 0.6;
}

/* ============================================
   DIALOGUE SHROUD
   Replaces native ::backdrop — show() avoids
   Chrome's expensive top-layer mechanism that
   showModal() triggers (~26ms render spike).
   Matches dialogue backdrop timing pattern.
   ============================================ */
#dialogue-shroud {
  position: fixed;
  inset: 0;
  background: var(--bg-panel-alpha);
  z-index: 199;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  cursor: pointer;
  transition:
    opacity var(--fade-standard) ease-out,
    visibility var(--fade-standard) ease-out;
  transition-delay: 350ms;
}

#dialogue-shroud.visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition-delay: 0ms;
}

/* ============================================
   DIALOGUE PANEL
   Cinematic enter/exit with staggered shroud.
   Uses show()/close() instead of showModal() to
   avoid top-layer promotion and ::backdrop cost.
   ============================================ */
#dialogue-panel {
  position: fixed;
  top: 50%;
  left: 50%;
  margin: 0;
  padding: calc(var(--gui-padding) * 3);
  width: min(90vw, 600px);
  max-height: 80vh;
  overflow-y: auto;
  background: var(--bg-panel-alpha);
  border: var(--border-hairline);
  font-family: var(--font-dialogue);
  pointer-events: auto;
  z-index: 200;
  box-shadow: var(--shadow-lg);
  contain: layout style paint;
  opacity: 0;
  translate: -50% calc(-50% - 40px) 0;
  scale: 0.8;
  transition: 
    opacity 450ms cubic-bezier(0.36, 0, 0.66, -0.56),
    translate 450ms cubic-bezier(0.36, 0, 0.66, -0.56),
    scale 450ms cubic-bezier(0.36, 0, 0.66, -0.56),
    display 450ms allow-discrete;
}

#dialogue-panel[open] {
  opacity: 1;
  translate: -50% -50% 0;
  scale: 1;
  transition: 
    opacity 550ms cubic-bezier(0.34, 1.56, 0.64, 1),
    translate 550ms cubic-bezier(0.34, 1.56, 0.64, 1),
    scale 550ms cubic-bezier(0.34, 1.56, 0.64, 1),
    display 550ms allow-discrete;
  transition-delay: 200ms;
}

#dialogue-panel[open].modal-entering {
  opacity: 0;
  translate: -50% calc(-50% - 40px) 0;
  scale: 0.8;
}

/* Inner wrapper for flex gap (dialog element doesn't support gap) */
#dialogue-inner {
  display: flex;
  flex-direction: column;
  gap: var(--gui-gap-lg);
}

/* Dialogue content wrapper - portrait + text side by side */
#dialogue-content {
  display: flex;
  gap: var(--gui-gap-lg);
}

/* Portrait - 2x the size of target frame portrait */
#dialogue-portrait {
  flex-shrink: 0;
  width: calc(var(--size-portrait-md) * 2);
  height: calc(var(--size-portrait-md) * 2);
  background: var(--frame-inner);
  display: flex;
  align-items: center;
  justify-content: center;
}

#dialogue-portrait .portrait-icon {
  font-size: calc(var(--portrait-icon-size) * 2);
}

/* Body - contains speaker name and text */
#dialogue-body {
  display: flex;
  flex-direction: column;
  gap: var(--gui-gap-lg);
  flex: 1;
  min-width: 0; /* Allow text to wrap */
  padding: var(--gui-padding-sm) 0;
}

#dialogue-speaker {
  padding: 0 var(--gui-padding) 0 0;
  font-size: var(--font-size-md);
  font-weight: 600;
  color: var(--accent);
}

#dialogue-text {
  font-size: var(--font-size-xl);
  line-height: 1.5;
  color: var(--text-primary);
  min-height: calc(3 * 1.5 * var(--font-size-xl)); /* Stabilize height during typewriter */
}

#dialogue-choices {
  display: flex;
  flex-direction: column;
  gap: var(--gui-gap-sm);
  padding: 0;
}

.dialogue-choice {
  background: var(--frame-inner);
  border: none;
  padding: var(--gui-padding);
  text-align: left;
  font-family: inherit;
  font-size: var(--font-size-lg);
  color: var(--text-primary);
  cursor: pointer;
  /* Slow trail fade-out on release */
  transition: background-color 0.6s ease-out, color 0.6s ease-out;
  display: flex;
  align-items: center;
  gap: var(--gui-gap);
}

.dialogue-choice:hover:not([disabled]),
.dialogue-choice.selected:not([disabled]) {
  background: var(--accent-hover);
  color: var(--text-primary);
  outline: none;
  transition: none;
}

.dialogue-choice:focus-visible:not([disabled]) {
  outline: 2px solid var(--alpha-white-40);
  outline-offset: -2px;
}

.dialogue-choice:hover:not([disabled]) .choice-text,
.dialogue-choice.selected:not([disabled]) .choice-text {
  color: var(--text-primary);
}

.dialogue-choice[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}

.choice-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  background: var(--bg-dark);
  border: none;
  font-size: var(--font-size-sm);
  font-weight: bold;
  color: var(--text-primary);
  flex-shrink: 0;
  order: 1;
}

.choice-text {
  flex: 1;
  /* Slow trail fade-out on release */
  transition: color 0.6s ease-out, background-color 0.6s ease-out;
}

.dialogue-choice:hover .choice-text,
.dialogue-choice.selected .choice-text {
  /* Instant snap on hover */
  transition: none;
}

/* Quest icon for quest-related dialogue options */
.choice-quest-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  background: var(--warning);
  color: var(--text-on-fill);
  font-size: var(--font-size-sm);
  font-weight: bold;
  flex-shrink: 0;
  margin-left: auto;
}

/* Back/navigation options in dialogue */
.dialogue-choice.back-option,
.dialogue-choice.goodbye-option {
  margin-top: var(--gui-gap-md);
}

/* Back option - muted yellow on hover */
.dialogue-choice.back-option:hover,
.dialogue-choice.back-option.selected {
  background: var(--warning-subtle);
}

/* Goodbye option - red on hover */
.dialogue-choice.goodbye-option:hover,
.dialogue-choice.goodbye-option.selected {
  background: var(--danger-subtle);
}

/* ============================================
   INVENTORY PANEL
   ============================================ */
#inventory-panel {
  position: relative;
  /* Wider than other column panels: a grid of slot cells needs the room. */
  min-width: 300px;
  background: var(--bg-panel-alpha);
  font-family: var(--font-display);
}

#inventory-panel.hidden {
  display: none;
}

/* When undocked, use fixed positioning */
#inventory-panel.undocked {
  position: fixed;
}

#inventory-list {
  max-height: 200px;
  overflow-y: auto;
  padding: var(--gui-padding);
}

.inventory-item {
  display: flex;
  align-items: center;
  gap: var(--gui-gap);
  padding: var(--gui-padding);
  cursor: pointer;
}

.inventory-item:hover,
.inventory-item:focus-visible {
  background: var(--alpha-white-05);
}

.inventory-item-icon {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-sm);
  font-weight: bold;
}

.inventory-item-icon.green { background: var(--color-rarity-uncommon); }
.inventory-item-icon.blue { background: var(--color-rarity-rare); }
.inventory-item-icon.gold { background: var(--color-rarity-legendary); }
.inventory-item-icon.common { background: var(--color-rarity-common); }
.inventory-item-icon.uncommon { background: var(--color-rarity-uncommon); }
.inventory-item-icon.rare { background: var(--color-rarity-rare); }
.inventory-item-icon.epic { background: var(--color-rarity-epic); }
.inventory-item-icon.legendary { background: var(--color-rarity-legendary); }

.inventory-item-info { flex: 1; }
.inventory-item-name { font-size: var(--font-size-md); font-weight: 600; }
.inventory-item-rarity { font-size: var(--font-size-xs); color: var(--text-secondary); }
.inventory-item-desc { font-size: var(--font-size-xs); color: var(--text-muted); }
.inventory-item-qty { font-size: var(--font-size-sm); color: var(--text-secondary); }
.inventory-use-btn {
  padding: 2px var(--gui-padding);
  font-size: var(--font-size-xs);
  background: var(--alpha-white-10);
  border: 1px solid var(--frame-border);
  color: var(--text-primary);
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.inventory-use-btn:hover { background: var(--alpha-white-15); }

/* Bag (roadmap 2.2) — functional grid. Visual polish deferred to Phase 5. Static
   layout only (no animated/transitioned properties), so no render-perf concern. */
.bag-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gui-gap-sm);
  padding: var(--gui-padding-sm) var(--gui-padding);
  border-bottom: 1px solid var(--frame-border);
}
.bag-tabs.hidden { display: none; }
.bag-tab {
  padding: 2px var(--gui-padding);
  font-size: var(--font-size-xs);
  font-family: var(--font-display);
  background: var(--alpha-white-05);
  border: 1px solid transparent;
  color: var(--text-secondary);
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.bag-tab:hover { background: var(--alpha-white-10); }
.bag-tab.active {
  background: var(--alpha-white-10);
  border-color: var(--frame-border);
  color: var(--text-primary);
}

#inventory-list.bag-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, var(--size-slot));
  gap: var(--gui-gap);
  justify-content: start;
  align-content: start;
}

.bag-cell {
  position: relative;
  width: var(--size-slot);
  height: var(--size-slot);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: var(--alpha-white-05);
  border: 1px solid var(--frame-border);
}
.bag-cell-actionable { cursor: pointer; }
.bag-cell-busy { opacity: 0.5; pointer-events: none; }
.bag-cell-icon { width: 36px; height: 36px; }
.bag-cell-qty {
  position: absolute;
  right: 2px;
  bottom: 1px;
  font-size: var(--font-size-xs);
  color: var(--text-primary);
  background: var(--bg-panel-alpha);
  padding: 0 2px;
}
.bag-empty {
  grid-column: 1 / -1;
  text-align: center;
  color: var(--text-muted);
  padding: var(--gui-padding);
}

/* Slotted-bag interaction states (roadmap 2.2 cutover). Static styles only — no
   animated layout / box-shadow / filter, so the render-perf lint stays clean. */
.bag-cell-empty { border-style: dashed; }
.bag-cell-grabbed {
  border-color: var(--accent);
  background: var(--alpha-white-10);
}
.bag-cell-drop-target { border-color: var(--accent); }
/* Worn / broken gear markers: a small corner dot. */
.bag-cell-worn::after,
.bag-cell-broken::after {
  content: '';
  position: absolute;
  left: 2px;
  bottom: 2px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
}
.bag-cell-worn::after { background: var(--color-rarity-uncommon); }
.bag-cell-broken::after { background: var(--color-rarity-legendary); }
/* Drag ghost (body-appended): follows the cursor while dragging a bag item. */
.bag-drag-ghost {
  position: fixed;
  z-index: 10001;
  pointer-events: none;
  transform: translate(-50%, -50%);
  width: var(--size-slot-sm);
  height: var(--size-slot-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-panel-alpha);
  border: 1px solid var(--accent);
  font-size: var(--font-size-md);
  color: var(--text-primary);
}

.bag-capacity {
  padding: var(--gui-padding-sm) var(--gui-padding);
  border-top: 1px solid var(--frame-border);
  font-size: var(--font-size-xs);
  color: var(--text-secondary);
  text-align: right;
}
.bag-capacity-full { color: var(--color-rarity-legendary); }

.bag-tooltip {
  position: fixed;
  z-index: 10000;
  pointer-events: none;
  max-width: 240px;
  padding: var(--gui-padding);
  background: var(--bg-panel-alpha);
  border: 1px solid var(--frame-border);
  font-family: var(--font-display);
  font-size: var(--font-size-xs);
}
.bag-tooltip[hidden] { display: none; }
.bag-tooltip-name { font-size: var(--font-size-sm); font-weight: 600; }
.bag-tooltip-name.green, .bag-tooltip-name.uncommon { color: var(--color-rarity-uncommon); }
.bag-tooltip-name.blue, .bag-tooltip-name.rare { color: var(--color-rarity-rare); }
.bag-tooltip-name.epic { color: var(--color-rarity-epic); }
.bag-tooltip-name.gold, .bag-tooltip-name.legendary { color: var(--color-rarity-legendary); }
.bag-tooltip-name.common { color: var(--color-rarity-common); }
.bag-tooltip-sub { color: var(--text-muted); margin-bottom: 2px; }
.bag-tooltip-stat { color: var(--text-secondary); }
.inventory-use-btn:disabled { opacity: 0.4; cursor: default; }

/* ============================================
   SHOP PANEL
   Float-only vendor interaction panel
   ============================================ */

#shop-panel {
  position: fixed;
  z-index: 60;
  background: var(--bg-panel);
  border: var(--frame-border-width) solid var(--frame-border);
  box-shadow: var(--shadow-lg);
  font-family: var(--font-display);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  user-select: none;
  contain: layout style;
}

#shop-panel.hidden { display: none; }

#shop-panel:not(.hidden) { pointer-events: auto; }

#shop-buy-section,
#shop-sell-section,
#shop-repair-section {
  flex: 1;
  overflow-y: auto;
  padding: 0;
  min-height: 80px;
}

#shop-buy-list,
#shop-sell-list,
#shop-repair-list {
  padding: var(--gui-padding);
}

.shop-section-label {
  padding: var(--gui-padding);
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border-bottom: 1px solid var(--frame-border);
  background: var(--alpha-white-03);
}

.shop-item {
  display: flex;
  align-items: center;
  gap: var(--gui-gap);
  padding: var(--gui-padding);
}

.shop-item:hover {
  background: var(--alpha-white-05);
}

.shop-price {
  font-size: var(--font-size-sm);
  color: var(--color-gold);
  white-space: nowrap;
  min-width: 36px;
  text-align: right;
}

.shop-price-sell {
  color: var(--color-jade);
}

.shop-buy-btn,
.shop-sell-btn {
  padding: var(--gui-padding-sm) var(--gui-padding);
  min-height: 24px;
  font-size: var(--font-size-xs);
  background: var(--alpha-white-10);
  border: 1px solid var(--frame-border);
  color: var(--text-primary);
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.shop-buy-btn:hover,
.shop-sell-btn:hover { background: var(--alpha-white-15); }

.shop-buy-btn:disabled,
.shop-sell-btn:disabled { opacity: 0.4; cursor: default; }

.shop-close-btn {
  opacity: 0.6;
  font-size: var(--font-size-lg);
  width: 24px;
  height: 24px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.shop-close-btn:hover {
  opacity: 1;
  background: var(--alpha-white-10);
}

#shop-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--gui-gap);
}

#shop-currency::before {
  content: '⬡ ';
  opacity: 0.6;
}

/* ============================================
   NOTIFICATIONS (Toasts & Prompts)
   Unified feedback system with color-coded left bars
   ============================================ */

/* Base notification styles */
.notification {
  background: var(--bg-panel);
  border-left: 3px solid var(--text-muted);
  padding: var(--gui-padding);
  font-family: var(--font-display);
  font-size: var(--font-size-md);
  color: var(--text-primary);
  pointer-events: none; /* Click-through - notifications are visual only */
}

/* Semantic color types */
.notification.info    { border-left-color: var(--info); }
.notification.success { border-left-color: var(--success); }
.notification.warning { border-left-color: var(--warning); }
.notification.error   { border-left-color: var(--danger); }
.notification.accent  { border-left-color: var(--accent); }

/* Game-specific types */
.notification.xp      { border-left-color: var(--xp-color); }
.notification.item    { border-left-color: var(--success); }
.notification.quest    { border-left-color: var(--warning); }
.notification.currency { border-left-color: var(--color-gold); }

/* --- Travel cast bar (fast travel: 5s server cast) --- */
#travel-cast {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gui-gap-sm);
  padding: var(--gui-padding-sm) var(--gui-padding);
  background: var(--bg-panel-alpha);
  border: 1px solid var(--alpha-white-08);
  border-radius: 4px;
  pointer-events: none;
}

#travel-cast-label {
  font-family: var(--font-sans);
  font-size: var(--font-size-xs);
  color: var(--text-secondary);
}

.travel-cast-track {
  width: 180px;
  height: 6px;
  background: var(--cast-bar-bg);
  border-radius: 2px;
  overflow: hidden;
}

.travel-cast-fill {
  width: 100%;
  height: 100%;
  background: var(--accent);
  border-radius: 2px;
  /* scaleX (not width) so cast progress animates on the compositor; the
     duration is stamped inline by travelCast.js from the server castTimeMs. */
  transform: scale3d(0, 1, 1);
  transform-origin: left center;
}

.reduced-motion .travel-cast-fill { transition-duration: 0.01ms !important; }

/* --- Teleport fade (fast travel / beacon arrival veil) ---
   The WoW/FFXIV arrival pattern: the one-frame world move happens behind
   black. Sibling of #tab-resume-overlay with the same stacking rationale;
   dormant at content-visibility: hidden (travelCast.js flips it). */
#teleport-fade {
  position: fixed;
  inset: 0;
  z-index: 9998; /* below #death-overlay (9999): a death mid-fade still wins */
  background: var(--bg-void);
  opacity: 0;
  pointer-events: none;
  transition: opacity 450ms ease-out;
  contain: layout style paint;
  content-visibility: hidden;
}

#teleport-fade.active {
  opacity: 1;
  transition: opacity 280ms ease-in;
}

.reduced-motion #teleport-fade { transition-duration: 0.01ms !important; }

/* --- Interact Prompt --- 
   Quick fade in/out animation. Override global .hidden to allow transition.
   AFTER the fade completes, checkInteraction() (game.js) flips inline
   display:none: sightings 19-22 convicted the always-laid-out hidden prompt
   of painting its empty box while computed-hidden in Safari (pixel-watcher
   paint-state probe: box paints, text doesn't, DOM reads honest).
   display:none removes the box entirely — the comms card's zero-hit
   precedent. The flip is JS-scheduled (not `transition: display ...
   allow-discrete`) because Firefox doesn't animate display yet (Bugzilla
   1834877: allow-discrete parses but exit fades die instantly), and the
   fade-out feel is this fix's bar — one JS mechanism behaves identically in
   all three floor engines. @starting-style keeps the fade-IN animating when
   the element re-enters from display:none (entry transitions work in all
   three engines; without it the prompt would pop to full opacity). */
#interact-prompt {
  display: flex !important;
  align-items: center;
  gap: var(--gui-gap);
  pointer-events: none;
  opacity: 1;
  visibility: visible;
  transition: opacity var(--fade-snappy) ease-out, visibility 0s linear 0s;
}

#interact-prompt.hidden {
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--fade-snappy) ease-out, visibility 0s linear var(--fade-snappy);
}

@starting-style {
  #interact-prompt {
    opacity: 0;
  }
}

#interact-prompt:empty { display: none !important; }

#interact-prompt kbd {
  background: var(--frame-inner);
  padding: var(--gui-padding-sm) var(--gui-padding);
  font-family: var(--font-numbers);
  font-size: var(--font-size-sm);
}

/* --- Comms Card (NPC phone calls) --- */
#comms-card {
  display: flex;
  align-items: flex-start;
  gap: var(--gui-gap-lg);
  background: var(--bg-panel-alpha);
  padding: calc(var(--gui-padding) * 2);
  max-width: 360px;
  pointer-events: auto;
  cursor: pointer;
}

#comms-card.hidden {
  display: none;
}

/* Enter animation */
#comms-card.comms-enter {
  animation: comms-in 0.3s ease forwards;
}

/* Exit animation */
#comms-card.comms-exit {
  animation: comms-out 0.3s ease forwards;
}

@keyframes comms-in {
  from {
    opacity: 0;
    transform: translate3d(0, 20px, 0);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

@keyframes comms-out {
  from {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
  to {
    opacity: 0;
    transform: translate3d(0, -10px, 0);
  }
}

/* Portrait */
#comms-portrait {
  position: relative;
  flex-shrink: 0;
  width: var(--size-portrait-md);
  height: var(--size-portrait-md);
  background: var(--frame-inner);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--portrait-icon-size);
}

/* Stroke timer SVG - shows countdown before dismiss */
.comms-timer-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--fade-standard) ease;
}

#comms-card.countdown .comms-timer-svg {
  opacity: 1;
}

.comms-timer-track {
  fill: none;
  stroke: var(--alpha-white-08);
  stroke-width: 2;
}

.comms-timer-fill {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  /* Perimeter of 46x46 rect = 2*(46+46) = 184 */
  stroke-dasharray: 184;
  stroke-dashoffset: 0;
}

/* Content area */
#comms-content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--gui-gap-lg);
  padding: var(--gui-padding-sm) 0;
}

/* Speaker row - name + wave animation */
#comms-speaker-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gui-gap);
}

#comms-speaker {
  color: var(--accent);
  font-family: var(--font-dialogue);
  font-size: var(--font-size-md);
  font-weight: 600;
  text-transform: capitalize;
}

/* Sound wave animation */
#comms-wave {
  display: flex;
  align-items: center;
  gap: 2px;
  height: 12px;
  opacity: 0;
  transition: opacity var(--fade-standard) ease-out;
}

#comms-card.typing #comms-wave {
  opacity: 1;
  transition: opacity var(--fade-snappy) ease-in;
}

#comms-wave span {
  display: block;
  width: 2px;
  height: 12px; /* Fixed max height - animation uses scaleY */
  background: var(--warning);
  border-radius: 1px;
  transform-origin: center center;
}

/* PERF: Each bar uses scaleY instead of height (GPU-accelerated) */
#comms-wave span:nth-child(1) {
  animation: comms-wave-1 0.42s ease-in-out infinite;
}
#comms-wave span:nth-child(2) {
  animation: comms-wave-2 0.55s ease-in-out infinite;
  animation-delay: 0.1s;
}
#comms-wave span:nth-child(3) {
  animation: comms-wave-3 0.38s ease-in-out infinite;
  animation-delay: 0.05s;
}
#comms-wave span:nth-child(4) {
  animation: comms-wave-4 0.48s ease-in-out infinite;
  animation-delay: 0.15s;
}

@keyframes comms-wave-1 {
  0%, 100% { transform: scale3d(1, 0.25, 1); }
  50% { transform: scale3d(1, 0.83, 1); }
}

@keyframes comms-wave-2 {
  0%, 100% { transform: scale3d(1, 0.42, 1); }
  35% { transform: scale3d(1, 1, 1); }
  70% { transform: scale3d(1, 0.5, 1); }
}

@keyframes comms-wave-3 {
  0%, 100% { transform: scale3d(1, 0.33, 1); }
  40% { transform: scale3d(1, 0.67, 1); }
  80% { transform: scale3d(1, 0.92, 1); }
}

@keyframes comms-wave-4 {
  0%, 100% { transform: scale3d(1, 0.25, 1); }
  60% { transform: scale3d(1, 0.75, 1); }
}

#comms-text {
  margin: 0;
  color: var(--text-primary);
  font-family: var(--font-dialogue);
  font-size: var(--font-size-xl);
  line-height: 1.4;
  text-wrap: pretty;
}

/* Typewriter word spans - prevent mid-word line breaks (shared by dialogue & comms) */
.tw-word {
  white-space: nowrap;
}

/* --- Toast Container --- */
/* In-game toasts. Body-level (moved out of #bottom-ui so its z-index isn't trapped by
   the bottom-HUD stacking context). Fixed bottom-center, above the combat dock. The
   z-index sits above in-game modals (settings 200, worldmap 320) but BELOW the launcher
   (10000), so a lingering persistent toast (e.g. "session expired") is covered by the
   launcher on return-to-lobby instead of bleeding over the sign-in screen. The bottom
   offset clears the portrait + abilities dock; verify on a real screen. */
/* ============================================
   RESURRECTION PROMPT (corpse-run revive-in-place)
   Non-blocking HUD element: movement stays live while it shows (walking
   away IS the decline), so it must never ride the dialogue panel (which
   blocks movement) and captures no input beyond its one button.
   Sits above the toast stack; removed from the DOM when hidden.
   ============================================ */
#revive-prompt {
  position: fixed;
  bottom: 230px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 500;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gui-gap);
  padding: var(--gui-padding);
  background: var(--bg-panel-alpha);
  border: var(--frame-border-width) solid var(--frame-border);
  pointer-events: none; /* container click-through; the button opts back in */
}

.revive-prompt-text {
  font-family: var(--font-display);
  font-size: var(--font-size-sm);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.revive-prompt-button {
  pointer-events: auto;
  background: var(--frame-inner);
  border: var(--frame-border-width) solid var(--frame-border);
  padding: var(--gui-padding-sm) var(--gui-padding);
  font-family: inherit;
  font-size: var(--font-size-lg);
  color: var(--text-primary);
  cursor: pointer;
}

.revive-prompt-button:hover,
.revive-prompt-button:focus-visible {
  background: var(--accent-hover);
  outline: none;
}

#toast-container {
  position: fixed;
  bottom: 170px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 500;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gui-gap-sm);
  pointer-events: none; /* Click-through */
}

#toast-container:empty { display: none; }

/* Toast-specific: animation */
.toast {
  animation: toast-in 0.3s ease, toast-out 0.3s ease 2.7s forwards;
  white-space: nowrap;
}

@keyframes toast-in {
  from { opacity: 0; transform: translate3d(0, 10px, 0); }
  to { opacity: 1; transform: translate3d(0, 0, 0); }
}

@keyframes toast-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

/* --- Persistent Toasts (no auto-dismiss) --- */
.toast.persistent {
  animation: toast-in 0.3s ease forwards;
  box-shadow: 0 2px 8px var(--alpha-black-40);
  display: flex;
  align-items: center;
  gap: var(--gui-gap);
  pointer-events: auto;
}

.toast-dismiss {
  flex-shrink: 0;
  background: none;
  border: none;
  color: inherit;
  font-size: 1.1em;
  cursor: pointer;
  opacity: 0.6;
  padding: 2px 4px;
  line-height: 1;
}

.toast-dismiss:hover,
.toast-dismiss:focus-visible {
  opacity: 1;
}

.toast.persistent.fade-out {
  animation: toast-out 0.2s ease forwards;
}

/* Error persistent toast - red border accent with subtle pulse */
.toast.persistent.error {
  border-left-color: var(--danger);
  animation: toast-in 0.3s ease, persistent-pulse 2s ease-in-out 0.3s infinite;
}

@keyframes persistent-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.8; }
}

/* --- Debug Panel (toggle via window.__vmShowToastDebug()) --- */
#toast-debug-panel {
  position: fixed;
  top: var(--gui-margin);
  right: var(--gui-margin);
  background: var(--bg-panel-alpha);
  border: var(--frame-border-width) solid var(--frame-border);
  padding: var(--gui-padding);
  z-index: 450;
  display: none;
  flex-direction: column;
  gap: var(--gui-gap-sm);
  max-width: 280px;
  pointer-events: auto; /* Interactive when visible */
}

#toast-debug-panel.visible {
  display: flex;
}

#toast-debug-panel .debug-title {
  font-family: var(--font-display);
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding-bottom: var(--gui-padding-sm);
  border-bottom: 1px solid var(--alpha-white-05);
}

#toast-debug-panel .toast {
  animation: none;
  position: relative;
}

#toast-debug-panel .toast-type-label {
  position: absolute;
  top: 50%;
  right: var(--gui-padding);
  transform: translateY(-50%);
  font-size: var(--font-size-2xs);
  font-family: var(--font-numbers);
  color: var(--text-muted);
  background: var(--alpha-black-50);
  padding: 2px 4px;
}

/* ============================================
   SETTINGS MENU
   ============================================ */

/* Settings shroud — replaces ::backdrop to avoid showModal top-layer cost.
   Matches #dialogue-shroud pattern. */
#settings-shroud {
  position: fixed;
  inset: 0;
  background: var(--bg-panel-alpha);
  z-index: 199;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  cursor: pointer;
  transition:
    opacity var(--fade-standard) ease-out,
    visibility var(--fade-standard) ease-out;
  transition-delay: 350ms;
}

#settings-shroud.visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition-delay: 0ms;
}

/* Settings modal — uses show()/close() to avoid top-layer promotion cost */
#settings-menu {
  position: fixed;
  top: 50%;
  left: 50%;
  margin: 0;
  translate: -50% calc(-50% - 40px) 0;
  pointer-events: auto;
  border: var(--border-hairline);
  padding: 0;
  width: 640px;
  max-width: 90vw;
  height: 85vh;
  background: var(--bg-panel-alpha);
  font-family: var(--font-display);
  color: inherit;
  box-shadow: var(--shadow-lg);
  contain: layout style paint;
  z-index: 200;
  opacity: 0;
  scale: 0.8;
  transition:
    opacity 450ms cubic-bezier(0.36, 0, 0.66, -0.56),
    scale 450ms cubic-bezier(0.36, 0, 0.66, -0.56),
    translate 450ms cubic-bezier(0.36, 0, 0.66, -0.56),
    display 450ms allow-discrete;
}

#settings-menu[open] {
  display: flex;
  flex-direction: column;
  opacity: 1;
  scale: 1;
  translate: -50% -50% 0;
  transition:
    opacity 550ms cubic-bezier(0.34, 1.56, 0.64, 1),
    scale 550ms cubic-bezier(0.34, 1.56, 0.64, 1),
    translate 550ms cubic-bezier(0.34, 1.56, 0.64, 1),
    display 550ms allow-discrete;
  transition-delay: 200ms;
}

#settings-menu[open].modal-entering {
  opacity: 0;
  scale: 0.8;
  translate: -50% calc(-50% - 40px) 0;
}

/* ---- Header ---- */
.settings-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--gui-padding);
  border-bottom: var(--border-hairline);
  background: var(--alpha-white-02);
  flex-shrink: 0;
}

.settings-header h2 {
  font-size: var(--font-size-lg);
  font-weight: 600;
  color: var(--text-primary);
}

.settings-close {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: var(--font-size-2xl);
  cursor: pointer;
  padding: 0;
  min-width: 24px;
  min-height: 24px;
  line-height: 1;
  transition: color 0.15s ease;
}

.settings-close:hover,
.settings-close:focus-visible {
  color: var(--text-primary);
}

/* ---- Body: sidebar + content ---- */
.settings-body {
  display: flex;
  flex: 1;
  min-height: 0; /* Allow flex children to shrink */
  /* max-height removed: parent #settings-menu has explicit height: 85vh;
     capping the body lower would leave an empty band at the dialog bottom
     in Chromium. Refactor 8 / visual-state-refactor.md §3.2. */
}

/* ---- Sidebar tab navigation ---- */
.settings-tabs {
  display: flex;
  flex-direction: column;
  width: 140px;
  flex-shrink: 0;
  background: var(--bg-dark);
  border-right: var(--border-hairline);
  padding: var(--gui-gap-sm) 0;
}

.settings-tab {
  display: block;
  width: 100%;
  padding: 0.5rem var(--gui-padding);
  background: none;
  border: none;
  border-left: 2px solid transparent;
  font-family: var(--font-display);
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-align: left;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}

.settings-tab:hover,
.settings-tab:focus-visible {
  color: var(--text-secondary);
  background: var(--alpha-white-02);
}

.settings-tab.active {
  color: var(--text-primary);
  border-left-color: var(--accent);
  background: var(--alpha-white-03);
}

/* ---- Content area ---- */
.settings-content {
  flex: 1;
  padding: var(--gui-padding);
  overflow-y: auto;
  min-width: 0; /* Prevent flex blowout */
  scrollbar-width: thin;
  scrollbar-color: var(--alpha-white-08) transparent;
}

/* Tab panes: hidden by default, shown when active */
.settings-pane {
  display: none;
}

.settings-pane.active {
  display: block;
}

/* ---- Sections within panes ---- */
.settings-section {
  margin-bottom: 1rem;
}

.settings-section:last-child {
  margin-bottom: 0;
}

.settings-fieldset {
  border: none;
  margin: 0;
  padding: 0;
}
.settings-fieldset > legend {
  padding: 0;
}

.settings-section h3 {
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-bottom: 0.5rem;
  padding-bottom: 0.25rem;
  border-bottom: 1px solid var(--alpha-white-05);
}

/* ---- Nameplate settings groups ---- */
.settings-group {
  margin-bottom: 0.625rem;
}

.settings-group:last-child {
  margin-bottom: 0;
}

.settings-label {
  display: block;
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  margin-bottom: 0.25rem;
}

/* ---- Radio groups (segmented control) ---- */
.settings-radio-group {
  display: flex;
  gap: 2px;
  background: var(--frame-inner);
  border: 1px solid var(--frame-border);
  padding: 2px;
}

.settings-radio {
  flex: 1;
  display: flex;
  cursor: pointer;
}

.settings-radio input[type="radio"] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.settings-radio input[type="radio"]:focus-visible + span {
  outline: 2px solid var(--alpha-white-40);
  outline-offset: -2px;
}

.settings-radio span {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 0.3rem 0.4rem;
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  text-align: center;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.settings-radio input[type="radio"]:checked + span {
  background: var(--accent);
  color: var(--text-on-fill);
}

.settings-radio:hover span {
  color: var(--text-primary);
}

.settings-radio input[type="radio"]:checked + span:hover {
  color: var(--text-on-fill);
}

/* ---- Toggle checkboxes ---- */
.settings-toggle {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.settings-toggle + .settings-toggle {
  margin-top: var(--gui-margin-sm);
}

.settings-toggle label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  font-size: var(--font-size-sm);
  color: var(--text-primary);
}

.settings-toggle input[type="checkbox"] {
  width: 14px;
  height: 14px;
  accent-color: var(--accent);
  cursor: pointer;
}

/* ---- Hints ---- */
.settings-hint {
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  line-height: 1.3;
  margin-left: var(--space-6);
}

/* Standalone hint (direct child of section, not under a toggle) */
.settings-section > .settings-hint {
  margin-left: 0;
  margin-bottom: 0.5rem;
}

/* (controls-list and settings-future-note removed — controls tab is now dynamic) */

/* ---- Slider control (range input) ---- */
.settings-slider-row {
  display: flex;
  align-items: center;
  gap: var(--gui-gap);
}

.settings-slider {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 24px;
  background: transparent;
  outline: none;
}
.settings-slider::-webkit-slider-runnable-track {
  height: 4px;
  background: var(--frame-inner);
  border: 1px solid var(--frame-border);
}
.settings-slider::-moz-range-track {
  height: 4px;
  background: var(--frame-inner);
  border: 1px solid var(--frame-border);
}

.settings-slider:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.settings-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 24px;
  height: 24px;
  background: var(--accent);
  border: none;
  border-radius: 2px;
  cursor: pointer;
}

.settings-slider::-moz-range-thumb {
  width: 24px;
  height: 24px;
  background: var(--accent);
  border: none;
  border-radius: 2px;
  cursor: pointer;
}

/* P1.5 preset row: the Custom pill is display-only (it reports "your
   settings match no preset"); dim it when a real tier is active so the
   selected pill reads as the one source of truth. The Auto Detect button
   sits under the pills with normal section spacing. */
.settings-radio-ghost {
  opacity: 0.45;
}
#video-autodetect-btn {
  margin-top: var(--gui-gap);
}

.settings-slider-value {
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  font-family: var(--font-numbers);
  min-width: 2.5em;
  text-align: right;
}

/* ---- Disabled / stubbed settings ---- */
.settings-disabled {
  opacity: 0.35;
  pointer-events: none;
}

/* ---- Coming Soon badge ---- */
.settings-coming-soon {
  display: inline-block;
  font-size: var(--font-size-2xs);
  color: var(--text-muted);
  border: 1px solid var(--alpha-white-08);
  padding: 1px 6px;
  margin-left: var(--gui-gap-sm);
  vertical-align: middle;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

/* ---- Version label ---- */
.settings-version {
  margin-top: 1.5rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--alpha-white-05);
  font-size: var(--font-size-2xs);
  color: var(--text-muted);
  text-align: center;
  letter-spacing: 0.05em;
}

/* ---- Graphics / browser info ---- */
.graphics-info {
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
}

.browser-note {
  margin-bottom: 0.5rem;
  line-height: 1.4;
}

.browser-ok {
  color: var(--success);
}

.graphics-details {
  background: var(--frame-inner);
  border: var(--frame-border-width) solid var(--frame-border);
  padding: var(--gui-padding);
  margin-top: var(--gui-gap);
}

.graphics-details summary {
  cursor: pointer;
  font-weight: 500;
  color: var(--text-primary);
  font-size: var(--font-size-xs);
}

.graphics-details[open] summary {
  margin-bottom: 0.5rem;
}

.graphics-steps {
  margin: 0.5rem 0 0.5rem 1.2rem;
  padding: 0;
  font-size: var(--font-size-xs);
  line-height: 1.6;
}

.graphics-steps li {
  margin-bottom: 0.25rem;
}

.terminal-note {
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  margin: 0.5rem 0 0.25rem;
}

.terminal-cmd {
  display: block;
  background: var(--terminal-cmd-bg);
  border: var(--frame-border-width) solid var(--frame-border);
  padding: 0.4rem 0.5rem;
  font-family: var(--font-numbers);
  font-size: var(--font-size-2xs);
  color: var(--punch-cyan);
  word-break: break-all;
  user-select: all;
}

/* ---- Keybind remapping UI ---- */
.keybind-row {
  display: flex;
  align-items: center;
  gap: var(--gui-gap-sm);
  padding: 0.25rem 0;
}

.keybind-label {
  flex: 1;
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.keybind-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 3rem;
  padding: 0.2rem 0.5rem;
  background: var(--frame-inner);
  border: 1px solid var(--alpha-white-08);
  font-family: var(--font-numbers);
  font-size: var(--font-size-xs);
  color: var(--text-primary);
  text-align: center;
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease;
  white-space: nowrap;
}

.keybind-key:hover,
.keybind-key:focus-visible {
  border-color: var(--accent);
  background: var(--alpha-white-03);
}

.keybind-key-empty {
  color: var(--text-muted);
  border-style: dashed;
}

.keybind-key-locked {
  cursor: default;
  opacity: 0.5;
  pointer-events: none;
}

.keybind-listening {
  border-color: var(--accent);
  background: var(--alpha-white-05);
  animation: keybind-pulse 1s ease-in-out infinite;
  color: var(--accent);
}

@keyframes keybind-pulse {
  0%, 100% { border-color: var(--accent); }
  50% { border-color: transparent; }
}

.keybind-conflict {
  border-color: var(--warning, var(--accent));
  color: var(--warning, var(--accent));
  font-size: var(--font-size-2xs);
}

.keybind-reset {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: var(--font-size-sm);
  cursor: pointer;
  padding: 0.1rem 0.3rem;
  min-width: 24px;
  min-height: 24px;
  line-height: 1;
  transition: color 0.15s ease;
}

.keybind-reset:hover,
.keybind-reset:focus-visible {
  color: var(--text-primary);
}

.keybind-fixed .keybind-label {
  color: var(--text-muted);
}

.keybind-actions {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: var(--border-hairline);
}

.settings-btn-secondary {
  background: var(--frame-inner);
  border: 1px solid var(--alpha-white-08);
  color: var(--text-secondary);
  font-family: var(--font-display);
  font-size: var(--font-size-xs);
  padding: 0.4rem 0.8rem;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.settings-btn-secondary:hover,
.settings-btn-secondary:focus-visible {
  background: var(--alpha-white-05);
  color: var(--text-primary);
}

.settings-btn-primary {
  background: var(--accent);
  border: 1px solid var(--accent-bright);
  color: var(--text-on-fill);
  font-family: var(--font-display);
  font-size: var(--font-size-xs);
  padding: 0.4rem 0.8rem;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.settings-btn-primary:hover,
.settings-btn-primary:focus-visible {
  background: var(--accent-bright);
}

.settings-btn-primary:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* ============================================
   GUI MODAL — Reusable <dialog> modal system
   Opens non-modal via .show() to stay out of Safari's mis-handled
   top layer. The dim comes from the sibling #welcome-shroud; focus is
   trapped via trapFocus() and Escape closes via the registered gui-modal
   panel in ui.js. Class-based .modal-entering drives the enter animation.
   ============================================ */
.gui-modal {
  position: fixed;
  top: 50%;
  left: 50%;
  background: var(--bg-panel);
  border: var(--frame-border-width) solid var(--frame-border);
  padding: 0;
  max-width: 420px;
  width: 90vw;
  font-family: var(--font-display);
  pointer-events: auto;
  z-index: 200;
  box-shadow: var(--shadow-lg);
  contain: layout style paint;
  /* Closed state — above and small (behind) */
  opacity: 0;
  translate: -50% calc(-50% - 40px) 0;
  scale: 0.8;
  /* Exit transition — anticipation easing (inverse of overshoot) */
  transition:
    opacity 450ms cubic-bezier(0.36, 0, 0.66, -0.56),
    translate 450ms cubic-bezier(0.36, 0, 0.66, -0.56),
    scale 450ms cubic-bezier(0.36, 0, 0.66, -0.56),
    display 450ms allow-discrete;
}

/* Open state — fully visible and centered */
.gui-modal[open] {
  opacity: 1;
  translate: -50% -50% 0;
  scale: 1;
  /* Enter transition — overshoot easing for the arc */
  transition:
    opacity 550ms cubic-bezier(0.34, 1.56, 0.64, 1),
    translate 550ms cubic-bezier(0.34, 1.56, 0.64, 1),
    scale 550ms cubic-bezier(0.34, 1.56, 0.64, 1),
    display 550ms allow-discrete;
  transition-delay: 200ms;
}

.gui-modal[open].modal-entering {
  opacity: 0;
  translate: -50% calc(-50% - 40px) 0;
  scale: 0.8;
}

/* Welcome shroud — dims the page behind the non-modal welcome card. Replaces
   ::backdrop (which only renders for showModal). Mirrors #settings-shroud, but
   keeps the card's original darker dim and is NOT click-to-close (the welcome
   card is required, not dismissible by clicking away). */
#welcome-shroud {
  position: fixed;
  inset: 0;
  background: var(--alpha-black-70);
  z-index: 199;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity var(--fade-standard) ease-out,
    visibility var(--fade-standard) ease-out;
  transition-delay: 350ms;
}

#welcome-shroud.visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition-delay: 0ms;
}

.gui-modal-body {
  padding: calc(var(--gui-padding) * 3);
}

.gui-modal h2 {
  font-size: var(--font-size-xl);
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 0.75rem;
}

.gui-modal p {
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  line-height: 1.5;
  margin-bottom: 0.75rem;
}

.gui-modal .graphics-details {
  margin-bottom: 1rem;
}

.modal-note {
  font-size: var(--font-size-xs);
  color: var(--text-muted);
  font-style: italic;
  margin-bottom: 1rem;
}

.modal-btn {
  display: block;
  width: 100%;
  padding: var(--gui-padding);
  background: var(--punch-cyan);
  border: none;
  color: var(--text-on-fill);
  font-family: var(--font-display);
  font-size: var(--font-size-md);
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--fade-snappy) ease;
}

.modal-btn:hover,
.modal-btn:focus-visible {
  background: var(--modal-hover);
}

/* ============================================
   ONBOARDING WAYPOINT
   ============================================ */
.onboarding-waypoint {
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 150;
}

.onboarding-waypoint::after {
  content: '';
  position: absolute;
  top: -12px;
  left: -6px;
  width: 0;
  height: 0;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-bottom: 12px solid var(--accent);
  transform: rotate3d(0, 0, 1, -90deg);
  animation: waypoint-pulse 1.5s ease-in-out infinite;
}

@keyframes waypoint-pulse {
  0%, 100% { opacity: 0.6; }
  50% { opacity: 1; }
}

/* ============================================
   RESIZE OVERLAY
   ============================================ */
#resize-overlay {
  position: fixed;
  inset: 0;
  z-index: 900; /* Above everything except during active use */
  background: var(--overlay-bg-warm);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--fade-snappy) ease-out, visibility var(--fade-snappy) ease-out;
  pointer-events: none;
}

#resize-overlay.visible {
  opacity: 1;
  visibility: visible;
}

.resize-overlay-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  color: var(--text-secondary);
  font-family: var(--font-display);
}

.resize-icon {
  font-size: var(--font-size-4xl);
  animation: resize-spin 1.5s linear infinite;
}

.resize-text {
  font-size: var(--font-size-lg);
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

@keyframes resize-spin {
  from { transform: rotate3d(0, 0, 1, 0deg); }
  to { transform: rotate3d(0, 0, 1, 360deg); }
}

/* Matches #resize-overlay baseline; below #gui-layer (100) so disconnect/settings stay above */
#tab-resume-overlay {
  position: fixed;
  inset: 0;
  z-index: 99;
  background: var(--overlay-bg-warm);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--fade-snappy) ease-out, visibility var(--fade-snappy) ease-out;
  pointer-events: none;
}

#tab-resume-overlay.visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* Instant opaque show (no fade-in) — removed before fade-out so exit transition runs */
#tab-resume-overlay.tab-resume-instant {
  transition: none;
}

/* ============================================
   DISCONNECTED-WORLD VEIL (dim + still)
   ============================================ */
/* Honest-downtime plan: while the connection is down or retrying, the world
   reads as a muted, stilled snapshot instead of a live-looking scene. The
   veil dims (translucent overlay, no backdrop-filter per the render-perf
   rules); .world-stilled pauses every CSS animation under #world (paused
   animations cost no per-frame work — css-animation-performance.mdc).
   Removed on reconnect, by stopGame's finally sweep, and by the
   world-entry defensive sweep. */
#disconnect-veil {
  position: fixed;
  inset: 0;
  z-index: 98; /* under #tab-resume-overlay (99) and #gui-layer (100): HUD + toasts stay readable */
  background: oklch(0.15 0 0 / 0.55);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--fade-snappy) ease-out, visibility var(--fade-snappy) ease-out;
  pointer-events: none;
}

#disconnect-veil.visible {
  opacity: 1;
  visibility: visible;
  /* Full lock (frame-it July 26): world clicks die on the veil itself
     instead of passing through to actors/tiles. The HUD above it goes
     inert via game.js's veil-lock push; toasts live outside #gui-layer. */
  pointer-events: auto;
}

/* The veil's own always-available exit (never-stuck constraint): surfaces
   after ~5s of continuous veil via game.js. Shares the Connection Lost
   button's look (rules below); positioned low so it reads as an escape
   hatch, not the headline. */
#disconnect-veil-return {
  position: absolute;
  left: 50%;
  bottom: 22%;
  transform: translateX(-50%);
}

#disconnect-veil-return.hidden {
  display: none;
}

#world.world-stilled,
#world.world-stilled * {
  animation-play-state: paused !important;
}

/* ============================================
   DISCONNECT OVERLAY
   ============================================ */
@keyframes disconnect-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

#disconnect-overlay {
  position: fixed;
  inset: 0;
  z-index: 10000;
  pointer-events: all;
  background: oklch(0.15 0 0 / 0.85);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--gui-gap);
  animation: disconnect-fade-in var(--fade-quick) ease-out;
}

#disconnect-heading {
  font-family: var(--font-sans);
  font-size: 1.5rem;
  font-weight: 600;
  color: oklch(0.95 0 0);
  margin: 0;
}

#disconnect-status {
  font-family: var(--font-sans);
  font-size: 0.875rem;
  color: oklch(0.7 0 0);
  margin: 0;
}

#disconnect-return,
#disconnect-veil-return {
  margin-top: var(--gui-padding);
  padding: 0.5rem 1.5rem;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 500;
  color: oklch(0.95 0 0);
  background: oklch(0.3 0 0);
  border: 1px solid oklch(0.45 0 0);
  border-radius: 4px;
  cursor: pointer;
  transition: background var(--fade-snappy);
}

#disconnect-return:hover,
#disconnect-veil-return:hover {
  background: oklch(0.38 0 0);
}

#disconnect-return:disabled,
#disconnect-veil-return:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ── LOGOUT TIMER OVERLAY ─────────────────────────────────── */

#logout-overlay {
  position: fixed;
  inset: 0;
  z-index: 9998;
  pointer-events: all;
  background: oklch(0.12 0 0 / 0.7);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--gui-gap);
  animation: disconnect-fade-in var(--fade-quick) ease-out;
}

#logout-overlay h2 {
  font-family: var(--font-sans);
  font-size: 1.25rem;
  font-weight: 600;
  color: oklch(0.9 0 0);
  margin: 0;
}

#logout-countdown {
  font-family: var(--font-mono);
  font-size: 2.5rem;
  font-weight: 700;
  color: oklch(0.95 0.05 55);
  margin: 0;
}

#logout-cancel {
  margin-top: var(--gui-padding);
  padding: 0.5rem 1.5rem;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 500;
  color: oklch(0.95 0 0);
  background: oklch(0.3 0 0);
  border: 1px solid oklch(0.45 0 0);
  border-radius: 4px;
  cursor: pointer;
  transition: background var(--fade-snappy);
}

#logout-cancel:hover {
  background: oklch(0.38 0 0);
}

/* ============================================
   RESPONSIVE
   ============================================ */
@media (max-width: 900px) {
  .unit-frame {
    min-width: 160px;
  }
}

@media (max-width: 768px) {
  :root {
    --tile-size: 20px;
  }

  /* Remove panel min-width for mobile and constrain to column width */
  .gui-panel:not(.frame-wrapper),
  #quest-tracker,
  #combat-log-container,
  #inventory-panel,
  #party-frames {
    min-width: 0;
    max-width: 100%;
  }

  /* Docked panels fill column width */
  #left-panel-column > .gui-panel,
  #right-panel-column > .gui-panel {
    width: 100%;
  }

  /* Remove min-width from portrait children */
  .frame-wrapper,
  .frame-status-container,
  .unit-frame {
    min-width: 0;
  }

  /* Prevent columns from overlapping */
  #left-panel-column {
    top: 0.5rem;
    left: 0.5rem;
    bottom: 0.5rem;
    max-width: calc(50% - 1rem);
  }

  #right-panel-column {
    top: 0.5rem;
    right: 0.5rem;
    max-width: calc(50% - 1rem);
  }

  #minimap-container {
    min-width: 0;
    /* Keep square aspect ratio */
    aspect-ratio: 1 / 1;
  }

  #quest-tracker {
    max-width: 100%;
  }

  #bottom-ui {
    bottom: 0.5rem;
    width: 100%;
    padding: 0 0.5rem;
  }

  #combat-hud-dock {
    width: 100%;
  }

  #portrait-row {
    min-width: auto;
    max-width: none;
    width: 100%;
  }

  .action-slot {
    width: 40px;
    height: 40px;
  }

  #weapon-display {
    display: none;
  }

  .unit-frame {
    padding: 0.25rem;
  }

  .portrait-wrapper { display: none; }
  #dialogue-portrait { display: none; }
  
  /* Settings: collapse sidebar to horizontal tab strip on narrow viewports */
  .settings-body {
    flex-direction: column;
  }
  
  .settings-tabs {
    flex-direction: row;
    width: 100%;
    border-right: none;
    border-bottom: var(--border-hairline);
    padding: var(--gui-gap-sm) var(--gui-gap-sm);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    gap: 2px;
    position: relative;
  }

  .settings-tabs::before,
  .settings-tabs::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    width: 20px;
    pointer-events: none;
    z-index: 1;
    opacity: 0;
    transition: opacity 200ms ease;
  }

  .settings-tabs::before {
    left: 0;
    background: linear-gradient(to right, var(--bg-panel), transparent);
  }

  .settings-tabs::after {
    right: 0;
    background: linear-gradient(to left, var(--bg-panel), transparent);
  }

  .settings-tabs.can-scroll-left::before { opacity: 1; }
  .settings-tabs.can-scroll-right::after { opacity: 1; }
  
  .settings-tab {
    border-left: none;
    border-bottom: 2px solid transparent;
    padding: 0.4rem 0.6rem;
    white-space: nowrap;
    text-align: center;
  }
  
  .settings-tab.active {
    border-left-color: transparent;
    border-bottom-color: var(--accent);
  }

  #xp-bar-track {
    height: 6px;
  }

  .xp-level-box {
    width: 20px;
    min-width: 20px;
    height: 20px;
  }

  /* XP row: small gap from ability bar, flush with bottom */
  #xp-row {
    margin-top: calc(-1.5 * var(--gui-margin));
    margin-bottom: calc(-0.5 * var(--gui-margin));
  }
}

/* ============================================
   MOBILE (≤512px)
   Full-width panels and action bar
   ============================================ */
@media (max-width: 512px) {
  /* Portrait containers take 50% minus gap */
  .portrait-dock-zone {
    min-width: calc(50% - var(--gui-margin) / 2);
  }

  /* Ability/XP bar parent goes full width */
  #abilities-row {
    width: 100%;
  }

  #action-block {
    width: 100%;
    /* Pull ability bar down to sit in XP bar footprint */
    margin-bottom: calc(-1.5 * var(--gui-margin));
  }

  /* XP row: pull up to close gap with ability bar */
  #xp-row {
    margin-top: calc(-0.5 * var(--gui-margin));
    margin-bottom: calc(-0.5 * var(--gui-margin));
  }

  #action-bar,
  #action-bar-2 {
    justify-content: center;
    background: var(--alpha-black-00);
  }

  /* All panels fill their parent column */
  #left-panel-column > .gui-panel,
  #right-panel-column > .gui-panel {
    width: 100%;
  }
}

/* ============================================
   CENTER-SCREEN ANNOUNCEMENTS
   Three systems: Zone Banner, Level-Up Display, Achievement Alert
   ============================================ */

.announcement-layer {
  position: fixed;
  pointer-events: none;
  z-index: 1000;
}

/* --- Zone Banner (WoW ZoneTextFrame) --- */
#zone-banner {
  top: 18%;
  left: 50%;
  transform: translate3d(-50%, 0, 0);
  text-align: center;
  opacity: 0;
  transition: none;
}

#zone-banner.zone-banner-visible {
  animation: zone-banner-show 4s ease forwards;
}

.zone-banner-prefix {
  font-size: var(--font-size-xs);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--zone-banner-color, var(--text-primary));
  margin-bottom: 0.25rem;
}

.zone-banner-title {
  font-size: 1.6rem;
  font-weight: 700;
  color: var(--zone-banner-color, var(--text-primary));
  text-shadow: 0 2px 12px var(--alpha-black-60), 0 0 4px var(--alpha-black-40);
  letter-spacing: 0.04em;
}

.zone-banner-zone {
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  text-shadow: 0 1px 6px var(--alpha-black-60);
  margin-top: 0.15rem;
}

.zone-banner-level {
  font-size: var(--font-size-xs);
  color: var(--zone-banner-color, var(--text-muted));
  text-shadow: 0 1px 4px var(--alpha-black-60);
  margin-top: 0.1rem;
}

@keyframes zone-banner-show {
  0%   { opacity: 0; transform: translate3d(-50%, 4px, 0); }
  12%  { opacity: 1; transform: translate3d(-50%, 0, 0); }
  50%  { opacity: 1; transform: translate3d(-50%, 0, 0); }
  100% { opacity: 0; transform: translate3d(-50%, -6px, 0); }
}

/* --- Level-Up Display (WoW LevelUpDisplay) --- */
#levelup-display {
  top: 30%;
  left: 50%;
  transform: translate3d(-50%, 0, 0);
  text-align: center;
  opacity: 0;
  transition: none;
}

#levelup-display.levelup-visible {
  animation: levelup-show 4.5s ease forwards;
}

#levelup-display.levelup-ability-mode.levelup-visible {
  animation: levelup-show 3.4s ease forwards;
}

.levelup-title {
  font-size: 2rem;
  font-weight: 800;
  color: var(--xp-color);
  text-shadow: 0 2px 16px var(--alpha-black-60), 0 0 30px var(--celebration-glow);
  letter-spacing: 0.06em;
}

.levelup-ability-mode .levelup-title {
  font-size: 1.5rem;
  color: var(--success);
  text-shadow: 0 2px 12px var(--alpha-black-60);
}

.levelup-stats {
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  text-shadow: 0 1px 6px var(--alpha-black-60);
  margin-top: 0.3rem;
  letter-spacing: 0.08em;
}

@keyframes levelup-show {
  0%   { opacity: 0; transform: translate3d(-50%, 0, 0) scale3d(0.92, 0.92, 1); }
  11%  { opacity: 1; transform: translate3d(-50%, 0, 0) scale3d(1, 1, 1); }
  56%  { opacity: 1; transform: translate3d(-50%, 0, 0) scale3d(1, 1, 1); }
  100% { opacity: 0; transform: translate3d(-50%, -8px, 0) scale3d(1, 1, 1); }
}

/* --- Achievement Alert (WoW AlertFrame) --- */
#achievement-container {
  top: 12%;
  right: var(--gui-margin);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  align-items: flex-end;
}

.achievement-alert {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 1rem;
  background: var(--bg-panel);
  border: 1px solid var(--alpha-white-10);
  border-left: 3px solid var(--xp-color);
  opacity: 0;
  transform: translate3d(20px, 0, 0);
  transition: opacity 0.4s ease, transform 0.4s ease;
  box-shadow: 0 4px 16px var(--alpha-black-40);
}

.achievement-alert.achievement-visible {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

.achievement-alert.achievement-out {
  opacity: 0;
  transform: translate3d(20px, 0, 0);
  transition: opacity 1.5s ease, transform 1.5s ease;
}

.achievement-icon {
  font-size: 1.3rem;
  color: var(--xp-color);
  flex-shrink: 0;
}

.achievement-title {
  font-size: var(--font-size-sm);
  font-weight: 700;
  color: var(--xp-color);
}

.achievement-desc {
  font-size: var(--font-size-xs);
  color: var(--text-muted);
}
