@font-face { font-family: "Console Mono"; font-weight: 400; src: url(/static/fonts/dejavumono-reg.woff2) format("woff2"); font-display: swap; }
@font-face { font-family: "Console Mono"; font-weight: 700; src: url(/static/fonts/dejavumono-bold.woff2) format("woff2"); font-display: swap; }
@font-face { font-family: "Mensa Cond"; font-weight: 300; src: url(/static/fonts/robocond-light.woff2) format("woff2"); font-display: swap; }
@font-face { font-family: "Mensa Cond"; font-weight: 400; src: url(/static/fonts/robocond-reg.woff2) format("woff2"); font-display: swap; }
@font-face { font-family: "Mensa Cond"; font-weight: 700; src: url(/static/fonts/robocond-bold.woff2) format("woff2"); font-display: swap; }
@font-face { font-family: "Mensa Body"; font-weight: 400; src: url(/static/fonts/opensans-reg.woff2) format("woff2"); font-display: swap; }
@font-face { font-family: "Mensa Body"; font-weight: 700; src: url(/static/fonts/opensans-bold.woff2) format("woff2"); font-display: swap; }
@font-face { font-family: "Report Display"; font-weight: 400; src: url(/static/fonts/gfsdidot-reg.woff2) format("woff2"); font-display: swap; }
@font-face { font-family: "Report Display"; font-weight: 700; src: url(/static/fonts/gfsdidot-bold.woff2) format("woff2"); font-display: swap; }
@font-face { font-family: "Mensa Mono"; font-weight: 400; src: url(/static/fonts/libmono-reg.woff2) format("woff2"); font-display: swap; }

:root {
  color-scheme: dark;
  --surface:   #0c0d0d;
  --panel:     #141613;
  --panel-2:   #1b1e19;
  --panel-3:   #21241e;
  --ink:       #f2f3ec;
  --ink-2:     #aeb0a4;
  /* #8f9385, not the original #6b6d64 -- that one only hit ~3.2-3.7:1 against
     these panels, below the 4.5:1 WCAG AA text minimum. This hits ~5-6:1. */
  --muted:     #8f9385;
  --line:      #2a2c26;
  --line-2:    #383a32;
  --accent:    #f2a33e;
  --accent-2:  #3ddbd9;
  --good:      #0ca30c;
  --warn:      #fab219;
  --critical:  #ec835a;
  --s1: #3987e5; --s2: #d95926; --s3: #199e70; --s4: #c98500;
  --s5: #d55181; --s6: #008300; --s7: #9085e9; --s8: #e66767;

  /* ---------- per-canteen identity hues ----------
     One fixed hue per canteenId (never assigned by rank or list position),
     so a location keeps the same color in every section it appears in.
     Both modes are the same five hues re-stepped for their own surface --
     not an automatic flip. The set was validated as a categorical palette
     against both surfaces on the *all-pairs* list (any two canteens can end
     up side by side, e.g. two chips on one dish): worst CVD dE 11.4 dark /
     10.3 light (>=8 target), worst normal-vision dE 17.1 / 16.1 (>=15
     floor), all >=3:1 against the panel. Color is still only ever a
     redundant accelerator here: every chip also carries the location name,
     so identity never rests on hue alone. Extending past five would break
     the all-pairs floors -- an unmapped canteen deliberately gets the
     neutral --c-unknown rather than an invented sixth hue. */
  --c1: #457cfd;   /* Mensa Stadtmitte */
  --c2: #879f18;   /* Mensa Lichtwiese */
  --c3: #30a4ae;   /* Mensa Schöfferstraße */
  --c4: #984b9f;   /* Mensa Dieburg */
  --c7: #c9212e;   /* Campusrestaurant Schöffers */
  --c-unknown: #8f9385;
}

/* ---------- light mode ----------
   Dark is the default look; light is opt-in, via the OS preference or the
   topbar toggle (which stamps data-theme on <html> and wins either way).
   Accent/status hues are darkened from their dark-mode values, not reused
   as-is -- the originals (tuned to pop on near-black) drop to ~1.5-2.5:1
   against a light surface, well under the 4.5:1 text minimum. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    color-scheme: light;
    --surface:   #f5f4ee;
    --panel:     #fdfdfa;
    --panel-2:   #eeece2;
    --panel-3:   #e4e1d3;
    --ink:       #1b1c17;
    --ink-2:     #4b4d43;
    --muted:     #6f7166;
    --line:      #ddd9cb;
    --line-2:    #c9c5b5;
    --accent:    #a15d00;
    --accent-2:  #0d7d7a;
    --good:      #087508;
    --warn:      #8a5a00;
    --critical:  #c1481f;
    --c1: #4366e8; --c2: #717d00; --c3: #0f89a0; --c4: #6b428b; --c7: #8f392a;
    --c-unknown: #6f7166;
  }
}
:root[data-theme="light"] {
  color-scheme: light;
  --surface:   #f5f4ee;
  --panel:     #fdfdfa;
  --panel-2:   #eeece2;
  --panel-3:   #e4e1d3;
  --ink:       #1b1c17;
  --ink-2:     #4b4d43;
  --muted:     #6f7166;
  --line:      #ddd9cb;
  --line-2:    #c9c5b5;
  --accent:    #a15d00;
  --accent-2:  #0d7d7a;
  --good:      #087508;
  --warn:      #8a5a00;
  --critical:  #c1481f;
  --c1: #4366e8; --c2: #717d00; --c3: #0f89a0; --c4: #6b428b; --c7: #8f392a;
  --c-unknown: #6f7166;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--surface);
  background-image: radial-gradient(ellipse at 15% -10%, rgba(242,163,62,0.05), transparent 55%);
  color: var(--ink);
  font-family: "Console Mono", monospace;
  font-size: 13.5px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
@media (prefers-reduced-motion: reduce) { * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; } }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
@keyframes rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }

.wrap { max-width: 1120px; margin: 0 auto; padding: 22px 18px 70px; }

/* ---------- top bar ---------- */
.topbar {
  display: flex; justify-content: space-between; align-items: center;
  padding-bottom: 12px; border-bottom: 1px solid var(--line-2); margin-bottom: 18px;
  flex-wrap: wrap; gap: 8px; font-size: 11.5px; color: var(--ink-2);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 6px; background: var(--good); box-shadow: 0 0 6px var(--good); animation: blink 2.4s ease-in-out infinite; }
