/* ── 01-tokens.css ── */
/* ============================================================
   Design tokens — the single place the palette, radii, shadows,
   fonts and glass recipes are decided.
   Concatenated first by scripts/build.js, so every later file in
   src/assets/css/ may read these through var(); nothing here
   selects anything.
   Couplings: 01a-theme.css repaints the colour roles per page
   theme; the glass recipes are consumed only by 01b-glass.css,
   whose translucent surfaces 15-fallbacks.css degrades to opaque.
   ============================================================ */
:root{
  /* ---------- page colours ---------- */
  --bg:#F6F9F7;
  --paper:#FFFFFF;
  --ink:#0C261D;
  --muted:#40584F;
  --faint:#6B8178;
  /* ---------- green ramp ---------- */
  --g900:#0E3B2E;
  --g700:#14532D;
  --g600:#166534;
  --g500:#1F8A4C;
  --g300:#7FD1A2;
  --mint:#DCF2E4;
  --mint-soft:#EAF6EE;
  --line:#DCE7E0;
  /* ---------- frosted-glass recipes ---------- */
  /* Applied by 01b-glass.css to .glass-card/.glass-strip/.glass-wash;
     15-fallbacks.css degrades the same surfaces. */
  --glass-bg:rgba(255,255,255,.55);
  --glass-line:rgba(255,255,255,.7);
  --glass-blur:blur(10px) saturate(1.15);
  --wash-bg:linear-gradient(180deg,rgba(246,249,247,.45),rgba(234,246,238,.5) 55%,rgba(246,249,247,.45));
  --wash-blur:blur(14px) saturate(1.2);

  /* ---------- status colours ---------- */
  --pos:#F2D024;
  --pos-deep:#D9B60A;
  --neg:#EE5C5C;
  --neg-deep:#D94545;
  /* ---------- wave cap, radius, shadow, fonts ---------- */
  /* one height for the wave caps (07-waves.css), the offset they cost the next
     band (13-join.css) and the photo masks (11a-news.css, 17c-dashboard.css) */
  --wave-h:70px;
  --radius:16px;
  --shadow:0 24px 48px -24px rgba(12,38,29,.16);
  --shadow-soft:0 10px 30px -12px rgba(12,38,29,.12);
  --font-display:'Sora',system-ui,-apple-system,'Segoe UI',sans-serif;
  --font-body:'Inter',system-ui,-apple-system,'Segoe UI',sans-serif;
}


/* ── 01a-theme.css ── */
/* ============================================================
   Page themes — the token sets that repaint a page for the platform
   it is about, selected by <body data-theme>, which src/layout.html
   emits from the page's front matter `theme:` (no key = green).
   Concatenated right after 01-tokens.css; later files read themeable
   colour only through these roles, so a new theme must redefine each
   one except --stream-track, the neutral empty tubing. Consumers:
   04-stream.css, 05-hero.css, 13-join.css, 03-nav.css (the charge bar
   reuses --stream-a/b/c) and the tank grounds —
   07-waves.css, 11-band.css, 11a-news.css, 14-footer.css,
   17c-dashboard.css. 03-nav.css also reads the attribute itself.
   Never themed: --mint*, the green ramp, the status colours.
   ============================================================ */

/* ---------- green: the neutral commons default ----------
   No platform is the page's subject, so 03-nav.css keeps both
   .btn-dc and .btn-gh neutral. The brand glyphs are currentColor and
   follow the button's text colour; the platform hover fills stay
   fixed (--pos-deep / --neg-deep, 01-tokens.css). */
:root{
  --accent:#1F8A4C;          /* marker and label weight */
  --accent-strong:#166534;   /* the text-safe weight */
  --accent-deep:#14532D;     /* the hover-fill weight */
  --accent-bright:#7FD1A2;   /* the pale weight */
  /* the tank: .band and its content take --tank / --tank-ink roles
     (11-band.css), the wave caps --tank-foam (07-waves.css) */
  --tank:#0E3B2E;
  --tank-foam:#166534;
  --tank-ink:#EAF6EE;
  --tank-ink-dim:#A9C7B8;
  --tank-ink-faint:#7FA98F;
  --tank-line:rgba(167,224,190,.22);
  /* electrolyte ramp, top of the page → tank: --stream-c always equals
     --tank, so the ramp ends in the tank */
  --stream-a:#1F8A4C;
  --stream-b:#166534;
   --stream-c:var(--tank);
  /* the tubing's inner shading stroke, neutral on every theme */
   --stream-track-inner:#D5D5D5;
  /* the empty tubing: neutral light grey on EVERY theme */
  --stream-track:#E8E9E8;
  /* the hero's corner glow (05-hero.css): a tint of --accent */
  --hero-glow:color-mix(in srgb, var(--accent) 14%, transparent);
  /* the join card's pale tints (13-join.css): background wash, hairline
     and edge crest, inner crest */
  --card-wash:#EAF6EE;
  --card-edge:#DCF2E4;
  --card-crest:#BFE7CD;
}

/* ---------- yellow: Discord / communication / committees ----------
   03-nav.css gives .btn-dc the solid highlight on this theme. */
[data-theme="yellow"]{
  --accent:#C9A20D;
  --accent-strong:#8A6D00;
  --accent-deep:#6B5400;
  --accent-bright:#F7DE5A;
  --tank:#453A06;
  --tank-foam:#D9B60A;
  --tank-ink:#FBF6DC;
  --tank-ink-dim:#D8C88B;
  --tank-ink-faint:#A9975C;
  --tank-line:rgba(242,208,36,.22);
  --stream-a:#F2D024;
  --stream-b:#D9B60A;
  --stream-c:var(--tank);    /* re-declared per theme on purpose: declared
                                once on :root it would resolve the green --tank */
  --stream-track-inner:#D5D5D5;
  --hero-glow:color-mix(in srgb, var(--accent) 14%, transparent);
  --card-wash:#FAF4D8;
  --card-edge:#F4E7B2;
  --card-crest:#EDD98A;
}

/* ---------- red: GitHub / data / resources ----------
   03-nav.css gives .btn-gh the solid highlight on this theme. */
[data-theme="red"]{
  --accent:#D94545;
  --accent-strong:#A93030;
  --accent-deep:#8A2420;
  --accent-bright:#F58484;
  --tank:#46201d;
  --tank-foam:#D94545;
  --tank-ink:#FDEDEB;
  --tank-ink-dim:#E7B0AA;
  --tank-ink-faint:#C0857E;
  --tank-line:rgba(238,92,92,.22);
  --stream-a:#EE5C5C;
  --stream-b:#D94545;
  --stream-c:var(--tank);
  --stream-track-inner:#D5D5D5;
  --hero-glow:color-mix(in srgb, var(--accent) 14%, transparent);
  --card-wash:#FCEAE8;
  --card-edge:#F7D2CD;
  --card-crest:#F0B3AC;
}


/* ── 01b-glass.css ── */
/* ============================================================
   Frosted glass surfaces — the three translucent ground recipes the site's cards and section bands are painted with.
   .glass-card: hero card, category card, committee row, repo link.
   .glass-strip: the figures bands (readout, chairs, repo band).
   .glass-wash: the platforms and minutes sections.
   An element takes its ground from exactly one of the three.
   Layout, padding, radius overrides and content live in the component's own later-numbered file, which wins the cascade at equal specificity.
   01-tokens.css defines the --glass-* and --wash-* recipes; nothing else in src/ reads them.
   15-fallbacks.css degrades all three where backdrop-filter is unsupported, so a new glass element needs no fallback line of its own.
   backdrop-filter is applied to the whole box, one filter per element.
   It re-runs its shader on every frame anything behind the box moves; behind these surfaces only the page ground and the scroll-driven stream move, so the cost is paid while the reader scrolls.
   ============================================================ */
.glass-card{
  background:var(--glass-bg);
  -webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-line);border-radius:var(--radius);
}
/* the band itself is the glass, not a card sitting in it: no radius, hairlines only */
.glass-strip{
  background:var(--glass-bg);
  -webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);
  border-block:1px solid var(--line);
}
/* the section is a page-ground veil, not a surface on it: gradient ground, stronger blur (14px against the card's 10px) */
.glass-wash{
  background:var(--wash-bg);
  -webkit-backdrop-filter:var(--wash-blur);backdrop-filter:var(--wash-blur);
  border-block:1px solid var(--line);
}


/* ── 02-base.css ── */
/* ============================================================
   Base — reset, typography, page furniture
   ============================================================ */
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth;scroll-padding-top:84px}   /* anchors land below the fixed nav: 64px in 03-nav.css, plus a gap */
body{
  font-family:var(--font-body);
  font-size:16.5px;line-height:1.65;
  color:var(--ink);background:var(--bg);
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
::selection{background:var(--mint);color:var(--g900)}

.wrap{max-width:1160px;margin-inline:auto;padding-inline:24px}

h1,h2,h3{font-family:var(--font-display);line-height:1.08;letter-spacing:-.015em}
h2{font-size:clamp(1.9rem,3.6vw,2.7rem);margin-bottom:14px}
h3{font-size:1.06rem;letter-spacing:0;line-height:1.3}
p.lede{color:var(--muted);font-size:clamp(1.02rem,1.5vw,1.14rem);max-width:56ch}

/* ---------- eyebrow: the small-caps label above a section heading ----------
   Hairline dash + --accent-strong text. Used by components/join-card.html,
   components/dash-spot.html and the page sections of index, committees
   and resources; its colour follows the page theme (01a-theme.css). */
.eyebrow{
  display:inline-flex;align-items:center;gap:8px;
  font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;
  color:var(--accent-strong);margin-bottom:18px;
}
.eyebrow::before{content:"";width:22px;height:2px;background:var(--accent);border-radius:2px}

a{color:inherit}
/* In-text links — an anchor with no class of its own is PROSE, so it gets the
   house idiom: accent text, no underline, and a pale mint hairline that on
   hover/focus pulls up to the colour of the text it belongs to. Prose links are
   semibold: the weight is what makes them readable as links at small sizes.
   Anchors that wear a class own their own look — .dash-link in
   17c-dashboard.css, the .cta-arrow calls to action in 08-sections.css,
   the .repo-link cards in 17a-repos.css — and the
   :not([class]) is what keeps this rule off them. The dark bands override the
   two colours in 11-band.css; the reduced-motion blanket in 19-motion.css
   silences the transition. */
main a:not([class]){color:var(--accent-strong);font-weight:600;text-decoration:none;border-bottom:1px solid var(--mint);transition:border-color .15s ease,color .15s ease}
main a:not([class]):hover,main a:not([class]):focus-visible{border-bottom-color:var(--accent-strong)}
/* src/layout.html's first element: keyboard focus reaches it before the nav */
.skip{
  position:absolute;left:-9999px;top:0;z-index:100;
  background:var(--g900);color:#fff;padding:10px 18px;border-radius:0 0 10px 0;
}
.skip:focus{left:0}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:6px}


/* ── 03-nav.css ── */
/* ============================================================
   Navigation — the fixed top bar (brand, menu links, join buttons)
   and the scroll-progress charge bar every page carries.
   Styles the markup of components/nav.html and components/join-btns.html;
   the dropdown panel rules live in 03a-navmenu.css, which overrides this
   file by source order and so must keep sorting after it.
   The nav.scrolled class and #chargeBar's scaleX transform are written by
   assets/js/01-stream.js; the button highlights read the page theme's
   --accent roles and the charge bar its --stream-a/b/c ramp from
   01a-theme.css.
   ============================================================ */
/* ---------- charge bar ----------
   Scroll-progress line: 01-stream.js writes its transform on every frame;
   scaleX(0) here is the top-of-page state.
   The ramp is the electrolyte's own: the same --stream-a/b/c stops and the
   same offsets as #streamGrad (04-stream.css + components/stream.html), run
   left→right instead of top→bottom. Because those three tokens are re-declared
   per page theme in 01a-theme.css, the bar repaints with the page exactly like
   the stream does — and its right end is --stream-c = --tank, the same place
   the tube drains into. */
#chargeBar{
  position:fixed;top:0;left:0;z-index:70;width:100%;height:3px;
  background:linear-gradient(90deg,var(--stream-a),var(--stream-b) 55%,var(--stream-c));
  transform:scaleX(0);transform-origin:left center;opacity:.55;
}
nav{
  position:fixed;top:0;left:0;right:0;z-index:60;height:64px;
  background:rgba(246,249,247,.82);
  -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);
  /* fixed + backdrop-filter needs its own compositor layer, or the bar swims mid-scroll */
  will-change:transform;
  border-bottom:1px solid transparent;
  /* border/shadow only: transitioning background-color re-rasterises the blur */
  transition:border-color .3s,box-shadow .3s;
}
nav.scrolled{border-color:var(--line);box-shadow:0 8px 24px -18px rgba(12,38,29,.25)}
/* height:100%, not 64px: the bar is border-box, so its content box is 63px
   once the bottom border is drawn. */
.nav-inner{height:100%;display:flex;align-items:center;justify-content:space-between;gap:24px}
.brand{display:flex;align-items:center;gap:11px;text-decoration:none;font-family:var(--font-display);font-weight:700;font-size:15.5px;letter-spacing:-.01em;color:var(--ink)}
.brand svg,.brand .logo{display:block;flex:none}
/* logo sized to the brand's two text lines; the attributes in nav.html match */
.brand .logo{width:40px;height:40px}
.brand small{display:block;font-family:var(--font-body);font-weight:500;font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);line-height:1;margin-top:2px}
.nav-links{display:flex;align-items:center;gap:30px;list-style:none}
.nav-links a{font-size:14px;font-weight:500;color:var(--muted);text-decoration:none;transition:color .2s}
.nav-links a:hover{color:var(--g700)}
.nav-links a[aria-current="page"]{color:var(--g700);font-weight:600}
/* ---------- button base ----------
   The pill geometry the platform buttons below extend; join-btns.html
   renders the only .btn markup. */
.btn{
  display:inline-flex;align-items:center;gap:9px;
  font-family:var(--font-body);font-size:14.5px;font-weight:600;
  padding:12px 24px;border-radius:999px;border:1px solid transparent;
  text-decoration:none;cursor:pointer;transition:transform .2s,box-shadow .2s,background .2s,color .2s;
}
.btn:active{transform:translateY(1px)}
.btn svg{flex:none;opacity:.9}
/* The two platforms (join-btns.html): neutral by default — on the green theme
   both stay neutral. The page theme highlights the platform the page is about:
   yellow → Discord, red → GitHub (roles from 01a-theme.css). */
