import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react' import { createRegie } from '../../../lib/hustlerRegie.js' // Regie-Ebene der Hochhaus-Welt (Spec E9): zeichnet die Effekte der Regie (src/lib/hustlerRegie.js) als absolut positionierte Elemente in // Weltkoordinaten über der Welt. Gehört als Kind in die skalierte Bühne (Wurzel mit position: absolute; inset: 0), nimmt keine Klicks an und // ist für Bildschirmleser versteckt (der Status steht schon an den Figuren). Keine Props: die Seite spielt über das Handle // { push(events), flush() } (regieRef) neue Ereignisse vor. Kein CSS-Import und keine Bilder: die Regeln stehen in styles/hustler/hustler-regie.css. // Ein Zeitgeber läuft nur, solange Effekte sichtbar sind oder gleich starten (setTimeout bis zur nächsten Änderung, kein Dauer-Intervall); // beim Abbau werden Zeitgeber und Beobachter aufgeräumt. „Weniger Bewegung“ (matchMedia, auch bei Änderung) zeigt nur Blasen, // ein verdeckter Tab leert die Ebene (visibilitychange) und nimmt keine neuen Ereignisse an. const WENIGER_BEWEGUNG = '(prefers-reduced-motion: reduce)' // Fenster und Dokument nur, wo es sie gibt (Tests unter Node, Vorschau ohne Browser) function bewegungMedium() { try { return typeof window !== 'undefined' && typeof window.matchMedia === 'function' ? window.matchMedia(WENIGER_BEWEGUNG) : null } catch { return null } } const verdeckt = () => typeof document !== 'undefined' && document.hidden === true // Gleiche Effekte (nach Kennung und Reihenfolge)? Dann bleibt der Zustand unverändert und React zeichnet nicht neu. const gleich = (a, b) => a.length === b.length && a.every((e, i) => e.id === b[i].id) // Regie, Zeitgeber und Handle: einmal pro Ebene, gehalten in einem Ref (kein Zustand, damit Aufrufe von außen nie veraltete Werte sehen) function baueSteuerung(setEffekte) { const s = { regie: createRegie(), timer: null, lebt: false } const stoppen = () => { if (s.timer !== null) { clearTimeout(s.timer) s.timer = null } } // Liste neu holen, zeigen und den Zeitgeber auf die nächste Änderung stellen (nur wenn etwas läuft oder wartet) const aktualisieren = () => { stoppen() if (!s.lebt) return const liste = s.regie.tick() setEffekte((alt) => (gleich(alt, liste) ? alt : liste)) const bis = s.regie.naechsteIn() if (bis !== null) s.timer = setTimeout(aktualisieren, Math.max(1, bis)) } s.aktualisieren = aktualisieren s.stoppen = stoppen s.push = (events) => { if (!s.lebt || verdeckt()) return s.regie.push(events) aktualisieren() } s.flush = () => { s.regie.flush() aktualisieren() } s.setReduziert = (wert) => { s.regie.setReduziert(wert) aktualisieren() } return s } // Lage in Weltkoordinaten (Mittelpunkt bzw. Spitze der Blase) und Dauer für die CSS const stil = (e) => ({ left: `${e.x}px`, top: `${e.y}px`, '--dauer': `${e.dauer}ms` }) function effektKnoten(e) { switch (e.art) { case 'blase': return (