// screens/home.jsx — Editorial homepage with rotating 3D hero

const PRINT_COLORS = [
{ id: "graphite", hex: "#2a2a2a", name: "Graphite PLA" },
{ id: "bone", hex: "#e8e3d8", name: "Bone PLA" },
{ id: "stone", hex: "#8a8478", name: "Stone PLA" },
{ id: "slate", hex: "#3d4148", name: "Slate PETG" },
{ id: "fog", hex: "#b7b5ae", name: "Fog PLA" },
{ id: "rust", hex: "#8a4a36", name: "Rust PLA" }];


const MATERIALS = [
{ code: "PETG", name: "Glycol-mod PET", blurb: "Tougher cousin of PLA. Water-tight, mildly flexible, good for outdoor parts.", spec: ["semi-gloss", "≤ 0.16mm layer", "in stock — black"] },
{ code: "PLA", name: "Polylactic Acid", blurb: "The default. Sharp detail, low warp, biodegradable corn-derived plastic.", spec: ["matte / silk", "≤ 0.12mm layer", "awaiting stock"] },
{ code: "ASA", name: "Acrylonitrile Styrene Acrylate", blurb: "UV-stable engineering plastic for outdoor parts. Solvent-smoothable. Printed in our heated chamber.", spec: ["matte", "≤ 0.20mm layer", "awaiting stock"] },
{ code: "TPU", name: "Thermoplastic PU", blurb: "Flexible, 95A shore rubber. Gaskets, grips, and squishy props.", spec: ["soft-touch", "≤ 0.20mm layer", "awaiting stock"] }];


// ─── NEW: conversion-focused components ─────────────────────────────────────

// Light material catalog used by the homepage price calculator
const CALC_MATERIALS = [
{ id: "PETG", density: 1.27, perGram: 0.13, label: "PETG", type: "PETG" },
{ id: "PLA", density: 1.24, perGram: 0.08, label: "PLA", type: "PLA" },
{ id: "ASA", density: 1.07, perGram: 0.15, label: "ASA", type: "ASA" }];


