Gezeichnete Markierung und Unterstreichung
Ein Kernbegriff bekommt eine Markierung, die sich vor den Augen zeichnet: Textmarker, Kreis oder ein zufälliger Handstrich.
Ein Schlüsselwort im Satz bekommt eine Markierung, die sich sichtbar zeichnet. Möglich sind eine Textmarker-Fläche (background mit linear-gradient, background-size 0 → 100 %), eine handgezeichnete SVG-Linie oder ein Kreis (stroke-dashoffset). Die Markierung erscheint beim Eintritt in den Viewport oder bei Hover und läuft von links nach rechts. Auf den geprüften Seiten sind Unterstreichung und SVG-Linie bei Hover belegt; der flächige Textmarker beim Scrollen stammt aus Osmo-Ressourcen.
Das Auge springt auf das eine markierte Wort. Weil die Markierung sich zeichnet, wirkt sie wie von Hand gesetzt: menschlich, redaktionell, wie eine Notiz oder Korrektur am Rand.
Wann einsetzen
Kernbegriffe in Headlines und Teasern, ein Satz in einem Testimonial, Links im Fließtext (dezente Unterstreichung bei Hover). Passt zu Kursen, Coaching, Redaktionen und freundlichen Marken.
Wann nicht
Mehr als eine Markierung pro Abschnitt, dann verliert sie ihre Wirkung. Sehr formale Marken, Recht, Finanzen. Ganze Absätze markieren.
Varianten
- Textmarker-Fläche, die sich beim Scrollen über das Wort legt; mehrzeilig Zeile für Zeile (Osmo „Highlight Marker Text Reveal“)
- Zufällige handgezeichnete Unterstreichung bei Hover, jedes Mal ein anderer Strich (Osmo „Draw Random Underline“)
- Eingekreistes Wort als SVG-Linie, die sich bei Hover zeichnet und verzögert wieder ausläuft (Resend)
- Feine Linie unter Links, die bei Hover von links einläuft und nach rechts ausläuft (Attio, Fantasy)
Umsetzung
Textmarker: .mark { background: linear-gradient(var(--hl), var(--hl)) 0 88% / 0% 60% no-repeat; box-decoration-break: clone; transition: background-size 750ms cubic-bezier(.66,.06,.36,.96) } .is-in .mark { background-size: 100% 60% }. Für Zeile-für-Zeile jede Zeile in ein eigenes span legen und um 200 ms versetzen. SVG-Linie: stroke-dasharray und stroke-dashoffset auf die Pfadlänge, dann offset → 0 in 360 ms; Rücklauf 240 ms nach 360 ms Verzögerung (Resend). Mit preserveAspectRatio="none" und vector-effect: non-scaling-stroke rechnet pathLength="1" falsch, die Länge dann per Skript in Bildschirm-Pixeln messen. Link-Linie wie bei Attio: background-size 0% .06em → 100% .06em an Position 0 91 %, Hover 300 ms, Rücklauf 400 ms; -webkit-text-stroke .1em in der Grundfarbe mit paint-order: stroke fill spart Unterlängen aus. Die Markierungsfarbe muss den Textkontrast halten.
Robust umsetzen
Telefon. Unverändert. Hover-Varianten spielen einmal, wenn das Element ins Bild kommt; Tippen kann sie neu zeichnen.
Ohne Bewegung. Markierung sofort im Endzustand zeigen, ohne Zeichnen.
Barrierefrei. Trägt die Markierung Bedeutung, das Wort semantisch mit <mark> oder <strong> auszeichnen. SVG-Linien mit aria-hidden="true" ausblenden. Textkontrast auf der Markerfarbe prüfen (dunkle Schrift auf Gelb).
Code
<!DOCTYPE html>
<!--
Gezeichnete Markierung und Unterstreichung
Fundus · Schrift-Animationen · https://cecodesign.de/lab/fundus/schrift-animationen/highlight-marker-underline/
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>Gezeichnete Markierung und Unterstreichung · 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: 'Bricolage Grotesque'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/bricolage-grotesque:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 200 800; font-display: swap; }
@font-face { font-family: 'Onest'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/onest:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Shantell Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/shantell-sans:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 300 800; 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 {
/* Silbenhaus, Sprachschule Augsburg (Deutsch für die Pflege): kühles Papierweiß, Tinte, Rotstift als Markenton */
--grund: #f2f4f6; --karte: #ffffff; --tinte: #15171c; --leise: #5d6470; --linie: rgba(21, 23, 28, .1);
--rot: #d2402a; --gelb: #f7c325; --erfolg: #10bf7a;
--rot-text: #c73d28; --erfolg-text: #0b8555; /* gleiche Töne, für Schrift dunkler (≥ 4,6:1); Striche bleiben in --rot/--erfolg */
/* Marker-Tinte: leicht schräg, unten satter, wie ein echter Textmarker */
--tinte-gelb: linear-gradient(176deg, #f9cf4a, #f7c325 45%, #f1b712);
/* Eine Kurve für alles Gezeichnete: gemessen bei Resend (.hoverword svg) */
--kurve: cubic-bezier(0.66, 0.06, 0.36, 0.96);
--ein: 360ms; --aus: 240ms; --aus-verzug: 360ms;
--marker-dauer: 750ms; --marker-versatz: 200ms;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--grund); color: var(--tinte); font-family: 'Onest', system-ui, sans-serif; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
body { overflow-x: hidden; }
a { color: inherit; }
button { font: inherit; color: inherit; }
.mono { font: 500 11px/1.3 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--leise); }
.hand { font-family: 'Shantell Sans', 'Onest', sans-serif; font-variation-settings: 'INFM' 45, 'BNCE' 0; font-weight: 500; }
.rand { width: min(1200px, 100% - 2 * clamp(16px, 4vw, 48px)); margin-inline: auto; }
/* ---------- Bühne ---------- */
.buehne { min-height: 100svh; display: grid; grid-template-rows: auto 1fr auto; }
.kopf { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding-block: 22px; }
.marke { display: flex; align-items: baseline; gap: 12px; text-decoration: none; }
.marke b { font: 800 23px/1 'Bricolage Grotesque', sans-serif; font-variation-settings: 'opsz' 36; letter-spacing: -.03em; position: relative; }
.marke b::after { content: ''; position: absolute; left: 0; right: 0; bottom: -5px; height: 6px; background: no-repeat center / 100% 100% url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 8' preserveAspectRatio='none'%3E%3Cpath d='M2 5.5C30 2.5 70 1.8 98 3.6' fill='none' stroke='%23d2402a' stroke-width='2.2' stroke-linecap='round' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E"); }
.marke span { font-size: 10px; }
.nav { display: flex; gap: 28px; list-style: none; font-size: 15px; }
.nav a { text-decoration: none; color: var(--leise); transition: color 150ms; }
.nav a:hover { color: var(--tinte); }
.knopf { display: inline-flex; align-items: center; gap: 10px; height: 48px; padding: 0 22px; border-radius: 999px; border: 0; background: var(--rot); color: #fff; font-weight: 600; font-size: 16px; text-decoration: none; cursor: pointer; transition: transform 150ms cubic-bezier(0.22, 1, 0.36, 1), background 150ms; }
.knopf:hover { background: #b9361f; }
.knopf:active { transform: scale(.97); }
.knopf.klein { height: 40px; padding: 0 18px; font-size: 15px; background: var(--tinte); }
.knopf.klein:hover { background: #2b2f38; }
.held { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, .9fr); gap: clamp(28px, 5vw, 72px); align-items: center; padding-block: clamp(20px, 4vh, 48px); }
.held .start { color: var(--rot-text); font-size: 15px; margin-bottom: 18px; display: flex; gap: 8px; align-items: center; }
.held .start::before { content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--erfolg); }
h1 { font: 700 clamp(2.4rem, 1.2rem + 4.4vw, 4.75rem)/1.04 'Bricolage Grotesque', sans-serif; font-variation-settings: 'opsz' 96; letter-spacing: -.02em; text-wrap: balance; max-width: 14ch; }
.einleitung { font-size: clamp(1.0625rem, 1rem + .2vw, 1.15rem); line-height: 1.6; color: var(--leise); max-width: 46ch; margin-top: 24px; text-wrap: pretty; }
.handeln { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 22px; margin-top: 32px; }
.preis { font-size: 15px; line-height: 1.4; color: var(--leise); }
.preis b { color: var(--tinte); font-weight: 600; font-variant-numeric: tabular-nums; font-size: 17px; }
/* ---------- Das Ziel im Titel: drei Arten zu markieren ---------- */
.ziel { position: relative; display: inline-block; white-space: nowrap; isolation: isolate; }
.ziel svg { position: absolute; overflow: visible; pointer-events: none; z-index: -1; }
/* Strichlänge --len misst das Skript in Bildschirm-Pixeln (preserveAspectRatio none + non-scaling-stroke) */
.ziel path, .linie path { fill: none; stroke: var(--rot); stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; stroke-dasharray: calc(var(--len, 4000) * 1px); stroke-dashoffset: calc(var(--len, 4000) * 1px); }
/* Kreis: Resend nimmt calc(100% + 20px); im großen Titel braucht die Ellipse mehr Luft, sonst schneidet sie die Buchstaben */
.ziel .kreis { left: -.3em; top: -.24em; width: calc(100% + .6em); height: calc(100% + .5em); }
.ziel .kreis path { stroke-width: max(2.5px, .05em); }
/* Strich: zufällig aus vier Handstrichen */
.ziel .strich { left: -6px; bottom: -.16em; width: calc(100% + 12px); height: .3em; }
.ziel .strich path { stroke-width: max(2.5px, .055em); }
.ziel:not([data-art='kreis']) .kreis, .ziel:not([data-art='strich']) .strich { display: none; }
/* Textmarker: Hintergrundfläche wächst von links (background-size 0 → 100 %) */
.mz { background: var(--tinte-gelb) 0 88% / 0% 62% no-repeat; -webkit-box-decoration-break: clone; box-decoration-break: clone; padding: 0 .1em; margin: 0 -.1em; border-radius: .14em .32em .2em .38em / .3em .22em .36em .26em; }
.ziel[data-art='marker'] .ziel-text { background: var(--tinte-gelb) 0 86% / 0% 58% no-repeat; padding: 0 .08em; margin: 0 -.08em; border-radius: .12em .3em .18em .34em / .3em .2em .34em .24em; }
/* Zustände: .an zeichnet ein, .aus läuft nach rechts aus (Rücklauf wie Resend: 240 ms nach 360 ms) */
.an path { stroke-dashoffset: 0; transition: stroke-dashoffset var(--ein) var(--kurve); }
.aus path { stroke-dashoffset: calc(var(--len, 4000) * -1px); transition: stroke-dashoffset var(--aus) var(--kurve) var(--aus-verzug); }
.ziel.an[data-art='marker'] .ziel-text, .an .mz, .mz.an { background-size: 100% 58%; transition: background-size var(--marker-dauer) var(--kurve) var(--d, 0ms); }
.an .mz, .mz.an { background-size: 100% 62%; }
.ziel.aus[data-art='marker'] .ziel-text { background-position: 100% 86%; background-size: 0% 58%; transition: background-size var(--aus) var(--kurve) var(--aus-verzug); }
/* ---------- Arbeitsblatt (eigene Bildwelt) ---------- */
.blatt { position: relative; background: var(--karte); border-radius: 18px; padding: 28px 28px 24px; box-shadow: 0 1px 0 rgba(21,23,28,.04), 0 24px 60px -28px rgba(21,23,28,.28); rotate: 1.4deg; }
.blatt .vorher, .blatt .nachher { background: repeating-linear-gradient(transparent 0 33px, rgba(70, 110, 170, .13) 33px 34px); margin-inline: -28px; padding-right: 28px; }
.blatt::before { content: ''; position: absolute; left: 56px; top: 0; bottom: 0; width: 1px; background: rgba(210, 64, 42, .22); }
.blatt .kopfzeile { display: flex; justify-content: space-between; gap: 12px; padding-left: 44px; margin-bottom: 18px; }
.blatt .kopfzeile .hand { font-size: 14px; color: var(--rot-text); }
.blatt .kopfzeile > * { white-space: nowrap; }
.blatt .notiz { padding-left: 44px; }
.blatt .vorher, .blatt .nachher { padding-left: 72px; }
.blatt .vorher { font-size: 16px; line-height: 34px; color: var(--leise); }
/* Durchgestrichen: ein Handstrich je Zeile (box-decoration-break: clone) */
.gestrichen { background: no-repeat 0 58% / 100% 8px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 8' preserveAspectRatio='none'%3E%3Cpath d='M1 5.2C25 3 60 4.8 99 2.6' fill='none' stroke='%23d2402a' stroke-width='1.8' stroke-linecap='round' vector-effect='non-scaling-stroke' opacity='.85'/%3E%3C/svg%3E"); -webkit-box-decoration-break: clone; box-decoration-break: clone; }
.blatt .nachher { font-size: 22px; line-height: 34px; color: var(--rot-text); margin-top: 4px; font-weight: 500; }
.blatt .notiz { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 18px; padding-top: 14px; border-top: 1px dashed var(--linie); }
.blatt .notiz .hand { font-size: 14px; color: var(--erfolg-text); display: flex; align-items: center; gap: 6px; }
.blatt .notiz svg { width: 18px; height: 18px; }
.punkte { display: flex; gap: 6px; }
.punkte i { width: 22px; height: 6px; border-radius: 3px; background: var(--linie); }
.punkte i.fertig { background: var(--erfolg); }
.randnotiz { position: absolute; right: -6px; top: -22px; rotate: 4deg; background: var(--gelb); color: var(--tinte); font-size: 13px; padding: 6px 12px 7px; border-radius: 4px 10px 6px 12px; box-shadow: 0 8px 18px -10px rgba(21,23,28,.4); }
/* ---------- Leiste: Variante und Werte ---------- */
.leiste { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px 24px; padding-block: 18px 22px; border-top: 1px solid var(--linie); }
.modi { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; }
.modi > span { margin-right: 8px; }
.modi button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--leise); background: none; border: 1px solid var(--linie); border-radius: 999px; height: 30px; padding: 0 12px; cursor: pointer; transition: color 150ms, background 150ms, border-color 150ms; }
.modi button:hover { color: var(--tinte); border-color: rgba(21, 23, 28, .3); }
.modi button[aria-pressed='true'] { background: var(--tinte); color: var(--grund); border-color: var(--tinte); }
.werte { display: flex; flex-wrap: wrap; gap: 6px 18px; }
.werte b { color: var(--tinte); font-weight: 500; }
/* ---------- Weiter unten ---------- */
.abschnitt { padding-block: clamp(72px, 12vw, 140px); border-top: 1px solid var(--linie); }
.abschnitt h2 { font: 650 clamp(1.75rem, 1.2rem + 2vw, 2.75rem)/1.1 'Bricolage Grotesque', sans-serif; font-variation-settings: 'opsz' 48; letter-spacing: -.02em; max-width: 18ch; margin-top: 14px; }
.lernweg { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.4fr); gap: clamp(32px, 6vw, 96px); }
.module { list-style: none; border-top: 1px solid var(--linie); }
.module li { display: grid; grid-template-columns: 7.5em 1fr; gap: 4px 20px; padding: 22px 0; border-bottom: 1px solid var(--linie); }
.module .hand { font-size: 14px; color: var(--rot-text); padding-top: 4px; line-height: 1.35; }
.module .hand small { display: block; font: 500 11px/1.3 'Geist Mono', monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--leise); margin-top: 4px; }
.lernweg > div:first-child { align-self: start; position: sticky; top: 40px; }
.module h3 { font: 600 clamp(1.2rem, 1rem + .7vw, 1.5rem)/1.25 'Bricolage Grotesque', sans-serif; font-variation-settings: 'opsz' 32; letter-spacing: -.01em; }
.module p { grid-column: 2; color: var(--leise); line-height: 1.55; max-width: 52ch; }
.linie { position: relative; display: inline-block; cursor: default; }
.linie svg { position: absolute; left: -4px; bottom: -.28em; width: calc(100% + 8px); height: .4em; overflow: visible; pointer-events: none; }
.linie path { stroke-width: 2.5px; }
.stimme blockquote { font: 600 clamp(1.75rem, 1.1rem + 2.6vw, 3.25rem)/1.18 'Bricolage Grotesque', sans-serif; font-variation-settings: 'opsz' 72; letter-spacing: -.02em; max-width: 24ch; margin-top: 22px; text-wrap: pretty; }
.stimme figcaption { display: flex; gap: 14px; align-items: center; margin-top: 30px; color: var(--leise); line-height: 1.45; font-size: 15px; }
.stimme figcaption b { color: var(--tinte); font-weight: 600; display: block; }
.portrait { width: 52px; height: 52px; border-radius: 50%; flex: none; }
.fragen p:not(.mono) { font-size: clamp(1.15rem, 1rem + .6vw, 1.5rem); line-height: 1.5; max-width: 40ch; margin-top: 18px; text-wrap: pretty; }
/* Unterstreichung wie bei Attio: Linie .06em, Position 0 91 %, Text-Kontur spart Unterlängen aus */
.fragen a { text-decoration: none; background: linear-gradient(currentColor, currentColor) 0 91% / 0% .06em no-repeat; -webkit-text-stroke: .1em var(--grund); paint-order: stroke fill; transition: background-size 400ms var(--kurve); color: var(--tinte); font-weight: 600; }
.fragen a:hover, .fragen a:focus-visible, .fragen a.an { background-size: 100% .06em; transition-duration: 300ms; }
.fragen a:active { transition-duration: 50ms; }
.fuss { padding-block: 32px 40px; border-top: 1px solid var(--linie); display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
/* ---------- Reduzierte Bewegung: alles sofort im Endzustand ---------- */
:root.reduziert * { transition-duration: .01ms !important; transition-delay: 0ms !important; animation-duration: .01ms !important; }
:root.reduziert .ziel[data-art='marker'] .ziel-text, :root.reduziert .stimme .mz { background-size: 100% 60%; background-position: 0 87%; }
:root.reduziert .ziel path, :root.reduziert .linie path { stroke-dashoffset: 0; }
:root.reduziert .module .linie:not(.an) path { stroke-dashoffset: calc(var(--len, 4000) * 1px); }
/* ---------- Touch: Hover-Varianten spielen beim Eintritt einmal ---------- */
@media (hover: none) {
.nav a:hover { color: var(--leise); }
.linie { cursor: auto; }
}
/* ---------- Schmal ---------- */
@media (max-width: 860px) {
.nav { display: none; }
.held { grid-template-columns: 1fr; }
.blatt { rotate: 0deg; max-width: 520px; }
.lernweg { grid-template-columns: 1fr; }
}
@media (max-width: 480px) {
.marke span { display: none; }
.held .start { font-size: 13px; }
.blatt { padding: 22px 18px 18px; }
.blatt::before { left: 40px; }
.blatt .kopfzeile, .blatt .notiz { padding-left: 34px; }
.blatt .vorher, .blatt .nachher { margin-inline: -18px; padding-left: 52px; padding-right: 18px; }
.blatt .kopfzeile .mono { display: none; }
.blatt .nachher { font-size: 19px; }
.module li { grid-template-columns: 1fr; }
.module p { grid-column: 1; }
.randnotiz { right: 8px; }
}
/* ---------- Galerie-Vorschau: nur die Bühne ---------- */
.vorschau body { overflow: hidden; }
.vorschau .abschnitt, .vorschau .fuss { display: none; }
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body>
<section class="buehne">
<header class="kopf rand">
<a class="marke" href="#" aria-label="Silbenhaus, Sprachschule Augsburg"><b>Silbenhaus</b><span class="mono">Sprachschule Augsburg</span></a>
<ul class="nav">
<li><a href="#lernweg">Lernweg</a></li>
<li><a href="#stimmen">Stimmen</a></li>
<li><a href="#">Milena</a></li>
<li><a href="#fragen">Fragen</a></li>
</ul>
<a class="knopf klein" href="#">Anmelden</a>
</header>
<div class="held rand">
<div>
<p class="start hand">B2 Pflege · 10 Wochen · Start 12. Januar 2027</p>
<h1>In zehn Wochen sicher durch jede <strong class="ziel" data-ziel data-art="marker" style="font-weight:inherit"><span class="ziel-text">Übergabe</span><svg class="kreis" viewBox="0 0 200 60" preserveAspectRatio="none" aria-hidden="true"><path d="M118 5C66 1 14 10 7 31C1 50 58 58 108 56C162 54 197 45 194 27C191 10 150 2 96 7C72 9 52 13 38 20"/></svg><svg class="strich" viewBox="0 0 200 20" preserveAspectRatio="none" aria-hidden="true"><path d="M3 12C50 6 120 5 197 9"/></svg></strong>.</h1>
<p class="einleitung">Deutsch für Pflegekräfte, die für die Anerkennung B2 brauchen. Jede Woche eine Szene aus dem Stationsalltag, eine Schreibübung und persönliche Korrektur von Milena.</p>
<div class="handeln">
<a class="knopf" href="#">Platz sichern</a>
<p class="preis"><b>590 €</b> oder 3 × 199 €<br>Förderung mit Bildungsgutschein möglich</p>
</div>
</div>
<figure class="blatt" aria-label="Beispiel aus Modul 03: ein Satz für die Übergabe wird auf sechs Wörter gekürzt">
<span class="randnotiz hand" aria-hidden="true">kurz & klar!</span>
<div class="kopfzeile"><span class="hand">Modul 03 · Die Übergabe</span><span class="mono">45 min</span></div>
<p class="vorher"><span class="gestrichen">Die Patientin aus Zimmer 12 hat in der Nacht gesagt, dass sie</span> starke Schmerzen im Rücken hat und nicht gut schlafen konnte.</p>
<p class="nachher hand">Zi. 12: nachts Rückenschmerzen, kaum geschlafen.</p>
<div class="notiz">
<span class="hand"><svg viewBox="0 0 18 18" aria-hidden="true"><path d="M3 9.5l4 4L15 4.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>22 → 6 Wörter</span>
<span class="punkte" aria-label="Aufgabe 2 von 4"><i class="fertig"></i><i class="fertig"></i><i></i><i></i></span>
</div>
</figure>
</div>
<div class="leiste rand">
<div class="modi mono" role="group" aria-label="Art der Markierung">
<span>Markierung</span>
<button type="button" data-modus="marker" aria-pressed="true">Textmarker</button>
<button type="button" data-modus="kreis" aria-pressed="false">Kreis</button>
<button type="button" data-modus="strich" aria-pressed="false">Strich zufällig</button>
</div>
<p class="werte mono" data-werte aria-live="polite"></p>
</div>
</section>
<section class="abschnitt" id="lernweg">
<div class="rand lernweg">
<div>
<p class="mono">Lernweg · Unterstreichung bei Hover</p>
<h2>Sechs Module, jedes mit einer Szene von der Station.</h2>
</div>
<ol class="module">
<li><span class="hand">Modul 01<small>40 min</small></span><h3><span class="linie">Ankommen im Team</span></h3><p>Du stellst dich vor, fragst nach und sagst, wenn du etwas nicht verstanden hast.</p></li>
<li><span class="hand">Modul 02<small>50 min</small></span><h3><span class="linie">Am Telefon</span></h3><p>Angehörige zurückrufen, Nachrichten notieren, Namen buchstabieren.</p></li>
<li><span class="hand">Modul 03<small>45 min</small></span><h3><span class="linie">Übergabe schreiben</span></h3><p>Kurze Sätze, Fachwörter und die richtige Zeitform für die Dokumentation.</p></li>
<li><span class="hand">Modul 04<small>55 min</small></span><h3><span class="linie">Die Visite verstehen</span></h3><p>Wiedergeben, was die Ärztin anordnet, nachfragen und bestätigen.</p></li>
<li><span class="hand">Modul 05<small>60 min</small></span><h3><span class="linie">Der Pflegebericht</span></h3><p>Beschreiben statt bewerten, mit Feedback in der Gruppe.</p></li>
<li><span class="hand">Modul 06<small>90 min</small></span><h3><span class="linie">Probeprüfung B2</span></h3><p>Schreiben und Sprechen wie in der echten Prüfung, von Milena schriftlich korrigiert.</p></li>
</ol>
</div>
</section>
<section class="abschnitt stimme" id="stimmen">
<figure class="rand">
<p class="mono">Stimmen · Textmarker beim Scrollen, einmal</p>
<blockquote>„Bei der Übergabe frage ich nicht mehr dreimal nach. <mark class="mz-satz" data-marker style="background:none;color:inherit">Und meine Berichte gehen jetzt ohne Rückfragen durch.</mark>“</blockquote>
<figcaption>
<svg class="portrait" viewBox="0 0 52 52" aria-hidden="true"><circle cx="26" cy="26" r="26" fill="#f7c325"/><circle cx="26" cy="21" r="8.5" fill="#15171c"/><path d="M10 45c3-9 9-13 16-13s13 4 16 13" fill="#15171c"/></svg>
<span><b>Ana Petrović</b>Pflegefachfrau in Haunstetten, Kurs Herbst 2025</span>
</figcaption>
</figure>
</section>
<section class="abschnitt fragen" id="fragen">
<div class="rand">
<p class="mono">Fragen · Unterstreichung bei Hover</p>
<p>Du willst wissen, ob die Arbeitsagentur oder dein Arbeitgeber den Kurs bezahlt? Milena schickt dir gern die <a href="#">Unterlagen zur Förderung</a>, oder du schaust in die <a href="#">häufigen Fragen</a>.</p>
</div>
</section>
<footer class="fuss rand mono"><span>Silbenhaus · Sprachschule · Augsburg</span><span>Nächster Kurs: 12.01.2027</span></footer>
<script>
(() => {
const H = document.documentElement;
const reduziert = () => H.classList.contains('reduziert');
const ziel = document.querySelector('[data-ziel]');
const strichPfad = ziel.querySelector('.strich path');
const werte = document.querySelector('[data-werte]');
// Vier Handstriche für „Strich zufällig“ (Osmo „Draw Random Underline“).
const STRICHE = [
'M3 12C50 6 120 5 197 9',
'M3 11C30 4 50 16 80 10S130 4 160 11S190 8 197 7',
'M4 8C60 4 140 5 196 6M14 15C70 11 130 12 188 13',
'M2 13C60 9 140 6 194 5C172 8 152 12 142 17',
];
let letzter = 0;
function zufallsStrich(pfad) {
let i;
do { i = Math.floor(Math.random() * STRICHE.length); } while (i === letzter);
letzter = i;
pfad.setAttribute('d', STRICHE[i]);
}
// Zeichnen und auslaufen lassen. Ausgangszustand hat keine Transition, darum springt
// nach dem Auslaufen alles unsichtbar zurück.
// Länge des Pfads auf dem Bildschirm messen. Wegen preserveAspectRatio="none" ist sie
// nicht die Länge im viewBox; pathLength="1" würde mit non-scaling-stroke falsch rechnen.
function messe(el) {
el.querySelectorAll('path').forEach(p => {
const svg = p.ownerSVGElement, vb = svg.viewBox.baseVal, r = svg.getBoundingClientRect();
if (!r.width || !p.getAttribute('d')) return;
const sx = r.width / vb.width, sy = r.height / vb.height, L = p.getTotalLength();
let summe = 0, vor = null;
for (let i = 0; i <= 80; i++) {
const q = p.getPointAtLength(L * i / 80), x = q.x * sx, y = q.y * sy;
if (vor) summe += Math.hypot(x - vor[0], y - vor[1]);
vor = [x, y];
}
p.style.setProperty('--len', Math.ceil(summe + 4));
});
}
const timer = new WeakMap();
function zeichne(el, pfad) {
clearTimeout(timer.get(el));
el.classList.remove('aus');
if (pfad) zufallsStrich(pfad);
messe(el);
void el.offsetWidth;
el.classList.add('an');
}
function loesche(el) {
if (!el.classList.contains('an')) return;
el.classList.replace('an', 'aus');
timer.set(el, setTimeout(() => el.classList.remove('aus'), reduziert() ? 0 : 640));
}
const WERTE = {
marker: [['Kurve', 'cubic-bezier(.66,.06,.36,.96)'], ['Ein', '750 ms'], ['Aus', '240 ms nach 360 ms'], ['Fläche', '0 → 100 % × 58 %']],
kreis: [['Kurve', 'cubic-bezier(.66,.06,.36,.96)'], ['Ein', '360 ms'], ['Aus', '240 ms nach 360 ms'], ['Weg', 'dashoffset 1 → 0 → −1']],
strich: [['Kurve', 'cubic-bezier(.66,.06,.36,.96)'], ['Ein', '360 ms'], ['Aus', '240 ms nach 360 ms'], ['Pfade', '4, zufällig']],
};
let modus = 'marker';
function zeigeWerte() {
werte.innerHTML = reduziert()
? '<span>Reduzierte Bewegung: <b>Markierung sofort sichtbar</b></span>'
: WERTE[modus].map(([k, v]) => `<span>${k} <b>${v}</b></span>`).join('');
}
function setzeModus(neu) {
ziel.classList.remove('an', 'aus');
clearTimeout(timer.get(ziel));
modus = neu;
ziel.dataset.art = neu;
document.querySelectorAll('[data-modus]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.modus === neu)));
zeigeWerte();
}
const titelZeichnen = () => zeichne(ziel, modus === 'strich' ? strichPfad : null);
document.querySelector('.modi').addEventListener('click', e => {
const b = e.target.closest('[data-modus]');
if (!b) return;
pause();
setzeModus(b.dataset.modus);
requestAnimationFrame(titelZeichnen);
});
// Hover auf das Wort zeichnet neu (bei „Strich“ jedes Mal ein anderer Strich), Tippen ebenso
ziel.addEventListener('pointerenter', e => { if (e.pointerType === 'mouse' && !uhr) { ziel.classList.remove('an', 'aus'); titelZeichnen(); } });
ziel.addEventListener('click', () => { if (uhr) return; ziel.classList.remove('an', 'aus'); requestAnimationFrame(titelZeichnen); });
// Lernweg: zufällige Unterstreichung bei Hover; ohne Hover einmal beim Eintritt
const linien = [...document.querySelectorAll('.linie')];
linien.forEach(l => {
l.insertAdjacentHTML('beforeend', '<svg viewBox="0 0 200 20" preserveAspectRatio="none" aria-hidden="true"><path d=""/></svg>');
const p = l.querySelector('path');
l.addEventListener('pointerenter', e => { if (e.pointerType === 'mouse') zeichne(l, p); });
l.addEventListener('pointerleave', e => { if (e.pointerType === 'mouse') loesche(l); });
});
const ohneHover = matchMedia('(hover: none)').matches;
if (ohneHover) {
const io = new IntersectionObserver(eintraege => eintraege.forEach(en => {
if (!en.isIntersecting) return;
const l = en.target;
setTimeout(() => zeichne(l, l.querySelector('path')), 120);
io.unobserve(l);
}), { threshold: 1, rootMargin: '0px 0px -15% 0px' });
linien.forEach(l => io.observe(l));
document.querySelectorAll('.fragen a').forEach(a => {
new IntersectionObserver(([en], o) => { if (en.isIntersecting) { a.classList.add('an'); o.disconnect(); } }, { rootMargin: '0px 0px -20% 0px' }).observe(a);
});
}
// Stimme: Textmarker über mehrere Zeilen. Jede Zeile bekommt ein eigenes Stück
// (box-decoration-break: clone) und zeichnet 200 ms nach der vorigen.
const satz = document.querySelector('[data-marker]');
const satzText = satz.textContent;
function zeilenTeilen() {
const war = satz.classList.contains('an');
satz.textContent = '';
const woerter = satzText.split(' ').map((w, i, a) => {
const s = document.createElement('span');
s.textContent = w + (i < a.length - 1 ? ' ' : '');
satz.appendChild(s);
return s;
});
const zeilen = [];
woerter.forEach(w => {
const top = Math.round(w.offsetTop);
const z = zeilen[zeilen.length - 1];
if (z && Math.abs(z.top - top) < 4) z.w.push(w.textContent); else zeilen.push({ top, w: [w.textContent] });
});
satz.textContent = '';
zeilen.forEach((z, i) => {
const s = document.createElement('span');
s.className = 'mz';
s.style.setProperty('--d', i * 200 + 'ms');
s.textContent = z.w.join('').replace(/ $/, '');
satz.appendChild(s);
if (i < zeilen.length - 1) satz.appendChild(document.createTextNode(' '));
});
satz.classList.toggle('an', war);
}
new IntersectionObserver(([en], o) => {
if (en.isIntersecting) { satz.classList.add('an'); o.disconnect(); }
}, { rootMargin: '0px 0px -25% 0px' }).observe(satz);
let breite = innerWidth;
addEventListener('resize', () => { if (innerWidth !== breite) { breite = innerWidth; zeilenTeilen(); messe(ziel); linien.forEach(messe); } });
// Vorführung: Textmarker, Kreis, Strich im Wechsel; jede Art zeichnet ein, hält, läuft aus
let uhr = null, schritt = 0;
const reihe = ['marker', 'kreis', 'strich'];
function vorfuehren() {
pause();
schritt = 0;
const takt = () => {
setzeModus(reihe[schritt % reihe.length]);
requestAnimationFrame(titelZeichnen);
uhr = setTimeout(() => {
loesche(ziel);
uhr = setTimeout(() => { schritt++; takt(); }, reduziert() ? 200 : 900);
}, 2400);
};
takt();
}
function pause() { clearTimeout(uhr); uhr = null; }
function weiter() { if (!uhr) vorfuehren(); }
function neustart() {
pause();
scrollTo(0, 0);
setzeModus(modus);
requestAnimationFrame(titelZeichnen);
}
document.fonts.ready.then(() => {
zeilenTeilen();
zeigeWerte();
messe(ziel);
if (!H.classList.contains('vorschau')) setTimeout(titelZeichnen, 400);
});
})();
</script>
</body>
</html>