/*
 * Geidorfstandl — Customer-Facing Chalkboard Theme
 *
 * Warm, analogue aesthetic evoking a real classroom blackboard.
 * All colour, spacing, font-size, and timing values reference tokens.css.
 *
 * Rules:
 *   - ZERO raw colour/spacing/font-size/timing values — everything via var()
 *   - Mobile-first: base styles are mobile; breakpoints ADD rules only
 *   - No !important
 *   - Component blocks prefixed with: /* Component: name * /
 *
 * Loaded on customer pages ONLY (never on admin pages).
 *   1. tokens.css   — custom properties defined on :root
 *   2. customer.css — this file; references token values via var()
 *
 *   tokens.css (loaded first) defines all custom properties on :root outside
 *   any @layer; those values are therefore visible to every layer declared here.
 */

@layer tokens, base, components;

/* ═══════════════════════════════════════════════════════════════════════════
   LAYER: base — resets, body, layout, typography
   ═══════════════════════════════════════════════════════════════════════════ */

@layer base {

  /* ── Reset ─────────────────────────────────────────────────────────────── */

  *,
  *::before,
  *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }

  /* ── Root scroll geometry ──────────────────────────────────────────────── */

  /* The sticky tab bar (components layer) overlays the viewport top once
     pinned. Root scroll-padding compensates EVERY scroll-into-view operation
     — no-JS :target jumps, keyboard focus, scrollIntoView — not just panel
     targets. Budget: 44px touch-min tabs + 4px list top padding + 1px border,
     with breathing room sized to also absorb the taller line boxes of 200%
     text-only zoom (WCAG 1.4.4), where the bar outgrows its 100% height.
     board.js reads this value for its snap-back so JS and no-JS navigation
     land at the same position. */
  html {
    scroll-padding-top: calc(var(--size-touch-min) + var(--space-xs) + var(--space-md));
  }

  /* ── Body — chalkboard background with CSS texture ─────────────────────── */

  body {
    font-family: var(--font-system);
    font-size: var(--font-size-body);
    line-height: 1.6;
    color: var(--color-chalk);
    background-color: var(--color-board);
    background-image:
      radial-gradient(ellipse at 20% 50%, rgba(255, 255, 255, 0.03) 0%, transparent 70%),
      radial-gradient(ellipse at 80% 20%, rgba(255, 255, 255, 0.02) 0%, transparent 60%);
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }

  /* ── Content container ─────────────────────────────────────────────────── */

  /* The board: a framed slate sitting on the darker "wall" (body). The frame
     is painted with a real wood-grain photo tile (border-image; walnut on the
     chalkboard, oak on the clipboard via token remap) — the solid border
     underneath is the no-image fallback. border-image ignores border-radius,
     so corners are square by design. box-sizing: border-box (global reset)
     keeps the border inside max-width so it never overflows. */
  .customer-container {
    max-width: 880px;
    margin: var(--space-lg) auto;
    padding-inline: var(--space-md);
    padding-block: var(--space-md);
    background-color: var(--color-board);
    background-image:
      radial-gradient(ellipse at 28% 18%, var(--color-chalk-haze), transparent 60%),
      radial-gradient(ellipse at 76% 82%, var(--color-chalk-haze), transparent 55%);
    border: var(--frame-width) solid var(--color-frame);
    /* Slice a narrow 8% strip — squeezing it into the frame keeps the grain
       near its photographed scale instead of crushing it into speckle.
       repeat (NOT round): round rescales the tiles to fit the edge length, so
       the grain visibly stretched every time the container height changed on a
       tab switch; repeat keeps the grain at natural scale and just clips. */
    border-image: var(--wood-grain) 8% / var(--frame-width) repeat;
    box-shadow:
      0 0 0 1px var(--color-frame-edge),   /* crisp inner wood edge */
      var(--shadow-slate-inset),           /* slate depth toward the frame */
      var(--shadow-board);                 /* frame lifted off the wall */
  }

  /* ── Typography ────────────────────────────────────────────────────────── */

  h1, h2, h3 {
    font-family: var(--font-display);
    font-weight: 700;
    line-height: 1.2;
  }

  h1 {
    font-size: var(--font-size-display);
  }

  h2 {
    font-size: var(--font-size-heading);
  }

  h3 {
    font-size: var(--font-size-dish);
  }

  a {
    color: var(--color-accent);
    text-decoration: underline;
    text-underline-offset: 2px;
  }

  a:hover {
    opacity: 0.85;
  }

  a:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
  }

  /* ── Responsive ────────────────────────────────────────────────────────── */

  @media (min-width: 768px) {
    .customer-container {
      padding-inline: var(--space-lg);
    }
  }

  @media (min-width: 1024px) {
    .customer-container {
      padding-inline: var(--space-xl);
    }
  }

} /* end @layer base */

/* ═══════════════════════════════════════════════════════════════════════════
   LAYER: components — reusable UI pieces
   ═══════════════════════════════════════════════════════════════════════════ */

