/* Design tokens and the display face – the one global stylesheet every other file builds on.
 * Imported first by src/styles/index.css; design/logos.html gets the same rules via dist/style.css. */
/* ---------- Design tokens (design/ui-revamp.md §2, «Soundsystem Swiss») ----------
 * Saturated --red/--gold/--green are fills and rules; words always use the *-text variant.
 * Red = time pressure, gold = selection and attention, green = commitment and proximity.
 */
:root {
  color-scheme: dark;
  /* surfaces */
  --bg: #14110e;
  --surface: #221c17;
  --surface-2: #2a2219;
  --line: #766759; /* spec lists #4d4036 as 3.2:1, measured 1.9:1; this is 3.5:1 on --bg, 3.1:1 on --surface */
  --line-soft: #2e2822;
  /* text */
  --text: #f3ead8;
  --muted: #bcae98;
  --faint: #7d7060;
  /* brand fills (plates, rules, stripes, chip backgrounds) */
  --red: #d8342f;
  --gold: #f2b632;
  --green: #1e8f4e;
  /* brand text variants (all >= 4.5:1 on --bg / --surface) */
  --red-text: #ff7a6e;
  --gold-text: #f2b632;
  --green-text: #4fd08a;
  --on-gold: #14110e;
  --on-red: #fffaf5; /* 4.6:1 on --red (spec's #fff5ec measured 4.4:1) */
  --on-green: #041008; /* 4.7:1 on --green (spec's #0f2a1a measured 3.7:1) */
  /* tints (14 % of the fill over --bg) */
  --red-tint: rgba(216, 52, 47, 0.14);
  --gold-tint: rgba(242, 182, 50, 0.14);
  --green-tint: rgba(30, 143, 78, 0.14);
  /* atmosphere */
  --sun: radial-gradient(60vw 40vh at 0% 0%, rgba(242, 182, 50, 0.1), transparent 70%);
  --grain-opacity: 0.035;

  /* typography (16 px base; nothing in the agenda below --fs-1) */
  --font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-display: "JahDisplay", "JahDisplay Fallback", "JahDisplay Fallback Condensed",
    "JahDisplay Fallback Sans", Impact, "Arial Narrow Bold", "Helvetica Neue", Arial, sans-serif;
  --fs-0: 0.78rem;
  --fs-1: 0.8125rem;
  --fs-2: 0.875rem;
  --fs-3: 0.9375rem;
  --fs-4: 1rem;
  --fs-5: 1.125rem;
  --fs-d1: 1.5rem;
  --fs-d2: 2.1rem;
  --fs-d3: clamp(2.4rem, 8vw, 3.6rem);
  --lh-tight: 1.1;
  --lh-body: 1.4;
  --track-caps: 0.1em;

  /* spacing, layout, radii, shadows */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --wrap: 760px;
  --gutter: 16px;
  --date-w: 3rem; /* 48 px – in rem, so the HEUTE / MORGEN pill still fits the column at 200 % text size */
  --side-w: 44px;
  --tap: 44px;
  --bar-top: 48px;
  --bar-chips: 36px;
  --safe-top: env(safe-area-inset-top, 0px); /* status-bar inset of a standalone app: the bar covers it */
  --bar-h: calc(
    var(--bar-top) + var(--bar-chips) + var(--safe-top)
  ); /* 84 px (+ inset), frozen: the bar never changes height */
  --r-1: 4px;
  --r-2: 8px;
  --r-3: 12px;
  --r-pill: 999px;
  --sh-1: 0 1px 2px rgba(0, 0, 0, 0.45);
  --sh-2: 0 8px 24px rgba(0, 0, 0, 0.45);
  --sh-bar: 0 4px 12px rgba(0, 0, 0, 0.35);
  --ring: 0 0 0 3px var(--gold), 0 0 0 6px var(--bg);

  /* motion: one reggae bar at 75 bpm = 800 ms, every duration is a subdivision */
  --t-1: 100ms;
  --t-2: 200ms;
  --t-3: 400ms;
  --t-4: 800ms;
  --ease-tape: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-skank: cubic-bezier(0.2, 0.9, 0.3, 1.1);
  --stagger: 24ms;

  font-family: var(--font-body);
}
@media (min-width: 768px) {
  :root {
    --bar-top: 56px;
    --bar-chips: 40px; /* 96 px bar on desktop */
    --date-w: 4rem;
  }
}
@media (prefers-reduced-motion: reduce) {
  :root {
    --t-1: 0ms;
    --t-2: 0ms;
    --t-3: 0ms;
    --t-4: 0ms;
  }
}

/* Display face: Anton, self-hosted Latin subset (img/fonts, ~9 KB, OFL). Weight range 400–900 so no
 * browser ever synthesises a bold; the size-adjusted local fallback keeps the layout stable while it loads. */
@font-face {
  font-family: "JahDisplay";
  src: url("img/fonts/jahdisplay.woff2") format("woff2");
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0020-007E, U+00A0-00FF, U+2013-2014, U+2022, U+2026;
}
@font-face {
  font-family: "JahDisplay Fallback";
  src: local("Impact"), local("Arial Narrow Bold"), local("ArialNarrow-Bold");
  font-weight: 400 900;
  size-adjust: 95%;
  ascent-override: 124%;
  descent-override: 35%;
  line-gap-override: 0%;
}
/* Phones have neither Impact nor Arial Narrow, so the face above never matched there and the lockup
 * painted 39 % too wide in a light sans until the woff2 arrived. One face per platform, each with its
 * own size-adjust (measured on «JAHGENDA» against Anton: Helvetica Neue Condensed Bold 88 %, Helvetica /
 * Arial Bold 69 %, Roboto Bold taken as Arial's); the line-box overrides are shared. */
@font-face {
  font-family: "JahDisplay Fallback Condensed";
  src: local("HelveticaNeue-CondensedBold"), local("Helvetica Neue Condensed Bold");
  font-weight: 400 900;
  size-adjust: 88%;
  ascent-override: 124%;
  descent-override: 35%;
  line-gap-override: 0%;
}
@font-face {
  font-family: "JahDisplay Fallback Sans";
  src: local("Roboto Bold"), local("Roboto-Bold"), local("Arial Bold"), local("Arial-BoldMT"),
    local("Helvetica Neue Bold"), local("HelveticaNeue-Bold");
  font-weight: 400 900;
  size-adjust: 69%;
  ascent-override: 124%;
  descent-override: 35%;
  line-gap-override: 0%;
}

/* Global base: reset, page background and atmosphere, focus ring, stripes, column, skip links, links. */
* {
  box-sizing: border-box;
}
html {
  -webkit-text-size-adjust: 100%;
  background: var(--bg);
  /* Keeps focused/anchored content clear of the sticky filter bar. */
  scroll-padding-top: calc(var(--bar-h, 64px) + 40px);
}
/* The hidden attribute wins over every display rule below (a .where-radius { display: flex } once
   showed radius chips the script had hidden). */
[hidden] {
  display: none !important;
}
body {
  position: relative;
  isolation: isolate;
  margin: 0;
  background: var(--bg);
  color: var(--text);
  line-height: var(--lh-body);
  /* Safety net: a stray unbreakable string can never scroll the page sideways (clip, unlike hidden,
     creates no scroll container, so the sticky filter bar keeps working). */
  overflow-x: clip;
}
/* Atmosphere: a warm sun glow from the top left corner, plus a screen-print grain tile on sharp screens.
 * Both are fixed, behind the content (z-index -1 inside body's own stacking context), never animated. */
body::before,
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
body::before {
  background: var(--sun);
}
body::after {
  display: none;
  background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='g'><feTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='160' height='160' filter='url(%23g)'/></svg>")
    0 0 / 160px 160px repeat;
  opacity: var(--grain-opacity);
}
@media (min-resolution: 1.5dppx) {
  body::after {
    display: block;
  }
}

/* Tricolor rule: the three stripe colours as structure (month heads, section titles, stuck bar, cards). */
.rule-tri,
.month,
.section-title {
  border-bottom: 3px solid;
  border-image: linear-gradient(90deg, var(--red) 33.3%, var(--gold) 33.3% 66.6%, var(--green) 66.6%) 1;
}

/* One focus ring for every interactive element: gold double ring, visible on any surface. */
:focus-visible {
  outline: none;
  box-shadow: var(--ring);
}
.seg button:focus-visible {
  box-shadow: inset 0 0 0 2px var(--gold); /* the pill group clips the outer ring */
}

/* Tabular numerals wherever a date or time appears. */
.date,
.meta time,
.dist,
.cal-num,
.popup-shows time {
  font-variant-numeric: tabular-nums;
}

.stripe {
  display: flex;
  height: 6px;
}
/* Standalone app on a notched phone: the stripe sits below the status bar (box-sizing is border-box,
   so the inset has to be part of the height or the coloured spans collapse to nothing). The top stripe
   follows the skip links on index.html, so it is matched by class, not by :first-child. */
.stripe:not(.bottom) {
  padding-top: env(safe-area-inset-top, 0px);
  height: calc(6px + env(safe-area-inset-top, 0px));
  background: var(--bg);
}
.stripe.bottom {
  padding-bottom: env(safe-area-inset-bottom, 0px);
  height: calc(6px + env(safe-area-inset-bottom, 0px));
  background: var(--bg);
}
.stripe span {
  flex: 1;
}
.stripe span:nth-child(1) {
  background: var(--red);
}
.stripe span:nth-child(2) {
  background: var(--gold);
}
.stripe span:nth-child(3) {
  background: var(--green);
}

.wrap {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 var(--gutter);
  /* Installed and held sideways on a notched phone: the gutter grows past the sensor housing. */
  padding-left: max(16px, env(safe-area-inset-left, 0px));
  padding-right: max(16px, env(safe-area-inset-right, 0px));
}
/* Skip links: invisible until focused, then a gold pill at the top left. */
.skip {
  position: absolute;
  left: 12px;
  top: -60px;
  z-index: 30;
  padding: 8px 14px;
  background: var(--gold);
  color: var(--bg);
  font-weight: 700;
  border-radius: 999px;
  text-decoration: none;
}
.skip:focus-visible {
  top: 12px;
  outline: 2px solid var(--text);
  outline-offset: 2px;
}
/* Every link gets the same gold ring as the buttons (not the browser's blue one). */
a[href]:focus-visible,
.leaflet-container a:focus-visible,
#foot:focus-visible,
.show:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: 4px;
}
/* The skip link lands on the whole agenda: no ring around 14 000 px of list (its top edge peeked out
 * under the bar as a stray gold bar); the next Tab is the first row. */
#views:focus-visible {
  outline: none;
}
#q::placeholder {
  color: var(--muted); /* the browser grey was 3.8:1 */
  opacity: 1;
}

.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Tricolor stripe at the bottom too */
.stripe.bottom {
  margin-top: 0;
}

/* Header (src/components/Header.svelte): lockup, tagline, slogan, notice. */
.head {
  padding-top: var(--s-6);
  padding-bottom: var(--s-5);
}
/* Standalone app: the filter bar carries the status-bar inset as padding; give some of it back here. */
@media (display-mode: standalone) {
  .head {
    padding-bottom: max(0px, calc(24px - env(safe-area-inset-top, 0px)));
  }
}
/* Lockup: mark + JAHGENDA in the display face, «Jah» cream / «genda» gold, one unbreakable word.
 * Both words are .logo-word spans with gap 0, so no flex gap can split them; the mark sits on the type
 * baseline (an image's flex baseline is its bottom edge) at cap height × 1.15 ≈ 1em. */
.logo {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0;
  font-family: var(--font-display);
  /* Never wider than the column: mark (1em) + gap + JAHGENDA (≈3.9em) ≤ 100vw − 2 gutters, so a 200 %
   * text size does not push the one unbreakable word past the edge (sideways page scroll). */
  font-size: min(var(--fs-d3), calc((100vw - 42px) / 5));
  font-weight: 400;
  font-synthesis: none;
  letter-spacing: 0.01em;
  line-height: 1;
  text-transform: uppercase;
  color: var(--text);
}
.wordmark {
  display: inline-flex;
  gap: 0;
  white-space: nowrap;
}
.logo-word {
  display: inline-block;
}
.logo-word.genda {
  color: var(--gold-text);
}
.logo-mark {
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
  border-radius: 0.2em;
}
/* Tagline, one line: REGGAE · DUB · SKA — SCHWEIZ with three tiny tricolor dots as separators. */
.tag {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--s-1);
  margin: var(--s-2) 0 0;
  font-size: var(--fs-1);
  font-weight: 600;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--muted);
}
.tri-dots {
  display: inline-flex;
  gap: 3px;
  margin: 0 var(--s-1);
}
.tri-dots i {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--red);
}
.tri-dots i:nth-child(2) {
  background: var(--gold);
}
.tri-dots i:nth-child(3) {
  background: var(--green);
}
/* Slogan under the tagline: one of data/slogans.json per page load. */
.slogan {
  margin: var(--s-2) 0 0;
  font-size: var(--fs-3);
  color: var(--muted);
}
/* The header lockup is a link back to the start (owner request 2026-10-13): plain «./» resets filters and hash. */
.logo-link {
  display: inline-flex;
  align-items: center;
  gap: inherit;
  color: inherit;
  text-decoration: none;
}
.logo-link:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 4px;
  border-radius: 6px;
}

