/*
  Component library — E0.6.
  Every component is static: no business logic, only states. The feature tickets (E2/E4/E7) bind them
  through the Razor partials in Views/Shared/Components/.

  design-altsystem-parität (11.08.2026): the dotted border on buttons, cards, boxes and list rows below
  is the old live system's signature, reproduced via the --border-motif token family in tokens.css
  rather than a literal "dotted" keyword — see docs/tickets/design-altsystem-paritaet.md.
*/

/* --- Button -------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-5);
  border: var(--border-motif);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  color: var(--color-ink);
  font: inherit;
  font-weight: var(--font-weight-bold);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 120ms ease, box-shadow 120ms ease;
}

.btn:hover {
  box-shadow: var(--shadow-sm);
}

.btn--primary {
  background-color: var(--color-brand);
}

.btn--primary:hover {
  background-color: var(--color-brand-dark);
}

.btn--secondary {
  background-color: var(--color-surface);
}

.btn--secondary:hover {
  background-color: var(--color-surface-sunken);
}

.btn--dark {
  /* Old system's .btn-dark carries no dotted border — solid, same as any other filled dark surface. */
  border-style: solid;
  background-color: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-surface);
}

.btn--dark:hover {
  background-color: #000000;
}

.btn--ghost {
  border-color: transparent;
  background-color: transparent;
  text-decoration: underline;
}

/* Abnahme 14.10.2026: ein Ghost-Knopf, der allein in seiner Zeile steht (z. B. „Zurück zum Warenkorb"
   unter der Zusammenfassung), sah durch Innenabstand + unsichtbaren Rahmen ~25 px eingerückt aus. Bündig
   mit der Inhaltskante: ohne seitlichen Innenabstand und Rahmen — die Trefferfläche bleibt
   --touch-target hoch. Nur für allein stehende Ghost-Knöpfe, nie direkt neben einem Primärknopf — die
   linke Kante einer .action-row (.action-row__start) zählt als allein stehend (15.10.2026). */
.btn--ghost.btn--flush {
  padding-inline: 0;
  border-inline-width: 0;
}

.btn--block {
  width: 100%;
}

.btn:disabled,
.btn[aria-disabled="true"] {
  background-color: var(--color-surface-sunken);
  border-color: var(--color-border-strong);
  color: var(--color-ink-muted);
  cursor: not-allowed;
  box-shadow: none;
}

.btn.is-loading {
  pointer-events: none;
  color: var(--color-ink-muted);
}

.btn.is-loading::before {
  content: "";
  width: 1em;
  height: 1em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: btn-spin 700ms linear infinite;
}

/* Design review 21.10.2026: the same ring as the loading button, as a component of its own — used in a
   busy notice box (AlertViewModel.IsBusy, e.g. the Stripe return page while it waits for the booking). */
.spinner {
  display: inline-block;
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  border: var(--border-width-strong) solid var(--color-ink);
  border-top-color: transparent;
  border-radius: 50%;
  animation: btn-spin 700ms linear infinite;
}

.spinner[hidden] {
  display: none;
}

@keyframes btn-spin {
  to {
    transform: rotate(360deg);
  }
}

/* Reduced motion: the ring stands still — the text says what is happening. */
@media (prefers-reduced-motion: reduce) {
  .btn.is-loading::before,
  .spinner {
    animation: none;
  }
}

/* --- Badge --------------------------------------------------------------- */
/* Design review 14.10.2026 (third round): every badge is square-ish like the buttons (--radius-sm) — no
   pill next to a square "nicht bestellbar" badge any more. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  border: var(--border-width) solid transparent;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  line-height: 1.4;
  white-space: nowrap;
}

.badge--neutral {
  background-color: var(--color-surface-sunken);
  color: var(--color-ink-soft);
  border-color: var(--color-border-strong);
}

.badge--brand {
  background-color: var(--color-brand);
  color: var(--color-ink);
  border-color: var(--color-ink);
}

.badge--success {
  background-color: var(--color-success-surface);
  color: var(--color-success);
  border-color: var(--color-success);
}

.badge--warning {
  background-color: var(--color-warning-surface);
  color: var(--color-warning-ink);
  border-color: var(--color-warning-ink);
}

.badge--danger {
  background-color: var(--color-danger-surface);
  color: var(--color-danger);
  border-color: var(--color-danger);
}

/* --- Notice box (shared) -------------------------------------------------
   Design review 14.10.2026, third round ("Hinweis genauso als Box darstellen wie alle anderen Hinweise —
   gilt für alle Hinweise"): THE one notice box of the site. Every status notice (_Alert, all variants),
   the registration notice (_RegistrationNotice) and the global notice banner (_NoticeBanner) carry this
   class, so they cannot drift apart again: white surface, the old shop's dotted motif, a small uppercase
   headline ("Hinweis", "Erledigt", "Achtung", "Fehler" or the notice's own title) and the text in Light.
   Layout extras (actions beside the text, dismiss button) stay with the component classes. */
.notice-box {
  padding: var(--space-4) var(--space-5);
  border: var(--border-motif);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  color: var(--color-ink);
}

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

/* A busy notice: the spinner left of headline and text, centred on them. */
.notice-box--busy {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.notice-box__title {
  margin: 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.notice-box__text {
  margin: 0;
  font-weight: var(--font-weight-light);
}

/* Editorial richtext inside a notice box (global notice, empty cart): paragraphs must not push the box
   apart. */
.notice-box__richtext > :first-child {
  margin-top: 0;
}

.notice-box__richtext > :last-child {
  margin-bottom: 0;
}

/* --- Alert / Störer ------------------------------------------------------ */
/* Status notices are notice boxes (above). The headline text carries the state; the variants only tint
   the headline in their ink (all AA on white, brand-notes.md) — the box itself stays identical. */
.alert__text {
  margin: 0;
}

.alert--success .notice-box__title {
  color: var(--color-success);
}

.alert--warning .notice-box__title {
  color: var(--color-warning-ink);
}

.alert--danger .notice-box__title {
  color: var(--color-danger);
}

/* The Störer is editorial ("Krapfen gibt es nur Freitag & Samstag!"), not a status message — it stays
   a prominent box on the brand surface (yellow behind black ink, never yellow text) with the dotted
   motif, and gets no headline. */
.alert--stoerer {
  padding: var(--space-3) var(--space-4);
  border: var(--border-motif);
  border-radius: var(--radius-sm);
  background-color: var(--color-brand-soft);
  font-weight: var(--font-weight-semibold);
}

/* Code sample on /styleguide — formerly borrowed .alert--info for its tinted box. */
.code-sample {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border: var(--border-motif);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface-muted);
}

/* The numbered design rules at the top of /styleguide (#gestaltungsregeln). The numbering runs on across
   the topic groups (<ol start>), so a rule keeps one number that reviews can cite. */
.design-rules {
  margin: 0;
  padding-left: var(--space-5);
}

.design-rules > li + li {
  margin-top: var(--space-2);
}

/* --- Card ---------------------------------------------------------------- */
.card {
  display: flex;
  flex-direction: column;
  border: var(--border-motif);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  overflow: hidden;
}

/*
  1,5rem Innenabstand statt 1rem, und 0,75rem zwischen den Zeilen statt 0,5rem: der gepunktete Rahmen
  ist ein sehr praesentes Element der Designsprache, und bei 1rem sass der Text sichtbar daran fest
  (Befund 13.08.2026 an den Kassenseiten — "Ihr Guthaben" und der Hinweis darunter beruehrten den
  Rahmen). Gilt bewusst auch fuer die Produktkarte, die denselben Baustein verwendet: dort war der
  Abstand zwischen Titel, Preis und Kaufknopf aus demselben Grund zu knapp.
*/
.card__body {
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  flex: 1;
}

.card__title {
  margin: 0;
  font-size: var(--heading-size-tile);
  /* Design review 14.10.2026: bold whether the element is an <h3> or a <p> (it was regular as a <p>). */
  font-weight: var(--heading-weight);
}

/* In-page sub-heading (design review 14.10.2026, third round) — the tile heading's size and weight, no
   dotted motif; the dotted heading stays reserved for page titles and section headings. The product detail
   page no longer uses it since the design review 30.10.2026: its data sheet has the old shop's small
   „inhaltsstoffe:" labels instead (.product-facts__title). */
.subheading {
  margin: 0;
  font-size: var(--heading-size-tile);
  font-weight: var(--heading-weight);
  line-height: var(--line-height-tight);
}

/* (.account-links — the stacked "Kontobereich" buttons on /konto — was removed with ticket 26g F: the link
   list is gone, its targets live in the account tiles, see the "Mein Konto" section at the end.) */

.card__text {
  margin: 0;
  color: var(--color-ink-muted);
}

/* --- Product card -------------------------------------------------------- */
.product-card__media {
  aspect-ratio: 4 / 3;
  background-color: var(--color-surface-sunken);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.product-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Placeholder keeps the same box, so a missing image causes no layout shift (T7). */
.product-card__placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  color: var(--color-ink-muted);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  text-align: center;
  padding: var(--space-3);
}

.product-card__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: auto;
}

.product-card__reason {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--color-ink-muted);
}

/* G-3: not orderable is visible and explained, never hidden and never colour alone. Design review
   13.10.2026 (second round): the tile is flat everywhere now, so the state is carried by the badge alone
   — no tinted surface that only some placements showed. */
.product-card--not-orderable {
  background-color: transparent;
}

/* ===========================================================================
   Produktdetailseite kompakt (E2.3/A9, A10 — Design-Review 30.10.2026, Ticket 26g Paket B)
   ---------------------------------------------------------------------------
   Kopfbereich auch auf dem Smartphone zweispaltig: links ein quadratisches Bild (~38 %), rechts Titel,
   Preis mit „inkl. MwSt.", Menge + Warenkorb-Button und die Beschreibung — Ziel bei 390 px: Bild, Titel,
   Preis und Kaufbereich auf dem ersten Bildschirm unter der Liefertag-Auswahl (AK11). Desktop dieselbe
   Struktur, nur größer. Darunter das Datenblatt im Altsystem-Stil (.product-facts). Der Standardtext
   (.product-detail__default-text, weiter unten) steht als letzter Abschnitt hinter der gepunkteten Linie.
   Die Allergen-Badges (.allergen-list) sind entfallen: Allergene sind jetzt eine Textliste mit Beistrichen.
   =========================================================================== */
/* Owner, 02.11.2026: the description is its own grid item. On a phone it runs full width under image and
   buy column (in the ~62 % column it was squeezed into a thin strip); from 768 px it moves back into the
   right-hand column under the buy area, the image spanning both rows. */
.product-detail {
  display: grid;
  grid-template-columns: minmax(0, 38fr) minmax(0, 62fr);
  grid-template-areas:
    "media main"
    "desc  desc";
  gap: var(--space-3);
  align-items: start;
}

.product-detail > .product-detail__media {
  grid-area: media;
}

.product-detail > .product-detail__main {
  grid-area: main;
}

.product-detail > .product-detail__description {
  grid-area: desc;
}

/* Quadratisch wie die Katalogkachel — ohne .product-card-Vorfahren gälte sonst 4:3 aus .product-card__media. */
.product-detail .product-detail__media {
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-md);
  overflow: hidden;
}

/* Eigener, enger Rhythmus statt .stack (24 px wären in der schmalen Spalte zu viel). */
.product-detail__main {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  min-width: 0;
}

.product-detail__main > * {
  margin: 0;
}

/* Seitentitel in der schmalen Spalte: Kachelgröße und keine gepunktete Linie — sie würde dort nur
   umbrechen oder verdrängt (E2.3/A10). Die Linie kommt erst mit der breiten Desktop-Spalte zurück. */
.product-detail .product-detail__title {
  margin: 0;
  align-self: stretch;
}

.product-detail .product-detail__title::after {
  display: none;
}

.product-detail .product-detail__title .section-heading__label {
  font-size: var(--heading-size-tile);
  line-height: var(--line-height-tight);
}

/* Preis und „inkl. MwSt." in einer Zeile; bricht nur um, wenn es nicht anders geht. */
.product-detail__price {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: var(--space-2);
}

.product-detail__vat {
  font-size: var(--font-size-sm);
  color: var(--color-ink-muted);
}

