/* ============================================================================
   Klangschalenwelt — design system
   Phase 3. Every colour pair here is verified by tools/check-contrast.py:
   19 pairs, 0 failures, body text at 14.45:1.

   Order: fonts, tokens, reset, base, layout, components, utilities, print.
   ========================================================================= */

/* ---------------------------------------------------------------- fonts --
   Self-hosted, subset to 175 glyphs, variable weight axis (§10, §12).
   No request leaves this origin — not even for a font.

   font-display: swap, paired with a metric-matched fallback below so the swap
   does not shift layout. Without the overrides, Georgia standing in for
   EB Garamond moves every line.                                            */

@font-face {
  font-family: "EB Garamond";
  src: url("/assets/fonts/eb-garamond-latin-var.woff2") format("woff2");
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "EB Garamond";
  src: url("/assets/fonts/eb-garamond-italic-latin-var.woff2") format("woff2");
  font-weight: 400 800;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Source Sans 3";
  src: url("/assets/fonts/source-sans-3-latin-var.woff2") format("woff2");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Source Sans 3";
  src: url("/assets/fonts/source-sans-3-italic-latin-var.woff2") format("woff2");
  font-weight: 200 900;
  font-style: italic;
  font-display: swap;
}

/* Metric-matched stand-ins. These render before the webfont arrives and are
   tuned so the line boxes match, which keeps CLS at zero during the swap. */
@font-face {
  font-family: "EB Garamond Fallback";
  src: local("Georgia"), local("Times New Roman"), local("Times");
  size-adjust: 94%;
  ascent-override: 92%;
  descent-override: 26%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Source Sans 3 Fallback";
  src: local("Segoe UI"), local("Helvetica Neue"), local("Arial");
  size-adjust: 100%;
  ascent-override: 94%;
  descent-override: 26%;
  line-gap-override: 0%;
}

/* ---------------------------------------------------------------- tokens */
:root {
  /* ======================================================================
     BRAND COLOURS — the two values to change if the brand ever changes.
     Everything else on the site references or is derived from these.
     ====================================================================== */
  --brand-primary:   #08233A;   /* navy  — sampled from oberer teil verbessert 7.8.26.png */
  --brand-secondary: #C9A44C;   /* gold  — sampled from the same render                    */

  /* -- surfaces: primary and its shades -- */
  --navy:            var(--brand-primary);
  --navy-deep:       #04182B;   /* derived: primary darkened, for the footer   */
  --navy-soft:       #123249;   /* derived: primary lifted, for cards on navy  */
  --cream:           #FAF6EE;
  --card:            #FFFCF6;
  --rule:            #E4DACA;

  /* -- gold: four values, and the distinction is load-bearing --
     --gold          DECORATION only. Fine on navy, but only 2.2:1 on cream, so
                     it must never carry text on a light ground.
     --gold-bright   decoration on navy where it needs to lift.
     --gold-on-navy  TEXT on navy.
     --gold-on-cream TEXT on cream. Deeper than the render shows, because the
                     render's value fails AA outright — the §6 deviation.

     These four are NOT derived by formula on purpose. Each was tuned until it
     passed WCAG AA, and a colour-mix would silently move it off the number that
     was verified. If you change --brand-secondary, run

         python tools/check-contrast.py

     and adjust these until it reports 0 failures. The same applies to
     --navy-deep and --navy-soft above.                                       */
  --gold:            var(--brand-secondary);
  --gold-bright:     #E0BC6A;
  --gold-on-navy:    #D9B45F;
  --gold-on-cream:   #7A5F1E;

  /* -- ink -- */
  --ink:             #242420;
  --ink-soft:        #4A4842;
  --paper:           #FFFFFF;
  --paper-soft:      #C9D2DC;

  /* -- type -- */
  --font-display: "EB Garamond", "EB Garamond Fallback", Georgia, "Times New Roman", serif;
  --font-body: "Source Sans 3", "Source Sans 3 Fallback", "Segoe UI", system-ui, -apple-system, Arial, sans-serif;

  /* ---- fluid type scale ------------------------------------------
     Generated by tools/build-type-scale.py — do not hand-edit.
     Body 16px -> 20px between 320px and 1600px viewport, then on to 22px
     at 2560px in the @media block that follows :root.

     Two segments because one was not enough: capped at 1600px, a 3440px
     monitor got exactly the same sizes as a 1720px window, and the
     page read as small. Above 2560px the scale is flat — past that,
     extra width becomes margin, not longer lines.

     Both ends are rem, so the browser's font-size setting still
     scales everything (WCAG 2.2 SC 1.4.4). The vw term only shapes
     the curve between the endpoints. ------------------------------ */
  --t-xs:    clamp(0.7812rem, 0.7578rem + 0.1172vw, 0.875rem);
  --t-sm:    clamp(0.875rem, 0.8359rem + 0.1953vw, 1.031rem);
  --t-base:  clamp(1rem, 0.9375rem + 0.3125vw, 1.25rem);
  --t-lead:  clamp(1.125rem, 1.031rem + 0.4688vw, 1.5rem);
  --t-h3:    clamp(1.25rem, 1.125rem + 0.625vw, 1.75rem);
  --t-h2:    clamp(1.5rem, 1.281rem + 1.094vw, 2.375rem);
  --t-h1:    clamp(1.875rem, 1.531rem + 1.719vw, 3.25rem);
  --t-hero:  clamp(1.875rem, 0.8438rem + 5.156vw, 6rem);
  --t-logo:  clamp(0.9375rem, 0.8281rem + 0.5469vw, 1.375rem);
  --t-eyebrow: clamp(1rem, 0.875rem + 0.625vw, 1.5rem);

  /* ---- fluid spacing, same segments ----------------------------- */
  --s-1:     clamp(0.25rem, 0.2344rem + 0.07812vw, 0.3125rem);
  --s-2:     clamp(0.5rem, 0.4688rem + 0.1562vw, 0.625rem);
  --s-3:     clamp(0.75rem, 0.7188rem + 0.1562vw, 0.875rem);
  --s-4:     clamp(1rem, 0.9375rem + 0.3125vw, 1.25rem);
  --s-5:     clamp(1.5rem, 1.406rem + 0.4688vw, 1.875rem);
  --s-6:     clamp(2rem, 1.812rem + 0.9375vw, 2.75rem);
  --s-7:     clamp(3rem, 2.688rem + 1.562vw, 4.25rem);
  --s-8:     clamp(4rem, 3.5rem + 2.5vw, 6rem);
  --s-9:     clamp(6rem, 5.312rem + 3.438vw, 8.75rem);

  /* ---- other fluid sizes, same segments ------------------------- */
  --icon:    clamp(4.5rem, 3.625rem + 4.375vw, 8rem);

  --lh-tight: 1.15;
  --lh-head:  1.22;
  --lh-body:  1.65;

  --ls-caps: 0.11em;   /* small-caps labels */
  --ls-head: -0.005em;


  /* Section rhythm — the one value that sets the site's calm. */
  --section-y: clamp(2.5rem, 6vw, 5.5rem);
  --gutter:    clamp(1.25rem, 5vw, 2.5rem);
  --measure:   68ch;   /* readable line length, per element */
  /* Two columns, deliberately. --column is the *heading* column: it matches
     .sec (80rem) so the top of a page lines up with the picture grid below it.
     --copy is the prose column, set from a counted line length rather than a
     guess -- at the lead size it lands around 86 characters, where 80rem would
     be 140. See DECISIONS D100. */
  --column:    min(92vw, 68rem);
  --copy:      min(92vw, 68rem);
  /* The container used to be a flat 75rem. On a 2560px monitor that left 680px
     of dead margin on each side and the whole page read as a small column
     floating in the middle — "painfully centered", which is exactly what it
     looked like. It now grows with the viewport and only stops at 110rem.
     Paragraphs stay readable regardless because --measure caps the line
     length independently. */
  --wrap:      min(92vw, calc(120rem + (100vw - 120rem) * 0.4));

  --radius:    3px;
  --radius-lg: 6px;

  --shadow-card: 0 1px 2px rgba(15, 36, 56, 0.05),
                 0 8px 24px -12px rgba(15, 36, 56, 0.12);

  --focus-color: var(--gold-on-cream);   /* 4.6:1 on cream */
  --focus: 3px solid var(--focus-color);
}