/* Filter bar (src/components/filters/): sticky bar, search, chips, results row, note pill, Wo / Wann
 * panels, view switch. */
/* Sticky filter bar: two rows of frozen height (--bar-top + --bar-chips = 84 px, 96 px from 768 px).
 * The height is set explicitly so neither the data nor the stuck state can ever change it. When stuck:
 * the bar shadow plus a 3 px tricolor rule drawn as an overlay along the bottom edge (no border, no shift). */
.filters {
  position: sticky;
  top: 0;
  z-index: 3;
  height: var(--bar-h);
  margin-bottom: 8px;
  /* The bar itself covers the status-bar inset of a standalone app, so nothing scrolls through the
   * clock area; the inset is part of --bar-h (see :root), so month heads and scroll offsets include it. */
  padding: var(--safe-top) 0 0;
  background:
    var(--sun) fixed no-repeat,
    var(--bg);
}
/* The rows bleed 8–10 px past the column (zebra plate, mark fader), so the bar's plate and shadow extend one
 * gutter beyond it; otherwise a marked row's fader shows beside the bar while scrolling under it. */
.filters::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0 calc(-1 * var(--gutter));
  background:
    var(--sun) fixed no-repeat,
    var(--bg);
  transition: box-shadow var(--t-2) var(--ease-tape);
}
.filters::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--red) 33.3%, var(--gold) 33.3% 66.6%, var(--green) 66.6%);
  opacity: 0;
  transition: opacity var(--t-2) var(--ease-tape);
  pointer-events: none;
}
.filters.stuck::before {
  box-shadow: var(--sh-bar);
}
.filters.stuck::after {
  opacity: 1;
}

.toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  height: var(--bar-top);
}
.toolbar input,
.icon-btn {
  font: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  height: var(--tap);
  min-width: 0;
}
.search {
  position: relative;
  flex: 1;
  min-width: 0;
}
.search-icon {
  position: absolute;
  left: 13px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted);
  pointer-events: none;
  transition: opacity var(--t-2) var(--ease-tape);
}
.search input {
  width: 100%;
  padding: 0 12px 0 40px;
  text-overflow: ellipsis; /* the placeholder on a narrow phone: "Band, Cl…" rather than cut mid-word */
}
.search input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  height: 14px;
  width: 14px;
  cursor: pointer;
  background: var(--muted);
  -webkit-mask: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M18.3 5.7 12 12l6.3 6.3-1.4 1.4L10.6 13.4 4.3 19.7 2.9 18.3 9.2 12 2.9 5.7l1.4-1.4 6.3 6.3 6.3-6.3z' fill='black'/></svg>")
    center / contain no-repeat;
}
/* Mini-brand: the cross mark takes the place of the magnifier while the bar is stuck (tap = back to top).
 * It is absolute inside the first row, so the bar never grows a third row. */
.mini-brand {
  position: absolute;
  left: 0;
  top: 50%;
  z-index: 1;
  display: none;
  width: var(--tap); /* 44 px hit area over the icon slot of the field; the mark itself is 22 px */
  height: var(--tap);
  place-items: center;
  transform: translateY(-50%);
  border-radius: var(--r-2);
  -webkit-tap-highlight-color: transparent;
}
.mini-brand img {
  display: block;
  width: 22px;
  height: 22px;
  border-radius: 4px;
}
.filters.stuck .mini-brand {
  display: grid;
}
.filters.stuck .search-icon {
  opacity: 0;
}
.icon-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: var(--tap);
  padding: 0;
  cursor: pointer;
  color: var(--muted);
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
@media (hover: hover) {
  .icon-btn:hover {
    color: var(--gold);
    border-color: var(--gold);
  }
}
.icon-btn[disabled],
.chip[disabled] {
  opacity: 0.5;
  cursor: progress;
}
.icon-btn[aria-pressed="true"] {
  color: var(--on-gold);
  background: var(--gold);
  border-color: var(--gold);
}

/* Chip row: one scrollable line of frozen height for the genre chips; arrows appear when there is more to
 * see. The row keeps its height before the data arrives. */
.chips-wrap {
  position: relative;
  height: var(--bar-chips);
  /* The chip row scrolls inside .chips; the wrap must not grow past its parent (min-width 0 in a flex/grid
   * parent). No overflow:hidden here – it would clip the chips' 44 px touch hit areas. */
  min-width: 0;
  max-width: 100%;
}
.chips {
  display: flex;
  align-items: flex-start;
  height: var(--bar-chips);
  padding: 1px 0 3px;
  cursor: grab;
  -webkit-user-select: none; /* Safari only honours the prefixed property */
  user-select: none;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-behavior: smooth;
  scroll-snap-type: x proximity;
  scroll-padding: 0 8px;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
@media (min-width: 768px) {
  .chips {
    padding: 4px 0 4px;
  }
}
.chips::-webkit-scrollbar {
  display: none;
}
.chips-wrap.can-left .chips {
  mask-image: linear-gradient(90deg, transparent, #000 40px);
}
.chips-wrap.can-right .chips {
  mask-image: linear-gradient(90deg, #000 calc(100% - 40px), transparent);
}
.chips-wrap.can-left.can-right .chips {
  mask-image: linear-gradient(90deg, transparent, #000 40px, #000 calc(100% - 40px), transparent);
}
.chip-group {
  display: flex;
  flex: 0 0 auto;
  gap: 6px;
}
/* A thin separator between chip groups. */
.chip-group:not(:empty) + .chip-group:not(:empty)::before {
  content: "";
  flex: 0 0 1px;
  align-self: center;
  height: 20px;
  margin: 0 6px;
  background: var(--line);
}
.chip-group:empty {
  display: none;
}
.scroll-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--text);
  font-size: 1.3rem;
  line-height: 1;
  padding: 0 0 2px;
  cursor: pointer;
}
.scroll-btn.left {
  left: 0;
}
.scroll-btn.right {
  right: 0;
}
@media (hover: hover) {
  .scroll-btn:hover {
    color: var(--gold);
    border-color: var(--gold);
  }
}
/* Chips (§3.3): 32 px high, visible 1 px edge, radius --r-1, muted; pressed = gold plate with dark text. */
.chip {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 32px;
  font: inherit;
  font-size: var(--fs-2);
  font-weight: 500;
  line-height: 1;
  color: var(--muted);
  background: none;
  border: 1px solid var(--line);
  border-radius: var(--r-1);
  padding: 0 11px;
  cursor: pointer;
  white-space: nowrap;
  scroll-snap-align: start;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition:
    transform var(--t-1) var(--ease-skank),
    color var(--t-2) var(--ease-tape),
    background-color var(--t-2) var(--ease-tape),
    border-color var(--t-2) var(--ease-tape);
}
.chip::after {
  /* 36 px hit area without changing the drawn box */
  content: "";
  position: absolute;
  inset: -2px;
}
.chip svg {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
}
@media (hover: hover) {
  .chip:hover {
    color: var(--text);
    border-color: var(--muted);
  }
}
.chip.is-empty {
  opacity: 0.45; /* 0 under the other filters: dimmed, still tappable (updateCounts) */
}
.chip:active {
  transform: scale(0.96);
}
.chip[aria-pressed="true"],
.chip.on {
  color: var(--on-gold);
  background: var(--gold);
  border-color: var(--gold);
  box-shadow: inset 0 0 0 1px var(--gold); /* reads as the 2 px border of the pressed state */
  font-weight: 600;
}
@media (forced-colors: active) {
  .chip[aria-pressed="true"],
  .chip.on {
    border: 3px solid;
  }
}
/* Focus: the chip row is a 36 px scroller that clips the outer double ring, so chips use an inset ring
 * instead, like .seg; on the gold plate it is the dark text colour. */
.chip:focus-visible {
  border-color: var(--gold);
  box-shadow: inset 0 0 0 2px var(--gold);
}
.chip[aria-pressed="true"]:focus-visible {
  box-shadow: inset 0 0 0 2px var(--on-gold);
}

.geo-status {
  margin: 0 0 8px;
  color: var(--muted);
  font-size: 0.9rem;
}
.geo-status a {
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px dotted var(--gold);
}
@media (hover: hover) {
  .geo-status a:hover {
    color: var(--gold);
  }
}
.geo-status:empty {
  display: none;
}
.geo-pin {
  vertical-align: -2px;
  margin-right: 2px;
  color: var(--red-text);
}

/* Results row: note pill on the left, filter buttons, view switch on the right. */
.results-row {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  margin: var(--s-3) 0 0;
  color: var(--muted);
  font-size: var(--fs-2);
}
/* Active filters (§3.4): removable gold pills on their own line under pill + view switch, one per
 * dimension («dub ✕ · Bern ✕ · Wochenende ✕»); «Alles zurücksetzen» only from two on. Without a filter
 * the line is folded away like .sr (not display:none): it stays the focus fallback after a re-render. */
.results {
  order: 3;
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 8px;
  align-items: center;
  margin: 0;
}
.results.empty {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}
.results:focus-visible {
  box-shadow: none;
  outline: 0;
}
.fpill {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 28px;
  padding: 0 8px 0 10px;
  font: inherit;
  font-size: var(--fs-2);
  font-weight: 600;
  line-height: 1;
  color: var(--on-gold);
  background: var(--gold);
  border: 0;
  border-radius: var(--r-1);
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-1) var(--ease-skank);
}
.fpill::after {
  /* 44 px hit area on touch without changing the drawn box */
  content: "";
  position: absolute;
  inset: -8px -2px;
}
.fpill,
.results .clear {
  max-width: 100%;
  overflow-wrap: anywhere; /* a pasted search term never pushes the line past the viewport */
}
.results .clear {
  position: relative;
  min-height: 28px;
}
.results .clear::after {
  content: "";
  position: absolute;
  inset: -8px -4px;
}
.fpill .x {
  font-size: 11px;
  opacity: 0.75;
}
.fpill:active {
  transform: scale(0.96);
}
@media (hover: hover) {
  .fpill:hover {
    filter: brightness(1.08);
  }
  .fpill:hover .x {
    opacity: 1;
  }
}
@media (forced-colors: active) {
  .fpill {
    border: 2px solid;
  }
}
.results .clear {
  color: var(--muted);
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}
.results-row .results-count {
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.results-row .results-count:empty {
  display: none;
}
.results .linkish,
.results-row .share-filters {
  color: var(--muted);
}
@media (hover: hover) {
  .results .linkish:hover,
  .results-row .share-filters:hover {
    color: var(--gold-text);
  }
}
.results-row .seg {
  margin-left: auto;
}
/* Phones: «Link zu dieser Auswahl» sits at the end of the pills line under the switch; the break before
 * that line is forced only while there are pills (or the link), so an unfiltered row stays one line high.
 * Desktop has no break – its row is nowrap (below), and a 100 % flex-basis item in a nowrap row ate the
 * whole line and squeezed the pills into a vertical column (CI screenshot, 2026-10-13). */
@media (max-width: 767.98px) {
  .results-row:has(.results:not(.empty), .share-filters:not([hidden]))::after {
    content: "";
    order: 2;
    flex-basis: 100%;
    height: 0;
    margin-top: -6px;
  }
}
.results-row .results {
  flex: 1 1 auto;
}
.results-row .share-filters {
  order: 4;
  position: relative;
  font-size: var(--fs-1);
  min-height: 24px;
  /* The same dotted rule as «Alles zurücksetzen» right above it: muted text without it read as a caption. */
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}
.results-row .share-filters::after {
  content: ""; /* 44 px hit area on touch, like the pills */
  position: absolute;
  inset: -10px -4px;
}
/* Note pill: the «collected by AI agents» notice as a one-line disclosure. The body opens as a card under
 * the row (grid-template-rows 0fr → 1fr over --t-3), so nothing around it moves. */
.note-pill {
  position: static;
}
.note-pill summary {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 32px;
  padding: 0 8px 0 7px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  color: var(--muted);
  font-size: var(--fs-2);
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  list-style: none;
  -webkit-user-select: none; /* Safari only honours the prefixed property */
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.note-pill summary::-webkit-details-marker {
  display: none;
}
.note-pill summary::after {
  /* 44 px hit area on touch without changing the drawn pill */
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -6px;
  bottom: -6px;
}
.note-pill[open] summary {
  color: var(--text);
  border-color: var(--muted);
}
@media (hover: hover) {
  .note-pill summary:hover {
    color: var(--text);
    border-color: var(--muted);
  }
}
.note-pill summary svg:first-child {
  color: var(--gold-text);
}
.note-pill .chev {
  transition: transform var(--t-2) var(--ease-tape);
}
.note-pill[open] .chev {
  transform: rotate(180deg);
}
.note-short {
  display: none;
}
.note-body {
  position: absolute;
  left: 0;
  top: calc(100% + 6px);
  z-index: 4;
  width: min(100%, 480px);
  display: grid;
  grid-template-rows: 0fr;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  box-shadow: var(--sh-2);
  transition: grid-template-rows var(--t-3) var(--ease-tape);
}
.note-pill.is-open .note-body {
  grid-template-rows: 1fr;
}
.note-body p {
  min-height: 0;
  overflow: hidden;
  margin: 0;
  padding: 0 14px;
  color: var(--text);
  line-height: var(--lh-body);
  transition: padding var(--t-3) var(--ease-tape);
}
.note-pill.is-open .note-body p {
  padding: 12px 14px;
}
.note-body a {
  color: var(--green-text);
}
@media (hover: hover) {
  .note-body a:hover {
    color: var(--gold-text);
  }
}
.linkish {
  font: inherit;
  color: var(--green-text);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-decoration: none; /* no underlines anywhere; the colour turns gold on hover */
}
@media (hover: hover) {
  .linkish:hover {
    color: var(--gold);
  }
}

@media (max-height: 500px) {
  /* Unstuck, but still the containing block of the Wo/Wann dropdowns (position: absolute; top: 100%) and
   * of its own ::before plate – `static` sent the panels to the end of the page and let the plate's
   * negative inset widen the document by one gutter (16 px sideways scroll in landscape). */
  .filters {
    position: relative;
  }
}

/* Wann: quick picks and month chips (multi-select). */
/* Wann button: the active choice sits inside the button as a label (like the Wo button). */
.icon-btn.when[aria-pressed="true"] {
  width: auto;
  flex-basis: auto;
  padding: 0 10px;
  gap: 6px;
  display: inline-flex;
  color: var(--bg);
  background: var(--gold);
  border-color: var(--gold);
}
.icon-btn.when b {
  font-size: 0.78rem;
  font-weight: 700;
  white-space: nowrap;
}
/* Month histogram: one clickable bar per month (count on top, label below); selected = gold, others dim. */
.when-months {
  --track: 48px; /* the room a 100 % bar gets (count above, label below) */
  display: flex;
  align-items: stretch;
  gap: 4px;
  height: 102px;
  margin-top: 12px;
  overflow-x: auto;
  scrollbar-width: thin;
  scroll-snap-type: x proximity;
}
/* More months than fit (narrow phones): a fade on the right says so – scrollbars do not show on touch. */
.when-months.can-scroll {
  mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
}
.when-months .bar {
  position: relative;
  flex: 1 1 0;
  min-width: 40px;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: stretch;
  gap: 4px;
  padding: 0 1px;
  font: inherit;
  color: var(--muted);
  background: none;
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}
.when-months .bar b {
  font-size: 0.72rem;
  font-weight: 700;
  text-align: center;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.when-months .bar i {
  display: block;
  /* --h is 0…1 (share of the busiest month). A percentage would resolve against the whole button and
     flex-shrink would then clamp every tall bar to the same height. */
  flex: 0 0 auto;
  height: calc(var(--track) * var(--h, 0));
  min-height: 3px;
  background: var(--gold);
  border-radius: 3px 3px 0 0;
  transition: opacity 0.15s;
}
.when-months .bar span {
  font-size: 0.74rem;
  font-weight: 700;
  text-align: center;
  white-space: nowrap;
  padding: 4px 0 6px; /* room below for the «today» dot */
  border-top: 1px solid var(--line);
}
.when-months .bar.today span {
  color: var(--text);
}
.when-months .bar.empty i {
  background: var(--line);
}
.when-months .bar.past i {
  opacity: 0.45;
}
.when-months .bar.dim i {
  opacity: 0.3;
}
.when-months .bar.dim b {
  color: var(--muted);
}
.when-months .bar[aria-pressed="true"] span {
  color: var(--gold);
  border-top-color: var(--gold);
}
.when-months .bar[aria-pressed="true"] i {
  opacity: 1;
}
@media (hover: hover) {
  .when-months .bar:hover span,
  .when-months .bar:hover b {
    color: var(--gold);
  }
}
.when-months .bar:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 1px;
}
.when-months .bar.today::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 6px;
  height: 6px;
  margin-left: -3px;
  border-radius: 50%;
  background: var(--red);
}
.when-quick {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.when-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 14px;
  margin: 10px 0 0;
  font-size: 0.85rem;
  color: var(--muted);
}
.when-foot #when-count {
  color: var(--text);
  font-weight: 600;
}
.when-past {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  cursor: pointer;
}
.when-past input {
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--gold);
}
@media (max-width: 480px) {
  .when-past {
    margin-left: 0;
  }
}