/* Owner, 30.10.2026: „an diesem wochentag nicht verfügbar" ran over the edge of the narrow phone column. The
   G-3 badge wraps here as it already does on the tile and in the quick order. (The login hint that used to
   live in this column now sits full width below the head area — see product.cshtml.) */
.product-detail__main > .badge {
  max-width: 100%;
  white-space: normal;
}

.product-detail__description {
  align-self: stretch;
}

.product-detail__description > :first-child {
  margin-top: 0;
}

.product-detail__description > :last-child {
  margin-bottom: 0;
}

/* Smartphone: Stepper und Warenkorb-Button müssen neben dem Bild Platz haben (≈ 200 px bei 360 px). */
@media (max-width: 767.98px) {
  .product-detail .stepper__input {
    width: 2.5rem;
  }

  .product-detail .product-detail__cart-form {
    flex-wrap: wrap;
  }
}

@media (min-width: 768px) {
  /* The 1fr row takes up the image's extra height, so the description follows the buy area directly. */
  .product-detail {
    grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    grid-template-areas:
      "media main"
      "media desc";
    column-gap: var(--space-6);
    row-gap: var(--space-4);
  }

  .product-detail__main {
    gap: var(--space-4);
  }

  /* Breite Spalte: wieder der volle Seitentitel mit gepunkteter Linie. */
  .product-detail .product-detail__title::after {
    display: block;
  }

  .product-detail .product-detail__title .section-heading__label {
    font-size: var(--heading-size-section);
  }
}

/* Datenblatt im Altsystem-Stil („inhaltsstoffe:", „allergene:", …): kleine fette Überschrift mit
   Doppelpunkt, der Inhalt direkt darunter ohne Leerzeile, zwischen den Einträgen nur ein kleiner Abstand.
   Die Überschrift ist ein Etikett in Grundschrift — keine vierte Überschriftengröße (Gestaltungsregel 5). */
.product-facts {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.product-facts__item > * {
  margin: 0;
}

.product-facts__title {
  font-size: var(--font-size-base);
  font-weight: var(--heading-weight);
  line-height: var(--line-height-tight);
}

/* --- Price / balance ----------------------------------------------------- */
.price {
  font-weight: var(--font-weight-bold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.price--lg {
  font-size: var(--font-size-xl);
}

/*
  Header balance badge. Design review 13.10.2026 reversed the earlier "solid pill" decision
  (docs/tickets/design-altsystem-paritaet.md): square corners like the buttons and the dotted motif
  border — both set further down in "Produktkarte & Detailseite".
*/
.balance-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding: var(--space-1) var(--space-3);
  border: var(--border-width-strong) solid var(--color-ink);
  /* Design-Review 13.10.2026: gleich eckig wie die Buttons (.btn), keine Pille mehr. */
  border-radius: var(--radius-sm);
  background-color: var(--color-brand);
  font-weight: var(--font-weight-bold);
  text-decoration: none;
}

.balance-badge__label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
}

.balance-badge--error {
  background-color: var(--color-danger-surface);
  border-color: var(--color-danger);
  color: var(--color-danger);
}

/* --- Mobile Kopfzeile: Guthaben nur als Betrag (Design-Review 30.10.2026, E0.6/A9) --- */
/*
  Unter 768px zeigt das gelbe Guthaben-Feld in der Kopfzeile nur den Betrag („76,59 €"), ohne das Wort
  „guthaben" — so passt die Aktionsgruppe ab 360px in eine Zeile (Layout: layout.css, „Mobile Kopfzeile").
  Das Wort ist nur optisch weg (gleicher Clip wie .visually-hidden): der zugängliche Name bleibt
  „Guthaben 76,59 €". Nur in .site-header — Konto-Kachel und Styleguide behalten die Beschriftung.
  Der Fehlerzustand darf im Feld zweizeilig werden, die Kopfzeile selbst bricht nie um.
*/
@media (max-width: 767.98px) {
  .site-header .balance-badge {
    padding-inline: var(--space-2);
    white-space: nowrap;
  }

  .site-header .balance-badge__label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }

  .site-header .balance-badge--error {
    max-width: 7.5rem;
    white-space: normal;
    font-size: var(--font-size-xs);
    line-height: 1.15;
  }
}

/*
  Cart entry (header) — Paritaet 24 (12.08.2026): an icon plus count bubble replacing the earlier
  "Warenkorb N Positionen" nav entry, matching the old system's top-right icon slot but with a modern,
  own icon instead of a copy of theirs. Bubble is the flat black-on-brand-yellow language every other
  badge/pill in this file uses, sized to sit on the icon's corner rather than as its own pill.
*/
.cart-entry {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--touch-target);
  height: var(--touch-target);
  color: var(--color-ink);
  text-decoration: none;
}

.cart-entry__icon {
  width: 26px;
  height: 26px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.cart-entry__count {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  border: var(--border-width-strong) solid var(--color-ink);
  background-color: var(--color-brand);
  color: var(--color-ink);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  line-height: 1;
}

/* --- Warenkorb-Bestaetigung ("In den Warenkorb") -------------------------
   Korrektur 14.09.2026: die Bestaetigung nach "In den Warenkorb" existierte nur als unsichtbare
   Live-Region (Paritaet 16). Dieselbe Region bekommt hier eine sichtbare Gestalt.

   Unten rechts statt oben: die Warenkorb-Blase im Kopfbereich ist die dauerhafte Bestaetigung und darf
   nicht von der fluechtigen verdeckt werden. Der Rahmen ist das gepunktete Altsystem-Motiv, die Flaeche
   das Markengelb mit schwarzem Text (nie Gelb als Textfarbe). */
.cart-toast-host {
  position: fixed;
  z-index: 200;
  left: var(--layout-gutter);
  right: var(--layout-gutter);
  bottom: var(--space-4);
  display: flex;
  justify-content: flex-end;
  /* Der Wirtsbereich spannt die ganze Breite auf, darf aber nichts darunter blockieren — nur das
     Kaestchen selbst nimmt wieder Zeiger entgegen. */
  pointer-events: none;
}

/* Abnahme 14.10.2026: auf /schnellbestellung lag das Kaestchen ueber dem klebenden „In den Warenkorb
   uebernehmen" (.quick-order-form__actions, unten rechts) und machte ihn unklickbar, solange es stand.
   Eine Regel fuer alle Seiten: traegt die Seite eine klebende Aktionsleiste am unteren Rand
   ([data-sticky-actions] — klebt bei bottom: --space-3, ein Knopf --touch-target hoch), schwebt der
   Toast darueber statt darauf. Ohne solche Leiste (Katalog, Produktseite) bleibt alles wie gehabt. */
.page:has([data-sticky-actions]) .cart-toast-host {
  bottom: calc(var(--space-3) + var(--touch-target) + var(--space-4));
}

/* /styleguide zeigt beide Zustaende in der Seite, nicht schwebend ueber ihr. */
.cart-toast-host--static {
  position: static;
  justify-content: flex-start;
  pointer-events: auto;
}

.cart-toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  max-width: 26rem;
  padding: var(--space-3) var(--space-4);
  border: var(--border-motif);
  border-radius: var(--radius-md);
  background-color: var(--color-brand);
  color: var(--color-ink);
  box-shadow: var(--shadow-md);
  pointer-events: auto;
  animation: cart-toast-in 160ms ease-out;
}

/* Muss ausdruecklich dastehen: gegen die display:flex-Regel darueber verliert das [hidden]-Attribut
   sonst, und das Kaestchen stuende ohne Text dauerhaft sichtbar auf jeder Seite. */
.cart-toast[hidden] {
  display: none;
}

.cart-toast--error {
  background-color: var(--color-danger-surface);
  border-color: var(--color-danger);
}

.cart-toast__icon {
  flex: none;
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* Beide Symbole stehen im Markup, die Variantenklasse entscheidet — so muss site.js keine
     Pfaddaten kennen. */
  display: none;
}

.cart-toast--success .cart-toast__icon--success {
  display: block;
}

.cart-toast--error .cart-toast__icon--error {
  display: block;
  color: var(--color-danger);
}

.cart-toast__text {
  margin: 0;
  font-weight: var(--font-weight-semibold);
}

@keyframes cart-toast-in {
  from {
    opacity: 0;
    transform: translateY(var(--space-2));
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .cart-toast {
    animation: none;
  }
}

.balance-tile {
  border: var(--border-motif);
  border-radius: var(--radius-md);
  background-color: var(--color-brand);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Design review 14.10.2026 ("Headline Styles sind nicht einheitlich"): the tile's label ("ihr guthaben",
   "Kontostand") is its heading — same size and weight as every other tile heading ("bestellung
   wiederholen" next to it on the home page), no longer the header badge's small semibold label. */
.balance-tile__label {
  font-size: var(--heading-size-tile);
  font-weight: var(--heading-weight);
  line-height: var(--line-height-tight);
}

.balance-tile__amount {
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  font-variant-numeric: tabular-nums;
}

/* Slice-6-Paritaet Punkt 2 (13.08.2026): the "Neuer Kontostand" preview line, checkoutReviewPage only. */
.balance-tile__hint {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-regular);
}


/*
  Design review 13.10.2026: the payment step's balance box (Components/_BalanceSummary.cshtml) — a
  summary table in the dotted card family: labels left, amounts right in tabular figures, a dotted rule
  before the result, and the result ("Neuer Kontostand") on the brand-yellow surface behind black ink.
  Capped in width so label and amount stay readable together on a 13" screen instead of drifting to
  the two ends of the container; on a phone it simply takes the full width.
*/
.balance-summary {
  max-width: 34rem;
  border: var(--border-motif);
  border-radius: var(--radius-md);
  background-color: var(--color-surface);
  overflow: hidden;
}

.balance-summary__list {
  margin: 0;
}

.balance-summary__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
}

.balance-summary__row:first-child {
  padding-top: var(--space-5);
}

.balance-summary__row dt {
  margin: 0;
  min-width: 0;
}

.balance-summary__row dd {
  margin: 0;
  text-align: right;
}

.balance-summary__row--result {
  margin-top: var(--space-2);
  padding-block: var(--space-4);
  border-top: var(--border-motif);
  background-color: var(--color-brand);
  font-weight: var(--font-weight-bold);
}

.balance-summary__row--result dd {
  font-size: var(--font-size-xl);
}

/* Not enough balance: the negative result in the danger colour, on its tinted surface (never red on
   brand yellow). The warning notice next to the box says the same in words. */
.balance-summary--insufficient .balance-summary__row--result {
  background-color: var(--color-danger-surface);
  color: var(--color-danger);
}

/* --- Form ---------------------------------------------------------------- */
.form-field {
  display: block;
  margin-bottom: var(--space-4);
}

.form-field__label {
  display: block;
  margin-bottom: var(--space-1);
  font-weight: var(--font-weight-semibold);
}

.form-field__required {
  color: var(--color-danger);
  margin-left: 2px;
}

.form-field__help {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--font-size-sm);
  color: var(--color-ink-muted);
}

.form-field__error {
  display: flex;
  align-items: flex-start;
  gap: var(--space-1);
  margin-top: var(--space-1);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-danger);
}

.form-control {
  display: block;
  width: 100%;
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  border: var(--border-width-strong) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  color: var(--color-ink);
  font: inherit;
}

textarea.form-control {
  min-height: 6rem;
  resize: vertical;
}

.form-control:focus-visible {
  border-color: var(--color-ink);
}

.form-control[aria-invalid="true"] {
  border-color: var(--color-danger);
  background-color: var(--color-danger-surface);
}

.form-control:disabled {
  background-color: var(--color-surface-sunken);
  border-color: var(--color-border);
  color: var(--color-ink-muted);
  cursor: not-allowed;
}

.form-check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-height: var(--touch-target);
  padding-block: var(--space-2);
}

.form-check input[type="checkbox"] {
  width: 24px;
  height: 24px;
  margin: 0;
  flex-shrink: 0;
  accent-color: var(--color-ink);
}

