/* ===== GOOGLE FONTS (loaded in base.html) =====
   One face for the whole site: Inter (--font-sans) — the navbar's font, used by
   body text, headings, numbers, form controls, maps, charts and toasts alike.
   The only exception is the «Хайрхоҳ» logo wordmark (--font-brand, Cormorant
   Garamond). Nothing else sets a font-family; new CSS uses var(--font-sans). */

/* ===== THEME VARIABLES =====
   Light, soft palette (2026-10-09). The two brand colours — blue #2A3BA5 and
   orange #F2A23A — are for accents and primary buttons only; grounds, cards,
   badges and section fills are pale tints of them, borders and shadows are
   faint, status colours are muted (still ≥ 4.5:1 as text on their tints).
   Dark theme: neutral dark-grey surfaces (no black, no navy), lighter brand
   tints for fills, dark ink on them. Every colour on the site comes from here. */
:root, [data-theme="light"] {
    --brand-blue: #2A3BA5;
    --brand-orange: #F2A23A;
    --brand-name: #2A3BA5;
    --bg: #f8f9fc;
    --surface: #ffffff;
    --surface-2: #f1f3f9;
    --text: #1d2433;
    --text-2: #3c4555;
    --muted: #5a6373;          /* ≥ 4.8:1 on every light plate */
    --primary: #2A3BA5;
    --on-primary: #ffffff;     /* text on filled primary / teal / danger buttons, tabs, pills */
    --primary-hover: #23318c;
    --primary-2: #1c7168;
    --primary-2-hover: #175f57;
    --primary-glow: rgba(42,59,165,0.08);
    --accent: #8a5a12;         /* text on --accent-glow; the fill colour is --brand-orange */
    --accent-glow: #fdf2df;
    --danger: #b04a3c;
    --danger-glow: rgba(176,74,60,0.08);
    --danger-line: rgba(176,74,60,0.32);   /* soft border of alert cards/banners */
    --ok: #1c7168;
    --info: #2e6a99;
    --info-glow: rgba(46,106,153,0.09);
    --line: #e7eaf2;
    --shadow: 0 1px 2px rgba(29,36,51,0.04), 0 4px 14px rgba(29,36,51,0.04);
    --shadow-hover: 0 2px 6px rgba(29,36,51,0.05), 0 12px 28px rgba(42,59,165,0.08);
    --nav-shadow: 0 1px 2px rgba(29,36,51,0.03);
    --card-bg: #ffffff;
    --card-border: #e9ecf3;
    --nav-bg: rgba(255,255,255,0.92);
    --nav-bg-solid: rgba(255,255,255,0.97);
    --input-bg: #ffffff;
    --bubble-bg: #f0f2fa;
    /* Section grounds */
    --section-alt: #f3f5fb;   /* alternating sections */
    --cta-bg: #eef1fb;        /* CTA blocks + footer */
    --card-soft: #f0f3fc;     /* testimonial / soft plates */
    --stats-gradient: linear-gradient(135deg, #2A3BA5, #3b4dbb);
    --hero-from: #23318c;
    --hero-via: #2A3BA5;
    --hero-to: #3b4dbb;
    --hero-ink: rgba(255,255,255,0.94);
    --hero-ink-muted: rgba(255,255,255,0.78);
    --peak-1: #2A3BA5;
    --peak-2: #4757bd;
    --peak-3: #5f7fb8;
    --peak-4: #3f9488;
    --peak-5: #1f7a70;
    --hero-scrim: linear-gradient(155deg, rgba(35,49,140,0.86) 0%, rgba(42,59,165,0.72) 45%, rgba(59,77,187,0.42) 100%);
    --ok-glow: rgba(31,122,112,0.10);
    /* Category bands on opportunity cards: pale tint + a coloured icon. */
    --media-ink: #2A3BA5;
}

[data-theme="dark"] {
    --brand-name: #b9c1f5;
    --bg: #18191d;
    --surface: #212227;
    --surface-2: #2a2c32;
    --text: #eceef2;
    --text-2: #c9ccd4;
    --muted: #9da2ad;
    --primary: #93a0f0;
    /* The dark theme's fills are light (#93a0f0 / #5cbfae / #e39a8a): white
       text on them would be ~2.4:1. Dark ink reads ≥ 6:1 on all of them. */
    --on-primary: #15161a;
    --primary-hover: #a6b1f4;
    --primary-2: #5cbfae;
    --primary-2-hover: #72cabb;
    --primary-glow: rgba(147,160,240,0.13);
    --accent: #f4b968;
    --accent-glow: rgba(242,162,58,0.14);
    --danger: #e39a8a;
    --danger-glow: rgba(227,154,138,0.12);
    --danger-line: rgba(227,154,138,0.34);
    --ok: #5cbfae;
    --info: #86b6dc;
    --info-glow: rgba(134,182,220,0.13);
    --line: rgba(255,255,255,0.08);
    --shadow: 0 1px 2px rgba(0,0,0,0.20), 0 6px 18px rgba(0,0,0,0.18);
    --shadow-hover: 0 2px 6px rgba(0,0,0,0.24), 0 14px 32px rgba(0,0,0,0.26);
    --nav-shadow: 0 1px 2px rgba(0,0,0,0.22);
    --card-bg: #212227;
    --card-border: rgba(255,255,255,0.07);
    --nav-bg: rgba(24,25,29,0.92);
    --nav-bg-solid: rgba(24,25,29,0.97);
    --input-bg: #2a2c32;
    --bubble-bg: #2c2e35;
    --section-alt: #1d1e23;
    --cta-bg: #222328;
    --card-soft: #26282e;
    --stats-gradient: linear-gradient(135deg, #343a5c, #3d4570);
    --hero-from: #1b1c21;
    --hero-via: #262a3d;
    --hero-to: #30365a;
    --hero-ink: rgba(255,255,255,0.94);
    --hero-ink-muted: rgba(255,255,255,0.72);
    --peak-1: #93a0f0;
    --peak-2: #8796e6;
    --peak-3: #7aaed4;
    --peak-4: #5cbfae;
    --peak-5: #72cabb;
    --hero-scrim: linear-gradient(155deg, rgba(24,25,29,0.90) 0%, rgba(38,42,61,0.82) 45%, rgba(48,54,90,0.50) 100%);
    --ok-glow: rgba(92,191,174,0.13);
    --media-ink: #b9c1f5;
}

/* ===== LAYOUT TOKENS =====
   A small spacing scale and a named set of content widths. Nothing in the
   codebase is required to use these, but new/touched CSS should pull from
   here instead of inventing one-off values. */
:root {
    --space-1: 0.25rem;  /* 4px */
    --space-2: 0.5rem;   /* 8px */
    --space-3: 0.75rem;  /* 12px */
    --space-4: 1rem;     /* 16px */
    --space-5: 1.5rem;   /* 24px */
    --space-6: 2rem;     /* 32px */
    --space-7: 3rem;     /* 48px */
    --space-8: 4rem;     /* 64px */
    --space-9: 5rem;     /* 80px */

    /* Typefaces — see the note at the top of this file. */
    --font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    --font-brand: 'Cormorant Garamond', 'Cormorant Fallback', 'PT Serif', serif;

    /* Height of the sticky topbar row — used as the `top` offset for anything
       else that sticks (the dashboard side rail). Keep in sync with .topbar. */
    --nav-h: 4.25rem;

    /* Content widths: lg = default page content, md = long-form reading,
       sm = multi-field forms, xs = auth cards. Nothing on the site is wider
       than 1200px, so xl (the CRM dashboards) is capped at lg too. */
    --container-xl: 1200px;
    --container-lg: 1200px;
    --container-md: 860px;
    --container-sm: 680px;
    --container-xs: 480px;

    /* Named spacing scale (2026-10-09) — the rhythm the layout rules below use:
       xs label→field · sm list rows / inside cards · md between fields, card
       heading→content, phone gutter · lg between blocks, card padding, desktop
       gutter · xl section top/bottom. Same values as --space-2…--space-5 + 40px. */
    --space-xs: 8px;
    --space-sm: 12px;
    --space-md: 16px;
    --space-lg: 24px;
    --space-xl: 40px;
    /* Side gutter of every page band (.container, .section): md on phones, lg from 768px. */
    --gutter: var(--space-md);
}
@media (min-width: 768px) {
    :root { --gutter: var(--space-lg); }
}

/* ===== LAYOUT / CONTAINERS =====
   Reusable width primitives. On very large screens content stops growing
   at its max-width instead of stretching edge to edge. */
.container, .container-lg, .container-xl, .container-md, .container-sm, .container-xs {
    width: 100%;
    max-width: var(--container-lg);
    margin-inline: auto;
    padding-inline: var(--gutter);
    box-sizing: border-box;
}
.container-xl { max-width: var(--container-xl); }
.container-md { max-width: var(--container-md); }
.container-sm { max-width: var(--container-sm); }
.container-xs { max-width: var(--container-xs); }
/* Full-bleed bars (topbar, footer, the video hero): the background spans the
   screen and has no side padding of its own — the .container inside caps the
   content at 1200px and its padding is the gutter, the same as every .section,
   so the logo, page content and footer line up on one edge. */

/* ===== RESET & BASE ===== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* ===== SPACING FLOW =====
   The reset above zeroes every margin, so blocks stacked in a plain (block)
   parent touched. Flex/grid parents space their children with `gap`; for the
   block parents that hold page content the rules below add the space. Both use
   :where() — zero specificity — so a component's own margin always wins and
   nothing is doubled; the last child never gets one. */
:where(.section) > :where(:not(:last-child)) { margin-bottom: var(--space-lg); }
:where(.card:not(.stat-card)) > :where(:not(:last-child)) { margin-bottom: var(--space-sm); }
html { scroll-behavior: smooth; }
body {
    font-family: var(--font-sans);
    background: var(--bg);
    color: var(--text);
    line-height: 1.6;
    transition: background 0.4s ease, color 0.3s ease;
    /* Long unbroken strings — a 60-char name, a 100-char email, a URL, a
       200-char "word" in a description or chat message — wrap inside their box
       instead of widening the page (horizontal scroll). Inherited everywhere;
       single-line places (navbar, list rows, table cells with .truncate) opt
       out with white-space: nowrap + an ellipsis + a title attribute. */
    overflow-wrap: anywhere;
}
/* Flex / grid children may shrink below their content (the default
   min-width: auto pins them to their longest word). Media keep their size. */
:where(.card, .grid, .two-col, .button-row, .page-header, .section-head, .profile-head, .row-rank, .mini-list-item, .opportunity-body) > :where(:not(img, svg, picture, video, canvas, .avatar)) { min-width: 0; }
/* Table cells: `anywhere` would shrink every column to one letter in the
   auto table layout ("Душанб|е", "09 Oct, 16:1|6"). break-word still breaks
   an over-long string, but only once the column can't grow any more. */
td, th { overflow-wrap: break-word; }
/* One-line text with an ellipsis; pair it with title="full text". */
.truncate { display: block; min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
a { color: inherit; text-decoration: none; transition: color 0.25s ease; }
/* Form controls don't inherit the font by default (UA styles give them the
   system face) — make every one use the site's. */
input, textarea, select, button, optgroup { font-family: inherit; }
/* MapLibre sets its own Helvetica on the map (controls, popups, attribution);
   two classes outrank its `.maplibregl-map` rule, whichever file loads last. */
.gc-map.maplibregl-map, .maplibregl-popup-content { font-family: var(--font-sans); }
main { min-height: 72vh; }
img { max-width: 100%; display: block; }
/* <picture> only chooses a source (WebP/srcset) — it must not add a box of
   its own, so the <img> lays out exactly as if it were a direct child. */
picture { display: contents; }
/* …and its <source> must not become an empty flex/grid item either: under
   display: contents it would take a `gap` (avatars in .row-rank / .profile-head
   shifted 16 px when milestone 6 wrapped them in <picture>). */
picture > source { display: none; }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; }
}

.skip-link {
    position: absolute;
    top: -3rem;
    left: 1rem;
    z-index: 1100; /* above the topbar when focused */
    padding: 0.7rem 1.1rem;
    border-radius: 8px;
    background: var(--primary);
    color: var(--on-primary);
    font-weight: 700;
    transition: top 0.2s ease;
}
.skip-link:focus { top: 1rem; }

:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* ===== TYPOGRAPHY =====
   Headings and big numbers use the same face as everything else (Inter);
   weight and size carry the hierarchy. */
h1, h2, h3, .display, .metric {
    letter-spacing: -0.015em;
    color: var(--text);
}
h1 { margin: 0.4rem 0 1rem; font-size: clamp(2.1rem, 5.6vw, 4.1rem); line-height: 1.06; font-weight: 700; }
h2 { margin: 0 0 0.9rem; font-size: clamp(1.6rem, 3.4vw, 2.35rem); font-weight: 700; }
h3 { margin: 0 0 0.6rem; font-size: 1.14rem; font-weight: 600; }
p { color: var(--muted); line-height: 1.65; transition: color 0.3s ease; }
.lede { font-size: 1.08rem; color: var(--text-2); max-width: 42rem; }

/* Accent word inside a heading — the product/keyword: primary colour + a thin
   teal underline. Kept as a sibling span so [data-i18n] textContent swaps still
   work on the surrounding text nodes. */
.h-accent {
    color: var(--primary);
    text-decoration: underline;
    text-decoration-color: var(--primary-2);
    text-decoration-thickness: 2px;
    text-underline-offset: 0.16em;
}

/* Eyebrow label above a section heading — uppercase, widely tracked, ~12px,
   primary blue, semibold. */
.eyebrow {
    display: inline-block;
    color: var(--primary);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-size: 0.72rem;
    margin-bottom: 0.35rem;
}

/* ===== TOPBAR =====
   Three zones on one line: brand (fixed) · nav links (flexible) · toolbar (fixed).
   The link row never wraps and never squeezes its items below their natural
   size — when there isn't room it scrolls horizontally inside its own zone
   (scrollbar hidden, an edge fades in to signal there's more, swipe / trackpad
   / wheel all work). This keeps the bar a single clean row at every width from
   the widest desktop down to the mobile-menu breakpoint.
   The bar's background spans the screen; its content sits in a .container
   (1200px). Height = --nav-h, every item centred vertically; a hairline
   border plus a soft shadow separate it from the page. */
.topbar {
    position: sticky;
    top: 0;
    z-index: 1000; /* above every in-page layer (maps are isolated at z-index 0, static/css/map.css) */
    padding: 0;
    background: var(--nav-bg);
    border-bottom: 1px solid var(--line);
    box-shadow: var(--nav-shadow);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    transition: background 0.3s ease, border-color 0.3s ease;
}
.topbar > .container {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    min-height: var(--nav-h);
}
.brand {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-weight: 700;
    font-size: 1.08rem;
    color: var(--primary);
}
/* Logo (static/img/logo-mark*.svg) + brand name, navbar and footer. The name
   is localized by i18n.js (brand.name); Cormorant Garamond covers Cyrillic
   incl. the Tajik "ҳ", so all three languages render in the same face.
   Until the web font arrives (display=swap) the name is drawn in a local
   Times scaled to Cormorant's width, so nothing jumps or overflows on swap. */
@font-face {
    font-family: 'Cormorant Fallback';
    src: local('Times New Roman Bold Italic'), local('TimesNewRomanPS-BoldItalicMT'),
         local('Liberation Serif Bold Italic'), local('LiberationSerif-BoldItalic'),
         local('Noto Serif Bold Italic'), local('NotoSerif-BoldItalic');
    font-style: italic;
    font-weight: 700;
    size-adjust: 88%;
}
.navbar__brand { display: flex; align-items: center; gap: 10px; text-decoration: none; min-width: 0; }
/* In the bar the brand gives way last. With the link row visible (> 960px) it
   never shrinks — the links scroll instead. In the phone bar (links in the
   burger) it may: on the narrowest phone, with an unusually wide fallback
   font, the name ellipsizes instead of pushing the bar into horizontal scroll. */
.topbar .navbar__brand { flex: 0 0 auto; }
@media (max-width: 960px) {
    .topbar .navbar__brand { flex: 0 1 auto; }
}
.brand-logo { flex: 0 0 auto; width: 36px; height: 36px; transition: transform 0.3s ease; }
.brand:hover .brand-logo { transform: rotate(-6deg); }
.brand-logo--dark { display: none; }
[data-theme="dark"] .brand-logo--light { display: none; }
[data-theme="dark"] .brand-logo--dark { display: block; }
.navbar__brand-name {
    font-family: var(--font-brand);
    font-weight: 700;
    font-style: italic;
    font-size: 28px;
    line-height: 1;
    color: var(--brand-name);
    white-space: nowrap;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    padding-right: 0.06em;   /* room for the italic overhang that overflow would clip */
}
@media (max-width: 767px) {
    .navbar__brand-name { font-size: 22px; }
    .brand-logo { width: 30px; height: 30px; }
}
/* 320–359px phones: the toolbar (language, theme, bell, menu) leaves ~40px,
   so the wordmark would ellipsize to "Хайр…". Show the logo mark alone; the
   name stays for screen readers (visually hidden, still the link's text). */
@media (max-width: 359px) {
    .topbar .navbar__brand-name {
        position: absolute; width: 1px; height: 1px; overflow: hidden;
        clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
    }
}
.brand-mark {
    display: grid;
    place-items: center;
    width: 2.3rem;
    height: 2.3rem;
    border-radius: 10px;
    color: white;
    font-weight: 700;
    font-size: 0.9rem;
    background: linear-gradient(135deg, var(--primary), var(--primary-2));
    transition: transform 0.3s ease;
}
.brand:hover .brand-mark { transform: rotate(-6deg); }

.nav-links {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: var(--space-5);
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-behavior: smooth;
    overscroll-behavior-x: contain;
    scrollbar-width: none;               /* Firefox */
    -webkit-overflow-scrolling: touch;
}
.nav-links::-webkit-scrollbar { display: none; }  /* WebKit / Blink */
/* Items keep their natural width and scroll past the edge — they never shrink. */
.nav-links > *,
.nav-links > .nav-logout-form > * { flex: 0 0 auto; }
/* Edge fade, only on the side that still has links hidden past it
   (.is-overflowing / .at-start / .at-end are maintained by base.html JS). */
.nav-links.is-overflowing {
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--nav-fade-l, 0px), #000 calc(100% - var(--nav-fade-r, 0px)), transparent 100%);
            mask-image: linear-gradient(to right, transparent 0, #000 var(--nav-fade-l, 0px), #000 calc(100% - var(--nav-fade-r, 0px)), transparent 100%);
}
.nav-links.is-overflowing:not(.at-start) { --nav-fade-l: 2.5rem; }
.nav-links.is-overflowing:not(.at-end)   { --nav-fade-r: 2.5rem; }

.nav-links a {
    position: relative;
    color: var(--text-2);
    font-weight: 600;
    font-size: 0.9rem;
    white-space: nowrap;
    padding: var(--space-2) 0;
    transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
/* Menu links are plain text — no fill, no border. Hover changes the colour;
   the current page (aria-current, set in base.html from the resolved url
   name) gets the primary colour plus a thin underline. A blue fill is kept
   for the one call-to-action button (.nav-cta), so the two never look alike. */
.nav-links a:not(.button)::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: 0.15rem;
    height: 2px;
    border-radius: 2px;
    background: currentColor;
    transform: scaleX(0);
    transition: transform 0.2s ease;
}
.nav-links a:not(.button):hover { color: var(--primary); }
.nav-links a:not(.button)[aria-current="page"] { color: var(--primary); }
.nav-links a:not(.button)[aria-current="page"]::after { transform: scaleX(1); }
/* Guest sign-in buttons: the site's own .button ghost / primary (as "View all"),
   at the link row's font size. "Become a volunteer" is the only filled button. */
.nav-links a.button { padding: 0.6rem 1.35rem; min-height: 2.5rem; font-size: 0.9rem; font-weight: 700; }
.nav-links a.button.ghost { color: var(--primary); border-color: var(--line); }
.nav-links a.button.ghost:hover,
.nav-links a.button.ghost[aria-current="page"] { color: var(--primary); background: var(--surface-2); border-color: var(--primary); }
.nav-links a.nav-cta { color: var(--on-primary); background: var(--primary); }
.nav-links a.nav-cta:hover { color: var(--on-primary); background: var(--primary-hover); }
.nav-links a.nav-cta[aria-current="page"] { color: var(--on-primary); background: var(--primary-hover); }
/* Guest bar: three groups — brand · menu links · sign-in buttons (+ the
   language/theme toolbar after them). The groups exist only in the guest
   markup; outside the guest desktop bar they add no box of their own. */
.nav-group { display: contents; }
@media (min-width: 961px) {
    /* brand · links · buttons + toolbar: the links take equal auto margins, so
       the space brand→links equals links→buttons at every width and language
       (an exact page-centre doesn't fit — the Tajik buttons are too wide).
       Signed-in users keep the plain row (logo left, links, toolbar right). */
    .topbar--guest .nav-links { display: contents; }
    .topbar--guest .nav-group { display: flex; align-items: center; }
    .topbar--guest .nav-group--links { gap: var(--space-6); margin-inline: auto; }
    .topbar--guest .nav-group--auth { gap: var(--space-3); }
}
/* Logout sits among text links — plain text like them, red on hover. */
.nav-links .nav-logout-form { display: flex; align-items: center; }
.nav-links .nav-logout {
    min-height: unset;
    white-space: nowrap;
    padding: var(--space-2) 0;
    border: none;
    border-radius: 0;
    background: transparent;
    color: var(--muted);
    font-weight: 600;
    font-size: 0.9rem;
    transition: color 0.2s ease;
}
.nav-links .nav-logout:hover { color: var(--danger); background: transparent; transform: none; box-shadow: none; }
/* Burger (shown below 960px): a 2.5rem rounded square matching the toolbar controls. */
.menu-button {
    display: none; flex: 0 0 auto;
    width: 2.5rem; min-height: 0; aspect-ratio: 1; padding: 0;
    border: 1px solid var(--line); border-radius: 10px; font-size: 1.1rem;
    transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
.menu-button:hover, .menu-button[aria-expanded="true"] { color: var(--primary); border-color: var(--primary); }

/* ===== TOOLBAR (lang · settings icon · profile) ===== */
.toolbar { flex: 0 0 auto; display: flex; align-items: center; gap: var(--space-2); }
/* Theme toggle: a fixed circle with an SVG icon centred by flexbox (no emoji —
   emoji glyphs sit off-centre differently per OS font, and Safari ignores
   display:grid on <button>). The icon for the current theme is chosen in CSS
   from [data-theme], so switching swaps nothing in the layout. */
.theme-toggle {
    flex: 0 0 auto;
    box-sizing: border-box;
    width: 2.3rem;
    height: 2.3rem;
    aspect-ratio: 1;
    min-width: 0;
    min-height: 0;   /* the global button rule's 2.7rem min-height made it an oval */
    padding: 0;
    gap: 0;
    border-radius: 50%;
    background: var(--surface-2);
    border: 1px solid var(--line);
    color: var(--text);
    line-height: 0;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background-color 0.2s ease, border-color 0.2s ease;
}
.theme-toggle__icon { display: block; width: 1.15rem; height: 1.15rem; flex: none; }
.theme-toggle__icon--sun { display: none; }
[data-theme="dark"] .theme-toggle__icon--moon { display: none; }
[data-theme="dark"] .theme-toggle__icon--sun { display: block; }
.theme-toggle:hover { border-color: var(--primary); }
/* Round profile chip in the corner (authenticated) — links to the dashboard. */
.nav-avatar {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 2.3rem;
    height: 2.3rem;
    border-radius: 50%;
    overflow: hidden;
    background: var(--primary);
    color: var(--on-primary);
    font-weight: 700;
    font-size: 0.9rem;
    transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.nav-avatar img { width: 100%; height: 100%; object-fit: cover; }
.nav-avatar:hover { transform: translateY(-1px); box-shadow: 0 6px 18px var(--primary-glow); }
.lang-group {
    display: flex;
    gap: 2px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 2px;
}
.lang-btn {
    padding: 0.3rem 0.5rem;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--muted);
    font-weight: 700;
    font-size: 0.75rem;
    cursor: pointer;
    transition: all 0.2s ease;
    min-height: 24px; /* WCAG 2.5.8 target size, also on the narrowest phones */
    min-width: 24px;
}
.lang-btn:hover { color: var(--primary); }
.lang-btn.active { background: var(--primary); color: var(--on-primary); }

/* ===== BUTTONS =====
   Fully rounded (pill), generous padding, flat fills — no gradient shimmer.
   primary = blue, secondary = teal (support / "help" actions). */
.button, button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    max-width: 100%;         /* a long label wraps (centred) instead of widening a 320px page */
    text-align: center;
    gap: 0.5rem;
    min-height: 2.7rem;
    padding: 0.8rem 1.65rem;
    border: 1px solid transparent;
    border-radius: 999px;
    font: inherit;
    font-weight: 700;
    font-size: 0.9rem;
    cursor: pointer;
    transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
/* Class selectors already outrank the bare `a { color: inherit }` tag rule,
   so these don't need !important — kept free of it on purpose. */
.primary { color: var(--on-primary); background: var(--primary); }
.primary:hover { transform: translateY(-1px); background: var(--primary-hover); box-shadow: 0 6px 18px var(--primary-glow); }
.secondary { color: var(--on-primary); background: var(--primary-2); }
.secondary:hover { transform: translateY(-1px); background: var(--primary-2-hover); box-shadow: 0 6px 18px var(--ok-glow); }
.ghost { color: var(--primary); background: transparent; border-color: var(--line); }
.ghost:hover { border-color: var(--primary); background: var(--surface-2); }
.danger { color: var(--on-primary); background: var(--danger); }
.danger:hover { transform: translateY(-1px); box-shadow: 0 6px 18px var(--danger-glow); }
.button:disabled, button:disabled { opacity: 0.55; cursor: not-allowed; transform: none !important; box-shadow: none !important; }
.button-row { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-4); }
.section-head .button-row { margin-top: 0; }
form.inline-form { display: inline-block; }

/* Five-peaks signature mark — used as a small brand accent (footer, regions panel) */
.peaks-mark { display: block; width: 100%; height: auto; }
.peaks-mark .peak { transition: transform 0.4s ease; transform-origin: bottom center; }

/* ===== SECTIONS & GRID =====
   .section is the default page-content container (--container-lg, 1200px).
   Add .section-md/.section-sm alongside it where a page needs a narrower
   reading/form measure instead — see AI Assistant and the auth/form pages. */
.section {
    padding: var(--space-xl) var(--gutter);
    max-width: var(--container-lg);
    margin: 0 auto;
}
.section.alt {
    max-width: none;
    background: var(--section-alt);
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}
/* A full-bleed .alt band keeps its children on the same content edge as a
   plain .section (max-width minus both gutters). */
.section.alt > * { max-width: calc(var(--container-lg) - 2 * var(--gutter)); margin-left: auto; margin-right: auto; }
.section.section-md { max-width: var(--container-md); }
.section.section-xl { max-width: var(--container-xl); }
.section.alt.section-xl > * { max-width: calc(var(--container-xl) - 2 * var(--gutter)); }
.section.alt.section-md > * { max-width: calc(var(--container-md) - 2 * var(--gutter)); }
/* An .alt band's background is always full-bleed — only its content is capped
   (the rules above). Without this, .section-xl/.section-md (same specificity,
   declared later) capped the band itself and left white strips at the sides. */
.section.alt.section-xl,
.section.alt.section-md { max-width: none; }
.section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; margin-bottom: var(--space-5); }
.section-head h2 { margin: 0; }