.dot.stale { background: var(--warn); box-shadow: 0 0 6px var(--warn); }
.dot.dead { background: var(--critical); box-shadow: 0 0 6px var(--critical); animation: none; }
.topbar b { color: var(--ink); font-weight: 700; }
.topbar .src { color: var(--muted); }
.theme-toggle {
  font-size: 14px; line-height: 1; border: 1px solid var(--line-2); background: var(--panel); color: var(--ink-2);
  width: 24px; height: 24px; border-radius: 50%; cursor: pointer; padding: 0; margin-left: auto;
}
.theme-toggle:hover { border-color: var(--accent); color: var(--accent); }
.theme-toggle::before { content: "☾"; }
:root[data-theme="light"] .theme-toggle::before { content: "☀"; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .theme-toggle::before { content: "☀"; }
}

/* ---------- site navigation ----------
   Present on every page, rendered server-side from one list (see PAGES in
   main.py), so it can't drift between pages and needs no JavaScript to
   appear. Scrolls sideways rather than wrapping on narrow screens: a nav bar
   that reflows to three lines pushes the actual page below the fold. */
.navbar {
  display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
  border-bottom: 1px solid var(--line); margin-bottom: 20px;
}
.sitenav {
  display: flex; gap: 4px; overflow-x: auto; flex: 1; min-width: 0;
  padding-bottom: 0; scrollbar-width: thin;
}
/* The one all-time figure that stays in the chrome rather than on a page:
   how far back the archive reaches qualifies everything else on the site, so
   it rides along with the navigation on every page instead of opening the
   landing page with a wall of key figures. Links to /statistiken, where the
   rest of them now live. */
.nav-badge {
  display: inline-flex; align-items: baseline; gap: 6px; flex-shrink: 0; margin-bottom: 7px;
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap;
  color: var(--muted); text-decoration: none;
  border: 1px solid var(--line-2); border-radius: 20px; padding: 2px 11px;
}
.nav-badge b { font-size: 12.5px; color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }
.nav-badge:hover { border-color: var(--accent-2); color: var(--accent-2); }
.sitenav a {
  font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap;
  color: var(--ink-2); text-decoration: none; padding: 7px 12px 8px;
  border-bottom: 2px solid transparent; border-radius: 3px 3px 0 0;
}
.sitenav a:hover { color: var(--accent-2); background: var(--panel); }
.sitenav a.active { color: var(--accent); border-bottom-color: var(--accent); background: var(--panel); font-weight: 700; }
.more-link {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; white-space: nowrap;
  color: var(--accent-2); text-decoration: none;
}
.more-link:hover { text-decoration: underline; }

/* ---------- masthead ---------- */
.masthead { margin-bottom: 22px; }
.masthead h1 {
  font-weight: 700; text-transform: uppercase; letter-spacing: 0.02em;
  font-size: clamp(26px, 5vw, 38px); margin: 0 0 8px; text-wrap: balance;
  color: var(--accent); text-shadow: 0 0 18px rgba(242,163,62,0.25);
}
.masthead .sub { color: var(--ink-2); max-width: 76ch; font-size: 13px; }

