/* Foral AI — "Second Brain" 10s intro.
   Recreates an Obsidian graph view in the brand's editorial coastal palette:
   a single central node blooms into a connected constellation of projects,
   clients and automations; an azulejo "scan" sweeps the graph (the AI reading
   the whole brain); then the Foral AI sign-off.
   Composed of Sprites/overlays inside the shared Stage (animations.jsx).
*/

const C = {
  paper: "#faf6ee", paperDeep: "#f4ecdb", surface: "#fffdf8",
  ink: "#1a1614", inkSoft: "#4a423a", inkMuted: "#6b635a",
  hair: "#e3d9c9", hairStrong: "#d4c6ad",
  brick: "#b54a2a", brickDeep: "#8a3820", brickTint: "#f7e9e2",
  azulejo: "#2c5d8f", azulejoTint: "#e4ecf3",
  sage: "#5d7a4a",
};
const DISPLAY = '"Fraunces", Georgia, serif';
const BODY = '"Newsreader", Georgia, serif';
const UI = '"Public Sans", system-ui, sans-serif';
const MONO = '"JetBrains Mono", ui-monospace, monospace';

const { Stage, useTime, Easing, interpolate, animate, clamp } = window;

const CX = 960, CY = 500; // graph center

/* ---- node graph (base positions, before bob) — spread to fill the frame ---- */
const NODES = [
  { id: "c",  x: CX,   y: CY,  r: 48, label: "2nd Brain", color: C.brick,   t0: 0.4, big: true },

  { id: "P",  x: 960,  y: 168, r: 20, label: "Projects",    color: C.ink,    t0: 2.35, parent: "c" },
  { id: "A",  x: 1246, y: 335, r: 20, label: "Automations", color: C.ink,    t0: 2.55, parent: "c" },
  { id: "Cl", x: 1246, y: 665, r: 20, label: "Clients",     color: C.ink,    t0: 2.75, parent: "c" },
  { id: "L",  x: 674,  y: 335, r: 20, label: "Leads",       color: C.ink,    t0: 2.95, parent: "c" },
  { id: "R",  x: 674,  y: 665, r: 20, label: "Resources",   color: C.ink,    t0: 3.15, parent: "c" },
  { id: "F",  x: 960,  y: 832, r: 22, label: "Calendar",    color: C.azulejo,t0: 3.35, parent: "c" },

  { id: "wa", x: 1520, y: 232, r: 12, label: "Messages",    color: C.inkSoft, t0: 3.85, parent: "A" },
  { id: "rm", x: 1560, y: 430, r: 12, label: "Reminders",   color: C.inkSoft, t0: 4.05, parent: "A" },
  { id: "fc", x: 1545, y: 610, r: 12, label: "Bookings",    color: C.inkSoft, t0: 4.25, parent: "Cl" },
  { id: "nc", x: 1505, y: 778, r: 12, label: "Invoices",    color: C.inkSoft, t0: 4.45, parent: "Cl" },
  { id: "tf", x: 1140, y: 70,  r: 12, label: "Tasks",       color: C.inkSoft, t0: 4.05, parent: "P" },
  { id: "rn", x: 780,  y: 70,  r: 12, label: "Workflows",   color: C.inkSoft, t0: 4.25, parent: "P" },
  { id: "qt", x: 500,  y: 215, r: 12, label: "Quotes",      color: C.inkSoft, t0: 4.45, parent: "L" },
  { id: "nt", x: 410,  y: 775, r: 12, label: "Notes",       color: C.inkSoft, t0: 4.25, parent: "R" },
];
const BY_ID = Object.fromEntries(NODES.map(n => [n.id, n]));

// extra real links beyond the parent tree (e.g. Bookings feed the Calendar)
const EXTRA = [["fc", "F"]];