function useAnimatedNumber(target, durationMs = 600) {
  const [val, setVal] = React.useState(target);
  const fromRef = React.useRef(target);
  React.useEffect(() => {
    fromRef.current = val;
    const start = performance.now();
    const from = fromRef.current;
    let raf;
    const tick = (now) => {
      const t = Math.min(1, (now - start) / durationMs);
      const eased = 1 - Math.pow(1 - t, 3);
      setVal(from + (target - from) * eased);
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [target]);
  return val;
}

// ─── Seamless looping video ─────────────────────────────────────────────────
// Single <video loop> with preload="auto" + black container background. The
// browser's native loop is frame-accurate on H.264; any visible seam is
// usually a missing keyframe at frame 0 (re-encode fix) rather than a JS issue.
function SeamlessLoopVideo({ src, className, style }) {
  return (
    <div className={className} style={{ position: "relative", overflow: "hidden", background: "#000", ...style }}>
      <video
        src={src}
        autoPlay loop muted playsInline preload="auto"
        style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" }}
      />
    </div>
  );
}

// ─── Marquee ticker — live stats strip ─────────────────────────────────────
function Marquee() {
  // Auto-incrementing prints-shipped counter (lightweight social proof)
  const [shipped, setShipped] = React.useState(1284);
  React.useEffect(() => {
    const id = setInterval(() => setShipped((v) => v + (Math.random() < 0.4 ? 1 : 0)), 3200);
    return () => clearInterval(id);
  }, []);
  const items = [
  `${shipped.toLocaleString()} prints shipped this month`,
  "02 printers online · queue 14m",
  "Next bed slot · 14:32 today",
  "Files parsed locally · never uploaded",
  "Free quote · no signup",
  "£0.18 / gram PLA",
  "48 hour standard turnaround",
  "0.08mm finest layer height"];

  // Triple for seamless loop
  const tripled = [...items, ...items, ...items];
  return (
    <div style={{
      position: "relative",
      overflow: "hidden",
      borderBottom: "1px solid var(--line)",
      background: "color-mix(in srgb, var(--ink) 4%, transparent)",
      padding: "10px 0"
    }}>
      <style>{`
        @keyframes sync-marquee { from { transform: translateX(0); } to { transform: translateX(-33.333%); } }
      `}</style>
      <div style={{
        display: "flex",
        whiteSpace: "nowrap",
        animation: "sync-marquee 60s linear infinite",
        width: "max-content"
      }}>
        {tripled.map((it, i) =>
        <span key={i} className="mono upper" style={{
          color: "var(--ink-soft)",
          padding: "0 24px",
          borderRight: "1px solid var(--line)",
          fontSize: 11,
          letterSpacing: "0.06em"
        }}>{it}</span>
        )}
      </div>
    </div>);

}

// ─── Hero (conversion) — dropzone-first, 3D companion ──────────────────────
function HeroConvert({ swatch, setSwatch, onUpload }) {
  const fileInputRef = React.useRef(null);
  const [dragOver, setDragOver] = React.useState(false);
  const [parsing, setParsing] = React.useState(false);
  const [error, setError] = React.useState("");

  const handleFile = (f) => {
    if (!f) return;
    if (!/\.(stl|3mf|step|stp)$/i.test(f.name)) {setError("Supported formats: STL, 3MF, STEP.");return;}
    setError("");setParsing(true);
    const reader = new FileReader();
    reader.onerror = () => {setError("Couldn't read file.");setParsing(false);};
    reader.onload = () => {
      setParsing(false);
      onUpload(reader.result, f.name);
    };
    reader.readAsArrayBuffer(f);
  };

  // Rotating "now printing" ticker — 3 recent jobs cycle
  const jobs = [
  { who: "Eli, Margate", part: "Impeller v2", price: 18.40, time: "2h 22m" },
  { who: "Hana, Bristol", part: "Aero bracket", price: 14.20, time: "1h 48m" },
  { who: "Mo, Glasgow", part: "Trefoil ring", price: 8.60, time: "1h 15m" }];

  const [jobIdx, setJobIdx] = React.useState(0);
  React.useEffect(() => {
    const id = setInterval(() => setJobIdx((i) => (i + 1) % jobs.length), 4200);
    return () => clearInterval(id);
  }, []);

  return (
    <section style={{
      padding: "clamp(10px, 1.2vw, 16px) var(--pad-x) clamp(24px, 3vw, 44px)",
      display: "grid",
      gridTemplateColumns: "1.15fr 0.9fr",
      gap: "clamp(28px, 4vw, 56px)",
      alignItems: "stretch",
      boxSizing: "border-box"
    }}>
      {/* LEFT — value prop + dropzone */}
      <div style={{ display: "flex", flexDirection: "column" }}>
        <div>
          <div className="mono upper" style={{ color: "var(--muted)", marginBottom: 14, letterSpacing: "0.14em" }}>
            Design · Simulation · 3D printing — London UK
          </div>
          <h1 className="serif" style={{
            fontSize: "clamp(38px, 4.4vw, 68px)",
            lineHeight: 1.0, margin: 0, letterSpacing: "-0.035em", textWrap: "balance"
          }}>
            Precision parts,<br />
            <span style={{ fontStyle: "italic" }}>designed &amp; made.</span>
          </h1>
          <p style={{ maxWidth: 460, marginTop: 16, fontSize: 15.5, lineHeight: 1.55, color: "var(--ink-soft)", textWrap: "pretty" }}>
            We design, simulate and manufacture precision parts — CAD
            modelling, CFD analysis and engineering-grade FDM printing.
            Upload a file for an instant quote, or bring us a sketch.
          </p>
        </div>

        {/* DROPZONE — the hero CTA */}
        <div style={{ marginTop: 28, flex: 1, display: "flex", flexDirection: "column" }}>
          <button
            onDragOver={(e) => {e.preventDefault();setDragOver(true);}}
            onDragLeave={() => setDragOver(false)}
            onDrop={(e) => {e.preventDefault();setDragOver(false);handleFile(e.dataTransfer.files?.[0]);}}
            onClick={() => fileInputRef.current?.click()}
            className="sync-drop-glow"
            style={{
              all: "unset", cursor: "pointer", boxSizing: "border-box",
              width: "100%", flex: 1,
              padding: "clamp(26px, 3vw, 48px) 28px",
              borderRadius: 14,
              border: dragOver ? "1.5px dashed var(--primary)" : "1.5px dashed color-mix(in srgb, var(--primary) 40%, var(--line))",
              background: dragOver ? "var(--primary-soft)" : "var(--card)",
              boxShadow: "var(--shadow-card)",
              display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 14,
              textAlign: "center",
              transition: "all 0.15s"
            }}>
            <div className="sync-pulse" style={{
              width: 48, height: 48, borderRadius: 999,
              background: "var(--primary)", color: "#fff",
              display: "grid", placeItems: "center"
            }}>
              {parsing ?
              <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="2">
                  <circle cx="11" cy="11" r="8" opacity="0.25" />
                  <path d="M11 3 A8 8 0 0 1 19 11" strokeLinecap="round">
                    <animateTransform attributeName="transform" type="rotate" from="0 11 11" to="360 11 11" dur="1s" repeatCount="indefinite" />
                  </path>
                </svg> :

              <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M12 17V5M6 11l6-6 6 6" />
                  <path d="M4 20h16" />
                </svg>
              }
            </div>
            <div className="serif" style={{ fontSize: 25, letterSpacing: "-0.02em", lineHeight: 1 }}>
              {parsing ? "Reading…" : "Drop your 3D file here"}
            </div>
            <div className="mono upper" style={{ color: "var(--muted)" }}>
              or click to browse · STL / 3MF / STEP · ≤ 50MB
            </div>
            <input ref={fileInputRef} type="file" accept=".stl,.3mf,.step,.stp" style={{ display: "none" }}
            onChange={(e) => handleFile(e.target.files?.[0])} />
          </button>

          {error && <div className="mono" style={{ color: "#b3261e", marginTop: 8, fontSize: 12 }}>{error}</div>}

          <div style={{ display: "flex", gap: 14, marginTop: 16, alignItems: "center", flexWrap: "wrap" }}>
            <button onClick={() => onUpload(SyncSTL.buildCubeSTL(20), "calibration_cube_20mm.stl")} className="mono" style={{
              all: "unset", cursor: "pointer",
              padding: "8px 14px", borderRadius: 999, whiteSpace: "nowrap",
              border: "1px solid var(--line)", fontSize: 11.5, color: "var(--ink-soft)"
            }}>↥ Try a 20mm cube</button>
            <span className="mono upper" style={{ color: "var(--muted)", display: "inline-flex", gap: 6, alignItems: "center", whiteSpace: "nowrap" }}>
              <svg width="11" height="11" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5">
                <rect x="2.5" y="5" width="7" height="5.5" rx="0.5" />
                <path d="M4 5V3.5a2 2 0 0 1 4 0V5" />
              </svg>
              Files never leave your browser
            </span>
          </div>

          <div className="mono" style={{
            marginTop: 14, display: "inline-flex", alignItems: "center", gap: 8,
            padding: "7px 12px", borderRadius: 999, fontSize: 12, fontWeight: 600,
            background: "color-mix(in srgb, var(--success) 10%, transparent)", color: "var(--success)",
          }}>
            Launch offer — 20% off your first order · code LAUNCH20
          </div>
        </div>

        {/* Stat strip */}
        <div className="mono upper r-2" style={{
          marginTop: 28, paddingTop: 18,
          borderTop: "1px solid var(--line)",
          display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 14
        }}>
          {[
          ["£0.18", "per gram PLA"],
          ["48hr", "dispatch"],
          ["±0.2mm", "tolerance"],
          ["400×400", "mm print bed"]].
          map(([v, l]) =>
          <div key={l}>
              <div className="serif" style={{ fontSize: 24, letterSpacing: "-0.02em", lineHeight: 1, color: "var(--ink)", whiteSpace: "nowrap" }}>{v}</div>
              <div style={{ color: "var(--muted)", marginTop: 4, whiteSpace: "nowrap", fontSize: 10.5 }}>{l}</div>
            </div>
          )}
        </div>
      </div>

      {/* RIGHT — half CAD / half manufactured split reveal.
          Hidden on phones (m-hide): the dropzone IS the mobile hero, and the
          2MB image would cost a screenful of scroll + data. lazy keeps the
          hidden element from downloading it. */}
      <div className="m-hide" style={{
        position: "relative", alignSelf: "stretch", minHeight: 0,
        borderRadius: 14, overflow: "hidden", border: "1px solid var(--line)",
        background: "linear-gradient(90deg, #212e4d 0%, #212e4d 50%, #666667 50%, #666667 100%)",
        display: "flex", alignItems: "center", justifyContent: "center"
      }}>
        <img src="assets/hero-split-v3.webp" alt="CAD design and manufactured part" loading="lazy"
          style={{ width: "100%", height: "100%", objectFit: "contain", objectPosition: "center", display: "block" }} />
      </div>
    </section>);

}

// ─── Price Calculator — interactive slider + live 3D + price ──────────────
function PriceCalculator({ onContinue }) {
  const [size, setSize] = React.useState(40);
  const [matId, setMatId] = React.useState("PLA");
  const [infill, setInfill] = React.useState(15);
  const material = CALC_MATERIALS.find((m) => m.id === matId);

  // Debounce the buffer rebuild so dragging the slider doesn't flicker the viewer
  const [bufferSize, setBufferSize] = React.useState(40);
  React.useEffect(() => {
    const id = setTimeout(() => setBufferSize(size), 200);
    return () => clearTimeout(id);
  }, [size]);
  const stlBuffer = React.useMemo(() => SyncSTL.buildCubeSTL(bufferSize), [bufferSize]);

  // Analytical estimate for a cube of edge s
  const volMm3 = size * size * size;
  const volCm3 = volMm3 / 1000;
  const surfaceAreaMm2 = 6 * size * size;
  const fakeFile = {
    volCm3, dims: [size, size, size],
    analysis: SyncSTL.quickAnalysisForPrimitive([size, size, size], volCm3, surfaceAreaMm2)
  };
  const slice = SyncSTL.estimateSlice(fakeFile, {
    layerHeight: "0.20", infillPct: infill, walls: 3, nozzleWidth: 0.4, topBottomLayers: 4, supportsEnabled: false
  }, material);

  const mass = slice.massG;
  const q = SyncSTL.quote(slice, material.type, 1);
  const totalInc = q.total; // no VAT

  // Animate the price so size changes feel tactile
  const animatedPrice = useAnimatedNumber(totalInc, 280);

  return (
    <section style={{
      padding: "var(--pad-y) var(--pad-x)",
      borderTop: "1px solid var(--line)",
      background: "color-mix(in srgb, var(--ink) 3%, transparent)"
    }}>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 2.2fr", gap: 48, alignItems: "start", marginBottom: 36 }}>
        <div className="mono upper" style={{ color: "var(--muted)" }}>01 — Estimate</div>
        <h2 className="serif" style={{
          fontSize: "clamp(36px, 4.6vw, 72px)",
          lineHeight: 1.02, margin: 0, letterSpacing: "-0.025em", maxWidth: 900
        }}>
          Don't have a file yet? <span style={{ fontStyle: "italic" }}>Play </span>
          with a cube.
        </h2>
      </div>

      <div style={{
        display: "grid",
        gridTemplateColumns: "1.05fr 1fr",
        gap: 36, alignItems: "stretch"
      }}>
        {/* Viewer */}
        <div style={{
          position: "relative", aspectRatio: "1.1 / 1",
          borderRadius: 8, overflow: "hidden",
          background: "color-mix(in srgb, var(--ink) 4%, transparent)",
          border: "1px solid var(--line)"
        }}>
          <SyncSTL.Viewer arrayBuffer={stlBuffer} color="#2a2a2a" />
          <div className="mono upper" style={{ position: "absolute", top: 14, left: 14, color: "var(--ink-soft)", pointerEvents: "none" }}>
            Cube {size}×{size}×{size} mm
          </div>
          <div className="mono" style={{ position: "absolute", bottom: 14, left: 14, color: "var(--muted)", fontSize: 11 }}>
            drag to rotate
          </div>
          <div className="mono" style={{ position: "absolute", bottom: 14, right: 14, color: "var(--ink-soft)", fontSize: 11, fontVariantNumeric: "tabular-nums" }}>
            {volCm3.toFixed(1)} cm³
          </div>
        </div>

        {/* Controls + price */}
        <div style={{ display: "flex", flexDirection: "column", gap: 24 }}>
          <div>
            <div className="mono upper" style={{ color: "var(--muted)", marginBottom: 8 }}>Estimated total</div>
            <div style={{ display: "flex", alignItems: "baseline", gap: 14 }}>
              <div className="serif" style={{
                fontSize: "clamp(72px, 9vw, 132px)",
                lineHeight: 0.85, letterSpacing: "-0.035em",
                color: "var(--success)",
                fontVariantNumeric: "tabular-nums"
              }}>£{animatedPrice.toFixed(2)}</div>
              <div className="mono" style={{ color: "var(--muted)", fontSize: 11 }}>no VAT</div>
            </div>
            <div className="mono" style={{ color: "var(--ink-soft)", marginTop: 8, fontSize: 12 }}>
              {mass.toFixed(1)}g · {slice.printHours < 1 ? `${Math.round(slice.printHours * 60)} min` : `${Math.floor(Math.round(slice.printHours * 60) / 60)}h ${Math.round(slice.printHours * 60) % 60}m`} print · {material.label}
            </div>
          </div>

          <div>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 8 }}>
              <div className="mono upper" style={{ color: "var(--ink)" }}>Size</div>
              <div className="mono" style={{ color: "var(--muted)", fontSize: 11 }}>{size} mm cube edge</div>
            </div>
            <input type="range" min="15" max="200" step="5" value={size}
            onChange={(e) => setSize(parseInt(e.target.value))}
            style={{ width: "100%", accentColor: "var(--ink)" }} />
            <div className="mono" style={{ display: "flex", justifyContent: "space-between", color: "var(--muted)", fontSize: 10, marginTop: 4 }}>
              <span>15mm</span><span>100mm</span><span>200mm</span>
            </div>
          </div>

          <div>
            <div className="mono upper" style={{ color: "var(--ink)", marginBottom: 8 }}>Material</div>
            <div className="keep-cols" style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", border: "1px solid var(--line)", borderRadius: 8, overflow: "hidden" }}>
              {CALC_MATERIALS.map((m, i) => {
                const active = m.id === matId;
                return (
                  <button key={m.id} onClick={() => setMatId(m.id)} style={{
                    all: "unset", cursor: "pointer", padding: "12px 8px", textAlign: "center",
                    background: active ? "var(--ink)" : "transparent",
                    color: active ? "var(--bg)" : "var(--ink)",
                    borderLeft: i > 0 ? "1px solid var(--line)" : "none"
                  }}>
                    <div className="mono" style={{ fontSize: 13 }}>{m.label}</div>
                    <div className="mono" style={{ fontSize: 10, opacity: 0.65, marginTop: 2 }}>£{m.perGram.toFixed(2)}/g</div>
                  </button>);

              })}
            </div>
          </div>

          <div>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 8 }}>
              <div className="mono upper" style={{ color: "var(--ink)" }}>Infill</div>
              <div className="mono" style={{ color: "var(--muted)", fontSize: 11 }}>{infill}% gyroid</div>
            </div>
            <input type="range" min="5" max="100" step="5" value={infill}
            onChange={(e) => setInfill(parseInt(e.target.value))}
            style={{ width: "100%", accentColor: "var(--ink)" }} />
          </div>

          <button onClick={() => onContinue(stlBuffer, `cube_${size}mm.stl`)} className="btn-primary" style={{
            padding: "16px 22px", borderRadius: 999,
            fontSize: 15,
            display: "flex", justifyContent: "space-between", alignItems: "center"
          }}>
            <span>Continue with this cube</span>
            <span>→</span>
          </button>
          <div className="mono" style={{ color: "var(--muted)", fontSize: 11, textAlign: "center" }}>
            or drop your real STL above for an accurate quote
          </div>
        </div>
      </div>
    </section>);

}

