/* ══════════════════════════════════════════════════════════════════════════
   PÄTTKEN · Plakat design system (Redesign v2, July 2026)

   The poster identity chosen from the July 2026 design lab: Bricolage
   Grotesque lowercase display + Instrument Sans UI + JetBrains Mono data,
   paper-cream ground, four pop colors with fixed jobs (cobalt = primary,
   marker yellow = highlight/discovery, signal orange = live/urgent — kept
   from the v1 brand, Preußen green = club/market), thick ink outlines,
   hard offset shadows, sticker badges, press-down interactions.

   Scoping mirrors editorial.css: every rule sits under
   body[data-redesign="v2"], so the v1 pages are untouched. v2 pages load
   ONLY this stylesheet (not kreativkai.css) — _base_v2.html is the sole
   consumer. @font-face and @keyframes cannot be scoped; the font files are
   shared app-wide and the keyframe names are prefixed v2- to avoid
   colliding with kreativkai's.

   Dark mode keys off html[data-theme="dark"] (stamped server-side and by
   the theme bootstrap script) — NOT prefers-color-scheme, matching the
   app-wide auto rule resolved in app/routes/theme.py. Pop colors stay
   saturated in both themes on purpose: poster colors don't get muted.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Self-hosted typefaces (SIL OFL 1.1 — license files alongside the
   woff2s in app/static/vendor/fonts/). Same pipeline as kreativkai.css:
   pulled once from Google Fonts, no outbound requests, CSP font-src 'self'.
   Bricolage and Instrument ship as variable fonts (one file per subset
   carries the whole weight range). unicode-range matches Google's split. */