// spiderweb cross-threads: connect ring nodes (and outer sub-nodes) in angular order
const _ang = n => Math.atan2(n.y - CY, n.x - CX);
function _loop(list) { const s = [...list].sort((a, b) => _ang(a) - _ang(b)); return s.map((n, i) => [n, s[(i + 1) % s.length]]); }
const WEB = [
  ..._loop(NODES.filter(n => n.parent === "c")),
  ..._loop(NODES.filter(n => n.parent && n.parent !== "c")),
];

// brain folds drawn inside the central node (authored for r=50, scaled)
function brainFolds(cx, cy, r) {
  const sc = r / 50;
  const st = { stroke: "rgba(255,253,248,0.62)", strokeWidth: 3 / sc, fill: "none", strokeLinecap: "round" };
  return (
    <g transform={`translate(${cx} ${cy}) scale(${sc})`}>
      <path d="M0,-34 C8,-22 -8,-10 0,2 C8,14 -8,26 0,34" {...st} />
      <path d="M-9,-26 C-26,-22 -27,-6 -12,-3" {...st} />
      <path d="M-12,5 C-28,9 -23,24 -8,27" {...st} />
      <path d="M9,-26 C26,-22 27,-6 12,-3" {...st} />
      <path d="M12,5 C28,9 23,24 8,27" {...st} />
    </g>
  );
}

// live position incl. subtle bob
function pos(n, t) {
  const ph = (n.x * 0.7 + n.y * 1.3) % 6.283;
  const amp = n.big ? 0 : 3.2;
  return { x: n.x + Math.sin(t * 0.55 + ph) * amp, y: n.y + Math.cos(t * 0.47 + ph) * amp };
}
const distC = (n) => Math.hypot(n.x - CX, n.y - CY);

/* ============================================================ backdrop */
function Backdrop() {
  return (
    <div style={{ position: "absolute", inset: 0, background: C.paper }}>
      <div style={{ position: "absolute", inset: 0, background:
        "radial-gradient(120% 80% at 18% 10%, rgba(255,255,255,0.6), transparent 55%)," +
        "radial-gradient(120% 90% at 84% 96%, rgba(244,236,219,0.72), transparent 50%)" }} />
      <div style={{ position: "absolute", inset: 0, opacity: 0.5, mixBlendMode: "multiply",
        backgroundImage:
          "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/><feColorMatrix values='0 0 0 0 0.18 0 0 0 0 0.16 0 0 0 0 0.14 0 0 0 0.04 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>\")" }} />
    </div>
  );
}