/* Count inside a genre chip, very light */
.chip small {
  margin-left: 5px;
  min-width: 2ch; /* counts change with the other filters; the chip never moves */
  text-align: left;
  font-size: 0.7em;
  font-weight: 600;
  opacity: 0.75; /* 0.55 on 9 px read as dust (3.4:1) */
  font-variant-numeric: tabular-nums;
}
.chip[aria-pressed="true"] small {
  opacity: 0.75;
}

/* View switch icons (Lucide, MIT) */
.seg button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.seg .ico {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
}
@media (max-width: 420px) {
  .seg button span {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
  }
  .seg .ico {
    width: 18px;
    height: 18px;
  }
}

/* Filter panels: dropdowns hanging from the sticky bar (so they open wherever you are on the page),
   one at a time, with a × in the corner. */
.filters .panel {
  position: absolute;
  left: 8px; /* the bar is 8 px wider than .wrap on both sides */
  right: 8px;
  top: 100%;
  z-index: 4;
  /* The panel hangs from the bar's real bottom edge: --panel-top is written by the filter store when a panel opens
   * and while the page scrolls under it (the bar sits ~160 px lower while the header is still visible,
   * so a value fixed to --bar-h would cut the lowest chips off below the fold). */
  max-height: calc(100vh - var(--panel-top, var(--bar-h, 120px)) - 24px);
  max-height: calc(100dvh - var(--panel-top, var(--bar-h, 120px)) - 24px); /* iOS: the visible viewport */
  overflow: auto;
  padding: 10px 14px 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-top: 0;
  border-radius: 0 0 12px 12px;
  box-shadow: 0 18px 32px -12px rgba(0, 0, 0, 0.75);
}
.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}
.panel-head strong {
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
}
.panel-close {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  margin-right: -6px;
  padding: 0;
  font: inherit;
  font-size: 1.35rem;
  line-height: 1;
  color: var(--muted);
  background: none;
  border: 1px solid var(--line);
  border-radius: 50%;
  cursor: pointer;
}
@media (hover: hover) {
  .panel-close:hover {
    color: var(--text);
    border-color: var(--gold);
  }
}
.panel-close:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 1px;
}
.icon-btn[aria-expanded="true"] {
  color: var(--gold);
  border-color: var(--gold);
}
/* Wo panel: own position, radius, canton chips */
.where-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.where-locate {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--gold);
  border-color: var(--gold);
}
.where-locate.active {
  color: var(--bg);
  background: var(--gold);
}
/* The four radius chips wrap inside the panel: in one line «Alle Distanzen» ran past the panel's right
 * edge on every phone up to 390 px (only «Alle» was left at 320 px). */
.where-radius {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-left: 4px;
}
.where-panel .geo-status {
  margin: 10px 0 4px;
}
.canton-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}
/* The chips are grouped like the spec's <optgroup>s: a small tracked label starts each group. */
.canton-group {
  display: contents;
}
.group-label {
  flex: 1 1 100%;
  margin-top: 6px;
  font-size: var(--fs-1);
  font-weight: 600;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--muted);
}
.canton-group:first-of-type .group-label {
  margin-top: 2px;
}
.canton-chips .chip.abroad {
  border-style: dashed;
}
.canton-chips .chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.canton-chips .chip img {
  width: 14px;
  height: 16px;
  object-fit: contain;
}
.icon-btn.where:has(b:not([hidden])) {
  width: auto;
  flex-basis: auto;
  padding: 0 10px;
  gap: 6px;
  display: inline-flex;
}
.icon-btn.where.located {
  color: var(--gold);
}
.icon-btn.where[aria-pressed="true"] {
  color: var(--bg);
  background: var(--gold);
  border-color: var(--gold);
}
.icon-btn.where b {
  font-size: 0.78rem;
  font-weight: 700;
  white-space: nowrap;
}
/* Labels inside the Wo / Wann buttons never push the toolbar past the viewport: capped with an ellipsis
   on tablets and desktop, dropped on phones (the gold button says "something is set", the panel and the
   result line say what; the search field keeps at least 120 px). Must follow the Wo / Wann rules above. */
.icon-btn.when[aria-pressed="true"],
.icon-btn.where:has(b:not([hidden])) {
  max-width: 150px;
}
.icon-btn.when b,
.icon-btn.where b {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
@media (max-width: 480px) {
  .icon-btn.when[aria-pressed="true"],
  .icon-btn.where:has(b:not([hidden])) {
    width: 44px;
    flex-basis: 44px;
    max-width: 44px;
    padding: 0;
  }
  .icon-btn.when b,
  .icon-btn.where b {
    display: none;
  }
  .search {
    flex: 1 1 120px;
    min-width: 120px;
  }
}
/* Phones / touch: the past-shows link gets a thumb-sized hit area without moving the line. */
@media (max-width: 600px), (hover: none) {
  .results .linkish {
    padding: 8px 0;
    margin: -8px 0;
  }
}

/* Touch screens: no hover state can stick after a tap (see the @media (hover: hover) guards above);
   a press gives short feedback instead. */
@media (hover: none) {
  .chip:active,
  .icon-btn:active,
  .cal-nav:active,
  .scroll-btn:active,
  .panel-close:active {
    border-color: var(--gold);
  }
}

/* Phones (≤ 420): the note pill shows its short label. */
@media (max-width: 420px) {
  .note-short {
    display: inline;
  }
  .note-long {
    display: none;
  }
}
/* Phones (≤ 599): the results row is one line – ⓘ · «98 Shows» · Liste|Kalender|Karte (design §3.4
 * draws one line at 375; the view switch stood alone on a second line with an empty left half, 46 px
 * of fold lost above the list). The note pill is its icon; the words stay its accessible name. */
@media (max-width: 599px) {
  .note-pill summary {
    padding: 0 8px;
  }
  .note-short,
  .note-long {
    display: inline;
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
  }
  .note-pill .chev {
    display: none;
  }
}
@media (max-width: 340px) {
  .seg button {
    padding: 0 12px; /* 288 px: ⓘ + «100 Shows» + the switch still fit on the line */
  }
}

/* View switch: one pill group, flat segments, gold active segment */
.seg {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
}
.seg button {
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
  background: none;
  border: 0;
  border-radius: 999px;
  padding: 6px 12px;
  cursor: pointer;
  line-height: 1;
}
@media (hover: hover) {
  .seg button:hover {
    color: var(--text);
  }
}
.seg button[aria-pressed="true"] {
  color: var(--on-gold);
  background: var(--gold);
  font-weight: 700;
}
/* Phones / touch: the segments are 36 px high. */
@media (max-width: 600px), (hover: none) {
  .seg button {
    min-height: 36px;
    padding: 0 14px;
  }
}

/* The word next to the pin and the calendar icon: from tablet width up, until a badge takes its place. */
.icon-word {
  display: none;
  font-size: 0.78rem;
  font-weight: 700;
}
@media (min-width: 601px) {
  .icon-btn.where:not(:has(b:not([hidden]))),
  .icon-btn.when:not(:has(b:not([hidden]))) {
    flex-basis: auto;
    grid-auto-flow: column;
    gap: 6px;
    padding: 0 12px;
  }
  .icon-btn:not(:has(b:not([hidden]))) .icon-word {
    display: inline;
  }
}
/* Touch: chips and the small text buttons reach a finger-sized height – 36 px drawn, 44 px hit
 * (design/ui-revamp.md: every target ≥ 44 px on touch). The chips keep their drawn box and their row
 * pitch grows to 44, so neighbouring hit areas touch but never overlap. */
@media (hover: none) {
  .chip {
    min-height: 36px;
  }
  .chip::after {
    inset: -4px -2px;
  }
  /* The genre row is a 36 px scroller that clips the chips' extension: the scroller grows to 44 px
   * (2 px into the toolbar's gap above, 6 px past the bar's bottom edge – both transparent) while the
   * bar and the drawn chips stay where they are. */
  .chips {
    height: 44px;
    margin: -2px 0 -6px;
    padding: 3px 0 5px;
  }
  .chips .chip::after {
    inset: -4px -2px -6px; /* a pixel more than the scroller shows: its clip edge is the 44 px line */
  }
  /* Panels: 44 px row pitch for the canton chips and the quick picks (the chips were 6 px apart). */
  .canton-chips,
  .when-quick,
  .where-row,
  .where-radius {
    gap: 8px;
  }
  .panel-close {
    position: relative;
  }
  .panel-close::after,
  .seg button::after {
    content: "";
    position: absolute;
    inset: -6px;
  }
  .seg button {
    position: relative;
  }
  .seg button::after {
    inset: -4px 0;
  }
  /* The chip row's scroll arrows (28 px, kept on touch: a swipe is not the only way to the end of the
   * row) get the chips' 44 px hit band – vertically the scroller's own, 8 px sideways. */
  .scroll-btn::after {
    content: "";
    position: absolute;
    inset: -7px -9px -11px;
  }
  #when-reset {
    padding: 8px 0;
    margin: -8px 0;
  }
}