// ─── Preset grid — popular parts pre-priced, click to start ────────────────
function PresetGrid({ onPick }) {
  // Each preset builds its own STL on click — no heavy parsing on page load
  const presets = React.useMemo(() => [
  {
    key: "cube", title: "Calibration cube", sub: "20×20×20mm",
    build: () => ({ stl: SyncSTL.buildCubeSTL(20), name: "calibration_cube_20mm.stl" }),
    volCm3: 20 ** 3 / 1000, surfaceAreaMm2: 6 * 400, dims: [20, 20, 20],
    icon: <svg viewBox="0 0 60 60" fill="none" stroke="currentColor" strokeWidth="1.2">
        <path d="M20 22 L30 17 L40 22 L40 38 L30 43 L20 38 Z" />
        <path d="M20 22 L30 27 L40 22 M30 27 L30 43" />
      </svg>
  },
  {
    key: "stand", title: "Phone stand", sub: "80×50×6mm + back",
    build: () => ({ stl: SyncSTL.buildPhoneStandSTL(), name: "phone_stand.stl" }),
    volCm3: (80 * 50 * 6 + 80 * 8 * 70) / 1000, surfaceAreaMm2: 2 * (80 * 50 + 80 * 6 + 50 * 6) + 2 * (80 * 8 + 80 * 70 + 8 * 70),
    dims: [80, 50, 76],
    icon: <svg viewBox="0 0 60 60" fill="none" stroke="currentColor" strokeWidth="1.2">
        <path d="M14 42 L46 42 L46 44 L14 44 Z" />
        <path d="M28 42 L28 16 L31 16 L31 42" />
      </svg>
  },
  {
    key: "knob", title: "Filter knob", sub: "Ø40 × 30mm",
    build: () => ({ stl: SyncSTL.buildCylinderSTL(20, 30), name: "filter_knob.stl" }),
    volCm3: Math.PI * 20 * 20 * 30 / 1000, surfaceAreaMm2: 2 * Math.PI * 20 * 30 + 2 * Math.PI * 20 * 20, dims: [40, 40, 30],
    icon: <svg viewBox="0 0 60 60" fill="none" stroke="currentColor" strokeWidth="1.2">
        <ellipse cx="30" cy="22" rx="14" ry="4" />
        <path d="M16 22 L16 40 A14 4 0 0 0 44 40 L44 22" />
        <ellipse cx="30" cy="22" rx="6" ry="1.5" opacity="0.4" />
      </svg>
  },
  {
    key: "tube", title: "Pen pot", sub: "Ø44 wall × 100mm",
    build: () => ({ stl: SyncSTL.buildTubeSTL(22, 19, 100), name: "pen_pot.stl" }),
    volCm3: Math.PI * (22 * 22 - 19 * 19) * 100 / 1000, surfaceAreaMm2: 2 * Math.PI * 22 * 100 + 2 * Math.PI * 19 * 100, dims: [44, 44, 100],
    icon: <svg viewBox="0 0 60 60" fill="none" stroke="currentColor" strokeWidth="1.2">
        <ellipse cx="30" cy="17" rx="13" ry="3.5" />
        <ellipse cx="30" cy="17" rx="9" ry="2.5" />
        <path d="M17 17 L17 47 A13 3.5 0 0 0 43 47 L43 17" />
      </svg>
  }],
  []);

  // Pre-compute prices (PLA, 20% infill, standard layer)
  const priced = presets.map((p) => {
    const fakeFile = {
      volCm3: p.volCm3,
      dims: p.dims,
      analysis: SyncSTL.quickAnalysisForPrimitive(p.dims, p.volCm3, p.surfaceAreaMm2)
    };
    const slice = SyncSTL.estimateSlice(fakeFile,
    { layerHeight: "0.20", infillPct: 20, walls: 3, nozzleWidth: 0.4, topBottomLayers: 4, supportsEnabled: false },
    { density: 1.24, type: "PLA" }
    );
    const total = SyncSTL.quote(slice, "PLA", 1).total;
    return { ...p, price: total, mass: slice.massG, hours: slice.printHours };
  });

  return (
    <section style={{ padding: "var(--pad-y) var(--pad-x)", borderTop: "1px solid var(--line)" }}>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 2.2fr", gap: 48, alignItems: "start", marginBottom: 36 }}>
        <div className="mono upper" style={{ color: "var(--muted)" }}>02 — Popular</div>
        <h2 className="serif" style={{
          fontSize: "clamp(36px, 4.6vw, 72px)",
          lineHeight: 1.02, margin: 0, letterSpacing: "-0.025em", maxWidth: 900
        }}>
          Or start with something <span style={{ fontStyle: "italic" }}>simple</span>.
        </h2>
      </div>
      <div style={{
        display: "grid",
        gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))",
        gap: 12
      }}>
        {priced.map((p) =>
        <button key={p.key} onClick={() => {const built = p.build();onPick(built.stl, built.name);}}
        style={{
          all: "unset", cursor: "pointer", boxSizing: "border-box",
          display: "flex", flexDirection: "column",
          padding: 18,
          border: "1px solid var(--line)", borderRadius: 8,
          background: "color-mix(in srgb, var(--ink) 3%, transparent)",
          transition: "background 0.12s, border-color 0.12s",
          minHeight: 220
        }}
        onMouseEnter={(e) => {e.currentTarget.style.background = "color-mix(in srgb, var(--ink) 7%, transparent)";e.currentTarget.style.borderColor = "color-mix(in srgb, var(--ink) 25%, transparent)";}}
        onMouseLeave={(e) => {e.currentTarget.style.background = "color-mix(in srgb, var(--ink) 3%, transparent)";e.currentTarget.style.borderColor = "var(--line)";}}>
          
            <div style={{ width: 56, height: 56, color: "var(--ink-soft)", marginBottom: 12 }}>{p.icon}</div>
            <div className="serif" style={{ fontSize: 24, letterSpacing: "-0.02em", lineHeight: 1, marginTop: "auto" }}>{p.title}</div>
            <div className="mono" style={{ marginTop: 8, color: "var(--ink-soft)", fontSize: 11 }}>{p.sub}</div>
            <div className="mono" style={{ marginTop: 4, color: "var(--muted)", fontSize: 10.5 }}>
              {p.mass.toFixed(1)}g · {p.hours < 1 ? `${Math.round(p.hours * 60)}min` : `${Math.floor(Math.round(p.hours * 60) / 60)}h ${Math.round(p.hours * 60) % 60}m`}
            </div>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginTop: 16, paddingTop: 16, borderTop: "1px solid var(--line)" }}>
              <span className="serif" style={{ fontSize: 28, letterSpacing: "-0.02em" }}>£{p.price.toFixed(2)}</span>
              <span className="mono" style={{ fontSize: 11, color: "var(--primary)", fontWeight: 600 }}>Quote →</span>
            </div>
          </button>
        )}
      </div>
    </section>);

}

