<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Tsum Tsum Style - Skill Gauge Fix</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
<style>
:root {
--bg-color: #fce4ec;
--game-bg: #2c3e50;
--fever-bg: #1a1a1a;
--panel-color: rgba(255, 255, 255, 0.95);
}

body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: var(--bg-color);
font-family: 'Hiragino Sans', 'Meiryo', sans-serif;
overflow: hidden;
touch-action: none;
transition: background-color 0.5s;
}

#game-container {
position: relative;
box-shadow: 0 20px 50px rgba(0,0,0,0.4);
border-radius: 20px;
overflow: hidden;
background-color: var(--game-bg);
border: 4px solid #fff;
transition: background-color 0.5s, border-color 0.5s;
}

#game-container.fever-active {
background-color: var(--fever-bg);
border-color: #ffeb3b;
}

canvas {
display: block;
}

#ui-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}

.hud {
padding: 10px 15px;
display: flex;
justify-content: space-between;
color: white;
font-size: 18px;
font-weight: 900;
text-shadow: 2px 2px 0px #000;
}

#fever-bar-container {
position: absolute;
top: 50px;
left: 50%;
transform: translateX(-50%);
width: 60%;
height: 10px;
background: rgba(255,255,255,0.2);
border-radius: 5px;
overflow: hidden;
}

#fever-bar-fill {
width: 0%;
height: 100%;
background: #00e5ff;
transition: width 0.2s;
}

#fever-label {
position: absolute;
top: 65px;
left: 50%;
transform: translateX(-50%);
color: #00e5ff;
font-weight: bold;
font-size: 12px;
text-shadow: 1px 1px 2px #000;
}

#skill-button-container {
position: absolute;
bottom: 15px;
left: 15px;
width: 80px;
height: 80px;
pointer-events: auto;
cursor: pointer;
z-index: 70;
}

#skill-canvas {
width: 100%;
height: 100%;
}

#fever-notice {
position: absolute;
top: 30%;
left: 50%;
transform: translate(-50%, -50%);
color: #ffeb3b;
font-size: 48px;
font-weight: 900;
text-shadow: 4px 4px 0px #e91e63;
display: none;
z-index: 80;
}

#message-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--panel-color);
color: #333;
padding: 30px;
border-radius: 20px;
text-align: center;
display: none;
pointer-events: auto;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
z-index: 100;
}

button {
margin-top: 15px;
padding: 10px 30px;
font-size: 18px;
background: #ff4081;
border: none;
color: white;
border-radius: 50px;
font-weight: bold;
cursor: pointer;
box-shadow: 0 4px 0 #c2185b;
}
</style>
</head>
<body>

<div id="game-container">
<canvas id="gameCanvas"></canvas>
<div id="ui-layer">
<div class="hud">
<div>SCORE: <span id="score-val">0</span></div>
<div id="timer">TIME: <span id="time-val">60</span></div>
</div>
<div id="fever-bar-container"><div id="fever-bar-fill"></div></div>
<div id="fever-label">FEVER NEXT: 29</div>
<div id="fever-notice">FEVER!</div>
<div id="skill-button-container"><canvas id="skill-canvas" width="120" height="120"></canvas></div>
<div id="message-overlay">
<h1 id="overlay-title">TIME UP!</h1>
<p>FINAL SCORE<br><strong id="final-score" style="font-size: 2em; color: #ff4081">0</strong></p>
<button onclick="resetGame()">RETRY</button>
</div>
</div>
</div>

<script>
// --- Config ---
const ASPECT_RATIO = 3 / 4;
const MAX_TSUMS = 75;
const GAME_DURATION = 60;
const SKILL_MAX = 13;
const FEVER_THRESHOLD = 29;
const FEVER_DURATION = 10000;
const MY_TSUM_INDEX = 0; // 赤色ツムがマイツム
// 消去速度(ミリ秒)
const POP_INTERVAL_NORMAL = 320;
const POP_INTERVAL_FEVER = 160;

const TSUM_TYPES = [
{ color: '#FF5E5E', highlight: '#FFADAD' }, // Smile (My Tsum)
{ color: '#54A0FF', highlight: '#A0CFFF' }, // Sleepy
{ color: '#1DD1A1', highlight: '#88F2D5' }, // Wink
{ color: '#FECA57', highlight: '#FFEB99' } // Surprise
];

