/* ===========================================================================
   heimseiten.de – NUR die in C5 umgewandelten Elemente.
   Das Basis-Design liefert live-design.css (echtes Live-CSS auf C5-Markup gemappt),
   das Spalten-Grid die grid-framework.css. Hier nur, was Live-CSS nicht kennt.
   =========================================================================== */

/******************************************************** hs-box (ehem. rsce_content_box) */
.hs-box { padding: clamp(16px, 3vw, 32px); border-radius: var(--border_radius, 3px); }
.hs-box.Zentriert { margin-inline: auto; }

/******************************************************** hs-spacer (ehem. rsce_margin_block) */
.hs-spacer { display: block; width: 100%; pointer-events: none; }
.hs-spacer.hs-spacer--header { height: 130px; }
@media (max-width:1023px){ .hs-spacer.hs-spacer--header { height: 51px; } }
.hs-spacer.hs-spacer--S { height: 20px; }
.hs-spacer.hs-spacer--M { height: 40px; }
.hs-spacer.hs-spacer--L { height: 80px; }
.hs-spacer.hs-spacer--XL { height: 140px; }

/******************************************************** Artikel-Hintergrundbild (ehem. rsce_article_background) */
.mod_article.hs-article-bg { background-size: cover; background-repeat: no-repeat; }
.mod_article.hs-article-bg > * { position: relative; z-index: 1; }

/******************************************************** hs-center (ehem. cc_center) */
.hs-center { text-align: center; }
.hs-center img, .hs-center figure { margin-inline: auto; }

/******************************************************** Hero-Slider (sliderStart .full_height) – voll-bleed, volle Höhe, grüne Caption-Boxen */
.mod_article :is(.ce_sliderStart, .content-custom-swiper).full_height,
.ce_sliderStart.full_height {
    width: 100vw; max-width: 100vw;
    margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw);
    padding: 0;
}
:is(.ce_sliderStart, .content-custom-swiper).full_height .content-slider { width: 100%; }
:is(.ce_sliderStart, .content-custom-swiper).full_height .slider-wrapper > * { width: 100%; }
:is(.ce_sliderStart, .content-custom-swiper).full_height img { width: 100%; height: calc(100svh - 130px); min-height: 440px; object-fit: cover; display: block; }
@media (max-width:1023px){ :is(.ce_sliderStart, .content-custom-swiper).full_height img { height: 62svh; min-height: 320px; } }
.mod_article:has(:is(.ce_sliderStart, .content-custom-swiper).full_height) { padding-block: 0; }
.mod_article:has(:is(.ce_sliderStart, .content-custom-swiper).full_height) .mod_article__inner { padding-block: 0; }
.mod_article:has(:is(.ce_sliderStart, .content-custom-swiper).full_height) > * { max-width: 100%; width: 100%; padding-inline: 0; }
:is(.ce_sliderStart, .content-custom-swiper).full_height .content-text.media { position: relative; }
:is(.ce_sliderStart, .content-custom-swiper).full_height .content-text.media figure { margin: 0; }
/* Caption-Positionierung + Pillen-Optik übernimmt der under_header-Block (Live-Regel
   `.animate_text .ce_text .text { left:10%; bottom:15% }` gilt im Original für ALLE Heros).
   Die frühere full_height-EIGENE .rte-Absolut-Regel kollidierte mit der under_header-Caption
   (width:max-content kollabierte um das herausgelöste absolute Kind → Buchstaben-Spalte,
   User-Screenshot 2026-07-08). */

/******************************************************** Swiper: Ken Burns + Text-Einfahren (Referenz contao57)
   Natives Contao-Swiper-Element + custom-swiper-bundle (customTpl CustomSwiper, effect fade).
   KenBurns = cssID-Klasse; Mechanik 1:1 aus contao57 files/layout/css/style.css
   (scale 1.05 → 1 am .swiper-slide-active, Timing über --sliderDelay/--sliderSpeed,
   die das CustomSwiper-Template als Inline-Vars rendert). */
.content-custom-swiper { overflow: hidden; }
.content-custom-swiper:is(.full_height, .under_header) .swiper-slide img { width: 100%; object-fit: cover; }
/* Zoom-Dauer = delay+speed (5,5s) — der Takt des LIVE-heimseiten-Originals (style.scss
   ken_burns: calc((sliderSpeed+sliderDelay)/1000*1s)); zusammen mit dem theme.js-Startversatz
   (erster Autoplay-Zyklus + speed) läuft jeder Slide exakt eine Zoom-/Text-Periode lang. */
.content-custom-swiper.KenBurns .swiper-wrapper > * img {
    scale: 1.05;
    transition: scale calc((var(--sliderDelay, 4000) + var(--sliderSpeed, 1500)) * 0.001s) ease-in-out;
    transform-origin: top left;
    backface-visibility: hidden; will-change: transform;
}
/* .preload killt Transitions (settings.css) — sonst fehlt der Zoom des Start-Slides */
body.preload .content-custom-swiper.KenBurns .swiper-wrapper > * img {
    transition: scale calc((var(--sliderDelay, 4000) + var(--sliderSpeed, 1500)) * 0.001s) ease-in-out !important;
}
.content-custom-swiper.KenBurns .swiper-wrapper > :nth-child(4n + 1) img { transform-origin: bottom left; }
.content-custom-swiper.KenBurns .swiper-wrapper > :nth-child(4n + 2) img { transform-origin: top right; }
.content-custom-swiper.KenBurns .swiper-wrapper > :nth-child(4n + 3) img { transform-origin: top left; }
.content-custom-swiper.KenBurns .swiper-wrapper > :nth-child(4n + 4) img { transform-origin: bottom right; }
.content-custom-swiper.KenBurns .swiper-slide-active img { scale: 1; }
/* Die AUSBLENDENDE Folie behält den End-Zoom (Live-Pendant: .tns-fadeOut img{scale(1)}) —
   sonst zoomt sie während des Fade-out sichtbar zurück (User-Befund 2026-07-09). Ihr Reset
   auf 1.05 passiert erst beim übernächsten Wechsel, dann bei opacity 0 unsichtbar. */
.content-custom-swiper.KenBurns :is(.swiper-slide-prev, .swiper-slide-duplicate-prev) img { scale: 1; }

/* Swiper 14 injiziert ein eigenes Icon-SVG in die Navigations-Buttons — zusammen mit
   unseren background-SVGs ergab das DOPPELTE Pfeile (User 2026-07-08) → Icon aus. */
.content-custom-swiper :is(.swiper-button-prev, .swiper-button-next) .swiper-navigation-icon { display: none; }
/* Hero-Pfeile: Live-Optik (weiße arrow-SVGs; Werte aus style.scss .tns-controls) */
.content-custom-swiper:is(.full_height, .under_header) :is(.swiper-button-prev, .swiper-button-next) {
    width: 40px; height: 40px; padding: 20px; top: 50%; margin-top: -20px;
    background: transparent center / contain no-repeat; border: 0; cursor: pointer;
    transition: var(--tr);
}
.content-custom-swiper:is(.full_height, .under_header) .swiper-button-prev { left: 0;  background-image: url('/files/layout/img/icons/arrow_left.svg'); }
.content-custom-swiper:is(.full_height, .under_header) .swiper-button-next { right: 0; background-image: url('/files/layout/img/icons/arrow_right.svg'); }
.content-custom-swiper:is(.full_height, .under_header) :is(.swiper-button-prev, .swiper-button-next):hover {
    transform: scale(1.2); background-color: rgba(0, 0, 0, 0.6);
}

/* TEXT-EINFAHREN je Slide (Live-Animation slide_in_and_out, an Swiper-Klassen):
   Pillen starten rechts außerhalb, fahren ein (P2 mit 0.5s Versatz, endet 15px weiter
   links) und beim Slide-Ende wieder hinaus. Grundzustand NUR bei initialisiertem Swiper
   (.swiper-initialized) — ohne JS bleiben die Texte sichtbar. */
.content-custom-swiper:is(.full_height, .under_header) .float_above_text p { opacity: 0; left: 100%; }
@media (scripting: none) {   /* ohne JS: Texte sichtbar lassen */
    .content-custom-swiper:is(.full_height, .under_header) .float_above_text p { opacity: 1; left: 0; }
}
.content-custom-swiper:is(.full_height, .under_header) .swiper-slide-active .float_above_text { z-index: 9; }
.content-custom-swiper:is(.full_height, .under_header) .swiper-slide-active .float_above_text p {
    animation: slide_in_and_out calc((var(--sliderSpeed, 1500) + var(--sliderDelay, 4000)) / 1000 * 1s) ease-in-out forwards;
}
.content-custom-swiper:is(.full_height, .under_header) .swiper-slide-active .float_above_text p:nth-child(2) {
    animation-delay: 0.5s; animation-name: slide_in_and_out_second;
}
/* slide_in_only-Slides (Live-Klasse, z. B. Heros webdesign-koeln/seo-koeln): Text fährt
   ein und BLEIBT stehen (Live-Keyframes slide_in/slide_in_second) — ohne diese Override-
   Regel bekämen sie fälschlich die and_out-Ausfahrt. Nur animation-name überschrieben,
   Dauer/forwards erben aus der Basisregel. */
.content-custom-swiper:is(.full_height, .under_header) .swiper-slide-active .slide_in_only .float_above_text p {
    animation-name: hs_slide_in;
}
.content-custom-swiper:is(.full_height, .under_header) .swiper-slide-active .slide_in_only .float_above_text p:nth-child(2) {
    animation-name: hs_slide_in_second;
}
@keyframes hs_slide_in {
    30% { opacity: 1; left: 0; }
    100% { opacity: 1; left: 0; }
}
@keyframes hs_slide_in_second {
    30% { opacity: 1; left: -15px; }
    100% { opacity: 1; left: -15px; }
}
@keyframes slide_in_and_out {
    30% { opacity: 1; left: 0; }
    70% { opacity: 1; left: 0; }
    100% { opacity: 0; left: -100%; }
}
@keyframes slide_in_and_out_second {
    30% { opacity: 1; left: -15px; }
    70% { opacity: 1; left: -15px; }
    100% { opacity: 0; left: -100%; }
}
@media (prefers-reduced-motion: reduce) {
    .content-custom-swiper.KenBurns .swiper-wrapper > * img { transition: none; scale: 1; }
    .content-custom-swiper:is(.full_height, .under_header) .float_above_text p { opacity: 1; left: 0; animation: none !important; }
}

/* Subpage-Hero (sliderStart .under_header): Caption ÜBER das Bild legen wie Live. Der C5-Core-Swipe rendert
   die Caption (.text.float_above_text) als media--above IM FLUSS unter dem Bild → Slider 558 statt 500 (+58).
   Live (tns) legt sie absolut übers Bild. Gemessene Live-Sollwerte (www.heimseiten.de/webdesign-koeln #878):
   .float_above_text absolut left:120px/top:257px (Slider 1200×500 = 10%/51,4%), p weiß 30px/weight100 mit
   GLEICHER grüner skewX-Pill wie die Startseite. In % statt px → responsiv + Live-treu bei Designbreite. */
:is(.ce_sliderStart, .content-custom-swiper).under_header .content-text.media--above { position: relative; }
:is(.ce_sliderStart, .content-custom-swiper).under_header .content-text.media--above > .image_container.float_above { margin: 0; }
/* Breite: Live shrink-to-fit an der breitesten Pille (gemessen 278 bei „gefunden werden") —
   KEINE feste Breite (width:220 erzwang Umbrüche); max-width als Überlauf-Schutz.
   Position: Live verankert UNTEN — left:10%; bottom:15% (belegt: computed bottom exakt 79.98
   = 15%·533 auf /seo-koeln UND /webdesign-koeln; ein top-%-Wert stimmt nur bei einer Sliderhöhe). */
:is(.ce_sliderStart, .content-custom-swiper).under_header .float_above_text {
    position: absolute; left: 10%; bottom: 15%; top: auto; z-index: 2; margin: 0;
    width: max-content; max-width: 80%;
}
:is(.ce_sliderStart, .content-custom-swiper).under_header .float_above_text .inside > .rte p,
.ce_sliderStart.under_header .float_above_text .inside > p {
    position: relative; display: table; background: transparent; color: #fff;
    padding: 10px 20px; margin: 0 0 10px; font-size: clamp(1.1rem, 2.35vw, 1.875rem); font-weight: 100;
    line-height: 1.8;   /* Live computed 54px @30px – Pille h74, EINzeilig */
}
:is(.ce_sliderStart, .content-custom-swiper).under_header .float_above_text p:before {
    content: ""; position: absolute; inset: 0; z-index: -1;
    background: rgba(124, 171, 22, 0.8); transform: skewX(-10deg); border-radius: 3px;
}
:is(.ce_sliderStart, .content-custom-swiper).under_header .float_above_text p:empty,
.ce_sliderStart.under_header .float_above_text p:has(br:only-child) { display: none; }

/* --- Hero-ANIMATIONEN wie Live (www.heimseiten.de/seo-koeln, alle Werte live gemessen) ---
   Live hängt sie an tns-Klassen des C4-tiny-sliders (.tns-item/.tns-slide-active), die der
   C5-Core-Swipe nicht setzt → Animationen waren tot. Mechanik 1:1 nachgebaut:
   · Bild „zoomt langsam heraus": Basis scale(1.07) origin top-left → scale(1),
     transition 5.5s ease-in-out (= (--sliderSpeed 1500 + --sliderDelay 4000)/1000).
     Trigger via @starting-style (Transition ab Erst-Rendering, ohne JS/tns).
   · Pillen fahren nacheinander ein: Grundzustand opacity:0; left:100%, dann die in
     live-design.css VORHANDENEN @keyframes slide_in (P1) / slide_in_second (P2, delay .5s,
     endet 15px weiter links – der Live-Versatz der zweiten Pille). Scope .slide_in_only
     wie Live (Testimonial-Slider hat die Klasse nicht → unberührt). */
:is(.ce_sliderStart, .content-custom-swiper).under_header .content-slider { overflow: hidden; } /* scale 1.07 + einfahrende Pillen clippen */
/* Zoom als ANIMATION statt transition: die .preload-Sperre (settings.css: .preload *
   {transition:none !important}) killt Transitions beim Erst-Rendering — Animationen nicht
   (Beleg: die Pillen liefen). Effekt identisch zu Live's tns-Klassen-Transition. */
:is(.ce_sliderStart, .content-custom-swiper).under_header .slide_in_only img {
    transform-origin: top left;
    animation: hs_hero_zoom_out 5.5s ease-in-out forwards;
    backface-visibility: hidden; will-change: transform;
}
@keyframes hs_hero_zoom_out {
    from { transform: scale(1.07); }
    to   { transform: scale(1); }
}
:is(.ce_sliderStart, .content-custom-swiper).under_header .slide_in_only .float_above_text p {
    opacity: 0; left: 100%;
    animation: slide_in 5.5s ease-in-out forwards;
}
:is(.ce_sliderStart, .content-custom-swiper).under_header .slide_in_only .float_above_text p:nth-child(2) {
    animation-delay: 0.5s; animation-name: slide_in_second;
}
/* A11y (CLAUDE.md): Animationen an prefers-reduced-motion koppeln — dann direkt der Endzustand. */
@media (prefers-reduced-motion: reduce) {
    :is(.ce_sliderStart, .content-custom-swiper).under_header .slide_in_only img { animation: none; }
    :is(.ce_sliderStart, .content-custom-swiper).under_header .slide_in_only .float_above_text p { animation: none; opacity: 1; left: 0; }
    :is(.ce_sliderStart, .content-custom-swiper).under_header .slide_in_only .float_above_text p:nth-child(2) { left: -15px; }
}

/******************************************************** Cubes – echte 3D-Würfel (ehem. rsce_cube) */
/* Vollflächen-Hintergrundfoto hinter den Cubes wie Live: apple-1853306.jpg.
   Live: in Artikel 841 ein `<div class="bg image parallax">` mit <img> apple-1853306.jpg (1920×1280),
   voll-bleed 100vw, per margin-top:-14% als Parallax hinter die Würfel gezogen. Beleg: Live-HTML Zeile
   373/374 + DOM-Messung (article 1600=100vw, bgDiv 1600). Hier als full-bleed CSS-Background. */
.mod_article:has(.cube_container) {
    width: 100vw;
    margin-inline: calc(50% - 50vw);
    padding-block: 60px;
    /* KEIN CSS-Background: das Apple-Foto liefert das bg-Inhaltselement 4568 (.bg.image.parallax, cc-Migration)
       wie Live – mit korrektem Parallax (margin-top:-14%) und Zuschnitt. Dunkles #1d1d1d entfernt. */
}
/* Container als Flexbox wie Live (Reihe 1 = 4 Würfel, Reihe 2 = 3 ZENTRIERT) – überschreibt Spalten4-Grid.
   Breiter als die 1024er-Content-Spalte (Live: 1110), damit 4 Würfel nebeneinander passen. */
