Zum Inhalt
← Preloader & Intro

Logo-Aufbau-Intro (Linien zeichnen, Teile fügen sich, Logo fliegt in die Navigation)

Ein Brillant zeichnet sich Facette für Facette, die Wortmarke steigt aus der Maske, dann fliegt die Marke per Flip in die Navigation und die dunkle Fläche rollt nach oben ab.

Das Logo baut sich mittig auf: SVG-Konturen zeichnen sich, Buchstaben steigen aus Masken oder geometrische Teile fügen sich zusammen. Nach einer kurzen Haltezeit schrumpft das Logo und fliegt an seinen Platz in der Navigation (Flip). Gleichzeitig öffnet sich die Fläche dahinter.

Die Marke prägt sich als Erstes ein. Weil das Logo in die Navigation fliegt, gehen Intro und Seite ohne toten Moment ineinander über.

Wann einsetzen

Bei Markenrelaunches, Häusern mit starkem Bildzeichen und Kampagnen. Nur auf der Startseite.

Wann nicht

Bei Wortmarken ohne grafische Substanz, das wirkt aufgebläht. Nicht auf Unterseiten.

Varianten

  • Kontur zeichnen (stroke-dashoffset oder DrawSVG)
  • Buchstaben steigen aus Masken
  • Geometrische Teile fügen sich zusammen
  • Logo-Flug in die Navigation (Flip)
  • Mit Ton (Nische)

Umsetzung

SVG-Pfade: strokeDasharray und strokeDashoffset auf die Pfadlänge setzen und auf 0 animieren, oder DrawSVGPlugin nutzen. Danach die Füllung einblenden. Für den Flug ist das Intro-Logo dasselbe Element wie das Logo in der Navigation: Zustand mit Flip.getState merken, Element in die Navigation hängen, dann Flip.from(state, { scale: true, duration: 1, ease: 'expo.inOut' }). Fallstrick: Für Fill-Logos mit variabler Strichstärke eine eigene Kontur-Version zeichnen. Beim Skalieren wächst auch die Strichstärke mit, deshalb stroke-width im Flug mitführen.

Robust umsetzen

Telefon. Kürzen, unter 1,5 s gesamt.

Ohne Bewegung. Kein Intro. Das Logo steht von Anfang an in der Navigation.

Barrierefrei. Das Intro-SVG ist aria-hidden. Das echte Logo in der Navigation trägt den zugänglichen Namen.

Code

<!DOCTYPE html>
<!--
  Logo-Aufbau-Intro (Linien zeichnen, Teile fügen sich, Logo fliegt in die Navigation)
  Fundus · Preloader & Intro · https://cecodesign.de/lab/fundus/preloader-intro/logo-aufbau-intro/
  Eigenständige Datei. Bilder liegen hier unter bilder/ – eigene einsetzen.
