/* Conscious Dance London — hand-written, mobile-first, no framework.
   Colour and spacing are custom properties so the palette is one edit. */

:root {
  --bg:          #fbf9f7;
  --surface:     #ffffff;
  --ink:         #17161a;
  --ink-muted:   #5d5a66;
  --line:        #e5e0da;
  --accent:      #7b3fbf;
  --accent-ink:  #ffffff;
  --tag-bg:      #f0ebf7;
  --tag-ink:     #4c2a78;

  /* One pair per `dance_type`. Nine hues 40° apart in OKLCH, held at one
     lightness and one chroma so no type shouts louder than another; every
     ink/bg pair clears WCAG AAA. Derivation and hue assignment:
     docs/design/0003-colour-by-dance-type.md. */
  --type-ecstatic_dance-bg:          #f9e9f1;
  --type-ecstatic_dance-ink:          #693c56;
  --type-5rhythms-bg:                #f0f0e0;
  --type-5rhythms-ink:                #534f1b;
  --type-movement_medicine-bg:       #e1f2f9;
  --type-movement_medicine-ink:       #13556b;
  --type-open_floor-bg:              #fceae8;
  --type-open_floor-ink:              #6f3d39;
  --type-biodanza-bg:                #e7f2e6;
  --type-biodanza-ink:                #325734;
  --type-contact_improvisation-bg:   #e8effc;
  --type-contact_improvisation-ink:   #3a4c75;
  --type-trance_dance-bg:            #f8ece1;
  --type-trance_dance-ink:            #68441e;
  --type-authentic_movement-bg:      #e1f3f0;
  --type-authentic_movement-ink:      #065a52;
  --type-conscious_dance_general-bg: #f1ebfa;
  --type-conscious_dance_general-ink: #56436d;

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.875rem;
  --space-4: 1.25rem;
  --space-5: 2rem;
  --space-6: 3rem;

  --radius: 10px;
  --measure: 62rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:         #131217;
    --surface:    #1c1a21;
    --ink:        #f2eff5;
    --ink-muted:  #a39eae;
    --line:       #2e2b36;
    --accent:     #b98bf0;
    --accent-ink: #17161a;
    --tag-bg:     #2a2333;
    --tag-ink:    #d7bcf7;

    /* The same nine hues, lightness swapped: pale ink on a deep tint. */
    --type-ecstatic_dance-bg:          #3e2232;
    --type-ecstatic_dance-ink:          #f1b9d7;
    --type-5rhythms-bg:                #302e0d;
    --type-5rhythms-ink:                #d3cf96;
    --type-movement_medicine-bg:       #08323f;
    --type-movement_medicine-ink:       #96d7f1;
    --type-open_floor-bg:              #422220;
    --type-open_floor-ink:              #fabab4;
    --type-biodanza-bg:                #1c331d;
    --type-biodanza-ink:                #aedaae;
    --type-contact_improvisation-bg:   #202c46;
    --type-contact_improvisation-ink:   #b4ccfe;
    --type-trance_dance-bg:            #3e270f;
    --type-trance_dance-ink:            #efc399;
    --type-authentic_movement-bg:      #033430;
    --type-authentic_movement-ink:      #93ddd2;
    --type-conscious_dance_general-bg: #322641;
    --type-conscious_dance_general-ink: #d7c0f4;
  }
}

* { box-sizing: border-box; }

/* Filtering sets the `hidden` attribute on cards and day blocks. The browser's
   own `[hidden] { display: none }` is a plain attribute rule, so any component
   rule that sets `display` beats it — `.card` sets `display: grid`, and without
   the `!important` here a filtered-out card stays fully visible while its day
   heading correctly disappears. See docs/design/0001-hidden-beats-display.md. */
[hidden] { display: none !important; }

body {
  margin: 0;
  padding: 0 var(--space-4);
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-text-size-adjust: 100%;
}

main, .site-header, .site-footer { max-width: var(--measure); margin-inline: auto; }
/* The month grid spans the screen: the measure is for reading cards, and a grid
   capped at it held two busy weeks only by scrolling. Header and footer widen
   with it so the wordmark, the sticky filter bar and the grid share one edge.
   See docs/design/0009-full-width-two-line-calendar.md. */
.page-calendar main,
.page-calendar .site-header,
.page-calendar .site-footer { max-width: none; }