/* ---- fluid scale, large-screen segment (generated) ---------------- */
/* The continuation from 1600px to 2560px. Same tokens, second segment; it
   has to sit after :root because it overrides the same properties. */
@media (min-width: 100rem) {
  :root {
    --t-xs:    clamp(0.875rem, 0.7708rem + 0.1042vw, 0.9375rem);
    --t-sm:    clamp(1.031rem, 0.875rem + 0.1562vw, 1.125rem);
    --t-base:  clamp(1.25rem, 1.042rem + 0.2083vw, 1.375rem);
    --t-lead:  clamp(1.5rem, 1.188rem + 0.3125vw, 1.688rem);
    --t-h3:    clamp(1.75rem, 1.333rem + 0.4167vw, 2rem);
    --t-h2:    clamp(2.375rem, 1.646rem + 0.7292vw, 2.812rem);
    --t-h1:    clamp(3.25rem, 2.104rem + 1.146vw, 3.938rem);
    --t-hero:  clamp(6rem, 3.5rem + 2.5vw, 7.5rem);
    --t-logo:  clamp(1.375rem, 0.9583rem + 0.4167vw, 1.625rem);
    --t-eyebrow: clamp(1.5rem, 1.083rem + 0.4167vw, 1.75rem);

    --s-1:     clamp(0.3125rem, 0.2604rem + 0.05208vw, 0.3438rem);
    --s-2:     clamp(0.625rem, 0.5208rem + 0.1042vw, 0.6875rem);
    --s-3:     clamp(0.875rem, 0.7708rem + 0.1042vw, 0.9375rem);
    --s-4:     clamp(1.25rem, 1.042rem + 0.2083vw, 1.375rem);
    --s-5:     clamp(1.875rem, 1.458rem + 0.4167vw, 2.125rem);
    --s-6:     clamp(2.75rem, 1.917rem + 0.8333vw, 3.25rem);
    --s-7:     clamp(4.25rem, 3rem + 1.25vw, 5rem);
    --s-8:     clamp(6rem, 4.125rem + 1.875vw, 7.125rem);
    --s-9:     clamp(8.75rem, 6.042rem + 2.708vw, 10.38rem);
    --icon:    clamp(8rem, 3.833rem + 4.167vw, 10.5rem);
  }
}
/* ---- end large-screen segment ------------------------------------- */

/* ----------------------------------------------------------------- reset */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;   /* Safari inflates text in landscape */
  text-size-adjust: 100%;
  scroll-behavior: smooth;
}

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

body {
  margin: 0;
  /* German runs to long compounds — "Klangschalensets",
     "Instrumente für Klangerlebnisse" — which overflow a narrow card and, with
     the card clipping, simply disappear. Automatic hyphenation is the correct
     answer for German and needs lang="de" on <html>, which the layout sets.
     break-word is the backstop for anything the hyphenation dictionary does
     not know. */
  hyphens: auto;
  -webkit-hyphens: auto;
  overflow-wrap: break-word;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--t-base);
  line-height: var(--lh-body);
  font-variant-numeric: oldstyle-nums;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

img, svg, video { max-width: 100%; height: auto; display: block; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: var(--lh-head);
  letter-spacing: var(--ls-head);
  color: var(--navy);
  margin: 0 0 var(--s-4);
  text-wrap: balance;          /* degrades silently where unsupported */
}

h1 { font-size: var(--t-h1); line-height: var(--lh-tight); }
h2 { font-size: var(--t-h2); }
h3 { font-size: var(--t-h3); }

p, ul, ol { margin: 0 0 var(--s-4); }
p { max-width: var(--measure); }

a { color: var(--navy); text-decoration-thickness: 1.5px; text-underline-offset: 3px; }

/* Hover only where hovering exists, so a card cannot stick in its hover state
   after a tap (§13). */
@media (hover: hover) {
  a:hover { text-decoration-thickness: 2.5px; }
}

:focus-visible {
  outline: 3px solid var(--focus-color);
  outline-offset: 2px;
  border-radius: 2px;
}

/* The single dark-gold ring was 2.66:1 against the navy masthead and 2.80:1
   against the lightbox backdrop — both under the 3:1 that SC 1.4.11 requires of
   a focus indicator. On dark ground it becomes near-white instead. */
.head,
.foot,
.section--navy,
.lightbox {
  --focus-color: var(--paper);
}

/* ---------------------------------------------------------------- layout */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.wrap--narrow { max-width: 46rem; }

/* Five short items spread across the full width of a 3440px monitor stop
   reading as a row and become five isolated things, which is why the icons felt
   small however large they were drawn — it is the spacing between them, not
   their size. Capping the row keeps them a group. */
.wrap--reasons { max-width: min(92vw, 112rem); }


/* Prose keeps the width it had at 1920. Everything else may use the wider
   container, but two columns of German text pushed further apart do not read
   better — they read worse, and the eye loses the return sweep. */
.wrap--prose {
  max-width: min(92vw, calc(2 * var(--measure) + var(--s-8)
                            + 2 * var(--gutter)));
}

.section { padding-block: var(--section-y); }
.section--navy { background: var(--navy); color: var(--paper); }
.section--navy h2, .section--navy h3 { color: var(--paper); }
.section--card { background: var(--card); }

/* ------------------------------------------------------------ components */

/* -- eyebrow: gold small-caps label above a section -- */
.eyebrow {
  font-family: var(--font-body);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--gold-on-cream);
  margin: 0 0 var(--s-2);
}