-->
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Logo-Aufbau-Intro (Linien zeichnen, Teile fügen sich, Logo fliegt in die Navigation) · Fundus-Demo</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<style>
@font-face { font-family: 'Bodoni Moda'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/bodoni-moda:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 400 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Bodoni Moda'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/bodoni-moda:vf@latest/latin-opsz-italic.woff2') format('woff2-variations'); font-weight: 400 900; font-style: italic; font-display: swap; }
@font-face { font-family: 'Instrument Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-sans:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 400 700; font-display: swap; }
@font-face { font-family: 'Geist Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/geist-mono:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }

/* Sanner Schmuckmanufaktur, Pforzheim · Rezept „Manufaktur-Stille“:
   Anthrazit und Weiß im Wechsel, Messing nur als Linie, Bodoni Moda für Titel, gesperrte Versalien für Labels */
:root {
  --anthrazit: #23282f; --weiss: #ffffff; --flaeche: #f4f4f2; --meta: #686d74; --linie: #d9dadb; --messing: #a88a5a;
  --hell-leise: rgba(255, 255, 255, .58); --hell-linie: rgba(255, 255, 255, .14);
  --zeichnen: cubic-bezier(0.65, 0, 0.35, 1);  /* power2.inOut, Messwert Zeichnen */
  --flug: cubic-bezier(0.87, 0, 0.13, 1);      /* expo.inOut, Messwert Flug */
  --ui: cubic-bezier(0.1, 0.6, 0.4, 1);        /* Rezept: NOMOS default, UI 150 ms */
  --pult: 56px;
  --gx: clamp(20px, 3.4vw, 56px);
}
* { box-sizing: border-box; margin: 0; }
html { background: var(--weiss); color: var(--anthrazit); font-family: 'Instrument Sans', system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
html, body { overflow-x: clip; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }
.label { font: 500 12px/1.4 'Instrument Sans', system-ui, sans-serif; letter-spacing: .08em; text-transform: uppercase; }
.serif { font-family: 'Bodoni Moda', 'Times New Roman', serif; font-optical-sizing: auto; font-weight: 400; }

/* ── Bühne: die fertige Seite ── */
.buehne { position: relative; height: calc(100svh - var(--pult)); min-height: 600px; display: grid; grid-template-rows: auto minmax(0, 1fr); }

.kopf { position: relative; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; height: 76px; padding: 0 var(--gx); border-bottom: 1px solid var(--linie); background: var(--weiss); }
.kopf ul { display: flex; gap: clamp(18px, 2.2vw, 34px); list-style: none; padding: 0; }
.kopf ul a, .kopf .rechts a { position: relative; transition: color 150ms var(--ui); }
.kopf ul a::after, .termin::after { content: ''; position: absolute; left: 0; right: 0; bottom: -5px; height: 1px; background: var(--messing); transform: scaleX(0); transform-origin: left; transition: transform 250ms var(--ui); }
.kopf ul a:hover::after, .kopf ul a[aria-current]::after { transform: scaleX(1); }
.rechts { display: flex; justify-content: flex-end; align-items: center; gap: clamp(16px, 2vw, 30px); }
.rechts .leise { color: var(--meta); }
.termin::after { transform: scaleX(1); }
.termin:hover::after { transform: scaleX(.4); }
.menue { display: none; }

/* Die Marke: Bildzeichen (Brillant von oben) und Wortmarke. Beide reisen beim Intro. */
.marke { display: inline-flex; align-items: center; gap: 12px; justify-self: center; }
.zeichen { position: relative; display: block; width: 34px; height: 34px; flex: none; overflow: visible; --sw: 3.1; }
.zeichen polygon, .zeichen circle { fill: none; stroke: var(--messing); stroke-width: var(--sw); stroke-linejoin: round; vector-effect: none; }
.zeichen .f { fill: var(--fuell, var(--messing)); fill-opacity: 0; transition: fill 250ms var(--ui); }
.wort { position: relative; display: inline-flex; flex-direction: column; align-items: center; color: var(--anthrazit); }
.wort .name { display: flex; font: 400 21px/1 'Bodoni Moda', serif; font-optical-sizing: auto; letter-spacing: .24em; margin-right: -.24em; }
.wort .name > span { display: block; overflow: hidden; padding: .08em 0 .06em; margin: -.08em 0 -.06em; }
.wort .name > span > span { display: block; }

/* Hero: Text auf Anthrazit, Produkt auf Hellgrau */
.held { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); min-height: 0; }
.text { background: var(--anthrazit); color: var(--weiss); display: grid; grid-template-rows: auto 1fr auto; gap: 28px; padding: clamp(28px, 4.4vh, 52px) var(--gx) clamp(26px, 4vh, 44px); min-height: 0; }
.text .label { color: var(--hell-leise); display: flex; justify-content: space-between; gap: 16px; }
.kern { align-self: center; display: grid; gap: clamp(16px, 2.6vh, 26px); }
.titel { font: 400 clamp(2.5rem, 5.5vw, 5.5rem)/1.05 'Bodoni Moda', serif; font-optical-sizing: auto; letter-spacing: -.01em; text-wrap: balance; }
.titel em { font-style: italic; }
.lead { font-size: .9375rem; line-height: 1.47; max-width: 40ch; color: rgba(255, 255, 255, .78); text-wrap: pretty; }
.daten { display: grid; grid-template-columns: auto 1fr; gap: 8px 22px; padding-top: 18px; border-top: 1px solid var(--hell-linie); font-size: .875rem; font-variant-numeric: tabular-nums; }
.daten dt { color: var(--hell-leise); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; padding-top: 2px; }
.handeln { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 26px; }
.knopf { display: inline-flex; align-items: center; height: 46px; padding: 0 22px; border: 1px solid var(--messing); color: var(--weiss); transition: background-color 150ms var(--ui), color 150ms var(--ui); }
.knopf:hover { background: var(--messing); color: var(--anthrazit); }
.knopf:focus-visible, .kopf a:focus-visible, .pult button:focus-visible { outline: 1px solid var(--messing); outline-offset: 4px; }
.weiter { color: var(--hell-leise); transition: color 150ms var(--ui); }
.weiter:hover { color: var(--weiss); }

.produkt { position: relative; background: var(--flaeche); display: grid; place-items: center; overflow: hidden; min-height: 0; }
.produkt svg { height: min(76%, 640px); width: auto; max-width: 86%; }
.produkt .ecke { position: absolute; color: var(--meta); }
.produkt .lo { left: var(--gx); top: 24px; }
.produkt .ro { right: var(--gx); top: 24px; font-variant-numeric: tabular-nums; }
.produkt .lu { left: var(--gx); bottom: 22px; }
.produkt .ru { right: var(--gx); bottom: 22px; display: flex; gap: 8px; }
.punkt { width: 6px; height: 6px; border-radius: 50%; border: 1px solid var(--meta); }
.punkt.an { background: var(--anthrazit); border-color: var(--anthrazit); }

/* ── Intro-Fläche: hier stehen Zeichen und Wortmarke, bis sie in die Navigation fliegen ── */
.intro { position: absolute; inset: 0; z-index: 10; display: none; background: var(--anthrazit); color: var(--weiss); clip-path: inset(0 0 0 0); }
.buehne[data-zustand='intro'] .intro, .buehne[data-zustand='flug'] .intro { display: grid; place-items: center; }
.i-mitte { display: grid; justify-items: center; gap: clamp(22px, 3.4vh, 34px); }
.i-zeichen { width: clamp(124px, 12.5vw, 176px); aspect-ratio: 1; }
.i-zeichen .zeichen { width: 100%; height: 100%; --sw: .82; --fuell: #efe3c8; }
.i-wort .wort { color: var(--weiss); }
.i-wort .wort .name { font-size: clamp(40px, 4.6vw, 66px); }
.i-zeile { color: var(--hell-leise); }
.i-ecke { position: absolute; color: var(--hell-leise); }
.i-ecke.lu { left: var(--gx); bottom: 26px; }
.i-ecke.ru { right: var(--gx); bottom: 26px; text-align: right; font-variant-numeric: tabular-nums; }
.buehne[data-zustand='flug'] .reist { z-index: 20; }
/* während des Intros ist die Seite darunter fertig, aber noch nicht bedienbar */
.buehne[data-wartet] > :not(.intro) { visibility: hidden; }

/* ── Pult: Varianten und Werte (liegt unter der Bühne, nicht darüber) ── */
.pult { height: var(--pult); display: flex; align-items: center; justify-content: space-between; gap: 10px 28px; padding: 0 var(--gx); border-top: 1px solid var(--linie); white-space: nowrap; font: 500 10.5px/1.4 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--meta); }
.modi { display: flex; gap: 4px; align-items: center; flex-wrap: nowrap; flex-shrink: 0; }
.modi > span { margin-right: 8px; }
.modi button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--meta); background: none; border: 1px solid var(--linie); height: 28px; padding: 0 11px; cursor: pointer; transition: background-color 150ms var(--ui), color 150ms var(--ui), border-color 150ms var(--ui); }
.modi button:hover { color: var(--anthrazit); border-color: var(--meta); }
.modi button[aria-pressed='true'] { background: var(--anthrazit); border-color: var(--anthrazit); color: var(--weiss); }
.modi .nochmal { border-color: transparent; }
.werte { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 2px 14px; letter-spacing: .08em; min-width: 0; }
.werte b { color: var(--anthrazit); font-weight: 500; }

