🎮ArcadeLab

ZEN POTTERY

by ThunderPhoenix57
348 lines11.0 KB
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>ZEN POTTERY</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
            -webkit-user-select: none;
            touch-action: none;
        }

        body {
            background-color: #1a1715;
            overflow: hidden;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            height: 100vh;
            width: 100vw;
            font-family: 'Segoe UI', system-ui, sans-serif;
        }

        canvas {
            display: block;
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            z-index: 1;
        }

        #ui-layer {
            position: absolute;
            bottom: 40px;
            z-index: 10;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 20px;
            pointer-events: none;
        }

        #hint {
            color: rgba(255, 255, 255, 0.4);
            font-size: 0.9rem;
            letter-spacing: 3px;
            text-transform: uppercase;
            transition: opacity 1s ease;
        }

        .btn-group {
            display: flex;
            gap: 15px;
            pointer-events: auto;
        }

        button {
            background: #362a22;
            border: 1px solid #4f3f33;
            color: #d4ba9f;
            padding: 12px 28px;
            font-size: 0.9rem;
            letter-spacing: 2px;
            border-radius: 4px;
            cursor: pointer;
            transition: all 0.2s ease;
            box-shadow: 0 4px 10px rgba(0,0,0,0.3);
            text-transform: uppercase;
        }

        button:hover {
            background: #47372d;
            transform: translateY(-2px);
        }

        button:active {
            transform: translateY(1px);
        }
    </style>
</head>
<body>

    <div id="ui-layer">
        <div id="hint">TOUCH & DRAG TO MOLD THE CLAY</div>
        <div class="btn-group">
            <button onclick="resetClay()">NEW CLAY</button>
            <button onclick="bakeClay()">FIRE IN KILN</button>
        </div>
    </div>
    <canvas id="canvas"></canvas>

