/* ==========================================================================
   Kärntner Spieletage — components
   Requires tokens.css. Order: reset → layout → blocks → pages.
   Naming: .kst-<block>__<element> --<variant>
   ========================================================================== */

/* --------------------------------------------------------------- 1. Reset */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--kst-bg);
  color: var(--kst-text);
  font-family: var(--kst-font-text);
  font-size: var(--kst-fs-body);
  line-height: var(--kst-lh-text);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg, video { display: block; max-width: 100%; height: auto; }
/* German compounds are long and the cards are narrow: "Öffentlichkeitsarbeit"
   walked straight out through the ink border. Nothing here clips — an ink box
   with overflow: hidden would cut the drawn corner off — so the word has to
   break instead. hyphens needs lang on <html>, which head.html sets to de-AT;
   the display face stays out of it, a hyphenated hero title looks broken. */
body { overflow-wrap: break-word; }
.kst-h1, .kst-h2, .kst-h3, .kst-card__title, .kst-person__name { hyphens: auto; }
button, input, select, textarea { font: inherit; color: inherit; }

h1, h2, h3, h4, p, figure, blockquote, ul, ol, dl { margin: 0; }
ul, ol { padding: 0; list-style: none; }
code, kbd, samp { font-family: var(--kst-font-mono); }

a { color: var(--kst-link); text-decoration: none; }
a:hover { color: var(--kst-link-hover); }
.kst-prose a { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 3px; }

:focus-visible {
  outline: 3px solid var(--kst-focus);
  outline-offset: 3px;
  border-radius: 2px;
}

.kst-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.kst-skiplink {
  position: absolute; top: -100px; left: var(--kst-s-4); z-index: 999;
  padding: var(--kst-s-3) var(--kst-s-4);
  background: var(--kst-yellow); color: var(--kst-ink);
  border: var(--kst-border); font-weight: 600;
}
.kst-skiplink:focus { top: var(--kst-s-3); }

/* -------------------------------------------------------------- 2. Layout */
.kst-shell { width: 100%; max-width: var(--kst-max); margin-inline: auto; padding-inline: var(--kst-gutter); }
.kst-shell--narrow { max-width: 860px; }

.kst-section { padding-block: var(--kst-section); }
/* A section that is a jump target (wir-ueber-uns.html#vorstand) stops below
   the sticky header, which is up to ~91px high when compact. */
.kst-section[id] { scroll-margin-top: var(--kst-s-9); }
.kst-section--alt { background: var(--kst-bg-alt); }

.kst-stack { display: flex; flex-direction: column; gap: var(--kst-s-4); }
.kst-stack--wide { gap: var(--kst-s-6); }
.kst-stack--tight { gap: var(--kst-s-3); }
.kst-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--kst-s-3); }

/* Auto-wrapping grid. --columns sets the minimum column width. */
.kst-grid { display: grid; gap: var(--kst-s-5); grid-template-columns: repeat(auto-fit, minmax(min(var(--columns, 260px), 100%), 1fr)); }
.kst-grid--2 { --columns: 380px; }
.kst-grid--3 { --columns: 260px; }
.kst-grid--4 { --columns: 200px; }
/* The column width stays what it would be with the grid full, however few
   cards there are: auto-fill keeps the empty tracks that auto-fit collapses,
   so one card is one column wide instead of the whole row. With --3 that is
   four to a row at full width. For the press documents, where two or three
   cards are the normal case. */
.kst-grid--fill { grid-template-columns: repeat(auto-fill, minmax(min(var(--columns, 260px), 100%), 1fr)); }

/* minmax(0, …) instead of a bare 1fr: a grid track refuses to go below the
   min-content width of what is in it, and a photo in its frame does not fold.
   At 320px that pushed the whole column past the screen edge and the page
   scrolled sideways. The same reasoning as in the lightbox further down. */
.kst-split { display: grid; gap: var(--kst-s-6); grid-template-columns: minmax(0, 1fr); align-items: center; }
@media (min-width: 860px) { .kst-split { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); gap: var(--kst-s-7); } }
/* A split whose second half is long — a form — starts both columns at the
   top: centred, the text beside it floated halfway down the page. */
.kst-split--top { align-items: start; }

/* ------------------------------------------------------------ 3. Typography */
.kst-hero-title {
  font-family: var(--kst-font-display);
  font-size: var(--kst-fs-hero);
  line-height: var(--kst-lh-tight);
  letter-spacing: var(--kst-ls-tight);
  text-transform: uppercase;
}
.kst-h1, .kst-h2, .kst-h3 { font-family: var(--kst-font-head); font-weight: 800; line-height: var(--kst-lh-head); letter-spacing: var(--kst-ls-tight); text-wrap: balance; }
.kst-h1 { font-size: var(--kst-fs-h1); }
.kst-h2 { font-size: var(--kst-fs-h2); }
.kst-h3 { font-size: var(--kst-fs-h3); font-weight: 600; letter-spacing: 0; }
.kst-lead { font-size: var(--kst-fs-lead); line-height: 1.5; color: var(--kst-text-muted); max-width: var(--kst-max-text); text-wrap: pretty; }
.kst-overline { font-size: var(--kst-fs-label); font-weight: 600; letter-spacing: var(--kst-ls-wide); text-transform: uppercase; color: var(--kst-grey-light); }
.kst-mini { font-size: var(--kst-fs-mini); color: var(--kst-text-muted); }
.kst-prose { max-width: var(--kst-max-text); }
.kst-prose > * + * { margin-top: var(--kst-s-4); }
.kst-prose h2 { margin-top: var(--kst-s-7); font-family: var(--kst-font-head); font-size: var(--kst-fs-h2); font-weight: 800; line-height: var(--kst-lh-head); }
.kst-prose h3 { margin-top: var(--kst-s-6); font-family: var(--kst-font-head); font-size: var(--kst-fs-h3); font-weight: 600; }
/* The two rules above give a heading air ahead of it — but not the first one
   in the column. Its air is the section padding, and the 3rem on top of that
   pushed the text down past whatever sits beside it in a .kst-split. */
.kst-prose > :first-child { margin-top: 0; }
.kst-prose ul, .kst-accordion__body ul { display: flex; flex-direction: column; gap: var(--kst-s-2); }
.kst-prose ul li, .kst-accordion__body ul li { position: relative; padding-left: var(--kst-s-5); }
.kst-prose ul li::before, .kst-accordion__body ul li::before { content: ""; position: absolute; left: 0; top: 0.68em; width: 9px; height: 9px; background: var(--kst-red); border-radius: var(--kst-corner-a); }

/* Highlighter stroke behind a line of text. Section headings alternate
   yellow and red down the page, starting with yellow. */
.kst-marker { background: linear-gradient(180deg, transparent 56%, var(--kst-yellow) 56%, var(--kst-yellow) 94%, transparent 94%); }
.kst-marker--red { background: linear-gradient(180deg, transparent 56%, var(--kst-red) 56%, var(--kst-red) 94%, transparent 94%); }

/* ------------------------------------------------------------- 4. Buttons */
.kst-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--kst-s-2);
  min-height: 48px; padding: var(--kst-s-3) var(--kst-s-5);
  font-family: var(--kst-font-text); font-size: var(--kst-fs-small); font-weight: 600;
  background: var(--kst-yellow); color: var(--kst-ink);
  border: var(--kst-border); border-radius: var(--kst-corner-a);
  cursor: pointer; text-align: center;
  transition: transform var(--kst-time) var(--kst-ease-snap), box-shadow var(--kst-time) var(--kst-ease), background-color var(--kst-time-fast) linear;
}
.kst-btn:hover { color: var(--kst-ink); background: var(--kst-yellow-deep); transform: translate(-2px, -2px); box-shadow: 4px 4px 0 var(--kst-ink); }
.kst-btn:active { transform: translate(1px, 1px); box-shadow: none; }
.kst-btn--red { background: var(--kst-red); color: var(--kst-paper); border-radius: var(--kst-corner-b); }
.kst-btn--red:hover { background: var(--kst-red-deep); color: var(--kst-paper); }
.kst-btn--ghost { background: transparent; border-radius: var(--kst-corner-c); }
.kst-btn--ghost:hover { background: var(--kst-yellow-light); }
.kst-btn--ink { background: var(--kst-ink); color: var(--kst-paper); }
.kst-btn--ink:hover { background: var(--kst-ink-deep); color: var(--kst-yellow); }
.kst-btn--large { min-height: 58px; font-size: var(--kst-fs-body); padding-inline: var(--kst-s-6); }
.kst-btn[disabled] { opacity: 0.45; cursor: not-allowed; transform: none; box-shadow: none; }

/* ---------------------------------------------------------- 4a. Form field */
/* One text field, drawn the ink way: the same 3 px frame and skewed corner
   as a button — but never tilted, a field that leans is unreadable to type
   into (CLAUDE.md). The focus ring is the off-brand blue every control
   shares; nothing here touches it. */
.kst-field {
  display: block; width: 100%; min-height: 48px; padding: var(--kst-s-3) var(--kst-s-4);
  font-family: var(--kst-font-text); font-size: var(--kst-fs-body); color: var(--kst-ink);
  background: var(--kst-paper); border: var(--kst-border); border-radius: var(--kst-corner-b);
}
.kst-field::placeholder { color: var(--kst-grey-light); }

/* The password form of a protected gallery: a label, the field, the button
   and one line for what the server said (a live region, see main.js). Kept
   to a reading width — it is one question, not a page. */
.kst-unlock { display: flex; flex-direction: column; align-items: flex-start; gap: var(--kst-s-3); max-width: 28rem; }
.kst-unlock label { font-weight: 600; }
.kst-unlock .kst-field { align-self: stretch; }
.kst-unlock__note { min-height: 1.5em; margin: 0; font-size: var(--kst-fs-small); color: var(--kst-red-deep); }

/* A registration form (the Gratis-Anreise on anreise.html): labels above
   their fields, two short fields side by side where there is room, the
   portal's refusal under the field it names, and one live line for what
   concerns the whole form. The hidden field is for bots only — off-screen
   rather than display:none, which some of them skip. */
.kst-form { display: flex; flex-direction: column; gap: var(--kst-s-4); }
/* Side by side only where both labels fit on one line: a label that wraps
   pushes its field below its neighbour's. The postcode is narrow by nature
   and keeps its town beside it. */
.kst-form__row { display: grid; gap: var(--kst-s-4); grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); }
.kst-form__row--zip { grid-template-columns: 7rem minmax(0, 1fr); }
.kst-form__field { display: flex; flex-direction: column; gap: var(--kst-s-2); min-width: 0; }
.kst-form__field > label { font-weight: 600; }
.kst-form__check { display: flex; gap: var(--kst-s-3); align-items: flex-start; font-weight: 400; }
.kst-form__check input { flex: none; width: 1.25rem; height: 1.25rem; margin-top: 0.15em; accent-color: var(--kst-red); }
.kst-form__error { margin: 0; font-size: var(--kst-fs-small); color: var(--kst-red-deep); }
.kst-form__note { min-height: 1.5em; margin: 0; font-size: var(--kst-fs-small); color: var(--kst-red-deep); }
.kst-form__trap { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.kst-form .kst-btn { align-self: flex-start; }
.kst-field[aria-invalid="true"] { border-color: var(--kst-red-deep); }
.kst-field--short { max-width: 12rem; }

/* Text link with an arrow that travels on hover. */
.kst-arrow { display: inline-flex; align-items: center; gap: var(--kst-s-2); font-weight: 600; font-size: var(--kst-fs-small); }
.kst-arrow::after { content: "→"; transition: transform var(--kst-time) var(--kst-ease); }
.kst-arrow:hover::after { transform: translateX(5px); }

/* --------------------------------------------------------------- 5. Cards */
.kst-card {
  display: flex; flex-direction: column; gap: var(--kst-s-3);
  padding: var(--kst-s-5);
  background: var(--kst-paper);
  border: var(--kst-border);
  border-radius: var(--kst-corner-a);
  transition: transform var(--kst-time) var(--kst-ease), box-shadow var(--kst-time) var(--kst-ease);
}
/* Every second and third card tilts the other way — never all alike. */
.kst-card:nth-child(3n+1) { transform: rotate(var(--kst-tilt-a)); }
.kst-card:nth-child(3n+2) { transform: rotate(var(--kst-tilt-b)); border-radius: var(--kst-corner-b); }
.kst-card:nth-child(3n+3) { transform: rotate(calc(var(--kst-tilt-a) * 0.5)); border-radius: var(--kst-corner-c); }
a.kst-card:hover { transform: rotate(0deg) translate(-3px, -3px); box-shadow: var(--kst-shadow); color: var(--kst-text); }
.kst-card__mark { display: flex; gap: var(--kst-s-1); flex: none; }
.kst-card__number { font-family: var(--kst-font-head); font-weight: 800; font-size: var(--kst-fs-number); line-height: 1; }
.kst-card__title { font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-h3); line-height: 1.2; }
.kst-card__text { font-size: var(--kst-fs-small); color: var(--kst-text-muted); }
/* A bundle ("Kombikarte", README §4, /event/bundles.html): a card with its
   price as the figure, the day-ticket price it replaces struck through under
   it, and the saving as a flag pinned to the top right corner — the hero
   flag's red, caps and tilt, so a discount looks like the house's own
   stamp and not a shop's. */
