/* =============================================================================
   KAUF — the /fertig checkout. One stylesheet for the three pages of the route:
   the shelf with the availability panel (index), the checkout for ONE site
   (kauf) and the return page (danke).

   WHY one file and not three: the three pages are one purchase. The stamp that
   says "frei in Imst" on the shelf is the same object as the area line on the
   checkout and the same object again on the confirmation — if they lived in
   three files they would drift apart within a month. Nothing here redefines a
   token: --ink --bone --brass --paper and the type scale all come from
   css/werk.css. The kraft values below are the ONE exception, and they live
   here for a measured reason.

   Section banners below are named exactly like the HTML sections they style.
   Every section in the markup carries data-sid + data-title; the same id is the
   hook in HTML, here and in js/kauf.js.
   ========================================================================== */


/* =============================================================================
   0. KRAFT TOKENS — the paper stock the availability panel is printed on
   These used to sit in the shelf page's own <style> block, because the panel
   was born inside a kraft card there. kauf.html shows the SAME panel
   (#kauf-verf) and never had them: every var() fell back to nothing, so the
   panel drew bone text on its own cream #EFE3C6 stock — 1.03:1, unreadable.
   The panel travels with this stylesheet, so its colours have to travel with
   it too. Same values the price tag on the demos uses (css/fertig.css).
   ========================================================================== */
:root {
  --kraft:  #D9C7A1;   /* the tag stock */
  --kraft-edge: #9B8151;
  --kraft-ink:  #1A1A1A;
  /* .72, not the .66 the tag uses: on kraft, .66 computes to exactly 4.50:1,
     which is the AA floor with no margin left for a rounding difference. */
  --kraft-dim:  rgba(26,26,26,.72);
  --kraft-line: rgba(26,26,26,.18);
  --stamp-red:  #8E1B12;
}


/* =============================================================================
   1. VERFUEGBARKEIT — data-sid="verfuegbarkeit"
   The panel that opens inside the chosen shelf card. ONE panel element exists
   on the page; js/kauf.js moves it into the card the visitor picked. WHY moved
   and not one panel per card: eleven panels would mean eleven selects, eleven
   inputs and eleven live regions in the accessibility tree of a page whose job
   is to be fast on a phone. Moving one node keeps the tree honest and still
   reads as "this card", because it is physically inside it.
   ========================================================================== */

/* The button that opens it gets its own row UNDER the actions rule, not a
   fourth chip inside it: "Demo ansehen" and WhatsApp are two ways to look at
   the card, this is the card's one question. It takes the kraft outline rather
   than the brass .btn — brass on kraft is the one pairing this palette has no
   contrast number for. */
.shelf-check { margin-top: .75rem; }
.verf-open {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  width: 100%;
  border: 1px solid var(--kraft-edge);
  border-radius: var(--radius-pill);
  padding: .55rem 1rem;
  font-size: .92rem;
  font-weight: 660;
  color: var(--kraft-ink);
  min-height: 44px;
  transition: background-color .16s ease, color .16s ease;
}
.verf-open:hover,
.verf-open[aria-expanded="true"] { background: var(--kraft-ink); color: var(--kraft); }

/* The panel itself: a docket clipped to the card. Paler stock than the mount
   (the same #EFE3C6 as the punched price chip) so it reads as a separate slip
   of paper lying on the kraft, not as a region of the card. */
.verf {
  margin-top: .9rem;
  border: 1px solid var(--kraft-edge);
  background: #EFE3C6;
  padding: .95rem .9rem 1rem;
  color: var(--kraft-ink);
}
.verf[hidden] { display: none; }

.verf-seite {
  font-family: var(--font-mono);
  font-size: .7rem;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--kraft-dim);
  margin-bottom: .55rem;
}
.verf-lead {
  font-size: .92rem;
  line-height: 1.45;
  margin-bottom: .9rem;
}

/* Two fields side by side from 360px up — country is three characters wide and
   a postcode is five, so stacking them wastes the one screen that matters. */
.verf-fields {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: .6rem;
  margin-bottom: .75rem;
}
.verf .field { margin-bottom: 0; }
.verf label {
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .03em;
  color: var(--kraft-dim);
  margin-bottom: .3rem;
}
/* 16px, not smaller: iOS zooms the whole layout when a focused input is under
   16px, and this control is on the phone screen of a visitor from an ad. */
.verf input[type="text"],
.verf select {
  background: #F7EFDC;
  border: 1px solid var(--kraft-edge);
  color: var(--kraft-ink);
  font-size: 16px;
  padding: .6rem .7rem;
  min-height: 44px;
}
.verf input[type="text"]:focus,
.verf select:focus { border-color: var(--kraft-ink); }
.verf :focus-visible { outline: 2px solid var(--kraft-ink); outline-offset: 2px; }

.verf-btn {
  background: var(--kraft-ink);
  color: var(--kraft);
  font-weight: 680;
  font-size: .95rem;
  padding: .7rem 1.4rem;
  border-radius: var(--radius-pill);
  min-height: 44px;
  width: 100%;
  justify-content: center;
  transition: filter .18s ease;
}
.verf-btn:hover { filter: brightness(1.25); }
.verf-btn[disabled] { opacity: .6; cursor: progress; }

.verf .error-msg { color: #8E1B12; }

/* DER VERFUEGBARKEITS-SCHRITT AUF kauf.html (BRIEF §16).
   Ohne ?plz= in der Adresse — der Weg aus einer Demo heraus — steht er ueber
   dem Formular und das Formular wartet. Beides entscheidet die Klasse
   `kauf-ohne-plz`, die der Ein-Zeiler im <head> von kauf.html VOR dem ersten
   Paint setzt: wuerde js/kauf.js das tun, erschiene erst das Formular und
   wuerde danach verschwinden — ein Layout-Sprung, den kein Besucher bestellt
   hat. js/kauf.js nimmt die Klasse wieder ab, sobald das Gebiet frei ist; das
   ist dann die Antwort auf einen Klick und zaehlt nicht als CLS.

   KEIN `hidden`-Attribut am Abschnitt: css/werk.css hat ein globales
   `[hidden]{display:none !important}`, das keine Regel dieser Datei schlägt.
   Also entscheidet allein die Klasse, und ohne JavaScript ist der Schritt
   nicht da — prüfen könnte er dort ohnehin nichts, und der Weg zu einem
   Menschen steht im Ohne-JavaScript-Block oben auf der Seite. */
#kauf-verf { display: none; }
html.kauf-ohne-plz #kauf-verf { display: block; }
html.kauf-ohne-plz #kauf-form { display: none; }
.kauf-verf { margin-top: 0; margin-bottom: 1.6rem; }
.kauf-verf h2 { font-size: 1.15rem; line-height: 1.25; margin-bottom: .5rem; }

/* GeoNames-Namensnennung (CC BY 4.0). Steht auf beiden Seiten mit PLZ-Feld, also
   einmal hier und nicht zweimal: auf dem Kraft-Panel erbt sie dessen Tinte, auf
   kauf.html die dunkle. Deshalb color: inherit mit gedrosselter Deckkraft.
   .72 statt .62: auf dem Kraft-Panel rechnete .62 zu 4,48:1 (axe, 20.09.2026) —
   knapp unter der AA-Schwelle. .72 ist derselbe Wert wie --kraft-dim und
   ergibt dort 6,09:1. */
.geo-credit {
  font-family: var(--font-mono);
  font-size: .68rem;
  line-height: 1.5;
  color: inherit;
  opacity: .72;
  margin-top: .55rem;
}
.geo-credit a { text-decoration: underline; text-underline-offset: 3px; }

/* THE ANTI-CLS RESERVE. The result lands here after a network round trip, so
   the box that will hold it is already this tall the moment the panel opens.
   9.5rem is the tallest of the three short states (free, unknown postcode,
   network error); the "taken" state is longer and grows downward, which happens
   inside the 500ms input window and is therefore not an unexpected shift. */
.verf-out {
  min-height: 9.5rem;
  margin-top: .9rem;
  padding-top: .9rem;
  border-top: 1px solid var(--kraft-line);
}
.verf-out:empty { border-top-color: transparent; }
/* Der Fehlerfall ist EIN Satz, und in 9,5rem sah er verloren aus (Screenshot
   Beri, 20.09.2026). Die Reserve hat ihre Arbeit dann schon getan: sie hält die
   Höhe, SOLANGE die Antwort unterwegs ist. Ist die Antwort da und ist sie eine
   Fehlermeldung, gibt es nichts mehr, wofür Platz freigehalten werden müsste —
   und das Schrumpfen passiert im 500ms-Fenster nach dem Klick, ist also kein
   unerwarteter Sprung (dasselbe Argument wie beim „vergeben"-Zustand oben). */
.verf-out.ist-fehler { min-height: 0; }


/* =============================================================================
   2. STEMPEL — the result, and the one deliberate piece of motion
   The shelf already speaks in rubber stamps: a sold card carries a red double
   ruled "Verkauft" across the print. The answer to "is it free here" is the
   same object, so the emotional moment is one the visitor has already seen on
   this page — pressed, not faded in.
   ========================================================================== */
.verf-stamp {
  display: inline-block;
  border: 3px double var(--kraft-ink);
  border-radius: 3px;
  padding: .3rem 1rem;
  font-family: var(--font-mono);
  font-size: .92rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--kraft-ink);
  transform: rotate(-2.5deg);
  transform-origin: 18% 60%;
  margin-bottom: .8rem;
  animation: verf-press .4s cubic-bezier(.2,.8,.2,1) both;
}
/* Taken uses the SAME stamp in the sold colour — one object, two inks. */
.verf-stamp.is-weg { border-color: var(--stamp-red); color: var(--stamp-red); }

