/* ==========================================================================
   Flex Massage — Tomasz Garbacz, Frauenfeld
   One stylesheet for both language documents.
   Mobile-first: base rules describe the 360px layout, every query is min-width.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts
   -------------------------------------------------------------------------- */

/* The practice asked for the typography of the old site: Arial bold headings
   over Arial body copy, one family for everything. Arial ships with every
   Windows, macOS, iOS and Android install, so there is no @font-face here and
   nothing to preload — the page paints in its final typeface on the first
   frame. Helvetica covers the older Apple systems, Liberation Sans the Linux
   ones; both are metric-compatible with Arial, so the layout does not shift
   whichever one answers. */

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* raw ramp — never referenced outside this block */
  --cream-50: #FFFFFF;
  --cream-100: #F5F5F5;
  --beige-200: #ECECEC;
  --beige-300: #D4D4D4;
  --sage-300: #FFD7B8;
  --sage-500: #F36B00;
  --sage-600: #C84F00;
  --sage-800: #202020;
  --brown-700: #262626;
  --brown-500: #5E5E5E;
  --gold-400: #F36B00;

  /* semantic — the only colours components are allowed to use.
     --color-brand resolves to sage-600, not sage-500: cream on sage-500 measures
     3.49:1 and fails AA for button labels. sage-600 measures 4.90:1. */
  --color-bg: var(--cream-50);
  --color-bg-alt: var(--cream-100);
  --color-bg-dark: var(--sage-800);
  --color-surface: var(--beige-200);
  --color-border: var(--beige-300);
  --color-text: var(--brown-700);
  --color-text-muted: var(--brown-500);
  --color-brand: var(--sage-600);
  --color-brand-hover: var(--sage-800);
  --color-brand-tint: var(--sage-300);
  --color-accent: var(--gold-400);
  --color-focus-ring: var(--brown-700);
  --color-on-brand: var(--cream-50);
  --color-on-dark: var(--cream-50);
  --color-on-dark-muted: var(--beige-300);
  --color-error: #8C3A2B;

  /* type — Major Third (1.250) off a 16px base.
     One family throughout; the heading/body split now carries weight and size
     only. Arial has a tall x-height and reads a step larger than the serif it
     replaced, so the two clamped steps were pulled back to keep the display
     line from overrunning its column. clamp() only on those two.
     --ls-heading is negative: Arial at 40px+ tracks loose by default, and bold
     display type wants the counters closed up. */
  --font-heading: Arial, 'Helvetica Neue', Helvetica, 'Liberation Sans', sans-serif;
  --font-body: Arial, 'Helvetica Neue', Helvetica, 'Liberation Sans', sans-serif;
  --fs-sm: 0.8rem;
  --fs-base: 1rem;
  --fs-md: 1.25rem;
  --fs-lg: 1.5625rem;
  --fs-xl: clamp(1.75rem, 1.65rem + 0.45vw, 1.953rem);
  --fs-2xl: clamp(2rem, 1.65rem + 1.5vw, 2.441rem);
  --fs-3xl: clamp(1.953rem, 1.55rem + 1.8vw, 2.441rem);
  --fs-display: clamp(2.1rem, 1.35rem + 3.3vw, 3.052rem);
  --lh-heading: 1.2;
  --lh-body: 1.65;
  --ls-heading: -0.015em;
  --ls-eyebrow: 0.16em;
  --measure: 65ch;

  /* spacing — 8px grid, with one 4px sub-step for hairline gaps */
  --space-xs: 0.25rem;
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.5rem;
  --space-4: 2rem;
  --space-5: 2.5rem;
  --space-6: 3rem;
  --space-7: 4rem;
  --space-8: 5rem;
  --space-9: 7.5rem;
  --section-padding: clamp(3.5rem, 8vw, var(--space-8));
  --gutter: clamp(var(--space-2), 4vw, var(--space-3));
  --content-max: 72rem;

  /* shape */
  --radius-sm: 4px;
  --radius-lg: 20px;
  --radius-pill: 999px;
  --hairline: 1px;

  /* motion — a strong ease-out; the built-in curves are too weak to read as
     intentional. Never ease-in: it delays the first frame, the one the eye
     is watching. */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --dur-press: 160ms;
  --dur-hover: 200ms;
  --dur-disclosure: 220ms;
  --dur-bar: 260ms;
  --dur-reveal: 600ms;

  /* layout constants shared with JS-free sticky offsets.
     --header-height is the logo row. Between 700px and 1280px the page links
     add a second row underneath it; on a phone they are in a panel that
     overlays rather than pushes, and above 1280px they share the logo's row —
     so in both of those cases the header is one row again.
     Anything that has to clear the whole sticky header — scroll targets, the
     hero's negative margin, the sticky portrait — measures against
     --header-offset, never against --header-height. The two media blocks in
     section 6 are the only places it changes. */
  --header-height: 4rem;
  --nav-height: 2.75rem;
  --header-offset: var(--header-height);
  --bar-height: 4.5rem;
}

/* --------------------------------------------------------------------------
   3. Base
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-offset) + var(--space-2));
}

body {
  margin: 0;
  background-color: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  font-weight: 400;
  line-height: var(--lh-body);
  overflow-x: hidden;
  padding-bottom: calc(var(--bar-height) + env(safe-area-inset-bottom));
  /* German compounds are longer than the columns this layout offers —
     "Fussreflexzonenmassage" is twenty-two characters and does not fit a
     service card at any width, "Datenschutzerklärung" does not fit a phone.
     Inherited, so it holds for every word on the page, and it only ever acts
     on a word that would otherwise leave its box. */
  overflow-wrap: break-word;
}

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: 700;
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-heading);
  text-wrap: balance;
  /* Headings are where the long compounds actually are, and they are set large
     enough that a break mid-syllable reads as a mistake. Hyphenation breaks
     them where German breaks them; the rule above catches the words no
     dictionary knows. */
  hyphens: auto;
}

p, ul, ol, dl, figure, blockquote {
  margin: 0;
}

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

a {
  color: var(--color-brand-hover);
  text-decoration-thickness: var(--hairline);
  text-underline-offset: 0.2em;
}

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

