Typo reagiert auf Cursor-Nähe
Die Headline spürt den Zeiger: Buchstaben in seiner Nähe werden fetter, kräftiger geschnitten oder weichen aus, der System-Cursor bleibt.
Statt eines eigenen Cursor-Elements reagiert die Schrift selbst auf den Zeiger. Für jedes Zeichen wird der Abstand zum Zeiger berechnet. Innerhalb eines Radius ändert sich das Zeichen je nach Nähe: Es wird fetter oder breiter (Variable Font), weicht aus, wird gescrambelt oder hebt sich farbig ab. Außerhalb des Radius kehrt es weich in den Ruhezustand zurück.
Der Zeiger wird spürbar, ohne dass ein Follower ihn ersetzt. Der System-Cursor bleibt, die Seite antwortet trotzdem. Weil sich nur wenige Zeichen gleichzeitig ändern, bleibt die Zeile lesbar und wirkt lebendig.
Wann einsetzen
Hero-Headlines, Footer-Wortmarken, Marken mit typografischem Profil, Hintergrundflächen aus Zeichen (ASCII-Felder).
Wann nicht
Lesetext und Formulare. Nicht auf mehreren Flächen derselben Seite, sonst nutzt sich der Effekt ab und kostet Leistung.
Varianten
- Gewicht nach Nähe (Variable Font, wght)
- Breite, optische Größe und Laufweite nach Nähe (wdth, opsz, letter-spacing)
- Ausweichen: Zeichen weichen radial aus und gleiten zurück
- Scramble im Radius: Zeichen werden kurz durch Zufallszeichen ersetzt
- Zeichenfeld (ASCII), das unter dem Zeiger aufleuchtet
Umsetzung
Text in Zeichen splitten, Wörter als nowrap-Gruppe halten. Zeichenmitten einmal messen und in Seitenkoordinaten cachen, neu bei Resize und nach dem Laden der Schrift. Auf pointermove nur die Zeigerposition speichern, gerechnet wird in einer requestAnimationFrame-Schleife: d = hypot(cx − mx, cy − my), t = 1 − d / R (begrenzt auf 0 bis 1), weich gemacht mit einer Sinus-Kurve. Den Wert pro Zeichen glätten (Lerp mit fester Zeitkonstante), dann je nach Variante font-variation-settings, letter-spacing oder transform setzen. Die Schleife schläft, sobald alle Zeichen ruhen. Fallstrick: getBoundingClientRect nicht in jedem Frame aufrufen.
Robust umsetzen
Telefon. Ohne Hover läuft der Effekt einmal automatisch als Welle durch die Zeilen. Danach reagiert die Fläche auf Tippen und waagerechtes Wischen; senkrechtes Scrollen bleibt frei (touch-action: pan-y).
Ohne Bewegung. Abschalten. Die Schrift bleibt im Ruhezustand.
Barrierefrei. Den vollen Text einmal für Screenreader ausgeben (visuell versteckt oder aria-label), die gesplitteten Zeichen mit aria-hidden ausblenden. Im Ruhezustand ist der Text unverändert lesbar.
Code
<!DOCTYPE html>
<!--
Typo reagiert auf Cursor-Nähe
Fundus · Cursor · https://cecodesign.de/lab/fundus/cursor/cursor-text-proximity/
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>Typo reagiert auf Cursor-Nähe · 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: 'Newsreader'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/newsreader:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 200 800; font-style: normal; font-display: swap; }
@font-face { font-family: 'Newsreader'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/newsreader:vf@latest/latin-opsz-italic.woff2') format('woff2-variations'); font-weight: 200 800; font-style: italic; font-display: swap; }
@font-face { font-family: 'Mona Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/mona-sans:vf@latest/latin-wdth-normal.woff2') format('woff2-variations'); font-weight: 200 900; font-stretch: 75% 125%; 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; }
:root {
--sand: #F3EEE7; --creme: #FEF2E3; --tinte: #3E3A37; --linie: #D5D1C8; --leise: #666863; --holz: #6E5A47;
--sinus: cubic-bezier(0.37, 0, 0.63, 1); /* token-luxus-weich (Rezept) */
--ui: cubic-bezier(0.4, 0, 0.2, 1); /* Buchungsleiste, 200 ms */
}
* { box-sizing: border-box; margin: 0; }
html { background: var(--sand); color: var(--tinte); font-family: 'Mona Sans', system-ui, sans-serif; font-stretch: 100%; -webkit-font-smoothing: antialiased; }
body { min-height: 100svh; overflow-x: clip; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.etikett { font: 560 11px/1.3 'Mona Sans', sans-serif; font-stretch: 118%; letter-spacing: .08em; text-transform: uppercase; }
.seite { max-width: 1280px; margin: 0 auto; min-height: 100svh; padding: clamp(16px, 2.6vw, 36px) clamp(16px, 3.4vw, 48px) 0; display: grid; grid-template-rows: auto 1fr auto auto; }
/* Kopf */
.kopf { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 24px; padding-bottom: clamp(14px, 1.8vw, 22px); border-bottom: 1px solid var(--linie); }
.marke { display: grid; gap: 3px; color: inherit; text-decoration: none; justify-self: start; }
.marke b { font: 640 15px/1 'Mona Sans', sans-serif; font-stretch: 125%; letter-spacing: .3em; text-transform: uppercase; }
.marke small { font: italic 380 13px/1 'Newsreader', serif; font-variation-settings: 'opsz' 14; color: var(--leise); letter-spacing: .01em; }
.nav { display: flex; gap: clamp(16px, 2.4vw, 34px); list-style: none; padding: 0; }
.nav a, .sprache { color: inherit; text-decoration: none; }
.nav a { background: linear-gradient(currentColor 0 0) 0 100% / 0 1px no-repeat; padding-bottom: 3px; transition: background-size .2s var(--ui); }
.nav a:hover, .nav a:focus-visible { background-size: 100% 1px; outline: none; }
.rechts { justify-self: end; display: flex; align-items: center; gap: 20px; }
.sprache { color: var(--leise); }
.sprache b { color: var(--tinte); font-weight: inherit; }
.knopf { display: inline-flex; align-items: center; gap: 10px; border: 0; cursor: pointer; background: var(--holz); color: var(--creme); padding: 12px 18px; border-radius: 2px; transition: background-color .2s var(--ui); }
.knopf:hover, .knopf:focus-visible { background: var(--tinte); outline: none; }
/* Hero */
.hero { display: grid; grid-template-columns: minmax(0, 1.18fr) minmax(0, .82fr); gap: clamp(28px, 5vw, 80px); align-items: center; padding: clamp(28px, 4.4vw, 64px) 0 clamp(28px, 4vw, 56px); }
.text { display: grid; gap: clamp(18px, 2.4vw, 30px); align-content: center; }
.ort { display: flex; align-items: center; gap: 12px; color: var(--leise); }
.ort::before { content: ""; width: 28px; height: 1px; background: currentColor; }
.titel { font-family: 'Newsreader', serif; font-weight: 300; font-size: clamp(2.6rem, 1rem + 5.4vw, 6.1rem); line-height: 1.02; letter-spacing: -.015em; font-variation-settings: 'wght' 300, 'opsz' 72; color: var(--tinte); }
.titel em { font-style: italic; }
.titel .zeile { display: block; white-space: nowrap; }
.titel .w { display: inline-block; white-space: nowrap; }
.titel .z { display: inline-block; font-variation-settings: 'wght' 300, 'opsz' 72; will-change: transform; }
.vorspann { font-size: 1.0625rem; line-height: 1.6; max-width: 36ch; color: #5b5651; }
.vorspann em { font: italic 400 1.12em/1 'Newsreader', serif; font-variation-settings: 'opsz' 18; color: var(--tinte); }
.weiter { display: inline-flex; gap: 12px; align-items: center; color: var(--tinte); text-decoration: none; justify-self: start; border-bottom: 1px solid var(--tinte); padding-bottom: 6px; }
.weiter span { transition: translate .2s var(--ui); }
.weiter:hover span { translate: 4px 0; }
.bild-rahmen { position: relative; justify-self: end; width: min(100%, 440px); }
.bogen { aspect-ratio: 4 / 5; border-radius: 999px 999px 2px 2px; overflow: hidden; background: #ECE4D8; }
.bogen .bild { display: block; width: 100%; height: 100%; }
.unterschrift { display: flex; justify-content: space-between; gap: 12px; margin-top: 14px; color: var(--leise); }
.unterschrift i { font: italic 400 15px/1.2 'Newsreader', serif; font-variation-settings: 'opsz' 16; color: var(--tinte); letter-spacing: 0; text-transform: none; }
/* Buchungsleiste */
.buchung { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)) auto; background: var(--creme); border: 1px solid var(--linie); border-radius: 2px; }
.feld { display: grid; gap: 6px; padding: 16px 22px; border: 0; border-right: 1px solid var(--linie); background: none; text-align: left; color: inherit; font: inherit; cursor: pointer; transition: background-color .2s var(--ui); }
.feld:hover, .feld:focus-visible { background: #fbe9d3; }
.feld:focus-visible { outline: 2px solid var(--tinte); outline-offset: -2px; }
.feld .etikett { color: var(--leise); }
.feld strong { font: 380 clamp(19px, 1.7vw, 24px)/1.1 'Newsreader', serif; font-variation-settings: 'opsz' 24; letter-spacing: -.01em; }
.buchung .knopf { margin: 8px; padding: 0 26px; }
.hinweis { display: flex; justify-content: space-between; gap: 16px; padding: 12px 2px 0; color: var(--leise); font-size: 13px; }
.hinweis em { font: italic 400 15px/1 'Newsreader', serif; font-variation-settings: 'opsz' 16; color: var(--tinte); }
/* Fundus-Bedienleiste */
.fundus { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px 24px; padding: 18px 2px clamp(16px, 2vw, 24px); margin-top: 18px; border-top: 1px solid var(--linie); }
.mono { font: 500 10.5px/1.3 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--leise); }
.umschalter { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.umschalter button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--leise); background: none; border: 1px solid transparent; border-radius: 99px; padding: 5px 10px; cursor: pointer; transition: color .15s var(--ui), border-color .15s var(--ui); }
.umschalter button:hover { color: var(--tinte); }
.umschalter button[aria-pressed="true"] { color: var(--tinte); border-color: var(--linie); }
.umschalter button:focus-visible { outline: 1px solid var(--tinte); outline-offset: 2px; }
.werte b { color: var(--tinte); font-weight: 500; }
/* Vorführung: gezeichneter Zeiger */
.zeiger { position: fixed; left: 0; top: 0; z-index: 9; width: 20px; height: 20px; pointer-events: none; display: none; }
.vorfuehrung .zeiger { display: block; }
.vorfuehrung .titel { pointer-events: none; }
/* Vorschau 960 × 720: nur die Bühne */
.vorschau body { overflow: hidden; }
.vorschau .hinweis, .vorschau .umschalter > .mono { display: none; }
.vorschau .fundus { margin-top: 10px; padding-top: 12px; }
.vorschau .hero { grid-template-columns: minmax(0, 1.3fr) minmax(0, .7fr); }
.vorschau .titel { font-size: 72px; }
/* Touch: kein Hover. Welle beim Laden, danach Wischen in der Headline */
.touch-hinweis { display: none; }
@media (hover: none) {
.titel { touch-action: pan-y; }
.touch-hinweis { display: inline; }
}
/* Reduzierte Bewegung: Effekt aus, Headline steht im Ruhezustand */
.reduziert .titel .z { transform: none !important; font-variation-settings: 'wght' 300, 'opsz' 72 !important; will-change: auto; }
.reduziert * { transition-duration: .01ms !important; }
@media (max-width: 900px) {
.nav { display: none; }
.kopf { grid-template-columns: 1fr auto; }
.hero { grid-template-columns: 1fr; }
.bild-rahmen { justify-self: stretch; width: 100%; display: grid; grid-template-columns: 1fr; }
.bogen { aspect-ratio: 16 / 10; border-radius: 999px 999px 2px 2px / 160px 160px 2px 2px; }
}
@media (max-width: 640px) {
.sprache { display: none; }
.kopf .knopf { padding: 10px 14px; }
.buchung { grid-template-columns: 1fr 1fr; }
.feld { padding: 14px 16px; border-bottom: 1px solid var(--linie); }
.feld:nth-child(2) { border-right: 0; }
.feld:nth-child(3) { grid-column: 1 / -1; border-right: 0; }
.buchung .knopf { grid-column: 1 / -1; padding: 16px; justify-content: center; }
.hinweis { flex-direction: column; gap: 4px; }
.bogen { aspect-ratio: 5 / 4; }
}
/* Kontrastmodus: Knöpfe mit Rahmen */
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body>
<div class="seite">
<header class="kopf">
<a class="marke" href="#start"><b>Weidenkahn</b><small>Gästehaus in Klein-Venedig</small></a>
<ul class="nav etikett">
<li><a href="#zimmer">Zimmer</a></li>
<li><a href="#fruehstueck">Frühstück</a></li>
<li><a href="#haus">Das Haus</a></li>
<li><a href="#anreise">Anreise</a></li>
</ul>
<div class="rechts">
<a class="sprache etikett" href="#en"><b>DE</b> / EN</a>
<button class="knopf etikett" type="button">Buchen</button>
</div>
</header>
<main class="hero" id="start">
<div class="text">
<p class="ort etikett">Am linken Regnitzufer · Bamberg</p>
<h1 class="titel" data-titel>
<span class="sr">Schlafen, wo die Regnitz leiser wird.</span>
<span aria-hidden="true" data-sicht><span class="zeile">Schlafen, wo</span><span class="zeile">die <em>Regnitz</em></span><span class="zeile">leiser wird.</span></span>
</h1>
<p class="vorspann">Neun Zimmer in einem Fischerhaus von 1761, jedes mit Holzbalkon über dem Wasser. Frühstück gibt es auf dem Steg, das Rauchbier <em>zwei Gassen weiter.</em></p>
<a class="weiter etikett" href="#zimmer">Zimmer ansehen <span aria-hidden="true">→</span></a>
</div>
<figure class="bild-rahmen">
<div class="bogen"><svg class="bild" viewBox="0 0 400 520" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Zeichnung: Fischerhäuser mit Holzbalkonen an der Regnitz, im Hintergrund die vier Türme des Bamberger Doms, davor ein Kahn"><rect width="400" height="520" fill="#ECE4D8"/><circle cx="318" cy="96" r="26" fill="#F6EEE2"/><path d="M-10 238 Q 90 196 200 214 T 410 206 L410 260 L-10 260Z" fill="#DCD5C9"/><rect x="46" y="148" width="12" height="92" fill="#CFC7BA"/><path d="M45 148 L52 114 L59 148Z" fill="#CFC7BA"/><rect x="64" y="144" width="12" height="96" fill="#CFC7BA"/><path d="M63 144 L70 110 L77 144Z" fill="#CFC7BA"/><rect x="118" y="144" width="12" height="96" fill="#CFC7BA"/><path d="M117 144 L124 110 L131 144Z" fill="#CFC7BA"/><rect x="136" y="148" width="12" height="92" fill="#CFC7BA"/><path d="M135 148 L142 114 L149 148Z" fill="#CFC7BA"/><rect x="58" y="170" width="84" height="56" fill="#CFC7BA"/><path d="M56 172 L100 150 L144 172Z" fill="#CFC7BA"/><g id="haeuser"><path d="M-11 252 L33.0 196 L77 252 Z" fill="#A98468"/><path d="M-11 252 L33.0 196 L77 252" fill="none" stroke="#3E3A37" stroke-width="1.2" stroke-opacity=".5"/><rect x="29.0" y="227.0" width="8" height="11" rx="4" fill="#3E3A37" fill-opacity=".75"/><rect x="-6" y="250" width="78" height="106" fill="#F8F2E8"/><line x1="-6.0" y1="250" x2="-6.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="20.0" y1="250" x2="20.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="46.0" y1="250" x2="46.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="72.0" y1="250" x2="72.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="-6" y1="250.0" x2="72" y2="250.0" stroke="#6E5A47" stroke-width="2.6"/><line x1="-6" y1="300.9" x2="72" y2="300.9" stroke="#6E5A47" stroke-width="2.6"/><line x1="-6" y1="356.0" x2="72" y2="356.0" stroke="#6E5A47" stroke-width="2.6"/><path d="M-6.0 300.9 L20.0 250.0 M72.0 300.9 L46.0 250.0" stroke="#6E5A47" stroke-width="1.8" fill="none"/><rect x="26.5" y="262.7" width="13.0" height="25.4" fill="#E9C79A" fill-opacity="1"/><rect x="1.0" y="311.9" width="12.0" height="23.2" fill="#3E3A37" fill-opacity="0.74"/><rect x="27.0" y="311.9" width="12.0" height="23.2" fill="#3E3A37" fill-opacity="0.74"/><rect x="53.0" y="311.9" width="12.0" height="23.2" fill="#3E3A37" fill-opacity="0.74"/><path d="M67 228 L120.0 158 L173 228 Z" fill="#8F6F58"/><path d="M67 228 L120.0 158 L173 228" fill="none" stroke="#3E3A37" stroke-width="1.2" stroke-opacity=".5"/><rect x="116.0" y="196.0" width="8" height="11" rx="4" fill="#3E3A37" fill-opacity=".75"/><rect x="72" y="226" width="96" height="130" fill="#F8F2E8"/><line x1="72.0" y1="226" x2="72.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="104.0" y1="226" x2="104.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="136.0" y1="226" x2="136.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="168.0" y1="226" x2="168.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="72" y1="226.0" x2="168" y2="226.0" stroke="#6E5A47" stroke-width="2.6"/><line x1="72" y1="288.4" x2="168" y2="288.4" stroke="#6E5A47" stroke-width="2.6"/><line x1="72" y1="356.0" x2="168" y2="356.0" stroke="#6E5A47" stroke-width="2.6"/><path d="M72.0 288.4 L104.0 226.0 M168.0 288.4 L136.0 226.0" stroke="#6E5A47" stroke-width="1.8" fill="none"/><rect x="112.0" y="241.6" width="16.0" height="31.2" fill="#3E3A37" fill-opacity="0.78"/><rect x="81.5" y="301.9" width="13.0" height="28.4" fill="#3E3A37" fill-opacity="0.74"/><rect x="113.5" y="301.9" width="13.0" height="28.4" fill="#E9C79A" fill-opacity="1"/><rect x="145.5" y="301.9" width="13.0" height="28.4" fill="#3E3A37" fill-opacity="0.74"/><path d="M163 264 L201.0 214 L239 264 Z" fill="#B08D70"/><path d="M163 264 L201.0 214 L239 264" fill="none" stroke="#3E3A37" stroke-width="1.2" stroke-opacity=".5"/><rect x="197.0" y="242.0" width="8" height="11" rx="4" fill="#3E3A37" fill-opacity=".75"/><rect x="168" y="262" width="66" height="94" fill="#F8F2E8"/><line x1="168.0" y1="262" x2="168.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="190.0" y1="262" x2="190.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="212.0" y1="262" x2="212.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="234.0" y1="262" x2="234.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="168" y1="262.0" x2="234" y2="262.0" stroke="#6E5A47" stroke-width="2.6"/><line x1="168" y1="307.1" x2="234" y2="307.1" stroke="#6E5A47" stroke-width="2.6"/><line x1="168" y1="356.0" x2="234" y2="356.0" stroke="#6E5A47" stroke-width="2.6"/><path d="M168.0 307.1 L190.0 262.0 M234.0 307.1 L212.0 262.0" stroke="#6E5A47" stroke-width="1.8" fill="none"/><rect x="195.5" y="273.3" width="11.0" height="22.6" fill="#3E3A37" fill-opacity="0.78"/><rect x="173.9" y="316.9" width="10.1" height="20.5" fill="#3E3A37" fill-opacity="0.74"/><rect x="195.9" y="316.9" width="10.1" height="20.5" fill="#3E3A37" fill-opacity="0.74"/><rect x="217.9" y="316.9" width="10.1" height="20.5" fill="#3E3A37" fill-opacity="0.74"/><path d="M229 240 L284.0 176 L339 240 Z" fill="#9A785F"/><path d="M229 240 L284.0 176 L339 240" fill="none" stroke="#3E3A37" stroke-width="1.2" stroke-opacity=".5"/><rect x="280.0" y="211.0" width="8" height="11" rx="4" fill="#3E3A37" fill-opacity=".75"/><rect x="234" y="238" width="100" height="118" fill="#F8F2E8"/><line x1="234.0" y1="238" x2="234.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="267.3" y1="238" x2="267.3" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="300.7" y1="238" x2="300.7" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="334.0" y1="238" x2="334.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="234" y1="238.0" x2="334" y2="238.0" stroke="#6E5A47" stroke-width="2.6"/><line x1="234" y1="294.6" x2="334" y2="294.6" stroke="#6E5A47" stroke-width="2.6"/><line x1="234" y1="356.0" x2="334" y2="356.0" stroke="#6E5A47" stroke-width="2.6"/><path d="M234.0 294.6 L267.3 238.0 M334.0 294.6 L300.7 238.0" stroke="#6E5A47" stroke-width="1.8" fill="none"/><rect x="276.0" y="252.2" width="16.0" height="28.3" fill="#E9C79A" fill-opacity="1"/><rect x="244.2" y="306.9" width="13.0" height="25.8" fill="#3E3A37" fill-opacity="0.74"/><rect x="277.5" y="306.9" width="13.0" height="25.8" fill="#3E3A37" fill-opacity="0.74"/><rect x="310.8" y="306.9" width="13.0" height="25.8" fill="#3E3A37" fill-opacity="0.74"/><path d="M329 256 L371.0 204 L413 256 Z" fill="#A98468"/><path d="M329 256 L371.0 204 L413 256" fill="none" stroke="#3E3A37" stroke-width="1.2" stroke-opacity=".5"/><rect x="367.0" y="233.0" width="8" height="11" rx="4" fill="#3E3A37" fill-opacity=".75"/><rect x="334" y="254" width="74" height="102" fill="#F8F2E8"/><line x1="334.0" y1="254" x2="334.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="358.7" y1="254" x2="358.7" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="383.3" y1="254" x2="383.3" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="408.0" y1="254" x2="408.0" y2="356" stroke="#6E5A47" stroke-width="2.2"/><line x1="334" y1="254.0" x2="408" y2="254.0" stroke="#6E5A47" stroke-width="2.6"/><line x1="334" y1="303.0" x2="408" y2="303.0" stroke="#6E5A47" stroke-width="2.6"/><line x1="334" y1="356.0" x2="408" y2="356.0" stroke="#6E5A47" stroke-width="2.6"/><path d="M334.0 303.0 L358.7 254.0 M408.0 303.0 L383.3 254.0" stroke="#6E5A47" stroke-width="1.8" fill="none"/><rect x="364.8" y="266.2" width="12.3" height="24.5" fill="#3E3A37" fill-opacity="0.78"/><rect x="340.7" y="313.6" width="11.3" height="22.3" fill="#3E3A37" fill-opacity="0.74"/><rect x="365.3" y="313.6" width="11.3" height="22.3" fill="#E9C79A" fill-opacity="1"/><rect x="390.0" y="313.6" width="11.3" height="22.3" fill="#3E3A37" fill-opacity="0.74"/><rect x="80" y="300" width="70" height="3" fill="#6E5A47"/><rect x="80" y="316" width="70" height="3" fill="#6E5A47"/><line x1="80.0" y1="303" x2="80.0" y2="316" stroke="#6E5A47" stroke-width="1.4"/><line x1="86.4" y1="303" x2="86.4" y2="316" stroke="#6E5A47" stroke-width="1.4"/><line x1="92.7" y1="303" x2="92.7" y2="316" stroke="#6E5A47" stroke-width="1.4"/><line x1="99.1" y1="303" x2="99.1" y2="316" stroke="#6E5A47" stroke-width="1.4"/><line x1="105.5" y1="303" x2="105.5" y2="316" stroke="#6E5A47" stroke-width="1.4"/><line x1="111.8" y1="303" x2="111.8" y2="316" stroke="#6E5A47" stroke-width="1.4"/><line x1="118.2" y1="303" x2="118.2" y2="316" stroke="#6E5A47" stroke-width="1.4"/><line x1="124.5" y1="303" x2="124.5" y2="316" stroke="#6E5A47" stroke-width="1.4"/><line x1="130.9" y1="303" x2="130.9" y2="316" stroke="#6E5A47" stroke-width="1.4"/><line x1="137.3" y1="303" x2="137.3" y2="316" stroke="#6E5A47" stroke-width="1.4"/><line x1="143.6" y1="303" x2="143.6" y2="316" stroke="#6E5A47" stroke-width="1.4"/><line x1="150.0" y1="303" x2="150.0" y2="316" stroke="#6E5A47" stroke-width="1.4"/><circle cx="83.6" cy="296.5" r="4.0" fill="#7C8466"/><circle cx="83.8" cy="294.5" r="1.6" fill="#A85D45"/><circle cx="90.7" cy="296.2" r="3.8" fill="#7C8466"/><circle cx="90.5" cy="294.2" r="1.6" fill="#A85D45"/><circle cx="97.1" cy="296.3" r="3.7" fill="#7C8466"/><circle cx="104.2" cy="296.7" r="4.0" fill="#7C8466"/><circle cx="112.2" cy="297.2" r="4.6" fill="#7C8466"/><circle cx="113.2" cy="295.2" r="1.6" fill="#A85D45"/><circle cx="118.6" cy="296.4" r="3.2" fill="#7C8466"/><circle cx="119.5" cy="294.4" r="1.6" fill="#A85D45"/><circle cx="125.4" cy="297.7" r="4.0" fill="#7C8466"/><circle cx="125.5" cy="295.7" r="1.6" fill="#A85D45"/><circle cx="132.1" cy="296.2" r="3.3" fill="#7C8466"/><circle cx="139.9" cy="296.9" r="3.9" fill="#7C8466"/><circle cx="146.6" cy="298.4" r="4.1" fill="#7C8466"/><circle cx="146.8" cy="296.4" r="1.6" fill="#A85D45"/><path d="M83 319 L83 386 M147 319 L147 386" stroke="#6E5A47" stroke-width="2"/><rect x="244" y="306" width="64" height="3" fill="#6E5A47"/><rect x="244" y="322" width="64" height="3" fill="#6E5A47"/><line x1="244.0" y1="309" x2="244.0" y2="322" stroke="#6E5A47" stroke-width="1.4"/><line x1="250.4" y1="309" x2="250.4" y2="322" stroke="#6E5A47" stroke-width="1.4"/><line x1="256.8" y1="309" x2="256.8" y2="322" stroke="#6E5A47" stroke-width="1.4"/><line x1="263.2" y1="309" x2="263.2" y2="322" stroke="#6E5A47" stroke-width="1.4"/><line x1="269.6" y1="309" x2="269.6" y2="322" stroke="#6E5A47" stroke-width="1.4"/><line x1="276.0" y1="309" x2="276.0" y2="322" stroke="#6E5A47" stroke-width="1.4"/><line x1="282.4" y1="309" x2="282.4" y2="322" stroke="#6E5A47" stroke-width="1.4"/><line x1="288.8" y1="309" x2="288.8" y2="322" stroke="#6E5A47" stroke-width="1.4"/><line x1="295.2" y1="309" x2="295.2" y2="322" stroke="#6E5A47" stroke-width="1.4"/><line x1="301.6" y1="309" x2="301.6" y2="322" stroke="#6E5A47" stroke-width="1.4"/><line x1="308.0" y1="309" x2="308.0" y2="322" stroke="#6E5A47" stroke-width="1.4"/><circle cx="248.1" cy="304.6" r="4.2" fill="#7C8466"/><circle cx="249.5" cy="302.6" r="1.6" fill="#A85D45"/><circle cx="254.2" cy="303.3" r="4.2" fill="#7C8466"/><circle cx="254.2" cy="301.3" r="1.6" fill="#A85D45"/><circle cx="261.1" cy="304.0" r="4.2" fill="#7C8466"/><circle cx="269.8" cy="302.9" r="4.1" fill="#7C8466"/><circle cx="276.2" cy="303.4" r="4.3" fill="#7C8466"/><circle cx="282.9" cy="304.0" r="3.1" fill="#7C8466"/><circle cx="290.3" cy="305.0" r="4.3" fill="#7C8466"/><circle cx="290.0" cy="303.0" r="1.6" fill="#A85D45"/><circle cx="297.3" cy="302.1" r="3.7" fill="#7C8466"/><circle cx="296.2" cy="300.1" r="1.6" fill="#A85D45"/><circle cx="303.1" cy="304.3" r="3.2" fill="#7C8466"/><circle cx="302.8" cy="302.3" r="1.6" fill="#A85D45"/><path d="M247 325 L247 386 M305 325 L305 386" stroke="#6E5A47" stroke-width="2"/><rect x="340" y="312" width="46" height="3" fill="#6E5A47"/><rect x="340" y="328" width="46" height="3" fill="#6E5A47"/><line x1="340.0" y1="315" x2="340.0" y2="328" stroke="#6E5A47" stroke-width="1.4"/><line x1="346.6" y1="315" x2="346.6" y2="328" stroke="#6E5A47" stroke-width="1.4"/><line x1="353.1" y1="315" x2="353.1" y2="328" stroke="#6E5A47" stroke-width="1.4"/><line x1="359.7" y1="315" x2="359.7" y2="328" stroke="#6E5A47" stroke-width="1.4"/><line x1="366.3" y1="315" x2="366.3" y2="328" stroke="#6E5A47" stroke-width="1.4"/><line x1="372.9" y1="315" x2="372.9" y2="328" stroke="#6E5A47" stroke-width="1.4"/><line x1="379.4" y1="315" x2="379.4" y2="328" stroke="#6E5A47" stroke-width="1.4"/><line x1="386.0" y1="315" x2="386.0" y2="328" stroke="#6E5A47" stroke-width="1.4"/><circle cx="344.7" cy="308.2" r="3.7" fill="#7C8466"/><circle cx="351.8" cy="310.5" r="4.4" fill="#7C8466"/><circle cx="351.5" cy="308.5" r="1.6" fill="#A85D45"/><circle cx="357.7" cy="310.7" r="4.5" fill="#7C8466"/><circle cx="356.7" cy="308.7" r="1.6" fill="#A85D45"/><circle cx="364.5" cy="308.7" r="3.8" fill="#7C8466"/><circle cx="371.5" cy="308.0" r="3.7" fill="#7C8466"/><circle cx="371.7" cy="306.0" r="1.6" fill="#A85D45"/><circle cx="379.9" cy="310.1" r="3.8" fill="#7C8466"/><path d="M343 331 L343 386 M383 331 L383 386" stroke="#6E5A47" stroke-width="2"/><rect x="-10" y="356" width="420" height="10" fill="#B9AE9E"/></g><rect x="0" y="366" width="400" height="154" fill="#C4C8BD"/><g opacity=".16" transform="translate(0 732) scale(1 -1)"><use href="#haeuser"/></g><line x1="-31" y1="374.7" x2="251" y2="374.7" stroke="#ECE4D8" stroke-width="1.9" stroke-linecap="round" stroke-opacity=".8"/><line x1="88" y1="385.0" x2="278" y2="385.0" stroke="#ECE4D8" stroke-width="1.5" stroke-linecap="round" stroke-opacity=".8"/><line x1="61" y1="391.4" x2="193" y2="391.4" stroke="#ECE4D8" stroke-width="1.1" stroke-linecap="round" stroke-opacity=".8"/><line x1="-14" y1="401.3" x2="167" y2="401.3" stroke="#ECE4D8" stroke-width="1.1" stroke-linecap="round" stroke-opacity=".8"/><line x1="-16" y1="410.0" x2="122" y2="410.0" stroke="#ECE4D8" stroke-width="1.4" stroke-linecap="round" stroke-opacity=".8"/><line x1="100" y1="419.6" x2="330" y2="419.6" stroke="#ECE4D8" stroke-width="1.2" stroke-linecap="round" stroke-opacity=".8"/><line x1="16" y1="430.0" x2="201" y2="430.0" stroke="#ECE4D8" stroke-width="1.1" stroke-linecap="round" stroke-opacity=".8"/><line x1="119" y1="441.9" x2="323" y2="441.9" stroke="#ECE4D8" stroke-width="1.6" stroke-linecap="round" stroke-opacity=".8"/><line x1="-24" y1="448.3" x2="158" y2="448.3" stroke="#ECE4D8" stroke-width="1.3" stroke-linecap="round" stroke-opacity=".8"/><line x1="-14" y1="460.8" x2="110" y2="460.8" stroke="#ECE4D8" stroke-width="2.1" stroke-linecap="round" stroke-opacity=".8"/><line x1="-17" y1="469.1" x2="201" y2="469.1" stroke="#ECE4D8" stroke-width="1.0" stroke-linecap="round" stroke-opacity=".8"/><line x1="117" y1="478.6" x2="392" y2="478.6" stroke="#ECE4D8" stroke-width="1.8" stroke-linecap="round" stroke-opacity=".8"/><line x1="19" y1="487.0" x2="169" y2="487.0" stroke="#ECE4D8" stroke-width="1.9" stroke-linecap="round" stroke-opacity=".8"/><line x1="85" y1="497.6" x2="264" y2="497.6" stroke="#ECE4D8" stroke-width="1.3" stroke-linecap="round" stroke-opacity=".8"/><line x1="118" y1="508.2" x2="391" y2="508.2" stroke="#ECE4D8" stroke-width="2.0" stroke-linecap="round" stroke-opacity=".8"/><line x1="78" y1="517.8" x2="239" y2="517.8" stroke="#ECE4D8" stroke-width="1.6" stroke-linecap="round" stroke-opacity=".8"/><path d="M118 446 L330 446 Q 322 462 300 464 L150 464 Q 128 462 118 446Z" fill="#3E3A37"/><path d="M130 446 L322 446" stroke="#6E5A47" stroke-width="2"/><line x1="286" y1="380" x2="314" y2="500" stroke="#3E3A37" stroke-width="2.4" stroke-linecap="round"/><path d="M126 478 L322 478" stroke="#3E3A37" stroke-width="1.4" stroke-opacity=".25" stroke-dasharray="40 14 70 10"/></svg></div>
<figcaption class="unterschrift etikett"><i>Zimmer 4, Regnitzblick</i><span>24 m² · ab 148 €</span></figcaption>
</figure>
</main>
<section aria-label="Verfügbarkeit prüfen">
<form class="buchung" onsubmit="return false">
<button class="feld" type="button"><span class="etikett">Anreise</span><strong>Fr, 13. Nov.</strong></button>
<button class="feld" type="button"><span class="etikett">Abreise</span><strong>So, 15. Nov.</strong></button>
<button class="feld" type="button"><span class="etikett">Gäste</span><strong>2 Erwachsene</strong></button>
<button class="knopf etikett" type="submit">Verfügbarkeit prüfen <span aria-hidden="true">→</span></button>
</form>
<p class="hinweis"><span>Direkt gebucht: Frühstück und Leihräder inklusive, Mindestaufenthalt 2 Nächte.</span><em>Herzlich, Ihre Familie Dorsch</em></p>
</section>
<div class="fundus">
<div class="umschalter mono" role="group" aria-label="Variante">
<span class="mono">Variante</span>
<button type="button" data-variante="gewicht" aria-pressed="true">Gewicht</button>
<button type="button" data-variante="optik" aria-pressed="false">Optische Größe</button>
<button type="button" data-variante="ausweichen" aria-pressed="false">Ausweichen</button>
</div>
<p class="werte mono" data-werte aria-live="polite"></p>
</div>
</div>
<svg class="zeiger" data-zeiger viewBox="0 0 20 20" aria-hidden="true"><path d="M2.5 1.8 17 10.2l-6.6 1.4-3.2 6.1Z" fill="#3E3A37" stroke="#FEF2E3" stroke-width="1.3" stroke-linejoin="round"/></svg>
<script>
(() => {
const html = document.documentElement;
const reduziert = () => html.classList.contains('reduziert');
const titel = document.querySelector('[data-titel]');
const sicht = titel.querySelector('[data-sicht]');
const werteAnzeige = document.querySelector('[data-werte]');
const zeiger = document.querySelector('[data-zeiger]');
const knoepfe = [...document.querySelectorAll('[data-variante]')];
const touch = matchMedia('(hover: none)').matches;
// Werte (identisch mit bau.werte in meta.json)
const RADIUS_EM = 1.6; // Wirkradius in em der Headline (≈ 150 px bei 94 px)
const TAU_MS = 80; // Glättung: Zeitkonstante, 95 % nach ≈ 240 ms (Magnet Type: transitionMs)
const VARIANTEN = {
gewicht: { name: 'Gewicht', info: 'wght <b>300 → 700</b> · opsz 72' },
optik: { name: 'Optische Größe', info: 'opsz <b>72 → 8</b> · wght 300 → 460 · Laufweite +0,06em' },
ausweichen: { name: 'Ausweichen', info: 'Versatz <b>0,2em</b> radial, waagerecht ×0,6 · wght 300 → 380' },
};
let variante = 'gewicht';
// 1) Zeichen splitten (Wörter bleiben zusammen, Kursiv bleibt erhalten)
const zeichen = [];
function teilen(knoten, zeile) {
[...knoten.childNodes].forEach(n => {
if (n.nodeType === 1) return teilen(n, zeile);
if (n.nodeType !== 3) return;
const frag = document.createDocumentFragment();
n.textContent.split(/(\s+)/).forEach(teil => {
if (!teil) return;
if (/^\s+$/.test(teil)) { frag.append(' '); return; }
const w = document.createElement('span'); w.className = 'w';
[...teil].forEach(c => {
const z = document.createElement('span'); z.className = 'z'; z.textContent = c;
w.append(z);
zeichen.push({ el: z, zeile, x: 0, y: 0, v: 0, ux: 0, uy: -1, zuletzt: -1 });
});
frag.append(w);
});
n.replaceWith(frag);
});
}
[...sicht.children].forEach((zeile, i) => teilen(zeile, i));
// 2) Mittelpunkte einmal messen (Seitenkoordinaten), bei Resize und nach dem Laden der Schrift neu
let radius = 150, em = 88;
function messen() {
zeichen.forEach(z => { z.el.style.transform = ''; z.el.style.fontVariationSettings = ''; z.el.style.letterSpacing = ''; z.zuletzt = -1; });
em = parseFloat(getComputedStyle(titel).fontSize);
radius = RADIUS_EM * em;
zeichen.forEach(z => {
const r = z.el.getBoundingClientRect();
z.x = r.left + r.width / 2 + scrollX; z.y = r.top + r.height / 2 + scrollY;
});
werteZeigen();
}
function werteZeigen() {
werteAnzeige.innerHTML = reduziert()
? 'Reduzierte Bewegung · Effekt aus'
: `R ${RADIUS_EM}em <b>${Math.round(radius)} px</b> · ${VARIANTEN[variante].info} · Glättung ${TAU_MS} ms`;
}
// 3) Zeiger (Seitenkoordinaten). Vorführung und Touch-Welle schreiben ebenfalls hierher.
const zeigerPos = { x: -9999, y: -9999, aktiv: false };
const fall = d => d >= radius ? 0 : 0.5 - 0.5 * Math.cos(Math.PI * (1 - d / radius)); // Sinus-Abfall, weich an beiden Enden
function anwenden(z) {
const v = z.v;
if (Math.abs(v - z.zuletzt) < 0.002) return;
z.zuletzt = v;
const s = z.el.style;
if (variante === 'gewicht') {
s.fontVariationSettings = `'wght' ${(300 + 400 * v).toFixed(1)}, 'opsz' 72`;
} else if (variante === 'optik') {
s.fontVariationSettings = `'wght' ${(300 + 160 * v).toFixed(1)}, 'opsz' ${(72 - 64 * v).toFixed(1)}`;
s.letterSpacing = `${(-0.015 + 0.06 * v).toFixed(4)}em`; // Grund-Laufweite der Headline -0,015em
} else {
s.fontVariationSettings = `'wght' ${(300 + 80 * v).toFixed(1)}, 'opsz' 72`;
const weg = 0.2 * em * v;
s.transform = `translate3d(${(z.ux * 0.6 * weg).toFixed(2)}px, ${(z.uy * weg).toFixed(2)}px, 0)`;
}
}
let laeuft = false, letzte = 0;
function bild(t) {
const dt = Math.min(64, t - letzte); letzte = t;
const k = 1 - Math.exp(-dt / TAU_MS);
let unruhig = zeigerPos.aktiv;
for (const z of zeichen) {
const dx = z.x - zeigerPos.x, dy = z.y - zeigerPos.y;
const d = Math.hypot(dx, dy);
const ziel = zeigerPos.aktiv ? fall(d) : 0;
if (ziel > 0 && d > 0.5) { z.ux = dx / d; z.uy = dy / d; } // Richtung merken, damit die Rückkehr gerade verläuft
z.v += (ziel - z.v) * k;
if (z.v < 0.0008 && ziel === 0) z.v = 0;
if (z.v > 0) unruhig = true;
anwenden(z);
}
if (unruhig && !reduziert()) requestAnimationFrame(bild); else laeuft = false;
}
function starten() {
if (reduziert() || laeuft) return;
laeuft = true; letzte = performance.now(); requestAnimationFrame(bild);
}
// Maus
addEventListener('pointermove', e => {
if (e.pointerType !== 'mouse' || html.classList.contains('vorfuehrung')) return;
zeigerPos.x = e.pageX; zeigerPos.y = e.pageY; zeigerPos.aktiv = true; starten();
}, { passive: true });
document.addEventListener('pointerleave', () => { if (!html.classList.contains('vorfuehrung')) zeigerPos.aktiv = false; });
// Touch: Wischen in der Headline
titel.addEventListener('pointermove', e => {
if (e.pointerType === 'mouse' || html.classList.contains('vorfuehrung')) return;
zeigerPos.x = e.pageX; zeigerPos.y = e.pageY; zeigerPos.aktiv = true; starten();
}, { passive: true });
titel.addEventListener('pointerdown', e => { if (e.pointerType !== 'mouse') { zeigerPos.x = e.pageX; zeigerPos.y = e.pageY; zeigerPos.aktiv = true; starten(); } });
['pointerup', 'pointercancel', 'pointerleave'].forEach(n => titel.addEventListener(n, e => { if (e.pointerType !== 'mouse' && !html.classList.contains('vorfuehrung')) zeigerPos.aktiv = false; }));
// Variante wechseln
function setzeVariante(name) {
variante = name;
knoepfe.forEach(b => b.setAttribute('aria-pressed', String(b.dataset.variante === name)));
zeichen.forEach(z => { z.v = 0; z.zuletzt = -1; z.el.style.transform = ''; z.el.style.letterSpacing = ''; z.el.style.fontVariationSettings = ''; });
werteZeigen(); starten();
}
knoepfe.forEach(b => b.addEventListener('click', () => setzeVariante(b.dataset.variante)));
// Bahn des gedachten Zeigers: einmal durch alle drei Zeilen, dann hinaus
function bahn(p, rand) {
// Zeilen aus den gemessenen Mittelpunkten: links, rechts, Mitte
const zeilen = [];
zeichen.forEach(z => { const L = zeilen[z.zeile] || (zeilen[z.zeile] = { l: Infinity, r: -Infinity, y: 0, n: 0 }); L.l = Math.min(L.l, z.x); L.r = Math.max(L.r, z.x); L.y += z.y; L.n++; });
zeilen.forEach(L => { L.y /= L.n; L.l -= em * 0.25; L.r += em * 0.25; });
const N = zeilen.length, erste = zeilen[0], letzte = zeilen[N - 1];
if (p < 0.06) return { x: erste.l - em * 1.2, y: erste.y - em * 0.7, drin: false };
if (p > 0.92) return { x: letzte.r + rand, y: letzte.y + em * 1.2, drin: false };
// Lesebahn im Zickzack: Zeile 1 nach rechts, Zeile 2 zurück, Zeile 3 wieder nach rechts
const q = (p - 0.06) / 0.86 * N, i = Math.min(N - 1, Math.floor(q)), s = q - i;
const sinus = a => 0.5 - 0.5 * Math.cos(Math.PI * Math.max(0, Math.min(1, a)));
const L = zeilen[i], vor = i % 2 === 0;
const x = vor ? L.l + (L.r - L.l) * sinus(s) : L.r - (L.r - L.l) * sinus(s);
const nach = zeilen[i + 1];
const y = L.y + (nach ? (nach.y - L.y) * sinus((s - 0.82) / 0.18) : 0) + Math.sin(s * Math.PI * 2) * em * 0.1;
return { x, y, drin: true };
}
// Touch: eine Welle beim Laden zeigt, dass die Headline reagiert
let welleUhr = null;
function welle() {
if (reduziert()) return;
cancelAnimationFrame(welleUhr);
const start = performance.now(), DAUER = 3600;
const schritt = t => {
const p = Math.min(1, (t - start) / DAUER);
const b = bahn(0.06 + p * 0.86, 0);
zeigerPos.x = b.x; zeigerPos.y = b.y; zeigerPos.aktiv = p < 1; starten();
if (p < 1) welleUhr = requestAnimationFrame(schritt);
};
welleUhr = requestAnimationFrame(schritt);
}
// Vorführung: gedachter Zeiger, jede Runde eine andere Variante
let uhr = null, start = 0, runde = -1;
const REIHE = ['gewicht', 'optik', 'ausweichen'], RUNDE_MS = 7000;
function vorfuehren() {
pause();
if (reduziert()) return;
html.classList.add('vorfuehrung');
start = performance.now() - Math.max(0, runde) * RUNDE_MS; if (runde < 0) runde = 0;
const schritt = t => {
const ges = Math.max(0, t - start) / RUNDE_MS, n = Math.floor(ges), p = ges - n;
if (n !== runde) { runde = n; setzeVariante(REIHE[n % REIHE.length]); }
const b = bahn(p, 80);
zeigerPos.x = b.x; zeigerPos.y = b.y; zeigerPos.aktiv = b.drin; starten();
zeiger.style.transform = `translate(${b.x - scrollX - 3}px, ${b.y - scrollY - 2}px)`;
uhr = requestAnimationFrame(schritt);
};
setzeVariante(REIHE[runde % REIHE.length]);
uhr = requestAnimationFrame(schritt);
}
function pause() { cancelAnimationFrame(uhr); uhr = null; }
function weiter() { if (!uhr && html.classList.contains('vorfuehrung')) vorfuehren(); }
function neustart() {
pause(); runde = -1;
html.classList.remove('vorfuehrung');
zeigerPos.aktiv = false;
setzeVariante('gewicht');
if (touch) welle();
}
// Start
let bereit = false;
const los = () => { messen(); if (!bereit) { bereit = true; if (touch && !html.classList.contains('vorschau')) setTimeout(welle, 500); } };
(document.fonts ? document.fonts.ready : Promise.resolve()).then(los);
let rz; addEventListener('resize', () => { clearTimeout(rz); rz = setTimeout(messen, 120); });
new MutationObserver(() => { werteZeigen(); if (reduziert()) { pause(); zeichen.forEach(z => { z.v = 0; z.zuletzt = -1; z.el.removeAttribute('style'); }); } })
.observe(html, { attributes: true, attributeFilter: ['class'] });
werteZeigen();
})();
</script>
</body>
</html>