/* =========================================================================
   Besa Swiss — besaswiss.ch
   Warm, light, minimal. Signature shapes: gold arch outlines, arch-framed
   images, soft pills. No build step — plain CSS.

   ── BRAND CASING ────────────────────────────────────────────────────────
   The display name is "Besa Swiss". To render it as "besaswiss" instead,
   change two values in the .brand block below:
       --brand-case: lowercase;   (from: none)
       --brand-gap:  0;           (from: .28em)
   That covers header and footer. The <title> and the og: tags in
   index.html carry the name as plain text — swap those too if the casing
   changes there as well.
   ========================================================================= */

/* ---------- Self-hosted fonts (no request leaves the server) ---------- */
@font-face{
  font-family:'Jost';font-style:normal;font-weight:300 500;font-display:swap;
  src:url('fonts/jost-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
    U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Jost';font-style:normal;font-weight:300 500;font-display:swap;
  src:url('fonts/jost-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,
    U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,
    U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:'Playfair Display';font-style:normal;font-weight:400 600;font-display:swap;
  src:url('fonts/playfair-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
    U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Playfair Display';font-style:normal;font-weight:400 600;font-display:swap;
  src:url('fonts/playfair-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,
    U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,
    U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:'Playfair Display';font-style:italic;font-weight:400 500;font-display:swap;
  src:url('fonts/playfair-italic-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
    U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,
    U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Playfair Display';font-style:italic;font-weight:400 500;font-display:swap;
  src:url('fonts/playfair-italic-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,
    U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,
    U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

/* =========================================================================
   TOKENS — three scales, and nothing outside them.

   Colour, type and space are each a closed set below. A literal value anywhere
   further down this file is a bug, not a decision; the two deliberate
   exceptions are marked where they occur.
   ========================================================================= */
:root{
  /* ---- Colour: one warm ramp, dark to light --------------------------
     SIX OF THESE ARE ALSO NEEDED AT PARTIAL OPACITY, AND CSS CANNOT PUT AN
     ALPHA ON A HEX TOKEN. So the colour used to be typed out a second time at
     the point of use — the header's ground was written as a literal 251 247
     242 at .86, which IS --ivory — and that copy did not follow its token:
     editing --ivory moved the whole page and left the header on the old
     colour. Same class of bug this file calls out at the top, wearing an alpha.

     Those six are therefore declared as CHANNELS, and the solid form is
     DERIVED from the channels rather than written out beside them, so there is
     one source and the two forms cannot drift apart. It is the trick --shade
     already uses for every shadow on the page. The hex in each comment is a
     reading aid, not a definition — change the channels, not the comment.
     Give a colour a -rgb twin the moment it needs an alpha variant, and read
     it back through rgb(var(--x-rgb) / a); never type the channels inline. */
  --espresso-rgb:46 33 26;       --espresso:rgb(var(--espresso-rgb));   /* #2E211A */
  --ink:#3B2C22;
  --clay-rgb:122 90 65;          --clay:rgb(var(--clay-rgb));           /* #7A5A41 */
  --mocha:#9A7455;
  --sand-rgb:220 195 162;        --sand:rgb(var(--sand-rgb));           /* #DCC3A2 */
  --gold-deep:#BD9565;      /* the dark end of the gold pill's sweep */
  --gold-rgb:203 166 121;        --gold:rgb(var(--gold-rgb));           /* #CBA679 */
  --gold-soft-rgb:232 211 180;   --gold-soft:rgb(var(--gold-soft-rgb)); /* #E8D3B4 */
  --cream:#F3EAE0;
  --ivory-rgb:251 247 242;       --ivory:rgb(var(--ivory-rgb));         /* #FBF7F2 */
  --paper:#FFFFFF;          /* Cal.com's own ground — used nowhere else */
  --muted:#6F5A48;          /* 5.8:1 on ivory */
  --line:rgb(var(--clay-rgb) / .16);
  --line-strong:rgb(var(--clay-rgb) / .24);

  /* The brown CTA card. Four stops down ONE axis, because the card has one
     light source: upper left. The highlight in .cta::before comes from the
     same direction — the old build lit it from the top centre while the
     gradient ran from the left, and the two cancelled into a flat slab. */
  --brown-lit:#85613F;
  --brown-mid:#78573D;
  --brown-shade:#684A33;
  --brown-deep:#5A3F2C;
  --brown-grad:linear-gradient(146deg,
    var(--brown-lit) 0%,var(--brown-mid) 34%,
    var(--brown-shade) 70%,var(--brown-deep) 100%);
  /* Small text on that card. --gold-soft reaches only 4.1:1 against
     --brown-lit; this clears 4.5:1 across the whole gradient. */
  --on-brown:#F5EDDF;

  /* ---- Shadow: always warm ------------------------------------------- */
  /* A neutral shadow on a cream page reads as dirt. Every shadow on the page
     is this one hue at some alpha, so they all fall in the same light. */
  --shade:60 42 28;
  --shadow-card:0 22px 44px -36px rgb(var(--shade) / .42);
  --shadow-card-lift:0 34px 54px -34px rgb(var(--shade) / .60);
  --shadow-photo:0 40px 70px -42px rgb(var(--shade) / .52);
  --shadow-pill:0 16px 30px -20px rgb(var(--shade) / .55);
  --shadow-pill-lift:0 24px 40px -18px rgb(var(--shade) / .48);

  /* ---- Type ----------------------------------------------------------
     One modular scale. Ratio 1.25 at 1440px from a 17px base; compressed to
     1.15 at 390px, because a ratio that reads as generous on a desktop reads
     as shouting on a phone. Steps below the base use 1.125 at both ends so
     they cannot invert. Every font-size in this file is one of these. */
  --fs-label   :clamp(0.7023rem, 0.6860rem + 0.067vw, 0.7462rem);  /* 11.2→11.9 */
  --fs-caption :clamp(0.7901rem, 0.7718rem + 0.075vw, 0.8395rem);  /* 12.6→13.4 */
  --fs-small   :clamp(0.8889rem, 0.8683rem + 0.085vw, 0.9444rem);  /* 14.2→15.1 */
  --fs-body    :clamp(1.0000rem, 0.9768rem + 0.095vw, 1.0625rem);  /* 16.0→17.0 */
  --fs-body-lg :clamp(1.0724rem, 1.0295rem + 0.176vw, 1.1879rem);  /* 17.2→19.0 */
  --fs-h3      :clamp(1.1500rem, 1.0838rem + 0.271vw, 1.3281rem);  /* 18.4→21.2 */
  --fs-lead    :clamp(1.5209rem, 1.3150rem + 0.845vw, 2.0752rem);  /* 24.3→33.2 */
  --fs-h2      :clamp(1.7490rem, 1.4352rem + 1.288vw, 2.5940rem);  /* 28.0→41.5 */
  --fs-display :clamp(2.0114rem, 1.5541rem + 1.876vw, 3.2425rem);  /* 32.2→51.9 */
  --fs-hero    :clamp(2.3131rem, 1.6668rem + 2.652vw, 4.0531rem);  /* 37.0→64.8 */

  /* Tracking IS the optical sizing here: this cut of Playfair Display ships a
     wght axis and no opsz (verified against the woff2), so the compensation
     large serif always needs has to be applied by hand, one value per step. */
  --track-hero:-.028em;
  --track-display:-.022em;
  --track-h2:-.018em;
  --track-lead:-.012em;
  --track-h3:-.005em;
  --track-label:.26em;      /* every uppercase label on the page */

  --lh-tight:1.04;          /* hero + display */
  --lh-head:1.14;           /* h2 */
  --lh-h3:1.28;
  --lh-lead:1.34;
  --lh-body:1.68;

  /* Measure. Long lines are the loudest "templated" tell there is.
     These are ch, and ch is the advance of "0" — a good deal wider than Jost's
     average letter in German text, so a ch figure renders about 1.3x that many
     actual characters. The values are therefore set from a measurement of the
     rendered line, not from the target: 50ch comes out at ~65 characters. */
  --measure:50ch;           /* body copy → ~65 characters */
  --measure-head:20ch;      /* display headings, in THEIR ch — see .shead h2 */
  --measure-sub:40ch;       /* the sentence under a heading → ~52 characters */

  --serif:'Playfair Display',Georgia,'Times New Roman',serif;
  --sans:'Jost','Helvetica Neue',Arial,sans-serif;

  /* ---- Space: 4px-based, doubling at the top ------------------------- */
  --s-1:.25rem; --s-2:.5rem;  --s-3:.75rem; --s-4:1rem;  --s-5:1.5rem;
  --s-6:2rem;   --s-7:3rem;   --s-8:4rem;   --s-9:6rem;  --s-10:8rem;

  /* Vertical rhythm. Every section pads by --space-section top and bottom, so
     the gap between any two sections is exactly 2 units. The only exception is
     a JOIN — two sections that are one thought — which halves both sides for a
     gap of 1. Those are the only two gap values on the page. */
  --space-section:clamp(var(--s-8),6.7vw,var(--s-9));   /* 64 → 96 */
  --space-block:clamp(var(--s-6),3.4vw,var(--s-7));     /* 32 → 48 */
  --gutter:clamp(var(--s-5),3.4vw,var(--s-8));          /* 24 → 64 */

  --radius-card:28px;
  --radius-pill:999px;

  --ease:cubic-bezier(.19,1,.22,1);
}

/* ---------- Reset / base ---------- */
*,*::before,*::after{box-sizing:border-box}
*{margin:0;padding:0}
/* THE SCHEME IS DECLARED, AND THAT IS A DEFENCE, NOT A PREFERENCE.
   This page is one warm light world with no dark variant. Chrome on Android
   ships Auto Dark Theme, which algorithmically inverts pages that never say
   which scheme they are — and inverting THIS one produces the cold clinical
   register the design system exists to refuse, with the before/after
   photographs, which are a client's skin and a client's consent, inverted
   along with it. Saying "light" out loud is what opts the page out.
   It also stops a visitor whose OS is in dark mode getting dark UA scrollbars
   and form controls against the cream. There is nothing to add for a dark
   theme here: not having one is the decision. */
html{color-scheme:light;scroll-behavior:smooth;-webkit-text-size-adjust:100%}
/* Lenis (assets/lenis.min.js) takes over the wheel while it is running and puts
   these classes on <html> itself. Its inertia and the native smooth scroll above
   would fight each other, so the native one steps aside for as long as Lenis is
   on. No Lenis (reduced motion, no JS) → none of this matches. */
html.lenis,html.lenis body{height:auto}
html.lenis{scroll-behavior:auto}
html.lenis.lenis-stopped{overflow:hidden}
html.lenis [data-lenis-prevent]{overscroll-behavior:contain}
body{
  font-family:var(--sans);font-weight:300;font-size:var(--fs-body);
  line-height:var(--lh-body);color:var(--ink);background:var(--ivory);
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
/* Line-height and tracking are set per step, not once for all three: a single
   -.015em is too much for a 21px h3 and nowhere near enough for a 65px h1. */
h1,h2,h3{font-family:var(--serif);font-weight:400;color:var(--espresso);
  line-height:var(--lh-head);letter-spacing:var(--track-h2);text-wrap:balance}
h3{line-height:var(--lh-h3);letter-spacing:var(--track-h3)}
/* Prose gets `pretty` instead: it leaves no single word on the last line.
   Ignored by browsers that do not have it, which is the whole point. */
p{text-wrap:pretty}
/* Short centred sentences get `balance` rather than `pretty`: two or three
   lines of equal length, instead of a full line and a dangling word. */
.shead p:not(.eyebrow),.pull p,.booking-fallback,.cta-note{text-wrap:balance}
img{display:block;max-width:100%;height:auto}
a{color:inherit;text-decoration:none}
ul{list-style:none}

/* One focus ring, everywhere. The brown card overrides only the colour. */
:focus-visible{outline:2px solid var(--clay);outline-offset:3px;border-radius:4px}
::selection{background:var(--gold-soft);color:var(--espresso)}

.wrap{width:min(1160px,90vw);margin-inline:auto}
/* :where() so it carries no specificity and the join modifiers below always
   win, wherever in the file they happen to sit. */
:where(.section,.cta-wrap,.site-footer){padding-block:var(--space-section)}
/* A JOIN: this section and its neighbour are one thought, so the pair keeps
   half a unit each and the gap between them lands at 1 instead of 2. Those two
   values — 2 units between movements, 1 across a join — are the only vertical
   gaps on the page. The old build had five, ranging from 127px to 230px. */
.section--join-top{padding-top:calc(var(--space-section) / 2)}
.section--join-bottom{padding-bottom:calc(var(--space-section) / 2)}
.skip{position:fixed;left:-9999px;top:0;background:var(--espresso);color:var(--ivory);
  padding:var(--s-3) var(--s-5);border-radius:0 0 12px 0;z-index:200}
.skip:focus{left:0}

/* ---------- Labels ----------
   One recipe for every piece of uppercase micro type on the page. They used to
   run at six different sizes and four different trackings; labels are exactly
   where a reader senses a system or the absence of one. */
.eyebrow,.crow small,.figcap{
  font-size:var(--fs-label);letter-spacing:var(--track-label);
  text-transform:uppercase;font-weight:500;line-height:1.4;color:var(--clay);
}
.eyebrow{display:flex;align-items:center;gap:var(--s-3);white-space:nowrap}
.eyebrow::after{content:"";height:1px;width:clamp(24px,4vw,56px);background:var(--gold);flex:none}
.eyebrow--center{justify-content:center}
.eyebrow--center::before{content:"";height:1px;width:clamp(24px,4vw,56px);background:var(--gold);flex:none}

.lead{font-family:var(--serif);font-size:var(--fs-lead);
  line-height:var(--lh-lead);letter-spacing:var(--track-lead);
  color:var(--espresso);font-weight:400;max-width:26ch}
.lead em{font-style:italic;color:var(--clay)}
.muted{color:var(--muted)}

/* ---------- Grain overlay ---------- */
.grain{
  position:fixed;inset:0;z-index:90;pointer-events:none;opacity:.038;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:180px 180px;mix-blend-mode:multiply;
}

/* ---------- Buttons ---------- */
.btn{
  display:inline-flex;align-items:center;gap:.65em;font-family:var(--sans);
  font-weight:500;font-size:var(--fs-small);letter-spacing:.015em;line-height:1.2;
  padding:1.05em 2em;border-radius:var(--radius-pill);border:1px solid transparent;cursor:pointer;
  transition:transform .35s var(--ease),box-shadow .35s var(--ease),background .3s ease,color .3s ease;
}
.btn .ico{width:1.15em;height:1.15em;fill:currentColor;flex:none}
.btn--solid{background:var(--espresso);color:var(--ivory)}
.btn--solid:hover{transform:translateY(-3px);box-shadow:var(--shadow-pill-lift)}
.btn--cream{background:var(--ivory);color:var(--espresso)}
.btn--cream:hover{transform:translateY(-3px);box-shadow:var(--shadow-pill-lift)}
/* gold pill — the primary "Termin buchen" action, hero and CTA alike.
   146deg, the same light direction as the CTA card and the arch shadows. */
.btn--gold{
  background:linear-gradient(146deg,var(--gold-soft) 0%,var(--gold) 58%,var(--gold-deep) 100%);
  color:var(--espresso);box-shadow:var(--shadow-pill);
}
.btn--gold:hover{transform:translateY(-3px);box-shadow:var(--shadow-pill-lift)}
/* outline pill on light ground — the secondary WhatsApp action in the hero */
.btn--outline{background:transparent;color:var(--espresso);border-color:rgb(var(--clay-rgb) / .38)}
.btn--outline:hover{transform:translateY(-3px);background:var(--cream);border-color:var(--gold)}
/* outline pill — the WhatsApp action on the brown CTA */
.btn--ghost{background:transparent;color:var(--ivory);border-color:rgb(var(--gold-soft-rgb) / .45)}
.btn--ghost:hover{transform:translateY(-3px);background:rgb(var(--ivory-rgb) / .09);
  border-color:var(--gold-soft)}
/* One slow light sweep across the primary pill, once, a beat after the hero has
   settled — app.js adds .btn--shine, then .is-shining, and strips both again on
   animationend so the sweep can never run twice. Nothing here without JS. */
.btn--shine{position:relative;overflow:hidden}
.btn--shine::after{
  content:"";position:absolute;top:0;bottom:0;left:0;width:44%;pointer-events:none;
  background:linear-gradient(100deg,transparent 0%,rgba(255,255,255,.6) 50%,transparent 100%);
  transform:translateX(-160%);
}
.btn--shine.is-shining::after{animation:btn-shine 1.25s cubic-bezier(.36,0,.24,1) 1 forwards}
@keyframes btn-shine{from{transform:translateX(-160%)}to{transform:translateX(320%)}}

/* line icons match the card / footer icon style rather than the solid ones */
.btn .ico--line{fill:none;stroke:currentColor;stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round;width:1.25em;height:1.25em}

/* underline link that draws a gold hairline */
.ulink{position:relative;font-size:var(--fs-small);letter-spacing:.02em;padding-bottom:.25em}
.ulink::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:var(--gold);
  transform:scaleX(0);transform-origin:left;transition:transform .5s var(--ease);
}
.ulink:hover::after,.ulink:focus-visible::after{transform:scaleX(1)}
/* on the brown pill the link needs to look like a link without a hover */
.ulink--on-dark::after{background:var(--gold-soft);transform:scaleX(1);opacity:.5;
  transition:opacity .4s ease}
.ulink--on-dark:hover::after,.ulink--on-dark:focus-visible::after{opacity:1}

/* ---------- Header ---------- */
.site-header{
  position:fixed;inset:0 0 auto;z-index:80;
  background:rgb(var(--ivory-rgb) / .86);backdrop-filter:blur(12px) saturate(1.1);
  border-bottom:1px solid var(--line);
  transition:transform .45s var(--ease);
}
.site-header.is-hidden{transform:translateY(-101%)}
.header-inner{display:flex;align-items:center;justify-content:space-between;gap:var(--s-4);
  padding-block:var(--s-4)}
.brand{
  /* ── BRAND CASING — the only place to change it (see note at top) ── */
  --brand-case:none;      /* lowercase  → "besaswiss"  */
  --brand-gap:.28em;      /* 0          → one word      */
  --brand-track:.18em;    /* letter-spacing            */
  font-family:var(--serif);font-size:clamp(1.15rem,1.05rem + .4vw,1.45rem);
  letter-spacing:var(--brand-track);text-transform:var(--brand-case);
  color:var(--espresso);line-height:1;
}
/* holds a real space (so it is read as "Besa Swiss") while the width stays
   ours to control — set --brand-space:0 and it renders as one word */
.brand .brand-sp{display:inline-block;width:var(--brand-gap);overflow:hidden}
.brand--footer{font-size:1.05rem}

/* ---------- Hero ---------- */
.hero{position:relative;padding-top:clamp(7rem,13vh,10rem);
  padding-bottom:var(--space-section);overflow:hidden}
.hero-grid{display:grid;grid-template-columns:1.06fr .94fr;gap:var(--gutter);
  align-items:center}
.hero .figure--arch{--arch-w:410px}
.hero h1{font-size:var(--fs-hero);line-height:var(--lh-tight);
  letter-spacing:var(--track-hero);margin-top:var(--s-5)}
.hero h1 .l{display:block}
.hero h1 .l--2{font-style:italic;color:var(--clay)}
.hero h1 .l--3{font-weight:500}
.hero-sub{margin-top:var(--s-5);font-size:var(--fs-body-lg);color:var(--muted);
  letter-spacing:.01em;max-width:var(--measure)}
.hero-cta{margin-top:var(--s-6);display:flex;flex-wrap:wrap;gap:var(--s-4);align-items:center}
.hero-blob{
  position:absolute;width:clamp(180px,26vw,330px);aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle at 34% 30%,rgb(var(--sand-rgb) / .6),rgb(var(--gold-rgb) / .18) 68%,transparent 72%);
  filter:blur(6px);right:-6%;top:14%;z-index:-1;
}

/* ---------- Signature shapes ---------- */
/* arch-framed image + thin gold arch outline, offset like the reference deck */
.figure{position:relative;margin:0}
.figure--arch{width:min(100%,var(--arch-w,400px));margin-inline:auto}
/* object-position is inert here and deliberately so: the baked files are
   exactly 3:4, so `cover` shows all of them. The crop lives in
   tools/prep-images.py, where it can be judged against the arch's real
   silhouette instead of guessed at in a percentage. */
.arch-img{
  width:100%;aspect-ratio:3/4;object-fit:cover;
  border-radius:var(--radius-pill) var(--radius-pill) 16px 16px;
  box-shadow:var(--shadow-photo);
  will-change:transform;
}
.arch-outline{
  position:absolute;inset:-30px -32px 42px -16px;border:1px solid var(--gold);
  border-radius:var(--radius-pill) var(--radius-pill) 18px 18px;pointer-events:none;
}
/* …and the same outline as a line that draws itself. app.js copies this exact
   geometry into the SVG path, then drops the border (.is-traced) and animates
   stroke-dashoffset to 0. Without JS the border above is all there is. */
.arch-outline .arch-line{display:block;width:100%;height:100%;overflow:visible}
.arch-outline .arch-line path{
  fill:none;stroke:var(--gold);stroke-width:1;stroke-linecap:round;
  stroke-dasharray:var(--len,0);stroke-dashoffset:var(--len,0);
}
.arch-outline.is-traced{border:0}
.arch-outline.is-drawn .arch-line path{     /* transition lives on the end state, */
  stroke-dashoffset:0;                      /* so setting --len never animates     */
  transition:stroke-dashoffset .95s cubic-bezier(.22,.68,.24,1);
}
/* wide "petal" image: two opposite corners fully rounded */
.figure--petal{width:100%}
.petal-img{
  width:100%;aspect-ratio:8/5;object-fit:cover;
  border-radius:26px 190px 26px 190px;
  box-shadow:var(--shadow-photo);will-change:transform;
}
.figcap{margin-top:var(--s-4);color:var(--mocha);text-align:center}

/* ---------- Section heads ---------- */
.shead{margin-bottom:var(--space-block)}
.shead--center{text-align:center}
.shead--center .eyebrow{justify-content:center}
/* The measure belongs on the heading, not on the wrapper: ch resolves against
   the element's OWN font, so 20ch on .shead was 20 characters of 17px Jost —
   about 210px — and it broke "Was dich erwartet" across two lines. On the h2
   the same 20ch is 20 characters of 52px Playfair, which is what was meant. */
.shead h2{font-size:var(--fs-display);line-height:var(--lh-tight);
  letter-spacing:var(--track-display);margin-top:var(--s-4);
  max-width:var(--measure-head)}
.shead--center h2{margin-inline:auto}
/* The sentence under a heading is prose, so it takes a prose measure — not the
   heading's. Scoped to :not(.eyebrow) because the eyebrow is a <p> too, and
   the old unscoped rule squeezed "Freie Termine" onto two lines at 1440px. */
.shead p:not(.eyebrow){color:var(--muted);margin-top:var(--s-4);
  max-width:var(--measure-sub)}
.shead--center p:not(.eyebrow){margin-inline:auto}

/* ---------- Intro ---------- */
.intro-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--gutter);
  align-items:center}
.intro-body{color:var(--muted);margin-top:var(--s-5);max-width:var(--measure)}
.pull{
  margin-top:var(--space-section);
  padding:clamp(var(--s-6),5vw,var(--s-8)) clamp(var(--s-5),4vw,var(--s-7));
  background:var(--cream);border-radius:clamp(28px,6vw,var(--radius-pill));text-align:center;
  position:relative;overflow:hidden;
}
/* One step below a section head on purpose: this is a pull-quote, not a
   heading, and at --fs-display the three words would break onto two lines. */
.pull h2{font-size:var(--fs-h2);letter-spacing:var(--track-h2)}
/* Clay, not gold. These full stops are text, and --gold on --cream measures
   1.9:1 — it can carry a hairline (decoration is exempt) but not a glyph.
   Clay reaches 4.69:1 and still reads as an accent against the espresso, which
   is six times darker. Measured by tools/contrast-audit.mjs, not assumed. */
.pull h2 .dot{color:var(--clay)}
.pull h2 .ph{white-space:nowrap}   /* keep each word glued to its gold full stop */
.pull p{color:var(--muted);margin-top:var(--s-4);max-width:42ch;margin-inline:auto}

/* ---------- Cards (Was dich erwartet) ---------- */
.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(var(--s-4),2vw,var(--s-5))}
.card{
  position:relative;background:var(--ivory);border:1px solid var(--line);
  border-radius:var(--radius-card);padding:clamp(var(--s-6),3vw,var(--s-7));overflow:hidden;
  box-shadow:var(--shadow-card);
  transition:transform .45s var(--ease),box-shadow .45s var(--ease);
}
.card::after{ /* gold hairline draws in on hover */
  content:"";position:absolute;left:0;right:0;top:0;height:1px;background:var(--gold);
  transform:scaleX(0);transform-origin:left;transition:transform .6s var(--ease);
}
.card:hover{transform:translateY(-7px);box-shadow:var(--shadow-card-lift)}
.card:hover::after{transform:scaleX(1)}
.card-ico{
  width:58px;height:58px;display:grid;place-items:center;margin-bottom:var(--s-5);
  background:var(--cream);border-radius:var(--radius-pill) var(--radius-pill) 10px 10px;
  color:var(--clay);
}
.card-ico svg{width:25px;height:25px;fill:none;stroke:currentColor;stroke-width:1.4;
  stroke-linecap:round;stroke-linejoin:round}
/* the same h3 as .bitem h3 — two sizes 1.8px apart was noise, not hierarchy */
.card h3{font-size:var(--fs-h3);margin-bottom:var(--s-3)}
.card p{color:var(--muted);font-size:var(--fs-small)}

/* ---------- Vorteile ---------- */
.benefits{background:var(--cream)}
.benefits-grid{display:grid;grid-template-columns:.85fr 1.15fr;gap:var(--gutter);
  align-items:center}
.blist{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-block)}
.bitem{border-top:1px solid var(--line-strong);padding-top:var(--s-4)}
/* The one place a label is not in the sans: these are serif numerals, a
   flourish, and serif figures need less tracking than uppercase sans. */
.bitem .num{font-family:var(--serif);font-size:var(--fs-caption);letter-spacing:.12em;
  color:var(--clay);display:block;margin-bottom:var(--s-3)}
.bitem h3{font-size:var(--fs-h3);margin-bottom:var(--s-2)}
.bitem p{color:var(--muted);font-size:var(--fs-small)}
.benefits .figure--arch{--arch-w:360px}

/* ---------- Ergebnisse (before/after wipe slider) ----------
   One face in one frame, a divider you drag across it, and four of them in a
   single row. The photos are somebody's skin and somebody's consent, so the
   design's job is still to frame them cleanly and get out of the way: a gold
   hairline, a small cream grip, no glow, no hover lift, no zoom.

   NOTHING CROPS HERE ANY MORE, AND THAT IS THE CHANGE.
   tools/split-results.py cuts each composite into two halves of exactly equal
   size and runs no colour operation at all. This row used to ask one thing
   more of them — a single shared ratio, .5106, reached with `object-fit:cover`
   — and the price was about a fifth of the width off each of the two
   portraits, faces included, plus one pair whose crop window had to be shoved
   off centre to keep a burnt-in date inside it. That is all gone: the images
   are `contain`, so the whole of every half is on screen and the only pixels
   ever taken off these photographs are the export margins trimmed in
   tools/prep-results.py.

   AND NOW ONE FRAME SIZE FOR ALL FOUR, WITH A PASSE-PARTOUT.
   Sizing each frame to its own pair's ratio kept every picture whole but gave
   the row four different widths, which read as an accident rather than a
   decision. Full pictures and one frame size can only both be true if the
   narrower pairs get side margins — so they get them, and they are made to
   look deliberate: the mat is the mount's own cream, the picture sits centred
   in it with an equal margin left and right, and the mat is identical on both
   halves of a pair, so the wipe crosses the whole frame while the seam only
   ever cuts the photograph. That is a passe-partout, which is how a print of
   the wrong shape has been hung in a standard frame for two centuries. */
/* THE RAIL owns every number the row is built from, and the row, the two
   arrows and the dots all read them from here. It has to be this element and
   not the row: the row is a scroll container, so an arrow inside it would
   scroll away with the frames — the arrows are the rail's children instead,
   and an arrow can only sit on the vertical middle of a FRAME if --frame-h
   and --chrome are in scope where it lives. */
.results-rail{
  position:relative;
  /* A BLOCK FORMATTING CONTEXT, AND IT IS LOAD-BEARING.
     The row carries margin-block:-2px -32px to hand back the room it reserves
     for the mounts' shadow. Without this the first of those two collapses
     THROUGH the rail — moving the rail itself up rather than the row inside it
     — and the frames end up 2px below where the rail's own coordinate system
     says they are, which is exactly 2px of misalignment on the arrows that
     centre themselves on a frame. flow-root contains both margins so the
     arithmetic in .rail-arrow's `top` is true. */
  display:flow-root;

  /* ONE BOX, FOUR PICTURES — THAT IS THE WHOLE LAYOUT.
     Same width and same height on every frame; each pair's picture drawn whole
     inside it and matted to fit. The four --ar values live on the figures in
     index.html, because they are per-pair data and not a design decision: they
     are the exported halves' own width/height, printed by the last block of
     `python3 tools/split-results.py`. Do not type them by hand.
     .640777  .663900  .510622  .515538 — in the row's order.

     --ar-frame IS THE WIDEST OF THOSE FOUR, AND IT HAS TO STAY THAT WAY.
     The box is cut to the widest pair, so that pair fills its frame edge to
     edge and no picture is ever asked to be wider than its box. Set it below
     the maximum and the widest pair would have to be scaled down to fit, which
     is the one thing this row does not do. If a pair is re-exported or added,
     this is max(--ar) over the figures — re-check it. */
  --ar-frame:.663900;      /* stirn-augen's, the widest of the four */
  /* What a frame's width is actually derived from. It is --ar-frame here and
     twice that without JavaScript, where both halves are on screen at once. */
  --ar-box:var(--ar-frame);
  --frame-pad:clamp(var(--s-3),1.2vw,var(--s-4));
  --row-gap:var(--s-5);
  --chrome:calc(2 * var(--frame-pad) + 2px);   /* the mount's padding + its hairline */
  --row-pad-x:8px;         /* shadow room inside the scroller — see `padding` */

  /* WIDER THAN .wrap, AND THE ONLY THING ON THE PAGE THAT IS.
     .wrap is the 1160px text column. Four portraits inside it come out
     needlessly small, so the row lives outside .wrap in index.html and takes
     the wider measure. It is a variable because the height is derived from it
     below, and the two must not be able to drift apart. */
  --row-w:min(1400px,90vw);
  width:var(--row-w);margin-inline:auto;

  /* THE HEIGHT — TWO CANDIDATES, THE SMALLER WINS.
     · --h-window: a frame may never be taller than the window it is in, or a
       laptop in landscape gets a column whose ends cannot be seen at once.
     · --h-width-cap: a frame may never be wider than --w-frame-max, or a whole
       picture does not fit on a phone at all — which is the very thing this
       layout exists to fix. That cap is a width, so it comes back as the
       height it implies.

     THERE IS NO THIRD CANDIDATE ANY MORE, AND THAT IS THE TRADE THIS MAKES.
     There used to be a --h-row-fit: the height at which all four frames, their
     mounts and the three gaps add up to exactly the row, applied from 1100px
     up so that a desktop saw the whole set at once. Equal frames cost that.
     Four boxes at the widest ratio need 2.6556 heights of width against the
     2.3308 the four ragged ones needed, and at 1440 that solves to a 403px
     frame — under the ~480px floor below which skin stops being legible, which
     is the point at which the pictures stop doing their job. Even at the row's
     full 1400px measure it only reaches 442px, so there is no viewport where
     the fit is worth having. The row therefore scrolls at EVERY width, exactly
     as it already did below 1100px, and the frames take the generous height
     instead: 594px at a 900px-tall window, against the 459px the fitted row
     managed. Two and nine tenths frames are visible at 1440 — the cut through
     the third is what says there is more this way, the same hint the phone
     layout has always used. */
  --h-window:clamp(340px,66vh,600px);
  /* 74vw, not the 80vw this was when the frames were ragged: back then the
     first frame in the row was one of the narrow pairs and the next one peeked
     in past it by itself. All four are the widest pair's width now, so the
     peek has to be paid for here — at 390px this leaves 30px of the second
     frame showing, and that peek is the whole "there is more this way". It is
     also why there are still no arrows. */
  --w-frame-max:min(74vw,440px);
  --h-width-cap:calc((var(--w-frame-max) - var(--chrome)) / var(--ar-box));
  --frame-h:min(var(--h-window),var(--h-width-cap));
}

/* The scroller itself. It fills the rail, so the measure is set once, above. */
.results-row{
  width:100%;

  /* max-content tracks: each figure has already worked its own width out, and
     the track is simply that. Nothing here divides the row into four, so a
     fifth pair added to index.html would just appear. */
  display:grid;grid-auto-flow:column;grid-auto-columns:max-content;
  gap:var(--row-gap);align-items:start;
  /* Never a second line and never a 2x2: one row at every width, and when it
     stops fitting it scrolls instead of shrinking. */
  overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;
  -webkit-overflow-scrolling:touch;
  /* The row overflows at every width now, so this always resolves to `start`.
     It stays because it is the correct pair of declarations either way, and
     because centring an overflowing scroller would put its first frame out of
     reach: `safe` is exactly that condition. A browser that does not know the
     keyword drops the second declaration and keeps `start`, which is the half
     that can never strand a frame. */
  justify-content:start;
  justify-content:safe center;
  scrollbar-width:none;-ms-overflow-style:none;
  /* An overflow container clips its children's ink as well as their boxes, so
     the mounts' shadow needs room inside it: 30px below, 8px to the sides, per
     --shadow-card. The negative margins hand that room straight back, so the
     row still sits on the page's rhythm and not 32px below it. */
  padding:2px var(--row-pad-x) 32px;margin-block:-2px -32px;
}
.results-row::-webkit-scrollbar{width:0;height:0;display:none}
/* The row is a scrollable region, so it is a tab stop and the arrow keys move
   it. Reaching the fourth slider by Tab works either way — focusing a handle
   scrolls it into view — but a keyboard user who only wants to LOOK should not
   have to take the handle to do it. */
.results-row:focus-visible{outline-offset:6px}

/* ---- The rail's controls: two arrows and a row of dots ----
   THE PAGE HAS ONE BUTTON VOCABULARY, SO THESE ARE THE HANDLE'S RECIPE.
   Cream fill, a gold hairline, a clay chevron, the same pill shadow — the
   compare handle already is that, and inventing a second round-button style
   for the same page would read as two designs. What separates them is the
   glyph and the place: ONE chevron at the outer edge of the row against the
   handle's TWO in the middle of a picture. That is also the semantic
   difference — one moves the row, two move a seam.

   They are built in app.js and are absent without it. The row still scrolls
   with a finger or a trackpad in that build, but a <button> whose whole job is
   to call scrollBy would be a dead control, which is the same reason the
   compare divider is not in the no-JS layout either.

   Hidden, both of them, whenever the row is not actually scrollable: an arrow
   that cannot move anything and a position indicator for a set that is fully
   on screen are both furniture. app.js puts .is-scrollable on the rail when
   scrollWidth exceeds clientWidth, and takes it off when it stops. */
.rail-arrow,.rail-dots{display:none}
.results-rail.is-scrollable .rail-arrow{display:grid}
.results-rail.is-scrollable .rail-dots{display:flex}

.rail-arrow{
  position:absolute;z-index:4;
  /* the vertical middle of the FRAME, not of the rail — the rail is taller by
     the caption and the dots. This lands on exactly the line the compare
     handles sit on, because that is frame-pad + border + half the picture,
     which is what this resolves to. */
  top:calc((var(--frame-h) + var(--chrome)) / 2);
  transform:translateY(-50%);
  width:44px;height:44px;place-items:center;padding:0;
  background:var(--cream);border:1px solid var(--gold);border-radius:50%;
  color:var(--clay);cursor:pointer;
  box-shadow:var(--shadow-pill);
  transition:background .3s var(--ease),box-shadow .3s var(--ease),
             opacity .3s var(--ease);
}
.rail-arrow:hover:not(:disabled){background:var(--ivory);box-shadow:var(--shadow-pill-lift)}
/* Faded and inert at the ends. Disabled rather than aria-disabled, so it also
   leaves the tab order — app.js hands focus to the other arrow if the one
   under it is the one that just went dead. */
.rail-arrow:disabled{opacity:.3;cursor:default;box-shadow:none}
.rail-arrow svg{
  width:11px;height:16px;fill:none;stroke:currentColor;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;
}
/* Below 1100px the row very nearly is the viewport, so the arrows sit on its
   own edge and overlap the outermost frame. There is nowhere else for them:
   at 390 the row leaves 19px either side and a 44px button does not fit in
   it. The overlap is 44px at the extreme edge of a frame, well clear of where
   a divider drag starts. */
.rail-arrow--prev{left:0}
.rail-arrow--next{right:0}
@media (min-width:1100px){
  /* From here the page has margin to spare — at 1440 the row leaves 72px a
     side — so the arrows step off the pictures entirely and straddle the edge
     of the row instead, which is also where the eye expects a rail control. */
  .rail-arrow{width:48px;height:48px}
  .rail-arrow--prev{left:-24px}
  .rail-arrow--next{right:-24px}
}

/* WHICH FRAME IS CENTRED, AND NOTHING ELSE. These are an indicator, not a
   control: the arrows move the row and the row itself is a focusable scroll
   region, so a second set of tap targets would only add ambiguity — and a dot
   is far below the 44px a touch target owes the finger. aria-hidden for the
   same reason: the position is already carried by the arrows going dead at
   the ends, and four unlabelled dots in the accessibility tree are noise.
   One per figure, counted from the DOM in app.js — a fifth pair gets a fifth
   dot without anyone editing this file. */
.rail-dots{
  justify-content:center;align-items:center;gap:var(--s-2);
  margin-top:var(--s-5);
}
.rail-dot{
  width:6px;height:6px;border-radius:var(--radius-pill);
  background:var(--sand);
  transition:width .45s var(--ease),background .45s var(--ease);
}
/* The active one stretches into a small pill rather than growing or darkening
   alone: the page is full of pills, and a change of SHAPE reads at a glance
   at 6px where a change of tone does not. */
.rail-dot.is-on{width:20px;background:var(--clay)}

/* Every figure is the same box: the shared ratio times the one shared height,
   plus the mount's padding and hairline. --plate-w is the width the PICTURE
   gets inside that box — its own pair's ratio times the same height — and the
   difference between the two is the mat, half of it either side. */
.result{
  margin:0;scroll-snap-align:center;
  --plate-w:calc(var(--ar) * var(--frame-h));
  width:calc(var(--ar-box) * var(--frame-h) + var(--chrome));
}
/* The cream mount. Was a link to the full-size JPEG and is not any more: the
   slider IS the interaction now, and a lightbox that swallowed the first
   pointerdown of every drag would fight it for the same gesture. */
.result-frame{
  width:100%;
  background:var(--cream);border:1px solid var(--line);
  border-radius:var(--radius-card);padding:var(--frame-pad);
  box-shadow:var(--shadow-card);
}
/* Without JavaScript there is no divider, so both halves are on screen at once
   and the frame is twice as wide for the same height. --ar-box doubles — the
   literal is 2 x --ar-frame, written out because a calc() cannot be divided by
   — and --w-frame-max opens up, because a double-wide frame at 74vw would be a
   very short one. These two, the object-fit below and two lines in the print
   block are the whole of what keys off html:not(.js). */
html:not(.js) .results-rail{--ar-box:1.327801;--w-frame-max:min(88vw,780px)}

/* ---- The slider ----
   BASE = the no-JS layout: the two halves side by side, plain, no interaction
   and no dead controls. html.js is set in <head> before the first paint, so a
   scripted browser never renders this state; the .js rules below are what it
   gets. The chips land on the outer corners either way. */
.compare{
  position:relative;display:flex;gap:1px;background:var(--line);
  border-radius:16px;overflow:hidden;
  /* The one box, on every figure in the row. */
  width:100%;height:var(--frame-h);
}
.compare>.compare-plate,.compare>.compare-top{flex:1 1 0;min-width:0}
.compare-top{order:-1}                 /* vorher left, though it is second in the DOM */
/* The plate is the picture's own box, and the mat is what is left of the frame
   around it. Cream, because that is the mount it is cut into — a passe-partout
   is one board, not a lighter panel set inside one. */
.compare-plate{position:relative;height:100%;background:var(--cream)}
.compare-top>.compare-plate{width:100%}
.compare-img{display:block;width:100%;height:100%;-webkit-user-drag:none}
.compare-divider{display:none}
/* contain, never cover, at every width and in both builds: the picture is
   drawn whole into the plate and the frame's cream takes up the difference. */
html:not(.js) .compare-img{object-fit:contain}

/* touch-action:pan-y IS THE RULE THAT KEEPS THE ROW AND THE DIVIDER APART.
   The row scrolls sideways and the divider is dragged sideways, and on a touch
   screen that is one gesture asking two things. This says: inside a picture, a
   horizontal drag is the divider's and nothing else's; a vertical one is still
   the page's. The row is left to scroll from the gaps and the cream mounts,
   which is the only surface that has no divider under it.

   It has to be static CSS. The browser resolves touch-action when the finger
   lands, so a class added in a pointerdown handler is already too late for the
   gesture it was meant to catch. app.js's setPointerCapture is the other half:
   it keeps a drag tracking after it leaves the frame. */
.js .compare{
  display:block;background:var(--cream);
  cursor:ew-resize;touch-action:pan-y;
  user-select:none;-webkit-user-select:none;
}
/* THE MAT, AND WHY IT IS A BOX AND NOT JUST `contain`'s LETTERBOX.
   Letting object-fit centre the picture inside a full-frame <img> would mat it
   correctly and cost nothing — but the picture would then be a bare rectangle,
   while the pair that fills its frame would keep the 16px corner .compare
   clips, and a third case (a mat narrower than that radius) would get its
   corners shaved by the curve. Three corner treatments in one row of four.
   So the picture gets its own box instead: --plate-w wide, the full height,
   centred by `inset:0` plus `margin-inline:auto` — over-constrained on purpose,
   which is precisely how CSS is told to split the remainder equally — and the
   radius lives on THAT. Same rounded corner in all four frames, mat or no mat.

   Both plates of a pair resolve from the same two custom properties, so they
   are the same box to the pixel: the halves cannot shift under the wipe.
   The clip-path stays on .compare-top, which is the whole frame — so the
   divider travels the full width, and where it crosses the mat there is
   nothing to see, because the mat under it is the same cream on both halves. */
.js .compare-plate{
  position:absolute;inset:0;margin-inline:auto;width:var(--plate-w);
  border-radius:16px;overflow:hidden;
}
.js .compare-top{position:absolute;inset:0}
.js .compare-img{width:100%;height:100%;object-fit:contain}
/* The reveal. "Nachher" fills the frame; "Vorher" lies on top and is clipped
   back from the right. Both sides are painted once and never re-laid-out —
   only this one property changes per frame. */
.js .compare-top{clip-path:inset(0 50% 0 0);will-change:clip-path}
.js .compare-divider{display:block}

/* Vorher / Nachher. The page's eyebrow recipe on a translucent ivory pill —
   not the reference's dark glass, and not monospace. Each chip fades out once
   its own side has narrowed past 12% — app.js sets the opacity.

   THE PILL IS OPAQUE, AND THAT IS A MEASUREMENT, NOT A PREFERENCE.
   besaswiss-compare-slider.md asked for a TRANSLUCENT ivory pill. It was tried
   at .88 and at .92 and tools/contrast-audit.mjs failed it both times — 3.58:1
   at 390px over the dark room behind the first pair, against the 4.5 an 11px
   label needs. The failure is not about the alpha. It is geometry: the audit
   samples nine points across the chip's BOX, and on a capsule the corner point
   lands within a tenth of a pixel of the rounded rim, where the pixel is half
   pill and half photograph whatever the fill is. Solve for it and the point
   sits on the rim at every alpha and every size; a 6px radius would move it
   clear, but that is no longer a pill.
   An opaque fill plus the 2px opaque ring makes a solid ivory annulus straight
   through that rim, so the sample is ivory-to-ivory and the ratio stops
   depending on which photograph is underneath: clay on ivory, 5.85:1, the same
   number in every frame. The shape and the colour the brief asked for survive;
   only the transparency does not. Do not lower it back without re-running the
   audit at 390 — that is the width it fails at first. */
.compare-chip{
  position:absolute;top:var(--s-3);z-index:2;pointer-events:none;
  font-size:var(--fs-label);letter-spacing:var(--track-label);
  text-transform:uppercase;font-weight:500;line-height:1;color:var(--clay);
  background:var(--ivory);border:0;border-radius:var(--radius-pill);
  /* tracking leaves a --track-label gap after the last letter too */
  padding:.62em .85em .55em;padding-right:calc(.85em - var(--track-label));
  box-shadow:0 0 0 2px var(--ivory), 0 2px 10px -4px rgb(var(--shade) / .5);
  transition:opacity .3s ease;
}
.compare-chip--before{left:var(--s-3)}
.compare-chip--after{right:var(--s-3)}

/* The divider: 1px of gold, the same hairline weight as the arch outlines and
   the rule after every eyebrow. No glow — the grip is what says "drag me", and
   a lit line laid over a photograph of skin would read as an edit to it. */
.compare-divider{
  position:absolute;top:0;bottom:0;left:50%;width:1px;margin-left:-.5px;
  background:var(--gold);z-index:3;pointer-events:none;will-change:left;
}
.compare-handle{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:44px;height:44px;display:grid;place-items:center;padding:0;
  background:var(--cream);border:1px solid var(--gold);border-radius:50%;
  color:var(--clay);cursor:ew-resize;pointer-events:auto;
  box-shadow:var(--shadow-pill);
  transition:background .3s var(--ease),box-shadow .3s var(--ease);
}
.compare-handle:hover{background:var(--ivory);box-shadow:var(--shadow-pill-lift)}
.compare-handle svg{
  width:18px;height:14px;fill:none;stroke:currentColor;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;
}

/* Only where a date is actually legible in the photograph. Two of the four
   carry none, and inventing a timeframe for those would be a caption that
   makes a claim the picture does not. */
.result-cap{
  margin-top:var(--s-4);text-align:center;color:var(--muted);
  font-size:var(--fs-label);letter-spacing:var(--track-label);
  text-transform:uppercase;font-weight:500;line-height:1.4;
}
.results-note{
  margin-top:var(--space-block);text-align:center;
  font-size:var(--fs-caption);color:var(--muted);
}
/* ---------- Kundenstimmen ---------- */
/* Belt and braces on the toggle in index.html. The UA rule [hidden]{display:none}
   is beaten by any author rule that sets display — that is exactly how the
   booking note once stayed on screen — so the section states it itself. */
.testimonials[hidden]{display:none}
.tcards{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(var(--s-4),2vw,var(--s-5))}
.tcard{
  position:relative;background:var(--cream);border:1px solid var(--line);
  border-radius:var(--radius-card);padding:clamp(var(--s-6),3vw,var(--s-7));
  overflow:hidden;box-shadow:var(--shadow-card);
  transition:transform .45s var(--ease),box-shadow .45s var(--ease);
}
.tcard::after{ /* the same gold hairline as .card — one hover treatment on the page */
  content:"";position:absolute;left:0;right:0;top:0;height:1px;background:var(--gold);
  transform:scaleX(0);transform-origin:left;transition:transform .6s var(--ease);
}
.tcard:hover{transform:translateY(-7px);box-shadow:var(--shadow-card-lift)}
.tcard:hover::after{transform:scaleX(1)}
.tquote{
  font-family:var(--serif);font-size:var(--fs-h3);line-height:var(--lh-h3);
  letter-spacing:var(--track-h3);color:var(--espresso);
}
.tname{
  margin-top:var(--s-5);color:var(--clay);
  font-size:var(--fs-label);letter-spacing:var(--track-label);
  text-transform:uppercase;font-weight:500;line-height:1.4;
}

/* ---------- CTA ---------- */
.cta-wrap{scroll-margin-top:4.5rem}   /* clears the fixed header */
.cta{
  position:relative;background:var(--brown-grad);color:var(--ivory);text-align:center;
  border-radius:clamp(32px,7vw,var(--radius-pill));
  padding:clamp(var(--s-7),7vw,var(--s-9)) clamp(var(--s-5),5vw,var(--s-8));
  overflow:hidden;
}
/* Light and shade, from ONE direction. The gradient runs 146deg; both layers
   here agree with it — a warm key light off the upper-left corner, and the
   ground falling away into the lower-right. Two layers on one pseudo-element,
   not two elements: this is depth, not an effect. */
.cta::before{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:
    radial-gradient(58% 62% at 14% -6%,rgb(var(--gold-soft-rgb) / .30),transparent 68%),
    radial-gradient(72% 84% at 96% 112%,rgb(var(--espresso-rgb) / .30),transparent 66%);
}
.cta-inner{position:relative;max-width:44ch;margin-inline:auto}
/* the heading opens the card — the padding above it is the whole spacing */
.cta h2{color:var(--ivory);font-size:var(--fs-display);line-height:var(--lh-tight);
  letter-spacing:var(--track-display);margin-top:0}
.cta-actions{margin-top:var(--s-6);display:flex;flex-direction:column;align-items:center;
  gap:var(--s-5)}
.cta-actions .ulink{color:var(--ivory)}
/* both pills share one width so the stack reads as one column, not a ragged pair */
.cta-buttons{display:flex;flex-direction:column;align-items:center;gap:var(--s-3);width:100%}
.cta-buttons .btn{width:min(100%,20rem);justify-content:center}
.cta-note{color:var(--on-brown);font-size:var(--fs-caption);letter-spacing:.02em}
/* clay is too dim against the brown card for a focus ring */
.cta :focus-visible{outline-color:var(--gold-soft)}

/* ---------- Buchung (Cal.com Inline-Embed) ---------- */
/* The last section of the page and the target of every "Termin buchen"
   anchor. scroll-margin-top clears the fixed header when one lands here. */
.booking{scroll-margin-top:4.5rem}
/* Kept tight: the calendar should follow its heading closely, not sit a screen
   under it. The heading's own measure needs no override — 20ch of Playfair
   holds "Direkt online buchen" on one line. */
.booking .shead{margin-bottom:var(--s-6)}
/* Inside the frame it stays Cal.com's look; the frame itself is ours — a soft
   cream card with a little whitespace, so the embed reads as part of the page.
   Unlike the old Google iframe this one needs no fixed height: Cal.com measures
   its own content and resizes, and it reflows to the width it is given — which
   is why the card may now run to 1000px instead of 960px. */
.booking-frame{
  width:min(100%,1000px);margin-inline:auto;
  background:var(--cream);border:1px solid var(--line);
  border-radius:var(--radius-card);padding:clamp(var(--s-2),1.4vw,var(--s-3));
  box-shadow:var(--shadow-card);
  overflow:hidden;
}
/* Height is Cal.com's business, not ours: <cal-inline> measures the booker and
   writes the iframe height itself (~570px for a month view on desktop, taller
   on a phone where it stacks, taller again once the booking form opens). We
   only set a floor so the card does not collapse to nothing while the embed
   boots — forcing a taller box just parks dead white space under the calendar,
   and forcing height:100% makes Cal misread the frame entirely. */
.cal-inline{
  width:100%;min-height:560px;overflow:auto;
  border-radius:22px;background:var(--paper);
}
.cal-inline>*{max-width:100%}
/* Shown in place of the calendar while CAL_LINK is still a placeholder. An
   empty white rectangle reads as a broken page; a sentence does not. */
.booking-note{
  padding:clamp(var(--s-6),7vw,var(--s-8)) var(--s-5);text-align:center;
  color:var(--muted);max-width:44ch;margin-inline:auto;
}
.booking-fallback{
  margin-top:var(--s-5);text-align:center;font-size:var(--fs-caption);
  color:var(--muted);max-width:var(--measure-sub);margin-inline:auto;
}

/* ---------- Footer ---------- */
.site-footer{border-top:1px solid var(--line);padding-bottom:var(--s-6)}
.footer-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:var(--gutter);
  align-items:start}
.motto{font-family:var(--serif);font-style:italic;font-size:var(--fs-lead);
  line-height:var(--lh-lead);letter-spacing:var(--track-lead);
  color:var(--clay);max-width:22ch}
.motto span{display:block}
.contact{display:grid;gap:var(--s-5)}
.crow{display:flex;align-items:center;gap:var(--s-4)}
.crow .ci{width:44px;height:44px;flex:none;display:grid;place-items:center;border-radius:50%;
  background:var(--cream);color:var(--clay)}
.crow .ci svg{width:19px;height:19px;fill:none;stroke:currentColor;stroke-width:1.5;
  stroke-linecap:round;stroke-linejoin:round}
.crow small{display:block}
.crow a,.crow p{font-size:var(--fs-body-lg);color:var(--espresso)}
.foot-bottom{
  margin-top:var(--space-block);padding-top:var(--s-5);border-top:1px solid var(--line);
  display:flex;justify-content:space-between;flex-wrap:wrap;gap:var(--s-3);
  font-size:var(--fs-caption);color:var(--muted);align-items:center;
}

/* ---------- Motion: reveal + split text + parallax ---------- */
/* Everything that starts out hidden hides only once app.js has confirmed it is
   there to bring it back (the inline snippet in <head> sets html.js before the
   first paint). Scripts blocked → the page renders plain and complete. */
.js .reveal{opacity:0;transform:translateY(20px);
  transition:opacity .85s ease var(--d,0s),transform .85s var(--ease) var(--d,0s)}
.reveal.is-in{opacity:1;transform:none}

[data-split] .w{display:inline-block}
[data-split] .wi{display:inline-block;opacity:0;transform:translateY(.36em);
  transition:opacity .7s ease var(--d,0s),transform .8s var(--ease) var(--d,0s)}
[data-split].is-in .wi{opacity:1;transform:none}

/* ---------- Hero entrance ----------
   The hero does not wait for the scroll observer: app.js starts it on load and
   the delays below are the whole choreography — eyebrow, then the three
   headline lines, then the photo wipes up out of its arch, and the gold outline
   traces itself last (that delay lives in app.js as data-arch-delay).
   Deliberately slow: it should read as one calm move, not three effects.       */
.hero .eyebrow{--d:.04s}
.hero-sub{--d:.58s}
.hero-cta{--d:.68s}
.hero .figure--arch{--d:.5s}

.js .hero-lines .l{
  opacity:0;transform:translateY(.34em);filter:blur(4px);
  transition:opacity .8s ease var(--d,0s),transform .95s var(--ease) var(--d,0s),
             filter .8s ease var(--d,0s);
}
.js .hero-lines.is-in .l{opacity:1;transform:none;filter:blur(0)}
.hero-lines .l--1{--d:.18s}
.hero-lines .l--2{--d:.30s}
.hero-lines .l--3{--d:.42s}

/* The photo reveals by uncovering itself from the bottom edge up, so the arch
   fills rather than slides. clip-path is used precisely because the parallax in
   app.js owns this element's transform. The figure itself does not rise — the
   wipe is the reveal. */
.js .hero .figure--arch{opacity:1;transform:none}
.js .hero .arch-img{
  clip-path:inset(100% 0 0 0);
  /* eased in as well as out: an expo curve (the house --ease) would uncover
     four fifths of the photo in the first quarter second and then crawl */
  transition:clip-path 1s cubic-bezier(.42,.08,.18,1) var(--d,.5s);
}
.js .hero .figure--arch.is-in .arch-img{clip-path:inset(0 0 0 0)}

/* ---------- Responsive ---------- */
@media (max-width:960px){
  .hero-grid,.intro-grid,.benefits-grid,.footer-grid{grid-template-columns:1fr}
  .hero-grid,.benefits-grid{gap:var(--s-7)}
  .hero .figure--arch{--arch-w:340px}
  .benefits .figure--arch{--arch-w:320px}
  .cards,.tcards{grid-template-columns:1fr}
  .foot-bottom{flex-direction:column;align-items:flex-start}
  .hero-blob{display:none}          /* would sit behind the headline once stacked */
  /* stacked, every head measures against the full column, so the head measure
     would otherwise leave the sub-line hanging much wider than its heading */
  .shead,.lead,.hero-sub{max-width:none}
}
@media (max-width:640px){
  /* Cal.com stacks its own layout this narrow and grows well past the floor;
     the floor only drops so a slow embed does not hold open a blank screenful */
  .booking-frame{border-radius:20px;padding:var(--s-1)}
  .cal-inline{min-height:460px;border-radius:15px}
  .blist{grid-template-columns:1fr}
  /* The mount's own padding is most of what separates a 390px frame from the
     screen edge, so it comes down with everything else here. */
  .result-frame{border-radius:20px}
  .hero{padding-top:6.5rem}
  .hero .figure--arch{--arch-w:290px}
  .arch-outline{inset:-18px -20px 30px -10px}
  .petal-img{border-radius:20px 120px 20px 120px}
  .btn{width:100%;justify-content:center}
  .hero-cta{gap:var(--s-3)}
}
@media (max-width:520px){
  /* hairlines around a centred eyebrow only make it wrap awkwardly this narrow */
  .eyebrow--center::before,.eyebrow--center::after{display:none}
}
@media (max-width:380px){
  .wrap,.results-row{width:92vw}
  .card{padding:var(--s-5) var(--s-4)}
}

/* ---------- Reduced motion: everything calms down ---------- */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{transition:none!important;animation:none!important}
  .reveal,[data-split] .wi,.hero-lines .l{opacity:1!important;transform:none!important;
    filter:none!important}
  /* no hero sequence at all: the photo simply is there, unclipped */
  .hero .arch-img{clip-path:none!important}
  .btn--shine::after{display:none}
  .btn:hover,.tcard:hover{transform:none}   /* the pills and cards lift on hover — not here */
  .site-header.is-hidden{transform:none}
  .arch-img,.petal-img{transform:none!important}
  /* arches never trace: the plain border stays, fully drawn and still */
  .arch-outline,.arch-outline.is-traced{border:1px solid var(--gold)}
  .arch-outline .arch-line{display:none}
  .grain{display:none}
}

