@layer pb-reset, pb-thread;

/* ============================================================
   PINGBOOK · Base
   ------------------------------------------------------------
   Fonts, reset, document defaults, typography & shared element
   styles. Loaded after tokens.css, before component styles.
   ============================================================ */

/* ---- Fonts: ABC Diatype Rounded (display) — 3 weights ---- */
@font-face {
  font-family: "Diatype Rounded";
  src: url("../assets/fonts/diatype-rounded-500.woff2") format("woff2"),
       url("../assets/fonts/diatype-rounded-500.woff") format("woff");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Diatype Rounded";
  src: url("../assets/fonts/diatype-rounded-800.woff2") format("woff2"),
       url("../assets/fonts/diatype-rounded-800.woff") format("woff");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Diatype Rounded";
  src: url("../assets/fonts/diatype-rounded-900.woff2") format("woff2"),
       url("../assets/fonts/diatype-rounded-900.woff") format("woff");
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}

/* ---- Font: IBM Plex Mono (latin) — Tags, Tooltips, Zahlen ----
   Selbst gehostet (CSP erlaubt font-src 'self'), SIL OFL 1.1. */
@font-face {
  font-family: "IBM Plex Mono";
  src: url("../assets/fonts/ibm-plex-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("../assets/fonts/ibm-plex-mono-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* ---- Reset ----
   Liegt in der UNTERSTEN Kaskadenebene (13.08.): ungeschichtete
   Regeln schlagen jede @layer-Regel — der Universal-Reset hat damit
   sämtliche Innenabstände von components/request-thread.css
   (@layer pb-thread) auf 0 gezogen, der Anfrage-Verlauf stand ohne
   Polster da. Als Ebene verliert der Reset korrekt gegen
   Komponenten- UND Seiten-Stile. Reihenfolge steht oben in
   @layer pb-reset, pb-thread. */
@layer pb-reset {
  *,
  *::before,
  *::after { box-sizing: border-box; margin: 0; padding: 0; }
}

html, body { height: 100%; }

body {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  /* Osmo-Standard (13.08.): Fließtext läuft mit −1 % — die leichte
     Spannung, die den UI-Text „gesetzt" wirken lässt. Titel −3 %,
     H2 −2 % (layout.css). */
  letter-spacing: var(--track-body);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale; /* Firefox/macOS pendant — same grayscale rendering */
  text-rendering: optimizeLegibility;
}

svg { display: block; }
img, svg { max-width: 100%; }

/* Osmo Supply — Textauswahl als leiser Grau-Schleier statt System-Blau
   (Vault: rgba(gray, 0.2)). color-mix statt fester Wert, damit es in
   beiden Themes trägt. */
::selection {
  background: color-mix(in srgb, var(--text-faint) 25%, transparent);
  text-shadow: none;
}

/* Osmo Supply — Disable User Drag (Images)
   CSS covers every <img>; key components also set draggable="false".
   File-drop upload zones are unaffected (they listen for file drops). */
img { -webkit-user-drag: none; }

/* Osmo Supply — Disable User Select on product chrome.
   Entire shell is non-selectable; form fields stay editable/copyable.
   More-specific rules (e.g. invite link `user-select: all`) still win. */
.app {
  -webkit-user-select: none;
  user-select: none;
}
.app input,
.app textarea,
.app select,
.app [contenteditable] {
  -webkit-user-select: text;
  user-select: text;
}

a { color: inherit; text-decoration: none; }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

/* Touch: die 300ms-Verzögerung vor dem Klick abschalten (der Browser
   wartet sonst, ob ein Doppeltipp zum Zoomen folgt). */
:where(a, button, input, select, textarea, [role="button"], [tabindex]) {
  touch-action: manipulation;
}

input, textarea, select {
  font: inherit;
  color: inherit;
}

ul, ol { list-style: none; }

/* ---- Eyebrow / uppercase label ----
   Osmo-Paarung (29.08.): Eyebrows laufen in der MONO — Uppercase in
   der Grotesk war der halbe Weg. Mono trägt kein 700, die 500 ist
   die kräftigste geladene Stufe und reicht auf .75em völlig. */
.pb-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-faint);
}