/* --- Quantity stepper ---------------------------------------------------- */
.stepper {
  display: inline-flex;
  align-items: stretch;
  border: var(--border-width-strong) solid var(--color-ink);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.stepper__button {
  min-width: var(--touch-target);
  min-height: var(--touch-target);
  border: 0;
  background: var(--color-surface);
  color: var(--color-ink);
  font: inherit;
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
  cursor: pointer;
}

/* Design review 13.10.2026 (second round, item 10): full brand yellow without transparency, black ink —
   the light-yellow tint read as washed out. Yellow stays a surface behind black text (brand-notes.md). */
.stepper__button:hover {
  background: var(--color-brand);
  color: var(--color-ink);
}

.stepper__input {
  width: 3.5rem;
  min-height: var(--touch-target);
  border: 0;
  border-inline: var(--border-width-strong) solid var(--color-ink);
  text-align: center;
  font: inherit;
  font-weight: var(--font-weight-bold);
}

/* G-3 (Ticket 22, E2.5): a locked quick-order row disables the field AND both buttons — this is the one
   place in the design system a stepper renders disabled, so the visual state lives here once rather than
   being redefined per caller. Source order matters: this must follow .stepper__button:hover (equal
   specificity) so a disabled button never regains the hover tint. */
.stepper__button:disabled,
.stepper__input:disabled {
  background: var(--color-surface-sunken);
  color: var(--color-ink-muted);
  cursor: not-allowed;
}

/*
  Delivery day group (cart, G-5) — the direct old-system counterpart: its ".cart .cartItem .col-wrapper"
  and ".cart .cartTableHeadings .col-wrapper" are dotted, so the group box, its header rule and its row
  separators below all adopt the motif (docs/tickets/design-altsystem-paritaet.md).
*/
.delivery-day-group {
  border: var(--border-width) var(--border-style-motif) var(--color-border-strong);
  border-radius: var(--radius-md);
  overflow: hidden;
  background-color: var(--color-surface);
}

.delivery-day-group__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  background-color: var(--color-brand);
  border-bottom: var(--border-motif);
}

.delivery-day-group__day {
  margin: 0;
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
}

.delivery-day-group__lines {
  list-style: none;
  margin: 0;
  padding: 0;
}

/*
  Paritaet 2 (12.08.2026): a third column for the line's thumbnail — the old system's own layout,
  filled in from the empty middle column this grid otherwise had. Media and total both span the two text
  rows (name + meta) so they stay vertically centred on the line as a whole, same idiom as before.
*/
.delivery-day-line {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: var(--border-width) var(--border-style-motif) var(--color-border);
}

.delivery-day-line__media {
  grid-column: 1;
  grid-row: 1 / span 2;
  align-self: center;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: var(--radius-sm);
  background-color: var(--color-surface-sunken);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.delivery-day-line__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.delivery-day-line__name {
  grid-column: 2;
  font-weight: var(--font-weight-semibold);
  min-width: 0;
}

.delivery-day-line__meta {
  grid-column: 2;
  font-size: var(--font-size-sm);
  color: var(--color-ink-muted);
}

.delivery-day-line__total {
  grid-column: 3;
  grid-row: 1 / span 2;
  align-self: center;
  text-align: right;
}

/* A line without a thumbnail (order history): two columns, so the name starts at the header's inline
   padding instead of behind an empty media column and its gap. */
.delivery-day-line--no-media {
  grid-template-columns: 1fr auto;
}

.delivery-day-line--no-media .delivery-day-line__name,
.delivery-day-line--no-media .delivery-day-line__meta {
  grid-column: 1;
}

.delivery-day-line--no-media .delivery-day-line__total {
  grid-column: 2;
}

.delivery-day-group__footer {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  background-color: var(--color-surface-muted);
  font-weight: var(--font-weight-bold);
}

/* Design review 14.10.2026 (third round): the day total sits flush right, in the column of the line
   totals above it (same inline padding), not centred between the label and the "Liefertag entfernen"
   action — that action gets a row of its own below. */
.delivery-day-group__footer > .price {
  margin-left: auto;
  text-align: right;
}

.delivery-day-group__footer > .cart-line-form {
  flex-basis: 100%;
}

/* Order history: notices and actions of one order, inside its frame below the total, at the same
   inline padding as the lines. */
.delivery-day-group__actions {
  padding: var(--space-3) var(--space-4);
  border-top: var(--border-width) var(--border-style-motif) var(--color-border);
}

.delivery-day-group__actions > * + .hero__actions {
  margin-top: var(--space-3);
}

/* Order confirmation (checkoutConfirmationPage, design review 15.10.2026): one frame, one line per
   delivery day, reusing the cart's line grid. The detail (order number or failure reason) is content the
   customer needs, so it keeps body text instead of the muted meta style (design rule 10). The last line
   drops its own rule — the frame closes it, or the footer's top rule does when a sum follows. */
.order-results__lines > :last-child {
  border-bottom: 0;
}

.order-results .delivery-day-group__footer {
  border-top: var(--border-width) var(--border-style-motif) var(--color-border);
}

.order-results .delivery-day-line__name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-weight: var(--font-weight-bold);
}

.order-results__detail {
  grid-column: 1;
  min-width: 0;
}

.delivery-day-group__hint {
  flex-basis: 100%;
  font-weight: var(--font-weight-regular);
  font-size: var(--font-size-sm);
  color: var(--color-ink-muted);
}


/*
  E4.3 (Ticket 15) — the conflict fragment (_CartConflicts.cshtml). Reuses .card for each position so it
  stays in the same dotted-motif family as every other box on the page (design-altsystem-parität).
*/
.cart-conflicts {
  margin-block-end: var(--space-4);
}

.cart-conflict-item__reasons {
  margin: 0;
  padding-inline-start: var(--space-5);
}

/*
  E4.2 (ticket 14) — per-line cart controls (Menge/Lieferdatum ändern/Entfernen) and the grand-total
  summary bar. .cart-summary reuses .delivery-day-group__footer's look (same dotted-motif box) so the
  page-level total/hint row is visually the same family as every day's own footer.
*/
.delivery-day-line__actions {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

.cart-line-form {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.cart-line-form__quantity {
  width: 4.5rem;
}

.cart-summary {
  flex-wrap: wrap;
}

/* ---------------------------------------------------------------------------
   Dotted section heading (design-altsystem-parität, 11.08.2026) — a modern rebuild of the old
   system's ".dotted" heading: a 4px dotted rule (--border-motif-lg, matching the old shop's own
   4px width for this specific rule) runs through the row, with the label knocked out of it. The old
   markup did this with an absolutely positioned rule at z-index: -1 behind a label with a white
   background — fragile the moment the surrounding background isn't white. This version instead lays
   out two independent rule segments either side of the label with a flex gap between them, so the
   knockout is a real gap, not a colour trick, and it works on any background.
   See Components/_SectionHeading.cshtml and docs/tickets/design-altsystem-paritaet.md.
   --------------------------------------------------------------------------- */
.section-heading {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 var(--space-4);
}

.section-heading::before,
.section-heading::after {
  content: "";
  flex: 1 1 auto;
  align-self: center;
  border-top: var(--border-motif-lg);
}

/* Left-aligned variant for subsections (e.g. the account page): no leading rule, just the label
   followed by a line running to the end. */
.section-heading--start::before {
  display: none;
}

/* May shrink down to its longest word and wrap: at 390px "per überweisung aufladen" (checkout payment)
   used to push the page 70px wider than the viewport (found in the 14.10.2026 browser check). */
.section-heading__label {
  flex: 0 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: var(--heading-size-section);
  font-weight: var(--heading-weight);
}

/* --- Utilities ----------------------------------------------------------- */
.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;
}

.text-muted {
  color: var(--color-ink-muted);
}

/*
  .text-verbatim nimmt einen Textknoten von der CI-Kleinschreibung aus — für Daten, die der Kunde
  abtippt, kopiert oder mit einem Beleg vergleicht: IBAN- und BIC-Wert, Kontoinhaber,
  Verwendungszweck, Bestell- und Kundennummern, E-Mail-Adressen.

  .text-acronym tut technisch dasselbe, aber aus dem anderen Grund: fest kodierte Abkürzungen, die
  kleingeschrieben falsch wären — „USt.", „MwSt." sowie die Beschriftungen „IBAN:"/„BIC:" (der Wert
  daneben bleibt .text-verbatim). Zwei Namen für eine Deklaration, weil Rechtschreibung und Datentreue
  zwei verschiedene Gründe sind: fällt die CI-Kleinschreibung je, sieht man an der Klasse, was warum
  ausgenommen war.

  Beide Regeln stehen bewusst in layout.css direkt unter `.page { text-transform: lowercase }`, weil
  Regel und Ausnahmen nur gemeinsam zu lesen sind; hier stehen sie nur der Auffindbarkeit wegen.
  Eingabefelder, <code>, <pre>, <kbd>, <samp> und alles mit [data-copy-value] sind dort bereits
  pauschal ausgenommen und brauchen keine der beiden Klassen.
*/

.lead {
  font-size: var(--font-size-lg);
}
/* ===========================================================================
   Ticket 26g C — Liefertag-Auswahl als gelbes Dropdown (E2.4/A9, AK10)
   Design review 30.10.2026. Components/_DeliveryDayBar.cshtml. The chip row (up
   to four lines on a phone) became ONE yellow select-like field: calendar icon,
   „lieferung fr., 30.10.", chevron; the order cutoff next to it (desktop) or as a
   smaller second line inside the field (mobile). A native <details>, so it opens
   without JavaScript; the opened panel lists the days as submit buttons of the
   plain POST form. The categories (.catalog-filter) follow below this section.
   =========================================================================== */
.delivery-bar {
  padding: var(--space-3) 0;
  border-bottom: var(--border-motif);
  font-size: var(--font-size-sm);
}

/* Alone (product detail page, Schnellbestellung): its own dotted frame, field left. */
.delivery-bar--inline {
  padding: var(--space-3) var(--space-4);
  border: var(--border-motif);
}

.delivery-bar__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
}

/* The alerts (A7, no day, rejected change) take a line of their own. */
.delivery-bar__inner > .alert {
  flex: 1 1 100%;
  margin: 0;
}

.delivery-select {
  position: relative;
  flex: 1 1 100%;
  min-width: 0;
}

/* The field: brand yellow as a surface behind black text (brand-notes.md), square
   corners like every control, touch-target height. */
.delivery-select__field {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target);
  padding: var(--space-2) var(--space-3);
  border: var(--border-width-strong) solid var(--color-ink);
  border-radius: var(--radius-sm);
  background-color: var(--color-brand);
  color: var(--color-ink);
  font-size: var(--font-size-base);
  line-height: var(--line-height-tight);
  cursor: pointer;
  list-style: none;
  user-select: none;
}

/* No native disclosure triangle — the chevron is the marker. */
.delivery-select__field::-webkit-details-marker {
  display: none;
}

.delivery-select__field::marker {
  content: "";
}

