﻿/* ═══════════════════════════════════════════════════════════════════════════
   AVL Association of Nepal, front of house
   ═══════════════════════════════════════════════════════════════════════════

   The visual language began by borrowing from the rooms our members work in:
   FOH positions, console surfaces, LED processors, dimmer racks. Much of that
   has since been dialled back — the page is plain white, the decorative layers
   are gone and the labels are a sans rather than a monospace — so what remains
   of it is the restraint: thin rules instead of heavy borders, and colour used
   the way a lighting designer uses it, sparingly, as signal.

   Structure:
     1  Tokens
     2  Reset & typography
     3  Layout primitives
     4  Decorative layers (removed)
     5  Header & navigation
     6  Buttons
     7  Hero
     8  Sections & headings
     9  Cards & glass surfaces
    10  Pillars
    11  Pathway / spec list
    12  Events, news, timeline
    13  Gallery & masonry
    14  Credentials (membership)
    15  Carousel, marquee, accordion
    16  Committee
    17  Forms & contact
    18  Footer
    19  Page header (inner pages)
    20  Responsive
    21  Reduced motion & print
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1 · Tokens ─────────────────────────────────────────────────────────── */

/* One token contract, two blocks — but only one of them ships.
 *
 * The site is LIGHT-ONLY. partials/header.php writes data-theme="light" on
 * <html> as a constant; there is no switch, no stored preference and no OS
 * sniffing. The light values in the [data-theme="light"] block below are
 * therefore the ones that render, every time.
 *
 * The dark values remain here in :root as the base that light overrides. They
 * are what you see if data-theme is ever missing, and they are deliberately
 * left in place: the light block is written as a *diff* against them, so
 * deleting them would mean flattening ~90 overrides into this block and
 * re-checking every component. Not worth the regression risk for tokens that
 * cost nothing to keep. If the dark theme is ever wanted back, restoring the
 * switch is all it takes.
 *
 * Component rules are never themed directly — only the values below, plus the
 * handful of places that need different *behaviour* (blend modes, photo
 * grading, the map filter), collected at the end of this section.
 *
 * Colour names are *roles*, not hues, and keep their original dark meaning:
 *   --blue  = primary action        (flag crimson as it renders)
 *   --cyan  = technical label/focus (flag blue as it renders)
 *   --amber = warm emphasis text
 * Naming them after the dark palette is a compromise, but renaming them to
 * --primary/--label/--warm across 2,000 lines buys nothing at this point.
 */

:root {
  /* Substrate. Four steps of near-black rather than one, so panels separate
     by luminance alone and we never need a heavy border to define an edge. */
  --bg-void:   #05070a;   /* page base, behind everything */
  --bg-deep:   #0a0d12;   /* default section */
  --bg-panel:  #0f141b;   /* cards, raised blocks */
  --graphite:  #161c24;   /* inputs, chips, inner wells */

  /* Hairlines. Everything that would be a 1px border in a light design is a
     translucent white rule here, it sits correctly on any of the four
     substrates without needing a per-surface value. */
  --line:      rgba(255, 255, 255, 0.08);
  --line-soft: rgba(255, 255, 255, 0.05);
  --line-hot:  rgba(46, 125, 255, 0.38);

  /* Signal colours. Read these as gel colours: each one means something.
     blue = primary action and structure, cyan = live data and technical
     labels, purple = the third beam, amber = warmth and emphasis,
     crimson = the flag, and "on air". */
  --blue:      #2e7dff;
  --blue-lit:  #5b9bff;   /* 7.0:1 on --bg-deep, the body-size variant */
  --blue-deep: #14337a;
  --cyan:      #22d3ee;
  --purple:    #8b5cf6;
  --amber:     #f5a524;
  --crimson:   #ce1126;   /* national flag; used for LIVE and the logo mark */

  /* Type colours. The brief asks for white typography, so hue lives in the
     accents and the text ramp stays neutral.
     Measured on --bg-deep: --text 17.9:1, --text-2 9.1:1, --text-3 4.6:1.
     --text-3 is therefore fine for UI labels and meta but never for prose. */
  --text:    #f5f7fa;
  --text-2:  #a7b0be;
  --text-3:  #6b7686;

  /* Glass. One recipe, used everywhere, so panels feel like one material. */
  --glass-bg:     rgba(255, 255, 255, 0.028);
  --glass-bg-lit: rgba(255, 255, 255, 0.055);
  --glass-blur:   14px;

  /* Glow. Layered rather than single, so the falloff reads optical instead
     of like a drop shadow with a colour picked at random. */
  --glow-blue:   0 0 0 1px rgba(46, 125, 255, 0.28), 0 8px 40px -12px rgba(46, 125, 255, 0.45);
  --glow-cyan:   0 0 0 1px rgba(34, 211, 238, 0.28), 0 8px 40px -12px rgba(34, 211, 238, 0.42);
  --glow-amber:  0 0 0 1px rgba(245, 165, 36, 0.30), 0 8px 40px -12px rgba(245, 165, 36, 0.42);
  --shadow-deep: 0 24px 60px -24px rgba(0, 0, 0, 0.9);
  --shadow-lift: 0 2px 4px rgba(0, 0, 0, 0.4), 0 24px 48px -28px rgba(0, 0, 0, 0.9);
  /* Card rest/hover pair for the square-cornered, borderless news cards. On
     dark the first stop is a hairline ring doing the job the 1px border used
     to — a cast shadow alone is invisible against #0a0d12. */
  --shadow-card:      0 0 0 1px var(--line), 0 1px 2px rgba(0, 0, 0, 0.5),
                      0 10px 24px -18px rgba(0, 0, 0, 0.9);
  --shadow-card-hover: 0 0 0 1px var(--line), 0 3px 6px rgba(0, 0, 0, 0.5),
                      0 18px 36px -20px rgba(0, 0, 0, 0.95);

  /* Focus ring: cyan, because it is the one colour nothing else in the
     layout uses as a large fill, so it can never be mistaken for decoration. */
  --ring: 0 0 0 2px var(--bg-void), 0 0 0 4px var(--cyan);

  /* Shape. Tighter than the previous design, console hardware is squarer. */
  --radius-sm:   6px;
  --radius-md:   10px;
  --radius-lg:   16px;
  --radius-xl:   24px;
  --radius-full: 999px;

  /* Spacing (8px base). Names kept from the previous system so the admin
     token block stays parallel and either sheet can be read against the other. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 28px;
  --space-7: 36px;
  --space-8: 48px;
  --space-9: 64px;
  --space-10: 88px;
  --space-11: 112px;
  --space-12: 148px;

  /* Type scale. Editorial and large at the top, disciplined below it -
     hierarchy comes from the gap between --text-hero and --text-base, not
     from six barely-distinguishable middle steps. */
  --text-micro:   0.68rem;   /* index badges and the smallest labels */
  --text-caption: 0.75rem;   /* eyebrows, meta, tags */
  --text-xs:      0.8rem;
  --text-sm:      0.9rem;
  --text-base:    1rem;
  --text-md:      1.1rem;
  --text-lg:      1.3rem;
  --text-xl:      clamp(1.6rem, 1.35rem + 1.1vw, 2.1rem);
  --text-2xl:     clamp(2.1rem, 1.6rem + 2.2vw, 3.4rem);
  --text-3xl:     clamp(2.6rem, 1.7rem + 3.6vw, 4.6rem);
  /* The cap has to stay clear of --text-3xl above or the scale inverts, so
     5rem is the floor for how far this can come down without also moving 3xl.
     It was 6.4rem, which overpowered the hero once the headline column
     narrowed to make room for the photo grid. */
  --text-hero:    clamp(2.75rem, 1.5rem + 4.3vw, 5rem);

  --font-display: "Livvic", "Inter", system-ui, sans-serif;
  --font-body:    "Livvic", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* Technical labels — eyebrows, meta rows, tags, index badges, form labels.
     This was JetBrains Mono; the monospace voice was dropped because it read
     as code rather than as an institution. Labels now share the body face and
     get their character from size, uppercase and letter-spacing instead. Point
     this at its own stack to give them a distinct face again. */
  --font-label:   var(--font-body);
  /* Humanist companion, used for committee designations. See the note on the
     font link in partials/header.php for why job titles are not mono. */
  --font-alt:     "Livvic", system-ui, sans-serif;

  /* Effectively full-bleed. At 1280px the content sat in a 1280 column with
     ~320px of dead white either side on an ordinary 1920 monitor, while the
     header and footer ran to the edges — so the page read as a narrow strip
     inside a wide frame. These are a sanity cap for very wide displays rather
     than a measure: below 2160px the layout is edge-to-edge, and the things
     that genuinely need a reading measure (--section-head p, .hero p) set their
     own max-width in ch and are unaffected by this. */
  --max-width: 2160px;
  --wide-width: 2160px;

  /* One gutter for the whole page. The container, the header's top strip and
     the footer all use this, so every edge on the page lines up. */
  --page-gutter: var(--space-6);

  /* Surfaces and treatments that differ between themes by behaviour, not just
     by value. Collected here so the light block below stays a token swap. */
  --btn-fill:         var(--blue);
  --btn-fill-hover:   #4a8dff;
  --btn-ink:          #ffffff;
  --btn-accent-fill:       var(--amber);
  --btn-accent-fill-hover: #ffb739;
  --btn-accent-ink:        #150d00;
  /* .btn-navy. Lifted off the flag blue on dark, where #003893 is nearly
     invisible against the near-black header. */
  --btn-navy-fill:         #2e7dff;
  --btn-navy-fill-hover:   #4a8dff;
  --sheen:        rgba(255, 255, 255, 0.28);
  /* Footer wash. Heavier alpha than light: over a near-black base a tint has
     to be stronger to register at all. */
  --footer-tint-a: rgba(46, 125, 255, 0.12);
  --footer-tint-b: rgba(46, 125, 255, 0.03);

  --header-bg:    rgba(5, 7, 10, 0.72);
  /* Once scrolled, the bar goes near-opaque: at 0.72 over a busy section the
     text scrolling behind it stayed legible enough to compete with the nav. */
  --header-bg-solid: rgba(5, 7, 10, 0.94);
  --drawer-bg:    rgba(5, 7, 10, 0.97);
  --backdrop:     rgba(2, 3, 5, 0.85);
  --scrim-top:    rgba(5, 7, 10, 0.9);
  --scrim-mid:    rgba(5, 7, 10, 0.55);
  --scrim-base:   var(--bg-void);
  --danger:       #ff5c6e;
  --danger-soft:  #ff7080;
  --beam-blend:   screen;
  --beam-a: rgba(46, 125, 255, 0.55);
  --beam-b: rgba(139, 92, 246, 0.42);
  --beam-c: rgba(34, 211, 238, 0.38);
  --lit-gradient: linear-gradient(100deg, var(--cyan), var(--blue) 45%, var(--purple));
  --grain-opacity: 0.035;
  --map-filter: invert(0.92) hue-rotate(185deg) saturate(0.7) brightness(0.95) contrast(0.92);
  /* Photography is graded down hard in dark so a bright still never punches a
     hole in the page; in light it only needs a touch of restraint. */
  --photo-filter:       grayscale(0.15) brightness(0.95) contrast(1.04);
  --photo-filter-hover: grayscale(0) brightness(1) contrast(1.02);
  --wordmark-stroke: rgba(255, 255, 255, 0.07);

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --transition-fast: 0.2s var(--ease-out);
  --transition-mid:  0.4s var(--ease-out);
  --transition-slow: 0.7s var(--ease-out);
}

/* ── Light theme ────────────────────────────────────────────────────────────
   Warm off-white paper, graphite type, and the national flag doing the work:
   crimson carries every fill and primary action (the flag's field), blue
   carries labels, links and focus (its border). That division is the same one
   the previous build arrived at, and it is the reason the page reads as an
   established institution rather than as a product launch.

   Every value here clears WCAG AA against the surface it lands on. The tight
   ones, measured on #ffffff: crimson #ce1126 5.6:1, flag blue #003893 11.4:1,
   --text-2 #4a525c 7.8:1, --text-3 #78818c 4.6:1 (labels only, never prose). */

:root[data-theme="light"] {
  /* These were all plain white for a while, with the alternation left to the
     hairline borders on .section-alt. On a long page that read as one
     undifferentiated sheet — you could not tell where one section ended and
     the next began — so the tint is back. Three steps, all warm and all very
     close together: enough that neighbouring bands separate, not so much that
     the page looks striped. Every consecutive pair on the home page differs. */
  /* Cool greys, not the warm beige these used to be. #f6f7f9 is the banded
     background — News and the other .section-alt bands — and the other two are
     the same hue at different strengths so the page steps between them without
     changing temperature halfway down. */
  --bg-void:   #fbfcfd;
  --bg-deep:   #ffffff;
  --bg-panel:  #f6f7f9;
  /* Was a warm #f4f1ec. Cooled to sit in the same family as the backgrounds
     above — it shows through as the empty state behind photos and inside form
     fields, where a beige well against a cool grey band was the one place the
     two temperatures met. */
  --graphite:  #eceef2;   /* inset wells and inputs, still tinted to read as recessed */

  --line:      rgba(26, 29, 33, 0.13);
  --line-soft: rgba(26, 29, 33, 0.07);
  --line-hot:  rgba(206, 17, 38, 0.42);

  --blue:      #ce1126;   /* primary action: the flag crimson */
  --blue-lit:  #002a6e;   /* body-size links: flag blue, darkened for prose */
  --blue-deep: #7d0a17;
  --cyan:      #003893;   /* technical labels, eyebrows, focus */
  --purple:    #5b21b6;
  --amber:     #8a5a00;   /* warm emphasis at text size */
  --crimson:   #ce1126;

  --text:    #14171a;
  --text-2:  #4a525c;
  --text-3:  #78818c;

  --glass-bg:     rgba(26, 29, 33, 0.022);
  --glass-bg-lit: rgba(26, 29, 33, 0.045);

  /* Light needs real shadows where dark used glow: on white a glow is
     invisible, and depth has to come from a cast shadow instead. */
  --glow-blue:  0 0 0 1px rgba(206, 17, 38, 0.22), 0 10px 30px -14px rgba(206, 17, 38, 0.5);
  --glow-cyan:  0 0 0 1px rgba(0, 56, 147, 0.22), 0 10px 30px -14px rgba(0, 56, 147, 0.45);
  --glow-amber: 0 0 0 1px rgba(138, 90, 0, 0.25), 0 10px 30px -14px rgba(138, 90, 0, 0.45);
  --shadow-deep: 0 24px 60px -28px rgba(20, 23, 26, 0.28);
  --shadow-lift: 0 1px 2px rgba(20, 23, 26, 0.05), 0 18px 40px -26px rgba(20, 23, 26, 0.3);
  /* No ring on light: white cards sit on a near-white band and the cast
     shadow is enough to lift them, the way the reference row does it. */
  --shadow-card:       0 1px 2px rgba(20, 23, 26, 0.06), 0 8px 20px -12px rgba(20, 23, 26, 0.18);
  --shadow-card-hover: 0 2px 4px rgba(20, 23, 26, 0.08), 0 16px 32px -14px rgba(20, 23, 26, 0.22);

  --btn-fill:       var(--blue);
  --btn-fill-hover: #b00f21;
  --btn-ink:        #ffffff;
  /* Both buttons stay crimson. Splitting them between crimson and blue was
     tried in an earlier build: the page read blue overall and no single colour
     meant "this is the main action". --btn-outline carries the secondary. */
  --btn-accent-fill:       #ce1126;
  --btn-accent-fill-hover: #b00f21;
  --btn-accent-ink:        #ffffff;
  /* .btn-navy — the flag blue, and the exception to the note above. Two buttons
     sitting together in the top bar are a pair to choose between, not two
     competing calls to action on a page, so there the second colour helps. */
  --btn-navy-fill:         #003893;
  --btn-navy-fill-hover:   #002a6e;

  --sheen:      rgba(255, 255, 255, 0.35);
  /* Flag blue at low alpha over white: strongest where the footer meets the
     page and fading down, so the edge is a transition rather than a slab. */
  --footer-tint-a: rgba(0, 56, 147, 0.10);
  --footer-tint-b: rgba(0, 56, 147, 0.02);

  --header-bg:  rgba(255, 255, 255, 0.86);
  --header-bg-solid: rgba(255, 255, 255, 0.97);
  --drawer-bg:  rgba(255, 255, 255, 0.98);
  --backdrop:   rgba(28, 26, 24, 0.45);
  --scrim-top:  rgba(255, 255, 255, 0.86);
  --scrim-mid:  rgba(255, 255, 255, 0.5);
  --danger:      #c00e1f;
  --danger-soft: #a80e1f;

  /* Screen blending adds light, which does nothing on white. Multiply removes
     it, so the beams read as a colour wash across paper instead of vanishing. */
  --beam-blend: multiply;
  --beam-a: rgba(206, 17, 38, 0.16);
  --beam-b: rgba(0, 56, 147, 0.13);
  --beam-c: rgba(91, 33, 182, 0.1);
  --lit-gradient: linear-gradient(100deg, var(--crimson), #7d0a17 55%, var(--cyan));
  --grain-opacity: 0.02;
  --map-filter: saturate(0.85) contrast(0.96);
  --photo-filter:        grayscale(0.12) contrast(1.02);
  --photo-filter-hover:  none;
  --portrait-filter:     grayscale(0.85) contrast(1.03);
  --hero-media-filter:   grayscale(0.3) contrast(1.02) brightness(1.02);
  --wordmark-stroke: rgba(20, 23, 26, 0.08);

  color-scheme: light;
}

/* The few places where light needs different behaviour, not a different value.
   Kept together so there is one list to check when a component is added. */

/* The duotone wash this used to switch off is gone from both themes now —
   committee portraits run in full colour everywhere. */

/* Cards read as raised paper rather than as lit glass: a cast shadow and a
   solid fill, because a translucent panel over white has nothing to reveal. */
:root[data-theme="light"] .card,
:root[data-theme="light"] .path-card,
:root[data-theme="light"] .quote,
:root[data-theme="light"] .event-card,
:root[data-theme="light"] .member-card {
  background: var(--bg-panel);
  box-shadow: var(--shadow-lift);
}
/* .hero-slider-stage used to be in that list, when it was a framed panel beside
   the copy. As a full-bleed backdrop it has no card treatment to give it. */
:root[data-theme="light"] .statement {
  background: linear-gradient(160deg, var(--bg-panel), var(--bg-deep));
  box-shadow: var(--shadow-lift);
}
:root[data-theme="light"] .credential {
  background: linear-gradient(165deg, #ffffff, var(--bg-deep));
  box-shadow: var(--shadow-lift);
}
/* The lanyard slot is punched through the card, so it has to be darker than
   the card in light and lighter in dark. */
:root[data-theme="light"] .credential::before { background: rgba(20, 23, 26, 0.16); }
:root[data-theme="light"] .credential-code { opacity: 0.5; }

/* The pillar wash is a colour cast on paper, so it needs more of it to read
   at all, and the panel itself sits on the warm ground rather than a panel. */
:root[data-theme="light"] .pillar { background: var(--bg-panel); box-shadow: var(--shadow-lift); }
:root[data-theme="light"] .pillar::after { opacity: 0.1; }
:root[data-theme="light"] .pillar:hover::after { opacity: 0.16; }

/* The nav underline and LED glow are pure light effects. They read as blur on
   paper, so they lose the bloom and keep the line. */
:root[data-theme="light"] nav.main-nav a:not(.btn)::after { box-shadow: none; }
:root[data-theme="light"] .timeline-item::before { box-shadow: none; }
:root[data-theme="light"] .pillar::before { box-shadow: none; }

/* Three chips carry literal electric-blue and amber tints from the dark
   palette. Restate them here rather than tokenising three one-off rgba pairs
   that nothing else would ever use. */
:root[data-theme="light"] .path-level {
  border-color: rgba(206, 17, 38, 0.3);
  background: rgba(206, 17, 38, 0.05);
  color: var(--blue);
}
:root[data-theme="light"] .credential-class {
  border-color: rgba(138, 90, 0, 0.35);
  background: rgba(138, 90, 0, 0.06);
}
:root[data-theme="light"] .tag-hot {
  border-color: rgba(138, 90, 0, 0.35);
  background: rgba(138, 90, 0, 0.06);
}

/* ── 2 · Reset & typography ─────────────────────────────────────────────── */

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

html {
  scroll-behavior: smooth;
  /* The sticky header would otherwise cover the target of every #anchor. */
  scroll-padding-top: 96px;
  background: var(--bg-void);
}

body {
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: 400;
  line-height: 1.65;
  color: var(--text-2);
  background: var(--bg-void);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* Set by js/script.js while the mobile nav drawer is open, so the page behind it
   does not scroll under your thumb. On <html> rather than <body> because body
   owns overflow-x above and a second `overflow` here would replace it. */
html.nav-locked { overflow: hidden; }

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--text);
  text-wrap: balance;
}

/* Display sizes get real negative tracking. At 4rem+ the default spacing
   reads as gaps between letters rather than as a word. */
h1, h2 { line-height: 1.02; letter-spacing: -0.032em; }
h3, h4 { line-height: 1.25; letter-spacing: -0.014em; }

p { line-height: 1.7; max-width: 58ch; }
p.lead { font-size: var(--text-md); color: var(--text-2); }

ul { list-style: none; }
img, svg, video { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }

strong, b { color: var(--text); font-weight: 600; }

::selection { background: var(--blue); color: #fff; }

:focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: var(--radius-sm);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: var(--space-3);
  z-index: 300;
  background: var(--blue);
  color: #fff;
  padding: 12px 20px;
  border-radius: var(--radius-sm);
  font-weight: 600;
}
.skip-link:focus {
  left: var(--space-4);
}

/* ── 3 · Layout primitives ──────────────────────────────────────────────── */

.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--page-gutter);
}
.container-wide { max-width: var(--wide-width); }

.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-6); }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-6); }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-5); }
.grid-spaced { margin-bottom: var(--space-8); }

/* The kicker labels that used to sit above every heading (MISSION, WHAT WE DO,
   EDUCATION) have been removed from the templates: the rule that drew them
   prefixed each one with a short line that read as an em dash, and most of the
   labels only restated the heading underneath. Sections now open on the
   heading itself.

   Nothing sets .eyebrow any more, so the rule is gone rather than left behind
   for someone to wonder about. The .mono utility went the same way: no
   template ever set it, and the name stopped being true once the label face
   became a sans. */

/* Scroll reveal. The element is laid out at full size from the start and
   only its transform and opacity change, so revealing costs no layout and
   contributes nothing to CLS. js/motion.js adds .in; the no-JS and
   reduced-motion paths both leave everything visible (see section 21). */
[data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition:
    opacity var(--transition-slow),
    transform var(--transition-slow);
  transition-delay: calc(var(--i, 0) * 70ms);
}
[data-reveal].in { opacity: 1; transform: none; }

/* -- 4 . Decorative layers ---------------------------------------------- */

/* The animated layers that used to live here are gone: the DMX patch grid,
   the moving-head beams, the spectrum analyser and the film grain, along with
   the .fx positioning wrapper that held them. The site reads as plain white
   now. Their markup has been removed from every template that carried it
   (index, about, page-header, footer), so nothing references these names.

   The tokens they consumed -- --beam-a/b/c, --beam-blend, --grain-opacity --
   are still defined in section 1 and are now unused. */

/* Hairline that glows at its centre, used as a section divider. */
.rule-lit {
  height: 1px;
  border: 0;
  background: linear-gradient(90deg, transparent, var(--line-hot), transparent);
}

/* ── 5 · Header & navigation ────────────────────────────────────────────── */

/* The header used to be transparent until you scrolled, floating over the
   hero with nothing behind it. That works over a dark photographic hero; over
   this one — white, with the headline starting a few lines down — the nav read
   as loose text that happened to be at the top of the page rather than as a
   bar. It now carries its own surface from the first frame. */
/* In the normal flow, not fixed: the header scrolls away with the page rather
   than following it down. It is two tiers tall now, and a bar that size pinned
   to the top spends the whole visit covering the content it sits over.
   position:relative so the mobile nav drawer can hang off it. */
.site-header {
  position: relative;
  z-index: 200;
  background: var(--header-bg-solid, #ffffff);
  /* No bottom border. The blue nav strip already ends the header with a hard
     edge, and a hairline under it drew a pale line between the bar and whatever
     the page opens with — most visibly against the hero photo. */
}
/* js/motion.js still sets .scrolled past 40px. Nothing hangs off it any more —
   the header is gone from view by then — but the class is harmless and the
   hook stays in case the bar is ever pinned again. */

/* Tier one: logo hard left, contact and socials hard right. Deliberately not
   inside .container — this strip is the frame around the page, so it runs to
   the viewport edges with only a small breathing gap. */
.header-top-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  /* Same gutter and cap as .container, so the logo lines up with the content
     below it instead of sitting on a different margin. */
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--page-gutter);
  min-height: 76px;
}

.header-contact {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  font-size: var(--text-sm);
}
.hc-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /* A phone number broken over two lines ("+977-1- / 4000000") is not a phone
     number any more, and it drags the whole 76px tier taller to hold the second
     line. It either fits on its line or the breakpoints below take it away. */
  white-space: nowrap;
  color: var(--text-2);
  transition: color var(--transition-fast);
}
.hc-item:hover { color: var(--blue); }
.hc-item svg { color: var(--cyan); flex: none; }

.header-social {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* A rule rather than a gap: the icons are a different kind of thing from the
     address and number beside them, and at this size only a line says so. */
  padding-left: var(--space-5);
  border-left: 1px solid var(--line);
}
.header-social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  color: var(--text-2);
  transition: color var(--transition-fast), background var(--transition-fast);
}
.header-social a:hover { color: var(--blue); background: var(--graphite); }

/* Sits after the socials. Pill shape so it does not read as another nav item.
   Spaced with margin only — a border-left here would have fought the .btn
   padding and left the label off-centre in its own pill. */
.header-login,
.header-apply {
  margin-left: var(--space-5);
  border-radius: var(--radius-full);
  cursor: pointer;
  /* Same reasoning as .hc-item: "Apply for / Membership" wrapping inside its own
     pill turns a button into a two-line paragraph and unbalances the pair beside
     it. Shrink the row instead — see the tablet block. */
  white-space: nowrap;
}
/* When Apply leads, it takes the margin that separates the pair from the socials
   and Login falls back on the flex gap — otherwise the two pills that belong
   together sit as far apart as they do from everything else. */
