/* ==========================================================================
   Component library — every reusable visual pattern used across the app.
   Class names are kept stable from the previous stylesheet wherever they
   already existed; only their visual rules changed. New patterns (icons,
   skeleton loaders, footer, breadcrumb, pagination, tabs, accordion,
   timeline) are appended at the end.
   ========================================================================== */

/* ---- Icons (Lucide renders inline SVG with stroke="currentColor") ---- */
.icon {
    display: inline-block;
    width: 18px;
    height: 18px;
    vertical-align: -3px;
    flex: none;
}
.icon-sm { width: 16px; height: 16px; vertical-align: -3px; }
.icon-md { width: 20px; height: 20px; vertical-align: -4px; }
.icon-lg { width: 28px; height: 28px; }
.icon-xl { width: 40px; height: 40px; }

/* ---- App shell / navbar ---- */
.app-shell {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

/* Fixed to the top of the viewport so it stays visible while scrolling —
   `.page` already reserves 68px of top padding to compensate (see below).
   Declared explicitly (not left to markup's `fixed-top` class alone) so it
   doesn't depend on Bootstrap's stylesheet loading first. Being `position:
   fixed` still makes it a valid containing block for the `::before` below
   (position:absolute) without affecting position:fixed descendants like the
   mobile nav panel/backdrop, unlike backdrop-filter (see next). */
.app-navbar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1030;
    padding-top: var(--space-3);
    padding-bottom: var(--space-3);
}

/* The frosted-glass look lives on a ::before instead of directly on
   .app-navbar. backdrop-filter (like transform/filter/perspective) makes an
   element the containing block for its position:fixed descendants — and
   `#navbarNav`/`#navbarBackdrop` (the mobile off-canvas panel) are real DOM
   children of .app-navbar that rely on `top/bottom: 0` resolving against the
   *viewport*. With backdrop-filter directly on .app-navbar, that panel
   collapsed to the navbar's own ~70px height instead of full-screen. A
   generated pseudo-element has no such descendants, so it can carry the blur
   without changing what .app-navbar's real children resolve their fixed
   positioning against. */
.app-navbar::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: rgba(255, 255, 255, .96);
    border-bottom: 1px solid var(--line);
    box-shadow: 0 6px 24px rgba(23, 33, 43, .06);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
    transition: background var(--duration-slow) var(--ease-standard),
                box-shadow var(--duration-slow) var(--ease-standard),
                border-color var(--duration-slow) var(--ease-standard);
}

/* ---- Hero-page navbar state (guest homepage only, see app.js) — starts
   lighter/borderless at the top of the hero, then firms into the default
   frosted look above once the page scrolls. Every other page never gets
   the `navbar-on-hero` class, so its navbar is unaffected. ---- */
.app-navbar.navbar-on-hero::before {
    background: rgba(255, 255, 255, .55);
    border-bottom-color: transparent;
    box-shadow: none;
}
.app-navbar.navbar-on-hero.is-scrolled::before {
    background: rgba(255, 255, 255, .96);
    border-bottom-color: var(--line);
    box-shadow: 0 6px 24px rgba(23, 33, 43, .06);
}

/* The frosted-glass rgba above is a literal white, not a token, since it
   needs an alpha channel tokens.css can't express — dark mode needs its own
   literal here too (matching --surface's dark value, #131c30). */
:root[data-theme="dark"] .app-navbar::before,
:root[data-theme="dark"] .app-navbar.navbar-on-hero.is-scrolled::before {
    background: rgba(19, 28, 48, .96);
    box-shadow: 0 6px 24px rgba(0, 0, 0, .35);
}
:root[data-theme="dark"] .app-navbar.navbar-on-hero::before {
    background: rgba(19, 28, 48, .55);
}

.navbar-brand {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--text) !important;
    font-weight: var(--fw-black);
    font-size: var(--text-md);
}

/* Two-tone wordmark (navbar only — the footer keeps a plain single-color
   lockup, which is a deliberate, separate choice, not an inconsistency). */
.brand-wordmark-kanou { color: var(--primary); }
.brand-wordmark-health { color: var(--secondary); }

.brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: var(--radius-md);
    background: var(--primary);
    color: #fff;
    flex: none;
}

.app-navbar .nav-link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    color: var(--muted) !important;
    border-radius: var(--radius-md);
    font-weight: var(--fw-semibold);
    padding: var(--space-2) var(--space-3) !important;
    white-space: nowrap;
    transition: color var(--duration-fast) var(--ease-standard), background var(--duration-fast) var(--ease-standard);
}

@media (min-width: 992px) {
    /* Authenticated pages moved their nav into the sidebar, so this navbar
       only ever renders the guest link set (7 items) at this breakpoint now.
       That's short of one row by ~11px right at 992px without this tighter
       padding, which used to wrap the whole bar onto a second, taller row —
       forcing every page to reserve extra top clearance just for that ~8px
       sliver of viewport widths. Trimming the link padding keeps it to a
       single row at every width lg and up, so .page/.app-sidebar can safely
       assume one fixed navbar height instead of padding for the wrapped case. */
    .app-navbar .nav-link { padding: var(--space-2) var(--space-2) !important; }
    .app-navbar .navbar-nav { flex-wrap: nowrap; justify-content: flex-end; }
    .app-navbar .navbar-nav-primary { margin-left: var(--space-5); justify-content: flex-start; }

    /* Search / theme toggle / language go icon-only once there's a fixed
       top navbar row to keep compact (mobile's off-canvas menu has plenty of
       vertical room and no hover affordance, so it keeps the text labels).
       Scoped to .nav-link-icon specifically, not the general .nav-link/
       .theme-toggle classes — Sign In and the CTA button share .nav-link's
       parent list and must keep their own natural width. */
    .nav-utility-label { display: none !important; }
    .app-navbar .nav-link-icon {
        width: 40px;
        justify-content: center;
        padding: var(--space-2) !important;
    }
}

.app-navbar .nav-link:hover {
    color: var(--primary-dark) !important;
    background: var(--surface-soft);
    text-decoration: none;
}

/* Active link reads as "underlined", not "pill-highlighted" — kept visually
   distinct from :hover (which still gets the soft pill) so the current page
   stays legible even while hovering a different link. */
.app-navbar .nav-link.active {
    color: var(--primary-dark) !important;
    background: transparent;
    box-shadow: inset 0 -2px 0 var(--primary);
    text-decoration: none;
}
.app-navbar .nav-link.active:hover { background: var(--surface-soft); }

.app-navbar .nav-link.text-danger:hover {
    color: var(--danger-700) !important;
    background: var(--danger-50);
}

.app-navbar .btn-primary.nav-cta {
    margin-left: var(--space-2);
}

/* ---- Theme toggle (navbar + sidebar share this) ---- */
.theme-toggle {
    background: transparent;
    border: 0;
    font-family: inherit;
    cursor: pointer;
}
.theme-toggle-icon-light, .theme-toggle-label-light { display: none; }
[data-theme="dark"] .theme-toggle-icon-dark, [data-theme="dark"] .theme-toggle-label-dark { display: none; }
[data-theme="dark"] .theme-toggle-icon-light { display: inline-block; }
[data-theme="dark"] .theme-toggle-label-light { display: inline; }

.app-navbar .dropdown-menu {
    background: var(--surface);
    margin-top: var(--space-1);
    padding: var(--space-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
}
.app-navbar .dropdown-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    border-radius: var(--radius-md);
    padding: var(--space-2) var(--space-3);
    color: var(--text);
    font-weight: var(--fw-medium);
}
.app-navbar .dropdown-item:hover,
.app-navbar .dropdown-item:focus {
    background: var(--surface-soft);
    color: var(--primary-dark);
}
.app-navbar .dropdown-item.active {
    background: var(--surface-soft);
    color: var(--primary-dark);
    font-weight: var(--fw-bold);
}
.app-navbar .dropdown-item.disabled,
.app-navbar .dropdown-item.disabled:hover {
    color: var(--gray-400);
    background: transparent;
    cursor: default;
}
.app-navbar .dropdown-item .field-hint { margin-left: auto; font-weight: var(--fw-regular); }
/* Each language_switcher.html entry is a <form> wrapping a <button
   class="dropdown-item"> (so choosing a language POSTs to set_language) —
   Bootstrap already styles button.dropdown-item like the <a> it replaced,
   this just resets the <form> wrapper's own box so it doesn't add spacing. */
.dropdown-item-form { margin: 0; }