/* Reduzierte Bewegung: kein Intro, Marke steht in der Navigation, kurzer Fade */
.reduziert .intro { display: none !important; }
.reduziert .buehne { animation: einblenden 250ms linear both; }
@keyframes einblenden { from { opacity: 0; } }
.reduziert .kopf a::after, .reduziert .knopf, .reduziert .modi button { transition: none; }

/* Vorschau: nur die Bühne */
.vorschau body { overflow: hidden; }
.vorschau .pult { display: none; }
.vorschau .buehne { height: 100svh; min-height: 0; }

@media (max-width: 1180px) {
  .kopf ul li:nth-child(n+3) { display: none; }
  .rechts .leise { display: none; }
  .pult { --pult: 84px; flex-direction: column; align-items: flex-start; justify-content: center; gap: 8px; }
  :root { --pult: 84px; }
  .werte { justify-content: flex-start; }
}
@media (max-width: 760px) {
  :root { --pult: auto; }
  .buehne { height: auto; min-height: 100svh; grid-template-rows: auto auto; }
  .kopf { height: 64px; }
  .kopf ul { display: none; }
  .menue { display: inline; justify-self: start; }
  .termin .lang { display: none; }
  .marke { gap: 9px; }
  .zeichen { width: 28px; height: 28px; }
  .wort .name { font-size: 18px; }
  .held { grid-template-columns: 1fr; }
  .produkt { order: -1; height: 48svh; min-height: 300px; }
  .produkt svg { height: 82%; }
  .text { gap: 22px; padding-top: 28px; }
  .titel { font-size: clamp(2.5rem, 12vw, 3.4rem); }
  .intro { position: fixed; height: 100svh; }
  .pult { height: auto; padding-top: 12px; padding-bottom: 14px; }
  .werte { display: none; }
  .modi { flex-wrap: wrap; row-gap: 6px; }
  .pult { max-width: 100%; }
}
@media (hover: none) {
  .kopf ul a:hover::after { transform: scaleX(0); }
  .termin:hover::after { transform: scaleX(1); }
  .knopf:hover { background: none; color: var(--weiss); }
  .modi button:hover { color: var(--meta); border-color: var(--linie); }
  .modi button[aria-pressed='true']:hover { color: var(--weiss); border-color: var(--anthrazit); }
}
@media (forced-colors: active) {
  button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; }
}
</style>
</head>
<body>
<div class="buehne" data-zustand="intro" data-wartet>
  <header class="kopf">
    <nav aria-label="Hauptnavigation">
      <ul>
        <li><a href="#" aria-current="page">Schmuck</a></li>
        <li><a href="#">Trauringe</a></li>
        <li><a href="#">Manufaktur</a></li>
        <li><a href="#">Journal</a></li>
      </ul>
      <a class="menue label" href="#">Menü</a>
    </nav>
    <a class="marke" href="#" aria-label="Sanner Schmuckmanufaktur Pforzheim, zur Startseite">
      <svg class="zeichen" data-zeichen viewBox="-50 -50 100 100" aria-hidden="true"></svg>
      <span class="wort" data-wort aria-hidden="true"><span class="name"><span><span>S</span></span><span><span>A</span></span><span><span>N</span></span><span><span>N</span></span><span><span>E</span></span><span><span>R</span></span></span></span>
    </a>
    <div class="rechts">
      <a class="leise" href="#">Suche</a>
      <a class="termin" href="#">Termin<span class="lang"> vereinbaren</span></a>
    </div>
  </header>

  <main class="held" id="inhalt">
    <div class="text">
      <p class="label"><span>Neuheit · Herbst 2026</span><span>Kollektion Enzlicht</span></p>
      <div class="kern">
        <h1 class="titel">Ein Stein. <em>Vier</em> Krallen. Sonst nichts.</h1>
        <p class="lead">Der Solitär Enzlicht hält einen Brillanten von 0,50 ct so offen, dass von unten Licht in den Stein fällt. Jede Fassung entsteht in unserer Werkstatt in der Pforzheimer Weststadt, gefasst von Hand und unter der Lupe geprüft.</p>
        <dl class="daten">
          <dt>Referenz</dt><dd>S 1908.047</dd>
          <dt>Fassung</dt><dd>750/000 Gelbgold, vier Krallen</dd>
          <dt>Stein</dt><dd>Brillant 0,50 ct · F · VS1</dd>
          <dt>Ringweite</dt><dd>48 bis 62, Anpassung im Haus</dd>
        </dl>
      </div>
      <div class="handeln">
        <a class="knopf label" href="#">Termin in der Boutique</a>
        <a class="weiter label" href="#">Alle Solitäre</a>
      </div>
    </div>
    <figure class="produkt" aria-label="Solitärring Enzlicht in Gelbgold mit Brillant">
      <svg data-ring viewBox="110 72 380 466" role="img" aria-label="Solitärring in Gelbgold, vier Krallen halten einen Brillanten"></svg>
      <figcaption class="ecke lo label">Ansicht 1 / 4</figcaption>
      <span class="ecke ro label">Ø 17,2 mm</span>
      <span class="ecke lu label">Gefertigt in Pforzheim</span>
      <span class="ecke ru" aria-hidden="true"><i class="punkt an"></i><i class="punkt"></i><i class="punkt"></i><i class="punkt"></i></span>
    </figure>
  </main>

  <div class="intro" aria-hidden="true">
    <div class="i-mitte">
      <div class="i-zeichen"></div>
      <div class="i-wort"></div>
      <p class="i-zeile label">Schmuckmanufaktur · Pforzheim · seit 1908</p>
    </div>
    <p class="i-ecke lu label">Goldstadt seit 1767</p>
    <p class="i-ecke ru label">48° 53′ N · 8° 42′ O</p>
  </div>