.header-apply + .header-login { margin-left: 0; }
/* It goes nowhere yet. A plain <a> with no href is not focusable and not a
   button, so it is marked up as one and given the affordances by hand —
   the alternative, an href="#", promises navigation that does not happen. */
.header-login[aria-disabled="true"] { cursor: default; }

/* The drawer's copy of the member action (partials/header.php). Hidden here and
   shown only in the 760px block: above that width .header-login above is doing
   the job in the top tier, and two live "Member Login" buttons on one page would
   be a second tab stop to the same place. */
.nav-member { display: none; }

/* Tier two: the nav on its own strip, centred, in the flag blue.
   var(--cyan) rather than a fresh hex — in the light palette that token already
   resolves to #003893, the blue of the national flag's border, and it is the
   colour this site uses wherever something has to read as institutional. Using
   the token keeps the strip in the palette instead of beside it. */
.header-bar {
  background: var(--cyan);
}
.header-bar .main-nav ul { justify-content: center; }

/* On the blue the whole link ramp inverts: the muted grey that reads as
   secondary on white is unreadable here, so links are white at rest and the
   underline that marked hover and the current page becomes white too. */
.header-bar nav.main-nav a:not(.btn) {
  color: rgba(255, 255, 255, 0.86);
  padding: 14px 0;
}
.header-bar nav.main-nav a:not(.btn):hover,
.header-bar nav.main-nav a:not(.btn).active { color: #ffffff; }
.header-bar nav.main-nav a:not(.btn)::after {
  background: #ffffff;
  box-shadow: none;
  bottom: 6px;
}

.brand { display: flex; align-items: center; gap: var(--space-4); }
.brand-logo {
  height: 58px;
  width: auto;
  transition: height var(--transition-mid);
}
.site-header.scrolled .brand-logo { height: 46px; }

/* The wordmark beside the logo.
   NOT .brand-name — that name is already taken by the partner-logo carousel down
   in section 15, and reusing it here silently inherited a 12px grey uppercase
   label instead of this.
   It is an <h1>, so the display-heading rules in section 2 reach it first: the
   negative tracking they set is meant for 4rem display type and closes the
   letters up at this size, hence the explicit letter-spacing.
   Colour is var(--cyan) — #003893, the flag blue at full strength, chosen for the
   wordmark on purpose. (The darkened --blue-lit was tried and rejected: it is the
   right call for prose, but a wordmark is signage, and next to a crimson-and-blue
   mark the full-strength blue is the one that matches the artwork.) The token
   rather than the literal, so the wordmark tracks the palette if the blue is ever
   retuned.
   Set on two lines, smaller and at 600 rather than one big bold line. A wordmark
   beside a mark is a caption to it, not a headline: at 1.15rem/700 the name was
   the loudest thing in the bar and the logo read as its bullet point. The break
   comes from max-width in ch — a measure, not a hard <br> — so a name edited in
   Settings → Branding re-wraps instead of breaking in the wrong place. */
.brand-wordmark {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: 0;
  color: var(--cyan);
  /* ONE LINE. This carried max-width: 15ch, which on a 24-character name broke
     "AVL Association of Nepal" after "Association" — and only on the widest
     screens, because the 1080px rule below already sets nowrap. The masthead
     had the most room exactly where it wrapped the name in half.

     A wrapped name stops reading as one name and starts reading as two, which
     is the one thing the line identifying the association must not do. The
     narrow rule at the bottom of this file still lets it wrap on a phone, where
     the trade genuinely is width against legibility.

     min-width:0 stays: it keeps the flex item shrinkable so a very long name
     cannot push the contact strip off the bar. */
  white-space: nowrap;
  min-width: 0;
}

nav.main-nav ul {
  display: flex;
  align-items: center;
  gap: var(--space-6);
}
nav.main-nav a:not(.btn) {
  position: relative;
  display: inline-block;
  padding: 10px 0;
  /* Up from --text-sm (0.9rem). Seven items across a full-width blue strip left
     a lot of air around small type, and the nav was reading as fine print
     rather than as the site's main controls. */
  font-size: 0.975rem;
  font-weight: 500;
  color: var(--text-2);
  transition: color var(--transition-fast);
}
nav.main-nav a:not(.btn)::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 2px;
  width: 0;
  height: 1.5px;
  background: var(--cyan);
  box-shadow: 0 0 10px var(--cyan);
  transition: width var(--transition-mid);
}
nav.main-nav a:not(.btn):hover { color: var(--text); }
nav.main-nav a:not(.btn):hover::after { width: 100%; }
nav.main-nav a:not(.btn).active { color: var(--text); }
nav.main-nav a:not(.btn).active::after { width: 100%; }

.header-tools { display: flex; align-items: center; gap: var(--space-3); }

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  background: var(--glass-bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.nav-toggle span {
  width: 18px;
  height: 1.5px;
  background: var(--text);
  transition: transform var(--transition-fast), opacity var(--transition-fast);
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

/* ── 6 · Buttons ────────────────────────────────────────────────────────── */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 13px 26px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--btn-fill);
  color: var(--btn-ink) !important;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.005em;
  line-height: 1.2;
  cursor: pointer;
  overflow: hidden;
  transition: transform var(--transition-fast), box-shadow var(--transition-fast),
              background var(--transition-fast);
}
.btn:hover {
  background: var(--btn-fill-hover);
  transform: translateY(-2px);
  box-shadow: var(--glow-blue);
}
.btn:active { transform: translateY(0); }

/* Sheen sweep on hover, the highlight moving across a gloss console lid. */
.btn::after {
  content: "";
  position: absolute;
  top: 0;
  left: -120%;
  width: 60%;
  height: 100%;
  background: linear-gradient(100deg, transparent, var(--sheen), transparent);
  transition: left 0.6s var(--ease-out);
}
.btn:hover::after { left: 130%; }

/* The single warmest thing on the page, reserved for the one action we most
   want taken on any given screen. Amber in dark; in light it resolves to
   crimson, because on paper the flag colour is what carries a primary action. */
.btn-accent {
  background: var(--btn-accent-fill);
  color: var(--btn-accent-ink) !important;
}
.btn-accent:hover { background: var(--btn-accent-fill-hover); box-shadow: var(--glow-amber); }

/* The flag's other colour, for the one place two buttons sit side by side and
   have to be told apart at a glance: Apply (crimson) and Login (blue) in the top
   bar. Everywhere else the page keeps its single primary colour — this is not a
   general-purpose second button, and using it as one would undo the reason
   --btn-fill and --btn-accent-fill were collapsed onto crimson in the first
   place (see the note by those tokens).

   #003893 is the blue of the Nepali flag, and it clears 12:1 against white. */
.btn-navy {
  background: var(--btn-navy-fill);
  color: #ffffff !important;
}
.btn-navy:hover {
  background: var(--btn-navy-fill-hover);
  box-shadow: 0 10px 24px -12px rgba(0, 56, 147, 0.65);
}

.btn-outline {
  background: var(--glass-bg);
  border-color: var(--line);
  color: var(--text) !important;
  backdrop-filter: blur(var(--glass-blur));
  -webkit-backdrop-filter: blur(var(--glass-blur));
}
.btn-outline:hover {
  background: var(--glass-bg-lit);
  border-color: var(--line-hot);
  box-shadow: none;
}

.btn-sm { padding: 9px 18px; font-size: var(--text-xs); }

/* Text link that behaves like a button in a section footer. */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-label);
  font-size: var(--text-caption);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue-lit);
  transition: gap var(--transition-fast), color var(--transition-fast);
}
.link-arrow:hover { gap: 14px; color: var(--cyan); }

/* ── 7 · Hero ───────────────────────────────────────────────────────────── */

/* Sized to its content, not to the viewport. It used to be min-height:100svh
   with the content lifted above centre, which on a tall monitor left a band of
   empty white between the buttons and the section below — the hero was being
   padded out to fill a screen it did not need.

   The padding is even top and bottom: the header sits in the normal flow above
   this, so there is no fixed bar to clear and the composition simply centres in
   its own band. */
.hero {
  position: relative;
  display: flex;
  /* Copy centres on the vertical. It was pinned to the bottom back when the
     hero was only as tall as its content; now that the section fills the
     viewport, bottom-aligning left a deep gap above the headline and almost
     none below it. */
  align-items: center;
  /* Fills the screen below the header. 100svh minus the header's two tiers
     (76px identity strip + ~48px nav) so the photo runs from under the nav to
     the fold and the next section starts exactly at the bottom edge — svh, not
     vh, so a mobile browser's collapsing toolbar does not crop it. */
  min-height: calc(100svh - 124px);
  padding: var(--space-10) 0;
  background: var(--bg-void);
  overflow: hidden;
  isolation: isolate;
}
.hero .container {
  position: relative;
  z-index: 2;
  /* .hero is a flex container, so .container is a flex item and sizes to its
     content rather than to the row. Its own `margin: 0 auto` then centred that
     shrunken box, which is why the copy sat in the middle of the screen instead
     of against the left gutter. width:100% makes it fill the row again, and the
     max-width inside it does the rest. */
  width: 100%;
}

/* Optional CMS backdrop image. Graded hard: desaturated, darkened, and
   scrimmed with a solid gradient rather than an opacity guess, so the
   headline contrast is the same whether an image is set or not. */
.hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.hero-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--hero-media-filter, grayscale(0.55) contrast(1.1) brightness(0.4));
}
.hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, var(--scrim-top) 0%, var(--scrim-mid) 45%, var(--scrim-base) 100%),
    linear-gradient(90deg, var(--scrim-base) 0%, var(--scrim-mid) 55%, transparent 100%);
}

/* One column of copy sitting over the photo, held to the left. The two-column
   panel this used to be — text beside a framed slider — is gone; the photo is
   the background now, so there is no second column and no card to draw. */
.hero-inner {
  position: relative;
  z-index: 2;
  /* rem, not ch. `ch` is relative to the element's own font-size, and this
     block inherits the 16px body size — so 52ch resolved to about 416px and
     squeezed the hero headline into five lines. The headline needs room at its
     own much larger size, which only an absolute measure gives it. */
  max-width: 46rem;
  /* Clear of the prev arrow, which sits on the left edge at mid-height and was
     landing on the headline. The arrow is 46px plus its gutter, so the copy
     starts inboard of it.

     No bottom padding any more: the block is centred in the row now, and an
     extra inset on one side only would push it back off centre. The dots have
     the hero's own bottom padding to sit in. */
  padding-left: calc(46px + var(--space-4));
}
.hero-inner h2,
.hero-inner p { color: #ffffff; }
/* --text-2 is a mid grey tuned for white paper and disappears against a
   photograph, so the standfirst gets a fixed near-white instead. */
.hero-inner p { color: rgba(255, 255, 255, 0.88); }

/* Console readout strip above the headline: status LED, timecode, node id. */
.hero-status {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-4);
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: var(--space-6);
}
.hero-status .chip-live {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 12px;
  border: 1px solid rgba(206, 17, 38, 0.45);
  border-radius: var(--radius-full);
  color: var(--danger);
  background: rgba(206, 17, 38, 0.1);
}
.hero-status .chip-live::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--crimson);
  box-shadow: 0 0 10px var(--crimson);
  animation: pulse-live 1.8s ease-in-out infinite;
}
@keyframes pulse-live {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.35; transform: scale(0.7); }
}
.hero-status .tc { color: var(--cyan); }

.hero h2 {
  font-size: var(--text-hero);
  font-weight: 600;
  line-height: 0.96;
  letter-spacing: -0.038em;
  color: #ffffff;
  margin-bottom: var(--space-6);
  /* 20ch is what breaks the headline as three lines:
         One Association for
         Nepal's AVL
         Professionals
     Done with a measure rather than a hard <br>, because the text is editable
     in the admin and a baked-in break would land mid-word the moment someone
     rewrites it. At 15ch it was breaking after "One Association". */
  max-width: 20ch;
  text-wrap: normal;
  /* Photographs have detail in them; a hairline of shadow keeps the letterforms
     off whatever happens to be behind them without dimming the picture. */
  text-shadow: 0 2px 24px rgba(0, 0, 0, 0.35);
}
/* The last word of the headline picks up the beam gradient, one flourish,
   applied once per page. */
/* Flat crimson, not the crimson-to-blue gradient this used to carry. The
   gradient ran the word from red into navy, so half of it read as a different
   colour from the button underneath and the last few letters went dark enough
   to look like a rendering fault. One flag colour, applied plainly. */
.hero h2 .lit {
  color: var(--crimson);
}
.hero p {
  font-size: var(--text-md);
  line-height: 1.65;
  color: rgba(255, 255, 255, 0.88);
  max-width: 52ch;
  margin-bottom: var(--space-7);
  text-shadow: 0 1px 16px rgba(0, 0, 0, 0.35);
}
.hero-actions { display: flex; flex-wrap: wrap; gap: var(--space-4); }
/* The outline button is built for a light page: a near-invisible fill, a
   hairline border and near-black text. Over a photograph all three vanish, so
   inside the hero it inverts to a white outline on a slight dark wash. The
   filled crimson button needs no change — it carries its own background. */
.hero-actions .btn-outline {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.55);
  color: #ffffff !important;
}
.hero-actions .btn-outline:hover {
  background: rgba(255, 255, 255, 0.22);
  border-color: #ffffff;
}

/* Right-hand photo slider: one frame that switches, with a dot per photo.
   This replaced a four-cell interlocking collage. The collage set its own
   height from clamped grid rows, which meant it never quite agreed with the
   headline column beside it; a single frame can simply be told to fill the
   row, so the two columns end level at every width. */
/* The photo stack is the hero's backdrop: full bleed, behind everything, with a
   scrim over it so the copy stays legible whichever photo is showing. */
.hero-slider {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.hero-slider-stage {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--graphite);
}
/* Two gradients, not one: the horizontal pass keeps the left side dark enough
   for the headline whatever is behind it, and the vertical pass stops a bright
   sky at the top and the dots at the foot from washing out. Solid colours
   rather than a blanket opacity, so contrast does not depend on which photo
   happens to be showing. */
.hero-slider::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  /* Lightened across the board. The left column still needs enough cover to
     carry white type over any photo, so that end is held near 0.8; everything
     right of the copy is close to clear now, where it used to sit at 0.25 and
     dim the whole picture. */
  background:
    linear-gradient(90deg, rgba(8, 10, 14, 0.8) 0%, rgba(8, 10, 14, 0.55) 38%, rgba(8, 10, 14, 0.1) 72%, rgba(8, 10, 14, 0) 100%),
    linear-gradient(180deg, rgba(8, 10, 14, 0.2) 0%, transparent 30%, rgba(8, 10, 14, 0.28) 100%);
}

/* Photos are stacked, all four corners pinned, and cross-faded by opacity
   alone. Nothing here may animate a property that triggers layout: this runs
   above the fold. */
.hero-slider-stage img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  filter: var(--photo-filter);
  transition: opacity 900ms var(--ease-out), filter var(--transition-slow);
}
.hero-slider-stage img.is-on { opacity: 1; }

/* Over the photo at the foot of the hero, aligned to the page gutter so they
   start on the same line as the headline above them. */
.hero-slider-dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--space-6);
  z-index: 3;
  display: flex;
  justify-content: center;
  gap: var(--space-2);
}
/* The active dot stretches rather than changing size, so the row never
   reflows around it. */
/* Dashes, not dots. On a photograph a 8px dot is nearly invisible; a short bar
   holds its shape against whatever is behind it. Fixed whites rather than theme
   tokens, for the same reason — --line does not exist against a picture. */
.hero-dot {
  width: 26px;
  height: 3px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: rgba(255, 255, 255, 0.45);
  cursor: pointer;
  transition: width var(--transition-mid), background var(--transition-mid);
}
.hero-dot:hover { background: rgba(255, 255, 255, 0.75); }
.hero-dot.is-on { width: 44px; background: #ffffff; }

/* Arrows on the outer edges, vertically centred on the photo. Translucent
   rather than solid: they sit over the picture, and a filled disc at this size
   punches a hole in it. */
.hero-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.25);
  color: #ffffff;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  transition: background var(--transition-fast), border-color var(--transition-fast);
}
.hero-arrow:hover {
  background: rgba(0, 0, 0, 0.45);
  border-color: #ffffff;
}
.hero-arrow-prev { left: var(--page-gutter); }
.hero-arrow-next { right: var(--page-gutter); }

/* The scroll cue that lived here — the word SCROLL over a pulsing vertical
   line — is gone, along with its @keyframes. It belonged to a full-viewport
   hero; the hero now ends where its content ends, so the section below is
   already on screen and there is nothing left to cue. */

/* ── 7b · About split (home) ─────────────────────────────────────────────── */

/* Photo one side, the story the other. The photo column is the narrower of the
   two because a circle wastes the corners of whatever box it sits in, so it
   needs less width than its apparent size suggests. */
.about-split {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: clamp(var(--space-8), 5vw, var(--space-11));
  align-items: center;
}

/* The About page runs the same split, and gets there with no photo when the
   banner block is empty — one column, and the writing keeps a measure rather
   than running the full width of the page. */
/* Home centres the photo against a short lede. The About page's column is much
   taller than the circle, and centring left it floating in the middle of the
   text with nothing beside its top third. */
.about-split-top { align-items: start; }
/* The ring sits above the top of the circle and wanders another 48px on its
   loop, so the figure needs headroom the text column does not — without it the
   ring runs into the page-title band above. */
.about-split-top .about-figure { margin-top: var(--space-7); }
.about-split-solo {
  grid-template-columns: minmax(0, 1fr);
  max-width: 78ch;
}
/* On the About page the split carries several blocks, not one lede, so the
   subheads under the opening statement need their own air. */
.about-body h3 {
  margin-top: var(--space-7);
  margin-bottom: var(--space-3);
  font-size: var(--text-md);
}

/* Circular crop with a ring sitting behind it, offset up and left. The ring is
   a pseudo-element rather than a second image so it costs nothing and follows
   the figure at any size. */
.about-figure {
  position: relative;
  aspect-ratio: 1 / 1;
}
/* A fat pale donut behind the top-left of the photo, tracking a slow loop.
   Thick and low-contrast rather than the hairline ring this was: at 2px it read
   as a stray outline, and the shape only works as a soft mass of colour. */
.about-figure::before {
  content: "";
  position: absolute;
  /* Sits above the top edge of the circle and a little in from the left, so it
     overlaps the photo's shoulder rather than floating off in the corner. */
  top: -11%;
  left: 24%;
  width: 30%;
  height: 30%;
  border: 14px solid rgba(255, 255, 255, 0.10);
  border-radius: 50%;
  /* Behind the photo, which carries z-index:1. */
  z-index: 0;
  animation: about-orbit 18s linear infinite;
}
:root[data-theme="light"] .about-figure::before { border-color: #e3e7f3; }
.about-figure img {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}

/* Dot field tucked behind the bottom-right of the circle, drifting slowly.
   A background-image rather than a grid of elements: it is decoration, and
   decoration should not put 80 nodes in the document. The mask fades it out
   at the edges so it reads as a field rather than as a cropped rectangle. */
.about-figure::after {
  content: "";
  position: absolute;
  right: -6%;
  bottom: 2%;
  width: 34%;
  aspect-ratio: 1 / 1;
  z-index: 0;
  color: var(--blue-lit);
  background-image: radial-gradient(currentColor 1.7px, transparent 1.8px);
  background-size: 15px 15px;
  opacity: 0.38;
  -webkit-mask-image: radial-gradient(closest-side, #000 55%, transparent 100%);
  mask-image: radial-gradient(closest-side, #000 55%, transparent 100%);
  animation: about-dots 14s ease-in-out infinite alternate;
}

@keyframes about-dots {
  from { transform: translate3d(0, 0, 0); opacity: 0.28; }
  to   { transform: translate3d(-14px, -10px, 0); opacity: 0.5; }
}
/* Four waypoints, not an alternating pair: the ring should wander a closed loop
   and arrive back where it started, so there is no moment where it visibly
   reverses direction. */
@keyframes about-orbit {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  25%  { transform: translate3d(30px, 20px, 0) scale(1.05); }
  50%  { transform: translate3d(14px, 48px, 0) scale(1); }
  75%  { transform: translate3d(-18px, 24px, 0) scale(0.96); }
  100% { transform: translate3d(0, 0, 0) scale(1); }
}

/* Decoration is the first thing to go when someone has asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  .about-figure::after,
  .about-figure::before { animation: none; }
}

/* A short rule, then the label. This is the kicker pattern that was stripped
   out of the templates once before — the note in section 4 explains why. It is
   back deliberately and built differently: the rule is a real element with a
   width, not a leading dash that read as an em dash mid-sentence, and the label
   never repeats the heading under it. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  font-family: var(--font-label);
  font-size: var(--text-micro);
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--cyan);
}
/* Two dashes, short then long. Drawn as one element with a hard-stop gradient
   rather than as two spans or a ::before/::after pair — ::after would land on
   the wrong side of the label, and the gradient keeps the whole mark in a
   single box that the flex gap can space against the text. */
.eyebrow::before {
  content: "";
  width: 52px;
  height: 2px;
  flex: none;
  background: linear-gradient(
    90deg,
    var(--crimson) 0 10px,
    transparent 10px 18px,
    var(--crimson) 18px 52px
  );
}

.about-body h2 {
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin-bottom: var(--space-5);
  text-wrap: balance;
}
.about-lede p {
  font-size: var(--text-md);
  color: var(--text-2);
  margin-bottom: var(--space-4);
}
.about-lede p:last-child { margin-bottom: 0; }

/* Name on its own line, explanation under it. The bottom hairline on every pair
   but the last is what keeps a run of four from reading as one paragraph. */
.about-points { margin: var(--space-7) 0; }
.about-points dt {
  font-weight: 600;
  color: var(--text);
  margin-bottom: 4px;
}
.about-points dd {
  font-size: var(--text-sm);
  color: var(--text-2);
  margin: 0 0 var(--space-4);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--line-soft);
}
.about-points dd:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: 0; }

/* Centred variant of the kicker, for section heads that sit on the page axis. */
.eyebrow-center { justify-content: center; }

/* A softer heading than the near-black --text. Used where a section title
   should sit back from the cards under it rather than compete with them. */
.heading-muted { color: #535b63; }

/* News band, retuned after the eyebrow above the heading and the "All News"
   button below the row were both removed. The head no longer has a label
   sitting on top of it, so --space-9 under it was measuring a gap that had
   nothing left to clear; and with no button under the cards the section's full
   --space-11 foot was landing straight after the pager. */
#news .section-head { margin-bottom: var(--space-6); }
/* Even top and bottom. The band was carrying the section's full --space-11 on
   top and a trimmed foot left over from when an "All News" button sat under the
   row — with the button gone that reads as the whole section riding high. */
#news { padding-block: var(--space-9); }

/* ── 7c · News cards (home) ──────────────────────────────────────────────── */

/* A fixed card width, not a fraction of the row. Dividing the container into
   equal columns is what squeezed eight cards into slivers with two words per
   line — the card decides how wide it is, the row shows as many as fit, and the
   arrows reach the rest. Same approach as the testimonial track above.

   Extra padding on top and bottom so the 4px lift on hover is not clipped by
   the scroll container. */
.news-track {
  grid-auto-columns: 360px;
  /* Room for the lift and for the card's cast shadow, which is wider than the
     4px this used to allow and was being clipped by the scroll container. */
  padding: 10px 8px var(--space-3);
}
/* This track must stay overflow-x:auto. It was set to hidden to stop stray
   trackpad drags, which broke the pager: pagers() sizes itself from
   scrollWidth / clientWidth, and a clipped track reports those as equal, so it
   decided there was one page and built no buttons at all.
   The stray-drag problem is handled in js/motion.js instead, which blocks the
   sideways wheel without touching the measurements. */

/* Page pills: an outlined capsule for each page, filled for the one you are on.
   Outline rather than a pale grey, so an inactive page still reads as a control
   you can press instead of as a disabled one. */
.news-pager {
  display: flex;
  justify-content: center;
  gap: var(--space-3);
  margin-top: var(--space-5);
}
/* JS builds these; with one page it builds none, and an empty flex row would
   still take its margin. */
.news-pager:empty { display: none; }
.news-pager button {
  width: 32px;
  height: 11px;
  padding: 0;
  /* A hair thinner than 1.5px: at 11px tall a heavy outline closes the capsule
     up and the empty state stops reading as empty. */
  border: 1.25px solid var(--crimson);
  border-radius: var(--radius-full);
  background: transparent;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.news-pager button:hover { background: rgba(206, 17, 38, 0.15); }
.news-pager button.is-on { background: var(--crimson); }

/* The whole card is the link. Photo on top, text below, and the "Learn More"
   line pinned to the bottom by margin-top:auto so it sits on one baseline
   across a row whose headlines run to different lengths. */
/* Square corners and no border: the card is separated from the band by a soft
   cast shadow only, which is what the reference row does. Hover deepens that
   shadow instead of colouring an edge — a border that lights up on hover was
   the one thing asked to go. */
.news-card {
  display: flex;
  flex-direction: column;
  color: inherit;
  border: 0;
  border-radius: 0;
  background: var(--bg-deep);
  overflow: hidden;
  box-shadow: var(--shadow-card);
  transition: transform var(--transition-mid), box-shadow var(--transition-mid);
}
.news-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-card-hover);
}

.news-card-photo {
  position: relative;
  aspect-ratio: 16 / 10;
  background: var(--graphite);
  overflow: hidden;
}
.news-card-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--transition-slow);
}
.news-card:hover .news-card-photo img { transform: scale(1.04); }
/* Only drawn when there is no cover image — an empty grey box needs something
   in it, and the category is the one thing we always have. */
.news-card-tag {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-label);
  font-size: var(--text-micro);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-3);
}

.news-card-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: var(--space-5);
}
.news-card-date {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: var(--space-3);
  font-family: var(--font-label);
  font-size: var(--text-caption);
  color: var(--text-3);
}
.news-card-date svg { color: var(--cyan); flex: none; }
/* Both the headline and the summary hold a fixed number of lines whether or not
   the text fills them: clamped so they cannot run longer, and floored at the
   same height so a one-line headline leaves the gap a two-line one would take.
   Without the floor a card whose title wraps once stands taller than the card
   beside it, and in a wrapping grid that difference shows as a ragged bottom
   edge down the whole page. Photo (fixed ratio) + two fixed text blocks + one
   action line means every card is exactly as tall as every other. */