// ─── Compare table — Sync vs other services with animated bars ─────────────
function CompareTable() {
  const rows = [
  { label: "Sync", price: 4.60, highlight: true, lead: "48 hr", notes: "Small UK farm · 4 materials" },
  { label: "Service A · DE", price: 14.20, lead: "5 days", notes: "Marketplace · 80+ materials" },
  { label: "Service B · NL", price: 18.60, lead: "5–7 days", notes: "Enterprise · industrial" },
  { label: "Service C · US", price: 24.80, lead: "10 days", notes: "Shipping + customs add £18" }];

  const max = Math.max(...rows.map((r) => r.price));

  const ref = React.useRef(null);
  const [seen, setSeen] = React.useState(false);
  React.useEffect(() => {
    if (!ref.current) return;
    const ob = new IntersectionObserver((entries) => {
      entries.forEach((e) => {if (e.isIntersecting) setSeen(true);});
    }, { threshold: 0.2 });
    ob.observe(ref.current);
    return () => ob.disconnect();
  }, []);

  return (
    <section ref={ref} style={{ padding: "var(--pad-y) var(--pad-x)", borderTop: "1px solid var(--line)" }}>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 2.2fr", gap: 48, alignItems: "start", marginBottom: 36 }}>
        <div className="mono upper" style={{ color: "var(--muted)" }}>03 — Compare</div>
        <h2 className="serif" style={{
          fontSize: "clamp(36px, 4.6vw, 72px)",
          lineHeight: 1.02, margin: 0, letterSpacing: "-0.025em", maxWidth: 900
        }}>
          A 20g PLA part, <span style={{ fontStyle: "italic" }}>quoted four ways</span>.
        </h2>
      </div>

      <div style={{ display: "flex", flexDirection: "column" }}>
        {rows.map((r, i) => {
          const w = r.price / max * 100;
          return (
            <div key={i} style={{
              display: "grid",
              gridTemplateColumns: "160px 1fr 80px",
              gap: 18,
              alignItems: "center",
              padding: "16px 0",
              borderTop: i === 0 ? "1px solid var(--line)" : "none",
              borderBottom: "1px solid var(--line)"
            }}>
              <div>
                <div className="serif" style={{ fontSize: 20, letterSpacing: "-0.018em", lineHeight: 1, fontStyle: r.highlight ? "italic" : "normal" }}>{r.label}</div>
                <div className="mono" style={{ color: "var(--muted)", fontSize: 10.5, marginTop: 4 }}>{r.lead} · {r.notes}</div>
              </div>
              <div style={{ position: "relative", height: 22 }}>
                <div style={{
                  width: seen ? w + "%" : "0%",
                  height: "100%",
                  background: r.highlight ? "var(--primary)" : "color-mix(in srgb, var(--ink) 25%, transparent)",
                  borderRadius: 3,
                  transition: `width 0.9s cubic-bezier(0.2, 0.8, 0.2, 1) ${i * 120}ms`
                }} />
              </div>
              <div className="serif" style={{ fontSize: 28, letterSpacing: "-0.02em", textAlign: "right", fontVariantNumeric: "tabular-nums" }}>£{r.price.toFixed(2)}</div>
            </div>);

        })}
      </div>
      <div className="mono" style={{ marginTop: 14, color: "var(--muted)", fontSize: 11 }}>
        Indicative quotes for a 20g, 20cm³ PLA part at standard quality.
        Competitor prices observed during 2026 Q1; vary by part and shipping origin.
      </div>
    </section>);

}

// ─── Big final CTA before footer ──────────────────────────────────────────
function BigCTA({ onUpload }) {
  return (
    <section style={{
      padding: "calc(var(--pad-y) * 1.15) var(--pad-x)",
      borderTop: "1px solid var(--line)",
      background: "var(--ink)",
      color: "var(--bg)",
      textAlign: "center"
    }}>
      <div className="mono upper" style={{ color: "color-mix(in srgb, var(--bg) 55%, transparent)", marginBottom: 20 }}>
        Quote any part · 8 seconds
      </div>
      <h2 className="serif" style={{
        fontSize: "clamp(48px, 7vw, 120px)",
        lineHeight: 0.9, margin: 0, letterSpacing: "-0.035em",
        textWrap: "balance"
      }}>
        Drop a file.<br />
        <span style={{ fontStyle: "italic" }}>Get a number.</span>
      </h2>
      <p style={{ maxWidth: 520, margin: "24px auto 0", fontSize: 16.5, lineHeight: 1.55, color: "color-mix(in srgb, var(--bg) 75%, transparent)" }}>
        We parse your STL in the browser, run a manifold &amp; overhang check,
        and quote it — without you signing up.
      </p>
      <div style={{ display: "flex", justifyContent: "center", gap: 14, marginTop: 30, flexWrap: "wrap" }}>
        <button onClick={() => onUpload()} style={{
          all: "unset", cursor: "pointer",
          padding: "18px 32px", borderRadius: 999,
          background: "var(--bg)", color: "var(--ink)",
          fontSize: 16, fontWeight: 500,
          display: "inline-flex", alignItems: "center", gap: 12
        }}>
          <span>Upload an STL</span>
          <span>→</span>
        </button>
        <button onClick={() => onUpload(SyncSTL.buildCubeSTL(20), "calibration_cube_20mm.stl")} className="mono" style={{
          all: "unset", cursor: "pointer",
          padding: "18px 26px", borderRadius: 999,
          border: "1px solid color-mix(in srgb, var(--bg) 35%, transparent)",
          color: "var(--bg)", fontSize: 13
        }}>Try a 20mm cube</button>
      </div>
      <div className="mono upper" style={{ color: "color-mix(in srgb, var(--bg) 50%, transparent)", marginTop: 36 }}>
        Files never leave your browser · No signup · Pay later
      </div>
    </section>);

}


