@font-face{font-family:'Inter';font-weight:400;font-style:normal;font-display:swap;src:url('fonts/inter-400.woff2') format('woff2')}
@font-face{font-family:'Inter';font-weight:600;font-style:normal;font-display:swap;src:url('fonts/inter-600.woff2') format('woff2')}
@font-face{font-family:'Inter';font-weight:700;font-style:normal;font-display:swap;src:url('fonts/inter-700.woff2') format('woff2')}
@font-face{font-family:'Source Serif 4';font-weight:400;font-style:normal;font-display:swap;src:url('fonts/sourceserif-400.woff2') format('woff2')}
@font-face{font-family:'Source Serif 4';font-weight:700;font-style:normal;font-display:swap;src:url('fonts/sourceserif-700.woff2') format('woff2')}
@font-face{font-family:'Space Grotesk';font-weight:500;font-style:normal;font-display:swap;src:url('fonts/spacegrotesk-500.woff2') format('woff2')}
@font-face{font-family:'Space Grotesk';font-weight:700;font-style:normal;font-display:swap;src:url('fonts/spacegrotesk-700.woff2') format('woff2')}
:root{--accent:#0004FF;--accent2:#E0479E;--accent-lite:#9ea0ff;--accent-dark:#00029e;--on-accent:#ffffff;--on-accent2:#111111;--on-accent-dark:#ffffff;--ink:#14141E;--muted:#6b6b6b;--prose:#303039;--line:#e7e3d9;--bg:#E4F2F0;--card:#fff;--gap:20px;--shadow:0 10px 40px -12px rgba(0,0,0,.18);--r:14px;--headf:'Mandali','Inter',system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;--bodyf:'Mandali','Inter',system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
html{font-size:100.0%}
body.soft{--card:var(--bg);--line:transparent;--r:22px;--shadow:8px 8px 20px rgba(0,0,0,.10),-8px -8px 20px rgba(255,255,255,.7)}
body.bold{--r:4px;--line:var(--ink);--shadow:8px 8px 0 var(--accent)}
body.editorial{--card:var(--bg);--line:color-mix(in srgb,var(--ink) 16%,transparent);--shadow:none;--r:2px}
body.editorial h1,body.editorial h2.section{font-weight:700;letter-spacing:-.01em}
body.editorial .prose{font-size:1.12rem}
body.glass{--card:color-mix(in srgb,var(--bg) 62%,transparent);--r:18px;--shadow:0 8px 32px -10px rgba(0,0,0,.28)}
body.glass .card,body.glass .band.card{backdrop-filter:blur(10px) saturate(1.3);border:1px solid color-mix(in srgb,var(--ink) 10%,transparent)}
/*preset:bold*/

*{box-sizing:border-box}
/* ── NOTHING LEAKS OUT OF ITS FRAME (2026-08-20) ─────────────────────────────
   Reported from a real page: an event summary containing
   `https://oregon.qualtrics.com/jfe/form/SV_4ZUS13TcKVQ2TRk` pushed its card
   wider than the grid cell and the text ran under its neighbour.

   Three separate causes, and all three have to be closed or the next long
   thing finds the one that is left:

   1. An unbreakable run (a URL, a long German compound, a file name) has no
      break opportunity, so the browser widens the box rather than the text.
      `overflow-wrap:anywhere` gives it permission to break mid-run.
   2. A CSS grid or flex CHILD has `min-width:auto` by default, which means
      "never smaller than my content" — so even a wrappable child refuses to
      shrink. `min-width:0` is the fix and it is needed on every track child,
      not just the ones that misbehave today.
   3. A replaced element (an image, an iframe, a table) ignores both. Those get
      `max-width:100%` and tables get their own scroller.

   Applied as a floor over the whole page rather than per-component: a rule you
   have to remember to add to each new block is a rule that will be forgotten,
   and the failure it prevents is one a visitor sees. */
.wrap,.card,.band,.band-inner,.prose,.tile,.hero-in,.wcol,.cards,.gallery{min-width:0}
/* NO `hyphens:auto`, and no `word-break:break-word` (2026-08-20, a regression
   fixed the day after it shipped). Both were added with `overflow-wrap` as a
   belt-and-braces guard against the long-URL leak, and both are wrong for
   ordinary prose:

     - `hyphens:auto` hyphenates on SYLLABLES, so "community" became
       "com-munity" mid-paragraph with plenty of room left on the line. A
       hyphen a reader did not write is a hyphen a reader has to parse.
     - `word-break:break-word` lets an ordinary word split anywhere, for the
       same non-reason.

   `overflow-wrap:anywhere` alone does the job it was brought in for: it only
   breaks a run that CANNOT otherwise fit — a 60-character URL in a 270px card
   — and leaves every normal word intact. The narrower rule was the correct one
   all along; the extra two were reflex. */
.card,.prose,.meta,.pullquote,.hero-in,.band-inner,.site-foot,.calwidget{
  overflow-wrap:anywhere}
.card>*,.band-inner>*{max-width:100%}
.card img,.band img,.prose img,.hero-in img{max-width:100%;height:auto}
/* a long link is the usual culprit and deserves to break before its container */
.card a,.prose a,.meta a{overflow-wrap:anywhere}
/* wide content scrolls INSIDE its frame rather than widening the page */
.card table,.prose table{display:block;max-width:100%;overflow-x:auto}
.prose pre{max-width:100%;overflow-x:auto}
/* and the page itself never scrolls sideways, whatever gets in */
html,body{max-width:100%;overflow-x:hidden}
html{scroll-behavior:smooth}
body{margin:0;color:var(--ink);background:var(--bg);
  font-family:var(--bodyf);
  line-height:1.6;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
/* page TEXTURE (appearance axis): a subtle self-contained pattern via gradients
   — off by default; dots / grid / diagonal hatch. Rides --line so it adapts to
   light/dark. */
body.tx-dots{background-image:radial-gradient(color-mix(in srgb,var(--ink) 8%,transparent) 1px,transparent 1px);background-size:22px 22px}
body.tx-grid{background-image:linear-gradient(color-mix(in srgb,var(--ink) 6%,transparent) 1px,transparent 1px),linear-gradient(90deg,color-mix(in srgb,var(--ink) 6%,transparent) 1px,transparent 1px);background-size:26px 26px}
body.tx-hatch{background-image:repeating-linear-gradient(45deg,color-mix(in srgb,var(--ink) 5%,transparent) 0 1px,transparent 1px 9px)}
.wrap{max-width:1080px;margin:0 auto;padding:0 24px}
/* fluid type scale: readable on a phone, generous on a wide screen */
h1{font-family:var(--headf);font-size:clamp(2.1rem,1.2rem+3.6vw,4rem);line-height:1.05;
  letter-spacing:-.02em;font-weight:800;margin:.2em 0}
body.bold h1{font-size:clamp(2.6rem,1.4rem+5vw,5.5rem);letter-spacing:-.03em;text-transform:uppercase}
h2.section{font-family:var(--headf);font-size:clamp(1.5rem,1.1rem+1.4vw,2.3rem);font-weight:800;letter-spacing:-.01em;
  margin:2.4em 0 .4em;scroll-margin-top:80px;position:relative;padding-bottom:.3em}
h2.section::after{content:"";position:absolute;left:0;bottom:0;width:64px;height:4px;
  border-radius:3px;background:var(--accent)}
.site-head{position:sticky;top:0;z-index:20;background:rgba(255,255,255,.72);
  backdrop-filter:saturate(1.4) blur(12px);border-bottom:1px solid var(--line)}
.site-head .wrap{display:flex;align-items:center;gap:20px;flex-wrap:wrap;padding:14px 24px}
.brand{font-weight:800;font-size:20px;color:var(--ink);text-decoration:none;letter-spacing:-.01em}
nav{display:flex;gap:6px;flex-wrap:wrap;margin-left:auto}
nav a{color:var(--muted);text-decoration:none;font-size:14px;padding:6px 12px;border-radius:8px;
  transition:background .2s,color .2s}
nav a:hover{color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,transparent)}
nav a.on{color:var(--accent);font-weight:700;background:color-mix(in srgb,var(--accent) 10%,transparent)}
/* full-bleed hero banner: image with a legibility gradient + big title over it */
.hero{position:relative;margin:0 0 12px;min-height:clamp(280px,42vw,520px);
  display:flex;align-items:flex-end;overflow:hidden;border-radius:0 0 var(--r) var(--r)}
.hero.plain{min-height:auto;padding:56px 0 20px;text-align:center;justify-content:center}
.hero .hero-bg{position:absolute;inset:0;background-size:cover;background-position:center;
  transform:scale(1.04)}
/* ── BANNER TREATMENT (2026-08-20) ───────────────────────────────────────────
   A photograph behind white text is only legible if something is done to it,
   and "something" used to be one hardcoded gradient that suited one kind of
   photo. These are the treatments a person actually reaches for, as classes on
   the background layer, so the SOURCE image is never modified — a flier stays
   a flier and the same file can sit in a gallery at full colour.

   `--bfilter` and `--bdim` carry the theme's default; a hero or band that sets
   its own overrides only itself. */
.hero-bg.f-mute{filter:saturate(.45) contrast(1.05)}
.hero-bg.f-mono{filter:grayscale(1) contrast(1.08)}
.hero-bg.f-warm{filter:sepia(.35) saturate(1.15) hue-rotate(-10deg)}
.hero-bg.f-cool{filter:saturate(.8) hue-rotate(12deg) brightness(1.02)}
.hero-bg.f-soft{filter:blur(3px) saturate(.9);transform:scale(1.08)}
.band-bg.f-mute{filter:saturate(.45) contrast(1.05)}
.band-bg.f-mono{filter:grayscale(1) contrast(1.08)}
.band-bg.f-warm{filter:sepia(.35) saturate(1.15) hue-rotate(-10deg)}
.band-bg.f-cool{filter:saturate(.8) hue-rotate(12deg) brightness(1.02)}
.band-bg.f-soft{filter:blur(3px) saturate(.9);transform:scale(1.06)}
.hero .hero-shade{position:absolute;inset:0;
  background:linear-gradient(to top,
    rgba(0,0,0,calc(var(--bdim,.45) + .18)),
    rgba(0,0,0,calc(var(--bdim,.45) * .3)) 55%,
    rgba(0,0,0,calc(var(--bdim,.45) * .55)))}
.hero .hero-in{position:relative;z-index:2;padding:40px 24px;max-width:1080px;
  margin:0 auto;width:100%}
.hero.banner h1{color:#fff;text-shadow:0 2px 24px rgba(0,0,0,.4)}
.hero.plain h1{color:var(--ink)}
.prose{max-width:68ch;color:var(--prose);font-size:1.06rem}
/* AUTHORED navigation (2026-07-23): explicit link/button elements + any
   component made clickable via link_to */
.btn{display:inline-block;margin:8px 0;padding:11px 22px;background:var(--accent);
  color:var(--on-accent);text-decoration:none;border-radius:10px;font-weight:700;
  box-shadow:0 2px 6px -1px color-mix(in srgb,var(--accent) 55%,transparent);
  transition:transform .16s,box-shadow .16s,filter .16s}
.btn:hover{transform:translateY(-1px);filter:brightness(1.06);
  box-shadow:0 5px 14px -3px color-mix(in srgb,var(--accent) 60%,transparent)}
.btn:active{transform:translateY(0);box-shadow:0 1px 3px -1px color-mix(in srgb,var(--accent) 55%,transparent)}
.navlink{display:inline-block;margin:8px 14px 8px 0;color:var(--accent);
  text-decoration:none;font-weight:600;border-bottom:2px solid transparent}
.navlink:hover{border-bottom-color:var(--accent)}
a.clickable{display:block;color:inherit;text-decoration:none;cursor:pointer}
/* W3 richer sections: pull-quote, stat/metric, divider */
.pullquote{margin:24px 0;padding:8px 0 8px 24px;border-left:4px solid var(--accent);
  font-size:clamp(1.15rem,1rem+.6vw,1.5rem);line-height:1.4;font-style:italic;color:var(--ink)}
.pullquote p{margin:0}
.pullquote cite{display:block;margin-top:10px;font-size:.85rem;font-style:normal;
  color:var(--muted);letter-spacing:.02em}
.stat{display:flex;flex-direction:column;align-items:center;text-align:center;padding:18px 8px}
.stat-num{font-size:clamp(2.2rem,1.5rem+2.4vw,3.4rem);font-weight:800;line-height:1;
  color:var(--accent);letter-spacing:-.02em}
.stat-label{margin-top:8px;color:var(--muted);font-size:.95rem}
.divider{margin:22px 0}
.divider.line{border:none;border-top:1px solid var(--line)}
.divider.space{height:32px}
.divider.dots{height:8px;background-image:radial-gradient(var(--line) 1.5px,transparent 1.5px);
  background-size:14px 8px;background-position:center;background-repeat:repeat-x}
/* horizontal band: components sit side-by-side, each spanning its --span/12;
   stacks to one column on narrow screens (W4 responsive, default behavior) */
.wrow{display:grid;grid-template-columns:repeat(12,1fr);gap:var(--gap,20px);
  align-items:start;margin:8px 0}
/* stretch makes side-by-side band cells the same height — the same
   "distribute" idea one level up from the card grid */
body.even-grid .wrow{align-items:stretch}
.wcol{grid-column:span var(--span,12);min-width:0}
.wcol>*:first-child{margin-top:0}
@media(max-width:720px){.wrow{grid-template-columns:1fr}.wcol{grid-column:1/-1}}
/* BANDS (W1): a themed section around a row — backgrounds + full-bleed give a
   modern site its alternating colored sections */
.band{padding:34px 0;margin:14px 0;border-radius:var(--r)}
.band .band-inner{max-width:1080px;margin:0 auto;padding:0 24px}
.band .band-inner>*:first-child,.band .wrow{margin-top:0}
.band.tint{background:color-mix(in srgb,var(--accent) 8%,var(--bg))}
.band.card{background:var(--card);box-shadow:0 1px 3px rgba(0,0,0,.05)}
.band.dark{background:#161619;color:#ededf0}
.band.dark .prose{color:#c8c8cf}
.band.accent{background:var(--accent);color:var(--on-accent)}
.band.accent h2.section::after{background:var(--on-accent)}
.band.accent .prose{color:rgba(255,255,255,.92)}
.band.accent .btn{background:#fff;color:var(--accent)}
.band.gradient{background:linear-gradient(135deg,var(--accent),var(--accent2));color:var(--on-accent)}
.band.gradient .prose{color:rgba(255,255,255,.92)}
.band.gradient h2.section::after{background:rgba(255,255,255,.7)}
/* full-bleed: the band spans the viewport; its inner content stays centered */
.band.full{width:100vw;margin-left:calc(50% - 50vw);border-radius:0}
/* IMAGED band (photo behind text — a modern marketing section) */
.band.imaged{position:relative;color:#fff;padding:64px 0;overflow:hidden}
.band.imaged .band-bg{position:absolute;inset:0;background-size:cover;
  background-position:center;transform:scale(1.03)}
.band.imaged .band-shade{position:absolute;inset:0;
  background:linear-gradient(to top,rgba(0,0,0,.72),rgba(0,0,0,.35))}
.band.imaged .band-inner{position:relative;z-index:2}
.band.imaged .prose{color:rgba(255,255,255,.95)}
.band.imaged h2.section::after{background:#fff}
.band.imaged .btn{background:#fff;color:var(--ink)}
.filter{margin:16px 0}
.filter input{width:100%;max-width:360px;padding:11px 14px;border:1px solid var(--line);
  border-radius:10px;font-size:14px;background:var(--card);transition:box-shadow .2s,border-color .2s}
.filter input:focus{outline:none;border-color:var(--accent);
  box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 18%,transparent)}
/* ── EVEN SPACING AND EQUAL CELLS (2026-08-20) ───────────────────────────────
   The author's ask: *"grids should support that alignment thing from figma,
   where they are evenly spaced."* Two things make a wall of cards read as a
   grid rather than as a pile:

   - ONE spacing step everywhere (`--gap`, a theme axis) instead of a different
     literal per component. That is what a design tool's spacing scale is for,
     and it is why changing it in one place changes the whole page's rhythm.
   - EQUAL HEIGHTS per row (`grid-auto-rows:1fr` under `body.even-grid`), so a
     card with three lines of summary does not leave its neighbour floating
     against a short box. This is the "distribute" alignment being asked for,
     and it is the default because a ragged row is almost never what somebody
     drawing a layout wanted. */
.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(270px,1fr));
  gap:var(--gap,16px);margin:calc(var(--gap,16px) + 2px) 0;align-items:stretch}
body.even-grid .cards{grid-auto-rows:1fr}
body.even-grid .cards>.card{display:flex;flex-direction:column}
/* the last paragraph pushes to the bottom so every card's baseline agrees */
body.even-grid .cards>.card>p:last-child{margin-bottom:0}
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--r);padding:18px 20px;
  box-shadow:0 1px 2px rgba(0,0,0,.04);transition:transform .22s cubic-bezier(.2,.7,.3,1),
  box-shadow .22s,border-color .22s}
.card:hover{transform:translateY(-4px);box-shadow:var(--shadow);border-color:transparent}
.card h3{margin:0 0 6px;font-size:1.1rem;font-weight:700}
/* the event card's top rule is the EVENT's own colour when it has one
   (event.color, a declared field) — a calendar of four organizations should
   not be four identical blue blocks */
.card.event{border-top:3px solid var(--card-accent,#3f6fae)}
.card.job{border-top:3px solid #5d7d3b}
.card .meta{color:var(--muted);font-size:.9rem;margin:2px 0}
/* ── FEATURED EVENT (2026-08-20) ─────────────────────────────────────────────
   One event, at a size a grid cell cannot give it: the image behind, the
   details over it. Structurally a hero that happens to be about a rune — same
   scrim-over-photo physics, same computed contrast — but it sits INSIDE the
   page flow rather than at the top of it, so it can appear three times on a
   long page without pretending to be three mastheads. */
.feature{position:relative;overflow:hidden;border-radius:var(--r);
  margin:calc(var(--gap,20px) + 6px) 0;display:flex;align-items:flex-end;
  isolation:isolate}
.feature.compact{min-height:clamp(180px,26vw,260px)}
.feature.tall{min-height:clamp(260px,38vw,420px)}
.feature.full{min-height:clamp(340px,52vw,560px)}
.feature.plain{background:color-mix(in srgb,var(--accent) 10%,var(--bg));
  border:1px solid var(--line)}
.feature .feature-bg{position:absolute;inset:0;background-size:cover;
  background-position:center;z-index:-2;transform:scale(1.03)}
.feature .feature-shade{position:absolute;inset:0;z-index:-1;
  background:linear-gradient(to top,
    rgba(0,0,0,calc(var(--bdim,.45) + .3)),
    rgba(0,0,0,calc(var(--bdim,.45) * .35)) 62%,
    rgba(0,0,0,calc(var(--bdim,.45) * .5)))}
.feature .feature-in{position:relative;padding:clamp(20px,3.4vw,40px);width:100%;
  max-width:70ch;min-width:0}
.feature h2{font-family:var(--headf);font-weight:800;letter-spacing:-.015em;
  margin:0 0 .35em;font-size:clamp(1.5rem,1.1rem+1.9vw,2.6rem);line-height:1.1}
/* over a photograph the text is white with a shadow; on the plain variant it
   is the page's own ink, because there is nothing to read it against */
.feature.imaged .feature-in{color:#fff;text-shadow:0 2px 18px rgba(0,0,0,.5)}
.feature.imaged .meta-row{color:rgba(255,255,255,.92)}
.feature.imaged .feature-sum{color:rgba(255,255,255,.94)}
.feature.plain h2{color:var(--ink)}
.feature .feature-sum{margin:.7em 0 1em;font-size:1.06rem;max-width:60ch}
.feature .btn{margin:0}

/* ── EVENT + FLIER ───────────────────────────────────────────────────────────
   Different job from `.feature`, and the difference is that the image is a
   DOCUMENT: a flier is opened full size, saved and forwarded, so it is a real
   tile that opens the lightbox rather than a background. `side` puts it beside
   the details; `stacked` puts it above. Both collapse to one column on a
   phone, where a flier wants the full width anyway. */
.evflier{display:grid;gap:var(--gap,20px);margin:var(--gap,20px) 0;
  align-items:start}
.evflier.side{grid-template-columns:minmax(0,1fr) minmax(0,1.15fr)}
.evflier.stacked{grid-template-columns:1fr}
.evflier .tile.flier{aspect-ratio:auto;cursor:zoom-in;background:var(--card)}
.evflier .tile.flier img{height:auto;object-fit:contain}
.evflier-body{min-width:0}
.evflier-body h3{font-family:var(--headf);margin:0 0 .4em;
  font-size:clamp(1.15rem,1rem+.7vw,1.5rem)}
.evflier-body p{overflow-wrap:anywhere}
@media(max-width:720px){.evflier.side{grid-template-columns:1fr}}

/* a block's authored caption sits above its grid, quieter than the prose */
.caption{margin:0 0 10px;font-size:.92rem}
/* ── ICONS (2026-08-20) ──────────────────────────────────────────────────────
   The author, looking at a page of event cards: *"events are literally just
   text. like in previous newsletters, we need to bring icons back."* They were
   right that it reads as a wall — a date, a place and a time are three
   different KINDS of fact and printing them as one grey run makes a reader
   parse a sentence to find a street address.

   Inline SVG, vendored, sized in `em` so an icon rides the type scale instead
   of fighting it, and coloured by `currentColor` so it inherits whatever text
   it sits beside — which means the contrast work above applies to icons for
   free, with no second palette to keep in sync. */
.ico{width:1em;height:1em;flex:0 0 auto;vertical-align:-.125em;
  opacity:.75;margin-right:.4em}
.meta-row{display:flex;align-items:flex-start;gap:.1em;margin:3px 0;
  color:var(--muted);font-size:.9rem;min-width:0}
.meta-row .ico{margin-top:.28em}
.meta-row span{min-width:0;overflow-wrap:anywhere}
/* an icon in a heading is decoration and steps back a little further */
h3 .ico{opacity:.55}
/* THE DIRECTORY (2026-08-19): a member card is a photo, a name and a line of
   role. `list` drops the photo to a small round avatar for a long roster;
   `card` is the drawn layout's searchable carousel of members. */
.card.person{text-align:center;border-top:3px solid var(--accent)}
.card.person .avatar{width:104px;height:104px;border-radius:50%;object-fit:cover;
  margin:0 auto 10px;display:block;border:2px solid var(--line)}
.card.person h3{margin:0 0 2px}
.card.person a{color:var(--accent);text-decoration:none}
.card.person a:hover{text-decoration:underline}
.cards.directory.list{grid-template-columns:1fr;gap:8px}
.cards.directory.list .card.person{display:flex;align-items:center;gap:14px;
  text-align:left;padding:12px 16px}
.cards.directory.list .card.person .avatar{width:56px;height:56px;margin:0}
.cards.directory.list .card.person p{margin:0}
.cards.directory.carousel{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;
  gap:16px;padding-bottom:8px}
.cards.directory.carousel .card.person{flex:0 0 clamp(220px,40vw,280px);
  scroll-snap-align:start}
/* the brand mark: the org's own logo when it has one, with the light/dark twin
   swapped by CSS (never by JS — a logo that flickers on load is worse than
   none). `.brand-mark.dark` is display:none until the dark theme claims it. */
.brand{display:flex;align-items:center;gap:10px}
.brand-mark{height:34px;width:auto;display:block}
.brand-mark.dark{display:none}
/* the language switch lives at the end of the nav, set apart from the pages —
   it changes the language of where you are, it is not a place to go */
nav a.langswitch{margin-left:8px;border:1px solid var(--line);font-weight:600}
/* the hero's second line: an address, a date, whatever the title needs under it */
.hero-sub{margin:8px 0 0;font-size:clamp(1rem,2vw,1.2rem);opacity:.92}
.hero.banner .hero-sub{color:#fff;text-shadow:0 1px 12px rgba(0,0,0,.5)}
.gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));
  gap:var(--gap,12px);margin:calc(var(--gap,12px) + 4px) 0}
.tile{display:block;overflow:hidden;border-radius:12px;border:1px solid var(--line);
  aspect-ratio:1/1;cursor:zoom-in;box-shadow:0 1px 2px rgba(0,0,0,.05)}
.tile img{width:100%;height:100%;object-fit:cover;transition:transform .4s cubic-bezier(.2,.7,.3,1)}
.tile:hover img{transform:scale(1.08)}
/* image-grid display MODES (2026-07-23) */
/* masonry: a Pinterest-style wall — tiles keep their natural aspect ratio */
.gallery.masonry{display:block;column-width:220px;column-gap:12px}
.gallery.masonry .tile{aspect-ratio:auto;break-inside:avoid;margin:0 0 12px}
.gallery.masonry .tile img{height:auto}
/* carousel: a horizontal swipe track with snap; prev/next scroll it */
.carousel-wrap{position:relative;margin:18px 0}
.gallery.carousel{display:flex;grid-template-columns:none;overflow-x:auto;
  scroll-snap-type:x mandatory;gap:12px;padding-bottom:6px;scrollbar-width:thin}
.gallery.carousel .tile{flex:0 0 clamp(220px,42vw,340px);aspect-ratio:4/3;scroll-snap-align:start}
.carousel-wrap .cbtn{position:absolute;top:50%;transform:translateY(-50%);z-index:4;
  width:44px;height:44px;border-radius:50%;border:none;cursor:pointer;font-size:26px;
  line-height:1;background:var(--card);color:var(--ink);box-shadow:var(--shadow);opacity:.92}
.carousel-wrap .cbtn:hover{opacity:1}
.carousel-wrap .cbtn.prev{left:-6px}.carousel-wrap .cbtn.next{right:-6px}
@media(prefers-reduced-motion:reduce){.gallery.carousel{scroll-behavior:auto}}
.empty{color:var(--muted);font-style:italic}
/* scroll-reveal: sections rise + fade as they enter (JS adds .in) */
.reveal{opacity:0;transform:translateY(22px);transition:opacity .7s ease,transform .7s cubic-bezier(.2,.7,.3,1)}
.reveal.in{opacity:1;transform:none}
.site-foot{margin-top:56px;padding:36px 0;border-top:1px solid var(--line);color:var(--muted);
  text-align:center;font-size:14px}
/* motion is opt-out — a reduced-motion visitor gets the still site */
@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *{transition:none!important;animation:none!important}
  .reveal{opacity:1;transform:none}
  .card:hover{transform:none}.tile:hover img{transform:none}
}
.lightbox{position:fixed;inset:0;background:rgba(0,0,0,.88);display:flex;flex-direction:column;
  align-items:center;justify-content:center;z-index:100;padding:24px}
.lightbox[hidden]{display:none}
.lightbox img{max-width:92vw;max-height:82vh;border-radius:6px}
.lightbox p{color:#eee;margin:12px 0 0}
.lb-close{position:absolute;top:16px;right:24px;color:#fff;font-size:40px;cursor:pointer;line-height:1}
.mapwidget{position:relative;height:440px;margin:14px 0;border:1px solid var(--line);
  border-radius:10px;overflow:hidden;background:#e8e6e1}
.mapwidget canvas{display:block;width:100%;height:100%;cursor:grab}
.mapwidget .attrib{position:absolute;left:6px;bottom:4px;font-size:11px;color:#555;
  background:rgba(255,255,255,.75);padding:1px 6px;border-radius:4px;z-index:2}
.mapwidget .mappop{position:absolute;background:#fff;border:1px solid var(--line);
  border-radius:6px;padding:4px 10px;font-size:13px;pointer-events:none;
  box-shadow:0 2px 8px rgba(0,0,0,.15);z-index:3;display:none}
.calwidget{margin:14px 0;border:1px solid var(--line);border-radius:10px;
  background:var(--card);padding:12px}
.calwidget .cal-bar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:10px}
.calwidget .cal-bar button{border:1px solid var(--line);background:#fff;border-radius:6px;
  padding:4px 10px;cursor:pointer;font-size:13px}
.calwidget .cal-bar button.on{background:var(--accent);color:var(--on-accent);border-color:var(--accent)}
.calwidget .cal-bar .cal-title{font-weight:700;margin:0 6px}
.calwidget .cal-bar .cal-links{margin-left:auto;font-size:13px}
.calwidget table{width:100%;border-collapse:collapse;table-layout:fixed}
.calwidget th{font-size:12px;color:var(--muted);padding:4px;border-bottom:1px solid var(--line)}
.calwidget td{vertical-align:top;border:1px solid var(--line);padding:3px;height:84px;font-size:12px}
.calwidget td.other{background:#faf9f6}
.calwidget td.today{background:#fff7e4}
.calwidget .dnum{color:var(--muted);font-size:11px}
.calwidget .ent{display:block;margin:2px 0;padding:1px 5px;border-radius:4px;
  color:#fff;text-decoration:none;overflow:hidden;white-space:nowrap;
  text-overflow:ellipsis;
  /* the chip's own event colour can be anything, so its label is forced to a
     legible weight rather than trusting a hue nobody checked */
  text-shadow:0 1px 2px rgba(0,0,0,.55)}
.calwidget .ent.inc{outline:2px solid #c8322f;outline-offset:-2px}
.calwidget .cols{display:grid;gap:8px}
.calwidget .col{border:1px solid var(--line);border-radius:8px;padding:6px;min-height:180px}
.calwidget .col h4{margin:0 0 6px;font-size:13px}
.calwidget .col.today{background:#fff7e4}
/* ── MOBILE (2026-08-20) ─────────────────────────────────────────────────────
   The author: *"calendar doesn't work well in mobile. overall, we really need
   to consider mobile too."* Both halves are addressed here, and the calendar
   needed a structural answer rather than smaller type.

   A MONTH GRID CANNOT FIT A PHONE. Seven columns across ~340px is 48px a cell:
   room for a date number and nothing else, so every event became an ellipsis
   and the widget was decoration. Shrinking the font makes it unreadable
   instead of illegible, which is not an improvement.

   So on a narrow screen the month grid BECOMES AN AGENDA — the same data, the
   list shape, which is what a phone is good at and what a person checking "what
   is on this week" actually wants. `app.js` reads the same breakpoint and
   renders the agenda; this half hides the table so a browser without JS still
   gets something legible rather than a crushed grid.

   The rest is the ordinary phone audit that had never been done: tap targets at
   44px (the platform minimum, and the difference between a working nav and a
   frustrating one), no full-bleed padding collapse, images that cannot force a
   sideways scroll, and the carousel arrows out of the way of a thumb. */
@media(max-width:900px){
  .wrap{padding-left:18px;padding-right:18px}
  .cards{grid-template-columns:repeat(auto-fill,minmax(230px,1fr))}
}
@media(max-width:640px){
  nav{width:100%;margin-left:0;justify-content:center}
  nav a{padding:10px 14px;min-height:44px;display:flex;align-items:center}
  .site-head .wrap{gap:10px;padding:10px 16px}
  .brand{font-size:18px}
  .hero{min-height:clamp(220px,60vw,340px)}
  .hero .hero-in{padding:24px 18px}
  .hero.plain{padding:36px 0 14px}
  h1{font-size:clamp(1.6rem,7vw,2.2rem)}
  h2.section{font-size:clamp(1.25rem,5.4vw,1.7rem)}
  /* one column: two 150px cards side by side is worse than one readable one */
  .cards{grid-template-columns:1fr}
  .gallery{grid-template-columns:repeat(auto-fill,minmax(140px,1fr))}
  .band{padding:22px 0;border-radius:0}
  .band .band-inner{padding:0 18px}
  .btn{display:block;text-align:center;padding:14px 20px}
  .carousel-wrap .cbtn{width:38px;height:38px}
  .carousel-wrap .cbtn.prev{left:2px}.carousel-wrap .cbtn.next{right:2px}
  .prose{font-size:1rem}
  /* THE CALENDAR BECOMES AN AGENDA (see above) */
  .calwidget table{display:none}
  .calwidget .cal-bar{gap:6px}
  .calwidget .cal-bar .cal-links{margin-left:0;width:100%;order:9}
  .calwidget .cal-bar button{min-height:38px;padding:6px 12px}
  .calwidget .cols{grid-template-columns:1fr!important}
  .calwidget .agenda{display:block}
  /* the map is a fixed-height canvas; give it a phone-shaped one */
  .mapwidget{height:min(70vh,380px)}
}
/* the agenda list the month grid falls back to, and `mode agenda` always uses */
.calwidget .agenda{display:none}
.calwidget .agenda .ag-day{display:flex;gap:10px;padding:8px 4px;
  border-bottom:1px solid var(--line);align-items:baseline}
.calwidget .agenda .ag-date{flex:0 0 5.2em;color:var(--muted);font-size:12px;
  font-variant-numeric:tabular-nums}
.calwidget .agenda .ag-items{flex:1 1 auto;min-width:0}
.calwidget .agenda .ag-items a{display:block;overflow-wrap:anywhere;
  text-decoration:none;color:var(--ink);padding:2px 0}
.calwidget .agenda .ag-dot{display:inline-block;width:9px;height:9px;
  border-radius:50%;margin-right:7px;vertical-align:baseline}
