/* ==========================================================
   Clipping Channel Dashboard — Dark Glassmorphism Theme
   ========================================================== */

/* --- CSS Variables / Design Tokens --- */
:root {
    /* Background */
    --bg-primary: #0a0a0f;
    --bg-secondary: #12121a;
    --bg-tertiary: #1a1a28;
    --bg-elevated: rgba(255, 255, 255, 0.03);
    
    /* Glass */
    --glass-bg: rgba(255, 255, 255, 0.04);
    --glass-border: rgba(255, 255, 255, 0.08);
    --glass-hover: rgba(255, 255, 255, 0.07);
    --glass-active: rgba(255, 255, 255, 0.10);
    --glass-blur: 20px;

    /* Text */
    --text-primary: #f0f0f5;
    --text-secondary: #8888a0;
    --text-tertiary: #55556a;
    --text-inverse: #0a0a0f;

    /* Accent Colors */
    --accent-primary: #7c5cff;       /* Purple */
    --accent-primary-glow: rgba(124, 92, 255, 0.25);
    --accent-secondary: #00d4aa;     /* Teal */
    --accent-secondary-glow: rgba(0, 212, 170, 0.2);
    --accent-warning: #ffb340;
    --accent-danger: #ff4757;
    --accent-success: #2ed573;
    --accent-info: #5f9cff;

    /* AI Brand Colors */
    --gemini-color: #4285f4;
    --ollama-color: #00d4aa;

    /* Gradients */
    --gradient-primary: linear-gradient(135deg, #7c5cff 0%, #5c8cff 50%, #00d4aa 100%);
    --gradient-card: linear-gradient(145deg, rgba(124, 92, 255, 0.08), rgba(0, 212, 170, 0.04));
    --gradient-glow: radial-gradient(ellipse at center, rgba(124, 92, 255, 0.15) 0%, transparent 70%);

    /* Spacing */
    --space-xs: 4px;
    --space-sm: 8px;
    --space-md: 16px;
    --space-lg: 24px;
    --space-xl: 32px;
    --space-2xl: 48px;

    /* Radii */
    --radius-sm: 8px;
    --radius-md: 12px;
    --radius-lg: 16px;
    --radius-xl: 20px;
    --radius-full: 9999px;

    /* Shadows */
    --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3);
    --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4);
    --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.5);
    --shadow-glow: 0 0 30px var(--accent-primary-glow);

    /* Typography */
    --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --font-size-xs: 0.75rem;
    --font-size-sm: 0.8125rem;
    --font-size-base: 0.875rem;
    --font-size-md: 1rem;
    --font-size-lg: 1.25rem;
    --font-size-xl: 1.5rem;
    --font-size-2xl: 2rem;
    --font-size-3xl: 2.5rem;

    /* Transitions */
    --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
    --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
    --transition-slow: 400ms cubic-bezier(0.4, 0, 0.2, 1);

    /* Layout */
    --content-max: 1180px;      /* one measure for every page */
    --sidebar-width: 240px;
    --sidebar-collapsed: 72px;
}


/* --- Reset & Base --- */
*, *::before, *::after {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html {
    font-size: 16px;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

body {
    font-family: var(--font-family);
    background: var(--bg-primary);
    color: var(--text-primary);
    line-height: 1.6;
    overflow: hidden;
    height: 100vh;
}

a {
    color: inherit;
    text-decoration: none;
}

button {
    font-family: inherit;
    cursor: pointer;
    border: none;
    outline: none;
}

input, textarea, select {
    font-family: inherit;
    outline: none;
}

::-webkit-scrollbar {
    width: 6px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background: var(--glass-border);
    border-radius: var(--radius-full);
}

::-webkit-scrollbar-thumb:hover {
    background: rgba(255, 255, 255, 0.15);
}


/* --- Animated Background --- */
.bg-gradient {
    position: fixed;
    inset: 0;
    z-index: 0;
    background:
        radial-gradient(ellipse 80% 50% at 20% 20%, rgba(124, 92, 255, 0.08) 0%, transparent 60%),
        radial-gradient(ellipse 60% 40% at 80% 80%, rgba(0, 212, 170, 0.06) 0%, transparent 60%),
        radial-gradient(ellipse 50% 50% at 50% 50%, rgba(95, 156, 255, 0.04) 0%, transparent 60%);
    animation: bgShift 20s ease-in-out infinite alternate;
}

@keyframes bgShift {
    0% { opacity: 1; transform: scale(1); }
    50% { opacity: 0.8; transform: scale(1.05); }
    100% { opacity: 1; transform: scale(1); }
}

.bg-noise {
    position: fixed;
    inset: 0;
    z-index: 0;
    opacity: 0.015;
    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
    pointer-events: none;
}


/* --- App Layout --- */
.app {
    display: flex;
    height: 100vh;
    position: relative;
    z-index: 1;
}


/* --- Sidebar --- */
.sidebar {
    width: var(--sidebar-width);
    height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--glass-bg);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    border-right: 1px solid var(--glass-border);
    padding: var(--space-lg) var(--space-md);
    flex-shrink: 0;
    transition: width var(--transition-base);
}

.sidebar-logo {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-sm);
    margin-bottom: var(--space-2xl);
}

.logo-icon {
    font-size: 1.75rem;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--gradient-primary);
    border-radius: var(--radius-md);
    animation: logoPulse 3s ease-in-out infinite;
}

@keyframes logoPulse {
    0%, 100% { box-shadow: 0 0 0 0 var(--accent-primary-glow); }
    50% { box-shadow: 0 0 20px 5px var(--accent-primary-glow); }
}

.logo-text {
    font-size: var(--font-size-lg);
    font-weight: 800;
    background: var(--gradient-primary);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    letter-spacing: -0.5px;
}

.nav-links {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    flex: 1;
}

.nav-link {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-md);
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-size: var(--font-size-base);
    font-weight: 500;
    transition: all var(--transition-fast);
    position: relative;
}

.nav-link:hover {
    color: var(--text-primary);
    background: var(--glass-hover);
}

.nav-link.active {
    color: var(--text-primary);
    background: var(--glass-active);
}

.nav-link.active::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 60%;
    background: var(--gradient-primary);
    border-radius: var(--radius-full);
}

.nav-link svg {
    flex-shrink: 0;
    opacity: 0.7;
    transition: opacity var(--transition-fast);
}

.nav-link:hover svg,
.nav-link.active svg {
    opacity: 1;
}

/* Sidebar Footer — AI Status */
.sidebar-footer {
    padding-top: var(--space-lg);
    border-top: 1px solid var(--glass-border);
}

.ai-status {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.ai-badge {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-xs) var(--space-md);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--text-tertiary);
}

.ai-badge .dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--text-tertiary);
    transition: background var(--transition-base);
}

