/* ==========================================================================
   PCSA Member Portal — design system
   ==========================================================================

   One system, three faces: admin back-office, member dashboard and the staff
   apps (agent / exhibitor) all draw from these tokens, so the portal reads as a
   single product rather than modules bolted together. The public marketing site
   layers site.css on top of the same tokens.

   Palette is derived from the organisation's own mark — five overlapping
   circles carrying a figure at each life stage (pregnancy, newborn, infant,
   toddler, child). Those five colours become the accent spectrum used for stat
   tiles, age-group tags and charts; the mark's blue, deepened for contrast,
   becomes the action colour, and its darkest value becomes the chrome navy.
   Nothing here is invented — it is all in the logo.

   Discipline, inherited from what worked before: blue is the only action
   colour, red is reserved exclusively for destructive moments, and emphasis is
   carried by weight 500/600 rather than bold everywhere.
   ========================================================================== */

/* The `hidden` attribute means hidden, always.
   Any component rule that sets `display` beats the browser's own
   `[hidden] { display: none }`, so a `<div class="x" hidden>` quietly renders.
   That has now been patched three separate times in this project — .lightbox,
   .suggest and the comment reply banner — which is the signal to fix the class
   of bug rather than the next instance of it. */
[hidden] { display: none !important; }


/* --------------------------------------------------------------------------
   Typeface — self-hosted (no third-party CDN on public pages: POPIA, and one
   less network dependency on a shared host).
   -------------------------------------------------------------------------- */

/* Radio Canada Big — the heading face. SIL OFL 1.1, Étienne Aubert Bonn, from
   the Google Fonts release; self-hosted like Inter, no CDN.

   "Big" is the display optical size of the Radio Canada family, so it is used
   only where it was drawn to be used: headings, page and section titles. Body,
   labels, form controls and anything numeric stay on Inter, which is drawn for
   UI and carries the tabular figures the spec bar and price columns rely on.

   Two weights, 600 and 700, because that is all the headings use. The latin
   subset of a display face is small — 11 KB each. */
@font-face {
    font-family: 'Radio Canada Big';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('../fonts/radio-canada-big-600.woff2') format('woff2');
}

@font-face {
    font-family: 'Radio Canada Big';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('../fonts/radio-canada-big-700.woff2') format('woff2');
}

@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('../fonts/inter-400.woff2') format('woff2');
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('../fonts/inter-500.woff2') format('woff2');
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url('../fonts/inter-600.woff2') format('woff2');
}
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('../fonts/inter-700.woff2') format('woff2');
}

/* --------------------------------------------------------------------------
   Tokens
   -------------------------------------------------------------------------- */

:root {
    /* Brand blue — the mark's child figure (#4762b6 in the artwork), deliberately
       deepened for UI contrast: white on #3a5da8 is 6.34:1 against the figure
       colour's 5.69:1. This ramp is chrome, not artwork, so it is NOT re-derived
       from the SVG — see --stage-5-figure for the exact brand blue. */
    --brand-900: #1b2a4f;
    --brand-800: #233954;   /* chrome navy, matches the existing site identity */
    --brand-700: #2f4a86;
    --brand-600: #3a5da8;   /* primary action */
    --brand-500: #5a7bc8;
    --brand-400: #849deb;   /* the mark's own blue disc, exact from the SVG */
    --brand-100: #dfe7f9;
    --brand-50:  #f0f4fd;

    /* Life-stage spectrum, taken from the logo artwork itself.
       ------------------------------------------------------------------
       Every value below is derived from `assets/img/pca-logo.svg`, not
       sampled by eye. The original palette was eyeballed from a raster
       export before the vector existed and every one of the five was a
       shade out — #b8d070 against the artwork's #BDD070, and so on.
       Re-derived; regenerate with `php scripts/palette_check.php` if the
       logo is ever redrawn.

       Each life stage is a disc with a figure on it, so each contributes
       two colours:

         --stage-N          the disc            exact from the SVG
         --stage-N-figure   the silhouette      exact from the SVG
         --stage-N-soft     the disc at 18%     tint behind badge text
         --stage-N-deep     the silhouette,     text on that tint
                            darkened only as
                            far as 4.5:1 needs

       Stages 1 and 5 reach 4.5:1 at the artwork's own figure colour and
       use it unmodified. Stages 2, 3 and 4 are a little light and are
       darkened the minimum amount — same hue, legible text. Check the
       pair if you ever change one: `--stage-N-deep` on `--stage-N-soft`,
       4.5:1, because badge text is small. */
    --stage-1: #df878a;  --stage-1-figure: #a84043;  --stage-1-deep: #a84043;  --stage-1-soft: #f9e9ea;  /* pregnancy  5.14:1 */
    --stage-2: #f4904d;  --stage-2-figure: #bf672b;  --stage-2-deep: #a45824;  --stage-2-soft: #fdebdf;  /* newborn    4.53:1 */
    --stage-3: #fed557;  --stage-3-figure: #cda427;  --stage-3-deep: #896d1a;  --stage-3-soft: #fff7e1;  /* infant     4.60:1 */
    --stage-4: #bdd070;  --stage-4-figure: #889945;  --stage-4-deep: #687535;  --stage-4-soft: #f3f7e5;  --stage-4-mid: #aec066;  /* toddler 4.60:1 */
    --stage-5: #849deb;  --stage-5-figure: #4762b6;  --stage-5-deep: #4762b6;  --stage-5-soft: #e9edfb;  /* child      4.87:1 */

    /* Semantic — each borrowed from the spectrum so alerts still feel in-brand */
    --success-600: #4d7921;  --success-100: #eaf4da;
    --warning-600: #96700a;  --warning-100: #fdf3d4;
    --danger-600:  #a83c3c;  --danger-500: #c25252;  --danger-100: #f9e5e5;

    /* Neutrals, warmed very slightly so they sit with the mark's warm discs.
       Four surface steps, and they have to stay distinguishable: the whole
       point of the scale is that a long form reads as sections rather than one
       white slab.
           page  --mist       238
           chrome--surface-2  244   card heads, card feet, table heads, insets
           input --field-bg   243
           card  --surface    255
       Before this the page was 245 and white was 255 — ten points apart, so a
       white card on the page had no edge but its hairline, and every card head
       was pure white above a pure white body. --surface-2 was referenced in
       five rules and never declared at all, so those backgrounds rendered
       transparent. */
    --ink-900: #1a2130;
    --ink-700: #394354;
    --ink-500: #626d80;
    --ink-400: #8a93a3;
    --ink-300: #b7bfcb;
    --line:    #dce2ec;
    --mist:    #e8ecf3;
    --surface: #ffffff;
    --surface-2: #f2f5fa;
    /* A *neutral* grey, unlike every other surface here — --surface-2, --mist
       and --line all carry a slight blue cast taken from the brand navy. This
       one was chosen by eye against the event cards and is deliberately
       untinted, so it reads as paper rather than as a tinted panel. Keep it for
       that job; --surface-2 is still the right grey inside forms and insets,
       where it sits next to blue-grey borders. */
    --surface-neutral: #ededed;
    /* A step darker, and the neutral counterpart to --mist. Panels that carry a
       foot or a head need the fill to change, not just a border, or the action
       bar floats after the last field instead of sitting on something. Also
       serves as the border colour inside a neutral panel, where the blue-grey
       --line reads as a different material. */
    --surface-neutral-2: #e3e3e3;
    --field-bg:#f1f3f7;

    --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --font-display: 'Radio Canada Big', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --font-mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

    /* How much of a field screen the header, the tab bar and the page's own
       padding take. Declared here so it always resolves, and overwritten with a
       measured value by scanner.js as soon as the stand app loads — the header
       is taller on a notched phone and shorter in a desktop window, and this
       number is only a reasonable starting point for the moment before the
       measurement lands. */
    --field-chrome: 190px;

    --radius-sm: 8px;
    --radius: 10px;
    --radius-lg: 14px;
    --radius-xl: 20px;

    /* Shadows tinted with the chrome navy rather than neutral black — the
       difference between "expensive" and "muddy". */
    --shadow-sm: 0 1px 2px rgba(27, 42, 79, .06), 0 1px 3px rgba(27, 42, 79, .07);
    --shadow-md: 0 4px 12px rgba(27, 42, 79, .08), 0 2px 4px rgba(27, 42, 79, .05);
    --shadow-lg: 0 12px 32px rgba(27, 42, 79, .16);

    --sidebar-w: 244px;

    /* Signature motif: the mark's overlapping soft circles, echoed as quiet
       texture on dark chrome. */
    /* The same five discs as the logo, as soft radial gradients. Values are
       the artwork's own, re-derived with the palette — they were the eyeballed
       ones and drifted with it. rgba rather than the tokens because each needs
       its own alpha. */
    --mark-circles:
        radial-gradient(circle at 12% 50%, rgba(223,135,138,.42) 0 8%, transparent 8.6%),
        radial-gradient(circle at 31% 50%, rgba(244,144,77,.42)  0 8%, transparent 8.6%),
        radial-gradient(circle at 50% 50%, rgba(254,213,87,.42)  0 8%, transparent 8.6%),
        radial-gradient(circle at 69% 50%, rgba(189,208,112,.42) 0 8%, transparent 8.6%),
        radial-gradient(circle at 88% 50%, rgba(132,157,235,.42) 0 8%, transparent 8.6%);
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

* { box-sizing: border-box; margin: 0; padding: 0; }

html { -webkit-text-size-adjust: 100%; }

body {
    font-family: var(--font);
    font-size: 15px;
    line-height: 1.55;
    color: var(--ink-700);
    background: var(--mist);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    font-feature-settings: 'cv05' 1; /* single-storey l — clearer at small sizes */
}

a { color: var(--brand-600); text-decoration: none; }
a:hover { color: var(--brand-700); }

h1, h2, h3, h4 {
    font-family: var(--font-display);
    color: var(--ink-900);
    line-height: 1.25;
    font-weight: 600;
    /* Eased from -.011em: the display face is narrower than Inter at the same
       size, so the tracking Inter needed reads cramped on it. */
    letter-spacing: -.005em;
}
h1 { letter-spacing: -.02em; }

img { max-width: 100%; }

button, input, select, textarea { font-family: inherit; }

:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 2px; border-radius: 4px; }

::selection { background: var(--brand-100); color: var(--brand-800); }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}

/* Numerals line up in tables, money and stats */
/* Visible to a screen reader, to nothing else. `display:none` would remove it
   from the accessibility tree as well, which is the opposite of the intent.

   Lives here rather than in site.css because it is a base accessibility
   utility, and only one of the six layouts loads site.css — an admin or member
   view reaching for it got a class that did not exist. */
.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.tnum, table.data td.num, table.data th.num, .stat .value, .money { font-variant-numeric: tabular-nums; }

/* ==========================================================================
   App shell — admin, member and staff apps share this chrome
   ========================================================================== */

.shell { display: flex; min-height: 100vh; }

.sidebar {
    width: var(--sidebar-w);
    flex-shrink: 0;
    background: linear-gradient(180deg, var(--brand-800) 0%, var(--brand-900) 100%);
    color: #fff;
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    height: 100vh;
    z-index: 40;
    overflow: hidden;
}

/* The mark's five circles, drifting quietly at the foot of the rail. */
.sidebar::after {
    content: '';
    position: absolute;
    left: -30px;
    bottom: -40px;
    width: 320px;
    height: 160px;
    background-image: var(--mark-circles);
    opacity: .55;
    pointer-events: none;
}

.sidebar-brand {
    padding: 22px 20px 18px;
    border-bottom: 1px solid rgba(255,255,255,.09);
    position: relative;
    z-index: 1;
}

.sidebar-brand img { width: 168px; display: block; }

.sidebar-brand .app-name {
    margin-top: 11px;
    font-size: 10.5px;
    font-weight: 500;
    letter-spacing: .15em;
    text-transform: uppercase;
    color: rgba(255,255,255,.5);
}

.sidebar-nav { padding: 12px 12px; flex: 1; overflow-y: auto; position: relative; z-index: 1; }

.sidebar-nav .nav-label {
    font-size: 10px;
    letter-spacing: .13em;
    text-transform: uppercase;
    color: rgba(255,255,255,.36);
    padding: 15px 12px 6px;
    font-weight: 600;
}

.sidebar-nav a {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 9px 12px;
    border-radius: var(--radius-sm);
    color: rgba(255,255,255,.78);
    font-weight: 400;
    font-size: 14px;
    margin-bottom: 2px;
    transition: background .12s, color .12s;
}

.sidebar-nav a svg { width: 17px; height: 17px; flex-shrink: 0; opacity: .72; }