.kst-bundles { display: flex; flex-direction: column; gap: var(--kst-s-5); }
.kst-bundle { position: relative; }
.kst-bundle__badge {
  position: absolute; top: calc(-1 * var(--kst-s-4)); right: var(--kst-s-4);
  padding: var(--kst-s-2) var(--kst-s-4);
  background: var(--kst-red); color: var(--kst-paper);
  font-family: var(--kst-font-head); font-weight: 800; font-size: var(--kst-fs-figure);
  line-height: 1; white-space: nowrap; font-variant-numeric: tabular-nums;
  border: var(--kst-border-fine); border-radius: var(--kst-corner-b);
  box-shadow: var(--kst-shadow);
  transform: rotate(calc(var(--kst-tilt-c) * -2.5));
}
.kst-bundle__was { font-size: var(--kst-fs-small); color: var(--kst-text-muted); }
.kst-bundle__was s { text-decoration-thickness: 2px; text-decoration-color: var(--kst-red); }
/* The title must not run under the flag. */
.kst-bundle:has(.kst-bundle__badge) .kst-card__title { padding-right: 6rem; }

/* ----------------------------------------------------------- 5b. The die
   The mark on a card is a die, and it shows the card's number: the first
   card a one, the second a two. From seven on there are two dice whose
   faces add up — 7 = 4+3, 8 = 4+4, 9 = 5+4, 10 = 5+5, 11 = 6+5, 12 = 6+6.
   More than twelve is not provided for.

   The face is written into the markup, the colour comes from the card's
   position in its grid — the same rhythm that already decides corner and
   tilt. The pips are hard-stop gradients, not a drawing: no file to load,
   nothing to keep in sync. */
.kst-dice {
  --kst-pip: var(--kst-ink);
  --kst-pip-tl: radial-gradient(circle 3.5px at 26% 26%, var(--kst-pip) 94%, transparent 96%);
  --kst-pip-tr: radial-gradient(circle 3.5px at 74% 26%, var(--kst-pip) 94%, transparent 96%);
  --kst-pip-ml: radial-gradient(circle 3.5px at 26% 50%, var(--kst-pip) 94%, transparent 96%);
  --kst-pip-mr: radial-gradient(circle 3.5px at 74% 50%, var(--kst-pip) 94%, transparent 96%);
  --kst-pip-bl: radial-gradient(circle 3.5px at 26% 74%, var(--kst-pip) 94%, transparent 96%);
  --kst-pip-br: radial-gradient(circle 3.5px at 74% 74%, var(--kst-pip) 94%, transparent 96%);
  --kst-pip-c:  radial-gradient(circle 3.5px at 50% 50%, var(--kst-pip) 94%, transparent 96%);
  width: 34px; height: 34px; flex: none;
  border: var(--kst-border-fine); border-radius: var(--kst-corner-a);
  background-color: var(--kst-yellow); background-repeat: no-repeat;
}
.kst-dice[data-kst-face="1"] { background-image: var(--kst-pip-c); }
.kst-dice[data-kst-face="2"] { background-image: var(--kst-pip-tl), var(--kst-pip-br); }
.kst-dice[data-kst-face="3"] { background-image: var(--kst-pip-tl), var(--kst-pip-c), var(--kst-pip-br); }
.kst-dice[data-kst-face="4"] { background-image: var(--kst-pip-tl), var(--kst-pip-tr), var(--kst-pip-bl), var(--kst-pip-br); }
.kst-dice[data-kst-face="5"] { background-image: var(--kst-pip-tl), var(--kst-pip-tr), var(--kst-pip-c), var(--kst-pip-bl), var(--kst-pip-br); }
.kst-dice[data-kst-face="6"] { background-image: var(--kst-pip-tl), var(--kst-pip-tr), var(--kst-pip-ml), var(--kst-pip-mr), var(--kst-pip-bl), var(--kst-pip-br); }

/* Yellow, red, ink — in the same three-step rhythm as the corners above, so
   nobody has to count colours by hand. */
.kst-card:nth-child(3n+2) .kst-dice, .kst-dice--red { background-color: var(--kst-red); border-radius: var(--kst-corner-b); --kst-pip: var(--kst-paper); }
.kst-card:nth-child(3n+3) .kst-dice, .kst-dice--ink { background-color: var(--kst-ink); --kst-pip: var(--kst-paper); }

/* Second die of a pair: other corner, thrown a touch askew. Last, so it also
   applies inside a card that has just set its own corner. */
.kst-dice + .kst-dice, .kst-card .kst-dice + .kst-dice { border-radius: var(--kst-corner-c); transform: rotate(var(--kst-tilt-b)); }

/* Card with a cover photo: image flush at the top, text in its own box below. */
.kst-card--image { padding: 0; gap: 0; overflow: hidden; }
.kst-card__image { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-bottom: var(--kst-border); }
.kst-card__body { display: flex; flex-direction: column; gap: var(--kst-s-3); padding: var(--kst-s-5); }
/* A press document's card (README §4, press.json). Its preview is usually a
   PDF's first page, which is portrait: the 3:2 crop keeps the top of the
   page, where the heading is. Without a preview the same crop holds the die
   and the file's format instead — no drawing, the card's own pieces. */
.kst-card__image--page { object-position: top; }
.kst-card__doc {
  display: flex; align-items: center; justify-content: center; gap: var(--kst-s-4);
  aspect-ratio: 3 / 2; border-bottom: var(--kst-border);
  background: var(--kst-yellow-light);
  font-family: var(--kst-font-head); font-weight: 800; font-size: var(--kst-fs-number); line-height: 1;
  color: var(--kst-ink);
}
/* While the photo is missing the placeholder keeps the same crop. */
.kst-placeholder.kst-card__image { min-height: 0; border-width: 0 0 var(--kst-stroke) 0; border-style: solid; border-color: var(--kst-ink); }

/* Photo in an ink frame, slightly tilted. */
.kst-frame { border: var(--kst-border); background: var(--kst-paper); padding: 6px; transform: rotate(var(--kst-tilt-b)); }
.kst-frame > img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.kst-frame--left { transform: rotate(var(--kst-tilt-a)); }
/* The caption sits inside the frame, not beside it. */
.kst-frame__caption { padding: var(--kst-s-3) var(--kst-s-2) var(--kst-s-1); font-size: var(--kst-fs-mini); color: var(--kst-text-muted); text-wrap: pretty; }

/* ------------------------------------------------- 5c. Visiting card
   One person per card: portrait on the left, name, role, a few lines and
   the contact on the right. Built like .kst-card — same border, the same
   three-step rhythm of corner and tilt, straightens itself on hover —
   so a row of people sits next to a row of cards without a seam. */
.kst-people {
  display: grid; gap: var(--kst-s-5);
  grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr));
}
.kst-person {
  display: grid; gap: var(--kst-s-4);
  padding: var(--kst-s-5);
  background: var(--kst-paper);
  border: var(--kst-border);
  border-radius: var(--kst-corner-a);
  transition: transform var(--kst-time) var(--kst-ease), box-shadow var(--kst-time) var(--kst-ease);
}
.kst-person:nth-child(3n+1) { transform: rotate(var(--kst-tilt-a)); }
.kst-person:nth-child(3n+2) { transform: rotate(var(--kst-tilt-b)); border-radius: var(--kst-corner-b); }
.kst-person:nth-child(3n+3) { transform: rotate(calc(var(--kst-tilt-a) * 0.5)); border-radius: var(--kst-corner-c); }
.kst-person:hover { transform: rotate(0deg) translate(-3px, -3px); box-shadow: var(--kst-shadow); }
/* Narrow: portrait above the text. From 520px on it moves to the side. */
@media (min-width: 520px) {
  .kst-person { grid-template-columns: 150px 1fr; align-items: start; }
}
.kst-person__image {
  width: 100%; aspect-ratio: 4 / 5; object-fit: cover;
  border: var(--kst-border-fine);
}
/* While the portrait is missing the placeholder keeps the same crop. It must
   also give up the full-width span that .kst-placeholder claims by default —
   here it is one column of the card, not the whole card. */
.kst-placeholder.kst-person__image { grid-column: auto; min-height: 0; padding: var(--kst-s-4); }
/* Click the portrait, the lightbox opens. The button is only the hit area:
   frame, crop and the card's tilt stay on the image, so a card with a photo
   and one with the placeholder sit side by side without a seam. */
.kst-person__zoom { display: block; padding: 0; border: 0; background: none; cursor: zoom-in; }
.kst-person__zoom img { display: block; }
.kst-person__body { display: flex; flex-direction: column; gap: var(--kst-s-2); }
.kst-person__name { font-family: var(--kst-font-head); font-weight: 700; font-size: var(--kst-fs-h3); line-height: 1.2; }
.kst-person__text { font-size: var(--kst-fs-small); color: var(--kst-text-muted); text-wrap: pretty; }
.kst-person__contact { display: flex; flex-direction: column; gap: var(--kst-s-1); margin-top: var(--kst-s-1); font-size: var(--kst-fs-small); }

/* ----------------------------------------------------------------- 6. Nav */
/* Header "3b": the logo hangs tilted over the lower edge into the hero, next
   to it the wordmark in the club logo's lettering ("Kärntner" over
   "Spieletage", Lobster), on the right the yellow date stamp. The wordmark
   stands right next to the logo; from 1280px the nav runs centred in its
   own row underneath. --wordmark-align: center puts the wordmark back on
   the centre of the shell (between logo and burger below 1280px).
   main.js sets data-compact="true" while scrolled or while the mobile panel
   is open: the logo, stamp and wordmark get smaller.
   Never give the header overflow: hidden — it would cut off the logo. */
.kst-header {
  --wordmark-align: start;  /* start: next to the logo; center: centre of the shell */
  --logo: 86px;          /* edge length of the logo */
  --logo-top: 10px;
  --logo-scale: 0.65;    /* compact: ~56px */
  --stamp-h: 104px;      /* height of the stamp in the bar, from 1280px */
  --stamp-scale: 0.58;   /* compact, from 1280px: ~60px, days ~30px */
  position: sticky; top: 0; z-index: var(--kst-z-nav);
  background: var(--kst-paper);
  border-bottom: var(--kst-border);
}
@media (min-width: 640px)  { .kst-header { --logo: 116px; --logo-scale: 0.48; } }
/* From 1280px the compact bar is two rows high (wordmark over nav). The
   compact logo fills that height like the stamp does, instead of sitting
   small at the top of the first row: ~80px, 12px clear above and below. */
@media (min-width: 1280px) { .kst-header { --logo: 150px; --logo-top: 12px; --logo-scale: 0.53; } }

/* Two classes: has to beat .kst-shell { padding-inline } regardless of order. */
.kst-header .kst-header__bar {
  position: relative;
  display: grid; align-items: center;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "brand burger";
  column-gap: var(--kst-s-4);
  min-height: 84px;
  padding-left: calc(var(--kst-gutter) + var(--logo) + var(--kst-s-4));
}
@media (min-width: 640px) { .kst-header .kst-header__bar { min-height: 104px; } }
/* From 1280px three equal-sided columns and equal padding on both sides, so
   the middle one is the true centre of the shell. The logo needs no
   padding of its own here: it is absolute, and the first column it hangs
   over stays empty. The stamp spans both rows on the right; the nav gets
   the whole second row and centres in it. */
@media (min-width: 1280px) {
  .kst-header .kst-header__bar {
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    grid-template-rows: auto auto;
    grid-template-areas: none;
    row-gap: 0;
    column-gap: var(--kst-s-5);
    min-height: 128px;
    padding-block: var(--kst-s-1) 0;
    --bar-pad: var(--kst-gutter);
    padding-inline: var(--bar-pad);
  }
}

/* Logo and wordmark: one link to the start page. */
.kst-header__brand { grid-area: brand; display: flex; align-items: center; justify-self: var(--wordmark-align); min-width: 0; color: var(--kst-ink); }
.kst-header__brand:hover { color: var(--kst-ink); }
/* From 1280px the brand spans all three columns of row 1, so the same
   justify-self places it next to the logo or on the true centre (the stamp
   overlaps the third column, but neither placement reaches it). Next to
   the logo it clears the logo's (scaled) width, measured from the bar's
   edge — the bar's padding (--bar-pad) already counts towards that. */
@media (min-width: 1280px) {
  .kst-header__brand {
    grid-area: auto; grid-row: 1; grid-column: 1 / -1;
    --logo-clear: max(0px, calc(var(--kst-gutter) + var(--logo) * var(--logo-shown, 1) + var(--kst-s-5) - var(--bar-pad)));
    margin-inline: var(--logo-clear);  /* both sides: keeps the centre centred */
  }
}

.kst-header__logo {
  position: absolute; z-index: 1;
  left: var(--kst-gutter); top: var(--logo-top);
  width: var(--logo); height: var(--logo);
  object-fit: contain;
  background: var(--kst-paper);
  border: var(--kst-border);
  border-radius: var(--kst-corner-a);
  box-shadow: var(--kst-shadow);
  transform: rotate(var(--kst-tilt-c));
  transform-origin: top left;
  transition: transform var(--kst-time-long) var(--kst-ease), box-shadow var(--kst-time) var(--kst-ease);
}
.kst-header__brand:hover .kst-header__logo { transform: rotate(0deg) translate(-2px, -2px); }

/* The wordmark is a logo: the club's lettering (--kst-font-wordmark, nowhere
   else), fixed sizes per breakpoint (--kst-fs-wordmark-*, not fluid), rising
   as a whole and with letters tilted beyond 1.5deg — that is its shape, not
   a tilt of text. "Kärntner" in ink, centred over "Spieletage" in red with
   its depth falling to the lower left, as in the logo. */
.kst-wordmark {
  display: flex; flex-direction: column; align-items: center;
  min-width: 0;
  font-family: var(--kst-font-wordmark); font-weight: 400; line-height: 1;
  transform: rotate(-3deg);
}
.kst-wordmark__region,
.kst-wordmark__name { display: inline-flex; white-space: nowrap; }

/* "Kärntner" is bolder in the logo than Lobster regular: a thin stroke
   keeps it readable at 14–18px too (t ≠ l). Its line is shorter than the
   font's, so it sits close on "Spieletage" without a negative margin. */
