/* ============================================================
   Zweizunull — overrides loaded AFTER the Webflow stylesheets.
   Brand palette:
     --zzn-blue:   #002632  (page background base)
     --zzn-green:  #364604
     --zzn-orange: #d24410
   ============================================================ */

/* ------------------------------------------------------------
   FONT ALIASES — the Webflow sheet only declares the families
   "Eudoxussans", "Eudoxussansgx" and "Font Bureau Heronsans".
   Several rules below (and older CMS sections) ask for
   "Heronsans" / "HeronSansCond", which were never declared and
   silently fell back to the system sans. The Google WebFont
   loader (Inter, Droid Serif, Open Sans) is stripped for DSGVO,
   so those never load either. These aliases map the missing
   names onto the self-hosted Heron Sans files.
   ------------------------------------------------------------ */
@font-face {
  font-family: "Heronsans";
  src: url("/fonts/Font-Bureau---HeronSans-Light.woff2") format("woff2"), url("/fonts/Font-Bureau---HeronSans-Light.otf") format("opentype");
  font-weight: 300; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Heronsans";
  src: url("/fonts/Font-Bureau---HeronSans-Light-Italic.woff2") format("woff2"), url("/fonts/Font-Bureau---HeronSans-Light-Italic.otf") format("opentype");
  font-weight: 300; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "Heronsans";
  src: url("/fonts/Font-Bureau---HeronSans-Regular.woff2") format("woff2"), url("/fonts/Font-Bureau---HeronSans-Regular.otf") format("opentype");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Heronsans";
  src: url("/fonts/Font-Bureau---HeronSans-Italic.woff2") format("woff2"), url("/fonts/Font-Bureau---HeronSans-Italic.otf") format("opentype");
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "Heronsans";
  src: url("/fonts/Font-Bureau---HeronSans-Medium.woff2") format("woff2"), url("/fonts/Font-Bureau---HeronSans-Medium.otf") format("opentype");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Heronsans";
  src: url("/fonts/Font-Bureau---HeronSans-Medium-Italic.woff2") format("woff2"), url("/fonts/Font-Bureau---HeronSans-Medium-Italic.otf") format("opentype");
  font-weight: 500; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "Heronsans";
  src: url("/fonts/Font-Bureau---HeronSans-SemiBold.woff2") format("woff2"), url("/fonts/Font-Bureau---HeronSans-SemiBold.otf") format("opentype");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Heronsans";
  src: url("/fonts/Font-Bureau---HeronSans-SemiBold-Italic.woff2") format("woff2"), url("/fonts/Font-Bureau---HeronSans-SemiBold-Italic.otf") format("opentype");
  font-weight: 600; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "Heronsans";
  src: url("/fonts/Font-Bureau---HeronSans-Bold.woff2") format("woff2"), url("/fonts/Font-Bureau---HeronSans-Bold.otf") format("opentype");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Heronsans";
  src: url("/fonts/Font-Bureau---HeronSans-Bold-Italic.woff2") format("woff2"), url("/fonts/Font-Bureau---HeronSans-Bold-Italic.otf") format("opentype");
  font-weight: 700; font-style: italic; font-display: swap;
}
/* No condensed cut is licensed/self-hosted — alias to the normal cut
   so it renders in brand type instead of the system sans. */
@font-face {
  font-family: "HeronSansCond";
  src: url("/fonts/Font-Bureau---HeronSans-Medium.woff2") format("woff2"), url("/fonts/Font-Bureau---HeronSans-Medium.otf") format("opentype");
  font-weight: 400 700; font-style: normal; font-display: swap;
}



:root {
  --zzn-blue: #002632;
  --zzn-green: #364604;
  --zzn-orange: #d24410;
  /* Soft blush the /about page lands on below the project wall, and the
     ink that reads on it. */
  --kr-blush: #fdf4f7;
  --kr-ink-on-blush: #0a0a0a;

  /* ---- Page container -------------------------------------------------
     One measure for the whole site. /about already framed its content this
     way (it was --kr-pad/--kr-measure, scoped to that page), and the
     homepage had grown five different systems instead: heads at 1200
     centred, a 90%-wide uncapped hero, a 1400 gallery, and two blocks —
     "Hunger auf Mehr" and "Aus dem Maschinenraum" — that were flush left
     and never centred at all, so at 2560px they sat at 80px and 48px while
     everything else was at ~620px. These promote that measure to a token
     so every section lands on the same left edge at every width. */
  --zzn-max: 1440px;
  --zzn-pad: clamp(1.25rem, 4vw, 3.5rem);
  --zzn-measure: calc(var(--zzn-max) - 2 * var(--zzn-pad));

  /* The homepage content column, as a viewport inset. The showreel is the
     element that defines it: `.wrapper` is 90% of a 5vw-padded section, i.e.
     81vw centred, so its edges land at 9.5% either side — and at >=1920 the
     showreel switches to `width: 80vw` (webflow.css:2928), moving them to 10%.
     Hero copy, the orange CTA band and the video all read off this token now.
     They used to each derive it separately and drifted apart above 1440: the
     hero sat at 411px where the band sat at 192px. */
  --zzn-col-inset: 9.5%;
}
@media screen and (min-width: 1920px) {
  :root { --zzn-col-inset: 10%; }
}

html {
  -webkit-text-size-adjust: 100%;
}
body {
  overflow-x: clip;
  overscroll-behavior-y: none;
}
/* Pinned dot-transition stage: pan-y hint so iOS never misreads a small
   horizontal drift as horizontal scroll intent competing with the pin. */
.zzn-stage { touch-action: pan-y; }

/* Animated brand gradient — moves background-position only, GPU cheap.
   Reused by nav cover, off-page menu, mobile overlay, cookie banner. */
@keyframes zzn-gradient-shift {
  0%   { background-position:   0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position:   0% 50%; }
}
@keyframes zzn-menu-gradient-drift {
  0%   { transform: translate3d(-3%, -2%, 0) scale(1.04); background-position: 0% 50%, 0% 50%, 0% 50%; }
  50%  { transform: translate3d(3%, 2%, 0) scale(1.08); background-position: 100% 50%, 60% 50%, 0% 50%; }
  100% { transform: translate3d(-3%, -2%, 0) scale(1.04); background-position: 0% 50%, 0% 50%, 0% 50%; }
}
@media (prefers-reduced-motion: reduce) {
  .zzn-grad-anim, .navigation-cover, .w-nav-overlay, .off-page-menu,
  .navigation-cover::after, .w-nav-overlay::before, .off-page-menu::before,
  #zzn-cookie-banner { animation: none !important; }
}

/* ---------- 1. Menu / navigation gradient ----------
   Webflow paints:
     - .navigation-cover  → solid orange bar behind navbar
     - .w-nav-overlay     → light-gray panel for mobile dropdown
     - .off-page-menu     → solid #fff slide-out for desktop "Menu" button
   Replace with the brand gradient on top of the deep-blue page base.
*/

.nav-bar,
.nav-bar.w-nav,
.w-nav {
  background-color: transparent !important;
}

/* Top bar behind logo / nav links — Webflow animates height on scroll;
   we only paint when it has size, so at scroll=0 nothing shows.
   Plain smoked glass rather than the brand gradient: the gradient fought
   whatever the page underneath happened to be, and read as a coloured
   band across the top of every page. */
.navigation-cover {
  overflow: hidden;
  isolation: isolate;
  background-color: transparent !important;
  background-image: linear-gradient(180deg, rgba(0, 0, 0, 0.58), rgba(0, 0, 0, 0.40)) !important;
  background-size: 100% 100%;
  background-repeat: no-repeat;
  /* saturate lifts the colour the blur would otherwise wash out — this is
     what separates glass from flat grey. */
  -webkit-backdrop-filter: blur(20px) saturate(180%);
          backdrop-filter: blur(20px) saturate(180%);
  /* Specular top edge + hairline foot, the two cues that read as a pane. */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    inset 0 -1px 0 rgba(255, 255, 255, 0.06),
    0 12px 36px -14px rgba(0, 0, 0, 0.5) !important;
}

/* ============================================================
   Nav-Leiste: Höhe steht, Glas läuft weiter herunter
   ------------------------------------------------------------
   Webflows IX2 schreibt die Höhe INLINE und animiert sie beim
   Scrollen: `.nav-bar` von der Ruhehöhe auf 65px, `.navigation-cover`
   von `height: 0%` auf `100%`. Das Logo ist 49,2px hoch und in der
   Leiste zentriert, die Luft darüber ist also (Höhe − 49,2)/2 —
   GEMESSEN 25,4px im Ruhezustand, 7,9px nach dem Scrollen. Das Logo
   wanderte 17,5px nach oben und klebte am Ende an beiden Kanten.

   Eine Deklaration im Stylesheet mit `!important` schlägt eine
   INLINE-Deklaration ohne `!important` — das ist der einzige Hebel
   gegen IX2, der ohne Eingriff in webflow.js funktioniert.

   Die Werte sind nicht erfunden, sie sind Webflows eigene Ruhehöhen
   je Breakpoint (Zeilen 341/3386/3787/4020 im Export). Sie hier zu
   wiederholen ist der Preis dafür, die Animation zu stoppen, ohne
   die Leiste neu zu bemaßen.
   ============================================================ */
.nav-bar,
.nav-bar.w-nav {
  height: 100px !important;
}
@media screen and (max-width: 991px) {
  .nav-bar, .nav-bar.w-nav { height: 75px !important; }
}
@media screen and (max-width: 767px) {
  .nav-bar, .nav-bar.w-nav { height: 70px !important; }
}
@media screen and (max-width: 479px) {
  .nav-bar, .nav-bar.w-nav { height: 65px !important; }
}

/* Das Glas darf die Leiste jetzt überragen.
   `.navigation-cover` ist `height: 100%` der Leiste und kann sie damit
   von sich aus nie überschreiten. Statt an der Höhe zu drehen — die ist
   der Träger der Einblend-Animation von 0% auf 100% — wird die gemalte
   Fläche gestreckt: der Kasten bleibt 100px, das Bild davon 145px.

   Die Maske ist der Punkt. Das Glas ist zu drei Vierteln `backdrop-filter`,
   und ein Weichzeichner mit harter Unterkante ist genau der Strich quer
   über die Seite, den man nicht will. `mask-image` blendet die Wirkung
   des Filters mit aus, nicht nur die Farbfläche — die Scheibe löst sich
   also nach unten auf, statt abzuschneiden.

   Deshalb fällt der äußere Schlagschatten weg: eine Maske beschneidet auf
   die Border-Box, alles außerhalb wird ohnehin abgeschnitten. Die
   auslaufende Kante ist sein Ersatz und der bessere Übergang. Die untere
   Haarlinie geht aus demselben Grund. Der Glanz oben bleibt. */
.navigation-cover {
  --zzn-nav-glass: 1.45;
  pointer-events: none;
  transform-origin: top center;
  transform: scaleY(var(--zzn-nav-glass));
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 56%, rgba(0, 0, 0, 0) 100%);
          mask-image: linear-gradient(180deg, #000 0%, #000 56%, rgba(0, 0, 0, 0) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16) !important;
}
@media (max-width: 767px) {
  /* Auf dem Telefon ist die Leiste 65–70px hoch; derselbe Faktor wäre
     dort nur noch ein Saum von 30px. Etwas mehr, damit der Übergang
     genauso weich liest. */
  .navigation-cover { --zzn-nav-glass: 1.6; }
}

/* Fix header logo aspect ratio — explicit width/height attributes on the
   brand image were distorting it when CSS constrained only one axis. */
.brand img,
.w-nav-brand img {
  width: 200px;
  height: auto;
  max-width: 100%;
  object-fit: contain;
  display: block;
}
@media (max-width: 767px) {
  .brand img,
  .w-nav-brand img {
    width: 150px;
  }
}

/* Light, quick sheen. Only transform animates, so it stays off the paint
   path; the long hold keeps it from turning into a nightclub. */
.navigation-cover::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    100deg,
    transparent 42%,
    rgba(255, 255, 255, 0.10) 50%,
    transparent 58%
  );
  transform: translate3d(-100%, 0, 0);
  animation: zzn-nav-sheen 9s ease-in-out infinite;
  will-change: transform;
}
@keyframes zzn-nav-sheen {
  0%, 80% { transform: translate3d(-100%, 0, 0); }
  100%    { transform: translate3d(100%, 0, 0); }
}

.w-nav-overlay::before,
.off-page-menu::before {
  content: "";
  position: absolute;
  inset: -18% -12% -18% 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(70% 80% at 82% 12%, rgba(210, 68, 16, 0.76) 0%, rgba(210, 68, 16, 0.34) 38%, transparent 68%),
    linear-gradient(105deg, transparent 0%, rgba(54, 70, 4, 0.84) 24%, rgba(0, 38, 50, 0.64) 48%, rgba(210, 68, 16, 0.58) 72%, rgba(54, 70, 4, 0.62) 100%),
    linear-gradient(90deg, rgba(0, 38, 50, 1) 0%, rgba(0, 38, 50, 0.95) 24%, transparent 66%);
  background-size: 130% 130%, 135% 135%, 100% 100%;
  background-position: 0% 50%, 0% 50%, 0% 50%;
  opacity: 0.98;
  animation: zzn-menu-gradient-drift 7s ease-in-out infinite;
  will-change: transform, background-position;
}

/* Nav dropdown overlay — styled to match the cookie banner */
[data-nav-menu-open],
.w-nav-overlay [data-nav-menu-open] {
  background: transparent !important;
}
.w-nav-overlay {
  isolation: isolate;
  /* Cancel the shared ::before drift gradient inherited from the menu panels */
  background: transparent !important;
  background-image: none !important;
  animation: none !important;
}
.w-nav-overlay::before {
  display: none !important;
}
.w-nav-overlay > * {
  position: relative;
  z-index: 1;
}

/* Full-screen overlay with centered menu items — same treatment at every
   width. It used to be a compact box anchored under the menu button on
   desktop/tablet-landscape, but that let footer/page content bleed in
   around its edges and read as unfinished; the immersive full page is
   the better and simpler choice everywhere. */
/* Plain black, like the studio's own ground: no tint, no glow. The page
   behind is fully covered, so a blur would only cost a frame. */
.w-nav-overlay {
  position: fixed !important;
  inset: 0 !important;
  width: 100vw !important;
  height: 100dvh !important;
  min-height: 100vh;
  background: #000 !important;
  /* Scrolls rather than clips if the list ever outgrows a very short screen
     (a phone on its side). */
  overflow-x: hidden !important;
  overflow-y: auto !important;
}
/* The logo stays on the bar while the menu is open, like the button beside
   it (whose wrapper sits at 101). Without a z-index it went under the
   overlay, which comes after it in the DOM. */
.w-nav .w-nav-brand {
  z-index: 101;
}
.w-nav-overlay [data-nav-menu-open] {
  background: transparent !important;
  position: relative !important;
  float: none !important;
  left: auto !important;
  right: auto !important;
  width: 100% !important;
  max-width: none !important;
  min-height: 100dvh;
  box-sizing: border-box;
  display: flex !important;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(8px, 1.6vh, 16px);
  text-align: center;
  padding: clamp(84px, 14vh, 140px) 24px 48px;
  margin: 0 !important;
}
/* The links in the headline face — Heron, bold, caps, tight — at a size that
   carries a full screen, instead of 22px body type. One rhythm: line-height
   1 plus the same padding on each, so the gaps between them are equal. */
.w-nav-overlay .nav-link,
.w-nav-overlay .w-nav-link {
  position: relative;
  z-index: 1;
  display: flex !important;
  align-items: center;
  justify-content: center;
  width: auto !important;
  box-sizing: border-box;
  margin-left: auto !important;
  margin-right: auto !important;
  color: #fff !important;
  font-family: 'Heronsans', 'Font Bureau Heronsans', system-ui, sans-serif;
  font-size: clamp(2.1rem, 3.6vw + 0.5rem, 3.4rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  text-align: center !important;
  padding: 0.14em 0.35em;
  /* Webflow rules each link off under 992px; at text width the rules came
     out five different lengths. The type carries the list on its own. */
  border-bottom: 0;
  transition: color 0.2s ease;
}
/* Hover: the others step back, rather than a box lighting up behind one. */
.w-nav-overlay [data-nav-menu-open]:has(.w-nav-link:hover) .w-nav-link:not(:hover) {
  color: rgba(255, 255, 255, 0.4) !important;
}
.w-nav-overlay .w-nav-link:focus-visible {
  outline: 1px solid rgba(255, 255, 255, 0.6);
  outline-offset: 4px;
}

/* Address + Impressum/Datenschutz: a clear step below the links, opened by a
   short hairline, in the body face — a courtesy line, not a second menu. */
.w-nav-overlay .zzn-nav-legal {
  position: relative;
  z-index: 1;
  margin-top: clamp(28px, 6vh, 56px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
}
.w-nav-overlay .zzn-nav-legal::before {
  content: "";
  width: 32px;
  height: 1px;
  margin-bottom: 4px;
  background: rgba(255, 255, 255, 0.28);
}
.w-nav-overlay .zzn-nav-legal address {
  font-family: 'Eudoxussans', system-ui, sans-serif;
  font-style: normal;
  font-weight: 400;
  font-size: 13px;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.55);
}
.w-nav-overlay .zzn-nav-legal__links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px 40px;
}
.w-nav-overlay .zzn-nav-legal__links a {
  font-family: 'Eudoxussans', system-ui, sans-serif;
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.55);
  text-decoration: none;
  transition: color 0.18s ease;
}
.w-nav-overlay .zzn-nav-legal__links a:hover {
  color: #fff;
}

/* Phone: these are thumb targets, so more air between them — each link
   ~48px tall with ~20px between, instead of 42px stacked almost flush. */
@media (max-width: 767px) {
  .w-nav-overlay [data-nav-menu-open] { gap: clamp(10px, 2.4vh, 22px); }
  .w-nav-overlay .nav-link,
  .w-nav-overlay .w-nav-link { padding: 0.2em 0.35em; }
}
/* Narrow: a size down so TECHNIK MIETEN keeps its margins. Short: also
   tighter, so the list and the address still fit without scrolling. */
@media (max-width: 380px) {
  .w-nav-overlay .nav-link,
  .w-nav-overlay .w-nav-link { font-size: clamp(1.6rem, 6vh, 2rem); }
}
@media (max-height: 640px) {
  .w-nav-overlay [data-nav-menu-open] { gap: 10px; padding-top: clamp(64px, 12vh, 96px); }
  .w-nav-overlay .nav-link,
  .w-nav-overlay .w-nav-link { font-size: clamp(1.6rem, 6vh, 2rem); padding: 0.16em 0.35em; }
  .w-nav-overlay .zzn-nav-legal { margin-top: 20px; gap: 10px; }
}
/* A phone on its side: the whole list has to fit in ~390px. */
@media (max-height: 460px) {
  .w-nav-overlay [data-nav-menu-open] { gap: 2px; padding-bottom: 24px; }
  .w-nav-overlay .nav-link,
  .w-nav-overlay .w-nav-link { padding: 0.1em 0.35em; }
  .w-nav-overlay .zzn-nav-legal { margin-top: 14px; }
}




/* Desktop slide-out "Menu" panel.
   Webflow toggles this via inline `display: flex` on click; the !important
   keeps our gradient on top of the inline white background that ships in
   the export. */
.off-page-menu {
  overflow: hidden;
  isolation: isolate;
  background-color: var(--zzn-blue) !important;
  background-image: linear-gradient(90deg, rgba(0, 38, 50, 0.99), rgba(0, 38, 50, 0.96)) !important;
  background-size: 100% 100%;
  background-repeat: no-repeat;
  color: #fff !important;
  backdrop-filter: blur(22px) saturate(150%);
  box-shadow: -40px 0 80px -20px rgba(0, 0, 0, 0.6);
}
.off-page-menu > * {
  position: relative;
  z-index: 1;
}
.off-page-menu a,
.off-page-menu .nav-link,
.off-page-menu .footer-nav-link,
.off-page-menu .quick-list-item,
.off-page-menu .quick-list-item * {
  color: #fff !important;
}
.off-page-menu .quick-list-item {
  border-bottom-color: rgba(255, 255, 255, 0.14) !important;
  transition: background 0.2s ease, padding-left 0.25s ease;
}
.off-page-menu .quick-list-item:hover {
  background: rgba(255, 255, 255, 0.06);
  padding-left: 28px;
}

/* Hamburger button when open. Also cancels a Webflow mobile-breakpoint rule
   that clipped the label to a blurred rainbow background image — harmless
   on the short word "Menu", but garish once it reads "SCHLIESSEN". */
.w-nav-button.w--open,
.menu-button.w--open {
  /* No box of its own: over the black menu a tinted panel behind the label
     read as a patch of a different colour. */
  background-color: transparent !important;
  background-image: none !important;
  color: #fff !important;
  -webkit-text-fill-color: #fff !important;
  -webkit-background-clip: initial !important;
  background-clip: initial !important;
}

/* Menu button: label swaps to the closing action's own name and a × glyph
   appears once the button carries Webflow's `.w--open` state — otherwise
   "MENU" just sits there with no visual cue that it now closes the page. */
/* Webflow's own `.w-nav[data-collapse='all'] .w-nav-button` rule outranks
   the flex row this needs (attribute selector beats a single class), so the
   label + × glyph fall onto separate lines without !important here. */
.menu-button {
  display: flex !important;
  align-items: center !important;
  gap: 8px;
}
/* Das Wort steht als eigenes Element im DOM (siehe rewritePaths in
   webflow.server.ts), nicht mehr als `content:` hier. Als CSS-Inhalt kam die
   Uebersetzung nicht daran: translateHtml ersetzt Textknoten, und ein
   generierter Inhalt ist keiner — auf /en stand deshalb "SCHLIESSEN". */
.zzn-menu-close {
  display: none;
  white-space: nowrap;
}
.menu-button.w--open .menu-button-text > div {
  display: none;
}
/* (0,4,0) gegen (0,3,1) der Regel darueber — .zzn-menu-close ist selbst ein
   div und wuerde sonst mitversteckt. */
.menu-button.w--open .menu-button-text > .zzn-menu-close {
  display: block;
}
.zzn-menu-x {
  position: relative;
  width: 13px;
  height: 13px;
  flex: none;
  opacity: 0;
  transform: scale(0.6) rotate(-45deg);
  transition: opacity 0.2s ease, transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.zzn-menu-x span {
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 2px;
  background: currentColor;
  border-radius: 1px;
}
.zzn-menu-x span:first-child { transform: translateY(-50%) rotate(45deg); }
.zzn-menu-x span:last-child { transform: translateY(-50%) rotate(-45deg); }
.menu-button.w--open .zzn-menu-x {
  opacity: 1;
  transform: scale(1) rotate(0deg);
}
@media (prefers-reduced-motion: reduce) {
  .zzn-menu-x { transition: none; }
}

/* ---------- 2. Cookie banner ----------
   Eckig, flächig, Haarlinien — dieselbe Sprache wie Projekt-Check-Dialog,
   Rental-Modal und Mittendrin. Keine Glasunschärfe, kein radialer Schimmer,
   kein animierter Verlaufsrahmen.

   Palette: Schwarz, Weiß, Orange. Zwei Fassungen, gesteuert über
   [data-ground] — das Attribut beschreibt den GRUND unter dem Kasten, nicht
   den Kasten selbst:

     data-ground="dark"   dunkle Seite  → weißer Kasten, schwarze Schrift
     data-ground="light"  helle Seite   → schwarzer Kasten, weiße Schrift

   Gesetzt wird es in cookie-banner.js, gemessen an dem, was an der Stelle des
   Kastens tatsächlich liegt. Vorher war der Kasten fest im Navy des Footers
   gehalten und stand auf der Startseite unten praktisch unsichtbar auf genau
   diesem Navy.

   Zwei Orangetöne, weil einer allein nicht auf beiden Gründen trägt:
   #ec6507 auf Schwarz sind 6,4:1, auf Weiß nur 3,3:1 — zu wenig für Fließtext
   dieser Größe. #d24410 liegt bei 4,6:1 gegen Weiß UND gegen Schwarz, deckt
   also die helle Fassung ab. Beide sind Hausorange.

   Jeder Haken, an dem cookie-banner.js hängt, bleibt unangetastet:
   #zzn-cookie-banner, [data-visible], [data-mode="prefs"], .zzn-cb-inner,
   .zzn-cb-intro, .zzn-cb-prefs, .zzn-cb-row, .zzn-cb-actions,
   .zzn-cb-choice, .zzn-cb-more, #zzn-cookie-reopen und die zwei Checkbox-IDs.
   ---------- */
#zzn-cookie-banner,
#zzn-cookie-reopen {
  /* Rückfall ist die weiße Fassung: die überwiegende Mehrheit der Seiten
     startet auf Navy oder Schwarz. Steht das Attribut noch nicht, ist der
     Kasten also schon richtig. */
  --cb-bg: #ffffff;
  --cb-ink: #000000;
  --cb-ink-soft: rgba(0, 0, 0, 0.68);
  --cb-ink-faint: rgba(0, 0, 0, 0.5);
  --cb-line: rgba(0, 0, 0, 0.16);
  --cb-line-soft: rgba(0, 0, 0, 0.1);
  --cb-btn-line: rgba(0, 0, 0, 0.34);
  --cb-btn-line-hover: rgba(0, 0, 0, 0.7);
  --cb-hover: rgba(0, 0, 0, 0.06);
  --cb-fill: #000000;
  --cb-fill-ink: #ffffff;
  --cb-accent: #d24410;
  --cb-shadow: 0 24px 60px -24px rgba(0, 0, 0, 0.5);
}
#zzn-cookie-banner[data-ground="light"],
#zzn-cookie-reopen[data-ground="light"] {
  --cb-bg: #000000;
  --cb-ink: #ffffff;
  --cb-ink-soft: rgba(255, 255, 255, 0.72);
  --cb-ink-faint: rgba(255, 255, 255, 0.55);
  --cb-line: rgba(255, 255, 255, 0.24);
  --cb-line-soft: rgba(255, 255, 255, 0.14);
  --cb-btn-line: rgba(255, 255, 255, 0.38);
  --cb-btn-line-hover: rgba(255, 255, 255, 0.72);
  --cb-hover: rgba(255, 255, 255, 0.1);
  --cb-fill: #ffffff;
  --cb-fill-ink: #000000;
  --cb-accent: #ec6507;
  --cb-shadow: 0 24px 60px -24px rgba(0, 0, 0, 0.75);
}

#zzn-cookie-banner {
  position: fixed;
  /* Linke Kante auf der Kante der Wortmarke im Header, damit beide eine Linie
     bilden. Der Wert kommt aus cookie-banner.js: die Navigation sitzt auf 5vw,
     unter 480px auf 12px, und die Marke selbst steht in manchen Breiten noch
     10px in ihrem Anker — das aus dem Stylesheet nachzubauen hieße, drei
     Webflow-Breakpoints zu spiegeln und bei jeder Änderung dort auseinander zu
     laufen. Gemessen wird deshalb das Bild selbst. 20px ist der Rückfall,
     falls die Marke fehlt. */
  left: var(--zzn-cb-left, 20px);
  bottom: 20px;
  z-index: 99999;
  /* Rechter Abstand gleich dem linken, damit der Kasten mittig im Blattrand
     sitzt statt einseitig zu kleben. */
  width: min(440px, calc(100vw - 2 * var(--zzn-cb-left, 20px)));
  color: var(--cb-ink);
  border-radius: 0;
  font-family: "Eudoxussans", Inter, system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  overflow: hidden;
  opacity: 0;
  transform: translateY(12px);
  will-change: transform, opacity;
  /* hide() entfernt das Element nach 320 ms. Die alte Kurve lief 520 ms und
     wurde dabei mitten in der Bewegung abgeschnitten; 260 ms passen hinein.
     Farbe und Rahmen laufen länger: das ist der Wechsel der Fassung, und der
     soll wie ein Überblenden aussehen, nicht wie ein Umschalten. */
  transition:
    transform 0.26s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.22s cubic-bezier(0.4, 0, 0.2, 1),
    background-color 0.34s ease,
    border-color 0.34s ease,
    color 0.34s ease,
    box-shadow 0.34s ease;
  background: var(--cb-bg);
  /* Der Banner schwebt auch über weißen Sektionen (Kreation auf /about),
     deshalb Rahmen UND ein flacher Schlagschatten statt Leuchten. */
  border: 1px solid var(--cb-line);
  box-shadow: var(--cb-shadow);
}
#zzn-cookie-banner[data-visible="true"] {
  transform: translateY(0);
  opacity: 1;
}
#zzn-cookie-banner .zzn-cb-inner {
  position: relative;
  z-index: 1;
  padding: 22px 24px;
}

#zzn-cookie-banner h2 {
  font-family: "Font Bureau Heronsans", "Eudoxussans", Inter, sans-serif;
  font-size: 15px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin: 0 0 10px;
  color: var(--cb-ink);
  transition: color 0.34s ease;
  display: flex;
  align-items: center;
  gap: 10px;
}
/* Quadrat, nicht Punkt — der Akzent trägt dieselbe Ecke wie alles andere. */
#zzn-cookie-banner h2::before {
  content: "";
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 0;
  background: var(--cb-accent);
  transition: background-color 0.34s ease;
}
#zzn-cookie-banner p {
  margin: 0 0 16px;
  color: var(--cb-ink-soft);
  transition: color 0.34s ease;
  font-size: 13px;
  line-height: 1.55;
}
#zzn-cookie-banner a {
  color: var(--cb-accent);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  transition: color 0.34s ease, border-color 0.15s ease;
}
#zzn-cookie-banner a:hover {
  color: var(--cb-ink);
  border-bottom-color: var(--cb-ink);
}
#zzn-cookie-banner .zzn-cb-actions {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 8px;
}
#zzn-cookie-banner button {
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0 16px;
  height: 44px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  border-radius: 0;
  border: 1px solid var(--cb-btn-line);
  background: transparent;
  color: var(--cb-ink);
  cursor: pointer;
  transition:
    background 0.18s ease,
    border-color 0.34s ease,
    color 0.34s ease,
    transform 0.08s ease;
}
#zzn-cookie-banner button:hover {
  background: var(--cb-hover);
  border-color: var(--cb-btn-line-hover);
  color: var(--cb-ink);
}
#zzn-cookie-banner button:active { transform: translateY(1px); }
#zzn-cookie-banner button:focus-visible {
  outline: 2px solid var(--cb-accent);
  outline-offset: 2px;
}
/* „Nur notwendige" und „Alle akzeptieren" sind gleichwertig: dieselbe
   gefüllte Fläche, dieselbe Breite, nebeneinander — Ablehnen darf nicht
   schwerer zu finden oder zu treffen sein als Zustimmen (DSK, § 25 TDDDG).
   Vorher trug nur „Alle akzeptieren" die Fläche. Invertiert, nicht orange
   gefüllt; Orange bleibt Akzent. */
#zzn-cookie-banner button.zzn-cb-choice {
  background: var(--cb-fill);
  border-color: var(--cb-fill);
  color: var(--cb-fill-ink);
  font-weight: 600;
  flex: 1 1 0;
  min-width: 0;
}
#zzn-cookie-banner button.zzn-cb-choice:hover {
  background: var(--cb-fill);
  border-color: var(--cb-fill);
  color: var(--cb-fill-ink);
  opacity: 0.86;
}
/* Der Nebenweg: umrandet, über die ganze Breite darunter. */
#zzn-cookie-banner button.zzn-cb-more { flex: 1 1 100%; }


/* Preferences panel */
#zzn-cookie-banner .zzn-cb-prefs {
  margin: 0;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  border-top: 1px solid transparent;
  padding-top: 0;
  transition:
    max-height 0.36s cubic-bezier(0.16, 1, 0.3, 1),
    opacity 0.24s ease,
    margin 0.32s ease,
    padding 0.32s ease,
    border-color 0.32s ease;
}
#zzn-cookie-banner[data-mode="prefs"] .zzn-cb-prefs {
  max-height: 520px;
  opacity: 1;
  margin: 4px 0 16px;
  padding-top: 14px;
  border-top-color: var(--cb-line);
}
#zzn-cookie-banner .zzn-cb-intro {
  transition: opacity 0.22s ease, max-height 0.32s ease;
  max-height: 320px;
  opacity: 1;
  overflow: hidden;
}
#zzn-cookie-banner[data-mode="prefs"] .zzn-cb-intro {
  opacity: 0;
  max-height: 0;
  pointer-events: none;
}
#zzn-cookie-banner .zzn-cb-row {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 12px 0;
}
#zzn-cookie-banner .zzn-cb-row + .zzn-cb-row {
  border-top: 1px solid var(--cb-line-soft);
  transition: border-color 0.34s ease;
}
#zzn-cookie-banner .zzn-cb-row label {
  flex: 1;
  font-size: 13px;
  font-weight: 500;
  color: var(--cb-ink);
  transition: color 0.34s ease;
  cursor: pointer;
}
#zzn-cookie-banner .zzn-cb-row label small {
  display: block;
  color: var(--cb-ink-faint);
  font-size: 11.5px;
  font-weight: 400;
  margin-top: 3px;
  letter-spacing: 0;
  text-transform: none;
}

/* Kästchen statt Kippschalter. Bleibt ein echtes <input type="checkbox">,
   also lesen .checked, das label[for] und die Tastatur unverändert. */
#zzn-cookie-banner .zzn-cb-row input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  width: 20px;
  height: 20px;
  margin: 1px 0 0;
  border-radius: 0;
  background: transparent;
  border: 1px solid var(--cb-btn-line);
  cursor: pointer;
  transition: background 0.16s ease, border-color 0.34s ease;
  flex-shrink: 0;
}
#zzn-cookie-banner .zzn-cb-row input[type="checkbox"]::after {
  content: "";
  position: absolute;
  left: 6px;
  top: 2px;
  width: 5px;
  height: 10px;
  /* Weiß in beiden Fassungen: der Haken steht immer auf dem Orange, nie auf
     dem Kastengrund. Gegen #d24410 sind das 4,6:1, gegen #ec6507 3,3:1 — für
     ein grafisches Element (1.4.11 verlangt 3:1) in beiden Fällen genug. */
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) scale(0);
  transform-origin: center;
  transition: transform 0.16s cubic-bezier(0.4, 0, 0.2, 1);
}
#zzn-cookie-banner .zzn-cb-row input[type="checkbox"]:checked {
  background: var(--cb-accent);
  border-color: var(--cb-accent);
}
#zzn-cookie-banner .zzn-cb-row input[type="checkbox"]:checked::after {
  transform: rotate(45deg) scale(1);
}
#zzn-cookie-banner .zzn-cb-row input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--cb-accent);
  outline-offset: 2px;
}
#zzn-cookie-banner .zzn-cb-row input[type="checkbox"]:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Reopen — kein Pill mehr, gleiche Kante und dieselben zwei Fassungen wie
   der Banner darüber. */
#zzn-cookie-reopen {
  position: fixed;
  left: var(--zzn-cb-left, 20px);
  bottom: 20px;
  z-index: 9998;
  background: var(--cb-bg);
  color: var(--cb-ink);
  border: 1px solid var(--cb-line);
  border-radius: 0;
  padding: 9px 14px 9px 30px;
  font-family: "Eudoxussans", Inter, sans-serif;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  transition:
    background-color 0.34s ease,
    border-color 0.34s ease,
    color 0.34s ease;
  display: none;
}
#zzn-cookie-reopen[data-visible="true"] { display: inline-flex; align-items: center; }
/* Weggeräumt, nicht entfernt (cookie-banner.js, nur /projekt-check auf dem
   Telefon): die Box bleibt, damit die Grund-Messung weiter greift. */
#zzn-cookie-reopen[data-tucked="true"] {
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
}
#zzn-cookie-reopen::before {
  content: "";
  position: absolute;
  left: 12px;
  top: 50%;
  margin-top: -4px;
  width: 8px;
  height: 8px;
  border-radius: 0;
  background: var(--cb-accent);
  transition: background-color 0.34s ease;
}
#zzn-cookie-reopen:hover {
  background: var(--cb-fill);
  border-color: var(--cb-fill);
  color: var(--cb-fill-ink);
}
#zzn-cookie-reopen:focus-visible {
  outline: 2px solid var(--cb-accent);
  outline-offset: 2px;
}

