// components.jsx - Sinatra Labs site components const { useState, useEffect, useRef } = React; function isTeamPagePath() { return /\/team(\.html)?$/i.test(window.location.pathname); } /* ---------- Reveal hook (IntersectionObserver) ---------- */ function useInView(opts = { once: true }) { const ref = useRef(null); const [inView, setInView] = useState(false); useEffect(() => { if (!ref.current) return; const el = ref.current; const check = () => { const r = el.getBoundingClientRect(); if (r.top < window.innerHeight - 40 && r.bottom > 0) { setInView(true); return true; } return false; }; if (check() && opts.once) return; const onScroll = () => { if (check() && opts.once) { window.removeEventListener("scroll", onScroll); } }; window.addEventListener("scroll", onScroll, { passive: true }); window.addEventListener("resize", onScroll); const t = setTimeout(onScroll, 60); return () => { clearTimeout(t); window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); }; }, []); return [ref, inView]; } /* ---------- Reveal wrapper ---------- */ function Reveal({ children, delay = 0, as = "div", className = "", ...props }) { const [ref, inView] = useInView(); const Tag = as; return ( {children} ); } /* ---------- Split words (line by line) ---------- */ function SplitLines({ children, delay = 0, stagger = 90 }) { const lines = String(children).split("\n"); const [ref, inView] = useInView(); return ( {lines.map((ln, i) => ( {ln} ))} ); } /* ---------- Top nav ---------- */ function Nav() { const [scrolled, setScrolled] = useState(false); const onTeamPage = isTeamPagePath(); useEffect(() => { const updateScrolled = () => { if (onTeamPage) { setScrolled(true); } else { const hero = document.getElementById("top"); if (!hero) { setScrolled(window.scrollY > 18); } else { setScrolled(hero.getBoundingClientRect().bottom <= 0); } } }; updateScrolled(); window.addEventListener("scroll", updateScrolled, { passive: true }); window.addEventListener("resize", updateScrolled); return () => { window.removeEventListener("scroll", updateScrolled); window.removeEventListener("resize", updateScrolled); }; }, [onTeamPage]); const brandHref = onTeamPage ? "index.html#top" : "#top"; const processHref = onTeamPage ? "index.html#process" : "#process"; const teamHref = onTeamPage ? "#team" : "team.html#team"; const contactHref = onTeamPage ? "index.html#contact" : "#contact"; return ( ); } /* ---------- Hero with animated neon LABS ---------- */ function Hero() { const videoRef = useRef(null); useEffect(() => { const video = videoRef.current; if (!video) return; const setPlaybackRate = () => { video.defaultPlaybackRate = 0.6; video.playbackRate = 0.6; }; setPlaybackRate(); video.addEventListener("loadedmetadata", setPlaybackRate); video.addEventListener("canplay", setPlaybackRate); return () => { video.removeEventListener("loadedmetadata", setPlaybackRate); video.removeEventListener("canplay", setPlaybackRate); }; }, []); const heroRef = useRef(null); useEffect(() => { const state = { targetY: 0, currentY: 0, targetScale: 1, currentScale: 1, targetMarkOpacity: 1, currentMarkOpacity: 1, targetMarkY: 0, currentMarkY: 0, targetMarkScale: 1, currentMarkScale: 1, targetMarkBlur: 0, currentMarkBlur: 0, raf: 0, }; const updateTargets = () => { const y = window.scrollY; state.targetY = Math.min(y * 0.09, 36); state.targetScale = 1 + Math.min(y / 14000, 0.015); const dissolve = Math.max(0, Math.min(1, y / 180)); state.targetMarkOpacity = 1 - dissolve; state.targetMarkY = dissolve * -20; state.targetMarkScale = 1 - dissolve * 0.04; state.targetMarkBlur = dissolve * 10; }; const tick = () => { if (!heroRef.current) return; state.currentY += (state.targetY - state.currentY) * 0.06; state.currentScale += (state.targetScale - state.currentScale) * 0.06; state.currentMarkOpacity += (state.targetMarkOpacity - state.currentMarkOpacity) * 0.08; state.currentMarkY += (state.targetMarkY - state.currentMarkY) * 0.08; state.currentMarkScale += (state.targetMarkScale - state.currentMarkScale) * 0.08; state.currentMarkBlur += (state.targetMarkBlur - state.currentMarkBlur) * 0.08; heroRef.current.style.setProperty("--hero-img-y", `${state.currentY.toFixed(2)}px`); heroRef.current.style.setProperty("--hero-img-scale", state.currentScale.toFixed(4)); heroRef.current.style.setProperty("--hero-mark-opacity", state.currentMarkOpacity.toFixed(3)); heroRef.current.style.setProperty("--hero-mark-y", `${state.currentMarkY.toFixed(2)}px`); heroRef.current.style.setProperty("--hero-mark-scale", state.currentMarkScale.toFixed(4)); heroRef.current.style.setProperty("--hero-mark-blur", `${state.currentMarkBlur.toFixed(2)}px`); state.raf = window.requestAnimationFrame(tick); }; updateTargets(); tick(); window.addEventListener("scroll", updateTargets, { passive: true }); window.addEventListener("resize", updateTargets); return () => { window.removeEventListener("scroll", updateTargets); window.removeEventListener("resize", updateTargets); window.cancelAnimationFrame(state.raf); }; }, []); return (
Sinatra LABS

