/* FestWiz — Halloween tracker
 *
 * Forked from the festival app's design language (teal nav, crimson accent)
 * but its own file: see HALLOWEEN-PLAN.md on why these are not shared.
 *
 * Dark only, by design — see the note on <html> below.
 *
 * The six day colours come from the printed guide. There they are page
 * backgrounds with black text; here they are headings, rules and borders,
 * because those pastels behind light text on a dark ground fail contrast
 * badly. Table view puts them back as fills with black text, where they can
 * behave as they do on paper.
 */

:root {
  --bg: #14120f;
  --bg-raised: #1c1915;
  --bg-card: #1e1a16;
  --line: #332c24;
  --line-soft: #241f1a;

  --text: #ece6dc;
  --text-2: #b9ada0;
  --text-3: #b0a598;   /* descriptions — see the note on the table rows */
  --text-4: #8d8275;

  /* The masthead is a fixed dark teal bar, lighter than the page behind it,
     so its text is set here rather than reusing the body text tokens. */
  --nav-bg: #0f3934;
  --nav-line: #2d5f57;
  --nav-text: #9fb8b2;
  --nav-fg: #ffffff;
  --nav-fg-dim: #cfe0dc;
  --accent: #bb124d;
  --glow: #a855f7;
  --amber: #f2a541;
  --money: #7dd3a0;
  --tag: #c86a4a;

  --day-1: #e8a3a3;
  --day-2: #efb27a;
  --day-3: #9ec6d0;
  --day-4: #c3b3de;
  --day-5: #a3c894;
  --day-6: #bdb5ab;

  --serif: Bitter, Georgia, "Times New Roman", serif;
  --cond: "Barlow Condensed", "Arial Narrow", system-ui, sans-serif;

  --gutter: 40px;

  /* Diagonal hatch behind the cards, from assets/diagonal-lines.svg.
     Intensity and tile size are set here; the COLOUR is the fill inside that
     file (currently white), because a tiled background cannot take a CSS
     variable for its colour the way a mask could. */
  --texture-opacity: 0.15;
  --texture-size: 6px;

  --row-stripe: 26, 23, 19;   /* #1a1713 — the opaque stripe */
  --row-alt:    41, 38, 33;   /* #292621 — the translucent one, lighter so the
                                 alternation reads even through the hatch */

  /* Measured at runtime by halloween.js — the masthead's height and the
     masthead+filters stack. Hardcoding these left the sticky table header
     parked below the filter bar with a strip of page showing through, and
     they change with the font, the breakpoint and the filter drawer. */
  --masthead-h: 78px;
  --sticky-top: 135px;
}


*, *::before, *::after { box-sizing: border-box; }

/* Dark is the only theme. Declaring it means native controls, scrollbars and
   the search field's clear button render dark instead of light-on-dark.
   A light theme was built and removed: it doubled what needed checking on
   every change, and a Halloween guide wants to be dark. */
html { -webkit-text-size-adjust: 100%; color-scheme: dark; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--serif);
  font-size: 15px;
  line-height: 1.5;
  overflow-wrap: break-word;
}

a { color: var(--amber); }
a:hover { color: #ffc46b; }

.u-visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }

/* Up to an iPad in landscape, the 40px gutter costs too much width. */
@media (max-width: 1024px) { :root { --gutter: 16px; } }

/* ── masthead ─────────────────────────────────────────────────────────── */

.masthead {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: 18px;
  padding: 14px var(--gutter);
  background: var(--nav-bg);
  border-bottom: 3px solid var(--accent);
}

/* Amber by default — the crimson is dark against the teal bar and hard to
   read; it becomes the hover state instead. The logotype's paths are
   currentColor, so this one property recolours the mark. */
