Zum Inhalt
← Zugang & Robustheit

Fokus-Stil als Gestaltungssystem

Tastaturfokus als Teil der Gestaltung: :focus-visible mit outline in der Markenfarbe, eigener Form für Link, Knopf, Feld und Karte, kurzem Übergang und mindestens 3:1 Kontrast, wahlweise als Doppelring oder gelbe Signalfläche.

Ruhige Seiten auf hellem Grund mit genau einer Markenfarbe: Freelancer, Architektur, Kanzlei, Praxis.

  1. Der Tastaturfokus wird gestaltet statt entfernt: :focus-visible mit outline in der Markenfarbe, Versatz per outline-offset und einem kurzen Übergang beim Erscheinen.
  2. Jede Art von Element bekommt eine eigene, wiedererkennbare Form: Textlinks einen engen Ring mit kräftiger Unterstreichung, Knöpfe einen Ring mit Abstand, Felder einen Rand, der zur Linie wird, Karten einen weiten Ring mit Fläche.
  3. Auf dunklen Flächen und Bildern schaltet der Ring auf eine helle Variante oder einen Doppelring um.
  4. Grundlage bleibt outline, damit der Fokus auch im Kontrastmodus sichtbar ist.

Wer mit Tastatur, Schaltern oder Sprachsteuerung arbeitet, sieht nur über den Fokus, wo er ist. Ein gestalteter Fokus wirkt wie ein Teil der Marke, nicht wie ein Browser-Rest, und Designer lassen ihn darum stehen. Unterschiedliche Formen verraten schon vor dem Lesen, ob gleich ein Link folgt, ein Knopf oder ein Feld.

Wann einsetzen

Auf jeder Seite. Besonders dort, wo die Standard-Fokusringe stören würden und deshalb oft entfernt werden: ruhige Portfolios, Marken mit eigener Farbe, Seiten mit Bildern und dunklen Flächen, Formulare.

Wann nicht

Nie ganz weglassen. Nicht nur über box-shadow oder Hintergrundfarbe lösen (verschwindet im Kontrastmodus). Keine Fokusringe, die nur bei Hover erscheinen oder nach kurzer Zeit ausblenden. Kein reines Farb-Signal ohne Formänderung, wenn die Farbe nah an Text oder Rand liegt.

Varianten

  • Ring in Markenfarbe: outline 2 px, Versatz je nach Element 0 bis 6 px
  • Doppelring hell/dunkel für Bilder, Farbflächen und Dark Mode (outline plus box-shadow)
  • Signalfläche wie bei GOV.UK: gelbe Fläche mit schwarzer Unterkante für Links, gelber Rand mit schwarzer Linie für Felder
  • Unterstreichung, die beim Fokus dicker und farbig wird (zusätzlich zum Ring)

Umsetzung

Grundzustand für alle bedienbaren Elemente: outline: 0 solid var(--fokus); outline-offset: 0; transition: outline-width 80ms, outline-offset 80ms (Verschwinden schneller als Erscheinen). Fokus: :focus-visible { outline-width: 2px; outline-offset: var(--o); transition-duration: 160ms }. --o je Art: Textlink 2px (dazu text-decoration-thickness 2px in Markenfarbe), Knopf 3px (Ring folgt dem border-radius), Feld 0 (dazu border-color in Markenfarbe), Karte 6px (dazu Hintergrundfläche). Auf dunklen Flächen eine helle Fokusfarbe per Variable. Doppelring: outline 2px Markenfarbe plus box-shadow 0 0 0 var(--o) Grundfarbe, 0 0 0 calc(var(--o) + 4px) Textfarbe. Signalfläche: gelbe Fläche mit schwarzer Unterkante, outline 3px transparent nur für den Kontrastmodus. Kontrastmodus: @media (forced-colors: active) { :focus-visible { outline-color: Highlight } }. Im Grundzustand outline-width 0 statt transparenter outline, sonst zeigt der Kontrastmodus überall Rahmen. scroll-padding für feste Kopfleisten. Sprunglink als erster Tab-Halt. Vorführungen in iframes nur simulieren (Attribut statt focus()), sonst springt der Fokus der Hauptseite.

