/* Portal stylesheet — design tokens & components derived from the
   control-plane (control-plane/operator-service/.../wwwroot/css/app.css). */

/* Brand fonts, self-hosted (SIL OFL 1.1) — copied from sailesman.ai to keep the
   portal self-contained (no marketing-CDN hotlink). DM Sans = body/UI, Outfit =
   headings + wordmark. See docs/superpowers/plans/282-palette-mapping.md (#282). */
@font-face {
    font-family: "DM Sans";
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url("/fonts/dm-sans-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: "DM Sans";
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url("/fonts/dm-sans-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: "Outfit";
    font-style: normal;
    font-weight: 500 800;
    font-display: swap;
    src: url("/fonts/outfit-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: "Outfit";
    font-style: normal;
    font-weight: 500 800;
    font-display: swap;
    src: url("/fonts/outfit-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
    /* Palette derived from sailesman.ai (light theme) — see
       docs/superpowers/plans/282-palette-mapping.md (#282). */
    --bg: #fafaf9;
    --surface: #ffffff;
    --surface-2: #f2f1ea;
    --border: #e8e7d8;
    --text: #042940;
    --text-muted: #71717a;
    --accent: #005c53;
    --accent-hover: #00473f;
    --accent-tint: rgba(0, 92, 83, 0.08);
    /* Accent used as TEXT/icon colour. Same as --accent in light; the dark
       override is lightened so accent-as-text clears WCAG AA on dark surfaces. */
    --accent-text: #005c53;
    --danger: #dc2626;
    --danger-tint: rgba(220, 38, 38, 0.12);
    --danger-bg: #fff1f2;
    --danger-border: #fecdd3;
    --danger-text: #9f1239;
    --success-bg: #ecfdf5;
    --success-border: #a7f3d0;
    --success-text: #065f46;
    --radius: 4px;
    /* Height of the app shell's sticky top bar. Load-bearing beyond the bar itself: the sidebar
       brand row matches it so both header rows line up, the preview shell sizes itself against it,
       and anything sticking below it (#467's section nav) offsets by it. */
    --topbar-height: 56px;
    --transition: 150ms ease;
    --shadow-sm: 0 1px 2px rgba(17, 24, 39, 0.06);
    --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 24px rgba(0, 0, 0, 0.04);
    color-scheme: light dark;
}

[data-theme="dark"] {
    /* Teal-navy dark, derived from sailesman.ai's own dark theme (#282). */
    --bg: #021824;
    --surface: #042940;
    --surface-2: #08314b;
    --border: #0a3550;
    --text: #e8f0f3;
    --text-muted: #7aadbd;
    --accent: #14857a;
    --accent-hover: #22a899;
    --accent-tint: rgba(74, 173, 160, 0.15);
    --accent-text: #4aada0;   /* lightened: ≥ 4.5:1 on --surface */
    --danger: #ff6b6b;
    --danger-tint: rgba(248, 113, 113, 0.18);
    --danger-bg: rgba(248, 113, 113, 0.1);
    --danger-border: rgba(248, 113, 113, 0.4);
    --danger-text: #fca5a5;
    --success-bg: rgba(16, 185, 129, 0.12);
    --success-border: rgba(16, 185, 129, 0.4);
    --success-text: #6ee7b7;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
    --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.3);
}

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

html,
body {
    margin: 0;
    padding: 0;
    min-height: 100%;
    /* No page-level scroll on the app shell: the content column is the sole scroll
       container (see .content-wrapper). Stops the whole page — and with it the sticky
       header — rubber-banding on overscroll (#347). */
    overscroll-behavior: none;
}

body {
    font-family: "DM Sans", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
    font-size: 14px;
    line-height: 1.5;
    color: var(--text);
    background: var(--bg);
}

a {
    color: var(--accent-text);
    text-decoration: none;
}

a:hover {
    color: var(--accent-hover);
}

button,
input,
select,
textarea {
    font: inherit;
}

/* Headings + the wordmark use the brand display face (Outfit); body/table text
   stays on DM Sans for scanning legibility at 14px. */
h1,
h2 {
    font-family: "Outfit", "DM Sans", "Segoe UI", sans-serif;
}

h1 {
    font-size: 1.5rem;
    font-weight: 700;
    margin: 0 0 1rem;
}

h2 {
    font-size: 1.15rem;
    font-weight: 600;
    margin: 1.5rem 0 0.75rem;
}

/* ----- Layout: authenticated shell ----- */
.portal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    height: 56px;
    padding: 0 1.5rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.portal-header__brand {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    color: var(--text);
    font-family: "Outfit", "DM Sans", "Segoe UI", sans-serif;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    font-size: 0.8125rem;
}

.portal-header__brand-icon {
    display: inline-flex;
    color: var(--accent-text);
}

.portal-header__nav a {
    color: var(--text-muted);
    font-size: 0.9rem;
}

.portal-header__nav a:hover {
    color: var(--text);
}

.portal-main {
    max-width: 48rem;
    margin: 2.5rem auto;
    padding: 0 1.5rem;
}

/* ----- Layout: tenant sidebar shell (#189) ----- */
.app {
    --sidebar-width: 248px;
    display: flex;
    height: 100vh;
    overflow: hidden;
}

.skip-link {
    position: absolute;
    left: -999px;
    top: 0;
    z-index: 100;
    padding: 0.5rem 0.9rem;
    background: var(--accent);
    color: #fff;
    border-radius: 0 0 var(--radius) 0;
}

.skip-link:focus {
    left: 0;
    color: #fff;
}

.portal-sidebar {
    width: var(--sidebar-width);
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border-right: 1px solid var(--border);
}

.portal-sidebar__brand {
    display: flex;
    align-items: center;
    /* Match the main-content top bar so both header rows line up exactly. */
    height: var(--topbar-height);
    padding: 0 1rem;
    border-bottom: 1px solid var(--border);
}

.portal-sidebar__brand-link {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    color: var(--text);
    font-family: "Outfit", "DM Sans", "Segoe UI", sans-serif;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    font-size: 0.8125rem;
}

.portal-sidebar__brand-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: 7px;
    background: var(--accent-tint);
    color: var(--accent-text);
    flex-shrink: 0;
}

.portal-nav {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.75rem 0;
    flex: 1;
    overflow-y: auto;
}

.portal-nav__heading {
    display: block;
    margin: 0;
    padding: 0.3rem 1rem 0.5rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* Single-shop heading is a link when its shop isn't active yet (#346) so an
   account-level page can still enter it; still reads as the section heading. */
.portal-nav__heading--link {
    text-decoration: none;
}

.portal-nav__heading--link:hover {
    color: var(--text);
}

.portal-nav__empty {
    margin: 0;
    padding: 0.2rem 1rem;
    font-size: 0.82rem;
    color: var(--text-muted);
}

/* Member (non-owner) read-only cue (#179): explains why an invited member sees the
   read surfaces but not the owner's management links. A quiet badge, not an alarm. */
.portal-nav__role {
    display: inline-block;
    margin: 0 1rem 0.4rem;
    padding: 0.15rem 0.5rem;
    border-radius: 0.4rem;
    background: var(--surface-muted, rgba(148, 163, 184, 0.16));
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* The switcher sits at the top of the sidebar when the owner has more than one
   shop (#346). Its label reads as a section heading, matching .portal-nav__heading. */
.portal-shop-switcher-field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    padding: 0.3rem 1rem 0.6rem;
}

.portal-shop-switcher__label {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.portal-shop-switcher {
    width: 100%;
    padding: 0.45rem 0.6rem;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    cursor: pointer;
}

.portal-shop-switcher:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.auth-alt {
    margin: 1rem 0 0;
    font-size: 0.9rem;
    color: var(--text-muted);
}

.portal-subnav {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    padding-bottom: 0.4rem;
}

.portal-subnav .portal-nav-link {
    padding-left: 1.6rem;
    font-size: 0.875rem;
}

.portal-nav-link {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.6rem 1rem;
    color: var(--text-muted);
    border-left: 3px solid transparent;
    transition:
        color var(--transition),
        background var(--transition),
        border-color var(--transition);
}

.portal-nav-link__icon {
    display: inline-flex;
    flex-shrink: 0;
    color: inherit;
}

.portal-nav-link:hover {
    color: var(--text);
    background: var(--surface-2);
}

.portal-nav-link.active {
    color: var(--text);
    background: var(--accent-tint);
    border-left-color: var(--accent);
}

.portal-nav-link.active .portal-nav-link__icon {
    color: var(--accent-text);
}

/* Config-hub grouping (#307): the four assistant-config surfaces (Assistant,
   Widget, Appearance, API keys) collapse under one "Configuration" section — a
   native <details>. The summary reads as a section sub-heading (reusing the
   weight/size/colour language of .portal-nav__heading) and hosts its own caret,
   so the default disclosure marker is suppressed. The group renders open by
   default; owners keep the native collapse affordance with no JS. */
.portal-nav__group {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}

.portal-nav__group-summary {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.6rem 1rem;
    color: var(--text-muted);
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    border-left: 3px solid transparent;
    cursor: pointer;
    list-style: none;
    transition:
        color var(--transition),
        background var(--transition);
}

/* Suppress the default disclosure triangle; the caret span is our own marker. */
.portal-nav__group-summary::-webkit-details-marker {
    display: none;
}

.portal-nav__group-summary:hover {
    color: var(--text);
    background: var(--surface-2);
}

.portal-nav__group-summary:focus-visible {
    outline: 2px solid var(--accent-text);
    outline-offset: 2px;
}

.portal-nav__group-caret {
    display: inline-flex;
    flex: 0 0 auto;
    margin-left: auto;
    color: inherit;
    transition: transform var(--transition);
}

.portal-nav__group[open] .portal-nav__group-caret {
    transform: rotate(90deg);
}

/* Nested config links indent a step deeper than the flat top-level links
   (.portal-subnav .portal-nav-link → 1.6rem) so the grouping reads visually. */
.portal-subnav .portal-nav__group .portal-nav-link {
    padding-left: 2.4rem;
}

.portal-sidebar__footer {
    margin-top: auto;
    padding: 0.6rem 0.5rem 1rem;
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.portal-sidebar__account-link,
.portal-sidebar__signout-btn {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    padding: 0.55rem 0.7rem;
    border-radius: var(--radius);
    color: var(--text-muted);
    font-size: 0.875rem;
    font-weight: 600;
    text-align: left;
}

.portal-sidebar__account-link:hover,
.portal-sidebar__signout-btn:hover {
    color: var(--text);
    background: var(--surface-2);
}

.portal-sidebar__account-link.active {
    color: var(--text);
    background: var(--accent-tint);
}

.portal-sidebar__signout {
    margin: 0;
}

.portal-sidebar__signout-btn {
    border: 0;
    background: transparent;
    cursor: pointer;
}

/* Theme toggle moved to the header (#346): pinned to the trailing edge, three
   unlabeled buttons (the base .theme-toggle pill styling applies). */
.portal-topbar__theme {
    margin-left: auto;
}

.content-wrapper {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    /* The content column is the scroll container so the sidebar (a sibling)
       stays pinned to the viewport; only this area scrolls. */
    overflow-y: auto;
    /* Contain the scroll chain and kill the momentum/rubber-band bounce that let the
       sticky header briefly travel out of view on overscroll (#347). */
    overscroll-behavior: contain;
}

.portal-topbar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    height: var(--topbar-height);
    padding: 0 1.25rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    /* Keep the top bar visible while the content below it scrolls. */
    position: sticky;
    top: 0;
    z-index: 30;
    flex-shrink: 0;
}

.portal-topbar__toggle {
    display: none;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
}

.portal-topbar__toggle:hover {
    background: var(--surface-2);
}

.portal-topbar__title {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text);
}

.portal-main--sidebar {
    width: 100%;
    max-width: 56rem;
    margin: 2rem auto;
    padding: 0 1.5rem;
}

.portal-scrim {
    display: none;
}

@media (max-width: 860px) {
    .portal-sidebar {
        position: fixed;
        inset: 0 auto 0 0;
        z-index: 50;
        transform: translateX(-100%);
        transition: transform var(--transition);
        box-shadow: var(--shadow-card);
    }

    body.portal-drawer-open .portal-sidebar {
        transform: translateX(0);
    }

    .portal-topbar__toggle {
        display: inline-flex;
    }

    .portal-scrim {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 40;
        background: rgba(0, 0, 0, 0.4);
        opacity: 0;
        visibility: hidden;
        transition: opacity var(--transition);
    }

    body.portal-drawer-open .portal-scrim {
        opacity: 1;
        visibility: visible;
    }

    body.portal-drawer-open {
        overflow: hidden;
    }
}

@media (prefers-reduced-motion: reduce) {
    .portal-sidebar,
    .portal-scrim {
        transition: none;
    }
}

/* ----- Layout: unauthenticated auth card ----- */
.auth-body {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
}

.auth-shell {
    width: 100%;
    max-width: 420px;
    padding: 32px 24px;
}

.auth-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 32px;
    box-shadow: var(--shadow-card);
}

.auth-brand {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 24px;
    color: var(--text);
    font-family: "Outfit", "DM Sans", "Segoe UI", sans-serif;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    font-size: 14px;
}

.auth-brand__icon {
    display: inline-flex;
    color: var(--accent-text);
}

.auth-card h1 {
    font-size: 1.5rem;
    margin: 0 0 1.25rem;
}

/* ----- Panel ----- */
.panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}

.panel-body {
    padding: 1.25rem;
}

/* ----- Disclosure (Assistant → advanced BYO, #460) ----- */
.disclosure > summary {
    cursor: pointer;
    list-style: none;
}

.disclosure > summary::-webkit-details-marker {
    display: none;
}

.disclosure-summary {
    display: inline;
}

.disclosure > summary::before {
    content: "▸";
    display: inline-block;
    margin-right: 0.5rem;
    color: var(--muted);
    transition: transform var(--transition);
}

.disclosure[open] > summary::before {
    transform: rotate(90deg);
}

/* Read-only effective prompt for a shared/global owner (#460): editable freehand is #236. */
.assistant-prompt-readonly {
    width: 100%;
    padding: 0.65rem 0.75rem;
    background: var(--surface-muted, var(--surface));
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    font: inherit;
    resize: vertical;
}

/* ----- Forms ----- */
.form-grid {
    display: grid;
    gap: 1rem;
}

.field {
    display: grid;
    gap: 0.4rem;
}

.field label {
    font-weight: 600;
}

.field input,
.field select,
.field textarea {
    width: 100%;
    padding: 0.65rem 0.75rem;
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    outline: none;
    transition:
        border-color var(--transition),
        box-shadow var(--transition);
}

.field input:focus,
.field select:focus,
.field textarea:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-tint);
}

