/*
  LeanOn site: shared layout and shared patterns. Colors come exclusively
  from tokens.css via var(--...); the only derived values are alpha mixes of
  those tokens (color-mix), so both themes stay a token change.

  The voice is the product family's: the apps set calm system type on cool
  panel surfaces with hairline borders; the site says the same thing louder,
  through scale contrast and surface treatment, not through a new face.

  The heart mark has no icon file; pages inline the SVG:
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
      <path d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/>
    </svg>
*/

/* ---------- the display face ---------- */
/* Bricolage Grotesque (OFL), self-hosted; no request leaves the site.
   Variable file, one per subset, weights 500-700. Body text stays on the
   system stack: the display face carries identity, the body carries speed. */
@font-face {
  font-family: "Bricolage Grotesque";
  src: url("/assets/fonts/bricolage-latin.woff2") format("woff2");
  font-weight: 500 700;
  font-display: swap;
  unicode-range: U+0000-00FF, U+2013-2014, U+201C-201E, U+2026;
}
@font-face {
  font-family: "Bricolage Grotesque";
  src: url("/assets/fonts/bricolage-latin-ext.woff2") format("woff2");
  font-weight: 500 700;
  font-display: swap;
  unicode-range: U+0100-024F, U+1E00-1EFF;
}

/* ---------- base ---------- */

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  margin: 0;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

.shell {
  max-width: 68rem;
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2rem);
}

/* Anchor targets (#impressum, #datenschutz, #imprint, #privacy) need air
   above them when jumped to. */
:target,
section[id] {
  scroll-margin-top: 2rem;
}

/* ---------- header ---------- */

header.site {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  padding-block: 1.1rem;
  border-bottom: 1px solid var(--hair);
  /* Always visible: sticky, stretched to the viewport edges (the header sits
     inside .shell, so it earns full-bleed via the margin/padding trade), on a
     translucent wash of the ground so content slides beneath it legibly. */
  position: sticky;
  top: 0;
  z-index: 50;
  margin-inline: calc(50% - 50vw);
  padding-inline: calc(50vw - 50%);
  background: color-mix(in srgb, var(--ground) 88%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.brand {
  font-family: "Bricolage Grotesque", -apple-system, "Segoe UI", sans-serif;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--ink);
  text-decoration: none;
  font-weight: 650;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  margin-right: auto;
}
.brand svg {
  width: 1.35rem;
  height: 1.35rem;
  color: var(--brand);
  flex: none;
}

nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem;
}
nav a {
  color: var(--ink);
  text-decoration: none;
  padding: 0.45rem 0.65rem;
  border-radius: 0.4rem;
  transition: color 120ms ease-out, background-color 120ms ease-out;
}
nav a:hover {
  color: var(--brand-strong);
  background: var(--brand-soft);
}
nav a[aria-current="page"] {
  color: var(--brand-strong);
  font-weight: 600;
}

.lang-switch {
  color: var(--muted);
  text-decoration: none;
  padding: 0.45rem 0.55rem;
  border: 1px solid var(--hair);
  border-radius: 0.4rem;
  font-size: 0.85rem;
  line-height: 1;
  transition: color 120ms ease-out, border-color 120ms ease-out;
}
.lang-switch:hover {
  color: var(--brand-strong);
  border-color: var(--brand);
}

/* Under ~26rem the nav takes its own row beneath the brand. */
@media (max-width: 26rem) {
  nav {
    order: 3;
    width: 100%;
    margin-inline: -0.65rem;
  }
}

/* ---------- type scale ---------- */

main > h1,
.hero h1 {
  font-family: "Bricolage Grotesque", -apple-system, "Segoe UI", sans-serif;
  font-size: clamp(2.3rem, 6vw, 3.6rem);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.015em;
  text-wrap: balance;
  margin: 2.5rem 0 1rem;
}

