:root {
    /* Brand — swap these to rebrand the UI. Hover is ~15% darker. */
    --color-brand: #1e3a5f;
    --color-brand-hover: #1a3151;

    /* Surfaces */
    --color-bg: #f5f5f5;
    --color-surface: #fff;
    --color-border: #e5e7eb;
    --color-border-strong: #d1d5db;

    /* Text */
    --color-text: #1a1a1a;
    --color-text-secondary: #333;
    --color-text-muted: #555;
    --color-text-subtle: #888;

    /* Disabled state */
    --color-disabled-bg: #cbd5e1;
    --color-disabled-text: #64748b;
    --color-disabled-subtle: #94a3b8;

    /* Semantic — success */
    --color-success-bg: #dcfce7;
    --color-success-text: #166534;
    --color-success-border: #86efac;

    /* Semantic — error */
    --color-error-bg: #fef2f2;
    --color-error-text: #991b1b;
    --color-error-border: #fecaca;

    /* Semantic — warning */
    --color-warning-bg: #fef3c7;
    --color-warning-text: #92400e;

    /* Semantic — info (used for neutral metadata badges like "CLI") */
    --color-info-bg: #dbeafe;
    --color-info-text: #1e40af;

    /* Shape */
    --radius-sm: 4px;
    --radius-md: 6px;
    --radius-lg: 8px;
}

*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: system-ui, -apple-system, sans-serif;
    line-height: 1.6;
    color: var(--color-text);
    background: var(--color-bg);
    min-height: 100vh;
}

/* Navbar */
.navbar {
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    position: sticky;
    top: 0;
    z-index: 50;
}

.navbar-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    max-width: 1140px;
    margin: 0 auto;
    padding: 0 1.5rem;
    height: 64px;
}

.navbar-brand {
    display: flex;
    align-items: center;
    text-decoration: none;
}

.navbar-logo {
    height: 40px;
}

.navbar-menu {
    display: flex;
    align-items: center;
}

.navbar-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.navbar-cog-wrapper {
    position: relative;
    display: flex;
    align-items: center;
    padding: 0.5rem;
    border-radius: 6px;
    cursor: pointer;
}

.navbar-cog-wrapper:hover {
    background: var(--color-bg);
}

.navbar-cog {
    width: 22px;
    height: 22px;
    color: var(--color-text-muted);
}

.navbar-cog-wrapper.open .navbar-dropdown {
    display: block;
}

.navbar-avatar-wrapper {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    cursor: pointer;
    position: relative;
    padding: 0.25rem 0.5rem;
    border-radius: 6px;
}

.navbar-avatar-wrapper:hover {
    background: var(--color-bg);
}

.navbar-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid var(--color-border);
}

.navbar-username {
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--color-text-secondary);
}

.navbar-dropdown {
    display: none;
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 0.25rem;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 6px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    min-width: 160px;
    z-index: 60;
    overflow: hidden;
}

.navbar-avatar-wrapper.open .navbar-dropdown {
    display: block;
}

.navbar-dropdown-item {
    display: block;
    width: 100%;
    padding: 0.5rem 1rem;
    font-size: 0.875rem;
    color: var(--color-text-secondary);
    text-decoration: none;
    text-align: left;
    background: none;
    border: none;
    cursor: pointer;
    font-family: inherit;
}

.navbar-dropdown-item:hover {
    background: var(--color-bg);
}


.main-content {
    min-height: calc(100vh - 64px);
}

@media (max-width: 640px) {
    .navbar-username {
        display: none;
    }
}

/* Layout */
.page-center {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: calc(100vh - 64px);
    padding: 2rem;
}

.card {
    background: var(--color-surface);
    border-radius: 8px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    padding: 2.5rem;
    width: 100%;
    max-width: 420px;
}

/* Typography */
h1 {
    font-size: 1.5rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
}

p {
    color: var(--color-text-muted);
    margin-bottom: 1.5rem;
}

/* Forms */
form {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

label {
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--color-text-secondary);
    margin-bottom: -0.5rem;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="tel"] {
    padding: 0.625rem 0.75rem;
    border: 1px solid var(--color-border-strong);
    border-radius: 6px;
    font-size: 0.9375rem;
    font-family: inherit;
    transition: border-color 0.15s;
    width: 100%;
}

input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="tel"]:focus {
    outline: none;
    border-color: var(--color-brand);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-brand) 10%, transparent);
}

button {
    padding: 0.625rem 1.25rem;
    background: var(--color-brand);
    color: var(--color-surface);
    border: none;
    border-radius: 6px;
    font-size: 0.9375rem;
    font-weight: 500;
    font-family: inherit;
    cursor: pointer;
    transition: background 0.15s;
    margin-top: 0.5rem;
}

button:hover {
    background: var(--color-brand-hover);
}

button:disabled,
button:disabled:hover {
    background: var(--color-disabled-bg);
    color: var(--color-disabled-text);
    cursor: not-allowed;
    opacity: 0.7;
}

.link-btn:disabled,
.link-btn:disabled:hover {
    background: none;
    color: var(--color-disabled-subtle);
    cursor: not-allowed;
    text-decoration: none;
}

/* Links */
.auth-footer {
    text-align: center;
    margin-top: 1.5rem;
    font-size: 0.875rem;
    color: var(--color-text-muted);
}

.auth-footer a {
    color: var(--color-brand);
    text-decoration: none;
    font-weight: 500;
}

.auth-footer a:hover {
    text-decoration: underline;
}

/* OAuth */
.auth-divider {
    display: flex;
    align-items: center;
    margin: 1.5rem 0;
    color: var(--color-text-subtle);
    font-size: 0.875rem;
}

.auth-divider::before,
.auth-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--color-border);
}

.auth-divider span {
    padding: 0 1rem;
}

.google-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    width: 100%;
    padding: 0.625rem 1.25rem;
    background: var(--color-surface);
    color: var(--color-text-secondary);
    border: 1px solid var(--color-border-strong);
    border-radius: 6px;
    font-size: 0.9375rem;
    font-weight: 500;
    font-family: inherit;
    text-decoration: none;
    cursor: pointer;
    transition: background 0.15s;
}

.google-btn:hover {
    background: var(--color-bg);
}

.google-icon {
    width: 20px;
    height: 20px;
}

/* Errors */
.error {
    background: var(--color-error-bg);
    color: var(--color-error-text);
    padding: 0.75rem 1rem;
    border-radius: 6px;
    font-size: 0.875rem;
    margin-bottom: 0.5rem;
}

/* Home page */
.home-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1rem;
}

.home-header form {
    display: inline;
}

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

.btn-secondary:hover {
    background: var(--color-border-strong);
}

/* Modal */
.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 100;
}

.modal {
    background: var(--color-surface);
    border-radius: 8px;
    padding: 2rem;
    max-width: 420px;
    width: 90%;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.modal h2 {
    font-size: 1.25rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
}

.modal-actions {
    display: flex;
    gap: 0.75rem;
    margin-top: 1.5rem;
}

.modal-actions form {
    display: inline;
}

/* Link-styled button */
.link-btn {
    background: none;
    color: var(--color-brand);
    padding: 0;
    margin: 0;
    font-weight: 500;
    cursor: pointer;
}

.link-btn:hover {
    background: none;
    text-decoration: underline;
}

/* Settings */
.settings-card {
    max-width: 520px;
    /* Auto margins keep the card horizontally centered when it's used
       outside .page-center (flex centering already handles the original
       /settings/* pages). The project-settings tab uses .page-body
       wrapping instead so the project-header above stays full-width. */
    margin-left: auto;
    margin-right: auto;
}

.settings-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1.5rem;
}

.back-link {
    text-decoration: none;
    padding: 0.375rem 0.75rem;
    font-size: 0.875rem;
    display: inline-block;
    text-align: center;
    border-radius: 6px;
}

.settings-section {
    border-top: 1px solid var(--color-border);
    padding-top: 1.5rem;
    margin-top: 1.5rem;
}

/* First section in a settings card has nothing above it (or just an h1
   whose margin already provides separation), so the divider is visual
   noise. Strip the top border + padding + margin so the section content
   sits flush with the top of the card. */
.settings-section-first {
    border-top: none;
    padding-top: 0;
    margin-top: 0;
}

/* GitHub-style "Danger Zone" — visually distinct red-bordered region
   inside a settings card for irreversible actions (delete, transfer
   ownership, etc.). The red box is the warning; descriptive copy and
   the action button live inside. */
