/* ============================================================================
   WebDevCore — Effekt-Decorator (effects.css) · Tier-1 (opt-in, rein CSS)
   ----------------------------------------------------------------------------
   Diese Schicht ergänzt base.css um OPTIONALE, an bestehende Sections ANDOCKENDE
   Effekte. Aktivierung ausschließlich über data-fx am Section-Root (vom Renderer
   aus site.json gesetzt): z. B. <section data-fx="clip-reveal hero-parallax">.
   Siehe design-registry.json (effects) — der design-director wählt sparsam 0–1
   Signatur-Effekt, das Pre-flight-Gate validiert (tier/appliesTo/fallback/extern).

   INVARIANTEN (gelten für JEDEN Effekt hier):
   - Additiv: setzt NUR transform/opacity/clip-path/filter — NIE semantische Tokens
     (--bg/--fg/--accent/Schrift) um. Inhalt ist ohne den Effekt voll vorhanden.
   - prefers-reduced-motion: reduce  -> KEIN Effekt (Endzustand sofort sichtbar).
   - Ohne JS / ohne Support: Inhalt bleibt statisch & sichtbar (kein Hidden-State).
   - Kein externer Request.
   Scroll-getriebene Effekte sind zusätzlich hinter @supports(animation-timeline)
   gegated; nicht unterstützende Browser (v. a. Firefox-Stable) zeigen den
   statischen Endzustand.
   ========================================================================== */

/* === clip-reveal — Medienflächen per clip-path-Wipe enthüllen (scroll) =======
   appliesTo: hero, gallery, intro, leistungen. Zielt auf Medien-Container (nicht
   auf .reveal-Textblöcke) -> additiv, kein Konflikt mit dem Default-Reveal. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    [data-fx~="clip-reveal"] .hero__media,
    [data-fx~="clip-reveal"] .hero__stage,
    [data-fx~="clip-reveal"] .gallery__item,
    [data-fx~="clip-reveal"] .lrow__media {
      animation-name: fx-clip-reveal;
      animation-fill-mode: both;
      animation-timing-function: var(--ease-out);
      animation-timeline: view();
      animation-range: entry 0% cover 40%;
    }
  }
}
@keyframes fx-clip-reveal {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* === image-wipe — Bilder in Grid/Zeilen per clip-Wipe + leichtem Zoom (scroll) =
   appliesTo: gallery, leistungen, hero. Eng auf bekannte Medien-Container gescopt,
   damit Logos/Avatare NICHT erfasst werden. Container haben overflow:hidden, der
   scale(1.04) legt also keine Kanten frei. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    [data-fx~="image-wipe"] .gallery__item picture,
    [data-fx~="image-wipe"] .gallery__item img,
    [data-fx~="image-wipe"] .lrow__media picture,
    [data-fx~="image-wipe"] .lrow__media img {
      animation-name: fx-image-wipe;
      animation-fill-mode: both;
      animation-timing-function: var(--ease-out);
      animation-timeline: view();
      animation-range: entry 0% cover 35%;
    }
  }
}
@keyframes fx-image-wipe {
  from { clip-path: inset(0 0 100% 0); transform: scale(1.04); }
  to   { clip-path: inset(0 0 0 0); transform: scale(1); }
}

/* === reveal-grid — Grid-/Bento-/Galerie-Items bauen sich gestaffelt auf (scroll)
   appliesTo: leistungen, gallery, usps, team. Überschreibt für Grid-KINDER bewusst
   das Default-.reveal mit einer kräftigeren Masken-/Rise-Geste (höhere Spezifität
   gewinnt -> es läuft genau EINE Animation pro Item). Jedes Item trackt via view()
   seine eigene Sichtbarkeit -> zeilenweiser Aufbau. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    [data-fx~="reveal-grid"] .grid > *,
    [data-fx~="reveal-grid"] .bento > *,
    [data-fx~="reveal-grid"] .gallery > * {
      opacity: 0;
      animation-name: fx-reveal-grid;
      animation-fill-mode: both;
      animation-timing-function: var(--ease-out);
      animation-timeline: view();
      animation-range: entry 0% cover 28%;
    }
  }
}
@keyframes fx-reveal-grid {
  from { opacity: 0; transform: translateY(26px); clip-path: inset(0 0 8% 0); }
  to   { opacity: 1; transform: none; clip-path: inset(0 0 0 0); }
}

/* === choreographed-hero — Eröffnungs-Stagger der Hero-Textblöcke (load) ========
   appliesTo: hero. ZEIT-basiert (kein scroll-timeline) -> läuft in ALLEN Browsern,
   braucht kein @supports. Die Kinder sind ohne JS sofort im DOM; animation-fill-
   mode:both blendet sie nur kurz gestaffelt ein. reduced-motion schaltet komplett
   ab (Kinder dann sofort sichtbar). */
