/* ============================================================
   gamescom 2026 guide — "Hall Signage"

   Trade-fair wayfinding crossed with print. Typography and hard
   edges do the work: no gradients, no blurred elevation, no
   rounded pill soup. Hall numbers are set on solid plates because
   that is the one thing you actually read while walking, and each
   exhibitor is a discrete panel rather than a cell of one table.

   Two earlier directions (a light "Fanzine" and a monospace
   "Console") were built on these same tokens and dropped once
   this one was chosen; recover them from css/themes.css in the
   history if you ever want to compare again.
   ============================================================ */

:root {
  /* ---- ground & ink (warm neutral, deliberately not navy) ---- */
  --ground:    #09090a;
  --ground-2:  #141416;
  --ground-3:  #1c1c1f;
  --rule:      #2b2c30;
  --rule-soft: #1f2023;
  --ink:       #f2efe9;
  --ink-mid:   #b3b1ab;
  --ink-dim:   #86847f;

  /* ---- one signal colour, used sparingly ---- */
  --signal:     #ff4d17;
  --signal-ink: #0b0b0c;
  --signal-wash: rgba(255, 77, 23, .12);
  --grain:       rgba(255, 255, 255, .025);

  /* The business area's own colour, taken from the official hall plan's
     `farbenvorgabe` and carried through data/hallplan/index.json — so the
     map's hall washes, the trade plates and the trade cards all mean the
     same thing by it. Needs light ink: see .hall-marker-trade. */
  --business: #7800ff;

  /* ---- status ---- */
  --ok:      #2fbf85;
  --warn:    #e8a33a;
  --neutral: #86847f;
  --alert:   #f2564f;

  /* ---- type ---- */
  --font-ui:      "Archivo", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-display: "Archivo Narrow", "Archivo", system-ui, sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  /* poster weight, used only for structural headings — exhibitor names stay on
     --font-display so 38 cards don't all shout at once */
  --font-poster:  "Anton", "Archivo Narrow", system-ui, sans-serif;

  /* ---- shape: near-square, signage-like ---- */
  --radius:   2px;
  --radius-sm: 2px;
  --border:   1px solid var(--rule);
  --wrap:     1200px;

  /* Hard offset, never blurred. On a near-black ground a black shadow is
     invisible, so the offset is LIGHTER than the ground — same printed
     second-layer effect as ink-on-paper, inverted. */
  --plate-shadow:       5px 5px 0 #212125;
  --plate-shadow-hover: 8px 8px 0 #2c2c31;

  font-size: 16px;
  color-scheme: dark;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html { -webkit-text-size-adjust: 100%; }

body {
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: .95rem;
  line-height: 1.55;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  -webkit-font-smoothing: antialiased;
  /* barely-there halftone; gives the ground a printed grain instead of flat ink */
  background-image: radial-gradient(var(--grain) .5px, transparent .5px);
  background-size: 4px 4px;
}

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 24px; width: 100%; }
.hidden { display: none !important; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  /* These are sentences, not labels — inheriting a `text-transform: uppercase`
     from the control they sit in makes some screen readers spell them out. */
  text-transform: none; letter-spacing: normal;
}

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

.skip-link {
  position: absolute; left: 24px; top: -60px; z-index: 200;
  background: var(--signal); color: var(--signal-ink);
  padding: 10px 16px; font-weight: 700; text-decoration: none;
  transition: top .15s;
}
.skip-link:focus { top: 12px; }

/* small letterspaced label used everywhere instead of icons/emoji */
.row-label,
.overline,
.field-label,
.block-head,
.section-num,
.tab-index,
.brand-kicker {
  font-family: var(--font-mono);
  font-size: .66rem;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* ============================================================
   header
   ============================================================ */
.site-header {
  background: var(--ground);
  border-bottom: 1px solid var(--rule);
  position: sticky;
  top: 0;
  z-index: 50;
}

.header-inner {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  padding-top: 20px;
  padding-bottom: 16px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-right: auto; /* holds the meta block and share button at the right edge */
  text-decoration: none;
  color: inherit;
}
.brand-mark {
  display: grid;
  place-items: center;
  width: 46px; height: 46px;
  background: var(--signal);
  color: var(--signal-ink);
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 1.25rem;
  letter-spacing: -.03em;
  border-radius: var(--radius-sm);
}
.brand-text { display: flex; flex-direction: column; gap: 1px; }
.brand-title {
  font-family: var(--font-poster);
  font-size: 1.6rem;
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: .005em;
  text-transform: uppercase;
}
.brand-year { color: var(--ink-dim); font-weight: 600; }

/* order + the auto margin, not source order: the share button is written
   next to the wordmark so the phone grid can lift it into the gap there,
   and belongs at the far right of the masthead everywhere else. */
.header-meta { text-align: right; display: flex; flex-direction: column; gap: 2px; order: 2; }
.meta-line { color: var(--ink-mid); font-size: .84rem; }
.countdown {
  font-family: var(--font-mono);
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--signal);
  font-variant-numeric: tabular-nums;
}

/* The masthead's stamped controls as one row rather than a stack of
   individually aligned buttons — each aligning itself against the meta text
   above is what left the language toggle floating mid-air on a phone. */
.header-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
}

/* ---- tabs: signage strip, not browser chrome ---- */
.tabs-outer { border-top: 1px solid var(--rule-soft); }
.tabs { display: flex; overflow-x: auto; scrollbar-width: none; padding: 0 24px; }
.tabs::-webkit-scrollbar { display: none; }

.tab {
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  color: var(--ink-dim);
  font: inherit;
  font-weight: 600;
  font-size: .88rem;
  letter-spacing: .01em;
  padding: 13px 20px 11px;
  margin-right: -1px;
  cursor: pointer;
  white-space: nowrap;
  display: flex;
  align-items: baseline;
  gap: 8px;
  transition: color .12s, border-color .12s;
}
.tab:first-child { padding-left: 0; }
.tab .tab-index { transition: color .12s; }
.tab:hover { color: var(--ink); }
.tab.active { color: var(--ink); border-bottom-color: var(--signal); }
.tab.active .tab-index { color: var(--signal); }

/* ============================================================
   layout
   ============================================================ */
main { flex: 1; padding-top: 34px; padding-bottom: 72px; }
.view { display: none; }
.view.active { display: block; animation: rise .2s ease-out; }

@keyframes rise {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}

.lede {
  border-left: 3px solid var(--signal);
  padding: 2px 0 2px 18px;
  margin-bottom: 32px;
}
.lede h2 {
  font-family: var(--font-poster);
  font-size: 2rem; font-weight: 400;
  text-transform: uppercase; letter-spacing: .005em; line-height: 1.05;
  margin-bottom: 8px;
}
.lede p { color: var(--ink-mid); }

.section-title {
  font-family: var(--font-poster);
  font-size: 1.35rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: .01em;
  margin: 40px 0 4px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--rule);
  display: flex;
  align-items: baseline;
  gap: 12px;
}
.section-title .section-num { color: var(--signal); }
.section-sub { color: var(--ink-dim); font-size: .85rem; margin: 10px 0 16px; }
.section-title + .day-board,
.section-title + .priority-table,
.section-title + .tips { margin-top: 16px; }

/* ============================================================
   toolbar
   ============================================================ */
.toolbar {
  border: var(--border);
  border-radius: var(--radius);
  margin-bottom: 24px;
  background: var(--ground-2);
}
.toolbar-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--rule-soft);
  flex-wrap: wrap;
}
.toolbar-row:last-child { border-bottom: none; }
.toolbar-row .row-label { flex: 0 0 76px; padding-top: 1px; }

.toolbar-search { padding: 0; gap: 0; }
.field { display: flex; flex-direction: column; gap: 0; }
.field-search { flex: 1; min-width: 240px; }
.field-sort { border-left: 1px solid var(--rule-soft); }
.field-label { padding: 9px 16px 0; }

#search, #sort {
  background: none;
  border: none;
  color: var(--ink);
  font: inherit;
  font-size: .95rem;
  padding: 2px 16px 11px;
  width: 100%;
}
#search::placeholder { color: var(--ink-dim); }
#search:focus-visible, #sort:focus-visible { outline-offset: -2px; }
#sort { cursor: pointer; font-size: .88rem; padding-right: 16px; }
#sort option { background: var(--ground-2); color: var(--ink); }

