/* ==========================================================================
   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); }

/* ---- Full-bleed breakout (guest home.html's hero + prevention sections,
   education/home.html's intro banner) — every page's content sits inside
   `.page`, a centered `min(1180px, calc(100% - 32px))` reading column, so a
   normal child is just as width-capped as a paragraph of text. These three
   photo sections are meant to read as edge-to-edge banners instead, so they
   escape the column with the standard "full-bleed" trick: `100vw` plus a
   negative inline margin re-centers the box on the viewport regardless of
   how deeply it's nested. `margin-inline` (not left/right) mirrors under
   `dir="rtl"` automatically, so this needs no rtl.css addition.
   The trick still resolves correctly through `.section-band`'s own padding
   and (on authenticated pages) the fixed sidebar's `margin-left` on
   `.app-content-area` — the sidebar just paints over the sliver of banner
   that would otherwise land underneath it (it has an opaque background and
   a higher z-index than these plain `position: relative` sections), and any
   pixels the math sends past the right edge are inert thanks to body's
   `overflow-x: hidden` (base.css) — no ancestor here clips with its own
   `overflow: hidden`. Only the photo + scrim should reach the edges, so each
   user of this class keeps its actual content (heading/cards/etc.) capped at
   `.page`'s own reading width — see `.prevention-photo-header` /
   `.prevention-photo .prevention-cards` / `.learning-intro-photo-content`
   below — and none of the three keep a border-radius, since rounded corners
   floating in from the browser edge would look wrong. ---- */
.full-bleed {
    width: 100vw;
    margin-inline: calc(50% - 50vw);
}

/* ---- Hero carousel (guest home.html) — a full-bleed photo carousel: the
   hero and "Support for every stage" messages live as rotating slides, each
   a photo with a dark scrim and centered white text/CTAs on top, plus arrow
   controls and dot indicators overlaid at the edges. Built on Bootstrap's
   existing carousel plugin (data-ride/data-slide, no new JS); only the look
   is custom. Colors stay within the existing token palette — the headline
   highlight and badge dot reuse --secondary (the brand's existing green),
   nothing new introduced. ---- */
.hero-carousel { position: relative; overflow: hidden; }
.hero-slide { position: relative; display: flex; align-items: center; justify-content: center; min-height: 460px; background: var(--surface-inverse); }
/* object-fit: cover — fills the slide edge-to-edge on every box shape, phone
   or desktop, with no letterboxing. (Previously "contain", which showed the
   whole photo uncropped but left the surrounding --surface-inverse fill
   visible as dark bands above/below or left/right whenever the photo's
   aspect ratio didn't match the box — removed per explicit request.) */
.hero-slide-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.hero-slide-scrim {
    position: absolute;
    inset: 0;
    /* Uniformly dark rather than a top-light/bottom-dark fade — the content
       block sits vertically centered, not anchored to the bottom, so every
       photo (including the brighter ones) needs enough tint behind the text
       wherever it lands, not just near the edges. */
    background: linear-gradient(180deg, rgba(5, 25, 51, .62) 0%, rgba(5, 25, 51, .68) 50%, rgba(5, 25, 51, .8) 100%);
}
.hero-slide-content {
    position: relative;
    z-index: 1;
    max-width: 640px;
    margin: 0 auto;
    padding: var(--space-8) var(--space-6);
    text-align: center;
    color: #fff;
    /* Belt-and-braces on top of the scrim — keeps text edges readable even
       over a photo's brightest patch (e.g. a window or a light backdrop). */
    text-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
.hero-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    margin-bottom: var(--space-4);
    border-radius: var(--radius-full);
    background: rgba(255, 255, 255, .12);
    border: 1px solid rgba(255, 255, 255, .3);
    color: #fff;
    font-size: var(--text-sm);
    font-weight: var(--fw-semibold);
}
.hero-badge-dot { width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--secondary-200); flex: none; }
/* base.css sets `color: var(--text)` directly on every h1-h6 element, which
   beats the white inherited from .hero-slide-content (a direct match always
   wins over inheritance) — so the heading needs its own explicit white. */
/* text-2xl (not text-3xl) on mobile — at the wider size the title alone ran
   4-5 lines and pushed the rest of the slide (subtitle, trust row, buttons)
   past a typical phone's fold; the min-width:768px rule below still steps
   it back up to the larger size once there's room. */