/* ---------- Print ---------- */
@media print{
  .site-header,.grain,.hero-blob,.booking-frame{display:none}  /* the embed prints blank */
  /* Paper has no sideways scroll, so the row stops being one: the four go back
     to a column and each gets the full width of the page. */
  .results-rail{width:auto}
  .results-row{display:block;width:auto;overflow:visible;padding:0;margin-block:0}
  .rail-arrow,.rail-dots{display:none!important}   /* paper does not scroll */
  .result{width:auto}
  .result+.result{margin-top:var(--s-7)}
  .compare{width:auto;height:auto}
  html:not(.js) .compare-img{height:auto;object-fit:fill}
  /* A wipe slider cannot be printed — paper has no divider to drag. So on
     paper it falls back to exactly what it is made of: the two halves, side
     by side, which is the same thing a visitor without JavaScript sees.
     No mat either: a half is the picture's own shape again, so there is
     nothing left over to mat with. */
  .js .compare{display:flex}
  .js .compare>.compare-plate,.js .compare-top{position:static;flex:1 1 0;min-width:0}
  .compare-plate,.js .compare-plate{position:static;width:auto;height:auto;
    border-radius:0;background:none}
  .js .compare-img{height:auto;object-fit:fill}
  .js .compare-top{clip-path:none}
  .compare-divider{display:none!important}
  .result-frame{width:auto}
  .result-frame,.tcard{box-shadow:none}
  body{background:#fff}
  .cta{background:none;color:#000;border:1px solid #999}
  .cta h2,.cta-actions .ulink{color:#000}
}