/* ============================================================ graph (SVG) */
function Graph({ t }) {
  // camera: slow push-in + drift
  const s = 1.0 + 0.035 * Easing.easeInOutSine(clamp(t / 10, 0, 1));
  const dx = Math.sin(t * 0.3) * 12;
  const dy = Math.sin(t * 0.23) * 9;
  // 3D orbit of the whole constellation (settles flat as the sign-off fades in)
  const settle = 1 - clamp((t - 8.3) / 0.8, 0, 1);
  const tiltY = Math.sin(t * 0.5) * 17 * settle;
  const tiltX = (5 + Math.sin(t * 0.37) * 5) * settle;
  const groupOpacity = animate({ from: 1, to: 0.04, start: 8.3, end: 9.1, ease: Easing.easeInOutCubic })(t);

  // AI scan wavefront
  const scanActive = t >= 5.7 && t <= 8.2;
  const scanR = interpolate([5.7, 8.1], [0, 700], Easing.easeInOutSine)(t);
  const scanFade = scanActive
    ? clamp((t - 5.7) / 0.4, 0, 1) * clamp((8.2 - t) / 0.5, 0, 1)
    : 0;

  const edges = NODES.filter(n => n.parent).map(n => {
    const p = BY_ID[n.parent];
    const a = pos(p, t), b = pos(n, t);
    const len = Math.hypot(b.x - a.x, b.y - a.y);
    const prog = clamp((t - (n.t0 - 0.2)) / 0.5, 0, 1);
    const isCenter = n.parent === "c";
    return (
      <line key={"e" + n.id} x1={a.x} y1={a.y} x2={b.x} y2={b.y}
        stroke={isCenter ? C.hairStrong : C.hair}
        strokeWidth={isCenter ? 1.8 : 1.3}
        strokeDasharray={len} strokeDashoffset={len * (1 - prog)}
        strokeLinecap="round" opacity={0.95} />
    );
  });

  const extraEdges = EXTRA.map(function (pair, i) {
    const na = BY_ID[pair[0]], nb = BY_ID[pair[1]];
    const a = pos(na, t), b = pos(nb, t);
    const len = Math.hypot(b.x - a.x, b.y - a.y);
    const prog = clamp((t - (Math.max(na.t0, nb.t0) + 0.1)) / 0.5, 0, 1);
    if (prog <= 0) return null;
    return <line key={"x" + i} x1={a.x} y1={a.y} x2={b.x} y2={b.y}
      stroke={C.hair} strokeWidth="1.3" strokeDasharray={len} strokeDashoffset={len * (1 - prog)}
      strokeLinecap="round" opacity={0.9} />;
  });

  // spiderweb cross-threads fade in toward the end
  const webFade = clamp((t - 6.0) / 1.4, 0, 1) - clamp((t - 8.3) / 0.7, 0, 1);
  const webEdges = webFade > 0.02 ? WEB.map(function (pair, i) {
    const a = pos(pair[0], t), b = pos(pair[1], t);
    return <line key={"w" + i} x1={a.x} y1={a.y} x2={b.x} y2={b.y}
      stroke={C.hairStrong} strokeWidth="1" opacity={clamp(webFade, 0, 1) * 0.5} />;
  }) : null;

  // flowing data particles along the brain's branches — the "thinking" pulse
  const particles = NODES.filter(n => n.parent === "c" && t >= n.t0).flatMap((n, i) => {
    const a = pos(BY_ID.c, t), b = pos(n, t);
    return [0, 0.5].map((off, k) => {
      const ph = (t * 0.45 + i * 0.16 + off) % 1;
      const fx = a.x + (b.x - a.x) * ph, fy = a.y + (b.y - a.y) * ph;
      const fade = Math.sin(ph * Math.PI);
      return <circle key={"fp" + n.id + k} cx={fx} cy={fy} r={3 + fade} fill={C.azulejo} opacity={0.5 * fade} />;
    });
  });

  return (
    <div style={{ position: "absolute", inset: 0, perspective: "1500px", perspectiveOrigin: "50% 42%" }}>
    <svg viewBox="0 0 1920 1080" width="1920" height="1080"
      style={{ position: "absolute", inset: 0, opacity: groupOpacity,
        transform: `rotateX(${tiltX}deg) rotateY(${tiltY}deg)`, transformOrigin: "center", willChange: "transform" }}>
      <g transform={`translate(${dx} ${dy}) translate(${CX} ${CY}) scale(${s}) translate(${-CX} ${-CY})`}>
        {edges}
        {extraEdges}
        {webEdges}
        {particles}

        {/* scan wavefront ring */}
        {scanFade > 0 && (
          <circle cx={CX} cy={CY} r={Math.max(1, scanR)} fill="none"
            stroke={C.azulejo} strokeWidth="2.5" opacity={scanFade * 0.55 * (1 - scanR / 760)} />
        )}

        {NODES.map(n => {
          const p = pos(n, t);
          const sc = t < n.t0 ? 0 : Math.min(1.12, Easing.easeOutBack(clamp((t - n.t0) / 0.5, 0, 1)));
          if (sc <= 0) return null;
          const r = n.r * sc;
          // scan glow as wavefront passes this node
          const d = distC(n);
          const glow = scanActive ? clamp(1 - Math.abs(scanR - d) / 110, 0, 1) * scanFade : 0;
          // gentle idle pulse on center node
          const pulseR = n.big ? r + 6 + Math.sin(t * 1.8) * 3 : 0;
          return (
            <g key={n.id}>
              {n.big && (
                <circle cx={p.x} cy={p.y} r={pulseR + 10} fill="none"
                  stroke={C.brick} strokeWidth="1.5" opacity={0.18 + 0.08 * Math.sin(t * 1.8)} />
              )}
              {glow > 0 && (
                <circle cx={p.x} cy={p.y} r={r * 2.3} fill={C.azulejo} opacity={glow * 0.28} />
              )}
              <circle cx={p.x} cy={p.y + r * 0.16 + 3} r={r} fill="rgba(26,22,20,0.13)" />
              <circle cx={p.x} cy={p.y} r={r}
                fill={glow > 0.25 ? C.azulejo : n.color}
                stroke={C.paper} strokeWidth={n.big ? 3 : 2} />
              {!n.big && (
                <circle cx={p.x - r * 0.32} cy={p.y - r * 0.34} r={r * 0.42}
                  fill="rgba(255,255,255,0.22)" />
              )}
              {n.big && brainFolds(p.x, p.y, r)}
            </g>
          );
        })}

        {/* labels */}
        {NODES.map(n => {
          const p = pos(n, t);
          const op = clamp((t - (n.t0 + 0.15)) / 0.4, 0, 1);
          if (op <= 0) return null;
          const fs = n.big ? 34 : n.r >= 18 ? 24 : 17;
          const dyL = n.r + (n.big ? 36 : n.r >= 18 ? 26 : 18);
          return (
            <text key={"l" + n.id} x={p.x} y={p.y + dyL}
              textAnchor="middle" fontFamily={UI}
              fontWeight={n.big ? 600 : n.r >= 17 ? 600 : 500}
              fontSize={fs} fill={n.big ? C.brick : C.inkSoft}
              stroke={C.paper} strokeWidth="4" opacity={op}
              style={{ paintOrder: "stroke", letterSpacing: n.big ? "-0.01em" : "0.005em" }}>
              {n.label}
            </text>
          );
        })}
      </g>
    </svg>
    </div>
  );
}

