/* ========================================================= Shadowbiz — core.jsx Mark (Option C: solid overlap + shadow), Wordmark, icons, Button, reveal hooks. Exports to window for other files. ========================================================= */ const { useState, useEffect, useRef, useCallback } = React; /* ---- Option C mark: two rounded panels, overlap shows a darker shadow ---- */ function Mark({ size = 40, variant = "light", animate = false, idle = false }) { // variant picks the back/front/overlap tones const sets = { light: { back: "#1d3a2e", front: "#84cc16", ovr: "#2e5016" }, // on cream dark: { back: "#f5f1e8", front: "#84cc16", ovr: "#65a30d" }, // on ink/teal lime: { back: "#1d3a2e", front: "#f5f1e8", ovr: "#2e4a3e" }, // on lime mono: { back: "#1d3a2e", front: "#1d3a2e", ovr: "#1d3a2e" }, }; const c = sets[variant] || sets.light; const cid = "ovr" + useRef(Math.random().toString(36).slice(2)).current; return ( {/* back panel — the business */} {/* front panel — Shadowbiz, alongside */} {/* the shadow where they meet */} ); } /* ---- wordmark ---- */ function Wordmark({ size = 21, tone = "light", className = "" }) { const shadow = tone === "dark" ? "var(--cream)" : "var(--ink)"; const biz = tone === "dark" ? "var(--lime)" : "var(--lime-deep)"; return ( shadowbiz ); } function Brand({ tone = "light", markSize = 32, wordSize = 21, animate = false }) { return ( ); } /* ---- line icons — Lucide-flavoured, 1.75 stroke ---- */ function Icon({ name, size = 22, stroke = 1.75 }) { const P = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round" }; const paths = { ear: <>, target: <>, blocks: <>, rocket: <>, compass: <>, layers: <>, spark: <>, mail: <>, phone: <>, globe: <>, arrow: <>, check: <>, pin: <>, clock: <>, school: <>, bag: <>, quote: <>, }; return {paths[name] || null}; } /* ---- button ---- */ function Btn({ kind = "primary", children, href, withArrow = false, className = "", onClick, type }) { const cls = `btn btn-${kind} ${className}`; const inner = <>{children}{withArrow && }; if (href) return {inner}; return ; } /* ---- reveal-on-scroll (synchronous scroll handler; rAF-free so it works even when the tab is backgrounded / rAF is throttled) ---- */ function useReveal() { useEffect(() => { let els = Array.from(document.querySelectorAll(".reveal")); const check = () => { const h = window.innerHeight || 800; els = els.filter(e => { if (e.getBoundingClientRect().top < h * 0.92) { e.classList.add("in"); return false; } return true; }); if (!els.length) detach(); }; const detach = () => { window.removeEventListener("scroll", check); window.removeEventListener("resize", check); }; check(); window.addEventListener("scroll", check, { passive: true }); window.addEventListener("resize", check, { passive: true }); // safety: never leave content hidden — force instant (bypass any frozen transition) const safety = setTimeout(() => { els.forEach(e => { e.style.transition = "none"; e.classList.add("in"); }); }, 1800); return () => { detach(); clearTimeout(safety); }; }); } /* ---- scroll position (for nav) ---- */ function useScrolled(at = 40) { const [s, setS] = useState(false); useEffect(() => { const on = () => setS(window.scrollY > at); on(); window.addEventListener("scroll", on, { passive: true }); return () => window.removeEventListener("scroll", on); }, [at]); return s; } Object.assign(window, { Mark, Wordmark, Brand, Icon, Btn, useReveal, useScrolled, React, useState, useEffect, useRef, useCallback });