/* ==========================================================================
   Campaign public funnel — signup, confirm, unsubscribe, check-in, login.

   Design intent: a campaign field piece, not a SaaS form. Big confident
   display type, a warm paper ground that sits with the candidate photography,
   and the campaign's own navy/cyan carried through from the email templates
   and the yard signs.

   Type: Fraunces (display) — the house display face, an optical-size serif
   with real character; Public Sans (body) — the US Web Design System's face,
   which is exactly the civic register this wants and is deliberately not the
   default sans everything else uses.
   ========================================================================== */

@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700;9..144,900&family=Public+Sans:wght@400;500;600;700&display=swap');

:root {
    /* Campaign palette — navy and cyan are the real brand, carried over from
       the email template and the yard signs. Gold is the warm counter that
       keeps a navy-heavy page from reading cold. */
    --navy: #113756;
    --navy-deep: #0a2438;
    --navy-soft: #1d4f76;
    --cyan: #259fda;
    --cyan-deep: #157aab;
    --gold: #e0a33c;

    --ink: #10222f;
    --slate: #5b6b78;
    --slate-soft: #7f8d99;

    /* Warm paper, not cold gray: it sits with the photography instead of
       fighting it. */
    --paper: #fbf7f1;
    --card: #ffffff;
    --line: #e3dbcf;
    --line-strong: #cec4b4;

    --danger: #b3261e;
    --danger-bg: #fdf0ef;
    --success: #1c7a4f;

    --radius: 10px;
    --radius-lg: 18px;
    --radius-pill: 999px;

    --shadow-sm: 0 1px 2px rgb(16 34 47 / 6%);
    --shadow: 0 2px 8px rgb(16 34 47 / 8%), 0 12px 32px rgb(16 34 47 / 6%);
    --shadow-lg: 0 4px 12px rgb(16 34 47 / 10%), 0 24px 64px rgb(16 34 47 / 10%);

    --step: 0.5rem;
    --measure: 34rem;
}

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink);
    font-family: 'Public Sans', ui-sans-serif, system-ui, sans-serif;
    font-size: 1.0625rem;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

/* A quiet paper grain. Two hairline gradients rather than an image asset —
   enough to stop the background reading as flat screen-white. */
body::before {
    content: '';
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 0;
    opacity: 0.5;
    background-image:
        linear-gradient(115deg, rgb(37 159 218 / 3.5%) 0%, transparent 45%),
        linear-gradient(300deg, rgb(224 163 60 / 4%) 0%, transparent 40%);
}

/* --------------------------------------------------------------- typography */

h1, h2, h3 {
    font-family: Fraunces, ui-serif, Georgia, serif;
    font-optical-sizing: auto;
    font-variation-settings: 'SOFT' 20, 'WONK' 1;
    line-height: 1.08;
    letter-spacing: -0.015em;
    margin: 0 0 var(--step);
    text-wrap: balance;
}

h1 {
    font-size: clamp(2.25rem, 6vw, 3.5rem);
    font-weight: 900;
}

h2 {
    font-size: clamp(1.5rem, 3.5vw, 2rem);
    font-weight: 700;
}

h3 {
    font-size: 1.25rem;
    font-weight: 700;
}

p {
    margin: 0 0 var(--step);
    max-width: var(--measure);
}

a {
    color: var(--cyan-deep);
    text-underline-offset: 3px;
    text-decoration-thickness: 1.5px;
}

a:hover {
    color: var(--navy);
}

.eyebrow {
    font-family: 'Public Sans', sans-serif;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    /* Cyan on the navy masthead is ~5.3:1 and fine. On a white card it drops to
       ~3:1 — under AA for 12px text, which 700 weight does not exempt. The
       deeper cyan clears 4.5:1 on white; the masthead overrides back to cyan. */
    color: var(--cyan-deep);
    margin: 0 0 calc(var(--step) * 1.5);
}

.masthead .eyebrow {
    color: var(--cyan);
}

.hint {
    display: block;
    font-size: 0.8125rem;
    font-weight: 400;
    color: var(--slate);
    letter-spacing: 0;
    text-transform: none;
}

/* Required marker. --danger clears AA on white (~6.4:1); a lighter red would
   be decoration a low-vision user cannot see, which defeats the purpose.
   aria-hidden at the call site: colour and a glyph are not an accessible way
   to convey "required", so the visible key below carries the meaning and the
   server is what actually enforces it. */