section.block { margin: 34px 0; animation: rise 0.5s ease both; }
section.block + section.block { margin-top: 0; padding-top: 34px; border-top: 2px solid var(--line-2); }
.block-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
h2.block-title { display: flex; align-items: baseline; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; font-size: 21px; margin: 0; color: var(--ink); }
h2.block-title .n {
  font-family: "Report Display", serif; font-weight: 400; font-size: 15px;
  color: var(--surface); background: var(--accent-2); border-radius: 3px;
  padding: 2px 7px; margin-right: 10px; letter-spacing: 0;
}
p.block-note { color: var(--ink-2); font-size: 12px; margin: 4px 0 0; max-width: 72ch; }
.tag { font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; border: 1px solid var(--line-2); border-radius: 3px; padding: 2px 7px; color: var(--ink-2); white-space: nowrap; }
.tag.live { color: #0d1a05; background: var(--good); border-color: var(--good); font-weight: 700; }
.tag.archive { color: var(--accent-2); border-color: var(--accent-2); }
.tag.inferred { color: var(--warn); border-color: var(--warn); }
.tag.up { color: var(--critical); border-color: var(--critical); }
.tag.down { color: var(--accent-2); border-color: var(--accent-2); }
.tag.top, .tag.reco { color: var(--good); border-color: var(--good); }
.ref-date { font-size: 10px; color: var(--muted); }

/* ---------- flap-cell numerals ---------- */
.flap { display: inline-flex; gap: 2px; font-weight: 700; font-variant-numeric: tabular-nums; }
.flap .cell {
  background: var(--panel-2); border-radius: 3px; padding: 2px 3px; position: relative;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), inset 0 -2px 3px rgba(0,0,0,0.45);
  min-width: 0.62em; text-align: center;
}
.flap .cell::after { content: ""; position: absolute; left: 1px; right: 1px; top: 50%; height: 1px; background: rgba(0,0,0,0.5); }
.flap .cell.punct { background: transparent; box-shadow: none; min-width: auto; padding: 2px 1px; }
.flap .cell.punct::after { display: none; }

.grid-kpi { display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; }
@media (max-width: 900px) { .grid-kpi { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 560px) { .grid-kpi { grid-template-columns: repeat(2, 1fr); } }
.tile { background: var(--panel); border: 1px solid var(--line); border-radius: 4px; padding: 12px 12px 10px; }
.tile .label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-2); margin-bottom: 6px; display: block; }
.tile .flap { font-size: 21px; color: var(--ink); }
.tile .unit { font-size: 11px; color: var(--muted); margin-left: 4px; font-weight: 400; }

/* ---------- ticket rows (Aushang) ---------- */
.ticket { background: var(--panel); border: 1px solid var(--line); border-radius: 4px; padding: 14px 16px 6px; }
.ticket-row {
  display: flex; align-items: baseline; gap: 8px; padding: 6px 0;
  border-bottom: 1px dotted var(--line-2); font-family: "Mensa Mono", "Console Mono", monospace;
}
.ticket-row:last-child { border-bottom: none; }
.ticket-row .label { font-family: "Mensa Cond", sans-serif; font-weight: 400; letter-spacing: 0.03em; text-transform: uppercase; font-size: 11.5px; color: var(--ink-2); white-space: nowrap; }
.ticket-row .canteen-dot { align-self: center; }
.ticket-row .leader { flex: 1; border-bottom: 1px dotted var(--line-2); margin-bottom: 4px; min-width: 10px; }
.ticket-row .value { font-weight: 700; font-size: 13.5px; text-align: right; font-variant-numeric: tabular-nums; color: var(--ink); min-width: 0; }
.ticket-row .value .unit { font-weight: 400; color: var(--muted); font-size: 11px; margin-left: 3px; }

/* ---------- alarm / stat callout ---------- */
.alarm-tile {
  --tint: var(--critical);
  background: var(--panel); border: 1px solid var(--tint); border-radius: 4px; padding: 16px 18px;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--tint) 12%, transparent), 0 0 30px -12px color-mix(in srgb, var(--tint) 35%, transparent);
}
.alarm-tile.trend-down { --tint: var(--good); }
.alarm-head { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.alarm-tile .flap { font-size: 32px; color: var(--tint); }
.alarm-label { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--tint); margin-bottom: 4px; }
.alarm-context { font-size: 12px; color: var(--ink-2); flex: 1; min-width: 220px; }
.alarm-context b { color: var(--ink); font-weight: 700; }
.price-history { margin-top: 16px; padding-top: 14px; border-top: 1px dashed var(--line-2); }

/* ---------- canteen identity ----------
   Every place a location is named wears the same two marks: its hue and its
   name. The *dot* carries the color; the label stays in a text token, so the
   chip never leans on a series color for legibility (and never has to clear
   the 4.5:1 text bar at 10px). --chip is set inline per element from the
   canteenId, so one rule serves all five locations. */
.canteen-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--chip, var(--c-unknown)); flex-shrink: 0; vertical-align: baseline;
}
.canteen-chip {
  display: inline-flex; align-items: center; gap: 5px; max-width: 100%;
  font-family: "Console Mono", monospace; font-size: 10px; line-height: 1.5;
  text-transform: uppercase; letter-spacing: 0.03em; white-space: nowrap;
  color: var(--ink-2); border-radius: 3px; padding: 1px 6px 1px 5px;
  border: 1px solid color-mix(in srgb, var(--chip, var(--c-unknown)) 42%, transparent);
  background: color-mix(in srgb, var(--chip, var(--c-unknown)) 11%, transparent);
}
.canteen-chip .canteen-dot { width: 6px; height: 6px; }
.canteen-chips { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; }
.canteen-chips .more { font-size: 10px; color: var(--muted); white-space: nowrap; }
/* Compact variant for dense table cells: abbreviated label, and never
   wrapping -- a wrapped chip row silently triples the height of every row
   in the table, which is worse than the wide column it was meant to fix. */
