@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap");
body {
    background-color: #1c2430;
    color: #e8ecf1;
    font-family: 'Segoe UI', system-ui, sans-serif;
    margin: 0;
}

.card {
    background-color: #232b38;
    border-radius: 10px;
    padding: 20px;
    box-sizing: border-box;
}

.eyebrow {
    font-size: 11px;
    letter-spacing: 1.5px;
    color: #8a94a6;
    text-transform: uppercase;
}

.kpi-label {
    font-size: 11px;
    letter-spacing: 1.5px;
    color: #8a94a6;
    text-transform: uppercase;
    margin-bottom: 6px;
}

.kpi-value {
    font-size: 26px;
    font-weight: 600;
}

.chat-bubble {
    border-radius: 10px;
    padding: 8px 12px;
    font-size: 12px;
    margin-bottom: 8px;
    max-width: 88%;
    line-height: 1.4;
}

.chat-bubble.user {
    background-color: rgba(58, 74, 102, 0.2);
    color: #dbe2ea;
    margin-left: auto;
}

.chat-bubble.bot {
    background-color: #2e3846;
    color: #dbe2ea;
}

@keyframes rotate-sphere {
    from { background-position: 0% 50%; }
    to { background-position: 100% 50%; }
}

@keyframes fade-in-out {
    0%, 100% { opacity: 0; }
    50% { opacity: 1; }
}

.demo-sphere {
    width: 160px; height: 160px; border-radius: 50%;
    background: radial-gradient(circle at 35% 35%, #3a4658, #1c2430);
    margin: 60px auto 30px;
    position: relative;
    box-shadow: inset -10px -10px 30px rgba(0,0,0,0.5);
}

.demo-dot {
    position: absolute; width: 8px; height: 8px; border-radius: 50%;
    animation: fade-in-out 4s ease-in-out infinite;
}

.demo-caption {
    text-align: center; color: #8a94a6; font-size: 13px;
    animation: fade-in-out 4s ease-in-out infinite;
}

.continue-btn {
    background: #3a6b4a;
    color: white;
    cursor: pointer;
    opacity: 1;
    transition: background 0.15s ease, opacity 0.15s ease;
}

.continue-btn:hover:not(:disabled) {
    background: #4a8a5e;
}

.continue-btn:disabled {
    background: #2e3846;
    color: #5a6478;
    cursor: not-allowed;
    opacity: 0.6;
}

.thin-scroll {
    scrollbar-width: thin;
    scrollbar-color: transparent transparent;
}

.thin-scroll:hover {
    scrollbar-color: #3a4658 transparent;
}

.thin-scroll::-webkit-scrollbar {
    width: 5px;
}

.thin-scroll::-webkit-scrollbar-track {
    background: transparent;
}

.thin-scroll::-webkit-scrollbar-thumb {
    background: transparent;
    border-radius: 10px;
}

.thin-scroll:hover::-webkit-scrollbar-thumb {
    background: #3a4658;
}

.viewer-toggle-btn {
    background: transparent;
    color: #8a94a6;
    border: 1px solid #3a4658;
    border-radius: 6px;
    padding: 4px 10px;
    font-size: 11px;
    cursor: pointer;
}

.viewer-toggle-active {
    background: #3a6b4a;
    color: white;
    border-color: #3a6b4a;
}

.spinner-ring {
    width: 40px;
    height: 40px;
    border: 4px solid #2e3846;
    border-top: 4px solid #238636;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

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

.cluster-card {
    transition: background 0.15s ease;
}

.cluster-card:hover {
    background: #232b38 !important;
}

#scan-3d-graph .js-plotly-plot {
    display: flex !important;
    justify-content: center !important;
    align-items: center !important;
}
/* ==========================================================================
   Page transitions
   .sv-page is on the content of the upload and dashboard pages, which Dash
   re-mounts on every navigation, so the enter animation plays each time a
   page appears. .sv-page-leaving is added by the callbacks that navigate
   away (New Scan, inspection done) just before they change the location.
   Only inner content carries these -- a transform on the page root would
   re-anchor the fixed loading overlays.
   ========================================================================== */

.sv-page {
    animation: sv-page-enter 380ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes sv-page-enter {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: none; }
}

.sv-page-leaving {
    animation: sv-page-leave 240ms cubic-bezier(0.4, 0, 1, 1) forwards;
    pointer-events: none;
}

@keyframes sv-page-leave {
    to { opacity: 0; transform: translateY(-8px); }
}

/* The dashboard's loading overlay dissolves rather than blinking off. */
#render-loading-overlay {
    transition: opacity 320ms ease;
}

#render-loading-overlay.is-hiding {
    opacity: 0;
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .sv-page,
    .sv-page-leaving {
        animation: none;
    }

    .sv-page-leaving {
        opacity: 0;
    }

    #render-loading-overlay {
        transition: none;
    }
}

/* ==========================================================================
   ScanVision dashboard reskin
   Everything below is namespaced under .sv-dash, which is set on the
   dashboard page's root element only. The sidebar, the upload page and the
   shared classes above are deliberately left alone.
   ========================================================================== */

.sv-dash {
    --sv-radius: 0.75rem;
    --sv-background: oklch(0.09 0 0);
    --sv-foreground: oklch(0.96 0 0);
    --sv-muted-foreground: oklch(0.62 0 0);
    --sv-primary: oklch(0.97 0 0);
    --sv-primary-foreground: oklch(0.1 0 0);
    --sv-border: oklch(1 0 0 / 10%);

    --sv-critical: oklch(0.68 0.21 27);
    --sv-moderate: oklch(0.81 0.15 82);
    --sv-minor: oklch(0.78 0.13 200);

    --sv-brand: oklch(0.72 0.16 275);
    --sv-brand-glow: oklch(0.72 0.16 275 / 18%);

    position: relative;
    background-color: var(--sv-background);
    color: var(--sv-foreground);
    font-family: "Space Grotesk", "Inter", system-ui, sans-serif;
}