.req {
    color: var(--danger);
    font-weight: 700;
    margin-left: 0.15em;
}

.required-key {
    font-size: 0.8125rem;
    color: var(--slate);
    margin: 0;
}

/* -------------------------------------------------------------------- hero */

.hero {
    position: relative;
    z-index: 1;
    background: var(--navy-deep);
    overflow: hidden;
}

.hero img {
    display: block;
    width: 100%;
    height: clamp(150px, 26vw, 280px);
    object-fit: cover;
    object-position: center 32%;
    /* The photo is a wide town-hall banner; darkening the lower edge lets the
       navy masthead below meet it without a hard seam. */
    mask-image: linear-gradient(to bottom, #000 72%, transparent 100%);
}

.masthead {
    background: var(--navy-deep);
    color: #fff;
    padding: 0 var(--step) calc(var(--step) * 5);
    margin-top: -1px;
    text-align: center;
}

.masthead h1 {
    color: #fff;
    margin: 0 auto calc(var(--step) * 1.5);
    max-width: 18ch;
}

.masthead h1 em {
    font-style: normal;
    color: var(--cyan);
}

.masthead p {
    margin: 0 auto;
    max-width: 46ch;
    color: #cfe0ec;
    font-size: 1.0625rem;
}

/* ------------------------------------------------------------------- shell */

.shell {
    position: relative;
    z-index: 1;
    max-width: 46rem;
    margin: 0 auto;
    padding: 0 calc(var(--step) * 2) calc(var(--step) * 10);
}

/* The form card overlaps the masthead — a small piece of depth that signals
   "this is the thing to do" without an arrow or a bouncing chevron. */
.shell--overlap {
    margin-top: calc(var(--step) * -4);
}

/* Pages reached from an emailed link (confirm, login, unsubscribe, check-in)
   have no hero to sit under, so they get top breathing room instead of a
   negative offset. These four had NO wrapper at all until now — content ran
   flush to the viewport edge on a phone, which is exactly where they are
   opened, since every one of them arrives by SMS or email. */
.shell--plain {
    margin-top: calc(var(--step) * 6);
    max-width: 34rem;
}

.card > .eyebrow:first-child {
    margin-top: 0;
}

.card h1 {
    font-size: clamp(1.75rem, 4.5vw, 2.5rem);
}

.lede {
    color: var(--slate);
    margin-bottom: calc(var(--step) * 2);
}

.card {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    padding: clamp(1.5rem, 4vw, 2.75rem);
}

.card + .card {
    margin-top: calc(var(--step) * 3);
}

/* ------------------------------------------------------------------- forms */

form {
    display: flex;
    flex-direction: column;
    gap: calc(var(--step) * 3);
}

.field {
    display: flex;
    flex-direction: column;
    gap: calc(var(--step) * 0.75);
}

.field > span:first-child,
.field-label {
    font-weight: 600;
    font-size: 0.9375rem;
    letter-spacing: 0.01em;
}

.row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: calc(var(--step) * 2);
}

/* Match by EXCLUSION, not by listing text-like types.

   Blazor's <InputText> renders no `type` attribute at all unless one is passed, and
   `input[type='text']` does not match an input that has no type — so an allowlist silently
   left First name, Last name and Organization unstyled while Email and Mobile (which pass
   type="email"/"tel") looked correct. Excluding the types that need their own treatment
   means a new field cannot fall through the gap.

   `select` and `textarea` belong in the SAME rule for the same reason. Styling only `input`
   left every dropdown and multi-line box on browser defaults — no radius, no padding, and a
   13px font next to a 16px one — which reads as a broken field sitting beside a correct one.
   It showed up on the staff add-event form, but the gap was global: any page with a select
   had it. */