.canteen-chip.compact { padding: 0 4px 0 4px; letter-spacing: 0.02em; gap: 4px; }
.canteen-chips.compact { flex-wrap: nowrap; }

/* ---------- eating type ----------
   The site's second color code, and the one most easily mistaken for the
   first: the canteen hues and the series hues were each validated as a
   palette on their own, so nothing stops a location's blue from sitting next
   to vegan's blue. Shape is therefore what separates the two systems --
   location marks are circles, eating-type marks are squares -- and never
   hue. Both always carry their written label nearby or on `title`, and any
   section that shows both draws a .code-legend naming each shape. */
.diet-mark {
  display: inline-block; width: 8px; height: 8px; border-radius: 2px;
  margin-right: 7px; flex-shrink: 0; vertical-align: baseline;
}
.diet-tag {
  display: inline-flex; align-items: center; font-size: 10px; color: var(--ink-2);
  text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap;
}
.diet-tag .diet-mark { margin-right: 5px; }

/* ---------- canteen selector / generic tab bar ---------- */
.canteen-select, .tab-select {
  display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 14px;
}
.canteen-select button, .tab-select button {
  font-family: "Console Mono", monospace; font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.03em;
  border: 1px solid var(--line-2); background: var(--panel); color: var(--ink-2);
  padding: 6px 11px; border-radius: 20px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
}
.canteen-select button.active, .tab-select button.active { border-color: var(--accent); color: var(--accent); background: var(--panel-2); }
.canteen-select button:hover, .tab-select button:hover { border-color: var(--accent-2); color: var(--accent-2); }
.canteen-select button:focus-visible, .tab-select button:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 1px; }
/* A canteen button picks up its own location's hue when selected, so the
   active state says *which* location as well as *that* one is active. Hover
   keeps the accent treatment for unselected buttons only -- otherwise
   pointing at the active button would repaint away the identity color. */
.canteen-select button.canteen-btn.active {
  border-color: var(--chip); color: var(--chip);
  background: color-mix(in srgb, var(--chip) 10%, var(--panel-2));
}
.canteen-select button.canteen-btn.active:hover { border-color: var(--chip); color: var(--chip); }

/* ---------- day switcher (browse announced days from the tray) ----------
   Deliberately compact -- a "<| Mo, 04.08. |>" strip, not a second tab bar
   next to the canteen selector -- so future-day browsing sits quietly
   beside "today" rather than competing with it. The middle control is the
   .tag already used to show today/archive state; clicking it opens the
   popup calendar below instead of adding a third visual language. */
.day-switcher { display: flex; align-items: center; gap: 10px; }
.day-arrow {
  width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--line-2);
  background: var(--panel); color: var(--ink-2); font-family: "Console Mono", monospace;
  font-size: 14px; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.day-arrow:hover:not(:disabled) { border-color: var(--accent-2); color: var(--accent-2); }
.day-arrow:disabled { opacity: 0.3; cursor: default; }
.day-current { background: none; border: none; padding: 0; cursor: pointer; }
.day-current:hover .tag, .day-current:focus-visible .tag { border-color: var(--accent); color: var(--accent); }

.day-calendar { min-width: 260px; }
.day-calendar-nav { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.day-calendar-month { font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ink); }
.day-calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.day-calendar-wd { font-size: 10px; text-align: center; color: var(--muted); text-transform: uppercase; padding-bottom: 2px; }
.day-calendar-cell {
  aspect-ratio: 1; border: 1px solid transparent; background: none; color: var(--ink-2);
  font-family: "Console Mono", monospace; font-size: 12px; border-radius: 3px;
}
/* Only days the API actually announced something for are clickable -- every
   other cell (past days, gaps beyond the forward window) stays inert rather
   than pretending to be a real navigation target. */
.day-calendar-cell.has-data { border-color: var(--line-2); color: var(--ink); cursor: pointer; }
.day-calendar-cell.has-data:hover { border-color: var(--accent-2); color: var(--accent-2); }
.day-calendar-cell.active { border-color: var(--accent); color: var(--accent); background: var(--panel-2); }
.day-calendar-cell.is-today:not(.active) { border-color: var(--accent-2); }

/* ---------- charts ---------- */
.chart-card { background: var(--panel); border: 1px solid var(--line); border-radius: 4px; padding: 14px 14px 8px; }
.chart-svg { width: 100%; height: auto; display: block; overflow: visible; }
.chart-svg text { font-family: "Console Mono", monospace; fill: var(--muted); font-size: 10px; }
/* Chart text that must read as primary/emphasized ink rather than the
   default muted axis-label color -- a real CSS-variable reference, not a
   color resolved once in JS, so it stays legible if the theme is toggled
   after the chart already rendered. */
.chart-svg text.ink { fill: var(--ink); }
.gridline { stroke: var(--line); stroke-width: 1; }
.axis-line { stroke: var(--line-2); stroke-width: 1; }
.gap-band { fill: var(--critical); opacity: 0.08; }
.gap-label { fill: var(--critical); font-size: 9.5px; letter-spacing: 0.04em; }
.legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 10.5px; color: var(--ink-2); }
.legend .item { display: flex; align-items: center; gap: 5px; }
.legend .swatch { width: 10px; height: 3px; border-radius: 1px; display: inline-block; }
.legend .swatch.dashed { background: none; border-top: 2px dashed currentColor; height: 0; }

