
/* Metric-matched stand-ins for the web fonts, so text set in the fallback occupies the
   same space and the swap to Libre Franklin / Bodoni Moda cannot reflow the page (the
   font swaps were a measured CLS source). The numbers come from comparing canvas
   fontBoundingBox metrics and the width of a long mixed sample at 100px in Chrome
   (2026-08-26): Franklin/Arial widths 6116.6/5908.8 → size-adjust 103.5%, ascent 97
   → 97/1.035, descent 25 → 25/1.035; Bodoni/Georgia 5977.5/5803.6 → 103%, ascent
   113 → 113/1.03, descent 40 → 40/1.03. Remeasure only if the font families change. */
@font-face { font-family:"Franklin Fallback"; src:local("Arial");
  size-adjust:103.5%; ascent-override:93.7%; descent-override:24.15%; line-gap-override:0%; }
@font-face { font-family:"Bodoni Fallback"; src:local("Georgia");
  size-adjust:103%; ascent-override:109.7%; descent-override:38.85%; line-gap-override:0%; }
:root {
  --paper:#FBF8F1; --panel:#F4EFE4; --ink:#201B15; --ink2:#5E5546; --muted:#6F6452;
  --curtain:#A6212C; --curtain-soft:rgba(166,33,44,.08); --gilt:#8F6B22; --gilt-bright:#B08430;
  --line:rgba(32,27,21,.16); --line-strong:rgba(32,27,21,.55); --card:#FFFDF8;
  --shadow:0 1px 2px rgba(32,27,21,.06), 0 8px 24px rgba(32,27,21,.07);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:#161210; --panel:#1E1815; --ink:#EFE8DC; --ink2:#BFB3A0; --muted:#94897A;
    --curtain:#D9535E; --curtain-soft:rgba(217,83,94,.12); --gilt:#D3A44A; --gilt-bright:#D3A44A;
    --line:rgba(239,232,220,.16); --line-strong:rgba(239,232,220,.55); --card:#1C1613;
    --shadow:0 1px 2px rgba(0,0,0,.4), 0 10px 30px rgba(0,0,0,.35);
  }
}
:root[data-theme="dark"] {
  --paper:#161210; --panel:#1E1815; --ink:#EFE8DC; --ink2:#BFB3A0; --muted:#94897A;
  --curtain:#D9535E; --curtain-soft:rgba(217,83,94,.12); --gilt:#D3A44A; --gilt-bright:#D3A44A;
  --line:rgba(239,232,220,.16); --line-strong:rgba(239,232,220,.55); --card:#1C1613;
  --shadow:0 1px 2px rgba(0,0,0,.4), 0 10px 30px rgba(0,0,0,.35);
}
* { box-sizing:border-box; }
/* Screen-reader-only text: off-screen but read by assistive tech and crawlers. Used to give
   the homepage a keyword-bearing <h1> without changing the visible Bodoni wordmark. */
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
  clip:rect(0,0,0,0); white-space:nowrap; border:0; }
/* Accessibility (2026-09-26). Light-mode --muted was #877B68: 3.9:1 on paper, under WCAG AA's 4.5:1
   for the small text it sets (card meta, inactive tabs, chips, dates). #6F6452 is ~5.5:1; dark mode
   keeps its own value. The skip link is the first focusable thing on a listing page and jumps
   past the masthead and filters to <main id="main">; hidden until it takes keyboard focus. */
.skip { position:absolute; left:-9999px; top:0; z-index:1000; }
.skip:focus { left:12px; top:12px; padding:8px 14px; background:var(--card); color:var(--ink);
  border:1px solid var(--line-strong); border-radius:6px; font-size:14px; text-decoration:none; }
body { background:var(--paper); color:var(--ink); margin:0;
  font-family:"Libre Franklin","Franklin Fallback",Arial,sans-serif;
  font-size:15.5px; line-height:1.55; -webkit-font-smoothing:antialiased; }
.wrap { max-width:1280px; margin:0 auto; padding:40px 24px 80px; }
/* Magazine-band tints for the All view's date sections. Mixed with the page colour so they
   adapt to light and dark automatically. Today warm, Tomorrow cool, Weekend rose. */
:root { --mag-today:color-mix(in srgb, var(--gilt) 15%, var(--paper));
  --mag-tomorrow:color-mix(in srgb, #3f7d6a 15%, var(--paper));
  --mag-weekend:color-mix(in srgb, var(--curtain) 11%, var(--paper)); }
a { color:var(--curtain); }
a:focus-visible, .tix:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:2px; }
.mast { display:flex; flex-wrap:wrap; gap:16px 32px; align-items:flex-end; justify-content:space-between;
  border-bottom:4px double var(--line-strong); padding-bottom:20px; }
.mast h1 { font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif; font-weight:700; font-size:clamp(44px,7vw,72px);
  line-height:.95; margin:0; letter-spacing:.01em; text-wrap:balance; }
/* The wordmark is the way home (2026-08-23): it clears every filter, returns to the
   first tab and goes back to the top, the way a masthead does on a news site.
   Deliberately NOT styled as a link — no underline, no colour shift, no hover state.
   Wes's brief was that it read as a header image you happen to be able to click, so the
   only affordances are the pointer cursor and a focus ring for keyboard users. It is a
   <button> rather than an <a> because there is nowhere to navigate to: one static page,
   and the "home" state is a DOM state, not a URL. Inheriting `font` keeps the Bodoni
   clamp above as the single source of truth for the wordmark's size. */
.mast h1 .home { appearance:none; -webkit-appearance:none; background:none; border:0;
  padding:0; margin:0; font:inherit; color:inherit; letter-spacing:inherit;
  text-align:inherit; cursor:pointer; }
.mast h1 .home:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:4px; }
/* The eyebrow shares its line with the sign-in button (Wes, 2026-09-05), so the ROW owns the
   bottom margin the <p> used to carry. baseline alignment, not centre: the two sit on the same
   text baseline, which is what makes the button read as part of the line rather than floated
   beside it. The eyebrow is a ~450px run of letter-spaced caps that wraps on a phone rather than
   shrinking, so the button is flex:0 0 auto with left padding — the wrapped line ends short of it
   instead of running into it. */
/* .mast is a flex row that shrink-wraps its children, so the eyebrow row was only as wide as its
   own text and the button ended up beside the words rather than at the page edge. The homepage
   masthead has exactly one child (the date block was dropped on 2026-08-29), so it may take the
   full width; other templates keep .mast's original behaviour, since they do not carry this class. */
.mast-lead { flex:1 1 100%; min-width:0; }
.eyebrow-row { display:flex; align-items:baseline; margin:0 0 10px; }
.eyebrow { text-transform:uppercase; letter-spacing:.18em; font-size:11.5px; font-weight:600; color:var(--gilt); margin:0; }
/* Capped so the masthead stays one line. flex-wrap decides from each item's max-content
   width before it will shrink anything, and .d2 is a single long run of letter-spaced
   caps that grows with every domain added — left uncapped it drags the whole block onto
   a second row and leaves the top right of the page empty. Below the 760px breakpoint
   the cap is dropped and the block stacks under the wordmark instead. */
.dateblock { text-align:right; border-left:3px solid var(--gilt-bright); padding-left:16px;
  max-width:42%; }
.dateblock .d1 { font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif; font-size:22px; font-style:italic; margin:0; }
.dateblock .d2 { margin:2px 0 0; color:var(--muted); font-size:12.5px; text-transform:uppercase; letter-spacing:.14em;
  text-wrap:balance; }
/* --- account / cross-device sync --- */
.acctbar { flex:0 0 auto; margin-left:auto; padding-left:18px; display:flex; gap:7px; align-items:center; }
/* The signup form, moved into the Weekly email window: the window is its frame. */
.sub-panel { max-width:430px; }
/* Phones get the short labels ("Email", "Sign in"): with both pills at full length the eyebrow
   beside them wrapped to five lines; short, it keeps the three it had with one pill (measured). */
.acctbar .lbl-s { display:none; }
@media (max-width:560px) { .acctbar .lbl-l { display:none; } .acctbar .lbl-s { display:inline; } }
.sub-panel .subscribe { margin:0; padding:0; border:none; background:none; max-width:none; }
/* Sized to sit ON the eyebrow line rather than tower over it: 10px caps against the eyebrow's
   11.5px, and vertical padding cut from 7px to 3px so the pill is barely taller than the text it
   shares the line with. */
.acct { appearance:none; font-family:"Libre Franklin","Franklin Fallback",Arial,sans-serif; font-size:10px; font-weight:700;
  letter-spacing:.08em; text-transform:uppercase; color:var(--ink2); background:var(--card); white-space:nowrap;
  border:1px solid var(--line-strong); border-radius:999px; padding:2px 11px; cursor:pointer; }
.acct:hover { color:var(--ink); border-color:var(--muted); }
.acct.on { color:var(--curtain); border-color:var(--curtain); }
.acct-modal { position:fixed; inset:0; z-index:200; display:flex; align-items:center; justify-content:center;
  background:rgba(20,15,12,.5); -webkit-backdrop-filter:blur(2px); backdrop-filter:blur(2px); padding:18px; }
.acct-modal[hidden] { display:none; }
.acct-panel { position:relative; width:100%; max-width:380px; background:var(--paper); color:var(--ink);
  border:1px solid var(--line-strong); border-radius:12px; box-shadow:0 12px 44px rgba(20,15,12,.35);
  padding:30px 26px 24px; }
.acct-x { position:absolute; top:9px; right:12px; appearance:none; background:none; border:none;
  font-size:26px; line-height:1; color:var(--muted); cursor:pointer; padding:2px 6px; }
.acct-x:hover { color:var(--ink); }
.acct-h { font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif; font-weight:700; font-size:23px; margin:0 0 6px; color:var(--ink); }
.acct-p { margin:0 0 18px; color:var(--ink2); font-size:14px; line-height:1.45; }
.acct-google { display:flex; align-items:center; justify-content:center; gap:10px; width:100%;
  appearance:none; font-family:inherit; font-size:14px; font-weight:600; color:var(--ink);
  background:var(--card); border:1px solid var(--line-strong); border-radius:8px; padding:11px; cursor:pointer; }