.delivery-select__icon,
.delivery-select__chevron {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.delivery-select__chevron {
  margin-left: auto;
  transition: transform 120ms ease;
}

.delivery-select[open] .delivery-select__chevron {
  transform: rotate(180deg);
}

.delivery-select__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.delivery-select__value strong {
  font-weight: var(--font-weight-bold);
}

/* Mobile: the cutoff as a smaller second line inside the field. */
.delivery-select__cutoff {
  font-size: var(--font-size-xs);
}

/* Desktop: the cutoff as small text next to the field — hidden on mobile, where it
   sits inside the field instead. */
.delivery-bar__cutoff {
  display: none;
  margin: 0;
  font-size: var(--font-size-sm);
}

/* The opened list overlays the page below instead of pushing the products down. */
.delivery-select__panel {
  position: absolute;
  z-index: 20;
  top: calc(100% + var(--space-1));
  left: 0;
  width: 100%;
  max-height: min(70vh, 32rem);
  overflow-y: auto;
  padding: var(--space-2);
  border: var(--border-width-strong) solid var(--color-ink);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  box-shadow: var(--shadow-md);
}

.delivery-select__form {
  margin: 0;
}

.delivery-select__options {
  display: grid;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.delivery-select__option {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0 var(--space-3);
  width: 100%;
  min-height: 2.5rem;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background-color: transparent;
  color: var(--color-ink);
  font: inherit;
  font-size: var(--font-size-base);
  text-align: left;
  cursor: pointer;
}

.delivery-select__option:hover,
.delivery-select__option:focus-visible {
  background-color: var(--color-brand-soft);
}

.delivery-select__option--selected {
  background-color: var(--color-brand);
  font-weight: var(--font-weight-bold);
}

.delivery-select__option--selected:hover {
  background-color: var(--color-brand);
}

/* G-3: visible but not choosable — struck-through day plus the reason as text,
   never colour alone. */
.delivery-select__option--disabled {
  color: var(--color-ink-muted);
  cursor: not-allowed;
}

.delivery-select__option--disabled:hover {
  background-color: transparent;
}

.delivery-select__day {
  text-decoration: line-through;
}

.delivery-select__reason {
  font-size: var(--font-size-sm);
}

/* „anderes datum" — the last entry, separated by the dotted rule. */
.delivery-select__more {
  margin-top: var(--space-2);
  padding: var(--space-2) var(--space-3) var(--space-1);
  border-top: var(--border-motif);
}

.delivery-select__more > summary {
  width: fit-content;
  cursor: pointer;
  text-decoration: underline;
}

.delivery-select__more[open] > summary {
  margin-bottom: var(--space-2);
}

@media (min-width: 768px) {
  .delivery-select {
    flex: 0 1 auto;
    min-width: 16rem;
  }

  .delivery-select__cutoff {
    display: none;
  }

  .delivery-bar__cutoff {
    display: block;
  }
}

@media (prefers-reduced-motion: reduce) {
  .delivery-select__chevron {
    transition: none;
  }
}

/* Styleguide only: room below the permanently opened example, so its overlaying
   list does not cover the next example. */
.styleguide-delivery-open {
  padding-bottom: 24rem;
}

/* --- /produkte: delivery day + cutoff + categories in one dotted row ------- */
/* Mobile: the yellow field at full width, the category bar below it; the frame
   comes from 768 px on, with a dotted divider between delivery day and categories. */
.catalog-toolbar {
  display: grid;
  gap: var(--space-3);
  min-width: 0;
}

.catalog-toolbar > .delivery-bar--inline {
  padding: 0;
  border: 0;
}

@media (min-width: 768px) {
  .catalog-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-3) var(--space-4);
    border: var(--border-motif);
  }

  .catalog-toolbar > .delivery-bar--inline {
    flex: 0 0 auto;
    max-width: 50%;
  }

  .catalog-toolbar > .catalog-filter {
    flex: 1 1 auto;
    padding-left: var(--space-4);
    border-left: var(--border-motif);
  }
}

/* Stripe top-up amounts (Components/_DepositAmountPicker). Design review 21.10.2026: the list had no
   rule at all and rendered as a bulleted column — now a chip row like the delivery days, the chips at
   touch-target height because the amount is the one choice of this form; the selected amount is
   .chip--selected (deposits.js), the same yellow as the selected delivery day. */
.chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.deposit-amount-picker .chip {
  min-height: var(--touch-target);
  min-width: 5rem;
  padding-inline: var(--space-4);
  font-variant-numeric: tabular-nums;
}

/* An amount, not a sentence — a narrow field instead of the card's full width. */
.deposit-amount-picker .form-control {
  max-width: 14rem;
}

/* Dotted outlines, no filled surfaces — the old shop's language. Corners: the buttons' --radius-sm
   (design review 14.10.2026, third round: one corner radius for every control). */
.chip {
  padding: 2px var(--space-2);
  border: var(--border-width) var(--border-style-motif) var(--color-border-strong);
  border-radius: var(--radius-sm);
  background-color: transparent;
  color: var(--color-ink-soft);
  font: inherit;
  line-height: 1.6;
  cursor: pointer;
}

.chip:hover,
.chip:focus-visible {
  border-color: var(--color-ink);
  background-color: var(--color-brand-soft);
  color: var(--color-ink);
}

/* The selected day is the one piece of state worth emphasising: brand yellow as a
   surface behind black text — the only way the brand colour may be used
   (wwwroot/assets/brand/brand-notes.md) — plus a solid outline and bold text. */
.chip--selected {
  border: var(--border-width-strong) solid var(--color-ink);
  background-color: var(--color-brand);
  color: var(--color-ink);
  font-weight: var(--font-weight-bold);
}

/* G-3: a day that cannot be chosen stays visible. Not communicated by colour
   alone — the reason sits in the accessible name and the title attribute. */
.chip--disabled {
  background-color: transparent;
  border-style: dashed;
  color: var(--color-ink-muted);
  cursor: not-allowed;
  text-decoration: line-through;
}

/* „anderes datum" inside the opened delivery-day field (Ticket 26g C). */
.delivery-bar__picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* Native date input: the shared .form-control field (same 2px border and --radius-sm as the cart's date
   select — design review 14.10.2026, third round), only kept compact here. Input and button share one
   height so neither dominates the row — scoped to this row rather than changing .btn--small, which other
   pages also use. */
.delivery-bar__picker .form-control,
.delivery-bar__picker .btn {
  min-height: 2.25rem;
}

.delivery-bar__picker .form-control {
  width: auto;
  min-width: 9.5rem;
  padding: var(--space-1) var(--space-2);
}

.delivery-bar__picker .btn {
  padding: var(--space-1) var(--space-3);
}

.delivery-bar__picker .form-control:hover,
.delivery-bar__picker .form-control:focus-visible {
  border-color: var(--color-ink);
}

.btn--small {
  padding: var(--space-1) var(--space-3);
  font-size: var(--font-size-sm);
}

/* ===========================================================================
   Ticket 26g C — Kategorieleiste (E2.4/A10, AK11, NEU-E2.4-3)
   Design review 30.10.2026. Components/_CatalogFilter.cshtml: a row of equal-
   height boxes „alle 92", „brot 24" … — name bold, count regular, the active box
   black with white text. Single-select GET links. On mobile one line that swipes
   horizontally inside its own box; the page itself never scrolls sideways
   (min-width: 0 on every flex/grid parent up to .catalog-toolbar).
   =========================================================================== */
.catalog-filter {
  position: relative;
  min-width: 0;
}

/* Design review 30.10.2026 ("make it clearer on mobile that it scrolls sideways"): the hidden scrollbar
   gave no hint. While more boxes follow, site.js sets data-scroll-more / data-scroll-less on the bar and
   that edge fades to white with a chevron (Lucide "chevron-right"/"chevron-left"). Without JavaScript the
   last box is simply cut off at the edge. pointer-events: none — the faded box stays tappable. */
.catalog-filter[data-scroll-more]::after,
.catalog-filter[data-scroll-less]::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: var(--space-1);
  width: 3.5rem;
  pointer-events: none;
  z-index: 1;
}

.catalog-filter[data-scroll-more]::after {
  right: 0;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23141310' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E")
      right center / 1.5rem no-repeat,
    linear-gradient(to right, rgb(255 255 255 / 0), var(--color-surface) 60%);
}

.catalog-filter[data-scroll-less]::before {
  left: 0;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23141310' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m15 18-6-6 6-6'/%3E%3C/svg%3E")
      left center / 1.5rem no-repeat,
    linear-gradient(to left, rgb(255 255 255 / 0), var(--color-surface) 60%);
}

.catalog-filter__list {
  display: flex;
  gap: var(--space-2);
  margin: 0;
  padding: 0 0 var(--space-1);
  list-style: none;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* Browser check 30.10.2026: no visible scrollbar under the row — the faded edge with its chevron
     (.catalog-filter[data-scroll-more] above) shows that it swipes. */
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.catalog-filter__list::-webkit-scrollbar {
  display: none;
}

/* Owner, 30.10.2026: "on desktop no horizontal scrolling" — from 768 px every category stays visible; the
   boxes wrap onto a second line instead of disappearing behind the right edge. Swiping (and its faded
   edge) is for phones only. */
@media (min-width: 768px) {
  .catalog-filter__list {
    flex-wrap: wrap;
    overflow-x: visible;
  }

  .catalog-filter[data-scroll-more]::after,
  .catalog-filter[data-scroll-less]::before {
    content: none;
  }
}

.catalog-filter__list > li {
  flex: 0 0 auto;
  display: flex;
}

.catalog-filter__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding: var(--space-1) var(--space-3);
  border: var(--border-width-strong) solid var(--color-ink);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  color: var(--color-ink);
  text-decoration: none;
  white-space: nowrap;
}

.catalog-filter__item:hover {
  background-color: var(--color-brand-soft);
}

.catalog-filter__name {
  font-weight: var(--font-weight-bold);
}

.catalog-filter__count {
  font-weight: var(--font-weight-regular);
  font-variant-numeric: tabular-nums;
}

.catalog-filter__item--active,
.catalog-filter__item--active:hover {
  background-color: var(--color-ink);
  color: var(--color-surface);
}

/* ===========================================================================
   Ticket 26g A — Katalog mobil zweispaltig (E2.4/A8, AK9)
   Product grid of the single product page — the old shop's layout: one
   continuous grid of equal cells. Design review 30.10.2026: two columns from
   0 px like the old shop (three from 64em), with a smaller column gap on mobile.
   Categories are not separate sections; a divider cell inside the same grid
   separates them (see .catalog-divider). The home page's „Neue Produkte"
   (.new-products__grid, blocks.css) uses the same columns and gaps.
   =========================================================================== */
/* Design review 13.10.2026 (second round, item 2): clearly more air between the rows — above all between
   one tile's price and the next row's photo — than between the columns. minmax(0, 1fr), so a long
   product name can never push a column wider than half the phone. */
.catalog-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-5) var(--space-3);
}

@media (min-width: 768px) {
  .catalog-grid {
    gap: var(--space-7) var(--space-4);
  }
}

@media (min-width: 64em) {
  .catalog-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* A half-phone-wide square is too small for a category heading plus its lead text: below 40em the
   category tile spans the whole row, as in the old shop's mobile view. */
@media (max-width: 39.99em) {
  .catalog-grid > .catalog-divider {
    grid-column: 1 / -1;
    aspect-ratio: auto;
    min-height: 10rem;
  }
}

/* Compact tile (old shop): name and bold price stacked on the left, the small yellow cart icon button
   to their right, the „nicht bestellbar" badge underneath across the full width — it wraps instead of
   being cut off at half width (AK9). The footer dissolves into this grid (display: contents), so the
   DOM keeps name → badge → price → buy for screen readers. */
.product-card .product-card__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "title cart"
    "price cart"
    "badge badge";
  align-items: start;
  column-gap: var(--space-2);
  row-gap: var(--space-1);
  padding: var(--space-2) 0 0;
}

/* Price still the emphasised element of the tile (rule 13), one step smaller on a half-width phone tile. */
.product-card .product-card__footer > .price {
  font-size: var(--font-size-lg);
}

@media (min-width: 768px) {
  .product-card .product-card__body {
    padding-top: var(--space-3);
  }

  .product-card .product-card__footer > .price {
    font-size: var(--font-size-xl);
  }
}

.product-card__body > .card__title {
  grid-area: title;
  /* Browser check 30.10.2026: "anywhere" split names mid-word without a hyphen ("apfelmusschni|tte2").
     German hyphenation (html lang="de-AT") breaks at syllables first; break-word stays the last resort. */
  overflow-wrap: break-word;
  hyphens: auto;
}

.product-card__body > .badge {
  grid-area: badge;
  justify-self: start;
  max-width: 100%;
  margin-top: var(--space-1);
}

.product-card__body > .product-card__footer {
  display: contents;
}

.product-card__footer > .price {
  grid-area: price;
}

.product-card__footer > .product-card__cart-form {
  grid-area: cart;
  align-self: center;
}

/* Browser check 30.10.2026: on a half-width phone tile the cart button beside the name left ~120px for
   it, so names like "apfelmusschnitte2" broke mid-word. Below 40em the name takes the full tile width and
   the button moves into the price row. */
@media (max-width: 39.99em) {
  .product-card .product-card__body {
    grid-template-areas:
      "title title"
      "price cart"
      "badge badge";
  }

  .product-card__footer > .price {
    align-self: center;
  }
}

/* The small yellow cart icon button — square, touch-target size (E0.6/A2). Scoped to the tile: the
   product detail page keeps its larger button next to the stepper. */
.product-card .cart-add-button {
  width: var(--touch-target);
  padding: 0;
}

.product-card .cart-add-button__icon {
  width: 20px;
  height: 20px;
}

