/* EIPME KI-Spot — 1200×1200, ~14.4s, comic style. Gleiche Figur/Werkstatt wie Cyber-Spot. Reads Stage/Sprite/useTime/useSprite/Easing/interpolate/clamp from window (animations.jsx). */ const { Stage, Sprite, useTime, useSprite, Easing, interpolate, clamp } = window; const R = React; /* ---------- palette (EIPME tokens, literal values) ---------- */ const C = { blue: "#1F5EFF", blue600: "#0A47E0", blue300: "#8AA8FF", blue100: "#D9E4FF", blue50: "#EEF3FF", orange: "#FF7A1A", orange300: "#FFA15C", orange100: "#FFDEC2", red: "#FF3B47", red600: "#E51F2D", red100: "#FFD6DA", ink: "#0C0E13", ink700: "#1C202A", ink500: "#3D434F", ink300: "#858B98", ink100: "#D6D9DF", paper: "#FAFBFC", white: "#FFFFFF", sky: "#EEF3FF", skin: "#FFD9B8", skinShade: "#F2BE93", cheek: "#FFB08A", }; const INK = C.ink; const GEIST = "'Geist', system-ui, sans-serif"; const MONO = "'Geist Mono', ui-monospace, monospace"; /* ============================================================ KOPF — shared comic head, origin = neck-base center. expression: grin | smile | wink | tired | talk | relaxed | shock ============================================================ */ function Kopf({ expression = "grin", blink = 0, talkPhase = 0 }) { const shocked = expression === "shock"; let eyes; if (expression === "tired") { eyes = ( ); } else if (expression === "relaxed") { eyes = ( ); } else if (expression === "wink") { eyes = ( ); } else if (shocked) { eyes = ( ); } else { const closed = blink > 0.5; eyes = closed ? ( ) : ( ); } let mouth; if (expression === "tired") { mouth = ; } else if (expression === "talk") { const open = 5 + 11 * Math.abs(Math.sin(talkPhase)); mouth = ; } else if (expression === "smile" || expression === "relaxed") { mouth = ; } else if (shocked) { mouth = ; } else { mouth = ; } const brows = expression === "tired" ? ( ) : shocked ? ( ) : ( ); return ( {!shocked && } {!shocked && } {brows} {eyes} {mouth} {/* Mütze */} E ); } /* ============================================================ STEHENDE FIGUR — für Outro (pose 'wave'), 360×520 viewBox. ============================================================ */ function StandHandwerker({ expression = "grin", blink = 0, bob = 0, waveA = 0 }) { return ( {/* linker Arm entspannt, rechter winkt */} ); } /* ============================================================ INNENRAUM — Werkstatt von innen, 1200×1200. sky: 'night' | 'day' — Uhr, Fenster, Pegboard, Lampe, Tisch, Papierstapel (stackP skaliert), sitzende Figur. ============================================================ */ function Stapel({ x, w, h, p, t, seed }) { const n = Math.max(2, Math.round(h / 12)); const sheets = []; for (let i = 0; i < n; i++) { sheets.push( ); } const wob = Math.sin(t * 2 + seed) * 0.9 * p; return ( {sheets} ); } function FlySheet({ x0, y0, dir, p }) { if (p <= 0) return null; const f = Easing.easeOutCubic(clamp(p, 0, 1)); const dx = dir * (120 + 180 * f); const dy = -160 * f + 330 * f * f; return ( ); } function SitzChar({ expression, blink = 0, talkPhase = 0, bob = 0, slumpP = 0, leanP = 0, armPose = "table", phoneUpP = 0 }) { const lean = -9 * leanP; const phoneRot = 76 * (1 - Easing.easeOutBack(clamp(phoneUpP, 0, 1))); let arms; if (armPose === "phone") { arms = ( 0.05 ? 1 : 0}> ); } else if (armPose === "lean") { arms = ( ); } else { arms = ( ); } return { lean, arms }; } function Innenraum({ sky = "night", clockH = 275, clockM = 60, stackP = 1, floorPaperO = 1, wobT = 0, char, sheets = null }) { const night = sky === "night"; const wall = night ? "#DCE4F6" : C.blue50; const floor = night ? "#C6CBD8" : C.ink100; const { lean, arms } = SitzChar(char); const headTilt = 7 * (char.slumpP || 0) - 3 * (char.leanP || 0); return ( {/* Wand + Boden */} {/* Fenster */} {night ? ( ) : ( )} {/* Pegboard mit Werkzeug */} {/* Hammer */} {/* Schraubenschlüssel */} {/* Uhr */} {/* Lampe */} {night && } {/* Stuhllehne */} {/* Figur (Torso + Kopf) */} {/* Tisch */} {/* Papierstapel */} {/* lose Blätter am Boden */} {sheets} {/* Arme (vor dem Tisch) */} {arms} ); } /* ---------- Sprechblase ---------- */ function SpeechBubble({ x, y, w, pop, text, tailX = 64, fontSize = 54 }) { const s = Easing.easeOutBack(clamp(pop, 0, 1)); return (
{text}
); } /* ---------- KI-Orb — freundlicher Assistent ---------- */ function Orb({ pop, t, size = 130 }) { const s = Easing.easeOutBack(clamp(pop, 0, 1)); if (s <= 0) return null; const bobY = Math.sin(t * 2.4) * 8; const blinkO = (t % 2.3) > 2.16 ? 1 : 0; return (
{blinkO ? ( ) : ( )}
); } function Sparkle({ x, y, p, size = 26, color = C.orange }) { const lp = clamp(p, 0, 1); if (lp <= 0) return null; const s = Easing.easeOutBack(Math.min(1, lp * 1.6)); const o = 1 - Math.max(0, lp - 0.6) / 0.4; return ( ); } /* ---------- Angebot-Dokument ---------- */ function AngebotDoc({ x, y, rot, scale, typeP }) { const widths = [88, 72, 82, 58]; return (
Angebot
E
{widths.map((w, i) => { const lp = Easing.easeOutCubic(clamp((typeP - i * 0.16) / 0.2, 0, 1)); return
; })}
Summe
); } /* ============================================================ SZENE 1+2 — Abends am Tisch → KI-Orb + Handy + Angebot fliegt. 0 … 6.6 (lokal) ============================================================ */ function SceneAbend() { const { localTime: lt } = useSprite(); const TT = [0, 3.0, 3.2, 3.75, 4.9, 5.5, 6.6]; const sc = interpolate(TT, [1.02, 1.1, 1.1, 1.3, 1.3, 1.16, 1.14], Easing.easeInOutCubic)(lt); const cx = interpolate(TT, [0, 8, 8, 50, 50, 28, 26], Easing.easeInOutCubic)(lt); const cy = interpolate(TT, [8, 20, 20, -22, -22, 4, 6], Easing.easeInOutCubic)(lt); const orbPop = clamp((lt - 3.3) / 0.4, 0, 1); const phoneUpP = clamp((lt - 3.5) / 0.35, 0, 1); const talking = lt > 3.9 && lt < 4.85; const expression = lt < 3.35 ? "tired" : lt < 3.9 ? "smile" : lt < 4.85 ? "talk" : lt > 5.8 && lt < 6.35 ? "wink" : "grin"; const blink = (lt > 1.5 && lt < 1.72) || (lt > 2.7 && lt < 2.9) || (lt > 5.2 && lt < 5.36) ? 1 : 0; const hop = -12 * Easing.easeOutBack(clamp((lt - 3.32) / 0.3, 0, 1)) * (1 - clamp((lt - 3.75) / 0.35, 0, 1)); const bob = Math.sin(lt * 1.7) * 4 + hop; const slumpP = 1 - Easing.easeInOutCubic(clamp((lt - 3.3) / 0.5, 0, 1)); const bubblePop = clamp((lt - 0.4) / 0.5, 0, 1) * (lt < 2.85 ? 1 : clamp((3.1 - lt) / 0.25, 0, 1)); const flyP = Easing.easeInOutCubic(clamp((lt - 4.15) / 0.85, 0, 1)); const docX = 790 + (400 - 790) * flyP; const docY = 500 + (430 - 500) * flyP - Math.sin(flyP * Math.PI) * 190; const docRot = 16 - 20 * flyP; const docS = interpolate([0, 0.6, 1], [0.15, 0.4, 1], Easing.easeInOutCubic)(flyP); const typeP = clamp((lt - 5.0) / 1.2, 0, 1); return (
{/* Sprechblase S1 — rechts oben, Uhr bleibt sichtbar */} {lt < 3.2 && } {/* KI-Orb */} {lt >= 3.25 && (
)} {/* Sprechwellen Richtung Handy */} {talking && ( {[0, 1, 2].map((i) => ( ))} )} {/* Angebot fliegt aus dem Handy */} {lt >= 4.15 && } {/* Funkeln bei Landung */} {lt >= 5.0 && ( )}
); } /* ============================================================ SZENE 3 — Dreierschnitt: Mail / Foto→Bericht / Kalender. 6.6 … 10.2 (je 1.2s) ============================================================ */ function PanelMail({ p }) { const widths = [92, 78, 88, 62, 40]; const planeP = Easing.easeInOutCubic(clamp((p - 0.74) / 0.2, 0, 1)); const idx = Math.min(4, Math.max(0, Math.floor((p - 0.14) / 0.1))); const curP = Easing.easeOutCubic(clamp((p - 0.14 - idx * 0.1) / 0.11, 0, 1)); return (
Neue Nachricht
AN:
{widths.map((w, i) => { const lp = Easing.easeOutCubic(clamp((p - 0.14 - i * 0.1) / 0.11, 0, 1)); return
; })}
-0.3 ? 1 : 0 }} />
{/* Papierflieger */} {p > 0.74 && ( )} {p > 0.8 && (
Gesendet
)}
); } function PanelBericht({ p }) { const flipP = clamp((p - 0.42) / 0.22, 0, 1); const showPhoto = flipP < 0.5; const sxPhoto = Math.max(0, 1 - flipP * 2); const sxDoc = Math.max(0, flipP * 2 - 1); const checkS = Easing.easeOutBack(clamp((p - 0.78) / 0.14, 0, 1)); return (
{showPhoto ? (
{/* Kran */} {/* halbe Mauer */}
Baustelle.jpg
) : (
Bericht
{[90, 76, 84, 64, 78, 46].map((w, i) => (
))}
)} {/* Funkeln beim Flip */}
); } function PanelKalender({ p }) { const days = ["M", "D", "M", "D", "F", "S", "S"]; const pills = { 2: 0, 5: 1, 9: 2, 12: 3, 16: 4, 19: 5, 24: 6 }; const checks = { 5: 0, 12: 1, 19: 2 }; const cells = []; for (let i = 0; i < 28; i++) { const pi = pills[i]; const pillS = pi !== undefined ? Easing.easeOutBack(clamp((p - 0.14 - pi * 0.08) / 0.14, 0, 1)) : 0; const ci = checks[i]; const checkS = ci !== undefined ? Easing.easeOutBack(clamp((p - 0.5 - ci * 0.1) / 0.14, 0, 1)) : 0; cells.push(
{i + 1}
{pi !== undefined && (
)} {ci !== undefined && (
)}
); } return (
Juli
{days.map((d, i) => (
{d}
))}
{cells}
); } function SceneCuts({ showCaptions }) { const { localTime: lt } = useSprite(); const seg = Math.min(2, Math.floor(lt / 1.2)); const segT = lt - seg * 1.2; const p = clamp(segT / 1.2, 0, 1); const enter = Easing.easeOutCubic(clamp(segT / 0.2, 0, 1)); const flash = Math.max(0, 1 - segT / 0.1); const bgs = [C.blue50, C.orange100, "#E7EDFB"]; const captions = ["E-Mail · schreibt sich selbst", "Foto → fertiger Bericht", "Termine · geplant"]; const capPop = Easing.easeOutBack(clamp((segT - 0.12) / 0.3, 0, 1)); return (
{seg === 0 && } {seg === 1 && } {seg === 2 && } {/* Mini-Orb arbeitet mit */}
{/* Schrittzähler */}
{seg + 1} / 3
{showCaptions && (
{captions[seg]}
)}
); } /* ============================================================ SZENE 4 — Stapel schrumpft, Uhr springt zurück, Lehnen + Lächeln. 10.2 … 12.2 ============================================================ */ function SceneRelief() { const { localTime: lt } = useSprite(); const sc = interpolate([0, 2.0], [1.05, 1.1], Easing.easeInOutCubic)(lt); const shrinkP = Easing.easeInOutCubic(clamp((lt - 0.2) / 0.6, 0, 1)); const stackP = 1 - 0.82 * shrinkP; const spinP = Easing.easeInOutCubic(clamp((lt - 0.45) / 0.9, 0, 1)); const clockH = 275 + (150 - 275) * spinP; const clockM = 60 - 720 * spinP; const leanP = Easing.easeOutBack(clamp((lt - 1.05) / 0.45, 0, 1)); const expression = lt < 1.15 ? "smile" : "relaxed"; const bob = Math.sin(lt * 1.9) * 3; const sheets = ( ); return (
); } /* ============================================================ SZENE 5 — Feierabend + Claim + Logo. 12.2 … 14.4 ============================================================ */ function LogoLockup({ size = 64 }) { return (
EIPME
EIPME
); } function SceneOutro({ headline }) { const { localTime: lt } = useSprite(); const wipe = Easing.easeOutCubic(clamp(lt / 0.5, 0, 1)); const walkP = Easing.easeOutCubic(clamp(lt / 0.75, 0, 1)); const x = -430 + 430 * walkP; const bob = Math.sin(lt * 13) * (7 * (1 - walkP) + 2); const waveRamp = Easing.easeOutBack(clamp((lt - 0.85) / 0.3, 0, 1)); const waveA = Math.sin(Math.max(0, lt - 0.85) * 7) * 15 * waveRamp; const textIn = Easing.easeOutCubic(clamp((lt - 0.5) / 0.5, 0, 1)); const logoIn = Easing.easeOutCubic(clamp((lt - 0.95) / 0.5, 0, 1)); const blink = (lt % 2.4) > 2.26 ? 1 : 0; const breathe = 1 + 0.006 * Math.sin(lt * 1.6); return (
{headline}
); } /* ============================================================ */ function HandwerkerKISpot({ headline, showCaptions }) { const ref = R.useRef(null); const HL = headline || "KI, die Ihnen Zeit schenkt."; const caps = showCaptions !== false; return (
); } function TimestampLabel({ rootRef }) { const t = useTime(); const sec = Math.floor(t); R.useEffect(() => { const el = rootRef.current && rootRef.current.querySelector("[data-om-exportable-video-with-duration-secs]"); (el || rootRef.current) && (el || rootRef.current).setAttribute("data-screen-label", "t=" + sec + "s"); }, [sec]); return null; } window.HandwerkerKISpot = HandwerkerKISpot;