/* Owner feedback 2026-10-13 18:30: a tidy results row. The KI note is an icon-only pill on wide screens
 * (the sentence lives in the card and in «Über Jahgenda»), so count and view switch share one line. */
@media (min-width: 768px) {
  /* One line: ⓘ · count · pills · «Link zu dieser Auswahl» · view switch at the right edge. */
  .results-row {
    flex-wrap: nowrap;
  }
  .results-row .results {
    order: 1;
    flex: 0 1 auto;
  }
  .results-row .share-filters {
    order: 2;
    white-space: nowrap;
  }
  .results-row .seg {
    order: 5;
  }
  /* Hidden for the eye only, as on phones: the words stay the summary's accessible name (review #50 –
   * display:none left the disclosure without a name, axe summary-name). */
  .note-pill summary .note-short {
    display: none;
  }
  .note-pill summary .note-long {
    display: inline;
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
  .note-pill summary {
    padding: 0 7px;
  }
}

/* Status box (src/components/Status.svelte, filters/EmptyState.svelte): loading skeleton, empty state, error,
 * the link note above the list. */
/* Empty / loading / error state: a clear box where the list would be. */
/* One line above the list after a share link that leads nowhere any more. */
.link-note {
  margin: 10px 0 0;
  padding: 8px 12px;
  color: var(--muted);
  font-size: 0.9rem;
  border-left: 3px solid var(--gold);
  background: rgba(242, 182, 50, 0.08);
  border-radius: 0 8px 8px 0;
}
.status {
  margin: 20px 0 8px;
  padding: 36px 20px;
  text-align: center;
  color: var(--muted);
  border: 1px dashed var(--line);
  border-radius: 12px;
}
.status p {
  margin: 0;
}
.status .big {
  margin-bottom: 6px;
  font-family: var(--font-display);
  font-size: var(--fs-d1);
  font-weight: 400;
  font-synthesis: none;
  letter-spacing: 0.02em;
  line-height: var(--lh-tight);
  text-transform: uppercase;
  color: var(--text);
  text-wrap: balance;
}
.status .state-icon {
  margin-bottom: 12px;
  color: var(--faint);
}
.status .state-icon svg {
  width: 48px;
  height: 48px;
}
/* Short or very narrow screens (a phone held sideways, 320 px): the card drops its icon and some padding,
 * so the message and its button («Nochmals versuchen») need no scroll. */
@media (max-height: 600px), (max-width: 340px) {
  .status .state-icon {
    display: none;
  }
  .status {
    padding: 20px 16px;
  }
}
/* The offers of the empty state (§3.8): «Tessin entfernen → 4 Shows», the richest first; the yield in
 * green, 0 in muted. «Alles zurücksetzen» as a quiet link under them. */
.status .fixes {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 16px;
}
.status .chip.fix {
  flex: 0 1 auto; /* a long label wraps inside the box instead of overflowing it on a phone */
  max-width: 100%;
  height: auto;
  min-height: 36px;
  padding: 7px 12px;
  line-height: 1.25;
  color: var(--text);
  white-space: normal;
  text-align: left;
}
.status .fix b {
  font-weight: 600;
  color: var(--green-text);
  white-space: nowrap;
}
.status .fix.none b {
  color: var(--muted);
}
.status .linkish.reset {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap); /* 44 px target (§5) without moving the line: 14 px gap minus the extra height */
  margin-top: 2px;
  padding: 0 8px;
  color: var(--muted);
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}
.status button.primary {
  margin-top: 16px;
  font: inherit;
  font-weight: 700;
  color: var(--on-gold);
  background: var(--gold);
  border: 0;
  border-radius: 999px;
  padding: 8px 18px;
  cursor: pointer;
}
@media (hover: hover) {
  .status button.primary:hover {
    filter: brightness(1.08);
  }
}
/* Loading: six skeleton rows in the final row grid; only their opacity shimmers (1.2 s). The box keeps the
 * footer below the fold until the data is in, so the first paint never shifts (CLS 0.12 came from there). */
.status.loading {
  min-height: calc(100vh - var(--bar-h, 84px) - 160px);
}
.status.loading {
  margin: var(--s-4) 0 0;
  padding: 0;
  text-align: left;
  border: 0;
}
.skeleton .show {
  min-height: 64px;
  pointer-events: none;
}
.sk {
  display: block;
  background: var(--surface);
  border-radius: var(--r-1);
  animation: shimmer 1.2s ease-in-out infinite;
}
.sk-date {
  width: 40px;
  height: 46px;
  margin: 13px auto 0;
}
.sk-bar {
  width: 72%;
  height: 15px;
  margin: 4px 0 8px;
}
.sk-bar.short {
  width: 46%;
  height: 13px;
}
.sk-badge {
  width: 24px;
  height: 28px;
  margin-top: 2px;
}
.skeleton .day:nth-child(2) .sk-bar {
  width: 58%;
}
.skeleton .day:nth-child(4) .sk-bar {
  width: 84%;
}
.skeleton .day:nth-child(5) .sk-bar.short {
  width: 36%;
}
@keyframes shimmer {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.55;
  }
}

.status:empty {
  display: none;
}

/* The list (ShowList, DayGroup, Row, src/components/list/): month heads, day groups, rows, actions,
 * date column, meta line, coat of arms, acts, past-shows foot. */
.dist {
  color: var(--green-text);
  white-space: nowrap;
}
/* Placeholder for the distance while the position is unknown: only on hover, one click asks for it. The
 * wrapper carries the « · » separator, so nothing of it (not even the dot) is left on the line while hidden. */
.geo-hint {
  opacity: 0;
  transition: opacity var(--t-2) var(--ease-tape);
}
.show:hover .geo-hint,
.geo-hint:focus-within {
  opacity: 1;
}
.geo-ask {
  position: relative;
  display: inline-block;
  min-height: 24px; /* WCAG 2.5.8: a 24 px target, not an 18 px text line */
  padding: 2px 0;
  font: inherit;
  font-size: 0.9em;
  line-height: 1.4;
  color: var(--muted);
  background: none;
  border: 0;
  border-bottom: 1px dotted var(--muted);
  cursor: pointer;
}
/* Separator and button fade in together on hover (nothing is left dangling while hidden). */
.geo-hint {
  opacity: 0;
  transition: opacity var(--t-2) var(--ease-tape);
}
.show:hover .geo-hint,
.geo-hint:focus-within {
  opacity: 1;
}
@media (hover: hover) {
  .geo-ask:hover {
    color: var(--gold);
    border-bottom-color: var(--gold);
  }
}
@media (hover: none) {
  .geo-hint {
    opacity: 0.7;
  }
  /* Touch: the hit area grows upwards into the meta line (downwards the icon row is next). */
  .geo-ask::after {
    content: "";
    position: absolute;
    inset: -10px -6px -2px;
  }
}
/* SBB timetable: a small red badge next to the distance, revealed on hover (always on touch screens). */
.sbb {
  display: inline-grid;
  place-items: center;
  height: 16px;
  margin-left: 6px;
  padding: 0 5px;
  vertical-align: 1px;
  border-radius: 3px;
  font-size: 0.6rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  color: #fff;
  background: #eb0000;
  opacity: 0;
  transition: opacity 0.15s;
}
@media (hover: hover) {
  .sbb:hover {
    color: #fff;
  }
}
.show:hover .sbb,
.dist:hover .sbb,
.geo-status .sbb,
.sbb:focus-visible {
  opacity: 1;
}
@media (hover: none) {
  .sbb {
    opacity: 1;
  }
}

/* Past days (§5): muted text instead of opacity (never below 4.5:1), «vorbei» in the meta line. */
.day.past .date,
.show.past .title,
.show.past .acts,
.show.past .meta time,
.show.past .canton {
  color: var(--muted);
}
.show.past .canton img {
  opacity: 0.6;
}

/* Month headings stick right under the filter bar while scrolling through that month. */
#list .month {
  position: sticky;
  top: var(--bar-h, 64px);
  z-index: 2;
  background:
    var(--sun) fixed no-repeat,
    var(--bg);
  padding-top: var(--s-3);
  margin-top: var(--s-6);
}
#list .month::after {
  /* a 1 px gold hairline along the rule, drawn from the left once the head is stuck (§4) */
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -3px;
  height: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-2) var(--ease-tape);
}
#list .month.stuck::after {
  transform: scaleX(1);
}
#list .month::before {
  /* same bleed as the bar: rows scrolling under the stuck head must not peek out beside it */
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0 calc(-1 * var(--gutter)); /* padding box only: a negative-z child paints above the border */
  background:
    var(--sun) fixed no-repeat,
    var(--bg);
}
/* Short phones (most phones in portrait): the month head stays sticky but shrinks to one compact line –
 * the visitor always knows which month the rows belong to (#60, owner decision 2026-10-15), and the
 * chrome costs about 30 px instead of 60. */
@media (max-height: 700px) {
  #list .month {
    padding-top: var(--s-1);
    padding-bottom: 2px;
    margin-bottom: 0;
    font-size: var(--fs-4);
  }
  #list .month .year {
    font-size: var(--fs-0);
  }
}
#list section:first-child .month {
  margin-top: var(--s-4);
}

/* Month head: the loudest element in the list. OKTOBER in the display face, the year small and muted,
 * both sitting on the tricolor rule (see .rule-tri above). */
.month {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  margin: var(--s-6) 0 var(--s-1);
  padding-bottom: var(--s-1);
  font-family: var(--font-display);
  font-size: var(--fs-d1);
  font-weight: 400;
  font-synthesis: none;
  letter-spacing: 0.02em;
  line-height: var(--lh-tight);
  text-transform: uppercase;
  color: var(--gold-text);
}
.month .year {
  font-family: var(--font-body);
  font-size: var(--fs-1);
  font-weight: 600;
  letter-spacing: var(--track-caps);
  color: var(--muted);
}

/* Day groups (§3.5): one <section class="day"> per date, grid [date | rows]. The single <time> cell on
 * the left is shared by every show of that day; a --line-soft hairline separates rows of the same day,
 * nothing separates days (the next date cell does). Days alternate a faint zebra plate that bleeds 8 px
 * past the column. */
.shows {
  margin: 0;
  padding: 0;
}
.day {
  display: grid;
  grid-template-columns: var(--date-w) minmax(0, 1fr);
  column-gap: var(--s-3);
  align-items: start;
  margin: 0 -8px;
  padding: 0 8px;
  border-radius: 6px;
  scroll-margin-top: 8px; /* html has scroll-padding-top (bar + month heading); the two would add up */
}
.shows > .day:nth-of-type(even) {
  background: rgba(243, 234, 216, 0.025);
}
.day > .date {
  padding-top: 13px; /* the DOW caps sit level with the first title */
}
.day-shows {
  list-style: none;
  margin: 0;
  padding: 0;
  min-width: 0;
}
.day-shows .show + .show {
  border-top: 1px solid var(--line-soft);
}
/* A row (§3.6): grid [body | 44 px side column], the actions line under the body. Two pseudo-elements paint
 * across the whole row width, date column included: ::after is the hover / highlight plate, ::before the
 * 3 px fader at the far left (gold = Interessiert, green = Ich gehe). */
.show {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--side-w);
  gap: 0 var(--s-3);
  align-items: start;
  min-height: 64px;
  padding: 8px 0 2px;
  scroll-margin-top: 8px; /* html scroll-padding-top already clears the bar */
  -webkit-tap-highlight-color: transparent;
}
.show > .body {
  min-width: 0;
}
/* Side column: coat of arms + green abbreviation (a filter button) over the star, both 44 px wide. */
.side {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: var(--side-w);
  margin-top: -4px;
}
/* From 600 px the side column spans the stack and the actions line: the actions sit right under the meta,
 * left of the star, and a short row is as tall as the side column (≈ 96 px) instead of side column +
 * actions line. Explicit areas: a row-locked item would otherwise be auto-placed into column 1. */