input:not([type='checkbox']):not([type='radio']):not([type='submit']):not([type='button']):not([type='hidden']):not([type='file']),
select,
textarea {
    width: 100%;
    font: inherit;
    font-size: 1rem;

    /* Explicit, so a select and an input in the same stack are the same height. Left to the
       defaults a select's line box is shorter than an input's and they sit ~5px apart. */
    line-height: 1.5;
    color: var(--ink);
    background: var(--card);
    border: 1.5px solid var(--line-strong);
    border-radius: var(--radius);
    padding: 0.7rem 0.85rem;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* Room for the native dropdown arrow, which sits inside the padding box.
   min-height is explicit because line-height does NOT reliably drive a <select>'s own
   height — WebKit in particular uses the control's intrinsic metrics — and without it the
   44px touch target holds only by arithmetic that a font or padding tweak would break. */
select {
    padding-right: 2rem;
    min-height: 44px;
}

/* Horizontal drag would break the form's column width; vertical is the useful one. */
textarea {
    resize: vertical;
    min-height: 5rem;
}

input::placeholder,
textarea::placeholder {
    color: var(--slate-soft);
}

input:not([type='checkbox']):not([type='radio']):not([type='submit']):not([type='button']):hover,
select:hover,
textarea:hover {
    border-color: var(--slate-soft);
}

/* The visible ring is the box-shadow, but Windows High Contrast Mode drops shadows
   entirely — so a transparent outline rides along and becomes the ring there. The original
   used a shadow alone at 18% alpha, which vanished in HCM and was barely visible anywhere. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible,
.county:focus-within,
.chip button:focus-visible {
    outline: 2px solid transparent;
    outline-offset: 2px;
    border-color: var(--cyan);
    box-shadow: 0 0 0 3px rgb(37 159 218 / 40%);
}

/* Hidden from sight, still announced. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* The honeypot. Positioned off-screen rather than display:none, because some bots skip
   hidden inputs but happily fill positioned ones. This class had NO stylesheet behind it
   until now, so the trap was rendering as a visible "Website" box on the login page. */
.hp {
    position: absolute;
    left: -9999px;
    top: auto;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.footnote {
    text-align: center;
    color: var(--slate);
    font-size: 0.9375rem;
    margin: calc(var(--step) * 3) auto 0;
}

/* ------------------------------------------------------------- portal detail */

.detail {
    display: grid;
    grid-template-columns: minmax(7rem, auto) 1fr;
    gap: 0.5rem calc(var(--step) * 2);
    margin: calc(var(--step) * 2) 0 calc(var(--step) * 3);
}

.detail dt {
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--slate);
    padding-top: 0.15rem;
}

.detail dd {
    margin: 0;
    overflow-wrap: anywhere;   /* long email addresses must not widen the page */
}

/* A stack on a narrow screen: a two-column definition list at 375px leaves the
   value column too narrow for an email address. */
@media (max-width: 30rem) {
    .detail {
        grid-template-columns: 1fr;
        gap: 0.15rem;
    }

    .detail dd {
        margin-bottom: var(--step);
    }
}

.chips--static {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    margin: 0;
}

/* No remove button, so no padding reserved for one. */
.chip--plain {
    padding: 0.2rem 0.7rem;
}

.btn-link {
    display: inline-block;
    background: var(--navy);
    color: #fff;
    text-decoration: none;
    font-weight: 700;
    font-size: 0.9375rem;
    border-radius: var(--radius-pill);
    padding: 0.7rem 1.5rem;
    transition: background 0.15s ease;
}

.btn-link:hover {
    background: var(--cyan-deep);
    color: #fff;
}

/* The vendor line, quiet by design. */
.vendor {
    text-align: center;
    font-size: 0.6875rem;
    color: var(--slate-soft);
    line-height: 1.45;
    padding: calc(var(--step) * 4) var(--step) calc(var(--step) * 2);
    margin: 0;
    max-width: none;
}

fieldset {
    border: 0;
    padding: 0;
    margin: 0;
    min-width: 0;
}

legend {
    font-family: Fraunces, serif;
    font-variation-settings: 'SOFT' 20, 'WONK' 1;
    font-size: 1.25rem;
    font-weight: 700;
    padding: 0;
    margin-bottom: calc(var(--step) * 0.5);
}

/* ---------------------------------------------------------- county picker */

/* With no ZIP field there is no proximity pre-check, so this control is the
   whole job: 159 counties, found fast, on a phone. Search narrows, chosen
   counties surface as removable chips so the selection is never buried, and
   the list itself is a dense grid rather than a stack of 159 rows. */

.picker-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--step);
    margin-bottom: var(--step);
}

.count-badge {
    flex: none;
    font-size: 0.8125rem;
    font-weight: 700;
    color: var(--navy);
    background: rgb(37 159 218 / 12%);
    border-radius: var(--radius-pill);
    padding: 0.15rem 0.7rem;
}

.count-badge[data-empty='true'] {
    color: var(--slate);
    background: rgb(91 107 120 / 10%);
}

