/**
 * Component styles. Consolidated into one file for now while the design
 * system stabilizes — see docs/architecture.md for the eventual per-component
 * split (design/components/hero, teaser, content-card, ...) once there's
 * enough real content to justify it (avoids premature structure).
 */

/* Sticky footer's growing child — see the body flex rule in
   typography.css. Only <main> grows; header/footer keep their natural
   height.
   `width: 100%` is required alongside `flex: 1 0 auto`: main has no
   explicit width of its own (it relies on .container's max-width +
   margin-inline: auto to center itself), and a flex item with auto
   cross-axis margins does NOT stretch to the container's cross size the
   way a plain block would — the auto margins instead just absorb
   whatever free space is left around its content-sized (max-content)
   width. Without this, main silently shrank to fit its longest line of
   text instead of the intended 1200px, so page width varied per page
   (591px on a short-text page like /wege, 1038px on /gebiete, full width
   only on pages with a full-bleed hero forcing it wide) — found by
   comparing computed widths across pages, not visible from the CSS
   alone. */
main {
  flex: 1 0 auto;
  width: 100%;
}

/* Base form controls — a site-wide reset, not a per-component style. Every
   <input>/<select>/<textarea>/<button> across the site (search, contact,
   disturbance report, event filters, consent banner) was rendering with
   raw OS/browser chrome — no border-radius, no font-family, mismatched
   sizing — because nothing had ever set a baseline for the bare elements,
   only for a few specific classed ones (.booking-field input,
   .nav-search__form). Targeting the elements directly here means every
   current and future plain <select>/<button> picks this up with zero
   per-template classes, instead of the ad-hoc "style=" attributes this
   replaced on the event-filter form.
   Buttons with their own look (.site-nav__toggle, .map-list-toggle button,
   .alert-banner__dismiss, .nav-search__form button, .booking-submit) all
   already carry a class, so their higher-specificity rules still win over
   this bare `button` default — verified by grepping every <button> in the
   templates before adding this. */
/* Wrapped in the `legacy-forms` cascade layer (declared, empty, at the top
   of frontend/tailwind/input.css, BEFORE Tailwind's own theme/base/
   components/utilities layers) so it stays lower-priority than Tailwind's
   output — per the CSS Cascade Layers spec, ANY unlayered rule always beats
   ANY layered rule regardless of selector specificity, so without this a
   plain `button {}` here silently overrode Tailwind's `.btn-secondary`
   (higher specificity, lower priority only because it was layered) —
   found live: "Ablehnen"/"Akzeptieren" rendered as two identical solid
   green buttons instead of secondary/primary. */
@layer legacy-forms {
input:not([type="checkbox"]):not([type="radio"]),
select,
textarea {
  font: inherit;
  font-size: var(--font-size-body);
  color: var(--color-text);
  background: var(--color-background);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

input:not([type="checkbox"]):not([type="radio"]):focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 20%, transparent);
}

/* Native select-arrow chrome varies wildly by OS/browser (and looked
   especially out of place next to the rest of the design system) — replace
   it with one consistent inline chevron everywhere. */
select {
  appearance: none;
  -webkit-appearance: none;
  padding-inline-end: var(--space-8);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23205b7d' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  background-size: 16px;
  cursor: pointer;
}

/* Bare, unclassed `<button>` fallback — for the handful of submit buttons
   that were never given `.btn` (search forms in _layout.twig/search/index.twig).
   `.btn`/`.btn-secondary` (frontend/tailwind/input.css) are the one real
   button system; this is just its reset-layer default so an unmigrated
   plain `<button>` doesn't fall back to raw browser chrome. There used to
   be a second, unused `.button`/`.button--secondary` pair duplicating this
   — removed 2026-09-29, since no template ever referenced them.
   Excludes `.maplibregl-*` buttons (popup close button, nav-control zoom/
   compass buttons): MapLibre's own CSS is loaded unlayered, so per the CSS
   Cascade Layers spec it wins over this layered rule ONLY for the specific
   properties it actually sets (background-color, border, cursor) — for
   every property it DOESN'T set (color, padding, box-shadow, font-weight),
   this rule was still winning by default and leaking in, since a layered
   rule only loses when an unlayered rule declares the SAME property. Found
   live on /karte: the popup close button rendered as a white "×" (this
   rule's `color: var(--color-primary-fg)`) on MapLibre's own transparent
   background — invisible. */
button:not([class^="maplibregl-"]) {
  font: inherit;
  font-weight: var(--font-weight-bold);
  padding: var(--space-2) var(--space-5);
  border: none;
  border-radius: var(--radius-sm);
  background: var(--color-primary);
  color: var(--color-primary-fg);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: background-color var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-fast);
}

button:hover {
  background: color-mix(in srgb, var(--color-primary) 80%, black);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

button:active {
  transform: translateY(0);
  box-shadow: var(--shadow-sm);
}

button:disabled {
  cursor: not-allowed;
  opacity: 0.55;
  box-shadow: none;
  transform: none;
}
} /* end @layer legacy-forms */

/* Filter/search forms (events list, /suche) — replaces what used to be a
   stack of inline style="" attributes (display:flex, margin:0, ...) with
   real classes, same reasoning as .booking-section: consistent tokens, no
   drift between two forms that are visually the same kind of thing. */
.filter-form {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--space-4);
  margin-block-end: var(--space-6);
}

.filter-form__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.filter-form__field label {
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-small);
}

.filter-form__field--action {
  padding-block-end: 1px; /* optical alignment with the adjacent select/input's bottom edge */
}

.filter-form__search-input {
  min-width: 20rem;
}

/* Skip link (REQ-1.2.1-A11Y) */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 1000;
  padding: var(--space-2) var(--space-4);
  background: var(--color-primary);
  color: #fff;
}
.skip-link:focus {
  left: var(--space-4);
  top: var(--space-4);
}

/* Screen-reader-only text — referenced by _partials/lang-switch.twig since
   it was first written, but never actually defined until now, so
   "Sprache:" has just been rendering as ordinary visible text by
   accident rather than being hidden-but-announced as its class name always
   implied. Defining it properly here also completes that latent fix. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Visible focus ring everywhere (REQ-1.2.1-A11Y) */