.ai-badge.gemini.connected .dot {
    background: var(--gemini-color);
    box-shadow: 0 0 8px var(--gemini-color);
}

.ai-badge.ollama.connected .dot {
    background: var(--ollama-color);
    box-shadow: 0 0 8px var(--ollama-color);
}


/* --- Main Content --- */
/* The banner and the page share one vertical column. .app is display:flex,
   so <main> cannot simply gain a sibling — that sibling would become another
   horizontal child and sit BESIDE the content. */
.content-column {
    flex: 1;
    min-width: 0;
    height: 100vh;
    display: flex;
    flex-direction: column;
}

/* One measure for every page.
   Pages used to fill whatever width the window had, so Analytics stretched
   its tiles across 1440px while Profiles stopped at ~350px and left half the
   screen empty. The side padding grows instead of the content, which keeps
   the scrollbar on the window edge where it belongs — a max-width on this
   element would pull the scrollbar inward with it. */
.content {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: var(--space-xl) max(var(--space-xl), (100% - var(--content-max)) / 2);
}


/* --- Glass Card --- */
.card {
    background: var(--glass-bg);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-lg);
    padding: var(--space-lg);
    transition: all var(--transition-base);
}

.card:hover {
    border-color: rgba(255, 255, 255, 0.12);
    box-shadow: var(--shadow-sm);
}

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

.card-title {
    font-size: var(--font-size-md);
    font-weight: 700;
    color: var(--text-primary);
}

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


/* --- Stats Grid (Dashboard) --- */
/* Both pages that use this render SIX tiles, and auto-fit laid them out
   five-then-one — an orphan tile on its own row, which is what made the
   Analytics header read as unfinished. Three columns divides six evenly and
   still fits the numbers comfortably; narrower windows fall back to auto-fit. */
.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
    gap: var(--space-md);
    margin-bottom: var(--space-xl);
}

@media (min-width: 1000px) {
    .stats-grid { grid-template-columns: repeat(3, 1fr); }
}

.stat-card {
    background: var(--glass-bg);
    backdrop-filter: blur(var(--glass-blur));
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-lg);
    padding: var(--space-lg);
    transition: all var(--transition-base);
    position: relative;
    overflow: hidden;
}

.stat-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    background: var(--gradient-primary);
    opacity: 0;
    transition: opacity var(--transition-base);
}

.stat-card:hover::before {
    opacity: 1;
}

.stat-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-md);
}

.stat-value {
    font-size: var(--font-size-2xl);
    font-weight: 800;
    background: var(--gradient-primary);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
    line-height: 1.2;
}

.stat-label {
    font-size: var(--font-size-sm);
    color: var(--text-secondary);
    font-weight: 500;
    margin-top: var(--space-xs);
}

.stat-icon {
    position: absolute;
    right: var(--space-lg);
    top: 50%;
    transform: translateY(-50%);
    font-size: 2rem;
    opacity: 0.15;
}


/* --- Buttons --- */
.btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-lg);
    border-radius: var(--radius-md);
    font-size: var(--font-size-base);
    font-weight: 600;
    transition: all var(--transition-fast);
    cursor: pointer;
    border: 1px solid transparent;
    white-space: nowrap;
}

.btn-primary {
    background: var(--gradient-primary);
    color: white;
    box-shadow: var(--shadow-glow);
}

.btn-primary:hover {
    transform: translateY(-1px);
    box-shadow: 0 0 40px var(--accent-primary-glow);
}

.btn-primary:active {
    transform: translateY(0);
}

.btn-secondary {
    background: var(--glass-bg);
    border-color: var(--glass-border);
    color: var(--text-primary);
}

.btn-secondary:hover {
    background: var(--glass-hover);
    border-color: rgba(255, 255, 255, 0.15);
}

.btn-danger {
    background: rgba(255, 71, 87, 0.15);
    border-color: rgba(255, 71, 87, 0.3);
    color: var(--accent-danger);
}

.btn-danger:hover {
    background: rgba(255, 71, 87, 0.25);
}

.btn-sm {
    padding: var(--space-xs) var(--space-md);
    font-size: var(--font-size-xs);
    border-radius: var(--radius-sm);
}

.btn-icon {
    width: 36px;
    height: 36px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-sm);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    color: var(--text-secondary);
    transition: all var(--transition-fast);
}

.btn-icon:hover {
    color: var(--text-primary);
    background: var(--glass-hover);
}


/* --- Input Fields --- */
.input-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}

/* Sentence case, matching the labels Profiles and Analytics already use.
   Uppercase + letter-spacing made the same field label look like a different
   component depending on which page it appeared on. */
.input-label {
    font-size: var(--font-size-sm);
    font-weight: 600;
    color: var(--text-secondary);
    letter-spacing: 0;
}

.input {
    background: var(--bg-secondary);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-md);
    padding: var(--space-sm) var(--space-md);
    color: var(--text-primary);
    font-size: var(--font-size-base);
    transition: all var(--transition-fast);
}

.input:focus {
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 3px var(--accent-primary-glow);
}

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

/* Compact inputs for editing in place — the profile card is an editor, not a
   form, so its controls have to sit inside a dense definition grid. */
.input-sm {
    width: 100%;
    padding: 4px 8px;
    font-size: var(--font-size-xs);
    border-radius: var(--radius-sm);
}

textarea.input-sm {
    resize: vertical;
    line-height: 1.4;
}

.input-url {
    width: 100%;
    padding: var(--space-md) var(--space-lg);
    font-size: var(--font-size-md);
    border-radius: var(--radius-lg);
    background: var(--bg-secondary);
    border: 2px solid var(--glass-border);
}

.input-url:focus {
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 4px var(--accent-primary-glow);
}


/* --- Status Badges --- */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 10px;
    border-radius: var(--radius-full);
    font-size: var(--font-size-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.badge-queued { background: rgba(95, 156, 255, 0.15); color: var(--accent-info); }
.badge-downloading,
.badge-transcribing,
.badge-detecting,
.badge-writing,
.badge-editing { background: rgba(124, 92, 255, 0.15); color: var(--accent-primary); }
.badge-ready { background: rgba(46, 213, 115, 0.15); color: var(--accent-success); }
.badge-published { background: rgba(0, 212, 170, 0.15); color: var(--accent-secondary); }
/* Caption/hashtags written to disk, but NOT uploaded — needs a manual post. */
.badge-prepared { background: rgba(255, 179, 64, 0.15); color: var(--accent-warning); }
.badge-scheduled { background: rgba(95, 156, 255, 0.15); color: var(--accent-info); }
.badge-failed { background: rgba(255, 71, 87, 0.15); color: var(--accent-danger); }
.badge-rejected { background: rgba(255, 179, 64, 0.15); color: var(--accent-warning); }

/* Processing badge animation */
.badge-downloading::before,
.badge-transcribing::before,
.badge-detecting::before,
.badge-writing::before,
.badge-editing::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    animation: pulse 1.5s ease-in-out infinite;
}