/* Ambient glow behind the panels. */
.sv-dash::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 0;
    background:
        radial-gradient(60% 40% at 50% 0%, var(--sv-brand-glow) 0%, transparent 100%),
        radial-gradient(40% 30% at 85% 100%, oklch(0.78 0.13 200 / 8%) 0%, transparent 100%);
}

/* ------------------------------ panels ---------------------------------- */

.sv-dash .card {
    position: relative;
    z-index: 1;
    background:
        linear-gradient(135deg, oklch(1 0 0 / 7%) 0%, oklch(1 0 0 / 2%) 38%, transparent 64%),
        oklch(1 0 0 / 3%);
    backdrop-filter: blur(30px) saturate(155%);
    border: 1px solid oklch(1 0 0 / 11%);
    border-radius: calc(var(--sv-radius) + 8px);
    padding: 16px;
    min-width: 0;
    box-shadow:
        inset 0 1px 0 oklch(1 0 0 / 9%),
        inset 0 -1px 0 oklch(0 0 0 / 22%),
        0 22px 55px oklch(0 0 0 / 28%);
    transition:
        border-color 400ms ease,
        background 400ms ease,
        box-shadow 400ms ease,
        transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sv-dash .card:hover {
    transform: translateY(-3px);
    border-color: oklch(1 0 0 / 22%);
    background:
        linear-gradient(135deg, oklch(1 0 0 / 10%) 0%, oklch(1 0 0 / 3.5%) 40%, transparent 66%),
        oklch(1 0 0 / 4.5%);
    box-shadow:
        inset 0 1px 0 oklch(1 0 0 / 15%),
        inset 0 -1px 0 oklch(0 0 0 / 24%),
        0 34px 80px oklch(0 0 0 / 42%),
        0 0 0 1px oklch(1 0 0 / 4%);
}

@media (prefers-reduced-motion: reduce) {
    .sv-dash .card,
    .sv-dash .card:hover {
        transition: none;
        transform: none;
    }
}

/* ---------------------------- typography -------------------------------- */

.sv-dash .eyebrow,
.sv-dash .kpi-label {
    font-size: 10px;
    font-weight: 500;
    letter-spacing: 0.18em;
    color: var(--sv-muted-foreground);
    text-transform: uppercase;
}

.sv-dash .title {
    font-size: clamp(1.05rem, 2vh, 1.3rem);
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--sv-foreground);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sv-dash .eyebrow {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sv-dash .kpi-value {
    font-size: clamp(1.25rem, 2.5vh, 1.8rem);
    font-weight: 700;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
}

/* ----------------------------- KPI tiles -------------------------------- */

.sv-dash .kpi-card {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 16px;
    min-width: 0;
}

/* Truncate instead of forcing the whole column wider. */
.sv-dash .kpi-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sv-dash .kpi-icon {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    font-size: 15px;
    background: oklch(1 0 0 / 8%);
    border: 1px solid oklch(1 0 0 / 12%);
}

/* --------------------------- severity ramp ------------------------------ */

.sv-dash .sev-critical { color: var(--sv-critical); }
.sv-dash .sev-moderate { color: var(--sv-moderate); }
.sv-dash .sev-minor    { color: var(--sv-minor); }

.sv-dash .sev-bar {
    width: 4px;
    flex-shrink: 0;
}

.sv-dash .sev-bar.critical {
    background: var(--sv-critical);
    box-shadow: 0 0 12px var(--sv-critical);
}

.sv-dash .sev-bar.moderate {
    background: var(--sv-moderate);
    box-shadow: 0 0 12px var(--sv-moderate);
}

.sv-dash .sev-bar.minor {
    background: var(--sv-minor);
    box-shadow: 0 0 12px var(--sv-minor);
}

/* --------------------------- cluster rows ------------------------------- */

.sv-dash .cluster-card {
    display: flex;
    align-items: stretch;
    overflow: hidden;
    border-radius: calc(var(--sv-radius) - 2px);
    background: oklch(1 0 0 / 4%);
    border: 1px solid oklch(1 0 0 / 9%);
    flex-shrink: 0;
    transition: background 0.15s ease;
}

.sv-dash .cluster-card:hover {
    background: oklch(1 0 0 / 7%) !important;
}

.sv-dash .cluster-meta {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
    border-left: 1px solid var(--sv-border);
    padding: 4px 12px;
    text-align: right;
    font-size: 11px;
    color: var(--sv-muted-foreground);
}

/* ------------------------------ toggles --------------------------------- */

/* A single pill slides between the options rather than each button fading
   its own background in and out — a cross-fade at 200ms reads as an instant
   swap. The indicator is a ::before on the group; the buttons are flex: 1 so
   every slot is the same width and translateX(100%) lands exactly one slot
   over. Selecting on :has() keeps it driven by the same className swap the
   Dash callbacks already do, with no extra state. */

.sv-dash .toggle-group {
    position: relative;
    display: inline-flex;
    align-items: center;
    padding: 3px;
    border-radius: 999px;
    background: oklch(1 0 0 / 4%);
    border: 1px solid oklch(1 0 0 / 9%);
}

.sv-dash .toggle-group::before {
    content: "";
    position: absolute;
    top: 3px;
    bottom: 3px;
    left: 3px;
    z-index: 0;
    border-radius: 999px;
    background: var(--sv-primary);
    box-shadow: 0 1px 2px oklch(0 0 0 / 25%);
    transition: transform 220ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Two options: Detection / Raw Scan. */
.sv-dash .toggle-group:has(> :nth-child(2):last-child)::before {
    width: calc((100% - 6px) / 2);
}

/* Three options: Overall / Area / Depth. */
.sv-dash .toggle-group:has(> :nth-child(3):last-child)::before {
    width: calc((100% - 6px) / 3);
}

.sv-dash .toggle-group:has(> :nth-child(1).viewer-toggle-active)::before {
    transform: translateX(0);
}

.sv-dash .toggle-group:has(> :nth-child(2).viewer-toggle-active)::before {
    transform: translateX(100%);
}

.sv-dash .toggle-group:has(> :nth-child(3).viewer-toggle-active)::before {
    transform: translateX(200%);
}

.sv-dash .viewer-toggle-btn {
    position: relative;
    z-index: 1;
    flex: 1;
    background: transparent;
    color: var(--sv-muted-foreground);
    border: none;
    border-radius: 999px;
    padding: 5px 13px;
    font-size: 12px;
    font-family: inherit;
    white-space: nowrap;
    cursor: pointer;
    transition: color 220ms ease;
}

.sv-dash .viewer-toggle-btn:hover {
    color: var(--sv-foreground);
}

/* The sliding indicator supplies the fill, so the active button only changes
   its text colour. */
.sv-dash .viewer-toggle-active,
.sv-dash .viewer-toggle-active:hover {
    background: transparent;
    color: var(--sv-primary-foreground);
    border-color: transparent;
    box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
    .sv-dash .toggle-group::before {
        transition: none;
    }
}

.sv-dash .icon-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 999px;
    background: oklch(1 0 0 / 8%);
    border: 1px solid oklch(1 0 0 / 12%);
    color: var(--sv-foreground);
    font-family: inherit;
    cursor: pointer;
    transition: all 200ms ease;
}

.sv-dash .icon-btn:hover {
    background: oklch(1 0 0 / 12%);
}

/* -------------------------------- chat ---------------------------------- */

.sv-dash .chat-bubble {
    border-radius: calc(var(--sv-radius) + 4px);
    padding: 12px;
    font-size: clamp(0.8rem, 1.6vh, 0.95rem);
    line-height: 1.55;
    margin-bottom: 8px;
    max-width: 95%;
}

.sv-dash .chat-bubble.user {
    background: oklch(1 0 0 / 4%);
    border: 1px solid oklch(1 0 0 / 9%);
    color: var(--sv-foreground);
    margin-left: auto;
    border-top-right-radius: 4px;
}

.sv-dash .chat-bubble.bot {
    background: oklch(1 0 0 / 8%);
    border: 1px solid oklch(1 0 0 / 12%);
    color: oklch(0.96 0 0 / 85%);
    border-top-left-radius: 4px;
}

/* dcc.Input renders a wrapper div that carries the className we pass, with the
   real <input> nested inside carrying the id. Styling both gave a pill inside a
   pill with the padding applied twice, so: the wrapper is the pill, and the
   input sits transparently inside it. Dash's own .dash-input-element defaults
   (padding: var(--Dash-Spacing); height: 12px) are what cropped the text, so
   the id rule has to restate height and padding. */
.sv-dash .chat-input {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    box-sizing: border-box;
    padding: 0;
    /* Dash's .dash-input-container pins a height that is shorter than the
       input it holds; auto lets the pill wrap its contents. */
    height: auto;
    background: oklch(1 0 0 / 4%);
    border: 1px solid oklch(1 0 0 / 9%);
    border-radius: 999px;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.sv-dash .chat-input:focus-within {
    background: oklch(1 0 0 / 8%);
    border-color: oklch(1 0 0 / 18%);
}

.sv-dash #chat-input {
    flex: 1;
    min-width: 0;
    height: auto;
    box-sizing: border-box;
    /* 8px + 18.2px line-box + 8px lands the pill at ~36px, matching the
       send button beside it. */
    padding: 8px 16px;
    background: transparent;
    border: none;
    border-radius: 999px;
    outline: none;
    color: var(--sv-foreground);
    font-family: inherit;
    font-size: 13px;
    line-height: 1.4;
}

.sv-dash #chat-input::placeholder {
    color: var(--sv-muted-foreground);
}

.sv-dash .chat-send {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border: none;
    border-radius: 999px;
    background: var(--sv-primary);
    color: var(--sv-primary-foreground);
    font-family: inherit;
    font-size: 15px;
    cursor: pointer;
    transition: opacity 0.15s ease;
}

.sv-dash .chat-send:hover {
    opacity: 0.85;
}

/* ------------------------------ scrollbar ------------------------------- */

.sv-dash .thin-scroll:hover {
    scrollbar-color: oklch(1 0 0 / 18%) transparent;
}

.sv-dash .thin-scroll:hover::-webkit-scrollbar-thumb {
    background: oklch(1 0 0 / 18%);
}

/* ------------------------------- spinner -------------------------------- */

.sv-dash .spinner-ring {
    border: 4px solid oklch(1 0 0 / 10%);
    border-top: 4px solid var(--sv-brand);
}

/* ------------------------- scan loading overlay ------------------------- */
/* Staged progress while the 3D view is built, in monochrome. The header
   carries the overall percentage and the current stage; each stage below
   has its own percentage and bar that run 0-100% in turn. */

.sv-dash .render-progress {
    width: min(360px, calc(100vw - 48px));
    padding: 22px 24px;
    border-radius: calc(var(--sv-radius) + 8px);
    background:
        linear-gradient(135deg, oklch(1 0 0 / 7%) 0%, oklch(1 0 0 / 2%) 38%, transparent 64%),
        oklch(1 0 0 / 3%);
    backdrop-filter: blur(30px) saturate(155%);
    border: 1px solid oklch(1 0 0 / 11%);
    box-shadow:
        inset 0 1px 0 oklch(1 0 0 / 9%),
        0 22px 55px oklch(0 0 0 / 28%);
    font-family: "Space Grotesk", "Inter", system-ui, sans-serif;
    font-variant-numeric: tabular-nums;
}

.sv-dash .render-progress-head {
    display: flex;
    align-items: center;
    gap: 16px;
}

.sv-dash .render-progress-head .spinner-ring {
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    border: 3px solid oklch(1 0 0 / 12%);
    border-top-color: var(--sv-foreground);
}

.sv-dash .render-progress-text {
    min-width: 0;
}

.sv-dash .render-progress-pct {
    font-size: 26px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--sv-foreground);
}