:focus-visible {
  outline: 3px solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* Header / navigation — strong primary-color band per docs/design-system.md
   (matches the live NNL-family reference site: colored nav band with white
   type, not a neutral surface tint). */
/* Sticky nav bar — deliberately plain CSS `position: sticky`, not a
   scroll-listener toggling `position: fixed` (the live NNL-family reference
   site does exactly that via Bootstrap's old `.affix` plugin, which is the
   root cause the user flagged as making scrolling "broken and brittle"
   there: a JS-driven layout swap mid-scroll causes reflow/jump. `sticky`
   needs no JS and never reflows — same convenience, without that bug
   class).
   The alert banner (REQ-1.3.2-ALERT) is a SIBLING before this element in
   the DOM (see _layout.twig), not nested inside it — two reasons: (1) it
   scrolls away normally instead of permanently eating viewport height, and
   (2) `position: sticky` only stays stuck while scrolled within its OWN
   parent's box. Nesting the alert banner inside .site-header would make
   that parent's box only ~header-height tall, so the sticky bar would lose
   its anchor and scroll away the moment the (short) header scrolled past —
   the exact same class of scroll brittleness we're avoiding, just from a
   different cause. .site-header's parent is <body>, which is always at
   least as tall as the page, so sticking works for the full scroll. */
/* Gradient direction is vertical (180deg), not diagonal — deliberately.
   The wave() dipping out of the header's BOTTOM edge is a flat solid
   color, so a diagonal gradient (varying left-to-right) would only ever
   match the wave on one side, leaving a visible seam on the other (caught
   on the Gohrischheide/duenen header). Vertical keeps the color uniform
   across the full width at any given y, so the wave — which uses this same
   --header-gradient-end, see _headerColorVar() in _macros.twig — matches
   the header's bottom edge everywhere.

   Both stops are pre-darkened toward black (75%/50% mix), not the raw CD
   token, so text can always be white here, overriding the CD's own fixed
   black-foreground pairing for --nnl-beige-dark specifically: that raw tan
   only clears ~3.4:1 against white, under the 4.5:1 minimum for normal
   text. A 75% mix brings even that worst case to ~5.5:1 (checked with a
   WCAG relative-luminance calculation, not eyeballed); khaki and the
   primary green already exceed 6:1 unmixed, so darkening them further is
   pure headroom, not a fix they needed. */
/* NO box-shadow on this rule. It has been added and removed from here more
   than once — a sticky colored band doesn't need a drop shadow to read as
   "above" the content, and per the CD/directive header guidance this stays
   flat. If you're about to add one, don't. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 3;
  --header-gradient-start: color-mix(in srgb, var(--color-primary) 75%, black);
  --header-gradient-end: color-mix(in srgb, var(--color-primary) 50%, black);
  background: linear-gradient(180deg, var(--header-gradient-start) 0%, var(--header-gradient-end) 100%);
  color: #fff;
  /* Sticky bar needs to visibly separate from the page once content
     scrolls under it — a flat edge against a white page read as the
     header just stopping rather than floating above the content. */
}

/* Per-SECTION header band (2026-09-29) — one color per primary-nav item
   (see _partials/nav.twig), not per protected area as this used to be
   (that was .site-header--heide/--duenen, keyed to which of the 2 areas a
   page was about). Area identity is already carried by photos/titles/
   breadcrumbs on every page; the header's job is orienting within the
   SITE STRUCTURE, so it now tracks which nav section a page belongs to —
   e.g. every trail/viewpoint/map page ("Besuchen & Erleben") reads as the
   same brown regardless of which area it happens to be about, instead of
   varying page-to-page in a way that had nothing to do with navigation.
   'forest' (Entdecken & Informieren / home) is just --color-primary
   itself, so no override needed below. Set via the page-level
   `headerTopic` variable (top-level `set`, read by _layout.twig). */
.site-header--visit {
  --header-gradient-start: color-mix(in srgb, var(--nnl-beige-dark) 75%, black);
  --header-gradient-end: color-mix(in srgb, var(--nnl-beige-dark) 50%, black);
  background: linear-gradient(180deg, var(--header-gradient-start) 0%, var(--header-gradient-end) 100%);
  color: #fff;
}

.site-header--learn {
  --header-gradient-start: color-mix(in srgb, var(--nnl-spot-blue) 75%, black);
  --header-gradient-end: color-mix(in srgb, var(--nnl-spot-blue) 50%, black);
  background: linear-gradient(180deg, var(--header-gradient-start) 0%, var(--header-gradient-end) 100%);
  color: #fff;
}

.site-header--participate {
  --header-gradient-start: color-mix(in srgb, var(--nnl-spot-orange) 75%, black);
  --header-gradient-end: color-mix(in srgb, var(--nnl-spot-orange) 50%, black);
  background: linear-gradient(180deg, var(--header-gradient-start) 0%, var(--header-gradient-end) 100%);
  color: #fff;
}

.site-header--book {
  --header-gradient-start: color-mix(in srgb, var(--nnl-spot-violet) 75%, black);
  --header-gradient-end: color-mix(in srgb, var(--nnl-spot-violet) 50%, black);
  background: linear-gradient(180deg, var(--header-gradient-start) 0%, var(--header-gradient-end) 100%);
  color: #fff;
}

/* Two rows: logo + language switch share the top row (switch pinned top
   right), nav gets its own full-width row below — previously all three
   were flex siblings on one wrapping row, which pushed the language
   switch down below the (wide, 7-item) nav once it wrapped, instead of
   keeping it pinned to the top-right corner. */
.site-header__bar {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-block: var(--space-4);
}

.site-header__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.site-header__logo {
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-h4);
  color: inherit;
  text-decoration: none;
}

.site-nav ul {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  margin: 0;
  padding: 0;
}

.site-nav a {
  color: inherit;
  text-decoration: none;
  font-weight: var(--font-weight-bold);
  padding: var(--space-2) 0;
  display: inline-block;
  border-bottom: 2px solid transparent;
  transition: border-color var(--transition-base), opacity var(--transition-fast);
}

.site-nav a:hover,
.site-nav a:focus-visible {
  border-bottom-color: currentColor;
}

.site-nav a:hover {
  opacity: 0.85;
}

/* Click-to-toggle sub-menu (see _partials/nav.twig for why: reproduces the
   live NNL-family reference site's per-item sub-topic menu without its
   hover-triggered white-on-white contrast bug or its flakiness on touch. */