Robust umsetzen

Telefon. Auf Touch erscheint :focus-visible kaum, außer mit externer Tastatur oder Schaltersteuerung; dann gilt dasselbe System. Trefferflächen bleiben mindestens 44 px hoch.

Ohne Bewegung. Übergang entfällt, der Ring steht sofort. Form und Farbe bleiben gleich.

Barrierefrei. WCAG 2.4.7 Focus Visible (AA in WCAG 2.2): jedes bedienbare Element zeigt den Tastaturfokus. 2.4.11 Focus Not Obscured (Minimum, AA): nichts Eigenes (feste Kopfleiste, Cookie-Banner, Pult) verdeckt das fokussierte Element ganz; scroll-padding hilft. 2.4.13 Focus Appearance (AAA, als Ziel): Fläche mindestens wie ein 2 px dicker Umriss und 3:1 zwischen fokussiertem und unfokussiertem Zustand. 1.4.11 Non-text Contrast (AA): Ring mindestens 3:1 zu den angrenzenden Farben, hier 5,4:1 auf hellem und 8,3:1 auf dunklem Grund. 2.4.3 Focus Order: Reihenfolge folgt dem Lesefluss, ein Sprunglink steht zuerst. Kontrastmodus: outline bleibt sichtbar, box-shadow und Hintergrund nicht.

Code

<!DOCTYPE html>
<!--
  Fokus-Stil als Gestaltungssystem
  Fundus · Zugang & Robustheit · https://cecodesign.de/lab/fundus/zugang/fokus-stil-system/
  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>Fokus-Stil als Gestaltungssystem · 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-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 freelancer-stille-visitenkarte: Graustufen, eine Farbe nur für Fokus und Links */
  --grund: #fcfcfc; --text: #0b0b09; --leise: #646464; --linie: #e8e8e8; --flaeche: #f0f0f0; --rand: #c8c8c8;
  --nacht: #0b0b09; --nacht-text: #f4f4f2; --nacht-leise: #a3a3a3; --nacht-linie: #2a2a28;
  --fokus: #2f5bea;            /* 5,4 : 1 auf #fcfcfc */
  --fokus-hell: #8aa4ff;       /* 8,3 : 1 auf #0b0b09 */
  --signal: #ffdd00;
  --kurve: cubic-bezier(0.165, 0.84, 0.44, 1);   /* easeOutQuart */
  --rein: 160ms; --raus: 80ms;
  scroll-padding-block: 96px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--grund); color: var(--text); font: 400 1rem/1.6 'Host Grotesk', system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
body { min-height: 100svh; overflow-x: hidden; }
button, input, textarea { font: inherit; color: inherit; }
em { font: italic 400 1.05em/1 'Newsreader', Georgia, serif; font-optical-sizing: auto; letter-spacing: -.005em; }
.mono { font: 500 10.5px/1.4 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; }
.ziffern { font-variant-numeric: tabular-nums; }

/* ================= Fokus-System ================= */
/* Grundzustand: outline existiert mit Breite 0 (nicht transparent: sonst malt der Kontrastmodus überall Rahmen) */
.f { outline: 0 solid var(--fokus); outline-offset: 0; --o: 0px;
     transition: outline-width var(--raus) var(--kurve), outline-offset var(--raus) var(--kurve), box-shadow var(--raus) var(--kurve),
                 background-color var(--raus) var(--kurve), border-color var(--raus) var(--kurve), text-decoration-color var(--raus) var(--kurve), color var(--raus) var(--kurve); }
.f:is(:focus-visible, [data-fokus-sim], [data-fokus-muster]) { outline-width: 2px; outline-offset: var(--o); transition-duration: var(--rein); }
.f:focus:not(:focus-visible) { outline-width: 0; }
.dunkel { --fokus: var(--fokus-hell); }
/* Formen je Art */
.f.link  { --o: 2px; border-radius: 3px; }
.f.knopf { --o: 3px; }
.f.feld  { --o: 0px; }
.f.karte { --o: 6px; border-radius: 10px; }
[data-v="ring"] .f.link:is(:focus-visible, [data-fokus-sim], [data-fokus-muster]) { text-decoration-color: var(--fokus); text-decoration-thickness: 2px; }
[data-v="ring"] .f.feld:is(:focus-visible, [data-fokus-sim], [data-fokus-muster]) { border-color: var(--fokus); }
[data-v="ring"] .f.karte:is(:focus-visible, [data-fokus-sim], [data-fokus-muster]) { background: var(--flaeche); }