/* ============================================================ text overlays */
function fadeWin(t, start, end, fin = 0.4, fout = 0.4) {
  return clamp((t - start) / fin, 0, 1) * clamp((end - t) / fout, 0, 1);
}

function Eyebrow({ t }) {
  const op = fadeWin(t, 0.4, 2.7, 0.5, 0.55);
  if (op <= 0) return null;
  return (
    <div style={{ position: "absolute", left: "50%", top: 70, transform: "translateX(-50%)",
      textAlign: "center", opacity: op }}>
      <div style={{ width: 34, height: 4, background: C.brick, margin: "0 auto 14px",
        transform: `scaleX(${clamp((t - 0.5) / 0.5, 0, 1)})`, transformOrigin: "center" }} />
      <div style={{ fontFamily: UI, fontWeight: 600, fontSize: 18, letterSpacing: "0.34em",
        textTransform: "uppercase", color: C.ink }}>
        Tramata&nbsp;&nbsp;·&nbsp;&nbsp;The Second Brain
      </div>
    </div>
  );
}

function Caption({ t, start, end, children }) {
  const op = fadeWin(t, start, end, 0.85, 0.7);
  if (op <= 0) return null;
  const ty = (1 - Easing.easeOutCubic(clamp((t - start) / 0.9, 0, 1))) * 16;
  return (
    <div style={{ position: "absolute", left: "50%", top: 952, width: 1320,
      transform: `translate(-50%, ${ty}px)`, textAlign: "center", opacity: op,
      fontFamily: BODY, fontSize: 40, lineHeight: 1.25, color: C.inkSoft,
      letterSpacing: "-0.005em" }}>
      {children}
    </div>
  );
}

