<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>One on One: Dr. J vs. Larry Bird - Three.js</title>
  <style>
    body {
      margin: 0;
      padding: 0;
      overflow: hidden;
      background-color: #111;
      font-family: 'Courier New', Courier, monospace;
      user-select: none;
    }
    #canvas-container {
      width: 100vw;
      height: 100vh;
      display: block;
    }
    #ui {
      position: absolute;
      top: 20px;
      left: 50%;
      transform: translateX(-50%);
      display: flex;
      gap: 40px;
      color: #fff;
      font-size: 20px;
      font-weight: bold;
      text-shadow: 2px 2px #000;
      pointer-events: none;
    }
    .score-card {
      background: rgba(0, 0, 0, 0.6);
      padding: 10px 20px;
      border: 2px solid #555;
      border-radius: 4px;
    }
    #controls-hint {
      position: absolute;
      bottom: 20px;
      left: 50%;
      transform: translateX(-50%);
      background: rgba(0, 0, 0, 0.7);
      color: #eee;
      padding: 8px 16px;
      border-radius: 4px;
      font-size: 14px;
      pointer-events: none;
    }
  </style>
  <!-- Three.js CDN -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>
  <div id="ui">
    <div class="score-card" style="color: #33cc33;">BIRD: <span id="bird-score">0</span></div>
    <div class="score-card" style="color: #3388ff;">DR. J: <span id="drj-score">0</span></div>
  </div>
  <div id="controls-hint">Press <b>SPACE</b> to Shoot (Larry Bird) / Trigger Shatter & Janitor</div>
  <div id="canvas-container"></div>

  <script>
    // --- Retro Audio Synthesizer (Web Audio API) ---
    const AudioEngine = {
      ctx: null,
      init() {
        if (!this.ctx) {
          this.ctx = new (window.AudioContext || window.webkitAudioContext)();
        }
      },
      playDribble() {
        if (!this.ctx) return;
        const osc = this.ctx.createOscillator();
        const gain = this.ctx.createGain();
        osc.type = 'triangle';
        osc.frequency.setValueAtTime(140, this.ctx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(40, this.ctx.currentTime + 0.08);
        gain.gain.setValueAtTime(0.6, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.08);
        osc.connect(gain);
        gain.connect(this.ctx.destination);
        osc.start();
        osc.stop(this.ctx.currentTime + 0.08);
      },
      playSwish() {
        if (!this.ctx) return;
        const bufferSize = this.ctx.sampleRate * 0.15;
        const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
        const data = buffer.getChannelData(0);
        for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;

        const noise = this.ctx.createBufferSource();
        noise.buffer = buffer;
        const filter = this.ctx.createBiquadFilter();
        filter.type = 'bandpass';
        filter.frequency.setValueAtTime(1200, this.ctx.currentTime);

        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.5, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.15);

        noise.connect(filter);
        filter.connect(gain);
        gain.connect(this.ctx.destination);
        noise.start();
      },
      playGlassShatter() {
        if (!this.ctx) return;
        // White noise burst + high-pass metallic ring
        const bufferSize = this.ctx.sampleRate * 0.6;
        const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
        const data = buffer.getChannelData(0);
        for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;

        const noise = this.ctx.createBufferSource();
        noise.buffer = buffer;

        const filter = this.ctx.createBiquadFilter();
        filter.type = 'highpass';
        filter.frequency.setValueAtTime(2500, this.ctx.currentTime);

        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(1.0, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.6);

        noise.connect(filter);
        filter.connect(gain);
        gain.connect(this.ctx.destination);
        noise.start();
      },
      playMutter() {
        if (!this.ctx) return;
        const osc = this.ctx.createOscillator();
        const gain = this.ctx.createGain();
        osc.type = 'sawtooth';
        osc.frequency.setValueAtTime(80 + Math.random() * 60, this.ctx.currentTime);
        gain.gain.setValueAtTime(0.2, this.ctx.currentTime);
        gain.gain.linearRampToValueAtTime(0.01, this.ctx.currentTime + 0.1);
        osc.connect(gain);
        gain.connect(this.ctx.destination);
        osc.start();
        osc.stop(this.ctx.currentTime + 0.1);
      }
    };

    // --- Scene Setup ---
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x1a1a1a);

    const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.set(0, 10, 22);
    camera.lookAt(0, 4, 0);

    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;
    document.getElementById('canvas-container').appendChild(renderer.domElement);

    const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
    scene.add(ambientLight);

    const spotLight = new THREE.DirectionalLight(0xffffff, 0.8);
    spotLight.position.set(5, 20, 15);
    spotLight.castShadow = true;
    scene.add(spotLight);

    // --- Court Floor ---
    const courtGeo = new THREE.PlaneGeometry(30, 20);
    const courtMat = new THREE.MeshStandardMaterial({ color: 0xb58042, roughness: 0.6 });
    const court = new THREE.Mesh(courtGeo, courtMat);
    court.rotation.x = -Math.PI / 2;
    court.receiveShadow = true;
    scene.add(court);

    // Key / Paint Lines
    const lineMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
    const keyGeo = new THREE.RingGeometry(2.5, 2.6, 32, 1, 0, Math.PI);
    const keyLine = new THREE.Mesh(keyGeo, lineMat);
    keyLine.rotation.x = -Math.PI / 2;
    keyLine.rotation.z = Math.PI / 2;
    keyLine.position.set(0, 0.01, -2);
    scene.add(keyLine);

    // --- Hoop & Backboard ---
    const hoopGroup = new THREE.Group();
    hoopGroup.position.set(0, 0, -8);

    const poleGeo = new THREE.CylinderGeometry(0.15, 0.15, 8);
    const poleMat = new THREE.MeshStandardMaterial({ color: 0x333333 });
    const pole = new THREE.Mesh(poleGeo, poleMat);
    pole.position.set(0, 4, -1);
    hoopGroup.add(pole);

    const armGeo = new THREE.CylinderGeometry(0.1, 0.1, 1.5);
    const arm = new THREE.Mesh(armGeo, poleMat);
    arm.rotation.x = Math.PI / 3;
    arm.position.set(0, 7.2, -0.4);
    hoopGroup.add(arm);

    // Glass Backboard
    const backboardGeo = new THREE.BoxGeometry(4, 2.8, 0.1);
    const backboardMat = new THREE.MeshStandardMaterial({
      color: 0xffffff,
      transparent: true,
      opacity: 0.7,
      roughness: 0.1
    });
    const backboard = new THREE.Mesh(backboardGeo, backboardMat);
    backboard.position.set(0, 7.5, 0);
    hoopGroup.add(backboard);

    // Rim
    const rimGeo = new THREE.TorusGeometry(0.65, 0.05, 8, 24);
    const rimMat = new THREE.MeshStandardMaterial({ color: 0xd63031 });
    const rim = new THREE.Mesh(rimGeo, rimMat);
    rim.rotation.x = Math.PI / 2;
    rim.position.set(0, 6.7, 0.8);
    hoopGroup.add(rim);

    scene.add(hoopGroup);

    // --- Shattered Glass Particles ---
    let glassShards = [];
    function shatterBackboard() {
      backboard.visible = false;
      AudioEngine.playGlassShatter();

      const shardCount = 70;
      const shardGeo = new THREE.BoxGeometry(0.2, 0.2, 0.05);
      const shardMat = new THREE.MeshStandardMaterial({
        color: 0xccffff,
        transparent: true,
        opacity: 0.8
      });

      for (let i = 0; i < shardCount; i++) {
        const shard = new THREE.Mesh(shardGeo, shardMat);
        shard.position.set(
          (Math.random() - 0.5) * 3.5,
          7.5 + (Math.random() - 0.5) * 2,
          -8 + (Math.random() - 0.5) * 0.5
        );
        shard.userData = {
          velocity: new THREE.Vector3(
            (Math.random() - 0.5) * 0.15,
            Math.random() * 0.1,
            Math.random() * 0.2
          ),
          rotSpeed: new THREE.Vector3(Math.random() * 0.2, Math.random() * 0.2, Math.random() * 0.2)
        };
        scene.add(shard);
        glassShards.push(shard);
      }
    }

    // --- Blocky Character Builder ---
    function createPlayer(bodyColor, skinColor, hairColor) {
      const group = new THREE.Group();

      // Torso / Jersey
      const torso = new THREE.Mesh(
        new THREE.BoxGeometry(0.9, 1.2, 0.5),
        new THREE.MeshStandardMaterial({ color: bodyColor })
      );
      torso.position.y = 2.4;
      group.add(torso);

      // Head
      const head = new THREE.Mesh(
        new THREE.BoxGeometry(0.5, 0.55, 0.5),
        new THREE.MeshStandardMaterial({ color: skinColor })
      );
      head.position.y = 3.3;
      group.add(head);

      // Hair
      const hair = new THREE.Mesh(
        new THREE.BoxGeometry(0.55, 0.25, 0.55),
        new THREE.MeshStandardMaterial({ color: hairColor })
      );
      hair.position.y = 3.6;
      group.add(hair);

      // Limbs
      const limbMat = new THREE.MeshStandardMaterial({ color: skinColor });
      const legGeo = new THREE.BoxGeometry(0.3, 1.2, 0.3);

      const leftLeg = new THREE.Mesh(legGeo, limbMat);
      leftLeg.position.set(-0.25, 1.2, 0);
      group.add(leftLeg);

      const rightLeg = new THREE.Mesh(legGeo, limbMat);
      rightLeg.position.set(0.25, 1.2, 0);
      group.add(rightLeg);

      return group;
    }

    // Larry Bird: Green jersey, light skin, blond hair
    const bird = createPlayer(0x00843d, 0xffdbac, 0xdec166);
    bird.position.set(-3, 0, 0);
    scene.add(bird);

    // Dr. J: Blue/Red Sixers vibe, darker skin, dark hair / afro shape
    const drJ = createPlayer(0x0046b8, 0x8d5524, 0x111111);
    drJ.position.set(2, 0, -2);
    scene.add(drJ);

    // Basketball
    const ballGeo = new THREE.SphereGeometry(0.32, 16, 16);
    const ballMat = new THREE.MeshStandardMaterial({ color: 0xee6711, roughness: 0.4 });
    const ball = new THREE.Mesh(ballGeo, ballMat);
    ball.position.set(-2.5, 1.5, 0.8);
    scene.add(ball);

    // --- Janitor Builder ---
    function createJanitor() {
      const group = new THREE.Group();
      // Overalls
      const torso = new THREE.Mesh(
        new THREE.BoxGeometry(0.8, 1.1, 0.5),
        new THREE.MeshStandardMaterial({ color: 0x4a6572 })
      );
      torso.position.y = 2.2;
      group.add(torso);

      // Head + Cap
      const head = new THREE.Mesh(
        new THREE.BoxGeometry(0.45, 0.45, 0.45),
        new THREE.MeshStandardMaterial({ color: 0xf5c6a5 })
      );
      head.position.y = 3.0;
      group.add(head);

      const cap = new THREE.Mesh(
        new THREE.BoxGeometry(0.55, 0.15, 0.65),
        new THREE.MeshStandardMaterial({ color: 0x333333 })
      );
      cap.position.set(0, 3.25, 0.05);
      group.add(cap);

      // Broom
      const handle = new THREE.Mesh(
        new THREE.CylinderGeometry(0.04, 0.04, 3),
        new THREE.MeshStandardMaterial({ color: 0x8b5a2b })
      );
      handle.position.set(0.4, 1.8, 0.5);
      handle.rotation.x = 0.2;
      group.add(handle);

      const bristles = new THREE.Mesh(
        new THREE.BoxGeometry(0.6, 0.4, 0.2),
        new THREE.MeshStandardMaterial({ color: 0xd2b48c })
      );
      bristles.position.set(0.4, 0.2, 0.8);
      group.add(bristles);

      group.position.set(-15, 0, -7); // Start off-court
      group.visible = false;
      return group;
    }

    const janitor = createJanitor();
    scene.add(janitor);

    // --- Game Logic & Animation States ---
    let state = 'DRIBBLE'; // DRIBBLE, SHOOTING, SHATTERED, CLEANING
    let shotProgress = 0;
    let ballStartPos = new THREE.Vector3();
    let scores = { bird: 0, drj: 0 };
    let janitorState = { active: false, targetX: 0, timer: 0 };

    window.addEventListener('keydown', (e) => {
      if (e.code === 'Space') {
        AudioEngine.init();
        if (state === 'DRIBBLE') {
          state = 'SHOOTING';
          shotProgress = 0;
          ballStartPos.copy(ball.position);
        }
      }
    });

    // --- Animation Loop ---
    let clock = new THREE.Clock();

    function animate() {
      requestAnimationFrame(animate);
      const delta = clock.getDelta();
      const elapsed = clock.getElapsedTime();

      // Idle Dribbling
      if (state === 'DRIBBLE') {
        const dribbleY = Math.abs(Math.sin(elapsed * 8)) * 1.2 + 0.3;
        ball.position.set(bird.position.x + 0.5, dribbleY, bird.position.z + 0.6);
        bird.position.y = Math.sin(elapsed * 4) * 0.05;
        drJ.position.y = Math.cos(elapsed * 4) * 0.05;

        if (dribbleY < 0.35 && Math.sin(elapsed * 8) < 0) {
          AudioEngine.playDribble();
        }
      }

      // Shooting Animation
      if (state === 'SHOOTING') {
        shotProgress += delta * 1.2;
        const t = shotProgress;

        // Quadratic arc to hoop rim
        const target = new THREE.Vector3(0, 6.7, -7.2);
        ball.position.x = THREE.MathUtils.lerp(ballStartPos.x, target.x, t);
        ball.position.z = THREE.MathUtils.lerp(ballStartPos.z, target.z, t);
        ball.position.y = THREE.MathUtils.lerp(ballStartPos.y, target.y, t) + Math.sin(t * Math.PI) * 4;

        bird.position.y = Math.sin(Math.min(t * 2, 1) * Math.PI) * 0.8;

        if (t >= 1) {
          state = 'SHATTERED';
          scores.bird += 2;
          document.getElementById('bird-score').innerText = scores.bird;
          shatterBackboard();
          ball.position.set(0, 0.35, -7.2);

          // Summon Janitor after a short delay
          setTimeout(() => {
            janitor.visible = true;
            janitorState.active = true;
          }, 800);
        }
      }

      // Shard Physics Update
      if (glassShards.length > 0) {
        glassShards.forEach((shard) => {
          if (shard.position.y > 0.05) {
            shard.userData.velocity.y -= 9.8 * delta * 0.1;
            shard.position.add(shard.userData.velocity);
            shard.rotation.x += shard.userData.rotSpeed.x;
            shard.rotation.y += shard.userData.rotSpeed.y;
          } else {
            shard.position.y = 0.02; // Settle on ground
          }
        });
      }

      // Janitor Clean-Up Logic
      if (janitorState.active) {
        if (janitor.position.x < 0) {
          // Walking to hoop
          janitor.position.x += delta * 3;
          janitor.position.y = Math.abs(Math.sin(elapsed * 8)) * 0.15;
        } else {
          // Sweeping animation
          janitorState.timer += delta;
          janitor.rotation.y = Math.sin(janitorState.timer * 4) * 0.4;
          
          if (Math.random() < 0.03) AudioEngine.playMutter();

          // Gradually clean up shards
          if (glassShards.length > 0 && Math.random() < 0.1) {
            const removed = glassShards.pop();
            scene.remove(removed);
          }

          // Finish and reset
          if (janitorState.timer > 4) {
            janitor.position.x -= delta * 3;
            if (janitor.position.x < -14) {
              janitorState.active = false;
              janitor.visible = false;
              janitor.rotation.y = 0;
              janitorState.timer = 0;
              
              // Restore backboard
              backboard.visible = true;
              state = 'DRIBBLE';
            }
          }
        }
      }

      renderer.render(scene, camera);
    }

    // Window Resize Support
    window.addEventListener('resize', () => {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    });

    animate();
  </script>
</body>
</html>