/* ============================================
   LOURDIN RPG - DESIGN SYSTEM
   Centralized CSS Variables & Utilities
   ============================================ */

/* ============================================
   CANONICAL BREAKPOINTS (convention, not vars)
   CSS custom properties cannot be used inside @media queries,
   so these are a documented contract. New media queries must use
   one of these values; do not invent component-local breakpoints.

       480px   phone (small)        @media (max-width: 480px)
       768px   mobile cutoff        @media (max-width: 768px)
       960px   MudBlazor SmAndDown / MdAndUp boundary
      1024px   tablet cutoff        @media (max-width: 1024px)
      1280px   desktop -> wide      @media (min-width: 1280px)

   Known drift (599/600/900/1100/1200/1366/1399px in older files)
   should be migrated to the nearest value above when the file is
   next touched - do not mass-migrate without visual review.
   ============================================ */

/* ============================================
   CSS CUSTOM PROPERTIES (DESIGN TOKENS)
   ============================================ */

:root {
    /* ===== CLASS ABILITY PALETTE =====
       Per-class accent colors for ability activation flourishes (banner, caster pulse,
       ability-name floatie). Distinct from the damage-type palette in combat/index.js
       so a Wizard purple ability cast doesn't read identically to a Necrotic damage hit.
       Used by playClassAbility (combat/index.js) via var(--class-<key>) lookups keyed
       on ABILITY_FAMILIES[abilityId].palette. Phase B seeds Barbarian; remaining 9
       classes land in Phase E. */
    --class-barbarian: #c1272d;   /* blood red */
    --class-warrior:   #c9a36a;   /* steel-tan */
    --class-wizard:    #9333ea;   /* arcane purple (offset from Necrotic #7e22ce) */
    --class-cleric:    #eab308;   /* radiant gold (offset from Radiant #fbbf24) */
    --class-rogue:     #4a4f57;   /* shadow grey */
    --class-paladin:   #ffe066;   /* holy yellow */
    --class-bard:      #d65a8e;   /* rose pink */
    --class-pirate:    #2b6e9a;   /* sea-blue */
    --class-monk:      #ddebef;   /* moonlight white */
    --class-ranger:    #4a8b3d;   /* hunter green */

    /* ===== COLOR PALETTE ===== */

    /* Primary - Gold (Adjusted for better contrast) */
    --color-primary: #ffd700;           /* Bright gold - for accents, borders, highlights */
    --color-primary-light: #ffec8b;     /* Light gold */
    --color-primary-dark: #daa520;      /* Goldenrod - better for buttons with white text */
    --color-primary-darker: #b8860b;    /* Dark goldenrod - for button gradients */
    
    /* Background - Dark Slate */
    --color-bg-base: #0a0f1e;
    --color-bg-secondary: #0f1419;
    --color-bg-elevated: rgba(30, 41, 59, 0.95);
    --color-bg-elevated-alt: rgba(15, 23, 42, 0.98);
    
    /* Text Colors */
    --color-text-primary: #f8fafc;
    --color-text-secondary: #94a3b8;
    --color-text-tertiary: #64748b;
    --color-text-muted: #475569;
    --color-text-light: #cbd5e1;        /* Light gray text */
    --color-text-lighter: #e2e8f0;      /* Lighter gray text */
    --color-text-lightest: #f1f5f9;     /* Lightest gray text */
    --color-text-dark: #334155;         /* Dark gray text */
    --color-text-darker: #1e293b;       /* Darker gray text */

    /* Gray Scale */
    --color-gray-50: #f8fafc;
    --color-gray-100: #f1f5f9;
    --color-gray-200: #e2e8f0;
    --color-gray-300: #cbd5e1;
    --color-gray-400: #94a3b8;
    --color-gray-500: #64748b;
    --color-gray-600: #475569;
    --color-gray-700: #334155;
    --color-gray-800: #1e293b;
    --color-gray-850: #12151c;          /* Custom darker gray */
    --color-gray-900: #0f172a;
    
    /* Semantic Colors */
    --color-success: #22c55e;
    --color-success-light: #4ade80;
    --color-success-lighter: #86efac;   /* Light green tint */
    --color-success-dark: #16a34a;

    --color-danger: #ef4444;
    --color-danger-light: #f87171;
    --color-danger-lighter: #fca5a5;    /* Light red tint */
    --color-danger-lightest: #fecaca;   /* Lightest red tint */
    --color-danger-dark: #dc2626;

    --color-warning: #f59e0b;
    --color-warning-light: #fcd34d;
    --color-warning-lighter: #fde047;   /* Light yellow tint */
    --color-warning-lightest: #fde68a;  /* Lightest yellow tint */
    --color-warning-bright: #fbbf24;    /* Bright yellow */
    --color-warning-dark: #d97706;

    --color-info: #3b82f6;
    --color-info-light: #60a5fa;
    --color-info-lighter: #93c5fd;      /* Light blue tint */
    --color-info-lightest: #bfdbfe;     /* Lightest blue tint */
    --color-info-dark: #2563eb;
    
    
    /* Class Colors */
    --color-warrior: #ef4444;
    --color-wizard: #3b82f6;
    --color-ranger: #22c55e;
    --color-rogue: #6366f1;
    --color-rogue-light: #a5b4fc;       /* Light indigo tint */
    --color-rogue-lighter: #c7d2fe;     /* Lighter indigo tint */
    --color-paladin: #f59e0b;
    --color-bard: #ec4899;
    --color-bard-light: #f9a8d4;        /* Light pink tint */
    --color-bard-lighter: #fbcfe8;      /* Lighter pink tint */
    --color-cleric: #10b981;
    --color-cleric-light: #6ee7b7;      /* Light teal tint */
    --color-cleric-lighter: #a7f3d0;    /* Lighter teal tint */
    --color-monk: #8b5cf6;
    --color-monk-light: #c084fc;        /* Light purple tint */
    --color-monk-lighter: #c4b5fd;      /* Lighter purple tint */
    --color-monk-lightest: #ddd6fe;     /* Lightest purple tint */
    --color-barbarian: #dc2626;
    --color-pirate: #06b6d4;
    --color-pirate-light: #67e8f9;      /* Light cyan tint */
    --color-pirate-lighter: #a5f3fc;    /* Lighter cyan tint */
    
    /* Stat Colors */
    --color-stat-str: #ef4444;
    --color-stat-dex: #22c55e;
    --color-stat-con: #f59e0b;
    --color-stat-int: #3b82f6;
    --color-stat-wis: #8b5cf6;
    --color-stat-cha: #ec4899;
    
    /* Combat Colors */
    --color-hp: #22c55e;
    --color-hp-low: #f59e0b;
    --color-hp-critical: #ef4444;
    --color-mana: #3b82f6;
    --color-swagger: #06b6d4;
    --color-ki: #c4b5fd;                /* Monk ki: its own lavender, never the low-HP amber */
    --color-xp: #ffd700;

    /* Action economy (combat HUD): ONE color per turn slot, wherever that slot is shown:
       the dock rail, the ability-row group chips, the card tags (ACT / BNS / FREE / RXN),
       the HUD pips, the movable hexes (Move) and the Spellbook casting-action badges. Each
       slot also has a shape. Bare markers (the dock rail dots) draw the shape so the slot
       reads without color. Icon pips and text tags keep their own outline and carry the
       color, except Free, which is always hollow (its ring).
           action    circle     one per turn
           bonus     triangle
           move      square
           free      ring       hollow, never filled
           reaction  diamond    reserved until the engine has reactions
       These tokens mean a turn slot and nothing else: never paint a slot with another color,
       never use an eco token for a non-slot meaning. Resource colors are the stated
       exceptions and keep their own hues even where they sit near a slot's: the HP bars
       (green / amber / red), mana blue (outlined on cost chips, so it never reads as the
       filled BNS tag), ki lavender, and the ritual / intent-spell lavender. */
    --color-eco-action: #f5b544;
    --color-eco-bonus: #6fb1ff;
    --color-eco-move: #5fd38d;
    --color-eco-free: #d5dbe7;
    --color-eco-reaction: #f9a8d4;

    /* AoE footprint (decision 7A): the hexes and tokens an aimed area spell would strike.
       Fuchsia is neither action amber nor bonus blue (the aim-glow colors), and no zone tint,
       hazard, movement hex or intent telegraph uses it. It is not unique on the board: the
       psychic-damage VFX (combat/index.js) uses this same fuchsia, but never at the same time
       (the preview shows before the cast, the VFX after it). The crowd-control status pip
       (#c084fc) is a near hue, though only as a small icon badge, never a hex fill or token
       outline. 7.8:1 against --color-bg-base. */
    --color-aoe-footprint: #e879f9;

    /* Board target (combat HUD WP12e): the selected target's corner brackets on the ground under its standee and
       its thin card outline. Near-white, so it is no turn slot, no side (red / blue), not whose turn it is (gold)
       and not the footprint fuchsia, and it reads by lightness under the deuteranopia palette too. The brackets
       are a shape as well as a color. */
    --color-target: #f0f9ff;

    /* ===== COMBAT SEMANTIC COLORS (WP12h) =====
       Every combat surface that tells good from bad, ally from enemy or high HP from low paints with these tokens,
       never a red / green literal or the site-wide --color-success / --color-danger: the board's sides, token rims
       and status rails, the HP bars (board tokens, vitals, party frames, enemy roster, preview card, initiative
       strip), the log (rows, chips, dice breakdown), the threat overlay, the aim refusals and the canvas floaties
       (combat/index.js and boardVfx.js read them through boardVfx.combatColor). Each default is today's color
       exactly, so the palette off changes nothing.
       The red-green palette (body.cb-red-green, the "Red-green" combat setting, below) redefines EVERY token in this
       block (and --color-eco-move) to a blue / orange scheme on the Okabe-Ito hues: good, ally and heal blue; bad,
       enemy and damage vermillion, each keeping its default's lightness, so text contrast is unchanged. HP gets a
       lightness ladder instead: high light sky, mid orange, low dark vermillion (about L* 84 / 71 / 49), so the steps
       read by lightness as well as hue and the high step stays clear of the mana blue beside it. --color-target
       (near-white) and the gold turn ring sit on neither axis and keep their colors.
       A token the canvas reads (boardVfx.combatColor parses it) must be hex; the rest may alias a site token. A token
       declared here resolves on :root, so a palette entry must redeclare a token itself, never only the token it was
       made from. Color is never the only signal: the
       ground brackets, the rails' side and shape, the "+N" chips, the HP numbers and the log's words stay. */

    /* Sides: token rims, nameplates, side titles. */
    --combat-ally: #3b82f6;
    --combat-ally-light: #93c5fd;
    --combat-enemy: #ef4444;
    --combat-enemy-light: #f87171;
    --combat-enemy-lighter: #fca5a5;
    --combat-enemy-dark: #c8321e;                /* the boss frame */

    /* The ally half of the board (the enemy half is already brown, the palette's own side hue). */
    --combat-ground-ally: #1c2a1c;
    --combat-ground-ally-vanguard: #223420;
    --combat-ground-ally-flank: #1a281a;
    --combat-ground-ally-rear: #162216;
    --combat-ground-ally-current: #243a1e;       /* the zone you stand in */
    --combat-ground-ally-edge: #507846;
    --combat-ground-ally-current-edge: #78be5a;
    --combat-ground-ally-glow: #1e3c1c;
    --combat-ground-ally-wall: #1e3219;          /* an elevated hex's side wall */
    --combat-ground-ally-label: #8cbe82;         /* YOUR SIDE */

    /* Good / bad: combat's success and danger (a hit chance, a ready badge, a connection, a refusal, Downed). */
    --combat-good-dark: #16a34a;
    --combat-good: #22c55e;
    --combat-good-light: #4ade80;
    --combat-good-lighter: #86efac;
    --combat-good-deep: #059669;                 /* the action banner's emerald */
    --combat-bad-deep: #7f1d1d;
    --combat-bad-darker: #b91c1c;
    --combat-bad-dark: #dc2626;
    --combat-bad: #ef4444;
    --combat-bad-light: #f87171;
    --combat-bad-lighter: #fca5a5;
    --combat-bad-lightest: #fecaca;

    /* HP: high / mid / low, with the shades each bar's gradient uses. */
    --combat-hp-high: #22c55e;
    --combat-hp-high-light: #4ade80;
    --combat-hp-high-dark: #16a34a;
    --combat-hp-high-alt: #34d399;               /* the initiative strip */
    --combat-hp-high-alt-dark: #10b981;
    --combat-hp-mid: #f59e0b;                    /* the meters' amber */
    --combat-hp-mid-light: var(--color-warning-bright);
    --combat-hp-mid-yellow: #eab308;             /* the board tokens' and roster's yellow */
    --combat-hp-mid-yellow-light: #facc15;
    --combat-hp-mid-yellow-dark: #ca8a04;
    --combat-hp-danger: #fb923c;                 /* the roster's third step */
    --combat-hp-low: #ef4444;
    --combat-hp-low-light: #f87171;
    --combat-hp-low-dark: #dc2626;
    --combat-hp-low-darker: #b91c1c;

    /* Outcomes: floaties, hit flashes, the log. */
    --combat-damage: #ef4444;
    --combat-damage-soft: #ff6b6b;
    --combat-heal: #4ade80;
    --combat-heal-light: #34d399;
    --combat-heal-dark: #10b981;
    --combat-heal-deep: #052e16;                 /* the classic HUD's heal overlay */
    --combat-hit: #34d399;                       /* a roll that hits, the breakdown's totals */
    --combat-hit-icon: #51cf66;                  /* the log's attack and heal icons */
    --combat-miss: #f87171;
    --combat-crit: var(--color-warning-bright);
    --combat-chip-good: #0bba83;                 /* the log's chips (MudBlazor's dark success and error) */
    --combat-chip-bad: #f64e62;

    /* Threat overlay and aiming. */
    --combat-threat: #f87171;
    --combat-aim-valid: #4ade80;                 /* a hex a picker accepts */
    --combat-aim-invalid: #f87171;               /* the engine's refusal */

    /* Status kinds (WP12e): the board's status rail, its count chips, the dock's target conditions and the tablet
       target panel's pips. One hue per kind; the rail adds a shape (harmful square, beneficial round) and a place
       (harmful above the divider, beneficial below), so the kind never rests on color alone. */
    --color-status-control: #c084fc;
    --color-status-dot: var(--color-warning-bright);
    --color-status-debuff: var(--color-danger-light);
    --color-status-buff: var(--color-success-light);

    /* The HUD's generic buff pip (.se-buff, below: vitals, party frames, initiative strip, encounter splash). */
    --combat-buff-pip: #059669;
    --combat-buff-pip-dark: #047857;
    --combat-buff-pip-edge: #34d399;
    /* ===== END COMBAT SEMANTIC COLORS ===== */

    /* Zone Colors (tactical zone combat) */
    --color-zone-vanguard: #ef4444;
    --color-zone-vanguard-light: #f87171;
    --color-zone-vanguard-bg: rgba(239, 68, 68, 0.07);
    --color-zone-vanguard-border: rgba(239, 68, 68, 0.28);

    --color-zone-flank: #f59e0b;
    --color-zone-flank-light: #fbbf24;
    --color-zone-flank-bg: rgba(245, 158, 11, 0.07);
    --color-zone-flank-border: rgba(245, 158, 11, 0.28);

    --color-zone-rear: #3b82f6;
    --color-zone-rear-light: #60a5fa;
    --color-zone-rear-bg: rgba(59, 130, 246, 0.07);
    --color-zone-rear-border: rgba(59, 130, 246, 0.28);

    /* Zone side accent borders */
    --color-zone-player-accent: rgba(59, 130, 246, 0.6);
    --color-zone-enemy-accent: rgba(239, 68, 68, 0.6);
    
    /* Rarity Colors */
    --color-rarity-common: #94a3b8;
    --color-rarity-uncommon: #22c55e;
    --color-rarity-rare: #3b82f6;
    --color-rarity-epic: #a855f7;
    --color-rarity-legendary: #f59e0b;

    /* Pet Rarity Colors (full set, distinct from item rarity where needed) */
    --color-pet-common: #94a3b8;
    --color-pet-uncommon: #4ade80;
    --color-pet-rare: #60a5fa;
    --color-pet-epic: #c084fc;
    --color-pet-legendary: #fbbf24;

    /* Pet Evolution Stage Colors */
    --color-pet-stage-1: #94a3b8;
    --color-pet-stage-2: #60a5fa;
    --color-pet-stage-3: #fbbf24;

    /* Pet Card Dimensions */
    --pet-card-portrait-sm: 20px;    /* compact row in combat bar */
    --pet-card-portrait-md: 52px;    /* sidebar card (tavern) */
    --pet-card-portrait-lg: 80px;    /* detail dialog */
    --pet-card-portrait-xl: 128px;   /* detail dialog hero */

    /* Pet XP Bar */
    --pet-xp-bar-height: 4px;

    /* Metal Colors (for rankings/medals) */
    --color-gold: #ffd700;
    --color-silver: #c0c0c0;
    --color-bronze: #cd7f32;
    
    /* Borders */
    --color-border-base: rgba(255, 215, 0, 0.15);
    --color-border-hover: rgba(255, 215, 0, 0.25);
    --color-border-active: rgba(255, 215, 0, 0.4);
    --color-border-subtle: rgba(255, 255, 255, 0.08);
    
    /* ===== SPACING SCALE ===== */
    --space-xs: 0.25rem;    /* 4px */
    --space-sm: 0.5rem;     /* 8px */
    --space-md: 0.75rem;    /* 12px */
    --space-lg: 1rem;       /* 16px */
    --space-xl: 1.5rem;     /* 24px */
    --space-2xl: 2rem;      /* 32px */
    --space-3xl: 3rem;      /* 48px */
    --space-4xl: 4rem;      /* 64px */
    
    /* ===== FONT SCALES ===== */
    --font-size-xs: 0.65rem;    /* 10.4px */
    --font-size-sm: 0.75rem;    /* 12px */
    --font-size-base: 0.875rem; /* 14px */
    --font-size-md: 1rem;       /* 16px */
    --font-size-lg: 1.125rem;   /* 18px */
    --font-size-xl: 1.25rem;    /* 20px */
    --font-size-2xl: 1.5rem;    /* 24px */
    --font-size-3xl: 2rem;      /* 32px */
    --font-size-4xl: 3rem;      /* 48px */
    --font-size-5xl: 4rem;      /* 64px */

    /* ===== COMBAT TEXT SIZE (WP12g) =====
       The combat reading surfaces (the log, the hover zooms and every tooltip opened in combat, the preview card,
       the aim banner, the monster card, the hotkey help, the settings drawer, the recent events panel) size every
       font with these tokens: a base size (the px value at Medium) times --combat-font-scale, the player's text size
       (Small 0.9, Medium 1, Large 1.2, Extra large 1.4; CombatFontScale.Multiplier). The combat page writes the scale
       inline on the document root, with the root class combat-text (the tooltips' hook, components-combat.css), and
       removes both on leaving, so the tokens resolve here, on :root: tooltips and popovers render in MudBlazor's
       popover provider, outside .combat-page, and still scale. Never redeclare the scale on a descendant: a token
       declared here has already resolved against the root value. The board's canvas damage numbers and floaties read
       the same inline value (boardVfx.js labelTextScale).
       Out of scope, keeping their authored size: hotbar slot faces, board token badges and nameplates, vitals bars,
       initiative portraits (their names live in the hover zoom). Use em for a box that should grow with the text,
       and --ct-box for a width authored in px that should widen (never narrow) with it. */
    --combat-font-scale: 1;
    --ct-3xs: calc(0.5625rem * var(--combat-font-scale, 1));  /* 9px at Medium */
    --ct-2xs: calc(0.65rem * var(--combat-font-scale, 1));    /* 10.4px (--font-size-xs) */
    --ct-xs: calc(0.6875rem * var(--combat-font-scale, 1));   /* 11px */
    --ct-sm: calc(0.75rem * var(--combat-font-scale, 1));     /* 12px (--font-size-sm) */
    --ct-md: calc(0.8125rem * var(--combat-font-scale, 1));   /* 13px */
    --ct-base: calc(0.875rem * var(--combat-font-scale, 1));  /* 14px (--font-size-base) */
    --ct-lg: calc(1rem * var(--combat-font-scale, 1));        /* 16px (--font-size-md) */
    --ct-xl: calc(1.125rem * var(--combat-font-scale, 1));    /* 18px (--font-size-lg) */
    --ct-2xl: calc(1.25rem * var(--combat-font-scale, 1));    /* 20px (--font-size-xl) */
    --ct-3xl: calc(1.5rem * var(--combat-font-scale, 1));     /* 24px (--font-size-2xl) */
    --ct-display: calc(clamp(1.5rem, 4vw, 2.4rem) * var(--combat-font-scale, 1)); /* the monster card's name */
    --ct-box: max(1, var(--combat-font-scale, 1));            /* width growth: 1 at Small and Medium */

    /* ===== FONT FAMILIES ===== */
    --font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --font-display: 'Cinzel', serif;
    --font-mono: 'Courier New', monospace;
    
    /* ===== FONT WEIGHTS ===== */
    --font-weight-normal: 400;
    --font-weight-medium: 500;
    --font-weight-semibold: 600;
    --font-weight-bold: 700;
    
    /* ===== BORDER RADIUS ===== */
    --radius-sm: 4px;
    --radius-md: 6px;
    --radius-lg: 8px;
    --radius-xl: 10px;
    --radius-2xl: 12px;
    --radius-3xl: 16px;
    --radius-round: 50%;
    
    /* ===== SHADOWS ===== */
    --shadow-sm: 0 2px 6px rgba(0, 0, 0, 0.2);
    --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.3);
    --shadow-lg: 0 8px 20px rgba(0, 0, 0, 0.4);
    --shadow-xl: 0 12px 30px rgba(0, 0, 0, 0.5);
    --shadow-gold: 0 0 20px rgba(255, 215, 0, 0.3);
    --shadow-gold-lg: 0 0 40px rgba(255, 215, 0, 0.4);
    
    /* ===== TRANSITIONS ===== */
    --transition-fast: 0.15s ease;
    --transition-base: 0.2s ease;
    --transition-slow: 0.3s ease;
    
    /* ===== Z-INDEX SCALE ===== */
    --z-base: 1;
    --z-elevated: 10;
    --z-dropdown: 100;
    --z-sticky: 200;
    --z-fixed: 300;
    --z-modal-backdrop: 400;
    --z-modal: 500;
    --z-popover: 600;
    --z-tooltip: 700;
    
    /* ===== PAGE LAYOUT ===== */
    /* Content width tokens - use for page containers */
    --page-max-width-sm: 800px;      /* Narrow content: forms, articles */
    --page-max-width-md: 1200px;     /* Medium content: most pages */
    --page-max-width-lg: 1400px;     /* Wide content: dashboards */
    --page-max-width-xl: 1600px;     /* Extra wide: grids, galleries */
    --page-max-width-full: 100%;     /* Full width (respects parent) */
    
    /* Standard page padding */
    --page-padding-x: clamp(1rem, 4vw, 2rem);
    --page-padding-y: var(--space-xl);
    
    /* Container gutter (space between edge and content on small screens) */
    --page-gutter: var(--space-lg);
}