@media (max-width: 480px) {
  #zzn-cookie-banner {
    /* Auch hier auf der Kante der Wortmarke, nur der Rückfall ist enger. */
    left: var(--zzn-cb-left, 10px);
    right: var(--zzn-cb-left, 10px);
    bottom: 10px;
    width: auto;
  }
  #zzn-cookie-banner .zzn-cb-inner { padding: 18px 18px; }
  #zzn-cookie-banner .zzn-cb-actions button { flex: 1 1 calc(50% - 4px); }
  #zzn-cookie-banner button.zzn-cb-more { flex: 1 1 100%; }
  #zzn-cookie-reopen { left: var(--zzn-cb-left, 10px); bottom: 10px; }
}

/* Studio styles moved to public/css/studio.css (linked from admin/auth route heads). */


/* ============================================================
   ZZN scroll-reveal + menu polish
   ============================================================ */

/* 1. Trigger Webflow reveals earlier — finish during approach. */
@supports (animation-timeline: view()) {
  [data-w-id]:not(.social-items [data-w-id]):not(.social-block):not(.w-nav-overlay):not(.off-page-menu):not(.navigation-cover) {
    animation: zzn-early-reveal linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 25%;
  }
  @keyframes zzn-early-reveal {
    from { opacity: 0; transform: translateY(24px); }
    to   { opacity: 1; transform: translateY(0); }
  }
}
@supports not (animation-timeline: view()) {
  [data-w-id][style*="opacity: 0"]:not(.social-items [data-w-id]):not(.social-block) {
    opacity: 1 !important;
  }
}


/* Social grid — enforce a true 2x2 square layout regardless of content. */
.box:has(> .social-items) {
  aspect-ratio: 1 / 1 !important;
  width: 100% !important;
  height: auto !important;
  min-height: 0 !important;
}
.social-items {
  width: 100% !important;
  height: 100% !important;
  aspect-ratio: 1 / 1 !important;
  gap: 0 !important;
  row-gap: 0 !important;
  column-gap: 0 !important;
  grid-template-rows: repeat(2, minmax(0, 1fr)) !important;
  grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  grid-auto-rows: minmax(0, 1fr) !important;
  grid-auto-columns: minmax(0, 1fr) !important;
}
.social-items .social-block {
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  box-sizing: border-box;
  opacity: 0;
  transform: translateY(14px);
  animation: zzn-social-in 0.42s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
}

/* /team gives <body> a drifting radial glow via ::before. The Webflow
   "shade" wrapper sits over it filled with flat, opaque site blue, which
   punched a hard-edged rectangle out of that glow around the "Lernen wir
   uns kennen" block. Only /team has both layers — every other page has the
   same opaque wrapper but no glow behind it, so it reads as blue on blue. */
body[data-page="team"] .wrapper.shade-wrapper { background-color: transparent; }

/* Icons only — the handles read as noise at tile size and repeat what the
   mark already says. Hidden rather than stripped from the markup so the
   link text stays available to screen readers via the tile's own label. */
.social-items .social-text { display: none !important; }
.social-items .social-block {
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
}
.social-items .social-icon { margin-bottom: 0 !important; }

.social-items .social-block:nth-child(1) { animation-delay: 0.00s; }
.social-items .social-block:nth-child(2) { animation-delay: 0.06s; }
.social-items .social-block:nth-child(3) { animation-delay: 0.12s; }
.social-items .social-block:nth-child(4) { animation-delay: 0.18s; }
@keyframes zzn-social-in {
  to { opacity: 1; transform: translateY(0); }
}
.social-items .social-block:hover {
  transform: translateY(-2px) scale(1.015);
}
.overlay-social {
  display: block !important;
  opacity: 0;
  transition: opacity 0.18s ease;
}
.social-block:hover .overlay-social { opacity: 1; }

/* 3. Menu — smoother open. (Webflow toggles inline `display`, which we must
   not override or the nav script's open/close state breaks.) */
.w-nav-overlay[style*="display: block"] .nav-link,
.w-nav-overlay[style*="display:block"] .nav-link,
.w-nav-overlay[style*="display: block"] .w-nav-link,
.w-nav-overlay[style*="display:block"] .w-nav-link {
  animation: zzn-navlink-in 0.36s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes zzn-navlink-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.w-nav-overlay .nav-link:nth-child(1),
.w-nav-overlay .w-nav-link:nth-child(1) { animation-delay: 0.05s; }
.w-nav-overlay .nav-link:nth-child(2),
.w-nav-overlay .w-nav-link:nth-child(2) { animation-delay: 0.10s; }
.w-nav-overlay .nav-link:nth-child(3),
.w-nav-overlay .w-nav-link:nth-child(3) { animation-delay: 0.15s; }
.w-nav-overlay .nav-link:nth-child(4),
.w-nav-overlay .w-nav-link:nth-child(4) { animation-delay: 0.20s; }
.w-nav-overlay .nav-link:nth-child(5),
.w-nav-overlay .w-nav-link:nth-child(5) { animation-delay: 0.25s; }
.w-nav-overlay .nav-link:nth-child(6),
.w-nav-overlay .w-nav-link:nth-child(6) { animation-delay: 0.30s; }

.off-page-menu .quick-list-item {
  animation: zzn-navlink-in 0.42s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.off-page-menu .quick-list-item:nth-child(1) { animation-delay: 0.06s; }
.off-page-menu .quick-list-item:nth-child(2) { animation-delay: 0.12s; }
.off-page-menu .quick-list-item:nth-child(3) { animation-delay: 0.18s; }
.off-page-menu .quick-list-item:nth-child(4) { animation-delay: 0.24s; }
.off-page-menu .quick-list-item:nth-child(5) { animation-delay: 0.30s; }

/* Hamburger button — smooth toggle. */
.w-nav-button,
.w-nav-button * {
  transition: transform 0.24s cubic-bezier(0.16, 1, 0.3, 1),
              background-color 0.2s ease;
}

/* Smooth close (driven by /js/menu-close.js adding `.zzn-closing`). */
.w-nav-overlay.zzn-closing {
  opacity: 0 !important;
  transform: translateY(-6px) scale(0.98) !important;
  transition: opacity 0.26s ease, transform 0.28s cubic-bezier(0.4, 0, 1, 1) !important;
  pointer-events: none !important;
}
.off-page-menu.zzn-closing {
  transform: translateX(100%) !important;
  opacity: 0 !important;
  transition: transform 0.38s cubic-bezier(0.4, 0, 1, 1), opacity 0.26s ease !important;
  pointer-events: none !important;
}



/* 4. Cookie banner button micro-interaction. */
#zzn-cookie-banner button {
  transition:
    background 0.14s ease,
    border-color 0.14s ease,
    color 0.14s ease,
    transform 0.08s ease;
}
#zzn-cookie-banner button:active { transform: scale(0.97); }

/* 5. Reduced motion — opt out of all of the above. */
@media (prefers-reduced-motion: reduce) {
  [data-w-id],
  .social-items .social-block,
  .w-nav-overlay,
  .w-nav-overlay .nav-link,
  .w-nav-overlay .w-nav-link,
  .off-page-menu,
  .off-page-menu .quick-list-item,
  .overlay-social,
  #zzn-cookie-banner {
    animation: none !important;
    transition: opacity 0.15s ease !important;
    transform: none !important;
  }
  [data-w-id] { opacity: 1 !important; }
}




/* ============================================================
   Project detail page — custom layout (replaces Webflow's
   two adapt-clients sections). Matches menu / cookie aesthetic:
   deep-blue base, brand-gradient accents, generous negative space.
   ============================================================ */

.zzn-projekt {
  color: #f4f6f8;
  background: var(--zzn-blue);
  padding-bottom: 6rem;
}

/* --- Hero --- */
.zzn-projekt__hero {
  position: relative;
  width: 100%;
  min-height: 62vh;
  overflow: hidden;
  isolation: isolate;
  display: flex;
  align-items: flex-end;
}
.zzn-projekt__hero-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  z-index: 0;
  transform: scale(1.02);
}
.zzn-projekt__hero-veil {
  position: absolute; inset: 0;
  z-index: 1;
  background:
    linear-gradient(180deg, rgba(0,38,50,0.35) 0%, rgba(0,38,50,0.55) 55%, rgba(0,38,50,0.98) 100%),
    linear-gradient(115deg, rgba(54,70,4,0.35), rgba(210,68,16,0.25) 60%, transparent 90%);
  background-size: 100% 100%, 220% 220%;
  animation: zzn-gradient-shift 22s ease-in-out infinite;
  pointer-events: none;
}
.zzn-projekt__hero-inner {
  position: relative;
  z-index: 2;
  max-width: 1200px;
  width: 100%;
  margin: 0 auto;
  padding: 6rem 2rem 4rem;
}
.zzn-projekt__kicker {
  display: inline-block;
  font-family: 'Eudoxussans', 'Inter', system-ui, sans-serif;
  font-size: 0.78rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: #f0e6d0;
  padding: 0.5rem 0.9rem;
  border-radius: 999px;
  background: linear-gradient(90deg, rgba(54,70,4,0.55), rgba(210,68,16,0.55));
  border: 1px solid rgba(255,255,255,0.14);
  margin-bottom: 1.5rem;
}
.zzn-projekt__title {
  font-family: 'Heronsans', 'Inter', system-ui, sans-serif;
  font-weight: 700;
  font-size: clamp(2.4rem, 6vw, 5rem);
  line-height: 1.02;
  letter-spacing: -0.01em;
  margin: 0;
  color: #fff;
  text-wrap: balance;
}

/* --- Body: 2-col meta + prose --- */
.zzn-projekt__body {
  max-width: 1200px;
  margin: 0 auto;
  padding: 4rem 2rem 3rem;
  display: grid;
  grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  gap: 4rem;
}
@media (max-width: 800px) {
  .zzn-projekt__body { grid-template-columns: 1fr; gap: 2rem; padding-top: 2.5rem; }
}
.zzn-projekt__meta {
  position: sticky;
  top: 6rem;
  align-self: start;
  padding: 1.75rem;
  border-radius: 20px;
  border: 1px solid rgba(255,255,255,0.10);
  transition: background 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}
/* Whole field takes the colour — no hard-cut logo tile */
.zzn-projekt__meta--light {
  background: #fff;
  border-color: rgba(0,0,0,0.08);
  box-shadow: 0 18px 40px -24px rgba(0,0,0,0.55);
}
.zzn-projekt__meta--dark {
  background: var(--zzn-blue, #0e2a34);
  border-color: rgba(255,255,255,0.12);
  box-shadow: 0 18px 40px -24px rgba(0,0,0,0.65);
}
@media (max-width: 800px) { .zzn-projekt__meta { position: static; } }
.zzn-projekt__meta-label {
  font-family: 'Eudoxussans', sans-serif;
  font-size: 0.72rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  margin-bottom: 1.1rem;
}
.zzn-projekt__meta--light .zzn-projekt__meta-label { color: rgba(14,42,52,0.55); }
.zzn-projekt__meta--dark .zzn-projekt__meta-label { color: rgba(255,255,255,0.55); }
.zzn-projekt__logo {
  display: block;
  width: 100%;
  max-height: 140px;
  object-fit: contain;
  object-position: left center;
  background: transparent;
  padding: 0;
  margin: 0;
}
/* Square-ish logo images get more vertical room so they don't look tiny,
   and stay centred inside the card instead of hugging the left edge. */
.zzn-projekt__logo--square {
  width: auto;
  max-width: min(100%, 240px);
  max-height: 200px;
  margin: 0.5rem auto 0;
  object-position: center;
}
/* When the logo is square, centre the whole field content around it */
.zzn-projekt__meta:has(.zzn-projekt__logo--square) { text-align: center; }
.zzn-projekt__meta-name {
  font-family: 'Heronsans', sans-serif;
  font-weight: 600;
  font-size: 1.15rem;
  line-height: 1.3;
}
.zzn-projekt__meta--light .zzn-projekt__meta-name { color: #0e2a34; }
.zzn-projekt__meta--dark .zzn-projekt__meta-name { color: #fff; }
.zzn-projekt__title--caps,
.zzn-proj-card__title--caps { text-transform: uppercase; }
.zzn-projekt__prose {
  font-family: 'Eudoxussans', 'Inter', system-ui, sans-serif;
  font-size: 1.08rem;
  line-height: 1.7;
  color: rgba(255,255,255,0.86);
  max-width: 65ch;
}
.zzn-projekt__prose > *:first-child { margin-top: 0; }
.zzn-projekt__prose p { margin: 0 0 1.2em; }
.zzn-projekt__prose p:first-of-type::first-line { color: #fff; }
.zzn-projekt__prose strong { color: #fff; font-weight: 600; }
.zzn-projekt__prose a {
  color: #fff;
  border-bottom: 1px solid rgba(210,68,16,0.65);
  text-decoration: none;
  transition: border-color .2s;
}
.zzn-projekt__prose a:hover { border-bottom-color: #d24410; }
.zzn-projekt__prose h2, .zzn-projekt__prose h3 {
  font-family: 'Heronsans', sans-serif;
  color: #fff;
  margin: 2em 0 0.6em;
  line-height: 1.2;
}
.zzn-projekt__prose h2 { font-size: 1.6rem; }
.zzn-projekt__prose h3 { font-size: 1.25rem; }
.zzn-projekt__prose ul, .zzn-projekt__prose ol { padding-left: 1.4em; margin: 0 0 1.2em; }
.zzn-projekt__prose li { margin: 0.35em 0; }

/* --- Gallery — Pinterest-style masonry that respects native image aspect ratios --- */
.zzn-projekt-gallery {
  max-width: 1200px;
  margin: 2rem auto 0;
  padding: 0 2rem;
  column-count: 3;
  column-gap: 1.25rem;
}
@media (max-width: 900px) { .zzn-projekt-gallery { column-count: 2; } }
@media (max-width: 560px) { .zzn-projekt-gallery { column-count: 1; } }
.zzn-projekt-gallery__item {
  margin: 0 0 1.25rem;
  border-radius: 16px;
  overflow: hidden;
  background: rgba(255,255,255,0.03);
  border: 1px solid rgba(255,255,255,0.06);
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
  display: block;
}
.zzn-projekt-gallery__item img {
  width: 100%;
  height: auto;
  display: block;
  transition: transform .5s ease;
}
.zzn-projekt-gallery__item:hover img { transform: scale(1.03); }

/* ============================================================
   Homepage project wall — editorial rhythm grid.
   Every 5th card spans 2 cols to break the flat matrix.
   ============================================================ */
.zzn-proj-wall {
  --gap: 1.25rem;
  display: grid;
  grid-auto-flow: dense;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-auto-rows: clamp(260px, 26vw, 420px);
  gap: var(--gap);
  margin: 2rem auto 0;
  width: 100%;
  max-width: 1280px;
}
@media (max-width: 1024px) { .zzn-proj-wall { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: clamp(280px, 40vw, 440px); } }
@media (max-width: 640px)  { .zzn-proj-wall { grid-template-columns: 1fr; grid-auto-rows: 68vw; } }

.zzn-proj-card {
  position: relative;
  overflow: hidden;
  border-radius: 22px;
  isolation: isolate;
  display: block;
  text-decoration: none;
  color: #fff;
  background: var(--zzn-blue);
  border: 1px solid rgba(255,255,255,0.06);
  transform: translateZ(0);
  transition: transform .5s cubic-bezier(.16,1,.3,1), box-shadow .5s;
}
.zzn-proj-card--wide { grid-column: span 2; }
@media (max-width: 640px) { .zzn-proj-card--wide { grid-column: span 1; } }

.zzn-proj-card__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  z-index: 0;
  transition: transform .8s cubic-bezier(.16,1,.3,1), filter .5s;
  filter: saturate(0.9);
}
.zzn-proj-card__veil {
  position: absolute; inset: 0;
  z-index: 1;
  background:
    linear-gradient(180deg, rgba(0,20,28,0) 25%, rgba(0,20,28,0.55) 55%, rgba(0,20,28,0.92) 82%, rgba(0,20,28,0.98) 100%),
    linear-gradient(115deg, rgba(54,70,4,0) 55%, rgba(210,68,16,0.30) 100%);
  transition: opacity .5s;
}
/* Sharper readability shade behind the text block itself */
.zzn-proj-card__body::before {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0; top: -40%;
  background: linear-gradient(180deg, rgba(0,20,28,0) 0%, rgba(0,20,28,0.55) 55%, rgba(0,20,28,0.9) 100%);
  z-index: -1;
  pointer-events: none;
}
.zzn-proj-card__body {
  position: absolute; left: 0; right: 0; bottom: 0;
  z-index: 2;
  padding: 1.4rem 1.5rem 1.4rem;
  display: flex; flex-direction: column; gap: 0.45rem;
  isolation: isolate;
}
.zzn-proj-card__title {
  font-family: 'Heronsans', 'Inter', system-ui, sans-serif;
  font-weight: 700;
  font-size: clamp(1.2rem, 1.7vw, 1.8rem);
  line-height: 1.12;
  margin: 0;
  color: #fff;
  text-wrap: balance;
  text-shadow: 0 2px 14px rgba(0,0,0,0.55), 0 1px 2px rgba(0,0,0,0.4);
}
.zzn-proj-card--wide .zzn-proj-card__title {
  font-size: clamp(1.4rem, 2.2vw, 2.2rem);
}
.zzn-proj-card__cta {
  display: inline-flex; align-items: center; gap: 0.5rem;
  margin-top: 0.65rem;
  font-family: 'Eudoxussans', sans-serif;
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  color: #fff;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .35s ease, transform .35s cubic-bezier(.16,1,.3,1);
}
.zzn-proj-card__cta svg { transition: transform .35s cubic-bezier(.16,1,.3,1); }

/* Brand-gradient outline ring, revealed on hover */
.zzn-proj-card::after {
  content: "";
  position: absolute; inset: 0;
  z-index: 3;
  border-radius: inherit;
  padding: 1.5px;
  background: linear-gradient(120deg, rgba(54,70,4,0.9), rgba(210,68,16,0.9) 60%, rgba(54,70,4,0.9));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: 0;
  transition: opacity .35s ease;
  pointer-events: none;
}

.zzn-proj-card:hover { transform: translateY(-4px); box-shadow: 0 24px 60px -20px rgba(0,0,0,0.55); }
.zzn-proj-card:hover .zzn-proj-card__img { transform: scale(1.06); filter: saturate(1.05); }
.zzn-proj-card:hover .zzn-proj-card__veil { opacity: 1; }
.zzn-proj-card:hover .zzn-proj-card__cta { opacity: 1; transform: translateY(0); }
.zzn-proj-card:hover .zzn-proj-card__cta svg { transform: translateX(4px); }
.zzn-proj-card:hover::after { opacity: 1; }

/* Kill Webflow's default wrapper padding around the injected wall */
.collection-list-wrapper-3 { width: 100%; }

/* "Mehr in Arbeit" note below the last published project. Used to be a
   navy card with radial glows and a border — against the white grid and a
   wall of photographic cards it read as an oversized button rather than a
   closing line, so it is plain text now: a small kicker, one muted
   sentence, no rule, no box, nothing boxed or clickable. */
.zzn-proj-more {
  grid-column: 1 / -1;
  max-width: 640px;
  margin: clamp(4rem, 9vw, 7rem) auto 0;
  padding-top: clamp(1.75rem, 4vw, 2.5rem);
  text-align: center;
}
.zzn-proj-more__title {
  font-family: 'Eudoxussans', 'Inter', system-ui, sans-serif;
  font-weight: 600;
  font-size: 0.78rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  margin: 0 0 0.5rem;
}
.zzn-proj-more__text {
  font-family: 'Eudoxussans', sans-serif;
  font-size: clamp(0.92rem, 1.2vw, 1.02rem);
  line-height: 1.55;
  margin: 0 auto;
  max-width: 42ch;
}
/* .zzn-about-projects--home forces white text (its cards sit on photos) at
   higher specificity than a plain .zzn-proj-more__title/__text rule can
   beat — this note sits directly on the white section instead, so it needs
   to win that fight explicitly.
   Solid black, not the half-alpha grey this started as: the collage now
   flies behind the copy, and a 50% ink read as washed out against it. */
.zzn-bw-zone .zzn-about-projects--home .zzn-proj-more__title {
  color: #000 !important;
  -webkit-text-fill-color: #000 !important;
}
.zzn-bw-zone .zzn-about-projects--home .zzn-proj-more__text {
  color: rgba(0, 0, 0, 0.72) !important;
  -webkit-text-fill-color: rgba(0, 0, 0, 0.72) !important;
}
/* Sobald die Spirale den Grund übernimmt, wäre schwarze Schrift darauf weg.
   `.is-dark` setzt zzn-dot-transition.js bei 42% des Fluges — also während der
   weiße Ball noch deckt, damit die Tinte schon weiß ist, wenn das Dunkel
   erscheint. Klasse statt GSAP-Tween, weil die Regeln oben !important tragen
   und Inline-Styles damit verlieren würden. */
.zzn-bw-zone .zzn-about-projects--home .zzn-proj-more__title,
.zzn-bw-zone .zzn-about-projects--home .zzn-proj-more__text {
  transition: color 380ms ease, -webkit-text-fill-color 380ms ease;
}
.zzn-bw-zone .zzn-about-projects--home .zzn-proj-more.is-dark .zzn-proj-more__title {
  color: #fff !important;
  -webkit-text-fill-color: #fff !important;
}
.zzn-bw-zone .zzn-about-projects--home .zzn-proj-more.is-dark .zzn-proj-more__text {
  color: rgba(255, 255, 255, 0.82) !important;
  -webkit-text-fill-color: rgba(255, 255, 255, 0.82) !important;
}
@media (max-width: 640px) {
  .zzn-proj-more { margin-top: clamp(3rem, 12vw, 5rem); }
}


/* ============================================================
   About / Projekte page — service categories + project wall
   ============================================================ */
/* ============================================================
   About — "Kreation → Umsetzung" strip
   ------------------------------------------------------------
   A white, hairline-ruled index in the vein of a fashion
   e-commerce landing (Balenciaga / 032c): rules instead of
   cards, uppercase display type, generous empty space.

   Structure: one expandable ROW (Kreation), four expandable
   COLUMNS (disciplines), one expandable ROW (Umsetzung).
   Behaviour lives in /js/zzn-kreation.js, which only toggles
   .is-open — all motion is CSS.
   ============================================================ */

.zzn-kr {
  --kr-ink: #0a0a0a;
  --kr-muted: rgba(0, 0, 0, 0.52);
  --kr-line: rgba(0, 0, 0, 0.16);
  --kr-hover: #f4f4f1;
  --kr-ease: cubic-bezier(0.16, 1, 0.3, 1);
  /* Pink/red ramp, shared by the top band and the outro headline so the
     page opens and closes on the same colour. */
  --kr-pink-deep: #4a0d22;
  --kr-pink: #c81e5a;
  --kr-pink-hot: #ff5c7a;

  position: relative;
  background: #fff;
  color: var(--kr-ink);
  padding-bottom: clamp(3rem, 7vw, 6rem);
  /* The section paints its own white, so it must sit above the
     dark-blue <body> that the rest of the site relies on. */
  isolation: isolate;
}

/* Blue → white ramp behind the (transparent) nav. Multi-stop so the
   teal does not pass through a muddy grey on its way to white. */
/* Top band: the homepage hero video, multiplied with the brand-surface
   gradient animation (zzn-gradient-shift) recoloured pink. The mask
   dissolves the whole stack into the white page, so the band can run deep
   enough to wash over the top of the heading without a hard edge. */
.zzn-kr__fade {
  position: absolute;
  inset: 0 0 auto 0;
  height: clamp(460px, 68vh, 860px);
  pointer-events: none;
  overflow: hidden;
  isolation: isolate; /* keeps the multiply off everything underneath */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 45%, transparent 100%);
          mask-image: linear-gradient(180deg, #000 0%, #000 45%, transparent 100%);
}
.zzn-kr__fade-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Half strength: at full opacity the multiply crushed the video to a flat
   red. At 0.5 the footage still reads through the pink. */
.zzn-kr__fade-tint {
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, var(--kr-pink-deep) 0%, var(--kr-pink) 45%, var(--kr-pink-hot) 100%);
  background-size: 220% 220%;
  animation: zzn-gradient-shift 14s ease-in-out infinite;
  mix-blend-mode: multiply;
  opacity: 0.5;
}
/* A looping video is motion too, so drop to the flat gradient. */
@media (prefers-reduced-motion: reduce) {
  .zzn-kr__fade-tint { animation: none; mix-blend-mode: normal; }
  .zzn-kr__fade-video { display: none; }
}

.zzn-kr__inner {
  position: relative;
  z-index: 1;
  max-width: 1440px;
  margin: 0 auto;
  padding: 0 clamp(1.25rem, 4vw, 3.5rem);
}

/* --- Head ------------------------------------------------- */
/* Starts inside the tail of the band on purpose — the mask has faded it to
   a light wash by here, so the heading reads while still sitting in colour. */
.zzn-kr__head {
  padding-top: clamp(380px, 56vh, 700px);
  padding-bottom: clamp(2.5rem, 6vw, 5rem);
}
.zzn-kr__eyebrow {
  font-family: 'Eudoxussans', sans-serif;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--kr-muted);
  margin: 0 0 clamp(1rem, 2.5vw, 1.75rem);
}
.zzn-kr__display {
  font-family: 'Font Bureau Heronsans', 'Heronsans', sans-serif;
  font-weight: 700;
  font-size: clamp(2.6rem, 8.5vw, 6.5rem);
  /* Uppercase German needs room for the umlaut: below ~0.95 the dots on
     Ü/Ö collide with the descender line of the row above. */
  line-height: 0.95;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: var(--kr-ink);
  margin: 0 0 clamp(1rem, 2.5vw, 1.75rem);
}
.zzn-kr__lede {
  font-family: 'Eudoxussans', sans-serif;
  font-size: clamp(0.95rem, 1.15vw, 1.05rem);
  line-height: 1.55;
  color: var(--kr-muted);
  margin: 0;
  max-width: 46ch;
}

/* --- Shared rules ----------------------------------------- */
.zzn-kr__grid { border-top: 1px solid var(--kr-line); }
.zzn-kr__row,
.zzn-kr__cols { border-bottom: 1px solid var(--kr-line); }

/* Every trigger is a real <button>; strip the UA chrome. */
.zzn-kr__trigger {
  display: flex;
  width: 100%;
  margin: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  transition: background-color 0.45s var(--kr-ease);
}
.zzn-kr__trigger:focus-visible {
  outline: 2px solid var(--zzn-orange, #d24410);
  outline-offset: -3px;
}

.zzn-kr__title {
  display: block;
  font-family: 'Font Bureau Heronsans', 'Heronsans', sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: -0.005em;
  line-height: 0.95;
  color: var(--kr-ink);
}
.zzn-kr__teaser {
  display: block;
  font-family: 'Eudoxussans', sans-serif;
  line-height: 1.5;
  color: var(--kr-muted);
}

/* --- Rows (Kreation / Umsetzung) --------------------------- */
.zzn-kr__row .zzn-kr__trigger {
  align-items: baseline;
  gap: clamp(1rem, 3.5vw, 3.5rem);
  padding: clamp(1.6rem, 3vw, 2.75rem) clamp(0.75rem, 1.5vw, 1.25rem);
}
/* Fixed gutter so the title and the expanded panel below sit on the same
   vertical. --kr-gutter mirrors the trigger's own padding. The ordinal this
   used to budget 3rem (plus its flex gap) for was removed with the markup, and
   the leftover reservation pushed every panel body 98px off the title. */
.zzn-kr__row {
  --kr-gutter: clamp(0.75rem, 1.5vw, 1.25rem);
}
.zzn-kr__row-main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  min-width: 0;
}
.zzn-kr__row .zzn-kr__title { font-size: clamp(1.9rem, 5vw, 3.4rem); }
.zzn-kr__row .zzn-kr__teaser {
  font-size: clamp(0.9rem, 1.1vw, 1rem);
  max-width: 52ch;
}

/* +/− indicator: two hairlines, the vertical one collapses when open. */
.zzn-kr__sign {
  position: relative;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  align-self: center;
}
.zzn-kr__sign::before,
.zzn-kr__sign::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 1px;
  background: var(--kr-ink);
  transition: transform 0.5s var(--kr-ease);
}
.zzn-kr__sign::after { transform: rotate(90deg); }
.zzn-kr__row.is-open .zzn-kr__sign::after { transform: rotate(90deg) scaleX(0); }

/* --- Columns (the disciplines) ----------------------------- */
.zzn-kr__cols {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  transition: grid-template-columns 0.65s var(--kr-ease);
}
/* Column is a flex stack so the trigger absorbs the leftover height. Without
   this a teaser that wraps to one more line pushes its "Mehr" foot out of
   line with its neighbours', since the grid stretches the column but not the
   button inside it. */
.zzn-kr__col {
  display: flex;
  flex-direction: column;
  border-left: 1px solid var(--kr-line);
  min-width: 0;
}
.zzn-kr__col:first-child { border-left: 0; }
.zzn-kr__col .zzn-kr__trigger { flex: 1 1 auto; }

.zzn-kr__col .zzn-kr__trigger {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.9rem;
  /* Tall and mostly empty on purpose — the whitespace is the point. */
  min-height: clamp(340px, 48vh, 520px);
  padding: clamp(1.4rem, 2.2vw, 2.1rem) clamp(1.05rem, 1.7vw, 1.6rem);
}
.zzn-kr__col { transition: background-color 0.45s var(--kr-ease); }
.zzn-kr__col .zzn-kr__title {
  font-size: clamp(1.5rem, 2.6vw, 2.3rem);
  transition: transform 0.5s var(--kr-ease);
}
.zzn-kr__col .zzn-kr__teaser { font-size: 0.88rem; max-width: 24ch; }

/* Pushes the foot to the bottom of the tall column. */
.zzn-kr__spacer { flex: 1 1 auto; }

.zzn-kr__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding-top: 1.25rem;
  border-top: 1px solid var(--kr-line);
  color: var(--kr-ink);
}
.zzn-kr__foot-label {
  font-family: 'Eudoxussans', sans-serif;
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--kr-muted);
  transition: color 0.4s var(--kr-ease);
}
/* Both labels ship in the markup; CSS picks the one that matches state,
   so the swap cannot fall out of sync with the .is-open class. */
.zzn-kr__foot-label [data-label="open"],
.zzn-kr__col.is-open .zzn-kr__foot-label [data-label="closed"] { display: none; }
.zzn-kr__col.is-open .zzn-kr__foot-label [data-label="open"] { display: inline; }
.zzn-kr__arrow {
  display: block;
  transition: transform 0.55s var(--kr-ease);
}
/* Flip up when open (dismissal). The hover nudge lives in the
   pointer-gated block below. */
.zzn-kr__col.is-open .zzn-kr__arrow { transform: rotate(180deg); }

/* --- Hover affordances ------------------------------------
   Gated on a real pointer. On touch, :hover latches after a tap and
   leaves a column tinted and shifted until you tap elsewhere, which
   reads as a stuck selection rather than feedback. */
@media (hover: hover) and (pointer: fine) {
  .zzn-kr__row:hover .zzn-kr__trigger { background-color: var(--kr-hover); }
  /* Tint the whole column, not just the trigger, so an open panel stays
     visually attached to the header that opened it. */
  .zzn-kr__col:hover { background-color: var(--kr-hover); }
  .zzn-kr__col:hover .zzn-kr__title { transform: translateX(4px); }
  .zzn-kr__col:hover .zzn-kr__foot-label { color: var(--kr-ink); }
  /* Nudge down on hover — the invitation to click. */
  .zzn-kr__col:hover .zzn-kr__arrow { transform: translateY(4px); }
  .zzn-kr__col.is-open:hover .zzn-kr__arrow { transform: rotate(180deg) translateY(4px); }
}

/* Hovered column takes a little extra width — the "lean in" cue.
   :has() is unsupported on older engines, where it simply no-ops.
   Must stay inside the 4-up breakpoint: these selectors outrank the
   narrow-screen track reset, so unscoped they would drag the 2-up and
   1-up layouts back to four columns on hover. */
@media (min-width: 992px) and (hover: hover) and (pointer: fine) {
  .zzn-kr__cols:has(.zzn-kr__col:nth-child(1):hover) { grid-template-columns: 1.14fr 1fr 1fr 1fr; }
  .zzn-kr__cols:has(.zzn-kr__col:nth-child(2):hover) { grid-template-columns: 1fr 1.14fr 1fr 1fr; }
  .zzn-kr__cols:has(.zzn-kr__col:nth-child(3):hover) { grid-template-columns: 1fr 1fr 1.14fr 1fr; }
  .zzn-kr__cols:has(.zzn-kr__col:nth-child(4):hover) { grid-template-columns: 1fr 1fr 1fr 1.14fr; }
}

/* --- Expanding panel --------------------------------------- */
/* 0fr → 1fr animates without measuring heights in JS. The inner box
   must clip and be allowed to shrink below its content height. */
.zzn-kr__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.6s var(--kr-ease);
}
.zzn-kr__panel-inner {
  overflow: hidden;
  min-height: 0;
}
.zzn-kr__panel-content {
  padding: 0 clamp(1.05rem, 1.7vw, 1.6rem) clamp(1.75rem, 3vw, 2.5rem);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.4s var(--kr-ease), transform 0.5s var(--kr-ease);
}
.is-open > .zzn-kr__panel { grid-template-rows: 1fr; }
.is-open > .zzn-kr__panel .zzn-kr__panel-content {
  opacity: 1;
  transform: none;
  transition-delay: 0.1s;
}
/* Rows indent their panel to clear the ordinal gutter. */
.zzn-kr__row .zzn-kr__panel-content {
  padding-left: var(--kr-gutter);
  max-width: 68ch;
}

.zzn-kr__panel-content p {
  font-family: 'Eudoxussans', sans-serif;
  font-size: clamp(0.88rem, 1.05vw, 0.98rem);
  line-height: 1.65;
  color: rgba(0, 0, 0, 0.72);
  margin: 0 0 1rem;
}
.zzn-kr__panel-content p:last-of-type { margin-bottom: 1.5rem; }

.zzn-kr__tags {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0;
  margin: 0;
}
.zzn-kr__tags li {
  font-family: 'Eudoxussans', sans-serif;
  font-size: 0.64rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--kr-ink);
  border: 1px solid var(--kr-line);
  padding: 0.42rem 0.75rem;
}

/* --- Outro: the closing statement under 03 ------------------ */
/* The showreel between the Umsetzung row and the outro. Webflow sizes
   `.showreel-wrapper` for a full-bleed homepage block — 70vh tall and, from
   1920 up, 80vw wide, which is wider than the 1440 column it now sits in.
   Both are replaced by an aspect box that simply fills the column. */
.zzn-kr__reel {
  margin: clamp(2.5rem, 5vw, 4.5rem) 0 0;
}
.zzn-kr__reel .showreel-wrapper {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  box-shadow: 0 18px 60px -26px rgba(0, 0, 0, 0.5);
}
@media (max-width: 767px) {
  .zzn-kr__reel .showreel-wrapper { aspect-ratio: 4 / 3; }
}

.zzn-kr__outro {
  padding: clamp(3.5rem, 8vw, 7rem) clamp(0.75rem, 1.5vw, 1.25rem)
           clamp(1rem, 3vw, 2rem);
}
.zzn-kr__outro-title {
  font-family: 'Font Bureau Heronsans', 'Heronsans', sans-serif;
  font-weight: 700;
  font-size: clamp(2.2rem, 6.5vw, 5rem);
  line-height: 0.95;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: var(--kr-ink);
  margin: 0 0 clamp(1.25rem, 2.5vw, 2rem);
}
/* Pink version of the homepage headline sweep. Three stops easing back and
   forth barely read as movement, so this uses a repeating dark->hot->dark
   ramp travelling linearly across 4x its own width — the same trick, and
   the same keyframes, as .zzn-grad-move. First and last stop match so the
   loop has no seam. Light end stops at #ff8fb0, which still holds on white. */