.site-nav__item {
  position: relative;
}

.site-nav__toggle {
  background: none;
  border: none;
  /* Opts out of the global `button` rule's shadow (components.css top) —
     this button is meant to blend flush into the header, and a shadow
     drawn around its (invisible) background box showed up as a stray dark
     smudge/outline behind the nav label on the green header. */
  border-radius: 0!important;
  box-shadow: none;
  color: inherit;
  font: inherit;
  font-weight: var(--font-weight-bold);
  padding: var(--space-2) 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  border-bottom: 2px solid transparent;
  cursor: pointer;
  transition: border-color var(--transition-base), opacity var(--transition-fast);
}

.site-nav__toggle:hover {
  opacity: 0.85;
}

.site-nav__toggle:hover,
.site-nav__toggle:focus-visible,
.site-nav__toggle[aria-expanded="true"] {
  border-bottom-color: currentColor;
}

.site-nav__caret {
  display: inline-block;
  transition: transform var(--transition-base);
}

.site-nav__caret::before {
  content: "▾";
  font-size: 0.7em;
}

.site-nav__toggle[aria-expanded="true"] .site-nav__caret {
  transform: rotate(180deg);
}

/* Deliberately a fixed opaque light surface with dark text, independent of
   the current (per-topic) header color — this is what prevents the
   reference site's white-text-on-white-background bug from being able to
   recur here at all, rather than just avoiding it by coincidence.
   Styling takes a cue from the live NNL-family reference site's own
   sub-menu (a flush panel bridging straight off the active nav item, dark
   green text rather than plain black) without copying its markup/assets —
   still opaque white, still a fixed panel, just not visually disconnected
   from the nav bar above it the way a generic dropdown card would be. */
/* `.site-nav ul { display: flex }` above has specificity (0,1,1) — higher
   than both the UA default `[hidden] { display: none }` (0,1,0) AND a bare
   `.site-nav__submenu { display: none }` (0,1,0), so either would silently
   lose to the flex rule. Scope through the parent (0,2,0) to unambiguously
   outrank it instead of relying on source order. */
.site-nav .site-nav__submenu {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 4;
  min-width: 14rem;
  margin: 0;
  padding: var(--space-2) 0;
  list-style: none;
  background: var(--color-background);
  border: 1px solid var(--color-border);
  border-radius: 0 var(--radius-md) var(--radius-md) var(--radius-md);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity var(--transition-base), transform var(--transition-base);
}

.site-nav .site-nav__submenu:not([hidden]) {
  display: block;
}

/* Toggled alongside removing [hidden] (see _partials/nav.twig) — a class
   is needed rather than animating off [hidden] directly, since a browser
   won't transition properties that change in the same frame as
   display:none -> display:block. */
.site-nav .site-nav__submenu.site-nav__submenu--visible {
  opacity: 1;
  transform: translateY(0);
}

/* Hover was var(--color-surface) (--nnl-beige-light) + an orange accent
   bar — a flat tan block that read as cheap, especially clashing against
   the green submenu text. Swapped for a tint of the submenu's own accent
   color (a faint green wash, not a new hue) plus a rounded "floating row"
   treatment instead of a full-bleed color block. */
.site-nav__submenu a {
  display: block;
  margin-inline: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  /* Square off the side the colored accent border sits on (border-inline-
     start below) — a rounded corner behind a straight colored bar left a
     visible sliver of mismatched background peeking out at the top/bottom
     of the bar. Applies sitewide to every colored-left-border element,
     not just this one. */
  border-start-start-radius: 0;
  border-end-start-radius: 0;
  /* Was var(--color-primary) (green) — read as an odd accent on plain nav
     labels that aren't links-within-content. Neutral dark-gray text now;
     the background tint + left border below still carry the interactive
     color cue on hover/focus, so the state feedback isn't lost. */
  color: var(--color-text);
  font-family: inherit;
  font-weight: var(--font-weight-bold);
  text-decoration: none;
  border-bottom: none;
  border-inline-start: 3px solid transparent;
  transition: background-color var(--transition-fast), border-color var(--transition-fast), padding-inline-start var(--transition-fast), color var(--transition-fast);
}

.site-nav__submenu a:hover,
.site-nav__submenu a:focus-visible {
  background: color-mix(in srgb, var(--color-primary) 12%, white);
  border-inline-start-color: var(--color-primary);
  color: var(--color-text);
  padding-inline-start: calc(var(--space-4) + var(--space-2));
}

/* Header-top search (_layout.twig, between the title and language switch)
   — expands INLINE next to its toggle button, not as a dropdown panel
   below it (a panel looked disconnected floating under the nav and
   duplicated /suche's own form when already on that page). */
.site-header__search {
  position: relative;
  display: flex;
  align-items: center;
}

.nav-search {
  display: none;
  align-items: center;
  gap: var(--space-2);
  margin-inline-start: var(--space-3);
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity var(--transition-base), transform var(--transition-base);
}

.nav-search:not([hidden]) {
  display: inline-flex;
}

.nav-search.site-nav__submenu--visible {
  opacity: 1;
  transform: translateY(0);
}

/* Sits translucent against the header until focused, rather than a flat
   opaque white pill that had no relationship to the surrounding header
   color at all. Text stays white while translucent (matches the nav
   itself) and switches to normal dark text once focused/opaque, when
   there's an actual white surface behind it to read against. */
