/* ============================================
   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);
}

/* ============================================
   GAME FRAME (the in-game menu, Docs/LRPG_GAME_MENU.md 5.9 and 6.3)
   The frame's geometry, read by everything that leans on the bar instead of a literal: the shell's page box
   (MainLayout's .gf-content pads its top by it), the war table's reveal (wwwroot/js/wartable/focus.js), the forge's page
   height (Forge.razor.css) and the sheet's Compare bottom sheet (SheetCompare.razor.css).
     --frame-top     what the frame covers at the top: the 64px world bar from 768 up, the phone's 56px top bar below,
                     0 in combat (no frame on /game/combat*: the shell marks itself data-frame="none").
     --frame-bottom  what the frame pins at the bottom: the phone's 72px dock plus the safe area below 768 held upright
                     (phase M), 0 while the dock yields (a yield page, a focused field, Garrick's conversation, the
                     sheet's Compare) and everywhere else. A page bar that stacks on the dock sits at bottom:
                     var(--frame-bottom); the page box pads its bottom by it.
     --frame-left    what the frame pins at the left: the 76px rail on a phone held sideways (phase M), else 0. Held
                     sideways --frame-top is 0: the rail replaces the top bar and the dock.
     --frame-floor   what takes the window's bottom edge: the dock, or the device's safe area where there is no dock.
     --frame-safe-pad the safe area a page bar still pads itself by: none on the dock, all of it at the window's foot.
   The bar's own colours below are the BL kit's (canvas menu.css, the bl- classes), the only literals the frame's scoped
   sheets read. The z-index ladder: the page < the bar (MudBlazor's app bar layer, 1300) < the panels (the hero menu,
   Fellowship, the bell: 1320) < More's journal (1350) < MudBlazor's dialogs and popovers opened from the frame (Give
   feedback) < the reconnect strip (1600). The phone's dock and the sideways rail sit on the bar's layer; the panels'
   phone sheets and More's journal stand on the dock and beside the rail, never over them. Pages' own fixed bars (the war
   table's sticky gold, the merchant's categories) sit lower, beside the dock, never under it. Modal layers sit above it:
   MudBlazor's bottom drawers (1302), the tavern's sheets (1302, standing on it), the merchant's confirms and Equipment
   Guide (the dialog layer, 1400). A MudBlazor menu or select list opened from page content (popovers, 1201) sits under
   the dock: one opened in the dock's strip shows its last rows under it until the page scrolls (MudBlazor flips against
   the window, not the dock).
   ============================================ */
