/* =========================================================================
   ENVY WEB — Zusatzeffekte
   -------------------------------------------------------------------------
   Bewusst in einer eigenen Datei, getrennt von style.css. So laesst sich
   der ganze Satz mit zwei Zeilen wieder herausnehmen: den Verweis auf
   diese Datei im <head> und den auf effects.js vor </body>.

   Enthalten sind fuenf Effekte:
     1  Aurora und Koernung im Hero
     3  Karten mit Lichtkegel (Leistungen, Pakete)
     4  Raeumliche Neigung (Referenzkarten, Portrait)
     6  Text faerbt sich beim Scrollen ein (Versprechen-Kasten)
     8  Zahlen zaehlen hoch (Streifen unter den Referenzen)

   Grundsatz: kein Effekt darf etwas verdecken, verschieben oder das
   Lesen stoeren. Bei "prefers-reduced-motion" steht am Ende alles still.
   ========================================================================= */


/* ---------- 1. Aurora und Koernung im Hero ----------
   Drei weiche Farbwolken ziehen langsam hinter dem Text. Sie liegen auf
   z-index 0, der Inhalt steht mit .hero__inner auf z-index 1 darueber.
   Der Hero hat bereits overflow:hidden, die Wolken koennen also nicht
   seitlich herauslaufen und keine Querscrollleiste ausloesen. */
.aurora{
  position:absolute; inset:-30% -15%; z-index:0; pointer-events:none;
  filter:blur(78px); opacity:.72;
}
.aurora i{
  position:absolute; display:block; border-radius:50%;
  mix-blend-mode:multiply; will-change:transform;
}
/* Violett wie die Marke, dazu ein warmer Sandton und ein kuehler Rest.
   Alle drei sind stark entsaettigt, damit die Schrift lesbar bleibt. */
.aurora i:nth-child(1){
  width:46vw; height:46vw; left:2%; top:4%;
  background:radial-gradient(circle,rgba(195,166,234,.85),rgba(195,166,234,0) 68%);
  animation:auroraA 21s ease-in-out infinite alternate;
}
.aurora i:nth-child(2){
  width:40vw; height:40vw; right:2%; top:16%;
  background:radial-gradient(circle,rgba(233,206,180,.8),rgba(233,206,180,0) 68%);
  animation:auroraB 26s ease-in-out infinite alternate;
}
.aurora i:nth-child(3){
  width:38vw; height:38vw; left:34%; bottom:-6%;
  background:radial-gradient(circle,rgba(178,205,222,.7),rgba(178,205,222,0) 68%);
  animation:auroraC 31s ease-in-out infinite alternate;
}
@keyframes auroraA{ to{ transform:translate3d(7vw,5vh,0)  scale(1.14); } }
@keyframes auroraB{ to{ transform:translate3d(-6vw,7vh,0) scale(.88);  } }
@keyframes auroraC{ to{ transform:translate3d(5vw,-5vh,0) scale(1.09); } }

/* Feine Koernung ueber der Flaeche. Nimmt den weichen Verlaeufen das
   Glatte und laesst sie gedruckt statt digital wirken. Das Muster ist ein
   140x140-SVG als Datenadresse, also kein zusaetzlicher Ladevorgang. */
.hero__grain{
  position:absolute; inset:0; z-index:0; pointer-events:none; opacity:.3;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
}
/* Auf dem Telefon muessen die Wolken groesser sein: sie sind in
   Bildschirmbreiten gerechnet, und 46vw sind auf einem schmalen Geraet
   nur noch knapp 180 Pixel. Dazu etwas weniger Unschaerfe, weil der Blur
   der teuerste Teil ist und auf einfachen Telefonen sonst auffaellt. */
@media (max-width:760px){
  .aurora{ filter:blur(52px); opacity:.7; }
  .aurora i:nth-child(1){ width:96vw; height:96vw; left:-26%; top:1%; }
  .aurora i:nth-child(2){ width:86vw; height:86vw; right:-24%; top:19%; }
  .aurora i:nth-child(3){ width:82vw; height:82vw; left:8%;   bottom:0; }
  .hero__grain{ opacity:.22; }
}