/* The press: the stamp arrives from above the paper, lands slightly too hard,
   settles. 55% is the contact frame. */
@keyframes verf-press {
  0%   { opacity: 0; transform: rotate(-2.5deg) scale(1.22); }
  55%  { opacity: 1; transform: rotate(-2.5deg) scale(.965); }
  100% { opacity: 1; transform: rotate(-2.5deg) scale(1); }
}
/* The copy under the stamp follows a beat later, like ink spreading. */
.verf-body { animation: verf-rise .45s .1s ease-out both; }
@keyframes verf-rise {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .verf-stamp, .verf-body { animation: none; }
  .verf-stamp { transform: rotate(-2.5deg); }
}

.verf-body p { font-size: .92rem; line-height: 1.5; margin-bottom: .7rem; }
.verf-body p:last-child { margin-bottom: 0; }
.verf-body .verf-fein { font-family: var(--font-mono); font-size: .74rem; color: var(--kraft-dim); }

/* The reserve button — the one brass-free primary on the kraft stock. */
.verf-go {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .45rem;
  width: 100%;
  background: var(--kraft-ink);
  color: var(--kraft);
  font-weight: 700;
  font-size: 1rem;
  padding: .8rem 1.4rem;
  border-radius: var(--radius-pill);
  min-height: 48px;
  margin-top: .3rem;
  transition: filter .18s ease, transform .18s ease;
}
.verf-go:hover { filter: brightness(1.25); transform: translateY(-1px); }

/* The free alternatives when the area is gone. A ruled list, not cards: they
   are a second answer to the same question, not a new product grid. */
.verf-alt { margin: .2rem 0 .9rem; }
.verf-alt li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .3rem .7rem;
  padding: .5rem 0;
  border-top: 1px solid var(--kraft-line);
  font-size: .92rem;
}
.verf-alt .verf-alt-preis { font-family: var(--font-mono); font-size: .8rem; color: var(--kraft-dim); }
.verf-alt a { font-weight: 660; text-decoration: underline; text-underline-offset: 4px; }

/* The individual-build offer. Boxed, because it is a different promise from
   everything above it and must not read as a thirteenth product. */
.verf-angebot {
  border: 1px solid var(--kraft-edge);
  padding: .75rem .8rem;
  margin-top: .8rem;
  background: rgba(26,26,26,.05);
}
.verf-angebot p { font-size: .9rem; margin-bottom: .55rem; }
/* Two links, two lines. Inline they ran together as one underlined run
   ("…anfragenOder das Briefing ausfüllen") with nothing between them. */
.verf-angebot a {
  display: block;
  width: fit-content;
  font-weight: 660;
  font-size: .92rem;
  padding: .3rem 0;
  text-decoration: underline;
  text-underline-offset: 4px;
}
.verf-angebot .verf-angebot-zweit { font-weight: 560; opacity: .78; }


/* =============================================================================
   2b. WARTELISTE — data-sid="verfuegbarkeit" (der "Vergeben"-Zweig)
   The very last offer on a taken area: one e-mail field, one notification, then
   the address is gone. It is styled QUIETER than the individual-build box above
   it on purpose — a visitor who can buy something today should read that box
   first, and this one only if neither answer suited him.
   ========================================================================== */
.verf-warte { margin-top: .9rem; padding-top: .8rem; border-top: 1px solid var(--kraft-line); }
.verf-warte .verf-warte-titel { font-weight: 660; font-size: .92rem; margin-bottom: .3rem; }
.verf-warte .verf-warte-text { font-size: .85rem; line-height: 1.5; color: var(--kraft-dim); margin-bottom: .6rem; }
.verf-warte label { display: block; font-size: .78rem; margin-bottom: .25rem; color: var(--kraft-dim); }
/* 16px, not smaller: iOS zooms the whole layout when a focused input is under
   16px, and this field sits inside a card that is already at the viewport edge.
   Same rule as the postal-code field above. */
.verf-warte input[type="email"] {
  width: 100%;
  font: 400 1rem/1.3 var(--font-sans);
  padding: .55rem .6rem;
  border: 1px solid var(--kraft-edge);
  background: rgba(255,255,255,.55);
  color: var(--kraft-ink);
}
.verf-warte-btn {
  margin-top: .55rem;
  font: 660 .82rem/1 var(--font-mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: .7rem 1rem;
  border: 1px solid var(--kraft-ink);
  background: transparent;
  color: var(--kraft-ink);
  cursor: pointer;
}
.verf-warte-btn:hover { background: var(--kraft-ink); color: var(--kraft); }
.verf-warte-btn[disabled] { opacity: .5; cursor: default; }
/* THE ANTI-CLS RESERVE, same idea as .verf-out: the confirmation replaces the
   whole form, so without a reserved line the card would jump on success. */
.verf-warte-out { min-height: 1.4rem; margin-top: .5rem; font-size: .85rem; line-height: 1.45; }

/* On the dark checkout page the same block sits on ink, not on kraft. */
.kauf-weg .verf-warte { border-top-color: var(--line); }
.kauf-weg .verf-warte .verf-warte-text { color: var(--bone-dim); }
.kauf-weg .verf-warte label { color: var(--bone-dim); }
.kauf-weg .verf-warte input[type="email"] {
  background: rgba(239,233,222,.06);
  border-color: var(--line);
  color: var(--bone);
}
.kauf-weg .verf-warte-btn { border-color: var(--bone-dim); color: var(--bone); }
.kauf-weg .verf-warte-btn:hover { background: var(--bone); color: var(--ink); }


/* =============================================================================
   3. KAUF-LAYOUT — data-sid="kauf"
   The checkout for ONE site. Mobile first: the object, then the form, then what
   you are buying, then the button — the order a person reads them in. From
   1000px the object becomes a sticky column on the left and the form takes the
   right, but "Das kaufen Sie" stays directly above the button at EVERY width,
   because that adjacency is the legal requirement, not a layout preference.
   ========================================================================== */
.kauf-page { padding-top: clamp(5.5rem, 12vh, 7.5rem); padding-bottom: clamp(3rem, 8vw, 6rem); }
.kauf-grid { display: grid; gap: clamp(1.8rem, 4vw, 3.2rem); align-items: start; }
@media (min-width: 1000px) {
  .kauf-grid { grid-template-columns: minmax(0, 360px) minmax(0, 1fr); }
  .kauf-objekt { position: sticky; top: 5.5rem; }
}
.kauf-col { min-width: 0; }

.kauf-head { margin-bottom: clamp(1.6rem, 4vw, 2.4rem); }
.kauf-head h1 {
  font-family: var(--font-disp);
  font-variation-settings: var(--wide);
  font-weight: 780;
  font-size: clamp(1.9rem, 5.2vw, 2.9rem);
  margin-bottom: .7rem;
}
.kauf-eyebrow { color: var(--brass); letter-spacing: .06em; margin-bottom: .9rem; }

/* The B2B notice. On the page, not only at the checkbox — a buyer must read it
   before he starts typing, not while he is ticking boxes (WKO e-commerce). */
.kauf-b2b {
  display: flex;
  gap: .7rem;
  border: 1px solid var(--brass);
  padding: .75rem .9rem;
  font-size: .92rem;
  line-height: 1.45;
  margin-bottom: clamp(1.6rem, 4vw, 2.2rem);
}
.kauf-b2b strong { color: var(--brass); }


/* =============================================================================
   4. OBJEKT — data-sid="objekt"
   The site being bought: its picture, its name, its full price, and the area
   that is being held. This is the "THIS site for THIS town" anchor — without it
   the page is a form, and a form is a shop cart.
   ========================================================================== */
.kauf-objekt { border: 1px solid var(--line); padding: 1rem 1rem 1.2rem; background: var(--ink-2); }
.kauf-shot {
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--ink);
  border: 1px solid var(--line);
  margin-bottom: 1rem;
}
.kauf-shot img { width: 100%; height: 100%; object-fit: cover; object-position: top center; }
/* The min-heights in this block are the anti-CLS reserve for the object column.
   Name, price, place and deposit are all written by js/kauf.js after the
   catalogue and the area check answer; each box is already as tall as its
   longest realistic value (two lines), so the numbers land without moving the
   form next to them. */