/* The product tile is flat everywhere: the old shop shows a square photo with
   name and price underneath, no card frame. Design review 13.10.2026 (second
   round, item 3): this used to be scoped to .catalog-grid, so the very same
   _ProductCard kept the dotted .card frame on the home page ("Neue Produkte")
   and in the styleguide — one component, two looks. It is now the tile's own
   look, wherever it is placed. */
.product-card {
  border: 0;
  border-radius: 0;
  background-color: transparent;
  box-shadow: none;
}

/* Padding and gaps of .product-card .card__body: see .product-card .product-card__body in the section
   „Ticket 26g A" above (compact grid, design review 30.10.2026). */

/* Square media box, like the old shop's 330 × 330 photos. */
.product-card .product-card__media {
  aspect-ratio: 1 / 1;
}

/* Item 2 (live site: name 16px regular, price 20px bold): the name is
   subordinate to the price — body size and weight instead of a heading. */
.product-card .card__title {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-regular);
  line-height: var(--line-height-tight);
}

/* The category separator — this cell *is* the Störer. Design review 13.10.2026:
   "Kategorie-Kachel gleich wie im Altsystem", so it now follows the old shop's
   teaser tile one to one (.gridItem .gridItemContent.borderDotted.teaserContent in
   Content/scss/_grid.scss, _global.scss, content/_teaserContent.scss): no fill —
   white page behind a 2px dotted black frame —, 15px padding, everything centred
   horizontally and vertically, line-height 1.4, a big bold heading and body text
   below it, overflow cut off like the old fixed-height cell. The earlier soft
   yellow tint is gone. */
.catalog-divider {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 15px;
  overflow: hidden;
  border: var(--border-motif);
  background-color: transparent;
  text-align: center;
  line-height: 1.4;
  font-size: var(--font-size-sm);

  /* Square, exactly the footprint of a product photo (the old shop: 330 × 330) —
     and top-aligned, so the grid row does not stretch it to the height of a
     product cell, which also carries name and price below its image. */
  aspect-ratio: 1 / 1;
  align-self: start;
}

/* Old shop: gridItemContent is 12–14px below 1200px and 16px from there. 14px is
   the floor here (readability); 16px from the old desktop breakpoint on. */
@media (min-width: 75em) {
  .catalog-divider {
    font-size: var(--font-size-base);
  }
}

/* Design review 14.10.2026 (third round): the category heading reads the heading tokens and looks the
   same everywhere on /produkte (square tile; /schnellbestellung uses .section-heading since 21.10.2026) — the tile heading size on
   narrow screens, where the tile is half a phone wide, and the section heading size (≈ the old shop's
   36px teaser h2) from 992px on. Bold, line-height 1.3, no bottom margin as in the old teaser. */
.catalog-divider__title {
  margin: 0;
  font-size: var(--heading-size-tile);
  font-weight: var(--heading-weight);
  line-height: 1.3;
}

@media (min-width: 62em) {
  .catalog-divider__title {
    font-size: var(--heading-size-section);
  }
}

.catalog-divider__lead,
.catalog-divider__notice,
.catalog-divider__empty {
  margin: var(--space-2) 0 0;
}

/* Old shop: "h2 + p { margin-top: 10px }". */
.catalog-divider .catalog-divider__title + * {
  margin-top: 10px;
}

.catalog-divider__lead > :first-child {
  margin-top: 0;
}

.catalog-divider__lead > :last-child {
  margin-bottom: 0;
}

/* The Störer text carries weight, not a coloured box — the cell already is the
   Störer, so an alert inside it would be a notice within a notice. */
.catalog-divider__notice {
  font-weight: var(--font-weight-bold);
}

.catalog-divider__empty {
  color: var(--color-ink-muted);
}

/* ---------------------------------------------------------------------------
   Ticket T07: the tile's cart form/login hint, and the registration notice
   partial (parity with the old shop's "Sie sind noch nicht registriert" box
   on /produkte, E2.4/A6, E2.3/A5, G-16, [OFFEN-25]).
   --------------------------------------------------------------------------- */
.product-card__cart-form {
  margin: 0;
}

/* Deliberately a plain link, not a button — there is nothing to submit yet
   (no cart, no account), so it must not look actionable in the same way a
   real buy button would (E2.4/A6). */
.product-card__login-hint {
  font-size: var(--font-size-sm);
  color: var(--color-ink-muted);
  text-decoration: underline;
}

/* Registration notice (_RegistrationNotice) — design review 13.10.2026: a white box with the old
   shop's dotted motif instead of the light-blue .alert--info, black ink, and the login/register
   actions as buttons inside the box (header pairing: anmelden outlined, registrieren dark).
   Text and buttons sit side by side on wide screens and stack on narrow ones. Box, headline and Light
   text come from the shared .notice-box (design review 14.10.2026); only the layout lives here. */
.registration-notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3) var(--space-5);
}

.registration-notice__body {
  flex: 1 1 22rem;
}

.registration-notice__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Centred page title, like the old shop's "unsere produkte". */
.catalog-title {
  text-align: center;
}

/* ---------------------------------------------------------------------------
   Clickable product photo with hover reveal — the old shop shows "mehr info »"
   over the image and navigates to the detail page on click.
   --------------------------------------------------------------------------- */
.product-card__media-link {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  color: var(--color-ink);
  text-decoration: none;
}

.product-card__more {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgb(255 255 255 / 82%);
  color: var(--color-ink);
  font-weight: var(--font-weight-bold);
  opacity: 0;
  transition: opacity 120ms ease;
}

.product-card__media-link:hover .product-card__more,
.product-card__media-link:focus-visible .product-card__more {
  opacity: 1;
}

/* Touch devices have no hover: the overlay would either never show or stick after
   a tap, so the photo is simply a link there. */
@media (hover: none) {
  .product-card__more {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .product-card__more {
    transition: none;
  }
}

/* Ticket 12 (E5.2): the inline result message of "Gutschein einlösen" keeps a clear gap from the
   "Einlösen" button. The margin sits on the alert, not on its container: the container is always
   rendered (site.js writes the message into it without a page reload), and Razor leaves whitespace
   inside it, so neither a plain margin nor :empty would work there — both would leave a phantom gap
   under the button while there is no message. The server-rendered _Alert partial and the markup
   site.js injects both carry .alert, so one rule covers the JS and the no-JS path alike. */
.voucher-form__message > .alert {
  margin-top: var(--space-4);
}

/* --- Checkout step indicator (Ticket 16, E4.5) ---------------------------
   "1/2/3" progress across Zahlung/Adressprüfung/Abschlussprüfung (E4.5 §4). Dotted motif for the
   number circles (design-altsystem-parität) — solid + brand fill only for the current step, matching
   the rule that brand yellow is always a surface behind black ink, never text on white.
   Design review 13.10.2026: no dotted rule under the steps any more — the page title's own dotted
   heading follows directly, two dotted lines stacked read as clutter; the distance to it grows instead. */
.checkout-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
}

/* Beats .stack > * + * (all three checkout steps put the indicator and the title into one .stack).
   Clearly more room between the steps and the page title (design review 13./14.10.2026). The title
   itself is the same dotted, centred page heading as on every other page — an exception without the
   dotted rule (third round) made cart and checkout look different from the rest of the shop and was
   withdrawn on 15.10.2026. */
.checkout-steps + .section-heading--page {
  margin-top: var(--space-8);
}

.checkout-steps__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-ink-muted);
  font-weight: var(--font-weight-semibold);
}

.checkout-steps__number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.75rem;
  height: 1.75rem;
  border: var(--border-motif);
  border-radius: var(--radius-pill);
  font-size: var(--font-size-sm);
}

.checkout-steps__item--active {
  color: var(--color-ink);
}

.checkout-steps__item--active .checkout-steps__number {
  background: var(--color-brand);
  border-style: solid;
}

.checkout-steps__item--done .checkout-steps__number {
  background: var(--color-surface-sunken);
}

/* Design review 21.10.2026: a step is clickable exactly when the page's own button to it is
   (CheckoutStepIndicatorViewModel.For) — with no visual change: the link looks like the plain step. */
.checkout-steps__link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: inherit;
  text-decoration: none;
}

/* --- Schnellbestellung (Ticket 22, E2.5) ---------------------------------
   The quantity grid. Category titles are the shop's normal .section-heading (design review 21.10.2026,
   previously the catalogue's dotted box as a row). Design review 13.10.2026 (second round): the category jump list
   stays (item 7 not adopted, NEU-E2.5-7), followed by the inline delivery-day bar — same order as
   /produkte. */
/* Design review 30.10.2026 ("same look as the new category bar"): the jump list now sits in the same
   dotted .catalog-toolbar row as on /produkte, next to the delivery day, and its boxes are the
   .catalog-filter__item boxes — so it has no styles of its own any more. It stays a jump list
   (NEU-E2.5-7): anchors on this page, no „alle", no active box. The search has its own row below. */
.quick-order-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-5);
}

/* Product search (NEU-E2.5-8, owner wish 15.10.2026). Design review 21.10.2026: the field did not fit —
   grey form outline and a label beside it inside its own dotted frame. Solid ink outline, square corners,
   touch-target height, a search icon instead of the visible label. */
.quick-order-search {
  flex: 0 1 22rem;
}

.quick-order-search__field {
  position: relative;
}

.quick-order-search__icon {
  position: absolute;
  top: 50%;
  left: var(--space-3);
  width: 18px;
  height: 18px;
  transform: translateY(-50%);
  fill: none;
  stroke: var(--color-ink-soft);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}

/* Room for the icon left and the own clear button right; the browser's built-in one would be a second "×". */
.quick-order-search__input {
  padding-left: calc(var(--space-3) + 18px + var(--space-2));
  padding-right: calc(var(--touch-target) + var(--space-1));
  border: var(--border-width) solid var(--color-ink);
  font-size: var(--font-size-sm);
}

.quick-order-search__input:focus-visible {
  border-color: var(--color-ink);
}

@media (max-width: 767px) {
  .quick-order-search {
    flex-basis: 100%;
  }
}

.quick-order-search__input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

.quick-order-search__clear {
  position: absolute;
  top: 0;
  right: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--touch-target);
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-ink);
  cursor: pointer;
}

/* Design review 21.10.2026: full brand yellow on hover like every other hover button (.stepper__button),
   drawn as a small inset square — filling the whole button ran the yellow over the field's outline. The
   button itself keeps the full touch target. */
.quick-order-search__clear::before {
  content: "";
  position: absolute;
  inset: 6px;
  border-radius: var(--radius-sm);
}

.quick-order-search__clear:hover::before,
.quick-order-search__clear:focus-visible::before {
  background-color: var(--color-brand);
}

.quick-order-search__clear:focus-visible {
  outline-offset: -4px;
}

