/* ============================================================
   Grill English — site stylesheet
   Tokens sourced from Grill English Design System/colors_and_type.css; fonts
   self-hosted locally, no CDN dependency.
   ============================================================ */

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

@font-face {
  font-family: 'Inter';
  src: url('../fonts/Inter-VariableFont_opsz_wght.ttf') format('truetype');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('../fonts/Inter-Italic-VariableFont_opsz_wght.ttf') format('truetype');
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: 'Raleway';
  src: url('../fonts/Raleway-VariableFont_wght.ttf') format('truetype');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Raleway';
  src: url('../fonts/Raleway-Italic-VariableFont_wght.ttf') format('truetype');
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: 'Lato';
  src: url('../fonts/Lato-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Lato';
  src: url('../fonts/Lato-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Lato';
  src: url('../fonts/Lato-Italic.ttf') format('truetype');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
:root {
  --color-dark-1:  #000000;
  --color-light-1: #FFFFFF;
  --color-dark-2:  #404040;
  --color-light-2: #F2F2F2;

  --color-accent-1: #3F6381;
  --color-accent-2: #8C3A3A;
  --color-accent-3: #5B8FA8;
  --color-accent-4: #C4A35A;

  --color-hyperlink:     #1F3864;
  --color-followed-link: #4E4039;

  --bg:           var(--color-light-1);
  --bg-secondary: var(--color-light-2);
  --fg:           var(--color-dark-1);
  --fg-secondary: var(--color-dark-2);
  --accent:       var(--color-accent-1);
  --accent-alt:   var(--color-accent-2);
  --link:         var(--color-hyperlink);
  --link-visited: var(--color-followed-link);

  --font-heading:    'Raleway', Arial, sans-serif;
  --font-body:       'Lato', Arial, sans-serif;
  --font-brand-name: 'Inter', Arial, sans-serif;

  --text-title:    26.67px;
  --text-subtitle: 21.33px;
  --text-body:     16px;
  --text-caption:  13.33px;

  --weight-regular:  400;
  --weight-semibold: 600;
  --weight-bold:     700;

  --leading-title: 1.2;
  --leading-body:  1.6;

  --content-max: 1100px;
  --content-prose: 65ch;
}

html { scroll-behavior: smooth; }

/* The page content sits ABOVE the trees layer. Position + z-index, not source
   order, is what decides it against a fixed element. */
main { position: relative; z-index: 1; }

html body {
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: var(--weight-regular);
  color: var(--fg);
  background: var(--bg);
  line-height: var(--leading-body);
  margin: 0;
  -webkit-font-smoothing: antialiased;
}

h1 {
  font-family: var(--font-heading);
  font-size: var(--text-title);
  font-weight: var(--weight-bold);
  line-height: var(--leading-title);
  margin: 0 0 0.5em;
}
h2 {
  font-family: var(--font-heading);
  font-size: var(--text-subtitle);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-title);
  margin: 1.5em 0 0.5em;
}
h3 {
  font-family: var(--font-heading);
  font-size: var(--text-body);
  font-weight: var(--weight-semibold);
  margin: 0 0 0.25em;
}
p { margin: 0 0 1em; }
.caption {
  font-size: var(--text-caption);
  color: var(--fg-secondary);
}
.brand-name { font-weight: var(--weight-bold); }

a { color: var(--link); text-decoration: underline; }
a:visited { color: var(--link-visited); }
a:hover { text-decoration: none; }

ul, ol { padding-left: 1.25em; }
li { margin-bottom: 0.4em; }

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

/* ── Layout ─────────────────────────────────────────────── */
.container {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 0 24px;
}

/* ── Prose (long-form body copy) ────────────────────────── */
.prose { max-width: var(--content-prose); }

/* ── Skip link (a11y) ───────────────────────────────────── */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--fg);
  color: #fff;
  padding: 10px 16px;
  z-index: 100;
}
.skip-link:focus { left: 0; }

/* ── Header / nav ───────────────────────────────────────── */
.site-header {
  border-bottom: 1px solid var(--bg-secondary);
}
.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding-top: 16px;
  padding-bottom: 16px;
}
.brand-lockup {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  color: var(--fg);
}
.brand-lockup img {
  height: 40px;
  width: auto;
}
.brand-lockup span {
  font-family: var(--font-brand-name);
  font-size: var(--text-subtitle);
  font-weight: var(--weight-bold);
}

.nav-toggle-input { position: absolute; opacity: 0; pointer-events: none; }
.nav-toggle-label { display: none; cursor: pointer; }

.site-nav {
  display: flex;
  gap: 28px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.site-nav a {
  color: var(--fg);
  text-decoration: none;
  font-size: 14px;
  font-weight: var(--weight-bold);
}
.site-nav a:hover { color: var(--accent); }
.site-nav a.current { color: var(--accent); }

.active-home .nav-home,
.active-llm .nav-llm,
.active-about .nav-about,
.active-contact .nav-contact {
  color: var(--accent);
  text-decoration: underline;
}

@media (max-width: 1100px) {
  .nav-toggle-label {
    display: flex;
    align-items: center;
  }
  .site-nav {
    display: none;
    flex-direction: column;
    gap: 4px;
    width: 100%;
    padding: 0 24px 16px;
  }
  .nav-toggle-input:checked ~ .site-nav {
    display: flex;
  }
  .site-header .container {
    flex-wrap: wrap;
  }
  .site-nav a {
    display: block;
    padding: 10px 0;
    border-top: 1px solid var(--bg-secondary);
  }
}

/* ── Hero ───────────────────────────────────────────────── */
.hero {
  background: var(--bg-secondary);
  padding: 56px 0;
}
.hero h1 { max-width: 640px; }
.hero p {
  max-width: 560px;
  color: var(--fg-secondary);
  font-size: var(--text-subtitle);
}

/* ── Section ────────────────────────────────────────────── */
.section { padding: 48px 0; }
.section-tight { padding: 24px 0; }

/* ── Card grid ──────────────────────────────────────────── */
.card-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}
.card {
  flex: 1 1 260px;
  border: 1px solid var(--bg-secondary);
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  text-decoration: none;
  color: inherit;
}
.card .card-icon {
  width: 32px;
  height: 32px;
  color: var(--accent);
}
.card h3 { font-size: var(--text-subtitle); font-weight: var(--weight-bold); }
.card p { color: var(--fg-secondary); font-size: 14px; flex: 1; margin: 0; }
.card .card-link {
  font-size: 14px;
  font-weight: var(--weight-bold);
  color: var(--accent);
}
.card:hover { border-color: var(--accent); }
.card:hover .card-link { text-decoration: underline; }