.cube_container {
    display: flex !important;
    flex-wrap: wrap;
    justify-content: center;
    align-items: flex-start;
    gap: 50px !important;
    width: min(1110px, 100%) !important;
    max-width: 100%;
    margin-inline: auto;
    padding: 25px 0;
}
/* perspective PRO Würfel im Transform; um halbe Tiefe zurückgezogen -> Vorderseite plan.
   Feste Live-Maße 228×220, Tiefe = halbe Breite (114). */
.hs-cube {
    position: relative; flex: 0 0 228px;
    width: 228px; height: 220px; max-width: 100%;
    --cube-depth: 114px;
    /* perspective als PROPERTY, KEIN transform am Container: die Hit-Box bleibt statisch,
       die Drehung passiert an den Flächen — vorher drehte die Hover-Fläche mit und
       flackerte am Transitionsende („Zucken", User 2026-07-08). */
    perspective: 684px;
}
.hs-cube > :first-child, .hs-cube > :last-child {
    position: absolute; inset: 0; margin: 0;
    transition: transform .8s ease-in-out;
    display: grid; place-content: center; gap: 12px; padding: 22px; box-sizing: border-box;
    background: #fff; backface-visibility: hidden; overflow: hidden;
    border: 1px solid #ddd;   /* Live (.surface); der frühere box-shadow lugte als „Schatten"
                                  neben den Flächen hervor (User-Screenshot 2026-07-08) */
}
.hs-cube > :first-child { transform: translateZ(calc(-1 * var(--cube-depth))) rotateY(0deg) translateZ(var(--cube-depth)); }   /* Vorderseite, plan */
.hs-cube > :last-child  { transform: translateZ(calc(-1 * var(--cube-depth))) rotateY(90deg) translateZ(var(--cube-depth)); }   /* rechte Seite */
.hs-cube:hover > :first-child, .hs-cube:focus-within > :first-child {
    transform: translateZ(calc(-1 * var(--cube-depth))) rotateY(-90deg) translateZ(var(--cube-depth));
}
.hs-cube:hover > :last-child, .hs-cube:focus-within > :last-child {
    transform: translateZ(calc(-1 * var(--cube-depth))) rotateY(0deg) translateZ(var(--cube-depth));
}
.hs-cube > :first-child img {
    max-width: 84px; max-height: 84px; width: auto; height: auto;
    display: block; margin-inline: auto;   /* mittig — die figcaption ist oft breiter als das Icon */
    filter: invert(0.6) sepia(0.45) saturate(31.98) hue-rotate(42deg) brightness(0.99) contrast(0.83);
}
.hs-cube > :first-child figcaption { font-weight: 600; color: #333; font-size: 15px; text-align: center; }
@media (max-width:1023px){
    /* Teaser-Karten mobil: das LABEL (anonymes Flex-Item, text-align greift dort nicht)
       über align-items zentrieren, das BILD aber weiter kartenfüllend (align-self:stretch) —
       center allein schrumpfte die figure auf die kleine 139er-Mobilvariante (User 2026-07-09) */
    .hs-teaser-grid .content-hyperlink { align-items: center; }
    .hs-teaser-grid .content-hyperlink figure { align-self: stretch; }
    /* !important nötig: die Desktop-Regel setzt gap 50/width 1110 mit !important
       (gegen RasterabstandL) — ohne Konter klebt der Container mobil randlos (iPhone-Befund) */
    /* Rand = HALBER Würfel-gap (User 2026-07-10): Container voll breit mit
       padding gap/2, Würfel exakt (Innenbreite − gap) / 2 */
    .cube_container {
        --wuerfel-gap: 24px;
        /* EINE Breiten-Variable wie das ORIGINAL (--cube_width in live-design.css):
           Breite, Tiefe (÷2) und Perspektive (×3) leiten sich daraus ab — Live:
           `figure.surface.right { rotateY(90deg) translateZ(calc(var(--cube_width) / 2)) }`,
           `.container { perspective: calc(var(--cube_width) / 2 * 6) }`. Nur so
           schließen die Würfelkanten bei JEDER Größe. Die frühere FIXE Tiefe 85px
           ließ die Flächen bei 163er-Würfeln (375px) auseinanderklaffen
           (User 2026-07-11 „die Würfelseiten sind nicht aneinander").
           Formel = Layout-Breite: Artikel/Container voll-bleed 100vw, padding
           2×12px, gap 24px → (100vw − 48px)/2, gedeckelt auf 170 (Live-Wert ≤1023). */
        --wuerfel-breite: min(170px, calc(50vw - var(--wuerfel-gap)));
        gap: var(--wuerfel-gap) !important;
        width: 100% !important;
        padding-inline: calc(var(--wuerfel-gap) / 2);
        box-sizing: border-box;
    }
    .cube_container > * { width: var(--wuerfel-breite) !important; }
    /* Würfel mobil WIE LIVE (User 2026-07-09: „funktionieren mobil super"): echtes 3D
       inkl. HOVER-Drehung bleibt aktiv (Original: .container:hover .cube { …
       rotateY(-90deg) }; Touch-Browser emulieren :hover beim Antippen), nur kleiner. */
    .hs-cube {
        flex-basis: auto; height: auto; aspect-ratio: 1; font-size: 13px;
        --cube-depth: calc(var(--wuerfel-breite) / 2);
        perspective: calc(var(--wuerfel-breite) * 3);
    }
    .hs-cube > :first-child img { max-width: 56px; max-height: 56px; }
}
/* Text-Mindestbreite neben Umfluss-Bildern (User 2026-07-11: „Texte müssen eine
   Mindestbreite haben"; bei 390px/iPhone 12 Pro ausdrücklich OK wie bisher):
   unter 390px blieben neben dem 210er-Porträt nur ~100px → Ein-Wort-Zeilen.
   Wie Live bei 375: Bild als eigener Block, Text in voller Breite darunter.
   green_icon-Karten ausgenommen (kleines Icon floatet weiter neben dem Text). */
@media (max-width: 389.98px) {
    #wrapper .media:not(.green_icon) figure:is(.float_left, .float_right) {
        float: none;
        margin: 0 0 12px;
    }
}
@media (prefers-reduced-motion: reduce){ .hs-cube > :first-child, .hs-cube > :last-child { transition: none; } }

/******************************************************** hs-teaser-grid (ehem. rsce_teaser) */
/* Label-Zeile zentriert; figure/img blockig ohne Eigen-Margins. Die komplette
   Karten-Anatomie (Kasten, 4/3-Bild, h2-Label, Hover-Scale) steht in der Sektion
   „Seiten-Teaser-Karten" weiter unten — die früheren Duplikat-Regeln hier
   (3/2-Bild, translateY-Hover, grüner Link-Label) widersprachen ihr und sind
   entfernt (Vereinheitlichung 2026-07-11). */
.hs-teaser-grid .content-hyperlink { text-align: center; }
.hs-teaser-grid .content-hyperlink figure, .hs-teaser-grid .content-hyperlink img { margin: 0; display: block; }
/* Teaser darf über die 1024er-Lesebreite hinaus bis zur cc_max_width (Artikel 116 = 1400px);
   überschreibt live-design.css `.mod_article > * { width:1024px }` (höhere Spezifität). */
.mod_article > .hs-teaser-grid { width: 100%; }
/* Teaser-Gap wie Live (~20px statt RasterabstandM) → Karten ~413 breit, Bilder höher (matcht Live-Höhe).
   !important, weil die RasterabstandM-Gap-Regel im Grid-Framework verschachtelt (höher spezifisch) ist. */
.hs-teaser-grid { gap: 0 !important; }
/* Teaser als FLEX statt Grid-Framework (User-Wunsch 2026-07-08): Karten exakt Grid-Zellbreite
   (1/3, gap 0), aber unvollständige Reihen werden ZENTRIERT (z. B. 2 Teaser auf
   /technologien-im-webdesign) — wie die Live-RSCE-Flexboxen mit justify-content:center.
   Mobil (<1024) stapelt weiterhin das Grid-Framework. */
@media (min-width: 1024px) {
    .hs-teaser-grid.Spalten3 { display: flex !important; flex-wrap: wrap; justify-content: center; }
    .hs-teaser-grid.Spalten3 > * { width: calc(100% / 3); }
}
/* ^ gap 0 — Live-Messung (offsetTop-Reihen [0,386], Container-CONTENT 1160 = 1200 − 2×20 Padding):
   die 387er-Wrapper stoßen OHNE gap aneinander; die sichtbaren 60px-Lücken zwischen den Karten-Kästen
   sind das 30er-Wrapper-Padding (Rahmen sitzt am inneren Item) — siehe ::before-Rahmen unten. */

/* Media-Split-Blöcke wie Live: FLOAT statt des text-image-positioning-Bundle-Grids. Das Bundle rendert
   .content-text.BildBisZurHälfte/.media--right ab 1024px als 6-Spalten-Grid mit `figure{position:absolute;
   height:100%}` → der Text landet in einer schmalen Spalte (statt um das Bild zu fließen) und das absolute
   Bild füllt die Zeile. Live hatte diese Elemente als reines C4-Float (Bild floated, Text wrappt herum;
   die BildBisZurHälfte-Klasse ist ein Migrations-Artefakt). Wir heben Grid + `position:absolute` auf und
   stellen den Float her (der entscheidende Punkt: figure/img von absolute → static, sonst greift float nicht). */
@media (min-width: 1024px) {   /* nur wo das Bundle den Grid setzt; mobil stapelt es ohnehin (wie Live) */
    #wrapper .content-text.BildBisZurHälfte {
        display: block !important;
        height: auto !important;
        overflow: visible !important;
    }
    #wrapper .content-text.BildBisZurHälfte .image_container {
        position: static !important;
        height: auto !important;
        width: auto !important;
        grid-column: auto !important; grid-row: auto !important;
    }
    #wrapper .content-text.BildBisZurHälfte .image_container.float_right { float: right !important; max-width: 50%; margin: 0 0 15px var(--image_margin, 30px) !important; }
    #wrapper .content-text.BildBisZurHälfte .image_container.float_left  { float: left  !important; max-width: 50%; margin: 0 var(--image_margin, 30px) 15px 0 !important; }
    #wrapper .content-text.BildBisZurHälfte .image_container picture { position: static !important; height: auto !important; display: block; }
    #wrapper .content-text.BildBisZurHälfte .image_container img { position: static !important; height: auto !important; width: 100%; max-width: 100%; object-fit: fill !important; }
}

/* Desktop-Hauptnavigation wie Live: KEINE sichtbaren Submenü-Pfeil-Buttons (Live-DOM: nur
   <span>-Links, kein svg/::after; die 24px-Core-Buttons verschoben zudem die Abstände).
   A11y bleibt: Buttons sind nur VISUELL versteckt (sr-only-Pattern) und erscheinen bei
   Tastatur-Fokus; Hover-Dropdowns liefern die live-design.css-Regeln (li:hover .level_2). */
body.nav-is-desktop #header .mod_navigation .btn-toggle-submenu {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: 0;
    overflow: hidden; clip-path: inset(50%); border: 0;
}
body.nav-is-desktop #header .mod_navigation .btn-toggle-submenu:focus-visible {
    position: static; width: auto; height: auto; clip-path: none; overflow: visible;
}
/* Abstände wie Live: NUR die a-Margins (0 20px → 40px-Lücken) aus live-design.css. Die a11y-Nav
   ergänzte ul display:flex+gap:20 und li padding 5px 20px 5px 0 → Lücken +40px zu groß und der
   letzte Punkt endete 20px vor der inside-Rechtskante (Live: Kontakt bündig bei x=1240). */
body.nav-is-desktop #header .mod_navigation ul.level_1 { display: block; gap: 0; }
body.nav-is-desktop #header .mod_navigation ul.level_1 > li { padding: 0; }
/* Live nullt den letzten Punkt über li.last/a.last — die a11y-Nav vergibt kein „last" → :last-child.
   Damit endet „Kontakt" bündig an der inside-Rechtskante (x=1240) wie Live. */
body.nav-is-desktop #header .mod_navigation ul.level_1 > li:last-child > :is(a, strong) { margin-right: 0; }
/* Live gibt Submenü-Punkten 16px rechts (li.submenu>a>SPAN{padding-right:16px}) — die a11y-Nav
   rendert keinen span → direkt aufs a (identische Geometrie; strong/aktiv hat es via Live-Regel). */
body.nav-is-desktop #header .mod_navigation ul.level_1 > li.submenu > a { padding-right: 16px; }
/* Die a11y-Nav malt eine grüne 1px-Unterstreichung (::before) unter aktive/hover-Punkte —
   Live hat KEINE (nur Farbwechsel #444 + 11px-Chevron am aktiven strong via ::after → bleibt!). */
body.nav-is-desktop #header .mod_navigation ul.level_1 :is(a, strong)::before { content: none; }

/* --- Desktop-DROPDOWN (Unternavigation) wie Live. Live stylt den SPAN im Link
   (`.level_2 li a span{padding:10px 25px 10px 20px; line-height:30px}` → Item h50, Box fs15);
   die a11y-Nav rendert KEINE spans → Items kollabierten auf 19px. Werte direkt aufs a/strong.
   Hover live GEMESSEN (span): Hintergrund #7cab16, Text weiß. padding braucht !important gegen
   live-design `.level_2 li a{padding:0}` (Spez. 1,4,3). */
body.nav-is-desktop #header .mod_navigation .navigation-main :is(ul.level_2, ul.level_3) > li > :is(a, strong) {
    padding: 10px 25px 10px 20px !important;
    line-height: 30px; font-size: 15px; display: block;
    transition: background .25s ease-in-out, color .25s ease-in-out;
}
body.nav-is-desktop #header .mod_navigation .navigation-main :is(ul.level_2, ul.level_3) > li > a:hover {
    background: #7cab16; color: #fff;
}
/* TRAIL-Elternpunkt im Dropdown grün (Live: li.submenu.trail>a span{color:#7cab16}, Hover weiß).
   NICHT der AKTIVE Punkt: der ist live WEISS auf grünem Grund (gemessen strong.active auf
   /webdesign-koeln/kundenstimmen: color #fff, bg #7cab16) — vorher färbte diese Regel auch
   .active grün → grün auf grün, Punkt unsichtbar (User-Meldung 2026-07-08). */
body.nav-is-desktop #header .mod_navigation .navigation-main :is(ul.level_2, ul.level_3) > li.trail > a:not(:hover) {
    color: #7cab16;
}
/* AKTIVER Punkt (strong) im Dropdown: weiß auf grün — live-belegt (style.scss:
   `.level_2 li a:hover span, strong { background: rgb($primary); color: #fff }`). */
body.nav-is-desktop #header .mod_navigation .navigation-main :is(ul.level_2, ul.level_3) > li > strong {
    background: #7cab16; color: #fff;
}
/* Core zeichnet eine kleine Dreieck-SPITZE über dem Dropdown (.level_2:before, 8px-Border-
   Dreieck; :not(.bounds-*) zentriert, bounds-Varianten am Rand) — Live hat KEINE Spitze
   (nur die grüne border-top-Linie). content:none deckt alle drei Positionsvarianten ab. */
body.nav-is-desktop .navigation-main .level_2::before { content: none; }
/* Core reserviert im Dropdown eine 40px-Toggle-Button-Spalte (li.submenu padding-right) —
   unser Button ist sr-only und der Chevron sitzt als ::after im Link. Ohne den Fix füllte
   der grüne Hover-/Aktiv-Kasten von Submenu-Punkten nicht die volle Dropdown-Breite
   (User-Screenshot „Backlinks für SEO", 2026-07-08). */
body.nav-is-desktop #header .mod_navigation .navigation-main :is(ul.level_2, ul.level_3) > li.submenu { padding-right: 0 !important; }
/* Box LINKSBÜNDIG am Menüpunkt (Live xRel=0; lokal stand sie 56px daneben — anderer statischer
   Kontext durch den a11y-.ul-wrapper) + Level 3 klappt wie Live LINKS neben dem Item aus. */
body.nav-is-desktop #header .mod_navigation ul.level_1 > li { position: relative; }
/* transform:none — das Core-a11y-JS schiebt Dropdowns per Inline-translateX in den Viewport
   (hier −141px); Live positioniert stur linksbündig am Menüpunkt. Die Einblende-Animation
   nutzt die separate `translate`-Property und bleibt erhalten. */
body.nav-is-desktop #header .mod_navigation .navigation-main ul.level_2 { left: 0; right: auto; transform: none !important; }
body.nav-is-desktop #header .mod_navigation ul.level_2 > li { position: relative; }
body.nav-is-desktop #header .mod_navigation .navigation-main ul.level_3 { right: 100%; left: auto; top: 0; margin-top: 0 !important; }
/* Live klappt L3 nur ≤1440 links (style.scss-@media); DARÜBER rechts an der L2-Kante
   (margin-left:100%; live @1680 gemessen: x = L2-Kante, oben Item-bündig). Die pauschale
   Links-Regel erzeugte >1440 den „fehlerhafte Navigation"-Eindruck (User-Screenshot). */