/* ===== PAGE HEADER =====
   The standard top-of-page block: title + optional lede on the left, an
   optional primary action on the right. Replaces the hand-rolled
   `.section-head > div > h1 + p.lede` pattern repeated across CRM/list pages.
   The inner-page h1 is a notch smaller than the hero h1 (which scales to 4.1rem). */
.page-header {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
    margin-bottom: var(--space-6);
}
.page-header__text { min-width: 0; }
.page-header h1 { margin: 0 0 0.3rem; font-size: clamp(1.7rem, 3.2vw, 2.3rem); line-height: 1.15; }
.page-header .lede { margin: 0; }
.page-header__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); flex-shrink: 0; }
.grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: var(--space-4);
}
.grid.grid-narrow { grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }
/* Wider columns for grids of *content* panels (a card with a list or a feed
   inside), which read badly when squeezed to card width — e.g. the admin
   panel's activity/emergency/applications row. The min() keeps the column from
   ever exceeding the container, so it can't force horizontal scroll on a phone.
   Higher specificity than the bare `.grid` tablet override below, so it isn't
   clawed back to a narrow column at 768px. */
.grid.grid-wide { grid-template-columns: repeat(auto-fill, minmax(min(100%, 28rem), 1fr)); }
/* Panels that size to their own content instead of stretching to the tallest sibling. */
.grid.grid-top { align-items: start; }
.two-col {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(300px, 0.85fr);
    gap: var(--space-6);
    align-items: start;
}
/* Both columns are grid items whose default `min-width: auto` lets a long
   unbreakable token (an email, a URL) push the track — and the page — wider
   than the viewport once the grid has collapsed to one column on a phone.
   Same guard the `.grid` / `.metric-row` / `.kpi-row` rows already carry. */
