html, body {
    /* Sarabun covers Latin and Thai, so it leads alone. Putting a Latin-only face
       first makes the browser fall back per-character for Thai and lay the marks
       out against the wrong metrics, which sinks them into the base glyph. */
    font-family: 'Sarabun', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    /* Thai needs headroom for สระบน + วรรณยุกต์ stacked above the consonant. */
    line-height: 1.6;
    height: 100%;
    overflow: hidden;
    margin: 0;
    padding: 0;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}

/* ---------------------------------------------------------- the auth pages could not scroll

   `overflow: hidden` above is the app-shell arrangement, and it is correct for `MainLayout`:
   that layout's root is `flex h-screen overflow-hidden` and its `<main id="cv-main">` carries
   `overflow-y-auto`, so the shell scrolls its content and the document does not scroll behind
   it. Every page that does NOT use `MainLayout` inherits the first half of that bargain and
   none of the second.

   `AuthLayout` is a `<main id="cv-main">` and nothing else — sign in, register, forgot
   password, reset password, terms, privacy. With `overflow: hidden` on both `html` and `body`
   and no scroller anywhere on the page, the part of those pages below the fold is not below the
   fold: it is unreachable. `tools/ui-rebuild/formcheck.py` measured it while asking C7's
   question about whether a form's errors are visible without scrolling, which turned out to
   have a worse answer than expected on these pages:

       /privacy   6533px of document in an 844px viewport, nothing scrollable
       /terms     7568px in 844px
       /register   854px in 844px -- and after a refusal, taller still

   A mouse wheel and the End key both move it 0px. The marketing site, which has no such rule,
   scrolls normally; the back office has no such rule either.

   `body > main#cv-main` is the whole of the fix and it is deliberately that narrow: in
   `MainLayout` the same id sits four elements deep, so this rule cannot reach it and cannot
   give the shell a second scrollbar. */
body > main#cv-main {
    height: 100%;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

/* ---------------------------------------------------------------- per-script faces
   Sarabun is a Thai typeface. Leading with it globally was correct while the product
   was Thai and Latin only, and it is wrong now: five CJK locales ship, and every
   Japanese, Korean and Chinese glyph was falling through Sarabun to whatever the
   system happened to pick. That is not a missing-font failure — the text renders —
   it is a wrong-font one, which is harder to see and reads as amateur to a native
   reader.

   Each rule names that language's own system UI face first and keeps Sarabun in the
   stack behind it, so a Thai string inside a Japanese interface still sets correctly.

   The scripts are separated because their conventions differ, not merely their
   glyphs: Japanese and Chinese take no extra leading, Korean syllable blocks need
   more than Latin at the same size, and Vietnamese needs room for two stacked marks
   on one letter. Traditional and Simplified are named apart because a reader of one
   notices the other's forms immediately. */

:lang(ja) {
    font-family: 'Hiragino Kaku Gothic ProN', 'Yu Gothic UI', 'Yu Gothic', 'Meiryo',
                 'Noto Sans JP', 'Sarabun', system-ui, sans-serif;
    line-height: 1.7;
}

:lang(ko) {
    font-family: 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR',
                 'Sarabun', system-ui, sans-serif;
    /* Hangul syllable blocks are visually denser than Latin at the same size. */
    line-height: 1.75;
    /* Korean breaks at spaces and never mid-word. */
    word-break: keep-all;
}

:lang(zh-Hant) {
    font-family: 'PingFang TC', 'Microsoft JhengHei UI', 'Microsoft JhengHei',
                 'Noto Sans TC', 'Sarabun', system-ui, sans-serif;
    line-height: 1.7;
}

:lang(zh-Hans) {
    font-family: 'PingFang SC', 'Microsoft YaHei UI', 'Microsoft YaHei',
                 'Noto Sans SC', 'Sarabun', system-ui, sans-serif;
    line-height: 1.7;
}

:lang(vi) {
    /* Vietnamese stacks a vowel-quality mark and a tone mark on one letter. Sarabun
       carries the Vietnamese range and is already tuned for stacked marks, so it
       leads here — but the line needs the same headroom Thai gets. */
    font-family: 'Sarabun', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
    line-height: 1.7;
}