/* Variante Doppelring: Markenfarbe zwischen Grund- und Textfarbe, trägt auf hell und dunkel */
[data-v="doppel"] .f { --o: 2px; --innen: var(--grund); --aussen: var(--text); }
[data-v="doppel"] .f.karte { --o: 6px; }
[data-v="doppel"] .dunkel .f { --innen: var(--nacht); --aussen: var(--nacht-text); }
[data-v="doppel"] .f:is(:focus-visible, [data-fokus-sim], [data-fokus-muster]) { box-shadow: 0 0 0 var(--o) var(--innen), 0 0 0 calc(var(--o) + 4px) var(--aussen); }
[data-v="doppel"] .f.karte:is(:focus-visible, [data-fokus-sim], [data-fokus-muster]) { background: var(--flaeche); }

/* Variante Signalfläche (GOV.UK-Prinzip): Gelb und Schwarz, outline nur noch für den Kontrastmodus */
[data-v="signal"] .f:is(:focus-visible, [data-fokus-sim], [data-fokus-muster]) { outline-color: transparent; outline-width: 3px; outline-offset: 0; }
[data-v="signal"] .f.link:is(:focus-visible, [data-fokus-sim], [data-fokus-muster]) { background: var(--signal); color: var(--nacht); text-decoration: none; border-radius: 0; box-shadow: 0 -2px var(--signal), 0 4px var(--nacht); }
[data-v="signal"] .f.knopf:is(:focus-visible, [data-fokus-sim], [data-fokus-muster]) { background: var(--signal); color: var(--nacht); border-color: var(--nacht); box-shadow: 0 4px 0 var(--nacht); }
[data-v="signal"] .f.feld:is(:focus-visible, [data-fokus-sim], [data-fokus-muster]) { outline-color: var(--signal); border-color: var(--nacht); box-shadow: inset 0 0 0 2px var(--nacht); }
[data-v="signal"] .f.karte:is(:focus-visible, [data-fokus-sim], [data-fokus-muster]) { background: var(--signal); box-shadow: 0 0 0 6px var(--signal), 0 10px 0 0 var(--nacht); }
[data-v="signal"] .f.karte:is(:focus-visible, [data-fokus-sim], [data-fokus-muster]) * { color: var(--nacht); }

@media (forced-colors: active) {
  .f:is(:focus-visible, [data-fokus-sim]) { outline-color: Highlight !important; outline-width: 3px; }
}

/* ================= Seite ================= */
.sprung { position: absolute; left: 16px; top: -64px; z-index: 10; background: var(--text); color: var(--grund); padding: 10px 16px; text-decoration: none; transition: top var(--raus) var(--kurve); }
.sprung:is(:focus-visible, [data-fokus-sim]) { top: 12px; transition-duration: var(--rein); }
[data-v="signal"] .sprung:is(:focus-visible, [data-fokus-sim]) { color: var(--nacht); }

.seite { max-width: 1072px; margin: 0 auto; padding: 0 32px; display: grid; grid-template-columns: minmax(0, 640px) minmax(220px, 280px); justify-content: space-between; gap: 48px; }
.spalte { padding: 48px 0 120px; display: grid; gap: 72px; align-content: start; }
.kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
.name { font-weight: 500; text-decoration: none; padding: 2px 0; }
.nav { display: flex; gap: 20px; list-style: none; }
a { color: inherit; text-decoration-line: underline; text-decoration-color: var(--rand); text-decoration-thickness: 1px; text-underline-offset: 4px; }
a:hover { text-decoration-color: var(--text); }
.nav a { text-decoration: none; color: var(--leise); padding: 4px 0; }
.nav a:hover { color: var(--text); }
.nav a[aria-current] { color: var(--text); }
h1 { font-size: clamp(1.25rem, 2vw, 1.5rem); font-weight: 500; line-height: 1.45; letter-spacing: -.01em; text-wrap: pretty; }
h1 a { font-weight: 500; }
h2 { font-size: 1rem; font-weight: 500; color: var(--leise); margin-bottom: 14px; }
.liste { list-style: none; display: grid; gap: 4px; margin: 0 -14px; }
.karte { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 24px; padding: 12px 14px; text-decoration: none; }
.karte:hover { background: var(--flaeche); }
.karte b { font-weight: 500; }
.karte span { color: var(--leise); grid-column: 1; font-size: .9375rem; }
.karte time { grid-row: 1; grid-column: 2; color: var(--leise); font-variant-numeric: tabular-nums; }