.news-card h3 {
  font-size: var(--text-md);
  font-weight: 600;
  line-height: 1.3;
  /* Two lines of the above line-height. */
  min-height: 2.6em;
  margin-bottom: var(--space-3);
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.news-card p {
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--text-2);
  /* Three lines of the above line-height. */
  min-height: 4.8em;
  margin-bottom: var(--space-5);
  /* Three lines, then an ellipsis: summaries come from the admin at whatever
     length someone typed, and a card three lines taller than its neighbours
     breaks the row. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.news-card-more {
  margin-top: auto;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--blue-lit);
}
.news-card:hover .news-card-more { color: var(--cyan); }

/* The news index runs the same card in a wrapping row instead of a carousel:
   the page is the whole list, so there is nothing for arrows to reach. Flex
   with a fixed basis rather than grid, for the reason .tile-grid gives — a
   last row of one or two cards centres under the rows above it instead of
   sitting hard against the left edge.

   Same 1600px cap as .tile-grid-3, so this row starts and finishes on the
   edges every other section of the site does. Cards keep the padding the
   carousel gives them so the hover lift and the cast shadow are not clipped. */
.news-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-5);
  max-width: 1600px;
  margin-inline: auto;
  padding: 10px 8px;
}
/* The card is 360px wide here as well, the width .news-track gives it on the
   home page — the two rows are the same card at the same size, one paged and
   one wrapped, rather than a fixed card in one place and an elastic one in the
   other. The row centres what fits and wraps the rest.

   max-width keeps it inside a phone: below 360px of usable width the card
   shrinks with the screen instead of pushing the page sideways. */
.news-grid > .news-card {
  flex: 0 0 360px;
  max-width: 100%;
}

/* ── 7d · Photo tiles (news and events indexes) ─────────────────────────── */

/* The events index and the Our Works album index are grids of photo tiles:
   cover image under a dark wash, the heading and its one or two facts sitting
   on it. Hover drops the photo back to a flat slate and brings up the action,
   which is the whole point of the pattern — the tile at rest is a photograph,
   the tile under the pointer is a button. (The news index no longer draws
   these; it runs .news-card, the same card as the home page.) */
/* Flex rather than grid, for the same reason as .gallery-grid and the committee
   rows: two events under a four-column grid sat hard against the left edge with
   half the row empty beside them, which reads as a layout that has lost its
   other two tiles rather than as a section with two events in it. Grid cannot
   centre only its last line — justify-content moves every row and justify-items
   moves items within their own tracks — so the tiles are flex items whose basis
   reproduces the column width exactly. A full row is pixel-identical to before.

   --tile-cols is the only thing the breakpoints below change. The basis is
   written once against it, so the column count and the width it implies cannot
   drift apart the way two separate declarations would. */
.tile-grid {
  --tile-cols: 4;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-5);
}
/* Three across instead of four. The cap exists because the container runs to
   2160px and three columns of that would make each tile enormous — but 1180px,
   the previous value, capped the row well inside the container on every ordinary
   desktop: at a 1440px window the page ran to 1380px and this row stopped at
   1180, so the news and events grids sat visibly narrower than the header, the
   footer and every other section on the page, with a band of empty white down
   both sides.

   1600px is past the width of a normal desktop container, so the row now fills
   the container exactly wherever the container is the narrower of the two, and
   the cap only starts doing anything on a genuinely wide monitor. Below the cap
   the percentages take over again, so the breakpoints further down still apply. */
.tile-grid-3 {
  --tile-cols: 3;
  max-width: 1600px;
  margin-inline: auto;
}
.tile-grid > .tile {
  /* One gap fewer than there are columns. */
  flex: 0 1 calc((100% - (var(--tile-cols) - 1) * var(--space-5)) / var(--tile-cols));
  max-width: calc((100% - (var(--tile-cols) - 1) * var(--space-5)) / var(--tile-cols));
}

.tile {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  /* The tile's own background when a notice has no cover image at all. */
  --tile-fill: #2a2f36;
  /* The wipe is a wash, not a lid: the photograph stays visible underneath it,
     darkened. Near-black at 82% — it neutralises whatever colours are in the
     photo instead of tinting them, which is why it carries white type more
     reliably than a coloured wash did. */
  --tile-wash: rgba(16, 18, 22, 0.82);
  background: var(--tile-fill);
  color: #ffffff;
  box-shadow: var(--shadow-card);
  transition: box-shadow var(--transition-mid), transform var(--transition-mid);
}
.tile:hover { transform: translateY(-4px); box-shadow: var(--shadow-card-hover); }

.tile-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity var(--transition-mid), transform var(--transition-slow);
}
/* The photo stays put under the wash — only a slow push-in, so the tile has
   some life while the pointer is on it. */
.tile:hover .tile-photo { transform: scale(1.04); }

/* The wash that keeps the headline legible over any photograph. */
.tile::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(180deg, rgba(16, 19, 23, 0) 28%, rgba(16, 19, 23, 0.88) 100%);
  transition: opacity var(--transition-mid);
}
/* Gone once the tile is solid colour: a dark gradient over blue is what makes
   the fill read as washed out rather than as one flat colour. */
.tile:hover::after { opacity: 0; transition-delay: 0.42s; }

/* The slate fill, wiped across the tile from the left rather than cross-faded
   in place. This is the move the whole hover hangs off: it arrives first and
   everything else is timed to land behind it, so the tile reads as one gesture
   travelling across instead of four properties changing at once. */
.tile::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--tile-wash);
  transform: scaleX(0);
  /* Origin flips between the two states: it grows from the left on the way in
     and collapses to the right on the way out, so the wipe carries on through
     the tile rather than rewinding to where it started. */
  transform-origin: right center;
  transition: transform 0.5s cubic-bezier(0.65, 0, 0.35, 1);
}
.tile:hover::before {
  transform: scaleX(1);
  transform-origin: left center;
}

/* Outline plus, top right. Decoration only — it repeats what the tile says. */
.tile-plus {
  position: absolute;
  top: var(--space-5);
  right: var(--space-5);
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1.5px solid rgba(255, 255, 255, 0.75);
  border-radius: 50%;
  font-size: 1.05rem;
  line-height: 1;
  opacity: 0;
  transform: scale(0.8);
  transition: opacity var(--transition-mid), transform var(--transition-mid);
}
/* Delays live on the hover rules only, so the tile arms itself in sequence but
   resets immediately when the pointer leaves — a staggered exit reads as lag. */
.tile:hover .tile-plus {
  opacity: 1;
  transform: scale(1);
  transition-delay: 0.3s;
}

/* Bottom-anchored text with the arrow parked in the padding below it. Hover
   grows that padding, which lifts the text off the bottom edge and opens the
   space the arrow fades into.

   Padding rather than a transform on the block: a transform has to be a fixed
   pixel offset, and the moment a tile carries an extra line — an events tile
   with a venue under its date — that offset clips the wrong thing. Padding is
   measured from the bottom edge, so it holds however tall the text is. */
.tile-body {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 2;
  padding: var(--space-4);
  transition: padding-bottom var(--transition-mid);
}
.tile:hover .tile-body { padding-bottom: 68px; transition-delay: 0.2s; }

.tile h3 {
  font-size: var(--text-base);
  font-weight: 700;
  line-height: 1.25;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  margin-bottom: var(--space-3);
  color: #ffffff;
}
.tile-date {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-label);
  font-size: var(--text-caption);
  color: rgba(255, 255, 255, 0.82);
  margin: 0;
}
.tile-date svg { flex: none; }
/* A tile can now carry three of these — count, date, place — and with margin:0
   they set solid. A small step between them is enough to read as three facts
   rather than one block, without lifting the stack into the photograph. */
.tile-date + .tile-date { margin-top: 4px; }

/* The same two facts above an album's photos, for a visitor who arrived on the
   album page directly and never saw the tile. One row, centred with the heading
   it sits under; the slash only appears between two values because the parts
   are joined rather than each drawn with a separator of its own. */
.album-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 var(--space-3);
}
.album-meta svg { flex: none; }
.album-meta-sep { opacity: 0.45; margin: 0 4px; }

.tile-go {
  position: absolute;
  left: var(--space-4);
  bottom: var(--space-4);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--btn-accent-fill);
  color: var(--btn-accent-ink);
  font-size: 1.15rem;
  line-height: 1;
  /* Rides up with the text block, then pops in last. */
  opacity: 0;
  transform: scale(0.7);
  transition: opacity var(--transition-mid), transform var(--transition-mid);
}
.tile:hover .tile-go {
  opacity: 1;
  transform: scale(1);
  transition-delay: 0.36s;
}

@media (max-width: 1200px) {
  .tile-grid { --tile-cols: 3; }
}
@media (max-width: 900px) {
  .tile-grid { --tile-cols: 2; }
}
/* One across on a phone. Two-up was the earlier choice and the reasoning was
 * about height: a tile is a 1:1 square, so a full-width tile on a 390px handset
 * is a 390px block, and nine of them ran to a great deal of scrolling.
 *
 * The answer to that is to stop insisting on the square, not to keep two
 * cramped tiles to a line. At half width a news headline had about eighteen
 * characters to play with and clamped to three lines, which turned most titles
 * into an ellipsis — the item was legible as a picture and unreadable as a
 * notice. One tile per line at 16:10 is shorter than two rows of squares AND
 * gives the headline a full line of width, so the list gets easier to scan and
 * the page gets no longer.
 *
 * --tile-cols drops to 1 here; the basis calc above reads it, so the gap and
 * the width stay written against each other rather than drifting apart.
 *
 * What does change is everything inside the tile. At 390px a tile is 167px
 * square, and the type, the arrow and the padding below were all sized for the
 * ~445px one. Scaled together, because they share the same bottom-left corner:
 * shrink the arrow without cutting the body's padding-bottom and the headline
 * floats above a gap; cut the padding without shrinking the arrow and the
 * headline lands on top of it. */
@media (max-width: 640px) {
  .tile-grid { --tile-cols: 1; }
  /* Landscape rather than square, and only at this width. A full-width 1:1 tile
     is as tall as the screen is wide; 16:10 keeps the photograph reading as a
     photograph while taking a third off the height. */
  .tile { aspect-ratio: 16 / 10; }

  /* No pointer to hover with, so the action can never be revealed — the tiles
     show their arrow from the start on touch-sized screens. */
  .tile-go {
    opacity: 1;
    transform: scale(1);
    width: 32px;
    height: 32px;
    left: var(--space-3);
    bottom: var(--space-3);
    font-size: 1rem;
  }
  /* 12px of padding, then the 32px disc, then 8px of air: the reserve is the
     arrow's own size plus its two margins, so it tracks the disc above rather
     than being a number that has to be remembered separately. */
  .tile-body { padding: var(--space-3); padding-bottom: 52px; }

  /* Two lines is plenty now the tile is full width — at this width a headline
     that needed three lines at half width fits in one or two. The clamp stays so
     an unusually long title still cannot climb the tile and push the photograph
     out of it. The type goes back up a step for the same reason: --text-xs was
     sized for a 167px-wide tile. */
  .tile h3 {
    font-size: var(--text-sm);
    margin-bottom: var(--space-2);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .tile-date { font-size: var(--text-micro); }
}

/* ── Single notice ──────────────────────────────────────────────────────── */

/* The reading column is narrower than the container it sits in, so it has to
   be centred itself — otherwise the article hugs the left edge on a wide
   screen with a band of empty page beside it. */
.post { max-width: 76ch; margin-inline: auto; }
.post-back {
  display: inline-block;
  margin-bottom: var(--space-5);
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-3);
}
.post-back:hover { color: var(--text); }
.post h2 { margin: var(--space-4) 0 var(--space-3); }
.post-date {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-label);
  font-size: var(--text-caption);
  color: var(--text-3);
  margin-bottom: var(--space-6);
}
.post-cover {
  width: 100%;
  margin-bottom: var(--space-6);
  border-radius: var(--radius-md);
}
.post-lede {
  font-size: var(--text-md);
  color: var(--text-2);
  margin-bottom: var(--space-5);
}

/* ── 7e · Member area ───────────────────────────────────────────────────── */

/* Sign in, forgotten password, set password. Narrow and centred: these are one
   short form each, and the site's full width would leave a pair of inputs
   stranded in the middle of an empty band. */
.member-auth {
  max-width: 420px;
  margin: 0 auto;
}
.member-auth .btn-block {
  display: block;
  width: 100%;
  text-align: center;
}
.member-auth-links {
  margin-top: var(--space-5);
  text-align: center;
  font-size: var(--text-sm);
}
.member-auth-help {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
  text-align: center;
}
.form-error-box {
  margin-bottom: var(--space-5);
  padding: var(--space-4);
  border-left: 3px solid var(--crimson);
  background: rgba(206, 17, 38, 0.08);
  font-size: var(--text-sm);
  color: var(--text);
}

/* Sign-out sits beside the header's account button as a plain link, not a
   second button — two buttons of equal weight side by side make the visitor
   read both before acting. */
.header-signout {
  margin-left: var(--space-4);
  font-family: var(--font-label);
  font-size: var(--text-caption);
  color: var(--text-3);
}
.header-signout:hover { color: var(--text); }

/* The member area's own styles used to live here — a tab strip, folder and
   directory cards, a .md-* dashboard and a file-row list. They have all gone:
   the signed-in pages now run on the admin's design system (admin/css/admin.css
   plus css/member-app.css) inside the shell in partials/member-shell-header.php,
   so none of it had a user left.

   .member-alert below stays. verify.php is a public page and still uses it. */
/* Status banner on the profile and above a blocked library. */
.member-alert {
  padding: var(--space-5);
  margin-bottom: var(--space-6);
  border-left: 3px solid var(--line-hot);
  background: var(--bg-panel);
  font-size: var(--text-sm);
}
.member-alert-warn { border-left-color: var(--crimson); }
.member-alert h3 { font-size: var(--text-base); margin-bottom: var(--space-2); }


/* ── 8 · Sections & headings ────────────────────────────────────────────── */

.section {
  position: relative;
  padding: var(--space-11) 0;
  background: var(--bg-deep);
}
.section-void {
  background: var(--bg-void);
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
}
.section-alt {
  background: var(--bg-panel);
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
}
.section-tight { padding: var(--space-9) 0; }
/* The first section on an inner page follows the title band, which has already
   opened the page. A full --space-11 on top of that reads as a gap rather than
   as breathing room, so the run-in is shortened. */
.page-header + .section { padding-top: var(--space-9); }
.section > .container { position: relative; z-index: 1; }

/* The 720px measure belongs to the body copy, not to the block. It used to cap
   the whole head, which meant a heading was held to a paragraph's reading width
   and broke early — "Meet the Executive Committee" needs about 840px at this
   size and was wrapping with "Committee" orphaned on its own line. Headings now
   run as wide as the container allows; the paragraph keeps the measure. */
.section-head { max-width: none; margin-bottom: var(--space-9); }
.section-head-center { margin-left: auto; margin-right: auto; text-align: center; }
/* A step below the page title. The band above sets the page's subject at
   calc(--text-3xl * 0.75); a section heading under it is a level down, so it
   must not arrive at the same size or the two read as competing titles. */
.section-head h2 {
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--text);
  margin-bottom: var(--space-4);
  /* When a heading genuinely is too long for one line — on a phone, or the
     50-character one on About — break it into even lines instead of leaving a
     single word stranded. Ignored by browsers that do not support it, which
     simply get the old behaviour. */
  text-wrap: balance;
}
.section-head p { font-size: var(--text-md); color: var(--text-2); max-width: 720px; }
.section-head-center p { margin-left: auto; margin-right: auto; }

/* Head with the copy left and an action right, on one baseline. */
.section-head-split {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-6);
  max-width: none;
}
/* Wider than the 720px measure so headings like "Shows our members put up" stay
   on one line, but not the full width — the action link sits to its right and
   must not be crowded. */
.section-head-split > div { max-width: min(860px, 100%); }

.section-footer { margin-top: var(--space-8); text-align: center; }

.block h2 { font-size: var(--text-xl); margin-bottom: var(--space-5); }
.block p { color: var(--text-2); margin-bottom: var(--space-4); }
.block p:last-child { margin-bottom: 0; }

/* ── 9 · Cards & glass surfaces ─────────────────────────────────────────── */

.card {
  position: relative;
  padding: var(--space-7);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--glass-bg);
  overflow: hidden;
  isolation: isolate;
  transition: border-color var(--transition-mid), transform var(--transition-mid),
              background var(--transition-mid);
}

/* Pointer-tracking highlight. js/motion.js writes --mx/--my as percentages;
   with no JS the values are unset and the layer simply never appears. */
.card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0;
  background: radial-gradient(
    380px circle at var(--mx, 50%) var(--my, 50%),
    rgba(46, 125, 255, 0.16),
    transparent 70%
  );
  transition: opacity var(--transition-mid);
}
.card:hover {
  border-color: var(--line-hot);
  transform: translateY(-4px);
  background: var(--glass-bg-lit);
}
.card:hover::before { opacity: 1; }

.card h3 {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text);
  margin-bottom: var(--space-3);
}
.card p { font-size: var(--text-sm); color: var(--text-2); }

/* Centred variant, used by the home page's "what we do" cards. Scoped to a
   modifier rather than applied to .card, because Events, Committee and About
   all use .card too and their cards are left-ranged around a badge. */
.cards-centered .card { text-align: center; }
.cards-centered .card h3 { text-wrap: balance; }
/* Stat tiles: the number and its label centre in the card. Left-ranged in a
   tile this wide, the figure floated away from the label under it. */
.stats-grid .statement { text-align: center; }
/* All six on one line. .grid-4 left a row of two hanging, and auto-fit on a
   full-width page spread them so far apart that the row stopped reading as one
   set of figures. Six equal columns in a narrower measure keeps them together;
   the tiles are smaller than the .grid-4 ones were, which is right — a stat is
   a number and a label, not a card that needs filling. */
.stats-grid {
  grid-template-columns: repeat(6, 1fr);
  gap: var(--space-4);
  max-width: 1400px;
  margin-inline: auto;
}
.stats-grid .statement { padding: var(--space-6) var(--space-4); }
/* The number is the content here; it should not shrink with the tile. */
.stats-grid .statement h3 { font-size: var(--text-xl) !important; }

/* Index badge, the card's "channel number". The home page's "what we do" cards
   no longer print one, but Events, Committee and About still do, so the rule
   stays as it was. */
.card .icon,
.icon-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 28px;
  padding: 0 10px;
  margin-bottom: var(--space-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--graphite);
  color: var(--cyan);
  font-family: var(--font-label);
  font-size: var(--text-micro);
  font-weight: 500;
  letter-spacing: 0.1em;
}

.card-photo {
  display: block;
  width: calc(100% + 2 * var(--space-7));
  max-width: none;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  margin: calc(-1 * var(--space-7)) calc(-1 * var(--space-7)) var(--space-6);
  filter: var(--photo-filter);
  transition: filter var(--transition-slow), transform var(--transition-slow);
}
.card:hover .card-photo { filter: var(--photo-filter-hover); }

/* Two-up mission / vision statement cards. Larger type, no photo, a coloured
   edge light down the leading side. */
.statement {
  position: relative;
  padding: var(--space-8);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: linear-gradient(160deg, var(--glass-bg-lit), transparent 60%);
  overflow: hidden;
  transition: border-color var(--transition-mid), transform var(--transition-mid);
}
.statement::after {
  content: "";
  position: absolute;
  left: 0;
  top: 12%;
  bottom: 12%;
  width: 2px;
  background: linear-gradient(to bottom, transparent, var(--blue), transparent);
  opacity: 0.7;
  transition: opacity var(--transition-mid), top var(--transition-mid), bottom var(--transition-mid);
}
.statement:nth-child(2)::after { background: linear-gradient(to bottom, transparent, var(--purple), transparent); }
.statement:hover { border-color: var(--line-hot); transform: translateY(-4px); }
.statement:hover::after { top: 0; bottom: 0; opacity: 1; }
.statement h3 {
  font-size: var(--text-xl);
  line-height: 1.15;
  margin-bottom: var(--space-4);
}
.statement p { font-size: var(--text-md); color: var(--text-2); max-width: 46ch; }

.section-photo {
  width: 100%;
  aspect-ratio: 21 / 8;
  object-fit: cover;
  border-radius: var(--radius-lg);
  border: 1px solid var(--line);
  filter: var(--photo-filter);
  margin-bottom: var(--space-9);
}

/* ── 10 · Pillars ───────────────────────────────────────────────────────── */

/* Audio / Visual / Lighting. Each panel owns a signature colour, set inline
   from the template as --c, so one rule serves all three. */
.pillars { display: grid; gap: var(--space-5); }
.pillar {
  position: relative;
  display: grid;
  /* The name column is wider than the longest discipline needs ("Lighting" at
     --text-2xl). The slack is deliberate: it sets the paragraph in from the
     card edge so the three bodies line up on a clear left margin well right of
     the names, rather than crowding them. */
  grid-template-columns: 260px minmax(0, 1fr);
  gap: var(--space-8);
  align-items: center;
  padding: var(--space-8);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-panel);
  overflow: hidden;
  transition: border-color var(--transition-mid), background var(--transition-mid);
}
.pillar::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--c, var(--blue));
  box-shadow: 0 0 30px 2px var(--c, var(--blue));
}
/* Wash of the pillar colour that grows on hover, like a fixture coming up. */
.pillar::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 40%;
  height: 200%;
  transform: translateY(-50%);
  background: radial-gradient(ellipse at 0% 50%, var(--c, var(--blue)), transparent 62%);
  opacity: 0.07;
  filter: blur(40px);
  pointer-events: none;
  transition: opacity var(--transition-slow), width var(--transition-slow);
}
.pillar:hover { border-color: color-mix(in srgb, var(--c, var(--blue)) 40%, transparent); }
.pillar:hover::after { opacity: 0.2; width: 70%; }

/* Holds only the discipline name now. The mono/uppercase/letter-spaced rules
   here used to style the three-letter code that sat above it; they no longer
   apply to any text but are harmless and would be needed again if the code
   ever returns. */
.pillar-mark {
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--c, var(--blue));
}
.pillar-mark strong {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--text);
  /* No margin-top: it existed to clear the code chip above, and with that gone
     it only knocked the name off centre against the paragraph beside it. */
}
.pillar-body p { font-size: var(--text-md); color: var(--text-2); max-width: 62ch; }
/* Below .pillar-body p on purpose: that rule is equally specific, so the spec
   line only keeps its discipline colour if it is declared afterwards. */
.pillar-body p.pillar-spec {
  font-family: var(--font-label);
  font-size: var(--text-caption);
  line-height: 1.7;
  letter-spacing: 0.04em;
  color: var(--c, var(--blue));
  margin-bottom: var(--space-4);
  max-width: none;
}

/* ── 11 · Pathway cards & spec list ─────────────────────────────────────── */

/* Certification levels. Numbered, ascending, with the level chip large
   enough to read as a rating plate. */
.path-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--space-7);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--glass-bg);
  transition: border-color var(--transition-mid), transform var(--transition-mid),
              background var(--transition-mid);
}
.path-card:hover {
  border-color: var(--line-hot);
  transform: translateY(-4px);
  background: var(--glass-bg-lit);
}
.path-level {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  margin-bottom: var(--space-5);
  border: 1px solid rgba(46, 125, 255, 0.35);
  border-radius: var(--radius-md);
  background: rgba(46, 125, 255, 0.08);
  color: var(--blue-lit);
  font-family: var(--font-label);
  font-size: var(--text-md);
  font-weight: 500;
}
.path-card h3 { font-size: var(--text-lg); margin-bottom: var(--space-2); }
.path-meta {
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: var(--space-4);
}
.path-card p { font-size: var(--text-sm); color: var(--text-2); }

/* Standards. Presented as a spec sheet: clause number in the gutter, rule
   title, rule text, the way the published document itself is laid out. */
.spec-list { border-top: 1px solid var(--line); }
.spec-row {
  display: grid;
  grid-template-columns: 110px minmax(0, 280px) minmax(0, 1fr);
  gap: var(--space-6);
  align-items: baseline;
  padding: var(--space-6) var(--space-4);
  border-bottom: 1px solid var(--line);
  transition: background var(--transition-fast), padding-left var(--transition-fast);
}
.spec-row:hover { background: var(--glass-bg); padding-left: var(--space-6); }
.spec-no {
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.1em;
  color: var(--amber);
}
.spec-row h3 { font-size: var(--text-md); margin: 0; }
.spec-row p { font-size: var(--text-sm); color: var(--text-2); max-width: 68ch; }

/* ── 12 · Events, news, timeline ────────────────────────────────────────── */

.event-card {
  position: relative;
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  gap: var(--space-5);
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--glass-bg);
  transition: border-color var(--transition-mid), transform var(--transition-mid),
              background var(--transition-mid);
}
.event-card:hover {
  border-color: var(--line-hot);
  transform: translateY(-4px);
  background: var(--glass-bg-lit);
}
.event-card h4 {
  font-size: var(--text-md);
  color: var(--text);
  margin-bottom: var(--space-2);
}
.event-card p { font-size: var(--text-sm); color: var(--text-2); }

.date-badge {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 92px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--graphite);
  border-top: 2px solid var(--blue);
}
.date-badge strong {
  font-family: var(--font-display);
  font-size: 1.7rem;
  font-weight: 600;
  line-height: 1;
  color: var(--text);
  letter-spacing: -0.03em;
}
.date-badge span {
  font-family: var(--font-label);
  font-size: var(--text-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--cyan);
  margin-top: 6px;
}

.tag {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  margin-bottom: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-full);
  background: var(--graphite);
  color: var(--text-2);
  font-family: var(--font-label);
  font-size: var(--text-micro);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.tag-hot { border-color: rgba(245, 165, 36, 0.4); color: var(--amber); background: rgba(245, 165, 36, 0.08); }

.event-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-top: var(--space-4);
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.06em;
  color: var(--text-3);
}
.event-meta span { display: inline-flex; align-items: center; gap: 7px; }
.event-meta span::before {
  content: "";
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--cyan);
}

/* Generic list row, kept from the previous design for news and archives. */
.list-item {
  display: flex;
  gap: var(--space-5);
  padding: var(--space-6) 0;
  border-bottom: 1px solid var(--line);
  transition: padding-left var(--transition-fast), border-color var(--transition-fast);
}
.list-item:last-child { border-bottom: none; }
.list-item:hover { padding-left: var(--space-4); border-color: var(--line-hot); }
.list-item-flush { padding: 0; border: 0; }
.list-item-flush:hover { padding-left: 0; border-color: transparent; }
.list-item h4 { font-size: var(--text-md); color: var(--text); margin-bottom: var(--space-2); }
.list-item p { font-size: var(--text-sm); color: var(--text-2); }