.sv-dash .render-progress-label {
    margin-top: 5px;
    font-size: 13px;
    color: var(--sv-muted-foreground);
}

.sv-dash .render-steps {
    list-style: none;
    margin: 20px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 11px;
}

.sv-dash .render-step {
    color: oklch(1 0 0 / 30%);
    transition: color 200ms ease;
}

.sv-dash .render-step-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    font-size: 13px;
    line-height: 18px;
}

.sv-dash .render-step-pct {
    flex-shrink: 0;
    font-size: 12px;
    letter-spacing: 0.02em;
}

.sv-dash .render-step-track {
    height: 3px;
    margin-top: 5px;
    border-radius: 999px;
    background: oklch(1 0 0 / 8%);
    overflow: hidden;
}

.sv-dash .render-step-fill {
    width: 0;
    height: 100%;
    border-radius: 999px;
    background: var(--sv-foreground);
    transition: width 120ms linear;
}

.sv-dash .render-step-done {
    color: oklch(1 0 0 / 62%);
}

.sv-dash .render-step-done .render-step-fill {
    background: oklch(1 0 0 / 62%);
}

.sv-dash .render-step-active {
    color: var(--sv-foreground);
}

.sv-dash .render-step-active .render-step-name {
    font-weight: 500;
}

@media (prefers-reduced-motion: reduce) {
    .sv-dash .render-step-fill {
        transition: none;
    }
}