.danger-zone {
    margin-top: 2rem;
    padding: 1.25rem 1.5rem 1.5rem;
    border: 1px solid #fecaca;
    border-radius: 6px;
    background: #fef2f2;
}
.danger-zone h2 {
    color: #b91c1c;
    margin: 0 0 0.5rem;
    font-size: 1rem;
    font-weight: 600;
}
.danger-zone .hint { color: #991b1b; }

/* Centered fixed-width action button used inside settings-section forms.
   Locks both the rename and delete buttons on the project-settings page
   to the same visual footprint regardless of their differing text lengths,
   and centers them horizontally so the section reads as "form input on
   top, single action button below". */
.settings-action {
    display: block;
    width: 220px;
    margin: 1rem auto 0;
}

.settings-section h2 {
    font-size: 1rem;
    font-weight: 600;
    margin-bottom: 0.75rem;
}

/* Settings-section <select> inputs default-render quite tight against the
   helper/status text above them. Mirror the 1rem margin .settings-action
   uses on top so the select sits with equal breathing room above (vs the
   text) and below (vs the Save button). */
.settings-section select {
    margin-top: 1rem;
}

/* Explicit divider between non-section regions (e.g., a settings-section
   and the danger zone). Matches the inline divider .settings-section gets
   from its border-top + padding-top + margin-top. The trailing rule
   suppresses any compounding top margin on whatever sits directly below
   so the visual rhythm matches a section-to-section transition. */
.settings-divider {
    border: none;
    border-top: 1px solid var(--color-border);
    margin: 1.5rem 0;
}
.settings-divider + .danger-zone {
    margin-top: 0;
}

.email-status {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.5rem;
}

.badge {
    font-size: 0.75rem;
    font-weight: 500;
    padding: 0.125rem 0.5rem;
    border-radius: 9999px;
}

.badge-verified,
.badge-success {
    background: var(--color-success-bg);
    color: var(--color-success-text);
}

.badge-unverified,
.badge-warning {
    background: var(--color-warning-bg);
    color: var(--color-warning-text);
}

.badge-error {
    background: var(--color-error-bg);
    color: var(--color-error-text);
}

.badge-info {
    background: var(--color-info-bg);
    color: var(--color-info-text);
}

/* Muted/neutral status — used for "revoked" keys and similar inactive
   states. Reads as a deactivated chip rather than a warning, since
   revoked is the expected end state of a key, not an error. */
.badge-muted {
    background: #f3f4f6;
    color: var(--color-text-secondary, #6b7280);
}

.inline-form {
    display: inline;
}

.success {
    background: var(--color-success-bg);
    color: var(--color-success-text);
    padding: 0.75rem 1rem;
    border-radius: 6px;
    font-size: 0.875rem;
    margin-bottom: 1rem;
}

.settings-link {
    color: var(--color-brand);
    text-decoration: none;
    font-weight: 500;
}

.settings-link:hover {
    text-decoration: underline;
}

/* Avatar */
.avatar-section {
    display: flex;
    align-items: center;
    gap: 1.5rem;
}

.avatar {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    object-fit: cover;
    border: 2px solid var(--color-border);
}

.avatar-form {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.avatar-btn {
    display: inline-block;
    text-align: center;
    cursor: pointer;
    padding: 0.625rem 1.25rem;
    background: var(--color-brand);
    color: var(--color-surface);
    border: none;
    border-radius: 6px;
    font-size: 0.9375rem;
    font-weight: 500;
    font-family: inherit;
}

.avatar-btn:hover {
    background: var(--color-brand-hover);
}

.avatar-hint {
    font-size: 0.75rem;
    color: var(--color-text-subtle);
    margin-top: 0.5rem;
}

/* 2FA */
.two-fa-options {
    display: flex;
    gap: 1rem;
    margin-top: 0.75rem;
}

.two-fa-option {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.two-fa-option button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.two-fa-actions {
    display: flex;
    gap: 0.75rem;
    margin-top: 0.75rem;
}

.two-fa-hint {
    font-size: 0.75rem;
    color: var(--color-text-subtle);
}

@media (max-width: 640px) {
    .two-fa-options {
        flex-direction: column;
    }
}

/* ============================================
   Landing Page Styles
   ============================================ */

/* Landing navbar extras */
.navbar-brand-text {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--color-text-secondary);
}

.navbar-nav {
    display: flex;
    list-style: none;
    margin: 0;
    padding: 0;
    gap: 2rem;
    align-items: center;
}

.navbar-nav-item {
    margin: 0;
}

.navbar-nav-link {
    text-decoration: none;
    color: var(--color-text-muted);
    font-weight: 500;
    font-size: 0.9375rem;
    transition: color 0.15s;
}

.navbar-nav-link:hover {
    color: var(--color-brand);
}

/* Hamburger */
.hamburger {
    display: none;
    flex-direction: column;
    cursor: pointer;
    padding: 8px;
    border-radius: 4px;
}

.hamburger:hover {
    background: var(--color-bg);
}

.bar {
    width: 22px;
    height: 2px;
    background: var(--color-text-secondary);
    margin: 3px 0;
    transition: all 0.3s ease;
    border-radius: 2px;
}

@media (max-width: 768px) {
    .navbar-nav {
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        flex-direction: column;
        background: var(--color-surface);
        border-bottom: 1px solid var(--color-border);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
        padding: 0;
        gap: 0;
        z-index: 50;
        opacity: 0;
        visibility: hidden;
        transform: translateY(-8px);
        transition: opacity 0.2s, visibility 0.2s, transform 0.2s;
    }

    .navbar-nav.active {
        opacity: 1;
        visibility: visible;
        transform: translateY(0);
    }

    .navbar-nav-item {
        width: 100%;
        border-bottom: 1px solid var(--color-bg);
    }

    .navbar-nav-item:last-child {
        border-bottom: none;
    }

    .navbar-nav-link {
        display: block;
        padding: 0.75rem 1.5rem;
        width: 100%;
    }

    .navbar-nav-link:hover {
        background: var(--color-bg);
    }

    .hamburger {
        display: flex;
    }

    .hamburger.active .bar:nth-child(2) {
        opacity: 0;
    }

    .hamburger.active .bar:nth-child(1) {
        transform: translateY(8px) rotate(45deg);
    }

    .hamburger.active .bar:nth-child(3) {
        transform: translateY(-8px) rotate(-45deg);
    }
}

/* Section layout */
.section-wrapper {
    max-width: 1140px;
    margin: 0 auto;
    padding: 0 1.5rem;
    width: 100%;
}

.section-title {
    font-size: 2.25rem;
    font-weight: 700;
    color: var(--color-text);
    margin-bottom: 0.75rem;
    text-align: center;
}

.section-subtitle {
    font-size: 1.125rem;
    color: var(--color-text-muted);
    margin-bottom: 3rem;
    max-width: 600px;
    margin-left: auto;
    margin-right: auto;
    line-height: 1.6;
    text-align: center;
}

/* CTA / Hero Section */
.cta-section {
    padding: 5rem 0;
    background: var(--color-brand);
    text-align: center;
}

.cta-section .section-title {
    color: var(--color-surface);
}

.cta-section .section-subtitle {
    color: rgba(255, 255, 255, 0.85);
    margin-bottom: 2.5rem;
}

.landing-btn-primary {
    display: inline-block;
    padding: 0.75rem 2rem;
    background: var(--color-surface);
    color: var(--color-brand);
    text-decoration: none;
    border-radius: 6px;
    font-weight: 600;
    font-size: 1rem;
    transition: all 0.15s;
    border: 2px solid var(--color-surface);
}

.landing-btn-primary:hover {
    background: transparent;
    color: var(--color-surface);
}

/* Code block */
.hero-code-block {
    background: #1e293b;
    border-radius: 8px;
    max-width: 520px;
    margin: 0 auto 2.5rem;
    overflow: hidden;
    text-align: left;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}

.code-header {
    display: flex;
    align-items: center;
    padding: 0.75rem 1rem;
    background: rgba(255, 255, 255, 0.05);
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    gap: 6px;
}

.code-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
}

.code-dot.red { background: #ef4444; }
.code-dot.yellow { background: #eab308; }
.code-dot.green { background: #22c55e; }

.code-tabs {
    display: flex;
    margin-left: auto;
    gap: 0;
}

.code-tab {
    background: none;
    border: none;
    color: rgba(255, 255, 255, 0.6);
    font-family: ui-monospace, monospace;
    font-size: 0.75rem;
    padding: 4px 10px;
    cursor: pointer;
    border-radius: 4px;
    margin: 0;
}

.code-tab:hover {
    color: var(--color-surface);
}

.code-tab.active {
    color: var(--color-surface);
    background: rgba(255, 255, 255, 0.1);
}

.code-panel {
    display: none;
}

.code-panel.active {
    display: block;
}

.code-body {
    padding: 1.25rem;
    font-family: ui-monospace, monospace;
    font-size: 0.8125rem;
    line-height: 1.7;
    color: #e2e8f0;
    overflow-x: auto;
}

.code-body .keyword { color: #c084fc; }
.code-body .function { color: #60a5fa; }
.code-body .string { color: var(--color-success-border); }
.code-body .comment { color: var(--color-disabled-text); }
.code-body .property { color: #fb923c; }

/* Capabilities / Features */
.capabilities-section {
    padding: 5rem 0;
    background: var(--color-bg);
}

.capabilities-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.5rem;
}

.capability-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 8px;
    padding: 2rem;
    transition: transform 0.15s, box-shadow 0.15s;
}

.capability-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}

.capability-icon {
    font-size: 1.75rem;
    margin-bottom: 0.75rem;
}

.capability-title {
    font-size: 1.0625rem;
    font-weight: 600;
    color: var(--color-text);
    margin-bottom: 0.5rem;
}

.capability-text {
    font-size: 0.875rem;
    color: var(--color-text-muted);
    line-height: 1.6;
    margin: 0;
}

@media (max-width: 768px) {
    .capabilities-grid {
        grid-template-columns: 1fr;
    }
}

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

/* How It Works */
.how-it-works {
    padding: 5rem 0;
    background: var(--color-surface);
}

.how-it-works .section-wrapper {
    text-align: center;
}

.steps-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 2rem;
    text-align: left;
}

.step-card {
    background: var(--color-bg);
    border-radius: 8px;
    padding: 2rem;
    border: 1px solid var(--color-border);
    transition: transform 0.15s, box-shadow 0.15s;
}

.step-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}

.step-number {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    background: var(--color-brand);
    color: var(--color-surface);
    border-radius: 6px;
    font-weight: 700;
    font-size: 0.875rem;
    margin-bottom: 1rem;
}

.step-title {
    font-size: 1.0625rem;
    font-weight: 600;
    color: var(--color-text);
    margin-bottom: 0.5rem;
}

.step-description {
    font-size: 0.875rem;
    color: var(--color-text-muted);
    line-height: 1.6;
    margin: 0;
}

.step-code {
    margin-top: 0.75rem;
    background: #1e293b;
    border-radius: 6px;
    padding: 0.625rem 0.875rem;
    font-family: ui-monospace, monospace;
    font-size: 0.75rem;
    color: #e2e8f0;
    overflow-x: auto;
}

.step-code .keyword { color: #c084fc; }
.step-code .function { color: #60a5fa; }
.step-code .string { color: var(--color-success-border); }

@media (max-width: 768px) {
    .steps-grid {
        grid-template-columns: 1fr;
    }

    .section-title {
        font-size: 1.75rem;
    }
}

/* Pricing */
.pricing-section {
    padding: 5rem 0;
    background: var(--color-bg);
}

.pricing-grid {
    /* Flex with wrap so any number of cards centers — avoids the
       "stretch to fill 4 columns" artifact when fewer plans exist. */
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 1.5rem;
    margin-top: 3rem;
}
.pricing-grid > .pricing-card {
    flex: 1 1 240px;
    max-width: 300px;
}

.pricing-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 8px;
    padding: 2rem 1.5rem;
    text-align: center;
    display: flex;
    flex-direction: column;
    transition: transform 0.15s, box-shadow 0.15s;
}

.pricing-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}

.pricing-card.featured {
    border-color: var(--color-brand);
    box-shadow: 0 0 0 1px var(--color-brand);
    position: relative;
}

.pricing-badge {
    position: absolute;
    top: -12px;
    left: 50%;
    transform: translateX(-50%);
    background: var(--color-brand);
    color: var(--color-surface);
    padding: 4px 14px;
    border-radius: 20px;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.pricing-tier {
    font-size: 1rem;
    font-weight: 600;
    color: var(--color-text);
    margin-bottom: 0.5rem;
}

.pricing-price {
    font-size: 2.25rem;
    font-weight: 700;
    color: var(--color-brand);
    margin-bottom: 0.25rem;
}

.pricing-price span {
    font-size: 1rem;
    font-weight: 400;
    color: var(--color-text-muted);
}

.pricing-events {
    font-size: 0.8125rem;
    color: var(--color-text-muted);
    margin-bottom: 1.5rem;
    padding-bottom: 1.5rem;
    border-bottom: 1px solid var(--color-border);
}

.pricing-features {
    list-style: none;
    padding: 0;
    margin: 0 0 1.5rem 0;
    text-align: left;
    flex-grow: 1;
}

.pricing-features li {
    padding: 0.375rem 0;
    font-size: 0.875rem;
    color: var(--color-text-muted);
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
}

.pricing-features li::before {
    content: '\2713';
    color: var(--color-brand);
    font-weight: 700;
    flex-shrink: 0;
}

.pricing-cta {
    display: block;
    padding: 0.625rem 1.25rem;
    border-radius: 6px;
    font-weight: 500;
    font-size: 0.9375rem;
    text-decoration: none;
    transition: all 0.15s;
    text-align: center;
}

.pricing-cta-primary {
    background: var(--color-brand);
    color: var(--color-surface);
    border: 2px solid var(--color-brand);
}

.pricing-cta-primary:hover {
    background: var(--color-brand-hover);
    border-color: var(--color-brand-hover);
}

.pricing-cta-secondary {
    background: transparent;
    color: var(--color-brand);
    border: 2px solid var(--color-brand);
}

.pricing-cta-secondary:hover {
    background: var(--color-brand);
    color: var(--color-surface);
}

/* Flex grid with max-width on each card already wraps cleanly;
   older grid-template-columns media queries are no longer needed. */
@media (max-width: 576px) {
    .pricing-grid > .pricing-card {
        flex: 1 1 100%;
        max-width: 360px;
    }
}

/* Footer */
.footer-section {
    background: var(--color-text);
    color: var(--color-surface);
    padding: 3rem 0 0;
}

.footer-container {
    max-width: 1140px;
    margin: 0 auto;
    padding: 0 1.5rem;
}

.footer-content {
    display: grid;
    grid-template-columns: 2fr 1fr 1fr 1.5fr;
    gap: 2.5rem;
    margin-bottom: 2.5rem;
}

.footer-column {
    text-align: center;
}

.footer-logo {
    margin-bottom: 1rem;
}

.footer-logo-img {
    height: 40px;
}

.footer-description {
    color: var(--color-text-subtle);
    line-height: 1.6;
    margin-bottom: 1.5rem;
    font-size: 0.875rem;
}

.footer-heading {
    font-size: 1rem;
    font-weight: 600;
    color: var(--color-surface);
    margin-bottom: 1rem;
    margin-top: 0;
}

.footer-links {
    list-style: none;
    padding: 0;
    margin: 0;
}

.footer-links li {
    margin-bottom: 0.5rem;
}

.footer-link {
    color: var(--color-text-subtle);
    text-decoration: none;
    font-size: 0.875rem;
    transition: color 0.15s;
}

.footer-link:hover {
    color: var(--color-surface);
}

.footer-bottom {
    border-top: 1px solid rgba(255, 255, 255, 0.1);
    padding: 1.5rem 0;
}

.footer-bottom-content {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: wrap;
    gap: 1.25rem;
}

.footer-bottom-links {
    display: flex;
    gap: 1.5rem;
}

.footer-bottom-link {
    color: var(--color-text-subtle);
    text-decoration: none;
    font-size: 0.8125rem;
    transition: color 0.15s;
}

.footer-bottom-link:hover {
    color: var(--color-surface);
}

.copyright {
    color: var(--color-text-subtle);
    margin: 0;
    font-size: 0.8125rem;
}

@media (max-width: 768px) {
    .footer-content {
        grid-template-columns: 1fr;
        gap: 2rem;
    }

    .footer-bottom-content {
        flex-direction: column;
        gap: 0.75rem;
    }
}

@media (max-width: 992px) and (min-width: 769px) {
    .footer-content {
        grid-template-columns: 1fr 1fr;
    }
}

/* Blog */
.blog-section {
    padding: 5rem 0;
    background: var(--color-bg);
}

.blog-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1.5rem;
    margin-top: 2rem;
}

.blog-card {
    background: var(--color-surface);
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid var(--color-border);
    transition: transform 0.15s, box-shadow 0.15s;
}

.blog-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}

.blog-card-image {
    overflow: hidden;
}

.blog-card-img {
    width: 100%;
    height: 180px;
    object-fit: cover;
    display: block;
}

.blog-card-body {
    padding: 1.5rem;
}

.blog-card-date {
    font-size: 0.75rem;
    color: var(--color-text-subtle);
    margin-bottom: 0.5rem;
}

.blog-card-title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--color-text);
    margin-bottom: 0.5rem;
    line-height: 1.4;
}

.blog-card-title a {
    color: var(--color-text);
    text-decoration: none;
}

.blog-card-title a:hover {
    color: var(--color-brand);
}

.blog-card-excerpt {
    font-size: 0.8125rem;
    color: var(--color-text-muted);
    line-height: 1.6;
    margin: 0;
}

@media (max-width: 768px) {
    .blog-grid {
        grid-template-columns: 1fr;
    }
}

/* Blog Single Post */
.blog-single {
    padding: 5rem 0;
    background: var(--color-surface);
}

.blog-single .section-wrapper {
    max-width: 720px;
}

.blog-single-image {
    margin-bottom: 2rem;
    border-radius: 8px;
    overflow: hidden;
}

.blog-single-img {
    width: 100%;
    height: auto;
    display: block;
}

.blog-single-title {
    font-size: 2rem;
    font-weight: 700;
    color: var(--color-text);
    margin-bottom: 0.75rem;
    line-height: 1.3;
}

.blog-single-meta {
    font-size: 0.875rem;
    color: var(--color-text-subtle);
    margin-bottom: 2rem;
    padding-bottom: 2rem;
    border-bottom: 1px solid var(--color-border);
}

.blog-single-content {
    font-size: 1rem;
    color: var(--color-text);
    line-height: 1.8;
}

.blog-single-content h2 {
    font-size: 1.5rem;
    margin-top: 2rem;
    margin-bottom: 0.75rem;
}

.blog-single-content p {
    margin-bottom: 1.25rem;
}

.blog-single-content code {
    background: var(--color-bg);
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 0.9em;
}

.blog-single-content pre {
    background: #1e293b;
    color: #e2e8f0;
    padding: 1.25rem;
    border-radius: 8px;
    overflow-x: auto;
    font-size: 0.875rem;
    line-height: 1.6;
}

.blog-single-content pre code {
    background: none;
    padding: 0;
}

/* Contact */
.contact-section {
    padding: 5rem 0;
    background: var(--color-bg);
}

.contact-container {
    max-width: 720px;
    margin: 0 auto;
    padding: 0 1.5rem;
}

.contact-header {
    text-align: center;
    margin-bottom: 3rem;
}

.contact-title {
    font-size: 2.25rem;
    font-weight: 700;
    color: var(--color-text);
    margin-bottom: 0.75rem;
}

.contact-subtitle {
    font-size: 1.0625rem;
    color: var(--color-text-muted);
    line-height: 1.6;
}

.contact-content {
    display: flex;
    justify-content: center;
}

.contact-form-wrapper {
    background: var(--color-surface);
    border-radius: 8px;
    padding: 2.5rem;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    border: 1px solid var(--color-border);
    width: 100%;
    max-width: 560px;
}

.contact-form {
    width: 100%;
}

.form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1.25rem;
    margin-bottom: 0;
}

.form-group {
    margin-bottom: 1.25rem;
}

.form-label {
    display: block;
    font-weight: 500;
    color: var(--color-text-secondary);
    margin-bottom: 0.375rem;
    font-size: 0.875rem;
}

.form-input {
    width: 100%;
    padding: 0.625rem 0.75rem;
    border: 1px solid var(--color-border-strong);
    border-radius: 6px;
    font-size: 0.9375rem;
    color: var(--color-text);
    background: var(--color-surface);
    transition: border-color 0.15s;
    font-family: inherit;
}

