Gemischte Schnitte in einer Zeile (Grotesk + Kursiv-Serif)
Ein Wort in Kursiv-Serif mitten im Grotesk-Satz: die leise Stimme einer Seite, sauber auf x-Höhe und Laufweite angeglichen.
Eine Headline in Grotesk, in der ein oder zwei Wörter in einer Kursiv-Serif stehen. Verwandte Formen: drei Schnitte zeilenweise gestapelt (Serif, Grotesk, Serif-Kursiv) oder nur der Anfangsbuchstabe im Hausschnitt.
Der Schnittwechsel betont, ohne lauter zu werden. Die Kursive klingt wie eine Stimme im sachlichen Satz und gibt der Seite Persönlichkeit, während Größe und Gewicht ruhig bleiben.
Wann einsetzen
Intro-Sätze auf Portfolio- und Beraterseiten, Editorial-Headlines, Fallstudien-Titel, Notizen- und Journal-Übersichten.
Wann nicht
Mehr als zwei kursive Wörter pro Headline, Fließtext-Betonungen, UI-Beschriftungen, Marken mit nur einer Schrift. Nicht mit einer Kursiven, deren x-Höhe stark abweicht, ohne sie anzugleichen.
Varianten
- Im Satz: ein bis zwei Wörter in Kursiv-Serif (Emil Kowalski, Paco Coursey)
- Gestapelt: je Zeile ein Schnitt, line-height 0.83, letter-spacing -0.05em (Instrument)
- Initiale im Hausschnitt: nur der erste Buchstabe in der Serif, mit Laufweitenkorrektur je Buchstabe (Every)
Umsetzung
Das Wort als <em> mit eigener Schrift auszeichnen: font-family Serif, font-style italic, font-optical-sizing auto. Laufweite auf 0 zurücksetzen, weil die enge Headline-Laufweite die Kursive zusammendrückt. Größe angleichen: entweder pauschal 1.05em oder exakt mit font-size-adjust: ex-height <x-Höhe der Grotesk>. Den Wert misst man im Browser mit einer Sonde width: 10ex. Für Initialen dasselbe mit cap-height. Für gestapelte Zeilen alle Schnitte gleich groß setzen und nur Laufweite und Zeilenabstand gemeinsam führen.
Robust umsetzen
Telefon. Funktioniert unverändert, weil die Mischung im Satz liegt. Gestapelte Zeilen mit clamp() verkleinern; Einzüge in em mitskalieren lassen.
Ohne Bewegung. Nicht relevant, das Muster selbst bewegt sich nicht. Ein gestaffeltes Einblenden der Zeilen entfällt bei reduzierter Bewegung.
Barrierefrei. <em> nur verwenden, wenn die Betonung auch inhaltlich gemeint ist, sonst <span>. Wörter nicht auf mehrere Elemente zerlegen. Die Kursive muss in der Größe gut lesbar bleiben; Kontrast wie beim übrigen Text.
Code
<!DOCTYPE html>
<!--
Gemischte Schnitte in einer Zeile (Grotesk + Kursiv-Serif)
Fundus · Typo-Techniken · https://cecodesign.de/lab/fundus/typo-techniken/gemischte-schnitte-zeile/
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>Gemischte Schnitte in einer Zeile (Grotesk + Kursiv-Serif) · 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: 'Host Grotesk'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/host-grotesk:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 300 800; font-display: swap; }
@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: '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 {
/* Rezept „Stille Visitenkarte“: Graustufen, eine einzige Farbe für Fokus und Link */
--grund: #fcfcfc; --text: #0b0b09; --zweit: #646464; --linie: #e8e8e8; --flaeche: #f0f0f0;
--blau: #2f5bea;
--kurve: cubic-bezier(0.165, 0.84, 0.44, 1); /* easeOutQuart */
--ui: 120ms; /* Hover, Farbe */
--form: 240ms; /* Größenwechsel der Kursiven */
--ein: 480ms; /* gestaffeltes Einblenden */
--staffel: 80ms;
--serif: 'Newsreader', Georgia, serif;
--sans: 'Host Grotesk', system-ui, sans-serif;
--mono: 'Geist Mono', ui-monospace, monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--grund); color: var(--text); font: 400 16px/1.6 var(--sans); -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
body { min-height: 100svh; padding: clamp(16px, 3vw, 40px) clamp(18px, 4vw, 56px) 64px; }
.mono { font: 450 11px/1.45 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--zweit); }
.ziff { font-variant-numeric: tabular-nums; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--blau); outline-offset: 3px; border-radius: 2px; }
/* ——— Bedienleiste (im Fluss, nicht fixiert) ——— */
.leiste { max-width: 1072px; margin: 0 auto; display: flex; flex-wrap: wrap; gap: 12px 28px; align-items: center; padding-bottom: 18px; border-bottom: 1px solid var(--linie); }
.gruppe { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; transition: opacity var(--ui) var(--kurve); }
.gruppe[aria-disabled="true"] { opacity: .35; pointer-events: none; }
.wahl { display: inline-flex; gap: 2px; padding: 2px; border: 1px solid var(--linie); border-radius: 8px; background: #fff; }
.wahl button, .schalter {
font: inherit; letter-spacing: .06em; text-transform: none; color: var(--zweit);
background: none; border: 0; border-radius: 6px; padding: 6px 10px; cursor: pointer;
transition: color var(--ui) var(--kurve), background-color var(--ui) var(--kurve);
}
.wahl button:hover, .schalter:hover { color: var(--text); background: var(--flaeche); }
.wahl button[aria-pressed="true"] { color: var(--grund); background: var(--text); }
.schalter { border: 1px solid var(--linie); background: #fff; display: inline-flex; align-items: center; gap: 8px; letter-spacing: .14em; text-transform: uppercase; }
.schalter::before { content: ""; width: 7px; height: 7px; border-radius: 50%; border: 1px solid currentColor; }
.schalter[aria-pressed="true"] { color: var(--text); }
.schalter[aria-pressed="true"]::before { background: var(--blau); border-color: var(--blau); }
.leiste .rechts { margin-left: auto; }
.hinweis { flex-basis: 100%; color: var(--blau); text-transform: none; letter-spacing: .02em; }
.hinweis[hidden] { display: none; }
/* ——— Visitenkarte ——— */
.seite { max-width: 1072px; margin: 0 auto; }
.kopf { display: grid; grid-template-columns: 1fr auto; gap: 4px 24px; padding: clamp(28px, 4vw, 52px) 0 0; }
.name { font-weight: 500; font-size: 1rem; letter-spacing: -.005em; }
.rolle { color: var(--zweit); }
.ort { text-align: right; color: var(--zweit); }
.frei { grid-column: 2; text-align: right; display: inline-flex; gap: 8px; align-items: center; justify-content: flex-end; }
.frei::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: #3aa76d; box-shadow: 0 0 0 3px rgba(58, 167, 109, .16); }
.buehne { position: relative; margin-top: clamp(48px, 9vh, 112px); min-height: clamp(360px, 46vh, 460px); }
.ansicht[hidden] { display: none; }
.ansicht > .mono { margin-bottom: clamp(18px, 2.4vw, 28px); }
/* Variante 1: Kursiv-Serif im Satz */
.satz {
font: 500 clamp(1.75rem, 1rem + 2.6vw, 2.75rem)/1.22 var(--sans);
letter-spacing: -.02em; max-width: 25ch; text-wrap: pretty;
}
.neben { margin-top: clamp(22px, 2.6vw, 34px); max-width: 52ch; color: var(--zweit); text-wrap: pretty; }
.neben a, .fuss a { color: var(--text); box-shadow: inset 0 -1px 0 var(--linie); transition: color var(--ui) var(--kurve), box-shadow var(--ui) var(--kurve); }
.neben a:hover, .fuss a:hover { color: var(--blau); box-shadow: inset 0 -1px 0 var(--blau); }
/* Das Muster: ein Wort im Hausschnitt der Stimme */
.kursiv {
font-family: var(--serif); font-style: italic; font-weight: 400;
font-optical-sizing: auto;
font-size: 1em; letter-spacing: inherit; /* Zustand „ohne“ */
transition: font-size var(--form) var(--kurve), letter-spacing var(--form) var(--kurve);
}
[data-angl="rezept"] .kursiv { font-size: 1.05em; letter-spacing: 0; }
[data-angl="exakt"] .kursiv { letter-spacing: 0; font-size-adjust: ex-height var(--xh-grotesk, .5); }
/* Variante 2: drei Schnitte gestapelt (Instrument-Prinzip) */
.stapel { font-size: clamp(3.5rem, 1rem + 9vw, 9rem); line-height: .83; letter-spacing: -.05em; }
.stapel .z { display: block; }
.stapel .z1 { font-family: var(--serif); font-weight: 400; font-optical-sizing: auto; }
.stapel .z2 { font-family: var(--sans); font-weight: 500; padding-left: .52em; }
.stapel .z3 { font-family: var(--serif); font-style: italic; font-weight: 400; font-optical-sizing: auto; padding-left: .16em; }
.fakten { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px 32px; max-width: 760px; margin-top: clamp(30px, 4vw, 52px); padding-top: 16px; border-top: 1px solid var(--linie); }
.fakten dt { margin-bottom: 4px; }
.fakten dd { font-size: .9375rem; line-height: 1.45; }
/* Variante 3: Initiale im Hausschnitt (Every-Prinzip) */
.notizen { list-style: none; max-width: 860px; border-top: 1px solid var(--linie); }
.notizen li { border-bottom: 1px solid var(--linie); }
.notizen a { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px 32px; align-items: baseline; padding: clamp(14px, 1.8vw, 22px) 0; transition: color var(--ui) var(--kurve); }
.notizen a:hover { color: var(--blau); }
.notiz { font: 500 clamp(1.5rem, 1rem + 1.9vw, 2.375rem)/1.15 var(--sans); letter-spacing: -.025em; text-wrap: balance; }
.hand {
font-family: var(--serif); font-weight: 400; font-style: normal; text-transform: uppercase;
font-optical-sizing: auto; font-size: 1em;
transition: font-size var(--form) var(--kurve);
}
.hand-F { margin-right: -.08em; } /* buchstabenweise Laufweite: F −8 % wie Every */
.hand-W { margin-right: -.03em; }
.hand-L { margin-right: -.02em; }
[data-angl="rezept"] .hand { font-size: 1.05em; }
[data-angl="exakt"] .hand { font-size-adjust: cap-height var(--cap-grotesk, .7); }
.notizen time { color: var(--zweit); }
/* ——— Gestaffeltes Einblenden (Signatur des Rezepts) ——— */
.w { display: inline-block; }
.ein .w, .ein .z, .ein .auf { animation: auf var(--ein) var(--kurve) both; animation-delay: calc(var(--n, 0) * var(--staffel)); }
@keyframes auf { from { opacity: 0; transform: translateY(.3em); } }
/* ——— Hilfslinien ——— */
.ebene { position: absolute; inset: -44px 0 0; pointer-events: none; overflow: hidden; }
.ebene > * { position: absolute; transition: top var(--form) var(--kurve), opacity var(--ui) var(--kurve); }
.hl { height: 0; border-top: 1px solid rgba(11, 11, 9, .28); }
.hl.grau { border-top: 1px dashed rgba(11, 11, 9, .45); }
.hl.blau { border-top-color: var(--blau); }
.hl b { position: absolute; bottom: 3px; font: 500 10px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; white-space: nowrap; color: var(--zweit); background: var(--grund); padding: 1px 4px; }
.hl b.r { right: 0; } .hl b.l { left: 0; }
.hl.blau b { color: var(--blau); }
.legende { display: grid; gap: 7px; font: 500 10px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--zweit); white-space: nowrap; transition: top var(--form) var(--kurve); }
.legende.zeile { display: flex; flex-wrap: wrap; gap: 6px 14px; left: 0; right: 0; top: 4px; }
.legende.zeile .wert { color: var(--text); }
.legende.zeile .wert::before { display: none; }
.legende span { display: flex; align-items: center; gap: 8px; }
.legende span::before { content: ""; width: 18px; border-top: 1px solid rgba(11, 11, 9, .28); }
.legende .grau::before { border-top: 1px dashed rgba(11, 11, 9, .45); }
.legende .blau { color: var(--blau); }
.legende .blau::before { border-top-color: var(--blau); }
.delta { font: 600 10px/1 var(--mono); letter-spacing: .04em; color: #fff; background: var(--blau); padding: 4px 6px; border-radius: 4px; white-space: nowrap; font-variant-numeric: tabular-nums; transition: background-color var(--ui) var(--kurve), top var(--form) var(--kurve), left var(--form) var(--kurve); }
.delta.gut { background: #1d7a4e; }
.aus .ebene { opacity: 0; }
.fuss { display: flex; flex-wrap: wrap; gap: 8px 28px; margin-top: clamp(40px, 6vw, 72px); padding-top: 16px; border-top: 1px solid var(--linie); color: var(--zweit); font-size: .875rem; }
.fuss .rechts { margin-left: auto; }
/* ——— Werte ——— */
.werte { max-width: 1072px; margin: 36px auto 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(310px, 100%), 1fr)); gap: 4px 40px; font: 400 11.5px/1.65 var(--mono); color: var(--zweit); }
.werte div { display: flex; gap: 12px; min-width: 0; }
.werte dt { color: var(--text); flex: none; min-width: 12.5em; }
.werte dd { overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
/* ——— Vorschau ——— */
.zustand { display: none; }
.vorschau body { overflow: hidden; padding: 26px 40px; }
.vorschau .leiste, .vorschau .werte, .vorschau .fuss { display: none; }
.vorschau .neben { max-width: 46ch; }
.vorschau .zustand { display: flex; justify-content: space-between; max-width: 1072px; margin: 0 auto; padding-bottom: 14px; border-bottom: 1px solid var(--linie); }
.vorschau .zustand b { color: var(--blau); font-weight: 500; }
.vorschau .buehne { margin-top: 56px; }
.vorschau .satz { font-size: 3.25rem; max-width: 20ch; }
.vorschau .notiz { font-size: 2.5rem; }
.vorschau .stapel { font-size: 9.6rem; }
:root.reduziert * { transition-duration: .01ms !important; animation-duration: .01ms !important; animation-delay: 0s !important; }
@media (max-width: 760px) {
.kopf { grid-template-columns: 1fr; }
.ort, .frei { grid-column: 1; text-align: left; justify-content: flex-start; }
.ort { margin-top: 10px; }
.fakten { grid-template-columns: 1fr; gap: 12px; }
.notizen a { grid-template-columns: 1fr; gap: 6px; }
.leiste { gap: 10px 16px; }
.leiste .rechts { margin-left: 0; }
.werte dt { min-width: 10em; }
.buehne { min-height: 0; }
}
@media (hover: none) {
.wahl button:hover, .schalter:hover { background: none; color: var(--zweit); }
.wahl button[aria-pressed="true"]:hover { background: var(--text); color: var(--grund); }
.wahl button, .schalter { padding: 9px 12px; }
.notizen a:hover { color: inherit; }
}
/* 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="leiste mono" role="toolbar" aria-label="Steuerung der Demo">
<div class="gruppe">
<span>Variante</span>
<div class="wahl" role="group" aria-label="Variante">
<button type="button" data-variante="satz" aria-pressed="true">Im Satz</button>
<button type="button" data-variante="stapel" aria-pressed="false">Gestapelt</button>
<button type="button" data-variante="initial" aria-pressed="false">Initiale</button>
</div>
</div>
<div class="gruppe" data-angl-gruppe>
<span>Angleichung</span>
<div class="wahl" role="group" aria-label="Größenangleichung der Serif">
<button type="button" data-angl="ohne" aria-pressed="false">ohne</button>
<button type="button" data-angl="rezept" aria-pressed="true">1.05em</button>
<button type="button" data-angl="exakt" aria-pressed="false">font-size-adjust</button>
</div>
</div>
<button type="button" class="schalter rechts" data-linien aria-pressed="true">Hilfslinien</button>
<p class="hinweis" data-hinweis hidden>Dieser Browser kennt font-size-adjust nicht. Die Stufe „font-size-adjust“ sieht hier aus wie „ohne“.</p>
</div>
<p class="zustand mono" aria-hidden="true"><span>Gemischte Schnitte · <b data-zustand>Im Satz</b></span><span data-zustand-angl>1.05em</span></p>
<div class="seite" data-seite>
<header class="kopf">
<span class="name">Marlene Ostertag</span>
<span class="ort mono ziff">Augsburg · <span data-uhr>09:41</span></span>
<span class="rolle">Interaction Design</span>
<span class="frei mono">Frei ab Februar 2027</span>
</header>
<main class="buehne" data-buehne data-angl="rezept">
<section class="ansicht" data-ansicht="satz" aria-label="Variante: Kursiv-Serif im Satz">
<h1 class="satz" data-staffeln>Ich gestalte <em class="kursiv">ruhige</em> Oberflächen für Werkzeuge, die man jeden Tag benutzt, und schreibe über <em class="kursiv">Formulare</em>.</h1>
<p class="neben auf" style="--n:5">Selbstständig seit 2019, davor sechs Jahre im Produktteam eines Logistik-Softwarehauses in Gersthofen. Gerade: eine Ausleih-App für das <a href="#leihwerk">Leihwerk Augsburg</a> und ein Antragsportal für eine Wohnbaugenossenschaft.</p>
</section>
<section class="ansicht" data-ansicht="stapel" aria-label="Variante: drei Schnitte gestapelt" hidden>
<p class="mono auf">Fallstudie 03 · Leihwerk Augsburg e. V.</p>
<h1 class="stapel"><span class="z z1" style="--n:0">Ausleihen</span><span class="z z2" style="--n:1">in drei</span><span class="z z3" style="--n:2">Minuten.</span></h1>
<dl class="fakten auf" style="--n:4">
<div><dt class="mono">Auftrag</dt><dd>Ausleih-App und Rückgabe-Formular für 1.400 Werkzeuge</dd></div>
<div><dt class="mono">Zeitraum</dt><dd class="ziff">März bis Juli 2026</dd></div>
<div><dt class="mono">Ergebnis</dt><dd class="ziff">Ausleihe ohne Tresen, im Schnitt 2:40 Minuten</dd></div>
</dl>
</section>
<section class="ansicht" data-ansicht="initial" aria-label="Variante: Initiale im Hausschnitt" hidden>
<p class="mono auf">Notizen</p>
<ol class="notizen">
<li class="auf" style="--n:1"><a href="#n1"><h2 class="notiz"><span class="hand hand-F">F</span>ehlermeldungen dürfen freundlich sein</h2><time class="mono ziff" datetime="2026-10-14">14.10.2026</time></a></li>
<li class="auf" style="--n:2"><a href="#n2"><h2 class="notiz"><span class="hand hand-W">W</span>arum ein Formular ein Gespräch ist</h2><time class="mono ziff" datetime="2026-08-27">27.08.2026</time></a></li>
<li class="auf" style="--n:3"><a href="#n3"><h2 class="notiz"><span class="hand hand-L">L</span>eere Zustände, volle Absicht</h2><time class="mono ziff" datetime="2026-06-03">03.06.2026</time></a></li>
</ol>
</section>
<div class="ebene" data-ebene aria-hidden="true"></div>
</main>
<footer class="fuss">
<a href="#mail">post@marlene-ostertag.de</a>
<span>Notizen als <a href="#rss">RSS</a></span>
<span class="rechts ziff">Ulrichsplatz 7 · 86150 Augsburg</span>
</footer>
</div>
<dl class="werte" data-werte></dl>
<script>
(() => {
const html = document.documentElement;
const reduziert = () => html.classList.contains('reduziert');
const $ = (s, w = document) => w.querySelector(s);
const $$ = (s, w = document) => [...w.querySelectorAll(s)];
const buehne = $('[data-buehne]'), ebene = $('[data-ebene]'), werteListe = $('[data-werte]');
const anglGruppe = $('[data-angl-gruppe]'), linienKnopf = $('[data-linien]');
const kannAdjust = !!(window.CSS && CSS.supports('font-size-adjust', 'ex-height 0.5'));
if (!kannAdjust) $('[data-hinweis]').hidden = false;
const NAMEN = { satz: 'Im Satz', stapel: 'Gestapelt', initial: 'Initiale' };
const ANGL = { ohne: 'ohne Angleichung', rezept: '1.05em', exakt: 'font-size-adjust' };
let variante = 'satz', angl = 'rezept', linien = true;
/* Uhr: Ortszeit Augsburg in Tabellenziffern */
const uhr = $('[data-uhr]');
const fmt = new Intl.DateTimeFormat('de-DE', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Berlin' });
const ticken = () => { uhr.textContent = fmt.format(new Date()); };
ticken(); setInterval(ticken, 30000);
/* Wörter einzeln einpacken, damit sie zeilenweise gestaffelt erscheinen */
function woerter(el) {
const tw = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
const knoten = []; while (tw.nextNode()) knoten.push(tw.currentNode);
knoten.forEach(t => {
const teile = t.textContent.split(/(\s+)/);
const frag = document.createDocumentFragment();
teile.forEach(s => {
if (!s) return;
if (/^\s+$/.test(s)) frag.append(s);
else { const w = document.createElement('span'); w.className = 'w'; w.textContent = s; frag.append(w); }
});
t.replaceWith(frag);
});
}
function zeilenNummern(el) {
let oben = null, n = -1;
$$('.w', el).forEach(w => {
const t = Math.round(w.offsetTop);
if (oben === null || Math.abs(t - oben) > 4) { n++; oben = t; }
w.style.setProperty('--n', n);
});
}
$$('[data-staffeln]').forEach(woerter);
/* Marken für die Grundlinie: leeres inline-block, dessen Oberkante auf der Grundlinie liegt */
const marke = el => { const m = document.createElement('i'); m.style.cssText = 'display:inline-block;width:0;height:0;vertical-align:baseline'; el.prepend(m); return m; };
const kursivErste = $('[data-ansicht="satz"] .kursiv');
const mSatz = marke(kursivErste);
const zeilenMarken = $$('.stapel .z').map(marke);
const handErste = $('[data-ansicht="initial"] .hand');
const mHand = marke(handErste);
/* x-Höhe und Versalhöhe so, wie der Browser sie verwendet: Sonden mit width: 10ex bzw. 10cap im echten Kontext */
const kannCap = !!(window.CSS && CSS.supports('width', '1cap'));
const sonde = (el, einheit) => { const i = document.createElement('i'); i.style.cssText = `position:absolute;visibility:hidden;height:0;width:10${einheit}`; el.append(i); return i; };
const satzH1 = $('.satz'), notizErste = handErste.closest('.notiz');
const S = {
xg: sonde(satzH1, 'ex'), xs: sonde(kursivErste, 'ex'),
cg: sonde(notizErste, kannCap ? 'cap' : 'ex'), cs: sonde(handErste, kannCap ? 'cap' : 'ex')
};
const hoehe = k => S[k].getBoundingClientRect().width / 10;
const px = el => parseFloat(getComputedStyle(el).fontSize) || 16;
const R = { xg: .5, xs: .5, cg: .7, cs: .7 };
let gemessen = false;
function messen() {
/* Verhältnisse der Grotesk (ex/Schriftgröße) werden zum Wert für font-size-adjust */
const a = $('[data-ansicht="satz"]'), b = $('[data-ansicht="initial"]'), ha = a.hidden, hb = b.hidden;
a.hidden = false; b.hidden = false;
const alt = buehne.dataset.angl; buehne.dataset.angl = 'ohne';
$$('.kursiv, .hand').forEach(e => e.style.transition = 'none');
R.xg = hoehe('xg') / px(satzH1); R.xs = hoehe('xs') / px(kursivErste);
R.cg = hoehe('cg') / px(notizErste); R.cs = hoehe('cs') / px(handErste);
if (!kannCap) { R.cg = .7; R.cs = .66; }
buehne.dataset.angl = alt; a.hidden = ha; b.hidden = hb;
void buehne.offsetWidth;
$$('.kursiv, .hand').forEach(e => e.style.transition = '');
buehne.style.setProperty('--xh-grotesk', R.xg.toFixed(3));
buehne.style.setProperty('--cap-grotesk', R.cg.toFixed(3));
gemessen = true;
}
/* ——— Hilfslinien ——— */
function linie(top, left, width, klasse, text, seite) {
const d = document.createElement('div');
d.className = 'hl ' + (klasse || '');
d.style.cssText = `top:${top}px;left:${left}px;width:${width}px`;
if (text) { const b = document.createElement('b'); b.className = seite || 'r'; b.textContent = text; d.append(b); }
ebene.append(d); return d;
}
function deltaMarke(top, left, wert) {
const d = document.createElement('span');
const gut = Math.abs(wert) < .35;
d.className = 'delta' + (gut ? ' gut' : '');
d.textContent = (gut ? 'Δ 0 px' : 'Δ ' + (wert > 0 ? '+' : '−') + Math.abs(wert).toFixed(1).replace('.', ',') + ' px');
d.style.cssText = `top:${top}px;left:${left}px`;
ebene.append(d);
}
function zeichnen() {
ebene.innerHTML = '';
if (!linien) return;
const e = ebene.getBoundingClientRect(), bb = buehne.getBoundingClientRect();
const b = { top: e.top, left: e.left, width: bb.width }; /* Bezug: Ebene (ragt 44 px über die Bühne) */
if (variante === 'satz' || variante === 'initial') {
const istSatz = variante === 'satz';
const el = istSatz ? kursivErste : handErste;
const block = istSatz ? satzH1 : notizErste;
const m = istSatz ? mSatz : mHand;
const grund = m.getBoundingClientRect().top - b.top;
const hg = hoehe(istSatz ? 'xg' : 'cg'), hs = hoehe(istSatz ? 'xs' : 'cs');
const br = block.getBoundingClientRect(), er = el.getBoundingClientRect();
const links = br.left - b.left;
const breit = b.width - (br.right - b.left) > 214; /* Platz für die Legende rechts? */
const breite = istSatz && breit ? b.width - links - 212 : Math.min(br.width, b.width - links);
const was = istSatz ? 'x-Höhe' : 'Versalhöhe';
linie(grund, links, breite, '');
linie(grund - hg, links, breite, 'grau');
linie(grund - hs, links, breite, 'blau');
if (breit) deltaMarke(grund + 8, er.left - b.left, hs - hg);
const ab = $('[data-abweichung]'); if (ab) ab.textContent = `${(hs - hg).toFixed(1)} px · ${istSatz ? 'x-Höhe' : 'Versalhöhe'} Grotesk ${hg.toFixed(1)} px, Serif ${hs.toFixed(1)} px`;
if (breit) {
const l = document.createElement('div');
l.className = 'legende';
l.style.cssText = `top:${Math.max(0, grund - hg / 2 - 22)}px;left:${b.width - 196}px`;
l.innerHTML = `<span class="blau">${was} Newsreader${istSatz ? ' Italic' : ''}</span><span class="grau">${was} Host Grotesk</span><span>Grundlinie</span>`;
ebene.append(l);
} else {
const l = document.createElement('div');
const d = hs - hg, gut = Math.abs(d) < .35;
l.className = 'legende zeile';
l.innerHTML = `<span class="blau">${was} Serif</span><span class="grau">Grotesk</span><span class="wert">Δ ${gut ? '0' : (d > 0 ? '+' : '−') + Math.abs(d).toFixed(1).replace('.', ',')} px</span>`;
ebene.append(l);
}
} else {
const namen = ['Newsreader 400', 'Host Grotesk 500', 'Newsreader Italic 400'];
const st = $('.stapel').getBoundingClientRect();
zeilenMarken.forEach((m, i) => {
const grund = m.getBoundingClientRect().top - b.top;
linie(grund, st.left - b.left, b.width - (st.left - b.left), i === 1 ? '' : 'blau', namen[i], 'r');
});
}
}
let zeichnenRaf = 0;
function nachfuehren(ms = 320) { /* während der Größen-Transition mitzeichnen */
cancelAnimationFrame(zeichnenRaf);
const ende = performance.now() + ms;
const schritt = () => { zeichnen(); if (performance.now() < ende) zeichnenRaf = requestAnimationFrame(schritt); };
schritt();
}
/* ——— Werte ——— */
function werte() {
const z = [];
const rx = v => v.toFixed(3).replace(/^0/, '');
if (variante === 'satz' || variante === 'initial') {
const istSatz = variante === 'satz';
z.push(['Grundschnitt', istSatz ? 'Host Grotesk 500, letter-spacing −.02em' : 'Host Grotesk 500, letter-spacing −.025em']);
z.push([istSatz ? 'Kursive (1–2 Wörter)' : 'Initiale', istSatz ? 'Newsreader Italic 400, opsz auto' : 'Newsreader 400 versal, opsz auto']);
z.push(['Angleichung', angl === 'ohne' ? 'font-size: 1em; letter-spacing geerbt'
: angl === 'rezept' ? (istSatz ? 'font-size: 1.05em; letter-spacing: 0' : 'font-size: 1.05em')
: (istSatz ? `font-size-adjust: ex-height ${rx(R.xg)}` : `font-size-adjust: cap-height ${rx(R.cg)}`)]);
z.push([istSatz ? 'x-Höhe gemessen' : 'Versalhöhe gemessen', istSatz ? `Host Grotesk ${rx(R.xg)} · Newsreader Italic ${rx(R.xs)}` : `Host Grotesk ${rx(R.cg)} · Newsreader ${rx(R.cs)}`]);
if (!istSatz) z.push(['Laufweite je Buchstabe', 'F −.08em · W −.03em · L −.02em (margin-right)']);
} else {
z.push(['Zeilen', 'Newsreader 400 / Host Grotesk 500 / Newsreader Italic 400']);
z.push(['Größe', 'clamp(3.5rem, 1rem + 9vw, 9rem)']);
z.push(['Laufweite, Zeilenhöhe', 'letter-spacing −.05em · line-height .83']);
}
z.push(['Einblenden', 'opacity + translateY .3em, 480 ms, Staffel 80 ms je Zeile']);
z.push(['Kurve', 'cubic-bezier(0.165, 0.84, 0.44, 1) · Größe 240 ms · UI 120 ms']);
if (variante !== 'stapel') z.unshift(['Abweichung (live)', '<span data-abweichung></span>']);
werteListe.innerHTML = z.map(([k, v]) => `<div><dt>${k}</dt><dd>${v}</dd></div>`).join('');
$('[data-zustand]').textContent = NAMEN[variante];
$('[data-zustand-angl]').textContent = variante === 'stapel' ? '−.05em · .83' : ANGL[angl];
}
/* ——— Zustände ——— */
function einblenden(ansicht) {
ansicht.classList.remove('ein');
if (reduziert()) return;
void ansicht.offsetWidth;
ansicht.classList.add('ein');
}
function setzeVariante(v, still) {
variante = v;
$$('[data-variante]').forEach(k => k.setAttribute('aria-pressed', String(k.dataset.variante === v)));
$$('[data-ansicht]').forEach(a => { a.hidden = a.dataset.ansicht !== v; });
const a = $(`[data-ansicht="${v}"]`);
$$('[data-staffeln]', a).forEach(zeilenNummern);
anglGruppe.setAttribute('aria-disabled', String(v === 'stapel'));
if (!still) einblenden(a);
werte(); nachfuehren();
}
function setzeAngl(w) {
angl = w;
buehne.dataset.angl = w;
$$('[data-angl]').forEach(k => { if (k.tagName === 'BUTTON') k.setAttribute('aria-pressed', String(k.dataset.angl === w)); });
werte(); nachfuehren();
}
function setzeLinien(an) {
linien = an;
linienKnopf.setAttribute('aria-pressed', String(an));
zeichnen();
}
$$('[data-variante]').forEach(k => k.addEventListener('click', () => { pause(); setzeVariante(k.dataset.variante); }));
$$('button[data-angl]').forEach(k => k.addEventListener('click', () => { pause(); setzeAngl(k.dataset.angl); }));
linienKnopf.addEventListener('click', () => { pause(); setzeLinien(!linien); });
$$('a[href^="#"]').forEach(a => a.addEventListener('click', e => e.preventDefault()));
addEventListener('resize', () => { $$('[data-staffeln]').forEach(zeilenNummern); zeichnen(); });
/* ——— Vorführung ——— */
const ABLAUF = [
[0, () => { setzeAngl('ohne'); setzeVariante('satz'); setzeLinien(true); }],
[2000, () => setzeAngl('rezept')],
[3800, () => setzeAngl('exakt')],
[5800, () => setzeVariante('stapel')],
[9000, () => { setzeAngl('ohne'); setzeVariante('initial'); }],
[11000, () => setzeAngl('exakt')],
];
const RUNDE = 13200;
let uhren = [];
function pause() { uhren.forEach(clearTimeout); uhren = []; }
function neustart() {
pause();
setzeAngl('rezept'); setzeLinien(true); setzeVariante('satz');
}
function vorfuehren() {
pause();
ABLAUF.forEach(([t, f]) => uhren.push(setTimeout(f, t)));
uhren.push(setTimeout(vorfuehren, RUNDE));
}
function weiter() { vorfuehren(); }
const start = () => { messen(); werte(); setzeVariante(variante, true); einblenden($(`[data-ansicht="${variante}"]`)); nachfuehren(600); };
(document.fonts && document.fonts.ready ? document.fonts.ready : Promise.resolve()).then(() => {
Promise.all(['500 20px "Host Grotesk"', 'italic 400 20px "Newsreader"', '400 20px "Newsreader"'].map(f => document.fonts.load(f).catch(() => {}))).then(start);
});
messen(); werte();
})();
</script>
</body>
</html>