.navbar-toggler {
    position: relative;
    z-index: 1060;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    color: var(--text);
    background: transparent;
    transition: background var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard);
}
.navbar-toggler:hover { background: var(--surface-soft); border-color: rgba(21, 101, 192, .4); }
/* Signals unread notifications are waiting inside the sidebar (the app's
   real nav drawer on mobile, hidden behind this toggle until opened) — the
   only notification affordance visible in the top bar itself before that. */
.navbar-toggler-badge {
    position: absolute;
    top: 5px;
    right: 5px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--danger-600);
    border: 2px solid var(--surface);
}
.navbar-toggler:focus { box-shadow: var(--shadow-focus); outline: none; }

.navbar-toggler-icon-wrap { position: relative; display: inline-flex; width: 20px; height: 20px; }
.navbar-toggler-icon-wrap .icon {
    position: absolute;
    inset: 0;
    transition: opacity var(--duration-base) var(--ease-standard), transform var(--duration-base) var(--ease-standard);
}
.navbar-toggler-icon-close { opacity: 0; transform: rotate(-90deg) scale(.5); }
.navbar-toggler[aria-expanded="true"] .navbar-toggler-icon-menu { opacity: 0; transform: rotate(90deg) scale(.5); }
.navbar-toggler[aria-expanded="true"] .navbar-toggler-icon-close { opacity: 1; transform: rotate(0) scale(1); }

/* ---- Mobile off-canvas nav panel + backdrop ----
   Bootstrap's collapse.js is intentionally not used here (its instant/jQuery-eased
   slide reads as dated) — visibility is driven entirely by our own `.show` class
   toggle in app.js so the panel gets one deliberate, tokenized transition. ---- */
.navbar-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1040;
    background: rgba(4, 20, 30, .45);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--duration-slow) var(--ease-standard), visibility 0s linear var(--duration-slow);
}
.navbar-backdrop.show {
    opacity: 1;
    visibility: visible;
    transition: opacity var(--duration-slow) var(--ease-standard);
}

@media (max-width: 991.98px) {
    .navbar-collapse {
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        z-index: 1050;
        display: flex;
        flex-direction: column;
        align-items: stretch;
        width: min(320px, 84vw);
        padding: 88px var(--space-5) var(--space-6);
        background: var(--surface);
        box-shadow: var(--shadow-lg);
        overflow-y: auto;
        transform: translateX(100%);
        transition: transform var(--duration-slow) var(--ease-standard);
    }
    .navbar-collapse.show { transform: translateX(0); }
    .navbar-collapse .navbar-nav { flex-direction: column; align-items: stretch; gap: var(--space-1); }
    .navbar-collapse .nav-item { width: 100%; }
    .navbar-collapse .nav-link { min-height: 44px; }
    /* .navbar-nav-utility carries Bootstrap's .ml-auto (margin-left: auto
       !important) for its desktop row layout, where it pushes the group to
       the far end of the bar. In this column layout that same auto margin
       pulls the whole list to the right edge instead of letting it stretch
       flush with the primary nav above it, so it must be neutralized here. */
    .navbar-collapse .navbar-nav-utility {
        margin-left: 0 !important;
        margin-top: var(--space-3);
        padding-top: var(--space-3);
        border-top: 1px solid var(--line);
    }
    body.nav-open { overflow: hidden; }
}
@media (min-width: 992px) {
    .navbar-backdrop { display: none; }
}

/* ---- Portal sidebar ----
   The single nav for logged-in users at every screen size — not a
   desktop-only extra alongside a separate mobile menu. Below 992px it's the
   *same* off-canvas mechanism as `.navbar-collapse` above (fixed, translateX
   to hide, `.show` to reveal, shared backdrop + body scroll lock), just
   sliding in from the left instead of the right since that's where it lives
   once docked. At 992px+ it becomes a persistent, always-visible column. ---- */
.app-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 1050;
    display: flex;
    flex-direction: column;
    width: min(280px, 84vw);
    padding: 68px var(--space-3) var(--space-5);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    overflow-y: auto;
    transform: translateX(-100%);
    transition: transform var(--duration-slow) var(--ease-standard);
}
.app-sidebar.show { transform: translateX(0); }

@media (min-width: 992px) {
    .app-sidebar {
        z-index: 1020;
        width: 240px;
        padding: 68px var(--space-3) var(--space-5);
        border-right: 1px solid var(--line);
        box-shadow: none;
        transform: none;
    }

    .app-shell.has-sidebar .app-content-area { margin-left: 240px; }
}

.sidebar-nav { display: flex; flex-direction: column; gap: var(--space-1); }
.sidebar-link {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    min-height: 44px;
    border-radius: var(--radius-md);
    color: var(--muted);
    font-weight: var(--fw-semibold);
    font-size: var(--text-sm);
    transition: background var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard);
}
/* Unread-notification count — same "push to the far edge of a flex row"
   technique already used for .dropdown-item .field-hint elsewhere. */
.notif-count-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    margin-left: auto;
    padding: 0 5px;
    border-radius: var(--radius-full);
    background: var(--danger-600);
    color: #fff;
    font-size: 11px;
    font-weight: var(--fw-bold);
    line-height: 1;
}
.sidebar-link:hover,
.sidebar-link.active {
    background: var(--surface-soft);
    color: var(--primary-dark);
    text-decoration: none;
}
.sidebar-link.text-danger:hover { background: var(--danger-50); color: var(--danger-700); }

.sidebar-footer {
    margin-top: auto;
    padding-top: var(--space-3);
    border-top: 1px solid var(--line);
}
/* Icon-only cluster (Search, Dark mode, Language, Logout) — same controls
   that used to be full-width .sidebar-link rows, now a compact row of
   circular icon buttons so they read as one group of quick settings rather
   than more nav items. */
.sidebar-footer-icons { display: flex; align-items: center; justify-content: space-between; gap: var(--space-1); }
.sidebar-icon-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: var(--radius-full);
    color: var(--muted);
    background: transparent;
    border: none;
    transition: background var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard);
}
.sidebar-icon-btn:hover,
.sidebar-icon-btn:focus-visible { background: var(--surface-soft); color: var(--primary-dark); text-decoration: none; }
.sidebar-icon-btn.text-danger:hover { background: var(--danger-50); color: var(--danger-700); }
.sidebar-lang { position: relative; }
.sidebar-lang .dropdown-menu {
    background: var(--surface);
    padding: var(--space-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
}
.sidebar-lang .dropdown-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    border-radius: var(--radius-md);
    padding: var(--space-2) var(--space-3);
    color: var(--text);
    font-weight: var(--fw-medium);
}
.sidebar-lang .dropdown-item:hover,
.sidebar-lang .dropdown-item:focus { background: var(--surface-soft); color: var(--primary-dark); }
.sidebar-lang .dropdown-item.active { background: var(--surface-soft); color: var(--primary-dark); font-weight: var(--fw-bold); }
.sidebar-lang .dropdown-item.disabled, .sidebar-lang .dropdown-item.disabled:hover { color: var(--gray-400); background: transparent; cursor: default; }
.sidebar-lang .dropdown-item .field-hint { margin-left: auto; font-weight: var(--fw-regular); }

/* ---- Page shell ---- */
.app-content-area {
    display: flex;
    flex-direction: column;
    flex: 1 0 auto;
    min-width: 0;
}

.page {
    width: min(1180px, calc(100% - 32px));
    margin: 0 auto;
    padding: 68px 0 var(--space-12);
    flex: 1 0 auto;
}

.page-wide { width: min(1500px, calc(100% - 32px)); }

.page-header {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-5);
    margin-bottom: var(--space-6);
}

.eyebrow {
    margin: 0 0 var(--space-2);
    color: var(--primary);
    font-size: var(--text-xs);
    font-weight: var(--fw-bold);
    letter-spacing: .08em;
    text-transform: uppercase;
}

.page-title {
    margin: 0;
    color: var(--text);
    font-size: clamp(1.75rem, 3vw, var(--text-4xl));
    font-weight: var(--fw-black);
    line-height: var(--lh-tight);
}

.page-subtitle {
    max-width: 680px;
    margin: var(--space-2) 0 0;
    color: var(--muted);
    font-size: var(--text-md);
    line-height: var(--lh-relaxed);
}

/* Warm one-line reinforcement between a page-title and its page-subtitle,
   e.g. "We're here to help." on the Contact page. */
.lead-line {
    margin: var(--space-3) 0 0;
    color: var(--text);
    font-size: var(--text-lg);
    font-weight: var(--fw-bold);
}

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

.panel-body { padding: var(--space-6); }

.section-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: var(--radius-md);
    background: var(--surface-soft);
    color: var(--primary);
    flex: none;
}