@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/static/vendor/fonts/bricolage-grotesque-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Bricolage Grotesque';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/static/vendor/fonts/bricolage-grotesque-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/vendor/fonts/instrument-sans-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/vendor/fonts/instrument-sans-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* JetBrains Mono is also declared in kreativkai.css — the two stylesheets
   are never loaded together (v1 pages load kreativkai, v2 pages load this),
   and the src URLs are identical so the browser cache is shared either way. */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("/static/vendor/fonts/jetbrains-mono-latin.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("/static/vendor/fonts/jetbrains-mono-latin-ext.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ── Tokens ─────────────────────────────────────────────────────────── */
body[data-redesign="v2"] {
  /* Pop colors — identical in light & dark (Plakatfarben bleiben Plakatfarben) */
  --cobalt: #2b45d9;
  --cobalt-deep: #1d33ad;
  --yellow: #ffd913;
  --yellow-deep: #eec500;
  --orange: #ff5a1f;          /* Pättken brand orange, kept as the "live" color */
  --green: #0f8a43;           /* Preußen / market green */
  --green-deep: #0b6f36;
  --pop-ink: #17150f;         /* ink ON pop colors — theme-independent */
  --pop-cream: #fdf8ea;       /* cream ON pop blocks — theme-independent */

  /* Theme surfaces (light) */
  --paper: #f6f1e4;
  --paper-2: #ece5d2;
  --card: #fffdf4;
  --ink: #17150f;
  --ink-soft: #514c3f;
  --mute: #8a8371;
  --rule: #ded5bd;            /* quiet separators */
  --bd: #17150f;              /* thick poster outlines */
  --hard-c: #17150f;          /* hard-shadow color */
  /* Der Versatz, der auf die SEITE fällt (Bogen №21). Pop-Flächen
     pinnen sich --hard-c auf Tinte, damit alles in ihrem Inneren
     theme-frei ist — ihr eigener Schlagschatten liegt aber nicht in
     ihnen, sondern auf dem Papier daneben. Dafür ist dieses Token da:
     es folgt IMMER der Seite, nie der Fläche. Im Hellen identisch mit
     --hard-c; im Dunkeln der Wert, den der Bogen entschieden hat. */
  --hard-page: #17150f;
  --wash: rgba(43, 69, 217, 0.06);
  --dock-bg: rgba(255, 253, 244, 0.94);
  --ok: #0f8a43;
  --shadow: 0 12px 32px rgba(23, 21, 15, 0.12);
  --dot: rgba(23, 21, 15, 0.05);

  /* Type + motion */
  --bric: 'Bricolage Grotesque', -apple-system, BlinkMacSystemFont, sans-serif;
  --inst: 'Instrument Sans', -apple-system, BlinkMacSystemFont, sans-serif;
  --v2-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
  --v2-t: 180ms cubic-bezier(0.2, 0.65, 0.3, 1);

  /* ── Die Skala (Druckbogen №7): the twelve-step type ladder ──────────
     Every font-size in this file sits on a step (as var), except the four
     NAMED identity clamps (.heute-head h1, .hero-big, .hero-big.long,
     .hero-big .u) and the SVG user units inside .wx-graph, which are
     chosen so the RENDERED pixel lands on the same ladder (§3). A size
     that fits no step is a design decision, not an implementation detail
     — tests/test_skala.py enforces the whole rule; feed.SKALA/BREITE
     mirror the steps for mono gutter math (glyphs run ~0.6 em). */
  --t01: 9px;    --t02: 10.5px; --t03: 12px;   --t04: 14px;
  --t05: 16px;   --t06: 19px;   --t07: 22px;   --t08: 26px;
  --t09: 30px;   --t10: 34px;   --t11: 42px;   --t12: 52px;
  /* Two axes, not three roles: size (the steps) × face. "Display" was
     never a size role — plakat (Bricolage 800, t08–t12) and akzent
     (Bricolage 650–800, t04–t07) are faces; text = Instrument t04–t06,
     etikett = Instrument 700 gesperrt t01–t03, mono = JetBrains t01–t04. */
  --f-plakat: var(--bric);
  --f-text: var(--inst);
  --f-mono: var(--v2-mono);

  /* v1-token compat aliases — Druckbogen №8 moved assistant.py + the
     photo-search card to classes (test_fragmente guards the module), but
     other consumers still speak the old names: main.py (need-widget
     ~Z. 2355), map_v1.js, and the templates route_v2/shop_v2/
     _shops_page_v2/karte_about_v2/fragen_v2 + markt/* + skulptur/*.
     The block falls only when `grep -rn "var(--display\|var(--mono)\|
     var(--cream)\|var(--ink-mute)\|var(--signal" app/` is empty.
     Nicht glauben — greppen. */
  --mono: var(--v2-mono);
  --display: var(--inst);
  --signal: var(--orange);
  --signal-deep: var(--cobalt);
  --signal-wash: var(--wash);
  --ink-mute: var(--mute);
  --cream: var(--card);
}
html[data-theme="dark"] body[data-redesign="v2"] {
  --paper: #161410;
  --paper-2: #1c1913;
  --card: #211e16;
  --ink: #f3edda;
  --ink-soft: #c3bca7;
  --mute: #867f6b;
  --rule: #33302a;
  --bd: #f3edda;
  /* Druckbogen №21 §1 K1 — die zweite Kontur (0.138.0).
     Im Hellen sind Rand und harter Versatz DIESELBE Tinte (--bd und
     --hard-c beide #17150f): ein Plakat, eine Farbe, zweimal
     aufgetragen. Beim ersten Dunkel-Satz wurde der Rand invertiert und
     der Versatz nicht — er wurde #000000, also dunkler als das ohnehin
     fast schwarze Papier, und stand damit bei 1,14:1 gegen #161410
     (im Hellen: 16,18:1). 38 der 43 --hard-c-Regeln haben seitdem eine
     Farbe gemalt, die niemand sehen konnte; die 5 Pop-Flächen sind
     unbetroffen, weil sie sich Tinte selbst pinnen.
     Ein Versatz FÄLLT NIE AUF SEIN EIGENES BAUTEIL, sondern auf das
     Papier dahinter — deshalb war er auf gelben Chips genauso stumm wie
     auf Karten, und deshalb ist ein Token der richtige Hebel.
     Auf fast schwarzem Papier kann er kein Schatten mehr sein (dunkler
     geht nur bis Schwarz, heller ist kein Schatten): er wird die zweite
     Kontur, creme wie der Rand, nur leiser. 22 % = 1,89:1 — die Stelle,
     an der er auf kleinen Bauteilen noch da ist und auf großen Karten
     noch nicht als eigenes Rechteck mitliest. Geometrie unverändert.
     Der Kartengrund bleibt bewusst bei #211e16 (§3 G1, Owner). */
  --hard-c: rgba(243, 237, 218, 0.22);
  --hard-page: rgba(243, 237, 218, 0.22);
  --wash: rgba(122, 143, 255, 0.08);
  --dock-bg: rgba(22, 20, 16, 0.94);
  --ok: #5fc98a;
  --shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
  --dot: rgba(243, 237, 218, 0.045);
}
/* ── Tageszeiten (round 64): the paper knows the hour ─────────────────
   Two bands tone ONLY the light paper surfaces: frueh (sunrise → +90min,
   a breath of Morgenrot) and golden (golden-hour start → dark, amber).
   Guarded on html[data-theme="light"] so a client-side flip to dark
   kills the band without touching the attribute; fixed-dark users never
   see one. Pop surfaces are literal-pinned — bands can't reach them by
   construction. data-tageszeit comes from routes/theme.py (real Münster
   sun via services/sun.py), stamped next to data-theme. */
html[data-theme="light"][data-tageszeit="frueh"] body[data-redesign="v2"] {
  --paper: #f6ede6; --paper-2: #ede2d4; --card: #fffaf3; --rule: #e0d4c6;
}
html[data-theme="light"][data-tageszeit="golden"] body[data-redesign="v2"] {
  --paper: #f8edd0; --paper-2: #f1e2ba; --card: #fffbe7; --rule: #e4d5a9;
}
/* Round 70b: the hour speaks through PAPER COLOR only (the bands above).
   The .tz-label chip that spelled the band out and the .morgen-play
   briefing button both lived in the masthead and both overflowed it at
   375px — removed; git history has their styles. */

/* ── Ground ─────────────────────────────────────────────────────────── */
body[data-redesign="v2"] {
  margin: 0;
  background: var(--paper);
  background-image: radial-gradient(var(--dot) 1.2px, transparent 1.2px);
  background-size: 26px 26px;
  color: var(--ink);
  font-family: var(--inst);
  font-size: var(--t05);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  /* iOS Safari (and Chrome on Android) "font boosting": inside a block far
     wider than the viewport they silently scale text UP, and the factor is
     derived from that block's width. The ticker track is ~3400px against a
     ~390px phone, so its 10.5px mono rendered large — and CHANGED SIZE
     whenever a long news headline made the track wider (user-reported,
     round 72). 100% (not `none`) pins every size to what we authored while
     leaving pinch-zoom intact. Plakat sizes are deliberate everywhere, so
     this belongs on the ground, not on one component. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* №12 D1: the app-wide dock is gone — the bottom chrome is the tab bar
     alone (~64px + safe-area) plus breathing room. /fragen restores the
     tall padding in its own style block for the real composer. */
  padding-bottom: calc(84px + env(safe-area-inset-bottom));
  /* The poster tilts (rotate() on cards, stickers, chips) may poke a few px
     past the viewport on tall elements — decorative bleed must clip, never
     become horizontal scroll. clip, not hidden: no scroll container. */
  overflow-x: clip;
}
@media (min-width: 900px) {
  /* No tab bar on desktop — just air under the footer. */
  body[data-redesign="v2"] { padding-bottom: 48px; }
}
body[data-redesign="v2"] button {
  font: inherit; color: inherit; background: none; border: none; cursor: pointer; padding: 0;
}
/* Author display rules (.fchip etc.) must not defeat the hidden attribute. */
body[data-redesign="v2"] [hidden] { display: none !important; }
body[data-redesign="v2"] a { color: inherit; text-decoration: none; }
body[data-redesign="v2"] :focus-visible {
  outline: 3px solid var(--cobalt); outline-offset: 2px; border-radius: 6px;
}
body[data-redesign="v2"] ::selection { background: var(--yellow); color: #17150f; }
body[data-redesign="v2"] .wrap {
  max-width: 1180px; margin: 0 auto; padding: 0 clamp(16px, 4vw, 48px);
}

/* ── The poster "push" interaction ──────────────────────────────────── */
body[data-redesign="v2"] .push {
  transition: transform var(--v2-t), box-shadow var(--v2-t),
              background var(--v2-t), color var(--v2-t), border-color var(--v2-t);
}
body[data-redesign="v2"] .push:hover  { transform: translate(-2px, -2px); }
body[data-redesign="v2"] .push:active { transform: translate(1px, 1px); box-shadow: 0 0 0 var(--hard-c) !important; }

/* ── Masthead ───────────────────────────────────────────────────────── */
body[data-redesign="v2"] .masthead { background: var(--paper); border-bottom: 2px solid var(--bd); }
body[data-redesign="v2"] .masthead-in {
  max-width: 1180px; margin: 0 auto; padding: 14px clamp(16px, 4vw, 48px);
  display: flex; align-items: center; gap: 18px;
}
body[data-redesign="v2"] .brand {
  /* The wordmark is drawn, not set (partials/_wordmark.html, Bogen №28 W2):
     the p's descender runs under the word and climbs back to the full stop,
     the same ribbon() the app icon uses. There is no text in here any more,
     so font-family, font-weight and the old letter-spacing:-0.03em are gone —
     they looked alive and did nothing. font-size STAYS and is load-bearing:
     the svg below is sized in em, so it keeps tracking the Skala. */
  font-size: var(--t08); line-height: 1;
  display: inline-flex; align-items: center;
}
body[data-redesign="v2"] .brand .wm {
  /* Generated geometry: the mark spans 1.121 em and reaches 0.402 em below
     the baseline. The margin does NOT protect the masthead's height — that
     was measured and is false: the header is 68 px at 1280 and 64 px at 375
     with this margin, without it, and with the old text brand, because the
     topnav and the padding set it, not the brand. What the -0.3 em actually
     does is drop the mark 3.9 px so the way's deepest point lands on the
     baseline of the „MÜNSTER" label beside it. Optical alignment, not a
     layout guard — tune it by eye, nothing breaks if it changes. */
  height: 1.121em; width: auto; display: block; margin-bottom: -0.3em;
  color: var(--ink);
}
body[data-redesign="v2"] .brand-city {
  font-family: var(--v2-mono); font-size: var(--t01); letter-spacing: 0.24em;
  text-transform: uppercase; color: var(--mute); align-self: flex-end; padding-bottom: 3px;
}
body[data-redesign="v2"] .wx-stick {
  margin-left: auto;
  font-family: var(--inst); font-weight: 700; font-size: var(--t03); letter-spacing: 0.04em;
  background: var(--yellow); color: var(--pop-ink);
  border: 2px solid var(--bd); border-radius: 999px; padding: 6px 14px;
  box-shadow: 3px 3px 0 var(--hard-c); transform: rotate(-1.5deg);
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
body[data-redesign="v2"] .topnav { display: none; gap: 6px; }
body[data-redesign="v2"] .topnav a {
  font-family: var(--inst); font-weight: 700; font-size: var(--t03);
  letter-spacing: 0.1em; text-transform: uppercase;
  padding: 8px 16px; color: var(--ink-soft); border-radius: 999px; border: 2px solid transparent;
}
body[data-redesign="v2"] .topnav a.active {
  color: var(--pop-ink); background: var(--yellow); border-color: var(--bd);
  box-shadow: 3px 3px 0 var(--hard-c);
}
body[data-redesign="v2"] .topnav a:hover { color: var(--ink); }
/* Unterwegs is the MODE among the destinations — cobalt, always (round 47).
   Active swaps yellow-pill for the pressed cobalt look. */
body[data-redesign="v2"] .topnav a.uw {
  color: #fff; background: var(--cobalt); border-color: var(--bd);
  box-shadow: 3px 3px 0 var(--hard-c);
  display: inline-flex; align-items: center; gap: 6px;
}
body[data-redesign="v2"] .topnav a.uw:hover { color: #fff; }
body[data-redesign="v2"] .topnav a.uw.active {
  color: #fff; background: var(--cobalt);
  box-shadow: none; transform: translate(2px, 2px);
}
@media (min-width: 900px) {
  body[data-redesign="v2"] .topnav { display: flex; margin-left: auto; }
  body[data-redesign="v2"] .wx-stick { margin-left: 0; }
}

/* ── Die Anschlagzeile (№13a) ───────────────────────────────────────────
   The marquee died, the Amt stays: ONE item stands, reads, lets itself
   be hit — every 8s the next prints on (plakat-live.js; job-live items
   hold 16s). Without JS only item 1 shows — a standing headline is the
   no-JS fallback a marquee never had. Ein Plakat läuft nicht, es hängt
   aus. */
/* №23 (A): die Zeile ist eine Zeile, keine Karte. Vorher trug sie eine
   eigene Fläche (--card) UND eine zweite 2px-Kante in --bd — direkt unter
   der 2px-Kante der Kopfzeile. Im Dunkeln ist --bd fast Weiß (#f3edda),
   also zwei randlose helle Balken über die volle Breite, dazwischen ein
   Kasten, der heller ist als das Papier. Dieselbe Regel, die im Hellen
   als Plakat liest, wird im Dunkeln Blendung — Bogen №21 andersherum.
   Jetzt: Seitenpapier wie die Kopfzeile, unten eine Haarlinie in --rule
   (das leise Trenn-Token, das es längst gibt). Die Kopfzeile behält ihre
   kräftige Kante — sie ist der Kopf, sie darf. Schrift unangetastet. */
body[data-redesign="v2"] .ticker { border-bottom: 1px solid var(--rule); background: var(--paper); }
body[data-redesign="v2"] .ticker-in {
  max-width: 1180px; margin: 0 auto; padding: 8px clamp(16px, 4vw, 48px);
  display: flex; align-items: center; gap: 10px;
}
body[data-redesign="v2"] .ticker-track { flex: 1; min-width: 0; display: flex; }
body[data-redesign="v2"] .ticker-item {
  /* №13a: nothing moves anymore, so the size climbs t02 → t03 (finally
     readable) and the tracking relaxes 0.1em → 0.06em. test_bogen4 pins
     the ONE size — now as the ladder step. */
  font-family: var(--v2-mono); font-size: var(--t03); letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-soft); display: none; gap: 9px; align-items: center; min-width: 0;
}
/* No-JS fallback: item 1 stands. The rotor stamps .js on the track and
   picks the visible item via .on from then on. */
body[data-redesign="v2"] .ticker-track:not(.js) .ticker-item:first-child,
body[data-redesign="v2"] .ticker-track.js .ticker-item.on { display: flex; }
body[data-redesign="v2"] .ticker-item b { color: var(--ink); font-weight: 500; }
body[data-redesign="v2"] .ticker-item a {
  color: inherit; text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body[data-redesign="v2"] .ticker-item a:hover { text-decoration: underline; text-underline-offset: 3px; }
/* The dot states the item's Amt, not its position (№12 T1 — the same lie
   №9 buried on the stop cards): orange only for what burns NOW, green for
   Markt & Preußen, cobalt for everything else. Classes come from
   plakat_feed._tick_link via _ticker_items.html. */
body[data-redesign="v2"] .ticker-item::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--cobalt); border: 1.5px solid var(--bd); flex: none;
}
body[data-redesign="v2"] .ticker-item.job-live::before  { background: var(--orange); }
body[data-redesign="v2"] .ticker-item.job-markt::before { background: var(--green); }
/* The counter makes the rotation honest — and is the manual page-turn
   (tap = next; under reduced motion it is the ONLY way the line turns). */
body[data-redesign="v2"] .ticker-count {
  font-family: var(--v2-mono); font-size: var(--t01); letter-spacing: 0.1em;
  color: var(--mute); flex: none; padding: 3px 5px;
  font-variant-numeric: tabular-nums;
}
/* Aufsetzen in the №12 print voice: 380ms from 1.5% and −0.6° into rest —
   transform + opacity only, no layout shift. reduced-motion never
   auto-advances (JS gate), so the print never plays there either. */
@media (prefers-reduced-motion: no-preference) {
  body[data-redesign="v2"] .ticker-track.js .ticker-item.on {
    animation: v2-anschlag 0.38s cubic-bezier(0.2, 0.65, 0.3, 1);
  }
}
@keyframes v2-anschlag {
  from { opacity: 0; transform: rotate(-0.6deg) scale(1.015); }
}

/* ── Page head (greeting / view head) ───────────────────────────────── */
body[data-redesign="v2"] .heute-head { padding: 34px 0 4px; }
/* Der Druck-Moment (№12 G1): the greeting SETS like a print — 380ms from
   1.5% size and −0.6° into rest, opacity trailing (the .card-in family,
   one notch quieter). Once per session: the .printing class is gated by
   sessionStorage in plakat-live.js, never re-fires on back-navigation.
   Transform + opacity only — no layout shift. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes v2-print {
    from { opacity: 0.45; transform: rotate(-0.6deg) scale(1.015); }
  }
  body[data-redesign="v2"] .heute-head h1.printing {
    animation: v2-print 380ms cubic-bezier(0.2, 0.65, 0.3, 1);
    transform-origin: left bottom;
  }
}
body[data-redesign="v2"] .view-head  { padding: 34px 0 4px; }
body[data-redesign="v2"] .kicker {
  font-family: var(--v2-mono); font-size: var(--t02); letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-soft); display: flex; align-items: center; gap: 10px; margin-bottom: 16px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body[data-redesign="v2"] .kicker b { color: var(--cobalt); font-weight: 500; }
html[data-theme="dark"] body[data-redesign="v2"] .kicker b { color: #93a5ff; }
body[data-redesign="v2"] .heute-head h1 {
  font-family: var(--bric); font-weight: 800; text-transform: lowercase;
  font-size: clamp(42px, 7vw, 78px); line-height: 0.98; letter-spacing: -0.025em;
  margin: 0 0 14px; text-wrap: balance;
}
/* §2a of the Skala: at 5vw the clamp middle never grabbed on a phone —
   the floor IS the phone size, so it is written as a step + the existing
   900px breakpoint instead of a vw glide that only tablets ever saw.
   Same treatment for the ledes and .d-main below. t12 caps 2px under the
   old 54 — the one move >1px in the whole Bogen, desktop-only. */
body[data-redesign="v2"] .view-head h2 {
  font-family: var(--bric); font-weight: 800; text-transform: lowercase;
  font-size: var(--t10); letter-spacing: -0.025em; line-height: 0.98;
  margin: 0 0 12px; text-wrap: balance;
}
/* The tight head (0.130.6, app-wide 0.130.8): for pages whose greeting
   fits ONE line and hands the saved row to the content below — chip
   rows ride up under it. 8px top stays so the .push hover lift (-2px)
   never slices the chips' ink border. Opt in with the class, never by
   page-local override: the system has two head rhythms, a page picks
   one. (.heute-head is the homepage greeting's identity twin — same
   rhythm, same opt-in.) */
body[data-redesign="v2"] .view-head.tight,
body[data-redesign="v2"] .heute-head.tight { padding-bottom: 0; }
body[data-redesign="v2"] .view-head.tight ~ .chiprow { padding-top: 8px; padding-bottom: 12px; }
@media (min-width: 900px) {
  body[data-redesign="v2"] .view-head h2 { font-size: var(--t12); }
}
body[data-redesign="v2"] h1 em,
body[data-redesign="v2"] h2 em {
  font-style: normal;
  background: var(--yellow); color: var(--pop-ink);
  padding: 0 0.14em; border-radius: 0.14em;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
/* ── Round 62: the mobile Kopf-Diät (app-wide) ─────────────────────────
   Kickers are editorial meta — on a phone they cost a line of the most
   valuable viewport (user call: "serves no purpose"). Ledes fold behind
   the "?" chip plakat-live.js injects; no-JS keeps the server-rendered
   lede visible. Desktop keeps the full editorial head. */
@media (max-width: 899.98px) {
  body[data-redesign="v2"] .view-head .kicker { display: none; }
  body[data-redesign="v2"] .view-head { padding-top: 16px; }
}
body[data-redesign="v2"] .lede-q {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; margin-left: 10px; padding: 0;
  vertical-align: 6px; flex: none;
  border: 2px solid var(--bd); border-radius: 999px;
  background: var(--card); color: var(--ink-soft);
  font-family: var(--v2-mono); font-size: var(--t04); font-weight: 700; line-height: 1;
  cursor: pointer; box-shadow: 2px 2px 0 var(--hard-c);
}
body[data-redesign="v2"] .lede-q[aria-expanded="true"] { background: var(--yellow); color: var(--pop-ink); }

body[data-redesign="v2"] .heute-head .lede,
body[data-redesign="v2"] .view-head .lede {
  font-size: var(--t05); color: var(--ink-soft);
  max-width: 600px; line-height: 1.5; margin: 0; font-weight: 400;
}
@media (min-width: 900px) {
  body[data-redesign="v2"] .heute-head .lede,
  body[data-redesign="v2"] .view-head .lede { font-size: var(--t06); }
}

/* ── Bento feed grid ────────────────────────────────────────────────── */
body[data-redesign="v2"] .feed { padding: 26px 0 8px; display: flex; flex-direction: column; gap: 16px; }
/* minmax(0,Xfr), never bare Xfr (the layout scar, see CLAUDE.md): with
   minmax(auto,…) a long unbreakable hero-big — e.g. the market hero on a
   "Wochenmarkt Gremmendorf" afternoon — stretched the whole layout
   viewport past 375 px. Found July 2026; every other grid was already
   converted, this one had been missed. */
body[data-redesign="v2"] .bento-top,
body[data-redesign="v2"] .bento-bot { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; }
body[data-redesign="v2"] .bento-top > *,
body[data-redesign="v2"] .bento-bot > * { min-width: 0; }
@media (min-width: 900px) {
  /* Round 57: the strip column is gone — the deck owns the full bento
     width on desktop (it shows two cards side by side; see home_v2). */
  body[data-redesign="v2"] .bento-top { grid-template-columns: minmax(0, 1fr); align-items: stretch; }
  body[data-redesign="v2"] .bento-bot { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); align-items: stretch; }
  body[data-redesign="v2"] .bento-bot.solo { grid-template-columns: minmax(0, 1fr); }
}
body[data-redesign="v2"] .strips { display: flex; flex-direction: column; gap: 16px; }
body[data-redesign="v2"] .card-in { animation: v2-pop 0.38s cubic-bezier(0.16, 1, 0.3, 1) backwards; }
body[data-redesign="v2"] .card-in:nth-child(2) { animation-delay: 0.05s; }
body[data-redesign="v2"] .card-in:nth-child(3) { animation-delay: 0.1s; }
@keyframes v2-pop {
  from { opacity: 0; transform: translateY(14px) rotate(-1.2deg) scale(0.98); }
  to   { opacity: 1; transform: none; }
}
@keyframes v2-wig {
  0%, 100% { transform: rotate(-2deg); }
  40%      { transform: rotate(1.6deg); }
  70%      { transform: rotate(-1deg); }
}
@keyframes v2-tabpop {
  0%   { transform: scale(0.85); }
  60%  { transform: scale(1.12); }
  100% { transform: scale(1); }
}
body[data-redesign="v2"] .wx-stick:hover,
body[data-redesign="v2"] .hero-stick:hover { animation: v2-wig 0.5s ease-in-out; }

/* ── Hero block: poster surface, color changes with context ─────────── */
body[data-redesign="v2"] .hero {
  border: 3px solid var(--bd); border-radius: 26px;
  padding: clamp(22px, 3vw, 34px);
  display: flex; flex-direction: column; gap: 12px; justify-content: space-between;
  min-height: 280px; box-shadow: 7px 7px 0 var(--hard-page);
  color: var(--pop-cream);
}
/* ── Hero context art (round 42): committed Gemini motifs as MASKS ────
   One alpha-mask PNG per context (scripts/generate_hero_art.py — the
   generate-once-and-commit pattern), tinted here by CSS: cream on the
   colored grounds, ink on the yellow geheim hero. Decorative only:
   z-index -1 inside the hero's own stacking context (isolation), so
   text and buttons always paint above. data-art comes from
   feed.context in home_v2; unknown keys match no selector and cost
   nothing. Pop-surface rule applies — identical in both themes. */
body[data-redesign="v2"] .hero { position: relative; isolation: isolate; }
body[data-redesign="v2"] .hero[data-art]::after {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  right: 8px; bottom: 8px; width: min(46%, 250px); height: 72%;
  background: var(--pop-cream); opacity: 0.15;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: bottom right; mask-position: bottom right;
  -webkit-mask-size: contain; mask-size: contain;
}
body[data-redesign="v2"] .hero.c-yellow[data-art]::after { background: var(--pop-ink); opacity: 0.10; }
body[data-redesign="v2"] .hero[data-art="heat"]::after      { -webkit-mask-image: url(/static/images/hero-art/heat.png); mask-image: url(/static/images/hero-art/heat.png); }
body[data-redesign="v2"] .hero[data-art="rain"]::after      { -webkit-mask-image: url(/static/images/hero-art/rain.png); mask-image: url(/static/images/hero-art/rain.png); }
body[data-redesign="v2"] .hero[data-art="spieltag"]::after  { -webkit-mask-image: url(/static/images/hero-art/spieltag.png); mask-image: url(/static/images/hero-art/spieltag.png); }
body[data-redesign="v2"] .hero[data-art="markt"]::after     { -webkit-mask-image: url(/static/images/hero-art/markt.png); mask-image: url(/static/images/hero-art/markt.png); }
body[data-redesign="v2"] .hero[data-art="geheim"]::after    { -webkit-mask-image: url(/static/images/hero-art/geheim.png); mask-image: url(/static/images/hero-art/geheim.png); }
body[data-redesign="v2"] .hero[data-art="weather"]::after,
body[data-redesign="v2"] .hero[data-art="still"]::after     { -webkit-mask-image: url(/static/images/hero-art/weather.png); mask-image: url(/static/images/hero-art/weather.png); }
/* Night gables (round 71, Bogen №4): same skyline with the lights on, plus
   moon and stars. Chosen server-side via feed.hero_art (build_v2_feed) —
   _pick_context stays untouched, so this is a pure presentation swap. */
body[data-redesign="v2"] .hero[data-art="weather-night"]::after { -webkit-mask-image: url(/static/images/hero-art/weather-night.png); mask-image: url(/static/images/hero-art/weather-night.png); }
/* Deck-card motifs (round 57) — same voice, one per card kind. */
body[data-redesign="v2"] .hero[data-art="deck-nachtbus"]::after { -webkit-mask-image: url(/static/images/hero-art/deck-nachtbus.png); mask-image: url(/static/images/hero-art/deck-nachtbus.png); }
body[data-redesign="v2"] .hero[data-art="deck-news"]::after   { -webkit-mask-image: url(/static/images/hero-art/deck-news.png); mask-image: url(/static/images/hero-art/deck-news.png); }
body[data-redesign="v2"] .hero[data-art="deck-abfuhr"]::after { -webkit-mask-image: url(/static/images/hero-art/deck-abfuhr.png); mask-image: url(/static/images/hero-art/deck-abfuhr.png); }
body[data-redesign="v2"] .hero[data-art="deck-parken"]::after { -webkit-mask-image: url(/static/images/hero-art/deck-parken.png); mask-image: url(/static/images/hero-art/deck-parken.png); }
body[data-redesign="v2"] .hero[data-art="deck-quiz"]::after   { -webkit-mask-image: url(/static/images/hero-art/deck-quiz.png); mask-image: url(/static/images/hero-art/deck-quiz.png); }
body[data-redesign="v2"] .hero[data-art="deck-damals"]::after { -webkit-mask-image: url(/static/images/hero-art/deck-damals.png); mask-image: url(/static/images/hero-art/deck-damals.png); }
body[data-redesign="v2"] .hero[data-art="deck-mensa"]::after  { -webkit-mask-image: url(/static/images/hero-art/deck-mensa.png); mask-image: url(/static/images/hero-art/deck-mensa.png); }
body[data-redesign="v2"] .hero[data-art="deck-events"]::after { -webkit-mask-image: url(/static/images/hero-art/deck-events.png); mask-image: url(/static/images/hero-art/deck-events.png); }
body[data-redesign="v2"] .hero[data-art="deck-moin"]::after   { -webkit-mask-image: url(/static/images/hero-art/deck-moin.png); mask-image: url(/static/images/hero-art/deck-moin.png); }
body[data-redesign="v2"] .hero.c-cobalt { background: var(--cobalt); }
body[data-redesign="v2"] .hero.c-green  { background: var(--green); }
body[data-redesign="v2"] .hero.c-orange { background: var(--orange); }
body[data-redesign="v2"] .hero.c-ink    { background: #17150f; }
/* Violet (0.104.4): the sixth pop face, born for the Abfuhr deck card —
   every other face already had a neighbor in the deck (user call:
   unique color, no same-color borders). Same cream-text/ink-outline
   physics as cobalt; use sparingly, it is the deck's accent. */
body[data-redesign="v2"] .hero.c-violet { background: #6d28d9; }
/* c-ink faces on the dark page (user-reported, 0.104.1): face #17150f vs
   paper #161410 — the card vanished. The BOUNDARY may follow the theme
   (round-41 scope note: edges harmonize with the surface AROUND a pop
   ground); face + contents stay pinned. Literal color = the dark theme's
   own --bd, because --bd is pinned to its light value inside pop surfaces. */
html[data-theme="dark"] body[data-redesign="v2"] .hero.c-ink,
html[data-theme="dark"] body[data-redesign="v2"] .bogen.c-ink,
html[data-theme="dark"] body[data-redesign="v2"] .special.c-ink { border-color: #f3edda; }
/* Discovery hero (Geheimtipp der Woche): marker yellow ground → ink text,
   the stick inverts, the primary button goes cobalt so it can't vanish. */
body[data-redesign="v2"] .hero.c-yellow { background: var(--yellow); color: var(--pop-ink); }
body[data-redesign="v2"] .hero.c-yellow .hero-stick { background: #17150f; color: var(--yellow); }
body[data-redesign="v2"] .hero.c-yellow .hbtn.primary { background: var(--cobalt); color: #fff; }
body[data-redesign="v2"] .hero-stick {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 8px;
  background: var(--yellow); color: var(--pop-ink);
  font-family: var(--inst); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.14em; text-transform: uppercase;
  border: 2px solid #17150f; border-radius: 999px; padding: 5px 13px;
  transform: rotate(-2deg); box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.28);
}
body[data-redesign="v2"] .hero-stick .live {
  width: 8px; height: 8px; border-radius: 50%; background: var(--orange);
  border: 1.5px solid #17150f; animation: v2-pulse 1.5s ease-in-out infinite;
}
body[data-redesign="v2"] .hero.c-orange .hero-stick .live { background: var(--cobalt); }
@keyframes v2-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
body[data-redesign="v2"] .hero-big {
  font-family: var(--bric); font-weight: 800; text-transform: lowercase;
  font-size: clamp(72px, 10vw, 128px); line-height: 0.9; letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  /* Text heroes (market names) must wrap inside the capped grid track
     instead of forcing the box wide — numbers are unaffected. hyphens
     gives German display words a typographic break ("gremmen-dorf");
     overflow-wrap stays as the guarantee for unhyphenatable tokens. */
  hyphens: auto;
  overflow-wrap: anywhere;
}
/* Long text heroes ("gremmendorf") drop a size tier so the word fits on
   one line at 375px instead of breaking mid-word (user feedback). The
   flag comes from plakat_feed (big_long_text). */
body[data-redesign="v2"] .hero-big.long {
  font-size: clamp(40px, 8.5vw, 88px); line-height: 1;
}
body[data-redesign="v2"] .hero-big .u {
  font-size: clamp(24px, 3.2vw, 38px); letter-spacing: -0.01em; margin-left: 10px; color: var(--yellow);
}
body[data-redesign="v2"] .hero-lede {
  font-size: var(--t05); font-weight: 500; opacity: 0.96;
  max-width: 44ch; line-height: 1.4;
}
@media (min-width: 900px) {
  body[data-redesign="v2"] .hero-lede { font-size: var(--t06); }
}
body[data-redesign="v2"] .hero-meta {
  font-family: var(--v2-mono); font-size: var(--t02); letter-spacing: 0.14em;
  text-transform: uppercase; opacity: 0.75;
}
/* Regen-Nowcast line (round 67): the marker-yellow turn signal on every
   hero face; ink on the yellow geheim hero so it can't vanish. */
body[data-redesign="v2"] .hero-meta .nc { color: var(--yellow); font-weight: 700; }
body[data-redesign="v2"] .hero.c-yellow .hero-meta .nc { color: var(--pop-ink); }
body[data-redesign="v2"] .hero-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 8px; }
body[data-redesign="v2"] .hbtn {
  display: inline-block;
  font-family: var(--inst); font-weight: 700; font-size: var(--t03);
  letter-spacing: 0.08em; text-transform: uppercase;
  border: 2px solid #17150f; border-radius: 999px; padding: 9px 18px;
  background: var(--pop-cream); color: var(--pop-ink);
  box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.3);
}
body[data-redesign="v2"] .hbtn.primary { background: var(--yellow); }

/* ── Wetter-Kino + das atmende Deck (round 63) ────────────────────────
   Two tempi, one rule: WEATHER IS THEATRE (it may play loud, it is
   live), EVERYTHING ELSE BREATHES — exactly one slow motion per deck
   card, in the language of its motif, periods ≥4.5s, opacity <25%.
   The 'still' hero stays still by design.

   Effect layers live in a `.kino` div (server-side conditional via
   hero.kino from plakat_feed) at z-index -2 inside the hero's isolation:
   the data-art mask (-1) paints above the weather, text/buttons above
   both. Only transform/opacity animate (GPU-composited); .kino clips its
   own bleed. Every animation sits behind prefers-reduced-motion
   no-preference — without motion this is exactly the static poster.
   Pop-surface rule: all effect colors literal, identical in both themes.
   The deck scroller JS toggles .kino-off on offscreen cards so only the
   visible card breathes (battery). */
body[data-redesign="v2"] .kino {
  position: absolute; inset: 0; z-index: -2; pointer-events: none;
  border-radius: inherit; overflow: hidden;
}

/* regen — falling print-hatch: rotated column stripes chopped into
   dashes by a static mask; the layer translates whole dash periods. */
body[data-redesign="v2"] .kino .rain-tilt { position: absolute; left: -50%; top: -50%; width: 200%; height: 200%; transform: rotate(12deg); }
body[data-redesign="v2"] .kino .rain-l { position: absolute; inset: -180px 0; }
body[data-redesign="v2"] .kino .rain-l.f {
  background: repeating-linear-gradient(90deg, transparent 0 16px, rgba(253, 248, 234, 0.20) 16px 17.5px, transparent 17.5px 27px);
  -webkit-mask-image: repeating-linear-gradient(180deg, #000 0 19px, transparent 19px 34px);
          mask-image: repeating-linear-gradient(180deg, #000 0 19px, transparent 19px 34px);
}
body[data-redesign="v2"] .kino .rain-l.s {
  background: repeating-linear-gradient(90deg, transparent 0 21px, rgba(253, 248, 234, 0.11) 21px 22px, transparent 22px 37px);
  background-position: 9px 0;
  -webkit-mask-image: repeating-linear-gradient(180deg, #000 0 24px, transparent 24px 46px);
          mask-image: repeating-linear-gradient(180deg, #000 0 24px, transparent 24px 46px);
}

/* hitze — breathing sun-glow + rising drawn flimmer strokes */
body[data-redesign="v2"] .kino .glow {
  position: absolute; left: -14%; top: -28%; width: 64%; aspect-ratio: 1;
  background: radial-gradient(circle, rgba(255, 217, 19, 0.34), rgba(255, 217, 19, 0) 66%);
}
body[data-redesign="v2"] .kino .flimmer { position: absolute; right: 6%; bottom: 24%; width: 44%; height: 46%; }
body[data-redesign="v2"] .kino .flimmer path { stroke: #fdf8ea; stroke-width: 2; fill: none; stroke-linecap: round; opacity: 0; }

/* wolken — flat printed clouds drifting in two depths */
body[data-redesign="v2"] .kino .cloud { position: absolute; }
body[data-redesign="v2"] .kino .cloud svg { display: block; width: 100%; height: auto; }
body[data-redesign="v2"] .kino .cl1 { top: 10%; width: 190px; }
body[data-redesign="v2"] .kino .cl2 { top: 38%; width: 120px; }

/* schnee — two dot tiles falling with a sway (sleeps until winter) */
body[data-redesign="v2"] .kino .snow-l { position: absolute; inset: -240px 0; }
body[data-redesign="v2"] .kino .snow-l.a {
  background-image:
    radial-gradient(circle 2.4px at 22px 16px, #fdf8ea 96%, transparent),
    radial-gradient(circle 1.8px at 68px 52px, #fdf8ea 96%, transparent),
    radial-gradient(circle 2.8px at 96px 90px, #fdf8ea 96%, transparent);
  background-size: 118px 118px; opacity: 0.7;
}
body[data-redesign="v2"] .kino .snow-l.b {
  background-image:
    radial-gradient(circle 1.6px at 40px 34px, #fdf8ea 96%, transparent),
    radial-gradient(circle 1.3px at 90px 72px, #fdf8ea 96%, transparent);
  background-size: 104px 104px; background-position: 31px 0; opacity: 0.45;
}

/* flutlicht — two beams breathing alternately + the ball rolls */
body[data-redesign="v2"] .kino .beam {
  position: absolute; top: -12%; width: 34%; height: 130%;
  background: linear-gradient(180deg, rgba(253, 248, 234, 0.16), rgba(253, 248, 234, 0) 78%);
}
body[data-redesign="v2"] .kino .beam.l { left: 2%; transform: rotate(16deg); transform-origin: top center; }
body[data-redesign="v2"] .kino .beam.r { right: 2%; transform: rotate(-16deg); transform-origin: top center; }
body[data-redesign="v2"] .kino .ball {
  position: absolute; bottom: 10px; left: 0; width: 15px; height: 15px; border-radius: 50%;
  background: #fdf8ea; opacity: 0.55; transform: translateX(-40px);
}
body[data-redesign="v2"] .kino .ball::after {
  content: ""; position: absolute; inset: 2.5px; border-radius: 50%;
  border: 1.5px solid rgba(23, 21, 15, 0.45); border-right-color: transparent; border-top-color: transparent;
}

/* wimpel — drawn bunting swings over the top edge */
body[data-redesign="v2"] .kino .wimpel { position: absolute; top: 8px; left: -2%; width: 104%; transform-origin: 50% 0; }
body[data-redesign="v2"] .kino .wimpel svg { display: block; width: 100%; height: auto; }

/* funkeln — three ink sparks blink around the geheim motif */
body[data-redesign="v2"] .kino .spark { position: absolute; width: 16px; height: 16px; opacity: 0; }
body[data-redesign="v2"] .kino .spark svg { display: block; width: 100%; height: 100%; }
body[data-redesign="v2"] .kino .sp1 { right: 34%; bottom: 58%; }
body[data-redesign="v2"] .kino .sp2 { right: 10%; bottom: 34%; width: 11px; height: 11px; }
body[data-redesign="v2"] .kino .sp3 { right: 44%; bottom: 18%; width: 13px; height: 13px; }

/* moin — day-one Empfang: the drawn hand waves, then three ink sparks
   pop, all on ONE 7s clock (keyframe offsets, not free-running delays,
   keep the wave and the sparks a single choreographed gesture). Ink on
   the yellow ground like the geheim funkeln; peaks stay ≤0.24. Without
   motion the sparks hold opacity 0 — the static poster is just the
   masked hand, same as every deck card. */
body[data-redesign="v2"] .kino .mspark { position: absolute; width: 15px; height: 15px; opacity: 0; }
body[data-redesign="v2"] .kino .mspark svg { display: block; width: 100%; height: 100%; }
body[data-redesign="v2"] .kino .mg1 { right: 28px; bottom: 48%; }
body[data-redesign="v2"] .kino .mg2 { right: 110px; bottom: 44%; width: 11px; height: 11px; }
body[data-redesign="v2"] .kino .mg3 { right: 8px; bottom: 30%; width: 12px; height: 12px; }

/* the deck breathes — one voice per card */
body[data-redesign="v2"] .kino .steam { position: absolute; right: 24%; bottom: 52%; width: 22%; height: 34%; }
body[data-redesign="v2"] .kino .steam path { stroke: #fdf8ea; stroke-width: 2.5; fill: none; stroke-linecap: round; opacity: 0; }
body[data-redesign="v2"] .kino .sheen {
  position: absolute; inset: 0; transform: translateX(-130%);
  background: linear-gradient(100deg, transparent 38%, rgba(253, 248, 234, 0.11) 50%, transparent 62%);
}
body[data-redesign="v2"] .kino .spot {
  position: absolute; top: -30%; left: -20%; width: 150%; height: 160%;
  background: radial-gradient(ellipse 34% 42% at center, rgba(253, 248, 234, 0.10), transparent 70%);
}
body[data-redesign="v2"] .kino .ping {
  position: absolute; right: 20%; bottom: 26%; width: 64px; height: 64px; border-radius: 50%;
  border: 2.5px solid #fdf8ea; opacity: 0; transform: scale(0.35);
}
body[data-redesign="v2"] .kino .staub {
  position: absolute; inset: -280px 0; opacity: 0.4;
  background-image:
    radial-gradient(circle 1.8px at 30px 40px, #fdf8ea 96%, transparent),
    radial-gradient(circle 1.4px at 100px 100px, #fdf8ea 96%, transparent),
    radial-gradient(circle 1.1px at 66px 150px, #fdf8ea 96%, transparent);
  background-size: 150px 190px;
}

@media (prefers-reduced-motion: no-preference) {
  body[data-redesign="v2"] .kino .rain-l.f { animation: v2k-fall 1.05s linear infinite; }
  body[data-redesign="v2"] .kino .rain-l.s { animation: v2k-fall2 1.7s linear infinite; }
  /* loop distances are whole multiples of each layer's dash-mask period */
  @keyframes v2k-fall  { from { transform: translateY(-170px); } to { transform: translateY(0); } }
  @keyframes v2k-fall2 { from { transform: translateY(-138px); } to { transform: translateY(0); } }

  body[data-redesign="v2"] .kino .glow { animation: v2k-glow 5.5s ease-in-out infinite alternate; }
  @keyframes v2k-glow { from { opacity: 0.55; transform: scale(1); } to { opacity: 1; transform: scale(1.07); } }
  body[data-redesign="v2"] .kino .flimmer path { animation: v2k-flm 4.2s ease-in-out infinite; }
  body[data-redesign="v2"] .kino .flimmer path:nth-child(2) { animation-delay: 1.1s; }
  body[data-redesign="v2"] .kino .flimmer path:nth-child(3) { animation-delay: 2.2s; }
  body[data-redesign="v2"] .kino .flimmer path:nth-child(4) { animation-delay: 3.1s; }
  @keyframes v2k-flm {
    0% { transform: translateY(14px); opacity: 0; }
    22% { opacity: 0.26; } 60% { opacity: 0.18; }
    100% { transform: translateY(-18px); opacity: 0; }
  }
  body[data-redesign="v2"] .hero[data-art="heat"]::after { animation: v2k-shimmer 4s ease-in-out infinite; }
  @keyframes v2k-shimmer { 0%, 100% { transform: skewX(0); } 50% { transform: skewX(0.9deg) translateX(1px); } }

  body[data-redesign="v2"] .kino .cl1 { animation: v2k-drift 85s linear infinite; animation-delay: -30s; }
  body[data-redesign="v2"] .kino .cl2 { animation: v2k-drift 120s linear infinite; animation-delay: -95s; }
  @keyframes v2k-drift { from { transform: translateX(-260px); } to { transform: translateX(900px); } }

  body[data-redesign="v2"] .kino .snow-l.a { animation: v2k-snow1 8.5s linear infinite; }
  body[data-redesign="v2"] .kino .snow-l.b { animation: v2k-snow2 13s linear infinite; }
  @keyframes v2k-snow1 { 0% { transform: translate(0, 0); } 50% { transform: translate(9px, 118px); } 100% { transform: translate(0, 236px); } }
  @keyframes v2k-snow2 { 0% { transform: translate(0, 0); } 50% { transform: translate(-7px, 104px); } 100% { transform: translate(0, 208px); } }

  body[data-redesign="v2"] .kino .beam.l { animation: v2k-beam 7s ease-in-out infinite alternate; }
  body[data-redesign="v2"] .kino .beam.r { animation: v2k-beam 7s ease-in-out infinite alternate-reverse; }
  @keyframes v2k-beam { from { opacity: 0.45; } to { opacity: 1; } }
  body[data-redesign="v2"] .kino .ball { animation: v2k-ball 11s linear infinite; }
  @keyframes v2k-ball {
    0% { transform: translateX(-40px) rotate(0); }
    22% { transform: translateX(660px) rotate(900deg); }
    100% { transform: translateX(660px) rotate(900deg); }
  }

  body[data-redesign="v2"] .kino .wimpel { animation: v2k-wimpel 4.8s ease-in-out infinite alternate; }
  @keyframes v2k-wimpel { from { transform: rotate(-1.1deg); } to { transform: rotate(1.1deg); } }

  body[data-redesign="v2"] .kino .spark { animation: v2k-spark 5.5s ease-in-out infinite; }
  body[data-redesign="v2"] .kino .sp2 { animation-delay: 1.8s; }
  body[data-redesign="v2"] .kino .sp3 { animation-delay: 3.6s; }
  @keyframes v2k-spark {
    0%, 24%, 100% { opacity: 0; transform: scale(0.5) rotate(0); }
    8% { opacity: 0.55; transform: scale(1.1) rotate(24deg); }
    16% { opacity: 0; transform: scale(0.6) rotate(40deg); }
  }

  body[data-redesign="v2"] .kino .steam path { animation: v2k-steam 6.5s ease-in-out infinite; }
  body[data-redesign="v2"] .kino .steam path:nth-child(2) { animation-delay: 3s; }
  @keyframes v2k-steam {
    0% { transform: translateY(10px); opacity: 0; }
    25% { opacity: 0.2; } 60% { opacity: 0.13; }
    100% { transform: translateY(-16px); opacity: 0; }
  }

  body[data-redesign="v2"] .hero[data-art="deck-abfuhr"]::after { transform-origin: bottom right; animation: v2k-wackel 7s ease-in-out infinite; }
  @keyframes v2k-wackel {
    0%, 84%, 100% { transform: rotate(0); }
    88% { transform: rotate(-1.8deg); }
    92% { transform: rotate(1.6deg); }
    96% { transform: rotate(-0.7deg); }
  }

  body[data-redesign="v2"] .kino .sheen { animation: v2k-sheen 9s linear infinite; }
  @keyframes v2k-sheen {
    0% { transform: translateX(-130%); }
    16% { transform: translateX(130%); }
    100% { transform: translateX(130%); }
  }

  body[data-redesign="v2"] .kino .spot { animation: v2k-spot 16s ease-in-out infinite alternate; }
  @keyframes v2k-spot { from { transform: translateX(-14%); } to { transform: translateX(14%); } }

  body[data-redesign="v2"] .kino .ping { animation: v2k-ping 4.5s ease-out infinite; }
  @keyframes v2k-ping {
    0% { opacity: 0; transform: scale(0.35); }
    8% { opacity: 0.5; }
    40% { opacity: 0; transform: scale(1.25); }
    100% { opacity: 0; transform: scale(1.25); }
  }

  body[data-redesign="v2"] .kino .staub { animation: v2k-staub 17s linear infinite; }
  @keyframes v2k-staub { 0% { transform: translate(0, 0); } 50% { transform: translate(6px, -190px); } 100% { transform: translate(0, -380px); } }

  /* moin — the wave rotates at the wrist (mask sits bottom-right; the
     origin lands on the drawn hand's wrist), sparks answer at the wave's
     end on the same clock. Amplitude ±5° is the deck's loudest rotate —
     deliberate: this card exists for one day and IS the greeting. */
  body[data-redesign="v2"] .hero[data-art="deck-moin"]::after { transform-origin: 72% 96%; animation: v2k-moin 7s ease-in-out infinite; }
  @keyframes v2k-moin {
    0%, 70%, 92%, 100% { transform: rotate(0); }
    75% { transform: rotate(-5deg); }
    81% { transform: rotate(4deg); }
    87% { transform: rotate(-2deg); }
  }
  body[data-redesign="v2"] .kino .mspark { animation: v2k-mgruss 7s ease-in-out infinite; }
  body[data-redesign="v2"] .kino .mg2 { animation-delay: 0.18s; }
  body[data-redesign="v2"] .kino .mg3 { animation-delay: 0.36s; }
  @keyframes v2k-mgruss {
    0%, 78% { opacity: 0; transform: scale(0.4) rotate(0); }
    86% { opacity: 0.24; transform: scale(1.05) rotate(20deg); }
    96%, 100% { opacity: 0; transform: scale(0.5) rotate(35deg); }
  }

  body[data-redesign="v2"] .hero[data-art="deck-quiz"]::after { transform-origin: bottom right; animation: v2k-gruebel 6.5s ease-in-out infinite; }
  @keyframes v2k-gruebel {
    0%, 70%, 100% { transform: rotate(0); }
    76% { transform: rotate(-2.4deg); }
    84% { transform: rotate(2deg); }
    90% { transform: rotate(-0.9deg); }
  }
}

/* offscreen deck cards hold their breath (toggled by the deck scroller JS) */
body[data-redesign="v2"] .hero.kino-off .kino *,
body[data-redesign="v2"] .hero.kino-off::after { animation-play-state: paused; }

/* ── Bus-mitfahren (round 65): follow banner + popup action ───────────
   The banner floats inside the map container (position:relative on every
   Leaflet root), above the panes but below modals. Pop-surface style on
   a map overlay: literal light-card values — a map is never dark-papered,
   its overlays keep one face in both themes (same rule as popups). */
body[data-redesign="v2"] .busfollow {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
  z-index: 1001; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  max-width: min(92%, 460px);
  background: #fffdf4; color: #17150f; border: 2px solid #17150f; border-radius: 14px;
  padding: 8px 11px; box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.35);
  font-family: var(--inst); font-size: var(--t03);
}
body[data-redesign="v2"] .busfollow .bf-line {
  background: var(--cobalt); color: #fff; border: 2px solid #17150f; border-radius: 8px;
  padding: 1px 8px; font-family: var(--bric); font-weight: 800; font-size: var(--t04);
}
body[data-redesign="v2"] .busfollow .bf-dir {
  font-weight: 700; max-width: 24ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body[data-redesign="v2"] .busfollow .bf-x {
  margin-left: auto; width: 24px; height: 24px; border-radius: 50%;
  border: 2px solid #17150f; background: #fffdf4; color: #17150f;
  font-size: var(--t02); line-height: 1; cursor: pointer;
}
body[data-redesign="v2"] .busfollow .bf-next {
  flex-basis: 100%; font-family: var(--v2-mono); font-size: var(--t02);
  letter-spacing: 0.06em; text-transform: uppercase; color: #514c3f;
}
body[data-redesign="v2"] .busfollow .bf-route {
  display: inline-block; background: var(--yellow); color: var(--pop-ink);
  border: 2px solid #17150f; border-radius: 999px; padding: 4px 12px;
  font-weight: 700; font-size: var(--t02); letter-spacing: 0.06em; text-transform: uppercase;
  text-decoration: none; box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.3);
}
/* Die Mitfahren-Aktion im Popup. Der Kommentar hier behauptete, Popups
   seien in beiden Themes hell — das stimmt nicht: der Popup-Körper trägt
   var(--card) und creme Rand, folgt also dem Theme. Der literale schwarze
   Versatz fiel damit im Dunkeln auf #211e16 und war unsichtbar (Bogen №21;
   von test_bogen21 gefunden, nicht von Hand). Der Rand bleibt literal:
   Tinte auf Kobalt ist in beiden Themes richtig. */
body[data-redesign="v2"] .leaflet-popup-content .bf-go {
  margin-top: 6px; display: inline-block; background: var(--cobalt); color: #fff;
  border: 2px solid #17150f; border-radius: 999px; padding: 4px 12px;
  font-family: var(--inst); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.06em; text-transform: uppercase; cursor: pointer;
  box-shadow: 2px 2px 0 var(--hard-c);
}

/* ── Strip cards (right column) ─────────────────────────────────────── */
body[data-redesign="v2"] .strip {
  background: var(--card); border: 2px solid var(--bd); border-radius: 18px;
  padding: 16px 19px 15px; display: flex; flex-direction: column; gap: 6px;
  box-shadow: 4px 4px 0 var(--hard-c);
  /* A strip is a grid/flex child everywhere it appears — without this it
     refuses to shrink below its longest word and drags the whole track
     wide (rule 1's other half). */
  min-width: 0;
}
body[data-redesign="v2"] .s-kicker {
  font-family: var(--inst); font-weight: 700;
  font-size: var(--t02); letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--cobalt); display: flex; justify-content: space-between; align-items: center; gap: 10px;
}
html[data-theme="dark"] body[data-redesign="v2"] .s-kicker { color: #93a5ff; }
body[data-redesign="v2"] .s-kicker .tag {
  font-size: var(--t01); letter-spacing: 0.14em; padding: 3px 10px; border-radius: 999px;
  border: 1.5px solid var(--bd); color: #fff; background: var(--cobalt);
  box-shadow: 2px 2px 0 var(--hard-c); white-space: nowrap;
}
body[data-redesign="v2"] .s-kicker .tag.t-orange { background: var(--orange); }
body[data-redesign="v2"] .s-kicker .tag.t-green  { background: var(--green); }
/* №14d: the quiet tag — information, not signal. For category labels
   that earn no color (the /deals cycle died for this). */
body[data-redesign="v2"] .s-kicker .tag.t-quiet  { background: var(--paper-2); color: var(--ink); }
body[data-redesign="v2"] .s-kicker .r {
  font-family: var(--v2-mono); font-weight: 400; font-size: var(--t01);
  color: var(--mute); letter-spacing: 0.08em; font-variant-numeric: tabular-nums;
}
/* RSS is hostile input for LAYOUT too, not just for markup: Polizei
   descriptions carry raw presseportal URLs (54 chars, zero break
   opportunities) and Stadt headlines carry 26-letter compounds. An
   unbreakable token sets the card's min-content width, which stretches
   whatever track holds it — /news ran 45px past a 375px viewport on a
   single police item. `anywhere` (not `break-word`) is the one that
   caps min-content; it is inert for text that already fits. */
body[data-redesign="v2"] .s-main,
body[data-redesign="v2"] .s-note { overflow-wrap: anywhere; }
body[data-redesign="v2"] .s-main { font-weight: 600; font-size: var(--t06); letter-spacing: -0.015em; line-height: 1.28; }
body[data-redesign="v2"] .s-main b {
  font-weight: 700; background: var(--yellow); color: var(--pop-ink);
  padding: 0 0.12em; border-radius: 0.12em;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
body[data-redesign="v2"] .s-note { font-size: var(--t04); color: var(--ink-soft); line-height: 1.42; }

/* ── Wide card (events / result lists) ──────────────────────────────── */
body[data-redesign="v2"] .widecard {
  background: var(--card); border: 2px solid var(--bd); border-radius: 18px;
  padding: 17px 19px; box-shadow: 4px 4px 0 var(--hard-c);
}
body[data-redesign="v2"] .w-head {
  font-family: var(--inst); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.18em; text-transform: uppercase;
  margin-bottom: 8px; display: flex; justify-content: space-between; gap: 10px; color: var(--ink);
}
body[data-redesign="v2"] .w-head .more { color: var(--mute); letter-spacing: 0.1em; }
body[data-redesign="v2"] .rowlist { display: flex; flex-direction: column; }
body[data-redesign="v2"] .rowitem {
  display: flex; align-items: baseline; gap: 14px; padding: 10px 2px;
  border-top: 1px solid var(--rule); text-align: left; width: 100%;
  border-radius: 8px; box-sizing: border-box;
}
body[data-redesign="v2"] .rowitem:first-child { border-top: none; }
body[data-redesign="v2"] .rowitem:hover { background: var(--wash); }
body[data-redesign="v2"] .rowitem .t {
  font-family: var(--v2-mono); font-size: var(--t02); font-weight: 500;
  background: var(--yellow); color: var(--pop-ink);
  border: 1.5px solid var(--bd); border-radius: 999px; padding: 2px 9px;
  flex: none; font-variant-numeric: tabular-nums;
}
body[data-redesign="v2"] .rowitem .txt { font-size: var(--t05); font-weight: 600; letter-spacing: -0.01em; flex: 1; min-width: 0; overflow-wrap: anywhere; }
body[data-redesign="v2"] .rowitem .sub { font-size: var(--t04); color: var(--mute); white-space: nowrap; }
body[data-redesign="v2"] .rowitem .go { font-weight: 700; color: var(--cobalt); flex: none; }
html[data-theme="dark"] body[data-redesign="v2"] .rowitem .go { color: #93a5ff; }
/* THE row recipe under 640px: the title owns the line, the sub drops
   underneath (full width, ellipsized). In the flex row the sub's 40%
   plus chip + arrow squeezed .txt to ~90px on long names — one word
   per line, and the overflowing word painted INTO the sub's box
   (reproduced on /finden geheim rows, July 2026). */
@media (max-width: 640px) {
  body[data-redesign="v2"] .rowitem { flex-wrap: wrap; row-gap: 1px; }
  body[data-redesign="v2"] .rowitem .sub {
    order: 10; flex: 1 1 100%; min-width: 0; max-width: 100%;
    overflow: hidden; text-overflow: ellipsis;
  }
}

/* ── Das Register (Druckbogen №9): eine Dichtestufe unter .rowitem ────
   Für lange Listen: Anker (volle Stimme) → Register (diese Zeilen) →
   Gruppen (.rl-band mitten in der Liste). Gutter BERECHNET über
   BREITE[t02] = 6,3 px/Glyphe: "4,8 km" = 7 Glyphen ≈ 44 → 46 px.
   (.t.quiet's 70 px bleibt die Zeit-Schwester: "vor 12 Std." = 11
   Glyphen ≈ 69 → 70 px — beide Maße sind dieselbe Rechnung.) */
body[data-redesign="v2"] .rowlist.dense .rowitem { padding: 7px 2px; gap: 10px; }
body[data-redesign="v2"] .rowlist.dense .rowitem .txt { font-size: var(--t04); }
body[data-redesign="v2"] .rowitem .t.dist {
  background: none; border: none; box-shadow: none; padding: 0;
  color: var(--mute); min-width: 46px;
}
body[data-redesign="v2"] .rl-band {
  font-family: var(--inst); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--mute);
  margin: 12px 0 2px;
}
body[data-redesign="v2"] .rl-band:first-child { margin-top: 4px; }
/* Zeilen direkt nach einem Band-Kopf starten ohne Trenner. */
body[data-redesign="v2"] .rl-band + .rowitem { border-top: none; }
/* Register-Nebenspalte (Buslinien, Quellen-Zeit): Mono, still. */
body[data-redesign="v2"] .rowlist.dense .rowitem .aside {
  font-family: var(--v2-mono); font-size: var(--t02); color: var(--ink-soft);
  flex: none; font-variant-numeric: tabular-nums; white-space: nowrap;
}
body[data-redesign="v2"] .rowlist.dense .rowitem .aside .more-n { color: var(--mute); }
/* Quell-Tag im Register (News): die .s-kicker-.tag-Pille eine Nummer
   kleiner — gleiche Stufe (t01), kompakteres Polster. */
body[data-redesign="v2"] .tag-mini {
  font-family: var(--inst); font-weight: 700; font-size: var(--t01);
  letter-spacing: 0.14em; text-transform: uppercase; padding: 2px 7px;
  border-radius: 999px; border: 1.5px solid var(--bd); color: #fff;
  background: var(--cobalt); box-shadow: 2px 2px 0 var(--hard-c);
  white-space: nowrap; flex: none;
}
body[data-redesign="v2"] .tag-mini.t-orange { background: var(--orange); }
body[data-redesign="v2"] .tag-mini.t-green  { background: var(--green); }

/* ── Tonnen-Farbcode im Abfuhr-Kalender (user call, 0.125.1) ─────────
   Die echten Deckelfarben der AWM-Tonnen: grau/blau/grün/rot sind
   Realwelt-Farben, literal gepinnt — kein Theme-Job, kein Pop-Amt.
   Der Tusche-Ring hält sie in beiden Themen lesbar. */
body[data-redesign="v2"] .awm-dot {
  display: inline-block; width: 10px; height: 10px; border-radius: 999px;
  border: 1.5px solid var(--bd); margin-right: 5px; vertical-align: -1px;
}
body[data-redesign="v2"] .awm-dot.restabfall { background: #8f8f8f; }
body[data-redesign="v2"] .awm-dot.papier     { background: #2b6fd9; }
body[data-redesign="v2"] .awm-dot.bio        { background: #2f8a3c; }
body[data-redesign="v2"] .awm-dot.wertstoff  { background: #d9402b; }

/* ── THE pop-surface rule (round 41, primer #5) ──────────────────────
   A pop surface renders IDENTICALLY in both themes — and so does
   everything ON it. Pinning the theme tokens at the surface boundary
   makes every child (chips, option buttons, bar tracks, borders,
   piktos via currentColor) theme-independent by construction. The
   quiz card's chips used to flip to dark-card-on-yellow in dark while
   being paper-on-yellow in light (primer finding). Add new pop
   surfaces to THIS list; never write per-child dark overrides on pop
   ground. */
body[data-redesign="v2"] .hero,
body[data-redesign="v2"] .bogen,
body[data-redesign="v2"] .disc,
body[data-redesign="v2"] .qz-card,
body[data-redesign="v2"] .u-walk,
body[data-redesign="v2"] .walkbar,
body[data-redesign="v2"] .stamp-toast,
body[data-redesign="v2"] .nu-poster,
body[data-redesign="v2"] .atem-chip {
  --paper: #f6f1e4; --paper-2: #ece5d2; --card: #fffdf4;
  --ink: #17150f; --ink-soft: #514c3f; --mute: #8a8371;
  --rule: #ded5bd; --bd: #17150f; --hard-c: #17150f;
  --wash: rgba(43, 69, 217, 0.06);
}

/* ── Chip pairs/rows (round 41, primer #3): ONE nowrap-safe recipe ───
   flex + gap does the spacing — no leftover margin-indent when a chip
   wraps to its own line; a chip never breaks mid-text, it ellipsizes.
   Use for any pair/row of action chips (quiz reveal links etc.). */
body[data-redesign="v2"] .chip-pair {
  display: flex; flex-wrap: wrap; gap: 10px 8px; align-items: center;
}
body[data-redesign="v2"] .chip-pair > * {
  flex: none; max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── Discovery block: the yellow marker card ────────────────────────── */
body[data-redesign="v2"] .disc {
  background: var(--yellow); color: var(--pop-ink);
  border: 3px solid var(--bd); border-radius: 22px;
  padding: 20px 22px; display: flex; flex-direction: column; gap: 7px;
  box-shadow: 6px 6px 0 var(--hard-page);
  transform: rotate(-0.6deg); transition: transform var(--v2-t);
}
body[data-redesign="v2"] .disc:hover { transform: rotate(0deg); }
body[data-redesign="v2"] .d-kicker {
  font-family: var(--inst); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.22em; text-transform: uppercase;
  display: flex; align-items: center; gap: 8px;
}
body[data-redesign="v2"] .d-kicker::before { content: "★"; font-size: var(--t03); }
body[data-redesign="v2"] .d-main {
  font-family: var(--bric); font-weight: 600; font-size: var(--t06);
  line-height: 1.3; letter-spacing: -0.015em;
}
@media (min-width: 900px) {
  body[data-redesign="v2"] .d-main { font-size: var(--t07); }
}
body[data-redesign="v2"] .d-main em {
  font-style: normal; background: var(--pop-ink); color: var(--yellow);
  padding: 0 0.14em; border-radius: 0.14em;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
body[data-redesign="v2"] .d-link {
  align-self: flex-start; margin-top: 4px; display: inline-block;
  font-family: var(--inst); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.1em; text-transform: uppercase;
  background: var(--pop-ink); color: var(--yellow); border: 2px solid var(--pop-ink);
  border-radius: 999px; padding: 7px 15px; box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.25);
}

/* ── Chip + Frag-Münster-Zeile ──────────────────────────────────────────
   The app-wide chat dock died with №12 D1 (its ~120px of fixed chrome
   doubled a path the tab bar already offers). The .dock-* rules live in
   fragen_v2.html now — the one page where the dock IS the input. What
   stays app-wide: the .chip vocabulary, and the FRAG-MÜNSTER row that
   carries the contextual chips in the page flow (partials/frag_chips.html,
   the Nebenbei/MIT-EXTRAS recipe: mono label + rule + scrolling chips). */
body[data-redesign="v2"] .chip {
  font-family: var(--inst); font-weight: 600; font-size: var(--t03);
  background: var(--card); border: 2px solid var(--bd); border-radius: 999px;
  padding: 7px 14px; color: var(--ink); white-space: nowrap;
  box-shadow: 3px 3px 0 var(--hard-c); display: inline-block;
}
body[data-redesign="v2"] .fragrow {
  display: flex; align-items: center; gap: 12px; margin: 26px 0 0;
}
body[data-redesign="v2"] .fragrow .fr-label {
  font-family: var(--v2-mono); font-size: var(--t01); letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--mute); flex: none;
}
body[data-redesign="v2"] .fragrow .fr-rule { display: none; }
@media (min-width: 900px) {
  body[data-redesign="v2"] .fragrow .fr-rule {
    display: block; flex: none; width: 28px; height: 2px; background: var(--bd); opacity: 0.35;
  }
}
body[data-redesign="v2"] .fragrow .fr-chips {
  display: flex; gap: 7px; overflow-x: auto; scrollbar-width: none;
  /* Edge fade = "there's more, swipe" — a chip clipped hard at the
     viewport edge read as broken layout on phones (user feedback). */
  -webkit-mask-image: linear-gradient(90deg, #000 90%, transparent);
  mask-image: linear-gradient(90deg, #000 90%, transparent);
  padding: 4px 18px 4px 4px;
}
body[data-redesign="v2"] .fragrow .fr-chips::-webkit-scrollbar { display: none; }

/* ── Walk-resume pill (plakat-live.js) ──────────────────────────────────
   Shown while a Spaziergang is engaged, on every page but /unterwegs.
   Pop surface: fixed ink on marker yellow, identical in dark mode. */
body[data-redesign="v2"] .walkbar {
  display: flex; align-items: center; gap: 4px;
  width: fit-content; max-width: 100%; margin: 0 auto 9px;
  font-family: var(--inst); font-weight: 700; font-size: var(--t03);
  background: var(--yellow); color: #17150f;
  border: 2px solid #17150f; border-radius: 999px;
  padding: 4px 6px 4px 16px; box-shadow: 3px 3px 0 var(--hard-page);
}
body[data-redesign="v2"] .walkbar .wb-link {
  display: flex; align-items: center; gap: 8px;
  min-width: 0; color: inherit; padding: 4px 0;
}
body[data-redesign="v2"] .walkbar .wb-title {
  min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
body[data-redesign="v2"] .walkbar .wb-x {
  flex: none; width: 28px; height: 28px; border-radius: 999px;
  display: grid; place-items: center; color: #17150f;
  font-size: var(--t03); font-weight: 700; opacity: 0.65;
}
body[data-redesign="v2"] .walkbar .wb-x:hover { opacity: 1; background: rgba(23, 21, 15, 0.1); }
body[data-redesign="v2"] .walkbar .wb-prog {
  flex: none; font-family: var(--v2-mono); font-size: var(--t02);
  font-variant-numeric: tabular-nums;
}
/* The pill adds ~48px to the dock stack — keep scroll-bottom content clear. */
body[data-redesign="v2"].has-walkbar { padding-bottom: 206px; }
@media (min-width: 900px) {
  body[data-redesign="v2"].has-walkbar { padding-bottom: 176px; }
}
/* Dockless pages (fullscreen map): float above the tab bar instead. */
body[data-redesign="v2"] .walkbar--fixed {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(72px + env(safe-area-inset-bottom)); z-index: 1100;
  margin: 0; max-width: calc(100vw - 24px);
}
@media (min-width: 900px) {
  body[data-redesign="v2"] .walkbar--fixed { bottom: 24px; left: auto; right: 24px; transform: none; }
}

/* ── Entdecker-Pass stamp button (round 24) ─────────────────────────────
   Lives inside Leaflet popups on /finden?c=geheim — cross-page, so it
   sits here and not in a page-scoped style block. .on = already stamped. */
body[data-redesign="v2"] .fd-stamp {
  display: inline-block; margin-top: 6px; cursor: pointer;
  font-family: var(--inst); font-weight: 700; font-size: var(--t03);
  padding: 4px 11px; border: 2px solid var(--bd); border-radius: 999px;
  background: var(--card); color: var(--ink); box-shadow: 2px 2px 0 var(--hard-c);
}
body[data-redesign="v2"] .fd-stamp.on {
  background: var(--yellow); color: var(--pop-ink);
  cursor: default; box-shadow: none;
}

/* ── Mobile tab bar ─────────────────────────────────────────────────── */
body[data-redesign="v2"] .tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 1105;
  background: var(--dock-bg); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  /* №25 A: Haarlinie statt 2px --bd. Im Dunkeln war --bd Creme — derselbe
     helle Balken, den №23 aus der Anschlagzeile genommen hat, nur unten.
     Die Leiste schwebt ohnehin (eigene Fläche + Weichzeichner) und braucht
     keine zweite Kante, um sich abzusetzen. */
  border-top: 1px solid var(--rule);
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  padding: 7px 10px calc(7px + env(safe-area-inset-bottom));
}
body[data-redesign="v2"] .tab {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  font-family: var(--inst); font-weight: 700;
  /* №25 B: die Beschriftung stand auf --t01 — der KLEINSTEN Stufe der
     ganzen Skala — und zwar erzwungen: bei 375px ist eine Spalte 75px
     breit, und „UNTERWEGS" belegte in Versalien mit 0.16em bereits 71px.
     Ein einziges Wort deckelte die Schriftgröße der ganzen Navigation.
     Kleinschreibung hebt den Deckel auf: dieselben neun Buchstaben
     brauchen auf t03 ohne Sperrung nur 62px — ein Drittel größer bei
     MEHR Reserve. Und es ist die Hausstimme; die Anzeigenschrift der App
     ist durchgehend klein. Wer hier wieder Versalien will, muss zuerst
     „unterwegs" kürzen. */
  font-size: var(--t03); letter-spacing: 0; text-transform: lowercase;
  color: var(--mute); padding: 5px 0 3px;
}
body[data-redesign="v2"] .tab svg { width: 21px; height: 21px; }
body[data-redesign="v2"] .tab .ic {
  display: grid; place-items: center; width: 46px; height: 30px; border-radius: 999px;
  border: 2px solid transparent; transition: all var(--v2-t);
}
body[data-redesign="v2"] .tab.active { color: var(--ink); }
body[data-redesign="v2"] .tab.active .ic {
  background: var(--yellow); color: var(--pop-ink);
  border-color: var(--bd); box-shadow: 2px 2px 0 var(--hard-c);
  animation: v2-tabpop 0.28s ease-out;
}
/* The Unterwegs center action (round 47): a raised cobalt circle — the
   mode among the destinations. Rise is capped at 12px so it never paints
   into the dock stack above (dock z 1100 < tabbar 1105 — overlap would
   cover the /fragen composer). Active = pressed (.push vocabulary), NOT
   the yellow pill: cobalt is this button's identity. */
body[data-redesign="v2"] .tab-uw { position: relative; }
body[data-redesign="v2"] .tab-uw .ic.uw-btn {
  position: relative;
  width: 44px; height: 44px; margin-top: -12px; border-radius: 999px;
  background: var(--cobalt); color: #fff;
  border: 2px solid var(--bd);
  /* №25 V3: ein Ring aus Papier um den Knopf. Gemessen saß seine Oberkante
     exakt auf der Leistenkante (731 zu 732) — bei 2px Kante las sich das
     noch als „sitzt auf", bei einer Haarlinie lief die Linie sichtbar in
     den Ring hinein und hörte dort auf. Zwei Dinge, die sich berühren,
     müssen entweder deutlich überlappen oder deutlich Abstand halten.
     Der Papierring gibt den Abstand — ohne feste Prozentwerte wie eine
     Lücke in der Linie, und er wandert mit, egal wie viele Felder die
     Leiste hat. Reihenfolge zählt: Papier zuerst, harter Versatz darüber. */
  box-shadow: 0 0 0 5px var(--paper), 2px 3px 0 var(--hard-c);
}
body[data-redesign="v2"] .tab-uw .ic.uw-btn .pi { width: 21px; height: 21px; }
body[data-redesign="v2"] .tab-uw.active .ic.uw-btn {
  background: var(--cobalt); color: #fff;
  /* Gedrückt heißt: der harte Versatz fällt weg, der Papierring BLEIBT —
     sonst berührte der aktive Knopf die Haarlinie wieder (№25 V3). */
  box-shadow: 0 0 0 5px var(--paper); transform: translate(1px, 2px);
  animation: none;
}
/* Live dot while Begleitung runs (class set by plakat-live.js) — signal
   orange = live/urgent, same job as everywhere else. */
body[data-redesign="v2"] .tab-uw.live .ic.uw-btn::after {
  content: ""; position: absolute; top: -3px; right: -3px;
  width: 11px; height: 11px; border-radius: 999px;
  background: var(--signal); border: 2px solid var(--bd);
}
@media (min-width: 900px) {
  body[data-redesign="v2"] .tabbar { display: none; }
}

/* ── Finden: filter chips + map box (Phase 3 consumers) ─────────────── */
body[data-redesign="v2"] .chiprow {
  display: flex; gap: 8px; overflow-x: auto; padding: 18px 4px 16px; scrollbar-width: none;
  /* Same edge fade the dock rail earned: a chip sliced in half at the
     viewport edge reads as broken layout, a faded one reads as "swipe". */
  -webkit-mask-image: linear-gradient(90deg, #000 92%, transparent);
  mask-image: linear-gradient(90deg, #000 92%, transparent);
  padding-right: 18px;
}
body[data-redesign="v2"] .chiprow::-webkit-scrollbar { display: none; }
body[data-redesign="v2"] .fchip {
  font-family: var(--inst); font-weight: 600; font-size: var(--t03);
  border: 2px solid var(--bd); border-radius: 999px; padding: 8px 16px;
  color: var(--ink); background: var(--card); white-space: nowrap;
  box-shadow: 3px 3px 0 var(--hard-c); display: inline-block;
}
body[data-redesign="v2"] .fchip.active,
body[data-redesign="v2"] .fchip[aria-pressed="true"] { background: var(--cobalt); color: #fff; }

/* ── Die Pille (№14): der App-weite "geh dorthin"-Knopf. ─────────────
   Die Pille GEHT (Link/Submit), der Chip WÄHLT (Filter/Toggle = .fchip).
   Auf Pop-Flächen gilt weiter .hbtn (Hero-Kleid) — dokumentierte Ausnahme. */
body[data-redesign="v2"] .pille {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--inst); font-weight: 700; font-size: var(--t03);
  letter-spacing: 0.08em; text-transform: uppercase;
  padding: 9px 18px; border: 2px solid var(--bd); border-radius: 999px;
  background: var(--cobalt); color: #fff;
  box-shadow: 3px 3px 0 var(--hard-c); cursor: pointer; text-decoration: none;
}
body[data-redesign="v2"] .pille.leise { background: var(--card); color: var(--ink); }
body[data-redesign="v2"] .pille.mini {
  font-size: var(--t02); letter-spacing: 0.06em;
  padding: 4px 12px; box-shadow: 2px 2px 0 var(--hard-c); gap: 5px;
}
body[data-redesign="v2"] .pille .pi { width: 14px; height: 14px; }

/* Feld-Formen (№14): Einzelfeld-Suche = 999er-Pille (bs-search),
   mehrfeldriges Formular = 12er-Karte (route rf-field). Entscheidung,
   kein Zufall — neue Felder wählen eine der beiden Formen. */

body[data-redesign="v2"] .mapbox {
  border: 2.5px solid var(--bd); border-radius: 22px; overflow: hidden;
  background: var(--paper-2); position: relative; height: clamp(240px, 40vw, 380px);
  box-shadow: 6px 6px 0 var(--hard-c);
}
/* Desktop deserves a real map, not a letterbox (user feedback, July 2026). */
@media (min-width: 900px) {
  body[data-redesign="v2"] .mapbox { height: clamp(420px, 58vh, 680px); }
}
body[data-redesign="v2"] .map-tag {
  position: absolute; left: 14px; bottom: 14px; z-index: 500;
  font-family: var(--v2-mono); font-size: var(--t01); letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink); background: var(--dock-bg); border: 2px solid var(--bd);
  border-radius: 999px; padding: 5px 11px;
}
/* Floating map action buttons (round 44): actions live ON the map, not
   in a chip row below it — the map gets the space. Stacked bottom-LEFT
   (zoom is top-left, attribution bottom-right). Same visual family as
   unterwegs' .u-follow. Pikto inherits currentColor. */
/* Leaflets Zoom-Balken in die Karten-Sprache geholt (user-gemeldet): er
   war als einziges Element auf jeder der 17 Karten noch Leaflet-Standard
   — weißer Kasten mit grauen Haarlinien, mitten zwischen Plakat-Knöpfen.
   Jetzt zwei runde Knöpfe im .map-fab-Rezept: gleiche Größe, gleicher
   Tinte-Rand, gleicher harter Versatz, gleiche Druck-Geste. Der Balken
   selbst legt seinen Rahmen ab und wird zur reinen Halterung mit Abstand.
   Gilt app-weit, weil Leaflet die Steuerung auf jeder Karte gleich baut. */
body[data-redesign="v2"] .leaflet-control-zoom {
  border: none !important; box-shadow: none !important;
  display: flex; flex-direction: column; gap: 8px;
}
body[data-redesign="v2"] .leaflet-control-zoom a {
  width: 44px; height: 44px;
  border: 2px solid var(--bd); border-radius: 999px;
  background: var(--card); color: var(--ink);
  box-shadow: 3px 3px 0 var(--hard-c);
  display: grid; place-items: center;
  font-family: var(--inst); font-weight: 700; font-size: var(--t07);
  line-height: 1; text-indent: 0;
  transition: transform var(--v2-t), box-shadow var(--v2-t),
              background var(--v2-t), color var(--v2-t);
}
/* Leaflet rundet den Balken über :first-child/:last-child ab (4px oben,
   4px unten). Hier hilft mehr Spezifität NICHT: die Leaflet-Regel hat
   dieselbe, und ihr Stylesheet wird pro Kartenseite im {% block head %}
   NACH plakat.css geladen — bei Gleichstand gewinnt das spätere Blatt.
   Deshalb ausnahmsweise !important; ohne es blieben zwei Ecken eckig. */
body[data-redesign="v2"] .leaflet-control-zoom a:first-child,
body[data-redesign="v2"] .leaflet-control-zoom a:last-child {
  border-radius: 999px !important;
}
/* Leaflet hängt eigene Hover-/Rand-Regeln an .leaflet-bar a — die müssen
   weg, sonst blitzt der graue Standard beim Überfahren durch. */
body[data-redesign="v2"] .leaflet-control-zoom a:hover,
body[data-redesign="v2"] .leaflet-control-zoom a:focus {
  background: var(--card); color: var(--ink); border-color: var(--bd);
}
body[data-redesign="v2"] .leaflet-control-zoom a:active {
  transform: translate(1px, 1px); box-shadow: 0 0 0 var(--hard-c);
}
/* Am Zoom-Ende deaktiviert Leaflet den Knopf — das muss man sehen. */
body[data-redesign="v2"] .leaflet-control-zoom a.leaflet-disabled {
  opacity: 0.42; box-shadow: 1px 1px 0 var(--hard-c);
}
body[data-redesign="v2"] .map-fab {
  position: absolute; left: 12px; z-index: 500;
  width: 44px; height: 44px; border: 2px solid var(--bd); border-radius: 999px;
  background: var(--card); color: var(--ink);
  box-shadow: 3px 3px 0 var(--hard-c); cursor: pointer;
  display: grid; place-items: center; font-size: var(--t06);
}
body[data-redesign="v2"] .map-fab[aria-pressed="true"] { background: var(--cobalt); color: #fff; }
body[data-redesign="v2"] .map-fab.busy { opacity: 0.55; }
body[data-redesign="v2"] .map-fab.err { border-color: var(--orange); color: var(--orange); }
body[data-redesign="v2"] .map-fab-badge {
  position: absolute; left: 50px; top: 50%; transform: translateY(-50%);
  font-family: var(--v2-mono); font-size: var(--t02); font-weight: 700;
  background: var(--card); color: var(--ink); border: 1.5px solid var(--bd);
  border-radius: 999px; padding: 2px 7px; white-space: nowrap;
}
body[data-redesign="v2"] .map-fab-badge:empty { display: none; }

/* Tile contrast: voyager reads washed-out on paper, dark_all sinks streets
   into the background. Pane-level (not per-tile — avoids fade-in seams);
   markers/popups live in sibling panes and are never touched.
   NOTE (round 36b): a "Papierkarte" desaturation (grayscale+sepia) was
   tried and REVERTED — it killed greens/streets at every zoom
   (user-reported). Map branding comes from markers/popups, not from
   muting the tiles. Tune contrast complaints here first. */
body[data-redesign="v2"] .leaflet-tile-pane {
  filter: contrast(1.12) saturate(1.15);
}
/* Dunkel, dritte Meldung derselben Sache („Karte zu dunkel, Straßen kaum
   sichtbar"). Die ersten beiden Versuche drehten an brightness — und das
   war der falsche Hebel: brightness() ist MULTIPLIKATIV. CARTO dark_all
   („Dark Matter") ist als schwarze Leinwand für Overlays gebaut, die
   Landfläche liegt bei ~#0e0e0e. 0,055 × 1,3 ist immer noch schwarz, und
   selbst × 1,9 bleibt es fast schwarz — deshalb hat es nie geholfen.
   Was Tiefen anhebt, ist contrast UNTER 1: das zieht alles Richtung
   Mittelgrau, erst danach hebt brightness sinnvoll an. Gemessen im
   Browser: bei contrast(0.62) sind die Straßen voll da, aber die Nacht
   ist weg; 0.82/1.65 ist die Stelle, an der beides stimmt.
   Die Regel aus der Notiz oben gilt unverändert: das hier ist ein
   Kontrast-Knopf, keine Marken-Einfärbung. */
html[data-theme="dark"] body[data-redesign="v2"] .leaflet-tile-pane {
  filter: contrast(0.82) brightness(1.65) saturate(1.12);
}
/* Leaflet's default attribution control, restyled into the map-tag voice
   — ONE attribution system on every map (the "Leaflet" prefix is dropped
   centrally in __nahbarBasemap). Links stay links: OSM's attribution
   policy wants the copyright page reachable. */
body[data-redesign="v2"] .leaflet-control-attribution {
  font-family: var(--v2-mono); font-size: var(--t01); letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--mute);
  background: var(--dock-bg); border: 1.5px solid var(--bd);
  border-radius: 999px; padding: 3px 10px; margin: 0 10px 10px 0;
}
body[data-redesign="v2"] .leaflet-control-attribution a {
  color: var(--mute); text-decoration: none;
}
/* ── Unified marker family (round 37) — every map speaks one hand ─────
   Two shapes from __nahbarPin() in plakat-live.js: the teardrop PIN
   (places you can go to) and the round BADGE (counts, numbers, dots,
   "you"), plus .nbp-line for live-bus line badges. HTML/SVG markers
   styled through the theme tokens, so ink borders and hard shadows
   flip with dark mode — the old hardcoded #17150f strokes vanished
   on dark tiles. Never hand-roll a marker again: call the factory. */
/* №24 A — der Ring der Tropfen-Pins ist Tinte, in BEIDEN Themen.
   Vorsicht mit der Historie: der Kommentar oben erzählt, dass die alten
   hartkodierten #17150f-Ränder auf dunklen Tiles verschwanden — deshalb
   wurde damals auf --bd umgestellt (dunkel = creme). Richtig war das,
   solange dark_all fast schwarz gerendert wurde. Seit 0.139.5 liegt der
   Kartengrund im Mittelgrau, und dort trägt Tinte wieder — während der
   creme Ring um gesättigtes Gelb genau die Überstrahlung erzeugt hat,
   die gemeldet wurde. Wer die Tile-Filter je wieder verdunkelt, muss
   diesen Ring mitprüfen: die beiden Werte hängen zusammen.
   Bewusst NICHT umgestellt: .nbp (Punkte, „du bist hier", Linien-Badges)
   — die sollen sich abheben, nicht einfügen. */
body[data-redesign="v2"] { --pin-ring: #17150f; }
body[data-redesign="v2"] .nbp-wrap { background: none; border: none; }
/* Bündel-Plakette (№24 B2): dieselbe Sprache wie ein Pin — gelbe Fläche,
   Tinte-Ring, harter Versatz — nur rund und mit einer Zahl statt Pikto. */
/* Fläche und Schriftfarbe kommen INLINE aus __nahbarCluster — eine
   Plakette trägt immer die Farbe der Pins, die in ihr stecken. Hier
   hartkodiertes Gelb hätte auf jeder Kobalt-Kategorie gelogen
   (user-gemeldet auf Trinkwasser: blaue Pins, gelbe Bündel). */
body[data-redesign="v2"] .nbp-cl {
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px; border: 2px solid var(--pin-ring);
  box-shadow: 2px 2px 0 var(--hard-c); box-sizing: border-box;
  font-family: var(--v2-mono); font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums; cursor: pointer;
  transition: transform var(--v2-t), box-shadow var(--v2-t);
}
/* Zeile überfahren, deren Pin in diesem Bündel steckt (№24). Die Kopplung
   aus №15 zeigt sonst nichts — der Marker hängt nicht an der Karte. */
body[data-redesign="v2"] .nbp-cl.hot {
  transform: scale(1.18); box-shadow: 3px 3px 0 var(--hard-c);
}
body[data-redesign="v2"] .nbp {
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px; border: 2px solid var(--bd);
  box-shadow: 2px 2px 0 var(--hard-c); box-sizing: border-box;
  font-family: var(--v2-mono); font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* User position: signal orange = "you, live", card ring lifts it off
   any tile color. */
body[data-redesign="v2"] .nbp-you {
  box-shadow: 0 0 0 2.5px var(--card), 3px 3px 0 var(--hard-c);
}
body[data-redesign="v2"] .nbp-line {
  display: inline-block; background: var(--cobalt); color: #fff;
  font-family: var(--v2-mono); font-size: var(--t02); font-weight: 700;
  padding: 3px 6px; border-radius: 6px; border: 2px solid var(--bd);
  box-shadow: 2px 2px 0 var(--hard-c); line-height: 1.2;
  text-align: center; white-space: nowrap;
}
/* Leaflet popups in the card language — one popup face on every map. */
body[data-redesign="v2"] .leaflet-container { font-family: var(--inst); }
body[data-redesign="v2"] .leaflet-popup-content-wrapper {
  background: var(--card); color: var(--ink);
  border: 2px solid var(--bd); border-radius: 14px;
  box-shadow: 4px 4px 0 var(--hard-c);
}
body[data-redesign="v2"] .leaflet-popup-content { margin: 12px 16px; font-size: var(--t04); line-height: 1.5; }
body[data-redesign="v2"] .leaflet-popup-content a { color: var(--cobalt); font-weight: 600; }
html[data-theme="dark"] body[data-redesign="v2"] .leaflet-popup-content a { color: #93a5ff; }
/* Popup-Distanz (Druckbogen №8) — Zahl spricht Zahlensprache. Lives here,
   not in a page style, so test_skala guards the step. */
body[data-redesign="v2"] .fd-dist {
  font-family: var(--v2-mono); font-size: var(--t02); letter-spacing: 0.05em;
}
body[data-redesign="v2"] .leaflet-popup-tip {
  background: var(--card); border: 2px solid var(--bd); box-shadow: none;
}
body[data-redesign="v2"] .leaflet-popup-close-button {
  font-size: var(--t05); color: var(--mute); padding: 7px 7px 0 0;
}
body[data-redesign="v2"] .results { margin-top: 18px; }

/* ── Assistant view (Phase 4 consumers) ─────────────────────────────── */
body[data-redesign="v2"] .chatpage { max-width: 740px; margin: 0 auto; }
body[data-redesign="v2"] .msg { margin-bottom: 18px; display: flex; flex-direction: column; gap: 8px; }
body[data-redesign="v2"] .msg .who {
  font-family: var(--v2-mono); font-size: var(--t01); letter-spacing: 0.24em;
  text-transform: uppercase; color: var(--mute);
}
body[data-redesign="v2"] .msg.user { align-items: flex-end; }
body[data-redesign="v2"] .bub { max-width: 85%; padding: 13px 18px; font-size: var(--t05); line-height: 1.5; font-weight: 500; }
body[data-redesign="v2"] .msg.user .bub {
  background: var(--cobalt); color: #fff; border: 2px solid var(--bd);
  border-radius: 20px 20px 6px 20px; box-shadow: 3px 3px 0 var(--hard-c);
}
body[data-redesign="v2"] .msg.bot .bub {
  background: var(--card); border: 2px solid var(--bd);
  border-radius: 6px 20px 20px 20px; box-shadow: 3px 3px 0 var(--hard-c);
}
body[data-redesign="v2"] .placecard {
  border: 2px solid var(--bd); background: var(--card); border-radius: 16px;
  padding: 14px 17px; max-width: 85%;
  display: flex; flex-direction: column; gap: 4px; box-shadow: 4px 4px 0 var(--hard-c);
}
body[data-redesign="v2"] .p-name { font-family: var(--bric); font-weight: 700; font-size: var(--t06); letter-spacing: -0.02em; }
body[data-redesign="v2"] .p-meta {
  font-family: var(--v2-mono); font-size: var(--t02); letter-spacing: 0.05em;
  color: var(--ink-soft); font-variant-numeric: tabular-nums;
}
body[data-redesign="v2"] .p-meta .open { color: var(--ok); font-weight: 500; }
body[data-redesign="v2"] .p-note { font-size: var(--t04); color: var(--ink-soft); }
body[data-redesign="v2"] .chat-exits { display: flex; gap: 8px; flex-wrap: wrap; }
body[data-redesign="v2"] .exit {
  display: inline-block;
  font-family: var(--inst); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.08em; text-transform: uppercase;
  border: 2px solid var(--bd); border-radius: 999px; padding: 8px 16px;
  background: var(--yellow); color: var(--pop-ink); box-shadow: 3px 3px 0 var(--hard-c);
}
body[data-redesign="v2"] .exit.quiet { background: var(--card); color: var(--ink-soft); }

/* ── Chat-Antwort-Fragmente (Druckbogen №8): die Blase ist die Karte ──
   Server-HTML aus assistant.py. Ein Rahmen pro Antwort: Fragmente
   zeichnen NIE einen eigenen Außenrand — die Bot-Blase hat Tusche,
   Ecke und Schatten schon. Daten-Antworten bekommen die volle Spalte. */
body[data-redesign="v2"] .msg.bot .bub:has(.fr) { max-width: 100%; }
body[data-redesign="v2"] .fr { margin-top: 14px; }
body[data-redesign="v2"] .fr-head {
  display: flex; justify-content: space-between; gap: 10px;
  font-family: var(--f-text); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink);
}
body[data-redesign="v2"] .fr-head .more { color: var(--mute); letter-spacing: 0.1em; }
body[data-redesign="v2"] .fr-group {
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  margin-top: 10px; padding-top: 9px; border-top: 2px solid var(--bd);
}
body[data-redesign="v2"] .fr-group .name { font-size: var(--t04); font-weight: 700; }
body[data-redesign="v2"] .fr-group .name.akzent {
  font-family: var(--f-plakat); font-weight: 650; font-size: var(--t05);
  letter-spacing: -0.015em; text-transform: lowercase;
}
body[data-redesign="v2"] .fr-group .meta {
  font-family: var(--f-mono); font-size: var(--t02); letter-spacing: 0.08em;
  color: var(--mute); text-transform: uppercase;
}
body[data-redesign="v2"] .fr-row {
  display: flex; align-items: center; gap: 10px; padding: 7px 0;
  border-top: 1px solid var(--rule);
}
body[data-redesign="v2"] .fr-group + .fr-row,
body[data-redesign="v2"] .fr-map + .fr-row { border-top: none; }
body[data-redesign="v2"] .fr-row .txt {
  flex: 1; min-width: 0; font-size: var(--t04); font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body[data-redesign="v2"] .fr-row .txt .sub {
  display: block; font-family: var(--f-mono); font-size: var(--t01);
  letter-spacing: 0.1em; color: var(--mute); text-transform: uppercase;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body[data-redesign="v2"] .fr-min {
  font-family: var(--f-mono); font-size: var(--t06); font-weight: 500;
  color: var(--cobalt); flex: none; font-variant-numeric: tabular-nums;
}
body[data-redesign="v2"] .fr-min.now { color: var(--orange); }
/* Kobalt auf dunkler Karte ≈ 2,4:1 — dieselbe Hebung wie die Popup-Links. */
html[data-theme="dark"] body[data-redesign="v2"] .fr-min { color: #93a5ff; }
html[data-theme="dark"] body[data-redesign="v2"] .fr-min.now { color: var(--orange); }
body[data-redesign="v2"] .fr-akzent {
  font-family: var(--f-plakat); font-weight: 800; font-size: var(--t07);
  letter-spacing: -0.02em; text-transform: lowercase; flex: none;
}
body[data-redesign="v2"] .fr-mono {
  font-family: var(--f-mono); font-size: var(--t03); color: var(--ink);
  flex: none; font-variant-numeric: tabular-nums;
}
body[data-redesign="v2"] .fr-unit {
  font-family: var(--f-mono); font-size: var(--t01); letter-spacing: 0.14em;
  color: var(--mute); text-transform: uppercase; flex: none;
}
body[data-redesign="v2"] .fr-stick {
  font-weight: 700; font-size: var(--t01); letter-spacing: 0.1em;
  text-transform: uppercase; background: var(--yellow); color: var(--pop-ink);
  border: 1.5px solid var(--bd); border-radius: 6px; padding: 2px 6px; flex: none;
}
body[data-redesign="v2"] .fr-stick.live { background: var(--orange); color: var(--pop-cream); }
body[data-redesign="v2"] .fr-glyph {
  display: grid; place-items: center; width: 30px; height: 30px;
  border: 2px solid var(--bd); border-radius: 999px; background: var(--card);
  box-shadow: 2px 2px 0 var(--hard-c); flex: none;
}
body[data-redesign="v2"] .fr-glyph .pi { width: 15px; height: 15px; vertical-align: 0; }
body[data-redesign="v2"] .fr-map {
  height: 150px; border: 2px solid var(--bd); border-radius: 14px;
  margin-top: 10px; overflow: hidden; background: var(--paper-2);
}
body[data-redesign="v2"] .fr-exits { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
/* Bilder in Fragmenten (Skulptur, Orte): runde Ecke, kein eigener Rahmen. */
body[data-redesign="v2"] .fr-img {
  width: 100%; height: 160px; object-fit: cover; display: block;
  border-radius: 10px; margin-top: 10px;
}
body[data-redesign="v2"] .fr .p-note { margin-top: 8px; }
/* Prosa-Anker + Listen der Antwort — assistant.py trägt nur noch Klassen
   (test_fragmente). .chat-a gilt auch im Skulptur-Chat (linkify_escaped). */
body[data-redesign="v2"] .chat-a {
  color: var(--cobalt); font-weight: 600;
  text-decoration: underline; text-underline-offset: 2px;
}
html[data-theme="dark"] body[data-redesign="v2"] .chat-a { color: #93a5ff; }
body[data-redesign="v2"] .chat-ul { margin: 8px 0; padding-left: 2px; }
body[data-redesign="v2"] .chat-ul .li { display: flex; gap: 8px; padding: 3px 0; }
body[data-redesign="v2"] .chat-ul .pt { color: var(--cobalt); font-weight: 600; }
html[data-theme="dark"] body[data-redesign="v2"] .chat-ul .pt { color: #93a5ff; }

/* ── Entdecken shelf + Mein Münster (Phase 5 consumers) ─────────────── */
body[data-redesign="v2"] .shelf { display: grid; grid-template-columns: 1fr; gap: 18px; padding-top: 12px; }
@media (min-width: 900px) {
  body[data-redesign="v2"] .shelf { grid-template-columns: 1fr 1fr; }
}
body[data-redesign="v2"] .special {
  border: 3px solid var(--bd); border-radius: 22px; padding: 24px 26px;
  display: flex; flex-direction: column; gap: 9px; min-height: 170px;
  text-align: left; box-shadow: 6px 6px 0 var(--hard-c);
  background: var(--card);
}
body[data-redesign="v2"] .special.c-ink    { background: #17150f; color: var(--pop-cream); }
body[data-redesign="v2"] .special.c-green  { background: var(--green); color: var(--pop-cream); }
body[data-redesign="v2"] .special.c-yellow { background: var(--yellow); color: var(--pop-ink); }
body[data-redesign="v2"] .sp-kicker {
  font-family: var(--inst); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.2em; text-transform: uppercase; opacity: 0.85;
}
body[data-redesign="v2"] .special h3 {
  margin: 0; font-family: var(--bric); font-weight: 800; text-transform: lowercase;
  font-size: var(--t09); letter-spacing: -0.025em; line-height: 1;
}
body[data-redesign="v2"] .special h3 em {
  font-style: normal; padding: 0 0.13em; border-radius: 0.13em;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  background: var(--cobalt); color: #fff;
}
body[data-redesign="v2"] .special.c-ink h3 em    { background: var(--yellow); color: var(--pop-ink); }
body[data-redesign="v2"] .special.c-green h3 em  { background: var(--pop-cream); color: var(--green-deep); }
body[data-redesign="v2"] .special.c-yellow h3 em { background: var(--pop-ink); color: var(--yellow); }
body[data-redesign="v2"] .special p { margin: 0; font-size: var(--t05); line-height: 1.45; opacity: 0.92; font-weight: 500; }
body[data-redesign="v2"] .mein {
  grid-column: 1 / -1; border: 3px solid var(--bd); border-radius: 22px;
  background: var(--card); padding: 24px 26px; box-shadow: 6px 6px 0 var(--hard-c);
}
body[data-redesign="v2"] .mein .sp-kicker { color: var(--cobalt); display: block; margin-bottom: 6px; opacity: 1; }
html[data-theme="dark"] body[data-redesign="v2"] .mein .sp-kicker { color: #93a5ff; }
body[data-redesign="v2"] .mein h3 {
  margin: 0 0 4px; font-family: var(--bric); font-weight: 800; text-transform: lowercase;
  font-size: var(--t08); letter-spacing: -0.02em;
}
body[data-redesign="v2"] .mein p.sub { margin: 0 0 18px; font-size: var(--t05); color: var(--ink-soft); }
body[data-redesign="v2"] .mein-grid { display: grid; grid-template-columns: 1fr; gap: 16px; }
@media (min-width: 720px) {
  body[data-redesign="v2"] .mein-grid { grid-template-columns: repeat(4, 1fr); }
}
body[data-redesign="v2"] .mlbl {
  font-family: var(--inst); font-weight: 700; font-size: var(--t01);
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--mute); margin-bottom: 8px;
}
body[data-redesign="v2"] .seg { display: flex; gap: 6px; flex-wrap: wrap; }
body[data-redesign="v2"] .seg button {
  font-family: var(--inst); font-weight: 600; font-size: var(--t03); padding: 7px 13px;
  border: 2px solid var(--bd); border-radius: 999px; color: var(--ink-soft);
  background: var(--card); box-shadow: 2px 2px 0 var(--hard-c);
  transition: all var(--v2-t);
}
body[data-redesign="v2"] .seg button[aria-pressed="true"] { background: var(--cobalt); color: #fff; }

/* ── Form dots (Preußen last-5, W/D/L) ──────────────────────────────── */
body[data-redesign="v2"] .formdots { display: flex; gap: 7px; align-items: center; margin: 2px 0 10px; }
body[data-redesign="v2"] .formdots .fd {
  width: 27px; height: 27px; border-radius: 50%; border: 2px solid var(--bd);
  display: grid; place-items: center; flex: none;
  font-family: var(--v2-mono); font-size: var(--t02); font-weight: 700;
  box-shadow: 2px 2px 0 var(--hard-c);
}
body[data-redesign="v2"] .formdots .fd-W { background: var(--green); color: var(--pop-cream); }
body[data-redesign="v2"] .formdots .fd-D { background: var(--paper-2); color: var(--ink); }
body[data-redesign="v2"] .formdots .fd-L { background: var(--orange); color: var(--pop-cream); }

/* ── Due chip (marker-yellow urgency, e.g. Abfuhr "Morgen") ─────────── */
body[data-redesign="v2"] .due-chip {
  display: inline-block; font-family: var(--v2-mono); font-size: var(--t02); font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  background: var(--yellow); color: var(--pop-ink);
  border: 2px solid var(--bd); border-radius: 999px; padding: 2px 10px;
  transform: rotate(-1.2deg); box-shadow: 2px 2px 0 var(--hard-c);
  vertical-align: 1px;
}

/* ── Weather graph (inline SVG, coords pre-computed in feed.py) ───────
   ONE consumer since round 74: the Tagesbogen (.bogen). The old card
   variant is deleted (Druckbogen №7 §1) — its three font sizes rendered
   at 6–9px on a 375px phone, below the ladder's bottom step, and nothing
   rendered it. These unscoped rules are the shared bones (face, weight,
   strokes); every font-size lives in the .bogen set further down. */
body[data-redesign="v2"] .wx-graph { width: 100%; height: auto; display: block; margin-top: 8px; }
body[data-redesign="v2"] .wx-graph text {
  font-family: var(--f-mono); fill: var(--mute);
}
/* Round 38: data color for the weather curve/bars. Pop cobalt sinks on
   the dark card at 3px — lighten it there (same pairing as .rowitem .go). */
body[data-redesign="v2"] { --wx-line: var(--cobalt); }
html[data-theme="dark"] body[data-redesign="v2"] { --wx-line: #7b8ff0; }
body[data-redesign="v2"] .wx-graph .wx-grid { stroke: var(--rule); }
body[data-redesign="v2"] .wx-graph .wx-base { stroke: var(--bd); }
body[data-redesign="v2"] .wx-graph .wx-line { stroke-width: 3; }
body[data-redesign="v2"] .wx-graph .wx-read { fill: var(--ink); font-weight: 700; }
body[data-redesign="v2"] .wx-graph .wx-cursor { stroke: var(--ink-soft); }
body[data-redesign="v2"] .wx-graph .wx-ann { fill: var(--ink); font-weight: 700; }
body[data-redesign="v2"] .wx-graph .wx-chip-t {
  fill: #fff; font-weight: 700; letter-spacing: 0.14em;
}

/* ── Der Tagesbogen: rank 1 of the Stadtpuls report (round 74) ────────
   Druckbogen №5, candidate 1b. ONE pop surface where nine same-weight
   cards used to be: the 24h curve, the day's headline, the Abfuhr
   sticker. Same physics as .hero (3px outline, 7px hard shadow, 26px
   radius) — and it is on the pop-surface list above, so every chip,
   border and pikto inside is theme-independent BY CONSTRUCTION. Never
   write a per-child dark override in here. */
body[data-redesign="v2"] .bogen {
  position: relative; isolation: isolate; overflow: hidden;
  border: 3px solid var(--bd); border-radius: 26px;
  padding: clamp(20px, 3vw, 34px);
  box-shadow: 7px 7px 0 var(--hard-page);
  color: var(--pop-cream);
  /* The curve draws in cream ON the pop ground — cobalt would vanish. */
  --wx-line: var(--pop-cream);
}
body[data-redesign="v2"] .bogen.c-ink    { background: #17150f; }
body[data-redesign="v2"] .bogen.c-cobalt { background: var(--cobalt); }
body[data-redesign="v2"] .bogen.c-violet { background: #6d28d9; }
/* flex-wrap is insurance, not decoration: neither pill shrinks, and the
   widest pair ("REGEN · NOWCAST" + "Morgen raus") lands within ~10px of
   the 375px card. One character more and it would push the card wide —
   the masthead scar. Wrapping keeps the sticker right-aligned below. */
body[data-redesign="v2"] .bg-head {
  display: flex; align-items: flex-start; flex-wrap: wrap;
  gap: 8px 10px; margin-bottom: 16px;
}
body[data-redesign="v2"] .bg-stick {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 8px;
  background: var(--yellow); color: var(--pop-ink);
  font-family: var(--inst); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.14em; text-transform: uppercase;
  border: 2px solid #17150f; border-radius: 999px; padding: 5px 13px;
  transform: rotate(-2deg); box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.28);
}
body[data-redesign="v2"] .bg-stick .live {
  width: 8px; height: 8px; border-radius: 50%; background: var(--orange);
  border: 1.5px solid #17150f; animation: v2-pulse 1.5s ease-in-out infinite;
}
/* The Abfuhr sticker: cream pill, tilted the other way than the stick so
   the two corners read as pinned paper, not as a toolbar. */
body[data-redesign="v2"] .bg-sticker {
  margin-left: auto; display: inline-flex; align-items: center; gap: 6px;
  background: var(--pop-cream); color: var(--pop-ink);
  font-family: var(--v2-mono); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.08em; text-transform: uppercase;
  border: 2px solid #17150f; border-radius: 999px; padding: 4px 10px;
  transform: rotate(1.5deg); box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.28);
  white-space: nowrap;
}
body[data-redesign="v2"] .bg-line {
  font-family: var(--bric); font-weight: 800; text-transform: lowercase;
  /* §2b: the ONE clamp whose middle really grabs on a phone (7.4vw =
     27.75px at 375) — the glide is real and wanted, so only the floor
     and cap are renamed onto the ladder. */
  font-size: clamp(var(--t08), 7.4vw, var(--t09)); line-height: 1; letter-spacing: -0.03em;
  margin: 14px 0 8px; text-wrap: balance;
}
body[data-redesign="v2"] .bg-meta {
  font-family: var(--v2-mono); font-size: var(--t02); letter-spacing: 0.14em;
  text-transform: uppercase; opacity: 0.72;
}
/* Anzieh-o-Mat chips ride the desktop text column only — at 375 the
   verdict sentence above already says it (IMPLEMENT_TAGESBOGEN §2). */
body[data-redesign="v2"] .bg-chips { display: none; }
body[data-redesign="v2"] .bg-chip {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--pop-cream); color: var(--pop-ink);
  font-family: var(--v2-mono); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.08em; text-transform: uppercase;
  border: 2px solid #17150f; border-radius: 999px; padding: 5px 12px;
  box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.28);
}
/* Graph on a pop ground: cream everywhere, yellow for the two numbers
   anyone actually reads. No paint-order halo — there is no --card behind
   the labels to stroke against.
   SVG font sizes are USER UNITS of the 800 viewBox (≈0.42× rendered at
   375px, ≈0.875× ≥900) — each is a ladder step read through that scale
   (§3): 25 E ≈ mono t02, 29 E ≈ t03, 38 E ≈ t05 mobile; 18 E ≈ t05
   desktop. The hi/lo 40/28 E and the two desktop 20 E stay off-ladder BY
   NAME: their rendered pixels (16.8 / 24.5 / 17.5) sit >0.8px from every
   step, and 40 E is coupled to ann_half in feed._GRAPH_VARIANTS — moving
   them is an optical decision, not a renaming. */
body[data-redesign="v2"] .bogen .wx-graph text { font-size: 29px; fill: rgba(253, 248, 234, 0.6); }
/* Axis ticks run one tier below the hour labels — mono t02, and 25 E is
   the largest value the ML-68 gutter admits at four glyphs: 68 − 8 −
   4 × 6.3/0.42 = 0 (feed.BREITE; pinned in test_tagesbogen). */
body[data-redesign="v2"] .bogen .wx-graph .wx-y { font-size: 25px; }
body[data-redesign="v2"] .bogen .wx-graph .wx-grid { stroke: rgba(253, 248, 234, 0.18); }
body[data-redesign="v2"] .bogen .wx-graph .wx-base { stroke: var(--pop-cream); stroke-width: 2.5; }
body[data-redesign="v2"] .bogen .wx-graph .wx-line { stroke-width: 7; }
body[data-redesign="v2"] .bogen .wx-graph .wx-ann {
  fill: var(--yellow); font-size: 40px;
}
/* fill is restated, not inherited: the blanket `.bogen .wx-graph text` rule
   above is (0,3,2) — `body` and `text` are both type selectors — and would
   otherwise outrank the (0,3,1) global .wx-chip-t rule, washing the label
   out to 60% cream on the orange chip. */
body[data-redesign="v2"] .bogen .wx-graph .wx-chip-t { fill: #fff; font-size: 25px; }
body[data-redesign="v2"] .bogen .wx-graph .wx-read { fill: var(--pop-cream); font-size: 25px; }
/* 6f — the rain lane. Marker yellow with an ink outline is the same role
   the hi/lo dots and .rowitem .t play: a marker ON data. Literal values,
   because this is a pop surface. */
body[data-redesign="v2"] .bogen .wx-graph .wx-rain {
  fill: var(--yellow); stroke: #17150f; stroke-width: 2;
}
body[data-redesign="v2"] .bogen .wx-graph .wx-rain-t {
  fill: rgba(253, 248, 234, 0.85); font-size: 29px;
}
body[data-redesign="v2"] .bogen .wx-graph .wx-cursor { stroke: rgba(253, 248, 234, 0.65); }
/* Desktop lays the Bogen down instead of stretching it: curve left,
   headline right, where the phone only had height to give. minmax(0,Xfr)
   — never a bare fr (§7.4), and the children carry min-width:0. */
@media (min-width: 900px) {
  body[data-redesign="v2"] .bogen {
    display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    gap: 38px; align-items: center;
  }
  body[data-redesign="v2"] .bogen > * { min-width: 0; }
  /* Weather upstream down (§7.7): with no curve the 7fr column holds only
     the stick, so the lay-down would leave more than half the surface
     blank. One column, headline full width. */
  body[data-redesign="v2"] .bogen.no-graph { grid-template-columns: minmax(0, 1fr); gap: 18px; }
  body[data-redesign="v2"] .bg-text { display: flex; flex-direction: column; gap: 18px; }
  body[data-redesign="v2"] .bg-line {
    /* Floor and cap sat on t10/t12 before the ladder had names; the vw
       glide between them (900→1333px) is real here, so it stays. */
    font-size: clamp(var(--t10), 3.9vw, var(--t12)); line-height: 0.96;
    letter-spacing: -0.035em; margin: 0;
  }
  body[data-redesign="v2"] .bg-chips { display: flex; flex-wrap: wrap; gap: 8px; }
  body[data-redesign="v2"] .bg-meta { line-height: 1.8; }
  /* Same user units render ~2× larger in the wider column — step the
     SVG type back down so the desktop graph reads like the phone one. */
  body[data-redesign="v2"] .bogen .wx-graph text { font-size: 20px; }
  body[data-redesign="v2"] .bogen .wx-graph .wx-y { font-size: 18px; }
  body[data-redesign="v2"] .bogen .wx-graph .wx-ann { font-size: 28px; }
  body[data-redesign="v2"] .bogen .wx-graph .wx-chip-t { font-size: 18px; }
  body[data-redesign="v2"] .bogen .wx-graph .wx-read { font-size: 18px; }
  body[data-redesign="v2"] .bogen .wx-graph .wx-rain-t { font-size: 20px; }
  body[data-redesign="v2"] .bogen .wx-graph .wx-rain { stroke-width: 1.5; }
  body[data-redesign="v2"] .bogen .wx-graph .wx-line { stroke-width: 5; }
  body[data-redesign="v2"] .bogen .wx-graph .wx-base { stroke-width: 2; }
}

/* ── Nebenbei: rank 3, the line that closes the report ────────────────
   Mobile follows the chip-rail recipe (scroll + edge fade + the 18px
   right padding) — a chip clipped hard at the viewport edge reads as
   broken. Desktop turns it into a rule: mono label, hairline, chips. */
body[data-redesign="v2"] .neben { margin-top: 4px; }
body[data-redesign="v2"] .nb-label {
  display: block; font-family: var(--v2-mono); font-size: var(--t01);
  letter-spacing: 0.22em; text-transform: uppercase; color: var(--mute);
  margin-bottom: 10px;
}
body[data-redesign="v2"] .nb-rule { display: none; }
body[data-redesign="v2"] .nb-chips {
  display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none;
  padding: 0 18px 5px 0;
  -webkit-mask-image: linear-gradient(90deg, #000 90%, transparent);
  mask-image: linear-gradient(90deg, #000 90%, transparent);
}
body[data-redesign="v2"] .nb-chips::-webkit-scrollbar { display: none; }
body[data-redesign="v2"] .nb-chip {
  display: inline-flex; align-items: center; gap: 7px; flex: none;
  font-family: var(--inst); font-weight: 600; font-size: var(--t03);
  border: 2px solid var(--bd); border-radius: 999px; padding: 7px 13px;
  background: var(--card); color: var(--ink); white-space: nowrap;
  box-shadow: 3px 3px 0 var(--hard-c);
}
@media (min-width: 900px) {
  body[data-redesign="v2"] .neben {
    display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 14px;
  }
  body[data-redesign="v2"] .nb-label { margin-bottom: 0; flex: none; }
  body[data-redesign="v2"] .nb-rule {
    display: block; flex: 1; min-width: 0; height: 1px; background: var(--rule);
  }
  body[data-redesign="v2"] .nb-chips {
    overflow: visible; flex-wrap: wrap; padding: 0;
    -webkit-mask-image: none; mask-image: none;
  }
}

/* Quiet row label (round 74): the Mitteilungen rows date themselves in
   mono without the marker-yellow pill — that pill is the price/time
   accent in Essen & Markt, and two pills per row read as a table. */
body[data-redesign="v2"] .rowitem .t.quiet {
  background: none; border: none; box-shadow: none;
  /* 70px, not 54: the gutter must HOLD its widest label or every row's
     title starts at a different x. "vor 12 Std." / "gerade eben" are 11
     glyphs × BREITE t02 (6.3px) = 69.3px (Druckbogen №7 §6.3). */
  color: var(--ink-soft); padding: 2px 0; min-width: 70px;
}
/* Pulse-card heads: the label never wraps ("WETTER · / 24H" read as two
   rows at 375px); the long corner meta truncates instead. */
body[data-redesign="v2"] .pulse-grid .w-head > span:first-child { white-space: nowrap; flex: none; }
body[data-redesign="v2"] .pulse-grid .w-head .more {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}

/* ── Pättken Piktos (round 39): inline-SVG glyphs replacing UI emojis ──
   <svg class="pi" aria-hidden="true"><use href="#pi-bus"/></svg>
   Rides currentColor + font-size, so it recolors with any chip/theme
   for free. Sprite: partials/_pictograms.html (included by _base_v2). */
body[data-redesign="v2"] .pi {
  width: 1.05em; height: 1.05em; flex: none;
  vertical-align: -0.14em; display: inline-block;
}

/* ── Discovery shuffle button (tap-to-deal the next fact) ───────────── */
body[data-redesign="v2"] .d-shuffle {
  margin-left: auto; width: 30px; height: 30px; flex: none;
  border-radius: 50%; border: 2px solid var(--pop-ink);
  background: transparent; color: var(--pop-ink);
  font-size: var(--t05); line-height: 1; cursor: pointer;
  box-shadow: 2px 2px 0 var(--pop-ink);
}

/* ── Skeleton rows + map dim (chip-tap loading feedback — the shared
   data-skel mechanism in plakat-live.js, round 49; shapes: rows,
   card-rows, strips, cards) ─────────────────────────────────────────── */
body[data-redesign="v2"] .skel { pointer-events: none; }
body[data-redesign="v2"] .skel-bar {
  display: block; height: 13px; border-radius: 6px;
  background: linear-gradient(90deg, var(--paper-2) 25%, var(--rule) 50%, var(--paper-2) 75%);
  background-size: 200% 100%;
  animation: v2-shimmer 1.1s linear infinite;
}
@keyframes v2-shimmer { to { background-position: -200% 0; } }
body[data-redesign="v2"] .map-loading { opacity: 0.45; filter: saturate(0.4); transition: opacity 0.3s; }
/* strips shape (news): a strip-shaped ghost — kicker, headline, sub. */
body[data-redesign="v2"] .strip.skel { display: flex; flex-direction: column; gap: 10px; }
/* cards shape (gastro): photo-card ghost — image block + two text bars. */
body[data-redesign="v2"] .skel-card {
  border: 2px solid var(--rule); border-radius: 16px; overflow: hidden;
  background: var(--card);
}
body[data-redesign="v2"] .skel-card .skel-img {
  height: 170px; border-bottom: 2px solid var(--rule);
  background: linear-gradient(90deg, var(--paper-2) 25%, var(--rule) 50%, var(--paper-2) 75%);
  background-size: 200% 100%;
  animation: v2-shimmer 1.1s linear infinite;
}
body[data-redesign="v2"] .skel-card .skel-pad {
  padding: 14px; display: flex; flex-direction: column; gap: 9px;
}

/* ── Market closing progress (elapsed share of today's open window) ─── */
body[data-redesign="v2"] .mkt-track {
  height: 8px; border: 1.5px solid var(--bd); border-radius: 999px;
  overflow: hidden; background: var(--paper-2); margin: 6px 0 10px;
}
body[data-redesign="v2"] .mkt-fill { height: 100%; background: var(--green); }

/* ── Parking sparkline + trend hint (24 h history) ──────────────────── */
body[data-redesign="v2"] .pk2-spark { width: 64px; height: 24px; flex: none; }
/* №14d: history, not action — the sparkline speaks in ink-mute, cobalt
   on /parking belongs to the .pille alone. */
body[data-redesign="v2"] .pk2-spark polyline { fill: none; stroke: var(--mute); stroke-width: 2.5; stroke-linejoin: round; }
body[data-redesign="v2"] .pk2-trend { font-family: var(--v2-mono); font-size: var(--t01); letter-spacing: 0.06em; white-space: nowrap; }
body[data-redesign="v2"] .pk2-trend.filling { color: var(--orange); }
body[data-redesign="v2"] .pk2-trend.emptying { color: var(--ok); }

/* ── Keyboard access (round 7): plakat-styled focus + skip link ─────── */
body[data-redesign="v2"] :focus-visible {
  outline: 3px solid var(--cobalt); outline-offset: 2px; border-radius: 6px;
}
body[data-redesign="v2"] .skip-link {
  position: fixed; left: 14px; top: -64px; z-index: 3000;
  font-family: var(--inst); font-weight: 700; font-size: var(--t03);
  background: var(--yellow); color: var(--pop-ink);
  border: 2px solid var(--bd); border-radius: 999px; padding: 10px 18px;
  box-shadow: 3px 3px 0 var(--hard-c); transition: top 0.15s;
}
body[data-redesign="v2"] .skip-link:focus { top: 14px; }

/* ── Speisekarten-Zeile (№14c, promoted 0.130.11): dish left, dotted
   leader, mono comma price right — /mensa's rows and the homepage
   lunch deck card share ONE grammar. Colors inherit: ink on paper,
   white on the green hero (where the pinned pop tokens keep the
   dotted rule legible and .veg folds into currentColor — green-on-
   green would vanish). */
body[data-redesign="v2"] .mn-row {
  display: flex; gap: 8px; align-items: baseline; padding: 8px 0;
}
body[data-redesign="v2"] .mn-row .n { min-width: 0; font-size: var(--t04); font-weight: 600; }
body[data-redesign="v2"] .mn-row .veg { color: var(--green); white-space: nowrap; }
body[data-redesign="v2"] .hero .mn-row .veg { color: inherit; }
body[data-redesign="v2"] .mn-row .veg .pi { width: 13px; height: 13px; }
body[data-redesign="v2"] .mn-row .dots {
  flex: 1; min-width: 24px; align-self: flex-end;
  border-bottom: 2px dotted var(--rule); transform: translateY(-4px);
}
body[data-redesign="v2"] .mn-row .p {
  flex: none; font-family: var(--v2-mono); font-size: var(--t03); font-weight: 500;
  font-variant-numeric: tabular-nums;
  /* On wrapped dish names, leader and price meet on the LAST line —
     classic menu typesetting, not first-baseline flex default. */
  align-self: flex-end;
}

/* ── Der Spaziergang heute (№13d) — the homepage erkunden card ───────
   One walk leads, drawn from its real OSM polyline (walks.py
   _route_path); the other three wait as one-liners with route minis.
   The route is ink on paper — cream on night in the dark, no new color;
   marker yellow and orange stay Plakat colors (theme-free). */
/* Centered, not top-pinned (0.130.7): the text column runs taller than
   the 104px drawing on phones — top-aligned, the map floated over dead
   space and read as misplaced. */
body[data-redesign="v2"] .wk-lead { display: flex; gap: 14px; align-items: center; }
body[data-redesign="v2"] .wk-map {
  flex: none; background: var(--paper); border: 2px solid var(--bd); border-radius: 12px;
}
body[data-redesign="v2"] .wk-txt { flex: 1; min-width: 0; }
body[data-redesign="v2"] .wk-zettel {
  display: inline-flex; align-items: center;
  font-family: var(--inst); font-weight: 700; font-size: var(--t01);
  letter-spacing: 0.14em; text-transform: uppercase;
  background: var(--yellow); color: var(--pop-ink);
  border: 1.5px solid var(--bd); border-radius: 6px; padding: 2px 7px;
  margin-bottom: 7px; transform: rotate(-1deg);
}
body[data-redesign="v2"] .wk-title {
  font-family: var(--bric); font-weight: 800; text-transform: lowercase;
  font-size: var(--t07); line-height: 1.02; letter-spacing: -0.02em;
  margin-bottom: 6px; overflow-wrap: anywhere;
}
body[data-redesign="v2"] .wk-title em {
  font-style: normal; background: var(--yellow); color: var(--pop-ink);
  padding: 0 0.12em; border-radius: 0.12em;
}
body[data-redesign="v2"] .wk-sub {
  font-size: var(--t03); line-height: 1.45; color: var(--ink-soft); margin: 0;
}
/* One line on 375px (0.130.10, user call): STARTEN + trimmed tracking
   and pill padding put stats and button on the SAME row inside the
   301px card budget (stats 29 mono glyphs ≈ 192px + pill ≈ 95px + 8px
   gap). flex-wrap stays as the safety net: if a stat line ever runs
   longer, the button drops below, right-aligned like the rest rows'
   arrows — never a mid-word break. */
body[data-redesign="v2"] .wk-stat { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 12px; }
body[data-redesign="v2"] .wk-nums {
  font-family: var(--v2-mono); font-size: var(--t02); letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink-soft); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
body[data-redesign="v2"] .wk-go {
  margin-left: auto; flex: none; display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--inst); font-weight: 700; font-size: var(--t02);
  letter-spacing: 0.08em; text-transform: uppercase;
  padding: 8px 12px; border: 2px solid var(--bd); border-radius: 999px;
  background: var(--yellow); color: var(--pop-ink);
  box-shadow: 3px 3px 0 var(--hard-c);
}
body[data-redesign="v2"] .wk-rest { border-top: 1.5px solid var(--rule); margin-top: 14px; }
body[data-redesign="v2"] .wk-row {
  display: flex; align-items: center; gap: 10px; padding: 9px 2px;
  border-bottom: 1.5px solid var(--rule); border-radius: 8px;
}
body[data-redesign="v2"] .wk-row:last-child { border-bottom: none; }
body[data-redesign="v2"] .wk-row:hover { background: var(--wash); }
body[data-redesign="v2"] .wk-row svg { flex: none; }
body[data-redesign="v2"] .wk-row .txt { flex: 1; min-width: 0; font-size: var(--t04); font-weight: 600; }
body[data-redesign="v2"] .wk-row .sub {
  font-family: var(--v2-mono); font-size: var(--t02); color: var(--mute);
  flex: none; font-variant-numeric: tabular-nums;
}
body[data-redesign="v2"] .wk-row .go { color: var(--mute); }
body[data-redesign="v2"] .wk-quiet { background: var(--card); color: var(--ink-soft); }
body[data-redesign="v2"] .wk-pill { align-self: flex-start; }

/* ── Sign-off sticker + minimal footer ──────────────────────────────── */
body[data-redesign="v2"] .signoff { display: flex; justify-content: center; padding: 30px 0 4px; }
body[data-redesign="v2"] .signoff span {
  font-family: var(--v2-mono); font-size: var(--t01); letter-spacing: 0.2em; text-transform: uppercase;
  background: var(--card); border: 2px solid var(--bd); border-radius: 999px;
  padding: 7px 16px; transform: rotate(1.2deg); box-shadow: 3px 3px 0 var(--hard-c);
  color: var(--ink-soft); display: inline-block;
}
body[data-redesign="v2"] .signoff b { color: var(--orange); }
body[data-redesign="v2"] .footer-v2 {
  margin-top: 34px; padding: 18px clamp(16px, 4vw, 48px) 8px;
  display: flex; justify-content: center; gap: 18px; flex-wrap: wrap;
}
body[data-redesign="v2"] .footer-v2 a {
  font-family: var(--v2-mono); font-size: var(--t01); letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--mute);
}
body[data-redesign="v2"] .footer-v2 a:hover { color: var(--ink); text-decoration: underline; }

/* ── NEU tags + first-visit lede (round 35) ─────────────────────────── */
/* Ink-on-yellow reads on both hosts: paper directory rows AND the yellow
   MIT-EXTRAS chips. Removed by plakat-live.js once the page was visited. */
body[data-redesign="v2"] .neu-tag {
  display: inline-block; margin-left: 6px; padding: 1px 6px 2px;
  font-family: var(--v2-mono); font-size: var(--t01); font-weight: 700;
  letter-spacing: 0.14em; vertical-align: super;
  background: #17150f; color: #ffd913;   /* pop pair — identical in dark */
  border-radius: 999px; transform: rotate(-3deg);
}

/* ── Contextual push offers (round 34) — inline, at the moment of
   demonstrated interest. Cobalt = interactive (works on paper cards AND
   the yellow quiz card); dismiss is quiet. Revealed by plakat-live.js
   under the nahbar_disco attention budget. ── */
body[data-redesign="v2"] .push-offer {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px;
}
body[data-redesign="v2"] .push-offer .po-yes {
  font-family: var(--inst); font-weight: 600; font-size: var(--t03);
  padding: 8px 14px; border: 2px solid var(--bd); border-radius: 999px;
  background: var(--cobalt); color: #fff;
  box-shadow: 2px 2px 0 var(--hard-c); cursor: pointer; text-align: left;
  /* Shrinkable (text wraps inside the pill) so the ✕ never orphan-wraps
     onto its own line (round 41, primer #3). */
  flex: 0 1 auto; min-width: 0;
}
body[data-redesign="v2"] .push-offer .po-x {
  font-size: var(--t02); font-weight: 700; padding: 6px 10px;
  border: 2px solid var(--bd); border-radius: 999px;
  background: var(--card); color: var(--ink-soft);
  box-shadow: 2px 2px 0 var(--hard-c); cursor: pointer; flex: none;
}
body[data-redesign="v2"] .push-offer .po-ok {
  font-family: var(--inst); font-size: var(--t03); font-weight: 500; color: var(--ink-soft);
}
body[data-redesign="v2"] .push-offer a { color: var(--cobalt); font-weight: 600; font-size: var(--t03); }
html[data-theme="dark"] body[data-redesign="v2"] .push-offer a { color: #93a5ff; }
html[data-theme="dark"] body[data-redesign="v2"] .nu-row .t a { color: #93a5ff; }

/* ── First-stamp toast (round 33) — one yellow moment, once ever.
   Fixed chrome: above Leaflet's escaping panes (>1000, round 23c rule)
   and above the dock stack (1100/1105). Pop surface = theme-independent. */
body[data-redesign="v2"] .stamp-toast {
  position: fixed; top: 14px; left: 50%; transform: translateX(-50%);
  z-index: 1110; display: flex; flex-direction: column; gap: 2px;
  background: var(--yellow); color: var(--pop-ink);
  border: 2.5px solid var(--bd); border-radius: 16px; padding: 10px 18px;
  box-shadow: 4px 4px 0 var(--hard-page); text-decoration: none;
  font-family: var(--inst); font-size: var(--t04); font-weight: 600;
  max-width: min(92vw, 380px);
  animation: stamp-toast-in 0.35s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}
body[data-redesign="v2"] .stamp-toast b {
  font-family: var(--bric); font-weight: 700; font-size: var(--t05); letter-spacing: -0.01em;
}
@keyframes stamp-toast-in {
  from { transform: translate(-50%, -18px); opacity: 0; }
}

/* ── Der Empfang (Druckbogen №16, Slate B) ───────────────────────────
   Erststart-Chrome: alles hier zahlt nur an Tag eins/zwei Miete — die
   Gates und Rentenenden leben in plakat-live.js (nahbar_disco). */

/* W1 — der Wisch-Zettel: Mini-Sticker auf der ERSTEN Deck-Karte, Tag
   eins. Nur gepinnte Pop-Tokens (§156 — nie Literale, nie per-child-
   dark-Overrides), t01 als kleinste bestehende Stufe, die eine
   rationierte Kippung. Aufdruck einmalig in der №12-Druck-Stimme —
   die bestehende v2-anschlag-Keyframe der Meldungs-Zeile, hinter
   no-preference; im Kill-Block steht der Zettel statisch. */
body[data-redesign="v2"] .wisch-zettel {
  position: absolute; right: 12px; bottom: 12px; z-index: 3;
  font-family: var(--v2-mono); font-size: var(--t01); font-weight: 700;
  letter-spacing: 0.14em;
  background: var(--card); color: var(--ink);
  border: 2px solid var(--bd); border-radius: 6px; padding: 4px 9px;
  transform: rotate(2deg);
}
@media (prefers-reduced-motion: no-preference) {
  body[data-redesign="v2"] .wisch-zettel {
    animation: v2-anschlag 0.38s cubic-bezier(0.2, 0.65, 0.3, 1);
  }
}
/* Freiraum-Pflicht (Blatt-Validator): Knöpfe laufen nie unter den
   Zettel — sie stapeln lieber als Ganzes (nowrap hält die Pille
   unteilbar, §157-Geist), statt in der Pille umzubrechen. */
body[data-redesign="v2"] .hero.has-wisch .hero-actions { padding-right: 118px; }
body[data-redesign="v2"] .hero.has-wisch .hero-actions .hbtn { white-space: nowrap; }

/* S1 — die Geister-Tonne: Mono-Fußzeile der Frage-Karte. Farben erben
   vom Pop-Grund (c-violet → cream), nichts Neues. */
body[data-redesign="v2"] .tn-foot {
  display: flex; align-items: center; gap: 12px; margin-top: 10px;
  font-family: var(--v2-mono); font-size: var(--t01); letter-spacing: 0.14em;
  opacity: 0.85;
}
body[data-redesign="v2"] .tn-foot .tn-x {
  font: inherit; letter-spacing: inherit; color: inherit;
  background: none; border: 0; padding: 0;
  text-decoration: underline; cursor: pointer;
}
/* Der gelbe Merken-Knopf — die .pille-Familie wächst um die Marker-
   Variante (Knöpfe wohnen in plakat.css, №14-Regel). */
body[data-redesign="v2"] .pille.gelb { background: var(--yellow); color: var(--pop-ink); }

/* P2 — der eine Moment trägt das .stamp-toast-Kleid (§129, Kill-Block-
   Eintrag gilt mit); .st-app macht nur Platz für das ✕. */
body[data-redesign="v2"] .stamp-toast.st-app { padding-right: 36px; }
body[data-redesign="v2"] .stamp-toast .st-x {
  position: absolute; top: 6px; right: 8px; padding: 2px 4px;
  background: none; border: 0; cursor: pointer;
  font-size: var(--t03); font-weight: 700; color: var(--ink-soft);
}

/* K2 — das Domplatz-Amt: die Sortier-Zeile im w-head ist ohne Geo ein
   Knopf (die Geo-Tür), mit Geo wieder reiner Zustandstext — der Text
   ist der Zustand, kein Ledger, keine Rente. */
body[data-redesign="v2"] .w-head button.more {
  font: inherit; letter-spacing: inherit; text-transform: inherit;
  color: var(--mute); background: none; border: 0; padding: 0;
  cursor: pointer; text-align: right;
}
body[data-redesign="v2"] .w-head .more b { color: var(--cobalt); font-weight: 700; }
html[data-theme="dark"] body[data-redesign="v2"] .w-head .more b { color: #93a5ff; }

/* ── Der breite Tisch (Druckbogen №15, Slate D) ──────────────────────
   Die Doktrin:
   1. Mehr Tisch, nicht mehr Tapete — der Wrap bleibt 1180; Breite wird
      in Nebeneinander investiert, nie in längere Zeilen.
   2. Zwei Schwellen, nicht fünf — 900 flippt das Chrome (besteht),
      1100 stellt die Werkbank auf. Dazwischen erfindet niemand Größen.
   3. Das Plakat schreit nicht lauter — t12 bleibt die Decke; Flächen
      wachsen, Schrift nicht.
   4. Die Karte ist das Fenster — auf jeder Werkbank rechts, sticky,
      viewport-hoch (finden · fragen-Fragment · unterwegs). Ein Muster,
      dreimal.
   5. Hover ist der neue Druck — .push existiert; dazu kommt genau eine
      Mechanik: Zeile ↔ Pin. Keine Tooltips, keine Schwebe-Menüs.
   Nicht-Katalog gilt weiter: keine Seitenleiste · kein drittes
   Register · kein Mega-Menü-Revival · Masthead drei Teile · Deck zwei
   Karten · Dock 740. */
@media (min-width: 1100px) {
  /* 15a/15e — die Werkbank: Register links (5fr), Karte rechts (7fr),
     sticky und viewport-hoch. Ein Muster für /finden und /unterwegs.
     Sticky trägt, weil beide Kinder EINE Grid-Zeile teilen — die Area
     der Karte ist so hoch wie die linke Spalte. (Die globale
     58-vh-Regel ≥900 bleibt global — /busstops und /map haben keine
     Werkbank und brauchen sie weiter; hier gewinnt die spezifischere
     Werkbank-Höhe.) */
  body[data-redesign="v2"] .werkbank {
    display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    gap: 18px; align-items: start;
  }
  body[data-redesign="v2"] .werkbank > * { min-width: 0; }
  body[data-redesign="v2"] .werkbank > .mapbox,
  body[data-redesign="v2"] .werkbank > .u-mapbox { order: 2; position: sticky; top: 16px; }
  body[data-redesign="v2"] .werkbank > .mapbox,
  body[data-redesign="v2"] .werkbank .u-mapbox .mapbox {
    height: calc(100vh - 96px); max-height: 720px;
  }
  body[data-redesign="v2"] .werkbank > .results { order: 1; margin-top: 0; }
  body[data-redesign="v2"] .werkbank > .u-sheet { order: 1; }

  /* 15b — /fragen: der Faden bleibt 740, nur Daten-Blasen (.fr) treten
     zentriert auf 1080 heraus. Passt ab 1100: die Blase ist mittig, der
     Überhang je Seite 170px — halbe Bühne (550) plus 540 ≤ Viewport. */
  body[data-redesign="v2"] .msg.bot .bub:has(.fr) {
    width: 1080px; max-width: none;
    margin-left: calc((740px - 1080px) / 2); box-sizing: border-box;
  }
  /* Fragmente mit Karte (Route) bekommen innen dasselbe Raster: Liste
     links, Karte rechts — CSS drumherum, das .fr-*-Markup aus
     assistant.py bleibt unangetastet. Die Karte spannt die Zeilen
     (span 42 deckt jede echte Antwort, leere Rest-Zeilen sind 0 hoch);
     ihr min-height-Überschuss verteilt sich als wenige px Luft auf die
     Etappen — gewollt, die kurze Liste darf atmen. */
  body[data-redesign="v2"] .fr:has(> .fr-map) {
    display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    column-gap: 18px;
  }
  body[data-redesign="v2"] .fr:has(> .fr-map) > * { grid-column: 1; min-width: 0; }
  body[data-redesign="v2"] .fr:has(> .fr-map) > .fr-head { grid-column: 1 / -1; }
  body[data-redesign="v2"] .fr:has(> .fr-map) > .fr-map {
    grid-column: 2; grid-row: 2 / span 42;
    height: auto; min-height: 320px;
  }

  /* 15c — Marginalien (Lese-Register /news, /damals): die Bänder wandern
     in eine 96-px-Randspalte und bleiben dort sticky stehen, wie die
     Randdaten einer Zeitungsseite. Opt-in per Klasse `marginal`,
     Zeilen-Markup unverändert. KEIN Grid: als Grid-Kind wäre der
     containing block eines sticky-Bands seine eigene, einzeilige
     Grid-Area — es stünde nie. Stattdessen: Randspalte über padding,
     Band 0 hoch im Fluss (margin-bottom gleicht die eigene Höhe aus),
     Kartenfarbe als Deckung — das jüngere Band löst das ältere beim
     Scrollen ab. */
  body[data-redesign="v2"] .marginal { padding-left: 110px; }
  body[data-redesign="v2"] .marginal > .w-head { margin-left: -110px; }
  body[data-redesign="v2"] .marginal > .rl-band,
  body[data-redesign="v2"] .marginal > .dm-month {
    position: sticky; top: 16px; z-index: 2;
    width: 96px; height: 18px; margin: 0 0 -18px -110px;
    background: var(--card);
  }
  /* Damals' Monatsköpfe sprechen in der Randspalte die Band-Stimme —
     19-px-Bricolage passt nicht in 96px. */
  body[data-redesign="v2"] .marginal > .dm-month {
    font-family: var(--inst); font-weight: 700; font-size: var(--t02);
    letter-spacing: 0.18em; text-transform: uppercase; color: var(--mute);
  }
  /* Das Band trägt keine Höhe mehr — die Gruppengrenze braucht ihre
     Trennlinie zurück (nur die allererste Zeile startet ohne). */
  body[data-redesign="v2"] .marginal > .rl-band + .rowitem { border-top: 1px solid var(--rule); }
  body[data-redesign="v2"] .marginal > .rl-band:first-child + .rowitem { border-top: none; }

  /* 15d — Zwei Spalten (Nachschlage-Register /busstops, jede Liste über
     ~40 Zeilen): multi-column, NICHT grid — innerhalb eines Bands liest
     die Spalte nach unten (Nachschlagen). Die Bänder regieren per
     column-span beide Spalten. Trennlinien drehen auf border-bottom:
     border-top doppelt sich am Spaltenkopf nicht sauber. */
  body[data-redesign="v2"] .rowlist.spalten {
    display: block; column-count: 2; column-gap: 30px;
    column-rule: 1px solid var(--rule);
  }
  body[data-redesign="v2"] .rowlist.spalten > .rl-band { column-span: all; }
  body[data-redesign="v2"] .rowlist.spalten > .rowitem {
    break-inside: avoid; border-top: none; border-bottom: 1px solid var(--rule);
  }
}

/* ── View transitions — soft crossfade between the four destinations.
   Progressive enhancement: browsers without support ignore the rule, and
   nesting it under no-preference keeps reduced-motion users opted out. ── */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  ::view-transition-old(root),
  ::view-transition-new(root) { animation-duration: 150ms; }
}

/* ── Reduced motion ─────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  body[data-redesign="v2"] .card-in { animation: none !important; }
  body[data-redesign="v2"] .stamp-toast { animation: none; }
  /* №16 (W1): der Aufdruck des Wisch-Zettels — dort steht er statisch. */
  body[data-redesign="v2"] .wisch-zettel { animation: none; }
  body[data-redesign="v2"] .skel-bar,
  body[data-redesign="v2"] .skel-card .skel-img { animation: none; }
  body[data-redesign="v2"] .push:hover,
  body[data-redesign="v2"] .push:active { transform: none; }
  body[data-redesign="v2"] .leaflet-control-zoom a:active { transform: none; }
  body[data-redesign="v2"] .disc,
  body[data-redesign="v2"] .disc:hover { transform: none; }
  body[data-redesign="v2"] .wx-stick:hover,
  body[data-redesign="v2"] .hero-stick:hover,
  body[data-redesign="v2"] .hero-stick .live,
  body[data-redesign="v2"] .bg-stick .live,
  body[data-redesign="v2"] .tab.active .ic { animation: none; }
}
