/* ========================================================================
   LUMINA — More tunnel visualizers (rave / club / festival mode)
   ======================================================================== */
(function () {
  const TAU = Math.PI * 2;
  const hsla = (h, s = 80, l = 60, a = 1) => `hsla(${h}, ${s}%, ${l}%, ${a})`;
  const bg = (ctx, w, h, P) => {
    ctx.fillStyle = `rgba(8,8,12,${P.bgAlpha ?? 0.18})`;
    ctx.fillRect(0, 0, w, h);
  };

  /* ============ WORMHOLE — perspective tube with flowing stripes ============ */
  const Wormhole = {
    id: "wormhole", name: "Wormhole", tag: "DEPTH ∞",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <ellipse cx="12" cy="12" rx="10" ry="6"/>
      <ellipse cx="12" cy="12" rx="7" ry="4"/>
      <ellipse cx="12" cy="12" rx="4" ry="2.5"/>
      <ellipse cx="12" cy="12" rx="2" ry="1"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const cx = w/2, cy = h/2;
      const N = Math.max(40, Math.floor(P.density * 80));
      const speed = (1 + A.bass * 5 * P.intensity);
      const scroll = (t * speed) % 1;
      ctx.shadowBlur = P.glow * 16;
      for (let i = N; i > 0; i--) {
        const u = (i + scroll) / N;
        const z = u * u; // perspective depth
        const r = Math.min(w, h) * 0.55 * z;
        const ry = r * 0.55;
        const hue = (P.hue + i * 7 + t * 60) % 360;
        const alpha = 1 - u * 0.5;
        ctx.strokeStyle = hsla(hue, 100, 60, alpha);
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.lineWidth = (1 - u) * P.lineWidth * 3 + 0.5;
        ctx.beginPath();
        ctx.ellipse(cx, cy, r, ry, 0, 0, TAU);
        ctx.stroke();
      }
      // center sun
      const sg = ctx.createRadialGradient(cx, cy, 0, cx, cy, 60 * (1 + A.bass));
      sg.addColorStop(0, hsla(P.hue, 100, 80, 0.8));
      sg.addColorStop(1, hsla(P.hue, 100, 50, 0));
      ctx.fillStyle = sg;
      ctx.beginPath(); ctx.arc(cx, cy, 60 * (1 + A.bass), 0, TAU); ctx.fill();
    },
  };

  /* ============ HEX TUNNEL ============ */
  const HexTunnel = {
    id: "hexTunnel", name: "Hex Tunnel", tag: "POLYGON",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <polygon points="12,2 21,7 21,17 12,22 3,17 3,7"/>
      <polygon points="12,7 17,9.5 17,14.5 12,17 7,14.5 7,9.5"/>
      <polygon points="12,11 14,12 14,14 12,15 10,14 10,12"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const cx = w/2, cy = h/2;
      const sides = 6;
      const N = 22;
      const speed = (1 + A.bass * 4 * P.intensity);
      const scroll = (t * speed * 0.6) % 1;
      const rot = t * 0.2 * P.motion;
      ctx.shadowBlur = P.glow * 18;
      for (let i = N; i > 0; i--) {
        const u = (i + scroll) / N;
        const z = u * u;
        const r = Math.min(w, h) * 0.6 * z;
        const hue = (P.hue + i * 10 + t * 40) % 360;
        const a = (1 - u * 0.4) * (i % 2 ? 1 : 0.45);
        ctx.strokeStyle = hsla(hue, 100, 60, a);
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.lineWidth = (1 - u) * P.lineWidth * 2.5 + 0.5;
        ctx.beginPath();
        for (let s = 0; s <= sides; s++) {
          const ang = (s / sides) * TAU + rot + (i % 2 ? Math.PI / sides : 0);
          const x = cx + Math.cos(ang) * r;
          const y = cy + Math.sin(ang) * r;
          if (s === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
        }
        ctx.stroke();
      }
    },
  };

  /* ============ NEON HOOPS — circular hoops flying past ============ */
  const NeonHoops = {
    id: "neonHoops", name: "Neon Hoops", tag: "DRIVE",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <ellipse cx="6" cy="12" rx="3" ry="8"/>
      <ellipse cx="12" cy="12" rx="3" ry="9"/>
      <ellipse cx="18" cy="12" rx="3" ry="8"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const cx = w/2, cy = h/2;
      const N = Math.max(20, Math.floor(P.density * 50));
      const speed = (1.2 + A.bass * 6 * P.intensity);
      const scroll = (t * speed * 0.8) % 1;
      ctx.shadowBlur = P.glow * 22;
      for (let i = N; i >= 0; i--) {
        const u = (i + scroll) / N;
        const z = Math.pow(u, 2.4);
        const offsetY = Math.sin(t * 1.5 + i * 0.5) * 30 * (1 - z) * P.motion;
        const r = Math.min(w, h) * 0.42 * z;
        const ry = r * 0.92;
        const hue = (P.hue + i * 14 + t * 80) % 360;
        const alpha = Math.max(0.05, 1 - u * 0.6);
        ctx.strokeStyle = hsla(hue, 100, 65, alpha);
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.lineWidth = (1 - u) * P.lineWidth * 5 + 1;
        ctx.beginPath();
        ctx.ellipse(cx, cy + offsetY, r, ry, 0, 0, TAU);
        ctx.stroke();
      }
    },
  };

  /* ============ STARGATE — concentric pulsing rings ============ */
  const Stargate = {
    id: "stargate", name: "Stargate", tag: "PULSE",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="7"/>
      <circle cx="12" cy="12" r="4"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const cx = w/2, cy = h/2;
      const baseR = Math.min(w, h) * 0.5;
      const N = 14;
      const speed = (1 + A.bass * 3 * P.intensity);
      const scroll = (t * speed) % 1;
      ctx.shadowBlur = P.glow * 24;
      // background nebula
      const bgGrd = ctx.createRadialGradient(cx, cy, 0, cx, cy, baseR);
      bgGrd.addColorStop(0, hsla(P.hue, 100, 50, 0.4 * A.energy));
      bgGrd.addColorStop(1, hsla(P.hue + 180, 100, 30, 0));
      ctx.fillStyle = bgGrd;
      ctx.fillRect(0, 0, w, h);
      // rings
      for (let i = 0; i < N; i++) {
        const u = ((i + scroll * 2) % N) / N;
        const r = baseR * (1 - u * 0.95);
        const hue = (P.hue + u * 360 + t * 30) % 360;
        const lw = (1 - u) * P.lineWidth * 6 + 2;
        const alpha = Math.pow(1 - u, 1.2);
        ctx.strokeStyle = hsla(hue, 100, 65, alpha);
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.lineWidth = lw;
        ctx.beginPath();
        ctx.arc(cx, cy, r, 0, TAU);
        ctx.stroke();
      }
      // beat-driven shockwave
      if (A.beat > 0.3) {
        ctx.strokeStyle = hsla((P.hue + 60) % 360, 100, 80, A.beat);
        ctx.lineWidth = 3 + A.beat * 4;
        ctx.shadowColor = "white";
        ctx.shadowBlur = 30;
        ctx.beginPath();
        ctx.arc(cx, cy, baseR * (0.3 + A.beat * 0.6), 0, TAU);
        ctx.stroke();
      }
    },
  };

  /* ============ WIREFRAME TUBE — sectional lines through tunnel ============ */
  const WireTube = {
    id: "wireTube", name: "Wireframe Tube", tag: "WIRE",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <ellipse cx="12" cy="12" rx="10" ry="5"/>
      <path d="M2 12 L22 12 M12 7 L12 17"/>
      <ellipse cx="12" cy="12" rx="5" ry="2.5"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const cx = w/2, cy = h/2;
      const SEG = 18;
      const SECT = 12;
      const speed = (0.8 + A.bass * 4 * P.intensity);
      const scroll = (t * speed) % (1 / SEG);
      ctx.shadowBlur = P.glow * 14;
      // ellipses (depth rings)
      for (let i = 0; i < SEG; i++) {
        const u = (i / SEG) + scroll * SEG;
        const z = u * u;
        const r = Math.min(w, h) * 0.55 * z;
        const ry = r * 0.6;
        const hue = (P.hue + i * 12 + t * 30) % 360;
        ctx.strokeStyle = hsla(hue, 100, 60, 1 - u * 0.5);
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.lineWidth = (1 - u) * P.lineWidth * 1.5 + 0.5;
        ctx.beginPath();
        ctx.ellipse(cx, cy, r, ry, 0, 0, TAU);
        ctx.stroke();
      }
      // longitudinal lines connecting rings
      for (let s = 0; s < SECT; s++) {
        const ang = (s / SECT) * TAU + t * 0.3 * P.motion;
        ctx.beginPath();
        for (let i = 0; i < SEG; i++) {
          const u = (i / SEG) + scroll * SEG;
          const z = u * u;
          const r = Math.min(w, h) * 0.55 * z;
          const ry = r * 0.6;
          const x = cx + Math.cos(ang) * r;
          const y = cy + Math.sin(ang) * ry;
          if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
        }
        const hue = (P.hue + s * 30) % 360;
        ctx.strokeStyle = hsla(hue, 100, 70, 0.7);
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.lineWidth = P.lineWidth * 0.7;
        ctx.stroke();
      }
    },
  };

  /* ============ SPIRAL VORTEX — hypnotic rotating spiral ============ */
  const SpiralVortex = {
    id: "spiralVortex", name: "Spiral Vortex", tag: "HYPNO",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M12 2 Q22 5 22 12 Q22 20 12 22 Q4 22 4 14 Q4 8 10 8 Q14 8 14 12 Q14 14 12 14"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const cx = w/2, cy = h/2;
      const arms = 4;
      const turns = 8;
      const N = 360;
      const rot = t * (0.5 + A.bass * 2) * P.motion;
      ctx.shadowBlur = P.glow * 16;
      for (let a = 0; a < arms; a++) {
        ctx.beginPath();
        for (let i = 0; i < N; i++) {
          const u = i / N;
          const r = u * Math.min(w, h) * 0.5;
          const ang = u * TAU * turns + (a / arms) * TAU + rot;
          const x = cx + Math.cos(ang) * r;
          const y = cy + Math.sin(ang) * r;
          if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
        }
        const hue = (P.hue + a * (360 / arms) + t * 50) % 360;
        ctx.strokeStyle = hsla(hue, 100, 60);
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.lineWidth = P.lineWidth + 1 + A.energy * 4 * P.intensity;
        ctx.stroke();
      }
    },
  };

  /* ============ TRON TUNNEL — grid floor + ceiling perspective ============ */
  const TronTunnel = {
    id: "tronTunnel", name: "Tron Tunnel", tag: "GRID",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M2 4 L22 4 M2 20 L22 20 M2 4 L12 12 L2 20 M22 4 L12 12 L22 20"/>
      <path d="M5 7 L19 7 M5 17 L19 17" opacity="0.5"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      // dark sky
      const sky = ctx.createLinearGradient(0, 0, 0, h);
      sky.addColorStop(0, hsla(P.hue + 220, 70, 4));
      sky.addColorStop(0.5, hsla(P.hue + 280, 70, 12));
      sky.addColorStop(1, hsla(P.hue + 320, 70, 18));
      ctx.fillStyle = sky; ctx.fillRect(0, 0, w, h);

      const vpX = w / 2, vpY = h / 2;
      const speed = (1 + A.bass * 4 * P.intensity);
      const scroll = (t * speed) % 1;
      ctx.shadowBlur = P.glow * 8;
      // grid floor
      ctx.strokeStyle = hsla(P.hue + 30, 100, 65, 0.9);
      ctx.shadowColor = hsla(P.hue + 30, 100, 60);
      ctx.lineWidth = 1.5;
      // depth lines (floor + ceiling)
      for (let i = -16; i <= 16; i++) {
        ctx.beginPath();
        ctx.moveTo(vpX + i * w * 0.06, h);
        ctx.lineTo(vpX, vpY);
        ctx.moveTo(vpX + i * w * 0.06, 0);
        ctx.lineTo(vpX, vpY);
        ctx.stroke();
      }
      // horizontal rows (perspective)
      for (let i = 1; i < 16; i++) {
        const u = ((i + scroll) % 16) / 16;
        const z = u * u;
        const y1 = vpY + (h - vpY) * z;
        const y2 = vpY - vpY * z;
        const alpha = 1 - u * 0.6;
        ctx.strokeStyle = hsla(P.hue + 30, 100, 65, alpha);
        ctx.beginPath();
        ctx.moveTo(0, y1); ctx.lineTo(w, y1);
        ctx.moveTo(0, y2); ctx.lineTo(w, y2);
        ctx.stroke();
      }
      // central glow
      const cg = ctx.createRadialGradient(vpX, vpY, 0, vpX, vpY, 200);
      cg.addColorStop(0, hsla(P.hue, 100, 80, 0.8 * (1 + A.bass)));
      cg.addColorStop(1, hsla(P.hue, 100, 50, 0));
      ctx.fillStyle = cg;
      ctx.beginPath(); ctx.arc(vpX, vpY, 200, 0, TAU); ctx.fill();
    },
  };

  /* ============ DNA TUNNEL — double helix flying past camera ============ */
  const DnaTunnel = {
    id: "dnaTunnel", name: "DNA Tunnel", tag: "HELIX",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M6 2 Q12 6 18 2 M6 6 Q12 10 18 6 M6 10 Q12 14 18 10 M6 14 Q12 18 18 14 M6 18 Q12 22 18 18"/>
      <path d="M8 4 L8 20 M16 4 L16 20" opacity="0.5"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const cx = w/2, cy = h/2;
      const N = 60;
      const speed = (1 + A.bass * 3 * P.intensity);
      const scroll = (t * speed * 0.5) % 1;
      const rot = t * 0.5 * P.motion;
      ctx.shadowBlur = P.glow * 18;
      // depth points along the tunnel
      for (let i = N; i > 0; i--) {
        const u = (i + scroll * N) / N;
        const z = u * u;
        const baseR = Math.min(w, h) * 0.35 * z;
        const ang = i * 0.4 + rot;
        // two helix strands
        for (let s = 0; s < 2; s++) {
          const a = ang + s * Math.PI;
          const x = cx + Math.cos(a) * baseR;
          const y = cy + Math.sin(a) * baseR * 0.55;
          const hue = (P.hue + s * 180 + i * 5 + t * 40) % 360;
          const r = (1 - u) * 8 + 2;
          ctx.fillStyle = hsla(hue, 100, 65, 1 - u * 0.5);
          ctx.shadowColor = hsla(hue, 100, 60);
          ctx.beginPath();
          ctx.arc(x, y, r, 0, TAU);
          ctx.fill();
        }
        // rungs every other frame
        if (i % 3 === 0) {
          const x1 = cx + Math.cos(ang) * baseR;
          const y1 = cy + Math.sin(ang) * baseR * 0.55;
          const x2 = cx + Math.cos(ang + Math.PI) * baseR;
          const y2 = cy + Math.sin(ang + Math.PI) * baseR * 0.55;
          ctx.strokeStyle = hsla((P.hue + 90) % 360, 100, 60, (1 - u) * 0.7);
          ctx.lineWidth = (1 - u) * P.lineWidth + 0.5;
          ctx.beginPath();
          ctx.moveTo(x1, y1); ctx.lineTo(x2, y2);
          ctx.stroke();
        }
      }
    },
  };

  /* ============ STAR TUNNEL — stars rushing past at warp ============ */
  const starsArr = [];
  const StarTunnel = {
    id: "starTunnel", name: "Warp Stars", tag: "WARP",
    icon: <svg viewBox="0 0 24 24" fill="currentColor">
      <path d="M2 12 L8 11 L8 13 Z"/><path d="M4 6 L9 10 L9 11 Z"/>
      <path d="M4 18 L9 14 L9 13 Z"/><path d="M22 12 L16 11 L16 13 Z"/>
      <path d="M20 6 L15 10 L15 11 Z"/><path d="M20 18 L15 14 L15 13 Z"/>
      <circle cx="12" cy="12" r="2"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      // fade trails
      ctx.fillStyle = `rgba(0,0,0,${0.25 - A.bass * 0.1})`;
      ctx.fillRect(0, 0, w, h);
      const cx = w/2, cy = h/2;
      const N = Math.max(80, Math.floor(P.density * 400));
      while (starsArr.length < N) {
        starsArr.push({
          ang: Math.random() * TAU,
          z: Math.random(), // 0=far, 1=close
          hue: Math.random() * 60,
        });
      }
      while (starsArr.length > N) starsArr.pop();
      const speed = 0.005 + A.bass * 0.08 * P.intensity;
      ctx.shadowBlur = P.glow * 10;
      for (const s of starsArr) {
        s.z += speed * (0.5 + s.z);
        if (s.z > 1) { s.z = 0; s.ang = Math.random() * TAU; s.hue = Math.random() * 60; }
        const r = s.z * Math.hypot(w, h) * 0.7;
        const x = cx + Math.cos(s.ang) * r;
        const y = cy + Math.sin(s.ang) * r;
        const x2 = cx + Math.cos(s.ang) * r * 0.85;
        const y2 = cy + Math.sin(s.ang) * r * 0.85;
        const hue = (P.hue + s.hue * 5) % 360;
        ctx.strokeStyle = hsla(hue, 100, 70, s.z);
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.lineWidth = s.z * P.lineWidth * 2 + 0.5;
        ctx.beginPath();
        ctx.moveTo(x, y); ctx.lineTo(x2, y2);
        ctx.stroke();
      }
    },
  };

  /* ============ NEON CHEVRONS — arrows flying down a tunnel ============ */
  const Chevrons = {
    id: "chevTunnel", name: "Chevron Drive", tag: "ARROW",
    icon: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
      <path d="M4 8 L12 4 L20 8"/>
      <path d="M4 14 L12 10 L20 14"/>
      <path d="M4 20 L12 16 L20 20"/>
    </svg>,
    draw(ctx, w, h, A, P, t) {
      bg(ctx, w, h, P);
      const cx = w/2, cy = h/2;
      const N = 12;
      const speed = (1 + A.bass * 4 * P.intensity);
      const scroll = (t * speed * 0.7) % 1;
      ctx.shadowBlur = P.glow * 18;
      for (let i = 0; i < N; i++) {
        const u = ((i + scroll) % N) / N;
        const z = u * u;
        const sz = Math.min(w, h) * 0.5 * z;
        const hue = (P.hue + i * 30 + t * 60) % 360;
        const alpha = (1 - u) * 0.95;
        ctx.strokeStyle = hsla(hue, 100, 65, alpha);
        ctx.shadowColor = hsla(hue, 100, 60);
        ctx.lineWidth = (1 - u) * P.lineWidth * 5 + 1.5;
        ctx.beginPath();
        ctx.moveTo(cx - sz, cy - sz * 0.35);
        ctx.lineTo(cx, cy + sz * 0.3);
        ctx.lineTo(cx + sz, cy - sz * 0.35);
        ctx.stroke();
      }
    },
  };

  window.VISUALIZERS.push(Wormhole, HexTunnel, NeonHoops, Stargate, WireTube,
                          SpiralVortex, TronTunnel, DnaTunnel, StarTunnel, Chevrons);
})();
