/* =============================================================
   LogicArcade Design Token System
   All components MUST use these variables. No hardcoded colors.
   ============================================================= */

:root {
    color-scheme: dark;
    /* --- Base Palette (Adult/Lounge defaults) --- */
    --bg-color: #0f172a;
    --surface-color: #1e293b;
    --surface-hover: #334155;
    --text-primary: #f8fafc;
    --text-secondary: #94a3b8;
    --accent-primary: #3b82f6;
    --accent-glow: rgba(59, 130, 246, 0.5);
    --border-color: #334155;
    --border-radius-sm: 0.375rem;
    --border-radius-md: 0.5rem;
    --border-radius-lg: 1rem;

    /* --- Glass Morphism --- */
    --glass-bg: rgba(255, 255, 255, 0.03);
    --glass-border: rgba(255, 255, 255, 0.08);
    --glass-border-bright: rgba(255, 255, 255, 0.18);
    --glass-input-bg: rgba(255, 255, 255, 0.05);
    --glass-input-focus: rgba(255, 255, 255, 0.08);

    /* --- Semantic Status Colors (theme-independent) --- */
    --color-success: #22c55e;
    --color-success-soft: rgba(34, 197, 94, 0.12);
    --color-success-text: #86efac;
    --color-error: #ef4444;
    --color-error-soft: rgba(239, 68, 68, 0.10);
    --color-error-text: #fca5a5;
    --color-info: #3b82f6;
    --color-info-soft: rgba(59, 130, 246, 0.12);
    --color-info-text: #93c5fd;
    --color-warning: #f59e0b;
    --color-warning-soft: rgba(245, 158, 11, 0.12);
    --color-warning-text: #fcd34d;

    /* --- Game cell fallbacks --- */
    --cell-bg-active: #2563eb;
    --cell-text-active: #ffffff;
    --cell-text: #f8fafc;
    --cell-text-locked: #94a3b8;
    --cell-text-error: #ef4444;

    /* --- Win/Loss UI --- */
    --win-title-color: #fbbf24;
    --win-title-gradient: linear-gradient(to right, #fbbf24, #f59e0b);
    --lose-title-color: #ef4444;
    --lose-title-gradient: linear-gradient(to right, #ef4444, #fca5a5);

    /* --- Game Type Badge Colors --- */
    --badge-lex: #3b82f6;
    --badge-grid: #a855f7;
    --badge-span: #ef4444;
    --badge-lattice: #00f2fe;
    --badge-default: #fbbf24;

    /* --- Leaderboard / Ranking --- */
    --rank-gold: #f59e0b;
    --rank-silver: #9ca3af;
    --rank-bronze: #b45309;
    --rank-positive: #10b981;
    --rank-neutral: #f59e0b;
    --rank-negative: #ef4444;

    /* --- Layout Tokens (task 939) ---
       MainLayout.razor.css's .sidebar width and .top-row height, hoisted here because they used
       to be duplicated as bare literals across MainLayout.razor.css AND all four game
       *Game.razor.css files (each of which subtracts the nav height or the sidebar width when
       sizing its board around them). Consume via var(--sidebar-w, 250px) / var(--topnav-h, 3.5rem)
       everywhere — the fallback keeps every call site correct even if a future stylesheet loads
       before this one, but the intent is that nothing outside this file ever hardcodes these two
       numbers again. */
    --sidebar-w: 250px;
    --topnav-h: 3.5rem;

    /* --- Misc Component Tokens --- */
    --text-on-accent: #ffffff;
    --wf-key-border: #1a1a1a;
    --wf-enter-bg: linear-gradient(135deg, #3b82f6, #1d4ed8);
    --wf-enter-disabled-bg: linear-gradient(135deg, #6b7280, #374151);
    --loading-spinner-size: 3rem;
    --loading-container-height: 400px;

    --font-heading: 'Outfit', 'Helvetica Neue', sans-serif;
    --font-body: 'Inter', 'Segoe UI', sans-serif;

    /* --- Glass Blur (controllable per theme) --- */
    --glass-blur-sm: 4px;
    --glass-blur-md: 8px;
    --glass-blur-lg: 12px;

    /* --- Structural Shadows --- */
    --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.15);
    --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.25);
    --shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.4);
}

.metrics-row {
    margin: 1rem 0 2rem;
    display: flex;
    justify-content: space-around;
    background: var(--glass-bg, rgba(255, 255, 255, 0.03));
    padding: 1rem;
    border-radius: var(--border-radius-md, 0.5rem);
    border: 1px solid var(--glass-border);
}

.metrics-row div {
    text-align: center;
}

.metrics-row small {
    color: var(--text-secondary);
    text-transform: uppercase;
    font-size: 0.7rem;
    letter-spacing: 0.05em;
}

.metrics-row strong {
    color: var(--text-primary);
    font-size: 1.2rem;
    font-family: var(--font-heading);
}

/* --- Win Overlay Shared Styles --- */
.win-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.7);
    backdrop-filter: blur(var(--glass-blur-md));
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 9999;
}

.win-card {
    text-align: center;
    max-width: 450px;
    padding: 3rem 2rem;
    border: 1px solid rgba(255, 215, 0, 0.3);
    /* Gold tint */
    box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5), 0 0 20px rgba(255, 215, 0, 0.1);
}

.win-icon {
    font-size: 4.5rem;
    color: var(--win-title-color);
    margin-bottom: 1rem;
    display: inline-block;
    filter: drop-shadow(0 0 10px rgba(255, 215, 0, 0.5));
}

.win-card h2 {
    font-size: 2.5rem;
    font-weight: 800;
    margin-bottom: 1rem;
    background: var(--win-title-gradient);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

.win-card p {
    font-size: 1.1rem;
    color: var(--text-secondary);
    margin-bottom: 2rem;
}

.win-actions {
    display: flex;
    gap: 1rem;
    justify-content: center;
    flex-wrap: wrap;
}

.auto-shared-note {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.85rem;
    color: var(--color-success-text);
    background: var(--color-success-soft);
    border: 1px solid var(--color-success-text);
    border-radius: var(--border-radius-md);
    padding: 0.4rem 0.9rem;
    opacity: 0.85;
}

/* Options button */
.assist-popup-wrapper {
    display: inline-block;
}

/* Options modal */
.options-modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1000;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
}