.quick-order-search__clear-icon {
  position: relative;
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The search hides rows, groups, jump-list entries and itself (no JS) via the hidden attribute. Each of
   these carries its own display rule (.quick-order-search is flex, a grid row becomes display: grid below
   640px) — and [hidden] loses against any class rule with a display of its own. layout.css already forces
   [hidden] globally; this says it again where it matters, so the search cannot silently stop hiding if
   that global rule is ever touched (same trap as the cart's "Aktualisieren" button, Paritaet 25). */
.quick-order-search[hidden],
.quick-order-search__clear[hidden],
.quick-order-group[hidden],
.quick-order-row[hidden],
.quick-order-jumplist__list li[hidden],
[data-quick-order-search-empty][hidden] {
  display: none !important;
}

.quick-order-group {
  margin-top: var(--space-7);
}

/* Design review 21.10.2026: the normal dotted section heading instead of the dotted row box. */
.quick-order-group__title {
  margin-bottom: var(--space-2);
}

.quick-order-group__notice,
.quick-order-group__empty {
  margin: 0;
  text-align: center;
}

.quick-order-group__notice {
  font-weight: var(--font-weight-bold);
}

.quick-order-group__empty {
  color: var(--color-ink-muted);
}

/* A2/AK-mobile: wide content scrolls inside its own box, the page itself never scrolls sideways
   (layout.css already sets this on every <table>, repeated on the wrapper for the narrow-viewport case
   where the table's own intrinsic width still exceeds the wrapper). */
.quick-order-grid-wrap {
  overflow-x: auto;
  margin-top: var(--space-3);
}

/* Design review 13.10.2026 (second round, item 8): every category is its own <table>, so auto layout let
   each one size its columns to its own content — unit, price and quantity jumped from table to table.
   Fixed layout with fixed widths for the three right-hand columns (<colgroup> in quickOrderPage.cshtml)
   lines them up across all tables; the name column takes the rest. The quantity column is sized for the
   stepper (2 × 44px buttons + 3.5rem field + dotted borders) plus cell padding. */
.quick-order-grid {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}

.quick-order-grid__col-unit {
  width: 7rem;
}

.quick-order-grid__col-price {
  width: 7rem;
}

.quick-order-grid__col-qty {
  width: 11.5rem;
}

.quick-order-grid th,
.quick-order-grid td {
  padding: var(--space-2) var(--space-3);
  text-align: left;
  border-bottom: var(--border-width) solid var(--color-border);
  vertical-align: middle;
}

/* Design review 21.10.2026: the column headings repeated above every category — kept for screen readers
   (same clip as .visually-hidden), invisible on screen. The <colgroup> still fixes the column widths. */
.quick-order-grid__head th {
  padding: 0;
  border: 0;
  height: 1px;
  line-height: 0;
  font-size: 0;
  overflow: hidden;
  clip-path: inset(50%);
}

/* Prices right-aligned with tabular digits, so the decimal commas stand in one line; the steppers flush
   right, so they stand exactly under each other. */
.quick-order-grid .quick-order-row__price {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.quick-order-grid .quick-order-row__qty {
  text-align: right;
}

.quick-order-grid .quick-order-row__unit {
  color: var(--color-ink-soft);
}

.quick-order-row__name {
  overflow-wrap: anywhere;
}

/* Product name and the "nicht lieferbar" badge each on their own line instead of running into each other. */
.quick-order-row__name .badge {
  margin-top: var(--space-1);
}

.quick-order-row__product {
  display: block;
}

/* G-3: a locked row stays fully visible (name, price, reason) — only its quantity field is disabled,
   never the row itself greyed out to the point of being unreadable. */
.quick-order-row--locked {
  background: var(--color-surface-muted);
}

.quick-order-row__reason {
  margin: var(--space-1) 0 0;
  font-size: var(--font-size-sm);
  color: var(--color-ink-muted);
}

/* Ticket 22 correction: the row's quantity control used to be its own bare <input> here
   (.quick-order-row__qty-input, now removed) — it is Components/_QuantityStepper (AdjustField mode)
   like the cart/product page, styled once in the shared ".stepper"/".stepper__*" rules above, including
   the disabled state (G-3). Nothing quick-order-specific to style here any more. */

/* Design review 13.10.2026 (second round, item 11): the sticky "In den Warenkorb übernehmen" sits flush
   right, above the quantity column. The sticky element is the full-width row, so it must not swallow
   clicks on the grid rows it floats over — only the button itself takes pointer events. */
.quick-order-form__actions {
  position: sticky;
  bottom: var(--space-3);
  z-index: 10;
  display: flex;
  justify-content: flex-end;
  margin-top: var(--space-5);
  pointer-events: none;
}

.quick-order-form__submit {
  pointer-events: auto;
  box-shadow: var(--shadow-md);
}

@media (max-width: 640px) {
  /* Mobile (item 8): no sideways scrolling. Each row becomes a small grid — the name across the full
     width, below it unit | price | quantity in fixed columns, so prices and steppers still stand exactly
     under each other from row to row. */
  .quick-order-grid thead,
  .quick-order-grid colgroup {
    display: none;
  }

  .quick-order-grid,
  .quick-order-grid tbody {
    display: block;
    width: 100%;
  }

  .quick-order-grid tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 5.5rem 9.75rem;
    align-items: center;
    column-gap: var(--space-2);
    row-gap: var(--space-1);
    border-bottom: var(--border-width) solid var(--color-border);
    padding: var(--space-3) 0;
  }

  .quick-order-grid td {
    border-bottom: 0;
    padding: 0;
  }

  .quick-order-grid .quick-order-row__name {
    grid-column: 1 / -1;
    font-weight: var(--font-weight-semibold);
  }

  /* Owner, 30.10.2026: the grey backdrop of a locked row runs edge to edge of the screen; its content stays
     on the same line as the other rows instead of being inset (the old padding-inline: --space-2). The rows
     are a one-column grid here and never need the table's sideways scrolling (layout.css sets overflow-x on
     every table, and the wrapper repeats it) — that scroll box would clip the widened row and scroll it. */
  .quick-order-grid-wrap,
  .quick-order-grid {
    overflow: visible;
  }

  /* .quick-order-grid tr above sets padding-inline to 0 — hence the matching specificity here. */
  .quick-order-grid tr.quick-order-row--locked {
    margin-inline: calc(-1 * var(--layout-gutter));
    padding-inline: var(--layout-gutter);
  }
}

/* ==========================================================================
   Ticket 26c — Hinweisbanner, UmbNav-Untermenue und der Altsystem-Footer
   (contract §6: T26c haengt hier NUR am Dateiende an, damit sich die drei
   parallelen Slice-9-Agenten nicht in denselben Hunks treffen.)
   ========================================================================== */

/* --- Globales Hinweisbanner (E7.3/A4) ------------------------------------ */
/*
  Markenfarbe als Flaeche hinter schwarzem Text, nie als Textfarbe (brand-notes.md).
  Der Balken sitzt zwischen Header und Headerbild und traegt deshalb oben keinen eigenen
  Rahmen — die 2px-Unterkante des Headers ist schon da.
*/
/*
  Design-Review 13.10.2026, zweite Runde: kein gelber Vollbreiten-Balken direkt unter dem Logo mehr (dort
  lag er unter dem überstehenden Logo), sondern ein gepunkteter Kasten in der Inhaltsspalte unter dem
  Headerbild — dieselbe Machart wie .registration-notice. Der Abstand nach oben trennt ihn vom Bild.
*/
.global-notice {
  padding-top: var(--space-5);
  color: var(--color-ink);
}

.global-notice__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3) var(--space-5);
  /* Rahmen, Flaeche, Ueberschrift und Light-Text: gemeinsame .notice-box (Design-Review 14.10.2026). */
}



.global-notice__action {
  flex: none;
}

/*
  Mit dem Schliessen-Knopf sind es drei Kinder in einer space-between-Reihe. Ohne
  diese Regel stuende der Text links, der Aktionsknopf in der Mitte und das X
  rechts — der Text soll stattdessen den freien Platz nehmen, damit Aktion und X
  als Paar rechts zusammenstehen. 16rem als Basis, darunter bricht die Zeile ohnehin um.
*/
.global-notice__text {
  flex: 1 1 16rem;
}

/*
  Schliessen-Knopf (NEU-E7.3-7). Bewusst kein .btn: der Balken traegt daneben schon
  eine Aktion im Knopf-Look, ein zweiter waere genauso laut wie der Hinweis selbst.
  Den Fokusring liefert die globale :focus-visible-Regel in layout.css. 44px
  Trefferflaeche fuer den Finger, das Symbol selbst bleibt klein; der negative
  Aussenabstand haelt den Balken trotzdem so hoch wie vorher.
*/
.global-notice__dismiss {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-block: calc(var(--space-3) * -1);
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  border-radius: 50%;
}

.global-notice__dismiss:hover {
  background-color: rgba(0, 0, 0, 0.12);
}

.global-notice__dismiss-icon {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- Zweite Navigationsebene (UmbNav erlaubt Kinder) --------------------- */
/*
  Kein Flyout: das Design-System kennt keins, und ein Hover-Menue waere auf Touch
  unbedienbar. Kinder stehen eingerueckt unter ihrem Elternpunkt — auf beiden
  Breakpoints lesbar und ohne JavaScript per Tastatur erreichbar.
*/
.site-nav__sublist {
  list-style: none;
  margin: 0;
  padding-left: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* Ein UmbNav-Eintrag vom Typ "Title" ist eine Beschriftung, kein Link. */
.site-nav__link--label {
  cursor: default;
  color: var(--color-ink-muted);
}

@media (min-width: 768px) {
  .site-nav__item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
  }

  .site-nav__sublist {
    flex-direction: row;
    padding-left: 0;
    gap: var(--space-2);
  }
}

/* --- Footer nach Altsystem-Vorbild (E7.3/NEU-E7.3-5) -------------------- */
/*
  Vorlage: docs/nachweise/altsystem/startseite-eingeloggt-desktop-1440.png — schwarzer
  Block, darueber ein gepunktetes Zierband, drei Spalten: Kontaktdaten, gelbe
  Sprechblase, Rechtslinks + Liefergebiete.

  Zierband — Design-Review 13.10.2026, Kehrtwende: frueher als radial-gradient nachgebaut,
  jetzt das Originalbild punkte_oben.png (1920x25, aus dem Altsystem-Repo
  bartl.ols.Web/Content/img/). Der Nachbau sah nie gleich aus — die gelben Punkte sitzen im
  Original unregelmaessig, und die zweite Reihe beginnt erst in der Bildmitte. Die Grafikerin
  verlangt das Original; ein Request mehr ist der Preis dafuer.
  Platzierung wie im Altsystem (_footer.scss .dots-bottom): 25px ueber dem schwarzen Block, auf
  weissem Grund, und um beide Achsen gespiegelt — die Doppelreihe steht dadurch links oben.
  repeat-x statt der alten Einzelbild-Breite: bis 1920px identisch, darueber laeuft das Band
  weiter statt abzubrechen. Dasselbe Bild traegt das Band am oberen Rand des Headers (layout.css).
*/
.site-footer {
  position: relative;
}

.site-footer__band {
  position: absolute;
  top: -25px;
  left: 0;
  right: 0;
  height: 25px;
  background: url("/assets/brand/punkte_oben.png") left top / auto 25px repeat-x;
  transform: scale(-1, -1);
  pointer-events: none;
}

/* Facebook am Ende der Rechtslinks-Zeile, wie im Altsystem: dessen eigenes Symbol (Font Awesome 4.7.0
   fa-facebook-square, gefülltes Quadrat mit ausgespartem „f“), weiß auf Schwarz. Altsystem
   (Content/scss/_footer.scss: footer a { color: white }, .icon-link .fa { font-size 18/30/36px }) —
   bei 36px Schriftgröße misst das Quadrat 1536/1792 em ≈ 31px; unter 768px kleiner wie dort.
   Hover änderte im Altsystem nur die Unterstreichung, am Symbol also nichts. */
.site-footer__social,
.site-footer__social:hover,
.site-footer__social:visited {
  justify-content: center;
  min-width: var(--touch-target);
  color: var(--color-surface);
}

.site-footer__social-icon {
  width: 24px;
  height: 24px;
  fill: currentColor;
  stroke: none;
}

@media (min-width: 768px) {
  .site-footer__social-icon {
    width: 31px;
    height: 31px;
  }
}

.site-footer__contact > :first-child {
  margin-top: 0;
}

.site-footer__contact > :last-child {
  margin-bottom: 0;
}

/* Die Sprechblase ist ein gepflegtes Bild, kein CSS-Nachbau: ihre Form (Blase mit
   Zipfel) gehoert zum Motiv und wuerde als Pseudoelement bei jeder Textlaenge
   anders sitzen. */
/* align-items is load-bearing: the bubble is a grid item of .site-footer__grid and is therefore as
   tall as the tallest column. The flex default `stretch` would hand that height to the image and
   override its `height: auto`, squashing the round bubble into an ellipse — centring it instead
   keeps the image at its natural aspect ratio on both breakpoints. */
.site-footer__bubble {
  display: flex;
  justify-content: center;
  align-items: center;
}

.site-footer__bubble-image {
  display: block;
  max-width: 220px;
  width: 100%;
  height: auto;
  /* Belt and braces for the same reason: should the bubble ever get an explicit height from a
     surrounding layout, the image letterboxes rather than deforms. */
  object-fit: contain;
}

.site-footer__links {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* "impressum | agb | datenschutz" stehen im Altsystem ohne Ueberschrift in einer Zeile. */
.site-footer__inline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
}

.site-footer__inline a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
}

.site-footer__delivery-area {
  font-size: var(--font-size-sm);
  color: #e5e2d8;
}

