﻿/* Ramona Brief loop animation (production embed, no playback UI) */
(function () {
  const LOOP = 6;
  const BASE_W = 600;
  const BASE_H = 375;

  const BLACK = '#222220';
  const PINK = '#F1B1BB';

  const CAT_NW = 372;
  const CAT_NH = 355;
  const CAT_SCALE = 1.55;
  const CAT_W = CAT_NW * CAT_SCALE;
  const CAT_H = CAT_NH * CAT_SCALE;

  const SQ_SIZE = 30;
  const SQ_RADIUS = 8;

  const LOGO_SQUARES = [
    { px: -8, py: 90, phase: 0.0 },
    { px: -42, py: 140, phase: 0.18 },
    { px: -2, py: 195, phase: 0.4 },
    { px: 18, py: 260, phase: 0.62 },
    { px: 380, py: 85, phase: 0.1 },
    { px: 408, py: 145, phase: 0.3 },
    { px: 360, py: 200, phase: 0.55 },
    { px: 392, py: 260, phase: 0.78 },
  ];

  const N_BLACK = 32;
  const N_PINK = 4;

  const Easing = {
    easeOutCubic: (t) => {
      const x = t - 1;
      return x * x * x + 1;
    },
    easeInOutSine: (t) => -(Math.cos(Math.PI * t) - 1) / 2,
  };

  function rng(seed) {
    let s = seed * 9301 + 49297;
    return function next() {
      s = (s * 9301 + 49297) % 233280;
      return s / 233280;
    };
  }

  function buildParticles(edges) {
    const out = [];
    const total = N_BLACK + N_PINK;

    for (let i = 0; i < total; i++) {
      const r = rng(i + 1);
      const isPink = i >= N_BLACK;
      const edge = edges[Math.floor(r() * edges.length)];

      const angle = Math.atan2(edge.ny, edge.nx) + (r() - 0.5) * 0.7;
      const nx = Math.cos(angle);
      const ny = Math.sin(angle);
      const distance = 180 + r() * 380;
      const phase = (i / total + r() * 0.08) % 1;
      const sizeMul = 0.72 + r() * 0.55;

      out.push({
        px: edge.x,
        py: edge.y,
        nx,
        ny,
        distance,
        phase,
        sizeMul,
        color: isPink ? PINK : BLACK,
        rotSpin: (r() - 0.5) * 30,
      });
    }

    return out.sort((a, b) => a.phase - b.phase);
  }

  function mapToBox(boxW, boxH, px, py) {
    const catX = (boxW - CAT_W) / 2;
    const catY = (boxH - CAT_H) / 2;

    return {
      x: catX + px * CAT_SCALE,
      y: catY + py * CAT_SCALE,
      catX,
      catY,
      catW: CAT_W,
      catH: CAT_H,
    };
  }

  function computeParticlePose(particle, p, boxW, boxH) {
    const life = (p + particle.phase) % 1;
    const travel = Easing.easeOutCubic(life);
    const dist = particle.distance * travel;
    const startOffset = SQ_SIZE * 0.55;

    const start = mapToBox(boxW, boxH, particle.px, particle.py);
    const x = start.x + particle.nx * (startOffset + dist);
    const y = start.y + particle.ny * (startOffset + dist);

    let opacity;
    if (life < 0.15) opacity = life / 0.15;
    else if (life < 0.55) opacity = 1;
    else opacity = Math.max(0, 1 - (life - 0.55) / 0.45);
    opacity = Easing.easeInOutSine(opacity);

    return {
      x,
      y,
      size: SQ_SIZE * particle.sizeMul,
      opacity,
      rotate: particle.rotSpin * travel,
    };
  }

  function computeLogoPose(s, i, p, boxW, boxH) {
    const phase = (p + s.phase) % 1;
    const dx = Math.sin(phase * Math.PI * 2) * 7;
    const dy = Math.cos(phase * Math.PI * 2 + i) * 6;
    const rot = Math.sin(phase * Math.PI * 2 + i * 0.7) * 6;
    const pos = mapToBox(boxW, boxH, s.px, s.py);

    return {
      x: pos.x + dx,
      y: pos.y + dy,
      rotate: rot,
      size: SQ_SIZE,
    };
  }

  function computeSceneFit(boxW, boxH, particles) {
    const cat = mapToBox(boxW, boxH, 0, 0);
    let minX = cat.catX;
    let minY = cat.catY;
    let maxX = cat.catX + cat.catW;
    let maxY = cat.catY + cat.catH;

    const includeSquare = (x, y, size) => {
      const half = size / 2;
      minX = Math.min(minX, x - half);
      maxX = Math.max(maxX, x + half);
      minY = Math.min(minY, y - half);
      maxY = Math.max(maxY, y + half);
    };

    const SAMPLES = 180;
    for (let step = 0; step < SAMPLES; step++) {
      const p = step / (SAMPLES - 1);

      for (let i = 0; i < LOGO_SQUARES.length; i++) {
        const l = computeLogoPose(LOGO_SQUARES[i], i, p, boxW, boxH);
        includeSquare(l.x, l.y, l.size);
      }

      for (let i = 0; i < particles.length; i++) {
        const pose = computeParticlePose(particles[i], p, boxW, boxH);
        includeSquare(pose.x, pose.y, pose.size);
      }
    }

    const padding = 8;
    minX -= padding;
    minY -= padding;
    maxX += padding;
    maxY += padding;

    const contentW = Math.max(1, maxX - minX);
    const contentH = Math.max(1, maxY - minY);
    const fit = Math.min(boxW / contentW, boxH / contentH);

    const tx = (boxW - contentW * fit) / 2 - minX * fit;
    const ty = (boxH - contentH * fit) / 2 - minY * fit;

    return { fit, tx, ty, cat };
  }

  function Square(props) {
    const { x, y, size, color, opacity, rotate } = props;
    return (
      <div
        style={{
          position: 'absolute',
          left: x - size / 2,
          top: y - size / 2,
          width: size,
          height: size,
          borderRadius: SQ_RADIUS,
          background: color,
          opacity,
          transform: `rotate(${rotate}deg)`,
          willChange: 'transform, opacity',
          pointerEvents: 'none',
        }}
      />
    );
  }

  function RamonaBriefLoop() {
    const [edges, setEdges] = React.useState([]);
    const [time, setTime] = React.useState(0);
    const [reducedMotion, setReducedMotion] = React.useState(false);
    const [viewportScale, setViewportScale] = React.useState(1);
    const rootRef = React.useRef(null);

    React.useEffect(() => {
      let cancelled = false;
      fetch('assets/anim/edge-points.json')
        .then((r) => r.json())
        .then((json) => {
          if (!cancelled && Array.isArray(json)) {
            setEdges(json);
          }
        })
        .catch(() => {
          if (!cancelled) setEdges([]);
        });
      return () => {
        cancelled = true;
      };
    }, []);

    React.useEffect(() => {
      if (!window.matchMedia) return;
      const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
      const sync = () => setReducedMotion(!!mq.matches);
      sync();
      if (mq.addEventListener) mq.addEventListener('change', sync);
      else mq.addListener(sync);
      return () => {
        if (mq.removeEventListener) mq.removeEventListener('change', sync);
        else mq.removeListener(sync);
      };
    }, []);

    React.useEffect(() => {
      const el = rootRef.current;
      if (!el) return;

      const measure = () => {
        const { width, height } = el.getBoundingClientRect();
        if (!width || !height) return;
        setViewportScale(Math.min(width / BASE_W, height / BASE_H));
      };

      measure();
      let ro = null;
      if (window.ResizeObserver) {
        ro = new ResizeObserver(measure);
        ro.observe(el);
      }
      window.addEventListener('resize', measure);

      return () => {
        if (ro) ro.disconnect();
        window.removeEventListener('resize', measure);
      };
    }, []);

    React.useEffect(() => {
      if (reducedMotion) {
        setTime(0);
        return;
      }

      let raf = 0;
      const startedAt = performance.now();

      const tick = (now) => {
        setTime((now - startedAt) / 1000);
        raf = requestAnimationFrame(tick);
      };

      raf = requestAnimationFrame(tick);
      return () => cancelAnimationFrame(raf);
    }, [reducedMotion]);

    const particles = React.useMemo(() => {
      if (!edges.length) return [];
      return buildParticles(edges);
    }, [edges]);

    const sceneFit = React.useMemo(
      () => computeSceneFit(BASE_W, BASE_H, particles),
      [particles]
    );

    const p = (time % LOOP) / LOOP;

    return (
      <div ref={rootRef} className="brief-loop" aria-hidden="true">
        <div
          className="brief-loop-stage"
          style={{
            width: BASE_W,
            height: BASE_H,
            transform: `translate(-50%, -50%) scale(${viewportScale})`,
          }}
        >
          <div
            className="brief-loop-scene"
            style={{
              transform: `translate(${sceneFit.tx}px, ${sceneFit.ty}px) scale(${sceneFit.fit})`,
            }}
          >
            {particles.map((particle, i) => {
              const pose = computeParticlePose(particle, p, BASE_W, BASE_H);
              return (
                <Square
                  key={`p-${i}`}
                  x={pose.x}
                  y={pose.y}
                  size={pose.size}
                  color={particle.color}
                  opacity={pose.opacity}
                  rotate={pose.rotate}
                />
              );
            })}

            {LOGO_SQUARES.map((s, i) => {
              const l = computeLogoPose(s, i, p, BASE_W, BASE_H);
              return (
                <Square
                  key={`l-${i}`}
                  x={l.x}
                  y={l.y}
                  size={l.size}
                  color={BLACK}
                  opacity={1}
                  rotate={l.rotate}
                />
              );
            })}

            <img
              src="assets/anim/cat.png"
              alt=""
              draggable={false}
              style={{
                position: 'absolute',
                left: sceneFit.cat.catX,
                top: sceneFit.cat.catY,
                width: sceneFit.cat.catW,
                height: sceneFit.cat.catH,
                pointerEvents: 'none',
                userSelect: 'none',
              }}
            />
          </div>
        </div>
      </div>
    );
  }

  window.RamonaBriefLoop = RamonaBriefLoop;
})();