.kst-wordmark__region {
  gap: 0.05em;
  font-size: var(--kst-fs-wordmark-region-sm); line-height: 0.82;
  color: var(--kst-ink);
  -webkit-text-stroke: 0.03em currentColor;
}
@media (max-width: 379.98px) { .kst-wordmark__region { font-size: var(--kst-fs-wordmark-region-xs); } }
@media (min-width: 640px)  { .kst-wordmark__region { font-size: var(--kst-fs-wordmark-region-md); } }
@media (min-width: 1280px) { .kst-wordmark__region { font-size: var(--kst-fs-wordmark-region-lg); } }

.kst-wordmark__name {
  gap: 0.035em;
  font-size: var(--kst-fs-wordmark-sm); line-height: 0.9;
  color: var(--kst-red);
  /* Room for the depth, which falls outside the glyphs to the lower left. */
  padding: 0 0 0.08em 0.08em;
}
@media (max-width: 379.98px) { .kst-wordmark__name { font-size: var(--kst-fs-wordmark-xs); } }
@media (min-width: 640px)  { .kst-wordmark__name { font-size: var(--kst-fs-wordmark-md); } }
@media (min-width: 1280px) { .kst-wordmark__name { font-size: var(--kst-fs-wordmark-lg); } }

/* Every letter its own tilt (--r) and offset (--y), like scattered game
   pieces. On hover they all tip the other way; --kst-hover-flip is 1 under
   reduced motion, so there nothing moves. */
.kst-wordmark__region > span,
.kst-wordmark__name > span {
  display: inline-block; position: relative;
  transform: rotate(var(--r)) translateY(var(--y));
  transition: transform var(--kst-time) var(--kst-ease-snap);
}
/* The depth: three steps of red sunk into ink, then an ink edge. Later
   letters lie UNDER earlier ones (z-index falling), so no letter's depth
   paints over its left neighbour. */
.kst-wordmark__name > span {
  text-shadow:
    -0.016em 0.016em 0 var(--kst-wordmark-depth), -0.032em 0.032em 0 var(--kst-wordmark-depth),
    -0.048em 0.048em 0 var(--kst-wordmark-depth), -0.064em 0.064em 0 var(--kst-ink);
}
.kst-wordmark__region > span:nth-child(1) { --r: -4deg; --y:  .02em; }
.kst-wordmark__region > span:nth-child(2) { --r:  3deg; --y: -.04em; }
.kst-wordmark__region > span:nth-child(3) { --r: -2deg; --y:  .03em; }
.kst-wordmark__region > span:nth-child(4) { --r:  3deg; --y: -.02em; }
.kst-wordmark__region > span:nth-child(5) { --r: -3deg; --y: -.04em; }
.kst-wordmark__region > span:nth-child(6) { --r:  2deg; --y:  .03em; }
.kst-wordmark__region > span:nth-child(7) { --r: -3deg; --y: -.02em; }
.kst-wordmark__region > span:nth-child(8) { --r:  4deg; --y:  .02em; }
.kst-wordmark__name > span:nth-child(1)  { --r: -3deg; --y:  .01em; z-index: 10; }
.kst-wordmark__name > span:nth-child(2)  { --r:  3deg; --y: -.03em; z-index: 9; }
.kst-wordmark__name > span:nth-child(3)  { --r: -2deg; --y:  .02em; z-index: 8; }
.kst-wordmark__name > span:nth-child(4)  { --r:  3deg; --y: -.01em; z-index: 7; }
.kst-wordmark__name > span:nth-child(5)  { --r: -3deg; --y: -.03em; z-index: 6; }
.kst-wordmark__name > span:nth-child(6)  { --r:  2deg; --y:  .02em; z-index: 5; }
.kst-wordmark__name > span:nth-child(7)  { --r: -3deg; --y: -.02em; z-index: 4; }
.kst-wordmark__name > span:nth-child(8)  { --r:  3deg; --y:  .01em; z-index: 3; }
.kst-wordmark__name > span:nth-child(9)  { --r: -2deg; --y: -.02em; z-index: 2; }
.kst-wordmark__name > span:nth-child(10) { --r:  4deg; --y:  .01em; z-index: 1; }
.kst-header__brand:hover .kst-wordmark span > span,
.kst-wordmark:hover span > span {
  transform: rotate(calc(var(--r) * var(--kst-hover-flip))) translateY(calc(var(--y) * var(--kst-hover-flip)));
}

/* Date stamp: in the header from 1280px, below that it hangs top right into
   the hero. Both lines come from the portal (/event/dates-days.html,
   /event/dates-month.html). Fixed sizes like the wordmark next to it
   (--kst-fs-stamp-*). */
.kst-header__stamp {
  position: absolute; z-index: 1;
  right: var(--kst-gutter); top: calc(100% + var(--kst-s-5));
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.18em;
  padding: var(--kst-s-2) var(--kst-s-4);
  background: var(--kst-yellow); color: var(--kst-ink);
  border: var(--kst-border);
  border-radius: var(--kst-corner-b);
  box-shadow: var(--kst-shadow);
  transform: rotate(calc(var(--kst-tilt-c) * -1));
  line-height: 1; text-align: center; white-space: nowrap;
  pointer-events: none;
}
.kst-header__stamp-days  { font-family: var(--kst-font-head); font-weight: 800; font-size: var(--kst-fs-stamp-days-sm); letter-spacing: var(--kst-ls-tight); }
.kst-header__stamp-month { font-size: var(--kst-fs-stamp-month-sm); font-weight: 600; letter-spacing: var(--kst-ls-caps); text-transform: uppercase; }
@media (min-width: 640px) {
  .kst-header__stamp { padding: var(--kst-s-3) var(--kst-s-5); }
  .kst-header__stamp-days  { font-size: var(--kst-fs-stamp-days-md); }
  .kst-header__stamp-month { font-size: var(--kst-fs-stamp-month-md); }
}
@media (min-width: 1280px) {
  .kst-header__stamp {
    position: static; grid-row: 1 / 3; grid-column: 3; justify-self: end;
    height: var(--stamp-h); pointer-events: auto;
    transform-origin: top right;
    transition: transform var(--kst-time-long) var(--kst-ease);
  }
  .kst-header__stamp-days  { font-size: var(--kst-fs-stamp-days-lg); }
  .kst-header__stamp-month { font-size: var(--kst-fs-stamp-month-lg); }
}

/* Compact: scrolled, or the mobile panel is open. Logo, wordmark and stamp
   get smaller; the stamp stays. From 1280px it shrinks in place like the
   logo, by transform, so it animates. Below 1280px it leaves the hero and
   moves into the bar between wordmark and burger — the open mobile panel
   starts right under the header, a hanging stamp would cover its first
   entries. */
.kst-header[data-compact="true"] .kst-header__logo { transform: rotate(0deg) scale(var(--logo-scale)); box-shadow: none; }
.kst-header[data-compact="true"] .kst-header__bar {
  grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: "brand stamp burger";
  min-height: 72px; padding-left: calc(var(--kst-gutter) + 56px + var(--kst-s-4));
}
.kst-header[data-compact="true"] .kst-wordmark__region { font-size: var(--kst-fs-wordmark-region-compact); }
.kst-header[data-compact="true"] .kst-wordmark__name { font-size: var(--kst-fs-wordmark-compact); }
@media (max-width: 1279.98px) {
  .kst-header[data-compact="true"] .kst-header__stamp { position: static; grid-area: stamp; gap: 0; padding: var(--kst-s-1) var(--kst-s-2); box-shadow: none; }
  .kst-header[data-compact="true"] .kst-header__stamp-days  { font-size: var(--kst-fs-small); }
  .kst-header[data-compact="true"] .kst-header__stamp-month { font-size: var(--kst-fs-label); letter-spacing: 0.04em; }
}
/* A phone has room for logo, stamp and burger, and only just for the
   wordmark next to them: smaller type, tighter gaps, the month wraps under
   itself ("November" over "2026" — the stamp is as wide as its widest
   word), and below 375px the wordmark steps aside — the logo stays, and so
   does the link's .kst-sr text.
   The stamp column is min-content, not auto: iOS Safari sized an auto track
   by the stamp's max-content width (the month on one line), left the column
   ~37px wider than the wrapped stamp and pushed the wordmark under it. */
@media (max-width: 639.98px) {
  .kst-header[data-compact="true"] .kst-header__bar {
    grid-template-columns: minmax(0, 1fr) min-content auto;
    column-gap: var(--kst-s-3); padding-left: calc(var(--kst-gutter) + 56px + var(--kst-s-3));
  }
  .kst-header[data-compact="true"] .kst-wordmark__name { font-size: var(--kst-fs-wordmark-compact-sm); }
  .kst-header[data-compact="true"] .kst-header__stamp { width: min-content; }
  .kst-header[data-compact="true"] .kst-header__stamp-month { white-space: normal; }
}
/* Below 375px the press dog-ear leaves no room for it even unscrolled, so
   there the wordmark steps aside in both states. */
@media (max-width: 374.98px) {
  .kst-header .kst-wordmark { display: none; }
}
@media (min-width: 1280px) {
  .kst-header[data-compact="true"] .kst-header__bar {
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); grid-template-areas: none;
    min-height: 0; padding-block: var(--kst-s-2) 0; padding-left: var(--kst-gutter);
  }
  .kst-header[data-compact="true"] { --logo-shown: var(--logo-scale); }
  .kst-header[data-compact="true"] .kst-wordmark__region { font-size: var(--kst-fs-wordmark-region-compact-lg); }
  .kst-header[data-compact="true"] .kst-wordmark__name { font-size: var(--kst-fs-wordmark-compact-lg); }
  /* Scaled from the top right corner; the negative margin takes the space
     the scale gives up out of the grid rows too, so the bar can shrink and
     the smaller stamp stays centred in it. */
  .kst-header[data-compact="true"] .kst-header__stamp {
    transform: rotate(0deg) scale(var(--stamp-scale));
    margin-bottom: calc(var(--stamp-h) * (var(--stamp-scale) - 1));
  }
}

/* Main navigation, from 1280px: the whole second row, centred. */
.kst-nav { display: none; grid-area: nav; }
@media (min-width: 1280px) {
  .kst-nav { display: flex; align-items: center; justify-content: center; grid-area: auto; grid-row: 2; grid-column: 1 / -1; }
  .kst-nav__list { display: flex; align-items: center; }
}
.kst-nav__item { position: relative; }
.kst-nav__link {
  display: inline-flex; align-items: center; gap: var(--kst-s-2);
  padding: var(--kst-s-2); min-height: 44px;
  font-size: var(--kst-fs-small); font-weight: 500; color: var(--kst-ink); white-space: nowrap;
  background: none; border: 0; cursor: pointer;
  border-bottom: var(--kst-stroke) solid transparent;
  transition: border-color var(--kst-time-fast) linear, color var(--kst-time-fast) linear;
}
.kst-nav__link:hover { color: var(--kst-red-deep); border-bottom-color: var(--kst-yellow); }
.kst-nav__link[aria-current="page"],
/* A page inside a dropdown marks its parent too — otherwise "Team" would be
   the only entry that never shows where you are. */
.kst-nav__item[data-kst-current="true"] > .kst-nav__link { color: var(--kst-ink); border-bottom-color: var(--kst-red); font-weight: 600; }

.kst-nav__arrow { width: 9px; height: 9px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(45deg) translate(-2px, -2px); transition: transform var(--kst-time) var(--kst-ease); }
.kst-nav__link[aria-expanded="true"] .kst-nav__arrow { transform: rotate(-135deg) translate(-3px, -3px); }

/* One reveal, four places: desktop dropdown, mobile panel, its subgroups and
   the accordion. Everything opens the same way — the panel animates from
   grid-template-rows 0fr to 1fr while an inner box clips the overflow. Both
   halves are listed here once; a component only adds what is special about
   it. Whoever adds a fifth one adds it to these two selector lists, not to a
   fifth copy of the same three declarations. */
.kst-dropdown,
.kst-mobilenav,
.kst-mobilenav__sub,
.kst-accordion__panel {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows var(--kst-time-long) var(--kst-ease);
}
.kst-dropdown__box,
.kst-mobilenav__box,
.kst-mobilenav__subbox,
.kst-accordion__box { overflow: hidden; }

/* Desktop dropdown: floats, and fades as well as unrolls. */
.kst-dropdown {
  position: absolute; top: calc(100% + 10px); left: 0; z-index: var(--kst-z-nav);
  min-width: 288px;
  opacity: 0; visibility: hidden; transform: translateY(-8px);
  transition: grid-template-rows var(--kst-time-long) var(--kst-ease),
              opacity var(--kst-time) var(--kst-ease),
              transform var(--kst-time) var(--kst-ease),
              visibility 0s linear var(--kst-time-long);
}
.kst-nav__item[data-open="true"] .kst-dropdown {
  grid-template-rows: 1fr; opacity: 1; visibility: visible; transform: translateY(0);
  transition-delay: 0s;
}
.kst-dropdown__box { background: var(--kst-paper); border: var(--kst-border); border-radius: var(--kst-corner-a); box-shadow: var(--kst-shadow-soft); }
.kst-dropdown__inner { padding: var(--kst-s-2); }
/* Same language as the bar above: a stroke underneath, no coloured area.
   Yellow while hovered, red for the page you are on. */
.kst-dropdown__link {
  display: block; padding: var(--kst-s-3);
  font-size: var(--kst-fs-small); color: var(--kst-ink);
  border-bottom: var(--kst-stroke) solid transparent;
  transition: border-color var(--kst-time-fast) linear, color var(--kst-time-fast) linear;
}
.kst-dropdown__link:hover { color: var(--kst-red-deep); border-bottom-color: var(--kst-yellow); }
.kst-dropdown__link[aria-current="page"] { color: var(--kst-ink); border-bottom-color: var(--kst-red); font-weight: 600; }
.kst-dropdown__link span { display: block; font-size: var(--kst-fs-mini); color: var(--kst-grey-light); }