h2 {
  font-family: "Bricolage Grotesque", -apple-system, "Segoe UI", sans-serif;
  font-weight: 600;
  font-size: 1.6rem;
  line-height: 1.25;
  letter-spacing: -0.01em;
  text-wrap: balance;
  margin: 2.75rem 0 0.9rem;
}

h3 {
  font-size: 1.05rem;
  margin: 1.5rem 0 0.3rem;
}
h3 + p,
h3 + ul {
  margin-top: 0;
}

.lede,
.hero .sub {
  font-size: 1.2rem;
  line-height: 1.5;
  color: var(--muted);
  max-width: 40rem;
  margin: 0 0 1.75rem;
}

main > section {
  margin-block: 3.25rem;
}
main > section > p,
main > section > ul {
  max-width: 44rem;
}

/* ---------- shared patterns ---------- */

.button {
  display: inline-block;
  padding: 0.7rem 1.4rem;
  border: none;
  border-radius: 0.55rem;
  background: var(--brand);
  color: var(--on-brand);
  text-decoration: none;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 120ms ease-out, transform 120ms ease-out;
}
.button:hover {
  background: var(--brand-strong);
}
.button:active {
  transform: translateY(1px);
}
.button:disabled {
  opacity: 0.6;
  cursor: default;
  transform: none;
}
.button.secondary {
  background: var(--brand-soft);
  color: var(--brand-strong);
}
.button.secondary:hover {
  background: var(--brand-soft);
  color: var(--ink);
}

/* Screenshot cards: the product's own surface language: a panel, a hairline,
   a soft real shadow (offset + blur, token-derived). */
.shots {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1.75rem;
  margin: 0;
  padding: 0;
}
.shots figure {
  margin: 0;
}
.shots img,
.media-card {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--hair);
  border-radius: 0.75rem;
  background: var(--panel);
  box-shadow:
    0 1px 2px color-mix(in srgb, var(--ink) 6%, transparent),
    0 10px 28px -12px color-mix(in srgb, var(--ink) 14%, transparent);
}
.shots figcaption {
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.5;
  margin-top: 0.6rem;
  max-width: 34rem;
}

/* Mission statement: one large, quiet claim in the Waymo-style register,
   carried by type scale, not by decoration. */
.statement {
  font-family: "Bricolage Grotesque", -apple-system, "Segoe UI", sans-serif;
  font-weight: 500;
  font-size: clamp(1.4rem, 3vw, 2rem);
  line-height: 1.35;
  letter-spacing: -0.015em;
  font-weight: 600;
  max-width: 48rem;
  text-wrap: balance;
  margin: 0 0 1rem;
}
.statement em {
  font-style: normal;
  color: var(--brand-strong);
}

/* The improvement ledger: before / with LeanOn, as typography not cards. */
.improve {
  display: grid;
  gap: 2rem 3.5rem;
  margin-top: 1.5rem;
}
@media (min-width: 54rem) {
  .improve { grid-template-columns: 1fr 1fr; }
}
.improve h3 { margin-top: 0; }
.improve .before {
  color: var(--muted);
  margin: 0 0 0.35rem;
}
.improve .after { margin: 0; }

/* Story beats: the deck's narrative as alternating image/text rows.
   Image and text swap sides per beat on wide screens; stacked, image first,
   on narrow ones. */
.story {
  display: grid;
  gap: 1.5rem;
  align-items: center;
  margin-block: 3.5rem;
}
.story h2 {
  margin-top: 0;
}
.story p {
  max-width: 34rem;
}
/* Portrait phone captures would tower over their text; a capped crop keeps
   every beat level. Top-anchored: an app screen's header is its identity. */
.story-media img {
  max-height: 30rem;
  object-fit: cover;
  object-position: top;
}
@media (min-width: 54rem) {
  .story {
    grid-template-columns: 1fr 1fr;
    gap: 3.5rem;
  }
  .story:nth-of-type(even) .story-media {
    order: 2;
  }
}

/* ---------- footer ---------- */