// ─── Floating sticky quote pill — appears after hero scroll ───────────────
function FloatingCTA({ onUpload }) {
  const [visible, setVisible] = React.useState(false);
  React.useEffect(() => {
    const onScroll = () => setVisible(window.scrollY > 480);
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  if (!visible) return null;
  return (
    <div className="sync-floating-pill" style={{
      position: "fixed", bottom: 24, left: "50%",
      transform: "translateX(-50%)", zIndex: 40,
      pointerEvents: "none"
    }}>
      <button onClick={() => onUpload()} className="sync-pulse" style={{
        border: "none", appearance: "none", font: "inherit", cursor: "pointer",
        padding: "14px 22px", borderRadius: 999,
        background: "var(--primary)", color: "#fff",
        fontSize: 14, fontWeight: 600,
        display: "inline-flex", alignItems: "center", gap: 12,
        pointerEvents: "auto",
        boxShadow: "0 12px 36px rgba(16,24,40,0.22)"
      }}>
        <span style={{
          display: "inline-block", width: 8, height: 8, borderRadius: 999,
          background: "#fff"
        }} />
        <span>Quote a part</span>
        <span style={{ opacity: 0.7 }}>→</span>
      </button>
    </div>);

}


// ─── Trust strip — quantified credibility right under the hero ─────────────
function TrustStrip() {
  const items = [
  { stat: "48hr", label: "Standard lead time" },
  { stat: "±0.2mm", label: "Dimensional tolerance" },
  { stat: "100%", label: "Hand-inspected" }];

  return (
    <section style={{
      padding: "0 var(--pad-x)",
      borderTop: "1px solid var(--line)",
      borderBottom: "1px solid var(--line)"
    }}>
      <div className="r-cards" style={{
        display: "grid",
        gridTemplateColumns: "repeat(3, 1fr)",
        gap: 0
      }}>
        {items.map((it, i) =>
        <div key={i} style={{
          padding: "24px 22px",
          borderRight: i < 2 ? "1px solid var(--line)" : "none",
          paddingLeft: i === 0 ? 0 : 22,
          display: "flex", flexDirection: "column", gap: 6
        }}>
            <div className="serif" style={{
            fontSize: "clamp(26px, 3vw, 38px)",
            letterSpacing: "-0.025em", lineHeight: 1,
            fontVariantNumeric: "tabular-nums"
          }}>{it.stat}</div>
            <div className="mono upper" style={{ color: "var(--muted)" }}>{it.label}</div>
          </div>
        )}
      </div>
    </section>);

}

// ─── Consistent left-aligned section header used by every block ────────────
function SectionHeader({ index, lead, accent }) {
  return (
    <div style={{ marginBottom: 30, maxWidth: 1100 }}>
      <div className="mono upper" style={{ color: "var(--muted)", marginBottom: 12 }}>{index}</div>
      <h2 className="serif" style={{
        fontSize: "clamp(32px, 3.8vw, 56px)",
        lineHeight: 1.02, margin: 0, letterSpacing: "-0.025em",
        textWrap: "balance"
      }}>{lead}</h2>
    </div>);

}


function HeroBlade({ heroVariant, swatch, setSwatch, onUpload, onNav }) {
  return (
    <section style={{
      padding: "0 var(--pad-x) var(--pad-y)",
      display: "grid",
      gridTemplateColumns: "1.05fr 1fr",
      gap: "clamp(24px, 4vw, 56px)",
      alignItems: "stretch",
      minHeight: "calc(100vh - 60px)"
    }}>
      {/* Left: editorial copy */}
      <div style={{ display: "flex", flexDirection: "column", justifyContent: "space-between", paddingTop: "clamp(20px,4vw,48px)" }}>
        <div>
          <div className="mono upper" style={{ color: "var(--muted)", marginBottom: 18 }}>
            <span style={{ color: "var(--accent)" }}>●</span>&nbsp; 02 printers online · queue 14m
          </div>
          <h1 className="serif" style={{
            fontSize: "clamp(56px, 8.4vw, 132px)",
            lineHeight: 0.92,
            margin: 0,
            letterSpacing: "-0.035em",
            textWrap: "balance"
          }}>
            Print<br />
            <span style={{ fontStyle: "italic" }}>anything</span><br />
            you can model.
          </h1>
          <p style={{
            maxWidth: 440, marginTop: 28,
            fontSize: 17, lineHeight: 1.5, color: "var(--ink-soft)"
          }}>
            Sync is a small FDM print farm for hobbyists, makers, and one-off
            projects. Upload an STL, choose a material, and we'll dispatch it
            inside forty-eight hours.
          </p>
        </div>

        <div style={{ marginTop: 40 }}>
          <div style={{ display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
            <button onClick={onUpload} style={{
              all: "unset", cursor: "pointer",
              padding: "16px 26px", borderRadius: 999,
              background: "var(--ink)", color: "var(--bg)",
              fontSize: 15, fontWeight: 500,
              display: "inline-flex", alignItems: "center", gap: 10
            }}>
              <span>Upload an STL</span>
              <span style={{ fontSize: 18, lineHeight: 1 }}>→</span>
            </button>
            <button onClick={() => {const el = document.getElementById("gallery");if (el) window.scrollTo({ top: el.offsetTop - 60, behavior: "smooth" });}} className="mono" style={{
              border: "1px solid var(--line)", appearance: "none", font: "inherit", cursor: "pointer",
              padding: "16px 22px", borderRadius: 999,
              background: "transparent",
              fontSize: 12, color: "var(--ink-soft)"
            }}>
              See sample prints
            </button>
          </div>

          <div className="mono upper" style={{ marginTop: 36, color: "var(--muted)", display: "flex", gap: 28, flexWrap: "wrap" }}>
            <span><b style={{ color: "var(--ink)", fontWeight: 500 }}>£0.18</b> · /gram PLA</span>
            <span><b style={{ color: "var(--ink)", fontWeight: 500 }}>48hr</b> · turnaround</span>
            <span><b style={{ color: "var(--ink)", fontWeight: 500 }}>0.08mm</b> · finest layer</span>
          </div>
        </div>
      </div>

      {/* Right: 3D viewer */}
      <div style={{ position: "relative", minHeight: 480, display: "flex", flexDirection: "column" }}>
        <div style={{
          position: "absolute", top: 16, right: 0, zIndex: 2,
          display: "flex", alignItems: "center", gap: 10
        }}>
          <span className="mono upper" style={{ color: "var(--muted)" }}>filament</span>
          <div style={{ display: "flex", gap: 6 }}>
            {PRINT_COLORS.map((c) =>
            <button key={c.id} onClick={() => setSwatch(c.hex)} title={c.name} style={{
              all: "unset", cursor: "pointer",
              width: 22, height: 22, borderRadius: 999,
              background: c.hex,
              boxShadow: swatch === c.hex ? "0 0 0 1.5px var(--ink), 0 0 0 4px var(--bg)" : "inset 0 0 0 1px rgba(0,0,0,0.08)"
            }} />
            )}
          </div>
        </div>

        <div style={{ flex: 1, position: "relative" }}>
          <Viewer variant={heroVariant} color={swatch} materialKind="matte" />
          {/* corner ticks */}
          <Tick pos="tl" />
          <Tick pos="tr" />
          <Tick pos="bl" />
          <Tick pos="br" />
          <div className="mono upper" style={{
            position: "absolute", bottom: 16, left: 0,
            color: "var(--muted)"
          }}>
            Specimen 01 · {
            {
              blade: "Twisted Blade",
              torus: "Trefoil Knot",
              gear: "Cog 14T",
              bracket: "Aero Bracket",
              impeller: "Turbine Impeller"
            }[heroVariant] || "Turbine Impeller"
            } · drag to rotate
          </div>
          <div className="mono" style={{
            position: "absolute", bottom: 16, right: 0,
            color: "var(--muted)", fontSize: 11
          }}>
            128.4 g · 6h 22m print
          </div>
        </div>
      </div>
    </section>);

}

const Tick = ({ pos }) => {
  const s = { position: "absolute", width: 12, height: 12, color: "var(--ink-soft)" };
  const map = {
    tl: { top: 0, left: 0, borderTop: "1px solid currentColor", borderLeft: "1px solid currentColor" },
    tr: { top: 0, right: 0, borderTop: "1px solid currentColor", borderRight: "1px solid currentColor" },
    bl: { bottom: 0, left: 0, borderBottom: "1px solid currentColor", borderLeft: "1px solid currentColor" },
    br: { bottom: 0, right: 0, borderBottom: "1px solid currentColor", borderRight: "1px solid currentColor" }
  };
  return <span style={{ ...s, ...map[pos] }} />;
};

function HeroDropzone({ swatch, setSwatch, onUpload }) {
  return (
    <section style={{
      padding: "clamp(32px,5vw,72px) var(--pad-x) var(--pad-y)",
      minHeight: "calc(100vh - 60px)",
      display: "grid", gridTemplateColumns: "1fr", gap: 32
    }}>
      <div style={{ maxWidth: 900 }}>
        <div className="mono upper" style={{ color: "var(--muted)", marginBottom: 14 }}>
          <span style={{ color: "var(--accent)" }}>●</span>&nbsp; 02 printers online
        </div>
        <h1 className="serif" style={{
          fontSize: "clamp(56px, 8vw, 124px)",
          lineHeight: 0.94,
          margin: 0,
          letterSpacing: "-0.035em"
        }}>
          Drop in <span style={{ fontStyle: "italic" }}>an STL.</span><br />
          Get a price in <span style={{ fontStyle: "italic" }}>seconds.</span>
        </h1>
      </div>
      <button onClick={onUpload} style={{
        all: "unset", cursor: "pointer",
        border: "1.5px dashed var(--ink)",
        borderRadius: 8,
        padding: "clamp(48px,8vw,120px) 24px",
        textAlign: "center",
        display: "flex", flexDirection: "column", alignItems: "center", gap: 14,
        background: "color-mix(in srgb, var(--ink) 3%, transparent)"
      }}>
        <svg width="46" height="46" viewBox="0 0 46 46" fill="none" stroke="currentColor" strokeWidth="1.5">
          <path d="M23 32V14M14 23l9-9 9 9" strokeLinecap="round" strokeLinejoin="round" />
          <rect x="6" y="36" width="34" height="2" fill="currentColor" stroke="none" />
        </svg>
        <div className="serif" style={{ fontSize: 30, letterSpacing: "-0.02em" }}>Drop your model here</div>
        <div className="mono upper" style={{ color: "var(--muted)" }}>.STL · .3MF · .STEP · ≤ 50MB</div>
      </button>
    </section>);

}

function HeroGallery({ swatch, setSwatch, onUpload, samples }) {
  return (
    <section style={{
      padding: "clamp(28px,4vw,56px) var(--pad-x) var(--pad-y)",
      minHeight: "calc(100vh - 60px)",
      display: "flex", flexDirection: "column", gap: 24
    }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: 24, flexWrap: "wrap" }}>
        <h1 className="serif" style={{
          fontSize: "clamp(48px, 7.2vw, 108px)",
          lineHeight: 0.94, margin: 0, letterSpacing: "-0.035em"
        }}>
          A small <span style={{ fontStyle: "italic" }}>print farm</span><br />for makers.
        </h1>
        <button onClick={onUpload} style={{
          all: "unset", cursor: "pointer",
          padding: "14px 22px", borderRadius: 999,
          background: "var(--ink)", color: "var(--bg)",
          fontSize: 14, fontWeight: 500
        }}>Upload an STL →</button>
      </div>
      <div style={{
        flex: 1, display: "grid",
        gridTemplateColumns: "1.4fr 1fr 1fr",
        gridTemplateRows: "1fr 1fr",
        gap: 14, minHeight: 520
      }}>
        <GalleryTile color={swatch} variant="impeller" big label="Turbine Impeller · Graphite PLA" />
        <GalleryTile color="#8a8478" variant="bracket" label="Aero Bracket · Stone PLA" />
        <GalleryTile color="#3d4148" variant="gear" label="Cog · Slate PETG" />
        <GalleryTile color="#e8e3d8" variant="torus" label="Trefoil · Bone" />
        <GalleryTile color="#b7b5ae" variant="blade" label="Blade · Fog" />
      </div>
    </section>);

}

function GalleryTile({ color, variant, big, label }) {
  return (
    <div style={{
      gridColumn: big ? "1 / 2" : "auto",
      gridRow: big ? "1 / 3" : "auto",
      position: "relative", borderRadius: 6,
      background: "color-mix(in srgb, var(--ink) 4%, transparent)",
      overflow: "hidden",
      border: "1px solid var(--line)"
    }}>
      <Viewer variant={variant} color={color} interactive={false} autoRotate={true} />
      <div className="mono upper" style={{
        position: "absolute", left: 12, bottom: 10,
        color: "var(--ink-soft)"
      }}>{label}</div>
    </div>);

}

function HeroImage({ swatch, setSwatch, onUpload }) {
  return (
    <section style={{
      padding: "0 var(--pad-x) var(--pad-y)",
      display: "grid",
      gridTemplateColumns: "1.05fr 1fr",
      gap: "clamp(24px, 4vw, 56px)",
      alignItems: "stretch",
      minHeight: "calc(100vh - 60px)", fontFamily: "Montserrat"
    }}>
      <div style={{ display: "flex", flexDirection: "column", justifyContent: "space-between", paddingTop: "clamp(20px,4vw,48px)" }}>
        <div>
          <div className="mono upper" style={{ color: "var(--muted)", marginBottom: 18 }}>
            <span style={{ color: "var(--accent)" }}>●</span>&nbsp; 02 printers online · queue 14m
          </div>
          <h1 className="serif" style={{
            fontSize: "clamp(56px, 8.4vw, 132px)",
            lineHeight: 0.92, margin: 0, letterSpacing: "-0.035em", textWrap: "balance"
          }}>
            Print<br />
            <span style={{ fontStyle: "italic" }}>anything</span><br />
            you can model.
          </h1>
          <p style={{ maxWidth: 440, marginTop: 28, fontSize: 17, lineHeight: 1.5, color: "var(--ink-soft)" }}>
            Sync is a small FDM print farm for hobbyists, makers, and one-off
            projects. Upload an STL, choose a material, and we'll dispatch it
            inside forty-eight hours.
          </p>
        </div>
        <div style={{ marginTop: 40 }}>
          <div style={{ display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }}>
            <button onClick={onUpload} style={{
              all: "unset", cursor: "pointer", padding: "16px 26px", borderRadius: 999,
              background: "var(--ink)", color: "var(--bg)", fontSize: 15, fontWeight: 500,
              display: "inline-flex", alignItems: "center", gap: 10
            }}>
              <span>Upload an STL</span>
              <span style={{ fontSize: 18, lineHeight: 1 }}>→</span>
            </button>
            <button onClick={() => {const el = document.getElementById("gallery");if (el) window.scrollTo({ top: el.offsetTop - 60, behavior: "smooth" });}} className="mono" style={{
              border: "1px solid var(--line)", appearance: "none", font: "inherit", cursor: "pointer", padding: "16px 22px", borderRadius: 999,
              background: "transparent",
              fontSize: 12, color: "var(--ink-soft)"
            }}>See sample prints</button>
          </div>
          <div className="mono upper" style={{ marginTop: 36, color: "var(--muted)", display: "flex", gap: 28, flexWrap: "wrap" }}>
            <span><b style={{ color: "var(--ink)", fontWeight: 500 }}>£0.18</b> · /gram PLA</span>
            <span><b style={{ color: "var(--ink)", fontWeight: 500 }}>48hr</b> · turnaround</span>
            <span><b style={{ color: "var(--ink)", fontWeight: 500 }}>0.08mm</b> · finest layer</span>
          </div>
        </div>
      </div>

      <div style={{ position: "relative", minHeight: 480, display: "flex", flexDirection: "column" }}>
        <div style={{ flex: 1, position: "relative" }}>
          <image-slot
            id="hero-image"
            shape="rounded"
            radius="8"
            placeholder="Drop your hero photo here"
            style={{ width: "100%", height: "100%", minHeight: 480, display: "block" }} />
          
          <Tick pos="tl" />
          <Tick pos="tr" />
          <Tick pos="bl" />
          <Tick pos="br" />
          <div className="mono upper" style={{
            position: "absolute", bottom: 16, left: 0, color: "var(--muted)"
          }}>
            Hero image · drop a photo or render
          </div>
        </div>
      </div>
    </section>);

}

// Materials grid
function MaterialsSection() {
  return (
    <section id="materials" style={{
      padding: "var(--pad-y) var(--pad-x)",
      borderTop: "1px solid var(--line)"
    }}>
      <SectionHeader
        index="03 — Materials"
        lead={<>Four filaments, picked because they cover roughly <span style={{ fontStyle: "italic" }}>ninety-eight percent</span> of what you'll actually want to print.</>} />
      
      <div className="r-cards" style={{
        display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 0,
        borderTop: "1px solid var(--line)"
      }}>
        {MATERIALS.map((m, i) =>
        <div key={m.code} style={{
          padding: "24px 20px 24px 0",
          borderRight: i < MATERIALS.length - 1 ? "1px solid var(--line)" : "none",
          paddingLeft: i === 0 ? 0 : 20,
          display: "flex", flexDirection: "column", gap: 12
        }}>
            <div className="mono" style={{ fontSize: 11, color: "var(--muted)" }}>0{i + 1}</div>
            <div>
              <div className="serif" style={{ fontSize: 34, letterSpacing: "-0.02em", lineHeight: 1 }}>{m.code}</div>
              <div className="mono upper" style={{ color: "var(--muted)", marginTop: 6 }}>{m.name}</div>
            </div>
            <p style={{ margin: 0, fontSize: 14, color: "var(--ink-soft)", lineHeight: 1.5 }}>{m.blurb}</p>
            <div className="mono" style={{ marginTop: "auto", fontSize: 11, color: "var(--ink-soft)", display: "flex", flexDirection: "column", gap: 3 }}>
              {m.spec.map((s, j) => <div key={j}>· {s}</div>)}
            </div>
          </div>
        )}
      </div>
    </section>);

}

// Process / How it works
function ProcessSection() {
  const steps = [
  { n: "01", t: "Upload", b: "STL, 3MF, STEP — up to 50MB. We parse geometry on the fly and tell you if anything's non-manifold." },
  { n: "02", t: "Configure", b: "Pick a material, colour, layer height, and infill. The price updates in real-time as you sculpt the spec." },
  { n: "03", t: "Print", b: "Your job hits the queue on our RatRig V-Core 4 Hybrid — a large-format CoreXY machine — usually within the hour." },
  { n: "04", t: "Ship", b: "Each part is hand-inspected, packed, and dispatched via Royal Mail Tracked 48. A photo of the finished print arrives in your inbox before it leaves the studio." }];

  return (
    <section style={{
      padding: "var(--pad-y) var(--pad-x)",
      background: "color-mix(in srgb, var(--ink) 5%, transparent)"
    }}>
      <SectionHeader
        index="01 — Process"
        lead={<>From upload to <span style={{ fontStyle: "italic" }}>dispatch</span>, in under forty-eight hours.</>} />
      
      <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 18 }}>
        {steps.map((s) =>
        <div key={s.n} style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            <div className="mono" style={{ color: "var(--accent)", fontSize: 12 }}>{s.n}</div>
            <div className="serif" style={{ fontSize: 30, letterSpacing: "-0.02em", lineHeight: 1 }}>{s.t}</div>
            <p style={{ margin: 0, fontSize: 14, color: "var(--ink-soft)", lineHeight: 1.55 }}>{s.b}</p>
          </div>
        )}
      </div>
    </section>);

}

// Services — CAD design + CFD analysis + Print (with POA pricing)
function ServicesSection({ onUpload, onNav }) {
  const services = [
  {
    n: "01",
    tag: "CAD design",
    title: "From a sketch to a printable part.",
    body: "Send us a napkin, a hand-sketch, or a few reference photos. We'll model it parametrically in Fusion 360 or SolidWorks, dimensioned and ready for the bed. Two revisions included; engineering drawings on request.",
    meta: ["Parametric CAD", "STEP / STL out", "DFM review"],
    price: "POA",
    priceSub: "Typical £140–£600",
    slotId: "svc-cad",
    img: "assets/service-cad.webp",
    slotPlaceholder: "Drop a CAD render here"
  },
  {
    n: "02",
    tag: "CFD analysis",
    title: "Simulate before you print.",
    body: "Steady-state flow, pressure drop, thermal, or external aero — meshed in OpenFOAM and visualised in ParaView. Useful for ducts, manifolds, impellers, heat-sinks. Comes with a written report and the post-processed geometry.",
    meta: ["OpenFOAM solver", "ParaView visuals", "Written report"],
    price: "POA",
    priceSub: "Typical £280–£1,200",
    slotId: "svc-cfd",
    slotPlaceholder: "Drop a CFD plot here"
  },
  {
    n: "03",
    tag: "Print",
    title: "Or just upload an STL.",
    body: "If you already have a model, skip the studio and head straight to the upload flow. The instant-quote calculator covers everything below 400×400×380mm in PLA, PETG, ASA, or TPU.",
    meta: ["PLA · PETG · ASA · TPU", "0.08–0.20mm layer", "48 hr turnaround"],
    price: "From £3.50",
    priceSub: "Instant quote · no VAT",
    slotId: "svc-print",
    img: "assets/service-print.webp",
    slotPlaceholder: "Drop a print photo here"
  }];

  return (
    <section id="services" style={{
      padding: "var(--pad-y) var(--pad-x)",
      borderTop: "1px solid var(--line)"
    }}>
      <SectionHeader
        index="02 — Services"
        lead={<>Three ways to <span style={{ fontStyle: "italic" }}>make</span> a part with us — from a blank page or a finished file.</>} />
      

      <div style={{ display: "flex", flexDirection: "column" }}>
        {services.map((s, i) => {
          const reverse = i % 2 === 1;
          return (
            <article key={s.n} style={{
              display: "grid",
              gridTemplateColumns: reverse ? "1fr 1.05fr" : "1.05fr 1fr",
              gap: "clamp(24px, 4vw, 64px)",
              alignItems: "stretch",
              padding: "clamp(26px, 3vw, 44px) 0",
              borderTop: "1px solid var(--line)",
              borderBottom: i === services.length - 1 ? "1px solid var(--line)" : "none"
            }}>
              {/* Copy column */}
              <div style={{
                order: reverse ? 2 : 1,
                display: "flex", flexDirection: "column", justifyContent: "space-between",
                paddingTop: 4
              }}>
                <div>
                  <div style={{ display: "flex", alignItems: "baseline", gap: 16, marginBottom: 18 }}>
                    <div className="mono" style={{ color: "var(--muted)", fontSize: 12 }}>{s.n}</div>
                    <div className="mono upper" style={{ color: "var(--ink)" }}>{s.tag}</div>
                  </div>
                  <h3 className="serif" style={{
                    margin: 0,
                    fontSize: "clamp(28px, 3.2vw, 42px)",
                    lineHeight: 1.02, letterSpacing: "-0.025em", maxWidth: 600
                  }}>{s.title}</h3>
                  <p style={{
                    maxWidth: 520, marginTop: 16, fontSize: 15.5, lineHeight: 1.55, color: "var(--ink-soft)"
                  }}>{s.body}</p>
                  <ul className="mono" style={{
                    listStyle: "none", padding: 0, margin: "18px 0 0",
                    display: "flex", flexDirection: "column", gap: 6,
                    color: "var(--ink-soft)", fontSize: 12
                  }}>
                    {s.meta.map((m) =>
                    <li key={m} style={{ display: "flex", alignItems: "center", gap: 10 }}>
                        <span style={{ width: 14, height: 1, background: "var(--ink-soft)" }} />
                        {m}
                      </li>
                    )}
                  </ul>
                </div>

                <div style={{
                  marginTop: 24,
                  display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: 24,
                  paddingTop: 16, borderTop: "1px solid var(--line)", flexWrap: "wrap"
                }}>
                  <div>
                    <div className="mono upper" style={{ color: "var(--muted)" }}>{s.price === "POA" ? "Price" : "Pricing"}</div>
                    <div className="serif" style={{ fontSize: 36, letterSpacing: "-0.025em", lineHeight: 1, marginTop: 4 }}>{s.price}</div>
                    <div className="mono" style={{ color: "var(--muted)", fontSize: 11, marginTop: 6 }}>{s.priceSub}</div>
                  </div>
                  <button className="mono" onClick={() => {
                    if (s.price === "POA") {
                      onNav && onNav("contact", { subject: s.tag === "CAD design" ? "cad" : "cfd" });
                    } else {
                      onUpload && onUpload();
                    }
                  }} style={{
                    border: "1px solid var(--ink)", appearance: "none", font: "inherit", cursor: "pointer",
                    padding: "12px 20px", borderRadius: 999,
                    background: s.price === "POA" ? "transparent" : "var(--ink)",
                    color: s.price === "POA" ? "var(--ink)" : "var(--bg)",
                    fontSize: 12
                  }}>{s.price === "POA" ? "Request a quote →" : "Upload an STL →"}</button>
                </div>
              </div>

              {/* Image-slot column — user drags a real photo/render in.
                  CFD row gets the looping hydrofoil simulation video instead. */}
              <div style={{ order: reverse ? 1 : 2, minHeight: 320 }}>
                {s.slotId === "svc-cfd" ? (
                  <div style={{
                    width: "100%", height: "100%", minHeight: 320,
                    borderRadius: 6, overflow: "hidden",
                    border: "1px solid var(--line)",
                    background: "color-mix(in srgb, var(--ink) 4%, transparent)",
                    position: "relative"
                  }}>
                    <SeamlessLoopVideo src="assets/cfd-hydrofoil.mp4"
                      style={{ width: "100%", height: "100%", minHeight: 320 }} />
                    <div className="mono upper" style={{
                      position: "absolute", left: 12, bottom: 10,
                      color: "var(--bg)", textShadow: "0 1px 2px rgba(0,0,0,0.5)",
                      letterSpacing: "0.06em", fontSize: 10.5,
                      zIndex: 3
                    }}>
                      OpenFOAM · hydrofoil · velocity field
                    </div>
                  </div>
                ) : s.img ? (
                  <div style={{
                    width: "100%", height: "100%", minHeight: 320,
                    borderRadius: 6, overflow: "hidden",
                    border: "1px solid var(--line)", background: "#fff"
                  }}>
                    <img src={s.img} alt={s.title} loading="lazy"
                      style={{ width: "100%", height: "100%", minHeight: 320, objectFit: "cover", display: "block" }} />
                  </div>
                ) : (
                  <image-slot
                    id={s.slotId}
                    shape="rounded"
                    radius="6"
                    placeholder={s.slotPlaceholder}
                    style={{ width: "100%", height: "100%", minHeight: 320, display: "block" }} />
                )}
              </div>
            </article>);

        })}
      </div>
    </section>);

}
function GalleryFeed({ swatch }) {
  const items = [
  { img: "assets/gallery-air-knife.webp", title: "Dual Blade Air Knive for Rapid Drying", mat: "" },
  { img: "assets/gallery-drilling-jig.webp", title: "Custom-Made Drilling Jig", mat: "" },
  { img: "assets/gallery-air-box.webp", title: "90 Degree Air Box with Air-Vane", mat: "" },
  { img: "assets/gallery-coanda-demo.webp", title: "Dyson Style Coanda Effect - Education Demo kit", mat: "" },
  { img: "assets/gallery-impeller.webp", title: "Impeller", mat: "" }];

  return (
    <section id="gallery" style={{ padding: "var(--pad-y) var(--pad-x)", borderTop: "1px solid var(--line)" }}>
      <SectionHeader
        index="04 — Recent"
        lead={<>Things we designed/<span style={{ fontStyle: "italic" }}>printed</span>.</>} />
      
      <style>{`
        .sync-gal{display:grid;grid-template-columns:repeat(6,1fr);gap:14px}
        .sync-gal .g-lg{grid-column:span 3}
        .sync-gal .g-sm{grid-column:span 2}
        .sync-gal .g-lg .g-img{aspect-ratio:4/3}
        .sync-gal .g-sm .g-img{aspect-ratio:1/1}
        @media(max-width:980px){.sync-gal .g-lg,.sync-gal .g-sm{grid-column:span 3}.sync-gal .g-sm .g-img{aspect-ratio:4/3}.sync-gal .g-sm:last-child{grid-column:span 6}.sync-gal .g-sm:last-child .g-img{aspect-ratio:2/1}}
        @media(max-width:600px){.sync-gal .g-lg,.sync-gal .g-sm{grid-column:span 6}}
      `}</style>
      <div className="sync-gal">
        {items.map((it, i) =>
        <article key={i} className={i < 2 ? "g-lg" : "g-sm"} style={{
          display: "flex", flexDirection: "column",
          borderRadius: 6, overflow: "hidden",
          background: "#fff",
          border: "1px solid var(--line)"
        }}>
            <div className="g-img" style={{ position: "relative", borderBottom: "1px solid var(--line)" }}>
              <img src={it.img} alt={it.title} loading="lazy"
                style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "contain", display: "block", padding: 14, boxSizing: "border-box" }} />
            </div>
            <div style={{ padding: "12px 14px 13px" }}>
              <div className="serif" style={{ fontSize: 16, letterSpacing: "-0.01em", lineHeight: 1.25 }}>{it.title}</div>
            </div>
          </article>
        )}
      </div>
    </section>);

}