/* Deliberately NOT theme-variable-driven: this always renders as a dark
   readout regardless of light/dark mode. It used to read var(--ink) on a
   hardcoded dark background, which meant light mode (where --ink flips to
   a near-black ink color) rendered near-invisible dark-on-dark text. */
.tooltip {
  position: fixed; pointer-events: none; background: #05100a; border: 1px solid #f2a33e;
  color: #f2f3ec; font-family: "Console Mono", monospace; font-size: 11px;
  padding: 7px 9px; border-radius: 3px; line-height: 1.5; z-index: 50;
  opacity: 0; transition: opacity 0.1s; max-width: 260px;
}
.tooltip.show { opacity: 1; }
.tooltip .t-row { display: flex; gap: 10px; justify-content: space-between; }
.tooltip .t-val { font-weight: 700; color: #f2a33e; }
.tooltip .t-key { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 5px; }

/* ---------- modal ----------
   Unlike .tooltip above, this is a primary reading surface someone looks at
   for several seconds (a history table), so it follows the site's normal
   light/dark theming rather than repeating the tooltip's fixed-dark
   exception. */
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 60;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.modal-backdrop[hidden] { display: none; }
.modal {
  position: relative; background: var(--panel); border: 1px solid var(--line-2); color: var(--ink);
  border-radius: 4px; padding: 18px 20px; max-width: 640px; width: 100%; max-height: 82vh;
  overflow-y: auto; box-shadow: 0 12px 36px rgba(0,0,0,0.45);
}
.modal:focus-visible { outline: none; }
.modal-close {
  position: absolute; top: 10px; right: 10px; width: 26px; height: 26px; line-height: 1;
  border: 1px solid var(--line-2); background: var(--panel-2); color: var(--ink-2); border-radius: 50%;
  cursor: pointer; font-size: 15px;
}
.modal-close:hover { border-color: var(--accent); color: var(--accent); }
.modal-head { padding-right: 28px; margin-bottom: 10px; }
.modal-head h3 { margin: 0 0 3px; font-size: 15px; color: var(--ink); }
.modal-meta { font-size: 11.5px; color: var(--ink-2); }
.history-table td.name-cell, .history-table th:nth-child(2) { text-align: left; }
.history-table td:nth-child(5), .history-table th:nth-child(5) { text-align: left; }
.history-table td.history-changes, .history-table th:last-child { text-align: left; }
.history-table td.history-changes { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.history-table tr.history-announced-row td {
  background: color-mix(in srgb, var(--accent-2) 8%, transparent);
}

/* Cards/rows a serving's revision history can be opened from -- same
   affordance language as any other clickable control on the site. */
.has-history { cursor: pointer; }
.has-history:hover, .has-history:focus-visible { outline: 1px dashed var(--accent-2); outline-offset: 2px; }
/* "Unread"-style marker, same glow language as the topbar's live-collection
   dot -- a small persistent cue that a dish carries revision history, not
   just something revealed on hover. Deliberately not a circle sized/colored
   like canteen-dot: the two mean unrelated things (location vs. "this has a
   history"), so they must not share the exact same look. */
.history-dot {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
  background: var(--accent-2); box-shadow: 0 0 5px var(--accent-2); margin-right: 5px; vertical-align: middle;
}

.table-toggle, .btn {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.05em; font-family: "Console Mono", monospace;
  border: 1px solid var(--line-2); background: transparent; color: var(--ink-2);
  padding: 3px 8px; border-radius: 3px; cursor: pointer;
}
.table-toggle:hover, .btn:hover { border-color: var(--accent); color: var(--accent); }
.data-table { display: none; width: 100%; border-collapse: collapse; font-size: 11.5px; margin-top: 8px; }
.data-table.show { display: table; }
.data-table th, .data-table td { text-align: right; padding: 4px 8px; border-bottom: 1px solid var(--line); font-variant-numeric: tabular-nums; }
.data-table th:first-child, .data-table td:first-child { text-align: left; }
.data-table th { color: var(--ink-2); font-weight: 400; text-transform: uppercase; font-size: 10px; letter-spacing: 0.04em; }
.table-scroll { overflow-x: auto; }

/* ---------- grids ---------- */
.grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 10px; }
.grid-5 { display: grid; grid-template-columns: repeat(5, minmax(0,1fr)); gap: 10px; }
@media (max-width: 900px) { .grid-5 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .grid-3 { grid-template-columns: 1fr; } }
@media (max-width: 460px) { .grid-5 { grid-template-columns: 1fr; } }
/* Below this the label/leader/value line has no room for a nowrap label
   beside its number; let it wrap rather than widen the page. */
@media (max-width: 460px) {
  .ticket-row { flex-wrap: wrap; }
  .ticket-row .label { white-space: normal; }
}

.station {
  background: var(--panel); border: 1px solid var(--line); border-radius: 4px; padding: 12px 14px;
  border-left: 3px solid var(--chip, var(--line-2));
  /* Grid items default to min-width:auto, i.e. they refuse to shrink below
     their content -- one long counter name would otherwise widen the whole
     page rather than wrap inside its own tile. */
  min-width: 0;
}
.station .row span:last-child { overflow-wrap: anywhere; }
.station .head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 8px; gap: 8px; }
.station .name {
  font-weight: 700; font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.03em;
  display: inline-flex; align-items: center; gap: 6px; min-width: 0;
}
.station .id { color: var(--muted); font-size: 10.5px; }
.station .row { display: flex; flex-direction: column; gap: 1px; font-size: 11.5px; padding: 4px 0; border-top: 1px dotted var(--line-2); }
.station .row:first-of-type { border-top: none; }
.station .row .k { color: var(--muted); font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; font-family: "Mensa Cond", sans-serif; }
.station .row span:last-child { font-weight: 700; font-size: 13px; }
.badge-row { display: flex; gap: 6px; margin-top: 8px; }
.badge { font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 6px; border-radius: 2px; border: 1px solid var(--line-2); color: var(--muted); }
.badge.on { color: var(--accent-2); border-color: var(--accent-2); }