.masthead__brand {
  position: relative;
  display: flex; align-items: center; color: var(--amber);
  background: none; border: 0; padding: 0; cursor: pointer;
}
.masthead__brand:hover { color: var(--accent); }
@media (prefers-reduced-motion: no-preference) {
  .masthead__brand { transition: color 220ms ease; }
}
.masthead__logo {
  display: block; width: 116px; height: auto;
  /* Two shadows: a tight one for definition, a wide one for the halo. */
  filter: drop-shadow(0 0 4px rgba(168, 85, 247, 0.9))
          drop-shadow(0 0 14px rgba(168, 85, 247, 0.55));
}
@media (prefers-reduced-motion: no-preference) {
  .masthead__logo { transition: filter 220ms ease; }
  .masthead__brand:hover .masthead__logo {
    filter: drop-shadow(0 0 5px rgba(168, 85, 247, 1))
            drop-shadow(0 0 20px rgba(168, 85, 247, 0.75));
  }
}

.masthead__divider { width: 1px; height: 26px; background: var(--nav-line); }

.masthead__titles { flex: 1 1 auto; min-width: 0; }

.masthead__title {
  margin: 0;
  font-family: var(--cond);
  font-size: 25px; font-weight: 600; letter-spacing: 0.4px;
  color: var(--nav-fg); line-height: 1.1;
}

.masthead__sub { margin: 2px 0 0; font-size: 12px; color: var(--nav-fg-dim); }

/* Toggle and both buttons sit closer together than the rest of the bar, and
   stretch to one height. The CTAs have no vertical padding, so the toggle is
   the tallest item and sets that height. */
.masthead__actions {
  display: flex; flex: none; align-items: stretch; gap: 8px;
}

