/*
  Block-spezifische Styles. Ein Abschnitt pro Blocktyp, siehe
  docs/content-model.md für die Feld-Definitionen und
  app/Templates/Blocks/*.php für das Markup.
*/

@layer blocks {
  .block {
    margin-block: var(--space-xl);
  }

  /* ---- hero: immer dunkel (kein Light-Hero im echten Design, siehe
     stc/assets/css/blocks.css .hero { background: var(--stc-black) }
     unconditional). Bild folgt der Ueberschrift in normalem Fluss darunter,
     kein Overlay -- CTA-Button unmittelbar unter dem Fliesstext, danach
     direkt das Bild ohne zusaetzlichen Abstand. */
  /* Header ist jetzt transparent (kein eigener Hintergrund) und schwebt
     ueber dem Hero -- der Hero muss deshalb bis ganz nach oben (hinter die
     Menuleiste) reichen, sonst blitzt dort der body-Hintergrund durch.
     Negative margin-top hebt ihn um die Menuehoehe an den echten
     Seitenanfang, padding-top gleicht das wieder aus (Inhalt bleibt an
     derselben Position wie vorher). */
  .block-hero {
    position: relative;
    margin-block: 0;
    margin-top: calc(-1 * var(--menu-h));
    padding-top: var(--menu-h);
    color: var(--color-text-on-dark);
    background: var(--color-bg-dark);
  }

  .block-hero__content {
    padding-block: var(--space-2xl) var(--space-xl);
  }

  .block-hero__media {
    width: 100%;
    aspect-ratio: 16 / 9;
    background: #111;
  }

  .block-hero__media img,
  .block-hero__media video {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* ---- hero media_layout: "overlay" -- Bild liegt als Hintergrund-Grafik
     hinter dem Text, statt als eigener Block darunter zu folgen. Fuer
     Line-Art-Illustrationen auf eigenem schwarzem Grund (kein Alpha-Kanal
     noetig -- geht nahtlos in den dunklen Hero-Hintergrund ueber). */
  .block-hero--media-overlay .block-hero__content {
    position: relative;
    z-index: 1;
  }

  .block-hero--media-overlay .block-hero__media {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
    background: none;
    z-index: 0;
  }

  .block-hero--media-overlay .block-hero__media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Kommt aus dem "position"-Feld des Medienobjekts (Bildausschnitt/
       Fokuspunkt), als --hero-media-position auf .block-hero__media gesetzt
       (siehe hero.php) -- ueber die "Bild waehlen"-Positionswahl im
       Editor-Panel steuerbar statt fest verdrahtet. */
    object-position: var(--hero-media-position, top right);
  }

  .block-hero__media .media-placeholder {
    min-height: 20rem;
  }

  .block-hero__headline {
    font: var(--font-h1);
    letter-spacing: var(--letter-spacing-h1);
    text-transform: uppercase;
    margin-bottom: var(--space-md);
    margin-top: 15vh;
  }

  @media (min-width: 48rem) {
    .block-hero__headline {
      margin-top: 35vh;
    }
  }

  .block-hero__subtext {
    font: var(--font-text);
    max-width: 40rem;
    margin-bottom: var(--space-md);
  }

  /* ---- hero media_layout: "sticky-swap" -- zwei gestapelte
     position:sticky-Panels statt normalem Fluss. Panel 1 (Headline) pinnt
     sich am oberen Viewport-Rand fest; Panel 2 (Medium + zweite Headline,
     als EINE Einheit verklebt statt zwei separaten Panels) ist normaler
     Nachfolger im Textfluss und faengt dadurch automatisch an, ueber Panel 1
     "hineinzurutschen", sobald sein eigener oberer Rand beim Scrollen die
     Fensteroberkante erreichen wuerde -- reines Sticky-Stacking, kein JS. */
  /* Gleicher margin-top/padding-top-Trick wie .block-hero (siehe dort):
     Hintergrund reicht bis ganz nach oben hinter die transparente
     Menueleiste, der negative margin-top wird durch padding-top exakt
     wieder ausgeglichen -- Panel 1 (position:sticky; top:0) landet dadurch
     im Netto an derselben Stelle wie ohne beide Regeln, kein Sprung beim
     Andocken. */
  .block-hero--sticky-swap {
    position: relative;
    margin-block: 0;
    margin-top: calc(-1 * var(--menu-h));
    padding-top: var(--menu-h);
    color: var(--color-text-on-dark);
    background: var(--color-bg-dark);
  }

  .block-hero__pin {
    position: sticky;
    top: 0;
    height: 100vh;
    overflow: hidden;
  }

  /* Linksbuendig, weit oben verankert statt in der Mitte/am unteren Rand --
     naeher an der --menu-h-Kante als an der Bildschirmmitte. Eigene, kleinere
     Hoehe statt der geerbten 100vh: dadurch ist Panel 2 (Video) im Dokument
     schon nach kurzem Scrollen erreicht und schaut sofort unten ins
     Sichtfeld rein, statt erst nach einer vollen Bildschirmhoehe Leerlauf
     aufzutauchen. */
  .block-hero__pin--text {
    z-index: 1;
    height: 52vh;
    display: flex;
    align-items: flex-start;
    justify-content: flex-start;
    max-width: var(--container-max);
    margin-inline: auto;
    padding: clamp(2rem, 6vw, 4rem) var(--gutter) 0;
    background: var(--color-bg-dark);
    color: var(--color-text-on-dark);
  }

  /* Auf Mobile ist die Ueberschrift durch den Zeilenumbruch nicht so hoch
     wie auf Desktop -- 52vh liess darunter unnoetig viel Leerraum, bevor
     Panel 2 (Video) im Sichtfeld auftaucht. */
  @media (max-width: 47.99rem) {
    .block-hero__pin--text {
      height: 37vh;
    }
  }

  .block-hero__pin--combo {
    z-index: 2;
    display: flex;
    flex-direction: column;
    background: var(--color-bg-dark);
  }

  .block-hero__pin-text {
    font: var(--font-h1);
    letter-spacing: var(--letter-spacing-h1);
    text-transform: uppercase;
    text-align: left;
  }

  .block-hero__combo-media {
    flex: 1 1 auto;
    min-height: 0;
    background: var(--color-bg-dark);
  }

  .block-hero__combo-media img,
  .block-hero__combo-media video {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* Auf Smartphones war das Video im Combo-Panel bis zu 90vh hoch (flex:1
     in einem 100vh-Pin), das sprengte den Viewport. Feste Deckelhoehe statt
     Vollbild-Video, Panel selbst wird dafuer auto-hoch statt weiterhin
     100vh -- sonst bliebe unter dem gedeckelten Video/Text leerer Raum
     stehen. */
  @media (max-width: 47.99rem) {
    .block-hero__pin--combo {
      height: auto;
    }

    .block-hero__combo-media {
      flex: none;
      max-height: 25rem;
    }
  }

  /* Zweite Headline "klebt" direkt unter dem Medium im selben Panel --
     eigene Bandhoehe statt Vollbild, damit sie sich als Anhang statt als
     eigene Buehne liest. */
  .block-hero__combo-text {
    flex: 0 0 auto;
    max-width: var(--container-max);
    margin-inline: auto;
    padding: clamp(1.25rem, 3vw, 2rem) var(--gutter);
    background: var(--color-bg-dark);
    color: var(--color-text-on-dark);
  }

  /* Gleiche Groesse wie die erste Headline (--font-h1 statt --font-h2) --
     beide Zeilen sollen als ein zusammenhaengendes Satzpaar wirken, nicht
     als Headline + kleinere Unterzeile. */
  .block-hero__combo-text .block-hero__pin-text {
    text-align: left;
    font: var(--font-h1);
    letter-spacing: var(--letter-spacing-h1);
    text-transform: uppercase;
  }

  /* ---- intro_text ---- */
  .block-intro__text {
    max-width: 45rem;
    font: var(--font-text);
  }

  .block-intro__text strong {
    font-family: var(--font-family-mono);
  }

  /* ---- stat_teaser_list ---- */
  .block-stats__list {
    display: grid;
    gap: var(--space-lg);
    grid-template-columns: 1fr;
    margin-bottom: var(--space-lg);
  }

  @media (min-width: 48rem) {
    .block-stats__list {
      grid-template-columns: repeat(4, 1fr);
    }
  }

  .block-stats__item {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
  }

  /* Trennlinie links ist ein Spalten-Divider fuer das 4-Spalten-Grid ab
     48rem -- auf Mobile (eine Spalte, jedes Item startet am linken Rand)
     sah sonst jedes Item ausser dem ersten eingerueckt aus. */
  @media (min-width: 48rem) {
    .block-stats__item {
      padding-left: var(--space-md);
      border-left: 1px solid var(--color-border-on-light);
    }

    .block-stats__item:first-child {
      padding-left: 0;
      border-left: 0;
    }
  }

  .block-stats__index {
    font: var(--font-h1);
    color: inherit;

  }

  /* Auf Mobile deutlich groesser als die normale --font-h1-Fluidgroesse
     (die Zahl ist hier das einzige grafische Element pro Item, ohne die
     Spalten-Trennlinie darf sie ruhig dominanter wirken) und eine
     Trennlinie ZWISCHEN den gestapelten Items statt der Spalten-Trennlinie
     von 48rem an (siehe .block-stats__item oben). */
  @media (max-width: 47.99rem) {
    .block-stats__index {
      /* War clamp(8rem, 40vw, 13rem) -- vier gestapelte Items kamen damit
         auf ueber 1000px Gesamthoehe fuer vier kurze Textzeilen, deutlich
         zu dominant. */
      font-size: clamp(4.5rem, 20vw, 7rem);
    }

    .block-stats__item:not(:first-child) {
      padding-top: var(--space-lg);
      border-top: 1px solid var(--color-border-on-light);
    }
  }

  @media (min-width: 48rem) {
      .block-stats__index {
        font-size: 18rem;
        margin-bottom: 8rem;
      }
  }

  .block-stats__title {
    font: var(--font-text);
  }

  /* ---- product_category_list: "Typo-Sandwich" (aus Kirby-Sandbox #v8) ----
     Baseline (kein JS / reduced-motion / Edit-Modus): einfache gestapelte
     Liste, voll klickbar und lesbar (CLAUDE.md: ohne JS ist alles sichtbar).
     Verstaerkte Variante (html.js, Motion an) weiter unten: gepinnter,
     horizontal scrubbender Filmstreifen via GSAP/ScrollTrigger
     (public/assets/js/product-sandwich.js). */
  .block-products--sandwich .demo__stage {
    position: relative;
    background: var(--color-bg-light);
  }

  .block-products--sandwich .demo__track-h {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
    padding: var(--space-xl) var(--gutter);
  }

  .sandwich-panel {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    overflow: hidden;
    border-radius: var(--radius-card);
  }

  /* Baseline-Liste (siehe oben) ist auf Mobile die staendige Ansicht, nicht
     nur ein kurz sichtbarer No-JS-Fallback (der horizontale Scrub-Track
     bleibt dort deaktiviert, siehe product-sandwich.js) -- volle
     Bildschirmhoehe pro Panel plus grosser Gap liess die Produkte zu weit
     auseinander wirken. Kompaktere Kartenhoehe + kleinerer Abstand statt
     100vh je Produkt. */
  @media (max-width: 47.99rem) {
    .block-products--sandwich .demo__track-h {
      gap: var(--space-sm);
      padding-block: var(--space-lg);
    }

    .sandwich-panel {
      min-height: 0;
      aspect-ratio: 4 / 3;
    }
  }

  /* Markengrafik (Pins strahlen radial vom Zentrum aus, wie das
     Steckgesicht eines Rundsteckverbinders -- public/assets/img/
     product-behind.svg, inline statt <img> eingebunden, siehe
     product_category_list.php) statt Typo hinter dem Foto -- rein dekorativ
     (aria-hidden im Template), zentriert hinter dem freigestellten Produkt.
     Dicht + Mikro, 10 Ringe (Variante 12 aus demo-radial-pins-sketches.html):
     sehr viele winzige Pins, bewusst OHNE eigene Puls-Animation je Pin --
     bei der Dichte wirkte ein Puls unruhig/flimmernd, die reine
     Gesamtrotation reicht als Bewegung. */
  .sandwich-panel .sandwich__behind {
    position: absolute;
    top: 50%;
    left: 50%;
    z-index: 1;
    width: clamp(16rem, 46vw, 34rem);
    transform: translate(-50%, -50%);
  }

  .sandwich-panel .sandwich__behind svg {
    display: block;
    width: 100%;
    height: auto;
  }

  /* An den Scroll-Fortschritt gekoppelte Rotation der Hintergrundgrafik:
     lief hier frueher per CSS animation-timeline:view() (nur unterhalb der
     GSAP-Pin-Breakpoint, 47.99rem -- ab 48rem uebernimmt product-sandwich.js
     dieselbe Rotation als GSAP-Tween in der Pin-Timeline, siehe dort). Auf
     iOS Safari (u.a. iPhone 17 zurueckgemeldet) wird animation-timeline
     nicht/nicht zuverlaessig unterstuetzt, kein @supports-Treffer dort =
     die Grafik blieb einfach unbewegt stehen. Jetzt komplett durch einen
     plain-JS rAF-Scroll-Handler in product-sandwich.js ersetzt (funktioniert
     browserunabhaengig identisch fuer beide Breakpoints), diese Regel
     entfaellt daher. */

  .sandwich-panel .sandwich__image {
    position: relative;
    z-index: 2;
    width: min(70rem, 78vw);
  }

  .sandwich-panel .sandwich__image picture,
  .sandwich-panel .sandwich__image img {
    display: block;
    width: 100%;
    height: auto;
  }

  .sandwich-panel .sandwich__front {
    position: absolute;
    z-index: 3;
    /* War die gleiche max()-Randformel wie .demo__fixed-group -- die ist
       nur fuer DESSEN full-bleed Pin-Stage noetig (dort sitzt das Panel am
       rohen Viewport-Rand). .sandwich__front ist aber genau umgekehrt: nur
       sichtbar, wenn NICHT gepinnt wird (Mobile-Baseline oder
       reduced-motion), und da steckt .sandwich-panel schon im normalen,
       gutter-respektierenden Blockfluss -- die Formel addierte den Gutter
       dadurch ein zweites Mal drauf (Button sass sichtbar eingerueckt statt
       buendig mit z.B. dem "Leistungen"-Button). Einfaches left:0 reicht,
       das Panel selbst liefert den Rand schon. */
    left: 0;
    bottom: clamp(0.75rem, 2vw, 1.5rem);
    display: inline-flex;
    /* Gleiche Groesse wie der generische .btn (siehe base.css). */
    padding: 0.4rem 0.85rem;
    border-radius: var(--radius-pill);
    background: var(--color-accent);
    color: var(--color-accent-contrast);
    font: 400 0.9rem var(--font-family-mono);
    letter-spacing: var(--letter-spacing-btn);
    text-transform: uppercase;
    text-decoration: none;
  }

  /* Fixierte Gruppe (Headline + Label/Button, data-fixed-group): liegt
     ausserhalb jedes Panels, direkt im Stage, bleibt beim Scrubben an Ort und
     Stelle stehen -- nur Text/Link wechseln (product-sandwich.js setActive()).
     Nur relevant in der gepinnten Variante, ersetzt dort die pro-Panel-
     Buttons; ohne JS/Pin macht ein "aktives Produkt" ohne Scroll-Fortschritt
     keinen Sinn, deshalb per Default versteckt. */
  .demo__fixed-group {
    display: none;
    position: absolute;
    z-index: 3;
    /* Gleicher Abstand zum Rand wie ueberall sonst -- dieser Block ist
       selbst kein .container (voller Filmstreifen-Scrub, edge-to-edge),
       daher reicht ein simples var(--gutter) nicht: auf Viewports breiter
       als --container-max sitzt der echte Container-Rand nicht mehr bei
       gutter vom Viewport-Rand, sondern zusaetzlich um die halbe
       Restbreite nach innen versetzt. max() nimmt den groesseren der
       beiden Werte -- unterhalb von --container-max wirkt einfach
       var(--gutter), darueber der Container-Versatz. Gleiche Rechnung
       fuer max-width: die verfuegbare Breite eines zentrierten
       Containers, nicht ein davon unabhaengiger vw-Wert (vorher 70vw --
       konnte auf breiten Screens ueber den rechten Container-Rand
       hinausragen). Betrifft Ueberschrift UND Button gleichermassen, da
       beide Kinder dieser Gruppe sind. */
    left: max(var(--gutter), calc((100% - var(--container-max)) / 2 + var(--gutter)));
    /* Am unteren Stage-Rand fixiert statt oben -- kollidiert dort nicht
       mit der Menueleiste, braucht deshalb auch keinen --menu-h-Offset. */
    bottom: clamp(0.75rem, 2vw, 1.5rem);
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-xs);
    max-width: calc(min(100%, var(--container-max)) - 2 * var(--gutter));
  }

  .demo__fixed-heading {
    margin: 0;
    font: var(--font-h3);
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-h3, 0);
    color: var(--color-text-on-light);
  }

  .demo__fixed {
    position: static;
    /* Gleiche Groesse wie .sandwich__front/.btn -- siehe dort. */
    padding: 0.4rem 0.85rem;
    border-radius: var(--radius-pill);
    background: var(--color-accent);
    color: var(--color-accent-contrast);
    font: 400 0.9rem var(--font-family-mono);
    letter-spacing: var(--letter-spacing-btn);
    text-transform: uppercase;
    text-decoration: none;
  }

  /* ---- verstaerkte Variante: gepinnter horizontaler Scrub-Track ----
     Aktiviert sich rein optisch schon ueber html.js (Endzustand der ersten
     Ansicht), das eigentliche Scrubben uebernimmt GSAP/ScrollTrigger sobald
     geladen. --sandwich-n wird von product-sandwich.js gesetzt (Anzahl
     Produkte). --panel-w/--panel-gap/--scroll-length kommen als Inline-Style
     vom Block-Template (Felder panel_width_vw/panel_gap_vw/scroll_length_vh,
     ueber die generische "Felder bearbeiten"-Drawer einstellbar), Fallback-
     Werte hier entsprechen den Defaults in product_category_list.php. */
  /* Nur ab Tablet-Breite (deckt sich mit dem Mobile-Gate in
     product-sandwich.js, mobileQuery) -- auf Touch-Breiten bleibt es bei der
     ungepinnten, gestapelten Baseline-Liste, siehe Kommentar dort. */
  @media (min-width: 48rem) {
    html.js:not(.reduced-motion):not(.motion-off) .block-products--sandwich {
      height: calc(var(--sandwich-n, 4) * var(--scroll-length, 100vh));
    }

    html.js:not(.reduced-motion):not(.motion-off) .block-products--sandwich .demo__stage {
      position: sticky;
      top: 0;
      height: 100vh;
      overflow: hidden;
    }

    html.js:not(.reduced-motion):not(.motion-off) .block-products--sandwich .demo__track-h {
      flex-direction: row;
      align-items: center;
      height: 100%;
      width: max-content;
      gap: var(--panel-gap, 3vw);
      padding: 0 var(--panel-gap, 3vw);
    }

    html.js:not(.reduced-motion):not(.motion-off) .block-products--sandwich .sandwich-panel {
      flex: 0 0 auto;
      width: var(--panel-w, 62vw);
      height: 86%;
      min-height: 0;
      border-radius: 0.75rem;
      overflow: visible;
    }

    html.js:not(.reduced-motion):not(.motion-off) .block-products--sandwich .sandwich__image {
      /* height: min(180%, 48rem); */
      filter: drop-shadow(0 2.5rem 4rem rgba(0, 0, 0, 0.3));
    }

    /* Gepinnte Variante: der Button bleibt fix stehen (bewegt sich nicht mit
       den Panels mit), nur sein Text/Link wechselt beim aktiven Produkt
       (product-sandwich.js). Die Pro-Panel-Buttons werden dafuer
       ausgeblendet. */
    html.js:not(.reduced-motion):not(.motion-off) .block-products--sandwich .sandwich-panel .sandwich__front {
      display: none;
    }

    html.js:not(.reduced-motion):not(.motion-off) .block-products--sandwich .demo__fixed-group {
      display: flex;
    }

    html.js:not(.reduced-motion):not(.motion-off) .block-products--sandwich .demo__fixed {
      display: inline-flex;
    }
  }

  /* ---- teaser_card ----
     Full-bleed dot-grid section (both consecutive teaser blocks share one
     continuous background, no margin between them -- own padding instead of
     the default .block spacing). Card itself: chamfered-corner shape, traced
     from the Figma frame vectors (public/assets/img/teaser-frame-*.svg --
     both variants are the same white shape, frame_style is not a color
     variant, only which SVG/orientation is used elsewhere). Title + CTA sit
     at the top, body text pinned to the bottom of a fixed-ratio card.

     Background lives on ::before instead of directly on the section: it
     bleeds --space-2xl past the section's own top/bottom edge (into the
     gap to the next block, or into a directly adjacent second teaser_card's
     matching bleed) via a classic "break out of the parent" trick
     (left:50% + width:100vw + translateX(-50%), independent of .block-teaser's
     own max-width) -- while .block-teaser itself now caps at
     --container-max like every other block, so the CARD stays aligned with
     the rest of the page's content on ultra-wide viewports instead of
     drifting out to the raw viewport edge.

     NOT isolation:isolate on .block-teaser (tried that first, reverted --
     see git history): it scopes z-index to each section, which means
     stacking between two DIFFERENT sections' boxes falls back to plain DOM
     order, and the LATER section (painted after) then covers the EARLIER
     section's card wherever its upward bleed overlaps it -- exactly the
     "card cut off when two teasers stack" bug. Instead: no isolation, plain
     global z-index -- ::before stays at -1, .block-teaser__card gets an
     explicit positive z-index, so the card always wins against ANY
     neighbor's bleed (its own section's or an adjacent one's) regardless of
     DOM order, because they're now compared directly in the same
     (un-isolated) stacking context all the way up to <body>. */
  .block-teaser {
    position: relative;
    max-width: var(--container-max);
    margin-inline: auto;
    margin-block: 0;
    /* Mehr Luft unten als oben -- das Punkteraster (::before) bleedet
       relativ zur Section-Box, waechst also automatisch mit, wenn hier
       nur der untere Wert steigt. */
    padding-block: clamp(2rem, 6vw, 4rem) clamp(3rem, 8vw, var(--space-2xl));
    /* Karte sitzt versetzt (--pos-left/right, margin-inline-*:auto), nicht
       zentriert -- ohne eigenes padding-inline liefe sie am Viewport-Rand
       bis auf 0 heran, statt im Raster zu bleiben wie jeder andere Block. */
    padding-inline: var(--gutter);
  }

  .block-teaser::before {
    content: "";
    position: absolute;
    z-index: -1;
    left: 50%;
    width: 100vw;
    transform: translateX(-50%);
    top: calc(-1 * var(--space-2xl));
    bottom: calc(-1 * var(--space-2xl));
    background-color: var(--color-bg-light);
    background-image: radial-gradient(var(--color-border-on-light) 1px, transparent 1px);
    background-size: 16px 16px;
  }

  .block-teaser__card {
    --chamfer: polygon(5.47% 0%, 0% 7.44%, 0% 92.93%, 5.17% 100%, 94.78% 100%, 100% 92.86%, 100% 7.29%, 94.78% 0%);
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    /* 100% ist hier bereits die Section-Content-Box (block-teaser hat sein
       eigenes padding-inline:var(--gutter)) -- kein zweites Gutter-Abziehen
       noetig, sonst waere der Rand auf schmalen Viewports doppelt so breit. */
    width: min(40rem, 100%);
    aspect-ratio: 887 / 651;
    padding: var(--space-lg) clamp(1.5rem, 5vw, 3.5rem);
    background: #fff;
    clip-path: var(--chamfer);
    text-decoration: none;
    color: var(--color-text-on-light);
    transition: transform var(--dur-fast) var(--ease-out-quad);
  }

  /* Versetzt statt zentriert -- welche Seite ist pro Block editierbar
     (Feld "position": "left"/"right", siehe teaser_card.php). */
  .block-teaser__card--pos-left {
    margin-inline-end: auto;
  }

  .block-teaser__card--pos-right {
    margin-inline-start: auto;
  }

  .block-teaser__card:hover,
  .block-teaser__card:focus-visible {
    transform: translateY(-4px);
  }

  .block-teaser__title {
    font: var(--font-h2);
    text-transform: uppercase;
    margin-bottom: var(--space-sm);
    /* Karte hat eine feste Hoehe (aspect-ratio + clip-path oben) statt sich
       an den Text anzupassen -- ein langes, ungetrenntes Wort (z.B.
       "UNABHAENGIGKEIT") lief sonst ueber den clip-path hinaus und wurde
       sichtbar abgeschnitten statt umzubrechen. overflow-wrap allein reicht
       nicht zuverlaessig bei einem einzelnen zu langen Wort ohne
       Trennstelle -- die Inhalte bekommen deshalb zusaetzlich echte
       Soft-Hyphens (U+00AD) an Silbengrenzen (siehe content-model.md). */
    overflow-wrap: break-word;
  }

  .block-teaser__text {
    font: var(--font-text);
    max-width: 32rem;
    margin-top: auto;
  }

  /* ---- product_detail: Moodboard aus freigestellten Bildern + Info-Bubbles,
     asymmetrisch positioniert (x/y/w/rotate in % vom Board, editierbar).
     data-anim="reveal-up"/"fade" + steigendes data-anim-delay nutzt das
     bestehende generische Motion-System -- jedes Element fliegt beim
     Scrollen einzeln rein, kein neuer JS-Code noetig. */
  .block-product-detail {
    position: relative;
    padding-block: clamp(2.5rem, 6vw, 4rem);
    scroll-margin-top: var(--menu-h);
  }

  /* Optionale Hintergrundfarbe (background_color-Feld): faerbt nicht die
     Block-Flaeche selbst ein (keine Kante irgendwo erkennbar), sondern die
     GANZE Seite -- body wechselt die Hintergrundfarbe, solange dieser Block
     im Viewport sichtbar ist, und morpht beim Verlassen zurueck. Ausloeser
     ist product-detail-bg.js (IntersectionObserver, siehe dort); hier nur
     der body[data-page-bg]-Empfaenger + die Transition. */
  body {
    transition: background-color var(--dur-slow) var(--ease-out-expo), color var(--dur-slow) var(--ease-out-expo);
  }

  html.reduced-motion body,
  html.motion-off body {
    transition: none;
  }

  body[data-page-bg="light"] {
    background: var(--color-bg-light);
    color: var(--color-text-on-light);
  }

  body[data-page-bg="dark"] {
    background: var(--color-bg-dark);
    color: var(--color-text-on-dark);
  }

  body[data-page-bg="accent"] {
    background: var(--color-accent);
    color: var(--color-accent-contrast);
  }

  .block-product-detail__heading {
    font: var(--font-h2);
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-h2);
    margin-bottom: var(--space-sm);
  }

  .block-product-detail__text {
    font: var(--font-text);
    max-width: 34rem;
    margin-bottom: var(--space-md);
  }

  .block-product-detail .btn {
    margin-bottom: var(--space-lg);
  }

  .block-product-detail__board {
    position: relative;
    min-height: 90vh;
    margin-top: var(--space-xl);
    padding-inline: var(--gutter);
    /* Bilder duerfen per x/w ueber den rechten/linken Rand hinausragen (freie
       Moodboard-Positionierung) -- overflow-x schneidet das am Block sauber
       ab, statt einen horizontalen Seiten-Scrollbalken zu erzeugen. NUR die
       x-Achse: board_height ist die einzige Stellschraube gegen vertikales
       Abschneiden (per Drag-Handle einstellbar, siehe weiter unten) -- ein
       Bild, dessen Hoehe (aus dem Seitenverhaeltnis) ueber das manuell
       gesetzte board_height hinausragt, muss sichtbar bleiben, sonst gibt es
       keinen Hinweis darauf, dass board_height vergroessert werden muss.

       "clip" statt "hidden": die Overflow-Spec rechnet "overflow-y: visible"
       IMMER zu "overflow-y: auto" um, sobald die andere Achse "hidden" (oder
       auto/scroll) ist -- unabhaengig davon, ob overflow-y explizit gesetzt
       ist oder nicht (kein Bug, sondern die definierte Kopplung der beiden
       Achsen). Das machte das Board bei jedem leicht zu knapp bemessenen
       board_height (haeufig, da manuell geschaetzt) zu einem eigenen
       Scroll-Container -- der Mauszeiger darueber scrollte dann das Board
       statt der Seite, bis der Board-Scroll an seine eigene Grenze stiess
       ("manchmal wird innerhalb des Containers weitergescrollt"). "clip" ist
       von dieser Kopplung ausgenommen (nie scrollbar, per Definition) --
       overflow-y bleibt dadurch echtes "visible". */
    overflow-x: clip;
    overflow-y: visible;
  }

  /* Im Editor bleibt auch der horizontale Ueberlauf sichtbar -- sonst liesse
     sich ein Bild, das ueber den Rand gezogen wurde, nicht mehr an seinem
     Drag-Handle packen (per overflow-x:clip abgeschnitten). */
  html.editing .block-product-detail__board {
    overflow-x: visible;
  }

  .block-product-detail__image {
    position: absolute;
  }

  html.editing .block-product-detail__image {
    cursor: grab;
  }

  html.editing .block-product-detail__image.is-dragging {
    cursor: grabbing;
  }

  .block-product-detail__image img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--radius-card);
  }

  .block-product-detail__bubble {
    position: absolute;
    max-width: 16rem;
  }

  /* Fester Versatz von der Bildkante, statt direkt an der Prozent-Koordinate
     zu kleben -- welche Ecke/Kante ist ergibt sich aus x/y (siehe
     product_detail.php), margin statt transform: kollidiert sonst mit dem
     Reveal-Preset (motion.css setzt dort schon transform: scale()/rotate()). */
  .block-product-detail__bubble--h-left {
    margin-left: -1.5rem;
  }

  .block-product-detail__bubble--h-right {
    margin-left: 1.5rem;
  }

  .block-product-detail__bubble--v-top {
    margin-top: -1.5rem;
  }

  .block-product-detail__bubble--v-bottom {
    margin-top: 1.5rem;
  }

  .block-product-detail__bubble-line {
    position: relative;
    display: block;
    width: 3rem;
    height: 2px;
    margin-bottom: var(--space-xs);
    background: var(--color-accent);
  }

  /* Pulsierender Punkt am Anfang der Linie -- "live"-Indikator-Motiv, macht
     die Bubble als eigenstaendiges, aktives Element erkennbar statt als
     reine Deko-Linie. */
  .block-product-detail__bubble-line::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    width: 0.5rem;
    height: 0.5rem;
    background: var(--color-accent);
    border-radius: 50%;
    transform: translate(-25%, -50%);
    animation: st-callout-pulse 1.8s var(--ease-out-quad) infinite;
  }

  @keyframes st-callout-pulse {
    0%, 100% {
      transform: translate(-25%, -50%) scale(1);
      opacity: 1;
    }
    50% {
      transform: translate(-25%, -50%) scale(1.8);
      opacity: 0.35;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .block-product-detail__bubble-line::before {
      animation: none;
    }
  }

  .block-product-detail__bubble p {
    font: var(--font-text);
    font-size: 0.85rem;
    line-height: 1.5;
  }

  @media (min-width: 48rem) {
    .block-product-detail__board {
      min-height: 85vh;
    }
  }

  /* ---- product_detail auf Mobile: einspaltig, volle Breite, untereinander --
     das freie Moodboard-Layout (x/y/w in %) ist fuer schmale Viewports nicht
     gemacht: kleine Prozentwerte ergeben auf 360px Breite andere Bild-
     groessen/-abstaende als am Desktop gedacht, und Bubbles koennten mit
     Bildern kollidieren. Statt das zu versuchen zu reparieren: unterhalb von
     48rem alles in den normalen Fluss zuruecksetzen, Reihenfolge = DOM-
     Reihenfolge (Bild, dann seine Bubbles, dann das naechste Bild). !important
     noetig, weil die x/y/w-Werte inline vom Template kommen (hoechste
     Spezifitaet ausser !important). */
  @media (max-width: 47.99rem) {
    .block-product-detail__board {
      display: flex;
      flex-direction: column;
      min-height: auto !important;
      /* War overflow:visible -- der GSAP-Scrub-Reveal (product-detail-
         images.js) verschiebt/skaliert Bilder per Transform bis zu +-140px/
         scale 2.1, das lief auf dem hier volle-Breite-fliessenden Element
         seitlich aus dem Board raus und machte die ganze Seite horizontal
         scrollbar (Burger-Menue dadurch wegscrollbar) -- deshalb war die
         Animation auf Mobile bisher komplett abgeschaltet (siehe isOff()
         dort). clip kappt den Ueberstand am Board-Rand, das Bild rutscht
         stattdessen sichtbar von aussen ins Board rein, wie am Desktop
         gedacht -- die Animation kann jetzt auch hier aktiv bleiben. */
      overflow: clip;
    }

    html.editing .block-product-detail__board {
      /* Im Edit-Modus laeuft keine Animation (html.motion-off, siehe
         product-detail-images.js isOff()), aber Drag-Handles fuer Bilder/
         Bubbles duerfen ueber den Board-Rand hinausragen. */
      overflow: visible;
    }

    .block-product-detail__image {
      position: static !important;
      left: auto !important;
      top: auto !important;
      width: 100% !important;
      margin-bottom: var(--space-lg);
    }

    .block-product-detail__bubble {
      position: static !important;
      left: auto !important;
      top: auto !important;
      max-width: 100%;
      /* Der feste Ecken-Versatz (--h-left/--h-right, siehe oben) ist nur fuer
         die freie Desktop-Positionierung gedacht -- auf Mobile ist die Bubble
         Teil des einspaltigen Flusses und soll flach/vollbreit sitzen. */
      margin-left: 0 !important;
      margin-top: var(--space-sm);
      margin-bottom: var(--space-lg);
    }

    .block-product-detail__bubble:last-child {
      margin-bottom: 0;
    }

    /* Board-Hoehe ist bei auto-Hoehe (min-height:auto) wirkungslos. */
    .block-product-detail__board .editor-drag-height-handle {
      display: none;
    }
  }

  /* ---- cta_banner ----
     Section hat eine feste Hoehe (aspect-ratio) und overflow:hidden -- die
     Parallax-Verschiebung (motion.js, --parallax-y) bewegt nur das
     ueberdimensionierte __media darin, nie die Section-Box selbst. Sonst
     wuerde sich die Kante zum Footer mitbewegen und dort kurz den hellen
     Seitenhintergrund freilegen (siehe Redakteur-Feedback: "weisse Blitzer"). */
  /* width:100% ist noetig, sobald min-height auf schmalen Viewports
     greift: mit width:auto + aspect-ratio errechnet der Browser sonst eine
     BREITE aus der erzwungenen Hoehe zurueck (statt einfach die
     Seitenverhaeltnis-Vorgabe zu verletzen) -- auf Mobile wurde die
     Section dadurch bis zu 685px breit und riss die ganze Seite horizontal
     auf (Burger-Menue rechts ausserhalb des Viewports). */
  .block-cta-banner {
    position: relative;
    width: 100%;
    margin-block: 0;
    aspect-ratio: 1440 / 874;
    min-height: 26rem;
    overflow: hidden;
    color: var(--color-text-on-dark);
  }

  .block-cta-banner__media {
    position: absolute;
    inset: -10% 0;
    height: 120%;
  }

  .block-cta-banner__media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .block-cta-banner__content {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    padding-block-start: var(--space-xl);
    gap: var(--space-md);
  }

  .block-cta-banner__headline {
    font: var(--font-h2);
    text-transform: uppercase;
    letter-spacing: var(--letter-spacing-h1);
  }

  /* ---- process_steps ---- */
  .block-process__list {
    display: flex;
    flex-direction: column;
  }

  .block-process__item {
    display: grid;
    gap: var(--space-md);
    align-items: start;
    padding-block: var(--space-lg);
    border-top: 1px solid var(--color-border-on-dark);
  }

  @media (min-width: 48rem) {
    .block-process__item {
      grid-template-columns: auto 1fr auto;
    }
  }

  .block-process__index {
    font: var(--font-h1);
    font-size: clamp(9rem, 19vw, 17rem);
    color: var(--color-accent);
    margin-right: var(--space-2xl);
  }

  .block-process__title {
    font: var(--font-h4);
    text-transform: uppercase;
    margin-bottom: var(--space-xs);
    max-width: 28rem;
    line-height: 1.25;
  }

  .block-process__text {
    max-width: 32rem;
    opacity: 0.8;
  }

  .block-process__media {
    /* width: clamp(6rem, 27vw, 25rem); */
    max-width: 27rem;
    aspect-ratio: 1;
    border-radius: var(--radius-card);
    overflow: hidden;
  }

  .block-process__media img,
  .block-process__media video {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* ---- accordion ---- */
  .block-accordion__heading {
    font: var(--font-h2);
    text-transform: uppercase;
    margin-bottom: var(--space-xl);
  }

  .block-accordion__layout {
    position: relative;
  }

  .block-accordion__list {
    max-width: 100%;
  }

  /* position:relative -- Anker fuer das absolut positionierte "+"
     (.block-accordion__summary::after) weiter unten. Titel+Icon oben, Text
     darunter (volle Breite) -- kein Nebeneinander von Titel- und Text-Spalte
     mehr: bei laengeren Titeln lief die erste Textzeile sonst unter das
     absolut positionierte Icon (das rechts an der GESAMTEN Item-Breite
     haengt, nicht an einer eigenen Text-Spalte). Einspaltig gilt das nicht
     mehr, auf jeder Breite. */
  .block-accordion__item {
    position: relative;
    border-top: 1px solid;
    padding-block: var(--space-sm);
    display: grid;
    grid-template-columns: 1fr;
    margin-bottom: var(--space-xl);
  }

  .block-accordion__summary {
    grid-column: 1;
    display: flex;
    align-items: center;
    /* Platz fuer das absolut positionierte "+" reservieren, damit langer
       Titeltext nicht darunter verschwindet. */
    padding-right: var(--space-lg);
    cursor: pointer;
    font: var(--font-text-big);
    list-style: none;
    line-height: 1.25;
    /* Strichdicke des gezeichneten Plus-Zeichens (::after), zentral
       einstellbar. */
    --accordion-plus-thickness: 0.15em;
  }

  .block-accordion__summary::-webkit-details-marker {
    display: none;
  }

  /* Ganz rechts aussen am Item, unabhaengig von der Grid-Spaltenaufteilung
     -- .block-accordion__item ist der positionierte Vorfahre (siehe oben),
     .block-accordion__text bleibt dadurch unangetastet in Spalte 2.
     Echtes gezeichnetes Plus statt Font-Glyphe ("+" faellt je nach Font
     unterschiedlich duenn/schief aus) -- zwei Balken per linear-gradient(),
     ein bekannter reiner CSS-Trick ohne zusaetzliche DOM-Knoten (::after
     kann selbst keine eigenen Pseudo-Elemente haben). Die 45deg-Drehung
     beim Oeffnen macht daraus optisch ein "x", genau wie vorher mit der
     Glyphe. */
  .block-accordion__summary::after {
    content: "";
    position: absolute;
    top: var(--space-sx);
    right: 0;
    display: block;
    width: 1em;
    height: 1em;
    background:
      linear-gradient(currentColor, currentColor) center / 100% var(--accordion-plus-thickness) no-repeat,
      linear-gradient(currentColor, currentColor) center / var(--accordion-plus-thickness) 100% no-repeat;
    color: var(--color-accent);
    transition: transform var(--dur-fast) var(--ease-out-quad);
  }

  .block-accordion__item[open] .block-accordion__summary::after {
    transform: rotate(45deg);
  }

  /* Text bleibt schmal und rechtsbuendig -- deckt sich mit dem Ausrichtungsraster
     der anderen Seiteninhalte, statt ueber die volle Breite zu laufen. Rutscht
     trotzdem sauber unter Titel+Icon (grid-row 2), kein erneutes Ueberlappen
     mit dem absolut positionierten "+"/"x" (das nur in Zeile 1 sitzt). */
  .block-accordion__text {
    grid-column: 1;
    grid-row: 2;
    max-width: 50%;
    margin-left: auto;
    padding-top: var(--space-sm);
    overflow: hidden;
    line-height: 1.6;
  }

  .block-accordion__text p + p {
    margin-top: var(--space-md);
  }

  @media (max-width: 47.99rem) {
    .block-accordion__text {
      max-width: 100%;
    }
  }

  /* accordion sits on the dark 03_standards page background. Volle Breite
     (kein .container) -- Trennlinien/Hintergrund gehen bis zum Viewport-Rand,
     nur der Inhalt behaelt ueber padding-inline einen Rand. */
  .block-accordion {
    color: var(--color-text-on-dark);
    padding-inline: var(--gutter);
  }

  /* ---- tile_band: eine echte Plakette mittig, links/rechts davon
     inhaltslose Klone als Kulisse (siehe tile_band.php) -- --fan-i ist die
     vorzeichenbehaftete Distanz zur Mitte (negativ links, 0 = die echte
     Plakette, positiv rechts), --fan-dist der Betrag davon fuer die
     Staffelung. Ruhezustand (auch ohne JS/reduced-motion) zeigt bereits
     den fertigen, aufgefaecherten Zustand -- "ohne JS ist sofort alles im
     Endzustand sichtbar" (CLAUDE.md). Bei aktiver Bewegung ziehen sich alle
     Klone zunaechst auf die Mitte zusammen (translateX um genau ihren
     eigenen Faecher-Abstand) und faechern sich beim Reinscrollen auf --
     dieselbe Formel ergibt fuer die mittlere Plakette (--fan-i: 0) immer
     0px, die braucht dadurch keinen eigenen Sonderfall. */
  .block-tile-band__list {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-md);
  }

  .block-tile-band__plaque {
    position: relative;
    list-style: none;
    flex: 0 0 clamp(9rem, 12vw, 12rem);
    aspect-ratio: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: var(--space-2xs);
    background: var(--color-bg-light);
    color: var(--color-text-on-light);
    font: var(--font-text);
    text-align: center;
    border-radius: var(--radius-card);
  }

  .block-tile-band__plaque--clone {
    flex-basis: clamp(6.5rem, 8.5vw, 9rem);
    background: color-mix(in srgb, var(--color-bg-light) 65%, black);
    opacity: 0.85;
  }

  .block-tile-band__label {
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    opacity: 0.65;
  }

  .block-tile-band__value {
    font: var(--font-h4);
    font-size: clamp(1.4rem, 1.8vw, 1.9rem);
  }

  .block-tile-band__rivet {
    position: absolute;
    width: 0.6rem;
    height: 0.6rem;
    border-radius: 50%;
    background: var(--color-accent);
    box-shadow: inset 0 1px 1px rgb(255 255 255 / 55%), inset 0 -1px 1px rgb(0 0 0 / 35%);
    transform: rotate(200deg);
  }

  .block-tile-band__rivet--tl { top: 0.55rem; left: 0.55rem; }
  .block-tile-band__rivet--tr { top: 0.55rem; right: 0.55rem; }
  .block-tile-band__rivet--bl { bottom: 0.55rem; left: 0.55rem; }
  .block-tile-band__rivet--br { bottom: 0.55rem; right: 0.55rem; }

  .block-tile-band__plaque--main::after {
    content: "";
    position: absolute;
    inset: -0.35rem;
    border: 1px solid var(--color-accent);
    border-radius: calc(var(--radius-card) + 0.35rem);
    opacity: 0;
    pointer-events: none;
  }

  /* Vor-Zustand bei aktiver Bewegung: Klone auf die Mitte zusammengezogen
     statt aufgefaechert, Nieten offen. */
  html.js:not(.reduced-motion):not(.motion-off) .block-tile-band[data-anim] .block-tile-band__plaque {
    opacity: 0;
    transform: translateX(calc(var(--fan-i) * -1 * (100% + var(--space-md)))) scale(0.85);
    transition: opacity 260ms linear, transform 420ms cubic-bezier(0.2, 0.9, 0.3, 1);
    transition-delay: calc(var(--fan-dist, 0) * 90ms);
  }

  html.js:not(.reduced-motion):not(.motion-off) .block-tile-band[data-anim] .block-tile-band__rivet {
    transform: rotate(0deg);
    transition: transform var(--dur-base) var(--ease-out-quad);
    transition-delay: calc(var(--fan-dist, 0) * 90ms + 300ms);
  }

  html.js:not(.reduced-motion):not(.motion-off) .block-tile-band.is-visible .block-tile-band__plaque {
    opacity: 1;
    transform: translateX(0) scale(1);
  }

  html.js:not(.reduced-motion):not(.motion-off) .block-tile-band.is-visible .block-tile-band__rivet {
    transform: rotate(200deg);
  }

  html.js:not(.reduced-motion):not(.motion-off) .block-tile-band.is-visible .block-tile-band__plaque--main::after {
    animation: tile-band-snap 300ms linear;
    animation-delay: 300ms;
  }

  @keyframes tile-band-snap {
    0% { opacity: 0.9; inset: -0.1rem; }
    100% { opacity: 0; inset: -0.6rem; }
  }

  /* Browsers with native CSS scroll-driven animations (motion.css's own
     @supports block) never get .is-visible added by JS for anything except
     data-anim="stagger-children" -- motion.js deliberately leaves it to
     animation-timeline:view() instead. The two html.js:not(...).is-visible
     rules above are therefore only the FALLBACK path (browsers without
     animation-timeline support, where motion.js's IntersectionObserver
     still runs and does toggle .is-visible). Without this block the
     plaques would sit at their pre-visible opacity:0 forever on any
     current-generation Chromium/Chrome browser -- .is-visible would simply
     never arrive (confirmed the hard way, see git history). Mirrors
     motion.css's own generic @supports pattern, but scoped directly to
     each plaque (its own view-timeline) instead of the section, since the
     effect needs per-plaque staggering. animation-delay on a
     scroll-timeline-driven animation shifts along that element's OWN entry
     progress rather than waiting for a sibling, so the stagger reads a
     little different from the transition-based fallback above -- still a
     clear inside-out fan, just not frame-identical. */
  @supports (animation-timeline: view()) {
    html.js:not(.reduced-motion):not(.motion-off) .block-tile-band__plaque {
      animation-name: tile-band-plaque-reveal;
      animation-timeline: view();
      animation-range: entry 0% cover 45%;
      animation-fill-mode: both;
      animation-delay: calc(var(--fan-dist, 0) * 60ms);
      transition: none;
    }

    html.js:not(.reduced-motion):not(.motion-off) .block-tile-band__rivet {
      animation-name: tile-band-rivet-reveal;
      animation-timeline: view();
      animation-range: entry 0% cover 45%;
      animation-fill-mode: both;
      animation-delay: calc(var(--fan-dist, 0) * 60ms);
      transition: none;
    }

    html.js:not(.reduced-motion):not(.motion-off) .block-tile-band__plaque--main::after {
      animation-name: tile-band-snap;
      animation-timeline: view();
      animation-range: entry 15% entry 45%;
      animation-fill-mode: both;
    }

    @keyframes tile-band-plaque-reveal {
      from {
        opacity: 0;
        transform: translateX(calc(var(--fan-i) * -1 * (100% + var(--space-md)))) scale(0.85);
      }
      to {
        opacity: 1;
        transform: none;
      }
    }

    @keyframes tile-band-rivet-reveal {
      from { transform: rotate(0deg); }
      to { transform: rotate(200deg); }
    }
  }

  /* ---- industry_stack: volle Breite, ein Bild pro Branche als eigene
     Sektion. Stapel-Effekt rein ueber CSS: jede Karte ist position:sticky
     mit wachsendem top/z-index (--i, vom Template gesetzt), dadurch schiebt
     sich beim Scrollen automatisch Karte fuer Karte darueber -- kein
     JS/GSAP noetig (CLAUDE.md: CSS vor JS, wo es reicht). */
  .block-industries {
    color: var(--color-text-on-dark);
  }

  .block-industries__heading {
    font: var(--font-h2);
    text-transform: uppercase;
    max-width: 40rem;
    margin-bottom: var(--space-lg);
  }

  .block-industries__stack {
    display: block;
  }

  .block-industries__card {
    --chamfer: polygon(2% 0%, 0% 4.7%, 0% 95.3%, 1.9% 100%, 98.1% 100%, 100% 95.3%, 100% 3.7%, 98% 0%);
    --covered-depth: 0;
    position: sticky;
    top: calc(var(--menu-h) + var(--i, 0) * 1.5rem);
    z-index: calc(var(--i, 0) + 1);
    display: block;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    width: 100%;
    margin-bottom: var(--space-md);
    clip-path: var(--chamfer);
    transform-origin: top center;
    box-shadow: 0 -1.5rem 3rem rgb(0 0 0 / 45%);
    transition: transform var(--dur-base) var(--ease-out-expo);
  }

  /* Karten, an denen bereits vorbeigescrollt wurde (industries.js beobachtet
     die eigene sticky-Position), verkleinern sich sichtbar -- der Stapel
     "schrumpft nach hinten" wie im Referenz-Screenshot, und zwar UMSO
     STAERKER, je tiefer die Karte im Stapel liegt (--covered-depth, von
     industries.js pro Karte gesetzt) statt eines einzigen festen Werts fuer
     jede abgedeckte Karte. min(...,6) deckelt den Effekt bei sehr langen
     Stapeln (0.9 - 6*0.04 = 0.66, nie kleiner). Nur mit Motion an: ohne
     JS/reduced-motion/Edit-Modus bleiben alle Karten in normaler Groesse
     gestapelt (CLAUDE.md: kein JS-Fehler darf den Effekt kaputt aussehen lassen). */
  html.js:not(.reduced-motion):not(.motion-off) .block-industries__card.is-covered {
    transform: scale(calc(0.9 - min(var(--covered-depth), 6) * 0.04));
  }

  @media (min-width: 48rem) {
    .block-industries__card {
      aspect-ratio: 21 / 9;
    }
  }

  .block-industries__card img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .block-industries__overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: var(--space-sm);
    padding: var(--space-lg) var(--gutter);
    background: linear-gradient(to bottom, rgb(0 0 0 / 70%), transparent 55%);
  }

  .block-industries__badge {
    font: var(--font-h2);
    text-transform: uppercase;
    color: var(--color-text-on-dark);
  }

  /* ---- industry_stack: Detail-Overlay ("MEHR +") ---- */
  .block-industries__modal {
    max-width: 34rem;
    width: calc(100% - 2 * var(--gutter));
    padding: var(--space-lg);
    border: 0;
    border-radius: var(--radius-card);
    background: var(--color-bg-light);
    color: var(--color-text-on-light);
  }

  .block-industries__modal::backdrop {
    background: rgb(0 0 0 / 65%);
  }

  .block-industries__modal-close {
    position: absolute;
    top: var(--space-sm);
    right: var(--space-sm);
    width: 2rem;
    height: 2rem;
    font-size: 1.25rem;
    line-height: 1;
    border-radius: 50%;
    background: var(--color-bg-light);
  }

  .block-industries__modal-close:hover,
  .block-industries__modal-close:focus-visible {
    background: var(--color-border-on-light);
  }

  .block-industries__modal h3 {
    font: var(--font-h4);
    text-transform: uppercase;
    margin-bottom: var(--space-sm);
    padding-right: var(--space-xl);
  }

  .block-industries__modal-text {
    font: var(--font-text);
  }

  /* ---- scrollytelling ----
     Pinning ist reines CSS (position: sticky), kein GSAP/ScrollTrigger --
     siehe Kommentar im Template. Ohne JS: einfache Bild+Text-Liste
     (.block-scrollytelling__step-media sichtbar, Sticky-Rail ausgeblendet). */
  .block-scrollytelling__heading {
    font: var(--font-h2);
    text-transform: uppercase;
    margin-bottom: var(--space-lg);
  }

  .block-scrollytelling__media {
    display: none;
  }

  .block-scrollytelling__steps {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
  }

  .block-scrollytelling__step {
    display: grid;
    gap: var(--space-md);
  }

  .block-scrollytelling__step-text {
    font: var(--font-text);
    max-width: 34rem;
  }

  html.js .block-scrollytelling__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-lg);
  }

  @media (min-width: 64rem) {
    html.js .block-scrollytelling__grid {
      grid-template-columns: 1fr 26rem;
      align-items: start;
    }

    /* Sticky-Crossfade nur für Besucher -- im Edit-Modus soll man alle Steps
       ohne 80vh-Scrollsprünge erreichen und direkt bearbeiten können. */
    html.js:not(.editing) .block-scrollytelling__media {
      display: grid;
      grid-column: 2;
      grid-row: 1;
      position: sticky;
      top: calc(86px + var(--space-lg));
      aspect-ratio: 8 / 9;
    }

    html.js:not(.editing) .block-scrollytelling__steps {
      grid-column: 1;
      grid-row: 1;
    }

    html.js:not(.editing) .block-scrollytelling__step {
      grid-template-columns: 1fr;
      min-height: 80vh;
      align-content: center;
    }

    html.js:not(.editing) .block-scrollytelling__step-media {
      display: none;
    }
  }

  .block-scrollytelling__media-item {
    grid-area: 1 / 1;
    opacity: 0;
    transition: opacity var(--dur-slow) var(--ease-out-quad);
  }

  .block-scrollytelling__media-item.is-active {
    opacity: 1;
  }

  /* ---- info_block: unnummerierte Fortsetzung der Prozess-Liste (stc
     .schritt ohne number -- gleicher Trenner/Rhythmus wie process_steps,
     nur ohne Index/Bild-Spalte). */
  .block-info {
    padding-block: var(--space-lg);
    border-top: 1px solid var(--color-border-on-dark);
    margin-block: 0;
  }

  .block-info__title {
    font: var(--font-h4);
    text-transform: uppercase;
    margin-bottom: var(--space-sm);
  }

  .block-info__text {
    max-width: 32rem;
    opacity: 0.8;
  }

  .block-info__button {
    margin-top: var(--space-sm);
    /* .btn ist normalerweise ein kurzes Pill-Label (nowrap) -- der laengere
       Download-Dateiname sprengte auf Mobile die Viewport-Breite (Overflow).
       Hier darf die Pille stattdessen in der Breite mitgehen und der Text
       zweizeilig umbrechen. */
    max-width: 100%;
    white-space: normal;
    text-align: center;
  }

  /* ---- info_block variant "accent": hervorgehobene Karte mit
     Markenorange-Hintergrund statt Fliesstext in der Prozess-Liste -- fuer
     einzelne CTAs (z.B. Flyer-Download), die staerker auffallen sollen als
     die anderen info_blocks auf derselben Seite. */
  .block-info--accent {
    background: var(--color-accent);
    color: var(--color-accent-contrast);
    border-top: none;
    border-radius: var(--radius-card);
    padding: var(--space-lg) var(--gutter);
  }

  .block-info--accent .block-info__title {
    font: var(--font-h3);
  }

  .block-info--accent .block-info__text {
    max-width: none;
    opacity: 1;
  }

  .block-info {
    color: var(--color-text-on-dark);
    border-top: 1px solid var(--color-border-on-dark);
    padding-top: var(--space-lg);
  }

  /* Rechtstexte (Impressum/Datenschutz) bestehen NUR aus info_block-Sektionen
     hintereinander -- die sonst gewollte gedaempfte Sekundaertext-Optik
     (.block-info__text opacity:0.8, fuer Flieszttext neben Bildern/Icons auf
     z.B. der Leistungen-Seite) macht lange Gesetzestexte hier nur schwerer
     lesbar. Ueber die body-Klasse (page.php) auf diese beiden Seiten
     beschraenkt, statt die Optik ueberall zu aendern. Echtes Weiss statt des
     site-weiten --color-text-on-dark (#f2f2f2, bewusst leicht gedaempft) --
     hier ausdruecklich gewuenscht, es wirkte sonst weiterhin grau. */
  .page-impressum .block-info__title,
  .page-impressum .block-info__text,
  .page-datenschutz .block-info__title,
  .page-datenschutz .block-info__text {
    opacity: 1;
    color: #fff;
  }
}