::selection {
  background-color: var(--color-surface);
  color: var(--color-text);
}

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

/* Components set display, and a plain `display: none` from the user-agent
   sheet loses to a class. Without this a hidden form still shows. */
[hidden] {
  display: none !important;
}

/* The icon sprite. Out of flow so it occupies nothing, and a class rather than
   a style attribute so that style-src can stay free of 'unsafe-inline'. */
.sprite {
  position: absolute;
  width: 0;
  height: 0;
}

/* Bait. Left in the flow but off-screen: a form filler that walks the DOM
   fills it in, a person never sees it. Not `display: none`, which the better
   bots already know to skip. */
.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.skip-link {
  position: absolute;
  top: var(--space-1);
  left: var(--space-1);
  z-index: 60;
  padding: var(--space-1) var(--space-2);
  background-color: var(--color-bg);
  border: var(--hairline) solid var(--color-text);
  border-radius: var(--radius-sm);
  transform: translateY(-200%);
}

.skip-link:focus-visible {
  transform: translateY(0);
}

/* --------------------------------------------------------------------------
   4. Layout primitives
   -------------------------------------------------------------------------- */

.container {
  width: 100%;
  max-width: var(--content-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section {
  padding-block: var(--section-padding);
}

.section--alt {
  background-color: var(--color-bg-alt);
}

.section--dark {
  background-color: var(--color-bg-dark);
  color: var(--color-on-dark);
  --color-focus-ring: var(--beige-200);
}

.section-head {
  max-width: var(--measure);
  margin-bottom: var(--space-5);
}

.eyebrow {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.section--dark .eyebrow {
  color: var(--color-on-dark-muted);
}

.section-title {
  font-size: var(--fs-2xl);
}

.section-intro {
  max-width: var(--measure);
  margin-top: var(--space-2);
  color: var(--color-text-muted);
}

.section--dark .section-intro {
  color: var(--color-on-dark-muted);
}

.prose p + p {
  margin-top: var(--space-2);
}

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

/* A gold hairline is the only ornament on the page. Gold measures 2.31:1 on
   cream — it can never carry text, only a 1px rule. */
.rule-accent {
  width: var(--space-6);
  height: var(--hairline);
  border: 0;
  margin: 0 0 var(--space-3);
  background-color: var(--color-accent);
}

/* --------------------------------------------------------------------------
   5. Buttons and links
   -------------------------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: 3rem;
  padding: var(--space-1) var(--space-4);
  border: var(--hairline) solid transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-hover) ease,
              border-color var(--dur-hover) ease,
              color var(--dur-hover) ease,
              transform var(--dur-press) var(--ease-out);
}

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

.button--secondary {
  background-color: transparent;
  border-color: var(--color-text);
  color: var(--color-text);
}

/* Hover is gated: touch devices fire it on tap and leave the state stuck. */
@media (hover: hover) and (pointer: fine) {
  .button--primary:hover {
    background-color: var(--color-brand-hover);
  }

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

/* Feedback on press, not on release — the interface has to answer immediately. */
.button:active {
  transform: scale(0.97);
}

.button[disabled] {
  cursor: not-allowed;
  opacity: 0.6;
}

.button[disabled]:active {
  transform: none;
}

.link-plain {
  color: inherit;
  text-decoration: none;
}

.icon {
  width: 1.5rem;
  height: 1.5rem;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --------------------------------------------------------------------------
   6. Header
   -------------------------------------------------------------------------- */

.header {
  position: sticky;
  top: 0;
  z-index: 40;
  min-height: var(--header-height);
  display: flex;
  align-items: center;
  border-bottom: var(--hairline) solid transparent;
  /* Cream from the first pixel, not only once scrolled: the header floats over
     the hero photograph, and the logo is artwork whose legibility cannot be
     left to whatever happens to be behind it. */
  background-color: color-mix(in srgb, var(--color-bg-dark) 96%, transparent);
  backdrop-filter: blur(10px);
  transition: background-color var(--dur-hover) ease,
              border-color var(--dur-hover) ease;
}

.header[data-scrolled] {
  background-color: var(--color-bg-dark);
  border-bottom-color: color-mix(in srgb, var(--color-on-dark) 18%, transparent);
}

@media (prefers-reduced-transparency: reduce) {
  .header {
    background-color: var(--color-bg-dark);
    backdrop-filter: none;
  }
}

/* The reference header spans the viewport rather than the article column. */
.header__inner {
  max-width: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  column-gap: var(--space-2);
  row-gap: 0;
  width: 100%;
  padding-inline: clamp(var(--space-2), 2.5vw, var(--space-5));
}

.header__logo img {
  width: clamp(8rem, 38vw, 9.75rem);
  height: auto;
}

.header__logo,
.header__actions {
  display: flex;
  align-items: center;
  min-height: var(--header-height);
}

.header__actions {
  gap: clamp(var(--space-1), 3vw, var(--space-3));
}

.header .button {
  display: none;
}

/* The logo and the gaps yield fluidly on phones so the logo, language switch
   and toggle stay on the header's single 64px row down to 320px. */
/* Navigation — two states, and the reason for each.
   On a phone the links stay behind the toggle, in a panel under the header.
   Seven labels laid out flat would take a row a phone cannot spare above the
   fold, and a panel one tap away is the established way to spend that space.
   From 900px up they share one permanent row with the logo, like the reference. */
/* Anchored to the header's real bottom edge, not to --header-height.
   The two are usually the same, but the header's top row wraps when the logo,
   the language switch and the toggle stop fitting side by side — a narrow
   phone, or a reader who has turned their text size up — and then the header
   is two rows tall while the constant still says one. A panel positioned by
   the constant opens across the language switch. `top: 100%` cannot be wrong:
   .header is sticky, so it is the containing block, and 100% of it is wherever
   it actually ends. Being its child also puts the panel inside the header's
   stacking context, so no z-index of its own is needed. */
.nav {
  position: absolute;
  inset-inline: 0;
  top: 100%;
  display: none;
  max-height: 70dvh;
  overflow-y: auto;
  padding: var(--space-1) var(--gutter) var(--space-3);
  background-color: var(--color-bg-dark);
  border-bottom: var(--hairline) solid color-mix(in srgb, var(--color-on-dark) 18%, transparent);

  /* The panel drops out of the header rather than appearing on top of it: a
     6px fall and a fade, over --dur-disclosure, the same timing every other
     disclosure on this page uses. Short enough that a second tap is never
     waiting on it.
     `display` is in the transition list because the panel is display:none when
     closed, and a discrete property would otherwise flip on frame one and skip
     the exit animation entirely. allow-discrete holds it at block until the
     fade has finished. */
  opacity: 0;
  translate: 0 -0.375rem;
  transition: opacity var(--dur-disclosure) var(--ease-out),
              translate var(--dur-disclosure) var(--ease-out),
              display var(--dur-disclosure) allow-discrete;
}

.nav[data-open] {
  display: block;
  opacity: 1;
  translate: 0 0;
}

/* The entry values. Without this the panel is already at its open state on the
   frame it first gets a box, and there is nothing to transition from — the
   opening would snap while the closing animated. */
@starting-style {
  .nav[data-open] {
    opacity: 0;
    translate: 0 -0.375rem;
  }
}

.nav__list {
  list-style: none;
  padding: 0;
  margin-inline: auto;
  max-width: var(--content-max);
}

.nav__list a {
  display: flex;
  align-items: center;
  min-height: 3rem;
  border-bottom: var(--hairline) solid color-mix(in srgb, var(--color-on-dark) 18%, transparent);
  color: var(--color-on-dark);
  font-size: var(--fs-md);
  font-family: var(--font-heading);
  letter-spacing: var(--ls-heading);
  text-decoration: none;
}

.nav__list li:last-child a {
  border-bottom: 0;
}

/* Colour alone would carry this, and colour alone is what a reader with a
   colour vision deficiency cannot rely on. The weight does not depend on
   seeing a hue; from the strip up, a rule under the label joins it. */
.nav__list [aria-current] {
  color: var(--color-accent);
  font-weight: 700;
}

.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background-color: transparent;
  color: var(--color-on-dark);
  cursor: pointer;
}

.nav-toggle__close,
.nav-toggle[aria-expanded='true'] .nav-toggle__open {
  display: none;
}

.nav-toggle[aria-expanded='true'] .nav-toggle__close {
  display: block;
}

/* Without a script the toggle can never open anything, so on a phone the links
   are shown inline instead — wrapped rather than unreachable. Above the strip
   breakpoint they are already inline and this is not needed. */
@media (scripting: none) and (max-width: 56.1875rem) {
  .nav {
    position: static;
    display: block;
    order: 3;
    width: 100%;
    max-height: none;
    padding: 0 0 var(--space-1);
    border-bottom: 0;
    /* Shown outright, never opened, so there is no transition to run and no
       closed state to inherit. */
    opacity: 1;
    translate: none;
    transition: none;
  }

  .nav__list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-3);
  }

  .nav__list a {
    min-height: 2.5rem;
    border-bottom: 0;
    font-size: var(--fs-base);
  }

  .nav-toggle {
    display: none;
  }

  /* The inline link row makes the header taller than --header-offset, and the
     hero's negative margin is measured against that constant. Without a script
     the header simply stops floating over the photograph. */
  .header {
    position: static;
  }

  .hero {
    margin-top: 0;
  }

  .hero__body {
    padding-top: var(--space-7);
  }
}

/* At 900px the compact labels fit beside the logo. Below that width a hamburger
   is clearer than wrapping the navigation onto a second row. */
@media (min-width: 56.25rem) {
  :root {
    --header-offset: var(--header-height);
  }

  .header__inner {
    flex-wrap: nowrap;
  }

  .nav {
    position: static;
    display: block;
    order: 0;
    width: auto;
    flex: 1 1 auto;
    min-width: 0;
    max-height: none;
    overflow: visible;
    padding: 0;
    border-bottom: 0;
    background-color: transparent;
    opacity: 1;
    translate: none;
    transition: none;
  }

  .nav__list {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(0rem, 1vw, var(--space-2));
    max-width: none;
    margin: 0;
    overflow: visible;
  }

  .nav__list a {
    min-height: var(--header-height);
    padding-inline: clamp(var(--space-xs), 0.6vw, var(--space-1));
    border-bottom: 0;
    font-family: var(--font-body);
    font-size: clamp(0.75rem, 1.2vw, 0.875rem);
    font-weight: 600;
    letter-spacing: 0.025em;
    text-transform: uppercase;
    white-space: nowrap;
    box-shadow: inset 0 -3px 0 transparent;
    transition: color var(--dur-hover) ease, box-shadow var(--dur-hover) ease;
  }

  .nav__list [aria-current] {
    box-shadow: inset 0 -3px 0 var(--color-accent);
  }

  .nav-toggle {
    display: none;
  }
}

@media (min-width: 56.25rem) and (hover: hover) and (pointer: fine) {
  .nav__list a:hover {
    color: var(--color-accent);
  }
}

.lang-switch {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.06em;
}

.lang-switch a,
.lang-switch span {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding-inline: var(--space-xs);
  color: var(--color-on-dark);
  text-decoration: none;
}

.lang-switch [aria-current='true'] {
  border-bottom: var(--hairline) solid var(--color-accent);
}

.lang-switch__sep {
  color: var(--color-on-dark-muted);
}

@media (min-width: 1200px) {
  .header .button {
    display: inline-flex;
    padding-inline: var(--space-3);
    font-size: 0.875rem;
    text-transform: uppercase;
    white-space: nowrap;
  }
}

/* Full reference proportions on large screens. */
@media (min-width: 1280px) {
  :root {
    --header-height: 5.875rem;
    --header-offset: var(--header-height);
  }

  .header__logo img {
    width: 17.5rem;
  }

  .nav__list {
    gap: clamp(var(--space-1), 1.4vw, var(--space-3));
  }

  .hero__body,
  .trust .container {
    max-width: none;
    padding-inline: clamp(var(--space-4), 4vw, var(--space-7));
  }
}

@media (min-width: 1280px) and (hover: hover) and (pointer: fine) {
  .nav__list a:hover {
    color: var(--color-accent);
  }
}

/* --------------------------------------------------------------------------
   7. Hero
   -------------------------------------------------------------------------- */

/* The practice photograph fills the whole hero and the copy sits on it.
   The scrim is a single flat tone, not a gradient. Its 72% is measured against
   the rendered crop, not guessed: the brightest pixel that lands behind the
   copy is the anatomy poster at rgb(247 227 213), and 72% is the lowest
   opacity at which cream text clears 4.5:1 over it. Swapping the hero photo
   or changing object-position invalidates that number — re-measure. */
.hero {
  position: relative;
  margin-top: calc(var(--header-offset) * -1);
  isolation: isolate;
  display: grid;
}

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

.hero__media {
  position: relative;
}

.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 55%;
}

.hero__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background-color: color-mix(in srgb, var(--color-bg-dark) 66%, transparent);
}

