/* Adelaide Tonight — production CSS.
   Two palettes ship: Parklands (light, warm cream + sage) is the default.
   Late edition (graphite + sodium amber) is active under
   prefers-color-scheme: dark. Designer wanted both; we honour the user's
   system preference rather than picking one.

   Type system, spacing, components per the design handoff README. */

@import url('/fonts.css');

/* ---- Tokens — Parklands (light, default) ---- */
:root {
  --bg:           oklch(96.5% 0.014 95);   /* warm cream */
  --surface:      oklch(94% 0.018 95);
  --text:         oklch(20% 0.022 145);
  --text-dim:     oklch(42% 0.022 145);
  --text-mute:    oklch(62% 0.018 145);
  --rule:         oklch(82% 0.018 95);
  --accent:       oklch(42% 0.10 150);     /* sage / eucalypt */
  --hot:          oklch(58% 0.18 35);      /* terracotta — live dot */
  --badge-bg-l:   88%;
  --badge-fg-l:   22%;
  --badge-bg-c:   0.10;
  --badge-fg-c:   0.06;

  --ff-display:   "IBM Plex Sans Condensed", "Helvetica Neue", system-ui, sans-serif;
  --ff-serif:     "Newsreader", Georgia, "Times New Roman", serif;
  --ff-sans:      "IBM Plex Sans", "Helvetica Neue", system-ui, sans-serif;
  --ff-mono:      "IBM Plex Mono", ui-monospace, "SFMono-Regular", monospace;

  --container-max: 1280px;
  --container-pad: 56px;
  --gutter:        28px;
}

/* ---- Tokens — Late edition (dark) ---- */
@media (prefers-color-scheme: dark) {
  :root {
    --bg:         oklch(16% 0.012 250);    /* graphite-blue near-black */
    --surface:    oklch(20% 0.014 250);
    --text:       oklch(95% 0.014 85);     /* warm white */
    --text-dim:   oklch(70% 0.018 85);
    --text-mute:  oklch(48% 0.018 85);
    --rule:       oklch(28% 0.012 250);
    --accent:     oklch(78% 0.16 70);      /* sodium-lamp amber */
    --hot:        oklch(72% 0.22 25);      /* hot red-orange */
    --badge-bg-l: 34%;
    --badge-fg-l: 92%;
    --badge-bg-c: 0.14;
    --badge-fg-c: 0.05;
  }
}

/* ---- Reset / base ---- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html {
  background: var(--bg);
  color: var(--text);
  font-family: var(--ff-sans);
  font-size: 16px;
  line-height: 1.45;
  -webkit-text-size-adjust: 100%;
  text-rendering: optimizeLegibility;
}
body { min-height: 100dvh; }

a { color: inherit; text-decoration: none; }
a:hover { color: var(--accent); }
a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
img, svg { display: block; max-width: 100%; }
button { font: inherit; background: none; border: 0; color: inherit; cursor: pointer; padding: 0; }
hr { border: 0; border-top: 1px solid var(--rule); margin: 0; }

.skip-link {
  position: absolute; left: -9999px;
  background: var(--text); color: var(--bg);
  padding: .75rem 1rem; font-family: var(--ff-mono); font-size: 12px;
  letter-spacing: .12em; text-transform: uppercase;
}
.skip-link:focus { left: 1rem; top: 1rem; z-index: 10; }

.container {
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--container-pad);
}
.container--bleed { padding-inline: 0; }

@media (max-width: 720px) {
  :root { --container-pad: 18px; }
}

/* ---- Page header (sticky wrapper) ----
   The wordmark + masthead, the date strip and the category-filter chips
   all live inside .page-header so they stick to the top of the viewport
   as one unit when the user scrolls down. The wordmark morphs from the
   full "ADELAIDE TONIGHT" two-liner to just "AT" via the `is-scrolled`
   class toggled on <html> by app.js. */
.page-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bg);
  /* Visually nothing when at the top of the page; a hairline rule on the
     date strip already provides the separator. When scrolled the shadow
     adds a soft lift so cards passing beneath read as "under" it. */
  transition: box-shadow 200ms ease;
}
html.is-scrolled .page-header {
  box-shadow: 0 6px 18px -12px rgba(0, 0, 0, 0.25);
}
.page-header__filter { padding-block: 0; }

/* ---- Header ---- */
.site-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  padding-block: 32px 18px;
  transition: padding-block 240ms ease;
}
/* When collapsed, the wordmark shrinks to one line so we can also tighten
   the masthead's vertical padding. Symmetric top/bottom so the gap above
   AT/FEED LIVE matches the gap below them (before the date strip). */
html.is-scrolled .site-header { padding-block: 18px 18px; }

/* On narrow viewports (phones), the masthead meta block is too wide to sit
   beside the full "ADELAIDE / TONIGHT" wordmark — the flex parent squeezes
   the wordmark column to ~160px and inline-block letters wrap mid-word.
   Stack the wordmark above the meta at rest; once the user scrolls and the
   wordmark collapses to "AT", restore the row layout so the sticky masthead
   keeps its side-by-side composition. */