.nav-search__form {
  display: flex;
  align-items: center;
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 999px;
  padding: 3px;
  transition: background-color var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.nav-search__form:focus-within {
  background: rgba(255, 255, 255, 0.8);
  border-color: #fff;
  box-shadow: 0 0 0 3px var(--color-focus-ring), var(--shadow-md);
}

.nav-search__form input[type="search"] {
  width: 14rem;
  padding: var(--space-2) var(--space-3);
  border: none;
  border-radius: 0;
  background: transparent;
  font: inherit;
  color: #fff;
  transition: color var(--transition-fast);
}

/* The base form reset (components.css legacy-forms layer) draws its own
   focus-visible glow + 4px corners on every plain input — here that
   collided with this pill's OWN focus treatment (.nav-search__form:
   focus-within's border-color + box-shadow, below), stacking a second,
   misaligned rounded box inside the pill. This input's focus state is
   fully owned by the parent pill; the input itself opts out. */
.nav-search__form input[type="search"]:focus-visible {
  box-shadow: none;
}

.nav-search__form input[type="search"]::placeholder {
  color: rgba(255, 255, 255, 0.8);
}

.nav-search__form:focus-within input[type="search"] {
  color: var(--color-text);
}

.nav-search__form:focus-within input[type="search"]::placeholder {
  color: var(--color-text-muted);
}

.nav-search__form input[type="search"]:focus-visible {
  outline: none;
}

.nav-search__form button {
  padding: var(--space-2) var(--space-5);
  border: none;
  border-radius: 999px;
  background: var(--color-primary);
  color: var(--color-primary-fg);
  font-weight: var(--font-weight-bold);
  cursor: pointer;
  transition: background-color var(--transition-fast), transform var(--transition-fast);
  padding: 6px;
}

.nav-search__form button:hover,
.nav-search__form button:focus-visible {
  background: color-mix(in srgb, var(--color-primary) 80%, black);
  transform: translateY(-1px);
}

/* Suggestions still drop down as a floating panel (unlike the input
   itself, a list of results genuinely belongs below, not inline) —
   anchored to .site-header__search, not .nav-search, so it doesn't shift
   if the toggle button's own width changes. */
.site-header__search .nav-search__suggestions {
  display: block;
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 4;
  min-width: 22rem;
  max-width: 26rem;
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: var(--space-2) 0;
  background: var(--color-background);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

.nav-search__suggestions[hidden] {
  display: none;
}

.nav-search__suggestions li {
  border-bottom: 1px solid var(--color-border);
}

.nav-search__suggestions li:last-child {
  border-bottom: none;
}

/* Real horizontal padding (was 0 — text ran flush to the box edges) and
   the section label is its own line (was appended inline, so it wrapped
   awkwardly onto the title's last line with no visual separation). */
.nav-search__suggestions a {
  display: block;
  padding: var(--space-3) var(--space-4);
  color: var(--color-primary);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-heading);
  text-decoration: none;
  transition: background-color var(--transition-fast);
}

.nav-search__suggestions a:hover,
.nav-search__suggestions a:focus-visible {
  background: color-mix(in srgb, var(--color-primary) 8%, white);
}

.nav-search__suggestion-section {
  display: block;
  margin-block-start: 2px;
  color: var(--color-text-muted);
  font-weight: var(--font-weight-regular);
  font-size: var(--font-size-small);
}

.site-header__tools {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  font-size: var(--font-size-small);
}

.site-header__tools a {
  color: inherit;
}

.lang-switch a[aria-current="true"] {
  font-weight: var(--font-weight-bold);
  text-decoration: underline;
}

/* Header layout + hamburger.
   Desktop (>=768px): the menu wrappers are display:contents, so search,
   language switch and nav are direct flex items of .site-header__top as
   before; the nav takes its own full-width row. */
.site-header__menu,
.site-header__menu-inner { display: contents; }
.site-header__top { row-gap: var(--space-2); }
.site-header__top .site-nav { flex-basis: 100%; order: 3; }
.site-header__burger { display: none; }

/* Header shortcuts (search, map, report a problem): pill buttons with an
   icon, deliberately separate from the 4 main nav sections. */
.site-header__shortcuts {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
}

.shortcut,
.shortcut.site-nav__toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  color: #fff;
  font: inherit;
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-bold);
  text-decoration: none;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 999px !important;
  box-shadow: none;
  cursor: pointer;
  transition: background-color var(--transition-fast), border-color var(--transition-fast);
}

.shortcut:hover,
.shortcut:focus-visible,
.shortcut.site-nav__toggle:hover,
.shortcut.site-nav__toggle[aria-expanded="true"] {
  background: rgba(255, 255, 255, 0.22);
  border-color: rgba(255, 255, 255, 0.6);
}

.shortcut .icon { width: 18px; height: 18px; }

/* Desktop/laptop: opening the search must not change the header layout.
   The shortcut group stays on the title's row, so the search field takes the
   place of the Karte / Störung melden pills while it is open (they come back
   on close) instead of growing the group and pushing it onto a new line. */
@media (min-width: 768px) {
  .site-header__shortcuts { flex-wrap: nowrap; }
  .site-header__shortcuts:has(.nav-search:not([hidden])) > .shortcut { display: none; }
  /* Sized so Suche pill + field = the width of the three pills (~360px), and
     as tall as a pill (~39px), so the row doesn't move when it opens. */
  .nav-search { width: 15.25rem; flex: none; }
  .nav-search__form { width: 100%; }
  .nav-search__form input[type="search"] { flex: 1; width: auto; min-width: 0; padding: 5px var(--space-3); font-size: var(--font-size-small); }
  .nav-search__form button { flex: none; padding: 5px var(--space-4); font-size: var(--font-size-small); }
}