/* News */
.news-lead {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-8);
  align-items: center;
  padding: var(--space-8);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: linear-gradient(140deg, var(--glass-bg-lit), transparent 65%);
  margin-bottom: var(--space-8);
}
.news-lead h3 { font-size: var(--text-xl); margin-bottom: var(--space-4); }
.news-lead img {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-radius: var(--radius-md);
  filter: var(--photo-filter);
}
.news-date {
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}

/* Timeline, the About history. A lit rail with nodes on it. */
.timeline { position: relative; padding-left: var(--space-8); }
.timeline::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 6px;
  bottom: 6px;
  width: 1px;
  background: linear-gradient(to bottom, var(--cyan), var(--blue) 40%, transparent);
}
.timeline-item { position: relative; padding-bottom: var(--space-8); }
.timeline-item:last-child { padding-bottom: 0; }
.timeline-item::before {
  content: "";
  position: absolute;
  left: calc(-1 * var(--space-8) + 1px);
  top: 8px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--bg-deep);
  border: 1px solid var(--cyan);
  box-shadow: 0 0 12px -1px var(--cyan);
}
.timeline-item h3 { font-size: var(--text-lg); margin-bottom: var(--space-3); }
.timeline-item p { color: var(--text-2); }
.timeline-item img {
  width: 100%;
  max-width: 460px;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius-md);
  border: 1px solid var(--line);
  filter: var(--photo-filter);
  margin-top: var(--space-5);
}

/* ── 13 · Gallery & masonry ─────────────────────────────────────────────── */

/* CSS columns rather than grid: a true masonry flow with no JS and no fixed
   row heights, which is what lets portrait and landscape stills sit together. */
.masonry { column-count: 3; column-gap: var(--space-4); }
.masonry > * { break-inside: avoid; margin-bottom: var(--space-4); }

/* Three across: the tiles stay large enough to read a room in, which four
   across did not manage at this container width.

   Flex rather than grid, for the same reason as .member-grid: an album rarely
   divides by three, and a last row of one or two has to centre under the full
   rows instead of hanging off the left edge. Grid cannot centre only its final
   line — justify-content moves every row, justify-items moves items inside
   their own tracks — so the row is made of flex items with a basis that
   reproduces the three-column width exactly. A full row is unchanged. */
/* Three across, capped and centred on the same numbers as .tile-grid-3 — the
   container runs to 2160px, so an uncapped three-up row turned each photo into
   most of a metre of image. Sharing the cap and the gap means a gallery photo
   and a news tile are the same width, and the two pages read as one site. */
.gallery-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-5);
  max-width: 1180px;
  margin-inline: auto;
}
.gallery-grid > .gallery-item {
  flex: 0 1 calc((100% - 2 * var(--space-5)) / 3);
  max-width: calc((100% - 2 * var(--space-5)) / 3);
}

/* ---- Album index ---------------------------------------------------------
 *
 * There is no album card any more. The gallery index draws .tile-grid-3 above,
 * which is the same tile the events page uses — a photograph under a dark wash
 * with the name on it — three to a row.
 *
 * What was here was a panel card: picture on top, then a white block holding
 * the album name and its description. It predated the tile treatment and was
 * the last page on the site still using its own card. The description it made
 * room for was the argument for keeping it, and that reads better on the album's
 * own page, where it introduces the photographs it describes, than shrunk into
 * two clamped lines under a thumbnail.
 *
 * .album-back below survives: it is the "All albums" link at the foot of an
 * album, and belongs to the detail view rather than to the index. */
.album-back { margin-top: var(--space-7); text-align: center; }

/* Square corners, deliberately — the photographs are the content here and a
   radius crops the frame of every one of them. The tile clips its image with
   overflow:hidden, so a radius on the tile IS a radius on the photo; this is
   the only place it has to be set. Do not round it back up for consistency
   with the cards elsewhere. */
.gallery-item {
  position: relative;
  display: block;
  border: 1px solid var(--line);
  background: var(--graphite);
  overflow: hidden;
  transition: border-color var(--transition-mid), transform var(--transition-mid);
}
.gallery-grid .gallery-item { aspect-ratio: 4 / 3; }
.gallery-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--photo-filter);
  transition: transform var(--transition-slow), filter var(--transition-slow);
}
.masonry .gallery-item img { height: auto; }
.gallery-item:hover { border-color: var(--line-hot); transform: translateY(-3px); }
.gallery-item:hover img { transform: scale(1.05); filter: grayscale(0) brightness(1); }
.gallery-item a { display: block; width: 100%; height: 100%; }

/* Caption covers the whole tile and centres, revealed on hover/focus. It used
   to be a strip along the foot; on a uniform grid the tiles are all the same
   shape, so a centred label reads as a deliberate caption rather than a
   subtitle that happens to sit at different heights on every row. */
.gallery-cap {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  text-align: center;
  background: linear-gradient(to top, rgba(5, 7, 10, 0.55), rgba(5, 7, 10, 0.35));
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* Not var(--text): this label always sits on a dark scrim over a photo, in
     both themes, so it does not follow the theme's text ramp. In the light
     theme --text is near-black and would disappear into the scrim. */
  color: #f5f7fa;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--transition-mid), transform var(--transition-mid);
}
.gallery-item:hover .gallery-cap,
.gallery-item:focus-within .gallery-cap { opacity: 1; transform: none; }

/* Lightbox */
.lightbox {
  width: min(1100px, calc(100vw - 32px));
  max-height: calc(100vh - 64px);
  margin: auto;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-panel);
  overflow: visible;
}
.lightbox::backdrop { background: var(--backdrop); backdrop-filter: blur(6px); }
.lightbox img {
  width: 100%;
  max-height: calc(100vh - 140px);
  object-fit: contain;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: #000;
}
/* Esc closes a native dialog for free, but a pointer user needs a target
   that is not "click the backdrop and hope". */
.lightbox-close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 38px;
  height: 38px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--header-bg);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-size: 1.3rem;
  line-height: 1;
  color: var(--text-2);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.lightbox-close:hover { background: var(--graphite); color: var(--text); }

.lightbox-cap {
  padding: var(--space-4) var(--space-5);
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.06em;
  color: var(--text-2);
  border-top: 1px solid var(--line);
}

/* ── 14 · Credentials (membership) ──────────────────────────────────────── */

/* Laminated backstage pass. The details that sell it are the lanyard slot,
   the holographic sweep and the barcode, all pure CSS. */
.credential {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--space-7) var(--space-6) var(--space-6);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: linear-gradient(165deg, #131922, #0b0f15);
  overflow: hidden;
  transition: transform var(--transition-mid), border-color var(--transition-mid);
}
/* Lanyard slot */
.credential::before {
  content: "";
  position: absolute;
  top: 14px;
  left: 50%;
  transform: translateX(-50%);
  width: 74px;
  height: 7px;
  border-radius: var(--radius-full);
  background: var(--bg-void);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.9);
}
/* Hologram sweep */
.credential::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 30%, rgba(139, 92, 246, 0.14) 45%,
              rgba(34, 211, 238, 0.14) 55%, transparent 70%);
  opacity: 0;
  transition: opacity var(--transition-slow), transform var(--transition-slow);
  transform: translateX(-20%);
  pointer-events: none;
}
.credential:hover { transform: translateY(-6px); border-color: var(--line-hot); }
.credential:hover::after { opacity: 1; transform: translateX(20%); }

.credential-inner { position: relative; z-index: 1; padding-top: var(--space-4); }
.credential-class {
  display: inline-flex;
  align-items: center;
  padding: 5px 12px;
  border: 1px solid rgba(245, 165, 36, 0.4);
  border-radius: var(--radius-sm);
  background: rgba(245, 165, 36, 0.09);
  color: var(--amber);
  font-family: var(--font-label);
  font-size: var(--text-micro);
  font-weight: 500;
  letter-spacing: 0.2em;
  margin-bottom: var(--space-5);
}
.credential h3 {
  font-size: var(--text-xl);
  letter-spacing: -0.03em;
  margin-bottom: var(--space-2);
}
/* Contact-page variant: no access-class chip, and headings long enough to
   need their own size. "Corporate Membership" wraps at --text-xl in a third
   of the grid, so these step down and get a little more room at the top where
   the chip used to sit.

   Centred, which works here only because the chip is gone: with it in place
   there was a left-ranged element the centred text would have fought. */
.credential-wide { text-align: center; }
.credential-wide .credential-inner { padding-top: var(--space-5); }
.credential-wide h3 {
  font-size: 1.3rem;
  letter-spacing: -0.022em;
  margin-bottom: var(--space-4);
}
.credential-wide p { margin-left: auto; margin-right: auto; }

.credential-elig {
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.08em;
  color: var(--text-3);
  margin-bottom: var(--space-5);
  padding-bottom: var(--space-5);
  border-bottom: 1px dashed var(--line);
}
.credential p { font-size: var(--text-sm); color: var(--text-2); margin-bottom: var(--space-6); }
.credential .btn { align-self: flex-start; }

/* Barcode strip. Repeating gradients at three widths so the bars are uneven
   enough to look scanned rather than printed as a pattern. */
.credential-code {
  height: 34px;
  margin-top: auto;
  padding-top: var(--space-5);
  background-image:
    repeating-linear-gradient(90deg, var(--text-2) 0 1px, transparent 1px 4px),
    repeating-linear-gradient(90deg, var(--text-2) 0 2px, transparent 2px 9px),
    repeating-linear-gradient(90deg, var(--text-2) 0 1px, transparent 1px 6px);
  background-size: 33% 18px, 34% 18px, 33% 18px;
  background-position: 0 100%, 33.5% 100%, 67% 100%;
  background-repeat: no-repeat;
  opacity: 0.35;
}

/* ── 15 · Carousel, marquee, accordion ──────────────────────────────────── */

/* Testimonials. Native scroll-snap does the work; the buttons just call
   scrollBy(), so the track stays swipeable, keyboard-scrollable and
   functional with JS off. */
.carousel { position: relative; }
.carousel-track {
  display: grid;
  grid-auto-flow: column;
  /* A fixed column width, not minmax(): with a max the columns shrink to fit
     the container, the track never overflows, and the carousel has nothing
     left to scroll. Overflow is the point. */
  grid-auto-columns: 400px;
  gap: var(--space-5);
  /* Centred while the cards fit, which is the usual case now the page runs
     wide — three 400px quotes in a 2000px row were sitting hard left with a
     screen's worth of white beside them.

     "safe" is the important half: plain `center` centres the overflow too, so
     once there are enough quotes to scroll, the first one is pushed off the
     left edge and cannot be scrolled back to. `safe` drops to start-alignment
     the moment the content overflows. */
  justify-content: safe center;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  padding-bottom: var(--space-2);
}
.carousel-track::-webkit-scrollbar { display: none; }
.carousel-track > * { scroll-snap-align: start; }

.quote {
  display: flex;
  flex-direction: column;
  padding: var(--space-7);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--glass-bg);
}
.quote::before {
  content: "❝";
  font-family: var(--font-display);
  font-size: 2.4rem;
  line-height: 1;
  color: var(--blue);
  opacity: 0.5;
  margin-bottom: var(--space-4);
}
.quote blockquote {
  font-size: var(--text-md);
  line-height: 1.6;
  color: var(--text);
  margin-bottom: var(--space-6);
  flex: 1;
}
.quote figcaption {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
}
.quote-avatar {
  width: 42px;
  height: 42px;
  flex: none;
  border-radius: 50%;
  overflow: hidden;
  background: var(--graphite);
  border: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-label);
  font-size: var(--text-caption);
  color: var(--text-3);
}
.quote-avatar img { width: 100%; height: 100%; object-fit: cover; }
.quote-who strong { display: block; font-size: var(--text-sm); color: var(--text); }
.quote-who span {
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.06em;
  color: var(--text-3);
}

/* Centred under the track it drives, rather than parked at the left edge. */
.carousel-nav { display: flex; justify-content: center; gap: var(--space-3); margin-top: var(--space-6); }
.carousel-btn {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--glass-bg);
  color: var(--text);
  font-size: var(--text-md);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}
.carousel-btn:hover { border-color: var(--line-hot); background: var(--glass-bg-lit); }
.carousel-btn[disabled] { opacity: 0.3; cursor: default; }

/* ── Brand carousel ─────────────────────────────────────────────────────── */

/* Arrows sit outside the track, so the row is padded in to leave them room
   rather than having them overlap the first and last tile. */
.brand-carousel {
  position: relative;
  padding: 0 var(--space-8);
}
.brand-track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 220px;
  gap: var(--space-5);
  overflow-x: auto;
  scrollbar-width: none;
  /* No scroll-snap here. Snap fights a continuous drift — the browser keeps
     pulling the track back onto the nearest tile, which shows up as a stutter
     every time a tile passes the edge. */
}
.brand-track::-webkit-scrollbar { display: none; }

/* A plain grey plate. Logos arrive in every shape, colour and aspect there is,
   so the tile gives them one shared frame and the contain fit stops a wide
   wordmark and a square mark being scaled to different visual weights. */
.brand-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4 / 3;
  padding: var(--space-6);
  background: var(--bg-panel);
  border-radius: var(--radius-sm);
  transition: background var(--transition-fast);
}
.brand-tile:hover { background: var(--graphite); }
.brand-tile img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.brand-name {
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
  text-align: center;
}

/* Circular, centred on the track's midline, and hidden from the flow so they
   do not shift the row when they disable at either end. */
.brand-arrow {
  position: absolute;
  top: calc(50% - var(--space-6));
  transform: translateY(-50%);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--crimson);
  color: #ffffff;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  transition: opacity var(--transition-fast), background var(--transition-fast);
}
.brand-arrow-prev { left: 0; }
.brand-arrow-next { right: 0; }
.brand-arrow:hover { background: var(--btn-accent-fill-hover); }
/* Faded rather than removed: a control that vanishes at the end of a row makes
   the row jump sideways as the arrow's space is reclaimed. */
.brand-arrow[disabled] { opacity: 0.25; cursor: default; }

/* The page dots that sat under this row are gone: the track drifts on its own
   now, so there is no fixed page for a dot to point at. The arrows remain. */

/* An eyebrow with no rule beside it, just the label. */
.eyebrow-plain::before { display: none; }

/* Partner marquee. No longer used on the home page — the brand row is a paged
   carousel now — but kept for any page that still wants a running strip. */
.marquee {
  position: relative;
  overflow: hidden;
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee-track {
  display: flex;
  width: max-content;
  gap: var(--space-8);
  animation: marquee 46s linear infinite;
}
.marquee:hover .marquee-track,
.marquee:focus-within .marquee-track { animation-play-state: paused; }
@keyframes marquee {
  to { transform: translateX(-50%); }
}
.partner {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 190px;
  height: 84px;
  padding: 0 var(--space-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--glass-bg);
  color: var(--text-2);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 500;
  text-align: center;
  white-space: nowrap;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}
.partner:hover { color: var(--text); border-color: var(--line-hot); }
.partner img { max-height: 44px; width: auto; filter: grayscale(1) brightness(1.7); opacity: 0.75; }
.partner:hover img { filter: none; opacity: 1; }

/* FAQ. Native <details> so it works with JS off and is keyboard-operable
   for free; motion.js only adds the height transition. */
.faq { border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-2);
  cursor: pointer;
  list-style: none;
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: 500;
  color: var(--text);
  transition: color var(--transition-fast);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--cyan); }
.faq summary::after {
  content: "+";
  flex: none;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font-family: var(--font-label);
  font-size: var(--text-md);
  color: var(--cyan);
  transition: transform var(--transition-fast), background var(--transition-fast);
}
.faq details[open] summary::after { transform: rotate(45deg); background: var(--graphite); }
.faq-answer { padding: 0 var(--space-2) var(--space-6); }
.faq-answer p { font-size: var(--text-sm); color: var(--text-2); max-width: 74ch; }

/* ── 16 · Committee ─────────────────────────────────────────────────────── */

/* Flex rather than the .grid-4 utility this used to borrow. A committee group
   rarely divides by four, and grid has no way to centre only its last line —
   `justify-content` on a grid moves every row at once, and `justify-items`
   moves items within their own tracks. Flex wraps and centres each line on its
   own, so a trailing row of one, two or three sits under the middle of the
   rows above. The basis reproduces the grid-4 column width exactly, so a full
   row of four is pixel-identical to what was there before. */
.member-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-5);
}
.member-grid > .member-card {
  flex: 0 1 calc((100% - 3 * var(--space-5)) / 4);
  max-width: calc((100% - 3 * var(--space-5)) / 4);
}

/* ── Committee portrait cards ───────────────────────────────────────────────
   A full-bleed portrait with the name sitting on it, and the member's details
   rising from the bottom edge on hover.

   Named .committee-* rather than reusing .member-card, which the signed-in
   member area (dashboard, directory, files) also uses for an unrelated card.
   Both class names existed in this file already and the committee block simply
   won on source order; restyling the shared name would have redesigned four
   member pages by accident. */
/* A column of centred rows, one per seniority tier, built in committee.php.
   --cc-w is the single number that sizes every card: the rows are centred and
   the cards are a fixed width, so a row of one president and a row of five
   members draw identically sized cards. A percentage basis cannot do that — it
   would stretch a lone card across the whole row. */
.committee-grid {
  /* Three caps, smallest wins, with a floor under the lot.

     1. 300px — the size the reference uses.
     2. A fifth of the row: five cards plus their gaps need ~1580px, and below
        that this hands back whatever a fifth actually is, so the row stays on
        one line instead of dropping its last card onto a line of its own.
     3. Viewport height. A 3/4 card 300px wide is 400px tall — two of those plus
        the title band above ran past the bottom of a laptop screen, so the
        second row was always half cut off. This cap sizes the card so two rows
        and their gap clear the fold: the 19rem is the title band plus the
        section padding above the grid, and the 3/4 converts the height budget
        back to a width.

        It is still two rows, not three, now that the lead card takes the first
        row alone. Sizing for all three would drive the faces down to thumbnails
        to save a scroll nobody minds — the point of the cap was that the grid
        should not LOOK like it ends at the fold, and a lead card plus a full
        row of five is already unambiguously the top of something longer.
        It tracks the trimmed band set in .page-committee .page-header below —
        make that band taller again and this number has to grow with it, or the
        second row starts hanging off the bottom.

     The 200px floor stops a short window (or a browser with a lot of toolbars)
     shrinking the faces to thumbnails — past that point it is better to scroll
     a little than to squint. */
  --cc-w: max(200px, min(
    300px,
    calc((100% - 4 * var(--space-5)) / 5),
    calc((100vh - 19rem) / 2 * 3 / 4)
  ));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
}
/* Below the lead card: a row of office-bearers, then a row of members, each of
   which wraps. The max-width is what decides where it wraps — five cards and their four gaps — rather than
   leaving it to whatever the container happens to be: --cc-w tops out at 300px,
   so on a wide enough screen a sixth card would have fitted on the line and the
   grid would quietly have become 1-6-4. Each breakpoint below restates this
   number for the count it wants, and that is the only place the count lives. */
.committee-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-5);
  width: 100%;
  max-width: calc(5 * var(--cc-w) + 4 * var(--space-5));
}
/* The lead row holds one card, which is wider than --cc-w. */
.committee-row-lead { max-width: none; }

.committee-card {
  /* The scrim over the photograph. One value, used at rest and in every other
     state: nothing darkens this card any more. It stays a variable because the
     gradient is long and ::after reads it, not because anything swaps it. */
  --cc-scrim-rest: linear-gradient(180deg,
    transparent 72%,
    rgba(8, 10, 15, 0.42) 88%,
    rgba(8, 10, 15, 0.82) 100%);
  --cc-scrim: var(--cc-scrim-rest);
  position: relative;
  display: block;
  width: var(--cc-w);
  aspect-ratio: 3 / 4;
  overflow: hidden;
  /* Half of --radius-lg (16px). Written as the division rather than as 8px so
     it keeps tracking the token if the scale is ever retuned. */
  border-radius: calc(var(--radius-lg) / 2);
  background: var(--graphite);
  color: #ffffff;
  text-decoration: none;
  box-shadow: var(--shadow-card);
  transition: transform var(--transition-mid), box-shadow var(--transition-mid);
}
/* The lead card — the top-ranked member, alone on the first row.

   Square rather than the 3/4 of the row below, and wider than --cc-w, so the
   distinction survives being seen out of context: a lone portrait blown up in
   the same 3/4 shape reads as a card that failed to fit rather than as the head
   of the committee. Height follows width through aspect-ratio, so "bigger" only
   ever needs the one number.

   The multiplier is applied to --cc-w instead of setting its own pixel width,
   which keeps the lead card tied to every cap already built into that token:
   shrink the window and the president shrinks with the row underneath, still
   square, still proportionally larger.

   1.25, pulled back from 1.4, which is what stops the lead card looming over the
   row instead of heading it.

   The ratio then adds the height back on its own. It was 1/1 — dead square — and
   1/1.08 is a nudge taller without the width following it, which the multiplier
   could not have done: raising that grows both dimensions at once and the card
   was already as wide as it wants to be. Still far short of the 3/4 the row
   below runs at, so the lead card still reads as a different shape rather than
   as an enlarged copy of one of them. */
.committee-card.is-lead {
  width: calc(var(--cc-w) * 1.25);
  aspect-ratio: 1 / 1.08;
}

/* The hover response grows the card; it does not move it.
 *
 * This was translateY(-4px), which flickered along the bottom edge: the pointer
 * lands on the card, the card slides 4px up, the pointer is now in the 4px strip
 * the card just vacated, :hover drops, the card slides back down under the
 * pointer, :hover applies again. That loop runs as fast as the browser can
 * repaint and there is no damping to be added — any hover effect that moves the
 * element out from under the cursor has a band along its trailing edge where it
 * oscillates.
 *
 * A scale cannot do that. The hover area only ever grows, so a pointer that is
 * inside the card stays inside it for the whole transition, at every edge. The
 * deepened shadow is what carries the "lifting" read that the translate was
 * there for.
 *
 * 1.02 rather than something larger because the photograph inside is scaling to
 * 1.05 at the same time: the two compound, and past this the portrait starts
 * visibly cropping on hover. */
/* Pointer devices only. On touch, :hover sticks after a tap and never lifts,
   which would leave the tapped card scaled up behind the modal it opened — the
   same latch that used to hold the dark scrim on. Keyboard focus is handled
   separately below, because :focus-visible does not have that problem. */
@media (hover: hover) and (pointer: fine) {
  .committee-card:hover {
    transform: scale(1.02);
    box-shadow: var(--shadow-card-hover);
  }
}
.committee-card:focus-visible {
  transform: scale(1.02);
  box-shadow: var(--shadow-card-hover);
}

.cc-photo { position: absolute; inset: 0; }
.cc-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--transition-slow);
}
@media (hover: hover) and (pointer: fine) {
  .committee-card:hover .cc-photo img { transform: scale(1.05); }
}
.cc-photo.is-empty { background: linear-gradient(150deg, var(--graphite), var(--bg-panel)); }

/* The scrim. At rest it is a band behind the name and nothing more: starting it
   at 34% put a haze across the lower two thirds of every portrait, which read as
   a smear on the photograph rather than as a background for the text. It now
   stays clear until 72% and only then darkens, so the face is untouched and the
   name still has something to sit on. Hover is unchanged — the panel opens with
   several lines of text over the middle of the card, and that does need the
   deeper wash to stay legible. */
.committee-card::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--cc-scrim);
  transition: background var(--transition-mid);
}

.cc-panel {
  position: absolute;
  z-index: 1;
  left: 0;
  right: 0;
  bottom: 0;
  /* Trimmed from --space-5 (20px). On a card that now sizes itself to the
     window, 20px a side was taking ~18% of the width away from the fact chips
     and pushing the panel further up over the photograph. */
  padding: var(--space-4) var(--space-3) var(--space-3);
}
/* Centred, unlike the panel's other text. The name sits under a portrait that is
   itself centred in the frame, so ranged left it read as a caption pinned to the
   corner rather than as that person's label. The facts and bio below stay ranged
   left — they are read as prose, and centred body copy gives every line a
   different starting edge. */
.cc-name {
  margin: 0;
  font-family: "Quicksand", var(--font-body);
  /* Was --text-md (1.1rem). A step down so a two-word-plus-honorific name
     ("Mr. Sandeep Kumar Sharda") still breaks to two lines at most, and the
     panel keeps more of the photograph uncovered. */
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: 0;
  text-align: center;
  color: #ffffff;
  /* Insurance for the top edge of a two-line name, which reaches above the
     darkest part of the trimmed scrim. Cheaper than darkening the whole band
     again for the sake of a few pixels of text. */
  text-shadow: 0 1px 3px rgba(8, 10, 15, 0.65);
}

/* 0fr → 1fr is the whole trick: a grid row can be animated between those two
   values, so the panel grows to exactly its content height with nothing about
   that height written down. The inner div carries the overflow — without it the
   content spills out of the collapsed row instead of being clipped. */
.cc-more {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  /* Slowed from 0.45s/0.3s. The panel carries four facts and a bio, and opening
     that much text in under half a second read as a snap rather than a reveal —
     the eye arrives after the motion has finished. The fade still lands ahead of
     the height so the text is legible as the row settles rather than after it. */
  transition: grid-template-rows 0.8s var(--ease-out), opacity 0.55s var(--ease-out);
}
.cc-more-inner { overflow: hidden; }
.committee-card:hover .cc-more,
.committee-card:focus-visible .cc-more {
  grid-template-rows: 1fr;
  opacity: 1;
}

/* Label / value pairs, filled green and blue to match the reference the
   committee asked this page to follow.

   Both are literals rather than palette tokens, and that is the point worth
   flagging: green is not otherwise in this site's palette, which is the flag's
   crimson and blue and nothing else. It lives here, scoped to these chips, so
   it cannot leak into anything else. Swapping the green for --cyan is a
   one-line change if the brand rule is ever enforced over the reference. */