.sidebar-nav a:hover { background: rgba(255,255,255,.08); color: #fff; }

.sidebar-nav a.active {
    background: rgba(128,152,232,.20);
    color: #fff;
    font-weight: 500;
    box-shadow: inset 3px 0 0 var(--brand-400);
}
.sidebar-nav a.active svg { opacity: 1; }

.sidebar-nav a .count {
    margin-left: auto;
    font-size: 11px;
    font-weight: 600;
    background: rgba(255,255,255,.15);
    padding: 1px 7px;
    border-radius: 99px;
    font-variant-numeric: tabular-nums;
}

.sidebar-foot {
    padding: 14px 16px;
    border-top: 1px solid rgba(255,255,255,.09);
    position: relative;
    z-index: 1;
    background: inherit;
}

.sidebar-user { display: flex; align-items: center; gap: 10px; }

.avatar {
    width: 34px; height: 34px;
    border-radius: 50%;
    background: var(--brand-400);
    color: var(--brand-900);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 12.5px;
    flex-shrink: 0;
    letter-spacing: .02em;
}

.sidebar-user .who { min-width: 0; flex: 1; }
/* Both are <span>, so they must be made block or the role runs on after the
   name instead of sitting under it. */
.sidebar-user .who .name,
.sidebar-user .who .role { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidebar-user .who .name { font-size: 13px; color: #fff; }
.sidebar-user .who .role { font-size: 11px; color: rgba(255,255,255,.45); }

.sidebar-logout { margin-top: 12px; }
.sidebar-logout button {
    width: 100%;
    display: flex; align-items: center; justify-content: center; gap: 8px;
    background: rgba(255,255,255,.06);
    border: 1px solid rgba(255,255,255,.15);
    color: rgba(255,255,255,.82);
    font-size: 13px; font-weight: 500;
    padding: 9px; border-radius: var(--radius-sm); cursor: pointer;
    transition: background-color .12s, color .12s, border-color .12s;
}
.sidebar-logout button:hover { background: rgba(255,255,255,.13); color: #fff; border-color: rgba(255,255,255,.26); }
.sidebar-logout button svg { width: 16px; height: 16px; }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.content { padding: 28px 32px 56px; max-width: 1320px; width: 100%; margin: 0 auto; }

/* Page header */
.page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 24px; flex-wrap: wrap; }

.page-head .eyebrow {
    font-size: 11px;
    letter-spacing: .12em;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--brand-600);
    margin-bottom: 5px;
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

.page-head h1 { font-size: 25px; }
.page-head .sub { color: var(--ink-500); margin-top: 5px; font-size: 14.5px; max-width: 680px; }

.page-actions { display: flex; gap: 10px; align-items: center; flex-shrink: 0; }

/* ==========================================================================
   Components
   ========================================================================== */

/* --- Buttons --- */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    font-size: 14px;
    font-weight: 500;
    padding: 9px 18px;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    cursor: pointer;
    transition: background .12s, border-color .12s, box-shadow .12s, transform .06s, color .12s;
    white-space: nowrap;
    text-decoration: none;
    line-height: 1.4;
}
.btn:active { transform: translateY(1px); }
.btn svg { width: 16px; height: 16px; flex-shrink: 0; }

.btn-primary { background: var(--brand-600); color: #fff; box-shadow: var(--shadow-sm); }
.btn-primary:hover { background: var(--brand-700); color: #fff; }

.btn-secondary { background: var(--surface); color: var(--ink-700); border-color: var(--line); box-shadow: var(--shadow-sm); }
.btn-secondary:hover { border-color: var(--ink-300); color: var(--ink-900); }

.btn-ghost { background: transparent; color: var(--brand-600); }
.btn-ghost:hover { background: var(--brand-50); color: var(--brand-700); }

.btn-danger { background: var(--surface); color: var(--danger-600); border-color: var(--line); }
.btn-danger:hover { background: var(--danger-100); border-color: var(--danger-500); }

.btn-danger-solid { background: var(--danger-600); color: #fff; }
.btn-danger-solid:hover { background: #8f3232; color: #fff; }

.btn-sm { padding: 6px 12px; font-size: 13px; border-radius: 7px; }
.btn-lg { padding: 12px 26px; font-size: 15.5px; border-radius: var(--radius); }

.btn[disabled], .btn.disabled { opacity: .55; cursor: not-allowed; pointer-events: none; }

.btn-group { display: inline-flex; gap: 8px; }

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

.card + .card { margin-top: 20px; }

/* .card + .card only fires between direct siblings — a <form> or .grid-2
   wrapping the first card breaks that adjacency, so give those wrappers the
   same trailing space explicitly. */
form:has(> .card),
form:has(> .grid-2) { margin-bottom: 20px; }
form:has(> .card):last-child,
form:has(> .grid-2):last-child { margin-bottom: 0; }

.card-head {
    padding: 15px 22px;
    border-bottom: 1px solid var(--line);
    background: var(--surface-2);
    /* .card has no overflow:hidden, so the fill has to carry the radius itself
       or its square corners poke out past the card's rounded ones. */
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}

/* A section head's icon. Same 24-grid, 1.7 stroke and currentColor as the
   facility icons, so the two sets are visibly one family. Sized to the cap
   height of the title beside it rather than to a round number. */
.card-head .sec-icon {
    width: 19px; height: 19px; flex: none;
    color: var(--brand-600);
}
.card-head .titled { display: flex; align-items: center; gap: 10px; min-width: 0; }

.card-head h2 { font-size: 16px; }
.card-head .hint { font-size: 13px; color: var(--ink-500); margin-top: 2px; font-weight: 400; }

.card-body { padding: 22px; }
.card-body.flush { padding: 0; }
.card-foot { padding: 15px 22px; border-top: 1px solid var(--line); background: var(--surface-2); border-radius: 0 0 var(--radius-lg) var(--radius-lg); }

/* --- Directory type-ahead, adding somebody to a practice ------------------
   A results list rather than a <datalist>: each row carries a photograph and
   where they already practise, which is how you tell two Dr Naidoos apart —
   and that is exactly the check this control exists to let somebody make. */
.team-results {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    margin: -6px 0 16px;
    overflow: hidden;
    background: var(--surface);
    box-shadow: var(--shadow-sm);
}
.team-result {
    display: flex;
    align-items: center;
    gap: 11px;
    width: 100%;
    padding: 9px 13px;
    border: 0;
    border-bottom: 1px solid var(--line);
    background: none;
    font: inherit;
    text-align: left;
    cursor: pointer;
    color: var(--ink-700);
}
.team-result:last-child { border-bottom: 0; }
.team-result:hover, .team-result:focus-visible { background: var(--brand-50); }
.team-result img {
    width: 32px; height: 32px; flex: none;
    border-radius: 50%; object-fit: cover;
    border: 1px solid var(--line);
}
.team-result .nm { display: block; font-weight: 550; color: var(--ink-900); }
.team-result .at { display: block; font-size: 12.5px; color: var(--ink-500); }

.team-chosen {
    display: flex;
    align-items: center;
    padding: 11px 14px;
    margin-bottom: 16px;
    border: 1px solid var(--brand-100);
    background: var(--brand-50);
    border-radius: var(--radius);
    color: var(--ink-900);
}

/* --- Claim banner ---------------------------------------------------------
   Somebody else put this person's name on a public listing and they are seeing
   it for the first time. It sits above the page's own cards and has to read as
   a different kind of object from them, without reading as an error.

   Deliberately NOT the amber alert treatment used for a lapsed membership:
   --warning-600 on --warning-100 measures 4.10:1, under the 4.5:1 needed for
   body text, and the tone is wrong besides — nothing has gone wrong yet. A
   white ground with a warning rule carries the same weight and puts the
   eyebrow at 4.55:1 and the prose at 16:1. */
.claim {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: inset 3px 0 0 var(--warning-600), var(--shadow-sm);
    padding: 20px 22px 20px 25px;
    margin-bottom: 20px;
}
.claim + .claim { margin-top: -6px; }

/* `.eyebrow` is only ever styled as `.page-head .eyebrow`, so outside a page
   head the class carries no type at all. Restated here rather than borrowed. */
.claim .eyebrow {
    font-size: 11px;
    letter-spacing: .12em;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--warning-600);
    margin-bottom: 6px;
}
.claim h2 { font-size: 16.5px; color: var(--ink-900); margin: 0; }
.claim p { color: var(--ink-700); margin: 8px 0 0; max-width: 62ch; font-size: 14.5px; }

/* The specimen: their name exactly as the public sees it today. Set apart
   because it is evidence, not prose — the one thing on the banner they are
   being asked to check. */
.claim .specimen {
    margin-top: 14px;
    padding: 12px 15px;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    display: flex;
    align-items: center;
    gap: 13px;
}

/* The face the public is being shown. Square-cropped rather than letterboxed:
   the upload is already resized to 800×800, and a portrait that changes shape
   between here and the listing is not the same check. */
.claim .specimen .face {
    width: 52px;
    height: 52px;
    flex: none;
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid var(--line);
    background: var(--surface);
}
.claim .specimen .name { font-weight: 650; color: var(--ink-900); font-size: 15px; }
.claim .specimen .role { color: var(--ink-500); font-size: 13.5px; margin-top: 2px; }

/* The same object asking a question rather than raising one. Nothing has
   happened yet, so the rule takes the brand colour: an amber bar would teach
   members to read "a colleague would like to work with you" as a problem. */
.claim.is-question { box-shadow: inset 3px 0 0 var(--brand-600), var(--shadow-sm); }
.claim.is-question .eyebrow { color: var(--brand-600); }

.claim .toolbar { margin-top: 16px; }
/* The refusal is a real choice, not a discouraged one — it keeps a full-sized
   button. It is secondary to completing the profile, and no further. */
.claim form { display: contents; }

/* --- Social channel inputs -------------------------------------------------
   Each field wears its own mark, so somebody filling the form is looking at the
   glyph the public page will show rather than reading six labels. */
/* --- Composer emoji picker -------------------------------------------------
   Reuses `.canned` / `.canned-menu` for the popover so the two controls in the
   composer foot open the same way; only the contents differ. */
/* Its own classes, not `.canned`. That one is a JavaScript hook as well as a
   style hook — the canned-replies script does `querySelector('.canned')` — so
   borrowing it for the look silently handed the emoji element to that script. */
.emoji-menu { position: relative; }
.emoji-menu > summary { list-style: none; cursor: pointer; font-size: 15px; line-height: 1; }
.emoji-menu > summary::-webkit-details-marker { display: none; }

.emoji-pop {
    position: absolute; bottom: calc(100% + 8px); left: 0;
    width: min(320px, calc(100vw - 40px));
    max-height: 340px; overflow-y: auto;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
    padding: 10px; z-index: 40;
}

.emoji-group + .emoji-group { margin-top: 10px; }
.emoji-group .lbl {
    font-size: 10.5px;
    letter-spacing: .09em;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--ink-400);
    margin-bottom: 4px;
}
.emoji-group .grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; }

.emoji-pick {
    border: 0;
    background: none;
    border-radius: 6px;
    padding: 4px 0;
    font-size: 18px;
    line-height: 1.25;
    cursor: pointer;
}
.emoji-pick:hover { background: var(--brand-50); }
.emoji-pick:focus-visible { outline: 2px solid var(--brand-500); outline-offset: -2px; }

.emoji-note {
    margin-top: 10px;
    padding-top: 8px;
    border-top: 1px solid var(--line);
    font-size: 11.5px;
    color: var(--ink-400);
}
.emoji-note kbd {
    display: inline-block;
    padding: 0 4px;
    border: 1px solid var(--line);
    border-radius: 4px;
    background: var(--surface-2);
    font-family: inherit;
    font-size: 11px;
}

/* --- WhatsApp attachments --------------------------------------------------
   A picture is a tile that opens the lightbox; a file is a card that says what
   clicking it will do, because "open in a viewer" and "download to your
   machine" are different promises and a paperclip makes neither. */
.wa-shot { cursor: zoom-in; border-radius: 8px; overflow: hidden; }
.wa-shot img { display: block; max-width: 100%; height: auto; }

.wa-file {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 11px;
    border: 1px solid var(--line);
    border-radius: 9px;
    background: var(--surface);
    text-decoration: none;
    color: var(--ink-900);
    max-width: 320px;
}
.wa-file:hover { border-color: var(--brand-400); background: var(--brand-50); }
.wa-file .ic {
    flex: none;
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 36px;
    border-radius: 8px;
    background: var(--surface-2);
    color: var(--ink-500);
}
.wa-file .ic svg { width: 20px; height: 20px; }
/* A PDF reads as a PDF at a glance, the way it does in every mail client and in
   WhatsApp itself. The other types share the neutral document glyph, because
   inventing a colour per extension is a system nobody asked for. */
.wa-file.pdf .ic { background: #fdecec; color: #a84043; }   /* --stage-1-deep */
.wa-file .nm {
    /* **`min-width: 0` is what makes the ellipsis work.** A flex item defaults
       to `min-width: auto` and so refuses to shrink below its content — the
       filename simply pushed the card wider than the bubble, the bubble wider
       than the pane, and the pane grew a horizontal scrollbar. `text-overflow`
       cannot do anything for a box that is never too small for its text. */
    flex: 1 1 auto; min-width: 0;
    display: flex; flex-direction: column; gap: 1px;
}
.wa-file .nm b {
    font-size: 13px; font-weight: 500;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wa-file .nm span { font-size: 11px; color: var(--ink-500); }
.wa-file .go {
    flex: none; align-self: center;
    font-size: 11.5px; color: var(--brand-600); font-weight: 600;
}

/* Chosen but not yet sent. */
.wa-attached {
    display: flex; align-items: center; gap: 8px;
    margin: 8px 0 0;
    padding: 6px 8px;
    border: 1px dashed var(--line);
    border-radius: 8px;
    font-size: 13px;
}
.wa-attached .nm { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wa-attached .x {
    border: 0; background: none; cursor: pointer;
    font-size: 18px; line-height: 1; color: var(--ink-400); padding: 0 2px;
}
.wa-attached .x:hover { color: var(--ink-900); }

/* --- WhatsApp reactions ----------------------------------------------------
   The chip row sits under the bubble text and above the timestamp, which is
   where WhatsApp puts it and therefore where somebody looks for it. */
.wa-msg .bubble { position: relative; }

.wa-reactions { display: flex; gap: 4px; margin: 6px 0 2px; }
.wa-reactions .rx {
    display: inline-flex;
    align-items: center;
    padding: 1px 6px;
    border-radius: 99px;
    background: rgba(0, 0, 0, .06);
    font-size: 13px;
    line-height: 1.5;
}
/* Ours is tinted so the two are distinguishable at a glance without a label —
   at most one of each can exist, so a legend would be more chrome than signal. */
.wa-reactions .rx.ours { background: var(--brand-100); }

/* The affordance, **beside the bubble rather than on top of it.**

   It used to sit `top: 2px; right: 6px` inside the bubble, which put it over
   the first line of every message — on a long one it covered the words you were
   reading, and on a short one it collided with the text itself. WhatsApp puts
   it outside, next to the message, and so does this.

   Always in the DOM so it is reachable by tab, never by hover alone. */
.rx-open {
    flex: none; align-self: center;
    width: 26px; height: 26px; margin: 0 2px;
    display: inline-flex; align-items: center; justify-content: center;
    border: 0; border-radius: 50%;
    background: var(--surface);
    box-shadow: var(--shadow-sm);
    font-size: 13px; line-height: 1;
    color: var(--ink-400);
    cursor: pointer;
    opacity: 0;
    transition: opacity .12s ease;
}
/* `order` rather than a second element: the button is written before the
   bubble in both renderers, and it belongs on the **inside** edge — to the
   right of an inbound bubble, to the left of an outbound one — where there is
   room and where it cannot be mistaken for part of the message. */
.rx-open { order: 1; }
.wa-msg.out .rx-open { order: -1; }
.wa-msg:hover .rx-open,
.rx-open:focus-visible { opacity: 1; }
.rx-open:hover { color: var(--brand-600); }
.rx-open:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 2px; opacity: 1; }

/* One row of six and a `+`, which is the shape WhatsApp uses. Wrapping, so the
   expanded set becomes two rows rather than a wider strip that would have to be
   nudged back on screen all over again. */
.rx-sheet {
    position: absolute;
    bottom: 100%;
    left: 0;
    z-index: 5;
    display: flex; flex-wrap: wrap;
    gap: 2px;
    width: max-content; max-width: 280px;
    padding: 5px 6px;
    margin-bottom: 6px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 99px;
    box-shadow: var(--shadow-lg);
}
/* An outbound bubble is against the right of the pane, so its picker hangs from
   the right corner; the inbound one hangs from the left. The JS still measures
   and nudges afterwards — this only means it usually has nothing to do. */
.rx-sheet.from-right { left: auto; right: 0; }
/* The expanded set is a block, not a pill. */
.rx-sheet.wide { border-radius: 14px; }
/* Nothing above it — the first message in a thread — so it opens downward. */
.rx-sheet.below { bottom: auto; top: 100%; margin: 6px 0 0; }

/* A message with nothing in it to show. Said quietly — it is information about
   the message, not part of it. */
.wa-unshowable { font-style: italic; color: var(--ink-500); font-size: 13.5px; }

/* Why a reaction did not go, beside the message it did not go to. */
.rx-note {
    margin-top: 6px;
    padding: 5px 8px;
    border-radius: 7px;
    background: var(--danger-100);
    color: var(--danger-600);
    font-size: 12px; line-height: 1.45;
}

.rx-sheet .rx-more {
    border: 0; background: var(--surface-2);
    width: 30px; height: 30px;
    border-radius: 50%;
    font-size: 16px; line-height: 1; color: var(--ink-500);
    cursor: pointer;
}
.rx-sheet .rx-more:hover { background: var(--brand-100); color: var(--brand-700); }
.rx-sheet .rx-pick {
    border: 0;
    background: none;
    border-radius: 50%;
    width: 30px; height: 30px;
    padding: 0;
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
}
.rx-sheet .rx-pick:hover { background: var(--brand-50); }
/* The one already chosen — tapping it again takes it back. */
.rx-sheet .rx-pick.on { background: var(--brand-100); }
.rx-sheet .rx-pick:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 1px; }

/* --- Badge artwork ---------------------------------------------------------
   The fold map: a 2x2 grid drawn to the real 88:132 panel proportion, so the
   picture on screen and the sheet that prints are the same shape. `aspect-ratio`
   on the cell rather than a fixed height, for the same reason the certificate
   stage uses one — the proportion is the information. */
.badge-map {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
    max-width: 420px;
}
.badge-map .bm-cell {
    aspect-ratio: 88 / 132;
    border: 1px dashed var(--line);
    border-radius: 8px;
    background: var(--surface-2);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    text-align: center;
    font-size: 12px;
    color: var(--ink-500);
    padding: 8px;
}
/* The lower two print inverted. Showing that here costs one transform and saves
   somebody uploading their artwork upside down to compensate. */
.badge-map .bm-cell.flip { transform: rotate(180deg); }
.badge-map .bm-cell em { font-style: normal; font-size: 10px; color: var(--ink-400); }

.type-swatches { display: flex; flex-wrap: wrap; gap: 10px; }
.type-swatch {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 128px;
    padding: 10px 16px;
    border-radius: 6px;
    color: #fff;
    font-weight: 700;
    font-size: 15px;
    letter-spacing: .04em;
}

.social-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; }
.social-fields label {
    display: flex;
    align-items: center;
    gap: 7px;
}
.social-fields label .brand-mark { width: 15px; height: 15px; color: var(--ink-500); flex: none; }

/* --- Single image field ----------------------------------------------------
   Pick or drop, watch it upload, see it, replace or remove. Replaces a bare
   file input that gave no sign anything had happened until the form was saved. */
.image-field .drop {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 26px 18px;
    border: 1.5px dashed var(--line);
    border-radius: var(--radius);
    background: var(--surface-2);
    text-align: center;
    cursor: pointer;
    transition: border-color .15s ease, background .15s ease;
}
.image-field .drop:hover,
.image-field .drop.is-over { border-color: var(--brand-500); background: var(--brand-50); }
.image-field .drop svg { width: 26px; height: 26px; color: var(--ink-400); }
.image-field .drop .t { font-size: 14px; color: var(--ink-700); }
.image-field .drop .t .link { color: var(--brand-600); text-decoration: underline; }
.image-field .drop .d { font-size: 12.5px; color: var(--ink-500); }

/* The input covers the box so the whole area is the control, and stays a real
   focusable input so the keyboard still reaches it. */
.image-field .drop input[type="file"] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}
.image-field .drop:focus-within { border-color: var(--brand-500); box-shadow: 0 0 0 3px var(--brand-100); }

.image-field .shot {
    position: relative;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--surface-2);
}
.image-field .shot img { display: block; width: 100%; max-height: 220px; object-fit: cover; }
.image-field .shot .acts {
    display: flex;
    gap: 8px;
    padding: 10px 12px;
    border-top: 1px solid var(--line);
    background: var(--surface);
}

.image-field progress {
    width: 100%;
    height: 6px;
    margin-top: 10px;
    border: 0;
    border-radius: 99px;
    background: var(--surface-2);
    appearance: none;
}
.image-field progress::-webkit-progress-bar { background: var(--surface-2); border-radius: 99px; }
.image-field progress::-webkit-progress-value { background: var(--brand-600); border-radius: 99px; }
.image-field progress::-moz-progress-bar { background: var(--brand-600); border-radius: 99px; }

.image-field .up-status:not(:empty) {
    margin-top: 7px;
    font-size: 12.5px;
    color: var(--ink-500);
}

/* --- The article editor ---------------------------------------------------
   A toolbar with no font, size or colour control. That is the association's
   rule — type belongs to the site's design — and the shape of the toolbar is
   how a writer learns it without being told. */
.rt { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }

.rt-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    padding: 8px 10px;
    border-bottom: 1px solid var(--line);
    background: var(--surface-2);
    border-radius: var(--radius) var(--radius) 0 0;
    position: sticky;
    top: 0;
    z-index: 5;
}
.rt-bar button {
    min-width: 32px;
    height: 30px;
    padding: 0 8px;
    border: 1px solid transparent;
    border-radius: 6px;
    background: none;
    color: var(--ink-700);
    font: inherit;
    font-size: 13px;
    cursor: pointer;
}
.rt-bar button:hover { background: var(--surface); border-color: var(--line); }
.rt-bar button.is-on { background: var(--brand-600); border-color: var(--brand-600); color: #fff; }
.rt-bar button:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 1px; }
.rt-bar .sep { width: 1px; height: 18px; background: var(--line); margin: 0 4px; }
.rt-bar .rt-status { margin-left: auto; font-size: 12.5px; color: var(--ink-500); }