.hero-slide-title { color: #fff; font-size: var(--text-2xl); font-weight: var(--fw-black); line-height: var(--lh-tight); margin: 0 0 var(--space-4); }
.hero-slide-highlight { color: var(--secondary-200); }
.hero-slide-text { font-size: var(--text-base); color: rgba(255, 255, 255, .88); line-height: var(--lh-relaxed); margin: 0 0 var(--space-3); }
.hero-slide-content .trust-row { justify-content: center; border-top-color: rgba(255, 255, 255, .25); }
.hero-slide-content .trust-item { color: rgba(255, 255, 255, .85); }
.hero-slide-content .trust-item .icon { color: var(--secondary-200); }
.hero-slide-content .field-hint { color: rgba(255, 255, 255, .7); }
.hero-chip-row { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2); margin: var(--space-4) 0; }
.hero-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-full);
    background: rgba(255, 255, 255, .12);
    border: 1px solid rgba(255, 255, 255, .25);
    font-size: var(--text-xs);
    color: #fff;
}
.hero-slide-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-3); margin-top: var(--space-5); }
.hero-slide-content .btn-outline-primary { color: #fff; border-color: rgba(255, 255, 255, .7); }
.hero-slide-content .btn-outline-primary:hover { background: rgba(255, 255, 255, .15); border-color: #fff; color: #fff; }
@media (min-width: 768px) {
    .hero-slide { min-height: 560px; }
    .hero-slide-title { font-size: var(--text-4xl); }
}
.hero-carousel-control {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 5;
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: var(--radius-full);
    background: rgba(7, 39, 82, .4);
    color: #fff;
    cursor: pointer;
    transition: background var(--duration-base) var(--ease-standard);
}
.hero-carousel-control:hover { background: rgba(7, 39, 82, .65); }
.hero-carousel-control:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.hero-carousel-control-prev { inset-inline-start: var(--space-4); }
.hero-carousel-control-next { inset-inline-end: var(--space-4); }
.hero-carousel-indicators {
    position: absolute;
    bottom: var(--space-4);
    margin: 0;
    z-index: 5;
}
.hero-carousel-indicators li {
    width: 8px;
    height: 8px;
    border-radius: var(--radius-full);
    background: rgba(255, 255, 255, .4);
    opacity: 1;
}
.hero-carousel-indicators li.active { background: #fff; }
@media (max-width: 575.98px) {
    .hero-carousel-control { width: 36px; height: 36px; }
}

/* ---- Quick-benefit trio (guest home.html, directly under the hero) — a
   compact 3-up row of tiny cards that must stay 3 columns even on a narrow
   phone. Unlike .info-grid's auto-fit minmax(240px,1fr), which collapses to
   a single column well before a ~375px viewport, this is a fixed 3-column
   grid with the safety floor set to 0 (minmax(0,1fr)) so it never overflows.
   Spacing uses `gap` only (no margins), so it's correct in RTL for free. ---- */
.quick-benefit-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-2);
}
.quick-benefit-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-3) var(--space-2);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    text-align: center;
}
.quick-benefit-card .icon-badge { width: 36px; height: 36px; }
.quick-benefit-card .icon-badge .icon { width: 18px; height: 18px; }
.quick-benefit-card h3 { margin: 0; font-size: var(--text-sm); font-weight: var(--fw-bold); }
.quick-benefit-card p { margin: 0; color: var(--muted); font-size: var(--text-xs); line-height: var(--lh-snug); }
@media (min-width: 576px) {
    .quick-benefit-grid { gap: var(--space-4); }
    .quick-benefit-card { padding: var(--space-4) var(--space-3); }
}

/* ---- Prevention section photo (guest home.html) — the whole block (title
   AND the four habit cards) sits on one full-bleed photo instead of just a
   banner strip behind the title, reusing the exact scrim gradient + text-
   shadow recipe from the hero carousel slides above (.hero-slide-bg /
   .hero-slide-scrim / .hero-slide-content) for a consistent photo-overlay
   look across the page. Header and card row are spaced with `gap`, not
   margin, per this codebase's RTL-safety convention. ---- */