.options-modal {
    background: var(--surface-color);
    border: 1px solid var(--glass-border);
    border-radius: var(--border-radius-md);
    padding: 1rem 1.25rem;
    width: min(90vw, 340px);
    max-height: 80dvh;
    overflow-y: auto;
    box-shadow: 0 8px 32px -4px rgba(0, 0, 0, 0.5);
}

.options-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0.75rem;
}

.options-modal-close {
    background: none;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 0.2rem 0.3rem;
    line-height: 1;
    border-radius: 4px;
}

.options-modal-close:hover {
    color: var(--text-primary);
    background: var(--glass-border);
}

.assist-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0.5rem;
}

.assist-panel-title {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-secondary);
}

.assist-source-legend {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.65rem;
    color: var(--text-secondary);
}

.assist-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.6rem;
    font-weight: 700;
    border-radius: 3px;
    padding: 0 3px;
    line-height: 1.4;
}

.group-badge {
    background: var(--color-info-soft);
    color: var(--color-info-text);
}

.me-badge {
    background: var(--color-success-soft);
    color: var(--color-success-text);
}

.assist-sources {
    display: inline-flex;
    gap: 2px;
    margin-left: auto;
}

.assist-option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    color: var(--text-primary);
    padding: 0.3rem 0;
    cursor: pointer;
    user-select: none;
}

.assist-option input[type="checkbox"] {
    accent-color: var(--accent-primary);
    width: 14px;
    height: 14px;
    cursor: pointer;
}

/* Settings modal assist checks */
.assist-checks {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding-left: 0.25rem;
}

.assist-check-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.875rem;
    color: var(--text-primary);
    cursor: pointer;
    user-select: none;
}

.assist-check-item input[type="checkbox"] {
    accent-color: var(--accent-primary);
    width: 14px;
    height: 14px;
    cursor: pointer;
}

.btn-glass-metric {
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    color: var(--text-primary);
    padding: 0.5rem 1rem;
    border-radius: var(--border-radius-md);
    cursor: pointer;
    transition: all 0.2s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    font-family: var(--font-body);
}

.btn-glass-metric:hover:not(:disabled) {
    background: var(--surface-hover);
    border-color: var(--accent-primary);
    transform: translateY(-1px);
}

.btn-glass-metric:active:not(:disabled) {
    transform: translateY(0);
}

.btn-glass-metric:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    filter: grayscale(1);
}

.icon-btn {
    width: 2.5rem;
    height: 2.5rem;
    padding: 0;
    font-size: 1.25rem;
}

.theme-adult {
    color-scheme: dark;
    /* DEFAULT THEME: THE LOUNGE (Dark Mode, Sleek, Neon Accents) */
    --bg-color: #0f172a;
    --surface-color: #1e293b;
    --surface-hover: #334155;
    --text-primary: #f8fafc;
    --text-secondary: #94a3b8;
    --accent-primary: #3b82f6;
    --accent-glow: rgba(59, 130, 246, 0.5);
    --border-color: #334155;

    --glass-bg: rgba(255, 255, 255, 0.03);
    --glass-border: rgba(255, 255, 255, 0.08);
    --glass-border-bright: rgba(255, 255, 255, 0.18);
    --glass-input-bg: rgba(255, 255, 255, 0.05);
    --glass-input-focus: rgba(255, 255, 255, 0.09);


    /* Grid */
    --grid-line: #475569;
    --grid-block-line: #94a3b8;
    --cell-bg: #1e293b;
    --cell-bg-active: #2563eb;
    --cell-text-active: #ffffff;
    --cell-text: #f8fafc;
    --cell-text-locked: #94a3b8;
    --cell-text-error: #ef4444;

    /* Span */
    --island-bg: #1e293b;
    --island-text: #f8fafc;
    --island-border: #475569;
    --island-bg-active: #2563eb;
    --island-bg-satisfied: #10b981;
    --island-bg-error: #dc2626;
    --bridge-line: #94a3b8;
    --bridge-cursor: rgba(148, 163, 184, 0.1);

    /* Lex - Arcade Palette */
    --wf-cell-bg: var(--glass-bg);
    --wf-cell-border: var(--glass-border);
    --wf-cell-active-border: var(--accent-primary);
    --wf-text: var(--text-primary);
    --wf-absent-bg: rgba(255, 255, 255, 0.05);
    /* Fade out */
    --wf-present-bg: #fbbf24;
    /* Neon Gold */
    --wf-correct-bg: #00f2fe;
    /* Cyber Cyan */
    --wf-key-bg: var(--surface-hover);
    --wf-glow-correct: rgba(0, 242, 254, 0.5);
    --wf-glow-present: rgba(251, 191, 36, 0.4);

    /* Lattice */
    --ws-cell-bg: #1e293b;
    --ws-cell-border: #334155;
    --ws-text: #f8fafc;
    --ws-cell-active-bg: #fbbf24;
    --ws-cell-active-text: #0f172a;
    --ws-cell-found-bg: #10b981;
    --ws-cell-found-text: #ffffff;
    --ws-word-text: #94a3b8;

    --font-heading: 'Outfit', 'Helvetica Neue', sans-serif;
    --font-body: 'Inter', 'Segoe UI', sans-serif;

    --glass-blur-sm: 4px;
    --glass-blur-md: 8px;
    --glass-blur-lg: 12px;
    --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.15);
    --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.25);
    --shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.4);
}

