🏃♂️逃走中タイマー
120:00
⏳
残り時間
💰現在の賞金
0円
✨
▶️ スタート
⏸️ ストップ
➕10分
➕20分
➕30分
⏩15分進める
🧨 ボーナス+10分
💥 進める-15分
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>💥逃走中タイマー風アプリ⏳💰</title>
<style>
/* アニメーション用だけをここに記述 */
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.06); }
100% { transform: scale(1); }
}
@keyframes pop {
0% { transform: scale(0.8); opacity: 0; }
60% { transform: scale(1.12); opacity: 1; }
100% { transform: scale(1); opacity: 1; }
}
@keyframes sparkle {
0% { transform: translateY(0) rotate(0); opacity: 1; }
100% { transform: translateY(-60px) rotate(200deg); opacity: 0; }
}
@keyframes wiggle {
0% { transform: rotate(-6deg); }
50% { transform: rotate(6deg); }
100% { transform: rotate(-6deg); }
}
@keyframes flash {
0% { box-shadow: 0 0 0 rgba(255,0,0,0.0); }
50% { box-shadow: 0 0 18px rgba(255,0,0,0.6); }
100% { box-shadow: 0 0 0 rgba(255,0,0,0.0); }
}
</style>
<body>
<div style="width:400px;height:400px;margin:0 auto;border:6px solid #222;border-radius:20px;overflow:hidden;position:relative;background:
linear-gradient(135deg,#fff8e1,#ffe0b2);box-sizing:border-box;font-family: 'Segoe UI', Roboto, 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif;">
<!-- 上部: タイマー -->
<div style="height:50%;display:flex;flex-direction:column;align-items:center;justify-content:center;padding-top:18px;box-sizing:border-box;">
<div style="font-size:20px;color:#333;margin-bottom:6px;">🏃♂️逃走中タイマー</div>
<div id="timerWrap" style="width:320px;height:110px;background:linear-gradient(180deg,#fff,#ffe6e6);border-radius:16px;display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;">
<div id="timerDigits" style="font-weight:700;font-size:34px;color:#b71c1c;animation: pulse 1.2s infinite;">120:00</div>
<div id="tickEmoji" style="position:absolute;left:12px;top:12px;font-size:22px;transform-origin:center;animation:wiggle 2s infinite;">⏳</div>
<div id="timerPop" style="position:absolute;right:12px;bottom:10px;font-size:18px;color:#444;">残り時間</div>
<!-- sparkles container -->
<div id="sparkles" style="pointer-events:none;position:absolute;left:0;top:0;width:100%;height:100%;"></div>
</div>
</div>
<!-- 下部: 賞金とコントロール -->
<div style="height:50%;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;padding:14px;box-sizing:border-box;">
<div id="moneyWrap" style="width:320px;height:100px;background:linear-gradient(180deg,#e8f5e9,#e0f2f1);border-radius:16px;display:flex;flex-direction:column;align-items:center;justify-content:center;position:relative;">
<div style="font-size:18px;color:#2e7d32;margin-bottom:6px;">💰現在の賞金</div>
<div id="moneyDigits" style="font-weight:700;font-size:34px;color:#0b8043;">0円</div>
<div id="moneyNote" style="position:absolute;left:12px;top:10px;font-size:20px;">✨</div>
</div>
<!-- ボタン群 -->
<div style="width:320px;margin-top:12px;display:flex;flex-wrap:wrap;gap:8px;justify-content:center;">
<div style="flex:1;min-width:96px;">
<div id="startBtn" style="width:100%;padding:8px;border-radius:10px;background:linear-gradient(180deg,#ffecb3,#ffd54f);text-align:center;cursor:pointer;user-select:none;font-weight:700;box-shadow:0 4px 0 rgba(0,0,0,0.12);">
▶️ スタート
</div>
</div>
<div style="flex:1;min-width:96px;">
<div id="stopBtn" style="width:100%;padding:8px;border-radius:10px;background:linear-gradient(180deg,#ef9a9a,#e57373);text-align:center;cursor:pointer;user-select:none;font-weight:700;box-shadow:0 4px 0 rgba(0,0,0,0.12);">
⏸️ ストップ
</div>
</div>
<div style="flex:1;min-width:96px;">
<div id="add10" style="width:100%;padding:8px;border-radius:10px;background:linear-gradient(180deg,#c8e6c9,#a5d6a7);text-align:center;cursor:pointer;user-select:none;font-weight:700;">
➕10分
</div>
</div>
<div style="flex:1;min-width:96px;">
<div id="add20" style="width:100%;padding:8px;border-radius:10px;background:linear-gradient(180deg,#bbdefb,#90caf9);text-align:center;cursor:pointer;user-select:none;font-weight:700;">
➕20分
</div>
</div>
<div style="flex:1;min-width:96px;">
<div id="add30" style="width:100%;padding:8px;border-radius:10px;background:linear-gradient(180deg,#f3e5f5,#e1bee7);text-align:center;cursor:pointer;user-select:none;font-weight:700;">
➕30分
</div>
</div>
<div style="flex:1;min-width:96px;">
<div id="minus15" style="width:100%;padding:8px;border-radius:10px;background:linear-gradient(180deg,#ffe0b2,#ffcc80);text-align:center;cursor:pointer;user-select:none;font-weight:700;">
⏩15分進める
</div>
</div>
</div>
<!-- スマホ用の大きな操作ボタン -->
<div style="width:320px;margin-top:8px;display:flex;gap:8px;justify-content:center;">
<div id="bigAdd" style="flex:1;padding:8px;border-radius:10px;background:linear-gradient(180deg,#fff59d,#fff176);text-align:center;cursor:pointer;user-select:none;font-weight:700;">
🧨 ボーナス+10分
</div>
<div id="bigMinus" style="flex:1;padding:8px;border-radius:10px;background:linear-gradient(180deg,#ffccbc,#ffab91);text-align:center;cursor:pointer;user-select:none;font-weight:700;">
💥 進める-15分
</div>
</div>
</div>
<!-- hidden audio element via canvas tag allowed? audio tag not allowed. Use WebAudio. -->
<canvas id="audioCanvas" width="1" height="1" style="display:none;"></canvas>
</div>
<script>
(function(){
// 初期値
let remainingSec = 120 * 60; // 120:00 in seconds
let money = 0; // in yen
let running = false;
let tickInterval = null;
const moneyPerSec = 200;
const timerDigits = document.getElementById('timerDigits');
const moneyDigits = document.getElementById('moneyDigits');
const sparkles = document.getElementById('sparkles');
const timerWrap = document.getElementById('timerWrap');
const moneyWrap = document.getElementById('moneyWrap');
function formatTime(sec){
if(sec < 0) sec = 0;
const mm = Math.floor(sec/60);
const ss = Math.floor(sec%60);
const mmStr = String(mm).padStart(2,'0');
const ssStr = String(ss).padStart(2,'0');
return mmStr + ':' + ssStr;
}
function formatMoney(yen){
// round down to 10's place (十の位以下は0で表示)
const rounded = Math.floor(yen / 10) * 10;
return rounded.toLocaleString() + '円';
}
function renderAll(){
timerDigits.textContent = formatTime(Math.floor(remainingSec));
moneyDigits.innerHTML = '<span style="font-weight:700;">' + formatMoney(money) + '</span>';
}
function spawnSparkle(xPct){
const e = document.createElement('div');
const emojis = ['✨','💫','🌟','🎉','🎊','🔥','💥','🚨'];
const emo = emojis[Math.floor(Math.random()*emojis.length)];
e.textContent = emo;
e.style.position = 'absolute';
e.style.left = (xPct*100) + '%';
e.style.top = '80%';
e.style.fontSize = (12 + Math.random()*18) + 'px';
e.style.opacity = '1';
e.style.transform = 'translate(-50%,0)';
e.style.pointerEvents = 'none';
e.style.animation = 'sparkle 900ms forwards';
sparkles.appendChild(e);
setTimeout(()=>{ try{ sparkles.removeChild(e);}catch(e){} },1000);
}
function beep(){
try{
// WebAudio beep
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const o = ctx.createOscillator();
const g = ctx.createGain();
o.type = 'sine';
o.frequency.value = 880;
g.gain.value = 0.0001;
o.connect(g);
g.connect(ctx.destination);
o.start();
g.gain.exponentialRampToValueAtTime(0.15, ctx.currentTime + 0.02);
g.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.6);
o.stop(ctx.currentTime + 0.65);
}catch(e){}
}
function startTimer(){
if(running) return;
running = true;
timerDigits.style.animation = 'pulse 1.2s infinite';
tickInterval = setInterval(()=>{
// each second
if(remainingSec <= 0){
stopTimer();
remainingSec = 0;
renderAll();
return;
}
remainingSec -= 1;
money += moneyPerSec;
renderAll();
// spawn sparkle occasionally
if(Math.random() < 0.6) spawnSparkle(Math.random());
// when reaches 0 play sound and flashy effect
if(remainingSec <= 0){
remainingSec = 0;
renderAll();
// big alert
timerWrap.style.animation = 'flash 1s';
beep();
setTimeout(()=>{ timerWrap.style.animation = ''; },1200);
}
},1000);
}
function stopTimer(){
if(!running) return;
running = false;
clearInterval(tickInterval);
tickInterval = null;
timerDigits.style.animation = '';
}
// Button handlers
document.getElementById('startBtn').addEventListener('click', function(e){
startTimer();
this.style.transform = 'translateY(2px)';
this.style.boxShadow = '0 2px 0 rgba(0,0,0,0.12)';
spawnSparkle(0.2);
setTimeout(()=>{ this.style.transform=''; this.style.boxShadow='0 4px 0 rgba(0,0,0,0.12)'; },120);
});
document.getElementById('stopBtn').addEventListener('click', function(e){
stopTimer();
spawnSparkle(0.5);
this.style.animation = 'pop 300ms';
setTimeout(()=>{ this.style.animation = ''; },350);
});
function addMinutes(min){
remainingSec += min*60;
// spawn multiple sparkles
for(let i=0;i<6;i++) spawnSparkle(0.1 + i*0.12 + Math.random()*0.05);
renderAll();
}
document.getElementById('add10').addEventListener('click', function(){
addMinutes(10);
this.style.transform = 'scale(0.98)';
setTimeout(()=>{ this.style.transform=''; },120);
});
document.getElementById('add20').addEventListener('click', function(){
addMinutes(20);
this.style.transform = 'scale(0.98)';
setTimeout(()=>{ this.style.transform=''; },120);
});
document.getElementById('add30').addEventListener('click', function(){
addMinutes(30);
this.style.transform = 'scale(0.98)';
setTimeout(()=>{ this.style.transform=''; },120);
});
function advance15(){
// reduce 15 minutes, but not below zero. The deducted seconds are added to money at 200 yen/sec
const sec15 = 15*60;
const actual = Math.min(sec15, Math.floor(remainingSec));
remainingSec -= actual;
money += actual * moneyPerSec;
// fun effect
spawnSparkle(0.85);
for(let i=0;i<8;i++) spawnSparkle(Math.random());
timerWrap.style.transform = 'rotate(-4deg) translateX(-4px)';
setTimeout(()=>{ timerWrap.style.transform=''; },260);
renderAll();
if(remainingSec <= 0){
remainingSec = 0;
stopTimer();
timerWrap.style.animation = 'flash 1s';
beep();
setTimeout(()=>{ timerWrap.style.animation=''; },1200);
}
}
document.getElementById('minus15').addEventListener('click', function(){
advance15();
this.style.animation = 'wiggle 400ms';
setTimeout(()=>{ this.style.animation = ''; },420);
});
// Big mobile buttons
document.getElementById('bigAdd').addEventListener('click', function(){
addMinutes(10);
spawnSparkle(0.3);
this.style.transform='translateY(2px)';
setTimeout(()=>{ this.style.transform=''; },140);
});
document.getElementById('bigMinus').addEventListener('click', function(){
advance15();
this.style.transform='translateY(2px)';
setTimeout(()=>{ this.style.transform=''; },140);
});
// Click anywhere in timer to pop emoji
timerWrap.addEventListener('click', function(e){
spawnSparkle((e.offsetX||0)/320);
const pop = document.createElement('div');
const faces = ['😱','😅','😈','😵💫','🙀','🤩','😎'];
pop.textContent = faces[Math.floor(Math.random()*faces.length)];
pop.style.position='absolute';
pop.style.left = (e.offsetX||160) + 'px';
pop.style.top = (e.offsetY||55) + 'px';
pop.style.transform='translate(-50%,-50%)';
pop.style.fontSize='28px';
pop.style.pointerEvents='none';
pop.style.animation='pop 600ms forwards';
timerWrap.appendChild(pop);
setTimeout(()=>{ try{ timerWrap.removeChild(pop);}catch(e){} },700);
});
// Initialize display
renderAll();
// Ensure when time hits 0 we notify
// Also update display more responsively if paused then advanced (no running)
setInterval(()=>{ renderAll(); },500);
// Expose debug to window for console (not necessary but harmless)
window._escTimer = {
get remaining(){ return remainingSec; },
get money(){ return money; },
};
// Accessibility: allow keyboard start/stop via space/enter, and mobile-friendly buttons shown
document.addEventListener('keydown', function(e){
if(e.code === 'Space' || e.key === ' '){
e.preventDefault();
if(running) stopTimer(); else startTimer();
}else if(e.key === 's' || e.key === 'S'){
startTimer();
}else if(e.key === 'p' || e.key === 'P'){
stopTimer();
}
});
// When remainingSec becomes 0 due to external adjustments, ensure beep once
let prevRemaining = remainingSec;
setInterval(()=>{
if(prevRemaining > 0 && remainingSec <= 0){
// just reached zero
beep();
timerWrap.style.animation = 'flash 1s';
setTimeout(()=>{ timerWrap.style.animation=''; },1200);
}
prevRemaining = remainingSec;
},200);
})();
</script>
</body>
</html>