// FAQ-ish strip
function Quotes() {
  const qs = [
  { q: "Will my part be waterproof?", a: "PETG, yes-ish. PLA, no. We can vapour-smooth ASA if you ask." },
  { q: "What's the biggest you can print?", a: "400 × 400 × 380mm in one piece. Larger jobs we'll happily split for you." },
  { q: "Can you print someone else's STL?", a: "Yes — if the license allows. We won't print things that infringe IP." }];

  return (
    <section style={{ padding: "var(--pad-y) var(--pad-x)", borderTop: "1px solid var(--line)" }}>
      <SectionHeader
        index="05 — Asked"
        lead={<>Common <span style={{ fontStyle: "italic" }}>questions</span>.</>} />
      
      <div style={{ display: "flex", flexDirection: "column" }}>
        {qs.map((it, i) =>
        <div key={i} style={{
          padding: "26px 0",
          borderTop: i === 0 ? "1px solid var(--line)" : "none",
          borderBottom: "1px solid var(--line)",
          display: "grid", gridTemplateColumns: "1fr 1.5fr", gap: 24
        }}>
            <div className="serif" style={{ fontSize: 26, letterSpacing: "-0.02em", lineHeight: 1.1 }}>{it.q}</div>
            <div style={{ fontSize: 15, color: "var(--ink-soft)" }}>{it.a}</div>
          </div>
        )}
      </div>
    </section>);

}

