/* EIPME Handwerker-Spot — 1200×1200, ~11.5s, comic style. Reads Stage/Sprite/useTime/Easing/interpolate/animate from window (animations.jsx). */ const { Stage, Sprite, useTime, useSprite, Easing, interpolate, animate, 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"; /* ============================================================ HANDWERKER — comic character, slightly exaggerated proportions. expression: 'grin' | 'wink' | 'shock' | 'smile' pose: 'crossed' (arms crossed) | 'up' (thumbs-up-ish open pose) Drawn in a 360×520 viewBox, anchored bottom-center. ============================================================ */ function Handwerker({ expression = "grin", pose = "crossed", sweat = 0, bob = 0, blink = 0 }) { const shocked = expression === "shock"; // head bobs gently; on shock it jumps a bit const headY = bob; /* --- eyes --- */ let eyes; if (expression === "wink") { eyes = ( {/* left eye open */} {/* right eye winking: arc */} ); } else if (shocked) { eyes = ( ); } else { const closed = blink > 0.5; eyes = closed ? ( ) : ( ); } /* --- mouth --- */ let mouth; if (shocked) { mouth = ; } else if (expression === "smile") { mouth = ; } else { // confident grin (open) mouth = ( ); } /* --- brows --- */ const brows = shocked ? ( ) : ( ); /* --- arms --- */ const arms = pose === "crossed" ? ( {/* crossed arms over chest */} {/* fists */} ) : ( {/* open, hands slightly raised — friendly */} ); return ( {/* legs */} {/* body — blue Latzhose over orange shirt */} {/* Latzhose bib + trousers */} {/* straps */} {/* bib pocket with pencil */} {arms} {/* head group (bobs) */} {/* neck */} {/* head — big, comic */} {/* ears */} {/* cheeks */} {!shocked && } {!shocked && } {/* moustache */} {brows} {eyes} {/* nose — big friendly */} {mouth} {/* cap (Mütze) — blue with orange brim + E badge */} E {/* sweat drops (comic) */} {sweat > 0 && ( )} ); } function SweatDrop({ x, y, p, dir, delay }) { const lp = clamp((p - delay) / 0.6, 0, 1); if (lp <= 0) return null; const fly = Easing.easeOutCubic(lp); const dx = dir * 46 * fly; const dy = -30 * fly + 26 * fly * fly; return ( ); } /* ============================================================ WERKSTATT — flat comic facade. Includes door + window w/ screen. Drawn 1200×1200 backdrop. screen: 'ok' | 'alarm' ; alarmT: 0..1 pulse driver ============================================================ */ function Werkstatt({ screen = "ok", alarmT = 0, doorSign = null }) { const alarmOn = screen === "alarm"; const pulse = alarmOn ? 0.75 + 0.25 * Math.sin(alarmT * Math.PI * 6) : 0; return ( {/* sky */} {/* clouds */} {/* ground */} {/* facade */} {/* roof band */} {/* awning stripes over window */} {/* shop sign */} WERKSTATT {/* window with desk + screen */} {/* desk */} {/* monitor */} {alarmOn ? ( {/* lock icon */} ) : ( )} {/* window frame bar */} {/* door */} {doorSign} ); } /* ---------- speech bubble ---------- */ function SpeechBubble({ x, y, w, pop, text, sub }) { const s = Easing.easeOutBack(clamp(pop, 0, 1)); return (
{text}
{sub}
); } /* ---------- EIPME shield ---------- */ function Shield({ pop }) { const s = Easing.easeOutBack(clamp(pop, 0, 1)); return ( ); } /* ---------- EIPME logo lockup (symbol svg + Geist wordmark) ---------- */ function LogoLockup({ size = 64 }) { return (
EIPME
EIPME
); } /* ============================================================ SCENES ============================================================ */ const T1 = 0, T1E = 3.4; // Szene 1 const T2 = 3.4, T2E = 6.6; // Szene 2 const T3 = 6.6, T3E = 11.0; // Szene 3 (+2s für Statistiken) const T4 = 11.0, DUR = 13.6; // Szene 4 /* camera helper: returns transform for the 1200×1200 world */ function useCam(segs) { const t = useTime(); const sc = interpolate(segs.t, segs.scale, Easing.easeInOutCubic)(t); const cx = interpolate(segs.t, segs.x, Easing.easeInOutCubic)(t); const cy = interpolate(segs.t, segs.y, Easing.easeInOutCubic)(t); return { transform: `scale(${sc}) translate(${-cx}px, ${-cy}px)`, transformOrigin: "600px 600px" }; } function SceneWorkshop() { const t = useTime(); const local1 = t - T1; const inS2 = t >= T2; const local2 = Math.max(0, t - T2); // camera: S1 slow push-in on character; S2 whip to the window/screen, then pull to face const cam = useCam({ t: [0, 3.0, T2, T2 + 0.55, T2 + 1.7, T2 + 2.2, T2E], scale: [1.0, 1.12, 1.12, 1.85, 1.85, 1.45, 1.42], x: [0, 30, 30, 195, 195, 60, 55], y: [0, 40, 40, -9, -9, 41, 41], }); // character states const wink = local1 > 2.1 && local1 < 2.75; const blink = local1 > 1.1 && local1 < 1.28 ? 1 : 0; const shockStart = T2 + 0.9; const shocked = t >= shockStart; const sweat = clamp((t - shockStart - 0.15) / 1.1, 0, 1); const bob = Math.sin(t * 2.1) * 4 + (shocked ? -6 * Easing.easeOutBack(clamp((t - shockStart) / 0.4, 0, 1)) : 0); const jitter = shocked ? Math.sin(t * 26) * 2.4 * clamp(1 - (t - shockStart) / 2.4, 0.25, 1) : 0; // alarm flash overlay on whole frame const alarmFlash = inS2 ? 0.10 + 0.08 * Math.sin(local2 * Math.PI * 5) : 0; const alarmRamp = clamp((t - (T2 + 0.35)) / 0.25, 0, 1); const bubblePop = clamp(local1 / 0.55, 0, 1) * (t < T2 - 0.25 ? 1 : clamp((T2 - t) / 0.25, 0, 1)); // exclamation "!" pop over head in S2 const bangPop = Easing.easeOutBack(clamp((t - shockStart) / 0.35, 0, 1)); return (
= T2 + 0.35 ? "alarm" : "ok"} alarmT={local2} /> {/* character standing in front, left of door */}
{/* speech bubble S1 */} {t < T2 && ( )} {/* shock "!" S2 */} {shocked && t < T2E - 0.1 && (
)}
{/* red alarm wash */} {inS2 && (
)} {/* red vignette in S2 */} {inS2 && (
)}
); } /* Szene 3 — Tür + Schild "Heute geschlossen" + Zahlen */ function SceneClosed() { const { localTime: lt } = useSprite(); // camera: tight on door const sc = interpolate([0, 4.4], [1.9, 2.02], Easing.easeInOutCubic)(lt); // sign drops + swings const drop = Easing.easeOutBounce ? Easing.easeOutBounce(clamp(lt / 0.8, 0, 1)) : Easing.easeOutBack(clamp(lt / 0.7, 0, 1)); const dropY = -520 + 520 * drop; const swing = Math.sin(Math.max(0, lt - 0.75) * 4.2) * 7 * Math.exp(-Math.max(0, lt - 0.75) * 1.6); const statsIn = Easing.easeOutCubic(clamp((lt - 0.95) / 0.4, 0, 1)); const statsPop = Easing.easeOutBack(clamp((lt - 0.95) / 0.55, 0, 1)); const countP = Easing.easeOutCubic(clamp((lt - 1.0) / 1.5, 0, 1)); const doorSign = ( Heute geschlossen ); return (
{/* dim + stats */}
Quelle · Bitkom 2025
); } function StatChip({ value, label, p }) { const shown = Math.round(value * p); return (
{shown} %
{label}
); } /* Szene 4 — hell, freundlich: Handwerker + Schild + Frage + Logo */ function SceneFinal() { const { localTime: lt } = useSprite(); const inWipe = Easing.easeOutCubic(clamp(lt / 0.5, 0, 1)); const charIn = Easing.easeOutBack(clamp((lt - 0.15) / 0.5, 0, 1)); const shieldPop = clamp((lt - 0.5) / 0.5, 0, 1); const textIn = Easing.easeOutCubic(clamp((lt - 0.85) / 0.5, 0, 1)); const logoIn = Easing.easeOutCubic(clamp((lt - 1.3) / 0.5, 0, 1)); const bob = Math.sin(lt * 2.2) * 4; const blink = (lt % 2.6) > 2.44 ? 1 : 0; const breathe = 1 + 0.006 * Math.sin(lt * 1.6); return (
{/* soft blue arc backdrop */}
{/* circular wipe reveal */}
{/* character bottom-left */}
{/* shield next to him */}
{/* headline + logo right block */}
Ist es Ihnen das wert?
IT-Sicherheit für den Mittelstand
); } /* ============================================================ */ function HandwerkerSpot() { // stamp timestamp label for comments const ref = R.useRef(null); 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.HandwerkerSpot = HandwerkerSpot;