{"MAKE YOUR NEXT AI MOVE"}

Strategies and Solutions

Schedule a Working Session
); } /* ---------- Tools ticker ---------- */ function ToolsBanner() { const tools = [ { key: "claude", label: "Claude", logo: "assets/tool-logos/claude.png" }, { key: "openai", label: "OpenAI", logo: "assets/tool-logos/openai.png" }, { key: "gemini", label: "Gemini", logo: "assets/tool-logos/gemini.png" }, { key: "aws", label: "AWS", logo: "assets/tool-logos/aws.png" }, { key: "gcp", label: "Google Cloud", logo: "assets/tool-logos/gcp.png" }, { key: "vercel", label: "Vercel", logo: "assets/tool-logos/vercel.png" }, { key: "postgresql", label: "PostgreSQL", logo: "assets/tool-logos/postgresql.png" }, { key: "supabase", label: "Supabase", logo: "assets/tool-logos/supabase.png" }, { key: "snowflake", label: "Snowflake", logo: "assets/tool-logos/snowflake.png" }, { key: "hubspot", label: "HubSpot", logo: "assets/tool-logos/hubspot.png" }, { key: "n8n", label: "n8n", logo: "assets/tool-logos/n8n.png" }, { key: "docker", label: "Docker", logo: "assets/tool-logos/docker.png" }, ]; return (

Tools We Use

{[0, 1].map((track) => (
{tools.map((tool) => (
{tool.label}
))}
))}
); } /* ---------- Proof strip ---------- */ function Proof() { const offerings = [ { n: "01", title: "AI preparation strategy", copy: "Choose where AI belongs, where it does not, and the first system worth building.", }, { n: "02", title: "CRM and database systems", copy: "Clean records, connect sources, create owner views, and make follow-up visible.", }, { n: "03", title: "Institutional-caliber research", copy: "Gather sources, preserve judgment, and produce decision briefs leadership can trust.", }, { n: "04", title: "New-hire onboarding systems", copy: "Turn company knowledge into guided paths, checklists, and answers new people can use.", }, { n: "05", title: "Custom tools and products", copy: "Create internal tools, portals, websites, and web apps around real work.", }, ]; return (

MAKE YOUR NEXT AI MOVE

Strategies and Solutions

{offerings.map((item, index) => ( {item.n}

{item.title}

{item.copy}

))}

Bring one messy workflow, tool stack, research question, or product idea. Leave with the first build worth scoping.

Schedule a working session
); } Object.assign(window, { useInView, Reveal, SplitLines, Nav, Hero, ToolsBanner, Proof, isTeamPagePath });