Prompt Details
Model
(gemini-3.1-pro)
Token size
571
Example input
[GAME_CONCEPT]: Stacking Jellies
Example output
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>舒缓果冻堆 - Healing Jelly</title>
<style>
:root {
--bg-color: #f7f3e9;
--text-color: #8e8d8a;
--ui-bg: rgba(255, 255, 255, 0.6);
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: var(--bg-color);
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
touch-action: none;
}
canvas {
display: block;
}
#ui {
position: absolute;
top: 20px;
left: 0;
right: 0;
text-align: center;
pointer-events: none;
color: var(--text-color);
user-select: none;
z-index: 10;
}
h1 {
font-weight: 300;
font-size: 1.5rem;
letter-spacing: 2px;
margin-bottom: 5px;
opacity: 0.8;
}
p {
font-size: 0.9rem;
opacity: 0.6;
}
#hint {
position: absolute;
bottom: 30px;
left: 0;
right: 0;
text-align: center;
color: var(--text-color);
opacity: 0.4;
font-size: 0.8rem;
pointer-events: none;
}
</style>
</head>
<body>
<div id="ui">
<h1>舒缓果冻堆</h1>
<p>呼吸,叠放,放松</p>
</div>
<div id="hint">轻点屏幕 放置果冻</div>
<canvas id="gameCanvas"></canvas>
<script>
/**
* SoundManager - Web Audio API 五声音阶
*/
class SoundManager {
constructor() {
this.ctx = null;
this.pentatonic = [261.63, 293.66, 329.63, 392.00, 440.00, 523.25];
this.lastNote = -1;
}
init() {
if (!this.ctx) {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
}
}
playJellySound() {
this.init();
if (this.ctx.state === 'suspended') this.ctx.resume();
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
let noteIdx;
do { noteIdx = Math.floor(Math.random() * this.pentatonic.length); } while (noteIdx === this.lastNote);
this.lastNote = noteIdx;
osc.type = 'sine';
osc.frequency.setValueAtTime(this.pentatonic[noteIdx], this.ctx.currentTime);
gain.gain.setValueAtTime(0, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(0.12, this.ctx.currentTime + 0.05);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 1.2);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 1.2);
}
playRippleSound() {
this.init();
if (this.ctx.state === 'suspended') this.ctx.resume();
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(600, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(300, this.ctx.currentTime + 0.3);
gain.gain.setValueAtTime(0.04, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.3);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.3);
}
}
const sounds = new SoundManager();
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let width, height;
let jellies = [];
let particles = [];
let ripples = [];
let cameraY = 0;
let smoothedHighestY = 0;
const PALETTE = ['#FFB7B2', '#FFDAC1', '#E2F0CB', '#B5EAD7', '#C7CEEA', '#FDFD96'];
const GRAVITY = 0.3;
class Jelly {
constructor(x, y, w, h, color) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = color;
this.vy = 0;
this.isStatic = false;
this.wobble = 0;
this.wobbleSpeed = 0;
}
update(ground) {
if (this.isStatic) {
this.y += (ground - this.h / 2 - this.y) * 0.2;
this.wobbleSpeed *= 0.85;
this.wobble += this.wobbleSpeed;
this.wobble *= 0.8;
return;
}
this.vy += GRAVITY;
this.y += this.vy;
if (this.y + this.h / 2 >= ground) {
const impactVel = this.vy;
this.y = ground - this.h / 2;
if (Math.abs(this.vy) < 1.2) {
this.isStatic = true;
this.vy = 0;
this.triggerWobble(10);
sounds.playJellySound();
createImpactParticles(this.x, this.y + this.h / 2, this.color);
} else {
this.vy *= -0.3;
this.triggerWobble(impactVel * 2);
}
}
}
triggerWobble(force) {
this.wobbleSpeed = force;
}
draw() {
ctx.save();
ctx.translate(this.x, this.y - cameraY);
const wMod = Math.sin(Date.now() * 0.01) * this.wobble * 0.2;
const hMod = this.wobble * 0.35;
const drawW = this.w + wMod;
const drawH = this.h - hMod;
ctx.shadowBlur = 20;
ctx.shadowColor = 'rgba(0,0,0,0.03)';
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.roundRect(-drawW / 2, -drawH / 2, drawW, drawH, 15);
ctx.fill();
ctx.fillStyle = 'rgba(255,255,255,0.25)';
ctx.beginPath();
ctx.roundRect(-drawW / 2 + 10, -drawH / 2 + 6, drawW - 20, 8, 4);
ctx.fill();
ctx.restore();
}
}
class Particle {
constructor(x, y, color) {
this.x = x; this.y = y;
this.vx = (Math.random() - 0.5) * 4;
this.vy = (Math.random() - 1) * 4;
this.size = Math.random() * 6 + 2;
this.color = color; this.life = 1.0;
}
update() { this.x += this.vx; this.y += this.vy; this.vy += 0.1; this.life -= 0.015; }
draw() { ctx.globalAlpha = this.life; ctx.fillStyle = this.color; ctx.beginPath(); ctx.arc(this.x, this.y - cameraY, this.size, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1; }
}
class Ripple {
constructor(x, y) { this.x = x; this.y = y; this.r = 0; this.life = 1.0; }
update() { this.r += 3; this.life -= 0.02; }
draw() { ctx.strokeStyle = `rgba(142, 141, 138, ${this.life * 0.2})`; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2); ctx.stroke(); }
}
function resize() {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
// 初始化平滑高度,确保一开始 finalCameraTarget 为 0
if (smoothedHighestY === 0 || smoothedHighestY > height) {
smoothedHighestY = height;
}
}
function createImpactParticles(x, y, color) {
for (let i = 0; i < 6; i++) particles.push(new Particle(x, y, color));
}
function spawnJelly(x, y) {
const w = Math.random() * 40 + 90;
const h = 45;
const color = PALETTE[Math.floor(Math.random() * PALETTE.length)];
// 修正生成逻辑,防止第一下点击位置过低导致生成的果冻已经在屏幕外
const spawnBaseY = Math.min(y, cameraY + height * 0.5);
jellies.push(new Jelly(x, spawnBaseY - 200, w, h, color));
}
function init() {
resize();
window.addEventListener('resize', resize);
const handleInput = (e) => {
const rect = canvas.getBoundingClientRect();
const clientX = e.clientX || (e.touches && e.touches[0].clientX);
const clientY = e.clientY || (e.touches && e.touches[0].clientY);
if (clientX !== undefined) {
const x = clientX - rect.left;
const y = clientY - rect.top;
ripples.push(new Ripple(x, y));
sounds.playRippleSound();
spawnJelly(x, y + cameraY);
}
};
canvas.addEventListener('mousedown', handleInput);
canvas.addEventListener('touchstart', (e) => { e.preventDefault(); handleInput(e); }, { passive: false });
loop();
}
function loop() {
const grad = ctx.createLinearGradient(0, 0, 0, height);
grad.addColorStop(0, '#fdfcf0');
grad.addColorStop(1, '#f7f3e9');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, width, height);
// --- 修复后的摄像机逻辑 ---
// 默认最高点是屏幕底部,只有出现果冻后才更新
let actualTopY = height;
let hasStableJelly = false;
jellies.forEach(j => {
if (j.isStatic || Math.abs(j.vy) < 2) {
if (j.y < actualTopY) {
actualTopY = j.y;
hasStableJelly = true;
}
}
});
// 如果还没有稳定的果冻,保持 smoothedHighestY 在底部
let targetForSmoothed = hasStableJelly ? actualTopY : height;
smoothedHighestY += (targetForSmoothed - smoothedHighestY) * 0.08;
// 目标是将最高点保持在屏幕 60% 的高度
// 修正:只有当 smoothedHighestY 真的很高(数值小)时,finalCameraTarget 才可能为负数(向上卷动)
let finalCameraTarget = Math.min(0, smoothedHighestY - height * 0.6);
let diff = finalCameraTarget - cameraY;
let followSpeed = diff < 0 ? 0.05 : 0.01;
cameraY += diff * followSpeed;
// 绘制装饰元素
for (let i = ripples.length - 1; i >= 0; i--) { ripples[i].update(); ripples[i].draw(); if (ripples[i].life <= 0) ripples.splice(i, 1); }
for (let i = particles.length - 1; i >= 0; i--) { particles[i].update(); particles[i].draw(); if (particles[i].life <= 0) particles.splice(i, 1); }
// 更新果冻
jellies.forEach((j, index) => {
let currentGround = height - 40;
for (let k = 0; k < jellies.length; k++) {
const other = jellies[k];
if (k !== index && other.y > j.y) {
const horizontalOverlap = Math.abs(j.x - other.x) < (j.w / 2 + other.w / 2) - 10;
if (horizontalOverlap) {
const surfaceY = other.y - other.h / 2;
if (surfaceY < currentGround) currentGround = surfaceY;
}
}
}
j.update(currentGround);
j.draw();
});
// 保持性能
if (jellies.length > 60 && jellies[0].y > height + cameraY + 600) {
jellies.shift();
}
requestAnimationFrame(loop);
}
window.onload = init;
</script>
</body>
</html>
By purchasing this prompt, you agree to our terms of service
GEMINI-3.1-PRO
Generates self-contained, single-file HTML5 healing mini-games. Just input a simple concept (e.g., "Stacking Jellies"), and it creates a fully playable game with zero external assets. Features include a built-in Web Audio API synthesizer for relaxing ASMR sounds, soft Morandi color palettes, and low-gravity physics for a satisfying "flow" state. Perfect for creating instant stress-relief web games that are responsive on both mobile and desktop!
...more
Added over 1 month ago
