<?php /* Simple Tetris clone in a single index.php (HTML + JS). Now with WebAudio sound effects (line clear, drop/lock, game over) + mute toggle. */ ?>
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Vibe Coding Test – Tetris (with SFX)</title>
  <style>
    :root { --bg:#0b0f14; --fg:#e6ebf0; --muted:#8a99a8; --panel:#121922; --accent:#4dd0e1; }
    * { box-sizing:border-box; }
    html,body { height:100%; }
    body { margin:0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji"; background:radial-gradient(1200px 700px at 20% -10%, #15202b 0, #0b0f14 60%); color:var(--fg); display:grid; place-items:center; }
    .wrap { display:grid; grid-template-columns:auto 240px; gap:16px; padding:20px; }
    .panel { background:var(--panel); border:1px solid #1d2631; border-radius:14px; padding:14px; box-shadow:0 10px 25px rgba(0,0,0,.25); }
    canvas { display:block; background:#0c121a; border:1px solid #1d2631; border-radius:12px; box-shadow:inset 0 0 0 1px rgba(255,255,255,.03); }
    h1 { margin:0 0 8px; font-size:18px; letter-spacing:.4px; color:#cfe6ff; display:flex; align-items:center; justify-content:space-between; gap:8px; }
    .stats { display:grid; gap:10px; }
    .stat { display:flex; justify-content:space-between; font-variant-numeric:tabular-nums; }
    .controls { margin-top:10px; font-size:13px; color:var(--muted); line-height:1.4; }
    .btns { display:flex; gap:8px; margin-top:10px; flex-wrap:wrap; }
    button { background:#17212b; border:1px solid #243242; color:var(--fg); padding:8px 10px; border-radius:10px; cursor:pointer; }
    button:hover { border-color:#2f455a; }
    .next { margin-top:10px; display:grid; grid-template-columns:repeat(4, 22px); grid-template-rows:repeat(4, 22px); gap:2px; justify-content:flex-start; }
    .cell { width:22px; height:22px; background:#0c121a; border:1px solid #1a2532; border-radius:4px; }
    .badge { display:inline-block; background:#13202a; border:1px solid #2a404f; padding:2px 6px; border-radius:6px; font-size:12px; color:#bfe9ff; }
    .footer { margin-top:8px; font-size:12px; color:#7f8fa1; }
    .muted { opacity:.6; }
  </style>
</head>
<body>
  <div class="wrap">
    <div class="panel">
      <canvas id="board" width="300" height="600" aria-label="Tetris board" role="img"></canvas>
    </div>
    <div class="panel" style="width:240px">
      <h1>
        <span>TETRIS <span class="badge">vanilla JS</span></span>
        <button id="btn-sound" title="Toggle sound">🔊</button>
      </h1>
      <div class="stats">
        <div class="stat"><span>Score</span><strong id="score">0</strong></div>
        <div class="stat"><span>Lines</span><strong id="lines">0</strong></div>
        <div class="stat"><span>Level</span><strong id="level">1</strong></div>
      </div>
      <div style="margin-top:10px">
        <div style="font-size:13px; color:#9fb3c6; margin-bottom:6px">Next</div>
        <div id="next" class="next" aria-label="Next tetromino preview"></div>
      </div>
      <div class="btns">
        <button id="btn-pause" title="P">Pause</button>
        <button id="btn-restart" title="R">Restart</button>
      </div>
      <div class="controls">
        <div>← → move, ↓ soft drop</div>
        <div>↑ rotate, Space hard drop</div>
        <div>P pause, R restart</div>
        <div style="margin-top:6px; color:#9fb3c6">Tip: Some browsers need a key press to enable audio.</div>
      </div>
      <div class="footer">© <?php echo date('Y'); ?> VIBING test</div>
    </div>
  </div>

<script>
(() => {
  // ====== Config ======
  const COLS = 10, ROWS = 20, SIZE = 30; // canvas cell size
  const GRAVITY_BASE_MS = 900; // level 1 drop interval
  const LEVEL_ACCEL = 0.88; // per level speed multiplier

  // Colors per piece type
  const COLORS = {
    I: '#66d9ff', J: '#85a5ff', L: '#ffc98b', O: '#f7f58a', S: '#9cff9c', T: '#e6a8ff', Z: '#ff9aae'
  };

  // Tetromino rotation states (4x4 matrices)
  const SHAPES = {
    I: [
      [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],
      [[0,0,1,0],[0,0,1,0],[0,0,1,0],[0,0,1,0]],
      [[0,0,0,0],[0,0,0,0],[1,1,1,1],[0,0,0,0]],
      [[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]]
    ],
    J: [
      [[1,0,0],[1,1,1],[0,0,0]],
      [[0,1,1],[0,1,0],[0,1,0]],
      [[0,0,0],[1,1,1],[0,0,1]],
      [[0,1,0],[0,1,0],[1,1,0]]
    ],
    L: [
      [[0,0,1],[1,1,1],[0,0,0]],
      [[0,1,0],[0,1,0],[0,1,1]],
      [[0,0,0],[1,1,1],[1,0,0]],
      [[1,1,0],[0,1,0],[0,1,0]]
    ],
    O: [
      [[1,1],[1,1]],
      [[1,1],[1,1]],
      [[1,1],[1,1]],
      [[1,1],[1,1]]
    ],
    S: [
      [[0,1,1],[1,1,0],[0,0,0]],
      [[0,1,0],[0,1,1],[0,0,1]],
      [[0,0,0],[0,1,1],[1,1,0]],
      [[1,0,0],[1,1,0],[0,1,0]]
    ],
    T: [
      [[0,1,0],[1,1,1],[0,0,0]],
      [[0,1,0],[0,1,1],[0,1,0]],
      [[0,0,0],[1,1,1],[0,1,0]],
      [[0,1,0],[1,1,0],[0,1,0]]
    ],
    Z: [
      [[1,1,0],[0,1,1],[0,0,0]],
      [[0,0,1],[0,1,1],[0,1,0]],
      [[0,0,0],[1,1,0],[0,1,1]],
      [[0,1,0],[1,1,0],[1,0,0]]
    ]
  };

  // ====== Helpers ======
  const $ = sel => document.querySelector(sel);
  const board = $('#board');
  const ctx = board.getContext('2d');
  board.width = COLS * SIZE; board.height = ROWS * SIZE;

  const ui = {
    score: $('#score'), lines: $('#lines'), level: $('#level'),
    next: $('#next'), pauseBtn: $('#btn-pause'), restartBtn: $('#btn-restart'), soundBtn: $('#btn-sound')
  };

  function emptyMatrix(w, h, v=0) { return Array.from({length:h}, () => Array(w).fill(v)); }

  // 7-bag randomizer
  class Bag {
    constructor(){ this.bag = []; }
    next(){
      if (this.bag.length === 0) this.bag = shuffle(Object.keys(SHAPES));
      return this.bag.pop();
    }
  }
  function shuffle(a){ for(let i=a.length-1;i>0;i--){ const j=Math.floor(Math.random()*(i+1)); [a[i],a[j]]=[a[j],a[i]]; } return a; }

  // Piece class
  class Piece {
    constructor(type){
      this.type = type;
      this.rot = 0;
      this.shape = SHAPES[type][0];
      this.x = Math.floor((COLS - this.shape[0].length)/2);
      this.y = -this.shape.length; // spawn above
    }
    rotate(dir){ // dir: +1 cw, -1 ccw
      const oldRot = this.rot;
      this.rot = (this.rot + dir + 4) % 4;
      this.shape = SHAPES[this.type][this.rot];
      // simple wall-kick: try offsets
      const kicks = [0, -1, 1, -2, 2];
      for (const dx of kicks){ if (!collide(boardState, this, this.x+dx, this.y)) { this.x += dx; sfx.tick(); return true; } }
      // revert
      this.rot = oldRot; this.shape = SHAPES[this.type][this.rot];
      return false;
    }
  }

  // ====== Audio (WebAudio synth) ======
  class SFX {
    constructor(){
      this.enabled = true;
      this.ctx = null;
      this.started = false;
      const gesture = () => { this.start(); window.removeEventListener('keydown', gesture); window.removeEventListener('pointerdown', gesture); };
      window.addEventListener('keydown', gesture);
      window.addEventListener('pointerdown', gesture);
    }
    start(){
      if (this.started) return;
      const AC = window.AudioContext || window.webkitAudioContext;
      if (!AC) return; // no audio support
      this.ctx = new AC();
      this.started = true;
    }
    toggle(){ this.enabled = !this.enabled; updateSoundButton(); }
    // tiny helper to make beeps
    beep(freq=440, dur=0.08, type='square', gain=0.03, when=0){
      if (!this.enabled || !this.ctx) return;
      const t = this.ctx.currentTime + when;
      const o = this.ctx.createOscillator();
      const g = this.ctx.createGain();
      o.type = type; o.frequency.setValueAtTime(freq, t);
      g.gain.setValueAtTime(0.0001, t);
      g.gain.exponentialRampToValueAtTime(gain, t+0.005);
      g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
      o.connect(g).connect(this.ctx.destination);
      o.start(t);
      o.stop(t + dur + 0.02);
    }
    drop(){ this.beep(220, 0.06, 'sawtooth', 0.04); }
    lock(){ this.beep(180, 0.07, 'triangle', 0.035); }
    line(n){
      const base = 520; // cheerful arpeggio
      for (let i=0;i<n;i++) this.beep(base + i*120, 0.08, 'square', 0.04, i*0.05);
    }
    gameover(){
      [440, 392, 349, 329].forEach((f,i)=> this.beep(f, 0.18, 'sawtooth', 0.035, i*0.12));
    }
    tick(){ this.beep(700, 0.03, 'square', 0.02); } // rotate/move
  }
  const sfx = new SFX();

  function updateSoundButton(){
    if (!ui.soundBtn) return;
    ui.soundBtn.textContent = sfx.enabled ? '🔊' : '🔈';
    ui.soundBtn.classList.toggle('muted', !sfx.enabled);
  }

  // ====== Game state ======
  let boardState = emptyMatrix(COLS, ROWS, 0);
  let bag = new Bag();
  let current = new Piece(bag.next());
  let nextPieceType = bag.next();
  let score = 0, lines = 0, level = 1;
  let dropTimer = 0, dropInterval = gravityFor(level), lastTime = 0;
  let paused = false, gameOver = false;

  drawNextPreview();
  updateUI();
  requestAnimationFrame(loop);

  // ====== Core ======
  function loop(time=0){
    const dt = time - lastTime; lastTime = time;
    if (!paused && !gameOver){
      dropTimer += dt;
      if (dropTimer >= dropInterval){
        dropTimer = 0; softDrop(true);
      }
    }
    render();
    requestAnimationFrame(loop);
  }

  function gravityFor(level){ return GRAVITY_BASE_MS * Math.pow(LEVEL_ACCEL, Math.max(0, level-1)); }

  function collide(mat, piece, ox = piece.x, oy = piece.y){
    const s = piece.shape;
    for (let y=0; y<s.length; y++){
      for (let x=0; x<s[y].length; x++){
        if (!s[y][x]) continue;
        const px = ox + x, py = oy + y;
        if (px < 0 || px >= COLS || py >= ROWS) return true;
        if (py >= 0 && mat[py][px]) return true;
      }
    }
    return false;
  }

  function lockPiece(){
    const s = current.shape;
    for (let y=0; y<s.length; y++){
      for (let x=0; x<s[y].length; x++){
        if (s[y][x]){
          const px = current.x + x, py = current.y + y;
          if (py < 0){ // topped out
            gameOver = true; paused = false; sfx.gameover();
            return;
          }
          boardState[py][px] = current.type;
        }
      }
    }
    sfx.lock();
    const cleared = clearLines();
    if (cleared > 0){
      lines += cleared;
      score += [0, 100, 300, 500, 800][cleared] * level;
      level = 1 + Math.floor(lines / 10);
      dropInterval = gravityFor(level);
      sfx.line(cleared);
    }
    current = new Piece(nextPieceType);
    nextPieceType = bag.next();
    drawNextPreview();
  }

  function clearLines(){
    let cleared = 0;
    for (let y = ROWS-1; y >= 0; y--){
      if (boardState[y].every(v => v)){
        boardState.splice(y,1);
        boardState.unshift(Array(COLS).fill(0));
        cleared++; y++;
      }
    }
    return cleared;
  }

  function softDrop(fromGravity=false){
    if (!collide(boardState, current, current.x, current.y+1)){
      current.y++;
      if (!fromGravity) sfx.drop(); // manual soft drop feedback
    } else {
      lockPiece();
    }
    updateUI();
  }

  function hardDrop(){
    let dist = 0;
    while (!collide(boardState, current, current.x, current.y+1)){
      current.y++; dist++;
    }
    score += 2 * dist; // small bonus
    sfx.drop();
    lockPiece();
    updateUI();
  }

  function move(dx){
    if (!collide(boardState, current, current.x+dx, current.y)) { current.x += dx; sfx.tick(); }
  }

  // ====== Rendering ======
  function render(){
    ctx.clearRect(0,0,board.width,board.height);
    // grid (subtle)
    ctx.globalAlpha = 0.05;
    for(let x=0;x<COLS;x++){
      for(let y=0;y<ROWS;y++){
        ctx.strokeRect(x*SIZE, y*SIZE, SIZE, SIZE);
      }
    }
    ctx.globalAlpha = 1;

    // draw board
    for (let y=0; y<ROWS; y++){
      for (let x=0; x<COLS; x++){
        const t = boardState[y][x];
        if (!t) continue;
        drawCell(x, y, COLORS[t]);
      }
    }

    // draw current piece
    const s = current.shape;
    for (let y=0; y<s.length; y++){
      for (let x=0; x<s[y].length; x++){
        if (!s[y][x]) continue;
        const px = current.x + x, py = current.y + y;
        if (py >= 0) drawCell(px, py, COLORS[current.type]);
      }
    }

    if (paused) overlayText('PAUSED');
    if (gameOver) overlayText('GAME OVER');
  }

  function overlayText(text){
    ctx.save();
    ctx.fillStyle = 'rgba(0,0,0,0.5)';
    ctx.fillRect(0,0,board.width,board.height);
    ctx.fillStyle = '#e6ebf0';
    ctx.font = 'bold 28px system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial';
    ctx.textAlign = 'center';
    ctx.fillText(text, board.width/2, board.height/2);
    ctx.restore();
  }

  function drawCell(x, y, color){
    const px = x*SIZE, py = y*SIZE;
    // base
    ctx.fillStyle = color;
    ctx.fillRect(px+1, py+1, SIZE-2, SIZE-2);
    // glossy inset
    ctx.fillStyle = 'rgba(255,255,255,.15)';
    ctx.fillRect(px+3, py+3, SIZE-6, Math.floor((SIZE-6)/2));
    // border
    ctx.strokeStyle = 'rgba(0,0,0,.35)';
    ctx.strokeRect(px+0.5, py+0.5, SIZE-1, SIZE-1);
  }

  function drawNextPreview(){
    ui.next.innerHTML = '';
    const grid = Array.from({length:16}, () => {
      const d = document.createElement('div'); d.className = 'cell'; return d; });
    grid.forEach(cell => ui.next.appendChild(cell));
    const shape = SHAPES[nextPieceType][0];
    // center in 4x4
    const offX = Math.floor((4 - shape[0].length)/2);
    const offY = Math.floor((4 - shape.length)/2);
    for (let y=0;y<shape.length;y++){
      for (let x=0;x<shape[y].length;x++){
        if (shape[y][x]){
          const idx = (offY+y)*4 + (offX+x);
          grid[idx].style.background = COLORS[nextPieceType];
          grid[idx].style.borderColor = '#2a3b4f';
        }
      }
    }
  }

  function updateUI(){
    ui.score.textContent = score;
    ui.lines.textContent = lines;
    ui.level.textContent = level;
  }

  // ====== Input ======
  window.addEventListener('keydown', (e) => {
    if (gameOver){ if (e.key.toLowerCase()==='r'){ restart(); } return; }
    switch (e.key) {
      case 'ArrowLeft': move(-1); break;
      case 'ArrowRight': move(+1); break;
      case 'ArrowDown': softDrop(); break;
      case 'ArrowUp': current.rotate(+1); break;
      case ' ': e.preventDefault(); hardDrop(); break;
      case 'p': case 'P': togglePause(); break;
      case 'r': case 'R': restart(); break;
    }
  });

  ui.pauseBtn.addEventListener('click', togglePause);
  ui.restartBtn.addEventListener('click', restart);
  ui.soundBtn.addEventListener('click', () => { sfx.toggle(); });

  function togglePause(){ if (gameOver) return; paused = !paused; }

  function restart(){
    boardState = emptyMatrix(COLS, ROWS, 0);
    bag = new Bag();
    current = new Piece(bag.next());
    nextPieceType = bag.next();
    score = 0; lines = 0; level = 1; dropInterval = gravityFor(level); dropTimer = 0; lastTime = 0;
    gameOver = false; paused = false;
    drawNextPreview(); updateUI();
  }

  // initialize sound button state
  updateSoundButton();
})();
</script>
</body>
</html>