/* ---- Keyboard focus ring (consistent across interactive elements) ---- */
:where(a, button, input, textarea, select, [tabindex]):focus-visible {
  outline: var(--ring-w) solid var(--ring);
  outline-offset: var(--ring-offset);
  /* outline follows each control's own border-radius — don't override it */
}
/* Programmatisch fokussierte Container (tabindex="-1": Fenster, Seitenkopf
   nach dem Seitenwechsel) zeigen beim Maus-Öffnen keinen Ring — nur der
   Tastatur-Fokus (:focus-visible) bleibt sichtbar (Design „Neu“, 01.10.2026). */
[tabindex="-1"]:focus:not(:focus-visible) { outline: none; }
/* Überschriften und Fenster-/Panel-Container sind keine Bedienelemente: der
   Fokus landet dort nur per Skript (Seitenwechsel, Sprunglink, Fenster) und
   zeigt nie einen Ring — der Browser hielt den Skript-Fokus nach dem
   Seitenwechsel oft für Tastatur-Fokus (Rahmen um die h1, Mathias 03.10.2026).
   Roving-Elemente mit tabindex="-1" (Reiter, Kalenderzellen) behalten den Ring. */
:is(h1, h2, h3, [role="dialog"], [role="tabpanel"], [role="group"], [role="status"])[tabindex="-1"]:focus { outline: none; }
/* Fensterrahmen, der beim Öffnen selbst den Fokus nimmt (overlay-focus.js
   trapFocus, 05.10.2026) — kein Bedienelement, also nie ein Ring. */
[data-pb-trap]:focus { outline: none; }
/* Nur für Screenreader: Live-Regionen (announce.js), versteckte
   Überschriften (Agent-Gespräch). Sichtbar bleibt nichts. */
.pb-sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* Feldfehler direkt unter dem Feld (util/field-error.js, WCAG 3.3.1) */
.pb-field-err {
  margin: 0.375rem 0 0;
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--bad-fg);
}
/* Wort mit Bindestrich zusammenhalten (keepHyphens() in util.js — Fenstertitel) */
.pb-nobr { white-space: nowrap; }
/* Harter Umbruch in Überschriften, der auf dem Handy zum Leerzeichen wird
   (Katalogtext mit <span class="pb-br"> </span> statt <br>, Design „Neu“) */
.pb-br { display: block; }
@media (max-width: 560px) { .pb-br { display: inline; } }

/* ---- Scroll-Sperre bei offenen Modals/Drawern ----
   Getoggelt von scripts/scroll-lock.js: jeder offene Scrim mit der
   Markerklasse `pb-modal-scrim` sperrt (plus LOCK_MARKERS).
   Das ist die EINZIGE Scroll-Sperre der App — `html.overlay-open`
   taugt dafür nicht: das steuert nur die Abdunklung und umfasst
   auch Popover, die bewusst nicht sperren (Kontomenü, Sortierung).

   scrollbar-gutter reserviert die Leiste dauerhaft, damit das
   Sperren die Seite nicht seitlich springen lässt. */
html { scrollbar-gutter: stable; }
html.scroll-locked { overflow: hidden; }

/* ---- Thin, calm scrollbars ----
   Daumen als Alpha-Tint statt fester Rampenstufe (Osmo-Standard:
   rgba(tint, α)) — hebt sich damit von JEDER Fläche gleich ab. */
* {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--text) 16%, transparent) transparent;
}
*::-webkit-scrollbar { width: 0.5625rem; height: 0.5625rem; }
*::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: var(--r-md);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* PROJ-2 (12.08.): Das frühere Persona-Gate <html class="no-send"> ist
   entfallen — die Auswahl-Leiste und "Hinzufügen" sind für alle sichtbar,
   auch für Freelancer. Gesperrt wird erst das ABSENDEN ohne Tarif
   (Membership-Hinweis in pb-request-panel, serverseitig
   MEMBERSHIP_REQUIRED). */

