@font-face{font-family:'Archivo';src:url('../fonts/archivo-var-latin.woff2?v=de2f1fe8') format('woff2');font-weight:100 900;font-stretch:62% 125%;font-display:optional/* NOT swap. Archivo at wdth 125 is ~34% wider than the Arial fallback, so a swap AFTER first paint re-sizes the hero grid track, re-wraps the lead and slides the right-aligned nav: CLS 0.0751 on ~1 cold load in 6 (measured 21.08.2026). optional keeps whichever face won the ~100ms race for that document's life, so the shift cannot happen. The font is preloaded and every repeat visit is cached. */}@font-face{font-family:'Martian Mono';src:url('../fonts/martianmono-var-latin.woff2?v=64092ca6') format('woff2');font-weight:400 700;font-display:swap}@font-face{font-family:'Archivo Fallback';src:local('Arial');size-adjust:99%;ascent-override:88%;descent-override:21%;line-gap-override:0%}@font-face{font-family:'Archivo Fallback Wide';src:local('Arial');size-adjust:104.4%;ascent-override:88%;descent-override:21%;line-gap-override:0%}@font-face{font-family:'Martian Fallback';src:local('Courier New');size-adjust:116.6%;ascent-override:100%;descent-override:20%;line-gap-override:0%}
/* ============================================================================
   VARIANT B TOKENS — paper / ink / forest / brass
   ----------------------------------------------------------------------------
   WHY the A token NAMES are kept and only their VALUES flip: every component
   rule below, every inline <style> on every page, and js/aufbau.js all read
   --ink / --bone / --brass off :root. Renaming them would mean touching 18
   HTML files; re-pointing them flips the whole site with one block and is the
   only reason a swapped stylesheet can carry a full theme.

   The model inverts exactly the way the built-in "cafe" palette already proves
   is safe: --ink stops meaning "dark" and starts meaning "the page ground".

     --ink       the page ground              paper      #F0EADB
     --ink-2     the second surface           paper-dim  #E8DDC8
     --bone      body copy                    ink        #191813   14.6:1
     --bone-dim  secondary copy               ink @ .72             6.2:1
     --brass     CTA + every interactive tint forest     #203D32    9.8:1
     --line      hairline rule                ink @ .20

   WHY --brass is forest and not brass: --brass is the CTA background token and
   .btn paints its label in --ink. Real brass (#A87524) under paper-coloured
   text measures 3.1:1 — it fails. Forest measures 9.8:1. Brass survives as two
   narrower tokens below, used only where the brief allows it (accents, never
   body text).
   ========================================================================== */