@media (min-width: 1441px) {
    /* margin-left/right:0 — live-design setzt margin-left:100% (die C4-Positionierung);
       zusammen mit left:100% wäre das doppelt verschoben (548px, aus dem Viewport). */
    body.nav-is-desktop #header .mod_navigation .navigation-main ul.level_3 {
        left: 100%; right: auto; margin-left: 0 !important; margin-right: 0 !important;
    }
}
/* Live GEMESSEN: L3-Box bündig mit Item-OBERKANTE (yRel 0), klappt links aus (rechte Kante an
   li-links); margin-top:0 gg. Core-Desktop-Regel (−50px, hob die Box ein Item zu hoch). */

/* Mobil-Navigation: live-design.css versteckt die Header-Nav <1024px (Live nutzt ein separates Mobil-Modul
   `body>.mod_navigation`); wir nutzen die barrierefreie Core-Nav (mod_navigation_accessible) MIT eigenem
   Burger-Overlay im Header → die Header-Nav muss mobil sichtbar bleiben, damit der Burger sie öffnen kann. */
@media (max-width: 1023px) {
    #header > .inside .mod_navigation,
    #header .mod_navigation { display: block !important; }
}

/* Mobil-Menü: Optik + Animationen = 1:1 die Referenz contao57.heimseiten.com. Dort kommt ALLES aus
   Core-navigation.css + den --nav-*-Variablen (settings.css, von c57 übernommen) + der identischen
   imports/mod_navigation_accessible.css — hier stehen NUR noch die Neutralisierungen der
   live-design.css-Desktop-Dropdown-Regeln, die es auf c57 nicht gibt (Spez. 1,4,2 gewinnt sonst
   gegen die Core-Regeln) + der Logo-Stacking-Fix. KEINE eigene Optik mehr (frühere Nachbau-Regeln
   der alten Live-Seite — 12/20-Zeilen, ❯-Text-Chevrons — entfernt; Core liefert den c57-Look). */
@media (max-width: 1023px) {
    /* Logo im offenen Menü sichtbar (wie c57): das Overlay lebt im z9-Kontext der Geschwister-
       .mod_navigation → Logo muss ÜBER 9. NUR z-index — das Logo ist mobil bereits position:absolute
       (live-design); ein position:relative-Override riss es beim Öffnen in den FLUSS → Header wuchs
       +46px → der bottom-verankerte Burger „hüpfte" bei jedem Toggle. */
    body.nav-is-mobile.navigation-open #header .logo { z-index: 10; }
    /* Burger vertikal mittig im 51px-Mobilheader: die live-design-Desktop-Verankerung
       (bottom:9px) schnitt ihn oben ab (44px-Button bei y=−6). WICHTIG: OHNE transform —
       ein transform am Vorfahren macht ihn zum Containing Block für position:fixed →
       das fixed Menü-Overlay (.navigation-main) wurde auf die 44px-Nav-Box geclippt
       (Menü „abgeschnitten"). Stattdessen rechnerisch: (51 − 44) / 2 = 3.5px. */
    body.nav-is-mobile #header .mod_navigation {
        top: calc((var(--header_height_mobile, 51px) - 44px) / 2); bottom: auto;
    }
    /* live-design.css (`#header>.inside .mod_navigation ul li>a{margin:0 20px;padding:0px;…}`,
       Spez. 1,4,2) plättet mobil das Core-Link-Layout → auf die Core-Werte zurück (12px 10px,
       Quelle: navigation.css des Core-Templates; auf c57 unverfälscht wirksam). */
    body.nav-is-mobile #header .mod_navigation .navigation-main :is(ul li > a, ul li > strong) {
        margin: 0 !important; padding: 12px 10px !important; display: inline-block !important;
    }
    /* live-design.css erzwingt für ALLE .level_2 position:absolute (Desktop-Dropdown) →
       mobil zurück in den Fluss, sonst überlagert das Akkordeon die Folgepunkte und die
       Core-Aufklapp-Animation (grid-template-rows 0fr→1fr) bleibt bei 0. */
    body.nav-is-mobile #header .mod_navigation .navigation-main ul.level_2 {
        position: static !important;
    }
    /* Desktop-Dropdown-LOOK (2px-Grünkante, Schatten, Weiß-BG aus live-design) mobil IMMER
       zurücknehmen — nicht nur im expanded-Zustand: beim ZUklappen fällt .nav-expanded weg und
       während der 0.6s-Ausblend-Animation (Blur!) käme sonst die grüne Kante zurück =
       verschwommener grüner Balken. */
    body.nav-is-mobile #header .mod_navigation .navigation-main ul.level_2 {
        box-shadow: none !important; border-top: 0 !important; background: transparent !important; min-width: 0 !important;
    }
    /* live-design.css hält .level_2 auf visibility:hidden/opacity:0 (zeigt nur auf :hover) —
       im aufgeklappten Zustand zurücknehmen. filter:blur(0) statt none, damit die Core-
       Blur-Transition beim Aufklappen animiert (c57-Animation). */
    body.nav-is-mobile #header .mod_navigation .navigation-main li.nav-expanded > .ul-wrapper > ul.level_2 {
        visibility: visible !important; opacity: 1 !important; filter: blur(0) !important;
    }

    /* ── EBENE 3 (User 2026-07-10, „dritte Ebene geht nicht"): exakt dieselbe Kur wie
       für level_2 — die live-Flyout-Regeln (Grünkante-Karte, absolute, :hover-Trigger,
       teils !important) müssen mit GLEICHER Spezifität überstimmt werden; die Versuche
       in mod_navigation_accessible.css verloren mangels Spezifität. Ohne Animation:
       schlicht display none/block über nav-expanded. ── */
    body.nav-is-mobile #header .mod_navigation .navigation-main ul.level_2 > li {
        /* relative statt static: der absolute Toggle-Button braucht das li als
           Bezugsrahmen (rutschte sonst eine Zeile hoch) */
        position: relative !important; height: auto !important;
    }
    body.nav-is-mobile #header .mod_navigation .navigation-main ul.level_2 .ul-wrapper {
        position: static !important; display: block !important;
        height: auto !important; overflow: visible !important;
        background: transparent !important; box-shadow: none !important;
        grid-template-rows: none !important;
    }
    body.nav-is-mobile #header .mod_navigation .navigation-main ul.level_3 {
        display: none !important;
        position: static !important; left: auto !important; top: auto !important;
        transform: none !important; width: 100% !important; min-width: 0 !important;
        background: transparent !important; box-shadow: none !important;
        border: 0 !important; padding: 0 !important; margin: 0 !important;
        opacity: 1 !important; visibility: visible !important; filter: none !important;
    }
    body.nav-is-mobile #header .mod_navigation .navigation-main li.nav-expanded > .ul-wrapper > ul.level_3 {
        display: block !important;
    }
    /* dritte Ebene optisch eine Stufe weiter einrücken */
    body.nav-is-mobile #header .mod_navigation .navigation-main ul.level_3 :is(a, strong) {
        padding-left: 30px !important;
    }
}

/******************************************************** hs-flash (Ersatz für wow.js „flash", 3 CEs)
   wow.js wurde entfernt (User-Auftrag 2026-07-08), Animationen laufen über das
   inviewport-Bundle (ivp/einblenden*-Klassen). „flash" hat dort kein Pendant —
   Keyframes 1:1 aus animate.css (opacity 1-0-1-0-1), getriggert über iivp.
   Dauer 2,5s + ease = LIVE GEMESSEN (data-wow-duration="2.5s", 1 Iteration, ease). */
.ivp.hs-flash.iivp { animation: hs_flash 2.5s ease both; }
@keyframes hs_flash {
    0%, 50%, 100% { opacity: 1; }
    25%, 75% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .ivp.hs-flash.iivp { animation: none; } }

/******************************************************** Footer: Social-Icons + Logos (Live-Werte)
   Quellen: RSCE-Inline-Styles der Live-Seite (.ce_3853/.ce_4044/.ce_4027 > .flexbox_inside …)
   + style.scss. Lokal wurden die RSCE-Flexboxen zu Element-Gruppen (Grid-Framework) migriert —
   hier auf die gemessene Live-Flex-Optik zurückgeholt. NUR Desktop: mobil bleibt der bereits
   verifizierte Grid-/Stapel-Zustand (Mobil-Sweep war overflow-frei). */
@media (min-width: 1024px) {
    /* 3-Spalten-Hauptblock (live ce_3853): Spalten calc(33% − 70px) = 339 statt Grid-253 */
    #footer .footer_main .Spalten3.RasterabstandXL {
        display: flex !important; flex-wrap: wrap; justify-content: center; align-items: flex-start;
        gap: 0 !important; margin: 10px -40px 60px;
    }
    #footer .footer_main .Spalten3.RasterabstandXL > * { width: calc(33% - 70px); margin: 40px 35px 20px; }
    /* Icon-Leiste (live ce_4044): 40x40-Kreise NEBENEINANDER, linksbündig (live computed:
       Icon width 40, margin 0 5px; fi display:flex, wrap, justify-content:flex-start) */
    #footer .content-element-group:has(> .ce_text_circle_button) {
        display: flex; flex-wrap: wrap; justify-content: flex-start; gap: 0 !important;
    }
    #footer .ce_text_circle_button { width: 40px; margin: 0 5px; }
    /* Logos-Leiste: space-between statt der Live-%-Zellen (User 2026-07-09: „Greenpeace
       nicht in der Mitte der umliegenden" — die ungleichen 26/20/20/24%-Zellbreiten machten
       die LÜCKEN ungleich). Kinder ohne Breiten/Abstände = intrinsische Logo-Breite,
       der Container verteilt die Lücken gleichmäßig. */
    #footer .logos {
        display: flex !important; flex-wrap: wrap; justify-content: space-between; align-items: center;
        gap: 0 !important; margin: 60px 0 30px;
    }
    #footer .logos > * { width: auto; margin: 0; padding: 0; box-sizing: border-box; }
}
/* live-design.css wurde .ce_image→.content-image gemappt — die Logo-html-CEs behalten aber ihr
   C4-Markup (div.ce_image) → dieselben Regeln (Werte aus style.scss .logos) fürs lokale Markup;
   inkl. der Hover-Rotation („Logos animieren nicht"-Fix Teil 1; Teil 2 = wow.js im Twig).
   figure-Margin nullen (Browser-Default 1em machte die Zellen 140 statt 114 hoch). */
/* Hover-Rotation liegt am INNEREN <a> (nicht am .ce_image!): Die transition-Shorthand am CE
   überschrieb sonst per ID-Spezifität die Einblende-Transition des inviewport-Bundles
   (.einblendenVonUnten.ivp.iivp) — die Logos fuhren dadurch in 0,25s ohne die
   verzögert05/1/15-Staffelung ein („alle gleichzeitig", Befund 2026-07-08). */
#footer .logos .ce_image { display: block; }
/* font-size:0: der Whitespace im html-CE-Markup neben dem <picture> kollabiert durch
   line-height:0 nur VERTIKAL — horizontal blieb ein ~16px-Leerzeichen in der zentrierten
   Zeile und schob jedes Logo 8px nach links („padding rechts", User 2026-07-09). */
#footer .logos .ce_image a { display: block; font-size: 0; transition: transform 0.25s ease-in-out; }
#footer .logos .ce_image a:hover { transform: translateZ(0) rotate(8deg); }
/* Einblende-Dauer wie die Live-wow-Animation (data-wow-duration="2.5s") */
#footer .logos .ce_image { --fade_duration: 2.5s; --move_duration: 2.5s; }
/* line-height:0: das html-CE-Markup hat Whitespace zwischen <a> und <picture> — lokal (picture
   rendert als block) erzeugte das eine leere 23px-Zeilenbox im inline-<a> (Zelle 140 statt 114;
   live ist picture inline und der Whitespace kollabiert). */
#footer .logos .ce_image figure { margin: 0; line-height: 0; }
#footer .logos .ce_image img { margin: auto; background: rgb(255 255 255 / 95%); border-radius: 5px; padding: 3px; height: 54px; }
#footer .logos .gls img { padding: 3px 6px; }
#footer .logos .greenpeace_energy img { background: none; padding: 0; border-radius: 0; }

/******************************************************** Technologie-Kacheln auf dem Video (live ce_3479)
   Quelle: RSCE-Inline-Style der Live-Seite (/webdesign-koeln/technologien-im-webdesign).
   Lokal war fb-3479 ein Spalten5-Grid (200px-Kacheln, lief rechts aus dem Viewport, 10. Kachel
   links statt mittig). Live: zentrierte Flexbox mit Breiten-Kaskade; letzte Reihe MITTIG.
   Mobil (<1024) bleibt der Grid-Stapel + flexbox-boxes.css-Optik (= Live-Mobilstufe). */