.hero__body {
  position: relative;
  z-index: 1;
  align-self: end;
  padding-top: calc(var(--header-offset) + var(--space-7));
  padding-bottom: var(--space-7);
  color: var(--color-on-dark);
}

.hero .eyebrow {
  color: var(--beige-200);
}

.hero__title {
  max-width: 16ch;
  font-size: var(--fs-display);
  color: var(--color-on-dark);
}

.hero__lead {
  max-width: 46ch;
  margin-top: var(--space-3);
  font-size: var(--fs-md);
  color: var(--cream-100);
}

.hero__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

/* Over a photograph the outlined button would depend on whatever pixels sit
   behind it, so in the hero it carries its own solid ground: 9.9:1, fixed. */
.hero__actions .button--secondary {
  background-color: var(--color-bg);
  border-color: var(--color-bg);
  color: var(--color-text);
}

@media (hover: hover) and (pointer: fine) {
  .hero__actions .button--secondary:hover {
    background-color: var(--color-bg-alt);
    border-color: var(--color-bg-alt);
  }
}

@media (min-width: 640px) {
  .hero__actions {
    flex-direction: row;
    flex-wrap: wrap;
  }
}

@media (min-width: 900px) {
  .hero__media img {
    min-height: 38rem;
    max-height: 46rem;
  }

  .hero__body {
    padding-bottom: var(--space-8);
  }
}