/* ---------- photo gallery (Jahresbericht) ---------- */
.gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 760px) { .gallery { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .gallery { grid-template-columns: 1fr; } }
.plate { background: var(--panel); border: 1px solid var(--line); border-radius: 4px; overflow: hidden; }
.plate .frame { aspect-ratio: 4/3; overflow: hidden; background: var(--panel-2); position: relative; }
.plate img { width: 100%; height: 100%; object-fit: cover; display: block; filter: saturate(0.94) brightness(0.96); }
.plate .rank { position: absolute; top: 8px; left: 8px; font-family: "Report Display", serif; font-size: 24px; color: #fff; text-shadow: 0 1px 8px rgba(0,0,0,0.7); }
.plate .body { padding: 10px 12px 12px; }
.plate .name { font-weight: 700; font-size: 13px; line-height: 1.3; font-family: "Mensa Body", sans-serif; }
.plate .sub { font-family: "Console Mono", monospace; font-size: 10.5px; color: var(--muted); display: flex; justify-content: space-between; gap: 8px; margin-top: 5px; }
.plate .sub .rating { color: var(--accent); font-weight: 700; white-space: nowrap; }
.plate .meta { font-family: "Console Mono", monospace; font-size: 10px; color: var(--muted); margin-top: 4px; }
/* Where the dish is served sits directly under the title -- the first thing
   read after the name, not buried in the grey meta line. */
.plate .canteen-chips { margin-top: 7px; }
/* Colored edge along the top of each plate, so a gallery row answers "which
   canteens are these from" before any label is read. A dish served at
   several locations gets a hard-stop gradient across exactly those hues
   (set inline) rather than one arbitrarily-picked color. */
.plate .bar { height: 3px; background: var(--chip, var(--line-2)); }
.prose { font-family: "Mensa Body", sans-serif; font-size: 13.5px; color: var(--ink-2); line-height: 1.6; max-width: 76ch; margin-top: 14px; }
.prose b { color: var(--ink); }

/* ---------- upcoming ticker ---------- */
.upcoming-content { margin-top: 2px; }
.upcoming-row { display: grid; grid-template-columns: 84px 1fr auto auto; gap: 10px; align-items: baseline; padding: 5px 0; border-bottom: 1px dotted var(--line); font-size: 12px; }
.upcoming-row:last-child { border-bottom: none; }
.upcoming-row .canteen { color: var(--muted); font-size: 10.5px; text-transform: uppercase; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.upcoming-row .name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.upcoming-row .price { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.upcoming-row .flags { display: flex; gap: 4px; }
/* Dashboard Auffälligkeiten: date | location | dish | price | flags. Five
   columns stop fitting long before a phone's width does, so below that the
   row becomes a wrapping stack -- dish name on its own line, price and flags
   under it -- instead of pushing the whole page into a sideways scroll. */
.alert-row { grid-template-columns: 58px 84px 1fr auto auto; }
@media (max-width: 640px) {
  .alert-row { display: flex; flex-wrap: wrap; column-gap: 8px; row-gap: 2px; }
  .alert-row .name { flex: 1 1 100%; order: 2; }
  .alert-row .price { order: 3; }
  .alert-row .flags { order: 4; flex-wrap: wrap; }
}

.upcoming-row .canteen .canteen-dot { margin-right: 5px; }

/* ---------- one day's menu (Speiseplan) ----------
   Grouped by location with a labelled break between groups (in place of
   repeating the location on every row), so several locations on the same day
   stay visually distinct while the table itself stays compact. */
.menu-table { table-layout: fixed; }
.menu-table th:first-child, .menu-table td:first-child { width: 44%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-table th:nth-child(2), .menu-table td:nth-child(2) { width: 14%; }
.menu-table th:nth-child(3), .menu-table td:nth-child(3) { width: 11%; }
.menu-table th:nth-child(4), .menu-table td:nth-child(4) { width: 12%; }
.menu-table th:nth-child(5), .menu-table td:nth-child(5) { width: 19%; }
.menu-table td.dish-cell { text-align: left; }
.menu-table td.flags-cell { white-space: nowrap; overflow: hidden; }
.menu-table td.flags-cell .tag, .menu-table td.flags-cell .ref-date { margin-left: 4px; }
/* The group header names the location, so it carries the location's hue as
   a left rule + dot; the text itself stays in ink rather than being tinted,
   for the same reason chip labels are. */
.data-table tr.group-row td {
  text-align: left; color: var(--ink); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.04em; font-size: 10px; padding-top: 14px; border-bottom: 1px solid var(--line-2);
  border-left: 3px solid var(--chip, var(--line-2)); padding-left: 8px;
  background: color-mix(in srgb, var(--chip, var(--line-2)) 8%, transparent);
}
.data-table tr.group-row td .canteen-dot { margin-right: 6px; }
.data-table tr.group-row:first-child td { padding-top: 6px; }

/* ---------- the tray (landing page) ----------
   One location at a time, across the full width, rather than a row of narrow
   cards that each show a truncated menu: the landing page's job is to answer
   "what is there to eat *where I eat*", and the location tab bar above the
   panel is what makes that one question instead of five simultaneous ones.
   Only one location is on screen, so its hue appears exactly twice -- on the
   active tab and on the panel's edge -- and never next to a dish, where it
   could be read as an eating-type mark. */
.tray-panel {
  background: var(--panel); border: 1px solid var(--line); border-radius: 4px;
  border-left: 3px solid var(--chip, var(--line-2)); padding: 14px 16px 16px;
}
.tray-head {
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  flex-wrap: wrap; padding-bottom: 10px; border-bottom: 1px solid var(--line-2);
}
.tray-head .name {
  font-weight: 700; font-size: 14px; text-transform: uppercase; letter-spacing: 0.04em;
  display: inline-flex; align-items: center; gap: 7px; min-width: 0;
}
.tray-head .meta { font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.tray-cat { margin-top: 16px; }
.tray-cat-title {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em;
  color: var(--accent-2); margin: 0 0 10px; display: flex; align-items: baseline; gap: 8px;
}
.tray-cat-title .count {
  font-size: 10px; color: var(--muted); border: 1px solid var(--line-2);
  border-radius: 10px; padding: 0 6px; font-weight: 400; letter-spacing: 0.04em;
}

/* Main dishes get the photo card -- they are the decision being made. */
.dish-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 12px; }
.dish-card {
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 4px;
  overflow: hidden; display: flex; flex-direction: column; min-width: 0;
}
.dish-card .frame { aspect-ratio: 4/3; background: var(--panel-3); overflow: hidden; }
.dish-card img { width: 100%; height: 100%; object-fit: cover; display: block; filter: saturate(0.94) brightness(0.96); }
.dish-card .body { padding: 9px 11px 11px; display: flex; flex-direction: column; gap: 5px; flex: 1; }
.dish-card .dish-name {
  font-family: "Mensa Body", sans-serif; font-weight: 700; font-size: 12.5px; line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
}
.dish-card .dish-foot {
  margin-top: auto; padding-top: 4px; display: flex; justify-content: space-between;
  align-items: baseline; gap: 8px;
}

/* Sides and desserts: same information, one line each, with a thumbnail --
   a full photo card per side dish would bury the mains it is meant to
   accompany. */
.dish-list { display: flex; flex-direction: column; }
.dish-row {
  display: grid; grid-template-columns: 40px 1fr auto auto auto; gap: 10px; align-items: center;
  padding: 6px 0; border-top: 1px dotted var(--line-2); font-size: 12px;
}
.dish-row:first-child { border-top: none; }
.dish-row .thumb { width: 40px; height: 40px; border-radius: 3px; overflow: hidden; background: var(--panel-3); }
.dish-row img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dish-row .dish-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Not every dish carries a photo, and a URL that 404s must not leave the
   browser's broken-image glyph in the layout -- both land here. */
.dish-card .frame.noimg, .dish-row .thumb.noimg {
  background-image: repeating-linear-gradient(135deg, var(--panel-3) 0 6px, var(--panel-2) 6px 12px);
}
.dish-card .price, .dish-row .price { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.dish-card .rating, .dish-row .rating { color: var(--accent); font-weight: 700; font-size: 11px; white-space: nowrap; }
.dish-tags { display: flex; gap: 4px; flex-wrap: wrap; }
.dish-row .dish-tags { flex-wrap: nowrap; }
/* Below this the five columns stop fitting; the thumbnail keeps its column
   and everything else stacks beside it rather than dragging the page
   sideways. */
@media (max-width: 560px) {
  .dish-row { grid-template-columns: 40px 1fr auto; row-gap: 2px; }
  .dish-row .dish-name { grid-column: 2 / -1; white-space: normal; }
  .dish-row .dish-tags { grid-column: 2; flex-wrap: wrap; }
}

/* ---------- color-code legend ----------
   Wherever both codes are on screen, this says which shape is which -- the
   one thing a reader cannot infer from the marks themselves. */
.code-legend {
  margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--line-2);
  display: flex; flex-direction: column; gap: 7px;
}
.code-legend[hidden] { display: none; }
.legend-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.legend-key {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted);
  min-width: 150px;
}
.legend-hint { font-size: 11px; color: var(--muted); max-width: 72ch; margin: 0; }

/* ---------- record cards (dashboard) ---------- */
.record-card { padding: 12px 14px 12px; }
.record-title {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--accent-2);
  margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid var(--line);
}
.record-row { padding: 6px 0; border-bottom: 1px dotted var(--line-2); }
.record-row:last-child { border-bottom: none; }
.record-name {
  font-family: "Mensa Body", sans-serif; font-weight: 700; font-size: 12.5px; line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.record-detail { font-size: 10.5px; color: var(--muted); font-variant-numeric: tabular-nums; margin-top: 2px; }
.record-row .canteen-chips { margin-top: 5px; }

/* ---------- source notes (Daten & Quellen) ---------- */
.ticket.source-note { padding: 14px 16px 14px; }
.source-note .source-title {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--accent);
  margin-bottom: 6px;
}
.source-note p { margin: 0; font-size: 11.5px; color: var(--ink-2); line-height: 1.6; }

/* ---------- navigation cards (dashboard footer) ---------- */
.navcards { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 10px; margin-top: 12px; }
.navcard {
  background: var(--panel); border: 1px solid var(--line); border-radius: 4px; padding: 12px 14px;
  text-decoration: none; color: var(--ink-2); display: block; border-left: 3px solid var(--line-2);
}
.navcard:hover { border-color: var(--accent-2); border-left-color: var(--accent-2); }
.navcard-title {
  display: block; font-weight: 700; font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink); margin-bottom: 5px;
}
.navcard-title::after { content: " →"; color: var(--accent-2); }
.navcard-text { display: block; font-size: 11.5px; line-height: 1.55; }

/* ---------- dish database ---------- */
.filters { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 12px; }
.filters input[type="search"], .filters select, .cat-filter {
  font-family: "Console Mono", monospace; font-size: 12px; background: var(--panel); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: 3px; padding: 6px 9px;
}
.filters input[type="search"] { flex: 1; min-width: 180px; }
.filters .count { color: var(--muted); font-size: 11px; white-space: nowrap; }
.filter-actions { display: flex; gap: 8px; margin-left: auto; flex-wrap: wrap; max-width: 100%; }
/* A <select> is as wide as its longest option unless told otherwise, and one
   of these carries "Pescetarisch — inkl. vegetarisch + Fisch": without this
   it alone sets the page's minimum width. */
.filters select, .filter-actions select, .cat-filter { max-width: 100%; min-width: 0; }
.filter-picker { position: relative; }
.filter-menu {
  position: absolute; top: calc(100% + 4px); right: 0; z-index: 20; min-width: 190px;
  max-height: 320px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line-2); border-radius: 4px; padding: 8px 10px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.35); display: flex; flex-direction: column; gap: 5px;
}
.filter-menu label {
  display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--ink-2); cursor: pointer;
  white-space: nowrap;
}
.filter-menu input[type="checkbox"] { accent-color: var(--accent); }
.filter-menu .menu-hint { font-size: 10.5px; color: var(--muted); margin-bottom: 2px; white-space: normal; max-width: 200px; }
.filter-menu[hidden] { display: none; }
.table-toggle.has-active { border-color: var(--accent); color: var(--accent); }
.dish-table th[data-key] { cursor: pointer; user-select: none; }
.dish-table th.sorted::after { content: " ▾"; color: var(--accent); }
.dish-table th.sorted.asc::after { content: " ▴"; }
.dish-table td { white-space: nowrap; }
/* The flex row lives on an inner wrapper, not on the <td> itself: a table
   cell with display:flex drops out of table layout and stops stretching to
   the row's height, which leaves its bottom border floating mid-row. */