.theme-kid {
    color-scheme: light;
    /* FAMILY/KID THEME: High Contrast, Bright, Accessible */
    --bg-color: #f8fafc;
    --surface-color: #ffffff;
    --surface-hover: #e2e8f0;
    --text-primary: #0f172a;
    --text-secondary: #475569;
    --accent-primary: #10b981;
    --accent-glow: rgba(16, 185, 129, 0.3);
    --border-color: #cbd5e1;

    --glass-bg: rgba(0, 0, 0, 0.02);
    --glass-border: rgba(0, 0, 0, 0.08);
    --glass-border-bright: rgba(0, 0, 0, 0.16);
    --glass-input-bg: rgba(0, 0, 0, 0.04);
    --glass-input-focus: rgba(16, 185, 129, 0.08);


    /* Grid */
    --grid-line: #cbd5e1;
    --grid-block-line: #0f172a;
    --cell-bg: #ffffff;
    --cell-bg-active: #fbbf24;
    --cell-text-active: #0f172a;
    --cell-text: #059669;
    --cell-text-locked: #0f172a;
    --cell-text-error: #dc2626;

    /* Span */
    --island-bg: #ffffff;
    --island-text: #0f172a;
    --island-border: #0f172a;
    --island-bg-active: #059669;
    --island-bg-satisfied: #10b981;
    --island-bg-error: #dc2626;
    --bridge-line: #0f172a;
    --bridge-cursor: rgba(15, 23, 42, 0.1);

    /* Lex */
    --wf-cell-bg: #ffffff;
    --wf-cell-border: #cbd5e1;
    --wf-cell-active-border: #0f172a;
    --wf-text: #0f172a;
    --wf-absent-bg: #94a3b8;
    --wf-present-bg: #f97316;
    /* Vivid Orange */
    --wf-correct-bg: #3b82f6;
    /* Bright Blue */
    --wf-key-bg: #e2e8f0;

    /* Lattice */
    --ws-cell-bg: #ffffff;
    --ws-cell-border: #cbd5e1;
    --ws-text: #0f172a;
    --ws-cell-active-bg: #fbbf24;
    --ws-cell-active-text: #0f172a;
    --ws-cell-found-bg: #059669;
    --ws-cell-found-text: #ffffff;
    --ws-word-text: #475569;

    --font-heading: 'Outfit', 'Helvetica Neue', sans-serif;
    --font-body: 'Inter', 'Segoe UI', sans-serif;

    --glass-blur-sm: 4px;
    --glass-blur-md: 8px;
    --glass-blur-lg: 12px;
    --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
    --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.12);
    --shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.18);
}

.theme-retro-dark {
    color-scheme: dark;
    /* RETRO DARK: Early 2000s dark web — navy-charcoal, classic link blue, no glass */
    --bg-color: #0e0e16;
    --surface-color: #16161e;
    --surface-hover: #1e1e2a;
    --text-primary: #ccccdc;
    --text-secondary: #7788aa;
    --accent-primary: #4488cc;
    --accent-glow: rgba(68, 136, 204, 0.4);
    --border-color: #2a2a3a;
    --border-radius-sm: 2px;
    --border-radius-md: 2px;
    --border-radius-lg: 4px;

    --glass-blur-sm: 0px;
    --glass-blur-md: 0px;
    --glass-blur-lg: 0px;
    --glass-bg: rgba(255, 255, 255, 0.02);
    --glass-border: rgba(255, 255, 255, 0.08);
    --glass-border-bright: rgba(255, 255, 255, 0.14);
    --glass-input-bg: rgba(255, 255, 255, 0.04);
    --glass-input-focus: rgba(68, 136, 204, 0.12);

    --font-heading: Georgia, 'Times New Roman', serif;
    --font-body: Verdana, Tahoma, Arial, sans-serif;

    --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
    --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.5);
    --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.6);

    /* Grid */
    --grid-line: #2a2a3a;
    --grid-block-line: #4455aa;
    --cell-bg: #16161e;
    --cell-bg-active: #1155cc;
    --cell-text-active: #ffffff;
    --cell-text: #ccccdc;
    --cell-text-locked: #7788aa;
    --cell-text-error: #ee5555;

    /* Span */
    --island-bg: #16161e;
    --island-text: #ccccdc;
    --island-border: #2a2a3a;
    --island-bg-active: #1155cc;
    --island-bg-satisfied: #117755;
    --island-bg-error: #cc3333;
    --bridge-line: #7788aa;
    --bridge-cursor: rgba(119, 136, 170, 0.1);

    /* Lex */
    --wf-cell-bg: var(--glass-bg);
    --wf-cell-border: var(--glass-border);
    --wf-cell-active-border: var(--accent-primary);
    --wf-text: var(--text-primary);
    --wf-absent-bg: rgba(255, 255, 255, 0.04);
    --wf-present-bg: #cc8800;
    --wf-correct-bg: #00aacc;
    --wf-key-bg: var(--surface-hover);
    --wf-glow-correct: rgba(0, 170, 204, 0.4);
    --wf-glow-present: rgba(204, 136, 0, 0.4);

    /* Lattice */
    --ws-cell-bg: #16161e;
    --ws-cell-border: #2a2a3a;
    --ws-text: #ccccdc;
    --ws-cell-active-bg: #cc8800;
    --ws-cell-active-text: #ffffff;
    --ws-cell-found-bg: #117755;
    --ws-cell-found-text: #ffffff;
    --ws-word-text: #7788aa;
}

