Models 44

Dots3-Note Preview (free)

by Dots StudioReasoningClean
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
51.2 s
Cost
$0
Output tokens
5,339
Spent thinking
0 tokens
Speed
105.9 tok/s
Console errors
0
First token after
718 ms
First visible text after
718 ms
Prompt tokens
510
Code size
16.5 KB
Finish reason
stop
Served by
AtlasCloud
Model id
dots-studio/dots-3-note-preview:free
Reasoning sent
{"effort":"none"}
Max output
32,000 tokens
Generated
Nov 9, 2026

What it was asked

The prompt
Build a polished Breakout (brick breaker) game in a single HTML file using vanilla JavaScript and a <canvas>. Requirements: - A paddle controlled with the mouse and with the left/right arrow keys. - The bounce angle off the paddle depends on where the ball hits it (center = straight up, edges = sharp angle). - One level with at least 6 rows of bricks, mixing normal bricks and tougher bricks that need 2 hits and visibly crack after the first one. - Some bricks visibly carry a power-up icon; when broken they drop it, and catching it with the paddle gives either a wider paddle (10 seconds) or multi-ball (3 balls). - The ball must never pass through bricks or the paddle. - Particles when a brick breaks. - 3 lives, score displayed. Game over and victory screens with a restart option.
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 629 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Breakout: Power-Up Edition</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body {
    height: 100%;
    background: #0f0f1a;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    color: #fff;
    overflow: hidden;
    user-select: none;
  }
  #wrap {
    position: relative;
    width: 100vw;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  canvas {
    background: linear-gradient(160deg, #1a1a2e 0%, #16213e 100%);
    border-radius: 12px;
    box-shadow: 0 0 40px rgba(0, 150, 255, 0.3);
    display: block;
    max-width: 100%;
    max-height: 100%;
  }
  .overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    background: rgba(0, 0, 0, 0.85);
    backdrop-filter: blur(4px);
    z-index: 10;
    transition: opacity 0.4s ease;
  }
  .overlay.hidden { opacity: 0; pointer-events: none; }
  .overlay h1 {
    font-size: clamp(2rem, 6vw, 3.5rem);
    margin-bottom: 1rem;
    background: linear-gradient(90deg, #00c6ff, #0072ff);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    text-shadow: 0 0 20px rgba(0, 150, 255, 0.4);
  }
  .overlay p { font-size: 1.2rem; margin-bottom: 2rem; opacity: 0.85; }
  .btn {
    padding: 12px 32px;
    font-size: 1.1rem;
    font-weight: 600;
    color: #fff;
    background: linear-gradient(90deg, #00c6ff, #0072ff);
    border: none;
    border-radius: 50px;
    cursor: pointer;
    transition: transform 0.2s, box-shadow 0.2s;
    box-shadow: 0 4px 20px rgba(0, 114, 255, 0.5);
  }
  .btn:hover { transform: translateY(-2px); box-shadow: 0 6px 25px rgba(0, 114, 255, 0.7); }
  .btn:active { transform: translateY(0); }
  .hud {
    position: absolute;
    top: 20px;
    left: 0;
    right: 0;
    display: flex;
    justify-content: space-between;
    padding: 0 30px;
    font-size: 1.1rem;
    font-weight: 600;
    text-shadow: 0 2px 4px rgba(0,0,0,0.7);
    pointer-events: none;
    z-index: 5;
  }
  .lives { color: #ff4d6d; }
  .score { color: #00e676; }
  .powerup-info {
    position: absolute;
    bottom: 20px;
    left: 0;
    right: 0;
    text-align: center;
    font-size: 0.95rem;
    opacity: 0.8;
    pointer-events: none;
    z-index: 5;
  }
</style>
</head>
<body>
<div id="wrap">
  <canvas id="game" width="900" height="600"></canvas>
  <div class="hud">
    <div class="lives">Lives: <span id="livesCount">3</span></div>
    <div class="score">Score: <span id="scoreCount">0</span></div>
  </div>
  <div class="powerup-info" id="powerupInfo"></div>

  <div class="overlay" id="startScreen">
    <h1>Breakout: Power-Up Edition</h1>
    <p>Move paddle with mouse or ← → keys<br>
       Catch falling power-ups: <b>Wide Paddle</b> (🟢) or <b>Multi-Ball</b> (🔵)</p>
    <button class="btn" id="startBtn">Start Game</button>
  </div>

  <div class="overlay hidden" id="gameOverScreen">
    <h1>Game Over</h1>
    <p>Final Score: <span id="finalScore">0</span></p>
    <button class="btn" id="restartBtn">Play Again</button>
  </div>

  <div class="overlay hidden" id="victoryScreen">
    <h1>Victory!</h1>
    <p>You cleared all bricks!</p>
    <button class="btn" id="victoryBtn">Play Again</button>
  </div>
</div>

<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;

// UI Elements
const startScreen = document.getElementById('startScreen');
const gameOverScreen = document.getElementById('gameOverScreen');
const victoryScreen = document.getElementById('victoryScreen');
const startBtn = document.getElementById('startBtn');
const restartBtn = document.getElementById('restartBtn');
const victoryBtn = document.getElementById('victoryBtn');
const livesCount = document.getElementById('livesCount');
const scoreCount = document.getElementById('scoreCount');
const finalScore = document.getElementById('finalScore');
const powerupInfo = document.getElementById('powerupInfo');

// Game State
let state = {
  paddle: { x: W/2 - 60, y: H - 40, w: 120, h: 15, speed: 10, baseW: 120, wideTimer: 0 },
  balls: [],
  bricks: [],
  particles: [],
  powerups: [],
  score: 0,
  lives: 3,
  running: false,
  keys: {},
  mouse: { x: W/2 }
};

// Brick Config
const brickRows = 6;
const brickCols = 10;
const brickW = 70;
const brickH = 25;
const brickPad = 8;
const brickOffsetTop = 60;
const brickOffsetLeft = (W - (brickCols * (brickW + brickPad) - brickPad)) / 2;

// Power-up Types
const POWERUP_TYPES = {
  WIDE: { color: '#00e676', icon: '🟢', name: 'Wide Paddle', duration: 10000 },
  MULTIBALL: { color: '#2196f3', icon: '🔵', name: 'Multi-Ball', duration: 0 }
};

// Utility
function rand(min, max) { return Math.random() * (max - min) + min; }

function createBricks() {
  state.bricks = [];
  const colors = ['#ff5252', '#ff9800', '#ffeb3b', '#4caf50', '#2196f3', '#9c27b0'];
  for (let r = 0; r < brickRows; r++) {
    for (let c = 0; c < brickCols; c++) {
      const isTough = Math.random() < 0.2; // 20% tough bricks
      const hasPowerup = Math.random() < 0.15; // 15% chance
      const powerupType = Math.random() < 0.5 ? 'WIDE' : 'MULTIBALL';
      
      state.bricks.push({
        x: brickOffsetLeft + c * (brickW + brickPad),
        y: brickOffsetTop + r * (brickH + brickPad),
        w: brickW,
        h: brickH,
        color: colors[r % colors.length],
        hits: isTough ? 2 : 1,
        maxHits: isTough ? 2 : 1,
        cracked: false,
        hasPowerup,
        powerupType
      });
    }
  }
}

function createBall(x, y) {
  const angle = rand(-Math.PI/4, -Math.PI/4 * 0.5); // upward angle
  const speed = 6;
  state.balls.push({
    x, y, r: 8,
    dx: Math.cos(angle) * speed * (Math.random() < 0.5 ? -1 : 1),
    dy: Math.sin(angle) * speed,
    active: true
  });
}

function resetBall() {
  state.balls = [];
  createBall(state.paddle.x + state.paddle.w/2, state.paddle.y - 15);
}

function spawnPowerup(x, y, type) {
  state.powerups.push({
    x, y, r: 12,
    type,
    dy: 3,
    ...POWERUP_TYPES[type]
  });
}

function createParticles(x, y, color, count = 12) {
  for (let i = 0; i < count; i++) {
    state.particles.push({
      x, y,
      dx: rand(-4, 4),
      dy: rand(-6, 2),
      life: 1,
      decay: rand(0.02, 0.05),
      size: rand(2, 5),
      color
    });
  }
}

function updatePaddle() {
  // Mouse control
  state.paddle.x = Math.max(0, Math.min(W - state.paddle.w, state.mouse.x - state.paddle.w/2));
  
  // Keyboard control
  if (state.keys['ArrowLeft'] || state.keys['a']) state.paddle.x -= state.paddle.speed;
  if (state.keys['ArrowRight'] || state.keys['d']) state.paddle.x += state.paddle.speed;
  state.paddle.x = Math.max(0, Math.min(W - state.paddle.w, state.paddle.x));
  
  // Wide paddle timer
  if (state.paddle.wideTimer > 0) {
    state.paddle.wideTimer -= 16;
    if (state.paddle.wideTimer <= 0) {
      state.paddle.w = state.paddle.baseW;
      state.paddle.wideTimer = 0;
      powerupInfo.textContent = '';
    }
  }
}

function updateBalls() {
  for (let i = state.balls.length - 1; i >= 0; i--) {
    const ball = state.balls[i];
    if (!ball.active) {
      state.balls.splice(i, 1);
      continue;
    }
    
    // Move ball
    ball.x += ball.dx;
    ball.y += ball.dy;
    
    // Wall collisions
    if (ball.x - ball.r < 0) { ball.x = ball.r; ball.dx = -ball.dx; }
    if (ball.x + ball.r > W) { ball.x = W - ball.r; ball.dx = -ball.dx; }
    if (ball.y - ball.r < 0) { ball.y = ball.r; ball.dy = -ball.dy; }
    
    // Bottom boundary (lose life)
    if (ball.y - ball.r > H) {
      ball.active = false;
      state.balls.splice(i, 1);
      if (state.balls.length === 0) {
        loseLife();
      }
      continue;
    }
    
    // Paddle collision
    const p = state.paddle;
    if (ball.y + ball.r > p.y && ball.y - ball.r < p.y + p.h &&
        ball.x + ball.r > p.x && ball.x - ball.r < p.x + p.w) {
      
      // Calculate hit position on paddle (-1 to 1)
      const hitPos = (ball.x - (p.x + p.w/2)) / (p.w/2);
      const angle = hitPos * (Math.PI/3); // Max 60 degrees
      
      ball.y = p.y - ball.r;
      ball.dx = Math.sin(angle) * 7;
      ball.dy = -Math.cos(angle) * 7;
    }
    
    // Brick collisions
    for (let j = state.bricks.length - 1; j >= 0; j--) {
      const brick = state.bricks[j];
      if (ball.x + ball.r > brick.x && ball.x - ball.r < brick.x + brick.w &&
          ball.y + ball.r > brick.y && ball.y - ball.r < brick.y + brick.h) {
        
        // Determine collision side
        const overlapX = Math.min(ball.x + ball.r - brick.x, brick.x + brick.w - ball.x + ball.r);
        const overlapY = Math.min(ball.y + ball.r - brick.y, brick.y + brick.h - ball.y + ball.r);
        
        if (overlapX < overlapY) {
          ball.dx = -ball.dx;
        } else {
          ball.dy = -ball.dy;
        }
        
        // Damage brick
        brick.hits--;
        if (brick.hits === 1 && brick.maxHits === 2) {
          brick.cracked = true;
        }
        
        if (brick.hits <= 0) {
          // Brick destroyed
          createParticles(brick.x + brick.w/2, brick.y + brick.h/2, brick.color, 15);
          state.score += brick.maxHits === 2 ? 20 : 10;
          scoreCount.textContent = state.score;
          
          // Spawn powerup
          if (brick.hasPowerup) {
            spawnPowerup(brick.x + brick.w/2, brick.y + brick.h/2, brick.powerupType);
          }
          
          state.bricks.splice(j, 1);
          
          // Check victory
          if (state.bricks.length === 0) {
            victory();
          }
        }
        break; // Only one collision per frame
      }
    }
  }
}

function updatePowerups() {
  for (let i = state.powerups.length - 1; i >= 0; i--) {
    const p = state.powerups[i];
    p.y += p.dy;
    
    // Paddle catch
    const paddle = state.paddle;
    if (p.y + p.r > paddle.y && p.y - p.r < paddle.y + paddle.h &&
        p.x + p.r > paddle.x && p.x - p.r < paddle.x + paddle.w) {
      
      if (p.type === 'WIDE') {
        state.paddle.w = state.paddle.baseW * 1.8;
        state.paddle.wideTimer = POWERUP_TYPES.WIDE.duration;
        powerupInfo.textContent = `Wide Paddle: ${(POWERUP_TYPES.WIDE.duration/1000).toFixed(0)}s`;
      } else if (p.type === 'MULTIBALL') {
        // Add 2 extra balls
        const currentBalls = state.balls.length;
        for (let b = 0; b < 2; b++) {
          const ball = state.balls[b % currentBalls];
          if (ball) {
            const newBall = {
              x: ball.x, y: ball.y, r: ball.r,
              dx: rand(-5, 5), dy: -Math.sqrt(25 - (state.balls[0].dx||0)**2),
              active: true
            };
            state.balls.push(newBall);
          }
        }
        powerupInfo.textContent = 'Multi-Ball Activated!';
        setTimeout(() => powerupInfo.textContent = '', 2000);
      }
      
      state.powerups.splice(i, 1);
      continue;
    }
    
    // Remove if off screen
    if (p.y - p.r > H) {
      state.powerups.splice(i, 1);
    }
  }
}

function updateParticles() {
  for (let i = state.particles.length - 1; i >= 0; i--) {
    const p = state.particles[i];
    p.x += p.dx;
    p.y += p.dy;
    p.dy += 0.2; // gravity
    p.life -= p.decay;
    
    if (p.life <= 0) {
      state.particles.splice(i, 1);
    }
  }
}

function loseLife() {
  state.lives--;
  livesCount.textContent = state.lives;
  if (state.lives <= 0) {
    gameOver();
  } else {
    resetBall();
  }
}

function drawPaddle() {
  const p = state.paddle;
  const gradient = ctx.createLinearGradient(p.x, p.y, p.x, p.y + p.h);
  gradient.addColorStop(0, '#00c6ff');
  gradient.addColorStop(1, '#0072ff');
  
  ctx.fillStyle = gradient;
  roundRect(ctx, p.x, p.y, p.w, p.h, 8);
  ctx.fill();
  
  // Highlight
  ctx.fillStyle = 'rgba(255,255,255,0.3)';
  roundRect(ctx, p.x + 2, p.y + 1, p.w - 4, 4, 3);
  ctx.fill();
}

function drawBalls() {
  for (const ball of state.balls) {
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
    ctx.fillStyle = '#fff';
    ctx.fill();
    
    // Shine
    ctx.beginPath();
    ctx.arc(ball.x - ball.r/3, ball.y - ball.r/3, ball.r/3, 0, Math.PI * 2);
    ctx.fillStyle = 'rgba(255,255,255,0.7)';
    ctx.fill();
  }
}

function drawBricks() {
  for (const brick of state.bricks) {
    // Base color
    ctx.fillStyle = brick.color;
    roundRect(ctx, brick.x, brick.y, brick.w, brick.h, 4);
    ctx.fill();
    
    // Crack effect for tough bricks
    if (brick.cracked) {
      ctx.strokeStyle = 'rgba(0,0,0,0.5)';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(brick.x + 5, brick.y + 2);
      ctx.lineTo(brick.x + brick.w/2, brick.y + brick.h/2);
      ctx.lineTo(brick.x + brick.w - 5, brick.y + 2);
      ctx.stroke();
      
      ctx.beginPath();
      ctx.moveTo(brick.x + brick.w/2, brick.y + brick.h/2);
      ctx.lineTo(brick.x + brick.w/2, brick.y + brick.h - 2);
      ctx.stroke();
    }
    
    // Highlight
    ctx.fillStyle = 'rgba(255,255,255,0.2)';
    roundRect(ctx, brick.x + 1, brick.y + 1, brick.w - 2, 4, 2);
    ctx.fill();
    
    // Power-up icon
    if (brick.hasPowerup) {
      ctx.font = '16px Arial';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(brick.powerupType === 'WIDE' ? '🟢' : '🔵', 
                   brick.x + brick.w/2, brick.y + brick.h/2);
    }
  }
}

function drawPowerups() {
  for (const p of state.powerups) {
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
    ctx.fillStyle = p.color;
    ctx.fill();
    
    ctx.font = '16px Arial';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(p.icon, p.x, p.y);
    
    // Glow
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.r + 4, 0, Math.PI * 2);
    ctx.strokeStyle = p.color;
    ctx.lineWidth = 2;
    ctx.globalAlpha = 0.6;
    ctx.stroke();
    ctx.globalAlpha = 1;
  }
}

function drawParticles() {
  for (const p of state.particles) {
    ctx.globalAlpha = p.life;
    ctx.fillStyle = p.color;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1;
}

function roundRect(ctx, x, y, w, h, r) {
  ctx.beginPath();
  ctx.moveTo(x + r, y);
  ctx.arcTo(x + w, y, x + w, y + h, r);
  ctx.arcTo(x + w, y + h, x, y + h, r);
  ctx.arcTo(x, y + h, x, y, r);
  ctx.arcTo(x, y, x + w, y, r);
  ctx.closePath();
}

function render() {
  ctx.clearRect(0, 0, W, H);
  
  drawBricks();
  drawPowerups();
  drawPaddle();
  drawBalls();
  drawParticles();
}

function gameLoop() {
  if (!state.running) return;
  
  updatePaddle();
  updateBalls();
  updatePowerups();
  updateParticles();
  render();
  
  requestAnimationFrame(gameLoop);
}

function startGame() {
  state = {
    paddle: { x: W/2 - 60, y: H - 40, w: 120, h: 15, speed: 10, baseW: 120, wideTimer: 0 },
    balls: [],
    bricks: [],
    particles: [],
    powerups: [],
    score: 0,
    lives: 3,
    running: true,
    keys: {},
    mouse: { x: W/2 }
  };
  
  scoreCount.textContent = '0';
  livesCount.textContent = '3';
  powerupInfo.textContent = '';
  
  createBricks();
  resetBall();
  
  startScreen.classList.add('hidden');
  gameOverScreen.classList.add('hidden');
  victoryScreen.classList.add('hidden');
  
  gameLoop();
}

function gameOver() {
  state.running = false;
  finalScore.textContent = state.score;
  gameOverScreen.classList.remove('hidden');
}

function victory() {
  state.running = false;
  victoryScreen.classList.remove('hidden');
}

// Event Listeners
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
victoryBtn.addEventListener('click', startGame);

// Keyboard
window.addEventListener('keydown', (e) => {
  state.keys[e.key] = true;
  if (state.running && (e.key === ' ' || e.key === 'Enter')) {
    // Pause/resume could go here
  }
});

window.addEventListener('keyup', (e) => {
  state.keys[e.key] = false;
});

// Mouse
canvas.addEventListener('mousemove', (e) => {
  const rect = canvas.getBoundingClientRect();
  state.mouse.x = (e.clientX - rect.left) * (W / rect.width);
});

// Touch support
canvas.addEventListener('touchmove', (e) => {
  e.preventDefault();
  const touch = e.touches[0];
  const rect = canvas.getBoundingClientRect();
  state.mouse.x = (touch.clientX - rect.left) * (W / rect.width);
}, { passive: false });

// Prevent arrow key scrolling
window.addEventListener('keydown', (e) => {
  if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', ' '].includes(e.key)) {
    e.preventDefault();
  }
}, { passive: false });
</script>
</body>
</html>