/* ---- Buttons ---- */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: .6rem 1.1rem;
    border-radius: var(--radius-md);
    font-weight: var(--fw-bold);
    font-size: var(--text-sm);
    line-height: var(--lh-normal);
    transition: background var(--duration-fast) var(--ease-standard),
                border-color var(--duration-fast) var(--ease-standard),
                color var(--duration-fast) var(--ease-standard),
                transform var(--duration-fast) var(--ease-standard);
}
.btn:active { transform: translateY(1px); }
.btn-sm { padding: .4rem .8rem; font-size: var(--text-xs); }
.btn-lg { padding: .8rem 1.4rem; font-size: var(--text-base); }

.btn-primary { background: var(--primary); border-color: var(--primary); }
.btn-primary:hover, .btn-primary:focus { background: var(--primary-dark); border-color: var(--primary-dark); }

.btn-outline-primary { color: var(--primary); border-color: var(--primary); background: transparent; }
.btn-outline-primary:hover { background: var(--primary); border-color: var(--primary); color: #fff; }

.btn-outline-secondary { color: var(--gray-700); border-color: var(--line); background: transparent; }
.btn-outline-secondary:hover { background: var(--gray-100); border-color: var(--gray-300); color: var(--text); }

.btn-danger { background: var(--danger-600); border-color: var(--danger-600); }
.btn-danger:hover { background: var(--danger-700); border-color: var(--danger-700); }

/* ---- Button loading state — hides the button's own content (not removed,
   so its accessible name is unchanged for assistive tech) and shows a
   spinner instead; pointer-events off so a slow double-submit can't fire. ---- */
.btn.is-loading {
    position: relative;
    color: transparent !important;
    pointer-events: none;
}
.btn.is-loading > * { visibility: hidden; }
.btn.is-loading::after {
    content: "";
    position: absolute;
    width: 16px;
    height: 16px;
    border-radius: var(--radius-full);
    border: 2px solid rgba(255, 255, 255, .5);
    border-top-color: #fff;
    animation: btn-spin .6s linear infinite;
}
.btn-outline-primary.is-loading::after,
.btn-outline-secondary.is-loading::after {
    border-color: rgba(21, 101, 192, .3);
    border-top-color: var(--primary);
}
@keyframes btn-spin {
    to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
    /* A loading spinner is essential motion (WCAG 2.3.3), not decorative —
       tokens.css's global `animation-duration: .01ms !important` rule would
       otherwise freeze this after one imperceptible spin, making a
       still-loading button look stuck. !important overrides that on
       purpose, for this one case only. */
    .btn.is-loading::after { animation-duration: .6s !important; animation-iteration-count: infinite !important; }
}

.btn-link { font-weight: var(--fw-semibold); }

/* ---- Forms ---- */
.form-control,
.custom-select,
select,
textarea.form-control {
    min-height: 44px;
    padding: .55rem .85rem;
    background: var(--surface);
    border: 1px solid var(--gray-300);
    border-radius: var(--radius-md);
    color: var(--text);
    font-size: var(--text-base);
    transition: border-color var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard);
}
textarea.form-control { min-height: auto; }
.form-control::placeholder { color: var(--gray-400); }
.form-control:focus,
.custom-select:focus,
select:focus {
    border-color: var(--primary);
    box-shadow: var(--shadow-focus);
    outline: none;
}
.is-invalid, .form-control.is-invalid { border-color: var(--danger-600); }

.form-group { margin-bottom: var(--space-4); }
.form-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-5);
}
.form-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-top: var(--space-5);
}
.field-hint {
    margin: var(--space-1) 0 0;
    color: var(--muted);
    font-size: var(--text-sm);
}
.invalid-feedback { color: var(--danger-600); font-size: var(--text-sm); margin-top: var(--space-1); }

.form-check { display: flex; align-items: flex-start; gap: var(--space-2); padding-left: 0; }
/* Resets Bootstrap 4's own .form-check-input (position: absolute; margin-left:
   -1.25rem;), which assumes the classic padding-left-on-parent technique —
   left in place here it fights the flexbox layout above and leaves a gap
   where the checkbox should sit in the row. */
.form-check-input { position: static; width: 18px; height: 18px; margin: 2px 0 0; accent-color: var(--primary); }
.form-check-label { color: var(--gray-700); font-weight: var(--fw-regular); }

.input-group { display: flex; align-items: stretch; }
.input-group .form-control { border-radius: var(--radius-md) 0 0 var(--radius-md); }
.input-group-append .btn {
    border: 1px solid var(--gray-300);
    border-left: 0;
    background: var(--gray-50);
    color: var(--gray-600);
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
}
.input-group-prepend .input-group-text {
    display: flex;
    align-items: center;
    padding: 0 var(--space-4);
    border: 1px solid var(--gray-300);
    border-right: 0;
    background: var(--gray-50);
    color: var(--gray-600);
    border-radius: var(--radius-md) 0 0 var(--radius-md);
    font-weight: var(--fw-semibold);
}
.input-group-prepend + .form-control { border-radius: 0 var(--radius-md) var(--radius-md) 0; }

/* ---- Badges ---- */
.badge-soft-success, .badge-soft-warning, .badge-soft-muted, .badge-soft-danger, .badge-soft-accent {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: .3rem .65rem;
    border-radius: var(--radius-full);
    font-size: var(--text-xs);
    font-weight: var(--fw-bold);
    line-height: 1.4;
}
.badge-soft-success { background: var(--success-100); color: var(--success-700); }
.badge-soft-warning { background: var(--warning-100); color: var(--warning-700); }
.badge-soft-danger { background: var(--danger-100); color: var(--danger-700); }
.badge-soft-accent { background: var(--accent-100); color: var(--accent-700); }
.badge-soft-muted { background: var(--gray-100); color: var(--gray-600); }

/* A quiet "you haven't opened this yet" marker on the reminders list. It
   replaced a danger-red "New" pill — none of the reminder kinds are urgent, and
   that red is the same one a genuine high-risk result uses, so a pill there read
   as an alert. A small accent dot signals "unread" without shouting. */
.reminder-unread-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--accent-700);
    vertical-align: middle;
    margin-inline-end: var(--space-1);
}

/* ---- Alerts ---- */
.alert {
    padding: var(--space-4) var(--space-5);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
    transition: opacity var(--duration-slow) var(--ease-standard);
}
.alert-success { background: var(--success-50); border-color: var(--success-100); color: var(--success-700); }
.alert-danger { background: var(--danger-50); border-color: var(--danger-100); color: var(--danger-700); }
.alert-warning { background: var(--warning-50); border-color: var(--warning-100); color: var(--warning-700); }
.alert-info { background: var(--accent-50); border-color: var(--accent-100); color: var(--accent-700); }
.alert .close { color: inherit; opacity: .5; text-shadow: none; }
.alert .close:hover { opacity: .85; }
.alert.fade:not(.show) { opacity: 0; }

/* ---- Tables ---- */
.table-shell {
    overflow-x: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
}
.table { margin-bottom: 0; background: var(--surface); font-size: var(--text-sm); }
.table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--gray-50);
    color: var(--gray-600);
    border-bottom: 1px solid var(--line);
    font-size: var(--text-xs);
    letter-spacing: .04em;
    text-transform: uppercase;
    white-space: nowrap;
    padding: var(--space-3) var(--space-4);
}
.table td { vertical-align: middle; padding: var(--space-3) var(--space-4); border-top: 1px solid var(--line); }
.table-hover tbody tr:hover { background: var(--surface-soft); }
.table-striped tbody tr:nth-of-type(odd) { background: var(--gray-25); }

/* ---- Modals ---- */
.modal-content { background: var(--surface); color: var(--text); border: 0; border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); }
.modal-header { padding: var(--space-5) var(--space-6); border-bottom: 1px solid var(--line); }
.modal-title { font-weight: var(--fw-bold); }
.modal-body { padding: var(--space-6); }
.modal-footer { padding: var(--space-5) var(--space-6); border-top: 1px solid var(--line); gap: var(--space-2); }
.modal-backdrop.show { opacity: .5; }

/* ---- Empty state ---- */
.empty-state { padding: var(--space-10) var(--space-6); text-align: center; }
.empty-state .icon,
.empty-state i {
    display: inline-flex;
    margin-bottom: var(--space-3);
    color: var(--primary);
    width: 40px;
    height: 40px;
}
.empty-state h1, .empty-state h2, .empty-state h3 { margin-bottom: var(--space-2); font-weight: var(--fw-black); }
.empty-state p { max-width: 480px; margin: 0 auto var(--space-5); color: var(--muted); }
.empty-illustration { display: inline-block; margin-bottom: var(--space-4); }

/* ---- Illustrations ---- */
.hero-illustration-wrap { display: none; flex: none; width: min(320px, 34vw); }
@media (min-width: 992px) {
    .hero-illustration-wrap { display: block; }
}