.theme-retro-light {
    color-scheme: light;
    /* RETRO LIGHT: Early 2000s light web — warm cream, white panels, classic navy */
    --bg-color: #e8e4de;
    --surface-color: #ffffff;
    --surface-hover: #f0eeea;
    --text-primary: #111111;
    --text-secondary: #555566;
    --accent-primary: #1155cc;
    --accent-glow: rgba(17, 85, 204, 0.3);
    --border-color: #aaaaaa;
    --border-radius-sm: 2px;
    --border-radius-md: 2px;
    --border-radius-lg: 4px;

    --glass-blur-sm: 0px;
    --glass-blur-md: 0px;
    --glass-blur-lg: 0px;
    --glass-bg: rgba(0, 0, 0, 0.02);
    --glass-border: rgba(0, 0, 0, 0.10);
    --glass-border-bright: rgba(0, 0, 0, 0.18);
    --glass-input-bg: rgba(0, 0, 0, 0.03);
    --glass-input-focus: rgba(17, 85, 204, 0.08);

    --font-heading: Georgia, 'Times New Roman', serif;
    --font-body: Verdana, Tahoma, Arial, sans-serif;

    --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12);
    --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.18);
    --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.22);

    /* Win/Loss UI */
    --win-title-color: #996600;
    --win-title-gradient: linear-gradient(to right, #996600, #cc8800);
    --lose-title-color: #cc2200;
    --lose-title-gradient: linear-gradient(to right, #cc2200, #ee4422);

    /* Grid */
    --grid-line: #bbbbbb;
    --grid-block-line: #333333;
    --cell-bg: #ffffff;
    --cell-bg-active: #1155cc;
    --cell-text-active: #ffffff;
    --cell-text: #111111;
    --cell-text-locked: #555566;
    --cell-text-error: #cc2200;

    /* Span */
    --island-bg: #ffffff;
    --island-text: #111111;
    --island-border: #aaaaaa;
    --island-bg-active: #1155cc;
    --island-bg-satisfied: #117744;
    --island-bg-error: #cc2200;
    --bridge-line: #555566;
    --bridge-cursor: rgba(17, 85, 204, 0.1);

    /* Lex */
    --wf-cell-bg: #ffffff;
    --wf-cell-border: #aaaaaa;
    --wf-cell-active-border: #111111;
    --wf-text: #111111;
    --wf-absent-bg: #cccccc;
    --wf-present-bg: #cc6600;
    --wf-correct-bg: #1155cc;
    --wf-key-bg: #e0ddd8;
    --wf-glow-correct: rgba(17, 85, 204, 0);
    --wf-glow-present: rgba(204, 102, 0, 0);

    /* Lattice */
    --ws-cell-bg: #ffffff;
    --ws-cell-border: #aaaaaa;
    --ws-text: #111111;
    --ws-cell-active-bg: #cc8800;
    --ws-cell-active-text: #ffffff;
    --ws-cell-found-bg: #117744;
    --ws-cell-found-text: #ffffff;
    --ws-word-text: #555566;

    /* Semantic colors */
    --color-success: #117744;
    --color-success-soft: rgba(17, 119, 68, 0.1);
    --color-success-text: #117744;
    --color-error: #cc2200;
    --color-error-soft: rgba(204, 34, 0, 0.1);
    --color-error-text: #cc2200;
    --color-info: #1155cc;
    --color-info-soft: rgba(17, 85, 204, 0.1);
    --color-info-text: #1155cc;
    --color-warning: #996600;
    --color-warning-soft: rgba(153, 102, 0, 0.1);
    --color-warning-text: #996600;
    --text-on-accent: #ffffff;
}

/* Base DOM Reset */
html,
body {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
    font-family: var(--font-body);
    background-color: var(--bg-color);
    color: var(--text-primary);
    -webkit-font-smoothing: antialiased;
    touch-action: manipulation;
    /* Prevent mobile double-tap zoom */
}

/* Mobile First App Layout Grid */
.app-container {
    display: flex;
    flex-direction: column;
    height: 100vh;
    max-width: 1200px;
    margin: 0 auto;
}

/* ==============================================================
   THE FLOATING CHAT'S RESERVED ZONE (task 1129)
   ==============================================================
   GroupChat is `position: fixed; bottom: 0` at `z-index: 9999`, and below 768px its width override
   makes it a FULL-WIDTH 40px BAR across the bottom of the viewport (GroupChat.razor.css). Anything
   fixed at that z-index is not "overlapping" the UI beneath it — it OWNS those pixels, and takes
   the taps too.

   This token is that ownership written down once. `--chat-h` is 0 when no group is active (no chat
   renders) and 40px when one is, so any layout that must not be sat on can subtract it without
   re-deriving where the chat is or whether it exists.

   WHY A TOKEN RATHER THAN A THIRD ONE-OFF DODGE. This is the third occlusion bug from this one
   widget: task 1125 (the client build id underneath it), task 1129 (Lex's keyboard), and the
   `.nav-bottom-pad` rule below, which was itself an earlier dodge. Each was fixed by moving the
   victim. That does not converge — the widget keeps acquiring new neighbours, and nothing tells a
   future author that the bottom 40px of the viewport is spoken for. See the note in
   LexGame.razor.css for why reserving space INSIDE the content (which is what the keyboard tried)
   does not work when the container clips.

   SCOPE, HONESTLY: this reserves the band and applies it to all four games' mobile containers,
   because all four were measured. It does NOT unify the four separate sizing models those
   containers still use — that is the `--nav-h`/chrome consolidation the display-layer memory
   describes, and it does not belong smuggled into a bug fix.

   THE SWEEP CHANGED THE CULPRIT. This task arrived describing a bottom-LEFT circular chat bubble
   over Lex's ENTER key. GroupChat is neither bottom-left nor circular, and measuring all four
   games found the actual offender: `.feedback-widget-container` (FeedbackComponent.razor.css) —
   `position: fixed; bottom: 60px; LEFT: 20px; z-index: 11000`, a 3.5rem circular button with a
   `bi-chat-left-dots-fill` icon. Bottom-left, circular, chat-bubble: exactly as reported, just not
   the component anyone suspected. It also is NOT group-gated, so unlike GroupChat it collides in
   DAILY AND FREE PLAY too, and it was measured covering controls in THREE of four games (Lex,
   Grid, Span; Lattice is clear). Its z-index 11000 outranks even the chat's 9999.

   So the zone below is shared: both floating widgets live in it, and no game content may. */
:root {
    --chat-h: 0px;
    --float-zone-h: 0px;
}

.has-floating-chat {
    --chat-h: 40px;
}

/* On a phone the games fill the viewport, so the floating widgets are guaranteed to sit on top of
   something. Reserving the band they occupy is what makes "fixed overlay" and "scrollable content"
   coexist: the scroll container is inset above the band, so content can scroll freely and still
   never pass underneath a widget. Reserving space INSIDE the content cannot achieve that — see the
   note on .wf-keyboard in LexGame.razor.css for the version of this that was already tried.

   62px = the FAB's mobile height (44px) + its offset above the chat (10px) + 8px of clearance.
   It is declared on `.page` rather than `:root` deliberately: `--chat-h` is set by
   `.has-floating-chat` ON `.page`, so a `:root` declaration would resolve it to 0 and silently
   under-reserve by 40px whenever a group is active. */
@media (max-width: 767px) {
    .page {
        --float-zone-h: calc(var(--chat-h) + 62px);
    }
}

/* Keep the nav version footer above the collapsed bar (task 1125). Now derived from the token
   rather than repeating the 40px with a mystery 4px of slack. */
.has-floating-chat .nav-bottom-pad {
    padding-bottom: calc(var(--chat-h) + 4px);
}

header {
    padding: 1rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: 1px solid var(--border-color);
    flex-wrap: wrap;
    gap: 0.5rem;
}

header h1 {
    font-family: var(--font-heading);
    margin: 0;
    font-size: 1.5rem;
    font-weight: 700;
    flex-shrink: 0;
    white-space: nowrap;
}

.puzzle-expiry-badge {
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--text-muted);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--border-radius-sm);
    padding: 0.15rem 0.5rem;
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
}