/* Truncating containers clip the box, so the tone mark above the x-height is the
   first thing lost. Keep their line box tall enough to survive the clip. */
.truncate,
[class*="line-clamp-"] {
    line-height: 1.5;
}

/* Modern Scrollbar */
* {
    scrollbar-width: thin;
    scrollbar-color: rgb(var(--cv-border-strong-rgb) / 0.7) transparent;
}

*::-webkit-scrollbar {
    width: 6px;
    height: 6px;
}

*::-webkit-scrollbar-track {
    background: transparent;
}

*::-webkit-scrollbar-thumb {
    background: rgb(var(--cv-border-strong-rgb) / 0.6);
    border-radius: 9999px;
    transition: background 0.2s ease;
}

*::-webkit-scrollbar-thumb:hover {
    background: rgb(var(--cv-border-strong-rgb) / 0.9);
}

*::-webkit-scrollbar-corner {
    background: transparent;
}

/* Dark sidebar scrollbar */
.bg-sidebar-dark::-webkit-scrollbar-thumb,
aside::-webkit-scrollbar-thumb,
aside *::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.15);
}

.bg-sidebar-dark::-webkit-scrollbar-thumb:hover,
aside::-webkit-scrollbar-thumb:hover,
aside *::-webkit-scrollbar-thumb:hover {
    background: rgba(255, 255, 255, 0.3);
}

/* Material Symbols Icons Configuration */
.material-symbols-outlined {
    font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}

.icon-filled {
    font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}

/* Iconify Icon Styles */
.iconify {
    display: inline-block;
    width: 1em;
    height: 1em;
    vertical-align: -0.125em;
}

.iconify-inline {
    vertical-align: -0.125em;
}

/* Animated Icon Styles */
.icon-spin {
    animation: icon-spin 1s linear infinite;
}

.icon-pulse {
    animation: icon-pulse 1.5s ease-in-out infinite;
}

.icon-bounce {
    animation: icon-bounce 1s ease infinite;
}

@keyframes icon-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

@keyframes icon-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50% { opacity: 0.6; transform: scale(0.95); }
}

@keyframes icon-bounce {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-3px); }
}


/* The scaffolded defaults, put on the tokens.
 *
 * These came with `dotnet new blazor` and were never touched: Bootstrap's link blue, Bootstrap's
 * button blue, and a validation palette of `#26b050` and `#e50000`. They are not decoration --
 * `.validation-message` is the colour of every form error in the portal, `.invalid` is the
 * outline drawn round the field that produced it, and `.info-tip-icon` is the only affordance
 * saying a field has an explanation. All of them were fixed to one theme and none of them was
 * a colour this product uses.
 *
 * `#6366f1` on the tooltip hover is the retired brand indigo, still here. No sweep found any of
 * this, because every sweep read `class="…"` attributes and this is a stylesheet. */
a, .btn-link {
    color: var(--cv-primary);
}

.btn-primary {
    color: var(--cv-primary-ink);
    background-color: var(--cv-primary);
    border-color: var(--cv-primary);
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem var(--cv-card), 0 0 0 0.25rem rgb(var(--cv-primary-rgb) / 0.55);
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--cv-success);
}

.invalid {
    outline: 1px solid var(--cv-danger);
}

.validation-message {
    color: var(--cv-danger);
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: var(--cv-border-strong);
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

@keyframes gen-progress-shine {
    0%   { background-position: 0% 0; }
    100% { background-position: 300% 0; }
}

/* ─── Info Tooltip ─── */
.info-tip {
    position: relative;
    display: inline-flex;
    align-items: center;
    margin-left: 4px;
    cursor: pointer;
    vertical-align: middle;
}

.info-tip-icon {
    font-size: 16px !important;
    /* gray-500. gray-400 measured 2.54:1 on white, under the 3:1 a graphical control needs, and
       this is the only affordance saying the field has an explanation behind it. */
    color: var(--cv-muted);
    transition: color 0.15s;
}

.info-tip:hover .info-tip-icon {
    color: var(--cv-primary);
}

.info-tip-overlay {
    position: fixed;
    inset: 0;
    z-index: 90;
}

.info-tip-popup {
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%);
    width: 260px;
    padding: 10px 14px;
    font-size: 12px;
    font-weight: 400;
    line-height: 1.6;
    color: var(--cv-bg);
    background: var(--cv-heading);
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
    z-index: 100;
    white-space: normal;
    animation: info-tip-in 0.15s ease-out;
}

