/* EIPME NIS2-Spot "Wissen, ob NIS2 Sie betrifft." — 1200×1200, 15s, comic style.
Schwesterfigur zum Handwerker: Chef eines Zulieferbetriebs (Sakko, offener Kragen, Sneaker), gleiche Farbwelt.
Reads Stage/Sprite/useTime/useSprite/Easing/interpolate/clamp from window (animations.jsx). */
const { Stage, Sprite, useTime, useSprite, Easing, interpolate, clamp } = window;
const R = React;
const C = {
blue: "#1F5EFF", blue600: "#0A47E0", blue300: "#8AA8FF", blue100: "#D9E4FF", blue50: "#EEF3FF",
orange: "#FF7A1A", orange300: "#FFA15C", orange100: "#FFDEC2",
red: "#FF3B47", red600: "#E51F2D", red100: "#FFD6DA",
green: "#0E6B27", greenTint: "rgba(14,107,39,0.14)",
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 CHEF — Comic-Kopf mit Kurzhaar (statt Mütze), gleiche Mimik.
============================================================ */
function KopfChef({ expression = "smile", blink = 0, talkPhase = 0 }) {
const shocked = expression === "shock";
let eyes;
if (expression === "relaxed") {
eyes = (
);
} else if (shocked) {
eyes = (
);
} else {
const closed = blink > 0.5;
eyes = closed ? (
) : (
);
}
let mouth;
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 = shocked ? (
) : (
);
return (
{!shocked && }
{!shocked && }
{brows}
{eyes}
{/* Brille */}
{mouth}
{/* kurzes, dunkles Haar mit Stirntolle — jünger, kein Grau */}
);
}
/* ---------- Sprechblase ---------- */
function SpeechBubble({ x, y, w, pop, text, tailX = 64, fontSize = 54, align = "left", accent = null }) {
const s = Easing.easeOutBack(clamp(pop, 0, 1));
return (
);
}
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 (
);
}
function LogoLockup({ size = 64 }) {
return (
EIPME
);
}
/* ============================================================
BÜRO-KULISSE — kleines Büro eines Zulieferbetriebs, Tag.
============================================================ */
function BueroRaum() {
return (
{/* Fenster mit Tageslicht */}
{/* Regal mit Kartons */}
{/* Firmenschild (oben, frei sichtbar) */}
Zulieferbetrieb
SEIT 2004
);
}
/* ---------- Chef-Figur, ganze Figur: Sakko, offenes Hemd, Sneaker ---------- */
function ChefBueste({ expression = "smile", blink = 0, pose = "crossed", t = 0, sweat = 0 }) {
const bob = Math.sin(t * 1.7) * 2.4;
return (
);
}
/* ============================================================
SZENE 1 — Chef entspannt, "NIS2? Betrifft uns nicht." 0 … 3.3
============================================================ */
function SceneRelaxed() {
const { localTime: lt } = useSprite();
const sc = interpolate([0, 3.3], [1.04, 1.1], Easing.easeInOutCubic)(lt);
const blink = (lt > 1.7 && lt < 1.86) || (lt > 2.7 && lt < 2.86) ? 1 : 0;
const bubblePop = clamp((lt - 0.35) / 0.4, 0, 1);
return (
{/* Chef steht vor dem Tisch, Arme verschränkt — ganze Figur */}
{/* Sprechblase am Kopf verankert, Spitze zeigt auf den Mund */}
NIS2?
Betrifft uns nicht.}
/>
);
}
/* ============================================================
SZENE 2 — Mail ploppt auf, Chef erschrickt. 3.0 … 6.3
============================================================ */
function SceneMail() {
const { localTime: lt } = useSprite();
const sc = interpolate([0, 4.45], [1.05, 1.12], Easing.easeInOutCubic)(lt);
const mailPop = Easing.easeOutBack(clamp((lt - 0.55) / 0.4, 0, 1));
const shock = lt > 0.95;
const sweat = clamp((lt - 1.25) / 0.4, 0, 1);
const blink = 0;
const shake = shock && lt < 1.7 ? Math.sin(lt * 40) * 2.2 : 0;
return (
{/* Mail-Karte auf dem Bildschirm — visueller Fokus */}
Betreff
Sicherheitsnachweis gemäß NIS2 erforderlich
{/* Chef rechts, erschrickt — hinter dem Tisch, unten sauber abgeschnitten */}
{/* Ausrufe-Funken beim Schreck */}
{shock && (
)}
);
}
/* ============================================================
SZENE 3 — Zahlenkarte, still. 6.0 … 9.3
============================================================ */
function SceneStat() {
const { localTime: lt } = useSprite();
const cardPop = Easing.easeOutBack(clamp((lt - 0.15) / 0.45, 0, 1));
const numGrow = interpolate([0.2, 0.9], [0.8, 1], Easing.easeOutBack)(clamp(lt, 0, 3));
const barP = Easing.easeInOutCubic(clamp((lt - 0.5) / 0.7, 0, 1));
return (
NIS2 · Betroffenheit
48
%
schätzen ihre Betroffenheit falsch ein
29.500 Betriebe direkt betroffen · Quelle: Cyber Security Report 2026 / BSI
);
}
/* ============================================================
SZENE 4 — EIPME überreicht NIS2-Check. 9.0 … 12.3
============================================================ */
function EipmeBadge({ pop, t }) {
const s = Easing.easeOutBack(clamp(pop, 0, 1));
if (s <= 0) return null;
return (
);
}
function SceneResolve() {
const { localTime: lt } = useSprite();
const badgePop = clamp((lt - 0.2) / 0.45, 0, 1);
const docPop = Easing.easeOutBack(clamp((lt - 0.7) / 0.5, 0, 1));
const check1 = clamp((lt - 1.2) / 0.4, 0, 1);
const check2 = clamp((lt - 1.7) / 0.4, 0, 1);
const smile = lt > 1.0;
const blink = (lt > 2.2 && lt < 2.36) ? 1 : 0;
return (
{/* Chef links, lächelt — ganze Figur */}
{/* EIPME-Badge rechts */}
{/* Dokument NIS2-Check in der Mitte */}
{/* Zwei Häkchen-Zeilen unten */}
{[["Betroffenheit geprüft", check1], ["Nachweis für Auftraggeber", check2]].map(([label, cp], i) => (
))}
);
}
/* ============================================================
SZENE 5 — Schlussbild, komplett still. 12.0 … 15.0
============================================================ */
function SceneOutro({ headline }) {
const { localTime: lt } = useSprite();
const wipe = Easing.easeOutCubic(clamp(lt / 0.5, 0, 1));
const textIn = Easing.easeOutCubic(clamp((lt - 0.3) / 0.45, 0, 1));
const logoIn = Easing.easeOutCubic(clamp((lt - 0.6) / 0.45, 0, 1));
return (
{/* Chef ruhig rechts — ganze Figur */}
);
}
/* ============================================================ */
function HandwerkerNIS2Spot({ headline }) {
const ref = R.useRef(null);
const HL = headline || "Wissen, ob NIS2 Sie betrifft.";
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.HandwerkerNIS2Spot = HandwerkerNIS2Spot;