Optische Größen nutzen (opsz-Achse)
Neuheitenseite einer Hanauer Uhrenmanufaktur in einer einzigen Bodoni Moda: Titel mit Haarstrichen, Gravur auf dem Zifferblatt robust, gesteuert über die opsz-Achse.
Variable Schriften mit opsz-Achse haben für jede Größe einen eigenen Zuschnitt. Groß werden Haarstriche feiner und Abstände enger, klein werden Striche kräftiger, Punzen offener und die Laufweite weiter. Mit font-optical-sizing: auto wählt der Browser die passende Stufe selbst.
Eine einzige Schrift sieht in jeder Größe richtig aus: der Titel elegant und kontrastreich, der Fließtext robust und lesbar. So kann eine kontrastreiche Serif wie Bodoni auch klein eingesetzt werden, ohne dass Haarstriche wegbrechen.
Wann einsetzen
Editorial, Luxus, Kultur und Marken mit einer Serif, die vom großen Titel bis zur Fußnote reicht. Besonders bei Didonen und feinen Serifen wie Bodoni Moda, Fraunces, Newsreader oder Literata.
Wann nicht
Bei Schriften ohne opsz-Achse bringt es nichts. Bei Groteskschriften mit geringem Strichkontrast ist der Unterschied kaum sichtbar. Wer jeden Kilobyte zählt, sollte prüfen, ob die größere Variable-Datei sich lohnt.
Varianten
- Automatisch: font-optical-sizing: auto (Clagnut, Linear)
- Feste Stufen je Größe der Skala, etwa Display 'opsz' 96 und Text 'opsz' 12 (Undercase)
- Zusatzachsen wie SOFT, WONK oder GRAD als Markensignatur (Undercase mit Fraunces)
- Alle Achsen gebündelt in einer Custom Property (Linear: --font-variations)
Umsetzung
Variable Schrift mit opsz-Achse laden. Für die Automatik genügt font-optical-sizing: auto, das ist in den meisten Browsern schon Standard. Für feste Werte font-variation-settings: 'opsz' 96 nutzen. Weil font-variation-settings nicht kaskadiert und jede Regel alle Achsen ersetzt, alle Achsen gemeinsam in einer Custom Property führen (z. B. --font-variations: 'opsz' 96, 'wght' 400).
Robust umsetzen
Telefon. Mit der Automatik passt sich opsz von selbst an die kleineren Größen an. Feste Stufen gelten unabhängig von der Bildschirmbreite und müssen bei stark schrumpfenden Titeln eventuell angepasst werden.
Ohne Bewegung. Nicht relevant, die Technik hat keine Bewegung.
Barrierefrei. Optische Größen verbessern die Lesbarkeit kleiner Texte. Trotzdem Kontrast prüfen und Fließtext nicht unter 16 px setzen. Bei Vergrößerung über transform (Lupe, Zoom-Effekte) bleibt die kleine Stufe stehen.
Code
<!DOCTYPE html>
<!--
Optische Größen nutzen (opsz-Achse)
Fundus · Typo-Techniken · https://cecodesign.de/lab/fundus/typo-techniken/optische-groessen-opsz/
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>Optische Größen nutzen (opsz-Achse) · Fundus-Demo</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<style>
@font-face { font-family: 'Bodoni Moda'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/bodoni-moda:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 400 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Bodoni Moda'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/bodoni-moda:vf@latest/latin-opsz-italic.woff2') format('woff2-variations'); font-weight: 400 900; font-style: italic; font-display: swap; }
@font-face { font-family: 'Instrument Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-sans:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 400 700; font-display: swap; }
@font-face { font-family: 'Geist Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/geist-mono:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
:root {
/* Rezept Manufaktur-Stille: Anthrazit statt Schwarz, Weiß, Hellgrau nur als Produktfläche, Messing nur als Linie */
--anthrazit: #23282f; --weiss: #ffffff; --flaeche: #f4f4f2; --meta: #676c73; --linie: #d9dadb; --messing: #a88a5a; --messing-text: #84683c; /* Messing als Schrift auf Weiß und Fläche */
--hell-leise: rgba(255, 255, 255, .62); --hell-linie: rgba(255, 255, 255, .14);
--kurve: cubic-bezier(0.1, 0.6, 0.4, 1); /* NOMOS --trans-func-default */
--ui: 150ms var(--kurve);
--serif: 'Bodoni Moda', 'Didot', Georgia, serif;
--sans: 'Instrument Sans', system-ui, sans-serif;
}
* { box-sizing: border-box; margin: 0; }
html { background: var(--weiss); color: var(--anthrazit); font-family: var(--sans); -webkit-font-smoothing: antialiased; }
body { min-height: 100svh; overflow-x: hidden; }
button { font: inherit; color: inherit; }
.mono { font: 500 11px/1.3 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; }
.versal { font: 500 .75rem/1.2 var(--sans); letter-spacing: .08em; text-transform: uppercase; }
/* ---------- Der Effekt: eine Bodoni Moda für alle Größen, die opsz-Achse folgt der Größe ---------- */
.opsz { font-family: var(--serif); font-weight: 400; }
/* Variante 1 (Standard): Automatik. Der Browser setzt opsz = Schriftgröße in px. */
[data-modus='auto'] .opsz { font-optical-sizing: auto; }
/* Variante 2: feste Stufen. Alle Achsen gebündelt in einer Custom Property,
weil font-variation-settings nicht kaskadiert (eine spätere Regel ersetzt alle Achsen). */
.opsz[data-stufe='display'] { --font-variations: 'opsz' 96, 'wght' 400; }
.opsz[data-stufe='marke'] { --font-variations: 'opsz' 48, 'wght' 500; }
.opsz[data-stufe='lead'] { --font-variations: 'opsz' 32, 'wght' 400; }
.opsz[data-stufe='ziffer'] { --font-variations: 'opsz' 24, 'wght' 500; }
.opsz[data-stufe='text'] { --font-variations: 'opsz' 12, 'wght' 400; }
.opsz[data-stufe='mini'] { --font-variations: 'opsz' 6, 'wght' 500; }
[data-modus='stufen'] .opsz { font-variation-settings: var(--font-variations); }
/* Gegenproben: so sieht es ohne optische Größen aus */
[data-modus='text'] .opsz { font-optical-sizing: none; } /* Standardwert der Datei: opsz 11 */
[data-modus='display'] .opsz { font-variation-settings: 'opsz' 96; } /* Display-Schnitt überall */
/* ---------- Fundus-Leiste (außerhalb der Seite) ---------- */
.leiste { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 24px; padding: 12px clamp(16px, 3vw, 40px); border-bottom: 1px solid var(--linie); color: var(--meta); }
.gruppen { display: flex; flex-wrap: wrap; gap: 8px 20px; align-items: center; }
.gruppe { display: flex; align-items: center; gap: 4px; }
.gruppe > span { margin-right: 6px; }
.gruppe button { letter-spacing: inherit; text-transform: inherit; font: inherit; color: var(--meta); background: none; border: 1px solid var(--linie); border-radius: 999px; height: 28px; padding: 0 11px; cursor: pointer; transition: color var(--ui), background var(--ui), border-color var(--ui); }
.gruppe button:hover { color: var(--anthrazit); border-color: var(--meta); }
.gruppe button[aria-pressed='true'] { background: var(--anthrazit); color: var(--weiss); border-color: var(--anthrazit); }
.gruppe.gegen button[aria-pressed='true'] { background: none; color: var(--anthrazit); border-color: var(--messing); box-shadow: inset 0 0 0 1px var(--messing); }
.gruppe button:focus-visible { outline: 2px solid var(--messing); outline-offset: 2px; }
.code { font-family: 'Geist Mono', ui-monospace, monospace; font-size: 11px; letter-spacing: 0; color: var(--anthrazit); text-transform: none; }
/* ---------- Seite: Kopf ---------- */
.kopf { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 18px clamp(16px, 3vw, 40px); }
.marke { font-size: 21px; letter-spacing: .2em; line-height: 1; text-transform: uppercase; color: var(--anthrazit); text-decoration: none; }
.nav { display: flex; gap: clamp(16px, 2.4vw, 34px); list-style: none; padding: 0; }
.nav a, .termin { color: inherit; text-decoration: none; }
.termin { border: 1px solid var(--anthrazit); padding: 11px 16px; }
.nav a:hover, .termin:hover { color: var(--messing); }
/* ---------- Seite: Neuheit (Anthrazit | Produktfläche) ---------- */
.neuheit { display: grid; grid-template-columns: 1.12fr 1fr; background: var(--anthrazit); color: var(--weiss); min-height: min(640px, calc(100svh - 120px)); }
.neuheit-text { padding: clamp(28px, 4.4vw, 64px) clamp(16px, 3vw, 40px); display: flex; flex-direction: column; justify-content: center; gap: clamp(14px, 1.8vw, 22px); }
.label-zeile { color: var(--hell-leise); display: flex; gap: 14px; align-items: center; }
.label-zeile::after { content: ''; width: 40px; height: 1px; background: var(--messing); }
h1 { font-size: clamp(3rem, 8.2vw, 7.25rem); line-height: 1.0; letter-spacing: -.01em; }
.lead { font-style: italic; font-size: clamp(1.25rem, 2vw, 1.625rem); line-height: 1.3; max-width: 22em; color: rgba(255, 255, 255, .9); }
.text { font-size: 1rem; line-height: 1.5; max-width: 34em; color: var(--hell-leise); }
.mini { font-size: 11px; line-height: 1.5; letter-spacing: .02em; color: var(--hell-leise); font-variant-numeric: tabular-nums lining-nums; }
.aktionen { display: flex; flex-wrap: wrap; gap: 12px 24px; align-items: center; margin-top: 6px; }
.knopf { color: var(--anthrazit); background: var(--weiss); padding: 14px 20px; text-decoration: none; transition: background var(--ui); }
.knopf:hover { background: var(--flaeche); }
.leise-link { color: var(--weiss); text-decoration: none; border-bottom: 1px solid var(--messing); padding-bottom: 3px; }
/* Messmarken: verwendete Werte dezent am Element */
.mass { display: flex; align-items: center; gap: 8px; font: 500 10px/1 'Geist Mono', ui-monospace, monospace; letter-spacing: .08em; color: var(--messing); font-variant-numeric: tabular-nums; margin-bottom: -6px; transition: color var(--ui); }
.mass::before { content: ''; width: 14px; height: 1px; background: currentColor; }
.weiss-teil .mass, .produkt .mass { color: var(--messing-text); }
.mass.neu { color: #d8bd8c; }
.weiss-teil .mass.neu { color: #6f5a35; }
/* ---------- Produktfläche mit Uhr (CSS + SVG, keine Fotos) ---------- */
.produkt { position: relative; background: var(--flaeche); overflow: hidden; display: grid; place-items: center; min-height: 420px; color: var(--anthrazit); --d: clamp(196px, 23vw, 318px); }
.uhr { position: relative; width: calc(var(--d) * 1.13); aspect-ratio: 1; }
.band { position: absolute; left: 50%; width: calc(var(--d) * .6); height: 120vh; translate: -50% 0; background: linear-gradient(90deg, #1b1f25, #2d333b 18%, #23282f 50%, #2d333b 82%, #1b1f25); }
.band.oben { bottom: 72%; clip-path: polygon(6% 0, 94% 0, 100% 100%, 0 100%); }
.band.unten { top: 72%; clip-path: polygon(0 0, 100% 0, 94% 100%, 6% 100%); }
.band::after { content: ''; position: absolute; inset: 0 7%; border-inline: 1px dashed rgba(255, 255, 255, .12); }
.gehaeuse { position: absolute; inset: 0; border-radius: 50%; background: radial-gradient(circle at 34% 28%, #ffffff 0%, #e9eaeb 38%, #c9cbce 72%, #eef0f1 100%); box-shadow: 0 30px 50px -24px rgba(35, 40, 47, .55), 0 2px 4px rgba(35, 40, 47, .18), inset 0 0 0 1px rgba(35, 40, 47, .12); }
.krone { position: absolute; right: -4.5%; top: 50%; width: 6%; height: 11%; translate: 0 -50%; border-radius: 2px 3px 3px 2px; background: repeating-linear-gradient(0deg, #c9cbce 0 1px, #f2f3f4 1px 3px); box-shadow: 0 1px 2px rgba(35, 40, 47, .3); }
.zifferblatt { position: absolute; left: 50%; top: 50%; width: var(--d); aspect-ratio: 1; translate: -50% -50%; border-radius: 50%; container-type: inline-size; background: radial-gradient(circle at 50% 40%, #fdfcf9, #efede7 70%, #e6e3dc); box-shadow: inset 0 0 0 1px rgba(35, 40, 47, .14), inset 0 2px 6px rgba(35, 40, 47, .12); }
.zifferblatt svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.ziffer { position: absolute; translate: -50% -50%; font-size: 8cqi; line-height: 1; font-variant-numeric: lining-nums; }
.signatur { position: absolute; left: 50%; top: 28%; translate: -50% 0; text-align: center; white-space: nowrap; }
.signatur b { display: block; font-weight: inherit; font-size: 4.2cqi; letter-spacing: .22em; text-indent: .22em; }
.signatur i { display: block; font-size: 3.2cqi; margin-top: .9cqi; }
.ort { position: absolute; left: 50%; top: 82.4%; translate: -50% -50%; font-size: 2.3cqi; letter-spacing: .3em; text-indent: .3em; white-space: nowrap; }
/* Lupe: dasselbe Zifferblatt, 2,4-fach vergrößert per transform. transform ändert die
berechnete Schriftgröße nicht, deshalb zeigt die Lupe die echte kleine opsz-Stufe. */
.lupe { position: absolute; right: clamp(14px, 3vw, 36px); bottom: clamp(14px, 3vw, 36px); width: clamp(116px, 13vw, 168px); aspect-ratio: 1; border-radius: 50%; overflow: hidden; background: #efede7; box-shadow: 0 0 0 1px var(--messing), 0 0 0 5px var(--flaeche), 0 0 0 6px rgba(35, 40, 47, .12), 0 18px 30px -16px rgba(35, 40, 47, .5); }
.lupe .zifferblatt { left: 50%; top: 50%; translate: -50% -82.4%; transform: scale(2.4); transform-origin: 50% 82.4%; box-shadow: none; }
.lupe-text { position: absolute; left: clamp(14px, 1.8vw, 28px); bottom: clamp(14px, 3vw, 36px); display: grid; gap: 6px; color: var(--meta); }
.lupe-text .mass { margin: 0; }
/* ---------- Seite: Werkstatt (Weiß) ---------- */
.werkstatt { display: grid; grid-template-columns: 1.25fr 1fr .9fr; gap: clamp(28px, 4vw, 64px); padding: clamp(48px, 7vw, 104px) clamp(16px, 3vw, 40px); border-bottom: 1px solid var(--linie); }
.werkstatt .spalte { display: grid; align-content: start; gap: 16px; }
.zitat { font-size: clamp(1.75rem, 3.1vw, 2.75rem); line-height: 1.12; text-wrap: balance; }
.zitat-von { color: var(--meta); }
.journal { font-size: 1rem; line-height: 1.55; text-wrap: pretty; }
.journal + .journal { margin-top: -4px; }
.daten { width: 100%; border-collapse: collapse; font-size: .875rem; font-variant-numeric: tabular-nums; }
.daten th, .daten td { padding: 10px 0; border-top: 1px solid var(--linie); text-align: left; font-weight: 400; vertical-align: baseline; }
.daten th { color: var(--meta); }
.daten td { text-align: right; }
.fussnote { font-size: 11px; line-height: 1.5; color: var(--meta); font-variant-numeric: tabular-nums lining-nums; }
.fuss { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 12px; padding: 22px clamp(16px, 3vw, 40px); color: var(--meta); }
/* ---------- Vorschau: nur Leiste, Kopf und Neuheit ---------- */
.vorschau body { overflow: hidden; }
.vorschau .werkstatt, .vorschau .fuss, .vorschau .nav, .vorschau .aktionen, .vorschau .label-zeile, .vorschau .lupe-text { display: none; }
.vorschau .neuheit { min-height: calc(100svh - 128px); }
/* ---------- Schmal ---------- */
@media (max-width: 1040px) { .nav { display: none; } }
@media (max-width: 860px) {
.werkstatt { grid-template-columns: 1fr 1fr; }
.werkstatt .spalte:first-child { grid-column: 1 / -1; }
}
@media (max-width: 720px) {
.neuheit { grid-template-columns: 1fr; min-height: 0; }
.produkt { min-height: 440px; --d: 214px; }
.werkstatt { grid-template-columns: 1fr; }
.leiste .code { display: none; }
.termin { padding: 10px 12px; }
.marke { font-size: 17px; }
}
@media (hover: none) {
.gruppe button { height: 36px; padding: 0 13px; }
}
:root.reduziert * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
/* 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">
<div class="gruppen">
<div class="gruppe" role="group" aria-label="Variante">
<span>Variante</span>
<button type="button" data-modus-knopf="auto" aria-pressed="true">Automatisch</button>
<button type="button" data-modus-knopf="stufen" aria-pressed="false">Feste Stufen</button>
</div>
<div class="gruppe gegen" role="group" aria-label="Gegenprobe">
<span>Gegenprobe</span>
<button type="button" data-modus-knopf="text" aria-pressed="false">opsz 11 überall</button>
<button type="button" data-modus-knopf="display" aria-pressed="false">opsz 96 überall</button>
</div>
</div>
<p class="code" data-code aria-live="polite">font-optical-sizing: auto;</p>
</div>
<div class="seite" data-buehne data-modus="auto">
<header class="kopf">
<a class="marke opsz" data-stufe="marke" data-w="500" href="#">Wendemuth</a>
<ul class="nav versal">
<li><a href="#">Zeitmesser</a></li>
<li><a href="#">Manufaktur</a></li>
<li><a href="#">Journal</a></li>
<li><a href="#">Boutique Hanau</a></li>
</ul>
<a class="termin versal" href="#">Termin vereinbaren</a>
</header>
<section class="neuheit" aria-labelledby="titel">
<div class="neuheit-text">
<p class="label-zeile versal">Neuheit 2026 · Ref. WB 38.2</p>
<p class="mass" data-mass="titel" data-name="Titel" aria-hidden="true"></p>
<h1 class="opsz" id="titel" data-stufe="display">Belvedere<br>Mondphase</h1>
<p class="mass" data-mass="lead" data-name="Vorspann" aria-hidden="true"></p>
<p class="lead opsz" id="lead" data-stufe="lead">Ein Mond, der erst nach 122 Jahren um einen Tag nachgestellt werden will.</p>
<p class="mass" data-mass="text" data-name="Text" aria-hidden="true"></p>
<p class="text opsz" id="text" data-stufe="text">Gefertigt in unserer Werkstatt am Hanauer Mainufer: Handaufzugskaliber W 11.3 mit 72 Stunden Gangreserve, von Hand gravierter Unruhbrücke und einem Mond aus massivem Silber.</p>
<p class="mass" data-mass="mini" data-name="Angaben" aria-hidden="true"></p>
<p class="mini opsz" id="mini" data-stufe="mini">Gehäuse 38,5 mm Weißgold · Saphirglas beidseitig · 50 Exemplare</p>
<div class="aktionen">
<a class="knopf versal" href="#">Termin in der Boutique</a>
<a class="leise-link versal" href="#">Das Kaliber</a>
</div>
</div>
<figure class="produkt" aria-label="Belvedere Mondphase, Weißgold, Zifferblatt Silber gekörnt">
<div class="uhr" aria-hidden="true">
<div class="band oben"></div>
<div class="band unten"></div>
<div class="gehaeuse"></div>
<div class="krone"></div>
<div class="zifferblatt" data-zifferblatt>
<svg viewBox="0 0 100 100" data-skala>
<defs><clipPath id="fenster"><path d="M37.5 71.5 A12.5 12.5 0 0 1 62.5 71.5 Z"/></clipPath></defs>
<g data-striche></g>
<g clip-path="url(#fenster)">
<rect x="36" y="58" width="28" height="14" fill="#23282f"/>
<circle cx="46.6" cy="66.4" r="4.1" fill="#e9eaeb"/>
<circle cx="46.6" cy="66.4" r="4.1" fill="none" stroke="#c9cbce" stroke-width=".3"/>
<circle cx="55.6" cy="62.4" r=".35" fill="#d9dadb"/><circle cx="58.4" cy="67.2" r=".25" fill="#d9dadb"/><circle cx="41.2" cy="62.2" r=".25" fill="#d9dadb"/><circle cx="53" cy="69.4" r=".2" fill="#d9dadb"/>
</g>
<path d="M37.5 71.5 A12.5 12.5 0 0 1 62.5 71.5 Z" fill="none" stroke="#a88a5a" stroke-width=".35"/>
</svg>
<p class="signatur opsz" data-stufe="marke" data-w="500"><b>Wendemuth</b><i class="opsz" data-stufe="mini">Hanau</i></p>
<p class="ort opsz" data-stufe="mini" id="ort">Mondphase</p>
<svg viewBox="0 0 100 100" data-zeiger></svg>
</div>
</div>
<div class="lupe" aria-hidden="true" data-lupe></div>
<figcaption class="lupe-text">
<span class="mass" data-mass="ziffer" data-name="Ziffern" aria-hidden="true"></span>
<span class="mass" data-mass="ort" data-name="Lupe 2,4×" aria-hidden="true"></span>
</figcaption>
</figure>
</section>
<section class="werkstatt weiss-teil" aria-label="Aus der Werkstatt">
<div class="spalte">
<p class="versal" style="color: var(--meta)">Aus der Werkstatt</p>
<p class="mass" data-mass="zitat" data-name="Zitat" aria-hidden="true"></p>
<blockquote class="zitat opsz" id="zitat" data-stufe="lead">„Ein Zifferblatt liest man auf Armeslänge, eine Gravur unter der Lupe. Beides muss stimmen.“</blockquote>
<p class="zitat-von versal">Konrad Wendemuth, Uhrmachermeister</p>
</div>
<div class="spalte">
<p class="mass" data-mass="journal" data-name="Lesetext" aria-hidden="true"></p>
<p class="journal opsz" id="journal" data-stufe="text">Der Mond der Belvedere ist ein Scheibenrad mit 135 Zähnen. Es läuft so fein, dass es seinen Rückstand erst nach 122 Jahren auf einen ganzen Tag summiert.</p>
<p class="journal opsz" data-stufe="text">Jede Scheibe wird bei uns aus Silber gedreht, von Hand mattiert und unter der Lupe gegen den Himmel aus Anthrazit gesetzt. Für eine Uhr brauchen wir drei Wochen.</p>
</div>
<div class="spalte">
<table class="daten">
<tr><th scope="row">Kaliber</th><td>W 11.3, Handaufzug</td></tr>
<tr><th scope="row">Gangreserve</th><td>72 h</td></tr>
<tr><th scope="row">Frequenz</th><td>21 600 A/h</td></tr>
<tr><th scope="row">Durchmesser</th><td>38,5 mm</td></tr>
<tr><th scope="row">Höhe</th><td>9,8 mm</td></tr>
</table>
<p class="fussnote opsz" data-stufe="mini">Abweichung der Mondphase: ein Tag in 122 Jahren. Abgebildet ist die Ausführung in Weißgold mit Zifferblatt Silber gekörnt.</p>
</div>
</section>
<footer class="fuss mono">
<span>Wendemuth Uhren · Hanau</span>
<span>Eine Schrift, sechs optische Größen</span>
</footer>
</div>
<script>
(() => {
const html = document.documentElement;
const reduziert = () => html.classList.contains('reduziert');
const buehne = document.querySelector('[data-buehne]');
const knoepfe = [...document.querySelectorAll('[data-modus-knopf]')];
const code = document.querySelector('[data-code]');
const KURVE = 'cubic-bezier(0.1, 0.6, 0.4, 1)';
const DAUER = 500; // Rezept: Bildwechsel 400–500 ms
const TAKT = 2600; // Vorführung: Standzeit je Zustand
// Feste Stufen, identisch mit den --font-variations im CSS
const STUFEN = { display: [96, 400], marke: [48, 500], lead: [32, 400], ziffer: [24, 500], text: [12, 400], mini: [6, 500] };
const CODE = {
auto: 'font-optical-sizing: auto;',
stufen: 'font-variation-settings: var(--font-variations);',
text: 'font-optical-sizing: none; /* opsz 11 */',
display: "font-variation-settings: 'opsz' 96;"
};
// Zifferblatt: Minutenkreis und Zeiger (10:09), Ziffern als echtes HTML, damit opsz der sichtbaren Größe folgt
const blatt = document.querySelector('[data-zifferblatt]');
const striche = blatt.querySelector('[data-striche]');
let s = '';
for (let i = 0; i < 60; i++) {
const a = i * 6 * Math.PI / 180, fuenf = i % 5 === 0;
const r1 = fuenf ? 44.2 : 45.4, r2 = 47.2;
s += `<line x1="${50 + r1 * Math.sin(a)}" y1="${50 - r1 * Math.cos(a)}" x2="${50 + r2 * Math.sin(a)}" y2="${50 - r2 * Math.cos(a)}" stroke="#23282f" stroke-width="${fuenf ? .55 : .25}"/>`;
}
striche.innerHTML = s + '<circle cx="50" cy="50" r="47.6" fill="none" stroke="#23282f" stroke-width=".18"/><circle cx="50" cy="50" r="43.6" fill="none" stroke="#23282f" stroke-width=".12"/>';
for (let h = 1; h <= 12; h++) {
if (h === 6) continue; // dort sitzt der Mond
const a = h * 30 * Math.PI / 180, r = 36.6;
const z = document.createElement('span');
z.className = 'ziffer opsz'; z.dataset.stufe = 'ziffer';
if (h === 12) z.id = 'ziffer';
z.textContent = h;
z.style.left = (50 + r * Math.sin(a)) + '%';
z.style.top = (50 - r * Math.cos(a)) + '%';
blatt.insertBefore(z, blatt.querySelector('[data-zeiger]'));
}
blatt.querySelector('[data-zeiger]').innerHTML =
'<g fill="#23282f">' +
'<path transform="rotate(304.5 50 50)" d="M50 53.4 C52 47 52.2 37 50 25.5 C47.8 37 48 47 50 53.4Z"/>' +
'<path transform="rotate(54 50 50)" d="M50 54 C51.5 45 51.4 30 50 12.5 C48.6 30 48.5 45 50 54Z"/>' +
'<circle cx="50" cy="50" r="1.9"/></g><circle cx="50" cy="50" r=".7" fill="#a88a5a"/>';
// Lupe: Kopie des Zifferblatts
document.querySelector('[data-lupe]').appendChild(blatt.cloneNode(true)).removeAttribute('id');
document.querySelectorAll('[data-lupe] [id]').forEach(el => el.removeAttribute('id'));
const texte = () => [...buehne.querySelectorAll('.opsz')];
let modus = 'auto';
function px(el) { return parseFloat(getComputedStyle(el).fontSize); }
function grundgewicht(el) { return Number(el.closest('[data-w]')?.dataset.w || el.dataset.w || 400); }
// Welche Achsenwerte gelten für ein Element in einem Modus?
function zustand(el, m) {
const st = STUFEN[el.dataset.stufe] || [11, 400];
if (m === 'stufen') return { o: st[0], w: st[1] };
const w = grundgewicht(el);
if (m === 'text') return { o: 11, w };
if (m === 'display') return { o: 96, w };
return { o: Math.round(Math.min(96, Math.max(6, px(el))) * 10) / 10, w }; // Automatik: opsz = px
}
const fvs = z => `'opsz' ${z.o}, 'wght' ${z.w}`;
function werte(blitz) {
document.querySelectorAll('[data-mass]').forEach(m => {
const el = document.getElementById(m.dataset.mass);
if (!el) return;
const z = zustand(el, modus), g = Math.round(px(el) * 10) / 10;
m.textContent = `${m.dataset.name} · ${String(g).replace('.', ',')} px → opsz ${String(Math.round(z.o)).replace('.', ',')}${modus === 'stufen' && z.w !== 400 ? ' · wght ' + z.w : ''}`;
if (blitz && !reduziert()) { m.classList.add('neu'); setTimeout(() => m.classList.remove('neu'), DAUER); }
});
}
function setzen(neu, bewegt = true) {
const els = texte();
const vorher = els.map(el => zustand(el, modus));
modus = neu;
buehne.dataset.modus = neu;
knoepfe.forEach(k => k.setAttribute('aria-pressed', String(k.dataset.modusKnopf === neu)));
code.textContent = CODE[neu];
// Übergang: font-variation-settings wird zwischen den Achsenwerten interpoliert, danach gilt wieder das CSS
if (bewegt && !reduziert()) els.forEach((el, i) => {
const a = vorher[i], b = zustand(el, neu);
if (a.o === b.o && a.w === b.w) return;
el.getAnimations().forEach(x => x.cancel());
el.animate([{ fontVariationSettings: fvs(a) }, { fontVariationSettings: fvs(b) }], { duration: DAUER, easing: KURVE });
});
werte(bewegt);
}
knoepfe.forEach(k => k.addEventListener('click', () => { pause(); setzen(k.dataset.modusKnopf); }));
addEventListener('resize', () => werte(false));
document.fonts && document.fonts.ready.then(() => werte(false));
werte(false);
// Vorführung: Automatik, Gegenprobe Display, Automatik, Gegenprobe Text, feste Stufen
const FOLGE = ['display', 'auto', 'text', 'auto', 'stufen', 'auto'];
let uhr = null, schritt = 0;
function takt() {
setzen(FOLGE[schritt % FOLGE.length]);
schritt++;
uhr = setTimeout(takt, TAKT);
}
function neustart() { pause(); schritt = 0; setzen('auto', false); }
function vorfuehren() { neustart(); html.classList.add('vorfuehrung'); uhr = setTimeout(takt, 1400); }
function pause() { clearTimeout(uhr); uhr = null; }
function weiter() { if (!uhr && html.classList.contains('vorfuehrung')) uhr = setTimeout(takt, 600); }
})();
</script>
</body>
</html>