@keyframes pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50% { opacity: 0.5; transform: scale(0.7); }
}


/* --- Score Badge --- */
.score-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
    font-weight: 800;
    border: 2px solid;
}

.score-high { border-color: var(--accent-success); color: var(--accent-success); background: rgba(46, 213, 115, 0.1); }
.score-mid { border-color: var(--accent-warning); color: var(--accent-warning); background: rgba(255, 179, 64, 0.1); }
.score-low { border-color: var(--accent-danger); color: var(--accent-danger); background: rgba(255, 71, 87, 0.1); }
/* Not judged. Deliberately neutral rather than red: no verdict is not a bad
   verdict, and .score-badge sets `border: 2px solid` with no colour, so a row
   with no modifier class would take whatever colour it inherited. */
.score-none { border-color: var(--text-secondary); color: var(--text-secondary); background: transparent; }


/* --- Clip List --- */
.clip-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.clip-item {
    display: grid;
    grid-template-columns: auto 1fr auto auto auto;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-md) var(--space-lg);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-md);
    transition: all var(--transition-fast);
    cursor: pointer;
}

.clip-item:hover {
    background: var(--glass-hover);
    border-color: rgba(255, 255, 255, 0.12);
    transform: translateX(4px);
}

.clip-item-info {
    min-width: 0;
}

.clip-item-title {
    font-weight: 600;
    font-size: var(--font-size-base);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.clip-item-meta {
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
    display: flex;
    gap: var(--space-md);
    margin-top: 2px;
}

.clip-item-actions {
    display: flex;
    gap: var(--space-xs);
    opacity: 0;
    transition: opacity var(--transition-fast);
}

.clip-item:hover .clip-item-actions {
    opacity: 1;
}

/* --- Clip Queue list (replaces the single-row layout on /queue only) ---
   One bordered panel with hairline row dividers, not a stack of individually
   bordered/margined cards — at 50 rows the per-row chrome (border + radius +
   margin) was most of what made the page feel long and busy. */
.queue-panel {
    background: var(--glass-bg);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.queue-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    flex-wrap: wrap;
    padding: var(--space-sm) var(--space-md);
    border-bottom: 1px solid var(--glass-border);
}

.queue-columns {
    display: grid;
    grid-template-columns: 28px 32px 1fr auto 108px;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-xs) var(--space-md);
    font-size: 0.6875rem;
    font-weight: 600;
    color: var(--text-tertiary);
    text-transform: uppercase;
    letter-spacing: 0.6px;
    border-bottom: 1px solid var(--glass-border);
}

.queue-card {
    border-bottom: 1px solid var(--glass-border);
    transition: background var(--transition-fast);
}

.queue-card:last-child {
    border-bottom: none;
}

.queue-card:hover {
    background: var(--glass-hover);
}

.queue-card-summary {
    display: grid;
    /* SIX tracks, one per direct child a full pending row renders: checkbox,
     * score badge, info, verdict chip, status badge, actions.
     *
     * This was five. The sixth child had no track, so it wrapped into an
     * implicit second row at column 1 — the row actions rendered under the
     * title, half off the card, and could not be clicked at all. Every check
     * this project had inspected pieces; nothing pressed the button. Found
     * 2026-08-14 by scripts/dashboard_walkthrough.js on its first attempt to
     * click Approve. */
    grid-template-columns: 28px 32px 1fr auto 108px auto;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-md);
    cursor: pointer;
}

/* Pinned to the last track rather than left to auto-placement, because the
 * decided sections render a row with fewer children (no verdict chip, no
 * status badge). Auto-placed, the actions would slide left into the verdict
 * chip's track and sit in a different place on every kind of row. */
.queue-card-summary > .queue-card-actions {
    grid-column: -2 / -1;
}

.queue-card-summary .score-badge {
    width: 32px;
    height: 32px;
    font-size: 0.6875rem;
    border-width: 1px;
}

.queue-card-title {
    font-weight: 600;
    font-size: var(--font-size-sm);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.queue-card-meta {
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
    display: flex;
    gap: var(--space-sm);
    margin-top: 2px;
    align-items: center;
}

.cta-chip {
    display: inline-flex;
    align-items: center;
    padding: 1px var(--space-sm);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-xs);
    background: rgba(0, 212, 170, 0.12);
    color: var(--accent-secondary);
    max-width: 220px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.cta-chip-none {
    background: transparent;
    color: var(--text-tertiary);
    border: 1px dashed var(--glass-border);
}

/* Clips the viral judge scored below the quality bar. They still render and
   publish — nothing is dropped — but a bad batch should be spottable at a
   glance so it can be bulk-rejected instead of reviewed one clip at a time. */
/* Base for the per-clip worth-posting chip; the variants below only recolour
   it. Replaced the old single-purpose .weak-chip, which had one state ("this
   is bad") and so could not say "this one is why you ran the job". */
.verdict-chip {
    display: inline-flex;
    align-items: center;
    padding: 1px var(--space-sm);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-xs);
    white-space: nowrap;
    background: rgba(245, 158, 11, 0.14);
    color: #f59e0b;
}

.verdict-chip.verdict-post {
    background: rgba(34, 197, 94, 0.14);
    color: #22c55e;
}

.verdict-chip.verdict-maybe {
    background: rgba(148, 163, 184, 0.16);
    color: #94a3b8;
}

.verdict-chip.verdict-skip {
    background: rgba(245, 158, 11, 0.14);
    color: #f59e0b;
}

.queue-card-actions {
    display: flex;
    justify-content: flex-end;
    gap: 2px;
}

.row-action {
    background: transparent;
    border: 1px solid transparent;
    padding: 4px var(--space-sm);
    font-size: var(--font-size-xs);
    font-weight: 600;
    border-radius: var(--radius-sm);
    transition: all var(--transition-fast);
    color: var(--text-secondary);
}

.row-action-approve:hover {
    color: var(--accent-success);
    background: rgba(46, 213, 115, 0.1);
}

.row-action-reject:hover {
    color: var(--accent-danger);
    background: rgba(255, 71, 87, 0.1);
}

.row-action-unapprove:hover {
    color: var(--text-primary);
    background: rgba(255, 255, 255, 0.06);
}

.row-action-publish:hover,
.row-action-export:hover {
    color: var(--accent-primary);
    background: var(--accent-primary-glow);
}

.queue-card-detail {
    padding: 0 var(--space-md);
    border-top: 0 solid var(--glass-border);
    display: none;
}

.queue-card.expanded .queue-card-detail {
    padding: var(--space-sm) var(--space-md) var(--space-md);
    border-top-width: 1px;
    display: block;
}