/* ---- Hero panel decoration (guest homepage) — soft blurred shapes sitting
   behind the hero content, in their own stacking context (position:relative +
   z-index:0) so a negative z-index on the shapes stays safely contained
   instead of leaking behind unrelated page elements. ---- */
.hero-panel {
    position: relative;
    z-index: 0;
    overflow: hidden;
    padding: var(--space-2) 0;
    border-radius: var(--radius-xl);
}
.hero-shapes {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
}
.hero-shape {
    position: absolute;
    border-radius: var(--radius-full);
    animation: hero-shape-float 10s var(--ease-standard) infinite;
}
.hero-shape-2 { width: 90px; height: 90px; right: 12%; top: 4%; background: var(--secondary-50); animation-duration: 8s; animation-direction: reverse; }
.hero-shape-3 { width: 60px; height: 60px; right: 22%; bottom: -20px; background: var(--accent-50); animation-duration: 12s; }
@keyframes hero-shape-float {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-16px); }
}
@media (prefers-reduced-motion: reduce) {
    .hero-shape { animation: none; }
}
/* hero-shape-2/-3 sit in the same right-hand region as .hero-illustration-wrap
   and only read as background texture behind it. Below 992px the illustration
   hides but these don't, so they land directly on the now-single-column hero
   text instead — hide them together with the illustration they're paired with. */
@media (max-width: 991.98px) {
    .hero-shape-2, .hero-shape-3 { display: none; }
}

/* ---- Landing-page section rhythm (guest home.html only) — every body
   section gets consistent internal padding instead of sitting flush with
   the page column, and alternating sections get a soft pale-blue "shelf"
   background so consecutive sections read as distinct steps down the page.
   Pale blue (var(--surface-soft), not white) specifically so the white
   .info-card/.metric grids nested inside still contrast against it, rather
   than blending the way white-on-white would. Padding values match the
   #cta section's own already-established padding for consistency. ---- */
.section-band {
    padding: var(--space-10) var(--space-6);
    border-radius: var(--radius-xl);
}
.section-band-tint {
    background: var(--surface-soft);
}
/* Centered section intro (badge + eyebrow + heading + optional subtitle).
   Deliberately doesn't apply to the hero itself (h1), which stays its own
   established asymmetric text+illustration layout, nor to the info-grid/
   timeline/metric-grid content below each intro, which stays left-aligned
   for scanability. */
.section-head {
    max-width: 700px;
    margin: 0 auto var(--space-8);
    text-align: center;
}
.section-head .page-subtitle {
    max-width: none;
    margin-left: auto;
    margin-right: auto;
}
.section-head-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    margin-bottom: var(--space-4);
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--primary);
    box-shadow: var(--shadow-xs);
}
.section-head-badge .icon { width: 26px; height: 26px; }

/* ---- Alternating media+text row (illustration on one side, prose on the
   other). Only used on sections that are pure prose with no card grid to
   protect (home #mission/#how-it-works, about #our-story) — deliberately
   not retrofitted onto info-grid/metric-grid sections, which keep the
   centered-intro-plus-left-aligned-grid pattern established above. ---- */
.media-row {
    display: flex;
    align-items: center;
    gap: var(--space-10);
}
.media-row-reverse { flex-direction: row-reverse; }
/* width:34% suits the roughly-square custom illustrations (viewBox ~360x300).
   A tall/narrow illustration (e.g. a full-body standing figure) would blow
   past the row's natural height if sized by width alone, so height is
   capped too and the image is contained/centered within that box rather
   than stretched to fill the width. */
.media-row .media-visual {
    flex: none;
    width: 34%;
    min-width: 220px;
    max-height: 380px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.media-row .media-illustration { display: block; width: 100%; max-width: 100%; height: auto; max-height: 380px; object-fit: contain; }
/* Real-photo variant: unlike the icon-style SVGs above, a photo should fill
   a fixed-aspect frame edge-to-edge (cropped, not contained), with a
   rounded card treatment so it reads as a deliberate photo card rather
   than a floating cutout. */
.media-row .media-visual-photo { max-height: none; display: block; }
.media-photo {
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-md);
}
.media-row .media-copy { flex: 1; min-width: 0; }
.media-row .media-copy .section-head { margin: 0; max-width: none; text-align: left; }
.media-row .media-copy .section-head-badge { margin-bottom: var(--space-3); }

/* The AI-generated (or rule-based fallback) recommendation's opening line —
   its own likelihood-of-diabetes judgment, split out from the numbered list
   that follows it (see predictor.views._split_recommendation()) so it can
   read as a headline instead of blending into the paragraph body. Used on
   both the results page (predict.html) and the dashboard (home.html). */
.recommendation-headline {
    font-size: var(--text-xl);
    font-weight: var(--fw-bold);
    line-height: var(--lh-tight);
    color: var(--primary-dark);
    margin: var(--space-2) 0 var(--space-3);
}

/* Portrait crop for a headshot (e.g. the About page founder profile) —
   taller than the default 4:3 photo frame so a square source image reads
   as a deliberate portrait instead of cropping into the forehead/chin.
   Sized down and fixed (rather than the 34%-of-row width other .media-row
   photos use) since this is a compact bio card, not a full illustrative
   section — the #founder id selector outranks both the base
   `.media-row .media-visual` rule and its mobile override so this size
   holds at every viewport. */
.founder-portrait-photo { aspect-ratio: 4 / 5; }
#founder .media-visual { width: 200px; min-width: 200px; max-width: 200px; }
#founder .media-row { align-items: flex-start; }
.founder-name { margin: 0 0 var(--space-1); font-family: var(--font-heading); font-size: var(--text-2xl); font-weight: var(--fw-black); color: var(--text); }
.founder-title { margin: 0 0 var(--space-2); color: var(--primary); font-weight: var(--fw-semibold); font-size: var(--text-md); }
.founder-contact { margin: 0 0 var(--space-4); font-size: var(--text-sm); color: var(--muted); }
.founder-contact a { color: var(--primary); text-decoration: none; }
.founder-contact a:hover { text-decoration: underline; }
.founder-bio { margin: 0; color: var(--muted); line-height: var(--lh-relaxed); text-align: justify; }

/* ---- Contact method rows (founder email/phone on the Contact page's
   "Contact the Founder" card) — stacked on separate lines but sharing one
   left-aligned column (width: fit-content sizes the column to the widest
   row, so a shorter row's icon still starts at the same x as the one
   above/below it, instead of each row centering independently). ---- */
.contact-method-list { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); width: fit-content; }
.contact-method-list-center { margin: 0 auto; }
.contact-method {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-lg);
    color: var(--text);
    font-weight: var(--fw-semibold);
    text-decoration: none;
    transition: background var(--duration-base) var(--ease-standard), color var(--duration-base) var(--ease-standard), transform var(--duration-base) var(--ease-standard);
}
.contact-method:hover, .contact-method:focus-visible {
    background: var(--surface-soft);
    color: var(--primary-dark);
    text-decoration: none;
    transform: translateX(2px);
}
.contact-method .icon-badge { flex: none; }

/* Divider with centered label between the direct contact buttons and the
   form on the Contact page's "Contact the Founder" card. */
.form-divider {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    color: var(--muted);
    font-size: var(--text-sm);
    font-weight: var(--fw-semibold);
}
.form-divider::before, .form-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--line);
}

@media (max-width: 767.98px) {
    .media-row, .media-row-reverse { flex-direction: column; gap: var(--space-6); }
    .media-row .media-visual { width: min(280px, 70%); }
    .media-row .media-copy .section-head { text-align: center; margin: 0 auto; }
}

/* .media-row-copy-first: opt-in per instance, not the .media-row default —
   most usages are marketing/illustrative sections where image-first reads
   fine once stacked. The assessment result's recommendation box is
   different — often-long, primary content a user shouldn't have to scroll
   past a photo to reach — so that one instance stacks (and puts copy above
   visual) a full breakpoint earlier than .media-row's own 767.98px, since a
   two-column split was already cramped at tablet width. Self-contained
   rather than layered onto the block above, so it doesn't depend on
   .media-row's narrower breakpoint also firing. Desktop's side-by-side order
   (photo left, recommendation right) is untouched above 991.98px. */
@media (max-width: 991.98px) {
    .media-row-copy-first { flex-direction: column; gap: var(--space-6); }
    .media-row-copy-first .media-visual { width: min(280px, 70%); }
    .media-row-copy-first .media-copy { order: -1; }
}

