/* christophlechner.com
   One file, dark only. Tokens first, then base, layout, components.
   Spacing scale: 4, 8, 16, 24, 32, 48, 64 (rem values below). No other spacing values.
   Motion: only transform and opacity, no catch-all transitions. */

@font-face {
  font-family: "Barlow Condensed";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/barlow-condensed-latin-500-normal.woff2") format("woff2");
}
/* Display face (chosen 2026-10-24 from a font test page, since deleted): Dela Gothic One, SIL OFL 1.1, licence text in
   fonts/OFL-Dela-Gothic-One.txt. Latin subset only. Used for the wordmark, H1 and H2; everything
   else stays in Barlow Condensed or the system stack. */
@font-face {
  font-family: "Dela Gothic One";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/dela-gothic-one-latin-400-normal.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow Condensed";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/barlow-condensed-latin-700-normal.woff2") format("woff2");
}

:root {
  color-scheme: dark;

  --bg: #0B0B0B;
  --surface: #151515;
  --line: #2E2E2E;
  --text: #F2EFE8;
  --muted: #A8A59D;
  --accent: #FF7A45; /* confirmed by Christoph on 2026-11-13 */
  --accent-hover: #FF9468;
  --todo: #FFE14D;

  --s1: 0.25rem;
  --s2: 0.5rem;
  --s3: 1rem;
  --s4: 1.5rem;
  --s5: 2rem;
  --s6: 3rem;
  --s7: 4rem;

  /* One spacing system for the whole site (measured 2026-10-24: section padding was 64px on 4 of 6
     sections, heading gap 32px everywhere, block gap 32px on 5 of 7 blocks, so those are the
     central values). Phones get their own, smaller set in the media query below. Nothing outside
     these three variables sets section, heading or block spacing any more. */
  --space-section: var(--s6);  /* top and bottom padding of every section */
  --space-heading: var(--s4);  /* heading to its content */
  --space-block: var(--s4);    /* block to block inside a section */


  --radius: 0.5rem;
  --header-h: 3rem;
  /* The bar ends up taller than --header-h: its content is a 44px touch target plus 2x var(--s1)
     padding, so 52px, and the header adds a 1px bottom border. Measured 53px at every width.
     --header-total is what anything positioned below the header has to use. */
  --header-slack: 5px;
  --header-total: calc(var(--header-h) + var(--header-slack));
  --gutter: 1rem;

  --font-head: "Barlow Condensed", "Arial Narrow", system-ui, sans-serif;
  --font-display: "Dela Gothic One", Impact, "Arial Narrow", sans-serif;

  /* Display sizes, one place for all of them (2026-10-24). Dela Gothic One runs about twice as wide
     as Barlow Condensed (measured per heading: 0.49 to 0.53 of the Dela width), so the old sizes
     could not just be scaled to 70 percent everywhere. Each value is the largest size at which the
     longest real string still fits its column, minus a little air:
       --fs-h1            "THE BROTHERS" (9.93em) and 404 "PAGE NOT FOUND" (10.95em) on one line
       --fs-hero-home     "TWO NATIONS." per line (9.41em)
       --fs-hero-athlete  "CHRISTOPH" per line (7.79em)
       --fs-h2            "SEASON PREVIEW" (11.45em) and "PARTNERS & MEDIA" (12.42em) on one line
       --fs-wordmark      "BROTHERS" plus its 1.45em indent (8.54em)
     Phones first, the clamp() grows with the window; the landscape hero overrides are further down. */
  --fs-h1: clamp(2rem, 8.2vw, 6rem);
  --fs-hero-home: clamp(1.75rem, 9.2vw, 4.375rem);   /* floor lowered 2026-10-28: at 320px the claim broke into 4 lines */
  --fs-hero-athlete: clamp(2.25rem, 11.3vw, 4.5rem); /* floor lowered 2026-10-28: at 320px "CHRISTOPH" was cut off */
  --fs-h2: clamp(1.75rem, 5.6vw, 3.125rem);
  --fs-wordmark: clamp(1.75rem, 4.9vw, 3.125rem);
  --fs-brand: clamp(1rem, 4.4vw, 1.25rem);   /* header wordmark, one line next to MENU */
  --lh-display: 1.05;
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

@media (min-width: 48em) { :root { --gutter: 1.5rem; --space-section: var(--s7); --space-heading: var(--s5); --space-block: var(--s5); } }
@media (min-width: 80em) { :root { --gutter: 2rem; } }

/* Reset and base */
*, *::before, *::after { box-sizing: border-box; }
html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + var(--s3));
  -webkit-text-size-adjust: 100%;
}
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 400 1rem/1.6 var(--font-body);
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, p, ul, ol, dl, dd, figure, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 0.2em; }
a:hover { color: var(--text); text-decoration-thickness: 2px; }
a:not(.btn):active { opacity: 0.7; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.wrap { max-width: 72rem; margin-inline: auto; padding-inline: var(--gutter); }
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; left: var(--s3); top: var(--s3); z-index: 100;
  padding: var(--s2) var(--s3); background: var(--accent); color: var(--bg);
  font-family: var(--font-mono); font-size: 0.8125rem;
  transform: translateY(-200%);
}
.skip:focus { transform: none; }

/* Draft marker. Loud on purpose, gone at launch. */
.todo {
  display: inline-block;
  padding: 0 var(--s2);
  border: 2px dashed var(--todo);
  background: #2A2500;
  color: var(--todo);
  font: 700 0.8125rem/1.6 var(--font-mono);
  letter-spacing: 0;
  text-transform: none;
  max-width: 100%;
  overflow-wrap: anywhere;
}

/* Type */
h1, h2, h3, h4, .display {
  font-family: var(--font-head);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: -0.02em;
  line-height: 1.05;
}
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
/* Display face for the page headlines only: every H1 and H2 inside a hero or a content section,
   plus the footer wordmark. Footer labels ("Explore", "Say hey!") are H2s too but sit outside
   .section and keep their mono style; the legal pages have their own .legal headings and are left
   in Barlow Condensed. Balanced wrapping so a heading that has to break never leaves one lonely
   word behind. */
