Single-file HTML prank app the friend runs with the window always visible. Reversible gags only (fake BSOD, fake virus->joke+confetti, screen crack, glitch, fireworks, beep); any key/ESC/click dismisses. No file writes, no autostart, no hiding, no network. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
296 lines
12 KiB
HTML
296 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>🎉 파티 장난 앱</title>
|
|
<style>
|
|
:root { --bg:#0f1117; --panel:#1a1d28; --accent:#5b8cff; --text:#e8eaf0; }
|
|
* { box-sizing: border-box; }
|
|
html, body { margin:0; height:100%; }
|
|
body {
|
|
font-family: "Segoe UI", "Malgun Gothic", system-ui, sans-serif;
|
|
background: var(--bg); color: var(--text);
|
|
display:flex; flex-direction:column; align-items:center;
|
|
min-height:100%; padding:24px;
|
|
}
|
|
.wrap { width:100%; max-width:640px; }
|
|
h1 { font-size:26px; margin:0 0 4px; }
|
|
.sub { color:#9aa0b4; margin:0 0 20px; font-size:14px; line-height:1.5; }
|
|
.banner {
|
|
background:#20351f; border:1px solid #3f7a3a; color:#bfe9ba;
|
|
padding:10px 14px; border-radius:10px; font-size:13px; margin-bottom:20px;
|
|
}
|
|
.grid { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
|
button.gag {
|
|
background: var(--panel); border:1px solid #2b2f3d; color:var(--text);
|
|
padding:18px 14px; border-radius:14px; font-size:16px; cursor:pointer;
|
|
text-align:left; transition:.15s; display:flex; flex-direction:column; gap:4px;
|
|
}
|
|
button.gag:hover { border-color: var(--accent); transform:translateY(-2px); }
|
|
button.gag .emoji { font-size:24px; }
|
|
button.gag small { color:#9aa0b4; font-size:12px; }
|
|
.foot { margin-top:22px; color:#6b7186; font-size:12px; line-height:1.6; }
|
|
|
|
/* Overlay base */
|
|
.overlay {
|
|
position:fixed; inset:0; z-index:9999; display:none;
|
|
align-items:center; justify-content:center; text-align:center;
|
|
}
|
|
.overlay.show { display:flex; }
|
|
.closehint {
|
|
position:fixed; bottom:16px; left:0; right:0; text-align:center;
|
|
font-size:14px; color:rgba(255,255,255,.85); z-index:10000;
|
|
}
|
|
|
|
/* BSOD */
|
|
#bsod { background:#0078d7; color:#fff; flex-direction:column; padding:8vw; }
|
|
#bsod .face { font-size:90px; }
|
|
#bsod h2 { font-weight:400; font-size:min(5vw,34px); line-height:1.4; max-width:800px; }
|
|
#bsod .pct { font-size:min(4vw,26px); margin-top:14px; }
|
|
#bsod .small { font-size:min(2.6vw,15px); color:#cfe6fb; margin-top:26px; max-width:620px; }
|
|
|
|
/* Virus */
|
|
#virus { background:#0a0a0a; flex-direction:column; color:#ff4b4b; }
|
|
#virus.reveal { color:#6ddf6d; }
|
|
#virus .big { font-size:min(8vw,64px); font-weight:800; }
|
|
#virus .scan { font-family:monospace; color:#ff8a8a; margin-top:10px; font-size:min(3.4vw,18px); white-space:pre; }
|
|
#virus .reveal-msg { display:none; font-size:min(6vw,44px); font-weight:800; }
|
|
#virus.reveal .pre-msg { display:none; }
|
|
#virus.reveal .reveal-msg { display:block; }
|
|
@keyframes flash { 0%,100%{opacity:1} 50%{opacity:.25} }
|
|
#virus .big { animation:flash .6s infinite; }
|
|
#virus.reveal .big { animation:none; }
|
|
|
|
/* Crack */
|
|
#crack { background:transparent; }
|
|
#crack svg { width:100vw; height:100vh; }
|
|
|
|
/* Glitch */
|
|
#glitch { background:#000; }
|
|
#glitch .bars { position:absolute; inset:0; overflow:hidden; }
|
|
#glitch .bar { position:absolute; left:0; right:0; height:40px; background:linear-gradient(90deg,#f0f,#0ff,#ff0); mix-blend-mode:screen; }
|
|
|
|
canvas#fx { position:fixed; inset:0; z-index:9998; pointer-events:none; display:none; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrap">
|
|
<h1>🎉 파티 장난 앱</h1>
|
|
<p class="sub">버튼을 누르면 화면에 장난 연출이 뜹니다. 전부 되돌릴 수 있고, 아무 키나 누르거나 ESC를 누르면 즉시 닫힙니다.</p>
|
|
<div class="banner">✅ 이 앱은 숨지 않습니다. 이 창은 항상 보이고, 아무것도 설치하거나 저장하지 않으며, 창을 닫으면 모두 끝납니다.</div>
|
|
|
|
<div class="grid">
|
|
<button class="gag" onclick="bsod()"><span class="emoji">💙</span><span>가짜 블루스크린</span><small>윈도우 오류 화면 연출</small></button>
|
|
<button class="gag" onclick="virus()"><span class="emoji">🦠</span><span>가짜 바이러스 경고</span><small>경고 후 "ㅋㅋ 장난" + 폭죽</small></button>
|
|
<button class="gag" onclick="crack()"><span class="emoji">💥</span><span>화면 깨짐</span><small>유리 깨진 효과</small></button>
|
|
<button class="gag" onclick="glitch()"><span class="emoji">📺</span><span>글리치</span><small>지직거리는 화면</small></button>
|
|
<button class="gag" onclick="party()"><span class="emoji">🎊</span><span>폭죽 / 색종이</span><small>축하 연출</small></button>
|
|
<button class="gag" onclick="beep()"><span class="emoji">🔊</span><span>효과음</span><small>삑 소리</small></button>
|
|
</div>
|
|
|
|
<p class="foot">
|
|
닫기: ESC 또는 아무 키 / 화면 클릭<br>
|
|
이건 둘이 같이 보면서 즐기는 파티용 장난 앱이에요. 상대가 모르게 몰래 돌리는 용도가 아니라, 창을 띄워두고 함께 터뜨리는 용도입니다.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Overlays -->
|
|
<div class="overlay" id="bsod">
|
|
<div class="face">:(</div>
|
|
<h2>PC에 문제가 발생하여 다시 시작해야 합니다.<br>오류 정보를 수집하는 중입니다.</h2>
|
|
<div class="pct">0% 완료</div>
|
|
<div class="small">중지 코드: JOKE_APP_HAHA_GOTCHA<br>(진짜 아니에요! 아무 키나 누르면 닫힙니다 😄)</div>
|
|
</div>
|
|
|
|
<div class="overlay" id="virus">
|
|
<div class="big pre-msg">⚠ 바이러스 감지됨!</div>
|
|
<div class="big reveal-msg">😂 ㅋㅋ 장난이야!</div>
|
|
<div class="scan pre-msg"></div>
|
|
</div>
|
|
|
|
<div class="overlay" id="crack">
|
|
<svg viewBox="0 0 1000 700" preserveAspectRatio="none" id="crackSvg"></svg>
|
|
</div>
|
|
|
|
<div class="overlay" id="glitch"><div class="bars" id="glitchBars"></div></div>
|
|
|
|
<canvas id="fx"></canvas>
|
|
<div class="closehint" id="closehint" style="display:none">아무 키나 누르거나 클릭하면 닫힙니다</div>
|
|
|
|
<script>
|
|
(function(){
|
|
"use strict";
|
|
var active = null; // currently shown overlay id
|
|
var timers = [];
|
|
var audioCtx = null;
|
|
|
|
function clearTimers(){ timers.forEach(clearTimeout); timers.forEach(clearInterval); timers = []; }
|
|
function later(fn, ms){ var t = setTimeout(fn, ms); timers.push(t); return t; }
|
|
function every(fn, ms){ var t = setInterval(fn, ms); timers.push(t); return t; }
|
|
|
|
function show(id){
|
|
closeAll(true);
|
|
active = id;
|
|
document.getElementById(id).classList.add('show');
|
|
document.getElementById('closehint').style.display = 'block';
|
|
}
|
|
|
|
function closeAll(silent){
|
|
clearTimers();
|
|
['bsod','virus','crack','glitch'].forEach(function(id){
|
|
var el = document.getElementById(id);
|
|
el.classList.remove('show','reveal');
|
|
});
|
|
document.getElementById('closehint').style.display = 'none';
|
|
stopFx();
|
|
active = null;
|
|
}
|
|
|
|
// ---- Sound (WebAudio, no asset files) ----
|
|
function ctx(){ if(!audioCtx){ audioCtx = new (window.AudioContext||window.webkitAudioContext)(); } return audioCtx; }
|
|
function tone(freq, ms, type){
|
|
try {
|
|
var c = ctx(); var o = c.createOscillator(); var g = c.createGain();
|
|
o.type = type||'square'; o.frequency.value = freq;
|
|
o.connect(g); g.connect(c.destination);
|
|
g.gain.setValueAtTime(0.18, c.currentTime);
|
|
g.gain.exponentialRampToValueAtTime(0.0001, c.currentTime + ms/1000);
|
|
o.start(); o.stop(c.currentTime + ms/1000);
|
|
} catch(e){}
|
|
}
|
|
|
|
// ---- Gags ----
|
|
window.beep = function(){ tone(880,120); later(function(){tone(660,160);},130); };
|
|
|
|
window.bsod = function(){
|
|
show('bsod');
|
|
tone(220,400,'sawtooth');
|
|
var p = 0, el = document.querySelector('#bsod .pct');
|
|
every(function(){ p = Math.min(100, p + Math.floor(Math.random()*8)+1); el.textContent = p + '% 완료'; }, 450);
|
|
};
|
|
|
|
window.virus = function(){
|
|
show('virus');
|
|
var v = document.getElementById('virus');
|
|
var scan = document.querySelector('#virus .scan');
|
|
var files = ['C:\\Windows\\System32\\kernel32.dll','C:\\Users\\friend\\Documents\\secret.txt','C:\\Program Files\\cat_pics\\','D:\\homework.docx','C:\\Users\\friend\\snacks.db'];
|
|
var i = 0;
|
|
tone(140,200,'sawtooth'); later(function(){tone(140,200,'sawtooth');},260);
|
|
every(function(){ scan.textContent = '검사 중: ' + files[i % files.length]; i++; }, 500);
|
|
later(function(){
|
|
v.classList.add('reveal');
|
|
party();
|
|
tone(523,150); later(function(){tone(659,150);},160); later(function(){tone(784,220);},320);
|
|
}, 3200);
|
|
};
|
|
|
|
window.crack = function(){
|
|
show('crack');
|
|
tone(120,300,'sawtooth');
|
|
var svg = document.getElementById('crackSvg');
|
|
svg.innerHTML = '';
|
|
var cx = 350 + Math.random()*300, cy = 250 + Math.random()*200;
|
|
var lines = '';
|
|
for(var a=0; a<16; a++){
|
|
var ang = (a/16)*Math.PI*2 + Math.random()*0.3;
|
|
var len = 200 + Math.random()*400;
|
|
var x = cx + Math.cos(ang)*len, y = cy + Math.sin(ang)*len;
|
|
lines += '<line x1="'+cx+'" y1="'+cy+'" x2="'+x+'" y2="'+y+'" stroke="rgba(255,255,255,.85)" stroke-width="'+(1+Math.random()*2.5)+'"/>';
|
|
// branches
|
|
var bx = cx + Math.cos(ang)*len*0.5, by = cy + Math.sin(ang)*len*0.5;
|
|
var ba = ang + (Math.random()-0.5);
|
|
lines += '<line x1="'+bx+'" y1="'+by+'" x2="'+(bx+Math.cos(ba)*120)+'" y2="'+(by+Math.sin(ba)*120)+'" stroke="rgba(255,255,255,.55)" stroke-width="1"/>';
|
|
}
|
|
svg.innerHTML = '<circle cx="'+cx+'" cy="'+cy+'" r="18" fill="rgba(255,255,255,.3)"/>' + lines;
|
|
document.getElementById('crack').style.background = 'rgba(0,0,0,.15)';
|
|
};
|
|
|
|
window.glitch = function(){
|
|
show('glitch');
|
|
tone(90,500,'sawtooth');
|
|
var box = document.getElementById('glitchBars');
|
|
box.innerHTML = '';
|
|
every(function(){
|
|
box.innerHTML = '';
|
|
for(var k=0;k<8;k++){
|
|
var b = document.createElement('div');
|
|
b.className = 'bar';
|
|
b.style.top = Math.random()*100 + 'vh';
|
|
b.style.height = (10+Math.random()*60)+'px';
|
|
b.style.opacity = 0.3 + Math.random()*0.6;
|
|
b.style.transform = 'translateX(' + (Math.random()*40-20) + 'px)';
|
|
box.appendChild(b);
|
|
}
|
|
}, 90);
|
|
later(closeAll, 2600);
|
|
};
|
|
|
|
// ---- Confetti / fireworks ----
|
|
var fxCanvas = document.getElementById('fx');
|
|
var fxCtx = fxCanvas.getContext('2d');
|
|
var particles = [];
|
|
var fxRunning = false;
|
|
var fxRaf = null;
|
|
|
|
function sizeCanvas(){ fxCanvas.width = innerWidth; fxCanvas.height = innerHeight; }
|
|
window.addEventListener('resize', sizeCanvas); sizeCanvas();
|
|
|
|
window.party = function(){
|
|
sizeCanvas();
|
|
fxCanvas.style.display = 'block';
|
|
tone(660,120); later(function(){tone(880,160);},130);
|
|
burst(innerWidth*0.3, innerHeight*0.4);
|
|
later(function(){ burst(innerWidth*0.7, innerHeight*0.3); }, 250);
|
|
later(function(){ burst(innerWidth*0.5, innerHeight*0.5); }, 500);
|
|
if(!fxRunning){ fxRunning = true; loopFx(); }
|
|
// auto stop if nothing else active
|
|
later(function(){ if(!active) stopFx(); }, 4000);
|
|
};
|
|
|
|
function burst(x,y){
|
|
var colors = ['#ff4b4b','#ffd93b','#6ddf6d','#5b8cff','#ff7bd5','#7bfff0'];
|
|
for(var i=0;i<80;i++){
|
|
var ang = Math.random()*Math.PI*2, sp = 2+Math.random()*6;
|
|
particles.push({ x:x, y:y, vx:Math.cos(ang)*sp, vy:Math.sin(ang)*sp - 2,
|
|
g:0.12, life:60+Math.random()*40, c:colors[(Math.random()*colors.length)|0], s:3+Math.random()*4 });
|
|
}
|
|
}
|
|
|
|
function loopFx(){
|
|
fxCtx.clearRect(0,0,fxCanvas.width,fxCanvas.height);
|
|
for(var i=particles.length-1;i>=0;i--){
|
|
var p = particles[i];
|
|
p.vy += p.g; p.x += p.vx; p.y += p.vy; p.life--;
|
|
fxCtx.globalAlpha = Math.max(0, p.life/80);
|
|
fxCtx.fillStyle = p.c;
|
|
fxCtx.fillRect(p.x, p.y, p.s, p.s);
|
|
if(p.life<=0 || p.y>fxCanvas.height+20) particles.splice(i,1);
|
|
}
|
|
fxCtx.globalAlpha = 1;
|
|
if(particles.length>0 || active){ fxRaf = requestAnimationFrame(loopFx); }
|
|
else { fxRunning = false; fxCanvas.style.display='none'; }
|
|
}
|
|
|
|
function stopFx(){
|
|
particles = [];
|
|
if(fxRaf) cancelAnimationFrame(fxRaf);
|
|
fxRunning = false;
|
|
fxCtx.clearRect(0,0,fxCanvas.width,fxCanvas.height);
|
|
fxCanvas.style.display='none';
|
|
}
|
|
|
|
// ---- Close on any key / click / ESC ----
|
|
function dismiss(){ if(active || fxRunning) closeAll(); }
|
|
window.addEventListener('keydown', function(e){ if(active){ e.preventDefault(); closeAll(); } });
|
|
window.addEventListener('click', function(e){
|
|
if(active){
|
|
// ignore clicks on the control buttons themselves (they don't fire while overlay covers them)
|
|
closeAll();
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|