.kauf-name {
  font-variation-settings: var(--wide);
  font-weight: 740;
  font-size: 1.45rem;
  line-height: 1.1;
  margin-bottom: .6rem;
  min-height: 3.2rem;
}
.kauf-preis {
  font-family: var(--font-mono);
  font-size: 1.25rem;
  font-weight: 700;
  margin-bottom: .2rem;
}
.kauf-preis-fein { font-family: var(--font-mono); font-size: .74rem; color: var(--bone-dim); }

/* The area line. Brass rule above it, because it is the promise the money buys. */
.kauf-gebiet {
  margin-top: 1rem;
  padding-top: .9rem;
  border-top: 1px solid var(--line);
}
.kauf-gebiet-ort { font-weight: 680; font-size: 1.02rem; margin-bottom: .25rem; min-height: 3.1rem; }
.kauf-gebiet-note { font-family: var(--font-mono); font-size: .74rem; color: var(--bone-dim); line-height: 1.5; min-height: 2.3rem; }

/* The deposit, stated as its own number so nobody has to work out 10 % of a
   price in his head while deciding whether to pay. */
.kauf-anzahlung {
  margin-top: 1rem;
  padding-top: .9rem;
  border-top: 1px solid var(--line);
}
.kauf-anzahlung-num {
  font-family: var(--font-mono);
  font-size: 1.55rem;
  font-weight: 700;
  color: var(--brass);
  line-height: 1.1;
  margin-bottom: .3rem;
}
.kauf-anzahlung-lab { font-family: var(--font-mono); font-size: .74rem; color: var(--bone-dim); line-height: 1.5; min-height: 2.3rem; }


/* =============================================================================
   5. KAUFEN-BLOCK — data-sid="das-kaufen-sie"
   Everything the buyer is agreeing to, immediately above the pay button. The
   lists are copied word for word from the live /fertig #drin section, because a
   deliverable that is worded differently in two places is two deliverables.
   ========================================================================== */
.kaufen {
  border: 1px solid var(--line);
  padding: clamp(1rem, 3vw, 1.5rem);
  margin-bottom: 1.4rem;
}
.kaufen > h2 {
  font-family: var(--font-mono);
  font-weight: 430;
  font-size: .78rem;
  letter-spacing: .04em;
  color: var(--bone-dim);
  padding-bottom: .8rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: 1rem;
}
.kaufen h3 {
  font-family: var(--font-mono);
  font-weight: 430;
  font-size: .72rem;
  letter-spacing: .04em;
  color: var(--bone-dim);
  margin-bottom: .5rem;
}
/* .kaufen-listen is the two-column pair; from 700px its children stop being
   adjacent siblings, so the block AFTER the pair needs the same seam or
   "Lieferung und Frist" runs straight into the last bullet above it. */
.kaufen-teil + .kaufen-teil,
.kaufen-listen + .kaufen-teil { margin-top: 1.25rem; padding-top: 1.25rem; border-top: 1px solid var(--line-soft); }
.kaufen li {
  position: relative;
  padding: .3rem 0 .3rem 1.4rem;
  font-size: .95rem;
  line-height: 1.5;
}
.kaufen li::before {
  content: '';
  position: absolute;
  left: 0; top: 1em;
  width: 9px; height: 1px;
  background: var(--brass);
}
.kaufen .is-out li { color: var(--bone-dim); }
.kaufen .is-out li::before { background: var(--bone-dim); }
.kaufen p { font-size: .93rem; line-height: 1.55; color: var(--bone-dim); margin-bottom: .6rem; }
.kaufen p:last-child { margin-bottom: 0; }
.kaufen strong { color: var(--bone); font-weight: 660; }
/* The deposit amount, written by js/kauf.js from the catalogue. Given the
   weight of a figure and not of a sentence, because it is the number the
   button is about. min-height reserves its line before it arrives. */
.kaufen .kaufen-betrag {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--brass);
  min-height: 1.7rem;
}
.kaufen a { text-decoration: underline; text-underline-offset: 3px; }

/* DIE DREI STUFEN — data-sid="das-kaufen-sie"
   Three rows, figure first. The figure column is fixed so the three amounts
   line up on their own edge instead of drifting with the sentence beside them;
   a payment plan that does not line up reads as three unrelated numbers.
   min-height reserves all three rows before js/kauf.js fills them, so the pay
   button never moves down once the catalogue answers. */
.kaufen-stufen { counter-reset: none; margin: .1rem 0 .9rem; }
.kaufen-stufen li {
  display: grid;
  grid-template-columns: 5.6rem 1fr;
  gap: .2rem .8rem;
  align-items: baseline;
  padding: .55rem 0;
  border-top: 1px solid var(--line);
  min-height: 3.1rem;
}
.kaufen-stufen .kaufen-stufe-num {
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 700;
  color: var(--brass);
}
.kaufen-stufen .kaufen-stufe-t { font-size: .9rem; line-height: 1.5; color: var(--bone-dim); }
/* Under ~400px the figure column eats the sentence, so the two stack. */
@media (max-width: 400px) {
  .kaufen-stufen li { grid-template-columns: 1fr; min-height: 4.2rem; }
}

/* Two columns for the included / not included pair from 700px — the same split
   the live page uses, so the block is recognisable to someone who read /fertig
   five minutes ago. */
@media (min-width: 700px) {
  .kaufen-listen { display: grid; grid-template-columns: 1fr 1fr; gap: 1.6rem; }
  .kaufen-listen .kaufen-teil + .kaufen-teil { margin-top: 0; padding-top: 0; border-top: 0; }
}


/* =============================================================================
   6. FORMULAR — data-sid="formular"
   Seven visible fields, three separate un-ticked checkboxes, one button. Every
   error is written next to the field that caused it and announced once.
   ========================================================================== */
.kauf-form .field label { font-size: .74rem; }
.kauf-form input[readonly] {
  color: var(--bone-dim);
  background: rgba(239,233,222,.04);
  cursor: not-allowed;
}
/* The helper under the business-number field. It is long on purpose — it is the
   list of every document that counts, and a buyer who cannot find his own on it
   leaves. aria-describedby ties it to the input, so a screen reader reads it
   with the label instead of after it. */
.field-hilfe {
  font-size: .82rem;
  line-height: 1.5;
  color: var(--bone-dim);
  margin-top: .4rem;
}
/* The same helper appears under the postal-code field inside the shelf panel,
   which is kraft stock and not ink. Bone on kraft measured as unreadable in the
   1440 contact-path screenshot, so that one instance takes the card's ink. */
.verf .field-hilfe { color: var(--kraft-dim); }
.kauf-fields-2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0 1rem; }
@media (max-width: 420px) { .kauf-fields-2 { grid-template-columns: 1fr; } }

.kauf-checks { display: flex; flex-direction: column; gap: 1rem; margin: 1.6rem 0; }
.kauf-checks .check-row label { color: var(--bone); line-height: 1.5; }
/* 44px of tappable target around an 18px box, without moving the box: the
   phone is the primary device and three mis-taps here is three lost sales. */
.kauf-checks .check-row input[type="checkbox"] { width: 20px; height: 20px; }
/* A .check-row is a flex row, so its message is a SIBLING after it. The indent
   lines the message up under the label instead of under the box. */
.kauf-checks .check-row + .error-msg { margin: -.5rem 0 0 1.9rem; }

.kauf-submit {
  width: 100%;
  justify-content: center;
  font-size: 1.05rem;
  font-weight: 720;
  padding: 1.05rem 1.6rem;
  min-height: 56px;
}
.kauf-submit[disabled] { opacity: .65; cursor: progress; }

/* The form-level status line: one live region for "sending", for a server error
   and for the one failure that has its own route (the area went in the meantime). */
.kauf-status { margin-top: 1rem; min-height: 1.5rem; font-size: .95rem; line-height: 1.5; }
.kauf-status .error-msg { margin-top: 0; }
.kauf-status a { text-decoration: underline; text-underline-offset: 3px; }

/* The human fallback under the button. Never a dead end: if anything at all
   goes wrong, there is still a phone number on the screen. */