/* ---- Schloss-Gate: Tagessatz + Kontaktdaten (PROJ-2) ----
   Seit der Freischaltung JE PROFIL entscheidet nicht mehr eine globale
   Klasse, ob der Tagessatz sichtbar ist, sondern der Zustand des
   einzelnen Profils: pb-talent-card und pb-talent-detail rendern
   ENTWEDER den Wert ODER das Schloss (zwei Bedingungen — bezahlter
   Tarif und dieses Profil geöffnet). Hier steht deshalb nur noch, wie
   das Schloss aussieht, kein Tausch mehr.

   Die Preis-Sortierung verschwindet seit 20.08. NICHT mehr
   (html.rate-locked blendete sie aus): pb-sort-menu.js rendert die
   Zeilen ohne Berechtigung ausgegraut mit Schloss — sichtbar, aber
   nicht wählbar (Wunsch Mathias). */
.pb-fmeta-pill--rate .rate-lock {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem; /* Text ("Tagessatz" / "Gesperrt") + gefülltes Schloss */
}
.pb-fmeta-pill--rate .rate-lock svg { width: 13px; height: 13px; flex: none; }

/* ---- Generisches Profilfoto in einem runden Avatar-Slot ----
   Überall dort, wo sonst Initialen stehen: Auswahl-Leiste, Anfrage-Panel
   und -Modal, Checkout-Liste, Sende-Ablauf. Der Slot braucht nur
   `position: relative` + `overflow: hidden`, die Rundung wird geerbt.
   Liegt in base.css (auf JEDER Seite geladen) — avatar.css hängt nur an
   drei Seiten, die Slots erscheinen aber überall. */
.pb-av-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  border-radius: inherit;
}

/* ---- Rechtliches: dezenter Seitenfuß unten mittig (app.js hängt ihn an
   jeden Barba-Container bzw. an Auth-Seiten an) ---- */
.pb-legalfoot {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.4375rem;
  padding: 3rem 1rem 1.25rem;
  /* Seit 05.10.2026 (Mathias) im Stil der Demomodus-Zeile unter der
     Chatpille — gleiche Größe --fs-xs, etwas durchsichtiger als deren
     --text-faint (80 %: Dunkel 4,8:1 auf --bg, Hell 3,4:1 — bewusst unter
     dem Audit-Wert vom 03.10.2026, dort --text-soft in --fs-sm);
     seitenübergreifend. Hover hebt auf --text. */
  font-size: var(--fs-xs);
  color: color-mix(in srgb, var(--text-faint) 80%, transparent);
  /* im Flex-Column-Container (.app-main) ans untere Ende rutschen,
     wenn der Inhalt kürzer als der Viewport ist */
  margin-top: auto;
  /* bewusst OHNE z-index-Anhebung; im Agent-Intro tritt der Fuß zurück,
     dort stehen die Links unter der Chatpille (agent.css, 05.10.2026). */
}
/* Beim harten Laden steht der Fuß unten in der noch leeren Seite, bis der
   Inhalt ihn wegschiebt — eine Layout-Verschiebung von ~0,10 CLS. app.js
   hält ihn so lange unsichtbar (Platz bleibt), bis die Seite steht
   (Performance-Studie Punkt 1, 03.10.2026). */
.pb-legalfoot[data-pending] { visibility: hidden; }
/* ============================================================
   Touch-Ziele
   ------------------------------------------------------------
   WCAG 2.2 SC 2.5.8 nennt 24×24px als Untergrenze, Apple
   empfiehlt 44pt, Material 48dp. Wir ziehen die Steuerelemente
   auf Touch-Geräten auf 44px hoch — aber NUR dort: mit Maus
   sind die kompakten Grössen richtig und gewollt.

   Bewusst nicht enthalten: Fliesstext-Links (z. B. im Seitenfuss).
   Die sind laut 2.5.8 als „inline" ausgenommen; sie auf 44px zu
   ziehen würde den Textfluss zerreissen.
   ============================================================ */