.acct-google:hover { border-color:var(--muted); }
.acct-google svg { display:block; }
.acct-or { display:flex; align-items:center; text-align:center; gap:12px; margin:15px 0; color:var(--muted);
  font-size:12px; text-transform:uppercase; letter-spacing:.1em; }
.acct-or::before, .acct-or::after { content:""; flex:1; height:1px; background:var(--line); }
.acct-in { display:block; width:100%; font-family:inherit; font-size:15px; color:var(--ink); background:var(--card);
  border:1px solid var(--line-strong); border-radius:8px; padding:11px 12px; margin-bottom:10px; }
.acct-in:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:1px; }
.acct-row { display:flex; gap:10px; margin-top:2px; }
.acct-primary { flex:1; appearance:none; font-family:inherit; font-size:14px; font-weight:700; cursor:pointer;
  color:#FDF6EC; background:var(--curtain); border:1px solid var(--curtain); border-radius:8px; padding:11px; }
.acct-primary:hover { filter:brightness(1.06); }
.acct-ghost { appearance:none; font-family:inherit; font-size:14px; font-weight:600; cursor:pointer;
  color:var(--ink2); background:none; border:1px solid var(--line-strong); border-radius:8px; padding:11px 14px; }
.acct-ghost:hover { color:var(--ink); border-color:var(--muted); }
.acct-err { margin:12px 0 0; color:var(--curtain); font-size:13px; font-weight:600; }
.acct-err[hidden] { display:none; }
.acct-fine { margin:16px 0 0; color:var(--muted); font-size:11.5px; line-height:1.5; }
#acct-signedin .acct-ghost { margin-top:8px; }
/* --- tab bar --- */
/* Homepage lede (one sentence under the masthead) and the "Coming up" text index at the foot —
   the crawlable copy of a page whose cards otherwise arrive as data. */
.sitelede { font-family:"Libre Franklin","Franklin Fallback",Arial,sans-serif; font-size:14px; line-height:1.55;
  color:var(--ink2); margin:12px 0 0; max-width:860px; }
.sitelede a { color:var(--curtain); text-decoration:none; border-bottom:1px solid var(--line-strong); }
.sitelede a:hover { border-bottom-color:var(--curtain); }
.upindex { margin:48px 0 0; padding-top:18px; border-top:1px solid var(--line-strong); }
.upindex summary { font-family:"Libre Franklin","Franklin Fallback",Arial,sans-serif; font-size:12.5px; font-weight:700;
  letter-spacing:.14em; text-transform:uppercase; color:var(--muted); margin:0 0 12px; cursor:pointer; }
.upindex summary:hover { color:var(--ink); }
.upindex ol { list-style:none; margin:0; padding:0; columns:3; column-gap:32px; font-family:"Libre Franklin","Franklin Fallback",Arial,sans-serif;
  font-size:12.5px; line-height:1.5; color:var(--ink2); }
.upindex li { break-inside:avoid; margin:0 0 4px; }
.upindex .upd { display:inline-block; min-width:74px; color:var(--muted); font-variant-numeric:tabular-nums; }
.upindex a { color:var(--ink); text-decoration:none; }
.upindex a:hover { color:var(--curtain); }
.upindex .upc { color:var(--muted); }
.upindex .upc::before { content:"— "; }
.upmore { font-family:"Libre Franklin","Franklin Fallback",Arial,sans-serif; font-size:12.5px; margin:14px 0 0; color:var(--muted); }
.upmore a { color:var(--curtain); text-decoration:none; }
@media (max-width:900px) { .upindex ol { columns:2; } }
@media (max-width:560px) { .upindex ol { columns:1; } .sitelede { font-size:13px; } }
.tabs { display:flex; gap:2px; margin:26px 0 0; border-bottom:1px solid var(--line-strong); flex-wrap:wrap; }
.tab { appearance:none; background:none; border:none; border-bottom:3px solid transparent;
  font-family:"Libre Franklin","Franklin Fallback",Arial,sans-serif; font-size:12.5px; font-weight:700; letter-spacing:.16em;
  text-transform:uppercase; color:var(--muted); padding:12px 20px 10px; cursor:pointer; text-decoration:none;
  display:flex; align-items:center; gap:9px; margin-bottom:-1px; }
.tab:hover { color:var(--ink); }
.tab.is-active { color:var(--curtain); border-bottom-color:var(--curtain); }
.tab-n { font-size:10.5px; font-weight:600; letter-spacing:.04em; color:var(--paper);
  background:var(--muted); border-radius:999px; padding:2px 7px; font-variant-numeric:tabular-nums; }
.tab.is-active .tab-n { background:var(--curtain); }
.tab:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:-3px; }
/* Section-calendar toggle: an icon-only button leading the tab bar, left of "All". */
.tabcal { appearance:none; background:none; border:none; border-bottom:3px solid transparent;
  color:var(--muted); padding:10px 14px 8px; margin-bottom:-1px; cursor:pointer;
  display:flex; align-items:center; }
.tabcal svg { width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:2; display:block; }
.tabcal:hover { color:var(--ink); }
.tabcal.on { color:var(--curtain); border-bottom-color:var(--curtain); }
.tabcal:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:-3px; }
/* When a section shows the magazine layout, its normal grouped listing is hidden and the
   reparented #allgrid stands in its place. */
.panel.seccal > .group { display:none; }
/* 16px from the chip row to the grid — the same breath the All view gives the Featured
   box below a month header's rule (.monthblock's 16px flex gap). The panel has no border
   or padding, so this margin COLLAPSES with the filter bar's 4px bottom margin (max, not
   sum) — hence the full 16, not 12. Without it the grid sat 4px under the chips. */
.panel.seccal > .allgrid { display:flex; margin-top:16px; }
.panel[hidden] { display:none; }
/* --- filter bar --- */
.filters { margin:18px 0 4px; display:flex; flex-direction:column; gap:11px; }
.frow { display:flex; gap:10px; flex-wrap:wrap; }
.fsearch { flex:1 1 320px; min-width:0; font-family:inherit; font-size:14px; color:var(--ink);
  background:var(--card); border:1px solid var(--line-strong); padding:10px 13px; }
.fsearch::placeholder { color:var(--muted); }
.fdist { font-family:inherit; font-size:13px; color:var(--ink); background:var(--card);
  border:1px solid var(--line-strong); padding:10px 12px; }
.fcustom { display:inline-flex; align-items:center; gap:7px; }
.fcustom[hidden] { display:none; }
.fcustom-sep { font-size:12px; color:var(--muted); }
.fdate { font-family:inherit; font-size:13px; color:var(--ink); background:var(--card);
  border:1px solid var(--line-strong); padding:9px 10px; }
.fdate:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:1px; }
/* A When/Distance select (or filled custom date) carrying a value reads as an active
   chip — same curtain fill — so a live filter is visible at a glance, including one
   restored from a previous visit. color-scheme:dark keeps the native dropdown arrow
   and date glyphs legible on the dark fill. */