.kauf-hilfe {
  margin-top: 1.3rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line-soft);
  font-size: .92rem;
  color: var(--bone-dim);
}
.kauf-hilfe a { text-decoration: underline; text-underline-offset: 3px; color: var(--bone); }

/* The "someone else took it while you were typing" panel. It replaces the form,
   so the button can never sit there promising something that is gone. */
.kauf-weg { border: 1px solid var(--stamp-red, #C8552E); padding: clamp(1rem, 3vw, 1.5rem); }
.kauf-weg h2 { font-size: 1.3rem; margin-bottom: .7rem; }
.kauf-weg p { color: var(--bone-dim); margin-bottom: .9rem; }
.kauf-weg .verf-alt li { border-top-color: var(--line); }
.kauf-weg .verf-alt .verf-alt-preis { color: var(--bone-dim); }
.kauf-weg .verf-angebot { border-color: var(--line); background: rgba(239,233,222,.03); }


/* =============================================================================
   7. DANKE — data-sid="danke"
   The return page. Three states, all designed: paid, pending, failed. It polls
   the signed token in the URL; no cookie and no storage is involved anywhere.
   ========================================================================== */
/* THE FOOTER RESERVE (measured 19.09.2026, CLS 0.53 -> under 0.05).
   Everything on this page below the status block is hidden until the status
   answer arrives: the file-room box, the four steps and the upsell. Without a
   reserve the first paint is half a screen tall, the injected site-footer sits
   inside the viewport, and the moment the answer lands the footer travels two
   thousand pixels down — one shift, and it was the whole CLS of the page.
   A full viewport of minimum height puts the footer below the fold from the
   first paint, so it can never be seen moving. It costs nothing on the long
   states, which are taller than this anyway. */
.danke-page { padding-top: clamp(5.5rem, 12vh, 7.5rem); padding-bottom: clamp(3rem, 8vw, 6rem); min-height: 100vh; }
.danke-wrap { max-width: 720px; }

/* The reserve: the whole block is written by js/kauf.js and swaps between the
   waiting state and one of six stages while the payment settles, so the help
   block under it would travel every time the answer lands.
   13rem was measured against the OLD two-state page; the stage sentences that
   came with the three-payment model are up to 465px tall at 390px (measured
   19.09.2026). 22rem is the middle of that range: it cannot be the tallest
   without leaving a hole under the short states, but it halves every distance
   and takes the page's remaining shift to under 0.03. */
.danke-state { margin-bottom: clamp(2rem, 5vw, 3rem); min-height: 22rem; }
.danke-state h1 {
  font-family: var(--font-disp);
  font-variation-settings: var(--wide);
  font-weight: 780;
  font-size: clamp(2rem, 6vw, 3.2rem);
  margin-bottom: .8rem;
}
.danke-state .lead { margin-bottom: 1.4rem; }

/* THE OPEN PAYMENT — data-sid="status"
   Shown only on the two stages that really have money outstanding (start_due,
   final_due), for the buyer who deleted the mail with the link in it. Brass,
   because on those two stages it IS the one thing to do; the deadline line
   under it is deliberately quiet so the button is not a threat.
   min-height on .danke-state already reserves the space for both. */
.danke-state .danke-pay { margin-bottom: .7rem; }
.danke-state .danke-pay + .mono { margin-bottom: 1.2rem; }

/* The pulse is the only thing on this page that moves, and it moves only while
   the payment is genuinely still open — a spinner that never stops is a lie. */
.danke-warte {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  font-family: var(--font-mono);
  font-size: .78rem;
  color: var(--bone-dim);
}
.danke-puls {
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--brass);
  animation: danke-puls 1.4s ease-in-out infinite;
}
@keyframes danke-puls { 0%,100% { opacity: .25; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .danke-puls { animation: none; opacity: .8; } }

/* THE WAY OUT — data-sid="hilfe"
   On every state of this page, including the one with no token at all. Money
   has just moved; a page that answers "you need your order link" and nothing
   else leaves the buyer with no way to reach anybody. Quiet type and a hairline
   so it never competes with the file-room box, but always present.
   The first line is hidden by js/kauf.js on the states that already printed the
   same sentence themselves, so nobody reads it twice. */
.danke-hilfe {
  border-top: 1px solid var(--line-soft);
  padding-top: 1rem;
  margin-bottom: clamp(2rem, 5vw, 2.8rem);
  font-size: .9rem;
  line-height: 1.55;
  color: var(--bone-dim);
}
.danke-hilfe p { margin-bottom: .4rem; }
.danke-hilfe p:last-child { margin-bottom: 0; }
.danke-hilfe a { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }

/* The file room link — the single most important object on the page, so it gets
   the page's only brass button and a box of its own. */
.danke-raum {
  border: 1px solid var(--brass);
  padding: clamp(1.1rem, 3vw, 1.6rem);
  margin-bottom: clamp(2rem, 5vw, 2.8rem);
}
.danke-raum h2 { font-size: 1.3rem; margin-bottom: .6rem; }
.danke-raum p { color: var(--bone-dim); font-size: .95rem; margin-bottom: 1rem; }
.danke-raum-link { word-break: break-all; font-family: var(--font-mono); font-size: .76rem; color: var(--bone-dim); margin-top: .8rem; }

/* WAS SIE BESTELLT HABEN — data-sid="bestellt" (BRIEF §19/§20)
   Zwei bestätigte Zeilen: die Sprachen der Seite und, wenn eines gewählt wurde,
   das Geschenk. Kein Kasten mit Messingkante — das ist keine Aufgabe für den
   Käufer, sondern eine Quittung. Die Messinglinie links reicht dafür. */
.danke-bestellt {
  border-left: 2px solid var(--brass);
  padding-left: 1rem;
  margin-bottom: clamp(1.6rem, 4vw, 2.2rem);
}
.danke-bestellt-z { font-weight: 660; line-height: 1.45; }
.danke-bestellt-z + .danke-bestellt-z,
.danke-bestellt-z + div .danke-bestellt-z { margin-top: .5rem; }
.danke-bestellt-b { color: var(--bone-dim); font-size: .93rem; line-height: 1.5; margin-top: .2rem; }

/* DER VERTRAG — data-sid="vertrag" (BRIEF §17)
   Steht über dem Datei-Raum, aber ohne dessen Messingkasten: der Datei-Raum ist
   das, was der Käufer JETZT tun soll, das PDF das, was er ablegen soll. Zwei
   gleich laute Kästen nebeneinander hätten beides entwertet. */
.danke-vertrag {
  border: 1px solid var(--line);
  padding: clamp(1.1rem, 3vw, 1.5rem);
  margin-bottom: clamp(1.6rem, 4vw, 2.2rem);
}
.danke-vertrag h2 { font-size: 1.2rem; margin-bottom: .5rem; }
.danke-vertrag p { color: var(--bone-dim); font-size: .95rem; margin-bottom: 1rem; }

/* The steps as a ruled ledger, numbered. Not cards in a row: this is a sequence
   in time, and a row of boxes reads as a set of options. How many steps there
   are depends on the prepayment path (js/kauf.js, malSchritte). */
/* Der Abschlussstrich sitzt auf der LISTE, nicht auf der letzten Zeile: welche
   Zeile die letzte ist, hängt am Zahlungsweg, und eine ausgeblendete letzte
   Zeile hätte den Strich mitgenommen. */
.danke-schritte { margin-bottom: clamp(2rem, 5vw, 3rem); border-bottom: 1px solid var(--line); }
.danke-schritte li {
  display: grid;
  grid-template-columns: 2.4rem minmax(0, 1fr);
  gap: 0 .9rem;
  padding: 1rem 0;
  border-top: 1px solid var(--line);
}
.danke-schritt-num {
  font-variation-settings: var(--cond);
  font-weight: 230;
  font-size: 1.7rem;
  line-height: 1;
  color: var(--bone-dim);
}
.danke-schritt-t { font-weight: 660; margin-bottom: .25rem; }
.danke-schritt-b { color: var(--bone-dim); font-size: .95rem; line-height: 1.5; }

/* The upsell. Only services that exist on services.html / fertig.html; an item
   without a published price links to /contact instead of inventing one. */
.danke-mehr { border-top: 1px solid var(--line); padding-top: clamp(1.6rem, 4vw, 2.2rem); }
.danke-mehr > h2 { font-size: 1.45rem; margin-bottom: .5rem; }
.danke-mehr > .lead { font-size: 1rem; margin-bottom: 1.4rem; }
.danke-mehr li { padding: 1rem 0; border-top: 1px solid var(--line-soft); }
.danke-mehr li:first-of-type { border-top: 0; }
.danke-mehr-t {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .3rem .8rem;
  font-weight: 660;
  margin-bottom: .3rem;
}
.danke-mehr-preis { font-family: var(--font-mono); font-size: .8rem; color: var(--brass); font-weight: 700; }
.danke-mehr-b { color: var(--bone-dim); font-size: .95rem; line-height: 1.5; margin-bottom: .5rem; }
.danke-mehr a { text-decoration: underline; text-underline-offset: 4px; font-weight: 600; }
.danke-mehr a:hover { color: var(--brass); }


/* =============================================================================
   8. FUNNEL — the site-wide floating CTAs stay off the checkout
   js/components.v2.js excludes /briefing, /konfigurator and danke by path, and
   this page is neither. A sticky WhatsApp bar under a "zahlungspflichtig
   reservieren" button is a second primary action over the first one, so it is
   hidden HERE rather than by editing the shared component for one page. The
   reserve it claims on <body> is released with it or the page keeps 76px of
   empty space at the bottom.
   ========================================================================== */
body[data-page="kauf"] #sticky-cta,
body[data-page="kauf"] #float-cta { display: none !important; }
body[data-page="kauf"].has-sticky-cta { padding-bottom: 0; }

/* Dieselbe Begruendung fuer die schwebende "Ihre Farben"-Leiste (js/farben.js
   haengt #farben an <body>): sie legt sich auf dem Telefon ueber den Text der
   Kaufseite und ueber die Reservierungsdaten auf der Dankeseite. Die Palette
   selbst darf weiterlaufen — nur ihre Bedienleiste ist hier fehl am Platz.
   farben.js kennt nur /briefing und /konfigurator, und die Datei wird fuer
   diese zwei Seiten nicht angefasst. */
body[data-page="kauf"] #farben,
body[data-page="danke-kauf"] #farben { display: none !important; }

/* Auf dem Regal (der Einstiegsseite) darf die Palette bleiben — dort ist sie
   ein echtes Angebot und kein Fremdkoerper. Nur unter 640px legt sich ihre
   Bedienleiste ueber den Hinweis unter dem "Seiten zeigen"-Knopf ("Danach
   steht bei jeder Seite ... prüfen"), gemessen bei 390px in
   docs/kauf-staging3-index-fold-390.png. Ab 640px ist rechts genug Platz. */
@media (max-width: 639.98px) {
  body[data-page="fertig-kauf"] #farben { display: none !important; }
}

/* =============================================================================
   9. DER SPAM-HINWEIS — data-sid="status" / data-sid="verfuegbarkeit"
   One sentence, shown wherever the buyer has just been told a mail is coming.
   Deliberately the quietest type on the page: it is a footnote for the person
   who cannot find the message, not a warning to everyone else. Reserved height
   is unnecessary — it is appended in the same paint as the text above it.
   ========================================================================== */
.danke-spam-hinweis,
.verf-spam-hinweis {
  font-size: .82rem;
  line-height: 1.5;
  margin-top: .7rem;
  opacity: .72;
}
.danke-spam-hinweis { color: var(--bone-dim); max-width: 52ch; }
.verf-spam-hinweis  { color: var(--kraft-dim); }
.kauf-weg .verf-spam-hinweis { color: var(--bone-dim); }


/* =============================================================================
   10. OHNE-JS — data-sid="ohne-js" (all three pages)
   Ordinary markup, NOT <noscript>. WHY: a crawler that reads the page without
   running scripts — and every AI agent that reads it on a buyer's behalf — skips
   a <noscript> block entirely, so the only description of what this page does
   would be invisible to exactly the readers who cannot see the rendered form.
   It is in the DOM for everyone, and js/kauf.js sets .js-on on <html> in its
   first statement, which hides it before first paint. A visitor with JS on
   therefore never sees it and it can never shift the layout.
   ========================================================================== */
.ohne-js {
  border: 1px solid var(--brass);
  padding: clamp(1rem, 3vw, 1.5rem);
  margin-bottom: clamp(1.6rem, 4vw, 2.2rem);
}
.ohne-js h2 { font-size: 1.2rem; margin-bottom: .6rem; }
.ohne-js p { color: var(--bone-dim); font-size: .95rem; line-height: 1.55; margin-bottom: .7rem; }
.ohne-js p:last-child { margin-bottom: 0; }
.ohne-js a { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }
/* Inside a kraft card (the shelf panel's fallback line) the ink palette would
   be unreadable, so that one instance borrows the card's own colours. */
.verf .ohne-js { border-color: var(--kraft-edge); }
.verf .ohne-js p { color: var(--kraft-dim); }
.verf .ohne-js a { color: var(--kraft-ink); }
html.js-on .ohne-js { display: none; }


/* =============================================================================
   11. HERO-VERFUEGBARKEIT — data-sid="hero-verfuegbarkeit"
   The availability question in the hero of the shelf page, above the fold on a
   phone. WHY here and not only on the cards: the check is the only thing on this
   page that answers "can I have it", and a visitor who has to scroll past twelve
   cards to discover that the question exists has already decided it does not.
   It stores country + postal code and hands them to every card's own check, so
   a card costs ONE tap after this.
   The box is sized by its content and nothing inside it is written by script, so
   it cannot shift the shelf below it.
   ========================================================================== */
.hero-verf {
  border: 1px solid var(--line);
  border-left: 2px solid var(--brass);
  padding: clamp(.9rem, 3vw, 1.2rem);
  margin-top: 1.6rem;
  max-width: 34rem;
}
.hero-verf-titel { font-weight: 680; font-size: 1.02rem; margin-bottom: .75rem; }
/* Country, postal code and the button on one row from 480px up; stacked below,
   because three controls in a 350px row leaves the postal field four characters
   wide and the button clipped. */
.hero-verf-row { display: grid; gap: .7rem; }
@media (min-width: 480px) {
  .hero-verf-row { grid-template-columns: minmax(0, 8rem) minmax(0, 1fr) auto; align-items: end; gap: .7rem .6rem; }
}
.hero-verf .field { margin-bottom: 0; }
.hero-verf label { font-size: .7rem; margin-bottom: .3rem; }
/* 16px, not smaller: iOS zooms the whole layout when a focused input is under
   16px, and a zoomed layout on the first tap of the page reads as broken. */
.hero-verf input[type="text"],
.hero-verf select { font-size: 16px; padding: .7rem .8rem; }
.hero-verf-btn {
  background: var(--brass);
  color: var(--ink);
  font-weight: 700;
  font-size: .95rem;
  padding: .72rem 1.3rem;
  border-radius: var(--radius-pill);
  min-height: 46px;
  white-space: nowrap;
  transition: filter .18s ease, transform .18s ease;
}
.hero-verf-btn:hover { filter: brightness(1.07); transform: translateY(-1px); }
/* ANTI-CLS RESERVE. Der Hilfssatz unter der Postleitzahl ("Postleitzahl oder
   ZIP-Code") steht nur bei Ländern außerhalb von AT/DE/CH da — und das weiß die
   Seite erst, wenn api/laender.json geantwortet hat, also nach dem ersten
   Paint. Die Zeile schob bis dahin Knopf, Hinweis und Fakten nach unten. Sie
   wird jetzt immer freigehalten; js/kauf.js leert sie nur noch (plzFeldFuerLand
   mit halteZeile). 1.5em ist genau die eine Zeile, die .field-hilfe hoch ist. */
#hero-plz-hilfe { min-height: 1.5em; }
.hero-verf-hint { font-family: var(--font-mono); font-size: .72rem; color: var(--bone-dim); margin-top: .7rem; line-height: 1.5; }
.hero-verf .error-msg { margin-top: .5rem; }
/* The shelf buttons change wording once a postal code is known (js/kauf.js
   rewrites them), and the card that is being checked is marked so the eye finds
   it after the scroll. */
.shelf-card.is-ziel { outline: 2px solid var(--brass); outline-offset: 4px; }


/* =============================================================================
   12. KONTAKT-WEG — data-sid="verfuegbarkeit" / data-sid="formular"
   The calm answer when a country or a postal code is not in the directory
   (BRIEF §14). It is deliberately NOT styled as an error: nothing went wrong,
   the place is simply not in a data file yet, and the buyer is one message away
   from an answer. Shown on the shelf panel and on the checkout.
   ========================================================================== */
.verf-kontakt { margin-top: .2rem; }
.verf-kontakt p { font-size: .9rem; line-height: 1.5; margin-bottom: .6rem; }
.verf-kontakt a {
  display: inline-block;
  margin-right: .9rem;
  font-weight: 660;
  text-decoration: underline;
  text-underline-offset: 4px;
}
.kauf-weg .verf-kontakt p { color: var(--bone-dim); }


/* =============================================================================
   13. WER DAS BAUT — data-sid="wer-das-baut"
   The trust strip: a face, a name, a trade number and an address, directly above
   the pay area and once on the shelf page. Every fact on it is copied from
   about.html #nachweis and impressum.html and nothing is added — a studio that
   invents a credential next to a pay button has lost the argument it is trying
   to win. No testimonial, because there is no real one yet (about.html says so
   in its own words, and that sentence is repeated here rather than paraphrased).
   The portrait has its intrinsic 560x1222 on the element and a fixed box in CSS,
   so it reserves its own space and cannot shift the button below it.
   ========================================================================== */
.wer {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr);
  gap: .9rem 1.1rem;
  border: 1px solid var(--line);
  padding: clamp(.9rem, 3vw, 1.3rem);
  margin-bottom: 1.4rem;
}
/* Bild plus Bildunterschrift stehen jetzt zusammen in der linken Spalte, also
   ist die <figure> das Rasterfeld und nicht mehr das <img>. */
.wer-figur { width: 84px; }
.wer-portrait {
  width: 84px; height: 84px;
  object-fit: cover;
  /* Kein object-position mehr: img/beri-portrait-studio.webp ist bereits
     quadratisch geschnitten, das Gesicht sitzt in der Mitte und der Kakadu
     links davon — beide liegen im Kreis. Der alte Wert (50% 8%) gehörte zum
     hochkanten beri-portrait.jpg, das diese Seiten nicht mehr laden. */
  border: 1px solid var(--line);
  border-radius: 50%;
}
/* Die KI-Offenlegung. 11 px ist das Minimum, nicht das Ziel — und
   --bone-dim auf --ink liegt über 4.5:1, also bleibt sie lesbar.
   Sie steht unter dem Bild und nicht daneben, weil die rechte Spalte den Namen
   und die Gewerbedaten trägt und eine Zeile dazwischen sie auseinanderreißen
   würde. */
.wer-kifoto {
  font-size: 11px;
  line-height: 1.35;
  color: var(--bone-dim);
  margin-top: .4rem;
  text-align: center;
}
.wer-name { font-weight: 700; font-size: 1.05rem; margin-bottom: .15rem; }
.wer-rolle { font-size: .9rem; color: var(--bone-dim); margin-bottom: .6rem; }
.wer-fakten { font-family: var(--font-mono); font-size: .74rem; line-height: 1.7; color: var(--bone-dim); }
.wer-fakten a { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }
/* The honest no-testimonials line spans both columns: it is about the studio,
   not about the portrait next to it. */
.wer-ehrlich {
  grid-column: 1 / -1;
  border-top: 1px solid var(--line-soft);
  padding-top: .8rem;
  font-size: .9rem;
  line-height: 1.55;
  color: var(--bone-dim);
}
.wer-ehrlich a { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }
/* On the shelf page the strip sits on ink between two kraft rows, so it gets a
   little more room and the wider measure the section around it uses. */
.wer-regal { margin: clamp(2.4rem, 5vw, 3.4rem) 0 0; max-width: 46rem; }
@media (max-width: 380px) {
  .wer { grid-template-columns: 1fr; }
  .wer-figur { width: 72px; }
  .wer-portrait { width: 72px; height: 72px; }
  /* Unter der 72-px-Spalte bricht "Foto mit KI bearbeitet" in drei Zeilen und
     sieht aus wie ein Fehler. Links ausgerichtet und über die ganze Breite,
     weil die Spalte hier ohnehin einspaltig ist. */
  .wer-kifoto { width: auto; text-align: left; }
}


/* =============================================================================
   13b. SCHRITT EINS: DER ZAHLWEG — data-sid="zahlweise-schritt"
   Die Wahl des Zahlweges und das Geschenk standen bis zur Integrationsrunde am
   Knopf. Gemessen bei 390 px: 1.400 px zwischen dem letzten Feld und dem Knopf,
   weil beide Karten plus die Stufenliste dazwischen lagen. Sie kosten nichts,
   sie bauen die halbe Seite um, und sie sind die einzige Entscheidung, die vor
   den Daten kommt — also stehen sie jetzt als Erstes im Formular.
   Keine eigene Optik: die beiden Fieldsets bringen ihre mit (Abschnitt 15).
   ========================================================================== */
.zahlweise-schritt { margin-bottom: 1.8rem; }
.zahlweise-schritt > h2 {
  font-family: var(--font-mono);
  font-weight: 430;
  font-size: .78rem;
  letter-spacing: .04em;
  color: var(--bone-dim);
  margin-bottom: .9rem;
}

/* =============================================================================
   14. ZAHLBEREICH — data-sid="zahlbereich"
   What is directly above the pay button and nothing else: one line with the
   chosen path's amounts, the one refund sentence, the three ticks. WHY so
   little: measured at 390px there were 2,531px between the last form field and
   the button, because the whole "Das kaufen Sie" block sat in between; the
   prepayment wave put 1,400px back. That block is the reason to buy and now
   stands ABOVE the form, and so do the payment choice and the gift; what a
   thumb needs in the last screen before paying is the amount, the way out,
   and the boxes.
   ========================================================================== */
/* Die Zusammenfassung des gewählten Weges. min-height, weil der 100-%-Satz
   kürzer ist als der 10-%-Satz: ohne sie springt der Knopf beim Umschalten. */
.zahl-kurz {
  min-height: 2.9rem;
  margin: 0 0 .9rem;
  font-size: .95rem;
  line-height: 1.5;
  color: var(--bone);
}
/* Die Erinnerung an die Belehrung, die jetzt oben steht. Leise, aber mit einem
   Link, der wirklich hinführt — eine Pflichtinformation, die man sucht, ist
   keine. */
.vb-erinnerung {
  font-size: .88rem;
  line-height: 1.5;
  color: var(--bone-dim);
  margin: 0 0 1.2rem;
}
.vb-erinnerung a { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }
.zahlbereich {
  border: 1px solid var(--brass);
  padding: clamp(1rem, 3vw, 1.4rem);
  margin-top: 1.6rem;
}
.zahlbereich > h2 {
  font-family: var(--font-mono);
  font-weight: 430;
  font-size: .78rem;
  letter-spacing: .04em;
  color: var(--bone-dim);
  padding-bottom: .8rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: .9rem;
}
/* The three figures as a compact ledger. Same grid as .kaufen-stufen so the
   short version and the long version upstairs are recognisably one object, but
   without the explanatory sentences — those were read on the way down. */
.zahl-stufen { margin-bottom: .9rem; }
.zahl-stufen li {
  display: grid;
  grid-template-columns: 5.6rem minmax(0, 1fr);
  gap: .2rem .8rem;
  align-items: baseline;
  padding: .45rem 0;
  border-top: 1px solid var(--line);
  min-height: 2.5rem;
}
.zahl-stufen li:first-child { border-top: 0; }
.zahl-stufen .zahl-num { font-family: var(--font-mono); font-size: 1.02rem; font-weight: 700; color: var(--brass); }
.zahl-stufen li.is-jetzt .zahl-num { font-size: 1.35rem; }
.zahl-stufen .zahl-lab { font-size: .88rem; line-height: 1.45; color: var(--bone-dim); }
.zahl-rueck { font-size: .93rem; line-height: 1.55; color: var(--bone-dim); margin-bottom: .2rem; }
.zahl-rueck strong { color: var(--bone); font-weight: 660; }

/* Under the button: the guarantee, then what happens on the next screen, then
   the way out. Three lines, in the order the last doubt arrives in. */
.kauf-garantie {
  text-align: center;
  font-size: .92rem;
  line-height: 1.5;
  color: var(--brass);
  margin-top: .7rem;
}
.kauf-weiter {
  font-size: .88rem;
  line-height: 1.55;
  color: var(--bone-dim);
  margin-top: .9rem;
}
/* Same size as the pay-button's own sub-line on purpose: at the moment of doubt
   the way to a human must not look like a footnote. */
.kauf-zweifel { font-size: .95rem; line-height: 1.5; margin-top: .9rem; }
.kauf-zweifel a { color: var(--bone); font-weight: 660; text-decoration: underline; text-underline-offset: 4px; }

/* DIE DREI ZAHLUNGSWEGE + DAS GESCHENK — data-sid="zahlweise", "geschenk"
   (BRIEF §18/§19). Dieselbe Karte wie „Wie kaufen Sie?" oben im Formular, weil
   es dieselbe Art von Wahl ist: eine Entscheidung, die den Rest der Seite
   umbaut. Gleiche Optik heißt hier, dass ein Käufer sie beim zweiten Mal
   wiedererkennt statt sie neu zu lernen.

   WARUM KEINE HERVORGEHOBENE KARTE: die 10 % sind vorbelegt, und eine optisch
   „empfohlene" Vollzahlung daneben wäre ein Verkaufsdruck an der Stelle, an der
   die Seite gerade verspricht, dass alles zurückkommt. */
.pay-wahl,
.geschenk-wahl {
  border: 1px solid var(--line);
  padding: .95rem 1rem 1.05rem;
  margin: 0 0 1.2rem;
}
.pay-wahl legend,
.geschenk-wahl legend {
  padding: 0 .4rem;
  font-size: .82rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--brass);
}
/* Die Überschrift trägt einen Betrag, den js/kauf.js erst nach der Antwort des
   Katalogs schreibt. Die Zeile wird deshalb VORGEHALTEN — ohne das springen
   beim Eintreffen der Zahlen drei Karten gleichzeitig und schieben den Knopf.

   GRÖSSER ALS DIE ÜBERSCHRIFT BEI „Wie kaufen Sie?", und das mit Absicht: dort
   stehen Wörter, hier stehen die drei Summen, zwischen denen der Käufer wählt.
   Gemessen kamen sie aus der globalen label-Regel mit 11,84 px heraus — kleiner
   als der Erklärsatz darunter, und „€ 119" wäre neben „€ 1.190" genau dort zu
   verwechseln, wo es am teuersten ist. Die Schrift bleibt die Mono der
   Kaufseite: jede andere Zahl auf dieser Seite steht in derselben. */