:root{--variant:b;--ink:#F0EADB;--ink-2:#E8DDC8;--line:rgba(25,24,19,.20);--line-soft:rgba(25,24,19,.11);--bone:#191813;--bone-dim:rgba(25,24,19,.72);--brass:#203D32;--paper:#E8DDC8;--paper-ink:#191813;--paper-dim:rgba(25,24,19,.72);--paper-line:rgba(25,24,19,.20);--error:#8E2A14;
/* Brass, split in two because one value cannot serve both jobs.
   --accent      #A87524  3.3:1 on paper — passes the 3:1 non-text floor, so it
                          may draw marks, rules and hairlines and nothing else.
   --accent-ink  #8A5E15  4.7:1 on paper — the darkened brass an EYEBROW (small
                          text) is allowed to use.
   --rule        #9E927C  the brief's drawn rule, used where a border must read
                          as a drafted line rather than as a tint of the ink. */
--accent:#A87524;--accent-ink:#8A5E15;--rule:#9E927C;
/* Blueprint grid ink. 5% is the brief's ceiling; it is a FILL, never a border,
   and section 2 below restricts where it is allowed to appear. */
--grid:rgba(32,61,50,.05);
--font-sans:'Archivo','Archivo Fallback',Arial,sans-serif;--font-disp:'Archivo','Archivo Fallback Wide',Arial,sans-serif;--font-mono:'Martian Mono','Martian Fallback','Courier New',monospace;--wide:'wdth' 125;--cond:'wdth' 68;--radius-pill:999px;--radius-input:0;--gutter:clamp(1.25rem,4vw,4rem);--section-y:clamp(4.5rem,11vw,9.5rem);--max:1280px}*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}body{background:var(--ink);color:var(--bone);font-family:var(--font-sans);font-size:1.06rem;line-height:1.65;-webkit-font-smoothing:antialiased;overflow-x:clip}img,svg,video{display:block;max-width:100%}a{color:inherit;text-decoration:none}button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}ul,ol{list-style:none}[hidden]{display:none !important}::selection{background:var(--brass);color:var(--ink)}:focus-visible{outline:2px solid var(--brass);outline-offset:3px;border-radius:2px}.paper:focus-visible{outline-color:var(--paper-ink)}.skip-link{position:fixed;top:-100px;left:1rem;z-index:300;background:var(--brass);color:var(--ink);padding:.7rem 1.2rem;border-radius:var(--radius-pill);font-weight:650;transition:top .2s}.skip-link:focus{top:1rem}@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*::before,*::after{transition-duration:.01ms !important;animation-duration:.01ms !important;animation-iteration-count:1 !important}}h1,h2,h3{line-height:1.02;letter-spacing:-0.015em;text-wrap:balance}.display{font-family:var(--font-disp);font-variation-settings:var(--wide);font-weight:810;font-size:clamp(2.9rem,8.5vw,6.6rem);line-height:.96;text-transform:none}h1,h2,h3,h4,h5,h6,.display{overflow-wrap:break-word}h2,.h2{font-family:var(--font-disp);font-variation-settings:var(--wide);font-weight:760;font-size:clamp(1.95rem,4.6vw,3.3rem)}h3,.h3{font-weight:660;font-size:1.32rem;line-height:1.25}.lead{font-size:clamp(1.12rem,1.6vw,1.3rem);line-height:1.55;color:var(--bone-dim);max-width:56ch}.dim{color:var(--bone-dim)}.paper .dim{color:var(--paper-dim)}.mono{font-family:var(--font-mono);font-size:.8rem;font-weight:430;letter-spacing:0;line-height:1.5}.mono-dim{color:var(--bone-dim)}.paper .mono-dim{color:var(--paper-dim)}.big-stat{font-variation-settings:var(--cond);font-weight:210;font-size:clamp(3.2rem,8vw,6rem);line-height:1;letter-spacing:-0.01em}.wrap{max-width:var(--max);margin:0 auto;padding-inline:var(--gutter)}.section{padding-block:var(--section-y);position:relative}.section + .section{border-top:1px solid var(--line-soft)}.cv{content-visibility:auto;contain-intrinsic-size:auto 900px}.sec-head{display:flex;align-items:flex-end;justify-content:space-between;gap:1.5rem;margin-bottom:clamp(2.2rem,5vw,4rem)}.sec-note{text-align:right;white-space:nowrap;padding-bottom:.5rem}@media (max-width:700px){.sec-head{flex-direction:column;align-items:flex-start}.sec-note{text-align:left;padding-bottom:0;white-space:normal}}.paper{background:var(--paper);color:var(--paper-ink)}.paper::selection{background:var(--paper-ink);color:var(--paper)}.btn{display:inline-flex;align-items:center;gap:.55rem;background:var(--brass);color:var(--ink);font-weight:680;font-size:1rem;padding:.95rem 1.9rem;border-radius:var(--radius-pill);transition:transform .18s ease,filter .18s ease}.btn:hover{transform:translateY(-1px);filter:brightness(1.07)}.btn:active{transform:translateY(0)}.btn:focus-visible{outline-color:var(--bone)}.paper .btn:focus-visible{outline-color:var(--paper-ink)}.btn-ghost{display:inline-flex;align-items:center;gap:.55rem;border:1px solid var(--line);color:var(--bone);font-weight:560;font-size:1rem;padding:.95rem 1.9rem;border-radius:var(--radius-pill);transition:border-color .18s ease,transform .18s ease}.btn-ghost:hover{border-color:var(--bone);transform:translateY(-1px)}.paper .btn-ghost{border-color:var(--paper-line);color:var(--paper-ink)}.paper .btn-ghost:hover{border-color:var(--paper-ink)}.link-arrow{display:inline-flex;align-items:center;gap:.4rem;font-weight:600;text-decoration:underline;text-underline-offset:5px;text-decoration-thickness:1px;text-decoration-color:var(--line);transition:color .15s ease,text-decoration-color .15s ease}.link-arrow:hover{color:var(--brass);text-decoration-color:var(--brass)}.paper .link-arrow:hover{color:var(--paper-ink);text-decoration-color:var(--paper-ink)}.brand-logo{position:fixed;top:0;left:0;z-index:120;padding:.9rem var(--gutter);transition:opacity .25s ease}.brand-logo.faded{opacity:0;pointer-events:none}.nav-logo{display:flex;align-items:center;gap:.65rem}.nav-logo-parrot{height:30px;width:auto}.nav-logo-textblock{display:flex;flex-direction:column;line-height:1.1}.nav-logo-name{font-variation-settings:var(--wide);font-weight:740;font-size:.92rem;letter-spacing:.01em;text-transform:uppercase}.nav-logo-sub{font-family:var(--font-mono);font-size:.58rem;color:var(--bone-dim);margin-top:.2rem}.site-nav{position:fixed;top:0;left:0;right:0;z-index:110;display:flex;align-items:center;justify-content:flex-end;gap:1.6rem;padding:.9rem var(--gutter);border-bottom:1px solid transparent;transition:background-color .25s ease,border-color .25s ease}.site-nav.scrolled{background:var(--ink);border-bottom-color:var(--line-soft)}.nav-links{display:flex;gap:1.4rem}.nav-links a{font-size:.95rem;font-weight:520;color:var(--bone-dim);padding:.3rem 0;border-bottom:2px solid transparent;transition:color .15s ease,border-color .15s ease}.nav-links a:hover{color:var(--bone)}.nav-links a.active{color:var(--bone);border-bottom-color:var(--brass)}.nav-right{display:flex;align-items:center;gap:1rem}.lang-switcher{display:flex;gap:.15rem;font-family:var(--font-mono);font-size:.74rem}.lang-btn{padding:.35rem .5rem;color:var(--bone-dim);border-radius:var(--radius-pill);min-width:34px;min-height:30px}.lang-btn.active{color:var(--ink);background:var(--bone);font-weight:700}.lang-btn:not(.active):hover{color:var(--bone)}.nav-cta{background:var(--brass);color:var(--ink);font-weight:660;font-size:.92rem;padding:.6rem 1.25rem;border-radius:var(--radius-pill);transition:transform .18s ease,filter .18s ease}.nav-cta:hover{transform:translateY(-1px);filter:brightness(1.07)}.site-footer{border-top:1px solid var(--line);padding:clamp(3rem,7vw,5.5rem) var(--gutter) 2rem;max-width:var(--max);margin:0 auto}.footer-grid{display:grid;grid-template-columns:1.4fr 1fr;gap:clamp(2rem,5vw,4.5rem);padding-bottom:3rem}@media (max-width:820px){.footer-grid{grid-template-columns:1fr}}.footer-logo{display:flex;align-items:center;gap:.65rem;margin-bottom:1.1rem}.footer-logo-parrot{height:34px;width:auto}.footer-logo-textblock{display:flex;flex-direction:column;line-height:1.1}.footer-logo-name{font-variation-settings:var(--wide);font-weight:740;font-size:.95rem;text-transform:uppercase}.footer-logo-sub{font-family:var(--font-mono);font-size:.6rem;color:var(--bone-dim);margin-top:.2rem}.footer-brand p{color:var(--bone-dim);font-size:.95rem;max-width:34ch}.footer-col .footer-h{font-family:var(--font-mono);font-weight:430;font-size:.74rem;color:var(--bone-dim);margin-bottom:1rem}.footer-col a{display:block;padding:.28rem 0;font-size:.97rem;color:var(--bone);width:fit-content}.footer-col a:hover{color:var(--brass)}.footer-col p{color:var(--bone-dim);font-size:.85rem;margin-top:.6rem}.footer-bottom{border-top:1px solid var(--line-soft);padding-top:1.4rem;display:flex;flex-wrap:wrap;gap:.8rem 2rem;align-items:center;justify-content:space-between;font-family:var(--font-mono);font-size:.72rem;color:var(--bone-dim)}.footer-legal{display:flex;gap:.8rem}.footer-legal a:hover{color:var(--bone)}.footer-gh{display:inline-flex;align-items:center}.sticky-cta{position:fixed;left:0;right:0;bottom:0;z-index:90;display:none;background:var(--ink);border-top:1px solid var(--line);padding:.65rem var(--gutter);padding-bottom:calc(.65rem + env(safe-area-inset-bottom,0));gap:.6rem}@media (max-width:768px){.sticky-cta{display:flex}body.has-sticky-cta{padding-bottom:72px}}.sticky-cta-primary{flex:1;text-align:center;background:var(--brass);color:var(--ink);font-weight:680;font-size:.98rem;padding:.8rem 1rem;border-radius:var(--radius-pill)}.sticky-cta-second{display:inline-flex;align-items:center;gap:.45rem;border:1px solid var(--line);padding:.8rem 1.05rem;border-radius:var(--radius-pill);font-weight:560;font-size:.92rem}label{display:block;font-family:var(--font-mono);font-size:.74rem;color:var(--bone-dim);margin-bottom:.45rem}input[type="text"],input[type="email"],input[type="tel"],input[type="number"],select,textarea{width:100%;background:var(--ink-2);border:1px solid var(--line);border-radius:var(--radius-input);color:var(--bone);font:inherit;font-size:16px;padding:.8rem 1rem;transition:border-color .15s ease}input:focus,select:focus,textarea:focus{outline:none;border-color:var(--brass)}input[type="range"]:focus-visible,input[type="checkbox"]:focus-visible{outline:2px solid var(--brass);outline-offset:4px;border-radius:2px}.paper input[type="range"]:focus-visible,.paper input[type="checkbox"]:focus-visible{outline-color:var(--paper-ink)}textarea{min-height:120px;resize:vertical}.field{margin-bottom:1.3rem}.field-error{border-color:var(--error) !important}.error-msg{color:var(--error);font-size:.85rem;margin-top:.4rem}.hp-field{position:absolute !important;left:-9999px !important;width:1px;height:1px;overflow:hidden}.check-row{display:flex;align-items:flex-start;gap:.7rem}.check-row input[type="checkbox"]{width:18px;height:18px;margin-top:.25rem;accent-color:var(--brass);flex-shrink:0}.check-row label,.check-row span{font-family:var(--font-sans);font-size:.9rem;color:var(--bone-dim);margin:0}.check-row a{text-decoration:underline;text-underline-offset:3px}/* Section footnotes sit in a full-width .wrap, so nothing else caps their measure.
   70ch matches .bleed-note; Martian Mono runs wide, so an uncapped line here
   reached 129 characters. */