/* Burger, below 1280px. The bars carry their own width: Safari wraps a
   button's content in an anonymous box that does not always stretch in a
   column flexbox, and stretched-only bars then end up 0px wide — an empty
   yellow square on some iPhones. */
.kst-burger {
  grid-area: burger;
  display: inline-flex; flex-direction: column; justify-content: center; align-items: center; gap: 5px;
  width: 52px; height: 52px; padding: 0;
  background: var(--kst-yellow); border: var(--kst-border); border-radius: var(--kst-corner-a); cursor: pointer;
}
@media (min-width: 1280px) { .kst-burger { display: none; } }
.kst-burger__bar { display: block; width: var(--kst-s-5); height: var(--kst-stroke); background: var(--kst-ink); transition: transform var(--kst-time) var(--kst-ease), opacity var(--kst-time-fast) linear; }
.kst-burger[aria-expanded="true"] .kst-burger__bar:nth-child(1) { transform: translateY(8px) rotate(45deg); }
.kst-burger[aria-expanded="true"] .kst-burger__bar:nth-child(2) { opacity: 0; }
.kst-burger[aria-expanded="true"] .kst-burger__bar:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

/* Mobile panel */
.kst-mobilenav {
  border-top: 0 solid var(--kst-ink);
  background: var(--kst-paper);
}
.kst-mobilenav[data-open="true"] { grid-template-rows: 1fr; border-top-width: var(--kst-stroke); }
/* A closed panel or group is only clipped to zero height, its links would
   still take Tab and be read out. visibility: hidden takes them out of both;
   the delay keeps them visible until the fold has finished. Open means
   inherit, not visible: a group left open inside a closed panel stays hidden. */
.kst-mobilenav__box,
.kst-mobilenav__subbox {
  visibility: hidden;
  transition: visibility 0s linear var(--kst-time-long);
}
.kst-mobilenav[data-open="true"] > .kst-mobilenav__box,
.kst-mobilenav__group[data-open="true"] .kst-mobilenav__subbox {
  visibility: inherit;
  transition-delay: 0s;
}
.kst-mobilenav__inner { padding: var(--kst-s-4) var(--kst-gutter) var(--kst-s-6); display: flex; flex-direction: column; gap: 2px; max-height: 72vh; overflow-y: auto; }
@media (min-width: 1280px) { .kst-mobilenav { display: none; } }
.kst-mobilenav__link { display: flex; align-items: center; justify-content: space-between; gap: var(--kst-s-3); padding: var(--kst-s-4) var(--kst-s-2); min-height: 52px; font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-body); color: var(--kst-ink); border-bottom: var(--kst-hairline); background: none; border-left: 0; border-right: 0; border-top: 0; width: 100%; text-align: left; cursor: pointer; }
.kst-mobilenav__link[aria-current="page"],
.kst-mobilenav__group[data-kst-current="true"] > .kst-mobilenav__link { color: var(--kst-red-deep); }
.kst-mobilenav__group[data-open="true"] .kst-mobilenav__sub { grid-template-rows: 1fr; }
.kst-mobilenav__sublink { display: block; padding: var(--kst-s-3) var(--kst-s-2) var(--kst-s-3) var(--kst-s-5); min-height: 48px; font-size: var(--kst-fs-small); color: var(--kst-grey); border-bottom: var(--kst-hairline); }
.kst-mobilenav__sublink[aria-current="page"] { color: var(--kst-red-deep); font-weight: 600; }

/* ---------------------------------------------------- 6b. Dog-ear (press) */
/* A folded newspaper corner top right in the header, leading to the press
   page. Closed: a yellow corner reading "Presse", over it the folded-back
   flap showing the print on the back of the page. On hover, focus or tap it
   peels open and a headline underneath leads to medien.html.

   How it moves: both triangles live in one square, __sheet, the size of the
   OPEN corner. Closed, the sheet is scaled down towards the top right
   corner; opening scales it to 1. Only transform changes, so nothing is
   repainted while it moves. The page's shading scales along (it is set in
   per cent); the print on the flap's back must not, so __print and
   __binding undo the scale. All three read the same --kst-dogear-scale,
   which is why it is an animated @property and not a plain transition on
   each element — two separate transitions would drift apart mid-way.
   The nudge every nine seconds is a separate wrapper, __lift, with a plain
   transform animation: that one runs on the compositor and stays smooth
   while the page is busy (video, countdown). */
@property --kst-dogear-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 0.2;
}

/* The sizes live on the header, so its bar can read them for its own
   clearance. --dogear-open is the edge of the whole square; the closed
   corner and the nudge are fractions of it (closed / open), because a scale
   is a plain number. */
.kst-header {
  --dogear-open: 272px;
  --dogear-closed: 0.1912;   /* 52px */
  --dogear-nudge: 0;         /* how far it lifts to be noticed */
}
/* Only a mouse gets the nudge; the token itself is 0 under reduced motion. */
@media (hover: hover) { .kst-header { --dogear-nudge: calc(0.0441 * var(--kst-dogear-nudge)); } }  /* 12px */
/* From 640px as large as on the desktop: below 348px the longest headline
   and its subline do not fit the triangle. */
@media (min-width: 640px) {
  .kst-header { --dogear-open: 348px; --dogear-closed: 0.2069; }  /* 72px */
  .kst-header[data-compact="true"] { --dogear-closed: 0.1494; }  /* 52px */
}
@media (min-width: 640px) and (hover: hover) {
  .kst-header { --dogear-nudge: calc(0.0345 * var(--kst-dogear-nudge)); }  /* 12px */
}

.kst-dogear {
  --kst-dogear-scale: var(--dogear-closed);
  --scale: var(--kst-dogear-scale);
  /* The nudge as a scale of the closed corner: (closed + 12px) / closed */
  --dogear-lift: calc(1 + var(--dogear-nudge) / var(--dogear-closed));
  position: absolute; top: 0; right: 0; z-index: 3;
  display: block;
  width: var(--dogear-open); height: var(--dogear-open);
  color: var(--kst-ink);
  contain: layout paint;          /* nothing reaches past the square */
  pointer-events: none;           /* closed, only the two triangles take the pointer */
  transform-origin: top right;
  transition: --kst-dogear-scale var(--kst-time-long) var(--kst-ease),
              transform var(--kst-time) var(--kst-ease);
}
.kst-dogear:hover { color: var(--kst-ink); }
/* The press page does not point at itself. */
.kst-dogear[data-kst-here="medien"] { display: none; }

/* Open: hover only on real pointers, touch opens through main.js. While
   hovered the whole square holds the pointer — otherwise the corner folds
   under a resting mouse, the triangle slides back under it, and it opens
   and closes in turn. Open, page and flap fill the square anyway. */
@media (hover: hover) {
  .kst-dogear:hover { --kst-dogear-scale: 1; --dogear-lift: 1; pointer-events: auto; }
}
.kst-dogear:focus-visible,
.kst-dogear[data-open="true"] { --kst-dogear-scale: 1; --dogear-lift: 1; outline: none; }

/* Every nine seconds the corner lifts for a moment: a slow breath up, a
   short hold, the same way down — each leg eased in and out, so it never
   starts or stops with a jolt. With a lift of 1 (touch, reduced motion,
   open) the animation runs, but nothing moves. */
@keyframes kst-dogear-nudge {
  0%, 84%   { transform: none; animation-timing-function: cubic-bezier(0.37, 0, 0.63, 1); }
  91%, 93%  { transform: scale(var(--dogear-lift)); animation-timing-function: cubic-bezier(0.37, 0, 0.63, 1); }
  100%      { transform: none; }
}
.kst-dogear__lift {
  position: absolute; inset: 0;
  display: block;
  transform-origin: top right;
}
@media (hover: hover) {
  .kst-dogear__lift { animation: kst-dogear-nudge 9s linear 3s infinite; }
}

/* Clearance in the header bar: burger and stamp stay left of the whole
   closed square including the nudge. Below 1280px the shell is full width,
   the bar ends at the viewport edge. */
.kst-header:has(.kst-dogear:not([data-kst-here="medien"])) .kst-header__bar {
  padding-right: calc(var(--dogear-open) * (var(--dogear-closed) + var(--dogear-nudge)) + var(--kst-s-3));
}
/* From 1280px the shell is centred and its outer margin counts too. 100vw
   includes the scrollbar, hence the extra reserve. Both sides get it, so
   the wordmark in the middle column stays on the centre of the shell. */
@media (min-width: 1280px) {
  .kst-header:has(.kst-dogear:not([data-kst-here="medien"])) .kst-header__bar {
    --bar-pad: max(var(--kst-gutter), calc(var(--dogear-open) * (var(--dogear-closed) + var(--dogear-nudge)) + var(--kst-s-4) - (100vw - var(--kst-max)) / 2));
    padding-inline: var(--bar-pad);
  }
}
/* Below 1280px, once the header turns compact (scrolled, or the mobile menu
   open), the stamp moves into the bar next to the burger and there is no
   room left: the corner folds away into the corner and the bar gets its
   right edge back. At the top of the page it comes back. visibility waits
   for the fold, so it is seen going, and keeps it out of tab and touch. */
@media (max-width: 1279.98px) {
  .kst-header[data-compact="true"] .kst-dogear {
    transform: scale(0);
    visibility: hidden;
    transition: --kst-dogear-scale var(--kst-time-long) var(--kst-ease),
                transform var(--kst-time) var(--kst-ease),
                visibility 0s linear var(--kst-time);
  }
  .kst-header[data-compact="true"]:has(.kst-dogear) .kst-header__bar { padding-right: var(--kst-gutter); }
}

/* --- The sheet: page and flap, scaled from the top right corner */
.kst-dogear__sheet {
  position: absolute; inset: 0;
  display: block;
  transform: scale(var(--scale));
  transform-origin: top right;
}
.kst-dogear__page,
.kst-dogear__edge,
.kst-dogear__flap { position: absolute; inset: 0; display: block; }

/* The uncovered page (upper right triangle), with the flap's shade towards
   the fold */
.kst-dogear__page {
  pointer-events: auto;
  clip-path: polygon(0 0, 100% 0, 100% 100%);
  background:
    linear-gradient(to bottom left, transparent 0 29.7%, rgb(var(--kst-ink-rgb) / 0.18) 50%),
    var(--kst-yellow);
}

/* The folded-back flap (lower left triangle). Its ink edge is a copy of the
   triangle behind it, moved by one stroke — a hard offset, no blur. The move
   is divided by the scale so it stays one stroke wide. */
.kst-dogear__edge {
  clip-path: polygon(0 0, 0 100%, 100% 100%);
  background: var(--kst-ink);
  transform: translate(calc(var(--kst-stroke) / var(--scale) * -1), calc(var(--kst-stroke) / var(--scale)));
}
.kst-dogear__flap {
  pointer-events: auto;
  overflow: hidden;
  clip-path: polygon(0 0, 0 100%, 100% 100%);
  background: var(--kst-paper-2);
}
/* The back of a newspaper page: a made-up report about the Spieletage. It
   reads like any newspaper, line by line from the top down; the lines break
   along the fold like the "Extrablatt" opposite (a float with
   shape-outside), so every word lies on the visible flap. It opens with
   the end of an earlier story, so the headline below lands where the flap
   is wide enough for it. At full size, not scaled: it stays put under the
   fold, as on real paper.
   Decoration only: aria-hidden with the sheet, data-nosnippet for search. */
.kst-dogear__print {
  position: absolute; top: 0; right: 0;
  display: block;
  width: 100%; height: 100%;
  overflow: hidden;
  padding: var(--kst-s-3) var(--kst-s-3) var(--kst-s-3) var(--kst-s-6);
  transform: scale(calc(1 / var(--scale)));
  transform-origin: top right;
  color: rgb(var(--kst-ink-rgb) / 0.82);
  font-size: var(--kst-fs-dogear-print); line-height: 1.3;
  text-align: left; hyphens: auto;
  /* Print lines in the top right corner only: that is all the closed
     corner shows, and no text ever reaches it. */
  background: repeating-linear-gradient(180deg, transparent 0 5px, rgb(var(--kst-ink-rgb) / 0.22) 5px 7px)
              top right / 40% 40% no-repeat;
}
.kst-dogear__print::before {
  content: ""; float: right;
  width: 100%; height: 100%;
  shape-outside: polygon(0 0, 100% 0, 100% 100%);
  shape-margin: var(--kst-s-4);
}
.kst-dogear__print-masthead,
.kst-dogear__print-title,
.kst-dogear__print-text { display: block; }
/* The name runs along the flap's bottom edge, the widest part: there it is
   always whole and away from the fold. Over the text, on paper, like the
   foot of a page; the fold cuts it off on the right. */
.kst-dogear__print-masthead {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: var(--kst-s-1) var(--kst-s-3) var(--kst-s-2) var(--kst-s-6);
  background: var(--kst-paper-2);
  border-top: var(--kst-stroke-thick) double var(--kst-ink);
  color: var(--kst-ink);
  font-family: var(--kst-font-head); font-weight: 800;
  font-size: var(--kst-fs-dogear-print-masthead); line-height: 1.1; letter-spacing: var(--kst-ls-caps);
  text-transform: uppercase; white-space: nowrap;
}
/* The text runs out into the paper just above the rule, so no half-cut
   line peeks over it, wherever the lines happen to fall. */
.kst-dogear__print-masthead::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: calc(100% + var(--kst-stroke-thick));
  height: var(--kst-s-6);
  background: linear-gradient(to bottom, rgb(var(--kst-paper-2-rgb) / 0), var(--kst-paper-2) 70%);
}
.kst-dogear__print-title {
  margin-block-start: var(--kst-s-2);
  hyphens: manual;
  color: var(--kst-ink);
  font-family: var(--kst-font-head); font-weight: 800;
  font-size: var(--kst-fs-dogear-print-title); line-height: 1.1;
}
.kst-dogear__print-text { margin-block-start: var(--kst-s-1); }
/* The small flap has less room below the headline: a shorter opening
   brings the headline and the first lines of the story up. */