/* --- Video Preview --- */
.preview-container {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-xl);
}

.preview-video-wrapper {
    width: 320px;
    aspect-ratio: 9/16;
    border-radius: var(--radius-lg);
    overflow: hidden;
    background: var(--bg-secondary);
    border: 2px solid var(--glass-border);
    position: relative;
}

.preview-video-wrapper video {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.preview-details {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
}


/* --- Page Headers --- */
.page-header {
    margin-bottom: var(--space-xl);
}

.page-title {
    font-size: var(--font-size-xl);
    font-weight: 800;
    letter-spacing: -0.5px;
}

.page-description {
    font-size: var(--font-size-base);
    color: var(--text-secondary);
    margin-top: var(--space-xs);
}


/* --- Ingest Page — URL Input Area --- */
.ingest-hero {
    text-align: center;
    padding: var(--space-2xl) 0;
}

.ingest-hero h2 {
    font-size: var(--font-size-2xl);
    font-weight: 800;
    margin-bottom: var(--space-sm);
}

.ingest-hero p {
    color: var(--text-secondary);
    margin-bottom: var(--space-xl);
}

.ingest-input-wrapper {
    display: flex;
    gap: var(--space-md);
    max-width: 700px;
    margin: 0 auto;
}

.ingest-input-wrapper .input-url {
    flex: 1;
}


/* --- Progress Bar --- */
.progress-bar {
    width: 100%;
    height: 4px;
    background: var(--bg-tertiary);
    border-radius: var(--radius-full);
    overflow: hidden;
}

.progress-bar-fill {
    height: 100%;
    background: var(--gradient-primary);
    border-radius: var(--radius-full);
    transition: width var(--transition-base);
    position: relative;
}

.progress-bar-fill::after {
    content: '';
    position: absolute;
    right: 0;
    top: 0;
    bottom: 0;
    width: 30px;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3));
    animation: progressShimmer 1.5s ease-in-out infinite;
}

@keyframes progressShimmer {
    0% { opacity: 0; }
    50% { opacity: 1; }
    100% { opacity: 0; }
}


/* --- Empty State --- */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: var(--space-2xl);
    text-align: center;
    min-height: 300px;
}

.empty-state-icon {
    font-size: 3rem;
    margin-bottom: var(--space-lg);
    opacity: 0.3;
}

.empty-state-title {
    font-size: var(--font-size-lg);
    font-weight: 700;
    margin-bottom: var(--space-sm);
}

.empty-state-text {
    color: var(--text-secondary);
    max-width: 400px;
}


/* --- Toast Notifications --- */
.toast-container {
    position: fixed;
    bottom: var(--space-xl);
    right: var(--space-xl);
    z-index: 1000;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.toast {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-md) var(--space-lg);
    background: var(--bg-secondary);
    backdrop-filter: blur(var(--glass-blur));
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    min-width: 300px;
    animation: toastSlideIn 0.3s ease-out;
    font-size: var(--font-size-sm);
}

.toast.toast-exit {
    animation: toastSlideOut 0.3s ease-in forwards;
}

@keyframes toastSlideIn {
    from { transform: translateX(100%); opacity: 0; }
    to { transform: translateX(0); opacity: 1; }
}

@keyframes toastSlideOut {
    from { transform: translateX(0); opacity: 1; }
    to { transform: translateX(100%); opacity: 0; }
}

.toast-success { border-left: 3px solid var(--accent-success); }
.toast-error { border-left: 3px solid var(--accent-danger); }
.toast-info { border-left: 3px solid var(--accent-info); }
.toast-warning { border-left: 3px solid var(--accent-warning); }


/* --- Loading Spinner --- */
.spinner {
    width: 20px;
    height: 20px;
    border: 2px solid var(--glass-border);
    border-top-color: var(--accent-primary);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}


/* --- Responsive --- */
@media (max-width: 1024px) {
    .sidebar {
        width: var(--sidebar-collapsed);
    }

    .logo-text,
    .nav-link span {
        display: none;
    }

    .sidebar-logo {
        justify-content: center;
    }

    .nav-link {
        justify-content: center;
        padding: var(--space-sm);
    }

    .ai-badge span:not(.dot) {
        display: none;
    }

    .preview-container {
        grid-template-columns: 1fr;
    }

    .preview-layout {
        grid-template-columns: 1fr;
    }

    .preview-sidebar {
        max-height: 40vh;
    }

    .preview-video-wrapper {
        width: 100%;
        max-width: 320px;
        margin: 0 auto;
    }
}

@media (max-width: 768px) {
    .stats-grid {
        grid-template-columns: repeat(2, 1fr);
    }

    .content {
        padding: var(--space-md);
    }

    .ingest-input-wrapper {
        flex-direction: column;
    }
}


/* --- Utility Classes --- */
.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-sm { gap: var(--space-sm); }
.gap-md { gap: var(--space-md); }
.gap-lg { gap: var(--space-lg); }
.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); }
/* The margin utilities were top-only until the posting-plan block needed a
   heading spaced off the grid beneath it. An undefined utility class fails
   silently and still looks plausible -- .input-sm sat undefined for a day
   exactly that way. */