/* ---------- 3. Karten mit Lichtkegel ----------
   Ein weicher Schein folgt dem Zeiger ueber die Karte. Position kommt aus
   --mx / --my, die effects.js beim Bewegen setzt. Ohne Zeiger (Telefon)
   passiert nichts, die Karte sieht aus wie bisher.

   Kein overflow:hidden auf der Karte: die Fahne "Empfohlen" der mittleren
   Preiskarte ragt oben absichtlich heraus und wuerde sonst abgeschnitten.
   Der Schein bleibt trotzdem in Form, weil das Pseudoelement genau auf der
   Karte liegt und ihre Rundung erbt. */
.fx-spot{ position:relative; isolation:isolate; }
.fx-spot::after{
  content:""; position:absolute; inset:0; z-index:-1; opacity:0;
  border-radius:inherit;
  background:radial-gradient(360px circle at var(--mx,50%) var(--my,50%),
             rgba(106,74,156,.15), rgba(106,74,156,0) 62%);
  transition:opacity .4s var(--ease); pointer-events:none;
}
.fx-spot:hover::after{ opacity:1; }
.fx-spot:hover{ border-color:var(--accent-lite); }
/* Auf der dunklen Empfehlungskarte muss der Schein hell sein, sonst
   sieht man ihn nicht. */
.plan--featured.fx-spot::after{
  background:radial-gradient(360px circle at var(--mx,50%) var(--my,50%),
             rgba(195,166,234,.22), rgba(195,166,234,0) 62%);
}
.plan--featured.fx-spot:hover{ border-color:var(--accent); }
/* Die Nutzenkarten setzen ihren Rand beim Hover auf transparent. Das
   wuerde den Akzentrand oben wieder aufheben, deshalb hier zurueckgeholt. */
.tile.fx-spot:hover{ border-color:var(--accent-lite); }


/* ---------- 4. Raeumliche Neigung ----------
   Die Karte kippt leicht zum Zeiger. Bewusst flach gehalten: mehr als
   neun Grad sieht nach Spielerei aus. Die Neigung wird von effects.js
   in die Variablen --rx / --ry geschrieben, damit ein eventuell
   bestehendes transform aus style.css hier vollstaendig ersetzt wird. */
.fx-tilt{
  transform:perspective(1100px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) translateY(var(--ty,0px));
  transition:transform .55s var(--ease), box-shadow .5s var(--ease);
}
.fx-tilt.is-tilting{ transition:transform .1s linear, box-shadow .5s var(--ease); }
/* Der Hoverzustand der Referenzkarte hebt sie in style.css per
   translateY(-7px) an. Das laeuft jetzt ueber --ty mit, sonst wuerden
   sich die beiden transform-Angaben gegenseitig ueberschreiben. */
.project.fx-tilt:hover{ transform:perspective(1100px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) translateY(var(--ty,-7px)); }

/* Glanzkante, die mit dem Zeiger wandert. Liegt ueber dem Bild, aber
   unter dem Etikett "Beispielwebsite" (das steht auf z-index 3). */
.fx-tilt .fx-shine{
  position:absolute; inset:0; z-index:2; pointer-events:none; opacity:0;
  background:radial-gradient(420px circle at var(--mx,50%) var(--my,50%),
             rgba(255,255,255,.35), rgba(255,255,255,0) 58%);
  transition:opacity .4s var(--ease);
}
.fx-tilt:hover .fx-shine{ opacity:1; }
/* Das Portrait bekommt dieselbe Neigung, aber ohne Glanz. Ein Gesicht
   mit Lichtschleier darueber wirkt unangenehm. */
.about__photo.fx-tilt{ transform-origin:50% 40%; }