@media (min-width: 1024px) {
    /* Live-Kette: cc_max_width-Wrapper des Artikels 694 = 1600 (article-cc), max-width:100% →
       Wrapper-content − Padding + fi-Überhang ±20 heben sich auf: Kachel-Basis = Wrapper-Breite.
       Als EIN Element: width 1600 (cc-Wert) + padding 0 ⇒ Basis = Elementbreite. Verifiziert
       @1200: Kacheln 380, x=10/410/810 = live; @1680 greift wie live die 20%-Stufe (5er-Reihen). */
    /* width/max-width kommen seit dem article-cc-Systemfix (width:1600 + max-width:100%)
       von dort; hier nur padding 0: Kachel-Basis = Elementbreite (Live-fi ragt ±20 über
       das ce und landet exakt auf der Wrapper-Breite). */
    .mod_article > .fb-3479 { padding: 0; }
    .fb-3479 {
        display: flex !important; flex-wrap: wrap; justify-content: center;
        gap: 0 !important;
    }
    /* Stufe ≤1250 (Live-@media): 3 pro Reihe. Selektor mit .mod_article-Präfix: die generierte
       flexbox-boxes.css lädt NACH dieser Datei und setzt sonst ihre Mobil-Paddings (20/10)
       durch — live sind es 30px (Kachel 200 statt 150 hoch). */
    .mod_article > .fb-3479 > * { width: calc(33.33% - 20px); margin: 10px; padding: 30px; background: #fff; }
}
@media (min-width: 1251px) and (max-width: 1550px) {
    .mod_article > .fb-3479 > * { width: calc(25% - 20px); }
}
@media (min-width: 1551px) {
    .mod_article > .fb-3479 > * { width: calc(20% - 40px); margin: 20px; padding: 30px 20px; background: rgb(255 255 255 / .9); }
}

/******************************************************** Updateservice-Boxen (live ce_4272 has_border)
   Quelle: RSCE-Inline-Style der Live-Seite (/contao-updateservice): fi flex + justify-center,
   fi-margin ±40 POSITIV, Kinder calc(33.33% − 40px) + margin 20 + padding 30/20 +
   background rgb(251 251 251) + border #ddd. ce live 1240 breit. Lokal war fb-4272 ein
   Spalten3-Grid mit 100er-Gap (Boxen 261 statt 347 breit). */
@media (min-width: 1024px) {
    /* width 1280 kommt vom article-cc-Systemfix; padding 0 60: Live-Kette Wrapper 1280 →
       ce-content 1240 → fi-margins +40 ⇒ Kachel-Basis 1160 = 1280 − 2×60. */
    .mod_article > .fb-4272 { padding: 0 60px; }
    .fb-4272 {
        display: flex !important; flex-wrap: wrap; justify-content: center;
        gap: 0 !important;
    }
    .mod_article > .fb-4272 > * {
        width: calc(33.33% - 40px); margin: 20px; padding: 30px 20px;
        background: rgb(251 251 251); border: 1px solid #ddd; border-radius: 2px;
    }
}

/******************************************************** Referenzen: slide_second_image-Hover (Live-Effekt)
   Live GEMESSEN (/webdesign-referenzen): Galerie-Wrapper 1230, ul 615 (=50%), BEIDE li 615
   übereinander (li2 absolute, z1, unter li1 z2); HOVER schiebt li2 mit transition .8s ease-in-out
   auf left:100% → zweites Bild fährt rechts neben dem ersten hervor; Galerie overflow:hidden.
   C5-Galerie hat keine row_0/row_1-Klassen → :first-child/:nth-child(2). Desktop-only:
   mobil stapeln die Bilder normal (Touch hat kein Hover). */
@media (min-width: 1024px) {
    /* Live-Breite: Galerie 1230 (cc_max_width; article-cc liefert max-width) — die
       .mod_article>*-1024-Klemme lösen, sonst Bild 202 statt 585. Die HALBIERUNG liefert Live
       selbst: `.referenzen .content-gallery{padding-right:50%}` steht in live-design.css und
       greift aufs C5-Markup → ul füllt die linke Hälfte (615). KEIN width am ul (doppelte
       Halbierung war der 305px-Bug). */
    .mod_article > .content-gallery.slide_second_image { width: 100%; }
    .content-gallery.slide_second_image { overflow: hidden; }
    /* overflow:visible am UL ist der SCHLÜSSEL: die C5-Galerie setzt ul{overflow:hidden} und
       clippte das mit left:100% herausfahrende li2 (Bild2 blieb unsichtbar — bewiesen per
       elementsFromPoint-A/B-Test). Außen clippt weiterhin die Galerie (padding-Box = 1230). */
    .content-gallery.slide_second_image > ul { position: relative; margin: 0; padding: 0; overflow: visible; }
    .content-gallery.slide_second_image > ul > li { list-style: none; }
    /* Live: auch li1 hat 30px (rechts) — Bild1 550 im 580er-li; zusammen mit li2-pad-left 30
       ergibt das die 60px-Lücke der Vorlage. */
    .content-gallery.slide_second_image > ul > li:first-child { position: relative; z-index: 2; padding-right: 30px; box-sizing: border-box; }
    /* Live GEMESSEN: li2 RUHE opacity:0 (Hover FADET ein) + padding-left:30px (ergibt die
       ~60px-Lücke zwischen den Bildern: Bild1 endet 30px vor li-Kante, Bild2 beginnt 30px
       nach li2-Start), transition all .8s (left UND opacity). */
    .content-gallery.slide_second_image > ul > li:nth-child(2) {
        position: absolute; top: 0; left: 0; width: 100%; z-index: 1;
        opacity: 0; padding-left: 30px; box-sizing: border-box;
        transition: left .8s ease-in-out, opacity .8s ease-in-out;
    }
    .content-gallery.slide_second_image:hover > ul > li:nth-child(2) { left: 100%; opacity: 1; }
    /* Beim Hover ÜBER die absolute Textspalte heben (live-design legt .referenzen-Texte mit
       z-index:2 über die rechte Galerie-Hälfte). Der z-Abfall beim Verlassen ist um die
       Animationsdauer VERZÖGERT (transition-delay-Trick) — sonst „springt" der Text sofort vor
       das noch zurückfahrende/ausblendende Bild. */
    .content-gallery.slide_second_image { transition: z-index 0s linear .8s; }
    .content-gallery.slide_second_image:hover { z-index: 3; transition-delay: 0s; }
    /* Die TRANSPARENTE rechte Galerie-Hälfte (padding-right:50%) liegt über der Textspalte und
       fraß deren Maus-Events (Text nicht markierbar, Links tot). pointer-events:none am Container,
       auto am ul → nur das BILD ist Hover-/Klick-Zone (ul:hover setzt den :hover der Galerie ✓,
       Lightbox-Klick bleibt); Text rechts ist voll interaktiv. */
    .content-gallery.slide_second_image { pointer-events: none; }
    .content-gallery.slide_second_image > ul { pointer-events: auto; }
    /* BREITENUNABHÄNGIGKEIT (Live löst das über einen gemeinsamen 1230er-Wrapper um
       Galerie+Text — Deckung live bei 1920 verifiziert: Bild2 = Text = 990..1575):
       Lokal klemmt article-cc.css jedes Kind EINZELN auf max-width:1230 → die %-Bezüge
       (Galerie-padding-right:50 % aus live-design, Text-left/width) rechnen weiter auf dem
       vollen Artikel und die Deckung bräche oberhalb ~1270 px Viewport (gemessen: ul 290
       statt 615, Text-margin-auto-Versatz 172,5). Daher:
       1) ul-Hälfte klemmt MIT: padding-right = min(50 % [Artikel-Content], 615px [=1230/2]).
       2) Text exakt auf die Bild2-Fläche, Bezug P = Artikel-Padding-Box:
          Galerie galW = min(P−40, 1230), zentriert → galX = (P−galW)/2;
          Bild2.left  = galX + galW/2 + 30 = P/2 + 30    → calc(50% + 30px) (Klemme kürzt sich!)
          Bild2.width = galW/2 − 30                      → min(calc(50% − 50px), 585px)
          margin:0/right:auto zwingend: live-design setzt right:0 + margin:auto — bei
          over-constrained left+width+right verteilen die auto-Margins den Slack (1920:
          2×172,5 px) und verschöben den Text sonst aus der Deckung. */
    .mod_article.referenzen .content-gallery.slide_second_image { padding-right: min(50%, 615px); }
    .mod_article.referenzen .content-gallery.slide_second_image + .content-text {
        left: calc(50% + 30px) !important;
        width: min(calc(50% - 50px), 585px) !important;
        right: auto !important;
        margin: 0 !important;
    }
}

/* (Box-Karten-Styling jetzt per-Element via files/layout/css/flexbox-boxes.css – apply_flexbox_boxes.php) */

/* Feature-Karten-Gruppen – vertikale Lücke wie Live. Live's `.flexbox_inside` ist flex und gibt jeder
   Karte (.cc_text_in_center_of_image) 10px oben+unten → im flex-Container KEIN Margin-Collapse → 20px
   zwischen Karten, 10px oben/unten (803: +80px Container = exakt die fehlenden −80). Die RSCE→nativ-
   Migration legte die Gruppe als display:block mit Kartenrand 0 ab → Karten klebten. Eng gescopt per
   :has() nur auf Gruppen, die diese Karten enthalten (verändert NICHT die Kartenhöhe, nur den Abstand). */
.content-element-group.RasterabstandS:not([class*="Spalten"]):has(> .green_icon) { display: flex; flex-direction: column; }
.content-element-group.RasterabstandS:not([class*="Spalten"]) > .green_icon { margin-block: 10px; }

/* Zähler-Flexbox #Sicherheit /contao-agentur-koeln (ce_4099 → fb-4099): der Live-<style>-Block gibt der
   Flexbox `margin-top:20px; margin-bottom:40px` und jedem Item `margin-top:20px` (Quelle: live
   `.ce_4099>.flexbox_inside`). Die fb-Migration übernahm Box-Optik + Spaltenbreiten, NICHT diese vertikalen
   Margins → Sektion −80px zu kurz (20+20+40). Exakt aus dem Live-<style> reproduziert. */
.fb-4099 { margin-top: 20px; margin-bottom: 40px; }
.fb-4099 > * { margin-top: 20px; }

/* ====== Risiko-Umbauten 2026-07-07 (freigegeben): Grid→Flex, EXAKTE Live-<style>-Reproduktion ======
   Die Spalten-Grids (Grid-Framework, gap 40/100) machen die Karten schmaler als Live's Flexbox mit
   NEGATIV-Gutter → mehr Textumbrüche → Höhen-Δ. Hier je Flexbox die komplette Live-Layout-Mechanik
   (display:flex, Negativ-Margins, calc-Breiten, Item-Margins, align) 1:1 aus dem Live-<style> übernommen.
   Box-Optik (padding/border/bg) kommt weiter aus flexbox-boxes.css. gap:0 (Live nutzt Margins statt gap). */

/* Nur Desktop (≥1024): darunter stapelt das Grid-Framework die Spalten (mobil verifiziert overflow-frei)
   und die 3-Spalten-calc-Breiten wären falsch. Live erreicht die Extra-Breite über Negativ-Gutter am
   1024er-Wrapper (= 1064/1044 effektiv); lokal erzwingen die Artikel-Regeln (.mod_article>*{width:1024;
   margin:auto} + #article-ID-Regeln) Breite/Margin/Padding → daher explizite Breite + !important. */
@media (min-width: 1024px) {
    /* fb-3784 (/ueber-mich, Text|Rundbild|Text): Live GEMESSEN: flexbox_inside-Content 1004,
       Items 315 (=33.33%·1004−20), Rundbild-Item 315×315, fb 335, align center. */
    .content-element-group.fb-3784 {
        display: flex !important; flex-wrap: wrap; gap: 0 !important; padding: 0 !important;
        width: min(1004px, 100%) !important; margin-inline: auto !important;
        justify-content: center; align-items: center;
    }
    .content-element-group.fb-3784 > * { width: calc(33.33% - 20px) !important; margin: 10px !important; box-sizing: border-box; }

    /* fb-4104 (/contao-agentur, 3 Karten über BG-Video): Live GEMESSEN: Items 360 breit
       (=33.33%·1200−40) und 321 hoch — der Artikel ist full-width, Container-Content 1200
       (nicht 1024+Gutter!). Container mt40/mb10; Items mb20; align normal=stretch wie Live. */
    .content-element-group.fb-4104 {
        display: flex !important; flex-wrap: wrap; gap: 0 !important; padding: 0 !important;
        width: min(1200px, 100%) !important; margin-inline: auto !important;
        margin-top: 40px; margin-bottom: 10px;
        justify-content: center; align-items: normal;
    }
    .content-element-group.fb-4104 > * { width: calc(33.33% - 40px) !important; margin: 0 20px 20px !important; box-sizing: border-box; }

    /* fb-4089 (/shopware, 2 Text-Spalten): Live GEMESSEN: Container-Content 1024, Items 472
       (=50%·1024−40), h429; lokal machte das Spalten2-Grid (gap 100) sie 442 breit → 450 hoch (+21). */
    .content-element-group.fb-4089 {
        display: flex !important; flex-wrap: wrap; gap: 0 !important; padding: 0 !important;
        width: min(1024px, 100%) !important; margin-inline: auto !important;
        justify-content: center; align-items: normal;
    }
    .content-element-group.fb-4089 > * { width: calc(50% - 40px) !important; margin: 0 20px !important; box-sizing: border-box; }
}

/* #article-756 (Contao-Logo + Titel): Live gibt der figure.float_above margin-bottom:20px (gemessen:
   Bild 300×82 + mb20 + H2 27 = 129; lokal fehlte die mb → 119, Artikel −20). Bewusst NUR auf diesen
   Artikel gescopt — .img_center/.media--above gibt es auch in Spalten-Gruppen + /shopware (884, dort
   ist lokal ZU HOCH), eine breite Regel würde dort überschießen. */
#article-756 .content-text .image_container.float_above { margin-bottom: 20px; }

/* ===========================================================================
   Theme-Gruppen-Wrapper (.hs-header/.hs-footer) transparent machen, damit das
   Live-CSS direkt auf #header/.inside > Logo/Nav/… und #footer/.inside greift.
   =========================================================================== */
.hs-header, .hs-footer { display: contents; }
/* Info-Leiste wie auf der Live-Seite ausblenden (dort #header_top { display:none }) */
#header .info_row, #header .info_box { display: none; }

/* Logo-Bild auf C5-figure-Struktur begrenzen (Live: max-height 80px, links) */
#header .logo { height: 100%; }
#header .logo figure, #header .logo figure a, #header .logo picture { height: 100%; display: flex; align-items: center; margin: 0; }
#header .logo img { max-height: 80px; width: auto; height: auto; object-fit: contain; object-position: left; }

/******************************************************** Number-Counter (plenta countup) – große grüne Zahl + Label, wie Live (.counter_wrapper .number 65px #7cab16) */
.content-plenta-countup { text-align: center; padding: 20px 30px 30px; }  /* wie Live .counter_wrapper (0 30 30) + 20px oben für die leere .text.before-Zeile */
.content-plenta-countup .countup-value { display: block; font-size: 65px; line-height: 1.2; color: var(--color_1, #7cab16); }
.content-plenta-countup .countup-suffix { display: block; font-size: 23px; color: var(--font_color, #555); }
@media (max-width:1023px){
    .content-plenta-countup .countup-value { font-size: 46px; }
    .content-plenta-countup .countup-suffix { font-size: 18px; }
}

/******************************************************** Kundenmeinungen-Slider (Swiper-Struktur)
   Der Testimonial-Slider ist seit dem Umbau ein natives Swiper-Element — die Live-Box-Regeln
   (live-design.css) zielen auf die alte .content-slider-Struktur und griffen nicht mehr.
   Werte 1:1 aus dem Live-CSS (.kundenmeinungen .content-slider .content-text …); Pfeile/
   Punkte-Optik aus den Live-tns-Regeln auf die Swiper-Controls gemappt. */
.kundenmeinungen :is(.ce_sliderStart, .content-custom-swiper) { position: relative; }
.kundenmeinungen .content-custom-swiper .content-text {
    border: 1px solid #ddd; background: white; padding: 50px 70px 70px 70px; border-radius: 10px;
}
@media (max-width: 1023px) {
    .kundenmeinungen .content-custom-swiper .content-text { padding: 50px 30px 70px 30px; min-height: 470px; }
}
.kundenmeinungen .content-custom-swiper .content-text strong { color: #7cab16; font-size: 24px; font-weight: 300; }
.kundenmeinungen .content-custom-swiper .content-text img { border-radius: 50%; margin-bottom: 20px; filter: grayscale(1); }
/******************************************************** Desktop-Nav: Untermenü-Pfeile + Subnav-Abstand (2026-07-09)
   Live zeigt an Punkten mit Untermenü einen 11px-Chevron (Regel zielt auf .submenu>a>SPAN:after —
   das C5-Nav-Markup mod_navigation_accessible hat KEIN span im Link → hier aufs C5-Markup gemappt,
   Werte 1:1 live gemessen: 11×11, top 0.5em, chevron-down 11px; in Ebene 2+ chevron-right 7px
   rechtsbündig). Abstand: Live-Lücke Link-Unterkante→Dropdown = -1px, das C5-Markup ergab -6px
   (Dropdown überlappte den Text) → margin-top 4px; minimale Überlappung bleibt (Hover-Brücke!). */
body.nav-is-desktop #header .mod_navigation li.submenu > :is(a, strong) { position: relative; }
body.nav-is-desktop #header .mod_navigation .level_1 > li.submenu > :is(a, strong)::after {
    content: ""; position: absolute; width: 11px; height: 11px; top: 0.5em; margin-left: 5px;
    background: url('/files/layout/plugins/fontawesome-pro/svgs/light/chevron-down.svg') center / 11px no-repeat;
    transition: transform .25s ease-in-out;
}
/* Pfeil dreht sich, sobald das Untermenü offen ist (Hover ODER Tastatur/Klick = nav-expanded) */
body.nav-is-desktop #header .mod_navigation .level_1 > li.submenu:hover > :is(a, strong)::after,
body.nav-is-desktop #header .mod_navigation .level_1 > li.submenu.nav-expanded > :is(a, strong)::after,
body.nav-is-desktop #header .mod_navigation .level_1 > li.submenu:focus-within > :is(a, strong)::after {
    transform: rotate(180deg);
}
body.nav-is-desktop #header .mod_navigation :is(.level_2, .level_3) li.submenu > :is(a, strong)::after {
    content: ""; position: absolute; width: 11px; height: 21px; top: 18px; right: 0; margin-right: 5px;
    background: url('/files/layout/plugins/fontawesome-pro/svgs/light/chevron-right.svg') center / 7px no-repeat;
}
body.nav-is-desktop #header .mod_navigation .level_1 > li > .ul-wrapper > .level_2 { margin-top: 4px; }
/* Unsichtbare HOVER-BRÜCKE über dem Dropdown: die 4px-Optik-Lücke liegt sonst außerhalb des
   li → li:hover riss beim Herunterfahren ab und das Menü schloss sich (User 2026-07-09).
   Als ::after, weil ::before von der Bounds-Pfeil-Regel (content:none) belegt ist; im
   Zu-Zustand ist das ul hidden → die Brücke fängt dann nichts. */
body.nav-is-desktop #header .mod_navigation .level_1 > li > .ul-wrapper > .level_2::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 8px;
}

/* Bewertungslinks-Flexbox 3798: Spalten-Layout kommt aus flexbox-boxes.css (Generator
   fb-3798, Live-Quelle = per-Element-<style> .ce_3798, Beleg _qa/live-html/
   webdesign_koeln_kundenstimmen.html Z. 138); hier nur die Live-min-width der Boxen
   (Generator erfasst width, nicht min-width) → mobil stapeln sie automatisch.
   flex+center (User 2026-07-09 „nicht mittig"): Icon+Text saßen als float-Media-Block
   linksbündig in der 382px-Box (−90px Asymmetrie) → als Paar zentriert. */
.fb-3798 > * { min-width: 350px; display: flex; justify-content: center; align-items: center; }
/* Außen-Margins der Flex-Items angleichen (figure 10 links vs. Text 20 rechts = 5px
   Linksversatz der zentrierten Gruppe); der 30px-Gap Icon↔Text bleibt (10+20).
   Facebook-Box nutzt float_right_text (Live-Markup), gerendert identisch. */
.fb-3798 > * :is(.float_left_text, .float_right_text) { margin-right: 10px; }
/* Pfeile — LIVE GEMESSEN (Desktop): 120×120-Klickfläche (padding 60), Icon 30px zentriert,
   vertikal MITTIG (top 50 %; die frühere top:116px-Regel ist live die MOBIL-Variante),
   links/rechts AN der Box-Kante. */
.kundenmeinungen .content-custom-swiper :is(.swiper-button-prev, .swiper-button-next) {
    width: 120px; height: 120px; top: 50%; margin-top: 0; transform: translateY(-50%);
    background: transparent center / 30px no-repeat; border: 0; cursor: pointer;
    transform-origin: top center; transition: transform .25s ease-in-out;
}
.kundenmeinungen .content-custom-swiper :is(.swiper-button-prev, .swiper-button-next)::after { content: none; }
.kundenmeinungen .content-custom-swiper .swiper-button-prev { left: 0;  background-image: url('/files/layout/img/icons/arrow_left_green.svg'); }
.kundenmeinungen .content-custom-swiper .swiper-button-next { right: 0; background-image: url('/files/layout/img/icons/arrow_right_green.svg'); }
.kundenmeinungen .content-custom-swiper :is(.swiper-button-prev, .swiper-button-next):hover { transform: scale(1.2) translateY(-50%); }
@media (max-width: 1023px) {
    .kundenmeinungen .content-custom-swiper :is(.swiper-button-prev, .swiper-button-next) { top: 116px; transform: none; }
    .kundenmeinungen .content-custom-swiper :is(.swiper-button-prev, .swiper-button-next):hover { transform: scale(1.2); }
}
/* Punkte — LIVE GEMESSEN: 10×10, hellgrau rgba(221,221,221,.5) MIT 1px-Rahmen
   rgba(153,153,153,.5); aktiv/hover GRAU rgba(153,153,153,.5) (nicht grün!); bottom 43px. */
.kundenmeinungen .content-custom-swiper .swiper-pagination { bottom: 43px; }
.kundenmeinungen .content-custom-swiper .swiper-pagination-bullet {
    width: 10px; height: 10px; margin: 0 4px; padding: 0; box-sizing: border-box;
    background: rgba(221, 221, 221, 0.5); border: 1px solid rgba(153, 153, 153, 0.5);
    border-radius: 50%; opacity: 1; transition: background .25s ease-in-out;
}
.kundenmeinungen .content-custom-swiper .swiper-pagination-bullet:hover,
.kundenmeinungen .content-custom-swiper .swiper-pagination-bullet-active { background: rgba(153, 153, 153, 0.5); }

/******************************************************** Seiten-Teaser-Karten (hs-teaser-grid) – Bild + grünes Label, Rahmen, Hover-Scale, wie Live (.ce_teaser_item) */
/* Metrik EXAKT nach Live-Anatomie (offsetHeight-Messung /seo-koeln #786 + Homepage #116, transformfrei):
   wrapper 387×386 = pad 30 ringsum · Bild 325×243 (NICHT vollbreit!) · Titelzone unten:
   pt30 + 21px-Zeile (fs16) + mb10 · Item-Fuß 20 (→ padding-bottom 30+20=50).
   Der RAHMEN sitzt live am INNEREN Item (327 breit), nicht am 387er-Wrapper → hier als
   ::before-Inset-Box (inset:30px), damit die sichtbaren Kästen + 60px-Lücken wie Live sind. */
.hs-teaser-grid .content-hyperlink {
    display: flex; flex-direction: column;
    position: relative; border: 0; background: transparent; overflow: visible;
    padding: 30px 30px 50px;
    transition: transform .25s ease-in-out;
}
/* Kachel-Invertierungs-Regel (User): Karten-Kasten WEISS auf grauen Artikeln (Default —
   alle Teaser-Artikel tragen explizite gray/white-Klassen), GRAU auf weißen. Vorher war er
   fest #fbfbfb = grau-auf-grau, nur der Rahmen war sichtbar. */
.hs-teaser-grid .content-hyperlink::before {
    content: ""; position: absolute; inset: 30px; z-index: 0;
    border: 1px solid #ddd; background: #fff;
    border-radius: var(--border_radius, 3px); /* Rundung an der Kachel statt am Bild (User) */
}
.mod_article.white .hs-teaser-grid .content-hyperlink::before { background: rgb(251, 251, 251); }
.hs-teaser-grid .content-hyperlink:hover { transform: translateZ(0) scale(1.02); }
/* figure bewusst UNPOSITIONIERT (der Über-dem-Kasten-z-index liegt am img): so ist die
   KACHEL der containing block des Stretched-Links unten — die ganze Karte ist klickbar,
   nicht nur das Bild (User 2026-07-09). */
.hs-teaser-grid .content-hyperlink > figure { order: 1; margin: 0; }
/* position:static PFLICHT (eine globale Regel setzt Links relative): nur so ist die KACHEL
   der containing block des ::after — sonst deckt der Stretched-Link bloß das Bild ab.
   overflow:hidden (User 2026-07-09): clippt die überstehende Bildkante („Unterstrich unter
   der Kachel"); Radius NUR OBEN und EXAKT der Kastenrundung (::before) — das Bild trägt
   keinen eigenen sichtbaren Radius, es folgt bündig der Kachel (5px-Rundum erzeugte an den
   oberen Ecken wieder Artefakte gegen den 3px-Kasten, beim Hover-Scale sichtbar). Das
   absolute ::after entgeht dem Clipping (containing block = Kachel) — Klickfläche bleibt. */
.hs-teaser-grid .content-hyperlink figure a { margin: 0; display: block; position: static;
    border-radius: var(--border_radius, 3px) var(--border_radius, 3px) 0 0; overflow: hidden; }
.hs-teaser-grid .content-hyperlink figure a::after { content: ""; position: absolute; inset: 0; z-index: 2; }
/* Der Hover-„Unterstrich" (live-designs animierter Link-Unterstrich a::before, 1px grün,
   scaleX-Sweep) — durch position:static spannte er sich am KACHELBODEN über die volle
   Elementbreite (User-Befund). Vom User gewünscht: als UNTERER INNENRAND der Kachel →
   deckungsgleich auf die untere Kastenrahmen-Linie gelegt (Kasten = ::before-Inset-Box 30px);
   content/height/Farbe/Animation kommen weiter aus der live-design-Basisregel.
   Seitlich um den Kastenradius eingezogen — sonst ragte die gerade Linie in den
   gerundeten Ecken über den Rahmen hinaus (User 2026-07-09). */
.hs-teaser-grid .content-hyperlink figure a::before {
    left: calc(30px + var(--border_radius, 3px));
    right: calc(30px + var(--border_radius, 3px));
    width: auto; bottom: 30px;
}
/* Hover-Scale DIREKT statt träge: das Bundle hält nach der Einblendung `transition: …
   var(--move_duration) … var(--ivp_animation_delay)` am Element (.einblendenVonUnten.ivp.iivp,
   0,3,0) — der Hover erbte Dauer + Staffel-Delay. theme.js setzt hs-hoverfix erst NACH der
   Einblendung (statisch ginge nicht: beim Klassenwechsel zählt die transition des
   ZIELzustands — das Einfahren selbst würde auf .25s verkürzt). */
.hs-teaser-grid .content-hyperlink.hs-hoverfix { transition: transform .25s ease-in-out; }
.hs-teaser-grid .content-hyperlink img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block;
    position: relative; z-index: 1; /* über dem ::before-Kasten (vorher am figure) */ }
.hs-teaser-grid .content-hyperlink > h2,
.hs-teaser-grid .content-hyperlink > h3 {
    order: 2; margin: 30px 0 10px; padding: 0; font-size: 16px; font-weight: 600;
    color: var(--color_1, #7cab16); line-height: 1.3;
    position: relative; z-index: 1;
}

/******************************************************** Spaltenlisten (lange Aufzählungen, z. B. Case-Study-Erweiterungen) */
.hs-spaltenliste ul { columns: 2; column-gap: 48px; }
.hs-spaltenliste li { break-inside: avoid; }
@media (max-width: 767px) { .hs-spaltenliste ul { columns: 1; } }

/******************************************************** hs-kacheln – generische Karten-Reihe im Website-Look */
/* DNA der Live-fb-Karten (flexbox-boxes.css): 1px #ddd, Radius 2, Padding 30,
   helle Fläche; Invertierung grau/weiß wie bei den Teaser-Kacheln. Als
   Element-Gruppe mit nativen Kind-CEs einsetzbar (cssID-Klasse hs-kacheln) —
   erster Einsatz: Case-Study-Seiten (User 2026-07-11 „Text in Kacheln"). */
.hs-kacheln { display: flex; flex-wrap: wrap; justify-content: center; gap: 20px; margin-block: 20px; }
.hs-kacheln > * {
    flex: 1 1 0; min-width: min(280px, 100%); margin: 0;
    border: 1px solid #ddd; border-radius: 2px; padding: 30px;
    background: #fbfbfb; box-sizing: border-box;
}
.mod_article.gray .hs-kacheln > * { background: #fff; }
.hs-kacheln .content-text :is(p, ul):last-child { margin-bottom: 0; }
@media (max-width: 767px) { .hs-kacheln { flex-direction: column; } .hs-kacheln > * { min-width: 0; } }

/******************************************************** Galerie-Spalten (C5-Modifier content-gallery--cols-N) */
/* Das C5-Gallery-Fragment rendert perRow nur als Klasse — ohne CSS stapeln die
   Bilder (Case-Study-Galerien 2026-07-11). Bewusst NUR --cols-3 und nur ≥1024:
   mobil bleibt der verifizierte Stapel; die beiden Alt-Galerien (cols-4,
   Demo-Kontexte) bleiben unberührt. */
@media (min-width: 1024px) {
    .content-gallery--cols-3 > ul {
        display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 20px; padding: 0; margin: 0; list-style: none;
    }
    .content-gallery--cols-3 li { margin: 0; }
    .content-gallery--cols-3 img { width: 100%; height: auto; }
}

/* Case-Study-Screenshot-Reihe (eigene Klasse, damit die globalen cols-4-Demo-
   Galerien unberührt bleiben): einzeilige Galerie unten, responsive. */
@media (min-width: 1024px) {
    .screenshot-galerie > ul {
        display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: 20px; padding: 0; margin: 0; list-style: none;
    }
    .screenshot-galerie li { margin: 0; }
    .screenshot-galerie img { width: 100%; height: auto; }
}
@media (min-width: 600px) and (max-width: 1023px) {
    .screenshot-galerie > ul {
        display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 16px; padding: 0; margin: 0; list-style: none;
    }
    .screenshot-galerie li { margin: 0; }
    .screenshot-galerie img { width: 100%; height: auto; }
}

/* Screenshot-Reihe: feiner Rahmen, damit die oft weißen Website-Screenshots sich vom
   (ebenfalls hellen) Seitenhintergrund abheben. */
.screenshot-galerie img, .screenshot-rahmen img { border: 0.5px solid #ddd; box-sizing: border-box; }

/* Spaltenliste („Umgesetzte Funktionen") mit Aufzählungspunkten (global list-style:none;
   die li sind position:static/padding-left:0 → hier relative + Platz für den Punkt schaffen). */
.hs-spaltenliste li { position: relative; padding-left: 20px; }
.hs-spaltenliste li::before {
    content: ""; position: absolute; left: 3px; top: 0.62em;
    width: 7px; height: 7px; border-radius: 50%; background: #7cab16;
}

/* Lightbox-Caption (glightbox): Text v+h mittig, kein Margin, heller grüner Hintergrund
   (viele Website-Screenshots sind weiß → Caption soll sich absetzen) + Trennlinie oben. */
.glightbox-clean .gslide-description.description-bottom,
.glightbox-clean .gslide-description { background: #eef5e2 !important; border-top: 1px solid #ddd; }
.glightbox-clean .gdesc-inner {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    text-align: center;
}
.glightbox-clean .gslide-title,
.glightbox-clean .gslide-desc { margin: 0 !important; text-align: center; }

/* Inline-Bestätigung nach Formular-Absenden (Kontakt/Rückruf) — self-contained,
   Marken-Grün, damit sie überall (auch im Footer) sauber erscheint. */
.form-confirmation {
    background: #eef5e2;
    border: 1px solid #cfe3b0;
    border-left: 4px solid #7cab16;
    border-radius: 8px;
    padding: 22px 26px;
    margin: 16px 0;
}
.form-confirmation h2 { margin-top: 0; margin-bottom: 8px; color: #5a7a12; }
.form-confirmation p { margin: 0; }

/* Formularfelder einheitlich abgerundet – gleicher Radius wie Seitenteaser
   (var(--border_radius,3px)) und Kacheln. Vorher uneinheitlich (0/1/2/3px).
   Nur text-artige Felder + Textarea + Select; Buttons/Submit + Checkbox/Radio
   bleiben außen vor (Buttons übernimmt ein anderer Agent). */
input[type=text], input[type=email], input[type=tel], input[type=url], input[type=number],
input[type=search], input[type=password], input[type=date], input[type=time],
input[type=datetime], input[type=datetime-local], input[type=month], input[type=week],
input:not([type]), textarea, select,
.ce_form input:not([type=submit]):not([type=button]):not([type=checkbox]):not([type=radio]):not([type=image]),
form .widget input:not([type=submit]):not([type=button]):not([type=checkbox]):not([type=radio]):not([type=image]),
form .widget textarea, form .widget select {
    border-radius: var(--border_radius, 3px) !important;
}

/* Textarea wächst automatisch mit dem Inhalt (moderne CSS-Eigenschaft field-sizing).
   height:auto hebt die feste Höhe auf, damit field-sizing greift; min-height hält die
   bisherige Startgröße (~4 Zeilen); max-height deckelt. Browser ohne field-sizing-Support
   ignorieren die Zeile → unverändertes Verhalten (feste, manuell resizebare Höhe). */
textarea {
    field-sizing: content;
    height: auto !important;
    min-height: 108px;
    max-height: 60vh;
}

/* Englische Seiten: Logo-Untertitel ist länger („Web Design from Cologne") → auf dem
   Desktop kleiner, sonst zu lang. Mobil (≤1023px, dort 9.4px) bleibt unberührt. */
@media (min-width: 1024px) {
    html[lang="en"] #header > .inside .logotext { font-size: 13.3px; }
}


/******************************************************** Kundenstimmen: STATISCHER Rahmen (nur das Innere slidet) + Pfeile INNERHALB des Rahmens */
/* Rahmen vom slidenden Slide auf die statische Viewport-Box (.content-slider) verlagern */
.kundenmeinungen .content-slider {
    border: 1px solid #ddd; background: #fff; border-radius: 10px; overflow: hidden;
}
.kundenmeinungen .content-slider .content-text {
    border: 0; background: transparent; border-radius: 0;
}
/* Pfeile nach innen rücken (ragten vorher über den Rahmen hinaus) – höhere Spezifität als swipe.min.css */
.kundenmeinungen :is(.ce_sliderStart, .content-custom-swiper) .slider-prev { left: 32px; right: auto; }
.kundenmeinungen :is(.ce_sliderStart, .content-custom-swiper) .slider-next { right: 32px; left: auto; }

/******************************************************** Header-Slogan "simple is beautiful" wie Live (Live nutzt --scroll-Parallax → ohne JS kaputt, daher fixe Position) */
#header > .inside { position: relative; }
/* Position kommt KOMPLETT aus live-design (`#header>.inside .slogan{ bottom:calc(var(--scroll)
   *1000px); margin-bottom:62px }`) — theme.js setzt --scroll seit 2026-07-08, das Bild wandert
   beim Scrollen nach oben heraus wie im Original. Die frühere statische bottom/margin-
   Überschreibung hier hatte genau das blockiert. */
#header > .inside .slogan img { width: 100%; height: auto; display: block; }

/******************************************************** Footer Partner-Logos: weiße abgerundete Box wie Live (greenpeace ohne Box) */
#footer .logos .ce_image img,
#footer .logos .content-image img {
    background: rgba(255,255,255,.95); padding: 3px; border-radius: 5px; height: 54px; width: auto;
}
#footer .logos .ce_image:nth-child(3) img { background: none; padding: 0; }  /* greenpeace (gpe_lg_neg, weißes Logo) */

/******************************************************** Footer Social-Icons: weißes Marken-SVG im Kreis (Inline-SVG ging bei der Migration verloren) */
.ce_text_circle_button a { position: relative; }
.ce_text_circle_button.linkedin a::before,
.ce_text_circle_button.xing a::before,
.ce_text_circle_button.facebook a::before,
.ce_text_circle_button.github a::before {
    content: ""; position: absolute; inset: 0; margin: auto; width: 20px; height: 20px;
    background: center / contain no-repeat;
}
/* Kontakt-Icons (Briefumschlag/Telefon/Stern) vor E-Mail/Telefon/Bewertung im Footer */
.contact-icon { width: 16px; height: auto; vertical-align: -2px; margin-right: 6px; }

.ce_text_circle_button.linkedin a::before { background-image: url('/files/layout/img/social/linkedin.svg'); }
.ce_text_circle_button.xing a::before     { background-image: url('/files/layout/img/social/xing.svg'); }
.ce_text_circle_button.facebook a::before { background-image: url('/files/layout/img/social/facebook.svg'); }
.ce_text_circle_button.github a::before   { background-image: url('/files/layout/img/social/github.svg'); }

/******************************************************** FAQ-Akkordeons (SEO-Ausbau 2026-07-08)
   Neue Money-Page-/Preise-FAQs als natives C5-Akkordeon (handorgel; Core liefert JS + Basis-CSS).
   Optik frei im Site-Look gestaltet (Rahmen #ddd, Grün als Akzent, Chevron aus dem Nav-Bestand);
   das FAQPage-JSON-LD erzeugt theme.js aus dem DOM (.mod_article.faq). */
.content-accordion { border: 1px solid #ddd; border-radius: 2px; background: #fff; }
.content-accordion .handorgel__header { margin: 0; }
.content-accordion .handorgel__header + .handorgel__content,
.content-accordion .handorgel__header:not(:first-child) { border-top: 1px solid #eee; }
.content-accordion .handorgel__header__button {
    display: flex; justify-content: space-between; align-items: center; gap: 15px;
    width: 100%; padding: 14px 20px; background: #fff; border: 0; cursor: pointer;
    font: inherit; font-weight: 500; text-align: left; color: rgb(85 85 85);
    transition: color .25s ease-in-out;
}
.content-accordion .handorgel__header__button::after {
    content: ""; flex-shrink: 0; width: 14px; height: 14px;
    background: url('/files/layout/plugins/fontawesome-pro/svgs/light/chevron-down.svg') center / 14px no-repeat;
    transition: transform .25s ease-in-out;
}
.content-accordion .handorgel__header__button:hover { color: var(--color_1, #7cab16); }
.content-accordion .handorgel__header--open .handorgel__header__button { color: var(--color_1, #7cab16); }
.content-accordion .handorgel__header--open .handorgel__header__button::after { transform: rotate(180deg); }
.content-accordion .handorgel__content__inner .content-text { padding: 2px 20px 14px; }
/* handorgel blendet das __inner erst mit der --opened-Klasse ein (opacity-Fade), die es NUR
   per transitionend vergibt — beim initialen data-open feuert keine Transition → der Inhalt
   der vorgeöffneten Frage blieb bis zum ersten Toggle UNSICHTBAR (Befund /preise 2026-07-09).
   Fix: Inhalt immer deckend, nur die Höhen-Animation behalten. */
.content-accordion .handorgel__content__inner { opacity: 1 !important; }
/* Letzter geöffneter Inhalt: kleine Luft zur Kasten-Unterkante (User 2026-07-09 „klebt am
   Rand der Kachel"). Nur im OFFENEN Zustand (--open bleibt beim Zuklappen bis transitionend
   → fährt weich mit raus); Core-transition `height .2s` MUSS mit aufgeführt werden, sonst
   killt das Shorthand die Höhen-Animation. */
.content-accordion .handorgel__content:last-child { transition: height .2s, margin-bottom .2s; }
.content-accordion .handorgel__content--open:last-child { margin-bottom: 14px; }

/******************************************************** Abstand letzter Artikel → Fußzeile (User-Wunsch 2026-07-09)
   Überschreibt live-designs .mod_article:last-child{padding-bottom:70px}. Der User hatte die
   Regel in style.css eingetragen — die ist NICHT eingebunden (nur Referenz-Konvertierung der
   alten style.scss; das aktive Design kommt aus live-design.css + dieser Datei). */
.mod_article:last-child { padding-bottom: 150px; }

/* Mobile-Nav: Overlay beginnt unter der 51px-Kopfzeile (User-Vorgabe 2026-07-09;
   das frühere Padding-oben 52px in --nav-mobile-padding ist dafür auf 0 gestellt). */
body.nav-is-mobile .navigation-main { margin-top: 51px; }

/* Text-Buttons (link_as_button) sitewide als Pille (User-Vorgabe 2026-07-09,
   erweitert auf alle Buttons für einheitliche Optik — z. B. auch „Kontakt aufnehmen").
   Hover: dunkleres Palette-Grün (#629100, Beleg: footer_bottom) — die transition
   bringt die Style-Manager-Button-Regel schon mit. */
.link_as_button a { border-radius: 2em; }
html body .content-text.link_as_button a:hover { background: #629100; color: #fff; }

/******************************************************** Kachel-Invertierung (User-Regel 2026-07-09)
   Artikel-Hintergründe wechseln strikt grau/weiß; KACHELN darin sind immer invertiert:
   auf grauen Artikeln weiße Kacheln (Bestand: Teaser, Zitat-Boxen, Preise-FAQ), auf
   WEISSEN Artikeln graue Kacheln — hier fürs FAQ-Akkordeon umgesetzt. */
.mod_article.white .content-accordion { background: rgb(251, 251, 251); }
.mod_article.white .content-accordion .handorgel__header__button { background: transparent; }

/******************************************************** hs-cards: Kachel-Raster (User 2026-07-09
   „abwechslungsreicher als Text und Strichlisten"). Karten-Optik im Site-Look (Rahmen #ddd,
   Radius 2px); Hintergrund INVERTIERT zum Artikel (Kachel-Regel). Variante --steps mit
   grünem Nummern-Kreis (CSS-Counter). Flex + justify-center: einzelne Kacheln mittig. */
.hs-cards { display: flex; flex-wrap: wrap; justify-content: center; gap: 20px;
    margin-top: 30px; /* Luft zum Text/zur Überschrift darüber (User 2026-07-09) */ }
.hs-cards > * { width: calc(33.33% - 14px); margin: 0; box-sizing: border-box;
    border: 1px solid #ddd; border-radius: 2px; background: #fff; padding: 25px 20px; }
.mod_article.white .hs-cards > * { background: rgb(251, 251, 251); }
.hs-cards .rte > p:first-child, .hs-cards p:first-child { margin-top: 0; }
.hs-cards--steps { counter-reset: hs-step; }
.hs-cards--steps > * { counter-increment: hs-step; text-align: center; }
.hs-cards--steps > *::before { content: counter(hs-step);
    display: flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; margin: 0 auto 12px; border-radius: 50%;
    background: var(--color_1, #7cab16); color: #fff; font-weight: 600; }
/* Kachel-Icons ({{fa::…}}-Insert-Tags des icon-Bundles, User 2026-07-09 „grün, mittig,
   größer"): Icon als eigene zentrierte Zeile über dem Titel. fill:currentColor defensiv —
   die Pro-SVGs (files/…) tragen es als Attribut, die Free-SVGs (assets/…) nicht. */
.hs-cards i.svg { display: block; width: 60px; margin: 0 auto 15px; color: var(--primary_color, #7cab16); }
.hs-cards i.svg svg { display: block; width: 100%; height: auto; fill: currentColor; }
.hs-cards i.svg + br { display: none; } /* das br im Content bräche sonst eine Leerzeile */
/* Kachel-Überschriften (das strong am Absatzanfang) mittig (User 2026-07-09);
   der Fließtext darunter bleibt linksbündig. */
.hs-cards p > strong:first-child { display: block; text-align: center; }
.hs-cards p > strong:first-child + br { display: none; } /* der Block bricht selbst um */
/* Kacheln sanft nacheinander einfahren (User 2026-07-09, wie Seitenteaser): Kinder tragen
   ivp einblendenVonUnten, die Staffelung kommt zentral über die Delay-Variable. */
.hs-cards > :nth-child(2) { --ivp_animation_delay: .2s; }
.hs-cards > :nth-child(3) { --ivp_animation_delay: .4s; }
.hs-cards > :nth-child(4) { --ivp_animation_delay: .6s; }
.hs-cards > :nth-child(5) { --ivp_animation_delay: .8s; }
.hs-cards > :nth-child(6) { --ivp_animation_delay: 1s; }

/* Inhalte nach Kachel-Raster bzw. CTA-Buttons nach einem Akkordeon klebten ohne
   Abstand aneinander (User 2026-07-10, /shopware-koeln) — Seiten-Rhythmus 30px
   (= gemessener Abstand Überschrift → Kacheln). */
.hs-cards + * { margin-top: 30px; }
.content-accordion + .hs-cta { margin-top: 30px; }
@media (max-width: 1023px) {
    .hs-cards > * { width: 100%; max-width: 420px; }
}

/******************************************************** Funnel-Einstieg (Startseite, 2026-07-09)
   Erste Funnel-Frage als Kachel-Links unter der Ablauf-Timeline; ?typ=… wählt die Antwort
   auf /kostenfreies-angebot vor (funnel.js springt zu Frage 2). Kachel-Optik = hs-cards. */
.funnel-einstieg { width: min(700px, 100%); margin: 34px auto 0; }
.funnel-einstieg .content-text p { margin: 0 0 16px; }
.funnel-einstieg .content-hyperlink { display: inline-block; margin: 0 10px 10px 0; }
.funnel-einstieg .content-hyperlink a {
    display: inline-flex; align-items: center; gap: 9px;
    padding: 14px 20px; background: #fff; border: 1px solid #ddd; border-radius: 8px;
    font-weight: 500; color: rgb(85 85 85); text-decoration: none;
    transition: border-color .2s ease, transform .15s ease, background .2s ease, color .2s ease;
}
.mod_article.white .funnel-einstieg .content-hyperlink a { background: rgb(251 251 251); }
.funnel-einstieg .content-hyperlink a:hover {
    border-color: var(--primary_color, #7cab16); transform: translateY(-2px);
}
/* Icon per CSS-Mask (der hyperlink-Linktext escaped Insert-Tag-HTML) — überschreibt
   zugleich live-designs Hover-Unterstrich-::before an diesen Links. */
.funnel-einstieg .content-hyperlink a::before {
    content: ""; position: static; display: inline-block; flex-shrink: 0;
    width: 20px; height: 20px; background: var(--primary_color, #7cab16);
    transform: none; visibility: visible;
    -webkit-mask: var(--fs-icon) center / contain no-repeat;
    mask: var(--fs-icon) center / contain no-repeat;
}
.funnel-einstieg .fs-webseite a::before { --fs-icon: url('/files/layout/icons/fontawesome/svgs/light/globe.svg'); }
.funnel-einstieg .fs-shop a::before { --fs-icon: url('/files/layout/icons/fontawesome/svgs/light/cart-shopping.svg'); }
.funnel-einstieg .fs-aendern a::before { --fs-icon: url('/files/layout/icons/fontawesome/svgs/light/wrench.svg'); }
.funnel-einstieg .fs-seo a::before { --fs-icon: url('/files/layout/icons/fontawesome/svgs/light/magnifying-glass.svg'); }

/******************************************************** hs-timeline: vertikaler Prozess
   (User 2026-07-09: „So läuft Ihr Website-Projekt ab" nicht NOCHMAL Kacheln — direkt davor
   sind schon die Konkret-Kacheln). Nummern-Kreise wie hs-cards--steps (Konsistenz), dazwischen
   eine Verbindungslinie; keine Boxen, schmale Lesespalte. Schritte fahren gestaffelt ein. */
.hs-timeline { counter-reset: hs-step; width: min(700px, 100%); margin: 30px auto 0; }
.hs-timeline > * { counter-increment: hs-step; position: relative;
    margin: 0; padding: 5px 0 32px 60px; box-sizing: border-box; }
.hs-timeline > *::before { content: counter(hs-step);
    position: absolute; left: 0; top: 0;
    display: flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; border-radius: 50%;
    background: var(--color_1, #7cab16); color: #fff; font-weight: 600; }
.hs-timeline > *:not(:last-child)::after { content: "";
    position: absolute; left: 17.5px; top: 44px; bottom: 8px;
    width: 1px; background: #ddd; }
.hs-timeline > *:last-child { padding-bottom: 0; }
.hs-timeline p { margin: 0; }
.hs-timeline > :nth-child(2) { --ivp_animation_delay: .2s; }
.hs-timeline > :nth-child(3) { --ivp_animation_delay: .4s; }
.hs-timeline > :nth-child(4) { --ivp_animation_delay: .6s; }
.hs-timeline > :nth-child(5) { --ivp_animation_delay: .8s; }

/* Seiten-Teaser: sanft nacheinander einblenden (User 2026-07-09) — Kacheln tragen
   ivp einblendenVonUnten (Bundle), die Staffelung kommt zentral über die Delay-Variable. */
.hs-teaser-grid > :nth-child(2) { --ivp_animation_delay: .2s; }
.hs-teaser-grid > :nth-child(3) { --ivp_animation_delay: .4s; }
.hs-teaser-grid > :nth-child(4) { --ivp_animation_delay: .6s; }
.hs-teaser-grid > :nth-child(5) { --ivp_animation_delay: .8s; }
.hs-teaser-grid > :nth-child(6) { --ivp_animation_delay: 1s; }

/* Kundenmeinungen: der Autoplay-Statusbalken (.swiper-progress, Bundle) ragte ECKIG über
   die runden 10px-Ecken der Zitat-Box (User 2026-07-09) → untere Ecken mitrunden. */
.kundenmeinungen .content-custom-swiper .swiper-progress { border-radius: 0 0 10px 10px; }
/* Ein-Bild-„Slider" (Titelbilder der Unterseiten): der Autoplay-Statusbalken ist dort
   sinnlos — es gibt nichts, worauf er hinzählt (User 2026-07-09). Generisch über die
   Slide-Anzahl gelöst; Slider mit ≥2 Bildern behalten ihre Bar. */
.content-custom-swiper:has(.swiper-wrapper > .swiper-slide:only-child) .swiper-progress { display: none; }

/* Content-h1: Abstand zum Folgetext (User 2026-07-09 „gedrungen") — die Basis-Regel h1,h5
   setzt margin:0; das enge Titel/Untertitel-Duo der Startseite (.headline_home, 1px
   !important) bleibt davon unberührt. */
#main h1 { margin-bottom: 20px; }

/* /ueber-mich: die drei kurzen Erfahrungs-/Kenntnis-Listenpunkte einzeilig (User 2026-07-09);
   der längste ragt ~22px ins 30px-Box-Padding der fb-3784-Karten — unsichtbar, alles
   overflow:visible. NUR Desktop: mobil (Karte 342px, ul 273px) schnitt nowrap die
   Listentexte sichtbar ab (Mobil-Durchgang 2026-07-09). */
@media (min-width: 1024px) {
    .fb-3784 li { white-space: nowrap; }
}

/* Reine Überschrift-Artikel (User-Regel 2026-07-09, Beispiel /seo-koeln „SEO-Maßnahmen…"):
   Ist ein content-text das EINZIGE Inhaltselement des Artikels und eine Überschrift sein
   einziger Inhalt, entfällt deren margin-bottom (der Artikel schließt symmetrisch mit
   seinem Padding). Gleiches für alleinstehende headline-Elemente. */
.mod_article > .content-text:only-child :is(h1, h2, h3, h4, h5, h6):only-child { margin-bottom: 0; }
.mod_article > .content-headline:only-child { margin-bottom: 0; }

/* Mobile Nav (User 2026-07-09): KEINE Untermenü-Chevrons im Overlay (die +/×-Buttons
   übernehmen das Signal); defensiv gegen Klassen-Races beim Viewport-Wechsel. */
body.nav-is-mobile #header .mod_navigation li.submenu > :is(a, strong)::after { content: none; }
/* Untermenü-Schließen: Ausblenden (opacity) läuft ETWAS SCHNELLER als das Verschwimmen
   (blur) — beide starten gleichzeitig (User-Wunsch; Core-CSS hat beide auf 0.5×Dauer). */
body.nav-is-mobile .navigation-main .level_1 .ul-wrapper ul {
    transition:
        filter calc(var(--submenu-height-animation-duration) * .5s) ease-out,
        visibility calc(var(--submenu-height-animation-duration) * .5s) ease-out,
        opacity calc(var(--submenu-height-animation-duration) * .3s) ease-out;
}

/* fb-3761/fb-3774: Live gibt den Kindern KEINE width mit (Extraktor: childW leer) — dort
   füllen die langen Texte die Zeile trotzdem voll (gemessen live: alle Kacheln exakt 984).
   Lokal schrumpften sie auf Inhaltsbreite (930/975/817) → explizit volle Breite
   (User 2026-07-09: „alle gleich breit, siehe Original"). */
.fb-3761 > *, .fb-3774 > * { width: 100%; }

/******************************************************** Modernes CSS-Parallax (User 2026-07-09)
   Scroll-Driven Animation (animation-timeline: view()) statt js_parallax-Scrollhandler:
   läuft am Compositor (butterweich, kein Main-Thread-JS). Die Animation ÜBERSCHREIBT das
   transform der alten Var-Mechanik (Animations schlagen normale Deklarationen); in Browsern
   ohne Support greift automatisch weiter js_parallax (@supports). Richtung wie das
   Live-Original: Hintergrund wandert langsamer nach unten als der Inhalt. */
@supports (animation-timeline: view()) {
    /* view-timeline am ARTIKEL (Fluss-Element; identische Geometrie): am absolut
       positionierten .bg und am überhohen .inside degeneriert der View-Fortschritt
       (blieb konstant 0.5 — gemessen). */
    .mod_article:has(> .bg.parallax) { view-timeline-name: --hs-bg; }
    .mod_article > .bg.parallax .inside {
        animation: hs_parallax linear both;
        animation-timeline: --hs-bg;
    }
    @keyframes hs_parallax {
        from { transform: translateY(-7%); }
        to   { transform: translateY(7%); }
    }
    @media (prefers-reduced-motion: reduce) {
        .mod_article > .bg.parallax .inside { animation: none; }
    }
}

/* BG-Bilder/-Videos MOBIL: volle Fläche + cover (Mobil-Durchgang 2026-07-09).
   Die generische Live-Regel `@media(max-width:1023px){.mod_article>div{width:var(--max-width-mobile)
   !important}}` trifft auch den absoluten .bg; da --max-width-mobile NIRGENDS definiert ist
   (weder Live-HTML noch Live-CSS), wird width unset → shrink-to-fit: Video/Bild stand in
   NATURBREITE links im Artikel (z. B. 1280px-Video bei 390px Viewport → nur der linke
   Ausschnitt sichtbar, Rest geclippt). Auf der Live-Site identisch kaputt — hier gefixt mit
   dem vorhandenen Live-Regelwerk (object-fit:cover ist dort global gesetzt, min-width/min-height
   100%). Der Parallax-Versatz (translateY ±7%) ist mit exakt 100%-Höhe nicht abdeckbar →
   mobil Animation aus, ebenso die obsoleten Live-Versätze margin-top:-25%/-5%. */
@media (max-width: 1023px) {
    .mod_article > .bg { width: 100% !important; max-width: 100% !important; }
    .mod_article > .bg .inside {
        width: 100% !important; height: 100% !important;
        margin-top: 0 !important; animation: none !important; transform: none !important;
    }
    .mod_article > .bg picture { display: block; height: 100%; }
    .mod_article > .bg :is(video, img) { width: 100%; height: 100%; object-fit: cover; }
}

/******************************************************** Rechtstexte (Impressum/Datenschutz,
   2026-07-10): live-design nullt alle Element-Margins global — die h2-Zwischentitel der neu
   geschriebenen Rechtstexte klebten dadurch am Absatz darüber. Klasse an Artikel 80 + 81. */
.mod_article.rechtstext :is(h2, h3) { margin-top: 34px; }
.mod_article.rechtstext :is(h2, h3):first-child { margin-top: 0; }

/******************************************************** Scroll-Fortschritt OHNE JS
   (User 2026-07-10): CSS Scroll-driven Animations ersetzen die --scroll-Body-Variable.
   .progress nutzt die vorhandenen live-keyframes „progress" direkt mit Scroll-Timeline
   (statt paused + negativem delay); der .slogan bekommt eigene keyframes statt
   bottom:calc(var(--scroll)·1000px). theme.js setzt --scroll nur noch als Fallback,
   wenn der Browser das hier nicht kann (Chrome/Edge ab 2023, Safari/Firefox ab 2025). */
@supports (animation-timeline: scroll()) {
    .progress {
        animation-duration: auto;
        animation-delay: 0s;
        animation-play-state: running;
        animation-timeline: scroll();
    }
    #header > .inside .slogan {
        animation-name: hs-slogan-steigen;
        animation-duration: auto;
        animation-timing-function: linear;
        animation-fill-mode: both;
        animation-timeline: scroll();
    }
    @keyframes hs-slogan-steigen {
        from { bottom: 0; }
        to { bottom: 1000px; }
    }
}

/******************************************************** Artikel-BG-Parallax OHNE JS
   (User 2026-07-10, wie der --scroll-Umbau): view()-Timeline ersetzt js_parallax.js.
   Bewegung = exakt die live-Formel translateY(scrolled_since_element / 6) — die
   Strecke beim Durchqueren des Viewports ist (100vh + Elementhöhe), davon 1/6.
   js_parallax.js läuft nur noch als Fallback ohne view()-Support. */
@supports (animation-timeline: view()) {
    /* Timeline am ARTIKEL, nicht am .bg: die Artikel haben overflow:hidden und wären
       damit selbst der (nie scrollende) „Scroller" ihres absoluten .bg — die Timeline
       stünde still (so gemessen an der Cubes-Sektion #article-841). Der Artikel liegt
       im Root-Scroller; seine Box = .bg-Box (inset 0), die Bewegung bleibt identisch. */
    .mod_article:has(> .bg.parallax) { view-timeline: --hs-bg-parallax block; }
    .mod_article > .bg.parallax .inside {
        animation-name: hs-bg-parallax;
        animation-duration: auto;
        animation-timing-function: linear;
        animation-fill-mode: both;
        animation-timeline: --hs-bg-parallax;
    }
    @keyframes hs-bg-parallax {
        from { transform: translateY(0); }
        to { transform: translateY(calc((100vh + 100%) / 6)); }
    }
    /* TITELBILD-Artikel (erster Artikel der Seite) sind beim Laden schon sichtbar —
       die view()-Range stünde mitten im Fortschritt und schöbe das Bild nach unten
       („Titelbild nur halb zu sehen", backlinks-fuer-seo 2026-07-10; das live-JS
       hatte dafür den pageYOffset-Sonderfall). Titelbilder stehen im modernen Pfad
       ruhig; der JS-Fallback behält sein live-Verhalten. */
    #main > .inside > .mod_article:first-child > .bg.parallax .inside { animation: none; }
}

/******************************************************** Header-Schrumpfen OHNE JS
   (Modernisierungs-Audit 2026-07-10): die live-Formel height = 130 − y/3 (Desktop,
   Klemme min-height:83 aus live-design) als scroll()-Timeline — 130→83 sind 47px,
   mal 3 = erreicht nach 141px Scrollweg (animation-range). theme.js schrumpft nur
   noch als Fallback ohne Scroll-Timelines. */
@supports (animation-timeline: scroll()) {
    @media (min-width: 1024px) {
        #header {
            animation-name: hs-header-schrumpf;
            animation-duration: auto;
            animation-timing-function: linear;
            animation-fill-mode: both;
            animation-timeline: scroll();
            animation-range: 0px 141px;
        }
    }
}
@keyframes hs-header-schrumpf {
    from { height: var(--header_height, 130px); }
    to { height: 83px; }
}

/******************************************************** hs-wachsen: Zahlen + Überschriften
   wachsen sanft auf (User 2026-07-10, Timeline „So läuft Ihr Website-Projekt ab" — Abwechslung
   zu den vielen Von-unten-Einblendungen). Nutzt das ivp-Protokoll des inviewport-Bundles
   (beobachtet alle .ivp, setzt .iivp beim Sichtbarwerden); Staffelung wie gehabt über
   --ivp_animation_delay. Der Block selbst fadet nur (keine Bewegung von unten). */
.hs-wachsen.ivp { opacity: 0; transition: opacity .5s ease var(--ivp_animation_delay, 0s); }
.hs-wachsen.ivp.iivp { opacity: 1; }
.hs-wachsen.ivp::before,
.hs-wachsen.ivp p > strong:first-child {
    transform: scale(.25);
    transition: transform .6s cubic-bezier(.34, 1.56, .64, 1) var(--ivp_animation_delay, 0s);
}
.hs-wachsen.ivp p > strong:first-child { display: inline-block; transform-origin: left center; }
.hs-wachsen.ivp.iivp::before,
.hs-wachsen.ivp.iivp p > strong:first-child { transform: scale(1); }
@media (prefers-reduced-motion: reduce) {
    .hs-wachsen.ivp, .hs-wachsen.ivp::before, .hs-wachsen.ivp p > strong:first-child {
        opacity: 1; transform: none; transition: none;
    }
}

/******************************************************** Mobil-Runde 2026-07-10 (User):
   Float-Bilder ohne Textabstand, Footer-Abstände viel zu groß, Social-Icons unter-
   einander, aktive Fußnav-Punkte farblich absichern. */
/* Text klebte am umflossenen Bild (Startseite „Ihr Webentwickler…") — gilt sitewide */
figure.image_container.float_left { margin: 0 20px 12px 0; }
figure.image_container.float_right { margin: 0 0 12px 20px; }

/* Aktiver Punkt in der Fußnavigation immer lesbar (weiß auf dem grünen Band) */
#footer strong.active, #footer a.active, #footer li.active > a { color: #fff; }
/* Header-Menü: aktiver level_2-Punkt war GRÜN auf dem grünen Aktiv-Balken (User 2026-07-10,
   gemessen auf /kundenstimmen: color UND Balken rgb(124 171 22)) */
/* !important nötig: das Core-navigation.css färbt mobil ALLE a/strong per
   !important mit var(--nav-mobile-color) = Grün */
#header .mod_navigation ul.level_2 strong.active,
#header .mod_navigation ul.level_2 li.active > a { color: #fff !important; }

/* Footer-Einblendungen OHNE Verschiebung/Staffel-Verzögerung: die einblendenVonUnten-
   Transforms (translateY bis 80px) + verzögert05/15-Delays hielten Logos/Blöcke am
   Seitenende SEKUNDENLANG verschoben — sah aus wie riesige kaputte Lücken (User-
   Screenshot 2026-07-10). Im Footer nur noch schnelles Fade. */
#footer .ivp {
    transform: none !important;
    transition-delay: 0s !important;
    transition-duration: .4s !important;
}

@media (max-width: 1023px) {
    /* Fußnav-Zeilen: Desktop-line-height 64px erzeugte 73px-Zeilen */
    #footer ul.level_1 > li > a,
    #footer ul.level_1 > li > strong { line-height: 2.2; }
    /* der absolute Pfeil (top:25px) passte zur alten 73px-Zeile — bei 44px mittig: */
    #footer ul.level_1 > li::before { top: 13px; }
    /* Social-Icons in EINE Reihe statt untereinander (die Gruppe fb-4044 ist eine
       168px-Desktop-Spalte → Icons wrappten 2×2; mobil volle Breite). Der umgebende
       Zweispalter fb-3853 (Service-Menü | Icons) bricht mobil auf column — sonst
       quetschte die volle Icon-Zeile die Service-Spalte buchstabenweise. */
    #footer .fb-3853 { flex-direction: column; }
    /* der eigentliche Zweispalter ist ein klassenloses flex-row-DIV IN der Gruppe */
    /* !important: flexbox-boxes.css lädt NACH dieser Datei und setzt align-items:center */
    /* margin-left 5% = dieselbe Spalten-Einrückung wie der Informationen-Wrapper
       (dort nativ 18.5px bei 371px). STRETCH statt flex-start: bei flex-start schrumpfte
       die Service-Titel-Box auf Wortbreite und ihr border-bottom-Unterstrich gleich mit
       (User 2026-07-10: „Kontakt/Informationen voller Unterstrich, Service nicht"). */
    /* Alle drei Spalten-DIVs (Kontakt | Informationen | Service) einheitlich: volle
       Breite + symmetrisches padding — vorher hatten sie Desktop-Spaltenbreiten
       (343/343/380), die Titel-Unterstriche endeten dadurch unterschiedlich. */
    #footer .fb-3853 > div {
        flex-direction: column; align-items: stretch !important;
        width: 100% !important; box-sizing: border-box;
        padding-inline: 5%; margin-left: 0;
    }

    /* NUR text-align — die seitlichen margins sind die native 18px-Einrückung,
       die auch die Informationen-Spalte trägt (User 2026-07-10: „nicht bündig") */
    #footer .fb-3853 :is(.rte, ul.level_1, .text) { text-align: left; }
    #footer div:has(> .ce_text_circle_button) {
        display: flex; justify-content: flex-start; align-items: center; gap: 16px;
        width: 100%; flex-wrap: nowrap;
    }
    /* flow-root: die Kreis-Button-Wrapper kollabierten auf Höhe 0 (Float im Inneren);
       margin-top 40px (alter Stapel-Abstand, ab dem 2. Icon) erzeugte in der Reihe
       ungleiche Höhen — !important wegen später ladender flexbox-boxes.css */
    #footer .ce_text_circle_button { width: auto; display: flow-root; margin: 0 !important; }
    #footer .ce_text_circle_button p { margin: 0; }
    /* Firmen-Logos: kompakte Lücken statt 50px */
    #footer .logos { gap: 16px !important; }
    #footer .logos .ce_image { margin: 0; }
}

/* Startseite: 38px-Listen-margin am Ende des Leistungs-Überblicks blähte die Lücke
   zur „Ihr Webentwickler"-Überschrift auf 78px (User 2026-07-10) → Sektionsabstand
   ist Sache der Artikel-Paddings (20+20). */
#article-975 .content-text :is(ul, p):last-child { margin-bottom: 0; }
@media (max-width: 1023px) {
    /* mobil zusätzlich das Artikel-Padding kappen — die Rest-Lücke steckt in tiefen
       live-Wrappern; Ziel ~40px Sektionsabstand wie überall */
    #article-975 { padding-bottom: 0; }
}

/* Referenz-Teaser mobil: das Grid-Framework stapelte 6 Kacheln à ~374px einspaltig
   mit gap 0 (Sektion 2242px hoch!) → zweispaltig mit Lücke, unter 480px einspaltig. */
@media (max-width: 1023px) {
    /* Live-Mobil-Anatomie (www.heimseiten.de @375, DOM-Messung 2026-07-11): Karte =
       schlichter Kasten (1px #ddd DIREKT am Item, unten 20px Padding), Bild vollbreit
       oben, Label darunter (mt 30 wie die Desktop-Titelzone), Gaps 20/20. Die
       Desktop-Anatomie (padding 30/30/50 + ::before-Inset-Rahmen) machte die
       ~170er-Mobil-Karten kaputt: 110px-Minibild in riesigen Leerräumen
       (User-Screenshot 2026-07-11 „teaser immer noch nicht korrekt"). */
    .hs-teaser-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; gap: 20px !important; }
    /* nicht randlos-vollbreit (Desktop-Regel .mod_article>.hs-teaser-grid{width:100%}),
       sondern mit dem Seitenrand der übrigen Inhalte */
    .mod_article > .hs-teaser-grid { width: auto; }
    .hs-teaser-grid > .content-hyperlink { margin: 0 !important; width: auto !important; }
    .hs-teaser-grid .content-hyperlink {
        padding: 0 0 20px;
        border: 1px solid #ddd; background: #fff;
        border-radius: var(--border_radius, 3px);
    }
    .mod_article.white .hs-teaser-grid .content-hyperlink { background: rgb(251, 251, 251); }
    .hs-teaser-grid .content-hyperlink::before { content: none; }        /* Desktop-Inset-Rahmen aus */
    .hs-teaser-grid .content-hyperlink figure a::before { content: none; } /* Unterstrich-Sweep (auf 30px-Inset getrimmt) aus */
    .hs-teaser-grid .content-hyperlink > h2,
    .hs-teaser-grid .content-hyperlink > h3 { margin: 30px 0 10px; }
    .hs-teaser-grid .content-hyperlink figure,
    .hs-teaser-grid .content-hyperlink picture { width: 100%; }
    .hs-teaser-grid .content-hyperlink figure img { width: 100%; height: auto; }
}

/* Linksbündige Sektions-Überschriften bündig zur Inhaltskante: live gibt
   .content-headline 9.75px margin + padding seitlich — die Überschrift stand
   dadurch ~10px eingerückt vor ihrem Text (User 2026-07-10, „Ihr Webentwickler…";
   gleiche Signatur an den neuen FAQ-/Leistungs-/Timeline-Überschriften). */
/* Sektions-Überschriften bündig zur Inhaltskante: die content-text-CEs haben
   seitlich 20px (Desktop) bzw. 10px (mobil) padding, die content-headline aber
   9.75px — die h2 stand dadurch versetzt (User 2026-07-10). Gleiche Werte setzen;
   die margins NICHT anfassen (zentrierende Breiten-Klemme der Artikel-Kinder). */
#article-973 .content-headline,
#article-974 .content-headline,
#article-986 .content-headline,
.mod_article.faq .content-headline {
    padding-left: 20px; padding-right: 20px;
}
@media (max-width: 1023px) {
    /* mobil sitzt die h2-BOX schon auf der Inhaltskante (10px) — padding 0 = bündig */
    #article-973 .content-headline,
    #article-974 .content-headline,
    #article-986 .content-headline,
    .mod_article.faq .content-headline {
        padding-left: 0; padding-right: 0;
    }
}


/* /ueber-mich mobil (User 2026-07-10, „viel zu große Abstände"): die drei Profil-
   Spalten (fb-3784: Steckbrief | Rund-Foto | Erfahrung) tragen Desktop-Werte
   (Spalten-padding 30/30, margins bis 40) — gestapelt ergab das 122/113px-Lücken.
   Kein Karten-Hintergrund an den Spalten → paddings mobil gefahrlos kappbar. */
@media (max-width: 1023px) {
    #article-815 .fb-3784 > :is(.content-text, .content-image) {
        padding-top: 8px; padding-bottom: 8px;
        margin-top: 12px; margin-bottom: 12px;
    }
}

/* fadein_left/right mobil OHNE Seitenversatz (User 2026-07-10, /webdesign-koeln +
   /kostenlose-seo-backlinks: „Bilder nicht bündig"): live schiebt die Elemente als
   Einblende-Start um ±100px (ein Viertel der 390er-Breite!) und löst erst mit wivp
   + 1,5s-Transition auf — beim Scrollen stehen Bilder sichtbar lange schief bzw.
   bleiben bei ausbleibendem Trigger dauerhaft verschoben. Das Opacity-Fade bleibt. */
@media (max-width: 1023px) {
    #wrapper .ivp.fadein_left { left: 0 !important; }
    #wrapper .ivp.fadein_right { right: 0 !important; }
    /* NUR die Varianten, die live-design mobil als BLOCK stapelt (float:none, volle
       Breite: cc_text_in_center_of_image, image_height_as_text + Verwandte), bündig
       zur Kante stellen — die Umfluss-margins (20px) rückten sie sonst 20px von der
       Kante weg. NICHT mehr pauschal ».media«: das nullte auch ECHT floatende Bilder
       (Startseiten-Porträt) und klebte den Text ans Bild (User 2026-07-11 „der
       bildabstand ist wieder weg"). AUSNAHME green_icon-Karten: dort floatet
       das ICON neben dem Text und braucht den Abstand (User 2026-07-10, /seo-koeln) */
    #wrapper :is(.cc_text_in_center_of_image, .text_in_center_of_image, .image_height_as_text, .image_full_width_height_as_text):not(.green_icon) figure.float_left { margin-right: 0; }
    #wrapper :is(.cc_text_in_center_of_image, .text_in_center_of_image, .image_height_as_text, .image_full_width_height_as_text):not(.green_icon) figure.float_right { margin-left: 0; }
    #wrapper .green_icon figure.float_left { margin-right: 24px; }
    #wrapper .green_icon figure.float_right { margin-left: 24px; }
    /* Referenz-Galerien: die Desktop-Collage-Paddings (unten 60, rechts 50% für den
       Hover-Ausfahr-Effekt) erzeugten mobil 140px-Lücken zum Text bzw. halbbreite
       Bilder (User 2026-07-10) */
    #wrapper .content-gallery.slide_second_image { padding-bottom: 0 !important; padding-right: 0 !important; margin-bottom: 12px; }
}


/* ── Mobil-Nachzügler 2026-07-10 ─────────────────────────────────────────── */
@media (max-width: 1023px) {
    /* Präventiver Umbruchschutz für lange Komposita in Titeln (Projekt-Standard,
       vgl. Zobel/Buchele + _vorlage-figma): ändert bestehende Umbrüche nicht —
       greift nur bei sonst überlaufenden Wörtern (html lang ist gesetzt).
       Mobil-Audit 2026-07-11: aktuell kein Overflow; Schutz gilt künftigen Inhalten. */
    h1, h2, h3 { overflow-wrap: break-word; hyphens: auto; }
    /* Einblendungen „fahren zu spät rein" (User, /preise): kürzere Strecke, flotte
       Transition, keine Staffel-Delays — mobil scrollt man schneller als Desktop */
    .einblendenVonUnten.ivp:not(.above_viewport) { transform: translateY(40px); }
    .einblendenVonUnten.ivp.iivp {
        transition-duration: .4s, .4s !important;
        transition-delay: 0s, 0s !important;
    }
    /* Artikel-Hintergrundbilder MOBIL ZEIGEN (User: „Hosting hat kein Hintergrundbild"):
       live blendet sie mobil bewusst aus (display:none + --background-image-mobile:none,
       4 Artikel) — auf User-Wunsch aktiv; Bilder sind WebP-Derivate (Größe 7). */
    .mod_article > .bg { display: block !important; }
    /* fb-Spalten OHNE Karten-Hintergrund gestapelt: Desktop-paddings (30) + margins (40)
       ergaben 130px-Lücken (User, /responsives-webdesign) — wie /ueber-mich kappen */
    .fb-4009 > .content-text:not(.green_icon) {
        padding-top: 8px; padding-bottom: 8px;
        margin-top: 12px; margin-bottom: 12px;
    }
}

/* ── Fußzeile: Rückrufformular-Labels (User 2026-07-11) ────────────────────
   Das live-CSS gibt Footer-Labels padding:9px 6px (Platzhalter-Lage IM Feld).
   Im hochgeschobenen .active-Zustand behält das Label die 9px + einen fast
   deckenden Hintergrund und ragte 18px ins Input — Eingaben waren oben
   angeschnitten. Oben/unten ohne Padding endet es an der Input-Oberkante. */
#footer .footer_main .widget.active label {
    padding-top: 0;
    padding-bottom: 0;
}

/* ── Sprachwechsler unter den Social-Icons (User 2026-07-11) ───────────────
   Sitzt als letztes Element der Icon-Spalte im footer_main; ohne Abstand klebte
   er an der Icon-Zeile. */
#footer .footer_main .mod_changelanguage { margin-top: 14px; }
#footer .footer_main .mod_changelanguage ul { list-style: none; margin: 0; padding: 0; }

/* ═══ Accessibility-Fixes aus PageSpeed Insights (2026-07-12) ═════════════ */

/* (2) link-in-text-block: Links im Fließtext dürfen sich nicht nur über die
   Farbe unterscheiden → Unterstrich + sanfter grüner Hover-Highlight.
   Scoped auf echte Text-Links in Absätzen/Listen; Button-Links (…<em>) und
   Bild-Links (…<img>) bleiben unberührt. Spezifität (0,2,2) schlägt die
   live-design-Regel `body .mod_article a{text-decoration:none}`. */
.mod_article .rte :is(p, li) a:not(:has(img)):not(:has(em)) {
    text-decoration: underline;
    text-underline-offset: 0.18em;
    text-decoration-thickness: from-font;
    border-radius: 3px;
    padding: 0 0.08em;
    margin: 0 -0.08em;
    transition: background-color 0.2s ease;
}
.mod_article .rte :is(p, li) a:not(:has(img)):not(:has(em)):hover,
.mod_article .rte :is(p, li) a:not(:has(img)):not(:has(em)):focus-visible {
    background-color: color-mix(in srgb, var(--color_1, #7cab16) 12%, transparent);
}

/* (3) target-size: Swiper-Pagination-Punkte auf ≥24px Tap-/Klick-Fläche.
   Der sichtbare Punkt bleibt 8px rund (als ::before), nur die tapbare Fläche
   wächst. !important schlägt das später geladene swiper-bundle.min.css. */
.swiper-pagination .swiper-pagination-bullet {
    width: 24px !important;
    height: 24px !important;
    margin: 0 2px !important;
    background: transparent !important;
    border: 0 !important;   /* nur die vergrößerte Tap-Fläche; sichtbarer Punkt = ::before */
    opacity: 1 !important;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.swiper-pagination .swiper-pagination-bullet::before {
    content: "";
    width: 10px;
    height: 10px;
    border-radius: 50%;
    box-sizing: border-box;
    background: rgba(221, 221, 221, 0.5);
    border: 1px solid rgba(153, 153, 153, 0.5);
    transition: background 0.2s ease;
}
.swiper-pagination .swiper-pagination-bullet-active::before {
    background: rgba(153, 153, 153, 0.5);
}

/* ═══ Kontaktformular (Form 2): E-Mail + Telefon nebeneinander ═══════════════
   Zwei Felder (Klasse hs-col) teilen sich eine Zeile; alle anderen Felder voll
   breit. Grid-gap sorgt für gleichen Spalten- und Zeilenabstand. Stabiler
   Selektor über data-ajax-form (unabhängig von der generierten ctrl_-ID). */
form[data-ajax-form="2"] .formbody {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 18px;
    row-gap: 14px;
    align-items: start;
}
form[data-ajax-form="2"] .formbody > * { grid-column: 1 / -1; margin-bottom: 0; }
form[data-ajax-form="2"] .formbody .widget.hs-col { grid-column: span 1; }
form[data-ajax-form="2"] .formbody input[type="hidden"] { display: none; }

@media (max-width: 640px) {
    form[data-ajax-form="2"] .formbody { grid-template-columns: 1fr; }
    form[data-ajax-form="2"] .formbody .widget.hs-col { grid-column: 1 / -1; }
}

/* ═══ Parallax auch für TITELBILDER (= erstes Artikel) aktivieren ════════════
   Der Bestand deaktivierte Parallax gezielt fürs erste Artikel
   (`#main > .inside > .mod_article:first-child > .bg.parallax .inside{animation:none}`),
   wodurch ausgerechnet die Titelbilder oben keinen Parallax hatten (Nutzerwunsch
   2026-07-12: alle Seiten mit Titelbild sollen Parallax haben). Gleiche Spezifität,
   aber später im File → gewinnt und reaktiviert die vorhandene view()-Timeline-Animation
   (Keyframes hs-bg-parallax + view-timeline --hs-bg-parallax liegen bereits am Artikel). */
/* Titelbild = ERSTES Artikel, liegt oben (schon im Viewport). Die allgemeine view()-Timeline
   gibt hier schon bei Scroll 0 ein großes translateY → Lücke oben (die .inside ist zentriert
   mit nur ~74px Overflow je Seite). Darum eigene, LÜCKENFREIE Variante: scroll()-Timeline
   (Scroll 0 = translateY 0 = bündig) + kleine sichere Auslenkung (< 74px Overflow). */
@supports (animation-timeline: scroll()) {
    #main > .inside > .mod_article:first-child > .bg.parallax .inside {
        animation-name: hs-title-parallax;
        animation-duration: auto;
        animation-timing-function: linear;
        animation-fill-mode: both;
        animation-timeline: scroll();
        animation-range: 0 100vh;
    }
    @keyframes hs-title-parallax {
        from { transform: translateY(0); }      /* oben: bündig, keine Lücke */
        to   { transform: translateY(56px); }   /* leicht nachlaufend (56px < 74px Overflow) */
    }
}
@media (prefers-reduced-motion: reduce) {
    #main > .inside > .mod_article:first-child > .bg.parallax .inside { animation: none; }
}

/* ═══ Header: fast deckendes Weiß, KEIN backdrop-filter ═══════════════════════
   PERFORMANCE (2026-07-12): Der fixierte Header hatte `backdrop-filter: blur(10px)`
   (Milchglas). Das verwischt bei JEDEM Scroll-Frame den Hintergrund neu — die
   klassische Chromium-Scroll-Ruckel-Ursache (nur Chromium, „GPU-Beschleunigung aus"
   → weg). A/B-Vergleich mit den beiden ruckelfreien Referenz-Seiten (contao57 +
   www.heimseiten.de LIVE): BEIDE haben KEINEN backdrop-filter — der Live-Header ist
   nur ~95% deckendes Weiß. Genau das stellen wir wieder her → identische Optik zur
   glatten Live-Seite, kein per-Frame-Blur mehr. (Falls der Milchglas-Effekt bewusst
   gewünscht wird: hier wieder `backdrop-filter: blur(8-10px)` + Deckkraft ~0.72 —
   dann kehrt aber die Scroll-Last zurück.) */
#header {
    background-color: rgba(255, 255, 255, 0.95);
}

/* ═══ Seitenteaser einen Tick kleiner ════════════════════════════════════════
   Das 3-Spalten-Teaser-Raster spannte fast über die volle Breite; leicht
   einschränken + zentrieren → alle Teaser-Karten etwas kompakter. !important +
   höhere Spezifität, da die .content-element-group-Regeln max-width setzen. */
.mod_article .hs-teaser-grid.Spalten3 {
    max-width: 1120px !important;
    margin-left: auto !important;
    margin-right: auto !important;
}

/* ═══ Case-Study-Button (Referenzen) ═════════════════════════════════════════
   Klassenbasiert statt Inline-Style. NIE Unterstrich (schlägt die A11y-Fließtext-
   Link-Regel), stattdessen ein schöner Hover: abdunkeln + leicht anheben + Schatten. */
.mod_article .rte .hs-cs-btn,
.hs-cs-btn {
    display: inline-block;
    margin-top: 10px;
    padding: 7px 18px;
    background: #7cab16;
    color: #fff !important;
    border-radius: 2em;
    font-weight: 500;
    font-size: .9em;
    text-decoration: none !important;
    box-shadow: 0 2px 6px rgba(124, 171, 22, .25);
    transition: background .2s ease, transform .2s ease, box-shadow .2s ease;
}
.mod_article .rte .hs-cs-btn:hover,
.mod_article .rte .hs-cs-btn:focus-visible,
.hs-cs-btn:hover,
.hs-cs-btn:focus-visible {
    background: #629100;
    color: #fff !important;
    text-decoration: none !important;
    transform: translateY(-2px);
    box-shadow: 0 7px 18px rgba(124, 171, 22, .4);
}

/* ═══ Inline-Altcha (nach Deaktivierung des floating-Modus, s. Performance-Fix) ═══
   Das Contao-Feldlabel „Sicherheitsfrage" ist überflüssig — das Altcha-Widget
   beschriftet sich selbst („Ich bin kein Roboter") — und überlappte es via
   move_labels. Ausblenden. */
.widget-altcha > label { display: none !important; }



/* ═══ PERFORMANCE: inviewport-Einblendungen von `top` (Layout) auf `transform` (Compositor) ═══
   Die fadeinup/fadein_up/fadein_bottom-Einblendungen animierten `top` über 1,5 s. `top` ist eine
   Layout-Eigenschaft → jeder Frame erzwingt Reflow, für JEDES einfahrende Element gleichzeitig.
   Beim Scrollen in den Content (~1 s nach Navigation) startet ein Schwung Einblendungen → massives
   Layout-Thrashing → Scrollen + Text-Einfahren stocken, KenBurns (transform/Compositor) nicht.
   Exakt der vom Nutzer per Screencast gezeigte Stutter. Fix: dieselbe Bewegung über
   `transform: translateY` (läuft auf dem Compositor, kein Reflow). Optik identisch. (2026-07-12) */
.fadeinup.ivp:not(.content-gallery),
.fadeinup.ivp .flexbox_inside > *,
.fadeinup.ivp .formbody > *,
.fadeinup.ivp .flex-images > *,
.ivp.fadein_up {
    top: 0 !important;
    transform: translateY(100px);
}
.ivp.fadein_bottom {
    top: 0 !important;
    transform: translateY(-100px);
}
.fadeinup.wivp:not(.content-gallery),
.fadeinup.wivp .flexbox_inside > .widget,
.fadeinup.wivp .flexbox_inside > .content-text,
.fadeinup.wivp .flexbox_inside > .content-image,
.fadeinup.wivp .formbody > .widget,
.fadeinup.wivp .flex-images > .widget,
.ivp.fadein_up.wivp,
.ivp.fadein_bottom.wivp {
    top: 0 !important;
    transform: translateY(0);
    transition: opacity 1.5s ease-in-out, transform 1.5s ease-in-out !important;
}


/* Referenz-Vorschaubild-Rahmen 0,5px (User 2026-07-13; top-level, überschreibt live-design) */
.mod_article.referenzen .content-gallery li img{border:0 !important}
.mod_article.referenzen .content-gallery li figure{position:relative}
.mod_article.referenzen .content-gallery li figure::after{content:"";position:absolute;inset:0;box-shadow:inset 0 0 0 0.5px #ddd;pointer-events:none}

/* Alternierender Artikel-Hintergrund site-weit #f8f8f8 (User 2026-07-13; überschreibt live-designs #fbfbfb) */
.mod_article:nth-child(odd){background:#f8f8f8}

/* Referenz-Titel als H2 ohne visuelle Änderung (SEO 2026-07-13) */
.referenzen .content-text h2.ref-title{margin:0 0 10px 0;padding:0;font-size:inherit;font-weight:inherit;line-height:inherit}



/* ═══ Einheitliche Buttons + Ranken-Hover (User 2026-07-13) ═══
   Einheitlicher Look für CTAs (.hs-cs-btn/.link_as_button a) UND
   Formular-Submit-Buttons (Kontakt/Funnel/Rückruf): kein Schatten, kein Unterstrich.
   Der Hover-Effekt (sich selbst zeichnende Ranken rund um den Button) wird per JS
   (Ranken-Hover 2026-07-13 wieder entfernt; hier nur der einheitliche Button-Look). */
:where(.hs-cs-btn, .content-text.link_as_button:not(.italic_link_as_button) a, .link_as_button a,
  button.submit, input[type="submit"], form button[type="submit"], .funnel-absenden button){
  position:relative; display:inline-block; margin-top:10px; padding:10px 26px;
  background:#7cab16 !important; color:#fff !important; border:0 !important; border-radius:2em !important;
  font-weight:500; font-size:.95em; line-height:1.25; text-decoration:none !important;
  box-shadow:none !important; cursor:pointer; transition:background .25s ease, transform .25s ease;
}
:where(.hs-cs-btn, .content-text.link_as_button a, .link_as_button a,
  button.submit, input[type="submit"], form button[type="submit"], .funnel-absenden button):hover,
:where(.hs-cs-btn, .content-text.link_as_button a, .link_as_button a,
  button.submit, input[type="submit"], form button[type="submit"], .funnel-absenden button):focus-visible{
  background:#6a9412 !important; color:#fff !important; text-decoration:none !important;
  box-shadow:none !important; transform:translateY(-1px);
}


/* Contao-„button_hover_underline"-::before (1px-Unterstrich) auf allen Buttons abschalten */
:where(.hs-cs-btn, .content-text.link_as_button a, .link_as_button a,
  button.submit, input[type="submit"], form button[type="submit"], .funnel-absenden button)::before{
  display:none !important; content:none !important;
}
