const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ============================================================
// PALETTES — deep-space tones, softer accent
// ============================================================
const PALETTES = {
  nebula: {
    name: "Nebula",
    bg: "#07091a",
    bgAlt: "#0c0f25",
    ink: "#e4e6ef",
    inkSoft: "#6a6e80",
    rule: "#171a2e",
    accent: "#b04848", // softer brick / wine red
    nebulaA: "rgba(110, 80, 160, 0.08)",
    nebulaB: "rgba(80, 100, 170, 0.06)"
  },
  cosmos: {
    name: "Cosmos",
    bg: "#070a12",
    bgAlt: "#0b1020",
    ink: "#dce0eb",
    inkSoft: "#5f6678",
    rule: "#141929",
    accent: "#5e7fb0", // steel blue
    nebulaA: "rgba(70, 100, 160, 0.07)",
    nebulaB: "rgba(60, 80, 140, 0.05)"
  },
  void: {
    name: "Void",
    bg: "#0a0908",
    bgAlt: "#100e0c",
    ink: "#e8e4dc",
    inkSoft: "#6c655c",
    rule: "#171511",
    accent: "#b88060", // amber-brass
    nebulaA: "rgba(120, 90, 60, 0.07)",
    nebulaB: "rgba(140, 110, 80, 0.05)"
  }
};

// ============================================================
// LOGO — true constellation: loose scatter, one faint arc
// ============================================================
function Logo({ accent }) {
  return (
    <span className="logo">
      <span className="logo-word">
        <span className="logo-endura" style={{ textAlign: "center" }}>ENDURA</span>
        <span className="logo-holdings">
          {'HOLDINGS'.split('').map((c, i) =>
          <span key={i}>{c}</span>
          )}
        </span>
      </span>
    </span>);

}

// ============================================================
// STARFIELD + PARTICLE LATTICE
// Combined: stationary faint stars + moving structured points
// ============================================================
const N_PARTICLES = 320;

function rand(i, seed) {
  const s = Math.sin(i * 12.9898 + seed * 78.233) * 43758.5453;
  return s - Math.floor(s);
}

// Each formation returns [x, y] in [0,1] for particle i in [0, N)
const FORMATIONS = [
// 1 - vertical column on far left
(i, N) => {
  const t = i / (N - 1);
  return [0.16 + (rand(i, 1) - 0.5) * 0.05, 0.10 + t * 0.80 + (rand(i, 2) - 0.5) * 0.015];
},
// 2 - dispersed (chaos)
(i) => [rand(i, 3), rand(i, 4)],
// 3 - vertical column on far right
(i, N) => {
  const t = i / (N - 1);
  return [0.84 + (rand(i, 5) - 0.5) * 0.05, 0.10 + t * 0.80 + (rand(i, 6) - 0.5) * 0.015];
},
// 4 - horizontal band middle
(i, N) => {
  const t = i / (N - 1);
  return [0.06 + t * 0.88 + (rand(i, 7) - 0.5) * 0.015, 0.50 + (rand(i, 8) - 0.5) * 0.07];
},
// 5 - sparse grid lattice
(i, N) => {
  const cols = 18,rows = Math.ceil(N / cols);
  const c = i % cols,r = Math.floor(i / cols);
  return [
  0.08 + c / (cols - 1) * 0.84 + (rand(i, 9) - 0.5) * 0.01,
  0.10 + r / (rows - 1) * 0.80 + (rand(i, 10) - 0.5) * 0.01];

},
// 6 - elliptical orbit
(i, N) => {
  const a = i / N * Math.PI * 2;
  const rr = 0.28 + (rand(i, 11) - 0.5) * 0.04;
  return [0.5 + Math.cos(a) * rr * 1.35, 0.5 + Math.sin(a) * rr];
}];