.prevention-photo {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--space-6);
    min-height: 420px;
    padding: var(--space-8) var(--space-6);
    overflow: hidden;
    background: var(--surface-inverse);
}
@media (min-width: 768px) {
    .prevention-photo { min-height: 560px; }
}
/* object-fit: cover, matching .hero-slide-bg above — fills the box edge-to-
   edge instead of letterboxing into the surrounding --surface-inverse fill. */
.prevention-photo-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.prevention-photo-scrim {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(5, 25, 51, .62) 0%, rgba(5, 25, 51, .68) 50%, rgba(5, 25, 51, .8) 100%);
}
/* .prevention-photo is full-bleed (viewport-wide); its content stays capped
   at .page's own 1180px reading width and centered, so the header/cards
   don't stretch uncomfortably wide on a large monitor. */
.prevention-photo-header {
    position: relative;
    z-index: 1;
    max-width: 1180px;
    margin-inline: auto;
    text-align: center;
    color: #fff;
    text-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
.prevention-photo-header .section-head-badge { background: rgba(255, 255, 255, .15); color: #fff; box-shadow: none; }
.prevention-photo-header h2 { color: #fff; }
/* Lifts the card row above the bg photo/scrim the same way .prevention-photo-header
   does — needed because .prevention-cards sits as its own sibling, not nested
   inside the header block. Also carries the same 1180px reading-width cap. */
.prevention-photo .prevention-cards { position: relative; z-index: 1; max-width: 1180px; margin-inline: auto; }

/* ---- Education Center intro photo (education/home.html) — same full-bleed
   photo-overlay recipe as the guest home page's hero/prevention sections
   (scrim gradient + text-shadow), for a consistent look wherever this photo
   treatment is used across the site. This section has an eyebrow + a full
   subtitle paragraph (the hero/prevention versions don't), so it additionally
   overrides .eyebrow/.page-subtitle colors for legibility on a dark photo. ---- */
.learning-intro-photo {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    min-height: 420px;
    padding: var(--space-10) var(--space-6);
    background: var(--surface-inverse);
}
@media (min-width: 768px) {
    .learning-intro-photo { min-height: 560px; }
}
/* object-fit: cover, matching .hero-slide-bg above — fills the box edge-to-
   edge instead of letterboxing into the surrounding --surface-inverse fill. */
.learning-intro-photo-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.learning-intro-photo-scrim {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(5, 25, 51, .62) 0%, rgba(5, 25, 51, .68) 50%, rgba(5, 25, 51, .8) 100%);
}
.learning-intro-photo-content {
    position: relative;
    z-index: 1;
    max-width: 640px;
    margin: 0 auto;
    text-align: center;
    color: #fff;
    text-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
.learning-intro-photo-content .section-head-badge { background: rgba(255, 255, 255, .15); color: #fff; box-shadow: none; }
.learning-intro-photo-content .eyebrow { color: var(--secondary-200); }
.learning-intro-photo-content h1, .learning-intro-photo-content h2 { color: #fff; }
.learning-intro-photo-content .page-subtitle { color: rgba(255, 255, 255, .88); margin-left: auto; margin-right: auto; }

/* ---- 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; }
/* flex-start here means "top-align" only in .media-row's desktop row
   layout — on mobile the row becomes a flex COLUMN (see .media-row's own
   max-width:767.98px rule), where align-items instead controls horizontal
   alignment, so this same flex-start value was pinning the photo to the
   left edge instead of centering it. Scoped to desktop so mobile falls
   back to .media-row's own default (align-items: center). */
@media (min-width: 768px) {
    #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; }
}

/* ---- 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 {
    display: block;
    color: inherit;
    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);
}
.metric:hover, .metric:focus-visible {
    border-color: rgba(21, 101, 192, .45);
    box-shadow: var(--shadow-md);
    color: inherit;
    text-decoration: none;
    transform: translateY(-2px);
}
.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 dropdown (education/home.html) — replaces a row of
   filter pills (which wrapped across several lines once there were 12
   topics) with a single search-styled trigger that opens a dropdown listing
   every topic. Built on Bootstrap's dropdown plugin already loaded site-wide
   (same data-toggle="dropdown" pattern as the navbar's language switcher —
   see .app-navbar .dropdown-menu above), so no new JS is needed for the
   open/close/outside-click behavior. ---- */
.topic-filter-dropdown { margin-bottom: var(--space-5); }
.topic-filter-trigger {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 220px;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--text);
    font-size: var(--text-sm);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: border-color var(--duration-base) var(--ease-standard);
}
.topic-filter-trigger .icon { color: var(--muted); flex: none; }
.topic-filter-trigger:hover, .topic-filter-trigger:focus { border-color: var(--primary); }
.topic-filter-menu {
    max-height: 340px;
    overflow-y: auto;
    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);
}
.topic-filter-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);
}
.topic-filter-item:hover, .topic-filter-item:focus {
    background: var(--surface-soft);
    color: var(--primary-dark);
}
.topic-filter-item.active { background: var(--primary); color: #fff; }
.topic-filter-item.active .icon { color: #fff; }

/* ---- Topic article list (education/home.html) — one line per article
   (title only, no topic badge/summary) instead of a full info-card grid, so
   scanning many articles doesn't take multiple screens of scrolling; the
   whole line is the click target through to the article. ---- */
.topic-article-list { display: flex; flex-direction: column; border-top: 1px solid var(--line); }
.topic-article-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-2);
    border-bottom: 1px solid var(--line);
    color: var(--text);
    font-weight: var(--fw-semibold);
    text-decoration: none;
}
.topic-article-number { flex: none; width: 24px; color: var(--muted); font-weight: var(--fw-bold); }
.topic-article-title { flex: 1; min-width: 0; }
.topic-article-item .icon { color: var(--muted); flex: none; }
.topic-article-item:hover, .topic-article-item:focus {
    background: var(--surface-soft);
    color: var(--primary-dark);
    text-decoration: none;
}
.topic-article-item:hover .icon, .topic-article-item:focus .icon { color: var(--primary); }

/* ---- Health Tips list (education/tips.html) — a compact numbered row per
   tip instead of a full .info-card with a large circular number badge, so a
   page of tips fits within one mobile screen instead of scrolling through
   what felt like one big card at a time. Tips have no separate detail page
   (the body text here is the whole tip), so each row stays a plain block,
   not a link — unlike .topic-article-item. ---- */
.tip-list { display: flex; flex-direction: column; border-top: 1px solid var(--line); }
.tip-list-item {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-2);
    border-bottom: 1px solid var(--line);
}
.tip-list-number { flex: none; width: 24px; padding-top: 1px; color: var(--muted); font-weight: var(--fw-bold); }
/* flex: 1 + min-width: 0 keeps the rendered body as ONE flex child that
   wraps normally, however many elements render_markdown produces inside it
   (a <p> here instead couldn't legally contain a heading/list — invalid
   block-in-inline nesting makes the browser auto-close it early, turning
   each inner element into its own bare flex sibling with no width
   constraint, which fractures into a row of narrow columns). */
