{
 "$schema": "../../schema.json",
 "schema": "fundus/1",
 "id": "button-zustaende",
 "name": "Button-Zustandsfolge (Idle → Hover → Press → Loading → Erfolg)",
 "name_en": "Stateful button / submit button states",
 "kurz": "Ein Buchungs-Button zeigt Hover, Druck, Laden und Erfolg direkt an sich selbst, ohne zu springen.",
 "kategorie": "Mikroanimationen",
 "kategorie_slug": "mikroanimationen",
 "kategorie_nr": 9,
 "bau_chat": "C4",
 "prioritaet": "A",
 "trendstatus": "etabliert",
 "belegstatus": "belegt",
 "website_belege": 3,
 "stimmung": [
  "präzise",
  "verspielt"
 ],
 "stimmung_roh": [
  "funktional",
  "präzise",
  "freundlich"
 ],
 "technik": [
  "css",
  "motion",
  "vanilla-js",
  "svg"
 ],
 "technik_gruppen": [
  "css",
  "js",
  "canvas",
  "andere"
 ],
 "leistungskosten": "niedrig",
 "steckbrief": {
  "beschreibung": "Ein Button zeigt seinen Zustand selbst an: Ruhe, Hover (Farbe), Druck (scale ~0,97), Laden (Label weicht einem Spinner oder Fortschrittsring), Erfolg (Häkchen zeichnet sich, Farbe wird grün) und zurück. Bei Fehler: Rot und kurzes Schütteln. Ändert sich das Label, animiert die Breite weich mit, damit nichts springt. Die Wechsel dauern 150 bis 300 ms, der Erfolg bleibt etwa 2 s stehen.",
  "warum_es_wirkt": "Die Antwort auf „Hat es geklappt?“ erscheint genau dort, wo geklickt wurde. Das senkt Unsicherheit und verhindert Doppelklicks.",
  "wann_einsetzen": "Formular absenden, In den Warenkorb, Speichern, Newsletter-Anmeldung, Kopieren, Downloads.",
  "wann_nicht": "Bei Navigation, dort gibt der Seitenwechsel das Feedback. Bei Antworten unter etwa 100 ms keinen Ladezustand zeigen, sonst flackert es. Loading erst nach rund 300 ms einblenden.",
  "varianten": [
   "Label: Text blendet aus (scale .9), Spinner erscheint im Button, danach Häkchen und neues Label (Glossier, Aurélien Vigne)",
   "Kreis: Button morpht zum Kreis mit Fortschrittsring, dann Erfolg (Codrops CircularProgressButton nach Colin Garvin)",
   "Fehler: Rot plus horizontales Schütteln (3 bis 4 Ausschläge, ±4 bis 6 px, 300 bis 400 ms)",
   "Label-Wechsel mit Blur-Überblendung und gezeichnetem Häkchen (Motion Copy Button)",
   "Punkte-Morph (Motion Dots Morph Button)",
   "Gedrückt halten statt klicken (siehe hold-to-confirm)"
  ],
  "umsetzung": "Zustand als data-state=\"idle|loading|erfolg|fehler\" am Button. CSS steuert die gestapelten Ebenen (Label, Spinner, Icon) über opacity und scale. Breite: per JS aus der Ebene des Zielzustands messen und als width mit transition setzen, oder interpolate-size: allow-keywords mit width: auto. Loading erst nach 300 ms zeigen (setTimeout) und mindestens 500 ms halten. Häkchen: <path pathLength=\"1\"> mit stroke-dasharray 1 und stroke-dashoffset 1 → 0 in 300 ms. Während Loading aria-disabled=\"true\" statt disabled, damit der Fokus bleibt; Klicks per pointer-events: none sperren. Status in einer aria-live=\"polite\"-Region ansagen.",
  "mobil": "Gleiche Folge, nur ohne Hover. Das Druck-Feedback ist auf Touch besonders wichtig.",
  "reduzierte_bewegung": "Zustände nur über Farbe, Icon und Text wechseln, ohne Morph und ohne Schütteln. Spinner langsamer drehen lassen (siehe Spinner).",
  "barrierefreiheit": "Status per aria-live ansagen, etwa „Wird in den Warenkorb gelegt …“ und „Liegt im Warenkorb“. Fokus nicht verlieren, also kein display: none und kein disabled am Button. Farbe nie allein: immer Icon und Text dazu."
 },
 "messwerte": {
  "easing": "ease-in-out (Glossier); ease (Aurélien Vigne)",
  "dauer_ms": "Label-Wechsel 200 (Glossier); Loader-Einblendung 150, Spinner-Umdrehung 600 (Aurélien Vigne)",
  "stagger_ms": "",
  "distanz": "Label scale 1 → .9 beim Laden (Glossier)",
  "sonstiges": "Disabled opacity .4; Klickschutz pointer-events none während Loading",
  "herkunft": "beobachtet",
  "quelle": "https://www.glossier.com/cdn/shop/t/1479/assets/style.css?v=73460793288505732281793195409"
 },
 "kurve": {
  "bezier": [
   0.42,
   0.0,
   0.58,
   1.0
  ],
  "css": "cubic-bezier(0.42, 0, 0.58, 1)",
  "name": "ease-in-out",
  "dauer_ms": 200
 },
 "gesehen_bei": [
  {
   "seite": "Codrops CircularProgressButton (Repo)",
   "url": "https://github.com/codrops/CircularProgressButton",
   "auszeichnung": "",
   "beobachtung": "Umsetzung des Submit-Button-Konzepts von Colin Garvin (Button → Kreis → Fortschritt → Erfolg).",
   "art": "demo"
  },
  {
   "seite": "Motion Examples: Create button / Dots morph button",
   "url": "https://motion.dev/examples/react-dots-morph-button",
   "auszeichnung": "",
   "beobachtung": "Offizielle Beispiele für morphende Button-Zustände.",
   "art": "demo"
  },
  {
   "seite": "Motion Examples: Copy button",
   "url": "https://motion.dev/examples/react-copy-button",
   "auszeichnung": "",
   "beobachtung": "Icon-Tausch mit Blur-Übergängen und Häkchen per pathLength (laut Beschreibung).",
   "art": "demo"
  },
  {
   "seite": "Collins",
   "url": "https://www.wearecollins.com/",
   "auszeichnung": "",
   "beobachtung": "E-Mail-Button (Nuxt-Komponente 'Email'): Attribut status wechselt idle → success ('Copied') → nach 2000 ms zurück zu idle; drei gestapelte Label-Ebenen (cta/email/success).",
   "art": "website"
  },
  {
   "seite": "Glossier",
   "url": "https://www.glossier.com/",
   "auszeichnung": "",
   "beobachtung": "Live geprüft 31.10.2026 (HTML/Bundle per curl abgerufen): style.css (Shopify-Theme): .btn__label mit transition opacity/transform .2s ease-in-out; .btn.is-loading blendet das Label aus (opacity 0, scale .9) und zeigt per :before ein loading.svg; JS-Module (incrementer, Produkt-Config) setzen die Klasse is-loading beim In-den-Warenkorb-Legen.",
   "art": "website"
  },
  {
   "seite": "Aurélien Vigne – Portfolio",
   "url": "https://aurelienvigne.com/",
   "auszeichnung": "",
   "beobachtung": "Live geprüft 31.10.2026 (HTML/Bundle per curl abgerufen): index.72294c06.css: Komponente .c-button-base mit Loader-Ebene (.c-button-base__loader, absolut über dem Label, opacity per --btn-loader-opacity, transition .15s ease); .is-loading setzt die Variable auf 1 und sperrt pointer-events/user-select; Spinner-Icon dreht spin .6s linear infinite; .is-disabled opacity .4.",
   "art": "website"
  }
 ],
 "tutorials": [
  {
   "titel": "Codrops CircularProgressButton (Repo)",
   "url": "https://github.com/codrops/CircularProgressButton"
  }
 ],
 "kombinierbar_mit": [
  "druck-feedback",
  "kopieren-feedback",
  "spinner-ladeindikator",
  "token-federnd-verspielt"
 ],
 "urls": {
  "detail": "https://cecodesign.de/lab/fundus/mikroanimationen/button-zustaende/",
  "meta": "https://cecodesign.de/lab/fundus/mikroanimationen/button-zustaende/meta.json",
  "demo": "https://cecodesign.de/lab/fundus/mikroanimationen/button-zustaende/demo"
 },
 "herkunft": {
  "quelldateien": [
   "hover.json",
   "patch-belege-1.json",
   "r3-belege-a.json"
  ],
  "pruefnotizen": [
   "patch-belege-1.json: Nur 1 Produktionsbeleg für eine Zustandsfolge (ohne Loading-Phase). Volle Folge Idle→Loading→Erfolg nicht im ausgelieferten Code gefunden.",
   "r3-belege-a.json (Runde 3, 31.10.2026): Beide Belege decken nur Idle → Loading ab; ein produktiver Erfolgs-Zustand mit Häkchen wurde nicht gefunden (Collins bleibt der einzige Erfolgs-Beleg)."
  ]
 },
 "bau": {
  "status": "fertig",
  "kurz": "Ein Buchungs-Button zeigt Hover, Druck, Laden und Erfolg direkt an sich selbst, ohne zu springen.",
  "demo": true,
  "abhaengigkeiten": [],
  "werte": {
   "easing": "cubic-bezier(0.42, 0, 0.58, 1) (ease-in-out) für alle Zustandswechsel; Spinner linear",
   "label_wechsel_ms": 200,
   "label_laden": "opacity 0, scale .9",
   "loader_einblendung_ms": 150,
   "spinner_umdrehung_ms": 600,
   "hover": "Fläche #f2e6d4 → #fff6e8, Pfeil +3 px, 200 ms",
   "press": "scale .97, 150 ms",
   "breite_morph_ms": 300,
   "kreis_variante": "Breite → Höhe (60 px Kreis), Fortschrittsring stroke 2.5, Dauer = Antwortzeit − 500 ms, Start 200 ms verzögert",
   "haeckchen": "pathLength 1, stroke-dashoffset 1 → 0 in 300 ms, 120 ms verzögert",
   "erfolg_farbe": "#3d8a5f",
   "fehler_farbe": "#e2583e",
   "shake": "4 Ausschläge ±6 px in 360 ms",
   "loading_verzoegerung_ms": 300,
   "loading_mindestens_ms": 500,
   "erfolg_halten_ms": 2000,
   "simulierte_antwort_ms": "Label/Fehler 1500, Kreis 2200",
   "klickschutz": "pointer-events none und aria-disabled true während Loading"
  },
  "ausloeser": "klick",
  "touch": "Gleiche Folge ohne Hover-Stufe (in der Zustandsleiste als entfällt markiert); Druck-Feedback scale .97 über :active, kein Tap-Highlight.",
  "reduziert": "Kein Breiten-Morph (feste Breite für alle Zustände), kein Kreis, kein Shake, kein Zeichnen des Häkchens; Zustände wechseln nur per Farbe, Icon und Text mit 150 ms Überblendung. Spinner dreht langsamer (1,2 s). Fehler zusätzlich mit rotem Außenring.",
  "korrekturen": {
   "beschreibung": "Ein Button zeigt seinen Zustand selbst an: Ruhe, Hover (Farbe), Druck (scale ~0,97), Laden (Label weicht einem Spinner oder Fortschrittsring), Erfolg (Häkchen zeichnet sich, Farbe wird grün) und zurück. Bei Fehler: Rot und kurzes Schütteln. Ändert sich das Label, animiert die Breite weich mit, damit nichts springt. Die Wechsel dauern 150 bis 300 ms, der Erfolg bleibt etwa 2 s stehen.",
   "warum_es_wirkt": "Die Antwort auf „Hat es geklappt?“ erscheint genau dort, wo geklickt wurde. Das senkt Unsicherheit und verhindert Doppelklicks.",
   "wann_einsetzen": "Formular absenden, In den Warenkorb, Speichern, Newsletter-Anmeldung, Kopieren, Downloads.",
   "wann_nicht": "Bei Navigation, dort gibt der Seitenwechsel das Feedback. Bei Antworten unter etwa 100 ms keinen Ladezustand zeigen, sonst flackert es. Loading erst nach rund 300 ms einblenden.",
   "varianten": [
    "Label: Text blendet aus (scale .9), Spinner erscheint im Button, danach Häkchen und neues Label (Glossier, Aurélien Vigne)",
    "Kreis: Button morpht zum Kreis mit Fortschrittsring, dann Erfolg (Codrops CircularProgressButton nach Colin Garvin)",
    "Fehler: Rot plus horizontales Schütteln (3 bis 4 Ausschläge, ±4 bis 6 px, 300 bis 400 ms)",
    "Label-Wechsel mit Blur-Überblendung und gezeichnetem Häkchen (Motion Copy Button)",
    "Punkte-Morph (Motion Dots Morph Button)",
    "Gedrückt halten statt klicken (siehe hold-to-confirm)"
   ],
   "umsetzung": "Zustand als data-state=\"idle|loading|erfolg|fehler\" am Button. CSS steuert die gestapelten Ebenen (Label, Spinner, Icon) über opacity und scale. Breite: per JS aus der Ebene des Zielzustands messen und als width mit transition setzen, oder interpolate-size: allow-keywords mit width: auto. Loading erst nach 300 ms zeigen (setTimeout) und mindestens 500 ms halten. Häkchen: <path pathLength=\"1\"> mit stroke-dasharray 1 und stroke-dashoffset 1 → 0 in 300 ms. Während Loading aria-disabled=\"true\" statt disabled, damit der Fokus bleibt; Klicks per pointer-events: none sperren. Status in einer aria-live=\"polite\"-Region ansagen.",
   "mobil": "Gleiche Folge, nur ohne Hover. Das Druck-Feedback ist auf Touch besonders wichtig.",
   "reduzierte_bewegung": "Zustände nur über Farbe, Icon und Text wechseln, ohne Morph und ohne Schütteln. Spinner langsamer drehen lassen (siehe Spinner).",
   "barrierefreiheit": "Status per aria-live ansagen, etwa „Wird in den Warenkorb gelegt …“ und „Liegt im Warenkorb“. Fokus nicht verlieren, also kein display: none und kein disabled am Button. Farbe nie allein: immer Icon und Text dazu."
  },
  "geprueft": "01.11.2026",
  "notiz": "Kursbuchung einer Keramikwerkstatt (Werkstatt Scherbe, erfunden) auf dunklem Ochsenblut statt einer weiteren Café-Produktseite. Umschalter für drei Varianten: Label (Glossier-Werte), Kreis mit Fortschrittsring, Fehler mit Shake. Eine Zustandsleiste unter dem Button zeigt Idle, Hover, Press, Loading und Erfolg/Fehler live, der Terminzähler im Kopf zählt mit. Serverantwort simuliert (1500 bzw. 2200 ms). Die Vorschau führt einen gezeichneten Zeiger heran und wechselt pro Runde die Variante. Bilder 01.11.2026: eigene Bilder statt Portfolio."
 }
}