.two-col > * { min-width: 0; }
/* Vertical rhythm for a column of cards, replacing one-off margin-bottoms. */
.stack { display: flex; flex-direction: column; gap: var(--space-5); }

/* ===== CARDS =====
   Flat, quiet, one clean shadow that deepens on hover. No blur, no shimmer. */
.card {
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: 16px;
    padding: var(--space-5);
    box-shadow: var(--shadow);
    transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease;
}
.card:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); }
.card h3 { margin-top: 0.15rem; }
@media (max-width: 767px) {
    .card { padding: var(--space-md); }
}
/* Card heading → content. */
.card > h2:first-child, .card > h3:first-child, .card > .section-head { margin-bottom: var(--space-md); }

/* Soft plate — a pale-blue card on a white/CTA ground (icon + heading + text),
   the reference system's testimonial / action-card shape. */
.card--soft { background: var(--card-soft); border-color: transparent; box-shadow: none; }

/* Icon tile — a pastel rounded square holding an icon at the top of a card. */
.icon-tile {
    display: grid;
    place-items: center;
    width: 2.9rem;
    height: 2.9rem;
    border-radius: 12px;
    background: var(--primary-glow);
    color: var(--primary);
    margin-bottom: var(--space-3);
}
.icon-tile svg { width: 1.4rem; height: 1.4rem; }
.icon-tile--teal { background: var(--ok-glow); color: var(--primary-2); }
.icon-tile--amber { background: var(--accent-glow); color: var(--accent); }

/* Equal-height cards in a shared group.
   A CSS grid already stretches every card in a row to the tallest one
   (align-items: stretch by default); making the card itself a column lets a
   trailing action row / "view full" link sit flush at the bottom so the whole
   row reads as one consistent shape. Content panels whose length genuinely
   differs opt out with `.grid.grid-top` (align-items: start) — see below. */
.grid > .card,
.grid > .opportunity-card,
.grid > .pet-card { display: flex; flex-direction: column; }
.grid > .card > .button-row:last-child,
.grid > .card > .card__foot,
.card__foot { margin-top: auto; }
/* …but a status pill at the top of such a card keeps its own width. */
.grid > .card > .tag,
.grid > .pet-card > .tag { align-self: flex-start; }

/* Clamp overflowing copy to a fixed number of lines instead of letting the
   card grow; pair with a `.card__more` link to the full view. */
.clamp-text {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: var(--clamp, 3);
    line-clamp: var(--clamp, 3);
    overflow: hidden;
}
.card__more {
    display: inline-block;
    margin-top: var(--space-2);
    font-weight: 600;
    font-size: 0.85rem;
    color: var(--primary);
}
.card__more:hover { text-decoration: underline; }

/* Lost & Found board card — a plain .card link with a tag row on top. */
.pet-card { color: inherit; gap: var(--space-2); }
.pet-card__tags { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.pet-card h3 { margin: 0; }

.stat-card { text-align: left; }
.metric { display: block; font-size: 2.1rem; font-weight: 700; color: var(--primary); line-height: 1.15; }
.metric-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--space-4); }
.metric-row > * { min-width: 0; }
.metric-row .stat-card .metric { color: var(--text); }
/* A small circular icon badge above a stat-card's number — used when the
   card is a link to a filtered/list page, so it reads as clickable without
   relying on colour or an arrow glyph. */
.stat-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--primary-glow);
    color: var(--primary);
    margin-bottom: var(--space-3);
}
.stat-icon svg { width: 20px; height: 20px; }

/* Compact divider list for dashboard "recent activity" panels — no card
   background of its own since it lives inside a .card already. */
.mini-list { display: grid; }
.mini-list > * { min-width: 0; }  /* grid items: don't let a long name widen the card */
.mini-list-item h4, .mini-list-item p { overflow-wrap: anywhere; }
.mini-list-item { padding: var(--space-sm) 0; border-bottom: 1px solid var(--line); }
.mini-list-item:last-child { border-bottom: none; }
.mini-list-item h4 { margin: 0 0 0.2rem; font-size: 0.92rem; display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.mini-list-item p { margin: 0; font-size: 0.8rem; color: var(--muted); }
.meta-label { display: block; margin-bottom: 0.25rem; color: var(--muted); font-weight: 700; font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.04em; }
.meta-label + p { margin: 0; color: var(--text); font-weight: 600; overflow-wrap: break-word; }
/* A grid of label/value pairs — same rhythm as .metric-row, named for its use.
   min-width: 0 lets a long unbroken value (an email, a username) wrap inside
   its own column instead of overflowing into the next one — grid items
   default to a content-based minimum width, which ignores the track size. */
.detail-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--space-4); margin-bottom: var(--space-5); }
.detail-meta > div { min-width: 0; }

/* A single centered content card for a status/confirmation message or a
   focused detail view — narrower than the page container on purpose. The
   default h1 scale (up to 4.1rem) is sized for full-width page headers and
   would overwhelm a 680px card, so it's toned down here. */
.detail-card { max-width: var(--container-sm); margin-inline: auto; }
.detail-card h1 { font-size: clamp(1.5rem, 3vw, 1.9rem); margin: 0.4rem 0 0.6rem; }

.tag {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.28rem 0.62rem;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--primary-2);
    font-weight: 700;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.02em;
}
/* Status-tag semantics — used consistently via myapp/_task_status_tag.html:
     (plain) .tag  → pending / free / neutral        (valley-teal)
     .tag.info     → active / in progress            (highland-blue)
     .tag.success  → completed / approved / available (valley-teal, filled tint)
     .tag.urgent   → overdue / cancelled / rejected / urgent (clay-red)
     .tag.muted    → offline / busy / archived / past (grey)
     .tag.accent   → highlight only (rank #1, "new") — never a status */
.tag.info { background: var(--info-glow); color: var(--info); }
.tag.urgent { background: var(--danger-glow); color: var(--danger); }
.tag.accent { background: var(--accent-glow); color: var(--accent); }
.tag.success { background: var(--ok-glow); color: var(--ok); }
.tag.muted { background: var(--surface-2); color: var(--muted); }
.tag.rank-1 { background: var(--accent-glow); color: var(--accent); }
.tag.rank-2, .tag.rank-3 { background: var(--surface-2); color: var(--text-2); }

/* ===== FORMS ===== */
.form-shell { max-width: var(--container-sm); margin: 3rem auto; padding: 0 1rem; }
.form-shell .card { padding: clamp(1.5rem, 4vw, 2.25rem); }
.form-shell h1, .form-shell h3 { font-size: clamp(1.6rem, 3.4vw, 2rem); }
.form-shell .button-row { justify-content: center; }

/* ===== FORM FIELDS (shared design system) =====
   Field wrappers, hints and errors rendered by templates/partials/_field.html
   and _form.html for every form in the app. Auth-page chrome (.auth-card,
   .auth-submit, .password-toggle, the role picker) lives in auth.css. */