/* --------------------------------------------------------------------------
   8. Trust strip
   -------------------------------------------------------------------------- */

.trust {
  border-block: var(--hairline) solid var(--color-border);
  background-color: var(--color-bg-alt);
  padding-block: var(--space-4);
}

.trust__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-5);
  list-style: none;
  padding: 0;
  justify-content: space-between;
}

.trust__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1 1 10rem;
}

.trust__item img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 3rem;
  object-fit: contain;
}

.trust__text {
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--color-text-muted);
}

.trust__text strong {
  display: block;
  font-weight: 600;
  color: var(--color-text);
}

@media (min-width: 900px) {
  .trust__item + .trust__item {
    padding-left: var(--space-4);
    border-left: var(--hairline) solid var(--color-border);
  }
}

/* --------------------------------------------------------------------------
   9. Angebot — therapy cards
   -------------------------------------------------------------------------- */

.group + .group {
  margin-top: var(--space-8);
}

.group__head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-2);
  border-bottom: var(--hairline) solid var(--color-border);
}

.group__title {
  font-size: var(--fs-xl);
}

.group__count {
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* A heading that opens a block inside a section, one level below .group__title. */
.subhead {
  margin-top: var(--space-6);
  font-size: var(--fs-xl);
}

.card-grid {
  display: grid;
  gap: var(--space-4);
  list-style: none;
  padding: 0;
}

.service-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background-color: var(--color-bg);
  border: var(--hairline) solid var(--color-border);
  border-radius: var(--radius-lg);
}

/* The ratio belongs on the image: as a flex child the wrapper's own
   aspect-ratio is overridden by the content's height. */
.service-card__media img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

.service-card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: var(--space-3);
}

.service-card__title {
  font-size: var(--fs-lg);
  font-weight: 700;
}

.service-card__text {
  margin-top: var(--space-1);
  font-size: var(--fs-base);
  color: var(--color-text-muted);
}

.effects {
  margin-top: var(--space-3);
  border-top: var(--hairline) solid var(--color-border);
  padding-top: var(--space-2);
}

.effects__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-height: 3rem;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--color-text);
  cursor: pointer;
  list-style: none;
}

.effects__summary::-webkit-details-marker {
  display: none;
}

.effects__summary .icon {
  width: 1.25rem;
  height: 1.25rem;
  transition: transform var(--dur-disclosure) var(--ease-out);
}

.effects[open] > .effects__summary .icon {
  transform: rotate(180deg);
}

.effects__list {
  list-style: none;
  padding: 0 0 var(--space-1);
  color: var(--color-text-muted);
}

.effects__list li {
  position: relative;
  padding-left: var(--space-3);
  font-size: var(--fs-base);
}

.effects__list li + li {
  margin-top: var(--space-1);
}

.effects__list li::before {
  content: '';
  position: absolute;
  top: 0.75em;
  left: 0;
  width: var(--space-2);
  height: var(--hairline);
  background-color: var(--color-accent);
}

@media (min-width: 640px) {
  .card-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 900px) {
  /* From 900px the bullets are always visible: there is room, and a disclosure
     that only ever shows one state is a control that does nothing. */
  .effects {
    border-top: 0;
    padding-top: 0;
  }

  .effects__summary {
    min-height: 0;
    padding-top: var(--space-1);
    cursor: default;
    pointer-events: none;
  }

  .effects__summary .icon {
    display: none;
  }

  /* Two rendering models for a closed <details>: browsers that hide the
     children directly, and browsers that hide ::details-content (below). Both
     have to be opened up, or the bullets vanish on desktop in one of them. */
  .effects:not([open]) > .effects__list {
    display: block;
  }

  .service-card__body {
    padding: var(--space-4);
  }
}