function ParticleField({ palette, intensity }) {
  const canvasRef = useRef(null);
  const stateRef = useRef(null);
  const rafRef = useRef(0);
  // Track latest palette/intensity in refs so the long-lived rAF loop
  // doesn't get torn down on color changes.
  const paletteRef = useRef(palette);
  const intensityRef = useRef(intensity);
  useEffect(() => {paletteRef.current = palette;}, [palette]);
  useEffect(() => {intensityRef.current = intensity;}, [intensity]);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    let w = 0,h = 0,dpr = 1;

    const resize = () => {
      const rect = canvas.getBoundingClientRect();
      dpr = Math.min(window.devicePixelRatio || 1, 2);
      w = rect.width || window.innerWidth;
      h = rect.height || window.innerHeight;
      canvas.width = w * dpr;
      canvas.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);

    // Stationary background stars — small, low alpha, twinkle phase
    const stars = Array.from({ length: 180 }, (_, i) => ({
      x: rand(i, 100),
      y: rand(i, 101),
      r: 0.4 + rand(i, 102) * 1.3,
      base: 0.15 + rand(i, 103) * 0.45,
      tw: rand(i, 104) * Math.PI * 2,
      bright: rand(i, 105) > 0.94 // a few brighter
    }));

    // Moving particles (lattice)
    if (!stateRef.current) {
      const initial = FORMATIONS[1];
      stateRef.current = Array.from({ length: N_PARTICLES }, (_, i) => {
        const [x, y] = initial(i, N_PARTICLES);
        return { x, y, tx: x, ty: y };
      });
    }
    const particles = stateRef.current;

    const DWELL = 2.6;
    const TRANSITION = 4.2;
    const CYCLE = DWELL + TRANSITION;

    const easeInOut = (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;

    let last = performance.now();
    const start = performance.now();

    const draw = (t) => {
      const dt = Math.min(0.05, (t - last) / 1000);
      last = t;

      const intensityVal = intensityRef.current;
      const pal = paletteRef.current;

      if (intensityVal === "off") {
        ctx.clearRect(0, 0, w, h);
        rafRef.current = requestAnimationFrame(draw);
        return;
      }
      const speedMul = intensityVal === "low" ? 0.55 : intensityVal === "high" ? 1.5 : 1.0;
      const elapsed = (t - start) / 1000 * speedMul;

      const phaseIdx = Math.max(0, Math.floor(elapsed / CYCLE));
      const phaseTime = elapsed - phaseIdx * CYCLE;
      const nF = FORMATIONS.length;
      const formA = FORMATIONS[phaseIdx % nF] || FORMATIONS[0];
      const formB = FORMATIONS[(phaseIdx + 1) % nF] || FORMATIONS[0];

      let m = phaseTime < DWELL ? 0 : (phaseTime - DWELL) / TRANSITION;
      const eased = easeInOut(Math.max(0, Math.min(1, m)));

      ctx.clearRect(0, 0, w, h);

      // 1) Stationary stars (background ambience)
      for (let i = 0; i < stars.length; i++) {
        const s = stars[i];
        const tw = 0.6 + 0.4 * Math.sin(elapsed * 0.6 + s.tw);
        const alpha = s.base * tw;
        ctx.fillStyle = pal.ink;
        ctx.globalAlpha = alpha;
        ctx.beginPath();
        ctx.arc(s.x * w, s.y * h, s.r, 0, Math.PI * 2);
        ctx.fill();
        if (s.bright) {
          ctx.globalAlpha = alpha * 0.25;
          ctx.beginPath();
          ctx.arc(s.x * w, s.y * h, s.r * 3, 0, Math.PI * 2);
          ctx.fill();
        }
      }

      // 2) Moving lattice particles — assembling and dissolving
      const lerpRate = 1 - Math.pow(0.0008, dt);
      for (let i = 0; i < particles.length; i++) {
        const p = particles[i];
        const [ax, ay] = formA(i, N_PARTICLES);
        const [bx, by] = formB(i, N_PARTICLES);
        const wander = 0.003;
        const wx = Math.sin(elapsed * 0.6 + i * 0.31) * wander;
        const wy = Math.cos(elapsed * 0.5 + i * 0.47) * wander;
        p.tx = ax * (1 - eased) + bx * eased + wx;
        p.ty = ay * (1 - eased) + by * eased + wy;
        p.x += (p.tx - p.x) * lerpRate;
        p.y += (p.ty - p.y) * lerpRate;

        const px = p.x * w,py = p.y * h;
        const isAccent = i % 23 === 0;
        const baseAlpha = 0.40 + rand(i, 11) * 0.30;
        ctx.fillStyle = isAccent ? pal.accent : pal.ink;
        ctx.globalAlpha = baseAlpha;
        const r = 1.0 + rand(i, 12) * 0.9;
        ctx.beginPath();
        ctx.arc(px, py, r, 0, Math.PI * 2);
        ctx.fill();
      }

      // 3) Connecting lines (only when particles are close → strong during formations)
      ctx.strokeStyle = pal.ink;
      ctx.lineWidth = 0.45;
      const maxDist = 56;
      const maxDist2 = maxDist * maxDist;
      for (let i = 0; i < particles.length; i++) {
        for (let j = i + 1; j < Math.min(particles.length, i + 6); j++) {
          const a = particles[i],b = particles[j];
          const dx = (a.x - b.x) * w;
          const dy = (a.y - b.y) * h;
          const d2 = dx * dx + dy * dy;
          if (d2 < maxDist2) {
            ctx.globalAlpha = (1 - d2 / maxDist2) * 0.12;
            ctx.beginPath();
            ctx.moveTo(a.x * w, a.y * h);
            ctx.lineTo(b.x * w, b.y * h);
            ctx.stroke();
          }
        }
      }
      ctx.globalAlpha = 1;

      rafRef.current = requestAnimationFrame(draw);
    };
    rafRef.current = requestAnimationFrame(draw);

    return () => {
      cancelAnimationFrame(rafRef.current);
      ro.disconnect();
    };
  }, []); // mount once — palette/intensity tracked via refs

  return <canvas ref={canvasRef} className="particle-field" />;
}