// the Tramata graph logo mark (option 9, seed 95) as an SVG string — matches the website logo
function logoMarkSVG() {
  var INKc = C.ink, BRICKc = C.brick, PAPERc = C.paper, HUBR = 30, CC = { x:100, y:100 };
  function folds(cx, cy, r){
    var sc = r/50;
    var s = 'stroke="rgba(255,253,248,0.66)" stroke-width="'+(3/sc)+'" fill="none" stroke-linecap="round"';
    return '<g transform="translate('+cx+' '+cy+') scale('+sc+')">'
      +'<path d="M0,-34 C8,-22 -8,-10 0,2 C8,14 -8,26 0,34" '+s+'/>'
      +'<path d="M-9,-26 C-26,-22 -27,-6 -12,-3" '+s+'/>'
      +'<path d="M-12,5 C-28,9 -23,24 -8,27" '+s+'/>'
      +'<path d="M9,-26 C26,-22 27,-6 12,-3" '+s+'/>'
      +'<path d="M12,5 C28,9 23,24 8,27" '+s+'/></g>';
  }
  function rng(seed){ return function(){ seed|=0; seed=seed+0x6D2B79F5|0; var t=Math.imul(seed^seed>>>15,1|seed); t=t+Math.imul(t^t>>>7,61|t)^t; return ((t^t>>>14)>>>0)/4294967296; }; }
  function d2(a,b){ return Math.hypot(a.x-b.x,a.y-b.y); }
  var RG = [ {count:8,rad:47,size:5.0,jr:8,ja:0.42}, {count:10,rad:69,size:3.6,jr:10,ja:0.34}, {count:11,rad:91,size:2.4,jr:9,ja:0.30}, {count:7,rad:111,size:1.6,jr:9,ja:0.26} ];
  var rand = rng(95), nodes = [];
  RG.forEach(function(ring, ri){ var base = rand()*Math.PI*2; for (var i=0;i<ring.count;i++){ var ang = base + i/ring.count*Math.PI*2 + (rand()-0.5)*ring.ja; var rad = ring.rad + (rand()-0.5)*2*ring.jr; nodes.push({ x:CC.x+Math.cos(ang)*rad, y:CC.y+Math.sin(ang)*rad, r:ring.size*(0.82+rand()*0.36), ring:ri, rad:rad }); } });
  var links = [];
  nodes.forEach(function(n){ var best=null,bd=1e9; nodes.forEach(function(m){ if(m===n)return; if(m.rad<n.rad-2){ var d=d2(n,m); if(d<bd){bd=d;best=m;} } }); if(n.ring===0||n.rad<bd) links.push([n,CC]); else if(best) links.push([n,best]); });
  for (var k=0;k<nodes.length;k++){ if(rand()<0.70){ var a=nodes[k],best=null,bd=1e9,second=null,sd=1e9; nodes.forEach(function(m){ if(m!==a){ var d=d2(a,m); if(d>6){ if(d<bd){sd=bd;second=best;bd=d;best=m;} else if(d<sd){sd=d;second=m;} } } }); if(best&&bd<50) links.push([a,best]); if(second&&sd<38&&rand()<0.5) links.push([a,second]); } }
  var g = '';
  links.forEach(function(l){ g+='<line x1="'+l[0].x.toFixed(1)+'" y1="'+l[0].y.toFixed(1)+'" x2="'+l[1].x.toFixed(1)+'" y2="'+l[1].y.toFixed(1)+'" stroke="'+INKc+'" stroke-width="0.85" stroke-linecap="round" opacity="0.3"/>'; });
  nodes.slice().sort(function(a,b){return a.r-b.r;}).forEach(function(n){ g+='<circle cx="'+n.x.toFixed(1)+'" cy="'+n.y.toFixed(1)+'" r="'+n.r.toFixed(1)+'" fill="'+INKc+'"/>'; });
  g+='<circle cx="'+CC.x+'" cy="'+CC.y+'" r="'+(HUBR+9)+'" fill="none" stroke="'+BRICKc+'" stroke-width="2.2" opacity="0.30"/>';
  g+='<circle cx="'+CC.x+'" cy="'+CC.y+'" r="'+(HUBR+3)+'" fill="'+PAPERc+'"/>';
  g+='<circle cx="'+CC.x+'" cy="'+CC.y+'" r="'+HUBR+'" fill="'+BRICKc+'"/>';
  g+=folds(CC.x, CC.y, HUBR);
  return g;
}
const LOGO_MARK = logoMarkSVG();