.form-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    margin-top: 0.25rem;
}

/* ----- Buttons ----- */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    padding: 0.55rem 1rem;
    border-radius: var(--radius);
    border: 1px solid transparent;
    cursor: pointer;
    text-decoration: none;
    transition:
        background var(--transition),
        border-color var(--transition),
        color var(--transition);
}

.btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: #ffffff;
}

.btn-primary:hover:not(:disabled) {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    color: #ffffff;
}

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

.btn-secondary:hover:not(:disabled) {
    background: var(--surface-2);
    color: var(--text);
}

.btn-link {
    background: transparent;
    border-color: transparent;
    color: var(--accent-text);
    padding-left: 0.25rem;
    padding-right: 0.25rem;
}

.btn-link:hover:not(:disabled) {
    color: var(--accent-hover);
    text-decoration: underline;
}

.btn:disabled,
.btn[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ----- Promoted star toggle (mirrors operator-service's .btn-star) ----- */
.btn-star {
    appearance: none;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius);
    padding: 0.25rem;
    cursor: pointer;
    color: var(--text-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: middle;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.btn-star:hover {
    background: var(--surface-2);
    color: var(--text);
}

.btn-star--on {
    color: #f5a623;
}

.btn-star--on:hover {
    color: #d18a17;
    background: rgba(245, 166, 35, 0.12);
}

/* ----- Notices & validation ----- */
.notice {
    padding: 0.85rem 1rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    margin: 0 0 1rem;
}

.notice-muted {
    color: var(--text-muted);
}

.notice-success {
    background: var(--success-bg);
    border-color: var(--success-border);
    color: var(--success-text);
}

.auth-error {
    background: var(--danger-bg);
    border: 1px solid var(--danger-border);
    color: var(--danger-text);
    padding: 0.85rem 1rem;
    border-radius: var(--radius);
    margin: 0 0 1rem;
}

.field-validation-error,
.validation-summary-errors {
    color: var(--danger);
}

.validation-summary-errors ul {
    margin: 0;
    padding-left: 1.2rem;
}

/* ----- Account page ----- */
.account-stack {
    display: grid;
    gap: 1.25rem;
}

.account-profile {
    display: grid;
    gap: 0.75rem;
    margin: 0;
}

.account-profile__row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem 1rem;
}

.account-profile__label {
    min-width: 5rem;
    font-weight: 600;
    color: var(--text-muted);
}

.account-profile__value {
    margin: 0;
    color: var(--text);
}

/* Billing folded into the account surface (#352). */
.account-billing__scope {
    margin: 0 0 1rem;
    color: var(--text-muted);
}

.account-billing__default {
    color: var(--text-muted);
    font-size: 0.85em;
}

.panel-body h3 {
    margin: 1.75rem 0 0.75rem;
    font-size: 1.05rem;
}

.role-badge {
    display: inline-flex;
    align-items: center;
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 600;
    background: var(--accent-tint);
    color: var(--accent-text);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.page-links {
    margin-top: 1.5rem;
}

/* ----- Theme toggle (#185) ----- */
.portal-header__actions {
    display: flex;
    align-items: center;
    gap: 1rem;
}

.auth-card__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 24px;
}

.auth-card__header .auth-brand {
    margin-bottom: 0;
}

.theme-toggle {
    display: flex;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--surface);
}

.theme-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.4rem 0.55rem;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    transition:
        background var(--transition),
        color var(--transition);
}

