/* global React, THREE */
const { useState, useEffect, useRef, useCallback } = React;

// ── Weezevent — URLs filtrées par contexte
// 👉 Remplacer chaque URL par le widget filtré correspondant (Weezevent > Canaux de vente > Modules de billetterie > Nouveau widget filtré)
const WZ_POPUP_OPTS = "width=650,height=600,top=100,left=100,toolbar=no,resizable=yes,scrollbars=yes,status=no";
const WZ = {
  regular:  "https://widget.weezevent.com/ticket/fb1dc096-5edd-4db7-8052-6df2b0efdc1f?id_evenement=2093971&locale=fr-FR&code=29313",
  viptable: "https://widget.weezevent.com/ticket/d7cd9430-5d05-4adf-b005-adeb8797f0b3?id_evenement=2093971&locale=fr-FR&code=29313",
  all:      "https://widget.weezevent.com/ticket/E2093971/?code=29313&locale=fr-FR&width_auto=1&color_primary=0032FA",
};
function openWeezevent(key) {
  const url = WZ[key] || WZ.all;
  const w = window.open(url, "Billetterie Weezevent", WZ_POPUP_OPTS);
  if (w) w.focus();
}

// ── Tables VIP réservées (fallback manuel si Google Sheets indispo)
// IDs : "vip-f1" à "vip-f4" (front), "vip-b1"/"vip-b2" (bâtiment),
//       "vip-e1a","vip-e1b" (gauche), "vip-e2a","vip-e2b" (droite)
const PERMANENTLY_RESERVED = [];

// ── Google Sheets — synchro temps réel entre tous les visiteurs
// 👉 Remplacer "" par l'URL Web App du Google Apps Script (voir guide)
const SHEETS_URL = "";

// ── Table catalog ──────────────────────────────────────────────────────────
const TABLE_TYPES = {
  vip:  { label: "Table VIP",    forfait: 600, color: 0xffc23d, accent: "#ffc23d", desc: "Table VIP bord de piscine. Accueil personnalisé, service dédié. La bouteille est incluse dans le forfait 600€.", bonus: "🍾 1 bouteille incluse dans le forfait 600€" },
  free: { label: "Table Lounge", forfait: 0,   color: 0x6dd5ff, accent: "#6dd5ff", desc: "Coin lounge avec coussins, transats et petite table. Accès libre — premiers arrivés, premiers servis ! Aucun paiement requis." },
};

function buildTables() {
  const tables = [];
  const POOL_W = 36, POOL_D = 10;
  const FRONT_Z  =  POOL_D / 2 + 2.5;   // positive Z — front of pool
  const BACK_Z   = -(POOL_D / 2 + 2.8);  // negative Z — building side
  const LEFT_X   = -(POOL_W / 2 + 5);
  const RIGHT_X  =  POOL_W / 2 + 5;

  // ── 10 VIP tables ──
  // 4 on front side (positive Z, facing pool)
  [-15, -5, 5, 15].forEach((x, i) => {
    tables.push({ id: `vip-f${i+1}`, type: "vip", x, z: FRONT_Z, label: `VIP ${i+1}` });
  });
  // 2 on building side — côte à côte (VIP 5 & 6 ensemble)
  tables.push({ id: "vip-b1", type: "vip", x: -8,  z: BACK_Z, label: "VIP 5" });
  tables.push({ id: "vip-b2", type: "vip", x: -2,  z: BACK_Z, label: "VIP 6" });
  // 2 × 2 on short ends — 2 tables on each face
  tables.push({ id: "vip-e1a", type: "vip", x: LEFT_X,  z:  3, label: "VIP 7" });
  tables.push({ id: "vip-e1b", type: "vip", x: LEFT_X,  z: -3, label: "VIP 9" });
  tables.push({ id: "vip-e2a", type: "vip", x: RIGHT_X, z:  3, label: "VIP 8" });
  tables.push({ id: "vip-e2b", type: "vip", x: RIGHT_X, z: -3, label: "VIP 10" });

  // ── 34 free lounge tables (transats + coussins + petites tables) ──
  // Group A — Derrière les VIP avant (positive Z), 3 rangées = 13 tables
  [
    { xs: [-19, -11, -3,  5, 13],  z: 10  },
    { xs: [-16,  -7,  2, 11],      z: 15  },
    { xs: [-13,  -3,  7, 17],      z: 20  },
  ].forEach(({ xs, z }) => xs.forEach(x => tables.push({ type: "free", x, z })));


  // Number the free tables
  let n = 1;
  tables.forEach(t => {
    if (t.type === "free") {
      t.id = `free-${n}`;
      t.label = `Lounge ${n++}`;
    }
  });
  return tables;
}

const ALL_TABLES = buildTables();