/* When tooltip would overflow top, show below instead — handled via left-align fallback */
.info-tip-popup::before {
    content: '';
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    border: 6px solid transparent;
    border-top-color: var(--cv-heading);
}

@keyframes info-tip-in {
    from {
        opacity: 0;
        transform: translateX(-50%) translateY(4px);
    }
    to {
        opacity: 1;
        transform: translateX(-50%) translateY(0);
    }
}

/* No `prefers-color-scheme` block here any more: this app themes by a class on
   `<html>`, so the media query fired on the wrong signal -- a reader on a light OS
   who chose dark mode kept the light tooltip, under an arrow whose colour did
   switch. Both halves are tokens now and neither needs a variant. */

/* ============================================
   MOBILE BOTTOM NAVIGATION BAR
   ============================================ */

/* Bottom nav container.
   It was frosted glass -- on the primary navigation surface of every mobile page in the
   portal. `.cv-bottom-bar` already paints it an opaque `--cv-card` with a hairline above; this
   rule was blurring what is behind an opaque bar, which costs a compositor layer and buys
   nothing even before the look was rejected. */
.btm-nav {
    backdrop-filter: none;
}

/* Active nav item indicator dot */
.btm-nav-active {
    position: relative;
}
.btm-nav-active::after {
    content: '';
    position: absolute;
    bottom: 4px;
    left: 50%;
    transform: translateX(-50%);
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--cv-primary);
}

/* Create button on bottom nav — no dot indicator */
.btm-nav-create.btm-nav-active::after {
    display: none;
}

/* Bottom sheet slide-up animation */
.btm-sheet-enter {
    animation: btm-sheet-slide-up 0.25s cubic-bezier(0.32, 0.72, 0, 1) forwards;
}

@keyframes btm-sheet-slide-up {
    from {
        transform: translateY(100%);
        opacity: 0.5;
    }
    to {
        transform: translateY(0);
        opacity: 1;
    }
}

/* ============================================
   MOBILE-FIRST COMPACT STYLES
   ============================================ */