.hero h1, .section h1, .section h2 {
  font-family: var(--font-display); font-weight: 400;
  line-height: var(--lh-display); letter-spacing: -0.02em;
  text-wrap: balance;
}
h3 { font-size: 1.5rem; line-height: 1.15; }
h4 { font-size: 1.0625rem; font-weight: 500; line-height: 1.2; } /* Lighter weight than h1-h3 to sit clearly below h3 in the hierarchy, using the font's other self-hosted weight. */
.label {
  font: 400 0.8125rem/1.4 var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.muted, .label--muted { color: var(--muted); }
.stats--after { margin-top: var(--space-block); }
.note--gap { margin-top: var(--space-block); }
/* One paragraph gap, nothing else. em on purpose, not the rem spacing scale: the gap follows the
   text size it separates. The same value on Christoph, Florian and the About page, so changing it
   here changes every bio at once. */
.prose { max-width: 40rem; --para-gap: 1em; }
.prose p + p { margin-top: var(--para-gap); }
.note { color: var(--muted); font-size: 0.875rem; }

/* Header and nav. Breakpoint --bp-nav (~900px). */
:root { --bp-nav: 56.25em; }
.site-header {
  position: sticky; top: 0; z-index: 20;
  background: rgba(11, 11, 11, 0.94);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}
/* Hide the bar while scrolling down, bring it back on the way up. Three conditions, all on purpose:
   only with JavaScript (html.js), only as a transform (so the bar keeps its space in the layout and
   nothing below it reflows), and only below 63.99em, so from 1024px up the bar stays put.
   js/nav-hide.js guards the same width with matchMedia, the two must stay in step. The class is
   set by that script. */
@media (max-width: 63.99em) {
  .js .site-header { transition: transform 200ms ease; }
  .js body.nav-hidden .site-header { transform: translateY(-100%); }
  /* Keyboard focus inside the bar always wins over the hidden state. */
  .js body.nav-hidden .site-header:focus-within { transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .js .site-header { transition: none; }
}

.site-header__bar {
  position: relative;
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s1) var(--s4); padding-block: var(--s1);
  min-height: var(--header-h);
}
/* Header wordmark (2026-10-24): same face and colour split as the footer wordmark, LECHNER in
   white, BROTHERS in the light-to-mid grey gradient, but on one line and small enough for the bar.
   "LECHNER BROTHERS" in Dela is 13.4em wide; at 390px it has to share the row with the 44px MENU
   button, which leaves about 250px, so the size tops out at ~18px there. */
.brand {
  order: 1;
  display: flex; align-items: center; gap: 0.28em; min-height: 2.75rem;
  font: 400 var(--fs-brand)/1 var(--font-display);
  letter-spacing: -0.02em; text-transform: uppercase; text-decoration: none;
  white-space: nowrap;
}
.brand__line--1 { color: var(--text); }
/* Header: accent to white, same gradient as the footer wordmark (back from grey on 2026-10-25). */
.brand__line--2 {
  background: linear-gradient(to bottom, var(--accent) 0%, var(--text) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
@supports not (background-clip: text) { .brand__line--2 { color: var(--accent); } }
/* Below 360px the one-line wordmark plus the MENU button no longer fit one row (measured at 320px:
   the bar wrapped to 101px and the page scrolled sideways). There it stacks like the footer
   wordmark, two short lines inside the same 44px touch target. */
@media (max-width: 22.49em) {
  .brand { flex-direction: column; align-items: flex-start; justify-content: center; gap: 0; line-height: 0.95; }
}

/* Round mobile menu button. Hidden unless JS added the .js class to <html>. */
.menu-toggle {
  order: 2; margin-inline-start: auto;
  display: none; align-items: center; justify-content: center;
  min-width: 2.75rem; min-height: 2.75rem; padding-inline: var(--s3);
  border: 2px solid var(--muted); border-radius: 999px; background: transparent; color: var(--text);
  font: 400 0.75rem/1 var(--font-mono); letter-spacing: 0.08em; text-transform: uppercase;
  cursor: pointer; transition: transform 100ms ease-out, border-color 150ms ease-out, color 150ms ease-out;
}
.menu-toggle:hover { border-color: var(--text); color: var(--text); }
.menu-toggle:active { transform: scale(0.96); }
.menu-toggle[aria-expanded="true"] { border-color: var(--text); color: var(--text); }
html.js .menu-toggle { display: inline-flex; }

/* Row nav: without JS this is the only nav, wrapping under the wordmark on narrow screens.
   With JS on narrow screens it is replaced by the button + panel below. */
.main-nav { order: 3; flex-basis: 100%; }
.main-nav__list { display: flex; flex-wrap: wrap; gap: var(--s1) var(--s4); padding-block: var(--s1) var(--s2); }
.main-nav__list a {
  display: flex; align-items: center; min-height: 2.75rem;
  font: 400 0.875rem/1 var(--font-mono); /* 14px minimum for the inline nav (2026-10-29, was 13px) */
  letter-spacing: 0.06em; text-transform: uppercase; text-decoration: none;
  white-space: nowrap; color: var(--muted);
}
.main-nav__list a { position: relative; }
.main-nav__list a:hover { color: var(--accent); }
.main-nav__list a:focus-visible { outline-offset: -2px; }
.main-nav__list a:active { color: var(--text); }
/* Active item: the orange line sits exactly on the header's own 1px divider and covers it for the
   width of the link, so only one line is ever visible. --nav-rule-gap is the distance from the
   link's bottom edge down to that divider; it differs between the wrapped row (nav on its own
   line, padding-block s1 s2) and the one-row desktop layout, so the desktop block below resets it. */
.main-nav__list a[aria-current="page"] { color: var(--text); }
.main-nav__list a[aria-current="page"]::after {
  content: ""; position: absolute; left: 0; right: 0;
  bottom: calc(-1 * (var(--nav-rule-gap, calc(var(--s2) + var(--s1))) + 1px));
  height: 1px; background: var(--accent);
}
html.js .main-nav { display: none; }

/* Mobile panel: closed by default (hidden attribute). JS removes/restores hidden and
   toggles .is-open so the opacity/transform transition can run; see js/menu.js.
   The :not([hidden]) guard matters: without it this rule's higher specificity would beat
   the UA [hidden]{display:none} rule and keep the panel's links reachable by keyboard
   even while visually hidden. */
.mobile-panel { display: none; }
html.js .mobile-panel:not([hidden]) {
  display: block;
  position: absolute; left: 0; right: 0; top: 100%; z-index: 19;
  background: var(--bg); border-bottom: 1px solid var(--line);
  padding: var(--s4) var(--gutter) var(--s5);
  transform: translateY(-6px); opacity: 0; pointer-events: none;
  transition: transform 200ms ease-out, opacity 200ms ease-out;
}
html.js .mobile-panel.is-open { transform: translateY(0); opacity: 1; pointer-events: auto; }
.mobile-panel__list { display: grid; gap: var(--s1); margin-bottom: var(--s4); }
.mobile-panel__list a {
  display: flex; align-items: center; min-height: 2.75rem;
  font: 700 1.25rem/1 var(--font-head); text-transform: uppercase; letter-spacing: -0.01em; text-decoration: none;
}
.mobile-panel__list a:hover { color: var(--accent); }
.mobile-panel__list a[aria-current="page"] { color: var(--text); text-decoration: underline; text-decoration-color: var(--accent); text-decoration-thickness: 2px; text-underline-offset: 0.35em; }
.mobile-panel__mail { width: 100%; }

/* Desktop (~900px+): one row, wordmark left, links right, no button, no panel, JS or not. */
@media (min-width: 56.25em) {
  .site-header__bar { flex-wrap: nowrap; }
  .menu-toggle, html.js .menu-toggle { display: none !important; }
  .main-nav, html.js .main-nav { display: block !important; order: 2; flex: 1; flex-basis: auto; }
  .main-nav__list { justify-content: flex-end; padding-block: 0; }
  /* One row: only the bar's own 4px bottom padding sits between link and divider. */
  .main-nav__list a[aria-current="page"] { --nav-rule-gap: var(--s1); }
  .mobile-panel, html.js .mobile-panel { display: none !important; }
}

/* Buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: 3rem; padding: 0 var(--s4);
  border: 2px solid var(--accent); border-radius: var(--radius);
  background: transparent; color: var(--text);
  font: 700 1.125rem/1 var(--font-head);
  letter-spacing: 0.04em; text-transform: uppercase; text-decoration: none;
  text-align: center; overflow-wrap: anywhere; cursor: pointer;
  transition: transform 100ms ease-out;
}
.btn:hover { border-color: var(--accent-hover); color: var(--accent); }
.btn:active { transform: scale(0.97); }
.btn--primary { background: var(--accent); color: var(--bg); }
.btn--primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--bg); }
.btn--ghost { border-color: var(--muted); }
.btn:disabled, .btn[aria-disabled="true"] {
  border-style: dashed; border-color: var(--line); background: transparent;
  color: var(--muted); cursor: not-allowed;
}
.btn:disabled:active, .btn[aria-disabled="true"]:active { transform: none; }
.btn-row { display: flex; flex-wrap: wrap; gap: var(--s3); align-items: center; }

/* Sections */
.section { padding-block: var(--space-section); }
/* Section marker (2026-10-24): a short white bar instead of the old full-width hairline, left
   aligned with the text column. It sits inside .wrap so it lines up with the headline, and the
   section's own top padding is the gap above it. The thin grey 1px lines inside the timeline, the
   press list and the menu stay as they are, so the two kinds of line never look alike. */
.section > .wrap::before {
  content: ""; display: block; width: 40px; height: 3px; background: var(--text);
  margin-bottom: var(--space-heading);
}
/* No marker where a section opens the page. */
.section--first > .wrap::before { content: none; }
/* Rhythm: --tight is half the padding, .wrap--narrow pulls the text column in. (--surface and
   --bleed are gone since 2026-10-24 and 2026-10-25.) */
.section--tight { padding-block: var(--space-block); }
/* Narrow column: the measure gets shorter, the left edge stays exactly where every other section
   starts. Centring a narrower .wrap would push the text inwards and break that alignment. */
.wrap--narrow > * { max-width: 56rem; }
/* Hobbies photo (2026-10-25): inside the column, 2:1 on desktop, 4:3 on phones. */
.photo--hobbies { margin-top: var(--space-block); } /* 2026-10-25: photo sits under the list */
.photo--hobbies .photo__slot { aspect-ratio: 4 / 3; }
@media (min-width: 48em) { .photo--hobbies .photo__slot { aspect-ratio: 2 / 1; } }
.section__head { display: grid; gap: var(--s2); margin-bottom: var(--space-heading); }
/* The eyebrow labels above section headings are gone (2026-10-14), so the heading itself
   carries the gap that .section__head used to carry. */
.section-title { margin-bottom: var(--space-heading); }
.split { display: grid; gap: var(--space-block); }
@media (min-width: 48em) { .split { grid-template-columns: 1fr 1fr; align-items: start; } }

/* Photo slots. Photos only with written OK from the photographer. */
.photo { position: relative; }
.photo__slot {
  position: relative; display: grid; place-items: center; overflow: hidden;
  background: repeating-linear-gradient(135deg, #111 0 16px, #161616 16px 32px);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--s3); text-align: center;
}
.photo--portrait .photo__slot { aspect-ratio: 4 / 5; }
.photo--wide .photo__slot { aspect-ratio: 4 / 3; }
/* The slot's own padding centers the dashed TODO placeholder text; a real photo should bleed to
   the border instead (it already gets object-fit: cover below), so a slot that holds an <img>
   drops the padding. */
.photo__slot:has(img) { padding: 0; }
/* Bug fix (2026-09-28): a percentage-height img inside a display:grid parent whose own
   height comes only from `aspect-ratio` (no explicit height, no flex-grow) does not reliably resolve
   to 100% in this combination -- the image instead sizes itself from its own intrinsic aspect ratio,
   overflows the slot, and gets silently clipped by the slot's overflow:hidden, roughly evenly top and
   bottom. This made object-position on affected photos (the home cards, the Hobbies photo) have
   almost no visible effect. position:absolute + inset:0 sizes the image to the slot reliably, same
   technique already used for hero images. Found on .card-photo and .photo--wide; the about-page
   portraits were unaffected because their parent gets a definite height from flex, not aspect-ratio,
   but this fix is safe there too. */
.photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* christoph-rock.jpg: the source photo is a tall portrait (1558x2000) with Christoph's head near
   the top third. The hero's crop window is much wider than tall on desktop, so a centered crop
   (the default 50% 50%) cuts his head off entirely; the card's crop window is close to the
   source's own aspect ratio and barely crops at all, but gets a small matching bias too. Tuned
   and checked at 360, 768 and 1440px per later chat instructions. Do not touch the file itself. */
.photo-christoph-hero { object-position: 50% 18%; }
.photo-christoph-card { object-position: 50% 45%; } /* re-tuned 2026-09-28 for the taller 5/7 box and the position:absolute fix above, so the full Olympic rings show with headroom above the helmet */
.photo-florian-card { object-position: 50% 45%; } /* re-tuned 2026-09-28: goggles aligned to the same height as Christoph's card */
.photo-florian-about-portrait { object-position: 50% 30%; }
.photo-brothers-about { position: absolute; inset: 0; height: 100%; object-position: 50% 20%; } /* re-measured 2026-10-14: the H1 moved out of the text column, so the photo box is shorter than before and the old 92% showed only the boots. 20% puts both faces, torsos and gloves in frame with sky above. */
.photo-about-hobbies { object-position: 50% 50%; } /* 2026-10-25: file is a 2:1 crop of _originals/about-hobbies.jpg (6000x4000, box x 5-4505, y 939-3189), riders centred with about 10% air above the upper helmet and below the lower front wheel. Phones (4:3) only trim the sides, both riders stay in. */
.photo-christoph-about-portrait { object-position: 50% 40%; }
.photo-christoph-about-action { object-position: 50% 20%; } /* board tip and sun both in the 8:7 frame */
.photo-florian-about-action { object-position: 50% 12%; }
/* About on Christoph and Florian (rebuilt 2026-10-29). Markup: .about-bio (portrait + bio) and
   .about-facts (action photo + stat table) inside .about-split; the wrappers dissolve
   (display: contents) and the four blocks are placed in the .split grid. */
.about-split {
  --about-measure: 50ch;      /* tablets: longest bio line, measured at most 73 characters */
  --about-photo-ratio: 4 / 3; /* tablets: both photos share this frame */
}
.about-bio, .about-facts { display: contents; }
.about-split .stats { margin-top: 0; } /* the gap comes from the grid */
/* Phones: portrait, bio, action photo, stat table, in DOM order, gaps from the .split grid, 4:5 frames. */
/* Portrait tablets and narrow windows, 48em to 63.99em (2026-10-29): both photos side by side in
   one row, same width and height, then the bio over the full width (capped at --about-measure),
   then the stat table over the full width. Replaces the float and the stacked action photo. */
@media (min-width: 48em) and (max-width: 63.99em) {
  .about-portrait { grid-column: 1; grid-row: 1; }
  .about-action { grid-column: 2; grid-row: 1; }
  .about-split .photo__slot { aspect-ratio: var(--about-photo-ratio); }
  .about-split .prose { grid-column: 1 / -1; grid-row: 2; max-width: var(--about-measure); }
  .about-split .stats { grid-column: 1 / -1; grid-row: 3; }
}
/* Desktop from 64em: both columns share two grid rows. Row 1 is the bio, row 2 is the stat table;
   the portrait sits next to the bio, the action photo starts at the table's first line. Both photos
   share one 8:7 frame. */
@media (min-width: 64em) {
  .about-split { row-gap: 0; align-items: stretch; }
  .about-split figure { width: 100%; max-width: 28rem; grid-column: 1; }
  .about-split .photo__slot { aspect-ratio: 8 / 7; }
  .about-portrait { grid-row: 1; margin-bottom: var(--space-block); } /* same gap as under the bio */
  .about-action { grid-row: 2; }
  .about-split .prose { grid-column: 2; grid-row: 1; margin-bottom: var(--space-block); }
  .about-split .stats { grid-column: 2; grid-row: 2; align-self: start; }
}
.credit {
  margin-top: var(--s2);
  font: 400 0.75rem/1.4 var(--font-mono);
  letter-spacing: 0.04em; color: var(--muted);
}
/* Reusable on-image photo credit (2026-09-27): a small label sitting on the photo itself, bottom
   right, for the rare case where a licence requires the credit directly on the image (unlike
   .credit above, which sits beside/below a photo). Always on a light scrim, per the "never bare
   text on a busy photo" rule: needs a .photo__slot ancestor with position: relative (already the
   default) to anchor against. Add <span class="photo-credit">Credit: NAME</span> right after the
   <img> inside that slot. */
.photo-credit {
  position: absolute; z-index: 1; right: var(--s2); bottom: var(--s2);
  font: 400 0.6875rem/1.2 var(--font-mono); letter-spacing: 0.02em; color: #0B0B0B;
  background: rgb(242 239 232 / 0.82); padding: 0.15rem 0.4rem; border-radius: 2px;
}

/* Hero */
.hero {
  position: relative; display: flex; align-items: flex-end;
  min-height: min(88svh, 52rem);
  padding-block: var(--s6) var(--s5);
  isolation: isolate; overflow: hidden;
}
.hero__media { position: absolute; inset: 0; z-index: -2; }
.hero__media .photo__slot { position: absolute; inset: 0; border: 0; border-radius: 0; aspect-ratio: auto; place-items: start start; padding: var(--s7) var(--gutter) 0; background: none; } /* no placeholder stripes behind a hero photo: where the photo fades out, the page background shows */
.hero__media img { position: absolute; inset: 0; }
.hero__ambient { display: none; } /* landscape only (Home, Florian); its lazy image is never fetched on phones */
/* Scrim: text never sits on the bare photo. */
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to top, var(--bg) 0%, rgba(11, 11, 11, 0.88) 40%, rgba(11, 11, 11, 0.35) 75%, rgba(11, 11, 11, 0.55) 100%);
}
/* Split hero (home, Christoph, Florian), one component for all three (rebuilt 2026-10-28).
   Phones and portrait windows: the photo fills the whole hero, the text sits at the bottom on top
   of it, a long soft fade (on .hero__body::before) runs from clear above the text to the page
   background behind it. Landscape desktops (block further down): text left, photo right, as before.
   Height: one screen minus the header, in svh (iOS address bar). Portrait windows (phones and
   tablets) are capped at 87.5rem (1400px), so even a 1440px-tall tablet gets a hero that fills the screen; landscape
   windows keep the 56.25rem cap (block further down). --hero-text-end is where the landscape text
   column stops (wrap edge + --hero-text-w). */
.hero--split {
  --hero-text-w: 34rem;
  --hero-wrap: 72rem; /* width of .wrap */
  --hero-left: calc(max(0px, (100vw - var(--hero-wrap)) / 2) + var(--gutter)); /* left edge of the text column */
  --hero-text-end: calc(var(--hero-left) + var(--hero-text-w));
  min-height: min(calc(100svh - var(--header-total)), 87.5rem); /* the real header height (53px), so the hero ends exactly at the bottom of the first screen (2026-10-29, was --header-h + --s1 = 52px) */
}
.hero--split.hero--athlete { --hero-text-w: 30rem; --hero-line-w: 32rem; } /* photo starts at 30rem, small text may run to 32rem; further right it drops below 4.5:1 over the bright sky */
/* Portrait: --hero-face-zone is the photo-only strip at the top that holds the faces, --hero-fade-len
   the soft fade above the text. Together they are the hero's top padding, so the text can never
   climb into the faces; on short screens the hero grows instead. */
.hero--split {
  --hero-face-zone: clamp(11rem, 30svh, 20rem);
  --hero-fade-len: clamp(8rem, 22svh, 14rem);
  padding-top: calc(var(--hero-face-zone) + var(--hero-fade-len));
  /* under the button: --space-block plus the iPhone home-indicator area (2026-10-29; was 0, the button sat
     on the hero's edge, and on tall text blocks that edge lies below the first screen). The hero only has
     a min-height, so it grows with its content and never cuts anything off. */
  padding-bottom: calc(var(--space-block) + env(safe-area-inset-bottom, 0px));
}
.hero--split h1 { font-size: var(--fs-h1); }
.hero--home h1 { font-size: var(--fs-hero-home); }
.hero--athlete.hero--split h1 { font-size: var(--fs-hero-athlete); }
.hero--split .hero__media .photo__slot { inset: 0; }
/* Photo: object-position y = where the faces sit in the photo, so they land at that same fraction
   of the hero height whatever the window shape; a zoom from the bottom edge moves them higher.
   Only these two values change per page (see .photo-*-hero). */
.hero--split .hero__media img {
  inset: 0; width: 100%; height: 100%; max-width: none;
  object-position: var(--face-x, 50%) var(--face-y, 30%);
  transform: scale(var(--face-zoom, 1)); transform-origin: 50% 100%;
}
.photo-brothers-hero  { --face-x: 48%; --face-y: 46%; --face-zoom: 1.5; }  /* faces at 42 to 49 % of the photo, land at 13 to 24 % of the hero */
.photo-christoph-hero { --face-x: 52%; --face-y: 20%; --face-zoom: 1.065; } /* 2026-10-29: helmet top at 18.5 % of the photo lands at 13.2 %, board end at 53 % of the hero (phones: the photo fills the height, so only the zoom moves him) */
.photo-florian-hero   { --face-x: 55%; --face-y: 35%; --face-zoom: 1.45; } /* powder photo (2026-10-29): rider small, helmet at 34 % and board at 55.5 % of the photo; on phones (photo fills the height) the zoom alone sets the height: helmet at about 4 %, board at about 35 % of the hero */
/* Portrait tablets (2026-10-29): face zone and fade measured against the height left under the
   header, so hero, button and the --space-block under it fit the first screen (at 768x1024 the hero
   was 5px too tall), and Christoph a bit higher: helmet at about 10.7 % of the hero (photo fills
   the height, so only the zoom moves him: 1 - 0.815 x zoom). */
@media (min-width: 48em) and (orientation: portrait) {
  .hero--split {
    --hero-avail: calc(100svh - var(--header-total));
    --hero-face-zone: clamp(11rem, calc(var(--hero-avail) * .3), 20rem);
    --hero-fade-len: clamp(8rem, calc(var(--hero-avail) * .22), 14rem);
  }
  .photo-christoph-hero { --face-zoom: 1.095; }
}
/* Fade behind the text: clear at the top, about half at the middle, page background (the same
   --bg variable, so no edge) from the top of the text down. It always spans the full window width. */
.hero--split .hero__body { position: relative; }
.hero--split .hero__body::before {
  content: ""; position: absolute; z-index: -1;
  top: calc(-1 * var(--hero-fade-len)); bottom: calc(-1 * (var(--space-block) + env(safe-area-inset-bottom, 0px))); inset-inline: calc(50% - 50vw); /* down to the hero's edge, through the space under the button */
  background: linear-gradient(to bottom,
    transparent 0,
    color-mix(in srgb, var(--bg) 10%, transparent) calc(var(--hero-fade-len) * 0.22),
    color-mix(in srgb, var(--bg) 30%, transparent) calc(var(--hero-fade-len) * 0.42),
    color-mix(in srgb, var(--bg) 52%, transparent) calc(var(--hero-fade-len) * 0.6),
    color-mix(in srgb, var(--bg) 72%, transparent) calc(var(--hero-fade-len) * 0.78),
    color-mix(in srgb, var(--bg) 88%, transparent) var(--hero-fade-len),
    var(--bg) calc(var(--hero-fade-len) + 6rem));
}
.hero--split::after { background: none; }
.hero--split .facts, .hero--split .hero__line { max-width: var(--hero-line-w, var(--hero-text-w)); }
@media (min-width: 48em) and (orientation: landscape) {
  .hero--split { padding-top: var(--s6); padding-bottom: var(--s5); min-height: min(calc(100svh - var(--header-h) - var(--s1)), 56.25rem); }
  /* Landscape: the headline shares the row with the photo, so it is held to the same footprint the
     Barlow headline had (about 30rem for an athlete name, 34rem for the home claim). */
  .hero--split { --fs-hero-home: 3.5rem; --fs-hero-athlete: 3.875rem; }
  /* Landscape (rebuilt 2026-10-28): text on the plain page background left, the sharp photo right
     (same crop as before). No blurred copy and no filter: the photo itself fades in through an eased
     10-stop mask, from --fade-a to --fade-b (percent of the photo box). Behind the mask is the hero
     media layer in var(--bg), so the colour it fades into is exactly the page background. --fade-b
     sits just left of the people and boards, so they are never dimmed; only these two values change
     per page. A fine local noise layer (assets/noise.png, overlay blend, 4 %) over the left 55vw
     breaks up banding; it stops before the people. */
  .hero--split .hero__body::before { content: none; }
  .hero--split .hero__media { background: var(--bg); }
  /* Box width capped at the hero height (see the home rule below for why). */
  .hero--split { --hero-land-h: min(calc(100svh - var(--header-h) - var(--s1)), 56.25rem); }
  /* Bleed (2026-10-29): on landscape desktops the hero photo runs past the hero's bottom edge into the
     next section's top padding and fades out there. --hero-bleed is how far at most; each page ends its
     fade at its own point (Home only has 3.75 % of the hero height of real photo below the edge). */
  :root { --hero-bleed: clamp(80px, 14svh, 160px); }
  .hero--split { overflow-x: clip; overflow-y: visible; }
  .hero--split .hero__media .photo__slot { bottom: calc(-1 * var(--hero-bleed)); }
  .hero--split .hero__media .photo__slot > img { bottom: auto; height: calc(100% - var(--hero-bleed)); } /* same box as before: the hero's height */
  .hero--split::after { bottom: calc(-1 * var(--hero-bleed)); }
  /* the next section paints above the bleed; its content starts below the end of the fade */
  .hero--split + .section { position: relative; z-index: 1; }
  .hero--split + .section { padding-top: max(var(--space-section), var(--hero-bleed)); } /* all three heroes since Home runs into the bleed too (2026-10-29) */

  .hero--split .hero__media img {
    inset: 0 0 0 auto; width: min(calc(100vw - var(--hero-text-end) + 2rem), var(--hero-land-h));
    --fade-span: calc(var(--fade-b) - var(--fade-a));
    -webkit-mask-image: var(--photo-fade), var(--photo-fade-bottom);
    mask-image: var(--photo-fade), var(--photo-fade-bottom);
    -webkit-mask-composite: source-in; mask-composite: intersect;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: 100% 100%; mask-size: 100% 100%;

    --photo-fade-bottom: linear-gradient(to bottom, #000 calc(100% - var(--fade-bottom-end, 0%) - var(--fade-bottom, 0px)), transparent calc(100% - var(--fade-bottom-end, 0%)));
    /* left edge: 13 eased stops (smootherstep) from --fade-a to --fade-b, percent of the photo box.
       Christoph overrides it with his short 6rem edge. */
    --photo-fade: linear-gradient(to right,
      rgb(0 0 0 / 0) var(--fade-a),
      rgb(0 0 0 / .002) calc(var(--fade-a) + var(--fade-span) * .083),
      rgb(0 0 0 / .015) calc(var(--fade-a) + var(--fade-span) * .167),
      rgb(0 0 0 / .045) calc(var(--fade-a) + var(--fade-span) * .25),
      rgb(0 0 0 / .1) calc(var(--fade-a) + var(--fade-span) * .333),
      rgb(0 0 0 / .18) calc(var(--fade-a) + var(--fade-span) * .417),
      rgb(0 0 0 / .29) calc(var(--fade-a) + var(--fade-span) * .5),
      rgb(0 0 0 / .42) calc(var(--fade-a) + var(--fade-span) * .583),
      rgb(0 0 0 / .58) calc(var(--fade-a) + var(--fade-span) * .667),
      rgb(0 0 0 / .74) calc(var(--fade-a) + var(--fade-span) * .75),
      rgb(0 0 0 / .87) calc(var(--fade-a) + var(--fade-span) * .833),
      rgb(0 0 0 / .96) calc(var(--fade-a) + var(--fade-span) * .917),
      rgb(0 0 0 / 1) var(--fade-b));
  }
  .hero--split .hero__media::after {
    content: ""; position: absolute; inset: 0 auto 0 0; width: 55vw; pointer-events: none;
    background: url("../assets/noise.png") 0 0 / 96px 96px repeat;
    mix-blend-mode: overlay; opacity: .04;
    -webkit-mask-image: linear-gradient(to right, #000 85%, transparent);
    mask-image: linear-gradient(to right, #000 85%, transparent);
  }
  /* small text stays left of 43% of the page width, where Christoph's overlay is still dark enough
     for 4.5:1 over his brightest clouds (measured on rendered pixels 2026-10-28: at 45% the line
     reached 4.4:1 at 1280px) */
  .hero--split .facts, .hero--split .hero__line { max-width: min(var(--hero-line-w, var(--hero-text-w)), calc(43vw - var(--hero-left))); }
  /* Home hero photo: the right edge of the right board (71.3 % of the photo width) sits at 93 % of the page width.
     The photo box is at least as wide as the photo itself (min-width, right-anchored), so its width C is known
     without measuring: the board edge lands at 100vw - 0.27 C (0.27 = 0.4 - 1.15 * (0.713 - 0.6), from the zoom
     origin at 60 % and the 1.15 zoom), and translateX(27% - 7vw) moves it to 93vw at every window size. */
  /* Box width is capped at the hero height (the photos are about 3:4, so at most about a quarter
     of a photo is cropped). Without the cap, on very wide windows (2560x1440, hero capped at 900px) the box got
     twice as wide as the photo and cover cut off the heads (found 2026-10-28). */
  /* Home photo, landscape (2026-10-29): the file is now home-hero-ext (6006 x 9287: the original minus 38px
     left, 4px right, 78px bottom, plus 1301px of snow, tools/extend-home-bottom.py). --home-bw is the box
     width the old file had; the new box is .99372 of it and object-position is the old top edge as a
     length, so every original pixel stays where it was (under 1px), and the snow continues below. */
  .hero--home {
    --home-bw: max(calc(var(--hero-land-h) * .75), min(calc(100vw - var(--hero-text-end) + 2rem), var(--hero-land-h)));
    --home-w: calc(var(--home-bw) * .99372);
    --home-top: calc(var(--hero-land-h) - var(--home-bw) * 1.33333);
  }
  .hero--home .hero__media img { width: var(--home-w); min-width: 0; }
  /* Home (2026-10-29): the sharp photo fades in over as much real photo as there is left of the people
     (15 % of the box; people and boards start at 17 %), and that fade lies on top of the ambient layer
     below, so it never ends in an edge. Florian's edges come from his own mask (further down); the clip
     below applies to both. */
  .hero--split .hero__media .photo-brothers-hero,
  .hero--split .hero__media .photo-florian-hero {
    --fade-a: 0%;
    /* the zoomed box starts mid-pixel; Chrome painted that half-covered column outside the mask as a
       1px line (measured 2026-10-28, value 15 on the 11 background). Clip it off. */
    clip-path: inset(0 0 0 2px);
  }
  /* bottom: from 90.5 % of the hero (boards end at 88.5 %, plus 13px; local .8848 under the 1.15 zoom around
     75 %) to the end of the bleed, 13 eased stops; the box now reaches through the bleed (slot height), so
     the zoom origin is set in px to stay at 75 % of the hero */
  .hero--split .hero__media .photo-brothers-hero { --fade-b: 15%;
    --photo-fade-bottom: linear-gradient(to bottom, rgb(0 0 0 / 1.000) calc(var(--hero-land-h) * .8848), rgb(0 0 0 / 0.998) calc(calc(var(--hero-land-h) * .8848) + (calc(var(--hero-land-h) * .75 + (var(--hero-land-h) * .25 + var(--hero-bleed)) / 1.15) - calc(var(--hero-land-h) * .8848)) * 0.083), rgb(0 0 0 / 0.985) calc(calc(var(--hero-land-h) * .8848) + (calc(var(--hero-land-h) * .75 + (var(--hero-land-h) * .25 + var(--hero-bleed)) / 1.15) - calc(var(--hero-land-h) * .8848)) * 0.167), rgb(0 0 0 / 0.955) calc(calc(var(--hero-land-h) * .8848) + (calc(var(--hero-land-h) * .75 + (var(--hero-land-h) * .25 + var(--hero-bleed)) / 1.15) - calc(var(--hero-land-h) * .8848)) * 0.250), rgb(0 0 0 / 0.900) calc(calc(var(--hero-land-h) * .8848) + (calc(var(--hero-land-h) * .75 + (var(--hero-land-h) * .25 + var(--hero-bleed)) / 1.15) - calc(var(--hero-land-h) * .8848)) * 0.333), rgb(0 0 0 / 0.820) calc(calc(var(--hero-land-h) * .8848) + (calc(var(--hero-land-h) * .75 + (var(--hero-land-h) * .25 + var(--hero-bleed)) / 1.15) - calc(var(--hero-land-h) * .8848)) * 0.417), rgb(0 0 0 / 0.710) calc(calc(var(--hero-land-h) * .8848) + (calc(var(--hero-land-h) * .75 + (var(--hero-land-h) * .25 + var(--hero-bleed)) / 1.15) - calc(var(--hero-land-h) * .8848)) * 0.500), rgb(0 0 0 / 0.580) calc(calc(var(--hero-land-h) * .8848) + (calc(var(--hero-land-h) * .75 + (var(--hero-land-h) * .25 + var(--hero-bleed)) / 1.15) - calc(var(--hero-land-h) * .8848)) * 0.583), rgb(0 0 0 / 0.420) calc(calc(var(--hero-land-h) * .8848) + (calc(var(--hero-land-h) * .75 + (var(--hero-land-h) * .25 + var(--hero-bleed)) / 1.15) - calc(var(--hero-land-h) * .8848)) * 0.667), rgb(0 0 0 / 0.260) calc(calc(var(--hero-land-h) * .8848) + (calc(var(--hero-land-h) * .75 + (var(--hero-land-h) * .25 + var(--hero-bleed)) / 1.15) - calc(var(--hero-land-h) * .8848)) * 0.750), rgb(0 0 0 / 0.130) calc(calc(var(--hero-land-h) * .8848) + (calc(var(--hero-land-h) * .75 + (var(--hero-land-h) * .25 + var(--hero-bleed)) / 1.15) - calc(var(--hero-land-h) * .8848)) * 0.833), rgb(0 0 0 / 0.040) calc(calc(var(--hero-land-h) * .8848) + (calc(var(--hero-land-h) * .75 + (var(--hero-land-h) * .25 + var(--hero-bleed)) / 1.15) - calc(var(--hero-land-h) * .8848)) * 0.917), rgb(0 0 0 / 0.000) calc(var(--hero-land-h) * .75 + (var(--hero-land-h) * .25 + var(--hero-bleed)) / 1.15));
    object-position: 50% var(--home-top);
    transform: translateX(calc(var(--home-bw) * .27 - 7vw)) scale(1.15); transform-origin: 60% calc(var(--hero-land-h) * .75);
  }
  /* Christoph: reference look (state of 2026-10-28, 13:35, restored). His photo reaches under the text,
     so it keeps the original build: short 6rem edge mask, then the long eased overlay below
     (.hero--overlay-fade) runs over the photo itself. Box width capped at 1.3 x hero height, which
     leaves 1280 to 1920 exactly as before and keeps his head inside the hero at 2560. */
  .hero--split .hero__media .photo-christoph-hero {
    --photo-fade: linear-gradient(to right, transparent 0, #000 6rem);
    width: min(calc(100vw - var(--hero-text-end) + 2rem), calc(var(--hero-land-h) * 1.3));
    object-position: 70% 25%; transform: scale(1.4); transform-origin: 75% 25%;
    /* bottom fade in the photo's own (unzoomed) coordinates: feet at local .25 h + .363 x the displayed photo
       height (61.3 % of the photo, object-position 25 %); +10px local is 14px on screen. End: hero bottom
       plus the bleed, mapped back through the 1.4 zoom around 25 % of the height. */
    --ch-bw: min(calc(100vw - var(--hero-text-end) + 2rem), calc(var(--hero-land-h) * 1.3));
    --ch-ih: max(calc(var(--ch-bw) / .779), var(--hero-land-h));
    --ch-fade-a: calc(var(--hero-land-h) * .25 + var(--ch-ih) * .363 + 10px);
    --ch-fade-b: calc(var(--hero-land-h) * .25 + (var(--hero-land-h) * .75 + var(--hero-bleed)) / 1.4);
    --photo-fade-bottom: linear-gradient(to bottom, rgb(0 0 0 / 1.000) var(--ch-fade-a), rgb(0 0 0 / 0.998) calc(var(--ch-fade-a) + (var(--ch-fade-b) - var(--ch-fade-a)) * 0.083), rgb(0 0 0 / 0.985) calc(var(--ch-fade-a) + (var(--ch-fade-b) - var(--ch-fade-a)) * 0.167), rgb(0 0 0 / 0.955) calc(var(--ch-fade-a) + (var(--ch-fade-b) - var(--ch-fade-a)) * 0.250), rgb(0 0 0 / 0.900) calc(var(--ch-fade-a) + (var(--ch-fade-b) - var(--ch-fade-a)) * 0.333), rgb(0 0 0 / 0.820) calc(var(--ch-fade-a) + (var(--ch-fade-b) - var(--ch-fade-a)) * 0.417), rgb(0 0 0 / 0.710) calc(var(--ch-fade-a) + (var(--ch-fade-b) - var(--ch-fade-a)) * 0.500), rgb(0 0 0 / 0.580) calc(var(--ch-fade-a) + (var(--ch-fade-b) - var(--ch-fade-a)) * 0.583), rgb(0 0 0 / 0.420) calc(var(--ch-fade-a) + (var(--ch-fade-b) - var(--ch-fade-a)) * 0.667), rgb(0 0 0 / 0.260) calc(var(--ch-fade-a) + (var(--ch-fade-b) - var(--ch-fade-a)) * 0.750), rgb(0 0 0 / 0.130) calc(var(--ch-fade-a) + (var(--ch-fade-b) - var(--ch-fade-a)) * 0.833), rgb(0 0 0 / 0.040) calc(var(--ch-fade-a) + (var(--ch-fade-b) - var(--ch-fade-a)) * 0.917), rgb(0 0 0 / 0.000) var(--ch-fade-b));
  }
  .hero--overlay-fade::after {
    background: linear-gradient(90deg,
      var(--bg) 0%, var(--bg) 8%,
      color-mix(in srgb, var(--bg) 97%, transparent) 16%, color-mix(in srgb, var(--bg) 92%, transparent) 23%,
      color-mix(in srgb, var(--bg) 85%, transparent) 30%, color-mix(in srgb, var(--bg) 75%, transparent) 36%,
      color-mix(in srgb, var(--bg) 63%, transparent) 42%, color-mix(in srgb, var(--bg) 50%, transparent) 47%,
      color-mix(in srgb, var(--bg) 37%, transparent) 51%, color-mix(in srgb, var(--bg) 25%, transparent) 55%,
      color-mix(in srgb, var(--bg) 14%, transparent) 58%, color-mix(in srgb, var(--bg) 6%, transparent) 60%,
      transparent 62%);
  } /* the two of us fill about the right third */
  .hero--split .hero__media .photo__slot > .photo-florian-hero {
    /* powder photo (2026-10-29): the box is --fl-bw wide (set on the hero, shared with the ambient layer),
       the photo is taller than the box (width-bound cover), the rider's middle (45 % of the photo) sits at
       45 % of the hero; the photo continues .55 x (photo height - hero height) below the edge, the box
       grows by that, at most --hero-bleed, and object-position is a length so the crop stays put.
       Edges: the band mask (--fl-band, see the hero block below), the bottom fade (--fl-fade-bottom) and a
       ramp from 0 at the box's left edge to 1 at 12 % of the box: left of the box only the ambient layer
       has picture, so the sharp photo must start at 0 there or its edge shows as a vertical line. */
    width: var(--fl-bw);
    --fl-bleed: min(var(--hero-bleed), calc((var(--fl-ih) - var(--hero-land-h)) * .55));
    height: calc(100% - var(--hero-bleed) + var(--fl-bleed));
    object-position: 50% calc((var(--hero-land-h) - var(--fl-ih)) * .45); transform: none;
    --fl-edge: linear-gradient(to right, rgb(0 0 0 / 0) calc(var(--fl-bw) * .12 * 0.000), rgb(0 0 0 / 0.002) calc(var(--fl-bw) * .12 * 0.083), rgb(0 0 0 / 0.015) calc(var(--fl-bw) * .12 * 0.167), rgb(0 0 0 / 0.045) calc(var(--fl-bw) * .12 * 0.250), rgb(0 0 0 / 0.1) calc(var(--fl-bw) * .12 * 0.333), rgb(0 0 0 / 0.18) calc(var(--fl-bw) * .12 * 0.417), rgb(0 0 0 / 0.29) calc(var(--fl-bw) * .12 * 0.500), rgb(0 0 0 / 0.42) calc(var(--fl-bw) * .12 * 0.583), rgb(0 0 0 / 0.58) calc(var(--fl-bw) * .12 * 0.667), rgb(0 0 0 / 0.74) calc(var(--fl-bw) * .12 * 0.750), rgb(0 0 0 / 0.87) calc(var(--fl-bw) * .12 * 0.833), rgb(0 0 0 / 0.96) calc(var(--fl-bw) * .12 * 0.917), rgb(0 0 0 / 1) calc(var(--fl-bw) * .12 * 1.000));
    -webkit-mask-image: var(--fl-band), var(--fl-fade-bottom), var(--fl-edge);
    mask-image: var(--fl-band), var(--fl-fade-bottom), var(--fl-edge);
    -webkit-mask-composite: source-in, source-in; mask-composite: intersect, intersect;
    -webkit-mask-size: var(--fl-band-size), 100% 100%, 100% 100%; mask-size: var(--fl-band-size), 100% 100%, 100% 100%;
    -webkit-mask-position: var(--fl-band-pos), 0 0, 0 0; mask-position: var(--fl-band-pos), 0 0, 0 0;
  }


  /* Ambient layer (Home and Florian). Layer 1 is the same photo, same file and crop as the sharp one
     (it comes from the cache), stretched sideways and blurred, so it fills the band where the photo box
     has no picture. No brightness or saturation filter. Since 2026-10-30 each page masks it with its own
     band mask (Home: --home-curve, Florian: --fl-band), so it only shows inside the band; the old haze
     grid (opacity over 42vw and from 25 to 85 % of the height) is gone, it made the grey cloud and the
     dark shoulder on Home. Layer 3: a 13-stop fade into var(--bg) over it; the text sits on that fade,
     as on /christoph. */
  .hero--ambient .hero__ambient {
    display: block; position: absolute; inset: 0; overflow: hidden;
    -webkit-mask-composite: source-in; mask-composite: intersect;
  }
  /* Home (2026-10-30, rebuilt like Florian): tools/make-home-hero-curve.py writes the edge mask in
     fractions of the hero, 1.25 hero heights tall (through the bleed): a smooth 50 % line (the shoulder at
     47 to 62 % of the height smoothed out, the bottom along Christoph's brown line) with a Gaussian S around
     it, as wide as the text (plus 40px) and the two of us (minus 13px) allow. Two files, split at 80em,
     because the headline is relatively much wider at 1024 and 1180px. The same mask covers the sharp
     photo's slot (whose own 15 % left fade starts it at 0 at the box edge), the ambient layer (fills the
     band left of the box) and the noise. */
  .hero--home { --home-curve: url("../assets/home-hero-curve.webp?v=20261030b"); }
  @media (max-width: 79.99em) { .hero--home { --home-curve: url("../assets/home-hero-curve-compact.webp?v=20261030b"); } }
  .hero--home .hero__media .photo__slot {
    -webkit-mask-image: var(--home-curve); mask-image: var(--home-curve);
    -webkit-mask-size: 100% calc((100% - var(--hero-bleed)) * 1.25); mask-size: 100% calc((100% - var(--hero-bleed)) * 1.25);
    -webkit-mask-position: 0 0; mask-position: 0 0;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  }
  /* the anti-banding noise only on the dark side of the curve (noise mask times one minus the curve):
     on the bright haze next to the line it made neighbouring columns jump by up to 10 of 255 */
  .hero--home.hero--ambient .hero__media::after {
    -webkit-mask-image: linear-gradient(to right, #000 85%, transparent), var(--home-curve);
    mask-image: linear-gradient(to right, #000 85%, transparent), var(--home-curve);
    -webkit-mask-size: 100% 100%, 100vw 125%; mask-size: 100% 100%, 100vw 125%;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-composite: source-out; mask-composite: subtract;
  }
  /* bottom: the ambient layer stops at the hero's edge, so it fades out from 78 to 98 % of the hero
     (13 stops) under the sharp photo's own fade; otherwise the edge showed as a step (129 to 73) */
  .hero--home.hero--ambient .hero__ambient {
    --amb-bottom: linear-gradient(to bottom, rgb(0 0 0 / 1.000) 78.00%, rgb(0 0 0 / 0.998) 79.67%, rgb(0 0 0 / 0.985) 81.33%, rgb(0 0 0 / 0.955) 83.00%, rgb(0 0 0 / 0.900) 84.67%, rgb(0 0 0 / 0.820) 86.33%, rgb(0 0 0 / 0.710) 88.00%, rgb(0 0 0 / 0.580) 89.67%, rgb(0 0 0 / 0.420) 91.33%, rgb(0 0 0 / 0.260) 93.00%, rgb(0 0 0 / 0.130) 94.67%, rgb(0 0 0 / 0.040) 96.33%, rgb(0 0 0 / 0.000) 98.00%);
    -webkit-mask-image: var(--amb-bottom), var(--home-curve); mask-image: var(--amb-bottom), var(--home-curve);
    -webkit-mask-size: 100% 100%, 100% 125%; mask-size: 100% 100%, 100% 125%;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-composite: source-in; mask-composite: intersect;
  }
  .hero--split .hero__media .hero__ambient img {
    position: absolute; inset: 0 0 0 auto; height: 100%; max-width: none;
    -webkit-mask-image: none; mask-image: none; clip-path: none;
    filter: blur(var(--amb-blur, 70px));
  }
  /* same box and crop as the sharp photo, then stretched 10x to the left from the end of its fade
     (15 % of the box) */
  .hero--home .hero__media .hero__ambient img {
    width: var(--home-w); min-width: 0;
    object-position: 50% var(--home-top); transform-origin: 60% 75%;
    transform: translateX(calc(var(--home-bw) * .27 - 7vw)) scale(1.15) translateX(-45%) scaleX(10) translateX(45%);
    --amb-blur: 60px; /* times the 1.15 zoom: about 70px on screen */
  }
  /* Florian: photo box 58 % of the hero width (at most 1.35 x its height), shared by the sharp photo,
     its edge mask, the ambient copy and the haze ramp */
  .hero--athlete.hero--ambient {
    --fl-bw: min(58vw, calc(var(--hero-land-h) * 1.35));
    --fl-ih: max(calc(var(--fl-bw) * 1.5), var(--hero-land-h)); /* displayed photo height, 5152 x 7728 */
    /* Edge between page and photo (2026-10-29, fourth build): tools/make-florian-hero-curve.py, the
       50 % line on Christoph's chosen form, a Gaussian S around it. In the photo's own coordinates,
       1.6 photo widths wide (it reaches .6 widths left of the box) and one photo high, placed
       right-aligned with the box and at the photo's top, so the distance to the rider is the same at
       every window size. The same file masks the sharp photo and the ambient layer, which fills the band
       where the box has no photo (upper half: the chosen line lies on or left of the box edge). */
    --fl-band: url("../assets/florian-hero-curve.webp?v=20261030a");
    --fl-band-size: calc(var(--fl-bw) * 1.6) var(--fl-ih);
    --fl-band-pos: 100% calc((var(--hero-land-h) - var(--fl-ih)) * .45);
    /* bottom: 13 eased stops from 13px under the board (55 % of the photo) to the end of each box (photo:
       end of the bleed; ambient layer: the hero's bottom edge) */
    --fl-fa: calc(var(--hero-land-h) * .45 + var(--fl-ih) * .10 + 13px);
    --fl-fade-bottom: linear-gradient(to bottom, rgb(0 0 0 / 1.000) var(--fl-fa), rgb(0 0 0 / 0.998) calc(var(--fl-fa) + (100% - var(--fl-fa)) * 0.083), rgb(0 0 0 / 0.985) calc(var(--fl-fa) + (100% - var(--fl-fa)) * 0.167), rgb(0 0 0 / 0.955) calc(var(--fl-fa) + (100% - var(--fl-fa)) * 0.250), rgb(0 0 0 / 0.900) calc(var(--fl-fa) + (100% - var(--fl-fa)) * 0.333), rgb(0 0 0 / 0.820) calc(var(--fl-fa) + (100% - var(--fl-fa)) * 0.417), rgb(0 0 0 / 0.710) calc(var(--fl-fa) + (100% - var(--fl-fa)) * 0.500), rgb(0 0 0 / 0.580) calc(var(--fl-fa) + (100% - var(--fl-fa)) * 0.583), rgb(0 0 0 / 0.420) calc(var(--fl-fa) + (100% - var(--fl-fa)) * 0.667), rgb(0 0 0 / 0.260) calc(var(--fl-fa) + (100% - var(--fl-fa)) * 0.750), rgb(0 0 0 / 0.130) calc(var(--fl-fa) + (100% - var(--fl-fa)) * 0.833), rgb(0 0 0 / 0.040) calc(var(--fl-fa) + (100% - var(--fl-fa)) * 0.917), rgb(0 0 0 / 0.000) 100%);
  }
  .hero--athlete.hero--ambient .hero__ambient {
    -webkit-mask-image: var(--fl-band), var(--fl-fade-bottom); mask-image: var(--fl-band), var(--fl-fade-bottom);
    -webkit-mask-size: var(--fl-band-size), 100% 100%; mask-size: var(--fl-band-size), 100% 100%;
    -webkit-mask-position: var(--fl-band-pos), 0 0; mask-position: var(--fl-band-pos), 0 0;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-composite: source-in; mask-composite: intersect;
  }
  /* same box and crop as the sharp photo; origin at 9 % of the box: the first 9 % of the photo are
     stretched 8x over the band left of the box (8 x .09 - .09 = .6 box widths, as far as the mask file
     reaches), so at the box edge it shows the colours of the photo's own column at 8 % */
  .hero--athlete .hero__media .hero__ambient img {
    width: var(--fl-bw);
    object-position: 50% calc((var(--hero-land-h) - var(--fl-ih)) * .45); transform-origin: 9% 50%; transform: scaleX(8);
  }
  .hero--ambient::after {
    background: linear-gradient(to right,
      var(--bg) var(--hero-left),
      color-mix(in srgb, var(--bg) 99.8%, transparent) calc(var(--hero-left) + var(--amb-span-o) * .083),
      color-mix(in srgb, var(--bg) 98.5%, transparent) calc(var(--hero-left) + var(--amb-span-o) * .167),
      color-mix(in srgb, var(--bg) 95.5%, transparent) calc(var(--hero-left) + var(--amb-span-o) * .25),
      color-mix(in srgb, var(--bg) 90%, transparent) calc(var(--hero-left) + var(--amb-span-o) * .333),
      color-mix(in srgb, var(--bg) 82%, transparent) calc(var(--hero-left) + var(--amb-span-o) * .417),
      color-mix(in srgb, var(--bg) 71%, transparent) calc(var(--hero-left) + var(--amb-span-o) * .5),
      color-mix(in srgb, var(--bg) 58%, transparent) calc(var(--hero-left) + var(--amb-span-o) * .583),
      color-mix(in srgb, var(--bg) 42%, transparent) calc(var(--hero-left) + var(--amb-span-o) * .667),
      color-mix(in srgb, var(--bg) 26%, transparent) calc(var(--hero-left) + var(--amb-span-o) * .75),
      color-mix(in srgb, var(--bg) 13%, transparent) calc(var(--hero-left) + var(--amb-span-o) * .833),
      color-mix(in srgb, var(--bg) 4%, transparent) calc(var(--hero-left) + var(--amb-span-o) * .917),
      transparent calc(var(--hero-left) + var(--amb-span-o)));
    --amb-span-o: calc(var(--hero-text-end) - var(--hero-left));
  }
}
/* Christoph hero, last row: social icons and the button on one line, vertically centred. */
.hero__actions--social { align-items: center; gap: var(--s2) var(--s4); }
.hero__actions--social .social-icons { margin-inline-start: calc(var(--s2) * -1); }
/* Same chip look as .photo-credit, but top-right: the hero photo's bottom edge fades out (mask-image
   above) or sits behind the text column on desktop, so a bottom-right credit would be unreadable. */
.hero__credit {
  position: absolute; top: var(--s3); right: var(--gutter); bottom: auto; z-index: 1; margin: 0;
  font: 400 0.6875rem/1.2 var(--font-mono); letter-spacing: 0.02em; color: #0B0B0B;
  background: rgb(242 239 232 / 0.82); padding: 0.15rem 0.4rem; border-radius: 2px;
}
.hero > .wrap { width: 100%; }
.hero__body { display: grid; gap: var(--s3); }
.hero h1 { margin-top: var(--s1); }
.hero__line { max-width: 34rem; font-size: 1.125rem; text-wrap: balance; } /* balanced so the last line never holds a single word (2026-10-24) */
/* second sentence of a hero line that phones leave out so the button fits the first screen (2026-10-29);
   it stays in the HTML for tablets, desktops and search */
/* The home caption is its own line, not blended into hero__line. */
.hero__caption { max-width: 34rem; font-size: 1.125rem; font-weight: 500; letter-spacing: 0.01em; }
.hero__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s4); margin-top: var(--s2); }
.link-row { display: flex; flex-wrap: wrap; gap: 0 var(--s2); margin-inline-start: calc(var(--s2) * -1); }
.link-row a {
  display: inline-flex; align-items: center; min-height: 2.75rem; min-width: 2.75rem; padding-inline: var(--s2);
  font: 400 0.8125rem/1 var(--font-mono);
  letter-spacing: 0.06em; text-transform: uppercase; text-decoration: none;
  border-bottom: 1px solid transparent;
}
.link-row a:hover { color: var(--text); text-decoration: underline; }

/* Partner strip */
.partners__row { display: grid; gap: var(--s2) var(--s4); padding-block: var(--s3); }
.partners__row + .partners__row { border-top: 1px solid var(--line); }
@media (min-width: 48em) { .partners__row { grid-template-columns: 9rem 1fr; align-items: center; } }
.partners ul, .partners__row ul { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3) var(--s5); } /* About's row has no .partners ancestor, so it stacked before */
/* Text-fallback and sizing rules scoped to .partners__row (About's static row) and
   .sponsor-stripe__list (the Christoph/Florian partner marquee), the two containers that
   actually use them now. */
.partners__row li, .sponsor-stripe__list li {
  display: flex; align-items: center;
  font: 700 1.5rem/1.2 var(--font-head); letter-spacing: 0.02em; text-transform: uppercase;
}
/* Logos have fixed width/height attributes in the HTML (display size, not lazy), no CSS sizing. */
.partners__row img.logo, .sponsor-stripe__list img.logo { flex: none; max-width: none; }

/* Sponsor stripe: pure-CSS looping marquee for the Christoph and Florian sponsor rows only.
   The About page keeps the plain static .partners__row above (five logos, no loop needed).
   --sponsor-speed is the one number to tune the pace by eye. */
/* The band runs inside the page container (.wrap, 72rem max), never across the full window. Both
   edges fade out through a mask only, so whatever is behind shows through untouched; no tinted
   overlay on top of the logos. */
.sponsor-stripe {
  --sponsor-speed: 34s;
  overflow: hidden; max-width: 100%;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
}
.sponsor-stripe__track {
  display: flex; width: max-content;
  animation: sponsor-scroll var(--sponsor-speed) linear infinite;
}
.sponsor-stripe__list { display: flex; align-items: center; gap: var(--s5); padding-inline-end: var(--s5); flex: 0 0 auto; }
@keyframes sponsor-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
/* Pause so a logo can actually be read: on hover, and on touch (:active is the closest pure-CSS
   equivalent to a pointer-down state). */
.sponsor-stripe:hover .sponsor-stripe__track,
.sponsor-stripe:active .sponsor-stripe__track,
.sponsor-stripe__track:focus-within { animation-play-state: paused; }

/* Partner marquee: an in-page section, in normal flow between Career and Press on the Christoph
   and Florian pages, not fixed to the viewport. The About page keeps its plain static row above
   and never gets a marquee (five logos is still too few to loop). A single fixed lead-in label
   that never scrolls, logos loop to its right via .sponsor-stripe. */
.partners-marquee__row { display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap; }
.partners-marquee__label {
  flex: 0 0 auto;
  font: 400 0.8125rem/1 var(--font-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);
}
.partners-marquee .sponsor-stripe--band { flex: 1 1 16rem; min-width: 0; }
.sponsor-stripe--band { --logo-scale: .72; } /* phones: only a little bigger than before */
.sponsor-stripe--band .sponsor-stripe__track { animation-duration: var(--sponsor-speed-band, 30s); }
.sponsor-stripe--band .sponsor-stripe__list { gap: var(--s6); padding-inline-end: var(--s6); }
/* Logos: width/height attributes stay as the fixed desktop size (no layout shift); the inline --w
   scales them with --logo-scale. Wide logos are capped in the HTML, compact ones are taller. */
.sponsor-stripe--band img.logo--band { width: calc(var(--w) * var(--logo-scale) * 1px); height: auto; }
@media (min-width: 48em) {
  .sponsor-stripe--band { --logo-scale: 1; }
  .sponsor-stripe--band .sponsor-stripe__track { animation-duration: var(--sponsor-speed-band, 40s); }
  .sponsor-stripe--band .sponsor-stripe__list { gap: var(--s7); padding-inline-end: var(--s7); }
}

/* Career closing line: one sentence after Christoph's timeline. */
.timeline-closing {
  margin-top: var(--space-block); padding-top: var(--space-block); border-top: 1px solid var(--line);
  font: 500 1.25rem/1.3 var(--font-head); letter-spacing: -0.01em; text-transform: uppercase; color: var(--text);
}

/* About */
.stats { display: grid; gap: 0; border-top: 1px solid var(--line); }
.stats > div { display: grid; gap: var(--s1); padding-block: var(--s3); border-bottom: 1px solid var(--line); }
@media (min-width: 30em) { .stats > div { grid-template-columns: 9rem 1fr; gap: var(--s3); } }
.stats dt { font: 400 0.8125rem/1.6 var(--font-mono); letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.stats dd { margin: 0; }

/* Following numbers (About page) */
/* Media numbers: plain off-white, half the old size, one small grey tick (accent is buttons only). */
.bignum { display: grid; gap: var(--s1); }
.bignum__value {
  font: 700 clamp(1.75rem, 7.5vw, 3.375rem)/1 var(--font-head);
  letter-spacing: -0.02em; color: var(--text);
}
.bignum__caption { font-size: 1rem; color: var(--muted); }
.table { width: 100%; border-collapse: collapse; }
.table th, .table td { padding: var(--s3) 0; border-bottom: 1px solid var(--line); text-align: left; font-weight: 400; }
.table td { text-align: right; font: 700 1.5rem/1 var(--font-head); letter-spacing: 0; }
.table tfoot th, .table tfoot td { border-bottom: 0; color: var(--text); }
.table tfoot th { font: 700 1rem/1.6 var(--font-mono); text-transform: uppercase; letter-spacing: 0.06em; }
.table tfoot td { font-size: 2.5rem; }

/* Career */
/* Top results row (2026-10-24), replaces the single typographic line. Bold title at body size,
   small grey date or venue underneath, thin vertical rules between the items. One row on desktop
   without wrapping, two columns on phones. Same component on both pages. */
.results {
  display: flex; flex-wrap: wrap; margin-bottom: var(--space-block); /* central block gap to the first timeline rule (was the section gap, 64px) */
  /* Every item carries the same side padding, so equal flex shares give equal widths; the row is
     pulled left by that padding so the first title still starts on the text edge. */
  margin-inline-start: calc(-1 * var(--s4));
}
/* Rules sit between the items, so the first one has none and the row has no rule hanging off
   its right-hand end. */
.results li {
  flex: 1 1 10rem; min-width: 0;
  padding: var(--s1) var(--s4);
}
.results li + li { border-inline-start: 1px solid var(--line); }
.results__title { font-weight: 700; line-height: 1.25; }
.results__meta { margin-top: var(--s1); font-size: 0.875rem; line-height: 1.4; color: var(--muted); }
/* Desktop: all items in one row, never wrapping, equal width. Both pages carry four items. */
@media (min-width: 48em) {
  .results { flex-wrap: nowrap; }
  .results li { flex: 1 1 0; }
}
@media (max-width: 47.99em) {
  /* Two columns. The item that opens a row drops the rule, otherwise every second line would
     start with one. */
  .results li { flex: 0 1 calc(50% - 1px); } /* no grow, so a lone item in the last row keeps the column width */
  .results li:nth-child(odd) { border-inline-start: 0; }
}
.timeline li { display: grid; gap: var(--s1); padding-block: var(--s4); border-top: 1px solid var(--line); }
@media (min-width: 48em) { .timeline li { grid-template-columns: 12rem 1fr; gap: var(--s4); } .timeline li > div { max-width: 40rem; } }
.timeline .label { padding-top: 0.25rem; }
.timeline h3 { margin-bottom: var(--s1); }

.textlink { display: inline-flex; align-items: center; min-height: 2.75rem; margin-top: var(--s2); font-weight: 600; }
/* .textlink on its own is built for a link that stands alone on its line: inline-flex with a 44px
   touch target and a top margin. Inside running text that same box blows the line up to 44px and
   pushes the lines apart (the About paragraph with two of these measured 181px instead of 77px).
   .textlink--inline turns it back into plain inline text, so the line box is the normal one. */
.textlink--inline { display: inline; align-items: initial; min-height: 0; margin-top: 0; }

/* Media and press */
.media-actions[hidden] { display: none; } /* pending switch, see the HTML comment above each block */
.media-actions { display: grid; gap: var(--s4); margin-bottom: var(--s6); }
.media-actions > div { display: grid; gap: var(--s2); justify-items: start; }
.press h3 { margin-bottom: var(--s3); }
.press li + li { border-top: 1px solid var(--line); }
.press a {
  display: flex; justify-content: space-between; align-items: center; gap: var(--s3);
  min-height: 3.5rem; padding-block: var(--s2); text-decoration: none;
}
.press a { transition: transform 100ms ease-out; }
.press a:hover { color: var(--text); text-decoration: underline; text-underline-offset: 0.35em; }
.press a:active { transform: translateX(2px); }
.press__year { font: 400 0.8125rem/1 var(--font-mono); letter-spacing: 0.06em; color: var(--muted); white-space: nowrap; }
.press__arrow { color: var(--muted); }
.press__archive { margin-top: var(--s4); }

/* Contact */
.contact { display: grid; gap: var(--s4); }
.contact__mail { justify-self: start; max-width: 100%; }

/* Footer. id="contact" lives on the <footer> itself, present on every page. */
.site-footer { padding-block: var(--s6) var(--s5); border-top: 1px solid var(--line); background: var(--surface); }
.site-footer h2 { font: 400 0.8125rem/1.4 var(--font-mono); letter-spacing: 0.08em; color: var(--muted); text-transform: uppercase; margin-bottom: var(--s3); }
.footer-grid { display: grid; gap: var(--s5); }
/* 64em, not 48em (2026-09-27): at 48em to ~62em the Explore column under the 1fr/2fr
   split was only ~224px wide, not enough for "Home About Christoph Florian" on one line (verified
   at 768px: Christoph/Florian wrapped to their own row even with .footer-links__pair). Single-column
   stacking (full width, same as below 48em) now runs a bit further, so the row always fits. */
@media (min-width: 64em) {
  .footer-grid { grid-template-columns: 1fr 2fr; gap: var(--s5) var(--s6); }
  .footer-highlight, .footer-bottom { grid-column: 1 / -1; }
}
/* Say hey!: one row per brother, name left in the normal text font, the four social icons right. */
.follow-list { display: grid; gap: var(--s1); }
.follow-row { display: flex; align-items: center; gap: var(--s4); }
.follow-name { flex: 0 0 6.5rem; font: 500 1.125rem/1.2 var(--font-body); color: var(--text); }
.follow-row .social-icons { margin-inline-start: 0; flex-wrap: nowrap; }
.follow-row .social-icon-link:hover { color: var(--text); }
/* Below 360px (2026-10-24): a follow row was 104px name + 24px gap + four 44px icons with their
   gaps (188px) = 316px, on a 288px wide footer at 320px. This row, not the mail button, set the width
   of the whole footer grid and made the page scroll sideways. Narrower name column and gap; the
   icons keep their 44px touch targets and both rows stay aligned (fixed name column). */
@media (max-width: 22.49em) {
  .follow-row { gap: var(--s3); }
  .follow-name { flex-basis: 5.25rem; font-size: 1rem; }
}
.social-icons--placeholder .social-icon-link { opacity: 0.35; cursor: default; }
.social-icons--placeholder .social-icon-link:hover { color: var(--muted); }
.follow-todo { margin-top: var(--s2); }

/* CTA and wordmark side by side from tablet width up, stacked (CTA above wordmark) below that. */
.footer-highlight {
  grid-column: 1 / -1;
  padding-top: var(--s5); border-top: 1px solid var(--line);
  display: grid; gap: var(--s5);
}
@media (min-width: 48em) {
  .footer-highlight { grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: var(--s6); }
  .footer-wordmark { justify-self: end; }
}
.footer-cta { display: grid; gap: var(--s3); justify-items: start; }
.footer-cta .btn { width: 100%; }
@media (min-width: 30em) { .footer-cta .btn { width: auto; } }
/* Mail button on narrow phones (2026-10-24): at 320px the address on one line made the button
   316px wide, and both footer grids took that as their column width, so the page scrolled 12px
   sideways. The grid items may now shrink below their content (min-width: 0), the button never
   grows past its column and may wrap anywhere, and below 360px the type and side padding step down
   so the full address normally still fits on one line. Desktop is untouched: there is room, so none
   of this changes anything. */
.footer-highlight, .footer-cta { min-width: 0; }
.footer-cta .btn { max-width: 100%; overflow-wrap: anywhere; }
@media (max-width: 22.49em) {
  /* Same step-down for the mail button in the open phone menu (2026-10-24): at 320px it wrapped the
     address onto two lines. */
  .footer-cta .btn, .mobile-panel__mail { font-size: 1rem; padding-inline: var(--s3); }
}

/* Two lines, two weights. Line 1 flat off-white and bold, line 2 a narrower weight with a
   top-to-bottom light-to-mid grey gradient. Line gap stays tight; line 2's horizontal offset is
   set to land roughly under the "H" of "LECHNER" (about 35-40% of its width). */
.footer-wordmark {
  display: flex; flex-direction: column;
  font-size: var(--fs-wordmark); line-height: 1;
  letter-spacing: -0.02em; text-transform: uppercase;
  overflow-wrap: break-word;
}
.footer-wordmark__line { font-family: var(--font-display); }
.footer-wordmark__line--1 { font-weight: 400; color: var(--text); }
.footer-wordmark__line--2 {
  font-weight: 400; margin-left: 1.45em;
  /* Accent to white, same as the header wordmark (back from grey on 2026-10-25). */
  background: linear-gradient(to bottom, var(--accent) 0%, var(--text) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
/* Fallback for browsers without background-clip: text support, so line 2 never renders invisible. */
@supports not (background-clip: text) {
  .footer-wordmark__line--2 { color: var(--accent); }
}

/* Social icons: inline self-hosted SVGs, currentColor so the link's text color drives
   hover/active state the same way every other link on the site does. */
.social-icons { display: flex; flex-wrap: wrap; gap: var(--s1); margin-inline-start: calc(var(--s1) * -1); }
.social-icon-link {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 2.75rem; min-height: 2.75rem; padding: var(--s2);
  color: var(--muted); text-decoration: none;
}
.social-icon-link:hover { color: var(--text); }
.social-icon-link:active { color: var(--text); }
.social-icon-link svg { width: 1.375rem; height: 1.375rem; display: block; }
.footer-bottom {
  margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); align-items: center; justify-content: space-between;
}
.footer-links { display: flex; flex-wrap: wrap; gap: 0 var(--s4); }
.footer-links a { display: inline-flex; align-items: center; min-height: 2.75rem; min-width: 2.75rem; }
/* Christoph and Florian sit in one flex item, so the footer never wraps between the two names. */
.footer-links__pair { display: inline-flex; gap: 0 var(--s4); }
.footer-mail { display: inline-flex; align-items: center; min-height: 2.75rem; min-width: 2.75rem; overflow-wrap: anywhere; }

/* Legal pages */
.legal { padding-block: var(--s6) var(--s7); }
.legal > * + * { margin-top: var(--s3); }
.legal h1 { font-size: clamp(2.5rem, 9vw, 4.5rem); }
.legal h2 { font-size: 1.75rem; margin-top: var(--s5); }
.legal p, .legal address { max-width: 42rem; font-style: normal; }
.legal .back { display: inline-flex; align-items: center; min-height: 2.75rem; }

/* Accessibility preferences */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .btn, .btn:active, .press a:active, .athlete-card-link, .athlete-card-link:active { transition: none; transform: none; }
  html.js .mobile-panel, .menu-toggle { transition: none; }
  /* Card trace: no running dot, just a soft static accent tint on the ring instead. */
  .card-trace::before, .athlete-card-link.is-tracing .card-trace, .athlete-card-link.is-tracing .card-trace::before {
    animation: none !important;
  }
  .card-trace::before { background: color-mix(in srgb, var(--trace-color) 40%, transparent); }
  .athlete-card-link.is-tracing .card-trace { opacity: 0; }
  /* Sponsor stripe: no loop, logos just sit in a wrapping row instead. Covers both the About
     page (n/a, static already) and the in-page partner marquee on Christoph/Florian. */
  .sponsor-stripe { overflow: visible; -webkit-mask-image: none; mask-image: none; }
  .sponsor-stripe__track { animation: none; width: auto; flex-wrap: wrap; }
  .sponsor-stripe__list[aria-hidden="true"] { display: none; }
  /* The list itself wraps too, otherwise seven logos in one row run off a phone screen. */
  .sponsor-stripe__list { flex: 1 1 auto; flex-wrap: wrap; padding-inline-end: 0; row-gap: var(--s4); }
}
@media (prefers-reduced-transparency: reduce) {
  .site-header { background: var(--bg); -webkit-backdrop-filter: none; backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  :root { --muted: #D4D1C9; --line: #8C8C8C; }
  .site-header { background: var(--bg); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .btn, .photo__slot { border-width: 2px; border-color: var(--text); }
  .btn--primary { border-color: var(--bg); }
}

/* Joint site additions */
.h1--long { font-size: clamp(3rem, 12.5vw, 7rem); }
.two-col { display: grid; gap: var(--s6); }
@media (min-width: 48em) { .two-col { grid-template-columns: 1fr 1fr; align-items: start; } }
.two-col--tight { gap: var(--s3); }
.col-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s3); margin-bottom: var(--s3); font-size: 2rem; }
.combined { margin-top: var(--s6); padding-top: var(--s4); border-top: 1px solid var(--line); }
.bignum--gap { margin-top: var(--s5); }
.prose.growth p + p { margin-top: var(--s1); }
/* Media stats row (stacked on phones), then a full-width row with the media kit link. Three blocks
   by default; media-stats--two (Florian, only 2 stats so far) uses two columns instead, so the row
   doesn't leave an empty third column. */
/* Rules between the figures (2026-10-25), same build as the results row: 1px grey, full column
   height, the row pulled left by the padding so the first figure starts on the text edge. Phones:
   stacked, a horizontal rule between. */
.media-stats { display: grid; gap: var(--space-block); }
.media-stats > * + * { border-top: 1px solid var(--line); padding-top: var(--space-block); }
@media (min-width: 48em) {
  .media-stats { grid-template-columns: repeat(3, 1fr); gap: 0; align-items: stretch; margin-inline-start: calc(-1 * var(--s4)); }
  .media-stats > * { padding-inline: var(--s4); }
  .media-stats > * + * { border-top: 0; padding-top: 0; border-inline-start: 1px solid var(--line); }
}
@media (min-width: 48em) { .media-stats--two { grid-template-columns: repeat(2, 1fr); } }
.media-links { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: var(--s2) var(--s4); margin-top: var(--space-block); }
.hero__actions .textlink { margin-top: 0; }
.hero--athlete .link-row { margin-top: var(--s2); }

.athletes { display: grid; gap: var(--s3); }
@media (min-width: 48em) { .athletes { grid-template-columns: 1fr 1fr; gap: var(--s4); } }
.athlete-card {
  display: grid; align-content: start; gap: var(--s3); padding: var(--s4);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
}
.athlete-card h3 { font-size: clamp(2rem, 6vw, 3rem); }
.athlete-card .btn { justify-self: start; }
/* Eye-catcher facts: a compact row of short facts, used on the home athlete cards and directly
   under the athlete-page hero name. Wraps naturally in narrow containers (the home card, or
   mobile) and stays close to a single row where there is room. */
.facts { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); margin-top: var(--s2); }
.facts li { padding-left: var(--s3); border-left: 2px solid var(--muted); font-size: 0.9375rem; }
/* Athlete hero (2026-10-25): the chips always stack, one per line, left-aligned, at every width. */
.hero .facts { flex-direction: column; flex-wrap: nowrap; align-items: flex-start; gap: var(--s2); }
.hero .facts li { white-space: nowrap; }
/* Below 360px the longest chip ("Only Austrian Snowboard Halfpipe Rider") is wider than the text
   column, so there it may wrap, balanced, instead of running into the side gutter. */
@media (max-width: 22.49em) { .hero .facts li { white-space: normal; text-wrap: balance; } }

.lead-sm { font: 500 1.75rem/1.15 var(--font-head); letter-spacing: -0.01em; text-transform: uppercase; }
.steps { margin-block: var(--s4); max-width: 42rem; }
.steps li { display: grid; grid-template-columns: 3rem 1fr; gap: var(--s3); align-items: baseline; padding-block: var(--s3); border-top: 1px solid var(--line); }
.steps__n { font: 700 2.5rem/1 var(--font-head); color: var(--text); }
.steps p { font-size: 1.125rem; }
.note + .prose { margin-top: var(--s3); }

.gallery { display: grid; gap: var(--s3); grid-template-columns: 1fr; }
@media (min-width: 30em) { .gallery { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64em) { .gallery { grid-template-columns: repeat(3, 1fr); } }
.btn-stack { display: grid; gap: var(--s2); justify-items: start; margin-top: var(--s2); }

.backlink { display: inline-flex; align-items: center; min-height: 2.75rem; }

.why__d { margin-top: var(--s1); color: var(--muted); max-width: 40rem; }
.why--us .why__t { font-size: clamp(1.5rem, 4.5vw, 2rem); }

/* Restructure v2: home hero + athlete cards, first-section border reset */
.hero__scroll { gap: var(--s1); }
.section--first { border-top: 0; }

.athlete-cards { display: grid; gap: var(--s4); }
@media (min-width: 40em) { .athlete-cards { grid-template-columns: 1fr 1fr; } }
.athlete-card-link {
  --trace-color: var(--text);
  --trace-width: 2px;
  --trace-lap: 2s;
  position: relative; isolation: isolate;
  display: grid; gap: var(--s3); align-content: start; padding: var(--s4);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  text-decoration: none; color: var(--text);
  transition: transform 100ms ease-out;
}
.athlete-card-link:active { transform: scale(0.99); }
.athlete-card-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.athlete-card-link h3 { font-size: clamp(2rem, 6vw, 3rem); }
.athlete-card-link__line { color: var(--muted); }
.card-photo .photo__slot { aspect-ratio: 5 / 7; } /* was 4/5 until 2026-09-28: taller now so Christoph's card can show the full Olympic rings with headroom above the helmet. The image-overflow bug fix is general now, see .photo img above; this only changes the box proportions. Florian's card grows by the same amount, both stay the same size as each other. */
.athlete-card-link .btn { pointer-events: none; }

/* Card trace (later chat instructions, replaces the old 1400ms one-shot ring): a glowing dot
   with a short tail runs once around the card border, 2s per lap. Desktop: only while hovered
   or focused, loops as long as it is.
   Mobile/touch: once, automatically, the first time the card scrolls into view (js/cards.js adds
   .is-tracing via IntersectionObserver, progressive enhancement - without JS the card just shows
   its plain resting border, see .athlete-card-link above). */
@property --trace-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}
.card-trace {
  position: absolute; inset: -1px; border-radius: inherit;
  pointer-events: none; opacity: 0;
  filter: drop-shadow(0 0 3px var(--trace-color)) drop-shadow(0 0 8px var(--trace-color));
  transition: opacity 200ms ease-out;
  z-index: 1;
}
.card-trace::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; padding: var(--trace-width);
  background: conic-gradient(
    from var(--trace-angle),
    transparent 0deg 240deg,
    color-mix(in srgb, var(--trace-color) 20%, transparent) 280deg,
    color-mix(in srgb, var(--trace-color) 75%, transparent) 335deg,
    var(--trace-color) 354deg,
    #fff 358deg,
    transparent 360deg
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}
@media (hover: hover) {
  .athlete-card-link:hover .card-trace, .athlete-card-link:focus-visible .card-trace { opacity: 1; }
  .athlete-card-link:hover .card-trace::before, .athlete-card-link:focus-visible .card-trace::before {
    animation: card-trace var(--trace-lap) linear infinite;
  }
}
.athlete-card-link.is-tracing .card-trace { animation: card-trace-fade calc(var(--trace-lap) * 1.1) ease-out 1 forwards; }
.athlete-card-link.is-tracing .card-trace::before { animation: card-trace var(--trace-lap) linear 1 forwards; }
@keyframes card-trace { to { --trace-angle: 360deg; } }
@keyframes card-trace-fade { 0%, 85% { opacity: 1; } 100% { opacity: 0; } }

/* Why Us: three plain points, no numbers, no boxes, three columns from 48em up. */
.why { display: grid; gap: var(--s5); }
@media (min-width: 48em) { .why { grid-template-columns: repeat(3, 1fr); gap: var(--s6); } }
.why p { font-size: 1.125rem; max-width: 24rem; }
.why strong { display: block; font-weight: 700; }

/* Brothers (restored 2026-10-14 from git HEAD 5847c64, text left and photo right) */
/* Phones: headline, then photo, then the body text. The photo sits in the second grid column, so
   it is pulled in front of the text column below 48em. */
@media (max-width: 47.99em) { .brothers-split .brothers-photo { order: -1; } }
@media (min-width: 48em) {
  .brothers-photo { width: 88%; margin-inline-start: auto; }
  /* Both columns end on the same line: the photo takes up the height the text column needs. */
  .brothers-split { align-items: stretch; }
  .brothers-photo { display: flex; flex-direction: column; }
  .brothers-photo .photo { flex: 1 1 auto; display: flex; }
  .brothers-photo .photo__slot { flex: 1 1 auto; aspect-ratio: auto; min-height: 24rem; }
}

/* First-screen section (2026-10-28, /about "The Brothers"): on landscape windows from 48em it is
   exactly one screen high (minus the header, svh, capped at 56.25rem like the heroes, never below
   38rem so nothing gets squeezed). Headline on top, below it text left and the portrait photo right;
   the photo column is as tall as the space left and as wide as the photo's own 3:4 ratio, so the
   portrait is never cropped into a landscape. Phones and portrait windows keep the normal flow. */
@media (min-width: 48em) and (orientation: landscape) {
  .section--screen {
    height: max(min(calc(100svh - var(--header-h) - var(--s1)), 56.25rem), 38rem);
    display: flex; flex-direction: column; justify-content: center;
  }
  .section--screen > .wrap {
    flex: 1 1 auto; min-height: 0; width: 100%;
    display: grid; grid-template-rows: auto minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
  }
  .section--screen .brothers-split {
    min-height: 0; grid-template-columns: minmax(0, 1fr) auto; align-items: stretch; gap: var(--space-section);
  }
  .section--screen .brothers-split .prose { max-width: 36rem; align-self: center; } /* about 65 characters per line */
  .section--screen .brothers-split > div:first-child { display: flex; align-items: center; }
  .section--screen .brothers-photo { width: auto; height: 100%; aspect-ratio: 3 / 4; margin: 0; }
  .section--screen .brothers-photo .photo,
  .section--screen .brothers-photo .photo__slot { height: 100%; min-height: 0; aspect-ratio: auto; }
}

/* The Brothers page (about/index.html) */
.h1--block { margin-bottom: var(--space-heading); } /* line height comes from --lh-display now */
.partners__row--standalone { grid-template-columns: 1fr; margin-bottom: var(--s5); }
.hobby-list li { padding-block: var(--s2); border-top: 1px solid var(--line); }
.hobby-list li:last-child { border-bottom: 1px solid var(--line); }
.info-card { max-width: 40rem; } /* no box: the content sits on the page background */
.info-card h3 { margin-bottom: var(--s2); color: var(--text); }
.info-card__goal { margin-top: var(--s3); color: var(--text); font-weight: 600; }
.press__title { display: flex; flex-wrap: wrap; gap: 0 var(--s2); }
.press__outlet { font-weight: 600; }

/* Christoph, narrow landscape (1024 to 1279px, 2026-10-29): the top right corner of "CHRISTOPH" sat on
   bright sky (3.5:1 at 1024x768). The photo box moves right by --ch-shift, so his elbow (leftmost point)
   moves from 57 to 61 % of the width at 1180px and from 61 to 65 % at 1024px, and the overlay can be
   denser behind the headline and still end before him. From 1280px on nothing changes. */
@media (min-width: 64em) and (max-width: 79.99em) and (orientation: landscape) {
  .hero--split .hero__media .photo-christoph-hero { --ch-shift: 3rem; transform: translateX(var(--ch-shift)) scale(1.4); }
  /* denser behind the headline (66 % page background at 49 %, where "CHRISTOPH" ends at 1024px) and
     done by 60 %: with the shift his elbow, the leftmost point of him, sits at 61 to 65 % of the width */
  .hero--overlay-fade::after {
    background: linear-gradient(90deg,
      var(--bg) 0%,
      var(--bg) 10%,
      color-mix(in srgb, var(--bg) 98%, transparent) 19%,
      color-mix(in srgb, var(--bg) 95%, transparent) 27%,
      color-mix(in srgb, var(--bg) 90%, transparent) 34%,
      color-mix(in srgb, var(--bg) 84%, transparent) 40%,
      color-mix(in srgb, var(--bg) 76%, transparent) 45%,
      color-mix(in srgb, var(--bg) 66%, transparent) 49%,
      color-mix(in srgb, var(--bg) 52%, transparent) 52.5%,
      color-mix(in srgb, var(--bg) 37%, transparent) 55%,
      color-mix(in srgb, var(--bg) 22%, transparent) 57%,
      color-mix(in srgb, var(--bg) 10%, transparent) 58.5%,
      color-mix(in srgb, var(--bg) 3%, transparent) 59.5%,
      transparent 60%);
  }
}