.zzn-kr__grad {
  background: linear-gradient(
    115deg,
    #4a0d22 0%, #8c0f2e 12%, #c81e5a 25%, #ff2d6f 37%,
    #ff8fb0 50%, #ff2d6f 63%, #c81e5a 75%, #8c0f2e 88%, #4a0d22 100%
  );
  background-size: 400% 100%;
  animation: zzn-grad-move 9s linear infinite;
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
@media (prefers-reduced-motion: reduce) {
  .zzn-kr__grad { animation: none; }
}
.zzn-kr__outro-text {
  font-family: 'Eudoxussans', sans-serif;
  font-size: clamp(0.95rem, 1.15vw, 1.05rem);
  line-height: 1.6;
  color: var(--kr-muted);
  margin: 0;
  max-width: 46ch;
}

/* --- Responsive -------------------------------------------- */
@media (max-width: 991px) {
  .zzn-kr__cols { grid-template-columns: repeat(2, 1fr); }
  /* Row 2 of the 2x2 needs its own rule, and col 3 restarts the row. */
  .zzn-kr__col:nth-child(3) { border-left: 0; }
  .zzn-kr__col:nth-child(n + 3) { border-top: 1px solid var(--kr-line); }
}
@media (max-width: 640px) {
  /* Deliberately still 2-up: one column per row would read as four more
     list items and blur the line between 01, 02 and 03. Two columns keep
     02 legible as a single block of four sub-steps. */
  .zzn-kr__col .zzn-kr__trigger { min-height: 200px; padding-inline: 0.85rem; }
  .zzn-kr__col .zzn-kr__title { font-size: 1.35rem; }
  .zzn-kr__col .zzn-kr__teaser { font-size: 0.8rem; }
  .zzn-kr__foot-label { font-size: 0.6rem; letter-spacing: 0.18em; }

  .zzn-kr__head { padding-top: clamp(210px, 30vh, 280px); }
  .zzn-kr__row .zzn-kr__trigger { gap: 0.75rem; padding-inline: 0.5rem; }
  .zzn-kr__row { --kr-gutter: 0.5rem; }

  /* Group separation. On a narrow screen the three stages otherwise read
     as one long ladder of rules, so 01 / 02 / 03 each get a heavier
     boundary than the hairlines used inside 02. */
  .zzn-kr__grid { border-top-width: 2px; border-top-color: var(--kr-ink); }
  .zzn-kr__row { border-bottom-width: 2px; border-bottom-color: var(--kr-ink); }
  .zzn-kr__cols { border-bottom-width: 2px; border-bottom-color: var(--kr-ink); }
}

@media (prefers-reduced-motion: reduce) {
  .zzn-kr__panel,
  .zzn-kr__panel-content,
  .zzn-kr__cols,
  .zzn-kr__arrow,
  .zzn-kr__title,
  .zzn-kr__sign::before,
  .zzn-kr__sign::after,
  .zzn-kr__trigger { transition: none !important; }
}

/* --- About: project wall wrapper --- */
.zzn-about-projects {
  background: var(--zzn-blue);
  color: #fff;
  /* Inline padding sits on the head/wall below via the page container, so
     it is not added twice. */
  padding: 3rem 0 6rem;
}
.zzn-about-projects__head {
  max-width: var(--zzn-max);
  margin: 0 auto 2rem;
  padding-inline: var(--zzn-pad);
  box-sizing: border-box;
}
.zzn-about-projects__kicker {
  font-family: 'Eudoxussans', sans-serif;
  font-size: 0.72rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.55);
  margin-bottom: 0.6rem;
}
.zzn-about-projects__title {
  font-family: 'Heronsans', sans-serif;
  font-weight: 700;
  font-size: clamp(1.8rem, 3.5vw, 2.8rem);
  margin: 0;
  color: #fff;
}
.zzn-about-projects .zzn-proj-wall {
  max-width: var(--zzn-max);
  margin: 0 auto;
  padding-inline: var(--zzn-pad);
  box-sizing: border-box;
}

/* --- About: project wall, light variant ---------------------
   Carries the white, hairline treatment of .zzn-kr through to
   the CMS wall so the page reads as one surface instead of two.
   Scoped to --light; the homepage keeps its dark --home variant.
   ------------------------------------------------------------ */
.zzn-about-projects--light {
  /* Same gutter + measure as .zzn-kr__inner, so the hairline under the
     heading lands on exactly the same vertical as the rules above it. */
  --kr-pad: clamp(1.25rem, 4vw, 3.5rem);
  --kr-measure: calc(1440px - 2 * clamp(1.25rem, 4vw, 3.5rem));

  background: #fff;
  color: #0a0a0a;
  padding: clamp(3.5rem, 7vw, 6rem) var(--kr-pad) 0;
}
.zzn-about-projects--light .zzn-about-projects__head,
.zzn-about-projects--light .zzn-proj-wall {
  max-width: var(--kr-measure);
  margin-inline: auto;
  padding-inline: 0;
}
.zzn-about-projects--light .zzn-about-projects__head {
  padding-bottom: clamp(1.5rem, 3vw, 2.25rem);
  margin-bottom: clamp(1.75rem, 3.5vw, 2.75rem);
  border-bottom: 1px solid rgba(0, 0, 0, 0.16);
}
.zzn-about-projects--light .zzn-about-projects__kicker { color: rgba(0, 0, 0, 0.52); }
.zzn-about-projects--light .zzn-about-projects__title {
  color: #0a0a0a;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  line-height: 0.95;
}
/* Square corners to match the ruled grid above. */
.zzn-about-projects--light .zzn-proj-card { border-radius: 0; }

/* White wall blushes into the soft-pink closing block. The two are only a
   few percent apart, so this is a short tint shift rather than the long
   ramp a dark landing needed. Full-bleed via the negative inline margin. */