.field-group { margin-bottom: var(--space-md); }
.field-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; margin-bottom: var(--space-xs); }
.field-label-row label { margin-bottom: 0; }
.field-hint-link { font-size: 0.82rem; font-weight: 600; color: var(--primary); }
.field-hint-link:hover { text-decoration: underline; }
/* Hints and errors stay readable (≥ 14 px) — many clients are elderly. */
.field-hint { display: block; margin-top: 0.35rem; color: var(--muted); font-size: 0.9rem; line-height: 1.45; }
.field-error { display: block; margin-top: 0.35rem; color: var(--danger); font-size: 0.9rem; font-weight: 600; }
.field-error:empty { margin-top: 0; }
.field-error:not(:empty)::before { content: "⚠\FE0E"; margin-right: 0.35em; }
.has-error .control, .control[aria-invalid="true"] { border-color: var(--danger); }
/* A label + action button on one row that wraps instead of squeezing the button
   into a multi-line pill on a phone (map picker's "Use my location"). */
.field-label-row--wrap { flex-wrap: wrap; align-items: center; row-gap: var(--space-2); }
.field-label-row--wrap .button { white-space: nowrap; }
.form-note { display: flex; gap: 0.6rem; align-items: flex-start; margin: var(--space-4) 0 0; padding: 0.8rem 1rem; border-radius: 12px; background: var(--surface-2); color: var(--text-2); }
.form-note svg { flex: none; width: 1.15rem; height: 1.15rem; margin-top: 0.15rem; color: var(--primary); }

.auth-alert {
    margin-bottom: var(--space-4);
    padding: var(--space-3) var(--space-4);
    border-radius: 10px;
    background: var(--danger-glow);
    border: 1px solid var(--danger-line);
    color: var(--danger);
    font-size: 0.88rem;
    font-weight: 600;
    text-align: left;
}

.checkbox-row { display: inline-flex; align-items: center; gap: 0.5rem; font-weight: 600; font-size: 0.88rem; color: var(--text-2); cursor: pointer; }
.checkbox-input { margin-right: 0; }

form p { margin: 0 0 1rem; }
label { display: block; margin-bottom: var(--space-xs); color: var(--text-2); font-weight: 600; font-size: 0.88rem; }
input:not([type="checkbox"]):not([type="radio"]), select, textarea, .control {
    width: 100%;
    min-height: 2.75rem;
    padding: 0.7rem 0.8rem;
    border: 1px solid var(--line);
    border-radius: 9px;
    background: var(--input-bg);
    color: var(--text);
    font: inherit;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
input[type="checkbox"], input[type="radio"] {
    width: 1.25rem;
    height: 1.25rem;
    min-height: unset;
    margin-right: 0.4rem;
    vertical-align: middle;
    accent-color: var(--primary);
    cursor: pointer;
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-glow); }
textarea { resize: vertical; }
.helptext { display: none; }
.errorlist { color: var(--danger); font-weight: 700; font-size: 0.9rem; list-style: none; }

/* Django's {{ form.as_p }} puts a checkbox's <label> before the <input> inside
   one <p>; the global `label { display: block }` would stack them on separate
   lines. Lay that specific pairing out as a row instead. */
p:has(> input[type="checkbox"]), p:has(> input[type="radio"]) {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
p:has(> input[type="checkbox"]) label, p:has(> input[type="radio"]) label {
    display: inline;
    margin-bottom: 0;
}

/* File inputs (avatar/photo uploads) ignore most box styling on their native
   button, so give them a lighter treatment instead of the full input box. */
input[type="file"] {
    min-height: unset;
    padding: 0.5rem 0.6rem;
    font-size: 0.86rem;
    color: var(--muted);
}
input[type="file"]::file-selector-button {
    margin-right: 0.75rem;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--surface-2);
    color: var(--text);
    font: inherit;
    font-weight: 700;
    font-size: 0.82rem;
    cursor: pointer;
    transition: border-color 0.2s ease, background 0.2s ease;
}
input[type="file"]::file-selector-button:hover { border-color: var(--primary); background: var(--surface); }

/* ===== FILTER BAR =====
   One horizontal, wrapping row for a filter/search form — whether its fields
   are hand-written <div>s or Django's {{ form.as_p }} (each field lands in
   its own <p>; treat those exactly like the hand-written field groups). */
.filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space-4);
    margin-top: var(--space-5);
    margin-bottom: var(--space-5);
}
.filter-bar > p,
.filter-bar .field-group {
    flex: 1 1 180px;
    min-width: 160px;
    margin: 0;
}
/* A checkbox field is one short row, not a stacked label+control, so it can't
   just bottom-align with the selects. Seat it against the middle of the select
   inputs instead: sit at the row's baseline, then lift it by ~half an input
   height so the box lines up with the text in the neighbouring controls. */
.filter-bar > p:has(> input[type="checkbox"]),
.filter-bar > p:has(> input[type="radio"]),
.filter-bar .field-group:has(input[type="checkbox"]),
.filter-bar .field-group:has(input[type="radio"]) {
    flex: 0 0 auto;
    align-self: flex-end;
    padding-bottom: var(--space-2);
}
.filter-bar .field-group:has(input[type="checkbox"]) { min-width: 0; }
.filter-bar button, .filter-bar .button { flex: 0 0 auto; align-self: flex-end; }
/* Phone collapse (base.html adds .is-collapsible + the toggle button). */
.filter-toggle { display: none; }
.filter-toggle__count { display: inline-flex; align-items: center; justify-content: center; min-width: 1.5rem; height: 1.5rem; margin-left: 0.5rem; padding: 0 0.4rem; border-radius: 999px; background: var(--primary); color: var(--on-primary); font-size: 0.8rem; }
@media (max-width: 600px) {
    .filter-bar.is-collapsible { padding: var(--space-3); }
    .filter-bar.is-collapsible > .filter-toggle { display: inline-flex; width: 100%; justify-content: center; align-self: stretch; }
    .filter-bar.is-collapsible:not(.is-open) > :not(.filter-toggle) { display: none; }
    .filter-bar.is-collapsible.is-open > .filter-toggle { margin-bottom: var(--space-3); }
}

/* An applicant/person row: avatar + details + actions, wrapping on narrow
   screens instead of overflowing. Reuses .avatar-fallback for the initial. */
.applicant-row { display: flex; gap: var(--space-4); align-items: flex-start; flex-wrap: wrap; }
.applicant-row .applicant-body { flex: 1 1 220px; min-width: 220px; }
.applicant-row .applicant-body h3 { margin-top: 0.4rem; }
.applicant-row .applicant-quote { font-style: italic; }
.applicant-row .button-row { align-items: center; }

/* ===== TABLE LIST & ROWS ===== */
.table-list { display: grid; gap: var(--space-3); }
.request-row {
    display: grid;
    /* minmax(0, …): a 1fr track never shrinks below its content's min-content,
       so one long unbreakable name would widen the row past a phone screen. */
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 1rem;
    align-items: center;
    padding: var(--space-4) var(--space-5);
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: 16px;
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.request-row:hover { border-color: var(--primary); box-shadow: var(--shadow); }
/* Rating page (no places, no points): volunteers grouped under region headings. */
/* Rating: one shared list; the volunteer's region is small info text under the name. */
.row-rank .row-region { margin: 0.1rem 0 0; font-size: 0.8rem; font-weight: 600; color: var(--muted); }
/* Badges and bio under the name: a little air between the lines of a row. */
.row-rank > div > * + *:not(.row-region) { margin-top: var(--space-xs); }
.request-row .row-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.row-rank { display: flex; align-items: center; gap: var(--space-4); min-width: 0; }
.row-rank > .avatar, .row-rank > picture > .avatar, .row-rank > .rank-num { flex: none; }
.row-rank > div { min-width: 0; }
/* Names in list rows: one line, ellipsis (the full name is on the profile). */
.row-rank h3 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row-rank p { overflow-wrap: anywhere; }
.row-rank .rank-num { font-weight: 700; font-variant-numeric: tabular-nums; font-size: 1.1rem; color: var(--muted); min-width: 1.6rem; }

/* ===== DATA TABLE (CRM lists) =====
   One scannable table on desktop; on narrow screens each row folds into a
   labelled card (thead hidden, each td shows its column name from
   data-label). The table scrolls inside .table-scroll — the page never does. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.data-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: 16px;
    overflow: hidden;
}
.data-table thead th {
    padding: var(--space-3) var(--space-4);
    text-align: left;
    font-size: 0.74rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
    background: var(--surface-2);
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}
.data-table tbody td {
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
    color: var(--text-2);
    font-size: 0.9rem;
}
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr { transition: background 0.15s ease; }
.data-table tbody tr:hover { background: var(--surface-2); }
.data-table td .cell-title { font-weight: 700; color: var(--text); }
.data-table td .cell-sub { display: block; font-size: 0.82rem; color: var(--muted); margin-top: 0.15rem; }
.data-table .col-actions { text-align: right; white-space: nowrap; }
.data-table .col-actions .button,
.data-table .col-actions button {
    min-height: 2.15rem;
    padding: 0.4rem 0.8rem;
    font-size: 0.82rem;
}
.data-table .col-actions form { display: inline-block; }
.data-table .col-actions--stack { display: flex; flex-wrap: wrap; gap: var(--space-2); justify-content: flex-end; }
.data-table .col-num { font-variant-numeric: tabular-nums; }

@media (max-width: 860px) {
    /* Break the table box model entirely so rows can be full-width cards. */
    .table-scroll { overflow-x: visible; }
    .data-table, .data-table tbody { display: block; width: 100%; border: none; background: transparent; }
    .data-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
    .data-table tbody tr {
        display: block;
        background: var(--card-bg);
        border: 1px solid var(--card-border);
        border-radius: 16px;
        box-shadow: var(--shadow);
        padding: var(--space-2) var(--space-4);
        margin-bottom: var(--space-3);
    }
    .data-table tbody tr:hover { background: var(--card-bg); }
    .data-table tbody td {
        display: block;
        padding: 0.5rem 0;
        border-bottom: 1px solid var(--line);
        text-align: left;
        overflow-wrap: anywhere;
    }
    .data-table tbody tr td:last-child { border-bottom: none; }
    .data-table tbody td::before {
        content: attr(data-label);
        display: block;
        margin-bottom: 0.2rem;
        font-size: 0.7rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        color: var(--muted);
    }
    .data-table td[data-label=""]::before { content: none; }
    .data-table td[data-label=""] { padding-top: 0.75rem; }
    /* Label beside its value (not above it): about twice as many rows per
       phone screen for staff working through a list. Tables with their own
       phone layout (rating cards, preferences matrix, notification list) opt out. The label floats, so a
       cell's inline content ("3 done · 0 active", tags) keeps flowing. */
    .data-table:not(.prefs-matrix):not(.rating-table):not(.notifications-table) tbody td:not([data-label=""]):not(.col-actions) {
        display: flow-root; /* a two-line label still makes the row taller */
        padding: 0.45rem 0 0.45rem 7.4rem;
    }
    .data-table:not(.prefs-matrix):not(.rating-table):not(.notifications-table) tbody td:not([data-label=""]):not(.col-actions)::before {
        float: left;
        width: 6.6rem;
        margin: 0.2rem 0 0 -7.4rem; /* sits in the padding: values keep their own column */
        letter-spacing: 0.02em;
        overflow-wrap: normal;
        hyphens: auto;
    }
    .data-table .col-actions { text-align: left; white-space: normal; }
    .data-table .col-actions.col-actions--stack {
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-start;
        gap: var(--space-2);
    }
}

/* ===== TAB BAR =====
   Segmented filter tabs (role / status filters). Replaces rows of .button
   used as tabs. Scrolls horizontally on narrow screens rather than wrapping
   into an uneven block. */