@media (max-width: 767.98px) {
  .site-header__bar { padding-block: var(--space-3); }
  .site-header__top { flex-wrap: wrap; align-items: flex-start; }
  .site-header__top > .site-header__logo { flex: 1; }


  .site-header__burger {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    flex: none;
    width: 44px;
    height: 44px;
    padding: 0 10px;
    background: none;
    border: none;
    border-radius: 0 !important;
    box-shadow: none;
    color: inherit;
    cursor: pointer;
  }
  .site-header__burger-bar {
    display: block;
    height: 2px;
    background: currentColor;
    transition: transform .25s ease, opacity .15s ease;
  }
  .site-header__burger[aria-expanded="true"] .site-header__burger-bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .site-header__burger[aria-expanded="true"] .site-header__burger-bar:nth-child(2) { opacity: 0; }
  .site-header__burger[aria-expanded="true"] .site-header__burger-bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

  /* Two-step open: (1) panel height grows, (2) items fade/slide in.
     Close runs in reverse: items fade out first, then the panel collapses.
     visibility flips after the collapse so closed links can't be tabbed. */
  .site-header__menu {
    display: grid;
    grid-template-rows: 0fr;
    flex-basis: 100%;
    order: 3;
    visibility: hidden;
    transition: grid-template-rows .3s ease .12s, visibility 0s linear .42s;
  }
  .site-header__burger[aria-expanded="true"] ~ .site-header__menu {
    grid-template-rows: 1fr;
    visibility: visible;
    transition: grid-template-rows .3s ease, visibility 0s;
  }
  .site-header__menu-inner {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    min-height: 0;
    overflow: hidden;
    opacity: 0;
    transform: translateY(-8px);
    transition: opacity .15s ease, transform .15s ease;
  }
  .site-header__burger[aria-expanded="true"] ~ .site-header__menu .site-header__menu-inner {
    opacity: 1;
    transform: none;
    /* Taller than the screen on short phones: scroll inside the panel. */
    max-height: calc(100dvh - 7rem);
    overflow-y: auto;
    transition: opacity .22s ease .2s, transform .22s ease .2s;
  }

  .site-header__top .site-nav { order: 0; flex-basis: auto; }
  .site-nav ul { flex-direction: column; flex-wrap: nowrap; gap: 0; }
  .site-nav a, .site-nav__toggle { padding-block: var(--space-3); }
  .site-header__shortcuts { order: -2; }
  .site-header__tools { order: -1; }
  /* Rows in the panel: [Karte] [Störung melden] first, then the search on
     its own full-width row (pill + field that expands to the right). */
  .site-header__shortcuts > a.shortcut { order: 1; }
  /* .nav-search drops out of the layout (display: contents) so its form and
     the suggestion list become rows of the search container: pill + field on
     the first row, suggestions full-width below. (As a flex row of its own
     the list sat beside the field and made the whole row tall.) */
  .site-header__search { order: 2; flex: 1 1 100%; flex-wrap: wrap; min-width: 0; }
  .nav-search:not([hidden]) { display: contents; }
  .site-header__search .nav-search__form { flex: 1 1 0; width: auto; min-width: 0; margin: 0 0 0 var(--space-2); }
  .site-header__search .nav-search__suggestions { flex: 1 1 100%; order: 3; margin-top: var(--space-2); }
  .shortcut, .shortcut.site-nav__toggle { min-height: 44px; }
  .nav-search__form input[type="search"] { flex: 1; width: auto; min-width: 0; }
  .nav-search__form button { flex: none; }

  /* Inline accordion instead of floating dropdowns (they'd be clipped by
     the collapsing panel's overflow:hidden). */
  .site-nav .site-nav__submenu,
  .site-header__search .nav-search__suggestions {
    position: static;
    min-width: 0;
    max-width: none;
    width: 100%;
    box-shadow: none;
    margin-block: var(--space-1) 0;
  }

  /* Translucent panel over the header color, white text at the nav's own
     size/weight (same contrast logic as the header: white on the darkened
     band). Overrides the desktop opaque-white card + dark text. */
  .site-nav .site-nav__submenu {
    background: rgba(255, 255, 255, 0.14);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    border: 1px solid rgba(255, 255, 255, 0.25);
    border-radius: var(--radius-md);
    padding: var(--space-1) 0;
  }
  .site-nav__submenu a {
    color: #fff;
    font-size: inherit;
    margin-inline: 0;
    padding: var(--space-3) var(--space-4);
  }
  .site-nav__submenu a:hover,
  .site-nav__submenu a:focus-visible {
    background: rgba(255, 255, 255, 0.16);
    border-inline-start-color: #fff;
    color: #fff;
    padding-inline-start: var(--space-4);
  }
}

@media (prefers-reduced-motion: reduce) {
  .site-header__menu, .site-header__menu-inner, .site-header__burger-bar { transition-duration: 0s !important; transition-delay: 0s !important; }
}

/* Click-to-load placeholder inside map containers (assets/js/map-gate.js):
   nothing third-party is requested until the visitor presses the button. */
.map-gate {
  height: 100%;
  min-height: 12rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  padding: var(--space-6);
  text-align: center;
  background: var(--color-surface);
}
.map-gate p { margin: 0; max-width: 36rem; }

/* Decorative wave divider (APPLICATION_DESIGN, not an NNL-mandated shape —
   see docs/design-system.md §6/§9: the manual's own dialog-element vectors
   are protected brand assets we deliberately don't reproduce. This is our
   own organic transition between colored bands and photography, inspired
   by the general shape language visible in the tender sitemap mockup and
   the live NNL-family reference site, not a copy of either.)
   Deliberately bold: it's meant to visibly overlap/eat into the photo
   behind it, not sit as a thin trim line — see .hero__media below.
   Heights/breakpoint (50px / 115px at 992px) are our own path scaled to
   match the real proportions measured from the live NNL-family reference
   site's equivalent element — not its actual asset, see docs/design-system.md. */
.wave-divider {
  display: block;
  width: 100%;
  height: 50px;
}

@media (min-width: 992px) {
  .wave-divider { height: 115px; }
}

/* Warning / alert banner (REQ-1.3.2-ALERT) — never relies on color alone */
.alert-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-left: 6px solid var(--color-alert-warning);
  background: color-mix(in srgb, var(--color-alert-warning) 10%, var(--color-background));
}

.alert-banner[data-severity="information"] { border-left-color: var(--color-alert-information); }
.alert-banner[data-severity="warning"] { border-left-color: var(--color-alert-warning); }
.alert-banner[data-severity="critical"] { border-left-color: var(--color-alert-critical); }

.alert-banner__label {
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  font-size: var(--font-size-small);
  white-space: nowrap;
}

.alert-banner__dismiss {
  margin-inline-start: auto;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-2);
  font-size: var(--font-size-h4);
  line-height: 1;
  color: inherit;
  cursor: pointer;
  transition: transform var(--transition-base), background-color var(--transition-fast);
}

.alert-banner__dismiss:hover,
.alert-banner__dismiss:focus-visible {
  transform: rotate(90deg);
  background: color-mix(in srgb, currentColor 12%, transparent);
}

.alert-banner a {
  color: inherit;
  font-weight: var(--font-weight-bold);
}

/* Hero — full-bleed photography per docs/design-system.md §5 ("few large
   images, not many small ones"), wave-cut into the white page content below.
   Heading/body text deliberately sits BELOW the photo, never overlaid on it
   — the CD manual explicitly forbids normal text directly on photographs. */
/* The caption/intro block below .hero__media (index.twig, _macros.twig's
   areaHero()) used to be its own `.container` — but the hero always sits
   inside <main class="container">, so that was `.container` nested inside
   `.container`, doubling both the inline padding AND the top/bottom
   padding for hero captions/headlines specifically (found live: the
   homepage's H1 sat visibly further right than the card grids below it,
   which only ever sit in the one outer container). `.hero__content` is
   deliberately unstyled — <main> already provides the width constraint and
   gutter; this class exists only as a semantic wrapper, not a second
   layout primitive. */