@media (min-width: 600px) {
  .show > .body {
    grid-area: 1 / 1 / 2 / 2;
  }
  .side {
    grid-area: 1 / 2 / 3 / 3;
  }
  .foot-row {
    grid-area: 2 / 1 / 3 / 2;
  }
}
.show::before,
.show::after {
  content: "";
  position: absolute;
  z-index: -1;
  top: 0;
  bottom: 0;
  pointer-events: none;
}
.show::after {
  left: calc(-1 * (var(--date-w) + var(--s-3) + 8px));
  right: -8px;
  border-radius: 6px;
  transition: background var(--t-1) var(--ease-tape);
}
.show:hover::after {
  background: rgba(242, 182, 50, 0.07);
}
/* Fader (§4, gesture 1): the track slides in from the left over --t-2 when the row is marked. */
.show::before {
  left: calc(-1 * (var(--date-w) + var(--s-3) + 8px));
  width: 3px;
  border-radius: 2px;
  transform: scaleX(0);
  transform-origin: left;
  transition:
    transform var(--t-2) var(--ease-tape),
    background var(--t-2) var(--ease-tape);
}
.show.highlight::before {
  background: var(--gold);
  transform: none;
}
.show.highlight::after {
  animation: flash 2.5s ease-out;
}
@keyframes flash {
  from {
    background: rgba(242, 182, 50, 0.18);
  }
  to {
    background: transparent;
  }
}
.foot-row {
  grid-column: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 8px;
  margin-top: 2px;
}

/* «Wochenende» rule (§3.5): a 1 px red line with its label, once, before the first day of the coming
 * weekend. */
.rule-weekend {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: var(--s-3) 0 var(--s-1);
  font-size: var(--fs-1);
  font-weight: 700;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--red-text);
}
.rule-weekend::before,
.rule-weekend::after {
  content: "";
  height: 1px;
  background: var(--red);
}
.rule-weekend::before {
  flex: 0 0 var(--s-3);
}
.rule-weekend::after {
  flex: 1;
}

/* Past shows (§3.10): the last row under the list, full width, 44 px, muted. */
.list-foot {
  margin-top: var(--s-3);
}
.list-foot .linkish {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  width: 100%;
  min-height: var(--tap);
  padding: 0 var(--s-2);
  border-top: 1px solid var(--line-soft);
  border-radius: var(--r-1);
  color: var(--muted);
  font-size: var(--fs-2);
  text-decoration: none;
  text-align: left;
}
@media (hover: hover) {
  .list-foot .linkish:hover {
    color: var(--text);
  }
}
.list-foot .arrow {
  color: var(--faint);
}
/* Desktop with a mouse (owner decision 2026-10-13 18:20, #54): the list stays quiet – the actions line only
 * appears when the row is hovered or focused; the reserved line keeps the rows from jumping. Rows get more
 * air so the list reads lighter. */
@media (hover: hover) and (min-width: 768px) {
  .show {
    padding: 14px 0 6px;
  }
  .show .actions {
    opacity: 0;
    transition: opacity 0.15s;
  }
  .show:hover .actions,
  .show:focus-within .actions,
  .show.highlight .actions {
    opacity: 1;
  }
}
/* Actions line (§3.6): its own 44 px line under the meta. Icon + word from 600 px, icon-only 44 × 44
 * targets below (the aria-label keeps the name, «Infos ↗» / «Tickets ↗» keep their word). On desktop with a
 * mouse the line is 36 px and sits at the right of the body column. */
.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 2px;
  margin-left: -10px; /* the first icon lines up with the title */
}
.act {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: var(--tap);
  min-width: var(--tap);
  padding: 0 10px;
  font: inherit;
  font-size: var(--fs-2);
  line-height: 1;
  white-space: nowrap;
  color: var(--muted);
  background: none;
  border: 0;
  border-radius: var(--r-2);
  cursor: pointer;
  text-decoration: none;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition:
    transform var(--t-1) var(--ease-skank),
    color var(--t-2) var(--ease-tape),
    background var(--t-2) var(--ease-tape);
}
.act:active {
  transform: scale(0.96);
}
@media (hover: hover) {
  .act:hover {
    color: var(--gold-text);
    background: var(--gold-tint);
  }
}
.act svg {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
}
.act .ext-mark {
  margin-left: -3px;
}
/* Feedback word: takes the label's place for 1.2 s, fading in over --t-2. */
.act .fb {
  color: var(--green-text);
  font-weight: 600;
  animation: fb-in var(--t-2) var(--ease-tape);
}
.act.fb-on .lbl {
  display: none;
}
@keyframes fb-in {
  from {
    opacity: 0;
  }
}
/* From 600 px the line is right-aligned in the body column (the tags chip stays left). A line that is wider
 * than the column – the 1.2 s «Link in Adresszeile» on desktop – overflows at the start, i.e. leftwards
 * under the date cell, never under the star. */
@media (min-width: 600px) {
  .foot-row {
    justify-content: flex-end;
  }
  .actions {
    justify-content: flex-end;
    gap: 0 4px;
    margin-left: 0;
  }
}
/* Desktop with a mouse: the line is 36 px and never wraps. */
@media (hover: hover) and (min-width: 768px) {
  .act {
    min-height: 36px;
  }
  .actions {
    flex-wrap: nowrap;
    gap: 0 2px;
  }
}
/* Below 768 px (§3.6: labels from 768 px) the actions are icon-only 44 × 44 targets named by their
 * aria-label; the feedback word pops up as a plate above the icon, so the line never reflows. */
@media (max-width: 767px) {
  .show .actions .act {
    position: relative;
    width: var(--tap);
    height: var(--tap);
    padding: 0;
  }
  .show .actions .act .lbl,
  .show .actions .act .ext-mark {
    display: none;
  }
  .show .actions .act .fb {
    position: absolute;
    bottom: calc(100% - 4px);
    left: 0;
    z-index: 1;
    padding: 4px 8px;
    white-space: nowrap;
    color: var(--green-text);
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: var(--r-1);
    box-shadow: var(--sh-1);
  }
  .show .actions .act:nth-last-child(-n + 3) .fb {
    left: auto;
    right: 0;
  }
  /* The ticket icon stays green: the one call to action in the line. */
  .show .actions .act.tickets {
    color: var(--green-text);
  }
}
/* Phones: the six icons spread over the body and side columns so that they fit on one line. The line is
 * 38 px tall and the targets grow 3 px into the row paddings above and below (still 44 × 44), which keeps
 * a row with acts and two meta lines near 130 px. */
@media (max-width: 599px) {
  .show {
    padding-top: 6px;
  }
  .foot-row {
    grid-column: 1 / -1;
    /* 4 px (not −4): 8 px of dead space between the star's 44 px box in the side column and the «Infos»
     * link under it (a low tap on the star opened the organiser's page), and between the «Distanz & Zug»
     * text and the icon row. Costs ~11 px per row. */
    margin-top: 7px;
    min-height: 38px;
  }
  .actions {
    flex: 1;
    justify-content: space-between;
    flex-wrap: nowrap;
    gap: 0;
    margin-left: -13px; /* the icon (18 px, centred in 44 px) lines up with the title */
  }
  /* A tap on a row of a hidden view (the calendar's day list) must never widen the layout viewport. */
  .cal-day,
  .foot-row {
    overflow-x: clip;
  }
  .show .actions .act {
    margin: -3px 0;
  }
  /* The star tucks 6 px under the coat-of-arms button, so the side column stays within a short row. */
  .side .star {
    margin-top: -6px;
  }
}
/* The narrowest phones (320 px): six 44 px targets are 264 px, the line has 243 – the targets shrink to
 * 40 px so the last one («Infos») is not cut by the viewport edge. */
@media (max-width: 339px) {
  .show .actions .act {
    width: 40px;
  }
}
/* The star in the side column: 44 × 44, icon only (the word lives in the tooltip and for screen readers);
 * its feedback word pops up as a small plate at its left. */
.side .star {
  position: relative;
  width: var(--tap);
  height: var(--tap);
  min-height: 0;
  padding: 0;
}
.side .star svg {
  width: 20px;
  height: 20px;
}
.side .star .lbl {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}
.side .star .fb {
  position: absolute;
  right: calc(100% + 2px);
  top: 50%;
  transform: translateY(-50%);
  padding: 4px 8px;
  font-size: var(--fs-1);
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-1);
  box-shadow: var(--sh-1);
}
.date {
  text-align: center;
  line-height: 1;
}
.date .dow {
  display: block;
  font-size: var(--fs-1);
  font-weight: 700;
  letter-spacing: var(--track-caps);
  color: var(--muted);
  text-transform: uppercase;
}
.date .num {
  display: block;
  margin-top: var(--s-1);
  font-family: var(--font-display);
  font-size: var(--fs-d2);
  font-weight: 400;
  font-synthesis: none;
  line-height: 0.9;
  font-variant-numeric: tabular-nums;
}
/* Red = time pressure: the DOWs of the coming weekend only, and the HEUTE / MORGEN pill in the DOW slot. */
.day.weekend .dow {
  color: var(--red-text);
}
.dow.now {
  display: inline-block;
  max-width: 100%;
  padding: 3px 4px 2px;
  white-space: nowrap;
  overflow: hidden; /* never painted over the title, whatever the text size */
  border-radius: var(--r-1);
  font-family: var(--font-display);
  font-weight: 400;
  font-synthesis: none;
  letter-spacing: 0.04em;
  color: var(--on-red);
  background: var(--red);
}

.title {
  margin: 0;
  font-size: var(--fs-4);
  font-weight: 600;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
@media (min-width: 768px) {
  .title {
    font-size: 1.05rem;
    font-weight: 700;
  }
}
.title a {
  color: inherit;
  text-decoration: none;
}
/* Link affordance (§3.6): a title with a URL gets a dotted gold underline and «↗»; without URL plain text. */
.title a.ext {
  text-decoration: underline dotted var(--gold);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}
.ext-mark {
  margin-left: 0.25em;
  font-size: 0.8em;
  font-weight: 600;
  color: var(--gold-text);
}
@media (hover: hover) {
  .title a:hover {
    color: var(--gold-text);
    text-decoration-style: solid;
  }
}
.meta {
  margin: 2px 0 0;
  color: var(--muted);
  font-size: var(--fs-2);
  overflow-wrap: anywhere;
}
.meta time {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.canton {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: var(--side-w);
  min-height: var(--tap);
  padding: 2px 0;
  font: inherit;
  font-size: var(--fs-1);
  font-weight: 700;
  letter-spacing: 0.06em;
  line-height: 1;
  color: var(--green-text);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-2);
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition:
    transform var(--t-1) var(--ease-skank),
    background var(--t-2) var(--ease-tape);
}
.canton:active {
  transform: scale(0.96);
}
.canton[aria-pressed="true"] {
  color: var(--gold-text);
  background: var(--gold-tint);
  border-color: var(--gold);
}
@media (hover: hover) {
  .canton:hover {
    background: var(--gold-tint);
    color: var(--text);
  }
}
@media (forced-colors: active) {
  .canton[aria-pressed="true"] {
    border: 3px solid;
  }
}
.canton img {
  width: 28px;
  height: 32px;
  object-fit: contain;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45));
}
.canton.abroad img {
  width: 28px;
  height: 19px;
  border-radius: 2px;
}

/* Acts line: cream, hairline left tick, clamped to 2 lines on phones unless a search hit sits inside it. */
.acts {
  margin: 2px 0 0;
  color: var(--text);
  font-size: var(--fs-3);
  overflow-wrap: anywhere; /* a soundsystem name written as one word must not widen the page */
}
.acts mark {
  padding: 0 0.1em;
  color: var(--gold-text);
  background: var(--gold-tint);
  border-radius: 2px;
}
@media (max-width: 420px) {
  .acts:not(.hit) {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
}
.acts span + span::before {
  content: " · ";
  color: var(--muted);
}

@media (max-width: 420px) {
  .day {
    column-gap: 10px;
  }
  .show {
    gap: 4px 10px;
  }
  .show::before,
  .show::after {
    left: calc(-1 * (var(--date-w) + 10px + 8px));
  }
  .date .num {
    font-size: 1.7rem;
  }
  .dow.now {
    padding: 3px 3px 2px; /* MORGEN must stay inside the 48 px date column */
    letter-spacing: 0.01em;
  }
}

/* Venue and ticket links inside a row */
.venue-link {
  color: inherit;
  border-bottom: 1px dotted var(--muted);
}
@media (hover: hover) {
  .venue-link:hover {
    color: var(--gold);
    border-color: var(--gold);
  }
}
.act.tickets {
  color: inherit;
}

/* Touch screens: a press gives short feedback instead of a hover state that could stick. */
@media (hover: none) {
  .act:active,
  .canton:active,
  .shows .show:active {
    background: rgba(242, 182, 50, 0.1);
  }
}

/* SBB badge: a finger-sized hit area around the small label on touch screens. */
.sbb {
  position: relative;
}
@media (hover: none) {
  .sbb::before {
    content: "";
    position: absolute;
    inset: -12px -8px;
  }
}

/* Owner feedback 2026-10-13 18:30: quieter rows. The inline SBB badge next to the distance reserved an
 * empty slot in the meta line; the SBB action in the row's action line is the one place for it. */
.dist .sbb {
  display: none;
}

/* Footer (src/components/Footer.svelte): «Über uns» band and the bottom bar; secondary pages too. */
/* Footer: a lighter full-width band for "Über uns", then a slim bottom bar with the data stamp. */
.foot {
  margin-top: 72px;
  color: var(--muted);
  background: var(--surface);
  border-top: 1px solid var(--line);
}
.foot > .wrap {
  padding-top: 40px;
  padding-bottom: 40px;
}
.foot-bar {
  background: var(--bg);
  border-top: 1px solid var(--line);
}
.foot-bar-inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 6px 16px;
  padding-top: 14px;
  /* --callout-h: height of the visible callout (stores/callout.svelte.js), so the bar is never hidden behind it. */
  padding-bottom: calc(14px + var(--callout-h, 0px));
  font-size: 0.82rem;
}
.foot-bar-inner p {
  margin: 0;
}
/* Footer links: inline text, but with a padded hit area (the padding is taken back by the margin, so
   the layout does not move) and a wider line pitch so two wrapped lines are not 3 px apart. */