.btn-dc,.btn-gh{border-color:var(--line);color:var(--ink);background:rgba(255,255,255,.6)}
.btn-dc:hover,.btn-gh:hover{border-color:var(--accent);color:var(--accent-deep)}
/* on hover the platform identity is always visible — Discord yellow, GitHub red */
.btn-dc:hover{background:var(--pos-deep);color:#fff;border-color:transparent;box-shadow:0 12px 24px -14px rgba(217,182,10,.7)}
.btn-gh:hover{background:var(--neg-deep);color:#fff;border-color:transparent;box-shadow:0 12px 24px -14px rgba(217,69,69,.7)}
[data-theme="yellow"] .btn-dc,
[data-theme="red"] .btn-gh{
  background:var(--accent-deep);color:#fff;border-color:transparent;
  box-shadow:0 12px 24px -14px color-mix(in srgb, var(--accent-deep) 70%, transparent);
}
[data-theme="yellow"] .btn-dc:hover{background:var(--pos-deep);box-shadow:0 16px 30px -14px rgba(217,182,10,.7)}
[data-theme="red"] .btn-gh:hover{background:var(--neg-deep);box-shadow:0 16px 30px -14px rgba(217,69,69,.7)}


/* ── 03a-navmenu.css ── */
/* ============================================================
   Nav menu — dropdown panels (desktop) + hamburger panel (<960px)
   Data: sites-general.js → nav (nested children) · markup: components/nav.html
   · behaviour: 03-navmenu.js, which owns the states styled here:
   .nav-item.open + .nav-top[aria-expanded] (one desktop dropdown at a time)
   and nav.menu-open (the <960px panel); :hover/:focus-within are pure CSS.
   Owns all responsive nav rules; must sort before 18-responsive.css (docs/authoring.md §3).
   ============================================================ */

/* ---------- top-level items ----------
   .nav-top needs two classes to outrank 03-nav.css's `.nav-links a` (0,1,1),
   which still colours the dropdown rows. */
.nav-links .nav-item{position:relative;display:flex;align-items:center}
.nav-links .nav-top{display:inline-flex;align-items:center;gap:6px;padding:8px 2px}
.nav-links .nav-top .chev{flex:none;opacity:.55;transition:transform .2s,opacity .2s}
.nav-links .nav-top .chev svg{width:10px;height:10px;stroke-width:2.6}  /* the shared caret mark, sized here */
.nav-item:hover .nav-top .chev,
.nav-item.open .nav-top .chev,
.nav-item:focus-within .nav-top .chev{transform:rotate(180deg);opacity:1}

/* ---------- dropdown panel ----------
   This base block is the DESKTOP panel; the <960px block at the bottom of this
   file overrides every property that matters there (top/left/z-index are inert
   on a static box), at the SAME specificity — so it wins on source order only.
   A new property added here therefore needs its mobile counterpart there, or
   wrap this block in @media (min-width:961px) and move the value into it. */
.nav-links .nav-menu{
  position:absolute;top:100%;left:50%;z-index:1;
  min-width:232px;list-style:none;padding:8px;
  background:var(--paper);border:1px solid var(--line);border-radius:14px;
  box-shadow:var(--shadow-soft);
  transform:translate(-50%,8px);
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .18s ease,transform .18s ease,visibility .18s;
}
/* invisible strip bridges the 8px gap, or :hover drops the panel before the cursor arrives */
.nav-links .nav-menu::before{content:"";position:absolute;left:0;right:0;top:-16px;height:16px}
.nav-links .nav-menu a{
  display:block;padding:9px 13px;border-radius:9px;
  font-size:13.5px;white-space:nowrap;
  transition:background-color .15s,color .15s;
}
.nav-links .nav-menu a:hover{background:var(--mint-soft);color:var(--g700)}
/* soon:true rows render a span, not an anchor: a not-yet-real resource must
   not look clickable */
.nav-links .nav-soon{
  display:flex;align-items:center;gap:9px;
  padding:9px 13px;border-radius:9px;
  font-size:13.5px;font-weight:500;color:var(--faint);
}
/* the pill wears the site's one GitHub red (--neg family), as in 10b-categories.css */
.soon-pill{
  margin-left:auto;flex:none;
  font-size:8.5px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  color:var(--neg-deep);background:rgba(238,92,92,.09);border:1px solid rgba(238,92,92,.4);
  padding:1.5px 6px;border-radius:999px;white-space:nowrap;
}
/* hover only where it is real; keyboard via :focus-within, touch via .open (03-navmenu.js).
   ⚠ EVERY rule that moves or reveals the panel must stay width-scoped to ≥961px:
   a media query adds no specificity, so an unscoped `.nav-item:hover .nav-menu`
   (0,3,0) would outrank the mobile `.nav-links .nav-menu{transform:none}`
   (0,2,0) at any width; below the breakpoint the panel is static and visible. */
@media (hover:hover) and (pointer:fine) and (min-width:961px){
  .nav-item:hover .nav-menu{
    opacity:1;visibility:visible;transform:translate(-50%,0);pointer-events:auto;
  }
}
@media (min-width:961px){
  .nav-item:focus-within .nav-menu,
  .nav-item.open .nav-menu{
    opacity:1;visibility:visible;transform:translate(-50%,0);pointer-events:auto;
  }
}
/* last group right-aligns its panel: centred it would overflow the viewport
   between 961 and ~1100px (≥961px only — below that the panel is static) */
@media (min-width:961px){
  .nav-item:last-child .nav-menu{left:auto;right:0;transform:translateY(8px)}
  .nav-item:last-child:focus-within .nav-menu,
  .nav-item:last-child.open .nav-menu{transform:translateY(0)}
}
@media (hover:hover) and (pointer:fine) and (min-width:961px){
  .nav-item:last-child:hover .nav-menu{transform:translateY(0)}
}

/* ---------- hamburger button (desktop: absent) ---------- */
.nav-toggle{display:none}
.nav-toggle svg{width:20px;height:20px;stroke-width:2}   /* the hamburger mark, lighter than the family default */

/* ---------- < 960px: one opaque panel, all three groups expanded ----------
   No nested disclosure: the panel shows the whole tree, parents read as group
   heads; 03-navmenu.js reveals it by toggling .menu-open on nav.
   top:64px and max-height mirror the fixed nav height of 03-nav.css — if that
   bar's height changes, these offsets must change with it. */
@media (max-width:960px){
  .nav-toggle{
    display:inline-flex;align-items:center;justify-content:center;flex:none;
    width:40px;height:40px;border:1px solid var(--line);border-radius:10px;
    background:var(--paper);color:var(--g700);cursor:pointer;
  }
  .nav-links{
    display:none;
    position:fixed;top:64px;left:0;right:0;z-index:1;
    flex-direction:column;align-items:stretch;gap:0;
    max-height:calc(100vh - 64px);overflow-y:auto;
    padding:6px 24px 18px;
    background:var(--bg);border-bottom:1px solid var(--line);
    box-shadow:0 20px 32px -26px rgba(12,38,29,.3);
  }
  nav.menu-open .nav-links{display:flex}
  .nav-links .nav-item{display:block;border-bottom:1px solid var(--line)}
  .nav-links .nav-item:last-child{border-bottom:0}
  .nav-links .nav-top{width:100%;justify-content:space-between;padding:13px 2px;font-size:15px;font-weight:600;color:var(--ink)}
  .nav-links .nav-top .chev{display:none}
  .nav-links .nav-menu{
    position:static;transform:none;opacity:1;visibility:visible;pointer-events:auto;
    min-width:0;padding:0 0 10px 8px;border:0;border-radius:0;box-shadow:none;background:transparent;
  }
  /* labels wrap here: the longest string the menu carries is the Data
     Infrastructure Committee name, and a phone is 320px wide — nowrap would
     push it out of the panel. The desktop rows keep nowrap because a floating
     card sizes itself to its content. */
  .nav-links .nav-menu a{padding:8px 10px;font-size:14px;white-space:normal}
  .nav-links .nav-soon{padding:8px 10px;font-size:14px}
}

@media (prefers-reduced-motion:reduce){
  .nav-links .nav-menu,.nav-links .nav-top .chev{transition:none}
}


/* ── 04-stream.css ── */
/* ============================================================
   The electrolyte stream — a page-long tube down the left margin, filled by
   scrolling (pathLength=100 dash trick). 01-stream.js generates the path,
   ending inside the footer's wave (components/footer.html): keep that pairing.

   Colour lives in 01a-theme.css: the page theme (front matter `theme:`) sets
   --stream-a/b/c + --stream-track-inner; --stream-track (empty tubing) is the
   same neutral grey on every theme. This file wires the variables to the
   strokes and stacks the page content above the svg.
   Couplings: 01-stream.js reads the track's computed stroke-width, so the
   stroke rules here are its source of truth; NARROW there ↔ 960px here.
   ============================================================ */
.page{position:relative}
#stream{
  position:absolute;top:0;left:0;width:100%;height:100%;
  z-index:0;pointer-events:none;
}
.stream-track{fill:none;stroke:var(--stream-track);stroke-width:30;stroke-linecap:round}
.stream-fill{
  fill:none;stroke:url(#streamGrad);stroke-width:11;stroke-linecap:round;
  stroke-dasharray:100;stroke-dashoffset:100;
  opacity:.55;
}
/* the tubing's inner line: static, the electrolyte flows past it */
.stream-track-inner{
  fill:none;stroke:var(--stream-track-inner);stroke-width:11;stroke-linecap:round;opacity:.55;
}
/* the gradient stops — a CSS rule beats the stop-color attributes in
   components/stream.html, so the ramp follows the theme */
#streamGrad .stream-stop-a{stop-color:var(--stream-a)}
#streamGrad .stream-stop-b{stop-color:var(--stream-b)}
#streamGrad .stream-stop-c{stop-color:var(--stream-c)}
/* Must equal NARROW in 01-stream.js: CSS cannot read the constant, so the
   pairing is by convention. Below it every stroke halves. */
@media (max-width:960px){
  .stream-track{stroke-width:15}
  .stream-fill{stroke-width:5.5}
  .stream-track-inner{stroke-width:5.5}
}
/* Without JS the stream can never fill: hide the empty tube. The `js` class
   comes from the inline script in layout.html. */
html:not(.js) #stream{display:none}
/* the page content sits above the absolutely-positioned #stream (z-index:0) */
main{position:relative;z-index:2}


/* ── 05-hero.css ── */
/* ============================================================
   Hero — the landing header of src/pages/index.html: copy
   column, corner glow and the avatar-orbit layer.
   The orbit's <img> swarm is generated markup between the
   ORBIT:BEGIN/END markers in index.html (scripts/gen-orbit.py):
   restyle it here, never edit the block. The hero card inside
   .hero-card-orbit is src/components/hero-card.html, styled by
   06-hero-card.css — which borrows @keyframes blink for .bulb.
   Couplings: --hero-glow and the accent colours come from
   01a-theme.css; 19-motion.css stops blink and cue under
   reduced motion.
   ============================================================ */
/* ---------- hero layout ----------
   .hero::before draws the dot grid, masked to a soft ellipse over
   the orbit side; the narrow-width collapse of .hero-grid is
   18-responsive.css. */
.hero{position:relative;padding:150px 0 96px;overflow:hidden}
.hero::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background-image:radial-gradient(rgba(12,38,29,.055) 1px,transparent 1.5px);
  background-size:26px 26px;
  -webkit-mask-image:radial-gradient(ellipse 90% 70% at 60% 20%,#000 0%,transparent 70%);
  mask-image:radial-gradient(ellipse 90% 70% at 60% 20%,#000 0%,transparent 70%);
}
/* ---------- corner glow ----------
   One light body of the theme's electrolyte, shared by the home
   hero and every sub-page hero (.page-hero, 17-page.css); the
   colour is --hero-glow (01a-theme.css) so it follows the page
   theme. */
.hero::after,.page-hero::after{
  content:"";position:absolute;width:640px;height:640px;left:-220px;top:-180px;border-radius:50%;
  background:radial-gradient(circle,var(--hero-glow),transparent 65%);pointer-events:none;
}
.hero-grid{display:grid;grid-template-columns:1.05fr .95fr;gap:56px;align-items:center;position:relative;z-index:2}
/* ---------- orbit layer ----------
   The rings are generated markup between the ORBIT markers in
   index.html (scripts/gen-orbit.py); .hero-copy sits above them.
   The swarm stands still with a fixed tilt per avatar: an infinite
   animation on 72 images keeps the page producing frames. */
.hero-card-orbit{position:relative}
.hero-copy{position:relative;z-index:1}
.hero-floats{position:absolute;inset:0;pointer-events:none;z-index:0}
/* The base centring for every avatar. The generated markup repeats
   translate(-50%,-50%) inside its own inline `transform`, because an inline
   transform replaces this whole declaration instead of adding to it. */
.hero-floats img{position:absolute;border-radius:50%;transform:translate(-50%,-50%)}
/* ---------- copy column ----------
   .chip is the status pill above the headline, on the theme's
   mint ground. */
.chip{
  display:inline-flex;align-items:center;gap:9px;
  background:var(--mint-soft);border:1px solid var(--mint);color:var(--accent-deep);
  font-size:13px;font-weight:600;padding:7px 16px;border-radius:999px;margin-bottom:26px;
}
.chip .dot{width:8px;height:8px;border-radius:50%;background:var(--accent);animation:blink 2.4s ease-in-out infinite}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.35}}
.hero h1{font-size:clamp(2.9rem,6vw,4.55rem);margin-bottom:22px}
.hero h1 .grad{
  /* the two tanks: Discord gold → GitHub red, in the -deep variants that are
     legible as text on light ground */
  background:linear-gradient(100deg,var(--pos-deep),var(--neg-deep));
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
.hero .sub{color:var(--muted);font-size:clamp(1.02rem,1.6vw,1.16rem);max-width:52ch;margin-bottom:34px}
/* ---------- buttons ----------
   .cta-row packs the join-btns.html buttons — their skin is
   03-nav.css; .scroll-cue is the anchor to the next section, its
   arrow bobbing on the cue keyframes. */
.cta-row{display:flex;flex-wrap:wrap;gap:14px}
.scroll-cue{margin-top:56px;display:inline-flex;align-items:center;gap:10px;font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);text-decoration:none}
.scroll-cue svg{animation:cue 2.2s ease-in-out infinite}
@keyframes cue{0%,100%{transform:translateY(0)}50%{transform:translateY(5px)}}


/* ── 06-hero-card.css ── */
/* ============================================================
   Hero card — the animated flow-battery diagram of the home hero; markup:
   components/hero-card.html, revealed by 02-reveal.js. Its ground is
   .glass-card (01b-glass.css); this file overrides that utility's hairline
   and radius and owns the diagram's animations: float, flowDash, spin,
   waveShift, rise and glowPulse are defined here, .bulb reuses @keyframes
   blink from 05-hero.css. The card sits inside the avatar orbit of
   index.html (ORBIT block generated by scripts/gen-orbit.py, styled in
   05-hero.css); every animation stops under 19-motion.css's guard.
   ============================================================ */
.hero-card{
  position:relative;z-index:1;border:1px solid var(--line);
  border-radius:24px;box-shadow:var(--shadow);padding:20px 20px 12px;
}
.hero-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:2px 6px 12px}
.hero-card-title{font-size:11.5px;font-weight:600;letter-spacing:.13em;text-transform:uppercase;color:var(--faint)}
.hero-card-float{animation:float 7s ease-in-out infinite}
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-9px)}}
.hero-card-svg{display:block;width:100%;height:auto}
.hero-card-foot{display:flex;justify-content:space-between;padding:6px 10px 8px;font-size:12px;color:var(--faint)}
/* The two labels echo the tanks above them: Discord left (yellow), GitHub right (red). */
.hero-card-foot .foot-dc{color:var(--pos-deep);font-weight:600}
.hero-card-foot .foot-gh{color:var(--neg-deep);font-weight:600}

