Zoom- und textabstandfestes Layout
Eine Manufaktur-Seite mit Riesen-Wortmarke und Schrift-Hero, die erhöhte Textabstände und 320 px Breite ohne Abschneiden hält; daneben zeigt eine anfällige Fassung, was sonst bricht.
Ein Layout mit Riesen-Wortmarke und großem Schrift-Hero, das 200 % Zoom, 320 CSS-Pixel Breite und erhöhte Textabstände aushält, ohne dass Text abgeschnitten wird, sich überlappt oder waagerecht gescrollt werden muss. Die Wortmarke ist ein SVG-Logo, das sich an die Breite anpasst. Der Hero-Titel ist echter Text mit clamp(), dessen Höchstwert höchstens das 2,5-Fache des Mindestwerts beträgt. Kein Element hat eine feste Höhe für Text, nichts mit Text hat overflow: hidden, Raster brechen über Container-Abfragen um, lange deutsche Wörter trennen.
Große Schrift-Heros sind die anfälligste Stelle einer Award-Seite: vw-Schrift wächst beim Zoomen nicht mit, feste Höhen schneiden Text ab, sobald jemand Zeilen- oder Buchstabenabstand erhöht. Wer das von Anfang an einplant, behält die Wirkung und schließt niemanden aus, der vergrößert oder eigene Lesestile nutzt.
Wann einsetzen
Bei jedem Layout mit sehr großer Schrift, Wortmarken, Kacheln mit Text, Preis- und Datentabellen, festen Kopfzeilen und Karten gleicher Höhe. Besonders für Rezepte mit Schrift-Heros (Luxus, Kultur, Kreativ) und für ältere Zielgruppen.
Wann nicht
Nicht als Ausrede für kleine, vorsichtige Typografie: die Wirkung bleibt, nur die Technik ändert sich. Echte zweidimensionale Inhalte wie Datentabellen, Karten oder Diagramme dürfen laut WCAG 1.4.10 waagerecht scrollen, der Rest der Seite nicht.
Varianten
- Wortmarke als SVG mit viewBox (Logo, passt sich jeder Breite an)
- Hero-Titel mit clamp(rem, rem + vw, rem), Verhältnis Höchst- zu Mindestwert höchstens 2,5
- Technische Daten als Definitionsliste, die unter einer Container-Breite einspaltig wird
- Fester Kopf nur, wenn genug Höhe da ist (@media (min-height: …))
- Prüfschalter „Textabstände erhöhen“ als Lesezeichen-Skript oder Entwicklungs-Klasse
Umsetzung
Wortmarke als SVG-Logo mit viewBox und textLength (oder als Pfad); Logos sind von 1.4.5 ausgenommen, Titel und Fließtext bleiben echter Text. Titel mit clamp() und rem-Anteil, Verhältnis Höchst- zu Mindestwert höchstens 2,5; vw oder cqi nie allein. Container-Abfragen statt nur Medienabfragen: dann bricht dasselbe Layout in schmalen Spalten und bei 320 px gleich um, und die 320-px-Prüfung lässt sich in einem Rahmen zeigen. Keine festen Höhen und kein overflow: hidden für Text, stattdessen min-block-size und padding; white-space: nowrap höchstens für ein kurzes Wort. Technische Daten als dl, unter 30rem einspaltig. Lange Wörter mit overflow-wrap: anywhere und hyphens: auto. Fester Kopf nur bei @media (min-height: 32rem). Prüfen mit Browser-Zoom 200 % und 400 % bei 1280 px, Fensterbreite 320 px und dem Textabstand-Lesezeichen (Werte siehe werte.textabstand_pruefung).
Robust umsetzen
Telefon. Das Telefon ist der Normalfall dieses Musters: 320 px entsprechen einem kleinen Telefon oder 400 % Zoom am Laptop. Container-Abfragen sorgen dafür, dass dasselbe Layout in schmalen Spalten genauso umbricht.
Ohne Bewegung. Nicht betroffen. Umschalten der Prüfzustände ohne Übergang.
Barrierefrei. 1.4.4 Textgröße ändern (AA): Text lässt sich auf 200 % vergrößern; fluide Größen mit rem-Anteil und Verhältnis höchstens 2,5. 1.4.10 Umbruch (AA): bei 320 CSS-Pixeln kein waagerechtes Scrollen außer bei echten 2D-Inhalten. 1.4.12 Textabstand (AA): Zeilenhöhe 1,5, Absatzabstand 2, Buchstabenabstand 0,12, Wortabstand 0,16 ohne Abschneiden oder Überlappen. 1.4.5 Bilder von Text (AA): Logos und Wortmarken sind ausgenommen, Fließtext und Titel bleiben echter Text. 2.4.11 Fokus nicht verdeckt (AA): fester Kopf nur bei genug Höhe. 1.3.1 Info und Beziehungen (A): technische Daten als dl, nicht als gestylte divs.
Code
<!DOCTYPE html>
<!--
Zoom- und textabstandfestes Layout
Fundus · Zugang & Robustheit · https://cecodesign.de/lab/fundus/zugang/zoom-und-textabstand-robust/
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>Zoom- und textabstandfestes Layout · 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 und Weiß im Wechsel, Messing nur als Linie und Fokus */
--weiss: #ffffff; --flaeche: #f4f4f2; --anthrazit: #23282f; --text: #23282f;
--meta: #5f656d; /* Meta-Text: #8b9097 erreicht auf Weiß nur 3,3 : 1, daher dunkler */
--linie: #d9dadb; --messing: #a88a5a;
--nacht-text: #eef0f2; --nacht-leise: #b9bec5; --nacht-linie: rgba(238, 240, 242, .18);
--kurve: cubic-bezier(0.1, 0.6, 0.4, 1); /* NOMOS --trans-func-default */
--ui: 250ms; /* NOMOS --trans-time-sm */
--demo: #f4f4f2; --gut: #1f7a48; --schlecht: #c42b2b;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--demo); color: var(--text); font-family: 'Instrument Sans', system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
body { min-height: 100svh; }
button { font: inherit; color: inherit; }
.mono { font: 500 10.5px/1.35 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; }
/* ================= Demo-Rahmen (nicht Teil der Kundenseite) ================= */
.zustand { display: flex; flex-wrap: wrap; gap: 6px 18px; align-items: center; padding: 10px clamp(12px, 2.4vw, 28px); background: rgba(244, 244, 242, .94); border-bottom: 1px solid var(--linie); color: var(--meta); }
.zustand b { color: var(--text); font-weight: 600; }
.zustand .an b { color: var(--gut); }
.vergleich { display: flex; justify-content: center; align-items: flex-start; gap: clamp(16px, 3vw, 40px); padding: 0; }
.spalte { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.ergebnis { display: none; align-items: center; gap: 8px; padding: 0 2px; color: var(--meta); }
.ergebnis i { width: 9px; height: 9px; border-radius: 50%; background: var(--gut); flex: none; }
.ergebnis.schlecht i { background: var(--schlecht); }
.ergebnis b { color: var(--text); font-weight: 600; }
.spalte-anfaellig { display: none; }
/* Rahmen = Container: alles darin richtet sich nach seiner Breite, nicht nach dem Fenster */
.rahmen { container: seite / inline-size; background: var(--weiss); min-width: 0; }
/* 320-px-Simulation und Vergleich */
.schmal .vergleich, .vergleich-an .vergleich { padding: 18px clamp(12px, 2.4vw, 28px) 24px; }
.vergleich-an .spalte-anfaellig { display: flex; }
.vergleich-an .ergebnis, .schmal .ergebnis { display: flex; }
.schmal .spalte { flex: 0 0 336px; } /* 320 px Inhalt + 2 × 8 px Gerätekante */
.schmal .rahmen, .vergleich-an .rahmen { height: calc(100svh - 120px); overflow: auto; border: 1px solid #c9ccd0; box-shadow: 0 18px 40px rgba(35, 40, 47, .14); scrollbar-width: thin; }
.schmal .rahmen { border-radius: 22px; border: 8px solid #23282f; scrollbar-width: none; }
.markiert { outline: 2px dashed var(--schlecht) !important; outline-offset: -2px; background-image: repeating-linear-gradient(135deg, rgba(196, 43, 43, .16) 0 6px, transparent 6px 12px) !important; }
/* ================= Kundenseite: robust ================= */
.kopf { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 24px; padding: 18px clamp(16px, 4cqi, 48px); background: var(--anthrazit); color: var(--nacht-text); border-bottom: 1px solid var(--nacht-linie); }
.kopf .zeichen { font: 400 18px/1 'Bodoni Moda', serif; letter-spacing: .32em; margin-right: auto; }
.nav ul { list-style: none; display: flex; flex-wrap: wrap; gap: 4px 22px; }
.nav a, .label { font: 600 12px/1.4 'Instrument Sans', sans-serif; letter-spacing: .08em; text-transform: uppercase; }
.nav a { color: var(--nacht-text); text-decoration: none; display: inline-flex; align-items: center; min-height: 44px; transition: color var(--ui) var(--kurve); }
.nav a:hover { color: #fff; text-decoration: underline; text-decoration-color: var(--messing); text-underline-offset: 6px; }
.knopf { display: inline-flex; align-items: center; justify-content: center; gap: 12px; min-height: 48px; padding: 12px 22px; border: 1px solid currentColor; font: 600 12px/1.4 'Instrument Sans', sans-serif; letter-spacing: .08em; text-transform: uppercase; text-decoration: none; color: inherit; background: transparent; transition: background-color var(--ui) var(--kurve), color var(--ui) var(--kurve); }
.knopf.voll { background: var(--weiss); color: var(--anthrazit); border-color: var(--weiss); }
.knopf.voll:hover { background: transparent; color: var(--weiss); }
.knopf.dunkel { background: var(--anthrazit); color: var(--weiss); border-color: var(--anthrazit); }
.knopf.dunkel:hover { background: var(--weiss); color: var(--anthrazit); }
a:focus-visible, button:focus-visible { outline: 2px solid var(--messing); outline-offset: 3px; }
.held { background: var(--anthrazit); color: var(--nacht-text); padding: clamp(18px, 4cqi, 48px) clamp(16px, 4cqi, 48px) clamp(36px, 7cqi, 88px); display: grid; gap: clamp(20px, 3.4cqi, 40px); }
.wortmarke { display: block; width: 100%; height: auto; border-bottom: 1px solid var(--messing); padding-bottom: clamp(10px, 2cqi, 22px); }
.wortmarke text { font-family: 'Bodoni Moda', Georgia, serif; font-weight: 400; fill: var(--nacht-text); font-optical-sizing: auto; }
.held-unten { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: 20px clamp(24px, 5cqi, 64px); align-items: end; }
.held h1 { font: 400 clamp(2.5rem, 1.6rem + 4.5cqi, 5.5rem)/1.05 'Bodoni Moda', Georgia, serif; font-optical-sizing: auto; letter-spacing: -.01em; text-wrap: balance; overflow-wrap: anywhere; hyphens: auto; }
.held h1 em { font-style: italic; color: #e3d2b4; }
.held .label { color: var(--messing); margin-bottom: 14px; display: block; }
.held p { font-size: .9375rem; line-height: 1.47; color: var(--nacht-leise); max-width: 40ch; }
.held-aktion { display: grid; gap: 18px; justify-items: start; }
.produkt { padding: clamp(36px, 7cqi, 96px) clamp(16px, 4cqi, 48px); display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: clamp(24px, 5cqi, 72px); align-items: center; }
.uhr { background: var(--flaeche); display: grid; place-items: center; padding: clamp(18px, 4cqi, 48px); aspect-ratio: 1; }
.uhr svg { width: 100%; max-width: 420px; height: auto; }
.produkt h2, .service h2 { font: 400 clamp(2rem, 1.4rem + 2.6cqi, 3.5rem)/1.05 'Bodoni Moda', serif; font-optical-sizing: auto; letter-spacing: -.01em; overflow-wrap: anywhere; hyphens: auto; }
.produkt .label { color: var(--meta); display: block; margin-bottom: 10px; }
.produkt p { font-size: .9375rem; line-height: 1.47; color: var(--meta); max-width: 46ch; margin-top: 14px; }
.daten { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); margin: 26px 0; border-top: 1px solid var(--linie); }
.daten dt, .daten dd { padding: 11px 0; border-bottom: 1px solid var(--linie); font-size: .9375rem; line-height: 1.4; }
.daten dt { font: 600 11.5px/1.4 'Instrument Sans', sans-serif; letter-spacing: .08em; text-transform: uppercase; color: var(--meta); padding-top: 13px; padding-right: 12px; }
.daten dd { font-variant-numeric: tabular-nums lining-nums; }
.preis { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px 22px; }
.preis b { font: 400 1.75rem/1 'Bodoni Moda', serif; font-variant-numeric: tabular-nums lining-nums; }
.service { background: var(--anthrazit); color: var(--nacht-text); padding: clamp(36px, 7cqi, 96px) clamp(16px, 4cqi, 48px); display: grid; gap: clamp(20px, 4cqi, 44px); }
.karten { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; background: var(--nacht-linie); border: 1px solid var(--nacht-linie); }
.karten article { background: var(--anthrazit); padding: clamp(18px, 3cqi, 32px); display: flex; flex-direction: column; gap: 12px; min-block-size: 12rem; }
.karten h3 { font: 400 1.5rem/1.15 'Bodoni Moda', serif; overflow-wrap: anywhere; hyphens: auto; }
.karten p { font-size: .9375rem; line-height: 1.47; color: var(--nacht-leise); }
.karten .label { color: var(--messing); margin-top: auto; }
.fuss { padding: 22px clamp(16px, 4cqi, 48px); display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px 24px; font-size: .875rem; line-height: 1.5; color: var(--meta); background: var(--weiss); }
/* Umbruch nach Containerbreite: gilt im Fenster und in der 320-px-Simulation gleich */
@container seite (inline-size < 62rem) {
.karten { grid-template-columns: minmax(0, 1fr); }
.karten article { min-block-size: 0; }
}
@container seite (inline-size < 48rem) {
.held-unten, .produkt { grid-template-columns: minmax(0, 1fr); }
.uhr { aspect-ratio: auto; }
.uhr svg { max-width: 300px; }
}
@container seite (inline-size < 30rem) {
.daten { grid-template-columns: minmax(0, 1fr); }
.daten dt { border-bottom: 0; padding-bottom: 0; }
.daten dd { padding-top: 2px; }
.kopf .zeichen { flex-basis: 100%; }
}
/* ================= WCAG 1.4.12: Prüfwerte wie im Lesezeichen-Skript ================= */
.abstand .rahmen * { line-height: 1.5 !important; letter-spacing: .12em !important; word-spacing: .16em !important; }
.abstand .rahmen p { margin-bottom: 2em !important; }
/* ================= Anfällige Fassung (nur im Vergleich, als Klon) ================= */
.rahmen.anfaellig .kopf, .rahmen.anfaellig .nav ul { flex-wrap: nowrap; white-space: nowrap; }
.rahmen.anfaellig .held { overflow: hidden; }
.rahmen.anfaellig .wortmarke-text { display: block; font: 400 21cqi/1 'Bodoni Moda', serif; white-space: nowrap; color: var(--nacht-text); border-bottom: 1px solid var(--messing); padding-bottom: 10px; overflow: hidden; }
.rahmen.anfaellig .held h1 { font-size: 9cqi; line-height: 1; height: 2.1em; overflow: hidden; overflow-wrap: normal; hyphens: manual; }
.rahmen.anfaellig .held-unten, .rahmen.anfaellig .produkt { grid-template-columns: minmax(0, 1fr); }
.rahmen.anfaellig .knopf { height: 48px; padding-block: 0; white-space: nowrap; overflow: hidden; }
.rahmen.anfaellig .daten { grid-template-columns: 9rem 12rem; }
.rahmen.anfaellig .daten dd { white-space: nowrap; }
.rahmen.anfaellig .karten article { height: 13rem; min-block-size: 0; overflow: hidden; }
.rahmen.anfaellig .karten h3 { overflow-wrap: normal; hyphens: manual; }
/* ================= Pult: kompakt in der Zustandsleiste, verdeckt keinen Inhalt ================= */
.zustand { position: sticky; top: 0; z-index: 30; min-height: 44px; padding-right: 600px; }
.pult { position: fixed; z-index: 40; right: 10px; top: 5px; display: flex; align-items: center; gap: 4px; padding: 3px 3px 3px 12px; border-radius: 999px; background: #23282f; color: #f3f2ee; box-shadow: 0 6px 18px rgba(35, 40, 47, .18); }
.pult .mono { color: rgba(243, 242, 238, .7); margin-right: 4px; }
.schalter { display: inline-flex; align-items: center; gap: 8px; min-height: 28px; padding: 0 11px 0 7px; border: 0; border-radius: 999px; background: transparent; color: inherit; cursor: pointer; font: 500 12.5px/1 'Instrument Sans', sans-serif; white-space: nowrap; transition: background-color var(--ui) var(--kurve); }
.schalter:hover { background: rgba(255, 255, 255, .1); }
.schalter small { font: 500 10px/1 'Geist Mono', monospace; letter-spacing: .04em; color: rgba(243, 242, 238, .66); }
.schalter .weiche { width: 24px; height: 14px; border-radius: 999px; background: rgba(255, 255, 255, .28); position: relative; flex: none; transition: background-color var(--ui) var(--kurve); }
.schalter .weiche::after { content: ''; position: absolute; left: 2px; top: 2px; width: 10px; height: 10px; border-radius: 50%; background: #fff; transition: translate var(--ui) var(--kurve); }
.schalter[aria-pressed="true"] { background: rgba(168, 138, 90, .28); }
.schalter[aria-pressed="true"] .weiche { background: #a88a5a; }
.schalter[aria-pressed="true"] .weiche::after { translate: 10px 0; }
.pult :focus-visible { outline: 2px solid #e3d2b4; outline-offset: 1px; }
.pult { padding-right: 30px; } /* Platz für den Knopf × des Rahmens */
@media (max-width: 1260px) { .schalter small { display: none; } .zustand { padding-right: 500px; } }
@media (max-width: 1000px) and (min-width: 641px) { .zustand { padding-right: clamp(12px, 2.4vw, 28px); padding-top: 50px; } }
@media (max-width: 640px), (hover: none) { .pult { top: auto; bottom: 12px; left: 12px; right: 12px; flex-wrap: wrap; border-radius: 14px; padding: 8px 30px 8px 12px; } .zustand { padding-right: clamp(12px, 2.4vw, 28px); } }
/* Reduzierte Bewegung */
.reduziert .nav a, .reduziert .knopf, .reduziert .schalter, .reduziert .schalter .weiche, .reduziert .schalter .weiche::after { transition: none; }
/* Touch */
@media (hover: none) {
.knopf.voll:hover { background: var(--weiss); color: var(--anthrazit); }
.knopf.dunkel:hover { background: var(--anthrazit); color: var(--weiss); }
.nav a:hover { text-decoration: none; }
}
/* Telefon: Vergleich untereinander, Simulation unnötig (das Fenster ist schon schmal) */
@media (max-width: 720px) {
.vergleich { flex-direction: column; align-items: stretch; }
.schmal .spalte { flex: 1 1 auto; }
.schmal .rahmen { border-width: 1px; border-radius: 0; }
.schmal .rahmen, .vergleich-an .rahmen { height: auto; max-height: 80svh; }
.zustand { font-size: 9.5px; }
}
/* Vorschau 960 × 720 */
.vorschau body { overflow: hidden; }
.vorschau .schmal .rahmen, .vorschau .vergleich-an .rahmen { height: 610px; }
</style>
</head>
<body>
<p class="zustand mono" data-zustand aria-live="polite"></p>
<div class="vergleich" data-vergleich>
<div class="spalte spalte-anfaellig" data-spalte-anfaellig aria-hidden="true" inert>
<p class="ergebnis mono" data-ergebnis="anfaellig"><i></i><span></span></p>
</div>
<div class="spalte">
<p class="ergebnis mono" data-ergebnis="robust"><i></i><span></span></p>
<div class="rahmen" data-rahmen>
<header class="kopf">
<span class="zeichen" aria-hidden="true">VERNIK</span>
<nav class="nav" aria-label="Hauptnavigation">
<ul>
<li><a href="#">Kollektion</a></li>
<li><a href="#">Manufaktur</a></li>
<li><a href="#">Service</a></li>
<li><a href="#">Boutique Villach</a></li>
</ul>
</nav>
</header>
<main>
<section class="held" aria-labelledby="titel">
<svg class="wortmarke" viewBox="0 0 1000 190" role="img" aria-label="Vernik" data-wortmarke>
<text x="0" y="172" font-size="238" textLength="1000" lengthAdjust="spacing">VERNIK</text>
</svg>
<div class="held-unten">
<div>
<span class="label">Uhrenmanufaktur · Villach, Kärnten</span>
<h1 id="titel">Zeit, in Villach <em>von Hand</em> reguliert.</h1>
</div>
<div class="held-aktion">
<p>Jedes Werk wird in unserer Werkstatt an der Drau zerlegt, gereinigt und in fünf Lagen reguliert, bevor es das Haus verlässt.</p>
<a class="knopf voll" href="#">Termin in der Boutique</a>
</div>
</div>
</section>
<section class="produkt" aria-labelledby="modell">
<figure class="uhr">
<svg viewBox="0 0 400 400" role="img" aria-label="Modell Dobratsch: silbernes Zifferblatt, gebläute Zeiger, kleine Sekunde bei sechs Uhr">
<defs>
<radialGradient id="blatt" cx="42%" cy="36%" r="70%"><stop offset="0" stop-color="#fbfbfa"/><stop offset=".7" stop-color="#e7e8e6"/><stop offset="1" stop-color="#cfd1cf"/></radialGradient>
<linearGradient id="lunette" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f2f3f4"/><stop offset=".45" stop-color="#9ea3a9"/><stop offset=".55" stop-color="#d7dadd"/><stop offset="1" stop-color="#6d737a"/></linearGradient>
</defs>
<rect x="168" y="0" width="64" height="70" fill="#3a3027"/><rect x="168" y="330" width="64" height="70" fill="#3a3027"/>
<circle cx="200" cy="200" r="150" fill="url(#lunette)"/>
<circle cx="200" cy="200" r="138" fill="url(#blatt)"/>
<circle cx="200" cy="200" r="126" fill="none" stroke="#c9cbc9" stroke-width="1"/>
<g stroke="#23282f" stroke-linecap="square">
<path d="M200 76v22M200 302v22M76 200h22M302 200h22" stroke-width="5"/>
<g stroke-width="2.4"><path d="M262 92.6l-6 10.4M307.4 138l-10.4 6M307.4 262l-10.4-6M262 307.4l-6-10.4M138 307.4l6-10.4M92.6 262l10.4-6M92.6 138l10.4 6M138 92.6l6 10.4"/></g>
</g>
<text x="200" y="138" text-anchor="middle" font-family="Bodoni Moda, serif" font-size="17" letter-spacing="5" fill="#23282f">VERNIK</text>
<text x="200" y="156" text-anchor="middle" font-family="Instrument Sans, sans-serif" font-size="7.5" letter-spacing="2.4" fill="#5f656d">VILLACH</text>
<circle cx="200" cy="262" r="30" fill="none" stroke="#b8bab8" stroke-width="1"/>
<path d="M200 262v-22" stroke="#23282f" stroke-width="1.6"/>
<g fill="#2a4a7f"><path d="M189.5 209.8 L290.1 148.0 L186.3 204.2 Z"/><path d="M213.9 204.6 L144.3 161.0 L209.1 211.5 Z"/></g>
<circle cx="200" cy="200" r="6" fill="#2a4a7f"/><circle cx="200" cy="200" r="2" fill="#a88a5a"/>
<rect x="348" y="190" width="14" height="20" rx="3" fill="#b9bdc1"/>
</svg>
</figure>
<div>
<span class="label">Neuheit 2026</span>
<h2 id="modell">Modell Dobratsch</h2>
<p>Benannt nach dem Hausberg. Handaufzug, Glasboden, in drei Größen gefertigt und von einer Hand reguliert.</p>
<dl class="daten">
<dt>Kaliber</dt><dd>VK 31, Handaufzug</dd>
<dt>Gangreserve</dt><dd>72 Stunden</dd>
<dt>Durchmesser</dt><dd>38,5 mm</dd>
<dt>Höhe</dt><dd>9,8 mm</dd>
<dt>Gehäuse</dt><dd>Edelstahl, satiniert</dd>
<dt>Wasserdicht</dt><dd>5 bar</dd>
</dl>
<div class="preis"><b>6.400 €</b><a class="knopf dunkel" href="#">In der Boutique anprobieren</a></div>
</div>
</section>
<section class="service" aria-labelledby="service-titel">
<h2 id="service-titel">Service im Haus</h2>
<div class="karten">
<article><h3>Revision in der Uhrmachermeisterwerkstatt</h3><p>Alle fünf Jahre zerlegen, reinigen, ölen und regulieren wir das Werk. Rechnen Sie mit rund sechs Wochen.</p><span class="label">ab 480 €</span></article>
<article><h3>Gravur</h3><p>Monogramm oder Datum auf dem Gehäuseboden, von Hand graviert und vorher auf Papier abgestimmt.</p><span class="label">ab 90 €</span></article>
<article><h3>Armbandwechsel</h3><p>Kalbsleder aus einer Kärntner Gerberei oder Milanaise, in zehn Minuten getauscht.</p><span class="label">ab 140 €</span></article>
</div>
</section>
</main>
<footer class="fuss">
<span>Vernik Uhrenmanufaktur · Villach, Kärnten</span>
<span>Boutique Di bis Sa, 10 bis 18 Uhr</span>
</footer>
</div>
</div>
</div>
<section class="pult" aria-label="Demo-Steuerung">
<p class="mono">Prüfen</p>
<button type="button" class="schalter" aria-pressed="false" data-schalter="abstand" title="WCAG 1.4.12: Zeilenhöhe 1,5, Absatzabstand 2, Buchstaben 0,12 em, Wörter 0,16 em"><span class="weiche" aria-hidden="true"></span>Textabstände <small>1.4.12</small></button>
<button type="button" class="schalter" aria-pressed="false" data-schalter="schmal" title="WCAG 1.4.10: 320 CSS-px entsprechen 400 % Zoom bei 1280 px"><span class="weiche" aria-hidden="true"></span>320 px <small>1.4.10</small></button>
<button type="button" class="schalter" aria-pressed="false" data-schalter="vergleich" title="Klon mit vw-Schrift, festen Höhen, overflow: hidden und nowrap"><span class="weiche" aria-hidden="true"></span>Anfällige Fassung</button>
</section>
<script>
(() => {
const html = document.documentElement;
const reduziert = () => html.classList.contains('reduziert');
const body = document.body;
const rahmen = document.querySelector('[data-rahmen]');
const spalteA = document.querySelector('[data-spalte-anfaellig]');
const zustand = document.querySelector('[data-zustand]');
const schalter = [...document.querySelectorAll('[data-schalter]')];
const st = { abstand: false, schmal: false, vergleich: false };
/* ---------- Anfällige Fassung als Klon: gleiche Inhalte, typische Fehler ---------- */
const klon = rahmen.cloneNode(true);
klon.classList.add('anfaellig');
klon.removeAttribute('data-rahmen');
klon.querySelectorAll('[id]').forEach(e => e.removeAttribute('id'));
klon.querySelectorAll('[aria-labelledby]').forEach(e => e.removeAttribute('aria-labelledby'));
const wm = klon.querySelector('[data-wortmarke]');
const wmText = document.createElement('p');
wmText.className = 'wortmarke-text'; wmText.textContent = 'VERNIK'; // Fehler: Wortmarke als Text in vw-Größe mit nowrap
wm.replaceWith(wmText);
spalteA.appendChild(klon);
/* ---------- Prüfung: abgeschnittener Text und waagerechtes Scrollen ---------- */
function pruefe(r) {
r.querySelectorAll('.markiert').forEach(e => e.classList.remove('markiert'));
let n = 0;
r.querySelectorAll('*').forEach(el => {
if (el.closest('svg')) return;
if (!(el.textContent || '').trim()) return;
const cs = getComputedStyle(el);
const kappt = /hidden|clip/.test(cs.overflowX + cs.overflowY);
if (!kappt) return;
if (el.scrollHeight > el.clientHeight + 2 || el.scrollWidth > el.clientWidth + 2) { el.classList.add('markiert'); n++; }
});
const quer = r.scrollWidth > r.clientWidth + 1;
return { n, quer };
}
function ergebnis(name, r) {
const p = document.querySelector(`[data-ergebnis="${name}"]`);
const { n, quer } = pruefe(r);
const schlecht = n > 0 || quer;
p.classList.toggle('schlecht', schlecht);
p.querySelector('span').innerHTML = `<b>${name === 'robust' ? 'Robust' : 'Anfällig'}</b> · ${n === 0 ? 'nichts abgeschnitten' : n === 1 ? '1 Stelle abgeschnitten' : n + ' Stellen abgeschnitten'} · ${quer ? 'waagerecht scrollen' : 'kein Querscrollen'}`;
return { n, quer };
}
let messung;
function messen() {
cancelAnimationFrame(messung);
messung = requestAnimationFrame(() => requestAnimationFrame(() => {
const r = ergebnis('robust', rahmen);
if (st.vergleich) ergebnis('anfaellig', klon);
zustand.innerHTML = `<span class="${st.abstand ? 'an' : ''}">Textabstände <b>${st.abstand ? 'erhöht (1.4.12)' : 'normal'}</b></span><span class="${st.schmal ? 'an' : ''}">Breite <b>${st.schmal ? '320 CSS-px (1.4.10)' : 'Fenster'}</b></span><span>Robuste Fassung <b>${r.n || r.quer ? r.n + ' Fehler' : 'hält'}</b></span>`;
}));
}
function setze(neu) {
Object.assign(st, neu);
body.classList.toggle('abstand', st.abstand);
body.classList.toggle('schmal', st.schmal);
body.classList.toggle('vergleich-an', st.vergleich);
schalter.forEach(b => b.setAttribute('aria-pressed', String(!!st[b.dataset.schalter])));
messen();
}
schalter.forEach(b => b.addEventListener('click', () => { stopp(); setze({ [b.dataset.schalter]: !st[b.dataset.schalter] }); }));
new ResizeObserver(messen).observe(rahmen);
document.fonts && document.fonts.ready.then(messen);
document.querySelectorAll('a[href="#"]').forEach(a => a.addEventListener('click', e => e.preventDefault()));
setze({});
/* ---------- Vorführung ---------- */
let zeit = [], laeuft = false;
const nach = (ms, f) => zeit.push(setTimeout(f, ms));
function runde() {
setze({ abstand: false, schmal: false, vergleich: false });
let t = 1200;
nach(t, () => setze({ abstand: true })); t += 2200;
nach(t, () => setze({ schmal: true })); t += 2400;
nach(t, () => setze({ vergleich: true })); t += 3600;
nach(t, () => setze({ abstand: false })); t += 2600;
nach(t, () => setze({ abstand: true })); t += 2600;
nach(t, runde);
}
function stopp() { zeit.forEach(clearTimeout); zeit = []; if (laeuft) { laeuft = false; html.classList.remove('vorfuehrung'); } }
function neustart() { stopp(); setze({ abstand: false, schmal: false, vergleich: false }); }
function vorfuehren() { neustart(); html.classList.add('vorfuehrung'); laeuft = true; runde(); }
function pause() { zeit.forEach(clearTimeout); zeit = []; }
function weiter() { if (html.classList.contains('vorfuehrung')) { laeuft = true; runde(); } else vorfuehren(); }
})();
</script>
</body>
</html>