</div>

<div class="pult">
  <div class="modi" role="group" aria-label="Aufbau des Logos">
    <span>Aufbau</span>
    <button type="button" data-v="kontur" aria-pressed="true">Kontur zeichnen</button>
    <button type="button" data-v="facetten" aria-pressed="false">Facetten fügen sich</button>
    <button type="button" data-v="maske" aria-pressed="false">Buchstaben-Maske</button>
    <button type="button" class="nochmal" data-nochmal>↻ Nochmal</button>
  </div>
  <p class="werte" data-werte></p>
</div>

<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/Flip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/DrawSVGPlugin.min.js"></script>
<script>
/* Bildzeichen: Brillant von oben (Tafel, 8 Sternfacetten, 8 Hauptfacetten, 16 Rundistfacetten, Rundiste).
   Dieselben Polygone werden je nach Variante gezeichnet, zusammengesetzt oder freigestellt. */
const NS = 'http://www.w3.org/2000/svg';
const pt = (r, grad) => { const a = (grad - 90) * Math.PI / 180; return [+(r * Math.cos(a)).toFixed(2), +(r * Math.sin(a)).toFixed(2)]; };
function zeichenBauen(svg) {
  const T = 19, S = 31, G = 46;               // Radien: Tafel, Sternspitzen, Rundiste
  const ringe = [[], [], [], []];
  const poly = (punkte, ring, fuell) => {
    const p = document.createElementNS(NS, 'polygon');
    p.setAttribute('points', punkte.map(q => q.join(',')).join(' '));
    if (fuell) { p.classList.add('f'); p.dataset.fuell = fuell; }
    // Mittelpunkt für die Variante „Facetten fügen sich“
    const m = punkte.reduce((s, q) => [s[0] + q[0] / punkte.length, s[1] + q[1] / punkte.length], [0, 0]);
    p.dataset.mx = m[0].toFixed(2); p.dataset.my = m[1].toFixed(2);
    svg.appendChild(p); ringe[ring].push(p);
  };
  poly(Array.from({ length: 8 }, (_, k) => pt(T, 22.5 + k * 45)), 0, .16);
  for (let k = 0; k < 8; k++) poly([pt(T, k * 45 - 22.5), pt(S, k * 45), pt(T, k * 45 + 22.5)], 1, .07);
  for (let k = 0; k < 8; k++) poly([pt(T, k * 45 + 22.5), pt(S, k * 45), pt(G, k * 45 + 22.5), pt(S, k * 45 + 45)], 2, 0);
  for (let k = 0; k < 8; k++) {
    poly([pt(S, k * 45), pt(G, k * 45 - 22.5), pt(G, k * 45)], 3, k % 2 ? .05 : 0);
    poly([pt(S, k * 45), pt(G, k * 45), pt(G, k * 45 + 22.5)], 3, k % 2 ? 0 : .05);
  }
  const rund = document.createElementNS(NS, 'circle');
  rund.setAttribute('r', G); svg.appendChild(rund);
  return { ringe, rund, alle: [...ringe.flat(), rund] };
}