/* ---- Scroll cue (hero -> next section anchor link) ---- */
.scroll-cue {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    margin-top: var(--space-4);
    color: var(--primary);
    font-weight: var(--fw-semibold);
    font-size: var(--text-sm);
    text-decoration: none;
}
.scroll-cue:hover { color: var(--primary); text-decoration: underline; }
.scroll-cue .icon { animation: scroll-cue-bounce 1.6s var(--ease-standard) infinite; }
@keyframes scroll-cue-bounce {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(4px); }
}
@media (prefers-reduced-motion: reduce) {
    .scroll-cue .icon { animation: none; }
}

/* ---- Skeleton loaders ---- */
@keyframes skeleton-shimmer {
    0% { background-position: -200px 0; }
    100% { background-position: calc(200px + 100%) 0; }
}
.skeleton { position: relative; overflow: hidden; background: var(--gray-100); border-radius: var(--radius-md); }
.skeleton::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .65), transparent);
    background-size: 200px 100%;
    background-repeat: no-repeat;
    animation: skeleton-shimmer 1.4s ease-in-out infinite;
}
:root[data-theme="dark"] .skeleton::after {
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .12), transparent);
    background-size: 200px 100%;
    background-repeat: no-repeat;
}
@media (prefers-reduced-motion: reduce) {
    .skeleton::after { animation: none; }
}
.skeleton-line { height: 12px; margin-bottom: var(--space-2); }
.skeleton-line:last-child { margin-bottom: 0; width: 70%; }
.skeleton-circle { border-radius: var(--radius-full); }
.skeleton-chart { height: 180px; margin-bottom: var(--space-2); }
.skeleton-table { min-height: 360px; }

/* ---- Scroll-reveal utility (see app.js IntersectionObserver hook) ---- */
.reveal {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity var(--duration-slow) var(--ease-standard), transform var(--duration-slow) var(--ease-standard);
}
.reveal-visible { opacity: 1; transform: translateY(0); }
.reveal-stagger > * {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity var(--duration-slow) var(--ease-standard), transform var(--duration-slow) var(--ease-standard);
}
.reveal-stagger.reveal-visible > * { opacity: 1; transform: translateY(0); }
.reveal-stagger.reveal-visible > *:nth-child(2) { transition-delay: 40ms; }
.reveal-stagger.reveal-visible > *:nth-child(3) { transition-delay: 80ms; }
.reveal-stagger.reveal-visible > *:nth-child(4) { transition-delay: 120ms; }
.reveal-stagger.reveal-visible > *:nth-child(5) { transition-delay: 160ms; }
.reveal-stagger.reveal-visible > *:nth-child(6) { transition-delay: 200ms; }
@media (prefers-reduced-motion: reduce) {
    /* Also covered by tokens.css's global transition-duration:.01ms override —
       repeated here explicitly, matching the other reveal-adjacent components
       in this file (.wizard-step, .result-banner, .skeleton), so content is
       visible immediately rather than relying on a near-zero transition. */
    .reveal, .reveal-stagger > * { opacity: 1; transform: none; transition: none; }
}

/* ---- Action grid / metric grid ---- */
.action-grid, .metric-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--space-4);
}
.metric, .action-card {
    padding: var(--space-5);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
}
.metric-label { margin: 0; color: var(--muted); font-size: var(--text-sm); display: flex; align-items: center; gap: var(--space-1); }
.metric-value { margin: var(--space-2) 0 0; font-size: var(--text-2xl); font-weight: var(--fw-black); }
.action-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-height: 180px;
    text-decoration: none;
    transition: transform var(--duration-base) var(--ease-standard),
                border-color var(--duration-base) var(--ease-standard),
                box-shadow var(--duration-base) var(--ease-standard);
}
.action-card:hover {
    border-color: rgba(21, 101, 192, .45);
    box-shadow: var(--shadow-md);
    text-decoration: none;
    transform: translateY(-2px);
}
.action-card .icon, .action-card > i {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: var(--radius-md);
    background: var(--surface-soft);
    color: var(--primary);
}
.action-card strong { color: var(--text); font-size: var(--text-md); font-weight: var(--fw-bold); }
.action-card span { color: var(--muted); line-height: var(--lh-relaxed); }

/* ---- Topic filter bar (education/home.html): client-side filter over one
   compact article grid, replacing a full icon-headed section-band per
   topic — with content spread thin across many topics, a whole band (often
   for just 1-2 cards) was most of the page's scroll length before reaching
   anything. Buttons, not links: this toggles a card's visibility in place,
   it doesn't navigate. ---- */
.topic-filter-bar { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-5); }
.topic-filter-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: .4rem .85rem;
    border-radius: var(--radius-full);
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--text);
    font-size: var(--text-sm);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: background var(--duration-base) var(--ease-standard),
                color var(--duration-base) var(--ease-standard),
                border-color var(--duration-base) var(--ease-standard);
}
.topic-filter-pill:hover { border-color: var(--primary); color: var(--primary-dark); }
.topic-filter-pill.is-active { background: var(--primary); border-color: var(--primary); color: #fff; }
.topic-filter-pill.is-active:hover { color: #fff; }

/* ---- Info cards (static informational content, e.g. landing page) ---- */
.info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--space-4); }

/* Fixed-count card sections (home's Benefits/Prevention, about's Who We
   Serve): all cards in a single row once there's room, instead of
   auto-fit's default wrap. minmax(0, 1fr) (not the 240px floor above) so
   the columns share the container width evenly rather than overflowing
   it. Below 992px, falls back to the normal .info-grid auto-fit behavior,
   same as every other info-grid on the site. Mission/Vision/Core Values
   is the same idea fixed at 3, since Core Values joined that row as a
   third compact card instead of running as its own section. */
@media (min-width: 992px) {
    #benefits .info-grid, #prevention .info-grid, #who-we-serve .info-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }
    #mission-vision-values { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Core values, folded into the mission/vision row as a compact card: values
   render as small pills instead of the full icon-badge+heading treatment
   the other info-cards use, so six of them still fit in one card. */
.value-chip-row { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-1); }
.value-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: .3rem .65rem;
    border-radius: var(--radius-full);
    background: var(--surface-soft);
    color: var(--text);
    font-size: var(--text-xs);
    font-weight: var(--fw-semibold);
}
.value-chip .icon { color: var(--primary); }

/* Icon + heading side by side (icon left, text right) instead of the
   default .info-card stacked layout — used where the card row is short
   on cards (mission/vision/values) and reads better as a compact header. */
.card-head { display: flex; align-items: center; gap: var(--space-3); }
.card-head h3 { margin: 0; }
.info-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-5);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    transition: transform var(--duration-base) var(--ease-standard),
                border-color var(--duration-base) var(--ease-standard),
                box-shadow var(--duration-base) var(--ease-standard);
}
.info-card:hover {
    border-color: rgba(21, 101, 192, .45);
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
}
.info-card h3 { font-size: var(--text-md); font-weight: var(--fw-bold); }
.info-card p { margin: 0; color: var(--muted); line-height: var(--lh-relaxed); }

/* ---- Record card (assessment history list rows) — scoped to this one
   class rather than the base `.panel` (used everywhere for static content
   that isn't interactive), since these rows have real actions inside them. ---- */
.record-card {
    transition: transform var(--duration-base) var(--ease-standard),
                border-color var(--duration-base) var(--ease-standard),
                box-shadow var(--duration-base) var(--ease-standard);
}
.record-card:hover {
    border-color: rgba(21, 101, 192, .45);
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
}

.icon-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: var(--radius-md);
    background: var(--surface-soft);
    color: var(--primary);
    flex: none;
}
.icon-badge-number { font-size: var(--text-md); font-weight: var(--fw-black); }

/* ---- Trust row (small credibility signals under a hero CTA) ---- */
.trust-row { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); margin-top: var(--space-6); padding-top: var(--space-5); border-top: 1px solid var(--line); }
.trust-item { display: flex; align-items: center; gap: var(--space-2); color: var(--muted); font-size: var(--text-sm); font-weight: var(--fw-semibold); }
.trust-item .icon { color: var(--primary); }

/* ---- Summary list / recommendation list ---- */
.summary-list { display: grid; gap: var(--space-3); margin: 0; }
.summary-list div { display: flex; justify-content: space-between; gap: var(--space-4); padding-bottom: var(--space-3); border-bottom: 1px solid var(--line); }
.summary-list dt { color: var(--muted); font-weight: var(--fw-semibold); }
.summary-list dd { margin: 0; text-align: right; font-weight: var(--fw-bold); }
/* Values here are user-supplied and frequently unbreakable — the account page
   puts an email address in both Username and Email. A flex item defaults to
   min-width:auto, so it refuses to shrink below its longest word and the text
   spills past the panel edge instead of wrapping (visible as a clipped
   "mahamatmaina26@hot…"). The value needs the floor lifted plus permission to
   break mid-word, since an email has no spaces to break at.
   Only the value, though: giving the label min-width:0 too lets it shrink
   narrower than the word "Username", whose text then renders across the gap
   and collides with the value. */