.hero__media {
  position: relative;
  line-height: 0;
  /* Was --space-8 (2rem) — left a wide gap between the photo/wave and the
     credit/wave-note captions below, which then read as their own small
     "section" competing with the headline instead of sitting tucked right
     against the image they caption. */
  margin-block-end: var(--space-2);
  /* Break out of the surrounding .container to full viewport width, since
     <main> stays a centered container for every other page/section. */
  width: 100vw;
  margin-inline-start: calc(50% - 50vw);
  /* .container's padding-top (layout.css) left a visible white gap between
     the sticky header and the hero photo/wave — found live, an event
     detail page's header band was visibly separated from its hero by a
     thin white strip. The horizontal full-bleed trick above only cancels
     .container's INLINE padding; the hero needs its own pull-up for the
     BLOCK-start padding so it still sits flush under the header. */
  margin-block-start: calc(-1 * var(--space-4) - 1px);
}

.hero__image {
  display: block;
  width: 100%;
  height: min(60vw, 560px);
  min-height: 260px;
  object-fit: cover;
}

@media (min-width: 992px) {
  .hero__image { min-height: 420px; }
}

/* Compact variant for secondary pages (e.g. area detail) — same wave
   treatment, shorter photo since it's not the homepage's main hero. */
.hero--compact .hero__image {
  height: min(45vw, 480px);
  min-height: 220px;
}

@media (min-width: 992px) {
  .hero--compact .hero__image { min-height: 320px; }
}

/* Both waves overlap the photo itself (not sit beside it), so the image
   shows through their valleys — matching the live reference's look of the
   header color visibly dipping into the photo, and the photo dipping into
   the white content below. */
.hero__media .wave-divider {
  position: absolute;
  left: 0;
  width: 100%;
}

.wave-divider--top {
  /* -1px: tucks under the (higher z-index) sticky header so rounding at
     fractional DPR/scaling can't leave a white hairline at the seam. */
  top: -1px;
  transform: scaleY(-1); /* mirror so it reads as a downward dip from the header edge */
}

/* bottom: -2px, not 0 — on Windows/fractional display scaling, the
   100vw-based full-bleed hero (.hero__media, above) can round to a
   sub-pixel size that doesn't EXACTLY match this absolutely-positioned
   SVG's box, leaving a 1px sliver of the raw photo (dark near the ground/
   tree-line in this forest shot) exposed right at the bottom edge — found
   live as a thin dark line between the wave and the credit text below it.
   Overlapping the wave's (white) fill 2px past the image's true bottom
   edge closes that gap; harmless since it just overlaps the already-white
   page background beneath it. */
.wave-divider--bottom {
  bottom: -2px;
}

/* Pulled up tight under the photo/wave and de-emphasized further (smaller
   than --font-size-small, lower-contrast muted color) — these are a bare
   photo credit and a wave-provenance footnote, not content the page wants
   to draw attention to above its own headline. Zeroed browser default `p`
   margins (typography.css sets no global p reset) so the two lines stack
   tightly instead of each carrying its own ~1em top/bottom gap. */
.hero__credit,
.hero__wave-note {
  margin: 0;
  font-size: 0.75rem;
  line-height: 1.3;
  /* Was color-mix(..., 80%, transparent) — lightened it below the WCAG AA
     4.5:1 minimum for normal-size text (this is 12px, so the relaxed 3:1
     large-text threshold doesn't apply). Found by the axe-core suite after
     this ran across every route for the first time this session. Full-
     strength --color-text-muted is already ~8.8:1 against white, so
     dropping the opacity mix keeps this the same size/de-emphasis without
     the contrast regression. */
  color: var(--color-text-muted);
  text-align: right;
}

.hero__credit {
  margin-block-start: var(--space-1);
}

.hero__intro {
  margin-block: var(--space-8) var(--space-12);
}

/* Mobile: more air between the photo credit/wave note and whatever follows
   the hero (page h1, etc.) — they sat nearly touching on narrow screens. */
@media (max-width: 767.98px) {
  .hero { margin-block-end: var(--space-8); }
}

.hero__intro h1 {
  font-size: var(--font-size-h1);
  font-weight: var(--font-weight-heavy);
  line-height: var(--line-height-heading);
  max-width: 40ch;
  color: var(--color-text);
}

.hero__intro p {
  max-width: 60ch;
  font-size: var(--font-size-h4);
  color: var(--color-text);
}

/* Teaser / content card grid */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-6);
  padding-block: var(--space-8);
}

.content-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-background);
  /* A resting shadow, not just a border, is what makes this read as a
     raised card instead of a flat outlined box — the hover state already
     lifted further (--shadow-lg), but at rest it had nothing, so the whole
     grid looked flat until you touched it. */
  box-shadow: var(--shadow-sm);
  overflow: hidden; /* clip the image to the card's rounded corners */
  transition: transform var(--transition-base), box-shadow var(--transition-base), border-color var(--transition-base);
}

.content-card:hover,
.content-card:focus-within {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.content-card__media {
  position: relative;
  overflow: hidden;
}

.content-card__image {
  transition: transform var(--transition-slow);
}

.content-card:hover .content-card__image {
  transform: scale(1.0);
}

.content-card__image {
  display: block;
  width: 100%;
  height: 180px;
  object-fit: cover;
}

/* Category kicker pinned over the image corner (card(item) badge param) —
   topic colors match the per-page header bands 1:1 (see _headerColorVar()
   in _macros.twig) so a badge never introduces a color the rest of the
   page isn't already using. */
.content-card__badge {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  padding: var(--space-1) var(--space-3);
  border-radius: 999px;
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-bold);
  color: #fff;
  background: var(--color-primary);
  box-shadow: var(--shadow-sm);
}

.content-card__badge--heide { background: var(--nnl-khaki); }
/* --nnl-beige-dark's own CD-mandated fixed foreground is BLACK
   (--nnl-beige-dark-fg in colors.css), not white — the shared
   .content-card__badge rule above hardcodes white for every variant,
   which only happens to be safe for --heide (khaki's fixed fg IS white).
   axe-core caught the white-on-beige-dark case at a failing ratio. */