@media (max-width: 639.98px) { .kst-dogear__print-more { display: none; } }
/* A thin shadow along the flap's straight edge */
.kst-dogear__binding {
  position: absolute; top: 0; left: 0;
  width: 20px; height: 100%;
  transform: scale(calc(1 / var(--scale)));
  transform-origin: top left;
  background: linear-gradient(90deg, transparent 8px, rgb(var(--kst-ink-rgb) / 0.3) 8px 12px, transparent 12px);
}
/* The light on the fold, over the print: it scales with the flap. Kept
   faint and narrow, so the print under it stays readable. */
.kst-dogear__flap::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to bottom left,
    rgb(var(--kst-paper-rgb) / 0) 50%,
    rgb(var(--kst-paper-rgb) / 0.55) 51.5%,
    rgb(var(--kst-paper-rgb) / 0) 56%,
    rgb(var(--kst-ink-rgb) / 0) 80%,
    rgb(var(--kst-ink-rgb) / 0.07) 100%);
}

/* The label while closed, parallel to the fold. It does not scale. */
.kst-dogear__tab {
  position: absolute;
  top: calc(var(--dogear-open) * var(--dogear-closed) * 0.36);
  right: calc(var(--dogear-open) * var(--dogear-closed) * 0.36);
  transform: translate(50%, -50%) rotate(45deg);
  font-family: var(--kst-font-head); font-weight: 800;
  font-size: var(--kst-fs-dogear-tab-sm); letter-spacing: 0.06em; text-transform: uppercase;
  white-space: nowrap;
  transition: opacity var(--kst-time-fast) linear;
}
@media (min-width: 640px) { .kst-dogear__tab { font-size: var(--kst-fs-dogear-tab); letter-spacing: var(--kst-ls-wide); } }

/* Content: the text runs along the fold through shape-outside. It lies over
   the sheet, not in it, so it is never scaled. */
.kst-dogear__content {
  position: absolute; inset: 0;
  display: block;
  padding: var(--kst-s-3) var(--kst-s-3) 0 0;
  text-align: right;
  opacity: 0;
  transition: opacity var(--kst-time-fast) linear;
}
.kst-dogear__content::before {
  content: ""; float: left;
  width: 100%; height: 100%;
  shape-outside: polygon(0 0, 100% 100%, 0 100%);
  shape-margin: var(--kst-s-4);
}
@media (min-width: 640px) { .kst-dogear__content { padding: var(--kst-s-4) var(--kst-s-4) 0 0; } }

/* As wide as its word, not as the line: a full-width border would run on
   under the float and across the fold onto the flap. */
.kst-dogear__kicker {
  display: block; width: fit-content; margin-left: auto;
  margin-bottom: var(--kst-s-2); padding-bottom: var(--kst-s-1);
  border-bottom: var(--kst-stroke-thick) double var(--kst-ink);
  font-family: var(--kst-font-head); font-weight: 800;
  font-size: var(--kst-fs-dogear-kicker); letter-spacing: 0.22em; text-transform: uppercase;
}
.kst-dogear__headline {
  display: block;
  font-family: var(--kst-font-head); font-weight: 800;
  font-size: var(--kst-fs-dogear-headline-sm); line-height: 1.1; letter-spacing: -0.01em;
  text-wrap: pretty;
}
.kst-dogear__subline {
  display: none;
  margin-top: var(--kst-s-2);
  font-size: var(--kst-fs-mini); line-height: 1.35; color: var(--kst-ink-80);
}
.kst-dogear__link {
  display: block;
  margin-top: var(--kst-s-1);
  font-size: var(--kst-fs-mini); font-weight: 600; line-height: 1.3;
  text-decoration: underline; text-decoration-thickness: var(--kst-stroke-fine); text-underline-offset: 3px;
  /* One word and its arrow, bound by &nbsp;. The triangle narrows towards
     the bottom, and from 1280px the subline stands above it: a second line
     ("Zu den / Medieninfos") no longer fit beside the fold and dropped below
     the box, out of sight. break-word stays as the safety net. */
}
@media (min-width: 640px) {
  .kst-dogear__headline { font-size: var(--kst-fs-dogear-headline-md); }
  .kst-dogear__link { font-size: var(--kst-fs-dogear-link); }
}
/* The subline only from 1280px; below that the triangle has no room. */
@media (min-width: 1280px) {
  .kst-dogear__headline { font-size: var(--kst-fs-dogear-headline-lg); }
  .kst-dogear__subline { display: block; }
}
.kst-dogear:focus-visible .kst-dogear__link { outline: 3px solid var(--kst-focus); outline-offset: 3px; }

/* The headline roll. nginx writes $request_id into data-kst-roll: 32 random
   hex digits per request. The last one picks: 0–4 the 1st headline, 5–9 the
   2nd, a–f the 3rd. Without a roll (no SSI) the 1st stays. A new headline
   means spreading the 16 endings anew. */
.kst-dogear__headline:not(:nth-child(1 of .kst-dogear__headline)) { display: none; }
.kst-dogear:is([data-kst-roll$="5"], [data-kst-roll$="6"], [data-kst-roll$="7"], [data-kst-roll$="8"], [data-kst-roll$="9"])
  .kst-dogear__headline:nth-child(1 of .kst-dogear__headline) { display: none; }
.kst-dogear:is([data-kst-roll$="5"], [data-kst-roll$="6"], [data-kst-roll$="7"], [data-kst-roll$="8"], [data-kst-roll$="9"])
  .kst-dogear__headline:nth-child(2 of .kst-dogear__headline) { display: block; }
.kst-dogear:is([data-kst-roll$="a"], [data-kst-roll$="b"], [data-kst-roll$="c"], [data-kst-roll$="d"], [data-kst-roll$="e"], [data-kst-roll$="f"])
  .kst-dogear__headline:nth-child(1 of .kst-dogear__headline) { display: none; }
.kst-dogear:is([data-kst-roll$="a"], [data-kst-roll$="b"], [data-kst-roll$="c"], [data-kst-roll$="d"], [data-kst-roll$="e"], [data-kst-roll$="f"])
  .kst-dogear__headline:nth-child(3 of .kst-dogear__headline) { display: block; }

/* Open: label gone at once; the text only once the page is nearly open,
   so it never shows before the yellow behind it */
@media (hover: hover) {
  .kst-dogear:hover .kst-dogear__tab     { opacity: 0; }
  .kst-dogear:hover .kst-dogear__content { opacity: 1; transition-delay: var(--kst-time); }
}
.kst-dogear:focus-visible .kst-dogear__tab,
.kst-dogear[data-open="true"] .kst-dogear__tab { opacity: 0; }
.kst-dogear:focus-visible .kst-dogear__content,
.kst-dogear[data-open="true"] .kst-dogear__content { opacity: 1; transition-delay: var(--kst-time); }

/* ---------------------------------------------------------------- 7. Hero */
.kst-hero { position: relative; overflow: hidden; background: var(--kst-ink); min-height: clamp(460px, 74vh, 760px); display: flex; align-items: flex-end; }
.kst-hero__media { position: absolute; inset: 0; }
/* Poster and video sit exactly on top of each other, the video fades in over
   the poster. BOTH must be positioned for that: an absolutely positioned
   element is always painted above a statically positioned one, regardless of
   source order. Without position on the video it kept playing invisibly
   behind the poster — you only ever saw the first instant. */
.kst-hero__media img,
.kst-hero__media video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.kst-hero__media video { opacity: 0; transition: opacity var(--kst-time-long) linear; }
.kst-hero__media video[data-ready="true"] { opacity: 1; }
/* Veil: at least 55 % coverage where the text sits — see CLAUDE.md.
   The gradient may be lighter at the top, no text stands there. */
.kst-hero__veil { position: absolute; inset: 0; background: linear-gradient(180deg, rgb(var(--kst-ink-rgb) / 0.42) 0%, rgb(var(--kst-ink-rgb) / 0.48) 30%, rgb(var(--kst-ink-rgb) / 0.66) 62%, rgb(var(--kst-ink-rgb) / 0.90) 100%); }
/* An extra local darkener right behind the text block. Without it legibility
   depends on whatever the poster happens to show. The ellipse is centred and
   sized closest-side so it fades to transparent exactly at the box edge —
   an off-centre farthest-corner ellipse still had coverage at the near edges
   and drew a visible dark rectangle over the video. */
.kst-hero__content::before { content: ""; position: absolute; inset: -20% 10% -20% -40%; background: radial-gradient(closest-side, rgb(var(--kst-scrim-rgb) / 0.72) 0%, rgb(var(--kst-scrim-rgb) / 0.42) 55%, transparent 100%); pointer-events: none; }
.kst-hero__content > * { position: relative; }
/* Top padding: clear of the logo and of the date stamp, which hangs into the
   hero below 1280px (offset s-5, the stamp, s-4 air). It only counts once the
   text fills the hero: on a small phone or a phone held sideways. */
.kst-hero__content { position: relative; width: 100%; padding-block: var(--kst-s-9) var(--kst-s-6); color: var(--kst-paper); display: flex; flex-direction: column; gap: var(--kst-s-4); }
@media (max-width: 639.98px) { .kst-hero__content { padding-top: var(--kst-s-10); } }
@media (min-width: 640px) and (max-width: 1279.98px) { .kst-hero__content { padding-top: calc(var(--kst-s-10) + var(--kst-s-2)); } }
.kst-hero__flag { align-self: flex-start; padding: var(--kst-s-2) var(--kst-s-4); background: var(--kst-red); color: var(--kst-paper); font-size: var(--kst-fs-mini); font-weight: 600; letter-spacing: var(--kst-ls-caps); text-transform: uppercase; transform: rotate(var(--kst-tilt-c)); }
/* Below 1280px the header stamp hangs right above it with the same date: the
   date flag steps aside. The "open now" flag during the event stays. */
@media (max-width: 1279.98px) { .kst-hero__flag--date { display: none; } }
.kst-hero__title { color: var(--kst-paper); }
/* Yellow on a photo is delicate: a brighter tone plus its own text shadow. */
.kst-hero__title em { font-style: normal; color: var(--kst-yellow-bright); text-shadow: 0 2px 18px rgb(var(--kst-scrim-rgb) / 0.85), 0 0 4px rgb(var(--kst-scrim-rgb) / 0.6); }
.kst-hero__line { font-size: var(--kst-fs-lead); color: rgb(var(--kst-paper-rgb) / 0.94); max-width: 48ch; }
.kst-hero__actions { display: flex; flex-wrap: wrap; gap: var(--kst-s-3); margin-top: var(--kst-s-2); }
/* Links in the hero sit on the darkened photo. Without this rule they inherit
   --kst-red-deep and all but vanish. Buttons carry their own colours and are
   therefore exempt. */
.kst-hero__content a:not(.kst-btn) { color: var(--kst-paper); }
.kst-hero__content a:not(.kst-btn):hover { color: var(--kst-yellow); }

/* ----------------------------------------------------------- 8. Countdown */
.kst-countdown { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--kst-s-3); padding-block: var(--kst-s-5); background: var(--kst-yellow); border-bottom: var(--kst-border); }
.kst-countdown__word { font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-small); }
/* Four fields at 74px plus gaps do not fit on a 320px screen — they have to
   be allowed to wrap into two rows, otherwise the seconds are cut off. */
.kst-countdown__clock { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--kst-s-2); }
.kst-countdown__field { min-width: 74px; padding: var(--kst-s-2) var(--kst-s-3); background: var(--kst-paper); border: var(--kst-border-fine); border-radius: var(--kst-corner-a); text-align: center; }
.kst-countdown__field:nth-child(even) { border-radius: var(--kst-corner-b); }
/* Under prefers-reduced-motion the seconds field disappears — the token in
   tokens.css turns it off. Everything that is permanently in motion goes
   away there, and a digit jumping once a second is exactly that. The three
   remaining fields keep their alternating corners and their own width. */
.kst-countdown__field--seconds { display: var(--kst-countdown-seconds); }
.kst-countdown__number { font-family: var(--kst-font-head); font-weight: 800; font-size: var(--kst-fs-digits); line-height: 1; font-variant-numeric: tabular-nums; }
.kst-countdown__unit { font-size: var(--kst-fs-label); letter-spacing: var(--kst-ls-caps); text-transform: uppercase; color: var(--kst-grey); }

/* Three states: before · during · after.
   js/main.js sets data-kst-phase on <html>, CSS decides what is visible.
   Elements are only ever hidden, display is never set — otherwise the
   button row in the hero would lose its flex and stack vertically.
   Several phases on one element: data-kst-phase-text="before during". */
[data-kst-phase="before"] [data-kst-phase-text]:not([data-kst-phase-text~="before"]),
[data-kst-phase="during"] [data-kst-phase-text]:not([data-kst-phase-text~="during"]),
[data-kst-phase="after"]  [data-kst-phase-text]:not([data-kst-phase-text~="after"]) { display: none; }
/* Without JS the "before" state stays. The trailing :not() exempts the
   showcases in the styleguide, which bring their own phase. */
html:not([data-kst-phase]) [data-kst-phase-text]:not([data-kst-phase-text~="before"]):not([data-kst-phase] *) { display: none; }
[data-kst-phase="after"] .kst-countdown__clock { display: none; }

/* After the event the start page tells its story backwards: the pictures move
   straight under hero and countdown, prices and opening hours are gone. Only
   the order changes — the markup stays where it is, and display is not
   switched on anywhere: .kst-home is a column at all times, and merely the
   order values depend on the phase. */
