/* Facere — cinematic scroll landing Three sections, three production-art backgrounds, one continuous camera push. */ const { useEffect, useRef, useState } = React; // Mobile heuristic — captured once at boot. Used to skip expensive // effects (extra particles, heavy scrub smoothing) on phones. const IS_MOBILE = typeof window !== "undefined" && window.matchMedia("(max-width: 760px), (pointer: coarse) and (max-width: 900px)").matches; /* ───────────────── PixelLogo ───────────────── Bitmap glyphs rendered as a CSS grid of s, so we control individual pixel colors / glow / animations. Original blocky design (no copyrighted mark). */ // 7 wide × 9 tall chunky glyphs, designed in the style of the reference: // thick stems, rounded/beveled corners on C/E/R, no diagonals. const GLYPH_W = 7; const GLYPH_H = 9; const GLYPHS = { F: ["#######", "#######", "##.....", "##.....", "######.", "######.", "##.....", "##.....", "##....."], A: [".#####.", "#######", "##...##", "##...##", "#######", "#######", "##...##", "##...##", "##...##"], C: [".######", "#######", "##.....", "##.....", "##.....", "##.....", "##.....", "#######", ".######"], E: ["#######", "#######", "##.....", "##.....", "######.", "######.", "##.....", "#######", "#######"], R: ["######.", "#######", "##...##", "##...##", "######.", "#####..", "##.##..", "##..##.", "##...##"] }; function useResponsiveLogoScale(word = "FACERE") { const [scale, setScale] = useState(() => computeLogoScale(word)); useEffect(() => { let raf = 0; const onResize = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => setScale(computeLogoScale(word))); }; window.addEventListener("resize", onResize); window.addEventListener("orientationchange", onResize); return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", onResize); window.removeEventListener("orientationchange", onResize); }; }, [word]); return scale; } function computeLogoScale(word) { // Total logo width = letters * GLYPH_W * scale + (letters-1) * scale * 1.6 // Solve for scale that fits the viewport with side margins. if (typeof window === "undefined") return 11; const vw = window.innerWidth; const letters = word.length; const sideMargin = vw < 480 ? 0.10 : vw < 760 ? 0.12 : 0.16; // each side const usable = vw * (1 - 2 * sideMargin); const denom = letters * GLYPH_W + (letters - 1) * 1.6; const fit = Math.floor(usable / denom); // Clamp so it doesn't get too small or absurdly large on ultrawide. return Math.max(4, Math.min(11, fit)); } function PixelLogo({ word = "FACERE", scale, gap = 0 }) { const responsiveScale = useResponsiveLogoScale(word); const px = scale ?? responsiveScale; const letters = word.split(""); return /*#__PURE__*/React.createElement("div", { className: "pixel-logo", "data-text": word }, /*#__PURE__*/React.createElement("div", { className: "pixel-logo-grid", style: { gap: `0 ${px * 1.6}px` } }, letters.map((ch, li) => { const g = GLYPHS[ch]; if (!g) return null; return /*#__PURE__*/React.createElement("div", { key: li, className: "pixel-letter", style: { gridTemplateColumns: `repeat(${GLYPH_W}, ${px}px)`, gridTemplateRows: `repeat(${GLYPH_H}, ${px}px)`, gap: `${gap}px` } }, g.flatMap((row, ri) => row.split("").map((c, ci) => /*#__PURE__*/React.createElement("span", { key: `${ri}-${ci}`, className: c === "#" ? "px on" : "px" })))); })), /*#__PURE__*/React.createElement("div", { className: "pixel-logo-ghost ghost-r", "aria-hidden": "true" }, /*#__PURE__*/React.createElement(PixelLogoStatic, { word: word, scale: px, gap: gap, color: "rgba(255,80,120,0.45)" })), /*#__PURE__*/React.createElement("div", { className: "pixel-logo-ghost ghost-c", "aria-hidden": "true" }, /*#__PURE__*/React.createElement(PixelLogoStatic, { word: word, scale: px, gap: gap, color: "rgba(33,234,255,0.55)" }))); } function PixelLogoStatic({ word, scale, gap, color }) { const letters = word.split(""); return /*#__PURE__*/React.createElement("div", { className: "pixel-logo-grid", style: { gap: `0 ${scale * 1.6}px` } }, letters.map((ch, li) => { const g = GLYPHS[ch]; if (!g) return null; return /*#__PURE__*/React.createElement("div", { key: li, className: "pixel-letter", style: { gridTemplateColumns: `repeat(${GLYPH_W}, ${scale}px)`, gridTemplateRows: `repeat(${GLYPH_H}, ${scale}px)`, gap: `${gap}px` } }, g.flatMap((row, ri) => row.split("").map((c, ci) => /*#__PURE__*/React.createElement("span", { key: `${ri}-${ci}`, className: "px", style: c === "#" ? { background: color, boxShadow: "none" } : { background: "transparent" } })))); })); } /* ───────────────── SmokeLayer ───────────────── Pixel-art smoke puffs above each chimney, positioned as % over the bg image. Each puff is a chunky, low-res-feeling translucent disc that drifts up + fades. */ function SmokeLayer({ chimneys, scale = 1, intensity = 1 }) { // Each chimney emits N puffs on staggered phases. const PUFFS_PER = IS_MOBILE ? 2 : 3; const puffs = []; chimneys.forEach((ch, ci) => { for (let p = 0; p < PUFFS_PER; p++) { const delay = p / PUFFS_PER * ch.dur + ci * 0.3; puffs.push({ id: `${ci}-${p}`, x: ch.x, y: ch.y, size: ch.size * (0.85 + p % 2 * 0.25), dur: ch.dur, delay, drift: ch.drift + (p % 3 - 1) * 0.4 }); } }); return /*#__PURE__*/React.createElement("div", { className: "smoke-layer", style: { "--smoke-scale": scale, "--smoke-intensity": intensity } }, puffs.map(p => /*#__PURE__*/React.createElement("span", { key: p.id, className: "smoke-puff", style: { left: `${p.x}%`, top: `${p.y}%`, width: `${p.size}px`, height: `${p.size}px`, animationDuration: `${p.dur}s`, animationDelay: `-${p.delay}s`, "--drift": `${p.drift}rem` } }))); } /* ───────────────── EmberLayer ───────────────── Tiny pulsing orange points scattered across the horizon — gives the city /factory a flicker without recreating any structure in CSS. */ function EmberLayer({ count = 18, area }) { const embers = useRef(null); if (!embers.current) { const arr = []; for (let i = 0; i < count; i++) { arr.push({ x: area.x + Math.random() * area.w, y: area.y + Math.random() * area.h, size: 2 + Math.random() * 2, dur: 1.6 + Math.random() * 3, delay: Math.random() * 4, hue: Math.random() > 0.7 ? "warm" : "soft" }); } embers.current = arr; } return /*#__PURE__*/React.createElement("div", { className: "ember-layer" }, embers.current.map((e, i) => /*#__PURE__*/React.createElement("span", { key: i, className: `ember ember-${e.hue}`, style: { left: `${e.x}%`, top: `${e.y}%`, width: `${e.size}px`, height: `${e.size}px`, animationDuration: `${e.dur}s`, animationDelay: `-${e.delay}s` } }))); } /* ───────────────── SparkLayer ───────────────── For factory interior — small orange sparks bursting from the welding zone. */ function SparkLayer() { const sparks = useRef(null); if (!sparks.current) { const arr = []; const count = IS_MOBILE ? 7 : 14; for (let i = 0; i < count; i++) { arr.push({ a: -90 + (Math.random() - 0.5) * 160, // angle deg d: 18 + Math.random() * 36, // distance size: 2 + Math.random() * 2, dur: 0.6 + Math.random() * 1.0, delay: Math.random() * 1.6 }); } sparks.current = arr; } return /*#__PURE__*/React.createElement("div", { className: "spark-layer" }, /*#__PURE__*/React.createElement("span", { className: "spark-core" }), sparks.current.map((s, i) => { const rad = s.a * Math.PI / 180; const tx = Math.cos(rad) * s.d; const ty = Math.sin(rad) * s.d; return /*#__PURE__*/React.createElement("span", { key: i, className: "spark", style: { "--tx": `${tx}px`, "--ty": `${ty}px`, width: `${s.size}px`, height: `${s.size}px`, animationDuration: `${s.dur}s`, animationDelay: `-${s.delay}s` } }); })); } /* ───────────────── ChipPulses ───────────────── Subtle pulsing orange dots over the PCB area. */ function ChipPulses() { // hand-placed % over the chip image const allDots = [{ x: 76, y: 38, dur: 2.4, delay: 0 }, { x: 82, y: 28, dur: 2.0, delay: 0.7 }, { x: 88, y: 33, dur: 2.8, delay: 1.2 }, { x: 73, y: 60, dur: 2.2, delay: 0.4 }, { x: 80, y: 75, dur: 2.6, delay: 1.0 }, { x: 92, y: 55, dur: 2.0, delay: 1.6 }, { x: 70, y: 48, dur: 3.0, delay: 0.2 }, { x: 95, y: 70, dur: 2.4, delay: 0.9 }]; const dots = IS_MOBILE ? allDots.filter((_, i) => i % 2 === 0) : allDots; return /*#__PURE__*/React.createElement("div", { className: "chip-pulses" }, dots.map((d, i) => /*#__PURE__*/React.createElement("span", { key: i, className: "chip-pulse", style: { left: `${d.x}%`, top: `${d.y}%`, animationDuration: `${d.dur}s`, animationDelay: `-${d.delay}s` } })), /*#__PURE__*/React.createElement("span", { className: "chip-glow" })); } /* ───────────────── HeroSection ───────────────── */ function HeroSection({ bgRef, contentRef }) { // Chimney positions (% over the exterior factory image) — hand-tuned to artwork. // The three tall chimneys cluster at ~74%, 78%, 83% with tops near y≈49.5%. // Smaller stacks at 71%, 80%, 85.5%. // Image 1: assets/exterior-factory.png const chimneys = [{ x: 71.5, y: 51.0, size: 22, dur: 7.2, drift: -0.3 }, { x: 74.5, y: 49.5, size: 32, dur: 8.0, drift: -0.4 }, { x: 78.5, y: 50.4, size: 30, dur: 7.6, drift: 0.2 }, { x: 80.5, y: 51.0, size: 22, dur: 7.0, drift: 0.3 }, { x: 83.5, y: 49.8, size: 32, dur: 8.2, drift: -0.2 }, { x: 86.0, y: 51.4, size: 22, dur: 7.4, drift: 0.4 }]; return /*#__PURE__*/React.createElement("section", { className: "scene scene-hero", "data-screen-label": "01 Hero" }, /*#__PURE__*/React.createElement("div", { className: "bg-wrap", ref: bgRef }, /*#__PURE__*/React.createElement("div", { className: "image-frame" }, /*#__PURE__*/React.createElement("div", { className: "bg-image" }), /*#__PURE__*/React.createElement(SmokeLayer, { chimneys: IS_MOBILE ? chimneys.filter((_, i) => i % 2 === 0) : chimneys }), /*#__PURE__*/React.createElement(EmberLayer, { count: IS_MOBILE ? 10 : 22, area: { x: 32, y: 48, w: 65, h: 8 } })), /*#__PURE__*/React.createElement("div", { className: "vignette" }), /*#__PURE__*/React.createElement("div", { className: "haze" })), /*#__PURE__*/React.createElement("div", { className: "content content-hero", ref: contentRef }, /*#__PURE__*/React.createElement("div", { className: "logo-wrap" }, /*#__PURE__*/React.createElement(PixelLogo, { word: "FACERE" }), /*#__PURE__*/React.createElement("div", { className: "logo-scanlines" }))), /*#__PURE__*/React.createElement("div", { className: "scroll-cue", "aria-hidden": "true" }, /*#__PURE__*/React.createElement("span", { className: "scroll-cue-line" }), /*#__PURE__*/React.createElement("span", { className: "scroll-cue-label" }, "SCROLL"))); } /* ───────────────── ProductSection ───────────────── */ function ProductSection({ bgRef, contentRef }) { return /*#__PURE__*/React.createElement("section", { className: "scene scene-product", "data-screen-label": "02 Product" }, /*#__PURE__*/React.createElement("div", { className: "bg-wrap", ref: bgRef }, /*#__PURE__*/React.createElement("div", { className: "image-frame" }, /*#__PURE__*/React.createElement("div", { className: "bg-image" }), /*#__PURE__*/React.createElement("div", { className: "arm-pivot arm-left" }), /*#__PURE__*/React.createElement("div", { className: "arm-pivot arm-right" }), /*#__PURE__*/React.createElement(SparkLayer, null), /*#__PURE__*/React.createElement("div", { className: "conveyor-pulse" })), /*#__PURE__*/React.createElement("div", { className: "vignette product-vignette" }), /*#__PURE__*/React.createElement("div", { className: "haze" })), /*#__PURE__*/React.createElement("div", { className: "content content-product", ref: contentRef }, /*#__PURE__*/React.createElement("div", { className: "tag" }, /*#__PURE__*/React.createElement("span", { className: "tag-dot" }), /*#__PURE__*/React.createElement("span", null, "SYSTEM ONLINE \u2014 NODE 02")), /*#__PURE__*/React.createElement("h1", { className: "headline" }, "Facere is a", /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement("span", { className: "hl" }, "Hardware Design Agent")), /*#__PURE__*/React.createElement("p", { className: "body" }, "From natural-language requirements to engineering files, Facere executes an end-to-end agent workflow"))); } /* ───────────────── InstallSection ───────────────── */ function InstallSection({ bgRef, contentRef }) { const [copied, setCopied] = useState(false); const cmd = "curl -fsSL https://facere.ai/install.sh | sh"; const copy = async () => { try { await navigator.clipboard.writeText(cmd); setCopied(true); setTimeout(() => setCopied(false), 1600); } catch (e) { setCopied(true); setTimeout(() => setCopied(false), 1600); } }; return /*#__PURE__*/React.createElement("section", { className: "scene scene-install", "data-screen-label": "03 Install" }, /*#__PURE__*/React.createElement("div", { className: "bg-wrap", ref: bgRef }, /*#__PURE__*/React.createElement("div", { className: "image-frame" }, /*#__PURE__*/React.createElement("div", { className: "bg-image" }), /*#__PURE__*/React.createElement(ChipPulses, null)), /*#__PURE__*/React.createElement("div", { className: "vignette" }), /*#__PURE__*/React.createElement("div", { className: "haze haze-deep" })), /*#__PURE__*/React.createElement("div", { className: "content content-install", ref: contentRef }, /*#__PURE__*/React.createElement("div", { className: "tag" }, /*#__PURE__*/React.createElement("span", { className: "tag-dot" }), /*#__PURE__*/React.createElement("span", null, "NODE 03 \u2014 SUBSTRATE")), /*#__PURE__*/React.createElement("h1", { className: "headline" }, "Get started with Facere"), /*#__PURE__*/React.createElement("p", { className: "subheadline" }, "Install the CLI"), /*#__PURE__*/React.createElement("div", { className: `cmd-box ${copied ? "copied" : ""}`, onClick: copy }, /*#__PURE__*/React.createElement("div", { className: "cmd-bar" }, /*#__PURE__*/React.createElement("span", { className: "cmd-led" }), /*#__PURE__*/React.createElement("span", { className: "cmd-led led-amber" }), /*#__PURE__*/React.createElement("span", { className: "cmd-led led-cyan" }), /*#__PURE__*/React.createElement("span", { className: "cmd-title" }, "~/facere \u2014 bash")), /*#__PURE__*/React.createElement("div", { className: "cmd-body" }, /*#__PURE__*/React.createElement("span", { className: "cmd-prompt" }, "$"), /*#__PURE__*/React.createElement("span", { className: "cmd-text" }, cmd), /*#__PURE__*/React.createElement("span", { className: "cmd-cursor" })), /*#__PURE__*/React.createElement("div", { className: "cmd-hint" }, copied ? "✓ COPIED" : "CLICK TO COPY")), /*#__PURE__*/React.createElement("a", { className: "docs-link", href: "#", onClick: e => e.preventDefault() }, "docs.facere.ai/cli ", /*#__PURE__*/React.createElement("span", { className: "arrow" }, "\u2192")))); } /* ───────────────── App / ScrollScene ───────────────── Pin the stage, push the camera deeper through each section. Each section's bg scales up while the next fades in over it. */ function App() { const stageRef = useRef(null); const heroBg = useRef(null), heroContent = useRef(null); const prodBg = useRef(null), prodContent = useRef(null); const instBg = useRef(null), instContent = useRef(null); const heroSceneRef = useRef(null); const prodSceneRef = useRef(null); const instSceneRef = useRef(null); const [progress, setProgress] = useState(0); const lastDepth = useRef(-1); useEffect(() => { if (!window.gsap || !window.ScrollTrigger) return; const { gsap, ScrollTrigger } = window; gsap.registerPlugin(ScrollTrigger); const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; // Initial state: only hero visible gsap.set(prodSceneRef.current, { opacity: 0 }); gsap.set(instSceneRef.current, { opacity: 0 }); const ctx = gsap.context(() => { // Master timeline pinned to stage; scrub linked to scroll. const tl = gsap.timeline({ scrollTrigger: { trigger: ".scroll-track", start: "top top", end: "bottom bottom", // On mobile, scrub:true (no smoothing lag) feels snappier and // skips an extra rAF interpolation per frame. scrub: IS_MOBILE ? true : 0.6, onUpdate: self => { // Re-render only when the displayed depth (0..100) changes — // otherwise we re-render the whole HUD on every scroll frame. const depth = Math.round(self.progress * 100); if (depth !== lastDepth.current) { lastDepth.current = depth; setProgress(self.progress); } } } }); // Timing map (extra-long product dwell): // 0.00 → 0.20 Hero camera push // 0.20 → 0.25 Hero → Product cross-fade // 0.25 → 0.82 Product (very long dwell) // 0.82 → 0.86 Product → Install cross-fade // 0.86 → 1.00 Install settle // Phase 1: Hero camera push-in tl.to(heroBg.current, { scale: reduceMotion ? 1.05 : 1.35, ease: "none" }, 0); tl.to(heroContent.current, { scale: reduceMotion ? 1.02 : 1.18, opacity: 0, ease: "none" }, 0); // Phase 2: cross-fade hero → product tl.to(heroSceneRef.current, { opacity: 0, ease: "none" }, 0.20); tl.fromTo(prodSceneRef.current, { opacity: 0 }, { opacity: 1, ease: "none" }, 0.20); tl.fromTo(prodBg.current, { scale: reduceMotion ? 1.05 : 1.18 }, { scale: 1, ease: "none" }, 0.20); tl.fromTo(prodContent.current, { y: 40, opacity: 0 }, { y: 0, opacity: 1, ease: "none" }, 0.23); // Phase 3: Long product dwell with slow camera creep tl.to(prodBg.current, { scale: reduceMotion ? 1.05 : 1.22, ease: "none" }, 0.27); tl.to(prodContent.current, { scale: reduceMotion ? 1.02 : 1.08, opacity: 0, ease: "none", duration: 0.06 }, 0.78); // Phase 4: cross-fade product → install tl.to(prodSceneRef.current, { opacity: 0, ease: "none" }, 0.82); tl.fromTo(instSceneRef.current, { opacity: 0 }, { opacity: 1, ease: "none" }, 0.82); tl.fromTo(instBg.current, { scale: reduceMotion ? 1.05 : 1.18 }, { scale: 1, ease: "none" }, 0.82); tl.fromTo(instContent.current, { y: 40, opacity: 0 }, { y: 0, opacity: 1, ease: "none" }, 0.86); // Phase 5: Install gentle settle tl.to(instBg.current, { scale: reduceMotion ? 1.0 : 1.05, ease: "none" }, 0.90); }, stageRef); return () => ctx.revert(); }, []); // Section indicator const section = progress < 0.22 ? 0 : progress < 0.84 ? 1 : 2; return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", { className: "hud" }, /*#__PURE__*/React.createElement("div", { className: "hud-brand" }, "FACERE"), /*#__PURE__*/React.createElement("div", { className: "hud-nav", "aria-hidden": "true" }, ["EXT", "INT", "SUB"].map((label, i) => /*#__PURE__*/React.createElement("div", { key: i, className: `hud-step ${section === i ? "is-active" : section > i ? "is-past" : ""}` }, /*#__PURE__*/React.createElement("span", { className: "hud-step-dot" }), /*#__PURE__*/React.createElement("span", { className: "hud-step-label" }, label)))), /*#__PURE__*/React.createElement("div", { className: "hud-meta" }, /*#__PURE__*/React.createElement("span", { className: "hud-meta-line" }), /*#__PURE__*/React.createElement("span", null, "DEPTH ", Math.round(progress * 100).toString().padStart(2, "0")))), /*#__PURE__*/React.createElement("div", { className: "scroll-track" }, /*#__PURE__*/React.createElement("div", { className: "stage", ref: stageRef }, /*#__PURE__*/React.createElement("div", { className: "scene-stack" }, /*#__PURE__*/React.createElement("div", { className: "scene-slot", ref: heroSceneRef }, /*#__PURE__*/React.createElement(HeroSection, { bgRef: heroBg, contentRef: heroContent })), /*#__PURE__*/React.createElement("div", { className: "scene-slot", ref: prodSceneRef }, /*#__PURE__*/React.createElement(ProductSection, { bgRef: prodBg, contentRef: prodContent })), /*#__PURE__*/React.createElement("div", { className: "scene-slot", ref: instSceneRef }, /*#__PURE__*/React.createElement(InstallSection, { bgRef: instBg, contentRef: instContent }))), /*#__PURE__*/React.createElement("div", { className: "grain" })))); } ReactDOM.createRoot(document.getElementById("root")).render(/*#__PURE__*/React.createElement(App, null));