/* Produkt: Solitärring, als SVG gezeichnet (auf einer echten Seite ein Freisteller-Foto auf #f4f4f2) */
function ringBauen(svg) {
  const g = [];
  g.push(`<defs>
    <linearGradient id="gold" x1="0" x2="1"><stop offset="0" stop-color="#7d5f2e"/><stop offset=".16" stop-color="#c9a865"/><stop offset=".3" stop-color="#f5e6bd"/><stop offset=".46" stop-color="#b38c4c"/><stop offset=".64" stop-color="#e2c486"/><stop offset=".8" stop-color="#fbf0d2"/><stop offset="1" stop-color="#8d6b36"/></linearGradient>
    <linearGradient id="innen" x1="0" x2="1"><stop offset="0" stop-color="#6e5225"/><stop offset=".5" stop-color="#a98445"/><stop offset="1" stop-color="#d9bc80"/></linearGradient>
    <linearGradient id="krone" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#ffffff"/><stop offset=".5" stop-color="#e3e9ee"/><stop offset="1" stop-color="#aab6c1"/></linearGradient>
    <filter id="weich" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="9"/></filter>
    <filter id="hauch" x="-20%" y="-20%" width="140%" height="140%"><feGaussianBlur stdDeviation="1.4"/></filter>
  </defs>`);
  // Schatten auf der Fläche
  g.push('<ellipse cx="304" cy="514" rx="138" ry="13" fill="rgba(35,40,47,.22)" filter="url(#weich)"/>');
  g.push('<ellipse cx="304" cy="512" rx="70" ry="5" fill="rgba(35,40,47,.28)" filter="url(#hauch)"/>');
  // Hintere Krallen
  g.push('<path d="M279 130 C281 150 285 168 289 196" stroke="#9c7a40" stroke-width="5" fill="none" stroke-linecap="round"/>');
  g.push('<path d="M321 130 C319 150 315 168 311 196" stroke="#8a6a35" stroke-width="5" fill="none" stroke-linecap="round"/>');
  // Ringschiene: Außenfläche minus Innenfläche, rechts sieht man die Innenseite
  g.push('<path fill-rule="evenodd" fill="url(#gold)" d="M300 190 a148 160 0 1 0 0.1 0 Z M306 207 a126 143 0 1 0 0.1 0 Z"/>');
  g.push('<path fill-rule="evenodd" fill="url(#innen)" d="M306 207 a126 143 0 1 0 0.1 0 Z M314 211 a119 139 0 1 0 0.1 0 Z" opacity=".9"/>');
  g.push('<path d="M181 300 C176 380 214 456 282 478" stroke="rgba(255,255,255,.75)" stroke-width="3" fill="none" stroke-linecap="round" filter="url(#hauch)"/>');
  g.push('<path d="M420 260 C432 300 434 350 420 400" stroke="rgba(255,250,235,.6)" stroke-width="2.5" fill="none" stroke-linecap="round" filter="url(#hauch)"/>');
  // Korb unter dem Stein
  g.push('<path d="M266 158 L334 158 L322 198 L278 198 Z" fill="url(#innen)"/>');
  g.push('<path d="M272 176 L328 176" stroke="rgba(255,240,205,.55)" stroke-width="2"/>');
  // Brillant von der Seite: Krone (Zickzack-Facetten) und Pavillon (Fächer)
  const tafel = [274, 287, 300, 313, 326], rund = [242, 265, 288, 312, 335, 358];
  const yT = 104, yR = 130, yS = 180;
  const hell = ['#ffffff', '#dfe6ec', '#f4f7f9', '#c3ced8', '#eef2f5', '#b4c1cc', '#ffffff', '#d6dfe6', '#e9eef2', '#c9d3dc'];
  let i = 0;
  for (let k = 0; k < 5; k++) {
    g.push(`<polygon points="${rund[k]},${yR} ${tafel[k]},${yT} ${rund[k + 1]},${yR}" fill="${hell[i++ % hell.length]}"/>`);
    if (k < 4) g.push(`<polygon points="${tafel[k]},${yT} ${rund[k + 1]},${yR} ${tafel[k + 1]},${yT}" fill="${hell[i++ % hell.length]}"/>`);
  }
  g.push(`<polygon points="${rund[0]},${yR} ${tafel[0]},${yT} ${rund[0]},${yR}" fill="#fff"/>`);
  g.push(`<polygon points="${tafel[0]},${yT} ${tafel[4]},${yT} ${tafel[4]},${yT - 1.5} ${tafel[0]},${yT - 1.5}" fill="#f8fafb"/>`);
  const pav = ['#9fadba', '#e6ecf0', '#b9c5cf', '#f6f8fa', '#8f9eac'];
  for (let k = 0; k < 5; k++) g.push(`<polygon points="${rund[k]},${yR} ${rund[k + 1]},${yR} 300,${yS}" fill="${pav[k]}"/>`);
  g.push(`<polyline points="${rund.map(x => x + ',' + yR).join(' ')}" fill="none" stroke="#8d9aa6" stroke-width="1.2"/>`);
  g.push(`<polygon points="${rund[0]},${yR} ${tafel[0]},${yT} ${tafel[4]},${yT} ${rund[5]},${yR} 300,${yS}" fill="none" stroke="rgba(80,95,110,.35)" stroke-width="1"/>`);
  // Lichtpunkt auf der Tafel
  g.push('<path d="M293 112 l2 -7 l2 7 l7 2 l-7 2 l-2 7 l-2 -7 l-7 -2 Z" fill="#fff" opacity=".95"/>');
  // Vordere Krallen mit Kugelköpfen über der Rundiste
  g.push('<path d="M246 124 C250 150 258 176 270 200" stroke="url(#gold)" stroke-width="7" fill="none" stroke-linecap="round"/>');
  g.push('<path d="M354 124 C350 150 342 176 330 200" stroke="url(#gold)" stroke-width="7" fill="none" stroke-linecap="round"/>');
  g.push('<ellipse cx="245" cy="123" rx="6" ry="7" fill="#d8b977"/><ellipse cx="243.5" cy="121" rx="2" ry="2.4" fill="#fff6dc"/>');
  g.push('<ellipse cx="355" cy="123" rx="6" ry="7" fill="#c7a564"/><ellipse cx="353.5" cy="121" rx="2" ry="2.4" fill="#fff3d4"/>');
  svg.innerHTML = g.join('');
}