.pay-t {
  min-height: 1.4em;
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.35;
}
/* Zwei Zeilen auf 390 px: „…ohne zweiten Zahlungsschritt. Die Restzahlung von
   € 595 folgt nach Ihrer Freigabe." Gemessen, nicht geschätzt. */
.pay-b { min-height: 3em; }
/* Der Rückgabesatz steht unter allen dreien und gehört zu keiner der Karten,
   also bekommt er die Trennlinie und nicht den Einzug einer Karte. */
.pay-alle {
  margin: 1rem 0 0;
  padding-top: .9rem;
  border-top: 1px solid var(--line);
  font-size: .9rem;
  line-height: 1.55;
  color: var(--bone-dim);
}

/* Das Geschenk erscheint erst, wenn „Alles jetzt" gewählt ist. Es wächst ein,
   statt da zu sein: ein Block, der ohne Bewegung auftaucht, liest sich wie ein
   Sprung der Seite. Die Bewegung ist kurz und einmalig — und wer sie abbestellt
   hat, bekommt sie nicht. */
@keyframes geschenk-auf {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
.geschenk-wahl:not([hidden]) { animation: geschenk-auf .26s ease-out both; }
@media (prefers-reduced-motion: reduce) {
  .geschenk-wahl:not([hidden]) { animation: none; }
}
.geschenk-spaeter {
  margin: 1rem 0 0;
  padding-top: .9rem;
  border-top: 1px solid var(--line);
  font-size: .9rem;
  line-height: 1.55;
  color: var(--bone-dim);
}
/* Die Meldung steht unter der ganzen Gruppe (js/kauf.js, setFieldError). Ohne
   den Abstand klebt sie am Rahmen des fieldsets. */
.geschenk-wahl + .error-msg { margin: -.7rem 0 1.2rem; }

/* DIE HÖHE DER STUFENLISTEN, VORGEHALTEN.
   Ein Weg mit zwei oder einer Stufe blendet die übrigen Zeilen aus statt sie zu
   entfernen, und die Liste behält die Höhe der längsten Fassung: sonst springt
   der Knopf beim Umschalten unter dem Daumen weg. Die Zahlen sind 3 x die
   min-height der jeweiligen Zeile. (Das Ausblenden selbst macht werk.css mit
   `[hidden]{display:none !important}` — das schlägt auch das display:grid der
   Zeile, hier ist also keine eigene Regel nötig.) */
/* Der Rückgabesatz hat zwei Fassungen, die fast gleich lang sind. Fast heißt:
   eine Zeile Unterschied, und die läge direkt über dem Knopf. Die Box hält die
   Höhe der längeren. */
.zahl-rueck-box { min-height: 4.4rem; }
.zahl-stufen { min-height: 7.5rem; }
.kaufen-stufen { min-height: 9.3rem; }
@media (max-width: 400px) {
  .kaufen-stufen { min-height: 12.6rem; }
}


/* =============================================================================
   15. STICKY-ZAHLEN — data-sid="sticky-zahlen"
   The amount and the button follow the buyer down the checkout, on phone AND on
   desktop. WHY on desktop too: the form is long enough at 1440 that the button
   leaves the viewport while the last fields are being filled.
   It is NOT a second primary action — it submits the same form, or scrolls to
   the first field that is still wrong.
   It hides itself while an input has focus on small screens (js/kauf.js sets
   .feld-fokus), because on a phone it otherwise sits on top of the field the
   keyboard has just pushed up.
   ========================================================================== */
.kauf-sticky {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 95;
  display: flex;
  gap: .8rem;
  align-items: center;
  justify-content: center;
  background: var(--ink);
  border-top: 1px solid var(--line);
  padding: .6rem var(--gutter);
  padding-bottom: calc(.6rem + env(safe-area-inset-bottom, 0px));
  transform: translateY(0);
  transition: transform .22s ease, opacity .22s ease;
}
.kauf-sticky[hidden] { display: none !important; }
.kauf-sticky-btn {
  width: 100%;
  max-width: 34rem;
  justify-content: center;
  font-size: 1rem;
  font-weight: 700;
  padding: .85rem 1.2rem;
  min-height: 52px;
  text-align: center;
}
/* The reserve on <body>, so the last thing on the page is never sitting under
   the bar. Set by js/kauf.js together with the bar itself, so a page without
   JavaScript keeps neither.
   The safe-area inset is part of the sum because the BAR adds it to its own
   padding (three rules up): on an iPhone the bar is 84px PLUS the home-bar
   inset tall, and a flat 84px reserve left the last footer line under it.
   Measured 19.09.2026 at 390 px. */
/* The selector has to be this specific: section 8 above resets
   body[data-page="kauf"].has-sticky-cta to 0, and js/components.v2.js puts
   has-sticky-cta on every page — so a plain body.hat-kauf-sticky lost, the
   reserve was 0 and the last footer line sat under the bar at 390 px
   (measured 19.09.2026, before this line).
   The height comes from js/kauf.js, which measures the bar instead of trusting
   a number here. It is 74px in all four languages since the French short label
   was shortened (20.09.2026; it wrapped to two lines at 390 and made the bar
   100px), and the measurement stays because a label is a translation and a
   translation can grow again. 84px is only the fallback for the frame before
   the measurement lands. */
body[data-page="kauf"].hat-kauf-sticky {
  padding-bottom: calc(var(--kauf-barh, 84px) + env(safe-area-inset-bottom, 0px));
}
/* Slid out of the way rather than display:none, so it does not re-flow the page
   under a virtual keyboard while the buyer is typing. */
@media (max-width: 768px) {
  body.feld-fokus .kauf-sticky { transform: translateY(110%); opacity: 0; pointer-events: none; }
}
@media (prefers-reduced-motion: reduce) {
  .kauf-sticky { transition: none; }
}

/* =============================================================================
   16. TEST-BANNER — data-sid="testbanner" (alle vier Seiten der Kaufstrecke)
   Solange der Server mit einem Mollie-TEST-Schlüssel läuft, sagt diese Leiste
   das auf jedem Schritt. js/kauf.js baut sie NUR, wenn action=mode "test"
   antwortet — nicht aus der URL: eine Staging-Adresse macht eine Zahlung nicht
   unecht, der Schlüssel tut das. Am Tag des Live-Schlüssels verschwindet sie
   von selbst, ohne dass jemand eine Seite anfassen muss.

   KEIN CLS, obwohl sie erst nach einer Antwort erscheint: die Leiste ist
   position:fixed und schiebt nichts im Fluss. Das Einzige, was mitwandert,
   sind .site-nav und .brand-logo — beide sind ebenfalls fixed, stehen also
   in keinem Fluss, den sie verschieben könnten. Dadurch überlappt die Leiste
   die Navigation nicht, und unten ist sie ohnehin nicht (dort sitzt der
   Zahlbalken).
   ========================================================================== */
.kauf-testbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 200;               /* über .site-nav (110) und .mobile-menu (100) */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  height: 28px;
  padding-inline: var(--gutter);
  /* Eine Nuance dunkler als --error (#C8552E): Weiß darauf rechnete zu 4,37:1
     (axe, 20.09.2026) und lag damit unter AA. #C04F2A ergibt 4,78:1. Nur hier,
     nicht am Token: --error trägt sonst überall dunkle Schrift und ist dort
     längst in Ordnung. */
  background: #C04F2A;
  color: #fff;
  font-family: var(--font-mono);
  font-size: .72rem;
  line-height: 1;
  letter-spacing: .02em;
  text-align: center;
}
/* 28px ist die Höhe der Leiste. Als eigene Variable, weil drei Regeln sie
   brauchen und eine vierte Zahl an vierter Stelle irgendwann abweicht. */
