// Componentes globais compartilhados (nav, footer, grain, intro) // Carregar via const { useState, useEffect, useRef } = React; function Grain() { return ; } function Nav({ active }) { return ( ); } function SplitLine({ text, className = "", delayOffset = 0 }) { // wrap each char in a span with .split-line for animation const chars = [...text]; return ( {chars.map((c, i) => {c === " " ? " " : c} )} ); } function PlayIcon({ size = 18 }) { return ( ); } function ArrowIcon() { return ( ); } // Generic cinematic placeholder for project videos function VideoStill({ label, timecode = "00:00:14:21", duration = "00:18", tone = 0, aspect = "16/10" }) { // tone shifts hue slightly so cards feel different const hueShift = [0, -8, 6, -14, 12, 0][tone % 6]; const baseStyle = { aspectRatio: aspect, filter: `hue-rotate(${hueShift}deg)` }; return (
{label &&
REC ● {label}
}
{timecode}
); } function SpinBadge({ text = "QUE MARA! • QUE MARA! • ", size = 140, color = "var(--color-dark)" }) { // svg with text on circle const radius = size / 2 - 12; const id = "tp-" + Math.random().toString(36).slice(2, 8); return (
{text.repeat(3)}
); } function Footer() { const [tc, setTc] = useState("00:00:00:00"); useEffect(() => { // marca o início da sessão (persiste entre navegações até fechar a aba) let start = Number(sessionStorage.getItem("mara-session-start")); if (!start) { start = Date.now(); sessionStorage.setItem("mara-session-start", String(start)); } const tick = () => { const elapsed = Date.now() - start; const totalSec = Math.floor(elapsed / 1000); const hh = String(Math.floor(totalSec / 3600)).padStart(2, "0"); const mm = String(Math.floor((totalSec % 3600) / 60)).padStart(2, "0"); const ss = String(totalSec % 60).padStart(2, "0"); const ff = String(Math.floor((elapsed % 1000) / 1000 * 24)).padStart(2, "0"); // 24fps setTc(`${hh}:${mm}:${ss}:${ff}`); }; tick(); const id = setInterval(tick, 1000 / 12); // 12hz suficiente pro frame piscar return () => clearInterval(id); }, []); return ( ); } function IntroOverlay({ onDone }) { const [gone, setGone] = useState(false); useEffect(() => { // only on first visit per session if (sessionStorage.getItem("mara-intro-played")) { setGone(true); onDone && onDone(); return; } const t = setTimeout(() => { setGone(true); sessionStorage.setItem("mara-intro-played", "1"); onDone && onDone(); }, 2400); return () => clearTimeout(t); }, []); return (
MARA
); } // scroll-trigger fade-up for elements with .fade-up function useFadeUp() { useEffect(() => { const els = document.querySelectorAll(".fade-up"); const io = new IntersectionObserver((entries) => { entries.forEach((e) => {if (e.isIntersecting) e.target.classList.add("in");}); }, { threshold: 0.15 }); els.forEach((el) => io.observe(el)); return () => io.disconnect(); }, []); } Object.assign(window, { Grain, Nav, Footer, IntroOverlay, SplitLine, PlayIcon, ArrowIcon, VideoStill, SpinBadge, useFadeUp });