:root {
    --frame-top: 56px;
    --frame-bottom: 0px;
    --frame-left: 0px;
    --frame-floor: max(var(--frame-bottom), env(safe-area-inset-bottom, 0px));
    --frame-safe-pad: max(0px, env(safe-area-inset-bottom, 0px) - var(--frame-bottom));

    /* The phone's dock (blend 1.5) and the sideways rail (blend 1.7): opaque ink, a gold hairline, the raised gold orb */
    --frame-dock-height: 72px;
    --frame-rail-width: 76px;
    --frame-dock-ink: #0a0f1e;
    --frame-dock-line: rgba(255, 215, 0, 0.22);
    --frame-dock-shadow: rgba(0, 0, 0, 0.55);
    --frame-orb-rim: linear-gradient(180deg, rgba(255, 215, 0, 0.6), rgba(218, 165, 32, 0.25));
    --frame-orb-quiet-rim: linear-gradient(180deg, rgba(255, 215, 0, 0.45), rgba(148, 163, 184, 0.25));
    --frame-orb-quiet-fill: linear-gradient(180deg, #1e293b, #111a2e);

    /* The bar (blend 1.1): a navy fill, a 16% gold hairline under it, a soft shadow */
    --frame-bar-height: 64px;
    --frame-phonebar-height: 56px;
    --frame-bar-fill: linear-gradient(180deg, #0e1529, #0b1120);
    --frame-bar-ink: #0b1120;                       /* the ring around a badge or the quiet dot: the bar's own colour */
    --frame-hairline: rgba(255, 215, 0, 0.16);
    --frame-shadow: 0 8px 26px rgba(0, 0, 0, 0.34);
    --frame-line: rgba(148, 163, 184, 0.18);
    --frame-line-strong: rgba(148, 163, 184, 0.32);
    --frame-line-gold: rgba(255, 215, 0, 0.5);
    --frame-well: rgba(15, 23, 42, 0.6);

    /* The lamps (blend 1.3): a slate hex at rest, gold with an ember when the place is the page */
    --frame-hex-rest: rgba(148, 163, 184, 0.4);
    --frame-hex-dim: rgba(148, 163, 184, 0.2);
    --frame-hex-hover: rgba(255, 215, 0, 0.5);
    --frame-hex-inner: linear-gradient(180deg, #1a2540, #0d1528);
    --frame-hex-gold: linear-gradient(180deg, #ffe766, #ffd700 45%, #daa520);
    --frame-hex-ember: radial-gradient(circle at 50% 82%, #c86a14 0%, #6e350b 46%, #1d140c 100%);
    --frame-ember-glyph: #ffe9a0;
    --frame-ember-glow: rgba(255, 160, 60, 0.55);
    --frame-hex: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);

    /* Words on the bar */
    --frame-gold-soft: #ffe58a;                     /* a lit label, the road chip's waypoint */
    --frame-gold-text: #e8c352;                     /* the no-hero line, the road chip's eyebrow */
    --frame-cream: #fff6dc;                         /* the crest's name, the road chip's adventure */

    /* Counts and dots (D9): one red count with white 11px text (AA), a quiet light dot for the bell's news */
    --color-count: #dc2626;
    --frame-quiet-dot: #e2e8f0;

    /* The crest (blend 1.2) and the road chip (blend 1.4) */
    --frame-crest-base: #111a2e;                    /* the crest's fill, tinted 14% by the class colour where it is drawn */
    --frame-crest-end: rgba(12, 18, 34, 0.85);
    --frame-crest-rest: #64748b;                    /* the rim with no class (no hero, loading) */
    --frame-ring-track: rgba(148, 163, 184, 0.22);
    --frame-ring-well: #0b1222;
    --frame-ring-edge: #0a1020;
    --frame-hp: linear-gradient(90deg, #16a34a, #4ade80);
    --frame-hp-low: linear-gradient(90deg, #d97706, #f59e0b);
    --frame-hp-critical: linear-gradient(90deg, #b91c1c, #ef4444);
    --frame-pool: linear-gradient(90deg, #2563eb, #60a5fa);
    --frame-bar-track: rgba(255, 255, 255, 0.08);
    --frame-locked: #a78bfa;                        /* Premium locks: violet, never gold */
    --frame-road-fill: linear-gradient(90deg, rgba(120, 90, 0, 0.2), rgba(15, 23, 42, 0.62) 60%);
    --frame-road-line: rgba(255, 215, 0, 0.32);
    --frame-road-party-fill: linear-gradient(90deg, rgba(30, 58, 138, 0.4), rgba(15, 23, 42, 0.62) 60%);
    --frame-road-party-line: rgba(96, 165, 250, 0.45);

    /* Layers */
    --frame-z-bar: 1300;
    --frame-z-panel: 1320;                          /* the bar's panels (phase H): over the bar, under More's journal */
    --frame-z-journal: 1350;                        /* More's journal: over the bar, under MudBlazor's dialogs (1400) */
    --frame-z-reconnect: 1600;
    --frame-scrim: rgba(3, 6, 14, 0.55);
    --frame-pop-fill: linear-gradient(180deg, #101a2f, #0b1222);
    --frame-pop-shadow: 0 26px 70px rgba(0, 0, 0, 0.62);

    /* More's journal (blend 2.1, the kit's bl-journal; phase J): B's book in the frame */
    --frame-scrim-bar: rgba(3, 6, 14, 0.5);         /* the bar under the open journal */
    --frame-scrim-page: rgba(3, 6, 14, 0.72);       /* the page under it */
    --frame-journal-fill: radial-gradient(ellipse 70% 50% at 30% 0%, rgba(255, 215, 0, 0.05), transparent 70%), linear-gradient(180deg, #111b31, #0b1222 55%, #080d1a);
    --frame-journal-rim: rgba(255, 215, 0, 0.5);
    --frame-journal-inset: inset 0 0 0 6px rgba(7, 11, 22, 0.55), inset 0 0 0 7px rgba(255, 215, 0, 0.13);
    --frame-journal-shadow: 0 50px 120px rgba(0, 0, 0, 0.78);
    --frame-corner: rgba(255, 215, 0, 0.42);
    --frame-ribbon: linear-gradient(180deg, #b8860b, #ffd700 30%, #daa520);
    --frame-head-rule: rgba(255, 215, 0, 0.2);
    --frame-head-tint: linear-gradient(180deg, rgba(255, 215, 0, 0.05), rgba(255, 215, 0, 0));
    --frame-section-rule: linear-gradient(90deg, rgba(255, 215, 0, 0.36), rgba(255, 215, 0, 0));
    --frame-page-lines: repeating-linear-gradient(180deg, transparent 0, transparent 31px, rgba(148, 163, 184, 0.028) 31px, rgba(148, 163, 184, 0.028) 32px);
    --frame-spine: rgba(255, 215, 0, 0.32);
    --frame-spine-shade: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.4) 46%, rgba(0, 0, 0, 0.4) 54%, transparent);
    --frame-cream-2: #f4ecd8;                       /* "Where to?" and the tiles' names */
    --frame-font-voice: 'EB Garamond', Georgia, serif; /* the hero's title */
    --frame-on-gold: #0a0f1e;                       /* words on gold */
    --frame-gold-glow: rgba(255, 215, 0, 0.18);
    --frame-focus-halo: rgba(255, 215, 0, 0.16);
    --frame-line-gold-soft: rgba(255, 215, 0, 0.24);
    --frame-kbd-line: rgba(148, 163, 184, 0.45);
    --frame-find-line: rgba(255, 215, 0, 0.45);
    --frame-find-fill: rgba(7, 11, 22, 0.55);
    --frame-keys-fill: rgba(7, 11, 22, 0.55);
    --frame-row-hover: rgba(148, 163, 184, 0.09);
    --frame-row-current: linear-gradient(90deg, rgba(255, 215, 0, 0.16), rgba(255, 215, 0, 0.03));
    --frame-opt-icon-fill: rgba(60, 46, 8, 0.4);
    --frame-card-fill: rgba(15, 23, 42, 0.55);
    --frame-skeleton: linear-gradient(90deg, rgba(148, 163, 184, 0.08), rgba(148, 163, 184, 0.17) 50%, rgba(148, 163, 184, 0.08));
    --frame-road-card-fill: linear-gradient(100deg, rgba(60, 46, 8, 0.42), rgba(14, 20, 36, 0.92) 55%);
    --frame-road-card-line: rgba(255, 215, 0, 0.42);
    --frame-info-ink: #bfdbfe;                      /* a party's words (info blue) */
    --frame-wait-request-fill: rgba(248, 113, 113, 0.16);
    --frame-wait-request-line: rgba(248, 113, 113, 0.4);
    --frame-wait-request-ink: #fecaca;
    --frame-wait-message-fill: rgba(96, 165, 250, 0.16);
    --frame-accept-fill: rgba(120, 90, 0, 0.32);
    --frame-good-solid: #16a34a;                    /* the new spells count */
    --frame-on-good: #04120a;
    --frame-good-fill: rgba(20, 83, 45, 0.5);
    --frame-good-wash: linear-gradient(90deg, rgba(20, 83, 45, 0.4), rgba(15, 23, 42, 0.55));
    --frame-good-line: rgba(74, 222, 128, 0.45);
    --frame-good-ink: #bbf7d0;
    --frame-warn-fill: rgba(120, 53, 15, 0.5);
    --frame-warn-line: rgba(252, 211, 77, 0.55);
    --frame-warn-ink: #fde68a;
    --frame-bad-fill: rgba(127, 29, 29, 0.42);
    --frame-bad-line: rgba(248, 113, 113, 0.55);
    --frame-bad-ink: #fecaca;
    --frame-premium-ink: #ddd6fe;                   /* "Buying needs Premium": violet, never gold */
    --frame-premium-line: rgba(167, 139, 250, 0.55);
    --frame-premium-well: #2e1065;
    --frame-purse-fill: rgba(120, 90, 0, 0.3);
    --frame-xp: linear-gradient(90deg, #b8860b, #ffd700);
    --frame-tile-scrim: linear-gradient(180deg, rgba(7, 11, 22, 0.08) 0%, rgba(7, 11, 22, 0.5) 38%, rgba(7, 11, 22, 0.9) 70%, rgba(7, 11, 22, 0.97));
    --frame-tile-scrim-soft: linear-gradient(180deg, rgba(7, 11, 22, 0) 45%, rgba(7, 11, 22, 0.7));
    --frame-tile-hearth: radial-gradient(ellipse 90% 80% at 50% 108%, rgba(255, 170, 70, 0.45), rgba(255, 120, 40, 0.12) 46%, transparent 74%), linear-gradient(180deg, #1b130d, #0b0d16);
    --frame-tile-market: radial-gradient(ellipse 80% 70% at 50% 100%, rgba(255, 170, 70, 0.22), transparent 70%), linear-gradient(180deg, #1a1410, #0b0d16);
    --frame-tile-here: rgba(255, 215, 0, 0.7);
    --frame-seal-fill: radial-gradient(circle at 50% 35%, #6e3a10, #1f150c);
    --frame-seal-halo: rgba(218, 165, 32, 0.12);
    --frame-text-shadow: rgba(0, 0, 0, 0.8);
    --frame-role-fill: rgba(148, 163, 184, 0.14);   /* the Workshop's role chip: slate */
    --frame-row-current-staff: linear-gradient(90deg, rgba(148, 163, 184, 0.2), rgba(148, 163, 184, 0.04)); /* the current tool: slate, never gold */

    /* The bar's panels (blend 3; phase H): disclosures under their trigger, bottom sheets on phones */
    --frame-panel-rim: rgba(255, 215, 0, 0.32);
    --frame-panel-unread: #ffd700;                  /* a notification's unread bar */
    --frame-panel-day: #94a3b8;                     /* "Today", "Earlier" */
}

/* The world bar from 768 up (Docs/LRPG_GAME_MENU.md 5.1): 64px on desktop and tablet. */
@media (min-width: 768px) {
    :root {
        --frame-top: 64px;
    }
}

/* The phone's dock below 768 held upright (phase M, 5.4): the page box and the pages' stacked bars stand on it. */
@media (max-width: 767px) {
    :root:has(.gf-dock) {
        --frame-bottom: calc(var(--frame-dock-height) + env(safe-area-inset-bottom, 0px));
    }

    /* It yields (5.5): a yield page (the route), a focused field on touch (frame.js watchKeyboard), Garrick's conversation,
       the sheet's Compare. GfDock.razor.css slides the dock away on the same four. */
    :root:has(.gf-dock[data-yield]),
    :root[data-gf-keyboard],
    :root:has(.garrick-conversation),
    :root:has(.sheet-compare-layer) {
        --frame-bottom: 0px;
    }
}

/* A phone held sideways (blend 1.7): the 76px rail replaces the top bar and the dock; the page sits beside it. */
@media (orientation: landscape) and (max-height: 480px) {
    :root:has(.gf-rail) {
        --frame-top: 0px;
        --frame-bottom: 0px;
        --frame-left: var(--frame-rail-width);
    }
}

/* No frame in combat (5.7): the board takes the whole window. */
:root:has(.gf-app[data-frame="none"]) {
    --frame-top: 0px;
    --frame-bottom: 0px;
    --frame-left: 0px;
}

/* The pages' own bars that stack on the dock (5.5), for the scroll padding below: the War Table's sticky gold under 1024,
   the merchant's categories below 768. */
@media (max-width: 1023px) {
    :root:has(.wt-page .wt-sticky-bar) {
        --frame-page-bar: 84px;
    }
}

@media (max-width: 767px) {
    :root:has(.merchant-tabs-container) {
        --frame-page-bar: 64px;
    }
}

/* Scroll padding (WCAG 2.4.11, C12): a focused control or an anchor never lands under the frame's top, the dock and the
   page bar stacked on it, or the rail. */
html {
    scroll-padding-top: var(--frame-top);
    scroll-padding-bottom: calc(var(--frame-bottom) + var(--frame-page-bar, 0px));
    scroll-padding-left: var(--frame-left);
}

/* ============================================
   FELLOWSHIP (the friends pages, Docs/LRPG_FELLOWSHIP.md 6.3; the kit's fs- values, canvas fellowship.css)
   Presence is never colour alone: a pip SHAPE (solid on, ring away, bar busy, none offline) beside an icon and words.
   The activity tones are each AA on the navy cards. Rows and taps lift on touch (pointer: coarse): 72px rows, 44px
   controls; 36px on desktop (24px minimum, WCAG 2.5.8). The Fellowship components' scoped sheets read only these and
   the frame's, front door's and design system's tokens (no hex).
   ============================================ */
:root {
    /* The pip */
    --fs-on: #4ade80;
    --fs-away: #fcd34d;
    --fs-busy: #f87171;
    --fs-off: #64748b;

    /* Activity lines (icon + words) */
    --fs-t-on: #86efac;
    --fs-t-tavern: #ffc58a;
    --fs-t-road: #ffe58a;
    --fs-t-battle: #fca5a5;
    --fs-t-party: #bfdbfe;
    --fs-t-away: #fde68a;
    --fs-t-off: #94a3b8;

    /* At the door: a gold-tinted well (people waiting on you) */
    --fs-door: linear-gradient(100deg, rgba(255, 215, 0, 0.07), rgba(15, 23, 42, 0.6) 55%);
    --fs-door-line: rgba(255, 215, 0, 0.3);
    --fs-ring-bg: #0b1222;
    --fs-card: linear-gradient(180deg, rgba(20, 29, 50, 0.92), rgba(12, 18, 34, 0.95));
    --fs-card-line: rgba(148, 163, 184, 0.18);
    --fs-new: rgba(74, 222, 128, 0.55);
    --fs-good-fill: rgba(20, 83, 45, 0.35);
    --fs-good-line: rgba(74, 222, 128, 0.45);
    --fs-bad-fill: rgba(127, 29, 29, 0.35);
    --fs-bad-line: rgba(248, 113, 113, 0.5);
    --fs-danger: #b91c1c;
    --fs-danger-hover: #dc2626;
    --fs-scrim: linear-gradient(90deg, rgba(8, 12, 24, 0.94) 0%, rgba(8, 12, 24, 0.86) 45%, rgba(8, 12, 24, 0.55) 100%);
    --fs-you: rgba(255, 215, 0, 0.1);
    --fs-silver: linear-gradient(180deg, #e2e8f0, #94a3b8);
    --fs-bronze: linear-gradient(180deg, #f6b17a, #b45309);

    /* Sizes */
    --fs-row-h: 64px;
    --fs-tap: 36px;
    --fs-side: 300px;
    --fs-panel-w: 600px;
}

@media (pointer: coarse) {
    :root {
        --fs-row-h: 72px;
        --fs-tap: 44px;
    }
}

/* ============================================
   FRONT DOOR TOKENS (public pages and auth, Docs/LRPG_FRONT_DOOR.md)
   The palette of the front-door redesign (Direction A, "Through the tavern door"): the in-game tavern, hall and
   sheet look, carried to the public pages. Every literal the front door needs lives here; the shared sheet
   (css/front-door.css) and the Components/FrontDoor scoped sheets read only var(--fd-*) and var(--color-*).
   Size tokens that change per tier (gutter, type ramp, section rhythm) are set on .fd-root in front-door.css.
   ============================================ */
:root {
    /* Surfaces */
    --fd-bg: #070b16;                        /* page: deliberately deeper than --color-bg-base */
    --fd-bar: #0b1120;                       /* site bar, footer, phone menu, sticky CTA */
    --fd-panel: #0a1020;
    --fd-night: #050914;                     /* the street's scrim */
    --fd-forge-black: #00040c;               /* the forge video's own black (its letterbox) */
    --fd-hearth-top: #140d0a;                /* the hearth band, warmest */
    --fd-hearth-bottom: #0a0b14;
    --fd-card-top: rgba(20, 29, 50, 0.92);
    --fd-card-bottom: rgba(12, 18, 34, 0.95);
    --fd-card: linear-gradient(180deg, var(--fd-card-top), var(--fd-card-bottom));
    --fd-surface: rgba(15, 23, 42, 0.6);     /* tiles, FAQ rows, inputs' wells */
    --fd-surface-strong: rgba(15, 23, 42, 0.85);
    --fd-glass: rgba(9, 14, 28, 0.8);        /* only over art */
    --fd-well: rgba(7, 11, 22, 0.75);        /* input fields */
    --fd-slate: #1e293b;                     /* quiet hex badges */
    --fd-art-well: #0b1222;                  /* behind art while it loads */

    /* Lines */
    --fd-line: rgba(148, 163, 184, 0.18);
    --fd-line-strong: rgba(148, 163, 184, 0.32);
    --fd-line-gold: rgba(255, 215, 0, 0.24);
    --fd-line-gold-strong: rgba(255, 215, 0, 0.5);

    /* Gold */
    --fd-gold: var(--color-primary);
    --fd-gold-dark: var(--color-primary-dark);
    --fd-gold-darker: var(--color-primary-darker);
    --fd-gold-pale: #ffe766;
    --fd-gold-text: #e8c352;                 /* eyebrows, links, icons on dark */
    --fd-gold-soft: #ffe58a;                 /* gold text on a gold tint */
    --fd-gold-grad: linear-gradient(180deg, var(--fd-gold-pale), var(--fd-gold) 45%, var(--fd-gold-dark));
    --fd-on-gold: #0a0f1e;                   /* text on gold */
    --fd-on-gold-soft: #3b2a00;              /* the big CTA's sub-line */

    /* Text */
    --fd-cream: #fff6dc;                     /* names */
    --fd-cream-2: #f4ecd8;                   /* headlines, Garrick's voice */
    --fd-text: var(--color-text-primary);
    --fd-text-2: var(--color-text-lighter);
    --fd-text-3: var(--color-text-light);
    --fd-text-4: var(--color-text-secondary); /* the smallest text allowed: never tertiary under 14px */
    --fd-text-5: var(--color-text-tertiary);

    /* States */
    --fd-good: var(--color-success-light);
    --fd-good-ink: #bbf7d0;
    --fd-good-deep: #14532d;
    --fd-info: var(--color-info-light);
    --fd-info-ink: var(--color-info-lightest);
    --fd-info-deep: #1e3a8a;
    --fd-warn: var(--color-warning-light);
    --fd-warn-ink: var(--color-warning-lightest);
    --fd-warn-deep: #78350f;
    --fd-bad: var(--color-danger-light);
    --fd-bad-ink: var(--color-danger-lightest);
    --fd-bad-text: var(--color-danger-lighter);
    --fd-bad-deep: #7f1d1d;
    --fd-ember: #ffaa46;                     /* candlelight and hearth glows */
    --fd-shadow-ink: #000000;                /* drop shadows, mixed down with color-mix */

    /* Type, radii, shadow, bar */
    /* Each web font is followed by a metric-matched local face (front-door.css, @font-face "... Fallback"), so a
       first visit paints its headings and paragraphs at the web font's size and the swap moves nothing (CLS). */
    --fd-font-display: 'Cinzel', 'Cinzel Fallback', serif;
    --fd-font-ui: 'Inter', 'Inter Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --fd-font-voice: 'EB Garamond', 'EB Garamond Fallback', Georgia, serif;
    --fd-r-btn: 10px;
    --fd-r-input: 12px;
    --fd-r-card: 14px;
    --fd-r-glass: 16px;
    --fd-r-band: 18px;
    --fd-r-arch: 999px 999px 16px 16px;
    --fd-shadow: 0 20px 50px rgba(0, 0, 0, 0.45);
    --fd-shadow-gold: 0 12px 34px rgba(255, 215, 0, 0.28);
    --fd-bar-h: 64px;
    --fd-sticky-h: 72px;
}

/* ============================================
   WAR TABLE TOKENS (/game/adventures, Docs/LRPG_WAR_TABLE.md 7.1, D12)
   The kit's seven muted region tints: region strips, dots, chip rims and the realm chart's washes, never text (they
   are under 4.5:1 as small text). The chart's ink and vellum, and the pin fills by state (3a M8). The static chart
   art (images/charts/*.svg) inlines the same palette: an SVG drawn as an image can't read custom properties.
   ============================================ */
:root {
    /* Region tints (WarTableCharts.Realm.Regions[].TintToken) */
    --color-region-greyspine: #9fb3c8;
    --color-region-verdant-weald: #8fbf6a;
    --color-region-ashfeld: #c9b48a;
    --color-region-cinderpeak: #e8875a;
    --color-region-shattered-isles: #5fb4b9;
    --color-region-kingshold: #c79bd0;
    --color-region-realm: #94a3b8;           /* Across the realm (MultiRegion) */

    /* The charts: gold ink on navy vellum */
    --wt-ink: #e8c352;
    --wt-ink-deep: #d9ad3c;
    --wt-vellum: #0d1630;
    --wt-vellum-deep: #070b16;
    --wt-land: #1a2440;
    --wt-sea: #0a1226;
    --wt-plate: rgba(7, 11, 22, 0.88);       /* pin and waypoint plates over the chart */

    /* Pin fills by state */
    --wt-pin-run: var(--color-primary);
    --wt-pin-open: var(--wt-ink);
    --wt-pin-party: var(--color-info-light);
    --wt-pin-premium: #a78bfa;
    --wt-pin-soon: var(--color-gray-500);
    --wt-pin-walked: var(--color-primary-dark);
    --wt-pin-boss: var(--color-danger);
    --wt-camp: #4ade80;                      /* rest stops on a road */
}

/* ============================================
   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; }
}