.mb-sm { margin-bottom: var(--space-sm); }
.text-secondary { color: var(--text-secondary); }
.text-tertiary { color: var(--text-tertiary); }
.text-xs { font-size: var(--font-size-xs); }
.text-sm { font-size: var(--font-size-sm); }
.font-bold { font-weight: 700; }
.truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* --- Discover page: trending chips --- */
.trend-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: var(--space-md);
}
.trend-chip {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    text-align: left;
    padding: var(--space-md);
    border-radius: var(--radius-md);
    border: 1px solid var(--glass-border);
    background: var(--glass-bg);
    cursor: pointer;
    transition: all 0.15s ease;
}
.trend-chip:hover {
    background: var(--glass-hover);
    border-color: var(--accent-primary);
    transform: translateY(-2px);
}
.trend-title {
    font-weight: 600;
    color: var(--text-primary);
    font-size: var(--font-size-sm);
}
.trend-traffic {
    font-size: var(--font-size-xs);
    color: var(--accent-secondary);
    font-weight: 600;
}
.trend-news {
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* --- Discover page: ranked candidates --- */
.candidate-item { margin-bottom: var(--space-sm); }
.candidate-item .clip-item-actions { gap: var(--space-sm); }

/* --- Preview page: grouped sidebar + player --- */
.preview-layout {
    display: grid;
    grid-template-columns: 320px 1fr;
    gap: var(--space-xl);
    align-items: start;
}

.preview-sidebar {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    max-height: calc(100vh - 180px);
    overflow-y: auto;
}

/*
 * The sidebar is a COLUMN FLEX container with a capped height, so every
 * .section child inherits flex-shrink: 1 and is squashed BELOW its own content
 * once enough groups exist -- and because .section sets overflow: hidden, the
 * rows are then CLIPPED rather than allowed to overflow. Measured at 9 groups:
 * the open group's box was 83.5px while its header alone was 79px and its rows
 * ran to 278px, so row 1 painted ~3px tall and elementFromPoint fell straight
 * through to the sidebar. All 75 clips were in the DOM and none of the top
 * group could be clicked. The sidebar's own overflow-y: auto cannot scroll
 * until the children stop shrinking. Same family as the .queue-card-summary
 * track bug -- see HANDOVER section 4.
 */
.preview-sidebar > .section { flex-shrink: 0; }

.preview-clip-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/*
 * FOUR tracks for FOUR children (verdict, score, title, tag) -- a track per
 * MAXIMUM child count, not the typical one. A grid silently auto-places the
 * child it has no track for into an implicit row, which is how the queue's
 * Approve button spent months painting half off the card and failing every
 * hit test while looking fine. See HANDOVER section 4.
 */
.preview-clip-row {
    display: grid;
    grid-template-columns: auto auto 1fr auto;
    gap: var(--space-sm);
    align-items: center;
    width: 100%;
    text-align: left;
    padding: var(--space-sm) var(--space-md);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
    font-size: var(--font-size-xs);
    transition: all 0.15s ease;
}

.preview-clip-row:hover {
    background: var(--glass-hover);
    color: var(--text-primary);
}

.preview-clip-row.active {
    background: var(--glass-bg);
    border-color: var(--accent-primary);
    color: var(--text-primary);
}

.preview-row-score {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    min-width: 2.2em;
}

.preview-row-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.preview-main {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
}

/* The verdict banner: the AI's call, stated rather than implied by a colour. */
.preview-verdict {
    padding: var(--space-md) var(--space-lg);
    border-radius: var(--radius-lg);
    border: 1px solid var(--glass-border);
    border-left-width: 4px;
    background: var(--glass-bg);
}

.preview-verdict[data-tone="post"]     { border-left-color: var(--success, #22c55e); }
.preview-verdict[data-tone="maybe"]    { border-left-color: var(--warning, #f59e0b); }
.preview-verdict[data-tone="skip"]     { border-left-color: var(--danger, #ef4444); }
.preview-verdict[data-tone="unjudged"] { border-left-color: var(--glass-border); }

.preview-verdict-head {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    align-items: center;
}

.preview-verdict-label {
    font-weight: 700;
    color: var(--text-primary);
}

.preview-verdict-score {
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
    font-size: var(--font-size-xs);
}

.preview-verdict-reason {
    margin-top: var(--space-sm);
    color: var(--text-secondary);
    font-size: var(--font-size-sm);
    line-height: 1.5;
}

/*
 * Title options wrap instead of being truncated at 40 chars with a "..."
 * glued on unconditionally -- the old markup appended the ellipsis even to
 * titles shorter than the cut, so every option looked truncated.
 */
.preview-title-options {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    margin-top: var(--space-sm);
}

.preview-title-options .title-option {
    max-width: 100%;
    text-align: left;
    white-space: normal;
}

.preview-publish {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
}

.badge-extra {
    background: var(--warning, #f59e0b);
    color: #1a1a1a;
    font-weight: 700;
}

/* --- Generate page: options grid --- */
/* Form rows sit inside a `.section-body`, which is deliberately unpadded so
   list rows can run full-bleed. A form is not a list: without its own inset
   the first and last fields touched the section border on both sides. */
.options-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--space-md);
    padding: var(--space-md);
}
@media (max-width: 720px) {
    .options-grid { grid-template-columns: 1fr 1fr; }
}
.options-grid select.input,
.options-grid input.input { width: 100%; }

/* --- Results grid of finished clips --- */
.clip-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: var(--space-lg);
}
.clip-card {
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transition: transform 0.15s ease, border-color 0.15s ease;
}
.clip-card:hover {
    transform: translateY(-2px);
    border-color: var(--accent-primary);
}
.clip-card-video {
    width: 100%;
    aspect-ratio: 9 / 16;
    background: #000;
    object-fit: contain;
    display: block;
}
.clip-card-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
}
.clip-card-name {
    font-size: var(--font-size-xs);
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* --- Kick / Twitch section ---------------------------------------------
 *
 * A picking grid, not a results grid: every card is a checkbox label, so the
 * whole card is the hit target rather than a 16px tick box. `.clip-card`
 * above supplies the frame; everything here is the picking behaviour on top.
 */

.ls-tabs {
    display: flex;
    gap: var(--space-xs);
    border-bottom: 1px solid var(--glass-border);
    margin-top: var(--space-md);
}
.ls-tab {
    padding: var(--space-sm) var(--space-lg);
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    color: var(--text-secondary);
    font-size: var(--font-size-sm);
    font-weight: 600;
    cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease;
}
.ls-tab:hover { color: var(--text-primary); }
.ls-tab.active {
    color: var(--text-primary);
    border-bottom-color: var(--accent-primary);
}

.ls-follows { margin-top: var(--space-lg); }
.ls-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
    margin-top: var(--space-md);
}
.ls-chip {
    display: inline-flex;
    align-items: center;
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-full);
    overflow: hidden;
}
.ls-chip.active {
    border-color: var(--accent-primary);
    background: rgba(124, 92, 255, 0.15);
}
.ls-chip-name,
.ls-chip-x {
    background: none;
    border: none;
    color: var(--text-secondary);
    font-size: var(--font-size-sm);
    cursor: pointer;
    padding: 6px 4px 6px 14px;
}
.ls-chip.active .ls-chip-name { color: var(--text-primary); }
.ls-chip-x {
    padding: 6px 12px 6px 6px;
    font-size: var(--font-size-md);
    line-height: 1;
}
.ls-chip-x:hover { color: var(--accent-danger); }

/*
 * The card is a <label>, so a click anywhere in it toggles the checkbox. The
 * tick itself sits on the thumbnail as a visible confirmation — a selected
 * state signalled only by a border reads as a hover on a dark theme.
 */
.ls-card {
    position: relative;
    cursor: pointer;
    padding: 0;
}
.ls-card.selected {
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 2px var(--accent-primary-glow);
}
.ls-tick {
    position: absolute;
    top: var(--space-sm);
    left: var(--space-sm);
    z-index: 2;
    width: 18px;
    height: 18px;
    accent-color: var(--accent-primary);
    cursor: pointer;
}
.ls-thumb {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    background: #000;
    display: block;
}
/* A clip with no thumbnail still needs to occupy the same box, or the grid
 * rows jump around it. */
.ls-thumb-empty { background: var(--glass-border); }

.ls-card-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    padding: var(--space-sm) var(--space-md) var(--space-md);
}
.ls-card-title {
    font-size: var(--font-size-sm);
    font-weight: 600;
    color: var(--text-primary);
    /* Two lines, then ellipsis: clip titles are long and wildly uneven, and
     * a card that grows to fit one of them breaks the grid rhythm. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.ls-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    font-size: var(--font-size-xs);
    color: var(--text-secondary);
}
.ls-card-badges {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
    margin-top: var(--space-xs);
}

/* Velocity is the number the grid is actually sorted by, so it gets the
 * accent. Mature is a warning colour and nothing more — it is a badge, never
 * a filter; the user declined the filter. */
.ls-badge-velocity { background: rgba(124, 92, 255, 0.15); color: var(--accent-primary); }
.ls-badge-category { background: rgba(95, 156, 255, 0.15); color: var(--accent-info); }
.ls-badge-warn { background: rgba(255, 179, 64, 0.15); color: var(--accent-warning); }

.ls-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: var(--space-lg);
    margin-top: var(--space-lg);
}

/* The banner opt-in sits beside the clip button it applies to, so the
 * choice is visible at the moment of clipping rather than buried in a
 * settings panel somewhere else. */
.ls-banner-opt {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--font-size-sm);
    color: var(--text-secondary);
    cursor: pointer;
    margin-right: auto;
}

/*
 * The VODs tab: a channel's recordings, a chat scan, and the ranked moments
 * it finds. Rows rather than cards — a moment has no thumbnail, only a
 * timestamp and the reason it was picked.
 */
.ls-vods { display: flex; flex-direction: column; gap: var(--space-md); }
.ls-vod-picker {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}
.ls-vod-scanning {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-md) 0;
}