#demos > .wrap > p.mono-dim,.price-foot > p.mono-dim{max-width:70ch}
.faq-item{border-top:1px solid var(--line)}.faq-item:last-child{border-bottom:1px solid var(--line)}.faq-q{width:100%;display:flex;align-items:center;justify-content:space-between;gap:1.5rem;text-align:left;padding:1.35rem 0;font-weight:620;font-size:1.08rem}.faq-q .faq-x{flex-shrink:0;width:20px;height:20px;position:relative;transition:transform .3s ease}.faq-q .faq-x::before,.faq-q .faq-x::after{content:'';position:absolute;background:var(--bone-dim);top:50%;left:0;width:100%;height:1.5px;transform:translateY(-50%)}.faq-q .faq-x::after{transform:translateY(-50%) rotate(90deg);transition:transform .3s ease}.faq-q[aria-expanded="true"] .faq-x::after{transform:translateY(-50%) rotate(0deg)}.faq-h{margin:0;font:inherit}.faq-a{display:grid;grid-template-rows:0fr;transition:grid-template-rows .35s ease}.faq-item.open .faq-a{grid-template-rows:1fr}.faq-a-inner{overflow:hidden}.faq-a-inner p{padding-bottom:1.35rem;color:var(--bone-dim);max-width:68ch}.paper .faq-q .faq-x::before,.paper .faq-q .faq-x::after{background:var(--paper-dim)}.paper .faq-a-inner p{color:var(--paper-dim)}.paper .faq-item,.paper .faq-item:last-child{border-color:var(--paper-line)}.demo-strip{display:grid;grid-template-columns:1.35fr 1fr;gap:clamp(1.5rem,4vw,3.5rem);align-items:center;padding-block:clamp(2rem,5vw,3.5rem);border-top:1px solid var(--line-soft)}.demo-strip:first-of-type{border-top:0}.demo-strip.flip{grid-template-columns:1fr 1.35fr}.demo-strip.flip .demo-media{order:2}@media (max-width:820px){.demo-strip,.demo-strip.flip{grid-template-columns:1fr}.demo-strip.flip .demo-media{order:0}}.demo-media{position:relative;aspect-ratio:16 / 10;overflow:hidden;background:var(--ink-2)}.demo-media img{width:100%;height:100%;object-fit:cover;transition:transform .5s ease}.demo-strip:hover .demo-media img{transform:scale(1.03)}.demo-media::before,.demo-media::after{content:'';position:absolute;width:20px;height:20px;border-color:var(--brass);opacity:.5;pointer-events:none;transition:opacity .25s ease}.demo-media::before{top:12px;left:12px;border-top:1.5px solid;border-left:1.5px solid}.demo-media::after{bottom:12px;right:12px;border-bottom:1.5px solid;border-right:1.5px solid}.demo-strip:hover .demo-media::before,.demo-strip:hover .demo-media::after,.demo-media:focus-visible::before,.demo-media:focus-visible::after{opacity:1}.gate-head{display:flex;align-items:baseline;gap:1rem;margin-bottom:1rem;flex-wrap:wrap}.gate-num{font-variation-settings:var(--cond);font-weight:230;font-size:clamp(1.5rem,2.4vw,1.9rem);color:var(--bone-dim);line-height:1}.gate-num-total{font-size:.58em;opacity:.8/* was .55 = 3.1:1 on --ink. Visible text needs 4.5:1 even inside an aria-hidden ornament: contrast is a low-vision concern, not a screen-reader one. */}.gate-tags{display:flex;flex-wrap:wrap;gap:.5rem}.gate-tag{display:inline-flex;align-items:center;border:1px solid var(--line);border-radius:var(--radius-pill);padding:.3rem .8rem;font-family:var(--font-mono);font-size:.66rem;letter-spacing:.02em;color:var(--bone-dim)}.paper .gate-tag{border-color:var(--paper-line);color:var(--paper-dim)}.demo-info h3{font-variation-settings:var(--wide);font-weight:730;font-size:clamp(1.5rem,2.6vw,2.1rem);margin-bottom:.7rem}.demo-info p{color:var(--bone-dim);max-width:46ch;margin-bottom:1.2rem}.demo-links{display:flex;flex-wrap:wrap;gap:.5rem 1.4rem}.hairline-top{border-top:1px solid var(--line)}.center{text-align:center}.nowrap{white-space:nowrap}[aria-hidden="true"]{user-select:none}.will-reveal{will-change:transform}@media print{[data-reveal]{visibility:visible !important;opacity:1 !important;clip-path:none !important;transform:none !important}}.bleed-intro{max-width:60ch;color:var(--bone-dim);font-size:1.1rem;margin-bottom:2.4rem}.bleed-tabs{display:flex;flex-wrap:wrap;gap:.6rem;margin-bottom:clamp(1.8rem,4vw,2.6rem)}.bleed-tab{border:1px solid var(--line);color:var(--bone-dim);padding:.6rem 1.15rem;border-radius:var(--radius-pill);font-size:.95rem;font-weight:560;transition:color .15s ease,border-color .15s ease}.bleed-tab:hover{color:var(--bone);border-color:var(--bone)}.bleed-tab.active{background:var(--bone);color:var(--ink);border-color:var(--bone);font-weight:690}.bleed-panel[hidden]{display:none}.bleed-grid{display:grid;grid-template-columns:1.35fr 1fr;gap:clamp(2rem,5vw,4rem);align-items:start}@media (max-width:820px){.bleed-grid{grid-template-columns:1fr}}.bleed-slider{padding:1.15rem 0;border-top:1px solid var(--line-soft)}.bleed-slider:first-child{border-top:0;padding-top:0}.bleed-slider label{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;font-family:var(--font-sans);font-size:.98rem;color:var(--bone);margin-bottom:.8rem}.bleed-val{font-family:var(--font-mono);font-size:.95rem;font-weight:700;white-space:nowrap}.bleed input[type="range"]{-webkit-appearance:none;appearance:none;width:100%;height:2px;background:var(--line);border-radius:0}.bleed input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:20px;height:20px;border-radius:50%;background:var(--brass);border:0;cursor:pointer}.bleed input[type="range"]::-moz-range-thumb{width:20px;height:20px;border-radius:50%;background:var(--brass);border:0;cursor:pointer}.bleed input[type="range"]::-moz-range-track{height:2px;background:var(--line)}.bleed-result{border:1px solid var(--line);padding:clamp(1.4rem,3vw,2rem)}.bleed-num{font-variation-settings:var(--cond);font-weight:230;font-size:clamp(2.6rem,5vw,4.1rem);line-height:1;margin:.55rem 0 .5rem;white-space:nowrap}.bleed-num .bleed-unit{font-size:.85rem;font-weight:430;color:var(--bone-dim)}.bleed-cta{display:flex;flex-wrap:wrap;gap:.9rem 1.4rem;align-items:center;margin-top:clamp(2rem,4vw,2.8rem)}.bleed-note{margin-top:1.3rem;max-width:70ch}@media (max-width:1000px) and (min-width:821px){.footer-grid{grid-template-columns:1fr 1fr}}

/* ===== IHRE FARBEN — six palettes, switched by html[data-farbe] ===== */
/* Written UNMINIFIED on purpose — the rest of this file is hand-minified, this
   block is the exception, because it is the one part of the stylesheet a human is
   meant to open: adding a trade = copy one palette block + one chip line + one line
   in js/farben.js. Built 2026-09-02.

   WHY it exists. A visitor from a Gasthof, a Friseursalon or a Maschinenbau firm
   flips the whole studio site into the pigments of their own trade. The colour is
   an argument ("he can do MY thing"), not decoration — and it closes a measured
   flank: 0 of the 4 Tirol competitors we measured map anything to a trade.

   THE FOUR RULES
   1. The default has NO [data-farbe]. It is the werkstatt palette in :root at the
      top of this file and is never redefined here, so an untouched visit renders
      byte-for-byte what it rendered yesterday.
   2. Every palette overrides the COMPLETE colour token set. Half a palette leaks
      the default ink into a light ground — the one failure mode that looks broken.
   3. Contrast is computed, not eyeballed. Per palette: body text >= 4.5:1 on the
      ground, accent >= 3:1 on the ground, CTA text (--ink on --brass) >= 4.5:1,
      dim text >= 4.5:1 (alpha composited first), error text >= 4.5:1.
   4. Nothing is stored on the device. The choice lives in ?farbe= and nowhere else
      — no localStorage, no cookie. The site has no consent banner because of it. */

/* Gasthof — walnut Stube at night, candlelight on the cloth, honey brass.
   The ground is deliberately a RED-brown, not a darker version of the default
   olive-black: the first draft (#1E1610) was so close to werkstatt that the two
   swatches were indistinguishable in the rail, which defeats the whole point. */
:root[data-farbe="gasthof"] {
  --ink: #2A1B12;
  --ink-2: #362317;
  --line: rgba(244,232,206,.15);
  --line-soft: rgba(244,232,206,.09);
  --bone: #F4E8CE;
  --bone-dim: rgba(244,232,206,.70);
  --brass: #E3A73C;
  --paper: #F4E8CE;
  --paper-ink: #2A1B12;
  --paper-dim: rgba(42,27,18,.68);
  --paper-line: rgba(42,27,18,.17);
  --error: #DC6A42;
}

/* Friseur — aubergine, blush, rose gold. Warm skin tones, no pink UI candy. */
:root[data-farbe="friseur"] {
  --ink: #241A26;
  --ink-2: #302432;
  --line: rgba(242,220,224,.15);
  --line-soft: rgba(242,220,224,.09);
  --bone: #F2DCE0;
  --bone-dim: rgba(242,220,224,.70);
  --brass: #D79A8C;
  --paper: #F2DCE0;
  --paper-ink: #241A26;
  --paper-dim: rgba(36,26,38,.68);
  --paper-line: rgba(36,26,38,.17);
  --error: #D96A58;
}

/* Handwerk — cold rolled steel, zinc, and the orange of a real Warnweste. */
:root[data-farbe="handwerk"] {
  --ink: #191C1E;
  --ink-2: #24282B;
  --line: rgba(227,230,232,.15);
  --line-soft: rgba(227,230,232,.09);
  --bone: #E3E6E8;
  --bone-dim: rgba(227,230,232,.70);
  --brass: #F07A26;
  --paper: #E3E6E8;
  --paper-ink: #191C1E;
  --paper-dim: rgba(25,28,30,.68);
  --paper-line: rgba(25,28,30,.17);
  --error: #E2624A;
}