/* One grid for the whole list, not a grid per row.
 *
 * The label column used to be a percentage of the card (minmax(84px, 36%)), set
 * on each row separately. Two things went wrong once the cards started sizing
 * themselves to the window: at ~220px wide the value column was too narrow for a
 * company name, and shrinking the label to compensate clipped the word
 * "Organization" outright.
 *
 * `auto` fixes both, but only if both rows share one column — sized per row, the
 * Organization chip and the Role chip would come out different widths and the
 * pairs would no longer line up. So the row wrappers are display: contents,
 * which drops their boxes and hands the dt/dd straight to this grid. The column
 * is then exactly as wide as the longest label and no wider, and every remaining
 * pixel goes to the value.
 *
 * minmax(0, 1fr) rather than 1fr on the value: a grid track's default minimum is
 * its content, so a long unbroken name would push the track wider than the card
 * instead of wrapping inside it.
 */
.cc-facts {
  margin: 10px 0 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 4px;
  align-items: stretch;
}
.cc-facts > div { display: contents; }
.cc-facts dt,
.cc-facts dd {
  margin: 0;
  padding: 5px 7px;
  border-radius: 3px;
  /* Below --text-caption (0.75rem), which is the smallest token in the scale.
     These chips are labels on a card, not body copy, and at the card's new
     width the token size no longer fits the words in it. */
  font-size: 0.68rem;
  line-height: 1.3;
  color: #ffffff;
  /* A company name with a long unbroken token must break inside its chip rather
     than run out past the card's rounded edge. */
  overflow-wrap: anywhere;
}
/* Crimson, matching the flag palette the rest of the site fills with — the green
   was the one colour on the page that belonged to neither half of it. Written as
   the literal rather than var(--crimson) because this panel sits on a photograph
   and needs the darker end of the ramp to hold white text: #ce1126 gives 5.3:1
   against #ffffff, where the brighter #dc143c does not clear 4.5. */
.cc-facts dt {
  background: #ce1126;
  font-weight: 700;
}
.cc-facts dd {
  background: #2b5c9b;
  font-weight: 500;
}

/* Sits on solid black rather than on the scrim: the reference runs the bio on a
   flat panel, and over a photograph a gradient alone leaves the smallest text
   on the card fighting whatever is behind it. */
.cc-bio {
  margin: 4px 0 0;
  padding: 7px 8px 3px;
  background: rgba(6, 8, 12, 0.92);
  border-radius: 0 0 3px 3px;
  /* Was --text-xs (0.8rem), which put the bio a size ABOVE the fact chips
     beside it — the smallest, least important text on the card reading as the
     largest. It now sits just under them. */
  font-size: 0.66rem;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.88);
  /* Three lines, then ellipsis. The full bio is still in the DOM — the modal
     and any unstyled reading of the page get all of it. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The hover reveal has been removed entirely. It used to open the
   organisation/role/bio panel over the photograph on a pointer device, and that
   is what the deep scrim existed to make readable — a card darkening heavily
   whenever the cursor crossed it, including while simply scrolling past the
   grid with the mouse over it.

   Every device now behaves the way touch already did: the card shows a face and
   a name, holds its resting scrim, and opens the full record in the modal when
   clicked. The details are not lost, they are one click away and identical
   everywhere, which is also one behaviour to reason about instead of two. */
/* Without hover, the scroll opens the panel instead: js/motion.js puts .is-near
   on a card once the whole of it is inside the viewport and takes it off again
   as soon as an edge leaves, so the details unfold on the card you have fully
   scrolled to and fold away again as it goes. The panel is anchored to the
   bottom of the card, so growing the row lifts the text up over the photograph
   and shrinking it drops the text back down — the open and close run with the
   scroll, and both directions replay every time.

   Every card open at once was the alternative, and it buried a page of
   photographs under a page of text. Where the animation cannot run — no
   IntersectionObserver, or reduced motion — motion.js adds .is-near to every
   card instead, so the content is never reachable only through motion. */
/* No media query any more: these are the rules for every device. They were the
   touch-only overrides, and with the hover reveal gone they describe the single
   behaviour the card now has everywhere. */
/* No motion on a touch device: the panel is simply open.

   This used to be a scroll-driven animation — js/motion.js puts .is-near on a
   card once the whole of it is in the viewport and takes it off as an edge
   leaves, and the card unfolded its panel, scaled up 2% and zoomed its
   portrait in response. On a pointer that choreography is a reply to
   something the reader did; on a phone it fires on everything you scroll past
   and then undoes itself, so a page of eleven portraits was eleven cards
   swelling and shrinking under the thumb, with the text arriving a beat after
   the card it belongs to.

   Static instead. Every card shows its name, chips and bio from the moment it
   is on screen, at rest, in the same place — nothing to wait for and nothing
   that moves while you are reading it. .is-near still lands on the cards (the
   observer is shared with the rest of the page); it just has nothing to say
   here, and the overrides below make sure a rule from the pointer block above
   cannot leak in through it. */
/* Name only. The chips and bio come off the card entirely rather than being
   shown at rest: the card exists to show a face and say whose it is, and the
   details have somewhere better to be — tapping the card opens the modal with
   the whole record in it, which is the same gesture as everywhere else here
   and the reason the text stays in the markup. */
.cc-more { display: none; }

.committee-card,
.committee-card.is-near {
  transform: none;
  box-shadow: var(--shadow-card);
}
.committee-card .cc-photo img,
.committee-card.is-near .cc-photo img {
  transform: none;
  transition: none;
}
/* The resting scrim is the only scrim, so there is nothing here to hold it
   against — .is-near just needs to not animate it. */
.committee-card.is-near::after { transition: none; }
.member-card {
  display: block;
  color: inherit;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--glass-bg);
  padding: var(--space-5);
  transition: transform var(--transition-mid), background var(--transition-mid);
}
/* No border-colour change on hover — the lift and the background shift already
   say the card is live, and the tinted edge was the loudest thing in a grid of
   otherwise quiet cards. */
.member-card:hover {
  transform: translateY(-4px);
  background: var(--glass-bg-lit);
}
.member-photo {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-md);
  background: var(--graphite);
  border: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  margin-bottom: var(--space-5);
  font-family: var(--font-label);
  font-size: var(--text-caption);
  color: var(--text-3);
}
/* Portraits run in full colour. They used to be desaturated and washed with a
   blue-violet duotone that lifted only on hover, so a member's face changed
   colour when you pointed at it and the grid read as a set of placeholders
   until you did. The zoom on hover is kept — that is the part that made the
   card feel like it could be opened. */
.member-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--transition-slow);
}
.member-card:hover .member-photo img { transform: scale(1.04); }

/* No photo: the shipped avatar graphic on the graphite tile. */
.member-photo.is-empty,
.member-detail-photo.is-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(150deg, var(--graphite), var(--bg-panel));
}
/* Fills the tile like a real portrait would, so a card with no photo has the
   same silhouette as one with. Qualified by the tile to outrank
   `.member-photo img` above, which would otherwise win on specificity. */
.member-photo .member-avatar,
.member-detail-photo .member-avatar {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.member-detail-photo.is-empty { box-shadow: none; }

/* Visually hidden but still announced, used for the "no photo" note that
   replaces alt text when the image itself is gone. */
.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;
}

/* Name and designation are centred under the portrait. The photo is a full
   width square, so a centred caption sits on the card's own axis; ranged left
   it read as though it had slipped out of alignment with the image above it. */
.member-card h4 {
  font-size: var(--text-base);
  color: var(--text);
  margin-bottom: 5px;
  text-align: center;
}

/* Committee page only: Quicksand, whose rounded terminals and open counters
   read softer than Livvic against a grid of faces. Scoped by the body's page
   class, so no other page changes voice. Names take a little more size and a
   little less weight — the softness comes from the shapes, and piling on bold
   would take it straight back out. */
.page-committee .member-card h4,
.page-committee .member-detail-id h3 {
  font-family: "Quicksand", var(--font-body);
  font-size: var(--text-md);
  font-weight: 600;
  letter-spacing: 0;
}
.page-committee .member-card > span,
.page-committee .member-detail-role {
  font-family: "Quicksand", var(--font-body);
  font-weight: 600;
  /* The site's designation style is uppercase on wide tracking; that is the
     severity being softened here, so it comes off. */
  text-transform: none;
  letter-spacing: 0.01em;
  font-size: 0.9rem;
}
/* A tighter title band on this page only.
   The committee grid is the one page whose content wants to be seen two rows at
   a time, so the band above it is trimmed to buy that height: less padding, and
   a title a step smaller. Scoped to .page-committee rather than changed on
   .page-header, because About, Events, News, Gallery, Contact and Membership all
   share that band and none of them has the same problem. */
.page-committee .page-header { padding: var(--space-5) 0; }
.page-committee .page-header h2 {
  /* 0.46 of --text-3xl against the 0.6 every other page uses. Still the largest
     thing on the page, just no longer taller than it needs to be. */
  font-size: calc(var(--text-3xl) * 0.46);
}

.page-committee .section-head h2 {
  font-family: "Quicksand", var(--font-display);
  font-weight: 700;
}
/* Direct child only. A descendant selector here also catches the initials and
   the screen-reader note inside .member-photo, which are not designations. */
.member-card > span {
  display: block;
  text-align: center;
  font-family: var(--font-alt);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cyan);
}
.member-card:hover h4 { color: var(--text); }

/* Member detail, dialog and the no-JS inline path share this. */
.member-modal {
  width: min(600px, calc(100vw - 32px));
  max-height: calc(100vh - 64px);
  /* The universal `* { margin: 0 }` reset above overrides the browser's own
     `dialog:modal { margin: auto }`, which is what centres a modal. Put it back. */
  margin: auto;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-panel);
  color: var(--text-2);
  box-shadow: var(--shadow-deep);
  overflow: visible;
}
.member-modal::backdrop { background: var(--backdrop); backdrop-filter: blur(5px); }
.member-modal-body {
  padding: var(--space-8) var(--space-7) var(--space-7);
  max-height: calc(100vh - 64px);
  overflow-y: auto;
  border-radius: var(--radius-lg);
}

/* Held well clear of the screen edges on a phone. The desktop rule above leaves
   32px, which is right on a large window and far too little on a handset — the
   panel filled it top to bottom and the backdrop, the thing that says "this is
   a layer over the page", disappeared entirely.

   Both the dialog and its scrolling body take the cap: the dialog's own
   max-height clips the panel, and the body's is what makes the overflow scroll
   inside it rather than being cut off. */
@media (max-width: 760px) {
  .member-modal {
    width: calc(100vw - 40px);
    max-height: 76vh;
  }
  .member-modal-body {
    max-height: 76vh;
    padding: var(--space-7) var(--space-5) var(--space-6);
  }
}

.member-modal-close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 38px;
  height: 38px;
  border: 1px solid var(--line);
  background: var(--glass-bg);
  border-radius: var(--radius-sm);
  font-size: 1.3rem;
  line-height: 1;
  color: var(--text-2);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.member-modal-close:hover { background: var(--graphite); color: var(--text); }

.member-detail-inline {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-panel);
  padding: var(--space-7);
  margin-bottom: var(--space-8);
}
.member-detail-back { margin-top: var(--space-5); font-size: var(--text-sm); }
.member-detail-back a { color: var(--blue-lit); }

.member-detail-head {
  display: flex;
  gap: var(--space-5);
  align-items: center;
  margin-bottom: var(--space-5);
}
.member-detail-photo {
  width: 96px;
  height: 96px;
  flex: none;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--graphite);
  border: 1px solid var(--line);
  box-shadow: var(--glow-blue);
}
.member-detail-photo img { width: 100%; height: 100%; object-fit: cover; }
.member-detail-id h3 { font-size: var(--text-xl); color: var(--text); margin-bottom: 6px; }
/* Matches the designation on the card, so opening a member does not change
   the face their job title is set in. Not centred: the detail panel is a
   left-ranged layout with the portrait beside the name, not under it. */
.member-detail-role {
  display: inline-block;
  font-family: var(--font-alt);
  font-size: 0.85rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--cyan);
}
/* The company sits between the job title and the group: it qualifies the
   title ("Treasurer, at this firm") rather than the committee grouping. */
.member-detail-company { font-size: var(--text-sm); color: var(--text-2); margin-top: 6px; }
.member-detail-group { font-size: var(--text-sm); color: var(--text-3); margin-top: 4px; }
/* margin-top as well as padding-top, matching .member-detail-contact and
   .member-detail-social below. The bio used to open the panel, where the head's
   own spacing was the gap above it and a margin would have been doubled space.
   It now follows the contact list, so it needs the same rule-with-air-either-
   side the other two blocks have or its divider sits tight under the phone
   number. */
.member-detail-bio {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
}
.member-detail-bio p { font-size: var(--text-sm); color: var(--text-2); margin-bottom: var(--space-3); }
.member-detail-bio p:last-child { margin-bottom: 0; }

.member-detail-contact {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
}
.member-detail-contact li { display: flex; gap: var(--space-4); align-items: baseline; font-size: var(--text-sm); }
.mdc-label {
  flex: 0 0 62px;
  font-family: var(--font-label);
  font-size: var(--text-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-3);
}
.member-detail-contact a { color: var(--blue-lit); word-break: break-word; }
.member-detail-contact a:hover { color: var(--cyan); }

/* A row of square icon buttons. Sized to the 44px pointer target rather than
   to the 18px glyph inside them, since on a phone these are the smallest
   things on the panel. */
.member-detail-social {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
}
.member-detail-social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text-2);
  transition: border-color var(--transition-fast), color var(--transition-fast),
              background var(--transition-fast);
}
.member-detail-social a:hover {
  border-color: var(--line-hot);
  background: var(--graphite);
  color: var(--cyan);
}
.member-detail-social svg { display: block; }

.member-detail-empty {
  border-top: 1px solid var(--line);
  padding-top: var(--space-5);
  font-size: var(--text-sm);
  color: var(--text-3);
}

/* ── 17 · Forms & contact ───────────────────────────────────────────────── */

/* Held to 1280 rather than the 2160 the rest of the site runs at. This is two
   columns of form fields and four lines of contact detail — stretched across a
   wide monitor the inputs become bands you cannot judge the length of, and the
   panel beside them grows a column of white space down its middle. Width does
   nothing for this page that it does for a grid of cards. */
.contact-grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: var(--space-9);
  align-items: start;
  max-width: 1280px;
}

/* ── Contact details panel ──────────────────────────────────────────────── */

/* Solid flag blue. --cyan is that colour in the light palette, so the panel
   uses the token the nav bar and the links already use rather than adding a
   fifth blue to the page. */
.contact-card {
  padding: var(--space-8);
  border-radius: var(--radius-md);
  background: var(--cyan);
  color: #ffffff;
}
.contact-card h3 {
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: #ffffff;
  margin-bottom: var(--space-8);
}
/* On the blue panel the eyebrow's own colour token would be invisible. */
.eyebrow-invert { color: rgba(255, 255, 255, 0.75); }

.contact-list { display: grid; gap: var(--space-6); }
.contact-list li { display: flex; gap: var(--space-4); align-items: flex-start; }
/* White disc with the mark punched out of it in the panel's own blue. */
.cc-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #ffffff;
  color: var(--cyan);
}
.cc-icon svg { display: block; }
.contact-list strong {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  color: #ffffff;
  margin-bottom: 2px;
}
.contact-list a,
.contact-list p {
  font-size: var(--text-sm);
  color: rgba(255, 255, 255, 0.85);
  margin: 0;
}
.contact-list a:hover { color: #ffffff; text-decoration: underline; }

.contact-form-title {
  font-size: var(--text-xl);
  font-weight: 700;
  letter-spacing: -0.02em;
  margin-bottom: var(--space-6);
}
.contact-form-lede {
  font-size: var(--text-sm);
  color: var(--text-2);
  margin: calc(-1 * var(--space-4)) 0 var(--space-6);
}

/* Filled fields with no visible label — the name lives in the placeholder, so
   the box needs enough presence to read as a field on its own. */
.contact-form-filled input,
.contact-form-filled textarea {
  padding: var(--space-5);
  border: 1px solid transparent;
  background: var(--bg-panel);
}
.contact-form-filled input:focus,
.contact-form-filled textarea:focus {
  border-color: var(--cyan);
  background: var(--bg-deep);
}
.contact-form-filled textarea { min-height: 170px; }
.btn-pill { border-radius: var(--radius-full); padding-inline: var(--space-8); }
.contact-intro h3 { font-size: var(--text-lg); margin-bottom: var(--space-2); }
.contact-intro p { color: var(--text-2); font-size: var(--text-sm); margin-bottom: var(--space-6); }

.info-block { display: flex; gap: var(--space-4); margin-bottom: var(--space-5); }
.info-block .icon {
  width: 40px;
  height: 40px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--graphite);
  /* The SVGs stroke with currentColor, so this is what makes them flag blue. */
  color: var(--cyan);
}
/* Sized here rather than per icon, so one rule governs the set. */
.info-block .icon svg { width: 18px; height: 18px; display: block; }
.info-block h4 { font-size: var(--text-base); color: var(--text); margin-bottom: 4px; }
.info-block p, .info-block a { font-size: var(--text-sm); color: var(--text-2); line-height: 1.5; }
.info-block a:hover { color: var(--cyan); }

form.contact-form { display: grid; gap: var(--space-5); }
.form-wide { max-width: 820px; margin: 0 auto; }

/* The application form as a container in its own right: an outline around the
   whole thing, and the fieldsets' existing top rules then read as divisions
   inside it rather than as stray lines on the page. */
.form-boxed {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-deep);
  padding: var(--space-8);
  box-shadow: var(--shadow-card);
}
/* The rules have to reach the outline to read as divisions rather than as
   underlines floating inside a box, so each fieldset is pulled out to the
   padding edge and given it back as its own inset. */
.form-boxed .form-set {
  margin-inline: calc(var(--space-8) * -1);
  padding-inline: var(--space-8);
}
@media (max-width: 640px) {
  .form-boxed { padding: var(--space-6); }
  .form-boxed .form-set {
    margin-inline: calc(var(--space-6) * -1);
    padding-inline: var(--space-6);
  }
  /* The OTP panel takes the same step down. It was left on --space-8 while
     .form-boxed came off it, which on a 360px screen left about 228px of content
     to hold a 1.6rem field tracked at 0.4em — six characters and five gaps did
     not fit. The tracking is the affordance here (it is what shows how many
     characters are left to type) so the size gives way instead of the spacing. */
  .otp-gate { padding: var(--space-6); }
  .otp-code { font-size: 1.3rem; letter-spacing: 0.3em; }
}
/* minmax(0, 1fr), NOT 1fr — the same trap the phone tab bar hit, and it bites
   harder here. A bare 1fr is minmax(auto, 1fr), and that auto is a MIN-CONTENT
   floor: the track refuses to be narrower than the widest unshrinkable thing in
   it. iOS Safari gives input[type=date] a large intrinsic width (it sizes to the
   date format plus the picker's own chrome) and honours it over width: 100%, so
   on an iPhone the Issued Date track would not shrink, the row grew past the
   card, and the field hung over the card's right edge. Zeroing the floor lets
   the columns divide the width they actually have.

   > * min-width: 0 for the same reason one level down: each column holds a
   wrapper div, and that div is a block whose own min-content floor would pass
   the input's width straight back up to the track. */
.form-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-5); }
.form-row > * { min-width: 0; }
.contact-form label {
  display: block;
  font-family: var(--font-label);
  font-size: var(--text-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--text-3);
  margin-bottom: var(--space-2);
}
.contact-form input,
.contact-form select,
.contact-form textarea {
  width: 100%;
  /* width: 100% alone is not enough to keep a control inside its column. It
     sets the PREFERRED width; a flex or grid parent still asks the control for
     its min-content width, and a native date or file input answers with
     something much wider than the box it is in. These two are what make the
     answer "as narrow as you like". */
  max-width: 100%;
  min-width: 0;
  padding: 13px 15px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--graphite);
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--text);
  transition: border-color var(--transition-fast), background var(--transition-fast),
              box-shadow var(--transition-fast);
}
.contact-form input::placeholder,
.contact-form textarea::placeholder { color: var(--text-3); }
.contact-form input:hover,
.contact-form select:hover,
.contact-form textarea:hover { border-color: rgba(255, 255, 255, 0.16); }
.contact-form input:focus,
.contact-form select:focus,
.contact-form textarea:focus {
  outline: none;
  border-color: var(--blue);
  background: var(--bg-panel);
  box-shadow: 0 0 0 3px rgba(46, 125, 255, 0.18);
}
/* THE DATE FAMILY, WHICH IOS SIZES BY ITSELF.
   Mobile Safari renders these with its own native box: it sizes to the date
   format, adds picker chrome, and treats an author width as advice. Turning the
   appearance off is what makes the rules above apply to it as they do to a text
   input — without this it is the one field on the form that overflows the card.

   line-height: normal, not a min-height and not 1.65. appearance: none drops
   the native box's own minimum, so the field's height becomes its line box plus
   the shared padding. The text inputs beside it never set a line-height, so
   theirs is `normal` — the font's own. Saying 1.65 here (the body's value, and
   the obvious guess) made the date field 56px against their 48px, measured in
   the browser. Matching `normal` is what makes the two boxes the same box, and
   it stays matched if the font ever changes.

   The picker still opens on tap. What is removed is Safari's sizing, not the
   control. */
.contact-form input[type="date"],
.contact-form input[type="time"],
.contact-form input[type="datetime-local"],
.contact-form input[type="month"] {
  -webkit-appearance: none;
  appearance: none;
  line-height: normal;
}
/* The value inside a date input is its own shadow element, and WebKit ships it
   with a margin and a centred alignment of its own. Left unstated, that margin
   is added to the padding above and the field prints taller than every text
   input beside it — 56px against 48px, measured. Zeroing it is what makes the
   two boxes the same box. */
.contact-form input[type="date"]::-webkit-date-and-time-value,
.contact-form input[type="time"]::-webkit-date-and-time-value,
.contact-form input[type="datetime-local"]::-webkit-date-and-time-value,
.contact-form input[type="month"]::-webkit-date-and-time-value {
  margin: 0;
  text-align: left;
}

.contact-form textarea { resize: vertical; min-height: 140px; }
/* The dark background does not reach native select popups on every platform,
   so keep the option list explicitly readable. */
.contact-form select option { background: var(--graphite); color: var(--text); }

.form-note {
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.04em;
  color: var(--text-3);
}

.field-error {
  font-family: var(--font-label);
  font-size: var(--text-caption);
  font-weight: 500;
  color: var(--danger-soft);
  margin-top: var(--space-2);
}
.contact-form input[aria-invalid="true"],
.contact-form select[aria-invalid="true"],
.contact-form textarea[aria-invalid="true"] {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px rgba(206, 17, 38, 0.18);
}

/* Honeypot: off-screen rather than display:none, since some bots skip hidden
   fields but not positioned ones. Never shown to real users. */
.hp-field { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ── Long forms (apply.php) ────────────────────────────────────────────────────
   The membership application asks for eleven things across four groups, which is
   long enough that it needs sectioning to stay readable. <fieldset> and <legend>
   rather than divs and headings: a screen reader announces the legend with every
   field inside it, so "PAN Number" is heard as part of "Your company" instead of
   arriving with no context. */
.form-set {
  border: 0;
  border-top: 1px solid var(--line);
  padding: var(--space-6) 0 0;
  margin: 0;
  display: grid;
  gap: var(--space-5);
}
.form-set:first-of-type { border-top: 0; padding-top: 0; }
.form-set legend {
  /* float+clear, because a <legend> cannot be a grid item: the browser treats it
     as a special child of the fieldset and grid ignores it. */
  float: left;
  width: 100%;
  padding: 0;
  margin-bottom: var(--space-4);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text);
}
.form-set legend + * { clear: both; }
/* A section that opens with a paragraph rather than a field. The legend's own
   margin is the whole gap — a negative margin-top on the paragraph cannot close
   it, because clearance is computed after margins and simply absorbs it — so the
   legend gives the space back instead. */
.form-set-tight legend { margin-bottom: var(--space-2); }

.form-hint {
  font-size: var(--text-sm);
  color: var(--text-2);
  max-width: 62ch;
  margin-top: calc(-1 * var(--space-2));
}
/* Under a label, the hint belongs to the field above it, so it tucks up close. */
.contact-form .form-hint { margin-top: var(--space-2); }

/* ── A file carried over from a failed submit ──────────────────────────────
   Sits directly under its own file input and names what the server is still
   holding, because the input above it cannot show it: a browser will not let a
   page pre-fill a file picker, so after a correction the picker reads "No file
   chosen" over an attachment that is in fact safe. See includes/apply_stash.php.

   Deliberately quiet. This is a reassurance, not an alert — the applicant did
   nothing wrong, and a bordered callout per field would turn six pieces of good
   news into a page that looks like six problems. */
.kept-file {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
  font-family: var(--font-label);
  font-size: var(--text-caption);
  color: var(--text-2);
}
/* Flag blue, the same colour the eyebrows and technical labels carry. There is
   no green in this palette, and adding one for a tick would put a colour on the
   page that means nothing anywhere else on the site. */
.kept-tick {
  color: var(--cyan);
  font-weight: 700;
}
.kept-name {
  font-weight: 600;
  color: var(--text);
  /* A long filename must not push the Remove control out of the row, and these
     are the applicant's own names — "scan of my citizenship front side.jpeg". */
  overflow-wrap: anywhere;
}
.kept-size { color: var(--text-3); }
/* Pushed to the end of the row so it reads as an action on the line rather than
   as another piece of the filename. */
.kept-drop {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-3);
  cursor: pointer;
  white-space: nowrap;
}
.kept-drop input { margin: 0; }
.kept-drop:hover { color: var(--danger-soft); }

/* The asterisk is redundant to a screen reader — the input carries `required`,
   which is announced — so it is purely visual and must not be read out twice.
   Colour alone never marks a required field: the asterisk is the signal. */
.req { color: var(--danger); font-weight: 700; }

/* The other half of the same signal. A form where most labels carry an asterisk
   and a few carry nothing reads as an oversight, so the fields the admin has
   relaxed say so in words — quieter than the label, never louder. Which fields
   these are is set in the admin: see includes/application_fields.php. */
.opt {
  color: var(--text-2);
  font-weight: 500;
  font-size: 0.78em;
  letter-spacing: 0.04em;
  text-transform: none;
}

.form-note-warn {
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--line-hot);
  border-radius: var(--radius-sm);
  background: var(--glass-bg-lit);
  color: var(--text-2);
  text-transform: none;
  letter-spacing: normal;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  margin-bottom: var(--space-6);
}
/* The reassuring counterpart, used on the payment step to say the application
   is already safe before asking for one more thing. */