function Footer({ brand, onNav }) {
  const click = (href) => (e) => {
    if (!onNav) return;
    e.preventDefault();
    if (href.startsWith("section:")) {
      // home-page anchored section
      const id = href.slice("section:".length);
      onNav("home", { scrollTo: id });
    } else if (href.startsWith("policy:")) {
      onNav("policy", { section: href.slice("policy:".length) });
    } else {
      onNav(href);
    }
  };
  return (
    <footer className="r-2" style={{
      padding: "var(--pad-y) var(--pad-x)",
      display: "grid", gridTemplateColumns: "1.4fr 1fr 1fr 1fr",
      gap: 32, borderTop: "1px solid var(--line)"
    }}>
      <div>
        <div className="serif" style={{ fontSize: 52, lineHeight: 0.85, letterSpacing: "-0.04em" }}>{brand}.</div>
        <div className="mono upper" style={{ color: "var(--muted)", marginTop: 12 }}>BY FORMA SYNC LTD · LONDON UK</div>
        <div className="mono" style={{ color: "var(--ink-soft)", marginTop: 18, fontSize: 11 }}>© 2026 · Reg. 16274422</div>
      </div>
      {[
      { h: "Shop", l: [
        { t: "Quote a part", href: "upload" },
        { t: "Filaments", href: "section:materials" },
        { t: "Services", href: "section:services" },
        { t: "Gallery", href: "section:gallery" }]
      },
      { h: "Service", l: [
        { t: "Help & FAQ", href: "policy:faq" },
        { t: "Shipping", href: "policy:shipping" },
        { t: "Returns", href: "policy:returns" },
        { t: "Contact", href: "contact" }]
      },
      { h: "Company", l: [
        { t: "About", href: "about" },
        { t: "Privacy", href: "policy:privacy" },
        { t: "3D printing in London", url: "/3d-printing-london" },
        { t: "3D printing costs (UK)", url: "/3d-printing-cost-uk" },
        { t: "PETG vs PLA", url: "/petg-vs-pla" },
        { t: "Print my STL file", url: "/print-my-stl-file" },
        { t: "Replacement parts", url: "/3d-printed-replacement-parts" },
        { t: "How strong is PETG?", url: "/how-strong-is-petg" }]
      }].
      map((c) =>
      <div key={c.h} style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          <div className="mono upper" style={{ color: "var(--muted)" }}>{c.h}</div>
          {c.l.map((x) => x.url
            ? <a key={x.t} href={x.url} style={{ color: "var(--ink)", fontSize: 14 }}>{x.t}</a>
            : <a key={x.t} href="#" onClick={click(x.href)} style={{ color: "var(--ink)", fontSize: 14 }}>{x.t}</a>)}
        </div>
      )}
    </footer>);

}