/* Collapsible filter drawer — open by default on desktop, closed on a phone
   where the full panel would otherwise fill the whole first screen. */
.toolbar-more { border-top: 1px solid var(--rule-soft); }
.toolbar-summary {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.toolbar-summary::-webkit-details-marker { display: none; }
.toolbar-summary .row-label { flex: 0 0 76px; }
.toolbar-summary::after {
  content: "";
  width: 7px; height: 7px;
  border-right: 1.5px solid var(--ink-dim);
  border-bottom: 1.5px solid var(--ink-dim);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .15s;
  order: -1;
  margin-right: -6px;
}
.toolbar-more[open] > .toolbar-summary::after { transform: rotate(-135deg) translate(-3px, -3px); }
.toolbar-more[open] > .toolbar-summary { border-bottom: 1px solid var(--rule-soft); }
.toolbar-summary:hover .filter-summary { color: var(--ink); }

.filter-summary { font-size: .84rem; color: var(--ink-mid); }
.filter-summary[data-active="true"] { color: var(--signal); font-weight: 600; }

.filter-row { display: flex; gap: 6px; flex-wrap: wrap; flex: 1; }

.chip {
  background: none;
  border: 1px solid var(--rule);
  color: var(--ink-mid);
  border-radius: var(--radius-sm);
  padding: 4px 11px;
  font-family: var(--font-ui);
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
  transition: color .12s, border-color .12s, background .12s;
}
.chip:hover { color: var(--ink); border-color: var(--ink-dim); }
.chip.active { background: var(--ink); border-color: var(--ink); color: var(--ground); }
.chip.hall-chip { font-family: var(--font-mono); font-weight: 500; font-size: .75rem; }
.chip.hall-chip.active,
.chip.age-chip.active { background: var(--signal); border-color: var(--signal); color: var(--signal-ink); }
/* Business-area halls, offered only in trade mode. Amber is the guide's
   badge-required colour throughout — the directory's stand plates and the
   map's hall groups carry the same one. */
.chip.hall-chip-trade { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 34%, var(--rule)); }
.chip.hall-chip-trade:hover { border-color: var(--warn); }
.chip.hall-chip-trade.active { background: var(--warn); border-color: var(--warn); color: var(--signal-ink); }
/* The badge pair sits above the filter rows and carries the same amber on its
   trade side, so the switch and the halls it unlocks read as one idea. */
.chip.badge-chip-trade { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 34%, var(--rule)); }
.chip.badge-chip-trade:hover { border-color: var(--warn); }
.chip.badge-chip-trade.active { background: var(--warn); border-color: var(--warn); color: var(--signal-ink); }

.toggle {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--ink-mid); font-size: .84rem; cursor: pointer; user-select: none;
}
.toggle:hover { color: var(--ink); }
.toggle input { accent-color: var(--signal); width: 15px; height: 15px; }

.reset {
  background: none; border: none; color: var(--signal);
  font: inherit; font-size: .8rem; font-weight: 600;
  cursor: pointer; text-decoration: underline; text-underline-offset: 3px;
  padding: 0;
}
.toolbar-foot { padding-top: 14px; padding-bottom: 14px; }
.result-count {
  margin-left: auto;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: .74rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   exhibitor cards
   ============================================================ */
/* Cards are discrete printed plates, not cells of one table: real gaps plus a
   hard un-blurred offset shadow. Blurred shadows read as generic material
   elevation; a hard offset reads as something laid on top of the page. */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
  gap: 20px;
}

.card {
  min-width: 0;
  background: var(--ground-2);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--plate-shadow);
  padding: 0;
  display: flex;
  flex-direction: column;
  transition: background .12s, box-shadow .12s, transform .12s;
}
/* Arriving from the hall map's "open in guide": say which of the 53 cards
   you were sent to, then get out of the way. The global reduced-motion
   rule collapses this to an instant, which is the right amount of it. */
@keyframes card-landed {
  from { box-shadow: 0 0 0 2px var(--signal), var(--plate-shadow); }
  to   { box-shadow: 0 0 0 2px transparent, var(--plate-shadow); }
}
.card-landed { animation: card-landed 1.8s ease-out; }

.card:hover {
  background: var(--ground-3);
  transform: translate(-2px, -2px);
  box-shadow: var(--plate-shadow-hover);
}

/* --- head: the hall number as a signage plate --- */
.exh-head { display: flex; gap: 0; border-bottom: 1px solid var(--rule-soft); }

/* The plate's box. It only exists so the face switch has a corner to sit in;
   without a second booth it is invisible and the plate behaves as it always
   did. */
.hall-plate { flex: 0 0 108px; position: relative; display: flex; }
.hall-marker {
  flex: 1 1 auto;
  padding: 14px 12px;
  background: var(--signal);
  color: var(--signal-ink);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
}
.hall-marker[data-state="unconfirmed"] { background: var(--warn); }
.hall-marker[data-state="tba"],
.hall-marker[data-state="absent"] { background: var(--ground-3); color: var(--ink-dim); }

/* --- the two faces of a card ---

   A booth in the business halls is drawn in Koelnmesse's own colour for that
   area (#7800FF, the same fill the map washes those halls with), so the plate
   itself says which show you are looking at. Note the ink inverts: the orange
   plate needs near-black (5.93:1, white would be 3.32:1) and the purple one
   needs the warm off-white (5.64:1, near-black would be 3.04:1). */
.hall-marker-trade .hall-marker,
.hall-marker-trade .hall-marker[data-state="unconfirmed"] {
  background: var(--business);
  color: var(--ink);
}

/* The way to the other booth, notched into the plate's corner: the corner a
   thumb is already near, and close enough to the big plate that the swap
   reads as an exchange rather than a jump across the card.

   Top-right rather than bottom-right, because the two faces carry different
   booth numbers and a long one wraps to a second line — anchored to the
   bottom, the square would sit at a different height on each face and move
   out from under the finger that just tapped it. The top edge does not move. */
.face-switch {
  position: absolute;
  right: 0;
  top: 0;
  width: 34px;
  height: 30px;
  padding: 0;
  border: 0;
  border-bottom: 1px solid var(--ground);
  border-left: 1px solid var(--ground);
  background: var(--signal);
  color: var(--signal-ink);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  transition: filter .12s;
}
.face-switch-trade { background: var(--business); color: var(--ink); }
.face-switch:hover { filter: brightness(1.18); }
.face-switch:focus-visible { outline: 2px solid var(--ink); outline-offset: -3px; }
/* The other side's saved state, so a saved trade stop is not invisible until
   you turn the card over. Same dot the map's hall chips use. */
.face-saved {
  position: absolute;
  bottom: 3px;
  right: 3px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
}
.face-switch:not([data-face-saved="true"]) .face-saved { display: none; }

/* The plate doubles as the link to that booth on the hall map when the
   map covers its hall (see hallMarker) — an anchor wearing the same
   plate, not a new control. */
a.hall-marker { text-decoration: none; position: relative; }
.hall-map-cue {
  font-family: var(--font-mono);
  font-size: .56rem; letter-spacing: .1em; text-transform: uppercase;
  opacity: .62;
  margin-top: 2px;
}
a.hall-marker:hover .hall-map-cue,
a.hall-marker:focus-visible .hall-map-cue { opacity: 1; }

/* The same link, in running text: every hall named in the planner rows opens
   that hall on the map (see hallLink). These sit inside dense lists, so the
   affordance is a quiet underline rather than another plate — it keeps the
   row's own colour until you reach for it, then takes the accent. */
.hall-link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.hall-link:hover,
.hall-link:focus-visible { color: var(--signal); text-decoration-color: currentColor; }

.hall-kicker {
  font-family: var(--font-mono);
  font-size: .58rem; letter-spacing: .16em; text-transform: uppercase;
  color: inherit; opacity: .7;
}
.hall-num {
  font-family: var(--font-mono);
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.04em;
  font-variant-numeric: tabular-nums;
}
.hall-marker[data-state="tba"] .hall-num,
.hall-marker[data-state="absent"] .hall-num { font-size: 1rem; letter-spacing: 0; }
/* A plate can now carry several stands — Nintendo holds four — and the
   comma is where the line is allowed to turn. break-all broke wherever
   the 108 px ran out, which split "A020/B019" after its first character
   and left a booth number nobody could read; break-word keeps each stand
   whole and only breaks inside one that could not fit a line on its own. */