.foot-links {
  line-height: 1.6;
}
.foot-links a {
  display: inline-block;
  padding: 8px 4px;
  margin: -8px -4px;
  color: var(--muted);
}
@media (hover: hover) {
  .foot-links a:hover {
    color: var(--gold);
  }
}
/* Section title on the tricolor rule, with the cross mark stamped behind it (96 px, 12 %). */
.section-title {
  position: relative;
  isolation: isolate;
  margin: 0;
  padding-bottom: var(--s-2);
  font-size: var(--fs-1);
  font-weight: 800;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--gold-text);
}
.about-body {
  display: flex;
  gap: 32px;
  align-items: center;
  margin-top: 18px;
}
.about-photo {
  flex: 0 0 auto;
  width: 168px;
  height: 168px;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid var(--gold);
  box-shadow:
    0 0 0 4px var(--bg),
    0 0 0 6px var(--green),
    0 0 0 8px var(--bg),
    0 0 0 10px var(--red);
}
.about p {
  margin: 0 0 12px;
  max-width: 58ch;
  font-size: 0.95rem;
  line-height: 1.55;
}
.about-body p:first-child {
  margin-top: 0;
}
@media (max-width: 520px) {
  .about-body {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }
  .about p {
    max-width: none;
  }
}
.about .about-head {
  margin: 0 0 10px;
  max-width: none;
  font-size: 1.45rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.15;
  color: var(--text);
}
.about .about-head::after {
  content: "";
  display: block;
  width: 44px;
  height: 4px;
  margin-top: 10px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--red) 0 33%, var(--gold) 33% 66%, var(--green) 66%);
}
.about .authors {
  margin-top: 4px;
  font-size: 0.85rem;
  color: var(--muted);
}
.about .authors strong {
  color: var(--text);
}
/* The one-line headline only fits next to the photo from tablet width up; below that it wraps
   (it was the one element that made 521–640 px pages scroll sideways). */
@media (max-width: 720px) {
  .about .about-head {
    white-space: normal;
  }
}
@media (max-width: 520px) {
  .about .about-head::after {
    margin-left: auto;
    margin-right: auto;
  }
}
.about a {
  color: var(--green-text);
}
.updated {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 2px 10px;
}
/* The separator belongs to the second item and only when both share a line (from 480 px up). */
@media (min-width: 480px) {
  #updated:not(:empty)::before {
    content: "· ";
  }
}

/* Secondary pages: footer bar only (no About band) */
.foot:not(:has(.about)) {
  margin-top: var(--s-7);
  background: var(--bg);
  border-top: 0;
}
.foot a {
  color: var(--muted);
}
@media (hover: hover) {
  .foot a:hover {
    color: var(--gold);
  }
}

/* About: the photo lines up with the first line of text; on phones the running text is left-aligned
   (centred paragraphs with a ragged left edge are hard to read). */
.about-body {
  align-items: flex-start;
}
@media (max-width: 520px) {
  .about-body p:not(.about-head):not(.authors) {
    text-align: left;
  }
}

/* Update toast (src/components/pwa/UpdateToast.svelte); the install nudge is a callout
 * (src/components/callout/Callout.svelte). */
/* Update toast (src/components/pwa/UpdateToast.svelte); the install nudge is a callout (src/components/callout/Callout.svelte). */
.toast {
  position: fixed;
  left: 50%;
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 21;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 12px;
  align-items: center;
  /* Centred with a transform, so it needs a real width: as wide as its content, at most the screen. */
  width: max-content;
  max-width: calc(100vw - 32px);
  text-align: center;
  padding: 10px 14px;
  background: var(--text);
  color: var(--bg);
  border-radius: 999px;
  font-size: 0.9rem;
  font-weight: 600;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}
.toast button {
  font: inherit;
  font-weight: 700;
  color: var(--on-green);
  /* The green plate that carries dark text at >= 4.5:1 (--on-green on --green itself is only 3.7:1). */
  background: var(--green-text);
  border: 0;
  border-radius: 999px;
  padding: 5px 12px;
  cursor: pointer;
}

/* Link colours, in one place and on purpose after the component files: «a, a:visited» and «a:hover» have
 * the specificity of «.foot a» or «.geo-status a» and must win over them (gold on hover, no underline,
 * visited links keep the site's green). Before moving this file up, check those pairs. */
/* Links: no underlines anywhere; colour change on hover instead. */
a {
  text-decoration: none;
}
.about a,
.foot-links a,
.geo-status a {
  text-decoration: none;
  border-bottom: 0;
}

/* Links everywhere: green, no underline, gold on hover — never the browser's blue. */
a,
a:visited {
  color: var(--green-text);
  text-decoration: none;
}
/* The wordmark link in the sticky bar keeps the brand colours (cream «Jah», gold «genda»), visited or not. */
.mini-brand,
.mini-brand:visited {
  color: var(--text);
}
.about .authors .heart {
  color: var(--red-text);
}
@media (hover: hover) {
  a:hover {
    color: var(--gold);
  }
}
.title a,
.venue-link,
.foot-links a,
.about .authors a {
  color: inherit;
}
.foot-links a {
  color: var(--muted);
}

/* Row actions all share one colour (muted → gold on hover); no accent singles one out */
.actions .act,
.actions .act:visited {
  color: var(--muted);
}
@media (hover: hover) {
  .actions .act:hover {
    color: var(--gold-text);
  }
}

/* Meine Shows (src/components/mine/): star, state toggle, echo, undo toast, ★ button, panel, list mode. */
/* --- Meine Shows (§7 S5): star = Merken on/off, state toggle in marked rows, echo, undo toast, panel --- */
.act.star {
  -webkit-touch-callout: none; /* the long press (= Ich gehe) must not open the touch menu */
  -webkit-user-select: none; /* Safari only honours the prefixed property */
  user-select: none;
}
.act.star svg path {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
}
.show.interested .act.star {
  color: var(--gold);
}
.show.interested .act.star svg path {
  fill: var(--gold);
}
.show.going .act.star {
  color: var(--green-text);
}
.show.going .act.star svg path {
  fill: currentColor;
}
.show.interested::before {
  background: var(--gold);
  transform: none;
}
.show.going::before {
  background: var(--green);
  transform: none;
}
.show.shared-row::after {
  background: rgba(242, 182, 50, 0.07);
}
@media (forced-colors: active) {
  .show.interested,
  .show.going {
    border-left: 3px solid;
  }
}
/* Echo (§4, gesture 2): three ghost stars scale 1 → 1.8 and fade over --t-4, 120 ms apart, on «Ich gehe». */
.act.star .echo {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
  color: var(--green-text);
  opacity: 0;
  animation: echo var(--t-4) var(--ease-tape) both;
}
.act.star .echo svg {
  width: 20px;
  height: 20px;
}
@keyframes echo {
  from {
    transform: scale(1);
    opacity: 0.9;
  }
  to {
    transform: scale(1.8);
    opacity: 0;
  }
}
/* State toggle «Interessiert | Ich gehe»: a small pill group under the meta line of a marked row. The
 * pressed segment is gold (Interessiert) or green (Ich gehe) with dark text, the words carry the state. */
.mark-slot:empty {
  display: none;
}
/* At large text sizes the two segments do not fit beside each other in a narrow row: they wrap onto two
 * lines inside the group instead of running past the screen edge (WCAG 1.4.4 / 1.4.10). */
.mark-seg {
  display: inline-flex;
  flex-wrap: wrap;
  max-width: 100%;
  padding: 2px;
  gap: 10px 2px; /* rows: the segments' 44 px touch extensions (±5 px, below) must not overlap */
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
}
.mark-seg .act {
  min-height: 26px;
  min-width: 0;
  padding: 0 10px;
  font-size: var(--fs-1);
  font-weight: 600;
  border-radius: 999px;
}
.mark-seg .act[aria-pressed="true"] {
  color: var(--on-gold);
  background: var(--gold);
  font-weight: 700;
}
.mark-seg .act[data-state="going"][aria-pressed="true"] {
  color: var(--on-green);
  background: var(--green-text);
}
@media (hover: hover) {
  .mark-seg .act[aria-pressed="true"]:hover {
    color: var(--on-gold);
    background: var(--gold);
  }
  .mark-seg .act[data-state="going"][aria-pressed="true"]:hover {
    color: var(--on-green);
    background: var(--green-text);
  }
}
.mark-seg .act:focus-visible {
  box-shadow: inset 0 0 0 2px var(--gold);
}
.mark-seg .act[aria-pressed="true"]:focus-visible {
  box-shadow: inset 0 0 0 2px var(--bg);
}
@media (forced-colors: active) {
  .mark-seg .act[aria-pressed="true"] {
    border: 3px solid;
  }
}
/* Touch: the segments draw 34 px high, but their hit area is the full --tap (44 px) via an invisible
 * extension above and below (§5); the toggle takes its own line above the actions. */
@media (max-width: 599px), (hover: none) {
  .mark-seg .act {
    position: relative;
    min-height: 34px;
    padding: 0 14px;
  }
  .mark-seg .act::before {
    content: "";
    position: absolute;
    inset: -5px 0;
  }
}
@media (max-width: 599px) {
  .mark-slot {
    flex: 1 1 100%;
    /* 11 px: 8 px of dead space between the segments' 44 px hit area and the action targets below (a tap
     * 8 px under the «Interessiert» pill opened Google Kalender in a new tab). Costs 8 px per marked row. */
    margin: 4px 0 11px;
  }
}
@media (min-width: 600px) {
  .mark-slot {
    margin-right: auto;
    align-self: center;
  }
}
/* A friend's «Ich gehe» in a shared list: green words, not a pill. */
.goes {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-right: auto;
  font-size: var(--fs-1);
  font-weight: 600;
  color: var(--green-text);
}
.goes svg {
  width: 14px;
  height: 14px;
  fill: currentColor;
}
/* Undo toast «… entfernt · Rückgängig» (4 s): enters from +24 px with the skank ease, docks above a visible
 * callout. */
.toast {
  animation: toast-in var(--t-3) var(--ease-skank);
}
.toast.undo {
  bottom: calc(16px + env(safe-area-inset-bottom, 0px) + var(--callout-h, 0px));
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--line);
  box-shadow: var(--sh-2);
}
/* One line, also at 320 px: a long title is the part that gets the ellipsis, the verb «entfernt» and the
 * Rückgängig button always survive. */
.toast.undo {
  flex-wrap: nowrap;
}
.toast.undo span {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3em;
  max-width: calc(100vw - 180px); /* what is left beside the button */
  white-space: nowrap;
}
.toast.undo .t {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600;
}
.toast.undo button {
  min-height: 32px;
}
@keyframes toast-in {
  from {
    transform: translate(-50%, 24px);
    opacity: 0;
  }
}
/* ★ button: always visible right of the search; muted without the badge while nothing is marked. */
.icon-btn.mine {
  position: relative;
}
.icon-btn.mine.empty {
  color: var(--faint);
  cursor: default;
}
@media (hover: hover) {
  .icon-btn.mine.empty:hover {
    color: var(--muted);
    border-color: var(--line);
  }
}
.icon-btn.mine.empty b {
  display: none;
}
.icon-btn.mine b {
  position: absolute;
  top: -2px; /* the 48 px toolbar leaves 2 px above the button: the badge must not leave the stuck bar */
  right: -3px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  font-family: var(--font-display);
  font-size: var(--fs-1);
  font-weight: 400;
  font-synthesis: none;
  line-height: 18px;
  text-align: center;
  color: var(--bg);
  background: var(--green-text);
  border: 2px solid var(--bg);
}
@media (min-width: 768px) {
  .icon-btn.mine b {
    top: -6px; /* the 56 px toolbar has room for the badge to sit on the corner */
    right: -6px;
  }
}

.mine-panel {
  margin: 12px 0 0;
  padding: 10px 12px;
  font-size: 0.85rem;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--gold);
  border-radius: 0 6px 6px 0;
}
.mine-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
}
.mine-panel .act {
  color: var(--text);
  white-space: normal; /* the pills may wrap; the row actions above never do */
  text-align: center;
  line-height: 1.2;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 4px 10px;
}
.mine-panel .act span {
  display: inline;
}