a { color: var(--accent); }
img { max-width: 100%; height: auto; display: block; }
.muted { color: var(--ink-muted); }
.small { font-size: 0.85rem; }

/* Header ---------------------------------------------------------------- */

/* One row: wordmark left, the view switch right. The tagline it used to carry
   is the page's meta description and nothing else — see
   docs/design/0006-compact-filter-bar.md for the height this was bought for. */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-3) 0;
}

.wordmark {
  display: inline-block;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  text-decoration: none;
  color: var(--ink);
}
.wordmark span { color: var(--accent); }

/* Two views of the same events — the list and the month grid. The pair sits in
   the header rather than in the filter bar, because switching view is not
   filtering: the filters survive neither page, and a chip does. It is now beside
   the wordmark rather than under it; the reason it is not in the bar is
   unchanged. */
.views { margin: 0; display: flex; gap: var(--space-2); }

.view {
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font-size: 0.85rem;
  text-decoration: none;
}
.view:hover { border-color: var(--accent); }
.view.is-current {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* Filters --------------------------------------------------------------- */

.filters {
  position: sticky;
  top: 0;
  z-index: 10;
  padding: var(--space-3) 0;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}

/* Label beside its chips, not above them: a group is one row, not two. Inside
   the panel the groups still stack, so the spacing rule is scoped to it. */
.filter-group { display: flex; align-items: baseline; gap: var(--space-2); }
.more-panel .filter-group + .filter-group { margin-top: var(--space-3); }

.filter-label {
  flex: none;
  margin: 0;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.chip {
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}
.chip:hover { border-color: var(--accent); }
.chip.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.chip .count { opacity: 0.6; font-variant-numeric: tabular-nums; }

.result-count {
  margin: 0;
  font-size: 0.85rem;
  color: var(--ink-muted);
}

/* The pinned row under Type: what is picked, how many events that leaves, and
   on the calendar the month arrows. It wraps on a phone, where the month nav
   drops onto its own line. */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.picks { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* The month nav in the toolbar rather than above the grid. It keeps its own
   markup — `rel="prev"/"next"` links — and loses the block it used to be. */
.toolbar .cal-head {
  margin: 0 0 0 auto;
  padding: 0;
  border: 0;
}
.toolbar .cal-month { font-size: 0.95rem; }

/* The toggle, Done and a pick share the `.view` pill shape — three small
   controls in one bar look like one family. */
.more-toggle,
.more-done,
.pick {
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}
.more-toggle:hover,
.more-done:hover,
.pick:hover { border-color: var(--accent); }
.more-toggle[aria-expanded="true"] { border-color: var(--accent); }
.pick { background: var(--tag-bg); color: var(--tag-ink); border-color: var(--tag-bg); }
/* Bottom right of the panel, where a dialog's confirm sits. */
.more-done { display: block; margin: var(--space-3) 0 0 auto; }

/* Floats over the events rather than pushing them down: opening the panel must
   not move the first card, or every pick costs a re-scan of the page. `.filters`
   is already `position: sticky`, which makes it the containing block.
   `[hidden]` wins over the `display` here — docs/design/0001-hidden-beats-display.md. */
.more-panel {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 11;
  max-height: 70vh;
  overflow: auto;
  padding: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgb(0 0 0 / .12);
}

/* Colour by dance type ---------------------------------------------------

   One rule per enum value, and nothing below it names a type. Everything that
   should carry a type's colour reads `--type-bg` / `--type-ink`, which the card,
   each dance tag and the detail article (`data-colour`) and the chip
   (`data-value`) set for their own subtree. `data-colour` is one type — the
   first — while `data-dance_type` may list several for the filter. A type with no rule here simply keeps the neutral
   fallbacks — a tenth enum value degrades, it does not break. `tests/` asserts
   every value in queries.DANCE_TYPE_LABELS has a pair in both themes.

   `--accent` is remapped too, so an event's own page and its card hover pick up
   the type colour without any rule having to know the type.
   See docs/design/0003-colour-by-dance-type.md. */

[data-colour="ecstatic_dance"],
.chip[data-field="dance_type"][data-value="ecstatic_dance"] {
  --type-bg:  var(--type-ecstatic_dance-bg);
  --type-ink: var(--type-ecstatic_dance-ink);
  --accent:   var(--type-ecstatic_dance-ink);
}

[data-colour="5rhythms"],
.chip[data-field="dance_type"][data-value="5rhythms"] {
  --type-bg:  var(--type-5rhythms-bg);
  --type-ink: var(--type-5rhythms-ink);
  --accent:   var(--type-5rhythms-ink);
}

[data-colour="movement_medicine"],
.chip[data-field="dance_type"][data-value="movement_medicine"] {
  --type-bg:  var(--type-movement_medicine-bg);
  --type-ink: var(--type-movement_medicine-ink);
  --accent:   var(--type-movement_medicine-ink);
}

[data-colour="open_floor"],
.chip[data-field="dance_type"][data-value="open_floor"] {
  --type-bg:  var(--type-open_floor-bg);
  --type-ink: var(--type-open_floor-ink);
  --accent:   var(--type-open_floor-ink);
}

[data-colour="biodanza"],
.chip[data-field="dance_type"][data-value="biodanza"] {
  --type-bg:  var(--type-biodanza-bg);
  --type-ink: var(--type-biodanza-ink);
  --accent:   var(--type-biodanza-ink);
}

[data-colour="contact_improvisation"],
.chip[data-field="dance_type"][data-value="contact_improvisation"] {
  --type-bg:  var(--type-contact_improvisation-bg);
  --type-ink: var(--type-contact_improvisation-ink);
  --accent:   var(--type-contact_improvisation-ink);
}

[data-colour="trance_dance"],
.chip[data-field="dance_type"][data-value="trance_dance"] {
  --type-bg:  var(--type-trance_dance-bg);
  --type-ink: var(--type-trance_dance-ink);
  --accent:   var(--type-trance_dance-ink);
}

[data-colour="authentic_movement"],
.chip[data-field="dance_type"][data-value="authentic_movement"] {
  --type-bg:  var(--type-authentic_movement-bg);
  --type-ink: var(--type-authentic_movement-ink);
  --accent:   var(--type-authentic_movement-ink);
}

[data-colour="conscious_dance_general"],
.chip[data-field="dance_type"][data-value="conscious_dance_general"] {
  --type-bg:  var(--type-conscious_dance_general-bg);
  --type-ink: var(--type-conscious_dance_general-ink);
  --accent:   var(--type-conscious_dance_general-ink);
}

/* The dance-type chips look like the tags they filter — that is the whole
   legend. Selected inverts the pair, so it is the same two colours either way.
   These carry one more attribute than `.chip:hover` and `.chip.is-active` and
   are placed after them, so they win both ties. */
.chip[data-field="dance_type"] {
  background:    var(--type-bg, var(--surface));
  color:         var(--type-ink, var(--ink));
  border-color:  var(--type-bg, var(--line));
}
.chip[data-field="dance_type"]:hover { border-color: var(--type-ink, var(--accent)); }
.chip[data-field="dance_type"].is-active {
  background:   var(--type-ink, var(--accent));
  color:        var(--type-bg, var(--accent-ink));
  border-color: var(--type-ink, var(--accent));
}
/* The count sits at 0.6 opacity elsewhere, which was measured against `--ink`
   on `--surface`. Blended into a type tint it falls to 2.84:1 at its worst
   (biodanza, light) — below AA. 0.85 puts every one of the nine over 4.5:1 in
   all four states. See docs/design/0003-colour-by-dance-type.md. */
.chip[data-field="dance_type"] .count { opacity: 0.85; }

/* Listing --------------------------------------------------------------- */

.listing { padding-top: var(--space-3); }

.day + .day { margin-top: var(--space-5); }

.day-heading {
  margin: 0 0 var(--space-3);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--line);
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.cards { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }

.card {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr);
  gap: var(--space-3);
  padding: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--line);
  /* The type's colour as a 3px spine, so a scan down the listing groups by
     type without reading a word. Falls back to the plain hairline. */
  border-inline-start: 3px solid var(--type-ink, var(--line));
  border-radius: var(--radius);
}
.card:hover {
  border-color: var(--accent);
  border-inline-start-color: var(--type-ink, var(--accent));
}

.card-image { width: 84px; height: 84px; object-fit: cover; border-radius: 8px; }
.card-body { min-width: 0; }
/* The image is only emitted when an event has one. Without it the body is the
   first child and would auto-place into the 84px thumbnail track. */
.card-body:first-child { grid-column: 1 / -1; }

.card-meta {
  margin: 0 0 var(--space-1);
  font-size: 0.8rem;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.dot { opacity: 0.5; }
.area.is-uncertain { border-bottom: 1px dotted currentColor; cursor: help; }

.card-title {
  margin: 0 0 var(--space-1);
  font-size: 1rem;
  line-height: 1.3;
  overflow-wrap: anywhere;
}
.card-title a { color: var(--ink); text-decoration: none; }
.card-title a:hover { color: var(--accent); text-decoration: underline; }

.card-venue {
  margin: 0 0 var(--space-2);
  font-size: 0.85rem;
  color: var(--ink-muted);
  overflow-wrap: anywhere;
}

.card-tags { margin: 0; display: flex; flex-wrap: wrap; gap: var(--space-1); min-width: 0; }

.tag {
  padding: 0.1rem 0.45rem;
  border-radius: 4px;
  background: var(--tag-bg);
  color: var(--tag-ink);
  font-size: 0.72rem;
  white-space: nowrap;
}
/* Only `.tag-dance` reads the type colour. The format, cadence and price tags
   sit inside the same card and so inside the same `--type-*` scope; leaving
   them on `--tag-bg` is what keeps the colour meaning one thing. */
.tag-dance {
  font-weight: 600;
  background: var(--type-bg, var(--tag-bg));
  color: var(--type-ink, var(--tag-ink));
}
.tag-price { font-variant-numeric: tabular-nums; }

.empty { padding: var(--space-5) 0; color: var(--ink-muted); }

/* Calendar ---------------------------------------------------------------

   Mobile first, and for the calendar that means something stronger than
   "narrower": below 48rem the month is not a grid at all but an agenda, one day
   per row, because a seventh of a 400px screen is 50px and fits a date and
   nothing else. The seven-column grid arrives at 48rem.
   See docs/design/0004-month-grid.md. */

.calendar { padding-top: var(--space-3); }

.cal-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--line);
}
.cal-month { margin: 0; font-size: 1.15rem; letter-spacing: -0.01em; }
.cal-step { font-size: 0.85rem; text-decoration: none; white-space: nowrap; }
.cal-step:hover { text-decoration: underline; }
.cal-step.is-off { color: var(--ink-muted); opacity: 0.3; }