.hall-booth {
  font-family: var(--font-mono);
  font-size: .68rem;
  color: inherit; opacity: .78;
  overflow-wrap: break-word;
}

.exh-id { padding: 14px 16px; display: flex; flex-direction: column; gap: 2px; justify-content: center; min-width: 0; }
.exh-id h3 {
  font-family: var(--font-display);
  font-size: 1.18rem;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -.005em;
}

.card-body { padding: 14px 16px; display: flex; flex-direction: column; gap: 14px; flex: 1; }
.desc { color: var(--ink-mid); font-size: .87rem; }

/* --- lineup block --- */
.block-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding-bottom: 6px;
  margin-bottom: 8px;
  border-bottom: 1px solid var(--rule-soft);
}

.games { list-style: none; display: flex; flex-direction: column; }
.game {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  gap: 2px 10px;
  padding: 7px 0 7px 14px;
  border-top: 1px solid var(--rule-soft);
  position: relative;
  font-size: .87rem;
}
.game:first-child { border-top: none; }
.game::before {
  content: "";
  position: absolute; left: 0; top: 13px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--neutral);
}
.game[data-status="confirmed"]::before { background: var(--ok); }
.game[data-status="expected"]::before  { background: var(--warn); }
.game[data-status="rumored"]::before   { background: var(--neutral); }

.game-title { font-weight: 600; }
.game-main { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; min-width: 0; }
.game-plat {
  font-family: var(--font-mono);
  font-size: .64rem;
  letter-spacing: .06em;
  color: var(--ink-dim);
  white-space: nowrap;
  align-self: center;
}
.game-note { grid-column: 1 / -1; color: var(--ink-dim); font-size: .78rem; line-height: 1.45; }

.badge {
  font-family: var(--font-mono);
  font-size: .6rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .1em;
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  border: 1px solid transparent;
}
.badge-playable { color: var(--signal); border-color: var(--signal-wash); background: var(--signal-wash); }
.badge-age { background: transparent; }
.badge-age[data-age-status="confirmed"] { color: var(--alert); border-color: var(--alert); }
.badge-age[data-age-status="expected"] { color: var(--warn); border-color: var(--warn); }
.block-head .badge-age { margin-left: 9px; }
.exh-id h3 .badge-age { margin-left: 6px; vertical-align: .2em; }

/* The one hand-applied element in the layout, and deliberately the only one:
   once per card it reads as a stamp, but repeated down every game row the
   tilts land at different x positions and read as jitter. */
.stamp {
  display: inline-block;
  margin-left: 9px;
  background: var(--signal);
  color: var(--signal-ink);
  font-family: var(--font-mono);
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: var(--radius-sm);
  transform: rotate(-2.5deg);
  box-shadow: 2px 2px 0 rgba(0, 0, 0, .45);
}
.badge-status[data-status="expected"] { color: var(--warn); border-color: rgba(232,163,58,.25); }
.badge-status[data-status="rumored"]  { color: var(--neutral); border-color: var(--rule); }

.more-games {
  align-self: flex-start;
  margin-top: 8px;
  background: none; border: none; padding: 0;
  color: var(--signal);
  font-family: var(--font-mono);
  font-size: .7rem; letter-spacing: .08em; text-transform: uppercase;
  cursor: pointer;
}
.more-games:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ============================================================
   full official directory
   ------------------------------------------------------------
   Deliberately not a second grid: cards are the edited guide, this is the raw
   list underneath it. It reads as a register — mono booth numbers, ruled rows,
   no plate shadow — so nothing about it competes with the cards above.
   ============================================================ */

