/* STL Daily design system: "The St. Louis Daily", a newspaper in ink on paper.
   Warm newsprint, black ink, a blackletter masthead, tall condensed serif
   headlines, hairline column rules, one red accent for tags. */

@import url("https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;800;900&family=Instrument+Serif:ital@0;1&family=Newsreader:opsz,wght@6..72,400;6..72,500;6..72,600;6..72,700&display=swap");

:root {
  --paper: #d8d1c5;
  --paper-2: #ddd6cb;
  /* Card surface light. --glow is the cursor-follow highlight, --card-sheen the
     fixed diagonal gradient, --card-rim the inner hairline. All three are white
     on paper and must be nearly nothing on ink, or a dark card turns milky and
     swallows its own text. */
  --glow: rgba(255, 255, 255, 0.6);
  --card-sheen: rgba(255, 255, 255, 0.4);
  --card-rim: rgba(255, 255, 255, 0.4);
  --card-rim-hover: rgba(255, 255, 255, 0.5);
  --ink: #1b1a17;
  --ink-soft: #4b463f;
  --rule: #1b1a17;
  --red: #c4472b;

  /* legacy names used across pages */
  --bg: var(--paper);
  --panel: var(--paper-2);
  --text: var(--ink);
  --muted: var(--ink-soft);
  --line: #a59d90;
  --accent: var(--red);
  --accent-text: var(--paper);
  --good: #2e6b45;
  --warn: #9a6a12;
  /* Text versions of the three accents.
     The accents themselves are tuned for ink on paper as decoration — rules,
     borders, the shadow under a button — where contrast is not a legibility
     question. As small text on newsprint they measure 3.1–4.4:1, under the
     4.5:1 that normal text needs, so anything SET in an accent uses these
     instead. The chrome keeps the colour it was designed with. */
  --red-ink: #9e351f;
  --good-ink: #2a6440;
  --warn-ink: #704d0c;
  --age-0: #3f7d55;
  --age-1: #c49a2c;
  --age-2: #cf6a2c;
  --age-3: #b8382a;

  /* zone colors survive only as small accents (maps, badges); the paper stays ink-and-red */
  --z-today: var(--red); --z-tonight: var(--red); --z-games: var(--red); --z-parking: var(--red);
  --z-beer: var(--red); --z-menu: var(--red); --z-fix: var(--red); --z-me: var(--red);
  --hi: var(--red);

  --radius: 0;
  --wobble: 0;
  --shadow: none;
  --shadow-lg: none;
  --stroke: 1px solid var(--ink);
  --header-h: 112px;
  --font-body: "Newsreader", Georgia, "Times New Roman", serif;
  --font-hand: "Instrument Serif", Georgia, serif;
  --font-display: "Playfair Display", "Times New Roman", serif;
  /* Blackletter looks the part and fails the only job a masthead has: its S
     is indistinguishable from a G, so "STL Daily" read as "GTL Daily".
     Playfair at 900 is still unmistakably a newspaper and is legible at a
     glance, which is what a wordmark is for. */
  --font-mast: "Playfair Display", "Times New Roman", serif;
  /* the surround behind the page sheet, a shade darker than the paper */
  --paper-3: #cfc7b8;
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.25 0 0 0 0 0.22 0 0 0 0 0.18 0 0 0 0.16 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
:root[data-theme="dark"] {
  --paper: #1c1b18; --paper-2: #22211d; --glow: rgba(255, 255, 255, 0.08); --ink: #e2dbcf; --ink-soft: #b3ab9d; --rule: #e2dbcf; --line: #5a554c;
  --card-sheen: rgba(255, 255, 255, 0.035); --card-rim: rgba(255, 255, 255, 0.07); --card-rim-hover: rgba(255, 255, 255, 0.11);
  --red: #e0603f; --good: #6fb98a; --warn: #e2b04a;
  /* On ink the accents are already well clear of 4.5:1, so text uses them. */
  --red-ink: #e0603f; --good-ink: #6fb98a; --warn-ink: #e2b04a;
  --age-0: #6fb98a; --age-1: #e2b04a; --age-2: #e88a4f; --age-3: #e0603f;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html, body { margin: 0; }
body {
  background: var(--paper) var(--grain);
  color: var(--ink);
  font: 18px/1.45 var(--font-body);
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--ink); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--red-ink); }
button { font: inherit; cursor: pointer; color: inherit; }
::selection { background: var(--ink); color: var(--paper); }