/* -------------------------- scan title + rename ------------------------- */

.sv-dash .scan-title {
    font-size: clamp(1.05rem, 2vh, 1.3rem);
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--sv-foreground);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

/* No scan yet: the heading is a placeholder, not a name. */
.sv-dash .scan-title-empty {
    color: var(--sv-muted-foreground);
    font-weight: 500;
}

/* The pencil stays quiet until the title is hovered, so it does not compete
   with the name itself. */
.sv-dash .icon-btn-sm {
    width: 26px;
    height: 26px;
    font-size: 12px;
    flex-shrink: 0;
    opacity: 0;
    transition: opacity 150ms ease, background 150ms ease;
}

.sv-dash #scan-title-view:hover .icon-btn-sm,
.sv-dash .icon-btn-sm:focus-visible {
    opacity: 1;
}

/* Same wrapper/inner split as the chat field: dcc.Input puts our className on
   a wrapper div and keeps the id on the <input> itself. */
.sv-dash .rename-input {
    flex: 1;
    min-width: 0;
    max-width: 420px;
    display: flex;
    align-items: center;
    height: auto;
    padding: 0;
    background: oklch(1 0 0 / 4%);
    border: 1px solid oklch(1 0 0 / 18%);
    border-radius: 999px;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.sv-dash .rename-input:focus-within {
    background: oklch(1 0 0 / 8%);
    border-color: oklch(1 0 0 / 28%);
}

.sv-dash #scan-rename-input {
    flex: 1;
    min-width: 0;
    height: auto;
    box-sizing: border-box;
    padding: 7px 16px;
    background: transparent;
    border: none;
    border-radius: 999px;
    outline: none;
    color: var(--sv-foreground);
    font-family: inherit;
    font-size: 13px;
    line-height: 1.4;
}

.sv-dash #scan-rename-input::placeholder {
    color: var(--sv-muted-foreground);
}

/* ----------------------------- export button ---------------------------- */

.sv-dash .export-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    padding: 6px 14px;
    border-radius: 999px;
    background: oklch(1 0 0 / 8%);
    border: 1px solid oklch(1 0 0 / 12%);
    color: rgba(245, 245, 245, 0.8);
    font-family: inherit;
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    transition: all 200ms ease;
}

.sv-dash .export-btn:hover {
    background: oklch(1 0 0 / 12%);
    color: var(--sv-foreground);
}

/* ------------------------------- load-in -------------------------------- */

/* The panels arrive together against a near-black page, which lands hard on
   first paint. They fade up in a short cascade instead — top row first, then
   the viewer, then the supporting panels.

   fill-mode is `backwards`, NOT `both`: backwards holds the opening frame
   during the stagger delay, then releases the element once the animation ends.
   `both` would keep the closing frame applied and pin transform to
   translateY(0), which would kill the .card:hover lift for good. */
@keyframes sv-panel-in {
    from {
        opacity: 0;
        transform: translateY(16px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.sv-dash .card {
    animation: sv-panel-in 700ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

.sv-dash .kpi-row > div:nth-child(1) .card { animation-delay: 0ms; }
.sv-dash .kpi-row > div:nth-child(2) .card { animation-delay: 80ms; }
.sv-dash .kpi-row > div:nth-child(3) .card { animation-delay: 160ms; }
.sv-dash .kpi-row > div:nth-child(4) .card { animation-delay: 240ms; }
.sv-dash .viewer-card { animation-delay: 320ms; }
.sv-dash .panel-breakdown { animation-delay: 400ms; }
.sv-dash .panel-clusters { animation-delay: 480ms; }
.sv-dash .chat-card { animation-delay: 560ms; }
.sv-dash #scan-3d-graph {opacity: 0; }

@media (prefers-reduced-motion: reduce) {
    .sv-dash .card {
        animation: none;
    }
}

/* ----------------------------- viewer morph ----------------------------- */

/* While the viewer expands or collapses the plot scales with the panel and
   then gets one relayout at the end. Both would read as jitter, so the plot
   is blurred for the duration and the sharp, resized plot fades back in. */
.sv-dash #scan-3d-graph {
    transition: filter 135ms ease, opacity 135ms ease;
}

.sv-dash .is-morphing #scan-3d-graph {
    filter: blur(10px);
    opacity: 0.55;
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .sv-dash #scan-3d-graph {
        transition: none;
    }
}

/* -------------------------------- plotly -------------------------------- */

/* Plotly paints its own background, which would otherwise sit as an opaque
   rectangle inside the glass. The figures are made transparent server-side
   too; this covers the wrapper elements Plotly builds around them. */
.sv-dash #scan-3d-graph .js-plotly-plot,
.sv-dash #donut-chart .js-plotly-plot {
    background: transparent !important;
}