.directory {
  margin-top: 34px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--ground-2);
}
.directory-summary {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 16px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.directory-summary::-webkit-details-marker { display: none; }
.directory-summary::after {
  content: "";
  width: 7px; height: 7px;
  border-right: 1.5px solid var(--ink-dim);
  border-bottom: 1.5px solid var(--ink-dim);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .15s;
  order: -1;
  margin-right: -6px;
}
.directory[open] > .directory-summary::after { transform: rotate(-135deg) translate(-3px, -3px); }
.directory[open] > .directory-summary { border-bottom: 1px solid var(--rule-soft); }
.directory-lede { font-size: .84rem; color: var(--ink-mid); min-width: 0; }
.directory-summary:hover .directory-lede { color: var(--ink); }

.directory-note {
  margin: 0;
  padding: 12px 16px 0;
  font-size: .8rem;
  color: var(--ink-dim);
  line-height: 1.5;
}
.directory-note:empty { display: none; }

.dir-list {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
}
.dir-row {
  display: grid;
  /* name takes the slack; country and stands stay in their own columns so the
     booth numbers line up into a readable rail down the right */
  grid-template-columns: minmax(0, 1fr) minmax(0, 130px) minmax(0, 250px);
  gap: 6px 14px;
  align-items: baseline;
  padding: 8px 16px;
  border-top: 1px solid var(--rule-soft);
  font-size: .85rem;
}
.dir-row:first-child { border-top: none; }
.dir-row:hover { background: var(--ground-3); }

.dir-name { min-width: 0; color: var(--ink); }
.dir-name a { color: inherit; text-decoration: none; }
.dir-name a:hover { color: var(--signal); text-decoration: underline; text-underline-offset: 3px; }
.dir-country { color: var(--ink-dim); font-size: .78rem; }

.dir-stands { display: flex; flex-wrap: wrap; gap: 4px 10px; min-width: 0; }
.dir-stand {
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .04em;
  color: var(--ink-mid);
  white-space: nowrap;
}
.dir-stand b { color: var(--ink); font-weight: 700; }
/* A stand in a hall the map draws is a link on the whole chip — the
   underline rides on the hall/booth alone, so the "at <host>" tail stays
   a note rather than looking like part of the destination. */
a.dir-stand { text-decoration: none; }
a.dir-stand .dir-stand-where {
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
a.dir-stand:hover .dir-stand-where,
a.dir-stand:focus-visible .dir-stand-where { text-decoration-color: currentColor; }
a.dir-stand:hover,
a.dir-stand:focus-visible { color: var(--signal); }
a.dir-stand:hover b,
a.dir-stand:focus-visible b { color: var(--signal); }
/* which curated booth this stand actually sits on — the 172 Indie Arena Booth
   studios are meaningless without it */
.dir-stand i {
  font-style: normal;
  font-family: var(--font-ui);
  color: var(--ink-dim);
  white-space: normal;
}
.dir-stand-trade { color: var(--ink-dim); }
.dir-stand-trade b { color: var(--warn); }
.dir-stand-tba { color: var(--ink-dim); }

.dir-more {
  display: block;
  margin: 14px 16px;
  font-family: var(--font-mono);
  font-size: .74rem;
  letter-spacing: .08em;
  text-transform: uppercase;
}

@media (max-width: 640px) {
  .dir-row { grid-template-columns: minmax(0, 1fr); gap: 3px; }
  .dir-country { font-size: .74rem; }
}

/* ============================================================
   trade exhibitors
   ------------------------------------------------------------
   The same register as the directory above — same rows, same booth rail —
   with two things the directory deliberately doesn't have: a save button, and
   a category tag row. Amber runs through it because amber is already the
   guide's "you need a badge for this" colour, set on the directory's business
   stand plates and on the map's hall groups.
   ============================================================ */

.trade-section { border-color: color-mix(in srgb, var(--warn) 26%, var(--rule)); }
.trade-section > .directory-summary .row-label { color: var(--warn); }

/* Closed, this is the whole feature: what the business area is, and the one
   switch. It reads as prose rather than a settings row on purpose. */
.trade-gate { padding: 14px 16px 16px; }
.trade-gate p {
  margin: 0 0 10px;
  font-size: .86rem;
  line-height: 1.6;
  color: var(--ink-mid);
  max-width: 68ch;
}
.trade-gate strong { color: var(--ink); font-weight: 700; }
.trade-enable {
  margin-top: 4px;
  padding: 10px 16px;
  border: 1px solid var(--warn);
  border-radius: 999px;
  background: color-mix(in srgb, var(--warn) 12%, transparent);
  color: var(--warn);
  font-family: var(--font-mono);
  font-size: .76rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  cursor: pointer;
}
.trade-enable:hover { background: color-mix(in srgb, var(--warn) 22%, transparent); }

/* Turning it back off lives in the note it belongs to, not in a toolbar. */
.linkish {
  border: 0;
  padding: 0;
  background: none;
  color: var(--ink-mid);
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--rule);
  cursor: pointer;
  white-space: nowrap;
}
.linkish:hover { color: var(--signal); text-decoration-color: currentColor; }

.trade-cats { padding: 12px 16px 0; }

/* One more column than a directory row (tags) and the save button riding the
   end of the first row, so the booth rail still lines up down the list. */
.trade-row {
  grid-template-columns: minmax(0, 1fr) minmax(0, 110px) minmax(0, 230px) auto;
  align-items: baseline;
}
/* Tags run under the name on their own row; the save button is pinned to the
   end of the first row so the right rail stays a rail rather than the button
   being pushed below the tags. */
.trade-row .trade-tags {
  grid-column: 1 / -1;
  grid-row: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.trade-row > .bm { grid-column: 4; grid-row: 1; justify-self: end; }
.trade-row .trade-tags:empty { display: none; }
/* A count, not a verdict — how many exhibitors list this stand. */
.trade-shared {
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .05em;
  padding: 1px 6px;
  border: 1px solid color-mix(in srgb, var(--ok) 34%, var(--rule));
  border-radius: 999px;
  color: var(--ok);
  white-space: nowrap;
  cursor: help;
}
.trade-row .trade-tags .tag { font-size: .64rem; }
.trade-row[data-saved="true"] { background: color-mix(in srgb, var(--signal) 7%, transparent); }

@media (max-width: 640px) {
  .trade-row { grid-template-columns: minmax(0, 1fr) auto; }
  .trade-row .dir-country { grid-column: 1; grid-row: 2; }
  .trade-row .dir-stands { grid-column: 1; grid-row: 3; }
  .trade-row .trade-tags { grid-row: 4; }
  .trade-row > .bm { grid-column: 2; grid-row: 1; }
}

/* ---- curated trade cards ---- */

/* The Lineup block's opposite number: what the booth is *for*. */
.offers {
  list-style: none;
  margin: 0;
  padding: 0;
}
.offers li {
  padding: 7px 12px;
  border-top: 1px solid var(--rule-soft);
  font-size: .84rem;
  line-height: 1.45;
  color: var(--ink-mid);
}
.offers li:first-child { border-top: none; }
.offers li::before {
  content: "→";
  margin-right: 8px;
  color: var(--warn);
  font-family: var(--font-mono);
}

/* Where a consumer card puts the queue index. Same question, different
   answer: not "how long is the wait" but "is there anything to walk up to". */
.trade-access {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 10px;
  align-items: baseline;
}
.trade-access-val {
  font-family: var(--font-mono);
  font-size: .78rem;
  letter-spacing: .04em;
  color: var(--warn);
  text-transform: uppercase;
}
.trade-access[data-access="open"] .trade-access-val { color: var(--ok); }
.trade-access-note {
  grid-column: 2;
  font-size: .78rem;
  line-height: 1.5;
  color: var(--ink-dim);
}

/* ============================================================
   saved & played marks
   ------------------------------------------------------------
   Saved is the visitor's own emphasis, so it takes the signal colour when set —
   that is what the one accent is for. Its "+" and "−" use the same language as
   "+ 4 more" above. Played deliberately does not take the signal colour: done
   is the opposite of emphasis, so its set plate and related content stay muted.
   ============================================================ */
.bm {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  padding: 0;
  background: none;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  color: var(--ink-dim);
  font-family: var(--font-mono);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color .12s, border-color .12s, background .12s, opacity .12s;
}
.bm:hover { color: var(--ink); border-color: var(--ink-dim); }
.bm[aria-pressed="true"] {
  background: var(--signal);
  border-color: var(--signal);
  color: var(--signal-ink);
}
.bm[aria-pressed="true"]:hover { background: #ff6b3b; border-color: #ff6b3b; color: var(--signal-ink); }
.bm[data-mark="played"][aria-pressed="true"],
.bm[data-mark="played"][aria-pressed="true"]:hover {
  background: var(--ink-dim);
  border-color: var(--ink-dim);
  color: var(--ground);
}
.bm-mark { font-size: .9rem; line-height: 1; }

/* card head: a plate on the right edge, bracketing the hall plate on the left */
.bm-wide { width: auto; height: auto; padding: 0 15px; }
.exh-head .bm {
  align-self: stretch;
  height: auto;
  margin-left: auto;
  border: none;
  border-left: 1px solid var(--rule-soft);
  border-radius: 0;
}
.exh-head .bm:not(.bm-wide) { width: 28px; }
.exh-head .bm + .bm { margin-left: 0; }
.card[data-saved="true"] { border-color: var(--signal); }
.card[data-played="true"] { border-color: var(--rule); }
.card[data-played="true"] .card-body { opacity: .62; }

/* Repeated down a 23-game lineup the buttons would out-shout the titles, so
   they sit back until you reach for one — or until they're set. */
.game .bm { width: 18px; height: 18px; align-self: center; opacity: .45; }
.game:hover .bm,
.game .bm:focus-visible,
.game .bm[aria-pressed="true"] { opacity: 1; }
.game[data-saved="true"] .game-title { color: var(--signal); }
.game[data-played="true"] .game-title,
.game[data-played="true"] .game-plat { color: var(--ink-dim); }
.game[data-played="true"]::before { background: var(--neutral); }

.saved-count {
  font-family: var(--font-mono);
  font-size: .72rem;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}
.toggle input:checked ~ .saved-count { color: var(--signal); }

.tag-row { display: flex; gap: 5px; flex-wrap: wrap; }
.tag {
  font-family: var(--font-mono);
  font-size: .66rem;
  color: var(--ink-dim);
  border: 1px solid var(--rule);
  padding: 1px 7px;
  border-radius: var(--radius-sm);
}

/* --- foot: queue index gauge --- */
.card-foot {
  margin-top: auto;
  padding: 12px 16px;
  border-top: 1px solid var(--rule-soft);
  background: rgba(0, 0, 0, .18);
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.queue { display: flex; align-items: center; gap: 12px; }
.queue-val {
  font-family: var(--font-mono);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  margin-left: auto;
}
.meter { display: flex; gap: 2px; }
.meter i { width: 22px; height: 6px; background: var(--rule); border-radius: 1px; }
.meter[data-level="1"] i:nth-child(-n+1),
.meter[data-level="2"] i:nth-child(-n+2) { background: var(--ok); }
.meter[data-level="3"] i:nth-child(-n+3) { background: var(--warn); }
.meter[data-level="4"] i:nth-child(-n+4),
.meter[data-level="5"] i:nth-child(-n+5) { background: var(--alert); }

.queue[data-level="1"] .queue-val, .queue[data-level="2"] .queue-val { color: var(--ok); }
.queue[data-level="3"] .queue-val { color: var(--warn); }
.queue[data-level="4"] .queue-val, .queue[data-level="5"] .queue-val { color: var(--alert); }

.advice { font-size: .82rem; color: var(--ink-mid); line-height: 1.5; }
.advice-label {
  font-family: var(--font-mono);
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--signal); margin-right: 7px;
}

/* Leaving the guide is a deliberate act, so the link sits quiet under the
   plan and only lights up on hover — it must not compete with the queue gauge. */
.foot-links { display: flex; align-items: center; gap: 14px; }
.official-link {
  font-family: var(--font-mono);
  font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-dim);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 1px;
}
.official-link:hover { color: var(--signal); border-bottom-color: var(--signal); }

/* The attribution marker. It has to be there on every card without becoming
   the thing you read, so it sits at the far end of the foot in the quietest
   ink on the page — the count is the whole label, and the accessible name
   spells out what it opens. margin-left:auto rather than an order rule so it
   still lands on the right when a card has no official page to sit beside. */
.src-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: auto;
  flex: 0 0 auto;
  /* Quiet, but still a comfortable thumb target — the padding grows the box
     without adding any ink. */
  min-height: 24px;
  padding: 2px 7px 2px 5px;
  background: none;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  color: var(--ink-dim);
  font-family: var(--font-mono);
  font-size: .64rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.src-btn:hover { color: var(--ink); border-color: var(--ink-dim); }
.src-glyph {
  display: inline-grid;
  place-items: center;
  width: 13px; height: 13px;
  border: 1px solid currentColor;
  border-radius: 50%;
  font-size: .56rem;
  font-weight: 700;
  line-height: 1;
}

/* ============================================================
   planner — day board
   ============================================================ */
.day-board { border: var(--border); border-radius: var(--radius); overflow: hidden; }
.day-row {
  display: grid;
  grid-template-columns: 92px 130px 1fr;
  gap: 0 18px;
  align-items: baseline;
  padding: 14px 18px;
  border-bottom: 1px solid var(--rule-soft);
  background: var(--ground-2);
}
.day-row:last-child { border-bottom: none; }
.day-row:hover { background: var(--ground-3); }

.day-when { display: flex; flex-direction: column; }
.day-dow {
  font-family: var(--font-poster);
  font-size: 1.45rem; font-weight: 400; line-height: 1;
  text-transform: uppercase; letter-spacing: .01em;
}
.day-date {
  font-family: var(--font-mono);
  font-size: .7rem; color: var(--ink-dim); font-variant-numeric: tabular-nums;
}
.day-access {
  font-family: var(--font-mono);
  font-size: .64rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  padding: 3px 8px; border-radius: var(--radius-sm); justify-self: start;
  border: 1px solid;
}
.day-access.public { color: var(--ok); border-color: rgba(47,191,133,.3); background: rgba(47,191,133,.08); }
.day-access.trade  { color: var(--alert); border-color: rgba(242,86,79,.3); background: rgba(242,86,79,.08); }

.day-detail { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.day-hours { font-family: var(--font-mono); font-size: .74rem; color: var(--ink); font-variant-numeric: tabular-nums; }
.day-note { color: var(--ink-mid); font-size: .85rem; }

/* ============================================================
   planner — your plan, shared shell (see the lens blocks below)
   ============================================================ */
#plan-section { scroll-margin-top: 140px; }
.plan-controls {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin: -4px 0 12px;
}
.plan-controls .result-count { margin-left: auto; }
.plan-lens { display: flex; gap: 6px; }
.plan-days {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
  margin: -2px 0 12px;
}

/* ============================================================
   planner — your plan, day lens (itinerary)
   ============================================================ */
.it-board { border: var(--border); border-radius: var(--radius); overflow: hidden; }
.it-group + .it-group { border-top: 1px solid var(--rule); }
.it-group-head {
  display: grid;
  grid-template-columns: 92px 130px 1fr;
  gap: 0 18px;
  align-items: baseline;
  padding: 11px 18px;
  background: var(--ground-3);
  border-bottom: 1px solid var(--rule);
}
.it-group-head.unassigned { display: block; }
.it-group-title {
  font-family: var(--font-mono);
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-mid);
}

.it-item {
  display: grid;
  grid-template-columns: minmax(150px, 1.1fr) minmax(180px, 1.35fr) auto 20px;
  gap: 0 16px;
  align-items: center;
  padding: 12px 18px;
  border-bottom: 1px solid var(--rule-soft);
  background: var(--ground-2);
}
.it-item:last-child { border-bottom: none; }
.it-item:hover { background: var(--ground-3); }
.it-item[data-played="true"] {
  opacity: .62;
  box-shadow: inset 3px 0 0 var(--ink-dim);
}
.it-main { display: flex; flex-direction: column; min-width: 0; }
.it-kind {
  font-family: var(--font-mono);
  font-size: .62rem;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.it-name { font-weight: 700; font-size: .95rem; }
.it-loc {
  font-family: var(--font-mono);
  font-size: .72rem;
  color: var(--ink-mid);
  font-variant-numeric: tabular-nums;
}
.it-days { display: flex; gap: 5px; flex-wrap: wrap; justify-content: flex-end; }
.day-chip {
  background: none;
  border: 1px solid var(--rule);
  color: var(--ink-mid);
  border-radius: var(--radius-sm);
  padding: 3px 8px;
  font-family: var(--font-mono);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color .12s, border-color .12s, background .12s;
}
.day-chip:hover { color: var(--ink); border-color: var(--ink-dim); }
.day-chip.active { background: var(--signal); border-color: var(--signal); color: var(--signal-ink); }
.day-chip[data-trade="true"]:not(.active) { border-style: dashed; color: var(--ink-dim); }
/* A day the business area is shut, offered to a business-area stop. Struck
   through rather than disabled: the guide warns, it does not forbid — you may
   well want to walk past a stand on Saturday. */
.day-chip[data-closed="true"]:not(.active) { color: var(--ink-dim); text-decoration: line-through; }
.day-chip[data-closed="true"].active {
  background: var(--warn);
  border-color: var(--warn);
  color: var(--signal-ink);
}
.it-item .bm { grid-column: -2 / -1; grid-row: 1; align-self: center; }

/* The consequence spelled out, on the row and once at the top of the day. */
.it-warn {
  grid-column: 1 / -1;
  margin-top: 8px;
  padding-left: 11px;
  border-left: 2px solid var(--warn);
  font-size: .76rem;
  line-height: 1.45;
  color: var(--warn);
}
.it-group-warn {
  margin: 0;
  padding: 9px 18px;
  border-bottom: 1px solid var(--rule-soft);
  background: color-mix(in srgb, var(--warn) 10%, var(--ground-2));
  font-size: .78rem;
  color: var(--warn);
}

/* ============================================================
   planner — priority table
   ============================================================ */
.priority-controls {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.priority-controls .result-count { margin-left: auto; }

.priority-table { border: var(--border); border-radius: var(--radius); overflow: hidden; }
.priority-item {
  display: grid;
  grid-template-columns: 40px minmax(150px, 1.1fr) 96px 2fr auto;
  gap: 0 16px;
  align-items: baseline;
  padding: 12px 18px;
  border-bottom: 1px solid var(--rule-soft);
  background: var(--ground-2);
}
/* the last column in either layout, so no override is needed on a phone */
.row-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}
.priority-item .row-actions { grid-column: -2 / -1; grid-row: 1; align-self: center; }
.priority-item[data-saved="true"] { box-shadow: inset 3px 0 0 var(--signal); }
.priority-item[data-played="true"] {
  opacity: .62;
  box-shadow: inset 3px 0 0 var(--ink-dim);
}

/* Which of your saved games this booth is actually running — the reason a
   publisher row is on your list at all. */
.priority-saved {
  grid-column: 2 / -1;
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 5px;
}
.priority-saved .row-label { color: var(--signal); flex: 0 0 auto; }
.priority-game {
  font-family: var(--font-mono);
  font-size: .72rem;
  color: var(--ink);
  border: 1px solid var(--signal-wash);
  background: var(--signal-wash);
  padding: 1px 7px;
  border-radius: var(--radius-sm);
}
.priority-item:last-child { border-bottom: none; }
.priority-item:hover { background: var(--ground-3); }
.priority-rank {
  font-family: var(--font-mono);
  font-weight: 700; font-size: .95rem;
  color: var(--signal);
  font-variant-numeric: tabular-nums;
}
.priority-name,
.wristband-name { font-weight: 700; font-size: .95rem; }
.priority-name .badge-age { margin-left: 6px; vertical-align: .1em; }
.priority-loc,
.wristband-loc {
  font-family: var(--font-mono);
  font-size: .76rem; color: var(--ink-mid);
  font-variant-numeric: tabular-nums;
}
.priority-advice,
.wristband-games { color: var(--ink-mid); font-size: .84rem; }

/* Same row language as queue priority, without the rank column. Four tracks, and
   every row fills all four — the "18+ expected" marker rides inside the titles
   cell, because each row is its own grid and a column only some rows fill would
   size their fr tracks differently. */
.wristband-item {
  grid-template-columns: minmax(150px, 1.1fr) minmax(130px, .8fr) minmax(200px, 2fr) auto;
}
.wristband-status { margin-left: 4px; vertical-align: baseline; }

/* ============================================================
   planner — your plan, hall lens (route)
   ============================================================ */
.route-board { border: var(--border); border-radius: var(--radius); overflow: hidden; }
.route-hall {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 9px 18px;
  border-bottom: 1px solid var(--rule-soft);
  background: var(--ground-3);
}
.route-hall-kicker {
  font-family: var(--font-mono);
  font-size: .64rem;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.route-hall-num {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.route-hall-map {
  font-family: var(--font-mono);
  font-size: .64rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-dim); text-decoration: none;
  border: 1px solid var(--rule); border-radius: var(--radius-sm);
  padding: 2px 7px;
}
.route-hall-map:hover, .route-hall-map:focus-visible { color: var(--ink); border-color: var(--ink-dim); }
.route-hall-count {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

.route-item {
  display: grid;
  grid-template-columns: minmax(150px, 1.2fr) minmax(110px, 1fr) auto auto;
  gap: 0 16px;
  align-items: baseline;
  padding: 12px 18px;
  border-bottom: 1px solid var(--rule-soft);
  background: var(--ground-2);
}
.route-item:last-child { border-bottom: none; }
.route-item:hover { background: var(--ground-3); }
.route-item[data-saved="true"] { box-shadow: inset 3px 0 0 var(--signal); }
.route-item[data-played="true"] {
  opacity: .62;
  box-shadow: inset 3px 0 0 var(--ink-dim);
}
.route-item .row-actions { grid-column: -2 / -1; grid-row: 1; align-self: center; }
.route-item .priority-saved { grid-column: 1 / -1; }
.route-name { font-weight: 700; font-size: .95rem; }
/* the day a stop is planned for, read straight off the itinerary */
.route-day {
  font-family: var(--font-mono);
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--signal);
  background: var(--signal-wash);
  border-radius: var(--radius-sm);
  padding: 2px 6px;
  margin-left: 8px;
  vertical-align: .14em;
  white-space: nowrap;
}
.route-booth {
  font-family: var(--font-mono);
  font-size: .76rem;
  color: var(--ink-mid);
  font-variant-numeric: tabular-nums;
}
.route-crowd {
  font-family: var(--font-mono);
  font-size: .72rem;
  font-weight: 700;
  color: var(--ink-mid);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.route-crowd[data-level="4"] { color: var(--warn); }
.route-crowd[data-level="5"] { color: var(--alert); }
.route-absent {
  margin-top: 12px;
  color: var(--ink-dim);
  font-size: .82rem;
}

/* ============================================================
   planner — tips
   ============================================================ */
.tips { list-style: none; counter-reset: tip; border-top: 1px solid var(--rule); }
.tips li {
  counter-increment: tip;
  position: relative;
  padding: 13px 0 13px 46px;
  border-bottom: 1px solid var(--rule-soft);
  color: var(--ink-mid);
  font-size: .9rem;
}
.tips li::before {
  content: counter(tip, decimal-leading-zero);
  position: absolute; left: 0; top: 13px;
  font-family: var(--font-mono);
  font-size: .74rem; font-weight: 700;
  color: var(--signal);
}

/* ============================================================
   event info
   ============================================================ */
.info-block { margin-bottom: 34px; }
.info-block > h2 {
  font-family: var(--font-poster);
  font-size: 1.35rem; font-weight: 400;
  text-transform: uppercase; letter-spacing: .01em;
  padding-bottom: 8px; margin-bottom: 14px;
  border-bottom: 1px solid var(--rule);
  display: flex; align-items: baseline; gap: 12px;
}
.info-block > h2 .section-num { color: var(--signal); }
.info-block p {
  color: var(--ink-mid);
  font-size: .9rem;
}
.info-block p + p { margin-top: 10px; }
.info-block b, .info-block strong { color: var(--ink); font-weight: 600; }
.info-block a { color: var(--ink); text-decoration: underline; text-decoration-color: var(--signal); text-underline-offset: 3px; }
.info-block a:hover { color: var(--signal); }

.info-block .headline-fact {
  font-family: var(--font-poster);
  font-size: 2.1rem; font-weight: 400; line-height: 1.1;
  text-transform: uppercase; letter-spacing: .005em;
  color: var(--ink);
  margin-bottom: 6px;
}
.info-block .fact-sub {
  font-family: var(--font-mono);
  font-size: .78rem; color: var(--ink-dim); letter-spacing: .04em;
}
/* The same switch as the toolbar's, offered again where the other badge and
   access facts live — the semantically right home for a setting that
   describes you rather than the list, and reachable from any view. */
#badge-toggle { margin-top: 14px; }
/* Same pill as .trade-enable so the two states read as one control, in plain
   ink rather than amber — turning it off is not the offer being made. */
.badge-off {
  padding: 10px 16px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: none;
  color: var(--ink-mid);
  font-family: var(--font-mono);
  font-size: .76rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  cursor: pointer;
}
.badge-off:hover { border-color: var(--ink-dim); color: var(--ink); }

.area-list { list-style: none; border-top: 1px solid var(--rule-soft); }
.area-list li {
  display: grid;
  grid-template-columns: 76px 1fr;
  gap: 0 16px;
  padding: 11px 0;
  border-bottom: 1px solid var(--rule-soft);
  align-items: baseline;
}
.area-hall {
  font-family: var(--font-mono);
  font-size: .8rem; font-weight: 700; color: var(--signal);
  font-variant-numeric: tabular-nums;
}
.area-hall.none { color: var(--ink-dim); font-weight: 400; }
.area-name { font-weight: 600; font-size: .92rem; }
.area-desc { color: var(--ink-mid); font-size: .85rem; }

/* Entrance names are words, not hall numbers — drop the tabular figures and
   let "South" sit in the same plate column without being clipped. */
.entrance-name { font-variant-numeric: normal; text-transform: uppercase; letter-spacing: .03em; }
.info-block .entrance-trade {
  margin-top: 14px;
  border-left: 2px solid var(--signal);
  padding-left: 12px;
}

.link-list { list-style: none; border-top: 1px solid var(--rule-soft); }
.link-list li { border-bottom: 1px solid var(--rule-soft); }
.link-list a { display: flex; gap: 12px; align-items: baseline; padding: 11px 0; text-decoration: none; color: var(--ink); }
.link-list a:hover .link-name { color: var(--signal); }
.link-name { font-weight: 600; font-size: .92rem; }
.link-desc { color: var(--ink-dim); font-size: .82rem; margin-left: auto; }

/* ============================================================
   updates timeline
   ============================================================ */
.timeline { border-left: 1px solid var(--rule); padding-left: 26px; margin-left: 6px; }
.timeline-entry { position: relative; padding-bottom: 30px; }
.timeline-entry::before {
  content: "";
  position: absolute; left: -31px; top: 7px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--signal); border: 2px solid var(--ground);
}
.timeline-entry:not(:first-child)::before { background: var(--rule); }
.timeline-head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 8px; }
.timeline-date { font-family: var(--font-mono); font-size: .88rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.rev-tag {
  font-family: var(--font-mono);
  font-size: .62rem; font-weight: 500; letter-spacing: .12em; text-transform: uppercase;
  color: var(--signal); border: 1px solid var(--signal-wash); background: var(--signal-wash);
  padding: 1px 7px; border-radius: var(--radius-sm);
}
.timeline-entry ul { list-style: none; }
.timeline-entry li {
  color: var(--ink-mid); font-size: .88rem;
  padding: 4px 0 4px 16px; position: relative;
}
.timeline-entry li::before {
  content: "—"; position: absolute; left: 0; color: var(--ink-dim);
}

/* ============================================================
   footer
   ============================================================ */
.site-footer { border-top: 1px solid var(--rule); padding: 32px 0 48px; }
.footer-inner {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 48px;
  align-items: start;
}
.footer-col { display: flex; flex-direction: column; gap: 6px; font-size: .82rem; color: var(--ink-dim); }
.footer-strong { color: var(--ink); font-weight: 600; }
.mono-note { font-family: var(--font-mono); font-size: .7rem; line-height: 1.6; margin-top: 4px; }

.legend { list-style: none; display: flex; flex-direction: column; gap: 5px; margin-top: 4px; }
.legend li { display: flex; align-items: baseline; gap: 8px; font-size: .78rem; }
.legend b { color: var(--ink); font-weight: 600; }
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--neutral); flex: 0 0 auto; }
.dot[data-status="confirmed"] { background: var(--ok); }
.dot[data-status="expected"]  { background: var(--warn); }
.dot[data-status="rumored"]   { background: var(--neutral); }

.empty { text-align: center; color: var(--ink-dim); padding: 70px 0; font-size: .9rem; }
.empty code { font-family: var(--font-mono); color: var(--ink); }

.subtle-link {
  color: var(--ink-dim);
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 3px;
}

/* ============================================================
   sheets — the panel chrome shared by every dialog
   ============================================================ */
.sheet {
  width: min(540px, calc(100vw - 32px));
  max-height: min(760px, calc(100vh - 32px));
  margin: auto;
  padding: 0;
  overflow: auto;
  background: var(--ground-2);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--plate-shadow-hover);
}
.sheet::backdrop { background: rgba(0, 0, 0, .78); }

.sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 18px 20px;
  border-bottom: 1px solid var(--rule);
}
.sheet-head h2 {
  margin-top: 2px;
  font-family: var(--font-poster);
  font-size: 1.65rem;
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: .005em;
  text-transform: uppercase;
}
.sheet-close {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  background: none;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  color: var(--ink-dim);
  font: inherit;
  font-size: 1.35rem;
  line-height: 1;
  cursor: pointer;
}
.sheet-close:hover { border-color: var(--ink-dim); color: var(--ink); }