@media (prefers-reduced-motion: no-preference) {
  [data-fx~="choreographed-hero"] .hero__copy > * {
    animation: fx-hero-enter var(--dur-slow) var(--ease-out) both;
  }
  [data-fx~="choreographed-hero"] .hero__copy > *:nth-child(1) { animation-delay: 0.05s; }
  [data-fx~="choreographed-hero"] .hero__copy > *:nth-child(2) { animation-delay: 0.16s; }
  [data-fx~="choreographed-hero"] .hero__copy > *:nth-child(3) { animation-delay: 0.27s; }
  [data-fx~="choreographed-hero"] .hero__copy > *:nth-child(4) { animation-delay: 0.38s; }
  [data-fx~="choreographed-hero"] .hero__copy > *:nth-child(n+5) { animation-delay: 0.49s; }
}
@keyframes fx-hero-enter {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

/* === hero-parallax — sanfte Medien-Parallax im Hero (scroll) =================
   appliesTo: hero. data-fx-Variante der [data-parallax]-Utility aus base.css:
   zielt auf das Hero-Medium (nicht das ganze Section). Nutzt das in base.css
   definierte @keyframes media-parallax (Keyframes sind global). Container haben
   overflow:hidden + das scale(1.06) im Keyframe verhindert freie Kanten. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    [data-fx~="hero-parallax"] .hero__media img,
    [data-fx~="hero-parallax"] .hero__media picture,
    [data-fx~="hero-parallax"] .hero__stage img,
    [data-fx~="hero-parallax"] .hero__stage picture {
      animation-name: media-parallax;
      animation-fill-mode: both;
      animation-timing-function: linear;
      animation-timeline: view();
      animation-range: cover;
      will-change: transform;
    }
  }
}

/* === hero-dock — Hero-Medium "dockt" beim Hinausscrollen ein (scroll) ========
   appliesTo: hero. Tier-1 (rein CSS): das Hero-Stage/-Medium skaliert leicht herunter,
   während es den Viewport verlässt — ein dezentes "shared element"-Gefühl ohne
   fragiles JS-FLIP. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    [data-fx~="hero-dock"] .hero__stage,
    [data-fx~="hero-dock"] .hero__media {
      animation-name: fx-hero-dock;
      animation-fill-mode: both;
      animation-timing-function: var(--ease-out);
      animation-timeline: view();
      animation-range: exit 0% exit 100%;
      will-change: transform;
    }
  }
}
@keyframes fx-hero-dock {
  from { transform: scale(1); }
  to   { transform: scale(0.9); border-radius: var(--radius-xl); }
}

/* ============================================================================
   Tier-2 — JS-gestützte Module (Aktivierung via site.json "motion"; react-only).
   Das CSS ist ohne das zugehörige JS inert (kein data-scrolled / keine .mcursor /
   kein .page-transition-Remount) und damit in beiden Stacks unschädlich.
   prefers-reduced-motion wird global (base.css *-Regel) respektiert.
   ========================================================================== */

/* magnetic-cursor (motion.cursor="magnetic"): MagneticCursor.tsx erzeugt .mcursor. */
.has-mcursor, .has-mcursor a, .has-mcursor button, .has-mcursor [data-magnetic] { cursor: none; }
.mcursor { position: fixed; top: 0; left: 0; pointer-events: none; z-index: var(--z-overlay); will-change: transform; }
.mcursor::before {
  content: ""; display: block; width: 26px; height: 26px; margin: -13px 0 0 -13px;
  border: 1.5px solid var(--accent); border-radius: var(--radius-full);
  transition: transform var(--dur-base) var(--ease-out), background var(--dur-base) var(--ease);
}
.mcursor--hot::before { transform: scale(1.7); background: var(--accent-soft); }

/* page-transition (motion.pageTransition=true): PageTransition.tsx wrappt main-Inhalt. */
.page-transition { animation: fx-page-fade var(--dur-slow) var(--ease-out) both; }
@keyframes fx-page-fade { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* sticky-shrink-mark (motion.stickyMark=true): StickyShrinkMark.tsx (react) bzw.
   motion.js (php) toggelt data-scrolled am Header; hier schrumpfen Logo/Mark + der
   Header verdichtet sich. */
.site-header .brand__logo, .site-header .brand__mark { transition: height var(--dur-base) var(--ease), transform var(--dur-base) var(--ease); }
.site-header[data-scrolled] { box-shadow: var(--shadow-sm); }
.site-header[data-scrolled] .header-inner { min-height: 3.6rem; }
.site-header[data-scrolled] .brand__logo { height: 1.7rem; }
.site-header[data-scrolled] .brand__mark { transform: scale(0.85); }

/* page-transition (php, motion.js body[data-page-transition]): beim Verlassen wird
   .is-leaving am <html> gesetzt -> sanftes Ausblenden vor der Navigation. */
@media (prefers-reduced-motion: no-preference) {
  html.is-leaving main { opacity: 0; transform: translateY(-6px); transition: opacity var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out); }
}

/* ============================================================================
   Premium-Layer Tier-1-Dekoratoren (opt-in via data-fx, rein CSS, gegated).
   Invarianten wie oben: additiv, reduced-motion aus, @supports-gegated wo scroll.
   ========================================================================== */

/* === line-sweep — feine Akzentlinie zeichnet sich unter dem Section-Kopf ein ===
   appliesTo: any (Section mit .section__head). Zeitbasiert beim Eintritt via view(). */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    [data-fx~="line-sweep"] .section__head { position: relative; }
    [data-fx~="line-sweep"] .section__head::after {
      content: ""; position: absolute; left: 0; bottom: calc(var(--space-sm) * -1);
      height: 2px; width: min(8rem, 40%); background: linear-gradient(90deg, var(--accent), transparent);
      transform-origin: left center; animation-name: fx-line-sweep; animation-fill-mode: both;
      animation-timing-function: var(--ease-out); animation-timeline: view(); animation-range: entry 0% cover 22%;
    }
  }
}
@keyframes fx-line-sweep { from { transform: scaleX(0); opacity: 0; } to { transform: scaleX(1); opacity: 1; } }