.ls-spike-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}
.ls-spike-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-md);
    cursor: pointer;
}
.ls-spike-row.selected {
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 2px var(--accent-primary-glow);
}
.ls-spike-tick {
    margin-top: 3px;
    width: 18px;
    height: 18px;
    accent-color: var(--accent-primary);
    cursor: pointer;
}
.ls-spike-body { display: flex; flex-direction: column; gap: var(--space-xs); }
.ls-spike-headline {
    font-size: var(--font-size-sm);
    font-weight: 600;
    color: var(--text-primary);
}
.ls-spike-evidence {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0;
    padding-left: var(--space-md);
    font-size: var(--font-size-xs);
    color: var(--text-secondary);
}

/* Used by the Kick grid to report a failed fetch in place. */
.text-danger { color: var(--accent-danger); }

/* --- Accounts page --- */
.account-item {
    grid-template-columns: auto 1fr auto auto;
    cursor: default;
}
.account-item:hover { transform: none; }
/* Account controls are the point of the row — don't hide them until hover. */
.account-item .clip-item-actions { opacity: 1; }
.account-item .clip-item-meta { flex-wrap: wrap; }

.account-limits {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
}
.account-limits .input-label { margin: 0; white-space: nowrap; }
.input-tiny {
    width: 64px;
    padding: 6px 8px;
    font-size: var(--font-size-xs);
    text-align: center;
}

.platform-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: var(--space-md);
}
.platform-card {
    padding: var(--space-md) var(--space-lg);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-md);
}
.platform-card.platform-live { border-color: rgba(46, 213, 115, 0.35); }
.platform-card.platform-blocked { opacity: 0.75; }
.platform-head {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
}
.platform-icon { font-size: 20px; }
.platform-name {
    font-weight: 600;
    text-transform: capitalize;
    flex: 1;
}
.platform-card code,
.card-subtitle code {
    font-size: var(--font-size-xs);
    padding: 2px 6px;
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.06);
}

@media (max-width: 900px) {
    .account-item { grid-template-columns: auto 1fr; }
    .account-limits, .account-item .clip-item-actions { grid-column: 2; }
}


/* ==========================================================
   Command Palette (Ctrl/Cmd-K)
   ========================================================== */

.palette-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(6, 6, 10, 0.6);
    backdrop-filter: blur(4px);
    z-index: 2000;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding-top: 12vh;
    animation: paletteFadeIn 0.12s ease-out;
}

@keyframes paletteFadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

.palette {
    width: min(560px, 92vw);
    max-height: 60vh;
    background: var(--bg-secondary);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg), 0 0 0 1px rgba(124, 92, 255, 0.15);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.palette-input-row {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-md) var(--space-lg);
    border-bottom: 1px solid var(--glass-border);
}

.palette-icon { color: var(--accent-primary); font-size: var(--font-size-lg); }

.palette-input {
    flex: 1;
    background: transparent;
    border: none;
    outline: none;
    color: var(--text-primary);
    font-family: inherit;
    font-size: var(--font-size-md);
}
.palette-input::placeholder { color: var(--text-tertiary); }

.palette-hint {
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
    border: 1px solid var(--glass-border);
    border-radius: 4px;
    padding: 1px 6px;
}

.palette-results {
    overflow-y: auto;
    padding: var(--space-sm);
}

.palette-group-label {
    font-size: var(--font-size-xs);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-tertiary);
    padding: var(--space-sm) var(--space-md) var(--space-xs);
}

.palette-item {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-md);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background var(--transition-fast);
}

.palette-item .palette-item-icon { font-size: var(--font-size-md); width: 24px; text-align: center; }
.palette-item .palette-item-label { flex: 1; font-size: var(--font-size-sm); }
.palette-item .palette-item-tag {
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
}

.palette-item.active,
.palette-item:hover {
    background: var(--glass-hover);
}

.palette-empty {
    padding: var(--space-lg);
    text-align: center;
    color: var(--text-tertiary);
    font-size: var(--font-size-sm);
}


/* ==========================================================
   Meters (WoopSocial quota, etc.) & lane chips
   ========================================================== */

.meter {
    width: 100%;
    height: 8px;
    border-radius: var(--radius-full);
    background: rgba(255, 255, 255, 0.06);
    overflow: hidden;
}
.meter-fill {
    height: 100%;
    border-radius: var(--radius-full);
    background: var(--gradient-primary);
    transition: width var(--transition-slow);
}
.meter-fill.meter-warn { background: var(--accent-warning); }
.meter-fill.meter-danger { background: var(--accent-danger); }

.lane-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--font-size-xs);
    padding: 2px 8px;
    border-radius: var(--radius-full);
    border: 1px solid var(--glass-border);
    background: var(--glass-bg);
    color: var(--text-secondary);
    white-space: nowrap;
}
.lane-chip.lane-live {
    border-color: rgba(46, 213, 115, 0.4);
    color: var(--accent-success);
}
.lane-chip.lane-public {
    border-color: rgba(0, 212, 170, 0.4);
    color: var(--accent-secondary);
}
.lane-chip.lane-pending {
    opacity: 0.6;
}