.summary-list dt { flex: 0 0 auto; }
.summary-list dd { min-width: 0; overflow-wrap: anywhere; }

.recommendation-list { display: grid; gap: var(--space-3); padding-left: 0; list-style: none; margin: 0; }
.recommendation-list li {
    padding: var(--space-3) var(--space-4);
    background: var(--gray-50);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    color: var(--gray-700);
    line-height: var(--lh-relaxed);
}

/* ---- Assessment wizard steps ---- */
.wizard-step { display: none; }
.wizard-step.is-active {
    display: block;
    animation: wizard-step-in var(--duration-slow) var(--ease-standard);
}
@keyframes wizard-step-in {
    from { opacity: 0; transform: translateX(10px); }
    to { opacity: 1; transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
    .wizard-step.is-active { animation: none; }
}

/* ---- Prediction layout + results ---- */
.prediction-layout {
    display: grid;
    grid-template-columns: minmax(0, 1.25fr) minmax(280px, .75fr);
    gap: var(--space-5);
    align-items: start;
}

@keyframes result-enter {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: translateY(0); }
}
.result-banner {
    padding: var(--space-5) var(--space-6);
    border-radius: var(--radius-lg);
    border-left: 5px solid var(--success-600);
    background: var(--success-50);
    animation: result-enter var(--duration-slow) var(--ease-standard);
}
.result-banner h2, .result-banner h3 { color: var(--success-700); }
@media (prefers-reduced-motion: reduce) {
    .result-banner { animation: none; }
}

/* ---- Result hero — the full results page treatment (predict.html), distinct
   from the compact .result-banner used in the dashboard's "latest result"
   summary (home.html), which stays small and unaffected by this. ---- */
.result-hero {
    text-align: center;
    padding: var(--space-10) var(--space-6);
    border-radius: var(--radius-lg);
    background: var(--success-50);
    border: 1px solid var(--success-100);
    animation: result-enter var(--duration-slow) var(--ease-standard);
}
.result-hero.risk { background: var(--warning-50); border-color: var(--warning-100); }
.result-hero.risk-high { background: var(--danger-50); border-color: var(--danger-100); }
.result-hero .result-icon {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 88px;
    height: 88px;
    border-radius: var(--radius-full);
    background: var(--success-100);
    color: var(--success-700);
    margin: 0 auto var(--space-4);
}
.result-hero.risk .result-icon { background: var(--warning-100); color: var(--warning-700); }
.result-hero.risk-high .result-icon { background: var(--danger-100); color: var(--danger-700); }
.result-hero .result-icon .icon { position: relative; z-index: 1; width: 34px; height: 34px; }

/* ---- Result ring — a decorative draw-in circle around the result icon.
   Deliberately binary (matches the two colors above), not a percentage
   gauge: the trained model is a hard/soft VotingClassifier decided at
   training time and doesn't reliably expose predict_proba, so there is no
   real confidence number to plot without touching predictor/views.py's
   model call — out of scope here. This just doubles as the page's
   "success" confirmation animation. ---- */
.result-ring {
    position: absolute;
    inset: 0;
    transform: rotate(-90deg);
}
.result-ring-track {
    fill: none;
    stroke: var(--success-100);
    stroke-width: 5;
}
.result-hero.risk .result-ring-track { stroke: var(--warning-100); }
.result-hero.risk-high .result-ring-track { stroke: var(--danger-100); }
.result-ring-progress {
    fill: none;
    stroke: var(--success-600);
    stroke-width: 5;
    stroke-linecap: round;
    stroke-dasharray: 245;
    stroke-dashoffset: 245;
    animation: result-ring-draw var(--duration-slow) var(--ease-standard) 200ms forwards;
}
.result-hero.risk .result-ring-progress { stroke: var(--warning-600); }
.result-hero.risk-high .result-ring-progress { stroke: var(--danger-600); }
@keyframes result-ring-draw {
    to { stroke-dashoffset: 0; }
}
.result-hero h2 { color: var(--success-700); font-size: var(--text-3xl); margin-bottom: var(--space-3); }
.result-hero.risk h2 { color: var(--warning-700); }
.result-hero.risk-high h2 { color: var(--danger-700); }
.result-hero .result-explainer {
    max-width: 620px;
    margin: 0 auto;
    color: var(--gray-700);
    font-size: var(--text-md);
    line-height: var(--lh-relaxed);
}
@media (prefers-reduced-motion: reduce) {
    .result-hero { animation: none; }
    .result-ring-progress { animation: none; stroke-dashoffset: 0; }
}

/* ---- Account page ----
   Two-column grid: a narrow identity/side column (profile summary + the
   compact "Your data" danger zone) and a wider main column for the two
   settings forms. No `align-items: start` here (grid's default is
   `stretch`) — the side column is naturally shorter, so it stretches to
   match the main column's height, and `flex: 1` on its last card absorbs
   that extra height so the two cards' bottom edges land exactly together. */
.account-grid { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); gap: var(--space-5); }
.account-grid-side, .account-grid-main { display: flex; flex-direction: column; gap: var(--space-5); min-width: 0; }
.account-grid-side > .panel:last-child { flex: 1 1 auto; }
.profile-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 72px;
    height: 72px;
    margin-bottom: var(--space-3);
    border-radius: var(--radius-full);
    background: var(--surface-soft);
    color: var(--primary);
}
.profile-avatar .icon { width: 32px; height: 32px; }
/* Username and email under the avatar. Usernames here are routinely email
   addresses, which have no spaces to break at — without this they render as
   one long line that overflows the panel rather than wrapping. */
.profile-identity { overflow-wrap: anywhere; }

/* ---- Auth pages ---- */
.auth-page { display: grid; grid-template-columns: minmax(0, 1fr) minmax(360px, 480px); min-height: 100vh; }
.auth-visual {
    position: relative;
    display: flex;
    align-items: flex-end;
    min-height: 100vh;
    padding: var(--space-12);
    overflow: hidden;
    background: var(--surface-inverse);
    color: #fff;
}
.auth-visual::before {
    position: absolute;
    inset: 0;
    content: "";
    background:
        linear-gradient(90deg, rgba(7, 39, 82, .88), rgba(7, 39, 82, .5)),
        url("/static/imgs/auth-hero.e5f184e6bd43.jpg") center/cover;
}
.auth-copy { position: relative; max-width: 620px; }
.auth-copy .eyebrow { color: #90caf9; } /* fixed light blue — panel stays dark in both themes */
.auth-copy h1 { margin: 0 0 var(--space-4); font-size: clamp(2rem, 4vw, var(--text-4xl)); font-weight: var(--fw-black); color: #fff; }
.auth-copy p { max-width: 560px; margin: 0; color: rgba(255, 255, 255, .82); font-size: var(--text-md); line-height: var(--lh-loose); }
.auth-panel { display: flex; align-items: center; justify-content: center; padding: var(--space-8); background: var(--bg); }
.auth-card {
    width: 100%;
    max-width: 420px;
    padding: var(--space-8);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
}
.auth-card h2 { margin: 0 0 var(--space-2); font-weight: var(--fw-black); }
.auth-card .lead { margin-bottom: var(--space-6); color: var(--muted); font-size: var(--text-base); }

/* ---- Footer ---- */
/* ---- Footer — brand column + 2 link columns on a neutral shelf, with a
   thin brand-gradient top edge (primary/secondary/accent) so it reads as a
   deliberate, distinct region regardless of whatever section (tinted or
   not) happens to precede it on any given page. ---- */
.site-footer {
    position: relative;
    margin-top: auto;
    border-top: 1px solid var(--line);
    background: var(--gray-50);
}
.site-footer::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 4px;
    background: linear-gradient(90deg, var(--primary), var(--secondary), var(--accent));
}
.footer-inner {
    width: min(1180px, calc(100% - 32px));
    margin: 0 auto;
    padding: var(--space-10) 0 var(--space-8);
}
.footer-grid {
    display: grid;
    grid-template-columns: 2fr 1fr 1fr;
    gap: var(--space-8);
}
.footer-brand-col { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-3); }
.footer-brand { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-lg); font-weight: var(--fw-bold); color: var(--text); }
.footer-brand:hover { color: var(--primary-dark); text-decoration: none; }
.footer-brand .brand-mark { width: 32px; height: 32px; }
.footer-description { margin: 0; max-width: 340px; color: var(--muted); font-size: var(--text-sm); line-height: var(--lh-relaxed); }
.footer-tagline { margin: 0; color: var(--gray-400); font-size: var(--text-xs); font-style: italic; }