footer.site {
  color: var(--muted);
  border-top: 1px solid var(--hair);
  margin-top: 4rem;
  padding-block: 1.75rem;
  font-size: 0.9rem;
}
footer.site a {
  color: var(--muted);
  text-underline-offset: 0.2em;
}
footer.site a:hover {
  color: var(--brand-strong);
}

/* ---------- forms (demo page) ---------- */

.field {
  margin-bottom: 1.15rem;
}
.field label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.35rem;
}
.field input,
.field select,
.field textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--hair);
  border-radius: 0.45rem;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  transition: border-color 120ms ease-out, box-shadow 120ms ease-out;
}
.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 25%, transparent);
}
.field textarea {
  min-height: 7rem;
  resize: vertical;
}

/* ---------- focus & motion ---------- */

:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* Hero band: a full-bleed wash from brand-soft into the ground, so the page
   opens on a surface rather than on bare paper. */
.hero {
  margin-inline: calc(50% - 50vw);
  padding-inline: calc(50vw - 50%);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--brand-soft) 55%, var(--ground)) 0%,
    var(--ground) 100%);
  border-bottom: 1px solid color-mix(in srgb, var(--hair) 60%, transparent);
}

/* Screenshot chrome: every capture sits in a quiet browser frame: a panel
   card with a title bar and three dots. CSS-only; the img keeps its own
   rounded corners inside. */
.story-media,
.shots figure {
  background: var(--panel);
  border: 1px solid var(--hair);
  border-radius: 0.85rem;
  padding: 1.75rem 0.6rem 0.6rem;
  position: relative;
  box-shadow:
    0 1px 2px color-mix(in srgb, var(--ink) 6%, transparent),
    0 14px 34px -14px color-mix(in srgb, var(--ink) 16%, transparent);
}
.story-media::before,
.shots figure::before {
  content: "";
  position: absolute;
  top: 0.68rem;
  left: 0.85rem;
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 50%;
  color: var(--hair);
  background: currentColor;
  box-shadow: 0.7rem 0 0 currentColor, 1.4rem 0 0 currentColor;
}
.story-media img,
.story-media svg.vignette,
.shots svg.vignette,
.shots img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--hair);
  border-radius: 0.45rem;
  box-shadow: none;
  background: var(--ground);
}
.shots figcaption {
  padding: 0.35rem 0.35rem 0.2rem;
}

/* The hero's arrival animation moved into the 2026 interactive layer block
   at the end of this file, now gated behind .js-motion like every other
   effect there. */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition: none !important;
    animation: none !important;
  }
}

/* ---------- lightbox for product screenshots ---------- */
/* A screenshot at grid width proves little; click opens the full frame in a
   native dialog. Only .zoomable figures participate; vignettes stay plain. */
.zoomable {
  all: unset;
  display: block;
  width: 100%;
  cursor: zoom-in;
}
.zoomable:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
  border-radius: 0.85rem;
}
dialog.lightbox {
  border: none;
  padding: 0;
  background: transparent;
  max-width: min(96vw, 1400px);
  max-height: 92vh;
}
dialog.lightbox::backdrop {
  background: color-mix(in srgb, var(--ink) 55%, transparent);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
dialog.lightbox img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 92vh;
  object-fit: contain;
  border-radius: 0.6rem;
  cursor: zoom-out;
}