// --- State ---
let width, height, tsumRadius;
let score = 0;
let timeLeft = GAME_DURATION;
let isGameOver = false;
let selectedTsums = [];
let isDragging = false;
let skillCount = 0;
let feverCount = 0;
let isFever = false;
let feverEndTime = 0;
let timerInterval;
let rainbowAngle = 0;
let particles = [];
let shockwaves = [];
// 消去待機キュー
let poppingQueue = [];
let lastPopTime = 0;

const { Engine, Runner, World, Bodies, Composite, Body } = Matter;
let engine, runner;
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const skillCanvas = document.getElementById('skill-canvas');
const sctx = skillCanvas.getContext('2d');

function initLayout() {
const windowHeight = Math.min(window.innerHeight * 0.85, 750);
height = windowHeight;
width = height * ASPECT_RATIO;
canvas.width = width;
canvas.height = height;
const container = document.getElementById('game-container');
container.style.width = width + 'px';
container.style.height = height + 'px';
tsumRadius = width / 14;
}

function initPhysics() {
engine = Engine.create();
engine.gravity.y = 1.5;
const wallThickness = 100;
const wallOptions = { isStatic: true, friction: 0.1, restitution: 0.1 };
const ground = Bodies.rectangle(width / 2, height + wallThickness/2, width, wallThickness, wallOptions);
const leftWall = Bodies.rectangle(-wallThickness/2, height / 2, wallThickness, height * 2, wallOptions);
const rightWall = Bodies.rectangle(width + wallThickness/2, height / 2, wallThickness, height * 2, wallOptions);
World.add(engine.world, [ground, leftWall, rightWall]);
runner = Runner.create();
Runner.run(runner, engine);
}

function createTsum(x, y) {
const typeIndex = Math.floor(Math.random() * TSUM_TYPES.length);
return Bodies.circle(x, y, tsumRadius, {
friction: 0.2, restitution: 0.1, density: 0.001, label: 'tsum', slop: 0.05,
plugin: { type: typeIndex }
});
}

function spawnTsum(yOffset = -50) {
const x = Math.random() * (width - tsumRadius * 2) + tsumRadius;
World.add(engine.world, createTsum(x, yOffset));
}

// --- Fever Logic ---
function updateFeverProgress(count) {
if (isFever) return;
feverCount += count;
const progress = Math.min((feverCount / FEVER_THRESHOLD) * 100, 100);
document.getElementById('fever-bar-fill').style.width = progress + '%';
document.getElementById('fever-label').innerText = `FEVER NEXT: ${Math.max(0, FEVER_THRESHOLD - feverCount)}`;
if (feverCount >= FEVER_THRESHOLD) startFever();
}

function startFever() {
isFever = true; feverCount = 0;
feverEndTime = Date.now() + FEVER_DURATION;
timeLeft += 5;
document.getElementById('game-container').classList.add('fever-active');
const notice = document.getElementById('fever-notice');
notice.style.display = 'block'; setTimeout(() => notice.style.display = 'none', 1000);
document.getElementById('fever-bar-fill').style.backgroundColor = '#ffeb3b';
document.getElementById('fever-label').innerText = 'FEVER TIME!!';
}

function endFever() {
isFever = false;
document.getElementById('game-container').classList.remove('fever-active');
document.getElementById('fever-bar-fill').style.backgroundColor = '#00e5ff';
updateFeverProgress(0);
}

// --- Effects ---
function addParticles(x, y, color, count = 10) {
for (let i = 0; i < count; i++) {
particles.push({
x, y, vx: (Math.random() - 0.5) * 15, vy: (Math.random() - 0.5) * 15,
life: 1.0, color, size: Math.random() * 6 + 2
});
}
}

function addShockwave(x, y, maxRadius, color) {
shockwaves.push({ x, y, radius: 0, maxRadius, life: 1.0, color });
}

// --- Sequential Popping Logic ---
function processPoppingQueue() {
if (poppingQueue.length === 0) return;
const now = Date.now();
const interval = isFever ? POP_INTERVAL_FEVER : POP_INTERVAL_NORMAL;

if (now - lastPopTime >= interval) {
lastPopTime = now;
popNextTsum();
}
}