.sheet-body { padding: 20px; }

/* ============================================================
   share saved list
   ============================================================ */
.share-count { margin-bottom: 14px; color: var(--ink-mid); font-size: .88rem; }

/* Mode and part rows — plain fieldsets wearing the toolbar's toggle look. */
.share-set {
  border: none;
  margin: 0 0 12px;
  padding: 0;
  display: flex;
  gap: 6px 18px;
  flex-wrap: wrap;
  align-items: center;
}
.share-set legend.field-label { padding: 0; margin-bottom: 6px; width: 100%; float: left; }
.share-set .toggle input:disabled { cursor: default; }
.share-set .toggle:has(input:disabled) { cursor: default; }
/* Only an *empty* part goes dim — the saved box is disabled because it always
   rides, not because it has nothing to say. */
.share-set .toggle:has(input:disabled:not(:checked)) { opacity: .45; }

.share-link-field {
  display: block;
  background: var(--ground);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
}
.share-link-field .field-label { display: block; padding: 8px 12px 0; }
.share-link-field input {
  display: block;
  width: 100%;
  padding: 4px 12px 10px;
  background: none;
  border: none;
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: .74rem;
  line-height: 1.5;
}
.share-link-field input:focus-visible { outline-offset: -2px; }
.share-link-field input::selection { background: var(--signal); color: var(--signal-ink); }