@media (pointer: coarse) {
  /* Buttons: pb-btn-lg landete bei 42px (u. a. Anmelden/Registrieren
     im Fuss des Mobilmenüs) — zwei Pixel unter der 44er-Empfehlung.
     `:root` davor: button.css und request-panel.css laden NACH dieser
     Datei und setzten Höhe und Breite mit gleicher Spezifität zurück —
     bis zur Mobil-Prüfung vom 04.10.2026 blieben „Weiter“, „Registrieren“,
     die Fensterknöpfe und die ✕ darum bei 35–38px. */
  /* Seit 05.10.2026 (Mathias: „die Buttons auf Mobile sind alle seltsam
     hoch“) nicht mehr sichtbar 44px hoch: sie behalten ihre kompakte Höhe
     (Pflicht nach 2.5.8 sind 24px), nur die Trefferfläche wächst unsichtbar
     auf 44px — wie bei .pb-btn-icon. `::before`, weil `::after` den
     Lade-Spinner trägt; die Positionierung ohne Spezifität (`:where`), damit
     absichtlich anders positionierte Knöpfe sie behalten. */
  :where(.pb-btn-lg, .pb-btn-go) { position: relative; }
  :root :is(.pb-btn-lg, .pb-btn-go)::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: max(100%, var(--touch));
    transform: translateY(-50%);
  }

  /* Ergebnis-Toolbar: Sortieren / Ansicht */
  /* Sichtbar rund lassen (Mathias 06.10.2026: „oval nach oben verzogen“ —
     min-height 44px bei 37px Breite); die Trefferfläche wächst unsichtbar. */
  .tool-btn { position: relative; }
  .tool-btn::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: max(100%, var(--touch));
    height: max(100%, var(--touch));
    transform: translate(-50%, -50%);
  }
  .view-seg button { width: 2.5rem; height: 2.5rem; }

  /* Schliessen-Knöpfe (Drawer / Panel / Modal / Auth / Send-Flow) */
  :root .pb-btn-x { width: var(--touch); height: var(--touch); }
  :root :is(.drawer, .rqp) .mcal-arrow { width: var(--touch); height: var(--touch); }

  /* Kleine Einzelknöpfe ohne Nachbarn in Griffweite (✕ im Talent-Fenster
     und im Hinweis-Streifen, Merken-Stern auf der Karte, ✕ der
     Trefferzeile, Schalter der Einstellungen): sichtbar bleiben sie klein,
     die Trefferfläche wächst unsichtbar auf 44px — dasselbe Muster wie
     .pb-btn-icon (button.css). */
  .sec-count-x { position: relative; }
  :is(.td2-x, .pb-star, .pb-strip-x, .sec-count-x, .set-toggle)::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: var(--touch);
    height: var(--touch);
    transform: translate(-50%, -50%);
  }

  /* Kalendertage: die Zelle bleibt schmal (7 pro Zeile passen sonst
     nicht), bekommt aber volle Höhe — 2.5.8 erlaubt das Unterschreiten
     in einer Achse, wenn der Abstand zum Nachbarn stimmt. */
  .drawer .cd,
  .rqp .cd { min-height: var(--touch); }

  /* Formularfelder: 16px Mindestgrösse verhindert, dass iOS beim
     Fokussieren automatisch in das Feld zoomt. */
  input, select, textarea { font-size: max(16px, var(--fs-body)); min-height: var(--touch); }
  /* Felder, die ihre Grösse in ihrem eigenen Stylesheet setzen, sind
     hier nicht erreichbar: gleiche Spezifität, aber die Seiten-CSS
     lädt später und gewinnt. Die stehen bei sich selbst — siehe
     `.wa-notes textarea` in pages/anfragen.css. */
}

.pb-legalfoot a { color: inherit; text-decoration: none; transition: color var(--dur) var(--ease); }
.pb-legalfoot a:hover { color: var(--text); text-decoration: underline; text-underline-offset: 0.15em; }

/* ============================================================
   Preloader (Mathias 05.10.2026): das Buch der V3-Logo-Animation
   (design/v3/lottie/logo-animation.json) mit seinen drei Funken, darunter
   daneben der Schriftzug; einmal je Sitzung beim ersten Laden, mittig auf
   dem Seitengrund. Einblenden, das Buch schlägt auf und fächert einmal
   durch, gleitet dabei nach links und legt „Pingbook“ frei, die Funken
   ploppen nacheinander auf und funkeln je einmal (wie im Entwurfsvideo des
   Illustrators), dann gleich ausblenden.
   Kein Player: das Buch als Vektor-Streifen (preloader-book.svg, alle 52
   Bilder der Vorlage mit potrace nachgezogen, Strich um 2 px verdickt,
   gepackt ≈ 85 KB), die Funken als nachgezeichnete Vektorstriche im Kopf-Skript.
   Das Kopf-Skript der App-Seiten legt #pb-pre an, setzt das Buch-SVG inline
   ein und schaltet „go“ erst dann (sonst nach 1,5 s weg); nie bei
   reduzierter Bewegung, nie im Testbrowser. Farben aus den Tokens wie die
   Buch-Ladeanzeige des Agenten (agent.css), je Theme. Der Strich
   der Animation ist um 2 px (Vorlage 227 breit) verdickt, damit er zum Logo
   passt. Tempo-Variablen nie --dur nennen (Token, 0.2s).
   ============================================================ */