@media (min-width: 1200px) {
  .card-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* --------------------------------------------------------------------------
   10. Angebot — wellness, offset two-column rows
   -------------------------------------------------------------------------- */

.offset-list {
  display: grid;
  gap: var(--space-6);
  margin-top: var(--space-5);
  list-style: none;
  padding: 0;
}

.offset-row {
  display: grid;
  gap: var(--space-3);
}

.offset-row__media img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-lg);
}

.offset-row__title {
  font-size: var(--fs-lg);
  font-weight: 700;
  margin-bottom: var(--space-1);
}

.offset-row__text {
  color: var(--color-text-muted);
  max-width: var(--measure);
}

@media (min-width: 900px) {
  .offset-list {
    gap: var(--space-7);
  }

  .offset-row {
    grid-template-columns: 5fr 6fr;
    align-items: center;
    gap: var(--space-5);
  }

  .offset-row:nth-child(even) .offset-row__media {
    order: 2;
  }
}

/* --------------------------------------------------------------------------
   11. Preise — durations drawn to scale
   This practice sells time, so the price list is a measure: each duration's
   rail is as long as the appointment. It is the one idea on the page that
   would make no sense on any other site.
   -------------------------------------------------------------------------- */

.price-rail {
  list-style: none;
  padding: 0;
  border-top: var(--hairline) solid var(--color-border);
}

.price-tier {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding-block: var(--space-3);
  border-bottom: var(--hairline) solid var(--color-border);
}

.price-tier__duration {
  font-family: var(--font-heading);
  font-size: var(--fs-lg);
  font-weight: 700;
  letter-spacing: var(--ls-heading);
}

.price-tier__amount {
  font-size: var(--fs-md);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Each rail is as long as the appointment: 30, 60, 90 and 120 minutes drawn
   against the longest. The share is a class rather than an inline custom
   property so that style-src needs no 'unsafe-inline'. */
.price-tier--30 { --tier-share: 0.25; }
.price-tier--60 { --tier-share: 0.5; }
.price-tier--90 { --tier-share: 0.75; }
.price-tier--120 { --tier-share: 1; }

.price-tier__measure {
  grid-column: 1 / -1;
  height: 2px;
  background-color: var(--color-brand-tint);
  width: calc(var(--tier-share) * 100%);
  transform-origin: left center;
}


.value-list {
  list-style: none;
  padding: 0;
  margin-top: var(--space-6);
}

.value-list li {
  padding-block: var(--space-2);
  border-top: var(--hairline) solid var(--color-accent);
  max-width: var(--measure);
}

.voucher {
  margin-top: var(--space-6);
}

.voucher img {
  width: 100%;
  object-fit: contain;
  border-radius: var(--radius-lg);
}

.voucher figcaption {
  margin-top: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
}

@media (min-width: 900px) {
  .price-layout {
    display: grid;
    grid-template-columns: 7fr 5fr;
    gap: var(--space-7);
    align-items: start;
  }

  .voucher {
    margin-top: 0;
  }
}

/* --------------------------------------------------------------------------
   12. Warum — plain definition list, no cards
   -------------------------------------------------------------------------- */

.why-layout {
  display: grid;
  gap: var(--space-4);
}

.why-list {
  margin: 0;
}

.why-list div {
  padding-block: var(--space-3);
  border-top: var(--hairline) solid var(--color-border);
}

.why-list dt {
  font-family: var(--font-heading);
  font-size: var(--fs-md);
  font-weight: 700;
  letter-spacing: var(--ls-heading);
}

.why-list dd {
  margin: var(--space-xs) 0 0;
  max-width: var(--measure);
  color: var(--color-text-muted);
}

@media (min-width: 900px) {
  .why-layout {
    grid-template-columns: 4fr 7fr;
    gap: var(--space-7);
  }

  .why-layout .section-head {
    position: sticky;
    top: calc(var(--header-offset) + var(--space-4));
    margin-bottom: 0;
  }
}

/* --------------------------------------------------------------------------
   13. Über mich
   -------------------------------------------------------------------------- */

.about-layout {
  display: grid;
  gap: var(--space-5);
}

.about__portrait img {
  width: 100%;
  border-radius: var(--radius-lg);
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: 50% 20%;
}

.about__caption {
  margin-top: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
}

.timeline {
  list-style: none;
  padding: 0;
  margin-top: var(--space-5);
}

.timeline li {
  padding-block: var(--space-3);
  border-top: var(--hairline) solid var(--color-border);
}

.timeline__year {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  color: var(--color-text-muted);
}

.timeline__title {
  display: block;
  margin-top: var(--space-xs);
  font-family: var(--font-heading);
  font-size: var(--fs-md);
  font-weight: 700;
}

.timeline__text {
  margin-top: var(--space-1);
  max-width: var(--measure);
  color: var(--color-text-muted);
}

/* The grid shows a 320px thumbnail of each scan inside a 150px box, so it is
   sharp on a retina screen and costs about 5 KB. The full scan — 1654px on the
   long edge for most of them, enough to read every line — is what the zoom link
   points at, and it is fetched only when a reader actually opens one.
   A grid, not wrapped flex. Wrapping made every row as tall as its longest
   caption and started the next row wherever the previous item happened to end,
   so on a phone — where only two fit across — the six scans read as a loose
   pile rather than a set. Grid rows line up on their own, and auto-fill takes
   the column count from the space actually there: two on a phone, three beside
   the portrait, more in a full-width column. */
.credentials {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: var(--space-4) var(--space-3);
  list-style: none;
  padding: 0;
  margin-top: var(--space-4);
}

/* The tile is the constant, not the scan inside it. Five of these are portrait
   and one is landscape; bottom-aligning a bare image left the landscape one
   hanging in empty space with nothing to sit against. Giving the box the white
   ground and the hairline instead makes every credential the same shape
   whatever the paper was, and puts the zoom badge in a real corner. */
.credential picture {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 9.375rem;
  padding: 0;
  background-color: transparent;
  border: 0;
  border-radius: 0;
}

.credential img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
}

.credential figcaption {
  margin-top: var(--space-1);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.35;
  text-wrap: pretty;
}

.credential figcaption span {
  display: block;
  font-weight: 400;
  color: var(--color-text-muted);
}

.membership {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: var(--hairline) solid var(--color-border);
}