// ============================================================
// INFO OVERLAY — streams generated about para
// ============================================================
function InfoOverlay({ open, onClose }) {
  const [tokens, setTokens] = useState([]);
  const [revealed, setRevealed] = useState(0);
  const abortRef = useRef({ cancelled: false });

  useEffect(() => {
    if (!open) return;
    setTokens([]);
    setRevealed(0);
    abortRef.current = { cancelled: false };

    const FALLBACK =
    "Endura Holdings acquires established service businesses and applies artificial intelligence to systematically improve their economics and transform the quality of service. " +
    "We hold without exit timelines, which frees us to make decisions for long-term compounding rather than distribution schedules. " +
    "Our approach is analytical and disciplined: we deploy technology with precision to drive measurable gains in operating margin and accelerate topline growth. " +
    "Success, for us, is the lasting vitality of the businesses we partner with.";

    const run = async () => {
      const full = FALLBACK;
      if (abortRef.current.cancelled) return;

      // Render ALL tokens upfront so layout is final from frame one.
      // Words then fade in place without the paragraph re-flowing.
      const toks = full.match(/\S+\s*/g) || [];
      // Mark tokens that belong to the final sentence (after 3rd period)
      // so they can fade in slower for emphasis.
      let periods = 0;
      let finalIdx = 0;
      const totalPeriods = (full.match(/[.!?]/g) || []).length;
      const tokMeta = toks.map((tok) => {
        const inFinalSentence = periods >= totalPeriods - 1;
        // Only emphasize the opening phrase "Success, for us," — first 3 tokens of final sentence.
        const isFinal = inFinalSentence && finalIdx < 3;
        if (inFinalSentence) finalIdx += 1;
        if (/[.!?]/.test(tok)) periods += 1;
        return { tok, isFinal };
      });
      setTokens(tokMeta);

      let i = 0;
      const tick = () => {
        if (abortRef.current.cancelled) return;
        i += 1;
        setRevealed(i);
        if (i >= tokMeta.length) return;
        const last = tokMeta[i - 1]?.tok || "";
        const isPunct = /[.!?]\s*$/.test(last);
        const isComma = /[,;:—]\s*$/.test(last);
        const lastIsFinal = tokMeta[i - 1]?.isFinal;
        const nextIsFinal = tokMeta[i]?.isFinal;
        const base = nextIsFinal ? 2 : 1;
        // Stretch comma pauses extra inside the emphasized phrase ("Success, for us,").
        const commaMul = isComma && lastIsFinal ? 3.2 : base;
        const delay = isPunct ? 390 * base : isComma ? 210 * commaMul : 120 * base;
        setTimeout(tick, delay);
      };
      // brief pause for layout to settle before first reveal
      setTimeout(tick, 420);
    };
    run();

    return () => {
      abortRef.current.cancelled = true;
      setTokens([]);
      setRevealed(0);
    };
  }, [open]);

  useEffect(() => {
    if (!open) return;
    const onKey = (e) => {if (e.key === "Escape") onClose();};
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open, onClose]);

  return (
    <div className={`info-overlay ${open ? "open" : ""}`}>
      <button className="info-close" onClick={onClose} aria-label="Close">
        <svg viewBox="0 0 24 24" width="13" height="13"><path d="M5 5l14 14M19 5L5 19" stroke="currentColor" strokeWidth="1.5" fill="none" /></svg>
        <span>Close</span>
      </button>
      <div className="info-content">
        <p className="info-text">
          {tokens.map((tk, idx) => {
            const w = tk.tok ?? tk; // tolerate legacy string entries
            const isFinal = tk.isFinal;
            const classes = ["info-word"];
            if (isFinal) classes.push("is-final");
            if (idx < revealed) classes.push("is-revealed");
            return (
              <span key={idx} className={classes.join(" ")}>{w}</span>
            );
          })}
        </p>
      </div>
    </div>);

}