.form-note-ok {
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--line);
  border-left: 3px solid var(--cyan);
  border-radius: var(--radius-sm);
  background: var(--bg-panel);
  color: var(--text-2);
  text-transform: none;
  letter-spacing: normal;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  margin-bottom: var(--space-6);
}

/* ── Payment step ──────────────────────────────────────────────────────────
   One centred column at every width: the code to scan, then the amount it is
   for, then who is paying.

   This was two columns — QR on the left, price and name on the right — which
   put the one thing the visitor came here to do hard against the left edge
   with a third of the panel empty beside it. The QR has a fixed maximum size
   for a reason (a QR that has shrunk is a QR nobody can scan), so the column
   beside it could never fill that space on a wide screen, and the layout was
   at its worst exactly where there was most room. Centred, the panel reads as
   one instruction with its details underneath, and the phone and the desktop
   get the same arrangement instead of two that drift apart. */
.pay-grid {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
  text-align: center;
}
/* The price and the name sit under the code, held to a readable measure so a
   long email address does not stretch the centred text across the panel. */
.pay-side {
  max-width: 46ch;
  display: grid;
  gap: var(--space-2);
}
/* .form-hint is ranged left and capped at 62ch by default, both of which fight
   the centring here. */
.pay-side .form-hint {
  margin: 0;
  max-width: none;
  text-align: center;
}
.pay-qr {
  padding: var(--space-4);
  background: #ffffff;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  line-height: 0;
  /* The frame sizes to the code inside it, which is up to 320px wide. On a
     narrow phone that plus the padding is wider than the panel, and a centred
     flex item does not shrink on its own — without this the QR pushes the page
     into a sideways scroll. */
  max-width: 100%;
}
/* Always on white and never colour-graded, whatever the surrounding theme does:
   a scanner reads contrast, and a tinted or inverted QR can simply fail. */
.pay-qr img {
  display: block;
  width: 100%;
  max-width: 320px;
  height: auto;
  filter: none;
}
/* The one statement of the price on this page, now that the fieldset carries no
   "Pay NPR 12,000" legend above it. The gap below comes from the column's own
   gap rather than a margin here, so it cannot double up with it. */
.pay-amount {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--text);
}
/* The two-column breakpoint that used to live here is gone: the column is
   centred at every width now, so there is nothing left to rearrange. The QR
   keeps its own max-width and simply stops growing. */

/* ── Email verification gate (apply.php) ────────────────────────────────── */

/* A panel rather than a bare form. It is the only thing on the page at this
   point, and without a boundary a lone email field under a page heading reads
   like the application form has failed to load. */
.otp-gate {
  padding: var(--space-8);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--glass-bg);
  max-width: 560px;
}
.otp-gate h3 { font-size: var(--text-lg); margin-bottom: var(--space-3); }
.otp-lede {
  font-size: var(--text-sm);
  color: var(--text-2);
  margin-bottom: var(--space-6);
}
/* Wide, spaced digits: six characters typed one at a time from a phone
   notification, where a normal text field gives no feedback about how many are
   left to go. */
.otp-code {
  font-family: var(--font-display);
  font-size: 1.6rem;
  letter-spacing: 0.4em;
  text-align: center;
}
.otp-help {
  margin-top: var(--space-5);
  font-size: var(--text-caption);
  color: var(--text-3);
}
.otp-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line);
}

.otp-confirmed {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
  font-size: var(--text-sm);
  color: var(--text-2);
}

/* A button that has to read as a link: these are secondary paths out of the
   flow ("send it again", "use a different email") and giving them real buttons
   would put three competing calls to action on one small panel. */
.btn-link {
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--blue-lit);
  text-decoration: underline;
  cursor: pointer;
}
.btn-link:hover { color: var(--cyan); }

.field-verified {
  margin-top: var(--space-2);
  font-family: var(--font-label);
  font-size: var(--text-caption);
  color: var(--text-3);
}
/* A readonly field is not disabled — it is still focusable and still submitted.
   It reads as settled rather than broken. */
.contact-form input[readonly] {
  background: var(--graphite);
  cursor: default;
}

/* File inputs: a plain white field with the button as a shaded segment on the
   left, divided by a rule — the browser's own arrangement, tidied.
   ::file-selector-button reaches the native button in every current engine, so
   it no longer has to be left alone; the padding is zeroed on the input itself
   so the button can sit flush against the field's edge instead of floating in
   the middle of a padded box. */
.contact-form input[type="file"] {
  width: 100%;
  /* Same reason as the date fields: "Choose File / no file selected" is the
     control's min-content width, and a long chosen filename makes it wider
     still. overflow: hidden below clips the text; these stop it pushing the
     column open before there is anything to clip. */
  max-width: 100%;
  min-width: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-deep);
  color: var(--text-2);
  font-size: var(--text-sm);
  line-height: 1;
  cursor: pointer;
  overflow: hidden;
}
.contact-form input[type="file"]::file-selector-button {
  padding: 11px var(--space-5);
  margin: 0 var(--space-4) 0 0;
  border: 0;
  /* The divider. A right border rather than a box, so the button reads as a
     segment cut off the field rather than a button sitting inside one. */
  border-right: 1px solid var(--line);
  border-radius: 0;
  background: var(--bg-panel);
  color: var(--text);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.contact-form input[type="file"]::file-selector-button:hover { background: var(--graphite); }
.contact-form input[type="file"]:hover { border-color: var(--line-hot); }
.contact-form input[type="file"]:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

/* ── Committee strip under the contact form ──────────────────────────────────
 *
 * Cards with the portrait breaking out over the top edge.
 *
 * Flex with a fixed basis, like .gallery-grid, .tile-grid and the committee
 * rows: the cards are a set width and the strip is centred, so a last row of
 * two sits under the middle of the row above instead of hanging off the left.
 * Grid cannot centre only its final line.
 *
 * The colours are this site's, not the reference's: the reference fills its
 * buttons coral and its cards light grey, which belong to that brand. Here the
 * card is --bg-panel with the standard hairline and radius, and the only accent
 * is the crimson ring on the portrait — the same crimson the fact chips on the
 * committee page use.
 */
.team-strip {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  /* Matches the gap the card widths above are calculated against. */
  gap: var(--space-4);
  /* 1040, not the 1280 .contact-grid uses. The strip used to match that cap so
     the first and last cards lined up with the edges of the panel and the form
     above. That alignment was bought with a quarter-of-1280 card — 308px — and
     once the email and telephone number moved into the popup there was nothing
     left to put in it: a name, a role, and ninety-odd pixels of empty card. An
     edge that lines up with a block one section away is worth less than a card
     that looks finished. Capped narrower, the strip stays centred under the
     form and each card sizes close to its content.

     margin-inline: auto is not optional. This strip is a child of .container
     rather than being the container the way .contact-grid is, so a cap on its
     own pins it to the left gutter while everything above it stays centred —
     the cap and the auto margin travel together. */
  max-width: 1040px;
  margin-inline: auto;
}
/* Portrait left, words right, sized to the name.
 *
 * No column fraction. Every version of this card that pinned all eleven to one
 * width had to pick which failure to take, because a shared width is the widest
 * name's width: at a quarter of 1280 the short names sat in ninety pixels of
 * empty card, and at a quarter of 1040 "Mr. Suhrid Raj Ghimire" and half the
 * committee broke across two lines. There is no number that fixes both, because
 * the names are not the same length.
 *
 * So the card is content-width and the strip centres the rows (see
 * justify-content above). "Mr. Anil Karki" gets a small card, "Mr. Sandeep
 * Kumar Sharda" gets a wide one, every name stays on one line, and no card
 * carries a band of empty space. The rows come out ragged at the ends, which is
 * what centring is for.
 *
 * flex-shrink 0, or a row one card too full would squeeze the cards instead of
 * wrapping — and a squeezed card wraps its name, which is the thing this is
 * here to prevent. max-width keeps a single very long name inside the strip on
 * a narrow screen rather than pushing the page sideways. */
.team-card {
  flex: 0 0 auto;
  max-width: 100%;
  display: flex;
  /* Centred, not top-aligned. The card used to print the person's email and
     telephone number under their role, and a four-line text column reached the
     bottom of the portrait on its own. Those two lines now live in the popup,
     so the text column is shorter than the photo beside it and pinning it to
     the top left a wedge of empty card under every name. */
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--bg-panel);
  /* Ranged left throughout now. Centred text under a left-hand portrait leaves
     the name floating away from the face it belongs to. */
  text-align: left;
  /* The card is an anchor — it opens the person's details in the shared modal —
     so it has to be told not to look like one. */
  color: inherit;
  text-decoration: none;
  transition: border-color var(--transition-mid), transform var(--transition-mid);
}
/* Everything that is not the photo, so the name and role stack in the remaining
   width. min-width: 0 because a flex item's default minimum is its content —
   without it a long name widens the card instead of wrapping inside it. */
.team-body { min-width: 0; }
.team-card:hover,
.team-card:focus-visible {
  border-color: var(--line-hot);
  transform: translateY(-3px);
}
/* A touch browser holds :hover after a tap until another element is tapped, so
   without this a tapped card stayed lifted and outlined behind the modal it had
   just opened. Same fix as the committee card's scrim above. */
@media not all and (hover: hover), (max-width: 600px) {
  .team-card:hover {
    border-color: var(--line);
    transform: none;
  }
}

.team-photo {
  /* flex: none, or the photo would be squashed by a long name beside it. */
  flex: none;
  /* A FIXED square. Both numbers are written down deliberately.
     Deriving the width from the height (align-self: stretch + aspect-ratio)
     aligned the bottom edge perfectly and then ate the page: the photo's width
     came from its height, the text column got whatever was left, narrower text
     wrapped taller, which made the photo taller and therefore wider again. It
     ran away to full-page portraits with one character per line of text.
     Never size this box from its own sibling's height.

     76, down from 96. The 96 was set against a four-line text column — name,
     role, email, telephone — and once the last two moved into the popup the
     portrait was taller than everything it sat next to, which made the strip
     read as eleven photographs with captions rather than eleven names. */
  width: 76px;
  height: 76px;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: var(--radius-md);
  /* No ring. The card already has a hairline around it, and a second one in
     brand red drew the eye to the frame rather than to the face inside it. */
  background: var(--graphite);
}
.team-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* No portrait: initials on the dark tile, matching committee_initials()'s use
   on the committee page rather than the light grey placeholder graphic. */
.team-initials {
  font-family: "Quicksand", var(--font-body);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--text-2);
}

.team-card h3 {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.3;
  /* The card takes its width from this line, so this line is not allowed to
     decide it needs two. Without it a flex item still wraps at its max-content
     width when the row is tight, and one wrapped name makes its card a
     different height from its neighbours'. */
  white-space: nowrap;
}
.team-role {
  margin: 3px 0 0;
  font-size: var(--text-caption);
  color: var(--text-2);
}


/* No column-count breakpoints between here and the phone. They used to reset
   flex-basis to a third and then a half of the row, which content-width cards
   do not need: a card is as wide as its name, the row wraps when the next one
   will not fit, and the count falls from four to three to two on its own as the
   window narrows. Re-stating a fraction here would just pin the cards back to a
   shared width and bring the wrapped names back with it. */
@media (max-width: 560px) {
  /* One per row, and full width rather than content width — a strip of cards of
     eleven different widths down a phone screen reads as broken alignment, not
     as centring. */
  .team-card { flex: 0 0 100%; max-width: 100%; }
  /* Wrapping is allowed again here, and only here. The card is no longer
     measured from the name — it is the width of the screen — so a name too long
     for one line should use two rather than run off the edge. */
  .team-card h3 { white-space: normal; }
}

/* Full-bleed map closing the Contact page. No container, no border, no radius:
   it runs to both edges and butts straight onto the footer, so the page ends on
   the map rather than on a framed picture of one. */
.map-band {
  position: relative;
  height: clamp(360px, 48vh, 560px);
  line-height: 0;
}
.map-band iframe {
  width: 100%;
  height: 100%;
  border: 0;
  filter: var(--map-filter);
}

/* Boxed variant, still used anywhere a map sits inside a column. */
.map-embed {
  position: relative;
  height: 300px;
  margin-top: var(--space-7);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  overflow: hidden;
}
/* Google's embed has no dark mode. Invert-and-rotate is the standard trick
   to grade it to match without hosting our own tiles. */
.map-embed iframe {
  width: 100%;
  height: 100%;
  border: 0;
  filter: var(--map-filter);
}

/* ── 18 · Footer ────────────────────────────────────────────────────────── */

/* A blue wash rather than another white band. The page runs white from the top
   down, and the footer had no reason to be distinguishable from the section
   above it; a tint gives the page a bottom edge.

   The colour is the flag blue the rest of the site already uses, laid over the
   base background at low alpha rather than picked as a new hex — so it stays
   the same blue as the nav bar and the links, just faded. Both stops are
   tokens, because dark and light need different alphas to read as the same
   strength of tint. */
/* The last section on a page now hands straight over to the footer, which draws
   its own border-top. Without this the two rules stack into one 2px line. */
main > .section:last-child { border-bottom: 0; }

footer.site-footer {
  position: relative;
  background:
    linear-gradient(180deg, var(--footer-tint-a) 0%, var(--footer-tint-b) 100%),
    var(--bg-deep);
  border-top: 1px solid var(--line);
  padding-top: var(--space-10);
  overflow: hidden;
}
footer.site-footer .container { position: relative; z-index: 1; }
/* Runs to the viewport edges with the same breathing gap the header's top
   strip uses, so both ends of the page align on one margin. */
.footer-grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1.2fr 1.2fr;
  gap: var(--space-8);
  max-width: var(--max-width);
  margin: 0 auto;
  /* No dividing rule above the bottom bar: across a full-width footer it drew a
     hard line straight through the wordmark behind it, and the change of
     alignment and type size already separates the two. */
  padding: 0 var(--page-gutter) var(--space-8);
}
.footer-grid h4 {
  font-family: var(--font-label);
  font-size: var(--text-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--text-3);
  margin-bottom: var(--space-5);
}
.footer-grid ul li { margin-bottom: var(--space-3); font-size: var(--text-sm); }
.footer-grid ul a { color: var(--text-2); transition: color var(--transition-fast); }
.footer-grid ul a:hover { color: var(--cyan); }
/* The logo carries the name here, so there is no wordmark heading to style. */
.footer-logo { height: 56px; width: auto; margin-bottom: var(--space-5); }
.footer-brand p { font-size: var(--text-sm); color: var(--text-2); max-width: 42ch; }

/* Round icon buttons, not the lettered pills this used to use. Three uppercase
   words in a row read as another link list competing with the column beside
   them; the marks are recognised without being read. */
.footer-social { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-6); }
.footer-social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--line);
  border-radius: var(--radius-full);
  color: var(--text-2);
  transition: border-color var(--transition-fast), color var(--transition-fast),
              background var(--transition-fast);
}
.footer-social a:hover {
  border-color: var(--line-hot);
  color: var(--cyan);
  background: var(--graphite);
}
.footer-social svg { display: block; }

/* Contact rows: icon in a fixed gutter so the text of every row starts on one
   line, however long the row above wrapped. */
.footer-contact li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-2);
}
.fc-icon { flex: none; color: var(--cyan); line-height: 1; margin-top: 2px; }
.fc-icon svg { display: block; }

.footer-cta p {
  font-size: var(--text-sm);
  color: var(--text-2);
  margin-bottom: var(--space-5);
}

.footer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-4);
  max-width: var(--max-width);
  margin: 0 auto;
  padding: var(--space-5) var(--page-gutter);
  font-family: var(--font-label);
  font-size: var(--text-micro);
  letter-spacing: 0.1em;
  color: var(--text-3);
}
/* Nav repeated along the bottom, separated by a dot drawn as a pseudo-element
   so there is no punctuation for a screen reader to read out between links. */
.footer-nav { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); }
.footer-nav li { position: relative; }
.footer-nav li + li::before {
  content: "";
  position: absolute;
  left: calc(var(--space-4) / -2);
  top: 50%;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--line-hot);
  transform: translate(-50%, -50%);
}
.footer-bottom a { color: var(--text-3); transition: color var(--transition-fast); }
.footer-bottom a:hover { color: var(--cyan); }

/* Back to top. A link, not a scripted button, so it works with JS off. Fixed
   to the corner and out of the way of the footer's own content. */
.to-top {
  position: fixed;
  right: var(--space-5);
  bottom: var(--space-5);
  z-index: 150;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  /* Red and borderless, carrying a chevron rather than an arrow glyph. The
     outline was there to hold the shape against the page; a solid fill with a
     shadow does that better, and red keeps it reading as a control rather than
     as part of the blue furniture around it. */
  border: 0;
  background: var(--btn-accent-fill);
  color: var(--btn-accent-ink);
  line-height: 1;
  box-shadow: 0 6px 16px -6px rgba(0, 0, 0, 0.5);
  transition: transform var(--transition-fast), background var(--transition-fast),
              box-shadow var(--transition-fast);
}
:root[data-theme="light"] .to-top { box-shadow: 0 6px 18px -6px rgba(20, 23, 26, 0.35); }
.to-top:hover {
  transform: translateY(-2px);
  background: var(--btn-accent-fill-hover);
  box-shadow: 0 10px 22px -8px rgba(20, 23, 26, 0.45);
}

/* Wordmark at architectural scale, sitting behind the footer rather than under
   it. It used to be a block in the normal flow, so it claimed a band of its own
   height at the very bottom of every page — a lot of blank space for a purely
   decorative mark. It is a background layer now: absolutely positioned, lifted
   so the columns and the bottom bar overlap it, and clipped by the footer's own
   overflow where it runs off the edge. */
.footer-mark {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.28em;
  z-index: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(4rem, 17vw, 15rem);
  line-height: 0.8;
  letter-spacing: -0.05em;
  text-align: center;
  color: transparent;
  -webkit-text-stroke: 1px var(--wordmark-stroke);
  user-select: none;
  pointer-events: none;
}
/* The footer's own content sits above the mark. .footer-grid and .footer-bottom
   are no longer .container elements, so they need this in their own right. */
.footer-grid,
.footer-bottom { position: relative; z-index: 1; }

/* ── 19 · Page header (inner pages) ─────────────────────────────────────── */

.page-header {
  position: relative;
  /* Even top and bottom, same as .hero — the header is in the flow above this
     and there is nothing fixed to clear. Kept tight: this band holds a single
     line of title and a breadcrumb, and at --space-9 it was reserving the
     height of a section for two lines of text. */
  padding: var(--space-7) 0;
  background: var(--bg-void);
  overflow: hidden;
  border-bottom: 1px solid var(--line-soft);
}
/* Centred: the page title and its breadcrumb sit on the page's centre line on
   every inner page (About, Committee, Events, News, Gallery, Contact,
   Membership). Set here on the band rather than per page, so a new page picks
   it up by including partials/page-header.php and nothing else. */