/* ==========================================================
   Alert strip (Dashboard home)
   ========================================================== */

.alert-strip {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    margin-bottom: var(--space-lg);
}
.alert-item {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-md) var(--space-lg);
    border-radius: var(--radius-md);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    font-size: var(--font-size-sm);
}
.alert-item.alert-danger { border-color: rgba(255, 71, 87, 0.4); }
.alert-item.alert-warning { border-color: rgba(255, 179, 64, 0.4); }
.alert-item .alert-icon { font-size: var(--font-size-lg); }
.alert-item .alert-body { flex: 1; }
.alert-item a { color: var(--accent-primary); font-weight: 600; }


/* ==========================================================
   Confetti (publish-success delight)
   ========================================================== */

.confetti-piece {
    position: fixed;
    top: -10px;
    width: 8px;
    height: 8px;
    z-index: 3000;
    pointer-events: none;
    animation: confettiFall 1.4s ease-in forwards;
}

@keyframes confettiFall {
    to {
        transform: translateY(100vh) rotate(540deg);
        opacity: 0;
    }
}

/* ==========================================================
   Profiles — niche isolation
   ========================================================== */

/* --- Switcher (sidebar) --- */
.profile-switcher {
    padding: 0 var(--space-md, 1rem) var(--space-md, 1rem);
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.profile-switcher-label {
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--text-tertiary);
}

.profile-select {
    width: 100%;
    padding: 0.5rem 0.6rem;
    border-radius: 10px;
    font: inherit;
    font-size: 0.85rem;
    color: var(--text-primary);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.profile-select:hover { background: var(--glass-hover); }

.profile-select:focus {
    outline: none;
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 3px var(--accent-primary-glow);
}

/* The native dropdown list inherits the page background on some platforms;
   pin it so options aren't white-on-white in the dark theme. */
.profile-select option {
    background: var(--bg-tertiary);
    color: var(--text-primary);
}

.profile-manage-link {
    font-size: 0.74rem;
    color: var(--text-tertiary);
    text-decoration: none;
    transition: color 0.15s ease;
}

.profile-manage-link:hover { color: var(--accent-primary); }

/* --- Profiles page --- */
/* auto-FIT, not auto-fill: auto-fill keeps the empty column tracks it could
   have filled, so two profiles sat as two narrow cards beside a phantom
   third. auto-fit collapses the unused tracks and lets the real cards share
   the row — which is also what stops the export path below from having to
   wrap mid-folder-name. */
.profile-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 1rem;
    margin-bottom: 1.5rem;
}

.profile-card-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-bottom: 0.5rem;
}

.profile-card-head h3 { margin: 0; font-size: 1.05rem; }

.profile-slug {
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 0.72rem;
    color: var(--text-tertiary);
}

.badge-active {
    background: rgba(46, 213, 115, 0.15);
    color: var(--accent-success);
}

.badge-paused {
    background: rgba(255, 179, 64, 0.15);
    color: var(--accent-warning);
}

.profile-niche {
    font-size: 0.85rem;
    color: var(--text-secondary);
    margin: 0 0 0.75rem;
}

.profile-niche-edit {
    margin: 0 0 0.75rem;
}

.profile-facts {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.3rem 0.8rem;
    font-size: 0.8rem;
    margin: 0 0 1rem;
}

.profile-facts dt { color: var(--text-tertiary); }

/* `break-word` split folder names down the middle (`...\export` / `s\default`)
   even when only a few characters were over. `anywhere` takes the normal
   break opportunities first and only splits a word as a last resort. */
.profile-facts dd {
    margin: 0;
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

.profile-facts .mono {
    font-family: ui-monospace, "SF Mono", Menlo, monospace;
    font-size: 0.72rem;
    color: var(--text-secondary);
}

.profile-card-actions {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.profile-form {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 0.9rem;
    margin-bottom: 1rem;
}

.profile-form label {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    font-size: 0.78rem;
    color: var(--text-secondary);
}

.profile-form-wide { grid-column: 1 / -1; }

.profile-form input,
.profile-form select,
.profile-form textarea {
    padding: 0.55rem 0.7rem;
    border-radius: 10px;
    font: inherit;
    font-size: 0.88rem;
    color: var(--text-primary);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    resize: vertical;
}

.profile-form input:focus,
.profile-form select:focus,
.profile-form textarea:focus {
    outline: none;
    border-color: var(--accent-primary);
    box-shadow: 0 0 0 3px var(--accent-primary-glow);
}

.profile-form select option {
    background: var(--bg-tertiary);
    color: var(--text-primary);
}

/* --- Collapsible sections -------------------------------------------------
   One primitive for every page. Replaces the stack-of-cards layout that made
   the Clip Queue and Accounts pages one long scroll with no way to put
   anything out of the way. */
.section {
    background: var(--glass-bg);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-lg);
    margin-bottom: var(--space-md);
    overflow: hidden;
}

/* The toggle button and the header's own actions are siblings — an <a> or a
   <button> nested inside a <button> is invalid and becomes unclickable. */
.section-headrow {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding-right: var(--space-md);
}

.section-headrow:hover { background: rgba(255, 255, 255, 0.03); }

.section-header {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex: 1;
    min-width: 0;
    padding: var(--space-sm) var(--space-md);
    background: none;
    border: none;
    color: var(--text-primary);
    font-family: inherit;
    font-size: var(--font-size-sm);
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    transition: background var(--transition-fast);
}

.section-actions { margin-left: auto; display: flex; gap: var(--space-sm); flex: 0 0 auto; }

.section-header:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: -2px;
}

.section-chevron {
    display: inline-block;
    font-size: 0.75rem;
    color: var(--text-tertiary);
    transition: transform var(--transition-fast);
}

/* Scoped to the section's OWN header. A descendant combinator here would reach
   into the nested groups of an open generation and leave them all pointing
   down, whatever their own state. */
.section.open > .section-headrow .section-chevron { transform: rotate(90deg); }

/* `flex: 0 0 auto` refused to shrink, so a long source title was chopped
   mid-word by the header's own `overflow: hidden` — no ellipsis, no hint that
   anything was missing. Shrink and truncate honestly instead. */