/* ---------- 6. Text faerbt sich beim Scrollen ein ----------
   Jedes Wort des Versprechens steckt in einem eigenen span. Beim Scrollen
   wechseln sie nacheinander von blass auf dunkel. Vorgelesen wird der
   Satz unveraendert, die Spans tragen keine eigene Bedeutung.

   Wichtig: style.css setzt fuer .pledge span eine kleinere Schrift (das ist
   die Zeile "Mein Versprechen an jeden neuen Kunden" darunter). Die Wort-
   Spans muessen die Groesse des Absatzes erben, sonst schrumpft das Zitat. */
.pledge .fx-read span,
.fx-read span{
  font-size:inherit; line-height:inherit; letter-spacing:inherit;
  color:#C9C0D4; transition:color .5s var(--ease);
}
.pledge .fx-read span.on,
.fx-read span.on{ color:var(--ink); }
/* Ohne JavaScript bleibt der Text normal dunkel, weil die Spans dann
   gar nicht erst entstehen. */


/* ---------- 8. Zahlen zaehlen hoch ----------
   Vier Angaben, die alle im uebrigen Text der Seite belegt sind. Sie
   laufen genau einmal hoch, wenn der Streifen ins Bild kommt. */
.fxstats{
  display:grid; grid-template-columns:repeat(4,1fr); gap:1.4rem;
  margin-top:clamp(2.5rem,4vw,3.5rem); padding-top:clamp(2.2rem,3.5vw,3rem);
  border-top:1px solid var(--line); text-align:center;
}
.fxstat b{
  display:block; font-family:var(--display); font-weight:400; color:var(--accent);
  font-size:clamp(2.6rem,1.6rem + 3vw,4.2rem); line-height:1;
  font-variant-numeric:tabular-nums;
}
/* Die Einheit hinter der Zahl, gesetzt von effects.js. Deutlich kleiner
   und etwas hoeher, damit die Zahl die Hauptrolle behaelt. */
.fxstat b i{ font-style:normal; font-size:.42em; letter-spacing:.01em; }
.fxstat span{
  display:block; margin-top:.5rem; font-size:.9rem; color:var(--ink-soft);
  max-width:20ch; margin-inline:auto;
}
@media (max-width:860px){ .fxstats{ grid-template-columns:repeat(2,1fr); gap:2rem 1rem; } }
@media (max-width:420px){ .fxstats{ grid-template-columns:1fr; } }


/* ---------- Ruecksicht auf Bewegungsempfindlichkeit ----------
   Wer im Betriebssystem weniger Bewegung eingestellt hat, bekommt den
   ruhigen Zustand: Wolken stehen still, nichts kippt, die Zahlen stehen
   sofort auf dem Endwert (das erledigt effects.js). */
@media (prefers-reduced-motion:reduce){
  .aurora i{ animation:none; }
  .fx-tilt{ transform:none; }
  .project.fx-tilt:hover{ transform:translateY(-7px); }
  .fx-read span{ color:var(--ink); transition:none; }
}


/* ---------- 10. Gleitende Markierung im Kopfmenue ----------
   Vorher war der aktive Punkt schlicht ein weisser Hintergrund am Link.
   Beim Wechsel blendete der eine aus und der andere ein, zwei Bewegungen
   gleichzeitig, das sah unsauber aus. Jetzt liegt eine einzelne Pille
   hinter den Links und faehrt an die neue Stelle.

   Eingehaengt wird sie von effects.js Abschnitt 10. Nur wenn das geklappt
   hat, traegt die Leiste .nav--pill, und erst dann wird der alte
   Hintergrund am Link abgeschaltet. Ohne Javascript bleibt alles wie
   bisher. */
.nav{ position:relative; }
.nav a{ position:relative; z-index:1; }
.navpill{
  position:absolute; left:0; top:.28rem; bottom:.28rem; z-index:0;
  border-radius:100px; background:var(--white);
  box-shadow:0 1px 3px rgba(31,24,38,.09), 0 0 0 1px var(--accent-soft);
  opacity:0; transform:translateX(0) scaleX(.6); transform-origin:left center;
  pointer-events:none;
  transition:transform .5s var(--ease-hero), width .5s var(--ease-hero),
             opacity .35s var(--ease);
}
.navpill.is-da{ opacity:1; }
.nav--pill a.is-active{ background:transparent; box-shadow:none; }
.nav--pill a:hover{ background:transparent; }
/* Beim Ueberfahren nur die Schrift dunkler, die Flaeche gehoert der Pille. */
.nav--pill a:hover{ color:var(--ink); }