.kst-home { display: flex; flex-direction: column; }
[data-kst-phase="after"] .kst-home > *                      { order: 2; }
[data-kst-phase="after"] .kst-home > .kst-hero,
[data-kst-phase="after"] .kst-home > .kst-countdown         { order: 0; }
[data-kst-phase="after"] .kst-home > [data-kst-phase-first] { order: 1; }

/* After the event the band carries a single sentence. */
.kst-countdown__epilogue { font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-h3); text-align: center; text-wrap: balance; }

/* ------------------------------------------------------ 9. Facts & lists */
.kst-facts { display: grid; gap: var(--kst-s-4); grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr)); }
.kst-fact { display: flex; flex-direction: column; gap: var(--kst-s-1); padding: var(--kst-s-4) 0; border-top: var(--kst-border-fine); }
.kst-fact__number { font-family: var(--kst-font-head); font-weight: 800; font-size: var(--kst-fs-number); line-height: 1; }
.kst-fact__label { font-size: var(--kst-fs-mini); letter-spacing: var(--kst-ls-caps); text-transform: uppercase; color: var(--kst-grey-light); }

/* Definition list for opening hours and contact details */
/* The value column has to be allowed to shrink — an e-mail address is one
   long word, and a bare 1fr would keep the whole list as wide as that word.
   With the track free to shrink, overflow-wrap in the reset breaks it. */
.kst-deflist { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--kst-s-3) var(--kst-s-5); align-items: baseline; }
.kst-deflist dt { font-weight: 600; }
.kst-deflist dd { margin: 0; color: var(--kst-text-muted); }

/* ----------------------------------------------------------- 10. Tables */
.kst-table-frame { overflow-x: auto; border: var(--kst-border); background: var(--kst-paper); }
.kst-table { width: 100%; border-collapse: collapse; min-width: 480px; }
.kst-table caption { padding: var(--kst-s-4); text-align: left; font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-h3); border-bottom: var(--kst-border); }
.kst-table__subcaption { display: block; margin-top: var(--kst-s-1); font-family: var(--kst-font-text); font-weight: 400; font-size: var(--kst-fs-small); color: var(--kst-text-muted); }
.kst-table th, .kst-table td { padding: var(--kst-s-3) var(--kst-s-4); text-align: left; border-bottom: var(--kst-hairline); }
.kst-table thead th { background: var(--kst-yellow); font-size: var(--kst-fs-mini); letter-spacing: var(--kst-ls-caps); text-transform: uppercase; border-bottom: var(--kst-border-fine); }
.kst-table tbody tr:last-child td { border-bottom: 0; }
.kst-table tbody tr:hover { background: var(--kst-yellow-light); }
.kst-table__price { font-family: var(--kst-font-head); font-weight: 800; font-size: var(--kst-fs-figure); white-space: nowrap; font-variant-numeric: tabular-nums; }
.kst-table__note { font-size: var(--kst-fs-mini); color: var(--kst-grey); }
/* --stack: a table that would otherwise scroll sideways on a phone, with
   its right-hand columns out of sight. Below 640px every row becomes a
   block: the row header on its own line, the cells side by side underneath
   (prices: "Erwachsene" above "€ 8,00 · pro Tag"). The head row stays for
   screen readers, only visually hidden (same pattern as .kst-sr).
   --stack-flip puts the data cell above the row header instead. The board
   table needs that: the portal writes the role first (board-rows.html),
   but on a phone the name should lead, so the order is flipped with flex
   `order` rather than in the markup. */
@media (max-width: 639.98px) {
  .kst-table--stack { min-width: 0; }
  .kst-table--stack, .kst-table--stack caption, .kst-table--stack tbody { display: block; }
  /* The yellow head row is hidden here, so the caption takes over the accent. */
  .kst-table--stack caption { background: var(--kst-yellow); color: var(--kst-ink); }
  .kst-table--stack thead {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  .kst-table--stack tbody tr {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--kst-s-1) var(--kst-s-3);
    padding: var(--kst-s-3) var(--kst-s-4); border-bottom: var(--kst-hairline);
  }
  .kst-table--stack tbody tr:last-child { border-bottom: 0; }
  .kst-table--stack tbody th, .kst-table--stack tbody td { display: block; padding: 0; border-bottom: 0; }
  .kst-table--stack tbody th { flex-basis: 100%; }
  .kst-table--stack-flip tbody td { order: -1; flex-basis: 100%; font-family: var(--kst-font-head); font-weight: 600; }
  .kst-table--stack-flip tbody th { font-weight: 400; font-size: var(--kst-fs-small); color: var(--kst-text-muted); }
}
/* Tabs, currently for the three days of the program. No tilt: this is
   navigation, and navigation stands straight. */
.kst-tabs { display: flex; flex-direction: column; gap: var(--kst-s-5); }
.kst-tabs__list { display: flex; flex-wrap: wrap; gap: var(--kst-s-2); }
.kst-tabs__tab {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  min-height: 52px; padding: var(--kst-s-2) var(--kst-s-5);
  background: var(--kst-paper); color: var(--kst-ink);
  border: var(--kst-border); border-radius: var(--kst-corner-a);
  font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-small);
  cursor: pointer; text-align: left;
  transition: transform var(--kst-time) var(--kst-ease-snap), box-shadow var(--kst-time) var(--kst-ease), background-color var(--kst-time-fast) linear;
}
.kst-tabs__tab:nth-child(even) { border-radius: var(--kst-corner-b); }
/* Same lift as .kst-btn: a tab is a button and has to feel like one.
   The selected one already stands lifted, so hovering it changes nothing —
   :active must come last, otherwise [aria-selected] would win the push-in. */
.kst-tabs__tab:hover { background: var(--kst-yellow-light); transform: translate(-2px, -2px); box-shadow: var(--kst-shadow); }
.kst-tabs__tab[aria-selected="true"], .kst-tabs__tab[aria-pressed="true"] { background: var(--kst-yellow); transform: translate(-2px, -2px); box-shadow: var(--kst-shadow); }
.kst-tabs__tab:active { transform: translate(1px, 1px); box-shadow: none; }
.kst-tabs__date { font-family: var(--kst-font-text); font-weight: 500; font-size: var(--kst-fs-mini); color: var(--kst-text-muted); }
.kst-tabs__tab[aria-selected="true"] .kst-tabs__date, .kst-tabs__tab[aria-pressed="true"] .kst-tabs__date { color: var(--kst-ink); }
.kst-tabs__panel { display: flex; flex-direction: column; gap: var(--kst-s-3); }

/* Program, version 2: the timetable. Times down, locations across, an item
   spans the half hours it covers (rowspan). The time column stays put while
   the locations scroll sideways inside .kst-table-frame on a phone. No
   tilt — it is a table. */
/* Fixed layout ignores min-width on cells, so the table itself is as wide as
   its columns need: main.js sets --kst-program-columns to the number of
   locations, and each gets at least 10rem before the frame scrolls.
   Separate borders, because the item cell below carries its own frame and
   rounded corners, which the collapsed model cannot draw. */
.kst-program {
  table-layout: fixed; border-collapse: separate; border-spacing: 0;
  min-width: calc(5rem + var(--kst-program-columns, 1) * 10rem);
}
.kst-program th:first-child {
  position: sticky; left: 0; z-index: 1;
  width: 5rem; background: var(--kst-paper);
  font-family: var(--kst-font-mono); font-size: var(--kst-fs-small); font-weight: 500; white-space: nowrap;
}
.kst-program thead th:first-child { background: var(--kst-yellow); }
.kst-program td { padding: var(--kst-s-1); vertical-align: top; border-left: var(--kst-hairline); }
.kst-program tbody tr:hover { background: transparent; }
/* The item cell IS the card. A rowspan cell is exactly as tall as its span
   in every browser, which a box inside it with height: 100% is not — in
   Firefox that box collapsed to its frame and the text ran out below. The
   transparent border is the gap to the neighbours; frame and red edge are
   inset shadows, so they follow the rounded corners. */
.kst-program td.kst-program__item {
  /* More room at the top than at the bottom: the hand-drawn corner's curve
     dips into the first line otherwise. */
  padding: var(--kst-s-4) var(--kst-s-3) var(--kst-s-2) calc(var(--kst-s-3) + var(--kst-stroke-thick));
  border: var(--kst-s-1) solid transparent; background: var(--kst-paper); background-clip: padding-box;
  box-shadow: inset 0 0 0 var(--kst-stroke-fine) var(--kst-ink), inset var(--kst-stroke-thick) 0 0 0 var(--kst-red);
  border-radius: var(--kst-corner-b);
}
.kst-program td.kst-program__item:nth-child(even) { border-radius: var(--kst-corner-c); }
.kst-program__card { display: flex; flex-direction: column; gap: 2px; }
.kst-program__time { font-family: var(--kst-font-mono); font-size: var(--kst-fs-mini); color: var(--kst-text-muted); }
.kst-program__audience { font-size: var(--kst-fs-mini); font-weight: 600; color: var(--kst-red-deep); }
/* The current minute: a red line across the whole table, on an event day
   only. main.js measures where it goes (--kst-now-y) and toggles hidden;
   everything it looks like is here. The plane is the positioned box the
   line is measured against, as wide as the table so the line scrolls with
   it; the label sticks to the time column. */
/* fit-content, not max-content: a table with width: 100% has an unbounded
   max-content width, and the plane would grow to a million pixels. */
.kst-program__plane { position: relative; min-width: 100%; width: fit-content; }
.kst-program__plane > .kst-program { width: 100%; }
.kst-program__nowline {
  position: absolute; top: 0; left: 0; right: 0; z-index: 2;
  height: var(--kst-stroke-thick); margin-top: calc(var(--kst-stroke-thick) / -2);
  background: var(--kst-red); pointer-events: none;
  transform: translateY(var(--kst-now-y, 0px));
}
.kst-program__nowlabel {
  position: sticky; left: 0; display: inline-block;
  transform: translateY(-50%); margin-top: calc(var(--kst-stroke-thick) / 2);
  padding: 2px var(--kst-s-2);
  background: var(--kst-paper); border: var(--kst-border-fine); border-radius: var(--kst-corner-pill);
  font-family: var(--kst-font-head); font-weight: 700; font-size: var(--kst-fs-mini); color: var(--kst-ink); white-space: nowrap;
}

/* Program, version 1: the hand-written list — fixed time column, flexible
   title. Kept until every installation serves version 2. */
.kst-program-list td:first-child { width: 128px; font-family: var(--kst-font-mono); font-size: var(--kst-fs-small); white-space: nowrap; }
.kst-program__place { display: inline-block; margin-top: 2px; padding: 2px 8px; background: var(--kst-red-light); color: var(--kst-red-deep); font-size: var(--kst-fs-mini); font-weight: 600; }

/* -------------------------------------------------------- 11. Accordion */
.kst-accordion { display: flex; flex-direction: column; gap: var(--kst-s-3); }
.kst-accordion__item { border: var(--kst-border); background: var(--kst-paper); border-radius: var(--kst-corner-a); overflow: hidden; }
.kst-accordion__item:nth-child(even) { border-radius: var(--kst-corner-b); }
.kst-accordion__head { display: flex; align-items: center; justify-content: space-between; gap: var(--kst-s-4); width: 100%; padding: var(--kst-s-4) var(--kst-s-5); min-height: 60px; background: none; border: 0; cursor: pointer; text-align: left; font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-body); }
.kst-accordion__head:hover { background: var(--kst-yellow-light); }
.kst-accordion__sign { flex: none; position: relative; width: 20px; height: 20px; }
.kst-accordion__sign::before, .kst-accordion__sign::after { content: ""; position: absolute; inset: 50% 0 auto 0; height: var(--kst-stroke); background: var(--kst-ink); transition: transform var(--kst-time) var(--kst-ease); }
.kst-accordion__sign::after { transform: rotate(90deg); }
.kst-accordion__head[aria-expanded="true"] .kst-accordion__sign::after { transform: rotate(0deg); }
.kst-accordion__item[data-open="true"] .kst-accordion__panel { grid-template-rows: 1fr; }
.kst-accordion__body { display: flex; flex-direction: column; gap: var(--kst-s-3); padding: 0 var(--kst-s-5) var(--kst-s-5); color: var(--kst-text-muted); }
/* An answer from the portal's FAQ may hold lists and bold (faq.Answer):
   bullets are the prose's red squares (section 3), numbers the heading face
   in ink. A numbered point's sub-list is its only block child; the point
   itself stays inline flow (a flex column would break its bold and links
   onto lines of their own), so the sub-list takes its air as padding. */
.kst-accordion__body ol { display: flex; flex-direction: column; gap: var(--kst-s-2); counter-reset: kst-answer; }
.kst-accordion__body ol > li { position: relative; padding-left: var(--kst-s-5); counter-increment: kst-answer; }
.kst-accordion__body ol > li > ul { padding-top: var(--kst-s-2); }
.kst-accordion__body ol > li::before { content: counter(kst-answer) "."; position: absolute; left: 0; font-family: var(--kst-font-head); font-weight: 800; color: var(--kst-ink); }
.kst-accordion__body strong { color: var(--kst-ink); font-weight: 600; }

/* ------------------------------------------------- 12. Gallery + lightbox */
.kst-gallery__head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--kst-s-3); padding-bottom: var(--kst-s-3); margin-bottom: var(--kst-s-5); border-bottom: var(--kst-border); }
.kst-gallery__grid { display: grid; gap: var(--kst-s-3); grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); }
.kst-tile { position: relative; display: block; padding: 0; background: var(--kst-paper-2); border: var(--kst-border-fine); cursor: zoom-in; overflow: hidden; aspect-ratio: 4 / 3; }
.kst-tile img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--kst-time-long) var(--kst-ease), filter var(--kst-time) linear; }
.kst-tile:hover img { transform: scale(1.05); }
.kst-tile::after { content: ""; position: absolute; inset: 0; background: var(--kst-yellow); mix-blend-mode: multiply; opacity: 0; transition: opacity var(--kst-time) linear; }
.kst-tile:hover::after { opacity: 0.22; }
.kst-gallery__more { display: flex; justify-content: center; margin-top: var(--kst-s-5); }