/* The writing surface carries `.prose`, the same class the public article uses,
   so what somebody types is set the way it will be read. */
.rt-area { padding: 22px 24px; min-height: 420px; outline: none; }
.rt-area:focus { box-shadow: inset 0 0 0 2px var(--brand-100); }
.rt-area figure { margin: 20px 0; }
.rt-area img { max-width: 100%; height: auto; border-radius: var(--radius); }
.rt-area figcaption { font-size: 13px; color: var(--ink-500); margin-top: 6px; }

/* --- Listing performance panel --------------------------------------------
   Three numbers, a shape, and a breakdown. The three are the three steps of one
   journey — found the listing, reached for the phone, wrote — so they read left
   to right as a funnel and are never sorted by size. */
.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 14px;
}

.stat-tile {
    padding: 16px 18px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface-2);
}
.stat-tile .lbl {
    font-size: 11px;
    letter-spacing: .1em;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--ink-500);
}
.stat-tile .num {
    font-size: 30px;
    font-weight: 650;
    line-height: 1.1;
    margin-top: 6px;
    color: var(--ink-900);
    /* These sit in a row and are read against one another. */
    font-variant-numeric: tabular-nums;
}
.stat-tile .delta {
    margin-top: 5px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--ink-500);
}
.stat-tile .delta.up   { color: var(--success-600); }
.stat-tile .delta.down { color: var(--danger-600); }
/* --ink-500, not --ink-400: on --surface-2 that is 4.78:1 against 2.83:1, and
   this is small supporting text rather than decoration. Same reason below for
   the chart's own axis labels. */
.stat-tile .delta .vs  { color: var(--ink-500); font-weight: 400; }
.stat-tile .sub {
    margin-top: 9px;
    font-size: 12.5px;
    color: var(--ink-500);
    line-height: 1.45;
}

/* --- The shape of the period --- */
.stats-chart {
    display: flex;
    align-items: flex-end;
    gap: 3px;
    height: 132px;
    margin-top: 24px;
    padding-bottom: 18px;   /* room for the labels, which sit outside the bars */
    position: relative;
}
/* No track behind the bar. A full-height grey column under every day made the
   chart read as thirty days at the maximum, with the actual data lost inside
   it — and `--brand-100` measures 1.24:1 against white, so the views bar was
   very nearly invisible in its own right. Only the data is drawn now. */
.stats-chart .bar {
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    height: 100%;
}
/* Clicks are drawn *inside* views, not beside them: the same visitors went one
   step further, and two bars side by side would imply two audiences. */
.stats-chart .bar span {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    border-radius: 3px 3px 0 0;
}
/* Both tones have to read on white and against each other: --brand-500 is
   4.12:1 on white, --brand-900 is 14.09:1, and the two are 3.42:1 apart — over
   the 3:1 that WCAG asks of a graphic carrying meaning. --brand-400 (2.62:1)
   and --brand-100 (1.24:1) were both too faint to be the data. */
.stats-chart .bar .v { background: var(--brand-500); }
.stats-chart .bar .c { background: var(--brand-900); }
.stats-chart .bar em {
    position: absolute;
    bottom: -17px;
    left: 0;
    right: 0;
    text-align: center;
    font-style: normal;
    font-size: 10.5px;
    color: var(--ink-500);
    font-variant-numeric: tabular-nums;
}
/* Thirty-one day labels in a row is a grey smear. Every fifth is a scale; the
   rest are in the hover title and the aria-label. Months are only twelve, so
   they all fit. */
.stats-chart[data-unit="day"] .bar:not(:nth-child(5n + 1)) em { visibility: hidden; }

.stats-key {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    margin-top: 6px;
    font-size: 12.5px;
    color: var(--ink-500);
}
.stats-key i {
    display: inline-block;
    width: 10px; height: 10px;
    border-radius: 2px;
    margin-right: 6px;
    vertical-align: -1px;
}
.stats-key .sw-v { background: var(--brand-500); }
.stats-key .sw-c { background: var(--brand-900); }

/* --- What people clicked --- */
.stats-kinds { margin-top: 24px; border-top: 1px solid var(--line); padding-top: 18px; }
.stats-kinds .k-head {
    font-size: 11px;
    letter-spacing: .1em;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--ink-500);
    margin-bottom: 12px;
}
.stats-kinds .k-row {
    display: grid;
    grid-template-columns: 130px 1fr 52px;
    align-items: center;
    gap: 12px;
    padding: 4px 0;
}
.stats-kinds .k-name { font-size: 13.5px; color: var(--ink-700); }
.stats-kinds .k-bar {
    height: 8px;
    border-radius: 4px;
    background: var(--surface-2);
    overflow: hidden;
}
.stats-kinds .k-bar i { display: block; height: 100%; background: var(--brand-500); border-radius: 4px; }
.stats-kinds .k-num {
    text-align: right;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--ink-900);
    font-variant-numeric: tabular-nums;
}

.stats-empty { margin-top: 22px; padding: 22px; text-align: center; background: var(--surface-2); border-radius: var(--radius); }
.stats-empty p { margin: 0 0 6px; font-size: 14px; color: var(--ink-700); }
.stats-empty p:last-child { margin-bottom: 0; font-size: 13px; }

@media (max-width: 560px) {
    .stats-kinds .k-row { grid-template-columns: 104px 1fr 44px; gap: 9px; }
    .stat-tile .num { font-size: 26px; }

    /* Once the two selects wrap onto their own lines they are two objects of
       the same kind in a column, so they take the same width. `.toolbar .select`
       sets `width: auto`, which left them ragged at whatever their longest
       option happened to be. */
    .stats-panel .card-head form { width: 100%; }
    .stats-panel .card-head .select { width: 100%; flex: 1 1 100%; }
}

/* --- Stat tiles --- */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }

/* The same tiles inside a card body, where `.stat-grid`'s bottom margin would
   push the card's own padding out by 24px. Used on the dashboard's WhatsApp
   card and on the Company docs screen. */
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; }

.stat {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 18px 20px;
    box-shadow: var(--shadow-sm);
    position: relative;
    overflow: hidden;
}

.stat .label { font-size: 11.5px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-500); }
.stat .value { font-size: 29px; font-weight: 600; color: var(--ink-900); margin-top: 6px; line-height: 1.1; letter-spacing: -.02em; }
.stat .delta { font-size: 12.5px; color: var(--ink-500); margin-top: 6px; }
.stat .delta strong { color: var(--brand-600); font-weight: 500; }

/* Accents run the life-stage spectrum in order, so a row of tiles reads as
   the mark laid out flat. */
.stat.accent-1 { box-shadow: inset 0 3px 0 var(--stage-1), var(--shadow-sm); }
.stat.accent-2 { box-shadow: inset 0 3px 0 var(--stage-2), var(--shadow-sm); }
.stat.accent-3 { box-shadow: inset 0 3px 0 var(--stage-3), var(--shadow-sm); }
.stat.accent-4 { box-shadow: inset 0 3px 0 var(--stage-4), var(--shadow-sm); }
.stat.accent-5 { box-shadow: inset 0 3px 0 var(--stage-5), var(--shadow-sm); }
.stat.accent-brand { box-shadow: inset 0 3px 0 var(--brand-600), var(--shadow-sm); }

/* --- Forms --- */
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px 20px; }
.form-grid .full { grid-column: 1 / -1; }
.form-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px 20px; }
.form-grid-3 .full { grid-column: 1 / -1; }

/* --- Form sections ---------------------------------------------------------
   For a long form that belongs in one card but is not one question. A section
   is a labelled group: a quiet rule, an optional line icon, a title, and an
   optional sentence saying what the group is for and why we are asking.

   The rule above the title is the whole device. A heading alone floats; a
   heading with a hairline above it and real space around it reads as the start
   of something, which is what somebody scanning a twelve-field form needs.
   Sections stack inside .card-body, so the first one has no rule — it would sit
   directly under the card head's own border. */
.form-section + .form-section { margin-top: 26px; padding-top: 24px; border-top: 1px solid var(--line); }

.form-section > .sec-title {
    display: flex; align-items: center; gap: 9px;
    font-size: 13px; font-weight: 600; color: var(--ink-900);
    letter-spacing: .01em; margin-bottom: 3px;
}
/* The icon carries its own size.
   It used to be sized only by `.card-head .sec-icon` and
   `.form-section > .sec-title .sec-icon`, so the same markup inside an .inset
   matched neither rule — and an inline SVG with a viewBox and no dimensions
   expands to fill whatever contains it. The shield in a review's dispute panel
   rendered about 400px tall. A component that only works in the two places it
   was first used is not a component. */
.sec-icon { width: 17px; height: 17px; flex: none; color: var(--brand-600); }

/* --- Brand icons -----------------------------------------------------------
   A bold glyph on a coloured disc, from the logo's own five circles. A second
   tier to `.sec-icon`, not a replacement: that one is a hairline mark labelling
   a form section, this one carries identity in navigation and on marketing
   pages.

   The size is declared unconditionally and on the element itself. An inline SVG
   with a viewBox and no width expands to fill whatever contains it — which is
   how a section icon once rendered as a 400px shield — and this set is used in
   more places than that one was. */
.b-icon { width: 40px; height: 40px; flex: none; display: block; }

/* The pattern page: every icon in one grid, at the size each set is used at. */
.icon-sheet {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
    gap: 18px 12px;
}
.icon-cell { display: flex; flex-direction: column; align-items: center; gap: 7px; text-align: center; }
.icon-cell code {
    font-family: var(--font-mono); font-size: 11px; color: var(--ink-500);
    overflow-wrap: anywhere; line-height: 1.35;
}
.b-icon.sheet { width: 52px; height: 52px; }
/* The hairline sets are 17px in use and unreadable on a reference sheet, so
   they are shown larger — on the grey disc the brand set uses, which is also
   the quickest way to see that the two families share a grid. */
.icon-cell .icon-plain {
    display: flex; align-items: center; justify-content: center;
    width: 52px; height: 52px; border-radius: 50%; background: var(--surface-2);
    border: 1px solid var(--line);
}
.sec-icon.sheet { width: 26px; height: 26px; }

.form-section > .sec-title .sec-icon { width: 17px; height: 17px; }
.form-section > .sec-note { font-size: 12.5px; color: var(--ink-500); margin: 0 0 14px; max-width: 62ch; }
.form-section > .sec-title:last-child { margin-bottom: 14px; }

/* An inset panel: a group that is genuinely set apart rather than merely next —
   a destructive action, a summary the form is about to act on, a read-only
   block among editable ones. Grey fill, so it reads as a different kind of
   thing and not just another section. */
.inset {
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 16px 18px;
}
.inset + .inset { margin-top: 12px; }
.inset .sec-title { margin-bottom: 8px; }

.field label { display: block; font-size: 13px; font-weight: 500; color: var(--ink-900); margin-bottom: 6px; }
.field .req { color: var(--danger-500); }
.field .help { font-size: 12.5px; color: var(--ink-500); margin-top: 5px; }
.field .error-text { font-size: 12.5px; color: var(--danger-600); margin-top: 5px; font-weight: 500; }

/* --- Error summary ---------------------------------------------------------
   What went wrong, at the top of the form, before the fields.

   A message beside a field only helps once you have found the field. On a long
   form the failure is often above or below the fold, so the summary names every
   problem in one place and each line jumps to the control it belongs to. The
   inline messages stay — the summary is how you find them, not a replacement.

   role="alert" and tabindex="-1" so it can be focused on load: a screen reader
   announces it, and everyone else is scrolled to it rather than left looking at
   a form that silently refused. */
.error-summary {
    /* Focusing the summary scrolls it to the top of the viewport, which on the
       public site is underneath the sticky header. Without this the first line
       of "There are 2 things to fix" is clipped by the thing meant to help. */
    scroll-margin-top: 96px;
    border: 1px solid var(--danger-500);
    border-left: 4px solid var(--danger-600);
    background: var(--danger-100);
    border-radius: var(--radius);
    padding: 16px 18px;
    margin-bottom: 20px;
}
.error-summary:focus { outline: 2px solid var(--danger-600); outline-offset: 2px; }
.error-summary .t {
    display: block; font-weight: 600; color: var(--danger-600);
    font-size: 15px; margin-bottom: 8px;
}
.error-summary ul { margin: 0; padding-left: 18px; }
.error-summary li + li { margin-top: 4px; }
.error-summary a {
    color: var(--danger-600); font-weight: 500;
    text-decoration: underline; text-underline-offset: 2px;
}
.error-summary a:hover { color: var(--ink-900); }