/* --- Listenmodus (★ Meine Shows / geteilte Liste): only the list, a slim header with ✕ in the sticky bar.
   body.list-mode folds every other filter away; the ★ button keeps its place at the right (still pressed). --- */
.list-mode .filters > :not(.list-head):not(.toolbar),
.list-mode .toolbar > :not(#toggle-mine),
.list-mode .when-panel,
.list-mode .where-panel,
.list-mode .results {
  display: none;
}
/* The bar keeps its frozen height; head and ★ sit vertically centred in it. */
.list-mode .toolbar {
  position: absolute;
  top: 0;
  right: 0;
  height: var(--bar-h);
}
.list-mode.list-shared .toolbar {
  display: none;
}
/* The title wraps under ✕ when the two do not fit on one line (long list names, 200 % text size), so
 * «Meine Shows» is not cut to «Mei…»; the frozen bar height holds both lines. */
.list-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  align-content: center;
  gap: 2px 10px;
  height: var(--bar-h);
  padding: 0 54px 0 0; /* room for the ★ at the right */
  animation: list-in 0.18s ease-out;
}
.list-mode.list-shared .list-head {
  padding-right: 0;
}
.list-close {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 36px;
  padding: 0 13px 0 11px;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
}
.list-close i,
.list-title i {
  font-style: normal;
}
.list-close b {
  font-weight: 400;
  font-size: 1.05rem;
  line-height: 1;
}
@media (hover: hover) {
  .list-close:hover {
    color: var(--gold);
    border-color: var(--gold);
  }
}
.list-close:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 1px;
}
.list-title {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.list-title:focus {
  outline: none;
}
.list-title svg {
  flex: none;
  width: 18px;
  height: 18px;
  color: var(--gold);
}
.list-title svg path {
  fill: currentColor;
}
.list-shared .list-title svg {
  color: var(--green-text);
}
.list-title .t {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.list-title .n {
  flex: none;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
}
.list-mode .mine-panel {
  margin-top: 4px;
}
.list-shared .mine-panel {
  border-left-color: var(--green);
}
.mine-panel .take {
  color: var(--bg);
  background: var(--green-text);
  border-color: var(--green-text);
  font-weight: 700;
}
@media (hover: hover) {
  .mine-panel .take:hover {
    color: var(--bg);
    background: var(--green);
  }
}
.mine-panel .take[disabled] {
  color: var(--muted);
  background: none;
  border-color: var(--line);
  cursor: default;
}
@keyframes list-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
}
@media (max-width: 600px) {
  /* Phones: the list actions as a 2-up grid of thumb-sized buttons (an odd last one spans the row). */
  .list-mode .mine-panel .mine-note {
    flex: 1 1 100%;
  }
  .list-mode .mine-panel .act {
    flex: 1 1 45%;
    justify-content: center;
    height: auto;
    min-height: 44px;
  }
  .list-mode .mine-panel .act[data-mine="ics"] {
    flex-basis: 100%; /* the long label gets the whole row; the two share links sit below */
  }
}
@media (max-width: 480px) {
  .list-close span i,
  .list-title i {
    display: none; /* phones: "✕ Zurück", "Liste von Kusi", no month span; wider: "✕ Zurück zur Agenda", "Geteilte Liste von Kusi · Oktober 2026" */
  }
}
/* 320 px: the ✕ alone (44 px round, aria-label «Zurück zur Agenda»), so «Meine Shows» and «Liste von
 * Seraina» – the one thing the head has to say – are not the part that gets the ellipsis. */
@media (max-width: 359px) {
  .list-close {
    justify-content: center;
    min-width: 44px;
    padding: 0;
    gap: 0;
  }
  .list-close span {
    display: none;
  }
}

/* ---------- S8: motion system, performance and preference rules (design/ui-revamp.md §4, §5) ---------- */

/* Switch-on (§4, gesture 3): the top stripe draws in once, scaleX 0 → 1 over 450 ms. */
.stripe:not(.bottom) {
  transform-origin: left;
  animation: switch-on 450ms var(--ease-tape) both;
}
@keyframes switch-on {
  from {
    transform: scaleX(0);
  }
}

/* Filter change: the first ten rows enter with --stagger (translateY 6 px → 0 + opacity); ShowList.svelte marks
 * them .enter and numbers them with --i. The fallback without view transitions is a --t-2 opacity dip. */
#list .show.enter {
  animation: row-in var(--t-3) var(--ease-tape) backwards;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}
@keyframes row-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
}
#list.dip {
  animation: dip var(--t-2) var(--ease-tape);
}
@keyframes dip {
  from {
    opacity: 0.4;
  }
}
/* Only the list takes part in the cross-fade. The root is opted out: a root transition snapshots the whole
 * page and, pointer-events or not, every tap in those 200 ms lands on <html> (measured in Chromium) – the
 * next chip after a filter tap was lost. Named, only #list is swapped as an image; the bar, chips, pills and
 * panels stay live and tappable while the rows fade. */
html {
  view-transition-name: none;
}
#list {
  view-transition-name: list;
}
::view-transition-group(list),
::view-transition-old(list),
::view-transition-new(list) {
  animation-duration: var(--t-2);
}
::view-transition {
  pointer-events: none;
}

/* Locate: the button's ring expands over --t-4, repeating until a fix (WherePanel.svelte sets .locating); the
 * distances then fade in per row with --stagger. */
.where-locate.locating {
  animation: locate-pulse var(--t-4) var(--ease-tape) infinite;
}
@keyframes locate-pulse {
  from {
    box-shadow: 0 0 0 0 rgba(242, 182, 50, 0.7);
  }
  to {
    box-shadow: 0 0 0 12px rgba(242, 182, 50, 0);
  }
}
#list .dist {
  animation: fade-in var(--t-3) var(--ease-tape) backwards;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}
@keyframes fade-in {
  from {
    opacity: 0;
  }
}

/* Performance (§4): a month section off screen is neither laid out nor painted. Paint containment clips
 * at the section box, so the section takes the gutter (the rows' zebra plates and the stuck month head
 * bleed into it by design). */
#list > section {
  content-visibility: auto;
  /* The placeholder height of a skipped month follows its row count (ShowList.svelte writes --rows per section:
   * ~145 px per phone row, ~108 px on desktop, plus the head), so a fast flick to the footer does not
   * grow the document by a thousand pixels on the way; `auto` keeps the real size once laid out. */
  --row-est: 145px;
  contain-intrinsic-size: auto calc(var(--rows, 4) * var(--row-est) + 60px);
  margin: 0 calc(-1 * var(--gutter));
  padding: 0 var(--gutter);
}
@media (min-width: 768px) {
  #list > section {
    --row-est: 108px;
  }
}

/* Preferences (§5). Reduced motion: colour states stay, nothing moves (the duration tokens are 0 ms;
 * what has a fixed duration or loops is switched off here – one block for the whole sheet). */
@media (prefers-reduced-motion: reduce) {
  html,
  .chips,
  .genres {
    scroll-behavior: auto;
  }
  .show.highlight::after {
    animation: none;
    background: var(--gold-tint); /* a static 3 s gold edge instead of the flash */
  }
  .filters,
  .shows .show {
    transition: none;
  }
  .sk,
  .list-head,
  .stripe:not(.bottom),
  #list .show.enter,
  #list.dip,
  #list .dist,
  .where-locate.locating,
  .toast {
    animation: none;
  }
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}
/* More contrast: the muted text becomes text, the lines become the muted colour. */
@media (prefers-contrast: more) {
  :root {
    --muted: #f3ead8;
    --faint: #bcae98;
    --line: #bcae98;
    --line-soft: #766759;
  }
}
/* Forced colours (Windows high contrast): a pressed or marked thing gets a solid border, icons follow the
 * text colour (the per-component rules above do the chips, pills, cantons, marked rows and the toggle). */
@media (forced-colors: active) {
  .show.going .mark-seg,
  .seg [aria-pressed="true"],
  .icon-btn[aria-pressed="true"],
  .bar[aria-pressed="true"] {
    border: 3px solid;
  }
  svg {
    forced-color-adjust: auto;
    fill: currentColor;
  }
  .stripe span,
  .cal-dots i,
  .pin-icon {
    forced-color-adjust: none;
  }
}

/* Festival styles (src/components/festival/). Imported in its place by src/styles/index.css, after the list
 * rules: the ≤ 420 px «.date .num» and «.dow.now» rules there are less specific or set other properties. */

/* Festival card («dubplate», §3.7, design/festival-spec.md): the day section itself is the card – surface
 * plate, radius, a 3 px tricolor top edge and the cross mark as an 8 % watermark bottom right (never
 * animated). The date cell shows the range («FR–SO» over «16–18», across a month boundary «30» over
 * «–1.8.») and, while the festival runs, a gold LÄUFT pill in the DOW slot. */
.day.festival {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  margin: var(--s-3) -8px;
  padding: 10px 8px 4px;
  background: var(--surface);
  border-radius: var(--r-3);
}
.shows > .day.festival:nth-of-type(even) {
  background: var(--surface);
}
.day.festival::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--red) 33.3%, var(--gold) 33.3% 66.6%, var(--green) 66.6%);
}
.day.festival::after {
  content: "";
  position: absolute;
  z-index: -1;
  right: -30px;
  bottom: -30px;
  width: 96px;
  height: 96px;
  background: url("img/logo/jahgenda.svg") center / contain no-repeat;
  opacity: 0.08;
  pointer-events: none;
}
.day.festival > .date {
  padding-top: 10px;
}
.day.festival .show {
  min-height: 72px;
}
.date .dow.range {
  letter-spacing: 0.03em;
}
.date .num.range {
  font-size: 1.45rem;
  letter-spacing: -0.01em;
}
.date .day-end {
  display: block;
  margin-top: 3px;
  font-size: var(--fs-1);
  font-weight: 700;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.dow.now.live {
  color: var(--on-gold);
  background: var(--gold);
}
.meta .ndays {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.acts.none {
  color: var(--muted);
  font-style: italic;
}

/* Line-up disclosure (festival-spec.md §3.1): a 40 px summary line of its own («▸ Alle 12 Acts nach Tag»,
 * open «▾ Weniger»), the days as a definition-style grid [FR 16. | acts]. Opens and closes by animating
 * grid-template-rows 0fr → 1fr (§4), the <details> itself is native (keyboard, screen readers). */
.lineup {
  margin: 0;
}
.lineup summary {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding-left: var(--s-2);
  border-radius: var(--r-1);
  font-size: var(--fs-2);
  color: var(--muted);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.lineup summary::-webkit-details-marker {
  display: none;
}
.lineup summary::before {
  content: "▸";
  width: 1em;
  color: var(--gold-text);
}
.lineup[open] summary::before {
  content: "▾";
}
.lineup summary .close,
.lineup[open] summary .open {
  display: none;
}
.lineup[open] summary .close {
  display: inline;
}
@media (hover: hover) {
  .lineup summary:hover {
    color: var(--text);
  }
}
.lineup-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--t-3) var(--ease-tape);
}
.lineup.is-open .lineup-body {
  grid-template-rows: 1fr;
}
.lineup-body > div {
  min-height: 0;
  overflow: hidden;
}
.days {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr); /* «FR 16.» … «TAG OFFEN» on one line */
  gap: 4px 10px;
  padding: 2px 0 8px var(--s-2);
  font-size: var(--fs-3);
  color: var(--text);
  overflow-wrap: anywhere;
}
/* 320 px: the acts column had 107 px next to the day label (2–3 words per line, an 800 px card); the
 * label goes above its acts. From 360 px the side-by-side grid of design/festival-spec.md §3.5 stands. */
@media (max-width: 359px) {
  .days {
    grid-template-columns: 1fr;
    gap: 2px 0;
  }
  .days .d:not(:first-child) {
    padding-top: 8px;
  }
}
.days .d {
  padding-top: 2px;
  font-size: var(--fs-1);
  font-weight: 700;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.days .a span + span::before {
  content: " · ";
  color: var(--muted);
}
.days mark {
  padding: 0 0.1em;
  color: var(--gold-text);
  background: var(--gold-tint);
  border-radius: 2px;
}

/* Ghost row (§3.5): «↳ Reeds Festival läuft noch bis So 1. Aug.» right under the head of every later month
 * a festival reaches into; a link to the card in its start month. */
.ghost {
  margin: var(--s-1) 0 0;
  padding-left: calc(var(--date-w) + var(--s-3));
  font-size: var(--fs-2);
}
.ghost a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: var(--tap); /* 44 px target (§5) */
  color: var(--muted);
  text-decoration: none;
  border-radius: var(--r-1);
}
.ghost .arrow {
  color: var(--gold-text);
  font-weight: 700;
}
@media (hover: hover) {
  .ghost a:hover {
    color: var(--text);
  }
}
@media (max-width: 420px) {
  .date .num.range {
    font-size: 1.2rem;
  }
  .date .dow.range {
    letter-spacing: 0;
  }
  .ghost {
    padding-left: calc(var(--date-w) + 10px);
  }
}