.theme-btn + .theme-btn {
    border-left: 1px solid var(--border);
}

.theme-btn:hover {
    background: var(--surface-2);
    color: var(--text);
}

.theme-btn.active {
    background: var(--accent-tint);
    color: var(--accent-text);
}

/* ----- Tenant pages (#186) ----- */
.page-back {
    display: inline-block;
    margin-bottom: 1rem;
    color: var(--text-muted);
    font-size: 0.9rem;
}

.page-subtitle {
    color: var(--text-muted);
    margin: -0.5rem 0 1.25rem;
}

.panel-body > h2:first-child {
    margin-top: 0;
}

/* In-page section nav (#467, Configuration). A sticky jump list over a page that stays one
   long-scroll document. It sticks inside .content-wrapper (the shell's sole scroll container),
   directly under .portal-topbar and BELOW it in the stack (the topbar is z-index 30).
   The negative inline margins bleed the opaque bar across .portal-main's 1.5rem padding so
   content never scrolls past it at the edges. */
.config-section-nav {
    position: sticky;
    top: var(--topbar-height);
    z-index: 20;
    display: flex;
    gap: 0.75rem;
    margin: 0 -1.5rem 1.25rem;
    padding: 0 1.5rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    /* The nav scrolls sideways when the labels outgrow a narrow viewport; the page never does. */
    overflow-x: auto;
    scrollbar-width: thin;
}

.config-section-nav a {
    padding: 0.5rem 0.25rem 0.75rem;
    font-size: 0.9rem;
    color: var(--text-muted);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    white-space: nowrap;
    transition: color var(--transition), border-color var(--transition);
}

.config-section-nav a:hover {
    color: var(--text);
}

.config-section-nav a:focus-visible {
    outline: 2px solid var(--accent-text);
    outline-offset: -2px;
}

.config-section-nav a.active {
    color: var(--accent-text);
    border-bottom-color: var(--accent-text);
}

/* Park an anchored section below the sticky topbar + nav instead of underneath them. This also
   fixes the pre-existing fragment jumps to these ids, which landed under the topbar. */
[data-section] {
    scroll-margin-top: calc(var(--topbar-height) + 3rem);
}

/* Same rule for the dashboard's Go-live card, which the onboarding-done email links straight to
   (#501). The dashboard has no second sticky layer (the section nav is the Configuration page's),
   so the topbar token plus breathing room is the whole offset — never a fresh literal, the token is
   load-bearing across the shell. */
.go-live-card {
    scroll-margin-top: calc(var(--topbar-height) + 1rem);
}

/* KPI tiles (dashboard) */
.kpi-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
    gap: 1rem;
    margin: 1.25rem 0;
}

.kpi-tile {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 1rem 1.1rem;
    display: grid;
    gap: 0.35rem;
}

.kpi-tile__label {
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

.kpi-tile__value {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--text);
}

/* Data tables (chats, api-keys, members) */
.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
}

.data-table th,
.data-table td {
    text-align: left;
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}

.data-table thead th {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
}

.data-table tbody tr:hover {
    background: var(--surface-2);
}

.data-table td .btn {
    padding: 0.3rem 0.65rem;
    font-size: 0.82rem;
}

.table-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

/* ----- Catalog search + filter toolbar (#348/#349) ----- */
.catalog-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.catalog-search {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex: 1 1 18rem;
    min-width: 0;
    padding: 0.15rem 0.15rem 0.15rem 0.6rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.catalog-search:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent);
}

.catalog-search__icon {
    display: inline-flex;
    color: var(--text-muted);
    flex-shrink: 0;
}

.catalog-search__input {
    flex: 1;
    min-width: 0;
    border: 0;
    padding: 0.4rem 0.2rem;
    background: transparent;
    color: var(--text);
    font-size: 0.9rem;
}

.catalog-search__input:focus {
    outline: none;
}

.catalog-search__clear {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: var(--radius);
    color: var(--text-muted);
    text-decoration: none;
    font-size: 1.1rem;
    line-height: 1;
    flex-shrink: 0;
}

.catalog-search__clear:hover {
    background: var(--surface-2);
    color: var(--text);
}

.catalog-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.45rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text-muted);
    font-size: 0.85rem;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
}

.catalog-toggle:hover {
    background: var(--surface-2);
    color: var(--text);
}

.catalog-toggle--on {
    border-color: var(--accent);
    background: var(--accent-tint);
    color: var(--text);
}

.catalog-count {
    margin: 0 0 0.75rem;
    color: var(--text-muted);
    font-size: 0.82rem;
}

/* ----- Bulk promote/demote selection (#339) ----- */
.catalog-bulk {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 0.75rem;
}

.catalog-bulk__count {
    margin-right: auto;
    color: var(--text-muted);
    font-size: 0.82rem;
}

/* Keep the select column tight so it doesn't steal width from the product title. */
.catalog-select-col {
    width: 1%;
    white-space: nowrap;
    text-align: center;
}

.pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    margin-top: 1.25rem;
}

.pager__btn,
.pager__page {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2rem;
    padding: 0.35rem 0.6rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    text-decoration: none;
    font-size: 0.85rem;
}