.county-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
    gap: calc(var(--step) * 0.75);
    max-height: 19rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: calc(var(--step) * 1.5);
    border: 1.5px solid var(--line-strong);
    border-radius: var(--radius);
    background: var(--paper);
    /* A hairline fade at the bottom edge so it reads as scrollable without a
       "scroll for more" label. */
    mask-image: linear-gradient(to bottom, #000 calc(100% - 1.5rem), transparent);
}

.county {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0.4rem 0.6rem;
    border-radius: var(--radius-pill);
    background: var(--card);
    border: 1.5px solid var(--line);
    font-size: 0.9375rem;
    cursor: pointer;
    user-select: none;
    transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.county:hover {
    border-color: var(--cyan);
}

.county input {
    accent-color: var(--cyan);
    width: 1rem;
    height: 1rem;
    margin: 0;
    flex: none;
    cursor: pointer;
}

.county:has(input:checked) {
    background: var(--navy);
    border-color: var(--navy);
    color: #fff;
    font-weight: 600;
}

.county[hidden] {
    display: none;
}

.county-empty {
    grid-column: 1 / -1;
    color: var(--slate);
    font-size: 0.9375rem;
    padding: var(--step);
}

/* Chosen counties, surfaced above the list so a selection made before
   searching never scrolls out of sight. */
.chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: var(--step);
}

.chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    background: var(--navy);
    color: #fff;
    font-size: 0.8125rem;
    font-weight: 600;
    border-radius: var(--radius-pill);
    padding: 0.2rem 0.35rem 0.2rem 0.7rem;
}

.chip button {
    all: unset;
    display: grid;
    place-items: center;
    width: 1.35rem;
    height: 1.35rem;
    border-radius: 50%;
    cursor: pointer;
    color: #cfe0ec;
    font-size: 0.9rem;
    line-height: 1;
}

.chip button:hover {
    background: rgb(255 255 255 / 18%);
    color: #fff;
}

/* ----------------------------------------------------------------- consent */

/* Consent is a compliance gate, so it gets visual weight rather than being a
   footnote: boxed, never pre-ticked, each channel separate. */
.consent {
    /* The county list is a dense, scrollable, bordered field and the consent
       block is the most consequential control on the page. Butted together they
       read as one continuous region — the consent rows look like the last rows
       of the picker. This margin sits on top of the form's own 24px gap, so the
       break is ~64px: clearly a new decision, not more of the same one. */
    margin-top: calc(var(--step) * 5);
    display: flex;
    flex-direction: column;
    gap: calc(var(--step) * 1.5);
    background: var(--paper);
    border: 1.5px solid var(--line);
    border-left: 4px solid var(--cyan);
    border-radius: var(--radius);
    padding: calc(var(--step) * 2);
}

.consent label {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.65rem;
    align-items: start;
    cursor: pointer;
    font-size: 0.9375rem;
}

.consent input[type='checkbox'] {
    accent-color: var(--cyan);
    width: 1.15rem;
    height: 1.15rem;
    margin: 0.2rem 0 0;
    cursor: pointer;
}

/* ---------------------------------------------------------------- buttons */

button[type='submit'] {
    font-family: 'Public Sans', sans-serif;
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    color: #fff;
    background: var(--navy);
    border: 0;
    border-radius: var(--radius-pill);
    padding: 0.85rem 2rem;
    cursor: pointer;
    align-self: start;
    box-shadow: var(--shadow-sm);
    transition: background 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease;
}

button[type='submit']:hover {
    background: var(--cyan-deep);
    box-shadow: var(--shadow);
}

button[type='submit']:active {
    transform: translateY(1px);
}

/* ------------------------------------------------------------- validation */

.errors {
    background: var(--danger-bg);
    border: 1.5px solid rgb(179 38 30 / 35%);
    border-left: 4px solid var(--danger);
    border-radius: var(--radius);
    padding: calc(var(--step) * 2);
    color: var(--danger);
}

.errors p {
    font-weight: 700;
    margin: 0 0 0.35rem;
}

.errors ul {
    margin: 0;
    padding-left: 1.15rem;
}

.validation-message {
    color: var(--danger);
    font-size: 0.875rem;
}

/* ---------------------------------------------------------- confirmations */

.confirmation {
    border-left: 4px solid var(--success);
}

.confirmation h1,
.confirmation h2 {
    margin-bottom: var(--step);
}