/* === tilt-in — Grid-/Bento-Kinder steigen mit minimalem Kippen auf (scroll) =====
   appliesTo: leistungen, usps, gallery, team. Bewusst dezent (<=2.5deg). */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    [data-fx~="tilt-in"] .grid > *, [data-fx~="tilt-in"] .bento > * {
      opacity: 0; transform-origin: center bottom; animation-name: fx-tilt-in; animation-fill-mode: both;
      animation-timing-function: var(--ease-premium); animation-timeline: view(); animation-range: entry 0% cover 30%;
    }
  }
}
@keyframes fx-tilt-in { from { opacity: 0; transform: perspective(1200px) rotateX(6deg) translateY(30px); } to { opacity: 1; transform: none; } }

/* ---------------------------------------------------------------------------
 * Scroll-gebundene Animation (animation-timeline). Laeuft auf dem Compositor,
 * ohne JavaScript, ohne IntersectionObserver. Browser ohne Unterstuetzung
 * ignorieren animation-timeline — deshalb ist der Ruhezustand IMMER der
 * sichtbare Endzustand, und @supports schaltet die Animation nur dazu.
 * Frei einsetzbares Primitiv (data-sda), unabhaengig vom kuratierten
 * data-fx-Effekt-Katalog oben — direkt in Blocks/project.css nutzbar.
 * ------------------------------------------------------------------------ */

/* pathLength="1" normalisiert JEDEN Pfad auf die Laenge 1 — keine JS-Messung
 * noetig (motion.js misst nicht mehr, s. Task B7). Ruhezustand ist der
 * GEZEICHNETE Pfad; die Animation kommt nur additiv dazu. */
.draw { stroke-dasharray: 1; stroke-dashoffset: 0; }

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {

    /* Eintritt: steigt beim Hereinscrollen auf. */
    [data-sda="rise"] {
      animation: sda-rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 85%;
    }
    @keyframes sda-rise {
      from { opacity: 0; transform: translateY(2.5rem); }
      to   { opacity: 1; transform: none; }
    }

    /* Parallax: Element wandert langsamer als der Scroll. */
    [data-sda="parallax"] {
      animation: sda-parallax linear both;
      animation-timeline: view();
      animation-range: cover;
    }
    @keyframes sda-parallax {
      from { transform: translateY(-8%); }
      to   { transform: translateY(8%); }
    }

    /* Selbstzeichnender Pfad, an den Scroll gekoppelt. Startzustand (unsichtbar)
     * nur HIER gesetzt, wo die Animation auch wirklich laeuft — der Ruhezustand
     * oben bleibt der gezeichnete Pfad. */
    [data-sda="draw"] .draw {
      stroke-dashoffset: 1;
      animation: sda-draw linear both;
      animation-timeline: view();
      animation-range: entry 10% cover 60%;
    }
    @keyframes sda-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }

    /* Lesefortschritt am Seitenkopf — Scroll-Timeline des Dokuments. */
    [data-sda="progress"] {
      transform-origin: left center;
      animation: sda-progress linear;
      animation-timeline: scroll();
    }
    @keyframes sda-progress { from { transform: scaleX(0); } }
  }

  /* Bewegung entlang eines Pfades (offset-path). Die konkrete Kurve setzt das
   * Projekt in assets/project.css (offset-path: path("...")); hier steht nur
   * das Geruest — braucht kein animation-timeline, laeuft zeitgesteuert in
   * jedem Browser mit offset-path-Support. */
  [data-follow] {
    offset-rotate: auto;
    animation: sda-follow linear both;
  }
  @keyframes sda-follow { to { offset-distance: 100%; } }

  /* An den Scroll gekoppelt statt zeitgesteuert, wenn zusaetzlich data-sda
   * gesetzt ist — der Nutzer treibt die Bewegung. */
  @supports (animation-timeline: view()) {
    [data-follow][data-sda] { animation-timeline: view(); animation-range: cover; }
  }
}