@layer components {

  /* Component: customer-header ─────────────────────────────────────────── */

  .customer-header {
    text-align: center;
    padding-top: var(--space-2xl);
    padding-bottom: var(--space-lg);
  }

  /* Component: customer-brand ──────────────────────────────────────────── */

  .customer-brand {
    font-family: var(--font-display);
    font-size: var(--font-size-display);
    font-weight: 700;
    color: var(--color-chalk);
    letter-spacing: 0.02em;
  }

  /* Component: section-heading ─────────────────────────────────────────── */

  .section-heading {
    font-family: var(--font-display);
    font-size: var(--font-size-heading);
    font-weight: 700;
    color: var(--color-chalk);
    margin-bottom: var(--space-md);
  }

  /* Hand-drawn chalk underline (Goal B): the wavy stroke is an SVG mask; the
     colour stays var(--color-accent). If mask is unsupported the ::after box
     degrades to a solid accent bar (≈ the previous straight underline). */
  .section-heading::after {
    content: "";
    display: block;
    width: 72px;
    height: 8px;
    margin-top: var(--space-sm);
    background: var(--color-accent);
    -webkit-mask: var(--squiggle) no-repeat left center / contain;
            mask: var(--squiggle) no-repeat left center / contain;
  }

  /* Component: chalk-bloom (Goal B) ────────────────────────────────────────
     A faint chalk haze on the display-font headings/dishes — kept tiny so text
     stays crisp. */
  .customer-brand,
  .section-heading,
  .menu-day__title,
  .stammkarte-group__heading,
  .menu-item__name {
    text-shadow: var(--shadow-chalk-text);
  }

  /* Component: section-divider ─────────────────────────────────────────── */

  .section-divider {
    height: 1px;
    border: none;
    background: linear-gradient(
      to right,
      transparent,
      var(--color-chalk-dim) 20%,
      var(--color-chalk-dim) 80%,
      transparent
    );
    margin-block: var(--space-xl);
  }

  /* Component: customer-main ───────────────────────────────────────────── */

  .customer-main {
    padding-bottom: var(--space-2xl);
  }

  /* Component: customer-banner (Epic 5) ─────────────────────────────── */

  .customer-banner:empty {
    display: none;
  }

  .customer-banner {
    padding-block: var(--space-md);
  }

  .customer-banner__message {
    background: var(--color-banner-bg);
    color: var(--color-accent);
    padding: var(--space-md);
    border-radius: var(--radius-md);
    font-family: var(--font-system);
    font-size: var(--font-size-body);
    overflow-wrap: break-word;
    margin-bottom: var(--space-sm);
  }

  /* Closure notice: bold status, light reason (both accent via the base rule). */
  .customer-banner__label {
    font-weight: 700;
  }

  .customer-banner__reason {
    font-weight: 400;
  }

  /* Admin announcement: highlighted, a touch heavier than body — not full bold. */
  .customer-banner__message--announcement {
    font-weight: 600;
  }

  /* Component: customer-status (Epic 5) ─────────────────────────────── */

  .customer-status:empty {
    display: none;
  }

  .customer-status {
    text-align: center;
    padding-block: var(--space-md);
  }

  /* Component: status-pill (Epic 5) ──────────────────────────────────── */

  .status-pill {
    display: inline-block;
    padding: var(--space-xs) var(--space-md);
    border-radius: var(--radius-full);
    font-family: var(--font-system);
    font-size: var(--font-size-body);
  }

  .status-pill--open {
    color: var(--color-accent);
    border: 1px solid var(--color-accent);
  }

  .status-pill--closed {
    color: var(--color-chalk-dim);
    border: 1px solid var(--color-chalk-dim);
  }

  /* Component: customer-permanent-items (Epic 4) ─────────────────────── */

  .customer-permanent-items {
    padding-bottom: var(--space-xl);
  }

  .customer-permanent-items:empty {
    display: none;
  }

  /* Component: stammkarte-group (Story 7.6) ───────────────────────────── */

  .stammkarte-group + .stammkarte-group {
    margin-top: var(--space-lg);
  }

  .stammkarte-group__heading {
    font-family: var(--font-display);
    font-size: 22px;                  /* between --font-size-dish (18) and --font-size-heading (24) */
    font-weight: 700;                 /* must outweigh the 700 dish names it groups */
    color: var(--color-chalk);
    margin-bottom: var(--space-sm);
    margin-top: 0;                    /* spacing lives on .stammkarte-group + .stammkarte-group */
    letter-spacing: 0.01em;
  }

  /* Component: customer-specials (Epic 4) ───────────────────────────── */

  .customer-specials {
    padding-bottom: var(--space-xl);
  }

  .customer-specials:empty {
    display: none;
  }

  /* Component: customer-map (Story 6.3) ───────────────────────────────── */

  .customer-map {
    padding-bottom: var(--space-xl);
  }

  .customer-map__iframe {
    width: 100%;
    height: 300px;
    border: 0;
    border-radius: var(--radius-md);
    background: var(--color-chalk-dim);
  }

  /* Component: customer-footer ─────────────────────────────────────────── */

  .customer-footer {
    text-align: center;
    padding-block: var(--space-xl);
    color: var(--color-chalk-dim);
    font-size: var(--font-size-body);
  }

  .customer-footer:empty {
    display: none;
  }

  /* Component: menu-section ─────────────────────────────────────────────── */

  .menu-section {
    margin-bottom: var(--space-xl);
  }

  /* Component: menu-day ───────────────────────────────────────────────── */

  .menu-day {
    margin-bottom: var(--space-lg);
  }

  .menu-day__title {
    font-family: var(--font-display);
    font-size: 22px;                  /* matches .stammkarte-group__heading — day labels must outweigh dish names */
    font-weight: 700;
    color: var(--color-chalk);
    margin-bottom: var(--space-sm);
    letter-spacing: 0.01em;
  }

  .menu-day--today {
    border-left: 3px solid var(--color-accent);
    padding-left: var(--space-md);
  }

  /* Days already past this week — dimmed so the eye lands on today + upcoming.
     They stay legible (still on the board), just visibly de-emphasised. */
  .menu-day--past {
    opacity: 0.45;
  }

  /* Small, dimmed date/meta sitting beside a larger chalk label — today's date
     after the weekday (Heute), each weekday's date, and the KW next to the
     Wochenkarte heading. Deliberately a step smaller than its label.
     (13px is admin-only fine print, so the customer scale stops at body/15px.) */
  .menu-date-suffix {
    font-size: var(--font-size-body);
    font-weight: 400;
    color: var(--color-chalk-dim);
    text-shadow: none;
    white-space: nowrap;
  }

  /* Component: menu-item ──────────────────────────────────────────────── */

  /* Two-column grid: the dish (name + description) takes a flexible first track
     that may shrink to zero — minmax(0, 1fr) is what lets a long name/description
     WRAP instead of pushing the price off the right edge — and the price sits in
     an auto track that always stays on the right. Full-width rows (sold-out badge)
     span both tracks below. */
  .menu-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--space-md);
    align-items: baseline;
    padding-block: var(--space-sm);
  }

  .menu-item__name-group {
    min-width: 0;
    display: flex;
    flex-direction: column;
  }

  .menu-item__name {
    font-family: var(--font-display);
    font-size: var(--font-size-dish);
    font-weight: 700;
    color: var(--color-chalk);
    overflow-wrap: anywhere;
  }

  .menu-item__price {
    font-family: var(--font-system);
    font-size: var(--font-size-price);
    color: var(--color-accent);
    white-space: nowrap;
    text-align: right;
  }

  .menu-item__description {
    font-family: var(--font-display);
    font-weight: 400;
    font-size: var(--font-size-body);
    color: var(--color-chalk-dim);
    line-height: 1.3;
    overflow-wrap: anywhere;
  }

  /* Component: allergen-list ──────────────────────────────────────────── */

  .allergen-list {
    font-family: var(--font-system);
    font-size: var(--font-size-body);
    color: var(--color-chalk-dim);
    white-space: nowrap;
  }

  /* Component: allergen-btn ───────────────────────────────────────────── */

  .allergen-btn {
    display: inline;
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    font-family: var(--font-system);
    font-size: var(--font-size-body);
    color: var(--color-chalk-dim);
    cursor: pointer;
    text-decoration: underline;
    text-decoration-style: dotted;
    text-underline-offset: 2px;
    vertical-align: baseline;
  }

  .allergen-btn:hover,
  .allergen-btn:focus {
    color: var(--color-chalk);
    outline: none;
  }

  .allergen-btn:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
    border-radius: 2px;
  }

  /* Component: allergen-popup ─────────────────────────────────────────── */

  .allergen-popup {
    position: absolute;
    z-index: 100;
    background: var(--color-board);
    color: var(--color-chalk);
    border: 1px solid var(--color-chalk-dim);
    border-radius: var(--radius-md);
    padding: var(--space-xs) var(--space-sm);
    font-family: var(--font-system);
    font-size: var(--font-size-body);
    white-space: nowrap;
    pointer-events: none;
    max-width: calc(100vw - 2 * var(--space-md));
  }

  /* Component: menu-item--sold-out ────────────────────────────────────── */

  .menu-item--sold-out .menu-item__name {
    color: var(--color-sold-out);
  }

  .menu-item--sold-out .menu-item__name del {
    text-decoration: line-through;
    text-decoration-color: var(--color-sold-out);
  }

  .menu-item--sold-out .menu-item__price {
    color: var(--color-sold-out);
  }

  .menu-item--sold-out .menu-item__description {
    color: var(--color-sold-out);
  }

  .menu-item--sold-out .allergen-list {
    color: var(--color-sold-out);
  }

  .menu-item--sold-out .allergen-btn {
    color: var(--color-sold-out);
  }

  /* Component: sold-out-badge ─────────────────────────────────────────── */

  .sold-out-badge {
    font-family: var(--font-system);
    font-size: var(--font-size-body);
    color: var(--color-sold-out);
    width: 100%;
    grid-column: 1 / -1;   /* full-width row under the name + price */
  }

  /* Component: allergen-badges ────────────────────────────────────────── */

  .allergen-badges {
    font-family: var(--font-system);
    font-size: var(--font-size-body);
    color: var(--color-chalk-dim);
    width: 100%;
    grid-column: 1 / -1;   /* full-width row under the name + price */
  }

  /* Component: daily-note ─────────────────────────────────────────────── */

  .daily-note {
    font-family: var(--font-display);
    font-weight: 300;
    font-style: italic;
    font-size: var(--font-size-price);
    color: var(--color-chalk);
    margin-bottom: var(--space-sm);
  }

  /* Component: menu-timestamp ─────────────────────────────────────────── */

  .menu-timestamp {
    font-family: var(--font-system);
    font-size: var(--font-size-body);
    color: var(--color-chalk-dim);
    text-align: center;
    padding-top: var(--space-xl);
    padding-bottom: var(--space-md);
  }

  /* Component: stale-message ──────────────────────────────────────────── */

  .stale-message {
    font-family: var(--font-system);
    font-size: var(--font-size-body);
    color: var(--color-chalk-dim);
    text-align: center;
    padding-block: var(--space-3xl);
  }

  /* Component: empty-state ─────────────────────────────────────────────── */

  .empty-state {
    text-align: center;
    padding-block: var(--space-3xl);
    color: var(--color-chalk-dim);
  }

  .empty-state__text {
    font-family: var(--font-display);
    font-size: var(--font-size-dish);
    font-weight: 300;
    font-style: italic;
    line-height: 1.6;
  }

  /* Component: customer-hours (Epic 5) ──────────────────────────────── */

  .customer-hours {
    text-align: left;
    padding-block: var(--space-lg);
  }

  .hours-table {
    width: 100%;
    max-width: 480px;
    margin: 0;
    font-family: var(--font-system);
    font-size: var(--font-size-body);
  }

  /* Each day stacks as its own block — day label above its hours — so long day
     ranges ("Montag – Donnerstag") and split-shift times ("11:00 – 14:00 &
     17:00 – 21:00") get the full column width instead of fighting a right-aligned
     time cell and wrapping mid-range. Table semantics (th scope=row) are kept in
     the markup for screen readers; only the visual box model is unstacked. */
  .hours-table,
  .hours-table tbody,
  .hours-row,
  .hours-row > th,
  .hours-row > td {
    display: block;
  }

  .hours-row {
    padding-block: var(--space-sm);
  }

  .hours-row + .hours-row {
    border-top: 1px solid var(--color-chalk-dim);
  }

  .hours-row > th {
    text-align: left;
    font-weight: 600;
    color: var(--color-chalk);
  }

  .hours-row > td {
    margin-top: var(--space-xs);
    text-align: left;
    color: var(--color-chalk-dim);
  }

  /* Today stands out: accent day label + full-strength hours. */
  .hours-row--today > th {
    color: var(--color-accent);
    font-weight: 700;
  }

  .hours-row--today > td {
    color: var(--color-chalk);
  }

  /* Muted kitchen subline under a row's stand hours ("Küche: 11:30 – 13:30"). */
  .hours-row__kitchen {
    display: block;
    margin-top: var(--space-xs);
    font-weight: 400;
    color: var(--color-chalk-dim);
  }

  /* Date-specific closures/exceptions — lead the Öffnungszeiten section in the
     accent colour so they are noticed before the regular weekly table. */
  .hours-overrides {
    list-style: none;
    padding: 0;
    margin-top: 0;
    margin-bottom: var(--space-md);
    font-size: var(--font-size-body);
    font-weight: 600;
    color: var(--color-accent);
    text-align: left;
  }

  .hours-override-note + .hours-override-note {
    margin-top: var(--space-xs);
  }

  /* Component: customer-contact (Info tab) ─────────────────────────────── */

  .customer-contact {
    text-align: left;
    padding-block: var(--space-md);
  }

  /* Phone + e-mail rows, each led by an inline line-art icon. */
  .contact-list {
    list-style: none;
    margin: 0;
    padding: 0;
    font-family: var(--font-system);
    font-size: var(--font-size-body);
  }

  .contact-item {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding-block: var(--space-xs);
    color: var(--color-chalk);
  }

  /* Icon inherits its size from the row's font and is accent-tinted via currentColor. */
  .contact-icon {
    flex: 0 0 auto;
    width: 1.2em;
    height: 1.2em;
    color: var(--color-accent);
  }

  .contact-item a {
    color: inherit;
    text-decoration: none;
  }

  .contact-item a:hover,
  .contact-item a:focus-visible {
    text-decoration: underline;
  }

  /* Component: board-tabs ──────────────────────────────────────────────────
     Tab bar above the panels. Horizontally scrollable on narrow screens so the
     four labels never wrap. Active state is driven by aria-selected (board.js)
     or, without JS, by a :has()/:target mapping further down.
     Sticky: pins to the viewport top while long panels scroll, so switching is
     always within reach. The opaque background hides content passing under the
     pinned bar — --color-board resolves to slate here and to the white sheet
     under the clipboard token remap, so one rule serves both themes. Cost on
     the chalkboard: the flat patch paints over the container's chalk-haze
     gradients even at rest — at their near-transparent alpha the seam is
     imperceptible, accepted. z-index 3 clears the clipboard stack (sheet 0 /
     lifted content 1 / metal clip 2); the allergen tooltip (100) intentionally
     stays above. */

  .board-tabs {
    position: sticky;
    top: 0;
    z-index: 3;
    background-color: var(--color-board);
    margin-bottom: var(--space-lg);
  }

  .board-tabs__list {
    display: flex;
    flex-wrap: nowrap;
    gap: var(--space-md);
    list-style: none;
    margin: 0;
    /* Block-start room so the focus ring's top edge survives the pinned bar
       sitting flush against the viewport; inline room so the first/last tab's
       ring (a 2px outline at 2px offset) is not clipped by overflow-x at the
       scroll-container edge. */
    padding-block: var(--space-xs) 0;
    padding-inline: var(--space-sm);
    overflow-x: auto;
    scrollbar-width: none;                /* Firefox — hide the scrollbar */
    -webkit-overflow-scrolling: touch;
    border-bottom: 1px solid var(--color-chalk-dim);
  }

  .board-tabs__list::-webkit-scrollbar {
    display: none;                        /* WebKit — hide the scrollbar */
  }

  .board-tab {
    position: relative;                   /* anchor the chalk-stroke ::after */
    display: inline-flex;
    align-items: center;
    min-height: var(--size-touch-min);
    padding-inline: var(--space-xs);
    font-family: var(--font-display);
    font-size: var(--font-size-dish);
    font-weight: 700;
    color: var(--color-chalk-dim);
    text-decoration: none;
    white-space: nowrap;
    border-bottom: 3px solid transparent; /* reserves space + overlaps list border */
    margin-bottom: -1px;                  /* overlap the list's bottom border */
    transition: color var(--timing-fast) ease;
  }

  /* Active-tab underline — same hand-drawn chalk stroke as the section headings,
     revealed by aria-selected (JS) or the no-JS :has() mirroring below. */
  .board-tab::after {
    content: "";
    position: absolute;
    left: var(--space-xs);
    right: var(--space-xs);
    bottom: 0;
    height: 6px;
    background: var(--color-accent);
    -webkit-mask: var(--squiggle) repeat-x left center / auto 100%;
            mask: var(--squiggle) repeat-x left center / auto 100%;
    opacity: 0;
    transition: opacity var(--timing-fast) ease;
  }

  .board-tab:hover {
    color: var(--color-chalk);
    opacity: 1;
  }

  .board-tab:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
  }

  .board-tab[aria-selected="true"] {
    color: var(--color-chalk);
  }

  .board-tab[aria-selected="true"]::after {
    opacity: 1;
  }

  /* Component: board-panel ─────────────────────────────────────────────────
     Visibility strategy is dual:
       - No-JS / pre-hydration: the URL hash drives :target, with :has() keeping
         #heute visible by default and hiding it once another panel is targeted.
       - With JS (html.board-js): board.js toggles .is-active and keeps the hash
         in sync, so deep-links and refresh still land on the right panel without
         the browser scroll-jumping to it. */

  html:not(.board-js) .board-panel { display: none; }
  html:not(.board-js) #heute { display: block; }
  html:not(.board-js) .board-panel:target { display: block; }
  html:not(.board-js) body:has(.board-panel:target) #heute:not(:target) { display: none; }

  html.board-js .board-panel { display: none; }
  html.board-js .board-panel.is-active { display: block; }

  /* Tasteful entrance on the active panel (load + every tab switch).
     Auto-disabled by the prefers-reduced-motion guard at the foot of this file. */
  html.board-js .board-panel.is-active {
    animation: board-panel-in var(--timing-normal) ease both;
  }

  .board-panel:focus { outline: none; }   /* programmatic focus after activation */
  .board-panel:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 4px;
  }

  /* No-JS active-tab mirroring: aria-selected is JS-only, so without JS the tab
     bar follows the URL hash. Default (no panel targeted) → Heute is active; when
     another panel is :target, light up its tab and dim Heute. */
  html:not(.board-js) body:not(:has(.board-panel:target)) #tab-heute,
  html:not(.board-js) body:has(#wochenkarte:target) #tab-wochenkarte,
  html:not(.board-js) body:has(#stammkarte:target)  #tab-stammkarte,
  html:not(.board-js) body:has(#info:target)        #tab-info {
    color: var(--color-chalk);
  }

  html:not(.board-js) body:not(:has(.board-panel:target)) #tab-heute::after,
  html:not(.board-js) body:has(#wochenkarte:target) #tab-wochenkarte::after,
  html:not(.board-js) body:has(#stammkarte:target)  #tab-stammkarte::after,
  html:not(.board-js) body:has(#info:target)        #tab-info::after {
    opacity: 1;
  }

  html:not(.board-js) body:has(.board-panel:target) #tab-heute {
    color: var(--color-chalk-dim);
  }

  /* Component: info-panel two-column layout ────────────────────────────────
     Wide screens only, and only when BOTH map and hours are present: hours +
     contact stack in the left column, the map fills the right column across
     both rows, and the stacked-layout dividers disappear. The grid display is
     scoped to the panel's visible states (.is-active / :target) so it never
     overrides the board-panel show/hide rules above. Narrow screens, or a
     missing map/hours section, keep today's stacked flow untouched. */

  @media (min-width: 768px) {
    html.board-js #info.is-active:has(.customer-map):has(.customer-hours),
    html:not(.board-js) #info:target:has(.customer-map):has(.customer-hours) {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      column-gap: var(--space-xl);
      align-items: start;
    }

    #info:has(.customer-map):has(.customer-hours) .customer-hours {
      grid-column: 1;
      grid-row: 1;
    }

    #info:has(.customer-map):has(.customer-hours) .customer-contact {
      grid-column: 1;
      grid-row: 2;
    }

    #info:has(.customer-map):has(.customer-hours) .customer-map {
      grid-column: 2;
      grid-row: 1 / span 2;
    }

    #info:has(.customer-map):has(.customer-hours) .section-divider {
      display: none;
    }
  }

  /* Component: clipboard theme (structure) ─────────────────────────────────
     The colour work happens via the token remap in tokens.css
     (body.theme-clipboard). Here: the desk, the wooden board itself (real
     photo grain), the white sheet lying on it, the metal clip, and the
     direction-aware sheet-swap slide used for tab switches. Chalkboard rules
     above are untouched — everything is scoped under body.theme-clipboard. */

  /* The wall becomes a desk (the board token now means "paper", so the body
     needs its own surface colour). overflow-x: clip (not hidden — creates no
     scroll container) keeps stray content overflow off the horizontal axis. */
  body.theme-clipboard {
    background-color: var(--color-clip-desk);
    overflow-x: clip;
  }

  /* The board IS wood now: photographic oak over a solid fallback colour,
     rounded clipboard corners, edge bevel + lift shadow — replaces the base
     rule's framed-slate look (border-image frame, haze gradients) wholesale.
     Fixed background-size keeps the grain at photographic scale on every
     viewport (never round/100% — rescaling visibly stretched the grain), and
     the tile is seamless, so tall menus repeat invisibly. Padding = wood ring
     (--size-paper-inset) + on-sheet margin. */
  body.theme-clipboard .customer-container {
    position: relative;
    margin-top: var(--space-2xl);
    border: none;
    border-image: none;
    border-radius: var(--radius-board);
    background-color: var(--color-clip-wood);
    background-image: var(--wood-board);
    background-position: top center;
    background-size: var(--size-wood-tile) auto;
    background-repeat: repeat;
    padding: calc(var(--size-paper-inset) + var(--space-lg))
             calc(var(--size-paper-inset) + var(--space-md));
    box-shadow:
      0 0 0 1px var(--color-clip-wood-edge),  /* crisp edge against the desk */
      var(--shadow-clip-bevel),               /* board edge relief           */
      var(--shadow-clip-board);               /* lifted off the desk         */
  }

  /* Wider viewports show more wood, an airier sheet margin, and a larger
     clip (the pseudo-elements inherit these custom properties). */
  @media (min-width: 768px) {
    body.theme-clipboard .customer-container {
      --size-paper-inset: var(--size-paper-inset-wide);
      --size-clip-w: var(--size-clip-w-wide);
      --size-clip-h: var(--size-clip-h-wide);
      padding-inline: calc(var(--size-paper-inset) + var(--space-lg));
    }
  }

  /* The white sheet lying on the board — a pseudo-element so the markup stays
     untouched. Content children are lifted above it just below. */
  body.theme-clipboard .customer-container::before {
    content: "";
    position: absolute;
    inset: var(--size-paper-inset);
    background: var(--color-clip-paper);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-clip-sheet);
    z-index: 0;
  }

  /* Lift all content above the sheet — every direct child except the tab bar,
     which is position: sticky with its own z-index: 3 (component block); this
     rule's higher specificity would otherwise reset it to relative and
     silently kill the stickiness. */
  body.theme-clipboard .customer-container > *:not(.board-tabs) {
    position: relative;
    z-index: 1;
  }

  /* Metal clip gripping the board's top edge and the sheet's top margin — one
     pseudo-element, layered gradients (screws → lever slot → plate sheen).
     The top offset tracks --size-paper-inset so the clip always overlaps the
     sheet by var(--space-sm) on every breakpoint. */
  body.theme-clipboard .customer-container::after {
    content: "";
    position: absolute;
    top: calc(var(--size-paper-inset) - var(--size-clip-h) + var(--space-sm));
    left: 50%;
    transform: translateX(-50%);
    width: var(--size-clip-w);
    height: var(--size-clip-h);
    background:
      radial-gradient(circle at var(--space-md) 50%, var(--color-clip-metal-edge) 0 3px, transparent 3.5px),
      radial-gradient(circle at calc(100% - var(--space-md)) 50%, var(--color-clip-metal-edge) 0 3px, transparent 3.5px),
      linear-gradient(var(--color-clip-metal-edge), var(--color-clip-metal-edge)) 50% 40% / 40% var(--space-sm) no-repeat,
      linear-gradient(180deg, var(--color-clip-metal) 0%, var(--color-clip-metal) 55%, var(--color-clip-metal-edge) 100%);
    border-radius: var(--radius-md) var(--radius-md) var(--radius-sm) var(--radius-sm);
    box-shadow: var(--shadow-clip-hw);
    z-index: 2;
  }

  /* Sheet-swap tab switch: the sheet (::before) never moves; the incoming
     panel slides in from the side of travel with a soft fade. Direction comes
     from the data-slide attribute board.js sets on .customer-main (absent on
     first paint → defaults to "from the right"). JS-only by selector
     (html.board-js); no-JS :target swaps stay instant, and the global
     reduced-motion guard below collapses the slide to an instant swap. */
  html.board-js body.theme-clipboard .board-panel.is-active {
    animation: page-slide-in-right var(--timing-slide) ease-out both;
  }

  html.board-js body.theme-clipboard .customer-main[data-slide="left"] .board-panel.is-active {
    animation-name: page-slide-in-left;
  }

} /* end @layer components */