.masthead__cta {
  flex: none;
  white-space: nowrap;
  font-family: var(--cond);
  font-size: 16px; font-weight: 600; letter-spacing: 0.5px;
  text-decoration: none;
  color: #14120f; background: var(--amber);
  display: flex; align-items: center;
  padding: 0 18px; border-radius: 3px;
}
.masthead__cta:hover { color: #14120f; background: #ffc46b; }
/* Outlined, so Subscribe stays the one filled call to action in the bar. */
.masthead__cta--secondary {
  color: var(--amber); background: transparent;
  box-shadow: inset 0 0 0 1px var(--amber);
}
.masthead__cta--secondary:hover { color: #14120f; background: var(--amber); }

.masthead__filters-btn {
  display: none; position: relative;
  width: 44px; height: 40px; flex: none;
  align-items: center; justify-content: center;
  background: #14463f; border: 1px solid var(--nav-line);
  border-radius: 3px; cursor: pointer;
}
.masthead__filters-btn svg { width: 18px; height: 18px; fill: none; stroke: var(--nav-fg); stroke-width: 2; stroke-linecap: round; }
.masthead__filters-count {
  position: absolute; top: -6px; right: -6px;
  min-width: 18px; height: 18px; padding: 0 5px;
  font-family: var(--cond); font-size: 12px; font-weight: 700; line-height: 18px;
  color: #fff; background: var(--accent); border-radius: 9px;
}

/* ── easter egg: the logo bleeds ──────────────────────────────────────── */

/* Hold the cursor on the logotype for 20 seconds and it starts to drip.
   Purely decorative, aria-hidden, and skipped entirely for anyone who has
   asked for reduced motion. */

.brand-blood {
  position: absolute;
  left: 0; right: 0; top: 100%;
  height: 0;
  pointer-events: none;
}

.drip {
  position: absolute;
  top: -2px;
  width: var(--w, 4px);
  height: 0;
  background: linear-gradient(#8f0a2f, #bb124d 60%, #d4194f);
  border-radius: 0 0 var(--w, 4px) var(--w, 4px);
  animation: drip-grow var(--dur, 2.6s) ease-in var(--delay, 0s) forwards;
}

/* The bead that lets go at the bottom of the run. */
.drip::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -2px;
  width: calc(var(--w, 4px) * 1.6);
  height: calc(var(--w, 4px) * 1.9);
  margin-left: calc(var(--w, 4px) * -0.8);
  background: #bb124d;
  border-radius: 50% 50% 55% 55%;
  opacity: 0;
  animation: drip-fall calc(var(--dur, 2.6s) * 1.5) ease-in
             calc(var(--delay, 0s) + var(--dur, 2.6s) * 0.75) infinite;
}

@keyframes drip-grow {
  0%   { height: 0; }
  100% { height: var(--len, 20px); }
}

@keyframes drip-fall {
  0%   { opacity: 0; transform: translateY(0) scale(0.6); }
  15%  { opacity: 1; transform: translateY(2px) scale(1); }
  100% { opacity: 0; transform: translateY(34px) scale(0.75); }
}

.brand-blood.is-drying .drip {
  animation: none;
  height: 0;
  transition: height 700ms ease-in-out;
}
.brand-blood.is-drying .drip::after { animation: none; opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .brand-blood { display: none; }
}

/* ── view toggle ──────────────────────────────────────────────────────── */

.viewtoggle {
  display: flex; flex: none; gap: 2px; padding: 2px;
  background: #14463f; border: 1px solid var(--nav-line); border-radius: 3px;
}
.viewtoggle__btn {
  font-family: var(--cond);
  font-size: 15px; font-weight: 600; letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--nav-fg-dim); background: transparent;
  border: 0; padding: 6px 15px; border-radius: 2px; cursor: pointer;
}
.viewtoggle__btn:hover { color: var(--nav-fg); }
/* Light pill on the dark bar. */
.viewtoggle__btn.is-active { color: #0b2b27; background: #ffffff; font-weight: 700; }

/* Six columns cannot work at phone width without horizontal scrolling, and a
   scrolling table is worse than either view, so below this the toggle goes. */
@media (max-width: 860px) {
  .masthead__actions, .masthead__divider { display: none; }
  .masthead__filters-btn { display: flex; }
  .masthead__title { font-size: 19px; }
  .masthead__logo { width: 86px; }
}

/* ── filters ──────────────────────────────────────────────────────────── */

/* One row of dropdowns rather than four rows of chips: with 7 tags and 5 age
   values, laying every option out flat cost more vertical space than the
   first row of events. Each control shows its current selection and opens a
   panel on click. */

.filters {
  position: sticky; top: var(--masthead-h); z-index: 20;
  padding: 10px var(--gutter);
  background: var(--bg-raised);
  border-bottom: 1px solid #2b2620;
}

.filterbar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }

.fdrop { position: relative; }

.fdrop__btn {
  display: flex; align-items: baseline; gap: 8px;
  min-height: 36px;
  font-family: var(--serif); font-size: 13px;
  color: var(--text); background: #262019;
  border: 1px solid #3a3229; border-radius: 3px;
  padding: 6px 11px; cursor: pointer;
}
.fdrop__btn:hover { border-color: #55493c; }
.fdrop.is-open .fdrop__btn { border-color: var(--amber); }

.fdrop__label {
  font-family: var(--cond); font-size: 13px; letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--text-4);
}
.fdrop__value { font-weight: 600; }
.fdrop.is-set .fdrop__value { color: var(--amber); }

.fdrop__chev {
  width: 13px; height: 13px; align-self: center;
  fill: none; stroke: var(--text-4); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform 140ms ease;
}
@media (prefers-reduced-motion: reduce) { .fdrop__chev { transition: none; } }
.fdrop.is-open .fdrop__chev { transform: rotate(180deg); }

/* The [hidden] attribute only hides things via the UA stylesheet's
   display:none, which ANY author display rule outranks — so .fdrop__panel's
   own display:flex made the panels render open whatever the attribute said.
   This restates it at class+attribute specificity, which wins. */
.fdrop__panel[hidden] { display: none; }

.fdrop__panel {
  position: absolute; top: calc(100% + 5px); left: 0; z-index: 40;
  min-width: 190px; padding: 8px;
  background: #221d18; border: 1px solid #443a30;
  border-radius: 4px; box-shadow: 0 10px 28px rgba(0, 0, 0, 0.55);
  display: flex; flex-direction: column; gap: 3px;
}
.fdrop__panel--grid {
  flex-direction: row; flex-wrap: wrap; gap: 6px;
  width: 340px; max-width: calc(100vw - 2 * var(--gutter));
}

.opt {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--serif); font-size: 13px; text-align: left;
  color: var(--text); background: transparent;
  border: 0; border-radius: 3px; padding: 7px 9px; cursor: pointer;
}
.opt:hover { background: #2e2720; }
.opt[aria-pressed="true"] { color: var(--amber); font-weight: 600; }
.opt__tick { width: 13px; flex: none; color: var(--amber); }

.opt--tag {
  border: 1px solid #3a3229; padding: 5px 11px;
}
.opt--tag[aria-pressed="true"] {
  color: #14120f;
  background: var(--tag-bg, var(--tag));
  border-color: var(--tag-bg, var(--tag));
  font-weight: 600;
}
.opt__count { color: var(--text-4); font-size: 12px; font-weight: 400; }
.opt--tag[aria-pressed="true"] .opt__count { color: #3d1b0e; }

.fdrop__note {
  margin: 6px 3px 1px; padding-top: 7px;
  border-top: 1px solid #3a3229;
  font-size: 12px; line-height: 1.35; color: var(--text-4);
}

.filters__search { flex: 1 1 200px; min-width: 160px; display: flex; }
.filters__search input {
  flex: 1 1 auto; min-height: 36px;
  font-family: var(--serif); font-size: 13px;
  color: var(--text); background: #262019;
  border: 1px solid #3a3229; border-radius: 3px; padding: 6px 11px;
}
.filters__search input::placeholder { color: var(--text-4); }

.filters__clear {
  font-family: var(--serif); font-size: 13px; color: var(--text-4);
  background: none; border: 0; cursor: pointer; text-decoration: underline;
  padding: 6px 2px;
}
.filters__clear:hover { color: var(--text); }

.filters__result { font-size: 12px; color: var(--text-4); margin-left: auto; }

/* The drawer's own copy of the view toggle. Hidden on desktop, where the
   masthead already has one. */
.filterbar__view { display: none; gap: 2px; padding: 2px;
  background: #262019; border: 1px solid #3a3229; border-radius: 3px; }

@media (max-width: 860px) {
  /* The drawer is FIXED under the masthead, not static. Static put it at its
     place in the document flow, so opening it while scrolled down toggled
     something off-screen and looked like a dead button. */
  .filters { display: none; }
  .filters.is-open {
    display: block;
    position: fixed;
    top: var(--masthead-h);
    left: 0; right: 0;
    z-index: 25;
    max-height: calc(100vh - var(--masthead-h) - 8px);
    overflow-y: auto;
    box-shadow: 0 16px 34px rgba(0, 0, 0, 0.55);
    border-bottom: 1px solid var(--line);
  }
  .filterbar { gap: 8px; }
  .filterbar__view { display: flex; align-self: flex-start; }
  .filterbar__view .viewtoggle__btn { color: var(--text); }
  .filterbar__view .viewtoggle__btn.is-active { color: #14120f; background: var(--amber); }

  /* Six columns cannot fit a phone, so let the table scroll sideways rather
     than crushing every column. */
  .tablewrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .dtable { min-width: 680px; }
  .fdrop, .fdrop__btn { width: 100%; }
  .fdrop__btn { justify-content: space-between; }
  .fdrop__panel { width: 100%; min-width: 0; }
  .fdrop__panel--grid { width: 100%; }
  .filters__search { flex-basis: 100%; }
  .filters__result { margin-left: 0; }
}

/* ── layout ───────────────────────────────────────────────────────────── */

main { padding: 0 var(--gutter) 48px; position: relative; }

/* Texture sits on the panel itself, in BOTH views, so they read as the same
   page. Cards and striped table rows have solid backgrounds and lie on top of
   it; day headings and unstriped rows are transparent, so the hatch runs
   through them. */
/* Table view runs the hatch harder: the rows cover most of it, so it reads
   mainly down the gutters and below the last row. The rows themselves damp
   it back down — see the translucent stripe below. */
body[data-view="table"] { --texture-opacity: 0.5; }

main::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  opacity: var(--texture-opacity);
  /* Brian's diagonal-lines.svg, tiled as a background-image.
     It was briefly a mask, so the hue could be a CSS variable —
     but a mask composites across the whole page height, which with this many
     events is tens of thousands of pixels and slow on a phone. A tiled
     background is rasterised once and repeated, which is cheap.
     The trade: the colour now lives in the SVG's fill rather than in a
     variable here. --texture-opacity still sets the intensity. */
  background-image: url("/halloween/assets/diagonal-lines.svg");
  background-repeat: repeat;
  background-size: var(--texture-size) var(--texture-size);
}

/* Lift the content above the texture layer. */
main > * { position: relative; z-index: 1; }

.status { color: var(--text-4); font-size: 14px; padding: 28px 0; }

/* 178 events is ~3,500 elements. Laying out and painting all of them on load
   blocks the main thread long enough that taps on the filter button are
   ignored. content-visibility lets the browser skip layout and paint for
   sections that are off screen, and contain-intrinsic-size gives it a size
   to assume for them so the scrollbar does not jump as they are reached. */
.day {
  scroll-margin-top: calc(var(--sticky-top) + 12px);
  content-visibility: auto;
  contain-intrinsic-size: auto 520px;
}

.dtable tbody {
  content-visibility: auto;
  contain-intrinsic-size: auto 320px;
}

.day__head {
  display: flex; align-items: baseline; gap: 14px;
  padding: 26px 0 5px; margin-bottom: 18px;
  border-bottom: 2px solid var(--day, var(--day-1));
}
/* The date never wraps and never shrinks: it is the thing you scan for.
   flex: none stops the subscribe link's length from squeezing it, and
   nowrap stops "SAT 10/17" breaking across two lines. */
.day__name {
  flex: none;
  white-space: nowrap;
  font-family: var(--cond); font-size: 32px; font-weight: 700;
  letter-spacing: 0.5px; color: var(--day, var(--day-1));
  text-transform: uppercase;
}
.day__today {
  flex: none; white-space: nowrap;
  font-family: var(--cond); font-size: 14px; font-weight: 600; letter-spacing: 1.4px;
  color: #14120f; background: var(--day, var(--day-1));
  padding: 3px 9px; border-radius: 2px;
}
/* Hidden for now, not removed — same as the table view's count. */
.day__count { display: none; margin-left: auto; font-size: 12px; color: var(--text-4); }

/* Sits where the count was, in the same colour as the date beside it — the
   day's own colour. No underline; the affordance arrives on hover. */
/* Takes whatever is left and wraps inside it. min-width:0 is what actually
   lets a flex item shrink below its content width. */
.day__sub {
  margin-left: auto;
  flex: 0 1 auto;
  min-width: 0;
  text-align: right;
  font-size: 12px;
  line-height: 1.3;
  color: var(--day, var(--day-1));
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.day__sub:hover,
.day__sub:focus-visible {
  color: #fff;
  border-bottom-color: #fff;
}

.past-head {
  display: flex; align-items: baseline; gap: 14px;
  margin: 46px 0 0; padding: 14px 0 12px;
  border-top: 3px solid var(--line); border-bottom: 1px solid var(--line);
}
.past-head h2 {
  margin: 0; font-family: var(--cond); font-size: 26px; font-weight: 700;
  letter-spacing: 1px; text-transform: uppercase; color: var(--text-4);
}
.past-head p { margin: 0; font-size: 12px; color: var(--text-4); }
.is-past { opacity: 0.62; }

/* ── about modal ──────────────────────────────────────────────────────── */

.modal-overlay {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  background: rgba(0, 0, 0, 0.72);
}
/* Same trap as the filter panels: an author display rule outranks the UA
   stylesheet's [hidden], so restate it at higher specificity. */
.modal-overlay[hidden] { display: none; }

/* ZAP! — lifted verbatim from the festival app so the two behave identically.
   The overlay flashes while the dialog shrinks, spins and blows out to white,
   then the JS removes it after 700ms. */
@keyframes zap-screen-flash {
  0%   { background: rgba(0,0,0,0.55); }
  10%  { background: rgba(255,240,60,0.75); }
  22%  { background: rgba(255,255,255,0.97); }
  35%  { background: rgba(255,220,30,0.55); }
  50%  { background: rgba(255,255,180,0.8); }
  65%  { background: rgba(200,180,0,0.3); }
  100% { background: rgba(0,0,0,0); }
}

@keyframes zap-modal-shrink {
  0%   { transform: scale(1)    rotate(0deg);   filter: brightness(1);          opacity: 1; }
  12%  { transform: scale(1.07) rotate(-2deg);  filter: brightness(5);          opacity: 1; }
  28%  { transform: scale(0.92) rotate(3deg);   filter: brightness(2);          opacity: 1; }
  50%  { transform: scale(0.55) rotate(-6deg);  filter: brightness(10) blur(2px); opacity: 0.85; }
  100% { transform: scale(0.04) rotate(18deg);  filter: brightness(25) blur(8px); opacity: 0; }
}

.modal-overlay.zapping {
  animation: zap-screen-flash 0.7s ease-out forwards;
  pointer-events: none;
}

.modal-overlay.zapping .modal {
  animation: zap-modal-shrink 0.7s cubic-bezier(0.4, 0, 1, 1) forwards;
}

/* A hard strobe is exactly what some people cannot safely be shown. */
@media (prefers-reduced-motion: reduce) {
  .modal-overlay.zapping,
  .modal-overlay.zapping .modal { animation: none; }
}

.modal {
  width: min(620px, 100%);
  max-height: min(78vh, 780px);
  display: flex; flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-top: 4px solid var(--accent);
  border-radius: 5px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
}

.modal__title {
  margin: 0; padding: 18px 24px 12px;
  font-family: var(--cond); font-size: 28px; font-weight: 700;
  letter-spacing: 0.5px; text-transform: uppercase; color: var(--amber);
}

.modal__body {
  padding: 0 24px 4px;
  overflow-y: auto;
  font-size: 14px; line-height: 1.6; color: var(--text-2);
}
.modal__body p { margin: 0 0 13px; }
.modal__body strong { color: var(--text); }
.modal__sig { color: var(--text-4); font-style: italic; }
.modal__colophon {
  margin-top: 16px; padding-top: 12px;
  border-top: 1px solid var(--line);
  font-size: 12px; color: var(--text-4);
}
.modal__body a { color: var(--amber); }
.modal__body a:hover { color: #ffc46b; }

.modal__foot {
  padding: 14px 24px 18px;
  border-top: 1px solid var(--line);
  text-align: right;
}

.btn-zap {
  font-family: var(--cond); font-size: 17px; font-weight: 700;
  letter-spacing: 1.2px; text-transform: uppercase;
  color: #14120f; background: var(--amber);
  border: 0; border-radius: 3px; padding: 9px 22px; cursor: pointer;
}
.btn-zap:hover { background: #ffc46b; }

@media (max-width: 720px) {
  .modal-overlay { padding: 12px; align-items: flex-end; }
  .modal { max-height: 86vh; }
  .modal__title { font-size: 23px; }
}

/* ── event links ──────────────────────────────────────────────────────── */

/* A quiet underline at rest so links are identifiable without relying on
   colour; on hover the whole word takes an amber ground with black text,
   which reads as clickable far more plainly than a colour change.
   
   The horizontal breathing room comes from a box-shadow spread, NOT padding:
   padding plus a negative margin made the inline box wider than its
   container, and with overflow-wrap: break-word on <body> that broke names
   mid-word ("Halloweir/d"). A shadow paints outside the box and takes no
   part in layout, so nothing can reflow. */
.card__name a,
.dtable .t-name a {
  text-decoration: none;
  border-bottom: 1px solid #4a423a;
  border-radius: 2px;
  /* A wrapped link keeps its background on every line. */
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
@media (prefers-reduced-motion: no-preference) {
  .card__name a, .dtable .t-name a {
    transition: background-color 200ms ease, color 200ms ease,
                box-shadow 200ms ease, border-color 200ms ease;
  }
}

.card__name a:hover, .card__name a:focus-visible,
.dtable .t-name a:hover, .dtable .t-name a:focus-visible {
  background: var(--amber);
  border-bottom-color: var(--amber);
  box-shadow: 0 0 0 3px var(--amber);
}

/* ── cards ────────────────────────────────────────────────────────────── */

.cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
@media (max-width: 1100px) { .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 720px)  { .cards { grid-template-columns: 1fr; } }

.card {
  display: flex; flex-direction: column; gap: 9px;
  padding: 15px 18px;
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-top: 3px solid var(--day, var(--day-1));
  border-radius: 4px;
}
@media (max-width: 720px) {
  .card { border-top: 1px solid var(--line); border-left: 3px solid var(--day, var(--day-1)); }

  /* Give the date room by taking it from the type size and the gap, not from
     the date's own line count. */
  .day__head {
    gap: 10px;
    padding-top: 20px;
    /* Tops, not baselines: the link wraps to two lines here, and baseline
       alignment would hang the date off the link's FIRST baseline, leaving
       the date sitting high above the block. */
    align-items: flex-start;
  }
  /* line-height 1 removes the half-leading above the glyphs, so the date's
     box top is its cap top and squares with the link's first line. The 2px
     is the link's own half-leading at 11px/1.3. */
  .day__name { font-size: 25px; line-height: 1; padding-top: 2px; }
  .day__today { font-size: 12px; padding: 2px 7px; }
  .day__sub { font-size: 11px; }

  /* No table view on a phone: six columns either crush or scroll sideways,
     and both are worse than the cards. The JS forces cards at this width, so
     hiding the toggle leaves no way to reach a view that does not work. */
  .filterbar__view { display: none; }
}

.card__top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.card__name { font-size: 21px; font-weight: 600; line-height: 1.22; margin: 0; }
/* Film titles inside an event name. Bitter's italic is loaded; this is only
   here so the emphasis is unmistakably slanted at small sizes. */
.card__name em, .t-name em { font-style: italic; }

/* ── sold out ─────────────────────────────────────────────────────────── */

/* Struck through, not hidden or dimmed: there may be a waiting list, and the
   venue, time and description are still worth reading. The strike says
   "you cannot buy a ticket", not "ignore this".
   On the title only — striking the whole card would make the details that
   are still useful look cancelled too. */
.is-soldout .card__name a,
.is-soldout .card__name,
.is-soldout .t-name a {
  text-decoration: line-through;
  text-decoration-thickness: 2px;
  text-decoration-color: var(--accent);
}
.is-soldout .card__name a:hover,
.is-soldout .t-name a:hover { text-decoration-color: #14120f; }

/* The price cell reads "Sold out", so give it the weight of a status. */
.is-soldout .card__price,
.is-soldout .t-money {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}
.card__name a { color: #fff; }
.card__name a:hover, .card__name a:focus-visible { color: #14120f; }

.card__time {
  flex: none; font-family: var(--cond); font-size: 18px; font-weight: 600;
  color: var(--amber); white-space: nowrap;
}
.card__time--tba { font-size: 15px; color: var(--text-4); }

.card__venue { font-size: 13px; color: var(--text-2); }
.card__desc  { font-size: 13px; color: var(--text-3); line-height: 1.5; font-style: italic; }

.card__meta { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; margin-top: auto; padding-top: 4px; }
.card__price { font-size: 12px; font-weight: 600; color: var(--money); }
/* Bold: these sit at 11-13px on a mid-tone fill, where regular weight is the
   first thing to go. */
.badge {
  font-family: var(--serif); font-size: 11px; font-weight: 600;
  color: var(--text); background: var(--line);
  padding: 3px 8px; border-radius: 2px;
  border: 0; line-height: 1.4;
}

/* Tags are buttons that filter to themselves — see tagButton() in the JS.
   Each carries its own --tag-bg, one of the six day colours, assigned by the
   tag's position in the dropdown so a tag keeps one colour throughout. The
   colour arrives as a custom property rather than an inline background, so
   the hover and pressed rules below can still win. */
.badge--tag {
  color: #14120f;
  background: var(--tag-bg, var(--tag));
  cursor: pointer;
}
.badge--tag:hover,
.badge--tag:focus-visible { background: var(--amber); }
.badge--tag[aria-pressed="true"] {
  background: var(--amber);
  box-shadow: 0 0 0 2px var(--bg), 0 0 0 3px var(--amber);
}
@media (prefers-reduced-motion: no-preference) {
  .badge--tag { transition: background-color 200ms ease; }
}

/* ── table ────────────────────────────────────────────────────────────── */

/* No top margin: the black header sits flush against the filter bar. */
.tablewrap { margin-top: 0; }
.tablewrap + .tablewrap { margin-top: 0; }

/* Transparent on purpose: the panel behind it is --bg (#14120f) carrying the
   hatch, and the table reads as laid over that rather than masking it. The
   striped rows below are the opaque ones. */
.dtable { width: 100%; border-collapse: collapse; font-size: 15px; background-color: transparent; }
.dtable th {
  font-family: var(--cond); font-size: 16px; font-weight: 600; letter-spacing: 1.3px;
  text-transform: uppercase; text-align: left; color: #fff; background: #000;
  padding: 9px 12px; position: sticky; top: var(--sticky-top); z-index: 10;
}
.dtable td { padding: 10px 12px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
/* Alternating rows: one opaque and dark, one lighter and translucent at 0.85
   so the hatch still reads through it. 0.85 rather than 0.5 — at half opacity
   a description sitting on a hatch line measures 2.6:1, which is why they
   were competing with the texture.
   The lighter alt row also needed --text-3 brightened from #9a9086 to
   #b0a598: on a hatch line inside this row the old grey fell to 3.9:1. */
.dtable tbody tr:nth-child(odd) td { background: rgb(var(--row-stripe)); }
.dtable tbody tr:nth-child(even) td { background: rgba(var(--row-alt), 0.85); }
.dtable .t-name a { color: var(--text); }
.dtable .t-name a:hover, .dtable .t-name a:focus-visible { color: #14120f; }

.dtable__day td {
  background: var(--day, var(--day-1)) !important;
  padding: 7px 12px;
}
.dtable__day-name {
  font-family: var(--cond); font-size: 24px; font-weight: 700;
  letter-spacing: 0.6px; text-transform: uppercase; color: #14120f;
}
.dtable__day-today {
  font-family: var(--cond); font-size: 14px; font-weight: 700; letter-spacing: 1.3px;
  color: var(--day, var(--day-1)); background: #14120f;
  padding: 2px 8px; border-radius: 2px; margin-left: 10px;
}
/* Hidden for now, not removed: a per-day count is worth showing again once
   the guide is full enough for the number to mean something. */
.dtable__day-count { display: none; float: right; font-size: 13px; color: #4a3a30; padding-top: 6px; }

/* Sits where the count was, in the same colour as the date beside it.
   No underline; the affordance arrives on hover and focus. */
.dtable__day-sub {
  float: right;
  font-family: var(--serif);
  font-size: 13px;
  color: #14120f;
  padding-top: 6px;
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
/* colour must be stated: without it the global `a:hover { color: #ffc46b }`
   takes over and the link goes pale amber on the pastel fill. */
.dtable__day-sub:hover,
.dtable__day-sub:focus-visible {
  color: #000;
  border-bottom-color: #000;
}

/* .badge is shared with the cards, so the table's copy is adjusted here only.
   The bottom margin keeps stacked tags from clumping when a cell wraps. */
.dtable .badge { font-size: 13px; margin-bottom: 2px; padding-bottom: 5px; }

.t-time  { color: var(--amber); white-space: nowrap; }
.t-money { color: var(--money); white-space: nowrap; }
.t-none  { color: var(--text-4); }
.t-name  { font-size: 19px; line-height: 1.25; }
.t-desc  { color: var(--text-3); font-size: 14px; line-height: 1.45; font-style: italic; margin-top: 2px; }

/* ── footer ───────────────────────────────────────────────────────────── */

.sitefoot {
  padding: 26px var(--gutter) 40px;
  background: #e69138; color: #14120f;
}
/* Full width: the longest line is ~132 characters, about 845px at this size,
   so each sits on one row on any desktop and wraps naturally below that.
   A measure cap would be better for long-form reading, but these are four
   short standalone lines, not a paragraph to read down. */
.sitefoot p { margin: 0 0 6px; font-size: 13px; }
.sitefoot__cta {
  display: inline-block; margin-bottom: 10px;
  font-size: 17px; color: #14120f; font-weight: 600;
}
.sitefoot__cta:hover { color: #000; }
.sitefoot__meta { margin-top: 14px; font-size: 12px; }
.sitefoot__meta a { color: #14120f; }
.sitefoot__meta a:hover { color: #000; }

/* ── light mode ───────────────────────────────────────────────────────── */