#pb-pre {
  /* Farben aus den Tokens (Mathias 05.10.2026): Seiten hell, Tinte dunkel;
     Funken in der Tinte. */
  /* Rangfolge im Kontrast (Mathias 05.10.2026): das Buch am stärksten, die
     Funken als zweites. Hell: Tinte eine Stufe dunkler als das aktive
     Menü-Icon, Funken im Ton des aktiven Menü-Icons (sidebar.css .nav-ic.on
     .nm); der Schriftzug in der Tinte. */
  --sheet: #fff; --ink: color-mix(in srgb, var(--text-soft) 35%, var(--text));
  --tile: var(--bg); --spark: color-mix(in srgb, var(--text-soft) 70%, var(--text));
  --word: var(--ink);
  --pre-book: 1.8s; --pre-w: 4.876rem; /* 5,418rem − 10 % (Mathias 05.10.2026) */
  position: fixed; inset: 0; z-index: 2147483000;
  display: grid; place-items: center;
  background: var(--bg);
}
/* Handy: Buch, Funken und Schriftzug gemeinsam 15 % kleiner (Mathias
   06.10.2026) — das Verhältnis bleibt, alles hängt an --pre-w; bei 390px ist
   die Grundschrift dieselbe wie am Desktop, das Logo wirkte nur größer. */
@media (max-width: 767.98px) { #pb-pre { --pre-w: calc(4.876rem * 0.85); } }
/* Dunkel wie die Agent-Ladeanzeige: Seiten hell, Tinte dunkel; Rahmen und Funken
   im Seitenton, damit sie auf dem dunklen Grund stehen. */
[data-theme="dark"] #pb-pre {
  /* Seiten so hell wie die Funken wirken lassen (Mathias 05.10.2026): als Fläche
     mit dunklen Linien darin erscheint derselbe Ton dunkler als der feine
     Funkenstrich — die Seiten liegen deshalb etwas heller (88 % statt 78 %). */
  --sheet: color-mix(in srgb, var(--text) 94%, var(--bg));
  /* Einband und Strich ein ruhiges Grau knapp über dem Grund (Mathias
     05.10.2026: heller als --bg, aber grauer und dunkler als vorher). */
  --ink: color-mix(in srgb, var(--text) 11%, var(--bg));
  /* Dunkel dieselbe Rangfolge: Seiten am hellsten (94 %), Funken 72 %; der
     Schriftzug im Seitenton. */
  --spark: color-mix(in srgb, var(--text) 72%, var(--bg));
  --word: var(--sheet);
}
/* Lockup (Mathias 05.10.2026): während das Buch auffächert, gleitet es nach
   links und legt dabei rechts daneben den Schriftzug frei — am Ende stehen
   Buch und „Pingbook“ nebeneinander, die Gruppe mittig. Maße in --pre-w (W):
   Buch sichtbar 0,938 W breit, Mitte bei 0,469 W; Schrift 0,39 W hoch
   ≈ 1,65 W breit; Abstand 0,16 W (der rechte Funke ragt 0,1 W über das Buch) →
   das Buch rückt um 0,905 W nach links, der Schriftzug beginnt 0,276 W links
   der Mitte, auf Höhe der Buchmitte.
   Nie überschneiden: eine weiche Transparenzmaske über dem Schriftzug, deren
   Kante im selben Takt wie das Buch mitläuft — sichtbar ist nur, was rechts von
   Buch und Funken liegt; dazu blendet der Schriftzug ein. Der Verlauf ist 0,3 W
   breit (Mathias 05.10.2026); damit das „P“ am Ende voll deckt, gleitet die Maske
   nach dem Buch noch 0,24 W allein weiter — dann liegen die Funken schon links
   vom Schriftzug. */