@media (max-width: 1023px) {
    /* Tighter page content padding */
    .content {
        padding-top: 0.5rem;
    }

    /* Compact page titles & subtitles */
    h1 {
        font-size: 1.25rem !important;
        line-height: 1.75rem;
    }

    /* Compact stat cards on dashboard */
    .bg-surface-light.rounded-xl,
    .bg-surface-dark.rounded-xl {
        padding: 0.75rem !important;
    }

    .bg-surface-light.rounded-xl .text-3xl,
    .bg-surface-dark.rounded-xl .text-3xl {
        font-size: 1.5rem !important;
    }

    .bg-surface-light.rounded-xl .h-12.w-12,
    .bg-surface-dark.rounded-xl .h-12.w-12 {
        height: 2.25rem !important;
        width: 2.25rem !important;
    }

    .bg-surface-light.rounded-xl .h-12.w-12 .text-2xl,
    .bg-surface-dark.rounded-xl .h-12.w-12 .text-2xl {
        font-size: 1.125rem !important;
    }

    /* Compact card padding for dashboard stat cards only */

    /* Reduce section headers on dashboard */

    /* Tighter spacing for content creation form */
    .space-y-5 > * + * {
        margin-top: 0.875rem !important;
    }
    .space-y-6 > * + * {
        margin-top: 1rem !important;
    }
    .space-y-8 > * + * {
        margin-top: 1.25rem !important;
    }

    /* Compact form inputs.
     *
     * The `font-size: 0.875rem !important` that used to be here is gone, and it is worth
     * saying exactly what it did. This block is inside `@media (max-width: 1023px)` — it only
     * ever applied to phones and tablets — and 14px is below the 16px threshold at which iOS
     * Safari zooms the page on focus and does not zoom back. So on the only devices where the
     * floor matters, every text field, email field, password field, select and textarea in the
     * client portal was 14px, and `.cv-input`'s `font-size: 16px` lost to it because
     * `!important` beats specificity.
     *
     * Sixteen fields measured at 14.0px across /settings, /credits, /content, /schedule and
     * /autopilot. The padding compaction stays; it costs nothing and reads well on a phone. */
    select, input[type="text"], input[type="email"], input[type="password"], textarea {
        padding-top: 0.5rem !important;
        padding-bottom: 0.5rem !important;
    }
    textarea {
        min-height: 80px !important;
    }

    /* Compact buttons */
    .bg-primary, button[class*="bg-primary"] {
        padding-top: 0.5rem !important;
        padding-bottom: 0.5rem !important;
        font-size: 0.8125rem !important;
    }

    /* Compact breadcrumbs */
    header.sticky {
        padding: 0.75rem 1rem !important;
    }
    header.sticky .mb-3 {
        margin-bottom: 0.25rem !important;
    }
    header.sticky h1 {
        font-size: 1.375rem !important;
    }

    /* Compact grid gaps */
    .grid.gap-4 {
        gap: 0.625rem !important;
    }
    .grid.gap-6 {
        gap: 0.75rem !important;
    }
    .grid.gap-8 {
        gap: 1rem !important;
    }

    /* Compact page wrapper padding on mobile — preserve bottom padding */
    .p-6, .p-8 {
        padding-left: 0.75rem !important;
        padding-right: 0.75rem !important;
        padding-top: 0.75rem !important;
    }
    .px-8 {
        padding-left: 0.75rem !important;
        padding-right: 0.75rem !important;
    }
    .py-5 {
        padding-top: 0.625rem !important;
        padding-bottom: 0.625rem !important;
    }

    /* Compact margin bottom for sections */
    .mb-6 {
        margin-bottom: 1rem !important;
    }
    .mb-8 {
        margin-bottom: 1.25rem !important;
    }

    /* Make images and previews compact */
    .max-w-7xl {
        padding-left: 0;
        padding-right: 0;
    }

    /* Compact modal / overlay adjustments */
    .fixed.inset-0 .rounded-xl,
    .fixed.inset-0 .rounded-2xl {
        margin: 0.5rem;
    }
}
/* ═══════════════════════════════════════════════════
   Floating above the mobile bottom bar
   ═══════════════════════════════════════════════════

   Anything pinned to the bottom of the window on a phone — a save bar, a toast, a preview
   notification — has to clear the navigation bar, and every one of them was picking its own
   number. Some knew about the bar (bottom-20 sm:bottom-4), some did not (bottom-6, bottom-4), and
   the ones that did not sat behind it: on AutoPilot the save and preview buttons were half hidden
   under the bar with no way to reach them.

   One class, one number. The bar is 57px plus the home-indicator inset; 72px leaves it clear with
   a little air. On desktop there is no bar, so it drops back to a normal margin.

   Use `class="fixed cv-above-bar right-4 …"` and no bottom-* utility. */
.cv-above-bar {
    bottom: calc(72px + env(safe-area-inset-bottom, 0px));
}

@media (min-width: 1024px) {
    .cv-above-bar {
        bottom: 1.5rem;
    }
}

/* The tap-target floor used to be declared here. It is in cv-brand.css now, so the marketing
   site and the back office get it too — the back office measured 466 undersized controls on
   its first run, every one of them a class this block already covered. */