.form-input:focus {
    outline: none;
    border-color: var(--color-brand);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-brand) 10%, transparent);
}

.form-textarea {
    resize: vertical;
    min-height: 100px;
}

.contact-btn {
    width: 100%;
    padding: 0.625rem 1.25rem;
    background: var(--color-brand);
    color: var(--color-surface);
    border: none;
    border-radius: 6px;
    font-weight: 500;
    font-size: 0.9375rem;
    cursor: pointer;
    transition: background 0.15s;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    margin: 0;
}

.contact-btn:hover {
    background: var(--color-brand-hover);
}

.btn-icon {
    width: 18px;
    height: 18px;
}

.form-message {
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
    border-radius: 6px;
    font-size: 0.875rem;
    font-weight: 500;
}

.form-message-success {
    background: var(--color-success-bg);
    color: var(--color-success-text);
    border: 1px solid #bbf7d0;
}

.form-message-error {
    background: var(--color-error-bg);
    color: var(--color-error-text);
    border: 1px solid var(--color-error-border);
}

@media (max-width: 576px) {
    .contact-form-wrapper {
        padding: 1.5rem;
    }

    .form-row {
        grid-template-columns: 1fr;
    }
}

/* Legal Pages */
.legal-section {
    padding: 5rem 0;
    background: var(--color-surface);
}

.legal-section .section-wrapper {
    max-width: 720px;
}

.legal-title {
    font-size: 2rem;
    font-weight: 700;
    color: var(--color-text);
    margin-bottom: 0.5rem;
}

.legal-updated {
    font-size: 0.875rem;
    color: var(--color-text-subtle);
    margin-bottom: 2rem;
}

.legal-content h2 {
    font-size: 1.25rem;
    font-weight: 600;
    color: var(--color-text);
    margin-top: 2rem;
    margin-bottom: 0.75rem;
}

.legal-content h3 {
    font-size: 1.0625rem;
    font-weight: 600;
    color: var(--color-text-secondary);
    margin-top: 1.5rem;
    margin-bottom: 0.5rem;
}

.legal-content p,
.legal-content li {
    font-size: 0.9375rem;
    color: var(--color-text-muted);
    line-height: 1.8;
}

.legal-content ul {
    padding-left: 1.5rem;
    margin-bottom: 1rem;
}

.legal-content a {
    color: var(--color-brand);
    text-decoration: none;
}

.legal-content a:hover {
    text-decoration: underline;
}

/* Animations */
.fade-in-left {
    opacity: 0;
    transform: translateX(-30px);
    transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}

.fade-in-right {
    opacity: 0;
    transform: translateX(30px);
    transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}

.fade-in-up {
    opacity: 0;
    transform: translateY(20px);
    transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}

.fade-in-left.animate,
.fade-in-right.animate,
.fade-in-up.animate {
    opacity: 1;
    transform: translate(0);
}

.animate-delay-1 { transition-delay: 0.1s; }
.animate-delay-2 { transition-delay: 0.2s; }

.page-transition {
    animation: pageFadeIn 400ms ease-out both;
}

/* Sticky-footer layout for landing pages — body is a flex column with
   the <main> in between navbar and footer absorbing all leftover space.
   On short pages (e.g. pricing) the footer is pushed to the viewport
   bottom instead of floating in the middle of an empty body. */
body.page-transition {
    display: flex;
    flex-direction: column;
}
.landing-main {
    flex: 1;
}

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

@media (prefers-reduced-motion: reduce) {
    .page-transition { animation: none; }
    .fade-in-left, .fade-in-right, .fade-in-up { opacity: 1; transform: none; transition: none; }
}


/* === Cookie Consent Modal === */
.cookie-modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.8);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 10000;
    animation: cookieFadeIn 0.3s ease-out;
}

.cookie-modal {
    background: var(--color-surface);
    border-radius: 12px;
    max-width: 600px;
    width: 90%;
    max-height: 90vh;
    overflow-y: auto;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
    animation: cookieSlideUp 0.3s ease-out;
}

.cookie-modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1.5rem;
    border-bottom: 1px solid var(--color-border);
}

.cookie-modal-header h3 {
    margin: 0;
    color: var(--color-text);
    font-size: 1.5rem;
}

.cookie-modal-close {
    background: none;
    border: none;
    font-size: 1.5rem;
    cursor: pointer;
    color: var(--color-text-subtle);
    padding: 0;
    margin: 0;
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: transform 0.2s ease 0.1s;
}

.cookie-modal-close:hover {
    background: none;
    color: var(--color-text-muted);
    transform: scale(1.2);
}

.cookie-modal-content {
    padding: 1.5rem;
}

.cookie-categories {
    margin: 1.5rem 0;
}

.cookie-category {
    margin-bottom: 1.5rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid var(--color-bg);
}

.cookie-category:last-child {
    border-bottom: none;
    margin-bottom: 0;
}

.cookie-toggle {
    display: flex;
    align-items: center;
    cursor: pointer;
    user-select: none;
}

.cookie-toggle input {
    margin-right: 0.75rem;
    accent-color: var(--color-brand);
}

.cookie-slider {
    display: none;
}

.cookie-label {
    font-weight: 600;
    color: var(--color-text);
}

.cookie-description {
    margin: 0;
    color: var(--color-text-muted);
    font-size: 0.9rem;
    padding-left: 2rem;
}

.cookie-modal-actions {
    display: flex;
    gap: 0.75rem;
    margin-top: 2rem;
    flex-wrap: wrap;
}

.cookie-btn {
    padding: 0.75rem 1.5rem;
    border-radius: 6px;
    font-weight: 500;
    cursor: pointer;
    border: none;
    transition: all 0.2s ease;
    flex: 1;
    min-width: 120px;
    background: var(--color-brand);
    color: var(--color-surface);
    font-family: inherit;
    font-size: 0.9rem;
    margin: 0;
}

.cookie-btn:hover {
    background: var(--color-brand-hover);
}

.cookie-policy-link {
    text-align: center;
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border);
}

.cookie-policy-link a {
    color: var(--color-brand);
    text-decoration: none;
    font-size: 0.9rem;
}

.cookie-policy-link a:hover {
    text-decoration: underline;
}

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

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

@media (max-width: 768px) {
    .cookie-modal {
        width: 95%;
        margin: 1rem;
    }

    .cookie-modal-actions {
        flex-direction: column;
    }
}

/* -------------------------------------------------------------------
   Billing — subscribe + payment-method pages + pricing toggle
   ------------------------------------------------------------------- */

/* Container Stripe Elements mounts into. Minimum height so the iframe
   Elements injects doesn't cause layout shift while it boots. */