/* Maschinenbau — night navy and the washed blue of a blueprint line. */
:root[data-farbe="maschinenbau"] {
  --ink: #0F1926;
  --ink-2: #182534;
  --line: rgba(231,238,245,.15);
  --line-soft: rgba(231,238,245,.09);
  --bone: #E7EEF5;
  --bone-dim: rgba(231,238,245,.70);
  --brass: #6FA6CE;
  --paper: #E7EEF5;
  --paper-ink: #0F1926;
  --paper-dim: rgba(15,25,38,.68);
  --paper-line: rgba(15,25,38,.17);
  --error: #DB6A4C;
}

/* Café — the one LIGHT palette: milk, espresso, terracotta.
   The whole model inverts here. --ink is the light ground and --bone the dark
   text, so the .paper sections (which are the inverse of the page by definition)
   become a dark espresso band inside a milk page instead of the other way round.
   The alpha tokens flip with it: dark ink over a light ground, same hairline
   weight. Do not "fix" this by keeping --paper light — the two would merge and
   #preise would lose its seam. */
:root[data-farbe="cafe"] {
  --ink: #F7F1E6;
  --ink-2: #EFE7D8;
  --line: rgba(43,30,21,.17);
  --line-soft: rgba(43,30,21,.10);
  --bone: #2B1E15;
  --bone-dim: rgba(43,30,21,.74);
  --brass: #A8492A;
  --paper: #2B1E15;
  --paper-ink: #F7F1E6;
  --paper-dim: rgba(247,241,230,.72);
  --paper-line: rgba(247,241,230,.17);
  --error: #95301A;
}

/* ── Three colours the tokens cannot reach ──
   Hard-coded fills that were correct only for the default palette. Scoped to
   [data-farbe] so the untouched default keeps exactly the CSS it has today. */

/* The WhatsApp glyphs are knock-outs: the handset is painted in the BUTTON's
   colour, not in a colour of its own. It was authored as the literal default
   value (#16130F, the glyph of the checkout's sticky bar in components.v2.js),
   so under any other palette a near-black handset sat on a coloured button. */
:root[data-farbe] .sticky-cta-second svg path + path { fill: var(--ink); }
/* The three "browser dots" in the hero mini-browser sit on that frame's own dark
   chrome, not on the page, so they must stay light even when the page is light. */