#pb-pre .pre-logo { position: relative; display: block; width: var(--pre-w); aspect-ratio: 227 / 190; }
#pb-pre .pre-logo .pre-stage { position: absolute; inset: 0; width: auto; }
#pb-pre .pre-word {
  position: absolute; left: calc(50% - var(--pre-w) * 0.276); top: calc(50% - 0.75rem);
  display: block; translate: 0 -50%;
  font: normal 800 calc(var(--pre-w) * 0.39) / 1.2 var(--font-display); letter-spacing: -0.01em;
  color: var(--word); white-space: nowrap;
  /* Maskenbild 4 W breit: durchsichtig bis 1,7 W, weicher Übergang, ab 2 W deckend;
     Position = deckende Kante − 2 W. */
  -webkit-mask: linear-gradient(to right, transparent calc(var(--pre-w) * 1.7), #000 calc(var(--pre-w) * 2)) no-repeat;
  mask: linear-gradient(to right, transparent calc(var(--pre-w) * 1.7), #000 calc(var(--pre-w) * 2)) no-repeat;
  -webkit-mask-size: calc(var(--pre-w) * 4) 100%; mask-size: calc(var(--pre-w) * 4) 100%;
  -webkit-mask-position: calc(var(--pre-w) * -0.855) 0; mask-position: calc(var(--pre-w) * -0.855) 0;
  opacity: 0;
}
#pb-pre.go .pre-word {
  animation:
    pb-pre-reveal 1.1s linear both,
    pb-pre-word-in 0.95s cubic-bezier(0.4, 0, 0.6, 1) 0.05s both, /* blendet über die ganze Gleitbewegung ein */
    pb-pre-fade-out 0.4s ease calc(var(--pre-book) + 0.15s) forwards;
}
/* Absender unten mittig, klein und unscheinbar (Mathias 05.10.2026); blendet
   mit dem Buch ein und aus. */
#pb-pre .pre-by {
  position: fixed; left: 0; right: 0; bottom: 2rem; display: block;
  font: normal 500 var(--fs-xs) / 1 var(--font-body);
  color: var(--text-faint); text-align: center; letter-spacing: 0.01em;
  opacity: 0;
}
#pb-pre.go .pre-by {
  animation:
    pb-pre-fade-in 0.4s ease 0.2s both,
    pb-pre-fade-out 0.4s ease calc(var(--pre-book) + 0.15s) forwards;
}
/* 0–0,85 s im Takt des Buchs (gleiche Kurve), danach 0,25 s allein weiter. */
@keyframes pb-pre-reveal {
  0%     { -webkit-mask-position: calc(var(--pre-w) * -0.855) 0; mask-position: calc(var(--pre-w) * -0.855) 0;
           animation-timing-function: cubic-bezier(0.6, 0, 0.2, 1); }
  77.27% { -webkit-mask-position: calc(var(--pre-w) * -1.76) 0; mask-position: calc(var(--pre-w) * -1.76) 0;
           animation-timing-function: ease-in-out; }
  100%   { -webkit-mask-position: calc(var(--pre-w) * -2) 0; mask-position: calc(var(--pre-w) * -2) 0; }
}
@keyframes pb-pre-word-in { from { opacity: 0; } to { opacity: 1; } }
/* Optisch mittig: In der Lottie-Fläche (227 × 190) liegt das aufgefächerte
   Buch bei x 0–213, y 0–143 — links oben, unten nur ein Funke. Die Fläche
   rückt deshalb um 7 / 23,5 Einheiten, damit das Buch selbst in der Mitte
   steht. */
#pb-pre .pre-stage {
  position: relative; display: block; width: var(--pre-w); aspect-ratio: 227 / 190;
  transform: translate(3.1%, 12.4%); translate: 0 -0.75rem;
}
/* Ablauf in Stufen, weich (Mathias 05.10.2026): Buch ein, gleitet nach links,
   Buch und Schriftzug aus, zuletzt der Grund. */