/* ---------------------------------------------------------------------------
   The font utilities, per script.

   WHY THIS BLOCK EXISTS. The :lang() rules above were written, reviewed and
   correct, and they applied to nothing. Tailwind's fontFamily config named
   Sarabun -- a Thai face with no kanji, no hangul and neither Chinese script --
   for sans, display, body and thai alike, so every .font-sans and .font-display
   carried it. Those two utilities sit on the root wrapper of 23 elements across
   the two apps, both MainLayouts among them, so they wrap every screen.

   A :lang(ja) selector and a .font-display selector have the same specificity
   (0,1,0), and the Tailwind Play CDN injects its sheet AFTER app.css -- so the
   later rule won, on every page, for every Japanese, Korean and Chinese
   customer. The ninth audit measured it: five markets rendering in a Thai face
   throughout.

   These rules are (0,2,0) and win outright. They are generated per locale from
   the same stacks as the :lang() rules above rather than repeated by hand,
   because two copies of a font stack is how one of them comes to be corrected.

   The Tailwind config itself now names a neutral system stack, so a locale with
   no rule here degrades to the platform's own UI font rather than to Thai.
   --------------------------------------------------------------------------- */

:lang(th) {
    font-family: 'Sarabun', 'Leelawadee UI', 'Noto Sans Thai', system-ui, sans-serif;
}

:lang(ja) .font-sans,
:lang(ja) .font-display,
:lang(ja) .font-body,
:lang(ja) .font-thai {
    font-family: 'Hiragino Kaku Gothic ProN', 'Yu Gothic UI', 'Yu Gothic', 'Meiryo', 'Noto Sans JP', 'Sarabun', system-ui, sans-serif
}

:lang(ko) .font-sans,
:lang(ko) .font-display,
:lang(ko) .font-body,
:lang(ko) .font-thai {
    font-family: 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', 'Sarabun', system-ui, sans-serif
}

:lang(zh-Hant) .font-sans,
:lang(zh-Hant) .font-display,
:lang(zh-Hant) .font-body,
:lang(zh-Hant) .font-thai {
    font-family: 'PingFang TC', 'Microsoft JhengHei UI', 'Microsoft JhengHei', 'Noto Sans TC', 'Sarabun', system-ui, sans-serif
}

:lang(zh-Hans) .font-sans,
:lang(zh-Hans) .font-display,
:lang(zh-Hans) .font-body,
:lang(zh-Hans) .font-thai {
    font-family: 'PingFang SC', 'Microsoft YaHei UI', 'Microsoft YaHei', 'Noto Sans SC', 'Sarabun', system-ui, sans-serif
}

:lang(vi) .font-sans,
:lang(vi) .font-display,
:lang(vi) .font-body,
:lang(vi) .font-thai {
    font-family: 'Sarabun', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif
}

:lang(th) .font-sans,
:lang(th) .font-display,
:lang(th) .font-body,
:lang(th) .font-thai {
    font-family: 'Sarabun', 'Leelawadee UI', 'Noto Sans Thai', system-ui, sans-serif
}

/* ---------------------------------------------------------------------------
   Meena, on call — the floating help control on /welcome.

   A <details>, styled. The marker is removed on both the standards selector and
   WebKit's older pseudo-element, because Safari still ships the latter and a
   stray disclosure triangle beside a face is the whole illusion gone.

   The panel is absolutely positioned above the button rather than below it: the
   control lives at the bottom of the viewport, so anything opening downwards
   opens off-screen. On a narrow phone it widens to the viewport minus the two
   gutters instead of overflowing to the right.

   `cv-above-bar` is not used here — /welcome has no bottom bar — but the safe
   area inset is, because the home indicator is there whether or not a bar is. */
.cv-meena {
    position: fixed;
    right: 1rem;
    bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
    z-index: 40;
}

.cv-meena__fab {
    display: inline-flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.375rem 1rem 0.375rem 0.375rem;
    border-radius: 999px;
    background: var(--cv-card);
    border: 1px solid var(--cv-border);
    box-shadow: var(--cv-shadow-md);
    cursor: pointer;
    list-style: none;
    user-select: none;
}

.cv-meena__fab::-webkit-details-marker { display: none; }
.cv-meena__fab::marker { content: ''; }

.cv-meena__fab:hover { border-color: var(--cv-primary); }

.cv-meena__fab:focus-visible {
    outline: 2px solid var(--cv-primary);
    outline-offset: 2px;
}