body.hat-testbar { --testbar: 28px; }
/* transform statt top: die Leiste kommt erst, wenn action=mode geantwortet hat,
   also nach dem ersten Paint. `top` zu verschieben IST eine Layout-Verschiebung
   — gemessen 20.09.2026, sie war die letzte verbliebene CLS-Quelle dieser
   Seite. Eine Transform verschiebt nur beim Zeichnen und zählt nicht. */
body.hat-testbar .site-nav,
body.hat-testbar .brand-logo { transform: translateY(var(--testbar)); }
/* Der Sprung-Link liegt sonst unter der Leiste. */
body.hat-testbar .skip-link:focus { top: calc(1rem + var(--testbar)); }


/* =============================================================================
   14. WER KAUFT + VERBRAUCHER-BLÖCKE — data-sid="wer-kauft", "fagg-info",
       "widerrufsbelehrung"
   Der zweite Kaufweg (BRIEF §15). Die Wahl steht ganz oben im Formular, weil
   sie den Rest des Formulars umbaut; die beiden Pflichtblöcke stehen direkt
   über dem Knopf, weil § 4 FAGG die Information VOR der Vertragserklärung
   verlangt und nicht als Link daneben.
   ========================================================================== */

/* Ein <fieldset> hat in jedem Browser eigene Ränder und ein eigenes Innenmaß,
   die hier nichts zu suchen haben — die Box zeichnet die Regel darunter. */