.sv-dash #scan-3d-graph .main-svg,
.sv-dash #donut-chart .main-svg {
    background: transparent !important;
}

/* ============ ScanVision upload page reskin ============ */
.sv-upload {
    --sv-background: oklch(0.09 0 0);
    --sv-card: oklch(0.13 0 0);
    --sv-card-foreground: oklch(0.95 0 0);
    --sv-border: oklch(1 0 0 / 10%);
    --sv-muted-foreground: oklch(0.65 0 0);
    --sv-brand: oklch(0.72 0.16 275);
    --sv-critical: oklch(0.68 0.21 27);
    --sv-moderate: oklch(0.81 0.15 82);
    --sv-minor: oklch(0.78 0.13 200);

    background: var(--sv-background);
    color: var(--sv-card-foreground);
    min-height: 100vh;
    font-family: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
}

.sv-upload .card {
    position: relative;
    background:
        linear-gradient(180deg, oklch(1 0 0 / 4%), transparent 40%),
        var(--sv-card);
    border: 1px solid var(--sv-border);
    border-radius: 16px;
    backdrop-filter: blur(30px);
    -webkit-backdrop-filter: blur(30px);
    box-shadow: 0 1px 0 oklch(1 0 0 / 6%) inset, 0 20px 40px -20px rgba(0, 0, 0, 0.6);
    transition: transform 180ms ease, box-shadow 180ms ease;
    animation: sv-upload-fade-in 420ms ease both;
}
.sv-upload .card:hover {
    transform: translateY(-2px);
    box-shadow: 0 1px 0 oklch(1 0 0 / 8%) inset, 0 24px 48px -20px rgba(0, 0, 0, 0.7);
}

@keyframes sv-upload-fade-in {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
}

.sv-upload .eyebrow {
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--sv-muted-foreground);
    margin-bottom: 4px;
}

.sv-upload .title {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.sv-upload .subtitle {
    color: var(--sv-muted-foreground);
    font-size: 13px;
    margin-top: 6px;
}

.sv-upload .dropzone {
    border: 1.5px dashed var(--sv-border);
    border-radius: 14px;
    padding: 48px 20px;
    text-align: center;
    cursor: pointer;
    transition: border-color 180ms ease, background 180ms ease;
}
.sv-upload .dropzone:hover {
    border-color: var(--sv-brand);
    background: oklch(1 0 0 / 3%);
}

.sv-upload .dropzone-icon {
    font-size: 26px;
    margin-bottom: 12px;
    opacity: 0.8;
}

.sv-upload .select-files-btn {
    background: oklch(1 0 0 / 6%);
    color: var(--sv-card-foreground);
    border: 1px solid var(--sv-border);
    border-radius: 8px;
    padding: 9px 18px;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    transition: background 150ms ease, border-color 150ms ease;
}
.sv-upload .select-files-btn:hover {
    background: oklch(1 0 0 / 10%);
    border-color: var(--sv-brand);
}

.sv-upload .uploaded-file-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 16px;
    border: 1px solid var(--sv-border);
    border-radius: 10px;
    background: oklch(1 0 0 / 3%);
}

.sv-upload .file-progress-track {
    background: oklch(1 0 0 / 8%);
    border-radius: 4px;
    height: 4px;
    margin-top: 8px;
    overflow: hidden;
}
.sv-upload .file-progress-fill {
    background: var(--sv-brand);
    height: 4px;
    border-radius: 4px;
}

.sv-upload .continue-btn {
    width: 100%;
    background: var(--sv-brand);
    color: oklch(0.12 0 0);
    border: none;
    border-radius: 10px;
    padding: 13px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: opacity 150ms ease, transform 150ms ease;
}
.sv-upload .continue-btn:hover:not(:disabled) {
    transform: translateY(-1px);
}
.sv-upload .continue-btn:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

.sv-upload .upload-error-text {
    color: var(--sv-critical);
    font-size: 12px;
    margin-top: 10px;
    min-height: 1em;
}

.sv-upload .demo-panel {
    position: relative;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    text-align: center;
}

.sv-upload .demo-sphere {
    position: relative;
    width: 220px;
    height: 220px;
    margin: 0 auto 32px;
    border-radius: 50%;
    background: radial-gradient(circle at 35% 30%, oklch(0.3 0 0), oklch(0.15 0 0) 70%);
    box-shadow: 0 0 60px -10px oklch(0.72 0.16 275 / 35%), inset 0 0 40px rgba(0, 0, 0, 0.5);
}

.sv-upload .demo-dot {
    position: absolute;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    animation: sv-upload-pulse 3.2s ease-in-out infinite;
}
@keyframes sv-upload-pulse {
    0%, 100% { opacity: 0.4; transform: scale(0.9); }
    50% { opacity: 1; transform: scale(1.2); }
}

.sv-upload .demo-caption {
    color: var(--sv-muted-foreground);
    font-size: 14px;
    max-width: 320px;
    margin: 0 auto;
    line-height: 1.5;
}

.sv-upload .spinner-ring {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 3px solid oklch(1 0 0 / 12%);
    border-top-color: var(--sv-brand);
    animation: sv-upload-spin 800ms linear infinite;
}
@keyframes sv-upload-spin {
    to { transform: rotate(360deg); }
}