.site-footer__delivery-area > :first-child {
  margin-top: 0;
}

/* Design-Review 13.10.2026: Überschrift und Ortsliste rücken zusammen — der Spaltenabstand von
   .site-footer__links und der Rand der Überschrift summierten sich zu einer Lücke. */
.site-footer__links .site-footer__title {
  margin-bottom: 0;
}

.site-footer__title + .site-footer__delivery-area {
  margin-top: calc(-1 * var(--space-2) + var(--space-1));
}

.site-footer__delivery-area > :last-child {
  margin-bottom: 0;
}

@media (min-width: 768px) {
  /* Wie im Original: die dritte Spalte laeuft rechtsbuendig gegen den Seitenrand. */
  .site-footer__links {
    text-align: right;
    align-items: flex-end;
  }

  .site-footer__inline {
    justify-content: flex-end;
  }
}

/* --- Mobiler Footer: Sprechblase neben dem Adressblock (Design-Review 30.10.2026, E7.3/A8) --- */
/*
  Unter 768px stand die Sprechblase groß und zentriert unter den Kontaktangaben. Jetzt: deutlich kleiner
  und RECHTS neben dem Adressblock, in derselben Zeile; die Links und Liefergebiete folgen darunter über
  die volle Breite. Den Zweispalter schaltet erst .site-footer__grid--with-bubble ein (_Footer.cshtml setzt
  ihn nur, wenn Kontakttext UND Bild gepflegt sind) — ohne Bild nimmt der Adressblock die volle Breite,
  es bleibt keine leere Spalte stehen.
  Lange Zeilen (E-Mail, Telefon): min-width 0 lässt die Textspalte schmaler werden als ihr längstes Wort,
  overflow-wrap: anywhere bricht nur ein Wort, das allein nicht in die Spalte passt — nichts läuft unter
  die Blase. Platz bei 360px: 328px = Kontakt ≈ 218 + Abstand 16 + Blase ≈ 94 (26vw).
  Ab 768px gilt unverändert das Dreispalten-Raster aus layout.css.
*/
@media (max-width: 767.98px) {
  .site-footer__grid--with-bubble {
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--space-4);
  }

  .site-footer__grid--with-bubble > .site-footer__contact {
    grid-column: 1;
    grid-row: 1;
  }

  .site-footer__grid--with-bubble > .site-footer__bubble {
    grid-column: 2;
    grid-row: 1;
    align-self: start;
  }

  .site-footer__grid--with-bubble > .site-footer__links {
    grid-column: 1 / -1;
  }

  .site-footer__contact {
    min-width: 0;
    overflow-wrap: anywhere;
  }

  .site-footer__bubble-image {
    width: clamp(80px, 26vw, 120px);
    max-width: none;
  }
}

/* --- Produkt-Standardtext (E7.3/A3) ---------------------------------------- */
/*
  Abgesetzt, aber ohne Ueberschrift: ein eigenes <h2> gaebe ihm auf jeder Produktseite
  mehr Gewicht als der Beschreibung. Seit dem Design-Review 30.10.2026 (E2.3/A9) der
  letzte Abschnitt der Seite, hinter der gepunkteten Trennlinie wie im Altsystem
  (border-top unten).
*/
.product-detail__default-text {
  padding-top: var(--space-4);
  border-top: var(--border-motif);
  color: var(--color-ink-soft);
}

.product-detail__default-text > :first-child {
  margin-top: 0;
}

.product-detail__default-text > :last-child {
  margin-bottom: 0;
}

/* --- Konto-Links (Text + Icon) in der Header-Aktionszeile (Design-Review 13.10.2026) -- */
/* Design-Review 13.10.2026, zweite Runde: mehr Luft zwischen Icon und Text, beide sauber mittig.
   line-height 1 nimmt dem Label den Zeilenabstand, der es sonst unter die Icon-Mitte drückt;
   das Icon ist ein Block ohne Grundlinien-Lücke. */
.site-header__account {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target);
  padding-inline: var(--space-2);
  color: var(--color-ink);
  line-height: 1;
  text-decoration: none;
}

.site-header__account:hover,
.site-header__account[aria-current="page"] {
  text-decoration: underline;
}

.site-header__account-icon {
  display: block;
  flex: none;
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- Startseiten-Banner (Design-Review 13.10.2026, Altsystem §1.1) -------- */
/*
  Original-Headerbild als Hintergrund; seine rechte Hälfte ist Holzwand und trägt den Text. Ein Verlauf
  nach rechts dunkelt die Textfläche ab, damit weiße Schrift auf jedem Bildausschnitt lesbar bleibt.
  Schmal rückt der Text über das ganze Bild, der Verlauf deckt dann flächig ab.
*/
.home-hero {
  position: relative;
  /* The bubble sits flush with the container's right edge and is rotated by 8°, so its corners stuck
     10px past the viewport between 768px and the container's max width (horizontal scrolling, found
     in the go-live check 03.11.2026). clip, not hidden: it creates no scroll container. */
  overflow-x: clip;
  color: var(--color-surface);
  background:
    linear-gradient(rgba(0, 0, 0, 0.55), rgba(0, 0, 0, 0.55)),
    url("/assets/home/hero-startseite-1920.jpg") 20% center / cover no-repeat;
  border-bottom: var(--border-width-strong) solid var(--color-ink);
}

.home-hero__inner {
  position: relative;
  display: flex;
  justify-content: flex-end;
  padding-block: var(--space-6) var(--space-7);
}

.home-hero__content {
  width: 100%;
  max-width: 36rem;
}

.home-hero__features {
  list-style: none;
  margin: 0 0 var(--space-5);
  padding: 0;
  border-top: 3px dotted var(--color-surface);
}

.home-hero__features li {
  position: relative;
  padding: var(--space-2) 0 var(--space-2) 2.25rem;
  border-bottom: 3px dotted var(--color-surface);
  font-size: calc(var(--font-size-xl) + 2px);
  font-weight: 300;
  line-height: var(--line-height-tight);
}

.home-hero__features strong {
  font-weight: var(--font-weight-bold);
}

/* Lucide "check" als Maske, damit das Häkchen der Markenfarbe folgt. */
.home-hero__features li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 1.75rem;
  height: 1.75rem;
  transform: translateY(-50%);
  background-color: var(--color-brand);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;
}

.home-hero__cta {
  font-size: var(--font-size-lg);
}

/* Graue Sprechblase rechts unten; der Zipfel ist ein gedrehtes Quadrat. Erst ab Tablet — schmal
   würde sie die Feature-Liste überdecken. */
.home-hero__bubble {
  display: none;
}