.flow{animation:flowDash 1.5s linear infinite}
.flow.slow{animation-duration:1.9s}
@keyframes flowDash{to{stroke-dashoffset:-32}}
.impeller{transform-box:fill-box;transform-origin:center;animation:spin 2.6s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
/* The left pump turns against the right one (the two loops mirror each other). */
.impeller.rev{animation-direction:reverse}
.wave{animation:waveShift 3.4s linear infinite}
.wave.rev{animation-direction:reverse;animation-duration:4.2s}
@keyframes waveShift{to{transform:translateX(-66px)}}
/* Both tanks at the same level. Wave baseline y=246; −26 lifts the surface to
   ~220 (≈77 % fill). */
.level{transform:translateY(-26px)}
.tank-mark{filter:drop-shadow(0 2px 4px rgba(23,24,26,.25))}
/* Marks wear the logo's exact tank colours (--pos/--neg), so they are one colour
   site-wide; they sit on the badge's white disc, not on the liquid. */
.mark-dc{fill:var(--pos)}
.mark-gh{fill:var(--neg)}
/* Pumps wear the electrolyte they move (--pos/--neg, not the -deep variants).
   Set in CSS because fill="var(…)" presentation attributes are not substituted. */
.impeller-dc{fill:var(--pos)}
.impeller-gh{fill:var(--neg)}
.bubble{animation:rise 4.6s ease-in infinite;opacity:0}
@keyframes rise{0%{transform:translateY(0);opacity:0}12%{opacity:.85}78%{opacity:.4}100%{transform:translateY(-92px);opacity:0}}
.glow{animation:glowPulse 3.2s ease-in-out infinite}
@keyframes glowPulse{0%,100%{opacity:.30}50%{opacity:.62}}
.bulb{animation:blink 2.4s ease-in-out infinite}


/* ── 07-waves.css ── */
/* ============================================================
   Wave dividers — the two-tone caps of components/wave-top.html and
   components/wave-bottom.html, joining bands and piping the stream into the
   footer tank. Height is --wave-h (01-tokens.css); caps overlap the band by 1px
   so no page-ground hairline shows at the seam. They are furniture, z-index:-1
   behind the band's content (stacking context: .band's isolation, 11-band.css).
   scripts/gen-wave-mask.js copies the caps' body paths into the band photos'
   CSS masks (WAVE-MASK blocks in 11a-news.css and 17c-dashboard.css).
   CRITICAL: an absolutely-positioned decorative SVG needs explicit
   width/height:100% — a viewBox alone sizes by aspect ratio and detaches the wave.
   ============================================================ */
.band .wave-top,.band .wave-bottom{position:absolute;left:0;width:100%;height:var(--wave-h);pointer-events:none;z-index:-1}
.band .wave-top svg,.band .wave-bottom svg{display:block;width:100%;height:100%}
.band .wave-top{top:calc(1px - var(--wave-h))}
.band .wave-bottom{bottom:calc(1px - var(--wave-h));transform:scaleY(-1)}
/* the two-tone fills follow the page theme (01a-theme.css) — the caps are the
   band's edge, so they take the tank colours exactly like the footer wave */
.wave-foam{fill:var(--tank-foam)}
.wave-body{fill:var(--tank)}

.foot-wave{position:absolute;top:calc(1px - var(--wave-h));left:0;width:100%;height:var(--wave-h);pointer-events:none}
.foot-wave svg{display:block;width:100%;height:100%}
/* the footer reuses the wave-top partial, so its .wave-top wrapper has to
   fill the .foot-wave box exactly (the .band rules above don't reach it) */
.foot-wave .wave-top{position:absolute;inset:0}


/* ── 08-sections.css ── */
/* ============================================================
   Section furniture shared across pages: the vertical rhythm of <section>,
   the heading block, the hairline row list (.rows) and the text-CTA arrow
   idiom (.cta-arrow — taken up by category-card, platform-row, news-slide
   and dash-spot, each styling the voice in its own file).
   ============================================================ */
section{padding:96px 0}
.sec-head{max-width:640px;margin-bottom:56px}
.footnote{margin-top:20px;font-size:12.5px;color:var(--faint)}
/* hairline row list: container top rule + one rule between siblings; used by
   platform rows, FAQ list, minutes groups */
.rows{border-top:1px solid var(--line)}
.rows > * + *{border-top:1px solid var(--line)}
/* text CTA with a trailing arrow mark: the nudge lives here, the voice (size,
   colour, measure) stays in the instance; --cta-gap for a wider word set */
.cta-arrow{display:inline-flex;align-items:center;gap:var(--cta-gap,6px);font-weight:600;text-decoration:none}
.cta-arrow svg{flex:none;transition:transform .25s}
.cta-arrow:hover svg{transform:translateX(4px)}


/* ── 08a-band.css ── */
/* ============================================================
   Figures band — the single home of the shared figures idiom: a
   row of display numerals divided by hairline column rules, with
   small-caps labels beneath. This file draws the charge line, the
   grid, the column rules and the figure type, and sets no boxes,
   no hover, no transition; an instance adds only its own box —
   padding, --fig-cols, item alignment, --fig-rule-t/-b insets and
   numeral size: .readout (12a-proof.css), .chairs (11b-chairs.css),
   .repo-band (17a-repos.css), .dash-facts (17c-dashboard.css) — and
   sorts later, so it wins at equal specificity. Markup: .figures in
   the three pages; items: readout-item, chair-item, repo-cat, dash-spot.
   ============================================================ */
.figures{position:relative}
/* the charge line; a band that must not carry it uses .figures-grid
   without .figures — the dark .dash-facts row, components/dash-spot.html */
.figures::before{content:"";position:absolute;left:0;right:0;top:0;height:2px;background:linear-gradient(90deg,var(--pos),var(--g500) 50%,var(--neg));opacity:.75}
.figures-grid{list-style:none;display:grid;grid-template-columns:repeat(var(--fig-cols,4),1fr)}
.figures-item{position:relative}
.figures-item + .figures-item::before{content:"";position:absolute;left:0;top:var(--fig-rule-t,10px);bottom:var(--fig-rule-b,10px);width:1px;background:var(--fig-rule,var(--line))}
.figures-v{font-family:var(--font-display);font-weight:700;line-height:1.05;font-size:clamp(2.2rem,3.6vw,3.05rem);color:var(--ink);font-variant-numeric:tabular-nums}
.figures-k{margin-top:9px;font-size:11.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
/* The dark variant (components/dash-spot.html) takes the hairline colour from the page theme's --tank-line (01a-theme.css). */
.is-dark{--fig-rule:var(--tank-line)}


/* ── 09a-aims.css ── */
/* ============================================================
   Aims — the home "Why a digital commons?" band (index only): pages/index.html
   repeats components/aim-item.html over aimItems in src/data/site-index.js
   (the ordinals are authored data, not list-style — the <ol> is
   decoration-free). Breakpoints live here, not in 18-responsive.css:
   component rules whose class names are unique to this band.
   ============================================================ */
.aims{padding-top:48px}
.aims-list{
  position:relative;
  list-style:none;
  display:grid;grid-template-columns:repeat(4,1fr);
  margin-top:44px;padding-top:28px;
}
.aims-list::before{
  content:"";position:absolute;left:0;right:0;top:0;height:2px;border-radius:2px;
  background:linear-gradient(90deg,var(--pos-deep),var(--neg-deep));
}
.aim-item{padding:0 26px}
.aim-item:first-child{padding-left:0}
.aim-item + .aim-item{border-left:1px solid var(--line)}
.aim-item .num{
  display:block;margin-bottom:12px;
  font-family:var(--font-display);font-weight:700;font-size:12px;
  letter-spacing:.14em;color:var(--accent);
}
/* min-height keeps the four aim titles on one shared baseline: the 4-column
   grid (≥1081px) is the only layout narrow enough for a title to wrap. */
.aim-item h3{font-size:clamp(1.16rem,1.7vw,1.42rem);margin-bottom:10px}
@media (min-width:1081px){
  .aim-item h3{min-height:2.4em}
}
.aim-item .gloss{font-size:14.5px;font-weight:600;color:var(--ink);margin-bottom:8px}
.aim-item .why{font-size:14px;line-height:1.6;color:var(--muted)}

/* two columns from 1080 down: the rule between a pair stays, the one that
   would open a new row goes */
@media (max-width:1080px){
  .aims-list{grid-template-columns:repeat(2,1fr);row-gap:36px}
  /* specificity: both selectors are 0-2-0 like `.aim-item + .aim-item`,
     and they come later, so they win the border-left fight */
  .aim-item:nth-child(odd){border-left:0;padding-left:0}
  .aim-item:nth-child(even){border-left:1px solid var(--line);padding-left:26px}
}
@media (max-width:640px){
  .aims-list{grid-template-columns:1fr;row-gap:26px;padding-top:24px}
  /* :nth-child(n) is 0-2-0, so it out-specifies both the `+ ` sibling
     rule and the even-column rule above; one column needs no rules between */
  .aim-item:nth-child(n){border-left:0;padding:0}
}


/* ── 10-cards.css ── */
/* ============================================================
   The resource category cards (home · section.categories) — the tile one
   category is drawn with: mark, title, one paragraph, one CTA. Its ground is
   .glass-card (01b-glass.css); this file owns only the layout and the hover.
   `.cat-grid` lays them out three across (18-responsive.css takes it to two,
   then one); markup components/category-card.html, data sites-general.js →
   categories. The tiles carry [data-reveal] (02-reveal.js, 16-reveal.css).
   10b-categories.css sorts after this file and re-accents `.cat` inside
   `section.categories` at higher specificity — keep the two in step.
   ============================================================ */
.cat-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:22px}
.cat{
  padding:30px 28px;text-decoration:none;color:inherit;display:block;
  transition:transform .3s cubic-bezier(.22,.61,.36,1),box-shadow .3s,border-color .3s;
}
.cat:hover{transform:translateY(-5px);box-shadow:var(--shadow);border-color:var(--accent-bright)}
.cat .ic{
  width:46px;height:46px;border-radius:13px;background:var(--mint-soft);border:1px solid var(--mint);
  display:flex;align-items:center;justify-content:center;color:var(--accent-strong);margin-bottom:20px;
  transition:background .3s,color .3s;
}
.cat:hover .ic{background:var(--accent-deep);color:#fff}
.cat h3{margin-bottom:8px}
.cat p{font-size:14.5px;color:var(--muted)}
.cat .go{margin-top:16px;font-size:13px;color:var(--accent-strong)}
.cat .go svg{width:13px;height:13px}   /* the mark is canonical 24-grid; the size is ours */
/* the card answers, not the CTA: the nudge is .cta-arrow's, this is the wider trigger */
.cat:hover .go svg{transform:translateX(4px)}


/* ── 10a-platforms.css ── */
/* ============================================================
   The two platforms — Discord and GitHub as two open rows on the section glass
   (home · section.platforms#platforms, "Where to find us").
   The brand key in `cls` picks the logo side, the reveal direction and the
   colour: dc = --pos (yellow tank), gh = --neg (red tank), mark and CTA
   alike — same convention as the tank electrolytes in 06-hero-card.css.
   Markup components/platform-row.html, data sites-general.js → platforms.
   The [data-live] spans inside .platform-meta are the live Discord/GitHub
   counts 05-community.js writes: it replaces text only, so nothing here may
   size to them. The rows' [data-reveal] is 02-reveal.js + 16-reveal.css.
   ============================================================ */
/* the section's ground is .glass-wash (01b-glass.css); the rows carry no card
   chrome (components/platform-row.html) and their hairlines are .rows
   (08-sections.css) */
.platform-rows{
  --slide:64px;                 /* reveal distance, see the media queries */
  display:grid;
}
.platform{
  display:flex;align-items:center;gap:44px;
  padding:40px 0;color:inherit;text-decoration:none;
}
.platform.gh{flex-direction:row-reverse;text-align:right}

.platform-mark{flex:none;display:flex;transition:transform .3s}
.platform-mark svg{width:92px;height:92px}
.platform:hover .platform-mark{transform:scale(1.05)}
.platform.dc .platform-mark svg{color:var(--pos)}
.platform.gh .platform-mark svg{color:var(--neg)}

.platform-body{display:grid;gap:8px;flex:1;min-width:0}
.platform-name{font-family:var(--font-display);font-size:1.4rem;font-weight:700;letter-spacing:-.01em;color:var(--ink)}
.platform-desc{font-size:15.5px;line-height:1.65;color:var(--muted);max-width:66ch}
.platform-meta{
  font-size:12.5px;font-weight:600;letter-spacing:.05em;color:var(--faint);
  font-variant-numeric:tabular-nums;
}
.platform.gh .platform-desc,.platform.gh .platform-meta{margin-left:auto}
.platform-cta{
  --cta-gap:7px;
  justify-self:start;margin-top:10px;
  font-size:13.5px;color:var(--accent-strong);transition:color .25s;
}
.platform-cta svg{width:13px;height:13px}
.platform.gh .platform-cta{justify-self:end}
/* :focus-visible and :active as well, so keyboard and tap get the mark's colour */
.platform.dc:hover .platform-cta,.platform.dc:focus-visible .platform-cta,.platform.dc:active .platform-cta{color:var(--pos)}
.platform.gh:hover .platform-cta,.platform.gh:focus-visible .platform-cta,.platform.gh:active .platform-cta{color:var(--neg)}
/* the row answers, not the CTA: .cta-arrow's nudge is kept here as the wider
   trigger, and the flipped row composes it with its own mirror */
.platform:hover .platform-cta svg{transform:translateX(4px)}
/* flipped row: the arrow leads out to the left, and these two come last
   so they beat the plain-row hover rule */
.platform.gh .platform-cta svg{order:-1;transform:scaleX(-1)}
.platform.gh:hover .platform-cta svg{transform:scaleX(-1) translateX(4px)}

/* reveal from the outside in, each row toward the side its text sits on;
   02-reveal.js adds .revealed and :not(.revealed) drops the slide rule, so
   .revealed's transform:none in 16-reveal.css wins unaided (html.js comes
   from the inline script in src/layout.html) */
html.js .platform-rows .platform.dc[data-reveal]:not(.revealed){transform:translateX(calc(var(--slide) * -1))}
html.js .platform-rows .platform.gh[data-reveal]:not(.revealed){transform:translateX(var(--slide))}

@media (max-width:960px){
  .platform-rows{--slide:44px}
  .platform{padding:30px 0;gap:28px}
  .platform-mark svg{width:64px;height:64px}
  .platform-name{font-size:1.25rem}
}
@media (max-width:700px){
  /* one column: the mark stacks above the text, left-aligned; the slide
     shrinks to a hint — there is no room to travel across */
  .platform-rows{--slide:24px}
  .platform,.platform.gh{flex-direction:column;align-items:flex-start;text-align:left;gap:20px}
  .platform.gh .platform-desc,.platform.gh .platform-meta{margin-left:0}
  .platform.gh .platform-cta{justify-self:start}
  .platform.gh .platform-cta svg{order:0;transform:none}
  .platform.gh:hover .platform-cta svg{transform:translateX(4px)}
}


/* ── 10b-categories.css ── */
/* ============================================================
   The category cards on the home page only — re-accented in the GitHub red,
   quiet until you touch it: hover fills the tile solid.
   Scoped to `section.categories` (0,3,x, no !important): 10-cards.css owns the
   tile itself, this file only re-colours the glyph, the border and the CTA.
   Same markup as there (components/category-card.html, sites-general.js →
   categories); resources.html renders that array as section.repo-band, which
   this selector never reaches (17a-repos.css). No JS state class is hooked.
   White on --neg is 3.3:1 — clears 1.4.11 (3:1) for the glyph; the 13px CTA is
   under 1.4.3's 4.5:1, accepted deliberately: a darkened red is not GitHub red.
   ============================================================ */

/* at rest only the glyph moves, to --ink — what the card <h3> computes to */
section.categories .cat .ic{color:var(--ink)}
/* hover: the tile charges solid, the CTA follows it into red */
section.categories .cat:hover{border-color:rgba(238,92,92,.45)}
section.categories .cat:hover .ic{background:var(--neg);color:#fff}
section.categories .cat .go{transition:color .25s}
section.categories .cat:hover .go{color:var(--neg)}


/* ── 11-band.css ── */
/* ============================================================
   Dark band ("the transparent stack") — the section that sits on the tank
   colour instead of glass: the home News & events deck (index.html →
   section.band#news, site-index.js news) and the resources dashboard band
   (resources.html → section.band#dashboard, site-resources.js dashboard).
   This file is the shared ground and ink only; wave caps are 07-waves.css,
   the news deck's rules 11a-news.css, the dashboard's 17c-dashboard.css.
   The band follows the page theme: its ground IS the tank colour — the same
   dark liquid the footer holds — so band, wave caps and footer read as one
   body of electrolyte on themed pages (01a-theme.css).
   ============================================================ */
/* isolation, not a transform: stacking context for the z-index:-1 wave caps (see 11a-news.css) */
.band{position:relative;isolation:isolate;background:var(--tank);color:var(--tank-ink);padding:130px 0 90px;margin-top:70px}
.band .eyebrow{color:var(--accent-bright)}
.band .eyebrow::before{background:var(--accent-bright)}
.band h2{color:#fff}
.band p.lede{color:var(--tank-ink-dim)}

.band .footnote{color:var(--tank-ink-faint)}   /* size + spacing come from 08-sections.css */
/* In-text links on the dark ground. 11-band.css owns the band's ink roles, so
   the dark half of the link idiom lives here rather than in 02-base.css. The
   one instance is the dashboard band's lede copy on resources.html — the Marimo
   notebook and the study-dashboard repo links in site-resources.js — plus any
   prose link a future band writes. The base rule's two colours are both wrong
   here: --accent-strong is dark green on dark green, and --mint is the pale mint
   that NEVER themes (01a-theme.css), so it would draw a green hairline across a
   yellow or red tank. The band takes its own accent instead: --accent-bright for
   the text (what .band .eyebrow and the band's own .dash-link in
   17c-dashboard.css already wear) and a 35 % wash of it for the hairline, so the
   line reads as the word's shadow and not a second colour; hover pulls it to
   white, the band's loudest ink. The 1px and its style come from the base rule —
   only the colours are re-decided. */
.band a:not([class]){color:var(--accent-bright);border-bottom-color:color-mix(in srgb,var(--accent-bright) 35%,transparent)}
.band a:not([class]):hover,.band a:not([class]):focus-visible{border-bottom-color:#fff}

/* ── 11a-news.css ── */
/* ============================================================
   News & events deck — the content of the home page's dark band (index.html
   #news): label, one slide at a time, control row. Slides: the partial
   components/news-slide.html, one per item of the `news` array in
   src/data/site-index.js; the control row: components/deck-ctrl.html.
   All slides share ONE grid cell (each `grid-area:1/1`), so paging never
   moves the page, and the longest lede sets every slide's height. 04-news.js
   adds .is-ready (stack on) and .is-active (which slide is visible) and sets
   [aria-current] on the dot it paged to; with no JS the band stays a plain
   list and .news-ctrl is hidden. The picture's visible crop is the width cap
   on `.news-photo img` — the ~46% site-index.js quotes when choosing w/h.
   ============================================================ */

/* ---------- the band as a stage ----------
   min-height, not a fixed height: a long lede must push the band taller, not overflow. */
.band-news{
  display:flex;flex-direction:column;
  padding:60px 0 46px;min-height:clamp(600px,80vh,860px);
}
/* width:100% is load-bearing: `.band-news` is a column flex container, so `.wrap`
   is a cross-axis flex item and the base `.wrap`'s margin-inline:auto cancels the
   default stretch — without a definite width the band collapses to max-content. */
.band-news .wrap{flex:1 1 auto;width:100%;display:flex;flex-direction:column;min-width:0}
.band-news .sec-head{margin-bottom:22px}
/* an h2 wearing the eyebrow look: keeps the section's place in the outline
   without a headline; the two-class selector outranks `.band h2` */
.band-news .eyebrow{font-family:var(--font-body);font-weight:600;color:var(--accent-bright);margin:0}
/* everything that is not the photo opts back into the stacking order: an
   absolutely positioned z-index:0 element paints above in-flow content. The wave
   caps are the layer below: z-index:-1 in the isolated .band — 07-waves.css */
.band-news .sec-head,.news-ctrl{position:relative;z-index:1}

/* ---------- the deck fills the band, so the controls land at the bottom ----------
   CRITICAL: no `transform` on the deck, the stage, a slide or any ancestor — a
   transformed ancestor becomes the containing block for the absolutely positioned
   photo backdrop and collapses it to the text column. Hence motion on `.news-text`
   and an opacity-only scroll reveal for the deck. */
.news-deck{flex:1 1 auto;display:flex;flex-direction:column;min-height:0}
html.js .band-news .news-deck[data-reveal]{transform:none}

/* align-content:start, not center: reads from the label down, and with the slides
   sharing one grid cell this keeps their text on the same line when paging */
.news-stage{flex:1 1 auto;display:grid;align-content:start}
.news-slide + .news-slide{margin-top:34px}      /* the no-JS / pre-init list */
/* list fallback (no JS, un-run JS, or a one-item deck): make the slide the
   containing block, or every backdrop would paint on the band itself */
.news-stage:not(.is-ready) .news-slide{position:relative}
.news-stage:not(.is-ready) .news-photo{top:0;bottom:0;-webkit-mask-image:none;mask-image:none}
html.js .news-stage.is-ready .news-slide{
  grid-area:1/1;margin-top:0;
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .5s ease,visibility 0s linear .5s;
}
html.js .news-stage.is-ready .news-slide.is-active{
  opacity:1;visibility:visible;pointer-events:auto;
  transition:opacity .5s ease,visibility 0s;
}
html.js .news-stage.is-ready .news-text{transform:translateY(12px);transition:transform .5s cubic-bezier(.22,.61,.36,1)}
html.js .news-stage.is-ready .news-slide.is-active .news-text{transform:none}

/* ---------- the slide: one text column (components/news-slide.html) ----------
   keeps the band's .sec-head measure (640px); the rest of the band is the picture's */
.news-slide{display:grid;grid-template-columns:minmax(0,640px)}
.news-text{position:relative;z-index:1;display:grid;align-content:start;min-width:0}
.news-tag{
  display:inline-flex;align-items:center;gap:9px;margin-bottom:15px;
  font-size:11px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  color:var(--accent-bright);
}
.news-tag::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--accent-bright)}
.news-title{
  font-family:var(--font-display);font-weight:700;color:#fff;
  font-size:clamp(1.45rem,2.6vw,2.1rem);line-height:1.14;letter-spacing:-.015em;
  margin-bottom:10px;
}
.news-meta{font-size:13px;color:var(--tank-ink-faint);margin-bottom:16px}
.news-lede{font-size:16px;line-height:1.72;color:var(--tank-ink-dim);max-width:62ch}
.news-link{
  --cta-gap:8px;
  justify-self:start;margin-top:22px;
  font-size:14px;color:var(--accent-bright);
  border-bottom:1px solid color-mix(in srgb, var(--accent-bright) 35%, transparent);padding-bottom:3px;
  transition:color .25s,border-color .25s;
}
.news-link:hover{color:#fff;border-color:#fff}
.news-link svg{width:13px;height:13px}

/* ---------- the picture: a backdrop, not a box ----------
   Bleeds to the viewport's right edge and fades out to the left in the band's own
   colour, so it has no visible edge; never interactive, never a layout input. The
   width cap keeps the crop honest on very wide screens. */
.news-photo{
  position:absolute;left:0;right:0;top:calc(1px - var(--wave-h));bottom:calc(1px - var(--wave-h));
  z-index:0;margin:0;overflow:hidden;pointer-events:none;
  /* UNION of three mask layers (upper cap, solid middle band, flipped lower cap): a
     lost layer costs only its strip. GENERATED by scripts/gen-wave-mask.js — re-run it. */
  /* WAVE-MASK:BEGIN generated by scripts/gen-wave-mask.js — do not hand-edit */
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1440' height='70' viewBox='0 0 1440 70' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 70 L0 34 C160 12 340 52 520 34 C700 16 880 56 1060 36 C1220 18 1340 44 1440 28 L1440 70 Z'/%3E%3C/svg%3E"),linear-gradient(#000,#000),url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1440' height='70' viewBox='0 0 1440 70' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' transform='translate(0,70) scale(1,-1)' d='M0 70 L0 30 C180 52 360 12 540 32 C720 52 900 14 1080 34 C1240 52 1360 24 1440 40 L1440 70 Z'/%3E%3C/svg%3E");
  -webkit-mask-size:100% 70px,100% calc(100% - 138px),100% 70px;
  -webkit-mask-position:left top,0 69px,left bottom;
  -webkit-mask-repeat:no-repeat;
          mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1440' height='70' viewBox='0 0 1440 70' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 70 L0 34 C160 12 340 52 520 34 C700 16 880 56 1060 36 C1220 18 1340 44 1440 28 L1440 70 Z'/%3E%3C/svg%3E"),linear-gradient(#000,#000),url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1440' height='70' viewBox='0 0 1440 70' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' transform='translate(0,70) scale(1,-1)' d='M0 70 L0 30 C180 52 360 12 540 32 C720 52 900 14 1080 34 C1240 52 1360 24 1440 40 L1440 70 Z'/%3E%3C/svg%3E");
          mask-size:100% 70px,100% calc(100% - 138px),100% 70px;
          mask-position:left top,0 69px,left bottom;
          mask-repeat:no-repeat;
  /* WAVE-MASK:END */
}
.news-photo img{
  /* the data's width/height attributes are overridden here: this box is the
     crop, object-fit does the rest — so the file's ratio need not match. */
  position:absolute;top:0;bottom:0;right:0;width:min(46%,760px);height:100%;
  object-fit:cover;object-position:50% 44%;   /* keep the faces, lose ceiling and floor */
}
.news-photo::after{
  content:"";position:absolute;top:0;bottom:0;right:0;width:min(46%,760px);
  /* two layers: a flat tint that pulls the picture into the band's palette, and
   the horizontal fade that dissolves its left edge into the dark. */
  background:
    linear-gradient(color-mix(in srgb, var(--tank) 20%, transparent),color-mix(in srgb, var(--tank) 34%, transparent)),
    linear-gradient(90deg,var(--tank) 0,color-mix(in srgb, var(--tank) 92%, transparent) 12%,
      color-mix(in srgb, var(--tank) 55%, transparent) 28%,color-mix(in srgb, var(--tank) 14%, transparent) 44%,transparent 58%);
}
/* the credit line; bottom:84px keeps it clear of the bottom wave */
.news-photo figcaption{
  position:absolute;right:16px;bottom:84px;z-index:1;
  font-size:10.5px;letter-spacing:.04em;color:color-mix(in srgb, var(--tank-ink) 62%, transparent);
  text-shadow:0 1px 10px color-mix(in srgb, var(--tank) 95%, transparent);
}
/* Degradation: an engine with neither mask syntax gets the straight-edged version.
   `left:auto` is what makes it straight: with left AND right set, the width is ignored. */
@supports not (mask-image:linear-gradient(#000,#000)){
  @supports not (-webkit-mask-image:linear-gradient(#000,#000)){
    .news-photo{top:0;bottom:0;left:auto;width:min(46%,760px)}
    .news-photo img,.news-photo::after{width:100%}
  }
}

/* ---------- the controls (the only interactive things in the band) ----------
   components/deck-ctrl.html: 04-news.js binds [data-news-prev], [data-news-next]
   and each dot's [data-news-go="<index>"], and marks the shown dot with
   [aria-current="true"] — the state this file styles. `html.js` is set by
   src/layout.html, so a page without the script never shows a dead control row. */
.news-ctrl{
  display:flex;align-items:center;justify-content:center;gap:10px;
  margin-top:auto;padding-top:34px;
}
html:not(.js) .news-ctrl{display:none}
.news-btn{
  flex:none;width:42px;height:42px;border-radius:50%;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  background:rgba(255,255,255,.05);border:1px solid var(--tank-line);color:var(--tank-ink);
  transition:background .25s,border-color .25s,color .25s,transform .25s;
}
.news-btn svg{width:16px;height:16px}
.news-btn:hover{background:var(--accent);border-color:var(--accent);color:#fff}
.news-btn:active{transform:translateY(1px)}
.news-dots{display:flex;align-items:center;margin:0 8px}
.news-dot{
  width:26px;height:26px;padding:0;background:none;border:0;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
}
.news-dot span{width:8px;height:8px;border-radius:50%;background:color-mix(in srgb, var(--tank-ink-dim) 32%, transparent);transition:background .25s,transform .25s}
.news-dot:hover span{background:color-mix(in srgb, var(--tank-ink-dim) 62%, transparent)}
.news-dot[aria-current="true"] span{background:var(--accent-bright);transform:scale(1.5)}

/* ---------- responsive ---------- */
@media (max-width:1100px){
  /* below a desktop the text would run into the transparent end of the fade —
     narrow both, don't overlap */
  .news-slide{grid-template-columns:minmax(0,520px)}
  /* narrow the PICTURE, never the backdrop box: the mask is sized to
     `.news-photo`, so a width there would squash the wave out of step with the caps */
  .news-photo img,.news-photo::after{width:min(44%,560px)}
}
@media (max-width:960px){
  /* no backdrop: below this the text needs every pixel of width */
  .band-news{min-height:0;padding:44px 0 40px}
  .band-news .sec-head{margin-bottom:20px}
  .news-slide{grid-template-columns:minmax(0,1fr)}
  .news-photo{display:none}
  .news-ctrl{margin-top:34px;padding-top:0}
}
@media (max-width:560px){
  .news-lede{font-size:15.5px}
}


/* ── 11b-chairs.css ── */
/* ============================================================
   Committee chairs — the committees-page sibling of the live readout
   (12a-proof.css): the same .glass-strip ground (01b-glass.css), hairlines and
   charge line, plain display (no links, hover or transitions). One column per
   committee; both roles at one size, the label carries the distinction.
   Markup components/chair-item.html, data site-committees.js → committees
   (title / chair / coChair); each column carries [data-reveal], hidden until
   02-reveal.js adds .revealed (16-reveal.css). The band furniture is the
   figures idiom in 08a-band.css, which sorts before this file, so the
   --fig-cols and padding set here win the cascade; a chairs column holds
   names, not numerals, so it takes no .figures-v/.figures-k.
   ============================================================ */
.chairs{padding:26px 0 22px}
.chairs-grid{--fig-cols:3}
.chairs-col{padding:12px 26px 14px}   /* column rules at the idiom's default inset */
.chairs-title{
  font-family:var(--font-body);font-size:11px;font-weight:600;
  letter-spacing:.12em;text-transform:uppercase;color:var(--accent-strong);
  margin-bottom:12px;
}
.chairs-row{display:flex;align-items:center;gap:11px;min-width:0}
.chairs-row + .chairs-row{margin-top:10px}
.chairs-avatar{border-radius:50%;display:block;flex:none}
.chairs-name{
  flex:1;min-width:0;
  font-family:var(--font-display);font-weight:600;color:var(--ink);
  font-size:15.5px;line-height:1.25;letter-spacing:-.01em;
}
.chairs-role{
  flex:none;font-size:10.5px;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;color:var(--faint);
}
.chairs .footnote{margin-top:16px;text-align:center}

/* ---------- fold ----------
   ≤960px: one committee per row, hairlines turn horizontal. */
@media (max-width:960px){
  .chairs-grid{--fig-cols:1}
  /* suppresses the column rule 08a-band.css draws on .figures-item */
  .chairs-col + .chairs-col::before{display:none}
  .chairs-col + .chairs-col::after{
    content:"";display:block;position:absolute;left:26px;right:26px;top:0;height:1px;background:var(--line);
  }
}


/* ── 12a-proof.css ── */
/* ============================================================
   Live readout — the hero's status bar: four plain display numbers on the
   seam between hero and argument (home · section.readout). Its ground is
   .glass-strip (01b-glass.css) and the band furniture (charge line, grid,
   column rules, numerals, labels) is the figures idiom in 08a-band.css, which
   sorts before this file, so what differs here wins the cascade. No links, no
   hover, no transitions. Markup components/readout-item.html, data
   site-index.js → communityStats; items carry [data-reveal] (02-reveal.js).
   The numerals are the .figures-v [data-live] spans 05-community.js rewrites
   with the live Discord/GitHub counts: text only, so nothing here may size to
   a digit count.
   ============================================================ */
.readout{padding:30px 0 26px}   /* taller than the other bands: it sits on the hero seam */
.readout-grid{--fig-cols:4}
/* the column rules sit deeper here than the idiom's default 10px */
.readout-item{padding:14px 26px 16px;text-align:center;--fig-rule-t:12px;--fig-rule-b:12px}
.readout-mark{display:flex;justify-content:center;margin-bottom:11px}
.readout-mark svg{width:19px;height:19px}   /* no opacity: the mark must be the
   exact tank colour, and a 90 % wash over the glass shifts both of them */
.readout-mark.dc svg{color:var(--pos)}   /* Discord · the yellow tank, exact */
.readout-mark.gh svg{color:var(--neg)}   /* GitHub · the red tank, exact */



/* ── 13-join.css ── */
/* ============================================================
   Join card — the closing conversion block of every page: the two
   external-platform buttons (Discord + GitHub), no form.
   Markup: the .join section of src/pages/*.html renders
   components/join-card.html, filled from the `join` key of
   src/data/sites-general.js. The card's pale tints follow the page
   theme (--card-wash/-edge/-crest, 01a-theme.css); inside it
   03-nav.css highlights the platform the page is about (yellow →
   Discord, red → GitHub, green → neither). Its blur is a recipe of
   its own, not one of the .glass-* utilities (01b-glass.css);
   15-fallbacks.css carries its no-backdrop-filter fallback.
   ============================================================ */
.join{padding:40px 0 110px}
/* the band's bottom wave cap (07-waves.css) hangs --wave-h below the band's box,
   past this section's 40px top padding, so it would show through the translucent card */
.band + .join{padding-top:calc(var(--wave-h) + 40px)}
.join-card{
  position:relative;overflow:hidden;text-align:center;
  background:linear-gradient(160deg,rgba(255,255,255,.6), color-mix(in srgb, var(--card-wash) 55%, transparent) 70%);
  -webkit-backdrop-filter:blur(12px) saturate(1.2);backdrop-filter:blur(12px) saturate(1.2);
  border:1px solid var(--card-edge);border-radius:28px;padding:76px 36px 70px;box-shadow:var(--shadow);
}
.join-card .jwave{position:absolute;left:0;right:0;bottom:0;height:120px;opacity:.5;pointer-events:none}
.jwave-edge{fill:var(--card-edge)}
.jwave-crest{fill:var(--card-crest)}
.join-card h2{max-width:18ch;margin-inline:auto}
.join-card p{color:var(--muted);max-width:48ch;margin:14px auto 34px}
.join-btns{display:flex;gap:14px;justify-content:center;flex-wrap:wrap;position:relative;z-index:1}
.join-note{margin-top:18px;font-size:12.5px;color:var(--faint);position:relative;z-index:1}
.join-note b{color:var(--accent-deep);font-weight:600;font-variant-numeric:tabular-nums}


/* ── 14-footer.css ── */
/* ============================================================
   Footer — the tank the electrolyte returns to.
   Renders components/footer.html at the bottom of every page.
   Colours are the page theme's --tank-* (01a-theme.css); the wave is the
   shared components/wave-top.html partial, whose .wave-foam/.wave-body
   paths pick the fill up in 07-waves.css.
   Its link columns are not authored here: build.js derives footerCols from
   the nav array of sites-general.js (docs/authoring.md §2b), so this file
   styles a VARIABLE number of columns — see .foot-grid.
   06-year.js rewrites the [data-footer-year] span at load.
   ============================================================ */
footer{position:relative;z-index:2;background:var(--tank);color:var(--tank-ink);padding:64px 0 36px}
footer .brand{color:#fff}
/* the same artwork as the nav (src/assets/img/logo.svg), one notch smaller;
   its light plate is what makes it read on the dark ground */
footer .brand .logo{width:30px;height:30px}
.foot-grid{display:flex;flex-wrap:wrap;gap:40px 36px;margin-bottom:48px}
/* Columns are WRAPPED, not counted: the footer renders one column per menu
   group (build.js derives them from sites-general.js → nav), so how many there are is a
   content decision, made in the data. A track list would have to be recounted
   by hand the day a fifth group appears; flex-basis + wrap cannot go stale. */
.foot-brand{flex:1 1 260px;min-width:0}
.foot-col{flex:1 1 165px;min-width:0}
.foot-brand p{font-size:14px;color:var(--tank-ink-dim);max-width:34ch;margin-top:14px}
footer h4{font-size:12px;letter-spacing:.13em;text-transform:uppercase;color:var(--tank-ink-faint);margin-bottom:16px;font-family:var(--font-body)}
footer ul{list-style:none;display:grid;gap:10px}
footer ul a{font-size:14px;color:var(--tank-ink-dim);text-decoration:none;transition:color .2s}
footer ul a:hover{color:#fff}
/* Every row in a column — the group's own landing page ("Overview") included —
   is styled uniformly by the `footer ul a` rule above: the landing row is the
   first link of the list, not a repeat of the column heading. */
/* a soon:true menu row reaches here too (build.js copies the flags). The pill
   itself is 03a-navmenu.css's .soon-pill — one GitHub red site-wide; this only
   lays it out inline, since outside a flex row its margin-left:auto is 0. */
footer .foot-soon{font-size:14px;color:var(--tank-ink-faint)}
footer .soon-pill{margin-left:8px}
/* Breakpoints are DELIBERATE, not left to the wrap: with five shrinkable
   columns a natural wrap can leave the last one alone on its row, stretched
   to the full width — which looks like a bug at some viewport widths and not
   at others. So each step is pinned, and the steps are even:
   wide      brand beside the four link columns
   ≤1100px   brand becomes a masthead, the four columns keep one row
   ≤760px    columns 2-up
   ≤460px    one column per row */
@media (max-width:1100px){
  .foot-brand{flex:1 1 100%}
}
@media (max-width:760px){
  .foot-col{flex:1 1 calc(50% - 18px)}
}
@media (max-width:460px){
  .foot-brand,.foot-col{flex:1 1 100%}
  .foot-grid{gap:32px}
}
.foot-bottom{border-top:1px solid var(--tank-line);padding-top:26px;display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;font-size:12.5px;color:var(--tank-ink-faint)}
.foot-bottom .lic b{color:var(--tank-ink);font-weight:600}


/* ── 15-fallbacks.css ── */
/* ============================================================
   No-backdrop-filter fallback — degrades the glass surfaces to
   opaque where the property is unsupported (Firefox < 103 and
   any engine without it).
   Scope rule: it covers the fixed nav bar, the three .glass-*
   utilities of 01b-glass.css, the translucent repo pills and head-line
   circles, and the join card — a new glass element needs a
   fallback line only when it introduces its own surface.
   ============================================================ */
@supports not ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){
  /* the fixed bar in 03-nav.css is 82% opaque: 18% of the scroll shows through */
  nav{background:var(--bg)}
  /* 55% white with nothing blurring it leaves the content behind readable through the card */
  .glass-card{background:var(--paper);border-color:var(--line)}
  .glass-strip{background:var(--paper)}
  .glass-wash{background:linear-gradient(180deg,var(--bg),var(--mint-soft) 55%,var(--bg))}
  .repo-cat .tag,.repo-h .tag,.repo-tag{background:var(--paper)}
  /* the head-line circles take .rail-btn's .65 white fill (17-rail.css) */
  .repo-btn{background:var(--paper)}
  .join-card{background:linear-gradient(160deg,#fff,var(--card-wash) 70%)}
}


/* ── 16-reveal.css ── */
/* ============================================================
   Reveal-on-scroll — the hidden state for [data-reveal] elements.
   Every rule is gated on html.js, the class the inline script in
   src/layout.html adds before the body parses: without JS these
   selectors never match and nothing is hidden. 02-reveal.js adds
   .revealed as an element enters the viewport; the transition
   delay it sets from data-delay comes from the `delay` field (or
   an @index stagger) authored in src/data/*.js and rendered by
   the components. 19-motion.css cancels the effect entirely under
   reduced motion.
   ============================================================ */
html.js [data-reveal]{opacity:0;transform:translateY(22px);transition:opacity .7s ease,transform .7s cubic-bezier(.22,.61,.36,1)}
html.js [data-reveal].revealed{opacity:1;transform:none}


/* ── 17-page.css ── */
/* ============================================================
   Sub-pages — the shared furniture of every page after the home:
   resources.html and committees.html. Built from the home's components, so
   almost no styles of their own.
   .page-hero is components/page-hero.html, rendered from each page's own
   front matter: `h1` (may carry a <span class="grad">), `sub`, and the
   optional `chip`; the chip itself is styled in 05-hero.css.
   .rule-list / .rule-item is components/committee-row.html, one row per
   entry of the `committees` array in src/data/site-committees.js.
   ============================================================ */

/* ---------- the hero: components/page-hero.html ---------- */
.page-hero{position:relative;padding:150px 0 64px;overflow:hidden}
/* above the dot pattern and the corner glow (05-hero.css) — the home hero does
   the same with .hero-grid{z-index:2} */
.page-hero .wrap{position:relative;z-index:1}
.page-hero::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background-image:radial-gradient(rgba(12,38,29,.055) 1px,transparent 1.5px);
  background-size:26px 26px;
  -webkit-mask-image:radial-gradient(ellipse 90% 70% at 60% 20%,#000 0%,transparent 70%);
  mask-image:radial-gradient(ellipse 90% 70% at 60% 20%,#000 0%,transparent 70%);
}
.page-hero h1{font-size:clamp(2.4rem,5vw,3.7rem);margin-bottom:20px}
.page-hero h1 .grad{
  /* same gold → red ramp as the home hero (05-hero.css) */
  background:linear-gradient(100deg,var(--pos-deep),var(--neg-deep));
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
.page-hero .sub{color:var(--muted);font-size:clamp(1.02rem,1.6vw,1.16rem);max-width:56ch}

/* ---------- the committee rows: components/committee-row.html ----------
   One row per committee, fed by `committees` in src/data/site-committees.js;
   the ground is .glass-card (01b-glass.css). */
.rule-list{display:grid;gap:14px;margin-top:36px}
.rule-item{
  padding:22px 26px;box-shadow:var(--shadow-soft);
  display:grid;grid-template-columns:auto 1fr;gap:6px 22px;align-items:baseline;
}
.rule-item .num{font-family:var(--font-display);font-weight:700;font-size:13px;color:var(--accent);letter-spacing:.1em}
.rule-item h3{grid-column:2;margin-bottom:4px}
.rule-item p{grid-column:2;font-size:14.5px;color:var(--muted)}
/* meeting badges — hairline pills, information not UI (no links, no hover) */
.rule-item .meetings{grid-column:2;display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
.meeting{
  display:inline-flex;align-items:baseline;gap:8px;
  padding:5px 13px;border:1px solid var(--line);border-radius:999px;
  background:rgba(255,255,255,.65);
  font-size:12.5px;font-weight:600;color:var(--ink);
}
.meeting-k{font-size:10px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--faint)}


/* ── 17-rail.css ── */
/* ============================================================
   The rail — the shared horizontal scroller: a head line with a pair of
   arrows, a snap-scrolling row of items, and a note that holds the row's
   box when it is empty. Two instances wear it: the repository list
   (components/repo-group.html, resources.html, sized by 17a-repos.css) and
   the committee minutes (components/minutes-group.html, committees.html,
   sized by 17b-minutes.css). 07-rail.js owns the arrows: window.fbcRail
   pages by one measured item and sets [disabled] on them, and the control
   row only shows under the `html.js` gate src/layout.html sets. Numbered
   17-rail so it sorts after 17-page.css and before its instances: at equal
   specificity the later file wins, so an instance overrides the utility.
   ============================================================ */

/* ---------- the scroller ----------
   overscroll-behavior-x:contain stops a flick at either end from dragging the
   page sideways; the scrollbar is hidden because the arrows already say "more
   to come". The snap pairs with `scroll-snap-align:start` on the items, and
   07-rail.js measures this row's column gap (--rail-gap, 17a-repos.css) into
   its paging step. */
.rail{list-style:none;display:flex;align-items:stretch;overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;scrollbar-width:none;-ms-overflow-style:none}
.rail::-webkit-scrollbar{display:none}
.rail[hidden]{display:none}                 /* display:flex outranks the UA [hidden]; 07a-minutes.js hides an emptied rail */

/* ---------- the head line ----------
   One wrapping row for the heading and the arrows: .rail-t needs
   min-width:0, or the flex item cannot shrink below its own text and a
   long name overflows instead of moving the arrows. Instances retune the
   12px gap (17a-repos.css .repo-t, 17b-minutes.css .minutes-h). */
.rail-h{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.rail-t{display:flex;align-items:center;gap:12px;flex-wrap:wrap;min-width:0}

/* ---------- the control row ----------
   margin-left:auto pushes the arrow pair to the right edge of the head line. */
.rail-ctrl{margin-left:auto;display:flex;align-items:center;gap:8px}
/* the arrows page nothing without 07-rail.js; 17a-repos.css narrows this hide to
   the arrow buttons, because that row also carries the GitHub escape-hatch link */
html:not(.js) .rail-ctrl{display:none}

/* ---------- the circle controls ----------
   The rail arrows and the head-line GitHub escape-hatch link share this box: it is
   always rendered, because controls that come and go make the head line jump, so
   07-rail.js moves only [disabled]. The box is --rail-btn, 32px by default —
   17a-repos.css takes that default, 17b-minutes.css raises it to 34px. */
.rail-btn{flex:none;width:var(--rail-btn,32px);height:var(--rail-btn,32px);border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.65);border:1px solid var(--line);color:var(--accent-strong);transition:background .25s,border-color .25s,color .25s,opacity .25s}
/* the icons.js marks inside the circle: the chevrons carry the family stroke 2.4,
   drawn smaller and lighter here — one home for both rails, so they match */
.rail-btn svg{width:15px;height:15px;stroke-width:2.2}
.rail-btn:hover{background:var(--accent-deep);border-color:var(--accent-deep);color:#fff}
.rail-btn[disabled]{opacity:.32;cursor:default;pointer-events:none}

/* ---------- the empty-state note ----------
   The note is what keeps an emptied rail from collapsing the page: the
   height it reserves is the instance's — --repo-h in 17a-repos.css,
   --minute-h + --rail-pad in 17b-minutes.css. */
.rail-note{font-size:14px;color:var(--faint)}


/* ── 17a-repos.css ── */
/* ============================================================
   resources.html — the category band (#categories) and the live list
   (#resources): one horizontal rail of repository cards per topic.
   Markup: components/repo-cat.html (band tile), components/repo-group.html
   (rail head and rail) and the repo-*-tpl templates in pages/resources.html
   (card, loading, empty, error). Icons: the externalGo and fork marks in
   src/data/icons.js. Data: the `categories` array in
   src/data/sites-general.js; the repository payload is built at runtime by
   src/assets/js/07-repos.js, so every rule here must also hold while GitHub
   has not answered. State hooked: .repo-cat.is-empty and [data-repo-count]
   (07-repos.js), the arrows' [disabled] (07-rail.js via window.fbcRail).
   Numbered 17a: after 17-page.css, before 18-responsive.css.
   ============================================================ */

/* ---------- the hashtag pill (band tile, rail head, card tag row) ----------
   The .65 white fill is .rail-btn's (17-rail.css): pill and arrow are one family. */
.repo-cat .tag,
.repo-h .tag,
.repo-tag{
  display:inline-flex;align-items:baseline;
  padding:3px 11px;border:1px solid var(--line);border-radius:999px;
  background:rgba(255,255,255,.65);
  font-family:var(--font-body);font-size:11.5px;font-weight:600;
  letter-spacing:.02em;color:var(--muted);
}

/* ---------- 1 · the category band (the figures idiom, 08a-band.css) ----------
   THE STRIP IS THE SECTION, like .readout (12a-proof.css) and .chairs
   (11b-chairs.css): the .glass-strip ground (01b-glass.css), its hairlines and
   the charge line span the full width, the six columns inside .wrap. The glass
   goes on the section, never on the grid: there the band insets at the column. */
/* 08a-band.css owns the charge line, the grid, the column rules and the figure
   type; only --fig-cols, the padding and the numeral size are this band's own */
.repo-band{padding:26px 0 22px}   /* the same box .chairs takes (11b-chairs.css) */
.repo-cats{--fig-cols:6}   /* one column per entry in `categories` (src/data/sites-general.js) */
/* A grid item's automatic minimum size is its min-content, so a 1fr track cannot
   shrink below a tile's longest word and the strip overflows the wrap: free it. */
.repo-cats>*{min-width:0}
.repo-cat{padding:12px 22px 14px;text-align:center}   /* rules at the idiom's default inset */
/* the cell is a link but must not read as one: :not([class]) is the specificity
   `main a:not([class])` in 02-base.css claims; a hairline would rule the cell */
.repo-cat a:not([class]){display:block;text-decoration:none;color:inherit;border-bottom:none}
.repo-cat .ic{display:flex;justify-content:center;margin-bottom:11px;color:var(--accent-strong)}
.repo-cat .ic svg{width:19px;height:19px}   /* no opacity: the mark is the accent */
/* the live count, empty until 07-repos.js answers: min-height holds the six
   columns on one baseline so the band cannot jump; six to a row, so smaller */
.repo-cat .figures-v{display:block;font-size:clamp(1.7rem,2.6vw,2.3rem);min-height:1.05em}
.repo-cat .figures-k{display:block}   /* both marks are <span>s in repo-cat.html */
.repo-cat a:hover .k{color:var(--ink);text-decoration:underline}
.repo-cat .tag{margin-top:10px}
/* 07-repos.js toggles .is-empty when the live count comes back zero: the tile
   recedes but stays a valid anchor for the rail below it */
.repo-cat.is-empty{opacity:.5}
/* the sentence is authored in pages/resources.html, not in a data file */
.repo-band-note{margin-top:16px;text-align:center;font-size:12.5px;color:var(--faint)}
.repo-band-note b{font-weight:600;color:var(--muted)}

/* ---------- 2 · the live list: one rail per category ---------- */

/* ---------- the live-count summary (07-repos.js writes it) ---------- */
/* no padding override: the 96px section rhythm (08-sections.css) already
   separates the strip from the list below it */
.repo-summary{margin-top:16px;font-size:13px;color:var(--faint);font-variant-numeric:tabular-nums}
/* the count line exists only once GitHub has answered: :empty collapses it so
   the fallback link is never orphaned under a blank line */
.repo-summary span{display:block;margin-bottom:6px}
.repo-summary span:empty{display:none;margin-bottom:0}

/* ---------- the rails, stacked ----------
   One row per category, the minutes-rail shape (17-rail.css, whose other
   instance is 17b-minutes.css) with cards instead of documents: a new
   repository adds a card to a rail, not height to the page. No rule divides
   the rows — the cards are the objects, and a hairline would fight their borders. */
.repo-groups{display:grid;gap:44px;margin-top:46px}   /* plain whitespace, the .cat-grid rhythm (10-cards.css) */
/* The same automatic minimum size on every flex/grid child of the path: a head
   line or a card that cannot shrink below its own text widens the page. */
.repo-groups>*,.repo-h>*,.repo-side>*,.repo-rail>*{min-width:0}

/* ---------- the rail head: the category and its live count ----------
   .rail-h and .rail-t (17-rail.css): the category at the left, its escape
   hatch and its arrows pushed to the right edge. The heading holds TEXT only
   — the buttons are siblings in repo-group.html, so a screen reader does not
   read "more repositories" as part of the category's name. */
.repo-t{gap:10px}          /* tighter than .rail-t's 12px: icon, name and tag are one phrase */
.repo-t .ic{display:flex;color:var(--accent-strong)}
.repo-t .ic svg{width:18px;height:18px}
.repo-t .k{font-weight:600;letter-spacing:-.01em}
/* the same [data-repo-count] as the band tile, set small beside the name; it
   is empty until 07-repos.js answers, and then it collapses so no gap is left */
.repo-n{
  font-family:var(--font-display);font-weight:600;font-size:12.5px;
  color:var(--faint);font-variant-numeric:tabular-nums;
}
.repo-n::before{content:"·";margin-right:7px;color:var(--accent-bright)}
.repo-n:empty{display:none}
/* ---------- the arrows and the GitHub escape hatch ----------
   All three are .rail-btn circles (17-rail.css), so the head line reads as one
   control row. --neg (GitHub red) belongs to the card links below and nowhere
   else on this page, the rule 10b-categories.css sets; every other mark takes
   --accent-strong. */
html:not(.js) .repo-side{display:flex}
/* the rail's blanket hide (17-rail.css) is narrowed here: the arrows are JS-only,
   the GitHub mark in the same .rail-ctrl is not — so it hides by element, not class */
html:not(.js) button.repo-btn{display:none}
/* the arrows are authored in repo-group.html and always rendered; the box, the
   hover fill and the [disabled] state 07-rail.js sets are .rail-btn's (17-rail.css) */
/* The mark sits inside the ground, not cut out of it: 20px in the 32px disc leaves a
   light ring, so the circle takes the arrows' polarity — dark mark on light ground. */
.repo-side>a.rail-btn svg{width:20px;height:20px}

/* ---------- the rail: the size chain that reserves the card height ----------
   Two name lines + three description lines + one meta line + three gaps + the
   inset, authored, not emergent from the text metrics: every card is then as
   tall as every other and the note reserves the same box, so the page cannot
   jump when GitHub answers. It hangs on the ROW: one number moves cards + note. */
.repo-group{
  --repo-name:20px;     /* one name line: 15px display type on a 20px box */
  --repo-desc:19px;     /* one description line: 13.5px on a 19px box */
  --repo-meta:14px;     /* one meta line: the 10.5px caps on a 14px box */
  --repo-tags:22px;     /* the pill row's box, and the line the fork label sits on */
  --repo-gap:8px;       /* the card's only vertical rhythm: three of these in --repo-h */
  --repo-inset:16px;    /* the card's padding-block, counted twice in --repo-h */
  --repo-pad:18px;      /* the card's padding-inline; the fork label keeps off it at both ends */
  --repo-mark:14px;     /* the fork glyph's box, and what the label steps left of it */
  --repo-tip-gap:8px;   /* label box to mark; the caret takes 5px of it */
  --repo-h:calc(var(--repo-name)*2 + var(--repo-desc)*3 + var(--repo-meta) + var(--repo-gap)*3 + var(--repo-inset)*2);
}
/* ---------- the card width: whole cards per view ----------
   --repo-w is the rail minus its gaps, divided by --repo-per, so a view holds
   exactly that many WHOLE cards; a fixed width saws the last one off at the
   rail's edge, and a sawn-off card reads as a narrow card. The paging step
   07-rail.js measures is one card plus one gap, so every snap lands flush. */
.repo-rail{
  --repo-per:4;         /* four whole cards at the desktop rail width; the breakpoints retune this */
  --rail-gap:14px;      /* 07-rail.js adds this gap to the measured paging step */
  --repo-w:calc((100% - (var(--repo-per) - 1) * var(--rail-gap)) / var(--repo-per));
  gap:var(--rail-gap);
  margin-top:14px;padding:10px 0 16px;
}
/* .rail (17-rail.css) is the scroller, the snap and the hidden scrollbar; the
   padding gives a lifted card room, since an overflow box clips its content */
.repo{flex:0 0 var(--repo-w);scroll-snap-align:start}

/* ---------- one repository card ----------
   The surface is .glass-card (01b-glass.css), the one .cat wears: its blur is
   what lets the electrolyte stream behind the list read through the cards, and
   its hairline is the only separation they need — no rule divides two cards or
   two categories. Height comes from --repo-h; 15-fallbacks.css covers no-glass. */
.repo-link{
  display:block;height:100%;min-height:var(--repo-h);
  padding:var(--repo-inset) var(--repo-pad);
  position:relative;                  /* .glass-card already sets this (01b-glass.css); repeated because the fork label measures from it */
  text-decoration:none;color:inherit;
  transition:transform .3s cubic-bezier(.22,.61,.36,1),box-shadow .3s,border-color .3s;
}
/* the lift is .cat's; --shadow-soft and not --shadow: the scroller clips at its
   padding box, and --shadow reaches ~24px down, past the rail's bottom padding */
.repo-link:hover,.repo-link:focus-visible{
  transform:translateY(-5px);box-shadow:var(--shadow-soft);border-color:var(--accent-bright);
}
.repo-line{display:flex;align-items:flex-start;gap:8px;min-height:calc(var(--repo-name)*2)}   /* the two name lines --repo-h counts */
/* clamped to those two lines; 07-repos.js moves the cut text into the link's
   aria-label, so the clamp hides nothing from a screen reader */
.repo-name{
  flex:1 1 auto;min-width:0;
  font-family:var(--font-display);font-weight:600;
  font-size:15px;line-height:var(--repo-name);color:var(--ink);overflow-wrap:break-word;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden;
  transition:color .25s;
}
.repo-go{
  flex:none;display:flex;margin-top:4px;color:var(--faint);opacity:.55;   /* the arrow keeps its box; the name takes the squeeze */
  transition:transform .25s,color .25s,opacity .25s;
}
.repo-go svg{width:13px;height:13px;stroke-width:2.2}   /* the external mark from icons.js, 2.2 as in .rail-btn (17-rail.css) */
.repo-desc{
  height:calc(var(--repo-desc)*3);margin-top:var(--repo-gap);   /* the three lines --repo-h reserves */
  font-size:13.5px;line-height:var(--repo-desc);color:var(--muted);overflow:hidden;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-clamp:3;
  transition:color .25s;
}
/* display:block is load-bearing: this is a <span>, and an inline box ignores
   height, vertical margin and overflow, so it could not ellipsize at all */
.repo-meta{
  display:block;
  height:var(--repo-meta);margin-top:var(--repo-gap);line-height:var(--repo-meta);
  font-size:10.5px;font-weight:600;letter-spacing:.1em;
  text-transform:uppercase;color:var(--faint);font-variant-numeric:tabular-nums;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
/* every site topic the repository is filed under, INCLUDING the one it stands
   in, else a single-topic repo shows an empty row; 07-repos.js caps at MAX_PILLS */
.repo-tags{display:flex;flex-wrap:wrap;gap:6px;margin-top:var(--repo-gap)}
.repo-tags:empty{display:none}   /* 07-repos.js drops an unfilled pill row; this is the same case from CSS */
.repo-tag{font-size:10.5px;padding:2px 9px}
.repo-tag.is-new{border-color:var(--accent-bright);color:var(--accent-strong)}   /* 07-repos.js adds .is-new under NEW_DAYS */

/* ---------- the fork mark, at the right end of that same row ----------
   A forked repository is filed here but written elsewhere, and the list call only
   says fork:true — the origin comes from a second call (07-repos.js resolveForks),
   so the mark appears with the card and the name arrives a moment later.
   order:1 outranks the pills' implicit 0 and margin-left:auto takes the free space,
   so the mark sits in the card's lower right corner whether the pills wrap or not. */
.repo-fork{
  order:1;flex:none;margin-left:auto;
  display:inline-flex;align-items:center;color:var(--faint);opacity:.7;
  transition:color .25s,opacity .25s;
}
.repo-fork svg{width:var(--repo-mark);height:var(--repo-mark);stroke-width:2.2}   /* icons.js fork, 2.2 as in .repo-go */
/* the label is the mark's child and inherits its opacity, so keyboard focus has to
   lift the mark too — otherwise the label opens washed out at .7 */
.repo-fork:hover,
.repo-link:focus-visible .repo-fork{color:var(--accent-strong);opacity:1}
/* The name is a label, not a link: the card already is one, and an anchor inside
   an anchor is invalid markup. It opens on the mark's own hover, and on the card's
   keyboard focus, because the mark is not a tab stop of its own. A touch visitor
   gets no hover — 07-repos.js puts the same name in the link's aria-label.
   It measures from the CARD, not from the mark: a 14px containing block leaves an
   absolutely positioned box no room to size itself in, and the label then grows
   past the card and the rail clips it.
   It sits LEFT of the mark, on the pill row's own line, and it takes that whole
   line: left and right together stretch it across the text column — left flush
   with the column's edge, right one --repo-tip-gap from the glyph — so it covers
   the pills rather than landing on top of them. bottom puts it on the row's
   midline and translateY(50%) centres it there, so it holds the mark's line
   whatever the label's own type does; the 4px entrance shares that transform.
   Both edges are the card's, so the label can never reach a neighbour card or the
   scroller's edge, where it would be cut off. A name too long for the column
   ellipsizes, and the link's aria-label still carries the whole of it. */
.repo-fork-name{
  position:absolute;z-index:2;
  left:var(--repo-pad);
  right:calc(var(--repo-pad) + var(--repo-mark) + var(--repo-tip-gap));
  bottom:calc(var(--repo-inset) + var(--repo-tags)/2);
  transform:translate(4px,50%);          /* 50% centres on the row, 4px is the entrance */
  padding:4px 9px;border-radius:8px;
  background:var(--ink);color:var(--paper);
  font-family:var(--font-body);font-size:11px;font-weight:600;letter-spacing:.01em;
  line-height:1.35;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  box-shadow:var(--shadow-soft);
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .18s,transform .18s,visibility .18s;
}
/* inside the card on purpose: .rail is a scroller, and anything reaching past its
   padding box is cut off — the pill row is the last row, so the label's line is
   still card */
.repo-fork-name::after{   /* the caret, pointing right at the mark it names */
  content:"";position:absolute;left:100%;top:50%;margin-top:-5px;
  border:5px solid transparent;border-left-color:var(--ink);border-right:0;
}
.repo-fork:hover .repo-fork-name,
.repo-link:focus-visible .repo-fork-name{opacity:1;visibility:visible;transform:translateY(50%)}
.repo-fork-name:empty{display:none}   /* no origin resolved: the mark stands alone, no empty bubble */
/* a card is a link out of the site, so it may answer the cursor: name to --neg,
   description to ink, the arrow steps out of its .55 opacity */
.repo-link:hover .repo-name,.repo-link:focus-visible .repo-name{color:var(--neg)}
.repo-link:hover .repo-desc,.repo-link:focus-visible .repo-desc{color:var(--ink)}
.repo-link:hover .repo-go,.repo-link:focus-visible .repo-go{transform:translate(3px,-3px);opacity:1;color:var(--neg)}

/* ---------- the honest states: loading, empty, error, no-JS ----------
   repo-group.html ships the placeholder note; 07-repos.js replaces it with the
   repo-*-tpl notes (pages/resources.html) or with cards. Each note inside a
   rail reserves the card's box (--repo-h), so the row keeps its height in every
   state — and a note stays plain text: a note is not a repository. */
.repo-note{line-height:1.6;max-width:70ch}   /* a note is prose: it takes a reading measure */
.repo-rail .repo-note{flex:0 0 auto;min-width:0;padding:var(--repo-inset) 0;min-height:var(--repo-h)}
/* the no-JS note sits under the whole list, not in a rail, so it closes the
   section with a hairline instead of reserving a card box */
.repos > .wrap > noscript .repo-note{border-top:1px solid var(--line);margin-top:34px}

/* ---------- responsive (this file owns its breakpoints, docs/authoring.md §3) ----------
   Six columns is four-and-a-half on a laptop and two on a phone; the rules move
   with the columns (11b-chairs.css folds the same way, to one column). */
@media (max-width:1100px){
  .repo-cats{--fig-cols:3}   /* six columns do not fit the laptop width: two rows of three */
  /* the column rules move with the grid: none where a row starts, and the
     22px inset matches the cell's own padding */
  .repo-cat:nth-child(3n+1)::before{display:none}
  .repo-cat:nth-child(n+4)::after{
    content:"";position:absolute;left:22px;right:22px;top:0;height:1px;background:var(--line);
  }
}
@media (max-width:960px){
  .repo-rail{--repo-per:3}   /* the site's NARROW fold (04-stream.css); 07-rail.js re-measures the step */
}
@media (max-width:640px){
  .repo-band{padding:20px 0 16px}
  .repo-cats{--fig-cols:2}
  .repo-cat{padding:12px 14px}
  /* both selectors are (0,2,1) like the base rule in 08a-band.css, so order —
     not specificity — decides; only the second column of a row keeps one */
  .repo-cat + .repo-cat::before{display:none}
  .repo-cat:nth-child(2n)::before{display:block}
  .repo-cat:nth-child(n+3)::after{   /* the 14px inset follows the cell's padding above */
    content:"";position:absolute;left:14px;right:14px;top:0;height:1px;background:var(--line);
  }
  .repo-cat .figures-v{font-size:1.6rem}   /* below the clamp's 1.7rem floor: two columns cannot hold it */
  .repo-groups{gap:36px;margin-top:36px}
  /* the head line wraps: the category name takes the first line, the escape hatch
     and the arrows the second, still pushed to the right edge */
  .repo-h{gap:9px}
  .repo-t{gap:8px}
  .repo-rail{--repo-per:2;--rail-gap:12px}
  .repo-group{--repo-inset:14px;--repo-pad:16px}   /* the one numbers the size chain hangs on */
  .repo-name{font-size:14px}
  .repo-desc{font-size:13px}   /* the type shrinks, the reserved line boxes do not */
}
/* two cards is 164px each below 480px — too narrow to read three lines of
   description in; one card across makes a swipe move exactly one repository. */
@media (max-width:480px){
  .repo-rail{--repo-per:1}
}


/* ── 17b-minutes.css ── */
/* ============================================================
   Committee minutes rail — committees.html #minutes: one hairline row per
   committee, its published documents in a rail that snaps entry by entry.
   Markup: components/minutes-group.html (row, head line, arrows, note) and
   components/minute-item.html (one entry), from committees[] and minutes[]
   in data/site-committees.js. State: 07a-minutes.js filters by year off
   data-minute-year — never data-year, which 06-year.js claims site-wide —
   and 07-rail.js (window.fbcRail) pages each rail and moves [disabled].
   The ground is .glass-wash (01b-glass.css), the surface the platforms band
   wears too (10a-platforms.css); the hairlines are .rows (08-sections.css)
   and the scroller 17-rail.css. Numbered 17b, before 18-responsive.css (docs/authoring.md §3).
   ============================================================ */
/* ---------- the rail's measures ----------
   One entry is glyph + gap + date line + inset, and that sum is a row's
   content box: AUTHORED, not left to the text metrics, so the note can
   reserve the same box and a row emptied by the year filter keeps its
   height. Change a number here and both states move together. */
.minutes{
  --minute-glyph:34px;      /* the drawn document mark (icons.js doc) */
  --minute-gap:16px;        /* glyph → date */
  --minute-line:26px;       /* fixed so --minute-h is not a font accident */
  --minute-inset:2px;       /* .minute-link vertical padding */
  --minute-h:calc(var(--minute-glyph) + var(--minute-gap) + var(--minute-line) + var(--minute-inset)*2);
  --rail-pad:16px;          /* .minutes-rail padding-top + padding-bottom */
  --row-gap:16px;           /* head line → content, rail and note alike */
  --rail-btn:34px;          /* the idiom's 32px (17-rail.css) one step up */
}
.minutes .sec-head{margin-bottom:24px}   /* the shared .sec-head space is 56px (08-sections.css) */

/* ---------- the rows ----------
   The .rows utility (08-sections.css) draws the hairlines; there are no
   boxes and no shadows — the section is set apart by its ground, not chrome. */
.minutes-group{padding:26px 0}

/* .rail-h / .rail-t (17-rail.css) with the minutes' own gap; the heading
   holds text only, the arrows are its siblings (minutes-group.html). */
.minutes-h,.minutes-t{gap:13px}
.minutes-h .num{
  font-family:var(--font-display);font-weight:700;font-size:12.5px;
  letter-spacing:.1em;color:var(--accent);
}
.minutes-h .k{
  font-family:var(--font-display);font-weight:600;font-size:1.06rem;
  letter-spacing:-.01em;color:var(--ink);
}
/* ---------- the controls ----------
   The arrows need no rule here: box, hover fill and [disabled] are .rail-btn
   (17-rail.css), and 07-rail.js only moves [disabled], so the head line never
   changes shape. Both controls are markup (minutes-group.html, and the menu
   in pages/committees.html); without JS both hide and the rail is a plain list. */
html:not(.js) .minutes-bar{display:none}

/* ---------- the year menu ----------
   ONE menu for the whole section, above the rows and authored in
   pages/committees.html — never inside a row's .minutes-ctrl, where it would
   vanish with the row it filters. A native <select> is the whole control
   (keyboard, screen reader, mobile picker), worn in the badge pill of 17-page.css. */
.minutes-bar{display:flex;align-items:center;justify-content:flex-end;gap:9px;margin-bottom:14px}
.minutes-bar-k{
  font-size:10px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;
  color:var(--faint);
}
.minutes-select{position:relative;display:inline-flex;align-items:center}
.minutes-year{
  -webkit-appearance:none;appearance:none;   /* the native arrow is replaced by .minutes-caret */
  font-family:var(--font-body);font-size:12.5px;font-weight:600;line-height:1.1;
  color:var(--ink);cursor:pointer;
  padding:6px 30px 6px 13px;border:1px solid var(--line);border-radius:999px;   /* the 30px clears the caret */
  background:rgba(255,255,255,.65);
  transition:border-color .25s,color .25s;
}
.minutes-year:hover{border-color:var(--accent-bright);color:var(--accent-strong)}
.minutes-caret{position:absolute;right:11px;pointer-events:none;color:var(--faint)}   /* inside the select's right padding, out of its hit area */
.minutes-caret svg{width:11px;height:11px;stroke-width:2.2}   /* icons.js caret, sized for this pill */
.minutes-year:hover + .minutes-caret{color:var(--accent-strong)}

/* ---------- the emptied row ----------
   A committee with nothing in the chosen year keeps its head: 07a-minutes.js
   hides the rail and shows the note, which reserves the rail's box, so
   filtering changes text and not layout. The note declares no display, so
   this is not the rail's [hidden] trap (17-rail.css); JS writes no text. */
.minutes-note{
  margin-top:var(--row-gap);padding-top:6px;   /* the rail's padding-top, so the
                                                  words start under the first glyph */
  min-height:calc(var(--minute-h) + var(--rail-pad));   /* the rail's box */
}
.minutes-note[hidden]{display:none}

/* ---------- the rail ----------
   The scroller, the snap, the hidden scrollbar and the rail's own [hidden]
   rule are .rail (17-rail.css); this file keeps only its measures. A fixed
   entry width lets the next entry peek past the right edge — that is the
   affordance, so no fade mask is needed. */
.minutes-rail{
  --minute-w:250px;   /* the paging step: 07-rail.js measures the first visible entry */
  margin-top:var(--row-gap);padding:6px 0 10px;   /* 6 + 10 = --rail-pad */
}
.minute{flex:0 0 var(--minute-w);scroll-snap-align:start}
.minute[hidden]{display:none}   /* a filtered-out entry leaves no gap */
.minute + .minute{border-left:1px solid var(--line)}

/* ---------- one published document ----------
   Glyph above the date, and the whole entry is the link. The first entry
   drops its left inset so its glyph lines up with the committee head above
   it; every later entry is inset from the hairline it starts behind. */
.minute-link{
  display:grid;align-content:start;gap:var(--minute-gap);height:100%;
  padding:var(--minute-inset) 26px;text-decoration:none;color:inherit;
}
.minute:first-child .minute-link{padding-left:0}
.minute:last-child .minute-link{padding-right:0}
.minute-ic{
  display:flex;color:var(--accent);
  transition:transform .35s cubic-bezier(.22,.61,.36,1),color .25s;
}
.minute-ic svg{width:var(--minute-glyph);height:var(--minute-glyph);stroke-width:1.6}  /* icons.js doc, thinned for its size */
.minute-line{display:flex;align-items:baseline;gap:9px;min-height:var(--minute-line)}
.minute-date{
  font-family:var(--font-display);font-weight:600;font-size:15.5px;
  letter-spacing:-.01em;color:var(--ink);font-variant-numeric:tabular-nums;
  transition:color .25s;
}
/* the corner arrow stays WITH the date: pinned to the right edge of a 250px
   entry it would read as a separate column and leave a void behind the date. */
.minute-go{
  display:flex;color:var(--faint);opacity:.55;
  transition:transform .25s,color .25s,opacity .25s;
}
.minute-go svg{width:13px;height:13px;stroke-width:2.2}   /* icons.js externalGo, set on the date's baseline */
/* an entry IS a link, so it answers the pointer; :focus-visible shares every
   rule below, so the keyboard path cannot drift from the hover states. */
.minute-link:hover .minute-ic,.minute-link:focus-visible .minute-ic{transform:translateY(-3px);color:var(--accent-deep)}
.minute-link:hover .minute-date,.minute-link:focus-visible .minute-date{color:var(--accent-strong)}
.minute-link:hover .minute-go,.minute-link:focus-visible .minute-go{transform:translate(3px,-3px);opacity:1;color:var(--accent-strong)}

/* ---------- responsive ----------
   The site's thresholds: 960px is the narrow fold (18-responsive.css, the
   NARROW constant in 01-stream.js), 560px the single column. */
@media (max-width:960px){
  .minutes-group{padding:22px 0}
  .minutes-rail{--minute-w:220px}
}
@media (max-width:560px){
  .minutes{--minute-glyph:30px;--minute-gap:13px;--row-gap:14px;--rail-btn:32px}  /* --minute-h follows */
  .minutes-h,.minutes-t{gap:10px}
  .minutes-h .k{font-size:1rem}
  .minutes-bar{margin-bottom:12px}
  .minutes-year{font-size:12px;padding:5px 27px 5px 11px}
  .minutes-rail{--minute-w:190px}
  .minute-link{padding:var(--minute-inset) 20px}
  .minute-date{font-size:14.5px}
}


/* ── 17c-dashboard.css ── */
/* ============================================================
   Reproducibility dashboard band — styles the dark #dashboard band of resources.html:
   a photo backdrop, a three-figure row, one link out to flowbatterydata.org.
   Markup: components/dash-spot.html; content: the `dashboard` key of src/data/site-resources.js.
   No behaviour module; 02-reveal.js only fades the text blocks. The dark ground and
   ink are 11-band.css under this page's `theme: red` (01a-theme.css), the caps 07-waves.css.
   The WAVE-MASK:BEGIN/END block in .dash-photo is generated by scripts/gen-wave-mask.js;
   scripts/check.sh fails the build if it drifts, so never hand-edit it.
   Concatenated as 17c, between 17b-minutes.css and 18-responsive.css, so this file
   owns its breakpoints (docs/authoring.md §3).
   ============================================================ */

/* ---------- band layout ----------
   min-height, not a fixed height: a longer lede must push the band taller,
   never overflow it — the backdrop is absolutely positioned and needs that
   height to have anything to show. Same construction as .band-news
   (11a-news.css), one step shorter: this band holds one lede, not a deck. */
.band-dash{min-height:clamp(560px,70vh,760px)}
/* everything in the column opts back into the stacking order: an absolutely
   positioned z-index:0 element paints above in-flow content (as in 11a-news.css) */
.band-dash .wrap{position:relative;z-index:1}
/* the .sec-head default 56px (08-sections.css) suits a head above a card grid;
   three more blocks follow here, so the head sits closer to them */
.band-dash .sec-head{margin-bottom:30px}
/* the same 62ch as .news-lede (11a-news.css): one measure for both dark bands */
.band-dash .lede{max-width:62ch}

/* ---------- stat/fact row ----------
   The .figures-grid idiom of 08a-band.css: its grid, column rules and figure
   typography; ours are the size overrides, the insets and the dark ink.
   --fig-cols is what 08a-band.css reads; the 640px is the .sec-head measure
   (08-sections.css), so the row keeps the copy's left edge. */
.dash-facts{
  --fig-cols:3;
  max-width:640px;
}
/* --fig-rule-t/-b are the ends the column rule of 08a-band.css is drawn
   between; they follow this row's padding, so the hairline stops short of it */
.dash-fact{padding:16px 22px 2px;--fig-rule-t:14px;--fig-rule-b:6px}
.dash-fact:first-child{padding-left:0}
.dash-fact .figures-v{display:block;font-size:clamp(1.9rem,3vw,2.6rem);color:var(--tank-ink)}
/* the unit inside a figure is authored in the data (`facts[].v` in
   src/data/site-resources.js) as <span class="small"> and set as its caption */
.dash-fact .figures-v .small{
  font-size:.5em;font-weight:600;letter-spacing:.02em;
  color:var(--tank-ink-dim);white-space:nowrap;
}
.dash-fact .figures-k{display:block;margin-top:8px;font-size:11px;color:var(--tank-ink-faint)}

/* ---------- link list ---------- */
.dash-act{display:flex;align-items:baseline;gap:16px;flex-wrap:wrap;margin-top:30px}
/* the news band's link at the same weight (11a-news.css): --cta-gap widens
   the 6px default of .cta-arrow (08-sections.css) for the arrow mark */
.dash-link{
  --cta-gap:8px;
  font-size:14px;color:var(--accent-bright);
  border-bottom:1px solid color-mix(in srgb, var(--accent-bright) 35%, transparent);
  padding-bottom:3px;transition:color .25s,border-color .25s;
}
.dash-link:hover{color:#fff;border-color:#fff}
.dash-link svg{width:13px;height:13px}
/* where the link actually goes — the band leaves this site, so it says so */
.dash-host{font-size:12px;letter-spacing:.02em;color:var(--tank-ink-faint)}
/* the same 640px measure as the head and the figure row (08-sections.css) */
.band-dash .footnote{max-width:640px;margin-top:34px}

/* ---------- photo frame ----------
   Bleeds to the viewport's right edge and dissolves leftwards into the band's
   own colour, so it has no visible edge, and never decides the band's height. */

/* ---------- wave mask: generated, never hand-edit ----------
   Between WAVE-MASK:BEGIN and :END below, scripts/gen-wave-mask.js writes the
   union of upper cap, solid middle band and flipped lower cap; scripts/check.sh
   runs it in --check mode, so a hand-edited block fails the build. 11a-news.css
   carries the sibling block for .news-photo — the mask is sized to its own element. */
.dash-photo{
  position:absolute;left:0;right:0;top:calc(1px - var(--wave-h));bottom:calc(1px - var(--wave-h));
  z-index:0;margin:0;overflow:hidden;pointer-events:none;
  /* WAVE-MASK:BEGIN generated by scripts/gen-wave-mask.js — do not hand-edit */
  -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1440' height='70' viewBox='0 0 1440 70' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 70 L0 34 C160 12 340 52 520 34 C700 16 880 56 1060 36 C1220 18 1340 44 1440 28 L1440 70 Z'/%3E%3C/svg%3E"),linear-gradient(#000,#000),url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1440' height='70' viewBox='0 0 1440 70' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' transform='translate(0,70) scale(1,-1)' d='M0 70 L0 30 C180 52 360 12 540 32 C720 52 900 14 1080 34 C1240 52 1360 24 1440 40 L1440 70 Z'/%3E%3C/svg%3E");
  -webkit-mask-size:100% 70px,100% calc(100% - 138px),100% 70px;
  -webkit-mask-position:left top,0 69px,left bottom;
  -webkit-mask-repeat:no-repeat;
          mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1440' height='70' viewBox='0 0 1440 70' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' d='M0 70 L0 34 C160 12 340 52 520 34 C700 16 880 56 1060 36 C1220 18 1340 44 1440 28 L1440 70 Z'/%3E%3C/svg%3E"),linear-gradient(#000,#000),url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1440' height='70' viewBox='0 0 1440 70' preserveAspectRatio='none'%3E%3Cpath fill='%23fff' transform='translate(0,70) scale(1,-1)' d='M0 70 L0 30 C180 52 360 12 540 32 C720 52 900 14 1080 34 C1240 52 1360 24 1440 40 L1440 70 Z'/%3E%3C/svg%3E");
          mask-size:100% 70px,100% calc(100% - 138px),100% 70px;
          mask-position:left top,0 69px,left bottom;
          mask-repeat:no-repeat;
  /* WAVE-MASK:END */
}
.dash-photo img{
  /* the visible crop, capped like .news-photo img (11a-news.css): the data's w/h are
     overridden here and object-fit does the rest, so the file's ratio need not match the band's */
  position:absolute;top:0;bottom:0;right:0;width:min(48%,800px);height:100%;
  object-fit:cover;object-position:26% 0;   /* a dashboard is identified by its title bar and tabs, so the crop keeps the top-left */
}
.dash-photo::after{
  content:"";position:absolute;top:0;bottom:0;right:0;width:min(48%,800px);   /* the tint covers exactly the crop above */
  /* two layers: a flat tint that pulls the screenshot into the band's palette,
     and the horizontal fade that dissolves its left edge into the dark. */
  background:
    linear-gradient(color-mix(in srgb, var(--tank) 22%, transparent),color-mix(in srgb, var(--tank) 34%, transparent)),
    linear-gradient(90deg,var(--tank) 0,color-mix(in srgb, var(--tank) 92%, transparent) 10%,
      color-mix(in srgb, var(--tank) 58%, transparent) 26%,color-mix(in srgb, var(--tank) 16%, transparent) 42%,transparent 56%);
}
/* the credit line; bottom:84px clears the 70px bottom cap (--wave-h, 01-tokens.css) */
.dash-photo figcaption{
  position:absolute;right:16px;bottom:84px;z-index:1;
  font-size:10.5px;letter-spacing:.04em;color:color-mix(in srgb, var(--tank-ink) 62%, transparent);
  text-shadow:0 1px 10px color-mix(in srgb, var(--tank) 95%, transparent);
}
/* Degradation: an engine with neither mask syntax gets the straight-edged version.
   `left:auto` makes it straight — with left and right both set the width is ignored (same fallback as .news-photo, 11a-news.css). */
@supports not (mask-image:linear-gradient(#000,#000)){
  @supports not (-webkit-mask-image:linear-gradient(#000,#000)){
    .dash-photo{top:0;bottom:0;left:auto;width:min(48%,800px)}
    .dash-photo img,.dash-photo::after{width:100%}
  }
}

/* ---------- responsive ---------- */
@media (max-width:1100px){
  .dash-facts{max-width:560px}
  .dash-fact{padding:14px 20px 2px}
  .dash-fact:first-child{padding-left:0}
  /* narrow the PICTURE, never the backdrop box: the mask is sized to
     `.dash-photo`, so a width there would squash the wave out of step with the caps */
  .dash-photo img,.dash-photo::after{width:min(46%,600px)}
  .band-dash .sec-head{margin-bottom:26px}
}
@media (max-width:960px){
  /* no backdrop: below this the text needs every pixel of width, and a
     cropped screenshot of a dashboard is decoration, not information */
  .band-dash{min-height:0}
  .dash-photo{display:none}
  .dash-facts{max-width:none}
  .dash-act{margin-top:26px}
  .band-dash .footnote{margin-top:28px}
}
@media (max-width:560px){
  /* one figure per line: the column rules turn horizontal and the numerals
     shrink, so the row stays a footnote to the copy and not the reverse */
  .dash-facts{--fig-cols:1;row-gap:2px}
  .dash-fact{padding:14px 0 2px}
  /* suppresses the column rule 08a-band.css draws on .figures-item */
  .dash-fact + .dash-fact::before{display:none}
  .dash-fact + .dash-fact::after{
    content:"";position:absolute;left:0;right:0;top:0;height:1px;background:var(--tank-line);
  }
  .dash-fact .figures-v{font-size:1.7rem}
}


/* ── 18-responsive.css ── */
/* ============================================================
   Responsive — the page-level folds shared by every section.
   The stylesheet assumes three widths: 1100px (grids lose a
   column), 960px (the narrow threshold — 01-stream.js matches
   it with its NARROW constant and 04-stream.css with a 960px
   query; 03a-navmenu.css switches at min-width:961px) and
   560px (single column). Component-owned breakpoints live with
   their components; only blocks no component file owns are
   folded here.
   ============================================================ */
@media (max-width:1100px){
  /* the three-across default of .cat-grid is set in 10-cards.css */
  .cat-grid{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:960px){
  /* page-level blocks only: .hero is 05-hero.css, .band 11-band.css,
     .readout-item 12a-proof.css (its column rules live in 08a-band.css) */
  /* components own their responsive rules: the nav bar's breakpoints live in 03a-navmenu.css */
  .hero{padding-top:120px}
  .hero-grid{grid-template-columns:1fr;gap:44px}
  .page-hero{padding-top:120px}
  /* the news deck's own breakpoints live in 11a-news.css — same rule */
  .band{padding:96px 0 76px}
  .readout-item{padding:10px 14px 12px}
  .footnote{margin-top:20px}
  /* the footer's own breakpoints live in 14-footer.css — same rule */
  section{padding:72px 0}
}
@media (max-width:560px){
  .cat-grid{grid-template-columns:1fr}
  /* readout folds to 2×2: drop the rule on the new column edge and
     draw a horizontal one across the gap instead */
  .readout-grid{grid-template-columns:1fr 1fr;row-gap:18px}
  .readout-item:nth-child(3)::before{display:none}
  .readout-item:nth-child(n+3)::after{
    content:"";position:absolute;left:14px;right:14px;top:-10px;height:1px;background:var(--line);
  }
  .cta-row .btn{width:100%;justify-content:center}
  /* .rule-item's two-column default grid is set in 17-page.css */
  .rule-item{grid-template-columns:1fr}
  .rule-item h3,.rule-item p,.rule-item .meetings{grid-column:1}
}


/* ── 19-motion.css ── */
/* ============================================================
   Reduced motion — the site must be fully usable without
   animation. The blanket rule kills every transition and
   animation, including the reveal transition of 16-reveal.css,
   whose hidden state the last rule then overrides so content
   can never get stuck invisible. The behaviour scripts
   (01-stream.js, 02-reveal.js, 04-news.js) test the same media
   query and skip their animations outright.
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none!important;transition:none!important}
  html{scroll-behavior:auto}
  html.js [data-reveal]{opacity:1;transform:none}
}


/* ── 20-faq.css ── */
/* ============================================================
   FAQ — native <details>/<summary>: no JS, keyboard- and screen-reader-accessible.
   Rows divided by hairline rules only — the .rows utility (08-sections.css).
   Opening rotates the plus 45° into a cross; the circle stays put.
   Home page only (index.html → section#faq): markup components/faq-item.html,
   data site-index.js → faq (q / a / delay). Each row carries [data-reveal],
   hidden until 02-reveal.js adds .revealed (16-reveal.css); the open state is
   the native [open] attribute, which no script touches.
   ============================================================ */
.faq-list{display:grid}

.faq-item summary{
  list-style:none;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:28px;
  padding:32px 0;font-family:var(--font-display);font-weight:600;font-size:16.5px;line-height:1.45;color:var(--ink);
}
.faq-item summary::-webkit-details-marker{display:none}

.faq-item .plus{
  flex:none;display:flex;color:var(--accent-strong);
  transition:transform .3s cubic-bezier(.22,.61,.36,1);
}
.faq-item .plus svg{width:20px;height:20px;stroke-width:1.7}   /* the shared plus mark, sized here */
/* [open] after :hover, so a hovered open row keeps its cross */
.faq-item:hover .plus{transform:scale(1.08)}
.faq-item[open] .plus{transform:rotate(45deg)}
.faq-item[open]:hover .plus{transform:rotate(45deg) scale(1.08)}

.faq-item .faq-a{padding:0 0 32px;font-size:14.5px;line-height:1.7;color:var(--muted);max-width:78ch}

@media (max-width:700px){
  /* component-level breakpoint: kept here, not in 18-responsive.css */
  .faq-item summary{padding:24px 0;gap:20px;font-size:15.5px}
  .faq-item .faq-a{padding-bottom:24px}
}