.tip-list-body { flex: 1; min-width: 0; color: var(--text); line-height: var(--lh-snug); }
.tip-list-body > *:last-child { margin-bottom: 0; }

/* ---- 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); }

/* ---- Prevention card grid (guest home.html) — a static 2-across grid at
   every width (not a swipeable row) so all four cards are visible together
   in one view on a phone instead of requiring a horizontal swipe; widens to
   4-across from desktop up. Spacing is all `gap`, RTL-safe with no mirror
   needed. ---- */
.prevention-cards {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
}
@media (min-width: 992px) {
    .prevention-cards { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-4); }
}

/* ---- Prevention section translucent cards (guest home.html) — the four
   habit cards sit on the section's full-bleed photo (see .prevention-photo
   above), so they need a frosted-glass look instead of the default opaque
   .info-card surface. Scoped to this modifier class, added alongside
   .info-card only on these four cards, so every other .info-card usage
   across the site keeps its normal opaque styling untouched. ---- */
.info-card.prevention-card {
    background: rgba(255, 255, 255, .14);
    border-color: rgba(255, 255, 255, .3);
}
.info-card.prevention-card:hover {
    border-color: rgba(255, 255, 255, .5);
    box-shadow: none;
}
.info-card.prevention-card .icon-badge { background: rgba(255, 255, 255, .18); color: #fff; }
.info-card.prevention-card h3 { color: #fff; }
.info-card.prevention-card p { color: rgba(255, 255, 255, .85); }

/* ---- 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 ----
   Three cards placed directly on one grid (not nested in two flex-column
   wrapper divs) so a mobile-only `order` can move "Your data" to the end,
   after Profile & reminders, without touching the desktop layout — explicit
   grid-column/grid-row placement drives desktop, not template source order,
   so the template is free to put the cards in whatever order mobile needs.
   `grid-template-rows: auto 1fr` on the side column's two rows means the
   danger card's row absorbs any extra height, so its bottom edge still
   lands level with the preferences column, same as the old flex:1 trick. */
.account-grid { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); grid-template-rows: auto 1fr; gap: var(--space-5); }
.account-profile-card { grid-column: 1; grid-row: 1; min-width: 0; }
.account-danger-card { grid-column: 1; grid-row: 2; min-width: 0; }
.account-preferences-card { grid-column: 2; grid-row: 1 / -1; min-width: 0; }
.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 ----
   Single centered column (was a split-screen with a large photo/gradient
   hero panel down the left). That hero pushed the card below the fold on
   mobile — you had to scroll past a full-height dark panel just to reach
   the username field — and even on desktop it didn't help the taller
   signup card ever fit above the fold, so it was cut rather than shrunk:
   just the card and the language switcher stack in the page now, no
   separate visual panel, brand mark, or trust row costing extra height. */
.auth-page { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: var(--space-6); background: var(--bg); }
.auth-panel { display: flex; flex-direction: column; align-items: center; gap: var(--space-4); width: 100%; max-width: 420px; }
.auth-card {
    width: 100%;
    padding: var(--space-6);
    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-4); color: var(--muted); font-size: var(--text-base); }

