Kontur-Button, der sich zeichnet
Ein Outline-Button, dessen Kontur sich bei Hover und Fokus einmal um ihn herum zeichnet: als Linie, als wachsende Ecken, als aufrückende Doppelkontur oder als wandernde Striche.
Grundvariante für ruhige Marken: Luxus und Manufaktur, Architektur, Fotografie; Nebenaktion neben gefülltem Hauptbutton.
Ein Outline-Button, dessen Kontur sich bei Hover oder Fokus zeichnet. In Ruhe liegt eine feine Grundlinie, darüber läuft eine kräftigere Linie einmal um den Button. Spielarten: eine Linie zeichnet sich (SVG-rect mit pathLength und stroke-dashoffset), vier Eckklammern wachsen zur geschlossenen Kontur, eine zweite versetzte Kontur rückt auf die erste, oder eine gestrichelte Kontur setzt sich in Bewegung.
Die Linie ist die ruhigste Form von Rückmeldung: keine Fläche, keine Farbe, nur ein Strich, der sich schließt. Das passt zu Marken, die mit Raum und Feinheit arbeiten. Der Zeichenvorgang lenkt den Blick, ohne die Seite lauter zu machen.
Wann einsetzen
Für Nebenaktionen neben einem gefüllten Hauptbutton (Details, Kaliber ansehen, Programm, Katalog) und für Karten in Kollektionen und Programmen. Rezepte: Luxus und Manufaktur, Architektur, Fotografie, Kultur (Eckklammern).
Wann nicht
Nicht als einziger oder wichtigster Button, wenn die Kontur wenig Kontrast hat: Outline ist sekundär. Nicht bei Pillen oder Formen mit stark gekrümmter Kontur, wenn die Linie ungleichmäßig wirkt. Laufende Striche nie dauerhaft, nur solange der Button Fokus oder Hover hat, und nie mehrere gleichzeitig.
Varianten
- zeichnen: SVG-rect mit pathLength=1, stroke-dasharray 1 1, stroke-dashoffset 1 → 0 bei Hover und Fokus
- ecken: vier Eckklammern liegen in Ruhe, bei Hover wachsen sie zur geschlossenen Kontur
- doppel: eine zweite Kontur liegt 3 px versetzt dahinter und rückt bei Hover auf die erste
- laufend: gestrichelte Kontur, die Striche wandern nur bei Hover und Fokus und halten nach wenigen Umläufen an
Umsetzung
Button oder Link mit echtem border (Grundlinie, auch für Kontrastmodus und ohne SVG) und einem inneren <svg aria-hidden="true" focusable="false"> ohne viewBox über die ganze Fläche (position absolute, inset -1px, overflow visible). Das rect hat width/height="100%" als Attribut und per CSS x/y .75px und calc(100% - 1.5px), damit der Strich genau auf der Kante liegt; pathLength="1" macht die Dash-Rechnung unabhängig von der Größe. Zeichnen: stroke-dasharray 1 1, dashoffset 1 → 0 bei :hover (nur @media (hover: hover)) und :focus-visible; die Übergangsdauer steht am Zielzustand, hin 480 ms expo-out, zurück 310 ms expo-in (Rezept: Öffnen langsamer als Schließen). Ecken: vier span mit je zwei Rändern, Breite und Höhe 12 px → 50 % + 1 px. Doppel: ::after mit 3 px Versatz. Laufend: zweites rect mit Pixel-Strichen, Animation mit begrenzter Iterationszahl. Laden nutzt dasselbe rect als umlaufendes Viertel. Ein Outline-Button ist sekundär: daneben steht ein gefüllter Hauptbutton.
Robust umsetzen
Telefon. Ohne Hover steht die Kontur geschlossen da (Endzustand), Druck über einen kurzen Farbwechsel der Fläche. Trefferfläche mindestens 44 px hoch.
Ohne Bewegung. Kein Zeichnen: Die Kontur erscheint bei Hover und Fokus sofort vollständig (Deckkraft 150 ms). Laufende Striche stehen still.
Barrierefrei. Grundkontur in Ruhe mit mindestens 3:1 gegen den Grund (WCAG 1.4.11), Text mindestens 4,5:1. Fokus über :focus-visible mit eigener Fokuskontur im Abstand, nicht nur über die gezeichnete Linie. Das SVG ist aria-hidden. Laufende Striche starten nur durch Hover oder Fokus und halten nach wenigen Umläufen an (WCAG 2.2.2). Im Kontrastmodus bleibt der echte border in ButtonText.
Code
<!DOCTYPE html>
<!--
Kontur-Button, der sich zeichnet
Fundus · Buttons · https://cecodesign.de/lab/fundus/buttons/kontur-button-zeichnen/
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>Kontur-Button, der sich zeichnet · 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: '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 luxus-manufaktur-stille: Anthrazit und Weiß im Wechsel, Messing nur als Linie und Fokus */
--anthrazit: #23282f; --weiss: #ffffff; --flaeche: #f4f4f2; --linie: #d9dadb;
--messing: #a88a5a; /* Linie, Fokus: 3,3:1 auf Weiß, 4,6:1 auf Anthrazit */
--leise: #62676e; /* Meta auf Weiß 5,7:1 (statt #8b9097, das nur 3,2:1 hat) */
--leise-dunkel: rgba(255, 255, 255, .72);
--auf: cubic-bezier(0.16, 1, 0.3, 1); /* Öffnen, Zeichnen */
--zu: cubic-bezier(0.7, 0, 0.84, 0); /* Schließen, Zurückziehen */
--t-auf: 480ms; --t-zu: 310ms; /* zu = etwa 65 % von auf */
}
* { box-sizing: border-box; margin: 0; }
html { background: var(--weiss); color: var(--anthrazit); font: 400 .9375rem/1.47 'Instrument Sans', system-ui, sans-serif; -webkit-text-size-adjust: 100%; }
:root:has(:focus-visible) { scroll-behavior: auto; }
body { min-height: 100svh; overflow-x: hidden; }
:focus-visible { outline: 1px solid var(--messing); outline-offset: 5px; }
.versal { font: 500 .75rem/1.3 'Instrument Sans', sans-serif; letter-spacing: .08em; text-transform: uppercase; }
.leise { color: var(--leise); }
/* ---------- Kopf ---------- */
.kopf { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 16px; padding: 22px clamp(20px, 4vw, 56px); border-bottom: 1px solid var(--linie); }
.kopf nav { display: flex; gap: clamp(16px, 2.6vw, 34px); flex-wrap: wrap; }
.kopf nav a, .kopf .termin { color: var(--anthrazit); text-decoration: none; padding: 12px 0; }
.kopf nav a:hover, .kopf .termin:hover { text-decoration: underline; text-underline-offset: 5px; text-decoration-thickness: 1px; }
.marke { text-align: center; color: var(--anthrazit); text-decoration: none; }
.marke b { display: block; font: 400 1.625rem/1 'Bodoni Moda', serif; font-optical-sizing: auto; letter-spacing: .28em; margin-right: -.28em; }
.marke small { display: block; margin-top: 6px; font-size: .625rem; letter-spacing: .2em; text-transform: uppercase; color: var(--leise); }
.kopf .termin { justify-self: end; }
/* ---------- Bühne ---------- */
.buehne { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); min-height: min(720px, calc(100svh - 86px)); }
.text { padding: clamp(36px, 6vw, 88px) clamp(20px, 5vw, 72px); display: flex; flex-direction: column; justify-content: center; }
.text > .versal { color: var(--leise); }
h1 .unterschneiden { margin-right: -.09em; } /* Bodoni Moda hat kein Kernpaar W–ö */
h1 { margin-top: 18px; font: 400 clamp(2.75rem, 1.2rem + 5vw, 5.5rem)/1.02 'Bodoni Moda', serif; font-optical-sizing: auto; letter-spacing: -.01em; }
h1 i { font-style: normal; color: var(--leise); }
.text p.satz { margin-top: 20px; max-width: 38ch; font-size: 1rem; }
.handlung { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 34px; }
.daten { display: grid; grid-template-columns: repeat(3, minmax(0, max-content)); gap: 4px 34px; margin-top: 44px; padding-top: 22px; border-top: 1px solid var(--linie); }
.daten dt { grid-row: 1; color: var(--leise); }
.daten dd { grid-row: 2; font: 400 1.375rem/1.2 'Bodoni Moda', serif; font-variant-numeric: tabular-nums lining-nums; }
.bild { background: var(--flaeche); display: grid; place-items: center; padding: clamp(24px, 4vw, 56px) clamp(24px, 4vw, 56px) 64px; position: relative; }
.bild svg { width: min(100%, 420px); height: auto; }
.bild figcaption { position: absolute; left: clamp(20px, 3vw, 36px); bottom: clamp(18px, 3vw, 30px); color: var(--leise); }
/* ---------- Primärer Button: gefüllt (Outline bleibt sekundär) ---------- */
.voll { display: inline-flex; align-items: center; justify-content: center; min-height: 52px; padding: 0 30px; background: var(--anthrazit); color: var(--weiss); text-decoration: none; border: 1px solid var(--anthrazit); transition: background-color 150ms var(--auf); }
.voll:hover { background: #343a43; }
/* =====================================================================
Kontur-Button: echte Grundlinie (border) + gezeichnete Linie darüber
===================================================================== */
.kontur {
--grund-linie: rgba(35, 40, 47, .55); /* ≥ 3:1 auf Weiß (WCAG 1.4.11) */
--strich: var(--anthrazit);
--text: var(--anthrazit);
position: relative; display: inline-flex; align-items: center; justify-content: center; gap: .7em;
min-height: 52px; padding: 0 30px; border: 1px solid var(--grund-linie); background: transparent;
color: var(--text); text-decoration: none; cursor: pointer; font: inherit;
transition: background-color 125ms linear, border-color var(--t-zu) var(--zu);
-webkit-tap-highlight-color: transparent;
}
.kontur .versal { white-space: nowrap; }
.kontur:active { background: rgba(35, 40, 47, .06); }
.auf-dunkel .kontur { --grund-linie: rgba(255, 255, 255, .5); --strich: var(--weiss); --text: var(--weiss); }
.auf-dunkel .kontur:active { background: rgba(255, 255, 255, .08); }
/* Linie: SVG ohne viewBox, rect = Größe des Buttons, pathLength 1 */
.kontur__svg { position: absolute; inset: -1px; width: calc(100% + 2px); height: calc(100% + 2px); overflow: visible; pointer-events: none; }
.kontur__svg rect { x: .75px; y: .75px; width: calc(100% - 1.5px); height: calc(100% - 1.5px); fill: none; stroke: var(--strich); stroke-width: 1.5; }
.kontur__zug { stroke-dasharray: 1 1; stroke-dashoffset: 1; transition: stroke-dashoffset var(--t-zu) var(--zu); }
.kontur__strich { display: none; stroke-dasharray: 7 5; stroke-width: 1.25; }
.kontur__ecke { display: none; position: absolute; width: 12px; height: 12px; border: 0 solid var(--strich); pointer-events: none; transition: width var(--t-zu) var(--zu), height var(--t-zu) var(--zu); }
.kontur__ecke--ol { top: -1px; left: -1px; border-top-width: 1.5px; border-left-width: 1.5px; }
.kontur__ecke--or { top: -1px; right: -1px; border-top-width: 1.5px; border-right-width: 1.5px; }
.kontur__ecke--ul { bottom: -1px; left: -1px; border-bottom-width: 1.5px; border-left-width: 1.5px; }
.kontur__ecke--ur { bottom: -1px; right: -1px; border-bottom-width: 1.5px; border-right-width: 1.5px; }
.kontur::after { content: ''; display: none; position: absolute; inset: -1px; border: 1px solid var(--messing); transform: translate(3px, 3px); transition: transform var(--t-zu) var(--zu), border-color var(--t-zu) var(--zu); pointer-events: none; }
/* aktiv = Hover (nur mit Zeiger), Fokus per Tastatur oder simulierter Zeiger */
@media (hover: hover) {
.kontur:hover .kontur__zug { stroke-dashoffset: 0; transition: stroke-dashoffset var(--t-auf) var(--auf); }
.kontur:hover .kontur__ecke { width: calc(50% + 1px); height: calc(50% + 1px); transition: width var(--t-auf) var(--auf), height var(--t-auf) var(--auf); }
.kontur:hover::after { transform: translate(0, 0); border-color: var(--strich); transition: transform var(--t-auf) var(--auf), border-color var(--t-auf) var(--auf); }
.kontur:hover .kontur__strich { animation: laufen 1600ms linear 3; }
}
.kontur:focus-visible .kontur__zug, .kontur.ist-hover .kontur__zug { stroke-dashoffset: 0; transition: stroke-dashoffset var(--t-auf) var(--auf); }
.kontur:focus-visible .kontur__ecke, .kontur.ist-hover .kontur__ecke { width: calc(50% + 1px); height: calc(50% + 1px); transition: width var(--t-auf) var(--auf), height var(--t-auf) var(--auf); }
.kontur:focus-visible::after, .kontur.ist-hover::after { transform: translate(0, 0); border-color: var(--strich); transition: transform var(--t-auf) var(--auf), border-color var(--t-auf) var(--auf); }
.kontur:focus-visible .kontur__strich, .kontur.ist-hover .kontur__strich { animation: laufen 1600ms linear 3; }
@keyframes laufen { to { stroke-dashoffset: -48; } } /* 4 Muster à 12 px je Umlauf, 3 Umläufe, dann Stillstand */
/* Varianten */
[data-variante="ecken"] .kontur__zug { display: none; }
[data-variante="ecken"] .kontur__ecke { display: block; }
[data-variante="doppel"] .kontur__zug { display: none; }
[data-variante="doppel"] .kontur::after { display: block; }
[data-variante="laufend"] .kontur__zug { display: none; }
[data-variante="laufend"] .kontur { border-color: transparent; }
[data-variante="laufend"] .kontur__strich { display: block; }
/* Laden: ein Viertel der Kontur läuft um, für alle Varianten */
.kontur[data-zustand="laedt"] { cursor: progress; }
.kontur[data-zustand="laedt"] .kontur__zug { display: block; stroke-dasharray: .22 .78; animation: umlauf 1200ms linear infinite; transition: none; }
.kontur[data-zustand="laedt"] .kontur__ecke, .kontur[data-zustand="laedt"]::after, .kontur[data-zustand="laedt"] .kontur__strich { display: none; }
@keyframes umlauf { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
.kontur[data-zustand="fertig"] .kontur__zug { display: block; stroke-dashoffset: 0; }
/* Deaktiviert: gestrichelte Grundlinie, kein Zeichnen, Begründung im Text */
.kontur[aria-disabled="true"]:not([data-zustand]) { cursor: not-allowed; border-style: dashed; }
.kontur[aria-disabled="true"]:not([data-zustand]) .kontur__svg, .kontur[aria-disabled="true"]:not([data-zustand]) .kontur__ecke, .kontur[aria-disabled="true"]:not([data-zustand])::after { display: none !important; }
.kontur[aria-disabled="true"]:active { background: transparent; }
[data-variante="laufend"] .kontur[aria-disabled="true"]:not([data-zustand]) { border-color: var(--grund-linie); }
/* ---------- Kollektion (Anthrazit) ---------- */
.auf-dunkel { background: var(--anthrazit); color: var(--weiss); padding: clamp(48px, 7vw, 104px) clamp(20px, 4vw, 56px); }
.auf-dunkel :focus-visible { outline-color: var(--messing); }
.koll-kopf { display: flex; justify-content: space-between; align-items: end; gap: 20px; flex-wrap: wrap; max-width: 1240px; margin: 0 auto; }
.koll-kopf h2 { font: 400 clamp(2rem, 1rem + 3vw, 3.5rem)/1.05 'Bodoni Moda', serif; font-optical-sizing: auto; }
.koll-kopf .versal { color: var(--leise-dunkel); }
.modelle { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; margin: 40px auto 0; max-width: 1240px; background: rgba(255, 255, 255, .14); border: 1px solid rgba(255, 255, 255, .14); }
.modell { background: var(--anthrazit); padding: 28px clamp(18px, 2.4vw, 32px) 32px; display: grid; gap: 6px; align-content: start; }
.modell svg { width: 132px; height: 132px; margin: 4px auto 22px; }
.modell h3 { font: 400 1.5rem/1.1 'Bodoni Moda', serif; }
.modell .ref { color: var(--leise-dunkel); }
.modell .preis { font-variant-numeric: tabular-nums; margin-top: 4px; }
.modell .kontur { margin-top: 18px; justify-self: start; }
.hinweis { color: var(--leise-dunkel); font-size: .875rem; min-height: 1.4em; }
/* ---------- Simulierter Zeiger ---------- */
.zeiger { position: fixed; left: 0; top: 0; z-index: 20; width: 20px; height: 20px; pointer-events: none; display: none; transition: transform 700ms var(--auf); }
.vorfuehrung .zeiger { display: block; }
/* ---------- Pult ---------- */
.pult { position: fixed; left: 16px; bottom: 16px; z-index: 30; display: grid; gap: 8px; padding: 12px 14px; max-width: min(430px, calc(100vw - 32px)); background: rgba(24, 27, 32, .94); color: #f3f2ee; border: 1px solid rgba(255, 255, 255, .14); }
.pult .label { font: 500 11px/1.2 'Geist Mono', monospace; letter-spacing: .14em; text-transform: uppercase; color: rgba(243, 242, 238, .72); }
.pult__knoepfe { display: flex; flex-wrap: wrap; gap: 6px; }
.pult__knoepfe button { min-height: 32px; padding: 0 11px; border: 1px solid rgba(243, 242, 238, .3); background: transparent; color: #f3f2ee; font: 500 11px/1 'Geist Mono', monospace; letter-spacing: .08em; text-transform: uppercase; cursor: pointer; }
.pult__knoepfe button[aria-pressed="true"] { background: #f3f2ee; color: #181b20; }
.pult__knoepfe button:focus-visible { outline: 2px solid var(--messing); outline-offset: 2px; }
.pult__werte { font: 400 11px/1.5 'Geist Mono', monospace; color: rgba(243, 242, 238, .78); }
.vorschau body { overflow: hidden; }
.vorschau .kopf nav, .vorschau .kopf .termin { visibility: hidden; }
/* ---------- Touch: Kontur steht geschlossen (Endzustand) ---------- */
@media (hover: none) {
.kontur__zug { stroke-dashoffset: 0; }
.kontur__ecke { width: calc(50% + 1px); height: calc(50% + 1px); }
.kontur::after { transform: none; border-color: var(--strich); }
}
/* ---------- Schmal ---------- */
@media (max-width: 860px) {
.buehne { grid-template-columns: minmax(0, 1fr); min-height: 0; }
.bild { order: -1; min-height: 340px; }
.bild svg { width: min(100%, 300px); }
.modelle { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 640px) {
.kopf { grid-template-columns: minmax(0, 1fr) auto; }
.kopf nav { display: none; }
.marke { text-align: left; }
.marke b { margin-right: 0; }
.handlung .voll, .handlung .kontur { flex: 1 1 100%; }
.daten { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px 14px; }
.daten dd { font-size: 1.125rem; }
}
/* ---------- Reduzierte Bewegung: kein Zeichnen, Kontur erscheint ganz ---------- */
:root.reduziert .kontur__zug { stroke-dashoffset: 0; opacity: 0; transition: opacity 150ms linear !important; }
:root.reduziert .kontur:hover .kontur__zug, :root.reduziert .kontur:focus-visible .kontur__zug, :root.reduziert .kontur.ist-hover .kontur__zug, :root.reduziert .kontur[data-zustand] .kontur__zug { opacity: 1; }
:root.reduziert .kontur__ecke { transition: none !important; }
:root.reduziert .kontur::after { transition: none !important; }
:root.reduziert .kontur__strich, :root.reduziert .kontur[data-zustand="laedt"] .kontur__zug { animation: none !important; }
:root.reduziert .kontur[data-zustand="laedt"] .kontur__zug { stroke-dasharray: 1 0; opacity: .5; }
:root.reduziert .zeiger { transition: none; }
@media (hover: none) { :root.reduziert .kontur__zug { opacity: 1; } }
@media (forced-colors: active) {
button, [role=button], [role=tab], [role=switch], input, select, textarea, .kontur, .voll { border: 1px solid ButtonText !important; }
.kontur__svg, .kontur__ecke, .kontur::after { display: none !important; }
.kontur:focus-visible, .voll:focus-visible { outline: 2px solid Highlight; }
.kontur[aria-disabled="true"] { border-color: GrayText !important; color: GrayText; }
}
</style>
</head>
<body data-variante="zeichnen">
<header class="kopf">
<nav aria-label="Hauptmenü"><a href="#" class="versal">Kollektion</a><a href="#" class="versal">Manufaktur</a><a href="#" class="versal">Service</a></nav>
<a class="marke" href="#" aria-label="Karner Uhrmacher, Startseite"><b>KARNER</b><small>Uhrmacher · Klagenfurt</small></a>
<a class="termin versal" href="#">Boutique Alter Platz</a>
</header>
<main>
<section class="buehne" aria-labelledby="titel">
<div class="text">
<p class="versal">Neu · Herbst 2026</p>
<h1 id="titel"><span class="unterschneiden">W</span>örth 38 <i>Seeblau</i></h1>
<p class="satz">Handaufzug, Kaliber K 61 aus der eigenen Werkstatt. Gehäuse aus Edelstahl, Zifferblatt in Grand-Feu-Email, gebrannt in drei Schichten.</p>
<div class="handlung">
<a class="voll versal" href="#">Termin in der Boutique</a>
<a class="kontur" href="#kaliber" id="kaliber-knopf">
<span class="versal">Kaliber ansehen</span>
<svg class="kontur__svg" aria-hidden="true" focusable="false"><rect class="kontur__zug" width="100%" height="100%" pathLength="1"/><rect class="kontur__strich" width="100%" height="100%"/></svg>
<span class="kontur__ecke kontur__ecke--ol" aria-hidden="true"></span><span class="kontur__ecke kontur__ecke--or" aria-hidden="true"></span><span class="kontur__ecke kontur__ecke--ul" aria-hidden="true"></span><span class="kontur__ecke kontur__ecke--ur" aria-hidden="true"></span>
</a>
</div>
<dl class="daten">
<dt class="versal">Durchmesser</dt><dd>38,0 mm</dd>
<dt class="versal">Gangreserve</dt><dd>52 h</dd>
<dt class="versal">Wasserdicht</dt><dd>5 bar</dd>
</dl>
</div>
<figure class="bild">
<svg viewBox="0 0 400 400" role="img" aria-label="Uhr Wörth 38 mit seeblauem Zifferblatt, Lederband">
<defs>
<radialGradient id="email" cx="42%" cy="36%" r="70%"><stop offset="0" stop-color="#4b6b8a"/><stop offset=".6" stop-color="#2f4a66"/><stop offset="1" stop-color="#1f3349"/></radialGradient>
<linearGradient id="stahl" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#f1f2f3"/><stop offset=".45" stop-color="#b9bdc2"/><stop offset=".55" stop-color="#d9dcdf"/><stop offset="1" stop-color="#8d9298"/></linearGradient>
<linearGradient id="leder" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#3a2a22"/><stop offset=".5" stop-color="#55402f"/><stop offset="1" stop-color="#3a2a22"/></linearGradient>
</defs>
<rect x="150" y="0" width="100" height="120" rx="10" fill="url(#leder)"/>
<rect x="150" y="280" width="100" height="120" rx="10" fill="url(#leder)"/>
<g stroke="#6b5240" stroke-width="1" stroke-dasharray="3 4" opacity=".6"><path d="M158 0v112M242 0v112M158 288v112M242 288v112"/></g>
<path d="M138 96h124l-10 30H148Zm0 208h124l-10-30H148Z" fill="url(#stahl)"/>
<circle cx="200" cy="200" r="112" fill="url(#stahl)"/>
<circle cx="200" cy="200" r="104" fill="#e6e8ea"/>
<circle cx="200" cy="200" r="98" fill="url(#email)"/>
<rect x="309" y="190" width="14" height="20" rx="3" fill="url(#stahl)"/>
<g id="index" fill="#d8bf8f"></g>
<text x="200" y="152" text-anchor="middle" font-family="Bodoni Moda, serif" font-size="13" letter-spacing="3.5" fill="#efe6d4">KARNER</text>
<text x="200" y="236" text-anchor="middle" font-family="Instrument Sans, sans-serif" font-size="7.5" letter-spacing="2" fill="#c9d3dd">HANDAUFZUG · K 61</text>
<circle cx="200" cy="268" r="17" fill="none" stroke="#c9d3dd" stroke-width=".8" opacity=".7"/>
<path d="M200 268V254" stroke="#efe6d4" stroke-width="1.2"/>
<g stroke-linecap="round">
<path d="M200 200 152 168" stroke="#efe6d4" stroke-width="5"/>
<path d="M200 200 262 138" stroke="#efe6d4" stroke-width="3.2"/>
</g>
<circle cx="200" cy="200" r="6" fill="#efe6d4"/><circle cx="200" cy="200" r="2" fill="#a88a5a"/>
</svg>
<figcaption class="versal">Ref. K61.38.04 · ab 7 900 €</figcaption>
</figure>
</section>
<section class="auf-dunkel" aria-labelledby="koll-titel">
<div class="koll-kopf">
<h2 id="koll-titel">Die Kollektion</h2>
<p class="versal">Kollektion 2026 · ein Kaliber</p>
</div>
<div class="modelle">
<article class="modell">
<svg viewBox="0 0 132 132" aria-hidden="true"><circle cx="66" cy="66" r="58" fill="#c9ccd0"/><circle cx="66" cy="66" r="52" fill="#f2efe8"/><g stroke="#23282f" stroke-width="1.5"><path d="M66 18v8M66 106v8M18 66h8M106 66h8"/></g><path d="M66 66 46 52M66 66l26-24" stroke="#23282f" stroke-width="2.4" stroke-linecap="round"/></svg>
<h3>Wörth 38 Elfenbein</h3>
<p class="ref versal">Ref. K61.38.01</p>
<p class="preis">7 400 €</p>
<a class="kontur" href="#">
<span class="versal">Details</span>
<svg class="kontur__svg" aria-hidden="true" focusable="false"><rect class="kontur__zug" width="100%" height="100%" pathLength="1"/><rect class="kontur__strich" width="100%" height="100%"/></svg>
<span class="kontur__ecke kontur__ecke--ol" aria-hidden="true"></span><span class="kontur__ecke kontur__ecke--or" aria-hidden="true"></span><span class="kontur__ecke kontur__ecke--ul" aria-hidden="true"></span><span class="kontur__ecke kontur__ecke--ur" aria-hidden="true"></span>
</a>
</article>
<article class="modell">
<svg viewBox="0 0 132 132" aria-hidden="true"><circle cx="66" cy="66" r="58" fill="#c9ccd0"/><circle cx="66" cy="66" r="52" fill="#1d2329"/><circle cx="66" cy="88" r="10" fill="none" stroke="#a88a5a" stroke-width="1"/><path d="M60 84a7 7 0 1 0 9 9 6 6 0 0 1-9-9Z" fill="#d8bf8f"/><g stroke="#d8bf8f" stroke-width="1.5"><path d="M66 18v8M18 66h8M106 66h8"/></g><path d="M66 66 50 44M66 66l30 4" stroke="#efe6d4" stroke-width="2.4" stroke-linecap="round"/></svg>
<h3>Lend 40 Mondphase</h3>
<p class="ref versal">Ref. K64.40.02</p>
<p class="preis">11 800 €</p>
<button class="kontur" type="button" aria-disabled="true" aria-describedby="lend-hinweis">
<span class="versal">Ab März 2027</span>
<svg class="kontur__svg" aria-hidden="true" focusable="false"><rect class="kontur__zug" width="100%" height="100%" pathLength="1"/><rect class="kontur__strich" width="100%" height="100%"/></svg>
</button>
<p class="hinweis" id="lend-hinweis">Die erste Serie ist vergeben. Bestellbar ab März 2027.</p>
</article>
<article class="modell">
<svg viewBox="0 0 132 132" aria-hidden="true"><rect x="30" y="18" width="76" height="98" fill="#1b1f25" stroke="rgba(255,255,255,.18)"/><rect x="26" y="14" width="76" height="98" fill="#f2efe8"/><text x="64" y="52" text-anchor="middle" font-family="Bodoni Moda, serif" font-size="9" letter-spacing="2.4" fill="#23282f">KARNER</text><path d="M44 60h40" stroke="#a88a5a" stroke-width=".8"/><text x="64" y="74" text-anchor="middle" font-family="Instrument Sans, sans-serif" font-size="5.5" letter-spacing="1.4" fill="#62676e">KOLLEKTION 2026</text><circle cx="64" cy="92" r="7" fill="none" stroke="#23282f" stroke-width=".9"/><path d="M64 92v-4.5M64 92l3 1.6" stroke="#23282f" stroke-width=".9"/></svg>
<h3>Katalog 2026</h3>
<p class="ref versal">36 Seiten · alle Referenzen</p>
<p class="preis">Als PDF, 4,2 MB</p>
<button class="kontur" type="button" id="katalog">
<span class="versal" data-text>Katalog anfordern</span>
<svg class="kontur__svg" aria-hidden="true" focusable="false"><rect class="kontur__zug" width="100%" height="100%" pathLength="1"/><rect class="kontur__strich" width="100%" height="100%"/></svg>
<span class="kontur__ecke kontur__ecke--ol" aria-hidden="true"></span><span class="kontur__ecke kontur__ecke--or" aria-hidden="true"></span><span class="kontur__ecke kontur__ecke--ul" aria-hidden="true"></span><span class="kontur__ecke kontur__ecke--ur" aria-hidden="true"></span>
</button>
<p class="hinweis" id="katalog-status" role="status" aria-live="polite"></p>
</article>
</div>
</section>
</main>
<div class="pult">
<p class="label" id="pult-titel">Variante</p>
<div class="pult__knoepfe" role="group" aria-labelledby="pult-titel">
<button type="button" data-v="zeichnen" aria-pressed="true">Zeichnen</button>
<button type="button" data-v="ecken" aria-pressed="false">Ecken</button>
<button type="button" data-v="doppel" aria-pressed="false">Doppel</button>
<button type="button" data-v="laufend" aria-pressed="false">Laufend</button>
</div>
<p class="pult__werte" data-werte></p>
</div>
<svg class="zeiger" data-zeiger viewBox="0 0 20 20" aria-hidden="true"><path d="M3 2 16 10l-6.2 1.3-3 5.8Z" fill="#23282f" stroke="#fff" stroke-width="1.3" stroke-linejoin="round"/></svg>
<script>
(() => {
const html = document.documentElement, body = document.body;
const VARIANTEN = ['zeichnen', 'ecken', 'doppel', 'laufend'];
const WERTE = {
zeichnen: 'rect pathLength 1 · dashoffset 1 → 0 · auf 480 ms cubic-bezier(.16,1,.3,1) · zu 310 ms cubic-bezier(.7,0,.84,0) · Strich 1,5 px',
ecken: 'Ecken 12 px → 50 % + 1 px · auf 480 ms · zu 310 ms · Strich 1,5 px',
doppel: 'Zweite Kontur Messing, Versatz 3 px → 0 · auf 480 ms · zu 310 ms',
laufend: 'Striche 7 / 5 px · 1600 ms je Umlauf, linear, 3 Umläufe, dann Stillstand'
};
/* Indizes des großen Zifferblatts */
const idx = document.getElementById('index');
for (let i = 0; i < 12; i++) {
if (i === 6) continue; /* Platz für die kleine Sekunde */
const r = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
const lang = i % 3 === 0;
r.setAttribute('x', lang ? 198.5 : 199); r.setAttribute('y', 108); r.setAttribute('width', lang ? 3 : 2); r.setAttribute('height', lang ? 16 : 10);
r.setAttribute('transform', `rotate(${i * 30} 200 200)`);
idx.appendChild(r);
}
/* Variante aus der URL */
const knoepfe = [...document.querySelectorAll('.pult__knoepfe button')];
const werteFeld = document.querySelector('[data-werte]');
const ausUrl = new URLSearchParams(location.search).get('v');
let aktuell = VARIANTEN.includes(ausUrl) ? ausUrl : 'zeichnen';
function variante(v, url) {
aktuell = v; body.dataset.variante = v;
knoepfe.forEach(k => k.setAttribute('aria-pressed', String(k.dataset.v === v)));
werteFeld.textContent = WERTE[v];
if (url) { const u = new URL(location.href); u.searchParams.set('v', v); history.replaceState(history.state, '', u); }
}
knoepfe.forEach(k => k.addEventListener('click', () => variante(k.dataset.v, true)));
variante(aktuell);
/* Links der Demo führen nirgends hin; deaktivierter Link bleibt ohne Wirkung */
document.querySelectorAll('a[href^="#"], .kontur[aria-disabled="true"]').forEach(a => a.addEventListener('click', e => e.preventDefault()));
/* Katalog: Laden → fertig */
const katalog = document.getElementById('katalog');
const kText = katalog.querySelector('[data-text]');
const kStatus = document.getElementById('katalog-status');
let uhrKatalog = null;
function anfordern() {
if (katalog.dataset.zustand === 'laedt') return;
if (katalog.dataset.zustand === 'fertig') { kStatus.textContent = 'Der Katalog öffnet sich in einem neuen Fenster.'; return; }
katalog.dataset.zustand = 'laedt'; katalog.setAttribute('aria-disabled', 'true');
kText.textContent = 'Wird vorbereitet'; kStatus.textContent = 'Der Katalog wird vorbereitet.';
clearTimeout(uhrKatalog);
uhrKatalog = setTimeout(() => {
katalog.dataset.zustand = 'fertig'; katalog.removeAttribute('aria-disabled');
kText.textContent = 'Katalog öffnen'; kStatus.textContent = 'Fertig. Katalog 2026, PDF, 4,2 MB.';
}, 2200);
}
katalog.addEventListener('click', anfordern);
function katalogZurueck() {
clearTimeout(uhrKatalog); delete katalog.dataset.zustand; katalog.removeAttribute('aria-disabled');
kText.textContent = 'Katalog anfordern'; kStatus.textContent = '';
}
/* Vorführung: gedachter Zeiger fährt auf „Kaliber ansehen“, verweilt, geht wieder */
const zeiger = document.querySelector('[data-zeiger]');
const ziel = document.getElementById('kaliber-knopf');
let uhren = [], laeuft = false;
const spaeter = (fn, ms) => uhren.push(setTimeout(fn, ms));
function stopp() { uhren.forEach(clearTimeout); uhren = []; }
function zeigerAuf(el, dx, dy) { const r = el.getBoundingClientRect(); zeiger.style.transform = `translate(${r.left + r.width * dx}px, ${r.top + r.height * dy}px)`; }
function grundzustand() { stopp(); document.querySelectorAll('.kontur.ist-hover').forEach(k => k.classList.remove('ist-hover')); katalogZurueck(); }
function runde() {
grundzustand();
zeiger.style.transition = 'none';
zeiger.style.transform = `translate(${innerWidth * .62}px, ${innerHeight + 20}px)`;
zeiger.getBoundingClientRect(); zeiger.style.transition = '';
spaeter(() => zeigerAuf(ziel, .7, .6), 150);
spaeter(() => ziel.classList.add('ist-hover'), 750);
spaeter(() => zeigerAuf(ziel, .3, .62), 1900);
spaeter(() => { ziel.classList.remove('ist-hover'); zeigerAuf(ziel, .5, 2.4); }, 2900);
spaeter(() => zeigerAuf(ziel, .5, .55), 3500);
spaeter(() => ziel.classList.add('ist-hover'), 4100);
spaeter(() => { ziel.classList.remove('ist-hover'); zeiger.style.transform = `translate(${innerWidth * .8}px, ${innerHeight * .8}px)`; }, 5600);
spaeter(runde, 6400);
}
function neustart() { laeuft = false; html.classList.remove('vorfuehrung'); grundzustand(); variante(aktuell); }
function vorfuehren() { stopp(); laeuft = true; html.classList.add('vorfuehrung'); runde(); }
function pause() { stopp(); laeuft = false; }
function weiter() { if (!laeuft && html.classList.contains('vorfuehrung')) vorfuehren(); }
})();
</script>
</body>
</html>