.kst-lightbox { position: fixed; inset: 0; z-index: var(--kst-z-lightbox); display: none; background: rgb(var(--kst-scrim-rgb) / 0.94); }
.kst-lightbox[data-open="true"] { display: grid; grid-template-rows: auto 1fr auto; }
.kst-lightbox__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--kst-s-4); padding: var(--kst-s-3) var(--kst-gutter); color: var(--kst-paper); }
.kst-lightbox__counter { font-family: var(--kst-font-mono); font-size: var(--kst-fs-mini); color: rgb(var(--kst-paper-rgb) / 0.72); }
/* grid-template-rows is what makes max-height: 100% below mean anything: an
   implicit auto row is sized by its content, so the image would be measuring
   itself against itself and a portrait photo would run off the bottom of the
   screen. width/height auto overrule the width and height attributes that
   the script writes for the loading state — without them the box keeps the
   full pixel width and the photo sits letterboxed in a black field. */
.kst-lightbox__stage { position: relative; display: grid; grid-template-rows: minmax(0, 1fr); place-items: center; padding: 0 var(--kst-s-4); min-height: 0; }
.kst-lightbox__image { width: auto; height: auto; max-width: min(1400px, 94vw); max-height: 100%; object-fit: contain; border: var(--kst-stroke-fine) solid var(--kst-paper); background: var(--kst-ink); }
.kst-lightbox__image[data-loading="true"] { opacity: 0.35; }
/* A banner put back together: the pieces touch, and the script sets the
   width so the row fits the stage as one picture (see fitStrip). */
.kst-lightbox__strip:not([hidden]) { display: flex; border: var(--kst-stroke-fine) solid var(--kst-paper); background: var(--kst-ink); }
.kst-lightbox__strip img { height: auto; }
/* A post's caption can run long, so the foot scrolls rather than pushing the
   picture off the screen. fit-content keeps a short caption centred and lets
   a long one read left-aligned at text width. */
.kst-lightbox__foot { display: grid; justify-items: center; gap: var(--kst-s-3); max-height: 30vh; overflow-y: auto; padding: var(--kst-s-3) var(--kst-gutter) var(--kst-s-5); color: rgb(var(--kst-paper-rgb) / 0.82); font-size: var(--kst-fs-small); text-wrap: pretty; }
.kst-lightbox__text { width: fit-content; max-width: var(--kst-max-text); }
.kst-lightbox__text:empty { display: none; }
.kst-lightbox__link { color: var(--kst-paper); min-height: 44px; }
/* The lightbox is a dark surface without .kst-inverse, so it inherits the
   light --kst-link-hover, which is ink. And a:hover outranks a single
   class, so the rule above does not survive the hover: the link turned
   near-black on the scrim and vanished. Same fix as .kst-footer a:hover. */
.kst-lightbox__link:hover { color: var(--kst-yellow); }
.kst-lightbox__link[hidden] { display: none; }
.kst-lightbox[data-single="true"] .kst-lightbox__nav,
.kst-lightbox[data-single="true"] .kst-lightbox__counter { visibility: hidden; }
.kst-lightbox__button { display: inline-flex; align-items: center; justify-content: center; min-width: 52px; min-height: 52px; background: rgb(var(--kst-paper-rgb) / 0.1); color: var(--kst-paper); border: var(--kst-stroke-fine) solid rgb(var(--kst-paper-rgb) / 0.5); border-radius: var(--kst-corner-a); font-size: var(--kst-fs-figure); cursor: pointer; }
.kst-lightbox__button:hover { background: var(--kst-yellow); color: var(--kst-ink); border-color: var(--kst-yellow); }
.kst-lightbox__nav { position: absolute; top: 50%; transform: translateY(-50%); }
.kst-lightbox__nav--prev { left: var(--kst-s-2); }
.kst-lightbox__nav--next { right: var(--kst-s-2); }

/* ------------------------------------------------------ 13. Feed & Logos */
.kst-feed { display: grid; gap: var(--kst-s-4); grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); }
.kst-feed__item { display: flex; flex-direction: column; background: var(--kst-paper); border: var(--kst-border-fine); overflow: hidden; }
.kst-feed__item:hover { box-shadow: var(--kst-shadow-soft); }
.kst-feed__media { position: relative; display: block; aspect-ratio: 1; overflow: hidden; }
.kst-feed__image { width: 100%; height: 100%; object-fit: cover; }
/* A banner: its pieces in a row, each a card wide. The strip slides so the
   window runs across the picture once — transform only, set off by the
   script through data-kst-pan. */
.kst-feed__strip { display: flex; width: calc(var(--count) * 100%); height: 100%; }
.kst-feed__strip img { width: calc(100% / var(--count)); height: 100%; object-fit: cover; }
.kst-feed__strip[data-kst-pan="run"] { animation: kst-feed-pan var(--kst-time-pan) var(--kst-ease); }
@keyframes kst-feed-pan {
  0%, 15%  { transform: translateX(0); }
  55%, 70% { transform: translateX(calc(-100% * (var(--count) - 1) / var(--count))); }
  100%     { transform: translateX(0); }
}
.kst-feed__count { position: absolute; top: var(--kst-s-2); right: var(--kst-s-2); padding: var(--kst-s-1) var(--kst-s-2); background: var(--kst-ink); color: var(--kst-paper); border-radius: var(--kst-corner-b); font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-label); }
/* The padding sits on the body, not on the clamped text: inside the text's
   own overflow box, padding is room the fourth line peeks through. */
.kst-feed__body { display: flex; flex-direction: column; gap: var(--kst-s-3); padding: var(--kst-s-3) var(--kst-s-4) var(--kst-s-4); }
.kst-feed__text { font-size: var(--kst-fs-mini); line-height: 1.5; color: var(--kst-text-muted); display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.kst-feed__source { font-size: var(--kst-fs-label); letter-spacing: var(--kst-ls-caps); text-transform: uppercase; color: var(--kst-red-deep); font-weight: 600; }

.kst-logowall { display: grid; gap: var(--kst-s-4); grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); align-items: stretch; }
.kst-logowall__cell { display: grid; place-items: center; align-content: center; gap: var(--kst-s-2); padding: var(--kst-s-4); min-height: 96px; background: var(--kst-white); border: var(--kst-hairline); }
.kst-logowall__cell img { max-height: 52px; width: auto; object-fit: contain; filter: grayscale(1); opacity: 0.72; transition: filter var(--kst-time) linear, opacity var(--kst-time) linear; }
/* Colour on hover — and on keyboard focus, which is the same interest. */
.kst-logowall__cell:hover img, .kst-logowall__cell:focus-visible img { filter: none; opacity: 1; }
a.kst-logowall__cell:hover { border-color: var(--kst-ink); }
.kst-logowall__caption { font-size: var(--kst-fs-mini); color: var(--kst-text-muted); text-align: center; text-wrap: pretty; }

/* The sponsors' band above the footer (partials/sponsors.html). In colour —
   a sponsor's logo is its brand — on white, one row per tier, the higher
   the tier the larger the logo: data-rank 0, 1, 2 (and everything below).
   Nothing moves; the only transition is the logowall's hover. */
.kst-sponsors { background: var(--kst-white); border-top: var(--kst-hairline); padding-block: var(--kst-s-6); }
.kst-sponsors__title { font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-small); letter-spacing: var(--kst-ls-caps); text-transform: uppercase; color: var(--kst-text-muted); text-align: center; margin-bottom: var(--kst-s-4); }
.kst-sponsors__groups { display: flex; flex-direction: column; gap: var(--kst-s-5); }
.kst-sponsors__group { --kst-sponsor-h: 40px; display: flex; flex-direction: column; align-items: center; gap: var(--kst-s-2); }
.kst-sponsors__group[data-rank="0"] { --kst-sponsor-h: clamp(52px, 8vw, 72px); }
.kst-sponsors__group[data-rank="1"] { --kst-sponsor-h: clamp(44px, 6vw, 52px); }
.kst-sponsors__list { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: var(--kst-s-3) var(--kst-s-5); list-style: none; padding: 0; margin: 0; }
/* 44 px at least, so a linked logo is a touch target on every rank. */
.kst-sponsors__item { display: grid; place-items: center; min-height: 44px; min-width: 44px; padding: var(--kst-s-1); }
.kst-sponsors__item img { height: var(--kst-sponsor-h); width: auto; max-width: min(240px, 70vw); object-fit: contain; opacity: 0.9; transition: opacity var(--kst-time) linear; }
.kst-sponsors__item:hover img, .kst-sponsors__item:focus-visible img { opacity: 1; }
.kst-sponsors__name { font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-small); }

/* Which of the three days someone is there. */
.kst-daytags { display: flex; flex-wrap: wrap; gap: var(--kst-s-1); }
/* An exhibitor's days, one group per location: the day badges, then where. */
.kst-daygroups { display: flex; flex-direction: column; gap: var(--kst-s-2); }
.kst-daygroup { display: flex; flex-wrap: wrap; align-items: center; gap: var(--kst-s-2); }
.kst-daygroup__place { font-weight: 600; }
.kst-daytag { padding: 2px 8px; background: var(--kst-yellow); border: var(--kst-border-fine); font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-mini); }

/* ----------------------------------------------------- 13b. Hall plan
   hallenplan.html: the two floors as inline SVG (partials/hallplan-*.html),
   one room per <a data-kst-room>. No tilt — the plan is navigation. Below
   a phone's landscape width the plan keeps its size and scrolls sideways
   inside its frame, like a wide table: shrunk to 320 px no label is
   readable. Sizes inside the SVG are in its own units (--kst-hallplan-*),
   which scale with the drawing. */
.kst-hallplan { display: flex; flex-direction: column; gap: var(--kst-s-5); }
.kst-hallplan__floors { display: flex; flex-direction: column; gap: var(--kst-s-7); }
.kst-hallplan__floor { display: flex; flex-direction: column; gap: var(--kst-s-3); }
.kst-hallplan__plan { overflow-x: auto; padding: var(--kst-s-2); background: var(--kst-paper); border: var(--kst-border); border-radius: var(--kst-corner-c); }
.kst-hallplan__svg { display: block; width: 100%; min-width: var(--kst-hallplan-min); height: auto; }

.kst-hallplan__room > polygon, .kst-hallplan__room > path { fill: var(--kst-paper); stroke: var(--kst-ink); stroke-width: var(--kst-stroke); stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.kst-hallplan__room--foyer > polygon { fill: rgb(var(--kst-ink-rgb) / 0.08); }
/* A room with a Standort is tinted, so what can be tapped is visible
   before anything is tapped. Hover and focus light up every drawn room of
   the same Standort (data-kst-room-hover, set by main.js). */
.kst-hallplan__room[data-kst-room-state="active"] { cursor: pointer; }
.kst-hallplan__room[data-kst-room-state="active"] > polygon,
.kst-hallplan__room[data-kst-room-state="active"] > path { fill: var(--kst-yellow-light); }
.kst-hallplan__room[data-kst-room-hover="true"] > polygon,
.kst-hallplan__room[data-kst-room-hover="true"] > path { fill: var(--kst-yellow); }
.kst-hallplan__room[data-kst-room-open="true"] > polygon,
.kst-hallplan__room[data-kst-room-open="true"] > path { fill: var(--kst-red); }
.kst-hallplan__room[data-kst-room-open="true"] .kst-hallplan__label { fill: var(--kst-white); }
/* The browser's ring hugs an SVG link badly — a rectangle around the room,
   in the system's accent colour, and on plain :focus too, so it stayed
   after every mouse click once the dialog handed the focus back. The
   outline of the room itself takes the focus colour instead, and only for
   :focus-visible. */
.kst-hallplan__room:focus { outline: none; }
.kst-hallplan__room:focus-visible > polygon,
.kst-hallplan__room:focus-visible > path { stroke: var(--kst-focus); stroke-width: var(--kst-stroke-thick); }

.kst-hallplan__label { font-family: var(--kst-font-head); font-weight: 700; font-size: var(--kst-hallplan-label); fill: var(--kst-ink); text-anchor: middle; pointer-events: none; }
.kst-hallplan__label--small, .kst-hallplan__sub { font-size: var(--kst-hallplan-note); font-weight: 500; }
.kst-hallplan__note { font-family: var(--kst-font-text); font-size: var(--kst-hallplan-note); fill: var(--kst-grey); text-anchor: middle; }
.kst-hallplan__note--strong { font-family: var(--kst-font-head); font-weight: 700; fill: var(--kst-ink); letter-spacing: var(--kst-ls-caps); text-transform: uppercase; }
.kst-hallplan__note--start { text-anchor: start; }
.kst-hallplan__service polygon { fill: var(--kst-paper); stroke: var(--kst-grey-light); stroke-width: var(--kst-stroke-fine); vector-effect: non-scaling-stroke; }
.kst-hallplan__entrance line { stroke: var(--kst-grey-light); stroke-width: var(--kst-stroke-fine); stroke-dasharray: 2 6; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.kst-hallplan__entrance circle { fill: var(--kst-paper); stroke: var(--kst-ink); stroke-width: var(--kst-stroke); vector-effect: non-scaling-stroke; }
.kst-hallplan__box { fill: none; stroke: none; pointer-events: none; }

/* The logos lie over the rooms and let every click through to them. */
.kst-hallplan__logos { pointer-events: none; }
.kst-hallplan__logogrid { display: flex; flex-wrap: wrap; align-items: center; align-content: center; justify-content: center; gap: var(--kst-s-1); width: 100%; height: 100%; overflow: hidden; }
.kst-hallplan__logo { max-width: 100%; max-height: var(--kst-hallplan-logo); width: auto; height: auto; object-fit: contain; padding: var(--kst-s-1); background: var(--kst-white); border: var(--kst-border-fine); border-radius: var(--kst-corner-a); }
.kst-hallplan__logo:nth-child(even) { border-radius: var(--kst-corner-b); }
.kst-hallplan__logo--name { padding: var(--kst-s-1) var(--kst-s-2); hyphens: auto; overflow-wrap: normal; font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-hallplan-logo-text); line-height: var(--kst-lh-head); color: var(--kst-ink); text-align: center; }

.kst-hallplan__dialog { width: min(640px, 100% - 2 * var(--kst-gutter)); max-height: calc(100dvh - 2 * var(--kst-s-5)); padding: 0; overflow-y: auto; background: var(--kst-paper); color: var(--kst-ink); border: var(--kst-border); border-radius: var(--kst-corner-b); box-shadow: var(--kst-shadow); }
.kst-hallplan__dialog::backdrop { background: rgb(var(--kst-scrim-rgb) / 0.6); }
.kst-hallplan__dialogbox { display: flex; flex-direction: column; gap: var(--kst-s-5); padding: var(--kst-s-5); }
.kst-hallplan__dialoghead { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--kst-s-4); }
.kst-hallplan__close { flex: none; display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; background: var(--kst-paper); color: var(--kst-ink); border: var(--kst-border-fine); border-radius: var(--kst-corner-a); font-size: var(--kst-fs-figure); cursor: pointer; }
.kst-hallplan__close:hover { background: var(--kst-yellow); }
.kst-hallplan__items { display: flex; flex-direction: column; gap: var(--kst-s-2); margin: 0; padding: 0; list-style: none; }
.kst-hallplan__item { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--kst-s-1) var(--kst-s-4); padding: var(--kst-s-3); border: var(--kst-hairline); }
.kst-hallplan__item[data-kst-now="true"] { background: var(--kst-yellow-light); border: var(--kst-border-fine); }
.kst-hallplan__time { font-family: var(--kst-font-head); font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.kst-hallplan__what { display: flex; flex-direction: column; gap: var(--kst-s-1); }
.kst-hallplan__now { grid-column: 2; justify-self: start; font-size: var(--kst-fs-label); font-weight: 600; letter-spacing: var(--kst-ls-caps); text-transform: uppercase; color: var(--kst-red-deep); }

/* ------------------------------------------------ 14. Consent placeholder */
/* For EVERY third-party embed (Instagram, maps, Vimeo). */
.kst-consent { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--kst-s-3); min-height: 320px; padding: var(--kst-s-6); text-align: center; background: var(--kst-paper-2); border: var(--kst-stroke) dashed var(--kst-ink); }
.kst-consent__title { font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-h3); }
.kst-consent__text { font-size: var(--kst-fs-small); color: var(--kst-text-muted); max-width: 52ch; text-wrap: pretty; }
/* What the placeholder turns into once someone agrees. The height comes from
   data-height on the placeholder, which js/main.js passes in as a variable —
   the rest belongs here, not in a style attribute built in JS. */