/* A field the summary has just jumped to, so the landing is obvious rather than
   leaving the reader to work out which of twelve fields was meant. */
.field:target,
.field.is-flagged { scroll-margin-top: 90px; }
.field.is-flagged > label { color: var(--danger-600); }

.input, .select, .textarea {
    width: 100%;
    font-size: 14.5px;
    color: var(--ink-900);
    background: var(--field-bg);
    border: 1px solid var(--ink-300);
    border-radius: var(--radius-sm);
    padding: 9px 12px;
    transition: border-color .12s, box-shadow .12s, background-color .12s;
}

/* Fields sit recessed until focused, then lift to white — it makes the active
   field unmistakable without a heavy border. */
.input:focus, .select:focus, .textarea:focus {
    outline: none;
    background: var(--surface);
    border-color: var(--brand-600);
    box-shadow: 0 0 0 3px rgba(58, 93, 168, .14);
}

.input.invalid, .select.invalid, .textarea.invalid { border-color: var(--danger-500); }
.input[readonly], .input[disabled] { color: var(--ink-500); cursor: default; }

.textarea { min-height: 110px; resize: vertical; line-height: 1.6; }

.select {
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23626d80' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    padding-right: 34px;
}

.input-group { display: flex; }
.input-group .prefix {
    display: inline-flex; align-items: center; padding: 0 12px;
    background: var(--field-bg); border: 1px solid var(--ink-300); border-right: 0;
    border-radius: var(--radius-sm) 0 0 var(--radius-sm);
    font-size: 14px; color: var(--ink-500); white-space: nowrap;
}
.input-group .input { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }

/* `.field label { display: block; margin-bottom: 6px }` is declared above this
   and, at one class plus one element, has exactly the same specificity as a
   bare `.checkbox-row` — so inside a .field it won and every radio and
   checkbox stacked above its own text instead of sitting beside it. Qualifying
   with the element ties the specificity, and this rule is later in the file, so
   it wins. The margin has to be reset for the same reason. */
.checkbox-row,
label.checkbox-row {
    display: flex;
    align-items: center;
    gap: 9px;
    font-size: 14px;
    font-weight: 400;
    margin-bottom: 0;
    cursor: pointer;
}
.checkbox-row input { width: 16px; height: 16px; accent-color: var(--brand-600); flex-shrink: 0; }
.checkbox-row + .checkbox-row { margin-top: 7px; }

/* A group of radios or checkboxes reads as one answer, not as several fields,
   so it gets the same inset treatment as any other grouped thing. */
.field > .checkbox-row:first-of-type { margin-top: 2px; }

/* Password field with show/hide toggle */
.pw-wrap { position: relative; }
.pw-wrap .input { padding-right: 44px; }
.pw-toggle {
    position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
    background: none; border: 0; padding: 6px; cursor: pointer;
    color: var(--ink-500); display: flex; border-radius: 6px;
}
.pw-toggle:hover { color: var(--brand-600); background: var(--field-bg); }
.pw-toggle svg { width: 18px; height: 18px; }

.form-foot { display: flex; align-items: center; justify-content: flex-end; gap: 12px; padding-top: 20px; margin-top: 22px; border-top: 1px solid var(--line); }
.form-foot .left { margin-right: auto; }

/* Radio / checkbox cards */
.choice-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px; }

.choice {
    position: relative;
    display: block;
    border: 1.5px solid var(--line);
    border-radius: var(--radius);
    padding: 14px 16px;
    cursor: pointer;
    background: var(--surface);
    transition: border-color .12s, box-shadow .12s, background-color .12s;
}
.choice:hover { border-color: var(--ink-300); }
.choice input { position: absolute; opacity: 0; }
.choice .t { font-weight: 500; color: var(--ink-900); font-size: 14.5px; display: flex; align-items: center; gap: 8px; }
.choice .d { font-size: 12.5px; color: var(--ink-500); margin-top: 4px; }
.choice .p { font-size: 17px; font-weight: 600; color: var(--brand-700); margin-top: 8px; letter-spacing: -.01em; }
.choice:has(input:checked) {
    border-color: var(--brand-600);
    box-shadow: 0 0 0 3px rgba(58, 93, 168, .12);
    background: var(--brand-50);
}
.choice:has(input:focus-visible) { outline: 2px solid var(--brand-500); outline-offset: 2px; }

/* --- The form panel --------------------------------------------------------
   A long form is a working surface, not more page. It sits on the grey so it
   reads as a distinct place to do something, and the controls invert: white
   inputs on grey, rather than the grey-on-white used inside an ordinary card.
   That inversion is the point — on a grey ground a white field reads as a slot
   waiting to be filled, which a grey field on white never quite does. */
.form-panel {
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 26px 26px 0;
}

.form-panel .input,
.form-panel .select,
.form-panel .textarea {
    background: var(--surface);
    border-color: var(--line);
}
.form-panel .input:focus,
.form-panel .select:focus,
.form-panel .textarea:focus { border-color: var(--brand-600); }

/* The panel's own foot: a step darker, full-bleed, so the form has a visible
   end and the action sits on it rather than floating after the last field. */
.form-panel .form-foot {
    margin: 28px -26px 0;
    padding: 18px 26px;
    background: var(--mist);
    border-top: 1px solid var(--line);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    display: flex; justify-content: flex-end; gap: 10px; flex-wrap: wrap;
}

@media (max-width: 560px) {
    .form-panel { padding: 18px 16px 0; }
    .form-panel .form-foot { margin: 22px -16px 0; padding: 15px 16px; }
    .form-panel .form-foot .btn { width: 100%; }
}

/* --- Ticket options --------------------------------------------------------
   A price is the thing being compared, so it gets its own column and the
   largest type in the block. Stacked under a description it was just another
   line of the paragraph. */
.choice-grid.ticket-list { grid-template-columns: 1fr; gap: 10px; }

.choice.ticket {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 20px;
    padding: 16px 20px;
    border-width: 1.5px;
}
.choice.ticket .ticket-main { min-width: 0; }
.choice.ticket .t { font-size: 16px; font-weight: 600; flex-wrap: wrap; }
.choice.ticket .d { font-size: 13px; margin-top: 5px; line-height: 1.5; }
.choice.ticket .d.is-reason { color: var(--danger-600); font-weight: 500; }
.choice.ticket .d.is-scarce { color: var(--warning-600); font-weight: 500; }

.choice.ticket .ticket-price { text-align: right; white-space: nowrap; }
.choice.ticket .ticket-price .p {
    display: block;
    font-size: 21px; font-weight: 600; color: var(--brand-700);
    letter-spacing: -.02em; margin: 0;
    font-variant-numeric: tabular-nums;
}
.choice.ticket .ticket-price .was {
    display: block; font-size: 12px; color: var(--ink-500); margin-top: 3px; font-weight: 400;
}
.choice.ticket.is-unavailable { opacity: .6; }

/* Selected reads as chosen, not merely hovered: a heavier border, a tint, and
   a ring. On a grey panel a white card needs all three to look picked. */
.choice.ticket:has(input:checked) {
    border-color: var(--brand-600);
    background: var(--brand-50);
    box-shadow: 0 0 0 3px rgba(58, 93, 168, .14);
}

@media (max-width: 560px) {
    .choice.ticket { grid-template-columns: 1fr; gap: 10px; padding: 14px 16px; }
    .choice.ticket .ticket-price { text-align: left; }
    .choice.ticket .ticket-price .p { font-size: 19px; }
}

/* --- Tables --- */
.table-wrap { overflow-x: auto; }

table.data { width: 100%; border-collapse: collapse; font-size: 14px; }

table.data th {
    text-align: left;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-500);
    padding: 12px 16px;
    border-bottom: 1px solid var(--line);
    background: var(--surface-2);
    white-space: nowrap;
}

table.data td { padding: 13px 16px; border-bottom: 1px solid var(--line); vertical-align: middle; }
table.data tr:last-child td { border-bottom: 0; }
table.data tbody tr { transition: background .1s; }
table.data tbody tr:hover { background: var(--brand-50); }
table.data td.num, table.data th.num { text-align: right; }
table.data .row-title { font-weight: 500; color: var(--ink-900); }
table.data .row-sub { font-size: 12.5px; color: var(--ink-500); margin-top: 1px; }
table.data td.actions { text-align: right; white-space: nowrap; }
table.data td.tight { width: 1%; white-space: nowrap; }

/* --- Expanded registration ------------------------------------------------
   One attendee opened out inside the registrations table: everything on the
   registration, the three ways to send the confirmation again, and the history.

   The panel sits in a row of its own that follows the attendee's row, so the
   table's own columns are not disturbed by it. Grey fill and an inset shadow
   because it is a layer *underneath* the row that opened it, not another card
   floating on top — the same reasoning as `.inset`, applied at table width. */
tr.reg-row > td { padding: 0; background: var(--surface-2); border-bottom: 1px solid var(--line); }
tr.reg-row:hover > td { background: var(--surface-2); }   /* not a row you click */

.reg-toggle { display: inline-flex; align-items: center; gap: 6px; }
.reg-toggle .chev { width: 14px; height: 14px; transition: transform .15s ease; }
.reg-toggle[aria-expanded="true"] .chev { transform: rotate(180deg); }
.reg-toggle[aria-expanded="true"] { background: var(--brand-600); color: #fff; border-color: var(--brand-600); }

.reg-loading { padding: 24px 20px; font-size: 13px; color: var(--ink-500); }
.reg-loading.is-bad { color: var(--danger-600); }

.reg-panel { padding: 20px; box-shadow: inset 0 1px 0 var(--line); }
.reg-grid { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: 16px; }

/* Each block is a white card on the grey: the panel is the section, and these
   are the groups inside it. */
.reg-block {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 14px 16px;
}
.reg-block + .reg-block { margin-top: 12px; }
.reg-block > .sec-title { margin-bottom: 10px; }

/* Label / value pairs. Two columns on a fixed label width so the values line up
   down the block; the label wraps rather than squeezing the value. */
.kv { display: grid; grid-template-columns: 108px minmax(0, 1fr); gap: 7px 14px; margin: 0; font-size: 13.5px; }
.kv dt { color: var(--ink-500); font-size: 12.5px; line-height: 1.5; padding-top: 1px; }
.kv dd { margin: 0; color: var(--ink-900); min-width: 0; overflow-wrap: anywhere; }
.kv dd .muted { color: var(--ink-400); }
.kv .badge { vertical-align: -2px; }

/* A registration question is a sentence, not a field name: "How did you hear
   about this event?" does not belong in a 108px label column beside its answer.
   Stacked, so the question reads and the answer sits under it. */
.kv-stack { grid-template-columns: minmax(0, 1fr); gap: 0; }
.kv-stack dt { padding-top: 10px; }
.kv-stack dt:first-child { padding-top: 0; }
.kv-stack dd { margin-top: 2px; }

.reg-notes { margin: 0; font-size: 13.5px; color: var(--ink-700); white-space: pre-line; }
.reg-hint { margin: 10px 0 0; font-size: 12px; color: var(--ink-500); line-height: 1.55; }
.reg-block > .reg-hint:first-of-type { margin-top: 0; margin-bottom: 10px; }

/* --- The three channels ---
   Quiet buttons, coloured only where colour means something. The channel mark
   carries the identity; the state line under the name carries the fact the
   person pressing it actually needs — when this channel last got through. */
.chan-list { display: flex; flex-direction: column; gap: 8px; }
.chan { margin: 0; }
.chan-btn {
    display: flex; align-items: center; gap: 11px; width: 100%;
    padding: 9px 11px; text-align: left; cursor: pointer;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius-sm); font: inherit; color: var(--ink-900);
    transition: border-color .12s, box-shadow .12s, background .12s;
}
.chan-btn:hover:not(:disabled) { border-color: var(--ink-300); box-shadow: var(--shadow-sm); }
.chan-btn:disabled { opacity: .55; cursor: not-allowed; }

.chan-mark {
    display: flex; align-items: center; justify-content: center; flex: none;
    width: 30px; height: 30px; border-radius: 8px;
}
.chan-mark svg { width: 17px; height: 17px; }
.chan-email    { background: var(--brand-50);      color: var(--brand-600); }
.chan-whatsapp { background: var(--success-100);   color: var(--success-600); }
.chan-sms      { background: var(--stage-5-soft);  color: var(--stage-5-figure); }

.chan-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.chan-name { font-size: 13.5px; font-weight: 500; }
.chan-state { font-size: 12px; color: var(--ink-400); }
.chan-state.is-good { color: var(--success-600); }
.chan-state.is-bad  { color: var(--danger-600); font-weight: 500; }
.chan-state.is-busy { color: var(--warning-600); font-weight: 500; }
.chan-go { flex: none; width: 15px; height: 15px; color: var(--ink-300); }
.chan-go svg { width: 100%; height: 100%; }
.chan-btn:hover:not(:disabled) .chan-go { color: var(--ink-500); }
.chan-error { margin: 5px 0 0 41px; font-size: 11.5px; color: var(--danger-600); line-height: 1.45; }

/* --- History ---
   A rail with a dot per entry. The dot's colour is the only place the outcome
   is encoded twice, and it is what makes a failed send findable in a column of
   otherwise identical rows. */
.reg-timeline { list-style: none; margin: 0; padding: 0 0 0 18px; position: relative; }
.reg-timeline::before {
    content: ''; position: absolute; left: 4px; top: 5px; bottom: 5px;
    width: 1px; background: var(--line);
}
.reg-timeline li { position: relative; padding: 0 0 13px; }
.reg-timeline li:last-child { padding-bottom: 0; }
.reg-timeline li::before {
    content: ''; position: absolute; left: -18px; top: 4px;
    width: 9px; height: 9px; border-radius: 50%;
    background: var(--ink-300); box-shadow: 0 0 0 3px var(--surface);
}
.reg-timeline li.tone-good::before  { background: var(--success-600); }
.reg-timeline li.tone-bad::before   { background: var(--danger-600); }
.reg-timeline li.tone-warn::before  { background: var(--warning-600); }
.reg-timeline li.tone-brand::before { background: var(--brand-600); }

.tl-label  { font-size: 13px; font-weight: 500; color: var(--ink-900); }
.tl-meta   { font-size: 11.5px; color: var(--ink-400); margin-top: 1px; }
.tl-detail { font-size: 12px; color: var(--ink-500); margin-top: 2px; overflow-wrap: anywhere; }

.reg-foot {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line);
}
.reg-foot .spacer { flex: 1; }
.reg-foot form { margin: 0; }
/* The badge-type select sits in a row of `btn-sm` controls, so it is sized to
   match them rather than to the full-height `.select` used inside forms. */
.reg-foot .select { padding: 5px 28px 5px 10px; font-size: 13px; height: auto; }

/* One column before the two would each be too narrow to hold a label and its
   value on one line. */
@media (max-width: 900px) {
    .reg-grid { grid-template-columns: minmax(0, 1fr); }
    .reg-side { margin-top: 12px; }
}
@media (max-width: 560px) {
    .reg-panel { padding: 14px; }
    .kv { grid-template-columns: minmax(0, 1fr); gap: 2px; }
    .kv dt { padding-top: 8px; }
    .kv dt:first-child { padding-top: 0; }
}

/* --- Badges --- */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 11.5px;
    font-weight: 500;
    letter-spacing: .02em;
    padding: 3px 9px;
    border-radius: 99px;
    white-space: nowrap;
}
.badge .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.badge svg { width: 12px; height: 12px; }