.tab-bar {
    display: flex;
    gap: var(--space-1);
    padding: var(--space-1);
    margin-bottom: var(--space-5);
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: 12px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.tab-bar a {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.95rem;
    border-radius: 8px;
    font-weight: 600;
    font-size: 0.88rem;
    color: var(--text-2);
    white-space: nowrap;
    transition: background 0.18s ease, color 0.18s ease;
}
.tab-bar a .tab-count { color: var(--muted); font-variant-numeric: tabular-nums; }
.tab-bar a:hover { color: var(--primary); background: var(--surface); }
.tab-bar a.is-active,
.tab-bar a[aria-current="page"] { background: var(--primary); color: var(--on-primary); }
.tab-bar a.is-active .tab-count,
.tab-bar a[aria-current="page"] .tab-count { color: rgba(255,255,255,0.75); }

/* ===== CODE BLOCK (one-time codes, tokens) ===== */
.code-block {
    display: block;
    margin: var(--space-3) 0;
    padding: var(--space-3) var(--space-4);
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: 10px;
    font-size: 0.9rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.04em;
    color: var(--text);
    overflow-x: auto;
    white-space: pre;
}
.code-block code { user-select: all; }

/* ===== STATUS SCREEN =====
   A single centered card for a confirmation / error / not-found screen.
   Built on the auth shell so it matches the login/register treatment
   instead of the old glass-card look. */
.status-screen { display: grid; place-items: center; min-height: 68vh; padding: clamp(2rem, 6vw, 4rem) 1rem; }
.status-screen .status-card {
    width: 100%;
    max-width: 460px;
    text-align: center;
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: 16px;
    box-shadow: var(--shadow);
    padding: clamp(1.75rem, 4vw, 2.5rem);
}
.status-screen .status-card h1 { font-size: clamp(1.5rem, 3vw, 1.95rem); margin: 0.6rem 0 0.5rem; }
.status-screen .status-card p { margin: 0 auto 0.4rem; max-width: 34rem; }
.status-screen .status-code {
    font-weight: 700;
    font-size: 0.9rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
}
.status-screen .button-row { justify-content: center; margin-top: var(--space-5); }

/* ===== FORM ACTIONS ===== */
.form-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-5); }
.form-actions .button, .form-actions button { min-width: 8rem; }
.form-actions--full .button, .form-actions--full button { width: 100%; }

.text-center { text-align: center; }

/* ===== OPPORTUNITY CARDS (help-request marketplace listings) =====
   A colour-coded band + icon stands in for a photo per category, since the
   product has no request photography. Distinct on purpose from a plain .card. */