(() => {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const mobil = () => matchMedia('(max-width: 760px)').matches;
  const $ = s => document.querySelector(s), $$ = s => [...document.querySelectorAll(s)];
  const buehne = $('.buehne'), intro = $('.intro'), zeile = $('.i-zeile'), ecken = $$('.i-ecke');
  const zeichen = $('[data-zeichen]'), wort = $('[data-wort]');
  const buchstaben = $$('.wort .name > span > span');
  const ring = $('[data-ring]');
  const platz = { zeichen: $('.i-zeichen'), wort: $('.i-wort') };
  const Z = zeichenBauen(zeichen);
  ringBauen(ring);

  // Messwerte: Zeichnen power2.inOut 1000–1600 ms, 60–120 ms je Pfad; Halt 200–400 ms; Flug expo.inOut 800–1100 ms.
  // Mobil alles × 0,55 (Steckbrief: unter 1,5 s gesamt).
  const W = { zeichnen: 1.0, versatz: .1, halt: .3, flug: .9, fuell: .4, buchstabe: .8, bVersatz: .06 };
  const V = {
    kontur:   { text: 'Kontur zeichnen' },
    facetten: { text: 'Facetten fügen sich' },
    maske:    { text: 'Buchstaben-Maske' },
  };
  let variante = 'kontur', tl = null, folge = null, vorfuehrung = false, bereit = false;

  const heimat = new Map([zeichen, wort].map(k => [k, [k.parentNode, k.nextSibling]]));
  const heim = k => { const [e, n] = heimat.get(k); e.insertBefore(k, n); };

  function werteZeigen() {
    const t = mobil() ? .55 : 1, ms = s => Math.round(s * t * 1000);
    const aufbau = variante === 'kontur' ? `DrawSVG 0 → 100 % · <b>${ms(W.zeichnen)} ms power2.inOut</b> · Ring-Versatz <b>${ms(W.versatz)} ms</b>`
      : variante === 'facetten' ? `Facetten x/y/rotate → 0 · <b>${ms(W.zeichnen)} ms power2.inOut</b> · Ring-Versatz <b>${ms(W.versatz)} ms</b>`
      : `Zeichen circle(0 → 75 %) · Buchstaben yPercent 110 → 0 · <b>${ms(W.zeichnen)} ms power2.inOut</b> · <b>${ms(.09)} ms</b> Versatz`;
    $('[data-werte]').innerHTML = reduziert()
      ? '<span>Reduziert: <b>kein Intro</b>, Logo steht in der Navigation, Fade <b>250 ms</b></span>'
      : `<span>${aufbau}</span><span>Halt <b>${ms(W.halt)} ms</b></span><span>Flug <b>Flip ${ms(W.flug)} ms expo.inOut</b></span>`;
  }

  function aufraeumen() {
    if (tl) tl.kill(); if (folge) folge.kill();
    tl = folge = null;
    const alle = [zeichen, wort, intro, zeile, ...ecken, ...buchstaben, ...Z.alle, ring];
    gsap.killTweensOf(alle);
    [zeichen, wort].forEach(k => { heim(k); k.classList.remove('reist'); });
    gsap.set(alle, { clearProps: 'all' });
    Z.alle.forEach(p => { p.removeAttribute('style'); });
    zeichen.style.removeProperty('--sw');
  }

  function fuellen(t, bei, d) {
    Z.ringe.flat().forEach(p => { if (p.dataset.fuell) t.to(p, { fillOpacity: +p.dataset.fuell, duration: d, ease: 'power2.inOut' }, bei); });
  }

  // Solange das Intro liegt, ist die Seite darunter nicht per Tastatur erreichbar (Fokus läge sonst verdeckt)
  const sperren = an => [...buehne.children].forEach(n => { if (n !== intro) n.inert = an; });
  new MutationObserver(() => sperren(buehne.dataset.zustand !== 'fertig')).observe(buehne, { attributes: true, attributeFilter: ['data-zustand'] });
  sperren(buehne.dataset.zustand !== 'fertig');

  function neustart() {
    if (!bereit) return;
    delete buehne.dataset.wartet;
    aufraeumen();
    werteZeigen();
    if (reduziert()) {
      buehne.dataset.zustand = 'fertig';
      Z.ringe.flat().forEach(p => { if (p.dataset.fuell) p.style.fillOpacity = p.dataset.fuell; });
      buehne.style.animation = 'none'; void buehne.offsetWidth; buehne.style.animation = '';
      if (vorfuehrung) folge = gsap.delayedCall(4, naechste);
      return;
    }
    const k = mobil() ? .55 : 1;
    const d = W.zeichnen * k, v = W.versatz * k;
    scrollTo(0, 0);
    buehne.dataset.zustand = 'intro';
    platz.zeichen.appendChild(zeichen);
    platz.wort.appendChild(wort);
    gsap.set(zeile, { autoAlpha: 0, y: 8 });
    gsap.set(ecken, { autoAlpha: 0 });
    tl = gsap.timeline();
    tl.to(ecken, { autoAlpha: 1, duration: .4 * k, ease: 'power2.inOut' }, 0);

    let ende;
    if (variante === 'kontur') {
      gsap.set(buchstaben, { yPercent: 110 });
      Z.ringe.forEach((r, i) => tl.fromTo(r, { drawSVG: '0%' }, { drawSVG: '100%', duration: d, ease: 'power2.inOut' }, i * v));
      tl.fromTo(Z.rund, { drawSVG: '50% 50%' }, { drawSVG: '0% 100%', duration: d, ease: 'power2.inOut' }, 4 * v);
      tl.to(buchstaben, { yPercent: 0, duration: W.buchstabe * k, ease: 'power2.inOut', stagger: W.bVersatz * k }, d * .45);
      ende = 4 * v + d;
      fuellen(tl, ende - W.fuell * k * .5, W.fuell * k);
    } else if (variante === 'facetten') {
      gsap.set(buchstaben, { yPercent: 110 });
      Z.ringe.forEach((r, i) => r.forEach(p => {
        const mx = +p.dataset.mx, my = +p.dataset.my, l = Math.hypot(mx, my) || 1, weit = 34 + i * 10;
        tl.fromTo(p, { x: mx / l * weit * (i ? 1 : 0), y: (i ? my / l * weit : 22), rotation: (i % 2 ? 1 : -1) * (24 + i * 8), opacity: 0, svgOrigin: `${mx} ${my}` },
          { x: 0, y: 0, rotation: 0, opacity: 1, duration: d, ease: 'power2.inOut' }, i * v + Math.random() * .05 * k);
      }));
      tl.fromTo(Z.rund, { drawSVG: '50% 50%' }, { drawSVG: '0% 100%', duration: d * .8, ease: 'power2.inOut' }, 3 * v + d * .3);
      tl.to(buchstaben, { yPercent: 0, duration: W.buchstabe * k, ease: 'power2.inOut', stagger: W.bVersatz * k }, d * .5);
      ende = 3 * v + d + .05 * k;
      fuellen(tl, ende - W.fuell * k * .5, W.fuell * k);
    } else {
      gsap.set(buchstaben, { yPercent: 110 });
      Z.ringe.flat().forEach(p => { if (p.dataset.fuell) p.style.fillOpacity = p.dataset.fuell; });
      tl.to(buchstaben, { yPercent: 0, duration: W.buchstabe * k, ease: 'power2.inOut', stagger: .09 * k }, 0);
      tl.fromTo(zeichen, { clipPath: 'circle(0% at 50% 50%)' }, { clipPath: 'circle(75% at 50% 50%)', duration: d, ease: 'power2.inOut' }, .25 * k);
      tl.fromTo(zeichen, { rotation: -22 }, { rotation: 0, duration: d, ease: 'power2.inOut' }, .25 * k);
      ende = .25 * k + d;
    }
    tl.to(zeile, { autoAlpha: 1, y: 0, duration: .5 * k, ease: 'power2.inOut' }, ende - .55 * k);
    tl.call(flug, null, ende + W.halt * k);
  }

  function flug() {
    const k = mobil() ? .55 : 1, d = W.flug * k;
    gsap.set(zeichen, { clearProps: 'clipPath,rotation,transform' });
    const stand = Flip.getState([zeichen, wort]);
    [zeichen, wort].forEach(n => { heim(n); n.classList.add('reist'); });
    buehne.dataset.zustand = 'flug';
    const t = gsap.timeline({ onComplete: fertig });
    t.add(Flip.from(stand, { targets: [zeichen, wort], scale: true, duration: d, ease: 'expo.inOut' }), 0);
    // Wortmarke bleibt hell, solange sie über der dunklen Fläche fliegt; wird dunkel, wenn die Fläche sie freigibt
    t.fromTo(wort, { color: '#ffffff' }, { color: '#23282f', duration: d * .14, ease: 'none' }, d * .58);
    // Strichstärke so führen, dass die Linie im großen und im kleinen Zeichen gleich fein wirkt (≈ 1 px)
    t.fromTo(zeichen, { '--sw': .82 }, { '--sw': 3.1, duration: d, ease: 'expo.inOut' }, 0);
    t.to([zeile, ...ecken], { autoAlpha: 0, duration: .2 * k, ease: 'power2.inOut' }, 0);
    t.fromTo(intro, { clipPath: 'inset(0% 0% 0% 0%)' }, { clipPath: 'inset(0% 0% 100% 0%)', duration: d, ease: 'expo.inOut' }, 0);
    t.fromTo(ring, { scale: 1.06, transformOrigin: '50% 60%' }, { scale: 1, duration: d * 1.4, ease: 'expo.inOut' }, 0);
    tl.add(t, tl.time());
  }

  function fertig() {
    buehne.dataset.zustand = 'fertig';
    [zeichen, wort].forEach(n => n.classList.remove('reist'));
    gsap.set([intro, zeichen, wort, ring], { clearProps: 'clipPath,transform,scale,color' });
    if (vorfuehrung) folge = gsap.delayedCall(2.2, naechste);
  }

  function setzen(v) { variante = v; $$('[data-v]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.v === v))); }
  function naechste() { const r = Object.keys(V); setzen(r[(r.indexOf(variante) + 1) % r.length]); neustart(); }

  $$('[data-v]').forEach(b => b.addEventListener('click', () => { vorfuehrung = false; setzen(b.dataset.v); neustart(); }));
  $('[data-nochmal]').addEventListener('click', () => { vorfuehrung = false; neustart(); });
  $$('.kopf a, .handeln a').forEach(a => a.addEventListener('click', e => e.preventDefault()));

  function vorfuehren() { vorfuehrung = true; gsap.globalTimeline.resume(); neustart(); }
  function pause() { gsap.globalTimeline.pause(); }
  function weiter() { gsap.globalTimeline.resume(); }


  if (!window.gsap || !window.Flip || !window.DrawSVGPlugin) {
    buehne.dataset.zustand = 'fertig'; delete buehne.dataset.wartet;
    Z.ringe.flat().forEach(p => { if (p.dataset.fuell) p.style.fillOpacity = p.dataset.fuell; });
    return;
  }
  gsap.registerPlugin(Flip, DrawSVGPlugin);
  werteZeigen();
  document.fonts.ready.then(() => { bereit = true; neustart(); });
})();
</script>
</body>
</html>