.cv-meena__face {
    width: 3.25rem;
    height: 3.25rem;
    border-radius: 999px;
    object-fit: cover;
    flex: none;
}

.cv-meena__fab-text {
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--cv-heading);
    white-space: nowrap;
}

.cv-meena__panel {
    position: absolute;
    right: 0;
    bottom: calc(100% + 0.75rem);
    width: min(20rem, calc(100vw - 2rem));
    padding: 1rem;
    text-align: left;
}

/* The face on the button is a picture of a person, and a person who slides into
   view reads as an interruption. It fades, and not at all for anyone who has
   asked the system to stop moving things. */
.cv-meena[open] .cv-meena__panel {
    animation: cv-meena-in 160ms ease-out;
}

@keyframes cv-meena-in {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .cv-meena[open] .cv-meena__panel { animation: none; }
}

/* Meena AI (2026-10-06): the panel becomes a small chat when a CLI can answer. Header with
   the AI mark, a conversation that scrolls, the question box, then the way to the real
   Meena. Class names follow data-cls="cv-meena__" in MeenaHelp.razor: wwwroot/js/meena-help.js
   builds the messages with that prefix. Tokens only, so dark mode follows the theme. */
.cv-meena__panel--ai {
    width: min(23rem, calc(100vw - 2rem));
    display: flex;
    flex-direction: column;
    max-height: min(38rem, calc(100vh - 7rem));
    max-height: min(38rem, calc(100dvh - 7rem));
}

.cv-meena__head { display: flex; align-items: center; gap: 0.75rem; flex: none; }
.cv-meena__titles { flex: 1 1 auto; min-width: 0; }
.cv-meena__avatar { position: relative; flex: none; display: inline-flex; }
.cv-meena__head-face { width: 3.5rem; height: 3.5rem; border-radius: 999px; object-fit: cover; }

/* The AI mark sits on the face, so the assistant is never mistaken for Meena herself. */
.cv-meena__ai-badge {
    position: absolute;
    right: -0.25rem;
    bottom: -0.125rem;
    padding: 0 0.3rem;
    border-radius: 999px;
    background: var(--cv-primary);
    color: var(--cv-primary-ink);
    border: 2px solid var(--cv-card);
    font-size: 0.625rem;
    font-weight: 700;
    line-height: 1.1rem;
    letter-spacing: 0.02em;
}

.cv-meena__close {
    flex: none;
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    margin: -0.375rem -0.375rem 0 0;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: var(--cv-muted);
    cursor: pointer;
}
.cv-meena__close .material-symbols-rounded { font-size: 1.375rem; width: 1.375rem; height: 1.375rem; overflow: hidden; }
.cv-meena__close:hover { background: var(--cv-bg-subtle); color: var(--cv-heading); }
.cv-meena__close:focus-visible { outline: 2px solid var(--cv-primary); outline-offset: 2px; }

.cv-meena__disclose {
    flex: none;
    margin: 0.75rem 0 0;
    padding: 0.5rem 0.625rem;
    border-radius: var(--cv-r-sm);
    background: var(--cv-primary-soft);
    color: var(--cv-primary-soft-ink);
    font-size: 0.75rem;
    line-height: 1.55;
}

.cv-meena__log {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-top: 0.75rem;
    padding-right: 0.125rem;
}
.cv-meena__log[hidden] { display: none; }

.cv-meena__msg {
    max-width: 92%;
    padding: 0.5rem 0.75rem;
    border-radius: var(--cv-r-md);
    font-size: 0.875rem;
    line-height: 1.6;
    overflow-wrap: anywhere;
}
.cv-meena__msg p { margin: 0; }
.cv-meena__msg-text { white-space: pre-line; }

.cv-meena__msg--you {
    align-self: flex-end;
    background: var(--cv-primary);
    color: var(--cv-primary-ink);
    border-bottom-right-radius: 0.25rem;
}

.cv-meena__msg--ai,
.cv-meena__msg--notice {
    align-self: flex-start;
    background: var(--cv-bg-subtle);
    color: var(--cv-text);
    border-bottom-left-radius: 0.25rem;
}