.badge-brand  { background: var(--brand-100);   color: var(--brand-700); }
.badge-navy   { background: #e0e5ee;            color: var(--brand-800); }
.badge-green  { background: var(--success-100); color: var(--success-600); }
.badge-amber  { background: var(--warning-100); color: var(--warning-600); }
.badge-red    { background: var(--danger-100);  color: var(--danger-600); }
.badge-grey   { background: #eceff3;            color: var(--ink-500); }
.badge-1 { background: var(--stage-1-soft); color: var(--stage-1-deep); }
.badge-2 { background: var(--stage-2-soft); color: var(--stage-2-deep); }
.badge-3 { background: var(--stage-3-soft); color: var(--stage-3-deep); }
.badge-4 { background: var(--stage-4-soft); color: var(--stage-4-deep); }
.badge-5 { background: var(--stage-5-soft); color: var(--stage-5-deep); }

/* Verified-member mark — the directory's core trust signal */
.badge-verified { background: var(--success-100); color: var(--success-600); font-weight: 600; }

/* --- Flash messages --- */
.flash-stack { margin-bottom: 20px; display: grid; gap: 10px; }

.flash {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 16px;
    border-radius: var(--radius);
    font-size: 14px;
    border: 1px solid;
    animation: flash-in .25s ease;
}
@keyframes flash-in { from { opacity: 0; transform: translateY(-4px); } }

.flash-success { background: var(--success-100); border-color: #c7e0a8; color: var(--success-600); }
.flash-error   { background: var(--danger-100);  border-color: #eec4c4; color: var(--danger-600); }
.flash-info    { background: var(--brand-100);   border-color: #c3d3f2; color: var(--brand-700); }
.flash-warn    { background: var(--warning-100); border-color: #f0dcae; color: var(--warning-600); }

/* The same component used inline on a page rather than in the transient stack:
   a standing explanation of the state a screen is in, so it must not animate in
   or read as something that will disappear on its own. */
.flash.inline { animation: none; margin-bottom: 20px; }

.flash button { margin-left: auto; background: none; border: 0; cursor: pointer; color: inherit; opacity: .6; font-size: 16px; line-height: 1; padding: 2px; }
.flash button:hover { opacity: 1; }

/* --- Empty states --- */
.empty { text-align: center; padding: 56px 24px; color: var(--ink-500); }
.empty .glyph {
    width: 56px; height: 56px;
    margin: 0 auto 16px;
    border-radius: 50%;
    background: var(--brand-50);
    color: var(--brand-600);
    display: flex; align-items: center; justify-content: center;
}
.empty .glyph svg { width: 26px; height: 26px; }
.empty h3 { font-size: 16px; margin-bottom: 6px; color: var(--ink-900); }
.empty p { font-size: 13.5px; max-width: 380px; margin: 0 auto 18px; }

/* --- Toolbar (search + filters above tables) --- */
.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.toolbar .input, .toolbar .select { width: auto; }
.toolbar .search { min-width: 240px; }
.toolbar .spacer { flex: 1; }

/* --- Pagination --- */
.pager { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; font-size: 13px; color: var(--ink-500); flex-wrap: wrap; }
.pager .pages { display: flex; gap: 4px; }
.pager a, .pager span.cur { padding: 6px 11px; border-radius: 7px; font-weight: 500; font-size: 13px; }
.pager a:hover { background: var(--brand-50); }
.pager span.cur { background: var(--brand-600); color: #fff; }
.pager .gap { padding: 6px 4px; }

/* --- Wizard steps --- */
.steps { display: flex; margin-bottom: 26px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-sm); }

.step {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 18px;
    font-size: 13.5px;
    color: var(--ink-400);
    border-right: 1px solid var(--line);
}
.step:last-child { border-right: 0; }
.step .n {
    width: 24px; height: 24px; border-radius: 50%;
    border: 1.5px solid var(--ink-300);
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 12px; font-weight: 600; flex-shrink: 0;
}
.step.done { color: var(--ink-500); }
.step.done .n { background: var(--success-100); border-color: var(--success-600); color: var(--success-600); }
.step.current { color: var(--brand-800); font-weight: 500; background: var(--brand-50); }
.step.current .n { background: var(--brand-600); border-color: var(--brand-600); color: #fff; }
a.step:hover { background: var(--brand-50); }

/* --- Progress --- */
.progress { height: 8px; border-radius: 99px; background: var(--line); overflow: hidden; }
.progress > span { display: block; height: 100%; background: linear-gradient(90deg, var(--brand-600), var(--brand-400)); border-radius: 99px; transition: width .4s ease; }

/* Profile-completeness meter shifts colour as it fills, so "nearly there"
   reads at a glance without extra copy. */
.meter { display: flex; align-items: center; gap: 12px; }
.meter .progress { flex: 1; }
.meter .pct { font-size: 13px; font-weight: 600; color: var(--ink-900); font-variant-numeric: tabular-nums; min-width: 38px; text-align: right; }
.meter.low  .progress > span { background: linear-gradient(90deg, var(--stage-2-deep), var(--stage-2)); }
.meter.mid  .progress > span { background: linear-gradient(90deg, var(--stage-3-deep), var(--stage-3)); }
.meter.high .progress > span { background: linear-gradient(90deg, var(--success-600), var(--stage-4)); }

/* --- Modal --- */
.modal-backdrop {
    position: fixed; inset: 0; background: rgba(27, 42, 79, .45);
    display: flex; align-items: center; justify-content: center;
    z-index: 100; padding: 20px;
    animation: fade-in .15s ease;
    backdrop-filter: blur(2px);
}
@keyframes fade-in { from { opacity: 0; } }

.modal {
    background: var(--surface); border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg); width: 100%; max-width: 480px;
    animation: pop-in .18s ease;
}
@keyframes pop-in { from { opacity: 0; transform: scale(.97) translateY(6px); } }

.modal .modal-head { padding: 18px 22px 0; }
.modal .modal-head h3 { font-size: 17px; }
.modal .modal-body { padding: 12px 22px 4px; color: var(--ink-500); font-size: 14px; }
.modal .modal-foot { padding: 18px 22px 20px; display: flex; justify-content: flex-end; gap: 10px; }

/* --- Dropdown menu --- */
.menu-wrap { position: relative; display: inline-block; }
.menu {
    position: absolute; right: 0; top: calc(100% + 6px);
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius); box-shadow: var(--shadow-lg);
    min-width: 190px; padding: 6px; z-index: 60;
}
.menu a, .menu button {
    display: flex; width: 100%; text-align: left; align-items: center; gap: 9px;
    padding: 8px 11px; font-size: 13.5px; color: var(--ink-700);
    background: none; border: 0; border-radius: 7px; cursor: pointer;
}
.menu a:hover, .menu button:hover { background: var(--brand-50); color: var(--brand-800); }
.menu .danger { color: var(--danger-600); }
.menu .danger:hover { background: var(--danger-100); color: var(--danger-600); }
.menu hr { border: 0; border-top: 1px solid var(--line); margin: 5px 4px; }
.menu svg { width: 15px; height: 15px; }

/* --- Tabs --- */
/* Scrollable, but without the bar.

   `overflow-x: auto` is here so a six-tab strip still reaches its last tab on a
   phone. The cost is that macOS with "Show scroll bars: Always" paints a track
   under the tabs on a 1900px-wide screen where nothing is overflowing — which is
   what a scrollbar under five tabs in a mostly-empty card head was.

   `.scroll-strip` is the shared answer for a *navigation* strip: it keeps the
   scrolling and drops the chrome. Deliberately not applied to `.table-wrap` —
   a data table wider than its card genuinely has more to the right, and there
   the bar is the only thing that says so. */
.scroll-strip { -ms-overflow-style: none; scrollbar-width: none; }
.scroll-strip::-webkit-scrollbar { width: 0; height: 0; display: none; }

.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line); margin-bottom: 22px; overflow-x: auto;
        -ms-overflow-style: none; scrollbar-width: none; }
.tabs::-webkit-scrollbar { width: 0; height: 0; display: none; }
.tabs a {
    padding: 10px 16px; font-size: 14px; font-weight: 500; color: var(--ink-500);
    border-bottom: 2px solid transparent; margin-bottom: -1px; white-space: nowrap;
}
.tabs a:hover { color: var(--brand-800); }
.tabs a.active { color: var(--brand-600); border-bottom-color: var(--brand-600); }

/* --- Definition rows --- */
.def { display: grid; grid-template-columns: 190px 1fr; gap: 10px 18px; font-size: 14px; }
.def dt { color: var(--ink-500); }
.def dd { color: var(--ink-900); }

/* --- Sparkline --- */
.spark { display: flex; align-items: flex-end; gap: 3px; height: 42px; }
.spark i { flex: 1; min-width: 4px; background: var(--brand-100); border-radius: 3px 3px 0 0; position: relative; }
.spark i > b { position: absolute; bottom: 0; left: 0; right: 0; background: var(--brand-500); border-radius: 3px 3px 0 0; display: block; }

/* ==========================================================================
   Auth — split panel carrying the mark's circles
   ========================================================================== */

.auth-shell { display: flex; min-height: 100vh; }

.auth-brand {
    flex: 1.1;
    background: linear-gradient(160deg, var(--brand-800) 0%, var(--brand-900) 72%);
    color: #fff;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: 48px 56px;
    position: relative;
    overflow: hidden;
}

/* Oversized, softly-blurred echo of the five discs */
.auth-brand::before {
    content: '';
    position: absolute;
    left: -8%; right: -8%; top: 38%;
    height: 420px;
    background-image: var(--mark-circles);
    opacity: .5;
    filter: blur(2px);
    pointer-events: none;
}

.auth-brand img { width: 220px; position: relative; z-index: 1; }

.auth-brand .pitch { position: relative; z-index: 1; max-width: 440px; }
.auth-brand .pitch h2 { color: #fff; font-size: 30px; line-height: 1.25; font-weight: 600; letter-spacing: -.022em; }
.auth-brand .pitch h2 em { font-style: normal; color: var(--brand-400); }
.auth-brand .pitch p { margin-top: 14px; color: rgba(255,255,255,.66); font-size: 15px; }

.auth-brand .foot { position: relative; z-index: 1; font-size: 12.5px; color: rgba(255,255,255,.42); }

.auth-panel { flex: 1; display: flex; align-items: center; justify-content: center; padding: 40px 24px; background: var(--surface); }

.auth-box { width: 100%; max-width: 384px; }
.auth-box .auth-logo { width: 200px; margin-bottom: 26px; display: none; }
.auth-box h1 { font-size: 22px; margin-bottom: 6px; }
.auth-box .sub { color: var(--ink-500); font-size: 14px; margin-bottom: 26px; }
.auth-box .field { margin-bottom: 16px; }
.auth-box .btn { width: 100%; margin-top: 6px; }
.auth-box .alt { margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--line); font-size: 13.5px; color: var(--ink-500); text-align: center; }
/* A second line inside the same block: a second `.alt` would draw a second
   rule and put 42px between two sentences that belong together. */
.auth-box .alt .alt-more { margin-top: 8px; }

/* ==========================================================================
   Error page
   ========================================================================== */

.err-wrap { max-width: 500px; margin: 12vh auto; padding: 0 20px; text-align: center; }
.err-wrap .code { font-size: 56px; font-weight: 700; color: var(--brand-100); line-height: 1; letter-spacing: -.03em; }
.err-wrap h1 { font-size: 21px; margin: 12px 0 8px; }
.err-wrap p { color: var(--ink-500); font-size: 14.5px; }
.err-wrap .btn { margin-top: 22px; }

/* ==========================================================================
   Utilities
   ========================================================================== */

.muted { color: var(--ink-500); }
.small { font-size: 12.5px; }
.mono { font-family: var(--font-mono); font-size: 12.5px; }
.strong { font-weight: 500; color: var(--ink-900); }
.mt-0 { margin-top: 0; } .mt-1 { margin-top: 8px; } .mt-2 { margin-top: 16px; } .mt-3 { margin-top: 24px; }
.mb-1 { margin-bottom: 8px; } .mb-2 { margin-bottom: 16px; } .mb-3 { margin-bottom: 24px; }
.flex { display: flex; align-items: center; gap: 10px; }
.flex-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.flex-wrap { flex-wrap: wrap; }
.right { margin-left: auto; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px; }
.grid-2:last-child { margin-bottom: 0; }
.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 20px; }
.w-100 { width: 100%; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }
.hidden { display: none !important; }
.divider { border: 0; border-top: 1px solid var(--line); margin: 22px 0; }

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width: 980px) {
    .grid-2 { grid-template-columns: 1fr; }
    .auth-brand { display: none; }
    .auth-box .auth-logo { display: block; }
    .form-grid-3 { grid-template-columns: 1fr; }
}

@media (max-width: 860px) {
    .shell { flex-direction: column; }
    .sidebar { width: 100%; height: auto; position: static; flex-direction: row; align-items: center; flex-wrap: wrap; }
    .sidebar::after { display: none; }
    .sidebar-brand { padding: 12px 16px; border-bottom: 0; }
    .sidebar-brand img { width: 132px; }
    .sidebar-brand .app-name { display: none; }
    .sidebar-nav { display: flex; overflow-x: auto; padding: 0 10px 10px; flex: 1 1 100%; order: 3;
                   -ms-overflow-style: none; scrollbar-width: none; }
    .sidebar-nav::-webkit-scrollbar { width: 0; height: 0; display: none; }
    .sidebar-nav .nav-label { display: none; }
    .sidebar-nav a { white-space: nowrap; }
    .sidebar-foot { border-top: 0; margin-left: auto; padding: 10px 16px; }
    .sidebar-logout { margin-top: 0; }
    .sidebar-user .who { display: none; }
    .content { padding: 20px 16px 40px; }
    .form-grid { grid-template-columns: 1fr; }
    .def { grid-template-columns: 1fr; gap: 2px 0; }
    .def dt { margin-top: 10px; }
    .steps { flex-direction: column; }
    .step { border-right: 0; border-bottom: 1px solid var(--line); }
    .step:last-child { border-bottom: 0; }
    .page-head h1 { font-size: 22px; }
}

/* --- Email designer -------------------------------------------------------
   The builder is a full-viewport tool, not a page inside the admin shell: it
   needs every pixel, and a sidebar next to a canvas that already has its own
   panels reads as clutter. The bar keeps the way back and the save state
   visible, which is the only chrome that earns its space here. */
.editor-body { height: 100vh; overflow: hidden; background: var(--brand-900); }
.editor-shell { display: flex; flex-direction: column; height: 100vh; }

.editor-bar {
    display: flex; align-items: center; gap: 14px;
    padding: 0 16px; height: 52px; flex-shrink: 0;
    background: linear-gradient(180deg, var(--brand-800) 0%, var(--brand-900) 100%); color: #fff;
    border-bottom: 1px solid rgba(255, 255, 255, .12);
}
.editor-bar .name { font-size: 14.5px; font-weight: 500; }
.editor-bar .status { font-size: 12.5px; color: rgba(255, 255, 255, .55); font-variant-numeric: tabular-nums; }
.editor-bar .status.saved { color: var(--stage-4); }
.editor-bar .status.failed { color: #ffb4b4; }
.editor-bar .right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.editor-bar .btn-ghost { color: #fff; border-color: rgba(255, 255, 255, .25); }
.editor-bar .btn-ghost:hover { background: rgba(255, 255, 255, .1); }

#unlayer-editor { flex: 1; min-height: 0; }

/* If the builder cannot be reached — offline, or a network that blocks the CDN —
   the admin still needs a way out and a way to edit, so the fallback is part of
   the page rather than an error state bolted on. */
.editor-offline { flex: 1; display: flex; align-items: center; justify-content: center; padding: 40px; }
.editor-offline .card { max-width: 620px; width: 100%; }

/* A stat that links somewhere lifts on hover; one that does not stays put, so
   the difference is felt before it is read. */
a.stat { text-decoration: none; transition: box-shadow .14s, transform .14s; }
a.stat:hover { transform: translateY(-1px); box-shadow: var(--shadow-md); }

/* --- WhatsApp inbox -------------------------------------------------------
   A two-pane layout that fills the viewport rather than scrolling the page:
   a conversation is read from the bottom, so the list of messages scrolls
   inside its own pane and the composer stays where the hand expects it. */
.wa-shell {
    display: grid;
    grid-template-columns: 320px 1fr;
    height: calc(100vh - 190px);
    min-height: 460px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}

.wa-list {
    border-right: 1px solid var(--line);
    background: #fbfcfd;
    /* The list scrolls; its tabs stay put. Without the column being a flex
       box the tab strip scrolled away with the conversations. */
    display: flex; flex-direction: column; min-height: 0;
}
.wa-list .wa-threads { flex: 1; min-height: 0; overflow-y: auto; }

/* The inbox's own tab strip.

   The shared `.tabs` is sized for a full-width page and put 454px of tabs into
   a 319px column: "Archived" fell off the end entirely and "Unread" was cut
   mid-word, with `overflow-x: auto` quietly hiding its own scrollbar so there
   was nothing to say the rest was there. Five filters have to fit, so they are
   set smaller, tighter, and share the width equally. */
.wa-list .tabs {
    flex: none;
    margin: 0; padding: 0 6px;
    overflow: visible;
    background: var(--surface);
}
.wa-list .tabs a {
    /* `auto`, not `0`: five equal columns give "Open" the same room as
       "Nobody's", so the short ones waste space the long one then has to
       ellipsis away. Sizing to content and sharing what is left over fits all
       five with room to spare. */
    flex: 1 1 auto; min-width: 0;
    padding: 11px 3px 9px;
    font-size: 12px; font-weight: 600; letter-spacing: .005em;
    text-align: center; white-space: nowrap;
    /* A tab label that ellipsises is a tab nobody can read. They fit; this is
       the guarantee for the day a count reaches three digits, not the plan. */
    overflow: hidden; text-overflow: ellipsis;
}
/* The count rides above the word rather than after it, so "Nobody's" does not
   have to find room for " (12)" on a 60px tab. */
.wa-list .tabs .n {
    display: inline-block;
    min-width: 15px; height: 15px; padding: 0 4px; margin-left: 3px;
    border-radius: 99px;
    background: var(--ink-300); color: #fff;
    font-size: 9.5px; font-weight: 700; line-height: 15px;
    vertical-align: 1px;
    font-variant-numeric: tabular-nums;
}
.wa-list .tabs a.active .n { background: var(--brand-600); }

.wa-thread {
    display: flex; align-items: center; gap: 11px;
    padding: 12px 14px;
    border-bottom: 1px solid var(--line);
    text-decoration: none; color: inherit;
    position: relative;
}
.wa-thread:hover { background: var(--brand-50); }
.wa-thread.active { background: var(--surface); box-shadow: inset 3px 0 0 var(--brand-600); }
.wa-thread .who { flex: 1; min-width: 0; }
.wa-thread .top { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.wa-thread .name { font-size: 14px; font-weight: 500; color: var(--ink-900); }
.wa-thread .when { font-size: 11.5px; color: var(--ink-500); flex-shrink: 0; }
.wa-thread .preview {
    display: block; font-size: 12.5px; color: var(--ink-500); margin-top: 2px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.wa-unread {
    background: var(--brand-600); color: #fff; font-size: 11px; font-weight: 600;
    min-width: 19px; height: 19px; border-radius: 99px;
    display: inline-flex; align-items: center; justify-content: center; padding: 0 5px;
}

/* **`min-height: 0` is the whole reason this pane scrolls.**

   A grid item defaults to `min-height: auto`, which means it refuses to be
   shorter than its own content. Without it this column grew to the full height
   of the conversation — 3443px inside a 700px shell — so `.wa-messages` was
   never taller than its own box and had nothing to scroll, and the composer was
   pushed thousands of pixels below the card where the shell's `overflow:
   hidden` quietly clipped it. A long thread could be neither read to the end
   nor replied to.

   **`.wa-messages` does not need the same declaration**, which is worth knowing
   before somebody adds it "to match": `min-height: auto` resolves to zero on
   any box whose `overflow` is not `visible`, and that pane scrolls. This one
   does not scroll, so nothing exempts it. Measured both ways — removing it here
   breaks the pane, removing it there changes nothing.

   `min-width: 0` was already present: the same trap on the other axis, found
   and fixed once before. */
.wa-conversation {
    display: flex; flex-direction: column;
    min-width: 0; min-height: 0;
}
.wa-head {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 13px 20px; border-bottom: 1px solid var(--line); flex-shrink: 0;
}

.wa-messages {
    flex: 1; overflow-y: auto; padding: 20px;
    display: flex; flex-direction: column; gap: 8px;
    /* The faint tint separates the conversation from the chrome around it
       without imitating WhatsApp's own wallpaper, which would read as a
       skin rather than as part of this portal. */
    background: var(--brand-50);
}

/* The day chip. Centred, quiet, and `sticky` so the day you are reading stays
   named while you scroll through it — the one WhatsApp behaviour that is easy
   to miss until a thread is long enough to need it. */
.wa-day {
    position: sticky; top: 0; z-index: 2;
    display: flex; justify-content: center;
    margin: 6px 0 2px;
    pointer-events: none;
}
.wa-day span {
    padding: 3px 11px;
    border-radius: 99px;
    background: var(--surface);
    box-shadow: var(--shadow-sm);
    font-size: 11px; font-weight: 600; letter-spacing: .03em;
    text-transform: uppercase;
    color: var(--ink-500);
}

.wa-msg { display: flex; }
.wa-msg.out { justify-content: flex-end; }
.wa-msg .bubble {
    max-width: min(560px, 78%);
    min-width: 0;
    /* A pasted URL, a long filename, a German compound — none of them may widen
       the pane. `anywhere` rather than `break-word` because it also lets the
       box *shrink* to its container, which is what stops the horizontal
       scrollbar rather than merely hiding it. */
    overflow-wrap: anywhere;
    background: var(--surface);
    border-radius: 12px 12px 12px 3px;
    padding: 9px 12px 6px;
    box-shadow: var(--shadow-sm);
    font-size: 14.5px; line-height: 1.55; color: var(--ink-900);
}
.wa-msg.out .bubble { background: var(--brand-100); border-radius: 12px 12px 3px 12px; }
.wa-msg .bubble img,
.wa-msg .bubble video { max-width: 100%; border-radius: 8px; display: block; margin-bottom: 6px; }
.wa-msg .bubble audio { width: 260px; max-width: 100%; margin-bottom: 4px; }
.wa-msg .meta {
    display: flex; align-items: center; justify-content: flex-end; gap: 6px;
    font-size: 11px; color: var(--ink-500); margin-top: 3px;
}
.wa-msg .meta .tag {
    background: var(--surface-2); border-radius: 4px; padding: 1px 5px;
    font-size: 10px; letter-spacing: .03em; text-transform: uppercase;
}
.wa-msg .ticks.read { color: var(--brand-600); font-weight: 600; }

.wa-composer { border-top: 1px solid var(--line); padding: 14px 20px; flex-shrink: 0; background: var(--surface); }
.wa-composer-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 9px; }

@media (max-width: 900px) {
    .wa-shell { grid-template-columns: 1fr; height: auto; }
    .wa-list { max-height: 260px; border-right: 0; border-bottom: 1px solid var(--line); }
    .wa-messages { max-height: 60vh; }
}

/* --- Field apps: check-in desk and exhibitor stand ------------------------
   A separate shell from the admin, because the context is different: a phone
   held in one hand, standing up, often outdoors, with a queue waiting. Targets
   are large, the result of a scan is the biggest thing on screen, and nothing
   needs two taps. */
.field-body { background: var(--mist); padding-bottom: env(safe-area-inset-bottom); }

.field-head {
    position: sticky; top: 0; z-index: 20;
    display: flex; align-items: center; gap: 12px;
    padding: calc(10px + env(safe-area-inset-top)) 14px 10px;
    background: linear-gradient(180deg, var(--brand-800) 0%, var(--brand-900) 100%);
    color: #fff;
}
.field-head .titles { flex: 1; min-width: 0; }
.field-head .t {
    font-size: 15.5px; font-weight: 600; letter-spacing: -.01em;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.field-head .s {
    font-size: 12px; color: rgba(255, 255, 255, .65); margin-top: 1px;
    /* One line, like the title above it. The header is a fixed row on a phone
       and a wrapping date pushes it taller for no gain. */
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The scanner user's name in the field header. Truncates rather than wraps:
   the header is one row on a phone and a long name must not push the sign-out
   button off the edge. */
.field-head .who-chip {
    flex: 0 1 auto; min-width: 0;
    /* The event name is what somebody needs to read first, so the chip gives
       ground to it on a narrow screen rather than the other way round. */
    max-width: 30vw;
    padding: 5px 10px;
    border-radius: 999px;
    background: rgba(255, 255, 255, .14);
    font-size: 12px; font-weight: 600; color: #fff;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.field-head .back,
.field-head .signout {
    display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; flex-shrink: 0;
    border: 0; background: rgba(255, 255, 255, .12); color: #fff;
    border-radius: 50%; cursor: pointer;
}
.field-head .back svg, .field-head .signout svg { width: 19px; height: 19px; }

.field-main { padding: 16px 14px 40px; max-width: 720px; margin: 0 auto; }

.field-stats {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 16px;
}
.field-stats > div {
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
    padding: 12px 10px; text-align: center; box-shadow: var(--shadow-sm);
}
.field-stats .n {
    display: block; font-size: 26px; font-weight: 600; color: var(--ink-900);
    letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1.1;
}
.field-stats .l { display: block; font-size: 11.5px; color: var(--ink-500); margin-top: 3px; }

.field-card {
    display: flex; align-items: center; gap: 12px;
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
    padding: 16px; margin-bottom: 10px; text-decoration: none; color: inherit;
    box-shadow: var(--shadow-sm);
}
.field-card .who { flex: 1; min-width: 0; }
.field-card .name { display: block; font-size: 15.5px; font-weight: 500; color: var(--ink-900); }
.field-card .sub { display: block; font-size: 12.5px; color: var(--ink-500); margin-top: 2px; }
.field-card .chev { width: 20px; height: 20px; color: var(--ink-400); flex-shrink: 0; }

/* --- Scanner --- */
.scan-modes { display: flex; gap: 8px; margin-bottom: 12px; }
.scan-modes .mode {
    flex: 1; padding: 11px; font-size: 14.5px; font-weight: 500; cursor: pointer;
    background: var(--surface); border: 1.5px solid var(--line); border-radius: var(--radius);
    color: var(--ink-700);
}
.scan-modes .mode.active { border-color: var(--brand-600); background: var(--brand-50); color: var(--brand-700); }

.scanner-wrap {
    position: relative; border-radius: var(--radius-lg); overflow: hidden;
    background: #0e1526; margin-bottom: 14px; min-height: 200px;
    display: flex; align-items: center; justify-content: center;
}
/* **The scanner container is never hidden, and that is the whole point.**

   It used to be `display: none` until the camera reported itself live, with the
   idle panel taking its place. Two things went wrong with that, and the second
   one is fatal:

     * html5-qrcode measures this element to size the video, and a hidden element
       measures zero — so it fell back to a hard-coded 300px on a screen that was
       347 wide.
     * A `<video>` inside a `display: none` subtree is not decoded. Safari never
       fires the events html5-qrcode waits on, so `start()`'s promise **never
       settles**, the class that would have revealed the container is never
       added, and it stays hidden for ever. Permission granted, camera light on,
       nothing on screen and no error — which is exactly what it looked like.

   So the container stays in the layout and the idle panel covers it instead. */
.scanner { width: 100%; min-height: inherit; }

/* The library writes `width: 300px` inline on the video it creates. An inline
   style can only be beaten by `!important`, and a viewfinder that does not fill
   the frame it is aiming through is worth beating it for. */
.scanner video {
    width: 100% !important;
    height: auto !important;
    display: block;
}

.scanner-idle {
    position: absolute; inset: 0; z-index: 1;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    padding: 34px 20px; text-align: center;
    background: #0e1526;
    color: rgba(255, 255, 255, .75);
}
.scanner-idle .muted { color: rgba(255, 255, 255, .55); }
.scanner-idle[hidden] { display: none; }

/* The answer to a scan. Sized to be read at arm's length without focusing —
   the volunteer is looking at the person, not the phone. */
.scan-result {
    border-radius: var(--radius-lg); padding: 18px; margin-bottom: 16px;
    border: 1.5px solid; text-align: center;
}
.scan-result.ok   { background: var(--success-100); border-color: #c7e0a8; }
.scan-result.warn { background: var(--warning-100); border-color: #f0dcae; }
.scan-result.bad  { background: var(--danger-100);  border-color: #eec4c4; }
.scan-result .outcome { font-size: 15px; font-weight: 600; }
.scan-result.ok   .outcome { color: var(--success-600); }
.scan-result.warn .outcome { color: var(--warning-600); }
.scan-result.bad  .outcome { color: var(--danger-600); }
.scan-result .subject {
    font-size: 25px; font-weight: 600; color: var(--ink-900);
    margin-top: 6px; line-height: 1.15; letter-spacing: -.02em;
}
.scan-result .sub { font-size: 13.5px; color: var(--ink-500); margin-top: 3px; }
.scan-result .chip {
    display: inline-block; margin: 8px 4px 0; padding: 3px 10px; border-radius: 99px;
    background: var(--surface); border: 1px solid var(--line); font-size: 12.5px; color: var(--ink-700);
}
.scan-result .chip.warn { border-color: #f0dcae; color: var(--warning-600); }
.scan-result .actions { display: flex; gap: 8px; justify-content: center; margin-top: 14px; }

/* --- Collapsible panels --- */
.field-panel {
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
    margin-bottom: 12px; box-shadow: var(--shadow-sm); overflow: hidden;
}
.field-panel > summary {
    padding: 14px 16px; cursor: pointer; font-size: 14.5px; font-weight: 500; color: var(--ink-900);
    list-style: none;
}
.field-panel > summary::-webkit-details-marker { display: none; }
.field-panel > summary::after { content: '+'; float: right; color: var(--ink-500); font-size: 17px; line-height: 1; }
.field-panel[open] > summary::after { content: '−'; }
.field-panel .pad { padding: 0 16px 16px; }
.field-panel > .pad:first-child { padding-top: 16px; }

.search-results { margin-top: 10px; }
.search-hit {
    display: flex; align-items: baseline; gap: 8px; width: 100%; text-align: left;
    padding: 11px 12px; background: none; border: 0; border-top: 1px solid var(--line); cursor: pointer;
}
.search-hit .name { font-size: 14.5px; color: var(--ink-900); }
.search-hit .sub { font-size: 12.5px; color: var(--ink-500); flex: 1; }
.search-hit .in { font-size: 12px; color: var(--success-600); }

.recent-row {
    display: flex; align-items: center; gap: 10px;
    padding: 9px 0; border-top: 1px solid var(--line);
}
.recent-row .who { flex: 1; min-width: 0; }
.recent-row .name { display: block; font-size: 14px; color: var(--ink-900); }
.recent-row .sub { display: block; font-size: 12px; color: var(--ink-500); }
.recent-row .when { font-size: 12.5px; color: var(--ink-500); font-variant-numeric: tabular-nums; }

/* --- The stand app's tab bar ------------------------------------------------

   Three jobs at a stand — scan a badge, type up a card, look at what you have —
   are separate moments, not sections of one page. A bar at the bottom of the
   screen is where a thumb already is and where anybody who has used a phone
   expects to find them.

   Sized deliberately: 56px of bar plus the home indicator, a 44px minimum touch
   target per tab (Apple's floor, and the honest one for somebody tapping while
   holding a conversation), and a solid ground rather than a translucent one so
   the camera preview scrolling behind it cannot make the labels unreadable. */
.tabbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    display: flex; justify-content: center;
    background: var(--surface);
    border-top: 1px solid var(--line);
    /* Lifts the bar off the content above it. A hairline alone disappears
       against a white panel, which is most of this screen. */
    box-shadow: 0 -1px 3px rgba(27, 42, 79, .06), 0 -8px 24px rgba(27, 42, 79, .04);
    padding-bottom: env(safe-area-inset-bottom);
}

/* The bar spans the screen; its contents stop where the content column does, so
   on a tablet the tabs sit under the page rather than stranded at the edges. */
.tabbar > .tab { flex: 1 1 0; max-width: 240px; }

.tab {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px;
    min-height: 56px; padding: 7px 4px 8px;
    color: var(--ink-500); text-decoration: none;
    -webkit-tap-highlight-color: transparent;
}
.tab .ico {
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    width: 52px; height: 26px;
    border-radius: 999px;
    transition: background-color .15s ease;
}
.tab .ico svg { width: 22px; height: 22px; stroke-width: 1.9; }
.tab .lbl { font-size: 11px; font-weight: 600; letter-spacing: .01em; }

/* The active tab, said three ways at once — colour, weight and a tinted pill —
   because one of them alone is easy to miss at arm's length on a bright stand. */
.tab.on { color: var(--brand-600); }
.tab.on .ico { background: var(--brand-50); }
.tab.on .ico svg { stroke-width: 2.3; }
.tab.on .lbl { font-weight: 700; }

.tab:hover:not(.on) { color: var(--ink-700); }
.tab:focus-visible { outline: 2px solid var(--brand-600); outline-offset: -3px; border-radius: 10px; }

/* The running lead count. Sits on the icon, not beside the label, so a
   three-character count cannot push the label out of line. */
.tab-badge {
    position: absolute; top: -3px; left: 50%; margin-left: 4px;
    min-width: 17px; height: 17px; padding: 0 5px;
    border-radius: 999px;
    background: var(--brand-600); color: #fff;
    font-size: 10.5px; font-weight: 700; line-height: 17px; text-align: center;
    font-variant-numeric: tabular-nums;
    box-shadow: 0 0 0 2px var(--surface);
}
.tab.on .tab-badge { box-shadow: 0 0 0 2px var(--surface); }

/* Clears the fixed bar. 56px of bar, the home indicator, and a little air. */
.tabbar-space { height: calc(56px + 16px + env(safe-area-inset-bottom)); }

.tab-pane[hidden] { display: none; }

/* The scan pane fills what is left of the screen.

   A stand hand holds the phone up at somebody's chest and points it at a badge.
   A viewfinder floating in the top third of an otherwise empty page is both
   harder to aim and a smaller target for the decoder, and it reads as a page
   with a widget on it rather than as a scanner.

   `--field-chrome` is measured once in JavaScript rather than guessed at here:
   the header is taller on a notched phone, shorter in the browser pane, and
   different again with a long event name wrapping. `100dvh`, not `100vh`, so
   mobile Safari's collapsing address bar does not leave the panel overhanging
   the screen it was sized to. */
#pane-scan {
    display: flex; flex-direction: column;
    min-height: calc(100dvh - var(--field-chrome));
}
#pane-scan .scanner-wrap {
    flex: 1 1 auto;
    /* Never so short that the viewfinder brackets crowd the frame, however
       little room is left — a landscape phone, or a result box open below. */
    min-height: 260px;
    display: flex; align-items: center; justify-content: center;
}
#pane-scan .scan-result { flex: 0 0 auto; }

/* Switching panes should feel like arriving somewhere, not like a redraw. Short
   enough not to be in the way of somebody working quickly. */
@media (prefers-reduced-motion: no-preference) {
    .tab-pane { animation: pane-in .18s ease-out; }
    @keyframes pane-in {
        from { opacity: 0; transform: translateY(4px); }
        to   { opacity: 1; transform: none; }
    }
}

/* --- Kiosk: the counter display --------------------------------------------

   The same agent screens with the chrome taken off. Nothing here changes what
   the page *is* — no layout is rebuilt, no component is replaced — which is what
   keeps one build running on a phone in somebody's hand and on a tablet bolted
   to a desk. */
.kiosk-body { background: var(--surface-2); }

/* The sponsor strip. Fixed 1200 × 120 artwork, so the box keeps that ratio and
   the image is never asked to be a shape it is not. */
.kiosk-strip {
    width: 100%; max-width: 1200px; margin: 0 auto;
    aspect-ratio: 10 / 1;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
}
.kiosk-strip img { display: block; width: 100%; height: 100%; object-fit: contain; }

.kiosk-head {
    padding: 18px 20px 10px;
    max-width: 720px; margin: 0 auto;
    text-align: center;
}
.kiosk-head .t { font-size: 22px; font-weight: 600; letter-spacing: -.015em; color: var(--ink-900); }
.kiosk-head .s { font-size: 14px; color: var(--ink-500); margin-top: 3px; }

/* The way in and the way out. Both quiet, neither hidden: the kiosk is
   ergonomic, not a lock, and a control that pretends otherwise is worse than
   one that admits it. */
.kiosk-enter { margin-top: 26px; text-align: center; }
.kiosk-enter p { margin: 8px auto 0; max-width: 42ch; }

.kiosk-exit { text-align: center; padding: 10px 0 26px; }
.kiosk-exit button {
    border: 0; background: none; cursor: pointer;
    font-size: 12px; color: var(--ink-400);
    padding: 8px 12px; border-radius: var(--radius-sm);
}
.kiosk-exit button:hover { color: var(--ink-700); background: var(--surface); }

/* Autofit works by zooming `.field-main`, which is set from JavaScript. Nothing
   to declare here beyond the starting point — but it is written down because a
   `zoom` appearing from nowhere in the inspector is a mystery otherwise. */
.kiosk-body .field-main { zoom: 1; }

/* --- Field roster: agents and scanner users ---------------------------------

   A list where every row opens in place. `<details>`, like the other disclosures
   in this file, so it works with no JavaScript at all — and unlike a dropdown
   menu it can hold forms, which is the whole point here: editing somebody and
   ticking the events they work are the two things this screen exists for, and
   neither belongs behind a second click into a modal. */
.roster { border-top: 1px solid var(--line); }
.roster > summary {
    display: flex; align-items: center; gap: 12px;
    padding: 13px 20px; cursor: pointer; list-style: none;
}
.roster > summary::-webkit-details-marker { display: none; }
.roster > summary:hover { background: var(--surface-2); }
.roster > summary::after {
    content: '+'; flex-shrink: 0; width: 18px; text-align: center;
    color: var(--ink-400); font-size: 17px; line-height: 1;
}
.roster[open] > summary::after { content: '−'; }
.roster[open] > summary { background: var(--surface-2); }

.roster .who { flex: 1; min-width: 0; }
.roster .name { display: block; font-size: 14.5px; font-weight: 500; color: var(--ink-900); }
.roster .sub  { display: block; font-size: 12.5px; color: var(--ink-500); }
.roster .tags { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

.roster .body {
    padding: 18px 20px 22px;
    background: var(--surface-2);
    border-top: 1px solid var(--line);
}
/* Spacing between the forms inside a row comes from here rather than from each
   form's own margin, so two of them never collapse into one another. */
.roster .body > * + * { margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--line); }

/* A magic link, shown once. Wraps rather than scrolls: somebody is going to
   read it aloud or copy it by eye, and a truncated credential is useless. */
.link-out {
    display: block; width: 100%;
    padding: 11px 13px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--field-bg);
    font-family: var(--font-mono, ui-monospace, monospace); font-size: 13px;
    color: var(--ink-900);
    word-break: break-all;
}

/* --- Exhibitor leads --- */
.lead { border-top: 1px solid var(--line); }
.lead > summary { display: flex; align-items: center; gap: 10px; padding: 11px 0; cursor: pointer; list-style: none; }
.lead > summary::-webkit-details-marker { display: none; }
.lead .who { flex: 1; min-width: 0; }
.lead .name { display: block; font-size: 14.5px; font-weight: 500; color: var(--ink-900); }
.lead .sub { display: block; font-size: 12.5px; color: var(--ink-500); }
.lead .tags { display: flex; align-items: center; gap: 7px; flex-shrink: 0; }
.lead .when { font-size: 12px; color: var(--ink-500); font-variant-numeric: tabular-nums; }
.dot-rating { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.dot-rating.hot  { background: var(--danger-500); }
.dot-rating.warm { background: var(--warning-600); }
.dot-rating.cold { background: var(--ink-400); }
.lead-body { padding: 4px 0 16px; }

.rating-row { display: flex; gap: 8px; }
.rating-row .rate {
    flex: 1; padding: 9px; font-size: 13.5px; cursor: pointer;
    background: var(--surface); border: 1.5px solid var(--line); border-radius: var(--radius-sm);
    color: var(--ink-700);
}
.rating-row .rate.on { border-color: var(--brand-600); background: var(--brand-50); color: var(--brand-700); font-weight: 500; }
.voice-row { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.voice-list audio { width: 100%; margin-top: 8px; }

/* --- Certificate designer -------------------------------------------------
   The stage is sized in millimetres and scaled: its width is the page width,
   so a field at 148.5mm sits at 148.5mm here and at 148.5mm in the PDF. That
   equivalence is the whole point — the editor is only trustworthy if what is
   dragged is what prints. */
.cert-layout { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 24px; align-items: start; }

.cert-stage-wrap {
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 16px;
    box-shadow: var(--shadow-sm);
}
.cert-stage {
    position: relative;
    width: 100%;
    background: #fff center/100% 100% no-repeat;
    box-shadow: 0 2px 10px rgba(27, 42, 79, .18);
    /* aspect-ratio does the millimetre maths: an unset background still holds
       exactly A4 proportions, so positions stay honest before artwork exists. */
    overflow: hidden;
    touch-action: none;   /* pointer events drive dragging, not scrolling */
    cursor: default;
}
.cert-stage.landscape { aspect-ratio: 297 / 210; }
.cert-stage.portrait  { aspect-ratio: 210 / 297; }

.cert-field {
    position: absolute;
    white-space: nowrap;
    line-height: 1.05;
    cursor: grab;
    outline: 1px dashed transparent;
    outline-offset: 3px;
}
.cert-field:hover { outline-color: var(--brand-400); }
.cert-field.selected {
    outline: 1.5px solid var(--brand-600);
    outline-offset: 3px;
    cursor: grabbing;
}
.cert-field > span { pointer-events: none; }

.cert-field-list { margin-top: 6px; max-height: 260px; overflow-y: auto; }
.cert-field-row {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    width: 100%; text-align: left; cursor: pointer;
    padding: 9px 10px; background: none; border: 0; border-top: 1px solid var(--line);
    font-size: 13.5px; color: var(--ink-700);
}
.cert-field-row:hover { background: var(--brand-50); }
.cert-field-row.on { background: var(--brand-50); color: var(--brand-700); font-weight: 500; }

@media (max-width: 1100px) {
    .cert-layout { grid-template-columns: 1fr; }
}

/* --- CPD summary --- */
.cpd-ring {
    display: flex; align-items: center; gap: 18px;
}
.cpd-ring .total {
    font-size: 40px; font-weight: 600; color: var(--ink-900);
    letter-spacing: -.02em; line-height: 1; font-variant-numeric: tabular-nums;
}
.cpd-ring .unit { font-size: 13px; color: var(--ink-500); margin-top: 4px; }

/* --- Video library --------------------------------------------------------
   A card grid rather than a list: a recording is chosen by recognising the
   thumbnail and the title together, which a table row does badly. */
.video-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 20px;
}

.video-card { text-decoration: none; color: inherit; display: block; }

.video-card .thumb {
    position: relative;
    display: flex; align-items: center; justify-content: center;
    aspect-ratio: 16 / 9;
    border-radius: var(--radius);
    background: var(--ink-900) center/cover no-repeat;
    color: rgba(255, 255, 255, .35);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
    transition: box-shadow .14s, transform .14s;
}
.video-card .thumb > svg { width: 40px; height: 40px; }
.video-card:hover .thumb { transform: translateY(-2px); box-shadow: var(--shadow-md); }

/* The play badge sits over the artwork, so it needs its own contrast rather
   than relying on whatever the thumbnail happens to be. */
.video-card .play {
    position: absolute; inset: 0; margin: auto;
    width: 46px; height: 46px; border-radius: 50%;
    background: rgba(16, 24, 40, .62);
    display: flex; align-items: center; justify-content: center;
    color: #fff;
    backdrop-filter: blur(2px);
    transition: background .14s;
}
.video-card .play svg { width: 17px; height: 17px; margin-left: 2px; }
.video-card:hover .play { background: var(--brand-600); }

.video-card .dur {
    position: absolute; right: 7px; bottom: 7px;
    background: rgba(16, 24, 40, .78); color: #fff;
    font-size: 11.5px; font-variant-numeric: tabular-nums;
    padding: 1px 6px; border-radius: 4px;
}
.video-card .seen {
    position: absolute; left: 7px; top: 7px;
    background: var(--success-600); color: #fff;
    font-size: 10.5px; letter-spacing: .03em; text-transform: uppercase;
    padding: 2px 7px; border-radius: 4px;
}

.video-card .meta { display: block; margin-top: 10px; }
.video-card .meta .t {
    display: block; font-size: 14.5px; font-weight: 500; color: var(--ink-900);
    line-height: 1.35;
}
.video-card .meta .s { display: block; font-size: 12.5px; color: var(--ink-500); margin-top: 3px; }

/* The player keeps 16:9 whatever the container width — an iframe with a fixed
   height letterboxes on a phone and crops on a desktop. */
.video-player {
    position: relative;
    aspect-ratio: 16 / 9;
    background: #000;
    border-radius: var(--radius-lg);
    overflow: hidden;
    box-shadow: var(--shadow-md);
}
.video-player iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.voice-note { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; }
.voice-note audio { flex: 1; min-width: 0; }

/* The transcript sits under its own player, not beside it — it is the length of
   whatever somebody said, and a flex sibling of the audio element would squeeze
   both. `flex-basis: 100%` puts it on the next line of the same row. */
.voice-text {
    flex: 0 0 100%;
    margin: 0;
    padding: 8px 10px;
    border-left: 2px solid var(--ink-300);
    background: var(--surface-2);
    border-radius: 0 6px 6px 0;
    color: var(--ink-700);
    white-space: pre-wrap;
}
.voice-note .drop-voice {
    flex-shrink: 0; width: 26px; height: 26px; border-radius: 50%;
    border: 1px solid var(--line); background: var(--surface);
    color: var(--ink-500); font-size: 16px; line-height: 1; cursor: pointer;
}
.voice-note .drop-voice:hover { border-color: var(--danger-500); color: var(--danger-600); }

/* --- Telephone field ------------------------------------------------------
   The dialling code sits against the number rather than above it, so the two
   read as one control. It is sized to its content: a 200px select for "+27"
   would imply the code matters as much as the number, which it does not. */
.phone-row { display: flex; gap: 8px; }
.phone-row .phone-code { width: auto; min-width: 116px; max-width: 190px; flex-shrink: 0; }
.phone-row .phone-number { flex: 1; min-width: 0; font-variant-numeric: tabular-nums; letter-spacing: .01em; }

@media (max-width: 480px) {
    .phone-row { flex-direction: column; }
    .phone-row .phone-code { width: 100%; max-width: none; }
}

/* Drag-to-reorder rows (admin lists). The handle is the only grab point, so the
   inputs in the row stay selectable. */
.drag-handle {
    cursor: grab;
    color: var(--ink-500);
    font-size: 18px;
    line-height: 1;
    padding-top: 9px;
    user-select: none;
}
.drag-handle:active { cursor: grabbing; }
tr.is-dragging { opacity: .45; background: var(--surface-2); }
.vocab-table tbody tr + tr { border-top: 1px solid var(--line); }

/* ------------------------------------------------------------------ Images
   Logo and photograph management in the listing editor. */
.image-panel { margin-bottom: 4px; }
.image-panel > label { display: block; font-weight: 600; margin-bottom: 8px; }

.logo-row { display: grid; grid-template-columns: 120px 1fr; gap: 16px; align-items: start; }
.logo-preview {
    width: 120px; height: 120px;
    display: flex; align-items: center; justify-content: center;
    background: #fff; border: 1px solid var(--line); border-radius: 10px; padding: 8px;
}
/* Contained, never cropped: a logo cropped to a square stops being the logo. */
.logo-preview img { max-width: 100%; max-height: 100%; object-fit: contain; }
.logo-preview:empty::after { content: 'No logo'; font-size: 12px; color: var(--ink-500); }

.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
.photo-card {
    position: relative; aspect-ratio: 4 / 3; border-radius: 10px; overflow: hidden;
    border: 1px solid var(--line); background: var(--surface-2); cursor: zoom-in;
}
.photo-card img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photo-card.is-uploading img { opacity: .45; }
.photo-card.is-removing { opacity: .3; pointer-events: none; }
.photo-card.is-dragging { opacity: .4; }
.photo-card progress {
    position: absolute; left: 8px; right: 8px; bottom: 8px; width: calc(100% - 16px); height: 6px;
}
.photo-remove {
    position: absolute; top: 6px; right: 6px; width: 26px; height: 26px;
    border: 0; border-radius: 50%; background: rgba(17, 24, 39, .72); color: #fff;
    font-size: 17px; line-height: 1; cursor: pointer;
}
.photo-remove:hover { background: var(--danger-600, #b91c1c); }
.photo-drag {
    position: absolute; bottom: 6px; left: 6px; padding: 2px 6px; border-radius: 6px;
    background: rgba(17, 24, 39, .62); color: #fff; font-size: 13px; cursor: grab; user-select: none;
}
.photo-drag:active { cursor: grabbing; }

/* ------------------------------------------------------------- Lightbox */
body.lightbox-open { overflow: hidden; }
.lightbox {
    position: fixed; inset: 0; z-index: 1000;
    display: flex; align-items: center; justify-content: center; gap: 8px;
    background: rgba(9, 14, 24, .88); padding: 24px;
}
.lb-stage { margin: 0; max-width: min(1100px, 92vw); max-height: 88vh; text-align: center; }
.lb-stage img { max-width: 100%; max-height: 82vh; border-radius: 10px; display: block; margin: 0 auto; }
.lb-stage figcaption { color: #e5e7eb; font-size: 14px; margin-top: 10px; }
.lightbox button {
    border: 0; background: rgba(255, 255, 255, .12); color: #fff; cursor: pointer;
    border-radius: 999px; width: 44px; height: 44px; font-size: 26px; line-height: 1; flex: 0 0 auto;
}
.lightbox button:hover { background: rgba(255, 255, 255, .24); }
.lb-close { position: absolute; top: 18px; right: 18px; }
@media (max-width: 640px) {
    .lb-prev, .lb-next { position: absolute; bottom: 18px; }
    .lb-prev { left: 18px; } .lb-next { right: 18px; }
}

/* ------------------------------------------------------------ Video comments */
.comments { margin-top: 34px; }
.comments-head h2 { font-size: 18px; margin: 0 0 16px; }
.comments-locked .cta-row { margin-top: 14px; }

.comment-form { margin-bottom: 26px; }
.comment-form-row { display: flex; gap: 12px; align-items: flex-start; }
.comment-form-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; gap: 12px; }
.replying-to {
    font-size: 13px; margin-bottom: 8px; padding: 6px 10px; border-radius: 8px;
    background: var(--surface-2); display: flex; gap: 10px; align-items: center;
}

.comment { padding: 16px 0; border-top: 1px solid var(--line); }
.comment:first-child { border-top: 0; }
.comment-head { display: flex; gap: 10px; align-items: center; margin-bottom: 7px; }
.comment-head .who { font-weight: 600; font-size: 14px; display: block; }
.comment-head .when { font-size: 12px; color: var(--ink-500); }
.comment-body { font-size: 15px; line-height: 1.6; overflow-wrap: anywhere; }
.comment-body.is-removed { color: var(--ink-500); font-style: italic; font-size: 14px; }

.comment-actions { display: flex; gap: 14px; margin-top: 8px; }
.link-btn {
    border: 0; background: none; padding: 0; cursor: pointer;
    font-size: 13px; font-weight: 600; color: var(--ink-500);
}
.link-btn:hover { color: var(--brand-700, #016585); text-decoration: underline; }
.link-btn.danger:hover { color: var(--danger-600, #b91c1c); }

/* Replies are indented once and only once — two levels is the whole model, so
   there is no deeper rule to write. */
.comment-replies { margin: 10px 0 0 34px; border-left: 2px solid var(--line); padding-left: 14px; }
.comment-replies .comment { padding: 12px 0; }
.comment-replies[data-collapsed] .comment.is-extra { display: none; }
.show-replies { margin-top: 6px; }

.avatar, .comment .avatar {
    width: 34px; height: 34px; flex: 0 0 auto; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--brand-50, #e6f3f8); color: var(--brand-700, #016585);
    font-size: 12px; font-weight: 700;
}
.avatar.sm { width: 27px; height: 27px; font-size: 11px; }

.edit-box { margin-top: 8px; }
.edit-actions { display: flex; gap: 8px; margin-top: 8px; }

.comment-staff-note {
    padding: 10px 14px; border-radius: 8px; margin-bottom: 22px;
    background: var(--surface-2); border: 1px dashed var(--line);
}

/* ==========================================================================
   Bulk directory tagging

   Two panes: the vocabulary stays put while the practice list scrolls, because
   the options are the thing being applied over and over and a list that scrolls
   away has to be found again for every batch.
   ========================================================================== */

.tag-coverage .card-body { display: grid; gap: 14px; }
.cov-top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; }
.cov-label { font-weight: 500; font-size: 13.5px; }
.cov-n { font-size: 12.5px; color: var(--ink-500); font-variant-numeric: tabular-nums; }
.cov-bar { height: 7px; border-radius: 99px; background: var(--field-bg); overflow: hidden; }
.cov-bar i { display: block; height: 100%; background: var(--brand-600); border-radius: 99px; }
.cov-note { margin: 2px 0 0; }

.filter-bar .card-body { gap: 10px; }

.tag-layout { display: grid; grid-template-columns: 280px 1fr; gap: 20px; align-items: start; }
.tag-terms { position: sticky; top: 18px; }
.tag-terms .term-list { list-style: none; margin: 0; padding: 6px 0; max-height: 52vh; overflow-y: auto; }
.tag-terms .term-list li { border-bottom: 1px solid var(--line); }
.tag-terms .term-list li:last-child { border-bottom: 0; }
.tag-terms .term-list label {
    display: flex; gap: 9px; align-items: flex-start; padding: 9px 16px;
    font-size: 13.5px; cursor: pointer;
}
.tag-terms .term-list label:hover { background: var(--field-bg); }
.note-inline { border-top: 1px solid var(--line); }
.note-inline p { margin: 0; }

.taglist { display: flex; flex-wrap: wrap; gap: 5px; }
.tagpill {
    font-size: 11.5px; padding: 2px 8px; border-radius: 99px;
    background: var(--field-bg); color: var(--ink-700); border: 1px solid var(--line);
    white-space: nowrap;
}
/* A suggestion is a proposal, not a fact — dashed until somebody applies it. */
.tagpill.is-sugg { background: transparent; border-style: dashed; border-color: var(--brand-400); color: var(--brand-700); cursor: help; }

tr.is-blocked { opacity: .58; }
tr.is-blocked .warn { color: var(--warning-600); }

/* The action bar follows the page rather than sitting at the bottom of a long
   table: with forty rows selected, the buttons must not require scrolling back. */
.tag-actions { position: sticky; bottom: 0; z-index: 5; margin-top: 18px; }
.tag-actions .inner {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
    padding: 12px 16px; box-shadow: 0 -2px 14px rgba(35, 57, 84, .07);
}

@media (max-width: 900px) {
    .tag-layout { grid-template-columns: 1fr; }
    .tag-terms { position: static; }
    .tag-terms .term-list { max-height: 240px; }
}

/* ==========================================================================
   Go-live readiness
   ========================================================================== */

.golive-head.is-blocked { border-left: 3px solid var(--danger-600); }
.golive-head.is-ready { border-left: 3px solid var(--success-600); }
.golive-head h2 { font-size: 19px; margin: 0 0 5px; }

.golive-list { list-style: none; margin: 0; padding: 0; }
.gl { display: flex; gap: 14px; padding: 15px 18px; border-bottom: 1px solid var(--line); }
.gl:last-child { border-bottom: 0; }
.gl-badge {
    flex: 0 0 74px; align-self: flex-start; text-align: center;
    font-size: 11px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
    padding: 3px 0; border-radius: 4px;
}
.gl-blocker .gl-badge { background: #fdeaea; color: var(--danger-600); }
.gl-warning .gl-badge { background: #fdf3e6; color: var(--warning-600); }
.gl-ok .gl-badge { background: #edf6ec; color: var(--success-600); }
.gl-ok { opacity: .72; }
.gl-body { min-width: 0; }
.gl-body p { margin: 3px 0 0; font-size: 13.5px; color: var(--ink-500); }
/* The fix is the useful half — set apart so it is not read as more detail. */
.gl-fix { color: var(--ink-700) !important; border-left: 2px solid var(--line); padding-left: 10px; margin-top: 7px !important; }

@media (max-width: 560px) {
    .gl { flex-direction: column; gap: 8px; }
    .gl-badge { flex: 0 0 auto; width: 74px; }
}

/* ==========================================================================
   Skip links

   Hidden until focused — invisible to a mouse user, the first thing a keyboard
   user meets. Positioned fixed rather than static so revealing one never
   reflows the header underneath it.
   ========================================================================== */

.skip-links { position: absolute; top: 0; left: 0; z-index: 999; }
.skip-link {
    position: absolute; left: -9999px; top: 0;
    display: inline-block; padding: 11px 18px;
    background: var(--brand-800); color: #fff; text-decoration: none;
    font-size: 14px; font-weight: 500;
    border-radius: 0 0 var(--radius) 0;
}
.skip-link:focus {
    left: 0;
    outline: 2px solid var(--brand-400);
    outline-offset: -4px;
}
/* The skip target takes focus programmatically; it must not then draw a ring
   around the whole region as though it were a control. */
main:focus, [id="dir-results"]:focus { outline: none; }

/* ==========================================================================
   Member picker (issuing a certificate by hand)
   ========================================================================== */

.people-results {
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface); margin-top: 6px; overflow: hidden;
    max-height: 260px; overflow-y: auto;
}
.people-hit {
    display: flex; flex-direction: column; gap: 2px; width: 100%;
    text-align: left; background: none; border: 0; cursor: pointer;
    padding: 9px 14px; border-bottom: 1px solid var(--line);
    font: inherit; color: var(--ink-900);
}
.people-hit:last-child { border-bottom: 0; }
.people-hit:hover, .people-hit:focus-visible { background: var(--field-bg); }
.people-hit span { font-size: 12.5px; color: var(--ink-500); }
.people-none { padding: 11px 14px; font-size: 13px; color: var(--ink-500); }

/* A caution that is not an error — correcting something already sent. */
.note-warn { border-left: 3px solid var(--warning-600); }

/* --- A week of windows ----------------------------------------------------
   One row per day, two time inputs each. Replaced a row of day chips and a
   single From/Until pair, which could describe a week only if every day of it
   kept the same hours — and the first thing wanted was a shorter Saturday.

   A day with no times is the off state, and it says so in words rather than by
   being empty: an empty row and a row somebody has not got to yet look the
   same otherwise. */
.wa-week { display: grid; gap: 6px; max-width: 520px; }
.wa-week-row {
    display: grid; grid-template-columns: 96px 1fr auto 1fr 74px;
    align-items: center; gap: 8px;
    padding: 6px 10px; border-radius: 9px;
    border: 1px solid transparent;
}
.wa-week-row.is-on { background: var(--brand-50); border-color: var(--brand-100); }
.wa-week-row .d { font-size: 13.5px; font-weight: 600; color: var(--ink-700); }
.wa-week-row .sep { font-size: 12.5px; color: var(--ink-400); }
.wa-week-row .off { font-size: 11.5px; color: var(--ink-400); text-align: right; }
.wa-week-row input[type="time"] { padding: 6px 9px; font-size: 13.5px; }

@media (max-width: 620px) {
    .wa-week-row { grid-template-columns: 1fr auto 1fr; row-gap: 4px; }
    .wa-week-row .d   { grid-column: 1 / -1; }
    .wa-week-row .off { grid-column: 1 / -1; text-align: left; }
}

/* --- Saved replies and conversation notes ---------------------------------
   Both are `<details>` — a disclosure that works with no JavaScript at all,
   and needs none to open. The script on the picker only handles inserting and
   filtering, so with it blocked the replies are still readable and copyable. */
.canned, .wa-notes { position: relative; }
.canned > summary, .wa-notes > summary { list-style: none; cursor: pointer; }
.canned > summary::-webkit-details-marker,
.wa-notes > summary::-webkit-details-marker { display: none; }

.canned-menu {
    position: absolute; bottom: calc(100% + 8px); left: 0;
    width: min(360px, calc(100vw - 40px));
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-lg, 0 18px 44px rgba(26,33,48,.18));
    padding: 10px; z-index: 40;
}
.canned-find { width: 100%; margin-bottom: 8px; }
.canned-list { max-height: 320px; overflow-y: auto; }
.canned-topic {
    font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
    color: var(--ink-400); padding: 8px 6px 4px;
}
.canned-item {
    display: flex; align-items: baseline; gap: 8px; width: 100%;
    background: none; border: 0; text-align: left; cursor: pointer;
    padding: 7px 8px; border-radius: 7px; font: inherit;
}
.canned-item:hover { background: var(--brand-50); }
.canned-item:focus-visible { outline: 2px solid var(--brand-600); outline-offset: -2px; }
.canned-item .t { font-size: 13.5px; color: var(--ink-900); flex: 1; }
.canned-item .sc { font-size: 11px; font-family: var(--font-mono); color: var(--ink-400); }
.canned-none { padding: 12px 8px; font-size: 13px; color: var(--ink-500); }

.wa-head-actions { display: flex; align-items: flex-start; gap: 8px; }
.wa-notes-body {
    position: absolute; top: calc(100% + 8px); right: 0;
    width: min(340px, calc(100vw - 40px));
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-lg, 0 18px 44px rgba(26,33,48,.18));
    padding: 12px; z-index: 40; max-height: 420px; overflow-y: auto;
}
.wa-notes-body textarea { min-height: 0; margin-bottom: 8px; }
.wa-note { border-top: 1px solid var(--line); padding: 10px 0 0; margin-top: 10px; }
.wa-note .b { font-size: 13.5px; color: var(--ink-900); line-height: 1.5; }
.wa-note .m { font-size: 11.5px; color: var(--ink-400); margin-top: 4px; }

/* --- The WhatsApp dialler --------------------------------------------------
   Lives in the conversation header. The state line carries the whole of the
   call's progress in words, because everything that can go wrong with a WebRTC
   call — no microphone, an insecure page, a declined call, a dropped route —
   looks identical from the outside unless something says which it was. */
.wa-call { display: flex; align-items: center; gap: 8px; }
.wa-call audio { display: none; }
.wa-call-state { font-size: 12.5px; color: var(--ink-500); white-space: nowrap; }
.wa-call-state.is-good { color: var(--success-600); font-weight: 600; font-variant-numeric: tabular-nums; }
.wa-call-state.is-bad  { color: var(--danger-600); }

/* One editable Company Doc. A rule between them rather than a card each: they
   are a list of variations on one thing, and eight cards reads as eight
   separate features. */
.doc-row { padding: 18px 0; border-top: 1px solid var(--line); }
.doc-row:first-child { border-top: 0; padding-top: 0; }
.doc-row .toolbar { margin-top: 10px; }

/* Who owns a conversation. Initials in a disc on the list row, because the
   thread list is scanned rather than read — a name would push the preview text
   out, and at a glance the question is "is this mine" rather than "whose". */
.wa-owner {
    display: inline-flex; align-items: center; justify-content: center;
    width: 19px; height: 19px; border-radius: 50%; flex: none;
    background: var(--brand-100); color: var(--brand-700);
    font-size: 9.5px; font-weight: 700; letter-spacing: .02em;
    margin-right: 5px; vertical-align: 1px;
}
.wa-assign .select { font-size: 12.5px; padding: 5px 26px 5px 9px; max-width: 170px; }