.puzzle-expiry-badge.expiring-soon {
    color: var(--color-warning, #f59e0b);
    border-color: var(--color-warning, #f59e0b);
}

.puzzle-expiry-badge.expired {
    color: var(--text-muted);
    opacity: 0.6;
}

.expiry-time {
    font-weight: 400;
    margin-left: 0.1rem;
}

.timer {
    font-variant-numeric: tabular-nums;
    min-width: 3.5rem;
    text-align: right;
    display: inline-block;
    flex-shrink: 0;
}

.toolbar-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
}

.game-layout {
    display: flex;
    flex-direction: column;
    flex: 1;
    overflow-y: auto;
    min-height: 0;
}

/* Desktop/Tablet Override */
@media (min-width: 768px) {
    .game-layout {
        flex-direction: row;
        align-items: flex-start;
        justify-content: center;
        padding: 2rem;
        gap: 2rem;
    }
}

/* Blazor Defaults Kept for Safety */
#blazor-error-ui {
    background: #ef4444;
    color: white;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 1rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

/* Global Game Status Indicators (Bronze, Silver, Gold) */
.status-indicator {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    margin-right: 8px;
    box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
}

.status-bronze {
    background-color: #cd7f32;
    border: 1px solid #a0522d;
}

.status-silver {
    background-color: #c0c0c0;
    border: 1px solid #808080;
}

.status-gold {
    background-color: #ffd700;
    border: 1px solid #b8860b;
    box-shadow: 0 0 8px rgba(255, 215, 0, 0.6);
}

/* Lex Specific Styles */
.wf-board-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2rem;
    width: 100%;
    margin-top: 1rem;
}

/* SIZING FOR THE LEX ROW LIVES IN LexGame.razor.css, NOT HERE (task 1073).
   This block used to carry `overflow-x: auto` with the comment "Long words on a narrow phone
   scroll horizontally rather than clip", plus a duplicate `.wf-cell { width/height: 3.5rem }` that
   the scoped stylesheet silently overrode anyway. Both are gone.

   The scroll escape hatch was the bug, not a mitigation. It let a 7-letter row render as a
   plausible 6-letter one with the leading column parked off-screen, reachable only by a gesture
   the player has no reason to know exists — Kris: "I can't see the 1st box and I have to scroll.
   it should be shrinking to fit." Cells shrink now, so the hatch is unnecessary; removing it also
   means a future regression cannot hide behind a scrollbar the player never discovers.

   ⚠️ DO NOT READ THE ABSENCE OF `overflow` HERE AS A SAFETY NET. An earlier version of this
   comment claimed "if a future change ever breaks the fit, it must be SEEN". THAT CLAIM IS FALSE
   and is corrected here rather than deleted, because it is exactly the kind of comforting sentence
   that stops the next person from checking. On a phone — the only place this bug has ever
   occurred — `.app-container` is `overflow-x: hidden` (LexGame.razor.css, the max-width:767px
   block), so an overflowing row is SILENTLY CLIPPED BY THE ANCESTOR. Same user-visible symptom as
   the original bug, minus even the scroll gesture that at least hinted something was there.

   THE ONLY THING WATCHING THIS IS tests/Coterie.E2E.Tests/LexRowFitTests.cs, which measures each
   column's own rect against the viewport at lengths 4-10 across three widths, with and without
   group-mode row chrome. bUnit cannot see it (no layout engine) and neither can any runtime
   behaviour. If you change the row's sizing, that suite is your detector — run ./e2e.sh. */
.wf-grid {
    display: flex;
    flex-direction: column;
    gap: 8px;
    align-items: center;
    max-width: 100%;
    /* LOAD-BEARING, and it was not obvious. `.wf-grid` used to be shrink-to-fit, taking its width
       from the intrinsic width of its fixed-size cells. Once the cells became `flex: 1 1 0` their
       min-content contribution is ~0, so the grid collapsed to 18px and every cell rendered 4x4px
       with a 0px glyph — measured, on the first run of the fix. The cells can only divide a width
       that something else has established, so the grid must claim it explicitly. */
    width: 100%;
}

.wf-grid-row.invalid-word {
    animation: h-shake 0.4s ease-in-out;
}

/* NOTE: width/height/font-size are deliberately absent — see the `.wf-grid` comment above.
   `.wf-grid-row .wf-cell` in LexGame.razor.css owns the box and the glyph. */
.wf-cell {
    background: var(--wf-cell-bg);
    border: 2px solid var(--wf-cell-border);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    color: var(--wf-text);
    text-transform: uppercase;
    transition: all 0.3s ease;
}

.wf-cell.active {
    border-bottom: 4px solid var(--wf-cell-active-border);
}

.wf-cell.absent {
    border-color: var(--cell-text-error);
}

.wf-cell.present {
    color: var(--wf-present-bg);
}

.wf-cell.correct {
    color: var(--wf-correct-bg);
}

.wf-keyboard {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 100%;
    max-width: 500px;
    touch-action: manipulation;
    margin: 1rem auto 0;
    padding-bottom: 2rem;
}

.wf-row {
    display: flex;
    justify-content: center;
    gap: 6px;
}

.wf-row.invalid-word {
    animation: h-shake 0.4s ease-in-out;
}

@keyframes h-shake {
    0% {
        transform: translateX(0);
    }

    20% {
        transform: translateX(-5px);
    }

    40% {
        transform: translateX(5px);
    }

    60% {
        transform: translateX(-5px);
    }

    80% {
        transform: translateX(5px);
    }

    100% {
        transform: translateX(0);
    }
}

.wf-key {
    background: var(--wf-key-bg);
    color: var(--wf-text);
    border: none;
    border-radius: 4px;
    font-size: 1.2rem;
    font-weight: 600;
    padding: 12px 0;
    flex: 1;
    min-width: 30px;
    max-width: 45px;
    cursor: pointer;
    text-transform: uppercase;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s ease;
}

.wf-key.wide {
    flex: 1.5;
    max-width: 70px;
    font-size: 0.9rem;
}

.wf-key:active {
    opacity: 0.8;
}