.cv-meena__msg--notice { background: var(--cv-warning-soft); color: var(--cv-text); }

.cv-meena__msg-tag { display: block; margin-bottom: 0.125rem; font-size: 0.6875rem; font-weight: 700; color: var(--cv-muted); }
.cv-meena__msg .cv-meena__msg-hint { margin-top: 0.375rem; font-size: 0.8125rem; }
.cv-meena__msg .cv-meena__ref { margin-top: 0.25rem; font-size: 0.6875rem; opacity: 0.85; font-variant-numeric: tabular-nums; }

.cv-meena__msg--pending { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; color: var(--cv-muted); }

.cv-meena__dots { display: inline-flex; gap: 0.2rem; }
.cv-meena__dots i {
    width: 0.375rem;
    height: 0.375rem;
    border-radius: 999px;
    background: currentColor;
    animation: cv-meena-dot 1s ease-in-out infinite;
}
.cv-meena__dots i:nth-child(2) { animation-delay: 0.15s; }
.cv-meena__dots i:nth-child(3) { animation-delay: 0.3s; }

@keyframes cv-meena-dot {
    0%, 100% { opacity: 0.3; transform: none; }
    50% { opacity: 1; transform: translateY(-2px); }
}

@media (prefers-reduced-motion: reduce) {
    .cv-meena__dots i { animation: none; opacity: 0.7; }
}

.cv-meena__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.75rem; margin-top: 0.375rem; }

.cv-meena__link-btn,
.cv-meena__line-send {
    display: inline-flex;
    align-items: center;
    min-height: 2.25rem;
    padding: 0 0.25rem;
    border: 0;
    background: transparent;
    /* The readable link colour on a tint or a card: --cv-primary-ink is the text ON a primary fill. */
    color: var(--cv-primary-soft-ink);
    font: inherit;
    font-size: 0.8125rem;
    font-weight: 600;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}
.cv-meena__link-btn:focus-visible,
.cv-meena__line-send:focus-visible { outline: 2px solid var(--cv-primary); outline-offset: 2px; border-radius: var(--cv-r-sm); }

.cv-meena__ask { flex: none; display: flex; align-items: flex-end; gap: 0.5rem; margin-top: 0.75rem; }
.cv-meena__ask[hidden] { display: none; }

.cv-meena__input {
    flex: 1 1 auto;
    min-width: 0;
    min-height: var(--cv-tap, 44px);
    max-height: 7.5rem;
    padding: 0.625rem 0.75rem;
    border: 1px solid var(--cv-border);
    border-radius: var(--cv-r-md);
    background: var(--cv-card);
    color: var(--cv-text);
    font: inherit;
    /* 16px: iOS zooms the page into any smaller field. */
    font-size: 1rem;
    line-height: 1.4;
    resize: none;
    field-sizing: content;
}
.cv-meena__input::placeholder { color: var(--cv-muted); }
.cv-meena__input:focus { outline: 2px solid var(--cv-primary); outline-offset: 0; border-color: var(--cv-primary); }

.cv-meena__send { flex: none; min-height: var(--cv-tap, 44px); }
.cv-meena__send:disabled { opacity: 0.55; cursor: default; }

.cv-meena__accuracy { flex: none; margin: 0.5rem 0 0; font-size: 0.6875rem; line-height: 1.5; color: var(--cv-muted); }

.cv-meena__line { flex: none; }
.cv-meena__panel--ai .cv-meena__line { margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--cv-border); }
.cv-meena__line-lead { margin: 0 0 0.5rem; font-size: 0.8125rem; color: var(--cv-text); }

/* Phones: a bottom sheet the width of the screen. When the on-screen keyboard is up,
   meena-help.js sets --meena-kb and --meena-vh so the question box stays above it. */
@media (max-width: 640px) {
    .cv-meena[open] .cv-meena__fab { visibility: hidden; }

    .cv-meena__panel {
        position: fixed;
        left: 0.5rem;
        right: 0.5rem;
        bottom: calc(var(--meena-kb, 0px) + 0.5rem + env(safe-area-inset-bottom, 0px));
        width: auto;
    }

    .cv-meena__panel--ai {
        max-height: calc(var(--meena-vh, 100vh) - 1rem);
        max-height: calc(var(--meena-vh, 100dvh) - 1rem);
    }
}