// ── 3D Scene component ────────────────────────────────────────────────────
function BookingScene({ selectedId, reservedIds, hoveredId, onSelect, onHover }) {
  const canvasRef = useRef(null);
  const stateRef = useRef(null); // holds {scene, camera, renderer, tables, ...}

  // Init scene once
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas || !window.THREE) return;
    const THREE = window.THREE;

    // ── Procedural texture helpers ──
    function makeGradientTexture(stops) {
      const c = document.createElement("canvas");
      c.width = 2; c.height = 512;
      const ctx = c.getContext("2d");
      const g = ctx.createLinearGradient(0, 0, 0, 512);
      stops.forEach(([offset, color]) => g.addColorStop(offset, color));
      ctx.fillStyle = g;
      ctx.fillRect(0, 0, 2, 512);
      return new THREE.CanvasTexture(c);
    }
    function makeGrassTexture() {
      const c = document.createElement("canvas");
      c.width = 512; c.height = 512;
      const ctx = c.getContext("2d");
      ctx.fillStyle = "#5cb058";
      ctx.fillRect(0, 0, 512, 512);
      for (let i = 0; i < 8000; i++) {
        const x = Math.random() * 512, y = Math.random() * 512;
        const r = Math.random();
        ctx.fillStyle = r < 0.45 ? "#4d9c4a" : r < 0.85 ? "#6ec169" : "#82d97a";
        ctx.fillRect(x, y, 1 + Math.random() * 2, 1 + Math.random() * 2);
      }
      // Darker grass strokes
      ctx.strokeStyle = "#3d8a3a";
      for (let i = 0; i < 600; i++) {
        ctx.lineWidth = 0.5 + Math.random();
        const x = Math.random() * 512, y = Math.random() * 512;
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.lineTo(x + Math.random() * 4 - 2, y + 1 + Math.random() * 4);
        ctx.stroke();
      }
      const tex = new THREE.CanvasTexture(c);
      tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
      tex.repeat.set(40, 40);
      return tex;
    }
    function makeWoodTexture() {
      const c = document.createElement("canvas");
      c.width = 512; c.height = 256;
      const ctx = c.getContext("2d");
      ctx.fillStyle = "#a86b3a";
      ctx.fillRect(0, 0, 512, 256);
      // Plank lines
      for (let i = 0; i < 8; i++) {
        ctx.fillStyle = i % 2 === 0 ? "#9e623a" : "#b3744c";
        ctx.fillRect(0, i * 32, 512, 32);
        ctx.fillStyle = "rgba(60,30,15,0.5)";
        ctx.fillRect(0, i * 32, 512, 1);
      }
      // Wood grain
      ctx.strokeStyle = "rgba(60,30,15,0.25)";
      ctx.lineWidth = 0.5;
      for (let i = 0; i < 200; i++) {
        const y = Math.random() * 256;
        ctx.beginPath();
        ctx.moveTo(0, y);
        ctx.bezierCurveTo(128, y + (Math.random() - 0.5) * 6, 384, y + (Math.random() - 0.5) * 6, 512, y);
        ctx.stroke();
      }
      const tex = new THREE.CanvasTexture(c);
      tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
      tex.repeat.set(8, 3);
      return tex;
    }
    function makeWaterNormalMap() {
      const size = 256;
      const c = document.createElement("canvas");
      c.width = size; c.height = size;
      const ctx = c.getContext("2d");
      const img = ctx.createImageData(size, size);
      for (let y = 0; y < size; y++) {
        for (let x = 0; x < size; x++) {
          const i = (y * size + x) * 4;
          const nx = Math.sin(x * 0.18 + Math.cos(y * 0.13) * 2) + Math.sin(y * 0.09);
          const ny = Math.cos(y * 0.16 + Math.sin(x * 0.11) * 2) + Math.cos(x * 0.08);
          img.data[i]     = 128 + nx * 40;
          img.data[i + 1] = 128 + ny * 40;
          img.data[i + 2] = 255;
          img.data[i + 3] = 255;
        }
      }
      ctx.putImageData(img, 0, 0);
      const tex = new THREE.CanvasTexture(c);
      tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
      tex.repeat.set(4, 2);
      return tex;
    }
    function makeStripedPoufTexture() {
      const c = document.createElement("canvas");
      c.width = 256; c.height = 64;
      const ctx = c.getContext("2d");
      ctx.fillStyle = "#e8e0d0";
      ctx.fillRect(0, 0, 256, 64);
      ctx.fillStyle = "#7eaad0";
      for (let i = 0; i < 8; i++) ctx.fillRect(i * 32, 0, 16, 64);
      const tex = new THREE.CanvasTexture(c);
      tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
      return tex;
    }

    const scene = new THREE.Scene();
    // Gradient sky background
    scene.background = makeGradientTexture([
      [0, "#4ea3d8"],
      [0.55, "#a3d8ee"],
      [0.85, "#f5e9c8"],
      [1, "#f5d4a8"],
    ]);
    scene.fog = new THREE.Fog(0xa3d8ee, 80, 170);

    // ── Backdrop panorama photo (real venue photo behind the forest) ──
    const loader = new THREE.TextureLoader();
    loader.load("assets/IMG_5360.jpeg", (tex) => {
      tex.encoding = THREE.sRGBEncoding;
      const bdGeo = new THREE.PlaneGeometry(220, 55);
      const bdMat = new THREE.MeshBasicMaterial({ map: tex, fog: false });
      const bd = new THREE.Mesh(bdGeo, bdMat);
      bd.position.set(0, 22, -88);
      scene.add(bd);
    });

    const camera = new THREE.PerspectiveCamera(42, canvas.clientWidth / canvas.clientHeight, 0.1, 250);
    // Vue d'ensemble : côté positif (où sont les tables), légèrement en hauteur
    camera.position.set(0, 28, 55);
    camera.lookAt(0, 0, 8);

    const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: false });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setSize(canvas.clientWidth, canvas.clientHeight, false);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.outputEncoding = THREE.sRGBEncoding;
    renderer.toneMapping = THREE.LinearToneMapping;
    renderer.toneMappingExposure = 1.0;

    // ── Lighting ──
    const sun = new THREE.DirectionalLight(0xfff2c8, 2.1);
    sun.position.set(25, 45, 18);
    sun.castShadow = true;
    sun.shadow.mapSize.set(2048, 2048);
    sun.shadow.camera.left = -50;
    sun.shadow.camera.right = 50;
    sun.shadow.camera.top = 50;
    sun.shadow.camera.bottom = -50;
    sun.shadow.bias = -0.0005;
    sun.shadow.radius = 4;
    scene.add(sun);
    scene.add(new THREE.AmbientLight(0xfff5e0, 0.35));
    const hemi = new THREE.HemisphereLight(0xa3d8ee, 0x5cb058, 0.55);
    scene.add(hemi);
    // Subtle warm fill light
    const fill = new THREE.DirectionalLight(0xffb877, 0.4);
    fill.position.set(-20, 15, -10);
    scene.add(fill);

    // ── Ground (textured grass) ──
    const grassTex = makeGrassTexture();
    const ground = new THREE.Mesh(
      new THREE.PlaneGeometry(200, 200),
      new THREE.MeshStandardMaterial({ map: grassTex, roughness: 0.95 })
    );
    ground.rotation.x = -Math.PI / 2;
    ground.receiveShadow = true;
    scene.add(ground);

    // Wood deck around pool — 4 strips forming a frame (no center, so water is visible)
    const woodTex = makeWoodTexture();
    const deckMat = new THREE.MeshStandardMaterial({ map: woodTex, roughness: 0.85 });
    const deckHalfW = 21, deckHalfD = 8;
    const deckStripT = 3; // border thickness
    // North band (negative Z)
    const deckN = new THREE.Mesh(new THREE.BoxGeometry(deckHalfW * 2, 0.2, deckStripT), deckMat);
    deckN.position.set(0, 0.05, -deckHalfD + deckStripT / 2);
    deckN.receiveShadow = true;
    scene.add(deckN);
    // South band (positive Z)
    const deckS = new THREE.Mesh(new THREE.BoxGeometry(deckHalfW * 2, 0.2, deckStripT), deckMat);
    deckS.position.set(0, 0.05, deckHalfD - deckStripT / 2);
    deckS.receiveShadow = true;
    scene.add(deckS);
    // West band
    const deckWest = new THREE.Mesh(
      new THREE.BoxGeometry(deckHalfW - 18 + 3, 0.2, (deckHalfD - deckStripT) * 2),
      deckMat
    );
    deckWest.position.set(-(18 + (deckHalfW - 18 - 3) / 2 + 1.5), 0.05, 0);
    deckWest.receiveShadow = true;
    scene.add(deckWest);
    // East band
    const deckEast = new THREE.Mesh(
      new THREE.BoxGeometry(deckHalfW - 18 + 3, 0.2, (deckHalfD - deckStripT) * 2),
      deckMat
    );
    deckEast.position.set(18 + (deckHalfW - 18 - 3) / 2 + 1.5, 0.05, 0);
    deckEast.receiveShadow = true;
    scene.add(deckEast);

    // ── Pool with realistic water ──
    // Underwater tiled floor (gives depth perception through the transparent water)
    const tilesCanvas = document.createElement("canvas");
    tilesCanvas.width = 256; tilesCanvas.height = 256;
    const tctx = tilesCanvas.getContext("2d");
    tctx.fillStyle = "#7ec5d8";
    tctx.fillRect(0, 0, 256, 256);
    tctx.strokeStyle = "#5a9eb0";
    tctx.lineWidth = 2;
    for (let i = 0; i < 8; i++) {
      tctx.beginPath();
      tctx.moveTo(i * 32, 0); tctx.lineTo(i * 32, 256);
      tctx.moveTo(0, i * 32); tctx.lineTo(256, i * 32);
      tctx.stroke();
    }
    // Faint darker streaks
    tctx.fillStyle = "rgba(40,90,110,0.18)";
    for (let i = 0; i < 50; i++) {
      tctx.fillRect(Math.random() * 256, Math.random() * 256, 2 + Math.random() * 6, 1);
    }
    const tilesTex = new THREE.CanvasTexture(tilesCanvas);
    tilesTex.wrapS = tilesTex.wrapT = THREE.RepeatWrapping;
    tilesTex.repeat.set(10, 3);

    const poolBox = new THREE.Group();
    // Inner pool walls (light tiled)
    const innerMat = new THREE.MeshStandardMaterial({ map: tilesTex, roughness: 0.65, color: 0xa0d8e0 });
    const floorMesh = new THREE.Mesh(new THREE.BoxGeometry(35.6, 0.1, 9.6), innerMat);
    floorMesh.position.y = -1.55;
    floorMesh.receiveShadow = true;
    poolBox.add(floorMesh);
    // 4 walls of the pool (so it feels like a real basin)
    const wallW = new THREE.Mesh(new THREE.BoxGeometry(0.4, 1.6, 9.6), innerMat);
    wallW.position.set(-17.8, -0.75, 0); poolBox.add(wallW);
    const wallE = new THREE.Mesh(new THREE.BoxGeometry(0.4, 1.6, 9.6), innerMat);
    wallE.position.set(17.8, -0.75, 0); poolBox.add(wallE);
    const wallN = new THREE.Mesh(new THREE.BoxGeometry(35.6, 1.6, 0.4), innerMat);
    wallN.position.set(0, -0.75, -4.8); poolBox.add(wallN);
    const wallS = new THREE.Mesh(new THREE.BoxGeometry(35.6, 1.6, 0.4), innerMat);
    wallS.position.set(0, -0.75, 4.8); poolBox.add(wallS);
    scene.add(poolBox);

    // Water surface — turquoise/cyan vif, simple et bien visible
    const waterNormal = makeWaterNormalMap();
    const poolMat = new THREE.MeshLambertMaterial({
      color: 0x14b6dc,
      emissive: 0x0a85a8,
      emissiveIntensity: 0.55,
      normalMap: waterNormal,
    });
    const pool = new THREE.Mesh(new THREE.PlaneGeometry(35.6, 9.6, 32, 8), poolMat);
    pool.rotation.x = -Math.PI / 2;
    pool.position.y = 0.05;
    pool.receiveShadow = true;
    scene.add(pool);
    // Glints layer on top for sun sparkles
    const glintsTex = makeWaterNormalMap();
    glintsTex.repeat.set(8, 4);
    const glintsMat = new THREE.MeshBasicMaterial({
      color: 0xa8e8f5,
      transparent: true,
      opacity: 0.22,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
    });
    const glints = new THREE.Mesh(new THREE.PlaneGeometry(35.6, 9.6), glintsMat);
    glints.rotation.x = -Math.PI / 2;
    glints.position.y = 0.08;
    scene.add(glints);

    // Pool rim (stone coping) — 4 strips forming a hollow frame around the water
    const rimMat = new THREE.MeshStandardMaterial({ color: 0xeae5d8, roughness: 0.85 });
    const rimT = 0.7; // thickness (strip width)
    const rimY = 0.16;
    const rimH = 0.32;
    const poolHalfW = 17.8, poolHalfD = 4.8;
    [
      // North strip (negative Z)
      { size: [poolHalfW * 2 + rimT * 2, rimH, rimT], pos: [0, rimY, -poolHalfD - rimT / 2] },
      // South strip (positive Z)
      { size: [poolHalfW * 2 + rimT * 2, rimH, rimT], pos: [0, rimY, poolHalfD + rimT / 2] },
      // West strip
      { size: [rimT, rimH, poolHalfD * 2], pos: [-poolHalfW - rimT / 2, rimY, 0] },
      // East strip
      { size: [rimT, rimH, poolHalfD * 2], pos: [poolHalfW + rimT / 2, rimY, 0] },
    ].forEach(({ size, pos }) => {
      const strip = new THREE.Mesh(new THREE.BoxGeometry(...size), rimMat);
      strip.position.set(...pos);
      strip.receiveShadow = true;
      scene.add(strip);
    });

    // Pool floats (inflatables for extra realism)
    function createFloat(x, z, color, type) {
      let mesh;
      if (type === "ring") {
        mesh = new THREE.Mesh(
          new THREE.TorusGeometry(0.55, 0.18, 12, 24),
          new THREE.MeshStandardMaterial({ color, roughness: 0.8 })
        );
        mesh.rotation.x = Math.PI / 2;
      } else {
        mesh = new THREE.Mesh(
          new THREE.CapsuleGeometry ? new THREE.CapsuleGeometry(0.35, 0.7, 6, 10) : new THREE.SphereGeometry(0.45, 8, 6),
          new THREE.MeshStandardMaterial({ color, roughness: 0.85 })
        );
        mesh.rotation.x = Math.PI / 2;
      }
      mesh.position.set(x, 0.05, z);
      mesh.castShadow = true;
      return mesh;
    }
    scene.add(createFloat(-10, 1, 0xff6f4e, "ring"));
    scene.add(createFloat(5, -2, 0xffcc55, "ring"));
    scene.add(createFloat(11, 1.5, 0xff85b8, "ring"));
    scene.add(createFloat(-3, 2.5, 0x9ef03a, "capsule"));

    // ── Trees: varied conifers + deciduous ──
    function createTree(x, z, type) {
      const group = new THREE.Group();
      const scale = 0.85 + Math.random() * 0.5;
      const trunkH = 1.4 + Math.random() * 1.6;
      const trunk = new THREE.Mesh(
        new THREE.CylinderGeometry(0.28 * scale, 0.45 * scale, trunkH, 7),
        new THREE.MeshStandardMaterial({ color: 0x5a3a22, roughness: 0.95 })
      );
      trunk.position.y = trunkH / 2;
      trunk.castShadow = true;
      group.add(trunk);

      if (type === "conifer") {
        for (let i = 0; i < 3; i++) {
          const coneH = (3.2 - i * 0.4) * scale;
          const coneR = (2.1 - i * 0.45) * scale;
          const cone = new THREE.Mesh(
            new THREE.ConeGeometry(coneR, coneH, 7),
            new THREE.MeshStandardMaterial({
              color: i === 0 ? 0x2a6638 : i === 1 ? 0x357a48 : 0x40925a,
              roughness: 0.95,
            })
          );
          cone.position.y = trunkH + coneH * 0.4 + i * (coneH * 0.55);
          cone.castShadow = true;
          group.add(cone);
        }
      } else {
        // Deciduous: 4-6 sphere clusters
        const clusters = 4 + Math.floor(Math.random() * 3);
        const palette = [0x2f7a44, 0x3a8a52, 0x4d9d5f, 0x6bb070];
        for (let i = 0; i < clusters; i++) {
          const r = (0.85 + Math.random() * 0.7) * scale;
          const sphere = new THREE.Mesh(
            new THREE.SphereGeometry(r, 9, 7),
            new THREE.MeshStandardMaterial({ color: palette[i % 4], roughness: 0.95 })
          );
          sphere.position.set(
            (Math.random() - 0.5) * 2 * scale,
            trunkH + r * 0.7 + Math.random() * 1.8,
            (Math.random() - 0.5) * 2 * scale
          );
          sphere.castShadow = true;
          group.add(sphere);
        }
      }
      group.position.set(x, 0, z);
      group.rotation.y = Math.random() * Math.PI * 2;
      group._sway = Math.random() * Math.PI * 2;
      return group;
    }
    // Tall cedar/fir tree generator — narrow, dense, very vertical (like the photos)
    function createCedar(x, z, scale) {
      const group = new THREE.Group();
      const s = scale * (0.95 + Math.random() * 0.4);
      const trunkH = 2 + Math.random() * 1.5;
      const trunk = new THREE.Mesh(
        new THREE.CylinderGeometry(0.28 * s, 0.55 * s, trunkH, 7),
        new THREE.MeshStandardMaterial({ color: 0x4a3220, roughness: 0.95 })
      );
      trunk.position.y = trunkH / 2;
      trunk.castShadow = true;
      group.add(trunk);
      // Stacked cones — narrow + tall to look like cedar/fir
      const levels = 5;
      const baseH = 3.2 * s;
      const baseR = 2.4 * s;
      for (let i = 0; i < levels; i++) {
        const r = baseR * (1 - i * 0.16);
        const h = baseH * (1 - i * 0.08);
        const cone = new THREE.Mesh(
          new THREE.ConeGeometry(r, h, 8),
          new THREE.MeshStandardMaterial({
            color: i === 0 ? 0x223e2a : i === 1 ? 0x2a4d34 : i === 2 ? 0x305a3c : i === 3 ? 0x386b46 : 0x4a8554,
            roughness: 0.96,
          })
        );
        cone.position.y = trunkH + i * (baseH * 0.5);
        cone.castShadow = true;
        group.add(cone);
      }
      group.position.set(x, 0, z);
      group.rotation.y = Math.random() * Math.PI * 2;
      group._sway = Math.random() * Math.PI * 2;
      return group;
    }

    const trees = [];
    // ── Cèdres / sapins hauts derrière la piscine (côté négatif Z, où il n'y a pas de tables)
    // Dense forest backdrop — z décalé à -27 pour rester derrière le bâtiment (mur arrière à z=-21.5)
    for (let i = 0; i < 22; i++) {
      const x = -50 + (i * 100) / 22 + (Math.random() - 0.5) * 4;
      const z = -27 + (Math.random() - 0.5) * 4;   // range [-29, -25] — bien derrière le bâtiment
      const tree = createCedar(x, z, 1.4 + Math.random() * 0.6);
      scene.add(tree);
      trees.push(tree);
    }
    // Second row of cedars further behind (depth)
    for (let i = 0; i < 18; i++) {
      const x = -55 + (i * 110) / 18 + (Math.random() - 0.5) * 5;
      const z = -38 + (Math.random() - 0.5) * 8;
      const tree = createCedar(x, z, 1.6 + Math.random() * 0.7);
      scene.add(tree);
      trees.push(tree);
    }
    // Far row (silhouettes, simpler/fog-blurred)
    for (let i = 0; i < 14; i++) {
      const x = -65 + (i * 130) / 14;
      const z = -55;
      const tree = createCedar(x, z, 2.0 + Math.random() * 0.5);
      scene.add(tree);
      trees.push(tree);
    }
    // A few accent trees on the sides (left only — right side cleared for concert stage)
    [
      [-38, -8], [-38, -2],
      [-50, 5], [-44, 12],
    ].forEach(([x, z]) => {
      const tree = createCedar(x, z, 1.1 + Math.random() * 0.4);
      scene.add(tree);
      trees.push(tree);
    });
    // A small mix of deciduous trees on the table side (positive Z) for variety
    // Right side (positive X) cleared for concert stage visibility
    [
      [-44, 28, "deciduous"], [-30, 30, "deciduous"],
      [-15, 32, "deciduous"], [15, 32, "deciduous"],
    ].forEach(([x, z, t]) => {
      const tree = createTree(x, z, t);
      scene.add(tree);
      trees.push(tree);
    });

    // ── Pool ladders (échelles métalliques) ──
    function createLadder(x, z) {
      const group = new THREE.Group();
      const metal = new THREE.MeshStandardMaterial({ color: 0xc0c8d0, metalness: 0.9, roughness: 0.25 });
      // Two vertical rails curving over the edge
      const railGeo = new THREE.TorusGeometry(0.25, 0.04, 6, 12, Math.PI);
      const railL = new THREE.Mesh(railGeo, metal);
      railL.position.set(x - 0.3, 0.3, z);
      railL.rotation.x = Math.PI / 2;
      group.add(railL);
      const railR = new THREE.Mesh(railGeo, metal);
      railR.position.set(x + 0.3, 0.3, z);
      railR.rotation.x = Math.PI / 2;
      group.add(railR);
      // Vertical posts going up
      const postGeo = new THREE.CylinderGeometry(0.04, 0.04, 1.4, 6);
      const postL = new THREE.Mesh(postGeo, metal);
      postL.position.set(x - 0.3, 0.85, z + 0.1);
      group.add(postL);
      const postR = new THREE.Mesh(postGeo, metal);
      postR.position.set(x + 0.3, 0.85, z + 0.1);
      group.add(postR);
      // Top handle
      const topGeo = new THREE.CylinderGeometry(0.04, 0.04, 0.6, 6);
      const top = new THREE.Mesh(topGeo, metal);
      top.position.set(x, 1.55, z + 0.1);
      top.rotation.z = Math.PI / 2;
      group.add(top);
      return group;
    }
    // 2 ladders on the table side, 2 on the swimmer side
    scene.add(createLadder(-12, 5.2));
    scene.add(createLadder(12, 5.2));
    scene.add(createLadder(-12, -5.2));
    scene.add(createLadder(12, -5.2));

    // ── Planters with bushes near deck ──
    function createPlanter(x, z) {
      const group = new THREE.Group();
      const pot = new THREE.Mesh(
        new THREE.CylinderGeometry(0.5, 0.6, 0.7, 12),
        new THREE.MeshStandardMaterial({ color: 0x6a4a30, roughness: 0.9 })
      );
      pot.position.y = 0.35;
      pot.castShadow = true;
      group.add(pot);
      const plant = new THREE.Mesh(
        new THREE.SphereGeometry(0.7, 9, 7),
        new THREE.MeshStandardMaterial({ color: 0x3a8a52, roughness: 0.95 })
      );
      plant.position.y = 1.1;
      plant.castShadow = true;
      group.add(plant);
      // Some smaller leaves around
      for (let i = 0; i < 3; i++) {
        const leaf = new THREE.Mesh(
          new THREE.SphereGeometry(0.35, 7, 5),
          new THREE.MeshStandardMaterial({ color: 0x4d9d5f, roughness: 0.95 })
        );
        leaf.position.set(
          Math.cos(i * 2.1) * 0.5,
          1 + Math.random() * 0.4,
          Math.sin(i * 2.1) * 0.5
        );
        leaf.castShadow = true;
        group.add(leaf);
      }
      group.position.set(x, 0, z);
      return group;
    }
    [
      [-21, 4.5], [21, 4.5], [-21, -4.5], [21, -4.5],
      [-30, 8], [30, 8], [-30, -8], [30, -8],
    ].forEach(([x, z]) => scene.add(createPlanter(x, z)));

    // ── Tall grass tufts (small green spikes around grass area) ──
    for (let i = 0; i < 40; i++) {
      const x = (Math.random() - 0.5) * 100;
      const z = (Math.random() - 0.5) * 70;
      if (Math.abs(x) < 25 && Math.abs(z) < 12) continue;
      // Avoid forest zone (negative Z, where trees are)
      if (z < -15) continue;
      const tuft = new THREE.Mesh(
        new THREE.ConeGeometry(0.15, 0.5, 5),
        new THREE.MeshStandardMaterial({ color: 0x5cc964, roughness: 0.95 })
      );
      tuft.position.set(x, 0.25, z);
      tuft.castShadow = true;
      scene.add(tuft);
    }

    // ── Bushes (low foliage scattered for detail) ──
    for (let i = 0; i < 25; i++) {
      const x = (Math.random() - 0.5) * 90;
      const z = (Math.random() - 0.5) * 60;
      // Skip if too close to pool/deck
      if (Math.abs(x) < 24 && Math.abs(z) < 14) continue;
      const bush = new THREE.Mesh(
        new THREE.SphereGeometry(0.4 + Math.random() * 0.5, 7, 5),
        new THREE.MeshStandardMaterial({ color: 0x3a8a52, roughness: 0.95 })
      );
      bush.position.set(x, 0.3, z);
      bush.castShadow = true;
      scene.add(bush);
    }

    // ── Wooden pathway from building to pool ──
    const pathMat = new THREE.MeshStandardMaterial({ map: woodTex, roughness: 0.9 });
    const path = new THREE.Mesh(new THREE.BoxGeometry(3, 0.08, 14), pathMat);
    path.position.set(-25, 0.04, -10);
    path.receiveShadow = true;
    scene.add(path);

    // ── Hedge / low wall behind table area ──
    for (let i = -18; i <= 18; i += 3) {
      const hedge = new THREE.Mesh(
        new THREE.BoxGeometry(2.5, 1.4, 1.5),
        new THREE.MeshStandardMaterial({ color: 0x2d6e3e, roughness: 0.95 })
      );
      hedge.position.set(i, 0.7, 22);
      hedge.castShadow = true;
      scene.add(hedge);
    }

    // ── Parasols around the deck (matches the venue photos) ──
    function createParasol(x, z, color, rotation) {
      const group = new THREE.Group();
      const pole = new THREE.Mesh(
        new THREE.CylinderGeometry(0.06, 0.06, 3.2, 8),
        new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.85 })
      );
      pole.position.y = 1.6;
      pole.castShadow = true;
      group.add(pole);
      const canopy = new THREE.Mesh(
        new THREE.ConeGeometry(1.7, 0.55, 12, 1, false),
        new THREE.MeshStandardMaterial({ color, roughness: 0.85, side: THREE.DoubleSide })
      );
      canopy.position.y = 3.35;
      canopy.castShadow = true;
      group.add(canopy);
      // Tassel ring
      const ring = new THREE.Mesh(
        new THREE.TorusGeometry(1.7, 0.03, 6, 24),
        new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.85 })
      );
      ring.rotation.x = Math.PI / 2;
      ring.position.y = 3.1;
      group.add(ring);
      group.position.set(x, 0, z);
      group.rotation.y = rotation || 0;
      return group;
    }
    // Parasols seulement sur le bord côté tables + 2 sur le bord opposé (pour baigneurs)
    [
      [-19, 6.5, 0x1a1a1a], [-13, 6.5, 0xd14a3a], [-3, 6.5, 0x1a1a1a],
      [7, 6.5, 0xd14a3a], [13, 6.5, 0x1a1a1a], [19, 6.5, 0xd14a3a],
      // 2 sur le bord arrière pour les baigneurs qui sortent
      [-12, -6.5, 0x1a1a1a], [12, -6.5, 0xd14a3a],
    ].forEach(([x, z, color]) => scene.add(createParasol(x, z, color, Math.random() * 0.3)));

    // ── Orange striped lounger texture (matches real venue photos) ──
    function makeLoungerTexture() {
      const c = document.createElement("canvas");
      c.width = 128; c.height = 256;
      const ctx = c.getContext("2d");
      const stripeW = 32;
      const colors = ["#f2a24a", "#ffffff", "#f2a24a", "#ffffff"];
      for (let i = 0; i < 4; i++) {
        ctx.fillStyle = colors[i];
        ctx.fillRect(0, i * stripeW * 2, 128, stripeW * 2);
      }
      const tex = new THREE.CanvasTexture(c);
      tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
      return tex;
    }
    const loungerTex = makeLoungerTexture();

    // ── Lounge chairs along the pool edge ──
    function createLounger(x, z, rotation) {
      const group = new THREE.Group();
      const frame = new THREE.Mesh(
        new THREE.BoxGeometry(0.75, 0.12, 1.95),
        new THREE.MeshStandardMaterial({ color: 0x7a4a28, roughness: 0.8 })
      );
      frame.position.y = 0.2;
      frame.castShadow = true;
      group.add(frame);
      // Orange striped cushion
      const cushion = new THREE.Mesh(
        new THREE.BoxGeometry(0.7, 0.16, 1.9),
        new THREE.MeshStandardMaterial({ map: loungerTex, roughness: 0.9 })
      );
      cushion.position.y = 0.34;
      cushion.castShadow = true;
      group.add(cushion);
      // Raised backrest
      const back = new THREE.Mesh(
        new THREE.BoxGeometry(0.7, 0.6, 0.16),
        new THREE.MeshStandardMaterial({ map: loungerTex, roughness: 0.9 })
      );
      back.position.set(0, 0.52, -0.88);
      back.rotation.x = -0.42;
      back.castShadow = true;
      group.add(back);
      // Small pillow at head
      const pillow = new THREE.Mesh(
        new THREE.BoxGeometry(0.55, 0.1, 0.35),
        new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.95 })
      );
      pillow.position.set(0, 0.47, -0.7);
      pillow.castShadow = true;
      group.add(pillow);
      group.position.set(x, 0, z);
      group.rotation.y = rotation || 0;
      return group;
    }
    // Chaises longues — côté avant (behind front VIP) + quelques-unes côté bâtiment
    [
      // Front side, behind VIP row (z = 8.5-9)
      [-20, 8.5, 0.05], [-13, 9,  -0.05], [-7, 8.5, 0.08],
      [0,   9,   0.05], [7,  8.5, -0.08], [13, 9,   0.05],
      [20,  8.5,  0.1],
      // Building side terrace (z = -9)
      [-16, -9, Math.PI + 0.05], [-8, -9, Math.PI - 0.05],
      [8,   -9, Math.PI + 0.08], [16, -9, Math.PI],
    ].forEach(([x, z, r]) => scene.add(createLounger(x, z, r)));

    // ── Building — long chalet-style running along the pool (negative Z side) ──
    const building = new THREE.Group();
    const buildingW = 44, buildingD = 7, buildingH = 4.2;
    const woodWallMat = new THREE.MeshStandardMaterial({ color: 0xa07850, roughness: 0.88 });
    const darkWoodMat = new THREE.MeshStandardMaterial({ color: 0x4a3220, roughness: 0.9 });

    // Main body
    const mainWall = new THREE.Mesh(new THREE.BoxGeometry(buildingW, buildingH, buildingD), woodWallMat);
    mainWall.position.y = buildingH / 2;
    mainWall.castShadow = true;
    building.add(mainWall);

    // Gable roof — two sloping panels
    const roofMat2 = new THREE.MeshStandardMaterial({ color: 0x3a2515, roughness: 0.95 });
    const roofHalf = new THREE.Mesh(
      new THREE.BoxGeometry(buildingW + 2, 0.28, buildingD / 2 + 1.8),
      roofMat2
    );
    roofHalf.position.set(0, buildingH + 1.05,  buildingD / 4 + 0.6);
    roofHalf.rotation.x =  0.38;
    roofHalf.castShadow = true;
    building.add(roofHalf);
    const roofHalf2 = roofHalf.clone();
    roofHalf2.position.set(0, buildingH + 1.05, -(buildingD / 4 + 0.6));
    roofHalf2.rotation.x = -0.38;
    building.add(roofHalf2);
    // Ridge beam
    const ridge = new THREE.Mesh(
      new THREE.BoxGeometry(buildingW + 2, 0.35, 0.45),
      roofMat2
    );
    ridge.position.y = buildingH + 2.05;
    building.add(ridge);

    // Front canopy / overhang (toward pool = positive local Z)
    const canopy = new THREE.Mesh(
      new THREE.BoxGeometry(buildingW + 2, 0.22, 4.5),
      darkWoodMat
    );
    canopy.position.set(0, buildingH + 0.1, buildingD / 2 + 2.25);
    canopy.castShadow = true;
    building.add(canopy);
    // Canopy support columns
    for (let cx = -20; cx <= 20; cx += 8) {
      const col = new THREE.Mesh(
        new THREE.CylinderGeometry(0.12, 0.14, buildingH + 0.2, 8),
        darkWoodMat
      );
      col.position.set(cx, (buildingH + 0.2) / 2, buildingD / 2 + 0.12);
      col.castShadow = true;
      building.add(col);
    }
    // End columns
    [-buildingW / 2 + 0.5, buildingW / 2 - 0.5].forEach(cx => {
      const col = new THREE.Mesh(new THREE.CylinderGeometry(0.14, 0.18, buildingH + 0.2, 8), darkWoodMat);
      col.position.set(cx, (buildingH + 0.2) / 2, 0);
      col.castShadow = true;
      building.add(col);
    });

    // Windows
    const winMat = new THREE.MeshStandardMaterial({ color: 0x6dd5ff, roughness: 0.2, metalness: 0.5, emissive: 0x223a4a, emissiveIntensity: 0.25 });
    for (let wx = -18; wx <= 18; wx += 9) {
      const win = new THREE.Mesh(new THREE.BoxGeometry(2.0, 1.5, 0.1), winMat);
      win.position.set(wx, buildingH / 2 + 0.3, buildingD / 2 + 0.06);
      building.add(win);
    }
    // Central door
    const doorMat2 = new THREE.MeshStandardMaterial({ color: 0x2a1a10, roughness: 0.8 });
    const door2 = new THREE.Mesh(new THREE.BoxGeometry(2.2, 3.2, 0.1), doorMat2);
    door2.position.set(0, 1.6, buildingD / 2 + 0.06);
    building.add(door2);
    // Terrace stone floor in front of building
    const terrace = new THREE.Mesh(
      new THREE.BoxGeometry(buildingW, 0.12, 5),
      new THREE.MeshStandardMaterial({ color: 0xc8b89a, roughness: 0.92 })
    );
    terrace.position.set(0, 0.06, buildingD / 2 + 2.5);
    terrace.receiveShadow = true;
    building.add(terrace);

    // Position building: along the negative Z side of the pool
    building.position.set(0, 0, -18);
    scene.add(building);

    // ── Concert stage — far behind VIP 8 (right short end, positive X side) ──
    const stage = new THREE.Group();
    const stageMat   = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.75 });
    const metalMat   = new THREE.MeshStandardMaterial({ color: 0x999999, metalness: 0.85, roughness: 0.25 });
    const darkMat    = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.85 });

    // Platform
    const platform = new THREE.Mesh(new THREE.BoxGeometry(16, 1.4, 11), stageMat);
    platform.position.y = 0.7;
    platform.castShadow = true;
    stage.add(platform);
    // Steps
    const steps = new THREE.Mesh(new THREE.BoxGeometry(4, 0.7, 2.5),
      new THREE.MeshStandardMaterial({ color: 0x2a2a2a, roughness: 0.8 }));
    steps.position.set(0, 0.35, 6.5);
    stage.add(steps);

    // Backdrop wall
    const backwall = new THREE.Mesh(new THREE.BoxGeometry(16, 9, 0.4), darkMat);
    backwall.position.set(0, 5.9, -5.8);
    backwall.castShadow = true;
    stage.add(backwall);
    // LED screen (glowing)
    const ledScreen = new THREE.Mesh(new THREE.BoxGeometry(13, 6.5, 0.1),
      new THREE.MeshStandardMaterial({ color: 0x100828, emissive: 0x5030e0, emissiveIntensity: 1.2, roughness: 0.2 }));
    ledScreen.position.set(0, 6.2, -5.6);
    stage.add(ledScreen);

    // Truss bar
    const truss = new THREE.Mesh(new THREE.BoxGeometry(18, 0.5, 0.5), metalMat);
    truss.position.set(0, 11, -4);
    stage.add(truss);
    // Vertical truss poles
    [-8, 8].forEach(x => {
      const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.18, 11, 8), metalMat);
      pole.position.set(x, 5.5, -4);
      pole.castShadow = true;
      stage.add(pole);
    });
    // Spotlights on truss
    [-6, -3, 0, 3, 6].forEach(x => {
      const spot = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.28, 0.65, 8),
        new THREE.MeshStandardMaterial({ color: 0xf0c030, emissive: 0xf0c030, emissiveIntensity: 0.9 }));
      spot.rotation.z = Math.PI;
      spot.position.set(x, 10.7, -4);
      stage.add(spot);
    });
    // Speaker stacks (left & right)
    [-9, 9].forEach(x => {
      const spk = new THREE.Mesh(new THREE.BoxGeometry(2, 5.5, 2), darkMat);
      spk.position.set(x, 1.4 + 2.75, -3.5);
      spk.castShadow = true;
      stage.add(spk);
      const spkTop = new THREE.Mesh(new THREE.BoxGeometry(1.8, 3, 1.8), darkMat);
      spkTop.position.set(x, 1.4 + 5.5 + 1.5, -3.5);
      spkTop.castShadow = true;
      stage.add(spkTop);
    });

    // Stage ambient glow light
    const stageLight = new THREE.PointLight(0x6040ff, 2.5, 30);
    stageLight.position.set(0, 8, -2);
    stage.add(stageLight);

    // Position: far right side, behind VIP 8 (x=23), facing the pool (rotation -PI/2)
    stage.position.set(56, 0, 3);
    stage.rotation.y = -Math.PI / 2;
    scene.add(stage);

    // expose for animation loop
    const animEnv = { waterNormal, trees, poolMat };

    // ── Tables ──
    const tableGroup = new THREE.Group();
    const tableMeshes = []; // for raycasting
    ALL_TABLES.forEach((t) => {
      const type = TABLE_TYPES[t.type];
      const isVIP = t.type === "vip";
      const isFree = t.type === "free";

      const group = new THREE.Group();
      group.position.set(t.x, 0, t.z);

      // Base platform / mat (used for raycast)
      const matW = isVIP ? 5 : 3.5;
      const matD = isVIP ? 2.4 : 3;
      const mat = new THREE.Mesh(
        new THREE.BoxGeometry(matW, 0.1, matD),
        new THREE.MeshStandardMaterial({ color: type.color, roughness: 0.7, emissive: 0x000000 })
      );
      mat.position.y = 0.18;
      mat.receiveShadow = true;
      mat.userData = { tableId: t.id };
      group.add(mat);
      tableMeshes.push(mat);

      if (isFree) {
        // Casual lounge: 3 bean bags (sphere-ish) + 1 low round table
        const beanMat = new THREE.MeshStandardMaterial({ color: 0xd8cfc0, roughness: 0.95 });
        const stripedBeanMat = new THREE.MeshStandardMaterial({ color: 0x8bb6d8, roughness: 0.9 });
        [
          { x: -1.0, z: -0.7, m: beanMat },
          { x:  1.0, z: -0.7, m: stripedBeanMat },
          { x:  0,   z:  0.9, m: beanMat },
        ].forEach(p => {
          const bean = new THREE.Mesh(
            new THREE.SphereGeometry(0.55, 9, 7),
            p.m
          );
          bean.scale.y = 0.55;
          bean.position.set(p.x, 0.42, p.z);
          bean.castShadow = true;
          bean.userData = { tableId: t.id };
          group.add(bean);
          tableMeshes.push(bean);
        });
        // Small low round table
        const lowT = new THREE.Mesh(
          new THREE.CylinderGeometry(0.5, 0.5, 0.22, 14),
          new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.8 })
        );
        lowT.position.y = 0.33;
        lowT.castShadow = true;
        lowT.userData = { tableId: t.id };
        group.add(lowT);
        tableMeshes.push(lowT);
      } else {
        // VIP: rectangular table + cushions
        const tableTop = new THREE.Mesh(
          new THREE.BoxGeometry(matW * 0.8, 0.15, matD * 0.4),
          new THREE.MeshStandardMaterial({ color: 0x2a2a2a, roughness: 0.85 })
        );
        tableTop.position.y = 0.45;
        tableTop.castShadow = true;
        tableTop.userData = { tableId: t.id };
        group.add(tableTop);
        tableMeshes.push(tableTop);

        // Cushions on each side
        const cushionMat = new THREE.MeshStandardMaterial({ color: 0xe8e0d0, roughness: 0.95 });
        const cushionGeo = new THREE.BoxGeometry(matW * 0.45, 0.5, matD * 0.7);
        const back = new THREE.Mesh(cushionGeo, cushionMat);
        back.position.set(0, 0.55, -matD * 0.4);
        back.castShadow = true;
        back.userData = { tableId: t.id };
        group.add(back);
        tableMeshes.push(back);
        const front = new THREE.Mesh(cushionGeo, cushionMat);
        front.position.set(0, 0.55, matD * 0.4);
        front.castShadow = true;
        front.userData = { tableId: t.id };
        group.add(front);
        tableMeshes.push(front);
        if (isVIP) {
          // Extra side cushions for VIPs
          const sideGeo = new THREE.BoxGeometry(matW * 0.15, 0.5, matD * 0.6);
          const left = new THREE.Mesh(sideGeo, cushionMat);
          left.position.set(-matW * 0.4, 0.55, 0);
          left.castShadow = true;
          left.userData = { tableId: t.id };
          group.add(left);
          tableMeshes.push(left);
          const right = new THREE.Mesh(sideGeo, cushionMat);
          right.position.set(matW * 0.4, 0.55, 0);
          right.castShadow = true;
          right.userData = { tableId: t.id };
          group.add(right);
          tableMeshes.push(right);
        }
      }

      // Floating sticker above each table with its label (sprite with canvas texture)
      const stickerCanvas = document.createElement("canvas");
      stickerCanvas.width = 256;
      stickerCanvas.height = 96;
      const sctx = stickerCanvas.getContext("2d");
      sctx.fillStyle = type.accent;
      sctx.strokeStyle = "#1a0d3a";
      sctx.lineWidth = 6;
      const r = 24;
      sctx.beginPath();
      sctx.moveTo(r, 0);
      sctx.lineTo(256 - r, 0);
      sctx.quadraticCurveTo(256, 0, 256, r);
      sctx.lineTo(256, 96 - r);
      sctx.quadraticCurveTo(256, 96, 256 - r, 96);
      sctx.lineTo(r, 96);
      sctx.quadraticCurveTo(0, 96, 0, 96 - r);
      sctx.lineTo(0, r);
      sctx.quadraticCurveTo(0, 0, r, 0);
      sctx.closePath();
      sctx.fill();
      sctx.stroke();
      sctx.fillStyle = "#1a0d3a";
      sctx.font = "bold 36px system-ui, sans-serif";
      sctx.textAlign = "center";
      sctx.textBaseline = "middle";
      sctx.fillText(t.label.toUpperCase(), 128, 50);
      const stickerTex = new THREE.CanvasTexture(stickerCanvas);
      const stickerMat = new THREE.SpriteMaterial({ map: stickerTex, transparent: true });
      const sticker = new THREE.Sprite(stickerMat);
      sticker.position.y = 3;
      sticker.scale.set(3, 1.1, 1);
      sticker.userData = { tableId: t.id, isSticker: true };
      group.add(sticker);

      tableGroup.add(group);
      // Store reference to the group for camera animation
      t._object3d = group;
      t._sticker = sticker;
      t._mat = mat;
    });
    scene.add(tableGroup);

    // ── Simple Orbit Controls (inline, sans dépendance externe) ──
    function makeOrbitControls(cam, dom) {
      const c = {
        target: new THREE.Vector3(0, 0, 8),
        distance: 60,
        targetDistance: 60,
        theta: 0,
        phi: 0.85,
        targetTheta: 0,
        targetPhi: 0.85,
        minDistance: 12,
        maxDistance: 95,
        minPhi: 0.18,
        maxPhi: Math.PI * 0.48,
        damping: 0.08,
        isDragging: false,
        startX: 0,
        startY: 0,
      };
      const onDown = (e) => {
        if (e.button === 2) return; // ignore right click
        c.isDragging = true;
        c.startX = e.clientX;
        c.startY = e.clientY;
        dom.setPointerCapture && dom.setPointerCapture(e.pointerId);
      };
      const onMove = (e) => {
        if (!c.isDragging) return;
        const dx = e.clientX - c.startX;
        const dy = e.clientY - c.startY;
        c.targetTheta -= dx * 0.005;
        c.targetPhi -= dy * 0.005;
        c.targetPhi = Math.max(c.minPhi, Math.min(c.maxPhi, c.targetPhi));
        c.startX = e.clientX;
        c.startY = e.clientY;
      };
      const onUp = (e) => {
        c.isDragging = false;
        dom.releasePointerCapture && e.pointerId != null && dom.releasePointerCapture(e.pointerId);
      };
      const onWheel = (e) => {
        e.preventDefault();
        const factor = 1 + e.deltaY * 0.0012;
        c.targetDistance *= factor;
        c.targetDistance = Math.max(c.minDistance, Math.min(c.maxDistance, c.targetDistance));
      };
      dom.addEventListener("pointerdown", onDown);
      window.addEventListener("pointermove", onMove);
      window.addEventListener("pointerup", onUp);
      dom.addEventListener("wheel", onWheel, { passive: false });
      dom.addEventListener("contextmenu", (e) => e.preventDefault());
      c.update = () => {
        c.theta += (c.targetTheta - c.theta) * c.damping;
        c.phi += (c.targetPhi - c.phi) * c.damping;
        c.distance += (c.targetDistance - c.distance) * c.damping;
        const sp = Math.sin(c.phi);
        cam.position.set(
          c.target.x + c.distance * sp * Math.sin(c.theta),
          c.target.y + c.distance * Math.cos(c.phi),
          c.target.z + c.distance * sp * Math.cos(c.theta)
        );
        cam.lookAt(c.target);
      };
      c.dispose = () => {
        dom.removeEventListener("pointerdown", onDown);
        window.removeEventListener("pointermove", onMove);
        window.removeEventListener("pointerup", onUp);
        dom.removeEventListener("wheel", onWheel);
      };
      return c;
    }
    const controls = makeOrbitControls(camera, canvas);

    // ── Raycaster for picking ──
    const raycaster = new THREE.Raycaster();
    const mouse = new THREE.Vector2();
    let lastHoverId = null;
    // Track click vs drag (OrbitControls also captures these events)
    let downX = 0, downY = 0, downTime = 0;

    function onPointerDown(e) {
      downX = e.clientX; downY = e.clientY; downTime = Date.now();
    }
    function onPointerUp(e) {
      const dx = e.clientX - downX;
      const dy = e.clientY - downY;
      const dist = Math.sqrt(dx * dx + dy * dy);
      // If pointer barely moved AND was a short tap → treat as click for selection
      if (dist < 6 && Date.now() - downTime < 400) {
        const rect = canvas.getBoundingClientRect();
        mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
        mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
        raycaster.setFromCamera(mouse, camera);
        const hits = raycaster.intersectObjects(tableMeshes, false);
        if (hits.length && state.onSelectRef.current) {
          state.onSelectRef.current(hits[0].object.userData.tableId);
        }
      }
    }
    function onMouseMove(e) {
      const rect = canvas.getBoundingClientRect();
      mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
      mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
      raycaster.setFromCamera(mouse, camera);
      const hits = raycaster.intersectObjects(tableMeshes, false);
      const id = hits.length ? hits[0].object.userData.tableId : null;
      if (id !== lastHoverId) {
        lastHoverId = id;
        canvas.style.cursor = id ? "pointer" : "grab";
        if (state.onHoverRef.current) state.onHoverRef.current(id);
      }
    }
    canvas.addEventListener("mousemove", onMouseMove);
    canvas.addEventListener("pointerdown", onPointerDown);
    canvas.addEventListener("pointerup", onPointerUp);

    // ── Resize handling ──
    function resize() {
      const w = canvas.clientWidth, h = canvas.clientHeight;
      renderer.setSize(w, h, false);
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
    }
    const ro = new ResizeObserver(resize);
    ro.observe(canvas);

    // ── Animation loop ──
    let raf;
    // When a table is selected, animate the controls.target to focus on that table
    // (camera keeps user's orbit angle but the look-at smoothly transitions)
    const targetLook = new THREE.Vector3(0, 0, 8);
    const tmpTime = { t: 0 };
    function tick() {
      tmpTime.t += 0.016;
      // Smooth target lerp toward where we want to look
      if (controls) {
        controls.target.lerp(targetLook, 0.08);
        controls.update();
      } else {
        camera.lookAt(targetLook);
      }
      // Pool: animate water normal map UV (ripple effect)
      animEnv.waterNormal.offset.x = tmpTime.t * 0.04;
      animEnv.waterNormal.offset.y = tmpTime.t * 0.025;
      animEnv.poolMat.emissiveIntensity = Math.sin(tmpTime.t * 1.3) * 0.08 + 0.30;
      // Trees: subtle wind sway
      animEnv.trees.forEach((tr) => {
        tr.rotation.z = Math.sin(tmpTime.t * 0.8 + tr._sway) * 0.015;
      });
      // Sticker bob
      ALL_TABLES.forEach((t, i) => {
        if (t._sticker) t._sticker.position.y = 3 + Math.sin(tmpTime.t * 2 + i) * 0.08;
      });
      renderer.render(scene, camera);
      raf = requestAnimationFrame(tick);
    }
    tick();

    const state = {
      scene, camera, renderer, tableMeshes, controls, targetLook,
      onSelectRef: { current: onSelect }, onHoverRef: { current: onHover },
    };
    stateRef.current = state;

    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      canvas.removeEventListener("mousemove", onMouseMove);
      canvas.removeEventListener("pointerdown", onPointerDown);
      canvas.removeEventListener("pointerup", onPointerUp);
      if (controls) controls.dispose();
      renderer.dispose();
      scene.traverse((obj) => {
        if (obj.geometry) obj.geometry.dispose();
        if (obj.material) {
          if (Array.isArray(obj.material)) obj.material.forEach((m) => m.dispose());
          else obj.material.dispose();
        }
      });
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Sync select/hover handlers refs without re-init
  useEffect(() => { if (stateRef.current) stateRef.current.onSelectRef.current = onSelect; }, [onSelect]);
  useEffect(() => { if (stateRef.current) stateRef.current.onHoverRef.current = onHover; }, [onHover]);

  // React to selectedId / reservedIds / hoveredId: update materials + camera target
  useEffect(() => {
    if (!stateRef.current || !window.THREE) return;
    const THREE = window.THREE;
    ALL_TABLES.forEach((t) => {
      if (!t._mat) return;
      const type = TABLE_TYPES[t.type];
      const isSelected = selectedId === t.id;
      const isHovered = hoveredId === t.id;
      const isReserved = reservedIds.includes(t.id);
      let color = type.color;
      let emissive = 0x000000;
      let emissiveIntensity = 0;
      if (isReserved) {
        color = 0x444444;
      } else if (isSelected) {
        emissive = type.color;
        emissiveIntensity = 0.6;
      } else if (isHovered) {
        emissive = type.color;
        emissiveIntensity = 0.25;
      }
      t._mat.material.color.setHex(color);
      t._mat.material.emissive.setHex(emissive);
      t._mat.material.emissiveIntensity = emissiveIntensity;
    });
    // Camera focus : avec OrbitControls, on ne déplace que le target (look-at)
    // Comme ça l'utilisateur garde son angle/distance d'orbit et peut tourner autour.
    const target = stateRef.current.targetLook;
    if (selectedId) {
      const t = ALL_TABLES.find((x) => x.id === selectedId);
      if (t) target.set(t.x, 1, t.z);
    } else {
      target.set(0, 0, 8);
    }
  }, [selectedId, hoveredId, reservedIds]);

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