@media (max-width: 520px) {
  .site-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }
  html.is-scrolled .site-header {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
    gap: 24px;
  }
}

/* Once scrolled into the feed, trim the masthead meta to just the date + the
   live "currently" weather, dropping the FEED LIVE pulse and the "Updated …"
   row (both still shown at rest, at the top of the page). Combined with the
   wordmark morphing to "AT" and the date strip collapsing, this leaves a
   compact "AT  ·  date · temp/conditions" bar — the lightest possible sticky
   masthead while reading. Scoped to phones; desktop keeps the full meta. */
@media (max-width: 720px) {
  html.is-scrolled .site-header__meta {
    grid-template-columns: 1fr;
  }
  html.is-scrolled .site-header__meta-row { grid-column: 1; }
  html.is-scrolled .site-header__meta-dot,
  html.is-scrolled .site-header__meta-row--live,
  html.is-scrolled .site-header__meta-row--updated {
    display: none;
  }
}

.wordmark {
  font-family: var(--ff-display);
  font-weight: 700; /* heaviest weight actually loaded — 800 would force Safari faux-bold */
  font-size: clamp(44px, 6.4vw, 96px);
  line-height: 0.86;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  /* h1 has a default browser margin of ~0.67em (so ~64px at this font-size).
     With `align-items: flex-end` on the flex parent, that margin lifts the
     wordmark off the bottom — kill it explicitly. */
  margin: 0;
}
.wordmark__line { display: block; }
.wordmark__letter {
  display: inline-block;
  /* `max-width` is the load-bearing property for the collapse — `width: 0`
     wouldn't transition smoothly from `auto`. `max-width: 1.2em` is big
     enough that no real letter is clipped at rest. */
  max-width: 1.2em;
  opacity: 1;
  overflow: hidden;
  vertical-align: top;
  transition: max-width 240ms ease, opacity 200ms ease;
}
/* When scrolled, collapse the two `__line` wrappers to inline so A and T
   end up adjacent ("AT") and fade the in-between letters to zero width.
   The wordmark box naturally shrinks to one line; combined with `margin: 0`
   above and `align-items: flex-end` on the flex parent, AT stays bottom-
   aligned with the FEED LIVE block. */
html.is-scrolled .wordmark__line { display: inline; }
html.is-scrolled .wordmark__letter--fade {
  opacity: 0;
  max-width: 0;
}

.wordmark--small {
  font-size: 18px;
  font-weight: 700;
  line-height: 1;
}

/* Site-header meta block — three text rows left-aligned to each other,
   with the pulsing dot in its own column to the left of the text baseline.
   Whole block still floats to the right of the wordmark (via the flex
   parent .site-header). */