// ============================================================
// PAGE
// ============================================================
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "nebula",
  "intensity": "med"
} /*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const palette = PALETTES[t.palette] || PALETTES.nebula;
  const [infoOpen, setInfoOpen] = useState(false);

  useEffect(() => {
    const r = document.documentElement;
    r.style.setProperty("--bg", palette.bg);
    r.style.setProperty("--bg-alt", palette.bgAlt);
    r.style.setProperty("--ink", palette.ink);
    r.style.setProperty("--ink-soft", palette.inkSoft);
    r.style.setProperty("--rule", palette.rule);
    r.style.setProperty("--accent", palette.accent);
    r.style.setProperty("--nebula-a", palette.nebulaA);
    r.style.setProperty("--nebula-b", palette.nebulaB);
    document.body.style.background = palette.bg;
    document.body.style.color = palette.ink;
  }, [palette]);

  return (
    <div className="page">
      {/* Background field — stars + lattice */}
      <ParticleField palette={palette} intensity={t.intensity} />
      {/* Soft nebula glow layers */}
      <div className="nebula" aria-hidden="true" />

      {/* HEADER */}
      <header className="nav">
        <span className="brand">
          <Logo accent={palette.accent} />
        </span>
        <nav className="nav-right">
          <span className="loc">EST. 2026 · NEW YORK</span>
          <a href="mailto:team@enduraholdings.org" className="contact-link">Contact</a>
        </nav>
      </header>

      <main className="main" id="top">
        <div className="hero">
          <h1 className="hero-headline">
            <span className="hl-intro" style={{ color: "rgb(176, 72, 72)" }}>
              Reimagining the services industry through{" "}
              <span style={{ color: "rgb(232, 234, 239)" }}>enduring partnerships</span>
              {" "}and{" "}
              <span style={{ color: "rgb(232, 234, 239)" }}>applied AI</span>
            </span>
            <span className="hl-feature">
              <em className="hl-phrase"></em>
            </span>
          </h1>
        </div>
      </main>

      <footer className="foot">
        <div>© 2026 ENDURA HOLDINGS</div>
        <div className="foot-mid">— · —</div>
        <button className="mission-btn" onClick={() => setInfoOpen(true)} aria-label="Our mission">
          <span className="mission-mark" aria-hidden="true">
            <svg viewBox="0 0 24 24" width="11" height="11">
              <circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" strokeWidth="1.4" />
              <line x1="12" y1="10" x2="12" y2="17" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
              <circle cx="12" cy="7.2" r="1.1" fill="currentColor" />
            </svg>
          </span>
          <span>OUR MISSION</span>
        </button>
      </footer>

      <InfoOverlay open={infoOpen} onClose={() => setInfoOpen(false)} />

      <TweaksPanel>
        <TweakSection label="Palette">
          <TweakRadio
            label="Theme"
            value={t.palette}
            onChange={(v) => setTweak("palette", v)}
            options={[
            { value: "nebula", label: "Nebula" },
            { value: "cosmos", label: "Cosmos" },
            { value: "void", label: "Void" }]
            } />
          
        </TweakSection>
        <TweakSection label="Field">
          <TweakRadio
            label="Motion"
            value={t.intensity}
            onChange={(v) => setTweak("intensity", v)}
            options={[
            { value: "off", label: "Off" },
            { value: "low", label: "Low" },
            { value: "med", label: "Med" },
            { value: "high", label: "High" }]
            } />
          
        </TweakSection>
      </TweaksPanel>
    </div>);

}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);