/* ---------------------------- New Scan button ---------------------------- */
.sv-dash .chat-thinking-dots {
    display: flex;
    gap: 4px;
    padding: 4px 0;
}
.sv-dash .chat-thinking-dots span {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.4;
    animation: sv-thinking-bounce 1.2s infinite ease-in-out;
}
.sv-dash .chat-thinking-dots span:nth-child(2) { animation-delay: 0.15s; }
.sv-dash .chat-thinking-dots span:nth-child(3) { animation-delay: 0.3s; }

@keyframes sv-thinking-bounce {
    0%, 80%, 100% { transform: translateY(0); opacity: 0.4; }
    40% { transform: translateY(-4px); opacity: 1; }
}

.sv-dash .chat-thinking-dots {
    display: flex;
    gap: 4px;
    padding: 4px 0;
}
.sv-dash .chat-thinking-dots span {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.4;
    animation: sv-thinking-bounce 1.2s infinite ease-in-out;
}
.sv-dash .chat-thinking-dots span:nth-child(2) { animation-delay: 0.15s; }
.sv-dash .chat-thinking-dots span:nth-child(3) { animation-delay: 0.3s; }

@keyframes sv-thinking-bounce {
    0%, 80%, 100% { transform: translateY(0); opacity: 0.4; }
    40% { transform: translateY(-4px); opacity: 1; }
}

/* Lives in the sidebar, which is outside the .sv-dash scope, so this rule is
   unscoped. Mirrors Lovable's bg-primary pill with transition-opacity. */
.new-scan-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    box-sizing: border-box;
    padding: 11px 14px;
    border: none;
    border-radius: 12px;
    background: #f5f5f5;
    color: #1a1a1a;
    font-family: "Space Grotesk", "Inter", system-ui, sans-serif;
    font-size: 15px;
    font-weight: 500;
    text-decoration: none;
    cursor: pointer;
    transition: all 200ms ease;
}

.new-scan-btn:hover {
    opacity: 0.85;
}

.new-scan-icon {
    font-size: 17px;
    line-height: 1;
    flex-shrink: 0;
}

/* --------------------------- sidebar tab handle -------------------------- */

/* Reads as a tab growing out of the sidebar rather than a button floating over
   it: flush to the panel's right edge, no left border so it shares the panel's
   own outline, and rounded only on the outer corners. Unscoped, since the
   sidebar sits outside .sv-dash. Only `left` is set inline, by the collapse
   callback, so the slide stays in step with the panel. */
.sidebar-tab {
    position: fixed;
    top: 50%;
    transform: translateY(-50%);
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 56px;
    padding: 0;
    background: #000000;
    border: 2px solid rgba(255, 255, 255, 0.85);
    border-left: none;
    border-radius: 0 10px 10px 0;
    color: rgba(255, 255, 255, 0.85);
    font-family: "Space Grotesk", "Inter", system-ui, sans-serif;
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
    transition: left 0.2s ease, background 0.15s ease, color 0.15s ease;
}

.sidebar-tab:hover {
    background: rgba(255, 255, 255, 0.12);
    color: #ffffff;
}

.sidebar-history-item {
    padding: 8px 8px;
    border-radius: 8px;
    font-size: 13px;
    color: #c9ced6;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: background 150ms ease, color 150ms ease;
}
.sidebar-history-item:hover {
    background: rgba(255, 255, 255, 0.08);
    color: #ffffff;
}
.sidebar-history-item-active {
    background: rgba(255, 255, 255, 0.12);
    color: #ffffff;
    font-weight: 600;
}
/* ==========================================================================
   Sign in / Register page
   Ported from the Lovable auth template. Namespaced under .sv-auth, which is
   the root of pages/login.py only. It is a fixed overlay so it covers the
   app shell (sidebar + toggle tab) without the shell knowing about it.
   ========================================================================== */

.sv-auth {
    --sv-radius: 0.75rem;
    --sv-background: oklch(0.09 0 0);
    --sv-foreground: oklch(0.96 0 0);
    --sv-muted-foreground: oklch(0.62 0 0);
    --sv-primary: oklch(0.97 0 0);
    --sv-primary-foreground: oklch(0.1 0 0);
    --sv-border: oklch(1 0 0 / 10%);
    --sv-critical: oklch(0.68 0.21 27);
    --sv-minor: oklch(0.78 0.13 200);
    --sv-brand: var(--sv-foreground);
    --sv-brand-glow: transparent;

    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    padding: 40px 16px;
    overflow: hidden auto;
    background-color: var(--sv-background);
    color: var(--sv-foreground);
    font-family: "Space Grotesk", "Inter", system-ui, sans-serif;
}

.sv-auth * {
    box-sizing: border-box;
}

.sidebar-logout-btn {
    width: 100%;
    padding: 10px 14px;
    border: 1px solid rgba(255, 255, 255, 0.16);
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.04);
    color: #9ca3af;
    font: inherit;
    cursor: pointer;
}

.sidebar-logout-btn:hover {
    border-color: rgba(255, 255, 255, 0.3);
    color: #f3f4f6;
    background: rgba(255, 255, 255, 0.08);
}

.sv-auth [hidden] {
    display: none !important;
}

/* ----------------------------- backdrop --------------------------------- */

.sv-auth-starfield,
.sv-auth-glow {
    position: fixed;
    inset: 0;
    pointer-events: none;
}

.sv-auth-starfield {
    overflow: hidden;
}

.sv-auth-star {
    position: absolute;
    border-radius: 50%;
    background: var(--sv-foreground);
    animation: sv-auth-twinkle 4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

@keyframes sv-auth-twinkle {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(0.35); }
}

.sv-auth-glow {
    background: transparent;
}

/* ------------------------------ panel ----------------------------------- */