#pb-pre.go .pre-stage {
  animation:
    pb-pre-fade-in 0.4s ease both,
    pb-pre-slide 0.85s cubic-bezier(0.6, 0, 0.2, 1) both, /* gleitet von Anfang an */
    pb-pre-fade-out 0.4s ease calc(var(--pre-book) + 0.15s) forwards;
}
@keyframes pb-pre-slide {
  from { translate: 0 -0.75rem; }
  to   { translate: calc(var(--pre-w) * -0.905) -0.75rem; }
}
@keyframes pb-pre-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes pb-pre-fade-out { from { opacity: 1; } to { opacity: 0; } }
/* Etwas Leben, physikalisch gedacht (Mathias 05.10.2026): beim Aufklappen
   wandert das Gewicht der Seiten nach links — das Buch hebt sich leicht, kippt
   mit den fliegenden Seiten ein paar Grad nach links, federt gedämpft zurück
   und liegt still, wenn der Fächer steht (die Seiten fliegen in den ersten
   ~55 % der Bilder). Drehpunkt ist der Buchrücken unten, nicht die Mitte;
   nur das Buch bewegt sich, die Funken bleiben an ihrem Platz. */
#pb-pre .pre-book { transform-origin: 42% 66%; }
#pb-pre.go .pre-book { animation: pb-pre-sway var(--pre-book) linear both; }
@keyframes pb-pre-sway {
  /* etwa halb so viel Ausschlag wie zuerst (Mathias 05.10.2026) */
  0%   { rotate: 0.5deg;  translate: 0 0.8%;  scale: 0.985; animation-timing-function: cubic-bezier(0.3, 0, 0.4, 1); }
  30%  { rotate: -1.8deg; translate: 0 -1.3%; scale: 1.008; animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1); }
  52%  { rotate: 0.8deg;  translate: 0 0.3%;  scale: 1;     animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1); }
  70%  { rotate: -0.3deg; translate: 0 -0.15%; animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1); }
  85%  { rotate: 0.1deg;  translate: 0 0; }
  100% { rotate: 0deg;    translate: 0 0;     scale: 1; }
}
/* Das Buch ist Vektor (preloader-book.svg, vom Kopf-Skript inline eingesetzt):
   52 vektorisierte Bilder nebeneinander, der Streifen rückt Bild für Bild
   nach links — scharf in jeder Größe, Farben per Token (.pf Seiten, .pi Tinte). */
#pb-pre .pre-book { position: absolute; inset: 0; display: block; }
#pb-pre .pre-book svg { display: block; width: 100%; height: 100%; }
#pb-pre .pre-book .pf { fill: var(--sheet); }
#pb-pre .pre-book .pi { fill: var(--ink); }
#pb-pre.go .pre-strip { animation: pb-pre-frames var(--pre-book) steps(51, end) forwards; }
/* Sketch-Look wie die Funken (Mathias 05.10.2026): die Buchlinien „kochen“ —
   drei leicht verwackelte Fassungen (feTurbulence + feDisplacementMap, nur auf
   der sichtbaren Fläche) wechseln mit 12 fps, so wirkt der Strich gezeichnet
   statt glatt. */