.wf-key.disabled {
    opacity: 0.2;
    pointer-events: none;
}

/* Lattice Specific Styles */
.ws-board-container {
    display: flex;
    flex-direction: column;
    /* `safe center` rather than `center`. Like the overflow below this is a LATENT guard, not an
       active one: plain centring pushes an over-wide child's leading edge off the SCROLL ORIGIN,
       but nothing here can currently be over-wide. Kept for the same reason. */
    align-items: safe center;
    gap: 2rem;
    width: 100%;
    max-width: 100%;
    margin-top: 1rem;
    /* A LATENT BACKSTOP THAT CANNOT CURRENTLY FIRE. Do not read it as an active mechanism.
       An earlier version of this comment claimed the board pans here once it hits its legibility
       floor. TRACE IT AND THAT IS FALSE: `.ws-grid` is `width: 100%` and a direct child of this
       container, `max-width` is an UPPER bound that cannot push an element wider than its
       containing block, and `.ws-cell` carries `min-width: 0` so the 1fr tracks collapse to a zero
       min-content contribution. So the grid's width is always min(container, max-width) — it can
       never exceed this container, and this rule never scrolls. The only reachable overflow is a
       container narrower than the 44px of non-shrinkable gaps.

       It stays because it is cheap and it is the correct behaviour IF that ever changes — deleting
       `min-width: 0` from `.ws-cell` would immediately make the grid able to outgrow this box, and
       panning is what keeps every cell reachable when it does. Clipping is the alternative and it
       is the one that loses cells.

       WHAT IT IS NOT: this is not the escape hatch task 1028 removed. That one was `overflow-x` on
       `.ws-grid` ITSELF, which let the GRID be wider than the box it declared, so a column could
       hang off the leading edge while the board still measured as a perfect fit. That failure mode
       is unreachable now — but because of the `width: 100%` + `min-width: 0` geometry above, not
       because of this line. */
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

/* ==============================================================
   LATTICE BOARD SIZING — SHRINK TO FIT (task 1028)
   ==============================================================
   Ported from the Lex row fix (task 1073) at Kris's direction: "the zooming for lex can work on
   lattice" / "has the lex fix gone to the other games, it is good for keeping things visible."

   WHAT THIS REPLACES. LatticeGame.razor computed a cell size in C# and emitted it INLINE as the
   grid's track size. Its width term was `var(--ws-avail-w, 100vw)`, and --ws-avail-w was only
   defined inside a 641-767px media band; from 768px up it fell back to raw 100vw even though the
   sidebar persists unbounded from 641px. The board was therefore sized for width the sidebar was
   already occupying. It was usually masked because the formula's HEIGHT term won once the viewport
   was tall enough to reach the 3rem cap — so the defect was never bounded to one width.

   THE MECHANISM: the width axis is NOT computed. `width: 100%` + `repeat(var(--ws-cols), 1fr)`
   makes the browser divide the container the board ACTUALLY has. A vw/clamp formula must restate
   how much page padding sits outside the grid and goes silently wrong when any ancestor changes;
   dividing the real container cannot drift. This is the same argument as Lex, and the same model
   GridGame.razor.css already uses (width:100% + a max-width cap + aspect-ratio).

   THE 2D DECISION. Lattice's board is square (GridSize x GridSize, one gap value on both axes) and
   its cells are square (`aspect-ratio: 1`), so BOARD HEIGHT == BOARD WIDTH identically. That makes
   the height budget expressible as a max-WIDTH — one cap, no second axis to reconcile, and no
   declared box that could disagree with its tracks (the failure memory
   coterie-display-layer-truth-2026-07-16 records for Span). The height axis is the one thing that
   still cannot be read from the real container in CSS, so --ws-chrome-h stays an explicit estimate;
   it is the same 230px the old C# formula used, just written once.

   NO --float-zone-h TERM, DELIBERATELY. Lattice is the one game task 1129 measured with ZERO
   floating-widget occlusion at every width/height swept, and applying the reservation anyway made
   it strictly worse (12 -> 34 unreachable cells). That decision is preserved here; this change does
   not revisit it.

   overflow-x: auto IS GONE. A board that fits needs no escape hatch, and the escape hatch is what
   let the board be too wide in the first place. */
.ws-grid {
    display: grid;

    --ws-gap: 4px;
    --ws-cell-max: 3rem;
    /* THE LEGIBILITY FLOOR — AND IT FLOORS THE HEIGHT-DERIVED CAP, NOT THE CELL.
       Be precise about the guarantee, because the obvious reading is wrong. This value only stops
       the max-width expression below from collapsing; it does NOT stop a NARROW CONTAINER from
       producing a smaller cell, because `width: 100%` still yields to the containing block. At a
       390px viewport the container is 342px and the cells measure 24.8px — genuinely below this
       1.6rem (25.6px). So: floored against a SHORT viewport, not against a narrow one.
       1.6rem is carried over verbatim from the `clamp(1.6rem, ..., 3rem)` in the C# formula this
       replaces, so the change stays about the MECHANISM rather than about sizing. */
    --ws-cell-min: 1.6rem;
    /* Top nav is excluded separately below; this is Lattice's own chrome (game header + timer +
       word-list panel), carried over unchanged from the C# formula this replaces.
       + var(--podium-h, 0px) is task 1401: LatticeGame.razor renders <PodiumPanel> in group mode
       (~120px — see the --podium-h inline style there, mirroring SpanGame.razor's identical
       widget), and this budget never accounted for it. On a short viewport that under-budget let
       the board claim vertical room group mode does not actually leave beneath the header, the
       puzzle-expiry badge and the podium strip. --podium-h is 0px in solo/daily mode, so this is a
       no-op there. */
    --ws-chrome-h: calc(230px + var(--podium-h, 0px));

    grid-template-columns: repeat(var(--ws-cols, 12), 1fr);
    gap: var(--ws-gap);

    /* The width axis: whatever the real container gives us. */
    width: 100%;

    /* THE FLOOR IS NOT OPTIONAL, AND LEAVING IT OUT WAS A REGRESSION.
       The first version of this rule was `max-width: min(desktop-cap, height-cap)` with no floor,
       which is broken in two ways that the tall-viewport test suite could not see:
         - Landscape phone 844x390: 390 - 56 - 230 = 104px of budget across 12 columns and 44px of
           gaps => ~5px cells.
         - Any viewport under ~286px tall (split screen, a short window, a large safe-area inset)
           makes the height term NEGATIVE. A negative max-width clamps to 0 at used-value time, so
           the board would be 0px wide with 144 cells crushed inside it — the "failures render as
           emptiness" mode from memory coterie-display-layer-truth-2026-07-16.
       The deleted C# formula had a 1.6rem floor AND `overflow-x: auto` as its escape. Removing the
       escape hatch was correct — a grid allowed to be wider than its own box is what clipped the
       leading column while still measuring as a fit. Removing the FLOOR alongside it was not.
       max() restores the floor and, as a side effect, makes the negative case unreachable. */
    max-width: max(
        calc(var(--ws-cols, 12) * var(--ws-cell-min) + (var(--ws-cols, 12) - 1) * var(--ws-gap)),
        min(
            calc(var(--ws-cols, 12) * var(--ws-cell-max) + (var(--ws-cols, 12) - 1) * var(--ws-gap)),
            calc(100dvh - var(--topnav-h, 3.5rem) - var(--ws-chrome-h) - env(safe-area-inset-bottom, 0px))
        )
    );
    margin-inline: auto;

    /* Establishes the query container the glyph rule below measures against. Safe here: this
       element's inline size is layout-driven (width:100% + max-width), never content-driven, so
       contain:inline-size cannot collapse it. The only absolutely-positioned descendants are the
       .ws-cell.found::after diagonal connectors, and .ws-cell.found is itself position:relative
       (LatticeGame.razor.css), so their containing block is unchanged. */
    container-type: inline-size;

    padding-bottom: 2px;
}

.ws-cell {
    aspect-ratio: 1;
    background: var(--ws-cell-bg);
    border: 1px solid var(--ws-cell-border);
    display: flex;
    align-items: center;
    justify-content: center;
    /* Allow the track to be narrower than the glyph wants; without this the automatic minimum
       size stops 1fr from shrinking and the board overflows again. */
    min-width: 0;
    /* DO NOT ADD `overflow: hidden` HERE. It was added in the first version of this change, copied
       from Lex where it is a harmless sub-pixel backstop, and it silently broke a feature Lex does
       not have: Lattice draws the connectors between letters of a found word as ::after
       pseudo-elements that are DELIBERATELY PAINTED OUTSIDE the cell box to span the 4px gap
       (LatticeGame.razor.css — `top: -6px; right: -6px`). Clipping applies to descendants
       including z-index:-1 pseudo-elements, so every diagonal connector was cut down to the
       quarter that happened to fall inside the cell.
       The glyph does not need the backstop anyway: font-size is derived from the same division the
       grid performs and is bounded by --ws-cell-min above, so it cannot outgrow its box. */
    /* Fallback for browsers without container query units (pre-Chrome 105 / Safari 16): they drop
       the cqw declaration below as unparseable and land here. 0.8rem fits the tightest cell this
       layout produces. */
    font-size: 0.8rem;
    /* The same division the grid just did, expressed where font-size can reach it. max() is a
       guard, not a preference: if the container query ever resolves to 0 the calc goes negative
       and the glyph computes to 0px — an invisible letter in a visible box. A too-large glyph is
       obvious; an absent one is not. */
    font-size: max(
        0.6rem,
        min(
            1.25rem,
            calc((100cqw - (var(--ws-cols, 12) - 1) * var(--ws-gap)) / var(--ws-cols, 12) * 0.55)
        )
    );
    font-weight: 600;
    color: var(--ws-text);
    text-transform: uppercase;
    cursor: pointer;
    user-select: none;
    touch-action: manipulation;
    transition: all 0.2s ease;
}

.ws-cell.active {
    background: var(--ws-cell-active-bg);
    color: var(--ws-cell-active-text);
    border-color: var(--ws-cell-active-bg);
    transform: scale(1.05);
}

.ws-cell.found {
    background: var(--ws-cell-found-bg);
    color: var(--ws-cell-found-text);
    border-color: var(--ws-cell-found-bg);
}

.ws-word-list {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    justify-content: center;
    max-width: 600px;
    padding: 1rem;
    background: var(--surface-color);
    border-radius: var(--border-radius-lg);
}

.ws-word {
    font-size: 1.2rem;
    font-weight: 600;
    color: var(--ws-text);
    padding: 0.25rem 0.5rem;
    border-radius: var(--border-radius-sm);
    transition: all 0.3s ease;
}

.ws-word.found {
    color: var(--ws-word-text);
    text-decoration: line-through;
    opacity: 0.6;
}

/* =============================================================
   Global Table Reset (Bootstrap Override)
   ============================================================= */
.table,
.table> :not(caption)>*>* {
    background-color: transparent !important;
    --bs-table-bg: transparent !important;
    --bs-table-accent-bg: transparent !important;
    --bs-table-striped-bg: transparent !important;
    --bs-table-hover-bg: transparent !important;
    color: var(--text-primary) !important;
    border-color: var(--glass-border) !important;
}

.table thead th {
    color: var(--text-secondary) !important;
    border-bottom: 1px solid var(--glass-border) !important;
}

/* =============================================================
   §8 Theming Utility Classes (replace inline styles)
   ============================================================= */

/* Loading spinner containers */
.loading-container {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    height: var(--loading-container-height, 400px);
}

.spinner-lg {
    width: var(--loading-spinner-size, 3rem);
    height: var(--loading-spinner-size, 3rem);
}

.spinner-xs {
    width: 0.75rem;
    height: 0.75rem;
}

/* Game Over icon and title (Lex loss state) */
.game-over-icon {
    color: var(--lose-title-color);
    filter: drop-shadow(0 0 10px var(--accent-glow));
}

.game-over-title {
    background: var(--lose-title-gradient);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
}

/* Error boundary detail panel */
.error-detail-panel {
    max-height: 400px;
    overflow-y: auto;
    background: var(--surface-color);
    padding: 10px;
    border-radius: var(--border-radius-sm);
}

.error-detail-pre {
    font-size: 0.8em;
    margin: 0;
    color: var(--text-primary);
    white-space: pre-wrap;
}

/* Nav helpers */
.nav-bottom-pad {
    padding-bottom: 2rem;
}

.nav-group-label {
    color: var(--text-secondary);
    font-size: 0.75rem;
    letter-spacing: 0.05em;
    margin-bottom: 8px;
}

/* Log table column widths */
.log-col-timestamp {
    width: 200px;
}

.log-col-level {
    width: 100px;
}

/* =============================================================
   §10 Group Theme Manager UI
   ============================================================= */

.theme-manager {
    z-index: 100;
}

.theme-card-full {
    padding: 1.5rem;
    border: 1px solid var(--glass-border-bright);
}

.theme-gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 1.25rem;
    margin-top: 1rem;
}