.event {
    background: var(--paper);
    border-radius: var(--radius);
    padding: calc(var(--step) * 2);
    border: 1.5px solid var(--line);
}

/* ------------------------------------------------------------ touch targets */

/* Measured at 375px: county pills came out 39px tall and a consent row 24px,
   both under the 44px minimum. Consent is the most important control on the
   page and the one most often tapped on a phone, since this link arrives by
   SMS. Keyed to pointer type rather than width so a touchscreen laptop gets it
   too, and so desktop keeps the tighter rhythm. */
@media (pointer: coarse) {
    .county {
        min-height: 44px;
        padding-block: 0.55rem;
    }

    .consent label {
        min-height: 44px;
        align-content: center;
    }

    .consent input[type='checkbox'] {
        width: 1.35rem;
        height: 1.35rem;
    }

    /* The only way to remove a chip by tapping, on a page whose audience is
       overwhelmingly on a phone. Padding on the chip gives the button a 44px
       hit area without inflating the chip itself. */
    .chip {
        padding-block: 0.45rem;
    }

    .chip button {
        width: 1.9rem;
        height: 1.9rem;
    }
}

/* --------------------------------------------------------------- motion */

/* One orchestrated entrance rather than scattered micro-animations. */
@media (prefers-reduced-motion: no-preference) {
    .hero,
    .masthead,
    .shell > * {
        animation: rise 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
    }

    .masthead { animation-delay: 0.06s; }
    .shell > * { animation-delay: 0.12s; }

    @keyframes rise {
        from { opacity: 0; transform: translateY(12px); }
        to { opacity: 1; transform: none; }
    }
}

/* --------------------------------------------------------------- responsive */

@media (max-width: 34rem) {
    body {
        font-size: 1rem;
    }

    .row {
        grid-template-columns: 1fr;
    }

    .shell {
        padding-inline: var(--step);
    }

    .shell--overlap {
        margin-top: calc(var(--step) * -2);
    }

    .county-list {
        max-height: 15rem;
    }
}

/* ------------------------------------------------------------ blazor chrome */

h1:focus {
    outline: none;
}

#blazor-error-ui {
    display: none;
    position: fixed;
    inset: auto 0 0 0;
    z-index: 1000;
    background: var(--navy-deep);
    color: #fff;
    padding: 0.9rem 1.25rem;
    box-shadow: var(--shadow-lg);
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    float: right;
}

.blazor-error-boundary {
    background: var(--danger);
    color: #fff;
    padding: 1rem;
    border-radius: var(--radius);
}

.blazor-error-boundary::after {
    content: 'An error has occurred.';
}

/* ------------------------------------------------------------- portal events */

.event-list {
    list-style: none;
    margin: calc(var(--step) * 2) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: calc(var(--step) * 2);
}

.event-row {
    display: grid;
    grid-template-columns: 4.5rem 1fr auto;
    gap: calc(var(--step) * 2);
    align-items: start;
    padding: calc(var(--step) * 2);
    border: 1.5px solid var(--line);
    border-radius: var(--radius);
    background: var(--paper);
}

/* An event you have committed to should be obvious at a glance in a long list. */
.event-row.going {
    border-color: var(--cyan);
    background: rgb(37 159 218 / 6%);
}

.event-when {
    text-align: center;
    line-height: 1.2;
}

.event-day {
    display: block;
    font-weight: 700;
    font-size: 0.875rem;
}

.event-time {
    display: block;
    font-size: 0.8125rem;
    color: var(--slate);
}

.event-body h3 {
    font-size: 1.0625rem;
    margin-bottom: 0.15rem;
}

.event-where {
    margin: 0 0 0.35rem;
    font-size: 0.875rem;
    color: var(--slate);
}

.event-desc {
    margin: 0 0 0.35rem;
    font-size: 0.9375rem;
}

.event-going {
    margin: 0;
    font-size: 0.8125rem;
    color: var(--slate);
}

.rsvp {
    font: 700 0.875rem 'Public Sans', sans-serif;
    color: #fff;
    background: var(--navy);
    border: 0;
    border-radius: var(--radius-pill);
    padding: 0.6rem 1.1rem;
    cursor: pointer;
    white-space: nowrap;
    min-height: 44px;
}

.rsvp:hover { background: var(--cyan-deep); }

.rsvp--on {
    background: var(--card);
    color: var(--danger);
    border: 1.5px solid var(--danger);
}

.rsvp--on:hover { background: var(--danger-bg); }

