/*
  AXO3 public — the single stylesheet of the public site (onepager, Impressum,
  Datenschutz). Loaded module-wide via the layoutConfig override.

  TAKEN OVER, not rewritten: this is `showcase/public/axo3.css` from
  Z:\labor\axo3-design, which in turn is the port of the Claude Design project
  "Projekt axo3 Abfrage" (design system `modernist-7111b61c`). The deviation
  list lives there, in docs/02-decisions/onepager-aus-claude-design.md — a
  second list here would drift.

  Two changes were made on the way into this repo, both mechanical:
    - the @font-face URLs point at public/assets/frontend/fonts/gt-walsheim/
    - the image slot lost its interactive states (the prototype could be filled
      by drag-and-drop; here it is a static placeholder until the screenshots
      exist)

  ⚠️ The markup carries the design tool's inline styles, and the breakpoints
  below override them with `!important`. Lifting those into classes would be a
  refactor of an approved design — worth doing, but as its own step, against
  the design source.

  ⚠️ Plain CSS, no Sass step: the source is plain CSS and the project has no
  frontend build. public/assets/ is gitignored and seeded from vendor packages
  only, so this file is copied to public/assets/frontend/css/ by hand — locally
  and on the server (ADR-024).
*/

/* ── Fonts ───────────────────────────────────────────────────────────────
   GT Walsheim, and nothing else — public is the brand's face. Licensed from
   Grilli Type and gitignored; the files are copied from
   assets/fonts/gt-walsheim/. Bold covers 700–900 so a stray `font-weight: 800`
   from the design system still lands on the real cut instead of a synthetic
   bold. Medium carries the uppercase kickers, where Bold closes the counters
   too far — see the h6 rule. */