.opportunity-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: var(--space-4); align-items: stretch; }
.opportunity-card {
    display: flex;
    flex-direction: column;
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: 16px;
    overflow: hidden;
    box-shadow: var(--shadow);
    transition: transform 0.22s ease, box-shadow 0.22s ease;
}
.opportunity-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-hover); }
.opportunity-media {
    position: relative;
    height: 108px;
    display: grid;
    place-items: center;
    color: var(--media-ink);
}
.opportunity-media svg { width: 34px; height: 34px; }
.opportunity-media .tag.urgent { position: absolute; top: 0.65rem; right: 0.65rem; background: var(--card-bg); }
/* Larger media band + bigger icon for a single-item detail view. */
.opportunity-media--lg { height: 180px; }
.opportunity-media--lg svg { width: 44px; height: 44px; }
.media-medical { background: linear-gradient(135deg, #e9ecfa, #e3f2ef); }
.media-grocery { background: linear-gradient(135deg, #e3f2ef, #eef6ec); }
.media-transport { background: linear-gradient(135deg, #e6ecf8, #e9ecfa); }
.media-household { background: linear-gradient(135deg, #fdf2df, #fbe9e4); }
.media-emotional { background: linear-gradient(135deg, #fbe9e4, #fdf2df); }
.media-documents { background: linear-gradient(135deg, #eef0f5, #e9ecfa); }
.media-other { background: linear-gradient(135deg, #f1f3f7, #eceef3); }
[data-theme="dark"] .media-medical { background: linear-gradient(135deg, #2b2f45, #26363a); }
[data-theme="dark"] .media-grocery { background: linear-gradient(135deg, #26363a, #2b3530); }
[data-theme="dark"] .media-transport { background: linear-gradient(135deg, #283143, #2b2f45); }
[data-theme="dark"] .media-household { background: linear-gradient(135deg, #3a3226, #3a2c2a); }
[data-theme="dark"] .media-emotional { background: linear-gradient(135deg, #3a2c2a, #3a3226); }
[data-theme="dark"] .media-documents { background: linear-gradient(135deg, #2c2e35, #2b2f45); }
[data-theme="dark"] .media-other { background: linear-gradient(135deg, #2a2c32, #26282e); }
.opportunity-body { padding: var(--space-4); display: flex; flex-direction: column; gap: 0.55rem; flex: 1; }
.opportunity-body h3 { margin: 0.1rem 0 0; }
/* Status pills keep their natural width (the column would stretch them). */
.opportunity-body > .tag { align-self: flex-start; }
/* Detail view reuses the same card with an <h1> in place of the listing <h3>. */
.opportunity-body h1 { margin: 0.1rem 0 0; font-size: clamp(1.5rem, 3vw, 2rem); }
.opportunity-body .detail-meta { margin-top: 0.4rem; }
.opportunity-location { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; color: var(--muted); font-size: 0.85rem; font-weight: 600; }
.opportunity-location svg { flex-shrink: 0; color: var(--primary-2); }
.opportunity-desc { font-size: 0.9rem; margin: 0; flex: 1; }
.opportunity-cta { margin-top: 0.35rem; align-self: flex-start; }

/* ===== PROFILE HEADER & AVATAR (shared) =====
   Used by the dashboard, the CRM volunteer detail page, the rating board, task
   detail and the AI page. Dashboard-only chrome (hero, KPI strip, queue,
   quick-actions) lives in dashboard.css. */
.profile-head { display: flex; gap: var(--space-4); align-items: center; }
/* Let the text column shrink and wrap a long email instead of forcing the
   card (and, on a phone, the page) wider than the viewport. */
.profile-head > :not(.avatar, picture) { min-width: 0; }
.profile-head p,
.profile-head h1,
.profile-head h2,
.profile-head h3 { overflow-wrap: anywhere; }  /* full name matters here: wrap, don't cut */
.detail-card > .profile-head { margin-bottom: var(--space-5); }
.avatar {
    width: 76px;
    height: 76px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--surface-2);
    border: 3px solid var(--surface-2);
    flex-shrink: 0;
}
/* No-avatar fallback: initials on a flat brand tint, never a fake person image. */
.avatar-fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 1.7rem;
    color: var(--primary);
    background: var(--primary-glow);
}
.avatar-sm { width: 44px; height: 44px; border-width: 2px; }
.avatar-fallback.avatar-sm { font-size: 1.05rem; }

/* Hidden visually, still read by screen readers and search engines. */
.visually-hidden {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* ===== IMAGES ===== */
/* height:auto: see .volunteers-photo in home.css (width/height attributes). */
.report-img { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 9px; }

/* ===== AI FLOATING BUTTON =====
   Persistent entry point to the AI assistant, shown on authenticated pages
   instead of a navbar link — see base.html for which pages exclude it. */
.ai-fab {
    position: fixed;
    right: 20px;
    bottom: 20px;
    z-index: 150;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--primary);
    color: var(--on-primary);
    box-shadow: var(--shadow-hover);
    transition: transform 0.22s ease, box-shadow 0.22s ease;
}
.ai-fab svg { width: 24px; height: 24px; }
.ai-fab:hover, .ai-fab:focus-visible { transform: translateY(-3px) scale(1.05); box-shadow: 0 14px 30px var(--primary-glow); }
.ai-fab::after {
    content: attr(data-tooltip);
    position: absolute;
    right: calc(100% + 12px);
    top: 50%;
    transform: translateY(-50%);
    background: var(--text);
    color: var(--surface);
    padding: 0.4rem 0.75rem;
    border-radius: 8px;
    font-size: 0.78rem;
    font-weight: 700;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s ease;
}
.ai-fab:hover::after, .ai-fab:focus-visible::after { opacity: 1; }
@media (max-width: 640px) {
    .ai-fab { width: 48px; height: 48px; right: 16px; bottom: 16px; }
    .ai-fab svg { width: 21px; height: 21px; }
    .ai-fab::after { display: none; }
}

/* ===== TOASTS ===== */
.toast-stack { position: fixed; right: 1rem; top: 5rem; z-index: 200; display: grid; gap: var(--space-2); }
.toast { padding: 0.8rem 1.2rem; border-radius: 10px; background: var(--surface); color: var(--text); border: 1px solid var(--line); box-shadow: var(--shadow); animation: toastIn 0.3s ease; }
@keyframes toastIn { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: translateX(0); } }

/* ===== EMPTY STATES ===== */
.empty-state { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); padding: var(--space-6) var(--space-5); border: 1px dashed var(--line); border-radius: 14px; color: var(--muted); }
.empty-state svg { color: var(--muted); }
.empty-state strong { color: var(--text); font-size: 1rem; }

/* ===== STATUS PANEL (volunteer application status) ===== */
.status-panel { text-align: center; padding: clamp(2rem, 5vw, 3rem); }
.status-icon { width: 64px; height: 64px; margin: 0 auto 1rem; border-radius: 50%; display: grid; place-items: center; }
.status-icon.is-pending { background: var(--accent-glow); color: var(--accent); }
.status-icon.is-approved { background: var(--ok-glow); color: var(--ok); }
.status-icon.is-rejected { background: var(--danger-glow); color: var(--danger); }
.status-panel h1 { margin-bottom: 0.4rem; }
.status-panel .status-badge { margin: 0.6rem auto 1.2rem; }
.status-panel .button-row { justify-content: center; }
.status-message { margin-top: var(--space-3); }
.status-meta { color: var(--muted); font-size: 0.86rem; margin-top: var(--space-5); }
.status-meta + .status-meta { margin-top: 0.2rem; }

/* ===== FOOTER =====
   Pale-blue ground (--cta-bg), three columns: brand + blurb + pill link ·
   column heading + text + pill badges · contacts (icon + line each). A divider,
   then copyright left / legal links right in small muted text. */
.footer {
    background: var(--cta-bg);
    color: var(--text-2);
    padding: clamp(2.5rem, 5vw, 3.5rem) 0 var(--space-6);
    border-top: 1px solid var(--line);
}
.footer-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--space-6);
}
.footer-col h3 { margin-bottom: var(--space-3); font-size: 1.05rem; }
.footer-col p { margin-bottom: var(--space-4); font-size: 0.9rem; }
.footer-col .brand { margin-bottom: var(--space-4); }
.footer-badges { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.footer-contacts { display: flex; flex-direction: column; gap: var(--space-3); }
.footer-contacts a, .footer-contacts span {
    display: flex;
    align-items: center;
    min-height: 2.25rem; /* comfortable tap height for phone / e-mail */
    gap: var(--space-2);
    font-size: 0.9rem;
    color: var(--text-2);
}
.footer-contacts a:hover { color: var(--primary); }
.footer-contacts svg { width: 1rem; height: 1rem; flex-shrink: 0; color: var(--primary-2); }
.footer-bottom {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-top: var(--space-6);
    padding-top: var(--space-5);
    border-top: 1px solid var(--line);
    color: var(--muted);
    font-size: 0.82rem;
}
.footer-bottom nav { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.footer-bottom nav a { display: inline-flex; align-items: center; min-height: 2.25rem; font-size: 0.9rem; }
.footer-bottom a:hover { color: var(--primary); }
.footer-mark { display: flex; align-items: center; gap: var(--space-2); }
.footer-mark .peaks-mark { width: 60px; }
@media (max-width: 820px) {
    .footer-bottom { flex-direction: column; align-items: flex-start; }
}

/* ===== ANIMATIONS ===== */
@keyframes fadeSlideUp { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: translateY(0); } }
/* Scroll reveal is an enhancement: content is hidden only once base.html's
   head script has marked JS as running (html.gc-reveal), and never for people
   who asked for less motion — a failed script can't leave a page blank. */
@media (prefers-reduced-motion: no-preference) {
    .gc-reveal .reveal { opacity: 0; transform: translateY(16px); transition: opacity 0.5s ease, transform 0.5s ease; }
    .gc-reveal .reveal.visible { opacity: 1; transform: translateY(0); }
    .gc-reveal .reveal:nth-child(2) { transition-delay: 0.06s; }
    .gc-reveal .reveal:nth-child(3) { transition-delay: 0.12s; }
    .gc-reveal .reveal:nth-child(4) { transition-delay: 0.18s; }
}

/* ===== RESPONSIVE =====
   Desktop 1440px+, tablet 768px+, mobile 320px+.
   Page-scoped responsive rules live with their component: hero in home.css,
   auth card in auth.css, map/ops in map.css, stepper in task-detail.css. */
/* Below this width the link row folds into a tap-to-open dropdown instead of a
   horizontal scroller — on a phone a full-height menu is easier to use than a
   one-line strip you have to swipe through. The brand stays left; the language
   switcher, theme toggle and menu button group on the right. */
@media (max-width: 960px) {
    .topbar .toolbar { margin-left: auto; }
    .menu-button {
        display: inline-flex;
        order: 3;
        background: var(--surface-2);
        color: var(--text);
    }
    .nav-links {
        flex: none;
        display: none;
        position: absolute;
        left: var(--space-4); right: var(--space-4);
        top: calc(var(--nav-h) + 0.35rem);
        max-height: calc(100vh - var(--nav-h) - 1.5rem);
        padding: var(--space-3);
        background: var(--surface);
        border: 1px solid var(--line);
        border-radius: 12px;
        box-shadow: var(--shadow);
        z-index: 99;
        flex-direction: column;
        align-items: stretch;
        flex-wrap: nowrap;
        overflow-x: hidden;
        overflow-y: auto;
        -webkit-mask-image: none;
                mask-image: none;
    }
    .nav-links.open { display: flex; gap: var(--space-1); }
    /* In the dropdown a link is a full-width row; the current page gets a
       light tint instead of the underline. */
    .nav-links a:not(.button) { padding: 0.6rem 0.85rem; border-radius: 10px; }
    .nav-links a:not(.button)::after { display: none; }
    .nav-links a:not(.button):hover,
    .nav-links a:not(.button)[aria-current="page"] { background: var(--surface-2); }
    /* Guest: links first, then the sign-in buttons as their own block —
       separated by a rule, full width, "Become a volunteer" last. */
    .nav-group--links { display: flex; flex-direction: column; gap: var(--space-1); }
    .nav-group--auth {
        display: flex; flex-direction: column; gap: var(--space-2);
        margin-top: var(--space-2); padding-top: var(--space-3);
        border-top: 1px solid var(--line);
    }
    .nav-group--auth .button { width: 100%; }
    .nav-links .nav-logout-form { display: flex; }
    .nav-links .nav-logout { width: 100%; justify-content: flex-start; padding: 0.6rem 0.85rem; border-radius: 10px; }
    /* The dropdown already carries the Dashboard/profile link, so the corner
       profile chip is redundant here and only costs horizontal room. */
    .nav-avatar { display: none; }
}
@media (max-width: 820px) {
    .two-col { grid-template-columns: 1fr; }
    .request-row { grid-template-columns: 1fr; }
    .footer { flex-direction: column; align-items: flex-start; }
    .grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
    .metric-row { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); }
    .page-header { align-items: flex-start; }
    .page-header__actions { width: 100%; }
}
@media (max-width: 560px) {
    /* Filter fields and their action button each take a full row rather than
       squeezing into an uneven wrap. */
    .filter-bar > p, .filter-bar .field-group { flex-basis: 100%; }
    .filter-bar button, .filter-bar .button { width: 100%; }
}
@media (max-width: 480px) {
    .grid, .opportunity-grid { grid-template-columns: 1fr; }
    .toolbar { gap: 0.3rem; }
    .lang-btn { padding: 0.25rem 0.4rem; font-size: 0.72rem; }
    .kpi-row { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); }
    /* The 220px content-based floor on .applicant-body (plus an avatar and,
       in the people directory, a trailing rating figure) no longer fits
       beside its siblings at this width — let it take the full row instead
       of forcing the row wider than the viewport. */
    .applicant-row .applicant-body { flex-basis: 100%; min-width: 0; }
}
@media (max-width: 600px) {
    /* Phone: tighten the row so logo + brand name, the toolbar (segmented
       language switcher, theme toggle, bell) and the burger fit one line. */
    .topbar > .container { gap: var(--space-2); }
}
/* Narrow phones: the signed-in toolbar (languages, theme, bell) plus the burger
   leave the brand name too little room — tighten the controls a little. */
@media (max-width: 420px) {
    .topbar > .container { gap: 6px; }
    .topbar .toolbar { gap: 3px; }
    .topbar .navbar__brand { gap: 8px; }
    .theme-toggle, .bell__button { width: 2rem; height: 2rem; }
    .menu-button { width: 2.1rem; }
    .lang-btn { padding: 0.25rem 0.3rem; }
}
/* Below 375px (360px Androids) the brand gets one notch smaller, so the
   longest name (EN, signed-in toolbar with the bell) still fits whole. */
@media (max-width: 374px) {
    .topbar > .container { gap: 4px; }
    .topbar .toolbar { gap: 2px; }
    .navbar__brand-name { font-size: 20px; }
    .brand-logo { width: 28px; height: 28px; }
}

/* Skills checkbox group on the edit-profile form — a wrapping row, not a
   bare bullet list (Django's CheckboxSelectMultiple default). */
.checkbox-group ul { list-style: none; display: flex; flex-wrap: wrap; gap: 0.5rem 0.9rem; margin: 0.35rem 0 0; padding: 0; }
.checkbox-group li { margin: 0; }
.checkbox-group label { display: inline-flex; align-items: center; gap: 0.4rem; margin: 0; font-weight: 500; color: var(--text-2); cursor: pointer; }
/* …but the group's own heading label keeps the normal label → field spacing. */
.field-group.checkbox-group > label { display: block; margin-bottom: var(--space-xs); font-weight: 600; cursor: default; }

/* ===== CRM: PAGINATION & KPI TILES =====
   Pagination reuses .button/.button-ghost for its controls — only the
   wrapping layout is new. KPI tiles reuse .card/.stat-card; the .kpi-row grid
   itself is defined just below. */
.pagination { display: flex; align-items: center; justify-content: center; gap: var(--space-4); margin-top: var(--space-6); }
.pagination-info { color: var(--muted); font-size: 0.85rem; }
/* KPI tiles: a fixed, compact size that wraps and left-aligns. The tile never
   stretches past ~11.5rem, so a row of two or three tiles on a full-width page
   stays a row of small tiles instead of becoming two or three huge cards; a
   row that fills leaves its slack at the trailing edge, the way a dashboard
   strip should. grid-auto-rows keeps every wrapped row the same height even
   when one label runs to two lines. */
.kpi-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 11.5rem));
    grid-auto-rows: 1fr;
    justify-content: start;
    gap: var(--space-4);
    margin: var(--space-5) 0;
}
.kpi-row > * { min-width: 0; }
/* Phones: fill the width two-up. (The older max-width rule above sits before
   the base .kpi-row rule, so the base columns used to win on phones too.) */
@media (max-width: 600px) {
    .kpi-row { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3); }
}
.kpi-row .stat-card { padding: var(--space-4); }
.kpi-row .metric { font-size: 1.6rem; }

/* ===== EMERGENCY / SOS =====
   The one area allowed a strong danger treatment. Still flat — a solid danger
   fill and a danger left-border, no glow, no pulsing UI. Reuses .button.danger. */
.emergency-sos { margin-top: var(--space-4); }
.emergency-sos__summary {
    list-style: none;
    width: 100%;
    max-width: 22rem;
}
.emergency-sos__summary::-webkit-details-marker { display: none; }
.emergency-sos[open] .emergency-sos__summary { border-radius: 10px 10px 0 0; }
.emergency-confirm {
    border: 1px solid var(--danger-line);
    border-top: none;
    border-radius: 0 0 12px 12px;
    background: var(--danger-glow);
    padding: var(--space-4);
    max-width: 22rem;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}
.emergency-confirm p { margin: 0; font-size: 0.88rem; color: var(--text-2, var(--text)); }
.emergency-confirm__field { margin: 0; }
.emergency-confirm textarea { width: 100%; }

.emergency-card { border-left: 3px solid var(--danger); }

/* Volunteer dashboard: the saved profile point used by matching. */
.profile-location.is-stale { border-left: 3px solid var(--danger); }
.profile-location__status { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin: 0; overflow-wrap: anywhere; }
.profile-location__status.is-error { color: var(--danger); }

/* Dispatch: do-not-disturb volunteers, listed apart from the ranked shortlist. */
.rec-away { margin-top: var(--space-4); border-top: 1px solid var(--line); padding-top: var(--space-3); }
.rec-away > summary { cursor: pointer; font-weight: 700; }
.rec-away__row { border-left: 3px solid var(--danger); }

/* Request cancellation (task_detail): a collapsed control, then a short form. */
.cancel-box { margin-top: var(--space-4); }
.cancel-box > summary { list-style: none; display: inline-flex; cursor: pointer; }
.cancel-box > summary::-webkit-details-marker { display: none; }
/* Destructive but not primary: danger-outlined, so it never reads as the main action. */
.cancel-box > summary.button { background: transparent; color: var(--danger); border: 1px solid var(--danger); box-shadow: none; }
.cancel-box > summary.button:hover { background: var(--danger-glow); }
.cancel-box__form { margin-top: var(--space-3); max-width: 28rem; display: flex; flex-direction: column; gap: var(--space-2); }
.cancel-box__form textarea { width: 100%; }
.cancel-box__form .button { align-self: flex-start; }
.cancel-note { margin-top: var(--space-4); padding: var(--space-3) var(--space-4); border-left: 3px solid var(--danger); background: var(--danger-glow); border-radius: 0 12px 12px 0; overflow-wrap: anywhere; }
.cancel-note p { margin: var(--space-1) 0 0; }
.emergency-notice {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-top: var(--space-4);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--danger-line);
    border-radius: 12px;
    background: var(--danger-glow);
}
.emergency-notice p { margin: 0.15rem 0 0; font-size: 0.86rem; }

.emergency-banner {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4);
    margin-bottom: var(--space-4);
    border: 1px solid var(--danger-line);
    border-left-width: 4px;
    border-radius: 12px;
    background: var(--danger-glow);
    color: var(--text);
}
.emergency-banner svg { color: var(--danger); flex: 0 0 auto; margin-top: 0.1rem; }
.emergency-banner p { margin: 0.2rem 0 0; font-size: 0.88rem; color: var(--muted); }
.emergency-banner strong { font-size: 1rem; }

.emergency-closed { display: flex; align-items: center; gap: var(--space-2); color: var(--muted); margin-bottom: var(--space-4); }
.emergency-actions { display: flex; flex-direction: column; gap: var(--space-4); }
.emergency-close-form .field-group { margin: 0 0 0.6rem; }
.detail-reason { margin-top: var(--space-4); }
.detail-reason p { margin: 0.25rem 0 0; }

.stat-card--alert { border-color: var(--danger-line); background: linear-gradient(var(--danger-glow), var(--danger-glow)), var(--card-bg); }
.stat-card--alert .metric { color: var(--danger); }
.card--alert { border-color: var(--danger-line); }
.data-table tr.row-alert td:first-child { box-shadow: inset 3px 0 0 var(--danger); }
.mini-list-item--link { display: block; text-decoration: none; color: inherit; }
.mini-list-item--link:hover { background: var(--surface-2); }

/* ===== NOTIFICATION BELL (base.html toolbar, static/js/live-ui.js) =====
   Sits in the toolbar, outside the collapsing .nav-links menu, so it stays
   visible at every width. Same 2.3rem circle as the theme toggle. */
.bell { position: relative; flex: 0 0 auto; }
.bell__button {
    position: relative;
    box-sizing: border-box;
    width: 2.3rem;
    height: 2.3rem;
    min-width: 0;
    min-height: 0;
    padding: 0;
    border-radius: 50%;
    background: var(--surface-2);
    border: 1px solid var(--line);
    color: var(--text);
    line-height: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.bell__button:hover { border-color: var(--primary); color: var(--primary); }
.bell.is-open .bell__button { border-color: var(--primary); color: var(--primary); }
.bell__button svg { width: 1.15rem; height: 1.15rem; }
.bell__badge {
    position: absolute;
    top: -0.3rem;
    right: -0.35rem;
    min-width: 1.15rem;
    height: 1.15rem;
    padding: 0 0.3rem;
    border-radius: 999px;
    background: var(--danger);
    color: var(--on-primary);
    font-size: 0.66rem;
    font-weight: 800;
    line-height: 1.15rem;
    text-align: center;
    box-shadow: 0 0 0 2px var(--nav-bg-solid);
    font-variant-numeric: tabular-nums;
}
.bell__badge[hidden] { display: none; }
.bell__panel {
    position: absolute;
    right: 0;
    top: calc(100% + 0.6rem);
    width: min(23rem, calc(100vw - 2rem));
    max-height: min(70vh, 32rem);
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 14px;
    box-shadow: var(--shadow-hover);
    z-index: 120;
    overflow: hidden;
}
.bell__panel[hidden] { display: none; }
.bell__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--line);
}
.bell__head form[hidden] { display: none; }
.bell__link, .bell__link:hover {
    min-height: 0;
    padding: 0.2rem 0.4rem;
    border: none;
    background: transparent;
    color: var(--primary);
    font-size: 0.8rem;
    font-weight: 700;
    transform: none;
    box-shadow: none;
}
.bell__link:hover { text-decoration: underline; }
.bell__status { margin: 0; padding: var(--space-4); color: var(--muted); font-size: 0.88rem; }
.bell__status[hidden] { display: none; }
.bell__list { list-style: none; margin: 0; padding: 0; overflow-y: auto; overscroll-behavior: contain; }
.bell__list li + li { border-top: 1px solid var(--line); }
.bell__list form { display: block; }
.bell__item {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0.1rem;
    width: 100%;
    min-height: 0;
    padding: var(--space-3) var(--space-4) var(--space-3) calc(var(--space-4) + 0.25rem);
    border: none;
    border-radius: 0;
    background: transparent;
    color: var(--text-2);
    text-align: left;
    font-weight: 500;
    font-size: 0.86rem;
    line-height: 1.4;
    position: relative;
    transform: none;
}
.bell__item:hover, .bell__item:focus-visible { background: var(--surface-2); transform: none; box-shadow: none; }
.bell__item.is-unread { background: var(--primary-glow); }
.bell__item.is-unread::before {
    content: "";
    position: absolute;
    left: 0.55rem;
    top: 1.05rem;
    width: 0.45rem;
    height: 0.45rem;
    border-radius: 50%;
    background: var(--primary);
}
.bell__item-title { font-weight: 700; color: var(--text); overflow-wrap: anywhere; }
.bell__item-body { overflow-wrap: anywhere; }
.bell__item-time { font-size: 0.74rem; color: var(--muted); }
.bell__foot {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--line);
    font-size: 0.84rem;
    font-weight: 700;
}
.bell__foot a { color: var(--primary); }
.bell__foot a:hover { text-decoration: underline; }
/* Phone: the panel spans the viewport under the topbar instead of hanging
   off the bell (which may sit near the middle of a narrow toolbar). */
@media (max-width: 600px) {
    .bell { position: static; }
    .bell__panel {
        position: fixed;
        left: var(--space-4);
        right: var(--space-4);
        top: calc(var(--nav-h) + 0.4rem);
        width: auto;
    }
}

/* Live toasts (notification.new) — the existing .toast look plus a title,
   a body line and a close button; never wider than the viewport. */
.toast-stack { max-width: min(24rem, calc(100vw - 2rem)); }
.toast--live { position: relative; padding-right: 2.4rem; overflow-wrap: anywhere; transition: opacity 0.4s ease, transform 0.4s ease; }
.toast--live strong { display: block; color: var(--text); font-size: 0.92rem; }
.toast--live p { margin: 0.15rem 0 0; color: var(--text-2); font-size: 0.85rem; }
.toast__close {
    position: absolute;
    top: 0.45rem;
    right: 0.45rem;
    width: 1.7rem;
    height: 1.7rem;
    min-height: 0;
    padding: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--muted);
}
.toast__close:hover { background: var(--surface-2); color: var(--text); transform: none; box-shadow: none; }

/* Flash messages (base.html): the type is shown three ways — icon, coloured
   edge and the wording itself — never by colour alone. Larger text than the
   live toasts: this is often the only confirmation an elderly client gets. */
.toast--flash {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 0.65rem;
    padding: 0.85rem 2.6rem 0.85rem 0.9rem;
    border-left: 4px solid var(--info);
    font-size: 0.98rem;
    line-height: 1.45;
    overflow-wrap: anywhere;
    transition: opacity 0.4s ease, transform 0.4s ease;
}
.toast__icon { flex: none; display: inline-flex; width: 1.6rem; height: 1.6rem; border-radius: 50%; align-items: center; justify-content: center; background: var(--info-glow); color: var(--info); }
.toast__icon svg { width: 1rem; height: 1rem; }
.toast__text { flex: 1; min-width: 0; padding-top: 0.1rem; }
.toast--flash .toast__close { width: 2rem; height: 2rem; font-size: 1.35rem; line-height: 1; }
.toast--success { border-left-color: var(--ok); }
.toast--success .toast__icon { background: var(--ok-glow); color: var(--ok); }
.toast--warning { border-left-color: var(--brand-orange); }
.toast--warning .toast__icon { background: var(--accent-glow); color: var(--accent); }
.toast--error { border-left-color: var(--danger); }
.toast--error .toast__icon { background: var(--danger-glow); color: var(--danger); }
.flash-stack { position: fixed; right: 1rem; top: 5rem; z-index: 200; display: grid; gap: var(--space-2); max-width: min(26rem, calc(100vw - 2rem)); }
@media (max-width: 560px) {
    /* Phone: in the page flow under the topbar — it pushes the content down
       instead of covering the heading, and stays until closed. */
    .flash-stack { position: static; max-width: none; margin: var(--space-3) 0.75rem 0; }
    .flash-stack .toast { animation: none; box-shadow: none; }
}

/* "This request was updated" (task_detail live update) — sticks under the
   topbar so it's seen without covering the page. */
.live-notice {
    position: sticky;
    top: calc(var(--nav-h) + var(--space-2));
    z-index: 50;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--info);
    border-radius: 12px;
    background: var(--surface);
    box-shadow: var(--shadow);
}
.live-notice[hidden] { display: none; }
.live-notice p { margin: 0; flex: 1 1 12rem; font-weight: 600; color: var(--text); }
.live-notice .button { min-height: 2.3rem; padding: 0.45rem 1.1rem; }

/* Do-not-disturb (volunteer away) — the tag, and the banner on the
   volunteer's own dashboard. */
.tag.away { background: var(--accent-glow); color: var(--accent); }
.tag.away[hidden] { display: none; }
.away-banner {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-3);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--accent);
    border-left-width: 4px;
    border-radius: 12px;
    background: var(--accent-glow);
}
.away-banner svg { color: var(--accent); flex: 0 0 auto; }
.away-banner__text { flex: 1 1 12rem; min-width: 0; }
.away-banner__text strong { display: block; color: var(--text); }
.away-banner__text p { margin: 0.1rem 0 0; font-size: 0.86rem; color: var(--text-2); }
.away-banner .button { min-height: 2.3rem; padding: 0.45rem 1.1rem; }

/* Notifications page */
.notifications-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-4); }
.notifications-toolbar .tab-bar { margin: 0; }
.notifications-toolbar form { margin: 0; }
.notif-filter { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-2); }
.notif-filter .control { min-width: 0; max-width: 100%; }
.notifications-table tr.is-unread td:first-child { box-shadow: inset 3px 0 0 var(--primary); }
.notifications-table tr.is-unread .cell-title { color: var(--primary); }
.notifications-table .notif-body { margin: 0.15rem 0 0; color: var(--text-2); font-size: 0.88rem; overflow-wrap: anywhere; }
.notifications-table td:first-child { min-width: 0; overflow-wrap: anywhere; }
.notif-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); justify-content: flex-end; }
.notif-actions form { display: inline-flex; margin: 0; }
.notifications-table .col-date { white-space: nowrap; color: var(--muted); font-size: 0.84rem; }
@media (max-width: 860px) {
    .notif-actions { justify-content: flex-start; }
}

/* Notification preferences: type × channel matrix. On phones (data-table
   stacked mode) each type becomes a card with one labelled row per channel. */
.prefs-matrix th:not(:first-child), .prefs-matrix td:not(:first-child) { text-align: center; width: 8.5rem; }
.prefs-matrix .pref-cell { display: inline-flex; align-items: center; gap: 0.4rem; justify-content: center; min-height: 2.75rem; min-width: 2.75rem; cursor: pointer; }
.prefs-matrix .pref-cell input { width: 1.5rem; height: 1.5rem; margin: 0; accent-color: var(--primary); }
.prefs-matrix .pref-lock { display: inline-flex; align-items: center; gap: 0.25rem; color: var(--muted); font-size: 0.76rem; font-weight: 600; }
.prefs-matrix .pref-required { display: block; margin-top: 0.15rem; font-size: 0.78rem; color: var(--danger); font-weight: 600; }
@media (max-width: 860px) {
    .prefs-matrix th:not(:first-child), .prefs-matrix td:not(:first-child) { text-align: left; width: auto; }
    .prefs-matrix .pref-cell { justify-content: flex-start; }
}
.notif-bulk { display: flex; justify-content: flex-end; margin-bottom: var(--space-3); }
@media (max-width: 860px) {
    /* Phone: one compact card per type — title row, then the channels side by side. */
    .prefs-matrix tbody tr { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--space-2); }
    .prefs-matrix tbody td:first-child { grid-column: 1 / -1; }
    .prefs-matrix tbody td:first-child::before { content: none; }
    .prefs-matrix tbody td:not(:first-child) { border-bottom: none; }
    .prefs-matrix .pref-cell { flex-wrap: wrap; }
}

/* ===== DIALOG (<dialog class="gc-dialog">, opened with showModal()) =====
   Native modal: focus trap, Esc and the top layer come from the browser; the
   page script returns focus to the opener on `close`. The dialog itself has no
   padding so a click whose target is the <dialog> is a backdrop click.
   First user: tracking consent (myapp/task_detail.html). */
.gc-dialog {
    width: min(34rem, calc(100vw - 2rem));
    max-height: calc(100dvh - 2rem);
    /* The global reset (* { margin: 0 }) removes the UA's auto margin that
       centres a modal <dialog> — restore it, or it sticks to the top-left. */
    margin: auto;
    padding: 0;
    border: 1px solid var(--card-border);
    border-radius: 18px;
    background: var(--card-bg);
    color: var(--text);
    box-shadow: 0 24px 60px rgba(0,0,0,0.28);
    overflow: auto;
}
.gc-dialog::backdrop { background: rgba(11,18,32,0.55); }
.gc-dialog[open] { animation: gcDialogIn 0.2s ease-out; }
@keyframes gcDialogIn {
    from { opacity: 0; transform: translateY(8px) scale(0.98); }
    to { opacity: 1; transform: none; }
}
.gc-dialog__body { padding: clamp(1.25rem, 4vw, 1.9rem); display: flex; flex-direction: column; gap: var(--space-3); }
.gc-dialog__body > * { margin: 0; }
.gc-dialog h2 { font-size: clamp(1.3rem, 4.5vw, 1.6rem); line-height: 1.25; }
.gc-dialog__body > p { color: var(--text-2); line-height: 1.5; }
.gc-dialog__icon {
    display: inline-grid;
    place-items: center;
    width: 3rem;
    height: 3rem;
    border-radius: 50%;
    background: var(--ok-glow);
    color: var(--ok);
}
.gc-dialog__list { list-style: none; padding: 0; display: grid; gap: var(--space-3); }
.gc-dialog__list li {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    padding: var(--space-2) var(--space-3);
    border-left: 3px solid var(--primary-2);
    background: var(--surface-2);
    border-radius: 0 10px 10px 0;
    line-height: 1.45;
}
.gc-dialog__list strong { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--primary-2); }
.gc-dialog__error { color: var(--danger); font-weight: 600; }
.gc-dialog__error[hidden] { display: none; }
/* The two choices stay in view on a short phone screen: the row sticks to the
   dialog's bottom edge while the explanation scrolls under it. */
.gc-dialog__body { padding-bottom: 0; }
.gc-dialog .button-row {
    position: sticky;
    bottom: 0;
    margin-top: var(--space-1);
    padding: var(--space-3) 0 clamp(1.25rem, 4vw, 1.9rem);
    background: var(--card-bg);
    box-shadow: 0 -10px 12px -10px rgba(0,0,0,0.18);
}
@media (max-width: 480px) {
    .gc-dialog .button-row { gap: var(--space-2); }
    .gc-dialog .button-row .button { flex: 1 1 100%; }
    .gc-dialog__list { gap: var(--space-2); }
}
@media (prefers-reduced-motion: reduce) {
    .gc-dialog[open] { animation: none; }
}

/* ===== CHAT ENTRY POINTS (feature 8) =====
   "Open chat" button + unread badge (myapp/_chat_button.html) on the task page,
   dashboards and the volunteer's active tasks; live-ui.js bumps the badge. */
.chat-entry { flex-wrap: wrap; }
.chat-entry__badge {
    display: inline-flex;
    align-items: center;
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
    background: var(--danger);
    color: var(--on-primary);
    font-size: 0.78rem;
    font-weight: 800;
    line-height: 1.35;
    white-space: nowrap;
}
.chat-entry__badge[hidden] { display: none; }
.mini-list-item h4 .chat-entry__badge { vertical-align: middle; margin-left: 0.25rem; }
.task-connect {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-4);
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--line);
}
.task-connect .field-hint { flex: 1 1 14rem; margin: 0; }
.task-connect .chat-entry { min-height: 3rem; }