.zzn-about-projects--light::after {
  content: "";
  display: block;
  height: clamp(140px, 18vh, 240px);
  margin: clamp(4rem, 8vw, 7rem) calc(-1 * var(--kr-pad)) 0;
  background: linear-gradient(180deg, #fff 0%, #fffafb 45%, var(--kr-blush) 100%);
}

/* ---- "Sie melden sich." closing block (/about only) ----
   Soft blush, near white, with the type inverted to dark. It paints
   nothing of its own, so without this the blue <body> showed through. */
.zzn-about-projects--light + .section {
  /* Webflow gives this section no vertical padding und der Inhalt lief 40px
     aus dem Kasten; das fängt ihn. Der Wert war zweimal zu groß: erst
     clamp(180px, 22vh, 300px) als Platz für die Farbrampe darunter, dann
     clamp(5rem, 10vh, 8rem) — gemessen 90 bis 100px leeres Blush unter der
     Hinweiszeile, das als Loch vor dem Footer las. Die Rampe gibt es nicht
     mehr und der Footer dieser Seite ist weiß, der Block braucht also nur
     noch seinen eigenen Fußabstand. */
  padding-bottom: clamp(2.75rem, 5vh, 4rem);
  background-color: var(--kr-blush);
  /* HIER STAND EINE RAMPE von Blush auf das Seitenblau, damit der Block in
     den damals navyfarbenen Footer übergeht. Der Footer dieser Seite ist
     inzwischen weiß — die Rampe lief also in ein Dunkelblau, unter dem
     unvermittelt eine helle Fläche begann, und genau das las sich als
     Fehler. Blush stößt jetzt direkt an das Weiß des Footers; zwischen
     #fdf4f7 und #fff liegen keine drei Prozent Helligkeit. */
  background-image: none;
}
/* Three Webflow children fill the same box with the flat site blue, which
   covered everything but the top few pixels. Cleared only for this page's
   instance — the homepage copy of this section keeps them. */
.zzn-about-projects--light + .section .background-shade,
.zzn-about-projects--light + .section .wrapper.shade-wrapper,
.zzn-about-projects--light + .section .background-cover {
  background-color: transparent;
  background-image: none;
}

/* Invert the type for the light ground. */
.zzn-about-projects--light + .section :is(h1, h2, h3, .large-heading, .giant-heading) {
  color: var(--kr-ink-on-blush);
}
/* `.section .body-display.large` sets the colour with !important and forces
   nested <strong> to #fff. Written out rather than wrapped in :is(), which
   takes only its most specific argument and tied at (0,3,0) — losing on
   source order to a rule ~170 lines further down. */
.zzn-about-projects--light + .section p,
.zzn-about-projects--light + .section .paragraph-2,
.zzn-about-projects--light + .section .body-display,
.zzn-about-projects--light + .section .body-display.large {
  color: rgba(10, 10, 10, 0.68) !important;
}
.zzn-about-projects--light + .section .paragraph-2 strong,
.zzn-about-projects--light + .section .body-display.large strong {
  color: var(--kr-ink-on-blush);
}
/* Webflow's .button.solid is a light pill for dark grounds; flip it. */
.zzn-about-projects--light + .section .button.solid {
  background-color: var(--kr-ink-on-blush);
  border-color: var(--kr-ink-on-blush);
  color: #fff;
}
.zzn-about-projects--light + .section .button.solid:hover {
  background-color: var(--zzn-orange);
  border-color: var(--zzn-orange);
}

/* This section carries a [data-w-id], so the global scroll-reveal fades the
   whole element in. That was fine while it was transparent, but it now
   paints the page ground — so mid-scroll the blush and the ramp faded too,
   letting the body show through as a grey wash with a hard edge along the
   section's top. Hold the box opaque; its contents still reveal via the
   .about-wrapper > * rule.
   !important because the reveal selector's five :not() arguments stack to
   (0,7,0) — no practical selector for this one section reaches that. */
@supports (animation-timeline: view()) {
  .zzn-about-projects--light + .section[data-w-id] {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* The footer is the same finding one section further down, and was missed.
   On /about and the project pages it is deliberately white (see the
   "Tonwertumkehr" further down), but it also carries a [data-w-id] — left
   over from a mouse-move interaction on another page, never a reveal — so the
   global scroll-reveal faded and lifted it in as well. For the first quarter
   of its approach it was a translucent white box over the body's navy, which
   reads as a washed-out teal-grey (#5a7078 is white at ~35% over #002632),
   and the 24px lift opened a navy hairline between the CTA and the footer.
   On every other page the footer is navy on navy, which is why it only ever
   showed here. The end state was always right; only the frames on the way
   were wrong. Held opaque and in place, same treatment as the section above. */
@supports (animation-timeline: view()) {
  body[data-page="about"] .section.footer[data-w-id],
  body[data-page="projekt"] .section.footer[data-w-id] {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* Drop the team photo strip and the social tiles from this page. Hidden
   rather than deleted server-side, which also stops the browser fetching
   their background images. */
.zzn-about-projects--light + .section .box-two,
.zzn-about-projects--light + .section .box {
  display: none;
}

/* ============================================================
   Homepage — unified section typography (match .giant-heading.start)
   + scroll-reveal animations + brand-gradient buttons
   ============================================================ */

/* Section headings on the homepage share the top hero style. */
.section .heading,
.section .large-heading,
.section .large-heading.small-tema,
.section .large-heading.small-tema.giant-heading.team,
.section .giant-heading.siemelden,
.section .giant-heading:not(.start) {
  font-family: 'Font Bureau Heronsans', 'Heronsans', 'Inter', system-ui, sans-serif !important;
  font-weight: 700 !important;
  font-size: clamp(2.6rem, 6.2vw, 5rem) !important;
  line-height: 1.05 !important;
  letter-spacing: -0.03em !important;
  text-transform: none;
  margin: 0 0 1.25rem;
  color: #fff;
}
.section .heading { text-transform: uppercase; }

/* Animated brand-gradient word in the "HUNGER AUF MEHR?" homepage heading. */
.zzn-fade-word {
  display: inline-block;
  color: #ffe8d6;
  background-image: linear-gradient(
    115deg,
    #fff6eb 0%,
    #ffe8d6 8%,
    #f7d9a8 17%,
    #f2a65a 28%,
    #ea6a3d 39%,
    #d24410 50%,
    #ea6a3d 61%,
    #f2a65a 72%,
    #f7d9a8 83%,
    #ffe8d6 92%,
    #fff6eb 100%
  );
  background-size: 220% 100%;
  background-repeat: repeat;
  background-position: 0% 50%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  animation: zzn-mehr-gradient 8s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
@keyframes zzn-mehr-gradient {
  0%, 100% { background-position: 0% 50%; }
  50%      { background-position: 100% 50%; }
}
@media (prefers-reduced-motion: reduce) {
  .zzn-fade-word { animation: none; background-position: 25% 50%; }
}

/* The .zzn-hunger SECTION no longer exists as a section: its heading and
   paragraph are relocated into the spiral's vortex eye at runtime (see
   zzn-dot-transition.js) and the emptied wrapper is removed, so every rule
   that used to lay this out as its own full-width block is gone with it.
   Only the per-word rule survives, because those spans travel with the
   heading into the eye. Styling for their new home is .zzn-exit-copy /
   .zzn-exit-headline / .zzn-exit-lede, further down. */
.zzn-hunger-word {
  display: inline-block;
  margin-right: 0.18em;
}
.zzn-hunger-word:last-child { margin-right: 0; }

/* TOT SEIT DEM AUSBAU DER SEKTION. `.zzn-hunger` steht in keiner HTML-Datei
   und in keinem Skript mehr — die Sektion wurde von der Startseite genommen,
   die Spirale blieb als Hintergrundband. Diese Regeln greifen nichts mehr an;
   sie stehen nur noch hier, weil die Spirale "vorerst" so bleibt und ein
   späterer Rückbau sie sonst neu erfinden müsste. Gleiches gilt für
   .zzn-exit-copy / .zzn-exit-headline / .zzn-exit-lede / .zzn-exit-scrim /
   .zzn-exit-word weiter unten. */
/* FALLBACK ONLY. .zzn-hunger survives in the DOM just when the spiral never
   ran — prefers-reduced-motion, or GSAP failing to load — since otherwise
   its copy is cloned into the vortex and this section is removed. Webflow's
   IX2 reveal (opacity 0 plus a 5deg skew it animates out on scroll) is what
   this markup ships with, and on this path nothing ever clears it, so the
   paragraph rendered skewed and nearly invisible. A stylesheet !important
   beats a non-important inline style, which is how IX2 writes, so this
   pins it readable. Keep it centred to match the vortex treatment. */
.zzn-hunger { background: var(--zzn-bg, #002632); }
.zzn-hunger .about-wrapper,
.zzn-hunger .about-wrapper > .clip {
  width: 100% !important;
  align-items: center !important;
  text-align: center !important;
}
.zzn-hunger .half-dual {
  display: flex !important;
  justify-content: center !important;
  width: 100% !important;
}
.zzn-hunger .giant-heading.siemelden { margin: 0 auto 1.25rem !important; }
.zzn-hunger .half-dual > .clip,
.zzn-hunger p {
  opacity: 1 !important;
  transform: none !important;
  text-align: center !important;
  margin-left: auto !important;
  margin-right: auto !important;
  max-width: 54ch !important;
}

/* Body paragraphs on the homepage share the top hero paragraph style. */
.section .paragraph-2,
.section .body-display,
.section .body-display.large {
  font-family: 'Eudoxussans', 'Inter', system-ui, sans-serif !important;
  font-size: clamp(1rem, 1.15vw, 1.15rem) !important;
  line-height: 1.65 !important;
  font-weight: 400 !important;
  color: rgba(255,255,255,0.82) !important;
  max-width: 60ch;
  margin: 0 0 1rem;
  -webkit-text-stroke: 0;
}
.section .body-display.large strong,
.section .paragraph-2 strong { color: #fff; font-weight: 700; }

/* Reintroduce the About-page project-wall backdrop under the homepage list. */
/* The parent .wrapper.effizientes-team is 90% wide with overflow:hidden — it
   visually clips our breakout section. Neutralise it whenever it contains us. */
.section:has(> .wrapper.effizientes-team > .zzn-about-projects--home),
.section:has(> .wrapper > .zzn-about-projects--home) { overflow: visible; }
.wrapper.effizientes-team:has(> .zzn-about-projects--home),
.wrapper:has(> .zzn-about-projects--home) {
  width: 100% !important;
  max-width: 100% !important;
  overflow: visible !important;
}
.zzn-about-projects--home {
  position: relative;
  width: 100vw;
  max-width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  /* Inline padding zeroed: this is a full-bleed band, and its head and wall
     each carry the page container themselves. The 2rem here was added on
     top of that, which pushed the heading to 88px at 1440 while every other
     section sat at 56. */
  padding: 6rem 0 7rem;
  background: #002632;
  overflow: hidden;
  isolation: isolate;
}
.zzn-about-projects--home::before {
  content: "";
  position: absolute; inset: 0;
  background:
    radial-gradient(55% 45% at 20% 45%, rgba(54,70,4,0.42), transparent 70%),
    radial-gradient(50% 45% at 85% 55%, rgba(210,68,16,0.32), transparent 70%),
    radial-gradient(70% 55% at 50% 50%, rgba(0,90,110,0.28), transparent 75%);
  filter: blur(40px);
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 22%, #000 78%, transparent 100%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 22%, #000 78%, transparent 100%);
  pointer-events: none;
  z-index: 0;
  animation: zzn-home-projects-drift 22s ease-in-out infinite alternate;
}
.zzn-about-projects--home::after {
  content: "";
  position: absolute; inset: 0;
  background-image:
    radial-gradient(rgba(255,255,255,0.035) 1px, transparent 1px);
  background-size: 3px 3px;
  mix-blend-mode: overlay;
  opacity: 0.6;
  pointer-events: none;
  z-index: 0;
}
@keyframes zzn-home-projects-drift {
  0%   { transform: translate3d(-2%, -1%, 0) scale(1); }
  100% { transform: translate3d(2%, 2%, 0) scale(1.08); }
}
.zzn-about-projects--home > * { position: relative; z-index: 1; }

/* Scroll-reveal for homepage sections (Safari-friendly fallback: no motion) */
@supports (animation-timeline: view()) {
  .section .centered-intro > .clip,
  .section .centered-call-to-action,
  .section .three-grid-wrapper.small-team > *,
  .section .about-wrapper > *,
  .zzn-about-projects--home .zzn-about-projects__head {
    opacity: 0;
    transform: translate3d(0, 40px, 0);
    animation: zzn-reveal-up linear both;
    animation-timeline: view();
    animation-range: entry 5% cover 30%;
  }
}
@keyframes zzn-reveal-up {
  to { opacity: 1; transform: translate3d(0, 0, 0); }
}

/* Brand-gradient outline buttons (like the About/menu aesthetic) */
.section .button,
.section .button-top {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: 999px;
  transition: transform .5s cubic-bezier(.16,1,.3,1), box-shadow .4s;
}
.section .button:hover,
.section .button-top:hover {
  transform: translateY(-2px);
  box-shadow: 0 18px 40px -20px rgba(210,68,16,0.55);
}
.section .button::before,
.section .button-top::before {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  background: linear-gradient(120deg, #f0d78c, #d24410 45%, #364604 80%, #f0d78c);
  background-size: 220% 220%;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: 0.9;
  animation: zzn-btn-drift 8s linear infinite;
  pointer-events: none;
  z-index: 1;
}
.section .button::after,
.section .button-top::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  background: linear-gradient(120deg, rgba(210,68,16,0.0), rgba(210,68,16,0.35), rgba(240,215,140,0.0));
  background-size: 220% 100%;
  opacity: 0;
  transition: opacity .4s;
  z-index: 0;
  pointer-events: none;
}
.section .button:hover::after,
.section .button-top:hover::after { opacity: 1; }
@keyframes zzn-btn-drift {
  0%   { background-position: 0% 50%; }
  100% { background-position: 220% 50%; }
}

/* Orange project CTA bar ("WAS DARF ES SEIN?" on home, "PROJEKT
   BESPRECHEN?" on the service pages) — equal breathing room to sections above
   and below, and visible on mobile (Webflow hid it by default). */
.section.thin-section { margin-top: 0 !important; }
@media (max-width: 767px) {
  .section.thin-section { display: flex !important; margin-top: 0 !important; }
  .section.thin-section .centered-call-to-action {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 1.5rem;
  }
  .section.thin-section .heading { font-size: 2.4rem; line-height: 1.05; }
}



/* Animated brand gradient for emphasised words (e.g. ERGEBNISSE).
   Rebuilt from the original Webflow Gradient_BG_ZZN.jpg palette with
   more stops so no single hue dominates and a wider bright/white band
   for contrast. The gradient is tilted a little more (110deg) so the
   two lines of "Marketing / Abteilung" sample visibly different colours.
   Identical start/end colour keeps the loop seamless. */
.zzn-grad-move {
  background: linear-gradient(
    115deg,
    #002632 0%,
    #062b35 3%,
    #0d3a42 6%,
    #1a4d53 9%,
    #2d6b66 12%,
    #3d857d 15%,
    #4f9d91 18%,
    #6ab3a3 21%,
    #8ec9b8 24%,
    #b3ddd0 27%,
    #d4ece3 30%,
    #f5f0e4 33%,
    #f7e6d0 36%,
    #f5d4b8 39%,
    #f0c090 42%,
    #e8a24a 45%,
    #e87a3c 48%,
    #d24410 51%,
    #b8240a 54%,
    #d24410 57%,
    #e87a3c 60%,
    #f0c090 63%,
    #f0d78c 66%,
    #d4b85c 69%,
    #a8c46a 72%,
    #8ec9b8 75%,
    #5ba89a 78%,
    #2d6b66 81%,
    #0d3a42 84%,
    #002632 87%,
    #002632 90%,
    #0d3a42 93%,
    #2d6b66 96%,
    #002632 100%
  );
  background-size: 500% 100%;
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  animation: zzn-grad-move 12s linear infinite;
  display: inline-block;
  padding-right: .05em; /* prevent italic-like clipping of last glyph */
  will-change: background-position;
}
/* Hero "Marketing Abteilung" keeps its sizing but swaps the static JPG for the moving gradient */
.gradient-span.zzn-grad-move {
  background-image: none;
  background: linear-gradient(
    115deg,
    #002632 0%,
    #062b35 3%,
    #0d3a42 6%,
    #1a4d53 9%,
    #2d6b66 12%,
    #3d857d 15%,
    #4f9d91 18%,
    #6ab3a3 21%,
    #8ec9b8 24%,
    #b3ddd0 27%,
    #d4ece3 30%,
    #f5f0e4 33%,
    #f7e6d0 36%,
    #f5d4b8 39%,
    #f0c090 42%,
    #e8a24a 45%,
    #e87a3c 48%,
    #d24410 51%,
    #b8240a 54%,
    #d24410 57%,
    #e87a3c 60%,
    #f0c090 63%,
    #f0d78c 66%,
    #d4b85c 69%,
    #a8c46a 72%,
    #8ec9b8 75%,
    #5ba89a 78%,
    #2d6b66 81%,
    #0d3a42 84%,
    #002632 87%,
    #002632 90%,
    #0d3a42 93%,
    #2d6b66 96%,
    #002632 100%
  );
  background-size: 500% 100%;
  -webkit-background-clip: text;
          background-clip: text;
  line-height: 1.1;
}
@keyframes zzn-grad-move {
  0%   { background-position: 0% 50%; }
  100% { background-position: 100% 50%; }
}

/* Homepage hero headline ("Geschmack" / "käuflich") only: the light half of
   the palette, no navy, and a loop with no seam.
   The shared sweep above passes through #002632 twice — the page's own
   background, so the words dropped out of the headline — and its 0%→100%
   run ends on a different slice of the ramp than it starts on, so it
   jumps at every restart. Here the ramp REPEATS with a 16em period and the
   animation moves it by exactly one period: the last frame is the first.
   At 115deg a period of 16em along the gradient is 16 / sin(115deg) =
   17.654em along x, which is the travel; the background is made that much
   wider than the word so the shift never uncovers an edge. In em, so both
   words, at every size, show the same stretch of colour. */
.zzn-home-main-hero .gradient-span.zzn-grad-move {
  background: repeating-linear-gradient(
    115deg,
    #f5f0e4 0em,
    #f7e6d0 1em,
    #f5d4b8 2em,
    #f0c090 3.2em,
    #e8a24a 4.4em,
    #e87a3c 5.6em,
    #d24410 6.8em,
    #c8340e 7.6em,
    #d24410 8.4em,
    #e87a3c 9.6em,
    #f0c090 10.6em,
    #f0d78c 11.4em,
    #d4b85c 12.2em,
    #a8c46a 13em,
    #8ec9b8 13.8em,
    #6ab3a3 14.6em,
    #b3ddd0 15.3em,
    #f5f0e4 16em
  );
  background-size: calc(100% + 17.654em) 100%;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
          background-clip: text;
  animation: zzn-hero-grad-loop 12s linear infinite;
}
@keyframes zzn-hero-grad-loop {
  from { background-position: 0 0; }
  to   { background-position: -17.654em 0; }
}
@media (prefers-reduced-motion: reduce) {
  .zzn-home-main-hero .gradient-span.zzn-grad-move { animation: none; }
}

/* Homepage hero headline: fluid instead of Webflow's fixed steps.
   Webflow sets .giant-heading.start to 80px with no small-screen rule of
   its own — its ≤991px rule targets the bare .giant-heading, which the
   .start compound outranks. So the hero stayed at 80px down to 390px and
   ran off the side. One clamp replaces all three breakpoints; the top end
   matches the old ≥1920px size. */
.giant-heading.start {
  /* Capped at 7.5rem, not 10rem: the container stops widening at 1440px, so
     past that the type has no more room to earn — it was still growing to
     160px on a 2560 screen and swamping a line that no longer got longer.
     8.2vw reaches 7.5rem at ~1460px, right where the container caps, so the
     two stop growing together. */
  font-size: clamp(2.5rem, 8.2vw, 7.5rem);
  line-height: 1.04;
}
/* Grid and flex items default to min-width:auto, i.e. "never shrink below
   your content". The hero heading sits three levels inside such items, so a
   long word pushed .centered-intro wider than its own .wrapper instead of
   wrapping — 515px of content in a 389px column at 480px viewport. */
.zzn-home-main-hero .centered-intro,
.zzn-home-main-hero .clip { min-width: 0; }

/* Hero paragraph shares the headline's left edge. Webflow centres
   .clip.top, which parked the copy 260px inside the heading it belongs to
   — the framing on /about has the sub-line starting on the same vertical
   as the headline above it. */
.zzn-home-main-hero .clip.top { justify-content: flex-start; }

/* Webflow sizes the two halves of this headline independently of the
   heading and of each other — .gradient-span at 50/90/160px per breakpoint,
   .bold-text at 50/90/160px, plus line-height:.6em on .bold-text below
   479px. So the plain words and the gradient words rendered at different
   sizes at every width, and the squashed leading made the lines overlap.
   Both now inherit, so the single clamp above governs the whole headline. */
.giant-heading.start .gradient-span,
.giant-heading.start .bold-text {
  font-size: inherit;
  line-height: inherit;
}
/* Inline-block so the gradient resolves per word rather than per line box;
   capped so a long word wraps to its own line instead of overflowing. */
.giant-heading.start .gradient-span { max-width: 100%; }

/* Mobile-only line break to pull "ist" down onto the "käuflich" line,
   shortening each line so the headline can scale larger. */
.zzn-hero-br-mobile { display: none; }

/* Wide screens: keep the headline at exactly two lines
   ("Unser Geschmack" / "ist käuflich") — the break after "Geschmack" is
   always on, and the first line is kept together so it never wraps
   word-by-word into a ragged third line. */
.zzn-hero-br-desktop { display: block; }
@media screen and (min-width: 768px) {
  .zzn-hero-line-1 { white-space: nowrap; }
}

/* Mobile: Webflow resets .gradient-span to the static JPG and a 1px
   line-height, which hides the animated text. Force the gradient and
   a readable line-height back on small screens. */
@media screen and (max-width: 767px) {
  .gradient-span.zzn-grad-move,
  .zzn-grad-move {
    -webkit-text-fill-color: transparent;
    color: transparent;
    -webkit-background-clip: text;
            background-clip: text;
    line-height: 1.1;
    opacity: 1;
  }

  /* Bigger hero headline on mobile: three short lines
     ("Unser" / "Geschmack" / "ist käuflich") scale up without overflowing.
     The floor was 2.6rem, which only ever mattered below ~385px (10.4vw
     crosses it there) — but at the narrowest real phones (320px) the
     nowrap "ist käuflich" line fits at most 2.55rem before it outgrows its
     column, so the floor was forcing it past that on every ~320-360px
     device. 2.35rem leaves a safety margin rather than sitting right on
     the measured edge. */
  .giant-heading.start {
    font-size: clamp(2.35rem, 10.4vw, 5.2rem);
    line-height: 1.02;
  }
  .zzn-hero-br-mobile { display: block; }
  .zzn-hero-nowrap-mobile { white-space: nowrap; }
}

/* Mobile: full-bleed the projects backdrop across the viewport while keeping
   content comfortably inset. width:100vw + the calc margins break out of any
   constrained wrapper without causing horizontal overflow. */
@media (max-width: 767px) {
  .zzn-about-projects--home {
    width: 100vw;
    max-width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    /* Inline padding stays 0 here too — the head and wall inside carry the
       page container, and 1.25rem on top of it put the heading at 40px on a
       390 screen while the rest of the page sat at 20. */
    padding: 3.5rem 0 4.5rem;
    box-sizing: border-box;
  }
  .zzn-about-projects__title {
    font-size: clamp(2rem, 9vw, 3rem) !important;
    line-height: 1.05 !important;
    word-break: break-word;
  }
}


/* ============================================================
   Technik mieten page — /rental
   Mirrors the project wall's editorial photography, square
   rules and restrained homepage palette. The cart and modal
   below remain the existing functional layer.
   ============================================================ */
.zzn-rental-section {
  background: #fff;
  color: #0a0a0a;
  padding: clamp(8rem, 14vw, 12rem) 0 clamp(7rem, 12vw, 10rem);
}
body:has(.zzn-rental-section) .menu-button,
body:has(.zzn-rental-section) .menu-button-text,
body:has(.zzn-rental-section) .zzn-lang__toggle,
body[data-page="projekt"] .menu-button,
body[data-page="projekt"] .menu-button-text,
body[data-page="projekt"] .zzn-lang__toggle {
  color: #002632 !important;
  -webkit-text-fill-color: #002632 !important;
}
body:has(.zzn-rental-section) .menu-button:hover,
body:has(.zzn-rental-section) .zzn-lang__toggle:hover,
body[data-page="projekt"] .menu-button:hover,
body[data-page="projekt"] .zzn-lang__toggle:hover {
  background: rgba(0, 38, 50, 0.08) !important;
}
/* Menu open: the bar now sits on the black overlay, so everything forced dark
   above goes back to white. The label and DE carry their own forced colour
   (they are listed above in their own right), so turning only the button
   white left "SCHLIESSEN" and DE dark-on-black. */
body:has(.zzn-rental-section) .nav-bar:has(.menu-button.w--open) :is(.menu-button, .menu-button-text, .zzn-lang__toggle),
body[data-page="projekt"] .nav-bar:has(.menu-button.w--open) :is(.menu-button, .menu-button-text, .zzn-lang__toggle) {
  color: #fff !important;
  -webkit-text-fill-color: #fff !important;
  background: transparent !important;
}
/* The nav text above is forced dark for this page's white hero, but the fixed
   header then scrolls over the wall of #002632 cards, where dark-on-dark left
   the logo, DE and MENU at ~1.2:1. Webflow grows .navigation-cover from 0% to
   100% height on scroll (inline style, no class to hook), so repainting the
   cover light gives the dark text a ground wherever it actually appears — and
   stays inert at the top, where the cover has no height. */
body:has(.zzn-rental-section) .navigation-cover {
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(255, 255, 255, 0.84)) !important;
  box-shadow:
    inset 0 -1px 0 rgba(0, 0, 0, 0.1),
    0 12px 36px -14px rgba(0, 0, 0, 0.18) !important;
}
.zzn-rental__wrap {
  --rental-pad: clamp(1.25rem, 4vw, 3.5rem);
  --rental-measure: calc(1440px - 2 * var(--rental-pad));
  max-width: var(--rental-measure);
  margin: 0 auto;
  padding: 0 var(--rental-pad);
  box-sizing: content-box;
}
.zzn-rental__hero-head {
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) minmax(18rem, .55fr);
  column-gap: clamp(2rem, 8vw, 8rem);
  align-items: end;
  padding-bottom: clamp(3rem, 7vw, 6rem);
}

.zzn-rental__title-hero {
  margin: 0;
  font-family: 'Heronsans', 'Inter', system-ui, sans-serif;
  font-size: clamp(2.8rem, 6.2vw, 5.7rem);
  font-weight: 700;
  /* Uppercase German needs room for the diaeresis: at 1.02 the Ü of "FÜR"
     collided with the Q of "EQUIPMENT." on the line above once the headline
     wrapped to four lines on a phone. */
  line-height: 1.1;
  letter-spacing: -.025em;
  text-transform: uppercase;
  color: #0a0a0a;
}
.zzn-rental__lede {
  margin: 0;
  max-width: 42ch;
  font-family: 'Eudoxussans', 'Inter', system-ui, sans-serif;
  font-size: clamp(.95rem, 1.15vw, 1.08rem);
  font-weight: 400;
  line-height: 1.65;
  color: rgba(0,0,0,.62);
}
.zzn-rental__nav {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: .45rem;
  margin-top: clamp(2rem, 4vw, 3rem);
}
.zzn-rental__chip {
  padding: .55rem .85rem;
  border: 1px solid rgba(0,0,0,.22);
  border-radius: 0;
  color: #0a0a0a;
  background: transparent;
  font-family: 'Eudoxussans', sans-serif;
  font-size: .65rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  text-decoration: none;
  transition: color .25s ease, background-color .25s ease;
}
.zzn-rental__chip:hover { color: #fff; background: #002632; }
.zzn-rental__cat { margin-bottom: clamp(5rem, 10vw, 9rem); scroll-margin-top: 7rem; }
.zzn-rental__cat-head {
  padding: 0 0 clamp(1.4rem, 3vw, 2.2rem);
  margin-bottom: clamp(1.5rem, 3vw, 2.4rem);
  border-bottom: 1px solid rgba(0,0,0,.16);
}
.zzn-rental__cat-title {
  margin: 0;
  color: #0a0a0a;
  font-family: 'Heronsans', 'Inter', system-ui, sans-serif;
  font-size: clamp(1.9rem, 4vw, 3.2rem);
  font-weight: 700;
  line-height: .95;
  letter-spacing: -.01em;
  text-transform: uppercase;
}
.zzn-rental__wall {
  display: grid;
  grid-auto-flow: dense;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.25rem;
  max-width: 100%;
}
.zzn-rental__card {
  min-width: 0;
  border: 1px solid rgba(0,0,0,.16);
  background: #fff;
  color: #0a0a0a;
  scroll-margin-top: 7rem;
}
.zzn-rental__card--wide { grid-column: span 2; }
.zzn-rental__media {
  position: relative;
  min-height: clamp(300px, 31vw, 470px);
  overflow: hidden;
  isolation: isolate;
  background: #002632;
}
.zzn-rental__hero { position: absolute; inset: 0; }
.zzn-rental__hero img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .8s cubic-bezier(.16,1,.3,1), filter .5s; }
.zzn-rental__veil {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg, rgba(0,20,28,.02) 22%, rgba(0,20,28,.3) 52%, rgba(0,20,28,.95) 100%);
  pointer-events: none;
}
.zzn-rental__placeholder {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  padding: 2rem;
  color: rgba(255,255,255,.3);
  background: #002632;
}
.zzn-rental__placeholder-icon { display: block; width: clamp(74px, 9vw, 116px); }
.zzn-rental__placeholder-icon svg {
  display: block;
  width: 100%;
  height: auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: square;
  stroke-linejoin: miter;
}

.zzn-rental__body {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  padding: 1.45rem 1.5rem;
  color: #fff;
}

.zzn-rental__head { display: flex; align-items: end; justify-content: space-between; gap: 1rem; }
.zzn-rental__title { margin: 0; color: #fff; font-family: 'Heronsans', sans-serif; font-size: clamp(1.3rem, 2vw, 2rem); font-weight: 700; line-height: 1; text-transform: uppercase; }
.zzn-rental__price { flex: 0 0 auto; text-align: right; white-space: nowrap; }
.zzn-rental__price-value { color: #fff; font-family: 'Heronsans', sans-serif; font-size: 1.1rem; font-weight: 600; }
.zzn-rental__price-unit { display: block; margin-top: 2px; color: rgba(255,255,255,.62); font-family: 'Eudoxussans', sans-serif; font-size: .62rem; letter-spacing: .12em; text-transform: uppercase; }
.zzn-rental__notes { margin-top: .65rem; color: rgba(255,255,255,.7); font-family: 'Eudoxussans', sans-serif; font-size: .78rem; }
/* The view switcher lives inside the details panel, below the copy — not
   floating over the photograph, where it covered the product and gave no way
   back to the first view. */
.zzn-rental__shots { margin-top: 1.4rem; }
.zzn-rental__thumbs { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .6rem; }
.zzn-rental__thumb { width: 60px; height: 60px; padding: 0; overflow: hidden; border: 1px solid rgba(0,0,0,.18); border-radius: 0; background: #f2f0ec; cursor: pointer; transition: border-color .2s ease, opacity .2s ease; opacity: .75; }
.zzn-rental__thumb:hover { opacity: 1; border-color: rgba(0,0,0,.45); }
.zzn-rental__thumb.is-active { opacity: 1; border-color: #002632; border-width: 2px; }
.zzn-rental__thumb:focus-visible { outline: 2px solid #002632; outline-offset: 2px; }
.zzn-rental__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.zzn-rental__card:hover .zzn-rental__hero img { transform: scale(1.05); filter: saturate(1.05); }
.zzn-rental__lower {
  display: grid;
  grid-template-columns: 1fr;
  align-items: start;
  gap: 1.4rem;
  padding: 1.15rem 1.1rem 1.4rem;
}
.zzn-rental__details { min-width: 0; }
.zzn-rental__details > summary { display: inline-flex; align-items: center; gap: .55rem; list-style: none; cursor: pointer; color: rgba(0,0,0,.68); font-family: 'Eudoxussans', sans-serif; font-size: .72rem; font-weight: 600; letter-spacing: .11em; text-transform: uppercase; }
.zzn-rental__details > summary::-webkit-details-marker { display: none; }
.zzn-rental__details > summary svg { transition: transform .3s ease; }
.zzn-rental__details[open] > summary svg { transform: rotate(90deg); }
.zzn-rental__details-body { padding: 1.25rem 0 .25rem; max-width: 56ch; }
.zzn-rental__desc { margin: 0 0 1rem; color: rgba(0,0,0,.65); font-family: 'Eudoxussans', sans-serif; font-size: .9rem; line-height: 1.55; }
.zzn-rental__specs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .65rem 1rem; margin: 0 0 1rem; padding: 1rem 0; border-top: 1px solid rgba(0,0,0,.12); border-bottom: 1px solid rgba(0,0,0,.12); }
.zzn-rental__specs > div { display: flex; flex-direction: column; gap: 2px; }
.zzn-rental__specs dt, .zzn-rental__block-label { color: rgba(0,0,0,.45); font-family: 'Eudoxussans', sans-serif; font-size: .64rem; letter-spacing: .12em; text-transform: uppercase; }
.zzn-rental__specs dd { margin: 0; color: #0a0a0a; font-family: 'Eudoxussans', sans-serif; font-size: .86rem; }
.zzn-rental__list { display: grid; gap: .25rem; margin: .45rem 0 0; padding: 0; list-style: none; }
.zzn-rental__list li { color: rgba(0,0,0,.68); font-family: 'Eudoxussans', sans-serif; font-size: .86rem; }
.zzn-rental__actions {
  position: relative;
  justify-self: stretch;
  width: calc(100% - 6px);
  margin: 6px 0 0 6px;
}
.zzn-rental__control {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  min-height: 50px;
  background: #002632;
  border: 1px solid #002632;
  color: #fff;
  transform: translate(-6px,-6px);
  transition: transform .25s cubic-bezier(.16,1,.3,1), background-color .25s ease, border-color .25s ease;
}
.zzn-rental__cta-minus {
  grid-column: 1;
  align-self: stretch;
  width: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  border-right: 1px solid rgba(255,255,255,.25);
  color: inherit;
  cursor: pointer;
  opacity: .75;
  transition: opacity 0.2s ease;
}
.zzn-rental__cta-minus[hidden] { display: none; }
.zzn-rental__cta-minus:hover { opacity: 1; }
.zzn-rental__cta-minus:active { transform: scale(0.95); }
.zzn-rental__actions::before {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid #0a0a0a;
  pointer-events: none;
}
.zzn-rental__actions .zzn-rental__cta {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: .55rem;
  /* Pinned to the second track. The minus is display:none until something is
     in the cart, and without an explicit column the CTA then auto-placed into
     track 1 ("auto", so content width) and left the 1fr track empty beside it —
     roughly 40% of the visible button did nothing when clicked. */
  grid-column: 2;
  width: 100%;
  min-height: 50px;
  padding: .95rem 1.4rem;
  background: transparent;
  border: 0;
  border-radius: 0;
  color: inherit;
  cursor: pointer;
  font-family: 'Heronsans', 'Eudoxussans', sans-serif;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .045em;
  text-transform: uppercase;
  text-align: left;
}
.zzn-rental__actions:has(.zzn-rental__cta:not([disabled])) .zzn-rental__control:hover { transform: translate(0,0); background: #d24410; border-color: #d24410; }
.zzn-rental__actions .zzn-rental__cta:focus-visible,
.zzn-rental__cta-minus:focus-visible { outline: 2px solid #d24410; outline-offset: -4px; }
.zzn-rental__card.is-added .zzn-rental__control { background: #d24410; border-color: #d24410; }
.zzn-rental__card.is-added .zzn-rental__cta[disabled] { color: rgba(255,255,255,.78); cursor: default; }
.zzn-rental__control:has(.zzn-rental__cta[data-available="false"]) { background: #eee; border-color: #ddd; color: rgba(0,0,0,.42); }
.zzn-rental__cta[disabled] svg { opacity: .35; }
/* At the stock cap the "+" is disabled, so hide it rather than dim a glyph that
   cannot do anything, and let the "n× ausgewählt" label carry the state. */
.zzn-rental__card.is-at-max .zzn-rental__cta[disabled] svg { display: none; }
.zzn-rental__card.is-at-max .zzn-rental__control { background: #b8380d; border-color: #b8380d; }
.zzn-rental__foot { margin-top: 1rem; padding-top: 2rem; border-top: 1px solid rgba(0,0,0,.16); color: rgba(0,0,0,.56); font-family: 'Eudoxussans', sans-serif; font-size: .9rem; }
.zzn-rental__foot a { color: #0a0a0a; border-bottom: 1px solid #d24410; }
.zzn-rental__empty { padding: 4rem 1rem; border-top: 1px solid rgba(0,0,0,.16); border-bottom: 1px solid rgba(0,0,0,.16); color: rgba(0,0,0,.55); text-align: center; }
@media (max-width: 960px) {
  .zzn-rental__hero-head { grid-template-columns: 1fr; }
  .zzn-rental__lede { margin-top: 1.5rem; }
  .zzn-rental__wall { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .zzn-rental-section { padding-top: 7rem; }
  .zzn-rental__wall { grid-template-columns: 1fr; }
  .zzn-rental__card--wide { grid-column: span 1; }
  .zzn-rental__media { min-height: 88vw; }
  .zzn-rental__lower { grid-template-columns: 1fr; }
  .zzn-rental__actions, .zzn-rental__cta { justify-self: stretch; width: 100%; }
}

/* Sticky cart bar */
.zzn-rental-bar {
  position: fixed;
  bottom: 1.25rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 90;
  /* A fixed box with `left` set and `right: auto` shrink-to-fits into
     `viewport - left`, i.e. half the screen, which wrapped both the label and
     the CTA on a phone. max-content restores the bar's natural width. */
  width: max-content;
  max-width: calc(100vw - 2rem);
  border-radius: 999px;
  padding: 0.5rem 0.5rem 0.5rem 1.3rem;
  background: rgba(0, 22, 30, 0.85);
  backdrop-filter: blur(16px);
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 20px 60px -12px rgba(0,0,0,0.6);
  animation: zzn-rental-bar-in 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes zzn-rental-bar-in {
  from { opacity: 0; transform: translate(-50%, 20px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}
.zzn-rental-bar__inner {
  display: flex;
  align-items: center;
  gap: 1rem;
  color: #f5f2ea;
}
.zzn-rental-bar__count {
  display: inline-flex;
  min-width: 1.6rem;
  height: 1.6rem;
  padding: 0 0.4rem;
  border-radius: 999px;
  background: linear-gradient(90deg, #cfe89a, #f4b982);
  color: #0f1a1d;
  font-weight: 700;
  font-size: 0.8rem;
  align-items: center;
  justify-content: center;
}
.zzn-rental-bar__label { font-size: 0.9rem; color: rgba(255,255,255,0.8); }
.zzn-rental-bar__open {
  padding: 0.6rem 1.1rem;
  border-radius: 999px;
  border: 0;
  cursor: pointer;
  background: linear-gradient(90deg, #cfe89a, #f4b982);
  color: #0f1a1d;
  font-size: 0.85rem;
  letter-spacing: 0.02em;
  transition: filter 0.2s ease, transform 0.2s ease;
}
.zzn-rental-bar__open:hover { filter: brightness(1.05); transform: translateY(-1px); }

/* Modal */
html.zzn-rental-modal-open { overflow: hidden; }
.zzn-rental-modal[hidden] { display: none !important; }
.zzn-rental-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  animation: zzn-rental-modal-in 0.25s ease;
}
@keyframes zzn-rental-modal-in { from { opacity: 0; } to { opacity: 1; } }
.zzn-rental-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 15, 20, 0.72);
  backdrop-filter: blur(6px);
}
.zzn-rental-modal__panel {
  position: relative;
  width: min(680px, 100%);
  max-height: 85vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  border-radius: 22px;
  background: linear-gradient(160deg, #002632 0%, #0d3a42 100%);
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 40px 80px -20px rgba(0,0,0,0.7);
  color: #f5f2ea;
}
.zzn-rental-modal__details {
  margin-top: 0.75rem;
  border-top: 1px solid rgba(255,255,255,0.08);
  padding-top: 0.75rem;
}
.zzn-rental-modal__details > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.4rem 0;
  font-size: 0.85rem;
  color: rgba(255,255,255,0.75);
  user-select: none;
}
.zzn-rental-modal__details > summary::-webkit-details-marker { display: none; }
.zzn-rental-modal__details > summary::after {
  content: "▾";
  transition: transform 0.2s ease;
  color: rgba(255,255,255,0.5);
}
.zzn-rental-modal__details[open] > summary::after { transform: rotate(180deg); }
.zzn-rental-modal__details-count {
  margin-left: auto;
  padding: 0.1rem 0.55rem;
  border-radius: 999px;
  background: rgba(255,255,255,0.08);
  font-size: 0.72rem;
  color: rgba(255,255,255,0.75);
}
.zzn-rental-modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.2rem 1.6rem;
  border-bottom: 1px solid rgba(255,255,255,0.08);
}
.zzn-rental-modal__head h2 {
  margin: 0;
  font-family: 'Font Bureau Heronsans', 'Heronsans', system-ui, sans-serif;
  font-weight: 400;
  font-size: 1.4rem;
}
.zzn-rental-modal__x {
  background: transparent;
  border: 0;
  color: rgba(255,255,255,0.7);
  font-size: 1.6rem;
  cursor: pointer;
  line-height: 1;
  padding: 0 0.5rem;
}
.zzn-rental-modal__x:hover { color: #fff; }
.zzn-rental-modal__body {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
  padding: 1.25rem 1.4rem;
  overflow: auto;
}
.zzn-rental-modal__cart h3 {
  margin: 0 0 0.8rem;
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.55);
}
.zzn-rental-modal__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.6rem;
  max-height: 42vh;
  overflow: auto;
}
.zzn-rental-modal__empty { color: rgba(255,255,255,0.5); font-size: 0.9rem; padding: 1rem 0; }
.zzn-rental-modal__item {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.3rem 0.75rem;
  padding: 0.75rem 2rem 0.75rem 0.9rem;
  border-radius: 12px;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.06);
}
.zzn-rental-modal__item-name { font-weight: 500; }
.zzn-rental-modal__item-meta { grid-column: 1; font-size: 0.78rem; color: rgba(255,255,255,0.55); }
.zzn-rental-modal__item-qty {
  grid-row: 1 / span 2;
  grid-column: 2;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  align-self: center;
}
.zzn-rental-modal__item-qty button {
  width: 26px; height: 26px;
  border-radius: 999px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
  color: #f5f2ea;
  cursor: pointer;
  line-height: 1;
}
.zzn-rental-modal__item-qty button:hover { background: rgba(255,255,255,0.16); }
.zzn-rental-modal__item-qty span { min-width: 1.5rem; text-align: center; }
.zzn-rental-modal__item-line { grid-column: 1 / span 2; font-size: 0.82rem; color: #f4b982; }
.zzn-rental-modal__item-remove {
  position: absolute;
  top: 6px; right: 8px;
  background: transparent;
  border: 0;
  color: rgba(255,255,255,0.45);
  font-size: 1.1rem;
  cursor: pointer;
  line-height: 1;
}
.zzn-rental-modal__item-remove:hover { color: #ff8a8a; }
.zzn-rental-modal__total {
  display: flex;
  justify-content: space-between;
  padding-top: 0.8rem;
  margin-top: 0.8rem;
  border-top: 1px solid rgba(255,255,255,0.08);
  font-size: 1rem;
  color: #f4b982;
  font-weight: 600;
}
.zzn-rental-modal__total-hint { margin: 0.3rem 0 0; font-size: 0.72rem; color: rgba(255,255,255,0.4); }

.zzn-rental-modal__form { display: grid; gap: 0.75rem; }
.zzn-rental-modal__form label {
  display: grid;
  gap: 0.3rem;
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.55);
}
.zzn-rental-modal__form input,
.zzn-rental-modal__form textarea {
  width: 100%;
  padding: 0.7rem 0.9rem;
  background: rgba(255,255,255,0.05);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 10px;
  color: #f5f2ea;
  font: inherit;
  text-transform: none;
  letter-spacing: normal;
}
.zzn-rental-modal__form input:focus,
.zzn-rental-modal__form textarea:focus {
  outline: none;
  border-color: rgba(244, 185, 130, 0.6);
  box-shadow: 0 0 0 3px rgba(244, 185, 130, 0.18);
}
.zzn-rental-modal__row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
.zzn-rental-modal__consent {
  flex-direction: row;
  align-items: flex-start;
  gap: 0.6rem !important;
  text-transform: none !important;
  letter-spacing: normal !important;
  font-size: 0.8rem !important;
  color: rgba(255,255,255,0.65) !important;
  display: flex !important;
}
.zzn-rental-modal__consent input { width: auto; margin-top: 0.15rem; }
.zzn-rental-modal__consent a { color: #f4b982; }

.zzn-rental-modal__actions { display: flex; gap: 0.6rem; justify-content: flex-end; margin-top: 0.4rem; }
.zzn-rental-modal__ghost,
.zzn-rental-modal__submit {
  padding: 0.7rem 1.2rem;
  border-radius: 999px;
  border: 0;
  cursor: pointer;
  font-size: 0.85rem;
  letter-spacing: 0.02em;
}
.zzn-rental-modal__ghost {
  background: transparent;
  border: 1px solid rgba(255,255,255,0.18);
  color: rgba(255,255,255,0.75);
}
.zzn-rental-modal__ghost:hover { background: rgba(255,255,255,0.05); }
.zzn-rental-modal__submit {
  background: linear-gradient(90deg, #cfe89a, #f4b982);
  color: #0f1a1d;
}
.zzn-rental-modal__submit:hover { filter: brightness(1.05); }
.zzn-rental-modal__submit[disabled] { opacity: 0.7; cursor: wait; }

.zzn-rental-modal__status { margin: 0; font-size: 0.85rem; color: rgba(255,255,255,0.7); min-height: 1.2em; }
.zzn-rental-modal__status.is-ok { color: #cfe89a; }
.zzn-rental-modal__status.is-err { color: #ffb0b0; }

/* Rental-page header logo: the source artwork is white, so reverse it only
   against this page's white header. Restore it while the dark menu is open. */
body:has(.zzn-rental-section) .nav-bar .brand img,
body[data-page="projekt"] .nav-bar .brand img {
  filter: brightness(0);
}
body:has(.zzn-rental-section) .nav-bar:has(.menu-button.w--open) .brand img,
body[data-page="projekt"] .nav-bar:has(.menu-button.w--open) .brand img {
  filter: none;
}

/* Rental inquiry UI — editorial light treatment */
.zzn-rental-bar {
  border-radius: 0;
  background: rgba(255,255,255,.96);
  border-color: rgba(0,0,0,.16);
  box-shadow: 0 18px 48px -18px rgba(0,0,0,.24);
}
.zzn-rental-bar__inner { color: #0a0a0a; }
.zzn-rental-bar__count {
  border-radius: 0;
  background: #d24410;
  color: #fff;
}
.zzn-rental-bar__label { color: rgba(0,0,0,.6); }
.zzn-rental-bar__open {
  border-radius: 0;
  background: #002632;
  color: #fff;
  text-transform: uppercase;
}
.zzn-rental-modal__backdrop { background: rgba(0,38,50,.64); backdrop-filter: blur(8px); }
.zzn-rental-modal__panel {
  border-radius: 0;
  background: #fff;
  border-color: rgba(0,0,0,.16);
  box-shadow: 0 36px 80px -24px rgba(0,0,0,.4);
  color: #0a0a0a;
}
.zzn-rental-modal__head,
.zzn-rental-modal__details,
.zzn-rental-modal__total { border-color: rgba(0,0,0,.16); }
.zzn-rental-modal__head h2 { font-weight: 700; text-transform: uppercase; }
.zzn-rental-modal__x,
.zzn-rental-modal__details > summary,
.zzn-rental-modal__details > summary::after,
.zzn-rental-modal__details-count,
.zzn-rental-modal__cart h3,
.zzn-rental-modal__empty,
.zzn-rental-modal__item-meta,
.zzn-rental-modal__total-hint,
.zzn-rental-modal__form label,
.zzn-rental-modal__status { color: rgba(0,0,0,.62); }
.zzn-rental-modal__x:hover { color: #0a0a0a; }
.zzn-rental-modal__details-count,
.zzn-rental-modal__item,
.zzn-rental-modal__item-qty button,
.zzn-rental-modal__form input,
.zzn-rental-modal__form textarea { background: rgba(0,0,0,.04); border-color: rgba(0,0,0,.14); }
.zzn-rental-modal__item { border-radius: 0; }
.zzn-rental-modal__item-qty button {
  border-radius: 0;
  color: #0a0a0a;
}
.zzn-rental-modal__item-qty button:hover:not([disabled]) { background: rgba(0,0,0,.1); }
.zzn-rental-modal__item-qty button[disabled] { opacity: .35; cursor: not-allowed; }
.zzn-rental-modal__item-remove { color: rgba(0,0,0,.45); }
.zzn-rental-modal__item-line,
.zzn-rental-modal__total { color: #d24410; }
.zzn-rental-modal__form input,
.zzn-rental-modal__form textarea { border-radius: 0; color: #0a0a0a; }
.zzn-rental-modal__consent { color: rgba(0,0,0,.68) !important; }
.zzn-rental-modal__ghost,
.zzn-rental-modal__submit { border-radius: 0; }
.zzn-rental-modal__ghost { border-color: rgba(0,0,0,.2); color: #0a0a0a; }
.zzn-rental-modal__ghost:hover { background: rgba(0,0,0,.05); }
.zzn-rental-modal__submit { background: #002632; color: #fff; text-transform: uppercase; }
.zzn-rental-modal__status.is-ok { color: #364604; }
.zzn-rental-modal__status.is-err { color: #b3340d; }

/* ============================================================
   Contact page — homepage style + soft pink fade
   ============================================================ */
body[data-page="contact"] .about-hero-section,
body[data-page="contact"] .contact-secction {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}
body[data-page="contact"] .about-hero-section {
  background: #002632;
}
body[data-page="contact"] .contact-secction {
  background: #002632;
  padding: 5rem 0 6rem;
}
body[data-page="contact"] .about-hero-section::before,
body[data-page="contact"] .contact-secction::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(45% 45% at 25% 45%, rgba(255, 155, 190, 0.28), rgba(255, 155, 190, 0.08) 55%, transparent 75%),
    radial-gradient(50% 45% at 80% 60%, rgba(210, 68, 16, 0.22), rgba(210, 68, 16, 0.06) 55%, transparent 75%),
    radial-gradient(40% 40% at 55% 50%, rgba(54, 70, 4, 0.26), rgba(54, 70, 4, 0.08) 55%, transparent 75%);
  filter: blur(70px);
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 22%, #000 78%, transparent 100%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 22%, #000 78%, transparent 100%);
  pointer-events: none;
  z-index: 0;
  animation: zzn-home-projects-drift 24s ease-in-out infinite alternate;
}
body[data-page="contact"] .about-hero-section::after,
body[data-page="contact"] .contact-secction::after {
  content: "";
  position: absolute; inset: 0;
  background-image: radial-gradient(rgba(255,255,255,0.035) 1px, transparent 1px);
  background-size: 3px 3px;
  mix-blend-mode: overlay;
  opacity: 0.55;
  pointer-events: none;
  z-index: 0;
}
body[data-page="contact"] .about-hero-section > *,
body[data-page="contact"] .contact-secction > * {
  position: relative;
  z-index: 1;
}
/* Ensure the inner wrapper of the contact section is horizontally centered
   at every breakpoint (Webflow's default leaves it flush-left on tablet/mobile). */
body[data-page="contact"] .contact-secction > .wrapper,
body[data-page="contact"] .about-hero-section > .wrapper {
  margin-left: auto !important;
  margin-right: auto !important;
  max-width: min(1200px, 92vw);
  width: 100%;
  box-sizing: border-box;
}

/* Center the "Sie melden sich" intro text */
body[data-page="contact"] .centered-intro {
  text-align: center;
  justify-items: center;
  margin-left: auto;
  margin-right: auto;
}
body[data-page="contact"] .centered-intro .large-heading,
body[data-page="contact"] .centered-intro .body-display,
body[data-page="contact"] .centered-intro .body-display.large {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
  align-items: center;
}
body[data-page="contact"] .centered-intro .clip {
  display: flex;
  justify-content: center;
  width: 100%;
}

/* Frosted glass form card in homepage aesthetic */
body[data-page="contact"] .form-wrapper {
  position: relative;
  max-width: 880px;
  margin: 2.5rem auto 0;
  padding: 2.5rem clamp(1.5rem, 3vw, 3rem);
  background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.02));
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 24px;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 30px 80px -40px rgba(0,0,0,0.6);
}
body[data-page="contact"] .form-wrapper::before {
  content: "";
  position: absolute; inset: -1px;
  border-radius: 24px;
  padding: 1px;
  background: linear-gradient(120deg, rgba(255,155,190,0.55), rgba(210,68,16,0.35), rgba(54,70,4,0.5));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: 0.55;
  pointer-events: none;
}

body[data-page="contact"] .text-field,
body[data-page="contact"] .text-field.w-input {
  background: rgba(255,255,255,0.04) !important;
  border: 1px solid rgba(255,255,255,0.12) !important;
  color: #fff !important;
  border-radius: 12px !important;
  transition: border-color .2s ease, background .2s ease;
}
body[data-page="contact"] .text-field:focus {
  border-color: rgba(255,155,190,0.55) !important;
  background: rgba(255,255,255,0.06) !important;
  outline: none !important;
}
body[data-page="contact"] .text-field::placeholder { color: rgba(255,255,255,0.4); }
body[data-page="contact"] .field-label { color: rgba(255,255,255,0.75); }

body[data-page="contact"] .submit-button {
  background: linear-gradient(90deg, #ff9bbe 0%, #f4b982 55%, #cfe89a 100%) !important;
  color: #0f1a1d !important;
  border: 0 !important;
  border-radius: 999px !important;
  padding: 0.9rem 1.6rem !important;
  font-weight: 600;
  letter-spacing: 0.02em;
  transition: filter .2s ease, transform .2s ease;
}
body[data-page="contact"] .submit-button:hover { filter: brightness(1.05); transform: translateY(-1px); }

/* About/Projekte: force Kontakt button to sit below paragraph on every breakpoint */
body[data-page="about"] .about-wrapper .half-dual .buttons-wrapper {
  grid-column: 1 / -1 !important;
  grid-row: auto !important;
  justify-self: start !important;
  margin-top: 1.5rem !important;
  display: flex !important;
  opacity: 1 !important;
  transform: none !important;
  width: 100%;
}
body[data-page="about"] .about-wrapper .half-dual .buttons-wrapper .button {
  position: static !important;
  transform: none !important;
  opacity: 1 !important;
}
@media (max-width: 767px) {
  body[data-page="about"] .about-wrapper .half-dual .buttons-wrapper {
    justify-self: center !important;
    justify-content: center !important;
    margin-top: 1.25rem !important;
  }
}

/* About: ensure paragraph + button stack vertically inside .clip */
/* NACHGEMESSEN: ab 1920px stand die Headline 417px weiter rechts als der
   Absatz darunter. Der Webflow-Export schaltet `.about-wrapper` in
   `@media (min-width: 1920px)` von Flex auf `display: block` — damit ist
   `> .clip` kein schrumpfendes Flex-Kind mehr, sondern volle 1450px breit,
   und weil `.clip` seinen Inhalt zentriert (justify-content: center), landete
   die 618px breite Headline in der Mitte: genau die halbe Restbreite von
   416px. Unter 1920px fiel es nicht auf, weil der Kasten dort so breit ist
   wie sein Inhalt. */
body[data-page="about"] .about-wrapper > .clip {
  justify-content: flex-start;
}
@media screen and (min-width: 1920px) {
  /* Und der Kasten darf nicht länger von der Textbreite leben.
     GEMESSEN: `.section` ist eine Flex-SPALTE mit `align-items: center`, das
     `.wrapper` darin also ein schrumpfendes Kind — seine Breite kam aus dem
     längsten Inhalt, und das war der ungekappte Absatz mit 1450px. Sobald
     der ein Textmaß bekam, schrumpfte der ganze Block auf 808px und wurde
     mittig neu gesetzt: Überschrift und Absatz sprangen 402px nach rechts.

     Die 1450px, die der Kasten vorher hatte, waren selbst ein Zufallswert
     aus dem Inhalt — `.wrapper` ist ab 1920 `width: auto` in einer Flex-
     SPALTE mit `align-items: center`, schrumpft also auf seinen längsten
     Inhalt. Eine Breite, die aus einer ungekappten Zeilenlänge kommt, ist
     keine Breite, auf die man zurückwill.

     Deshalb auf den Container der Seite: der Hero läuft an jeder Breite
     über 1328px, mittig (GEMESSEN x=56 bei 1440, 296 bei 1920, 616 bei
     2560). Der Block steht damit erstmals auf derselben Achse wie der Kopf
     der Seite, statt auf einer eigenen.

     Nur ab 1920, denn erst dort ist `.about-wrapper` ein `display: block` —
     darunter ist es Flex mit `space-between`, und dort stimmt die Breite
     schon. */
  body[data-page="about"] .wrapper:has(> .about-wrapper) {
    width: min(100%, 1328px);
  }
}
body[data-page="about"] .about-wrapper .half-dual > .clip {
  display: block !important;
  width: 100%;
}
/* `max-width` steht hier NICHT mehr auf 100%. Es war dazu da, das Seitenmaß
   `.section .body-display.large { max-width: 60ch }` aufzuheben — und genau
   das war der Fehler: GEMESSEN lief der Absatz dadurch bei 1920 und 2560px
   über 1450px, also 146 Zeichen pro Zeile. Fließtext ist bis etwa 75 lesbar,
   danach findet das Auge am Zeilenende die nächste Zeile nicht mehr
   zuverlässig. Ohne die Zeile trägt wieder das Maß der Seite, dasselbe wie
   bei jedem anderen `body-display` — kein zweiter Wert nur für diese Stelle.
   `width: 100%` bleibt: der Absatz muss ein normaler Block im auf `block`
   gestellten `.clip` sein, sonst greift die Kappung an der falschen Achse. */
body[data-page="about"] .about-wrapper .half-dual > .clip > p {
  display: block !important;
  width: 100% !important;
}

/* ========== BTS Pinterest Gallery ========== */
.zzn-bts {
  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  /* Inline padding moved onto the children, which now carry the page
     container themselves — with it here the head was left-bound against
     the section (48px at every width) instead of following the measure. */
  padding: clamp(4rem, 8vw, 8rem) 0 0;
  overflow: hidden;
  isolation: isolate;
  background: #000;
  border-top: 1px solid rgba(255,255,255,0.22);
}
.zzn-bts__bg {
  position: absolute; inset: 0 0 25% 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(60% 45% at 15% 10%, rgba(232, 148, 90, 0.07), transparent 70%),
    radial-gradient(55% 40% at 88% 20%, rgba(90, 168, 232, 0.08), transparent 72%);
  filter: blur(20px);
  animation: zzn-bts-drift 28s ease-in-out infinite alternate;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 18%, #000 82%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 18%, #000 82%, transparent 100%);
}
@keyframes zzn-bts-drift {
  0%   { transform: translate3d(-2%, -1%, 0) scale(1.02); }
  100% { transform: translate3d(2%, 1.5%, 0) scale(1.06); }
}
.zzn-bts__head {
  max-width: var(--zzn-max);
  margin: 0 auto clamp(2.5rem, 5vw, 4rem);
  padding-inline: var(--zzn-pad);
  box-sizing: border-box;
  text-align: left;
}
.zzn-bts__kicker {
  font-family: 'Font Bureau Heronsans', 'Inter', system-ui, sans-serif;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  font-size: 0.75rem;
  color: rgba(255,255,255,0.55);
  margin-bottom: 1rem;
}
.zzn-bts__title {
  /* Nothing follows the title in the head any more: no bottom margin. */
  margin: 0;
  font-family: 'Font Bureau Heronsans', 'Inter', system-ui, sans-serif;
  font-weight: 800;
  text-transform: uppercase;
  line-height: 0.95;
  letter-spacing: -0.01em;
  font-size: clamp(2.4rem, 7vw, 5.75rem);
  color: #fff;
  overflow-wrap: break-word;
  word-break: break-word;
}
.zzn-bts__title .zzn-fade-word { display: inline-block; }

/* A CSS Grid of fixed-height cells, not column-count masonry. The masonry
   version balanced items across columns based on each image's *loaded*
   height — every lazy-loaded image that finished decoding after the
   browser had already balanced the columns forced a re-balance, which
   could shuffle an image the reader was already looking at into a
   different column (or off the bottom of the collapsed view entirely) —
   read as images randomly glitching out of view mid-scroll. A grid with
   explicit row spans lays out from geometry alone, before any image has
   loaded, and never recomputes because one finished decoding later. */
.zzn-bts__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 220px;
  grid-auto-flow: dense;
  gap: 1.25rem;
  max-width: 1400px;
  margin: 0 auto;
}
.zzn-bts__item--tall { grid-row: span 2; }
@media (max-width: 991px) {
  .zzn-bts__grid { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 200px; }
}
@media (max-width: 560px) {
  .zzn-bts__grid { grid-template-columns: 1fr; gap: 1rem; grid-auto-rows: 260px; }
}

.zzn-bts__item {
  display: block;
  border-radius: 0;
  overflow: hidden;
  position: relative;
  background: rgba(255,255,255,0.04);
  box-shadow: 0 12px 40px -20px rgba(0,0,0,0.55);
  transform: translateZ(0);
  transition: transform 620ms cubic-bezier(0.22, 1, 0.36, 1),
              box-shadow 620ms cubic-bezier(0.22, 1, 0.36, 1);
}
.zzn-bts__item::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08);
  pointer-events: none;
  transition: box-shadow 480ms ease;
}
.zzn-bts__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 900ms cubic-bezier(0.22, 1, 0.36, 1),
              filter 600ms ease;
  filter: saturate(0.92);
}
.zzn-bts__item:hover {
  transform: translateY(-4px);
  box-shadow: 0 24px 60px -22px rgba(0,0,0,0.7);
}
.zzn-bts__item:hover img {
  transform: scale(1.045);
  filter: saturate(1.08);
}
.zzn-bts__item:hover::after {
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.22);
}

/* Collapsible reveal with fade-to-bg + "Mehr davon" toggle */
.zzn-bts__reveal {
  position: relative;
  max-width: var(--zzn-max);
  margin: 0 auto;
  padding-inline: var(--zzn-pad);
  box-sizing: border-box;
  max-height: 66vh;
  overflow: hidden;
  transition: max-height 700ms cubic-bezier(0.22, 1, 0.36, 1),
              padding-bottom 700ms cubic-bezier(0.22, 1, 0.36, 1);
}
.zzn-bts__toggle:checked ~ .zzn-bts__reveal {
  max-height: 12000px;
  /* Room for the button below the grid once expanded — collapsed it floats
     over the fade, which is the point; expanded it would sit on the last
     photo. */
  padding-bottom: 6.5rem;
}
.zzn-bts__fade {
  pointer-events: none;
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 42%;
  background: linear-gradient(to bottom,
    rgba(0, 0, 0, 0) 0%,
    #000 78%,
    #000 100%);
  transition: opacity 500ms ease;
}
.zzn-bts__toggle:checked ~ .zzn-bts__reveal .zzn-bts__fade { opacity: 0; }

/* Square, like every other door on the site ("Direkt Kontakt"): one white
   line, no radius, no glass. The fill is solid black so the label stays
   legible where the button sits over the fade into the photos. */
.zzn-bts__more {
  position: absolute;
  left: 50%;
  bottom: 2.25rem;
  transform: translateX(-50%);
  z-index: 3;
  cursor: pointer;
  padding: 1.05rem 2.4rem;
  border-radius: 0;
  background: #000;
  border: 1px solid #fff;
  color: #fff;
  font-family: 'Font Bureau Heronsans', sans-serif;
  font-weight: 700;
  font-size: 0.95rem;
  line-height: 1;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: background-color 200ms ease, color 200ms ease;
}
.zzn-bts__more:hover {
  background: #fff;
  color: #000;
}
@media (prefers-reduced-motion: reduce) {
  .zzn-bts__more { transition: none; }
}
.zzn-bts__more-label::before { content: attr(data-more); }
.zzn-bts__toggle:checked ~ .zzn-bts__reveal .zzn-bts__more-label::before { content: attr(data-less); }

/* ============================================================
   Team page — headline sizing is defined inline in team.html
   (responsive clamps per breakpoint) so no override here.
   ============================================================ */


/* ============================================================
   Team page — orange ambient glow (single continuous wash)
   ============================================================ */
body[data-page="team"] {
  background: #002632;
  position: relative;
}

/* One fixed, full-viewport orange wash behind everything — no seams */
body[data-page="team"]::before {
  content: "";
  position: fixed;
  inset: 0;
  background:
    radial-gradient(70% 60% at 60% 30%, rgba(210, 68, 16, 0.55) 0%, rgba(210, 68, 16, 0.18) 50%, transparent 80%),
    radial-gradient(65% 65% at 20% 60%, rgba(210, 68, 16, 0.42) 0%, rgba(210, 68, 16, 0.08) 55%, transparent 82%),
    radial-gradient(55% 55% at 85% 75%, rgba(210, 68, 16, 0.32) 0%, transparent 72%),
    radial-gradient(50% 50% at 45% 90%, rgba(255, 155, 190, 0.10) 0%, transparent 75%);
  filter: blur(80px);
  pointer-events: none;
  z-index: 0;
  animation: zzn-home-projects-drift 26s ease-in-out infinite alternate;
}

/* Subtle noise overlay for texture */
body[data-page="team"]::after {
  content: "";
  position: fixed; inset: 0;
  background-image: radial-gradient(rgba(255,255,255,0.035) 1px, transparent 1px);
  background-size: 3px 3px;
  mix-blend-mode: overlay;
  opacity: 0.5;
  pointer-events: none;
  z-index: 0;
}

/* Sections themselves must be transparent so the fixed wash shows through */
body[data-page="team"] .zzn-team-hero,
body[data-page="team"] .zzn-team-care,
body[data-page="team"] .no-top-padding {
  background: transparent !important;
  position: relative;
  z-index: 1;
}

/* Keep content above the wash */
body[data-page="team"] .zzn-team-hero > .wrapper,
body[data-page="team"] .zzn-team-care > .wrapper,
body[data-page="team"] .no-top-padding > .wrapper {
  position: relative;
  z-index: 1;
}

/* CTA section — remove the default blue cover so the orange wash shows through */
body[data-page="team"] .no-top-padding > .background-shade {
  position: absolute !important;
  inset: 0 !important;
  z-index: 0 !important;
  background: transparent !important;
}
body[data-page="team"] .no-top-padding > .background-shade .background-cover {
  background: transparent !important;
}


/* ============================================================
   Hero 3D room outline (public/js/hero-room.js)
   ============================================================ */
.zzn-home-main-hero {
  isolation: isolate;
  overflow: hidden;
}

/* Explicit local stack for the real, visible hero. */
.zzn-home-main-hero > .hero-div-bg { z-index: 0; }
.zzn-home-main-hero > .background_fade { z-index: 1; }
.zzn-home-main-hero > .wrapper { z-index: 3; }

/* Hero on the page container. Webflow's .wrapper is `width: 90%`, which
   keeps widening forever — the hero measured 1166px at 1440 but 2304px at
   2560, its left edge wandering 137 -> 96 -> 128px while every framed
   section sat at 56 -> 296 -> 616. */
.zzn-home-main-hero > .wrapper {
  width: 100%;
  max-width: var(--zzn-max);
  margin-inline: auto;
  padding-inline: var(--zzn-pad);
  box-sizing: border-box;
}

.zzn-hero-stage {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 2;
  overflow: hidden;
  opacity: 0;
  transition: opacity 0.6s ease;
}
.zzn-hero-stage.is-visible { opacity: 1; }

.zzn-hero-room {
  width: 100%;
  height: 100%;
  display: block;
  mix-blend-mode: screen;
  opacity: 0.6;
  -webkit-mask-image: linear-gradient(to bottom, #000 65%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 65%, transparent 100%);
}

/* Room for the goat, and the orange project band in the first view. In
   landscape the hero ends at 83% of the screen, so the band shows across
   the bottom of the first view; hero-room.js still lays the goat out in a
   room 110% of the screen tall (the hero's old height), so it keeps its size
   and its legs run into the canvas's bottom fade. On a portrait tablet the
   hero is 85%, which shows the band too. Phones are untouched here: the goat
   is width-bound there. svh, so a collapsing toolbar on a tablet doesn't
   resize the hero (and refit the goat) mid-scroll.
   The spacing that stacked around the copy — 150px section padding plus a
   150px margin above it, 100 + 60px below — would alone have filled most of
   the 83%, so in landscape it is measured from the fixed nav (100px tall)
   and the screen height instead, and the hero centres the copy in what is
   left. A short window still has to fit both headline lines: there the
   headline is capped at 14% of the screen height (it only bites below about
   860px of height — 88px instead of 120px at 1766x630). */
@media (min-width: 768px) and (orientation: landscape) {
  .section.zzn-home-main-hero {
    min-height: 83vh;
    min-height: 83svh;
    padding-top: calc(100px + clamp(24px, 4svh, 56px));
    padding-bottom: clamp(24px, 4svh, 48px);
  }
  .zzn-home-main-hero .centered-intro { margin-top: 0; margin-bottom: 0; }
  .zzn-home-main-hero .giant-heading.start {
    font-size: min(clamp(2.5rem, 8.2vw, 7.5rem), 14svh);
  }
}
@media (min-width: 768px) and (orientation: portrait) {
  .section.zzn-home-main-hero { min-height: 85vh; min-height: 85svh; }
}

/* Phone: room above the headline for the goat's head. hero-room.js sizes the
   goat to the screen width and starts it just below the nav, so the gap grows
   with the width too (Webflow's own margin here is a flat 100px). */
@media (max-width: 767px) {
  .zzn-home-main-hero .centered-intro { margin-top: calc(100px + 27.5vw); }
  /* The goat spans the column there, so it sits behind the copy: full
     strength for the head above the headline, then fading through the
     headline to a trace behind the paragraph, which is small enough type to
     lose against it. The two stops are the headline's and the paragraph's
     tops, written by hero-room.js because they move with the phone width. */
  .zzn-hero-room {
    -webkit-mask-image: linear-gradient(to bottom,
      #000 var(--zzn-fade-from, 34%),
      rgba(0, 0, 0, 0.18) var(--zzn-fade-to, 55%),
      rgba(0, 0, 0, 0.1) 85%,
      transparent 100%);
            mask-image: linear-gradient(to bottom,
      #000 var(--zzn-fade-from, 34%),
      rgba(0, 0, 0, 0.18) var(--zzn-fade-to, 55%),
      rgba(0, 0, 0, 0.1) 85%,
      transparent 100%);
  }
}
.zzn-hero-video-slot {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.8s ease;
  pointer-events: none;
}
.zzn-hero-stage[data-mode="video"] .zzn-hero-video-slot { opacity: 1; }
.zzn-hero-stage[data-mode="video"] .zzn-hero-room { opacity: 0; }

/* === ZZN DOT TRANSITION + BW ZONE ==================================
 * The merge lives on one full-bleed canvas (.zzn-field) painted from JS, and
 * the two names on one SVG above it. CSS sizes neither figure: the field is
 * scaled in JS against the type, because the contour the names ride has to
 * pass exactly through the point the headline word already sits at.
 * --zzn-bg is written from JS at init (sampled from the real page bg). */

.zzn-dot-stage {
  position: relative;
  /* Entry colour must equal the section above, or the top seam shows a step.
     GSAP drives this to black and then to white. */
  background: var(--zzn-bg, #002632);
  margin-bottom: -1px;
  border: 0;
  outline: 0;
  box-shadow: none;
  /* One viewport of content plus the scroll distance the timeline needs.
     --zzn-stage-len is set by zzn-dot-transition.js (shorter on touch). svh,
     not vh, so the iOS URL bar showing or hiding cannot resize the stage
     mid-scroll and shift every trigger under the reader. */
  height: calc(100svh * var(--zzn-stage-len, 4));
}
.zzn-dot-stage + * {
  position: relative;
  z-index: 1;
}
.zzn-dot-sticky {
  /* Native sticky, deliberately: it is the ONLY thing holding this element
     on screen. GSAP no longer pins the stage — see zzn-dot-transition.js.
     The browser sticks this on the compositor, so it stays put even when
     the main thread is throttled (iOS Low Power Mode), which is exactly
     when GSAP's transform-based pin used to lag a frame behind the scroll
     and wobble. Two systems holding the same element is what caused the
     original desync; now there is only one, and it is the fast one. */
  position: sticky;
  top: 0;
  /* lvh, not svh: the panel must cover the LARGEST viewport (URL bar hidden)
     or a strip of the page shows below it while the bar is retracting, which
     reads as the stage sliding. lvh never changes, so nothing resizes
     mid-scroll either. Falls back to svh where lvh is unsupported. */
  height: 100svh;
  height: 100lvh;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  outline: 0;
  box-shadow: none;
  overflow: hidden;
  isolation: isolate;
  /* iOS Safari: promote to its own compositor layer so scrubbed transforms
     inside don't force a full-page repaint each frame. */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  contain: layout paint;
}

/* Structural centering: zero-size anchor placed by CSS at the true
   viewport center of .zzn-dot-sticky. Never animated, never referenced
   by GSAP. All dots + labels position relative to this point. */
.zzn-center-anchor {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  /* Above .zzn-goo and the sphere skins: the word reads on its own body. */
  z-index: 6;
}

/* Contour field — the whole merge. One canvas, full bleed, painted on demand
   from the scrubbed timeline (see zzn-field.js). Everything that used to live
   in this range — two white dots, an SVG goo filter to fake a neck between
   them, a sphere graticule and a limb-shading gradient to make a flat disc
   read as a body, a stream, a pair of glow orbs — is gone. All of it existed
   to make two solid shapes look like they were merging. The field draws no
   shapes at all: two point sources, and every line on screen is an isoline of
   the one function they define, so the merge is a property of the geometry
   rather than a choreography laid over it. */
.zzn-field {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  display: block;
  pointer-events: none;
}

/* The two names, hung on the contour they belong to. Same face, weight and
   size as the headline lines they peel out of — recomputeGeom copies them off
   .zzn-line--a at layout time — because the hand-over between the flat word
   and the curved label is a cross-fade in place, and any difference in the
   type shows up as a flicker at exactly the moment the reader is looking. */
.zzn-field-labels {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 5;
  overflow: visible;
  pointer-events: none;
}
.zzn-flab {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  /* start, not middle: JS gives every glyph its own absolute x (the glyph's
     START edge), which makes each one its own text chunk. Under text-anchor:
     middle each chunk would then be centred on the start edge it was handed,
     shifting every glyph left by half its own width — which is uneven, because
     the widths differ. Horizontal centring of the run is done in placeName; the
     vertical half is measured (LAB_DY), since it follows the face's cap height. */
  text-anchor: start;
}

/* Paint lives on the GROUP so each name and the stream drawn out of it are one
   material, and so the fade is applied above both smear filters rather than to
   the names underneath them. */
.zzn-flab-goo {
  opacity: 0;
  fill: #f2f8ff;
}

/* The spatial fade. Everything on a side — its filaments, its ghost and its
   crisp letters — is painted with that side's gradient, so brightness depends
   only on how close a thing is to the middle. See the note in
   zzn-dot-transition.js on why this replaced darkening the centre. */
.zzn-fstream--a,
.zzn-fghost--a,
.zzn-flab--a {
  fill: url(#zzn-str-a);
}

.zzn-fstream--b,
.zzn-fghost--b,
.zzn-flab--b {
  fill: url(#zzn-str-b);
}

/* The ball the filaments fuse into. Painted from its own centre out, so it is a
   body with a bright core rather than a flat disc. */
.zzn-fcore {
  fill: url(#zzn-core);
}

/* Singularity canvas — the ray-marched black hole that takes over from the
   fused mark. Sized in JS (see zzn-dot-transition.js: the silhouette is a
   known share of the canvas height, and that is what lines the shadow up with
   ring the field leaves behind), centred on the same axis, and transparent
   outside the glow so it never shows an edge against the stage. Sits above the
   field: the two cross-fade ring to ring at the handover. */
.zzn-sing {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 3;
  display: block;
  pointer-events: none;
  opacity: 0;
}

/* Stage background — sits behind the anchor, above the flat stage bg.
   Deliberately a flat colour: drifting radial glows used to live here and
   left visible colour in the corners against the black. */
.zzn-stage-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
  background: var(--zzn-bg, #002632);
}
.zzn-dot-label {
  position: absolute;
  left: 0;
  top: 0;
  white-space: nowrap;
  font-family: 'HeronSansCond', sans-serif;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  /* Sized so the longest line ("Das Team liefert die Umsetzung") still fits
     on one line at narrow widths — the lines never wrap. */
  font-size: clamp(16px, 3.6vmin, 40px);
  line-height: 1;
  color: #fff;
  pointer-events: none;
  transform-origin: 50% 50%;
  will-change: transform, opacity, color;
  /* Above the dots so an encapsulated word reads on top of the white dot. */
  z-index: 4;
}
/* Write-on: one span per character, revealed by the scrub. Starts hidden so
   a line never flashes in before GSAP takes over. */
.zzn-char {
  display: inline-block;
  white-space: pre;
  opacity: 0;
}
.zzn-label-pre,
.zzn-label-word {
  display: inline-block;
  will-change: transform, opacity;
}

/* Reduced-motion fallback: static stage showing the two-dot logo. */
.zzn-dot-stage--static {
  height: 60vh;
}
/* Reduced motion: the opening frame and nothing else. The field is drawn once
   at rest and the two sentences sit on their own centres — which is the same
   image the animated stage opens on, just with no scroll attached to it. GSAP
   never loads on this path, so the lines are placed here rather than by the
   transform the timeline would otherwise own. */
.zzn-dot-stage--static .zzn-line--a { transform: translate(-50%, calc(-50% - 12vmin)); }
.zzn-dot-stage--static .zzn-line--b { transform: translate(-50%, calc(-50% + 12vmin)); }
@media (max-width: 640px) {
  .zzn-dot-stage--static .zzn-line--a { transform: translate(-50%, calc(-50% - 9.75vmin)); }
  .zzn-dot-stage--static .zzn-line--b { transform: translate(-50%, calc(-50% + 9.75vmin)); }
}

/* Showreel overlay — fades to sampled page bg before the pin. */
.zzn-showreel-blackout {
  position: absolute;
  inset: 0;
  background: var(--zzn-bg, #000);
  opacity: 0;
  pointer-events: none;
  z-index: 5;
}



/* Warped grid texture behind the white CMS section — drawn on a single
   <canvas> by zzn-bw-grid.js, sized to the section's exact full height
   (including the exit stage appended into it) rather than a tiled
   background-image. A pre-built SVG tile used to sit here; it was not
   truly seamless, and once this section grew tall enough (grid riding all
   the way to the exit stage instead of fading out early) the repeat
   boundary became visible as a hard line partway down. One canvas drawn
   for the section's actual height has no boundary to show. */
.zzn-bw-zone {
  position: relative;
  isolation: isolate;
}
.zzn-bw-grid-canvas {
  position: absolute;
  left: 0;
  right: 0;
  z-index: 0;
  width: 100%;
  pointer-events: none;
  /* top and height are written from JS. The mesh starts above its own section
     — the stage before this one ends on a white flood, so it can begin drawing
     itself into that white before the section formally starts — which means it
     cannot be pinned to the section box here. See RISE in zzn-bw-grid.js. */
  /* Fades in over a fixed distance from the top, then stays opaque for the
     rest of the section — including the appended exit stage, since that is
     now this same element's last child (see zzn-dot-transition.js). There
     is no fade-out because none is needed — the exit stage's own opaque
     background and the spiral canvas cover it long before its own end. */
  -webkit-mask-image: linear-gradient(
    to bottom, transparent 0, #000 var(--grid-fade, 180px));
          mask-image: linear-gradient(
    to bottom, transparent 0, #000 var(--grid-fade, 180px));
}
/* The travelling pulse rides directly on the mesh, same origin, same mask. */
.zzn-bw-grid-pulse { z-index: 0; }
/* Written from JS. The mesh overhangs the stage above it, which is still black
   until its flood lands, so the overhang is faded in against where the reader
   actually is — see gate() in zzn-bw-grid.js. Inside the section this is
   always 1, so the mesh proper is never touched by it. */
.zzn-bw-grid-canvas { opacity: var(--grid-ext, 1); }
/* Content above the texture. The projects block paints its own white, which
   would hide the grid entirely, so it goes transparent — the section under
   it is already white. */
.zzn-bw-zone > * { position: relative; z-index: 1; }
/* Same specificity as the rule above, so source order alone would decide —
   too fragile. This needs position:absolute to win outright: as a
   position:relative (in-flow) element the canvas would sit in the section's
   own flex column and contribute its own height to it, which the height
   this canvas is drawn at is read FROM — a feedback loop where each
   ResizeObserver-triggered redraw makes the section (and so the canvas)
   taller than the last, observed firsthand ballooning a ~4500px section
   to ~50,000px in a handful of frames. */
.zzn-bw-zone > .zzn-bw-grid-canvas { position: absolute; z-index: 0; }
.zzn-bw-zone .zzn-about-projects,
.zzn-bw-zone .zzn-about-projects--home { background: transparent !important; }

/* --- BW zone (Effizientes Team + CMS grid) -------------------------- */
.zzn-bw-zone {
  --zzn-bg: #ffffff;
  --zzn-text: #000000;
  --zzn-accent: #000000;
  --zzn-muted: #666666;
  background: #fff !important;
  color: #000 !important;
  /* Top only — and explicitly zeroing the bottom, not just omitting it:
     Webflow's base .section rule ships its own 100px bottom padding, so
     dropping the padding-block shorthand that used to override both sides
     would let that 100px resurface. The exit stage (spiral) is appended as
     this section's own last child — see zzn-dot-transition.js — so any
     bottom padding here would leave a flat white gap AFTER the spiral
     finishes, right before the navy "Hunger auf mehr" section. The
     matching bottom breathing room lives on .wrapper.effizientes-team
     instead, before that append point. */
  padding-top: clamp(96px, 12vh, 160px);
  padding-bottom: 0;
  /* Webflow's .section ships `padding: 150px 5vw 100px`; that 5vw inline
     value plus the 90%-wide .wrapper inside it is why "Effizientes Team"
     sat at 627px while "Ausgewählte Projekte" directly below it was at 296.
     Cleared here so the blocks inside can carry the page container
     themselves and land on the same left edge as every other section. */
  padding-inline: 0;
  position: relative;
  z-index: 1;
  /* Webflow's scroll interaction fades and slides this section in. Over the
     blue page background the fade reads as a grey-blue wash, and the 24px
     slide leaves a teal strip between the stage and this section — both
     break the white landing the transition just made. Children keep their
     own reveal animations. */
  opacity: 1 !important;
  transform: none !important;
}
.zzn-bw-zone .wrapper.effizientes-team {
  padding-bottom: clamp(96px, 12vh, 160px);
  /* Full width so its children can centre their own container; at 90% the
     block's own left edge drifted with the viewport instead of holding the
     measure. The projects block inside already carries the container, so no
     inline padding here or it would be applied twice. */
  width: 100%;
  max-width: none;
}
/* The three-column intro block is display:block inside a centring flex
   parent, so it was shrink-to-fit and centred rather than starting on the
   measure. */
.zzn-bw-zone .wrapper.effizientes-team > .three-grid-wrapper {
  width: 100%;
  max-width: var(--zzn-max);
  margin-inline: auto;
  padding-inline: var(--zzn-pad);
  box-sizing: border-box;
}
/* Kill any decorative shade/veil that Webflow injected inside this section. */
.zzn-bw-zone .background-shade,
.zzn-bw-zone .background-cover,
.zzn-bw-zone .gradient-background-fill {
  background: #fff !important;
  filter: none !important;
  box-shadow: none !important;
}
/* Force text black across the Effizientes Team hero copy. Excludes the CMS
   "Ausgewählte Projekte" block via :not(...) so the card copy keeps its
   own white palette, and excludes .zzn-fade-word ("Ergebnisse") plus
   .zzn-grad-move so their brand gradient stays intact. */
/* :not(.zzn-exit-copy *) — the spiral exit stage is nested inside this
   section (its own last child), and the "Hunger auf Mehr?" heading and
   paragraph now live inside it, sitting on the dark vortex rather than on
   this section's white. Without this they inherit black-on-navy and vanish. */
.zzn-bw-zone :is(h1, h2, h3, h4, .large-heading, .giant-heading, .heading-5, .body-display, .paragraph-2, p, strong):not(.zzn-fade-word):not(.zzn-grad-move):not(.zzn-about-projects *):not(.zzn-about-projects):not(.zzn-exit-copy *):not(.zzn-exit-copy) {
  color: #000 !important;
  -webkit-text-fill-color: #000 !important;
  text-shadow: none !important;
}


/* CMS "Ausgewählte Projekte" block sits inside the bw-zone section but must
   keep its ORIGINAL dark palette, masonry grid, gradient veils, and white
   text. We fully opt it out of every bw-zone rule below by re-asserting the
   original values with !important. */
.zzn-bw-zone .zzn-about-projects,
.zzn-bw-zone .zzn-about-projects--home {
  /* Transparent, not white: the section behind is already white and now
     carries the warped grid texture, which an opaque block would hide. */
  background: transparent !important;
  color: #fff !important;
}
.zzn-bw-zone .zzn-about-projects--home::before,
.zzn-bw-zone .zzn-about-projects--home::after {
  content: none !important;
  display: none !important;
  background: none !important;
  animation: none !important;
}

.zzn-bw-zone .zzn-about-projects__kicker {
  color: rgba(0,0,0,0.55) !important;
  -webkit-text-fill-color: rgba(0,0,0,0.55) !important;
  background: none !important;
}
.zzn-bw-zone .zzn-about-projects__title {
  color: #000 !important;
  -webkit-text-fill-color: #000 !important;
  background: none !important;
}

/* Ergebnisse (fade-word) inside BW zone: black/grey animated gradient */
@keyframes zzn-bw-grad-move {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}
/* :not(.zzn-exit-word) — the spiral exit stage is now nested INSIDE this
   same section (its own last child; see zzn-dot-transition.js), but its
   "Hunger auf Mehr?" overlay sits on the dark vortex eye, not the white
   CMS background, and needs the original warm gradient this rule
   overrides everything else to black/grey for. */
.zzn-bw-zone .zzn-fade-word:not(.zzn-exit-word),
.zzn-bw-zone .zzn-grad-move:not(.zzn-exit-word) {
  background-image: linear-gradient(115deg,
    #000 0%, #1a1a1a 10%, #3a3a3a 20%, #666 30%, #9a9a9a 40%,
    #d0d0d0 50%, #9a9a9a 60%, #666 70%, #3a3a3a 80%, #1a1a1a 90%, #000 100%
  ) !important;
  background-size: 300% 100% !important;
  background-repeat: no-repeat !important;
  -webkit-background-clip: text !important;
  background-clip: text !important;
  -webkit-text-fill-color: transparent !important;
  color: transparent !important;
  animation: zzn-bw-grad-move 6s ease-in-out infinite !important;
  animation-play-state: running !important;
}
@media (prefers-reduced-motion: reduce) {
  .zzn-bw-zone .zzn-fade-word:not(.zzn-exit-word),
  .zzn-bw-zone .zzn-grad-move:not(.zzn-exit-word) {
    animation: zzn-bw-grad-move 6s ease-in-out infinite !important;
  }
}

/* Global media desaturation — SCOPE OUT the CMS project wall so cards keep
   native color. */
.zzn-bw-zone img,
.zzn-bw-zone video {
  filter: grayscale(1) contrast(1.06);
}
.zzn-bw-zone .zzn-about-projects img,
.zzn-bw-zone .zzn-about-projects video,
.zzn-bw-zone .zzn-proj-card__img {
  filter: none !important;
}

/* Cards inside the CMS block keep native chrome (radius, veil, hover ring,
   white gradient text). Neutralise every bw-zone !important reset here. */
.zzn-bw-zone .zzn-about-projects .zzn-proj-card {
  background: #0a0a0a !important;
  border: 0 !important;
  border-radius: 22px !important;
  box-shadow: 0 12px 40px -18px rgba(0,0,0,0.6) !important;
  color: #fff !important;
}
.zzn-bw-zone .zzn-about-projects a,
.zzn-bw-zone .zzn-about-projects .zzn-proj-card a {
  color: #fff !important;
  background: transparent !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}
.zzn-bw-zone .zzn-about-projects a:hover,
.zzn-bw-zone .zzn-about-projects .zzn-proj-card a:hover {
  background: transparent !important;
  color: #fff !important;
}
.zzn-bw-zone .zzn-about-projects .zzn-proj-card__title,
.zzn-bw-zone .zzn-about-projects .zzn-proj-card .heading-5 {
  color: #fff !important;
  -webkit-text-fill-color: #fff !important;
  background: none !important;
  text-shadow: 0 2px 18px rgba(0,0,0,0.65) !important;
}
.zzn-bw-zone .zzn-about-projects .zzn-proj-card__cta {
  color: #fff !important;
  -webkit-text-fill-color: #fff !important;
  background: none !important;
}
.zzn-bw-zone .zzn-about-projects .zzn-proj-card__veil {
  background: linear-gradient(180deg, transparent 40%, rgba(0,0,0,0.65) 100%) !important;
}

/* "Effizientes Team" copy block. Webflow gives this node place-self:center,
   and justify-self does apply to block-level boxes — so the 450px block gets
   centred in its column and sits ~108px in from the headline. Flush it left
   with the headline instead. !important because the Webflow rule is an ID
   selector, which no class selector can outrank. */
.wrapper.effizientes-team .three-grid-wrapper.small-team > .max-450 {
  justify-self: start !important;
}

/* Team page, closing CTA. The block ends on a hard horizontal edge exactly
   where the footer starts, so the warm backdrop looks cut off rather than
   finished. A fade to the footer colour over the last stretch joins the two
   into one surface. It sits at z-index 0 with the copy lifted above it, or
   it washes out the Kontakt button it is supposed to sit behind.

   The target has to track the footer: this page's footer is #000, so a fade
   to --zzn-blue put a teal ramp above it that snapped back to black at the
   footer edge. Keep the two colours in step if either changes. */
body[data-page="team"] .section.no-top-padding {
  position: relative;
}
body[data-page="team"] .section.no-top-padding::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: clamp(110px, 20vh, 220px);
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, #000 92%);
  pointer-events: none;
  z-index: 0;
}
body[data-page="team"] .section.no-top-padding > .wrapper {
  position: relative;
  z-index: 1;
}

/* ============================================================
   FOOTER — corporate, quiet. Four columns on desktop collapsing to one
   on mobile, a hairline rule instead of boxes, muted type that only
   resolves to full white on hover, and the brand orange used exactly
   once (the hover underline) so it reads as an accent, not decoration.
   ============================================================ */
.section.footer {
  background: var(--zzn-bg, #002632) !important;
  padding: 0 !important;
}

/* Legal pages inherit Webflow's IX2 fade-in, which can stay at opacity 0
   if the interaction engine never measures the trigger. Force visible. */
.zzn-legal-page .section.footer {
  opacity: 1 !important;
  transform: none !important;
  visibility: visible !important;
}
/* Flush with the top bar: the footer's content and its rule start where the
   logo starts and end where "Menu" ends, at every width. Both edges come from
   the nav's own geometry — Webflow pads .nav-bar by 5vw, and below 480px by
   12px; below 768px the logo sits another 10px in (.w-nav-brand) — so the two can only
   drift apart if the nav changes, and then these two numbers change with it. */
:root {
  --zzn-bar-l: 5vw;
  --zzn-bar-r: 5vw;
}
@media screen and (max-width: 767px) {
  :root { --zzn-bar-l: calc(5vw + 10px); }
}
@media screen and (max-width: 479px) {
  :root {
    --zzn-bar-l: 22px;
    --zzn-bar-r: 12px;
  }
}
.section.footer > .wrapper {
  box-sizing: border-box;
  width: 100%;
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding-left: var(--zzn-bar-l);
  padding-right: var(--zzn-bar-r);
}
.zzn-foot {
  box-sizing: border-box;
  width: 100%;
  max-width: none;
  margin: 0;
  /* Bottom padding clears the fixed "Cookie-Einstellungen" pill, which sits
     in the bottom-left corner and otherwise covers the copyright line. Sides
     are zero: the wrapper above carries the bar's edges. */
  padding: clamp(3.5rem, 6vw, 6rem) 0 clamp(5rem, 7vw, 6rem);
  font-family: 'Eudoxussans', 'HeronSans', system-ui, sans-serif;
  color: rgba(255, 255, 255, 0.58);
  font-size: 0.9375rem;
  line-height: 1.7;
}
.zzn-foot__top {
  display: grid;
  grid-template-columns: 1.7fr repeat(2, minmax(0, 1fr));
  gap: clamp(2rem, 4vw, 4.5rem);
  align-items: start;
}
.zzn-foot__logo {
  display: inline-block;
  width: clamp(150px, 14vw, 190px);
}
.zzn-foot__logo img { width: 100%; height: auto; display: block; }
.zzn-foot__claim {
  margin: 1.35rem 0 0;
  max-width: 34ch;
  color: rgba(255, 255, 255, 0.5);
}
/* Column headings: the only uppercase type down here, kept small and wide
   so they read as labels rather than headlines. */
.zzn-foot__h {
  margin: 0 0 1.1rem;
  font-family: 'HeronSansCond', 'Eudoxussans', sans-serif;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
}
.zzn-foot__list { list-style: none; margin: 0; padding: 0; }
.zzn-foot__list li { margin: 0 0 0.6rem; }
/* One link treatment for the whole footer: an orange rule that wipes in
   from the left while the label lifts to full white. */
.zzn-foot a:not(.zzn-foot__social):not(.zzn-foot__logo) {
  color: inherit;
  text-decoration: none;
  position: relative;
  transition: color 220ms ease;
}
.zzn-foot a:not(.zzn-foot__social):not(.zzn-foot__logo)::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -2px;
  width: 100%;
  height: 1px;
  background: var(--zzn-orange, #ec6507);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
}
.zzn-foot a:not(.zzn-foot__social):not(.zzn-foot__logo):hover {
  color: #fff;
}
.zzn-foot a:not(.zzn-foot__social):not(.zzn-foot__logo):hover::after {
  transform: scaleX(1);
}
.zzn-foot__address {
  font-style: normal;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}
.zzn-foot__socials {
  display: flex;
  gap: 0.6rem;
  margin-top: 1.6rem;
}
.zzn-foot__social {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.16);
  color: rgba(255, 255, 255, 0.62);
  transition: color 220ms ease, border-color 220ms ease, background-color 220ms ease, transform 220ms ease;
}
.zzn-foot__social svg { width: 17px; height: 17px; fill: currentColor; display: block; }
.zzn-foot__social:hover {
  color: #fff;
  border-color: rgba(255, 255, 255, 0.42);
  background: rgba(255, 255, 255, 0.06);
  transform: translateY(-2px);
}
.zzn-foot__bar {
  margin-top: clamp(2.5rem, 4vw, 4rem);
  padding-top: 1.5rem;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  align-items: center;
  justify-content: space-between;
  font-size: 0.8125rem;
  color: rgba(255, 255, 255, 0.42);
}
.zzn-foot__copy { margin: 0; }
.zzn-foot__legal { display: flex; flex-wrap: wrap; gap: 0.5rem 1.75rem; }

@media (max-width: 991px) {
  .zzn-foot__top { grid-template-columns: 1fr 1fr; gap: 2.5rem; }
  .zzn-foot__brand { grid-column: 1 / -1; }
}
@media (max-width: 560px) {
  /* Keep the two short link lists side by side — stacking all four blocks
     turns the footer into a 1100px scroll on a phone. */
  .zzn-foot__top { grid-template-columns: 1fr 1fr; gap: 2rem 1.5rem; }
  .zzn-foot__brand,
  .zzn-foot__col--contact { grid-column: 1 / -1; }
  .zzn-foot__bar { justify-content: flex-start; gap: 0.5rem 1.25rem; }
}
@media (prefers-reduced-motion: reduce) {
  .zzn-foot a::after { transition: none; }
  .zzn-foot__social { transition: none; }
  .zzn-foot__social:hover { transform: none; }
}

/* Safety net: never let a section stay invisible on touch devices.
 * Webflow's IX2 reveals the footer by animating it from opacity 0 when it
 * scrolls into view. On mobile viewports it was measured stuck at opacity 0
 * — the reveal never fires, so the page ends on a blank screen, and it is
 * the footer that carries the legal links. The fade is decoration; being
 * able to read the imprint is not. Desktop keeps the animation.
 * ("Hunger auf Mehr" was listed here too until its markup moved inside the
 * spiral's sticky, where IX2 never touches it.) */
@media (hover: none) and (pointer: coarse) {
  .section.footer {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* === EXIT STAGE (dot shrinks into a spiral vortex) ===================
 * White ball at cover scale shrinks to reveal a coiled ribbon spiral that
 * already fills the frame; the dot settles into the vortex eye and is
 * swallowed, then the scrub flies through the coil until the eye itself has
 * grown past the corners and left only the page blue that "Hunger auf mehr"
 * sits on. Entry rhymes with the dot takeover that landed on the CMS block;
 * the exit is flown through the eye, not faded out. */
.zzn-exit-stage {
  position: relative;
  /* Matches .zzn-bw-zone, which now contains this as its own last child —
     no seam to step at the top by construction. GSAP drives this to the
     page blue underneath the covering ball as the coil flies past. */
  background: #fff;
  /* Pulls the section's own bottom edge up 1px into this, so a subpixel
     rounding gap can never expose a sliver of .zzn-bw-zone's white behind
     the fully-navy last frame, right where "Hunger auf mehr" begins. */
  margin-bottom: -1px;
  border: 0;
  outline: 0;
  box-shadow: none;
  /* Kein Pin mehr: das Band ist so hoch, wie es im Fluss Platz einnimmt, und
     scrollt normal durch. Der Flug wird über seine Reise durchs Bild
     gescrubbt (siehe ScrollTrigger in zzn-dot-transition.js). */
  height: calc(100svh * var(--zzn-exit-len, 1));
  /* Full-bleed breakout. Its parent is a Webflow `.section`: flex, centered,
     with 5vw of its own left/right padding — as a flex item this would
     otherwise shrink to its (near-zero) intrinsic content width, sitting
     centered and padded in from both edges instead of covering the
     viewport. */
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
}
/* .zzn-exit-stage now lives INSIDE .zzn-bw-zone (its last child), so the
   section that actually follows .zzn-bw-zone in the DOM — "Hunger auf
   mehr" — is the one that needs lifting above whatever stacking context
   the sticky exit stage leaves behind, not a sibling of the exit stage
   itself. */
.zzn-bw-zone + * {
  position: relative;
  z-index: 1;
}
/* Die Spirale liegt in der "Mehr in Arbeit"-Bande statt in einem eigenen
   Band. Sie füllt sie als unterste Ebene; die Ausschnitte und die Kopie malen
   darüber (positionierte Geschwister mit z-index:auto in DOM-Reihenfolge,
   die Spirale wird als erstes Kind eingehängt). */
.zzn-exit-stage.zzn-exit-stage--bg {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  margin: 0;
}
/* Oberkante ausblenden, damit die Spirale aus der weißen Sektion darüber
   auftaucht statt hart anzuschneiden. Der Ball deckt die Naht, solange er
   weiß ist; sobald er weg ist, ist es diese Maske, die den Grund unter der
   Kante durchscheinen lässt. */
.zzn-exit-stage--bg .zzn-exit-sticky {
  --zzn-exit-fade-top: clamp(120px, 18vh, 240px);
  -webkit-mask-image: linear-gradient(
    180deg,
    transparent 0,
    #000 var(--zzn-exit-fade-top),
    #000 100%
  );
  mask-image: linear-gradient(
    180deg,
    transparent 0,
    #000 var(--zzn-exit-fade-top),
    #000 100%
  );
}

.zzn-exit-sticky {
  /* War sticky. Genau das war der Scroll-Stop: die Bühne blieb stehen,
     während der Flug ablief. Jetzt läuft sie als Hintergrundband mit. Der
     Klassenname bleibt, weil JS und die Layer darunter ihn tragen. */
  position: relative;
  /* Füllt das Band, egal wie hoch --zzn-exit-len es macht. */
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  /* Clips the ball as it drops, so it leaves the frame instead of the page. */
  overflow: hidden;
  isolation: isolate;
  touch-action: pan-y;
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  contain: layout paint;
}

.zzn-exit-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: #fff;
  pointer-events: none;
}
/* Vortex layer: one <canvas>, painted exactly once with the whole coil (see
   buildSpiralCanvas). The scrub only animates the parent stage's transform
   (rotate + zoom), so the entire flight is a single composited texture with
   no repaint. Sits between the flat bg and the ball: the shrinking white dot
   is what reveals it, and it must never cover the dot while the dot is
   parked in its eye. */
.zzn-spiral-wrap {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  z-index: 1;
  pointer-events: none;
}


.zzn-spiral-stage {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 50% 50%;
  /* will-change is toggled from JS only while the exit timeline runs, so an
     oversized GPU layer isn't held for the whole page. */
}
.zzn-spiral-stage > canvas {
  position: absolute;
  inset: 0;
  display: block;
  border-radius: 50%;
}

/* Legibility scrim for the copy, faded in by exitTl just ahead of the ink
   flip. It sits ABOVE the ball (z-index 3, copy is 4) deliberately: with it
   below, the ball's own white kept painting over it, and since copy and ball
   are both centred, white type over a still-white ball disappeared. Directly
   beneath the type it guarantees a dark backdrop no matter what the coil,
   the ball or the eye happen to be doing underneath. */
.zzn-exit-scrim {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  /* Transparent by default, raised only by the timeline. GSAP sets this
     element's opacity inside `if (exitHeadline)`, so if the copy is ever
     missing (markup changed upstream) nothing would zero it — without this
     the stage would carry a permanent dark wash over the spiral. */
  opacity: 0;
  /* Focal point sits at 40%, the middle of the type's own span (it runs from
     the left inset to about 76% of the width), and the core is near-opaque:
     at the moment the ink flips, the ball behind is still white, so a weaker
     wash reads as mid-grey and the type crosses it invisibly. */
  background: radial-gradient(88% 52% at 40% 50%,
    rgba(0, 20, 27, 0.97) 0%,
    rgba(0, 20, 27, 0.92) 42%,
    rgba(0, 20, 27, 0.55) 72%,
    transparent 100%);
}
@media (max-width: 767px) {
  /* Two stacked lines instead of one, so the covered area is taller and
     less wide. */
  .zzn-exit-scrim {
    background: radial-gradient(96% 40% at 45% 50%,
      rgba(0, 22, 29, 0.92) 0%,
      rgba(0, 22, 29, 0.74) 50%,
      rgba(0, 22, 29, 0.32) 78%,
      transparent 100%);
  }
}

.zzn-exit-anchor {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  z-index: 2;
}
.zzn-exit-ball {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--exit-ball, 30vmin);
  height: var(--exit-ball, 30vmin);
  border-radius: 50%;
  background: #fff;
  will-change: transform;
  pointer-events: none;
  /* Gaussian edge while shrinking. The radius is fixed per layout (keyed to
     the ball's own size) and NEVER animated — filter renders before
     transform, so the compositor scales the one pre-blurred texture and the
     softness tracks the ball for free on every device. Animating the blur
     value instead would re-run the filter every scrubbed frame.
     EXIT_COVER in zzn-dot-transition.js carries extra margin so the soft
     edge stays off screen while the ball must read as solid white. */
  filter: blur(calc(var(--exit-ball, 30vmin) * 0.055));
}

/* "Hunger auf Mehr?" inside the vortex eye — the real section's own heading
   and paragraph, relocated here by zzn-dot-transition.js (there is no
   separate section after the spiral any more). Centred over the ball/spiral
   layers; GSAP drives opacity/scale/colour from inside exitTl, in step with
   the same scrub that flies the spiral, so this needs no ScrollTrigger of
   its own. */
.zzn-exit-copy {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 4;
  /* Left-bound WITHIN the page container, not against the viewport. Pinned
     to the viewport it sat at 80px on a 2560 screen while every framed
     section above it was at ~620px, which is what read as "too far left". */
  width: 100%;
  max-width: var(--zzn-max);
  padding-inline: var(--zzn-pad);
  box-sizing: border-box;
  text-align: left;
}
/* The relocated nodes are stripped of their Webflow classes on the way in
   (see zzn-dot-transition.js — IX2 resolves animation targets by class, and
   kept driving them otherwise), so this is now the whole of their
   typography rather than a set of overrides fighting `.section
   .giant-heading.siemelden`. No !important needed anywhere here. */
.zzn-exit-headline {
  margin: 0;
  pointer-events: none;
  text-align: left;
  white-space: nowrap;
  font-family: 'Font Bureau Heronsans', 'Heronsans', 'Inter', system-ui, sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  line-height: 0.95;
  letter-spacing: -0.03em;
  color: #fff;
  /* One nowrap line measures ~8em at this tracking (measured, not guessed),
     so share-of-viewport = 8 x font-size / vw. 8vw lands the line at ~64% of
     the viewport: a real hero size that still keeps a wide margin inside the
     eye. The version that read as "way too big" was 11vw with no padded
     container, which put it at ~88% for every width under 1280. */
  font-size: min(8vw, 7rem);
}
/* Narrow screens: let it stack instead of shrinking to fit one line. At
   390px a single line caps out around 20px, which is not a headline. The
   words are separated by real spaces (the relocation swaps the old mobile
   <br>s for them), so normal wrapping does the stacking the <br>s used to. */
@media (max-width: 767px) {
  .zzn-exit-headline {
    white-space: normal;
    font-size: clamp(2.2rem, 13vw, 3.6rem);
  }
}
.zzn-exit-word {
  display: inline-block;
  margin-right: 0.18em;
}
.zzn-exit-word:last-child { margin-right: 0; }
/* .zzn-fade-word (the "Mehr?" span) runs its own gradient text-fill — a
   plain color here would fight it on tied specificity, decided by whichever
   rule happens to load later. GSAP owns colour on the plain words via
   inline style either way; this is just naming that explicitly. */
.zzn-exit-word:not(.zzn-fade-word) { color: #fff; }

/* Paragraph under it — likewise its full typography, since it no longer
   carries .body-display.large. The <br>s in the source markup were placed
   for the old wide two-column layout and break mid-sentence here; the lead
   sentence keeps its own line by being a block instead. */
.zzn-exit-lede {
  margin: clamp(1.25rem, 2.5vw, 2rem) 0 0;
  max-width: 54ch;
  text-align: left;
  text-wrap: pretty;
  pointer-events: none;
  font-family: 'Eudoxussans', 'Inter', system-ui, sans-serif;
  font-size: clamp(1rem, 1.15vw, 1.15rem);
  line-height: 1.65;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.82);
}
.zzn-exit-lede strong {
  color: #fff;
  font-weight: 700;
}
.zzn-exit-lede br { display: none; }
.zzn-exit-lede .zzn-hunger-lead {
  display: block;
  margin-bottom: 0.9rem;
}



/* Nav links asked for Inter, which is no longer loaded (Google WebFont
   loader stripped for DSGVO) — use the brand sans instead. */
.nav-link {
  font-family: 'Eudoxussans', system-ui, sans-serif;
}

/* ============================================================
   LANGUAGE SWITCHER — a <details> dropdown beside the MENU button.
   Real links inside, and <details> for the open/close, so it works with
   no JS at all: it renders and operates before any bundle runs, and it is
   keyboard- and screen-reader-operable for free. That matters because it
   is the only route to the other languages.
   ============================================================ */
.zzn-lang {
  position: relative;
  align-self: center;
  margin-right: 6px;
  font-family: 'Eudoxussans', 'Inter', system-ui, sans-serif;
  font-size: 12px;
  letter-spacing: 0.08em;
  line-height: 1;
  flex: none;
}
.zzn-lang__toggle {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 7px 8px;
  border-radius: 7px;
  color: rgba(255, 255, 255, 0.72);
  cursor: pointer;
  user-select: none;
  list-style: none;
  transition: color 0.18s ease, background 0.18s ease;
}
/* Both are needed: WebKit uses the pseudo-element, everything else honours
   list-style on the summary itself. */
.zzn-lang__toggle::-webkit-details-marker { display: none; }
.zzn-lang__toggle::marker { content: ""; }
.zzn-lang__toggle:hover { color: #fff; background: rgba(255, 255, 255, 0.08); }
.zzn-lang[open] .zzn-lang__toggle { color: #fff; background: rgba(255, 255, 255, 0.12); }
.zzn-lang__chev {
  width: 9px;
  height: 6px;
  transition: transform 0.2s ease;
}
.zzn-lang[open] .zzn-lang__chev { transform: rotate(180deg); }

.zzn-lang__menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  margin: 0;
  padding: 5px;
  list-style: none;
  min-width: 132px;
  border-radius: 11px;
  background: rgba(0, 22, 30, 0.97);
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 24px 60px -18px rgba(0, 0, 0, 0.75);
  backdrop-filter: blur(20px) saturate(160%);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
}
.zzn-lang__opt {
  display: block;
  padding: 8px 10px;
  border-radius: 7px;
  color: rgba(255, 255, 255, 0.66);
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.16s ease, background 0.16s ease;
}
.zzn-lang__opt:hover { color: #fff; background: rgba(255, 255, 255, 0.08); }
.zzn-lang__opt.is-current { color: #fff; background: rgba(255, 255, 255, 0.12); }

@media (max-width: 767px) {
  /* The nav bar is tight on phones, so the control shrinks rather than
     disappearing — a language switch nobody can reach on mobile is not a
     language switch. */
  .zzn-lang { font-size: 11px; margin-right: 2px; }
  .zzn-lang__toggle { padding: 6px 6px; gap: 4px; }
  .zzn-lang__menu { min-width: 120px; }
}
@media (prefers-reduced-motion: reduce) {
  .zzn-lang__chev { transition: none; }
}

/* ============================================================
   DARK EDITORIAL INVERSE — TEAM + CONTACT
   The monochrome counterpart to Rental/Projects: the same framed
   measure, oversized type, square imagery and restrained rules,
   inverted to a true-black canvas with white ink.
   ============================================================ */
body[data-page="team"],
body[data-page="contact"] {
  background: #000 !important;
  color: #fff;
}
body[data-page="team"]::before,
body[data-page="contact"]::before,
body[data-page="team"]::after,
body[data-page="contact"]::after {
  display: none !important;
  content: none !important;
}
body[data-page="team"] .zzn-team-hero,
body[data-page="team"] .zzn-team-care,
body[data-page="team"] .section.no-top-padding,
body[data-page="contact"] .about-hero-section,
body[data-page="contact"] .contact-secction {
  background: #000 !important;
  color: #fff;
}
body[data-page="contact"] .about-hero-section::before,
body[data-page="contact"] .about-hero-section::after,
body[data-page="contact"] .contact-secction::before,
body[data-page="contact"] .contact-secction::after {
  display: none !important;
  content: none !important;
  background: none !important;
  animation: none !important;
}
body[data-page="team"] .nav-bar,
body[data-page="contact"] .nav-bar,
body[data-page="team"] .menu-button,
body[data-page="contact"] .menu-button,
body[data-page="team"] .zzn-lang__toggle,
body[data-page="contact"] .zzn-lang__toggle {
  color: #fff !important;
}
body[data-page="team"] .navigation-cover,
body[data-page="contact"] .navigation-cover {
  background: rgba(0, 0, 0, 0.88) !important;
  background-image: none !important;
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.12) !important;
}
body[data-page="team"] .nav-bar > .nav-bar-wrapper .brand img,
body[data-page="contact"] .nav-bar > .nav-bar-wrapper .brand img {
  filter: none !important;
}
body[data-page="team"] .zzn-team-hero > .wrapper,
body[data-page="team"] .zzn-team-care > .wrapper,
body[data-page="team"] .section.no-top-padding > .wrapper,
body[data-page="contact"] .about-hero-section > .wrapper,
body[data-page="contact"] .contact-secction > .wrapper {
  width: 100%;
  max-width: var(--zzn-max);
  margin-inline: auto !important;
  padding-inline: var(--zzn-pad);
  box-sizing: border-box;
}
body[data-page="team"] .large-heading,
body[data-page="contact"] .large-heading,
body[data-page="team"] .body-display,
body[data-page="contact"] .body-display,
body[data-page="team"] strong,
body[data-page="contact"] strong {
  color: #fff !important;
}
body[data-page="team"] .zzn-grad-move {
  color: transparent !important;
  background: linear-gradient(
    115deg,
    #d24410 0%,
    #f0c090 16%,
    #fff6eb 32%,
    #8ec9b8 48%,
    #fff 64%,
    #ea6a3d 82%,
    #d24410 100%
  );
  background-size: 400% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent !important;
  animation: zzn-grad-move 12s linear infinite;
}
body[data-page="team"] .zzn-fade-word {
  color: transparent !important;
  -webkit-text-fill-color: transparent !important;
  animation: zzn-mehr-gradient 8s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
@media (prefers-reduced-motion: reduce) {
  body[data-page="team"] .zzn-grad-move,
  body[data-page="team"] .zzn-fade-word {
    animation: none !important;
  }
}

/* Team — one continuous editorial story. */
body[data-page="team"] .zzn-team-hero {
  min-height: auto;
  padding: clamp(9rem, 15vw, 14rem) 0 clamp(4rem, 8vw, 8rem) !important;
}
body[data-page="team"] .zzn-team-hero__stage {
  padding: 0;
  text-align: left;
}
/* Webflow's ≥1920px breakpoint sets `.clip { justify-content: center }`
   site-wide. On /team the headline (8.5ch) and intro (54ch) are narrower
   than their .clip, so from 1920 up both were centred in it — 147px and
   300px in from the edge the photo and every section below start on. The
   /contact intro line had the same 222px drift under its headline. */
body[data-page="team"] .zzn-team-hero__copy > .clip,
body[data-page="contact"] .left-intro .clip {
  justify-content: flex-start;
}
body[data-page="team"] .zzn-team-hero__copy .large-heading {
  max-width: 8.5ch;
  margin: 0 !important;
  font-size: clamp(4.5rem, 13vw, 12rem) !important;
  line-height: 0.82;
  letter-spacing: -0.045em;
  text-wrap: balance;
  word-break: normal;
  overflow-wrap: normal;
}
body[data-page="team"] .zzn-team-hero__copy .body-display {
  max-width: 54ch;
  margin: clamp(2rem, 4vw, 3.5rem) 0 0;
  font-size: clamp(1rem, 1.45vw, 1.35rem);
  line-height: 1.55;
}
body[data-page="team"] .zzn-team-hero__photo {
  width: 100%;
  max-width: none;
  margin: clamp(3rem, 7vw, 7rem) 0 0;
  aspect-ratio: 16 / 7;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 0;
  background: #0b0b0b;
  box-shadow: none;
}
body[data-page="team"] .zzn-team-care {
  padding: clamp(4rem, 8vw, 8rem) 0 !important;
  border-top: 1px solid rgba(255, 255, 255, 0.22);
}
body[data-page="team"] .zzn-team-care > .wrapper {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: clamp(2rem, 5vw, 6rem);
}
body[data-page="team"] .zzn-team-care__intro,
body[data-page="team"] .zzn-team-care__photos {
  grid-column: 1 / -1;
  width: 100%;
  max-width: none;
}
body[data-page="team"] .zzn-team-care__intro .large-heading {
  max-width: 10ch;
  font-size: clamp(3.8rem, 9.8vw, 9rem) !important;
  line-height: 0.86;
  letter-spacing: -0.04em;
}
body[data-page="team"] .zzn-team-care__col {
  grid-column: 2;
  max-width: 52ch;
  font-size: clamp(1rem, 1.35vw, 1.25rem);
  line-height: 1.6;
}
body[data-page="team"] .zzn-team-care__photos {
  margin: 0;
  gap: 1px;
  background: rgba(255, 255, 255, 0.22);
  border: 1px solid rgba(255, 255, 255, 0.22);
}
body[data-page="team"] .zzn-team-care__photos figure {
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: #0b0b0b;
}
body[data-page="team"] .section.no-top-padding {
  padding: clamp(4rem, 9vw, 9rem) 0 !important;
  border-top: 1px solid rgba(255, 255, 255, 0.22);
}
body[data-page="team"] .section.no-top-padding > .background-shade {
  display: none !important;
}
body[data-page="team"] .section.no-top-padding .centered-intro {
  max-width: none;
  margin: 0;
  text-align: left;
  align-items: flex-start;
  /* Der Container bringt aus dem Webflow-Stylesheet `gap: 36px 24px` mit.
     Zusammen mit den Außenabständen der Kinder ergaben sich daraus 68px
     zwischen Headline und Satz und 76px zwischen Satz und Buttons — der Satz
     hing also näher an den Aktionen als an der Zeile, zu der er gehört, und
     stand deshalb frei im Raum. Der Abstand steht hier ab jetzt an den
     Elementen selbst, nicht in einer Summe aus drei Quellen. */
  row-gap: 0;
}
body[data-page="team"] .section.no-top-padding .large-heading {
  max-width: 10ch;
  /* Knapp: der Satz ist die Unterzeile der Headline, keine eigene Einheit. */
  margin: 0 0 clamp(1rem, 1.8vw, 1.5rem);
  text-align: left;
  font-size: clamp(3.8rem, 9.8vw, 9rem) !important;
  line-height: 0.86;
  letter-spacing: -0.04em;
}
body[data-page="team"] .section.no-top-padding .body-display {
  max-width: 48ch;
  /* Und darunter die eigentliche Zäsur — die Buttons sind der nächste Schritt,
     nicht Teil des Satzes. */
  margin: 0 0 clamp(2.5rem, 4.5vw, 3.75rem);
  text-align: left;
}

/* Contact — direct introduction followed by an unboxed editorial form grid. */
body[data-page="contact"] .about-hero-section {
  min-height: 92svh;
  padding: clamp(9rem, 15vw, 14rem) 0 clamp(4rem, 8vw, 8rem) !important;
}
body[data-page="contact"] .dual-hero-wrapper,
body[data-page="contact"] .left-intro,
body[data-page="contact"] .left-intro-content {
  width: 100%;
  max-width: none;
  margin: 0;
}
body[data-page="contact"] .left-intro .large-heading {
  max-width: 10ch;
  margin: 0;
  font-size: clamp(4.5rem, 11.6vw, 10.5rem) !important;
  line-height: 0.82;
  letter-spacing: -0.045em;
}
body[data-page="contact"] .left-intro .body-display {
  max-width: 46ch;
  margin-top: clamp(2rem, 4vw, 3.5rem);
  font-size: clamp(1rem, 1.45vw, 1.35rem);
  line-height: 1.55;
}
body[data-page="contact"] .left-intro .buttons-wrapper {
  margin-top: clamp(2rem, 4vw, 3rem);
}
body[data-page="contact"] .right-scroll-link,
body[data-page="contact"] .about-hero-section > .background-shade {
  display: none !important;
}
body[data-page="contact"] .contact-secction {
  padding: clamp(4rem, 8vw, 8rem) 0 !important;
  border-top: 1px solid rgba(255, 255, 255, 0.22);
}
body[data-page="contact"] .form-wrapper {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: clamp(3rem, 8vw, 9rem);
  width: 100%;
  max-width: none;
  margin: 0;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
body[data-page="contact"] .form-wrapper::before {
  display: none;
  content: none;
}
body[data-page="contact"] .centered-intro,
body[data-page="contact"] .centered-intro .large-heading,
body[data-page="contact"] .centered-intro .body-display,
body[data-page="contact"] .centered-intro .body-display.large {
  margin: 0;
  text-align: left;
  justify-items: start;
  align-items: flex-start;
}
body[data-page="contact"] .centered-intro .clip {
  justify-content: flex-start;
}
body[data-page="contact"] .centered-intro .large-heading {
  max-width: 8ch;
  font-size: clamp(3.5rem, 7.5vw, 7rem) !important;
  line-height: 0.86;
  letter-spacing: -0.04em;
}
body[data-page="contact"] .form-wrapper .large-heading {
  max-width: 7ch;
  font-size: clamp(3.25rem, 5vw, 5.5rem) !important;
  white-space: normal;
  overflow-wrap: normal;
  word-break: normal;
}
body[data-page="contact"] .centered-intro .body-display {
  max-width: 34ch;
  margin-top: 1.75rem;
  line-height: 1.6;
}
body[data-page="contact"] .form-block {
  width: 100%;
  margin: 0;
  padding-top: 0.3rem;
}
body[data-page="contact"] .field-label {
  display: block;
  margin: 0 0 0.65rem;
  color: rgba(255, 255, 255, 0.72);
  font-size: 0.76rem;
  line-height: 1.2;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
body[data-page="contact"] .text-field,
body[data-page="contact"] .text-field.w-input {
  min-height: 3.5rem;
  margin: 0 0 1.5rem;
  padding: 0.9rem 1rem;
  background: #000 !important;
  border: 1px solid rgba(255, 255, 255, 0.28) !important;
  border-radius: 0 !important;
  color: #fff !important;
  box-shadow: none !important;
}
body[data-page="contact"] textarea.text-field,
body[data-page="contact"] textarea.text-field.w-input {
  min-height: 10rem;
  resize: vertical;
}
body[data-page="contact"] .text-field::placeholder {
  color: rgba(255, 255, 255, 0.62);
}
body[data-page="contact"] .text-field:focus {
  border-color: #fff !important;
  background: #080808 !important;
  outline: 1px solid #fff !important;
  outline-offset: 2px;
}
body[data-page="contact"] #consent {
  accent-color: #fff;
}
body[data-page="contact"] #consent + span,
body[data-page="contact"] #consent + span a {
  color: rgba(255, 255, 255, 0.72) !important;
}

/* Selective offset-frame controls — the same geometry used on Rental. */
body[data-page="team"] .section.no-top-padding .button,
body[data-page="contact"] .left-intro .button,
body[data-page="contact"] .submit-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3.4rem;
  width: fit-content !important;
  max-width: calc(100% - 6px);
  padding: 0.9rem 1.5rem !important;
  background: #fff !important;
  border: 1px solid #fff !important;
  border-radius: 0 !important;
  box-shadow: 6px 6px 0 #fff;
  color: #000 !important;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  transform: translate(-6px, -6px) !important;
  transition:
    transform 0.18s ease,
    box-shadow 0.18s ease,
    background 0.18s ease,
    color 0.18s ease;
}
body[data-page="team"] .section.no-top-padding .button:hover,
body[data-page="contact"] .left-intro .button:hover,
body[data-page="contact"] .submit-button:hover {
  background: #000 !important;
  color: #fff !important;
  box-shadow: none;
  filter: none;
  transform: translate(0, 0) !important;
}
body[data-page="contact"] .button-arrow {
  filter: invert(1);
}
body[data-page="contact"] .left-intro .button:hover .button-arrow {
  filter: none;
}
body[data-page="contact"] .success-message,
body[data-page="contact"] .error-message {
  padding: 1.25rem;
  background: #000;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 0;
  color: #fff;
}
/* The dark pages want a black footer, but .zzn-foot is the centred max-width
   1400px inner wrapper — colouring that painted a black box floating inside the
   full-width teal .section.footer, with teal gutters either side. The page-wide
   background belongs on the section; the rule above it keeps the hairline. */
body[data-page="team"] .section.footer,
body[data-page="contact"] .section.footer {
  /* !important to beat `.section.footer { background: var(--zzn-bg) !important }`
     — --zzn-bg is sampled from the page by JS and stays teal on these pages. */
  background: #000 !important;
}
body[data-page="team"] .zzn-foot,
body[data-page="contact"] .zzn-foot {
  border-top: 1px solid rgba(255, 255, 255, 0.22);
}

/* ------------------------------------------------------------------
   Projekte (/about): weißer Footer.
   Die Seite endet weiß — Kreation-Strip und Projekte-Wall sind beide auf
   #fff —, ein teal Footer schnitt darunter hart ab. Also die komplette
   Tonwertumkehr des Footers: derselbe Aufbau, dieselben Maße, nur die
   Farben gespiegelt. Das Orange der Hover-Linie bleibt, es trägt auf Weiß
   (4.5:1 als 1px-Regel ist ohnehin Dekoration, der Labelwechsel auf
   #0a0a0a ist die eigentliche Rückmeldung).
   ------------------------------------------------------------------ */
body[data-page="about"] .section.footer,
body[data-page="projekt"] .section.footer {
  /* !important, um `.section.footer { background: var(--zzn-bg) !important }`
     zu schlagen. */
  background: #fff !important;
}
body[data-page="about"] .zzn-foot,
body[data-page="projekt"] .zzn-foot {
  border-top: 1px solid rgba(0, 0, 0, 0.14);
  color: rgba(0, 0, 0, 0.66);
}
/* Die Wortmarke ist ein weißes PNG auf Transparenz und wäre hier
   unsichtbar. invert(1) macht daraus dieselbe Marke in Schwarz — kein
   zweites Asset, und es kann nicht mit dem ersten auseinanderlaufen. */
body[data-page="about"] .zzn-foot__logo img,
body[data-page="projekt"] .zzn-foot__logo img {
  filter: invert(1);
}
body[data-page="about"] .zzn-foot__claim,
body[data-page="projekt"] .zzn-foot__claim {
  color: rgba(0, 0, 0, 0.6);
}
body[data-page="about"] .zzn-foot__h,
body[data-page="projekt"] .zzn-foot__h {
  color: rgba(0, 0, 0, 0.48);
}
body[data-page="about"] .zzn-foot a:not(.zzn-foot__social):not(.zzn-foot__logo):hover,
body[data-page="projekt"] .zzn-foot a:not(.zzn-foot__social):not(.zzn-foot__logo):hover {
  color: #0a0a0a;
}
body[data-page="about"] .zzn-foot__social,
body[data-page="projekt"] .zzn-foot__social {
  border-color: rgba(0, 0, 0, 0.18);
  color: rgba(0, 0, 0, 0.62);
}
body[data-page="about"] .zzn-foot__social:hover,
body[data-page="projekt"] .zzn-foot__social:hover {
  color: #0a0a0a;
  border-color: rgba(0, 0, 0, 0.42);
  background: rgba(0, 0, 0, 0.05);
}
body[data-page="about"] .zzn-foot__bar,
body[data-page="projekt"] .zzn-foot__bar {
  border-top-color: rgba(0, 0, 0, 0.12);
  color: rgba(0, 0, 0, 0.5);
}

@media (max-width: 767px) {
  body[data-page="team"] .zzn-team-hero,
  body[data-page="contact"] .about-hero-section {
    padding-top: 8.5rem !important;
  }
  body[data-page="team"] .zzn-team-hero__copy .large-heading,
  body[data-page="team"] .section.no-top-padding .large-heading {
    font-size: clamp(3.8rem, 20vw, 6.5rem) !important;
  }
  body[data-page="contact"] .dual-hero-wrapper,
  body[data-page="contact"] .left-intro,
  body[data-page="contact"] .left-intro-content,
  body[data-page="contact"] .left-intro .clip,
  body[data-page="contact"] .left-intro .body-display,
  body[data-page="contact"] .left-intro .buttons-wrapper {
    min-width: 0;
    width: 100%;
    max-width: 100%;
  }
  body[data-page="contact"] .dual-hero-wrapper {
    display: block;
    grid-template-columns: minmax(0, 1fr);
  }
  body[data-page="contact"] .left-intro .large-heading {
    width: 100%;
    max-width: 100%;
    /* Sized to the longest unbreakable word ("LIEBESBRIEFE"). The old floor of
       3.1rem still overflowed at 320px, so both ends had to come down. */
    font-size: clamp(2.4rem, 12.3vw, 3rem) !important;
    line-height: 0.84;
  }
  body[data-page="contact"] .left-intro .button {
    width: fit-content !important;
  }
  body[data-page="team"] .zzn-team-hero__photo {
    aspect-ratio: 4 / 3;
  }
  body[data-page="team"] .zzn-team-care > .wrapper,
  body[data-page="contact"] .form-wrapper {
    grid-template-columns: 1fr;
    gap: 2.5rem;
  }
  /* The desktop rule pins this headline to max-width:7ch at a 3.25rem floor, so
     on a phone 401px of non-breaking text was forced into a 230px box and
     silently clipped by .wrapper's overflow-x:hidden. Added here rather than
     edited above, because verify-dark-editorial-pages.mjs asserts that rule's
     literal text. */
  body[data-page="contact"] .form-wrapper .large-heading {
    max-width: 100%;
    font-size: clamp(2.25rem, 12vw, 3.4rem) !important;
  }
  body[data-page="team"] .zzn-team-care__col {
    grid-column: 1;
  }
  body[data-page="team"] .zzn-team-care__photos {
    grid-template-columns: 1fr;
  }
  body[data-page="contact"] .dual-text-fields {
    grid-template-columns: 1fr;
  }
}

/* ============================================================
   /en/contact — Schutz für die zweizeilige Schlagzeile
   ------------------------------------------------------------
   „LOVE LETTERS / BY POST, PLEASE." wird über geschützte Leerzeichen
   auf zwei Zeilen gezwungen, wie das deutsche Original. Die zweite
   Zeile ist dadurch unteilbar und ragt über ihren Kasten hinaus —
   am Desktop 226px, was die Seite verkraftet (das Deutsche ragt
   127px hinaus, das ist hier Hausstil).

   Unter 400px wird es knapp: GEMESSEN lag die Zeile bei 390px noch
   1px innerhalb des Fensters, bei 344px 1px und bei 320px 2px
   darüber. Waagerechtes Scrollen entsteht dabei nicht, aber der
   Schlusspunkt berührt die Kante. Zwei Prozent weniger Schriftgröße
   holen ihn zurück, ohne dass man den Unterschied sieht.

   `:lang(en)` statt einer Seitenklasse: es ist ein Problem dieser
   einen Sprachfassung, nicht dieser einen Seite. Das Deutsche hat
   an derselben Stelle 30px Luft und darf nicht mitschrumpfen.
   ============================================================ */
@media (max-width: 400px) {
  /* Dieselbe Formel wie die Regel, die hier gewinnt
     (`body[data-page="contact"] .left-intro .large-heading` mit
     `clamp(2.4rem, 12.3vw, 3rem) !important`), nur mit 12.0 statt 12.3vw.
     Das sind 2,4% weniger — bei 390px 46,8 statt 48,0px. Unsichtbar, holt
     aber rund 9px zurück und bringt die Zeile mit Abstand ins Fenster.
     `!important` und die zusätzliche `html:lang(en)`-Stufe sind nötig, weil
     die Seitenregel selbst mit `!important` bei (0,3,1) steht. */
  html:lang(en) body[data-page="contact"] .left-intro .large-heading {
    font-size: clamp(2.4rem, 12vw, 3rem) !important;
  }
}

/* ============================================================
   EDITORIAL PROJECT CTA CONTROLS
   Selective offset-frame actions plus the white homepage band.
   ============================================================ */

/* Die Hinweiszeile unter den Knöpfen erbte das Absatzgrau der Sektion —
   rgba(10,10,10,0.68) aus `.zzn-about-projects--light + .section p`, mit
   !important bei (0,2,1). Hier steht sie schwarz.

   Auf den SCHLUSSBLOCK begrenzt, nicht auf die ganze Seite: /about hat
   seit dem zweiten Tür-Paar auch eines im orangen Band, und dort ist die
   richtige Tinte Navy wie in den Knöpfen daneben, nicht Schwarz. */
body[data-page="about"] .zzn-duo.zzn-project-contact-cta .zzn-duo__note {
  color: #0a0a0a !important;
  -webkit-text-fill-color: #0a0a0a !important;
  opacity: 1;
}
body[data-page="about"] .zzn-project-contact-cta {
  overflow: visible !important;
}
body[data-page="about"] .zzn-project-contact-cta .button {
  position: relative !important;
  isolation: isolate;
  width: fit-content;
  min-height: 3.25rem;
  margin: 0 6px 6px 0;
  padding: 0.9rem 1.35rem;
  /* .zzn-about-projects--light + .section .button.solid is (0,4,0) and would
     otherwise win these two. Black, not the old navy: the page is black and
     white now, and navy on the blush ground read as a stray third colour. */
  border: 1px solid #0a0a0a !important;
  border-radius: 0;
  background: #0a0a0a !important;
  color: #fff;
  box-shadow: none;
  opacity: 1 !important;
  overflow: visible;
  transform: none !important;
  transition:
    background 0.18s ease,
    color 0.18s ease;
}
body[data-page="about"] .zzn-project-contact-cta .button::after {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: -1;
  border: 1px solid #002632;
  pointer-events: none;
  transform: translate(6px, 6px);
  transition: transform 0.18s ease;
  /* .section .button::after ships opacity:0 and an orange gradient; both have
     to be cleared here or this frame never paints. */
  opacity: 1;
  background: none;
}
body[data-page="about"] .zzn-project-contact-cta .button:hover {
  background: #d24410;
  color: #fff;
}
body[data-page="about"] .zzn-project-contact-cta .button:hover::after {
  transform: translate(0, 0);
}

/* ============================================================
   Zwei Türen — Projekt-Check und direkter Kontakt
   ------------------------------------------------------------
   Überall, wo die Seite bisher einen einzelnen „Kontakt"-Knopf hatte, stehen
   jetzt beide Wege: der geführte Projekt-Check und der direkte Kontakt.

   Warum eine eigene zweite Stufe und nicht Webflows `.solid`: die gibt es hier
   nicht mehr. Gemessen an derselben Stelle, an der die Knöpfe stehen, rendern
   `.button` und `.button.solid` auf /about, /team und /contact identisch — die
   Regeln weiter oben haben beide Stufen längst auf eine gezogen. Zwei davon
   nebeneinander wären zwei gleich schwere Blöcke ohne Rangfolge.

   Die Geometrie kommt weiter von der Seite: die Knöpfe behalten ihre
   `.button`-Klasse und damit Höhe, Innenabstand, Versalien und den versetzten
   Rahmen der jeweiligen Seite. Diese Regeln tauschen an der zweiten Tür nur
   Fläche, Rahmen und Tinte. `!important` deshalb, weil die Seitenregeln ihre
   Fläche selbst mit `!important` setzen (und auf /about zusätzlich einen
   ::after-Rahmen zeichnen).

   Die Tinte steht explizit und wird NICHT geerbt: an `.zzn-project-contact-cta`
   auf /about erbt `color` weiß — die sichtbare Schrift dort kommt aus einer
   spezifischeren Regel. Ein über `currentColor` gebauter Rahmen wäre dort
   weiß auf Blush gewesen, also 1,08:1 und unsichtbar.
   ============================================================ */
/* Spalte, nicht Reihe: die Knöpfe stehen in .zzn-duo__row, die erklärende
   Zeile darunter. Flach ging es nicht — mit `flex-basis: 100%` UND
   `max-width: 46ch` an derselben Box bestimmt die Kappung die hypothetische
   Hauptgröße, und die Zeile passte auf /contact mit 925 von 941px noch in die
   Reihe, auf /about mit 1048 von 1050px nur um zwei Pixel nicht. Dass der
   Umbruch dort trotzdem kam, war Zufall, keine Regel. */
.zzn-duo {
  /* Rückfall ist der dunkle Grund — /team, /contact und die Fachseiten. */
  --duo-ink: #fbfaf5;
  --duo-on-ink: #001b23;
  display: flex !important;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
  /* Das Band auf der Projektseite steht in `.centered-intro`, und das setzt
     `text-align: center`. Die Knöpfe hängen an `align-items`, die Notizzeile
     aber am Textfluss — sie stand dort mittig, während Überschrift und Türen
     links standen. Eine direkt zugewiesene Deklaration schlägt den geerbten
     Wert unabhängig von der Spezifität, deshalb reicht die eine Zeile.
     Auf /team, /contact und /about ändert sie nichts, dort ist links schon
     der berechnete Wert. */
  text-align: left;
}
.zzn-duo__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1rem;
  /* .buttons-wrapper ist auf /about und /contact ein Grid mit eigenen
     Spalten; als Flex-Kind erbt die Reihe davon nichts, braucht aber die
     ganze Breite, damit die zweite Tür nicht an einer Grid-Spalte hängt. */
  width: 100%;
}
/* Blush auf /about, Orange auf dem „Wir müssen reden…"-Band der Projektseite.
   Beides sind helle Gründe: Blush 14,7:1 gegen #fdf4f7, Navy gegen unser
   #ec6507 noch 4,66:1. Weiße Tinte läge dort bei 2,9:1. */
.zzn-duo--light {
  --duo-ink: #002632;
  --duo-on-ink: #ffffff;
}
/* Auf Orange ist die Reserve knapp: Navy mit 0,78 Deckkraft mischt sich zu
   rgb(52,52,41) und damit auf 3,67:1 — unter den 4,5:1, die 14px brauchen.
   Volle Deckkraft bringt sie auf 4,66:1. Auf Blush und den dunklen Seiten
   bleibt die Zeile gedämpft, dort ist genug Luft.

   NACHGEPRÜFT: die Projektdetailseite ist die EINZIGE ausgelieferte Seite
   mit einer `.paralax-section`. Auf /about streift der Server das Band weg
   („Strip the original about-hero and parallax quote sections"), und die
   Leistungsvorlage `/leistungen/$slug` hat keine Einträge — 404, auch live.
   Deshalb bleibt die Regel gescoped, statt breiter zu greifen als es
   Seiten gibt. */
body[data-page="projekt"] .paralax-section .zzn-duo__note {
  opacity: 1;
}
@media screen and (min-width: 1920px) {
  /* Ab 1920 stellt Webflow `.centered-intro` von Grid auf Flex mit
     `align-items: center` um. Die Tür-Spalte schrumpft dann auf ihre
     Inhaltsbreite (510 von 766px) und rückt 128px nach rechts, während die
     Überschrift die Spalte weiter füllt. `stretch` holt sie zurück auf die
     Achse der Überschrift; die Knöpfe darin bleiben über `align-items`
     linksbündig. */
  body[data-page="projekt"] .paralax-section .zzn-duo {
    align-self: stretch;
  }
}

/* Spezifität, gemessen mit CSS.getMatchedStylesForNode statt geschätzt:
   `body[data-page="team"] .section.no-top-padding .button` ist **(0,4,1)** —
   `.section.no-top-padding` sind zwei Klassen an einem Element, nicht eine.
   Mit `:hover` wird daraus (0,5,1). Die Seitenregeln setzen ihre Fläche mit
   `!important`, bei gleichem `!important` entscheidet also die Spezifität, und
   eine (0,3,2) verliert am ersten Vergleich (b: 3 < 4). In der ersten Fassung
   stand die zweite Tür auf /team deshalb weiß auf weiß.

   Die doppelten Klassen sind Absicht, kein Tippfehler: `.zzn-duo.zzn-duo`
   und `.button.button` heben b auf 4 bzw. mit `.zzn-duo__alt` auf 5, ohne
   sich an eine Webflow-Klasse zu hängen, die morgen anders heißen kann.
   Wer sie „aufräumt", bringt die zweite Tür zum Verschwinden. */
body .zzn-duo.zzn-duo a.button.button.zzn-duo__alt {
  background: transparent !important;
  border: 1px solid var(--duo-ink) !important;
  color: var(--duo-ink) !important;
  /* Der versetzte Rahmen bleibt der ersten Tür. Auf den dunklen Seiten ist er
     ein box-shadow, auf /about ein ::after — beide müssen weg. */
  box-shadow: none !important;
  /* Dieselbe Verschiebung wie die erste Tür, damit beide Flächen auf einer
     Linie stehen: dort sitzt der Rahmen als Schatten 6px nach unten rechts,
     die Fläche selbst also 6px nach oben links. */
  transform: translate(-6px, -6px) !important;
}
body .zzn-duo.zzn-duo a.button.button.zzn-duo__alt > div {
  color: inherit !important;
  -webkit-text-fill-color: currentColor;
}
body .zzn-duo.zzn-duo a.button.button.zzn-duo__alt::after {
  display: none !important;
}
/* `.section .button::before` (0,2,1) ist der animierte Marken-Gradient-Ring,
   den jeder Button der Seite trägt — 1,5px Rahmen über einen Maskentrick, in
   8s driftend. Auf der ersten Tür bleibt er, er ist das Erkennungszeichen.
   Auf der zweiten muss er weg: ein bunter, wandernder Ring PLUS die eigene
   1px-Linie machte die leise Tür lauter als die laute und kehrte die
   Rangfolge um. Zweite Tür heißt hier: eine Linie, sonst nichts. */
body .zzn-duo.zzn-duo a.button.button.zzn-duo__alt::before {
  display: none !important;
  animation: none !important;
}
body .zzn-duo.zzn-duo a.button.button.zzn-duo__alt:hover {
  background: var(--duo-ink) !important;
  border-color: var(--duo-ink) !important;
  color: var(--duo-on-ink) !important;
  transform: translate(-6px, -6px) !important;
}
/* Fokus außerhalb der Fläche: auf den dunklen Seiten ist die erste Tür weiß
   gefüllt, ein weißer Ring darin wäre unsichtbar. 3px Abstand setzt ihn auf
   den Grund, und weil er die Tinte trägt, stimmt der Kontrast auf jedem
   Grund von selbst. */
body .zzn-duo.zzn-duo a.button.button:focus-visible {
  outline: 2px solid var(--duo-ink);
  outline-offset: 3px;
}
/* Die Zeile, die erklärt, was hinter der unbekannten Beschriftung steckt.
   Eigene Zeile über flex-basis, nicht als drittes Element in der Reihe.

   Die Farbe steht bewusst OHNE `!important`: auf /about färbt
   `.zzn-about-projects--light + .section p` alle Absätze der Sektion auf
   rgba(10,10,10,0.68), und das ist dort die richtige Tinte — dieselbe wie im
   Text darüber, 6,8:1 gegen Blush. Auf /team und /contact greift keine solche
   Regel, dort trägt die Variable. */
.zzn-duo__note {
  margin: 0.25rem 0 0;
  max-width: 46ch;
  font-family: 'Eudoxussans', 'Inter', system-ui, sans-serif;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--duo-ink);
  opacity: 0.78;
}
@media (max-width: 560px) {
  /* Auf dem Telefon untereinander und über die ganze Spalte, damit die
     Trefferfläche nicht auf halbe Wortbreite schrumpft. Die Seitenregeln
     setzen `width: fit-content !important` bei (0,4,1). */
  .zzn-duo__row { gap: 0.625rem; }
  body .zzn-duo.zzn-duo a.button.button {
    width: 100% !important;
    max-width: calc(100% - 6px) !important;
  }
}

/* Hero copy on the shared content column.
   Three things pulled it off that column, and all three have to go, not just
   the last one: the section's own 5vw side padding, the wrapper's
   `max-width: 1440px` + 56px padding, and the auto margins that then centred
   .centered-intro inside whatever was left. Above 1600 the 1440 cap is what
   did the real damage — the wrapper stopped growing while the viewport kept
   going, so the copy walked inward to 411px at 1920 and 565px at 2228 while
   the band and video stayed put.
   Only the HORIZONTAL section padding is cleared; `.section`'s 150px/100px
   vertical padding is doing real work and stays. */
.section.zzn-home-main-hero {
  padding-left: 0;
  padding-right: 0;
}
.zzn-home-main-hero > .wrapper {
  width: 100%;
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding-left: var(--zzn-col-inset);
  padding-right: var(--zzn-col-inset);
  box-sizing: border-box;
}
.zzn-home-main-hero .centered-intro {
  width: 100%;
  max-width: none;
  margin-left: 0;
  margin-right: 0;
}

/* Past 1920 the hero keeps growing with the screen, at half the rate.
   Everything in it was fixed in px — 120px headline, 18.4px copy,
   150/150/100px of spacing — so on a 3840 screen it was the 1920 hero in a
   corner: a 932px strip with the copy at a quarter of the width. Growing at
   the full rate (3840 = 2x) overshot the other way. Each value is
   half its 1920 size fixed plus half scaled with the width — 120px becomes
   60px + 3.125vw — so 1920 is unchanged and 3840 is 1.5x. The goat follows
   on its own: hero-room.js sizes it from the screen height. The vertical
   spacing scales this way only in portrait; in landscape it comes from the
   screen height (see "Room for the goat"), and the headline keeps the same
   14svh cap for short, very wide windows. */
@media screen and (min-width: 1921px) {
  .zzn-home-main-hero .centered-intro {
    gap: calc(12px + 0.625vw);
  }
  .zzn-home-main-hero .giant-heading.start {
    font-size: min(calc(60px + 3.125vw), 14svh);
  }
  /* !important because `.section .paragraph-2` sets its size with it. */
  .section.zzn-home-main-hero .paragraph-2 {
    font-size: calc(9.2px + 0.4792vw) !important;
    margin-bottom: calc(8px + 0.4167vw);
  }
}
@media screen and (min-width: 1921px) and (orientation: portrait) {
  .section.zzn-home-main-hero {
    padding-top: calc(75px + 3.9063vw);
    padding-bottom: calc(50px + 2.6042vw);
  }
  .zzn-home-main-hero .centered-intro {
    margin-top: calc(75px + 3.9063vw);
    margin-bottom: calc(30px + 1.5625vw);
  }
}

.zzn-home-project-cta {
  width: 100%;
  min-height: 0;
  margin: 0 !important;
  /* Kompakt wie ursprünglich: keine bildschirmfüllende Höhe.

     height: auto ist NACHGEMESSEN, nicht vorsorglich. Webflow gibt
     .section.thin-section eine prozentuale Höhe — 100%, unter 480px 35% —,
     was hier bei 390px eine Bandhöhe von 350px ergab, während der gestapelte
     Inhalt 351px braucht: Button und Kontaktlink standen unterhalb der
     Bandkante und wurden vom overflow:hidden des Bandes abgeschnitten. Die
     Prozentangabe stammt aus der Zeit, als das Band ein fester Streifen war.
     !important, weil .section.thin-section (0,2,0) sonst gewinnt. */
  height: auto !important;
  padding: clamp(2.75rem, 5vw, 5rem) 0 !important;
  display: flex !important;
  align-items: center;
  justify-content: center;
  border-top: 1px solid rgba(0, 0, 0, 0.16);
  border-bottom: 0;
  /* --zzn-orange-bright, the design system's surface orange (it carries the
     letter's logo bar and the nav cover); --zzn-orange #d24410 is the accent,
     reserved for type. White display type on it measures 3.26:1 — over the 3:1
     bar this headline qualifies for at 56-144px, but it has no headroom, so
     nothing smaller than the headline should go on this band in white. */
  background: #ec6507;
  color: #fff;
}
.zzn-home-project-cta > .background-fill {
  display: none !important;
}

/* Keine Einblendanimation im Band. `.section .centered-call-to-action` trägt
   weiter oben die Scroll-Reveal (@supports animation-timeline) und startete
   den Block auf opacity 0 mit 40px Versatz; die IX2-Inline-Styles sind schon
   aus dem Markup entfernt. */
.zzn-home-project-cta .centered-call-to-action {
  animation: none !important;
  opacity: 1 !important;
  transform: none !important;
}

.zzn-home-project-cta .wrapper.projektbesprechen {
  width: 100%;
  /* Webflow setzt .wrapper unter 480px auf overflow:hidden — das schnitt den
     um 6px versetzten Rahmen des Buttons ab, sobald er die volle Breite
     nimmt. */
  overflow: visible;
  /* Aligned to the showreel directly below rather than to the site's 1440 grid:
     that block is 81% of the viewport (90% of 90%), centred, so its edges sit
     at 9.5% either side. The max-width cap has to go with it — past ~1516px the
     showreel is wider than 1440, so a capped wrapper could never line up. */
  max-width: none;
  margin: 0 auto;
  padding: 0 var(--zzn-col-inset);
  box-sizing: border-box;
}
/* Zwei Spalten, ~60/40, beide senkrecht mittig und in sich linksbündig.
   3fr/2fr statt 60%/40%, damit die Lücke aus der Summe kommt und nicht
   obendrauf: mit Prozenten plus gap wären es 100% + gap und die rechte Spalte
   liefe aus dem Inhaltsrand. minmax(0, …), sonst setzt die automatische
   Mindestbreite eines Grid-Items (min-content) die Spur auf und die
   Überschrift kann nicht kleiner werden als ihr längstes Wort. */
.zzn-home-project-cta .centered-call-to-action {
  display: grid !important;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  align-items: center;
  gap: clamp(2rem, 4vw, 4.5rem);
  width: 100%;
  min-width: 0;
  overflow: visible !important;
  text-align: left;
}
.zzn-home-project-cta .call-to-action-heading,
.zzn-home-project-cta .centered-call-to-action .button {
  opacity: 1 !important;
  transform: none !important;
}
/* Webflow emittiert `place-self: center` an der Überschriftenzelle und
   `place-self: center start` am Button. Die Zelle würde damit in ihrer Spur
   zentriert statt links stehen; der Button sitzt jetzt in .zzn-cta-action,
   einer Spalten-Flexbox, wo align-self die Querachse meint — `center` hätte
   ihn waagerecht mittig gesetzt. */
.zzn-home-project-cta .call-to-action-heading {
  justify-self: start !important;
  align-self: center !important;
}
.zzn-home-project-cta .centered-call-to-action .button {
  align-self: flex-start !important;
  /* Der rechte Rand hält den versetzten Rahmen vom Nachbarn frei. Links 6px,
     weil der Block auf translate(-6px, -6px) steht: ohne den Ausgleich stünde
     seine sichtbare Kante 6px links von Erklärsatz und Kontaktlink, statt mit
     ihnen auf einer Linie. */
  margin: 0 6px 6px 6px;
}
/* Die Überschriftenzelle ist selbst ein Webflow-Grid (auto 1fr). Als
   linksbündige Spalte neu gestapelt, damit der Subhead UNTER der Überschrift
   sitzt und nicht daneben. */
.zzn-home-project-cta .call-to-action-heading {
  width: 100%;
  min-width: 0;
  display: flex !important;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(0.75rem, 1.3vw, 1.15rem);
}

/* Rechte Spalte: Erklärsatz, Button, Kontaktlink — linksbündig gestapelt. */
.zzn-home-project-cta .zzn-cta-action {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(1rem, 1.6vw, 1.4rem);
  width: 100%;
  min-width: 0;
}
/* Navy auf dem Orange: 4.86:1 und damit AA. Weiß läge bei 3.26:1 und wäre
   für Text dieser Größe zu wenig. */
.zzn-home-project-cta .heading {
  /* Kein ch-Deckel: der Umbruch steht als <br> im Markup. Die Größe ist auf
     die linke Spur gerechnet — Inhaltsbreite 81vw, davon 3/5 abzüglich der
     Lücke; "BESPRECHEN?" (Leistungsseiten) braucht bei Heronsans Bold rund
     das 6,8-fache der Schriftgröße, 7vw passt damit bis 2560 hinein. "WAS
     HABEN" auf der Startseite ist kürzer und passt erst recht. */
  max-width: none;
  margin: 0;
  color: #fff;
  font-size: clamp(2.5rem, 7vw, 6.5rem);
  line-height: 0.85;
  letter-spacing: -0.045em;
  text-align: left;
}

/* Subhead: größer, aber nicht mehr durchgehend fett — die Betonung liegt auf
   "Kreativität" und "Ergebnisse", der Rest steht normal.

   Die Größe ist dabei nicht frei wählbar. Weiß misst auf #ec6507 3.26:1; für
   Fließtext verlangt WCAG 1.4.3 4.5:1, für "große Schrift" reicht 3:1 — und
   groß heißt ab 18.66px FETT ODER 24px normal. Mit dem Wechsel auf Gewicht
   400 rutscht die Untergrenze also von 18.8px auf 24px hoch. 1.5rem ist
   genau diese Grenze; darunter wäre der Satz nicht mehr normgerecht. Die
   beiden fetten Wörter lägen mit 18.66px tiefer, folgen aber ohnehin der
   Zeile. */
.zzn-home-project-cta .zzn-cta-sub {
  max-width: 34ch;
  margin: 0;
  color: #002632;
  font-family: 'Eudoxussans', 'Inter', system-ui, sans-serif;
  font-size: clamp(0.95rem, 1.15vw, 1.075rem);
  font-weight: 500;
  line-height: 1.5;
  text-align: left;
}
.zzn-home-project-cta .zzn-cta-sub strong {
  font-weight: 700;
  color: inherit;
}
.zzn-home-project-cta .button {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: fit-content;
  min-height: 4.5rem;
  margin: 0 6px 6px 0;
  padding: 1.35rem 2.75rem;
  /* Navy-Fläche, weiße Schrift; der versetzte Rahmen ist ebenfalls Navy und
     zeichnet die Tiefe. Rahmen des Blocks = seine eigene Füllung, sonst
     stünden zwei ineinander gesetzte Rechtecke und der hintere läse sich als
     das vordere. Weiß auf Navy misst 15.9:1. */
  border: 1px solid #002632;
  border-radius: 0;
  background: #002632;
  color: #fff;
  /* Webflow's .button ships 12px; this one carries the band on its own, so it
     needs the weight to hold against a 9rem headline. */
  font-size: 1rem;
  letter-spacing: 0.14em;
  box-shadow: none;
  overflow: visible;
  transition:
    background 0.18s ease,
    color 0.18s ease;
}
/* .section .button::before paints an animated gold/olive gradient ring inside
   the border. Against the orange band it muddies the crisp white edge this
   button now relies on, so it is dropped here only. */
.zzn-home-project-cta .button::before {
  display: none;
}
.zzn-home-project-cta .button::after {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: -1;
  /* Navy-Rahmen, um 6px versetzt. Er darf den Block nicht überzeichnen: der
     Button setzt `isolation: isolate`, also malt dieses z-index:-1-Pseudo
     ÜBER seinen Hintergrund statt dahinter — deshalb nur ein Rahmen, keine
     Fläche. */
  border: 1px solid #002632;
  pointer-events: none;
  transform: translate(6px, 6px);
  transition: transform 0.18s ease;
  /* Same collision as the /about CTA: clear .section .button::after's
     opacity:0 and gradient. Ties that rule on specificity, wins on order. */
  opacity: 1;
  background: none;
}
.zzn-home-project-cta .button:hover {
  /* Umkehr auf Weiß. Das übliche Orange der Seite fiele auf dem orangen Band
     in sich zusammen. */
  background: #fff;
  border-color: #fff;
  color: #002632;
}
.zzn-home-project-cta .button:hover::after {
  transform: translate(0, 0);
}

@media (max-width: 991px) {
  /* Untereinander statt nebeneinander: erst Headline und Subhead, dann
     Erklärung, Button, Kontaktlink. Alles linksbündig. */
  .zzn-home-project-cta .centered-call-to-action {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    gap: clamp(1.5rem, 4vw, 2.25rem);
  }
  .zzn-home-project-cta .call-to-action-heading {
    align-self: start !important;
  }
}
@media (max-width: 767px) {
  /* Der Button ist width:fit-content mit 2.75rem Innenabstand je Seite und
     6px Rahmenversatz; bei 390px bleibt der Beschriftung sonst zu wenig und
     sie bricht mitten im Begriff. Innenabstand schrumpft mit, und der Block
     darf die volle Breite nehmen. */
  .zzn-home-project-cta .centered-call-to-action .button {
    width: 100%;
    max-width: calc(100% - 6px);
    padding-left: clamp(1.25rem, 5vw, 2.75rem);
    padding-right: clamp(1.25rem, 5vw, 2.75rem);
  }
  .zzn-home-project-cta .zzn-cta-action {
    gap: 1rem;
  }
}
@media (max-width: 767px) {
  .zzn-home-project-cta .heading {
    max-width: 100%;
    font-size: clamp(2.25rem, 12.5vw, 5rem);
    text-align: left;
  }
  body[data-page="about"] .zzn-project-contact-cta {
    justify-content: flex-start !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  .zzn-home-project-cta .button,
  .zzn-home-project-cta .button::after,
  body[data-page="about"] .zzn-project-contact-cta .button,
  body[data-page="about"] .zzn-project-contact-cta .button::after,
  body[data-page="team"] .section.no-top-padding .button,
  body[data-page="contact"] .left-intro .button,
  body[data-page="contact"] .submit-button {
    transition: none !important;
  }
}

/* ============================================================
   Shared CTA scale
   One button size across the site, matched to the homepage banner
   ("Kontakt" in .zzn-home-project-cta). The page-specific rules above
   each set their own smaller padding — and the contact submit sets it
   with !important — so this has to come last and match that weight.
   ============================================================ */
body[data-page="about"] .zzn-project-contact-cta .button,
body[data-page="team"] .section.no-top-padding .button,
body[data-page="contact"] .left-intro .button,
body[data-page="contact"] .submit-button,
.section.paralax-section .centered-intro .button {
  min-height: 4.5rem !important;
  padding: 1.35rem 2.75rem !important;
  font-size: 1rem !important;
  letter-spacing: 0.14em !important;
}
/* On a phone that scale was too much: 72px buttons, 92px once "Projekt-Check
   starten" broke onto a second line inside a 316px column. Smaller type,
   tighter tracking and less padding keep every label on one line at 56px —
   still a generous tap target. */
@media (max-width: 767px) {
  body[data-page="about"] .zzn-project-contact-cta .button,
  body[data-page="team"] .section.no-top-padding .button,
  body[data-page="contact"] .left-intro .button,
  body[data-page="contact"] .submit-button,
  .section.paralax-section .centered-intro .button {
    min-height: 3.5rem !important;
    padding: 1rem 1.25rem !important;
    font-size: 0.875rem !important;
    letter-spacing: 0.1em !important;
  }
}

/* team-office-2.jpg is a 3:4 portrait sitting in the 4:3 slot, so object-fit:
   cover discards 44% of its height. Centred, that keeps mostly empty ceiling
   and cuts the sunlit floor; biasing the crop downwards keeps the lounge, the
   windows and the light on the floor — the part of the room worth showing. */
body[data-page="team"] .zzn-team-care__photos figure:nth-of-type(2) img {
  object-position: center 75%;
}

/* ============================================================
   Projekt-Detail parallax banner ("Wir müssen reden...")
   Its CTA was still Webflow's default 100px pill at 12px, which no
   longer matches the square editorial buttons everywhere else.
   ============================================================ */
.section.paralax-section .centered-intro .button {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: fit-content;
  margin: 0 6px 6px 0;
  border: 1px solid #002632;
  border-radius: 0;
  background: #002632;
  color: #fff;
  box-shadow: none;
  overflow: visible;
  transition:
    background 0.18s ease,
    color 0.18s ease;
}
/* Same gradient ring as the homepage banner button — it fights the square
   edge, so it is dropped here too. */
.section.paralax-section .centered-intro .button::before {
  display: none;
}
.section.paralax-section .centered-intro .button::after {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: -1;
  /* Matches the fill: the button sets `isolation: isolate`, so a contrasting
     colour here would paint a line across the block rather than behind it. */
  border: 1px solid #002632;
  pointer-events: none;
  transform: translate(6px, 6px);
  transition: transform 0.18s ease;
  opacity: 1;
  background: none;
}
.section.paralax-section .centered-intro .button:hover {
  background: #fff;
  border-color: #fff;
  color: #002632;
}
.section.paralax-section .centered-intro .button:hover::after {
  transform: translate(0, 0);
}
@media (prefers-reduced-motion: reduce) {
  .section.paralax-section .centered-intro .button,
  .section.paralax-section .centered-intro .button::after {
    transition: none !important;
  }
}

/* ============================================================
   Contact — consent row
   The consent copy sits in a .field-label, so it inherited the uppercase,
   tracked, small treatment meant for short labels like "DEIN NAME". Over
   four lines of legal text that reads as shouting and is hard to scan, so
   it gets the body face in sentence case instead.
   ============================================================ */
body[data-page="contact"] label[for="consent"] {
  font-family: "Eudoxussans", Inter, system-ui, sans-serif;
  font-size: 0.78rem;
  font-weight: 400;
  line-height: 1.55;
  letter-spacing: 0;
  text-transform: none;
  margin-top: 0.5rem;
}
/* Breathing room between the consent row and the submit button, which sat
   almost flush against it. */
body[data-page="contact"] .submit-button {
  margin-top: 2rem !important;
}

/* The banner's brand-gradient plate (Gradient_BG_ZZN.jpg on
   .background-fill-image) is dropped, so the band sits on the page's own navy
   and the headline plus CTA carry it. Uppercase matches every other headline
   on the site. */
.section.paralax-section {
  /* Same surface orange as the homepage banner (--zzn-orange-bright); the
     design system reserves #d24410 for type. */
  background: #ec6507;
}
.section.paralax-section .background-fill-image {
  display: none !important;
}
.section.paralax-section .centered-intro .large-heading {
  text-transform: uppercase;
}

/* ---- Contact: intro column ------------------------------------------
   .centered-intro is a grid stretched to the height of the form beside it,
   so its two rows drifted apart — the declared 24px gap rendered as 115px
   between headline and copy. Pin the rows to the top and let the surplus
   fall below, where it reads as column length rather than a hole. */
body[data-page="contact"] .form-wrapper .centered-intro {
  align-content: start;
  gap: 2rem;
}

/* ---- Contact: submit button -----------------------------------------
   input[type=submit] cannot carry ::after, so the offset frame every other
   CTA draws with a pseudo has to be a box-shadow here. A single solid
   shadow rendered as a second filled block; two stacked shadows — the page
   colour on top, 1px of white behind — give the same 1px offset outline. */
body[data-page="contact"] .submit-button {
  box-shadow:
    6px 6px 0 0 #000,
    6px 6px 0 1px #fff !important;
}

/* ---- Offset-frame press motion --------------------------------------
   The block should sink into its frame on hover, the way the /team button
   already does (element translate(-6,-6) -> 0,0, shadow cleared). These
   three animated the ::after instead, so the block stood still and the
   outline slid up over it — it read as the background clicking upwards.

   Moving both together keeps the frame pinned to the layout position: at
   rest the element sits at -6,-6 while the pseudo's +6,+6 puts the frame
   back at 0,0; on hover both go to 0,0, so the block presses down into it. */
.zzn-home-project-cta .centered-call-to-action .button,
body[data-page="about"] .zzn-project-contact-cta .button,
.section.paralax-section .centered-intro .button {
  transform: translate(-6px, -6px) !important;
  transition:
    background 0.18s ease,
    color 0.18s ease,
    transform 0.18s ease !important;
}
.zzn-home-project-cta .centered-call-to-action .button:hover,
body[data-page="about"] .zzn-project-contact-cta .button:hover,
.section.paralax-section .centered-intro .button:hover {
  transform: translate(0, 0) !important;
}
/* The contact submit already had the right motion, but the !important on its
   box-shadow above outranked the shared hover's `box-shadow: none`, so the
   frame stayed put and cancelled the press. */
body[data-page="contact"] .submit-button:hover {
  box-shadow: none !important;
}
@media (prefers-reduced-motion: reduce) {
  .zzn-home-project-cta .centered-call-to-action .button,
  body[data-page="about"] .zzn-project-contact-cta .button,
  .section.paralax-section .centered-intro .button {
    transition: none !important;
  }
}

/* The /about CTA sits inside .about-wrapper .half-dual .buttons-wrapper, which
   carries its own `transform: none !important` at (0,5,1) — higher than the
   rule above, so the press motion never applied there. Same chain plus the CTA
   class to outrank it. */
body[data-page="about"] .about-wrapper .half-dual .buttons-wrapper.zzn-project-contact-cta .button {
  transform: translate(-6px, -6px) !important;
}
body[data-page="about"] .about-wrapper .half-dual .buttons-wrapper.zzn-project-contact-cta .button:hover {
  transform: translate(0, 0) !important;
}
/* …and then taken back out again on /about. On the blush ground the offset
   frame read as a misprint, and the -6px shift pulled both buttons out of the
   text column they sit under (31px against the copy's 37px on a phone). Here
   the buttons stand flat on the column's edge; the first door keeps its fill
   and gradient ring, the second its single line, and both answer the pointer
   with a colour change instead of a press. Same chain as above, later. */
body[data-page="about"] .about-wrapper .half-dual .buttons-wrapper.zzn-project-contact-cta .button,
body[data-page="about"] .about-wrapper .half-dual .buttons-wrapper.zzn-project-contact-cta .button:hover {
  transform: none !important;
  margin: 0 !important;
}
body[data-page="about"] .zzn-project-contact-cta .button::after {
  display: none !important;
}
body[data-page="about"] .zzn-duo--light {
  --duo-ink: #0a0a0a;
}

/* Offset controls paint outside their layout boxes: the face moves 6px up
   and left, and keyboard focus / the submit shadow extend farther still.
   Webflow clips .wrapper below 480px, cutting off the About CTA's left edge
   and the contact submit's bottom outline. Release only wrappers containing
   these controls; image, gallery and section clipping stays in place. */
.wrapper:has(.zzn-duo),
body[data-page="contact"] .wrapper:has(.submit-button) {
  overflow: visible;
}

/* ============================================================
   "Mehr in Arbeit" — Mosaik (public/js/zzn-collage.js)
   Eine Wand aus Reihen von Collage-Ausschnitten hinter einer weißen Karte
   mit der Kopie. Die Reihen gleiten seitlich und scheren beim Scrollen;
   die Karte steht. Die Sektion geht über die volle Breite, damit die Wand
   sie ohne 100vw-Ausbruch füllen kann; die Kopie behält ihr eigenes Maß.
   ============================================================ */
.zzn-proj-more {
  max-width: none;
  position: relative;
  /* Flex, not grid: an absolutely positioned child of a grid resolves inset
     against its grid *area*, which here was only as tall as the copy — the
     backdrop came out 155px instead of filling the band. A flex container
     resolves it against the padding box. */
  display: flex;
  align-items: center;
  justify-content: center;
  /* Am Desktop setzt zzn-collage.js die Höhe passend zur Wand; das hier ist
     nur der Platzhalter, bis sie steht. */
  min-height: clamp(560px, 80vh, 900px);
  /* Luft vor dem Verlauf in den Footer. */
  margin-bottom: clamp(4rem, 10vh, 8rem);
}
/* Die Karte: der Kasten der Kopie selbst, also außerhalb der Wand — sie
   bewegt sich nie, und kein Ausschnitt liegt darauf (z-index über der Wand). */
.zzn-proj-more .zzn-proj-more__inner {
  position: relative;
  z-index: 1;
  max-width: 640px;
  background: #fff;
  padding: 18px 20px 20px;
  margin: 0 22px;
  border-radius: 4px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.16);
}
@media (min-width: 768px) {
  .zzn-proj-more .zzn-proj-more__inner {
    max-width: 600px;
    padding: 30px 44px 34px;
    margin: 0;
    border-radius: 6px;
  }
  .zzn-proj-more .zzn-proj-more__title {
    font-size: 0.95rem;
    margin-bottom: 0.8rem;
  }
  .zzn-proj-more .zzn-proj-more__text {
    font-size: clamp(1.1rem, 1.25vw, 1.4rem);
    max-width: 34ch;
  }
}
.zzn-proj-more .zzn-proj-more__fx {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
/* Die Wand. Blendet erst ein, wenn die Ausschnitte dekodiert sind, und
   läuft an Ober- und Unterkante der Sektion weich aus — eine Maske auf der
   ganzen Wand, nicht auf einzelnen Ausschnitten. */
.zzn-mosaic {
  position: absolute;
  inset: 0;
  overflow: hidden;
  opacity: 0;
  transition: opacity 500ms ease;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 18%, #000 82%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0, #000 18%, #000 82%, transparent 100%);
}
.zzn-mosaic.is-in {
  opacity: 1;
}
/* Sehr breite Schirme: die Wand ist ein mittiges Fenster (siehe
   desktopWall), auch links und rechts weich auslaufend. */
.zzn-mosaic--window {
  -webkit-mask-image:
    linear-gradient(180deg, transparent 0, #000 18%, #000 82%, transparent 100%),
    linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(180deg, transparent 0, #000 18%, #000 82%, transparent 100%),
    linear-gradient(90deg, transparent 0, #000 10%, #000 90%, transparent 100%);
  mask-composite: intersect;
}
/* Eine Reihe; --rise ist das leichte Mitsteigen der ganzen Wand beim
   Scrollen. Jeder Ausschnitt wird einzeln seitlich gesetzt (transform). */
.zzn-mosaic__row {
  position: absolute;
  left: 0;
  right: 0;
  transform: translate3d(0, var(--rise, 0px), 0);
}
.zzn-mosaic__row img {
  position: absolute;
  left: 0;
  top: 0;
  display: block;
  max-width: none;
  user-select: none;
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .zzn-mosaic {
    transition: none;
  }
}
/* Nur wenn die Spirale IN der Bande liegt (zzn-dot-transition.js setzt die
   Klasse, wenn SPIRAL_BG an ist): dann ist ihr unteres Ende das Ende des
   Fluges und dazwischen darf kein Innenabstand stehen, sonst läuft die
   Animation aus, bevor die Seite endet. Und dann braucht sie Strecke, also
   ein höheres Band. Ist die Spirale aus, bleibt die Bande eine normale
   Sektion mit ihren Abständen. */
.zzn-spiral-bg .zzn-bw-zone .zzn-about-projects--home:has(.zzn-proj-more),
.zzn-spiral-bg .zzn-bw-zone .wrapper.effizientes-team:has(.zzn-proj-more) {
  padding-bottom: 0;
}
.zzn-spiral-bg .zzn-proj-more {
  min-height: clamp(520px, 88vh, 900px);
}
@media (max-width: 767px) {
  .zzn-spiral-bg .zzn-proj-more {
    min-height: clamp(520px, 84vh, 760px);
  }
  .zzn-proj-more {
    /* Am Telefon füllt die Wand die ganze Sektion: eine Bildschirmhöhe. */
    min-height: clamp(640px, 100vh, 900px);
    /* And more air above it, so the wall is not already underway while the
       project cards are still leaving. */
    margin-top: clamp(4.5rem, 18vw, 7rem);
  }
}

/* ============================================================
   Übergang der weißen Zone in den Footer (nur Startseite)
   ------------------------------------------------------------
   Vorher endete die weiße Sektion auf einer harten Kante gegen das Navy des
   Footers — mitsamt dem Gitternetz, das mit voller Deckkraft in diese Kante
   lief und dort abgeschnitten wurde. Das war der sichtbare Bruch: nicht der
   Weißraum, sondern der Schnitt an seinem Ende.

   Der Verlauf liegt als ::after auf der Sektion selbst, nicht auf dem Footer:
   nur so ist seine Unterkante garantiert dieselbe Linie wie die Oberkante des
   Footers, egal wie hoch die Sektion gerade ausfällt.

   Stapelreihenfolge, von unten nach oben:
     .zzn-bw-grid-canvas   position:absolute, z-index:0  — das Gitter
     ::after (dieser Verlauf)            z-index:0       — später im Baum,
                                                           deckt es also zu
     .zzn-bw-zone > *                    z-index:1       — Inhalt
     .wrapper.effizientes-team           z-index:2       — die Collage
   Das Gitter löst sich damit im Dunkler-Werden auf, die Ausschnitte der
   Collage fliegen weiter darüber und lösen sich über ihre eigene Maske auf.

   Alpha-Verlauf, keine Farbinterpolation von #fff nach Navy: über der
   Sektion liegt das Gitter, und ein deckendes Weiß am oberen Ende würde es
   schlagartig verschlucken statt es mitzunehmen. Die Stops sind nach hinten
   gewichtet (ease-in) — die erste Hälfte der Strecke ist fast noch Weiß,
   erst die zweite kippt. Ein linearer Verlauf liest sich auf dieser Länge
   als grauer Balken, weil die Mitte lange in einem toten Blaugrau steht.
   ============================================================ */
.zzn-bw-zone::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* Nicht 42vh o. ä.: die Rampe soll den LETZTEN Bildschirm ausfüllen, und
     der ist Viewporthöhe minus Footerhöhe hoch — bei 900px sind das 372px,
     bei 1440px schon 896px. Ein fester vh-Anteil trifft beides nicht. Die
     460px sind die Footerhöhe auf dem Desktop (528–544px) minus ein
     Sicherheitsstück, damit die Rampe eher etwas über den letzten Bildschirm
     hinausragt als darunter zu bleiben. */
  height: var(--zzn-foot-ramp, clamp(300px, calc(100vh - 460px), 1000px));
  /* ÜBER der Collage (die liegt auf 2), nicht darunter. Darunter blieben die
     Ausschnitte auf hohen Fenstern bis zum Seitenende sichtbar: bei 1440px
     Fensterhöhe steht die Bande selbst noch im letzten Bild, und kein
     Weißraum der Welt schiebt sie da heraus, ohne die Seite mit einer
     Leerfläche zu beenden. Über der Collage nimmt der Verlauf sie einfach
     mit — das Dunklerwerden IST ihr Ausblenden. */
  z-index: 3;
  pointer-events: none;
  background-image: linear-gradient(
    to bottom,
    rgba(0, 38, 50, 0) 0%,
    rgba(0, 38, 50, 0.03) 15%,
    rgba(0, 38, 50, 0.09) 30%,
    rgba(0, 38, 50, 0.2) 45%,
    rgba(0, 38, 50, 0.38) 60%,
    rgba(0, 38, 50, 0.58) 72%,
    rgba(0, 38, 50, 0.76) 82%,
    rgba(0, 38, 50, 0.88) 90%,
    rgba(0, 38, 50, 0.96) 96%,
    #002632 100%
  );
}
@media (max-width: 767px) {
  /* Auf dem Telefon ist der Footer (1001px bei 390x844) höher als das Bild:
     der letzte Bildschirm ist dort ohnehin nur Footer, die Rampe muss also
     nicht den Bildschirm füllen, sondern nur den Übergang tragen. */
  .zzn-bw-zone { --zzn-foot-ramp: clamp(220px, 34vh, 300px); }
}

/* ============================================================
   LEGAL PAGES — Impressum, Datenschutzerklärung

   These three were the worst-set pages on the site. Each carried its own
   inline <style> in the body — so it won on document order, whatever the
   stylesheet said — and between them they were the only pages that scrolled
   sideways on a phone.

   What was measured before this, at 390px and 1440px:

     body copy    12px at 390px, 16px at 1440   (12px for a legal document)
     weight       300, thin, on a dark ground
     h1/h2/h3     18/16/14px at 390px — h2 landed on what the body should be
                  and h3 sat 2px above it, so the hierarchy nearly vanished
     measure      900px of 16px text ≈ 112 characters per line
     overflow     449px of content in a 390px viewport on the Impressum

   The inline blocks are gone from the legal documents (they also carried a
   #scrollbar rule for an element that exists on none of them), and the whole
   treatment lives here instead: one place, every legal page, consistent.

   Everything is fluid rather than stepped. The old rules dropped type at two
   breakpoints, which is what produced the 12px; clamp() moves it continuously
   and cannot go below 16px at any width.
   ============================================================ */

/* Black and white, like the studio: the Zweizunull design system's ground
   and ink ramp, not the site's teal. `.zzn-legal-page` is put on <body> by
   servePage() for /legal/* only. Re-pointing Webflow's --background-2 and
   our own --zzn-bg on the body turns every surface on these pages that
   reads them (body, footer, nav overlay) black in one place, instead of
   chasing each one with its own override. */
body.zzn-legal-page {
  --background-2: #000;
  --zzn-bg: #000;
  --legal-ink: #fff;
  --legal-ink-2: rgba(255, 255, 255, 0.72);
  --legal-ink-4: rgba(255, 255, 255, 0.42);
  --legal-line: rgba(255, 255, 255, 0.22);
  background-color: #000;
}
.zzn-legal-page .zzn-foot {
  border-top: 1px solid var(--legal-line);
}
/* The text section carries a Webflow IX2 scroll fade (inline opacity, 0.7 at
   the top of the page and falling as you scroll) — it greyed the whole
   document out while it was being read. A legal text is read, not revealed.
   !important beats the inline style IX2 writes. */
.zzn-legal-page .section.contact-secction {
  opacity: 1 !important;
  transform: none !important;
}

/* Webflow's ≥1920px breakpoint pins .section.contact-secction to height:100vh
   (every narrower width leaves it at auto). The legal texts are several
   screens long, so on a wide monitor the section stopped one viewport down,
   the rest of the text spilled out of it, and the footer that follows painted
   straight over it — the Impressum cut off mid-sentence, most of the
   Datenschutzerklärung hidden. /contact uses the same section with
   overflow:hidden, where the same rule would crop the form on a short window.
   Back to auto, so the section is as tall as what is in it at every width. */
@media screen and (min-width: 1920px) {
  .section.contact-secction {
    height: auto;
  }
}

/* Webflow drops .w-layout-blockcontainer to max-width: none under 767px, so
   the container took a full viewport width while sitting 20px in from the left
   — left: 20, width: 390, right: 410 against a 390px client width, which is
   where the sideways scroll came from. box-sizing is stated because the
   padding has to come out of the width rather than add to it.

   This three-class combo is what Webflow emits in exactly these three
   documents and nowhere else on the site, so it cannot reach another page. */
.w-layout-blockcontainer.container.w-container {
  box-sizing: border-box;
  width: 100%;
  max-width: 100%;
  margin-left: 0;
  margin-right: 0;
  padding-left: clamp(1.25rem, 5vw, 2.5rem);
  padding-right: clamp(1.25rem, 5vw, 2.5rem);
}

/* .container is display:flex, which makes .legal_code a flex item — and a flex
   item defaults to min-width:auto, meaning it refuses to shrink below its
   min-content width. At 320px that min-content was 300px inside a 248px
   content box, so it overflowed by exactly the difference no matter what
   padding was set: measured left 36, right 336, against a 320px viewport.
   min-width:0 is what lets it shrink; overflow-wrap:anywhere below is what
   makes the min-content small enough to be worth shrinking to. */
.legal_code,
.legal {
  min-width: 0;
}

.legal {
  /* The Zweizunull design system's reading column: 64 characters. Legal
     prose is read in long unbroken runs, so the measure matters more here
     than anywhere else on the site. */
  max-width: 64ch;
  margin-left: auto;
  margin-right: auto;
  /* Clears the fixed nav at the top and gives the footer room below. */
  padding-top: clamp(6rem, 16vh, 10rem);
  padding-bottom: clamp(4.5rem, 12vh, 8rem);
  /* Sentences in Eudoxus, headlines in Heron — the system's one type rule. */
  font-family: 'Eudoxussans', system-ui, sans-serif;
  /* 16px at 360, ~17px past 1200. Never smaller. */
  font-size: clamp(1rem, 0.96rem + 0.18vw, 1.0625rem);
  font-weight: 400;
  line-height: 1.68;
  color: var(--legal-ink-2);
  /* German legal compounds are long enough to overflow on their own —
     "Verbraucherstreitbeilegung/Universalschlichtungsstelle" is a heading in
     the Impressum. hyphens works because <html lang="de-DE"> is set. */
  /* anywhere rather than break-word: only `anywhere` reduces the element's
     min-content width, which is what the flex shrink above needs. hyphens is
     preferred by the line breaker, so German text still breaks at syllables
     and falls back to an arbitrary break only when nothing else fits. */
  overflow-wrap: anywhere;
  hyphens: auto;
}

/* Title and section heads: Heron, bold, caps, tight — the system's Display. */
.legal h1,
.legal h2 {
  font-family: 'Heronsans', 'Font Bureau Heronsans', system-ui, sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--legal-ink);
  /* Headings set their own hyphenation off — a broken heading reads as a
     mistake where a broken paragraph does not. */
  hyphens: manual;
  text-wrap: balance;
}

/* Display "wall": the page title. */
.legal h1 {
  font-size: clamp(2rem, 5.2vw, 4.25rem);
  line-height: 0.94;
  letter-spacing: -0.025em;
  margin: 0 0 0.45em;
}

/* The line directly under the title is a standfirst ("Angaben gemäß § 5
   TMG" in the Impressum): meta under a title, set as a sentence — never a
   label above it. */
.legal h1 + p {
  font-size: clamp(1rem, 1.45vw, 1.35rem);
  line-height: 1.45;
  color: var(--legal-ink-2);
  margin: 0;
}

/* A line, not a card: every section opens on the system's hairline. */
.legal h2 {
  font-size: clamp(1.3rem, 1.05rem + 1.1vw, 1.9rem);
  line-height: 1;
  letter-spacing: -0.01em;
  margin: 3.2em 0 0.9em;
  padding-top: 1.1em;
  border-top: 1px solid var(--legal-line);
}

/* Sub-heads stay sentences — several are full questions ("Wer ist
   verantwortlich …", "Widerspruchsrecht … (Art. 21 DSGVO)") that would be
   unreadable in caps. Eudoxus bold in full ink is the step below Heron. */
.legal h3 {
  font-family: 'Eudoxussans', system-ui, sans-serif;
  font-weight: 700;
  font-size: 1.0625em;
  line-height: 1.35;
  margin: 2.2em 0 0.4em;
  color: var(--legal-ink);
  hyphens: manual;
  text-wrap: balance;
}

.legal p {
  margin: 0 0 1.15em;
}

.legal ul,
.legal ol {
  margin: 0 0 1.3em;
  padding-left: 1.35em;
}

.legal li {
  margin-bottom: 0.55em;
}

.legal li::marker {
  color: var(--legal-ink-4);
}

.legal strong {
  font-weight: 600;
  color: var(--legal-ink);
}

/* Links stay white and carry a hairline underline that firms up on hover.
   No orange: in the system, orange only ever means an error. */
.legal a {
  color: var(--legal-ink);
  text-decoration: underline;
  text-decoration-color: var(--legal-line);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  transition: text-decoration-color 0.15s ease;
}

.legal a:hover,
.legal a:focus-visible {
  text-decoration-color: currentColor;
}

@media (prefers-reduced-motion: reduce) {
  .legal a { transition: none; }
}

@media (max-width: 767px) {
  .legal {
    /* Less of a run-up on a phone, where vertical space is the scarce thing. */
    padding-top: clamp(5rem, 12vh, 6.5rem);
  }

  .legal h1,
  .legal h2,
  .legal h3 {
    /* The one place a heading may break: at 390px
       "Verbraucherstreitbeilegung/Universalschlichtungsstelle" does not fit any
       other way, and a mid-word break beats a horizontal scrollbar. */
    hyphens: auto;
  }
}

/* ============================================================
   Home work wall — "SO SIEHT ZUSAMMENARBEIT AUS:"
   ============================================================
   Its own block, not the .zzn-proj-card overlay component. That component
   sets the title INSIDE the image over a veil and is still what /about
   renders; this layout puts every word underneath the picture, so sharing
   class names would have meant overriding its absolute image, its aspect box
   and its hover veil on the homepage while /about kept depending on all
   three. Cheaper and safer to let the two be different things.

   No eyebrow anywhere: the section headline stands on its own, and the
   client/discipline line sits UNDER each card title, set as a sentence
   ("Projekt mit: … / Im Bereich: …") rather than as a label above it. */
/* Head and wall on the shared content column, not the 1440-capped container
   .zzn-about-projects gives its /about sibling. That container is a third
   grid on top of the hero's and the band's — at 1440 it starts at 56px where
   the video and the CTA band start at 137px — and this section sits directly
   under both, so the seam was visible. --zzn-col-inset is the same token the
   hero and the orange band read. */
.zzn-about-projects--home .zzn-about-projects__head,
.zzn-about-projects--home .zzn-work {
  max-width: none;
  margin-inline: 0;
  padding-inline: var(--zzn-col-inset);
  box-sizing: border-box;
}

.zzn-about-projects--home .zzn-about-projects__title {
  text-transform: uppercase;
  font-family: "Font Bureau Heronsans", "Inter", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(2rem, 5.2vw, 4.25rem);
  line-height: 0.94;
  letter-spacing: -0.025em;
  max-width: 18ch;
}

.zzn-work {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(2.5rem, 5vw, 4.5rem) clamp(1.25rem, 2.4vw, 2.25rem);
}
.zzn-work__card {
  display: flex;
  flex-direction: column;
  gap: clamp(0.9rem, 1.5vw, 1.35rem);
  text-decoration: none;
  color: inherit;
}
/* Partnerships and every fifth project run the full row; the rest pair up. */
.zzn-work__card--wide { grid-column: span 2; }

.zzn-work__media {
  position: relative;
  overflow: hidden;
  /* Square is the default — that is the shape the pairs are meant to hold.
     The full-row card gets a letterbox instead: at span-2 a 1:1 would be
     over a metre tall on a desktop and push everything under it off-screen. */
  aspect-ratio: 1 / 1;
  background: rgba(255, 255, 255, 0.06);
}
.zzn-work__card--wide .zzn-work__media { aspect-ratio: 16 / 7; }
.zzn-work__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.zzn-work__card:hover .zzn-work__img { transform: scale(1.04); }

.zzn-work__arrow {
  position: absolute;
  right: clamp(0.85rem, 1.4vw, 1.5rem);
  bottom: clamp(0.85rem, 1.4vw, 1.5rem);
  display: grid;
  place-items: center;
  width: clamp(2.5rem, 3.2vw, 3.25rem);
  aspect-ratio: 1;
  border-radius: 50%;
  background: #fff;
  color: #002632;
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.zzn-work__card:hover .zzn-work__arrow { transform: translate(2px, -2px); }

.zzn-work__body { display: flex; flex-direction: column; gap: 0.5rem; }
.zzn-work__title {
  margin: 0;
  text-transform: uppercase;
  font-family: "Font Bureau Heronsans", "Inter", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(1.5rem, 2.9vw, 2.5rem);
  line-height: 1.0;
  letter-spacing: -0.025em;
}
/* The full-row card carries a bigger headline than the squares beside it —
   it has twice the width to fill, and the size step is what makes the pair
   below read as secondary rather than as three equal items. */
.zzn-work__card--wide .zzn-work__title {
  /* Der Deckel lag bei 3.25rem — gemessen 52px in einer Titelspalte, die je
     nach Breite 554 bis 992px hat. Die Zeile stand damit klein in einem sehr
     großen Kasten. 5rem füllt die Spalte, ohne die Karte zu sprengen: die
     Projektnamen sind Claims aus kurzen Wörtern (längstes Wort über alle
     Titel: drei Zeichen), ein Umbruch in zwei Zeilen ist hier also kein
     Risiko, sondern gewollt. */
  font-size: clamp(2.25rem, 5.2vw, 5rem);
  letter-spacing: -0.03em;
}
/* The line the eyebrow used to be. Muted and small, but BELOW the title. */
.zzn-work__meta {
  margin: 0;
  font-size: clamp(0.8rem, 1vw, 0.9rem);
  line-height: 1.45;
  letter-spacing: 0.02em;
  opacity: 0.62;
}
.zzn-work__meta strong { font-weight: 600; }
.zzn-work__meta-sep { opacity: 0.5; padding: 0 0.15em; }
.zzn-work__text {
  margin: 0.15rem 0 0;
  max-width: 46ch;
  font-size: clamp(0.95rem, 1.15vw, 1.05rem);
  line-height: 1.5;
  opacity: 0.8;
}

/* On the full-row card the copy moves beside the title instead of under it —
   a span-2 card is wide enough that a single stacked column leaves a long
   empty right half. */
@media (min-width: 861px) {
  .zzn-work__card--wide .zzn-work__body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: clamp(2rem, 4vw, 4rem);
    align-items: start;
  }
  .zzn-work__card--wide .zzn-work__title { grid-column: 1; grid-row: 1; }
  .zzn-work__card--wide .zzn-work__meta { grid-column: 1; grid-row: 2; }
  .zzn-work__card--wide .zzn-work__text {
    grid-column: 2;
    grid-row: 1 / span 2;
    margin-top: 0;
  }
}

@media (max-width: 860px) {
  .zzn-work { grid-template-columns: 1fr; }
  .zzn-work__card--wide { grid-column: span 1; }
  /* One column means no pair to hold the square rhythm, so every card takes
     the same letterbox and the run reads as one rhythm rather than alternating
     tall/short down the page. */
  .zzn-work__media,
  .zzn-work__card--wide .zzn-work__media { aspect-ratio: 4 / 3; }
}

@media (prefers-reduced-motion: reduce) {
  .zzn-work__img,
  .zzn-work__arrow { transition: none; }
}

/* The wall lives inside .zzn-bw-zone, which flips the whole stage to a white
   ground mid-scroll while .zzn-about-projects--home still forces `color:#fff`
   on its subtree — so card copy set to `inherit` went white-on-white and the
   text under every picture simply vanished. Same inversion the neighbouring
   .zzn-proj-more__title/__text already carry, applied to this block. */
.zzn-bw-zone .zzn-about-projects--home .zzn-work__title {
  color: #000 !important;
  -webkit-text-fill-color: #000 !important;
}
.zzn-bw-zone .zzn-about-projects--home .zzn-work__meta,
.zzn-bw-zone .zzn-about-projects--home .zzn-work__text {
  color: rgba(0, 0, 0, 0.72) !important;
  -webkit-text-fill-color: rgba(0, 0, 0, 0.72) !important;
}

/* Team block and work wall are the SAME section — "DIREKT MIT DEN MACHERN"
   sits directly above "SO SIEHT ZUSAMMENARBEIT AUS:" — so they cannot keep
   two different left edges. The wall reads --zzn-col-inset; this block was
   still on the webflow geometry, and the offender is .three-grid-wrapper,
   not the wrapper around it: its `max-width: 1440px` + 56px padding put the
   headline at 56px on a 1440 screen and, once the capped block starts
   centring, at 296px on a 1920 one — drifting the same way the hero did.
   Scoped by :has() on the work wall, which only the homepage injects:
   /foto, /video, /3d and /layout-design carry the very same
   .wrapper.effizientes-team > .three-grid-wrapper.small-team markup and must
   keep their existing geometry. The pages have no data-page attribute in the
   served HTML, so there is nothing simpler to key on. */
.wrapper.effizientes-team:has(> .zzn-about-projects--home)
  > .three-grid-wrapper.small-team {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding-left: var(--zzn-col-inset);
  padding-right: var(--zzn-col-inset);
  box-sizing: border-box;
}

/* Placeholder state for a card whose hero image is missing or fails to load.
   Deliberately not a grey box: the brand ramp keeps the composition readable
   so the layout can still be judged, and it doubles as the production
   fallback for a project saved without a picture. Purely visual — no ::after
   label, because CSS text is invisible to the translator and to a screen
   reader, and a placeholder should not become copy. The <img> is hidden here
   rather than left broken; the onerror hook adds this class. */
.zzn-work__media--empty {
  background-color: #002632;
  background-image:
    repeating-linear-gradient(
      135deg,
      rgba(255, 255, 255, 0.055) 0 10px,
      rgba(255, 255, 255, 0) 10px 20px
    ),
    linear-gradient(115deg, #d24410 0%, #e8a24a 32%, #f5f0e4 52%, #8ec9b8 74%, #002632 100%);
}
.zzn-work__media--empty .zzn-work__img { display: none; }

/* ============================================================
   Projekt-Check: der kleine Ausweichlink unter dem CTA-Button
   ============================================================
   Der wiederkehrende Panel-Button ("Projekt besprechen") öffnet jetzt den
   Projekt-Check als Overlay. Darunter steht klein "oder direkt Kontakt" für
   alle, die den Fragebogen überspringen wollen.

   Warum ein eigenes Grid-Item statt eines Wrappers um den Button:
   .centered-call-to-action ist ein Grid, und der Button ist als DIREKTES Kind
   adressiert — auf der Startseite gleich dreifach (opacity/transform :1
   !important gegen die IX2-Inline-Styles, align-self:end, die Press-Motion).
   Ein zwischengeschobener <div> hätte all diese Selektoren gelöst und den
   Button mit seinem Inline-opacity:0 unsichtbar zurückgelassen. Der Link ist
   deshalb ein Geschwisterelement und wird per grid-column in die Spalte des
   Buttons gesetzt, also genau darunter.

   Er trägt bewusst KEIN eigenes opacity:0 / data-w-id: ohne passende
   Interaction-ID würde IX2 ihn nie einblenden. Er kommt mit dem Panel herein,
   weil `.section .centered-call-to-action` weiter oben bereits die
   Scroll-Reveal-Animation (@supports animation-timeline) trägt. */
.centered-call-to-action .zzn-pc-alt {
  grid-column: 2;
  justify-self: start;
  align-self: start;
  margin: 0;
  font-family: 'Eudoxussans', 'Inter', system-ui, sans-serif;
  font-size: 0.875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: underline;
  text-underline-offset: 0.35em;
  text-decoration-thickness: 1px;
  color: inherit;
  opacity: 0.78;
  transition: opacity 0.18s ease;
}
.centered-call-to-action .zzn-pc-alt:hover,
.centered-call-to-action .zzn-pc-alt:focus-visible {
  opacity: 1;
}
/* Fokus: bisher trugen beide Einstiege nur den Browser-Ring. Auf dem orangen
   Band der Startseite misst dessen Blau (#005fcc) gegen #ec6507 nur 1.91:1 und
   verfehlt damit die 3:1 aus WCAG 1.4.11 — getragen wurde der Ring allein von
   seinem weißen Halo. Die Komponente selbst zeichnet jede Fläche mit
   3px #ec6507 aus (projekt-check.css, `:where(button, a, summary,
   [tabindex]):focus-visible`); die beiden Seiten-Einstiege führen in dieselbe
   Komponente und bekommen deshalb dieselbe Anzeige. Der Zusatz an opacity
   bleibt, ist aber nicht mehr die einzige Fokus-Affordanz — er ist mit :hover
   identisch und damit für sich genommen keine. */
.centered-call-to-action .zzn-pc-alt:focus-visible,
.centered-call-to-action [data-projekt-check]:focus-visible {
  outline: 3px solid #ec6507;
  outline-offset: 3px;
}
/* Auf dem orangen Band ist Orange auf Orange unsichtbar. Navy ist dort die
   zweite Farbe des Bandes (der Button ist darin geflächt) und misst 4.86:1 —
   dieselbe Farbe, die der Ausweichlink dort ohnehin schon trägt. */
.zzn-home-project-cta .centered-call-to-action .zzn-pc-alt:focus-visible,
.zzn-home-project-cta .centered-call-to-action [data-projekt-check]:focus-visible {
  outline-color: #002632;
}

/* Startseite: das orange Band. Weiß misst hier nur 3.26:1 — für 14px zu wenig.
   Navy #002632 auf #ec6507 sind 4.86:1 und ist ohnehin die zweite Farbe des
   Bandes (der Button ist darin geflächt). Der Button steht auf
   translate(-6px,-6px) und hat margin 0 6px 6px 0; der Link sitzt bündig zu
   seiner optischen Kante. */
/* Der Ausweichlink steht auf den Unterseiten in Versalien mit 0.14em
   Sperrung — hier soll er normal gesetzt sein und nur unterstrichen. */
.zzn-home-project-cta .centered-call-to-action .zzn-pc-alt {
  color: #002632;
  opacity: 1;
  margin-top: 0;
  transform: none;
  font-size: clamp(0.9rem, 1.05vw, 1rem);
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  text-decoration: underline;
  text-underline-offset: 0.3em;
  text-decoration-thickness: 1px;
  align-self: flex-start;
}
.zzn-home-project-cta .centered-call-to-action .zzn-pc-alt:hover {
  text-decoration-color: #fff;
}

/* NACHGEMESSEN statt angenommen: Auf den Unterseiten ist der CTA-Container
   GAR KEIN Grid. `.centered-call-to-action` bringt zwar display:grid mit, aber
   die zweite Klasse `.clip` setzt im Webflow-Stylesheet display:flex und steht
   dort weiter unten — gleiche Spezifität, spätere Regel gewinnt. Deshalb waren
   grid-column/grid-row am Ausweichlink wirkungslos:
     ≤991px  flex-flow: column  → der Link stand zwar unter dem Button, aber
             durch align-self:start am linken Containerrand statt unter ihm.
     ≥992px  flex-flow: row wrap → der Link stand als drittes Flex-Item RECHTS
             neben dem Button (gemessen 1440: Button l=577, Link l=842).
   Auf der Startseite fällt das nicht auf, weil `.zzn-home-project-cta` das
   Grid per !important zurückholt.

   Ab 992px wird deshalb wieder ein Grid daraus — mit exakt den Werten des
   bisherigen Flex-Zustands (zwei auto-Spalten, linksbündig, 48/16px Abstand),
   das Bild von Überschrift und Button bleibt Pixel für Pixel gleich. Erst die
   damit gewonnene zweite Zeile trägt den Link unter den Button. */
@media (min-width: 992px) {
  .section:not(.zzn-home-project-cta) .centered-call-to-action.clip {
    display: grid;
    grid-template-columns: auto auto;
    justify-content: start;
    align-items: center;
    column-gap: 48px;
    row-gap: 16px;
  }
  .section:not(.zzn-home-project-cta) .centered-call-to-action.clip .zzn-pc-alt {
    grid-column: 2;
    grid-row: 2;
    justify-self: start;
  }
}
/* Darunter bleibt es die Flex-Spalte, in der Überschrift, Button und Link
   ohnehin schon übereinander stehen. Dort steuert align-self die Querachse —
   der Link folgt damit der zentrierten Ausrichtung des Buttons. */
@media (max-width: 991px) {
  .section:not(.zzn-home-project-cta) .centered-call-to-action .zzn-pc-alt {
    align-self: center;
  }
}

/* NACHGEMESSEN: Webflows IX2 blendet den Panel-Button auf den Unterseiten ein
   und startet ihn dabei 51px (≤767) bzw. 53px (768–991) unter seiner Ruhelage;
   die Bewegung braucht rund 900ms. Der Ausweichlink steht nur 24px bzw. 16px
   darunter, der verschobene Button liegt also die ganze Einblendung über
   genau auf ihm. Gemessen wurde dabei nicht nur eine Überdeckung, sondern ein
   falsches Ziel: `document.elementFromPoint` auf die Mitte des gerade sichtbar
   werdenden Buttons lieferte a.zzn-pc-alt, ein echter Klick landete auf
   /contact statt im Projekt-Check.

   Die Ursache steckt in der Malreihenfolge, nicht in der Geometrie. Der Button
   ist positioniert (`.section .button` bringt position:relative mit) und würde
   damit vor statischem Inhalt liegen — aber das `opacity: 0.78` des Links
   erzeugt seinerseits einen Stapelkontext auf derselben Ebene, und als
   späteres Geschwister gewinnt er. Eine ausdrückliche Stapelposition am Button
   stellt die Reihenfolge wieder her: in seinem überstrichenen Korridor
   entscheidet wieder der Button, also das primäre und weit größere Ziel.

   Mehr Abstand wäre die vollständigere Lösung, ist hier aber kein Weg: der
   umgebende `.wrapper.projektbesprechen` hat aus dem Webflow-Stylesheet eine
   feste Höhe (223px) und overflow:hidden — der Link steht bereits an dieser
   Kante und würde weggeschnitten. */
.centered-call-to-action [data-projekt-check] {
  position: relative;
  z-index: 1;
}
@media (prefers-reduced-motion: reduce) {
  .centered-call-to-action .zzn-pc-alt { transition: none !important; }
}

/* ============================================================
   Projektdetail (/projekte/…) auf die Sprache der Projekte-Seite
   ------------------------------------------------------------
   Die Seite war als dunkles Gegenstück gebaut: Navy-Grund, weiße Schrift,
   ein dunkler Bildhero mit Titel darin, runde Karten. /about spricht anders
   — weiß, Tinte #0a0a0a, quadratische Kanten, Bild oben und Text darunter,
   weißer Footer. Beides auf einer Website ist eine Sprache zu viel.

   Umgestellt wird hier, nicht im Block oben: der trägt die Detailseite in
   ihrer alten Fassung, und ein zweiter Ort für dieselbe Regel ist leichter
   zu lesen als ein Block, der beides zugleich versucht. Die neun
   Footer-Regeln teilt sich die Seite direkt mit /about, statt sie zu
   verdoppeln — siehe die erweiterten Selektoren weiter oben.
   ============================================================ */
body[data-page="projekt"] {
  --pd-ink: #0a0a0a;
  --pd-muted: rgba(10, 10, 10, 0.68);
  --pd-line: rgba(10, 10, 10, 0.14);
  background: #fff;
}
body[data-page="projekt"] .zzn-projekt {
  background: #fff;
  color: var(--pd-ink);
  /* NACHGEMESSEN: die Navigation ist auf jeder Breite `position: fixed`,
     100px hoch und durchsichtig. Vorher begann das Bildband bei y=0, die
     Leiste lag also auf dem Foto — und da ein CMS-Bild hell ODER dunkel sein
     kann, ist dort keine Schriftfarbe sicher. /about kommt damit durch, weil
     seine Seite oben mit einem Markengradienten anfängt; hier steht ein
     beliebiges Foto. Der Inhalt beginnt deshalb unter der Leiste, damit die
     dunkle Navigation immer auf dem weißen Seitengrund sitzt. */
  padding-top: 100px;
  padding-bottom: clamp(3rem, 6vw, 5rem);
}

/* --- Hero: Bild als Band, Titel DARUNTER --------------------------------
   Vorher lag der Titel im Bild, auf einem dunklen Schleier. Auf weißem
   Grund wäre das ein dunkler Fremdkörper. /about setzt es andersherum:
   Bild, darunter Überschrift und Angaben auf Weiß (siehe .zzn-work__card).
   Das Seitenverhältnis hält das Band flach, damit der Titel über der Falz
   bleibt. */
body[data-page="projekt"] .zzn-projekt__hero {
  display: block;
  min-height: 0;
  overflow: visible;
  isolation: auto;
}
body[data-page="projekt"] .zzn-projekt__hero-img {
  position: static;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 7;
  object-fit: cover;
  transform: none;
  border-radius: 0;
}
body[data-page="projekt"] .zzn-projekt__hero-veil { display: none; }
body[data-page="projekt"] .zzn-projekt__hero-inner {
  max-width: var(--zzn-max);
  margin-inline: auto;
  padding: clamp(2rem, 4vw, 3.25rem) var(--zzn-pad) 0;
}
body[data-page="projekt"] .zzn-projekt__title {
  color: var(--pd-ink);
  font-family: "Font Bureau Heronsans", "Heronsans", Inter, sans-serif;
  font-size: clamp(2.25rem, 6.2vw, 5.5rem);
  line-height: 0.92;
  letter-spacing: -0.03em;
  text-transform: uppercase;
}

/* --- Angaben und Text ------------------------------------------------- */
body[data-page="projekt"] .zzn-projekt__body {
  max-width: var(--zzn-max);
  padding: clamp(1.75rem, 3.5vw, 2.75rem) var(--zzn-pad) clamp(2.5rem, 5vw, 4rem);
  gap: clamp(2rem, 4vw, 4rem);
}
/* Keine Karte mehr — eine Haarlinie oben, wie die Indexzeilen auf /about. */
body[data-page="projekt"] .zzn-projekt__meta,
body[data-page="projekt"] .zzn-projekt__meta--light,
body[data-page="projekt"] .zzn-projekt__meta--dark {
  padding: 1.25rem 0 0;
  border: 0;
  border-top: 1px solid var(--pd-line);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  color: var(--pd-ink);
  text-align: left;
}
body[data-page="projekt"] .zzn-projekt__meta:has(.zzn-projekt__logo--square) { text-align: left; }
body[data-page="projekt"] .zzn-projekt__meta-label,
body[data-page="projekt"] .zzn-projekt__meta--light .zzn-projekt__meta-label,
body[data-page="projekt"] .zzn-projekt__meta--dark .zzn-projekt__meta-label {
  color: var(--pd-muted);
  margin-bottom: 0.75rem;
}
body[data-page="projekt"] .zzn-projekt__meta-name,
body[data-page="projekt"] .zzn-projekt__meta--light .zzn-projekt__meta-name,
body[data-page="projekt"] .zzn-projekt__meta--dark .zzn-projekt__meta-name {
  color: var(--pd-ink);
  font-family: "Font Bureau Heronsans", "Heronsans", Inter, sans-serif;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  font-size: clamp(1.1rem, 1.5vw, 1.35rem);
}
body[data-page="projekt"] .zzn-projekt__logo,
body[data-page="projekt"] .zzn-projekt__logo--square {
  margin: 0;
  object-position: left center;
  max-height: 96px;
  width: auto;
  max-width: 100%;
}
body[data-page="projekt"] .zzn-projekt__prose {
  color: var(--pd-muted);
  font-size: clamp(1rem, 1.2vw, 1.125rem);
}
body[data-page="projekt"] .zzn-projekt__prose p:first-of-type::first-line { color: var(--pd-ink); }
body[data-page="projekt"] .zzn-projekt__prose strong,
body[data-page="projekt"] .zzn-projekt__prose h2,
body[data-page="projekt"] .zzn-projekt__prose h3 { color: var(--pd-ink); }
body[data-page="projekt"] .zzn-projekt__prose a {
  color: var(--pd-ink);
  border-bottom-color: var(--zzn-orange, #d24410);
}

/* --- Galerie: quadratische Kanten, Haarlinie statt Schatten ----------- */
body[data-page="projekt"] .zzn-projekt-gallery {
  max-width: var(--zzn-max);
  margin-inline: auto;
  padding-inline: var(--zzn-pad);
}
body[data-page="projekt"] .zzn-projekt-gallery__item,
body[data-page="projekt"] .zzn-projekt-gallery__item img {
  border-radius: 0;
  box-shadow: none;
  background: transparent;
}

/* --- Die Sektion nach dem orangen Band --------------------------------
   NACHGEMESSEN: den Grund malt dort nicht die Sektion, sondern der
   eingesetzte Projekte-Block `.zzn-about-projects--home` mit #002632. Meine
   erste Fassung hat nur die Sektion weiß gesetzt und die Tinte dunkel
   gezogen — dunkel auf dunkel, die Überschrift war weg. Auf /about macht das
   `--light`; hier steht `--home`, also die gleiche Behandlung von Hand.

   Dazu die Reveal-Animation: die Sektion trägt ein [data-w-id] und wurde bei
   opacity 0 erwischt. Solange sie durchsichtig ist, scheint das Seitennavy
   des <body> durch das weiße Weiß — derselbe Befund wie damals auf /about.
   Also opak halten, die Kinder blenden weiter ein. */
body[data-page="projekt"] .section.extra-top-padding,
body[data-page="projekt"] .section.no-bottom-padding {
  background: #fff;
}
@supports (animation-timeline: view()) {
  body[data-page="projekt"] .section.extra-top-padding[data-w-id] {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}
body[data-page="projekt"] .zzn-about-projects,
body[data-page="projekt"] .zzn-about-projects--home {
  background: #fff !important;
  color: var(--pd-ink);
}
body[data-page="projekt"] .zzn-about-projects--home .zzn-about-projects__title,
body[data-page="projekt"] .zzn-about-projects--home .zzn-work__title {
  color: var(--pd-ink) !important;
  -webkit-text-fill-color: var(--pd-ink) !important;
}
body[data-page="projekt"] .zzn-about-projects--home .zzn-about-projects__kicker,
body[data-page="projekt"] .zzn-about-projects--home .zzn-work__meta,
body[data-page="projekt"] .zzn-about-projects--home .zzn-work__text {
  color: var(--pd-muted) !important;
  -webkit-text-fill-color: var(--pd-muted) !important;
}
body[data-page="projekt"] .zzn-about-projects--home .zzn-proj-card,
body[data-page="projekt"] .zzn-about-projects--home .zzn-work__media { border-radius: 0; }
/* Der `--home`-Block bringt zwei dekorative Farbwaschungen als ::before und
   ::after mit — auf der Startseite schaltet `.zzn-bw-zone` sie ab, diese Seite
   hat die Klasse nicht und trug sie deshalb als Schleier über dem Weiß. */
body[data-page="projekt"] .zzn-about-projects--home::before,
body[data-page="projekt"] .zzn-about-projects--home::after {
  display: none;
}
/* Und dieselbe Tinte für die Abschlusszeile der Wand. Sie ist im
   `--home`-Zustand weiß (dort steht sie auf Navy) und war hier unlesbar. */
body[data-page="projekt"] .zzn-proj-more__title {
  color: var(--pd-ink) !important;
  -webkit-text-fill-color: var(--pd-ink) !important;
}
body[data-page="projekt"] .zzn-proj-more__text {
  color: var(--pd-muted) !important;
  -webkit-text-fill-color: var(--pd-muted) !important;
}
body[data-page="projekt"] .section.extra-top-padding :is(h1, h2, h3, .large-heading, .giant-heading),
body[data-page="projekt"] .section.extra-top-padding .body-display,
body[data-page="projekt"] .section.extra-top-padding p {
  color: var(--pd-ink) !important;
  -webkit-text-fill-color: var(--pd-ink) !important;
}
body[data-page="projekt"] .section.extra-top-padding .background-shade,
body[data-page="projekt"] .section.extra-top-padding .background-cover,
body[data-page="projekt"] .section.extra-top-padding .gradient-background-fill {
  background: #fff !important;
  filter: none !important;
}

/* Webflow's export gives these two classes a placeholder background on its
   CDN (d3e54v103j8qbb.cloudfront.net). Unused here, and a third-party request
   before consent if it ever rendered. */
.speaker-image, .leistungenclass { background-image: none; }