/* ==========================================================================
   2026 interactive layer (motion.js)

   motion.js adds `.js-motion` on <html>, and only when reduced motion is
   off. Every hidden/offset starting state below is therefore scoped under
   `.js-motion`: no script, or a visitor who asked for less motion, gets the
   page exactly as calm and complete as it already reads without this block.
   The MPA view-transition and the lightbox open transition are the two
   exceptions: both are native browser behaviour that needs no script to
   run, so they are gated on the media query alone.
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {

  /* ---------- scroll reveals ---------- */
  /* Fade and rise once per element; alternating story beats slide in from
     the side instead, via a modifier class chosen in the HTML. */
  .js-motion [data-reveal] {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity 350ms ease, transform 350ms ease;
  }
  .js-motion [data-reveal].slide-left { transform: translateX(-16px); }
  .js-motion [data-reveal].slide-right { transform: translateX(16px); }
  .js-motion [data-reveal].in-view {
    opacity: 1;
    transform: none;
  }

  /* ---------- vignette draw-in ---------- */
  /* pathLength="100" on each primary stroke normalises the dash length, so
     one rule draws every icon path and curve at the same visual speed
     regardless of its real length. Fills, panels and hairline grids carry
     no [data-draw] and stay put. */
  .js-motion [data-draw] {
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    transition: stroke-dashoffset 700ms ease-out;
  }
  .js-motion [data-reveal].in-view [data-draw] {
    stroke-dashoffset: 0;
  }

  /* ---------- header depth ---------- */
  /* motion.js toggles .scrolled once window.scrollY passes 8px. */
  .js-motion header.site {
    transition: box-shadow 200ms ease, padding-block 200ms ease;
  }
  .js-motion header.site.scrolled {
    padding-block: 0.7rem;
    box-shadow:
      0 1px 0 var(--hair),
      0 10px 22px -14px color-mix(in srgb, var(--ink) 22%, transparent);
  }

  /* ---------- press and hover ---------- */
  .js-motion nav a {
    transition: color 120ms ease-out, background-color 120ms ease-out,
      transform 120ms ease-out;
  }
  .js-motion .button:active,
  .js-motion nav a:active {
    transform: scale(0.97);
  }
  @media (hover: hover) and (pointer: fine) {
    .js-motion .button:hover,
    .js-motion nav a:hover {
      transform: translateY(-1px);
    }
  }

  /* ---------- hero entrance ---------- */
  /* Three-step stagger on load: headline, sub, CTA row. CSS-only, once;
     `backwards` fill holds each element at its `from` state until its own
     delay elapses instead of flashing the final frame first. */
  .js-motion .hero > * {
    animation: hero-settle 480ms cubic-bezier(0.16, 1, 0.3, 1) backwards;
  }
  .js-motion .hero > *:nth-child(2) { animation-delay: 70ms; }
  .js-motion .hero > *:nth-child(3) { animation-delay: 140ms; }
  @keyframes hero-settle {
    from { opacity: 0; transform: translateY(0.5rem); }
  }

  /* ---------- lightbox open ---------- */
  /* @starting-style animates the SAME reused dialog element every time
     showModal() runs; a keyframe animation would only ever play once. Not
     js-motion-gated: this is the dialog opening, not a scroll effect. */
  dialog.lightbox {
    transition: opacity 150ms ease-out, transform 150ms ease-out;
    opacity: 1;
    transform: scale(1);
  }
  @starting-style {
    dialog.lightbox[open] {
      opacity: 0;
      transform: scale(0.96);
    }
  }

  /* ---------- MPA view transitions ---------- */
  /* Progressive: a browser without support keeps its instant navigation.
     Native, so this needs no script and no js-motion gate either. */
  @view-transition {
    navigation: auto;
  }
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 180ms;
    animation-timing-function: ease;
  }
  ::view-transition-old(root) { animation-name: view-fade-out; }
  ::view-transition-new(root) { animation-name: view-fade-in; }
  @keyframes view-fade-out {
    to { opacity: 0; }
  }
  @keyframes view-fade-in {
    from { opacity: 0; }
  }
}

/* ---------- reduced motion: belt and braces ---------- */
/* Every rule above is already gated behind .js-motion, which motion.js
   never adds once prefers-reduced-motion matches here, and the lightbox and
   view-transition rules already live inside the no-preference query above.
   This second lock costs nothing and catches a future rule that forgets the
   gate, so it zeroes the same selectors again, delays included. */
@media (prefers-reduced-motion: reduce) {
  [data-reveal],
  [data-draw],
  .hero > *,
  dialog.lightbox {
    animation: none !important;
    transition: none !important;
    animation-delay: 0ms !important;
    transition-delay: 0ms !important;
  }
}