.dish-table td.name-cell { text-align: left; }
.dish-table td.name-cell .name-inner { display: flex; align-items: center; gap: 8px; }
/* Name and location are truncated to a character/chip budget in JS (full
   text on the cell's title), which is what actually bounds these two
   columns -- without it one 90-character dish name stretches the whole
   table. The max-widths are a second belt against very wide glyph runs. */
.dish-table td.name-cell span { overflow: hidden; text-overflow: ellipsis; }
.dish-table th[data-col="dish_name"], .dish-table td.name-cell { max-width: 340px; }
.dish-table td.canteen-cell { text-align: left; }
.dish-table img.thumb { width: 28px; height: 28px; object-fit: cover; border-radius: 2px; background: var(--panel-2); flex-shrink: 0; }
.dish-table .delta-up { color: var(--critical); }
.dish-table .delta-down { color: var(--accent-2); }
.dish-table tbody tr:hover { background: var(--panel-2); }

.allergen-legend { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 4px 14px; font-size: 10.5px; color: var(--ink-2); margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line-2); }
.allergen-legend b { color: var(--accent-2); }

footer { margin-top: 40px; padding-top: 14px; border-top: 1px solid var(--line-2); font-size: 10.5px; color: var(--muted); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; text-transform: uppercase; letter-spacing: 0.04em; }
a { color: var(--accent-2); }
:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }
.loading-note { color: var(--muted); font-size: 12px; padding: 30px 0; text-align: center; }