function SignOff({ t }) {
  const op = fadeWin(t, 8.7, 12, 0.9, 0.3); // holds to the end — no fade-out
  if (op <= 0) return null;
  const ty = (1 - Easing.easeOutCubic(clamp((t - 8.7) / 0.8, 0, 1))) * 22;
  const markScale = 0.6 + 0.4 * Easing.easeOutBack(clamp((t - 8.7) / 0.7, 0, 1));
  return (
    <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column",
      alignItems: "center", justifyContent: "center", opacity: op,
      transform: `translateY(${ty}px)` }}>
      <div style={{ display: "flex", alignItems: "center", gap: 24 }}>
        <svg viewBox="-18 -18 236 236" width="210" height="210"
          style={{ flex: "none", transform: `scale(${markScale})` }}
          dangerouslySetInnerHTML={{ __html: LOGO_MARK }} />
        <div style={{ fontFamily: DISPLAY, fontWeight: 600, fontSize: 122, color: C.ink,
          letterSpacing: "-0.02em", lineHeight: 1, whiteSpace: "nowrap" }}>
          Trama<span style={{ fontStyle: "italic", color: C.brick }}>ta</span>
        </div>
      </div>
      <div style={{ fontFamily: BODY, fontSize: 38, color: C.inkSoft, marginTop: 26,
        letterSpacing: "-0.005em", whiteSpace: "nowrap" }}>
        Automation, <span style={{ fontStyle: "italic", color: C.brick }}>woven into your business</span>.
      </div>
    </div>
  );
}

/* ============================================================ root scene */
function Movie() {
  const t = useTime();

  // surface the timestamp on the root for commenting
  React.useEffect(() => {
    const root = document.getElementById("video-root");
    if (root) root.setAttribute("data-screen-label", "t=" + t.toFixed(1) + "s");
  }, [Math.floor(t)]);

  // when embedded in the hero, tell the parent page once the first 10s play completes
  React.useEffect(() => {
    if (EMBED && t >= 9.6 && !window.__brainIntroDone) {
      window.__brainIntroDone = true;
      try { parent.postMessage({ type: "foral-brain-intro-complete" }, "*"); } catch (e) {}
    }
  }, [Math.floor(t * 4)]);

  return (
    <React.Fragment>
      <Backdrop />
      <Graph t={t} />
      <Eyebrow t={t} />
      <Caption t={t} start={2.9} end={5.6}>
        Every project, client and automation —
        <br />
        <span style={{ fontStyle: "italic", color: C.ink }}>captured in one connected graph.</span>
      </Caption>
      <Caption t={t} start={5.95} end={8.1}>
        Your AI reads the whole brain —
        <br />
        <span style={{ fontStyle: "italic", color: C.ink }}>full context, every single time.</span>
      </Caption>
      <SignOff t={t} />
    </React.Fragment>
  );
}

const EMBED = new URLSearchParams(location.search).has("embed");

// embed plays once per load — start fresh at 0 rather than resuming the saved playhead
if (EMBED) { try { localStorage.setItem("foral-2ndbrain-embed:t", "0"); } catch (e) {} }

ReactDOM.createRoot(document.getElementById("video-root")).render(
  <Stage width={1920} height={1080} duration={10} background={C.paper}
    controls={!EMBED} loop={!EMBED} persistKey={EMBED ? "foral-2ndbrain-embed" : "foral-2ndbrain"}>
    <Movie />
  </Stage>
);
