{
  "kind": "code",
  "model": "gemini-4-argon",
  "generatedAt": "2026-10-21T05:28:16.943Z",
  "prompt": "Use the attached blue ceramic cup image as inspiration for a tiny 3D-style cup collection game. Move a player with arrow keys or touch buttons, collect cups, show score and restart. Use a perspective ground plane and projected 3D geometry with canvas. No external resources. Return JSON {title,content:\"complete self-contained HTML document\"}. Include only inline scripts, CSS and inline SVG or canvas, no external libraries, URLs, network access or imports. Make the example polished, responsive, keyboard and touch usable. Support prefers-reduced-motion and pause animation when document.hidden. Include meaningful accessible text. Never claim tests were run. Keep implementation concise under 18000 characters.",
  "sourceImage": "source-cup.png",
  "scope": "Direct real Vertex generation with production schema validation and JavaScript syntax checks. Not an account, billing or browser interaction test.",
  "usage": {
    "promptTokenCount": 1344,
    "candidatesTokenCount": 10812,
    "totalTokenCount": 12156,
    "trafficType": "ON_DEMAND",
    "promptTokensDetails": [
      {
        "modality": "TEXT",
        "tokenCount": 244
      },
      {
        "modality": "IMAGE",
        "tokenCount": 1100
      }
    ],
    "candidatesTokensDetails": [
      {
        "modality": "TEXT",
        "tokenCount": 10812
      }
    ]
  },
  "output": {
    "title": "Cobalt Kiln: 3D Ceramic Cup Collector",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">\n<title>Cobalt Kiln: 3D Ceramic Cup Collector</title>\n<style>\n  :root {\n    --bg-top: #dcdfe2;\n    --bg-horizon: #f3f5f7;\n    --cobalt-dark: #0c2647;\n    --cobalt-mid: #1d4e89;\n    --cobalt-light: #4a80b8;\n    --clay-rim: #786d5f;\n    --text: #1c252c;\n    --surface: rgba(255, 255, 255, 0.85);\n    --border: rgba(28, 37, 44, 0.12);\n  }\n  * { box-sizing: border-box; user-select: none; -webkit-user-select: none; }\n  html, body {\n    margin: 0; padding: 0; width: 100%; height: 100%;\n    overflow: hidden; background: var(--bg-horizon);\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n    color: var(--text);\n  }\n  .sr-only {\n    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;\n    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;\n  }\n  #game-container {\n    position: relative; width: 100%; height: 100%; display: flex;\n    flex-direction: column; justify-content: space-between;\n  }\n  canvas {\n    position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1;\n    display: block;\n  }\n  .hud {\n    position: relative; z-index: 2; display: flex; justify-content: space-between;\n    align-items: center; padding: 16px 24px; pointer-events: none; flex-wrap: wrap; gap: 12px;\n  }\n  .brand {\n    background: var(--surface); backdrop-filter: blur(8px);\n    padding: 10px 18px; border-radius: 999px; border: 1px solid var(--border);\n    box-shadow: 0 4px 16px rgba(12, 38, 71, 0.06);\n    display: flex; align-items: center; gap: 10px; pointer-events: auto;\n  }\n  .cup-icon {\n    width: 22px; height: 22px; border-radius: 50%;\n    background: radial-gradient(circle at 35% 35%, var(--cobalt-light), var(--cobalt-dark));\n    border: 2.5px solid var(--clay-rim);\n    box-shadow: inset 0 2px 4px rgba(0,0,0,0.4);\n  }\n  .brand h1 {\n    font-size: 1rem; font-weight: 600; margin: 0; letter-spacing: -0.01em;\n  }\n  .stats {\n    display: flex; gap: 12px; align-items: center; pointer-events: auto;\n  }\n  .pill {\n    background: var(--surface); backdrop-filter: blur(8px);\n    padding: 8px 16px; border-radius: 999px; border: 1px solid var(--border);\n    box-shadow: 0 4px 16px rgba(12, 38, 71, 0.06);\n    font-size: 0.95rem; font-weight: 600; display: flex; gap: 6px; align-items: center;\n    font-variant-numeric: tabular-nums;\n  }\n  .pill span.label { font-weight: 400; opacity: 0.7; font-size: 0.85rem; }\n  button.btn {\n    background: var(--cobalt-mid); color: #fff; border: none;\n    padding: 9px 18px; border-radius: 999px; font-weight: 600; font-size: 0.9rem;\n    cursor: pointer; box-shadow: 0 4px 12px rgba(29, 78, 137, 0.25);\n    transition: transform 0.12s ease, background 0.15s ease;\n    border: 1px solid rgba(255,255,255,0.15);\n  }\n  button.btn:hover { background: var(--cobalt-dark); transform: translateY(-1px); }\n  button.btn:active { transform: translateY(1px); }\n  button.btn:focus-visible { outline: 3px solid var(--cobalt-light); outline-offset: 2px; }\n\n  .controls-area {\n    position: relative; z-index: 2; display: flex; justify-content: space-between;\n    align-items: flex-end; padding: 20px 24px; pointer-events: none;\n  }\n  .instructions {\n    background: var(--surface); backdrop-filter: blur(8px);\n    padding: 10px 16px; border-radius: 14px; border: 1px solid var(--border);\n    font-size: 0.85rem; line-height: 1.4; max-width: 280px; pointer-events: auto;\n    box-shadow: 0 4px 16px rgba(0,0,0,0.04);\n  }\n  .dpad {\n    display: grid; grid-template-columns: repeat(3, 52px); grid-template-rows: repeat(2, 52px);\n    gap: 8px; pointer-events: auto; touch-action: manipulation;\n  }\n  .dpad-btn {\n    background: var(--surface); backdrop-filter: blur(8px);\n    border: 1px solid var(--border); border-radius: 14px;\n    display: flex; align-items: center; justify-content: center;\n    color: var(--cobalt-dark); cursor: pointer;\n    box-shadow: 0 4px 12px rgba(12, 38, 71, 0.08);\n    transition: background 0.1s, transform 0.08s;\n    padding: 0;\n  }\n  .dpad-btn:active, .dpad-btn.active {\n    background: var(--cobalt-mid); color: #fff; transform: scale(0.94);\n  }\n  .dpad-btn svg { width: 24px; height: 24px; fill: currentColor; pointer-events: none; }\n  .dpad-up { grid-column: 2; grid-row: 1; }\n  .dpad-left { grid-column: 1; grid-row: 2; }\n  .dpad-down { grid-column: 2; grid-row: 2; }\n  .dpad-right { grid-column: 3; grid-row: 2; }\n\n  #overlay {\n    position: absolute; inset: 0; z-index: 5; background: rgba(243, 245, 247, 0.78);\n    backdrop-filter: blur(6px); display: flex; align-items: center; justify-content: center;\n    padding: 20px; opacity: 0; pointer-events: none; transition: opacity 0.25s ease;\n  }\n  #overlay.visible { opacity: 1; pointer-events: auto; }\n  .modal {\n    background: #fff; border: 1px solid var(--border); border-radius: 24px;\n    padding: 32px; max-width: 380px; width: 100%; text-align: center;\n    box-shadow: 0 20px 40px rgba(12, 38, 71, 0.12);\n  }\n  .modal h2 { margin: 0 0 8px; font-size: 1.5rem; color: var(--cobalt-dark); }\n  .modal p { margin: 0 0 24px; color: #52606d; font-size: 0.95rem; line-height: 1.5; }\n  .modal .score-big {\n    font-size: 2.8rem; font-weight: 800; color: var(--cobalt-mid);\n    margin: 12px 0; display: block;\n  }\n\n  @media (max-width: 600px) {\n    .hud { padding: 12px 16px; }\n    .brand h1 { font-size: 0.9rem; }\n    .instructions { display: none; }\n    .controls-area { justify-content: center; padding-bottom: 24px; }\n    .dpad { grid-template-columns: repeat(3, 60px); grid-template-rows: repeat(2, 56px); }\n  }\n</style>\n</head>\n<body>\n\n<div id=\"game-container\">\n  <header class=\"hud\" role=\"banner\">\n    <div class=\"brand\">\n      <div class=\"cup-icon\" aria-hidden=\"true\"></div>\n      <h1>Cobalt Kiln</h1>\n    </div>\n    <div class=\"stats\" role=\"region\" aria-label=\"Game Score and Controls\">\n      <div class=\"pill\" aria-live=\"polite\">\n        <span class=\"label\">Cups</span>\n        <span id=\"score-val\">0 / 12</span>\n      </div>\n      <div class=\"pill\">\n        <span class=\"label\">Time</span>\n        <span id=\"time-val\">0.0s</span>\n      </div>\n      <button type=\"button\" class=\"btn\" id=\"restart-btn\" aria-label=\"Restart Game\">Restart</button>\n    </div>\n  </header>\n\n  <div id=\"sr-status\" class=\"sr-only\" aria-live=\"assertive\"></div>\n\n  <main>\n    <canvas id=\"scene\" aria-label=\"3D perspective tabletop ceramic cup collection game\"></canvas>\n  </main>\n\n  <footer class=\"controls-area\">\n    <div class=\"instructions\">\n      <strong>How to play:</strong> Guide the warm wooden coaster across the studio table using <strong>Arrow Keys / WASD</strong> or touch buttons to gather all 12 glazed stoneware cups.\n    </div>\n    <div class=\"dpad\" role=\"group\" aria-label=\"Directional movement controls\">\n      <button type=\"button\" class=\"dpad-btn dpad-up\" data-dir=\"up\" aria-label=\"Move Forward\">\n        <svg viewBox=\"0 0 24 24\"><path d=\"M12 5l-7 7h4.5v7h5v-7H19l-7-7z\"/></svg>\n      </button>\n      <button type=\"button\" class=\"dpad-btn dpad-left\" data-dir=\"left\" aria-label=\"Move Left\">\n        <svg viewBox=\"0 0 24 24\"><path d=\"M5 12l7-7v4.5h7v5h-7V19l-7-7z\"/></svg>\n      </button>\n      <button type=\"button\" class=\"dpad-btn dpad-down\" data-dir=\"down\" aria-label=\"Move Backward\">\n        <svg viewBox=\"0 0 24 24\"><path d=\"M12 19l7-7h-4.5V5h-5v7H5l7 7z\"/></svg>\n      </button>\n      <button type=\"button\" class=\"dpad-btn dpad-right\" data-dir=\"right\" aria-label=\"Move Right\">\n        <svg viewBox=\"0 0 24 24\"><path d=\"M19 12l-7 7v-4.5H5v-5h7V5l7 7z\"/></svg>\n      </button>\n    </div>\n  </footer>\n\n  <div id=\"overlay\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"modal-title\">\n    <div class=\"modal\">\n      <h2 id=\"modal-title\">Collection Complete!</h2>\n      <span class=\"score-big\" id=\"final-time\">14.2s</span>\n      <p id=\"modal-desc\">All 12 artisanal cobalt stoneware cups have been safely gathered onto the studio tray.</p>\n      <button type=\"button\" class=\"btn\" id=\"play-again-btn\">New Kiln Batch</button>\n    </div>\n  </div>\n</div>\n\n<script>\n(function() {\n  const canvas = document.getElementById('scene');\n  const ctx = canvas.getContext('2d');\n  const scoreEl = document.getElementById('score-val');\n  const timeEl = document.getElementById('time-val');\n  const srStatus = document.getElementById('sr-status');\n  const overlay = document.getElementById('overlay');\n  const finalTimeEl = document.getElementById('final-time');\n  const restartBtn = document.getElementById('restart-btn');\n  const playAgainBtn = document.getElementById('play-again-btn');\n\n  const reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');\n  let prefersReducedMotion = reducedMotionQuery.matches;\n  reducedMotionQuery.addEventListener('change', (e) => { prefersReducedMotion = e.matches; });\n\n  // World and Camera constants\n  const TOTAL_CUPS = 12;\n  const WORLD_X = 520;\n  const WORLD_Z_MIN = 180;\n  const WORLD_Z_MAX = 1180;\n  const CAM_Y = 230;\n  const CAM_Z = -180;\n  const FOV = 620;\n\n  let width = 0, height = 0, horizonY = 0;\n  let score = 0;\n  let elapsedTime = 0;\n  let gameActive = true;\n  let lastTime = performance.now();\n\n  const keys = { up: false, down: false, left: false, right: false };\n\n  const player = {\n    x: 0,\n    z: 360,\n    vx: 0,\n    vz: 0,\n    radius: 42,\n    stack: 0\n  };\n\n  let cups = [];\n  let ripples = [];\n\n  function resize() {\n    const dpr = Math.min(window.devicePixelRatio || 1, 2);\n    width = window.innerWidth;\n    height = window.innerHeight;\n    canvas.width = width * dpr;\n    canvas.height = height * dpr;\n    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n    horizonY = height * 0.30;\n  }\n  window.addEventListener('resize', resize);\n  resize();\n\n  // Project 3D world (x, y, z) to 2D canvas (sx, sy, scale)\n  // y is height above the studio table plane (y=0)\n  function project(x, y, z) {\n    const dz = Math.max(10, z - CAM_Z);\n    const scale = FOV / dz;\n    return {\n      x: width * 0.5 + x * scale,\n      y: horizonY + (CAM_Y - y) * scale,\n      scale: scale,\n      dz: dz\n    };\n  }\n\n  function spawnCups() {\n    cups = [];\n    for (let i = 0; i < TOTAL_CUPS; i++) {\n      let placed = false;\n      let attempts = 0;\n      while (!placed && attempts < 50) {\n        attempts++;\n        const cx = (Math.random() - 0.5) * (WORLD_X * 1.75);\n        const cz = WORLD_Z_MIN + 80 + Math.random() * (WORLD_Z_MAX - WORLD_Z_MIN - 140);\n        const distToPlayer = Math.hypot(cx - player.x, cz - player.z);\n        if (distToPlayer < 120) continue;\n        let overlap = false;\n        for (const other of cups) {\n          if (Math.hypot(cx - other.x, cz - other.z) < 105) { overlap = true; break; }\n        }\n        if (!overlap || attempts === 50) {\n          cups.push({\n            x: cx,\n            z: cz,\n            angle: (Math.random() - 0.5) * 0.9, // Handle orientation\n            bobPhase: Math.random() * Math.PI * 2,\n            collected: false,\n            collectAnim: 0\n          });\n          placed = true;\n        }\n      }\n    }\n  }\n\n  function resetGame() {\n    score = 0;\n    elapsedTime = 0;\n    gameActive = true;\n    player.x = 0;\n    player.z = 340;\n    player.vx = 0;\n    player.vz = 0;\n    player.stack = 0;\n    ripples = [];\n    spawnCups();\n    updateHUD();\n    overlay.classList.remove('visible');\n    srStatus.textContent = 'New game started. Collect 12 ceramic cups.';\n    lastTime = performance.now();\n  }\n\n  function updateHUD() {\n    scoreEl.textContent = `${score} / ${TOTAL_CUPS}`;\n    timeEl.textContent = `${elapsedTime.toFixed(1)}s`;\n  }\n\n  // Input listeners\n  const keyMap = {\n    ArrowUp: 'up', KeyW: 'up',\n    ArrowDown: 'down', KeyS: 'down',\n    ArrowLeft: 'left', KeyA: 'left',\n    ArrowRight: 'right', KeyD: 'right'\n  };\n\n  window.addEventListener('keydown', (e) => {\n    if (keyMap[e.code]) {\n      keys[keyMap[e.code]] = true;\n      e.preventDefault();\n    }\n  });\n  window.addEventListener('keyup', (e) => {\n    if (keyMap[e.code]) {\n      keys[keyMap[e.code]] = false;\n    }\n  });\n\n  // Touch / Pointer D-Pad\n  const dpadBtns = document.querySelectorAll('.dpad-btn');\n  dpadBtns.forEach(btn => {\n    const dir = btn.dataset.dir;\n    const start = (e) => { e.preventDefault(); keys[dir] = true; btn.classList.add('active'); };\n    const end = (e) => { e.preventDefault(); keys[dir] = false; btn.classList.remove('active'); };\n    btn.addEventListener('pointerdown', start);\n    btn.addEventListener('pointerup', end);\n    btn.addEventListener('pointerleave', end);\n    btn.addEventListener('pointercancel', end);\n  });\n\n  restartBtn.addEventListener('click', resetGame);\n  playAgainBtn.addEventListener('click', resetGame);\n\n  // Draw the studio backdrop and perspective tabletop grid\n  function drawStudioTable() {\n    // Backdrop wall gradient (matches the soft grey-white studio photography)\n    const wallGrad = ctx.createLinearGradient(0, 0, 0, horizonY + 30);\n    wallGrad.addColorStop(0, '#cfd4d9');\n    wallGrad.addColorStop(1, '#eceff2');\n    ctx.fillStyle = wallGrad;\n    ctx.fillRect(0, 0, width, horizonY + 2);\n\n    // Tabletop surface gradient\n    const tableGrad = ctx.createLinearGradient(0, horizonY, 0, height);\n    tableGrad.addColorStop(0, '#f4f6f8');\n    tableGrad.addColorStop(0.25, '#eef1f4');\n    tableGrad.addColorStop(1, '#dfe4e9');\n    ctx.fillStyle = tableGrad;\n    ctx.fillRect(0, horizonY, width, height - horizonY);\n\n    // Soft studio horizon blend\n    const seamGrad = ctx.createLinearGradient(0, horizonY - 18, 0, horizonY + 28);\n    seamGrad.addColorStop(0, 'rgba(236, 239, 242, 0)');\n    seamGrad.addColorStop(0.5, 'rgba(244, 246, 248, 0.95)');\n    seamGrad.addColorStop(1, 'rgba(244, 246, 248, 0)');\n    ctx.fillStyle = seamGrad;\n    ctx.fillRect(0, horizonY - 18, width, 46);\n\n    // Perspective tabletop tile lines\n    ctx.lineWidth = 1;\n    // Longitudinal lines\n    for (let x = -WORLD_X; x <= WORLD_X; x += 130) {\n      const pNear = project(x, 0, WORLD_Z_MIN - 60);\n      const pFar = project(x, 0, WORLD_Z_MAX);\n      ctx.strokeStyle = 'rgba(28, 37, 44, 0.055)';\n      ctx.beginPath();\n      ctx.moveTo(pNear.x, pNear.y);\n      ctx.lineTo(pFar.x, pFar.y);\n      ctx.stroke();\n    }\n    // Latitudinal depth lines\n    for (let z = WORLD_Z_MIN; z <= WORLD_Z_MAX; z += 125) {\n      const pLeft = project(-WORLD_X, 0, z);\n      const pRight = project(WORLD_X, 0, z);\n      const alpha = Math.max(0.015, 0.07 * (1 - (z - WORLD_Z_MIN) / (WORLD_Z_MAX - WORLD_Z_MIN)));\n      ctx.strokeStyle = `rgba(28, 37, 44, ${alpha.toFixed(3)})`;\n      ctx.beginPath();\n      ctx.moveTo(pLeft.x, pLeft.y);\n      ctx.lineTo(pRight.x, pRight.y);\n      ctx.stroke();\n    }\n\n    // Studio table boundary border\n    const c1 = project(-WORLD_X, 0, WORLD_Z_MIN);\n    const c2 = project(WORLD_X, 0, WORLD_Z_MIN);\n    const c3 = project(WORLD_X, 0, WORLD_Z_MAX);\n    const c4 = project(-WORLD_X, 0, WORLD_Z_MAX);\n    ctx.strokeStyle = 'rgba(29, 78, 137, 0.14)';\n    ctx.lineWidth = 2;\n    ctx.beginPath();\n    ctx.moveTo(c1.x, c1.y);\n    ctx.lineTo(c2.x, c2.y);\n    ctx.lineTo(c3.x, c3.y);\n    ctx.lineTo(c4.x, c4.y);\n    ctx.closePath();\n    ctx.stroke();\n  }\n\n  // Draw a 3D projected Blue Ceramic Cup inspired by the reference photo\n  function drawCeramicCup(x, y, z, scaleFactor, handleAngle, bobOffset) {\n    const p = project(x, y + bobOffset, z);\n    const pGround = project(x, 0, z);\n    const s = p.scale * scaleFactor;\n    if (s <= 0.02) return;\n\n    const rx = 30 * s;        // Cup top radius\n    const ry = 10.5 * s;      // Perspective ellipse vertical radius\n    const h = 42 * s;         // Cup height\n    const baseRx = 18 * s;    // Tapered ceramic foot radius\n    const baseRy = 6.5 * s;\n\n    const topY = p.y - h;\n    const botY = p.y;\n\n    ctx.save();\n\n    // 1. Soft directional studio shadow to the left (matching reference image)\n    const shadowStretch = Math.max(0.4, 1 - (y + bobOffset) / 90);\n    const shGrad = ctx.createRadialGradient(\n      pGround.x - rx * 0.65, pGround.y + ry * 0.15, rx * 0.1,\n      pGround.x - rx * 0.55, pGround.y + ry * 0.15, rx * 1.9 * shadowStretch\n    );\n    shGrad.addColorStop(0, 'rgba(18, 28, 38, 0.26)');\n    shGrad.addColorStop(0.55, 'rgba(18, 28, 38, 0.11)');\n    shGrad.addColorStop(1, 'rgba(18, 28, 38, 0)');\n    ctx.fillStyle = shGrad;\n    ctx.beginPath();\n    ctx.ellipse(pGround.x - rx * 0.55, pGround.y + ry * 0.15, rx * 1.85 * shadowStretch, ry * 1.35 * shadowStretch, 0, 0, Math.PI * 2);\n    ctx.fill();\n\n    // Ambient occlusion directly beneath the stoneware foot\n    ctx.fillStyle = 'rgba(10, 18, 28, 0.38)';\n    ctx.beginPath();\n    ctx.ellipse(pGround.x, pGround.y, baseRx * 1.05, baseRy * 1.05, 0, 0, Math.PI * 2);\n    ctx.fill();\n\n    // 2. Ear-shaped ceramic handle on the right side\n    const hx = p.x + Math.cos(handleAngle) * (rx * 0.88);\n    const hyTop = topY + h * 0.24;\n    const hyBot = topY + h * 0.76;\n    const handleOut = 17 * s;\n\n    ctx.lineCap = 'round';\n    // Outer earth-toned rim of handle\n    ctx.beginPath();\n    ctx.moveTo(hx, hyTop);\n    ctx.bezierCurveTo(hx + handleOut, hyTop - 3 * s, hx + handleOut, hyBot + 3 * s, hx - 3 * s, hyBot);\n    ctx.strokeStyle = '#62594e';\n    ctx.lineWidth = 6.5 * s;\n    ctx.stroke();\n\n    // Cobalt glaze along handle\n    ctx.strokeStyle = '#1b497d';\n    ctx.lineWidth = 4.6 * s;\n    ctx.stroke();\n\n    // Specular glaze highlight on handle\n    ctx.strokeStyle = 'rgba(165, 205, 245, 0.55)';\n    ctx.lineWidth = 1.6 * s;\n    ctx.stroke();\n\n    // 3. Unglazed dark stoneware foot ring at bottom\n    ctx.fillStyle = '#3d372e';\n    ctx.beginPath();\n    ctx.ellipse(p.x, botY + 1.5 * s, baseRx * 0.94, baseRy * 0.94, 0, 0, Math.PI * 2);\n    ctx.fill();\n\n    // 4. Rounded bowl body (cobalt reactive glaze gradient)\n    ctx.beginPath();\n    ctx.moveTo(p.x - rx, topY);\n    ctx.bezierCurveTo(\n      p.x - rx * 1.05, topY + h * 0.58,\n      p.x - baseRx * 1.32, botY,\n      p.x - baseRx, botY\n    );\n    ctx.ellipse(p.x, botY, baseRx, baseRy, 0, Math.PI, 0, true);\n    ctx.bezierCurveTo(\n      p.x + baseRx * 1.32, botY,\n      p.x + rx * 1.05, topY + h * 0.58,\n      p.x + rx, topY\n    );\n    ctx.closePath();\n\n    const bodyGrad = ctx.createRadialGradient(\n      p.x + rx * 0.28, topY + h * 0.42, rx * 0.12,\n      p.x - rx * 0.15, topY + h * 0.55, rx * 1.45\n    );\n    bodyGrad.addColorStop(0, '#548cc4');   // Bright mottled ocean blue\n    bodyGrad.addColorStop(0.45, '#255994'); // Mid reactive cobalt\n    bodyGrad.addColorStop(0.78, '#13335c'); // Deep indigo shadow\n    bodyGrad.addColorStop(1, '#1c242b');    // Dark iron-rich base glaze\n    ctx.fillStyle = bodyGrad;\n    ctx.fill();\n\n    // Subtle vertical glaze drip band near the upper rim\n    ctx.save();\n    ctx.clip();\n    const rimDripGrad = ctx.createLinearGradient(0, topY, 0, topY + h * 0.36);\n    rimDripGrad.addColorStop(0, 'rgba(6, 26, 56, 0.75)');\n    rimDripGrad.addColorStop(0.6, 'rgba(18, 68, 128, 0.35)');\n    rimDripGrad.addColorStop(1, 'rgba(40, 100, 160, 0)');\n    ctx.fillStyle = rimDripGrad;\n    ctx.fillRect(p.x - rx * 1.2, topY, rx * 2.4, h * 0.4);\n\n    // Earthy bronze/charcoal glaze pooling near the bottom foot\n    const footPoolGrad = ctx.createLinearGradient(0, botY - h * 0.25, 0, botY + baseRy);\n    footPoolGrad.addColorStop(0, 'rgba(45, 42, 34, 0)');\n    footPoolGrad.addColorStop(1, 'rgba(38, 34, 26, 0.72)');\n    ctx.fillStyle = footPoolGrad;\n    ctx.fillRect(p.x - rx, botY - h * 0.25, rx * 2, h * 0.35);\n\n    // Soft studio window reflection on the left curve\n    ctx.fillStyle = 'rgba(215, 235, 255, 0.18)';\n    ctx.beginPath();\n    ctx.ellipse(p.x - rx * 0.52, topY + h * 0.44, rx * 0.16, h * 0.26, -0.12, 0, Math.PI * 2);\n    ctx.fill();\n    ctx.restore();\n\n    // 5. Cup Interior (deep glossy midnight blue)\n    ctx.beginPath();\n    ctx.ellipse(p.x, topY, rx, ry, 0, 0, Math.PI * 2);\n    const insideGrad = ctx.createLinearGradient(p.x - rx, topY - ry, p.x + rx, topY + ry);\n    insideGrad.addColorStop(0, '#061529');\n    insideGrad.addColorStop(0.55, '#0e2c52');\n    insideGrad.addColorStop(1, '#26588c');\n    ctx.fillStyle = insideGrad;\n    ctx.fill();\n\n    // 6. Characteristic olive-brown raw stoneware lip\n    ctx.beginPath();\n    ctx.ellipse(p.x, topY, rx, ry, 0, 0, Math.PI * 2);\n    ctx.strokeStyle = '#7e7362';\n    ctx.lineWidth = 2.8 * s;\n    ctx.stroke();\n\n    // Glossy white highlight along the front ceramic lip\n    ctx.beginPath();\n    ctx.ellipse(p.x, topY, rx * 0.98, ry * 0.98, 0, 0.15 * Math.PI, 0.85 * Math.PI);\n    ctx.strokeStyle = 'rgba(240, 248, 255, 0.65)';\n    ctx.lineWidth = 1.3 * s;\n    ctx.stroke();\n\n    ctx.restore();\n  }\n\n  // Draw the player's warm wooden coaster / tray and stacked mini cups\n  function drawPlayerTray() {\n    const p = project(player.x, 0, player.z);\n    const s = p.scale;\n    const rx = player.radius * s;\n    const ry = rx * 0.35;\n    const thickness = 9 * s;\n\n    ctx.save();\n\n    // Soft shadow under coaster\n    const shGrad = ctx.createRadialGradient(p.x - rx * 0.2, p.y + 3 * s, rx * 0.2, p.x, p.y + 3 * s, rx * 1.35);\n    shGrad.addColorStop(0, 'rgba(12, 38, 71, 0.28)');\n    shGrad.addColorStop(1, 'rgba(12, 38, 71, 0)');\n    ctx.fillStyle = shGrad;\n    ctx.beginPath();\n    ctx.ellipse(p.x, p.y + 3 * s, rx * 1.35, ry * 1.35, 0, 0, Math.PI * 2);\n    ctx.fill();\n\n    // Coaster side edge\n    ctx.fillStyle = '#6e5239';\n    ctx.beginPath();\n    ctx.ellipse(p.x, p.y, rx, ry, 0, 0, Math.PI);\n    ctx.lineTo(p.x - rx, p.y - thickness);\n    ctx.ellipse(p.x, p.y - thickness, rx, ry, 0, Math.PI, 0, true);\n    ctx.closePath();\n    ctx.fill();\n\n    // Coaster top surface\n    const topGrad = ctx.createLinearGradient(p.x - rx, p.y - thickness - ry, p.x + rx, p.y - thickness + ry);\n    topGrad.addColorStop(0, '#a68262');\n    topGrad.addColorStop(1, '#846447');\n    ctx.fillStyle = topGrad;\n    ctx.beginPath();\n    ctx.ellipse(p.x, p.y - thickness, rx, ry, 0, 0, Math.PI * 2);\n    ctx.fill();\n\n    // Recessed inner groove on coaster\n    ctx.strokeStyle = 'rgba(55, 38, 22, 0.45)';\n    ctx.lineWidth = 2 * s;\n    ctx.beginPath();\n    ctx.ellipse(p.x, p.y - thickness, rx * 0.78, ry * 0.78, 0, 0, Math.PI * 2);\n    ctx.stroke();\n\n    ctx.restore();\n\n    // Draw collected ceramic cup sitting on the coaster\n    if (player.stack > 0) {\n      drawCeramicCup(player.x, 9, player.z, 0.65, 0.2, 0);\n      // Badge counter on player tray if more than 1 collected\n      if (player.stack > 1) {\n        const badgeP = project(player.x + 22, 42, player.z);\n        ctx.save();\n        ctx.fillStyle = '#0c2647';\n        ctx.beginPath();\n        ctx.arc(badgeP.x, badgeP.y, 11 * s, 0, Math.PI * 2);\n        ctx.fill();\n        ctx.strokeStyle = '#fff';\n        ctx.lineWidth = 1.5 * s;\n        ctx.stroke();\n        ctx.fillStyle = '#fff';\n        ctx.font = `bold ${Math.max(9, Math.round(11 * s))}px sans-serif`;\n        ctx.textAlign = 'center';\n        ctx.textBaseline = 'middle';\n        ctx.fillText(`×${player.stack}`, badgeP.x, badgeP.y);\n        ctx.restore();\n      }\n    }\n  }\n\n  function drawRipples(dt) {\n    for (let i = ripples.length - 1; i >= 0; i--) {\n      const r = ripples[i];\n      r.progress += dt * 2.2;\n      if (r.progress >= 1) {\n        ripples.splice(i, 1);\n        continue;\n      }\n      const p = project(r.x, 2, r.z);\n      const radius = (25 + r.progress * 55) * p.scale;\n      const alpha = (1 - r.progress) * 0.55;\n      ctx.save();\n      ctx.strokeStyle = `rgba(37, 89, 148, ${alpha.toFixed(3)})`;\n      ctx.lineWidth = 2.5 * p.scale;\n      ctx.beginPath();\n      ctx.ellipse(p.x, p.y, radius, radius * 0.35, 0, 0, Math.PI * 2);\n      ctx.stroke();\n      ctx.restore();\n    }\n  }\n\n  function update(dt) {\n    if (!gameActive) return;\n\n    elapsedTime += dt;\n    timeEl.textContent = `${elapsedTime.toFixed(1)}s`;\n\n    // Player movement physics\n    const accel = 1850;\n    const damping = 6.5;\n    let ax = 0, az = 0;\n    if (keys.left) ax -= 1;\n    if (keys.right) ax += 1;\n    if (keys.up) az += 1;\n    if (keys.down) az -= 1;\n\n    if (ax !== 0 && az !== 0) {\n      const inv = 1 / Math.SQRT2;\n      ax *= inv;\n      az *= inv;\n    }\n\n    player.vx += (ax * accel - player.vx * damping) * dt;\n    player.vz += (az * accel - player.vz * damping) * dt;\n\n    player.x += player.vx * dt;\n    player.z += player.vz * dt;\n\n    // Clamp to studio table boundaries\n    const pad = player.radius;\n    if (player.x < -WORLD_X + pad) { player.x = -WORLD_X + pad; player.vx = 0; }\n    if (player.x > WORLD_X - pad) { player.x = WORLD_X - pad; player.vx = 0; }\n    if (player.z < WORLD_Z_MIN + pad) { player.z = WORLD_Z_MIN + pad; player.vz = 0; }\n    if (player.z > WORLD_Z_MAX - pad) { player.z = WORLD_Z_MAX - pad; player.vz = 0; }\n\n    // Check cup collisions\n    for (const c of cups) {\n      if (!c.collected) {\n        if (!prefersReducedMotion) {\n          c.bobPhase += dt * 3.2;\n        }\n        const dist = Math.hypot(player.x - c.x, player.z - c.z);\n        if (dist < player.radius + 30) {\n          c.collected = true;\n          c.collectAnim = 0.001;\n          score++;\n          player.stack = score;\n          updateHUD();\n          if (!prefersReducedMotion) {\n            ripples.push({ x: c.x, z: c.z, progress: 0 });\n          }\n          srStatus.textContent = `Collected cup ${score} of ${TOTAL_CUPS}.`;\n          if (score >= TOTAL_CUPS) {\n            gameActive = false;\n            finalTimeEl.textContent = `${elapsedTime.toFixed(1)}s`;\n            overlay.classList.add('visible');\n            srStatus.textContent = `All 12 ceramic cups collected in ${elapsedTime.toFixed(1)} seconds!`;\n            playAgainBtn.focus();\n          }\n        }\n      } else if (c.collectAnim > 0 && c.collectAnim < 1) {\n        c.collectAnim = Math.min(1, c.collectAnim + dt * 5);\n      }\n    }\n  }\n\n  function render(dt) {\n    ctx.clearRect(0, 0, width, height);\n    drawStudioTable();\n    drawRipples(dt);\n\n    // Depth-sort all 3D entities (painter's algorithm from back to front)\n    const renderList = [];\n\n    renderList.push({\n      z: player.z,\n      draw: () => drawPlayerTray()\n    });\n\n    for (const c of cups) {\n      if (c.collected && (prefersReducedMotion || c.collectAnim >= 1)) continue;\n      renderList.push({\n        z: c.z,\n        draw: () => {\n          const bob = prefersReducedMotion ? 0 : Math.sin(c.bobPhase) * 3.5;\n          let scale = 1;\n          let lift = 0;\n          if (c.collected) {\n            scale = 1 - c.collectAnim;\n            lift = c.collectAnim * 55;\n          }\n          drawCeramicCup(c.x, lift, c.z, scale, c.angle, bob);\n        }\n      });\n    }\n\n    renderList.sort((a, b) => b.z - a.z);\n    for (const item of renderList) {\n      item.draw();\n    }\n  }\n\n  function loop(now) {\n    requestAnimationFrame(loop);\n    if (document.hidden) {\n      lastTime = now;\n      return;\n    }\n    const dt = Math.min(0.05, Math.max(0.001, (now - lastTime) / 1000));\n    lastTime = now;\n    update(dt);\n    render(dt);\n  }\n\n  document.addEventListener('visibilitychange', () => {\n    if (!document.hidden) lastTime = performance.now();\n  });\n\n  resetGame();\n  requestAnimationFrame(loop);\n})();\n</script>\n</body>\n</html>"
  }
}