/* ============================================
   PAGE CONTAINER UTILITIES
   ============================================ */

/* Base page container - centers content with max-width */
.page-container {
    width: 100%;
    max-width: var(--page-max-width-lg);
    margin-left: auto;
    margin-right: auto;
    padding-left: var(--page-gutter);
    padding-right: var(--page-gutter);
    box-sizing: border-box;
}

/* Size variants */
.page-container-sm { max-width: var(--page-max-width-sm); }
.page-container-md { max-width: var(--page-max-width-md); }
.page-container-lg { max-width: var(--page-max-width-lg); }
.page-container-xl { max-width: var(--page-max-width-xl); }
.page-container-full { max-width: var(--page-max-width-full); }

/* ============================================
   TYPOGRAPHY UTILITIES
   ============================================ */

.text-display {
    font-family: var(--font-display);
    letter-spacing: 0.05em;
}

.text-mono {
    font-family: var(--font-mono);
}

.text-primary { color: var(--color-text-primary); }
.text-secondary { color: var(--color-text-secondary); }
.text-tertiary { color: var(--color-text-tertiary); }
.text-muted { color: var(--color-text-muted); }

.text-gold { color: var(--color-primary); }
.text-success { color: var(--color-success-light); }
.text-danger { color: var(--color-danger-light); }
.text-warning { color: var(--color-warning-light); }
.text-info { color: var(--color-info-light); }

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