// ── Booking UI overlay + page ─────────────────────────────────────────────
const LS_KEY = "ss_reserved_vip";

function PageBooking({ setPage }) {
  const [selectedId, setSelectedId] = useState(null);
  const [hoveredId, setHoveredId] = useState(null);
  const [confirmedTable, setConfirmedTable] = useState(null);
  const [entryQty, setEntryQty] = useState(1);

  // Fusion : tables permanentes (admin) + tables réservées en session courante (localStorage)
  const [reservedIds, setReservedIds] = useState(() => {
    try {
      const stored = JSON.parse(localStorage.getItem(LS_KEY) || "[]");
      return [...new Set([...PERMANENTLY_RESERVED, ...stored])];
    } catch { return [...PERMANENTLY_RESERVED]; }
  });

  // Charge les réservations depuis Google Sheets (synchro entre tous les visiteurs)
  useEffect(() => {
    if (!SHEETS_URL) return;
    fetch(SHEETS_URL)
      .then(r => r.json())
      .then(d => {
        if (Array.isArray(d.reserved) && d.reserved.length > 0) {
          setReservedIds(prev => [...new Set([...prev, ...d.reserved])]);
        }
      })
      .catch(() => {});
  }, []);

  const selected = selectedId ? ALL_TABLES.find((t) => t.id === selectedId) : null;
  const selectedType = selected ? TABLE_TYPES[selected.type] : null;
  const isReserved = selected && reservedIds.includes(selected.id);

  const total = selected && selectedType ? selectedType.forfait : 0;

  const reserve = () => {
    if (!selected || isReserved) return;
    const next = [...reservedIds, selected.id];
    setReservedIds(next);
    // Persiste en localStorage (seulement les VIP réservés par l'utilisateur, pas les permanents)
    try {
      const userReserved = next.filter(id => !PERMANENTLY_RESERVED.includes(id));
      localStorage.setItem(LS_KEY, JSON.stringify(userReserved));
    } catch {}
    setConfirmedTable({ id: selected.id, label: selected.label, type: selected.type, total });
    setSelectedId(null);
  };

  const reserveEntry = () => {
    setConfirmedTable({ id: "entry", label: `${entryQty} entrée${entryQty > 1 ? "s" : ""} simple`, type: "entry", total: 20 * entryQty });
  };

  // VIP : ouvre le formulaire de noms avant Weezevent
  const [vipForm, setVipForm] = useState(null); // { table } ou null
  const openVipForm = () => {
    if (!selected || isReserved) return;
    setVipForm({ table: selected });
  };
  const confirmVipForm = async (guests, contact) => {
    const t = vipForm.table;
    // Marque la table comme réservée localement
    const next = [...reservedIds, t.id];
    setReservedIds(next);
    try {
      const userReserved = next.filter(id => !PERMANENTLY_RESERVED.includes(id));
      localStorage.setItem(LS_KEY, JSON.stringify(userReserved));
      const all = JSON.parse(localStorage.getItem("ss_vip_guests") || "{}");
      all[t.id] = { table: t.label, guests, contact, date: new Date().toISOString() };
      localStorage.setItem("ss_vip_guests", JSON.stringify(all));
    } catch {}

    // Construction du récap email
    let guestList = "";
    guests.forEach((g, i) => { guestList += `${i + 1}. ${g.firstName} ${g.lastName}\n`; });

    // 1) Envoi email à l'organisation via FormSubmit
    try {
      await fetch("https://formsubmit.co/ajax/summersky.off@gmail.com", {
        method: "POST",
        headers: { "Content-Type": "application/json", "Accept": "application/json" },
        body: JSON.stringify({
          _subject: `[Summer Sky VIP] ${t.label} — ${guests.length} pers.`,
          _template: "table",
          _captcha: "false",
          "Table VIP": t.label,
          "Nombre d'invités": guests.length,
          "Contact réservant": contact?.name || "(non renseigné)",
          "Téléphone": contact?.phone || "(non renseigné)",
          "Email": contact?.email || "(non renseigné)",
          "Liste des invités": guestList,
          "Note": "Chaque personne ci-dessus peut se présenter seule à l'entrée le jour J et accéder à la table VIP.",
          "Date de réservation": new Date().toLocaleString("fr-FR"),
        }),
      });
    } catch (err) {
      console.error("Erreur envoi mail", err);
    }

    // 2) Synchro Google Sheets (table devient indisponible pour tous les visiteurs)
    if (SHEETS_URL) {
      try {
        await fetch(SHEETS_URL, {
          method: "POST",
          mode: "no-cors",
          headers: { "Content-Type": "text/plain" },
          body: JSON.stringify({
            tableId: t.id,
            table: t.label,
            contactName: contact?.name || "",
            contactEmail: contact?.email || "",
            contactPhone: contact?.phone || "",
            guests: guests.map(g => `${g.firstName} ${g.lastName}`).join(", "),
          }),
        });
      } catch (err) {
        console.error("Erreur sync Sheets", err);
      }
    }

    setVipForm(null);
    setSelectedId(null);
    // Redirige vers Weezevent
    openWeezevent("viptable");
  };

  // Stats
  const remainingByType = {
    vip:  10 - reservedIds.filter((id) => id.startsWith("vip-")).length,
    free: 13 - reservedIds.filter((id) => id.startsWith("free-")).length,
  };

  return (
    <div className="booking-root">
      <div className="booking-scene-wrap">
        <BookingScene
          selectedId={selectedId}
          reservedIds={reservedIds}
          hoveredId={hoveredId}
          onSelect={(id) => setSelectedId(id)}
          onHover={(id) => setHoveredId(id)}
        />

        {/* Top bar */}
        <div className="booking-topbar">
          <button className="booking-back" onClick={() => setPage("tickets")} aria-label="Retour">
            <span aria-hidden="true">←</span> Retour
          </button>
          <div className="booking-title">
            <div className="eyebrow" style={{ marginBottom: 4 }}>Réservation 3D</div>
            <h2>Choisissez votre table</h2>
          </div>
          <div className="booking-counters">
            <div><span className="dot" style={{ background: "#ffc23d" }} /> VIP <strong>{remainingByType.vip}/10</strong></div>
            <div><span className="dot" style={{ background: "#6dd5ff" }} /> Lounge <strong>18 tables · libre</strong></div>
          </div>
        </div>

        {/* Side panel */}
        <aside className={`booking-side ${selectedId ? "open" : ""}`}>
          {selected && selectedType && (
            <>
              <div className="booking-side-header" style={{ background: selectedType.accent }}>
                <div className="eyebrow" style={{ color: "var(--ink)" }}>{selectedType.label}</div>
                <h3>{selected.label}</h3>
              </div>
              {selected.type === "vip" ? (
                <img src="assets/plongeoir-piscine.jpeg" alt="Table VIP — Le Plongeoir" className="booking-side-media" />
              ) : (
                <img src="assets/IMG_5357.jpeg" alt="Table lounge" className="booking-side-media" />
              )}
              <div className="booking-side-body">
                <p className="booking-side-desc">{selectedType.desc}</p>
                <div className="booking-side-rows">
                  {selectedType.bonus && (
                    <div style={{ background: "var(--sun-3)", borderRadius: 8, padding: "8px 12px", fontWeight: 700, fontSize: 15 }}>
                      {selectedType.bonus}
                    </div>
                  )}
                  {selectedType.forfait > 0 ? (
                    <div className="total"><span>Forfait table</span><strong>{selectedType.forfait} €</strong></div>
                  ) : (
                    <div className="total"><span>Prix</span><strong>Inclus dans le billet</strong></div>
                  )}
                </div>
                {isReserved ? (
                  <button className="btn btn-ghost booking-cta" disabled>
                    {selected.type === "free" ? "Emplacement noté ✓" : "Table déjà réservée"}
                  </button>
                ) : selected.type === "free" ? (
                  <div>
                    <div style={{ background: "var(--sky-2,#e0f5ff)", border: "2px solid var(--ink)", borderRadius: 10, padding: "10px 14px", fontSize: 14, fontWeight: 600, lineHeight: 1.5, marginBottom: 12 }}>
                      🏖️ Ces tables ne se réservent pas — soyez les <strong>premiers sur place</strong> pour les obtenir !
                    </div>
                    <button className="btn btn-ghost booking-cta" onClick={reserve}>
                      J'ai compris <span aria-hidden="true">✓</span>
                    </button>
                  </div>
                ) : (
                  <button className="btn btn-pink booking-cta" onClick={openVipForm}>
                    Réserver — {selectedType.forfait} € <span aria-hidden="true">→</span>
                  </button>
                )}
                <button className="booking-side-close" onClick={() => setSelectedId(null)}>Voir tout le plan ↺</button>
              </div>
            </>
          )}
        </aside>

        {/* Empty state / Entry-only panel when nothing selected */}
        {!selectedId && (
          <div className="booking-entry-card">
            <div className="eyebrow">Pas de table ?</div>
            <h3>Entrée simple de la Pool Party</h3>
            <p>Accès complet à l'événement, main stage et piscine. Sans table réservée.</p>
            <div className="booking-entry-row">
              <button className="qty-btn" onClick={() => setEntryQty(Math.max(1, entryQty - 1))}>−</button>
              <div className="qty-num">{entryQty}</div>
              <button className="qty-btn" onClick={() => setEntryQty(entryQty + 1)}>+</button>
              <div className="qty-price">{20 * entryQty} €</div>
            </div>
            <button className="btn btn-ghost booking-cta" onClick={reserveEntry}>
              Prendre {entryQty} entrée{entryQty > 1 ? "s" : ""} <span aria-hidden="true">→</span>
            </button>
          </div>
        )}

        {/* Legend */}
        <div className="booking-legend">
          <div><span className="sw" style={{ background: "#ffc23d" }} /> VIP · 600€ · 🍾 bouteille incluse</div>
          <div><span className="sw" style={{ background: "#6dd5ff" }} /> Lounge · 13 tables · Libre · premiers arrivés</div>
          <div><span className="sw" style={{ background: "#444" }} /> Réservée</div>
        </div>
      </div>

      {/* Formulaire VIP — invités */}
      {vipForm && (
        <VipGuestForm
          table={vipForm.table}
          onClose={() => setVipForm(null)}
          onConfirm={confirmVipForm}
        />
      )}

      {/* Confirmation modal */}
      {confirmedTable && (
        <div className="booking-modal-backdrop" onClick={() => setConfirmedTable(null)}>
          <div className="booking-modal" onClick={(e) => e.stopPropagation()}>

            {confirmedTable.type === "free" ? (
              /* ── Lounge : premiers arrivés, premiers servis ── */
              <>
                <div className="booking-modal-check" style={{ background: "var(--sky-1,#6dd5ff)", fontSize: 32 }}>🏖️</div>
                <h3>Tables lounge — accès libre</h3>
                <p>
                  Les tables lounge ne sont <strong>pas réservables</strong> — elles sont attribuées sur place aux <strong>premiers arrivés</strong>.
                </p>
                <p style={{ fontSize: 13, opacity: 0.75, lineHeight: 1.6 }}>
                  Conseil : arrivez tôt pour choisir votre coin ! Les tables sont disponibles dès l'ouverture des portes (11h00).
                </p>
                <div style={{ display: "flex", gap: 12, marginTop: 16, justifyContent: "center" }}>
                  <button className="btn btn-ghost" onClick={() => setConfirmedTable(null)}>Continuer</button>
                  <button className="btn btn-pink" onClick={() => { setConfirmedTable(null); openWeezevent("regular"); }}>
                    Acheter mon billet <span aria-hidden="true">→</span>
                  </button>
                </div>
              </>
            ) : (
              /* ── VIP ou entrée : paiement Weezevent ── */
              <>
                <div className="booking-modal-check">✓</div>
                <h3>{confirmedTable.type === "vip" ? "Table VIP retenue !" : "Commande enregistrée"}</h3>
                <p>
                  <strong>{confirmedTable.label}</strong>
                  {confirmedTable.total > 0 ? ` — ${confirmedTable.total} €` : ""}
                  {confirmedTable.type === "vip" && (
                    <span style={{ display: "block", marginTop: 6, fontSize: 14, fontWeight: 700, color: "#c07800" }}>
                      🍾 Bouteille incluse dans le forfait 600€
                    </span>
                  )}
                </p>
                <p style={{ fontSize: 13, opacity: 0.75, lineHeight: 1.6 }}>
                  Finalisez votre paiement sécurisé sur <strong>Weezevent</strong>. Votre table sera confirmée par email dès réception du règlement.
                </p>
                <div style={{ display: "flex", flexDirection: "column", gap: 10, marginTop: 16, alignItems: "center" }}>
                  <button className="btn btn-pink" style={{ width: "100%", maxWidth: 320 }} onClick={() => { setConfirmedTable(null); openWeezevent(confirmedTable.type === "vip" ? "viptable" : "regular"); }}>
                    Payer sur Weezevent <span aria-hidden="true">→</span>
                  </button>
                  <button className="btn btn-ghost" onClick={() => setConfirmedTable(null)}>
                    Continuer à réserver
                  </button>
                </div>
              </>
            )}

          </div>
        </div>
      )}
    </div>
  );
}