.content-card__badge--duenen { background: var(--nnl-beige-dark); color: var(--nnl-beige-dark-fg); }

.content-card__body {
  padding: var(--space-6);
}

/* Card titles can be rendered as h2 OR h3 (card()'s headingLevel param, to
   match whatever heading the call site nests under) — this used to only
   cover h3, so any card grid using headingLevel=2 (e.g. stubs/mitmachen.twig,
   stubs/buchen.twig) rendered completely unstyled browser-default titles.
   Deliberately its own compact sizing, not the generic .h2/.h3 page-heading
   recipe (frontend/tailwind/input.css) — those carry large section-spacing
   margins that would blow out a card's tight body padding. */
.content-card h2,
.content-card h3 {
  font-size: var(--font-size-h3);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-heading);
  color: var(--color-text);
  margin-block-end: var(--space-2);
}

.content-card__title-link {
  color: inherit;
  text-decoration: none;
}

.content-card__title-link:hover,
.content-card__title-link:focus-visible {
  color: var(--color-primary);
  text-decoration: underline;
}

/* Icon-prefixed fact row (card() macro's item.meta — trail distance/
   duration) — same icon() set as the area/event facts() tiles, so a
   route/clock icon reads the same everywhere on the site. */
.content-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  color: var(--color-text-muted);
}

.content-card__meta-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.content-card__meta-item .icon {
  color: var(--color-primary);
}

.content-card a.card-link {
  color: var(--color-primary);
  font-weight: var(--font-weight-bold);
  text-decoration: none;
  display: inline-block;
  transition: transform var(--transition-fast);
}

.content-card a.card-link:hover,
.content-card a.card-link:focus-visible {
  text-decoration: underline;
  transform: translateX(4px);
}

/* Facts (e.g. area size, established year) — a row of colored stat tiles,
   not a plain bordered text list, so a detail page's key numbers actually
   read as highlights instead of blending into the surrounding paragraphs. */
.facts {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-block: var(--space-8);
  padding: 0;
  border: none;
}

.facts__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-6);
  background: color-mix(in srgb, var(--color-primary) 8%, var(--color-background));
  border-left: 4px solid var(--color-primary);
  border-radius: var(--radius-md);
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}

.facts__icon {
  display: inline-flex;
  color: var(--color-primary);
}

.facts__icon .icon {
  width: 28px;
  height: 28px;
}

.facts__label {
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
}

.facts__value {
  margin: 0;
  font-size: var(--font-size-h3);
  font-weight: var(--font-weight-bold);
  color: color-mix(in srgb, var(--color-primary) 70%, black);
}

.icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}

/* Mobile: stack the stat cards at one shared full width (they used to
   shrink-wrap their content, so each had a different width/left edge) and
   let long labels wrap instead of pushing the page wider than the screen. */
@media (max-width: 767.98px) {
  .facts { flex-direction: column; align-items: stretch; }
  .facts__item { width: 100%; box-sizing: border-box; min-width: 0; padding: var(--space-4); }
  .facts__item > * { min-width: 0; }
  .facts__label, .facts__value { overflow-wrap: anywhere; }
  .facts--feature .facts__item { padding: var(--space-4) var(--space-5); }
}

/* Homepage stat band — same .facts markup/macro as the compact area/event
   detail-page version above, just a bigger, full-bleed, more decorative
   presentation for the one place (homepage, right under the hero) where
   these three counts are the featured content, not a supporting aside.
   Full-bleed via the same width:100vw/negative-margin trick as
   .hero__media, with padding-inline recentering the actual stat row back
   to the page's normal content width so the tinted BAND spans edge to
   edge while the numbers/icons still align with everything above/below. */
.facts--feature {
  width: 100vw;
  margin-inline-start: calc(50% - 50vw);
  padding-inline: max(var(--content-padding-inline), calc((100vw - var(--content-max-width)) / 2 + var(--content-padding-inline)));
  padding-block: var(--space-8);
  margin-block: var(--space-4) var(--space-12);
  gap: var(--space-6);
  justify-content: center;
  background: linear-gradient(135deg, color-mix(in srgb, var(--color-primary) 10%, var(--color-background)) 0%, color-mix(in srgb, var(--color-primary) 4%, var(--color-background)) 100%);
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

.facts--feature .facts__item {
  padding: var(--space-6) var(--space-8);
  gap: var(--space-4);
  background: var(--color-background);
  border-left-width: 5px;
  border-radius: var(--radius-lg);
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  box-shadow: var(--shadow-sm);
  transition: transform var(--transition-base), box-shadow var(--transition-base);
  /* Fades/rises in on load, staggered per item below — a purely cosmetic
     entrance, not information conveyed by motion (REQ-1.2.1-A11Y), and
     already covered by the sitewide prefers-reduced-motion override in
     layout.css which forces near-zero duration regardless. */
  animation: facts-feature-in var(--transition-slow) ease-out backwards;
}

.facts--feature .facts__item:nth-child(1) { animation-delay: 0ms; }
.facts--feature .facts__item:nth-child(2) { animation-delay: 80ms; }
.facts--feature .facts__item:nth-child(3) { animation-delay: 160ms; }

@keyframes facts-feature-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

@media (hover: hover) and (pointer: fine) {
  .facts--feature .facts__item:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow-md);
  }
}

.facts--feature .facts__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  flex-shrink: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--color-primary) 14%, transparent);
}

.facts--feature .facts__icon .icon {
  width: 28px;
  height: 28px;
}

.facts--feature .facts__label {
  font-size: var(--font-size-small);
}

.facts--feature .facts__value {
  font-size: clamp(2rem, 4vw, 2.75rem);
  line-height: 1.1;
}

/* Breadcrumb */
.breadcrumb {
  font-size: var(--font-size-small);
  padding-block: var(--space-4);
}

.breadcrumb ol {
  list-style: none;
  display: flex;
  gap: var(--space-2);
  padding: 0;
  margin: 0;
  flex-wrap: wrap;
}

.breadcrumb li:not(:last-child)::after {
  content: "›";
  margin-inline-start: var(--space-2);
  color: var(--color-text-muted);
}

/* /karte's own trail/closure lists — each item pairs a label with one or
   two actions ("Auf Karte zeigen" re-frames the map in place; "Details
   ansehen" navigates). Plain <ul> bullets read oddly once a row also has
   inline buttons, so this drops the marker and lays each row out as a
   label + action cluster instead. */