@font-face { font-family: "GT Walsheim"; src: url("../fonts/gt-walsheim/GT-Walsheim-Regular.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "GT Walsheim"; src: url("../fonts/gt-walsheim/GT-Walsheim-Medium.woff2")  format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: "GT Walsheim"; src: url("../fonts/gt-walsheim/GT-Walsheim-Bold.woff2")    format("woff2"); font-weight: 700 900; font-style: normal; font-display: swap; }

/* ── Tokens ──────────────────────────────────────────────────────────────
   Design system "Modernist", with the pages' petrol override resolved in.
   Accent-2 equals accent on purpose: the surface has one accent, ochre is
   the warning state and not a second brand colour. */
:root {
  --color-accent: #14646e;
  --color-accent-100: #dceaec;
  --color-accent-200: #bfd9dd;
  --color-accent-300: #aecfd3;
  --color-accent-400: #4fb3c0;
  --color-accent-500: #1a7d89;
  --color-accent-600: #10545d;
  --color-accent-700: #0c434a;
  --color-accent-800: #0a3438;
  --color-accent-900: #082427;
  --color-accent-2: #14646e;
  --color-accent-2-100: #dceaec;
  --color-accent-2-800: #0a3438;

  --color-bg: #f1f3f3;
  --color-surface: #e4e9ea;
  --color-text: #1b2325;
  --color-divider: color-mix(in srgb, #1b2325 40%, transparent);

  --color-neutral-100: #f8f4f4;
  --color-neutral-200: #e4e9ea;
  --color-neutral-300: #ced7d8;
  --color-neutral-400: #b0bcbe;
  --color-neutral-500: #8f9d9f;
  --color-neutral-600: #7d7979;
  --color-neutral-700: #605d5d;
  --color-neutral-800: #444141;
  --color-neutral-900: #2d2b2b;

  /* public speaks in GT Walsheim throughout — it is the brand's face, not a
     working surface. Inter belongs to member, where the split earns its keep:
     marketing voice against tool. A heading/body split within one page would
     be convention without a reason. */
  --font-heading: "GT Walsheim", system-ui, sans-serif;
  --font-heading-weight: 700;   /* Bold — the heaviest cut we license */
  --font-body: "GT Walsheim", system-ui, sans-serif;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;

  --radius-sm: 0px;
  --radius-md: 0px;
  --radius-lg: 0px;

  /* Rule weight — one lever for every line on the page: band separators,
     boxes, the hero connector, the menu dividers. The design system draws
     at 2px, which is right at desktop measure and reads heavy once the
     bands are 480px wide; the mobile breakpoint thins it. */
  --rule: 2px;

  /* Height of the sticky header bar. These are fallbacks — axo3.js measures
     the rendered bar and overwrites the value, because the bar is taller than
     its parts suggest (the wordmark's line box exceeds the 36px signet) and
     computing it by hand got it wrong by 8px. */
  --header-h: 78px;

  /* The only red on the public site: a form field that failed its check.
     Measured, not chosen — 7.14:1 on --color-bg, 6.04:1 on its own 10% tint,
     so the message is readable as body text in both places. Deliberately NOT
     the design system's ochre: ochre is the warning state, and a rejected
     field is an error the visitor has to fix. */
  --color-danger: #9b221c;

  --shadow-sm: 0 1px 2px color-mix(in srgb, #2d2b2b 14%, transparent);
  --shadow-md: 0 3px 10px color-mix(in srgb, #2d2b2b 16%, transparent);
  --shadow-lg: 0 12px 32px color-mix(in srgb, #2d2b2b 22%, transparent);
}

/* ── Design system base ─────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
body {
  margin: 0; font-size: 15px; line-height: 1.55; font-weight: 400;
  background: var(--color-bg); color: var(--color-text);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  line-height: 1.12; letter-spacing: -0.015em; margin: 0 0 var(--space-2);
}
/* Display tracking. The source draws its headlines in Archivo at 800; the
   licensed GT Walsheim set stops at Bold (700) — see
   docs/02-decisions/schriftwahl.md. Tightening the large sizes recovers most
   of the density that the missing weight would have given, without faking a
   cut the licence does not cover. Small headings keep the looser default,
   where tight tracking costs legibility rather than buying presence. */
h1 { letter-spacing: -0.032em; }
h2 { letter-spacing: -0.030em; }
h3 { letter-spacing: -0.024em; }
h1 { font-size: 42px; }
h2 { font-size: 32px; }
h3 { font-size: 25px; }
h4 { font-size: 20px; }
h5 { font-size: 16px; }
/* The kicker. Versalien give up the ascenders and descenders the eye reads
   words by, and Bold closes the counters on top of that — at 13px with long
   German compounds ("FÜR IMMOBILIENVERWALTER, BAUTRÄGER, VERMARKTER") the
   line clumps. Medium instead of Bold, a size up, and half again the
   tracking: caps want air. Short kickers ("WAS ES TUT") gain from it too. */
h6 {
  font-size: 14px; font-weight: 500;
  letter-spacing: 0.12em; text-transform: uppercase;
}
p { margin: 0 0 var(--space-3); }
a { color: var(--color-accent); text-underline-offset: 3px; }
img { display: block; max-width: 100%; }
figure { margin: 0; }
:focus { outline: none; }
:focus-visible { outline: var(--rule) solid var(--color-accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; text-decoration: none;
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 14px; line-height: 1.2; color: var(--color-text);
  background: transparent; border: 1px solid transparent;
  padding: var(--space-2) calc(var(--space-3) * 1.2);
  border-radius: var(--radius-md);
}
.btn-primary { background: var(--color-accent); color: var(--color-bg); }
.btn-primary:hover { background: var(--color-accent-600); }
.btn-primary:active { background: var(--color-accent-700); }
.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }
.btn-ghost { color: var(--color-accent); padding-inline: var(--space-1); }
.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }

/* The drawing tool's entry in the header and in the burger panel (B5).

   It is not a third door: it needs no account, and it is the one thing on
   this site a stranger can try in the first minute. So it gets its own
   weight — a tinted pill between the ghost «Anmelden» and the solid
   «Registrieren» — instead of the bordered box it used to share with every
   other secondary button.

   ⚠️ `nowrap` on the header actions: «Gebäude zeichnen» is two words, and
   around 1150px it broke into two lines and pushed «Anmelden» out of the
   row. A header action never wraps; if it does not fit, the layout is
   wrong, not the label. */
.header-tool {
  gap: 8px;
  color: var(--color-accent);
  background: var(--color-accent-100);
  border-color: transparent;
}
.header-tool:hover  { background: var(--color-accent-200); }
.header-tool:active { background: var(--color-accent-300); }
.mark-draw { width: 17px; height: 17px; flex: none; }
.header-actions .btn { white-space: nowrap; }

/* Demo application form (B3 v1.11.0) — the target of a demo tenant's
   «Jetzt bewerben». Plain fields; the notice speaks in the accent tone
   because it is information, not an error. */
.an-form__input {
  font: inherit; font-size: 16px; color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
  padding: 10px 12px; width: 100%;
}
.an-form__input:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 1px; }
.an-form__notice {
  margin: 0; font-size: 16px; text-wrap: pretty;
  color: var(--color-accent-800); background: var(--color-accent-100);
  border-radius: var(--radius-md); padding: 12px 16px;
}
.an-form__notice[hidden] { display: none; }

/* «Anbindung anfragen» (`/anbindung`) — the public-form standard in this
   site's own vocabulary. The generic partial's `fe-form` classes have no rule
   in this stylesheet, which is why partials/publicForm is overridden; these
   are the rules that override needs.

   ⚠️ `.an-form__row--error` is the class named in the form's
   `data-error-class`. Rename one and the other stops working silently —
   `public-form.js` sets whatever the attribute says, and a class without a
   rule fails without a word. */
.an-form__row { display: flex; flex-direction: column; gap: 6px; }
.an-form__row--radio { border: 0; margin: 0; padding: 0; min-width: 0; }
.an-form__row--radio > legend { padding: 0; margin-bottom: 6px; }
textarea.an-form__input { resize: vertical; min-height: 7em; line-height: 1.55; }

.an-form__choices { display: flex; flex-direction: column; gap: 8px; }
.an-form__choice {
  display: flex; align-items: flex-start; gap: 10px;
  font-size: 16px; line-height: 1.45; cursor: pointer;
}
/* The native box, only bigger and in the brand's accent. It is stripped
   nowhere: a checkbox has no box of its own to fall back on — strip it and it
   paints NOTHING while staying fully clickable (the framework paid for that
   lesson as NORMALIZE-CHOICE-001). */
.an-form__choice input { flex: none; width: 18px; height: 18px; margin-top: 2px; accent-color: var(--color-accent); }

/* The per-field message. It is rendered even when empty — the script writes
   into it, and an aria-live region only announces what it already owns. Empty
   it must take no room, so no min-height and no margin. */
.an-form__hint { margin: 0; font-size: 14px; color: var(--color-danger); }
.an-form__hint:empty { display: none; }

/* The row's failed state. The border does the work; the label stays black so
   the question remains readable while the answer is being corrected. */
.an-form__row--error .an-form__input { border-color: var(--color-danger); }
.an-form__row--error > legend { color: var(--color-danger); }

/* The banner above the fields: which of CSRF, rate limit or «check the red
   fields» it says is the server's business. */
.an-form__error {
  margin: 0; font-size: 16px; text-wrap: pretty;
  color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 10%, transparent);
  border-left: var(--rule) solid var(--color-danger);
  padding: 12px 16px;
}

.an-form__req { color: var(--color-danger); }
.an-form__legend { margin: 0; font-size: 14px; color: color-mix(in srgb, var(--color-text) 55%, transparent); }

/* Honeypots. ⚠️ Not `display: none` and not `hidden`: some bots skip what is
   plainly gone, and some browsers skip it too. Off-canvas keeps the field a
   real, fillable input for whoever fills everything — which is the point. */
.an-form__hp {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

.tag {
  display: inline-flex; align-items: center; font-size: 11px;
  letter-spacing: 0.02em; padding: 3px 10px;
  border-radius: calc(var(--radius-md) * 0.75);
}
.tag-accent { background: var(--color-accent-100); color: var(--color-accent-800); }
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }

/* ── Page scaffolding ────────────────────────────────────────────────────
   The source repeats this grid container inline on every band. One class,
   same output. */
.wrap {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: 24px; max-width: 1280px; margin: 0 auto;
}
.band { border-bottom: var(--rule) solid var(--color-divider); }

/* Anchor targets stop below the sticky header instead of behind it.
   `scroll-margin-top` moves only the scroll destination — no box, no paint,
   no hit area changes. The old `padding-top: N; margin-top: -N` trick would
   drag each band's background and click area N pixels upward, which on this
   page means the petrol claim band bleeding over the section above it.

   Four pixels short of the bar rather than past it: the band's own divider
   is the last rule of the section above, so a positive offset parks that
   line just under the header's, reading as one thick double stroke. Tucking
   it behind instead lets the section start clean against the bar. */
section[id] { scroll-margin-top: calc(var(--header-h) - 4px); }

/* Gliding, not jumping — but never against an explicit system preference:
   animated scrolling is a trigger for vestibular disorders. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}
.muted-55 { color: color-mix(in srgb, var(--color-text) 55%, transparent); }
.muted-65 { color: color-mix(in srgb, var(--color-text) 65%, transparent); }
.muted-70 { color: color-mix(in srgb, var(--color-text) 70%, transparent); }
.kicker { margin: 0 0 16px; color: var(--color-accent-700); }
.num {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 13px; letter-spacing: 0.08em; color: var(--color-accent-700);
}
.lbl { font-family: var(--font-heading); font-weight: var(--font-heading-weight); }

/* ── Signet — a real cube at the isometric angle, six faces from one accent */
.signet { display: block; width: 36px; height: 36px; --panel: #f1f3f3; }
.cube {
  display: block;
  --edge: 20px;
  width: var(--edge); height: var(--edge);
  margin: 8px 0 0 8px;
  position: relative;
  transform-style: preserve-3d;
  transform: rotateX(-35.264deg) rotateY(45deg);
}
@media (prefers-reduced-motion: no-preference) {
  .cube { animation: cube-turn 16s linear infinite; }
}
@keyframes cube-turn {
  from { transform: rotateX(-35.264deg) rotateY(45deg); }
  to   { transform: rotateX(-35.264deg) rotateY(405deg); }
}
.cube i { position: absolute; inset: 0; }
.f-top    { transform: rotateX(90deg)  translateZ(calc(var(--edge) / 2)); background: color-mix(in oklab, var(--color-accent) 30%, var(--panel)); }
.f-bottom { transform: rotateX(-90deg) translateZ(calc(var(--edge) / 2)); background: color-mix(in oklab, var(--color-accent) 75%, #000); }
.f-front  { transform: translateZ(calc(var(--edge) / 2));                 background: var(--color-accent); }
.f-back   { transform: rotateY(180deg) translateZ(calc(var(--edge) / 2)); background: var(--color-accent); }
.f-left   { transform: rotateY(-90deg) translateZ(calc(var(--edge) / 2)); background: color-mix(in oklab, var(--color-accent) 62%, #10181a); }
.f-right  { transform: rotateY(90deg)  translateZ(calc(var(--edge) / 2)); background: color-mix(in oklab, var(--color-accent) 62%, #10181a); }

/* ── Hero diagram — index.html only ─────────────────────────────────────── */
@keyframes axo-flow { 0% { transform: translateY(-6px); opacity: 0 } 15% { opacity: 1 } 85% { opacity: 1 } 100% { transform: translateY(46px); opacity: 0 } }
@keyframes axo-pulse { 0%, 100% { opacity: 0.25 } 50% { opacity: 1 } }
.pipe { height: 44px; position: relative; display: flex; justify-content: center; }
.pipe span { width: var(--rule); height: 100%; background: var(--color-divider); }
.pipe i { position: absolute; top: 0; width: 8px; height: 8px; background: var(--color-accent); animation: axo-flow 2.4s linear infinite; }
.beat { width: 6px; height: 18px; background: #fff; animation: axo-pulse 1.6s ease-in-out infinite; }

/* Source picker — the `opt()` style object from the source, as a class. */
.sw-opt {
  background: transparent; color: var(--color-text);
  border: var(--rule) solid var(--color-divider);
  padding: 8px 12px; font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 13px; line-height: 1.2; cursor: pointer; text-align: left;
}
.sw-opt[aria-pressed="true"] {
  background: var(--color-accent); color: #fff; border-color: var(--color-accent);
}

/* ── Beispiele — eine Liste, kein Kartenraster ───────────────────────────
   The section used to carry three equal screenshot cards, and an image slot
   standing in for screenshots that were never delivered. Equal boxes force
   equal weight, and the three kinds of output do not have it: the widget is a
   live demo nobody has seen before, onepager and multipager are ordinary
   websites that only need a link.

   So all three are rows of one list — label and sub on the left, what you can
   open on the right. The widget row simply has the running demo hanging under
   it. That puts the word «Widget» ABOVE the thing instead of in a caption
   several hundred pixels below, where a visitor scrolling into a working
   apartment search has no idea what they are looking at. */
.ex-row {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: 24px; align-items: baseline; padding: 22px 0;
  border-top: var(--rule) solid var(--color-divider);
}
.ex-rows > :last-child { border-bottom: var(--rule) solid var(--color-divider); }
.ex-row__head { grid-column: 1 / span 4; display: flex; flex-direction: column; gap: 2px; }
.ex-row__title { font-size: 20px; }
.ex-row__sub { font-size: 15px; text-wrap: pretty; }

/* No border of its own — the row above opens the stage, the row below closes
   it. No fixed height either: the widget sizes itself, min-height only holds
   the section's shape while the script is still loading. */
.ex-stage { min-height: 320px; padding: 4px 0 36px; }

/* Two shapes of the same stock, one behind the other — table and cards. The
   tabs stand ON the stage, above the widget and under the row's label: the
   word «Widget» names the thing, the tabs name what you are looking at right
   now. Segmented like the source picker of the hero (`.sw-opt`), because it
   is the same kind of control — one of n, and the pressed one is filled.

   ⚠️ The panels only ever get `hidden` flipped; both widgets are loaded from
   the start (see the partial). Hence the explicit `[hidden]` rule: the day a
   `.ex-view` gets a `display` of its own, the attribute would stop hiding
   it. */
.ex-tabs { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 20px; }
.ex-tab {
  background: transparent; color: var(--color-text);
  border: var(--rule) solid var(--color-divider);
  padding: 8px 14px; font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 13px; line-height: 1.2; cursor: pointer;
}
.ex-tab[aria-selected="true"] {
  background: var(--color-accent); color: #fff; border-color: var(--color-accent);
}
.ex-view[hidden] { display: none; }

/* One entry per line, not a wrapping row. Three references in a wrapping row
   break 2 + 1 and the second line starts under nothing — the ragged edge reads
   as an accident. A column is tidy at any count and the eye runs down it.
   Past roughly six entries switch to two columns:
   `display: grid; grid-template-columns: 1fr 1fr` — the markup carries it. */
.ex-links {
  grid-column: 5 / span 8; display: flex; flex-direction: column;
  align-items: flex-start; gap: 10px; margin: 0; padding: 0; list-style: none;
}
.ex-links a {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 18px; color: var(--color-accent-700); text-decoration: none;
  border-bottom: var(--rule) solid transparent; padding-bottom: 2px;
}
.ex-links a:hover { border-bottom-color: var(--color-accent); }
.ex-path { font-weight: 400; color: color-mix(in srgb, var(--color-text) 55%, transparent); }
/* Ortsangabe eines Beispiels — Text, kein Link. Die Beispiele nennen keine
   echte Adresse und verlinken keinen Kartendienst mehr (2026-09-01), also
   darf hier auch nichts wie ein Link aussehen: gleiche Typografie wie die
   Liste, aber Textfarbe und keine Unterkante. */
.ex-place {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 18px; color: var(--color-text);
}
/* Placeholder for a demo that does not exist yet. Deliberately not styled as
   a dead link — it must not invite a click. */
.ex-pending {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 18px; color: color-mix(in srgb, var(--color-text) 35%, transparent);
}

/* ── Preistabelle ────────────────────────────────────────────────────────
   A real <table>: the prices have two axes (position × year), and a grid of
   divs would leave a screen reader without the row/column relation. Column
   one carries the position, the two figure columns are right aligned so the
   francs stack on their last digit. The figure is the monthly rate, the yearly
   amount stands next to it in small type — billed is the year, the month is
   the reading aid. */
.price { width: 100%; border-collapse: collapse; }
.price th, .price td {
  text-align: right; vertical-align: baseline;
  padding: 22px 0 22px 24px;
  border-bottom: var(--rule) solid var(--color-divider);
}
.price th:first-child, .price td:first-child { text-align: left; padding-left: 0; }
.price thead th {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 14px; padding-top: 0;
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
  border-bottom-color: var(--color-text);
}
.price tbody th { font-weight: 400; }
.price tbody tr:last-child th, .price tbody tr:last-child td { border-bottom: none; }
.price-name { display: block; font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: 22px; }
.price-note { display: block; font-size: 15px; color: color-mix(in srgb, var(--color-text) 65%, transparent); margin-top: 2px; }
.price-cur  { font-size: 15px; color: color-mix(in srgb, var(--color-text) 65%, transparent); margin-right: 6px; }
.price-fig  { font-family: var(--font-heading); font-weight: var(--font-heading-weight); font-size: 36px; line-height: 1; color: var(--color-accent-700); letter-spacing: -0.02em; }
.price-per  { font-size: 15px; color: color-mix(in srgb, var(--color-text) 65%, transparent); margin-left: 4px; }
.price-cell { display: inline-flex; align-items: baseline; justify-content: flex-end; flex-wrap: wrap; }
/* ⚠️ Each half stays whole: a figure must never break away from its unit.
   The wrapping happens BETWEEN the halves, never inside one. */
.price-month, .price-year { display: inline-flex; align-items: baseline; white-space: nowrap; }
/* The year is the quiet twin here — same parts, one size down, so it reads as
   the same statement rather than a second price. */
.price-year { margin-left: 14px; }
.price-year .price-cur { margin-right: 4px; }
.price-year .price-fig {
  font-family: inherit; font-weight: inherit; font-size: 15px; letter-spacing: 0;
  color: color-mix(in srgb, var(--color-text) 65%, transparent);
}

/* Inclusion lists — no bullet glyph, a rule per item instead: the page draws
   its structure with lines everywhere else. */
.price-list { list-style: none; margin: 0; padding: 0; }
.price-list li {
  font-size: 15px; padding: 10px 0;
  border-top: 1px solid color-mix(in srgb, var(--color-text) 18%, transparent);
  text-wrap: pretty;
}
.price-list li:first-child { border-top: none; padding-top: 0; }

/* ── Mobile navigation ───────────────────────────────────────────────────
   Not in the source — the source has no responsive handling at all. Below
   900px the inline nav and the two header buttons give way to one burger
   that opens a panel holding all of it. Built to grow: further pages are
   one more link in the panel.

   Three bars rather than a three-dot overflow menu: a kebab conventionally
   means "more actions on this object", not "navigation of this site".

   The bars are sheared -30deg onto the same isometric plane the signet's
   cube stands on, so the mark belongs to the brand rather than being a
   stock icon dropped beside it. Still a burger at a glance — the shear is
   the only thing separating it from one. No box: the 44px hit area is
   invisible, the bars align optically with the band's right edge.

   Opening replaces the shear with the X rotation, so the cross comes out
   square rather than as a leaning cross. */
.nav-toggle {
  display: none;                 /* desktop: the inline nav does the work */
  width: 44px; height: 44px; flex: none;
  align-items: center; justify-content: center;
  margin-right: -10px;           /* hit area overhangs; the bars stay on the edge */
  padding: 0; cursor: pointer;
  background: transparent; border: 0;
  -webkit-tap-highlight-color: transparent;
}
.nav-toggle .bars { display: block; position: relative; width: 24px; height: 17px; }
.nav-toggle .bars i {
  position: absolute; left: 0; width: 24px; height: 2.5px;
  background: var(--color-text);
  transform: skewX(-30deg);
  transition: transform 0.2s ease, opacity 0.12s ease;
}
.nav-toggle .bars i:nth-child(1) { top: 0; }
.nav-toggle .bars i:nth-child(2) { top: 7px; }
.nav-toggle .bars i:nth-child(3) { top: 14px; }
.nav-toggle[aria-expanded="true"] .bars i:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .bars i:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .bars i:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
@media (prefers-reduced-motion: reduce) {
  .nav-toggle .bars i { transition: none; }
}

.nav-panel { display: none; }    /* desktop: never, even if left open */
.nav-panel a.nav-link {
  display: block; padding: 15px 0;
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 19px; color: var(--color-text); text-decoration: none;
  border-bottom: var(--rule) solid color-mix(in srgb, var(--color-text) 12%, transparent);
}
.nav-panel a.nav-link:active { color: var(--color-accent); }
.nav-panel .panel-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 22px; }
.nav-panel .panel-actions .btn { font-size: 16px; padding: 14px 20px; justify-content: flex-start; }

/* ── Manual (/zeichnen/hilfe, B5 v1.16.0) ────────────────────────────────
   Nine stations, each one picture and one paragraph. The pictures are
   generated from the running tool (work/bin/probes/hilfe.json) — they carry
   the tool's own chrome, so they get the same thin rule the map has.
   ⚠ Every direct child of `.wrap` needs its grid-column: unplaced, it would
   sit in ONE of the twelve columns. */
.hi-steps {
  grid-column: 1 / span 12; list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 56px;
}
.hi-step { display: grid; grid-template-columns: 4fr 7fr; column-gap: 32px; align-items: start; }
.hi-step__num {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 13px; letter-spacing: 0.08em; color: var(--color-accent-700);
}
.hi-step__title { font-size: 24px; margin: 6px 0 8px; text-wrap: pretty; }
.hi-step__body { margin: 0; font-size: 16px; text-wrap: pretty; color: var(--color-text); }
.hi-step__fig { margin: 0; }
.hi-step__fig img {
  display: block; width: 100%; height: auto;
  border: var(--rule) solid var(--color-divider); background: var(--color-surface);
}
.hi-h2 { font-size: 28px; margin: 0 0 8px; }
.hi-note { margin: 0 0 24px; font-size: 15px; color: color-mix(in srgb, var(--color-text) 65%, transparent); text-wrap: pretty; }
.hi-topic { grid-column: span 6; margin-bottom: 28px; }
.hi-topic__title {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 15px; margin: 0 0 10px;
}
.hi-topic__list { margin: 0; display: grid; grid-template-columns: auto 1fr; column-gap: 16px; row-gap: 8px; font-size: 15px; }
.hi-topic__list dt { color: var(--color-accent-800); white-space: nowrap; }
.hi-topic__list dd { margin: 0; color: color-mix(in srgb, var(--color-text) 75%, transparent); text-wrap: pretty; }

/* ── Rechner (/zeitgewinn) ───────────────────────────────────────────────
   Zwei Eingaben, ein Ergebnis. Die Felder tragen keinen Kasten: eine
   Unterlinie im Akzent ist dieselbe Behandlung, die die Preistabelle den
   Zahlen gibt — eine tippbare Zahl liest sich damit als dieselbe Art Ding
   wie eine gedruckte. Editierbar macht sie nur der Cursor und die Linie,
   die beim Überfahren voll wird. */
.calc-field { display: flex; flex-direction: column; }
.calc-lbl { font-size: 15px; }
.calc-line {
  display: flex; align-items: baseline; gap: 8px;
  border-bottom: var(--rule) solid var(--color-divider);
  margin-top: 6px; padding-bottom: 4px;
}
.calc-field:hover .calc-line, .calc-line:focus-within { border-bottom-color: var(--color-accent); }
.calc-cur { font-size: 17px; color: color-mix(in srgb, var(--color-text) 65%, transparent); }
.calc-input {
  flex: 1 1 auto; min-width: 0; width: 100%;
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 40px; line-height: 1.1; letter-spacing: -0.02em;
  color: var(--color-accent-700);
  background: transparent; border: 0; padding: 0;
  -moz-appearance: textfield;
}
.calc-input::-webkit-outer-spin-button,
.calc-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.calc-input:focus { outline: none; }
.calc-line:focus-within { outline: var(--rule) solid var(--color-accent); outline-offset: 6px; }
.calc-hint { font-size: 14px; margin-top: 10px; text-wrap: pretty; }

/* Der Schieber ist der grobe Griff, das Zahlenfeld der genaue. Gleiche
   Spurhöhe wie die Linien der Seite, damit er in derselben Familie steht
   statt wie ein geborgtes Bedienelement zu wirken. */
.calc-range {
  -webkit-appearance: none; appearance: none;
  width: 100%; margin: 18px 0 0; padding: 0;
  height: 6px; background: var(--color-accent-200); border: 0;
  cursor: pointer;
}
.calc-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 16px; height: 26px; border: 0; border-radius: 0;
  background: var(--color-accent); cursor: grab;
}
.calc-range::-moz-range-thumb {
  width: 16px; height: 26px; border: 0; border-radius: 0;
  background: var(--color-accent); cursor: grab;
}
.calc-range::-moz-range-track { height: 6px; background: var(--color-accent-200); }

/* Das Ergebnis. Dieselbe Zahlenfamilie wie die Preistabelle, zwei Stufen
   grösser — es ist die eine Aussage der Seite. */
.calc-out { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.calc-out__fig {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 72px; line-height: 1; letter-spacing: -0.03em;
  color: var(--color-accent-700);
  font-variant-numeric: tabular-nums;
}
.calc-out__unit, .calc-out__cur {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 20px; color: color-mix(in srgb, var(--color-text) 65%, transparent);
}
.calc-out__cur { font-size: 17px; }

/* Herleitung und die zwei Quelltabellen. Eine Linie je Zeile, wie bei den
   Einschlusslisten — die Seite zeichnet ihre Struktur überall mit Linien. */
.calc-tab { width: 100%; border-collapse: collapse; font-size: 16px; }
.calc-tab caption {
  text-align: left; font-size: 15px; padding-bottom: 10px;
  color: color-mix(in srgb, var(--color-text) 65%, transparent);
}
.calc-tab th, .calc-tab td {
  text-align: right; padding: 11px 0 11px 16px;
  border-top: 1px solid color-mix(in srgb, var(--color-text) 18%, transparent);
  font-variant-numeric: tabular-nums;
}
.calc-tab th:first-child, .calc-tab td:first-child {
  text-align: left; padding-left: 0; text-wrap: pretty;
}
.calc-tab thead th {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 13px; letter-spacing: 0.06em; text-transform: uppercase;
  border-top: 0; padding-top: 0;
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
}
.calc-tab tbody tr:first-child td { border-top-width: var(--rule); border-top-color: var(--color-text); }
.calc-tab thead + tbody tr:first-child td { border-top-width: 1px; border-top-color: color-mix(in srgb, var(--color-text) 18%, transparent); }
.calc-tab tfoot td {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  border-top: var(--rule) solid var(--color-text);
  color: var(--color-accent-700);
}
.calc-tab--3 td:not(:first-child), .calc-tab--3 th:not(:first-child) { width: 22%; }

/* ── Breakpoints ─────────────────────────────────────────────────────────
   `!important` throughout: the source carries its padding and font sizes as
   inline styles, which otherwise outrank anything declared here. */
/* ── Der Schreier ───────────────────────────────────────────
   Ein Reiter am rechten Rand, der zum Kontakt führt — die Angaben stehen erst
   in der letzten Sektion. Senkrecht gesetzt (`writing-mode`), damit er neben
   dem Inhalt steht statt auf ihm: er belegt gut 40 Pixel Randbreite und
   verdeckt bei keiner Fensterbreite Text.

   ⚠️ `z-index: 4` ist eine Wahl, keine Zahl aus der Luft: die Kopfzeile
   liegt auf 5 und muss gewinnen (sie ist klebrig und fährt am Reiter vorbei),
   die Filter-Overlays des Widgets liegen auf 99999 und müssen erst recht
   gewinnen — ein Schreier über einem offenen Sheet wäre ein Fehler.

   ⚠️ **Ruhend ist er VERSTECKT**, und das ist eine Korrektur: die erste
   Fassung lieferte ihn sichtbar aus, damit er auch ohne JavaScript dasteht.
   Nur läuft `axo3.js` mit `defer` als letzte Zeile vor `</body>` — der Reiter
   stand also vom ersten Bild an da und wurde erst nach dem ganzen Dokument
   weggeblendet. Das sah bei jedem Aufruf aus wie ein Blinken. Ein Element,
   dessen Sichtbarkeit ohnehin vom Scrollstand abhängt, kann keinen sinnvollen
   Ruhezustand vor dem Skript haben; ohne JavaScript entfällt er, und der
   gleiche Weg steht im Burger-Menü und in der Fusszeile.

   `visibility` statt nur `opacity`, damit er im versteckten Zustand auch aus
   der Tab-Reihe fällt — ein unsichtbares Ziel, das den Fokus fängt, ist
   schlimmer als gar keines. */
.shout {
  position: fixed; right: 0; top: 50%; z-index: 4;
  transform: translateY(-50%);
  writing-mode: vertical-rl;
  background: var(--color-accent); color: #fff; text-decoration: none;
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 14px; line-height: 1; letter-spacing: 0.02em;
  padding: 18px 12px;
  border: var(--rule) solid var(--color-accent);
  opacity: 0; visibility: hidden;
  /* ⚠️ Eigene Ebene. Ein festes Element, das der Browser NICHT eigens
     zusammensetzt, zwingt ihn beim Scrollen dazu, den Bereich darunter neu zu
     malen — auf manchen Rechnern sieht das aus, als flackere der Text. */
  will-change: opacity;
}
.shout__short { display: none; }
.shout:hover, .shout:focus-visible {
  background: var(--color-accent-600); border-color: var(--color-accent-600);
}
.shout.is-on { opacity: 1; visibility: visible; }

/* ⚠️ KEINE Blende beim Erscheinen. Wer langsam über die Auslöse-Grenze
   scrollt, sonst zweimal die Sekunde eine halbe Blende sieht — das liest sich
   als Flackern, nicht als Übergang. Nur die Farbe beim Zeigen darauf bleibt
   weich, die hängt an keiner Scrollposition. */
@media (prefers-reduced-motion: no-preference) {
  .shout { transition: background-color .15s ease; }
}

@media (max-width: 1100px) {
  .wrap { padding-inline: 32px !important; }
  .hero-title { font-size: 48px !important; }
  .sec-title { font-size: 36px !important; }
  .claim { font-size: 40px !important; }
}
/* Between the wide layout and the burger the three actions and the section
   nav share one row. Tighten both instead of dropping one of them: the
   drawing entry is the reason the header has three actions at all. */
@media (max-width: 1150px) {
  .site-nav { gap: 18px !important; font-size: 13px !important; }
  .header-actions { gap: 6px !important; }
  .header-actions .btn { padding-inline: calc(var(--space-3) * 0.85); font-size: 13px; }
  .header-tool { gap: 6px; }
  .mark-draw { width: 15px; height: 15px; }
}

@media (max-width: 900px) {
  /* Senkrecht am Rand ist auf einem Telefon ein schlechtes Ziel — zu schmal
     für den Daumen und mitten im Wischbereich. Unten rechts als Pille, mit
     Abstand zum Rand, damit die Systemleisten nicht danebengreifen. */
  .shout {
    writing-mode: horizontal-tb; top: auto; bottom: 16px; right: 16px;
    transform: none; padding: 15px 20px; font-size: 15px;
  }
  .shout__long  { display: none; }
  .shout__short { display: inline; }

  :root { --rule: 1px; --header-h: 73px; }

  /* the manual stacks: picture under its paragraph, one topic column */
  .hi-step { grid-template-columns: 1fr; row-gap: 16px; }
  .hi-steps { gap: 40px; }
  .hi-topic__list { grid-template-columns: 1fr; row-gap: 2px; }
  .hi-topic__list dd { margin-bottom: 8px; }

  .wrap { display: block !important; padding-inline: 24px !important; }
  .wrap > * { grid-column: auto !important; margin-bottom: 28px; }
  .wrap > *:last-child { margin-bottom: 0; }

  /* Vertical rhythm. The source's 64–72px band padding is measured against a
     1280px column; on a 480px one it leaves the page looking empty. Scaled to
     roughly half, with two exceptions: `wrap-cont` continues the band above it
     and must not reopen a gap, `wrap-tight` is a tinted strip that was already
     shallow. */
  .wrap { padding-block: 40px !important; }
  .wrap-tight { padding-block: 26px !important; }
  .wrap-cont { padding-top: 0 !important; }
  footer .wrap { padding-block: 34px !important; }
  footer .wrap + .wrap { padding-top: 0 !important; }
  .hero-title { font-size: 36px !important; }
  .sec-title { font-size: 28px !important; }
  .claim { font-size: 28px !important; }
  .site-nav { display: none !important; }
  /* Bare spans become blocks so they stack instead of running together —
     the step numbers and their timings, the price row's note. Spans that
     carry an inline `display` (brand lockup, footer columns) keep it. */
  .wrap > span { display: block; }
  .steps > .num { margin-bottom: 4px !important; }

  /* The header is the one band that must not stack: brand left, burger
     right, on one line. More specific than the `.wrap` rule above, so it
     wins despite both being `!important`. */
  .site-header .wrap {
    display: flex !important; align-items: center; justify-content: space-between;
    gap: 16px; padding-block: 14px !important;
  }
  .site-header .wrap > * { margin-bottom: 0 !important; }
  .header-actions { gap: 0 !important; }
  .header-cta { display: none !important; }
  .nav-toggle { display: inline-flex; }

  /* Beispiele: the row grid collapses, the widget stage loses a third of its
     height — 440px against a 480px column reads as a wall, not a stage. */
  .ex-stage { min-height: 220px; padding: 4px 0 26px; }
  .ex-tabs { margin-bottom: 12px; }
  .ex-tab { flex: 1 1 auto; text-align: center; font-size: 14px; padding: 10px 12px; }
  .ex-row { display: block !important; padding: 16px 0; }
  .ex-links { margin-top: 10px; gap: 8px; }
  .ex-links a, .ex-pending, .ex-place { font-size: 17px; }

  /* Preise: three columns on 480px would put each franc figure on its own
     line. The table stays a table — two figures and a short label still fit —
     but everything shrinks a step. */
  .price th, .price td { padding: 16px 0 16px 12px; }
  .price thead th { font-size: 12px; }
  .price-name { font-size: 17px; }
  .price-note { font-size: 13px; }
  .price-fig  { font-size: 24px; }
  .price-cur  { font-size: 12px; margin-right: 3px; }
  .price-per  { font-size: 12px; margin-left: 2px; }

  /* ⚠️ Here only the YEAR stands (Peter 2026-08-26). Both amounts side by
     side needed ~112px in a 99px column, so the figure broke off its unit;
     stacked they cost a line each and the table grew taller than the section.
     The year is the one that is invoiced, so the year is the one that stays.
     The column head above already says WHICH year — hence no «im Jahr».
     ⚠️ `.price-year .price-fig` (0,2,0) has to restate the whole figure face:
     it overrides its own desktop rule, which made the twin small and grey. */
  .price-month { display: none; }
  .price-year  { margin-left: 0; }
  .price-year .price-cur { margin-right: 3px; }
  .price-year .price-fig {
    font-family: var(--font-heading); font-weight: var(--font-heading-weight);
    font-size: 24px; letter-spacing: -0.02em; color: var(--color-accent-700);
  }
  .price-year .price-per { display: none; }

  .nav-panel {
    display: block;
    border-top: var(--rule) solid var(--color-divider);
    background: var(--color-bg);
    padding: 4px 24px 26px;
  }
  .nav-panel[hidden] { display: none; }

  /* Rechner: die Zahl bleibt die Aussage, aber 72px sprengen eine 480er
     Spalte. Die Tabellen rücken enger, die dritte Spalte schmaler. */
  .calc-input { font-size: 32px; }
  .calc-out__fig { font-size: 52px; }
  .calc-out__unit { font-size: 17px; }
  .calc-tab { font-size: 15px; }
  .calc-tab th, .calc-tab td { padding: 9px 0 9px 10px; }
  .calc-tab--3 td:not(:first-child), .calc-tab--3 th:not(:first-child) { width: 20%; }
}

/* ── Isometrie — /isometrie only ─────────────────────────────────────────
   Not in the source. The page explains the isometry and carries two live
   examples; what follows is its frame, never the product inside. */

/* The axis diagram next to "Was eine Isometrie ist". Faces from the same
   accent mixes as the signet's cube, so the figure and the mark agree. */
.iso-fig svg { display: block; max-width: 300px; margin: 0 auto; }
.iso-fig__top   { fill: color-mix(in oklab, var(--color-accent) 30%, var(--color-bg)); }
.iso-fig__left  { fill: color-mix(in oklab, var(--color-accent) 62%, #10181a); }
.iso-fig__right { fill: var(--color-accent); }
.iso-fig__cube polygon { stroke: var(--color-bg); stroke-width: 1; }
.iso-fig__axes line { stroke: var(--color-text); stroke-width: 1.2; stroke-dasharray: 3 3; opacity: 0.55; }
.iso-fig__angle path { fill: none; stroke: var(--color-accent-700); stroke-width: 1.2; }
.iso-fig__lbl text {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 11px; fill: var(--color-accent-700);
}

/* Leistung: the ex-row list reused for bullet points instead of links.
   Same column, same rhythm, plain weight — these are statements, not
   things to open. */
.iso-list li {
  font-family: var(--font-body); font-weight: 400; font-size: 16px;
  color: var(--color-text); text-wrap: pretty;
  padding-left: 18px; position: relative;
}
.iso-list li::before {
  content: ""; position: absolute; left: 0; top: 0.62em;
  width: 7px; height: 7px; background: var(--color-accent);
}

/* FAQ as a definition list: one rule per pair, question in the heading
   face, answer at body size. Visible text and the FAQPage JSON-LD in the
   head carry the same pairs — keep them in step. */
.faq { margin: 0; }
.faq__item {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: 24px;
  padding: 22px 0; border-top: var(--rule) solid var(--color-divider);
}
.faq__item:last-child { border-bottom: var(--rule) solid var(--color-divider); }
.faq dt {
  grid-column: 1 / span 5; margin: 0;
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 20px; letter-spacing: -0.01em; text-wrap: pretty;
}
.faq dd { grid-column: 6 / span 7; margin: 0; font-size: 16px; text-wrap: pretty; }

/* The example stage. The bar is the page's chrome (2D/3D, views,
   Auto) — the set ships without a head row. Below it, one pane at a time;
   both get the same box so switching 2D/3D does not jump
   (axo3/showcase/isometrie/README.md, "Groesse vor Sprungfreiheit"). */
.iso-demo { min-height: 0; }
.iso-demo__bar {
  display: flex; align-items: center; gap: 24px; flex-wrap: wrap;
  padding: 0 0 16px;
}
.iso-demo__grp { display: flex; gap: 6px; }
.iso-demo__hint { font-size: 13px; text-wrap: pretty; }
.iso-demo__pane { display: flex; }
.iso-demo__pane[hidden] { display: none; }
.iso-demo .iso-app, .iso-demo .i3app {
  border-radius: 0; border-color: var(--color-divider);
  font-family: var(--font-body);
}
.iso-demo .iso-app .stage, .iso-demo .i3app .i3stage { min-height: 520px; }

@media (max-width: 900px) {
  .iso-fig svg { max-width: 220px; }
  .faq__item { display: block; padding: 16px 0; }
  .faq dt { font-size: 18px; margin-bottom: 8px; }
  .iso-list li { font-size: 15px; }
  .iso-demo__bar { gap: 12px; }
  .iso-demo__hint { flex-basis: 100%; }
  .iso-demo .iso-app .stage, .iso-demo .i3app .i3stage { min-height: 360px; }
}

/* An older generator state ships the set's own head row (title, SE/SW/NW/NE,
   Auto) — and that head carries the baked-in project title. The page's bar
   does that job for both examples, so the set's head is hidden here. Host
   CSS, the fragment stays verbatim. ⚠️ Hiding is the fallback, not the rule:
   what AXO3 ships must be HEADLESS (B6 spec v1.3.2) — a head row names the
   building the examples are deliberately anonymous about. */
.iso-demo .iso-app .iso-head, .iso-demo .i3app .i3head { display: none; }
