import React, { useEffect, useMemo, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
/**
* Arbeidssikkerhet – React animasjonsdemo i blåtoner
* --------------------------------------------------
* • Minimalistisk, moderne flat stil i blånyanser
* • 3 scener (intro + to sikkerhetstips + outro)
* • Humoristisk, men trygg – ingen ulykker vises
* • Bruker kun SVG + Tailwind + Framer Motion
*
* Tips:
* - Klikk Play ▶ for å starte. Reset ↺ for å kjøre på nytt.
* - Du kan eksportere opptak fra nettleseren (skjermopptak) og bli til video,
* eller bruke dette som grunnlag i et større prosjekt.
*/
const BG = {
light: "#f5f7fb",
dark: "#0f172a", // slate-900
};
const PALETTE = {
b100: "#ebf5ff",
b200: "#d6eaff",
b300: "#a8d1ff",
b400: "#7bb6ff",
b500: "#4e97ff",
b600: "#2f76e5",
b700: "#1e5fc4",
b800: "#184f9c",
ink: "#0b2447",
};
const SceneFrame: React.FC<{ children: React.ReactNode; label?: string }>
= ({ children, label }) => (
);
function useTimer(durationMs: number, isPlaying: boolean) {
const [t, setT] = useState(0);
useEffect(() => {
if (!isPlaying) return;
const start = performance.now();
let raf: number;
const loop = (now: number) => {
const elapsed = now - start;
setT(Math.min(1, elapsed / durationMs));
if (elapsed < durationMs) raf = requestAnimationFrame(loop);
};
raf = requestAnimationFrame(loop);
return () => cancelAnimationFrame(raf);
}, [durationMs, isPlaying]);
return t; // 0..1
}
const HelmetIcon = ({ x = 0, y = 0, scale = 1, opacity = 1 }) => (
);
const GloveIcon = ({ x = 0, y = 0, flip = false, opacity = 1 }) => (
);
const Person = ({
x = 0,
y = 0,
shocked = false,
}: { x?: number; y?: number; shocked?: boolean }) => (
{/* eyes */}
{shocked ? (
) : (
)}
{/* legs */}
);
const Box = ({ x = 0, y = 0, tilt = 0 }: { x?: number; y?: number; tilt?: number }) => (
);
const Cable = ({ x = 0, y = 0, w = 220 }: { x?: number; y?: number; w?: number }) => (
);
const Broom = ({ x = 0, y = 0 }: { x?: number; y?: number }) => (
);
// Scene 1: Løft riktig – PPE flyr på plass
const SceneLift: React.FC<{ play: boolean }> = ({ play }) => {
const t = useTimer(4000, play); // 4 sek timeline
const boxY = useMemo(() => 40 - 20 * Math.sin(t * Math.PI), [t]);
const tilt = useMemo(() => (t < 0.5 ? -8 + 16 * t : 0), [t]);
const glovesIn = Math.max(0, (t - 0.35) / 0.25);
const helmetIn = Math.max(0, (t - 0.2) / 0.25);
return (
);
};
// Scene 2: Rydd ledninger – kost feier inn
const SceneTidy: React.FC<{ play: boolean }> = ({ play }) => {
const t = useTimer(4000, play);
const broomX = useMemo(() => 700 - 520 * Math.min(1, Math.max(0, (t - 0.2) / 0.5)), [t]);
const cableW = useMemo(() => 260 * (1 - Math.min(1, Math.max(0, (t - 0.55) / 0.35))), [t]);
return (
);
};
// Scene 3: Outro – Safety First
const SceneOutro: React.FC<{ play: boolean }> = ({ play }) => (
);
export default function SafetyVideoDemo() {
const [step, setStep] = useState<0 | 1 | 2 | 3>(0);
const [playing, setPlaying] = useState(false);
// Enkel sekvensering av scener
useEffect(() => {
let timers: number[] = [];
if (playing) {
setStep(1);
timers.push(window.setTimeout(() => setStep(2), 4200));
timers.push(window.setTimeout(() => setStep(3), 8400));
}
return () => timers.forEach(clearTimeout);
}, [playing]);
const reset = () => {
setPlaying(false);
setStep(0);
};
return (
{step === 0 && (
)}
{step === 1 && (
)}
{step === 2 && (
)}
{step === 3 && (
)}
);
}
{children}
Arbeidssikkerhet – animasjonsdemo
Enkel, moderne og morsom demo i blåtoner. Klikk Play for å starte.
Scene: {step === 0 ? "Klar" : step === 1 ? "Løft riktig" : step === 2 ? "Ryddighet" : "Outro"}
Tips: Ta opp skjermen mens demoen spiller for å lagre som video, eller
port komponenten inn i ditt animasjons-/presentasjonsoppsett.