.section--navy .eyebrow { color: var(--gold-on-navy); }

/* -- divider: lotus mark between two hairlines (§6 motif) -- */
.divider {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin-block: var(--s-6);
  color: var(--gold);
}

.divider::before,
.divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: currentColor;
  opacity: 0.5;
}

.divider__mark { flex: none; width: 22px; height: 22px; }

/* -- buttons: gold pill, generous padding -- */
.cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 48px;                    /* §13: 44 is the floor, 48 the target */
  min-width: 48px;
  padding: var(--s-3) var(--s-5);
  border: 1px solid transparent;
  border-radius: 999px;
  background: var(--gold);
  color: var(--navy);                  /* 6.69:1 */
  font-family: var(--font-body);
  font-size: var(--t-sm);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-decoration: none;
  /* At 200% text on a 375px phone this button is 361px of label and padding in
     a 335px column, and it pushed the page 41px wide — a WCAG 2.2 SC 1.4.4
     failure. It may wrap to two lines rather than overflow; the pill shape and
     the 48px floor both survive that. */
  max-width: 100%;
  text-align: center;
  text-wrap: balance;
  text-align: center;
  cursor: pointer;
  transition: background-color 140ms ease, transform 140ms ease;
}

@media (hover: hover) {
  .cta:hover { background: var(--gold-bright); }
}

.cta:active { transform: translateY(1px); }

.cta--ghost {
  background: transparent;
  border-color: var(--gold);
  color: var(--gold-on-cream);
}

.section--navy .cta--ghost { color: var(--gold-on-navy); }

@media (hover: hover) {
  .cta--ghost:hover { background: rgba(201, 164, 76, 0.14); }
}

/* -- cards -- */
.card {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.card__body { padding: var(--s-4) var(--s-4) var(--s-5); min-width: 0; }

/* The card clips, so anything that will not wrap is lost rather than merely
   ugly. German compounds — "Klangerlebnisse", "Klangschalensets" — are longer
   than a 107px card at the largest text setting, so breaking inside a word is
   the correct fallback here even though it is not normally desirable. */
.card__title,
.card__text {
  overflow-wrap: anywhere;
  hyphens: auto;
}

.card__title { font-size: var(--t-h3); margin-bottom: var(--s-2); }
.card__text { color: var(--ink-soft); font-size: var(--t-sm); margin: 0; }

.card__media {
  aspect-ratio: 4 / 3;
  background: var(--rule);
  object-fit: cover;
  width: 100%;
}

/* -- grids. Explicit collapse per §13, not left to auto-fit -- */
.grid { display: grid; gap: var(--s-5); }

/* A grid item defaults to `min-width: auto`, which means it refuses to shrink
   below its content's intrinsic width. At 320px that made the two Sortiment
   cards 156px each inside a 280px track and pushed 16px of horizontal scroll
   onto the page — which §18 forbids at any width from 320px up. */
.grid > * { min-width: 0; }

/* Five Sortiment cards: 2 up on phones, 3 on tablets, 5 on desktop. */
.grid--sortiment { grid-template-columns: repeat(2, 1fr); }
@media (min-width: 48rem) { .grid--sortiment { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 68rem) { .grid--sortiment { grid-template-columns: repeat(5, 1fr); } }

/* The five category names are not the same length — "Instrumente für
   Klangerlebnisse" takes two lines where the others take one — so every card's
   description started at a different height and the row read as ragged.
   Reserving two lines for the title aligns them. It is a reservation, not a
   cap: a longer title still wraps past it rather than being clipped. Only
   where the cards actually sit side by side. */
@media (min-width: 48rem) {
  .grid--sortiment .card__title {
    line-height: var(--lh-tight);
    min-height: calc(2em * var(--lh-tight));
  }
}

/* Five reasons: 2 up, then 3, then 5. */
.grid--reasons { grid-template-columns: repeat(2, 1fr); }
@media (min-width: 44rem) { .grid--reasons { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 62rem) { .grid--reasons { grid-template-columns: repeat(5, 1fr); } }

/* Two wide professional cards: stacked, then side by side. */
.grid--pro { grid-template-columns: 1fr; }
@media (min-width: 56rem) { .grid--pro { grid-template-columns: repeat(2, 1fr); } }

/* Two-column prose. */
.grid--prose { grid-template-columns: 1fr; gap: 0 var(--s-7); }
@media (min-width: 56rem) { .grid--prose { grid-template-columns: repeat(2, 1fr); } }


/* -- holiday banner (§8) --
   Warm amber on deep navy text, deliberately unlike the navy header so it
   reads as a notice rather than as chrome. Not dismissible. */
.banner {
  background: var(--gold-bright);
  color: #3A2C05;
  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
  padding-top: env(safe-area-inset-top);
}

.banner__inner {
  max-width: var(--wrap);
  margin-inline: auto;
  padding: var(--s-3) var(--gutter);
}

.banner__text { margin: 0; max-width: none; font-size: var(--t-sm); }
.banner__label { font-weight: 700; }
.banner a { color: inherit; font-weight: 700; }

/* -- skip link -- */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
}

.skip:focus {
  left: var(--s-2);
  top: var(--s-2);
  display: inline-flex;
  align-items: center;
  min-height: 44px;          /* §13: applies to the skip link too */
  padding: var(--s-2) var(--s-5);
  background: var(--navy);
  color: var(--paper);
  border-radius: var(--radius);
  text-decoration: none;
}

/* -- header --
   Two rows, matching the mockup: identity and primary action above, the
   navigation on its own bar below. The nav having a full row is what lets the
   long "Instrumente für Klangerlebnisse" label survive; squeezed beside the
   logo and the CTA it did not fit and four items ran off the edge. */
/* The shell exists only to give the overlay masthead something to be
   positioned against. It starts below the holiday banner, so a banner pushes
   the masthead down instead of being painted over by it. */
.shell { position: relative; }

.head {
  background: var(--navy);
  color: var(--paper);
  padding-top: env(safe-area-inset-top);
  border-bottom: 1px solid rgba(201, 164, 76, 0.22);
}



/* One row when the three parts fit, two when they do not. The document order is
   identical either way — only `order` changes — so tab order never depends on
   the viewport. Below the breakpoint the navigation takes a full row of its own
   and scrolls; the long "Instrumente für Klangerlebnisse" label survives intact
   rather than being abbreviated. */
.head__inner {
  /* The masthead spans the window rather than sitting inside the content
     container. That is what the reference render shows — the wordmark about 2%
     from the left edge, the button the same from the right — and on a phone it
     is the difference between the identity and the button sharing a line and
     taking one each. Everything below the masthead still uses --wrap. */
  max-width: none;
  margin-inline: 0;
  padding: var(--s-4) var(--gutter);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-5);
}

/* `0 1 auto`, not `0 0 auto`: the logo must be allowed to shrink. Forbidding
   it meant the wordmark kept its intrinsic width at 200% text on a 375px
   phone and pushed the page 53px wide — a WCAG 2.2 SC 1.4.4 failure. */
.head__inner > .logo { order: 1; flex: 0 1 auto; min-width: 0; }
.head__inner > .cta--header { order: 2; flex: 0 0 auto; margin-inline-start: auto; }
.head__inner > .nav { order: 3; flex: 1 0 100%; min-width: 0; }

/* -- logo lockup: mark, name, owner, tagline -- */
.logo {
  display: flex;
  min-width: 0;
  align-items: center;
  gap: var(--s-4);
  min-height: 44px;
  text-decoration: none;
  color: inherit;
}

.logo__mark { flex: none; color: var(--gold); width: 46px; height: 46px; }

/* Same reasoning as the hero title: the lockup is a flex child holding one very
   long token. */
.logo__text { min-width: 0; }
.logo__name,
.logo__owner,
.logo__tagline { overflow-wrap: anywhere; hyphens: auto; }


.logo__name {
  display: block;
  font-family: var(--font-display);
  font-size: var(--t-logo);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.15;
}

.logo__owner {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(0.9375rem, 0.82rem + 0.45vw, 1.1875rem);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.2;
  color: var(--paper);
}

.logo__tagline {
  display: block;
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--t-sm);
  color: var(--gold-on-navy);
  line-height: 1.35;
  margin-top: 2px;
}