.font-normal { font-weight: var(--font-weight-normal); }
.font-medium { font-weight: var(--font-weight-medium); }
.font-semibold { font-weight: var(--font-weight-semibold); }
.font-bold { font-weight: var(--font-weight-bold); }

.uppercase { text-transform: uppercase; }
.letter-spacing { letter-spacing: 0.05em; }

/* ============================================
   CARD SYSTEM (BASE STYLES)
   ============================================ */

.card {
    background: linear-gradient(145deg, var(--color-bg-elevated), var(--color-bg-elevated-alt));
    border: 1px solid var(--color-border-base);
    border-radius: var(--radius-2xl);
    padding: var(--space-xl);
    transition: all var(--transition-slow);
}

.card-sm {
    padding: var(--space-lg);
}

.card-lg {
    padding: var(--space-2xl);
}

.card-hover:hover {
    transform: translateY(-4px);
    border-color: var(--color-border-hover);
    box-shadow: var(--shadow-lg), var(--shadow-gold);
}

.card-interactive {
    cursor: pointer;
    transition: all var(--transition-slow);
}

.card-interactive:hover {
    transform: translateY(-2px);
    border-color: var(--color-border-hover);
}

.card-interactive:active {
    transform: translateY(0);
}

/* Card Header */
.card-header {
    background: rgba(255, 215, 0, 0.15);
    padding: var(--space-lg) var(--space-xl);
    border-bottom: 1px solid var(--color-border-base);
    border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
    margin: calc(var(--space-xl) * -1) calc(var(--space-xl) * -1) var(--space-xl);
}