/* ===== REVIEWS (feature 9) =====
   Public average (.review-summary — never comments), read-only stars, the
   review card (author / volunteer / staff only), the task-page call to action
   and the client's form: big radio stars (native arrow-key radio group) and
   large toggle chips for tags. */
.review-stars { display: inline-flex; gap: 0.05em; line-height: 1; vertical-align: -0.08em; }
.review-star { color: var(--muted); }
.review-star.is-filled { color: var(--accent); }
.review-summary { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; margin: 0; font-size: 0.95rem; }
.review-summary--compact { font-size: 0.85rem; }
.review-summary__avg { color: var(--text); }
.review-summary__count { color: var(--muted); font-weight: 500; }
.review-summary--empty { color: var(--muted); font-size: 0.88rem; }
.row-rank .review-summary { margin: 0.1rem 0 0.2rem; }

.review-tile .metric { display: block; }
.review-tile p { display: flex; flex-direction: column; gap: 0.2rem; }
.review-tile__stars { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.85rem; }
a.review-tile:hover { border-color: var(--primary); }

.review-card { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.review-card__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); }
.review-card__head .review-stars { font-size: 1.3rem; }
.review-card__head a { color: var(--primary); font-weight: 600; overflow-wrap: anywhere; }
.review-card__date { color: var(--muted); font-size: 0.85rem; }
.review-card__tags { list-style: none; display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0; padding: 0; }
.review-card__tags .tag { text-transform: none; letter-spacing: 0; font-size: 0.82rem; }
.review-card__comment { margin: 0; color: var(--text-2); line-height: 1.5; overflow-wrap: anywhere; }
.review-card--hidden { opacity: 0.75; }