.fdist.on, .fdate.on { background:var(--curtain); border-color:var(--curtain);
  color:#FDF6EC; color-scheme:dark; font-weight:700; }
/* Right Here Right Now — a round icon button (concentric range rings) that filters to events
   near the reader's live location, with a companion 0–1 mile slider. Shares the .fdist frame
   so it sits flush with the When/Distance selects; .on borrows the same curtain fill. */
.frhrn { display:inline-flex; align-items:center; justify-content:center; cursor:pointer;
  padding:8px 11px; appearance:none; }
.frhrn svg { display:block; }
.frhrn:hover { border-color:var(--line-strong); }
.frhrn.on { background:var(--curtain); border-color:var(--curtain); color:#FDF6EC; }
.frhrn.locating { opacity:.55; cursor:progress; }
.frhrn-slide { display:inline-flex; align-items:center; gap:9px; }
.frhrn-slide[hidden] { display:none; }
.frhrn-slide input[type=range] { width:130px; accent-color:var(--curtain); }
.frhrn-out { font-size:13px; font-weight:600; color:var(--ink); min-width:46px; white-space:nowrap; }
.frhrn-msg { flex-basis:100%; font-size:12px; color:var(--curtain); }
.frhrn-msg[hidden] { display:none; }
.fsearch:focus-visible, .fdist:focus-visible, .fchip:focus-visible, .fclear:focus-visible {
  outline:2px solid var(--gilt-bright); outline-offset:1px; }
.fchips { display:flex; flex-wrap:wrap; gap:6px; }
/* The "Filters" fold toggle is phone-only chrome — hidden (and the chips shown) everywhere else. */
.fchip-toggle { display:none; }
.fchip { appearance:none; cursor:pointer; font-family:inherit; font-size:10.5px; font-weight:600;
  text-transform:uppercase; letter-spacing:.12em; color:var(--muted); background:none;
  border:1px solid var(--line); padding:5px 10px; }
.fchip[hidden] { display:none; }
.fchip:hover { color:var(--ink); border-color:var(--line-strong); }
.fchip-genre { font-weight:700; color:var(--ink2); }
/* Owner-only troubleshooting chips: hidden for everyone unless the page is in debug mode
   (localStorage.ct_debug='1', via ctDebug()), when <html> gets the `debug` class.
   Dashed outline + gilt tint marks them apart from the real vocabulary chips. */
.fchip-debug { display:none !important; border-style:dashed; color:var(--gilt-bright); }
html.debug .fchip-debug { display:inline-flex !important; }
.fchip.on { background:var(--curtain); border-color:var(--curtain); color:#FDF6EC; }
/* Exclude (second click): struck through in the curtain colour over a faint wash, so it
   reads as "this tag, removed" and never looks like the solid include chip. */
.fchip.ex { border-color:var(--curtain); color:var(--curtain); text-decoration:line-through;
  text-decoration-thickness:1.5px; background:color-mix(in srgb, var(--curtain) 8%, transparent); }
.fstatus { margin:0; display:flex; align-items:center; gap:14px; font-size:13px; color:var(--ink2);
  border-top:1px solid var(--line); padding-top:10px; }
/* The JS toggles this bar with `status.hidden = !on`, but the [hidden] attribute only
   sets display:none at UA-stylesheet strength and loses to .fstatus{display:flex} above
   — so the bar sat on screen permanently. Same trap the cards hit; they solve it with an
   explicit .hide class, this one needs the attribute selector to outrank the class. */
.fstatus[hidden] { display:none; }
.fclear { appearance:none; cursor:pointer; background:none; border:none; font-family:inherit;
  font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.12em;
  color:var(--curtain); text-decoration:underline; text-underline-offset:3px; padding:0; }
/* --- filtered mode: results collapse into one flat grid --- */
.item.hide { display:none !important; }
/* Stale = the event's last date is before the reader's today (marked client-side on load). Gone
   from every view, permanently for this page load, regardless of the filter engine's own .hide. */
.item.stale { display:none !important; }
/* Filter motion. Excluded cards fade in place before they leave the flow; the
   survivors then glide (FLIP, driven from JS) into their new positions instead of
   snapping. Both stages are skipped entirely under prefers-reduced-motion. */
.item.leaving { opacity:0; transform:scale(.975); pointer-events:none;
  transition:opacity 360ms ease, transform 360ms ease; }
.item.entering { animation:cardIn 900ms cubic-bezier(.16,.84,.24,1) both; }
@keyframes cardIn { from { opacity:0; transform:translate(-16px,28px); } to { opacity:1; transform:none; } }
.results { display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:26px; margin-top:30px; }
/* Same [hidden] trap as .fstatus: without this the empty results grid keeps its
   30px top margin while idle. .noresults needs no such rule — it sets no display. */
.results[hidden] { display:none; }
.panel.filtering .group { display:none; }
.noresults { margin:38px 0; text-align:center; color:var(--muted); font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif;
  font-style:italic; font-size:20px; }
.panel .group:first-of-type .rule-head { margin-top:16px; }   /* tight to the filter chips */
/* The section Featured cutout (a `.group.mag`, the lead pulled out of the month section). The
   gilt box owns its spacing via .mag padding, so zero the header margin the first-of-type rule
   would otherwise add — the "Featured" label hugs the top of the box exactly as in the magazine
   view. 16px from the chip row to the box border — the same breath the All view gives the
   box below a month header's rule (.monthblock's 16px flex gap); the margin COLLAPSES with
   the filter bar's 4px bottom margin (max, not sum), so 16 is the full figure. Two classes
   on purpose: the later `.mag { margin:0 }` rule ties on specificity with a bare `.secfeat`
   and silently zeroed the old margin — the box sat 4px under the chips. */
.group.secfeat { margin-top:16px; }
.panel .group.secfeat > .rule-head { margin:0; }
/* Equal space above (before the top rule) and below (after the bottom rule), reduced. */
.rule-head { margin:26px 0; border-top:1px solid var(--line-strong); border-bottom:1px solid var(--line); padding:10px 0; }
.rule-head h2 { margin:0; font-size:13px; font-weight:700; text-transform:uppercase; letter-spacing:.22em; color:var(--ink2); }
/* Year divider inside the filtered results box. The browsing view stamps its month
   headers with the year only once it leaves the current one ("Opening January 2027");
   a filtered list has no month sections, so when the survivors cross into a later
   year we drop in a full-width year rule to mark the boundary. Year only, no month.
   grid-column:1/-1 spans every column so it reads as a rule, not a card. */
.year-divider { grid-column:1/-1; padding:10px 0; border-top:1px solid var(--line-strong); border-bottom:1px solid var(--line); }
.year-divider.hide { display:none; }
.year-divider span { font-size:13px; font-weight:700; text-transform:uppercase; letter-spacing:.22em; color:var(--ink2); }
.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:26px; }
.card { background:var(--card); border:1px solid var(--line); box-shadow:var(--shadow); display:flex; flex-direction:column;
  /* Off-screen cards skip style/layout/paint entirely — laying out ~860 of them was the
     page's main-thread bill (~2.8s throttled). The intrinsic-size hint keeps the scrollbar
     honest before a card first renders; `auto` remembers the real size afterwards. The
     image loader listens for contentvisibilityautostatechange, so art still promotes the
     moment the browser decides a card is worth rendering. */
  content-visibility:auto; contain-intrinsic-size:auto 480px; }
/* The landing screenful (build-time eager set) renders unconditionally — see card(). */
.card.cv-eager { content-visibility:visible; }
.media { aspect-ratio:16/10; overflow:hidden; background:var(--panel); }
.media img { width:100%; height:100%; object-fit:cover; object-position:center 25%; display:block; }
/* An image still waiting for the lazy loader (data-src, no src yet) would render its alt
   text inside the well in some browsers; the panel background alone is the loading state. */
img[data-src] { color:transparent; }
/* Portrait art: the whole card stays visible, standing on a blurred enlargement of
   itself so the tile still reads as a filled block rather than a letterbox. */
.media-fit { position:relative; overflow:hidden; }
.media-fit .media-bg { position:absolute; inset:-8%; background-size:cover; background-position:center;
  filter:blur(18px) saturate(.75) brightness(.9); opacity:.5; }
.media-fit img { position:relative; width:auto; max-width:100%; height:100%;
  object-fit:contain; margin:0 auto; }
/* designed title card, used when a production has no art of its own */
.titlecard { display:flex; align-items:center; justify-content:center; padding:26px 22px; }
.tc-inner { display:flex; flex-direction:column; align-items:center; gap:13px; width:100%; }
.tc-rule { display:block; width:44px; height:1px; opacity:.5; }
.tc-title { font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif; font-weight:700; line-height:1.08;
  font-size:clamp(22px,3.4vw,32px); text-align:center; text-wrap:balance; letter-spacing:.005em; }
.tc-by { font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif; font-style:italic; font-size:14.5px; opacity:.82; text-align:center; }
.feature .tc-title { font-size:clamp(26px,3.6vw,40px); }
.feature .tc-rule { width:60px; }
.card-body { padding:18px 20px 20px; display:flex; flex-direction:column; gap:8px; flex:1; }
.dates { margin:0; font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.16em;
  color:var(--gilt); font-variant-numeric:tabular-nums; }
.title { margin:0; font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif; font-weight:700; font-size:26px; line-height:1.12; text-wrap:balance; }
.title-xl { font-size:clamp(30px,4vw,42px); }
.byline { margin:0; font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif; font-style:italic; font-size:16px; color:var(--ink2); }
.meta { margin:0; font-size:13px; color:var(--muted); }
/* Company / venue / neighbourhood are filter entry points. They keep the muted meta
   styling so a card still scans as prose — a dotted underline is the only affordance
   until hover, at which point they go crimson like every other link on the page. */
.facet { appearance:none; background:none; border:none; padding:0; margin:0; font:inherit;
  color:inherit; cursor:pointer; text-decoration:underline; text-decoration-style:dotted;
  text-decoration-thickness:1px; text-underline-offset:3px; text-decoration-color:var(--line-strong); }
.facet:hover { color:var(--curtain); text-decoration-style:solid; text-decoration-color:currentColor; }
.facet:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:2px; border-radius:2px; }
@media (hover: none) { .facet { text-decoration-color:var(--muted); } }
.summary { margin:4px 0 0; color:var(--ink2); }
.sched { margin:2px 0 0; font-size:13px; font-weight:600; color:var(--ink2); line-height:1.45; }
.summary-none { color:var(--muted); font-style:italic; }
.price { margin:2px 0 0; font-size:13.5px; font-weight:600; color:var(--gilt); }
.card-foot { margin-top:auto; padding-top:14px; display:flex; align-items:flex-end; justify-content:space-between; gap:12px; }
.foot-actions { display:flex; align-items:center; gap:6px; flex:0 0 auto; }
/* Per-card "copy link" — a quiet chain glyph beside Details; flashes a "Link copied" tooltip. */
.permalink { appearance:none; background:none; border:none; padding:5px; cursor:pointer; line-height:0;
  color:var(--muted); border-radius:5px; position:relative; display:inline-block; text-decoration:none; }
.permalink:hover { color:var(--curtain); }
.permalink svg { width:17px; height:17px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; display:block; }
.permalink:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:1px; }
/* "Open in Maps" pin — same quiet meta treatment as the copy-link chain, sitting to its left. */
.mapbtn { display:inline-flex; padding:5px; line-height:0; color:var(--muted); border-radius:5px; }
.mapbtn:hover { color:var(--curtain); }
.mapbtn svg { width:17px; height:17px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; display:block; }
.mapbtn:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:1px; }
.permalink.copied::after { content:"Link copied"; position:absolute; bottom:calc(100% + 7px); left:50%; transform:translateX(-50%);
  white-space:nowrap; font-family:"Libre Franklin","Franklin Fallback",Arial,sans-serif; font-size:10px; font-weight:700; letter-spacing:.08em;
  text-transform:uppercase; color:var(--paper); background:var(--ink); padding:4px 8px; border-radius:4px; pointer-events:none; }
/* An event scrolled to via its share link gets a brief gilt ring so it stands out on arrival. */
.item.anchored { outline:3px solid var(--gilt-bright); outline-offset:3px; border-radius:2px; }
/* Corner overlays: the New badge and the Like heart both sit over the card's art. */
.item { position:relative; }
.badge-new { position:absolute; top:9px; left:9px; z-index:3; font-size:9.5px; font-weight:700;
  letter-spacing:.09em; text-transform:uppercase; color:#FDF6EC; background:var(--curtain);
  padding:3px 7px; border-radius:2px; box-shadow:0 1px 3px rgba(0,0,0,.28); pointer-events:none; }