.share-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.share-action {
  padding: 7px 13px;
  background: none;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.share-action:hover { border-color: var(--ink-dim); }
.share-action-primary {
  background: var(--signal);
  border-color: var(--signal);
  color: var(--signal-ink);
}
.share-action-primary:hover { background: #ff6b3b; border-color: #ff6b3b; }
.share-action[hidden] { display: none; }
.share-status {
  margin-top: 10px;
  min-height: 1.2em;
  color: var(--ink-mid);
  font-size: .8rem;
}

.share-qr {
  display: grid;
  grid-template-columns: minmax(180px, 250px) 1fr;
  align-items: center;
  gap: 18px;
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--rule-soft);
}
.share-qr[hidden], .share-qr-fallback[hidden] { display: none; }
.share-qr-image {
  width: 100%;
  padding: 8px;
  background: #fff;
  color: #000;
  line-height: 0;
}
.share-qr-image svg { display: block; width: 100%; height: auto; }
.share-qr p, .share-qr-fallback { color: var(--ink-dim); font-size: .8rem; }
.share-qr-fallback {
  margin-top: 20px;
  padding: 12px 14px;
  border-left: 3px solid var(--warn);
  background: rgba(232, 163, 58, .08);
}

/* Sharing the guide itself leads with the code instead of trailing it: the
   whole point is holding the screen out for a stranger's camera, so it gets
   the top of the sheet and a size you can read across a queue. */
.share-qr-solo {
  grid-template-columns: 1fr;
  justify-items: center;
  gap: 12px;
  margin: 0 0 18px;
  padding-top: 0;
  border-top: none;
}
.share-qr-solo .share-qr-image { max-width: 260px; }
.share-qr-solo p { text-align: center; max-width: 34ch; }

/* ============================================================
   sources dialog
   ============================================================ */
.src-dialog { width: min(500px, calc(100vw - 32px)); }
.src-subject {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.25;
}
.src-note { margin-top: 4px; color: var(--ink-mid); font-size: .84rem; }

.src-list { list-style: none; margin-top: 16px; border-top: 1px solid var(--rule-soft); }
.src-list li {
  display: flex;
  align-items: baseline;
  gap: 12px;
  border-bottom: 1px solid var(--rule-soft);
}
.src-num {
  font-family: var(--font-mono);
  font-size: .66rem;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
  padding: 11px 0;
}
/* The host is the part that tells you who is making the claim, so it is set as
   the link; the path trails behind it in dimmer ink and is allowed to wrap
   rather than pushing the panel sideways. */
.src-list a {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 8px;
  padding: 10px 0;
  text-decoration: none;
  color: var(--ink);
}
.src-host {
  font-family: var(--font-mono);
  font-size: .78rem;
  border-bottom: 1px solid var(--rule);
}
.src-list a:hover .src-host { color: var(--signal); border-bottom-color: var(--signal); }
.src-path {
  min-width: 0;
  color: var(--ink-dim);
  font-family: var(--font-mono);
  font-size: .68rem;
  line-height: 1.5;
  overflow-wrap: anywhere;
}
.src-caveat {
  margin-top: 18px;
  padding: 12px 14px;
  border-left: 3px solid var(--warn);
  background: rgba(232, 163, 58, .08);
  color: var(--ink-mid);
  font-size: .8rem;
  line-height: 1.5;
}

/* Event info carries the same marker for the whole page's data. */
.info-foot {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 26px;
  padding-top: 16px;
  border-top: 1px solid var(--rule-soft);
  color: var(--ink-dim);
  font-size: .8rem;
}
.info-foot .src-btn { margin-left: 0; }

/* ============================================================
   installed-app chrome — install button, offline flag, toast
   ============================================================ */

/* Reads as a stamped label rather than a call-to-action button: it sits in
   the masthead next to the countdown and must not out-shout the hall plates. */
.install-btn {
  background: none;
  border: 1px solid var(--signal);
  color: var(--signal);
  border-radius: var(--radius-sm);
  padding: 3px 10px;
  font-family: var(--font-mono);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background .12s, color .12s;
}
.install-btn:hover { background: var(--signal); color: var(--signal-ink); }

/* The language toggle sits beside the install button in the masthead and
   wears the same stamped-label shape, in the quieter rule/ink pair: it is
   a preference, not an invitation, and the signal colour belongs to the
   one control asking to be pressed. Labelled with the language it switches
   *to*, in that language, which is the one word every reader recognises. */
/* The share button wears the same shape for the same reason: passing the
   guide on is a thing you may want, not a thing the page is asking for. */
.lang-switch,
.header-share {
  background: none;
  border: 1px solid var(--rule);
  color: var(--ink-mid);
  border-radius: var(--radius-sm);
  padding: 3px 10px;
  font-family: var(--font-mono);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color .12s, color .12s;
}
.lang-switch:hover,
.header-share:hover { border-color: var(--ink-mid); color: var(--ink); }

/* Bottom edge comes from .header-inner's flex-end, so it lands level with
   the language toggle rather than with the top of the meta block. */
.header-share {
  order: 3;
  white-space: nowrap;
}

/* The footer copy runs as prose, so the backstop switcher there reads as
   one more link in the Imprint · Privacy row rather than a second button. */
.lang-switch-link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
  color: var(--ink-dim);
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 3px;
}
.lang-switch-link:hover { color: var(--ink); }