function popNextTsum() {
const item = poppingQueue.shift();
if (!item) return;

if (Composite.allBodies(engine.world).includes(item.body)) {
const x = item.body.position.x;
const y = item.body.position.y;
addParticles(x, y, item.color, 8);
World.remove(engine.world, item.body);
spawnTsum();
const multiplier = isFever ? 3 : 1;
score += 120 * multiplier;
document.getElementById('score-val').innerText = score;
updateFeverProgress(1);

// 通常消去時のマイツム判定
if (item.type === MY_TSUM_INDEX) {
skillCount = Math.min(SKILL_MAX, skillCount + 1);
}

// ボム生成
if (item.isLast && item.shouldSpawnBomb) {
World.add(engine.world, Bodies.circle(x, y, tsumRadius * 1.5, {
label: 'bomb',
friction: 0.05,
restitution: 0.2
}));
}
}
}

function flushPoppingQueue() {
while (poppingQueue.length > 0) {
popNextTsum();
}
}

// --- Drawing ---
function drawFace(x, y, r, type) {
ctx.save(); ctx.translate(x, y); ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; ctx.strokeStyle = 'rgba(0, 0, 0, 0.7)';
ctx.lineWidth = 2.5; const eyeSize = r * 0.15; const eyeOff = r * 0.4;
if (type === 0) {
ctx.beginPath(); ctx.arc(-eyeOff, -r*0.1, eyeSize, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(eyeOff, -r*0.1, eyeSize, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(0, r*0.15, r*0.4, 0.4, Math.PI-0.4); ctx.stroke();
} else if (type === 1) {
ctx.beginPath(); ctx.moveTo(-eyeOff-eyeSize, -r*0.1); ctx.lineTo(-eyeOff+eyeSize, -r*0.1); ctx.stroke();
ctx.beginPath(); ctx.moveTo(eyeOff-eyeSize, -r*0.1); ctx.lineTo(eyeOff+eyeSize, -r*0.1); ctx.stroke();
ctx.beginPath(); ctx.arc(0, r*0.35, r*0.2, 0, Math.PI*2); ctx.fill();
} else if (type === 2) {
ctx.beginPath(); ctx.arc(-eyeOff, -r*0.1, eyeSize, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(eyeOff, -r*0.1, eyeSize, Math.PI, 0); ctx.stroke();
ctx.fillStyle = '#ff80ab'; ctx.beginPath(); ctx.arc(0, r*0.25, eyeSize, 0, Math.PI*2); ctx.fill();
} else {
ctx.beginPath(); ctx.arc(-eyeOff, -r*0.1, eyeSize*1.3, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(eyeOff, -r*0.1, eyeSize*1.3, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(0, r*0.35, r*0.2, 0, Math.PI*2); ctx.stroke();
}
ctx.restore();
}

function draw() {
ctx.clearRect(0, 0, width, height);
rainbowAngle += 0.05;
if (isFever) {
if (Date.now() > feverEndTime) endFever();
else {
const remaining = feverEndTime - Date.now();
document.getElementById('fever-bar-fill').style.width = (remaining / FEVER_DURATION * 100) + '%';
}
}

processPoppingQueue();

const bodies = Composite.allBodies(engine.world);
if (selectedTsums.length > 1) {
ctx.beginPath(); ctx.strokeStyle = isFever ? '#ffeb3b' : 'rgba(255, 255, 255, 0.8)';
ctx.lineWidth = 14; ctx.lineJoin = 'round';
selectedTsums.forEach((t, i) => {
if (i === 0) ctx.moveTo(t.position.x, t.position.y);
else ctx.lineTo(t.position.x, t.position.y);
});
ctx.stroke();
}

bodies.forEach(body => {
if (body.label === 'tsum') {
const type = body.plugin.type;
const isSelected = selectedTsums.includes(body);
const isPopping = poppingQueue.some(p => p.body === body);
ctx.globalAlpha = isPopping ? 0.7 + Math.sin(Date.now()*0.008)*0.2 : 1.0;
ctx.beginPath(); ctx.arc(body.position.x, body.position.y, tsumRadius, 0, Math.PI * 2);
ctx.fillStyle = isSelected ? TSUM_TYPES[type].highlight : TSUM_TYPES[type].color;
ctx.fill();
if (isSelected) { ctx.strokeStyle = '#fff'; ctx.lineWidth = 5; ctx.stroke(); }
drawFace(body.position.x, body.position.y, tsumRadius, type);
ctx.globalAlpha = 1.0;
} else if (body.label === 'bomb') {
const grad = ctx.createRadialGradient(body.position.x, body.position.y, 0, body.position.x, body.position.y, tsumRadius*1.5);
grad.addColorStop(0, `hsl(${(rainbowAngle*100)%360}, 100%, 85%)`); grad.addColorStop(1, `hsl(${(rainbowAngle*100+180)%360}, 100%, 55%)`);
ctx.beginPath(); ctx.arc(body.position.x, body.position.y, tsumRadius * 1.5, 0, Math.PI*2);
ctx.fillStyle = grad; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 3; ctx.stroke();
ctx.fillStyle = '#fff'; ctx.font = 'bold 24px Arial'; ctx.textAlign = 'center'; ctx.textBaseline='middle'; ctx.fillText('B', body.position.x, body.position.y);
}
});

for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i]; p.x += p.vx; p.y += p.vy; p.vy += 0.4; p.life -= 0.03;
if (p.life <= 0) { particles.splice(i, 1); continue; }
ctx.globalAlpha = p.life; ctx.fillStyle = p.color;
ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI*2); ctx.fill();
}
for (let i = shockwaves.length - 1; i >= 0; i--) {
const s = shockwaves[i]; s.radius += (s.maxRadius - s.radius) * 0.2; s.life -= 0.05;
if (s.life <= 0) { shockwaves.splice(i, 1); continue; }
ctx.globalAlpha = s.life; ctx.strokeStyle = s.color; ctx.lineWidth = 8;
ctx.beginPath(); ctx.arc(s.x, s.y, s.radius, 0, Math.PI*2); ctx.stroke();
}
ctx.globalAlpha = 1.0;

sctx.clearRect(0, 0, 120, 120); sctx.beginPath(); sctx.arc(60, 60, 52, 0, Math.PI*2); sctx.fillStyle = '#1a252f'; sctx.fill();
const ratio = Math.min(skillCount / SKILL_MAX, 1);
sctx.beginPath(); sctx.moveTo(60, 60); sctx.arc(60, 60, 52, -Math.PI/2, -Math.PI/2 + (Math.PI*2*ratio));
sctx.fillStyle = ratio >= 1 ? `hsl(${(rainbowAngle*120)%360}, 95%, 70%)` : '#ff4081'; sctx.fill();
sctx.strokeStyle = '#fff'; sctx.lineWidth = 5; sctx.stroke();
sctx.fillStyle = '#fff'; sctx.font = 'bold 22px Arial'; sctx.textAlign='center';
sctx.fillText(ratio >= 1 ? 'OK!' : 'SKILL', 60, 68);

if (!isGameOver) requestAnimationFrame(draw);
}

// --- Actions ---
function useSkill() {
if (skillCount < SKILL_MAX || isGameOver) return;
flushPoppingQueue();
skillCount = 0;
const cx = width / 2; const cy = height / 2; const range = width * 0.55;
addShockwave(cx, cy, range * 1.8, '#fff');
const bodies = Composite.allBodies(engine.world);
let count = 0;
let myTsumCount = 0;

bodies.forEach(b => {
if (b.label === 'tsum') {
const d = Math.sqrt((b.position.x - cx)**2 + (b.position.y - cy)**2);
if (d < range) {
// スキル範囲内のマイツムをカウント
if (b.plugin.type === MY_TSUM_INDEX) myTsumCount++;
addParticles(b.position.x, b.position.y, TSUM_TYPES[b.plugin.type].color, 5);
World.remove(engine.world, b); count++;
}
}
});

// スキルで消したマイツムをゲージに反映
skillCount = Math.min(SKILL_MAX, myTsumCount);

const multiplier = isFever ? 3 : 1;
score += count * 250 * multiplier;
document.getElementById('score-val').innerText = score;
updateFeverProgress(count);
for (let i = 0; i < count; i++) spawnTsum(-50 - (i*10));
}

function explodeBomb(bomb) {
flushPoppingQueue();
const bPos = { x: bomb.position.x, y: bomb.position.y };
addShockwave(bPos.x, bPos.y, tsumRadius * 7, 'rgba(255,255,255,0.9)');
const bodies = Composite.allBodies(engine.world);
let count = 0;
let myTsumInBlast = 0;

bodies.forEach(b => {
if (b.label === 'tsum') {
const d = Math.sqrt((b.position.x-bPos.x)**2 + (b.position.y-bPos.y)**2);
if (d < tsumRadius * 5) {
// 爆発範囲内のマイツムをカウントしてゲージへ
if (b.plugin.type === MY_TSUM_INDEX) myTsumInBlast++;
addParticles(b.position.x, b.position.y, TSUM_TYPES[b.plugin.type].color, 5);
World.remove(engine.world, b); count++;
}
}
});
// ボムで消したマイツムをゲージに加算
skillCount = Math.min(SKILL_MAX, skillCount + myTsumInBlast);

World.remove(engine.world, bomb);
const multiplier = isFever ? 3 : 1;
score += count * 180 * multiplier;
document.getElementById('score-val').innerText = score;
updateFeverProgress(count);
for (let i = 0; i < count; i++) spawnTsum();
}

function handleStart(x, y) {
if (isGameOver) return;
const bodies = Composite.allBodies(engine.world);
const bomb = bodies.find(b => b.label === 'bomb' && Math.sqrt((b.position.x-x)**2 + (b.position.y-y)**2) < tsumRadius * 2.5);
if (bomb) { explodeBomb(bomb); return; }
isDragging = true; selectedTsums = []; handleMove(x, y);
}

function handleMove(x, y) {
if (!isDragging || isGameOver) return;
const bodies = Composite.allBodies(engine.world);
const tsum = bodies.find(b => b.label === 'tsum' && Math.sqrt((b.position.x-x)**2 + (b.position.y-y)**2) < tsumRadius * 1.5);
if (tsum && !poppingQueue.some(p => p.body === tsum)) {
if (selectedTsums.length === 0) selectedTsums.push(tsum);
else {
const last = selectedTsums[selectedTsums.length - 1];
if (selectedTsums.length > 1 && tsum === selectedTsums[selectedTsums.length-2]) selectedTsums.pop();
else if (!selectedTsums.includes(tsum) && tsum.plugin.type === last.plugin.type) {
const d = Math.sqrt((tsum.position.x-last.position.x)**2 + (tsum.position.y-last.position.y)**2);
if (d < tsumRadius * 3.2) selectedTsums.push(tsum);
}
}
}
}

function handleEnd() {
if (!isDragging) return;
isDragging = false;
const count = selectedTsums.length;
if (count >= 3) {
const shouldSpawnBomb = count >= 7;
selectedTsums.forEach((t, index) => {
poppingQueue.push({
body: t,
color: TSUM_TYPES[t.plugin.type].color,
type: t.plugin.type,
isLast: index === count - 1,
shouldSpawnBomb: shouldSpawnBomb
});
});
}
selectedTsums = [];
}

function resetGame() {
score = 0; skillCount = 0; feverCount = 0; isFever = false; timeLeft = GAME_DURATION;
particles = []; shockwaves = []; poppingQueue = []; lastPopTime = Date.now();
document.getElementById('score-val').innerText = '0';
document.getElementById('message-overlay').style.display = 'none';
document.getElementById('game-container').classList.remove('fever-active');
const bodies = Composite.allBodies(engine.world);
bodies.forEach(b => { if (b.label === 'tsum' || b.label === 'bomb') World.remove(engine.world, b); });
for (let i = 0; i < MAX_TSUMS; i++) spawnTsum(-Math.random() * 1000);
isGameOver = false;
clearInterval(timerInterval);
timerInterval = setInterval(() => {
timeLeft--; document.getElementById('time-val').innerText = timeLeft;
if (timeLeft <= 0) {
isGameOver = true; clearInterval(timerInterval);
document.getElementById('final-score').innerText = score;
document.getElementById('message-overlay').style.display = 'block';
}
}, 1000);
draw();
}

canvas.addEventListener('mousedown', e => { const r = canvas.getBoundingClientRect(); handleStart(e.clientX-r.left, e.clientY-r.top); });
window.addEventListener('mousemove', e => { const r = canvas.getBoundingClientRect(); handleMove(e.clientX-r.left, e.clientY-r.top); });
window.addEventListener('mouseup', handleEnd);
canvas.addEventListener('touchstart', e => { e.preventDefault(); const r = canvas.getBoundingClientRect(); const t = e.touches[0]; handleStart(t.clientX-r.left, t.clientY-r.top); }, {passive: false});
canvas.addEventListener('touchmove', e => { e.preventDefault(); const r = canvas.getBoundingClientRect(); const t = e.touches[0]; handleMove(t.clientX-r.left, t.clientY-r.top); }, {passive: false});
canvas.addEventListener('touchend', e => { e.preventDefault(); handleEnd(); }, {passive: false});
document.getElementById('skill-button-container').addEventListener('mousedown', (e) => { e.stopPropagation(); useSkill(); });
document.getElementById('skill-button-container').addEventListener('touchstart', (e) => { e.stopPropagation(); e.preventDefault(); useSkill(); });

window.onload = () => { initLayout(); initPhysics(); resetGame(); };
</script>
</body>
</html>