.theme-item {
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--border-radius-lg);
    overflow: hidden;
    cursor: pointer;
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    position: relative;
    display: flex;
    flex-direction: column;
}

.theme-item:hover {
    transform: translateY(-4px);
    border-color: var(--accent-primary);
    box-shadow: 0 10px 20px -5px rgba(0, 0, 0, 0.4);
}

.theme-item.active {
    border-color: var(--accent-primary);
    border-width: 2px;
    background: rgba(255, 255, 255, 0.05);
}

.theme-preview {
    height: 80px;
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}

.theme-preview .accent-stripe {
    position: absolute;
    top: 0;
    left: 0;
    width: 6px;
    height: 100%;
}

.theme-preview .surface-preview {
    width: 60%;
    height: 40%;
    border-radius: 4px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}

.theme-info {
    padding: 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.theme-name {
    font-weight: 600;
    font-size: 0.95rem;
    color: var(--text-primary);
}

.theme-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.small-badge {
    font-size: 0.65rem;
    padding: 0.2rem 0.5rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 700;
}

@keyframes animate-slide-down {
    from {
        opacity: 0;
        transform: translateY(-20px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.animate-slide-down {
    animation: animate-slide-down 0.4s ease-out forwards;
}

/* --- Theme Editor Modal --- */
.theme-editor-modal {
    max-width: 450px !important;
}

.theme-editor-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.5rem;
    margin-top: 1.5rem;
}

.editor-field {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.editor-field label {
    font-size: 0.85rem;
    font-weight: 600;
    opacity: 0.8;
}

.editor-field input[type="color"] {
    width: 100%;
    height: 40px;
    border: 1px solid var(--glass-border);
    border-radius: 6px;
    background: var(--glass-bg);
    cursor: pointer;
}

.theme-preview-large {
    padding: 2rem;
    border-radius: var(--border-radius-lg);
    border: 2px solid transparent;
    transition: all 0.3s ease;
}

.preview-surface {
    padding: 1.5rem;
    border-radius: var(--border-radius-md);
    position: relative;
    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}

.preview-accent {
    position: absolute;
    top: 0;
    left: 0;
    width: 4px;
    height: 100%;
    border-radius: 4px 0 0 4px;
}


.preview-sub {
    font-size: 0.85rem;
    opacity: 0.7;
    margin-top: 0.25rem;
}

/* =============================================================
   Bootstrap Overrides for Dynamic Theming
   ============================================================= */
.btn-primary {
    background-color: var(--accent-primary) !important;
    border-color: var(--accent-primary) !important;
    color: var(--text-on-accent, #ffffff) !important;
}

.btn-primary:hover,
.btn-primary:focus,
.btn-primary:active {
    background-color: var(--accent-primary) !important;
    border-color: var(--accent-primary) !important;
    color: var(--text-on-accent, #ffffff) !important;
    filter: brightness(1.2);
}

.bg-primary {
    background-color: var(--accent-primary) !important;
}

.text-primary {
    color: var(--accent-primary) !important;
}

.badge.bg-primary-soft {
    background-color: color-mix(in srgb, var(--accent-primary) 20%, transparent);
    color: var(--accent-primary);
}
/* SW handshake update banner — shown by SwUpdatePrompt.razor when a new service worker
   is waiting. Matches the app's dark theme; sits at the top of the viewport above all content. */
.coterie-sw-update-banner {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 10001;
    background: var(--accent-primary, #3b82f6);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    padding: 10px 16px;
    font-size: 0.875rem;
    font-weight: 600;
    box-shadow: 0 2px 8px rgba(0,0,0,0.35);
}

.coterie-sw-update-banner__text {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex: 1;
    min-width: 0;
}

.coterie-sw-update-banner__btn {
    background: rgba(255,255,255,0.22);
    border: 1px solid rgba(255,255,255,0.55);
    color: #fff;
    border-radius: var(--border-radius-sm, 0.375rem);
    padding: 4px 16px;
    font-size: 0.825rem;
    font-weight: 700;
    cursor: pointer;
    white-space: nowrap;
    min-height: 34px;
    transition: background 0.15s;
}

.coterie-sw-update-banner__btn:hover,
.coterie-sw-update-banner__btn:focus-visible {
    background: rgba(255,255,255,0.38);
    outline: 2px solid rgba(255,255,255,0.7);
    outline-offset: 1px;
}

.coterie-sw-update-banner__dismiss {
    background: none;
    border: none;
    color: rgba(255,255,255,0.75);
    cursor: pointer;
    padding: 4px 6px;
    border-radius: var(--border-radius-sm, 0.375rem);
    display: flex;
    align-items: center;
    min-height: 34px;
    transition: color 0.15s;
}

.coterie-sw-update-banner__dismiss:hover,
.coterie-sw-update-banner__dismiss:focus-visible {
    color: #fff;
    outline: 2px solid rgba(255,255,255,0.5);
    outline-offset: 1px;
}

.offline-banner {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 9999;
    background: #b45309;
    color: #fff;
    text-align: center;
    padding: 8px 16px;
    font-size: 0.875rem;
    font-weight: 600;
    letter-spacing: 0.01em;
}

/* Boot splash — shown until Blazor mounts and replaces #app contents.
   Full-viewport so there's no white flash on the SWA before WASM loads. */
.coterie-splash {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg-color, #0f172a);
}

.coterie-splash img {
    width: clamp(96px, 22vmin, 192px);
    height: auto;
    border-radius: 22%;
    animation: coterie-splash-pulse 1.8s ease-in-out infinite;
}

@keyframes coterie-splash-pulse {
    0%, 100% { opacity: 0.75; transform: scale(0.97); }
    50%      { opacity: 1;    transform: scale(1); }
}