.like { position:absolute; top:7px; right:7px; z-index:3; appearance:none; cursor:pointer;
  width:32px; height:32px; padding:5px; border:none; border-radius:50%; color:#FDF6EC;
  background:rgba(20,15,12,.34); -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px);
  line-height:0; transition:transform .12s ease, background .12s ease; }
.like svg { width:100%; height:100%; fill:none; stroke:currentColor; stroke-width:2; display:block; }
.like .ic-break { display:none; }
.like:hover { background:rgba(20,15,12,.55); transform:scale(1.08); }
.like[aria-pressed="true"] { color:var(--curtain); background:rgba(255,253,248,.94); }
.like[aria-pressed="true"] svg { fill:currentColor; stroke:currentColor; }
.like:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:2px; }
/* Disliked (a second click on a liked heart "breaks" it): the button shows the broken-heart
   OUTLINE — never filled — and the card is dimmed like a struck-through Letterboxd entry, but the
   heart stays crisp so it can be clicked again. Fully hidden only when "Hide Disliked" is on
   (html.hide-disliked, via the filter pass). */
.item[data-disliked="1"] .like .ic-heart { display:none; }
.item[data-disliked="1"] .like .ic-break { display:block; }
.item[data-disliked="1"] .like { color:var(--ink2); background:rgba(255,253,248,.9); }
.item[data-disliked="1"] .like svg { fill:none; stroke:currentColor; }
.item[data-disliked="1"] > *:not(.like) { opacity:.42; filter:grayscale(.4);
  transition:opacity .18s ease, filter .18s ease; }
/* The "already started" grey fade (2026-09-18) was ROLLED BACK on 2026-09-21 (Wes): a card whose
   curtain has gone up no longer dims. paintPast() below is a no-op that clears the class. */
/* The public "New" and "Liked" chips lead the filter row, accented so they read as the
   primary controls before the vocabulary chips. */