/* ---- masthead ---- */
.topbar {
  position: sticky; top: 0; z-index: 1000;
  display: grid; grid-template-columns: 1fr auto 1fr; grid-template-areas: "loc brand me" "nav nav nav";
  align-items: center; column-gap: 12px;
  padding: 10px 28px 0;
  background: var(--paper) var(--grain); border-bottom: 1px solid var(--ink);
}
.topbar::before { content: "St. Louis, MO"; grid-area: loc; font-size: 15px; color: var(--ink); }
.brand {
  grid-area: brand; justify-self: center;
  font-family: var(--font-mast); font-weight: 900; font-size: 30px; line-height: 1; color: var(--ink);
  text-decoration: none; white-space: nowrap; letter-spacing: -0.015em;
}
.brand::after { content: none; }
.topbar nav {
  grid-area: nav; display: flex; justify-content: center; gap: 0; overflow-x: auto; scrollbar-width: none;
  margin: 10px -28px 0; padding: 0 28px; border-top: 1px solid var(--ink);
}
.topbar nav::-webkit-scrollbar { display: none; }
.topbar nav a {
  font-family: var(--font-body); font-weight: 600; font-size: 13px; letter-spacing: 0.14em; text-transform: uppercase;
  padding: 9px 16px; color: var(--ink); text-decoration: none; white-space: nowrap; border-left: 1px solid var(--line);
}
.topbar nav a:first-child { border-left: 0; }
.topbar nav a:hover { color: var(--red-ink); }
.topbar nav a[aria-current="page"] { background: var(--ink); color: var(--paper); }
.me-chip {
  grid-area: me; justify-self: end; display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-body); font-weight: 600; font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase;
  text-decoration: none; color: var(--ink); padding: 3px 12px 3px 3px; border: 1px solid var(--ink);
}
.me-chip:hover { background: var(--ink); color: var(--paper); }
.me-chip .av { width: 30px; height: 30px; display: grid; place-items: center; border: 1px solid var(--ink); background: var(--paper-2); font-size: 17px; overflow: hidden; }
.me-chip b { font-weight: 600; }
@media (max-width: 720px) {
  .topbar { grid-template-columns: auto 1fr auto; grid-template-areas: "brand brand me" "nav nav nav"; padding: 8px 14px 0; }
  .topbar::before { display: none; }
  .brand { justify-self: start; font-size: 28px; }
  .topbar nav { justify-content: flex-start; margin: 8px -14px 0; padding: 0 14px; mask-image: linear-gradient(90deg, #000 85%, transparent); }
  .topbar nav a { padding: 10px 12px; min-height: 40px; display: inline-flex; align-items: center; }
}

/* ---- ticker: a black wire-service band ---- */
.ticker {
  display: flex; align-items: stretch; overflow: hidden; border-bottom: 1px solid var(--ink);
  background: var(--ink); color: var(--paper); font-family: var(--font-hand); font-size: 18px; min-height: 34px;
}
.ticker .tag {
  flex: none; display: flex; align-items: center; padding: 4px 14px; background: var(--red); color: #f3ece1;
  font-family: var(--font-body); font-weight: 700; font-size: 12px; letter-spacing: 0.16em; text-transform: uppercase; white-space: nowrap;
}
.ticker .track { flex: 1; overflow: hidden; display: flex; align-items: center; }
.ticker .run { display: inline-flex; gap: 56px; white-space: nowrap; padding-left: 100%; animation: ticker var(--ticker-dur, 40s) linear infinite; }
.ticker:hover .run, .ticker:focus-within .run, .ticker.held .run { animation-play-state: paused; }
.ticker .tk { color: inherit; text-decoration: none; padding: 6px 0; border-bottom: 1px solid transparent; }
.ticker .tk::after { content: " \2192"; opacity: 0.55; font-family: var(--font-body); font-size: 14px; }
.ticker .tk:hover, .ticker .tk:focus-visible { color: #f3ece1; border-bottom-color: var(--red); outline: none; }
.ticker .run span::before { content: "\2022"; margin-right: 14px; color: var(--red); }
@keyframes ticker { to { transform: translateX(-100%); } }
/* (The old ticker's reduced-motion fallback lived here; the train band below
   brings its own, which keeps every car on the track.) */

/* ---- page + typography ---- */
.page { max-width: 1040px; margin: 0 auto; padding: 28px 28px 64px; }
h1, .display { font-family: var(--font-display); font-weight: 900; letter-spacing: -0.02em; }
.page h1 { font-size: clamp(38px, 6vw, 64px); line-height: 0.95; margin: 4px 0 10px; text-transform: uppercase; }
.page .lede { font-family: var(--font-hand); font-size: clamp(22px, 2.6vw, 30px); line-height: 1.15; color: var(--ink); margin: 0 0 22px; }
.kicker { font-family: var(--font-body); font-weight: 700; font-size: 12px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--red-ink); }

/* Sections read like newspaper columns: a heavy rule on top, a small caps label. */
.card {
  background: transparent; border: 0; border-top: 3px double var(--ink); border-radius: 0; box-shadow: none;
  padding: 12px 0 16px; margin-bottom: 18px;
}
.card h2 {
  display: block; font-family: var(--font-body); font-weight: 700; font-size: 12px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink); margin: 0 0 10px; padding: 0 0 6px; background: none; border-bottom: 1px solid var(--line); transform: none;
}
.muted { color: var(--ink-soft); }
.small { font-size: 15px; }
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 18px; border: 1px solid var(--ink); border-radius: 0;
  background: var(--ink); color: var(--paper); font-family: var(--font-body); font-weight: 600; font-size: 13px; letter-spacing: 0.14em; text-transform: uppercase; line-height: 1.2;
  text-decoration: none; box-shadow: none; transition: background 0.15s, color 0.15s;
}
.btn:hover { background: var(--red); border-color: var(--red); color: #f3ece1; transform: none; box-shadow: none; }
.btn:active { transform: translateY(1px); box-shadow: none; }
.btn.ghost { background: transparent; color: var(--ink); }
.btn.ghost:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.btn:disabled { opacity: 0.45; cursor: default; transform: none; }
.btn:disabled:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.btn.ghost:disabled:hover { background: transparent; color: var(--ink); }
.pill {
  display: inline-block; padding: 1px 7px; border: 0; border-radius: 0; background: var(--red); color: #f3ece1;
  font-family: var(--font-body); font-weight: 700; font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
}
.pill.good { background: var(--good); color: #f3ece1; }
.pill.warn { background: var(--warn); color: #f3ece1; }
.pill.bad { background: var(--red); color: #f3ece1; }
input[type="text"], input[type="search"], input[type="datetime-local"], select {
  font: inherit; font-size: 17px; padding: 8px 4px; border: 0; border-bottom: 1px solid var(--ink); border-radius: 0;
  background: transparent; color: var(--ink);
}
select { padding-right: 18px; }
input:focus, select:focus { outline: none; border-bottom: 2px solid var(--red); box-shadow: none; }
:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
.chip {
  padding: 6px 12px; border: 1px solid var(--ink); border-radius: 0; background: transparent;
  color: var(--ink); font-family: var(--font-body); font-weight: 600; font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase;
}
.chip[aria-pressed="true"] { background: var(--ink); color: var(--paper); box-shadow: none; }

/* The big reversed-out block (front-page nameplate, section openers). */
.ink-block { background: var(--ink); color: var(--paper); padding: 18px 22px; margin: 0 0 22px; overflow: hidden; }
.ink-block .huge {
  display: block; font-family: var(--font-display); font-weight: 900; text-transform: uppercase; line-height: 0.82;
  letter-spacing: -0.035em; font-size: clamp(64px, 15.5vw, 210px); white-space: nowrap; text-align: center;
}
.ink-block .under { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-top: 12px; padding-top: 8px;
  border-top: 1px solid currentColor; font-family: var(--font-body); font-size: 13px; letter-spacing: 0.14em; text-transform: uppercase; }

/* Column rules: put .cols on a grid to separate its children with hairlines. */
.cols > * + * { border-left: 1px solid var(--ink); padding-left: 22px; }
@media (max-width: 720px) {
  .page { padding: 20px 16px 56px; }
  .cols > * + * { border-left: 0; padding-left: 0; }
}

/* ---- skip link ----
   Every page puts a nameplate, eight zone links and a scrolling headline band
   above the content. Without this a keyboard reaches the page itself on about
   the twelfth tab, on every single page. Visible only when focused. */
.skip-link {
  position: absolute; left: 8px; top: -60px; z-index: 100;
  padding: 10px 16px; background: var(--ink); color: var(--paper);
  border: 2px solid var(--paper); border-radius: 0 0 10px 10px;
  font-family: var(--font-body); font-weight: 700; text-decoration: none;
  transition: top .15s ease;
}
.skip-link:focus { top: 0; color: var(--paper); }
@media (prefers-reduced-motion: reduce) { .skip-link { transition: none; } }

/* ---- dialogs ---- */
dialog {
  width: min(580px, calc(100vw - 28px)); max-height: calc(100vh - 40px); overflow-y: auto;
  padding: 26px 28px; border: 1px solid var(--ink); outline: 1px solid var(--ink); outline-offset: -7px; border-radius: 0;
  background: var(--paper) var(--grain); color: var(--ink); box-shadow: 0 30px 80px rgb(0 0 0 / 0.35);
}
dialog::backdrop { background: rgb(20 18 15 / 0.65); }
dialog h2 { font-family: var(--font-display); font-weight: 900; font-size: 34px; line-height: 1; margin: 0 0 8px; text-transform: uppercase; color: var(--ink); background: none; transform: none; padding: 0; border: 0; letter-spacing: -0.02em; }
dialog h3 { font-family: var(--font-body); font-weight: 700; font-size: 12px; letter-spacing: 0.18em; text-transform: uppercase; margin: 18px 0 6px; padding-bottom: 4px; border-bottom: 1px solid var(--line); }
dialog .lead { font-family: var(--font-hand); font-size: 22px; line-height: 1.2; color: var(--ink); margin-top: 0; }
dialog ul { padding-left: 20px; margin: 0; }
dialog li { margin: 4px 0; }
dialog .actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; }
.key { display: inline-flex; align-items: center; margin-right: 10px; white-space: nowrap; font-size: 13px; }
.key i { width: 11px; height: 11px; border-radius: 50%; margin-right: 4px; border: 1.5px solid var(--ink); }

/* ---- toasts (XP, level ups) ---- */
.toasts { position: fixed; z-index: 3000; left: 50%; bottom: 18px; transform: translateX(-50%); display: grid; gap: 8px; width: min(420px, calc(100vw - 24px)); pointer-events: none; }
.toast {
  pointer-events: auto; background: var(--ink); color: var(--paper); border: 0; border-radius: 0; box-shadow: 0 12px 30px rgb(0 0 0 / 0.3);
  padding: 11px 16px; font-family: var(--font-hand); font-size: 20px; display: flex; align-items: center; gap: 10px;
  animation: toast-in 0.35s cubic-bezier(.2, 1.4, .5, 1);
}
.toast .ic { font-size: 22px; }
.toast.big { background: var(--red); color: #f3ece1; }
.toast .btn.ghost { color: inherit; border-color: currentColor; margin-left: auto; }
@keyframes toast-in { from { transform: translateY(30px) scale(0.9); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .toast { animation: none; } }

/* ---- cosmetics (profile + leaderboards) ---- */
.who { display: inline-flex; align-items: center; gap: 7px; }
.av-ring { width: 36px; height: 36px; flex: none; display: grid; place-items: center; border-radius: 50%; font-size: 18px; background: var(--paper-2); border: 2px solid var(--ink); }
.av3d { overflow: hidden; padding: 0; background: #fffdf6; }
.av3d img { width: 100%; height: 100%; object-fit: contain; display: block; }
.me-chip .av.av3d img { width: 100%; height: 100%; }
.frame-ink { border-color: var(--ink); }
.frame-brick { border-color: #b5452f; }
.frame-river { border-color: #2a7fb8; }
.frame-brass { border-color: #b8873a; box-shadow: 0 0 0 2px #e3c27a; }
.frame-arch { border-color: #d4a017; box-shadow: 0 0 0 3px #ffe08a, 0 0 12px #ffd23f; }
.frame-neon { border-color: #ff3fa4; box-shadow: 0 0 8px #ff3fa4, inset 0 0 6px #ff3fa4; }
.frame-redbird { border-color: #c8102e; }
.frame-bluenote { border-color: #003f87; box-shadow: 0 0 0 2px #fcb514; }
.frame-confetti { border-color: #ff6b6b; box-shadow: 0 0 0 2px #ffd23f, 0 0 0 4px #4ddb9a, 0 0 0 6px #b69cff; }
.color-ink { color: var(--ink); }
.color-brick { color: #b5452f; }
.color-river { color: #2a7fb8; }
.color-provel { color: #d98a1c; }
.color-custard { color: #c9a227; }
.color-ivy { color: #2f7d4f; }
.color-plum { color: #9b4fc4; }
.color-gold { background: linear-gradient(90deg, #b8860b, #ffd700, #b8860b); -webkit-background-clip: text; background-clip: text; color: transparent; }
.banner-paper { background: var(--paper-2) var(--grain); }
.banner-brickrow { background: repeating-linear-gradient(0deg, #c4644b 0 14px, #8a3b2b 14px 16px), #c4644b; }
.banner-riverfront { background: linear-gradient(180deg, #9fd3f0 0 55%, #4a8fb8 55% 100%); }
.banner-ballpark { background: radial-gradient(circle at 70% 20%, #fffbe0 0 6%, transparent 7%), linear-gradient(180deg, #0f1a3a 0 60%, #2f8f3a 60%); }
.banner-neon { background: linear-gradient(135deg, #1a0b2e, #3b0f4f 60%, #ff3fa4); }
.banner-dusk { background: linear-gradient(180deg, #ff9a5a 0%, #ff6b8a 45%, #5b3a8c 100%); }
.rarity-common { --r: #9aa0a6; }
.rarity-rare { --r: #3a86ff; }
.rarity-epic { --r: #9b5de5; }
.rarity-legendary { --r: #f5b700; }

/* ---- maps ---- */
:root[data-theme="dark"] .basemap { filter: invert(1) hue-rotate(180deg) brightness(0.85) contrast(0.9); }
.leaflet-container { font-family: var(--font-body); }

/* ---- age gate ---- */
dialog.gate { width: 100vw; height: 100vh; max-width: none; max-height: none; margin: 0; border: 0; border-radius: 0; box-shadow: none;
  place-items: center; padding: 16px; background: var(--paper) var(--grain); overflow-y: auto; }
dialog.gate[open] { display: grid; }
dialog.gate::backdrop { background: var(--paper); }
.gate .card { max-width: 440px; width: 100%; text-align: center; }
.gate .card { border-top: 0; outline: 1px solid var(--ink); outline-offset: 10px; padding: 26px; }
.gate .gate-big { font-family: var(--font-display); font-weight: 900; text-transform: uppercase; font-size: 44px; margin: 6px 0 10px; line-height: 0.95; letter-spacing: -0.02em; }
.gate .dob { display: flex; gap: 8px; justify-content: center; margin: 14px 0; flex-wrap: wrap; }
.gate .fine { font-size: 13px; color: var(--ink-soft); }
.gate .err { color: var(--red); min-height: 1.2em; font-family: var(--font-hand); font-size: 20px; margin: 4px 0; }

/* ---- animated banner scenes (assets/banners.js) ---- */
.banner-scene { display: block; width: 100%; height: 100%; }
.bn-drift { animation: bn-drift var(--d, 30s) linear infinite; }
@keyframes bn-drift { from { transform: translateX(-120px); } to { transform: translateX(640px); } }
.bn-smoke { animation: bn-smoke 3.2s ease-out infinite; animation-delay: var(--delay, 0s); transform-box: fill-box; transform-origin: center; }
@keyframes bn-smoke { 0% { transform: translate(0, 0) scale(.5); opacity: 0; } 20% { opacity: 1; } 100% { transform: translate(14px, -30px) scale(1.4); opacity: 0; } }
.bn-sway { animation: bn-sway 4s ease-in-out infinite alternate; }
@keyframes bn-sway { from { transform: rotate(-3deg); } to { transform: rotate(3deg); } }
.bn-fly { animation: bn-fly 14s linear infinite; }
@keyframes bn-fly { from { transform: translate(0, 0); } 50% { transform: translate(340px, -10px); } to { transform: translate(680px, 6px); } }
.bn-waves { animation: bn-waves 3s linear infinite; }
@keyframes bn-waves { to { transform: translateX(60px); } }
.bn-boat { animation: bn-boat 26s linear infinite; }
@keyframes bn-boat { from { transform: translateX(-100px); } to { transform: translateX(700px); } }
.bn-spin { animation: bn-spin 1.4s linear infinite; }
@keyframes bn-spin { to { transform: rotate(360deg); } }
.bn-twinkle { animation: bn-twinkle 2.2s ease-in-out infinite; animation-delay: var(--delay, 0s); }
@keyframes bn-twinkle { 50% { opacity: .2; } }
.bn-flicker { animation: bn-flicker 3.4s steps(1) infinite; animation-delay: var(--delay, 0s); }
@keyframes bn-flicker { 0%, 100% { opacity: 1; } 47% { opacity: .55; } 49% { opacity: 1; } 51% { opacity: .7; } }
.bn-firework { animation: bn-firework 2.6s ease-out infinite; animation-delay: var(--delay, 0s); }
@keyframes bn-firework { 0% { transform: scale(.1); opacity: 0; } 15% { opacity: 1; } 70% { transform: scale(1); opacity: .9; } 100% { transform: scale(1.15); opacity: 0; } }
.bn-neon { animation: bn-neon 4s steps(1) infinite; filter: drop-shadow(0 0 6px #ff3fa4); }
@keyframes bn-neon { 0%, 100% { opacity: 1; } 62% { opacity: .35; } 64% { opacity: 1; } 66% { opacity: .5; } 68% { opacity: 1; } }
.bn-blink { animation: bn-twinkle 1.6s steps(1) infinite; }
.bn-sunset { animation: bn-sunset 9s ease-in-out infinite alternate; }
@keyframes bn-sunset { from { transform: translateY(-14px); } to { transform: translateY(10px); } }
@media (prefers-reduced-motion: reduce) {
  .banner-scene * { animation: none !important; }
}

/* ======================================================================
   Cartoon layer: same newspaper layout, drawn like a Sunday comic strip.
   Thick ink outlines, hand-drawn corners, hard offset shadows, a yellow
   highlighter, and springy motion on everything you can touch.
   ====================================================================== */
:root {
  --pop: #ffd23f;
  --pop-ink: #1b1a17;
  --sky: #9ad8ff;
  --o: 2.5px solid var(--ink);
  --wob: 18px 7px 16px 9px / 9px 16px 7px 18px;
  --wob-2: 9px 18px 8px 16px / 16px 8px 18px 9px;
  --pop-shadow: 5px 5px 0 var(--ink);
  --spring: cubic-bezier(.34, 1.56, .64, 1);
}
:root[data-theme="dark"] { --pop: #f2c230; --sky: #3b3f7a; }

/* masthead */
.brand { display: inline-block; transition: transform .35s var(--spring); }
.brand:hover { animation: jiggle .55s ease-in-out; color: var(--ink); }
.topbar nav { padding-block: 5px; gap: 4px; border-top: var(--o); }
.topbar { border-bottom: var(--o); }
.topbar nav a {
  display: inline-block; border: 2px solid transparent; border-radius: 999px; padding: 6px 14px;
  transition: transform .3s var(--spring), background .15s, border-color .15s;
}
.topbar nav a:first-child, .topbar nav a { border-left-color: transparent; }
.topbar nav a:hover { transform: translateY(-2px) rotate(-3deg); background: var(--pop); color: var(--pop-ink); border-color: var(--ink); }
.topbar nav a[aria-current="page"] { border-color: var(--ink); box-shadow: 2px 2px 0 var(--red); }
.topbar nav a[aria-current="page"]:hover { background: var(--ink); color: var(--paper); }
.me-chip { border: var(--o); border-radius: 999px; box-shadow: 3px 3px 0 var(--ink); transition: transform .3s var(--spring), box-shadow .2s; }
.me-chip:hover { transform: translate(-2px, -2px) rotate(-2deg); box-shadow: 5px 5px 0 var(--ink); background: var(--pop); color: var(--pop-ink); }
.me-chip .av { border-radius: 50%; border: 2px solid var(--ink); transition: transform .4s var(--spring); }
.me-chip:hover .av { transform: rotate(-14deg) scale(1.15); }
@media (max-width: 720px) { .topbar nav a { padding: 8px 12px; } }

/* ticker */
.ticker { border-bottom: var(--o); }
.ticker .tag { border-right: var(--o); border-radius: 0 999px 999px 0; padding-right: 18px; animation: tag-nudge 5s ease-in-out infinite; transform-origin: left center; }
.ticker .tk { display: inline-block; transition: transform .25s var(--spring), color .15s; }
.ticker .tk:hover, .ticker .tk:focus-visible { transform: translateY(-2px) scale(1.04); color: var(--pop); border-bottom-color: var(--pop); }
@keyframes tag-nudge { 0%, 88%, 100% { transform: none; } 91% { transform: rotate(-3deg) scale(1.05); } 94% { transform: rotate(2deg) scale(1.05); } 97% { transform: rotate(-1deg); } }

/* headlines get a highlighter swipe behind them */
.page h1 { text-shadow: 3px 3px 0 var(--pop); }
:root[data-theme="dark"] .page h1 { text-shadow: 3px 3px 0 var(--red); }
.kicker { display: inline-block; transform: rotate(-2deg); }

/* panels: each section is a comic panel */
.card {
  background: linear-gradient(135deg, var(--paper-2) 0%, var(--card-sheen) 100%); border: var(--o); border-radius: var(--wob); box-shadow: var(--pop-shadow);
  padding: 14px 18px 16px; margin-bottom: 22px;
  transition: transform .4s var(--spring), box-shadow .25s ease, opacity .45s ease;
}
.card:nth-child(even) { border-radius: var(--wob-2); }
.card:hover { transform: translate(-3px, -4px); box-shadow: 8px 9px 0 var(--ink); }
.card h2 {
  display: inline-block; background: var(--pop); color: var(--pop-ink); border: 2px solid var(--ink); border-radius: 999px;
  padding: 3px 12px; margin: -26px 0 10px -6px; box-shadow: 2px 2px 0 var(--ink); transform: rotate(-2deg);
  transition: transform .35s var(--spring);
}
.card:nth-child(even) h2 { transform: rotate(1.5deg); }
.card:hover h2 { transform: rotate(-5deg) scale(1.06); }
.card:nth-child(even):hover h2 { transform: rotate(4deg) scale(1.06); }

/* buttons: squishy rubber */
.btn {
  border: var(--o); border-radius: 999px; box-shadow: 3px 3px 0 var(--red);
  transition: transform .3s var(--spring), box-shadow .2s, background .15s, color .15s;
}
.btn:hover { background: var(--red); border-color: var(--ink); color: #fff8ec; box-shadow: 5px 5px 0 var(--ink); animation: jelly .55s var(--spring) forwards; }
.btn.ghost { box-shadow: 3px 3px 0 var(--ink); }
.btn.ghost:hover { background: var(--pop); color: var(--pop-ink); border-color: var(--ink); }
.btn:active { animation: none; transform: translate(3px, 3px) scale(.96); box-shadow: 0 0 0 var(--ink); }
.btn:disabled, .btn:disabled:hover { animation: none; transform: none; box-shadow: 3px 3px 0 var(--line); }
@keyframes jelly {
  0% { transform: none; }
  30% { transform: translate(-2px, -2px) scale(1.12, .9); }
  50% { transform: translate(-2px, -2px) scale(.94, 1.08); }
  70% { transform: translate(-2px, -2px) scale(1.04, .97); }
  100% { transform: translate(-2px, -2px) scale(1.03) rotate(-1.5deg); }
}

.pill { border: 2px solid var(--ink); border-radius: 999px; padding: 1px 9px; transform: rotate(-2deg); }
.chip {
  border: var(--o); border-radius: 999px; box-shadow: 2px 2px 0 var(--ink); background: var(--paper-2);
  transition: transform .3s var(--spring), box-shadow .2s, background .15s;
}
.chip:hover { transform: translate(-1px, -2px) rotate(-2deg); background: var(--pop); color: var(--pop-ink); box-shadow: 3px 4px 0 var(--ink); }
.chip[aria-pressed="true"] { transform: translate(2px, 2px); box-shadow: 0 0 0 var(--ink); }
.chip[aria-pressed="true"]:hover { background: var(--ink); color: var(--paper); }

input[type="text"], input[type="search"], input[type="datetime-local"], select {
  border: var(--o); border-radius: 12px; padding: 8px 10px; background: var(--paper);
  box-shadow: inset 2px 2px 0 rgb(0 0 0 / .08); transition: box-shadow .2s, transform .25s var(--spring);
}
input:focus, select:focus { border: var(--o); box-shadow: 3px 3px 0 var(--pop), 0 0 0 2px var(--ink); transform: translateY(-1px); }

/* front-page nameplate: letters hop when hovered */
.ink-block { border: var(--o); border-radius: 22px 10px 24px 12px / 12px 24px 10px 22px; box-shadow: 7px 7px 0 var(--red); }
.ink-block .huge .ltr { display: inline-block; transition: transform .35s var(--spring), color .2s; animation: drop-in .6s var(--spring) backwards; animation-delay: calc(var(--i) * 45ms + .2s); }
.ink-block .huge .ltr:hover { transform: translateY(-14%) rotate(-7deg) scale(1.08); color: var(--pop); }
@keyframes drop-in { from { transform: translateY(-60%) scale(.6); opacity: 0; } }

/* dialogs pop open like a speech bubble */
dialog { border: 3px solid var(--ink); outline: 0; border-radius: 24px 12px 26px 14px / 14px 26px 12px 24px; box-shadow: 9px 9px 0 var(--ink); }
dialog[open]:not(.gate) { animation: pop-open .45s var(--spring); }
dialog::backdrop { animation: fade-in .25s ease; }
dialog.gate { border-radius: 0; box-shadow: none; }
.gate .card { outline: 0; border-top: var(--o); animation: pop-open .5s var(--spring); }
@keyframes pop-open { from { transform: scale(.6) rotate(-4deg); opacity: 0; } }
@keyframes fade-in { from { opacity: 0; } }

.toast { border: var(--o); border-radius: 16px 8px 18px 10px / 10px 18px 8px 16px; box-shadow: 5px 5px 0 var(--red); animation: toast-in .5s var(--spring); }
.toast .ic { display: inline-block; animation: jiggle .6s .3s ease-in-out; }
.av-ring { transition: transform .35s var(--spring); }
.av-ring:hover { transform: scale(1.15) rotate(-8deg); }
@keyframes jiggle { 0%, 100% { transform: none; } 20% { transform: rotate(-6deg) scale(1.06); } 40% { transform: rotate(5deg); } 60% { transform: rotate(-3deg); } 80% { transform: rotate(2deg); } }

/* scroll pop-in (assets/intro.js toggles .pop-wait) */
.pop-wait { opacity: 0; transform: translateY(26px) scale(.92) rotate(-1deg); }
.page h1, .page .hello { transition: transform .5s var(--spring), opacity .4s ease; }

/* ---- cloud-parting intro ---- */
.intro { position: fixed; inset: 0; z-index: 5000; overflow: hidden; pointer-events: none; }
.intro::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(circle at 50% 110%, #fff6c8 0 18%, transparent 45%), linear-gradient(180deg, var(--sky), #e9f7ff);
  transition: opacity .55s ease .3s;
}
:root[data-theme="dark"] .intro::before { background: radial-gradient(circle at 50% 110%, #ff9a5a55 0 18%, transparent 45%), linear-gradient(180deg, #1d1f45, var(--sky)); }
.intro.part::before { opacity: 0; }
.intro .cloud { position: absolute; transition: transform 1s cubic-bezier(.6, -.2, .7, 1); transition-delay: calc(var(--i) * 18ms); }
.intro .bob { animation: bob 1.8s ease-in-out infinite alternate; animation-delay: calc(var(--i) * -0.27s); }
.intro .cloud svg { display: block; width: 100%; height: auto; overflow: visible; }
.intro .cloud path { fill: #fffdf6; stroke: var(--ink); stroke-width: 2.6; stroke-linejoin: round; }
:root[data-theme="dark"] .intro .cloud path { fill: #e9e3f5; stroke: #14132a; }
.intro .cloud path.shine { fill: none; stroke-width: 2.2; stroke-linecap: round; opacity: .35; }
.intro .cloud { animation: cloud-in .7s var(--spring) backwards; animation-delay: calc(var(--i) * 40ms); }
.intro.part .cloud.l { transform: translateX(-85vw) scale(1.6); }
.intro.part .cloud.r { transform: translateX(85vw) scale(1.6); }
.intro-sign {
  position: absolute; left: 50%; top: 50%; translate: -50% -50%; display: grid; justify-items: center; gap: 2px;
  padding: 16px 30px 14px; background: var(--paper); color: var(--ink); border: 3.5px solid var(--ink);
  border-radius: 26px 12px 28px 14px / 14px 28px 12px 26px; box-shadow: 8px 8px 0 var(--red);
  animation: sign-in .7s var(--spring) .35s backwards; transition: transform .7s cubic-bezier(.5, 0, .75, 0), opacity .5s ease;
}
.intro-sign b { font-family: var(--font-mast); font-weight: 400; font-size: clamp(46px, 11vw, 96px); line-height: 1; }
.intro-sign small { font-family: var(--font-body); font-weight: 700; font-size: 12px; letter-spacing: .2em; text-transform: uppercase; }
.intro-sign small:first-child { background: var(--pop); color: var(--pop-ink); border: 2px solid var(--ink); border-radius: 999px; padding: 2px 10px; transform: rotate(-3deg); }
.intro.part .intro-sign { transform: scale(3.2); opacity: 0; }
@keyframes bob { to { transform: translateY(-10px) rotate(.6deg); } }
@keyframes cloud-in { from { opacity: 0; transform: scale(.7); } }
@keyframes sign-in { from { transform: scale(0) rotate(-12deg); } 60% { transform: scale(1.08) rotate(3deg); } }

/* the paper underneath zooms up to meet you */
.intro-hold .topbar, .intro-hold .ticker, .intro-hold main { transform: scale(.82); filter: blur(3px); }
.intro-zoom .topbar, .intro-zoom .ticker, .intro-zoom main { animation: zoom-in 1.05s cubic-bezier(.2, .9, .3, 1.15) both; }
@keyframes zoom-in { from { transform: scale(.82) translateY(24px); filter: blur(3px); } to { transform: none; filter: none; } }

@media (prefers-reduced-motion: reduce) {
  .card, .btn, .chip, .topbar nav a, .me-chip, .card h2, .ink-block .huge .ltr, .ticker .tag, .toast, dialog[open] { animation: none !important; transition: none !important; }
  .card:hover, .btn:hover, .chip:hover, .topbar nav a:hover, .me-chip:hover { transform: none; }
  .pop-wait { opacity: 1; transform: none; }
}
.intro-hold .ink-block .huge .ltr { animation-play-state: paused; }

/* tiles you can tap: springy lift on hover, squish on press */
.tabs button, .qn label, .options span, .vote, .dish, .beer, .item, .av3d-card, .emoji-row button,
.guesses li:not(.slot), .tile, .superlatives div, .stat, .milestones li, .events li, .superlatives > * {
  transition: transform .3s var(--spring), box-shadow .2s, background .15s;
}
.tabs button, .qn label, .options span, .vote, .emoji-row button, .item, .av3d-card:not(.locked) { box-shadow: 3px 3px 0 var(--ink); }
.tabs button:hover, .qn label:hover, .options span:hover, .vote:hover, .emoji-row button:hover,
.item:not(.locked):hover, .av3d-card:not(.locked):hover {
  transform: translate(-2px, -3px) rotate(-1.5deg); box-shadow: 5px 6px 0 var(--ink); background: var(--pop); color: var(--pop-ink);
}
.dish:hover, .beer:hover, .tile:hover, .stat:hover, .superlatives > *:hover { transform: translate(-2px, -3px); box-shadow: 6px 7px 0 var(--ink); }
.tabs button:active, .qn label:active, .options span:active, .vote:active, .emoji-row button:active, .item:active, .av3d-card:active {
  transform: translate(3px, 3px) scale(.97); box-shadow: 0 0 0 var(--ink);
}
.tabs button[aria-selected="true"], .tabs button[aria-pressed="true"] { transform: translate(2px, 2px); box-shadow: 1px 1px 0 var(--ink); }
#nb-shape { border-radius: 18px; box-shadow: inset 3px 3px 0 rgb(0 0 0 / .08); }
@media (prefers-reduced-motion: reduce) {
  .tabs button, .qn label, .options span, .vote, .dish, .beer, .item, .av3d-card, .emoji-row button, .tile, .stat { transition: none; }
}

/* ======================================================================
   Identity layer: STL Daily stops looking like a newspaper bolted onto a
   website and starts looking like one St. Louis thing. Three materials the
   city is actually made of, used consistently:

     brick      red clay walls, the gutters and the footers
     newsprint  the paper the sheet is printed on, laid ON the brick
     mosaic     glazed tile and grout, the Basilica's trick, for panels
                plus two local patterns: the checkerboard and the Arch

   Everything below is additive and rides on the tokens the zones already
   use, so no page has to change.
   ====================================================================== */

:root {
  --brick: #a8402b;
  --brick-dk: #7e2d20;
  --mortar: #cdbfa8;
  --limestone: #e7dfcf;
  --river: #3d7fa6;
  --mosaic: #2f6f8f;
  --gold: #e0a82e;
  --ivy: #2f7d4f;

  /* red stays the accent name every zone already uses; it's brick now */
  --red: #a8402b;
  --pop: var(--gold);

  /* ---- textures (inline SVG so nothing extra loads) ---- */
  --tex-tile: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18'%3E%3Cg fill='none' stroke='%231b1a17' stroke-width='1' opacity='.07'%3E%3Cpath d='M0 9h18M9 0v18'/%3E%3C/g%3E%3C/svg%3E");
  --tex-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cg fill='%231b1a17' opacity='.05'%3E%3Crect width='10' height='10'/%3E%3Crect x='10' y='10' width='10' height='10'/%3E%3C/g%3E%3C/svg%3E");
  --tex-halftone: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Ccircle cx='2' cy='2' r='1' fill='%231b1a17' opacity='.09'/%3E%3Ccircle cx='6' cy='6' r='1' fill='%231b1a17' opacity='.09'/%3E%3C/svg%3E");
  /* Diagonal hairlines, like the edge of a ticket. */
  --tex-diag: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3E%3Cg stroke='%231b1a17' stroke-width='1' opacity='.06'%3E%3Cpath d='M0 10 L10 0M-1 1 L1 -1M9 11 L11 9'/%3E%3C/g%3E%3C/svg%3E");
  /* Little crosses, the way a puzzle grid marks its squares. */
  --tex-cross: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22'%3E%3Cg stroke='%231b1a17' stroke-width='1' opacity='.07'%3E%3Cpath d='M9 11h4M11 9v4M-2 0h4M0 -2v4M20 0h4M22 -2v4M-2 22h4M0 20v4M20 22h4M22 20v4'/%3E%3C/g%3E%3C/svg%3E");
  /* Rings left by a wet glass. */
  --tex-rings: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='58' height='58'%3E%3Ccircle cx='29' cy='29' r='17' fill='none' stroke='%231b1a17' stroke-width='1.5' opacity='.04'/%3E%3C/svg%3E");
  --tex-fiber: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.75' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 .18 0 0 0 0 .16 0 0 0 0 .12 0 0 0 .1 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23f)'/%3E%3C/svg%3E");
  --checker-rule: repeating-linear-gradient(90deg, var(--ink) 0 11px, var(--gold) 11px 22px);
}
:root[data-theme="dark"] {
  --brick: #8c3626; --brick-dk: #5e2318; --mortar: #43403a; --limestone: #2a2824;
  --red: #c9513a; --mosaic: #4f93b4; --gold: #d9a637; --pop: #d9a637;
}

/* ---- the sheet sits on a brick wall ----
   The wall is a blurred backdrop, not a pattern to read: it lives on its own
   fixed layer so it can be blurred and dimmed without touching the text that
   sits over it. Blurring a background-image is impossible; blurring a layer
   is not. */
/* The page used to sit on a tiled brick wall. At 120x56 the tile repeated
   visibly on a tall page and the seams drifted against the scrolling paper,
   which read as a broken background rather than a wall. It is now a quiet
   paper surround that has nothing to misalign. */
body { background: var(--paper-3, #cfc7b8); }
body::before {
  content: ""; position: fixed; inset: 0; z-index: -2; pointer-events: none;
  background: linear-gradient(170deg, #d8d1c3 0%, #c9c1b1 55%, #bdb4a3 100%);
  background-image: var(--grain), linear-gradient(170deg, #d8d1c3 0%, #c9c1b1 55%, #bdb4a3 100%);
}
body::after { /* depth: light from the top, shadow into the corners */
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(ellipse at 50% -10%, rgb(255 236 200 / .16), transparent 55%),
    radial-gradient(ellipse at 50% 0%, transparent 40%, rgb(0 0 0 / .45));
}
:root[data-theme="dark"] body::before { background-color: #15140f; }
.topbar, .ticker { background-image: var(--tex-fiber); background-color: var(--paper); }
main.page, .page {
  position: relative;
  background: var(--paper) var(--tex-fiber);
  box-shadow: 0 0 0 1px rgb(0 0 0 / .25), 14px 0 30px rgb(0 0 0 / .28), -14px 0 30px rgb(0 0 0 / .28);
}
/* torn deckle edge where the paper meets the wall */
.page::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -10px; height: 10px;
  background:
    radial-gradient(6px 10px at 6px 0, var(--paper) 98%, transparent) repeat-x 0 0 / 14px 10px;
  opacity: .9; pointer-events: none;
}

/* ---- masthead: an arch keyline and a checkerboard rule ---- */
.topbar { border-bottom: 0; box-shadow: inset 0 -3px 0 var(--ink); }
.brand { position: relative; }
.brand::before { /* the Arch, hairline, behind the nameplate */
  content: ""; position: absolute; left: 50%; top: 48%; translate: -50% -50%;
  width: 190%; aspect-ratio: 2.4 / 1; pointer-events: none; opacity: .13;
  background: radial-gradient(ellipse at 50% 100%, transparent 0 74%, var(--ink) 74% 76.5%, transparent 77%);
}
.topbar nav { border-top: 0; box-shadow: inset 0 2px 0 var(--ink); }
.topbar::after {
  content: ""; grid-area: nav; align-self: end; height: 6px; margin: 0 -28px -3px;
  background: var(--checker-rule); border-block: 1.5px solid var(--ink);
}
@media (max-width: 720px) { .topbar::after { margin: 0 -14px -3px; } }

/* ---- panels become glazed tile with grout ---- */
/* Tile texture, the zone's own tint, and a soft diagonal sheen. The hard ink
   shadow IS the panel: it is what makes a card read as a comic panel instead of
   a web box, so it stays a solid colour rather than fading to a percentage that
   vanishes against dark paper. */
.card {
  background: var(--card-tex, var(--tex-tile)),
    linear-gradient(135deg, var(--card-bg, var(--card-paper)) 0%, var(--card-sheen) 100%);
  box-shadow: 4px 4px 0 var(--ink), inset 0 0 0 1px var(--card-rim);
}
.card:hover { box-shadow: 7px 8px 0 var(--ink), inset 0 0 0 1px var(--card-rim-hover); }

/* A glow that follows the cursor, only where there is a cursor to follow. A
   touch screen never moves a pointer across a card, so --x/--y would keep their
   defaults and every card would wear a bright blob in the middle of it forever
   — which is what most of our traffic was seeing. */
@media (hover: hover) and (pointer: fine) {
  .card {
    background:
      radial-gradient(600px circle at var(--x, 50%) var(--y, 50%), var(--glow-on, transparent), transparent 40%),
      var(--card-tex, var(--tex-tile)),
      linear-gradient(135deg, var(--card-bg, var(--card-paper)) 0%, var(--card-sheen) 100%);
  }
  /* Lit only while the pointer is actually on the card. Until then --x/--y
     still hold their 50% defaults, and a glow drawn there is just a bright
     blob parked in the middle of every panel on the page. */
  .card:hover { --glow-on: var(--glow); }
}
.card h2 { background: var(--gold); }
/* Grout seam along the top of every panel. Absolute, so a card whose first
   child is artwork (the profile banner) is not pushed around by it. */
.card { position: relative; }
.card::before {
  content: ""; position: absolute; left: 10px; right: 10px; top: 5px; height: 4px; border-radius: 99px;
  background: var(--checker-rule); opacity: .5; pointer-events: none;
}
.card.bar-week::before, .gate .card::before, .card.hero::before { display: none; }
.card.hero, .card:has(> .hero-banner) { overflow: hidden; }

/* ---- every zone's panels wear that zone ----
   One crosshatch on every card across nine pages made the whole site read as
   the same spreadsheet in different colours. A panel now takes its grain and
   its tint from the page it is standing on: a diner tablecloth on the Secret
   Menu, a street grid on Parking, glass rings on Bars.

   What stays the same everywhere is the hard ink shadow and the grout seam
   along the top. Those are the newspaper, and the site still has to look like
   one paper rather than nine sites.

   --card-tint is mixed faintly into the panel and used whole for its heading
   pill. The front page overrides --card-bg per card (see today.css), which
   wins over the zone tint, because each card there is its own section. */
:root { --card-paper: color-mix(in srgb, var(--card-tint, var(--paper-2)) 9%, var(--paper-2)); }

body[data-zone="today"]   { --card-tex: var(--tex-halftone); --card-tint: #c9b89a; }
body[data-zone="tonight"] { --card-tex: var(--tex-diag);     --card-tint: #b9a9e8; }
body[data-zone="games"]   { --card-tex: var(--tex-cross);    --card-tint: #74c08c; }
body[data-zone="parking"] { --card-tex: var(--tex-tile);     --card-tint: #cfcabc; }
body[data-zone="beer"]    { --card-tex: var(--tex-rings);    --card-tint: #e0a82e; }
body[data-zone="dates"]   { --card-tex: none;                --card-tint: #d98aa0; }
body[data-zone="menu"]    { --card-tex: var(--tex-check);    --card-tint: #d98f6a; }
body[data-zone="fix"]     { --card-tex: var(--tex-tile);     --card-tint: #93c6dd; }
body[data-zone="me"]      { --card-tex: var(--tex-fiber);    --card-tint: #e8b59a; }
/* The room behind the door: low light, old paper, nothing bright. */
body[data-zone="speakeasy"] { --card-tex: var(--tex-fiber);  --card-tint: #8c6f4a; }

/* The heading pill picks up the zone colour; a page that names no tint keeps
   the gold the masthead uses. */
.card h2 { background: var(--card-tint, var(--gold)); }

/* Dark paper takes a much weaker tint: the same 9% that reads as a hint on
   newsprint reads as a coloured box on ink. */
:root[data-theme="dark"] { --card-paper: color-mix(in srgb, var(--card-tint, var(--paper-2)) 14%, var(--paper-2)); }


/* ---- headlines get painted-brick weight, not a drop shadow gimmick ---- */
.page h1 {
  text-shadow: 2px 2px 0 var(--gold), 4px 4px 0 rgb(27 26 23 / .25);
  -webkit-text-stroke: 0.5px var(--ink);
}
.ink-block { background-image: var(--tex-halftone); box-shadow: 7px 7px 0 var(--brick); }
.kicker { color: var(--brick); }
:root[data-theme="dark"] .kicker { color: var(--gold); }

/* ---- the ticker: a painted band, flat on purpose ----
   Brick behind moving type is unreadable, so the band keeps the wall's color
   and drops its texture. */
.ticker {
  background: linear-gradient(180deg, var(--brick) 0%, var(--brick-dk) 100%);
  background-image: linear-gradient(180deg, rgb(255 255 255 / .1), transparent 45%), linear-gradient(180deg, var(--brick), var(--brick-dk));
  color: #fff4e3; box-shadow: inset 0 -2px 0 rgb(0 0 0 / .35);
}
.ticker .tk { text-shadow: 0 1px 0 rgb(0 0 0 / .45); }
.ticker .tag { background: var(--gold); color: var(--ink); text-shadow: none; }
.ticker .run span::before { color: var(--gold); }

/* ---- a reusable arch divider: <div class="arch-rule"></div> ---- */
.arch-rule {
  height: 34px; margin: 26px 0; border-bottom: 2.5px solid var(--ink);
  background: radial-gradient(ellipse at 50% 100%, transparent 0 70%, var(--ink) 70% 74%, transparent 74%);
}

/* ---- footers / page bottom: hand back to the wall ---- */
.page > :last-child { margin-bottom: 0; }

@media (max-width: 720px) {
  .page { box-shadow: none; }
  .page::after { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  body { background-attachment: scroll; }
}

/* ======================================================================
   The headline train. The band is a strip of dusk sky over a track; a
   locomotive pulls one boxcar per headline across it, puffing smoke.

   Two identical trains sit in `.run` and the strip is translated by exactly
   half its width, so the second train is already on screen when the first
   leaves and the loop never shows bare track.
   ====================================================================== */

.ticker {
  /* The smoke leaves the band and drifts over the masthead, so the band cannot
     clip its own top edge — but the train behind it is thousands of pixels
     wide and must not be allowed to widen the page. `overflow-x: clip` cuts
     it off sideways WITHOUT making a scroll container (which is the one thing
     `hidden` cannot do), and that is what lets the other axis stay visible so
     the plume can climb out. On a browser too old for `clip` both axes end up
     clipped, which is simply the band as it was. */
  position: relative; display: flex; align-items: stretch;
  overflow-x: clip; overflow-y: visible;
  /* Above the sticky masthead (z 1000) so the plume reads over the tabs. The
     moment the page scrolls, shell.js marks the body and the band drops back
     under the masthead, because a headline strip sliding over a stuck
     masthead looks broken. */
  z-index: 1001;
  min-height: 108px; border-bottom: var(--o); color: #fff4e3;
  /* dusk over the river: amber at the horizon, deep blue overhead */
  background:
    linear-gradient(180deg, #1d2b4a 0%, #3c4d74 38%, #8a5f6a 64%, #d8864a 82%, #f0b45c 100%);
  box-shadow: inset 0 -3px 0 rgb(0 0 0 / .35);
}
:root[data-theme="dark"] .ticker {
  background: linear-gradient(180deg, #0d1324 0%, #1d2740 42%, #43304a 68%, #7a4530 86%, #a85f2c 100%);
}
/* A flat skyline on the far bank. It does NOT move: the band is one fixed
   view of a crossing, so the only thing travelling through it is the train.
   Scrolling the ground and the sleepers made the train look like it was
   standing still on a treadmill, which is the opposite of the intent. */
.ticker::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 26px; height: 30px; opacity: .3; pointer-events: none;
  background-repeat: repeat-x; background-size: 420px 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='420' height='30'%3E%3Cg fill='%231b1a17'%3E%3Crect x='10' y='12' width='26' height='18'/%3E%3Crect x='44' y='4' width='16' height='26'/%3E%3Crect x='66' y='16' width='34' height='14'/%3E%3Crect x='112' y='8' width='20' height='22'/%3E%3Cpath d='M150 30 q30-30 60 0 z'/%3E%3Crect x='222' y='14' width='30' height='16'/%3E%3Crect x='260' y='6' width='14' height='24'/%3E%3Crect x='284' y='18' width='40' height='12'/%3E%3Crect x='336' y='10' width='22' height='20'/%3E%3Crect x='368' y='17' width='40' height='13'/%3E%3C/g%3E%3C/svg%3E");
}

/* the station sign stays put at the left */
.ticker .tag {
  position: relative; z-index: 3; flex: none; display: flex; align-items: center; padding: 4px 18px 4px 14px;
  background: var(--gold); color: var(--ink); border-right: var(--o); border-radius: 0 999px 999px 0;
  font-family: var(--font-body); font-weight: 700; font-size: 12px; letter-spacing: .16em; text-transform: uppercase;
  white-space: nowrap; text-shadow: none; align-self: center; box-shadow: 3px 3px 0 rgb(0 0 0 / .3);
}

/* ---- the track ---- */
.ticker .rails {
  position: absolute; left: 0; right: 0; bottom: 0; height: 22px; pointer-events: none; z-index: 1;
  /* Sleepers, then ballast under them — both painted, both still. */
  background:
    repeating-linear-gradient(90deg, #6b5340 0 14px, transparent 14px 40px) 0 6px / 40px 9px repeat-x,
    linear-gradient(180deg, #8d7a62, #6a5944);
}
.ticker .rails::before, .ticker .rails::after { /* the two rails */
  content: ""; position: absolute; left: 0; right: 0; height: 3px; background: #d8d2c6;
  box-shadow: 0 1px 0 rgb(0 0 0 / .45);
}
.ticker .rails::before { top: 3px; }
.ticker .rails::after { top: 15px; background: #b3aa9a; }

/* The window the train runs through.

   `min-width: 0` is the whole of why the train used to barely appear on a
   phone. .track is a flex item holding .run, which is `width: max-content` —
   thousands of pixels of train — and a flex item's automatic minimum size is
   its content's max-content width. So .track resolved to about 3300px inside a
   375px band: the ticker's own clip stopped the page scrolling, but the train
   was animating across a track ten times wider than the screen and spent most
   of its loop somewhere off to the right. Resetting the minimum makes the
   track the width it looks like it is.

   It does NOT clip. The ticker above it already does, and it must be the one
   that does: a clip here lands exactly on the station sign's right edge, so
   the locomotive vanished at a hard vertical line beside the sign instead of
   passing behind it. The sign sits at z-index 3 with an opaque background, so
   with the clip moved up a level the train slides under it the way the band
   always looked like it should. */
.ticker .track {
  position: relative; z-index: 2; flex: 1; min-width: 0;
  overflow: visible; padding-left: 80px; display: flex; align-items: flex-end;
}
.ticker .run {
  /* No padding here: the loop translates this element by exactly half its own
     width, so anything added to its width opens a gap at the seam. The start
     offset belongs on .track instead. */
  display: flex; align-items: flex-end; width: max-content; padding: 0 0 11px;
  animation: train var(--ticker-dur, 44s) linear infinite;
}
@keyframes train { to { transform: translateX(-50%); } }
.ticker:hover .run, .ticker:focus-within .run, .ticker.held .run { animation-play-state: paused; }
.ticker:hover .loco .w, .ticker.held .loco .w { animation-play-state: paused; }

.ticker .train { display: flex; align-items: flex-end; padding-right: 90px; }

/* ---- the locomotive ---- */
.ticker .loco { position: relative; flex: none; margin-right: 8px; }
.loco-svg { display: block; height: 58px; width: auto; overflow: visible; }
.loco-svg .body { fill: #23262e; stroke: #0f1014; stroke-width: 2; }
.loco-svg .ink { fill: #a8402b; stroke: #0f1014; stroke-width: 2; }
.loco-svg .trim { fill: #e0a82e; }
.loco-svg .trim2 { fill: #a8402b; stroke: #0f1014; stroke-width: 2; }
.loco-svg .deck { fill: #3a2f27; stroke: #0f1014; stroke-width: 2; }
.loco-svg .lamp { fill: #ffe9a8; stroke: #0f1014; stroke-width: 2; filter: drop-shadow(-3px 0 7px #ffd97a); }
.loco-svg .window { fill: #8fd3f0; stroke: #0f1014; stroke-width: 2; }
.loco-svg .w circle { fill: #2b2f38; stroke: #0f1014; stroke-width: 2.5; }
.loco-svg .w .spoke { stroke: #8d8577; stroke-width: 2; fill: none; }
.loco-svg .w { transform-box: fill-box; transform-origin: center; animation: wheel-spin .55s linear infinite; }
/* Nothing on rails is perfectly still: the body rides on springs. One pixel,
   in time with the drivers, is the difference between a train and a decal. */
.loco-svg .loco-body { transform-box: fill-box; animation: loco-bob .55s ease-in-out infinite; }
@keyframes loco-bob { 50% { transform: translateY(0.9px); } }
.ticker:hover .loco-svg .loco-body, .ticker.held .loco-svg .loco-body { animation-play-state: paused; }
.loco-svg .w.sm { animation-duration: .4s; }
@keyframes wheel-spin { to { transform: rotate(-360deg); } }

/* ---- smoke ----
   A plume is not one puff repeated: each one leaves the stack a little
   different and the older ones have drifted further back and spread wider.
   Eleven puffs, each with its own pace, drift, sway and size, on negative
   delays so the column is already full on the first frame.

   The plume now climbs clear OUT of the band and over the nav tabs, which is
   what makes the strip read as a thing in front of the page rather than a
   stripe printed on it. It is tuned to be nearly gone by the time it reaches
   the masthead's own top edge, so it thins out instead of ending in a line. */
.ticker .smoke { position: absolute; left: 31px; top: 2px; width: 2px; height: 2px; pointer-events: none; }
.ticker .smoke i {
  position: absolute; left: 0; top: 0; width: 15px; height: 15px; margin: -8px 0 0 -8px; border-radius: 50%;
  /* Every one of these is set per puff by shell.js. */
  scale: var(--size, 1);
  background: radial-gradient(circle at 36% 32%, rgb(252 250 246 / .98), rgb(178 172 162 / .9) 54%, rgb(150 144 134 / 0) 74%);
  filter: drop-shadow(0 1px 1px rgb(40 38 34 / .2));
  opacity: 0;
  animation: puff var(--dur, 2.2s) cubic-bezier(.32, .5, .38, 1) infinite;
  /* Negative delays, staggered by a quarter of a wheel turn, so the column is
     already full on the first frame and the chuffs line up with the drivers. */
  animation-delay: calc(var(--i) * -0.1375s);
}

/* A puff leaves the stack small, dense and sharp, and ends large, faint and
   soft. Blurring as it expands is most of what separates smoke from a circle:
   real smoke loses its edge as it spreads. */
@keyframes puff {
  0% { opacity: 0; transform: translate(0, 6px) scale(.26) rotate(0deg); filter: blur(0); }
  9% { opacity: var(--peak, .9); }
  50% {
    opacity: calc(var(--peak, .9) * .55);
    transform: translate(calc(var(--drift) * .42 + var(--sway)), calc(var(--rise) * .44)) scale(calc(var(--grow) * .48)) rotate(16deg);
    filter: blur(1.6px);
  }
  76% { opacity: calc(var(--peak, .9) * .22); }
  92% { opacity: .05; }
  100% {
    opacity: 0;
    transform: translate(var(--drift), var(--rise)) scale(var(--grow)) rotate(32deg);
    filter: blur(4px);
  }
}

/* ---- boxcars ---- */
.ticker .car { position: relative; flex: none; display: flex; align-items: flex-end; margin-right: 26px; padding-bottom: 7px; }
.ticker .car .coupler {
  position: absolute; left: -22px; bottom: 16px; width: 22px; height: 5px; border-radius: 2px;
  background: #4a4238; box-shadow: 0 1px 0 rgb(0 0 0 / .4);
}
.ticker .car-body {
  position: relative; display: flex; align-items: center; min-height: 40px; padding: 7px 18px;
  border: 2.5px solid #14151a; border-radius: 8px 8px 4px 4px;
  background: linear-gradient(180deg, rgb(255 255 255 / .22), transparent 42%), var(--car);
  box-shadow: 0 3px 0 rgb(0 0 0 / .35), inset 0 -6px 0 rgb(0 0 0 / .14);
}
.ticker .car-body::before, .ticker .car-body::after { /* plank seams */
  content: ""; position: absolute; top: 6px; bottom: 6px; width: 2px; background: rgb(0 0 0 / .18);
}
.ticker .car-body::before { left: 8px; }
.ticker .car-body::after { right: 8px; }
.ticker .car .wheel {
  position: absolute; bottom: 0; left: 22px; width: 15px; height: 15px; border-radius: 50%;
  background:
    linear-gradient(0deg, transparent 45%, #18191e 45% 55%, transparent 55%),
    linear-gradient(90deg, transparent 45%, #18191e 45% 55%, transparent 55%),
    radial-gradient(circle at 50% 50%, #8d8577 0 22%, #2b2f38 23%);
  border: 2px solid #14151a; animation: wheel-spin .55s linear infinite;
}
.ticker .car .wheel.r { left: auto; right: 22px; }
.ticker:hover .car .wheel, .ticker.held .car .wheel { animation-play-state: paused; }
.ticker .c-brick { --car: #a8402b; }
.ticker .c-gold  { --car: #d79b24; }
/* cream on gold is too thin to read; that one car gets ink lettering */
.ticker .c-gold .tk { color: #1b1a17; text-shadow: 0 1px 0 rgb(255 255 255 / .4); }
.ticker a.c-gold:hover .tk, .ticker .c-gold a.tk:hover { color: #3a2a08; }
.ticker .c-river { --car: #2f6f8f; }
.ticker .c-ivy   { --car: #2f7d4f; }
.ticker .c-plum  { --car: #6b4a8c; }
.ticker .c-rust  { --car: #b4652c; }
.ticker .tk {
  font-family: var(--font-hand); font-size: 20px; line-height: 1.1; color: #fff6e8; text-decoration: none;
  white-space: nowrap; text-shadow: 0 1px 0 rgb(0 0 0 / .5); border: 0; padding: 0; display: inline-block;
  transition: transform .25s var(--spring), color .15s;
}
.ticker .tk::after { content: none; }
.ticker a.tk:hover, .ticker a.tk:focus-visible { color: #ffe08a; transform: translateY(-2px) scale(1.04); outline: none; }
.ticker a.tk:focus-visible { text-decoration: underline; text-underline-offset: 4px; }
.ticker .run span::before { content: none; }

@media (max-width: 720px) {
  .ticker { min-height: 88px; }
  .loco-svg { height: 48px; }
  .ticker .tag { font-size: 10px; padding: 3px 12px 3px 10px; letter-spacing: .12em; }
  .ticker .tk { font-size: 17px; }
  .ticker .car-body { min-height: 34px; padding: 6px 14px; }
}

/* Reduced motion: the train parks at the platform and the first headlines
   stay readable. Nothing moves, including the smoke. */
@media (prefers-reduced-motion: reduce) {
  .ticker .run { animation: none; transform: none; }
  .loco-svg .w, .ticker .car .wheel, .ticker .smoke i { animation: none; }
  .ticker .smoke { display: none; }
  .ticker .train + .train { display: none; }
  /* With nothing moving the band is a scrollable list of headlines instead,
     so the track goes back to being a real scroll container. */
  .ticker { overflow: hidden; }
  .ticker .track { overflow-x: auto; overflow-y: hidden; scrollbar-width: none; min-width: 0; }
}

/* Once the page moves, the headline band tucks back under the sticky masthead.
   Standing at the top of the page the smoke belongs over the tabs; mid-scroll
   a band riding over a stuck masthead is just a bug. shell.js sets the class. */
body.scrolled .ticker { z-index: 999; }

/* ---- circulation counter ----
   A paper prints its circulation; this one does it down the side of the page.
   Rotated and fixed on wide screens, a small chip in the corner on a phone. */
.visits {
  position: fixed; z-index: 900; right: 0; top: 52%; transform: translateY(-50%);
  display: flex; align-items: center; gap: 8px;
  padding: 7px 12px; background: var(--paper); color: var(--ink);
  border: 2.5px solid var(--ink); border-right: 0; border-radius: 14px 0 0 14px;
  box-shadow: -4px 4px 0 var(--ink); writing-mode: vertical-rl; text-orientation: mixed;
  font-family: var(--font-body); cursor: default; user-select: none;
}
.visits .v-kicker { font-size: 10px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase; color: var(--red); }
.visits b { font-family: var(--font-display); font-weight: 900; font-size: 20px; font-variant-numeric: tabular-nums; letter-spacing: .02em; }
.visits .v-unit { font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; }
.visits .v-today { font-size: 10px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft); }
.visits .v-today::before { content: "·"; margin-right: 5px; opacity: .6; }

@media (max-width: 980px) {
  .visits {
    top: auto; bottom: 12px; right: 12px; transform: none; writing-mode: horizontal-tb;
    gap: 6px; padding: 5px 11px; border-right: 2.5px solid var(--ink); border-radius: 999px;
    box-shadow: 3px 3px 0 var(--ink); opacity: .92;
  }
  .visits b { font-size: 15px; }
  .visits .v-kicker { display: none; }
}

/* ---- profile banner ----
   Three depth layers that follow the pointer. --bn-x is set by me.js from the
   cursor's position across the banner (-1 to 1); each layer takes a different
   share of it, which is what sells the depth. Lit windows blink on their own
   so the scene is alive even when nothing is moving. */
.hero-banner { position: relative; overflow: hidden; background: var(--paper-2); }
.banner-scene { width: 100%; height: 100%; display: block; }
.bn-layer { transition: transform .45s cubic-bezier(.2, .8, .3, 1); will-change: transform; }
/* Each layer is scaled a touch so sliding it never exposes the edge of the
   drawing — without that, parallax leaves a bare strip at one side. */
.bn-layer { transform-origin: 50% 100%; }
.bn-layer.far { transform: translateX(calc(var(--bn-x, 0) * 5px)) scale(1.03); }
.bn-layer.mid { transform: translateX(calc(var(--bn-x, 0) * 13px)) scale(1.05); }
.bn-layer.near { transform: translateX(calc(var(--bn-x, 0) * 26px)) scale(1.09); }
.bn-window { animation: bn-window 7s steps(1, end) infinite; animation-delay: var(--delay, 0s); }
@keyframes bn-window { 0%, 58% { opacity: 1; } 60%, 72% { opacity: .15; } 74%, 100% { opacity: 1; } }
/* A soft vignette keeps the avatar readable where it overlaps the scene. */
.hero-banner::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg, transparent 55%, rgb(0 0 0 / .28));
}
@media (prefers-reduced-motion: reduce) {
  .bn-layer { transition: none; transform: none; }
  .bn-window { animation: none; }
}

/* ======================================================================
   MOBILE
   Most people open this on a phone, so the phone layout is not a squeezed
   desktop: the chrome above the content is cut down, the display type stops
   shouting, and anything you tap is at least 44px tall.

   Everything below is deliberately last in the file so it wins without
   !important.
   ====================================================================== */
@media (max-width: 720px) {
  /* ---- masthead: one compact row, tabs that scroll with a hint ---- */
  /* 14px, not 12: the masthead's own underline rule is pulled out by -14px
     further up this file, and a mismatch here pushed 2px of horizontal scroll
     onto every page. */
  .topbar { padding: 6px 14px 0; }
  .brand { font-size: 23px; }
  .topbar nav { margin: 6px -14px 0; padding: 0 14px; gap: 2px; scroll-snap-type: x proximity; }
  .topbar nav a {
    padding: 9px 11px; font-size: 11px; letter-spacing: .08em; min-height: 44px; scroll-snap-align: start;
  }
  /* The old fade ended at 85%, which hid the fact that the row scrolls at all.
     A harder edge reads as "there is more over here". */
  .topbar nav { mask-image: linear-gradient(90deg, #000 92%, transparent); }
  .me-chip { padding: 2px 9px 2px 2px; font-size: 11px; }
  .me-chip .av { width: 26px; height: 26px; font-size: 15px; }

  /* ---- headline band: shorter, so it costs less of a small screen ---- */
  .ticker { min-height: 68px; }
  .loco-svg { height: 38px; }
  .ticker .tk { font-size: 15px; }
  .ticker .car-body { min-height: 28px; padding: 5px 11px; border-radius: 6px 6px 3px 3px; }
  .ticker .car { margin-right: 18px; }
  .ticker .track { padding-left: 46px; }
  .ticker .rails { height: 16px; }
  .ticker .tag { font-size: 9px; padding: 3px 10px 3px 8px; letter-spacing: .1em; }

  /* ---- the page itself ---- */
  .page { padding: 14px 14px 20px; }

  /* ---- display type: loud, not deafening ----
     The stacked text-shadow reads as a doubled, broken render once the
     letters are small, so it tightens to a single offset. */
  .page h1 { text-shadow: 1.5px 1.5px 0 var(--gold); -webkit-text-stroke: 0; }
  .ink-block { padding: 12px 14px; margin-bottom: 16px; box-shadow: 4px 4px 0 var(--brick); }
  .ink-block .huge { letter-spacing: -0.02em; }
  .ink-block .under { margin-top: 8px; padding-top: 6px; gap: 2px 10px; font-size: 11px; }
  .lede { font-size: 16px; }

  /* ---- anything you tap ---- */
  /* 46, not 44: the borders here are 2.5px, so a 44px min-height renders at
     43.5 and rounds under the target a thumb needs. */
  .btn, .chip, select, input[type="search"], input[type="text"], input[type="datetime-local"] { min-height: 46px; }
  .btn { padding: 11px 16px; }

  /* ---- dialogs behave like sheets ---- */
  dialog:not(.gate) {
    width: 100%; max-width: none; margin: auto 0 0; border-radius: 20px 20px 0 0;
    max-height: 88vh; overflow-y: auto;
  }

  /* ---- the circulation counter joins the page instead of floating over it ----
     Fixed position on a phone means it sits on top of whatever you are
     reading. It is the last element in the body, so dropping the positioning
     lands it at the foot of the page where a masthead figure belongs. */
  .visits {
    position: static; transform: none; margin: 18px 14px 0; opacity: 1;
    display: flex; justify-content: center; gap: 8px; border-radius: 999px;
    border: 2.5px solid var(--ink); box-shadow: 3px 3px 0 var(--ink); padding: 8px 14px;
  }
  .visits .v-kicker { display: inline; font-size: 11.5px; }
  .visits .v-unit, .visits .v-today { font-size: 11.5px; }

  /* The wordmark is a link home; give it a real row to be tapped in. */
  .brand { display: inline-flex; align-items: center; min-height: 44px; }

  /* Headline links ride a moving train, so they are awkward targets at the
     best of times. Padding at least gives a thumb something to land on. */
  .ticker .tk { padding: 6px 0; }
  .ticker .tag { font-size: 10.5px; }
}

@media (max-width: 460px) {
  /* The nameplate is the single biggest thing between someone and the news.
     Capped here so the front page starts above the fold. */
  .ink-block .huge { font-size: 46px; }
  .page { padding: 12px 12px 18px; }
  .topbar nav a { padding: 9px 9px; }
}

/* ---- touch devices of any width ----
   The layout breakpoint is 720px, but a tablet at 768–1024 is still operated
   with a thumb. This sizes targets by INPUT METHOD rather than by width, so an
   iPad gets the desktop layout with hand-sized controls. */
@media (pointer: coarse) {
  .btn, .chip, button, select, summary,
  input[type="search"], input[type="text"], input[type="datetime-local"] { min-height: 46px; }
  .topbar nav a, .zone-jump a { min-height: 46px; display: inline-flex; align-items: center; justify-content: center; }
  .options label, .options span { min-height: 46px; }
  .ev .title { display: block; padding: 8px 0; min-height: 44px; }
  input[type="range"] { height: 44px; }
  label.check { display: flex; align-items: center; gap: 10px; min-height: 44px; }
  label.check input[type="checkbox"] { width: 22px; height: 22px; flex: none; }
}

/* ---- the wordmark waves ----
   shell.js wraps each letter in a .w span and sets --i. The wave runs once on
   load and again on hover, so the masthead has life without a permanent
   animation looping behind every page. */
.brand .w { display: inline-block; animation: brand-wave 1.1s var(--spring) both; animation-delay: calc(var(--i) * 45ms); }
.brand:hover .w, .brand:focus-visible .w { animation: brand-wave .9s var(--spring); animation-delay: calc(var(--i) * 38ms); }
@keyframes brand-wave {
  0% { transform: translateY(0) rotate(0); }
  35% { transform: translateY(-26%) rotate(-4deg); }
  65% { transform: translateY(9%) rotate(2deg); }
  100% { transform: translateY(0) rotate(0); }
}
.brand:hover { animation: none; }
@media (prefers-reduced-motion: reduce) { .brand .w { animation: none !important; } }

/* The station sign: full wording where there is room, a short form where there
   is not. At 390px the long version was taking half the band. */
.ticker .tag-short { display: none; }
@media (max-width: 720px) {
  .ticker .tag-long { display: none; }
  .ticker .tag-short { display: inline; }
  .ticker .tag { max-width: 34vw; }
}

/* ======================================================================
   ZONE IDENTITY

   Every page already declares itself on <body data-zone>. Until now that
   drove nothing: the zone colours were all aliases of the same red, so
   Tonight, Games and Parking were the same paper with different words.

   Each zone now owns a colour, and that one variable drives the things a
   reader uses to tell where they are: the current tab, the headline's
   shadow, the rule under it, the station sign on the headline band, and the
   accent inside the page's own components. The paper, the ink and the
   newsprint furniture stay identical, so it reads as one publication with
   named sections rather than eight different sites.
   ====================================================================== */
body { --zone: var(--red); --zone-ink: #fff4e3; }
body[data-zone="today"]   { --zone: #b8452c; }  /* brick, the masthead red */
body[data-zone="tonight"] { --zone: #4b3b8f; }  /* after dark */
body[data-zone="games"]   { --zone: #2f7d4f; }  /* felt green */
body[data-zone="parking"] { --zone: #2f6f8f; }  /* street sign blue */
body[data-zone="beer"]    { --zone: #b07d12; }  /* amber glass */
body[data-zone="menu"]    { --zone: #a8432b; }  /* provel red */
body[data-zone="fix"]     { --zone: #3d7fa6; }  /* blueprint */
body[data-zone="me"]      { --zone: #6b4a8c; }  /* plum */
/* The two machine-room pages. Slate, because neither is part of the paper —
   one reports on the press, the other runs it. */
body[data-zone="status"]  { --zone: #4a5a63; }  /* slate */
body[data-zone="admin"]   { --zone: #3a3f45; }  /* darker slate */

/* The tab you are on wears the zone's colour, so the masthead answers
   "where am I" without reading. */
.topbar nav a[aria-current="page"] { background: var(--zone); color: var(--zone-ink); border-color: var(--ink); }
.topbar nav a[aria-current="page"]:hover { background: var(--zone); color: var(--zone-ink); }
.topbar nav a:hover { color: var(--zone); }

.page h1 { text-shadow: 2px 2px 0 var(--zone), 4px 4px 0 rgb(27 26 23 / .22); }
.kicker { color: var(--zone); }
.ticker .tag { background: var(--zone); color: var(--zone-ink); }
.section-rule { border-bottom-color: var(--zone); }
.ink-block { box-shadow: 7px 7px 0 var(--zone); }
.lede::after {
  content: ""; display: block; width: 68px; height: 4px; margin-top: 10px; border-radius: 99px;
  background: var(--zone);
}
@media (max-width: 720px) { .page h1 { text-shadow: 1.5px 1.5px 0 var(--zone); } }

/* ---- feedback ----
   Mounted by shell.js on every zone, including the full-bleed ones that have
   no .page sheet to hang off. */
.feedback-bar { text-align: center; padding: 30px 0 10px; }
#feedback-dialog { max-width: 32rem; width: min(32rem, calc(100vw - 32px)); }
#feedback-dialog textarea {
  width: 100%; margin: 6px 0 4px; padding: 10px 12px; resize: vertical;
  font-family: var(--font-body); font-size: 16px; color: var(--ink);
  background: var(--paper); border: 2px solid var(--ink); border-radius: 12px 6px 14px 8px / 8px 14px 6px 12px;
}
#feedback-dialog textarea:focus-visible { outline: 3px solid var(--red); outline-offset: 2px; }
.fb-label {
  display: block; font-family: var(--font-body); font-weight: 700; font-size: 12px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-soft);
}
#feedback-dialog .small.err { color: var(--red); font-weight: 600; }

/* Leaflet draws its own zoom buttons at 26px, which is a mouse's target on a
   map you pan with a thumb. The three map pages all inherit this. */
@media (pointer: coarse) {
  .leaflet-touch .leaflet-bar a, .leaflet-bar a {
    width: 44px; height: 44px; line-height: 44px; font-size: 22px;
  }
}

/* ======================================================================
   TOUCH: the same life, on press

   Everything above that pops, lifts or tilts does it on :hover, and hover is
   a thing a mouse has. On a phone none of it ever fires, so the site a thumb
   gets is the same layout with the animation taken out — which is most of
   this site's character, and most of its traffic.

   A press is the touch equivalent of a hover: the moment the interface should
   acknowledge you. So every control that lifts under a pointer now presses
   under a finger. Deliberately the opposite direction — a hover lifts an
   element toward you, a press pushes it down, because that is what pressing a
   physical thing does and it reads correctly under a fingertip that is
   covering the element anyway.

   Only controls. A .card is not pressed, it is read, and a card that flinched
   when you touched it to scroll would be noise rather than life — the cards
   already have their entrance animation. Buttons and links are also the only
   elements :active fires reliably on across mobile browsers.
   ====================================================================== */
@media (hover: none) {
  .btn, .chip, .topbar nav a, .me-chip, .ticker a.tk, .av-ring, button, summary {
    /* No 300ms wait before the feedback: the delay is what makes a tap feel
       like it did not register. */
    touch-action: manipulation;
  }
  .btn:active {
    transform: translate(2px, 2px) scale(.97);
    box-shadow: 1px 1px 0 var(--ink);
    background: var(--red); border-color: var(--ink); color: #fff8ec;
  }
  .chip:active {
    transform: translate(1px, 2px) scale(.97);
    box-shadow: 1px 1px 0 var(--ink);
    background: var(--pop); color: var(--pop-ink);
  }
  .topbar nav a:active {
    transform: translateY(1px) scale(.97);
    background: var(--pop); color: var(--pop-ink); border-color: var(--ink);
  }
  .me-chip:active {
    transform: translate(1px, 1px) scale(.98);
    box-shadow: 2px 2px 0 var(--ink);
    background: var(--pop); color: var(--pop-ink);
  }
  .me-chip:active .av { transform: rotate(-14deg) scale(1.1); }
  .ticker a.tk:active { transform: scale(.96); color: #ffe08a; }
  .av-ring:active { transform: scale(1.08) rotate(-8deg); }
  summary:active { background: var(--pop); color: var(--pop-ink); }
  /* .dish, .beer, .bar, .stat and .tile lift under a pointer and are left
     alone here on purpose. They are an <li>, three <article>s and a <div> —
     rows you read, with their own buttons inside them. A row that flinched
     because a thumb landed on it mid-scroll would be noise, which is the same
     reason the cards are not in this block. */
  /* Each letter of the nameplate tips under a pointer; on a phone the one you
     actually touch tips. The rest of the word is left alone. */
  .ink-block .huge .ltr:active { transform: translateY(-10%) rotate(-5deg) scale(1.06); color: var(--pop); }

  /* The transition has to be quick enough to be seen at all: a finger is down
     for a fraction of a second, and a 200ms ease would still be animating
     when it lifts. */
  .btn, .chip, .topbar nav a, .me-chip, .ink-block .huge .ltr {
    transition-duration: 90ms;
  }
}

/* Reduced motion means no press animation either — the colour change on its
   own is still an acknowledgement, and it is the part that carries meaning. */
@media (prefers-reduced-motion: reduce) {
  .btn:active, .chip:active, .topbar nav a:active, .me-chip:active, .me-chip:active .av,
  .ticker a.tk:active, .av-ring:active, .ink-block .huge .ltr:active {
    transform: none !important;
  }
}

/* ======================================================================
   LOADING: a shape, not a sentence

   Every panel on the site used to say "Loading…" in muted type while its
   data arrived. On a fast connection that is a flash of the word "Loading";
   on a slow one it is a paragraph-shaped hole, and on a dead one it is
   indistinguishable from a panel that has quietly broken. Worse, the page
   reflows the moment the real content lands, so the thing you were about to
   tap moves.

   A skeleton fixes all three: it occupies the shape the content will occupy,
   it is obviously a placeholder rather than a result, and it is visibly
   alive. The sweep is the only motion — no pulse, because a panel that
   breathes next to a panel that does not reads as a fault.
   ====================================================================== */
.skel {
  --skel-ink: color-mix(in srgb, var(--ink) 11%, transparent);
  position: relative; overflow: hidden; border-radius: 8px;
  background: var(--skel-ink);
  /* It is furniture, not text: a screen reader hears the aria-busy on the
     panel and has no use for the bones. */
  color: transparent; user-select: none;
}
.skel::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 20%, color-mix(in srgb, var(--paper) 70%, transparent) 50%, transparent 80%);
  transform: translateX(-100%);
  animation: skel-sweep 1.4s ease-in-out infinite;
}
.skel.line { height: 14px; margin: 9px 0; }
.skel.line.title { height: 26px; width: 62%; margin: 4px 0 12px; border-radius: 6px; }
.skel.line.short { width: 38%; }
.skel.line.medium { width: 72%; }
.skel.dot { width: 34px; height: 34px; border-radius: 50%; margin: 0; }
.skel-row { display: grid; grid-template-columns: 34px 1fr; gap: 14px; align-items: start; padding: 14px 0; }
.skel-row + .skel-row { border-top: 1px solid var(--line); }
.skel-stack { display: grid; gap: 10px; }
@keyframes skel-sweep { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .skel::after { animation: none; opacity: .35; transform: none; } }

/* ======================================================================
   EMPTY STATES

   The other half of the same problem. A panel with nothing in it said
   "Nothing matches that right now." in grey, left-aligned, in the top-left
   corner of a box the size of a list — which looks like the list failed. An
   empty state is a small piece of design: centred, with a mark, a sentence
   that says what happened, and wherever possible the way out of it.
   ====================================================================== */
.nothing {
  display: grid; justify-items: center; gap: 6px; text-align: center;
  padding: 28px 20px; margin: 0;
  border: 2px dashed var(--line); border-radius: var(--wob, 14px);
  color: var(--ink-soft);
}
.nothing .mark { font-size: 30px; line-height: 1; filter: grayscale(.25); }
.nothing b { font-family: var(--font-display); font-weight: 900; font-size: 19px; color: var(--ink); text-transform: uppercase; letter-spacing: -.01em; }
.nothing p { margin: 0; font-size: 15px; max-width: 42ch; }
.nothing .btn { margin-top: 8px; }

/* ======================================================================
   DETAILS PANELS

   A <details class="card"> is a panel whose whole body can be shut. The
   heading pill is pulled 26px above the card's top edge, which is correct for
   a panel with content under it and wrong for one with nothing under it: the
   title floats while the box keeps its full padded height, and the result
   reads as a panel that failed rather than one you can open.

   Any card that is also a disclosure gets its padding from the summary and
   the body instead of from itself.
   ====================================================================== */
details.card { padding: 0; }
details.card > summary { cursor: pointer; list-style: none; }
details.card > summary::-webkit-details-marker { display: none; }
details.card > summary > h2 { margin: 0; }

/* Text that is only for a screen reader: a rank column that reads "—" to the
   eye has to read "unranked" to a voice. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ======================================================================
   COLLECTIBLE STAMPS

   One per bar you have ever told us you were at. The look is a rubber stamp
   rather than a badge or a card, because a stamp is the one collectible whose
   real-world version you get for *having been somewhere* — a passport, a
   loyalty card, the back of your hand at a venue door. A badge would read as
   something the site awarded you; a stamp reads as something the night left
   on you.

   Ink colours are deliberately slightly faded and the keyline is slightly
   broken, because a stamp that is perfectly crisp looks like a logo.
   ====================================================================== */
.stamp {
  --si: var(--red);
  margin: 0; display: grid; gap: 8px; justify-items: center; text-align: center;
  rotate: var(--tilt, -2deg);
  transition: rotate .35s var(--spring), scale .35s var(--spring);
}
@media (hover: hover) { .stamp:hover { rotate: 0deg; scale: 1.05; } }
.stamp-face {
  position: relative; display: grid; align-content: center; gap: 2px;
  width: 100%; aspect-ratio: 1; padding: 10px 12px;
  color: var(--si); border: 3px solid var(--si); border-radius: 50%;
  background: color-mix(in srgb, var(--si) 7%, transparent);
  /* The second keyline, drawn inside rather than as another element. */
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--si) 35%, transparent);
  overflow: hidden;
}
/* The ink is never perfectly even. A faint blotch, fixed per shape, is what
   stops four stamps in a row looking like four copies of a button. */
.stamp-face::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; opacity: .5;
  background:
    radial-gradient(60% 40% at 25% 20%, color-mix(in srgb, var(--paper) 65%, transparent), transparent 70%),
    radial-gradient(45% 35% at 80% 75%, color-mix(in srgb, var(--paper) 50%, transparent), transparent 70%);
}
.stamp-top, .stamp-date {
  font-family: var(--font-body); font-weight: 700; font-size: 9px; letter-spacing: .22em; text-transform: uppercase;
  opacity: .85;
}
.stamp-name {
  font-family: var(--font-display); font-weight: 900; font-size: 14px; line-height: 1.1;
  text-transform: uppercase; letter-spacing: -0.01em; word-break: normal; overflow-wrap: break-word; margin: 2px 0;
  max-width: 95%;
}
.stamp-hood { font-family: var(--font-body); font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase; opacity: .8; max-width: 90%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stamp-line {
  font-family: var(--font-hand); font-size: 14px; line-height: 1.25; color: var(--ink-soft);
  max-width: 24ch; word-break: normal; overflow-wrap: break-word;
}

/* Rarity Badge & Tiers for repeat visits */
.stamp-badge {
  position: absolute;
  bottom: 6px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-body);
  font-weight: 800;
  font-size: 8px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 1px 7px;
  border-radius: 8px;
  line-height: 1.2;
  z-index: 2;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.stamp-badge.rarity-rare {
  background: #2b3544;
  color: #e2e8f0;
  border: 1px solid #94a3b8;
}
.stamp-badge.rarity-epic {
  background: #78350f;
  color: #fef08a;
  border: 1px solid #eab308;
}
.stamp-badge.rarity-legendary {
  background: linear-gradient(135deg, #4c1d95, #7c3aed);
  color: #fff;
  border: 1px solid #c084fc;
}

/* Upgraded border/sheen for repeat visits */
.stamp.rarity-rare .stamp-face {
  box-shadow: inset 0 0 0 2.5px rgba(148, 163, 184, 0.75), 0 2px 8px rgba(0, 0, 0, 0.08);
}
.stamp.rarity-epic .stamp-face {
  box-shadow: inset 0 0 0 2.5px rgba(234, 179, 8, 0.85), 0 3px 10px rgba(234, 179, 8, 0.22);
}
.stamp.rarity-legendary .stamp-face {
  box-shadow: inset 0 0 0 2.5px rgba(192, 132, 252, 0.9), 0 3px 12px rgba(124, 58, 237, 0.3);
}

/* ---- the four shapes ---- */
.stamp-oval .stamp-face { border-radius: 50% / 62%; }
.stamp-shield .stamp-face { border-radius: 48% 48% 44% 44% / 18% 18% 72% 72%; }
.stamp-square .stamp-face { border-radius: 14px; aspect-ratio: 1 / .92; }

/* ---- the four inks ---- */
.stamp.ink-red { --si: #a6382a; }
.stamp.ink-ink { --si: #2f3a52; }
.stamp.ink-gold { --si: #8a6212; }
.stamp.ink-green { --si: #2c6245; }
:root[data-theme="dark"] .stamp.ink-red { --si: #e07a63; }
:root[data-theme="dark"] .stamp.ink-ink { --si: #8ea2cc; }
:root[data-theme="dark"] .stamp.ink-gold { --si: #d9ad4a; }
:root[data-theme="dark"] .stamp.ink-green { --si: #6fb98e; }

/* ---- sizes ---- */
.stamp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 18px 14px; }
.stamp.lg { gap: 12px; }
.stamp.lg .stamp-face { width: 210px; }
.stamp.lg .stamp-name { font-size: 22px; }
.stamp.lg .stamp-top, .stamp.lg .stamp-date { font-size: 11px; }
.stamp.lg .stamp-hood { font-size: 12px; }
.stamp.lg .stamp-line { font-size: 19px; max-width: 26ch; }

/* The moment it is earned: the stamp comes down on the page. */
.stamp.fresh { animation: stamp-down .5s cubic-bezier(.2, 1.5, .4, 1) backwards; }
@keyframes stamp-down {
  from { opacity: 0; scale: 2.1; rotate: -14deg; }
  70% { opacity: 1; scale: .94; }
  to { opacity: 1; scale: 1; rotate: var(--tilt, -2deg); }
}
@media (prefers-reduced-motion: reduce) {
  .stamp, .stamp.fresh { animation: none; transition: none; }
  .stamp:hover { rotate: var(--tilt, -2deg); scale: 1; }
}
@media (max-width: 420px) {
  .stamp-grid { grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); gap: 16px 10px; }
  .stamp.lg .stamp-face { width: 176px; }
}

/* Earned weekly podium title, shown in profiles and leaderboard reward previews. */
.title-weekly-glow { color:var(--warn-ink); text-shadow:0 0 8px color-mix(in srgb,var(--warn) 48%,transparent); animation:weekly-title-glow 3s ease-in-out 3 alternate; }
@keyframes weekly-title-glow { from { text-shadow:0 0 3px color-mix(in srgb,var(--warn) 22%,transparent); } to { text-shadow:0 0 12px color-mix(in srgb,var(--warn) 65%,transparent); } }
@media (prefers-reduced-motion:reduce) { .title-weekly-glow { animation:none; } }

.title-games-celestial,.title-games-circuit,.title-games-clockwork{font-weight:700;animation:weekly-title-glow 3s ease-in-out 3 alternate}
.title-games-celestial{text-shadow:0 0 10px #e8bd6788;color:var(--warn-ink)}
.title-games-circuit{text-shadow:0 0 10px #b58bd988;color:var(--ink)}
.title-games-clockwork{text-shadow:0 0 8px #83e7df88;color:var(--ink)}
@media(prefers-reduced-motion:reduce){.title-games-celestial,.title-games-circuit,.title-games-clockwork{animation:none}}

/* Category silhouettes remain recognizable across repeat-visit tiers. */
.stamp.category-bar { --si:#803c70; }
.stamp.category-bar .stamp-face { border-radius:50%; border-style:dashed; }
.stamp.category-restaurant { --si:#a8482e; }
.stamp.category-restaurant .stamp-face { border-radius:28%; border:5px double var(--si); outline:2px dashed var(--si); outline-offset:3px; }
.stamp.category-event { --si:#176f78; }
.stamp.category-event .stamp-face { border-radius:6px; border-inline:4px dotted var(--si); aspect-ratio:1/.86; background:radial-gradient(circle at 0 50%,var(--paper) 0 8px,transparent 9px),radial-gradient(circle at 100% 50%,var(--paper) 0 8px,transparent 9px),color-mix(in srgb,var(--si) 8%,transparent); }
.stamp-event-date { font-size:9px; font-weight:700; }
.stamp-edition { display:block; font:700 9px var(--font-body); letter-spacing:.06em; margin-top:5px; text-transform:uppercase; color:var(--si); }
:root[data-theme="dark"] .stamp.category-bar { --si:#d79bc8; }
:root[data-theme="dark"] .stamp.category-restaurant { --si:#ef9e7e; }
:root[data-theme="dark"] .stamp.category-event { --si:#7dd3d8; }
.stamp.fresh .stamp-face::after { animation:ink-settle .65s ease-out; }
@keyframes ink-settle { from {opacity:1} to {opacity:.5} }
.event-passport { margin-top:12px; }
.event-passport [data-event-stamp] { max-width:200px; margin:16px 0; }
@media(prefers-reduced-motion:reduce) { .stamp.fresh .stamp-face::after { animation:none; } }

.stamp.category-restaurant .stamp-face { clip-path:polygon(99.50% 50.00%,98.98% 51.92%,97.63% 53.75%,95.91% 55.43%,94.42% 57.04%,93.64% 58.68%,93.73% 60.50%,94.49% 62.55%,95.43% 64.76%,95.99% 66.97%,95.73% 68.94%,94.52% 70.52%,92.57% 71.69%,90.33% 72.59%,88.35% 73.50%,87.00% 74.72%,86.39% 76.44%,86.30% 78.62%,86.33% 81.03%,86.00% 83.28%,85.00% 85.00%,83.28% 86.00%,81.03% 86.33%,78.62% 86.30%,76.44% 86.39%,74.72% 87.00%,73.50% 88.35%,72.59% 90.33%,71.69% 92.57%,70.52% 94.52%,68.94% 95.73%,66.97% 95.99%,64.76% 95.43%,62.55% 94.49%,60.50% 93.73%,58.68% 93.64%,57.04% 94.42%,55.43% 95.91%,53.75% 97.63%,51.92% 98.98%,50.00% 99.50%,48.08% 98.98%,46.25% 97.63%,44.57% 95.91%,42.96% 94.42%,41.32% 93.64%,39.50% 93.73%,37.45% 94.49%,35.24% 95.43%,33.03% 95.99%,31.06% 95.73%,29.48% 94.52%,28.31% 92.57%,27.41% 90.33%,26.50% 88.35%,25.28% 87.00%,23.56% 86.39%,21.38% 86.30%,18.97% 86.33%,16.72% 86.00%,15.00% 85.00%,14.00% 83.28%,13.67% 81.03%,13.70% 78.62%,13.61% 76.44%,13.00% 74.72%,11.65% 73.50%,9.67% 72.59%,7.43% 71.69%,5.48% 70.52%,4.27% 68.94%,4.01% 66.97%,4.57% 64.76%,5.51% 62.55%,6.27% 60.50%,6.36% 58.68%,5.58% 57.04%,4.09% 55.43%,2.37% 53.75%,1.02% 51.92%,0.50% 50.00%,1.02% 48.08%,2.37% 46.25%,4.09% 44.57%,5.58% 42.96%,6.36% 41.32%,6.27% 39.50%,5.51% 37.45%,4.57% 35.24%,4.01% 33.03%,4.27% 31.06%,5.48% 29.48%,7.43% 28.31%,9.67% 27.41%,11.65% 26.50%,13.00% 25.28%,13.61% 23.56%,13.70% 21.38%,13.67% 18.97%,14.00% 16.72%,15.00% 15.00%,16.72% 14.00%,18.97% 13.67%,21.38% 13.70%,23.56% 13.61%,25.28% 13.00%,26.50% 11.65%,27.41% 9.67%,28.31% 7.43%,29.48% 5.48%,31.06% 4.27%,33.03% 4.01%,35.24% 4.57%,37.45% 5.51%,39.50% 6.27%,41.32% 6.36%,42.96% 5.58%,44.57% 4.09%,46.25% 2.37%,48.08% 1.02%,50.00% 0.50%,51.92% 1.02%,53.75% 2.37%,55.43% 4.09%,57.04% 5.58%,58.68% 6.36%,60.50% 6.27%,62.55% 5.51%,64.76% 4.57%,66.97% 4.01%,68.94% 4.27%,70.52% 5.48%,71.69% 7.43%,72.59% 9.67%,73.50% 11.65%,74.72% 13.00%,76.44% 13.61%,78.62% 13.70%,81.03% 13.67%,83.28% 14.00%,85.00% 15.00%,86.00% 16.72%,86.33% 18.97%,86.30% 21.38%,86.39% 23.56%,87.00% 25.28%,88.35% 26.50%,90.33% 27.41%,92.57% 28.31%,94.52% 29.48%,95.73% 31.06%,95.99% 33.03%,95.43% 35.24%,94.49% 37.45%,93.73% 39.50%,93.64% 41.32%,94.42% 42.96%,95.91% 44.57%,97.63% 46.25%,98.98% 48.08%); border:0; border-radius:0; outline:0; background:color-mix(in srgb,var(--si) 15%,var(--paper)); box-shadow:inset 0 0 0 6px var(--si); }
.stamp.category-restaurant .stamp-face::before { content:""; position:absolute; inset:12px; border:2px dashed var(--si); border-radius:50%; pointer-events:none; }

.stamp.edition-special { --si:#996b16; }
.stamp.edition-special .stamp-face { background-color:color-mix(in srgb,#eab54c 16%,var(--paper)); outline:2px dashed var(--si); outline-offset:4px; }
:root[data-theme="dark"] .stamp.edition-special { --si:#efd17b; }

/* One souvenir reveal across every place and event. */
.passport-stamp-dialog { width:min(400px,calc(100vw - 28px)); padding:22px; text-align:center; background:var(--paper); }
.passport-stamp-dialog::backdrop { background:rgb(27 26 23 / .5); backdrop-filter:blur(2px); }
.passport-stamp-dialog [data-reveal-stamp] { display:grid; justify-items:center; padding:18px 0; }
.passport-stamp-dialog .actions { flex-wrap:wrap; justify-content:center; }
.passport-stamp-dialog .actions .btn { flex:1 1 auto; }
.bar-expand, .event-expand { min-width:0; }
.bar-preview, .event-summary { cursor:pointer; list-style:none; }
.bar-preview::-webkit-details-marker, .event-summary::-webkit-details-marker { display:none; }
.bar-preview:focus-visible, .event-summary:focus-visible { outline:3px solid var(--red-ink); outline-offset:4px; border-radius:8px; }
.bar-expand-hint, .event-expand-hint { display:flex; align-items:center; gap:8px; font-size:12px; font-weight:700; }
.bar-expand-hint > span, .event-expand-hint > span { display:inline-block; transition:transform .32s cubic-bezier(.22,1,.36,1); }
.bar-expand[open] .bar-expand-hint > span, .event-expand[open] .event-expand-hint > span { transform:rotate(45deg); }
.bar-slide-body, .event-slide-body { border-top:1px solid var(--line); margin-top:14px; padding-top:14px; }
.bar-slide-body > .venue-visit > summary, .restaurant-body > .venue-visit > summary { display:none; }
@media(prefers-reduced-motion:reduce) { .bar-expand-hint > span, .event-expand-hint > span { transition:none; } }

.title-seasonal, [class*="title-games-season-"] { color:var(--warn-ink); font-weight:700; text-shadow:0 0 9px color-mix(in srgb,var(--warn) 40%,transparent); }
