// shared.jsx — atoms, helpers, and hooks shared across sections
var useState = React.useState, useEffect = React.useEffect, useLayoutEffect = React.useLayoutEffect, useRef = React.useRef;

var SITEWORKS_MINIFIED_ASSETS = new Set([
  "assets/beacon-face.png",
  "assets/beacon-think-1.png",
  "assets/beacon-think-2.png",
  "assets/beacon-think-3.png",
  "assets/beacon-think-4.png",
  "assets/clients-color/doubleai.png",
  "assets/clients-color/ezraider.png",
  "assets/clients-color/lwf.png",
  "assets/clients-color/moonshot.png",
  "assets/clients-color/perspective.png",
  "assets/clients-color/phasev.png",
  "assets/clients-color/sunrun.png",
  "assets/clients-color/vimi.png",
  "assets/clients-color/violaventures.png",
  "assets/clients-dark/doubleai.png",
  "assets/clients-dark/ezraider.png",
  "assets/clients-dark/lwf.png",
  "assets/clients-dark/moonshot.png",
  "assets/clients-dark/perspective.png",
  "assets/clients-dark/phasev.png",
  "assets/clients-dark/sunrun.png",
  "assets/clients-dark/vimi.png",
  "assets/clients-dark/violaventures.png",
  "assets/clients/aiirify.png",
  "assets/clients/airtop.png",
  "assets/clients/allrify.png",
  "assets/clients/aspenmotor.png",
  "assets/clients/bilecki.png",
  "assets/clients/bluegrid.png",
  "assets/clients/bragabrothers.png",
  "assets/clients/carny.png",
  "assets/clients/conquer.png",
  "assets/clients/crew.png",
  "assets/clients/doubleai.png",
  "assets/clients/emerix.png",
  "assets/clients/enso.png",
  "assets/clients/ezraider.png",
  "assets/clients/francines.png",
  "assets/clients/hastings.png",
  "assets/clients/hihealth.png",
  "assets/clients/impala.png",
  "assets/clients/lwf.png",
  "assets/clients/marble.png",
  "assets/clients/moonshot.png",
  "assets/clients/peoplechoice.png",
  "assets/clients/perspective.png",
  "assets/clients/phasev.png",
  "assets/clients/powerchord.png",
  "assets/clients/scorebuddy.png",
  "assets/clients/scout.png",
  "assets/clients/sunrun.png",
  "assets/clients/tonic.png",
  "assets/clients/vimi.png",
  "assets/clients/violaventures.png",
  "assets/clients/visionon.png",
  "assets/clients/zenzap.png",
  "assets/clips/airtop-desktop.png",
  "assets/clips/bluegrid-desktop.png",
  "assets/clips/bluegrid-mobile.png",
  "assets/clips/doubleai-desktop.png",
  "assets/clips/doubleai-mobile.png",
  "assets/clips/enso-desktop.png",
  "assets/clips/ezraider-desktop.png",
  "assets/clips/ezraider-mobile.png",
  "assets/clips/hastings-desktop.png",
  "assets/clips/hastings-mobile.png",
  "assets/clips/icostore-desktop.png",
  "assets/clips/icostore-mobile.png",
  "assets/clips/impala-desktop.png",
  "assets/clips/impala-mobile.png",
  "assets/clips/ldr-desktop.png",
  "assets/clips/majestic-desktop.png",
  "assets/clips/moonshot-desktop.png",
  "assets/clips/powerchord-desktop.png",
  "assets/clips/powerchord-mobile.png",
  "assets/clips/vimi-desktop.png",
  "assets/clips/vimi-mobile.png",
  "assets/clips/viola-desktop.png",
  "assets/clutch-wordmark.png",
  "assets/convert-texture.png",
  "assets/cta-orbit.png",
  "assets/headshots/chantel-jeffers-face.png",
  "assets/headshots/chantel-jeffers.png",
  "assets/headshots/daivik-patel.png",
  "assets/headshots/francine-demelo-face.png",
  "assets/headshots/francine-demelo.png",
  "assets/headshots/gilad-water.png",
  "assets/headshots/julian-parr.png",
  "assets/headshots/louis-long.png",
  "assets/headshots/lucas-braga.png",
  "assets/headshots/matthew-lee.png",
  "assets/headshots/tanner-luke.png",
  "assets/hero-beam.png",
  "assets/hero-orbit-bg.png",
  "assets/how-2.png",
  "assets/how-step1.png",
  "assets/how-step2.png",
  "assets/how-step3.png",
  "assets/k-rv-after-desktop.png",
  "assets/k-rv-before-desktop.png",
  "assets/k-rv-card-needs.png",
  "assets/k-rv-card-review.png",
  "assets/k-rv-card-services.png",
  "assets/k-rv-card-systems.png",
  "assets/k-rv-card-work.png",
  "assets/logo-cro.png",
  "assets/logo-ezraider.png",
  "assets/logo-francine.png",
  "assets/logo-siteworks-dark.png",
  "assets/logo-siteworks-light-t.png",
  "assets/rv-atmosphere.png",
  "assets/taurist-mark.png",
  "assets/trustpilot-wordmark.png",
]);