.stripe-element {
    padding: 0.75rem;
    border: 1px solid var(--border, #ddd);
    border-radius: 6px;
    min-height: 52px;
    background: #fff;
}

/* "← back" link used at the top of subscribe / payment-method pages
   so users can bail out without using the browser back button. */
.back-link {
    display: inline-block;
    margin-bottom: 0.75rem;
    color: var(--muted, #6b7280);
    text-decoration: none;
    font-size: 0.9rem;
}
.back-link:hover { text-decoration: underline; }

/* Monthly/yearly toggle. Lives per-pricing-card; only rendered when a
   plan offers both intervals AND the prices differ. Two pill buttons
   inside a rounded track; active one gets the primary background. */
.pricing-toggle {
    display: inline-flex;
    gap: 0.2rem;
    padding: 0.2rem;
    background: rgba(0, 0, 0, 0.05);
    border-radius: 999px;
}
.pricing-toggle-card {
    margin: 0 auto 0.75rem;
}
.pricing-toggle-option {
    border: 0;
    background: transparent;
    padding: 0.35rem 0.8rem;
    font-size: 0.8rem;
    border-radius: 999px;
    cursor: pointer;
    color: var(--text, #111);
    white-space: nowrap;
}
.pricing-toggle-option .muted { color: var(--muted, #6b7280); font-size: 0.75rem; }
.pricing-toggle-option.is-active {
    /* Active reads as "pressed" — same accent, slightly darkened so
       it's visually distinct from an inactive option being hovered. */
    background: var(--accent, #4f46e5);
    color: #fff;
    filter: brightness(0.9);
}
.pricing-toggle-option.is-active .muted { color: rgba(255, 255, 255, 0.85); }

/* Hover overrides the global button:hover. Keep white text in both
   states; shift the background shade instead of swapping the color.
   Inactive-hover uses the base accent (lighter than active); hovering
   the already-active option brightens it back toward the base tone. */
.pricing-toggle-option:hover {
    background: var(--accent, #4f46e5);
    color: #fff;
}
.pricing-toggle-option:hover .muted { color: rgba(255, 255, 255, 0.85); }
.pricing-toggle-option.is-active:hover { filter: brightness(1); }


/* =====================================================================
   Projects + API keys design system

   These pages are the user's primary workspace, so they break out of
   the .page-center / .card pattern used for settings/profile (config
   surfaces) and use a full-bleed page-header + content-grid layout.
   ===================================================================== */

/* Page header — full-bleed band above content with title + primary CTA. */
/* Page header — full-bleed band (border + background) wrapping a constrained
   .page-header-inner that aligns with the navbar's content width. Mirrors
   the .navbar / .navbar-inner pattern so chrome edges line up vertically. */
.page-header {
    border-bottom: 1px solid var(--color-border, #e5e7eb);
    background: var(--color-surface, #fff);
}
.page-header-inner {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 1rem;
    max-width: 1140px;
    margin: 0 auto;
    padding: 2rem 1.5rem 1.5rem;
}
.page-header-titles { flex: 1; min-width: 0; }
.breadcrumb {
    font-size: 0.875rem;
    margin-bottom: 0.25rem;
}
.breadcrumb a {
    color: var(--color-text-secondary, #6b7280);
    text-decoration: none;
}
.breadcrumb a:hover { color: var(--color-brand); }
.page-title {
    font-size: 1.875rem;
    font-weight: 600;
    margin: 0;
    line-height: 1.2;
}
.page-subtitle {
    color: var(--color-text-secondary, #6b7280);
    margin: 0.5rem 0 0;
    max-width: 50rem;
}
.page-header-actions {
    display: flex;
    gap: 0.5rem;
    align-items: center;
}

.page-body {
    padding: 1.5rem 1.5rem 3rem;
    max-width: 1140px;
    margin: 0 auto;
    width: 100%;
    box-sizing: border-box;
}

/* Tabs (server-rendered: each tab is its own URL). Same band/inner split as
   .page-header so the active-tab underline lines up with the title above it. */
.tabs {
    border-bottom: 1px solid var(--color-border, #e5e7eb);
    background: var(--color-surface, #fff);
}
.tabs-inner {
    display: flex;
    gap: 0.25rem;
    max-width: 1140px;
    margin: 0 auto;
    padding: 0 1.5rem;
}
.tab {
    padding: 0.75rem 1rem;
    color: var(--color-text-secondary, #6b7280);
    text-decoration: none;
    font-weight: 500;
    font-size: 0.9375rem;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color 0.15s, border-color 0.15s;
}
.tab:hover { color: var(--color-text); }
.tab-active {
    color: var(--color-brand);
    border-bottom-color: var(--color-brand);
}

/* Project card grid — primary navigation surface for the workspace. */
.project-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 1rem;
}
.project-card {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 1.25rem;
    background: var(--color-surface, #fff);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 8px;
    text-decoration: none;
    color: inherit;
    transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.project-card:hover {
    border-color: var(--color-brand);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.project-card-name {
    font-size: 1.125rem;
    font-weight: 600;
    margin: 0;
}
/* `.copy-pill` modifier on a `.copy-row` — the wrapper itself becomes
   the grey pill so both the <code> and the copy icon sit inside it.
   Strips per-element backgrounds that would otherwise nest, tightens
   gap, and re-tones the icon hover so it reads against the grey base
   instead of darkening too aggressively. Applied to both the project
   card list and the project overview. */
.copy-pill {
    background: #f3f4f6;
    border-radius: 4px;
    padding: 0.25rem 0.375rem 0.25rem 0.5rem;
    gap: 0.25rem;
}
.copy-pill .copy-target {
    background: transparent;
    padding: 0;
    color: var(--color-text-secondary, #6b7280);
}
.copy-pill .btn-copy-icon {
    padding: 0.25rem;
    color: var(--color-text-secondary, #6b7280);
}
.copy-pill .btn-copy-icon:hover {
    background: rgba(0, 0, 0, 0.06);
}

/* Project-card IDs render at the smaller size used elsewhere on cards. */
.project-card-id .copy-target {
    font-size: 0.75rem;
}
.project-card-footer {
    font-size: 0.8125rem;
    color: var(--color-text-secondary, #6b7280);
    margin-top: auto;
}

/* Empty state — a centered call-to-action when there's nothing to list. */
.empty-state {
    text-align: center;
    padding: 4rem 2rem;
    background: var(--color-surface, #fff);
    border: 1px dashed var(--color-border, #e5e7eb);
    border-radius: 8px;
}
.empty-state h2 {
    margin: 0 0 0.5rem;
    font-size: 1.25rem;
}
.empty-state p {
    color: var(--color-text-secondary, #6b7280);
    max-width: 36rem;
    margin: 0 auto 1.5rem;
}
.empty-state-compact {
    padding: 2rem;
}

/* Panels — content blocks within a tab. Looser styling than settings cards. */
.panel {
    background: var(--color-surface, #fff);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 8px;
    padding: 1.5rem;
    margin-bottom: 1rem;
}

/* .project-id-header — header section at the top of the Overview
   tab. Label stacks above the value pill (which lives in a
   .copy-row.copy-pill). Bumped contrast vs the default copy-pill
   chrome: visible border + slightly darker background so the ID
   pops as a primary surface element. */
.project-id-header {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.25rem;
    margin-bottom: 1.5rem;
}
.project-id-label {
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--color-text-secondary, #6b7280);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.project-id-pill {
    /* Hug the contents — the parent is column-flex with
       align-items: flex-start so width:auto doesn't stretch. */
    background: #e5e7eb;
    border: 1px solid var(--color-border, #d1d5db);
    padding: 0.375rem 0.5rem 0.375rem 0.625rem;
}
.project-id-pill .copy-target {
    color: var(--color-text-primary, #111827);
    font-size: 0.9375rem;
}

/* .scoped-flash — inline success/error banner rendered inside a
   specific config step instead of at the top of the page. Same
   colors as the global .success / .error classes but tighter
   padding + smaller font so it reads as a contextual note. */
.scoped-flash {
    padding: 0.5rem 0.75rem;
    font-size: 0.875rem;
    margin: 0 0 0.75rem;
    border-radius: 4px;
}

/* .scan-stepper — horizontal pipeline indicator on the scan
   progress panel. Three steps: Discovery → Planning → Review
   surface. JS toggles `.active` on the current step and
   `.complete` on prior steps as SSE status_change events arrive;
   pending steps stay in the default gray state. The connecting
   line is drawn via the ::before pseudo on every step after the
   first — turning green between complete steps. */
.scan-stepper {
    display: flex;
    list-style: none;
    margin: 1rem 0 0.75rem;
    padding: 0;
    gap: 0;
}
.scan-step {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    position: relative;
    gap: 0.375rem;
    min-width: 0;
}
/* Connecting line behind each step (except the first). Sits at
   the vertical center of the dot row so it reads as a track the
   dots ride on. z-index: 0 keeps it below the dot. */
.scan-step + .scan-step::before {
    content: "";
    position: absolute;
    top: 0.5625rem;
    left: -50%;
    right: 50%;
    height: 2px;
    background: var(--color-border, #e5e7eb);
    z-index: 0;
    transition: background 0.2s ease;
}
.scan-step-dot {
    width: 1.25rem;
    height: 1.25rem;
    border-radius: 50%;
    background: #fff;
    border: 2px solid var(--color-border, #e5e7eb);
    z-index: 1;
    transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
.scan-step-label {
    font-size: 0.8125rem;
    color: var(--color-text-secondary, #6b7280);
    text-align: center;
    line-height: 1.3;
    transition: color 0.2s ease, font-weight 0.2s ease;
}
/* Active step — current stage. Brand-color dot + pulsing halo so
   the eye lands here. */
.scan-step.active .scan-step-dot {
    background: var(--color-brand, #1e3a5f);
    border-color: var(--color-brand, #1e3a5f);
    animation: scan-step-pulse 1.6s ease-in-out infinite;
}
.scan-step.active .scan-step-label {
    color: var(--color-text-primary, #111827);
    font-weight: 600;
}
/* Complete step — past stage. Green dot, and the line connecting
   from THIS step to the next also goes green. */
.scan-step.complete .scan-step-dot {
    background: #10b981;
    border-color: #10b981;
}
.scan-step.complete + .scan-step::before {
    background: #10b981;
}
@keyframes scan-step-pulse {
    0%, 100% { box-shadow: 0 0 0 0   color-mix(in srgb, var(--color-brand, #1e3a5f) 35%, transparent); }
    50%      { box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-brand, #1e3a5f)  0%, transparent); }
}

/* .scan-spinner — small spinning ring next to the scan status line
   on the progress panel. Reuses the same `btn-spin` keyframe as the
   NLP search button spinner so the visual language is consistent
   across long-running LLM operations. Only rendered while the scan
   is in `progress` state — the panel branch handles visibility, so
   no JS toggle is needed. */
.scan-spinner {
    display: inline-block;
    width: 14px;
    height: 14px;
    border: 2px solid var(--color-brand, #1e3a5f);
    border-right-color: transparent;
    border-radius: 50%;
    animation: btn-spin 0.8s linear infinite;
    vertical-align: -2px;
    margin-right: 0.375rem;
}

/* ==========================================================
   Review surface (instrument-review.gohtml)
   Renders only after a successful scan, when scan.status
   = awaiting_review. Two-pane layout: left = service +
   candidate list (grouped by category, each candidate in
   its own card), right = chat-with-agent. Footer below
   carries the "Open PR" CTA + counts.
   ========================================================== */

/* Page heading on the review surface — matches the visual weight
   of .configuration-title on the overview tab so the two surfaces
   feel like siblings. */
.review-heading {
    font-size: 1.5rem;
    font-weight: 600;
    margin: 0 0 1rem;
    color: var(--color-text-primary, #111827);
}

.review-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(280px, 22rem);
    gap: 1.25rem;
    margin-top: 1rem;
}
.review-pane {
    min-width: 0;
}
.review-pane-left {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}
@media (max-width: 900px) {
    .review-layout {
        grid-template-columns: 1fr;
    }
    /* On narrow viewports the two panes stack. The chat surface
       is the primary "what do I do next" affordance, so it lands
       above the candidate list — under the page heading but
       above the per-handler cards. CSS `order` overrides DOM
       order without disturbing the wide-screen grid. */
    .review-pane-right { order: -1; }
    .review-pane-left  { order: 0;  }
    /* Drop sticky behavior on small screens — sticky inside a
       single-column flow just pins the chat to the top while
       the candidate list scrolls underneath it, which feels
       cramped on mobile. */
    #chat-pane {
        position: static;
        max-height: none;
    }
}

/* Each .review-section is a card with a heading. The Services
   summary at the top and each category group (auth, billing, …)
   render as their own section so the review surface reads as a
   stack of focused groupings. */
.review-section {
    background: var(--color-surface, #fff);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 8px;
    padding: 1rem 1.125rem;
}
.review-section-title {
    font-size: 0.9375rem;
    font-weight: 600;
    margin: 0 0 0.75rem;
    color: var(--color-text-primary, #111827);
}

/* Services summary row — one row per service, with a checkbox to
   include/exclude the whole service at once. */
.service-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
.service-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}
.service-toggle {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    cursor: pointer;
    font-size: 0.9375rem;
}
.service-name {
    font-weight: 500;
}
.service-meta,
.service-count {
    color: var(--color-text-secondary, #6b7280);
    font-size: 0.8125rem;
}

/* Each candidate is a card so the route + event name + meta +
   actions read as one tile and the list reads as a stack of
   distinct proposals rather than a wall of text. */
.candidate-list {
    display: flex;
    flex-direction: column;
    gap: 0.625rem;
}
.candidate-card {
    background: #fafafa;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    padding: 0.75rem 0.875rem;
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
    transition: border-color 0.15s ease, opacity 0.15s ease;
}
.candidate-card:hover {
    border-color: var(--color-brand, #1e3a5f);
}
/* Excluded candidates: muted so they still read but the eye lands
   on the active ones. */
.candidate-excluded {
    opacity: 0.55;
}

.candidate-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}
.candidate-route {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
}
.route-method {
    display: inline-block;
    padding: 0.0625rem 0.4375rem;
    background: var(--color-brand, #1e3a5f);
    color: #fff;
    font-size: 0.6875rem;
    font-weight: 600;
    border-radius: 3px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
}
.route-pattern {
    font-size: 0.8125rem;
    color: var(--color-text-secondary, #6b7280);
}
.candidate-event {
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--color-text-primary, #111827);
}

/* Confidence pill — colored by level so a glance at the list
   shows where the agent is sure vs. where the user should look. */
.candidate-confidence {
    margin-left: auto;
    display: inline-block;
    padding: 0.0625rem 0.4375rem;
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border-radius: 999px;
}
.confidence-high    { background: #d1fae5; color: #065f46; }
.confidence-medium  { background: #fef3c7; color: #92400e; }
.confidence-low     { background: #fee2e2; color: #991b1b; }
.confidence-unknown { background: #e5e7eb; color: #4b5563; }

.candidate-meta {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    font-size: 0.8125rem;
    color: var(--color-text-secondary, #6b7280);
}
.candidate-rationale {
    color: var(--color-text-secondary, #6b7280);
    font-style: italic;
}

.candidate-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    margin-top: 0.125rem;
}
.candidate-actions .btn-link {
    padding: 0;
    background: transparent;
    color: var(--color-brand, #1e3a5f);
    font-size: 0.8125rem;
    border: none;
    margin: 0;
    cursor: pointer;
}
.candidate-actions .btn-link:hover {
    text-decoration: underline;
}

/* .candidate-panel — disclosure body for Rename / Tenant source.
   Sits as a full-width block under the action row so opening it
   doesn't reflow the Exclude / Rename / Tenant source trigger row.
   Hidden by default (the `hidden` attribute toggled by
   instrument-review.js drives visibility). */
.candidate-panel {
    margin-top: 0.5rem;
    padding: 0.5rem 0.625rem;
    background: #fff;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 4px;
}
.candidate-panel .inline-form {
    /* Explicit row direction overrides the global `form { flex-direction: column }`
       rule — otherwise `flex: 1 1 14rem` on the input is interpreted as a
       14rem MAIN-axis (height) basis, rendering the input as a tall box. */
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.375rem;
    margin: 0;
}
.candidate-panel .inline-form input[type="text"] {
    flex: 1 1 14rem;
    min-width: 0;
    height: auto;
    padding: 0.3125rem 0.5rem;
    font-size: 0.8125rem;
    line-height: 1.4;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 4px;
}
.candidate-panel .inline-form select {
    padding: 0.3125rem 0.5rem;
    font-size: 0.8125rem;
    line-height: 1.4;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 4px;
    background: #fff;
}
.candidate-panel .inline-form .btn-sm {
    padding: 0.3125rem 0.625rem;
    font-size: 0.8125rem;
    margin: 0;
}
/* The "Rename"/"Tenant source" trigger buttons share .btn-link
   appearance with Exclude, but they live in the actions flex row
   as regular buttons (not <details>). Show an open-state indicator
   so the chevron-like cue still reads. */
.candidate-toggle[aria-expanded="true"] {
    font-weight: 600;
    text-decoration: underline;
}
/* Tiny "what is this?" label rendered above the confidence
   dropdown so users know what the low/medium/high/unknown
   options refer to. flex-basis: 100% forces it onto its own row
   inside the form's flex container. */
.inline-form-label {
    flex-basis: 100%;
    margin: 0 0 0.25rem;
    font-size: 0.75rem;
    color: var(--color-text-secondary, #6b7280);
}

/* Embed-placement card — renders below the candidate groups when
   the scan has wants_embed=true and embed planning produced a row.
   Mirrors the .review-section visual treatment so it reads as
   another grouped section, distinct from a candidate card. */
.embed-placement-card .embed-placement-amended {
    margin-left: 0.375rem;
    padding: 0.125rem 0.5rem;
    border-radius: 999px;
    background: #ecfdf5;
    color: #047857;
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    vertical-align: middle;
}
.embed-placement-meta {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.375rem 0.875rem;
    margin: 0 0 0.875rem;
    font-size: 0.875rem;
}
.embed-placement-meta dt {
    color: var(--color-text-secondary, #6b7280);
    font-weight: 500;
}
.embed-placement-meta dd {
    margin: 0;
    color: var(--color-text-primary, #111827);
}
.embed-placement-meta dd code {
    font-size: 0.8125rem;
    background: #f3f4f6;
    padding: 0.0625rem 0.375rem;
    border-radius: 4px;
}
.embed-placement-preview {
    margin: 0 0 0.75rem;
}
.embed-placement-preview summary {
    cursor: pointer;
    font-size: 0.8125rem;
    color: var(--color-text-secondary, #6b7280);
    user-select: none;
}
.embed-placement-preview pre {
    margin: 0.5rem 0 0;
    padding: 0.75rem 0.875rem;
    background: #f9fafb;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    font-size: 0.8125rem;
    line-height: 1.5;
    overflow-x: auto;
}
.embed-placement-minter,
.embed-placement-rationale,
.embed-placement-empty,
.embed-placement-minter-only,
.embed-placement-no-minter {
    margin: 0 0 0.5rem;
    font-size: 0.875rem;
    line-height: 1.45;
}
.embed-placement-minter:last-child,
.embed-placement-rationale:last-child,
.embed-placement-empty:last-child,
.embed-placement-minter-only:last-child,
.embed-placement-no-minter:last-child {
    margin-bottom: 0;
}
.embed-placement-minter code,
.embed-placement-minter-only code {
    font-size: 0.8125rem;
    background: #f3f4f6;
    padding: 0.0625rem 0.375rem;
    border-radius: 4px;
}
.embed-placement-rationale {
    color: var(--color-text-secondary, #6b7280);
    font-style: italic;
}
.embed-placement-no-minter {
    color: #92400e;
    background: #fffbeb;
    border: 1px solid #fde68a;
    border-radius: 6px;
    padding: 0.625rem 0.75rem;
}

/* Amendment form: inline input + Send button, sits at the bottom
   of the card. Visually separated from the content above by a top
   margin + thin divider so the user reads "current placement"
   distinct from "make a change". */
.embed-placement-amend {
    display: flex;
    gap: 0.5rem;
    margin: 0.875rem 0 0;
    padding-top: 0.875rem;
    border-top: 1px solid var(--color-border, #e5e7eb);
}
.embed-placement-amend input[type="text"] {
    flex: 1;
    padding: 0.5rem 0.625rem;
    font-size: 0.875rem;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
}
.embed-placement-amend input[type="text"]:disabled {
    background: #f9fafb;
    color: var(--color-text-secondary, #6b7280);
}
.embed-placement-amend button {
    padding: 0.5rem 1rem;
    font-size: 0.875rem;
    font-weight: 500;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    background: var(--color-surface, #fff);
    cursor: pointer;
}
.embed-placement-amend button:hover:not(:disabled) {
    background: #f9fafb;
}
.embed-placement-amend button:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

/* Status line below the form — streams assistant tokens during a
   turn, then settles into "Updated." / refusal text / error. */
.embed-placement-status {
    margin: 0.5rem 0 0;
    font-size: 0.8125rem;
    color: var(--color-text-secondary, #6b7280);
    white-space: pre-wrap;
    min-height: 1em;
}
.embed-placement-status:empty {
    display: none;
}
.embed-placement-status-refusal {
    color: #92400e;
}
.embed-placement-status-error {
    color: #b91c1c;
}

/* visually-hidden — accessible label for the input that screen
   readers announce but the UI doesn't render. Standard pattern;
   defined here so the embed-placement form can use it without
   introducing a global utility if one doesn't exist elsewhere. */
.embed-placement-card .visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Chat pane — bubble-style messages, like other agent chat UIs.
   The pane itself reads as a card (same border + radius + bg as
   .review-section) for visual consistency with the left pane. */
#chat-pane {
    background: var(--color-surface, #fff);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 8px;
    padding: 1rem 1.125rem;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    align-self: start;
    position: sticky;
    top: 5rem;
    max-height: calc(100vh - 6rem);
}
/* Sub-heading below "Chat with the agent" — explains what the
   agent can do so the user knows what kind of message to send.
   Sits between the section title and the chat log. The negative
   top margin tightens the gap to the title (which has its own
   `margin: 0 0 0.75rem`). */
.chat-subheading {
    margin: -0.5rem 0 0;
    line-height: 1.4;
}

.chat-log {
    flex: 1 1 auto;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding-right: 0.25rem;
    min-height: 12rem;
}
.chat-message {
    max-width: 88%;
    padding: 0.5rem 0.75rem;
    border-radius: 12px;
    font-size: 0.875rem;
    line-height: 1.45;
    word-wrap: break-word;
    white-space: pre-wrap;
}
.chat-user {
    align-self: flex-end;
    background: var(--color-brand, #1e3a5f);
    color: #fff;
    border-bottom-right-radius: 4px;
}
.chat-assistant {
    align-self: flex-start;
    background: #f3f4f6;
    color: var(--color-text-primary, #111827);
    border-bottom-left-radius: 4px;
}
/* Chat input — text box full-width, Send button centered
   underneath. Column direction so the input and Send stack
   vertically; align-items: center horizontally centers the
   Send button at its content width. */
.chat-form {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    margin: 0;
}
.chat-form input[type="text"] {
    width: 100%;
    padding: 0.5rem 0.75rem;
    font-size: 0.875rem;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
}
.chat-form button {
    padding: 0.5rem 1.25rem;
    margin: 0;
}

/* Open PR progress modal — full-viewport overlay that the user
   sees from the moment they click Open PR until the scan
   transitions to pr_opened (or failed). instrument-open-pr.js
   toggles the .open-pr-state child divs as the SSE stream
   reports status changes. */
.modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 100;
    padding: 1rem;
}
/* `display: flex` above clobbers the browser's default treatment
   of the HTML `hidden` attribute (which is just `display: none`).
   This restores the attribute's effect so the modal stays
   invisible until JS removes `hidden`. */
.modal-overlay[hidden] {
    display: none;
}
body.modal-open {
    overflow: hidden;
}
.modal-card {
    background: var(--color-surface, #fff);
    border-radius: 10px;
    padding: 1.75rem 1.75rem 1.5rem;
    max-width: 28rem;
    width: 100%;
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
    text-align: center;
}
.modal-title {
    font-size: 1.125rem;
    font-weight: 600;
    margin: 0 0 0.5rem;
    color: var(--color-text-primary, #111827);
}
.modal-body {
    margin: 0 0 1rem;
    line-height: 1.5;
}
.modal-actions {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    margin-top: 0.75rem;
}
.modal-actions button {
    margin: 0;
    padding: 0.5rem 1.25rem;
}

/* Big spinner that lives at the top of the working state.
   Reuses the same `btn-spin` keyframe as the small inline
   spinner so the motion language is consistent. */
.open-pr-spinner {
    width: 42px;
    height: 42px;
    border: 3px solid var(--color-brand, #1e3a5f);
    border-right-color: transparent;
    border-radius: 50%;
    animation: btn-spin 0.9s linear infinite;
    margin: 0 auto 1rem;
}
/* Agent narration live-log inside the Open-PR modal. Hidden until
   the first SSE agent_text event arrives (so the modal doesn't
   flash an empty code panel during the lead-in). The wrapper holds
   a small dim header + a fixed-height scrolling <pre>; the JS
   appends each text delta and autoscrolls when the user is pinned
   to the bottom. */
.open-pr-agent-log-wrapper {
    margin-top: 1rem;
    text-align: left;
}
.open-pr-agent-log-header {
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-text-secondary, #6b7280);
    margin-bottom: 0.4rem;
}
.open-pr-agent-log {
    margin: 0;
    max-height: 14rem;
    overflow-y: auto;
    padding: 0.75rem 0.875rem;
    background: #0f172a;
    color: #e2e8f0;
    border-radius: 6px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.75rem;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
}

/* Big check mark for the success state. The `.open-pr-check`
   div is the green disc; the ✓ inside it is the glyph. */
.open-pr-check {
    width: 48px;
    height: 48px;
    line-height: 48px;
    margin: 0 auto 0.75rem;
    background: #10b981;
    color: #fff;
    font-size: 1.75rem;
    font-weight: 700;
    border-radius: 50%;
}

/* Review footer — sticks below the two-pane layout. Stacks the
   selected-events count above the Open PR CTA and centers both.
   The count is the live disclosure ("4 of 8 events selected") so
   the button label can stay short ("Open PR" without the
   redundant count). */
.review-footer {
    margin-top: 1.5rem;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border, #e5e7eb);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
}
.review-footer-stats {
    font-size: 0.9375rem;
    color: var(--color-text-primary, #111827);
    text-align: center;
}
.review-footer form {
    margin: 0;
}
.review-footer form button {
    padding: 0.5rem 1.25rem;
    margin: 0;
}

/* .agent-log — live "what the agent is doing right now" panel on
   the scan progress page. Receives streamed text deltas from the
   worker stages via the `agent_text` SSE event. Bounded height
   with auto-scroll so a chatty agent doesn't push the rest of
   the page off-screen. Empty until the first delta arrives. */
.agent-log {
    margin: 1rem 0 0;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    background: #fafafa;
    overflow: hidden;
}
.agent-log-header {
    padding: 0.5rem 0.75rem;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-text-secondary, #6b7280);
    background: #f3f4f6;
    border-bottom: 1px solid var(--color-border, #e5e7eb);
}
.agent-log-body {
    margin: 0;
    padding: 0.75rem 1rem;
    max-height: 18rem;
    overflow-y: auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
    color: var(--color-text, #111827);
    white-space: pre-wrap;
    word-break: break-word;
}

/* .connected-repo-card — styled tile for the "repo is currently
   connected" state. Clickable link to the repo on GitHub, with a
   GitHub-style icon, the owner/repo identifier as the primary
   text, and the default branch + "Connected" indicator below. */
.connected-repo-card {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.875rem 1rem;
    background: var(--color-surface, #fff);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    text-decoration: none;
    color: inherit;
    margin: 0.75rem 0;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.connected-repo-card:hover {
    border-color: var(--color-brand, #1e3a5f);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.connected-repo-icon {
    flex-shrink: 0;
    color: var(--color-text-secondary, #6b7280);
}
.connected-repo-text {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 0;
}
.connected-repo-name {
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--color-text-primary, #111827);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.connected-repo-meta {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    font-size: 0.8125rem;
    color: var(--color-text-secondary, #6b7280);
}
.connected-repo-branch {
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
}
.connected-repo-branch::before {
    content: "⎇ ";
}
/* Green "Connected" pill above the repo name — same green family
   as success flashes so the signal reads as positive at a glance.
   `align-self: flex-start` keeps it tight to its label width
   instead of stretching across the card. */
.connected-repo-pill {
    align-self: flex-start;
    display: inline-block;
    padding: 0.125rem 0.5rem;
    background: #d1fae5;
    color: #065f46;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    border-radius: 999px;
    line-height: 1.4;
}

/* .pr-link-card — sibling of .connected-repo-card, used in the
   key_display and pr_opened states. Same shape so the two cards
   stack with consistent visual weight: GitHub pull-request octicon
   on the left, "PR opened" pill + the GitHub URL on the right.
   Whole card is a link to the PR. */
.pr-link-card {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.875rem 1rem;
    background: var(--color-surface, #fff);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    text-decoration: none;
    color: inherit;
    margin: 0.75rem 0;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.pr-link-card:hover {
    border-color: var(--color-brand, #1e3a5f);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.pr-link-icon {
    flex-shrink: 0;
    color: var(--color-text-secondary, #6b7280);
}
.pr-link-text {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 0;
}
.pr-link-pill {
    align-self: flex-start;
    display: inline-block;
    padding: 0.125rem 0.5rem;
    background: #d1fae5;
    color: #065f46;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    border-radius: 999px;
    line-height: 1.4;
}
.pr-link-url {
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
    font-size: 0.8125rem;
    color: var(--color-brand, #1e3a5f);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* All buttons inside the Configuration card share one shape —
   same padding, font, radius, and zero top margin — so every
   CTA reads as the same control family. Scoped to
   .configuration-card so the global button rules used by modals
   + pagination elsewhere stay unchanged. */
.configuration-card .btn {
    padding: 0.5rem 1rem;
    font-size: 0.9375rem;
    font-weight: 500;
    border-radius: 6px;
    border: none;
    cursor: pointer;
    margin: 0;
    line-height: 1.25;
}

/* .configuration-card — the always-visible "Configuration" panel
   on the project Overview tab. Holds two child cards under a
   "2 ways to connect" sub-heading. Larger title than other panels
   so the section reads as the page's primary content block. */
.configuration-title {
    font-size: 1.5rem;
    font-weight: 600;
    margin: 0 0 0.25rem;
}
.configuration-card .config-subhead {
    font-size: 1rem;
    font-weight: 600;
    color: var(--color-text-secondary, #6b7280);
    margin: 0 0 1rem;
}

/* .config-option — child card nested inside .configuration-card.
   Distinct background + subtle border so each option reads as
   its own enclosed surface, not a continuous section. */
.configuration-card .config-option {
    background: #f9fafb;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 8px;
    padding: 1.25rem;
}
/* Adjacent-sibling selector instead of :first-of-type — the two
   options can be different element types (Option 1 is <details>,
   Option 2 is <div>) and :first-of-type would treat both as
   first-of-their-type, collapsing the gap between them. */
.configuration-card .config-option + .config-option {
    margin-top: 1rem;
}
.configuration-card .config-option-title {
    font-size: 1.0625rem;
    font-weight: 600;
    margin: 0 0 0.5rem;
}
.configuration-card .config-option-body {
    color: var(--color-text-secondary, #6b7280);
    margin: 0 0 1rem;
}

/* Collapsible variant of .config-option built on <details>. The
   default browser disclosure triangle is hidden in favor of a
   styled caret we control via the [open] selector for rotation.
   Used by Option 1 (GitHub App) so users can collapse the flow
   once they're done with it. */
.configuration-card .config-option-collapsible > summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    user-select: none;
}
.configuration-card .config-option-collapsible > summary::-webkit-details-marker {
    display: none;
}
.configuration-card .config-option-collapsible > summary::after {
    content: "▸";
    color: var(--color-brand, #1e3a5f);
    font-size: 1.75rem;
    font-weight: 700;
    transition: transform 0.15s ease;
    line-height: 1;
    display: inline-block;
    text-align: center;
    /* Push the caret to the far right of the summary row. The
       summary itself is `display: flex`, so margin-left: auto on
       this pseudo-element claims the leftover space. */
    margin-left: auto;
}
.configuration-card .config-option-collapsible[open] > summary::after {
    transform: rotate(90deg);
}
/* Override the generic .config-option-title margin so the summary
   row aligns the caret with the title centerline. */
.configuration-card .config-option-collapsible > summary .config-option-title {
    margin: 0;
}

/* .config-steps — numbered step list inside option 1. The native
   <ol> counter is hidden in favor of a styled `01 / 02` pill
   beside each step's title, which reads better than the default
   marker against the gray card background. */
.config-steps {
    list-style: none;
    padding: 0;
    margin: 0;
    counter-reset: config-step;
    display: flex;
    flex-direction: column;
    gap: 0.875rem;
}
.config-step {
    counter-increment: config-step;
    background: var(--color-surface, #fff);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    padding: 0.875rem 1rem;
    /* Anchor scroll targets — see redirectToSection. The sticky
       navbar (.navbar) covers the top of the viewport, so without
       this offset a #fragment scroll lands the anchored element
       partially hidden behind it. */
    scroll-margin-top: 5rem;
}
.config-step-already-installed[id] {
    scroll-margin-top: 5rem;
}
.config-step-title {
    font-weight: 600;
    margin-bottom: 0.25rem;
}
.config-step-title::before {
    content: counter(config-step, decimal-leading-zero) "  ·  ";
    color: var(--color-text-secondary, #6b7280);
    font-weight: 500;
}
.config-step-body {
    color: var(--color-text-secondary, #6b7280);
    margin: 0 0 0.5rem;
    font-size: 0.9375rem;
}
.config-step-body:last-child {
    margin-bottom: 0;
}

/* "Already installed?" sync surface — always-visible escape
   hatch under step 1 for users who installed the App outside
   the Everscribe-driven flow. Separated from the primary install
   CTA above by a top border so it reads as a distinct subsection. */
.config-step-already-installed {
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border, #e5e7eb);
    font-size: 0.875rem;
}
.config-step-subtitle {
    font-weight: 600;
    margin-bottom: 0.25rem;
}
.config-sync-form {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
    margin-top: 0.5rem;
}
.config-sync-form input[type="text"] {
    width: 100%;
    max-width: 22rem;
    padding: 0.375rem 0.625rem;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 4px;
}
/* Override the global .scoped-flash bottom margin when the banner
   is a flex child of .config-sync-form — the form already provides
   the right spacing via `gap`, so margin would compound. Width
   matches the input above so the banner doesn't stretch full-width
   on wide screens. */
.config-sync-form .scoped-flash {
    margin: 0;
    width: 100%;
    max-width: 22rem;
}

/* .config-repo-select — repo dropdown rendered inside step 2 of
   the GitHub option when state == "picker". Single <select>
   replaces the prior radio-row list; cleaner UI when a user has
   many repos, and same encoded "<install_id>:<owner>/<name>"
   value the server-side parser already accepts. */
.config-repo-select {
    width: 100%;
    max-width: 32rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 4px;
    background: var(--color-surface, #fff);
    font-size: 0.9375rem;
}

/* .action-row groups multiple action buttons (typically inside forms)
   into an inline row. Without this, the global `form { display: flex;
   flex-direction: column; }` rule stretches the button to the panel's
   full width — which makes single-CTA panels (e.g., "Connect GitHub")
   look badly-proportioned. Children render inline; the inner forms
   collapse so their buttons size to content. */
.action-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.5rem;
}
.action-row form {
    display: inline;
}
.action-row form button {
    margin-top: 0;
}

.panel-title {
    font-size: 1.125rem;
    font-weight: 600;
    margin: 0 0 0.25rem;
}
.panel-subtitle {
    color: var(--color-text-secondary, #6b7280);
    margin: 0 0 1rem;
    font-size: 0.9375rem;
}
.panel-danger {
    border-color: #fecaca;
    background: #fef2f2;
}
.panel-danger .panel-title { color: #b91c1c; }

/* Two-up layout used on Overview tab (project ID + quickstart). */
.overview-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 1rem;
}

/* Tab-pane section header — mirrors .page-header-inner: title + subtitle
   stacked on the left, CTA button on the right aligned to the bottom of
   the title block (i.e., the button sits next to the subtitle row).
   Prefixed with `tab-` to avoid colliding with the landing page's
   pre-existing centered .section-title / .section-subtitle. */
.tab-section-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 1rem;
    margin: 1.5rem 0 1rem;
}
.tab-section-titles { flex: 1; min-width: 0; }
.tab-section-title {
    font-size: 1.125rem;
    font-weight: 600;
    margin: 0;
}
.tab-section-subtitle {
    color: var(--color-text-secondary, #6b7280);
    font-size: 0.9375rem;
    margin: 0.25rem 0 0;
}

/* Data table — full-width tabular display for keys (and future event lists).
   `overflow-x: auto` lets the inner table scroll horizontally on narrow
   viewports instead of getting clipped. The table's `min-width` keeps
   columns readable at the cost of forcing that scroll on mobile, which
   is the right trade-off — squashing six columns into 360px is unreadable. */
.table-wrap {
    background: var(--color-surface, #fff);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 8px;
    overflow-x: auto;
}
.data-table {
    width: 100%;
    min-width: 600px;
    border-collapse: collapse;
    font-size: 0.9375rem;
}
.data-table th,
.data-table td {
    text-align: left;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--color-border, #e5e7eb);
}
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table th {
    background: #f9fafb;
    color: var(--color-text-secondary, #6b7280);
    font-weight: 500;
    font-size: 0.8125rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.data-table-actions { text-align: right; }
.muted { color: var(--color-text-secondary, #6b7280); }

/* Code block + copy row — used for project ID and snippets. */
.code-block {
    background: #1f2937;
    color: #f3f4f6;
    border-radius: 6px;
    padding: 1rem;
    overflow-x: auto;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
    margin: 0;
    white-space: pre;
}

/* JSON syntax highlighting in the Inspect Event modal's Raw tab.
   Tuned for the dark slate-900 code-block background. Punctuation
   (braces, commas, colons) inherits the default light gray text. */
.json-key  { color: #93c5fd; }                  /* keys — light blue */
.json-str  { color: #86efac; }                  /* string values — green */
.json-num  { color: #fcd34d; }                  /* numbers — yellow */
.json-bool { color: #c084fc; }                  /* true/false — purple */
.json-null { color: #9ca3af; font-style: italic; } /* null — muted italic */
.copy-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.copy-target {
    flex: 1;
    min-width: 0;
    background: #f3f4f6;
    padding: 0.5rem 0.75rem;
    border-radius: 4px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
    font-size: 0.8125rem;
    word-break: break-all;
}

/* Buttons (primary already exists as `button` default; adding utility variants). */
.btn-primary {
    background: var(--color-brand, #1e3a5f);
    color: #fff;
    padding: 0.5rem 1rem;
    font-size: 0.9375rem;
    font-weight: 500;
    border-radius: 6px;
    border: none;
    cursor: pointer;
}
.btn-primary:hover { filter: brightness(1.1); }

/* Plus-icon modifier — prefixes any "Create X" / "New X" CTA so the action
   reads as additive at a glance. Pseudo-element keeps the HTML clean. */
.btn-plus::before {
    content: "+";
    display: inline-block;
    margin-right: 0.5rem;
    font-weight: 700;
    font-size: 1.05em;
    line-height: 1;
}
/* Icon-only copy button (replaces the old .btn-copy text variant).
   Renders two SVGs (copy + check) with display toggled by the .copied
   class so the icon swaps to a check on success. Background is transparent
   so the button picks up the surrounding surface — works on both light
   surfaces (project cards, panels) and the dark code block (see the
   .code-block-wrap override below). */
.btn-copy-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: 1px solid transparent;
    color: inherit;
    padding: 0.375rem;
    border-radius: 4px;
    cursor: pointer;
    opacity: 0.6;
    transition: opacity 0.15s, background 0.15s, color 0.15s;
}
/* Make the inner SVGs transparent to pointer events so hovers always
   register on the <button> itself — without this, browsers can leave
   the cursor as text/default when hovering directly on an SVG path
   instead of the button's padding area. */
.btn-copy-icon svg { pointer-events: none; }
.btn-copy-icon:hover {
    opacity: 1;
    background: rgba(0, 0, 0, 0.05);
}
.btn-copy-icon .icon-check { display: none; }
.btn-copy-icon.copied {
    opacity: 1;
    color: #10b981;
}
.btn-copy-icon.copied .icon-copy { display: none; }
.btn-copy-icon.copied .icon-check { display: block; }

/* Code block wrapper — positions a copy icon in the top-right corner of a
   dark code block. The override flips the hover background to white so the
   button stays visible against the dark surface. */
.code-block-wrap { position: relative; }
.code-block-wrap .btn-copy-icon {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    color: #d1d5db;
}
.code-block-wrap .btn-copy-icon:hover { background: rgba(255, 255, 255, 0.1); }
.btn-danger {
    background: #dc2626;
    color: #fff;
    padding: 0.5rem 1rem;
    font-size: 0.9375rem;
    border-radius: 6px;
    border: none;
    cursor: pointer;
}
.btn-danger:hover { background: #b91c1c; }
/* Small inline danger button used in table action columns. Shaped
   distinctly enough to read as a button (border + padding) while staying
   visually quieter than a full .btn-danger filled button — the row is
   read-mostly, the action is occasional. */
.btn-revoke {
    background: transparent;
    color: #dc2626;
    border: 1px solid #fecaca;
    border-radius: 4px;
    font-size: 0.875rem;
    font-weight: 500;
    padding: 0.25rem 0.625rem;
    margin: 0;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}
.btn-revoke:hover {
    background: #fef2f2;
    border-color: #dc2626;
}
/* Dismiss button — the "×" on dismissible banners (e.g., new-key reveal).
   Sized so the hover background covers the full glyph; transparent bg
   defeats the global button:hover navy fill so it doesn't go illegible
   on the yellow banner. Hover tint uses the banner's accent border colour
   one step darker than the banner background. */
.btn-dismiss {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    margin: 0;
    background: transparent;
    border: none;
    border-radius: 4px;
    font-size: 1.25rem;
    line-height: 1;
    color: inherit;
    cursor: pointer;
    transition: background 0.15s;
}
.btn-dismiss:hover { background: rgba(0, 0, 0, 0.08); }
.key-banner .btn-dismiss:hover { background: #fcd34d; }

/* Toggleable modal — opt-in via .modal-toggle so the always-visible
   verify-email modal pattern keeps working unchanged. */
.modal-overlay.modal-toggle { display: none; }
.modal-overlay.modal-toggle.open { display: flex; }

/* New-key reveal banner — prominent, dismissible, sits above the keys table.
   position:relative anchors the absolutely-positioned dismiss button to the
   banner. Right padding on the body keeps the title text from sliding under
   the dismiss button on narrow widths. */
.key-banner {
    position: relative;
    background: var(--color-warning-bg, #fef3c7);
    border: 1px solid #fcd34d;
    border-left: 4px solid #d97706;
    border-radius: 6px;
    padding: 1rem 1.25rem;
    margin-bottom: 1.5rem;
}
.key-banner strong {
    color: #92400e;
    font-size: 1rem;
}
.key-banner p {
    margin: 0.25rem 0 0.75rem;
    color: #92400e;
    font-size: 0.875rem;
}
.key-banner-body {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding-right: 2rem;
}
.key-banner .btn-dismiss {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    color: #92400e;
}

/* Form helpers. */
.hint {
    color: var(--color-text-secondary, #6b7280);
    font-size: 0.8125rem;
    margin: 0.25rem 0 0;
}
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Mobile tweaks. */
@media (max-width: 640px) {
    .page-header-inner {
        flex-direction: column;
        align-items: stretch;
        padding: 1.25rem;
    }
    .page-body { padding-left: 1.25rem; padding-right: 1.25rem; }
    /* Tabs wrap into rows of up to 3 on mobile rather than scrolling
       horizontally. Five+ tabs in one scrollable strip looked cramped
       and hid trailing items off the right edge — wrapping makes them
       all reachable without horizontal scroll. Grid (vs flex-wrap)
       keeps the columns evenly sized so text centers cleanly. */
    .tabs-inner {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 0;
        padding: 0;
        overflow-x: visible;
    }
    .tab {
        text-align: center;
        padding: 0.625rem 0.5rem;
        font-size: 0.875rem;
    }
    .data-table { font-size: 0.875rem; }
    .data-table th, .data-table td { padding: 0.5rem 0.75rem; }
    .tab-section-header { flex-direction: column; align-items: stretch; }
}

/* =====================================================================
   Events tab — list view, time-range filter, live-poll banner, detail
   modal. The table reuses .data-table from the keys page; everything
   below is events-specific layout.
   ===================================================================== */

/* Filter toolbar — column container that stacks two inner rows:
   `.time-row` (time-range presets + custom-range pickers) on top, then
   `.filter-bar` (column-filter dropdowns + Apply) below. Keeping the
   two rows in separate flex contexts means the time presets never
   wrap up next to a column dropdown when the form is wide enough. */
.events-toolbar {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.75rem;
    margin: 0.5rem 0;
}

/* Time-range row inside the filter form — preset chip strip with the
   custom-range datetime pickers tucked beside it when "Custom" is the
   selected preset. */
.time-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem;
}

/* The `hidden` HTML attribute normally collapses an element via the
   UA's `[hidden] { display: none }` rule, but the class selector
   above has equal specificity and wins on cascade order. Pin
   `display: none` for hidden toolbars so the collapsible filter form
   actually collapses. */
.events-toolbar[hidden] { display: none; }

/* Set-filters panel — three tabs (AI, Filters, Query). The outer
   container is the element the Set filters toggle hides; the tablist
   sits at the top and tab panels stack vertically beneath it. */
.filter-tabs {
    margin: 0 0 0.75rem;
    padding: 0.5rem 0.75rem 0.75rem;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 8px;
    background: var(--color-surface, #fafafa);
}
.filter-tabs[hidden] { display: none; }

.filter-tablist {
    display: flex;
    gap: 0.25rem;
    border-bottom: 1px solid var(--color-border, #e5e7eb);
    margin: 0 0 0.75rem;
}
.filter-tab {
    background: transparent;
    border: none;
    color: var(--color-text-secondary, #6b7280);
    font-size: 0.875rem;
    font-weight: 500;
    padding: 0.5rem 0.875rem;
    margin: 0 0 -1px;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s;
}
.filter-tab:hover {
    background: transparent;
    color: var(--color-text);
    border-bottom-color: var(--color-border, #e5e7eb);
}
.filter-tab.active,
.filter-tab.active:hover {
    background: transparent;
    color: var(--color-brand);
    border-bottom-color: var(--color-brand);
}
.filter-panel { display: none; }
.filter-panel.active { display: block; }
/* Rows inside a panel stack with breathing room — needed because the
   panel sits inside a flex `column` form whose `gap` only spaces
   sibling panels, not the rows nested inside one. */
.filter-panel > * + * { margin-top: 0.75rem; }

/* AI tab — natural-language prompt. Label sits above the input row;
   the input row uses flex so input grows and Apply stays content-
   sized. Beta caption sits muted beneath the row. */
.nlp-form { display: block; }
.nlp-label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.25rem;
    font-size: 0.875rem;
    color: var(--color-text-secondary, #6b7280);
}
.nlp-beta-badge {
    /* Tighter than other badges since it rides on a one-word label. */
    padding: 0 0.4rem;
    line-height: 1.4;
}
.nlp-input {
    display: block;
    width: 100%;
    padding: 0.5rem 0.75rem;
    font-size: 0.875rem;
    border: 1px solid var(--color-border, #d1d5db);
    border-radius: 6px;
    background: var(--color-input-bg, #fff);
    color: var(--color-text);
}

/* Search button row inside the AI and Query tabs. Sits beneath the
   input on its own line so the button never has to compete with the
   input for horizontal room. */
.filter-panel-actions {
    display: flex;
    margin-top: 0.5rem;
}

/* In-button loading spinner. JS sets data-loading="true" + swaps the
   button label to "<spinner> Searching…" on submit so the user sees
   feedback during the round-trip — most visible on the AI tab where
   the LLM call adds noticeable latency. */
.btn-spinner {
    display: inline-block;
    width: 12px;
    height: 12px;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: btn-spin 0.6s linear infinite;
    vertical-align: -1px;
    margin-right: 0.375rem;
}
@keyframes btn-spin {
    to { transform: rotate(360deg); }
}
button[data-loading="true"] {
    cursor: progress;
}
/* Metadata / changed-fields section has more vertical content above
   the button (heading + multi-row form), so it needs a bit more
   breathing room between the last form row and the action button. */
.add-filter-inline .filter-panel-actions {
    margin-top: 1rem;
}

/* Tab-scoped error — used by the AI and Query tabs to surface load
   failures inline (above the Search button) rather than as a
   top-of-page banner detached from the input that caused it. */
.nlp-error-inline {
    margin: 0.5rem 0 0;
}

/* "You asked: ..." banner — model interpretation that lives inside
   the AI tab so the input, error, and interpretation all sit
   together. Quiet styling so it doesn't compete with the form. */
.nlp-banner {
    position: relative;
    margin-top: 0.5rem;
    padding: 0.75rem 2.25rem 0.75rem 0.875rem;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    background: #f9fafb;
    font-size: 0.875rem;
    color: var(--color-text);
}
.nlp-banner-body { padding: 0; }
.nlp-banner-body p { margin: 0; }
.nlp-banner-body p + p { margin-top: 0.25rem; }
.nlp-banner-translated {
    font-size: 0.8125rem;
}
.nlp-banner-translated code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.8125rem;
    padding: 0.05rem 0.3rem;
    background: #ffffff;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 4px;
    color: var(--color-text);
}
/* Unsupported-filter list — uses .error styling. Reset the inner ul
   so bullets sit cleanly inside the error padding. */
.nlp-unsupported { margin-top: 0.5rem; margin-bottom: 0; }
.nlp-unsupported ul {
    margin: 0;
    padding-left: 1.25rem;
}
.nlp-unsupported li { margin: 0; }
.nlp-banner-q {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.8125rem;
}
.nlp-banner-dismiss {
    position: absolute;
    top: 0.25rem;
    right: 0.25rem;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    background: transparent;
    border: none;
    color: var(--color-text-secondary, #6b7280);
    font-size: 1.125rem;
    line-height: 1;
    cursor: pointer;
    border-radius: 4px;
}
.nlp-banner-dismiss:hover {
    background: var(--color-border, #e5e7eb);
    color: var(--color-text);
}

/* Inline "Add filter" section in the Filters tab — previously a
   modal, now sits as a labelled sub-panel beneath the column-filter
   row. Visual divider sets it apart from the row above without
   shouting. */
.add-filter-inline {
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border, #e5e7eb);
}
.add-filter-heading {
    margin: 0 0 0.25rem;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--color-text);
}
.add-filter-inline .hint {
    margin: 0 0 0.75rem;
    font-size: 0.75rem;
    color: var(--color-text-secondary, #6b7280);
}
.add-filter-form {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.add-filter-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.875rem;
}
.add-filter-label {
    flex: 0 0 8rem;
    color: var(--color-text-secondary, #6b7280);
}
.add-filter-row input[type="text"],
.add-filter-row select {
    flex: 1;
    min-width: 0;
    padding: 0.375rem 0.5rem;
    font-size: 0.875rem;
    border: 1px solid var(--color-border, #d1d5db);
    border-radius: 6px;
    background: var(--color-input-bg, #fff);
    color: var(--color-text);
}
.add-filter-variant {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.add-filter-error {
    margin: 0;
    font-size: 0.8125rem;
    color: var(--color-error-text);
}

/* Query tab — power-user DSL input. Label above, monospace input
   full-width, short grammar hint below. */
.advanced-query-label {
    display: block;
    margin-bottom: 0.25rem;
    font-size: 0.875rem;
    color: var(--color-text-secondary, #6b7280);
}
.advanced-query-input {
    display: block;
    width: 100%;
    padding: 0.5rem 0.75rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.875rem;
    border: 1px solid var(--color-border, #d1d5db);
    border-radius: 6px;
    background: var(--color-input-bg, #fff);
    color: var(--color-text);
}
/* Controls row — Live left, Select columns right. Sits between the
   filter toolbar and the events table so changes to the table view
   (live polling, column visibility) live next to the data they
   affect. */
.events-controls {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin: 0 0 0.75rem;
}

/* Time-range preset chips. Active preset reads as filled (brand color);
   inactive presets read as quiet outlined chips. */
.time-range-presets {
    display: inline-flex;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    overflow: hidden;
}
.range-preset {
    padding: 0.375rem 0.875rem;
    font-size: 0.875rem;
    color: var(--color-text-secondary, #6b7280);
    text-decoration: none;
    border-right: 1px solid var(--color-border, #e5e7eb);
    transition: background 0.15s, color 0.15s;
}
.range-preset:last-child { border-right: none; }
.range-preset:hover { background: #f9fafb; color: var(--color-text); }
.range-preset-active,
.range-preset-active:hover {
    background: var(--color-brand);
    color: #fff;
}

/* Custom range datetime inputs — collapse-and-expand isn't worth the
   JS, so they sit inline with the toolbar when the user selects Custom. */
.custom-range {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    font-size: 0.875rem;
}
.custom-range label {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--color-text-secondary, #6b7280);
    margin: 0;
}
.custom-range input[type="datetime-local"] {
    padding: 0.375rem 0.5rem;
    font-size: 0.8125rem;
    margin: 0;
    width: auto;
}

/* Live toggle — checkbox styled as a pill with a status dot. The dot
   pulses when polling is active to give a real-time-feeling cue. */
.live-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.875rem;
    color: var(--color-text-secondary, #6b7280);
    cursor: pointer;
    user-select: none;
    margin: 0;
}
.live-toggle input { display: none; }
.live-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #d1d5db;
    transition: background 0.15s, box-shadow 0.15s;
}
.live-toggle input:checked ~ .live-dot {
    background: #10b981;
    box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.15);
    animation: live-pulse 2s ease-in-out infinite;
}
@keyframes live-pulse {
    0%, 100% { box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.15); }
    50%      { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0.05); }
}

/* Polling banner — appears above the table when the poll endpoint
   reports new events. Click anywhere on it to reload with the latest.
   Content-sized + auto-centered so the brand-colored pill doesn't
   stretch the full table width. */
.poll-banner {
    display: block;
    width: max-content;
    max-width: 100%;
    padding: 0.5rem 0.875rem;
    margin: 0.5rem auto 1rem;
    background: var(--color-brand);
    color: #fff;
    border-radius: 6px;
    text-align: center;
    text-decoration: none;
    font-size: 0.875rem;
    font-weight: 500;
    transition: filter 0.15s;
}
.poll-banner:hover { filter: brightness(1.1); }

/* Events table rows are clickable (open the Inspect Event modal). Hover
   state signals interactivity; cursor pointer reinforces it. */
.events-table tbody tr {
    cursor: pointer;
}
.events-table tbody tr:hover {
    background: #f9fafb;
}
.events-table time {
    font-variant-numeric: tabular-nums;
    color: var(--color-text-secondary, #6b7280);
    font-size: 0.875rem;
}

/* Load-more row is a centered button-link below the table. */
.load-more-row {
    display: flex;
    justify-content: center;
    margin: 1.5rem 0 0;
}

/* Metadata key/value table in the Inspect Event modal — proper
   column widths, padding, and row separators so keys, types, and
   values don't visually collide. Borrows the data-table look so
   it reads as part of the same design system. */
.metadata-kv-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.875rem;
    margin-top: 0.5rem;
    table-layout: fixed;
}
.metadata-kv-table thead th {
    text-align: left;
    padding: 0.5rem 0.875rem;
    background: #f9fafb;
    color: var(--color-text-secondary, #6b7280);
    font-weight: 500;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border-bottom: 1px solid var(--color-border, #e5e7eb);
}
.metadata-kv-table tbody td {
    padding: 0.625rem 0.875rem;
    border-bottom: 1px solid var(--color-border, #e5e7eb);
    vertical-align: top;
    word-break: break-word;
}
.metadata-kv-table tbody tr:last-child td { border-bottom: none; }
.metadata-kv-table tbody tr:hover { background: #fafafa; }
.metadata-kv-table .col-mdkey   { width: 30%; }
.metadata-kv-table .col-mdtype  { width: 6rem; }
.metadata-kv-table .col-mdvalue { width: auto; }
.metadata-kv-table .col-mdtype span { font-size: 0.75rem; }
.metadata-kv-table code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.8125rem;
    background: transparent;
    padding: 0;
    color: var(--color-text);
}

/* Wider modal variant for event detail — JSON payloads need horizontal
   room that the standard 420-wide modal can't give. position:relative
   anchors the absolutely-positioned dismiss button. */
.modal-wide {
    max-width: 720px;
    width: 90%;
    position: relative;
}
.event-modal h2 { margin: 0 0 0.25rem; }
.event-modal h2 code {
    font-size: 1.125rem;
    background: transparent;
    padding: 0;
}
.event-modal-dismiss {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
}
.event-modal .code-block-wrap {
    max-height: 60vh;
    overflow-y: auto;
}

/* =====================================================================
   Phase 2/3: filter bar, diff table, mobile event cards
   ===================================================================== */

/* Configurable column visibility. Each <th>/<td> on the events table
   carries a `col-<name>` class; the table-level `hide-col-<name>` class
   collapses both header and cells. Same rules apply on mobile because
   the specificity (.events-table.hide-col-X .col-X) beats the generic
   .events-table td { display: block } from the mobile breakpoint. */
.events-table.hide-col-time   .col-time   { display: none; }
.events-table.hide-col-action .col-action { display: none; }
.events-table.hide-col-actor  .col-actor  { display: none; }
.events-table.hide-col-target .col-target { display: none; }
.events-table.hide-col-tenant .col-tenant { display: none; }
.events-table.hide-col-result .col-result { display: none; }

/* Columns dropdown — sits in the .events-controls row, right-aligned
   via that row's `justify-content: space-between`. The `.open` class
   on the wrapper is toggled by JS; outside-click closes it via the
   same listener pattern used by the navbar dropdowns. */
.columns-wrapper {
    position: relative;
    display: inline-block;
}
.columns-trigger {
    height: 32px;
    padding: 0.375rem 0.875rem;
    font-size: 0.875rem;
    margin: 0;
}
.columns-dropdown {
    display: none;
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 0.25rem;
    background: var(--color-surface, #fff);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    min-width: 160px;
    padding: 0.5rem 0;
    z-index: 60;
}
.columns-wrapper.open .columns-dropdown { display: block; }
.columns-option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 1rem;
    font-size: 0.875rem;
    font-weight: normal;
    color: var(--color-text);
    margin: 0;
    cursor: pointer;
    user-select: none;
}
.columns-option:hover { background: #f9fafb; }
.columns-option input[type="checkbox"] { margin: 0; }
.columns-divider {
    border: none;
    border-top: 1px solid var(--color-border, #e5e7eb);
    margin: 0.5rem 0;
}
.columns-reset {
    display: block;
    width: 100%;
    padding: 0.375rem 1rem;
    background: transparent;
    color: var(--color-text-secondary, #6b7280);
    border: none;
    font-size: 0.8125rem;
    text-align: left;
    cursor: pointer;
    margin: 0;
}
.columns-reset:hover {
    background: #f9fafb;
    color: var(--color-text);
}

/* Export modal — two side-by-side format cards that act as radio
   buttons. The actual <input type="radio"> is visually hidden (still
   keyboard/screen-reader accessible) and the card's selected state is
   driven by the :has() selector. The Download button is disabled in
   the markup and re-enabled in JS once one of the radios is checked. */
.export-format-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.75rem;
    margin: 1rem 0;
}
.export-format-card {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
    padding: 1rem;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    color: inherit;
    cursor: pointer;
    user-select: none;
    transition: border-color 0.15s, background 0.15s;
    margin: 0;
}
.export-format-card:hover {
    border-color: var(--color-brand);
    background: #f9fafb;
}
.export-format-card:has(input[type="radio"]:checked) {
    border-color: var(--color-brand);
    background: rgba(30, 58, 95, 0.06);
}
.export-format-card:has(input[type="radio"]:focus-visible) {
    outline: 2px solid var(--color-brand);
    outline-offset: 1px;
}
.export-format-card input[type="radio"] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
.export-format-card strong {
    font-size: 0.9375rem;
    color: var(--color-text);
}
.export-format-card span {
    font-size: 0.8125rem;
    color: var(--color-text-secondary, #6b7280);
    line-height: 1.4;
}
@media (max-width: 480px) {
    .export-format-grid { grid-template-columns: 1fr; }
}

/* Outlined compact toolbar button — shared style for every quiet
   action in the events controls row (Set filters, Export, Select
   columns). Matches .filter-toggle's outlined treatment so the three
   buttons read as a visually consistent group instead of a mix of
   outlined and filled. */
.btn-toolbar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.375rem;
    height: 32px;
    padding: 0.375rem 0.875rem;
    background: transparent;
    color: var(--color-text);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    font-size: 0.875rem;
    font-weight: 500;
    margin: 0;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}
.btn-toolbar:hover {
    background: #f9fafb;
    border-color: var(--color-text-secondary, #6b7280);
}

/* Generic CSS tooltip — any element with data-tooltip="..." renders
   the value as a styled bubble to the RIGHT of the element on hover.
   Right-anchored placement avoids the left-edge truncation problem
   that hits left-aligned controls (the Live toggle in the events
   toolbar) when a centered-above tooltip extends past the viewport. */
[data-tooltip] {
    position: relative;
}
[data-tooltip]::after {
    content: attr(data-tooltip);
    position: absolute;
    top: 50%;
    left: calc(100% + 0.5rem);
    transform: translateY(-50%);
    padding: 0.5rem 0.75rem;
    background: #1f2937;
    color: #f3f4f6;
    border-radius: 4px;
    font-size: 0.75rem;
    font-weight: normal;
    line-height: 1.4;
    white-space: normal;
    width: max-content;
    max-width: 260px;
    text-align: left;
    pointer-events: none;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s, visibility 0s linear 0.15s;
    z-index: 100;
}
[data-tooltip]::before {
    content: "";
    position: absolute;
    top: 50%;
    left: calc(100% + 0.125rem);
    transform: translateY(-50%);
    border: 4px solid transparent;
    border-right-color: #1f2937;
    pointer-events: none;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s, visibility 0s linear 0.15s;
    z-index: 100;
}
[data-tooltip]:hover::after,
[data-tooltip]:hover::before,
[data-tooltip]:focus-visible::after,
[data-tooltip]:focus-visible::before {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.15s, visibility 0s;
}

/* Left-anchored variant — bubble + arrow flip to render to the LEFT of
   the element. Used on right-aligned controls (e.g. the Rotate secret
   button in a tab-section-header) where the default right-anchored
   placement would extend past the viewport. */
.tooltip-left[data-tooltip]::after {
    left: auto;
    right: calc(100% + 0.5rem);
}
.tooltip-left[data-tooltip]::before {
    left: auto;
    right: calc(100% + 0.125rem);
    border-right-color: transparent;
    border-left-color: #1f2937;
}

/* Inspect Event subtitle — stacks action / timestamp / ID on
   separate lines. Each span is block-display so the lines flow top
   to bottom; the parent `<p>` keeps the muted color from `.hint`. */
.inspect-subtitle {
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
}
.inspect-subtitle-line {
    display: block;
}

/* Tabs inside the Inspect Event modal — only rendered for mutation
   events (raw + diff). Underline-style tabs so they sit visually below
   the modal heading without competing with it. JS toggles the .active
   class on the matching tab/panel pair. */
.inspect-tabs {
    display: flex;
    gap: 0.25rem;
    border-bottom: 1px solid var(--color-border, #e5e7eb);
    margin: 1rem 0 0.75rem;
}
.inspect-tab {
    background: transparent;
    border: none;
    color: var(--color-text-secondary, #6b7280);
    font-size: 0.875rem;
    font-weight: 500;
    padding: 0.5rem 0.875rem;
    margin: 0 0 -1px;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s;
}
.inspect-tab:hover {
    background: transparent;
    color: var(--color-text);
    border-bottom-color: var(--color-border, #e5e7eb);
}
.inspect-tab.active,
.inspect-tab.active:hover {
    background: transparent;
    color: var(--color-brand);
    border-bottom-color: var(--color-brand);
}
.inspect-panel { display: none; }
.inspect-panel.active { display: block; }

/* Right cluster on the controls row — groups the Set filters toggle
   with the Select columns dropdown so both right-side controls share a
   single visual unit, opposite the Live toggle on the left. */
.controls-right {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

/* Active filter chips row — sits between the controls row and the
   collapsible form. Shown only when filters are active so the empty
   state stays compact. Clear all link pinned to the right. */
.filter-chips-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin: 0 0 0.5rem;
}

.filter-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.375rem 0.875rem;
    background: transparent;
    color: var(--color-text);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    font-size: 0.875rem;
    font-weight: 500;
    margin: 0;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}
.filter-toggle:hover {
    background: #f9fafb;
    border-color: var(--color-text-secondary, #6b7280);
}
.filter-count {
    background: var(--color-brand);
    color: #fff;
    border-radius: 9999px;
    padding: 0.0625rem 0.4375rem;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.4;
    min-width: 1.25rem;
    text-align: center;
}
.filter-toggle-caret {
    font-size: 1.125rem;
    line-height: 1;
    color: var(--color-text-secondary, #6b7280);
    transition: transform 0.15s;
}
.filter-toggle[aria-expanded="true"] .filter-toggle-caret {
    transform: rotate(180deg);
}

/* Active-filter chips — one per active column filter. The whole chip
   is a link to a URL with that one filter cleared (other filters
   preserved); the × is purely a visual affordance. */
.active-filter-chips {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0.375rem;
}
.filter-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.25rem 0.5rem 0.25rem 0.625rem;
    background: #f3f4f6;
    color: var(--color-text);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 4px;
    font-size: 0.8125rem;
    text-decoration: none;
    line-height: 1.4;
    transition: background 0.15s, border-color 0.15s;
}
.filter-chip:hover {
    background: #e5e7eb;
}
.filter-chip .chip-x {
    color: var(--color-text-secondary, #6b7280);
    font-size: 1rem;
    line-height: 1;
}
.filter-chip:hover .chip-x { color: #dc2626; }

/* Outlined button matching .filter-toggle so the chips row reads as
   one cluster of UI controls (filter chips on the left, Clear all on
   the right). margin-right: auto pins it via the flex parent. */
.filter-clear-all {
    display: inline-flex;
    align-items: center;
    padding: 0.375rem 0.875rem;
    margin: 0;
    margin-right: auto;
    background: transparent;
    color: var(--color-text);
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}
.filter-clear-all:hover {
    background: #f9fafb;
    border-color: var(--color-text-secondary, #6b7280);
}

/* Filter bar — sits below the time-range presets. Compact dropdowns +
   inputs flow horizontally; on narrow screens they wrap into rows. */
.filter-bar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.filter-select,
.filter-input {
    padding: 0.375rem 0.625rem;
    font-size: 0.875rem;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 4px;
    background: var(--color-surface, #fff);
    color: var(--color-text);
    font-family: inherit;
    margin: 0;
    height: 32px;
    box-sizing: border-box;
}
.filter-select { width: 9rem; }
/* Actor search is wider than dropdowns to fit the multi-field placeholder
   without truncating ("Actor (id, name, email)"). Fixed width keeps it
   from outgrowing the row when the filter bar wraps. */
.filter-input  { width: 13rem; }
.filter-select:focus,
.filter-input:focus {
    outline: none;
    border-color: var(--color-brand);
}

.filter-clear {
    color: var(--color-text-secondary, #6b7280);
    text-decoration: none;
    font-size: 0.8125rem;
    margin-left: 0.5rem;
}
.filter-clear:hover {
    color: var(--color-brand);
    text-decoration: underline;
}

/* Time-range presets live inside the filter form's filter-bar; no
   special hoisting needed any more. The base .time-range-presets rule
   above provides the inline-flex chip-strip styling. */

/* Section heading inside the event detail modal — separates "Change"
   diff and "Raw event" JSON. Sits flush left, below the modal title. */
.event-section-heading {
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-text-secondary, #6b7280);
    margin: 1.25rem 0 0.5rem;
}

/* Diff table — side-by-side before/after with line-level color coding.
   Removed lines (left) get a red wash; added lines (right) get green;
   unchanged lines render flush so the eye scans changes immediately. */
.diff-wrap {
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    overflow: auto;
    max-height: 50vh;
    background: var(--color-surface, #fff);
}
.diff-table {
    width: 100%;
    border-collapse: collapse;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
}
.diff-table thead th {
    position: sticky;
    top: 0;
    background: #f9fafb;
    color: var(--color-text-secondary, #6b7280);
    font-weight: 500;
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    text-align: left;
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--color-border, #e5e7eb);
    width: 50%;
}
.diff-table td {
    width: 50%;
    vertical-align: top;
    padding: 0;
    border-bottom: 1px solid #f3f4f6;
}
.diff-table td pre {
    margin: 0;
    padding: 0.125rem 0.75rem;
    white-space: pre-wrap;
    word-break: break-word;
}
.diff-before-removed pre { background: #fef2f2; color: #991b1b; }
.diff-after-added    pre { background: #ecfdf5; color: #065f46; }
.diff-before-blank pre,
.diff-after-blank  pre { background: #fafafa; min-height: 1.2em; }
.diff-before-same pre,
.diff-after-same  pre { color: var(--color-text); }

/* Mobile card layout for events. Below 640px the table layout collapses
   into one card per event — column headers become labels per row, so
   nothing gets clipped or horizontally scrolled inside the modal-narrow
   viewport. Above 640px the data-table layout takes over (already styled). */
@media (max-width: 640px) {
    .events-toolbar { flex-direction: column; align-items: stretch; }
    .events-toolbar .filter-bar {
        width: 100%;
        /* Stack each filter on its own row instead of letting them
           share width. Fixed widths get reset to 100% so the inputs
           span the toolbar cleanly without truncating placeholders. */
        flex-direction: column;
        align-items: stretch;
        gap: 0.5rem;
    }
    .filter-select, .filter-input { width: 100%; flex: none; }

    /* Stack every control in the events-controls row on phones: Live
       on top, then Set filters / Export / Select columns underneath
       as a vertical button stack. Without this, Live floats to the
       left of the (column-stacked) buttons and the layout reads
       awkwardly. */
    .events-controls {
        flex-direction: column;
        align-items: stretch;
    }
    .controls-right {
        flex-direction: column;
        align-items: stretch;
        width: 100%;
    }
    .controls-right > * { width: 100%; }
    .controls-right .filter-toggle,
    .controls-right .btn-toolbar,
    .controls-right .columns-trigger {
        justify-content: center;
    }
    /* Select columns lives inside .columns-wrapper, so the wrapper
       takes 100% from the rule above but the button inside doesn't
       inherit that — make it span its wrapper explicitly. */
    .controls-right .columns-wrapper > .columns-trigger {
        width: 100%;
    }
    .controls-right .columns-dropdown {
        right: auto;
        left: 0;
        width: 100%;
    }

    /* Reset table-layout so we can repaint each row as a card. */
    .events-table { display: block; }
    .events-table thead { display: none; }
    .events-table tbody, .events-table tr, .events-table td { display: block; width: 100%; }
    .events-table tr {
        border: 1px solid var(--color-border, #e5e7eb);
        border-radius: 6px;
        margin-bottom: 0.5rem;
        background: var(--color-surface, #fff);
        padding: 0.75rem;
    }
    .events-table td {
        padding: 0.25rem 0;
        border-bottom: none;
    }
    /* Pseudo-labels: each cell prefixed with its column name, drawn from
       a data-attribute-free convention since the cells themselves are in
       a fixed order. */
    .events-table td:nth-child(1)::before { content: "Time: "; color: var(--color-text-secondary, #6b7280); }
    .events-table td:nth-child(2)::before { content: "Action: "; color: var(--color-text-secondary, #6b7280); }
    .events-table td:nth-child(3)::before { content: "Actor: "; color: var(--color-text-secondary, #6b7280); }
    .events-table td:nth-child(4)::before { content: "Target: "; color: var(--color-text-secondary, #6b7280); }
    .events-table td:nth-child(5)::before { content: "Tenant: "; color: var(--color-text-secondary, #6b7280); }
    .events-table td:nth-child(6)::before { content: "Result: "; color: var(--color-text-secondary, #6b7280); }

    /* Diff table reverts to a single column on narrow widths — side-by-
       side diffs are unreadable below ~600px. Each diffLine renders the
       two cells stacked. */
    .diff-table thead { display: none; }
    .diff-table tr { display: block; border-bottom: 1px solid #f3f4f6; }
    .diff-table td { display: block; width: 100%; }
}

/* ============================================================
   Scan history (Configuration → Setup with AI)
   ============================================================
   Compact table of every scan ever run for a project, rendered
   underneath the Connect-Your-Repo steps. Each row links to
   /projects/{id}/instrument/scans/{scan_id}/debug. */
.config-scan-history {
    margin-top: 1.25rem;
    padding-top: 1rem;
    border-top: 1px dashed var(--color-border, #e5e7eb);
}
.config-scan-history-title {
    margin: 0 0 0.25rem 0;
    font-size: 0.95rem;
}
.config-scan-history-body {
    margin: 0 0 0.75rem 0;
}
/* The table uses .data-table for the base look (uppercase tracked
   header, generous padding, border via .table-wrap) — same visual
   treatment as the events table. The rules below layer on the
   "clickable row" affordances on top. */
.config-scan-history-table {
    /* .data-table sets min-width: 600px. The scan-history columns
       are narrower than the events table; relax it so the table
       doesn't trigger horizontal scroll on common viewports. */
    min-width: 480px;
}
.config-scan-history-table tbody td {
    vertical-align: middle;
}
.config-scan-history-row {
    cursor: pointer;
    transition: background-color 0.1s ease;
}
.config-scan-history-row:hover {
    background: #f9fafb;
}
.config-scan-history-row:hover td {
    color: var(--color-text, #111827);
}
.config-scan-history-row:focus {
    outline: 2px solid var(--color-link, #2563eb);
    outline-offset: -2px;
}

/* Trailing chevron column. The › glyph signals "this row
   navigates" — a stronger affordance than hover alone, since
   the cue is visible at rest. The column sizes to its content
   so the other columns keep their natural widths. */
.config-scan-history-chevron-col {
    width: 1.5rem;
}
.config-scan-history-chevron {
    color: var(--color-text-secondary, #9ca3af);
    text-align: right;
    font-size: 1.25rem;
    line-height: 1;
    transition: color 0.1s ease, transform 0.1s ease;
}
.config-scan-history-row:hover .config-scan-history-chevron {
    color: var(--color-link, #2563eb);
    transform: translateX(2px);
}

/* Status pills used both in the scan history table and on the
   debug page. Color follows the scan's terminal vs in-flight vs
   failed disposition. */
.scan-status-pill {
    display: inline-block;
    padding: 0.125rem 0.5rem;
    border-radius: 9999px;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.4;
    background: #e5e7eb;
    color: #374151;
}
.scan-status-pr_opened { background: #dcfce7; color: #166534; }
.scan-status-in_progress { background: #dbeafe; color: #1e40af; }
.scan-status-pending { background: #f3f4f6; color: #374151; }
.scan-status-failed { background: #fee2e2; color: #991b1b; }
.scan-status-canceled { background: #f3f4f6; color: #6b7280; }

/* ============================================================
   Scan debug page
   ============================================================
   Read-only inspector layout — definition lists + plain tables
   on a content-width column. */
.scan-debug-heading {
    margin-top: 1rem;
    margin-bottom: 0.25rem;
}
.scan-debug-subhead {
    margin-top: 0;
    margin-bottom: 1rem;
}
.scan-debug-backlink {
    margin-bottom: 1.5rem;
}
.scan-debug-section {
    margin-bottom: 2rem;
    padding: 1rem 1.25rem;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 8px;
    background: #fff;
}
.scan-debug-section-title {
    margin: 0 0 0.75rem 0;
    font-size: 1rem;
    font-weight: 600;
}
.scan-debug-subtitle {
    margin: 1.25rem 0 0.5rem 0;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--color-text-secondary, #6b7280);
}
.scan-debug-meta {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.4rem 1rem;
    margin: 0;
}
.scan-debug-meta dt {
    font-weight: 600;
    color: var(--color-text-secondary, #6b7280);
    font-size: 0.875rem;
}
.scan-debug-meta dd {
    margin: 0;
    font-size: 0.875rem;
    word-break: break-all;
}
.scan-debug-meta-inline {
    margin: 0 0 0.75rem 0;
}
.scan-debug-error {
    color: #991b1b;
    background: #fee2e2;
    padding: 0.5rem 0.75rem;
    border-radius: 4px;
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 0.8125rem;
    white-space: pre-wrap;
}
.scan-debug-pre {
    margin: 0;
    padding: 0.5rem 0.75rem;
    background: var(--color-bg-subtle, #f9fafb);
    border-radius: 4px;
    font-size: 0.8125rem;
    white-space: pre-wrap;
    word-break: break-word;
}
.scan-debug-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.875rem;
}
.scan-debug-table th,
.scan-debug-table td {
    text-align: left;
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--color-border-subtle, #f3f4f6);
    vertical-align: top;
}
.scan-debug-table th {
    font-weight: 600;
    color: var(--color-text-secondary, #6b7280);
    background: var(--color-bg-subtle, #fafafa);
}

/* Agent activity (per-stage captured narration) on the scan-
   debug page. Each stage is a <details> so the user can pick
   what to expand — diff_gen is often >100KB and dominating the
   default scroll view isn't useful. */
.scan-debug-narration-intro {
    margin: 0 0 1rem 0;
}
.scan-debug-narration-stage {
    margin-bottom: 0.75rem;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 6px;
    background: #fff;
}
.scan-debug-narration-stage[open] {
    background: var(--color-bg-subtle, #fafafa);
}
.scan-debug-narration-summary {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.5rem 0.75rem;
    cursor: pointer;
    list-style: none;
    user-select: none;
}
.scan-debug-narration-summary::-webkit-details-marker { display: none; }
.scan-debug-narration-summary::before {
    content: "▸";
    margin-right: 0.5rem;
    color: var(--color-text-secondary, #6b7280);
    font-size: 0.75rem;
    transition: transform 0.1s ease;
}
.scan-debug-narration-stage[open] .scan-debug-narration-summary::before {
    content: "▾";
}
.scan-debug-narration-label {
    font-weight: 600;
    flex: 1;
}
.scan-debug-narration-meta {
    margin-left: 1rem;
}
.scan-debug-narration-trunc {
    color: #b45309;
    font-weight: 600;
}
/* Copy button inside a narration <summary> sits to the right of
   the byte-count meta. The flex layout naturally pushes it to
   the end; the small left margin separates it from the meta
   text so the icon doesn't visually fuse with the count. */
.scan-debug-narration-summary .btn-copy-icon {
    margin-left: 0.5rem;
}
.scan-debug-narration-content {
    margin: 0;
    max-height: 24rem;
    overflow-y: auto;
    padding: 0.75rem 0.875rem;
    background: #0f172a;
    color: #e2e8f0;
    border-bottom-left-radius: 6px;
    border-bottom-right-radius: 6px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.75rem;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
}