/* The masthead costs 218px on a 375px phone. The tagline is the one line that
   can go: it is decorative, and the hero repeats it verbatim a moment later.

   This block has to sit *after* the .logo__tagline rules it overrides. Media
   queries add no specificity, so placed before them its `display: none` lost to
   the later `display: block` and the tagline never disappeared — the same
   ordering mistake recorded in D62. */
@media (max-width: 30rem) {
  /* The tagline stays. It was hidden here to buy vertical space and the owner
     missed it immediately — it is part of the identity, not decoration, and it
     costs nothing horizontally because the wordmark above it is always the
     widest line. Only the padding is trimmed. */
  .head__inner { padding-block: var(--s-3); }
}

@media (min-width: 34rem) {
  .logo__mark { width: 58px; height: 58px; }
}


/* -- navigation --
   Its own full-width bar under the logo row, as the render has it. Always in
   the flow: no disclosure widget, so it works with JavaScript disabled without
   needing to (§13). On narrow screens it scrolls horizontally, with the edge
   fade as the affordance. */
.nav { position: relative; }

/* §13: a horizontally scrollable row must show that it scrolls. This fades the
   row's own content out at the right edge with a mask, rather than painting a
   gradient on top of it.

   The painted version ended in solid navy, which was fine while the masthead
   was a solid navy band — and became a dark rectangle hanging under the button
   the moment the masthead turned transparent over the photograph. A mask makes
   the content itself transparent, so it blends over whatever is behind, and it
   costs nothing when the row fits: there is simply nothing under the fade. */
.nav__list {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 3rem), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 3rem), transparent);
}

.nav__list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: var(--wrap);
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  padding-block: 3px;
  padding-inline: var(--gutter);
  -webkit-overflow-scrolling: touch;
}

.nav__list::-webkit-scrollbar { display: none; }

.nav__item { display: flex; align-items: center; flex: none; }

.nav__link {
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  padding: var(--s-2) var(--s-3);
  color: var(--paper);
  text-decoration: none;
  white-space: nowrap;
  font-size: var(--t-sm);
  border-radius: var(--radius);
}

.nav__link.is-current { color: var(--gold-on-navy); font-weight: 700; }

@media (hover: hover) {
  .nav__link:hover { background: rgba(255, 255, 255, 0.08); }
}

/* The gold dot separators from §6.

   On the preceding item, not the following one. As a ::before on the next item
   the dot became the first thing on a wrapped line — an orphaned dot leading
   the row on a landscape phone. Bound to the item before it, it wraps with the
   link it follows and can never start a line. */
.nav__item:not(:last-child)::after {
  content: "";
  flex: none;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--gold);
}

@media (min-width: 48rem) {
  /* Centred when the row fits, flush left when it does not.
     `justify-content: center` cannot do this: on an overflowing scroll
     container it pushes the first item off the left edge where no amount of
     scrolling reaches it. Auto margins split the free space when there is any
     and collapse to zero when there is none, which is the behaviour wanted at
     both ends and needs no feature query. */
  .nav__item:first-child { margin-inline-start: auto; }
  .nav__item:last-child { margin-inline-end: auto; }
  .nav__link { padding: var(--s-2) var(--s-4); }
}

/* On a wide monitor a fixed-size lockup and nav look lost against the width of
   the page, so both scale up here.

   This block MUST stay after the 48rem nav rules above: media queries do not
   add specificity, so with equal specificity the later rule wins. Placed before
   them, the 48rem block silently overrode this one and the nav stayed 17px at
   every width. */
@media (min-width: 100rem) {
  .logo__mark { width: 76px; height: 76px; }
  .head__inner { padding-block: var(--s-4); }
}

/* The masthead on one row: identity, navigation, action — the reference
   render's arrangement. The breakpoint is measured rather than chosen: with the
   nav on --t-sm the three parts plus their gaps need about 1300px, so below
   82rem the navigation drops to a row of its own instead of being squeezed. */
/* The masthead floats over the hero photograph from tablet width up — the
   reference render's look, and the part of it that matters most. It is
   deliberately NOT tied to the one-row layout below: the two are separate
   questions, and hanging the transparency on the one-row breakpoint would mean
   a 1440px laptop got a solid navy band for no reason.

   Phones keep the solid header. Two rows and ~200px of masthead floated over
   the hero would eat the photograph rather than sit on it. */
@media (min-width: 48rem) {
  .head--overlay {
    position: absolute;
    inset-inline: 0;
    top: 0;
    z-index: 5;
    background: linear-gradient(to bottom,
                rgba(4, 20, 36, 0.88) 0%,
                rgba(4, 20, 36, 0.62) 55%,
                rgba(4, 20, 36, 0) 100%);
    border-bottom: 0;
  }

  /* Clearance for the overlaid masthead, on the text layer only — .hero__media
     shares the same grid area, so putting it on .hero would push the photograph
     down as well and there would be nothing left to float over. Sized from the
     measured two-row header (165-190px) with headroom; audit-formats.py asserts
     the gap never closes. */
  .head--overlay + .main .hero__inner {
    padding-block-start: clamp(12rem, 8rem + 6vw, 15rem);
  }
}

/* One row: identity, navigation, action, as the render has it. The breakpoint
   is measured, not chosen. With seven items the three parts need about 1610px
   and the container offers 92vw, so they first fit at roughly 1760px; 112rem
   gives that a little headroom. Below it the navigation takes a row of its own
   rather than being squeezed or clipped. */
