// peper-surfer.jsx, 3D endless runner met Three.js. Hete Peper rent
// als sprite-billboard over een echte 3D weg met perspectivische camera,
// fog, directional sun-light, scrollende road-texture en 3D obstakels.

function PeperSurfer({ onClose }) {
  const FIELD_W = 960;
  const FIELD_H = 540;
  const LANE_X = [-2.4, 0, 2.4];

  const [phase, setPhase] = React.useState("intro");
  const [score, setScore] = React.useState(0);
  const [pepers, setPepers] = React.useState(0);
  const [best, setBest] = React.useState(() => {
    try { return parseInt(localStorage.getItem("peper-surfer-best") || "0", 10) || 0; }
    catch { return 0; }
  });
  const [scale, setScale] = React.useState(1);
  const [narrow, setNarrow] = React.useState(typeof window !== "undefined" && window.innerWidth <= 600);
  const [threeReady, setThreeReady] = React.useState(typeof window !== "undefined" && !!window.THREE);
  const [, forceRender] = React.useReducer(x => x + 1, 0);

  const wrapRef = React.useRef(null);
  const canvasRef = React.useRef(null);
  const sceneRefs = React.useRef(null);

  const playerRef = React.useRef({ lane: 1, lerpLane: 1, y: 0, vy: 0, ducking: false, duckT: 0, runT: 0 });
  const obstaclesRef = React.useRef([]);
  const peperItemsRef = React.useRef([]);
  const lastFrameRef = React.useRef(0);
  const lastSpawnRef = React.useRef(0);
  const lastPeperRef = React.useRef(0);
  const speedRef = React.useRef(20);
  const distRef = React.useRef(0);
  const scoreRef = React.useRef(0);
  const peperCountRef = React.useRef(0);
  const idRef = React.useRef(0);
  const flashRef = React.useRef(0);
  const cameraShakeRef = React.useRef(0);

  // -- THREE.js loader --
  React.useEffect(() => {
    if (window.THREE) { setThreeReady(true); return; }
    const existing = document.querySelector('script[data-three-loader]');
    if (existing) {
      existing.addEventListener("load", () => setThreeReady(true));
      return;
    }
    const s = document.createElement("script");
    s.src = "/js/vendor/three.min.js";
    s.dataset.threeLoader = "1";
    s.onload = () => setThreeReady(true);
    s.onerror = () => console.error("three.js loading failed");
    document.head.appendChild(s);
  }, []);

  // -- responsive scale --
  React.useEffect(() => {
    function measure() {
      setNarrow(window.innerWidth <= 600);
      if (!wrapRef.current) return;
      const r = wrapRef.current.getBoundingClientRect();
      if (r.width > 0 && r.height > 0) {
        setScale(Math.max(0.3, Math.min(r.width / FIELD_W, r.height / FIELD_H)));
      }
    }
    measure();
    let ro;
    if (typeof ResizeObserver !== "undefined" && wrapRef.current) {
      ro = new ResizeObserver(measure);
      ro.observe(wrapRef.current);
    }
    window.addEventListener("resize", measure);
    window.addEventListener("orientationchange", measure);
    return () => {
      if (ro) ro.disconnect();
      window.removeEventListener("resize", measure);
      window.removeEventListener("orientationchange", measure);
    };
  }, []);

  React.useEffect(() => {
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => { document.body.style.overflow = prev; };
  }, []);

  // -- texture builders --
  function makeRoadTexture(THREE) {
    const c = document.createElement("canvas");
    c.width = 256; c.height = 1024;
    const g = c.getContext("2d");
    // base asphalt gradient
    const grad = g.createLinearGradient(0, 0, 256, 0);
    grad.addColorStop(0, "#221308");
    grad.addColorStop(0.5, "#3D2412");
    grad.addColorStop(1, "#221308");
    g.fillStyle = grad;
    g.fillRect(0, 0, 256, 1024);
    // grain
    for (let i = 0; i < 1200; i++) {
      const x = Math.random() * 256, y = Math.random() * 1024;
      g.fillStyle = `rgba(255,255,255,${0.02 + Math.random() * 0.05})`;
      g.fillRect(x, y, 1, 1);
    }
    for (let i = 0; i < 600; i++) {
      const x = Math.random() * 256, y = Math.random() * 1024;
      g.fillStyle = `rgba(0,0,0,${0.05 + Math.random() * 0.1})`;
      g.fillRect(x, y, 2, 2);
    }
    // baan-streepjes (links en rechts van het midden)
    g.fillStyle = "#FFD23F";
    const dashH = 80;
    const dashGap = 60;
    for (let y = 0; y < 1024; y += dashH + dashGap) {
      g.fillRect(86 - 4, y, 8, dashH);
      g.fillRect(170 - 4, y, 8, dashH);
    }
    // weg-randen (links en rechts hard donker)
    g.fillStyle = "#0A0502";
    g.fillRect(0, 0, 6, 1024);
    g.fillRect(250, 0, 6, 1024);
    // gele waarschuwingsblokjes langs de berm
    g.fillStyle = "#FFD23F";
    for (let y = 0; y < 1024; y += 36) {
      g.fillRect(8, y, 8, 16);
      g.fillRect(240, y, 8, 16);
    }
    g.fillStyle = "#996800";
    for (let y = 0; y < 1024; y += 36) {
      g.fillRect(8, y, 8, 4);
      g.fillRect(240, y, 8, 4);
    }
    const tex = new THREE.CanvasTexture(c);
    tex.wrapS = THREE.RepeatWrapping;
    tex.wrapT = THREE.RepeatWrapping;
    tex.repeat.set(1, 8);
    tex.colorSpace = THREE.SRGBColorSpace;
    return tex;
  }

  function makeSidewalkTexture(THREE) {
    const c = document.createElement("canvas");
    c.width = 128; c.height = 256;
    const g = c.getContext("2d");
    g.fillStyle = "#7B1D11";
    g.fillRect(0, 0, 128, 256);
    // tegels
    g.strokeStyle = "rgba(0,0,0,0.45)";
    g.lineWidth = 2;
    for (let y = 0; y < 256; y += 32) {
      g.beginPath();
      g.moveTo(0, y);
      g.lineTo(128, y);
      g.stroke();
    }
    for (let x = 0; x < 128; x += 32) {
      g.beginPath();
      g.moveTo(x, 0);
      g.lineTo(x, 256);
      g.stroke();
    }
    // grain
    for (let i = 0; i < 600; i++) {
      const x = Math.random() * 128, y = Math.random() * 256;
      g.fillStyle = `rgba(255,255,255,${0.02 + Math.random() * 0.05})`;
      g.fillRect(x, y, 1, 1);
    }
    const tex = new THREE.CanvasTexture(c);
    tex.wrapS = THREE.RepeatWrapping;
    tex.wrapT = THREE.RepeatWrapping;
    tex.repeat.set(0.5, 6);
    tex.colorSpace = THREE.SRGBColorSpace;
    return tex;
  }

  function makeSkyTexture(THREE) {
    const c = document.createElement("canvas");
    c.width = 64; c.height = 512;
    const g = c.getContext("2d");
    const grad = g.createLinearGradient(0, 0, 0, 512);
    grad.addColorStop(0, "#1B1240");
    grad.addColorStop(0.25, "#5C2160");
    grad.addColorStop(0.5, "#E6602D");
    grad.addColorStop(0.7, "#FFA050");
    grad.addColorStop(0.9, "#FFD688");
    grad.addColorStop(1, "#FFE0B0");
    g.fillStyle = grad;
    g.fillRect(0, 0, 64, 512);
    // wolken
    g.fillStyle = "rgba(255,255,255,0.3)";
    for (let i = 0; i < 14; i++) {
      const cx = Math.random() * 64;
      const cy = 200 + Math.random() * 120;
      const r = 6 + Math.random() * 10;
      g.beginPath();
      g.arc(cx, cy, r, 0, Math.PI * 2);
      g.fill();
    }
    const tex = new THREE.CanvasTexture(c);
    tex.colorSpace = THREE.SRGBColorSpace;
    return tex;
  }

  function makeBuildingTexture(THREE, baseColor, ambientColor) {
    const c = document.createElement("canvas");
    c.width = 64; c.height = 128;
    const g = c.getContext("2d");
    g.fillStyle = baseColor;
    g.fillRect(0, 0, 64, 128);
    // ramen
    for (let y = 8; y < 128 - 8; y += 12) {
      for (let x = 4; x < 64 - 4; x += 10) {
        if (Math.random() < 0.55) {
          g.fillStyle = "#FFD23F";
          g.fillRect(x, y, 6, 6);
          g.fillStyle = "rgba(255,210,60,0.4)";
          g.fillRect(x - 1, y - 1, 8, 8);
        } else {
          g.fillStyle = ambientColor;
          g.fillRect(x, y, 6, 6);
        }
      }
    }
    const tex = new THREE.CanvasTexture(c);
    tex.colorSpace = THREE.SRGBColorSpace;
    tex.wrapS = THREE.RepeatWrapping;
    tex.wrapT = THREE.RepeatWrapping;
    return tex;
  }

  function makeSpriteTexture(THREE, src) {
    const tex = new THREE.TextureLoader().load(src);
    tex.colorSpace = THREE.SRGBColorSpace;
    tex.minFilter = THREE.LinearFilter;
    tex.magFilter = THREE.LinearFilter;
    return tex;
  }

  function makeWasknijperBarrier(THREE) {
    // vier wasknijpers naast elkaar als sprite-plane
    const tex = new THREE.TextureLoader().load("/assets/strip-01-wasknijper.png");
    tex.colorSpace = THREE.SRGBColorSpace;
    const mat = new THREE.MeshBasicMaterial({ map: tex, transparent: true, alphaTest: 0.4 });
    const grp = new THREE.Group();
    for (let i = 0; i < 3; i++) {
      const geo = new THREE.PlaneGeometry(1.0, 1.0);
      const m = new THREE.Mesh(geo, mat);
      m.position.x = (i - 1) * 0.7;
      m.position.y = 0.5;
      grp.add(m);
    }
    grp.userData.kind = "jump";
    return grp;
  }

  function makeBannerObstacle(THREE) {
    const c = document.createElement("canvas");
    c.width = 512; c.height = 128;
    const g = c.getContext("2d");
    g.fillStyle = "#FFE16A";
    g.fillRect(0, 0, 512, 128);
    g.save();
    g.beginPath();
    g.rect(0, 0, 512, 128);
    g.clip();
    g.strokeStyle = "rgba(0,0,0,0.55)";
    g.lineWidth = 30;
    for (let i = -128; i < 512 + 128; i += 60) {
      g.beginPath();
      g.moveTo(i, 0);
      g.lineTo(i + 128, 128);
      g.stroke();
    }
    g.restore();
    g.fillStyle = "#1A1A1A";
    g.font = "bold 76px 'Bangers', sans-serif";
    g.textAlign = "center";
    g.textBaseline = "middle";
    g.fillText("BUKKEN!", 256, 70);
    g.strokeStyle = "#1A1A1A";
    g.lineWidth = 8;
    g.strokeRect(4, 4, 504, 120);
    const tex = new THREE.CanvasTexture(c);
    tex.colorSpace = THREE.SRGBColorSpace;
    const mat = new THREE.MeshBasicMaterial({ map: tex, transparent: false });
    const geo = new THREE.PlaneGeometry(2.4, 0.6);
    const mesh = new THREE.Mesh(geo, mat);
    mesh.position.y = 1.55;
    // ophang-touwtjes (twee dunne cylinders)
    const ropeGeo = new THREE.CylinderGeometry(0.02, 0.02, 0.6, 6);
    const ropeMat = new THREE.MeshBasicMaterial({ color: 0x1a1a1a });
    const r1 = new THREE.Mesh(ropeGeo, ropeMat);
    r1.position.set(-0.9, 2.05, 0);
    const r2 = new THREE.Mesh(ropeGeo, ropeMat);
    r2.position.set(0.9, 2.05, 0);
    const grp = new THREE.Group();
    grp.add(mesh, r1, r2);
    grp.userData.kind = "duck";
    return grp;
  }

  function makeIceBlockObstacle(THREE) {
    const geo = new THREE.BoxGeometry(1.6, 1.6, 1.6);
    const mat = new THREE.MeshPhongMaterial({
      color: 0x6EC9F0,
      shininess: 100,
      specular: 0xFFFFFF,
      transparent: true,
      opacity: 0.92,
    });
    const mesh = new THREE.Mesh(geo, mat);
    mesh.position.y = 0.8;
    // edge lines
    const edges = new THREE.EdgesGeometry(geo);
    const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0x0F2638, linewidth: 2 }));
    line.position.y = 0.8;
    // glanslijn
    const hi = new THREE.Mesh(
      new THREE.BoxGeometry(1.0, 0.2, 1.62),
      new THREE.MeshBasicMaterial({ color: 0xFFFFFF, transparent: true, opacity: 0.5 })
    );
    hi.position.y = 1.3;
    const grp = new THREE.Group();
    grp.add(mesh, line, hi);
    grp.userData.kind = "block";
    return grp;
  }

  function makePeperItem(THREE) {
    // peper-billboard sprite (gegenereerd op canvas)
    const c = document.createElement("canvas");
    c.width = 64; c.height = 96;
    const g = c.getContext("2d");
    g.clearRect(0, 0, 64, 96);
    // groen blaadje
    g.fillStyle = "#3FA34D";
    g.beginPath();
    g.moveTo(28, 6);
    g.lineTo(36, 6);
    g.quadraticCurveTo(42, 14, 36, 22);
    g.lineTo(28, 22);
    g.quadraticCurveTo(22, 14, 28, 6);
    g.fill();
    g.strokeStyle = "#1A1A1A";
    g.lineWidth = 2.5;
    g.stroke();
    // body shape
    g.fillStyle = "#E63946";
    g.beginPath();
    g.moveTo(20, 22);
    g.bezierCurveTo(8, 32, 12, 78, 32, 90);
    g.bezierCurveTo(52, 78, 56, 32, 44, 22);
    g.closePath();
    g.fill();
    g.strokeStyle = "#1A1A1A";
    g.lineWidth = 3;
    g.stroke();
    // glanslijn
    g.fillStyle = "rgba(255,255,255,0.55)";
    g.beginPath();
    g.ellipse(22, 42, 4, 14, -0.3, 0, Math.PI * 2);
    g.fill();
    // shadow rechts
    g.fillStyle = "rgba(0,0,0,0.18)";
    g.beginPath();
    g.ellipse(46, 60, 5, 18, 0.2, 0, Math.PI * 2);
    g.fill();
    const tex = new THREE.CanvasTexture(c);
    tex.colorSpace = THREE.SRGBColorSpace;
    const mat = new THREE.SpriteMaterial({ map: tex, transparent: true });
    const spr = new THREE.Sprite(mat);
    spr.scale.set(0.7, 1.0, 1);
    return spr;
  }

  // -- scene setup --
  function buildScene(THREE) {
    const renderer = new THREE.WebGLRenderer({ canvas: canvasRef.current, antialias: true, alpha: false });
    renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1));
    renderer.setSize(FIELD_W, FIELD_H, false);
    renderer.outputColorSpace = THREE.SRGBColorSpace;

    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0xE6602D);
    scene.fog = new THREE.Fog(0xE6602D, 18, 90);

    // camera
    const camera = new THREE.PerspectiveCamera(72, FIELD_W / FIELD_H, 0.1, 300);
    camera.position.set(0, 4.2, 7.5);
    camera.lookAt(0, 1.6, 0);

    // lichten
    const ambient = new THREE.HemisphereLight(0xFFD9A0, 0x8B2820, 0.85);
    scene.add(ambient);
    const sun = new THREE.DirectionalLight(0xFFE0A0, 1.4);
    sun.position.set(4, 12, 6);
    scene.add(sun);
    const back = new THREE.DirectionalLight(0xFF8C42, 0.6);
    back.position.set(0, 5, -20);
    scene.add(back);

    // sky-dome (large sphere with inverted faces showing sky texture)
    const skyTex = makeSkyTexture(THREE);
    const skyGeo = new THREE.SphereGeometry(180, 32, 16);
    const skyMat = new THREE.MeshBasicMaterial({ map: skyTex, side: THREE.BackSide, fog: false });
    const sky = new THREE.Mesh(skyGeo, skyMat);
    scene.add(sky);

    // sun visual
    const sunGeo = new THREE.CircleGeometry(8, 32);
    const sunMat = new THREE.MeshBasicMaterial({ color: 0xFFEC8A, fog: false });
    const sunMesh = new THREE.Mesh(sunGeo, sunMat);
    sunMesh.position.set(0, 18, -90);
    scene.add(sunMesh);
    // halo
    const haloGeo = new THREE.CircleGeometry(16, 32);
    const haloMat = new THREE.MeshBasicMaterial({ color: 0xFFD23F, transparent: true, opacity: 0.35, fog: false });
    const halo = new THREE.Mesh(haloGeo, haloMat);
    halo.position.set(0, 18, -91);
    scene.add(halo);

    // weg
    const roadTex = makeRoadTexture(THREE);
    const roadMat = new THREE.MeshLambertMaterial({ map: roadTex });
    const roadGeo = new THREE.PlaneGeometry(8, 400);
    const road = new THREE.Mesh(roadGeo, roadMat);
    road.rotation.x = -Math.PI / 2;
    road.position.z = -195;
    scene.add(road);

    // sidewalks (rood-bruine grond aan beide kanten)
    const sideTex = makeSidewalkTexture(THREE);
    const sideMat = new THREE.MeshLambertMaterial({ map: sideTex });
    const sideGeoLeft = new THREE.PlaneGeometry(40, 400);
    const sideLeft = new THREE.Mesh(sideGeoLeft, sideMat);
    sideLeft.rotation.x = -Math.PI / 2;
    sideLeft.position.set(-24, -0.01, -195);
    scene.add(sideLeft);
    const sideRight = new THREE.Mesh(sideGeoLeft, sideMat);
    sideRight.rotation.x = -Math.PI / 2;
    sideRight.position.set(24, -0.01, -195);
    scene.add(sideRight);

    // gebouwen langs de weg, beide kanten
    const buildingsLeft = [];
    const buildingsRight = [];
    const colors = [0x7B1D11, 0x5C1109, 0x4D0A06, 0x9B1421];
    for (let i = 0; i < 24; i++) {
      const z = -i * 16 - 6;
      const w = 4 + Math.random() * 6;
      const h = 5 + Math.random() * 14;
      const d = 4 + Math.random() * 6;
      const c = colors[Math.floor(Math.random() * colors.length)];
      const tex = makeBuildingTexture(THREE,
        "#" + c.toString(16).padStart(6, "0"),
        "#3A0A05");
      const mat = new THREE.MeshLambertMaterial({ map: tex });
      const geo = new THREE.BoxGeometry(w, h, d);
      const ml = new THREE.Mesh(geo, mat);
      ml.position.set(-7 - Math.random() * 8, h / 2, z);
      scene.add(ml);
      buildingsLeft.push(ml);
      const mr = new THREE.Mesh(geo.clone(), mat);
      mr.position.set(7 + Math.random() * 8, h / 2, z);
      scene.add(mr);
      buildingsRight.push(mr);
    }

    // peper-vlag op een toren (rood vlaggetje, mast)
    const flagTowerGeo = new THREE.BoxGeometry(2, 18, 2);
    const flagTowerMat = new THREE.MeshLambertMaterial({ color: 0x4D0A06 });
    const flagTower = new THREE.Mesh(flagTowerGeo, flagTowerMat);
    flagTower.position.set(-14, 9, -50);
    scene.add(flagTower);
    const mastGeo = new THREE.CylinderGeometry(0.05, 0.05, 4, 8);
    const mast = new THREE.Mesh(mastGeo, new THREE.MeshBasicMaterial({ color: 0x1A1A1A }));
    mast.position.set(-14, 20, -50);
    scene.add(mast);
    const flagGeo = new THREE.PlaneGeometry(1.6, 1);
    const flagMat = new THREE.MeshBasicMaterial({ color: 0xE63946, side: THREE.DoubleSide });
    const flag = new THREE.Mesh(flagGeo, flagMat);
    flag.position.set(-13.2, 21, -50);
    scene.add(flag);

    // speler-billboard (Hete Peper)
    const heroTex = new THREE.TextureLoader().load("/assets/hete-peper.png");
    heroTex.colorSpace = THREE.SRGBColorSpace;
    heroTex.minFilter = THREE.LinearFilter;
    heroTex.magFilter = THREE.LinearFilter;
    const heroMat = new THREE.SpriteMaterial({ map: heroTex, transparent: true });
    const hero = new THREE.Sprite(heroMat);
    hero.scale.set(2.2, 2.6, 1);
    hero.position.set(0, 1.3, 4.5);
    scene.add(hero);

    // skateboard onder hero
    const skateGeo = new THREE.BoxGeometry(1.6, 0.12, 0.55);
    const skateMat = new THREE.MeshPhongMaterial({ color: 0xFF8C42, shininess: 60 });
    const skate = new THREE.Mesh(skateGeo, skateMat);
    skate.position.set(0, 0.18, 4.5);
    scene.add(skate);
    // wieltjes
    const wheelGeo = new THREE.CylinderGeometry(0.15, 0.15, 0.18, 16);
    const wheelMat = new THREE.MeshPhongMaterial({ color: 0xFFD23F, shininess: 80 });
    const wheels = [];
    for (const dx of [-0.55, 0.55]) {
      for (const dz of [-0.18, 0.18]) {
        const w = new THREE.Mesh(wheelGeo, wheelMat);
        w.rotation.z = Math.PI / 2;
        w.position.set(dx, 0.05, 4.5 + dz);
        scene.add(w);
        wheels.push(w);
      }
    }

    // schaduw onder hero (cirkelvormig discus)
    const shadowGeo = new THREE.CircleGeometry(0.9, 24);
    const shadowMat = new THREE.MeshBasicMaterial({ color: 0x000000, transparent: true, opacity: 0.4, fog: false });
    const heroShadow = new THREE.Mesh(shadowGeo, shadowMat);
    heroShadow.rotation.x = -Math.PI / 2;
    heroShadow.position.set(0, 0.02, 4.5);
    scene.add(heroShadow);

    // particle group voor sparkles (bij oppakken peper)
    const sparkleParticles = [];

    return {
      THREE, scene, camera, renderer,
      roadTex, sky, sunMesh, halo,
      hero, heroShadow, skate, wheels,
      sparkleParticles,
    };
  }

  function spawnObstacle(refs, kind, lane) {
    const { THREE, scene } = refs;
    let mesh;
    if (kind === "jump") mesh = makeWasknijperBarrier(THREE);
    else if (kind === "duck") mesh = makeBannerObstacle(THREE);
    else mesh = makeIceBlockObstacle(THREE);
    mesh.position.set(LANE_X[lane], 0, -85);
    scene.add(mesh);
    return mesh;
  }

  function spawnPeperItem(refs, lane, zStart) {
    const { THREE, scene } = refs;
    const sprite = makePeperItem(THREE);
    sprite.position.set(LANE_X[lane], 1.0, zStart);
    scene.add(sprite);
    return sprite;
  }

  function spawnSparkles(refs, x, y, z, n) {
    const { THREE, scene } = refs;
    for (let i = 0; i < n; i++) {
      const geo = new THREE.SphereGeometry(0.06 + Math.random() * 0.05, 6, 6);
      const mat = new THREE.MeshBasicMaterial({ color: 0xFFD23F, transparent: true, opacity: 1, fog: false });
      const m = new THREE.Mesh(geo, mat);
      m.position.set(x + (Math.random() - 0.5) * 0.4, y + (Math.random() - 0.5) * 0.4, z);
      m.userData.vx = (Math.random() - 0.5) * 6;
      m.userData.vy = 1 + Math.random() * 4;
      m.userData.vz = (Math.random() - 0.5) * 4;
      m.userData.life = 0.8;
      m.userData.max = 1.0;
      scene.add(m);
      refs.sparkleParticles.push(m);
    }
  }

  // -- start/reset/control --
  function reset(refs) {
    playerRef.current = { lane: 1, lerpLane: 1, y: 0, vy: 0, ducking: false, duckT: 0, runT: 0 };
    if (refs) {
      const { scene, sparkleParticles } = refs;
      for (const o of obstaclesRef.current) scene.remove(o.mesh);
      for (const it of peperItemsRef.current) scene.remove(it.mesh);
      for (const s of sparkleParticles) scene.remove(s);
      sparkleParticles.length = 0;
    }
    obstaclesRef.current = [];
    peperItemsRef.current = [];
    lastSpawnRef.current = 0;
    lastPeperRef.current = 0;
    speedRef.current = 20;
    distRef.current = 0;
    scoreRef.current = 0;
    peperCountRef.current = 0;
    flashRef.current = 0;
    cameraShakeRef.current = 0;
    setScore(0);
    setPepers(0);
  }

  function start() {
    if (sceneRefs.current) reset(sceneRefs.current); else reset();
    setPhase("play");
  }
  function jump() {
    const p = playerRef.current;
    if (p.y === 0 && !p.ducking) p.vy = 7.2;
  }
  function duck() {
    const p = playerRef.current;
    if (p.y === 0) { p.ducking = true; p.duckT = 0.55; }
  }
  function switchLane(dir) {
    const p = playerRef.current;
    p.lane = Math.max(0, Math.min(2, p.lane + dir));
  }
  function endGame() {
    flashRef.current = 0.5;
    cameraShakeRef.current = 0.4;
    setPhase("over");
    const finalScore = Math.floor(scoreRef.current);
    if (finalScore > best) {
      setBest(finalScore);
      try { localStorage.setItem("peper-surfer-best", String(finalScore)); } catch {}
    }
  }

  // -- main loop --
  React.useEffect(() => {
    if (phase !== "play") return;
    if (!threeReady) return;
    const THREE = window.THREE;
    if (!THREE) return;
    if (!sceneRefs.current) {
      sceneRefs.current = buildScene(THREE);
    }
    const refs = sceneRefs.current;
    refs.renderer.setSize(FIELD_W, FIELD_H, false);

    lastFrameRef.current = performance.now();
    let raf;
    function frame(t) {
      const dt = Math.min(0.05, (t - lastFrameRef.current) / 1000);
      lastFrameRef.current = t;

      speedRef.current = Math.min(45, 20 + distRef.current * 0.04);
      distRef.current += speedRef.current * dt;
      scoreRef.current += dt * 14 + dt * speedRef.current * 0.6;
      setScore(Math.floor(scoreRef.current));
      if (cameraShakeRef.current > 0) cameraShakeRef.current = Math.max(0, cameraShakeRef.current - dt);

      const p = playerRef.current;
      p.lerpLane += (p.lane - p.lerpLane) * Math.min(1, dt * 14);
      p.runT += dt * (8 + speedRef.current * 0.1);
      if (p.y > 0 || p.vy > 0) {
        p.y += p.vy * dt;
        p.vy -= 18 * dt;
        if (p.y < 0) { p.y = 0; p.vy = 0; }
      }
      if (p.ducking) {
        p.duckT -= dt;
        if (p.duckT <= 0) p.ducking = false;
      }

      // road texture scroll (illusion van vooruit-bewegen)
      if (refs.roadTex) {
        refs.roadTex.offset.y -= speedRef.current * dt * 0.06;
      }

      // hero bob/duck/lane
      const px = LANE_X[0] + (LANE_X[2] - LANE_X[0]) * (p.lerpLane / 2);
      const bob = p.y === 0 ? Math.sin(p.runT * 2) * 0.06 : 0;
      const heroBaseY = 1.3 + p.y + bob;
      const heroY = p.ducking ? heroBaseY - 0.55 : heroBaseY;
      refs.hero.position.set(px, heroY, 4.5);
      refs.hero.scale.set(2.2, p.ducking ? 1.5 : 2.6, 1);
      refs.heroShadow.position.set(px, 0.02, 4.5);
      const shadowScale = Math.max(0.55, 1 - p.y * 0.18);
      refs.heroShadow.scale.set(shadowScale, shadowScale, 1);
      refs.heroShadow.material.opacity = Math.max(0.12, 0.45 - p.y * 0.12);
      refs.skate.position.set(px, 0.18 + p.y, 4.5);
      refs.skate.rotation.z = (p.vy > 0 ? -0.18 : (p.y > 0 ? 0.12 : Math.sin(p.runT * 2) * 0.04));
      for (let i = 0; i < refs.wheels.length; i++) {
        const w = refs.wheels[i];
        const dx = i < 2 ? -0.55 : 0.55;
        const dz = (i % 2 === 0) ? -0.18 : 0.18;
        w.position.set(px + dx, 0.05 + p.y, 4.5 + dz);
        w.rotation.x += speedRef.current * dt * 1.5;
      }

      // camera shake
      const shake = cameraShakeRef.current;
      refs.camera.position.set(
        (Math.random() - 0.5) * shake * 2,
        4.2 + (Math.random() - 0.5) * shake * 1.5,
        7.5 + (Math.random() - 0.5) * shake
      );
      refs.camera.lookAt(0, 1.6, 0);

      // spawn obstacles
      lastSpawnRef.current += dt;
      const startGrace = distRef.current < 30 ? 1.5 : 0;
      const spawnGap = Math.max(0.6, 1.4 - distRef.current * 0.0006) + startGrace;
      if (lastSpawnRef.current >= spawnGap) {
        lastSpawnRef.current = 0;
        const types = distRef.current > 90 ? ["jump", "duck", "block"] : ["jump", "block"];
        const kind = types[Math.floor(Math.random() * types.length)];
        const lane = distRef.current < 60
          ? (Math.random() < 0.5 ? 0 : 2)
          : Math.floor(Math.random() * 3);
        const mesh = spawnObstacle(refs, kind, lane);
        obstaclesRef.current.push({ id: ++idRef.current, mesh, lane, kind, passed: false });
      }

      // spawn pepers
      lastPeperRef.current += dt;
      if (lastPeperRef.current >= 0.6 + Math.random() * 0.4) {
        lastPeperRef.current = 0;
        const lane = Math.floor(Math.random() * 3);
        const count = 2 + Math.floor(Math.random() * 3);
        for (let i = 0; i < count; i++) {
          const sprite = spawnPeperItem(refs, lane, -85 - i * 1.2);
          peperItemsRef.current.push({ id: ++idRef.current, mesh: sprite, lane, got: false });
        }
      }

      // beweeg obstakels en pepers naar de camera (Z+)
      const moveZ = speedRef.current * dt;
      for (const o of obstaclesRef.current) o.mesh.position.z += moveZ;
      for (const it of peperItemsRef.current) {
        it.mesh.position.z += moveZ;
        // wobble voor visueel
        it.mesh.position.y = 1.0 + Math.sin(distRef.current * 0.05 + it.id) * 0.1;
      }

      // collision (bij Z ~ 4.5, speler positie)
      const playerLane = Math.round(p.lerpLane);
      const aligned = Math.abs(p.lerpLane - p.lane) < 0.35;
      for (const o of obstaclesRef.current) {
        if (o.passed) continue;
        if (o.mesh.position.z > 3.4 && o.mesh.position.z < 5.2) {
          if (playerLane === o.lane && aligned) {
            if (o.kind === "jump" && p.y > 0.55) {
              // gesprongen
            } else if (o.kind === "duck" && p.ducking) {
              // gebukt
            } else {
              endGame();
              raf && cancelAnimationFrame(raf);
              break;
            }
          }
          o.passed = true;
        }
      }

      for (const it of peperItemsRef.current) {
        if (it.got) continue;
        if (it.mesh.position.z > 3.6 && it.mesh.position.z < 5.0) {
          if (playerLane === it.lane && aligned && !p.ducking && p.y < 1.4) {
            it.got = true;
            peperCountRef.current += 1;
            scoreRef.current += 15;
            setPepers(peperCountRef.current);
            spawnSparkles(refs, it.mesh.position.x, it.mesh.position.y, it.mesh.position.z, 10);
            refs.scene.remove(it.mesh);
          }
        }
      }

      // cleanup
      obstaclesRef.current = obstaclesRef.current.filter(o => {
        if (o.mesh.position.z > 12) { refs.scene.remove(o.mesh); return false; }
        return true;
      });
      peperItemsRef.current = peperItemsRef.current.filter(it => {
        if (it.got) return false;
        if (it.mesh.position.z > 12) { refs.scene.remove(it.mesh); return false; }
        return true;
      });

      // sparkles update
      for (const sp of refs.sparkleParticles) {
        sp.position.x += sp.userData.vx * dt;
        sp.position.y += sp.userData.vy * dt;
        sp.position.z += sp.userData.vz * dt;
        sp.userData.vy -= 8 * dt;
        sp.userData.life -= dt;
        sp.material.opacity = Math.max(0, sp.userData.life / sp.userData.max);
      }
      refs.sparkleParticles = refs.sparkleParticles.filter(s => {
        if (s.userData.life <= 0) { refs.scene.remove(s); return false; }
        return true;
      });

      // flash overlay (rendered on top via material trick: simulated via fog mix temporarily)
      // -- simpler: we render normally, then do a 2D overlay via canvas2d state? Skip for now.

      refs.renderer.render(refs.scene, refs.camera);
      raf = requestAnimationFrame(frame);
    }
    raf = requestAnimationFrame(frame);
    return () => raf && cancelAnimationFrame(raf);
  }, [phase, threeReady]);

  // initial render to show background even before play
  React.useEffect(() => {
    if (phase === "play") return;
    if (!threeReady) return;
    const THREE = window.THREE;
    if (!THREE) return;
    if (!sceneRefs.current) sceneRefs.current = buildScene(THREE);
    const refs = sceneRefs.current;
    refs.renderer.setSize(FIELD_W, FIELD_H, false);
    refs.renderer.render(refs.scene, refs.camera);
  }, [phase, threeReady, scale]);

  // controls
  React.useEffect(() => {
    function down(e) {
      if (phase === "intro" || phase === "over") {
        if (e.key === " " || e.key === "Enter") { start(); e.preventDefault(); }
        else if (e.key === "Escape") onClose && onClose();
        return;
      }
      if (e.key === "ArrowLeft" || e.key === "a" || e.key === "A") { switchLane(-1); e.preventDefault(); }
      else if (e.key === "ArrowRight" || e.key === "d" || e.key === "D") { switchLane(1); e.preventDefault(); }
      else if (e.key === "ArrowUp" || e.key === "w" || e.key === "W" || e.key === " ") { jump(); e.preventDefault(); }
      else if (e.key === "ArrowDown" || e.key === "s" || e.key === "S") { duck(); e.preventDefault(); }
      else if (e.key === "Escape") onClose && onClose();
    }
    window.addEventListener("keydown", down);
    return () => window.removeEventListener("keydown", down);
  }, [phase, onClose]);

  const touchRef = React.useRef(null);
  function onTouchStart(e) {
    if (phase !== "play") return;
    const t = e.touches && e.touches[0];
    if (!t) return;
    touchRef.current = { x: t.clientX, y: t.clientY };
  }
  function onTouchEnd(e) {
    if (!touchRef.current) return;
    const t = e.changedTouches && e.changedTouches[0];
    if (!t) { touchRef.current = null; return; }
    const dx = t.clientX - touchRef.current.x;
    const dy = t.clientY - touchRef.current.y;
    const adx = Math.abs(dx), ady = Math.abs(dy);
    if (Math.max(adx, ady) > 28) {
      if (adx > ady) { dx > 0 ? switchLane(1) : switchLane(-1); }
      else { dy < 0 ? jump() : duck(); }
    } else if (phase === "play") {
      jump();
    }
    touchRef.current = null;
  }

  return (
    <div onClick={e => e.stopPropagation()} style={{
      position: "fixed", inset: 0, zIndex: 200,
      background: "linear-gradient(180deg, #1B1240 0%, #5C2160 25%, #E6602D 60%, #FFD688 100%)",
      fontFamily: "'Patrick Hand', cursive",
      color: "#1A1A1A",
      display: "flex", flexDirection: "column",
      overscrollBehavior: "contain",
      touchAction: "none",
    }}>
      <div style={{
        flexShrink: 0,
        display: "flex", alignItems: "center", justifyContent: "space-between",
        padding: narrow ? "8px 10px" : "10px 16px",
        gap: 8,
        background: "rgba(255,255,255,0.55)",
        borderBottom: "3px solid #1A1A1A",
        backdropFilter: "blur(4px)",
      }}>
        <div style={{
          fontFamily: "'Bangers', sans-serif",
          fontSize: narrow ? 22 : 30, letterSpacing: "0.04em",
          color: "#E63946",
          WebkitTextStroke: "1.4px #1A1A1A",
          textShadow: "2px 2px 0 #1A1A1A",
          display: "flex", alignItems: "center", gap: 6,
          whiteSpace: "nowrap",
        }}>
          <span style={{ fontSize: narrow ? 22 : 26, WebkitTextStroke: 0, textShadow: "none" }}>🛹</span>
          {narrow ? "SURFER" : "PEPER SURFER"}
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: narrow ? 6 : 10 }}>
          <div style={hudBadge("#FFD23F", "#1A1A1A", narrow)}>⚡ {score}</div>
          <div style={hudBadge("#E63946", "#FFF", narrow)}>🌶️ {pepers}</div>
          {!narrow && <div style={hudBadge("#3FA34D", "#FFF", false)}>🏆 {best}</div>}
          <button onClick={onClose} aria-label="Sluiten" style={{
            width: 44, height: 44, borderRadius: "50%",
            background: "#1A1A1A", color: "#FFE4F2",
            border: "3px solid #FFE4F2", boxShadow: "2px 2px 0 #1A1A1A",
            fontFamily: "'Bangers', sans-serif", fontSize: 22,
            cursor: "pointer", flexShrink: 0,
          }}>✕</button>
        </div>
      </div>

      <div ref={wrapRef} style={{
        flex: 1, position: "relative",
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: 6, overflow: "hidden",
      }}>
        <div style={{ width: FIELD_W * scale, height: FIELD_H * scale, position: "relative" }}>
          <div onTouchStart={onTouchStart} onTouchEnd={onTouchEnd}
            style={{
              position: "absolute",
              width: FIELD_W, height: FIELD_H,
              left: 0, top: 0,
              transform: `scale(${scale})`,
              transformOrigin: "0 0",
              border: "5px solid #1A1A1A",
              boxShadow: "0 8px 0 #1A1A1A, inset 0 0 0 3px rgba(255,255,255,0.4)",
              overflow: "hidden",
              cursor: phase === "play" ? "pointer" : "default",
              background: "#000",
            }}>
            <canvas ref={canvasRef}
              width={FIELD_W} height={FIELD_H}
              style={{ display: "block", width: FIELD_W, height: FIELD_H }} />

            {!threeReady && phase === "intro" && (
              <Overlay>
                <div style={{
                  fontFamily: "'Bangers', sans-serif", fontSize: 36,
                  color: "#E63946",
                  WebkitTextStroke: "1.5px #1A1A1A",
                  textShadow: "3px 3px 0 #1A1A1A",
                }}>3D ENGINE LADEN…</div>
              </Overlay>
            )}

            {phase === "intro" && threeReady && (
              <Overlay>
                <div style={{
                  fontFamily: "'Bangers', sans-serif",
                  fontSize: 68, lineHeight: 1,
                  color: "#E63946",
                  WebkitTextStroke: "2.5px #1A1A1A",
                  textShadow: "5px 5px 0 #1A1A1A",
                  letterSpacing: "0.03em",
                  transform: "rotate(-2deg)",
                }}>PEPER SURFER</div>
                <div style={{
                  background: "#FFF", border: "4px solid #1A1A1A",
                  boxShadow: "5px 5px 0 #1A1A1A",
                  padding: "12px 20px",
                  fontFamily: "'Patrick Hand', cursive", fontSize: 18,
                  maxWidth: 520, lineHeight: 1.4,
                }}>
                  Drie banen, eindeloos rennen door Pepperoni-City. Wissel met
                  <b> ← →</b> of veeg, <b>spring</b> met ↑ over wasknijpers,
                  <b> buk</b> met ↓ onder hekken door. Pak <b>🌶️ pepertjes</b>
                  voor extra punten.
                </div>
                <button onClick={start} style={primaryBtn}>🛹 SURFEN!</button>
              </Overlay>
            )}

            {phase === "over" && (
              <Overlay dark>
                <div style={{
                  fontFamily: "'Bangers', sans-serif",
                  fontSize: 60, lineHeight: 1,
                  color: "#FFD23F",
                  WebkitTextStroke: "2.4px #1A1A1A",
                  textShadow: "5px 5px 0 #1A1A1A",
                  letterSpacing: "0.03em",
                  transform: "rotate(-2deg)",
                }}>BOTSING!</div>
                <div style={{ fontFamily: "'Patrick Hand', cursive", fontSize: 22, color: "#FFF" }}>
                  Hete Peper liep tegen iets pittigs aan.
                </div>
                <div style={{ display: "flex", gap: 14, marginTop: 8, flexWrap: "wrap", justifyContent: "center" }}>
                  <div style={overBadge("#FFD23F", "#1A1A1A")}>⚡ {Math.floor(scoreRef.current)}</div>
                  <div style={overBadge("#E63946", "#FFF")}>🌶️ {pepers}</div>
                  <div style={overBadge("#3FA34D", "#FFF", true)}>🏆 {best}</div>
                </div>
                <button onClick={start} style={{ ...primaryBtn, fontSize: 28, padding: "12px 28px" }}>🛹 NOG EEN KEER!</button>
              </Overlay>
            )}

            {phase === "play" && narrow && (
              <div style={{
                position: "absolute",
                left: 0, right: 0, bottom: 0,
                display: "flex", justifyContent: "space-between",
                padding: 12, gap: 8,
                pointerEvents: "none",
                zIndex: 5,
              }}>
                <div style={{ display: "flex", gap: 8, pointerEvents: "auto" }}>
                  <button onClick={() => switchLane(-1)} aria-label="Naar links" style={ctrlBtnStyle}>◀</button>
                  <button onClick={() => switchLane(1)} aria-label="Naar rechts" style={ctrlBtnStyle}>▶</button>
                </div>
                <div style={{ display: "flex", gap: 8, pointerEvents: "auto" }}>
                  <button onClick={duck} aria-label="Bukken" style={ctrlBtnStyle}>▼</button>
                  <button onClick={jump} aria-label="Springen" style={ctrlBtnStyle}>▲</button>
                </div>
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

function hudBadge(bg, fg, narrow) {
  return {
    background: bg, color: fg,
    border: "3px solid #1A1A1A", boxShadow: "3px 3px 0 #1A1A1A",
    padding: narrow ? "4px 10px" : "6px 14px",
    fontFamily: "'Bangers', sans-serif",
    fontSize: narrow ? 16 : 18, letterSpacing: "0.04em",
    whiteSpace: "nowrap",
  };
}
function overBadge(bg, fg, special) {
  return {
    background: bg, color: fg,
    border: special ? "4px solid #FFD23F" : "4px solid #1A1A1A",
    boxShadow: "4px 4px 0 #1A1A1A",
    padding: "8px 18px",
    fontFamily: "'Bangers', sans-serif", fontSize: 24,
  };
}
const primaryBtn = {
  background: "linear-gradient(180deg, #FF8C42 0%, #E63946 60%, #B91322 100%)",
  color: "#FFF",
  fontFamily: "'Bangers', sans-serif",
  fontSize: 32, letterSpacing: "0.05em",
  padding: "14px 36px",
  border: "5px solid #1A1A1A",
  boxShadow: "6px 6px 0 #1A1A1A",
  cursor: "pointer",
  WebkitTextStroke: "1.2px #1A1A1A",
  textShadow: "2px 2px 0 #1A1A1A",
};
const ctrlBtnStyle = {
  width: 56, height: 56, borderRadius: "50%",
  background: "rgba(255,255,255,0.9)", color: "#1A1A1A",
  border: "3px solid #1A1A1A", boxShadow: "3px 3px 0 #1A1A1A",
  fontFamily: "'Bangers', sans-serif", fontSize: 24,
  cursor: "pointer",
};

function Overlay({ children, dark }) {
  return (
    <div style={{
      position: "absolute", inset: 0,
      background: dark ? "rgba(40, 20, 10, 0.85)" : "rgba(255, 220, 180, 0.92)",
      display: "flex", flexDirection: "column",
      alignItems: "center", justifyContent: "center",
      gap: 14, padding: 20, textAlign: "center",
      zIndex: 10,
    }}>{children}</div>
  );
}

Object.assign(window, { PeperSurfer });