/* Only meaningful while the network is gone, so it is absent — not merely
   dimmed — the rest of the time. */
.offline-flag {
  display: none;
  font-family: var(--font-mono);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--warn);
}
html[data-offline="true"] .offline-flag { display: block; }

.toast {
  position: fixed;
  left: 24px;
  right: 24px;
  bottom: calc(24px + env(safe-area-inset-bottom));
  z-index: 100;
  margin: 0 auto;
  max-width: 460px;
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--ground-2);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--signal);
  border-radius: var(--radius);
  box-shadow: var(--plate-shadow);
  padding: 12px 14px;
  font-size: .84rem;
  color: var(--ink);
}
.toast[hidden] { display: none; }
.toast-text { flex: 1; }
.toast-action {
  flex: 0 0 auto;
  background: var(--signal);
  border: none;
  color: var(--signal-ink);
  border-radius: var(--radius-sm);
  padding: 5px 12px;
  font-family: var(--font-mono);
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  cursor: pointer;
}
.toast-dismiss {
  flex: 0 0 auto;
  background: none;
  border: none;
  color: var(--ink-dim);
  font-size: 1.2rem;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}
.toast-dismiss:hover { color: var(--ink); }

/* Standalone has no browser chrome to sit under, so the app has to keep
   itself clear of the notch and the home indicator on its own. */