/* ---------------------------------------------------------------------------
   The welcome rail.

   Named cv-onboard-step, not cv-step: the design system already owns .cv-step, a numbered
   marketing timeline whose ::before carries a decimal-leading-zero counter. Reusing the name
   put a stray "01" bubble on top of this rail, because both rules matched and each won a
   different property.

   Three steps, and a line drawn between them so they read as a sequence rather
   than three cards that happen to be stacked. The line is a pseudo-element on
   the item rather than a border on the list, because it has to stop at the last
   step and a border does not know which step it is on.

   On a phone the rail turns horizontal and loses its copy: three dots and a
   count carry the same information in 40px instead of 240. */
.cv-onboard-step {
    position: relative;
    display: flex;
    gap: 0.875rem;
    padding-bottom: 1.5rem;
}

.cv-onboard-step:last-child { padding-bottom: 0; }

.cv-onboard-step::before {
    content: '';
    position: absolute;
    left: 1.0625rem;
    top: 2.25rem;
    bottom: 0.25rem;
    width: 2px;
    background: var(--cv-border);
}

.cv-onboard-step:last-child::before { display: none; }

.cv-onboard-step__dot {
    position: relative;
    z-index: 1;
    display: grid;
    place-items: center;
    width: 2.125rem;
    height: 2.125rem;
    border-radius: 999px;
    border: 2px solid var(--cv-border);
    background: var(--cv-card);
    color: var(--cv-muted);
    font-size: 0.8125rem;
    font-weight: 700;
    flex: none;
}

.cv-onboard-step--current .cv-onboard-step__dot {
    border-color: var(--cv-primary);
    background: var(--cv-primary-soft);
    color: var(--cv-primary-soft-ink);
}

.cv-onboard-step--done .cv-onboard-step__dot {
    border-color: var(--cv-success);
    background: var(--cv-success);
    color: #fff;
}

.cv-onboard-step--done::before { background: var(--cv-success); }

/* The rail entry and the phone dots are both controls, so both clear the 44px floor the
   design system sets in --cv-tap. The dot keeps its small ring; it is the hit area that
   grows, which is the part nobody can see and everybody misses. */
.cv-onboard-step button {
    min-height: var(--cv-tap);
}

.cv-onboard-dot {
    width: var(--cv-tap);
    height: var(--cv-tap);
}

/* ExpiryCountdown: the date, then a pill that says how soon, then an optional bar of the
   period that is left. Flat fills from the status tokens, so dark mode follows them. */
.cv-expiry {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.375rem 0.5rem;
}

.cv-expiry__date {
    font-size: 0.8125rem;
    color: var(--cv-muted);
    white-space: nowrap;
}

.cv-expiry__pill {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.125rem 0.5rem 0.125rem 0.375rem;
    border-radius: 9999px;
    font-size: 0.75rem;
    font-weight: 600;
    line-height: 1.25rem;
    white-space: nowrap;
}

.cv-expiry__pill .material-symbols-rounded {
    font-size: 0.9375rem;
}

.cv-expiry__pill--success { background: var(--cv-success-soft); color: var(--cv-success); }
.cv-expiry__pill--warning { background: var(--cv-warning-soft); color: var(--cv-warning); }
.cv-expiry__pill--danger  { background: var(--cv-danger-soft);  color: var(--cv-danger); }

.cv-expiry__bar {
    flex-basis: 100%;
    height: 0.25rem;
    border-radius: 9999px;
    background: var(--cv-bg-subtle);
    overflow: hidden;
}

.cv-expiry__fill {
    display: block;
    height: 100%;
    border-radius: inherit;
}

.cv-expiry__fill--success { background: var(--cv-success); }
.cv-expiry__fill--warning { background: var(--cv-warning); }
.cv-expiry__fill--danger  { background: var(--cv-danger); }