.footer-col-title {
    margin: 0 0 var(--space-3);
    color: var(--gray-500);
    font-size: var(--text-xs);
    font-weight: var(--fw-bold);
    letter-spacing: .06em;
    text-transform: uppercase;
}
.footer-col { display: flex; flex-direction: column; gap: var(--space-2); }
.footer-col a { display: flex; align-items: center; gap: var(--space-2); color: var(--gray-600); font-size: var(--text-sm); font-weight: var(--fw-medium); }
.footer-col a .icon { color: var(--gray-400); }
.footer-col a:hover { color: var(--primary-dark); }
.footer-col a:hover .icon { color: var(--primary-dark); }

.footer-bottom {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    margin-top: var(--space-8);
    padding-top: var(--space-5);
    border-top: 1px solid var(--line);
}
.footer-disclaimer { max-width: 640px; margin: 0; color: var(--gray-500); font-size: var(--text-xs); line-height: var(--lh-relaxed); }
.footer-copyright-block { display: flex; flex-direction: column; gap: 2px; }
.footer-copyright { margin: 0; color: var(--gray-600); font-size: var(--text-xs); white-space: nowrap; }
.footer-attribution { margin: 0; color: var(--gray-500); font-size: var(--text-xs); white-space: nowrap; }
.footer-attribution a { color: var(--gray-600); font-weight: var(--fw-semibold); text-decoration: none; }
.footer-attribution a:hover { color: var(--primary); text-decoration: underline; }
.footer-contact { margin: 2px 0 0; color: var(--gray-500); font-size: var(--text-xs); }
.footer-contact a { color: var(--gray-600); text-decoration: none; }
.footer-contact a:hover { color: var(--primary); text-decoration: underline; }

/* ---- Breadcrumb ---- */
.breadcrumb {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    padding: 0;
    margin: 0 0 var(--space-4);
    background: transparent;
    list-style: none;
    font-size: var(--text-sm);
}
.breadcrumb-item { color: var(--muted); }
.breadcrumb-item a { color: var(--muted); font-weight: var(--fw-medium); }
.breadcrumb-item a:hover { color: var(--primary-dark); }
.breadcrumb-item + .breadcrumb-item::before { content: "/"; margin-right: var(--space-2); color: var(--gray-300); }
.breadcrumb-item.active { color: var(--text); font-weight: var(--fw-semibold); }

/* ---- Pagination ---- */
.pagination { display: flex; gap: var(--space-1); list-style: none; padding: 0; margin: var(--space-4) 0 0; }
.page-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 36px;
    height: 36px;
    padding: 0 var(--space-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    color: var(--gray-700);
    font-weight: var(--fw-semibold);
    font-size: var(--text-sm);
}
.page-link:hover { background: var(--surface-soft); color: var(--primary-dark); text-decoration: none; }
.page-item.active .page-link { background: var(--primary); border-color: var(--primary); color: #fff; }
.page-item.disabled .page-link { color: var(--gray-300); border-color: var(--line); }

/* ---- Tabs ---- */
.nav-tabs { border-bottom: 1px solid var(--line); gap: var(--space-2); }
.nav-tabs .nav-link {
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    padding: var(--space-3) var(--space-2);
    color: var(--muted);
    font-weight: var(--fw-semibold);
}
.nav-tabs .nav-link:hover { color: var(--primary-dark); border-color: var(--line); }
.nav-tabs .nav-link.active { color: var(--primary-dark); border-color: var(--primary); background: transparent; }
.tab-content { padding-top: var(--space-5); }

/* ---- Accordion ---- */
.accordion .card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md); margin-bottom: var(--space-3); overflow: hidden; }
.accordion .card-header {
    width: 100%;
    padding: var(--space-4) var(--space-5);
    background: var(--gray-50);
    border: 0;
    border-bottom: 0;
    color: inherit;
    font-family: inherit;
    font-size: var(--text-base);
    font-weight: var(--fw-semibold);
    text-align: left;
    cursor: pointer;
}
.accordion .card-header:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.accordion .card-body { padding: var(--space-5); color: var(--gray-700); line-height: var(--lh-relaxed); }
.accordion-chevron { transition: transform var(--duration-base) var(--ease-standard); }
.accordion .card-header.collapsed .accordion-chevron { transform: rotate(0deg); }
.accordion .card-header:not(.collapsed) .accordion-chevron { transform: rotate(180deg); color: var(--primary); }
@media (prefers-reduced-motion: reduce) {
    .accordion-chevron { transition: none; }
}

/* ---- Timeline ---- */
.timeline { position: relative; margin: 0; padding-left: var(--space-6); list-style: none; }
.timeline::before { content: ""; position: absolute; left: 5px; top: 4px; bottom: 4px; width: 2px; background: var(--line); }
.timeline-item { position: relative; padding-bottom: var(--space-6); }
.timeline-item:last-child { padding-bottom: 0; }
.timeline-item::before {
    content: "";
    position: absolute;
    left: -25px;
    top: 4px;
    width: 12px;
    height: 12px;
    border-radius: var(--radius-full);
    background: var(--primary);
    box-shadow: 0 0 0 2px var(--surface);
}
.timeline-date { color: var(--muted); font-size: var(--text-xs); font-weight: var(--fw-semibold); text-transform: uppercase; letter-spacing: .04em; }
.timeline-title { margin: var(--space-1) 0; font-weight: var(--fw-bold); }
.timeline-body { color: var(--gray-700); line-height: var(--lh-relaxed); }

/* ---- Progress ---- */
.progress { height: 8px; border-radius: var(--radius-full); background: var(--gray-100); overflow: hidden; }
.progress-bar { background: var(--primary); }

/* ---- Article body (rendered markdown) ---- */
.article-body { color: var(--gray-700); line-height: var(--lh-relaxed); }
.article-body h1, .article-body h2, .article-body h3 { margin: var(--space-6) 0 var(--space-3); }
.article-body h1:first-child, .article-body h2:first-child, .article-body h3:first-child { margin-top: 0; }
.article-body p { margin: 0 0 var(--space-4); }
.article-body ul, .article-body ol { margin: 0 0 var(--space-4); padding-left: var(--space-6); }
.article-body li { margin-bottom: var(--space-2); }
.article-body a { font-weight: var(--fw-semibold); }
.article-body blockquote {
    margin: var(--space-5) 0;
    padding: var(--space-4) var(--space-5);
    border-left: 3px solid var(--primary);
    background: var(--surface-soft);
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
    color: var(--gray-700);
}
.article-body code { background: var(--gray-100); padding: .1em .4em; border-radius: 4px; font-size: .9em; }

/* ---- Legal / statement document (Privacy Policy, Terms of Service,
   Accessibility statement) — one centred card, read top to bottom like a
   printed document: a heading block, a table of contents, then auto-numbered
   sections of plain prose (paragraphs, bullet and numbered lists,
   sub-headings). Prose rules deliberately mirror .article-body. Not wrapped
   in .reveal — a legal document must be readable with JavaScript disabled. ---- */
.legal-doc {
    max-width: 820px;
    margin: 0 auto;
    padding: clamp(var(--space-6), 5vw, var(--space-12));
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    counter-reset: legal-section;
    color: var(--gray-700);
    line-height: var(--lh-relaxed);
}

.legal-doc__eyebrow {
    margin: 0 0 var(--space-2);
    color: var(--primary);
    font-size: var(--text-xs);
    font-weight: var(--fw-bold);
    letter-spacing: .08em;
    text-transform: uppercase;
}
.legal-doc__title {
    margin: 0;
    color: var(--text);
    font-size: clamp(1.6rem, 3vw, var(--text-3xl));
    font-weight: var(--fw-black);
    line-height: var(--lh-tight);
}
.legal-doc__meta { margin: var(--space-3) 0 0; color: var(--muted); font-size: var(--text-sm); }
.legal-doc__lead { margin: var(--space-4) 0 0; color: var(--gray-700); font-size: var(--text-md); line-height: var(--lh-relaxed); }

.legal-doc__rule { border: 0; border-top: 1px solid var(--line); margin: var(--space-8) 0; }

/* Table of contents */
.legal-doc__toc {
    margin: var(--space-6) 0 0;
    padding: var(--space-5) var(--space-6);
    background: var(--surface-soft);
    border-radius: var(--radius-md);
}
.legal-doc__toc-title {
    margin: 0 0 var(--space-3);
    font-size: var(--text-xs);
    font-weight: var(--fw-bold);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--muted);
}
.legal-doc__toc ol { margin: 0; padding-left: var(--space-5); display: grid; gap: var(--space-2); }
.legal-doc__toc li { padding-left: var(--space-1); }
.legal-doc__toc a { font-weight: var(--fw-semibold); color: var(--gray-700); text-decoration: none; }
.legal-doc__toc a:hover, .legal-doc__toc a:focus-visible { color: var(--primary); text-decoration: underline; }