/* Language switcher below the auth card — a guest arriving straight at
   /login/ or /signup/ (e.g. from an emailed link) previously had no way to
   change language until after signing in. Each option is its own tiny POST
   form to the same set_language view the navbar/sidebar switcher uses
   (partials/language_switcher.html) — not reused directly, since that
   partial's classes (.dropdown-item) target a Bootstrap dropdown menu, not a
   standalone row of pill buttons. Plain flex row: wraps and re-centers on
   its own under dir="rtl", no mirror needed. */
.auth-lang { display: flex; flex-wrap: wrap; gap: var(--space-2); justify-content: center; }
.auth-lang form { margin: 0; }
.auth-lang-btn {
    min-height: 44px;
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--muted);
    font-size: var(--text-sm);
    font-weight: var(--fw-semibold);
    cursor: pointer;
}
.auth-lang-btn:hover, .auth-lang-btn:focus-visible { border-color: var(--primary-200); color: var(--primary); }
.auth-lang-btn.active { background: var(--surface-soft); border-color: var(--primary-200); color: var(--primary); }

/* Login: "Keep me signed in" + "Forgot password?" on one compact row,
   replacing the old two-link row. Flexbox row with justify-content:
   space-between — reverses on its own under dir="rtl", no mirror needed. */
.auth-form-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin: var(--space-2) 0 var(--space-5);
}
.auth-form-row .form-check { margin: 0; }
.auth-form-row a { font-size: var(--text-sm); font-weight: var(--fw-semibold); white-space: nowrap; }

/* Signup: labeled <fieldset> sections ("Your details" / "Your sign-in
   details"). Legend styling carries no letter-spacing/text-transform (unlike
   .eyebrow), so Arabic shaping is untouched and no rtl.css override is needed. */
.form-section { margin: 0 0 var(--space-5); padding: 0; border: 0; min-inline-size: 0; }
.form-section:last-of-type { margin-bottom: var(--space-3); }
.form-section-title {
    display: block;
    width: 100%;
    margin: 0 0 var(--space-3);
    padding: 0 0 var(--space-2);
    border-bottom: 1px solid var(--line);
    color: var(--muted);
    font-size: var(--text-sm);
    font-weight: var(--fw-bold);
}
/* Slightly tighter than the sitewide .form-group (used in many longer forms
   where --space-4 reads better) — scoped to these fieldsets specifically
   because signup's own goal is fitting above the fold. */
.form-section .form-group { margin-bottom: var(--space-3); }

/* Password strength meter — aria-hidden, purely visual; the segment fill
   grows from the flex start edge, which is the correct reading-start side
   in both LTR and RTL, so no rtl.css mirror is needed. Advisory only:
   Django's server-side validators remain the actual source of truth. */