@media (prefers-reduced-motion:reduce){
  .navpill{ transition:opacity .2s linear; }
}


/* ---------- 11. Die Feinheiten zu den Paketen ----------
   Drei eigene Karten mit Abstand: links hell, was ohnehin dabei ist,
   rechts auf dunklem Grund, was zusaetzlich kostet, darunter flach die
   Zahlungsbedingungen. Getrennte Karten statt einer geteilten Flaeche,
   weil die beiden Seiten unterschiedlich lang sind: als eigenstaendige
   Objekte faellt das nicht auf, bei einer durchgehenden Flaeche mit
   Kante in der Mitte sieht es aus, als fehle unten etwas.

   style.css macht aus .planinfo eine weisse Karte. Das gilt hier nur noch
   fuer die Kopfzeile, der Inhalt darunter liegt frei auf dem Abschnitt.
   Markup: index.html, Block id="planinfo". Bewegung: effects.js Abschnitt 9. */
.pi{ background:transparent; border:0; box-shadow:none; overflow:visible; }
.pi > summary{
  background:var(--white); border:1px solid var(--line); border-radius:var(--r-lg);
  box-shadow:var(--sh-sm);
}
.pi[open] > summary{ border-bottom:1px solid var(--line); color:var(--accent-deep); }

.pi__grid{
  display:grid; grid-template-columns:1fr 1fr; gap:1.1rem;
  padding-top:1.1rem;
  /* align-items bleibt beim Standard "stretch": beide Karten sind damit
     gleich hoch und schliessen unten auf einer Linie ab, auch wenn die
     dunkle weniger Zeilen hat. */
}
.pi__h{
  font-family:var(--sans); font-size:.72rem; font-weight:600;
  letter-spacing:.16em; text-transform:uppercase; color:var(--accent);
  margin:0 0 1.2rem;
}
.planinfo .pi__grid ul{ list-style:none; margin:0; padding:0; }
.planinfo .pi__grid li::before{ content:none; display:none; }

.pi__karte{
  background:var(--white); border:1px solid var(--line); border-radius:var(--r-lg);
  padding:clamp(1.5rem,3vw,2.2rem); box-shadow:var(--sh-sm);
}

/* ----- helle Karte ----- */
.pi__karte--hell li{
  display:grid; grid-template-columns:auto 1fr; gap:.75rem;
  padding:.6rem .5rem; margin:0 -.5rem; border-radius:8px;
  align-items:baseline; transition:background-color .25s var(--ease);
}
.pi__karte--hell li svg{ color:var(--accent); margin-top:.3rem; flex:none; }
.pi__karte--hell b{ font-weight:500; font-size:.96rem; color:var(--ink); line-height:1.45; }
.pi__karte--hell span{ display:block; font-size:.85rem; color:var(--ink-mute); margin-top:.18rem; line-height:1.5; }