.section-title {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Never wraps and never shrinks: once the title started truncating, the count
   was the flex item with slack, so "21 · 🔥 11" broke across two lines and
   made some rows taller than their neighbours. The title is the part that
   should give way — it is the one with an ellipsis to say so. */
.section-count {
    padding: 1px var(--space-sm);
    border-radius: var(--radius-sm);
    background: rgba(255, 255, 255, 0.07);
    color: var(--text-secondary);
    font-size: var(--font-size-xs);
    font-weight: 600;
    flex: 0 0 auto;
    white-space: nowrap;
}

/* Collapsed by default; the .open class is written from the persisted store on
   every render, so a background refresh cannot silently close a section.

   Sections NEST — a generation holds "Your clips" and "Extra picks" — so this
   must be a CHILD combinator. As a descendant selector it matched the inner
   bodies too, which meant an open generation held its groups open and clicking
   their headers changed nothing on screen. */
.section-body { display: none; }
.section.open > .section-body { display: block; }

/* Tones reuse the verdict palette already defined on .verdict-chip, so the
   section header and the clips inside it read as the same thing. */
.section[data-tone="post"]   { border-left: 2px solid #22c55e; }
.section[data-tone="maybe"]  { border-left: 2px solid #94a3b8; }
.section[data-tone="skip"]   { border-left: 2px solid #f59e0b; }
.section[data-tone="danger"] { border-left: 2px solid var(--accent-danger); }

.section[data-tone="post"]  .section-count { background: rgba(34, 197, 94, 0.14);  color: #22c55e; }
.section[data-tone="skip"]  .section-count { background: rgba(245, 158, 11, 0.14); color: #f59e0b; }
.section[data-tone="danger"] .section-count { background: rgba(255, 71, 87, 0.14); color: var(--accent-danger); }

/* A section holding a queue panel supplies its own padding; the panel already
   has its own borders and must sit flush. */
.section-body > .queue-panel { border: none; border-radius: 0; background: none; }

/* A generation's two groups ("Your clips" / "Extra picks") sit INSIDE it. The
   indent is what makes "these are the 10 you asked for, these are the extras
   on top" readable at a glance — without it the nesting is invisible and the
   page reads as one flat list again, which is the complaint this structure
   answers. The inner sections drop their own chrome so a generation reads as
   one card rather than cards inside cards. */
.section-nested {
    padding-left: var(--space-md);
    margin-left: var(--space-sm);
    border-left: 1px solid var(--glass-border);
}

.section-nested .section {
    background: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border: none;
    border-radius: 0;
    margin-bottom: var(--space-sm);
}

/* The generation's date, sitting in the header's actions slot. */
.section-subtitle {
    font-size: 12px;
    color: var(--text-secondary);
    white-space: nowrap;
}
.section-body > .card { border: none; background: none; margin: 0; }

/* The WoopSocial quota rides in the Accounts page header rather than in a
   collapsible section — a cap you are about to hit is useless behind a click. */
.quota-strip {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    flex-wrap: wrap;
    padding: var(--space-sm) var(--space-md);
    margin-bottom: var(--space-md);
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-md);
}

.quota-strip .meter { flex: 1; min-width: 140px; }
.quota-strip-label { flex: 0 0 auto; }

/* Health banner — sits above every page, not inside any of them. The mount
   point is outside #content because the router rewrites that on every
   navigation; see index.html. */
.health-banner {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    /* Same measure as .content, so the banner and the page share one edge. */
    padding: var(--space-md) max(var(--space-xl), (100% - var(--content-max)) / 2) 0;
    flex: 0 0 auto;
}

.health-alert {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-md);
    border-radius: var(--radius-md);
    border: 1px solid var(--glass-border);
    border-left: 3px solid var(--accent-danger);
    background: rgba(255, 71, 87, 0.08);
}

.health-alert.health-warning {
    border-left-color: var(--accent-warning);
    background: rgba(255, 179, 64, 0.08);
}

.health-alert-main { min-width: 0; }

.health-title { font-weight: 600; word-break: break-word; }

.health-count {
    margin-left: var(--space-sm);
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

.health-fix {
    margin-top: var(--space-xs);
    font-size: 0.85em;
    color: var(--text-secondary);
    word-break: break-word;
}

/* Two controls now share the alert row. Flex, deliberately: §2l's
   .queue-card-summary declared five grid tracks for six children and the sixth
   painted off the card, unclickable, for months. Flex has no tracks to run
   out of. */
.health-alert-actions {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex-shrink: 0;
}

.health-restart {
    background: var(--accent-primary);
    color: #fff;
    border: none;
    border-radius: var(--radius-sm);
    padding: var(--space-xs) var(--space-md);
    font-size: var(--font-size-xs);
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
}

.health-restart:hover { box-shadow: 0 0 20px var(--accent-primary-glow); }

.health-restart:disabled { opacity: 0.6; cursor: default; box-shadow: none; }

.health-dismiss {
    background: none;
    border: none;
    cursor: pointer;
    color: var(--text-secondary);
    font-size: 1rem;
    line-height: 1;
    padding: 2px var(--space-xs);
    flex-shrink: 0;
}

.health-dismiss:hover { color: var(--text-primary); }


/* ==========================================================
   Layout consistency pass — 2026-08-20
   ==========================================================
   The tokens above already described one spacing scale, one radius set and
   one type scale. The pages had drifted from them: cards and sections used
   different skins, long values broke mid-word, and a state toggle wore the
   brightest treatment in the app. Nothing here invents a new look — it
   applies the existing tokens consistently. CSS only: no markup changed, so
   no behaviour could regress.
   ========================================================== */

/* --- Long values wrap at the edge, never mid-word ------------------------
   Export paths rendered as `...workspace\export` / `s\default`, splitting a
   folder name across two lines. `anywhere` breaks only when a line genuinely
   cannot fit, and prefers the existing break opportunities first. */
.mono,
code {
    overflow-wrap: anywhere;
}

/* --- A state toggle is not a call to action -----------------------------
   The account enable/disable control takes `btn-primary` when an account is
   DISABLED, so the loudest gradient in the app marked the accounts doing
   nothing. Same class in the markup, calmer treatment here: the state reads
   from a tinted dot rather than from the button's own weight. */
.btn[data-toggle-account] {
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    color: var(--text-primary);
    box-shadow: none;
    display: inline-flex;
    align-items: center;
    gap: var(--space-sm);
    min-width: 104px;
    justify-content: center;
}

.btn[data-toggle-account]::before {
    content: '';
    width: 7px;
    height: 7px;
    border-radius: var(--radius-full);
    background: var(--text-tertiary);
    flex: 0 0 auto;
}

.btn[data-toggle-account][data-enabled="true"]::before {
    background: var(--accent-success);
}

.btn[data-toggle-account]:hover {
    background: var(--glass-hover);
    border-color: rgba(255, 255, 255, 0.16);
}

/* --- Inputs stop at the card edge ---------------------------------------
   Autopilot's last field ran flush to its card border because the grid
   children could not shrink below their content. */
.input,
.select,
textarea.input {
    max-width: 100%;
    min-width: 0;
}

/* --- Quality floor ------------------------------------------------------
   A keyboard user could tab through the whole app without ever seeing where
   they were: the reset removed the UA outline and nothing replaced it. */
:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

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