.site-header__meta {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: 4px 12px;
  font-family: var(--ff-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: left;
  color: var(--text-dim);
}
.site-header__meta-row {
  grid-column: 2;
}
.site-header__meta-row--live { color: var(--text); }
.site-header__meta-dot {
  grid-column: 1;
  grid-row: 1;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--hot);
  /* Sit on the cap-height of the first row, not its full line-box top. */
  align-self: center;
  justify-self: center;
  margin-top: 2px;
  box-shadow: 0 0 0 0 color-mix(in oklch, var(--hot) 60%, transparent);
  animation: at-pulse 2.4s infinite;
}
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}
/* Back-compat for any code still rendering the old inline span. */
.live-indicator { color: var(--text); }
.live-indicator__dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--hot);
  display: inline-block;
  box-shadow: 0 0 0 0 color-mix(in oklch, var(--hot) 60%, transparent);
  animation: at-pulse 2.4s infinite;
}
@keyframes at-pulse {
  0%   { box-shadow: 0 0 0 0   color-mix(in oklch, var(--hot) 60%, transparent); }
  70%  { box-shadow: 0 0 0 8px color-mix(in oklch, var(--hot) 0%,  transparent); }
  100% { box-shadow: 0 0 0 0   color-mix(in oklch, var(--hot) 0%,  transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .site-header__meta-dot,
  .live-indicator__dot { animation: none; }
  .wordmark__letter { transition: none; }
  .page-header { transition: none; }
  .site-header { transition: none; }
  .date-strip { transition: none; }
}

/* ---- Date strip ---- */
.date-strip {
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(72px, 1fr);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
}
.date-strip::-webkit-scrollbar { height: 4px; }
.date-cell {
  scroll-snap-align: start;
  padding: 14px 10px 12px;
  text-align: left;
  border-right: 1px solid var(--rule);
  border-top: 3px solid transparent;
  color: var(--text-dim);
  display: grid;
  gap: 4px;
  align-content: start;
  font-variant-numeric: tabular-nums;
  transition: background 120ms ease, color 120ms ease;
}
.date-cell:last-child { border-right: 0; }
.date-cell:hover { background: var(--surface); color: var(--text); }
.date-cell--selected {
  border-top-color: var(--accent);
  color: var(--text);
  background: var(--surface);
}
.date-cell--empty { color: var(--text-mute); }
.date-cell--today .date-cell__now {
  font-family: var(--ff-mono);
  font-size: 8px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  margin-top: 4px;
}
.date-cell__weekday {
  font-family: var(--ff-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.date-cell__day {
  font-family: var(--ff-display);
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  color: var(--text);
}
.date-cell--empty .date-cell__day { color: var(--text-mute); }
.date-cell__month {
  font-family: var(--ff-mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.date-cell__dots {
  /* Up to four dots in a single row, with a trailing "+" glyph when a day
     has more than four events. min-height reserves the row even when empty
     so cells stay vertically aligned. max-width + overflow:hidden are belt-
     and-braces protection — the dot row used to bleed into the next cell
     when 5 dots + "+" overflowed the 52px usable cell width on mobile. */
  margin-top: 10px;
  display: flex;
  align-items: center;
  gap: 4px;
  min-height: 7px;
  max-width: 100%;
  overflow: hidden;
}
.date-cell__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-dim);
}
.date-cell__more {
  font-family: var(--ff-mono);
  font-size: 10px;
  line-height: 1;
  color: var(--text-dim);
}
.date-cell--selected .date-cell__dot { background: var(--accent); }
.date-cell--selected .date-cell__more { color: var(--accent); }

/* On phones the sticky header is ~65% of the viewport at rest and ~50% once
   collapsed — the date strip is ~110px of that. Once the user has scrolled
   into the feed (the same `is-scrolled` threshold that morphs the wordmark
   to "AT"), collapse the strip away so the sticky header is just the AT mark,
   the status meta and the filter row. The strip is one flick back to the top
   away. app.js observes the page-header with a ResizeObserver, so
   `--header-height` (and therefore the sticky section-heading offset)
   re-measures through the transition automatically.
   `overflow-y: hidden` lets `max-height` clip the strip while leaving the
   existing `overflow-x: auto` horizontal scroll intact. */
@media (max-width: 720px) {
  .date-strip {
    overflow-y: hidden;
    /* Cap is only the collapse target's start point — kept just above the
       strip's natural ~130px so the retract animation has little dead travel
       while leaving headroom so the cap never clips the cell content. */
    max-height: 160px;
    transition: max-height 240ms ease, opacity 180ms ease;
  }
  html.is-scrolled .date-strip {
    max-height: 0;
    opacity: 0;
    border-top-width: 0;
    border-bottom-width: 0;
  }
}

/* ---- Category filter chips ---- */
.category-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-block: 24px 8px;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 8px;
}

/* On phones the wrapped grid blew the masthead to ~4 rows of chips
   (185px tall). Switch to a single horizontally-scrolling row so the
   filter occupies one row of vertical real estate and matches the
   existing date-strip's horizontal-scroll affordance directly above it. */
@media (max-width: 720px) {
  /* Tighten the vertical air around the header on phones. The date strip
     carries hairline rules top and bottom, so the masthead and the filter
     don't each need a wide gap on top of those separators. */
  .site-header { padding-block: 32px 10px; }
  .category-filter {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: thin;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    /* was 24px 8px + 8px margin — the date strip's bottom rule already
       separates it from the chips, so the 24px top was redundant air. */
    padding-block: 12px 6px;
    margin-bottom: 4px;
  }
  /* The first content section sits directly under the sticky filter; its
     full 28px section padding doubled up with the filter's gap. The rest of
     the sections keep their rhythm. */
  [data-at="main"] > .section:first-child { padding-top: 8px; }
  .category-filter::-webkit-scrollbar { height: 4px; }
  .category-filter .category-chip {
    flex-shrink: 0;
    scroll-snap-align: start;
    /* 7px gave a ~32px tap target; 13px brings it to ~44px to meet the
       Apple HIG minimum touch size on phones. Desktop keeps the tighter
       7px (mouse target, no HIG floor). */
    padding-block: 13px;
  }
}
.category-chip {
  font-family: var(--ff-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 0;
  padding: 7px 12px;
  cursor: pointer;
  display: inline-flex;
  gap: 8px;
  align-items: center;
  transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
}
/* Hover previews the brighter text colour — but it MUST NOT apply to the active
   chip. `.category-chip:hover` is specificity (0,2,0) and would outrank
   `.category-chip--active` (0,1,0), forcing color:var(--text) — which is exactly
   the active pill's var(--text) *background*, collapsing it to an unreadable
   solid block. iOS makes :hover sticky after a tap, so the selected filter
   rendered as a permanent black box. Scoping the preview to :not(--active)
   keeps the active pill's inverted colours uncontested. */
.category-chip:hover:not(.category-chip--active) { color: var(--text); border-color: var(--text-dim); }
/* Background of the active pill stays var(--text) so the pill is highly
   distinct from the page in both themes. The text inside flips to var(--bg)
   in light mode (cream on dark — already readable), but in dark mode --bg
   is a cool graphite-blue that washed out against the warm-white pill at
   11px — force a neutral near-black instead so contrast is unambiguous.
   Font weight bumped 500 → 600 to compensate for thin-letter perception
   from the inversion's bright background. */
.category-chip--active {
  color: var(--bg);
  background: var(--text);
  border-color: var(--text);
  font-weight: 600;
}
@media (prefers-color-scheme: dark) {
  .category-chip--active { color: oklch(16% 0 0); }
}
.category-chip--active .category-chip__count { opacity: 0.65; }
.category-chip__count {
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  opacity: 0.7;
}

/* Hide cards whose public-category doesn't match the active filter.
   Works on both expanded (Tonight) + compact (Coming Up) cards.
   The "show" rules need equal-or-greater specificity than the "hide" rule
   so they win when both match — hence the duplicated [data-at] attr. */
body[data-filter-category]:not([data-filter-category=""]) [data-at="event-card"] {
  display: none;
}
body[data-filter-category="sport"] [data-at="event-card"][data-public-category="sport"] { display: grid; }
body[data-filter-category="live-music"] [data-at="event-card"][data-public-category="live-music"] { display: grid; }
body[data-filter-category="theatre"] [data-at="event-card"][data-public-category="theatre"] { display: grid; }
body[data-filter-category="comedy"] [data-at="event-card"][data-public-category="comedy"] { display: grid; }
body[data-filter-category="festival"] [data-at="event-card"][data-public-category="festival"] { display: grid; }
body[data-filter-category="food-wine"] [data-at="event-card"][data-public-category="food-wine"] { display: grid; }
body[data-filter-category="family"] [data-at="event-card"][data-public-category="family"] { display: grid; }
body[data-filter-category="community"] [data-at="event-card"][data-public-category="community"] { display: grid; }

/* Hide whole day sections that have no card matching the active filter
   (modern :has() — Chrome 105+, Safari 15.4+, Firefox 121+). The markup
   renders day sections as [data-at="day-block"] (class .section--day); the
   old `.day-block` class these rules targeted no longer exists, so they
   silently matched nothing and left orphaned, empty day headings pinned in
   the feed when a filter was active. A non-functional attr()-in-selector
   attempt was removed at the same time. */
body[data-filter-category="sport"] [data-at="day-block"]:not(:has([data-public-category="sport"])) { display: none; }
body[data-filter-category="live-music"] [data-at="day-block"]:not(:has([data-public-category="live-music"])) { display: none; }
body[data-filter-category="theatre"] [data-at="day-block"]:not(:has([data-public-category="theatre"])) { display: none; }
body[data-filter-category="comedy"] [data-at="day-block"]:not(:has([data-public-category="comedy"])) { display: none; }
body[data-filter-category="festival"] [data-at="day-block"]:not(:has([data-public-category="festival"])) { display: none; }
body[data-filter-category="food-wine"] [data-at="day-block"]:not(:has([data-public-category="food-wine"])) { display: none; }
body[data-filter-category="family"] [data-at="day-block"]:not(:has([data-public-category="family"])) { display: none; }
body[data-filter-category="community"] [data-at="day-block"]:not(:has([data-public-category="community"])) { display: none; }

/* Hide the empty "Quiet night. Try the Hills." message when filter is active
   but the day has events in OTHER categories — the user filtered to something
   that's not on tonight, so the empty-tonight phrasing would be misleading. */
body[data-filter-category]:not([data-filter-category=""]) [data-at="empty-tonight"] {
  display: none;
}

/* ---- Section bands ---- */
.section { padding-block: 28px; }
/* COMING UP is a banner-only section — its band carries the heading but
   no event list. Tightened padding so the heading reads as a transient
   marker between TONIGHT's events and the first day block. */
.section--banner { padding-block: 8px; }
/* Day-block sections (TOMORROW, THURSDAY, ...) sit between TONIGHT and
   subsequent day blocks; they want a tighter rhythm than the L1 sections. */
.section--day { padding-block: 0; margin-bottom: 28px; }

/* Section-band now only holds the supporting bits that sit BELOW the
   sticky heading (the italic sub-line on TONIGHT, the weather strip on
   the right). The heading itself is a direct child of `.section` so its
   sticky containing block is the whole section, giving it the full
   section height to slide through. */
.section-band {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: start;
  gap: 28px;
  padding-block: 4px 20px;
}
.section-eyebrow {
  font-family: var(--ff-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 6px;
  display: block;
}

/* Every L1 / L2 section heading is sticky at the slot directly under the
   page header. With each heading bound to its own <section> as its
   containing block, only one heading sits in the slot at a time —
   TONIGHT → COMING UP → TOMORROW → THURSDAY → ... — naturally cycling
   as the user scrolls.

   The exact offset is set by app.js as the inline custom property
   `--header-height` on <html>; the fallback covers first-paint before the
   measurement runs. */
.section-heading {
  font-family: var(--ff-display);
  font-weight: 700; /* heaviest weight actually loaded — 800 would force Safari faux-bold */
  font-size: clamp(40px, 5.6vw, 80px);
  line-height: 0.92;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  margin: 0;
  position: sticky;
  /* app.js measures the real header and sets --header-height on load. The
     fallback only applies for the first paint (and the rare no-JS visitor,
     where the header never collapses): it is sized near the full at-rest
     header height so headings pin just below it rather than behind it (a
     too-small value tucks them under the taller, higher-z-index header). */
  top: var(--header-height, 340px);
  /* Solid background so the previous section's cards don't ghost through
     while the heading is pinned. */
  background: var(--bg);
  z-index: 5;
  padding-block: 12px 8px;
}
/* L2 day heading — TOMORROW, THURSDAY, FRIDAY... — smaller than the L1
   TONIGHT/COMING UP display headings, but participates in the same
   sticky slot. */
.section-heading--day {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  gap: 18px;
  border-top: 2px solid var(--text);
  border-bottom: 1px solid var(--rule);
  padding-block: 14px;
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0 0 4px;
}
.section-heading--day .section-heading__weekday {
  font-family: var(--ff-display);
  text-transform: uppercase;
  color: var(--text);
}
.section-heading--day .section-heading__date {
  font-family: var(--ff-mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-dim);
  font-weight: 500;
}
.section-sub {
  font-family: var(--ff-serif);
  font-style: italic;
  font-size: 18px;
  color: var(--text-dim);
  margin-top: 10px;
  max-width: 52ch;
}

/* ---- Header "currently" weather ----
   Single mono row dropped into the right-rail metadata stack, just below
   "47 listings · Mon 25 May". Reads flat with the rest of the stack — same
   mono / 11px / 0.12em / text-dim recipe as the other rows. */
.weather-now {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-family: var(--ff-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.weather-now__sep { color: var(--text-mute); }
.weather-now__temp {
  font-variant-numeric: tabular-nums;
}

/* ---- Compact per-day weather ----
   Used by:
   - TONIGHT / TODAY section bands (top-right, absolutely positioned)
   - Each day-block heading (right-aligned cell of the heading grid)
   Format: "14° → 9° · CLOUDY" — hi/low + dominant condition. The TONIGHT
   variant additionally has a "Showers 11 PM"-style transition clause
   appended client-side when an hourly change is notable.
   Visual weight intentionally matches `.section-heading__date` so it sits
   as a peer to the eyebrow / date metadata around it. */
.weather-compact {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  font-family: var(--ff-mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
  min-width: 0;
}
.weather-compact__temp {
  font-family: var(--ff-display);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  text-transform: none;
}
.weather-compact__sep { color: var(--text-mute); }
.weather-compact__summary {
  /* Sentence-case content from JS — keep its casing rather than uppercasing
     the rest of the row. The leading "·" separator is part of the string. */
  text-transform: none;
  letter-spacing: 0;
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* Section-band placement: sit inside the sticky H2 (heading text on the
   left, weather right-aligned) so the weather pins together with the
   heading as the user scrolls — mirroring the day-block heading row. */
.section-heading--with-weather {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
}
.section-heading__title {
  min-width: 0;
}
.section-heading--with-weather .weather-compact--section {
  flex-shrink: 0;
}

/* Day-block placement: third column of the existing heading grid. */
.section-heading--day .weather-compact--day {
  justify-self: end;
}

/* On narrow viewports the day-heading grid is too tight for hi/low +
   summary alongside the weekday + date. Let it wrap below the date row. */
@media (max-width: 520px) {
  .section-heading--day {
    grid-template-columns: auto auto;
  }
  .section-heading--day .weather-compact--day {
    grid-column: 1 / -1;
    justify-self: start;
    margin-top: 2px;
  }
  .weather-compact__summary {
    white-space: normal;
  }
  /* The L1 TONIGHT/TODAY heading is a flex row (big display title + weather
     aside). On phones the title + hi→low + summary can exceed the viewport
     width, so let the weather wrap onto its own line beneath the title
     instead of forcing a horizontal overflow. */
  .section-heading--with-weather {
    flex-wrap: wrap;
    gap: 2px 24px;
  }
  .section-heading--with-weather .weather-compact--section {
    flex-basis: 100%;
  }
}

/* ---- Weather strip (legacy, currently unused on home) ----
   On the TODAY and TONIGHT sections the weather is absolutely positioned at
   the top-right of the section so it sits flush with the eyebrow + heading
   area. The heading stays a direct child of `.section` (so its sticky scope
   remains the whole section); wrapping it in a grid cell or flex row would
   have shrunk the sticky range. On narrow viewports the weather falls back
   into normal flow below the heading. */
.section--tonight,
.section--today { position: relative; }
.section--tonight .weather,
.section--today .weather {
  position: absolute;
  top: 28px;
  right: 0;
  /* The sticky `.section-heading` has z-index: 5 and a full-width background
     (to cover the events scrolling beneath it when it's pinned). Without a
     higher z-index here the heading's background paints over the weather. */
  z-index: 6;
}
@media (max-width: 1100px) {
  .section--tonight .weather,
  .section--today .weather {
    position: static;
    margin-top: 16px;
  }
}
.weather {
  border: 1px solid var(--rule);
  min-width: 360px;
  font-variant-numeric: tabular-nums;
  background: var(--bg);
}
.weather__head {
  display: flex;
  justify-content: space-between;
  padding: 8px 12px;
  border-bottom: 1px solid var(--rule);
  font-family: var(--ff-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.weather__row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
}
.weather__point {
  padding: 10px 12px;
  border-right: 1px solid var(--rule);
}
.weather__point:last-child { border-right: 0; }
.weather__time {
  font-family: var(--ff-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  color: var(--text-dim);
  text-transform: uppercase;
}
.weather__temp {
  font-family: var(--ff-display);
  font-size: 18px;
  font-weight: 700;
  margin-top: 2px;
  color: var(--text);
}
.weather__cond {
  font-family: var(--ff-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-top: 2px;
}
@media (max-width: 720px) {
  .section-band { grid-template-columns: 1fr; }
  .weather { min-width: 0; width: 100%; }
}

/* ---- Tonight: expanded event card ---- */
.card-list { list-style: none; padding: 0; margin: 0; }
.card-list--expanded > li { border-top: 1px solid var(--rule); }
.card-list--expanded > li:last-child { border-bottom: 1px solid var(--rule); }

.card--expanded {
  display: grid;
  grid-template-columns: 140px 1fr auto;
  align-items: start;
  gap: 28px;
  padding-block: 28px;
}
.card-category {
  font-family: var(--ff-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-dim);
  grid-column: 1 / -1;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
.card-source {
  font-family: var(--ff-mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: lowercase;
  color: var(--text-mute);
}
.card-time {
  font-family: var(--ff-display);
  font-weight: 600;
  font-size: 44px;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.card-time--started {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.05;
}
.card-time__eyebrow {
  font-family: var(--ff-display);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 4px;
}
.card-time__value {
  display: inline-block;
}
.card-body {
  display: grid;
  gap: 8px;
}
.card-venue {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--ff-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.card-title {
  font-family: var(--ff-serif);
  font-weight: 700;
  font-size: clamp(26px, 2.6vw, 36px);
  line-height: 1.08;
  letter-spacing: -0.015em;
  color: var(--text);
  margin: 0;
  text-wrap: balance;
}
.card-title a { color: inherit; }
.card-title a:hover { color: var(--accent); }
.card-subtitle {
  font-family: var(--ff-serif);
  font-style: italic;
  font-size: 16px;
  color: var(--text-dim);
  margin: 0;
}
.card-more {
  align-self: end;
  font-family: var(--ff-mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  border-bottom: 1px solid var(--accent);
  padding-bottom: 2px;
  white-space: nowrap;
}
@media (max-width: 720px) {
  .card--expanded {
    grid-template-columns: 1fr;
    gap: 14px;
  }
  .card-time { font-size: 36px; }
  .card-title { font-size: 24px; }
  .card-more { justify-self: start; }
}

/* ---- Venue badge ---- */
.venue-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  padding: 4px 7px;
  border-radius: 3px;
  font-family: var(--ff-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  background: oklch(var(--badge-bg-l) var(--badge-bg-c) var(--badge-hue, 0));
  color:      oklch(var(--badge-fg-l) var(--badge-fg-c) var(--badge-hue, 0));
}
.venue-badge--lg {
  min-width: 56px;
  padding: 8px 12px;
  font-size: 14px;
  letter-spacing: 0.08em;
}

/* ---- Coming up: compact rows ---- */
/* (The old `.day-block*` rules were removed — day sections now render as
   `.section--day` with `.section-heading--day`, styled in the Section bands
   block above; the `.day-block` class is no longer emitted by the markup.) */
.card--compact {
  display: grid;
  grid-template-columns: 96px 80px 1fr auto auto;
  align-items: center;
  gap: 16px;
  padding-block: 12px;
  border-bottom: 1px solid var(--rule);
}
.card--compact .card-category {
  grid-column: auto;
  display: block;
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--accent);
}
.card--compact .card-time {
  font-family: var(--ff-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}
.card--compact .card-title {
  font-family: var(--ff-serif);
  font-size: 17px;
  font-weight: 500;
  line-height: 1.18;
}
.card--compact .card-subtitle {
  font-family: var(--ff-sans);
  font-size: 13px;
  font-style: normal;
  color: var(--text-dim);
  margin-top: 2px;
}
.card--compact .card-arrow {
  font-family: var(--ff-mono);
  color: var(--text-dim);
  font-size: 14px;
}
@media (max-width: 720px) {
  /* FIXED category + time columns so the time and title line up row-to-row
     regardless of category/time length. Each compact card is its own grid, so
     an `auto` column is sized per-card (NOT shared across rows) — that's what
     threw the columns out of alignment: "LIVE MUSIC" pushed its row's time +
     title right while "COMEDY" pulled them left. The time column also varied
     ("11:30 am" 62px vs "7:00 pm" 54px). 64px fits the widest single-word
     category ("Community" ~59px) and the widest time ("11:30 am" ~62px); a
     multi-word category ("LIVE MUSIC", "FOOD & WINE") wraps to two lines, which
     is fine now that the column width — and therefore the title's left edge —
     is constant. */
  .card--compact {
    grid-template-columns: 64px 64px 1fr auto 12px;
    gap: 10px;
  }
  .card--compact .card-category {
    font-size: 9px;
    letter-spacing: 0.12em;
    line-height: 1.3; /* readable when wrapped to two lines */
  }
  .card--compact .card-time { white-space: nowrap; }
}

/* ---- Regional highlights ---- */
.regional-list { list-style: none; padding: 0; margin: 0; }
.regional-row {
  display: grid;
  grid-template-columns: 140px 1fr auto auto;
  align-items: baseline;
  gap: 16px;
  border-top: 1px solid var(--rule);
  padding-block: 14px;
}
.regional-row:last-child { border-bottom: 1px solid var(--rule); }
.regional-row__date {
  font-family: var(--ff-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}
.regional-row__title {
  font-family: var(--ff-serif);
  font-size: 22px;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--text);
}
.regional-row__place {
  font-family: var(--ff-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.regional-row__arrow { font-family: var(--ff-mono); color: var(--text-dim); }

/* ---- Footer ---- */
.site-footer {
  border-top: 2px solid var(--text);
  margin-top: 64px;
  padding-block: 32px;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 28px;
  font-family: var(--ff-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.site-footer h2 { margin: 0 0 6px; font-size: inherit; font-weight: 500; color: var(--text); letter-spacing: inherit; text-transform: inherit; }
.site-footer p { margin: 0 0 6px; max-width: 28ch; }
@media (max-width: 720px) {
  .site-footer { grid-template-columns: 1fr; }
}

/* ---- Event detail page ---- */
.detail-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  padding-block: 24px 14px;
  border-bottom: 1px solid var(--rule);
}
.back-link {
  font-family: var(--ff-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.back-link:hover { color: var(--accent); }

.event-detail { padding-block: 40px 24px; }
.event-detail__eyebrow {
  font-family: var(--ff-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0 0 12px;
}
.event-detail__title {
  font-family: var(--ff-serif);
  font-weight: 700;
  font-size: clamp(40px, 6vw, 88px);
  line-height: 0.96;
  letter-spacing: -0.025em;
  color: var(--text);
  margin: 0;
  text-wrap: balance;
}
.event-detail__subtitle {
  font-family: var(--ff-serif);
  font-style: italic;
  font-size: 22px;
  color: var(--text-dim);
  margin-top: 16px;
}
.event-detail__meta {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  margin-top: 28px;
  border-top: 2px solid var(--text);
  border-bottom: 1px solid var(--rule);
}
.event-detail__meta > div {
  /* Equal padding on all sides so each cell has breathing room around its
     dividers — first cell included (user feedback: needs left padding). */
  padding: 20px;
  border-right: 1px solid var(--rule);
}
.event-detail__meta > div:last-child { border-right: 0; }
.event-detail__meta dt {
  font-family: var(--ff-mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-mute);
  margin-bottom: 8px;
}
.event-detail__meta dd {
  margin: 0;
  font-family: var(--ff-serif);
  font-size: 20px;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 10px;
}
.event-detail__body-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 48px;
  margin-top: 36px;
}
.event-detail__body {
  font-family: var(--ff-serif);
  font-size: 19px;
  line-height: 1.55;
  color: var(--text);
  max-width: 60ch;
}
.event-detail__body p { margin-block: 0 1em; }
.event-detail__body p:last-child { margin-bottom: 0; }
.event-detail__attribution {
  font-family: var(--ff-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-mute);
  margin-top: 24px;
}
.event-detail__cta {
  display: inline-block;
  background: var(--text);
  color: var(--bg);
  padding: 14px 22px;
  font-family: var(--ff-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.event-detail__cta:hover { background: var(--accent); color: var(--bg); }

.event-detail__cta-row {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

@media (min-width: 720px) {
  .event-detail__cta-row {
    flex-direction: row;
    flex-wrap: wrap;
  }
}

.event-detail__cta--primary {
  /* Inherits all existing .event-detail__cta rules. Kept as an explicit
   * class so the secondary variant has a precedence anchor. */
}

.event-detail__cta--secondary {
  background: transparent;
  color: inherit;
  border: 1px solid currentColor;
}

.event-detail__cta--secondary:hover {
  background: rgba(0, 0, 0, 0.04);
}

@media (prefers-color-scheme: dark) {
  .event-detail__cta--secondary:hover {
    background: rgba(255, 255, 255, 0.06);
  }
}

.event-detail__sidebar h2 {
  font-family: var(--ff-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-mute);
  margin: 0 0 10px;
}
.event-detail__nearby {
  list-style: none;
  padding: 0;
  margin: 16px 0 0;
  border-top: 1px solid var(--rule);
}
.event-detail__nearby li { border-bottom: 1px solid var(--rule); padding-block: 10px; display: flex; gap: 10px; align-items: baseline; }
.event-detail__nearby time { font-family: var(--ff-mono); font-size: 11px; letter-spacing: 0.08em; color: var(--text-dim); min-width: 64px; }
/* Always let the source URL wrap rather than overflow the cell. Long
   venue domains (e.g. adelaidecentralmarket.com.au) would otherwise punch
   through the right border on mobile. anywhere is preferable to break-word
   for URLs because it breaks at any character, not just word boundaries. */
.event-detail__meta dd a { overflow-wrap: anywhere; }
@media (max-width: 720px) {
  /* Single column on phones: 2x2 grid squeezes each cell to ~150px which
     forces 'Food & Wine' to wrap, '7:00 am' to break its space, and the
     date 'Wed, 27 May' to triple-stack. Stacking gives each row the full
     container width and the serif numerals room to breathe. */
  .event-detail__meta { grid-template-columns: 1fr; }
  .event-detail__meta > div {
    border-right: 0;
    border-bottom: 1px solid var(--rule);
    padding: 16px 4px;
  }
  /* When/Venue dd holds two children (time+date, badge+name). flex-wrap
     means narrow viewports can drop the second child below if needed
     rather than letting it shrink-wrap a single word per line. */
  .event-detail__meta dd { flex-wrap: wrap; row-gap: 4px; }
  .event-detail__body-grid { grid-template-columns: 1fr; }
  .event-detail__cta { display: block; text-align: center; }
}

/* ---- About / prose page ---- */
.prose { padding-block: 32px; max-width: 60ch; }
.prose h1 {
  font-family: var(--ff-serif);
  font-weight: 700;
  font-size: clamp(36px, 4.5vw, 60px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin: 0 0 24px;
}
.prose h2 {
  font-family: var(--ff-mono);
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-mute);
  margin: 32px 0 12px;
}
.prose p {
  font-family: var(--ff-serif);
  font-size: 19px;
  line-height: 1.55;
  margin: 0 0 1em;
}
.prose a { color: var(--accent); border-bottom: 1px solid var(--accent); }

/* ---- Sources page ---- */
.page-sources .sources-blurb {
  font-family: var(--ff-serif);
  font-style: italic;
  color: var(--text-dim);
  margin: 0 0 16px;
  font-size: 16px;
}
.page-sources .sources-list {
  list-style: none;
  padding: 0;
  margin: 0 0 24px;
  border-top: 1px solid var(--rule);
}
.page-sources .sources-row {
  border-bottom: 1px solid var(--rule);
  padding: 14px 0;
  display: grid;
  /* Name on the left growing into available space, meta on the right. The
     align-self:baseline keeps the meta line aligned with the first line of
     the name when long titles wrap. */
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: 16px;
}
.page-sources .sources-row__name {
  font-family: var(--ff-serif);
  font-size: 19px;
  line-height: 1.35;
  color: var(--text);
  border-bottom: none;
}
.page-sources .sources-row__name:hover { color: var(--accent); }
.page-sources .sources-row__meta {
  font-family: var(--ff-mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-mute);
  white-space: nowrap;
}
@media (max-width: 520px) {
  .page-sources .sources-row {
    grid-template-columns: 1fr;
    gap: 4px;
  }
  .page-sources .sources-row__meta { white-space: normal; }
}

/* ---- Utilities ---- */
.empty-message {
  font-family: var(--ff-serif);
  font-style: italic;
  font-size: 19px;
  color: var(--text-dim);
  padding-block: 32px;
}
[hidden] { display: none !important; }

/* /request form
   ----------------------------------------------------------------- */

.page-request .prose {
  max-width: 40rem;
}

.form__field {
  display: block;
  margin: 1.25rem 0;
}

.form__label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.35rem;
}

.form__hint {
  font-weight: 400;
  opacity: 0.7;
  font-size: 0.9em;
}

.form__field input[type="text"],
.form__field input[type="email"],
.form__field select,
.form__field textarea {
  width: 100%;
  font: inherit;
  padding: 0.5rem 0.6rem;
  border: 1px solid currentColor;
  border-radius: 4px;
  background: transparent;
  color: inherit;
  box-sizing: border-box;
}

.form__field textarea {
  resize: vertical;
  min-height: 8rem;
}

.form__honeypot {
  position: absolute;
  left: -9999px;
  height: 0;
  width: 0;
  overflow: hidden;
}

.form__actions {
  margin-top: 1.5rem;
}

/* Solid primary button, matched to .event-detail__cta so the visual
   vocabulary is consistent across surfaces. Note the explicit var(--text)
   — `background: currentColor` does NOT work here because `color` is
   set to var(--bg) on the same rule, so currentColor would resolve to
   the cream background and the button would vanish. */
.form__submit {
  font: inherit;
  font-weight: 600;
  padding: 0.6rem 1.2rem;
  border: 1px solid var(--text);
  border-radius: 4px;
  background: var(--text);
  color: var(--bg);
  cursor: pointer;
}

.form__submit:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
}

.form__submit:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.form__error {
  color: var(--hot);
  margin: 1rem 0;
  font-weight: 600;
}

.form__noscript {
  margin-top: 1.5rem;
  padding: 0.75rem 1rem;
  border: 1px dashed currentColor;
  font-size: 0.95em;
  opacity: 0.85;
}

/* Warm prose treatment for the post-submit confirmation. The default
   site H2 styling is uppercase-mono which read as a clinical label here
   ("THANKS — WE WILL BE IN TOUCH."); switching to the body serif at a
   larger size keeps it conversational. */
.form__thanks-headline {
  font-family: var(--ff-serif);
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.25;
  margin: 0 0 0.5rem;
  letter-spacing: 0;
  text-transform: none;
}