:root[data-farbe="cafe"] .hb-dot { background: #F5EBDD; }

/* The ground washes over when a palette is PICKED; type and accents snap. Only two
   selectors, so this can never become a whole-document transition — and it is gated
   on [data-farbe-wash], which js/farben.js sets on the first switch and never on
   load: without the gate, arriving at ?farbe=gasthof played a 350ms fade from the
   default ground, i.e. a visible flash of the wrong palette. */
:root[data-farbe-wash] body,
:root[data-farbe-wash] .paper { transition: background-color .35s ease, color .35s ease; }

/* ── The rail ──
   Desktop: a fixed strip on the right edge, vertically centred so it never lands
   on the hero CTA.
   It carries its own --ink ground on purpose: constant contrast for the swatch
   hairlines no matter which section scrolls behind it. */
.farben {
  position: fixed;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 88;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .7rem;
  padding: .85rem .55rem;
  background: var(--ink);
  border: 1px solid var(--line);
  border-right: 0;
}
.farben-label {
  writing-mode: vertical-rl;
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .06em;
  color: var(--bone-dim);
  text-transform: uppercase;
}
/* .5rem: the site-wide :focus-visible outline is 2px at a 3px offset, so two
   neighbouring chips need 10px between them or the rings touch. */
.farben-group { display: flex; flex-direction: column; gap: .55rem; }
/* Square chips, not dots: they read as pigment samples and they show two colours
   each — the ground the visitor would get, and the accent that sits on it. */
.farben-sw {
  width: 26px;
  height: 26px;
  border: 1px solid var(--line);
  background: linear-gradient(var(--chip-ground) 0 64%, var(--chip-accent) 64% 100%);
  transition: transform .18s ease, box-shadow .18s ease;
}
.farben-sw:hover { transform: translateX(-2px); }
/* Checked ring, drawn OUTSIDE the chip so it never eats into the colour sample.
   :focus-visible keeps the site-wide brass outline; the two never collide. */
.farben-sw[aria-checked="true"] { box-shadow: 0 0 0 2px var(--ink), 0 0 0 3px var(--bone); }
.farben-toggle { display: none; }

.farben-sw[data-farbe="werkstatt"]    { --chip-ground: #16130F; --chip-accent: #D2A62C; }
.farben-sw[data-farbe="gasthof"]      { --chip-ground: #2A1B12; --chip-accent: #E3A73C; }
.farben-sw[data-farbe="friseur"]      { --chip-ground: #241A26; --chip-accent: #D79A8C; }
.farben-sw[data-farbe="handwerk"]     { --chip-ground: #191C1E; --chip-accent: #F07A26; }
.farben-sw[data-farbe="maschinenbau"] { --chip-ground: #0F1926; --chip-accent: #6FA6CE; }
.farben-sw[data-farbe="cafe"]         { --chip-ground: #F7F1E6; --chip-accent: #A8492A; }

/* Phone: a rail on the edge would sit on the text, so it collapses to one pill in
   the bottom right — parked ABOVE the sticky CTA bar (72px, z-index 90) the same
   way body.has-sticky-cta reserves room for it. js/farben.js builds the rail only
   after that bar exists, so the pill is never repositioned after paint (CLS 0). */
@media (max-width: 768px) {
  .farben {
    top: auto;
    bottom: calc(1.1rem + env(safe-area-inset-bottom, 0));
    right: clamp(1rem, 4vw, 2rem);
    transform: none;
    align-items: flex-end;
    gap: .55rem;
    padding: 0;
    background: none;
    border: 0;
  }
  body.has-sticky-cta .farben { bottom: calc(72px + .9rem + env(safe-area-inset-bottom, 0)); }
  .farben-label { display: none; }
  .farben-toggle {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    background: var(--ink);
    color: var(--bone);
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    font-family: var(--font-mono);
    font-size: .68rem;
    letter-spacing: .04em;
    padding: .6rem .95rem;
    min-height: 40px;
  }
  .farben-chip { width: 12px; height: 12px; background: var(--brass); flex-shrink: 0; }
  .farben-group {
    flex-direction: row;
    gap: .4rem;
    padding: .5rem;
    background: var(--ink);
    border: 1px solid var(--line);
  }
  .farben:not(.open) .farben-group { display: none; }
  .farben-sw { width: 32px; height: 32px; }
  .farben-sw:hover { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  :root[data-farbe-wash] body,
  :root[data-farbe-wash] .paper,
  .farben-sw { transition: none; }
  .farben-sw:hover { transform: none; }
}

/* ############################################################################
   VARIANT B — everything above this line is werk.css byte-for-byte except the
   :root token block. Everything below is what the hybrid-B brief adds on top.
   Read top-down: ground, type, seams, controls, air, dark stages, per page.
   ############################################################################ */


/* ============================================================================
   B/1 — GROUND
   WHY body needs an explicit repaint: A's body rule paints var(--ink), which
   now IS paper, so the ground is already right. What is NOT inherited is the
   selection pair — ::selection painted --brass behind --ink, i.e. forest behind
   paper, which is correct, but .paper::selection inverted it for the dark
   sections that no longer exist. Both are restated once, in B terms.
   ========================================================================== */
::selection { background: var(--brass); color: var(--ink); }
.paper::selection { background: var(--brass); color: var(--ink); }

/* The one thing the token flip cannot reach: a focus ring drawn in --brass on a
   paper ground is forest on paper (9.8:1) and correct, but on a FOREST button
   it disappears. Re-point the ring on filled controls to the paper itself. */
.btn:focus-visible,
.nav-cta:focus-visible,
.sticky-cta-primary:focus-visible,
.skip-link:focus-visible { outline-color: var(--ink); }


/* ============================================================================
   B/2 — BLUEPRINT GRID
   The brief: "faint (<=5% ink), only behind heroes/stages, never behind long
   text." So it is attached to hero blocks and to the frames that hold a 3D
   stage, and to nothing that contains a paragraph column. It is a background
   IMAGE on an element that already has a background colour, so it costs no
   extra box and cannot move anything (CLS 0).
   ========================================================================== */
/* The first layer is painted ON TOP of the two grid layers (CSS paints
   background layers first-listed-first) and washes the paper back in below the
   masthead band. That is what keeps "never behind long text" true on /about,
   whose hero carries a full bio column: the grid belongs to the masthead, not
   to the reading column under it. One extra gradient, no extra box, no extra
   element, so it still cannot move anything.
   .demo-frame / .case-poster are deliberately NOT in this list — B/7 makes them
   dark stages and a grid on a dark plate is noise. */
.hero,
.page-hero,
.briefing-header {
  background-image:
    linear-gradient(to bottom, rgba(0,0,0,0) 0 320px, var(--ink) 560px),
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 100% 100%, 24px 24px, 24px 24px;
  background-repeat: no-repeat, repeat, repeat;
  background-position: 0 0, -1px -1px, -1px -1px;
}
/* A .paper hero washes back in its own tone, not the page's. */
.paper.page-hero {
  background-image:
    linear-gradient(to bottom, rgba(0,0,0,0) 0 320px, var(--paper) 560px),
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
}


/* ============================================================================
   B/3 — TYPE: THE NUMBERED EYEBROW
   The brief's "01 / SYSTEM". The number is a CSS counter over .section, so it
   always matches the real section order of whatever page it is on, and it
   appears ONLY where the page already authored a .sec-note — nothing is
   invented on a section that has no eyebrow. Zero markup, zero copy change.
   ========================================================================== */
body { counter-reset: wdm-sec; }
.section { counter-increment: wdm-sec; }
.sec-head .sec-note {
  text-transform: uppercase;
  letter-spacing: .13em;
  font-weight: 600;
  font-size: .68rem;
  color: var(--accent-ink);         /* 4.7:1 — the darkened brass, see tokens */
}
.sec-head .sec-note::before {
  content: counter(wdm-sec, decimal-leading-zero) " / ";
  color: var(--accent);             /* a mark, not text: the 3:1 token is fine */
  font-weight: 700;
  letter-spacing: .04em;            /* the number is read as a figure, not as a
                                       tracked-out word: .13em split "01" apart */
}
/* Page heroes carry their own eyebrow class instead of a .sec-note. */
.hero-eyebrow,
.page-hero .mono-dim:first-child {
  text-transform: uppercase;
  letter-spacing: .13em;
  font-weight: 600;
  color: var(--accent-ink);
}

/* Display type: A's 810 weight was tuned for light-on-dark, where the ground
   eats the stem. Ink on paper renders the same weight visibly heavier, so the
   whole display scale steps down one notch rather than being restyled. */
.display { font-weight: 760; }
h2, .h2 { font-weight: 720; }


/* ============================================================================
   B/4 — SEAMS: HAIRLINES, NOT SHADOWS
   On a dark ground a card is separated by a lift (a shadow, a second fill). On
   paper the document convention is a drawn line, so every seam in the shared
   components becomes one hairline and nothing is allowed to float.
   ========================================================================== */
.section + .section { border-top: 1px solid var(--line-soft); }
/* The drawn rule (#9E927C) where a border is structural rather than a tint:
   the top of the footer, the frame around a result panel, a tab that is off. */
.site-footer,
.hairline-top,
.bleed-result { border-color: var(--rule); }

/* Fills that existed only to lift a block off a dark ground. On paper the same
   fill reads as a grey card-in-a-card, which anti-goal 1 forbids. */
.demo-media { background: var(--ink-2); }

/* The .paper section is no longer an inversion, it is a tone step. Give it the
   seam back as a rule, or two adjacent paper blocks merge into one field. */
.paper { border-block: 1px solid var(--rule); }
.section.paper + .section.paper { border-top-width: 1px; }


/* ============================================================================
   B/5 — CONTROLS
   .btn / .nav-cta / .sticky-cta-primary already resolve to forest
   on paper through the token flip. What does NOT resolve is anything painted
   as a literal hex inside injected SVG, and the ghost pair, whose 1px tint
   border is invisible against paper at A's alpha.
   ========================================================================== */
.btn-ghost,
.bleed-tab { border-color: var(--rule); }
.btn-ghost:hover,
.bleed-tab:hover { border-color: var(--bone); }

/* WhatsApp glyph knock-out. The handset was authored as A's literal hex
   (components.v2.js), so on the paper ghost button it stayed
   near-black-on-near-black. It is repainted in its OWN button's ground, which
   is what a knock-out means. (The forest float button that had the same fix
   left with the float, 2026-10-08.) js/components-b.js
   emits tokens instead, but this keeps the rule true even under components.v2. */
.sticky-cta-second svg path + path { fill: var(--ink); }

/* The nav sits on paper now. A transparent bar over a paper hero has no seam at
   all, so the scrolled state carries the rule rather than a tint of it. */
.site-nav.scrolled { background: var(--ink); border-bottom-color: var(--rule); }
.sticky-cta { border-top-color: var(--rule); }

/* Form fields: A gave them a raised fill (--ink-2) on a dark page. On paper a
   filled field reads as disabled, so the field becomes the page with a drawn
   box around it — the engineering-form convention the brief asks for. */
input[type="text"], input[type="email"], input[type="tel"],
input[type="number"], select, textarea {
  background: transparent;
  border-color: var(--rule);
}
input:focus, select:focus, textarea:focus { border-color: var(--brass); }


/* ============================================================================
   B/6 — AIR
   The brief asks for "more air on mobile". Only the two spacing tokens move, so
   every block on every page gains the same air and nothing is re-laid out.
   ========================================================================== */
@media (max-width: 768px) {
  :root {
    --section-y: clamp(5.5rem, 15vw, 8rem);
    --gutter: clamp(1.5rem, 6vw, 2rem);
  }
  .sec-head { margin-bottom: clamp(2.6rem, 8vw, 3.4rem); }
  .lead { font-size: 1.08rem; line-height: 1.62; }
}


/* ============================================================================
   B/7 — THE 3D STAGES STAY DARK
   The brief is explicit and it is not decoration: the pump viewer, the case
   posters and the shelf prints all hold DARK pixels. A paper-coloured frame
   around dark content flashes white while the iframe loads and then letterboxes
   in the wrong colour forever. Each frame therefore keeps A's ink, and gets the
   drawn rule around it so it reads as a plate mounted on the page.
   ========================================================================== */
.demo-frame,
.case-poster,
.shelf-print,
.model-frame iframe {
  background-color: #16130F;         /* A's ink, deliberately literal: this is a
                                        stage colour, not a theme colour, and it
                                        must not follow a palette switch. */
  border: 1px solid var(--rule);
}
/* The grid belongs on the paper AROUND the plate, not on the plate itself. */
.demo-frame, .case-poster { background-blend-mode: normal; background-image: none; }
.demo-frame::before, .case-poster::before { content: none; }

/* The homepage's full-viewport scene (js/aufbau.js) reads --ink/--bone/--brass
   off :root, so under B it draws INK lines on paper — which is the blueprint
   the brief wants and is why the canvas is kept rather than hidden. But dark
   lines on a light ground are far louder than light lines on a dark one, so the
   opacity ceiling drops to roughly the "cafe" light-palette values. html:root
   raises specificity over aufbau.css's own :root, which loads after this file. */
html:root { --aufbau-op: .05; --aufbau-op-hero: .26; }
@media (max-width: 899px) {
  html:root { --aufbau-op: .04; --aufbau-op-hero: .20; }
}
/* Phones: A lifts the canvas back to .7 because a dark scene under dark copy is
   harmless. Ink-on-paper under ink copy is not. */
@media (max-width: 899px) { .aufbau-canvas.is-live { opacity: var(--aufbau-op); } }


/* ============================================================================
   B/8 — PER-PAGE OVERRIDES: fertig
   fertig.html's inline <style> defines its own kraft tokens and paints two
   literal darks. Every selector below is prefixed with body[data-page="fertig-kauf"]
   so it outranks the page's own single-class rules, which load after this file.
   ========================================================================== */
body[data-page="fertig-kauf"] {
  --kraft: #E2D3B2;        /* A's #D9C7A1 sat almost on top of paper-dim; the
                              tag has to read as a DIFFERENT stock, not a stain */
  --kraft-edge: var(--rule);
  --kraft-ink: #191813;
  --kraft-dim: rgba(25,24,19,.72);
  --kraft-line: rgba(25,24,19,.20);
}
/* The ledge under a shelf card: a drawn edge, not a dark bar on paper. */
body[data-page="fertig-kauf"] .shelf-card::after {
  background: linear-gradient(180deg, var(--rule) 0 1px, rgba(25,24,19,.14) 1px 100%);
}
body[data-page="fertig-kauf"] .shelf-print { border-color: var(--rule); }
body[data-page="fertig-kauf"] .shelf-eur { background: #F6EFDD; border-color: var(--rule); }
/* The glass highlight was a white diagonal for a dark print; on paper it fogs
   the screenshot instead of glazing it. */
body[data-page="fertig-kauf"] .shelf-print::after { background: none; }


/* ============================================================================
   B/9 — PER-PAGE OVERRIDES: konfigurator
   Two selected-states were authored as the literal #211C14, which is A's ink
   one step up. On paper they are black rectangles. The selected state becomes
   what it means on a form: the same paper, with the accent rule thickened.
   The print document (.doc-sheet, white + serif) is left alone on purpose —
   it is a printed Angebot, not a page section.
   ========================================================================== */
body[data-page="konfigurator"] { --bg3: var(--ink-2); }
body[data-page="konfigurator"] .kf-pkg.selected,
body[data-page="konfigurator"] .kf-dopt.selected {
  background: var(--ink-2);
  border-color: var(--brass);
  box-shadow: inset 0 0 0 1px var(--brass);
}
body[data-page="konfigurator"] .kf-service:hover { background: rgba(25,24,19,.04); }
body[data-page="konfigurator"] .kf-service.selected { background: rgba(32,61,50,.06); }
body[data-page="konfigurator"] .kf-calc { border-color: rgba(32,61,50,.34); }
body[data-page="konfigurator"] #docModal { background: rgba(25,24,19,.72); }
body[data-page="konfigurator"] .kf-pages-row input,
body[data-page="konfigurator"] .kf-res { background: transparent; border-color: var(--rule); }


/* ============================================================================
   B/10 — PER-PAGE OVERRIDES: apply, 3-months, about, case-*, 3d-*
   All five are token-clean apart from one hardcoded translucent nav and a
   handful of --ink-2 fills that were lifts on a dark ground. Same rule as B/4:
   on paper a lift becomes a line.
   ========================================================================== */
body[data-page="apply"] { background: var(--ink); color: var(--bone); }
body[data-page="apply"] .apply-nav,
body[data-page="apply"] nav[class*="nav"] {
  background: rgba(240,234,219,.88);
  border-bottom-color: var(--rule);
}
body[data-page="apply"] .check,
body[data-page="apply"] .proof a,
body[data-page="apply"] .fit__col { background: var(--ink-2); border: 1px solid var(--rule); }
body[data-page="apply"] .proof a:hover { background: #E1D4BC; }
body[data-page="apply"] .field input,
body[data-page="apply"] .field textarea,
body[data-page="apply"] .field select {
  background: transparent; color: var(--bone); border-color: var(--rule);
}

body[data-page="3-months"] .tl-shot,
body[data-page="3-months"] .tl-junk li { background: var(--ink-2); border-color: var(--rule); }

body[data-page="briefing"] .step-card { background: var(--ink-2); border-color: var(--rule); }

/* The three case studies and both 3D landing pages share one poster component;
   B/7 already made the plate dark, this only settles its caption and note so
   they sit on paper under it rather than inheriting the plate's tone. */
body[data-page^="lp-"] .demo-frame-caption,
body[data-page^="lp-"] .demo-frame-note,
body[data-page^="lp-"] .case-poster-note { color: var(--bone-dim); }


/* ============================================================================
   B/11 — THE PALETTE RAIL
   "Ihre Farben" stays on demo and service pages (components-b.js drops it on
   the homepage only). Its chips carried A's ink ground so the hairlines held
   constant contrast; on paper that ground is now the paper itself, and the rail
   needs the drawn rule or it floats.
   ========================================================================== */
.farben { background: var(--ink); border-color: var(--rule); }
.farben-sw { border-color: var(--rule); }
.farben-sw[aria-checked="true"] { box-shadow: 0 0 0 2px var(--ink), 0 0 0 3px var(--bone); }
@media (max-width: 768px) {
  .farben-group { background: var(--ink); border-color: var(--rule); }
  .farben-toggle { background: var(--ink); color: var(--bone); border-color: var(--rule); }
}
/* The rail is hidden on every page, not just home (2026-09-15 task). The ?farbe=
   mechanism keeps working: farben.js only ever writes html[data-farbe] when the
   URL carries an explicit, non-default ?farbe= (js/farben.js:36-37), so gating
   the rail on that attribute shows it exactly when a visitor arrived with the
   param and never on an untouched visit — same selector on both breakpoints
   because the 768px block above never redeclares display. */
.farben { display: none; }
:root[data-farbe] .farben { display: flex; }


/* ============================================================================
   B/13 — THE EYEBROW DASH
   aufbau.css loads AFTER this file and gives .station-cap a ::before that is a
   1.5rem x 1px brass dash. B/3 re-points the same pseudo-element at the section
   counter, so the number inherited the dash's 24x1px box and rendered as a
   crushed glyph. The box is released; the dash idea survives as the leading
   rule the counter now sits next to.
   ========================================================================== */
.sec-head .sec-note.station-cap::before {
  width: auto;
  height: auto;
  background: none;
  flex: 0 0 auto;
}

/* ============================================================================
   B/14 — HOME HERO: STAGE ABOVE THE FOLD ON MOBILE
   Task 2026-09-15: on phones the 3D stage (the .poster plate that stands in
   for the aufbau canvas — #aufbau-canvas itself is position:fixed and never a
   grid child, see index.html:116) has to land right after the H1 + lead,
   before the buttons and the mono trust line. aufbau.css:300-314 makes
   .hero-grid a CSS grid with h1/.hero-copy/.poster as its only three direct
   children — the lead, buttons and trust line are all nested one level down,
   inside .hero-copy, so a grid-area swap of the three direct children alone
   cannot interleave a fourth item between two of .hero-copy's own children.
   .hero-copy is switched to display:contents so its children become direct
   flex items of .hero-grid instead, and each one gets an explicit order.
   body[data-page="home"] outranks aufbau.css's unprefixed .hero-grid /
   .hero-copy rules (same pattern as B/8-B/10) even though that file loads
   after this one. Desktop (>640px) keeps aufbau.css's grid untouched — this
   whole block is scoped to the phone breakpoint the task named. Copy is not
   touched, only layout order. */
@media (max-width: 640px) {
  body[data-page="home"] .hero-grid { display: flex; flex-direction: column; }
  body[data-page="home"] .hero-copy { display: contents; }
  body[data-page="home"] .hero-grid > h1 { order: 1; }
  body[data-page="home"] .hero-copy > .lead { order: 2; }
  body[data-page="home"] .hero-grid > .poster { order: 3; }
  body[data-page="home"] .hero-copy > .hero-actions { order: 4; }
  body[data-page="home"] .hero-copy > .hero-trust { order: 5; }
}

/* ============================================================================
   B/16 — THE STICKY BAR MUST NOT EAT THE LAST SCREEN (measured 2026-09-15)
   A reserves a flat `body.has-sticky-cta{padding-bottom:72px}` for a bar that
   actually measures 73.66px at 360-768px, and the bar adds
   env(safe-area-inset-bottom) to its own padding while the reserve does not —
   so on a notched phone ~36px of the footer sits under the bar. Both numbers
   are inherited from werk.css, so A has the same shortfall.
   76px, not 73.66: the bar's buttons are text-sized, and Archivo is
   font-display:optional, so a cold load that renders in the Arial fallback is
   a fraction taller. The 320px rule is not speculative — at 320 the two
   buttons wrap to a second row and the bar measures 99.13px.
   ========================================================================== */
@media (max-width: 768px) {
  body.has-sticky-cta { padding-bottom: calc(76px + env(safe-area-inset-bottom, 0px)); }
}
@media (max-width: 359px) {
  body.has-sticky-cta { padding-bottom: calc(102px + env(safe-area-inset-bottom, 0px)); }
}

/* ============================================================================
   B/17 — .nowrap utility (2026-09-15)
   Same declaration as werk.css A/3. /b/ pages load this file INSTEAD of
   werk.css, so the class has to exist here too or the citation span in
   services.html / konfigurator.html is inert under theme B.
   ========================================================================== */
.nowrap { white-space: nowrap; }

/* ============================================================================
   B/18 — THE LANGUAGE DROPDOWN (2026-09-16)
   The nav no longer carries two DE/EN buttons; js/i18n.js renders one
   .wdm-lang listbox into each [data-lang-host] slot. Four languages as a row of
   codes would eat the nav — BRIEF-i18n-2026-09-16, anti-goal 2.
   The component ships neutral defaults and reads four custom properties; these
   are the studio's skin. Everything below is written in TOKENS, not hexes, so
   the same block is correct in werk.css and werk-b.css even though B inverts
   --ink and --bone.
   min-width on the host is the anti-CLS reserve: the control is injected after
   first paint, so the nav has to hold its box before it arrives.
   Since 2026-10-08 (B/19) the host is in the header at every width: the overlay
   menu that used to carry it on phones is gone.
   ========================================================================== */
.lang-host { display: flex; justify-content: flex-end; min-width: 47px; min-height: 30px; }

.wdm-lang {
  --wdm-lang-bg: transparent;
  --wdm-lang-fg: var(--bone-dim);
  --wdm-lang-border: var(--bone-dim);
  --wdm-lang-font: var(--font-mono);
  font-size: .74rem;
}
.wdm-lang__list { background: var(--ink); }
.wdm-lang__btn:hover { color: var(--brass); border-color: var(--brass); }
.wdm-lang__list li[aria-selected="true"] { color: var(--brass); }
.wdm-lang__list li:hover,
.wdm-lang__list li:focus { background: var(--brass); color: var(--ink); }


/* ============================================================================
   B/19 — THE FOOTER SITEMAP (2026-10-08: this sheet became css/werk.css)
   The paper theme used to ship only through the /b/ mirror, whose own script
   (js/components-b.js) still built the footer link columns itself. The main
   site runs js/components.v2.js, which ADOPTS the .footer-sitemap node every
   page authors in its HTML — so the block that styles it has to live in this
   sheet too. It is carried over verbatim from the dark sheet
   (css/werk-dark.css), comment included, so the two stay diffable; the one
   rule after it is the only thing that differs.
   ========================================================================== */
/* ---------------------------------------------------------------------------
   Footer-Sitemap: alle Hauptseiten
   WHY it is styled here and authored in every page's HTML rather than built by
   js/components.v2.js like the rest of the footer: nav and footer are injected,
   so the raw source used to contain no internal links at all and a crawler that
   does not run JavaScript saw no link graph at all (2026-08-08: nine money pages
   were reachable only through sitemap.xml). components.v2.js moves the authored
   node into the footer, so it renders exactly once.

   The block carries its OWN colour tokens instead of the page's: two of the 24
   pages that hold it (/3d-fit-check/ and the Anna-Schley case study) do not load
   this stylesheet and have light palettes of their own, and the same markup has
   to read the same on all of them.
   --------------------------------------------------------------------------- */
.footer-sitemap {
  --fs-ink: #16130F;
  --fs-bone: #EFE9DE;
  --fs-dim: rgba(239, 233, 222, .68);
  --fs-line: rgba(239, 233, 222, .14);
  --fs-brass: #D2A62C;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 1.5rem 2rem;
  margin-bottom: 2rem;
  /* No side padding here, unlike the standalone copy of these rules: .site-footer
     already carries the page gutter, and an extra inset would push the columns
     out of line with the brand text above them. */
  padding: 1.7rem 0 1.5rem;
  border-top: 1px solid var(--fs-line);
  background: var(--fs-ink);
  color: var(--fs-bone);
  font-family: 'Archivo', 'Archivo Fallback', Arial, sans-serif;
  font-size: 1rem;
  line-height: 1.5;
}
.footer-sitemap .fs-h {
  margin: 0 0 .65rem;
  font-family: 'Martian Mono', 'Martian Fallback', 'Courier New', monospace;
  font-weight: 430;
  font-size: .72rem;
  color: var(--fs-dim);
}
.footer-sitemap a {
  display: block;
  padding: .22rem 0;
  max-width: 100%;
  font-size: .93rem;
  color: var(--fs-bone);
  text-decoration: none;
  overflow-wrap: break-word;
  transition: color .15s ease;
}
.footer-sitemap a:hover { color: var(--fs-brass); }
/* Two columns rather than five at phone width: auto-fit would give one very tall
   list, and 148px tracks would overflow a 390px viewport once the gutter is off. */
@media (max-width: 560px) {
  .footer-sitemap { grid-template-columns: 1fr 1fr; gap: 1.2rem 1rem; padding: 1.4rem 0 1.2rem; }
}
/* The legal row may wrap: the French labels need 367px on one line, 31px more than a 360px phone has. */
.footer-legal { flex-wrap: wrap; }
/* The block above hard-codes the DARK studio colours on purpose (see its
   comment). On pages that load this sheet the footer is paper, and a palette
   (:root[data-farbe]) may re-point the tokens — so here, and only here, its
   five private tokens follow the page's. Same specificity, later in the file:
   this wins without touching the verbatim copy. */
.footer-sitemap {
  --fs-ink: var(--ink);
  --fs-bone: var(--bone);
  --fs-dim: var(--bone-dim);
  --fs-line: var(--line);
  --fs-brass: var(--brass);
}


/* ============================================================================
   B/20 — KEYBOARD FOCUS ON FIELDS (2026-10-08)
   The dark sheet gained this rule after this file was forked from it, so the
   paper theme never had it. Copied as authored, its own comment included;
   "the rule above" in that comment is input:focus{outline:none} in the base.
   ========================================================================== */
input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--brass);outline-offset:2px/* The rule above kills the outline for BOTH pointer and keyboard focus, so a keyboard user only ever got a 1px border colour change — below the 3:1 non-text contrast floor and invisible on a dark field. This puts the site-wide focus ring back for keyboard focus only. Offset 2px, not the global 3px: these are boxed fields and 3px collided with the neighbouring field's border in the two-column rows. */}


/* ============================================================================
   B/21 — FOUND BY THE CONTRAST PROBE, ROUND 2 (2026-10-08)
   .gate-num-total ("/03" on home, "/22" on portfolio) is --bone-dim at
   opacity .8. That was sized for the dark ground; on paper it composites to
   #747168 on #F0EADB = 4.06:1. At .9 it is ~5.1:1 and still reads as the
   quieter half of the number. Two classes, not one: agenturen/index.html
   repeats the .8 in its own <style>, which loads after this file.

   .sec-note on a .paper section: --accent-ink (#8A5E15) is 4.7:1 on the page
   ground but 4.22:1 on the second surface (#E8DDC8), where 25 eyebrows on 14
   pages sit. #7A5310 is 5.08:1 there. Re-pointing the token inside .paper
   fixes every eyebrow at once and leaves the ones on the ground as they were.

   apply.html's top bar: B/10 aims at .apply-nav / nav[class*="nav"], but the
   bar is <header class="topbar"> with a literal rgba(22,19,15,.82). Its links
   and wordmark read the flipped tokens, so they were ink on ink (1.4:1 and
   1.04:1). Same paint B/10 meant to give it.
   ========================================================================== */
.gate-num .gate-num-total { opacity: .9; }
.paper { --accent-ink: #7A5310; }
body[data-page="apply"] .topbar {
  background: rgba(240,234,219,.88);
  border-bottom-color: var(--rule);
}


/* ============================================================================
   B/19 — KOPF: THE TRIMMED HEADER (2026-10-08)
   js/components.v2.js now builds logo, two links, the language dropdown and the
   "Projekt starten" pill, and nothing else. The hamburger, the full-screen
   .mobile-menu and the two nav-fit rules for nine labels (1100-1379px) were
   deleted from this file with it. Two links fit a phone row, so every width
   gets the same header and no overlay.
   ========================================================================== */
/* 44px tall without looking like a button: the tap target is the padding. */
.nav-links a { padding: .7rem 0; }
/* The language button is drawn 30px tall; its tap area is 44px, grown with an
   invisible pseudo-element so the pill itself keeps its size. */
.site-nav .wdm-lang__btn { position: relative; }
.site-nav .wdm-lang__btn::after { content: ""; position: absolute; inset: -7px 0; }
/* The logo is its own fixed box (it fades on scroll, the bar does not). Same
   height as the bar and centred in it, or the taller links sit lower than it. */
.brand-logo { display: flex; align-items: center; height: 4.75rem; padding-block: 0; }
.site-nav { height: 4.75rem; padding-block: 0; }
@media (max-width: 768px) {
  /* On phones the pill lives in the sticky bar at the bottom of the screen
     (.sticky-cta-primary); twice on one screen is once too many. */
  .site-nav .nav-cta { display: none; }
}
@media (max-width: 480px) {
  /* What has to fit in 312px (a 360px phone minus two gutters): the mark, the
     name, two links and the dropdown. The sub-line goes first, then the name
     is stacked one word per line (min-content) instead of shrinking below a
     readable size: a three-line wordmark is as wide as "Moments" and no wider. */
  .brand-logo .nav-logo-sub { display: none; }
  .brand-logo .nav-logo-name { font-size: .72rem; line-height: 1.15; width: min-content; }
  .site-nav { gap: .7rem; }
  .nav-links { gap: .7rem; }
  .nav-links a { font-size: .88rem; white-space: nowrap; }
}


/* ============================================================================
   B/22 — THE ROLLOUT: ONE WHATSAPP BUTTON, AND THE CLOSE BLOCK (2026-10-08)
   For every page js/components.v2.js serves; the recipe is docs/rollout-spec.md.
   css/werk-dark.css carries the same block as A/6, for /werkstadt/.
   ========================================================================== */
/* While a WhatsApp button of the page itself is on screen, the header pill and
   the sticky bar step back (components.v2.js watchOwnWa sets body.wa-own).
   visibility, not display: the nav keeps its box and nothing moves. */
body.wa-own .site-nav .nav-cta,
body.wa-own .sticky-cta { visibility: hidden; }
/* The hidden pill would leave a hole at the right end of the header, so the
   links and the language button slide into it. A transform, not a layout
   change: it costs no layout shift when the pill comes back on scroll. */
.site-nav .nav-links,
.site-nav .lang-host { transition: transform .25s ease; }
@media (min-width: 769px) {
  body.wa-own .site-nav .nav-links,
  body.wa-own .site-nav .lang-host { transform: translateX(var(--wa-pill, 0px)); }
}

/* The sticky bar's first button is the WhatsApp one now, and its label is the
   long one. It has to stay on one row beside "Briefing" on a 360px phone. */
.sticky-cta-primary { white-space: nowrap; }
.sticky-cta-second { text-align: center; }
@media (max-width: 420px) {
  .sticky-cta-primary { font-size: .9rem; padding-inline: .55rem; }
  .sticky-cta-second { padding-inline: .85rem; }
}

/* -- START: the close block. One sentence, one button, on the forest ground -- */
/* The only full-colour block on the page. It is the CTA colour turned into a
   wall, so the last screen cannot be mistaken for another content section.
   Paper on forest measures 9.8:1 (werk.css token table). */
.start { background: var(--brass); color: var(--ink); }
.section + .section.start { border-top: 0; }
.start h2 { max-width: 16ch; margin-bottom: 1.6rem; }
.start .lead { color: var(--ink); margin-bottom: 2.2rem; }
/* Inverted button: the forest .btn would vanish on a forest ground. */
.start-btn { background: var(--ink); color: var(--bone); }
.start-btn:focus-visible { outline-color: var(--ink); }
.start-wege { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem 1.4rem; }
/* werk.css underlines .link-arrow in a tint of the ink; on forest the line and
   the hover colour have to be the paper. */
.start .link-arrow { padding: .7rem 0; text-decoration-color: currentColor; }
.start .link-arrow:hover { color: var(--ink); }
.start-mail { margin-top: 1.4rem; font-size: .78rem; }
.start-mail a { text-decoration: underline; text-underline-offset: 3px; }
/* werk.css draws focus rings in --brass, which IS this section's ground. */
.start a:focus-visible { outline-color: var(--ink); }
.start ::selection { background: var(--ink); color: var(--bone); }


/* ============================================================================
   B/23 — PHONE SAFETY FOR LONG GERMAN WORDS AND ARTICLE TABLES
   "Datenschutzerklärung" and "Buchungssoftware?" in an h2 are wider than a
   390px column and pushed the page sideways (measured 41px and 4px). Headings
   may always break a word that does not fit, and ONLY a word that does not fit.
   There is no hyphens:auto here any more: html lang follows the visitor's
   language (js/i18n.js), so it hyphenated French "Choisissez" into "Choisis-sez"
   and German "interaktives" into "interak-tives" in a column wide enough for
   the whole word (found 2026-10-08).
   ========================================================================== */
/* LAST-RESORT NET ONLY. It used to be the fix for long words, but a net cuts at
   the first letter that does not fit: on wide screens "auseinandernehm|en",
   "Impressu|m" and "Datensch|utz" (found 2026-10-09). Every heading word that is
   wider than its column now has a cause-level fix instead: a size that follows the
   screen (legal h1, werkstadt h1, case h1, h2.display on phones) or a soft hyphen
   at a syllable joint in the copy (&shy;). Measured with the net switched OFF on
   50 pages x 4 languages x 9 widths (360 to 1920): no heading breaks mid-word and
   none overflows, so the net never fires. It stays for widths and strings nobody
   measured, where an overflowing word is worse than a cut one. */
h1, h2, h3, .h2 { overflow-wrap: break-word; }
/* !important on purpose: the wissen articles and the case pages still carry an
   inline hyphens:auto on their H1 (older, page-owned style blocks) that would
   win over this sheet and cut "Web-site" in a column wide enough for it. */
h1, h2, h3, .h2, .display { -webkit-hyphens: manual !important; hyphens: manual !important; }
@media (max-width: 640px) {
  /* A heading that is a flex item of .sec-head keeps its longest word as
     min-content width: "Datenschutzerklärung" pushed the layout 71px past a
     360px phone. Capping the item at the column lets break-word work. */
  .sec-head > h2, .sec-head > h3 { max-width: 100%; }
}
@media (max-width: 420px) {
  /* Section headlines (h2.display) sat on the 2.9rem floor of .display, so a 9 to
     11 letter word such as "Mandatemi" or "Parliamone" (314px) was wider than the
     312px column of a 360px phone and was cut mid-word by the net above. The size
     now follows the screen down to 2.3rem. 11.5vw reaches the old 2.9rem at 404px,
     so 412px and wider are unchanged; the page h1 keeps its own size. */
  h2.display { font-size: clamp(2.3rem, 11.5vw, 2.9rem); }
}
/* Article tables: .table-scroll is the wrapper the /wissen/ articles already
   use. Positioning it makes it the containing block of the visually hidden
   <thead> (clip + nowrap) that the stacked phone layout uses, so that
   header's long text can no longer measure past the viewport (15px on the
   Handwerker article, intermittent). */
.table-scroll { position: relative; max-width: 100%; overflow-x: auto; }


/* ============================================================================
   B/24 — THE MENU BUTTON (2026-10-13)
   Beri asked for the hamburger back: without it the footer was the only road
   to Leistungen, Fertig-Seite, 3D and Wissen. js/components.v2.js initMenu()
   builds one button and one small sheet that drops from it, not the old
   full-screen overlay. css/werk-dark.css carries the same block as A/7.
   ========================================================================== */
/* The wrapper is the sheet's anchor. It also works on the homepage, where the
   header sits in the flow (css/home.css KOPF) instead of being fixed. */
.nav-menu-wrap { position: relative; display: flex; }
/* 44px square, so it is a real tap target, and part of the bar from the first
   paint: it is in the injected header markup, nothing arrives later. */
.nav-menu-btn {
  width: 44px; height: 44px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  transition: border-color .15s ease;
}
.nav-menu-btn:hover { border-color: var(--bone); }
.nav-menu-btn span { display: block; width: 18px; height: 2px; background: var(--bone); transition: transform .2s ease, opacity .15s ease; }
/* Open: the three lines become a cross. The state is read from aria-expanded,
   so what the eye sees and what a screen reader hears cannot disagree. */
.nav-menu-btn[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-menu-btn[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-menu-btn[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
/* The sheet: right-aligned under the button, never wider than the phone. */
.nav-menu {
  position: absolute; top: calc(100% + 1rem); right: 0; z-index: 130;
  width: min(18rem, calc(100vw - 2 * var(--gutter)));
  max-height: calc(100vh - 6.5rem); overflow-y: auto;
  display: flex; flex-direction: column;
  background: var(--ink); border: 1px solid var(--line);
  padding: .6rem 1.2rem 1.2rem;
  box-shadow: 0 18px 40px -24px rgba(0,0,0,.45);
}
.nav-menu[hidden] { display: none; }
.nav-menu a {
  font-variation-settings: var(--wide); font-weight: 680; font-size: 1.02rem;
  color: var(--bone); padding: .62rem 0; border-bottom: 1px solid var(--line-soft);
}
.nav-menu a:hover { color: var(--brass); }
.nav-menu a.active { color: var(--brass); }
/* The briefing is the menu's one call to action: the site's pill, not a row. */
.nav-menu a.nav-menu-cta {
  font-variation-settings: normal; align-self: flex-start; margin-top: 1rem; border-bottom: 0;
  background: var(--brass); color: var(--ink); font-weight: 660; font-size: .95rem;
  padding: .7rem 1.3rem; border-radius: var(--radius-pill);
}
.nav-menu a.nav-menu-cta:hover { color: var(--ink); filter: brightness(1.07); }
.nav-menu a.nav-menu-cta:focus-visible { outline-color: var(--bone); }
@media (prefers-reduced-motion: no-preference) {
  .nav-menu:not([hidden]) { animation: nav-menu-in .18s ease-out; }
}
@keyframes nav-menu-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@media (max-width: 480px) {
  /* On a phone the button takes the place of the two inline links (both are in
     the sheet): mark, name, language and button fit 312px, a third control
     beside the links would not. */
  .site-nav .nav-links { display: none; }
}