.card-title {
    font-family: var(--font-display);
    font-size: var(--font-size-xl);
    font-weight: var(--font-weight-semibold);
    color: var(--color-primary);
    margin: 0;
}

.card-subtitle {
    font-size: var(--font-size-sm);
    color: var(--color-text-secondary);
    margin: var(--space-xs) 0 0;
}

/* ============================================
   BUTTON SYSTEM
   ============================================ */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-sm);
    padding: var(--space-md) var(--space-xl);
    font-family: var(--font-display);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    border: none;
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: all var(--transition-base);
    white-space: nowrap;
}

.btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none !important;
}

/* Button Sizes */
.btn-sm {
    padding: var(--space-sm) var(--space-md);
    font-size: var(--font-size-sm);
}

.btn-lg {
    padding: var(--space-lg) var(--space-2xl);
    font-size: var(--font-size-lg);
}

/* Button Variants */
.btn-primary {
    background: linear-gradient(135deg, var(--color-primary-dark), #b8860b);
    color: #ffffff;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}

.btn-primary:hover:not(:disabled) {
    transform: translateY(-2px);
    box-shadow: var(--shadow-md), var(--shadow-gold);
    background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark));
}

.btn-success {
    background: linear-gradient(135deg, var(--color-success), var(--color-success-dark));
    color: white;
}

.btn-success:hover:not(:disabled) {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(34, 197, 94, 0.4);
}

.btn-danger {
    background: linear-gradient(135deg, var(--color-danger), var(--color-danger-dark));
    color: white;
}

.btn-danger:hover:not(:disabled) {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
}

.btn-warning {
    background: linear-gradient(135deg, var(--color-warning), var(--color-warning-dark));
    color: white;
}

.btn-warning:hover:not(:disabled) {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4);
}

.btn-info {
    background: linear-gradient(135deg, var(--color-info), var(--color-info-dark));
    color: white;
}

.btn-info:hover:not(:disabled) {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}

.btn-outline {
    background: rgba(255, 215, 0, 0.05);
    color: var(--color-primary);
    border: 2px solid rgba(255, 215, 0, 0.4);
}

.btn-outline:hover:not(:disabled) {
    border-color: rgba(255, 215, 0, 0.8);
    background: rgba(255, 215, 0, 0.1);
    transform: translateY(-2px);
}

.btn-ghost {
    background: transparent;
    color: var(--color-text-secondary);
}

.btn-ghost:hover:not(:disabled) {
    background: rgba(255, 255, 255, 0.05);
    color: var(--color-primary);
}

/* Chip modifier — compact, understated action for dense in-game UI (sheet/combat headers,
   toolbars, inline controls). The full .btn is deliberately loud (uppercase Cinzel, fat
   padding); this trims it to a sleeker control: tighter padding, smaller sentence-case text,
   thinner outline, smaller icon. Compose with a variant, e.g. `btn btn-chip btn-outline`. */
.btn-chip {
    gap: var(--space-xs);
    padding: var(--space-xs) var(--space-sm);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    letter-spacing: 0.02em;
    text-transform: none;
    border-radius: var(--radius-sm);
}

.btn-chip.btn-outline {
    border-width: 1px;
}

.btn-chip .mud-icon-root {
    font-size: 1rem;
}

/* ============================================
   BADGE SYSTEM
   ============================================ */

.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    white-space: nowrap;
}

/* Badge Variants */
.badge-primary {
    background: rgba(255, 215, 0, 0.2);
    color: var(--color-primary);
    border: 1px solid rgba(255, 215, 0, 0.3);
}

.badge-success {
    background: rgba(34, 197, 94, 0.2);
    color: var(--color-success-light);
    border: 1px solid rgba(34, 197, 94, 0.3);
}

.badge-danger {
    background: rgba(239, 68, 68, 0.2);
    color: var(--color-danger-light);
    border: 1px solid rgba(239, 68, 68, 0.3);
}

.badge-warning {
    background: rgba(245, 158, 11, 0.2);
    color: var(--color-warning-light);
    border: 1px solid rgba(245, 158, 11, 0.3);
}

.badge-info {
    background: rgba(59, 130, 246, 0.2);
    color: var(--color-info-light);
    border: 1px solid rgba(59, 130, 246, 0.3);
}