.home-hero__arrow {
  position: absolute;
  left: 50%;
  bottom: var(--space-3);
  width: 2.25rem;
  height: 2.25rem;
  transform: translateX(-50%);
  fill: none;
  stroke: var(--color-surface);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (min-width: 768px) {
  .home-hero {
    background:
      linear-gradient(90deg, rgba(0, 0, 0, 0) 30%, rgba(0, 0, 0, 0.6) 55%, rgba(0, 0, 0, 0.6) 100%),
      url("/assets/home/hero-startseite-1920.jpg") left center / cover no-repeat;
  }

  .home-hero__inner {
    min-height: 32rem;
    align-items: center;
    padding-block: var(--space-7) calc(var(--space-7) + var(--space-5));
  }

  .home-hero__content {
    margin-right: 9rem;
  }

  .home-hero__features li {
    font-size: calc(var(--font-size-2xl) + 4px);
  }

  .home-hero__bubble {
    position: absolute;
    right: 0;
    bottom: var(--space-6);
    display: flex;
    flex-direction: column;
    justify-content: center;
    width: 9.5rem;
    height: 9.5rem;
    margin: 0;
    border-radius: 50%;
    background-color: rgba(170, 170, 170, 0.92);
    color: var(--color-ink);
    font-size: var(--font-size-lg);
    line-height: 1.3;
    text-align: center;
    transform: rotate(8deg);
  }

  .home-hero__bubble::after {
    content: "";
    position: absolute;
    left: 1.25rem;
    bottom: -0.6rem;
    width: 1.6rem;
    height: 1.6rem;
    background-color: inherit;
    clip-path: polygon(0 100%, 100% 0, 30% 0);
  }
}

/* „nicht bestellbar: <Grund>" (Design-Review 13.10.2026) ist länger als ein Status-Badge sonst — in
   Produktkarte und Schnellbestellung darf es deshalb umbrechen statt über den Rand zu laufen. */
.product-card .badge,
.quick-order-row .badge {
  white-space: normal;
}

/* Checkbox-Felder (Design-Review 13.10.2026): enger untereinander, Label in Fließtext-Gewicht statt fett. */
.form-field:has(> .form-check) {
  margin-bottom: var(--space-1);
}

.form-check {
  min-height: 0;
  padding-block: var(--space-1);
}

.form-check .form-field__label {
  margin-bottom: 0;
  font-weight: var(--font-weight-regular);
}


/* --- Produktkarte & Detailseite (Design-Review 13.10.2026) --------------- */
/* Produktname ohne Unterstreichung; Hover unterstreicht, damit der Link erkennbar bleibt. */
.card__title a {
  text-decoration: none;
}

.card__title a:hover {
  text-decoration: underline;
}

/* Preis größer. Zweite Runde (Punkt 2): der Preis ist das hervorgehobene Element der Kachel, der Name
   ordnet sich unter (.product-card .card__title). */
.product-card .price {
  font-size: var(--font-size-xl);
  line-height: 1.2;
}

.price--lg {
  font-size: calc(var(--font-size-xl) + 4px);
}

/* „In den Warenkorb" als Icon-Button mit dem Header-Warenkorb (Components/_CartAddButton.cshtml). */
.cart-add-button {
  padding-inline: var(--space-3);
}

/* Stepper and icon button side by side, flush: same height and top edge instead of inline baseline
   alignment, which left the button taller and lower than the stepper. */
.product-detail__cart-form {
  display: flex;
  align-items: stretch;
  gap: var(--space-2);
}

.cart-add-button__icon {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Mengenauswahl und Guthaben-Badge mit gepunkteter Kontur — das Motiv der übrigen Kästen. */
.stepper {
  border: var(--border-motif);
}

.stepper__input {
  border-inline: var(--border-motif);
}

.balance-badge {
  border: var(--border-motif);
}

/* ==========================================================================
   Ticket 26g F — Mein Konto (E3.3 A10, design review 30.10.2026)
   Mockups: wide yellow balance tile + orders tile in the first row, three equal tiles ("lieferung",
   "persönliche daten", "benachrichtigungen") in the second; one column below 64em in the same order,
   centred "abmelden" at the end. Pure arrangement — every rule (G-10, G-15, E5.2–E5.4, E3.5) unchanged.
   Kept as one block so the parallel 26g packages (A–E) can merge components.css without overlap.
   ========================================================================== */

/* "hallo ‹vorname› · kundennummer ‹nr›" centred under the dotted page title. */
.account-greeting {
  margin: 0;
  text-align: center;
  color: var(--color-ink-soft);
}

.account-overview {
  display: grid;
  gap: var(--space-5);
}

.account-overview__top,
.account-overview__details {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
}

@media (min-width: 64em) {
  .account-overview__top {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  }

  /* Three equal, equally tall tiles (grid rows stretch; .card__body grows with flex: 1). */
  .account-overview__details {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Mobile only: the page ends with a centred "abmelden" link — on desktop the header carries it
   (NEU-E3.3-5: the desktop header stays as it is). */
.account-overview__logout {
  display: flex;
  justify-content: center;
}

@media (min-width: 64em) {
  .account-overview__logout {
    display: none;
  }
}

/* --- Account tile (a .card with a heading row and an optional action link) --- */
.account-tile p {
  margin: 0;
}

.account-tile__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.account-tile__action {
  flex-shrink: 0;
  font-weight: var(--font-weight-bold);
  color: var(--color-ink);
}

/* Small muted caption above or before a value ("nächste lieferung", "platzierung der ware"). */
.account-tile__label {
  font-size: var(--font-size-sm);
  color: var(--color-ink-muted);
}

/* Pushed to the bottom of the tile, so "passwort ändern" and the opt-in hint line up across the row. */
.account-tile__foot {
  margin-top: auto;
}

.account-tile__hint {
  font-size: var(--font-size-sm);
  color: var(--color-ink-muted);
}

/* --- Balance tile (_AccountBalanceTile) --------------------------------- */
/* Brand yellow as a surface behind black ink (brand-notes.md). Mobile: summary → online top-up →
   "lieber per überweisung aufladen" → voucher. From 48em: summary and the wire link on the left, the
   white top-up box on the right, the voucher row across the full width below a dotted rule. */
.account-balance {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    "summary"
    "topup"
    "wire"
    "voucher";
  gap: var(--space-4);
  padding: var(--space-5);
  border: var(--border-motif);
  border-radius: var(--radius-md);
  background-color: var(--color-brand);
  color: var(--color-ink);
}

.account-balance__summary {
  grid-area: summary;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.account-balance__label {
  margin: 0;
  font-size: var(--heading-size-tile);
  font-weight: var(--heading-weight);
  line-height: var(--line-height-tight);
}

/* The one big figure of the page (mockup: ~2.5× the tile heading). */
.account-balance .account-balance__amount {
  margin: 0;
  font-size: calc(var(--font-size-3xl) + 0.75rem);
  font-weight: 800;
  line-height: var(--line-height-tight);
  font-variant-numeric: tabular-nums;
}

.account-balance__error {
  margin: 0;
  font-weight: var(--font-weight-semibold);
}

/* White dotted box "online aufladen" inside the yellow tile. */
.account-balance__topup {
  grid-area: topup;
  padding: var(--space-4);
  border: var(--border-motif);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
}

.account-balance__topup-title {
  margin: 0 0 var(--space-3);
  font-size: var(--font-size-lg);
  font-weight: var(--heading-weight);
}

.account-balance__wire {
  grid-area: wire;
  margin: 0;
  text-align: center;
  font-weight: var(--font-weight-bold);
}

.account-balance__wire a {
  color: var(--color-ink);
}

.account-balance__voucher {
  grid-area: voucher;
  padding-top: var(--space-4);
  border-top: var(--border-motif);
}

@media (min-width: 48em) {
  .account-balance {
    grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
    grid-template-areas:
      "summary topup"
      "wire topup"
      "voucher voucher";
    column-gap: var(--space-5);
  }

  /* Without Stripe the top-up box is gone — one column, nothing left floating on the right. */
  .account-balance--no-topup {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "summary"
      "wire"
      "voucher";
  }

  .account-balance__wire {
    align-self: end;
    text-align: left;
  }
}

/* --- Top-up picker, account-tile layout (_DepositAmountPicker IsAccountTile) --- */
.deposit-amount-picker--tile {
  display: grid;
  gap: var(--space-3);
}

.deposit-amount-picker__row {
  display: grid;
  gap: var(--space-3);
}

/* Mobile: the three fixed amounts share one row. */
.deposit-amount-picker__amounts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.deposit-amount-picker--tile .deposit-amount-picker__amount {
  width: 100%;
  min-width: 0;
  border: var(--border-width) solid var(--color-border-strong);
  background-color: var(--color-surface);
  color: var(--color-ink);
  font-weight: var(--font-weight-semibold);
}

/* Selected amount: black with white text — the usual yellow .chip--selected would vanish inside the
   yellow tile (same reason the main button is .btn--dark here). */
.deposit-amount-picker--tile .deposit-amount-picker__amount.chip--selected,
.deposit-amount-picker--tile .deposit-amount-picker__amount.chip--selected:hover {
  border: var(--border-width) solid var(--color-ink);
  background-color: var(--color-ink);
  color: var(--color-surface);
  font-weight: var(--font-weight-bold);
}

.deposit-amount-picker__free-label {
  display: block;
  margin-bottom: var(--space-1);
  font-size: var(--font-size-sm);
}

.deposit-amount-picker--tile .form-control {
  max-width: none;
}

.deposit-amount-picker__submit-row {
  display: grid;
  gap: var(--space-2);
}

.deposit-amount-picker__submit {
  width: 100%;
  font-variant-numeric: tabular-nums;
}

.deposit-amount-picker__minimum {
  display: none;
  margin: 0;
  font-size: var(--font-size-sm);
}

@media (min-width: 48em) {
  .deposit-amount-picker__row {
    grid-template-columns: auto minmax(7rem, 1fr);
    align-items: center;
  }

  .deposit-amount-picker__amounts {
    grid-template-columns: repeat(3, minmax(4.5rem, auto));
  }

  /* Browser check 30.10.2026: in the account tile's white box the 4.5rem chips left the free field so
     narrow that its placeholder read "anderer Be". Chips as wide as their label, the field gets 8.5rem. */
  .deposit-amount-picker--tile .deposit-amount-picker__row {
    grid-template-columns: auto minmax(8.5rem, 1fr);
  }

  .deposit-amount-picker--tile .deposit-amount-picker__amounts {
    grid-template-columns: repeat(3, auto);
  }

  .deposit-amount-picker--tile .deposit-amount-picker__amount {
    padding-inline: var(--space-3);
    white-space: nowrap;
  }

  /* Desktop: the placeholder names the field (mockup); the label stays for screen readers, and the
     minimum moves next to the button. */
  .deposit-amount-picker__free-label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  .deposit-amount-picker__submit-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-4);
  }

  .deposit-amount-picker__submit {
    width: auto;
  }

  .deposit-amount-picker__minimum {
    display: block;
  }
}

/* --- Voucher form, one-row layout (_VoucherRedeemForm IsInline) --- */
/* Mobile: heading above, code field and button in one row. From 48em: all three in one row. */
.voucher-form--inline {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.voucher-form__inline-label {
  grid-column: 1 / -1;
  font-size: var(--font-size-lg);
  font-weight: var(--heading-weight);
}

@media (min-width: 48em) {
  .voucher-form--inline {
    grid-template-columns: auto minmax(0, 1fr) auto;
  }

  .voucher-form__inline-label {
    grid-column: auto;
  }
}

/* --- Orders tile (_AccountOrdersTile) ------------------------------------ */
.account-orders__next {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.account-tile .account-orders__value {
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-bold);
}

.account-tile .account-orders__cutoff {
  margin-top: var(--space-3);
  color: var(--color-ink-soft);
}

.account-orders__error {
  font-weight: var(--font-weight-semibold);
}

/* The buttons sit at the bottom of the tile, level with the voucher row beside it. */
/* Browser check 30.10.2026: two equal grid columns broke "alle bestellungen" onto two lines. The labels
   now never wrap; if both do not fit side by side, the second button moves to its own row. */
.account-orders__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: auto;
}

.account-orders__actions .btn {
  flex: 1 1 auto;
  padding-inline: var(--space-3);
  white-space: nowrap;
}

/* --- Delivery tile (_AccountDeliveryTile) -------------------------------- */
/* Mobile (mockup): address in one line, "platzierung: …" in one line. From 64em: street and town on
   two lines, "platzierung der ware" as a caption above its value. */
.account-delivery__placement .account-tile__label {
  margin-right: var(--space-1);
}

.account-delivery__placement-long {
  display: none;
}

@media (min-width: 64em) {
  .account-delivery__address > span {
    display: block;
  }

  .account-delivery__address > .account-delivery__separator,
  .account-delivery__placement-colon {
    display: none;
  }

  .account-delivery__placement-long {
    display: inline;
  }

  .account-delivery__placement .account-tile__label {
    display: block;
    margin-right: 0;
  }
}

/* --- Personal data tile (_AccountPersonalTile) --------------------------- */
/* Mobile shows the customer number only in the greeting (mockup). */
/* Browser check 30.10.2026: needs the doubled class — the later .value-pairs__row rule otherwise wins and
   showed the number on mobile too. */
.value-pairs__row.account-personal__customer-number {
  display: none;
}

@media (min-width: 64em) {
  .value-pairs__row.account-personal__customer-number {
    display: contents;
  }
}

/* --- Notifications tile (_AccountNotificationsTile) ---------------------- */
.account-notifications .form-field {
  margin-bottom: var(--space-3);
}

/* The phone number belongs to the SMS reminder: indented under its checkbox (24px box + gap). */
.account-notifications__phone {
  padding-left: calc(24px + var(--space-3));
}

/* Browser check 30.10.2026: on a phone the full indent left the field too narrow for "+43 660 7654321". */
@media (max-width: 47.99em) {
  .account-notifications__phone {
    padding-left: var(--space-4);
  }

  .account-notifications__phone .input-action .btn {
    padding-inline: var(--space-3);
  }
}

/* ==========================================================================
   Ticket 26g F/G — shared small building blocks (Mein Konto, meine Stammdaten)
   ========================================================================== */

/* Label/value pairs ("e-mail martin@…", "kundennr. S1008"). */
/* Browser check 30.10.2026: one grid for all rows (rows are display: contents) with a label column only
   as wide as its longest label — the fixed 6.5rem column left a phone tile too little room and the e-mail
   broke before its last letter. */
.value-pairs {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  margin: 0;
}

.value-pairs__row {
  display: contents;
}

.value-pairs dt {
  font-size: var(--font-size-sm);
  color: var(--color-ink-muted);
}

.value-pairs dd {
  margin: 0;
  overflow-wrap: break-word;
}

/* A text link with a leading Lucide icon ("passwort ändern", "abmelden"). */
.icon-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: var(--font-weight-bold);
  color: var(--color-ink);
}

.icon-link__icon,
.back-link__icon {
  width: 1.1em;
  height: 1.1em;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* A field with its own button in the same row (phone number + "speichern"). */
.input-action {
  display: flex;
  align-items: stretch;
  gap: var(--space-2);
}

.input-action .form-control {
  flex: 1 1 auto;
  min-width: 0;
}

.input-action .btn {
  flex-shrink: 0;
}

/* ==========================================================================
   Ticket 26g G — meine Stammdaten (E3.4 A9, design review 30.10.2026)
   Back link, dotted title, then from 64em two columns: "lieferdaten" wide on the left, "kontakt" over
   "zugang" on the right; one column below in the same order.
   ========================================================================== */
.back-link {
  margin: 0;
}

.back-link a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-weight: var(--font-weight-bold);
  color: var(--color-ink);
}

.form-columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: var(--space-5);
}

.form-columns__side {
  display: grid;
  gap: var(--space-5);
}

@media (min-width: 64em) {
  .form-columns {
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  }
}

/* Fields side by side; .form-row--zip: a narrow postcode next to the town, at every width. */
.form-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: var(--space-3);
}

.form-row--zip {
  grid-template-columns: minmax(5.5rem, 7rem) minmax(0, 1fr);
}

/* "* pflichtfeld" left, the submit button right — on a phone the button takes the full width. */
.form-submit-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.form-submit-row--end {
  justify-content: flex-end;
}

.form-submit-row__note {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--color-ink-muted);
}

.form-submit-row .btn {
  width: 100%;
}

@media (min-width: 48em) {
  .form-submit-row .btn {
    width: auto;
  }
}

/*
  Vorüberweisung: bank details with the EPC QR code ("Zahlen mit Code") beside them. The code wraps
  below the details when the card is too narrow for both, otherwise it sits to their right. It keeps a
  white quiet zone of its own (QRCoder draws it), so it scans on the yellow brand surface as well.
*/
.wire-transfer {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
}

.wire-transfer > p {
  flex: 1 1 18rem;
  margin: 0;
}

.wire-transfer__qr {
  flex: 0 0 auto;
  margin: 0;
  width: 11rem;
}

.wire-transfer__qr-code svg {
  display: block;
  width: 100%;
  height: auto;
}

.wire-transfer__qr-caption {
  margin-top: var(--space-2);
  font-size: var(--font-size-sm);
  color: var(--color-ink-muted);
}

/*
  A phone cannot scan its own screen: on touch devices and narrow screens the "QR-Code speichern" button
  and the import hint replace the scan hint. Desktop keeps only the scan hint.
*/
.wire-transfer__qr-download,
.wire-transfer__qr-hint--save {
  display: none;
}

@media (pointer: coarse), (max-width: 47.99em) {
  .wire-transfer__qr-hint--scan {
    display: none;
  }

  .wire-transfer__qr-hint--save {
    display: inline;
  }

  .wire-transfer__qr-download {
    display: inline-flex;
    margin-top: var(--space-2);
  }
}