.page-header .container {
  position: relative;
  z-index: 1;
  text-align: center;
}
.page-header h2 {
  /* 0.6 of --text-3xl. Written as a multiple rather than a fresh
     clamp() so it keeps tracking the type scale: retune --text-3xl and this
     follows at the same ratio. Scoped to the page-header deliberately, so
     other users of that token keep it at full size. */
  font-size: calc(var(--text-3xl) * 0.6);
  letter-spacing: -0.035em;
  /* Was flag blue (--cyan, #003893 in light). Now a neutral graphite, so the
     title band states the page rather than competing with the crimson and blue
     the controls use. Kept to the light theme — #454545 on the dark palette's
     near-black background is unreadable, so dark stays on --text. */
  color: var(--text);
  margin-bottom: var(--space-4);
}
:root[data-theme="light"] .page-header h2 { color: #454545; }
.breadcrumb {
  font-family: var(--font-label);
  font-size: var(--text-caption);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}
.breadcrumb a { color: var(--text-2); transition: color var(--transition-fast); }
.breadcrumb a:hover { color: var(--cyan); }

/* ── 20 · Responsive ────────────────────────────────────────────────────── */

/* Wide displays get more columns, not wider cards.
 *
 * Widening the page without this just inflated every tile: a gallery still went
 * from a sensible card to half a metre of photo, and a three-card row spread
 * until the gaps read as the layout. The extra width is spent on fitting more
 * per row instead, so each item stays close to the size it was and the page
 * gains density rather than scale.
 *
 * 1500px is where a 4-up row of gallery cards first has room to be larger than
 * the 3-up row was at 1280 — below that, adding a column would shrink them. */
@media (min-width: 1500px) {
  .grid-3 { grid-template-columns: repeat(4, 1fr); }

  /* The gallery is deliberately left out of the wide-screen column bumps: it is
     held at three across at every width to match the news and events tiles. */

  .member-grid > .member-card {
    flex-basis: calc((100% - 4 * var(--space-5)) / 5);
    max-width: calc((100% - 4 * var(--space-5)) / 5);
  }

}

/* Wider still: one more column again on the sets that carry many items. Cards
   with a photo and a paragraph stay at four — past that the text column gets
   too narrow for the body copy inside it. */
@media (min-width: 1900px) {
  .member-grid > .member-card {
    flex-basis: calc((100% - 5 * var(--space-5)) / 6);
    max-width: calc((100% - 5 * var(--space-5)) / 6);
  }

}

@media (max-width: 1080px) {
  /* The hero is a single column of copy over a full-bleed photo at every width,
     so there is nothing to stack here any more — only the measure comes in. */
  .hero-inner { max-width: 34rem; }
  /* Stacked, with the photo capped so a full-width circle does not become the
     whole screen before the words start. */
  .about-split { grid-template-columns: 1fr; gap: var(--space-8); }
  .about-figure { max-width: 420px; margin-inline: auto; }
  .spec-row { grid-template-columns: 90px minmax(0, 1fr); }
  .spec-row p { grid-column: 2; }
  .masonry { column-count: 2; }
  .gallery-grid > .gallery-item {
    flex-basis: calc((100% - var(--space-5)) / 2);
    max-width: calc((100% - var(--space-5)) / 2);
  }
  /* Tracks .grid-3/.grid-4 dropping to two across at this width. */
  .member-grid > .member-card {
    flex-basis: calc((100% - var(--space-5)) / 2);
    max-width: calc((100% - var(--space-5)) / 2);
  }
  /* Three across — the tablet count. Two across at this width drew cards
     ~360px wide: on an iPad the row below the president read as two posters
     rather than as a committee, and a single portrait took a third of the
     screen's height. Three brings the card back to roughly the ~230-260px the
     desktop row runs at, which is also the width the panel's chips were laid
     out for, and puts three faces on a line where the page is wide enough to
     read them. The floor in the base --cc-w does not apply here: this is a
     percentage of the row, deliberately, so the cards track the window.

     Below 760px it drops to two again — see that breakpoint. */
  .committee-grid { --cc-w: calc((100% - 2 * var(--space-5)) / 3); }
  .committee-row  { max-width: calc(3 * var(--cc-w) + 2 * var(--space-5)); }
}

/* ---- The tablet masthead --------------------------------------------------
 *
 * The top tier holds five things — logo, email, phone, socials, two pills — and
 * between 760px and the desktop layout there is no longer room for all of them.
 * Nothing was hidden there, so instead everything shrank at once: the phone
 * number broke across two lines, the Apply pill wrapped inside itself, and the
 * social icons closed up against their rule. Five crushed items read as clutter;
 * the fix is to carry fewer, in the order of what the bar is actually for.
 *
 * Socials go first: they are a duplicate — the footer carries the same set at a
 * size worth tapping — and they are the only items here that are decoration
 * rather than an action or a way to make contact. */
@media (max-width: 1080px) {
  .header-social { display: none; }

  /* The wordmark goes to one line. On desktop it is set to a 15ch measure and
     breaks after "AVL Association" on purpose — beside a 58px mark in a wide bar
     it reads as a caption to the artwork. In this band that same break was the
     loudest piece of clutter in the masthead: a two-line name stacked against a
     two-line phone number and a two-line pill, three ragged blocks on one row.
     With the socials gone (and the contact rows below 920) the line has room for
     the name whole, and a name read straight across is what a masthead is for.

     A hair larger with it, because one line of 1rem next to a 58px mark reads as
     small print rather than as the name of the association. */
  .brand-wordmark {
    max-width: none;
    white-space: nowrap;
    font-size: 1.08rem;
  }
  /* The rule was the socials' left border, and with them gone the gap it was
     dividing does not exist. */
  .header-contact { gap: var(--space-4); }
  .header-login,
  .header-apply { margin-left: var(--space-4); }
}

/* Below this the two pills and the email address cannot share the line either.
 * The pills stay — they are the two things the masthead is for — and the email
 * and phone go: both are one tap away in the footer and on the contact page, and
 * on a tablet neither is a link anybody dials from the top of the screen. */
@media (max-width: 920px) {
  .hc-item { display: none; }
  /* .header-contact is still the flex parent of the two pills, so it stays; it
     is only its text rows that have gone. Without this it kept the leading gap
     of an empty first item. */
  .header-contact { gap: 0; }
}

/* ---- The tablet hero ------------------------------------------------------
 *
 * A band, not a screenful. The base rule fills the viewport below the header
 * (100svh - 124px), which on a 768x1024 iPad is a ~900px hero holding ~350px of
 * copy: the headline and buttons float in the middle of a portrait crop of a
 * landscape photograph, and the section below never appears above the fold, so
 * the page reads as having nothing under it.
 *
 * The cap is on the range, not on phones: below 760px the viewport is short
 * enough that the full-height rule produces a normal-looking hero, and shrinking
 * it there would strand the copy in a letterbox. Desktop keeps the full screen,
 * where the photo's aspect ratio and the viewport's actually agree.
 *
 * clamp, so it is still proportional between the two ends of the tablet range
 * rather than one fixed number: 62svh on a 1024-tall iPad is ~635px, the 600px
 * ceiling holds a 1366-tall iPad Pro to the same band, and the 420px floor keeps
 * a landscape tablet from crushing the headline. */
@media (min-width: 761px) and (max-width: 1080px) {
  .hero {
    min-height: clamp(420px, 62svh, 600px);
    padding: var(--space-9) 0;
  }
}

@media (max-width: 900px) {
  .section { padding: var(--space-9) 0; }
  /* Was `140px 0 var(--space-8)`, reserving room for a position:fixed header.
     The header is position:relative and in the flow now (see line ~551), and the
     base rule above already says so — this override was the last place still
     paying for a layout that changed. On a 390x844 phone it pushed the page title
     to y=216: a quarter of the screen, white, above every inner page. Symmetric
     again, one step down from the base --space-7 because the band is tighter here.
     .page-committee .page-header (line ~3450) is unaffected — two classes beat
     one at any width — so the trimmed committee band and the --cc-w height math
     that tracks it both still stand. */
  .page-header { padding: var(--space-6) 0; }
  .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
  /* Six across stops fitting a number and its label well before this. */
  .stats-grid { grid-template-columns: repeat(3, 1fr); }
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .contact-grid { grid-template-columns: 1fr; gap: var(--space-8); }
  .pillar { grid-template-columns: 1fr; gap: var(--space-5); padding: var(--space-6); }
  .news-lead { grid-template-columns: 1fr; gap: var(--space-6); padding: var(--space-6); }
  .section-head-split { flex-direction: column; align-items: flex-start; }
}

@media (max-width: 760px) {
  /* Back to two across. Three cards on a large phone are ~230px each before
     the gaps, and the panel underneath carries a name plus two label/value
     chips — "Second Vice President" against a company name has nowhere to go at
     that width. The tablet count above only earns its keep while the row is
     wide enough for the text on the cards. */
  .committee-grid { --cc-w: calc((100% - var(--space-5)) / 2); }
  .committee-row  { max-width: calc(2 * var(--cc-w) + var(--space-5)); }

  /* The rows stop being rows here. committee.php cuts the list into 1, 3 and
     the rest, which is a desktop shape: two across, the row of three wrapped to
     2+1 and left one card stranded in the middle of the page before the members
     started again underneath. display: contents takes the row boxes out, so the
     cards become items of the grid itself and pack as one run — the lead card,
     then 2-2-2-2 to the end, with only the very last line allowed to be short.

     The lead card still gets a line to itself without being told to: it is
     wider than --cc-w, so a second card never fits beside it. */
  .committee-grid {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
  }
  .committee-row { display: contents; }

  /* The height of the one remaining header tier, as a token because three rules
     need to agree on it: the bar itself, the drawer's offset, and the hero's
     min-height. They previously each carried their own number (76px, 64px, 62px)
     and all three were different — hence a drawer that opened into the logo and
     a hero that overshot the screen. One value, no drift. */
  :root { --header-h: 68px; }

  /* The contact strip and the nav strip both collapse: on a phone an email
     address, a phone number and three social icons cannot share a line with
     the logo, and the nav becomes a drawer as before. What survives in the top
     tier is the logo and the toggle. */

  /* 58px was sized for a bar that also held an email address, a phone number,
     three socials and a login pill — it had to hold its own against all that.
     Alone on the line with just the toggle it only has to be legible, and at
     46px the mark reads as a considered piece of the bar rather than the largest
     thing in it. Stays left: the conventional corner, and the space it leaves is
     now the header's breathing room instead of a hole where the pill was. */
  .brand-logo { height: 46px; }
  .header-top-inner { min-height: var(--header-h); }

  /* The wordmark stays on phones. It came off here on the argument that it was
     a third thing competing for one short line with the mark and the toggle —
     but the mark sets its letters in a circle and reads as a device, so a
     handset visitor arriving on it was told nothing about whose site this is
     until they scrolled. That is exactly the visitor who needs telling.

     It keeps the desktop lockup: two lines on a ~15ch measure beside the mark,
     which at this size costs about 110px and leaves the 46px mark, the 44px
     toggle and the gutters comfortable inside 360px. text-wrap:balance (from
     the heading rule in section 2) evens the two lines rather than stranding
     "Nepal" alone on the second.

     Sized with clamp on vw rather than a flat rem, and that part is deliberate:
     Android Chrome applies the OS accessibility font-size on top of rem, so a
     fixed size rendered far wider on a real handset than in an emulator and
     pushed the name into the toggle. The width available is a viewport problem,
     so a viewport unit drives it; the px bounds keep it legible on a 320px
     phone and stop it inflating on a small tablet. */
  .brand-wordmark {
    max-width: 15ch;
    font-size: clamp(0.78rem, 3.1vw, 0.95rem);
    line-height: 1.2;
  }
  /* The gap comes back with the wordmark — at 0 the name sat against the mark
     and read as part of the artwork. */
  .brand { gap: var(--space-3); min-width: 0; }
  .nav-toggle { flex: none; }
  /* The whole contact strip goes, Login included. It used to stay here on the
     argument that it is an action rather than a detail — true, but it left three
     competing things on a 360px line (logo, a crimson pill, the toggle) and the
     pill read as the page's primary call to action when it is a side door for
     existing members. It reappears as the last row of the drawer instead
     (.nav-member below), which is where a phone user looks for account links. */
  .hc-item, .header-social, .header-apply, .header-login, .header-signout { display: none; }
  /* The wrapper goes too, not just its contents. With everything inside hidden it
     was a zero-width flex item that still drew .header-top-inner's 20px gap, which
     pushed the centred lockup 10px off true centre — a shift small enough to look
     like sloppiness rather than a bug. */
  .header-contact { display: none; }
  /* The nav leaves the bar for a drawer at this width, so the blue strip has
     nothing left to hold and would show as a stray band under the logo. */
  .header-bar { background: transparent; }

  /* == THE PHONE DRAWER =====================================================
     A panel down the left, sized to its own content, over a dimmed page.

     It used to be a full-width sheet hanging off the bottom of the header
     (position: absolute; top: 100%; left/right: 0) with its height capped at a
     fraction of the screen. Two things were wrong with that. It read as the
     page having grown a section rather than as a menu having opened over it,
     and the cap was a guess at how tall seven rows and two buttons come out --
     which is how a button ended up sliced in half at the fold.

     A panel fixes both. fixed, not absolute, because it now covers the header
     that opened it rather than hanging beneath it; and with the page behind it
     locked (html.nav-locked, set by js/script.js) there is nothing left for it
     to detach from.

     NO HEIGHT, AND NOT FULL HEIGHT EITHER. This menu is seven rows and an
     account block. A panel stretched to the bottom of the screen would be
     mostly empty, which reads as a list that failed to load the rest of itself.
     It takes the height of its content and stops. max-height is only the guard
     for the case where that content does outgrow the screen -- svh, so a phone
     browser's toolbars are counted -- and then it scrolls inside itself. */
  nav.main-nav {
    position: fixed;
    top: 0;
    /* RIGHT, not left. The hamburger that opens this sits in the right of the
       header, so the panel now comes out from under the control that summoned
       it rather than from the opposite edge — and on a phone held one-handed
       the right edge is the side the thumb is already on.

       Not full width: the strip of dimmed page down the LEFT edge is what says
       the site is still there behind this and a tap gets you back to it. min()
       so it is a proportion on a small phone and a fixed panel on a big one,
       rather than a 340px card floating on a 320px screen. */
    right: 0;
    /* 80vw/300px, down from 86vw/340px. The panel was reading as a page rather
       than a menu on a real handset: seven short labels do not need 340px, and
       the wider it is the more of the site it hides for the sake of a list of
       one-word links. The narrower strip of page down the left also shows more
       of what you are coming back to. */
    width: min(80vw, 300px);
    max-height: 100svh;
    overflow-y: auto;
    /* Scrolling the panel to its end must not start scrolling the page behind
       it -- on a phone that reads as the menu sliding out from under the
       thumb. */
    overscroll-behavior: contain;
    display: none;
    flex-direction: column;
    /* No horizontal padding on the panel itself. Every row draws its own, so a
       row's tap target runs the full width of the panel instead of stopping
       short of an inherited gutter. */
    padding: 0 0 calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
    background: var(--drawer-bg);
    backdrop-filter: blur(var(--glass-blur));
    -webkit-backdrop-filter: blur(var(--glass-blur));
    /* Square. The left pair of corners was rounded to make the panel read as a
       sheet lying on the page, but the panel runs the full height of its
       content against the top of the screen, so the top-left curve cut a notch
       out of the head rather than softening an edge. The shadow already does
       the separating. */
    border-radius: 0;
    /* The shadow falls to the left, onto the page the panel is covering. */
    box-shadow: -18px 18px 50px -12px rgba(10, 14, 20, .45);
    /* Resolved inside .site-header's stacking context, against the scrim's 1.
       A large number here would buy nothing — see the scrim's note below. */
    z-index: 2;
  }
  nav.main-nav.open { display: flex; }
  /* The scrim. A pseudo-element on the element that already carries the open
     state, so there is no extra node in the markup and nothing for JavaScript
     to keep in step. Clicks on it reach the document, where the dismiss-on-
     outside-click handler already closes the drawer, so it needs no listener of
     its own. */
  /* The scrim, drawn by the HEADER rather than by <html>.
     .site-header is position:relative with z-index 200, which makes it a
     STACKING CONTEXT: the drawer's z-index is resolved inside it and cannot
     lift the panel past anything outside. A scrim on <html> therefore painted
     over the panel — the whole menu came out dimmed behind its own overlay —
     and raising the header above the scrim instead left the header's logo and
     hamburger as the one bright strip on a darkened page.

     Putting the scrim in the same context as the panel settles both. It is
     fixed, so it still covers the viewport; z-index 1 puts it over the header's
     own contents, which are in normal flow and so paint below it; and the panel
     at 2 sits over the scrim. Everything but the drawer is dimmed, including
     the header that opened it. */
  html.nav-locked .site-header::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 1;
    background: rgba(10, 14, 20, .5);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
  }

  /* Two X's side by side is one too many. The hamburger turns into a close
     cross when open, and the panel now carries its own — so while the drawer is
     up the header's is redundant AND sits just outside the panel's edge, which
     reads as a second, different close. It comes back the moment the drawer
     shuts, which is before js/script.js hands focus back to it. */
  html.nav-locked .nav-toggle { display: none; }

  /* -- The panel's own head ------------------------------------------------
     The crest and the association's name, then the close button. It repeats
     what is behind the scrim because the panel covers it: without this the
     sheet has no mark of its own and no way out but a tap on the dimmed strip,
     which is a gesture you either know or you do not. */
  .nav-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--line);
  }
  .nav-head-logo { height: 30px; width: auto; flex: none; }
  .nav-head-name {
    flex: 1;
    min-width: 0;
    font-family: var(--font-display);
    font-size: 0.78rem;
    font-weight: 700;
    line-height: 1.2;
    color: var(--text);
  }
  .nav-close {
    flex: none;
    /* 34px, still comfortably bigger than the 16px glyph inside it. Below the
       44px guideline, and deliberately: the panel has two other ways out — the
       dimmed page beside it and the hardware back gesture — so this is a
       convenience rather than the only exit, and at 40px it was the largest
       thing in the head and pulled the eye off the association's name. */
    width: 34px;
    height: 34px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: transparent;
    color: var(--text-2);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .nav-close svg { width: 16px; height: 16px; }
  .nav-close:active { background: var(--line-soft); }

  /* -- The call-out row ----------------------------------------------------
     The office's number, above the list. It is the one row here that does
     something rather than going somewhere, so it is set apart: tinted, and with
     its icon in a filled disc that no list row has. */
  .nav-callout {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 9px 12px;
    border-bottom: 1px solid var(--line);
    background: var(--line-soft);
    text-decoration: none;
    line-height: 1.25;
  }
  .nav-callout-ico svg { width: 14px; height: 14px; }
  .nav-callout-ico {
    flex: none;
    width: 25px;
    height: 25px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    /* --cyan, not --blue. In the light palette --blue resolves to the flag
       crimson (it is the primary-action token), which put a red disc and a red
       number on a row that is not the page's primary action — the two buttons
       at the foot of the panel are. --cyan is #003893 here, the flag's blue,
       which is the colour this site uses wherever something reads as
       institutional rather than as a call to action. */
    background: var(--cyan);
    color: #fff;
  }
  /* Both nowrap. flex:1 with a zero min-width let "Ask the office" break onto a
     second line even though the row has the width for it — the label is three
     short words and a wrapped one reads as a cramped row rather than a
     deliberate one. auto margin does the pushing-apart instead of flex-grow, so
     the label keeps its natural width and the number stays hard right. */
  .nav-callout-txt {
    flex: none;
    margin-right: auto;
    white-space: nowrap;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--text);
  }
  .nav-callout-num {
    flex: none;
    white-space: nowrap;
    font-size: 0.78rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--cyan);
  }

  /* -- The rows ------------------------------------------------------------ */
  nav.main-nav ul { flex-direction: column; align-items: stretch; gap: 0; width: 100%; }
  nav.main-nav ul li { width: 100%; border-bottom: 1px solid var(--line-soft); }
  nav.main-nav ul li:last-child { border-bottom: none; }
  /* The drawer is a light panel, so the white-on-blue treatment from the bar
     has to be undone here or the links vanish into it.

     Flex with a gap rather than a block, because each row is now an icon and a
     label. The 16px horizontal padding is the row's own, matching the head and
     the call-out above, so everything in the panel lines up on one left edge. */
  .header-bar nav.main-nav a:not(.btn),
  nav.main-nav a:not(.btn) {
    display: flex;
    align-items: center;
    gap: 12px;
    /* 10px/12px, down from 13px/16px, and the label down from --text-base
       (1rem) to 0.85rem. At the old size seven rows came to 364px of a 844px
       screen for seven one-word links, and each row read as a card. The row is
       still 40px tall, which clears the 44px guideline once the 1px separator
       and the thumb's own slop are counted, and nothing here is a control you
       hunt for -- the labels are the target. */
    padding: 10px 12px;
    font-size: 0.85rem;
    color: var(--text-2);
  }
  /* The mark, quieter than its label -- it is there to make a row findable at a
     glance, not to be read. */
  .nav-ico {
    flex: none;
    display: inline-flex;
    color: var(--text-3);
  }
  /* Sized here rather than in the icon() call in partials/header.php: the same
     markup feeds the desktop strip, and this keeps the drawer's scale a purely
     phone-side decision. */
  .nav-ico svg { display: block; width: 16px; height: 16px; }
  .header-bar nav.main-nav a:not(.btn):hover,
  .header-bar nav.main-nav a:not(.btn).active { color: var(--text); }
  /* The page you are on, marked on both halves of the row at once, which is
     what saves it needing a second treatment such as a fill or a bar. */
  nav.main-nav a:not(.btn).active { color: var(--blue); font-weight: 600; }
  nav.main-nav a:not(.btn).active .nav-ico { color: var(--blue); }
  nav.main-nav a:not(.btn)::after { display: none; }
  .nav-toggle { display: flex; }

  /* The member row, drawer-only (it is display:none at every wider width, where
     the header pill does the job). A real button rather than another text link:
     it is the one row here that is an account action instead of a page, and the
     border-top separates it from the seven navigation rows above without needing
     a heading to say so. */
  /* padding-top, not margin: the row keeps the border-bottom the li above it
     draws, so the gap reads as "the list ended and an action follows" rather
     than as a button that lost its place in the list. */
  /* Flex column with a gap, not a block. As a block the two full-width buttons
     stacked with nothing between them and fused into a single red-and-blue slab
     — no edge to tell you they are two targets, and a thumb aiming at the seam
     hits whichever one it lands on. The gap is what makes them read as a pair of
     buttons. `align-items: stretch` is the flex default, which is also what now
     gives them their full width; the explicit rule below stays for the logged-in
     case, where "My Account" is the only child. */
  .nav-member {
    display: flex;
    flex-direction: column;
    gap: 8px;
    border-bottom: none !important;
    padding: 12px 12px 0;
  }
  /* Sized down with the rest of the panel. .btn-sm's own 9px/18px is set for a
     pill sitting in a header beside other things; here they are two full-width
     slabs and the padding only makes them taller. */
  .nav-member .btn {
    width: 100%;
    padding: 10px 14px;
    font-size: 0.8rem;
  }
  .nav-member-out {
    display: block;
    /* The column gap above supplies the space this padding used to; keeping both
       double-spaced "Sign out" away from the button it belongs to. */
    padding: 0;
    font-size: var(--text-sm);
    color: var(--text-3);
    text-align: center;
  }

  /* The photo stays on phones now. It was hidden here when it was a panel
     stacked under the headline, pushing the buttons below the fold — as a
     full-bleed backdrop it costs no vertical space, and hiding it would strand
     white text on a white page. */

  /* Arrows come off on a phone: at this width they overlap the headline, and a
     swipe is the natural gesture anyway. The dashes still page through. With
     them gone the copy reclaims the inset that was reserving room for one. */
  .hero-arrow { display: none; }
  .hero-inner { padding-left: 0; }

  /* Required for the swipe in motion.js to fire at all, and the reason it did not
     work on a real phone at first. Under the default `touch-action: auto` the
     browser claims a horizontal drag as a pan of its own, and the moment it does
     it sends pointercancel instead of pointerup — so the gesture was always
     cancelled before it could be measured. `pan-y` hands vertical scrolling back
     to the browser and reserves the horizontal axis for us; `pinch-zoom` is listed
     explicitly because `pan-y` alone would also take zoom away, and being unable
     to magnify the one full-bleed photograph on the page is too high a price for
     a slideshow gesture. */
  .hero { touch-action: pan-y pinch-zoom; }

  /* ---- Touch targets -------------------------------------------------------
     A pointer lands where it is aimed; a fingertip covers about 8mm, which is
     why 44px is the floor on every mobile platform. Everything below was under
     it. Scoped to this breakpoint on purpose — the desktop sizes are the design
     and a mouse does not need the help.

     Two techniques, picked per control. Where the element's visible size IS the
     design (the hero dashes, the pager capsules) the target is grown with a
     transparent ::after and the graphic is left alone. Where the element is a
     control or a text row with slack around it, it gets real padding. Inflating
     a 3px dash to 44px of visible white would trade one defect for a worse one. */

  /* The pill in the drawer, the footer CTA, the "read more" links. 9px of padding
     put this at 35px; 13px lands it at 45. */
  .btn-sm { padding: 13px 20px; }

  /* Form fields come out at 43px from their own 13px padding — one pixel short,
     and worth closing because these are the controls a member actually has to
     hit, six file pickers among them on the application form. min-height rather
     than more padding so the text stays where it was vertically. */
  .contact-form input,
  .contact-form select,
  .contact-form textarea { min-height: 44px; }

  /* The footer's three link columns, and the phone/email rows beside them. These
     were 19px and 25px tall — the densest cluster of links on any page, stacked
     one under another, and the easiest place on the site to hit the wrong one.
     Padding on the anchor rather than margin on the li so the target grows
     instead of just the gap between targets. */
  .footer-grid ul a { display: inline-block; padding: 13px 0; }
  .footer-grid ul li { margin-bottom: 0; }
  .footer-contact li a { display: inline-block; padding: 10px 0; }
  /* Pulled back to the height the text alone had, like .contact-list above and
     for both of the same reasons. Two of these four rows are links and two are
     plain text, so the padding was showing up twice over: the phone and email
     marks hung in the air 10px above the line they label, while .fc-icon's 2px
     nudge — measured against a bare text row — kept the address and the hours
     correct; and the four rows sat at three different spacings. */
  .footer-contact li a { margin-block: -10px; }

  /* "Home / Contact" under the page title. 16px tall, and the one link on an
     inner page that sits alone with room around it. */
  .breadcrumb a { display: inline-block; padding: 14px 0; }

  /* The remaining bare text links: the email and phone in the contact panel and
     "Forgotten your password?" on the sign-in form. Both were 19-21px, both are
     things someone taps rather than reads, and one starts a mail or dial app —
     an accidental hit is more annoying than most. Padding on the anchor, so the
     row grows with it and the list keeps even spacing. */
  .contact-list a,
  .member-auth-links a { display: inline-block; padding: 12px 0; }

  /* The padding above grows the target; this pulls the box back to the height
     the text alone had, so the target bleeds into the gap instead of adding to
     it. Without it the contact panel's four rows came out ragged: Email and
     Phone carry a link and gained 24px each, Address and Office Hours carry a
     <p> and gained nothing, so the same label/value pair sat at two different
     spacings down one short list. The bleed is 12px a side against a 24px gap,
     so no two targets touch. */
  .contact-list a { margin-block: -12px; }

  /* The dash is 3px tall because that is what holds its shape over a photograph,
     so the thing you press is not the thing you see. Slightly longer bars too:
     with the arrows hidden at this width these are the only visible control, and
     at 26px they read as a rule rather than something to press. */
  .hero-dot { width: 32px; height: 4px; position: relative; }
  .hero-dot.is-on { width: 52px; }
  .hero-dot::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: max(100%, 44px);
    height: 44px;
  }

  /* Same treatment for the news row's pager. The dots sit under a card with the
     section's padding below them, so the overlay's bleed lands on dead space and
     cannot swallow a tap meant for something else. */
  .news-pager button { position: relative; }
  .news-pager button::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    /* Centred and sized in absolute terms rather than as an inset, so the target
       is 44px whatever the capsule's own width happens to be — an inset of -6px
       assumed a 32px button and came out 2px short. */
    width: max(100%, 44px);
    height: 44px;
  }

  /* Round icon buttons: real size, since 44px is also simply better proportioned
     against the 56px footer logo above them. Three plus gaps is 156px, which fits
     the 324px of content a 360px screen leaves. */
  .footer-social a { width: 44px; height: 44px; }

  /* Vertical padding only. The separator dots are positioned into the flex gap
     between list items, so horizontal padding would slide the text out from
     under them. 15px each side takes a 15px text row to 45. */
  .footer-nav a { display: inline-block; padding: 15px 0; }

  /* iOS Safari zooms the viewport when a field under 16px takes focus, and then
     leaves the page scrolled sideways with no way back. 1rem exactly rather than
     --text-sm: raising the whole step to 16px would inflate every card paragraph
     along with it, and this only needs to be true of the inputs. */
  .contact-form input,
  .contact-form select,
  .contact-form textarea { font-size: 1rem; }

  .grid-2, .grid-3 { grid-template-columns: 1fr; }
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  /* .gallery-grid is flex now, so it is sized by the item basis set in the
     1080px block above, not by a column count here. */
  .form-row { grid-template-columns: minmax(0, 1fr); }
  .carousel-track { grid-auto-columns: 82vw; }
  /* A band on a phone too, for the same reason the tablet range above is capped.
     Filling the screen below the header (100svh - --header-h) gave a ~770px hero
     on a 390x844 handset carrying maybe 400px of headline, buttons and standfirst
     — the copy floated in the middle of a tall crop of a landscape photograph,
     and nothing below the hero was ever on screen when the page opened, so the
     site read as a single picture with no content under it.

     clamp rather than a fixed height, so it stays proportional across the range
     of handset sizes: 66svh is ~557px on a 844-tall phone, the 560px ceiling
     holds a taller handset to the same band, and the 400px floor keeps a phone
     held sideways from crushing the headline into the buttons. The photograph is
     still the first thing on the page and still the largest — it just stops
     being the only thing. */
  .hero {
    /* Was clamp(400px, 66svh, 560px) — about 557px on a 844px phone, two thirds
       of the screen before anything below it existed. Still the first thing on
       the page and still the largest; it just stops being the whole of it, and
       the section under it now shows at the bottom edge instead of being a
       scroll away.

       The padding comes down with it and that is the half that does the work:
       min-height is a floor, not a ceiling — the headline, the paragraph, two
       stacked buttons and the dots add up to roughly 360px on their own, so at
       --space-9 the band could never have gone below 490 whatever the clamp
       said. --space-7 is what lets the floor actually be the height. */
    min-height: clamp(340px, 52svh, 440px);
    padding: var(--space-7) 0;
  }
  .hero h2 { max-width: none; }

  /* THE HERO'S CONTENT, SCALED AS ONE THING.
     This was three separate rules a moment ago — a smaller clamp on the
     headline, --text-base on the paragraph, tighter margins on both — and it
     was the wrong method. Shrinking elements one at a time re-proportions the
     block: the type came down, the buttons did not, and two full-width pills
     that had looked right under a 44px headline suddenly looked enormous under
     a 32px one. Every element left untouched becomes wrong relative to the ones
     that moved, and you find them one screenshot at a time.

     zoom scales the whole composition instead. Headline, paragraph, both
     buttons, the gaps between them and the dot row all come down by the same
     factor, so the hero at 390px is the design that was drawn for a laptop,
     smaller — which is what it should have been from the start. Everything
     stays in the proportion somebody chose deliberately.

     0.82 rather than a round number: it is what brings the band to about 400px
     on a 844px phone while leaving the headline comfortably readable.

     zoom, not transform: scale(). A transform paints smaller but reserves the
     original space, so the hero would keep the height of the thing it no longer
     draws. zoom reflows, which is the entire point here. It is in the CSS
     Viewport spec and every current browser has it; the type is set in rem
     throughout, so a reader who has raised their default still gets it raised,
     multiplied by this rather than overridden by it. */
  .hero-inner { zoom: 0.82; }

  .spec-row { padding: var(--space-5) var(--space-2); gap: var(--space-3); }
  .spec-row:hover { padding-left: var(--space-3); }
}