.rsvp:disabled { opacity: 0.55; cursor: default; }

/*
   On a phone the row stacks and the RSVP button goes FULL WIDTH at the bottom —
   the primary action on the primary screen, reached from an SMS link. The
   original hid it entirely at this width.
*/
@media (max-width: 34rem) {
    .event-row {
        grid-template-columns: 4.5rem 1fr;
    }

    .event-action {
        grid-column: 1 / -1;
    }

    .rsvp {
        width: 100%;
    }
}


/* --------------------------------------------------- uploads & media galleries */
/* Shared, not staff-only: the volunteer portal's captain panel renders these too, and a
   public component must not depend on the staff stylesheet happening to be loaded. */

/* app.css's shared field rule deliberately excludes [type='file'] — the widget does not
   take the same treatment — so it needs the box drawn here, or it sits borderless beside
   the fields above it. */
input[type='file'] {
    width: 100%;
    font: inherit;
    font-size: 1rem;
    color: var(--ink);
    background: var(--card);
    border: 1.5px solid var(--line-strong);
    border-radius: var(--radius);
    padding: 0.45rem 0.85rem;
    min-height: 44px;
}

input[type='file']::file-selector-button {
    font: 600 0.875rem 'Public Sans', sans-serif;
    background: var(--card);
    color: var(--ink);
    border: 1.5px solid var(--line-strong);
    border-radius: var(--radius-pill);
    padding: 0.45rem 1rem;
    margin-right: 0.75rem;
    cursor: pointer;
}

input[type='file']::file-selector-button:hover {
    color: var(--ink);
    border-color: var(--ink);
}

.gallery {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    gap: calc(var(--step) * 2);
}

.gallery-item {
    background: var(--card);
    border: 1.5px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.gallery-item > a {
    display: block;
    aspect-ratio: 4 / 3;
    background: var(--paper);
}

.gallery-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* A video's poster frame is not free here — a legacy Drive thumbnail renders a still, and a
   pasted link has none at all. A glyph is honest about that and keeps the grid even. */
.gallery-video {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    font-size: 2rem;
    color: var(--cyan);
}

.gallery-meta {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: var(--step) calc(var(--step) * 1.5) calc(var(--step) * 1.5);
    font-size: 0.8125rem;
    color: var(--slate);
}

.gallery-meta strong {
    color: var(--ink);
    font-size: 0.875rem;
    line-height: 1.3;
}

.gallery-caption {
    color: var(--ink);
    font-style: italic;
}

/* ------------------------------------------------------------ environment banner */

/* Demo and Production are two databases on one SQL Server, so nothing about the page
   itself tells you which one you are looking at. This does. Sticky rather than static:
   it must still be visible after scrolling into the middle of a volunteer list. */
.env-banner {
    position: sticky;
    top: 0;
    z-index: 100;
    padding: 0.5rem 1rem;
    background: var(--navy-deep);
    color: #fff;
    font: 600 0.8125rem 'Public Sans', sans-serif;
    letter-spacing: 0.04em;
    text-align: center;
    text-transform: uppercase;
}

/* A non-production environment that CAN send to real people is the dangerous
   configuration, not the safe one, and it should not look the same as the safe one. */
.env-banner--hot {
    background: var(--danger);
}

/* ------------------------------------------------------------------ album index */

.album-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
    gap: calc(var(--step) * 2);
}

.album > a {
    display: block;
    text-decoration: none;
    color: inherit;
    background: var(--card);
    border: 1.5px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.album > a:hover {
    border-color: var(--cyan);
    box-shadow: var(--shadow-sm);
}

.album-cover {
    position: relative;
    display: block;
    aspect-ratio: 3 / 2;
    background: var(--paper);
}

.album-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* The count sits on the cover so an album reads as a stack rather than a single photo. */
.album-count {
    position: absolute;
    right: 0.5rem;
    bottom: 0.5rem;
    background: rgb(10 36 56 / 80%);
    color: #fff;
    font: 700 0.75rem 'Public Sans', sans-serif;
    padding: 0.15rem 0.55rem;
    border-radius: var(--radius-pill);
}

.album-meta {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: var(--step) calc(var(--step) * 1.5) calc(var(--step) * 1.5);
    font-size: 0.8125rem;
    color: var(--slate);
}

.album-meta strong {
    color: var(--ink);
    font-size: 0.9375rem;
    line-height: 1.3;
}