function HomeScreen({ heroVariant, onUpload, brand, onNav, scrollTo }) {
  const [swatch, setSwatch] = React.useState("#2a2a2a");

  // Scroll to a section if requested (e.g. footer link → #materials)
  React.useEffect(() => {
    if (!scrollTo) return;
    const id = setTimeout(() => {
      const el = document.getElementById(scrollTo);
      if (el) window.scrollTo({ top: el.offsetTop - 60, behavior: "smooth" });
    }, 60);
    return () => clearTimeout(id);
  }, [scrollTo]);

  return (
    <main>
      {heroVariant === "convert" ?
      <HeroConvert swatch={swatch} setSwatch={setSwatch} onUpload={onUpload} /> :
      heroVariant === "dropzone" ?
      <HeroDropzone swatch={swatch} setSwatch={setSwatch} onUpload={onUpload} /> :
      heroVariant === "gallery" ?
      <HeroGallery swatch={swatch} setSwatch={setSwatch} onUpload={onUpload} /> :
      heroVariant === "image" ?
      <HeroImage swatch={swatch} setSwatch={setSwatch} onUpload={onUpload} /> :
      heroVariant === "stl" ?
      <HeroSTL swatch={swatch} setSwatch={setSwatch} onUpload={onUpload} /> :
      <HeroBlade heroVariant={heroVariant === "viewer-cog" ? "gear" : heroVariant === "viewer-knot" ? "torus" : heroVariant === "viewer-blade" ? "blade" : heroVariant === "viewer-bracket" ? "bracket" : heroVariant === "viewer-vase" ? "vase" : "impeller"} swatch={swatch} setSwatch={setSwatch} onUpload={onUpload} />
      }
      <ProcessSection />
      <ServicesSection onUpload={onUpload} onNav={onNav} />
      <MaterialsSection />
      <GalleryFeed swatch={swatch} />
      <Quotes />
      <BigCTA onUpload={onUpload} />
      <Footer brand={brand} onNav={onNav} />

      <FloatingCTA onUpload={onUpload} />
    </main>);

}

window.HomeScreen = HomeScreen;
window.FloatingCTA = FloatingCTA;
window.Footer = Footer;