/* ═══════════════════════════════════════════════════════════════════════════
   KEYFRAMES — panel entrance (gated by reduced-motion guard below)
   ═══════════════════════════════════════════════════════════════════════════ */

@keyframes board-panel-in {
  from { opacity: 0; transform: translateY(var(--space-sm)); }
  to   { opacity: 1; transform: none; }
}

/* Clipboard sheet-swap: the incoming page slides onto the pad from the side
   of travel (direction attribute set by board.js, right is the default). */
@keyframes page-slide-in-right {
  from { opacity: 0; transform: translateX(var(--size-slide-distance)); }
  to   { opacity: 1; transform: none; }
}

@keyframes page-slide-in-left {
  from { opacity: 0; transform: translateX(calc(-1 * var(--size-slide-distance))); }
  to   { opacity: 1; transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   ACCESSIBILITY: Reduced motion
   ═══════════════════════════════════════════════════════════════════════════ */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   LAYER: components (appended) — stand logo (spec-logo-customer-display)
   ═══════════════════════════════════════════════════════════════════════════ */

@layer components {

  /* Component: stand-logo ─────────────────────────────────────────────── */

  /* The uploaded stand logo, rendered twice: inside the brand h1 in the
     customer header (it replaces the visible title text; max 160px) and at
     the top of the Info tab (max 200px via .customer-info-logo below).
     Intrinsic-ratio safe: only max bounds are set, width/height stay auto
     (the width/height ATTRIBUTES from the controller give the browser the
     aspect ratio pre-load). Token-driven spacing/radius — no theme-specific
     colours, so it sits cleanly on both the chalkboard and the clipboard
     sheet. */
  .stand-logo {
    display: block;
    max-width: 160px;
    max-height: 160px;
    width: auto;
    height: auto;
    margin: 0 auto var(--space-md);
    border-radius: var(--radius-md);
  }

  /* Inside the header h1 the logo is the only child — the h1's own spacing
     applies, so the img drops its bottom margin (no double gap). */
  .customer-brand .stand-logo {
    margin-bottom: 0;
  }

  /* Component: customer-info-logo ─────────────────────────────────────── */

  /* Stand name + logo inside the Kontakt section (below the heading, above
     the phone and mail rows) — logo deliberately smaller than the 160px
     header copy and left-aligned to match the contact rows (margin: 0
     cancels the base rule's auto-centering). The wrapper's padding is the
     single source of vertical spacing — no stacked gaps. */
  .customer-info-logo {
    padding-bottom: var(--space-md);
  }

  .customer-info-logo__name {
    font-family: var(--font-display);
    font-size: var(--font-size-dish);
    font-weight: 700;
    margin: 0;
  }

  .customer-info-logo .stand-logo {
    max-width: 120px;
    max-height: 120px;
    margin: 0 0 var(--space-xs);   /* gap below the logo, above the name */
  }

} /* end @layer components (stand logo) */

/* ═══════════════════════════════════════════════════════════════════════════
   LAYER: components (appended) — map consent + legal pages
   (spec-legal-compliance-pages)
   ═══════════════════════════════════════════════════════════════════════════ */

@layer components {

  /* Component: customer-map__address ───────────────────────────────────────
     Plain-text stand address above the consent box. <address> defaults to
     italics — straighten it; full chalk (not dim), it's primary info. */

  .customer-map__address {
    font-style: normal;
    color: var(--color-chalk);
    margin-bottom: var(--space-sm);
  }

  /* Component: customer-map__consent ───────────────────────────────────────
     Two-click placeholder shown instead of the eager Google-Maps iframe.
     min-height reduces the layout shift on consent (matches the iframe
     height when content fits; on narrow viewports the placeholder can grow
     taller, so the swap may still shift). The "Karte laden" button is
     server-rendered [hidden]; map-consent.js reveals it — without JS only
     the explanation text + external link show. */

  .customer-map__consent {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-md);
    min-height: 300px;            /* matches .customer-map__iframe height */
    padding: var(--space-lg) var(--space-md);
    border: 1px solid var(--color-chalk-dim);
    border-radius: var(--radius-md);
    background: var(--color-banner-bg);
    text-align: center;
  }

  .customer-map__consent-text {
    font-family: var(--font-system);
    font-size: var(--font-size-body);
    color: var(--color-chalk-dim);
  }

  .customer-map__consent-button {
    min-height: var(--size-touch-min);
    padding: var(--space-sm) var(--space-lg);
    font-family: var(--font-display);
    font-size: var(--font-size-dish);
    font-weight: 700;
    color: var(--color-board);
    background: var(--color-accent);
    border: none;
    border-radius: var(--radius-md);
    cursor: pointer;
    transition: opacity var(--timing-fast) ease;
  }

  /* Defence-in-depth: keep the [hidden] semantics even if a future rule sets
     an explicit display on the button (UA [hidden] styling would lose then). */
  .customer-map__consent-button[hidden] {
    display: none;
  }

  .customer-map__consent-button:hover {
    opacity: 0.85;
  }

  .customer-map__consent-button:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 2px;
  }

  .customer-map__external-link {
    font-family: var(--font-system);
    font-size: var(--font-size-body);
  }

  /* Component: customer-footer__legal ──────────────────────────────────────
     Impressum/Datenschutz links under the footer brand line — dimmed like the
     footer itself, full chalk only on interaction. */

  .customer-footer__legal {
    display: flex;
    justify-content: center;
    gap: var(--space-md);
    margin-top: var(--space-sm);
    font-family: var(--font-system);
    font-size: var(--font-size-body);
  }

  .customer-footer__legal a {
    color: var(--color-chalk-dim);
  }

  .customer-footer__legal a:hover,
  .customer-footer__legal a:focus-visible {
    color: var(--color-chalk);
  }

  /* Component: legal-page (layout-legal.php content) ───────────────────────
     Typography for the long-form Impressum/Datenschutz text. The underline
     treatment is inverted versus the menu page: the page title carries the
     hand-drawn squiggle, the h2s are plain .legal-page__heading. */

  .legal-page {
    padding-bottom: var(--space-xl);
  }

  .legal-page__title {
    font-family: var(--font-display);
    font-size: var(--font-size-display);  /* page level — one step above the h2s' --font-size-heading */
    font-weight: 700;
    color: var(--color-chalk);
    margin-bottom: var(--space-xl);
  }

  /* Hand-drawn chalk underline — same mechanism as .section-heading::after:
     the wavy stroke is an SVG mask; the colour stays var(--color-accent).
     If mask is unsupported the ::after box degrades to a solid accent bar
     (≈ a straight underline). */
  .legal-page__title::after {
    content: "";
    display: block;
    width: 72px;
    height: 8px;
    margin-top: var(--space-sm);
    background: var(--color-accent);
    -webkit-mask: var(--squiggle) no-repeat left center / contain;
            mask: var(--squiggle) no-repeat left center / contain;
  }

  /* Sub-headings — section-level type WITHOUT the squiggle (that moved to the
     title). Spacing above comes from the .legal-section rhythm below. */
  .legal-page__heading {
    font-family: var(--font-display);
    font-size: var(--font-size-heading);
    font-weight: 700;
    color: var(--color-chalk);
    margin-bottom: var(--space-md);
  }

  /* Chalk bloom — same grouping treatment as the display headings in the base
     layer (.customer-brand/.section-heading/…). */
  .legal-page__title,
  .legal-page__heading {
    text-shadow: var(--shadow-chalk-text);
  }

  .legal-section {
    margin-bottom: var(--space-xl);
  }

  .legal-page p,
  .legal-page li {
    font-family: var(--font-system);
    font-size: var(--font-size-body);
    color: var(--color-chalk);
    margin-bottom: var(--space-sm);
  }

  .legal-list {
    list-style: none;
    padding: 0;
    margin: 0 0 var(--space-sm);
  }

  .legal-list li + li {
    margin-top: var(--space-xs);
  }

  /* Template-disclaimer / "Stand:" footnotes — dimmed like other meta text. */
  .legal-page .legal-note {
    color: var(--color-chalk-dim);
  }

  /* Brand link in the legal header — keeps the chalk look, no link underline. */
  .legal-home-link {
    color: inherit;
    text-decoration: none;
  }

  .legal-home-link:hover {
    opacity: 1;
    color: var(--color-accent);
  }

  /* Clipboard parity (body.theme-clipboard) ────────────────────────────────
     Deliberately NO scoped overrides: every rule above colours through role
     tokens — --color-chalk/-dim (text, borders), --color-banner-bg (consent
     box fill), --color-accent (button fill, links, title squiggle),
     --color-board (button ink), --shadow-chalk-text (heading bloom) — and
     tokens.css remaps exactly
     those to the clip palette under body.theme-clipboard (chalk→ink,
     board→paper, accent→red pen, banner→paper tint, bloom→none). Consent box
     + button, the footer legal links, and the legal-page typography therefore
     read ink-on-paper as soon as a layout emits the theme class (layout.php
     and layout-legal.php); duplicating the remap here would only invite
     drift. */

} /* end @layer components (map consent + legal pages) */