.membership img {
  width: 9rem;
  border: 0;
  border-radius: 0;
}

@media (min-width: 900px) {
  .about-layout {
    grid-template-columns: 5fr 7fr;
    gap: var(--space-7);
    align-items: start;
  }

  .about__portrait {
    position: sticky;
    top: calc(var(--header-offset) + var(--space-4));
  }

  .membership {
    flex-direction: row;
    align-items: center;
    gap: var(--space-4);
  }
}

/* --------------------------------------------------------------------------
   14. Zoom-Lightbox — reading a diploma at size
   -------------------------------------------------------------------------- */

/* Every enlargeable scan is wrapped in a plain link to the image file. With no
   script the link still opens the full image in a tab, which is the browser's
   own zoom and needs nothing from us; the script upgrades that same link into
   the dialog below. Underline off — the picture is the affordance, and the
   magnifier badge says the rest. */
.zoom-link {
  position: relative;
  display: block;
  text-decoration: none;
  cursor: zoom-in;
}

.zoom-link__badge {
  position: absolute;
  right: var(--space-1);
  bottom: var(--space-1);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  color: var(--color-on-brand);
  background-color: var(--color-brand);
  border-radius: var(--radius-pill);
  opacity: 0.85;
  transition: opacity var(--dur-hover) var(--ease-out);
}

.zoom-link__badge .icon {
  width: 1rem;
  height: 1rem;
}

@media (hover: hover) and (pointer: fine) {
  .zoom-link:hover .zoom-link__badge {
    opacity: 1;
  }
}

.membership .zoom-link {
  flex: none;
  width: 9rem;
}

.membership .zoom-link img {
  width: 100%;
}

/* <dialog> earns its place here: showModal() gives the modal its own top-layer
   stacking context, traps Tab inside it, closes on Escape and restores focus to
   the link on close — four behaviours that a div would have to reimplement and
   would get subtly wrong. */
.lightbox {
  width: min(96vw, 68rem);
  max-width: none;
  max-height: 92vh;
  padding: 0;
  color: var(--color-text);
  background-color: var(--color-bg);
  border: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.lightbox::backdrop {
  background-color: rgba(63, 74, 60, 0.82);
}

.lightbox:not([open]) {
  display: none;
}

/* The one scrolling box on the page that is meant to scroll in both axes: past
   the first zoom step the scan is larger than the stage and the reader pans it
   the way they would pan a map. */
/* No padding, so clientWidth/clientHeight are exactly the box the scan has to
   fit into and the script can measure it without unpicking a computed style. */
.lightbox__stage {
  display: grid;
  place-items: center;
  height: 74vh;
  overflow: auto;
  background-color: var(--color-bg-alt);
  overscroll-behavior: contain;
}

/* Width is the only axis given a value; height stays auto, so every scan keeps
   its own ratio whatever the zoom — five of these are portrait and one is
   landscape. Constraining the other axis with max-width instead does not work:
   given an explicit height, a browser clamps the width to max-width and leaves
   the height where it was, and the landscape scan comes out stretched.
   --fit-width is the width at which the scan exactly fills the stage. Only the
   script can know it, because it depends on the file's intrinsic size; --zoom
   multiplies it. The 100% fallback covers the frame between showModal() and
   the image reporting its dimensions. */
.lightbox__image {
  width: calc(var(--zoom, 1) * var(--fit-width, 100%));
  max-width: none;
  height: auto;
  border: var(--hairline) solid var(--color-border);
  background-color: #FFF;
}

.lightbox__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2);
  border-top: var(--hairline) solid var(--color-border);
}

.lightbox__caption {
  flex: 1 1 12rem;
  font-size: var(--fs-sm);
  line-height: 1.35;
  color: var(--color-text-muted);
}

.lightbox__tools {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.lightbox__button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  font-family: var(--font-body);
  font-size: var(--fs-md);
  font-weight: 700;
  line-height: 1;
  color: var(--color-text);
  background-color: transparent;
  border: var(--hairline) solid var(--color-border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.lightbox__button[disabled] {
  color: var(--color-text-muted);
  opacity: 0.45;
  cursor: default;
}

@media (hover: hover) and (pointer: fine) {
  .lightbox__button:not([disabled]):hover {
    background-color: var(--color-surface);
  }
}

.lightbox__level {
  min-width: 3.5rem;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--color-text-muted);
}

/* --------------------------------------------------------------------------
   15. Stimmen — editorial columns, never a slider
   -------------------------------------------------------------------------- */

.testimonials {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-5);
}

.testimonial {
  border-top: var(--hairline) solid var(--color-accent);
  padding-top: var(--space-2);
  break-inside: avoid;
}

.testimonial__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  margin-bottom: var(--space-2);
}

.testimonial__name {
  font-family: var(--font-heading);
  font-size: var(--fs-md);
  font-weight: 700;
  letter-spacing: var(--ls-heading);
}

.testimonial__date {
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
}

.testimonial__note {
  font-size: var(--fs-sm);
  font-style: italic;
  color: var(--color-text-muted);
}

.testimonial blockquote p + p {
  margin-top: var(--space-2);
}

.more {
  margin-top: var(--space-1);
}

.more__summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 3rem;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--color-brand-hover);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
  list-style: none;
}

.more__summary::-webkit-details-marker {
  display: none;
}

.more[open] > .more__summary {
  display: none;
}

/* The form sits after the entries, not before them: a visitor reads what
   others wrote and then decides to write. Separated by a rule so the page does
   not read as though the form were another entry. */
.review-write {
  max-width: 40rem;
  margin-top: var(--space-8);
  padding-top: var(--space-5);
  border-top: var(--hairline) solid var(--color-border);
}

.review-write .subhead {
  margin-top: 0;
}

.review-write .form {
  margin-top: var(--space-4);
}

@media (min-width: 900px) {
  .testimonials {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-5) var(--space-6);
  }
}

@media (min-width: 1200px) {
  .testimonials {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* --------------------------------------------------------------------------
   16. Praxis — the one dark section
   -------------------------------------------------------------------------- */

.gallery {
  display: grid;
  gap: var(--space-2);
  list-style: none;
  padding: 0;
  margin-top: var(--space-5);
}

.gallery img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius-lg);
  transition: transform var(--dur-hover) var(--ease-out);
}