.map-list {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.map-list__item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}

.map-list__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  flex-shrink: 0;
}

.map-list__focus-btn,
.map-list__actions .btn-secondary {
  padding: var(--space-1) var(--space-4);
  font-size: var(--font-size-small);
}

/* Map / list toggle (REQ-1.2.1-A11Y, REQ-1.3.4-MAP) */
.map-list-toggle {
  display: inline-flex;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  margin-block-end: var(--space-4);
}

.map-list-toggle button {
  border: none;
  /* Segmented-control look (two buttons flush together inside one bordered
     group) — an individual shadow per button would draw a seam between
     them instead of reading as one joined control. */
  box-shadow: none;
  background: var(--color-surface);
  color: var(--color-text);
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
  font: inherit;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.map-list-toggle button[aria-pressed="true"] {
  background: var(--color-primary);
  color: #fff;
}

/* Closing call-to-action band (homepage) — a plain text link at the bottom
   of the page read as trailing off rather than closing on a clear "what to
   do next", so this gets the same tinted-surface + border-left treatment
   as .facts__item (reusing an existing pattern, not introducing a new
   "hero band" component just for this one section). */
.cta-band {
  margin-block: var(--space-12);
  padding: var(--space-8);
  background: color-mix(in srgb, var(--color-primary) 6%, var(--color-background));
  border-left: 4px solid var(--color-primary);
  border-radius: var(--radius-md);
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}

.cta-band .h2 {
  margin-top: 0;
}

@media (max-width: 767.98px) {
  .cta-band { padding: var(--space-5) var(--space-5); margin-block: var(--space-8); }
  /* Wide status table scrolls inside itself instead of widening the page. */
  .proof-table { display: block; overflow-x: auto; }
}

/* Footer — colored band matching the header, per the live NNL-family
   reference site (dark band top and bottom, white content between). */
.site-footer {
  background: linear-gradient(135deg, var(--color-primary) 0%, color-mix(in srgb, var(--color-primary) 72%, black) 100%);
  color: var(--color-primary-fg);
  padding-block: var(--space-8);
  margin-block-start: var(--space-16);
  font-size: var(--font-size-small);
}

.site-footer a {
  color: var(--color-primary-fg);
}

/* Event booking section (events/_event.twig) — was a stack of ad-hoc
   inline style="" attributes; moved to real classes so it picks up the
   same tokens/transitions as everything else instead of drifting. */
.booking-section {
  margin-block-start: var(--space-8);
  padding-block-start: var(--space-8);
  border-top: 1px solid var(--color-border);
}

.booking-note {
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
}

.booking-confirmation {
  border-left: 4px solid var(--color-primary);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
}

.booking-alert {
  border-left: 4px solid var(--color-alert-critical);
  padding: var(--space-3) var(--space-4);
}

.booking-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 26rem;
  margin-block-start: var(--space-4);
}

.booking-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.booking-field--narrow {
  max-width: 8rem;
}

.booking-field label {
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-small);
}

.booking-field input {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font: inherit;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.booking-field input:focus-visible {
  border-color: var(--color-primary);
}

.booking-field__error {
  display: block;
  color: var(--color-alert-critical);
  font-size: var(--font-size-small);
}

/* Embedded preview map (trail/viewpoint detail pages — _partials/mini-map.twig).
   Fixed height, not full-bleed: it's a supporting preview next to the
   accessible list on /karte, never the only way to get this information
   (REQ-1.2.1-A11Y, ADR-0003). */
.mini-map {
  margin-block: var(--space-4);
}

.mini-map__canvas {
  height: 320px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.mini-map__link {
  font-size: var(--font-size-small);
  margin-block-start: var(--space-2);
}

/* Entity-specific in-body photo (_macros.twig inlinePhoto()) — smaller and
   framed, unlike the full-bleed top hero, since it sits inside the
   narrow article column rather than breaking out of it. */
.inline-photo {
  margin: var(--space-6) 0;
}

.inline-photo__image {
  display: block;
  width: 100%;
  max-height: 420px;
  object-fit: cover;
  border-radius: var(--radius-md);
}

.inline-photo__credit {
  font-size: var(--font-size-small);
  color: var(--color-text-muted);
  text-align: right;
  margin-block-start: var(--space-1);
}

/* /proof requirement-coverage table — found live: a completely bare
   <table>, zero CSS targeting table/th/td anywhere on the site, columns
   running together with no visual gap at all ("V WARTET AUF EXTERNE
   INFO" glued straight onto the previous cell). Worth a real fix: this is
   the tender's own bid-evidence page (requirements.yaml → /proof), not a
   throwaway debug view. */
.proof-table {
  width: 100%;
  border-collapse: collapse;
  margin-block: var(--space-6);
  font-size: var(--font-size-small);
}

.proof-table th,
.proof-table td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  border-bottom: 1px solid var(--color-border);
  vertical-align: top;
}

.proof-table thead th {
  background: var(--color-surface);
  font-weight: var(--font-weight-bold);
  border-bottom: 2px solid var(--color-border);
}

.proof-table tbody tr:hover {
  background: color-mix(in srgb, var(--color-primary) 4%, transparent);
}

/* Status pill — color communicates progress at a glance, but never ALONE
   (the label text is always present too, REQ-1.2.1-A11Y color-alone rule).
   verified/poc = strongest signal (dark green); functional_stub = the
   common "built but not fully proven" middle state (blue); planned/
   pending_external_information = not yet buildable (amber, matches the
   site's existing alert-warning color); visual_stub = weakest/placeholder
   (also amber, same "not really there yet" signal as pending). */
.proof-status {
  display: inline-block;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-small);
  font-weight: var(--font-weight-bold);
  white-space: nowrap;
  color: #000; /* Pure black text guarantees sufficient contrast against all light tinted backgrounds. */
}

.proof-status--verified,
.proof-status--poc {
  background: color-mix(in srgb, var(--nnl-spot-dark-green) 16%, white);
}

.proof-status--functional_stub {
  background: color-mix(in srgb, var(--nnl-spot-blue) 14%, white);
}

.proof-status--planned,
.proof-status--pending_external_information,
.proof-status--visual_stub {
  background: color-mix(in srgb, var(--color-alert-warning) 20%, white);
}