/* Kalender and Karte (src/components/views/): the calendar grid, the map frame, Leaflet's controls and popups,
 * the pins, clusters and legends. Rules that mix these with other parts of the page (touch hit areas, forced
 * colours) live in src/styles/motion.css. Imported in its place by src/styles/index.css. */
/* Kalender: month grid, Monday first */
.calendar {
  margin-top: 18px;
}
.cal-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: var(--s-3);
  padding-bottom: var(--s-1);
}
.cal-title {
  margin: 0 4px;
  min-width: 0;
  font-family: var(--font-display);
  font-size: var(--fs-d1);
  font-weight: 400;
  font-synthesis: none;
  letter-spacing: 0.02em;
  line-height: var(--lh-tight);
  text-transform: uppercase;
  color: var(--gold-text);
}
.cal-nav {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--text);
  font-size: 1.3rem;
  line-height: 1;
  padding: 0 0 2px;
  cursor: pointer;
}
@media (hover: hover) {
  .cal-nav:hover {
    color: var(--gold);
    border-color: var(--gold);
  }
}
.cal-today {
  margin-left: auto;
  font-size: 0.85rem;
}
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
  background: var(--line-soft);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  overflow: hidden;
}
.cal-dow {
  background: var(--bg);
  padding: 6px 4px;
  text-align: center;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.cal-dow:nth-child(n + 5) {
  color: var(--red-text);
}
.cal-cell {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  min-height: 92px;
  padding: 4px;
  background: var(--surface);
}
.cal-cell.other {
  background: var(--bg);
}
.cal-cell.other .cal-num {
  opacity: 0.6; /* 0.35 fell to 2:1 */
}
.cal-num {
  align-self: flex-start;
  width: 26px;
  height: 26px;
  padding: 0;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  background: none;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}
.cal-cell.past .cal-num {
  color: var(--muted);
}
.cal-cell.weekend:not(.past) .cal-num {
  color: var(--red-text);
}
.cal-cell.today {
  box-shadow: inset 0 0 0 2px var(--gold); /* the gold ring marks today */
}
.cal-cell.today .cal-num {
  color: var(--gold-text);
}
/* «Ich gehe» (§3.11): the pill carries a star and the green role; a dot gets a cream ring. */
.cal-pills .pill.going {
  --pill: var(--green-text);
}
.cal-dots i.going {
  box-shadow: 0 0 0 1.5px var(--text);
}
.cal-dots {
  display: none;
}
.cal-pills {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.pill {
  display: block;
  width: 100%;
  font: inherit;
  font-size: var(--fs-0);
  font-weight: 700;
  text-align: left;
  color: var(--text);
  background: rgba(255, 255, 255, 0.07);
  background: color-mix(in srgb, var(--pill) 22%, transparent);
  border: 0;
  border-left: 3px solid var(--pill);
  border-radius: 3px;
  padding: 2px 5px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
@media (hover: hover) {
  .pill:hover {
    background: rgba(255, 255, 255, 0.14);
    background: color-mix(in srgb, var(--pill) 42%, transparent);
  }
}
.cal-cell.past .pill {
  opacity: 0.5;
}
.cal-day {
  display: none;
}
.cal-empty {
  margin: 12px 0 0;
  color: var(--muted);
}
.cal-legend {
  font-size: var(--fs-1);
}
@media (max-width: 600px) {
  /* Compact calendar: one square per day with dots, the selected day's shows below.
     The whole cell is the tap target (Calendar.svelte), not only the number. */
  .cal-cell {
    aspect-ratio: 1;
    min-height: 0;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 2px;
    cursor: pointer;
  }
  .cal-cell .cal-num {
    align-self: center;
    width: 32px;
    height: 32px;
  }
  /* Head row: ‹ Monat › Heute on one line, "Abonnieren" wraps underneath (right-aligned) on narrow phones. */
  .cal-head {
    flex-wrap: wrap;
  }
  .cal-nav {
    flex: none;
    width: var(--tap); /* a finger-sized target on the phone (§5) */
    height: var(--tap);
  }
  .cal-title {
    white-space: nowrap;
  }
  .cal-today,
  .cal-subscribe {
    padding: 10px 8px;
  }
  .cal-head .cal-subscribe {
    margin-left: auto; /* beats the general .cal-subscribe margin below */
  }
  .cal-pills {
    display: none;
  }
  .cal-dots {
    display: flex;
    gap: 2px;
    height: 6px;
  }
  .cal-dots i {
    width: 6px;
    height: 6px;
    border-radius: 50%;
  }
  .cal-cell.selected {
    background: var(--surface-2);
    box-shadow: inset 0 0 0 2px var(--text);
  }
  .cal-cell.selected.today {
    box-shadow:
      inset 0 0 0 2px var(--text),
      inset 0 0 0 4px var(--gold);
  }
  .cal-day {
    display: block;
  }
}

/* Karte: Leaflet in the site's colours; the tiles are dimmed with a filter. */
.map-view {
  margin-top: 18px;
}
.map {
  /* Never taller than what fits under the bar (dvh: the real viewport on iOS, vh as the fallback). */
  height: min(70vh, calc(100vh - var(--bar-h, 64px) - 24px));
  height: min(70vh, calc(100dvh - var(--bar-h, 64px) - 24px));
  min-height: 320px;
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  overflow: hidden;
  background: var(--surface);
  z-index: 0; /* keep Leaflet's panes below the sticky filter bar */
}
/* Low landscape phones: a smaller floor, so the map still fits under the bar (vanilla order: after .map). */
@media (max-height: 500px) {
  .map {
    min-height: 240px;
  }
}
.map:not(.leaflet-container) {
  display: grid;
  place-items: center;
}
.map-note {
  margin: 0;
  padding: 0 var(--s-5);
  color: var(--muted);
  text-align: center;
}
/* Offline: «Karte braucht Netz» in the frame, with the way to the list (no Leaflet is fetched). */
.map-note.offline strong {
  display: block;
  margin-bottom: var(--s-1);
  color: var(--text);
}
.map-tiles {
  filter: brightness(0.75) contrast(1.1) saturate(0.8);
}
.map-view .leaflet-container {
  font: inherit;
  background: var(--surface);
}
.map-view .leaflet-container .leaflet-control-attribution {
  background: rgba(20, 17, 14, 0.85);
  color: var(--muted);
  font-size: 11px;
}
.map-view .leaflet-container .leaflet-control-attribution a {
  color: var(--gold);
}
.map-view .leaflet-container .leaflet-bar a {
  background: var(--surface);
  color: var(--text);
  border-bottom-color: var(--line);
}
@media (hover: hover) {
  .map-view .leaflet-container .leaflet-bar a:hover {
    color: var(--gold);
  }
}
.map-view .leaflet-container .leaflet-bar a.leaflet-disabled {
  color: var(--line);
}
.map-view .leaflet-popup-content-wrapper,
.map-view .leaflet-popup-tip {
  background: var(--surface);
  color: var(--text);
  box-shadow: 0 3px 14px rgba(0, 0, 0, 0.6);
}
.map-view .leaflet-popup-content {
  margin: 10px 14px;
  font-size: 0.9rem;
  line-height: 1.4;
  overflow-wrap: anywhere;
}
.map-view .leaflet-popup-content-wrapper {
  max-width: calc(100vw - 80px);
}
.map-view .leaflet-container a.leaflet-popup-close-button {
  color: var(--muted);
}
/* The × is a thumb-sized target hanging over the wrapper's corner; the venue line keeps clear of it. */
.map-view .leaflet-container a.leaflet-popup-close-button {
  top: -2px;
  right: -2px;
  width: 40px;
  height: 40px;
  padding: 0;
  font-size: 26px;
  line-height: 40px;
  text-align: center;
}
.map-view .leaflet-popup-content > strong:first-child {
  display: inline-block;
  max-width: calc(100% - 26px);
}
/* Several venues in one house share a pin: the popup lists them one after the other. */
.map-view .popup-venue {
  margin: 8px 0 0;
}
/* Touch screens: finger-sized zoom buttons, and a map that fits a phone held sideways. */
@media (hover: none) {
  .map-view .leaflet-touch .leaflet-bar a {
    width: 44px;
    height: 44px;
    line-height: 44px;
  }
  /* The shows in a popup are rows a thumb can tell apart (44 px, a hairline between them). */
  .map-view .leaflet-container .popup-shows li + li {
    border-top: 1px solid var(--line-soft);
  }
  .map-view .leaflet-container .popup-shows a {
    display: block;
    min-height: 44px;
    padding: 11px 0;
    box-sizing: border-box;
  }
}
/* Several pins on one spot (the start view of a portrait phone): one cluster badge, a tap zooms in. */
.cluster-icon {
  background: none;
  border: 0;
  display: grid;
  place-items: center;
  cursor: pointer;
}
.cluster-icon span {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: #14110e;
  border: 3px solid var(--gold);
  box-shadow:
    0 0 0 2px #14110e,
    0 2px 4px rgba(0, 0, 0, 0.6);
}
.cluster-icon b {
  color: #f3ead8;
  font-size: 0.85rem;
  font-weight: 800;
  line-height: 1;
}
.cluster-icon:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: 50%;
}
/* While the first tiles are on their way the frame says so (the pins float on a plain square until then). */
.map.tiles-pending::after {
  content: attr(data-loading); /* t("map.loading"), in the page language */
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: 500;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(20, 17, 14, 0.8);
  color: var(--muted);
  font-size: var(--fs-1);
  pointer-events: none;
}

.popup-shows {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
}
.map-view .leaflet-container .popup-shows a {
  color: var(--text);
  text-decoration: none;
}
@media (hover: hover) {
  .map-view .leaflet-container .popup-shows a:hover {
    color: var(--gold);
    text-decoration: underline;
  }
}
.popup-shows time {
  margin-right: 4px;
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

/* Map: tiles toned down to sit on the dark page; legend under the map. */
/* Dark map from the free OSM tiles: invert + desaturate (no third-party tile service, no API key). */
.map .leaflet-tile-pane {
  filter: invert(100%) hue-rotate(180deg) brightness(0.78) contrast(0.92) saturate(0.3);
}
.map {
  background: #0d0b09;
}
.map-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 14px;
  margin: 10px 0 0;
  color: var(--muted);
  font-size: var(--fs-1);
}
.legend-item {
  display: inline-flex;
  align-items: center;
  white-space: nowrap; /* icon and label wrap together, never apart */
}
/* Pin markers (Leaflet divIcon): SVG pin plus a small count badge. */
.pin-icon {
  background: none;
  border: 0;
}
.pin-icon svg {
  display: block;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.5));
}
.pin-icon b {
  position: absolute;
  top: -6px;
  right: -9px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: #f3ead8;
  color: #14110e;
  font-size: 0.7rem;
  font-weight: 800;
  line-height: 18px;
  text-align: center;
  border: 2px solid #14110e;
}

/* Legend: the same striped pins as on the map (SVG from pinSvg), a mini cluster badge, the blue dot. */
.pin-mini {
  display: inline-flex;
  margin-right: 5px;
  vertical-align: -4px;
}
.pin-mini svg {
  display: block;
}
.cluster-mini {
  display: inline-block;
  width: 12px;
  height: 12px;
  margin-right: 5px;
  border-radius: 50%;
  background: #14110e;
  border: 2px solid var(--gold);
  box-shadow: 0 0 0 1px #14110e;
}
.pin-mini.you {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #4285f4;
  box-shadow: 0 0 0 3px rgba(66, 133, 244, 0.3);
}
.you-icon {
  background: none;
  border: 0;
}
.you-icon .halo {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: rgba(66, 133, 244, 0.25);
}
.you-icon .dot {
  position: absolute;
  left: 4px;
  top: 4px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #4285f4;
  border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}

.cal-subscribe {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: 10px;
}
.cal-subscribe svg {
  width: 13px;
  height: 13px;
}

/* Calendar: arrows stop at the first/last month with shows; empty month note; dot legend. */
.cal-nav[disabled] {
  opacity: 0.35;
  cursor: default;
}
.cal-month-empty {
  margin: 10px 0 0;
}
.cal-month-empty a {
  color: var(--gold);
}
.cal-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin: 8px 0 0;
  font-size: 0.75rem;
  color: var(--muted);
}
.cal-legend i {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 6px;
  border-radius: 50%;
  vertical-align: 0;
}
/* Desktop pills were cut to ~10 characters in a 103 px cell: two lines instead of an ellipsis. */
@media (min-width: 768px) {
  .pill {
    white-space: normal;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-height: 1.25;
  }
}

/* Touch: «Heute» gets the 44 px hit band (after the phone layout above, as in the former single stylesheet). */
@media (hover: none) {
  .cal-today {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
    padding: 0 8px;
  }
}