.fchip-new, .fchip-liked { font-weight:700; color:var(--curtain); border-color:var(--curtain); }
.fchip-new.on, .fchip-liked.on { background:var(--curtain); border-color:var(--curtain); color:#FDF6EC; }
/* The Liked tab sits immediately after the last listing tab; its view is a plain saved grid. */
.tab-liked { display:inline-flex; align-items:center; gap:6px; }
.tab-liked svg { width:15px; height:15px; fill:none; stroke:currentColor; stroke-width:2; }
.tab-liked .ic-break { display:none; }   /* HEART_USE ships both glyphs; the tab only shows the intact heart (not the broken one) */
.tab-liked.has-likes svg { fill:var(--curtain); stroke:var(--curtain); }
.likedview { margin-top:30px; }
.likedview[hidden] { display:none; }
.likedgrid { display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:26px; }

/* ---------- Compact mode (html.compact) ----------
   Folds EVERY card down to image + date + title + byline, all at once, via one root class so
   the grid stays even (per-card folding would ragged the rows). Column WIDTH is untouched — only
   height changes: below-title content is hidden, the title is clamped to 2 lines, and portrait art
   is cropped to the same 16:10 well as everything else, so folded tiles are one clean height.
   A single floating toggle (#compact-toggle) flips it; persisted in ct_compact and set pre-paint
   (head boot) so there is no flash. Instant, no animation — a 900-card fold transition is the one
   thing here that would actually cost. */
/* Hide only below-the-meta content. venue + neighbourhood live in .meta (via bycompany), so it
   is KEPT — a folded tile is image + date + title + byline + venue/location. */
html.compact .summary,
html.compact .sched,
html.compact .price,
html.compact .card-foot { display:none !important; }
html.compact .card-body,
html.compact .feature-body { min-height:96px; padding:11px 15px 13px; gap:4px; justify-content:flex-start; }
html.compact .dates { font-size:11px; }
html.compact .title { font-size:18px; line-height:1.18;
  display:-webkit-box; -webkit-line-clamp:1; -webkit-box-orient:vertical; overflow:hidden; }
html.compact .byline { font-size:13.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
html.compact .meta { font-size:12.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* FIXED image height (not an aspect ratio): the whole compact card must be one fixed, viewport-
   independent height so the contain-intrinsic-size reservation below can match it EXACTLY — a 2:1
   ratio made card height scale with column width, so any single reserved number was wrong at most
   viewports and cards stuttered as they rendered. Crop still anchors to the TOP (bottom trimmed). */
html.compact .media { height:165px; aspect-ratio:auto; max-height:none; }
/* Still crop to fill (no wide letterbox borders), but LESS off the bottom than before: a slightly
   taller well plus `object-position:center` splits the trim top and bottom instead of taking it all
   from the bottom — so a poster's title survives without a stark border (Wes, 2026-08-29). */
html.compact .media img,
html.compact .media-fit img { object-fit:cover; object-position:center; width:100%; height:100%; }
html.compact .media-fit .media-bg { display:none; }
html.compact .titlecard { padding:10px 14px; }
html.compact .tc-title { font-size:16px; line-height:1.1;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
/* Every compact card is forced to EXACTLY 260px (150px image + 110px body, content clipped) and the
   off-screen reservation is the same fixed 260 — note NO `auto`. Two things fall out of this:
   (1) reserved == actual for every card at every viewport, so nothing shrinks/grows on render → no
   scroll stutter; (2) a FIXED value (no `auto`) is transparent to content-visibility's per-card size
   memory, so switching modes leaves no stale height behind — full keeps its own `auto 480` and stays
   smooth. The reservation CHANGE on toggle would shift the off-screen layout, but the toggle's
   synchronous scroll-anchor measures the post-change layout and compensates, so the page stays put. */
html.compact .card { height:275px; overflow:hidden; contain-intrinsic-size:275px; }
/* A FIXED 260 off-screen reservation (matching the folded height, no `auto`) so a page that
   LOADS in compact — the mobile default — reserves exactly what each card renders to, and
   below-fold tiles don't shrink 480->260 as they scroll in. On a toggle, ct-eager disables
   content-visibility entirely, so this is ignored there; it matters only for the load-in-compact
   path, where it keeps rendering lazy (fast) AND stutter-free. */
/* After the reader FIRST uses compact, render EVERY card eagerly for the rest of the session (the
   toggle adds html.ct-eager). This is what removes the cross-mode scroll stutter in BOTH directions:
   content-visibility:auto reserves a single remembered height per card, which goes stale across a
   fold/unfold and shrinks/grows on render (unfixable while lazy-rendering is on — the reservation is
   always "auto"). Eager cards have no reservation to go stale. Doing it only AFTER the first toggle
   keeps the INITIAL load lazy and fast (the whole point of content-visibility) for the majority who
   never open compact; a reader who opts in pays a one-time layout at that toggle and then scrolls
   smoothly in either mode for the session. */
html.ct-eager .card { content-visibility:visible; }
/* Compact used to drop the Featured carousel (2026-08-29); since 2026-09-18 it shows at every
   density (Wes: "a carousel at all densities"). The section panels' own marquees are still folded
   to plain cards by asCard in the filter pass. `.allfeature` is the hidden build-time source. */
html.compact .allfeature { display:none !important; }
/* A carousel SLIDE at Compact/Dense is still the marquee, not a folded tile: its art keeps a real
   2:1 well (not the tile's fixed 165px/100px crop, which cut posters off mid-title), the title stays
   marquee-sized, and the body is the folded set — date, title, venue — vertically centred beside
   the art. The summary stays hidden as everywhere in Compact; a tap on the title opens the popup. */
html.compact .feature .media, html.compact .feature .media-wide { height:auto; aspect-ratio:2/1; max-height:360px; }
html.compact .feature .media img { object-fit:cover; object-position:center top; }
html.compact .feature-body { justify-content:center; padding:16px 20px; gap:6px; }
html.compact .feature .title-xl { font-size:clamp(21px,2.6vw,28px); line-height:1.12; }
/* While the compact toggle is switching, kill all motion so the relayout is instant and the
   toggle's own scroll-anchor lands cleanly (no FLIP glide fighting the scroll correction). */
html.ct-switching, html.ct-switching * { transition:none !important; animation:none !important; }
/* DENSE — a third density below Compact (Wes, 2026-08-29). A dense card ALSO carries html.compact, so
   it inherits every fold / feat-off / eager-render / tap-to-expand behaviour; these rules only add a
   tighter grid (2 across on phones, up to 6 on wide desktop) and shrink the tile further — dropping
   the byline and cropping more off the image. Declared AFTER the compact block so they out-order the
   equal-specificity html.compact rules they refine. */
html.dense .grid, html.dense .results, html.dense .likedgrid { grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; }
@media (min-width:600px) { html.dense .grid, html.dense .results, html.dense .likedgrid { grid-template-columns:repeat(3,minmax(0,1fr)); } }
@media (min-width:900px) { html.dense .grid, html.dense .results, html.dense .likedgrid { grid-template-columns:repeat(4,minmax(0,1fr)); } }
@media (min-width:1200px) { html.dense .grid, html.dense .results, html.dense .likedgrid { grid-template-columns:repeat(6,minmax(0,1fr)); } }
html.dense .media { height:100px; }                    /* shorter than compact's 165 — more edge cropped off */
html.dense .card { height:214px; contain-intrinsic-size:214px; }  /* 100 image + 114 body: fits 2-line date + 2-line title + venue */
/* NEW COMPACT (Wes, 2026-08-29) — the mobile default and a distinct 4th density. Compact's folded
   165px art in a DENSER responsive grid (2 cols on phones, up to 5 on wide desktop) with a 2-line
   scaled body (byline dropped) so the narrower tiles never clip. Distinct from OLD COMPACT
   (html.compact.c1), which keeps the original single-wide grid + 1-line title + byline. Applies at
   every width; it's roomier than Dense (bigger art, one fewer column) and denser than Old Compact. */
html.compact:not(.dense):not(.c1) .grid,
html.compact:not(.dense):not(.c1) .results,
html.compact:not(.dense):not(.c1) .likedgrid { grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; }
@media (min-width:600px) { html.compact:not(.dense):not(.c1) .grid, html.compact:not(.dense):not(.c1) .results, html.compact:not(.dense):not(.c1) .likedgrid { grid-template-columns:repeat(3,minmax(0,1fr)); } }
@media (min-width:900px) { html.compact:not(.dense):not(.c1) .grid, html.compact:not(.dense):not(.c1) .results, html.compact:not(.dense):not(.c1) .likedgrid { grid-template-columns:repeat(4,minmax(0,1fr)); } }
@media (min-width:1300px) { html.compact:not(.dense):not(.c1) .grid, html.compact:not(.dense):not(.c1) .results, html.compact:not(.dense):not(.c1) .likedgrid { grid-template-columns:repeat(5,minmax(0,1fr)); } }
html.compact:not(.dense):not(.c1) .card-body { padding:9px 11px; gap:2px; }
html.compact:not(.dense):not(.c1) .byline { display:none; }
html.compact:not(.dense):not(.c1) .title { font-size:15px; line-height:1.2; -webkit-line-clamp:2; max-height:2.4em; }
html.compact:not(.dense):not(.c1) .meta { font-size:11.5px; }
html.compact:not(.dense):not(.c1) .tc-title { font-size:14px; -webkit-line-clamp:2; max-height:2.4em; }
html.dense .card-body { padding:8px 10px; gap:2px; min-height:0; }
html.dense .byline { display:none; }                   /* drop the byline to save a line */
html.dense .dates { font-size:10px; }
/* Self-contained clamp: -webkit-line-clamp only clips when display is -webkit-box, so set it here
   explicitly rather than leaning on the compact rule (a long title was overrunning its 2 lines). */
html.dense .title { font-size:14px; line-height:1.2; display:-webkit-box; -webkit-box-orient:vertical;
  -webkit-line-clamp:2; overflow:hidden; max-height:2.4em; }   /* max-height = hard 2-line clip even where -webkit-box fails */
html.dense .meta { font-size:11px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
html.dense .titlecard { padding:7px 10px; }
html.dense .tc-title { font-size:13px; line-height:1.2; display:-webkit-box; -webkit-box-orient:vertical;
  -webkit-line-clamp:3; overflow:hidden; max-height:3.6em; }
/* A carousel SLIDE's title at every folded density: marquee-sized, two WHOLE lines. The tile rules
   above (New Compact's 15px, Dense's line-height with a 2.4em cap) out-specify or out-order
   html.compact .feature .title-xl, which shrank the marquee title to tile size in New Compact and
   left a sliver of a third line in Dense. This selector ties New Compact's on specificity and wins
   on order; max-height is exactly two line-heights so no partial line shows even where the clamp
   does not engage. */
html.compact .feature .title.title-xl { font-size:clamp(21px,2.6vw,28px); line-height:1.15;
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; overflow:hidden; max-height:2.3em; }
/* DESKTOP: the carousel is FULL VIEW at every density (Wes, 2026-09-28: "on desktop, featured
   item carousel items should be full view no matter what the density setting"). Every rule above
   that folds a slide (hidden summary/price/foot, 2:1 art, 28px clamped title, tile-sized dates,
   byline and meta, Dense's title card) is reset here to the Full-mode values from the base rules,
   measured against Full in the browser. #allgrid out-specifies every html.compact / .dense / New
   Compact selector, and the !important ones answer html.compact's own !important hides. Phones
   (<=760px) keep the folded slide, which is what fits a phone. */
@media (min-width:761px) {
  html.compact #allgrid .mag-featured .feat-hold .feature .media,
  html.compact #allgrid .mag-featured .feat-hold .feature .media-wide { height:auto; aspect-ratio:4/3; max-height:500px; }
  html.compact #allgrid .mag-featured .feat-hold .feature .feature-body { min-height:0; padding:26px 28px; gap:9px;
    justify-content:flex-start; }
  html.compact #allgrid .mag-featured .feat-hold .feature .title.title-xl { font-size:clamp(30px,4vw,42px); line-height:1.12;
    display:block; -webkit-line-clamp:unset; max-height:none; overflow:visible; padding-bottom:0; }
  html.compact #allgrid .mag-featured .feat-hold .feature .dates { font-size:12px; }
  html.compact #allgrid .mag-featured .feat-hold .feature .byline { display:block; font-size:16px; white-space:normal; overflow:visible; }
  html.compact #allgrid .mag-featured .feat-hold .feature .meta { font-size:13px; white-space:normal; overflow:visible; }
  html.compact #allgrid .mag-featured .feat-hold .feature .summary { display:block !important; }
  html.compact #allgrid .mag-featured .feat-hold .feature .sched { display:block !important; }
  html.compact #allgrid .mag-featured .feat-hold .feature .price { display:block !important; }
  html.compact #allgrid .mag-featured .feat-hold .feature .card-foot { display:flex !important; }
  html.compact #allgrid .mag-featured .feat-hold .feature .titlecard { padding:26px 22px; }
  html.compact #allgrid .mag-featured .feat-hold .feature .tc-title { font-size:clamp(26px,3.6vw,40px); line-height:1.08;
    display:block; -webkit-line-clamp:unset; max-height:none; overflow:visible; padding-bottom:0; }
}

/* The toggle floats so it is reachable at any scroll depth (the filter bar is not sticky), and it
   is ONE element, not one per card. */
#compact-toggle { position:fixed; right:16px; bottom:16px; z-index:60; display:inline-flex;
  align-items:center; gap:7px; padding:9px 14px; border-radius:999px; cursor:pointer; font:inherit;
  font-size:13px; font-weight:600; letter-spacing:.02em; color:var(--ink); background:var(--card);
  border:1px solid var(--line-strong); box-shadow:var(--shadow); }
#compact-toggle:hover { border-color:var(--curtain); }
#compact-toggle svg { display:block; }
html.compact #compact-toggle { color:var(--paper); background:var(--curtain); border-color:var(--curtain); }
@media (prefers-reduced-motion: no-preference) { #compact-toggle { transition:border-color .15s ease, background .15s ease; } }
/* Phone: the label is dropped, so the button is icon-only — size it to Apple's 44x44pt minimum
   (16px icon + 14px padding each side) instead of the 36x36 it used to be, and sit it further in
   from the corner. At right:12px/bottom:12px it was both under-sized and pressed into the edge,
   where a thumb reaching across the phone competes with iOS's own edge gestures (Wes, 2026-09-01:
   "hard to hit with my thumb at the edge of the phone"). The mode menu moves up to clear it. */
@media (max-width:760px) {
  #compact-toggle { right:16px; bottom:20px; padding:14px; }
  #compact-toggle .ct-lbl { display:none; }
}
/* Pick-a-mode menu: right-click / long-press the density toggle to jump straight to a density.
   Sits just above the toggle, right-aligned; the current mode carries a check. */
.ctmode-menu { position:fixed; right:16px; bottom:60px; z-index:61; display:flex; flex-direction:column;
  min-width:158px; padding:5px; background:var(--paper); border:1px solid var(--line-strong);
  border-radius:10px; box-shadow:0 8px 26px var(--shadow);
  font-family:"Libre Franklin","Franklin Fallback",Arial,sans-serif; }
.ctmode-menu[hidden] { display:none; }
.ctmode-menu button { appearance:none; background:none; border:none; text-align:left; cursor:pointer;
  padding:9px 13px 9px 31px; margin:0; font-size:13.5px; font-weight:500; line-height:1; color:var(--ink);
  border-radius:6px; position:relative; white-space:nowrap; }
.ctmode-menu button:hover { background:var(--panel); color:var(--curtain); }
.ctmode-menu button[aria-checked="true"] { color:var(--curtain); font-weight:700; }
.ctmode-menu button[aria-checked="true"]::before { content:"✓"; position:absolute; left:12px; font-weight:700; }
.ctmode-menu button:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:-1px; }
/* Phone: follow the density button, which is now 46px tall at bottom:20px — its top edge is 66px
   up, so the menu clears it at 74px (was right:12px/bottom:58px, tuned to the old 36px button and
   now overlapping it). */
@media (max-width:760px) { .ctmode-menu { right:16px; bottom:74px; } }

/* ---------- Tap-to-expand (compact mode) ----------
   A folded tile shows only image + date + title + byline + venue. Tapping the TITLE floats the
   full card — summary, price, chips, tickets — in a centred lightbox over a dimmed page. The card
   content is already in the DOM (compact only CSS-folds it), so the popup CLONES the tile and the
   rules below simply UNDO the fold on that clone; the app's delegated handlers keep like/tickets/
   chips live on the clone (they key on data-id, exactly as the All/Liked clones already do). */
html.compact .item .title, html.compact .item .tc-title { cursor:pointer; }
/* Hover affordance so a folded title reads as the tap target it is (desktop/precision pointers). */
html.compact .item .title:hover, html.compact .item .tc-title:hover { text-decoration:underline; text-underline-offset:3px; text-decoration-thickness:1px; }
/* On touch there is no :hover to reveal the affordance, and compact is the touch (mobile) default —
   so the folded title, the tap target that opens the full card, carries a persistent, subtle
   underline there (the same dotted cue the facet links use). Hover pointers keep the reveal above. */
@media (hover: none) {
  html.compact .item .title, html.compact .item .tc-title {
    text-decoration:underline; text-decoration-style:dotted; text-decoration-thickness:1px;
    text-underline-offset:3px; text-decoration-color:var(--muted); }
}
.cardpop-slot .title, .cardpop-slot .tc-title { text-decoration:none !important; }
/* -webkit-line-clamp forces overflow:hidden on the title, and that clip cuts off an underline
   drawn below the baseline (the box is exactly the text height, ~2px shy of the line height). So
   reserve a few px beneath the clamped line for the hover/touch underline to live in. Absorbed by
   the fixed-height compact card, so it shifts nothing visible. */
html.compact .item .title, html.compact .item .tc-title { padding-bottom:4px; }
html.pop-lock { overflow:hidden; }
/* RHRN map view. Full-screen mode below the lightbox (cardpop z-index 200): a slim top bar (Back +
   live status + distance slider) over a map that fills the rest. Leaflet + its CSS are lazy-loaded
   on first open, so none of this touches the base page weight. */
html.map-lock { overflow:hidden; }
#rhrn-map-wrap { position:fixed; inset:0; z-index:120; background:var(--paper,#fff); display:flex; flex-direction:column; }
#rhrn-map-wrap[hidden] { display:none; }
#rhrn-map-bar { flex:0 0 auto; display:flex; flex-direction:column; gap:7px; padding:8px 13px;
  background:var(--panel,#eee); border-bottom:1px solid var(--line,#ccc); }
.mapbar-top { display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:10px; }
#rhrn-map-close { justify-self:start; padding:7px 13px; border:1px solid var(--line-strong,#999); border-radius:20px;
  background:var(--card,#fff); color:var(--ink,#222); font:600 13px/1 inherit; cursor:pointer; }
.mapbar-title { justify-self:center; font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif;
  font-weight:700; font-size:20px; color:var(--ink,#222); white-space:nowrap; }
#rhrn-map-status { justify-self:end; font-size:12.5px; color:var(--ink2,#555); white-space:nowrap; }
.mapbar-slide { display:flex; align-items:center; gap:12px; }
.mapbar-slide input[type=range] { flex:1 1 auto; width:auto; min-width:0; accent-color:var(--curtain,#A6212C); }
#rhrn-map-out { flex:0 0 auto; font-size:14px; font-weight:600; color:var(--ink,#222); min-width:56px; text-align:right; white-space:nowrap; }
#rhrn-map { flex:1 1 auto; width:100%; min-height:0; }
@media (max-width:440px) { .mapbar-title { font-size:18px; } }
.frhrn-mapbtn { display:inline-flex; align-items:center; gap:5px; padding:0 12px; height:34px; border:1px solid var(--line-strong,#999);
  border-radius:18px; background:var(--card,#fff); color:var(--ink,#222); font:600 13px/1 inherit; cursor:pointer; }
.frhrn-mapbtn[hidden] { display:none; }
.frhrn-mapbtn svg { opacity:.85; }
.ct-mk { background:none; border:none; }
.ct-mk-dot { display:block; width:16px; height:16px; border-radius:50%; background:var(--curtain,#A6212C);
  border:2px solid #fff; box-shadow:0 1px 4px rgba(0,0,0,.45); }
/* Zoom-in reveals the dot as a folded-tile thumbnail (image + one-line title). */
.ct-tile-mk { background:none; border:none; }
.ct-tile { display:block; width:132px; background:var(--card,#fff); border:1px solid var(--line-strong,#999);
  border-radius:9px; overflow:hidden; box-shadow:0 3px 11px rgba(0,0,0,.42); cursor:pointer; }
.ct-tile-img { display:block; width:100%; height:62px; background-size:cover; background-position:center top; background-color:var(--panel,#ddd); }
.ct-tile-t { display:block; padding:5px 8px; font:600 12px/1.25 inherit; color:var(--ink,#222); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.leaflet-container { font:inherit; background:var(--panel,#eee); }
.cardpop { position:fixed; inset:0; z-index:200; display:flex; align-items:center; justify-content:center; padding:20px; }
.cardpop[hidden] { display:none; }
.cardpop-back { position:absolute; inset:0; background:rgba(0,0,0,.55); }
.cardpop-inner { position:relative; z-index:1; width:100%; max-width:400px; max-height:88vh; overflow-y:auto;
  background:var(--card); border:1px solid var(--line-strong); box-shadow:0 24px 70px rgba(0,0,0,.45); }
.cardpop-x { position:absolute; top:8px; left:8px; z-index:3; width:30px; height:30px; padding:0; border:none;
  border-radius:999px; cursor:pointer; font-size:20px; line-height:30px; text-align:center; color:#fff;
  background:rgba(0,0,0,.5); }
.cardpop-x:hover { background:rgba(0,0,0,.72); }
/* The New badge shares the art's top-left corner with the ✕ and painted over it (Wes, 2026-09-28).
   The card the reader tapped already showed it, so the popup copy simply drops it. */
.cardpop-slot .badge-new { display:none !important; }
/* Force the clone to render in full. On the mobile-default compact path (no ct-eager) the base
   card keeps content-visibility:auto, which size-CONTAINS the clone to its 260px intrinsic
   reservation and clips everything below — most visibly on the tall featured poster. */
.cardpop-slot .card { box-shadow:none; border:none; content-visibility:visible !important; contain-intrinsic-size:auto !important; }
.cardpop-slot .title, .cardpop-slot .tc-title { cursor:auto !important; }
/* Undo the compact fold on the cloned card (compact's display:none rules carry !important, so the
   overrides need the html.compact prefix to out-specify them). */
html.compact .cardpop-slot .card { height:auto !important; overflow:visible !important; }
html.compact .cardpop-slot .card-body { min-height:0 !important; }
/* Keep the popup image cropped to the SAME 16:10 as normal mode instead of letting it grow to its
   natural (tall) height — the vertical image growth was the main reason the popup needed scrolling.
   The compact `object-fit:cover` already crops it to fill; capping the height keeps a very tall
   poster from dominating. (Wes, 2026-08-29.) */
html.compact .cardpop-slot .media { aspect-ratio:16/10 !important; height:auto !important; max-height:38vh !important; }
html.compact .cardpop-slot .media img { object-fit:cover !important; object-position:center !important; height:100% !important; }
html.compact .cardpop-slot .summary { display:block !important; }
html.compact .cardpop-slot .sched { display:block !important; }
html.compact .cardpop-slot .price { display:block !important; }
html.compact .cardpop-slot .card-foot { display:flex !important; }
html.compact .cardpop-slot .title, html.compact .cardpop-slot .tc-title {
  display:block !important; -webkit-line-clamp:unset !important; overflow:visible !important; }
html.compact .cardpop-slot .byline, html.compact .cardpop-slot .meta {
  white-space:normal !important; overflow:visible !important; }

.likedempty { color:var(--ink2); font-size:15px; line-height:1.6; padding:34px 0; max-width:44ch; }

/* ---- All tab: uber-chips + magazine date sections ---------------------- */
/* In All mode the whole tab bar folds away and the uber-chips (which include an "All" chip)
   take its place. The tabs explode into confetti that recongeals into the chips, and clicking
   the "All" chip runs it in reverse back to the tabs — handled by confettiMorph() in the JS. */
.uberchips { display:none; flex-wrap:wrap; gap:8px; margin:0 0 2px; }
html.allmode .uberchips { display:flex; }
html.allmode .tabs { display:none; }
/* Same look as the .fchip vocabulary chips — just larger. */
.uber { appearance:none; cursor:pointer; font-family:inherit; font-size:14px; font-weight:700;
  text-transform:uppercase; letter-spacing:.12em; color:var(--ink2); background:none;
  border:1px solid var(--line-strong); padding:9px 17px;
  transition:opacity .18s ease, background .14s, color .14s, border-color .14s; }
.uber:hover { color:var(--ink); border-color:var(--ink2); }
.uber.on { background:var(--curtain); border-color:var(--curtain); color:#FDF6EC; }
.uber:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:2px; }
/* The calendar toggle is an icon-only square, same height as the text chips. */
.uber-cal { display:inline-flex; align-items:center; justify-content:center; padding:9px; }
.uber-cal svg { width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:2; display:block; }
/* confettiMorph toggles opacity inline as the particles land; the transition makes the
   reveal a soft fade (and covers reduced-motion, where the particles are skipped). */
.tabs .tab { transition:opacity .18s ease; }

.allview { margin-top:10px; }
.allview[hidden] { display:none; }
.allfeature { display:none; }   /* build-time source; the card is moved into the Featured box */
.feat-hold { margin-top:11px; }
.allgrid { display:flex; flex-direction:column; gap:16px; }
/* A collapsed far block (windowing, ?windowed=1) stands in for its block at exactly the height
   the block measured. One flex item swapped for another of the same height leaves the document
   height — and therefore every scroll offset below it — untouched. */
.allspacer { flex:0 0 auto; }
/* Plain view (calendar toggle OFF): one date-sorted grid, year dividers only. */
.allplain { display:none; }
.allplain > .grid { margin-top:4px; }
#panel-all.plain #allgrid { display:none; }
#panel-all.plain #allplain { display:block; }
.allempty { color:var(--ink2); font-size:15px; padding:34px 0; }
.allempty[hidden] { display:none; }
/* Spacing in the All view is set entirely by uniform FLEX GAPS on the nesting containers,
   never per-element margins — so the space above a month header (the gap between month blocks)
   equals the space below it (the gap to its first section), with no margin-collapse surprises.
   Each date section = a header (reusing .rule-head) over a .grid of cards. */
.allsec { margin:0; }
.allsec.hide { display:none; }
/* The magazine is spaced ENTIRELY by the flex gaps of its month/group/week wrappers — the base
   .rule-head's 26px margins (right for the normal grouped view) would ADD to those gaps here and
   blow out the space around the Next Week / TBA headers, so kill them inside the grid. */
.allgrid .rule-head { margin:0; }
.allsec > .grid { margin-top:11px; }
.allsec > .grid:first-child { margin-top:0; }   /* untitled section (no header): no wasted top */
.allsec .rule-head h2 .seccount { font-weight:600; color:var(--muted); margin-left:8px; letter-spacing:.08em; }
/* Magazine sections: a tinted box, bordered and shadowed so it reads as cut out of the page.
   Confined to the content column (no full-bleed), and hugging its header at the top. */
.mag { border:1px solid var(--line-strong); border-radius:9px; box-shadow:0 2px 12px rgba(32,27,21,.10);
  padding:7px 22px 22px; margin:0; }
/* Equal distance: header text -> lower rule (padding-bottom + border) == lower rule -> first
   item (grid/feat-hold margin-top). Kill the base .rule-head bottom margin that broke it. */
.mag > .rule-head { margin:0; }
.mag > .rule-head h2 { line-height:1.2; }
.mag > .grid { margin-top:11px; }
.mag-today { background:var(--mag-today); }
.mag-tomorrow { background:var(--mag-tomorrow); }
.mag-weekend { background:var(--mag-weekend); }
/* Ongoing (runs on now) leads the All view — a quiet neutral box, no colour. */
.mag-now { background:color-mix(in srgb, var(--ink) 5%, var(--paper)); }
/* Featured box: the hero, in its own gilt-tinted cutout where Ongoing would otherwise lead. */
.mag-featured { background:color-mix(in srgb, var(--gilt) 10%, var(--paper)); }
/* Featured CAROUSEL — All view only (Wes, 2026-08-28). The one #allgrid Featured box holds
   FEAT_N feature cards on a horizontal scroll-snap track: finger-swipe on touch, two-finger
   horizontal on a trackpad, both native. Scoped to #allgrid so the per-section marquees (a plain
   single .feat-hold in render_tab) are untouched. */
/* Any narrowing filter (not the uber section-chips) hides the whole Featured carousel — its picks
   are prestige-chosen and would not respect the filter. Dedicated class so collapse() can't undo it. */
#allgrid .mag-featured.feat-off { display:none !important; }
#allgrid .mag-featured .feat-hold { display:flex; align-items:flex-start; overflow-x:auto; overflow-y:hidden;
   scroll-snap-type:x mandatory; scroll-behavior:smooth;
   scrollbar-width:none; transition:height .18s ease; }
/* align-items:flex-start lets each slide keep its OWN height (no stretch-to-tallest, which left a
   short card padded with whitespace to match a wordy neighbour). The JS then sets the track height
   to the slide in view, so the carousel is exactly as tall as what you're looking at. */
#allgrid .mag-featured .feat-hold::-webkit-scrollbar { display:none; }
#allgrid .mag-featured .feat-hold > .feature { flex:0 0 100%; min-width:0; scroll-snap-align:center; }
.featdots { display:flex; gap:9px; justify-content:center; align-items:center; margin-top:14px; }
.featdots[hidden] { display:none; }
.featdot { width:8px; height:8px; padding:0; border:none; border-radius:50%; background:currentColor;
   color:var(--ink); opacity:.26; cursor:pointer; transition:opacity .2s, transform .2s; }
.featdot:hover { opacity:.55; }
.featdot.active { opacity:1; transform:scale(1.15); }
@media (prefers-reduced-motion: reduce) { #allgrid .mag-featured .feat-hold { scroll-behavior:auto; } }
/* A group wraps Next Week (a header over its week/weekend). */
.allgroup { margin:0; display:flex; flex-direction:column; gap:12px; }
.grouphide.hide { display:none; }
.weekblock { margin:0; display:flex; flex-direction:column; gap:10px; }
/* A month block groups everything the non-broken-week rule assigns to one month, under a
   full-width month header. Flex gap = same value as .allgrid's gap, so the header's top and
   bottom spacing match. */
.monthblock { margin:0; display:flex; flex-direction:column; gap:16px; }
.monthhead { border-bottom:2px solid var(--line-strong); padding-bottom:8px; margin:0; }
.monthhead h2 { font-size:17px; color:var(--ink); letter-spacing:.14em; }
/* Week subsection header. Same weight as "Next Week" (2026-09-04) — both are week-level headings
   and reading one as a heading and the other as a caption made the listing look like it changed
   structure halfway down. The rules above and below come from .rule-head itself. */
.weekhead { margin:0; }
.allgroup > .rule-head h2,
.weekblock > .rule-head h2 { font-size:15px; color:var(--ink); letter-spacing:.16em; }
/* A weekend subsection sits inside a week; same rose tint, its own header. */
.weekend-sub { margin:0; }
.weekend-sub .rule-head h2 { font-size:11px; }
.chips { display:flex; flex-wrap:wrap; gap:6px; }
/* Card chips are filter entry points, like .facet. Kept visually quiet until hover
   so a card still scans as prose rather than as a row of buttons. */
.chip { appearance:none; background:none; font:inherit; cursor:pointer;
  font-size:10.5px; text-transform:uppercase; letter-spacing:.12em; font-weight:600;
  border:1px solid var(--line); color:var(--muted); padding:3px 8px; }
.chip:hover { color:var(--ink); border-color:var(--line-strong); }
.chip.on { background:var(--curtain); border-color:var(--curtain); color:#FDF6EC; }
.chip:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:1px; }
.chip-genre { border-color:var(--ink2); color:var(--ink); font-weight:700; }
/* Per-item category label (Plays / Musicals / Classical / …). Echoes the "on" uber-chip
   (curtain fill) so a card in the mixed All list carries the same badge as the section it
   came from. A quiet <span>, not a filter button. Hidden everywhere by default; revealed
   only inside the All view, and only when more than one section is selected (html.multicat) —
   with a single section active the label is redundant, so it stays off. */
.chip-cat { display:none; appearance:none; align-items:center; cursor:pointer; font:inherit;
  font-size:10.5px; text-transform:uppercase; letter-spacing:.12em; font-weight:700;
  border:1px solid var(--curtain); background:var(--curtain); color:#FDF6EC; padding:3px 8px; }
.chip-cat:hover { background:transparent; color:var(--curtain); }
.chip-cat:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:1px; }
html.multicat #allgrid .chip-cat,
html.multicat #allplain .chip-cat,
html.multicat .allfeature .chip-cat { display:inline-flex; }
.tix { flex:0 0 auto; text-decoration:none; font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.14em;
  color:var(--paper); background:var(--ink); padding:9px 16px; }
.tix:hover { background:var(--curtain); color:#FBF8F1; }
.tix-red { background:var(--curtain); color:#FBF8F1; }
.tix-red:hover { background:var(--ink); color:var(--paper); }
.feature { display:grid; grid-template-columns:minmax(0,7fr) minmax(0,5fr); background:var(--card);
  border:1px solid var(--line); box-shadow:var(--shadow); }
.media-wide { aspect-ratio:4/3; max-height:500px; }
.feature .media img { height:100%; object-position:center top; }
.feature-body { padding:26px 28px; display:flex; flex-direction:column; gap:9px; }
.upcoming { max-width:75ch; }
.up-month { margin:18px 0 6px; font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif; font-style:italic; font-size:19px; color:var(--gilt); }
.up-row { margin:0 0 8px; font-size:14px; color:var(--ink2); }
.up-date { font-size:11.5px; font-weight:700; text-transform:uppercase; letter-spacing:.12em; color:var(--muted);
  font-variant-numeric:tabular-nums; margin-right:6px; }
.foot { margin-top:64px; border-top:4px double var(--line-strong); padding-top:18px; font-size:12.5px; color:var(--muted); max-width:75ch; }
.foot p { margin:0 0 8px; }
.foot-follow { margin-top:12px; }
.foot-follow a { color:var(--curtain); font-weight:600; text-decoration:none; border-bottom:1px solid currentColor; padding-bottom:1px; }
.foot-follow a:hover { opacity:.72; }
/* Email signup (newsletter) */
.subscribe { max-width:64ch; margin:60px auto 0; padding:26px 22px; border:1px solid var(--line-strong); border-radius:10px; background:var(--card); text-align:center; }
.subscribe h2 { margin:0 0 6px; font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif; font-weight:700; font-size:23px; }
.subscribe .sub-lede { margin:0 0 16px; color:var(--muted); font-size:14px; }
.sub-row { display:flex; gap:8px; justify-content:center; flex-wrap:wrap; }
#sub-email { flex:1 1 240px; max-width:320px; padding:10px 12px; border:1px solid var(--line-strong); border-radius:7px; font:inherit; font-size:15px; background:var(--paper); color:var(--ink); }
#sub-email:focus { outline:2px solid var(--gilt-bright); outline-offset:1px; }
#sub-btn { padding:10px 18px; border:none; border-radius:7px; background:var(--curtain); color:#FDF6EC; font:inherit; font-weight:700; font-size:15px; cursor:pointer; }
#sub-btn:hover { opacity:.9; }
#sub-btn:disabled { opacity:.55; cursor:default; }
.sub-hp { position:absolute; left:-9999px; width:1px; height:1px; opacity:0; }
.subscribe .cf-turnstile { margin:14px auto 0; display:flex; justify-content:center; }
.sub-msg { margin:12px 0 0; min-height:1.2em; font-size:13.5px; }
.sub-msg.ok { color:var(--curtain); font-weight:600; }
.sub-msg.err { color:#b3261e; }
@media (max-width:560px) { .subscribe { margin-top:40px; padding:22px 16px; } .subscribe h2 { font-size:20px; } }
/* About page prose */
.about { max-width:66ch; margin:22px 0 0; }
.about-lede { font-size:18px; line-height:1.5; color:var(--ink); margin:0 0 10px; }
.about h2 { font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif; font-weight:700; font-size:22px; margin:32px 0 8px; }
.about p { font-size:15.5px; line-height:1.62; color:var(--ink); margin:0 0 12px; }
.about a { color:var(--curtain); text-decoration:none; border-bottom:1px solid currentColor; }
.about a:hover { opacity:.75; }
.about-follow { margin:0 0 6px; padding-left:20px; }
.about-follow li { font-size:15.5px; line-height:1.55; margin:0 0 9px; }
.about code { font-size:12.5px; background:var(--card); padding:1px 5px; border-radius:4px; word-break:break-all; }
.about .subscribe { margin:22px auto; }
/* Sources page override: pin left, use the full page width like the card grid does — the flat
   source list reads better wide, and the centered 66ch column the About prose uses is wrong for
   a directory. */
.about-sources { max-width:none; }
/* Sources page — flat list, one row per registered source, with an optional venue logo. */
.src-intro { font-size:15.5px; line-height:1.55; color:var(--ink2); margin:0 0 6px; }
.src-list { list-style:none; margin:0 0 4px; padding:0; }
.src-list li { display:flex; align-items:center; gap:12px; padding:9px 0; border-bottom:1px solid var(--line); font-size:15.5px; line-height:1.35; }
.src-list li:last-child { border-bottom:0; }
.src-logo { flex:0 0 40px; width:40px; height:40px; object-fit:contain; background:var(--card); border-radius:6px; }
.src-body { min-width:0; }
.src-name { font-weight:600; }
.src-name a { border-bottom:0; }
.src-name a:hover { border-bottom:1px solid currentColor; }
.src-area { display:block; font-size:13px; color:var(--muted); margin-top:1px; }
.foot p a { color:var(--curtain); text-decoration:none; border-bottom:1px solid currentColor; }
.foot p a:hover { opacity:.72; }
/* minmax(0,1fr), NOT 1fr: a bare 1fr track's minimum is the content's min-content width, and a
   folded slide's venue line is nowrap — so at Compact/Dense the track grew to the whole venue
   string (462px on a 281px slide), the art and body overflowed the slide, and the carousel showed
   a cropped, off-centre image with its title and venue running off the right (Wes, 2026-09-22). */
@media (max-width:760px) { .feature { grid-template-columns:minmax(0,1fr); }
  /* Marquee on phones: a shorter (2:1) image, tighter body and a smaller title, so the featured
     tile stays close to one viewport (Wes, 2026-08-29). The summary is NOT clamped any more: the
     four-line cut left an ellipsis with nothing to tap for the rest (Wes, 2026-09-18), and a
     summary is at most 70 words, so the whole blurb costs a few lines, not a scroll. */
  .media-wide { aspect-ratio:2/1; }
  .feature-body { padding:15px 17px; gap:6px; }
  .feature .title-xl { font-size:24px; line-height:1.12; }
  /* iOS Safari auto-zooms into any input whose font is under 16px, which then pushes the page off
     the right edge. 16px on the focusable form fields stops the zoom (and the overflow) entirely. */
  .fsearch, .fdist, .fdate, .acct-in { font-size:16px; } }
/* The masthead's own breakpoint, above the card grid's. Its limit is not the wordmark
   but the eyebrow: a fixed ~450px run of letter-spaced caps that never shrinks. Below
   this the date block stops trying to share the line and stacks, left-aligned, so it
   reads as a deliberate second line rather than a wrap. */
@media (max-width:900px) { .dateblock { text-align:left; max-width:none; } }
@media (max-width:520px) { .tab { padding:11px 13px 9px; font-size:11px; letter-spacing:.1em; } }
/* Phone: smaller uber chips, and the top row splits in two — the search box takes its OWN full
   row, then the When + Distance selects and the RHRN button share the next line (they have the
   whole width to themselves there, so they keep their default size). (Wes, 2026-08-29.) */
@media (max-width:560px) {
  /* Phone masthead: drop the "Last Updated on…" line and the listings breakdown — the wordmark
     alone leads, and the space is better spent on the controls (Wes, 2026-08-29). */
  .dateblock { display:none; }
  .uber { font-size:11px; letter-spacing:.05em; padding:6px 9px; }
  .uber-cal { padding:7px 8px; }
  .fsearch { flex:1 1 100%; }                    /* search: its own full row */
  /* When + Distance share the next line (they'd auto-size to their widest OPTION and overflow, so
     let them flex-shrink to fit — default 16px font kept); the RHRN button keeps its size. The
     reduced vertical padding brings all three down to the uber chips' height so the row lines up. */
  #when, #dist { flex:1 1 0; min-width:0; padding:5px 12px; }
  .frhrn { flex:0 0 auto; padding:5px 11px; }
  /* RHRN active: the distance slider takes its OWN full row (it was squeezing the When select on the
     shared line once the map button joined it); the range grows to a comfortable touch target. */
  .frhrn-slide { flex:1 1 100%; }
  .frhrn-slide input[type=range] { flex:1 1 auto; width:auto; }
  .frhrn-mapbtn { flex:0 0 auto; height:auto; padding:5px 11px; }
  /* One shared height for the filter bar: the search, When/Distance selects and the RHRN + map
     buttons match the uber chips, so the row reads as one system. box-sizing keeps 16px inside the
     box (no iOS zoom) without cropping; the uber chip's own value is the target. (Wes, 2026-08-29.) */
  .uber, .fsearch, #when, #dist, .frhrn, .frhrn-mapbtn { height:30px; box-sizing:border-box; }
  .fsearch, #when, #dist, .frhrn, .frhrn-mapbtn { padding-top:0; padding-bottom:0; }
  .frhrn, .frhrn-mapbtn { align-items:center; }
  /* Fold the sub-chips behind a "Filters" toggle to save vertical space; they take ~4 rows before
     any event shows and most visits never touch them. A count badge keeps a hidden-but-active
     filter visible, and the JS auto-opens the fold when one is restored. (Wes, 2026-08-29.) */
  .fchip-toggle { display:inline-flex; align-items:center; gap:7px; appearance:none; cursor:pointer;
    font-family:inherit; font-size:10.5px; font-weight:600; text-transform:uppercase; letter-spacing:.12em;
    color:var(--ink2); background:none; border:1px solid var(--line-strong); padding:7px 12px; }
  .fchip-toggle .fct-count { display:inline-flex; align-items:center; justify-content:center;
    min-width:16px; height:16px; padding:0 4px; border-radius:999px; background:var(--curtain);
    color:#FDF6EC; font-size:10px; letter-spacing:0; }
  .fchip-toggle .fct-count[hidden] { display:none; }
  .fchip-toggle.has-active { border-color:var(--curtain); color:var(--curtain); }
  .fchip-toggle .fct-caret { transition:transform .15s ease; }
  html.chips-open .fchip-toggle .fct-caret { transform:rotate(180deg); }
  .fchips { display:none; }
  html.chips-open .fchips { display:flex; }
}
@media (prefers-reduced-motion: no-preference) {
  .card, .feature { transition:transform .18s ease; }
  .card:hover { transform:translateY(-2px); }
}
/* --- per-topic SEO landing pages (Tier 2). Unused on the main page. --- */
.topic-h1 { font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif; font-weight:700; font-size:clamp(34px,5.4vw,56px);
  line-height:1.02; letter-spacing:-.01em; color:var(--ink); margin:0; }
.brandlink { font-family:"Bodoni Moda","Bodoni Fallback",Georgia,serif; font-size:22px; font-style:italic; color:var(--curtain);
  text-decoration:none; }
.brandlink:hover { text-decoration:underline; }
.topicnav { display:flex; flex-wrap:wrap; gap:2px; margin:22px 0 0; border-bottom:1px solid var(--line-strong); }
.topicnav a { font-family:"Libre Franklin","Franklin Fallback",Arial,sans-serif; font-size:12.5px; font-weight:700; letter-spacing:.14em;
  text-transform:uppercase; color:var(--muted); text-decoration:none; padding:12px 18px 10px; margin-bottom:-1px;
  border-bottom:3px solid transparent; }
.topicnav a:hover { color:var(--ink); }
.topicnav a.cur { color:var(--curtain); border-bottom-color:var(--curtain); }
.topicnav a.all { color:var(--ink2); }
.topic-empty { color:var(--muted); font-style:italic; padding:30px 0; }
/* Prominent "back to the full app" banner. A real crawlable <a href="/"> (never an auto-redirect,
   which would read as a doorway page) — it sells the functionality this focused landing page lacks:
   filters, calendar view, saved favourites. The topic page keeps its own listings + self-canonical,
   so it stays a legitimate content page, not a doorway. */
.mainsite-cta { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:12px 22px; margin:20px 0 4px; padding:16px 22px; background:var(--curtain); color:#FDF6EC;
  text-decoration:none; box-shadow:var(--shadow); }
.mainsite-cta:hover { background:var(--ink); }
.mainsite-cta:focus-visible { outline:2px solid var(--gilt-bright); outline-offset:2px; }
.mainsite-cta-text { font-family:"Libre Franklin","Franklin Fallback",Arial,sans-serif; font-size:14px; line-height:1.5;
  color:#FDF6EC; max-width:60ch; }
.mainsite-cta-text strong { font-weight:700; }
.mainsite-cta-go { flex:0 0 auto; font-family:"Libre Franklin","Franklin Fallback",Arial,sans-serif; font-size:12.5px;
  font-weight:700; text-transform:uppercase; letter-spacing:.14em; border:1px solid #FDF6EC;
  padding:9px 16px; white-space:nowrap; }

/* Pre-paint boot state (html.allboot, set by the tiny head script on mobile bare-URL
   loads): first paint already shows the All-view skeleton — section tabs hidden, uber
   chips up, section panels suppressed — so the JS route to show('all') changes nothing
   the reader can see. The old order (paint Plays, then swap) was the page's single
   biggest measured layout shift (0.54). The :empty spacer keeps the footer safely below
   the first viewport until the grid is cloned in, so the fill cannot shift what's
   on screen either; once cards exist the rule stops matching. The tab script removes
   allboot after routing. */
html.allboot .panel { display:none !important; }
html.allboot #panel-all { display:block; }
#allgrid:empty { min-height:130vh; }
#allplain.building { min-height:130vh; }   /* until buildPlain appends the rest */
/* Pre-paint (html.allboot): the magazine grid leads by default (empty until script;
   its 130vh spacer keeps the footer below the fold). html.plainboot — a stored
   calendar-off preference — flips to the plain list, whose server-rendered first
   screenful must paint during parse; the empty #allgrid must then NOT sit above it
   (its spacer once pushed the fold card a full viewport down and stalled the LCP). */
html.plainboot #allplain { display:block; }
html.plainboot #allgrid { display:none; }
/* Same idea for the section tabs' calendar-by-default layout (html.secboot): first paint
   already suppresses the server-rendered month groups that applySecCal will swap for the
   reparented #allgrid, with a spacer so the footer stays out of the first viewport. */
html.secboot .panel.is-active > .group { display:none; }
html.secboot .panel.is-active { min-height:130vh; }