/* ----- dunkle Karte ----- */
.pi__karte--dunkel{
  position:relative; overflow:hidden;
  background:var(--dark); border-color:var(--dark); color:rgba(255,255,255,.7);
  box-shadow:var(--sh-md);
}
.pi__karte--dunkel .pi__h{ color:var(--accent-lite); }
.pi__karte--dunkel li{
  display:grid; grid-template-columns:1fr auto; gap:.26rem 1.2rem;
  align-items:baseline; padding:.8rem .5rem; margin:0 -.5rem; border-radius:8px;
  transition:background-color .25s var(--ease);
}
.pi__karte--dunkel li + li{ box-shadow:0 -1px 0 rgba(255,255,255,.1); }
.pi__karte--dunkel b{ font-weight:500; font-size:.96rem; color:#fff; line-height:1.4; }
.pi__n{ grid-column:1 / -1; font-size:.85rem; color:rgba(255,255,255,.5); line-height:1.5; }
/* Der Betrag steht in der Serifenschrift, in der auch die Paketpreise
   weiter oben gesetzt sind. font-style bewusst normal: der Schnitt hat
   eine Kursive, die sonst greift. */
.pi__p{
  grid-column:2; justify-self:end; white-space:nowrap;
  font-family:var(--display); font-style:normal; font-size:1.3rem; line-height:1.15;
  color:var(--accent-lite); font-variant-numeric:tabular-nums;
}
.pi__p small{ font-family:var(--sans); font-size:.7rem; color:rgba(255,255,255,.45); margin-left:.1rem; }
/* Posten ohne festen Betrag treten zurueck, damit die echten Zahlen
   die Spalte fuehren. */
.pi__p--soft{ font-family:var(--sans); font-size:.84rem; color:rgba(255,255,255,.5); }

@media (hover:hover) and (pointer:fine){
  .pi__karte--hell li:hover{ background:var(--bg-alt); }
  .pi__karte--dunkel li:hover{ background:rgba(255,255,255,.05); }
}

/* ----- flache Karte mit den Zahlungsbedingungen ----- */
.pi__fuss{
  grid-column:1 / -1;
  background:var(--white); border:1px solid var(--line); border-radius:var(--r-lg);
  box-shadow:var(--sh-sm);
  display:grid; grid-template-columns:repeat(3,1fr); gap:1.6rem;
  padding:1.4rem clamp(1.5rem,3vw,2rem);
}
.pi__fuss p{ margin:0; font-size:.86rem; color:var(--ink-mute); line-height:1.55; }
.pi__fuss b{ display:block; color:var(--ink); font-weight:600; font-size:.92rem; margin-bottom:.1rem; }

/* ----- Auftritt -----
   Warum .is-in[open] und nicht nur [open]: main.js klappt den Block auf
   breiten Schirmen schon beim Laden auf. Ohne die Bedingung waere die
   Bewegung laengst vorbei, bevor jemand so weit gescrollt hat. .is-in
   setzt der Beobachter aus main.js erst, wenn der Block ins Bild kommt.
   Klappt jemand ihn spaeter zu und wieder auf, wechselt [open] und
   dieselbe Regel greift erneut.

   Reihenfolge: die beiden Karten kommen hoch, die dunkle etwas spaeter,
   darin zeichnen sich die Haekchen und die Betraege schieben sich von
   rechts nach, zum Schluss die Fusskarte. */
.pi.is-in[open] .pi__karte{ animation:piKarte .55s var(--ease-hero) both; }
.pi.is-in[open] .pi__karte--dunkel{ animation-delay:.1s; }
.pi.is-in[open] .pi__fuss{ animation:piKarte .55s var(--ease-hero) .55s both; }
.pi.is-in[open] .pi__karte--hell li{ animation:piZeile .5s var(--ease-hero) both; animation-delay:calc(var(--i) * 50ms + 150ms); }
.pi.is-in[open] .pi__karte--dunkel li{ animation:piZeile .5s var(--ease-hero) both; animation-delay:calc(var(--i) * 50ms + 260ms); }
@keyframes piKarte{ from{ opacity:0; transform:translateY(16px); } to{ opacity:1; transform:none; } }
@keyframes piZeile{ from{ opacity:0; transform:translateY(10px); } to{ opacity:1; transform:none; } }

/* Der Haken wird gezeichnet, statt einfach dazustehen. Der Pfad ist rund
   25 Einheiten lang, deshalb 26 als Strichlaenge: etwas mehr, damit am
   Ende nichts stehen bleibt. */
.pi.is-in[open] .pi__karte--hell li svg path{
  stroke-dasharray:26; stroke-dashoffset:26;
  animation:piHaken .45s var(--ease) both;
  animation-delay:calc(var(--i) * 50ms + 260ms);
}
@keyframes piHaken{ to{ stroke-dashoffset:0; } }

/* Der Betrag setzt kurz nach seiner Zeile ein und schiebt sich von rechts
   an seinen Platz. So liest man erst den Posten, dann den Preis. */
.pi.is-in[open] .pi__p{
  animation:piBetrag .55s var(--ease-hero) both;
  animation-delay:calc(var(--i) * 50ms + 400ms);
}
@keyframes piBetrag{ from{ opacity:0; transform:translateX(16px); } to{ opacity:1; transform:none; } }

/* Einmal wandert ein Lichtschimmer ueber die dunkle Karte. */
.pi__karte--dunkel::after{
  content:""; position:absolute; inset:-10% -30%; pointer-events:none; z-index:2;
  background:linear-gradient(104deg,
    rgba(195,166,234,0) 38%, rgba(195,166,234,.14) 50%, rgba(195,166,234,0) 62%);
  transform:translateX(-120%);
}
.pi.is-in[open] .pi__karte--dunkel::after{ animation:piGlanz 1.6s var(--ease) .5s both; }
@keyframes piGlanz{ from{ transform:translateX(-120%); } to{ transform:translateX(120%); } }

@media (max-width:900px){
  .pi__grid{ grid-template-columns:1fr; }
  .pi__fuss{ grid-template-columns:1fr; gap:1.1rem; }
}
@media (max-width:520px){
  .pi__karte--dunkel li{ grid-template-columns:1fr; }
  .pi__p{ grid-column:1; justify-self:start; margin-top:.15rem; }
}

@media (prefers-reduced-motion:reduce){
  .pi.is-in[open] .pi__karte,
  .pi.is-in[open] .pi__fuss,
  .pi.is-in[open] .pi__karte--hell li,
  .pi.is-in[open] .pi__karte--dunkel li,
  .pi.is-in[open] .pi__p,
  .pi.is-in[open] .pi__karte--dunkel::after{ animation:none; }
  .pi.is-in[open] .pi__karte--hell li svg path{ animation:none; stroke-dashoffset:0; }
}

/* ---------- 12. Betreuung nach dem Livegang ----------
   Die Hoehe der Karte faehrt ueber effects.js Abschnitt 9 weich auf, dort
   laeuft dieselbe Mechanik wie beim Block darunter. Hier kommen die vier
   Zeilen darin gestaffelt nach, damit der Inhalt aufgebaut wird statt als
   fertiger Block zu erscheinen. Kuerzer getaktet als in der hellen Karte
   weiter unten, weil es weniger Zeilen sind und der Block sonst nachhinkt.

   Anders als dort haengt die Bewegung nicht an .is-in: die Karte klappt
   erst auf Klick auf, sie ist zu diesem Zeitpunkt also ohnehin im Bild. */
.care[open] li{
  animation:careZeile .45s var(--ease-hero) both;
  animation-delay:calc(var(--i) * 60ms + 60ms);
}
@keyframes careZeile{ from{ opacity:0; transform:translateY(9px); } to{ opacity:1; transform:none; } }

/* Der Haken wird gezeichnet, wie in der hellen Karte weiter unten.
   piHaken steht in Abschnitt 11. */
.care[open] li svg path{
  stroke-dasharray:26; stroke-dashoffset:26;
  animation:piHaken .45s var(--ease) both;
  animation-delay:calc(var(--i) * 60ms + 150ms);
}

@media (prefers-reduced-motion:reduce){
  .care[open] li{ animation:none; }
  .care[open] li svg path{ animation:none; stroke-dashoffset:0; }
}

/* Die WebP-Huelle darf das Layout nicht veraendern: mit
   display:contents bleibt das <img> Kind seines urspruenglichen
   Elternteils, alle Bildregeln greifen unveraendert weiter. */
picture{ display:contents; }

/* Die Vorschauen der Beispielwebsites sind Standbilder statt eingebetteter
   Seiten. style.css positioniert dort ein <iframe> absolut; fuer das Bild
   braucht es nur diese Zeile. Der abdeckende Link darueber bleibt
   unveraendert, an der Bedienung aendert sich nichts.
   Neu erzeugen lassen sich die Bilder mit: node tools/vorschaubilder.js */
.livepreview img{ width:100%; height:100%; object-fit:cover; object-position:top center; display:block; }