@media (min-width: 112rem) {
  .head__inner { flex-wrap: nowrap; }
  .head__inner > .nav { order: 2; flex: 1 1 auto; }
  .head__inner > .cta--header { order: 3; margin-inline-start: 0; }

  /* One row is a shorter masthead, so it needs less clearance. */
  .head--overlay + .main .hero__inner {
    padding-block-start: clamp(9rem, 5rem + 4vw, 12rem);
  }
}

/* ---- the masthead on a phone ----------------------------------------------
   The wordmark and "Termin vereinbaren" were taking a row each, which made the
   masthead ~190px tall before a visitor read anything. Getting them onto one
   line is only possible by giving back width, so this trims exactly the things
   that cost it and nothing that carries meaning: the masthead's own side
   padding (the page gutter is unchanged), the lotus mark, the wordmark's
   letter-spacing, and the button's type and padding.

   The label itself is untouched — §9 fixes one label site-wide, and a shorter
   one on phones would break that for a few pixels.

   Placed here, after every rule it overrides. Media queries add no specificity,
   so a block that must win has to be written last (D62). */
@media (max-width: 47.999rem) {
  .head__inner {
    padding-inline: var(--s-3);
    gap: var(--s-2) var(--s-2);
  }

  .logo { gap: var(--s-2); }
  .logo__mark { width: 32px; height: 32px; }

  /* 14px rather than the ~15.4px the token gives here, and much less tracking.
     Between them these are worth about 26px of width, which is most of what was
     keeping the button on a line of its own. Still in rem, so the browser's own
     font-size setting scales it (WCAG 2.2 SC 1.4.4). */
  /* Solved for 13px at 390 and 17px at 767, so it meets the token's own value
     at the breakpoint instead of jumping there. Still rem at both ends, so the
     browser's font-size setting scales it (WCAG 2.2 SC 1.4.4). */
  .logo__name {
    font-size: clamp(0.8125rem, 0.554rem + 1.061vw, 1.0625rem);
    letter-spacing: 0.02em;
  }

  .cta--header {
    font-size: var(--t-xs);
    padding: var(--s-2) var(--s-3);
    letter-spacing: 0.02em;
  }
}

/* ------------------------------------------------------- Impressionen ---
   A justified gallery: rows of photographs, each row filling the width, every
   picture keeping its own proportions. Not a grid of identical tiles — a
   quarter of these are portrait and cropping them all to one shape would throw
   away what makes them worth showing.

   How the row justification works, since it is not obvious:

     flex-basis  = ratio x row height   ->  a starting width proportional to shape
     flex-grow   = ratio                ->  slack shared in the same proportion

   Final width is therefore ratio x (row height + slack / sum of ratios), which
   is exactly proportional to the ratio, so every picture in a row ends up the
   same height. --ar comes from assets/css/gallery.css, generated from the real
   pixel dimensions; it cannot be an inline style because the CSP has no
   'unsafe-inline'.

   Rows are taller on a big screen and shorter on a phone, so the number of
   pictures per row stays sane rather than the pictures becoming stamps. */
.gallery {
  --gallery-row: clamp(7rem, 4rem + 11vw, 15rem);
  list-style: none;
  margin: var(--s-7) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}

.gallery__item {
  flex-grow: var(--ar, 1.3333);
  flex-shrink: 1;
  flex-basis: calc(var(--ar, 1.3333) * var(--gallery-row));
  min-width: 0;
  margin: 0;
}

.gallery__link {
  display: block;
  border-radius: var(--radius);
  overflow: hidden;
}

.gallery__item img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: var(--ar, 1.3333);
  object-fit: cover;
  background: var(--rule);
}

@media (hover: hover) {
  .gallery__link:hover img { opacity: 0.88; }
}

/* The last row would otherwise stretch its one or two pictures across the whole
   width. This absorbs the slack instead, and is empty and aria-hidden so it is
   a layout device only. */
.gallery__fill {
  flex-grow: 999999;
  flex-basis: 0;
  min-width: 0;
  height: 0;
  margin: 0;
}

/* A short in-page strip uses a shorter row, so its handful of photographs land
   on one line instead of leaving the last one stranded and undersized beside the
   filler. The full gallery keeps the taller row. */
.gallery--strip { --gallery-row: clamp(6rem, 3rem + 8vw, 11rem); margin-top: var(--s-7); }

/* A single column of full-width photographs on the narrowest screens reads far
   better than four stamps per row. */
@media (max-width: 26rem) {
  .gallery { --gallery-row: 60vw; }
}

/* WCAG 2.2 SC 2.4.11, Focus Not Obscured. The open panel covers the viewport,
   but on its own it does not stop the 59 focusable things behind it from taking
   focus: four Tab presses from an open photograph landed on a footer link that
   was completely hidden underneath. `visibility: hidden` both hides them and
   takes them out of the tab order, which is exactly what is wanted, and it is
   the only way to do it without JavaScript since `inert` is an HTML attribute.

   Wrapped in @supports because :has() is what makes it possible. Where it is
   unsupported the page behaves as it did before — no worse, just not better. */
@supports selector(:has(*)) {
  body:has(.lightbox:target) .skip,
  body:has(.lightbox:target) .banner :is(a, button),
  body:has(.lightbox:target) .head :is(a, button),
  body:has(.lightbox:target) .foot :is(a, button),
  .page:has(.lightbox:target) > *:not(.lightbox) :is(a, button) {
    visibility: hidden;
  }
}

/* ---- the full-screen view -------------------------------------------------
   A :target panel, so it needs no JavaScript: following the fragment opens it,
   the back button closes it, and previous/next are ordinary links that wrap
   around at both ends. With scripting off it behaves identically, which is the
   whole point (§13).

   `display: none` until targeted also means the browser never fetches the large
   variants for the 41 photographs nobody has opened. */
.lightbox {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 100;
  /* Deliberately well short of opaque: the page stays visible behind the
     photograph, which the owner preferred to a solid blackout. Taken from 0.97
     to 0.87 on their request. */
  background: rgba(4, 20, 36, 0.87);
  padding:
    calc(var(--s-5) + env(safe-area-inset-top))
    calc(var(--s-5) + env(safe-area-inset-right))
    calc(var(--s-5) + env(safe-area-inset-bottom))
    calc(var(--s-5) + env(safe-area-inset-left));
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--s-4);
}

.lightbox:target { display: grid; }

.lightbox__stage {
  grid-column: 2;
  display: grid;
  place-items: center;
  min-width: 0;
  min-height: 0;
  height: 100%;
}

/* The photograph arrives as a background image, generated per id into
   assets/css/gallery.css. It is not an <img> because `loading="lazy"` is inert
   when scripting is disabled, so all 42 hidden panels fetched their full-size
   file: measured 84 requests and 6.7 MB with JS off against 42 and 0.8 MB with
   it. A background on an element that is not rendered is never fetched, so
   exactly one large image loads — the one actually opened.

   `contain` means a portrait and a landscape shot both sit whole on the screen;
   nothing is ever cropped here. The element carries role="img" and the
   photograph's description, so it is not invisible to a screen reader. */