/* Sections */
.legal-doc__section { margin-top: var(--space-8); scroll-margin-top: 84px; }
.legal-doc__section > h2 {
    counter-increment: legal-section;
    margin: 0 0 var(--space-4);
    color: var(--text);
    font-size: var(--text-xl);
    font-weight: var(--fw-bold);
    line-height: var(--lh-snug);
}
.legal-doc__section > h2::before {
    content: counter(legal-section) ". ";
    color: var(--primary);
    font-weight: var(--fw-black);
}
.legal-doc h3 { margin: var(--space-5) 0 var(--space-2); color: var(--text); font-size: var(--text-md); font-weight: var(--fw-bold); }
.legal-doc p { margin: 0 0 var(--space-4); }
.legal-doc > *:last-child, .legal-doc__section > *:last-child { margin-bottom: 0; }
.legal-doc ul, .legal-doc ol { margin: 0 0 var(--space-4); padding-left: var(--space-6); }
.legal-doc li { margin-bottom: var(--space-2); }
.legal-doc li:last-child { margin-bottom: 0; }
.legal-doc a { font-weight: var(--fw-semibold); color: var(--primary); }

/* In-document callout (e.g. the "not medical advice" reminder) */
.legal-doc__note {
    margin: var(--space-5) 0 0;
    padding: var(--space-4) var(--space-5);
    border-left: 3px solid var(--primary);
    background: var(--surface-soft);
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
    color: var(--gray-700);
    font-size: var(--text-sm);
}
.legal-doc__note p { margin: 0; }

@media (max-width: 575.98px) {
    .legal-doc__toc { padding: var(--space-4); }
}

/* ---- DataTables theming ---- */
.dataTables_wrapper .dt-buttons { display: inline-flex; flex-wrap: wrap; gap: var(--space-2); margin: 0 0 var(--space-3); }
.dataTables_wrapper .dt-button {
    border: 1px solid var(--line) !important;
    border-radius: var(--radius-md) !important;
    background: #fff !important;
    color: var(--text) !important;
    font-weight: var(--fw-semibold);
    padding: .4rem .85rem !important;
    font-size: var(--text-sm) !important;
}
.dataTables_wrapper .dt-button:hover { background: var(--surface-soft) !important; border-color: var(--primary) !important; }
.dataTables_wrapper .dataTables_filter input,
.dataTables_wrapper .dataTables_length select {
    border-radius: var(--radius-md);
    border: 1px solid var(--gray-300);
    padding: .3rem .6rem;
}
.dataTables_wrapper .dataTables_info, .dataTables_wrapper .dataTables_paginate { font-size: var(--text-sm); color: var(--muted); }
.dataTables_wrapper .paginate_button.current {
    background: var(--primary) !important;
    border-color: var(--primary) !important;
    color: #fff !important;
    border-radius: var(--radius-sm);
}

/* ---- Responsive ----
   `minmax(0, 1fr)`, not a bare `1fr`: a plain `1fr` track's automatic minimum
   is its content's min-content size, so one oversized child (a wide table, an
   un-shrunk canvas, anything without its own max-width) can force the whole
   track — and the page — wider than the viewport. `minmax(0, 1fr)` clamps
   that minimum to 0 so the track always honors the viewport instead. ---- */
@media (max-width: 991.98px) {
    .auth-page, .prediction-layout, .prediction-layout.has-result, .account-grid {
        grid-template-columns: minmax(0, 1fr);
    }
    .auth-visual { min-height: 280px; padding: var(--space-8); }
    .page-header { align-items: flex-start; flex-direction: column; }
    .footer-grid { grid-template-columns: minmax(0, 1fr); gap: var(--space-6); }
    .footer-bottom { flex-direction: column; }
}

@media (max-width: 767.98px) {
    .page { width: calc(100% - 20px); padding-top: 68px; }
    .auth-panel, .auth-visual { padding: var(--space-6); }
    .form-grid { grid-template-columns: minmax(0, 1fr); }
    .panel-body, .auth-card { padding: var(--space-5); }
}

/* ---- Sitewide floating contact widget (partials/contact_widget.html,
   included from base.html/auth_base.html) — always available on every page,
   instead of "contact" being reachable only by navigating to its own page.
   Fixed position, above everything else on the page (nav dropdowns included). ---- */
.contact-fab { position: fixed; right: var(--space-5); bottom: var(--space-5); z-index: 1040; display: flex; flex-direction: column; align-items: flex-end; }
.contact-fab-menu { display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-2); margin-bottom: var(--space-3); }
.contact-fab-option {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4) var(--space-2) var(--space-2);
    background: var(--surface);
    color: var(--text);
    border-radius: var(--radius-full);
    box-shadow: var(--shadow-md);
    text-decoration: none;
    font-weight: var(--fw-semibold);
    white-space: nowrap;
    transition: transform var(--duration-base) var(--ease-standard), box-shadow var(--duration-base) var(--ease-standard);
}
.contact-fab-option:hover, .contact-fab-option:focus-visible {
    transform: translateX(-2px);
    box-shadow: var(--shadow-lg);
    color: var(--text);
    text-decoration: none;
}
.contact-fab-option .icon-badge { flex: none; }
.contact-fab-toggle {
    width: 56px;
    height: 56px;
    border: none;
    border-radius: var(--radius-full);
    background: var(--primary);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: var(--shadow-lg);
    cursor: pointer;
    transition: background var(--duration-base) var(--ease-standard), transform var(--duration-base) var(--ease-standard);
}
.contact-fab-toggle:hover, .contact-fab-toggle:focus-visible { background: var(--primary-dark); transform: scale(1.05); }
.contact-fab-toggle .icon { width: 24px; height: 24px; }

@media (max-width: 575.98px) {
    .contact-fab { right: var(--space-4); bottom: var(--space-4); }
    .contact-fab-option span:last-child { font-size: var(--text-sm); }
}

/* ---- Sitewide instant search widget (partials/search_widget.html, included
   from navbar.html and sidebar.html) — a live dropdown anchored to its own
   trigger button, replacing the old full-page /search/ view entirely.
   position: fixed, not absolute: .app-sidebar has overflow-y: auto, which
   clips an absolutely-positioned descendant the moment it extends past the
   sidebar's own (240px) width — fixed positioning escapes that clipping the
   same way .app-sidebar/.contact-fab already do. Since fixed isn't relative
   to .search-widget anymore, app.js computes top/left/right/bottom from the
   trigger's getBoundingClientRect() at open time instead of relying on CSS
   here for placement — .search-widget-up/-down below only pick which pair
   of edges JS is expected to set (sidebar's button opens the panel upward
   since it sits at the bottom of the sidebar; navbar's opens downward). ---- */
.search-widget { position: relative; }
/* .search-toggle is a raw <button> carrying .nav-link-icon/.sidebar-icon-btn
   for layout, but neither of those resets a button's default browser chrome
   (border/background) the way .theme-toggle does for itself — without this,
   the search icon shows a visible box around it that the other icon buttons
   next to it don't have. */
.search-toggle { background: transparent; border: 0; font-family: inherit; cursor: pointer; }
.search-panel {
    position: fixed;
    width: 340px;
    max-width: min(90vw, 340px);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
    z-index: 1060;
}
.search-panel-input-wrap {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3);
    border-bottom: 1px solid var(--line);
    color: var(--muted);
}
.search-panel-input-wrap input {
    flex: 1;
    border: none;
    outline: none;
    background: transparent;
    color: var(--text);
    font-size: var(--text-md);
}
.search-panel-results { max-height: 360px; overflow-y: auto; padding: var(--space-2); }
.search-result-group-label {
    padding: var(--space-2) var(--space-2) var(--space-1);
    font-size: var(--text-xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--muted);
}
.search-result-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2);
    border-radius: var(--radius-md);
    text-decoration: none;
    color: var(--text);
}
.search-result-item:hover, .search-result-item:focus-visible {
    background: var(--surface-soft);
    color: var(--text);
    text-decoration: none;
}
.search-result-item .icon-badge { flex: none; }
.search-result-item .text-muted { font-weight: var(--fw-regular); }
.search-panel-hint, .search-panel-empty {
    padding: var(--space-5) var(--space-4);
    text-align: center;
    color: var(--muted);
    font-size: var(--text-sm);
}
