// hero-tweaks.jsx // Tweaks panel for repositioning, rotating and scaling the four hand-drawn // hero icons (cloud / heart / bloom / thumb). When the Tweaks toggle is on, // each icon becomes draggable and gets a rotation + size slider in the panel. const { TweaksPanel, TweakSection, TweakSlider, TweakButton, useTweaks } = window; const HERO_ICON_DEFAULTS = { cloud: { x: 80, y: 14, rot: -4, scale: 1 }, heart: { x: 16, y: 52, rot: -10, scale: 1 }, bloom: { x: 40, y: 82, rot: 6, scale: 1 }, thumb: { x: 89, y: 74, rot: -8, scale: 1 }, }; const ICON_LABEL = { cloud: "Wolke", heart: "Herz", bloom: "Blume", thumb: "Daumen", }; function HeroTweaks() { // Seed from the same EDITMODE JSON the rest of the page uses, falling back // to the bundled defaults so a fresh page boots cleanly. const seed = window.TERMINE_DEFAULTS || {}; const initial = { ...seed, heroIcons: { ...HERO_ICON_DEFAULTS, ...(seed.heroIcons || {}) }, }; const [t, setTweak] = useTweaks(initial); const icons = t.heroIcons || HERO_ICON_DEFAULTS; // Track whether the host has activated edit mode — we only enable drag & // show grab cursors while the user is actively editing. const [active, setActive] = React.useState(false); React.useEffect(() => { const onMsg = (e) => { const d = e && e.data; if (!d || typeof d !== "object") return; if (d.type === "__activate_edit_mode") setActive(true); if (d.type === "__deactivate_edit_mode") setActive(false); }; window.addEventListener("message", onMsg); return () => window.removeEventListener("message", onMsg); }, []); // Apply current icon state to the DOM. We override the static CSS rules // with inline styles anchored at the icon's centre (translate(-50%,-50%)) // so X/Y are intuitively "where the centre sits inside the hero". React.useEffect(() => { const hero = document.querySelector(".hero"); if (!hero) return; Object.entries(icons).forEach(([name, cfg]) => { const el = hero.querySelector(".hero__deco--" + name); if (!el) return; el.style.left = cfg.x + "%"; el.style.top = cfg.y + "%"; el.style.right = "auto"; el.style.bottom = "auto"; el.style.transform = `translate(-50%, -50%) rotate(${cfg.rot}deg) scale(${cfg.scale})`; el.style.transformOrigin = "center center"; }); }, [icons]); // Drag wiring — attached/detached as `active` and `icons` change. React.useEffect(() => { const hero = document.querySelector(".hero"); if (!hero) return; const cleanups = []; Object.keys(icons).forEach((name) => { const el = hero.querySelector(".hero__deco--" + name); if (!el) return; // Idle styling: only intercept pointer events while tweaks is active, // otherwise the icons stay purely decorative. el.style.pointerEvents = active ? "auto" : "none"; el.style.cursor = active ? "grab" : ""; el.style.touchAction = active ? "none" : ""; el.style.outline = ""; if (!active) return; const down = (e) => { e.preventDefault(); const heroRect = hero.getBoundingClientRect(); try { el.setPointerCapture(e.pointerId); } catch (err) {} el.style.cursor = "grabbing"; el.style.outline = "2px dashed rgba(255, 97, 110, .55)"; el.style.outlineOffset = "4px"; let lastX = icons[name].x; let lastY = icons[name].y; const move = (ev) => { const x = ((ev.clientX - heroRect.left) / heroRect.width) * 100; const y = ((ev.clientY - heroRect.top) / heroRect.height) * 100; lastX = Math.max(2, Math.min(98, x)); lastY = Math.max(2, Math.min(98, y)); el.style.left = lastX + "%"; el.style.top = lastY + "%"; }; const up = () => { el.removeEventListener("pointermove", move); el.removeEventListener("pointerup", up); el.removeEventListener("pointercancel", up); el.style.cursor = "grab"; el.style.outline = ""; setTweak("heroIcons", { ...icons, [name]: { ...icons[name], x: +lastX.toFixed(1), y: +lastY.toFixed(1) }, }); }; el.addEventListener("pointermove", move); el.addEventListener("pointerup", up); el.addEventListener("pointercancel", up); }; el.addEventListener("pointerdown", down); cleanups.push(() => { el.removeEventListener("pointerdown", down); el.style.pointerEvents = ""; el.style.cursor = ""; el.style.touchAction = ""; el.style.outline = ""; }); }); return () => cleanups.forEach((fn) => fn()); }, [active, icons, setTweak]); const updateIcon = (name, patch) => { setTweak("heroIcons", { ...icons, [name]: { ...icons[name], ...patch }, }); }; const resetAll = () => setTweak("heroIcons", HERO_ICON_DEFAULTS); return (
Ziehen Sie die Icons im Header, um sie frei zu positionieren. Drehung und Größe regeln Sie hier.
{Object.entries(icons).map(([name, cfg]) => ( updateIcon(name, { x: v })} /> updateIcon(name, { y: v })} /> updateIcon(name, { rot: v })} /> updateIcon(name, { scale: v / 100 })} /> ))}
); } ReactDOM.createRoot(document.getElementById("hero-tweaks-root")).render();