@media (max-width: 600px) {
  /* Type floor for phones. The small end of the scale was tuned for a desktop
     card in a 300px column; at arm's length on a handset 10.88px tracked
     uppercase is a texture, not a word. Raised as tokens rather than per-rule
     because ~85 declarations point at these steps and every one wants the same
     correction. The top of the scale is deliberately untouched: --text-base and
     up already read well, and the clamp()s are fluid by design. */
  :root {
    --text-micro:   0.75rem;     /* was .68 → 10.88px; now 12px */
    --text-caption: 0.8125rem;   /* was .75 → 12px;    now 13px */
    --text-xs:      0.875rem;    /* was .8  → 12.8px;  now 14px */
    --text-sm:      0.9375rem;   /* was .9  → 14.4px;  now 15px */
  }

  .container { padding: 0 18px; }

  /* The rule that used to sit here styled `.header-inner`, which is not in the
     markup — the element is .header-top-inner. It was harmless on its own, but
     it is where the drawer's `top: 64px` came from, and that one was not: the
     real top tier is 76px, so the drawer opened 12px into the logo row. Both are
     gone. `top` is left to the 760px block, which sets top:100% — the drawer
     hangs off the bottom of .site-header and so cannot be wrong about its
     height, whatever that height later becomes. */
  nav.main-nav { padding-left: 18px; padding-right: 18px; }
  .section { padding: var(--space-8) 0; }
  .section-head { margin-bottom: var(--space-7); }
  .card, .statement, .path-card, .credential, .quote { padding: var(--space-5); }
  .card-photo {
    width: calc(100% + 2 * var(--space-5));
    margin: calc(-1 * var(--space-5)) calc(-1 * var(--space-5)) var(--space-5);
  }
  /* Side by side at their own width, not two stacked full-width slabs.
     A button as wide as the paragraph above it stops reading as a button — it
     reads as a bar, and two of them one under the other read as a menu. They
     also carry short labels, so most of that width was empty: "Learn More" in
     the middle of 350px of crimson is mostly crimson.

     They fit easily on one row here. .hero-inner is scaled to 0.82 (see above),
     which means the row has about 475px of layout width to divide at a 390px
     screen, against roughly 350px of button. flex-wrap on the base rule is the
     safety net if a longer label is ever set from the admin: they stack again
     rather than overflow, and stacking is only the fallback, not the plan. */
  .hero-actions { align-items: center; }
  .event-card { grid-template-columns: 72px minmax(0, 1fr); padding: var(--space-5); }
  .date-badge { height: 72px; }
  .date-badge strong { font-size: 1.4rem; }
  /* THE TITLE BAND STOPS BEING A BAND.
     Trimming its padding was treating the symptom. The real cost was that it is
     a BAND at all: its own background, its own bottom border, its content
     centred — the full apparatus of a page banner, wrapped around one or two
     words. Under a header that is already a full-width bar with a logo and a
     menu button, that made two stacked pieces of furniture before any content,
     and the second one was carrying almost nothing.

     So on a phone it dissolves. No background and no border: the title becomes
     an ordinary heading sitting directly above the first card, at a size that
     reads as a heading rather than a banner. It still says where you are, still
     is the page's <h2>, and costs about a third of what the band did.

     Still centred, as it is on every other width. Left-aligning it was tried
     and is the wrong instinct here: this title is the page announcing itself,
     not a section label introducing the list under it, and the centre line is
     what the rest of the site uses to say so.

     The breadcrumb goes with it. On this site it is always exactly
     "HOME / <the title directly beneath it>", so its whole content on a phone
     is one redundant word and a Home link — and the tab bar now carries Home
     from every page. Both stay on a laptop, where the band is not competing for
     room and a breadcrumb is a convention readers look for.

     Listed with .page-committee too: that page trims the same band on its own
     (see ~line 3480) at a specificity this would otherwise lose to, and the
     point here is that every inner page arrives at one treatment. */
  .page-header,
  .page-committee .page-header {
    padding: var(--space-5) 0 0;
    background: none;
    border-bottom: 0;
  }
  .page-header h2,
  .page-committee .page-header h2 {
    /* 0.42 of --text-3xl, against the 0.6 the band uses on a laptop. Written as
       a multiple like the base rule so it keeps tracking the type scale. */
    font-size: calc(var(--text-3xl) * 0.42);
    margin-bottom: 0;
  }
  .page-header .breadcrumb { display: none; }
  /* The gap under the title is now this section's own top padding, and --space-9
     was sized for a band that closed itself with a border. */
  .page-header + .section { padding-top: var(--space-5); }

  .footer-grid { grid-template-columns: 1fr; gap: var(--space-6); }
  .footer-bottom { flex-direction: column; align-items: flex-start; }
  .masonry { column-count: 1; }
  /* Held at two across, like the news tiles and the committee cards. This used
     to drop to one here on the grounds that a 167x125 thumbnail is too small to
     make out a stage in — which is true of the thumbnail and beside the point
     for the grid, because every one of these opens full-screen in the lightbox
     on tap. One-up spent a whole phone width per photo to make a contact sheet
     you could only see six of at a time. */
  .gallery-grid > .gallery-item {
    flex-basis: calc((100% - var(--space-5)) / 2);
    max-width: calc((100% - var(--space-5)) / 2);
  }
  .timeline { padding-left: var(--space-6); }
  .timeline-item::before { left: calc(-1 * var(--space-6) + 1px); }
}

/* Phones run 1-2-2: the lead card across the full width, everyone else in a grid
 * of portrait tiles two to a line.
 *
 * The two treatments are genuinely different cards, not one card at two sizes,
 * because half a phone — ~170px — will not carry the panel. So:
 *
 * - The LEAD card keeps the full-width treatment. Its photo takes a 4:5 frame
 *   rather than the card's 3/4 — closer to what a phone camera takes, so a
 *   portrait is not cropped through the top of the head — and the card itself is
 *   a one-cell grid with photo and panel stacked in that cell. Every panel is
 *   open here (there is no hover to gate the reveal on), and a name plus two
 *   chips plus a bio is routinely taller than a fixed-ratio card leaves room
 *   for, so overflow: hidden used to slice the last line off. Stacked, the cell
 *   is as tall as the taller of the two and the card grows to fit the text.
 *
 * - The TILES stay as they are on desktop: fixed 3/4 frame, photo behind, name
 *   over the scrim. The chips and bio are hidden rather than shrunk. That is the
 *   width this page was one-across for originally: at two-up the panel is the
 *   ~150px that broke the name over three lines, split "SECRETARY" mid-word and
 *   left the chips no room for a company name. Dropping the panel's text is what
 *   makes two-up work — the tile carries a face and a name, and tapping opens
 *   the modal with the rest, which is the same gesture as everywhere else here.
 */
@media (max-width: 600px) {
  /* Two across. The row centres, so a short last line sits under the middle of
     the one above it rather than hanging off the left edge. */
  .committee-grid { --cc-w: calc((100% - var(--space-4)) / 2); gap: var(--space-4); }
  .committee-row  { gap: var(--space-4); max-width: 100%; }

  /* Full width, not 1.4 × a third. The lead card is the one thing on this page
     that is allowed to be as wide as the screen. */
  .committee-card.is-lead {
    width: 100%;
    display: grid;
    /* minmax(0, 1fr), not the implicit auto column: an auto track is sized by
       its items' content, and the panel's chips and bio pushed it wider than the
       card, so the text ran out under the rounded edge. */
    grid-template-columns: minmax(0, 1fr);
    /* The ratio moves to the photo. On the card it would cap the height, which
       is the clipping. */
    aspect-ratio: auto;
  }
  /* Photo and panel share one cell; the scrim (::after) stays absolute over the
     whole card, so it still covers whatever height the card ends up at. */
  .is-lead .cc-photo,
  .is-lead .cc-panel { grid-area: 1 / 1; }

  .is-lead .cc-photo {
    position: relative;
    inset: auto;
    align-self: stretch;
    aspect-ratio: 4 / 5;
  }

  /* Heads sit above the middle of a portrait far more often than below it, and a
     4:5 crop of a wider group shot takes its slice from the centre — 30% keeps
     the face in frame in both cases instead of cropping through it. Kept on
     every card: the tiles are portrait too, and more so. */
  .cc-photo img { object-position: center 30%; }

  .is-lead .cc-panel {
    align-self: end;
    position: relative;
    padding: var(--space-4);
  }

  /* Room for it, so the name goes back up a step. Centred like every other card
     here — the lead card is a portrait with a name under it, and ranging that
     one name left broke the alignment it shares with the tiles below. The chips
     under it stay ranged left; they are read as label/value pairs. */
  .is-lead .cc-name { font-size: 1.15rem; }

  /* Label column and value column both kept, exactly as on desktop — a full
     phone width is enough for "Organization" and a company name side by side.
     Only the sizes go up: at 0.68rem the chips were set for a 200px card. */
  .is-lead .cc-facts { margin-top: 8px; gap: 5px; }
  /* 0.8rem, up from 0.74: these are literal rem values rather than scale steps,
     so the type floor raised at the top of this block does not reach them, and
     11.84px was still under it. The lead card has its aspect-ratio moved to the
     photo, so it grows to fit the taller text. */
  .is-lead .cc-facts dt,
  .is-lead .cc-facts dd {
    padding: 6px 9px;
    font-size: 0.8rem;
  }

  /* Two lines instead of three: the bio is the least important thing on the card
     and it was the tallest. The full text is still in the modal on tap. */
  .is-lead .cc-bio {
    font-size: 0.8rem;
    -webkit-line-clamp: 2;
  }

  /* The tiles. Name only, sized to a card half a phone wide, with room to break
     over two lines — most of these are three words. */
  .committee-card:not(.is-lead) .cc-panel { padding: var(--space-3); }
  .committee-card:not(.is-lead) .cc-name {
    font-size: 0.95rem;
    line-height: 1.25;
  }
  /* The tiles carry the same organisation / role / bio panel as the lead card,
     and open it the same way — .is-near, once the whole tile is on screen.

     They used to have display: none on both, which was right while the panel was
     pinned OPEN at this width: everything in flow at once pushed the name off a
     tile half a phone wide. Now that the panel is closed at rest and unfolds only
     on the tile you have scrolled to, that reason is gone, and its absence was
     conspicuous — the president animated, the ten cards under them sat still.

     It is the same panel as everywhere else, only smaller: label chip beside
     value chip, in the two columns the desktop card already uses. Stacking the
     label over its value was tried first and it is the wrong shape — it doubles
     the row count, and four full-width bands plus a bio buried the portrait the
     tile exists to show.

     The bio is the one part the tiles do not get. It is the longest text on the
     card and the least specific, and on a tile it costs more of the photograph
     than the two facts together. The paragraph stays in the markup, so the tap
     that opens the modal still brings the whole thing with it. */
  .committee-card:not(.is-lead) .cc-facts {
    margin-top: 6px;
    gap: 2px;
  }
  /* Small enough that "Organization" leaves the value column something to work
     with. The label is the wider of the two and it is a fixed word, so trimming
     its padding buys characters for the company name on every card at once. */
  .committee-card:not(.is-lead) .cc-facts dt,
  .committee-card:not(.is-lead) .cc-facts dd {
    padding: 3px 5px;
    font-size: 0.56rem;
    line-height: 1.25;
    border-radius: 2px;
  }
  .committee-card:not(.is-lead) .cc-bio { display: none; }

  /* "optional" was 0.78em of a label that is itself --text-micro, and a fraction
     of a fraction bottomed out at 8.5px — the smallest text on the site by a wide
     margin, on the one page where a visitor is reading carefully. It does not need
     to be smaller than the label to read as quieter: it is lowercase and untracked
     against uppercase and tracked, which already carries the hierarchy. */
  .opt { font-size: 1em; letter-spacing: 0; }

  /* A 360px column is a different measure from the desktop card these were set
     in: an email address in a member card, a district in a chip, or a filename in
     a file row all exceed it, and body{overflow-x:hidden} would silently clip them
     rather than scroll. break-word only breaks a word with no better option, so
     ordinary prose is untouched. Inherited, so it reaches everything from here.
     No hyphens: they need per-language dictionaries and this page mixes English
     with transliterated Nepali, where the browser guesses wrong. */
  body { overflow-wrap: break-word; }

  /* 0.96 is set for a two- or three-line desktop headline. At four or five lines
     on a phone the descenders of one line touch the caps of the next. The size is
     deliberate and stays — only the leading opens up. */
  .hero h2 { line-height: 1.04; }

  /* ---- Contact panel -------------------------------------------------------
     Set for a column beside a form, not for a phone. At 390px the 48px inset
     took a quarter of the card's width and the panel came out 551px tall — a
     blue slab that filled the screen before a visitor reached the form it is
     supposed to introduce. The inset drops a step and the heading's 48px rule
     halves; the four rows themselves are untouched, so what shrinks is the
     empty space, not the contact details. */
  .contact-card { padding: var(--space-6); }
  .contact-card h3 { margin-bottom: var(--space-6); }

  /* Two by two, and the row itself turns on its side: disc on top, label under
     it, value under that. Four full-width rows read as a list you scroll past
     to reach the form — the same four facts in a square block are taken in at a
     glance, and the panel gives back another ~120px on top of the padding it
     already lost. There is room for it because the values are all short: the
     longest, the office hours, is the one that wraps, and it wraps cleanly at
     the comma. */
  .contact-list {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-6) var(--space-4);
  }
  .contact-list li {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
  }

  /* A step down to the phone type floor, which is what buys the email address
     its one line: at --text-sm's 15px it needed ~155px of a ~145px tile and
     broke after "info@avlnepal.org.n", leaving a lone "p" on the second line.
     The labels stay at 15px, so the tile keeps its hierarchy. Nothing goes
     below 13px — see the floor at the top of this block.

     anywhere as well as the smaller step, because an address is a single
     unbreakable token: a longer one than this office's would otherwise run out
     of the tile rather than wrap in it. */
  .contact-list a,
  .contact-list p {
    font-size: var(--text-caption);
    overflow-wrap: anywhere;
  }

  /* The icon disc is a 40px circle against a ~24px label — the largest thing on
     a tile it only annotates. 34px keeps it clearly a disc and gives the
     address lines back the width. */
  .cc-icon { width: 34px; height: 34px; }

  /* ---- Committee strip ----------------------------------------------------
     Eleven cards, one per row, is the longest thing on this page by a wide
     margin — over half its height. Nothing below is about the count, which is
     the association's and not a layout decision; it is about making one card
     cost less.

     Centring is inherited from the base rule now, which centres for the same
     reason. Only the spacing changes here. */
  .team-card {
    gap: var(--space-4);
    padding: var(--space-3) var(--space-4);
  }

  /* Round, and down from 96px. A circle reads as a person where the rounded
     square read as a tile — eleven dark squares down a phone screen was the
     heaviest thing on the page, and the square is doing no work here that the
     card's own corners are not already doing. Still 60 rather than tracking the
     desktop 76: a phone card is the full page width, and the circle is the only
     thing holding the left of it. */
  .team-photo {
    width: 60px;
    height: 60px;
    border-radius: 50%;
  }
}

@media (max-width: 440px) {
  .grid-4 { grid-template-columns: 1fr; }
  /* Stats stay two-up rather than dropping to one: a short number and a short
     label do not need a full phone width each. */
  .stats-grid { grid-template-columns: repeat(2, 1fr); }
  .member-grid > .member-card { flex-basis: 100%; max-width: 100%; }
  /* The committee card is handled at 600px, where it goes one across and moves
     its aspect ratio onto the photo. */
  .spec-row { grid-template-columns: 1fr; gap: var(--space-2); }
  .spec-row p { grid-column: 1; }
  form.contact-form .btn { width: 100%; }
}

/* The floor under the contact panel's 2×2 tiles, set by the one value that
   cannot be wrapped: an email address. A tile is half the panel's inner width,
   which is 137px at 381 and 106px at 320 — and "info@avlnepal.org.np" is 123px
   at the 13px the tiles use, so somewhere in between the two-up stops being a
   layout and starts being a hyphenation problem. The number is the tile the
   address stops fitting in, not a device.
     Below it the panel goes back to what it is above 600px: four full-width
   rows, mark in a gutter to the left of the words, at the full --text-sm. A
   longer address than this office's own gets the same treatment automatically,
   because it wraps rather than being cut. */
@media (max-width: 380px) {
  .contact-list { grid-template-columns: 1fr; gap: var(--space-5); }
  .contact-list li {
    flex-direction: row;
    align-items: flex-start;
    gap: var(--space-4);
  }
  .contact-list a,
  .contact-list p { font-size: var(--text-sm); }

  /* A tile is 132px square here, and three clamped lines plus the date plus the
     arrow's reserve add up to the whole of it — the headline stopped sitting on
     the photograph and started replacing it. Two lines gives the picture the top
     third of the tile back. The tiles stay two across: the headline is what does
     not fit at this width, not the grid. */
  .tile h3 { -webkit-line-clamp: 2; }
}

/* ── 21 · Reduced motion & print ────────────────────────────────────────── */

/* This design carries a lot of motion, so the opt-out has to be total rather
   than a token gesture: no beams, no meters, no marquee, no reveal, every
   element renders in its final state on load. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  [data-reveal] { opacity: 1 !important; transform: none !important; }
  .marquee-track { animation: none; transform: none; }
  .marquee { mask-image: none; -webkit-mask-image: none; }
  .btn::after { display: none; }

  /* The hero photos stop auto-advancing in JS, so the fade has nothing left
     to time. The dots still work: a deliberate click may switch instantly. */
  .hero-slider-stage img { transition: none; }
  .hero-dot { transition: none; }
}

/* The hero lift needs vertical slack to borrow from. On a short viewport — a
   1440x800 laptop is the common one — the flex centring has already run out,
   and lifting further slides the headline under the fixed 84px header. Drop it
   and let the composition sit centred. */
@media (max-height: 900px) {
  .hero .container { transform: none; }
}

/* Without JS there is no IntersectionObserver to add .in, so revealed
   content would stay invisible forever. Undo the initial state. */
.no-js [data-reveal] { opacity: 1; transform: none; }

/* Same reasoning for the committee panels: on a touch device the scroll opens
   them, and with no JS nothing ever would. Open on every card, as they were
   before the scroll took over the job. */
.no-js .cc-more { grid-template-rows: 1fr; opacity: 1; }
.no-js .cc-more-inner { transform: none; }

@media print {
  body { background: #fff; color: #000; }
  .site-header, .footer-mark, .marquee { display: none !important; }
  * { color: #000 !important; background: transparent !important; }
}

/* ── Certificate check (verify.php) ──────────────────────────────────────────
   The result table on the public certificate verifier. A definition list rather
   than a table: it is a set of label/value pairs about one thing, not a grid,
   and a <dl> keeps that relationship for a screen reader without any ARIA.

   The label column is a fixed width rather than a fraction, so "Membership
   number" and "Name" put their values on the same left edge — ragged value
   starts made the six rows read as six separate facts instead of one record. */
.cert-check {
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--glass-bg);
  padding: var(--space-5) var(--space-6);
}
.cert-check > div {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--line);
}
.cert-check > div:last-child { border-bottom: 0; }
.cert-check dt {
  margin: 0;
  font-size: var(--text-caption);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-2);
}
.cert-check dd {
  margin: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* Stacked on a phone: a 180px label column beside a value leaves about half a
   phone width for a name, and "Membership number" wrapped to three lines to buy
   space the value then could not use. */
@media (max-width: 600px) {
  .cert-check { padding: var(--space-4) var(--space-5); }
  .cert-check > div { grid-template-columns: 1fr; gap: var(--space-1); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE PHONE TAB BAR
   Markup in partials/footer.php. ALWAYS ON, at every scroll position.

   It used to be driven by scroll direction — reading down brought it up,
   heading back up took it away. The reasoning was that a bar is chrome sitting
   on the content you are reaching for. In use it was the wrong trade: a
   navigation you have to produce by scrolling the right way is one you cannot
   rely on being there, every appearance re-covered the content it had just
   uncovered, and the sliding was what put it in the path of the back-to-top
   button. A phone's bottom bar is furniture. Furniture does not move.

   What that decision costs is stated plainly: the bar covers --tabbar-h of the
   viewport at all times. body reserves exactly that much at the bottom, so it
   never covers CONTENT — only the last of the scroll runway.

   Hidden outright above 760px. A laptop has the whole main nav on screen at
   all times; a bar of five repeats pinned over it would be noise with a
   drop-shadow. This is a phone affordance and it is scoped to phones.
   ═══════════════════════════════════════════════════════════════════════════ */
.tabbar { display: none; }

/* The phone drawer's furniture: its head, its close button, its call-out row
   and the icon beside each label. All of it exists only inside the panel at
   760px and under — in the desktop strip the nav is a horizontal row of seven
   words, and a crest, a phone number and seven icons would have nowhere to go.

   Hidden in a min-width query rather than unconditionally-then-re-shown. This
   rule sits LATER in the file than the phone block that styles these, so an
   unconditional `display: none` of equal specificity won on the phone too and
   the head and the icons never appeared. Scoping it to the widths that actually
   want them hidden removes the source-order question entirely. */
@media (min-width: 761px) {
  /* Selectors specific enough to win. `.nav-callout` alone is one class, and
     `nav.main-nav a:not(.btn)` — which sets display: inline-block for the
     desktop strip — scores (0,2,2), because :not(.btn) contributes its argument's
     class. So the plain version lost and the call-out appeared as a stray
     phone-number row in the blue nav bar.

     a.nav-callout matches that (0,2,2) and sits later in the file, which is what
     settles the tie. The head and the icon are a div and a span, so the link
     rule never touched them and one class is enough. */
  nav.main-nav a.nav-callout { display: none; }
  nav.main-nav .nav-head,
  nav.main-nav .nav-ico { display: none; }
}

/* ── NO FOOTER ON A PHONE ─────────────────────────────────────────────────────
   The footer is a desktop structure: four columns of links that exist because a
   wide screen has room beside the content for them. Stacked into one phone
   column it became roughly two full screens of scrolling — six association
   links, four contact rows, a call to action, a copyright line and the same six
   links again — sitting under every page on the site, in front of nothing. The
   trims that came before this (hiding the tagline, the socials, the duplicate
   link row) each made it shorter and none of them made it worth arriving at.

   The tab bar is what replaced it, and it is the better trade: the four places
   the footer was mostly linking to are now one tap away from anywhere on the
   page, instead of a scroll to the bottom away.

   WHAT A PHONE LOSES, stated plainly rather than discovered later:
     - the office address, hours, phone and email. Phone and email are already
       tap-targets in the header strip, and the address is on contact.php, which
       the tab bar links to directly.
     - the copyright line.
     - the Admin link. Staff reach the panel at /admin/ or from a laptop; it was
       never a route a visitor used.

   Hidden in CSS rather than skipped in partials/footer.php: the markup is one
   partial shared with every page, the decision is purely about width, and a
   crawler or a reader-mode view still gets the whole thing. */
@media (max-width: 760px) {
  .site-footer { display: none; }
}

@media (max-width: 760px) {
  /* THE BAR'S REAL HEIGHT, IN ONE PLACE.
     Two rules need it — the page's bottom padding and the back-to-top button —
     and both used to carry the literal 64px. That number was wrong on an
     iPhone and the two bugs it caused looked unrelated: the button sat ON the
     bar, and the last of the page hid behind it.

     56px is .tab's min-height (border-box, so it already includes that rule's
     7px padding), 1px is the border-top, and env() is the home indicator the
     bar pads itself clear of. Zero on anything without an inset, which is the
     old 57px everywhere else.

     js/script.js overwrites this with the bar's measured offsetHeight on load
     and on resize, so a future change to .tab's padding or font cannot put the
     arithmetic here out of step with the bar. This value is the fallback for
     the first paint and for JS being off. */
  :root { --tabbar-h: calc(57px + env(safe-area-inset-bottom, 0px)); }

  .tabbar {
    display: grid;
    /* minmax(0, 1fr), NOT 1fr. A bare 1fr is minmax(auto, 1fr), and that auto
       is a min-content floor: the column refuses to go narrower than the widest
       unbreakable thing in it. Five columns floored by "Our Works" and "Contact"
       added up to more than a 390px phone, so the grid quietly overflowed to the
       right and pushed Join off the edge of the screen — four tabs where the
       markup plainly had five, with nothing in the DOM to show for it. Zeroing
       the floor lets the columns divide the width they actually have. */
    grid-template-columns: repeat(5, minmax(0, 1fr));
    position: fixed;
    z-index: 900;   /* Under the nav drawer, which must cover it when open. */
    left: 0;
    right: 0;
    bottom: 0;
    /* Hardcoded white rather than a token: this site is light-only by design
       (see the note at the top of this file, and the data-theme="light" that
       partials/header.php stamps on <html>), and the bar needs the alpha for
       the blur behind it, which a solid token cannot carry. */
    background: rgba(255, 255, 255, .96);
    backdrop-filter: saturate(140%) blur(10px);
    border-top: 1px solid var(--line);
    /* The shadow points UP, away from the edge it is stuck to — the light in
       this design comes from above, so a bar at the bottom of the screen casts
       onto the content it overlaps, not off the bottom of the phone. */
    box-shadow: 0 -6px 22px -14px rgba(17, 24, 39, .5);
    /* Clear of the iPhone home indicator. Zero everywhere else. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  /* No transform and no transition. The bar was parked at translateY(110%) and
     slid in on a class; it now simply sits at bottom: 0, which is also what
     makes its position something a phone browser cannot get halfway through. */

  /* The page has to end above the bar — the footer that used to absorb this is
     hidden on phones, so the last section of real content is what the bar would
     otherwise cover.

     This reserve was once gated on the class that showed the bar, so the
     document's scrollHeight grew and shrank by 64px on every change of scroll
     direction. At the bottom of a page that is the browser being told the page
     just got longer while a thumb is mid-drag, and iOS lets you carry on into
     the new blank space under the bar — "I can scroll below the nav". With the
     bar permanent the reserve is permanent too, the page is one length, and the
     bar sits exactly over it. */
  body { padding-bottom: var(--tabbar-h); }

  .tab {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    /* 56px + the label. Apple asks 44, Material 48; this is a thumb reaching
       the bottom of a phone one-handed, which is the least accurate tap there
       is. */
    min-height: 56px;
    padding: 7px 2px;
    color: var(--text-2);
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
  }
  .tab-ico { display: flex; line-height: 0; }
  .tab-ico svg { display: block; }
  .tab-label {
    /* Centred and allowed to shrink, so a two-word label cannot be what decides
       the width of the bar. */
    text-align: center;
    font-size: .68rem;
    font-weight: 500;
    letter-spacing: .01em;
    line-height: 1;
  }

  /* Where you are, marked in the same flag blue the main nav uses for it. */
  .tab.is-here { color: var(--cyan); }
  .tab.is-here .tab-label { font-weight: 600; }

  /* Join is an action, not a fifth place. Crimson mark and label rather than a
     filled pill: a solid block in a row of four outlines would tilt the whole
     bar toward one corner, and the colour alone is enough to separate a verb
     from four nouns. */
  .tab-cta { color: var(--crimson, #ce1126); }
  .tab-cta .tab-label { font-weight: 700; }

  .tab:active { background: rgba(17, 24, 39, .05); }

  /* The back-to-top button, parked permanently above the bar.

     Unconditional — no .tabbar-on in front of it. Every version of this that
     landed the button ON the bar was a version where one of the two could move:
     first the offset was a wrong constant, then it was right but had to be
     right about a bar that was mid-slide. With the bar fixed at the bottom at
     all times, --tabbar-h is a standing measurement rather than a guess about
     an animation, and one rule holds at every scroll position.

     js/script.js keeps --tabbar-h equal to the bar's real offsetHeight, so this
     clears it by --space-4 whatever the safe-area inset or the tab padding
     turns out to be. */
  .to-top { bottom: calc(var(--tabbar-h) + var(--space-4)); }
}