.sv-auth-panel {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 448px;
    padding: 32px;
    border-radius: calc(var(--sv-radius) + 12px);
    background: oklch(1 0 0 / 3%);
    backdrop-filter: blur(30px) saturate(155%);
    border: 1px solid oklch(1 0 0 / 11%);
    box-shadow:
        inset 0 1px 0 oklch(1 0 0 / 9%),
        inset 0 -1px 0 oklch(0 0 0 / 22%),
        0 22px 55px oklch(0 0 0 / 28%);
    transition:
        border-color 400ms ease,
        background 400ms ease,
        box-shadow 400ms ease,
        transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
    will-change: transform;
}

.sv-auth-panel:hover {
    transform: translateY(-3px);
    border-color: oklch(1 0 0 / 22%);
    background: oklch(1 0 0 / 4.5%);
    box-shadow:
        inset 0 1px 0 oklch(1 0 0 / 15%),
        inset 0 -1px 0 oklch(0 0 0 / 24%),
        0 34px 80px oklch(0 0 0 / 42%),
        0 0 0 1px oklch(1 0 0 / 4%);
}

@media (min-width: 640px) {
    .sv-auth-panel {
        max-width: 512px;
        padding: 40px;
    }
}

/* ------------------------------ icons ----------------------------------- */
/* Lucide outlines as CSS masks so they take currentColor. */

.sv-auth-icon {
    display: inline-block;
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    background-color: currentColor;
    -webkit-mask: var(--sv-auth-icon) center / contain no-repeat;
    mask: var(--sv-auth-icon) center / contain no-repeat;
}

.sv-auth-icon-box     { --sv-auth-icon: url("/assets/icons/box.svg"); }
.sv-auth-icon-mail    { --sv-auth-icon: url("/assets/icons/mail.svg"); }
.sv-auth-icon-lock    { --sv-auth-icon: url("/assets/icons/lock.svg"); }
.sv-auth-icon-user    { --sv-auth-icon: url("/assets/icons/user.svg"); }
.sv-auth-icon-key     { --sv-auth-icon: url("/assets/icons/key.svg"); }
.sv-auth-icon-shield-check { --sv-auth-icon: url("/assets/icons/shield-check.svg"); }
.sv-auth-icon-eye     { --sv-auth-icon: url("/assets/icons/eye.svg"); }
.sv-auth-icon-eye-off { --sv-auth-icon: url("/assets/icons/eye-off.svg"); }
.sv-auth-icon-loader  { --sv-auth-icon: url("/assets/icons/loader.svg"); }

/* ------------------------------ brand ----------------------------------- */

.sv-auth-brand {
    display: flex;
    align-items: center;
    gap: 12px;
}

.sv-auth-brand-mark {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: var(--sv-radius);
    color: var(--sv-brand);
    background: oklch(1 0 0 / 8%);
    backdrop-filter: blur(24px) saturate(160%);
    border: 1px solid oklch(1 0 0 / 12%);
    box-shadow: none;
}

.sv-auth-brand-name {
    font-size: 20px;
    font-weight: 600;
    letter-spacing: -0.025em;
}

/* ---------------------------- typography -------------------------------- */

.sv-auth-heading {
    margin: 32px 0 0;
    font-size: 30px;
    line-height: 36px;
    font-weight: 600;
    letter-spacing: -0.025em;
}

.sv-auth-subtitle {
    margin: 8px 0 0;
    font-size: 16px;
    line-height: 24px;
    color: var(--sv-muted-foreground);
}

.sv-auth-footer {
    margin: 24px 0 0;
    text-align: center;
    font-size: 12px;
    line-height: 16px;
    color: var(--sv-muted-foreground);
}

/* ---------------------------- mode switch ------------------------------- */

.sv-auth-mode-switch {
    position: relative;
    display: flex;
    align-items: center;
    gap: 4px;
    width: 100%;
    margin-top: 28px;
    padding: 4px;
    border-radius: 999px;
    background: oklch(1 0 0 / 4%);
    backdrop-filter: blur(20px) saturate(140%);
    border: 1px solid oklch(1 0 0 / 9%);
}

.sv-auth-mode-switch::before {
    content: "";
    position: absolute;
    top: 4px;
    bottom: 4px;
    left: 4px;
    width: calc(50% - 4px);
    z-index: 0;
    border-radius: 999px;
    background: var(--sv-primary);
    box-shadow: 0 1px 2px oklch(0 0 0 / 25%);
    transition: transform 300ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sv-auth-mode-signup::before {
    transform: translateX(100%);
}

.sv-auth-mode-btn {
    position: relative;
    z-index: 1;
    flex: 1;
    padding: 8px 16px;
    border: none;
    border-radius: 999px;
    background: transparent;
    color: var(--sv-muted-foreground);
    font-family: inherit;
    font-size: 14px;
    font-weight: 500;
    line-height: 20px;
    cursor: pointer;
    transition: color 300ms ease;
}

.sv-auth-mode-btn:hover {
    color: var(--sv-foreground);
}

.sv-auth-mode-active,
.sv-auth-mode-active:hover {
    color: var(--sv-primary-foreground);
}

/* ------------------------------- form ----------------------------------- */

.sv-auth-form {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin: 24px 0 0;
}

.sv-auth-field {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 16px;
    border-radius: var(--sv-radius);
    background: oklch(1 0 0 / 4%);
    backdrop-filter: blur(20px) saturate(140%);
    border: 1px solid oklch(1 0 0 / 9%);
    color: var(--sv-muted-foreground);
    cursor: text;
    transition: background 150ms ease, border-color 150ms ease;
}

.sv-auth-field:focus-within {
    background: oklch(1 0 0 / 8%);
    border-color: oklch(1 0 0 / 14%);
}

.sv-auth-input,
.sv-auth-input input {
    flex: 1;
    min-width: 0;
    padding: 0;
    border: none;
    outline: none;
    box-shadow: none;
    background: transparent;
    color: var(--sv-foreground);
    font-family: inherit;
    font-size: 16px;
    line-height: 24px;
}

.sv-auth-input input {
    width: 100%;
    height: auto;
}

.sv-auth-input input:focus,
.sv-auth-input input:focus-visible {
    border: none;
    outline: none;
    box-shadow: none;
}

/* Dash and some Chromium themes add their own coloured focus ring to the
   generated input element. Target the concrete controls so the auth form
   stays monochrome regardless of those defaults. */
.sv-auth #auth-name,
.sv-auth #auth-email,
.sv-auth #auth-password,
.sv-auth #auth-code,
.sv-auth #auth-name input,
.sv-auth #auth-email input,
.sv-auth #auth-password input,
.sv-auth #auth-code input {
    border: 0 !important;
    outline: 0 !important;
    box-shadow: none !important;
    background: transparent !important;
    color: var(--sv-foreground) !important;
    caret-color: var(--sv-foreground) !important;
    -webkit-appearance: none;
    appearance: none;
}