.pager__btn:hover,
.pager__page:hover {
    background: var(--surface-2);
}

.pager__pages {
    display: inline-flex;
    gap: 0.3rem;
}

.pager__page.is-current {
    border-color: var(--accent);
    background: var(--accent-tint);
    font-weight: 700;
    cursor: default;
}

.pager__btn.is-disabled {
    color: var(--text-muted);
    background: var(--surface);
    opacity: 0.5;
    cursor: not-allowed;
}

.pager__status {
    margin-left: auto;
    color: var(--text-muted);
    font-size: 0.82rem;
}

/* Sortable column headers (catalog) */
.sort-link {
    color: inherit;
    text-decoration: none;
}

.sort-link:hover {
    color: var(--text);
    text-decoration: underline;
}

.sort-indicator {
    font-size: 0.65rem;
}

/* Key/value lists (product detail) */
.kv-list {
    display: grid;
    gap: 0.5rem;
    margin: 0;
}

.kv-row {
    display: grid;
    grid-template-columns: minmax(8rem, 14rem) 1fr;
    gap: 0.75rem;
    align-items: baseline;
}

.kv-row dt {
    font-weight: 600;
    color: var(--text-muted);
    overflow-wrap: anywhere;
}

.kv-row dd {
    margin: 0;
    overflow-wrap: anywhere;
}

/* Product image gallery (product detail) */
.product-gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 0.75rem;
}

.product-gallery img {
    width: 100%;
    aspect-ratio: 1;
    object-fit: contain;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/* Turn cards (chat detail) */
.turn-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: 1rem 1.1rem;
    margin-bottom: 1rem;
}

.turn-card__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.6rem;
}

.turn-card__title {
    font-size: 1rem;
    font-weight: 600;
    margin: 0;
}

.turn-card__meta {
    color: var(--text-muted);
    font-size: 0.8rem;
}

.turn-msg {
    margin: 0.4rem 0;
}

.turn-msg__role {
    font-weight: 600;
    color: var(--text-muted);
    margin-right: 0.35rem;
}

.turn-card__error {
    background: var(--danger-bg);
    border: 1px solid var(--danger-border);
    color: var(--danger-text);
    padding: 0.5rem 0.7rem;
    border-radius: var(--radius);
    margin-top: 0.5rem;
}

.turn-card pre {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.75rem;
    overflow-x: auto;
    font-size: 0.82rem;
}

/* Styled fieldsets (widget, billing) */
.form-fieldset {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1rem 1.1rem;
    margin: 0;
}

.form-fieldset legend {
    font-weight: 600;
    padding: 0 0.4rem;
    color: var(--text);
}

.check-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.4rem 0;
    font-weight: 400;
}

/* Plan options (billing) */
.plan-options {
    display: grid;
    gap: 0.75rem;
}

.plan-option {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.6rem;
    align-items: start;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-2);
    padding: 0.85rem 1rem;
    cursor: pointer;
    font-weight: 400;
}

.plan-option:hover {
    border-color: var(--accent);
    background: var(--surface);
}