.lightbox__stage {
  width: 100%;
  height: 100%;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}

.lightbox__close,
.lightbox__nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 56px;
  min-height: 56px;
  border-radius: 50%;
  color: var(--paper);
  text-decoration: none;
  font-size: 2rem;
  line-height: 1;
  background: rgba(255, 255, 255, 0.10);
  border: 1px solid rgba(255, 255, 255, 0.22);
}

@media (hover: hover) {
  .lightbox__close:hover,
  .lightbox__nav:hover { background: rgba(255, 255, 255, 0.20); }
}

.lightbox__nav--prev { grid-column: 1; }
.lightbox__nav--next { grid-column: 3; }

.lightbox__close {
  position: absolute;
  top: var(--s-4);
  right: var(--s-4);
  z-index: 1;
}

/* On a phone the two arrows would leave the picture a sliver of the width, so
   they move to the bottom and the photograph gets the whole screen. */
@media (max-width: 40rem) {
  .lightbox {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr auto;
    padding: var(--s-3);
  }
  .lightbox__stage { grid-column: 1 / -1; grid-row: 1; }
  .lightbox__nav--prev { grid-column: 1; grid-row: 2; justify-self: start; }
  .lightbox__nav--next { grid-column: 2; grid-row: 2; justify-self: end; }
}

/* -- footer -- */
.foot {
  background: var(--navy-deep);
  color: var(--paper-soft);
  padding-block: var(--section-y);
  padding-bottom: max(var(--section-y), env(safe-area-inset-bottom));
  font-size: var(--t-sm);
}

.foot__inner {
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: grid;
  gap: var(--s-6);
}

/* §13: phone and email first on mobile, in thumb reach. */
.foot__contact { order: -1; }

@media (min-width: 56rem) {
  .foot__inner { grid-template-columns: 1.4fr 1fr 1fr 1fr; }
  .foot__contact { order: 0; }
  .foot__legal { grid-column: 1 / -1; }
}

.foot__heading {
  font-family: var(--font-body);
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: var(--ls-caps);
  text-transform: uppercase;
  color: var(--gold-on-navy);
  margin: 0 0 var(--s-3);
}

.foot__name { font-family: var(--font-display); font-size: var(--t-lead); color: var(--paper); margin: 0; }
.foot__tagline { font-family: var(--font-display); font-style: italic; color: var(--gold-on-navy); margin: var(--s-1) 0 0; }
.foot__list { list-style: none; margin: 0; padding: 0; }

.foot a { color: var(--paper); }
.foot__list a { display: inline-flex; align-items: center; min-height: 44px; font-weight: 600; }

.foot__banner {
  margin-top: var(--s-3);
  padding: var(--s-2) var(--s-3);
  border-left: 3px solid var(--gold);
  color: var(--paper);
  font-weight: 600;
}

.foot__legal ul {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  margin: 0;
  padding: 0;
}

/* §13 again: these were 21px tall, well under the 44px floor. */
.foot__legal a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* ------------------------------------------------------------- page body */
/* Every page template except the homepage roots itself in `.page`, so this is
   the container for all of them. It was missing entirely — the templates were
   still the phase-2 "semantic structure only" stubs — which is why every
   sub-page sat flush against the left edge of the window with no gutter.

   Deliberately the same `--wrap` the header, the nav and the homepage sections
   use, so a sub-page's left edge lines up with the masthead above it instead of
   starting somewhere of its own. Line length is governed inside, by --measure,
   not by narrowing the container. */
.page {
  padding-block: var(--section-y);
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.page__h1 { margin-bottom: var(--s-3); max-width: var(--column); }

/* Why not one column for both.

   `--measure` is 68ch, and `ch` is relative to the element's *own* font size —
   so the same token gave a 36px heading a 1245px box and a 20px paragraph a
   659px one. The heading stuck out nearly twice as far as the text under it,
   which is what made these pages look ragged down the right.

   Both tokens below are absolute, so nothing depends on the element's own size
   any more. They are two values rather than one because a heading and a
   paragraph do not want the same width: the heading wants the section's edge,
   and the paragraph wants a line it can be read along. Forcing them to agree
   means either short headings or 140-character prose. */
/* Headings take the wide column, prose the narrow one. */
.page__sub,
.sec__heading { max-width: var(--column); }

.page__intro > p,
.page > p,
.sec > p { max-width: var(--copy); }

/* Body copy inside a section was set at --t-base in a --copy-wide box, which
   measured 105-111 characters per line. The box is not the problem -- the size
   is. At the lead size the same box reads at about 86, and the page carries
   more presence than a longer line would have given it. */
.sec > p,
.page__intro > p,
.page > p {
  font-size: var(--t-lead);
  /* Extra leading, because these lines are long. Sharing one edge with the
     picture grid means a measure of roughly 95-110 characters, and the cost of
     a long line is losing your place on the way back to the left margin --
     which is what leading buys back. 1.65 is right for 70 characters; this is
     not 70 characters. */
  line-height: 1.75;
}

.page__sub {
  font-family: var(--font-display);
  font-size: var(--t-h3);
  font-weight: 400;
  color: var(--gold-on-cream);
  margin: 0 0 var(--s-5);
  max-width: var(--column);
}

.page__intro { font-size: var(--t-lead); }

/* The same edge as the text above it. It used to be 80rem while the text was
   60rem, and the picture grid then stuck out a fifth of the page further than
   the prose beside it -- the ragged right edge. Meeting at 68rem costs the
   cards about 90px each and buys one edge for the whole page. */
.sec { margin-top: var(--s-7); max-width: var(--column); }
.sec__heading { margin-bottom: var(--s-4); }
/* An item list becomes columns once there is room for them. Down the left of a
   3440px monitor a seven-item list is a thin ribbon with two thirds of the
   window empty beside it; the cap keeps it to at most three columns so the
   lines never get longer than they read well at. */
.sec__items {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-5) var(--s-7);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr));
}

.sec__item { max-width: var(--measure); }

/* A catalogue item's photograph: the full width of the item's own column, so
   it lines up with the text it belongs to, and one shared shape so a row of
   items reads as a row rather than as ragged tiles. Clicking it opens the same
   full-screen viewer the gallery uses. */
.sec__item-media {
  display: block;
  width: 100%;
  margin-bottom: var(--s-3);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--rule);
}

.sec__item-media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

@media (hover: hover) {
  .sec__item-media:hover img { opacity: 0.9; }
}

.sec__item-label {
  display: block;
  font-weight: 700;
  color: var(--navy);
  font-size: var(--t-base);
  margin-bottom: var(--s-1);
}

.sec__item-text { display: block; color: var(--ink-soft); font-size: var(--t-base); }