// Resolve a project-relative asset path to its inlined blob URL when the page
// has been bundled for offline use (window.__resources is keyed by path), and
// fall back to the relative path on the live site.
function assetUrl(p) {
  if (!p) return p;
  if (/^(https?:|\/\/|data:|blob:|\/siteworks-static\/)/i.test(p)) return p;
  var R = (typeof window !== "undefined" && window.__resources) || {};
  if (R[p]) return R[p];
  var resolved = SITEWORKS_MINIFIED_ASSETS.has(p)
    ? p.replace(/^assets\//, "assets-min/").replace(/\.(png|jpg|jpeg|webp)$/i, ".webp")
    : p;
  if (typeof window !== "undefined" && window.location && window.location.protocol === "file:") return resolved;
  return "/siteworks-static/" + resolved.replace(/^\/+/, "");
}

// --- Logo wordmark ---
function Logo({ height = 26, dark = false }) {
  // dark=true means we're sitting on a LIGHT background (use the dark-ink wordmark)
  var src = dark ? assetUrl("assets/logo-siteworks-light-t.png") : assetUrl("assets/logo-siteworks-dark.png");
  return <img src={src} alt="Siteworks" style={{ height, width: "auto", display: "block", transition: "opacity .2s" }} />;
}

// --- responsive: true when viewport is at/below the breakpoint ---
function useIsMobile(bp = 720) {
  const q = `(max-width: ${bp}px)`;
  const [m, setM] = useState(typeof window !== "undefined" && window.matchMedia(q).matches);
  useEffect(() => {
    const mq = window.matchMedia(q);
    const fn = (e) => setM(e.matches);
    setM(mq.matches);
    mq.addEventListener("change", fn);
    return () => mq.removeEventListener("change", fn);
  }, []);
  return m;
}

// --- scroll-reveal: adds .in/.seen when element enters viewport ---
// .in is also force-added by App's safety net (throttled/offscreen contexts), so it
// can't drive opacity/blur animations safely. .seen is added ONLY by a genuine
// in-viewport intersection (live timeline); .settled lands 1.4s later as a wall-clock
// guarantee. The premium card "materialize" keys off .seen / .settled so it can never
// stay stuck on its hidden 0% frame in throttled/capture contexts.
function useReveal(options = {}) {
  const ref = useRef(null);
  useLayoutEffect(() => {
    const el = ref.current;
    if (!el) return;
    let settleTimer;
    el.classList.add("reveal-pending");
    if (!("IntersectionObserver" in window)) {
      el.classList.remove("reveal-pending");
      el.classList.add("in", "seen", "settled");
      return;
    }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) {
          el.classList.remove("reveal-pending");
          el.classList.add("in", "seen");
          settleTimer = setTimeout(() => el.classList.add("settled"), 1400);
          io.unobserve(el);
        }
      });
    }, { threshold: options.threshold ?? 0.18, rootMargin: options.rootMargin ?? "0px 0px -8% 0px" });
    io.observe(el);
    return () => {
      io.disconnect();
      clearTimeout(settleTimer);
      el.classList.remove("reveal-pending");
    };
  }, []);
  return ref;
}

// section header block (eyebrow + headline + optional lead)
function SectionHead({ eyebrow, children, lead, align = "left", maxWidth = 640, style }) {
  const ref = useReveal();
  return (
    <div ref={ref} className="sec-head" style={{ maxWidth, margin: align === "center" ? "0 auto" : undefined, textAlign: align, ...style }}>
      <div className="eyebrow" style={{ justifyContent: align === "center" ? "center" : "flex-start" }}>
        <span className="dot" />{eyebrow}
      </div>
      <h2 className="h-section">{children}</h2>
      {lead && <p className="p-lead">{lead}</p>}
    </div>
  );
}

// --- WordWaves: split text into words, fade each up in one of 3 random waves ---
function WordWaves({ text, tokens, base = 0.5, step = 0.12, jitter = 0.03 }) {
  const parts = React.useMemo(() => {
    const arr = tokens || String(text).split(/\s+/).filter(Boolean).map((w) => ({ w }));
    return arr.map((t) => ({ ...t, g: Math.floor(Math.random() * 3), j: (Math.random() * 2 - 1) * jitter }));
  }, []);
  return parts.map((t, i) => (
    <React.Fragment key={i}>
      <span className={"ld-word" + (t.gradient ? " gradient-text" : "")}
        style={{ animationDelay: (base + t.g * step + t.j).toFixed(3) + "s" }}>{t.w}</span>
      {" "}
    </React.Fragment>
  ));
}

// status pill
function Pill({ kind, children }) {
  return <span className={`pill pill-${kind}`}><span className="pdot" />{children}</span>;
}

// tool connection chip
function ToolChip({ icon, children }) {
  return <span className="tool-chip">{icon && <i className={icon} />}{children}</span>;
}

// glossy brand orb — the iconography system
function Orb({ icon, tone = "magenta", size = 48 }) {
  return (
    <span className={`orb orb-${tone}`} style={{ width: size, height: size }}>
      <i className={icon} style={{ fontSize: Math.round(size * 0.44) }} />
    </span>
  );
}

Object.assign(window, { assetUrl, Logo, useReveal, WordWaves, SectionHead, Pill, ToolChip, Orb });