.plan-option:focus-within {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.plan-option__desc {
    display: block;
    color: var(--text-muted);
    font-size: 0.85rem;
}

.status-badge {
    display: inline-flex;
    align-items: center;
    padding: 0.1rem 0.55rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    background: var(--accent-tint);
    color: var(--accent-text);
}

/* Key callout + snippet block (api-keys, widget) */
.key-callout {
    background: var(--success-bg);
    border: 1px solid var(--success-border);
    color: var(--success-text);
    padding: 1rem 1.1rem;
    border-radius: var(--radius);
    margin-bottom: 1.25rem;
}

.key-callout code {
    display: block;
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.6rem 0.75rem;
    margin: 0.5rem 0;
    word-break: break-all;
}

.snippet-block pre {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.75rem;
    overflow-x: auto;
    font-size: 0.82rem;
}

.snippet-block__actions {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.5rem;
}

/* Platform-aware install guidance (#394): a picker + one paste-step block per platform, JS shows
   one at a time by toggling the `hidden` attribute. The step blocks are plain block divs, so the UA
   [hidden]{display:none} already hides them — this rule is the explicit belt-and-braces pairing the
   unit convention asks for (a display override elsewhere must never leave a hidden block visible). */
.install-guidance {
    margin-top: 0.75rem;
}

/* Reuses `.field` for the label + select styling; only the layout tweaks are unit to the picker. */
.install-guidance__picker {
    margin-bottom: 0.5rem;
    max-width: 22rem;
}

.install-guidance [data-platform-steps][hidden] {
    display: none;
}

/* Dashboard preview-in-shell (#425 §4.1, reworked from the #376/#388 full-viewport takeover). The
   preview now renders INLINE inside the dashboard shell: the snapshot fills the content panel while
   the sidebar + topbar stay visible — the structural fix for the "my store was hacked" feeling. The
   real keyed widget (position:fixed, top z-index 2147483647) still floats in the corner over it,
   exactly as on the live storefront. The panel's bar is a proper header of the content pane (a
   persistent "Snapshot" badge + the desktop/mobile toggle), and a click on the snapshot flashes a
   hint that it's a picture — so the owner never mistakes it for a broken live storefront. A
   missing/failed screenshot degrades to a placeholder; the widget stays usable either way. Onboarding
   renders its own .onb-scene — this surface is the dashboard preview only. */
/* The shell breaks the preview out of the narrow centered content column (.portal-main--sidebar)
   so it can fill the shell's content area edge-to-edge below the sticky topbar. */
.portal-main:has(.shop-preview-shell) { max-width: none; margin: 0; padding: 0; }
.shop-preview-shell { display: flex; flex-direction: column; height: calc(100vh - var(--topbar-height)); padding: 1.25rem; box-sizing: border-box; }
.shop-preview { position: relative; flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
    border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden;
    background: var(--surface); box-shadow: 0 1px 3px rgba(4, 41, 64, 0.06); }
.shop-preview__bar { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; padding: 0.6rem 0.9rem; border-bottom: 1px solid var(--border);
    background: var(--surface-2); }
/* The persistent "Snapshot" badge (#425 snapshot signal): reads as a banner label so the owner
   knows immediately the page is a static picture, not a live storefront. */
.shop-preview__badge { display: inline-flex; align-items: center; gap: 0.4rem; flex: 0 0 auto;
    padding: 0.28rem 0.7rem; border-radius: 999px; font-size: 0.82rem; font-weight: 600;
    color: var(--accent-text); background: var(--accent-tint); border: 1px solid var(--accent-tint); }
.shop-preview__badge svg { flex: 0 0 auto; }
.shop-preview__controls { display: flex; align-items: center; gap: 0.75rem; flex-shrink: 0; }
.shop-preview__toggle { display: inline-flex; gap: 0.25rem; }
.shop-preview__vp { padding: 0.25rem 0.75rem; border: 1px solid var(--border);
    border-radius: 999px; background: transparent; color: var(--text); cursor: pointer; font-size: 0.85rem; }
.shop-preview__vp[aria-pressed="true"] { background: var(--accent); color: #fff;
    border-color: var(--accent); }
/* Readiness "still exploring" strip (#425 §4.4 / #382): a slim aria-live status line under the bar,
   above the step list (#500). Raised by the poll on every visit and never lowered again (#512) —
   the line's own emptiness is not a reason to take the list beside it off screen. */
.shop-preview__readinessbar { flex: 0 0 auto; display: flex; flex-direction: column; gap: 0.3rem;
    padding: 0.5rem 0.9rem; border-bottom: 1px solid var(--border); background: var(--surface); }
.shop-preview__readinessbar[hidden] { display: none; }
.shop-preview__readiness { margin: 0; font-size: 0.85rem; color: var(--text-muted);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* An empty line is the steady state of a healthy shop (the nudge clears after 6s), and the strip is
   a flex column with a gap — so an empty <p> would otherwise hold a row and a gap open above the
   list forever. */
.shop-preview__readiness:empty { display: none; }
.shop-preview__progress-track { position: relative; overflow: hidden; height: 4px; max-width: 20rem;
    border-radius: 999px; background: var(--border); }
.shop-preview__progress-track[hidden] { display: none; }
.shop-preview__progress-fill { position: absolute; top: 0; left: 0; height: 100%; width: 40%;
    border-radius: 999px; background: var(--accent); animation: onb-progress 1.4s ease-in-out infinite; }
/* Screen-reader-only text: each step's state, and the transition announcer. The visible marker is
   decorative, so the state has to reach the a11y tree as text. */
.shop-preview__announce { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
/* Onboarding step list (#500): a CHECKLIST, not a stepper — the steps run concurrently, so there is
   deliberately no connector line, no numbering, and no dimming that would read as "blocked". */
.preview-steps { display: flex; flex-wrap: wrap; gap: 0.2rem 1.25rem; margin: 0.15rem 0 0;
    padding: 0; list-style: none; }
.preview-steps[hidden] { display: none; }
.preview-steps__item { display: flex; align-items: flex-start; gap: 0.4rem; font-size: 0.8rem;
    color: var(--text-muted); }
.preview-steps__body { display: flex; flex-direction: column; }
.preview-steps__detail { font-size: 0.75rem; opacity: 0.85; }
.preview-steps__detail[hidden] { display: none; }
.preview-steps__marker { flex: 0 0 auto; position: relative; box-sizing: border-box;
    margin-top: 0.15rem; width: 13px; height: 13px; border-radius: 50%;
    border: 1.5px solid var(--border); background: transparent; }
.preview-steps__item[data-state="working"] { color: var(--text); }
.preview-steps__item[data-state="working"] .preview-steps__marker { border-color: var(--accent);
    animation: preview-step-pulse 1.6s ease-in-out infinite; }
.preview-steps__item[data-state="done"] .preview-steps__marker { border-color: var(--accent);
    background: var(--accent); }
/* Checkmark drawn from two borders so the marker needs no icon font or inline SVG. */
.preview-steps__item[data-state="done"] .preview-steps__marker::after { content: ""; position: absolute;
    left: 3.5px; top: 1px; width: 3px; height: 6px; border: solid #fff; border-width: 0 1.5px 1.5px 0;
    transform: rotate(45deg); }
/* Overrun, not failed: the assistant is already answering, so this stays calm and muted rather
   than borrowing the danger palette. A dash — not a spinner, not a tick. */
.preview-steps__item[data-state="attention"] .preview-steps__marker { border-style: dashed; }
.preview-steps__item[data-state="attention"] .preview-steps__marker::after { content: ""; position: absolute;
    left: 2px; top: 4.25px; width: 6px; height: 0; border-top: 1.5px solid var(--text-muted); }
/* Polling has stopped, so the list is a snapshot rather than a live view — freeze the pulse. A
   marker still turning after the last poll is a spinner over work nobody is reporting on. */
.preview-steps[data-settled] .preview-steps__marker { animation: none; }
.preview-steps__item[data-state="unknown"] { opacity: 0.6; }
.preview-steps__item[data-state="unknown"] .preview-steps__marker { border-style: dotted; }
@keyframes preview-step-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
}
.shop-preview__scroll { position: relative; flex: 1 1 auto; overflow: auto; background: var(--surface-2);
    display: flex; justify-content: center; align-items: flex-start; padding: 1.25rem; box-sizing: border-box; }
.shop-preview--loading .shop-preview__scroll { align-items: center; }
.shop-preview__scroll[data-viewport="mobile"] { padding: 1.5rem 0; align-items: flex-start; }
/* The "dimmed frame" (#425 snapshot signal): the snapshot sits in an explicit device-frame — a
   thin border, soft shadow, and a slight desaturation — so it reads as a framed picture, not a live
   page. cursor:default (never the link/text cursors a live page shows) reinforces it, and the image
   is un-selectable/un-draggable. Kept subtle so the preview still delivers the "this is my shop" wow. */
.shop-preview__frame { display: flex; flex-direction: column; align-items: center; width: 100%;
    max-width: 1440px; margin: auto; border-radius: 12px; overflow: hidden;
    border: 1px solid var(--border); background: var(--surface);
    box-shadow: 0 8px 30px rgba(4, 41, 64, 0.14); cursor: default; }
.shop-preview__scroll[data-viewport="mobile"] .shop-preview__frame { max-width: 390px; }
.shop-preview__img { display: block; width: 100%; height: auto; cursor: default;
    user-select: none; -webkit-user-drag: none; filter: saturate(0.92);
    opacity: 1; transition: opacity 300ms ease-in-out; }
/* The author display:block above beats the UA [hidden]{display:none}, so pair it explicitly —
   otherwise img.hidden=true is inert and a broken/failed screenshot stays visible next to the
   degrade placeholder (the native broken-image glyph #384 must not show). */
.shop-preview__img[hidden] { display: none; opacity: 0; }
.shop-preview__placeholder { margin: auto; padding: 2rem; max-width: 34ch; text-align: center;
    color: var(--text-muted); }
.shop-preview__placeholder[hidden] { display: none; }
/* Snapshot click hint (#425 snapshot signal): a transient toast pinned to the bottom of the panel,
   flashed by a click on the picture. */
.shop-preview__hint { position: absolute; left: 50%; bottom: 1rem; transform: translateX(-50%);
    margin: 0; padding: 0.5rem 0.9rem; max-width: min(90%, 42ch); text-align: center;
    font-size: 0.85rem; color: var(--text); background: var(--surface);
    border: 1px solid var(--border); border-radius: 999px; box-shadow: 0 4px 16px rgba(4, 41, 64, 0.18);
    opacity: 0; transition: opacity 250ms ease-in-out; pointer-events: none; }
.shop-preview__hint[hidden] { display: none; }
.shop-preview__hint.is-visible { opacity: 1; }
/* #384 loading state: a shimmer block standing in for the screenshot while the sync capture runs,
   with a short caption. Shown only under .shop-preview--loading; hidden by showResult. */
.shop-preview__skeleton { margin: auto; display: flex; flex-direction: column; align-items: center;
    gap: 1rem; width: 100%; max-width: 720px; padding: 2rem 1.5rem; box-sizing: border-box; }
/* Same [hidden] pairing as the img: display:flex above beats UA [hidden]{display:none}, so
   showResult's loading.hidden=true wouldn't hide the skeleton over the screenshot without this. */
.shop-preview__skeleton[hidden] { display: none; }
.shop-preview__skeleton-shimmer { width: 100%; height: min(60vh, 420px); border-radius: 14px;
    background: linear-gradient(100deg, #e9eaee 30%, #f4f5f7 50%, #e9eaee 70%);
    background-size: 200% 100%; animation: shop-preview-shimmer 1.4s ease-in-out infinite; }
.shop-preview__skeleton-caption { margin: 0; font-size: 0.9rem; color: var(--text-muted); }
@keyframes shop-preview-shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
    .shop-preview__skeleton-shimmer { animation: none; }
    .shop-preview__progress-fill { animation: none; width: 100%; opacity: 0.55; }
    /* The in-progress step keeps its accent ring; only the pulse stops (#411). */
    .preview-steps__item[data-state="working"] .preview-steps__marker { animation: none; }
    .shop-preview__img { transition: none; }
    .shop-preview__img[hidden] { opacity: 1; }
    .shop-preview__hint { transition: none; }
}
@media (max-width: 640px) {
    .shop-preview-shell { height: auto; min-height: calc(100vh - var(--topbar-height)); padding: 0.75rem; }
    .shop-preview__bar { flex-wrap: wrap; gap: 0.5rem; }
}

/* First-run dialog (#425 §4.3): a modal over the freshly-rendered preview, shown exactly once. */
.shop-firstrun { position: fixed; inset: 0; z-index: 1200; display: flex; align-items: center;
    justify-content: center; padding: 1.5rem; background: rgba(4, 41, 64, 0.5); }
.shop-firstrun[hidden] { display: none; }
.shop-firstrun__dialog { width: 100%; max-width: 30rem; background: var(--surface); color: var(--text);
    border: 1px solid var(--border); border-radius: 10px; padding: 1.5rem 1.6rem;
    box-shadow: 0 18px 50px rgba(4, 41, 64, 0.35); }
.shop-firstrun__title { margin: 0 0 0.75rem; font-size: 1.2rem; }
.shop-firstrun__body { margin: 0; color: var(--text-muted); font-size: 0.95rem; }
.shop-firstrun__body p { margin: 0 0 0.75rem; }
.shop-firstrun__body p:last-child { margin-bottom: 0; }
.shop-firstrun__actions { display: flex; justify-content: flex-end; gap: 0.6rem; margin-top: 1.25rem; }

/* ----- Product keyword surface (#336) ----- */
/* Warn/error notice tints reuse the shared danger tokens; base .notice covers layout. */
.notice-warn {
    background: var(--surface-2);
    border-color: var(--border);
}
.notice-error {
    background: var(--danger-bg);
    border-color: var(--danger-border);
    color: var(--danger-text);
}

.keyword-group { margin: 0 0 1.25rem; }
.keyword-group:last-child { margin-bottom: 0; }
.keyword-group__head {
    display: flex; align-items: baseline; gap: 0.5rem;
    margin: 0 0 0.5rem;
}
.keyword-group__title { font-size: 0.95rem; margin: 0; }
.keyword-group__count { color: var(--text-muted); font-size: 0.85rem; }

.keyword-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.keyword-chip {
    display: inline-flex; align-items: center; gap: 0.35rem;
    padding: 0.2rem 0.6rem;
    border-radius: 999px;
    font-size: 0.82rem;
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text);
}
/* The source is a wire value surfaced read-only; the tint just distinguishes groups. */
.keyword-chip--llm { border-color: var(--accent); background: var(--accent-tint); color: var(--accent-text); }
.keyword-chip--image { border-color: var(--success-border); background: var(--success-bg); color: var(--success-text); }
.keyword-chip--manual { border-color: var(--border); background: var(--surface-2); }

.keyword-chip__remove {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.05rem; height: 1.05rem; padding: 0;
    border: none; border-radius: 50%;
    background: transparent; color: inherit;
    font-size: 0.95rem; line-height: 1; cursor: pointer;
}
.keyword-chip__remove:hover { background: var(--danger-tint); color: var(--danger); }
.keyword-chip__form { display: inline-flex; margin: 0; }
.keyword-more { color: var(--text-muted); font-size: 0.82rem; align-self: center; }

.keyword-preview-groups { display: flex; flex-direction: column; gap: 0.75rem; }

.regenerate-actions { margin-top: 1rem; display: flex; flex-direction: column; gap: 0.4rem; }
.regenerate-actions form { display: flex; flex-direction: column; gap: 0.3rem; margin: 0; }
.kw-job-status { margin: 0; font-size: 0.85rem; color: var(--text-muted); }
.kw-job-status.is-running { color: var(--accent-text); }
.kw-job-status.is-error { color: var(--danger-text); }

/* ----- #415 single-scene onboarding ----- */
/* One continuous full-viewport scene: a pulsing background throughout (the shop screenshot is no
   longer rendered here — #432 dropped it, #430 moved that payoff to /preview), plain form steps
   in the center, and the widget lookalike that sits in the storefront corner for the form steps
   then takes center stage for the dream (#432). The scene hands off to the in-shell /preview at
   the wake (#430) — there is no post-wake in-scene header or live preview any more. All state
   swaps are two-frame class toggles driven by onboarding-scene.js: elements transition
   opacity/transform while IN the layout, and the [hidden] flip happens outside the animated frame
   (a CSS transition cannot interpolate across display:none, #411). Every animation carries a
   prefers-reduced-motion guard. */
.scene-body { margin: 0; }

/* [hidden] must always win inside the scene: several scene elements carry author display
   values (.btn is inline-flex, header/steps/forms are flex), and an author display beats the
   UA's [hidden]{display:none} — the classic trap this file documents twice for .shop-preview.
   One scoped pairing covers every current and future hook; the two-frame reveal/conceal
   helpers in onboarding-scene.js rely on hidden actually hiding. display is never animated
   (only opacity/transform are), so !important costs nothing. */
.onb-scene [hidden] { display: none !important; }

.onb-scene {
    position: fixed; inset: 0; overflow: hidden;
    display: flex; flex-direction: column;
    background: var(--bg, #f4f5f7); color: var(--text);
    /* The real widget's theme custom properties (ChatThemeTokens.ToCssVariables naming) that
       the lookalike bubbles read; onboarding-scene.js overwrites the full token set from the
       ?handler=theme response so the lookalike and the real widget can't drift. Only the
       consumed tokens carry defaults here. */
    --advisor-text: #111827; --advisor-border: #e5e7eb; --advisor-bubble-assistant: #f3f4f6;
}

/* Background: the pulse is the sole scenery throughout. The shop screenshot is no longer
   rendered here (#432) — its payoff moved to the in-shell /preview (#430); onboarding fires the
   capture only to pre-warm /preview, never a backdrop. */
.onb-scene__bg { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.onb-scene__pulse { position: fixed; inset: 0; }
.onb-scene__pulse::before {
    content: ""; position: absolute; inset: -20%;
    background: radial-gradient(circle at 50% 42%, var(--accent-tint, rgba(79, 70, 229, 0.16)), transparent 62%);
    animation: onb-pulse 5.5s ease-in-out infinite;
}
@keyframes onb-pulse {
    0%, 100% { opacity: 0.45; transform: scale(0.96); }
    50% { opacity: 1; transform: scale(1.04); }
}
@media (prefers-reduced-motion: reduce) {
    .onb-scene__pulse::before { animation: none; opacity: 0.7; transform: none; }
}

/* Center: the plain form steps float over the background. pointer-events pass through the
   (grid) filler so the live step's backdrop stays scrollable; the step card re-enables them. */
.onb-center { position: relative; z-index: 2; flex: 1 1 auto; min-height: 0;
    display: grid; place-items: center; padding: 1.5rem; pointer-events: none; }
.onb-step {
    pointer-events: auto;
    width: 100%; max-width: 30rem; box-sizing: border-box;
    background: var(--surface, #fff); border: 1px solid var(--border, #e5e7eb);
    border-radius: 1rem; box-shadow: 0 18px 48px rgba(0, 0, 0, 0.12);
    padding: 1.75rem; display: flex; flex-direction: column; gap: 1rem;
    opacity: 1; transform: translateY(0);
    transition: opacity 220ms ease-in-out, transform 220ms ease-in-out;
}
.onb-step.is-entering { opacity: 0; transform: translateY(0.6rem); }
.onb-step.is-leaving { opacity: 0; transform: translateY(-0.4rem); }
@media (prefers-reduced-motion: reduce) {
    .onb-step { transition: none; }
}
.onb-step__title { margin: 0; font-size: 1.25rem; }
.onb-step__lede { margin: 0; color: var(--text); }
.onb-step__lede--muted { color: var(--text-muted); text-align: center; }
.onb-step__continue { align-self: flex-start; }
/* The dream step is only the quiet accessible status line now (#432): the lookalike takes
   center stage as the focal content, so this pins to the foot of the scene as a muted caption
   that never overlaps the centered character (and stays the visible no-findings fallback). */
.onb-step--dream {
    position: fixed; left: 0; right: 0; bottom: 1.75rem;
    max-width: none; width: auto; padding: 0 1.5rem; pointer-events: none;
    background: transparent; border: none; box-shadow: none;
}

/* Plain form fields. */
.onb-form { display: flex; flex-direction: column; gap: 1rem; }
.onb-field { display: flex; flex-direction: column; gap: 0.35rem; }
.onb-field__label { font-size: 0.85rem; font-weight: 600; color: var(--text-muted);
    display: inline-flex; align-items: center; gap: 0.35rem; }
.onb-field__row { display: flex; gap: 0.5rem; }
.onb-field__row input { flex: 1 1 auto; min-width: 0; }
.onb-field input {
    width: 100%; box-sizing: border-box; padding: 0.6rem 0.9rem;
    background: var(--surface-2); color: var(--text);
    border: 1px solid var(--border); border-radius: 0.6rem; outline: none;
    transition: border-color var(--transition);
}
.onb-field input:focus { border-color: var(--accent); background: var(--surface); }
.onb-field__error { margin: 0; color: var(--danger, #b91c1c); font-size: 0.85rem; }

/* ?-tooltips: the demoted explanations (#415 — forms stay plain). Hover/focus reveal. */
.onb-tip {
    position: relative; display: inline-flex; align-items: center; justify-content: center;
    width: 1.05rem; height: 1.05rem; border-radius: 50%;
    border: 1px solid var(--border); background: var(--surface-2); color: var(--text-muted);
    font-size: 0.7rem; font-weight: 700; cursor: help; text-transform: none; letter-spacing: 0;
}
.onb-tip::after {
    content: attr(data-tip); position: absolute; bottom: 140%; left: 50%;
    transform: translateX(-50%); width: 16rem; padding: 0.55rem 0.7rem;
    border-radius: 0.5rem; background: var(--text, #111827); color: var(--bg, #fff);
    font-size: 0.78rem; font-weight: 400; line-height: 1.4; text-align: left;
    opacity: 0; visibility: hidden; transition: opacity 160ms ease-in-out; z-index: 10;
    pointer-events: none;
}
.onb-tip:hover::after, .onb-tip:focus-visible::after { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) {
    .onb-tip::after { transition: none; }
}

/* Finding cards (#415 step 1): one small card per probe finding. */
.onb-findings { display: flex; flex-direction: column; gap: 0.6rem;
    opacity: 1; transition: opacity 220ms ease-in-out; }
.onb-findings.is-entering { opacity: 0; }
.onb-card {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.65rem 0.85rem; border: 1px solid var(--border);
    border-radius: 0.6rem; background: var(--surface-2); font-size: 0.92rem;
}
.onb-card__label { display: flex; flex-direction: column; gap: 0.3rem; flex: 1 1 auto; }
.onb-card__label span { font-size: 0.78rem; font-weight: 600; color: var(--text-muted); }
.onb-card__swatch { width: 0.95rem; height: 0.95rem; border-radius: 50%;
    border: 1px solid rgba(0, 0, 0, 0.15); flex-shrink: 0; }

/* The widget lookalike (#415): the storefront launcher's exact corner geometry (loader btn:
   bottom/right 24px, pill radius 24, #111, 14px system-ui) so the wake masks the swap to the
   real widget. Bubbles stack above the pill, newest at the bottom. */
.onb-widget { position: fixed; bottom: 24px; right: 24px; z-index: 4;
    display: flex; flex-direction: column; align-items: flex-end; gap: 0.55rem;
    /* 220ms matches onboarding-scene.js SWAP_MS — conceal() flips hidden at that mark. */
    opacity: 1; transition: opacity 220ms ease-in-out; }
/* Both swap start states (#432): conceal fades out via .is-leaving; centerLookalike is the first
   caller to reveal the lookalike, and reveal()'s two-frame fade-in needs an .is-entering
   opacity:0 start state (as every other reveal target has) or it hard-cuts in. */
.onb-widget.is-leaving, .onb-widget.is-entering { opacity: 0; }
.onb-widget__pill {
    display: inline-flex; align-items: center; gap: 0.55rem;
    padding: 12px 18px; border-radius: 24px;
    background: #111; color: #fff; font: 14px system-ui;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
.onb-widget__faces { display: grid; }
.onb-widget__face { grid-area: 1 / 1; white-space: nowrap; text-align: left;
    opacity: 0; transition: opacity 220ms ease-in-out; }
.onb-widget[data-state="asleep"] .onb-widget__face[data-face="asleep"],
.onb-widget[data-state="dreaming"] .onb-widget__face[data-face="dreaming"],
.onb-widget[data-state="awake"] .onb-widget__face[data-face="awake"],
.onb-widget[data-state="nightmare"] .onb-widget__face[data-face="nightmare"] { opacity: 1; }
.onb-widget[data-state="dreaming"] .onb-widget__pill { animation: onb-dream 1.6s ease-in-out infinite; }
@keyframes onb-dream {
    0%, 100% { opacity: 0.65; }
    50% { opacity: 1; }
}
.onb-widget__label { opacity: 0.75; }
.onb-widget__bubble {
    margin: 0; max-width: 17rem; padding: 0.6rem 0.85rem;
    background: var(--advisor-bubble-assistant, #f3f4f6); color: var(--advisor-text, #111827);
    border: 1px solid var(--advisor-border, #e5e7eb);
    border-radius: 0.85rem; border-bottom-right-radius: 0.25rem;
    font-size: 0.88rem; font-style: italic; box-shadow: 0 6px 24px rgba(0, 0, 0, 0.1);
    opacity: 1; transform: translateY(0);
    transition: opacity 220ms ease-in-out, transform 220ms ease-in-out;
}
.onb-widget__bubble.is-entering { opacity: 0; transform: translateY(0.35rem); }
.onb-widget__bubble.is-leaving { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
    .onb-widget, .onb-widget__face, .onb-widget__bubble { transition: none; }
    .onb-widget[data-state="dreaming"] .onb-widget__pill { animation: none; opacity: 0.85; }
}

/* Center-stage dream treatment (#432): at the scrape the lookalike leaves the corner and
   presents as a fuller, centered character with its mutter bubbles beneath it — the focal
   content of the honest wait. Since #430 it never wakes into the real widget, so it sheds the
   launcher-pill look entirely. Toggled by onboarding-scene.js at dream start (two-frame swap,
   #411) and retired by the hand-off (the redirect tears the scene down). */
.onb-widget--center {
    inset: 0;                                        /* fill the viewport (overrides the corner bottom/right) */
    align-items: center; justify-content: center;
    flex-direction: column-reverse;                 /* DOM is bubbles then pill -> face on top, bubbles below */
    gap: 1.1rem; padding: 2rem; text-align: center;
    pointer-events: none;                            /* decorative/aria-hidden — never intercept the viewport */
}
/* Shed the pill: transparent chrome, no launcher label, the face enlarged as a centered avatar. */
.onb-widget--center .onb-widget__pill {
    background: none; box-shadow: none; padding: 0; color: var(--text);
    font-size: clamp(2rem, 6vw, 3.25rem);
}
.onb-widget--center .onb-widget__label { display: none; }
.onb-widget--center .onb-widget__faces { justify-items: center; }
.onb-widget--center .onb-widget__face { text-align: center; }
/* Bubbles centered beneath the character, a touch wider; the corner speech-tail is meaningless
   off the launcher, so square it back up. */
.onb-widget--center .onb-widget__bubbles {
    display: flex; flex-direction: column; align-items: center; gap: 0.55rem;
}
.onb-widget--center .onb-widget__bubble {
    max-width: min(32rem, 86vw); text-align: center; border-bottom-right-radius: 0.85rem;
}

/* ----------------------------------------------------------------------------
   Mobile onboarding (#421): one responsive scene, no fork. On a phone the corner
   widget lookalike becomes a PROMINENT, persistent top character panel (present from
   step 1), with the plain form steps beneath it and the pulse throughout. Post-#430/#432
   the desktop dream is already a centered character + mutters over the pulse, so the
   dream treatment (.onb-widget--center) needs no reflow here beyond sizing the face to
   match the panel — it's a subtle emphasis, not the big corner→center move it is on
   desktop.

   BREAKPOINT CONTRACT: 640px is mirrored in onboarding-scene.js (isMobileViewport) to gate
   the desktop-only screenshot pre-warm. The two MUST stay the same value — a drift would
   fire the capture on a phone or skip it on a laptop (same discipline as SWAP_MS mirroring
   the CSS transition). Change one, change the other.

   The top-panel override is scoped :not(.onb-widget--center) so it never touches the dream's
   full-viewport centered treatment (which stays inset:0); the element swaps between the two
   via the same two-frame class toggle (#411) the desktop uses. */
@media (max-width: 640px) {
    /* Character: corner launcher -> large centered top band, launcher pill shed (like the
       dream avatar, but pinned to the top while the owner fills the form). aria-hidden and
       pointer-events:none — the interactive content is the form below it. */
    .onb-widget:not(.onb-widget--center) {
        top: 0; left: 0; right: 0; bottom: auto;
        flex-direction: column; align-items: center; justify-content: center;
        gap: 0.5rem; padding: 1.75rem 1rem 0.75rem; text-align: center;
        pointer-events: none;
    }
    .onb-widget:not(.onb-widget--center) .onb-widget__pill {
        background: none; box-shadow: none; padding: 0; color: var(--text);
        font-size: clamp(2.5rem, 13vw, 4rem);
    }
    .onb-widget:not(.onb-widget--center) .onb-widget__label { display: none; }
    .onb-widget:not(.onb-widget--center) .onb-widget__faces { justify-items: center; }
    .onb-widget:not(.onb-widget--center) .onb-widget__face { text-align: center; }

    /* Form clears the fixed top panel and scrolls beneath it (a soft keyboard can shrink
       the viewport below panel+form). Align to the top rather than the viewport middle so
       the URL field sits just under the character, one-handed reach. */
    .onb-center {
        place-items: start center; align-content: start;
        padding: 9rem 1rem 1.5rem; overflow-y: auto;
        /* Re-enable pointer events (the base rule disables them for a scrollable desktop
           backdrop that no longer exists post-#432): nothing sits behind the form on mobile
           but the decorative aria-hidden character/pulse, and this keeps the overflow
           reliably touch-scrollable when a soft keyboard shrinks the viewport. */
        pointer-events: auto;
    }
    .onb-step { padding: 1.35rem; }

    /* Touch targets: inputs and buttons comfortably tappable (>=44px) one-handed. */
    .onb-field input { padding: 0.75rem 0.9rem; font-size: 1rem; }
    .onb-scene .btn { min-height: 2.75rem; }

    /* The dream reads as a subtle emphasis here: the character is already prominent in the
       panel, so size the centered face to match rather than jumping (desktop's 6vw clamp is
       tiny on a phone). Full-viewport inset:0 is inherited from .onb-widget--center. */
    .onb-widget--center .onb-widget__pill { font-size: clamp(2.5rem, 13vw, 4rem); }
}

/* ============================================================================
   Guided tour (#425 Part 2) — driver.js popover themed to the portal tokens.
   driver.css ships light-only defaults; we re-skin the popover chrome so it
   reads as part of the portal in BOTH light and dark (the tokens flip via
   [data-theme="dark"], so a single token-based skin covers both). popoverClass
   is "portal-tour-popover" (set in portal-tour.js).
   ============================================================================ */
.driver-popover.portal-tour-popover {
    background-color: var(--surface);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-card);
    max-width: 320px;
    padding: 16px;
    font-family: inherit;
}
.driver-popover.portal-tour-popover .driver-popover-title {
    color: var(--text);
    font-size: 1.05rem;
    font-weight: 700;
}
.driver-popover.portal-tour-popover .driver-popover-description {
    color: var(--text);
}
.portal-tour-popover .portal-tour-popover__progress {
    display: block;
    margin-bottom: 6px;
    color: var(--text-muted);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}
.portal-tour-popover .portal-tour-popover__body {
    display: block;
    color: var(--text-muted);
    font-size: 0.9rem;
    line-height: 1.5;
}
.driver-popover.portal-tour-popover .driver-popover-close-btn {
    color: var(--text-muted);
}
.driver-popover.portal-tour-popover .driver-popover-close-btn:hover {
    color: var(--text);
}
/* Footer buttons: secondary (Back) + primary accent (Next/Finish). */
.driver-popover.portal-tour-popover .driver-popover-footer-btn {
    background-color: var(--surface);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 6px 12px;
    font-size: 0.85rem;
    font-weight: 600;
    transition: background-color var(--transition), border-color var(--transition), color var(--transition);
}
.driver-popover.portal-tour-popover .driver-popover-footer-btn:hover {
    background-color: var(--surface-2);
}
.driver-popover.portal-tour-popover .driver-popover-next-btn {
    background-color: var(--accent);
    color: #ffffff;
    border-color: var(--accent);
    text-shadow: none;
}
.driver-popover.portal-tour-popover .driver-popover-next-btn:hover {
    background-color: var(--accent-hover);
    border-color: var(--accent-hover);
}
.driver-popover.portal-tour-popover .driver-popover-footer-btn:focus-visible {
    outline: 2px solid var(--accent-text);
    outline-offset: 2px;
}
/* Arrow matches the popover surface (only the pointing side is coloured; driver
   sets the other three transparent, so we override just the visible side). */
.portal-tour-popover .driver-popover-arrow-side-bottom { border-bottom-color: var(--surface); }
.portal-tour-popover .driver-popover-arrow-side-top { border-top-color: var(--surface); }
.portal-tour-popover .driver-popover-arrow-side-left { border-left-color: var(--surface); }
.portal-tour-popover .driver-popover-arrow-side-right { border-right-color: var(--surface); }

/* "Resume tour" pill — shown when the owner wanders off the tour's current page
   (spec §4.5). Fixed, corner-anchored, above content but below the widget. */
.portal-tour-resume {
    position: fixed;
    bottom: 20px;
    left: 20px;
    z-index: 999990;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 10px 16px;
    background-color: var(--accent);
    color: #ffffff;
    border: none;
    border-radius: 999px;
    box-shadow: var(--shadow-card);
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color var(--transition);
}
.portal-tour-resume:hover { background-color: var(--accent-hover); }
.portal-tour-resume:focus-visible {
    outline: 2px solid var(--accent-text);
    outline-offset: 2px;
}

/* Sidebar "Take the tour" control — matches the account/sign-out footer links. */
.portal-sidebar__tour-btn {
    all: unset;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 12px;
    border-radius: var(--radius);
    color: var(--text-muted);
    font-size: 0.9rem;
    cursor: pointer;
    transition: background-color var(--transition), color var(--transition);
}
.portal-sidebar__tour-btn:hover {
    background-color: var(--surface-2);
    color: var(--text);
}
.portal-sidebar__tour-btn:focus-visible {
    outline: 2px solid var(--accent-text);
    outline-offset: 2px;
}
.portal-sidebar__tour-btn svg { flex: 0 0 auto; }

@media (prefers-reduced-motion: reduce) {
    .portal-tour-resume, .portal-sidebar__tour-btn { transition: none; }
    /* Config-hub group (#307): the caret rotation is transform-only (safe across
       the open/closed toggle — no display:none interpolation), but still respect
       a reduced-motion preference. */
    .portal-nav__group-summary, .portal-nav__group-caret { transition: none; }
}