.kst-embed { display: block; width: 100%; border: 0; height: var(--kst-embed-height, 420px); }

/* ------------------------------------------------------ 15. Placeholder */
/* Visible placeholder for assets that are still missing. Remove before launch. */
.kst-placeholder {
  display: grid; place-items: center; gap: var(--kst-s-2); padding: var(--kst-s-6); min-height: 200px;
  background: var(--kst-paper-2);
  background-image: repeating-linear-gradient(135deg, rgb(var(--kst-ink-rgb) / 0.055) 0 10px, transparent 10px 20px);
  border: var(--kst-stroke-fine) dashed var(--kst-grey-light);
  font-family: var(--kst-font-mono); font-size: var(--kst-fs-mini); color: var(--kst-grey); text-align: center;
}
/* A placeholder inside a grid stands for the whole row, not for one cell —
   "nothing loaded" is never a tile. Ignored outside a grid. */
.kst-placeholder { grid-column: 1 / -1; }
/* Same crop as the photo that will replace it. */
.kst-placeholder--cover { min-height: 0; aspect-ratio: 16 / 9; }
.kst-placeholder--photo { min-height: 0; aspect-ratio: 4 / 3; }
/* Inside a logo cell the placeholder is only the company name — the cell
   already brings its own frame and padding. */
.kst-logowall__cell .kst-placeholder { min-height: 0; padding: 0; border: 0; background: none; }
/* A list that has nothing published yet: one quiet line, no stripes, no
   200 px block — the striped box stays for missing images and errors. */
.kst-placeholder--quiet {
  min-height: 0; padding: var(--kst-s-4);
  background: none;
  font-family: var(--kst-font-text); font-size: var(--kst-fs-small); color: var(--kst-text-muted);
}

/* ------------------------------------------------------------- 16. Pages */
.kst-pagehead { padding-block: var(--kst-s-7) var(--kst-s-6); border-bottom: var(--kst-border); background: var(--kst-paper-2); }
/* Below 1280px the header's date stamp hangs into the page head: breadcrumb
   and heading start under it. */
@media (max-width: 1279.98px) { .kst-pagehead { padding-top: var(--kst-s-10); } }
.kst-pagehead__inner { display: flex; flex-direction: column; gap: var(--kst-s-3); }
/* The one subtitle line may run wider than running text — below it the
   content uses the full shell, and a 68ch lead breaks off far too early. */
.kst-pagehead .kst-lead { max-width: var(--kst-max-lead); }
.kst-breadcrumb { display: flex; flex-wrap: wrap; gap: var(--kst-s-2); font-size: var(--kst-fs-mini); color: var(--kst-grey-light); }
.kst-breadcrumb a { color: var(--kst-grey); }
.kst-breadcrumb li:not(:last-child)::after { content: "/"; margin-left: var(--kst-s-2); color: var(--kst-line); }
.kst-breadcrumb ol { display: contents; }

.kst-sectionhead { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--kst-s-4); margin-bottom: var(--kst-s-6); }

.kst-note { display: flex; gap: var(--kst-s-4); padding: var(--kst-s-5); background: var(--kst-yellow-light); border: var(--kst-border-fine); border-radius: var(--kst-corner-c); }
.kst-note--red { background: var(--kst-red-light); }
/* A note under a grid of cards in the same shell: the grid has no gap
   below it. */
.kst-note--spaced { margin-top: var(--kst-s-6); }
/* A flex child will not go below its min-content width on its own, so a long
   word in the box held the box open past the screen edge on a phone. */
.kst-note > * { min-width: 0; }
/* Beside running text a box starts at the top, not in the middle: it is text
   itself and should line up with the heading next to it. A photo in the same
   split stays centred — there the middle is right. */
.kst-split > .kst-note { align-self: start; }
/* The red bar down the left is decoration — it belongs in CSS, not as an
   aria-hidden span in every one of these boxes. */
.kst-note::before { content: ""; flex: none; width: 10px; align-self: stretch; background: var(--kst-red); }

/* ------------------------------------------------------------ 17. Footer */
.kst-footer { background: var(--kst-ink); color: var(--kst-paper); border-top: var(--kst-stroke-thick) solid var(--kst-red); }
.kst-footer a { color: rgb(var(--kst-paper-rgb) / 0.82); }
.kst-footer a:hover { color: var(--kst-yellow); }
.kst-footer__grid { display: grid; gap: var(--kst-s-6); padding-block: var(--kst-s-7); grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); }
.kst-footer__title { font-family: var(--kst-font-head); font-weight: 600; font-size: var(--kst-fs-small); letter-spacing: var(--kst-ls-caps); text-transform: uppercase; color: var(--kst-yellow); margin-bottom: var(--kst-s-3); }
.kst-footer__list { display: flex; flex-direction: column; gap: var(--kst-s-2); font-size: var(--kst-fs-small); }
.kst-footer__bottom { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--kst-s-3); padding-block: var(--kst-s-4); border-top: 1px solid rgb(var(--kst-paper-rgb) / 0.16); font-size: var(--kst-fs-mini); color: rgb(var(--kst-paper-rgb) / 0.6); }

/* ----------------------------------------------- 18. Demo mode (temporary)
   Belongs to the ?day= parameter in js/main.js and goes out with it.
   Deliberately conspicuous: nobody should mistake a test view for the
   real state. */
.kst-demobar {
  position: fixed; right: var(--kst-s-4); bottom: var(--kst-s-4);
  z-index: 9999;
  display: flex; align-items: center; gap: var(--kst-s-2);
  padding: var(--kst-s-2) var(--kst-s-4);
  background: var(--kst-ink); color: var(--kst-yellow);
  border: var(--kst-border-fine); border-color: var(--kst-yellow);
  font-family: var(--kst-font-mono); font-size: var(--kst-fs-mini);
  white-space: nowrap;
}
.kst-demobar a { color: var(--kst-yellow); text-decoration: underline; }
/* Ink on ink without this: the bar is dark, but a:hover outranks the rule
   above and reaches for the light --kst-link-hover. See .kst-lightbox__link. */
.kst-demobar a:hover { color: var(--kst-paper); }

/* --------------------------------------------------- 19. Styleguide only
   Chrome for public/styleguide.html — the frame around the samples, never
   used on a real page. Kept here rather than in a style attribute so the
   page that documents the rules also follows them. */
.kst-sg { display: flex; flex-direction: column; gap: var(--kst-s-5); }
.kst-sg .kst-hero-title { color: var(--kst-ink); }
.kst-sg__card { gap: var(--kst-s-2); }
.kst-sg__card .kst-card__title { font-size: var(--kst-fs-body); }
/* Colour sample. The colour itself comes in per instance as --swatch, so no
   value is ever written into the markup. */
.kst-sg__swatch {
  height: 72px; display: grid; place-items: center;
  background: var(--swatch); color: var(--swatch-ink);
  border: var(--kst-border-fine);
  font-family: var(--kst-font-mono); font-size: var(--kst-fs-mini);
}
/* A sample shown with its label above it. */
/* The header's stamp on its own, not hanging into anything. */
.kst-sg__header { gap: var(--kst-s-6); }
.kst-sg__header .kst-header__stamp { position: static; }
/* The press dog-ear, opened, in a frame of its own size. Its sizes normally
   come from the header, so the frame sets them. */
.kst-sg__dogear {
  --dogear-open: 348px; --dogear-closed: 0.2069; --dogear-nudge: 0;
  position: relative; width: min(100%, var(--dogear-open)); height: var(--dogear-open);
  border: var(--kst-border-fine);
}
.kst-sg__demo { display: flex; flex-direction: column; gap: var(--kst-s-2); }
.kst-sg__demo .kst-countdown { border-top: var(--kst-border); }
.kst-sg__inverse { padding: var(--kst-section) var(--kst-s-6); border-radius: var(--kst-corner-a); }

/* -------------------------------------------------------------- 20. Utilities */
[hidden] { display: none !important; }

/* ---------------------------------------- 21. Editorial marker (temporary)
   REMOVE BEFORE LAUNCH — together with --kst-todo-blink in tokens.css and
   the markTodo() helper in js/main.js.

   Marks every spot that still needs copy. <blink> has been gone from
   browsers for years; an opacity keyframe does the same job and is one of
   the three properties this design allows to animate. With reduced motion
   the token turns the animation off and the marker goes red instead — just
   as hard to miss, without the flicker.

   Not to be confused with .kst-marker, which highlights real content. */
.kst-todo {
  display: inline-block;
  padding: 0.1em 0.5em;
  background: var(--kst-todo-bg); color: var(--kst-todo-ink);
  border: var(--kst-border-fine);
  border-radius: var(--kst-corner-b);
  font-family: var(--kst-font-head); font-weight: 700;
  font-size: 0.92em; letter-spacing: var(--kst-ls-wide);
  text-transform: uppercase;
  animation: var(--kst-todo-blink);
}
@keyframes kst-todo-blink {
  0%, 60%   { opacity: 1; }
  60.01%, 100% { opacity: 0.35; }
}

/* ------------------------------------------------- programm.html, full view
   The program on a page of its own: the whole width of the window (minus
   the gutter) instead of the 1240 px shell, so more locations fit side by
   side; and in the Fullscreen API's full screen, the paper stays paper. */
.kst-programview { padding-inline: var(--kst-gutter); }
.kst-programview:fullscreen { overflow: auto; padding: var(--kst-gutter); background: var(--kst-paper); }

/* ------------------------------------------------------------ Print / PDF
   What "Drucken / PDF" on programm.html produces — and the start page, if
   somebody prints that: every day of the program on a page of its own, A4
   landscape, without the chrome around it. Only here does CSS show a hidden
   panel: on paper there are no tabs to click. */
@media print {
  @page { size: A4 landscape; margin: 12mm; }
  html, body { background: var(--kst-white); }
  * { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  .kst-header, .kst-mobilenav, .kst-footer, .kst-demobar, .kst-skiplink, .kst-breadcrumb,
  .kst-tabs__list, .kst-program__nowline, [data-kst-program-actions] { display: none; }
  /* !important against the global [hidden] rule, which is !important too;
     the more specific selector wins. */
  .kst-tabs__panel[hidden] { display: flex !important; }
  .kst-tabs__panel + .kst-tabs__panel { break-before: page; }
  .kst-table-frame { overflow: visible; border: var(--kst-border-fine); }
  .kst-program { min-width: 0; font-size: var(--kst-fs-mini); }
  .kst-program th:first-child { position: static; width: 3.5rem; }
  .kst-program th, .kst-program td { padding: 2px var(--kst-s-2); }
  .kst-program td.kst-program__item { break-inside: avoid; }
  .kst-program__plane { width: 100%; }
}