// ── Formulaire d'invités pour Table VIP ────────────────────────────────────
function VipGuestForm({ table, onClose, onConfirm }) {
  const [guests, setGuests] = useState([{ firstName: "", lastName: "" }]);
  const [contact, setContact] = useState({ name: "", phone: "", email: "" });
  const [sending, setSending] = useState(false);

  const addGuest = () => {
    if (guests.length >= 15) return;
    setGuests([...guests, { firstName: "", lastName: "" }]);
  };
  const removeGuest = (idx) => {
    if (guests.length <= 1) return;
    setGuests(guests.filter((_, i) => i !== idx));
  };
  const updateGuest = (idx, field, value) => {
    setGuests(guests.map((g, i) => i === idx ? { ...g, [field]: value } : g));
  };

  const validGuests = guests.filter(g => g.firstName.trim() && g.lastName.trim());
  const contactOk = contact.name.trim() && contact.email.trim() && contact.phone.trim();
  const canSubmit = validGuests.length >= 1 && contactOk && !sending;

  const handleSubmit = async () => {
    if (!canSubmit) return;
    setSending(true);
    await onConfirm(validGuests, contact);
    // pas besoin de reset sending — le modal va se fermer
  };

  return (
    <div
      className="booking-modal-backdrop"
      onClick={onClose}
      style={{ overflowY: "auto", WebkitOverflowScrolling: "touch", alignItems: "flex-start", padding: "20px" }}
    >
      <div
        className="booking-modal"
        onClick={(e) => e.stopPropagation()}
        style={{ maxWidth: 600, maxHeight: "none", padding: "28px 24px", textAlign: "left", marginTop: "20px", marginBottom: "20px" }}
      >
        {/* Header */}
        <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 16 }}>
          <div style={{ width: 48, height: 48, borderRadius: 999, background: "var(--sun-3)", border: "3px solid var(--ink)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 24, flexShrink: 0 }}>⭐</div>
          <div style={{ flex: 1 }}>
            <div style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--pink-1)", fontWeight: 700 }}>Table VIP · {table.label}</div>
            <div style={{ fontFamily: "var(--font-display)", fontWeight: 900, fontSize: 22, letterSpacing: "-0.02em", marginTop: 2, color: "var(--ink)" }}>Qui vient avec vous ?</div>
          </div>
        </div>

        {/* Explication importante */}
        <div style={{
          background: "var(--cream-2, #fff7e0)",
          border: "2px solid var(--ink)",
          borderRadius: "var(--r-sm)",
          padding: "14px 16px", marginBottom: 20,
          fontSize: 13, lineHeight: 1.6, color: "var(--ink)",
        }}>
          <div style={{ fontWeight: 800, marginBottom: 6, display: "flex", alignItems: "center", gap: 6 }}>
            <span>📝</span> Pourquoi remplir cette liste ?
          </div>
          Le jour J, si <strong>une personne de votre table arrive avant les autres</strong>, elle pourra accéder à la zone VIP en se présentant à l'entrée avec son <strong>nom et prénom</strong>.
          Vous pouvez inscrire jusqu'à <strong>15 personnes</strong> (modifiable jusqu'à 48h avant l'événement).
        </div>

        {/* Coordonnées du réservant */}
        <div style={{ marginBottom: 18 }}>
          <div style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--pink-1)", fontWeight: 700, marginBottom: 10 }}>
            ✦ Vos coordonnées (réservant)
          </div>
          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            <input
              type="text"
              placeholder="Nom complet du réservant *"
              value={contact.name}
              onChange={(e) => setContact({ ...contact, name: e.target.value })}
              style={{ padding: "10px 12px", fontSize: 14, border: "2px solid var(--ink)", borderRadius: 8, fontFamily: "inherit", background: "#fff", color: "var(--ink)", outline: "none" }}
            />
            <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
              <input
                type="email"
                placeholder="Email *"
                value={contact.email}
                onChange={(e) => setContact({ ...contact, email: e.target.value })}
                style={{ flex: "1 1 180px", padding: "10px 12px", fontSize: 14, border: "2px solid var(--ink)", borderRadius: 8, fontFamily: "inherit", background: "#fff", color: "var(--ink)", outline: "none", minWidth: 0 }}
              />
              <input
                type="tel"
                placeholder="Téléphone *"
                value={contact.phone}
                onChange={(e) => setContact({ ...contact, phone: e.target.value })}
                style={{ flex: "1 1 140px", padding: "10px 12px", fontSize: 14, border: "2px solid var(--ink)", borderRadius: 8, fontFamily: "inherit", background: "#fff", color: "var(--ink)", outline: "none", minWidth: 0 }}
              />
            </div>
          </div>
        </div>

        <div style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.18em", textTransform: "uppercase", color: "var(--pink-1)", fontWeight: 700, marginBottom: 10 }}>
          ✦ Liste des invités
        </div>

        {/* Liste des invités */}
        <div style={{ display: "flex", flexDirection: "column", gap: 10, marginBottom: 16 }}>
          {guests.map((g, i) => (
            <div key={i} style={{ display: "flex", gap: 8, alignItems: "center" }}>
              <div style={{
                width: 28, height: 28, borderRadius: 999,
                background: i === 0 ? "var(--ink)" : "rgba(0,0,0,0.08)",
                color: i === 0 ? "var(--sun-3)" : "var(--ink)",
                display: "flex", alignItems: "center", justifyContent: "center",
                fontSize: 11, fontWeight: 800, fontFamily: "var(--font-mono)",
                flexShrink: 0,
              }}>{i + 1}</div>
              <input
                type="text"
                placeholder="Prénom"
                value={g.firstName}
                onChange={(e) => updateGuest(i, "firstName", e.target.value)}
                style={{
                  flex: 1, padding: "10px 12px", fontSize: 14,
                  border: "2px solid var(--ink)", borderRadius: 8,
                  fontFamily: "inherit", background: "#fff", color: "var(--ink)",
                  outline: "none", minWidth: 0,
                }}
              />
              <input
                type="text"
                placeholder="Nom"
                value={g.lastName}
                onChange={(e) => updateGuest(i, "lastName", e.target.value)}
                style={{
                  flex: 1, padding: "10px 12px", fontSize: 14,
                  border: "2px solid var(--ink)", borderRadius: 8,
                  fontFamily: "inherit", background: "#fff", color: "var(--ink)",
                  outline: "none", minWidth: 0,
                }}
              />
              {guests.length > 1 && (
                <button
                  onClick={() => removeGuest(i)}
                  style={{
                    width: 28, height: 28, borderRadius: 999,
                    background: "rgba(255, 92, 58, 0.12)", color: "#d44a2e",
                    border: "none", cursor: "pointer", fontSize: 16, fontWeight: 700,
                    display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0,
                  }}
                  aria-label="Supprimer"
                >×</button>
              )}
            </div>
          ))}
        </div>

        {/* Add button + compteur */}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 20, flexWrap: "wrap", gap: 10 }}>
          <button
            onClick={addGuest}
            disabled={guests.length >= 15}
            style={{
              background: guests.length >= 15 ? "rgba(0,0,0,0.06)" : "var(--ink)",
              color: guests.length >= 15 ? "rgba(0,0,0,0.4)" : "var(--sun-3)",
              border: "none", borderRadius: 8,
              padding: "9px 14px", fontSize: 13, fontWeight: 700,
              cursor: guests.length >= 15 ? "default" : "pointer",
              fontFamily: "var(--font-mono)", letterSpacing: "0.06em",
              display: "flex", alignItems: "center", gap: 6,
            }}
          >
            <span style={{ fontSize: 18 }}>+</span> Ajouter une personne
          </button>
          <div style={{ fontSize: 12, color: "var(--ink-2, rgba(0,0,0,0.55))", fontFamily: "var(--font-mono)", letterSpacing: "0.06em", fontWeight: 600 }}>
            {validGuests.length}/15 personne{validGuests.length > 1 ? "s" : ""}
          </div>
        </div>

        {/* Récap forfait */}
        <div style={{
          background: "var(--ink)", color: "#fff",
          borderRadius: 10, padding: "12px 16px", marginBottom: 16,
          display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12,
        }}>
          <div>
            <div style={{ fontSize: 11, opacity: 0.7, fontFamily: "var(--font-mono)", letterSpacing: "0.1em" }}>FORFAIT TABLE</div>
            <div style={{ fontWeight: 700, fontSize: 15, marginTop: 2 }}>🍾 1 bouteille incluse</div>
          </div>
          <div style={{ fontFamily: "var(--font-display)", fontWeight: 900, fontSize: 28, color: "var(--sun-3)" }}>600 €</div>
        </div>

        {/* Boutons */}
        <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
          <button
            onClick={onClose}
            className="btn btn-ghost"
            style={{ flex: "1 1 120px", fontSize: 14 }}
          >Annuler</button>
          <button
            onClick={handleSubmit}
            disabled={!canSubmit}
            className="btn btn-pink"
            style={{ flex: "2 1 200px", fontSize: 14, opacity: canSubmit ? 1 : 0.45, cursor: canSubmit ? "pointer" : "not-allowed" }}
          >
            {sending ? "Envoi en cours…" : <>Valider &amp; payer sur Weezevent <span aria-hidden="true">→</span></>}
          </button>
        </div>

        <div style={{ fontSize: 11, color: "var(--ink-2, rgba(0,0,0,0.55))", marginTop: 12, textAlign: "center", lineHeight: 1.5 }}>
          La liste est envoyée automatiquement à l'organisation. Vous pouvez la modifier jusqu'à 48h avant l'événement en nous contactant via la page Contact.
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { PageBooking });