#pb-pre.go .pre-boil { animation: pb-pre-bookboil 0.25s steps(1, end) infinite; }
@keyframes pb-pre-bookboil {
  0%      { filter: url(#pbb1); }
  33.33%  { filter: url(#pbb2); }
  66.66%  { filter: url(#pbb3); }
}
@keyframes pb-pre-frames { to { transform: translateX(-11577px); } }

/* Funken im Sketch-Toon-Stil (Mathias 05.10.2026): die drei Funken des Logos
   als saubere Vektorstriche nachgezeichnet (vierzackig, senkrecht gestreckt,
   eingezogene Seiten, runde Enden), dazu zwei leicht verwackelte Fassungen
   wie von Hand. Animiert wie Zeichentrick mit 12 Bildern pro Sekunde (wie im
   Entwurfsvideo KatalogKreativEntwurfV01.mov): der Strich zeichnet sich ein,
   der Funke ploppt mit Squash & Stretch, die Linien „kochen“ (die drei
   Fassungen wechseln einander ab), später wächst er einmal groß und schrumpft
   klein wie im Video. Stufig (steps) statt weich. Sie stehen fest, nur das
   Buch bewegt sich. Größe/Lage aus den Funken der Lottie-Fläche (227 × 190):
   --sw = Höhe des Funkens / 227, --x/--y = Mittelpunkt. */
#pb-pre .pre-sp {
  position: absolute; display: block; width: var(--sw); aspect-ratio: 1;
  left: calc(var(--x) - var(--sw) / 2); top: calc(var(--y) - var(--sw) * 227 / 190 / 2);
  opacity: 0;
}
#pb-pre .pre-sp svg { display: block; width: 100%; height: 100%; overflow: visible; }
#pb-pre .pre-sp path {
  /* Strichstärke wie die Buchlinien: 3,8 Einheiten der Lottie-Fläche, geteilt
     durch die Höhe des Funkens (23 bzw. 35) — gleich dick, egal wie groß. */
  fill: none; stroke: var(--spark); stroke-width: var(--st); stroke-linejoin: round; stroke-linecap: round;
  stroke-dasharray: 1; stroke-dashoffset: 0.75;
}
#pb-pre .pre-sp1 { --sw: 10.1%; --st: 0.165; --x: 9.47%;  --y: 12.11%; --pop: 0.1s;  --tw: 0.95s; }
#pb-pre .pre-sp2 { --sw: 15.4%; --st: 0.109; --x: 94.49%; --y: 74.74%; --pop: 0.55s; --tw: 1.4s; }
#pb-pre .pre-sp3 { --sw: 10.1%; --st: 0.165; --x: 82.6%;  --y: 93.68%; --pop: 0.8s;  --tw: 1.65s; }
#pb-pre.go .pre-sp {
  animation:
    pb-pre-toon-pop 0.42s linear var(--pop) both,
    pb-pre-toon-twinkle 0.5s linear var(--tw);
}
/* Strich einzeichnen in drei Stufen ab dem ersten sichtbaren Bild (Ploppen
   beginnt unsichtbar, siehe unten) — das erste Bild zeigt schon ein Viertel des
   Strichs, kein einzelner Punkt vorab (Mathias 05.10.2026). */
#pb-pre.go .pre-sp path { --vis: calc(var(--pop) + 0.0735s); animation: pb-pre-draw 0.25s steps(3, end) var(--vis) forwards, pb-pre-boil 0.25s steps(1, end) var(--vis) infinite; }
/* Linien kochen: jede der drei Fassungen ist ein Drittel der Zeit sichtbar (12 fps). */
#pb-pre.go .pre-sp path:nth-child(2) { animation-delay: var(--vis), calc(var(--vis) - 0.0833s); }
#pb-pre.go .pre-sp path:nth-child(3) { animation-delay: var(--vis), calc(var(--vis) - 0.1667s); }
@keyframes pb-pre-draw { to { stroke-dashoffset: 0; } }
@keyframes pb-pre-boil { 0% { opacity: 1; } 33.34%, 100% { opacity: 0; } }
/* Erstes Halbbild unsichtbar, dann breit gedrückt, hochgestreckt, sitzt
   (jump-none: es springt genau zwischen den Zuständen, wie Zeichentrick). */
@keyframes pb-pre-toon-pop {
  0%   { opacity: 0; transform: scale(1.3, 0.8); animation-timing-function: steps(2, jump-none); }
  35%  { opacity: 1; transform: scale(1.3, 0.8); animation-timing-function: steps(2, jump-none); }
  65%  { opacity: 1; transform: scale(0.85, 1.2); animation-timing-function: steps(2, jump-none); }
  100% { opacity: 1; transform: none; }
}
@keyframes pb-pre-toon-twinkle {
  0%   { scale: 1; animation-timing-function: steps(2, end); }
  40%  { scale: 1.45; animation-timing-function: steps(2, end); }
  75%  { scale: 0.6; animation-timing-function: steps(1, end); }
  100% { scale: 1; }
}
/* Zuletzt blendet der Grund aus, nachdem Buch und Schriftzug verschwunden sind. */
#pb-pre.go { animation: pb-pre-out 0.45s ease forwards; animation-delay: calc(var(--pre-book) + 0.45s); }
@keyframes pb-pre-out { to { opacity: 0; visibility: hidden; } }
@media (prefers-reduced-motion: reduce) { #pb-pre { display: none; } }