/* The weekday header is a property of the grid; in the agenda each row carries
   its own weekday instead. */
.cal-dow-row { display: none; }

.cal-day {
  min-width: 0;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--line);
}
/* Neighbouring months' spacer cells exist to line the grid up. In the agenda
   there is no grid to line up. */
.cal-day.is-outside { display: none; }
/* A day with nothing on it is not worth a row. `:has()` is re-evaluated as the
   filter hides entries, so the agenda follows the filtering without the script
   knowing anything about calendars. Where `:has()` is unsupported the row shows
   as a bare date — untidy, not broken. */
.cal-day:not(:has(.cal-event:not([hidden]))) { display: none; }
.cal-day.is-past { opacity: 0.55; }

.cal-date {
  margin: 0 0 var(--space-1);
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.cal-dow { text-transform: uppercase; letter-spacing: 0.06em; }
.cal-day.is-today .cal-date { color: var(--accent); }
.cal-today-flag {
  margin-inline-start: var(--space-1);
  padding: 0.05rem 0.35rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 0.65rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* The fifth consumer of the per-type custom properties, after the tag, the card
   spine, the chip and `--accent` — an entry and the card for the same event are
   the same colour because both read the same two variables. The fallback is the
   neutral tag pair, exactly as everywhere else.
   See docs/design/0003-colour-by-dance-type.md. */
.cal-event {
  display: block;
  min-width: 0;
  margin-top: var(--space-1);
  padding: 0.2rem 0.4rem;
  border-radius: 6px;
  background: var(--type-bg, var(--tag-bg));
  color: var(--type-ink, var(--tag-ink));
  font-size: 0.78rem;
  line-height: 1.35;
  text-decoration: none;
  overflow-wrap: anywhere;
}
.cal-event:hover { outline: 2px solid var(--type-ink, var(--accent)); }
.cal-time { font-weight: 700; font-variant-numeric: tabular-nums; }
.cal-event-title { font-weight: 600; }
.cal-event-type { display: block; opacity: 0.85; font-size: 0.7rem; }

/* A multi-day event. In the agenda there are no neighbouring cells to join, so
   each day's piece is an ordinary entry that says which day it is, and a
   filler — which only holds a bar's lane in the grid — takes no room.
   See docs/design/0008-multi-day-bars.md. */
.cal-span-filler { display: none; }
.cal-span-day { display: block; opacity: 0.85; font-size: 0.7rem; }
/* The grid's short second line; the agenda already says "Day n of m" in full. */
.cal-span-meta { display: none; }

@media (min-width: 48rem) {
  .cal-dow-row,
  .cal-week {
    display: grid;
    /* `minmax(0, 1fr)`, never `1fr`: a bare `1fr` is `minmax(auto, 1fr)` and the
       auto floor refuses to shrink below the longest word in the cell, which is
       how a seven-column grid of event titles overflows its container. The same
       trap browser.py's docstring records for the card. */
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: var(--space-1);
  }
  .cal-dow-row { margin-bottom: var(--space-1); }
  .cal-dow-head {
    padding: 0 0.25rem;
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-muted);
  }
  .cal-grid { display: grid; gap: var(--space-1); }

  .cal-day,
  /* Both agenda collapses are undone here rather than scoped the other way, so
     that the narrow rules stay the default and this block is purely additive.
     Each selector repeats the one it overrides — same specificity, later wins. */
  .cal-day:not(:has(.cal-event:not([hidden]))) {
    display: block;
    min-height: 5.75rem;
    padding: var(--space-1);
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--surface);
  }
  .cal-day.is-outside { display: block; background: none; border-color: transparent; }
  .cal-day.is-outside .cal-date { opacity: 0.4; }

  .cal-dow { display: none; }
  .cal-event { font-size: 0.72rem; }
  /* One long title otherwise sets the height of the whole week row — a cell is
     as tall as its text and a row is as tall as its tallest cell. Two lines for
     the whole entry, time included: the time runs inline ahead of the title
     rather than taking a line of its own, and the full title is on the `title`
     attribute and the event's own page. Bars are excluded — they have their own
     fixed two-line box below. docs/design/0009-full-width-two-line-calendar.md. */
  /* The vertical padding becomes a transparent border of the same size: the
     clamp's `overflow` cuts at the padding edge, so padding would show the top
     of the third line. The background still paints under a border. */
  .cal-event:not(.cal-span) {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    padding-block: 0;
    border-block: 0.2rem solid transparent;
  }
  /* A bar is two lines tall in every cell of its week, lane for lane, so its
     pieces line up whatever else a cell holds: the title once per row on the
     first, and on the second every piece's day and its own start time. The
     filler is an invisible piece of the same height; `[hidden]` still removes
     both when the bar is filtered out, and the whole lane goes with them. */
  .cal-span,
  .cal-span-filler {
    display: block;
    height: 2.8rem;
    margin-top: var(--space-1);
    padding-block: 0;
    line-height: 1.4rem;
    white-space: nowrap;
    overflow: hidden;
  }
  .cal-span-filler { visibility: hidden; }
  /* The title is written once per week row, where the bar starts in it, and
     runs the length of the bar rather than stopping at its first cell. The
     width is the piece's own content box times the cells it spans, plus what
     lies between them: `--cells` pieces' inline padding (0.4rem a side), and
     each join's cell padding, border and grid gap, less the bridge already
     counted in this piece. The cell holding it is raised so the next day's
     piece does not paint over the text — `.is-past` opacity makes every past
     cell its own stacking context. Where `:has()` is unsupported the title is
     cut at the first cell. Every piece stays a named link — "Bound & Unbound
     … Day 2 of 3" — so the other pieces, and the day label on all of them,
     are clipped for screen readers rather than hidden. */
  .cal-span-day,
  .cal-span:not([data-title-here]) .cal-span-text {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .cal-span[data-title-here] { overflow: visible; }
  /* Flex, so the title is a box no wider than it is drawn: a clipped inline
     span keeps its full text width, which verify.py's width probe rightly
     reads as overflow. */
  .cal-span-text { display: flex; min-width: 0; }
  .cal-span-text .cal-time { display: block; flex: none; margin-inline-end: 0.3em; }
  .cal-span .cal-event-title {
    display: block;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* The second line stays inside its own cell, even on the piece whose title
     runs on past it, so each day's label is clear of the next. Where the title
     is clipped away the first line is left as bare colour, so the label is
     pushed down to meet the second line of its neighbours. */
  .cal-span-meta {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    font-variant-numeric: tabular-nums;
    opacity: 0.85;
  }
  .cal-span:not([data-title-here]) .cal-span-meta { margin-top: 1.4rem; }
  .cal-span[data-title-here][data-bridges-after] .cal-span-text {
    width: calc(
      var(--cells) * 100%
      + (var(--cells) - 2) * (var(--space-1) + 1px)
      - var(--space-1)
      + (var(--cells) - 1) * 0.8rem
    );
  }
  /* Titles only run rightward, so a cell sits above every cell after it in its
     row: two bars starting on consecutive days each keep their title clear. */
  .cal-day:has(.cal-span[data-title-here]) { position: relative; z-index: 1; }
  .cal-week > .cal-day:nth-child(1):has(.cal-span[data-title-here]) { z-index: 7; }
  .cal-week > .cal-day:nth-child(2):has(.cal-span[data-title-here]) { z-index: 6; }
  .cal-week > .cal-day:nth-child(3):has(.cal-span[data-title-here]) { z-index: 5; }
  .cal-week > .cal-day:nth-child(4):has(.cal-span[data-title-here]) { z-index: 4; }
  .cal-week > .cal-day:nth-child(5):has(.cal-span[data-title-here]) { z-index: 3; }
  .cal-week > .cal-day:nth-child(6):has(.cal-span[data-title-here]) { z-index: 2; }
  .cal-span[data-continues-before] { border-start-start-radius: 0; border-end-start-radius: 0; }
  .cal-span[data-continues-after] { border-start-end-radius: 0; border-end-end-radius: 0; }
  /* Reach through the cell's padding and border, and the gap after it, to meet
     the next day's piece: `--space-1` padding, 1px border, `--space-1` gap. */
  .cal-span[data-bridges-before] { margin-inline-start: calc(-1 * var(--space-1) - 1px); }
  .cal-span[data-bridges-after] { margin-inline-end: calc(-2 * var(--space-1) - 1px); }
  .cal-span[data-bridges-before]:hover,
  .cal-span[data-bridges-after]:hover { outline-offset: -2px; }

  /* In the grid an entry has two lines and the type label would need a third.
     It stays in the DOM for screen readers — and the filter bar is the legend
     for everyone else. docs/design/0004-month-grid.md, 0009. */
  .cal-event-type {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* Detail ---------------------------------------------------------------- */

.detail { padding: var(--space-4) 0 var(--space-6); }
.back { margin: 0 0 var(--space-4); font-size: 0.9rem; }
.detail-image { width: 100%; max-height: 320px; object-fit: cover; border-radius: var(--radius); }
.detail h1 {
  margin: var(--space-4) 0 var(--space-4);
  font-size: 1.6rem;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.detail-facts { margin: 0; display: grid; gap: var(--space-3); }
.detail-facts dt {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.detail-facts dd { margin: var(--space-1) 0 0; }

/* Flex so that two buttons (an event two sources list) wrap with a gap. */
.cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-5) 0 var(--space-3);
}

.button {
  display: inline-block;
  padding: 0.7rem 1.2rem;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
  text-decoration: none;
}
.button:hover { filter: brightness(1.1); }

/* Footer ---------------------------------------------------------------- */

.site-footer {
  margin-top: var(--space-6);
  padding: var(--space-4) 0 var(--space-6);
  border-top: 1px solid var(--line);
  font-size: 0.85rem;
  color: var(--ink-muted);
}
.site-footer p { margin: 0 0 var(--space-2); }

/* Wider screens --------------------------------------------------------- */

@media (min-width: 40rem) {
  body { padding: 0 var(--space-5); }
  .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .detail h1 { font-size: 2rem; }
  .detail-facts { grid-template-columns: 8rem 1fr; align-items: baseline; }
  .detail-facts dt { grid-column: 1; }
  .detail-facts dd { grid-column: 2; margin-top: 0; }
}

@media (min-width: 64rem) {
  .cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