.badge-purple {
    background: rgba(139, 92, 246, 0.2);
    color: #c4b5fd;
    border: 1px solid rgba(139, 92, 246, 0.3);
}

.badge-pink {
    background: rgba(236, 72, 153, 0.2);
    color: #f9a8d4;
    border: 1px solid rgba(236, 72, 153, 0.3);
}

.badge-teal {
    background: rgba(20, 184, 166, 0.2);
    color: #5eead4;
    border: 1px solid rgba(20, 184, 166, 0.3);
}

/* ============================================
   STAT BAR SYSTEM
   ============================================ */

.stat-bar-container {
    margin-bottom: var(--space-md);
}

.stat-bar-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--space-xs);
}

.stat-bar-label {
    font-size: var(--font-size-sm);
    color: var(--color-text-secondary);
    font-weight: var(--font-weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.stat-bar-value {
    font-size: var(--font-size-sm);
    color: var(--color-text-primary);
    font-weight: var(--font-weight-semibold);
}

.stat-bar {
    height: 24px;
    background: rgba(0, 0, 0, 0.3);
    border-radius: var(--radius-sm);
    overflow: hidden;
    position: relative;
}

.stat-bar-fill {
    height: 100%;
    border-radius: var(--radius-sm);
    transition: width 0.5s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: white;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

/* Stat Fill Colors */
.stat-bar-fill.hp {
    background: linear-gradient(90deg, var(--color-hp), var(--color-success-light));
}

.stat-bar-fill.hp.low {
    background: linear-gradient(90deg, var(--color-hp-low), var(--color-warning-light));
}

.stat-bar-fill.hp.critical {
    background: linear-gradient(90deg, var(--color-hp-critical), var(--color-danger-light));
}

.stat-bar-fill.mana {
    background: linear-gradient(90deg, var(--color-mana), var(--color-info-light));
}

.stat-bar-fill.xp {
    background: linear-gradient(90deg, var(--color-xp), var(--color-primary-light));
}

.stat-bar-fill.swagger {
    background: linear-gradient(90deg, var(--color-swagger), #22d3ee);
}

.stat-bar-fill.ki {
    background: linear-gradient(90deg, var(--color-monk), var(--color-ki));
}

/* Compact horizontal layout (label | bar | value inline) — mobile cards, combat HUD.
   Used by the ResourceBar primitive when Size = Compact/Micro. */
.stat-bar-row {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin-bottom: var(--space-xs);
}

.stat-bar-row .stat-bar {
    flex: 1 1 auto;
}

.stat-bar-row .stat-bar-label {
    flex: 0 0 auto;
    min-width: 2.25rem;
}

.stat-bar-row .stat-bar-value {
    flex: 0 0 auto;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* Bar height modifiers */
.stat-bar--compact { height: 14px; }
.stat-bar--micro   { height: 8px; }

/* Slim hero treatment (tavern strip): 8px bar + per-variant glow + Cinzel value.
   Applied to the wrapper so it can reach both the header value and the bar/fill. */
.stat-bar-container--slim .stat-bar,
.stat-bar-row--slim .stat-bar {
    height: 8px;
}

.stat-bar-container--slim .stat-bar-value {
    font-family: var(--font-display);
}

.stat-bar-container--slim .stat-bar-fill.hp,
.stat-bar-row--slim .stat-bar-fill.hp {
    box-shadow: 0 0 8px rgba(34, 197, 94, 0.4);
}

.stat-bar-container--slim .stat-bar-fill.mana,
.stat-bar-row--slim .stat-bar-fill.mana {
    box-shadow: 0 0 8px rgba(59, 130, 246, 0.4);
}

.stat-bar-container--slim .stat-bar-fill.xp,
.stat-bar-row--slim .stat-bar-fill.xp {
    box-shadow: 0 0 8px var(--color-border-active);
}

/* Predicted incoming-damage overlay (HP only) — sits at the leading edge of current health */
.stat-bar-incoming {
    position: absolute;
    top: 0;
    height: 100%;
    background: repeating-linear-gradient(
        45deg,
        rgba(220, 38, 38, 0.6) 0,
        rgba(220, 38, 38, 0.6) 5px,
        rgba(127, 29, 29, 0.45) 5px,
        rgba(127, 29, 29, 0.45) 10px);
    pointer-events: none;
}

/* Resource-cost tick marks (e.g. prepared-spell mana costs) */
.stat-bar-tick {
    position: absolute;
    top: 0;
    height: 100%;
    width: 2px;
    margin-left: -1px;
    background: rgba(255, 255, 255, 0.55);
    pointer-events: none;
}

/* ============================================
   CHARACTER PORTRAIT
   Canonical avatar primitive (CharacterPortrait.razor). Size driven by the inline
   --portrait-size custom property; fallback gradient supplied inline by
   CharacterDisplayHelpers.GetCharacterAvatarStyle.
   ============================================ */

.character-portrait {
    position: relative;
    display: block;
    flex-shrink: 0;
    width: var(--portrait-size, 128px);
    height: var(--portrait-size, 128px);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.character-portrait.clickable {
    cursor: pointer;
}

.character-portrait-fallback {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
}

.character-portrait-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.character-portrait-edit {
    position: absolute;
    right: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-xs);
    color: white;
    background: rgba(0, 0, 0, 0.6);
    border-top-left-radius: var(--radius-md);
}

/* ============================================
   COMBAT STAT BLOCK
   Canonical AC / Initiative / Attack / Best Save tiles (CombatStatBlock.razor).
   One responsive auto-fit grid replaces the row + card-grid duplicates.
   .stat-icon colors are scoped under .combat-stat-block to avoid colliding with
   other .stat-icon usages elsewhere.
   ============================================ */

.combat-stat-block {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: var(--space-md);
}

.combat-stat-tile {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-md);
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border-base);
    border-radius: var(--radius-lg);
}

.combat-stat-block .stat-icon {
    font-size: 1.75rem;
    flex-shrink: 0;
}

.combat-stat-block .stat-icon.ac     { color: var(--color-info); }
.combat-stat-block .stat-icon.init   { color: var(--color-success); }
.combat-stat-block .stat-icon.attack { color: var(--color-danger); }
.combat-stat-block .stat-icon.saves  { color: var(--color-monk); }

.combat-stat-info {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.combat-stat-value {
    font-size: var(--font-size-xl);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text-primary);
    line-height: 1.1;
}

.combat-stat-label {
    font-size: var(--font-size-xs);
    color: var(--color-text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.combat-stat-ac-line {
    display: flex;
    justify-content: space-between;
    gap: var(--space-lg);
}

.combat-stat-ac-line.total {
    margin-top: var(--space-xs);
    padding-top: var(--space-xs);
    border-top: 1px solid var(--color-border-base);
    font-weight: var(--font-weight-semibold);
}

/* ============================================
   CLASS COLOR UTILITIES (A5)
   The single consumption layer for the existing --color-{class} UI tokens (NOT the
   --class-{class} ability-flourish VFX palette, which stays the concern of combat/index.js).
   Pair a role class with the GetClassCssName() output ("warrior", "wizard", ...):
       class="class-text @CharacterHelpers.GetClassCssName(cls)"    -> class-tinted text
       class="class-avatar @CharacterHelpers.GetClassCssName(cls)"  -> class avatar gradient + border
   --class-accent is exposed for component borders/glows: e.g. border-color: var(--class-accent).
   One setter rule per class feeds the shared --class-color indirection; the three role
   classes consume it, so a palette change touches only --color-{class}.
   ============================================ */

.class-text.warrior,   .class-accent.warrior,   .class-avatar.warrior   { --class-color: var(--color-warrior); }
.class-text.wizard,    .class-accent.wizard,    .class-avatar.wizard    { --class-color: var(--color-wizard); }
.class-text.ranger,    .class-accent.ranger,    .class-avatar.ranger    { --class-color: var(--color-ranger); }
.class-text.rogue,     .class-accent.rogue,     .class-avatar.rogue     { --class-color: var(--color-rogue); }
.class-text.paladin,   .class-accent.paladin,   .class-avatar.paladin   { --class-color: var(--color-paladin); }
.class-text.bard,      .class-accent.bard,      .class-avatar.bard      { --class-color: var(--color-bard); }
.class-text.cleric,    .class-accent.cleric,    .class-avatar.cleric    { --class-color: var(--color-cleric); }
.class-text.monk,      .class-accent.monk,      .class-avatar.monk      { --class-color: var(--color-monk); }
.class-text.barbarian, .class-accent.barbarian, .class-avatar.barbarian { --class-color: var(--color-barbarian); }
.class-text.pirate,    .class-accent.pirate,    .class-avatar.pirate    { --class-color: var(--color-pirate); }

.class-text {
    color: var(--class-color, var(--color-text-primary));
}

.class-accent {
    --class-accent: var(--class-color, var(--color-primary));
}

.class-avatar {
    background: linear-gradient(135deg,
        color-mix(in srgb, var(--class-color, var(--color-primary)) 24%, transparent),
        color-mix(in srgb, var(--class-color, var(--color-primary)) 6%, transparent));
    border: 1px solid color-mix(in srgb, var(--class-color, var(--color-primary)) 35%, transparent);
}

/* ============================================
   STATUS EFFECT CATEGORY COLORS (se-*)
   Applied to .status-effect-badge, .sticky-status-badge, .mobile-status-badge.
   Base sizing stays in component CSS; these classes provide color/border/shadow.
   ============================================ */

.se-dot-fire   { background: linear-gradient(135deg, #ea580c, #c2410c); border-color: #fb923c; box-shadow: 0 2px 6px rgba(234,88,12,0.4); }
.se-dot-poison { background: linear-gradient(135deg, #16a34a, #14532d); border-color: #4ade80; box-shadow: 0 2px 6px rgba(22,163,74,0.4); }
.se-dot-bleed  { background: linear-gradient(135deg, #dc2626, #7f1d1d); border-color: #fca5a5; box-shadow: 0 2px 6px rgba(220,38,38,0.4); }
.se-cc-stun    { background: linear-gradient(135deg, #7c3aed, #5b21b6); border-color: #a78bfa; box-shadow: 0 2px 6px rgba(124,58,237,0.4); }
.se-cc-freeze  { background: linear-gradient(135deg, #0284c7, #075985); border-color: #7dd3fc; box-shadow: 0 2px 6px rgba(2,132,199,0.4); }
.se-cc-sleep   { background: linear-gradient(135deg, #6d28d9, #4c1d95); border-color: #c4b5fd; box-shadow: 0 2px 6px rgba(109,40,217,0.3); }
.se-cc-charm   { background: linear-gradient(135deg, #db2777, #9d174d); border-color: #f9a8d4; box-shadow: 0 2px 6px rgba(219,39,119,0.4); }
.se-debuff     { background: linear-gradient(135deg, #b45309, #92400e); border-color: #fcd34d; box-shadow: 0 2px 6px rgba(180,83,9,0.3); }
.se-buff       { background: linear-gradient(135deg, var(--combat-buff-pip), var(--combat-buff-pip-dark)); border-color: var(--combat-buff-pip-edge); box-shadow: 0 2px 6px color-mix(in srgb, var(--combat-buff-pip) 40%, transparent); }
.se-more       { background: rgba(107, 114, 128, 0.4); border-color: #9ca3af; }

.se-dot-fire .mud-icon-root,
.se-dot-poison .mud-icon-root,
.se-dot-bleed .mud-icon-root,
.se-cc-stun .mud-icon-root,
.se-cc-freeze .mud-icon-root,
.se-cc-sleep .mud-icon-root,
.se-cc-charm .mud-icon-root,
.se-debuff .mud-icon-root,
.se-buff .mud-icon-root { color: white; }

/* ============================================
   SECTION HEADER SYSTEM
   ============================================ */

.section-header {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding-bottom: var(--space-md);
    border-bottom: 1px solid var(--color-border-base);
    margin-bottom: var(--space-xl);
}

.section-icon {
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-lg);
    color: var(--color-primary);
}

.section-title {
    font-family: var(--font-display);
    font-size: var(--font-size-xl);
    font-weight: var(--font-weight-semibold);
    color: var(--color-primary);
    flex: 1;
    margin: 0;
}

.section-subtitle {
    font-size: var(--font-size-base);
    color: var(--color-text-secondary);
}

.section-count {
    font-size: var(--font-size-base);
    color: var(--color-text-tertiary);
    background: rgba(0, 0, 0, 0.3);
    padding: var(--space-xs) var(--space-md);
    border-radius: 12px;
}

/* ============================================
   GRID SYSTEMS
   ============================================ */

.grid-2 {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-lg);
}

.grid-3 {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-lg);
}

.grid-4 {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-lg);
}

.grid-auto-fill {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: var(--space-lg);
}

.grid-auto-fit {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: var(--space-lg);
}

/* Responsive Grid Breakpoints */
@media (max-width: 768px) {
    .grid-2,
    .grid-3,
    .grid-4 {
        grid-template-columns: 1fr;
    }
}

@media (min-width: 769px) and (max-width: 1024px) {
    .grid-3,
    .grid-4 {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* ============================================
   SPACING UTILITIES
   ============================================ */

.m-0 { margin: 0; }
.mt-0 { margin-top: 0; }
.mr-0 { margin-right: 0; }
.mb-0 { margin-bottom: 0; }
.ml-0 { margin-left: 0; }

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

.mt-xs { margin-top: var(--space-xs); }
.mt-sm { margin-top: var(--space-sm); }
.mt-md { margin-top: var(--space-md); }
.mt-lg { margin-top: var(--space-lg); }
.mt-xl { margin-top: var(--space-xl); }

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

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

/* ============================================
   FLEX UTILITIES
   ============================================ */

.flex { display: flex; }
.flex-col { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.items-start { align-items: flex-start; }
.items-end { align-items: flex-end; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.justify-end { justify-content: flex-end; }
.gap-xs { gap: var(--space-xs); }
.gap-sm { gap: var(--space-sm); }
.gap-md { gap: var(--space-md); }
.gap-lg { gap: var(--space-lg); }
.gap-xl { gap: var(--space-xl); }

/* ============================================
   ANIMATIONS (SHARED)
   ============================================ */

@keyframes float {
    0%, 100% {
        transform: translateY(0);
    }
    50% {
        transform: translateY(-10px);
    }
}

@keyframes pulse {
    0%, 100% {
        opacity: 1;
    }
    50% {
        opacity: 0.7;
    }
}

@keyframes glow-pulse {
    0%, 100% {
        box-shadow: 0 0 10px rgba(255, 215, 0, 0.3);
    }
    50% {
        box-shadow: 0 0 30px rgba(255, 215, 0, 0.6);
    }
}

@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(20px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes slideIn {
    from {
        opacity: 0;
        transform: translateX(-20px);
    }
    to {
        opacity: 1;
        transform: translateX(0);
    }
}

@keyframes shake {
    0%, 100% { transform: translateX(0); }
    10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
    20%, 40%, 60%, 80% { transform: translateX(4px); }
}

/* Animation Utility Classes */
.animate-float { animation: float 3s ease-in-out infinite; }
.animate-pulse { animation: pulse 2s ease-in-out infinite; }
.animate-glow { animation: glow-pulse 2s ease-in-out infinite; }
.animate-fadeIn { animation: fadeIn 0.6s ease-out forwards; }
.animate-slideIn { animation: slideIn 0.4s ease-out forwards; }
.animate-shake { animation: shake 0.4s ease-out; }

/* Animation Delays */
.delay-100 { animation-delay: 0.1s; }
.delay-200 { animation-delay: 0.2s; }
.delay-300 { animation-delay: 0.3s; }
.delay-400 { animation-delay: 0.4s; }
.delay-500 { animation-delay: 0.5s; }

/* ============================================
   UTILITY CLASSES
   ============================================ */

.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }

.cursor-pointer { cursor: pointer; }
.cursor-not-allowed { cursor: not-allowed; }

.hidden { display: none; }
.opacity-50 { opacity: 0.5; }
.opacity-75 { opacity: 0.75; }

.w-full { width: 100%; }
.h-full { height: 100%; }

/* ============================================
   ACCESSIBILITY
   ============================================ */

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* Focus Visible Styles */
*:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}

button:focus-visible,
a:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}

/* ============================================
   SPELL SCHOOLS
   Used by SpellCard art-zone tints and badges.
   ============================================ */

.spell-school-abjuration {
    background: linear-gradient(135deg, rgba(59, 130, 246, 0.14) 0%, rgba(37, 99, 235, 0.08) 100%) !important;
    border-color: rgba(59, 130, 246, 0.35) !important;
}

    .spell-school-abjuration .mud-icon-root {
        filter: drop-shadow(0 2px 8px rgba(59, 130, 246, 0.45)) !important;
        color: #60a5fa !important;
    }

.spell-school-conjuration {
    background: linear-gradient(135deg, rgba(20, 184, 166, 0.14) 0%, rgba(13, 148, 136, 0.08) 100%) !important;
    border-color: rgba(20, 184, 166, 0.35) !important;
}

    .spell-school-conjuration .mud-icon-root {
        filter: drop-shadow(0 2px 8px rgba(20, 184, 166, 0.45)) !important;
        color: #2dd4bf !important;
    }

.spell-school-divination {
    background: linear-gradient(135deg, rgba(99, 102, 241, 0.14) 0%, rgba(79, 70, 229, 0.08) 100%) !important;
    border-color: rgba(99, 102, 241, 0.35) !important;
}

    .spell-school-divination .mud-icon-root {
        filter: drop-shadow(0 2px 8px rgba(99, 102, 241, 0.45)) !important;
        color: #a5b4fc !important;
    }

.spell-school-enchantment {
    background: linear-gradient(135deg, rgba(236, 72, 153, 0.14) 0%, rgba(219, 39, 119, 0.08) 100%) !important;
    border-color: rgba(236, 72, 153, 0.35) !important;
}

    .spell-school-enchantment .mud-icon-root {
        filter: drop-shadow(0 2px 8px rgba(236, 72, 153, 0.45)) !important;
        color: #f9a8d4 !important;
    }

.spell-school-evocation {
    background: linear-gradient(135deg, rgba(249, 115, 22, 0.14) 0%, rgba(234, 88, 12, 0.08) 100%) !important;
    border-color: rgba(249, 115, 22, 0.35) !important;
}

    .spell-school-evocation .mud-icon-root {
        filter: drop-shadow(0 2px 8px rgba(249, 115, 22, 0.45)) !important;
        color: #fb923c !important;
    }

.spell-school-illusion {
    background: linear-gradient(135deg, rgba(168, 85, 247, 0.14) 0%, rgba(147, 51, 234, 0.08) 100%) !important;
    border-color: rgba(168, 85, 247, 0.35) !important;
}

    .spell-school-illusion .mud-icon-root {
        filter: drop-shadow(0 2px 8px rgba(168, 85, 247, 0.45)) !important;
        color: #c084fc !important;
    }

.spell-school-necromancy {
    background: linear-gradient(135deg, rgba(22, 163, 74, 0.1) 0%, rgba(21, 128, 61, 0.06) 100%) !important;
    border-color: rgba(22, 163, 74, 0.3) !important;
}

    .spell-school-necromancy .mud-icon-root {
        filter: drop-shadow(0 2px 8px rgba(22, 163, 74, 0.35)) !important;
        color: #4ade80 !important;
        mix-blend-mode: lighten;
    }

.spell-school-transmutation {
    background: linear-gradient(135deg, rgba(234, 179, 8, 0.14) 0%, rgba(202, 138, 4, 0.08) 100%) !important;
    border-color: rgba(234, 179, 8, 0.35) !important;
}

    .spell-school-transmutation .mud-icon-root {
        filter: drop-shadow(0 2px 8px rgba(234, 179, 8, 0.45)) !important;
        color: #fcd34d !important;
    }

/* ============================================
   DAMAGE TYPES
   Used by SpellCard border glow and dmgtype badge.
   ============================================ */

.damage-type-fire        { color: #fb923c; border-color: rgba(249, 115, 22, 0.45); }
.damage-type-cold        { color: #93c5fd; border-color: rgba(147, 197, 253, 0.45); }
.damage-type-lightning   { color: #facc15; border-color: rgba(250, 204, 21, 0.45); }
.damage-type-acid        { color: #a3e635; border-color: rgba(163, 230, 53, 0.45); }
.damage-type-poison      { color: #4ade80; border-color: rgba(74, 222, 128, 0.4); }
.damage-type-radiant     { color: #fde68a; border-color: rgba(253, 230, 138, 0.45); }
.damage-type-necrotic    { color: #86efac; border-color: rgba(134, 239, 172, 0.35); }
.damage-type-psychic     { color: #f0abfc; border-color: rgba(240, 171, 252, 0.45); }
.damage-type-force       { color: #a5b4fc; border-color: rgba(165, 180, 252, 0.45); }
.damage-type-thunder     { color: #bfdbfe; border-color: rgba(191, 219, 254, 0.4); }
.damage-type-slashing    { color: var(--color-danger-light); border-color: rgba(248, 113, 113, 0.35); }
.damage-type-piercing    { color: var(--color-danger-lighter); border-color: rgba(252, 165, 165, 0.35); }
.damage-type-bludgeoning { color: var(--color-warning-light); border-color: rgba(252, 211, 77, 0.3); }
.damage-type-physical    { color: var(--color-text-tertiary); border-color: rgba(255, 255, 255, 0.08); }

/* ============================================
   SPELL BADGE VARIANTS
   Concentration, ritual and casting-action badges on SpellCard
   and the Spellbook detail drawer.
   ============================================ */

.spell-badge-concentration {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 2px 7px;
    border-radius: var(--radius-md);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    background: rgba(245, 158, 11, 0.18);
    color: var(--color-warning-light);
    border: 1px solid rgba(245, 158, 11, 0.35);
}

.spell-badge-ritual {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 2px 7px;
    border-radius: var(--radius-md);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    background: rgba(168, 85, 247, 0.18);
    color: var(--color-monk-lighter);
    border: 1px solid rgba(168, 85, 247, 0.35);
}

.spell-badge-action {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 2px 7px;
    border-radius: var(--radius-md);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    background: rgba(59, 130, 246, 0.15);
    color: var(--color-info-light);
    border: 1px solid rgba(59, 130, 246, 0.3);
}

/* Casting-action badge, one modifier per turn slot (SpellCardHelpers.GetCastingActionBadgeClass):
   filled from the action economy tokens with navy text, like the combat card tags (ACT / BNS /
   RXN). Filled rather than tinted so an Action badge never reads as the amber Concentration ring
   beside it. Ritual is not a turn slot and keeps its own lavender (the combat RIT tag's). */
.spell-badge-action.slot-action,
.spell-badge-action.slot-bonus,
.spell-badge-action.slot-reaction,
.spell-badge-action.slot-ritual {
    color: var(--color-gray-900);
    border-color: transparent;
}

.spell-badge-action.slot-action   { background: color-mix(in srgb, var(--color-eco-action) 85%, transparent); }
.spell-badge-action.slot-bonus    { background: color-mix(in srgb, var(--color-eco-bonus) 85%, transparent); }
.spell-badge-action.slot-reaction { background: color-mix(in srgb, var(--color-eco-reaction) 85%, transparent); }
.spell-badge-action.slot-ritual   { background: color-mix(in srgb, var(--color-monk-lighter) 85%, transparent); }

/* ============================================
   SPELL FILTER BAR
   ============================================ */

.spell-filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space-md);
    padding: var(--space-md) 0 var(--space-lg);
}

.spell-filter-search {
    flex: 1 1 220px;
    min-width: 180px;
}

.spell-filter-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    min-width: 120px;
}

.spell-filter-toggle {
    justify-content: flex-end;
    padding-bottom: var(--space-sm);
}

.filter-label {
    color: var(--color-text-tertiary);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 0.65rem;
    padding-left: 2px;
}

.filter-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
}

/* ============================================
   PHASE 9: COMBAT A11Y + GAME-FEEL POLISH
   ============================================ */

/* sr-only — visually hidden text for the combat aria-live announcer.
   Uses the classic clip-rect pattern so screen readers still receive
   updates while the element takes no visible space. */
.sr-only {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}

/* Combat text size: the --ct-* tokens on :root (WP12g, "COMBAT TEXT SIZE" above). The page's own size stays at the
   authored 1rem whatever the player picks, so the surfaces that keep their size (hotbar slots, board badges, vitals,
   initiative portraits) never inherit the scale; only text sized with a token follows it. */
.combat-page {
    font-size: 1rem;
}

/* Red-green colorblind palette (WP12h): deuteranopia and protanopia, one palette. CombatColorblindMode.Deuteranopia
   (stored as 1, labelled "Red-green") puts the class on <body> (Combat.ApplyAccessibilityPreferencesAsync; leaving
   combat removes it). It redefines every COMBAT SEMANTIC COLORS token (and --color-eco-move): good / ally / heal
   become Okabe-Ito blues and bad / enemy / damage vermillion, each at its default's lightness; HP is a lightness
   ladder (high light sky, mid orange, low dark vermillion; the roster's four steps add a yellow and an orange), so
   healthier is lighter and the high step never reads as the mana bar. Status kinds: buff blue,
   debuff vermillion, damage over time yellow (clear of the vermillion by lightness), control magenta (purple would
   sit on the buff blue); the HUD's control pips (.se-cc-*, after this block) take the magenta too, since the freeze
   pip's sky blue read as the blue buff pip. Move turns a pale sage, a light neutral to a red-green reader: its green
   read as the action amber, a reddish purple sat on the control magenta, and the threat stroke is vermillion. Gold
   (whose turn it is, a crit) and the near-white target mark keep their colors. Checked with the Machado 2009
   deuteranopia and protanopia matrices: every pair the board and HUD need is at least 11 CIEDE2000 apart, most over
   17. CombatColorblindPaletteTests guards that every token is redefined here and recomputes the pairs. */
body.cb-red-green {
    --combat-ally: #038fc4;
    --combat-ally-light: #7dc9fa;
    --combat-enemy: #d76107;
    --combat-enemy-light: #e48247;
    --combat-enemy-lighter: #f0ae88;
    --combat-enemy-dark: #b14d00;

    --combat-ground-ally: #132936;
    --combat-ground-ally-vanguard: #103345;
    --combat-ground-ally-flank: #102734;
    --combat-ground-ally-rear: #0e212d;
    --combat-ground-ally-current: #023951;
    --combat-ground-ally-edge: #0076a3;
    --combat-ground-ally-current-edge: #2eb8f8;
    --combat-ground-ally-glow: #003a52;
    --combat-ground-ally-wall: #013146;
    --combat-ground-ally-label: #5abbf1;

    --combat-good-dark: #0997cf;
    --combat-good: #2cb7f7;
    --combat-good-light: #7fcdfe;
    --combat-good-lighter: #b4dfff;
    --combat-good-deep: #058dc1;
    --combat-bad-deep: #702f00;
    --combat-bad-darker: #9d4300;
    --combat-bad-dark: #bc5200;
    --combat-bad: #d76107;
    --combat-bad-light: #e48247;
    --combat-bad-lighter: #f0ae88;
    --combat-bad-lightest: #f7ceb8;

    --combat-hp-high: #a2d8fe;
    --combat-hp-high-light: #cee9fe;
    --combat-hp-high-dark: #7cccff;
    --combat-hp-high-alt: #a2d8fe;
    --combat-hp-high-alt-dark: #7cccff;
    --combat-hp-mid: #e7a002;
    --combat-hp-mid-light: #ffb217;
    --combat-hp-mid-yellow: #e7a002;
    --combat-hp-mid-yellow-light: #f0e442;
    --combat-hp-mid-yellow-dark: #cf8f01;
    --combat-hp-danger: #f16b00;
    --combat-hp-low: #c05400;
    --combat-hp-low-light: #d86000;
    --combat-hp-low-dark: #ad4b00;
    --combat-hp-low-darker: #964000;

    --combat-damage: #d76107;
    --combat-damage-soft: #e97f3d;
    --combat-heal: #7fcdfe;
    --combat-heal-light: #60c5fe;
    --combat-heal-dark: #12aeed;
    --combat-heal-deep: #012b3d;
    --combat-hit: #60c5fe;
    --combat-hit-icon: #50c2fe;
    --combat-miss: #e48247;
    --combat-crit: var(--color-warning-bright);  /* kept: gold is on neither axis */
    --combat-chip-good: #0072b2;                 /* darker than the L*-matched blue: white chip text, 5.2:1 */
    --combat-chip-bad: #c04f00;                  /* 4.8:1 */

    --combat-threat: #e48247;
    --combat-aim-valid: #7fcdfe;
    --combat-aim-invalid: #e48247;

    --color-status-control: #dd5599;
    --color-status-dot: #f0e442;
    --color-status-debuff: #e48247;
    --color-status-buff: #7fcdfe;

    --combat-buff-pip: #058dc1;
    --combat-buff-pip-dark: #02719c;
    --combat-buff-pip-edge: #60c5fe;

    --color-eco-move: #c6d8b4;
}

/* The HUD's control pips (vitals, party frames, initiative strip, encounter splash, the classic HUD) take the control
   magenta under the palette, like the board rail's control kind: each kind keeps its icon. */
body.cb-red-green :is(.se-cc-stun, .se-cc-freeze, .se-cc-sleep, .se-cc-charm) {
    background: linear-gradient(135deg, #c2417f, #8f2c5f);
    border-color: var(--color-status-control);
    box-shadow: 0 2px 6px color-mix(in srgb, #c2417f 40%, transparent);
}

/* Status-effect fly-in ghost — transient floating icon that travels from
   attacker → target before the persistent pip appears in the strip.
   Spawned by combat/index.js statusFlyIn() onto <body> so it can cross
   component boundaries. Position is set inline via JS. */
.status-fly-in-ghost {
    position: fixed;
    pointer-events: none;
    z-index: 9999;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(255, 220, 120, 0.95), rgba(180, 80, 40, 0.85));
    color: #1a1a1a;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    font-weight: 800;
    text-transform: uppercase;
    box-shadow: 0 0 10px rgba(255, 200, 80, 0.7), 0 2px 6px rgba(0, 0, 0, 0.6);
    transform-origin: center center;
}

.status-fly-in-ghost .material-icons {
    font-size: 18px;
    color: #1a1a1a;
}

@media (prefers-reduced-motion: reduce) {
    .status-fly-in-ghost { display: none; }
}
