ZEN POTTERY
by ThunderPhoenix57348 lines11.0 KB
<!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.