.gallery li {
  overflow: hidden;
  border-radius: var(--radius-lg);
  aspect-ratio: 4 / 3;
}

@media (hover: hover) and (pointer: fine) {
  .gallery li:hover img {
    transform: scale(1.03);
  }
}

@media (min-width: 640px) {
  .gallery {
    grid-template-columns: repeat(2, 1fr);
  }

  .gallery li:first-child {
    grid-column: span 2;
    aspect-ratio: 16 / 9;
  }
}

@media (min-width: 900px) {
  .gallery {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3);
  }

  .gallery li:first-child {
    grid-column: span 2;
    grid-row: span 2;
    aspect-ratio: auto;
  }
}

/* --------------------------------------------------------------------------
   17. FAQ
   -------------------------------------------------------------------------- */

.faq {
  max-width: 52rem;
}

.faq__item {
  border-top: var(--hairline) solid var(--color-border);
}

.faq__item:last-of-type {
  border-bottom: var(--hairline) solid var(--color-border);
}

.faq__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: 3.5rem;
  padding-block: var(--space-2);
  font-family: var(--font-heading);
  font-size: var(--fs-md);
  font-weight: 700;
  letter-spacing: var(--ls-heading);
  cursor: pointer;
  list-style: none;
}

.faq__summary::-webkit-details-marker {
  display: none;
}

.faq__summary .icon {
  transition: transform var(--dur-disclosure) var(--ease-out);
}

.faq__item[open] > .faq__summary .icon {
  transform: rotate(180deg);
}

.faq__body {
  padding-bottom: var(--space-3);
  max-width: var(--measure);
  color: var(--color-text-muted);
}

.faq__body p + p,
.faq__body ul {
  margin-top: var(--space-2);
}

.faq__body ul {
  padding-left: var(--space-3);
}

.faq__body li + li {
  margin-top: var(--space-1);
}

/* Disclosures animate height where the browser supports interpolating to
   auto. Transitions, not keyframes, so a fast double-toggle retargets instead
   of restarting. Browsers without support toggle instantly — nothing breaks. */
@supports (interpolate-size: allow-keywords) {
  :root {
    interpolate-size: allow-keywords;
  }

  .faq__item::details-content,
  .effects::details-content,
  .more::details-content {
    height: 0;
    overflow: hidden;
    opacity: 0;
    transition: height var(--dur-disclosure) var(--ease-out),
                opacity var(--dur-disclosure) var(--ease-out),
                content-visibility var(--dur-disclosure) allow-discrete;
  }

  .faq__item[open]::details-content,
  .effects[open]::details-content,
  .more[open]::details-content {
    height: auto;
    opacity: 1;
  }
}

@media (min-width: 900px) {
  @supports (interpolate-size: allow-keywords) {
    .effects::details-content {
      height: auto;
      opacity: 1;
      overflow: visible;
      content-visibility: visible;
    }
  }
}

/* --------------------------------------------------------------------------
   18. Standort
   -------------------------------------------------------------------------- */

.location-layout {
  display: grid;
  gap: var(--space-5);
}

.address-block {
  font-style: normal;
}

.address-block dl {
  margin: 0;
}

.address-block div {
  display: grid;
  gap: var(--space-xs);
  padding-block: var(--space-3);
  border-top: var(--hairline) solid var(--color-border);
}

.address-block dt {
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.address-block dd {
  margin: 0;
}

.map-link {
  margin-top: var(--space-3);
}

.map {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background-color: var(--color-surface);
}

.map iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

.map__placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-4);
  border: 0;
  background-color: var(--color-surface);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  text-align: center;
  cursor: pointer;
  transition: background-color var(--dur-hover) ease;
}

@media (hover: hover) and (pointer: fine) {
  .map__placeholder:hover {
    background-color: var(--color-border);
  }
}

.map__hint {
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
}

@media (min-width: 900px) {
  .location-layout {
    grid-template-columns: 5fr 7fr;
    gap: var(--space-7);
    align-items: start;
  }
}

/* --------------------------------------------------------------------------
   19. Kontakt and form
   -------------------------------------------------------------------------- */

.contact-layout {
  display: grid;
  gap: var(--space-6);
}

.contact-direct {
  list-style: none;
  padding: 0;
  margin-top: var(--space-3);
}

.contact-direct li {
  border-top: var(--hairline) solid var(--color-border);
}

.contact-direct a,
.contact-direct__static {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 3.5rem;
  padding-block: var(--space-1);
  color: var(--color-text);
  text-decoration: none;
}

.contact-direct a:hover {
  text-decoration: underline;
}

.contact-direct span {
  display: block;
}

.contact-direct .contact-direct__label {
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
}

.form {
  display: grid;
  gap: var(--space-3);
  max-width: 40rem;
}

.field {
  display: grid;
  gap: var(--space-1);
  /* Grid items refuse to shrink below the input's intrinsic size unless told
     to. Without this the form pushes the page wider than a 320px viewport. */
  min-width: 0;
}

.field label {
  font-size: var(--fs-sm);
  font-weight: 600;
}

.field .req {
  color: var(--color-text-muted);
  font-weight: 400;
}

.field input,
.field select,
.field textarea {
  width: 100%;
  min-width: 0;
  min-height: 3rem;
  padding: var(--space-1) var(--space-2);
  border: var(--hairline) solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  line-height: 1.5;
  transition: border-color var(--dur-hover) ease;
}

.field textarea {
  min-height: 8rem;
  resize: vertical;
}

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  border-color: var(--color-brand);
}

.field [aria-invalid='true'] {
  border-color: var(--color-error);
}

.field__hint {
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
}

.field__error {
  font-size: var(--fs-sm);
  color: var(--color-error);
}

.field__error:empty {
  display: none;
}

.field--check {
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: var(--space-2);
}

.field--check input {
  min-height: 0;
  width: 1.5rem;
  flex: none;
  height: 1.5rem;
  margin-top: var(--space-xs);
  accent-color: var(--color-brand);
}