.pw-meter { display: flex; gap: var(--space-1); margin: var(--space-2) 0; }
.pw-meter-seg {
    flex: 1;
    height: 4px;
    border-radius: var(--radius-full);
    background: var(--gray-200);
    transition: background var(--duration-base) var(--ease-standard);
}
.pw-meter[data-score="1"] .pw-meter-seg:nth-child(-n+1) { background: var(--danger-600); }
.pw-meter[data-score="2"] .pw-meter-seg:nth-child(-n+2) { background: var(--warning-600); }
.pw-meter[data-score="3"] .pw-meter-seg:nth-child(-n+3) { background: var(--warning-600); }
.pw-meter[data-score="4"] .pw-meter-seg { background: var(--success-600); }

/* Password rules — live checklist: a bullet swaps for a checkmark via ::before
   as each client-checkable rule is satisfied (JS toggles .is-met only). Both
   glyphs are direction-agnostic symbols on a flex first-child, so they sit on
   the start edge automatically in both LTR and RTL — no rtl.css mirror needed. */
.pw-rules { margin-top: var(--space-2); }
.pw-rules li { display: flex; align-items: flex-start; gap: var(--space-2); }
.pw-rules li::before { content: "\2022"; flex: none; color: var(--gray-400); font-weight: var(--fw-bold); }
.pw-rules li.is-met { color: var(--success-700); }
.pw-rules li.is-met::before { content: "\2713"; color: var(--success-600); }

/* Inline positive confirmation ("Passwords match") — same direction-agnostic
   glyph-on-flex-item reasoning as .pw-rules above. */
.field-ok { margin: var(--space-1) 0 0; color: var(--success-700); font-size: var(--text-sm); }
.field-ok::before { content: "\2713\00a0"; }

/* Caps Lock hint — reuses .field-hint sizing/spacing, just a warning tone. */
.field-hint.is-warning { color: var(--warning-700); }

/* ---- 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; }
}

/* ---- Mobile-only accordion ----
   Same accordion.card/card-header/collapse markup and Bootstrap collapse JS
   as above, but for content that should stay a normal, always-open card on
   a wider screen and collapse into a tap-to-expand row only on a phone —
   one template for both instead of maintaining two layouts. Below 768px it
   behaves exactly like .accordion (collapsed by default, click to open);
   at 768px+ the trigger goes inert and the body is forced open regardless
   of its .show state. */
@media (min-width: 768px) {
    .mobile-collapse-trigger { cursor: default; pointer-events: none; }
    .mobile-collapse-trigger .accordion-chevron { display: none; }
    .mobile-collapse.collapse:not(.show) { display: block; }
}

/* ---- 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, then the page's content written and
   formatted in one place by the admin (accounts.widgets.RichTextWidget).
   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);
    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__rule { border: 0; border-top: 1px solid var(--line); margin: var(--space-6) 0 0; }

/* Body — written and formatted by the admin as one piece (see
   accounts/templates/accounts/_legal_page.html); an admin who wants a table
   of contents just types one at the top, as ordinary links. */
.legal-doc__body { margin-top: var(--space-6); }
.legal-doc h2 {
    margin: var(--space-8) 0 var(--space-4);
    color: var(--text);
    font-size: var(--text-xl);
    font-weight: var(--fw-bold);
    line-height: var(--lh-snug);
}
.legal-doc__body > h2:first-child { margin-top: 0; }
.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__body > *: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) — the
   toolbar's blockquote button. */
.legal-doc 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);
    font-size: var(--text-sm);
}
.legal-doc blockquote p:last-child { margin-bottom: 0; }

/* ---- 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) {
    .prediction-layout, .prediction-layout.has-result, .account-grid {
        grid-template-columns: minmax(0, 1fr);
    }
    .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; }
    /* Single column now, so `order` (not the desktop grid-row placement
       above) controls stacking: profile summary, then the settings form,
       with the "Your data" danger zone moved to the very end instead of
       sitting right under the profile card. */
    .account-grid { grid-template-rows: none; }
    .account-profile-card { grid-column: 1; grid-row: auto; order: 1; }
    .account-preferences-card { grid-column: 1; grid-row: auto; order: 2; }
    .account-danger-card { grid-column: 1; grid-row: auto; order: 3; }
}

@media (max-width: 767.98px) {
    .page { width: calc(100% - 20px); padding-top: 68px; }
    .auth-page { padding: var(--space-4); }
    .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);
}