.kauf-wer-kauft {
  border: 1px solid var(--line);
  padding: .95rem 1rem 1.05rem;
  margin: 0 0 1.5rem;
}
.kauf-wer-kauft legend {
  padding: 0 .4rem;
  font-size: .82rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--brass);
}
.wahl-row { display: flex; gap: .75rem; align-items: flex-start; }
.wahl-row + .wahl-row { margin-top: .9rem; padding-top: .9rem; border-top: 1px solid var(--line); }
/* Gleiche 20px wie die Häkchen weiter unten: auf dem Telefon ist das der
   Unterschied zwischen treffen und danebentippen. margin-top rückt den Punkt
   auf die Grundlinie der ersten Zeile, statt ihn oben kleben zu lassen. */
.wahl-row input[type="radio"] { width: 20px; height: 20px; margin: .15rem 0 0; flex: 0 0 auto; }
.wahl-row label { cursor: pointer; }
.wahl-t { display: block; color: var(--bone); line-height: 1.4; }
.wahl-b { display: block; margin-top: .25rem; font-size: .88rem; line-height: 1.5; color: var(--bone-dim); }

/* Die beiden Pflichtblöcke. Auf Tinte gesetzt wie der Rest der Kaufseite, mit
   einer Messinglinie oben — dieselbe Geste wie bei der Gebietszeile: was
   rechtlich zählt, bekommt die Linie. */