html[data-standalone="true"] body { padding-top: env(safe-area-inset-top); }
html[data-standalone="true"] .site-footer { padding-bottom: calc(48px + env(safe-area-inset-bottom)); }
.wrap { padding-left: max(24px, env(safe-area-inset-left)); padding-right: max(24px, env(safe-area-inset-right)); }

/* ============================================================
   legal pages — imprint, privacy
   Prose, not app chrome: one narrow column on the same ground, so
   these read as part of the guide without pulling in the masthead.
   ============================================================ */
/* body is a flex column, so main has to grow or the footer floats up the
   page on a short document */
.legal-page main { flex: 1; padding-top: 40px; padding-bottom: 64px; }
.legal-bar { border-bottom: 1px solid var(--rule); padding: 16px 0; }
.legal-bar .wrap { display: flex; align-items: baseline; gap: 14px; }
.legal-back {
  color: var(--ink-dim);
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: .74rem;
}
.legal-back:hover { color: var(--ink); }
.legal-doc { max-width: 68ch; }
.legal-doc h1 {
  font-family: var(--font-poster);
  font-size: clamp(2rem, 6vw, 2.9rem);
  line-height: 1.05;
  letter-spacing: .01em;
  text-transform: uppercase;
  margin-bottom: 8px;
}
.legal-doc h2 {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 700;
  margin: 34px 0 8px;
  padding-top: 14px;
  border-top: 1px solid var(--rule-soft);
}
.legal-doc h3 {
  font-size: .9rem;
  font-weight: 700;
  color: var(--ink);
  margin: 18px 0 4px;
}
.legal-doc p, .legal-doc li { color: var(--ink-mid); font-size: .9rem; }
.legal-doc p { margin-bottom: 10px; }
.legal-doc ul { margin: 0 0 12px 18px; display: flex; flex-direction: column; gap: 5px; }
.legal-doc a { color: var(--ink); text-decoration: underline; text-decoration-color: var(--rule); text-underline-offset: 3px; }
.legal-doc a:hover { text-decoration-color: var(--signal); }
.legal-doc address { font-style: normal; color: var(--ink); line-height: 1.7; }
.legal-doc code { font-family: var(--font-mono); font-size: .8rem; color: var(--ink); }
/* the one thing on these pages worth reading first */
.legal-lede { color: var(--ink) !important; font-size: 1rem !important; border-left: 2px solid var(--signal); padding-left: 14px; }
.legal-updated { font-family: var(--font-mono); font-size: .7rem; color: var(--ink-dim); margin-bottom: 26px; }

@media (max-width: 620px) {
  .legal-page main { padding-top: 26px; }
  .legal-doc h2 { margin-top: 26px; }
}

/* ============================================================
   responsive
   ============================================================ */

/* The masthead's own breakpoint, wider than the phone one below: the single
   row wants ~640px before the share button starts wrapping onto a line of
   its own, and a lone button under the wordmark reads as a mistake. Stacking
   early costs nothing — the row is mostly air at these widths anyway. */
@media (max-width: 760px) {
  /* Grid, not a column: the share button rides up beside the wordmark into
     the masthead's one piece of empty space, while the meta block keeps the
     full width underneath. Column flex could only stack all three. */
  .header-inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 12px;
    padding-bottom: 14px;
  }
  .brand { grid-column: 1; grid-row: 1; margin-right: 0; }
  .header-share { grid-column: 2; grid-row: 1; justify-self: end; }
  /* Left edge with everything else in the stack — right alignment was for
     the wide row, and there is nothing here to align against. */
  .header-meta { grid-column: 1 / -1; grid-row: 2; text-align: left; }
  .header-actions { justify-content: flex-start; }
}

/* Under ~340px the wordmark and the share button no longer fit on one line
   together, and "gamescom 2026" breaking across two is worse than a slightly
   smaller masthead. Everything gives a few pixels rather than one thing
   giving all of them. */
@media (max-width: 340px) {
  .header-inner { column-gap: 8px; }
  .brand { gap: 10px; }
  .brand-mark { width: 40px; height: 40px; font-size: 1.1rem; }
  .brand-title { font-size: 1.5rem; }
  .header-share { padding: 3px 8px; }
}

@media (max-width: 860px) {
  .footer-inner { grid-template-columns: 1fr 1fr; }
  .priority-item { grid-template-columns: 34px 1fr auto; row-gap: 4px; }
  .priority-loc, .priority-advice { grid-column: 2 / -1; }
  .route-item { grid-template-columns: 1fr auto auto; row-gap: 4px; }
  .route-booth { grid-column: 1 / -1; grid-row: 2; }
  .route-crowd { grid-column: 2; grid-row: 1; }
  .wristband-item { grid-template-columns: 1fr auto; }
  .wristband-loc,
  .wristband-games { grid-column: 1 / -1; justify-self: start; }
}

@media (max-width: 620px) {
  .wrap { padding-left: max(16px, env(safe-area-inset-left)); padding-right: max(16px, env(safe-area-inset-right)); }
  .tabs { padding: 0 16px; }
  .toast { left: 16px; right: 16px; bottom: calc(16px + env(safe-area-inset-bottom)); }
  /* the full masthead is ~290px on a phone — far too much to pin above a
     long scrolling list, so it scrolls away here instead */
  .site-header { position: static; }
  /* …and with nothing pinned on top, the sticky-header scroll offset would
     just strand the route heading 140px down the screen */
  #route-section { scroll-margin-top: 16px; }
  .grid { grid-template-columns: 1fr; }
  .footer-inner { grid-template-columns: 1fr; gap: 26px; }
  .day-row { grid-template-columns: 76px 1fr; row-gap: 8px; }
  .day-access { grid-column: 2; }
  .day-detail { grid-column: 1 / -1; }
  .it-group-head { grid-template-columns: 76px 1fr; row-gap: 8px; }
  .it-item { grid-template-columns: minmax(110px, 1fr) minmax(0, 1.2fr) 20px; row-gap: 9px; }
  .it-days { grid-column: 1 / -1; justify-content: flex-start; }
  .toolbar-row .row-label { flex-basis: 100%; }
  /* let the summary text take a full line instead of squeezing into a column */
  .toolbar-summary { flex-wrap: wrap; row-gap: 4px; }
  .toolbar-summary .row-label { flex: 0 0 auto; }
  .filter-summary { flex: 1 1 100%; order: 3; }
  .directory-summary { flex-wrap: wrap; row-gap: 4px; }
  .directory-lede { flex: 1 1 100%; order: 3; }
  .field-sort { border-left: none; border-top: 1px solid var(--rule-soft); }
  .toolbar-search { flex-direction: column; align-items: stretch; }
  .result-count { margin-left: 0; flex-basis: 100%; }
  .area-list li { grid-template-columns: 1fr; row-gap: 2px; }
  .sheet { width: calc(100vw - 24px); max-height: calc(100vh - 24px); }
  .sheet-head, .sheet-body { padding: 16px; }
  .share-qr { grid-template-columns: 1fr; }
  .share-qr-image { max-width: 280px; margin: 0 auto; }
  .share-qr p { text-align: center; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* ---- rendering cost ----

   Every long list here is built in one innerHTML write, and laying all of it
   out at once is most of what froze the first seconds on a phone: 127 cards,
   up to 200 directory rows behind them. content-visibility lets the browser
   skip layout and paint for everything below the fold until it is scrolled
   toward, so the cost of a list tracks the viewport, not the dataset.

   The intrinsic sizes are rough per-item heights — they only steer the
   scrollbar's first guess and are replaced by real geometry once an item has
   been on screen. Browsers without the property just render everything, which
   is exactly the behaviour this replaces. The changelog timeline stays out:
   its date dot is drawn 31px outside the entry's box, and the paint
   containment this property brings would clip it off. */
.card { content-visibility: auto; contain-intrinsic-size: auto 460px; }
.dir-row { content-visibility: auto; contain-intrinsic-size: auto 60px; }
.priority-item { content-visibility: auto; contain-intrinsic-size: auto 92px; }
.it-item { content-visibility: auto; contain-intrinsic-size: auto 96px; }
.route-item { content-visibility: auto; contain-intrinsic-size: auto 88px; }