/* ── Buttons ────────────────────────────────────────────── */
.btn {
  display: inline-block;
  background: var(--accent);
  color: #fff;
  padding: 12px 24px;
  font-size: 14px;
  font-weight: var(--weight-bold);
  text-decoration: none;
  border: none;
  font-family: var(--font-body);
  cursor: pointer;
}
.btn:hover { background: #2e4d65; }

/* ── Callout ────────────────────────────────────────────── */
.callout {
  border-left: 4px solid var(--accent);
  padding: 16px 20px;
  background: #f5f8fb;
}
.callout p:last-child { margin-bottom: 0; }

/* ── Homepage proof cards (review §1.6) ─────────────────── */
/* Lines up with the .service column above it. min() keeps the 24px gutter below
   the cap -- same pattern as the Training service. */
.home-proof { max-width: min(900px, calc(100% - 48px)); margin: 8.8vh auto; }
.proof-heading {
  font-size: 11px;
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  color: var(--accent);
  margin: 0 0 12px;
}
.proof-cards {
  display: grid;
  /* 160px, not 180: five cards + four 16px gaps must clear 900px, or the fifth
     wraps alone -- the same trap the three-card teasers hit at minmax(150px). */
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 16px;
}
.proof-card {
  background: #fff;
  border: 1px solid #dfe4e9;
  border-left: 4px solid var(--accent);
  padding: 18px;
  text-decoration: none;
  color: var(--fg);
  /* column, so the icon set (coming) sits over the text as its first child */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  transition: border-color 0.2s ease;
}
.proof-card:hover { border-color: var(--accent); }
.proof-text {
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  font-size: 15px;
  line-height: 1.35;
}
/* Words while the cards run horizontally; digits once they stack. The swap is in
   the ≤720px block below -- one rule each way, so neither can be left showing. */
.num-digit { display: none; }
.callout .callout-label {
  font-size: 11px;
  font-weight: var(--weight-bold);
  letter-spacing: 0.06em;
  color: var(--accent);
  display: block;
  margin-bottom: 6px;
}

/* ── Legal page ─────────────────────────────────────────── */
.legal-block {
  white-space: pre-line;
  font-size: 14px;
  color: var(--fg-secondary);
  line-height: var(--leading-body);
}
.legal-block h2, .legal-block h3 { color: var(--fg); white-space: normal; }

/* ── Footer ─────────────────────────────────────────────── */
.site-footer {
  /* Lifted above the trees layer, exactly as main is. The footer sits OUTSIDE
     main, and a fixed positioned layer at z-index 0 paints over non-positioned
     in-flow content however late it comes in the source. Without this the tree
     draws across the footer links whenever the footer reaches into the reading
     band -- which it does at the bottom of a short window, and always on mobile,
     where .footer-bottom carries 180px of padding. */
  position: relative;
  z-index: 1;
  border-top: 1px solid var(--bg-secondary);
  background: var(--bg-secondary);
  margin-top: 48px;
}
.site-footer .container {
  padding-top: 40px;
  padding-bottom: 32px;
  display: flex;
  flex-wrap: wrap;
  gap: 40px;
  justify-content: space-between;
}
/* sentence case, no uppercase—design system rule, never title case, never ALL CAPS */
.footer-col h3 {
  font-size: 12px;
  letter-spacing: 0.06em;
  color: var(--fg-secondary);
  margin-bottom: 12px;
}
.footer-col ul { list-style: none; padding: 0; margin: 0; }
.footer-col li { margin-bottom: 6px; }
.footer-col a {
  color: var(--fg);
  text-decoration: none;
  font-size: 14px;
}
.footer-col a:hover { text-decoration: underline; }
/* The email under Contact is the one footer link that is an action, so it carries
   alizarin crimson instead of the column's black. Only mailto, so adding another
   footer link cannot pick the colour up by accident. #D42130 is alizarin crimson
   darkened one step from #E32636; it also clears the 4.5:1 AA floor for 14px text
   at 4.62:1 on the footer's #F2F2F2, which #E32636 (4.08:1) did not. */
.footer-col a[href^="mailto:"] { color: #D42130; }
.footer-bottom {
  border-top: 1px solid #e2e2e2;
  padding: 16px 0;
}
.footer-bottom .container {
  padding-top: 0;
  padding-bottom: 0;
  font-size: 13px;
  color: var(--fg-secondary);
}

/* ============================================================
   Scroll layout — home + service pages (v0.5)
   Website design decisions. The design system governs colour,
   type, casing, and the no-radius/no-shadow rules; how those
   land in a browser is decided here.

   NO SHADOWS ANYWHERE. Dave, 2026-07-20: visible drop shadows
   are a 90s/Vista skeuomorphic tell and the antithesis of this
   site. Separation comes from hairline borders, white surfaces,
   and negative space. If something stops reading as a separate
   surface, fix the border or the spacing—never reintroduce a
   shadow, however subtle.
   ============================================================ */

/* ── Background, behind everything ────────────────────────── */
/* Plain white. The ruled placeholder lines were removed 2026-08-23
   on Dave's instruction; do not reintroduce a background pattern. */
.texture {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-color: #fff;
}

/* ── Constituency trees — drawn as you scroll ─────────────── */

/* Six sentences as constituency trees; every branch point is a set of words
   behaving as one grammatical unit. Every word sits under its own preterminal
   (NP = DET ADJ N) and every sentence is rooted at S. One sentence per page load.

   THE WHOLE MECHANISM IS A HANDFUL OF NUMBERS PUBLISHED BY site.js — --p, --i,
   --ty and --s. Everything below is calc() over them. No keyframes, no classes,
   no timers, no display toggling by script. The var() fallbacks are the drawn end
   state, so a browser that never runs the JS still shows a finished tree — the
   failure mode is never a blank column. */
.trees {
  position: fixed;
  top: 0;
  left: 0;
  height: 100vh;
  /* FULL WIDTH. It was min(52vw, 720px), and a mask clips to the element box, so
     overflow: visible bought nothing — anything past that box got mask alpha 0 and
     the sentence was cut off mid-word on a narrow window and on tablet. */
  width: 100vw;
  /* BEHIND the content — background means behind. 0, not -1: at -1 a fixed layer
     paints behind the body's own background and disappears altogether. The content
     is lifted to 1 (see main, below), and .service is translucent so the tree
     reads through it rather than being hidden by it. */
  z-index: 0;
  pointer-events: none;

  /* The reading band. The eye sits here on a scrolling page, so this is where the
     tree is being drawn. vh, so it scales with the device the way viewing distance
     does. */
  --read: 46vh;
  /* The scroll fade, 0.95 -> 0.45. NOT in the mask — a mask dims everything it
     covers, words included, so it could never exempt them. Applied per class below.
     site.js publishes it as a plain number. It is applied to the INK — fill-opacity on
     the labels, stroke-opacity on the connectors — never as element opacity, which
     would take the whole node with it. The 0.8 fallbacks are the no-JS state. */
  /* The mask does shape only: a soft top edge, full ink across the band, and
     nothing at all below it — that part is not drawn yet. site.js fits the tree
     into 6vh–46vh, so no node ever lands in the transparent zone above. */
  -webkit-mask-image: linear-gradient(to bottom,
      rgb(0 0 0 / 0) 0,
      #000 calc(var(--read) - 40vh),
      #000 var(--read),
      rgb(0 0 0 / 0) calc(var(--read) + 2vh));
  mask-image: linear-gradient(to bottom,
      rgb(0 0 0 / 0) 0,
      #000 calc(var(--read) - 40vh),
      #000 var(--read),
      rgb(0 0 0 / 0) calc(var(--read) + 2vh));
  /* A gradient mask tiles by default. */
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
/* 🚨 A PLAIN FULL-VIEWPORT CANVAS. No viewBox, no width/height attribute, so its user
   units are CSS px and NOTHING is ever clipped at an svg viewport edge. The previous
   build declared width="447" from the generator's own guess at monospace metrics; where
   the real font is wider than that guess the tree overflowed its own svg box and was
   sliced off — on both sides. site.js measures the rendered tree with getBBox() and
   fits from that, so no font assumption survives into the layout. */
.trees svg { display: block; width: 100%; height: 100%; overflow: visible; }

/* One sentence per load. JS removes the other five outright — no class, no
   display switch it has to arrive and undo. Without JS six groups remain and
   this rule shows the first; with JS the survivor IS the first. */
.trees .tree:not(:first-of-type) { display: none; }

/* The tree travels up so the node being drawn stays on the reading band.
   site.js publishes --tx, --ty and --s, all derived from the MEASURED tree.
   🚨 A CSS transform on an SVG element REPLACES its transform= attribute outright,
   which is why the generator bakes each group's offset into the coordinates and
   emits no transform attribute. The 6vh fallback is the no-JS park: at 0 the tree
   sits where the mask is transparent, which is a blank layer.
   ponytail: arithmetic here stays one level deep; deriving these in CSS meant
   nesting calc inside clamp inside a custom-property token, and the whole chain
   silently resolved to nothing. */
.trees .tree {
  transform: translate(var(--tx, 4px), var(--ty, 6vh)) scale(var(--s, 1));
  transform-origin: 0 0;
}

/* 🚨 EVERY NODE IS DRAWN, NOT SWITCHED ON.
   One progress term per element, 0 -> 1 over a single step of scroll:
       clamp(0, var(--i) - var(--d), 1)
   TEXT WIPES LEFT TO RIGHT — the word writes itself, typewriter fashion.
   BRANCHES GROW along their own length, from the parent end downward.
   The --i fallback of 999 is the no-JS state — everything drawn, never blank. */
.trees text {
  clip-path: inset(0 calc(100% - clamp(0, var(--i, 999) - var(--d), 1) * 100%) 0 0);
}
.trees line {
  stroke-dashoffset: calc(var(--len) * (1 - clamp(0, var(--i, 999) - var(--d), 1)));
}

.trees text { font-family: monospace; text-anchor: middle; }
/* Connectors light grey, and they fade with the scroll. */
.trees line { stroke: #B8B8B8; stroke-width: 1; stroke-opacity: var(--fade, 0.8); }
/* Constituent labels alizarin crimson, and they fade with the scroll. */
.trees .lbl { fill: #D42130; font-size: 8px; fill-opacity: var(--fade, 0.8); }
/* 🚨 THE WORDS DO NOT FADE. Showing the words is the priority; the scaffolding is
   what recedes. Stated, not left to default. */
.trees .word { fill: #007BA7; font-size: 10px; fill-opacity: 1; }

/* ── Centre line — alizarin crimson, fills with scroll ────── */
/* The book gutter. Base is wide and faint; the fill saturates
   from the top down to wherever you have scrolled to. */
.centerline {
  position: fixed;
  top: 0;
  height: 100vh;      /* explicit, not top/bottom—the fill's % height needs a definite parent */
  left: 50%;
  /* 30% THINNER, 2026-08-23: 28px -> 20px, half-width -14 -> -10. The mask's viewBox
     is 0 0 28 1000 with preserveAspectRatio="none" and sized `/ 100% 100%`, so the
     path squeezes to whatever the element box is — the wobble keeps its shape and no
     path coordinate is touched. */
  width: 20px;
  margin-left: -10px;
  z-index: -1;
  pointer-events: none;
  /* 15% more transparent, 2026-08-23: 0.16 -> 0.136. The thread was reading through
     body copy on the text-heavy pages. */
  background: rgba(227,38,54,0.136);
  /* Same trick as the buttons: a FILLED ink shape, not a stroked path, so the
     line wanders and its width breathes. The mask sits on the CONTAINER, so the
     fill inside is clipped by the identical shape—the wobble stays put while the
     fill grows, instead of restretching on every scroll event. Regenerate by
     sampling a sum-of-sines every 22 units if it ever needs retuning. */
  /* Named once: the @supports block below needs the same shape as a second mask
     layer, and this string is 2 kB. */
  --wobble: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 1000' preserveAspectRatio='none'%3E%3Cpath d='M16.6 4 L15.1 26 L11.9 48 L12.0 70 L13.0 92 L12.6 114 L12.0 136 L13.7 158 L15.9 180 L16.5 202 L17.1 224 L18.3 246 L17.3 268 L13.7 290 L10.9 312 L9.6 334 L7.9 356 L6.2 378 L6.7 400 L8.0 422 L7.4 444 L6.7 466 L7.6 488 L7.9 510 L6.2 532 L5.2 554 L5.9 576 L6.3 598 L6.4 620 L8.9 642 L12.5 664 L13.8 686 L13.7 708 L14.2 730 L14.1 752 L12.3 774 L11.3 796 L12.5 818 L13.2 840 L12.9 862 L14.0 884 L16.3 906 L16.9 928 L15.9 950 L16.2 972 L16.2 994 L16.7 994 L18.9 972 L21.0 950 L23.5 928 L24.0 906 L22.1 884 L20.9 862 L21.0 840 L20.4 818 L19.7 796 L21.0 774 L22.4 752 L21.3 730 L19.3 708 L18.5 686 L17.0 664 L13.9 642 L11.8 620 L11.7 598 L11.2 576 L10.4 554 L12.1 532 L15.1 510 L16.1 488 L15.9 466 L16.3 444 L16.1 422 L14.2 400 L13.4 378 L15.2 356 L16.8 334 L17.2 312 L18.8 290 L21.4 268 L22.1 246 L21.6 224 L22.1 202 L22.3 180 L20.4 158 L18.7 136 L19.5 114 L20.6 92 L20.6 70 L21.1 48 L20.4 26 L17.3 4 Z' fill='%23000'/%3E%3C/svg%3E");
  /* The text-block fade. site.js overwrites it every scroll with a gradient that
     dips to alpha 0.28 across each block of body copy crossing the thread, and stays
     opaque everywhere else. A flat opaque gradient here IS the no-JS state: no fade,
     exactly the thread as it was. */
  --textmask: linear-gradient(#000, #000);
  -webkit-mask: var(--wobble) no-repeat center / 100% 100%;
          mask: var(--wobble) no-repeat center / 100% 100%;
}
/* 🚨 GATED ON mask-composite. Two mask layers default to compositing `add` — the
   UNION — so without intersect the text gradient would not dim the thread, it would
   ADD a full-width rectangle and destroy the wobble outright. Where the browser
   cannot intersect, the gate keeps the single-layer mask above and the thread simply
   does not fade. site.js checks the same thing before it writes --textmask. */
@supports (mask-composite: intersect) {
  .centerline {
    -webkit-mask-image: var(--wobble), var(--textmask);
            mask-image: var(--wobble), var(--textmask);
    -webkit-mask-repeat: no-repeat, no-repeat;
            mask-repeat: no-repeat, no-repeat;
    -webkit-mask-size: 100% 100%, 100% 100%;
            mask-size: 100% 100%, 100% 100%;
    mask-composite: intersect;
  }
}
.centerline-fill {
  width: 100%;
  height: 0;
  /* The only gradient on the site (Dave, 2026-07-26). The saturated fill used to
     stop dead against the faint track; the last 90px now fade to transparent so
     the faint track shows through instead of meeting a hard edge. 90px, not a
     percentage: the element's height changes on every scroll event, so a
     percentage fade would grow and shrink with it. Below 90px of fill (top of
     the page) calc() goes negative and the whole fill is fade—correct. */
  /* 15% more transparent, 2026-08-23: 0.92 -> 0.782. This is the saturated part, so
     it is what actually sat over the text; the same 0.85 factor as the faint track
     takes more off it in absolute terms. */
  background: linear-gradient(to bottom,
    rgba(227,38,54,0.782) 0,
    rgba(227,38,54,0.782) calc(100% - 90px),
    rgba(227,38,54,0) 100%);
}

/* Landscape gets a further 15% off both alphas: 0.136 -> 0.116, 0.782 -> 0.665.
   A wide viewport puts long measure either side of the thread, so it crosses more
   body copy per screen than a portrait one does. Orientation, not width — a narrow
   landscape window has the same problem a wide one does. Same 0.85 factor as the
   base cut, so retuning is one multiplication. 2026-08-23, Dave's instruction. */
@media (orientation: landscape) {
  .centerline { background: rgba(227,38,54,0.116); }
  .centerline-fill {
    background: linear-gradient(to bottom,
      rgba(227,38,54,0.665) 0,
      rgba(227,38,54,0.665) calc(100% - 90px),
      rgba(227,38,54,0) 100%);
  }
}

/* ── Intro band (scrolls away) ────────────────────────────── */
.intro {
  /* Gap under "Communication" cut by 50% horizontal / 30% vertical (Dave,
     2026-07-25). 84vh -> 42vh here; the ≤720px block restores it to 59vh.
     This is the larger of the two knobs -- the intro is centred, so its bottom
     half is most of the space above the Training card. The other is the
     .service:first-of-type margin below. Change both together or the ratio drifts. */
  min-height: 42vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 120px 26px 0;   /* room at the head: the brand name sits at the top, above the h1, clear of the fixed nav */
}
.intro .brand-name {
  font-family: var(--font-brand-name);
  font-size: clamp(24px, 4vw, 44px);
  color: var(--accent);
}
.intro h1 {
  font-size: clamp(40px, 9vw, 120px);
  color: var(--fg);
  margin: 0.1em 0 0.2em;
}
.intro p {
  font-size: var(--text-subtitle);
  color: var(--fg-secondary);
  margin: 0;
}
.scroll-cue { margin-top: 35px; font-size: 13px; color: var(--fg-secondary); }

/* ── Overlapping card-tabs — landscape nav (fixed) ────────── */
.tabs {
  position: fixed;
  top: 18px;
  right: 20px;
  z-index: 50;
  display: flex;
  align-items: flex-start;
  padding-left: 24px;
}
.tabs a {
  position: relative;
  margin-left: -18px;                 /* overlap */
  padding: 13px 22px;
  background: #fff;
  color: var(--fg);
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  font-size: 15px;
  text-decoration: none;
  border: 1px solid #dfe4e9;
  /* transparent by default so the active state adds colour, not height—a media
     query or state change must never shift the row */
  border-bottom: 3px solid transparent;
  transition: transform 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.tabs a:first-child { margin-left: 0; }

/* ── Round home button ────────────────────────────────────── */
/* A circle, not a rounded rectangle. The design system's rule is NO ROUNDED
   CORNERS—a circle has no corners, so this does not break it and is not an
   exception to it. Rectangles on this site stay square-cornered; that rule is
   untouched. The circle reads as a control rather than another card-tab. */
.home-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 46px;
  height: 46px;
  border: 1px solid #dfe4e9;
  border-radius: 50%;
  background: #fff;
  color: var(--accent);
  text-decoration: none;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
.home-btn svg { width: 22px; height: 22px; display: block; }
.home-btn:hover,
.home-btn:focus-visible {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  transform: translateY(-2px);
}
/* In the tab row it must stay a circle: no card padding, no overlap pulling the
   next tab across it, and no square bottom-border from the .tabs a rule. */
.tabs .home-btn {
  padding: 0;
  margin-right: 6px;
  border-bottom-width: 1px;
  align-self: center;
}
.tabs .home-btn + a { margin-left: 0; }
/* The language switch is the same circle as the home button, at the very end of
   the row. It must cancel the -18px card overlap inherited from .tabs a, or it
   slides under the last tab. (The round "More" button that shared this rule went
   2026-08-15 — it was a single link to Legal | Impressum wearing a menu's
   clothes, and that link is a footer item.) */
.tabs .lang-btn {
  margin-right: 0;
  margin-left: 6px;
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  /* DE/EN are letters on the baseline and need no optical lift. */
  font-size: 13px;
  letter-spacing: 0.02em;
}
/* the home button is child 1, so the tabs themselves are 2..8 (Editing added v0.8) */
.tabs a:nth-child(2) { padding-top: 16px; padding-bottom: 16px; }
.tabs a:nth-child(3) { padding-top: 12px; padding-bottom: 12px; }
.tabs a:nth-child(4) { padding-top: 15px; padding-bottom: 15px; }
.tabs a:nth-child(5) { padding-top: 13px; padding-bottom: 13px; }
.tabs a:nth-child(6) { padding-top: 18px; padding-bottom: 18px; }
.tabs a:nth-child(7) { padding-top: 14px; padding-bottom: 14px; }
.tabs a:nth-child(8) { padding-top: 17px; padding-bottom: 17px; }
.tabs a:hover {
  z-index: 10;
  transform: translateY(-4px) scale(1.10);
  color: var(--accent);
  border-color: var(--accent);
}
/* scroll-spy: the tab for the section you are looking at colours and underlines.
   Was an inset box-shadow; an inset bar is an underline, not a shadow, so it is
   a real border now. */
/* The selected tab HOLDS THE HOVER SHAPE — same lift, same scale, same full
   accent outline — so the user can see where they are at a glance without
   pointing at anything. z-index stays 5, below hover's 10, so pointing at a
   neighbouring tab still brings that one to the front. Every other tab is
   unchanged. */
.tabs a.active {
  color: var(--accent);
  border-color: var(--accent);
  border-bottom-color: var(--accent);
  transform: translateY(-4px) scale(1.10);
  z-index: 5;
}

/* ── Floater — brand mark + page-cards menu ───────────────── */
/* At rest the whole floater sits at 70% opacity and its bar's white fill at 30%,
   so the page reads through it. Pointing at it — or opening the menu, or tabbing
   into it — brings both back to full. :focus-within keeps the keyboard route
   equal to the mouse route. */
.floater {
  position: fixed;
  left: 20px;
  bottom: 20px;
  z-index: 60;
  opacity: 0.7;
  transition: opacity 0.18s ease, transform 0.3s ease;
}
/* At the bottom of the page the floater sits on top of the footer's first column
   of links. `body.at-footer` (site.js, from the footer's own position) slides it
   across to the right-hand side, where it covers Elsewhere's two items instead of
   Services' five. Transform, not left/right, so it animates.
   100vw counts the scrollbar on Windows and Linux, so the parked floater can end
   up ~15px further right than the 20px margin — it cannot overflow the page,
   because a fixed element adds nothing to scrollable area. Cancelled on mobile,
   where the floater is the whole navigation and nothing is beside it. */
body.at-footer .floater { transform: translateX(calc(100vw - 100% - 40px)); }
.floater:hover,
.floater:focus-within,
.floater.open { opacity: 1; }
.floater-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  background: rgba(255, 255, 255, 0.3);
  border: 1px solid #dfe4e9;
  padding: 20px 35px 20px 26px;
  cursor: pointer;
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  font-size: 16px;
  color: var(--fg);
  transition: border-color 0.18s ease, background-color 0.18s ease;
}
.floater:hover .floater-bar,
.floater:focus-within .floater-bar,
.floater.open .floater-bar { background: #fff; }
.floater-bar:hover { border-color: var(--accent); }
/* max-width:none—the global img{max-width:100%} above otherwise clamps the wide
   logo and quietly shrinks it back below the intended height */
.floater-bar img { height: 96px; width: auto; max-width: none; display: block; }
.floater-home { display: block; text-decoration: none; }
/* Contact, on the bar itself. Accent-filled so it reads as the action among the
   bar's other controls, which are all outlines. Square, no shadow, sentence case. */
.floater-contact {
  display: inline-flex;
  align-items: center;
  padding: 10px 16px;
  background: var(--accent);
  border: 1px solid var(--accent);
  color: #fff;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.18s ease, color 0.18s ease;
}
.floater-contact:hover { background: #fff; color: var(--accent); }
.floater-menu {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}
.floater-toggle {
  margin-left: 4px;
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid #d5dbe1;
  color: var(--accent);
  font-size: 16px; line-height: 1;
}
.floater.open .floater-toggle { background: var(--accent); color: #fff; border-color: var(--accent); }

.floater-cards {
  position: absolute;
  left: 0;
  bottom: calc(100% + 11px);
  width: 352px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 9px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
}
.floater.open .floater-cards { opacity: 1; visibility: visible; transform: translateY(0); }
.floater-card {
  background: #fff;
  border: 1px solid #dfe4e9;
  padding: 15px 15px 18px;
  text-decoration: none;
  color: var(--fg);
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  font-size: 15px;
  transition: transform 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.floater-card:hover { transform: translateY(-2px); border-color: var(--accent); color: var(--accent); }

/* ── Snake / Z service rectangles ─────────────────────────── */
.service {
  position: relative;
  max-width: 900px;
  margin: 8.8vh auto;
  /* TRANSPARENT. The trees layer is behind the content and must read through the
     card, so the card contributes no fill at all — the border alone carries the
     card's edge. Was rgba(255,255,255,0.72), and before that opaque white. */
  background: transparent;
  /* NOTE: the first card's top margin is overridden below -- see .service:first-of-type */
  /* carried the separation that the removed 40px shadow used to do */
  border: 1px solid #dfe4e9;
  padding: 44px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
  align-items: center;
  transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.2,0.7,0.2,1);
}
/* Second half of the "Communication -> Training" gap reduction: 8.8vh -> 4.4vh,
   the same 50%; the ≤720px block restores it to 6.2vh (30%).
   first-of-type, NOT first-child -- the intro <header> shares <main>
   with these sections, which is the trap that once inverted the whole stagger. */
.service:first-of-type { margin-top: 4.4vh; }
/* Reactive reveal. Progressive enhancement: the hidden state only applies when
   JS is on (html.js), so no-JS users still see everything. */
.js .service { opacity: 0; transform: translateY(32px); }
.js .service.is-visible { opacity: 1; transform: translateY(0); }
/* ~8% off-centre stagger. Centred by margin:auto, then nudged with a percentage
   offset, so the stagger stays 8% of the viewport at every width—a percentage
   margin would let the offset grow on wide screens. */
/* nth-of-type, not nth-child: the intro <header> shares <main> with these
   sections, and nth-child would count it and flip the whole stagger. */
.service:nth-of-type(odd)  { left:  8%; }
.service:nth-of-type(even) { left: -8%; }
.service:nth-of-type(even) .service-copy { order: 2; text-align: right; }
.service:nth-of-type(even) .carousel     { order: 1; }

/* min-width:0 — grid items default to min-width:auto, so the carousel's flex
   track (whose min-content is a full slide) refuses to shrink and pushes the
   whole page wider than the viewport. This is what causes horizontal scroll on
   mobile; it is not a padding problem. */
.service > .service-copy,
.service > .teaser-cards,
.service > .carousel { min-width: 0; }
.service:nth-of-type(even) .teaser-cards { order: 1; }

/* ── v0.6 teaser cards — the homepage bands' links into the service pages ── */
.teaser-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
/* two-card bands don't force a third column */
.teaser-cards:has(.teaser-card:nth-child(2):last-child) { grid-template-columns: repeat(2, 1fr); }
.teaser-cards:has(.teaser-card:only-child) { grid-template-columns: 1fr; }
/* A card HAS an image and text: picture on top, its label beneath, one border
   round both. */
.teaser-card {
  background: #fff;
  border: 1px solid #dfe4e9;
  text-decoration: none;
  color: var(--fg);
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  font-size: 16px;
  transition: transform 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
/* same treatment as the carousel slides: contain, not cover—cover crops, and
   cropping a face is not on. Letterbox bars are the acceptable cost. */
.teaser-media {
  display: block;
  aspect-ratio: 4 / 3;
  /* TRANSPARENT, not the grey --bg-secondary panel. contain letterboxes the image,
     and the bars used to be a grey box around every card image; the page reads
     through them now. 2026-08-23, Dave's instruction. */
  background: transparent center/contain no-repeat;
}
.teaser-text { display: block; padding: 15px 18px 19px; }

/* The one image that sits inside body copy -- the reading-notes link on About.
   Not a card and not a carousel slide, so it needs neither the fixed 4:3 box nor
   the scroll-snap strip: a plain <img> scales itself and carries real alt text.
   Capped at the same 520px as the service-page carousel so it does not dominate
   the column. Border and hover match .teaser-card; no shadow, per the site rule. */
.prose-figure { display: block; max-width: 520px; margin: 28px 0; text-decoration: none; }
.prose-figure img { display: block; width: 100%; height: auto; border: 1px solid #dfe4e9; transition: border-color 0.15s ease; }
.prose-figure:hover img { border-color: var(--accent); }
.teaser-card:hover { transform: translateY(-2px); border-color: var(--accent); color: var(--accent); }

/* Training band runs the full width of the screen (Dave's sketch): one column,
   no ±8% stagger, cards across, discover at the bottom-right. */
/* Capped at twice the 900px width of the services below it, so it stops growing
   on extra-wide screens instead of running to the edge. min() keeps the 24px
   gutter below the cap; auto margins centre it above the cap. */
.service.band-full {
  max-width: min(1800px, calc(100% - 48px));
  left: 0;
  margin-left: auto;
  margin-right: auto;
  grid-template-columns: 1fr;
}
/* Text sits opposite the images, as on every other band: cards left, copy right. */
.service.band-full { grid-template-columns: minmax(0, 3fr) minmax(0, 1fr); }
.service.band-full .teaser-cards { order: 1; }
.service.band-full .service-copy { order: 2; text-align: right; }
/* auto-fit, not repeat(3): three fixed columns squeeze to 64px on a phone. */
.service.band-full .teaser-cards { grid-template-columns: repeat(auto-fit, minmax(200px, 260px)); justify-content: start; }
.band-cta { grid-column: 1 / -1; justify-self: end; }
.band-cta { justify-self: end; }

.service-copy h2 { font-size: clamp(28px, 4vw, 44px); margin: 0 0 0.3em; color: var(--fg); }
.service-copy .lede { color: var(--fg); font-size: var(--text-subtitle); margin: 0 0 0.9em; }
.service-copy p { color: var(--fg-secondary); margin: 0 0 1.2em; }
/* Audit scribing (band s6) drops one step down the type scale, subtitle -> body.
   Its lede is a 40-word sentence where every other band's is under ten, so at the
   shared size it reads as a wall of large type next to its neighbours. Scoped to
   the homepage band; the audit-scribing page's own lede is untouched. */
.active-home #s6 .lede { font-size: var(--text-body); }

/* ── Ink buttons — one palette colour + one wavy edge each ── */
/* Hairline border all round; the marked edge carries a thick, slightly wavy
   hand-drawn line. The wave is one SVG used as a MASK, so each button's own
   colour paints through it—one graphic serves all six colours instead of six
   recoloured copies. */
.btn-ink {
  position: relative;
  display: inline-block;
  padding: 13px 26px;
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  font-size: 15px;
  text-decoration: none;
  color: var(--btn);
  background: #fff;
  border: 1px solid color-mix(in srgb, var(--btn) 40%, #fff);
  transition: background 0.18s ease, color 0.18s ease;
}
.btn-ink:hover { background: var(--btn); color: #fff; }
.btn-ink::after {
  content: "";
  position: absolute;
  background-color: var(--btn);
  pointer-events: none;
}
/* The stroke is a FILLED shape, not a stroked path: its two edges wander
   independently, so the line thickens and thins along its length and tapers at
   the ends. A stroked path has one constant width and reads as a drawn curve,
   not as ink. It also does not tile—one stroke stretches the whole edge, so
   there is no repeating tell. Reuse this anywhere a hand-drawn line is wanted. */
.btn-ink.edge-top::after,
.btn-ink.edge-bottom::after {
  left: -1px; right: -1px; height: 14px;
  -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 240 14' preserveAspectRatio='none'%3E%3Cpath d='M2 5.2 C 26 3.5 45 5.5 68 4.1 C 93 2.8 111 5.9 136 4.5 C 157 3.6 183 6.1 205 4.3 C 218 3.4 231 5.1 238 3.9 L 238 9.5 C 229 11.2 217 9.1 203 10.5 C 181 12.1 155 9.3 134 10.9 C 109 12.4 90 8.9 66 10.5 C 44 11.9 26 9.5 2 11.3 Z' fill='%23000'/%3E%3C/svg%3E") no-repeat left center / 100% 14px;
          mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 240 14' preserveAspectRatio='none'%3E%3Cpath d='M2 5.2 C 26 3.5 45 5.5 68 4.1 C 93 2.8 111 5.9 136 4.5 C 157 3.6 183 6.1 205 4.3 C 218 3.4 231 5.1 238 3.9 L 238 9.5 C 229 11.2 217 9.1 203 10.5 C 181 12.1 155 9.3 134 10.9 C 109 12.4 90 8.9 66 10.5 C 44 11.9 26 9.5 2 11.3 Z' fill='%23000'/%3E%3C/svg%3E") no-repeat left center / 100% 14px;
}
.btn-ink.edge-top::after    { top: -8px; }
.btn-ink.edge-bottom::after { bottom: -8px; }
.btn-ink.edge-left::after,
.btn-ink.edge-right::after {
  top: -1px; bottom: -1px; width: 14px;
  -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 240' preserveAspectRatio='none'%3E%3Cpath d='M5.2 2 C 3.5 26 5.5 45 4.1 68 C 2.8 93 5.9 111 4.5 136 C 3.6 157 6.1 183 4.3 205 C 3.4 218 5.1 231 3.9 238 L 9.5 238 C 11.2 229 9.1 217 10.5 203 C 12.1 181 9.3 155 10.9 134 C 12.4 109 8.9 90 10.5 66 C 11.9 44 9.5 26 11.3 2 Z' fill='%23000'/%3E%3C/svg%3E") no-repeat center top / 14px 100%;
          mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 240' preserveAspectRatio='none'%3E%3Cpath d='M5.2 2 C 3.5 26 5.5 45 4.1 68 C 2.8 93 5.9 111 4.5 136 C 3.6 157 6.1 183 4.3 205 C 3.4 218 5.1 231 3.9 238 L 9.5 238 C 11.2 229 9.1 217 10.5 203 C 12.1 181 9.3 155 10.9 134 C 12.4 109 8.9 90 10.5 66 C 11.9 44 9.5 26 11.3 2 Z' fill='%23000'/%3E%3C/svg%3E") no-repeat center top / 14px 100%;
}
.btn-ink.edge-left::after  { left: -7px; }
.btn-ink.edge-right::after { right: -7px; }

/* ── Carousel ─────────────────────────────────────────────── */
.carousel { position: relative; }
.carousel-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
}
.carousel-track::-webkit-scrollbar { display: none; }
.slide {
  position: relative;
  flex: 0 0 100%;
  scroll-snap-align: center;
  aspect-ratio: 4 / 3;
  /* contain, not cover: cover crops, and cropping a face is not on. Letterbox
     bars are the acceptable cost. */
  /* TRANSPARENT, not the grey --bg-secondary panel. contain letterboxes the image,
     and the bars used to be a grey box around every card image; the page reads
     through them now. 2026-08-23, Dave's instruction. */
  background: transparent center/contain no-repeat;
  display: flex;
  align-items: flex-end;
  justify-content: flex-start;
  text-decoration: none;
  color: var(--fg-secondary);
  overflow: hidden;
}
.slide:hover .slide-label { color: var(--accent); }
.slide-label {
  position: relative;
  z-index: 1;
  margin: 13px;
  padding: 7px 13px;
  background: rgba(255,255,255,0.92);
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  font-size: 13px;
  color: var(--fg);
  transition: color 0.18s ease;
}
.carousel-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 34px; height: 34px;
  background: #fff;
  border: 1px solid #dfe4e9;
  color: var(--accent);
  font-size: 18px;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.carousel-nav.prev { left: 8px; }
.carousel-nav.next { right: 8px; }
.carousel-nav:disabled { opacity: 0.3; cursor: default; }
.carousel-dots { display: flex; gap: 6px; justify-content: center; margin-top: 11px; }
.carousel-dots span { width: 7px; height: 7px; background: #cfd6dd; cursor: pointer; transition: background 0.18s ease, transform 0.18s ease; }
.carousel-dots span:hover { transform: scale(1.4); }
/* The dot for the slide currently in the track. site.js sets it from the track's own
   scrollLeft, so it follows a swipe as well as an arrow click. */
.carousel-dots span.is-active { background: var(--accent); }
/* ONE image is not a carousel: site.js marks it and both controls go. The rule is
   here, not in the markup, so a page that gains a second image needs no edit. */
.carousel.single .carousel-nav,
.carousel.single .carousel-dots { display: none; }

/* ── Service page header (inner pages) ────────────────────── */
/* Same ink language as the home sections, one colour per service, set by
   --btn on the page's own body class. */
.service-page-head {
  max-width: 900px;
  margin: 0 auto;
  /* top padding must clear the fixed .tabs row (top:18px + ~56px tall + breathing
     room), or the h1 slides underneath it on short viewports */
  padding: 132px 44px 0;
}
.service-page-head h1 { font-size: clamp(32px, 5vw, 56px); margin: 0 0 0.3em; }
.service-page-head .lede { font-size: var(--text-subtitle); color: var(--fg); margin: 0; }
.service-page-body {
  max-width: 900px;
  margin: 0 auto;
  padding: 24px 44px 8.8vh;
  /* TRANSPARENT. This is the card the page's images and copy sit on, and an opaque
     white card is a solid block over the trees layer behind it — it cut the tree off
     down the middle of the page. Same rule as .service on the homepage.
     2026-08-23, Dave's instruction. */
  background: transparent;
}
.service-page-body p { color: var(--fg-secondary); }
.service-page-body .prose { max-width: var(--content-prose); }
/* the carousel is supporting context, not the subject—cap it so it does not
   swamp a short service page the way a full-width 4:3 block does */
/* ALIGNED RIGHT at the foot of every service page: auto LEFT margin, 0 right, so
   the capped 520px block sits on the body's right edge. The trees layer stays on
   the left and is untouched by this. 2026-08-23, Dave's instruction. */
.service-page-body .carousel { max-width: 520px; margin: 32px 0 32px auto; }

/* The page-foot CTA sits JUST LEFT OF THE CENTRE LINE. Left-aligned it stood over
   the trees layer's column and obscured it. .service-page-body is max-width 900px,
   margin auto, with symmetric 44px padding, so 50% of its content box IS the
   viewport centre and IS the centre line's axis. margin-right 50% puts the
   paragraph's right edge on that axis; the extra 18px clears the line's own 14px
   half-width by 4px, so the button ends just to its left, never under it.
   :has() — the CTA is the last child on every service page but legal-impressum,
   whose last child is legal body copy and must not move. Where :has() is not
   supported the button simply stays where it was. 2026-08-23, Dave's instruction. */
.service-page-body > p:last-child:has(> .btn-ink) {
  margin-right: calc(50% + 18px);
  text-align: right;
}

/* ── Mobile: centred, tabs scroll away, floater takes over ── */
@media (max-width: 720px) {
  .service { grid-template-columns: 1fr; padding: 28px; }
  /* Centred in portrait. Must repeat the nth-child selectors—a media query adds
     no specificity, so a bare .service rule would lose. */
  .service:nth-of-type(odd),
  .service:nth-of-type(even) { left: 0; }
  .service:nth-of-type(even) .service-copy,
  .service:nth-of-type(even) .carousel { order: 0; }
  .service:nth-of-type(even) .service-copy { text-align: left; }
  /* the full-width training band collapses to one column too */
  .service.band-full { grid-template-columns: 1fr; }
  .service.band-full .service-copy { order: 0; text-align: left; }

  /* Vertical keeps more air than horizontal: 30% off the gap, not 50% (Dave,
     2026-07-25). Both values must be repeated here -- a media query adds no
     specificity, so these override only because they come later in the file. */
  .intro { min-height: 59vh; padding-top: 0; }
  .service:first-of-type { margin-top: 6.2vh; }

  /* Proof cards go vertical, and the numbers become digits (Dave, 2026-07-25):
     "20, 15, 100s, 10, 8" read faster in a narrow column than the spelled-out
     words do. Forcing one column is what makes "vertical" actually vertical --
     auto-fit alone would still give three columns at this width. */
  .proof-cards { grid-template-columns: 1fr; }

  /* Shown on a phone since 2026-08-22. It used to be display:none because the
     tree sat straight under the copy; it is now scaled to fit the screen width
     and sits behind the content. */
  .num-word { display: none; }
  .num-digit { display: inline; }

  /* tabs are static (in-flow) on mobile, so no clearance needed here */
  /* Homepage on a phone: one sentence per section, no paragraphs. The .lede is
     that sentence; everything else is hidden, not deleted—the full copy still
     ships in the markup and lives on the service pages. */
  .active-home .service-copy p:not(.lede) { display: none; }

  .service-page-head { padding: 24px 24px 0; }
  .service-page-body { padding: 16px 24px 48px; }
  .service-page-body .carousel { max-width: none; }
  /* No half-width column on a phone: the CTA returns to the flow, left-aligned. */
  .service-page-body > p:last-child:has(> .btn-ink) {
    margin-right: 0;
    text-align: left;
  }

  /* tabs sit at the top of the flow and scroll away with the page */
  .tabs {
    position: static;
    justify-content: flex-start;   /* flex-end + overflow scrolls off to the
                                      unreachable left; start keeps it reachable */
    padding: 12px 12px 0 24px;
    overflow-x: auto;              /* six labels do not fit 375px—scroll the row,
                                      do not let it push the whole page sideways */
    scrollbar-width: none;
  }
  .tabs::-webkit-scrollbar { display: none; }
  /* margin-left:0 — the desktop -18px overlap is wider than the mobile 13px
     padding, so the next tab covers the previous tab's last characters */
  .tabs a { padding: 10px 13px; font-size: 13px; flex: 0 0 auto; margin-left: 0; }
  .tabs a + a { border-left: none; }
  /* No lift on the selected tab here. The mobile row is overflow-x: auto, which
     clips vertically as well, so a translated/scaled tab is cut off at the top.
     The accent border and underline carry the selected state on a phone. */
  .tabs a.active { transform: none; }
  /* the two round buttons keep their own border on both sides */
  .tabs .home-btn { border-left: 1px solid #dfe4e9; }

  /* DE/EN sits last in the markup, which on a phone puts it off the right-hand
     end of the scrolling tab row -- reachable only by swiping the row sideways.
     order:-1 places it at the head of the row instead, so it is on screen at
     375px with no swipe and no scroll. Mobile only; the desktop row is not
     scrolled and keeps the button at the far end where it was designed. */
  .tabs .lang-btn { order: -1; margin-left: 0; margin-right: 6px; }

  /* floater hidden until the tabs have scrolled out of reach */
  .floater { opacity: 0; visibility: hidden; transition: opacity 0.25s ease, visibility 0.25s; }
  /* one column, so there is no footer link beside it to uncover — and a phone-width
     slide would push the bar off the right edge */
  body.at-footer .floater { transform: none; }
  body.scrolled-past .floater { opacity: 1; visibility: visible; }
  .floater-bar { padding: 12px 16px 12px 12px; gap: 10px; }
  .floater-bar img { height: 56px; }
  .floater-cards { width: 264px; }

  /* Contact gets its own full-width row under the bar's controls. At 375px the
     first row is already ~363px wide (46px home button + a 170px logo + "Menu +"),
     so a fourth control inline would overflow the screen. Wrapping keeps every
     control and gives Contact the biggest tap target on the floater.
     order:1 puts it last; flex-basis:100% forces the line break. */
  .floater-bar { flex-wrap: wrap; }
  .floater-contact { order: 1; flex: 1 0 100%; justify-content: center; padding: 12px; }
  .home-btn { width: 40px; height: 40px; }
  .home-btn svg { width: 20px; height: 20px; }

  /* Room under the last footer link for the floater to sit on. The mobile floater
     is ~156px tall — 56px logo + 24px bar padding + the wrapped Contact row + the
     20px it stands off the bottom — and it is fixed, so it covered Elsewhere and
     the copyright line. The desktop floater slides aside at the footer instead;
     on one column there is nowhere to slide to, so the page makes room.
     ponytail: a measured number, not a JS height read. If the bar ever gains a
     row, raise it here. */
  .footer-bottom { padding-bottom: 180px; }
}

/* respect users who ask for less motion—no reveal jumps, no smooth-scroll,
   texture stays put. Accessibility basic, not optional. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js .service { opacity: 1; transform: none; }
  .texture { transition: none; }
  /* The drawing STAYS under reduced motion. It is not autonomous motion: every
     step is the visitor's own scrolling, and nothing on the page shifts. What
     goes is the tree's travel, which is movement the visitor did not ask for.
     That is done in site.js, by parking --ty at the top of the visible ink.
     NOT with transform:none here — that returned the tree to the top of the
     layer, where the mask is transparent, and Reduce Motion saw a blank column. */
  *, *::before, *::after { transition-duration: 0.01ms !important; }
}