<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const hint = document.getElementById('hint');

    let width, height, centerX, wheelBaseY, vaseTopY, segH;
    
    const segments = 120;
    let radii = new Float32Array(segments).fill(90);
    
    let isBaked = false;
    let clayColor = { r: 163, g: 93, b: 64 }; // Raw Terracotta
    let targetColor = { r: 163, g: 93, b: 64 };
    
    const glazes = [
        { name: 'JADE', r: 91, g: 140, b: 119 },
        { name: 'COBALT', r: 49, g: 78, b: 115 },
        { name: 'OBSIDIAN', r: 35, g: 35, b: 35 },
        { name: 'CREAM', r: 235, g: 227, b: 213 },
        { name: 'CRIMSON', r: 138, g: 40, b: 40 }
    ];

    let pointer = { x: -1000, y: -1000, isDown: false };
    let smokeParticles = [];

    class Smoke {
        constructor(x, y) {
            this.x = x;
            this.y = y;
            this.vx = (Math.random() - 0.5) * 1.5;
            this.vy = -Math.random() * 2 - 1;
            this.size = Math.random() * 20 + 15;
            this.life = 1.0;
        }
        update() {
            this.x += this.vx;
            this.y += this.vy;
            this.size += 0.4;
            this.life -= 0.015;
        }
        draw() {
            ctx.fillStyle = `rgba(220, 220, 220, ${this.life * 0.15})`;
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
            ctx.fill();
        }
    }

    function resize() {
        width = canvas.width = window.innerWidth;
        height = canvas.height = window.innerHeight;
        centerX = width / 2;
        wheelBaseY = height * 0.8;
        vaseTopY = height * 0.2;
        segH = (wheelBaseY - vaseTopY) / (segments - 1);
    }
    window.addEventListener('resize', resize);

    function resetClay() {
        isBaked = false;
        targetColor = { r: 163, g: 93, b: 64 }; // Back to raw clay
        for(let i=0; i<segments; i++) {
            radii[i] = 80;
        }
        hint.innerText = "TOUCH & DRAG TO MOLD THE CLAY";
        hint.style.opacity = 1;
    }

    function bakeClay() {
        if (isBaked) return;
        isBaked = true;
        
        let glaze = glazes[Math.floor(Math.random() * glazes.length)];
        targetColor = { r: glaze.r, g: glaze.g, b: glaze.b };
        
        hint.innerText = glaze.name + " GLAZE REVEALED";
        hint.style.opacity = 1;
        
        // Spawn steam
        for(let i=0; i<25; i++) {
            smokeParticles.push(new Smoke(centerX + (Math.random()-0.5)*150, wheelBaseY - Math.random()*400));
        }
    }

    function smoothArray() {
        let temp = new Float32Array(segments);
        for(let i=1; i<segments-1; i++) {
            // Pull points gently toward their neighbors to simulate surface tension
            temp[i] = radii[i] * 0.6 + radii[i-1] * 0.2 + radii[i+1] * 0.2;
        }
        for(let i=1; i<segments-2; i++) {
            radii[i] = temp[i]; // Leave the absolute bottom anchor untouched
        }
    }

    function animate() {
        // Smooth color transition for baking
        clayColor.r += (targetColor.r - clayColor.r) * 0.03;
        clayColor.g += (targetColor.g - clayColor.g) * 0.03;
        clayColor.b += (targetColor.b - clayColor.b) * 0.03;

        ctx.fillStyle = '#1a1715';
        ctx.fillRect(0, 0, width, height);

        // --- DRAW WHEEL ---
        ctx.fillStyle = '#111';
        ctx.beginPath();
        ctx.ellipse(centerX, wheelBaseY + 15, 240, 35, 0, 0, Math.PI*2);
        ctx.fill();
        
        ctx.fillStyle = '#221f1d';
        ctx.beginPath();
        ctx.ellipse(centerX, wheelBaseY, 240, 35, 0, 0, Math.PI*2);
        ctx.fill();

        if (!isBaked) {
            let time = Date.now() * 0.003;
            let spinX = centerX + Math.cos(time) * 180;
            let spinY = wheelBaseY + Math.sin(time) * 26;
            ctx.fillStyle = 'rgba(0,0,0,0.3)';
            ctx.beginPath();
            ctx.arc(spinX, spinY, 6, 0, Math.PI*2);
            ctx.fill();
        }

        // --- MOLDING LOGIC ---
        if (pointer.isDown && !isBaked) {
            hint.style.opacity = 0;
            let y = pointer.y;
            
            if (y > vaseTopY - 50 && y < wheelBaseY + 50) {
                let yIndex = Math.floor(((y - vaseTopY) / (wheelBaseY - vaseTopY)) * segments);
                let dist = Math.abs(pointer.x - centerX);
                let targetR = Math.max(15, Math.min(dist, width * 0.4)); // Constraints
                
                let brushSize = 12; // How wide your fingers are
                let pressure = 0.2; // How soft the clay is
                
                for(let i = -brushSize; i <= brushSize; i++) {
                    let idx = yIndex + i;
                    if (idx >= 0 && idx < segments) {
                        let weight = Math.cos((i / brushSize) * (Math.PI / 2));
                        radii[idx] += (targetR - radii[idx]) * (pressure * weight);
                    }
                }
            }
            smoothArray(); // Keep clay smooth while hands are on it
        }
        
        // Keep base securely anchored to the wheel
        radii[segments-1] = 120;
        radii[segments-2] = 120;

        // --- DRAW VASE SILHOUETTE ---
        let maxR = Math.max(...radii, 100);
        let currentHex = `rgb(${clayColor.r|0}, ${clayColor.g|0}, ${clayColor.b|0})`;

        ctx.save();
        ctx.beginPath();
        ctx.moveTo(centerX - radii[0], vaseTopY);
        ctx.lineTo(centerX + radii[0], vaseTopY);
        for(let i=1; i<segments; i++) {
            ctx.lineTo(centerX + radii[i], vaseTopY + i * segH);
        }
        // Curved bottom edge to give it 3D cylindrical depth
        ctx.ellipse(centerX, wheelBaseY, radii[segments-1], radii[segments-1] * 0.15, 0, 0, Math.PI, false);
        
        for(let i=segments-2; i>=0; i--) {
            ctx.lineTo(centerX - radii[i], vaseTopY + i * segH);
        }
        ctx.closePath();
        ctx.clip(); // Restrict all painting to inside this shape

        // Base color
        ctx.fillStyle = currentHex;
        ctx.fillRect(centerX - maxR - 50, vaseTopY, (maxR + 50) * 2, wheelBaseY - vaseTopY + 50);

        // 3D Shading & Lighting Overlay
        let grad = ctx.createLinearGradient(centerX - maxR, 0, centerX + maxR, 0);
        grad.addColorStop(0.0, 'rgba(0,0,0,0.85)'); // Deep left shadow
        grad.addColorStop(0.2, 'rgba(0,0,0,0.1)');  
        grad.addColorStop(0.5, 'rgba(0,0,0,0)');    // Center ambient
        
        // Specular highlight
        let gloss = isBaked ? 0.7 : 0.15; // Shiny if glazed
        grad.addColorStop(0.7, 'rgba(255,255,255,0)');
        grad.addColorStop(0.78, `rgba(255,255,255,${gloss})`);
        grad.addColorStop(0.86, 'rgba(255,255,255,0)');
        
        grad.addColorStop(0.9, 'rgba(0,0,0,0.2)');
        grad.addColorStop(1.0, 'rgba(0,0,0,0.8)');  // Right edge shadow

        ctx.fillStyle = grad;
        ctx.fillRect(centerX - maxR - 50, vaseTopY, (maxR + 50) * 2, wheelBaseY - vaseTopY + 50);
        ctx.restore();

        // --- DRAW OPENING (INSIDE THE VASE) ---
        ctx.fillStyle = `rgb(${clayColor.r*0.2|0}, ${clayColor.g*0.2|0}, ${clayColor.b*0.2|0})`;
        ctx.beginPath();
        ctx.ellipse(centerX, vaseTopY, radii[0], radii[0] * 0.15, 0, 0, Math.PI*2);
        ctx.fill();

        // --- DRAW SMOKE PARTICLES ---
        for (let i = smokeParticles.length - 1; i >= 0; i--) {
            smokeParticles[i].update();
            smokeParticles[i].draw();
            if (smokeParticles[i].life <= 0) smokeParticles.splice(i, 1);
        }

        requestAnimationFrame(animate);
    }

    // Input Handling
    function setPointer(e) {
        if (e.touches) {
            pointer.x = e.touches[0].clientX;
            pointer.y = e.touches[0].clientY;
        } else {
            pointer.x = e.clientX;
            pointer.y = e.clientY;
        }
    }

    window.addEventListener('mousedown', (e) => { 
        if (e.target.tagName === 'BUTTON') return;
        pointer.isDown = true; 
        setPointer(e); 
    });
    window.addEventListener('mousemove', (e) => { 
        if (pointer.isDown) setPointer(e); 
    });
    window.addEventListener('mouseup', () => pointer.isDown = false);

    window.addEventListener('touchstart', (e) => { 
        if (e.target.tagName === 'BUTTON') return;
        pointer.isDown = true; 
        setPointer(e); 
    }, {passive: false});
    window.addEventListener('touchmove', (e) => { 
        if (e.target.tagName !== 'BUTTON') e.preventDefault(); 
        if (pointer.isDown) setPointer(e); 
    }, {passive: false});
    window.addEventListener('touchend', () => pointer.isDown = false);

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

Game Source: ZEN POTTERY

Creator: ThunderPhoenix57

Libraries: none

Complexity: complex (348 lines, 11.0 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: zen-pottery-thunderphoenix57" to link back to the original. Then publish at arcadelab.ai/publish.