/* Faint lotus watermark in the lower right of long text sections (§6).
   `overflow: hidden` is load-bearing, not tidiness: the mark is deliberately
   bled 2rem past the right edge, and without clipping that 32px became real
   horizontal page scroll — which §18 forbids at any width. */
.watermark {
  position: relative;
  overflow: hidden;
}
.watermark::after {
  content: "";
  position: absolute;
  right: -2rem;
  bottom: -1rem;
  width: 14rem;
  height: 14rem;
  background: url("/assets/img/lotus.svg") no-repeat center / contain;
  opacity: 0.05;
  pointer-events: none;
}

@media (max-width: 48rem) { .watermark::after { display: none; } }

/* ---------------------------------------------------------------- states */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Not a dark theme — just don't look broken when the OS is dark (§13). */
@media (prefers-color-scheme: dark) {
  body { background: var(--cream); color: var(--ink); }
}

/* ----------------------------------------------------------------- print
   People print the address and the appointment details (§13). */
@media print {
  .banner, .skip, .nav, .cta, .watermark::after { display: none !important; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .head, .foot, .section--navy { background: #fff !important; color: #000 !important; }
  .head *, .foot *, .section--navy * { color: #000 !important; }
  a { color: #000; text-decoration: underline; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; }
  a[href^="tel:"]::after, a[href^="mailto:"]::after { content: ""; }
  .card { border: 1px solid #000; box-shadow: none; }
  .page, .section { padding-block: 0.5cm; }
}

/* ============================================================================
   HOMEPAGE
   Written mobile-first: every grid starts narrow and widens.
   ========================================================================= */

/* ------------------------------------------------------------------ hero --
   Matches `oberer teil verbessert 7.8.26.png`: text left-aligned over the
   photograph, the brand and the owner at display size on two lines, the gold
   tagline beneath at roughly half, then the supporting lines, the appointment
   note with the lotus mark, and the gold button.

   Height uses svh, never vh: on iOS Safari and Android Chrome the viewport
   height changes as the toolbar hides, and a vh-sized hero jumps at exactly the
   moment the visitor starts scrolling (§13).                                */
.hero {
  position: relative;
  isolation: isolate;
  display: grid;
  background: var(--navy);
}

.hero__media,
.hero__inner { grid-area: 1 / 1; }

.hero__media { position: relative; height: 100%; overflow: hidden; }

/* .hero__img sizing (aspect-ratio, object-position) is generated into
   photo.css from content/image-slots.json — see tools/build-photo-css.py.
   It is not repeated here, so the crop and the box can never disagree. */

/* The scrim is weighted to the left, where the text sits, and fades out over
   the bowls on the right so the photograph still reads. §13: light text over a
   bright photograph is much harder to read on a phone in daylight than on a
   monitor, so the mobile gradient is heavier. */
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgba(8, 35, 58, 0.80) 0%, rgba(8, 35, 58, 0.60) 60%, rgba(8, 35, 58, 0.80) 100%);
}

@media (min-width: 48rem) {
  .hero__media::after {
    background:
      linear-gradient(to right, rgba(8, 35, 58, 0.88) 0%, rgba(8, 35, 58, 0.72) 42%, rgba(8, 35, 58, 0.30) 72%, rgba(8, 35, 58, 0.18) 100%);
  }
}

.hero__inner {
  position: relative;
  z-index: 1;
  align-self: center;
  width: 100%;
  padding-block: clamp(2.5rem, 8vw, 5.5rem);
  padding-top: max(clamp(2.5rem, 8vw, 5.5rem), env(safe-area-inset-top));
}

.hero__title {
  margin: 0 0 var(--s-4);
  display: grid;
  gap: 0;
  min-width: 0;
}

/* "Klangschalenwelt.de" is one unbreakable token. A grid or flex child defaults
   to min-width: auto, which resolves to min-content — the width of that single
   longest word — so the item refuses to shrink below it and pushes the page
   sideways. At the browser's largest text setting on a 375px phone that word is
   596px wide, which was a WCAG 2.2 SC 1.4.4 failure: text scaled, but content
   was lost off the side.
   min-width: 0 lets the item shrink; overflow-wrap: anywhere lets the word
   break rather than overflow. */
.hero__title > span {
  min-width: 0;
  overflow-wrap: anywhere;
  hyphens: auto;
}

/* Brand and owner carry the display size, on two lines as in the render. */
.hero__brand,
.hero__owner {
  display: block;
  color: var(--paper);
  font-size: var(--t-hero);
  font-weight: 500;
  line-height: 1.04;
  letter-spacing: -0.01em;
  text-shadow: 0 2px 28px rgba(4, 24, 43, 0.55);
}

/* The tagline supports them: gold, and about a third of the size. At display
   size it competed with the name instead of supporting it. */
.hero__tagline {
  display: block;
  margin-top: var(--s-3);
  color: var(--gold-on-navy);
  font-size: clamp(1.25rem, 0.95rem + 1.5vw, 2.125rem);
  font-weight: 600;
  line-height: 1.2;
  text-shadow: 0 2px 22px rgba(4, 24, 43, 0.6);
}

.hero__sub {
  color: var(--paper-soft);
  font-family: var(--font-body);
  font-size: var(--t-lead);
  font-weight: 400;
  line-height: 1.45;
  margin: 0 0 var(--s-4);
  max-width: 44ch;
  text-shadow: 0 1px 18px rgba(4, 24, 43, 0.6);
}

.hero__support {
  color: var(--paper);
  font-size: clamp(1.0625rem, 0.95rem + 0.6vw, 1.375rem);
  line-height: 1.5;
  margin: 0 0 var(--s-4);
  max-width: none;
  text-shadow: 0 1px 18px rgba(4, 24, 43, 0.65);
}

.hero__note {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  color: var(--paper);
  font-size: var(--t-base);
  margin: 0 0 var(--s-5);
  max-width: none;
  text-shadow: 0 1px 16px rgba(4, 24, 43, 0.7);
}

.hero__note-mark {
  flex: none;
  color: var(--gold);
  /* Scales with the line it sits beside rather than staying at one pixel size,
     which left it looking like a speck once the note grew. */
  width: clamp(32px, 1.6rem + 1.1vw, 44px);
  height: clamp(32px, 1.6rem + 1.1vw, 44px);
}

.hero__note-mark svg { width: 100%; height: 100%; }

.hero__cta { font-size: var(--t-base); padding: var(--s-4) var(--s-6); }

/* Height is capped in svh so a short window (a laptop with a bookmarks bar, say)
   does not get a hero that fills the whole screen and pushes the headline to the
   bottom edge. svh rather than vh so it does not jump when a mobile toolbar
   retracts (§13). */
/* The hero box follows the art-directed crop's own aspect ratio, so on most
   windows nothing is cropped at all. The cap stops it swallowing a short
   window — a laptop with a bookmarks bar — where the remaining crop is
   anchored on the focal point rather than the centre. */
.hero { min-height: clamp(20rem, 78svh, 46rem); }

@media (min-width: 48rem) {
  .hero { min-height: clamp(22rem, 58svh, 40rem); }
}

@media (min-width: 100rem) {
  .hero { min-height: clamp(26rem, 62svh, 44rem); }
  /* The headline should not run the whole width of a very wide monitor. */
  .hero__title, .hero__sub, .hero__support { max-width: 30ch; }
  .hero__sub { max-width: 44ch; }
}

/* ------------------------------------------- Klang muss man erleben ------ */
.erleben__heading { text-align: center; }
.erleben__col p:last-child { margin-bottom: 0; }

@media (min-width: 56rem) {
  /* Two columns of prose, per §6. The gap is generous because two columns of
     German text set close together are hard to track between. */
  .grid--prose { gap: 0 var(--s-8); }
}

/* --------------------------------------------------- Warum persönlich? ---
   Matches `oberer teil verbessert 7.8.26.png`: the heading sits on one line
   with a thin gold rule running out to either side, and the five items spread
   across the full width beneath it with large gold line-art icons.

   The first attempt set the heading small with a separate divider below it and
   44px icons that read as tiny at this scale. Both are corrected here.       */
.warum__head {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  margin-bottom: clamp(2rem, 5vw, 3.5rem);
}

.warum__head::before,
.warum__head::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--gold);
  opacity: 0.55;
}

.warum__eyebrow {
  margin: 0;
  flex: none;
  color: var(--gold-on-navy);
  font-family: var(--font-body);
  font-size: var(--t-eyebrow);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  /* nowrap keeps the heading on one line between its two gold rules, which is
     the point of the treatment. But at the browser's largest text setting
     "Warum persönlich?" is 376px on a 375px phone, and nowrap turned that into
     horizontal page scroll. Wrapping is allowed once the line genuinely cannot
     fit — the rules simply sit above and below instead. */
  white-space: nowrap;
}

@media (max-width: 34rem) {
  .warum__head { flex-wrap: wrap; }
  .warum__eyebrow { white-space: normal; text-align: center; flex: 1 1 100%; }
}

.warum__list { list-style: none; margin: 0; padding: 0; align-items: start; }

.reason { text-align: center; }

.reason__icon {
  /* Sized off the same 320->1600 endpoints as the type scale so they stay in
     proportion at every width instead of being pinned to one number. Weight is
     a property of the drawing, not of this box: the SVGs carry a 1.7 stroke on
     a 48-unit viewBox, which stays fine at every size here. */
  width: var(--icon);
  height: var(--icon);
  margin: 0 auto var(--s-4);
  color: var(--gold);
}

.reason__icon svg { width: 100%; height: 100%; }

.reason__label {
  font-family: var(--font-body);
  font-size: clamp(0.75rem, 0.7rem + 0.22vw, 0.875rem);
  font-weight: 700;
  /* Tighter than the other caps labels so "INDIVIDUELL & KOMPETENT" fits one
     line, as it does in the render. Wrapping made the five items ragged: the
     body text started at a different height under each. */
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gold-on-navy);
  margin: 0 0 var(--s-2);
  /* Two lines' worth, so the body text lines up across all five even if one
     label does wrap on a narrow screen. */
  min-height: 2.6em;
  display: flex;
  align-items: center;
  justify-content: center;
}