.field--check label {
  font-weight: 400;
  font-size: var(--fs-base);
}

.field--check .field__error {
  grid-column: 1 / -1;
}

.form__status {
  font-size: var(--fs-sm);
  color: var(--color-error);
}

.form__status:empty {
  display: none;
}

.form-success {
  padding: var(--space-4);
  border: var(--hairline) solid var(--color-border);
  border-radius: var(--radius-lg);
  background-color: var(--color-bg-alt);
  max-width: 40rem;
}

.form-success h3 {
  font-size: var(--fs-lg);
  font-weight: 700;
  margin-bottom: var(--space-1);
}

@media (min-width: 640px) {
  .form__row {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-3);
  }
}

@media (min-width: 900px) {
  .contact-layout {
    grid-template-columns: 7fr 4fr;
    gap: var(--space-7);
    align-items: start;
  }
}

/* --------------------------------------------------------------------------
   18c. Legal pages — long-form prose, no photographs
   -------------------------------------------------------------------------- */

/* These pages are read, not scanned, so the measure is narrower than the rest
   of the site and the headings carry the structure on their own. There is no
   sticky bar and no hero here: nothing on a privacy policy should compete with
   the sentence being read. */
.legal {
  max-width: 52rem;
}

.legal h2 {
  margin-top: var(--space-6);
  font-size: var(--fs-xl);
}

.legal h3 {
  margin-top: var(--space-4);
  font-size: var(--fs-md);
  font-weight: 700;
}

.legal p,
.legal ul,
.legal address {
  margin-top: var(--space-2);
  max-width: var(--measure);
}

.legal ul {
  padding-left: var(--space-3);
}

.legal li + li {
  margin-top: var(--space-1);
}

.legal__updated {
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
}

.legal__address {
  font-style: normal;
  padding: var(--space-3);
  border-left: 2px solid var(--color-accent);
  background-color: var(--color-bg-alt);
}

.legal code {
  padding: 0.1em 0.3em;
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  font-size: 0.9em;
}

/* No sticky call-to-action bar on a legal page, so the body needs none of the
   room the bar would otherwise occupy. */
body:has(.legal) {
  padding-bottom: 0;
}

/* --------------------------------------------------------------------------
   20. Footer
   -------------------------------------------------------------------------- */

.footer {
  border-top: var(--hairline) solid var(--color-border);
  padding-block: var(--space-6);
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
}

.footer__grid {
  display: grid;
  gap: var(--space-4);
}

.footer__title {
  font-size: var(--fs-sm);
  font-family: var(--font-body);
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--color-text);
  margin-bottom: var(--space-1);
}

.footer ul {
  list-style: none;
  padding: 0;
}

.footer li + li {
  margin-top: var(--space-xs);
}

.footer a {
  display: inline-block;
  padding-block: var(--space-xs);
  color: var(--color-text-muted);
}

.footer__bottom {
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: var(--hairline) solid var(--color-border);
}

@media (min-width: 640px) {
  .footer__grid {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-5);
  }
}

/* --------------------------------------------------------------------------
   21. Sticky mobile CTA bar
   -------------------------------------------------------------------------- */

.cta-bar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--bar-height);
  padding: var(--space-1) var(--gutter);
  padding-bottom: calc(var(--space-1) + env(safe-area-inset-bottom));
  background-color: var(--color-bg);
  border-top: var(--hairline) solid var(--color-border);
  /* Percentage translate: the bar hides by exactly its own height whatever
     the safe-area inset turns out to be. Exit is quicker than entry. */
  transform: translateY(100%);
  transition: transform 180ms var(--ease-out);
}

.cta-bar[data-visible] {
  transform: translateY(0);
  transition-duration: var(--dur-bar);
}

.cta-bar .button {
  flex: 1;
}

.cta-bar__whatsapp {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  flex: none;
  border: var(--hairline) solid var(--color-border);
  border-radius: var(--radius-pill);
  color: var(--color-text);
  transition: background-color var(--dur-hover) ease,
              transform var(--dur-press) var(--ease-out);
}

.cta-bar__whatsapp:active {
  transform: scale(0.97);
}

@media (min-width: 900px) {
  .cta-bar {
    display: none;
  }

  body {
    padding-bottom: 0;
  }
}

/* --------------------------------------------------------------------------
   22. Scroll reveal
   -------------------------------------------------------------------------- */

/* The starting state is only applied where a script can undo it. With
   JavaScript off the whole page is simply visible — a reveal that never
   arrives would otherwise hide the entire site. A browser that does not know
   the `scripting` feature ignores this block, which fails the same safe way. */
@media (scripting: enabled) {
  [data-reveal] {
    opacity: 0;
    transform: translateY(8px);
    transition: opacity var(--dur-reveal) var(--ease-out),
                transform var(--dur-reveal) var(--ease-out);
    transition-delay: var(--reveal-delay, 0ms);
  }

  [data-reveal='shown'] {
    opacity: 1;
    transform: translateY(0);
  }
}

/* --------------------------------------------------------------------------
   23. Reduced motion — nothing moves, everything is visible
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 1ms;
    animation-iteration-count: 1;
    transition-duration: 1ms;
    transition-delay: 0ms;
  }

  [data-reveal] {
    opacity: 1;
    transform: none;
  }

  .cta-bar {
    transform: translateY(100%);
  }

  .cta-bar[data-visible] {
    transform: translateY(0);
  }

  .gallery li:hover img,
  .button:active {
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   6b. Ferienhinweis
   -------------------------------------------------------------------------- */

/* Above the header, not between header and hero: the hero pulls itself up by
   --header-height, so a strip in between would end up underneath it. Here the
   notice scrolls away and the sticky header takes over, which is how an
   announcement bar is expected to behave.

   Switched by the `hidden` attribute from the panel, so it is absent from the
   served markup rather than hidden by a script — it reads the same with
   JavaScript off. */
.notice {
  background-color: var(--color-brand);
  color: var(--color-on-brand);
}

.notice__inner {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-block: var(--space-2);
  font-size: var(--fs-sm);
}

.notice .icon {
  flex-shrink: 0;
}
