// Componentes globais compartilhados (nav, footer, grain, intro)
// Carregar via
const { useState, useEffect, useRef } = React;
// WhatsApp oficial da MARA (link com mensagem pré-preenchida)
const WHATSAPP = "https://wa.me/5562994647141?text=Oi!%20Estava%20no%20site%20de%20voc%C3%AAs%20e%20gostaria%20de%20conversar%20sobre%20a%20produ%C3%A7%C3%A3o%20de%20um%20projeto";
function WhatsAppIcon({ size = 14 }) {
return (
);
}
function Grain() {
return
;
}
function Nav({ active }) {
const [logoOk, setLogoOk] = useState(true);
return (
<>
{/* Botão flutuante de WhatsApp — fora do #menu (que usa mix-blend), em todas as páginas */}
>);
}
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 / thumbnail cover for project videos
function VideoStill({ label, timecode = "00:00:14:21", duration = "00:18", tone = 0, aspect = "16/10", thumbnail }) {
const hueShift = [0, -8, 6, -14, 12, 0][tone % 6];
const baseStyle = {
aspectRatio: aspect,
filter: thumbnail ? "none" : `hue-rotate(${hueShift}deg)`,
backgroundImage: thumbnail
? `url(${thumbnail}), linear-gradient(135deg, #3a3938 0%, #2a2928 50%, #1f1e1d 100%)`
: null,
backgroundSize: "cover",
backgroundPosition: "center 20%"
};
return (
{thumbnail &&
}
{label &&
REC ● {label}
}
{timecode}
);
}
function SpinBadge({ text = "QUE MARA! • QUE MARA! • ", size = 140, color = "var(--color-dark)" }) {
const radius = size / 2 - 12; // raio do anel de texto
const uid = Math.random().toString(36).slice(2, 8);
const id = "tp-" + uid;
const maskId = "mk-" + uid;
const maskRef = useRef(null);
const coreRef = useRef(null);
const cx = size / 2, cy = size / 2;
const coreR = size * 0.20; // raio do ponto vermelho (= inset 30% do .core antigo)
const circleD = `M ${cx} ${cy - coreR} A ${coreR} ${coreR} 0 1 1 ${cx} ${cy + coreR} A ${coreR} ${coreR} 0 1 1 ${cx} ${cy - coreR} Z`;
// triângulo de "play" centralizado (centróide no centro), apontando para a direita
const a = coreR * 1.1, b = coreR * 0.55, hh = coreR * 0.9;
const playD = `M ${cx + a} ${cy} L ${cx - b} ${cy - hh} L ${cx - b} ${cy + hh} Z`;
React.useLayoutEffect(() => {
// no mobile o selo fica estático (sem DrawSVG/Morph) — menos processamento
if (typeof gsap === "undefined" || window.innerWidth <= 768) return;
const tweens = [];
// DrawSVG numa máscara: as palavras "preenchem" ao redor do círculo ao abrir
if (window.DrawSVGPlugin && maskRef.current) {
gsap.registerPlugin(window.DrawSVGPlugin);
tweens.push(gsap.fromTo(maskRef.current,
{ drawSVG: "0%" },
{ drawSVG: "100%", duration: 1.6, ease: "power1.inOut", delay: 0.2 }));
}
// MorphSVG: o ponto vermelho alterna entre círculo e triângulo de play
if (window.MorphSVGPlugin && coreRef.current) {
gsap.registerPlugin(window.MorphSVGPlugin);
tweens.push(gsap.to(coreRef.current,
{ morphSVG: playD, duration: 1.3, ease: "power2.inOut", repeat: -1, yoyo: true, repeatDelay: 1.0 }));
}
return () => tweens.forEach(t => t && t.kill());
}, []);
return (
{/* camada que gira: só o anel de palavras */}
{/* camada fixa: o ponto vermelho que vira play */}
);
}
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 (
);
}
// 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
});