.reason__text {
  font-size: var(--t-sm);
  line-height: 1.5;
  color: var(--paper-soft);
  margin: 0 auto;
  max-width: 22ch;
}

/* -------------------------------------------------------- Unser Showroom - */
.showroom__inner { display: grid; gap: var(--s-6); align-items: center; }
.showroom__img { width: 100%; border-radius: var(--radius); }
.showroom__body p:last-of-type { margin-bottom: var(--s-5); }

@media (min-width: 56rem) {
  .showroom__inner { grid-template-columns: 1.1fr 1fr; gap: var(--s-8); }
}

/* -------------------------------------------------------- Unser Sortiment  */
.sortiment__heading { text-align: center; }
.sortiment__list { list-style: none; margin: 0; padding: 0; }

.card--link {
  height: 100%;
  text-decoration: none;
  color: inherit;
  transition: transform 160ms ease, box-shadow 160ms ease;
}

@media (hover: hover) {
  .card--link:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-card);
  }
  .card--link:hover .card__title { color: var(--gold-on-cream); }
}

.card--link .card__body { display: grid; gap: var(--s-2); }
.card--link .card__title { display: block; }
.card--link .card__text { display: block; }

/* ------------------------------------------ Für professionelle Anwendung - */
.pro__heading { text-align: center; }
.pro__card { height: 100%; }
.pro__body { display: grid; gap: var(--s-3); align-content: start; }

.pro__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-1);
  font-size: var(--t-sm);
  color: var(--ink-soft);
}

.pro__list li { display: flex; gap: var(--s-2); align-items: baseline; }

.pro__list li::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  margin-top: 0.45em;
  border-radius: 50%;
  background: var(--gold);
}

.pro__body .cta { justify-self: start; margin-top: var(--s-2); }

/* Two actions side by side, deliberately unequal.
   "Beratung anfragen" is the one we want taken, so it keeps the pill. "Weitere
   Informationen" only leads to another page of this same site, and a second
   pill would make the pair look like a choice between equals. A text link with
   a chevron reads as "there is more to read", which is what it is.
   Wraps to two rows below ~28rem of card, so nothing overflows on a phone. */
.pro__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3) var(--s-4);
  margin-top: var(--s-2);
}

.pro__actions .cta { margin-top: 0; }

.cta--more {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  /* 44px is the WCAG 2.2 SC 2.5.8 floor for a target, and a bare text link
     misses it by a long way. The height is bought with padding rather than a
     border, so it stays a link to look at. */
  min-height: 44px;
  padding-block: var(--s-2);
  color: var(--gold-on-cream);
  font-family: var(--font-body);
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.28em;
  text-decoration-color: rgba(139, 105, 20, 0.45);
  transition: text-decoration-color 160ms ease, color 160ms ease;
}

/* The chevron is a pseudo-element: decorative, and a screen reader announcing
   "greater-than sign" after every link would be noise. */
.cta--more::after {
  content: "";
  width: 0.42em;
  height: 0.42em;
  border-top: 2px solid currentColor;
  border-right: 2px solid currentColor;
  transform: rotate(45deg);
  transform-origin: 60% 40%;
  transition: transform 160ms ease;
}

.section--navy .cta--more { color: var(--gold-on-navy); }
.section--navy .cta--more { text-decoration-color: rgba(201, 164, 76, 0.5); }

@media (hover: hover) {
  .cta--more:hover { text-decoration-color: currentColor; }
  .cta--more:hover::after { transform: rotate(45deg) translate(2px, -2px); }
}

@media (prefers-reduced-motion: reduce) {
  .cta--more, .cta--more::after { transition: none; }
  .cta--more:hover::after { transform: rotate(45deg); }
}
