import React, { useEffect, useMemo, useRef, useState } from "react";
import { motion, useReducedMotion } from "framer-motion";
import { Camera, Wand2, Code2, Clapperboard, Rocket, Volume2, VolumeX, ArrowUpRight, Smartphone, TabletSmartphone, Monitor, PencilLine, Upload, Download } from "lucide-react";
// === THEME ===
const BELITZ_YELLOW = "#FFD400"; // Primärgelb
const BELITZ_YELLOW_SOFT = "rgba(255,212,0,0.15)";
const BG = "#0A0A0A"; // Tiefschwarz
// === Simple content store with localStorage (plain JS) ===
const DEFAULT_CONTENT = {
heroTitle: "Cutting‑Edge Creative &\nHigh‑Impact Editing",
heroCopy:
"Belitzmedia baut dir die Show, die hängen bleibt: kompromissloses Editing, starke Markeninszenierung, schnelles Storytelling. Schwarz‑Gelb. Direkt. Unübersehbar.",
contactEmail: "hello@belitz.media",
colorPrimary: BELITZ_YELLOW,
bg: BG,
useSVGLogo: false,
svgString: "",
logoText: "BELITZMEDIA",
};
function useContent() {
const [content, setContent] = useState(() => {
if (typeof window !== "undefined") {
const raw = localStorage.getItem("belitzLandingContent");
if (raw) return { ...DEFAULT_CONTENT, ...JSON.parse(raw) };
}
return DEFAULT_CONTENT;
});
useEffect(() => {
if (typeof window !== "undefined") {
localStorage.setItem("belitzLandingContent", JSON.stringify(content));
}
}, [content]);
return { content, setContent };
}
export default function BelitzmediaLanding() {
const prefersReduced = useReducedMotion();
const { content, setContent } = useContent();
const [mouse, setMouse] = useState({ x: 0, y: 0 });
const [audioOn, setAudioOn] = useState(false);
const [editorOpen, setEditorOpen] = useState(false);
const [device, setDevice] = useState("desktop"); // 'desktop' | 'tablet' | 'mobile'
// Spotlight follows cursor
useEffect(() => {
const handleMove = (e) => setMouse({ x: e.clientX, y: e.clientY });
window.addEventListener("mousemove", handleMove);
return () => window.removeEventListener("mousemove", handleMove);
}, []);
// Ambient sound (safe, only on click)
const audioRef = useRef(null);
const toggleAudio = async () => {
try {
if (!audioOn) {
const ctx = new (window.AudioContext || window.webkitAudioContext)();
audioRef.current = ctx;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
const lfo = ctx.createOscillator();
const lfoGain = ctx.createGain();
osc.type = "sine";
osc.frequency.value = 72;
gain.gain.value = 0.0001;
lfo.frequency.value = 0.03;
lfoGain.gain.value = 18;
lfo.connect(lfoGain).connect(osc.frequency);
osc.connect(gain).connect(ctx.destination);
osc.start();
lfo.start();
gain.gain.exponentialRampToValueAtTime(0.02, ctx.currentTime + 2);
setAudioOn(true);
} else if (audioRef.current) {
const ctx = audioRef.current;
const dest = ctx.destination;
// quick suspend for simplicity
ctx.suspend();
setAudioOn(false);
}
} catch (_) {
setAudioOn(false);
}
};
const deviceWidth = device === "mobile" ? 390 : device === "tablet" ? 768 : undefined;
return (
{/* HUD: Editor & Device toggles */}
{/* Cursor Spotlight */}
{/* Canvas-like Background (2D Particles) */}
{/* NAV */}
{/* CONTENT WIDTH WRAPPER for device preview */}
{/* HERO */}
{/* 3D‑LOOK LOGO (WebGL‑freie Variante) */}
Interactive Logo (No‑WebGL)
{content.heroTitle.split("\n").map((line, i) => (
{line}
))}
{content.heroCopy}
Showreel ansehen
Projekt anfragen
{/* CAPABILITIES */}
{capabilities.map((c, i) => (
))}
{/* WORK GRID */}
{projects.map((p, idx) => (
))}
{/* PROCESS */}
Prozess
{processSteps.map((s, i) => (
0{i + 1}
{s.title}
{s.copy}
))}
{/* CTA */}
{/* Footer */}
{/* EDITOR PANEL */}
{editorOpen && (
)}
{/* util styles */}
);
}
// === 3D-LOOK (Fallback ohne WebGL) ===
function LogoStageFallback({ color, useSVG, svgString, text }) {
const ref = useRef(null);
const [tilt, setTilt] = useState({ x: 0, y: 0 });
const onMove = (e) => {
const el = ref.current;
if (!el) return;
const rect = el.getBoundingClientRect();
const px = (e.clientX - rect.left) / rect.width; // 0..1
const py = (e.clientY - rect.top) / rect.height; // 0..1
const ry = (px - 0.5) * 18; // rotateY
const rx = (0.5 - py) * 12; // rotateX
setTilt({ x: rx, y: ry });
};
const onLeave = () => setTilt({ x: 0, y: 0 });
return (
{/* Glow */}
{/* Content */}
{useSVG ? (
) : (
{text}
Signature Cut • Black & Yellow
)}
{/* Fake depth layers */}
);
}
// === Capability Card ===
function CapabilityCard({ title, copy, icon: Icon, index, color }) {
return (
{title}
{copy}
);
}
const capabilities = [
{
title: "High‑End Editing",
copy: "Tempo, Timing, Taktgefühl – wir schneiden so, dass die Message trifft und hängen bleibt.",
icon: Clapperboard,
},
{
title: "Motion & FX",
copy: "Titel, Transitions, 2.5D/3D – stilvoll und on‑brand. Dezent, wenn's reicht. Wild, wenn's wirkt.",
icon: Wand2,
},
{
title: "Creative Direction",
copy: "Konzept bis Kampagne: Story, Look, Sound. Schwarz‑Gelb als Signatur.",
icon: Rocket,
},
{
title: "Web & Interaktion",
copy: "Landingpages mit Punch: schnelle, reaktive Erlebnisse, die konvertieren.",
icon: Code2,
},
{
title: "Brand Film",
copy: "Cinematic Snippets, die wie Trailer wirken – für TikTok, Reels & YouTube.",
icon: Camera,
},
{
title: "Packaging Content",
copy: "Produkt sexy erzählen: Macro Shots, Sounddesign, Micro‑Animationen.",
icon: Wand2,
},
];
// === Projects (placeholder thumbs) ===
const projects = [
{ title: "Mall of Berlin – TikTok Hype", tag: "Campaign Editing" },
{ title: "SugarGang – Product Shorts", tag: "Social Spots" },
{ title: "Nicflex – YT Cuts", tag: "YouTube Editing" },
{ title: "Wonderwaffel – Sizzle", tag: "Hero Reel" },
{ title: "Rennesaince Wear – Drop", tag: "Brand Film" },
{ title: "VegaHappy – Explainer", tag: "Motion Design" },
];
function ProjectCard({ title, tag, color }) {
return (
{/* Thumb */}
{/* Overlay */}
);
}
// === Process ===
const processSteps = [
{ title: "Kickoff", copy: "Kurzer Call, klare Ziele. Wir definieren Look, Tonalität und KPIs." },
{ title: "Prototyp", copy: "Styleframes, Snippets, Intro – schnell greifbare Vorschau deiner Marke." },
{ title: "Delivery", copy: "Iterationen in Tempo, Master‑Export, Formate für alle Plattformen." },
];
// === Particle Canvas (2D) ===
function ParticleCanvas({ color = BELITZ_YELLOW, density = 1, reduceMotion = false }) {
const canvasRef = useRef(null);
const mouse = useRef({ x: 0, y: 0 });
const particles = useRef([]);
const raf = useRef(null);
const config = useMemo(() => ({
COUNT: Math.floor(140 * density),
MAX_V: reduceMotion ? 0.08 : 0.25,
R_MIN: 0.6,
R_MAX: 1.8,
LINK_DIST: 110,
MOUSE_PULL: reduceMotion ? 0.05 : 0.12,
}), [density, reduceMotion]);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
const resize = () => {
const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.floor(window.innerWidth * dpr);
canvas.height = Math.floor(window.innerHeight * dpr);
canvas.style.width = window.innerWidth + "px";
canvas.style.height = window.innerHeight + "px";
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
};
resize();
window.addEventListener("resize", resize);
// init particles
particles.current = Array.from({ length: config.COUNT }, () => ({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
vx: (Math.random() - 0.5) * config.MAX_V,
vy: (Math.random() - 0.5) * config.MAX_V,
r: config.R_MIN + Math.random() * (config.R_MAX - config.R_MIN),
}));
const onMove = (e) => {
mouse.current.x = e.clientX;
mouse.current.y = e.clientY;
};
window.addEventListener("mousemove", onMove);
const draw = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// subtle vignette
const grad = ctx.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, Math.max(canvas.width, canvas.height) * 0.6);
grad.addColorStop(0, "rgba(255,255,255,0.02)");
grad.addColorStop(1, "rgba(0,0,0,0.35)");
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// update & draw particles
ctx.globalCompositeOperation = "lighter";
particles.current.forEach((p) => {
const dx = mouse.current.x - p.x;
const dy = mouse.current.y - p.y;
const dist = Math.hypot(dx, dy) || 1;
const pull = Math.min(config.MOUSE_PULL / dist, 0.02);
p.vx += dx * pull;
p.vy += dy * pull;
const sp = Math.hypot(p.vx, p.vy);
const MAX_V = config.MAX_V;
if (sp > MAX_V) {
p.vx = (p.vx / sp) * MAX_V;
p.vy = (p.vy / sp) * MAX_V;
}
p.x += p.vx;
p.y += p.vy;
if (p.x < -10) p.x = window.innerWidth + 10;
if (p.x > window.innerWidth + 10) p.x = -10;
if (p.y < -10) p.y = window.innerHeight + 10;
if (p.y > window.innerHeight + 10) p.y = -10;
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.globalAlpha = 0.12;
ctx.fill();
});
ctx.globalAlpha = 0.06;
for (let i = 0; i < particles.current.length; i++) {
const a = particles.current[i];
for (let j = i + 1; j < particles.current.length; j++) {
const b = particles.current[j];
const dx = a.x - b.x;
const dy = a.y - b.y;
const d = dx * dx + dy * dy;
if (d < 110 * 110) {
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.strokeStyle = color;
ctx.lineWidth = 1;
ctx.stroke();
}
}
}
ctx.globalAlpha = 1;
raf.current = requestAnimationFrame(draw);
};
draw();
return () => {
window.removeEventListener("resize", resize);
window.removeEventListener("mousemove", onMove);
if (raf.current) cancelAnimationFrame(raf.current);
};
}, [color, config]);
return
;
}
// === EDITOR PANEL ===
function EditorPanel({ content, setContent }) {
const onSVGUpload = async (e) => {
const file = e.target.files?.[0];
if (!file) return;
const text = await file.text();
setContent({ ...content, svgString: text, useSVGLogo: true });
};
const downloadJSON = () => {
const blob = new Blob([JSON.stringify(content, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "belitz-landing.json";
a.click();
URL.revokeObjectURL(url);
};
const onJSONUpload = async (e) => {
const file = e.target.files?.[0];
if (!file) return;
const text = await file.text();
try {
const data = JSON.parse(text);
setContent({ ...DEFAULT_CONTENT, ...data });
} catch (err) {
alert("Ungültige JSON-Datei");
}
};
return (
);
}