.fall { background: var(--nacht); color: var(--nacht-text); padding: 28px 28px 26px; display: grid; gap: 16px; border-radius: 4px; }
.fall .mono { color: var(--nacht-leise); }
.fall p.gross { font-size: 1.25rem; line-height: 1.4; font-weight: 500; text-wrap: pretty; }
.fall .zeilen { display: flex; flex-wrap: wrap; gap: 14px 22px; align-items: center; }
.fall a { color: var(--nacht-text); text-decoration-color: #5a5a58; }
.muster-fall { height: 64px; background:
  repeating-linear-gradient(90deg, transparent 0 22px, var(--nacht-linie) 22px 23px),
  linear-gradient(90deg, #1c1c1a 0 38%, transparent 38%), linear-gradient(90deg, #1c1c1a 0 64%, transparent 64%);
  background-size: auto, 100% 10px, 100% 10px; background-position: 0 0, 0 14px, 0 40px; background-repeat: repeat, no-repeat, no-repeat; border-bottom: 1px solid var(--nacht-linie); }

.knopf { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 20px; border-radius: 999px; border: 1px solid var(--text); background: var(--text); color: var(--grund); cursor: pointer; font-weight: 500; }
.knopf:hover { background: #2a2a28; }
.knopf.leise { background: transparent; color: var(--text); border-color: var(--rand); }
.knopf.leise:hover { background: var(--flaeche); }
.fall .knopf { background: var(--nacht-text); color: var(--nacht); border-color: var(--nacht-text); }
.fall .knopf:hover { background: #ffffff; }

.kontakt { display: grid; gap: 18px; }
.mail { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; font-size: 1.125rem; }
.kopiert { color: var(--leise); font-size: .875rem; min-height: 1.4em; }
form { display: grid; gap: 14px; }
.zeile { display: grid; gap: 6px; }
label { font-size: .875rem; font-weight: 500; }
label small { font-weight: 400; color: var(--leise); font-size: inherit; }
.feld { width: 100%; border: 1px solid var(--rand); background: #ffffff; border-radius: 8px; padding: 11px 13px; min-height: 46px; }
textarea.feld { min-height: 104px; resize: vertical; }
.feld::placeholder { color: #8a8a8a; }
.absenden { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.status { color: var(--leise); font-size: .875rem; }
.fuss { display: flex; gap: 20px; flex-wrap: wrap; color: var(--leise); font-size: .875rem; border-top: 1px solid var(--linie); padding-top: 18px; }
.fuss a { color: var(--leise); }

/* Randspalte: Legende, folgt dem Fokus */
.legende { position: sticky; top: 0; align-self: start; padding: 52px 0 32px; display: grid; gap: 14px; }
.legende h2 { margin: 0; }
.legende ol { list-style: none; display: grid; gap: 2px; border-top: 1px solid var(--linie); }
.legende li { display: grid; grid-template-columns: 74px 1fr; gap: 12px; align-items: center; padding: 12px 8px; border-bottom: 1px solid var(--linie); color: var(--leise); transition: background-color var(--raus) var(--kurve), color var(--raus) var(--kurve); }
.legende li.an { background: var(--flaeche); color: var(--text); transition-duration: var(--rein); }
.legende li b { display: block; font-weight: 500; color: var(--text); font-size: .9375rem; }
.legende li small { font: 400 10.5px/1.45 'Geist Mono', ui-monospace, monospace; letter-spacing: .02em; display: block; }
.probe { display: grid; place-items: center; height: 40px; }
.probe span { pointer-events: none; cursor: default; }
.probe .link { text-decoration: underline; text-underline-offset: 4px; text-decoration-color: var(--rand); font-size: .875rem; }
.probe .knopf { min-height: 26px; padding: 0 10px; font-size: .75rem; }
.probe .feld { width: 62px; min-height: 26px; padding: 0; border-radius: 6px; display: block; }
.probe .karte { width: 54px; height: 22px; padding: 0; display: block; border: 1px dashed var(--rand); border-radius: 4px; }
.ablesen { font: 400 10.5px/1.6 'Geist Mono', ui-monospace, monospace; color: var(--leise); min-height: 3.2em; }
.ablesen b { color: var(--text); font-weight: 500; }
.hinweis-tab { color: var(--leise); font-size: .875rem; }
.taste { display: inline-grid; place-items: center; min-width: 46px; height: 26px; padding: 0 8px; border: 1px solid var(--rand); border-bottom-width: 3px; border-radius: 6px; font: 500 10.5px/1 'Geist Mono', ui-monospace, monospace; letter-spacing: .06em; color: var(--text); background: #fff; transition: translate 90ms var(--kurve), border-bottom-width 90ms var(--kurve); vertical-align: middle; }
.taste.druck { translate: 0 2px; border-bottom-width: 1px; }

/* Pult der Demo */
.pult { position: fixed; right: 16px; bottom: 16px; z-index: 20; display: flex; align-items: center; gap: 10px; background: rgb(252 252 252 / .94); border: 1px solid var(--linie); border-radius: 999px; padding: 6px 6px 6px 14px; }
.pult .mono { color: var(--leise); }
.umschalter { display: flex; gap: 2px; }
.umschalter button { border: 0; background: transparent; border-radius: 999px; padding: 0 12px; min-height: 32px; cursor: pointer; font: 500 10.5px/1 'Geist Mono', ui-monospace, monospace; letter-spacing: .1em; text-transform: uppercase; color: var(--leise); }
.umschalter button[aria-pressed="true"] { background: var(--text); color: var(--grund); }
.umschalter button:focus-visible { outline: 2px solid var(--fokus); outline-offset: 2px; }

/* Reduzierte Bewegung: Ring steht sofort */
:root.reduziert .f, :root.reduziert .sprung, :root.reduziert .legende li, :root.reduziert .taste { transition: none !important; }

@media (hover: none) {
  .karte:hover, .knopf.leise:hover { background: transparent; }
  .knopf:hover { background: var(--text); }
  .fall .knopf:hover { background: var(--nacht-text); }
}
@media (max-width: 880px) {
  .seite { grid-template-columns: minmax(0, 1fr); gap: 0; padding: 0 20px; }
  .legende { position: static; padding: 0 0 120px; }
  .spalte { padding-bottom: 56px; gap: 56px; }
}
@media (max-width: 480px) {
  .nav { gap: 14px; }
  .kopf { flex-wrap: wrap; }
  .fall { padding: 22px 20px; }
  .pult { left: 12px; right: 12px; bottom: 12px; justify-content: space-between; }
  .pult > .mono { display: none; }
}

/* Vorschau 960 × 720 */
.vorschau body { overflow: hidden; }
.vorschau .seite { grid-template-columns: minmax(0, 560px) 260px; gap: 40px; }
.vorschau .spalte { padding-top: 34px; gap: 48px; }
.vorschau .legende { padding-top: 38px; }
</style>
</head>
<body data-v="ring">
<a class="sprung f link" href="#inhalt" data-art="link">Zum Inhalt springen</a>
<div class="seite">
  <div class="spalte">
    <header class="kopf">
      <a class="name f link" href="#" data-art="link">Fenna Ostermeyer</a>
      <nav aria-label="Hauptnavigation">
        <ul class="nav">
          <li><a class="f link" href="#arbeiten" aria-current="page" data-art="link">Arbeiten</a></li>
          <li><a class="f link" href="#notizen" data-art="link">Notizen</a></li>
          <li><a class="f link" href="#kontakt" data-art="link">Kontakt</a></li>
        </ul>
      </nav>
    </header>

    <main id="inhalt" tabindex="-1" style="display: grid; gap: inherit; outline: none;">
      <h1>Fenna Ostermeyer gestaltet Oberflächen, die man <em>ohne Maus</em> versteht. Interaction-Designerin in Celle, vorher acht Jahre in der Antragsstrecke eines Versicherers, heute für kleine Teams mit <a class="f link" href="#notizen" data-art="link">großen Formularen</a>.</h1>

      <section id="arbeiten" aria-labelledby="h-arbeiten">
        <h2 id="h-arbeiten">Arbeiten</h2>
        <ul class="liste">
          <li><a class="karte f" href="#" data-art="karte"><b>Antragsstrecke für eine Wohnungsgenossenschaft</b><time class="ziffern" datetime="2026">2026</time><span>Elf Schritte wurden vier. Tastatur zuerst, Fehler sagen, was zu tun ist.</span></a></li>
          <li><a class="karte f" href="#" data-art="karte"><b>Ausleihe-App für drei Stadtbüchereien</b><time class="ziffern" datetime="2025">2025</time><span>Vormerken mit einem Daumen, Vorlesefunktion, große Schrift ab Werk.</span></a></li>
          <li><a class="karte f" href="#" data-art="karte"><b>Designsystem für ein Pflegeportal</b><time class="ziffern" datetime="2024">2024</time><span>62 Bausteine, jeder mit Fokus, Fehler- und Leerzustand.</span></a></li>
        </ul>
      </section>

      <section class="fall dunkel" id="notizen" aria-labelledby="h-fall">
        <div class="muster-fall" aria-hidden="true"></div>
        <p class="mono" id="h-fall">Fallstudie · 12 Minuten Lesezeit</p>
        <p class="gross">Ein Wohngeld-Antrag, der sich mit der Tastatur in <em>vier Minuten</em> ausfüllen lässt.</p>
        <div class="zeilen">
          <button class="knopf f" type="button" data-art="knopf">Fallstudie lesen</button>
          <a class="f link" href="#" data-art="link">Als PDF, 2 MB</a>
        </div>
      </section>

      <section class="kontakt" id="kontakt" aria-labelledby="h-kontakt">
        <h2 id="h-kontakt" style="margin:0">Kontakt</h2>
        <p class="mail"><a class="f link" href="#" data-art="link">hallo@fenna-ostermeyer.de</a><button class="knopf leise f" type="button" data-art="knopf" data-kopieren>Adresse kopieren</button></p>
        <p class="kopiert" role="status" data-kopiert></p>
        <form data-form>
          <div class="zeile"><label for="f-mail">Ihre E-Mail</label><input class="feld f" id="f-mail" type="email" autocomplete="email" placeholder="name@firma.de" data-art="feld"></div>
          <div class="zeile"><label for="f-text">Worum geht es? <small>zwei, drei Sätze genügen</small></label><textarea class="feld f" id="f-text" rows="3" data-art="feld"></textarea></div>
          <div class="absenden"><button class="knopf f" type="submit" data-art="knopf">Anfrage senden</button><span class="status">Antwort meist am nächsten Werktag.</span></div>
        </form>
      </section>
    </main>

    <footer class="fuss"><a class="f link" href="#" data-art="link">Impressum</a><a class="f link" href="#" data-art="link">Datenschutz</a><a class="f link" href="#" data-art="link">Barrierefreiheit</a></footer>
  </div>

  <aside class="legende" aria-labelledby="h-legende">
    <h2 id="h-legende" class="mono">Fokus auf dieser Seite</h2>
    <p class="hinweis-tab">Mit <span class="taste" data-taste aria-hidden="true">Tab ⇥</span> durch die Seite gehen.</p>
    <ol>
      <li data-zeile="link"><span class="probe" aria-hidden="true"><span class="f link" data-fokus-muster>Link</span></span><span><b>Link</b><small data-wert="link"></small></span></li>
      <li data-zeile="knopf"><span class="probe" aria-hidden="true"><span class="f knopf" data-fokus-muster>Knopf</span></span><span><b>Knopf</b><small data-wert="knopf"></small></span></li>
      <li data-zeile="feld"><span class="probe" aria-hidden="true"><span class="f feld" data-fokus-muster></span></span><span><b>Feld</b><small data-wert="feld"></small></span></li>
      <li data-zeile="karte"><span class="probe" aria-hidden="true"><span class="f karte" data-fokus-muster></span></span><span><b>Karte</b><small data-wert="karte"></small></span></li>
    </ol>
    <p class="ablesen" data-ablesen aria-live="polite"></p>
  </aside>
</div>

<div class="pult">
  <span class="mono">Variante</span>
  <div class="umschalter" role="group" aria-label="Fokus-Variante wählen">
    <button type="button" data-v="ring" aria-pressed="true">Ring</button>
    <button type="button" data-v="doppel" aria-pressed="false">Doppelring</button>
    <button type="button" data-v="signal" aria-pressed="false">Signal</button>
  </div>
</div>

<script>
(() => {
  const html = document.documentElement, body = document.body;
  const $ = (s, r = document) => r.querySelector(s);
  const $$ = (s, r = document) => [...r.querySelectorAll(s)];
  const reduziert = () => html.classList.contains('reduziert');
  const VARIANTEN = ['ring', 'doppel', 'signal'];

  /* Werte je Variante und Art (stehen auch im Bau-Block) */
  const WERTE = {
    ring: {
      link: 'outline 2px #2f5bea · Versatz 2px · Radius 3px · Unterstrich 2px',
      knopf: 'outline 2px #2f5bea · Versatz 3px · folgt der Pille',
      feld: 'outline 2px · Versatz 0 · Rand wird #2f5bea',
      karte: 'outline 2px · Versatz 6px · Radius 10px · Fläche #f0f0f0',
      kontrast: 'Ring zu Grund <b>5,4 : 1</b> (#2f5bea auf #fcfcfc), auf Schwarz <b>8,3 : 1</b> (#8aa4ff)'
    },
    doppel: {
      link: 'Grund 2px · outline 2px #2f5bea · Rand 4px Text',
      knopf: 'gleicher Doppelring, Versatz 2px',
      feld: 'gleicher Doppelring, Versatz 2px',
      karte: 'Versatz 6px · Doppelring · Fläche #f0f0f0',
      kontrast: 'äußerer Ring <b>19 : 1</b> zum Grund, dreht auf Schwarz die Reihenfolge'
    },
    signal: {
      link: 'Fläche #ffdd00 · Unterkante 4px #0b0b09',
      knopf: 'Fläche #ffdd00 · Rand und Unterkante #0b0b09',
      feld: 'outline 3px #ffdd00 · innen 2px #0b0b09',
      karte: 'Fläche #ffdd00 · Unterkante 4px #0b0b09',
      kontrast: 'Schwarz auf Gelb <b>14,6 : 1</b>; im Kontrastmodus bleibt eine 3-px-outline'
    }
  };
  let variante = 'ring';
  const zeilen = $$('[data-zeile]'), ablesen = $('[data-ablesen]');
  function setzeVariante(v, url) {
    if (!VARIANTEN.includes(v)) v = VARIANTEN[0];
    variante = v; body.dataset.v = v;
    $$('[data-v]').forEach(b => b.matches('button') && b.setAttribute('aria-pressed', String(b.dataset.v === v)));
    Object.entries(WERTE[v]).forEach(([art, t]) => { const el = $(`[data-wert="${art}"]`); if (el) el.textContent = t; });
    ablesen.innerHTML = WERTE[v].kontrast;
    if (url) { const q = new URLSearchParams(location.search); q.set('v', v); history.replaceState(null, '', location.pathname + '?' + q + location.hash); }
  }
  $$('.umschalter button').forEach(b => b.addEventListener('click', () => { const lief = laeuft; if (lief) pause(); setzeVariante(b.dataset.v, true); if (lief) vorfuehren(); }));
  setzeVariante(new URLSearchParams(location.search).get('v') || 'ring', false);

  /* Legende folgt dem echten Tastaturfokus */
  function kurzname(el) {
    const t = ((el.labels && el.labels[0] ? el.labels[0].firstChild.textContent : '') || (el.querySelector('b') || el).textContent).trim().replace(/\s+/g, ' ');
    if (t.length <= 30) return t;
    return t.slice(0, 30).replace(/\s+\S*$/, '') + ' …';
  }
  function markiere(el) {
    const art = el && el.dataset.art;
    zeilen.forEach(z => z.classList.toggle('an', z.dataset.zeile === art));
    if (art) ablesen.innerHTML = `<b>${kurzname(el) || art}</b> · ${WERTE[variante][art]}<br>${WERTE[variante].kontrast}`;
  }
  document.addEventListener('focusin', e => { if (e.target.matches(':focus-visible')) markiere(e.target); });
  document.addEventListener('focusout', () => setTimeout(() => { if (!document.activeElement || document.activeElement === body) { zeilen.forEach(z => z.classList.remove('an')); ablesen.innerHTML = WERTE[variante].kontrast; } }));

  /* Kleine echte Funktionen */
  $('[data-kopieren]').addEventListener('click', async () => {
    const t = 'hallo@fenna-ostermeyer.de', out = $('[data-kopiert]');
    try { await navigator.clipboard.writeText(t); out.textContent = 'Kopiert. Sie können die Adresse jetzt einfügen.'; }
    catch (e) { out.textContent = 'Kopieren ging nicht. Die Adresse lautet hallo@fenna-ostermeyer.de.'; }
  });
  $('[data-form]').addEventListener('submit', e => { e.preventDefault(); $('.status').textContent = 'Danke. Die Anfrage ist angekommen.'; });

  /* Vorführung: „Tab“ ohne echten Fokus (die Vorschau im iframe darf der Galerie keinen Fokus stehlen) */
  const taste = $('[data-taste]');
  let uhren = [], laeuft = false, schritt = 0;
  const ziele = () => {
    const alle = $$('.f').filter(el => !el.closest('.probe'));
    const reihe = [alle[0], alle[2], $('h1 a'), ...$$('.karte').slice(0, 2), $('.fall .knopf'), $('.fall a'), $('[data-kopieren]'), $('#f-mail'), $('#f-text'), $('form .knopf')];
    return reihe;
  };
  function zeigeSchritt() {
    const liste = ziele(), el = liste[schritt % liste.length];
    $$('[data-fokus-sim]').forEach(x => x.removeAttribute('data-fokus-sim'));
    taste.classList.add('druck'); uhren.push(setTimeout(() => taste.classList.remove('druck'), 140));
    el.setAttribute('data-fokus-sim', '');
    markiere(el);
    // nur im eigenen Fenster scrollen (scrollIntoView würde die Galerie mitziehen)
    const r = el.getBoundingClientRect(), rand = 90;
    if (r.top < rand || r.bottom > innerHeight - rand) {
      const ziel = scrollY + r.top - innerHeight * .38;
      scrollTo({ top: Math.max(0, ziel), behavior: reduziert() ? 'auto' : 'smooth' });
    }
    if (schritt % liste.length === 0) scrollTo({ top: 0, behavior: 'auto' });
    schritt++;
    uhren.push(setTimeout(zeigeSchritt, schritt % liste.length === 0 ? 1800 : 1100));
  }
  function vorfuehren() { neustart(); laeuft = true; html.classList.add('vorfuehrung'); uhren.push(setTimeout(zeigeSchritt, 500)); }
  function pause() { uhren.forEach(clearTimeout); uhren = []; laeuft = false; }
  function weiter() { if (!laeuft && html.classList.contains('vorfuehrung')) { laeuft = true; zeigeSchritt(); } }
  function neustart() {
    pause(); schritt = 0; html.classList.remove('vorfuehrung');
    $$('[data-fokus-sim]').forEach(x => x.removeAttribute('data-fokus-sim'));
    zeilen.forEach(z => z.classList.remove('an')); ablesen.innerHTML = WERTE[variante].kontrast;
    scrollTo({ top: 0, behavior: 'auto' });
  }

})();
</script>
</body>
</html>