.vb-box {
  border: 1px solid var(--line);
  border-top: 2px solid var(--brass);
  padding: 1rem 1rem 1.1rem;
  margin: 0 0 1.2rem;
  font-size: .92rem;
  line-height: 1.6;
}
.vb-box h3 {
  font-size: .82rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--brass);
  margin: 0 0 .8rem;
}
.vb-box p { margin: 0 0 .7rem; color: var(--bone-dim); }
.vb-box p:last-child { margin-bottom: 0; }
.vb-box strong { color: var(--bone); }
.vb-box a { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }

/* Die Pflichtangaben als Beschreibungsliste, nicht als Fließtext: sieben
   Fragen, sieben Antworten, und ein Käufer sucht genau eine davon. Ab 560px
   zweispaltig — darunter würde die Frage neben der Antwort zerbrechen. */
.vb-liste { margin: 0; }
.vb-liste dt {
  font-size: .78rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--bone);
  margin: 0 0 .2rem;
}
.vb-liste dd { margin: 0 0 .85rem; color: var(--bone-dim); }
.vb-liste dd:last-child { margin-bottom: 0; }
/* EIN PAAR, DAS NUR MANCHMAL GILT (20.09.2026). Die Pflicht-Betreuung ist
   eine Zeile dieser Liste, die nur Artikel mit `care` bekommen — und ein
   dt/dd-Paar lässt sich nicht als Paar verstecken. Es steckt deshalb in einem
   <div> (in einer <dl> ist das gültiges HTML5), und das <div> muss aus dem
   Layout verschwinden: ohne `display: contents` wird es ab 560px EIN
   Rasterfeld und quetscht Frage und Antwort in die schmale erste Spalte
   (gemessen bei 1440, docs/kauf-salon-care-fagg-de-1440.png).
   `:not([hidden])` ist nicht Kosmetik: die UA-Regel [hidden]{display:none}
   hat geringere Spezifität als diese hier, und ohne den Filter stünde der
   Block auf JEDEM Artikel. */
.vb-liste > div:not([hidden]) { display: contents; }
/* Das dd ist letztes Kind SEINES div, nicht der Liste — die Regel oben würde
   ihm den Abstand nehmen und die nächste Frage klebte daran. */
.vb-liste > div > dd { margin-bottom: .85rem; }
@media (min-width: 560px) {
  .vb-liste { display: grid; grid-template-columns: 8.5rem minmax(0, 1fr); gap: .1rem 1rem; }
  .vb-liste dt { margin: 0; padding-top: .1rem; }
  .vb-liste dd { margin: 0 0 .7rem; }
  .vb-liste > div > dd { margin-bottom: .7rem; }
}
.vb-formular { padding-top: .2rem; }

/* Die zweite Fassung des Start-Häkchens ist deutlich länger als die erste.
   Ohne das hier springt die Zeile beim Umschalten und schiebt den Knopf —
   der Platz wird also vorgehalten, nicht nachträglich gefunden. */
.kauf-checks .check-row label { min-height: 1.5em; }


/* =============================================================================
   15. RÜCKTRITTSFUNKTION — data-sid="ruecktrittsfunktion"
   § 13a FAGG (ab 1.10.2026) verlangt die Funktion „hervorgehoben und für
   Verbraucher leicht zugänglich". Deshalb bekommt sie die Messingkante und
   steht oben auf der Bestellseite — nicht im Kleingedruckten und nicht hinter
   einem Aufklapper. Nur Verbraucher sehen sie, und nur solange die Frist läuft;
   das entscheidet der Server.
   ========================================================================== */
.widerruf {
  border: 1px solid var(--line);
  border-left: 3px solid var(--brass);
  padding: 1.15rem 1.15rem 1.3rem;
  margin: 0 0 clamp(1.6rem, 4vw, 2.2rem);
}
.widerruf h2 {
  font-size: clamp(1.2rem, 3.4vw, 1.5rem);
  margin: 0 0 .6rem;
}
.widerruf > p { color: var(--bone-dim); font-size: .94rem; line-height: 1.6; margin: 0 0 .9rem; }
.widerruf .field { margin-bottom: 1rem; }

/* Der Vertrag, um den es geht: zu lesen, nicht zu ändern. Deshalb kein
   readonly-Input, das wie ein Feld aussieht, in dem man tippen könnte. */
.widerruf-label {
  display: block;
  font-size: .8rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--bone-dim);
  margin-bottom: .3rem;
}
.widerruf-vertrag {
  color: var(--bone);
  border-left: 2px solid var(--line);
  padding-left: .8rem;
  line-height: 1.5;
  margin: 0;
}

/* Der Absendeknopf trägt einen gesetzlich vorgeschriebenen Wortlaut, der
   länger ist als ein übliches Knopflabel. Volle Breite, damit er auf 390 px
   nicht umbricht und nicht beschnitten wird. */
.widerruf .btn { width: 100%; justify-content: center; min-height: 52px; }
.widerruf .btn[disabled] { opacity: .65; cursor: progress; }

.widerruf-status { margin: .8rem 0 0; min-height: 1.4rem; font-size: .93rem; line-height: 1.5; }
.widerruf-fein { margin: 1rem 0 0; font-size: .86rem; line-height: 1.55; color: var(--bone-dim); }

/* Nach dem Widerruf steht an derselben Stelle die Bestätigung. Keine Kante
   mehr in Messing: hier ist nichts mehr zu tun. */
.widerruf.is-done { border-left-color: var(--line); }
.widerruf-wann { color: var(--bone); margin: 0; }