.sv-auth #auth-name:focus,
.sv-auth #auth-name:focus-visible,
.sv-auth #auth-email:focus,
.sv-auth #auth-email:focus-visible,
.sv-auth #auth-password:focus,
.sv-auth #auth-password:focus-visible,
.sv-auth #auth-code:focus,
.sv-auth #auth-code:focus-visible,
.sv-auth #auth-name input:focus,
.sv-auth #auth-name input:focus-visible,
.sv-auth #auth-email input:focus,
.sv-auth #auth-email input:focus-visible,
.sv-auth #auth-password input:focus,
.sv-auth #auth-password input:focus-visible,
.sv-auth #auth-code input:focus,
.sv-auth #auth-code input:focus-visible {
    border: 0 !important;
    outline: 0 !important;
    box-shadow: none !important;
}

.sv-auth input::selection {
    background: oklch(1 0 0 / 22%);
    color: var(--sv-foreground);
}

.sv-auth-input::placeholder,
.sv-auth-input input::placeholder {
    color: var(--sv-muted-foreground);
}

.sv-auth button,
.sv-auth input {
    accent-color: oklch(0.97 0 0);
}

.sv-auth button:focus-visible {
    outline: 2px solid oklch(1 0 0 / 55%);
    outline-offset: 2px;
}

.sv-auth-requirements {
    margin: -4px 0 2px;
    padding-left: 22px;
    color: var(--sv-muted-foreground);
    font-size: 12px;
    line-height: 18px;
}

.sv-auth-requirements li + li {
    margin-top: 2px;
}

/* Chrome paints its own background on autofilled inputs; keep the glass. */
.sv-auth-input:-webkit-autofill,
.sv-auth-input:-webkit-autofill:hover,
.sv-auth-input:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--sv-foreground);
    -webkit-box-shadow: 0 0 0 1000px oklch(0.13 0 0) inset;
    transition: background-color 9999s ease-in-out 0s;
}

.sv-auth-eye {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    padding: 4px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--sv-muted-foreground);
    cursor: pointer;
    transition: color 150ms ease;
}

.sv-auth-eye:hover {
    color: var(--sv-foreground);
}

.sv-auth-error,
.sv-auth-notice {
    margin: 0;
    font-size: 14px;
    line-height: 20px;
}

.sv-auth-error { color: var(--sv-critical); }
.sv-auth-notice { color: var(--sv-minor); }

/* Collapse the gap when there is no message to show. */
.sv-auth-error:empty,
.sv-auth-notice:empty {
    display: none;
}

.sv-auth-submit {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin-top: 6px;
    padding: 14px;
    border: none;
    border-radius: var(--sv-radius);
    background: var(--sv-primary);
    color: var(--sv-primary-foreground);
    font-family: inherit;
    font-size: 16px;
    font-weight: 500;
    line-height: 24px;
    cursor: pointer;
    transition: opacity 150ms ease;
}

.sv-auth-submit:hover {
    opacity: 0.85;
}

.sv-auth-submit:disabled {
    opacity: 0.6;
    cursor: default;
}

.sv-auth-link-button {
    align-self: center;
    padding: 4px 8px;
    border: 0;
    background: transparent;
    color: var(--sv-muted-foreground);
    font: inherit;
    font-size: 13px;
    cursor: pointer;
}

.sv-auth-link-button:hover {
    color: var(--sv-foreground);
    text-decoration: underline;
}

.sv-auth-link-button:disabled {
    opacity: 0.55;
    cursor: default;
}

/* Spinner only shows while the button is disabled (i.e. a request is
   in flight, once there is one). */
.sv-auth-spinner {
    display: none;
    animation: spin 1s linear infinite;
}

.sv-auth-submit:disabled .sv-auth-spinner {
    display: inline-block;
}

@media (prefers-reduced-motion: reduce) {
    .sv-auth-panel,
    .sv-auth-panel:hover {
        transition: none;
        transform: none;
    }

    .sv-auth-star {
        animation: none;
    }

    .sv-auth-mode-switch::before {
        transition: none;
    }
}

/* ---------------------------- exit transition --------------------------- */
/* Added to the root on submit. The panel lifts and dissolves, the sky and
   glow fade behind it, then the page swaps to the dashboard. */

.sv-auth-leaving .sv-auth-panel {
    pointer-events: none;
    animation: sv-auth-panel-out 480ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

@keyframes sv-auth-panel-out {
    to {
        opacity: 0;
        transform: translateY(-16px) scale(0.965);
        filter: blur(8px);
    }
}

.sv-auth-starfield,
.sv-auth-glow {
    transition: opacity 520ms ease;
}

.sv-auth-leaving .sv-auth-starfield,
.sv-auth-leaving .sv-auth-glow {
    opacity: 0;
    transition-delay: 120ms;
}

.sv-auth-submit .sv-auth-spinner {
    color: inherit;
}

@media (prefers-reduced-motion: reduce) {
    .sv-auth-leaving .sv-auth-panel {
        animation: none;
        opacity: 0;
    }

    .sv-auth-starfield,
    .sv-auth-glow {
        transition: none;
    }
}