.review-cta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3) var(--space-4);
    margin-top: var(--space-4);
    padding: var(--space-4);
    border-radius: 14px;
    background: var(--accent-glow);
    border-left: 4px solid var(--accent);
}
.review-cta--done { background: var(--surface-2); border-left-color: var(--primary-2); align-items: flex-start; }
.review-cta__text { flex: 1 1 16rem; min-width: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.review-cta__text p { margin: 0; }
.review-cta .button { min-height: 3rem; }

.review-form { display: flex; flex-direction: column; gap: var(--space-5); margin-top: var(--space-4); }
.review-form fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
.review-form legend { padding: 0; font-weight: 700; font-size: 1.05rem; color: var(--text); }
.review-stars-input__options { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }
.review-stars-input__option { position: relative; }
.review-stars-input__option input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
.review-stars-input__option label {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.1rem;
    min-width: 3.6rem;
    min-height: 4rem;
    padding: 0.35rem 0.5rem;
    border: 2px solid var(--line);
    border-radius: 14px;
    background: var(--card-bg);
    cursor: pointer;
    font-weight: 700;
    transition: border-color 0.15s ease, background 0.15s ease;
}
.review-stars-input__option label .review-star { font-size: 1.9rem; line-height: 1; }
.review-stars-input__num { font-size: 0.95rem; color: var(--text-2); }
.review-stars-input__option label:hover { border-color: var(--accent); }
.review-stars-input__option input:focus-visible + label { outline: 3px solid var(--primary); outline-offset: 2px; }
.review-stars-input__option input:checked + label { border-color: var(--accent); background: var(--accent-glow); }
/* Fill every star up to the chosen one (progressive: without :has() only the chosen one lights). */
.review-stars-input__option input:checked + label .review-star,
.review-stars-input__options:has(#reviewStars2:checked) .review-stars-input__option:nth-child(-n+2) .review-star,
.review-stars-input__options:has(#reviewStars3:checked) .review-stars-input__option:nth-child(-n+3) .review-star,
.review-stars-input__options:has(#reviewStars4:checked) .review-stars-input__option:nth-child(-n+4) .review-star,
.review-stars-input__options:has(#reviewStars5:checked) .review-stars-input__option:nth-child(-n+5) .review-star { color: var(--accent); }

.review-tags-input ul { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-3) 0 0; padding: 0; }
.review-tags-input li { position: relative; margin: 0; }
.review-chip__input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
.review-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-height: 3rem;
    padding: 0.5rem 1rem;
    border: 2px solid var(--line);
    border-radius: 999px;
    background: var(--card-bg);
    color: var(--text);
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease;
}
.review-chip__tick { display: none; }
.review-chip:hover { border-color: var(--primary-2); }
.review-chip__input:checked + .review-chip { border-color: var(--primary-2); background: var(--ok-glow); color: var(--primary-2); }
.review-chip__input:checked + .review-chip .review-chip__tick { display: block; }
.review-chip__input:focus-visible + .review-chip { outline: 3px solid var(--primary); outline-offset: 2px; }
.review-form textarea { width: 100%; }
@media (max-width: 480px) {
    .review-stars-input__options { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); }
    .review-stars-input__option label { min-width: 0; width: 100%; }
}

/* ===== ACHIEVEMENTS (feature 6) =====
   Profile grid (myapp/achievements/_grid.html) + the badge dialog (reuses
   .gc-dialog) + the small row on public cards (_badge_row.html). The SVG
   badges carry their own colours, so they read the same in both themes; a
   locked badge is greyscale with its progress. The "shine" sweeps once on
   hover / keyboard focus and is off under prefers-reduced-motion. */
.badge-grid {
    list-style: none;
    margin: var(--space-3) 0 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
    gap: var(--space-3);
}
.badge-grid li { margin: 0; min-width: 0; }
.badge-tile {
    width: 100%;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.35rem;
    padding: var(--space-2) 0.35rem var(--space-3);
    border: 1px solid transparent;
    border-radius: 14px;
    background: transparent;
    color: var(--text);
    font: inherit;
    text-align: center;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}
.badge-tile:hover { background: var(--surface-2); border-color: var(--line); }
.badge-tile:focus-visible { outline: 3px solid var(--primary); outline-offset: 2px; }
.badge-tile__title {
    font-size: 0.82rem;
    font-weight: 700;
    line-height: 1.25;
    overflow-wrap: anywhere;
}
.badge-tile__meta { font-size: 0.75rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.badge-tile.is-earned .badge-tile__meta { color: var(--primary-2); font-weight: 600; }

.badge-art {
    position: relative;
    display: inline-block;
    width: 4rem;
    height: 4rem;
    border-radius: 50%;
    overflow: hidden;
    flex: none;
    filter: drop-shadow(0 3px 6px rgba(15,23,42,0.18));
}
.badge-art img { display: block; width: 100%; height: 100%; }
.badge-art::after {
    content: "";
    position: absolute;
    inset: -20%;
    background: linear-gradient(115deg, transparent 35%, rgba(255,255,255,0.65) 50%, transparent 65%);
    transform: translateX(-120%);
    pointer-events: none;
}
.badge-tile.is-earned:hover .badge-art::after,
.badge-tile.is-earned:focus-visible .badge-art::after { animation: badgeShine 0.9s ease-out; }
@keyframes badgeShine { to { transform: translateX(120%); } }
.badge-tile.is-locked .badge-art,
.badge-art.is-locked { filter: grayscale(1); opacity: 0.45; }
.badge-art--lg { width: 7.5rem; height: 7.5rem; }
.badge-art--sm { width: 1.75rem; height: 1.75rem; filter: none; }
[data-theme="dark"] .badge-art { filter: drop-shadow(0 3px 8px rgba(0,0,0,0.45)); }
[data-theme="dark"] .badge-tile.is-locked .badge-art,
[data-theme="dark"] .badge-art.is-locked { filter: grayscale(1); opacity: 0.4; }

.badge-progress {
    display: block;
    width: 80%;
    height: 0.35rem;
    border-radius: 999px;
    background: var(--surface-2);
    overflow: hidden;
}
.badge-progress > span { display: block; height: 100%; border-radius: inherit; background: var(--primary-2); }
.badge-progress--lg { width: 100%; height: 0.55rem; }
.badge-progress[hidden] { display: none; }

.badge-dialog { width: min(26rem, calc(100vw - 2rem)); }
.badge-dialog__inner {
    padding: clamp(1.25rem, 4vw, 1.9rem);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    text-align: center;
}
.badge-dialog__inner > * { margin: 0; }
.badge-dialog__inner h3 { font-size: clamp(1.25rem, 4.5vw, 1.5rem); overflow-wrap: anywhere; }
.badge-dialog__inner > p { color: var(--text-2); line-height: 1.5; }
.badge-dialog__inner [hidden] { display: none; }
.badge-dialog__status { font-weight: 700; color: var(--text) !important; font-variant-numeric: tabular-nums; }
.badge-dialog__inner .button { min-width: 10rem; }
.tier-tag--bronze { background: rgba(154,90,42,0.14); color: #8a4f22; }
.tier-tag--silver { background: var(--surface-2); color: var(--text-2); }
.tier-tag--gold { background: var(--accent-glow); color: var(--accent); }
.tier-tag--legendary { background: var(--info-glow); color: var(--info); }
[data-theme="dark"] .tier-tag--bronze { background: rgba(224,162,106,0.16); color: #e0a26a; }

.badge-row { list-style: none; display: flex; gap: 0.3rem; margin: 0.3rem 0 0; padding: 0; }
.badge-row__item { margin: 0; line-height: 0; }
@media (max-width: 480px) {
    .badge-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2); }
    .badge-art { width: 3.4rem; height: 3.4rem; }
    .badge-art--lg { width: 6.5rem; height: 6.5rem; }
    .badge-art--sm { width: 1.6rem; height: 1.6rem; }
}
@media (prefers-reduced-motion: reduce) {
    .badge-tile.is-earned:hover .badge-art::after,
    .badge-tile.is-earned:focus-visible .badge-art::after { animation: none; }
}

/* ===== DASHBOARD TREND (feature 10) =====
   The 14-day "requests" card on the curator/admin dashboard is a Chart.js line
   (static/js/dashboard.js + charts-theme.js); this is its HTML legend under the
   chart (totals, readable without JS). Dot colours = charts-theme.js ENTITY:
   created → primary, completed → teal (light / dark), test-locked. */
.trend-legend { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); margin: 0; font-size: 0.85rem; color: var(--text-2); }
.trend-legend__item { display: inline-flex; align-items: center; gap: 0.4rem; }
.trend-legend__item::before { content: ""; width: 0.6rem; height: 0.6rem; border-radius: 50%; background: #2A3BA5; }
.trend-legend__item--completed::before { background: #24907F; }
[data-theme="dark"] .trend-legend__item::before { background: #8796F2; }
[data-theme="dark"] .trend-legend__item--completed::before { background: #4DC4B0; }
