Add visible-window party prank app (no tray/hide)

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>
This commit is contained in:
EJClaw
2026-10-04 03:29:50 +09:00
parent dae9f588e6
commit 7c8c5af4bb
2 changed files with 322 additions and 0 deletions

27
README.md Normal file
View File

@@ -0,0 +1,27 @@
# 🎉 파티 장난 앱
둘이 같이 보면서 터뜨리는 데스크톱 장난 앱입니다. **창은 항상 보이고**, 숨기거나 트레이로 내려가는 기능은 없습니다. 아무것도 설치/저장하지 않고, 창을 닫으면 끝납니다.
## 실행 방법
`index.html` 파일을 더블클릭해서 브라우저로 열면 끝입니다. (크롬/엣지 권장)
전체화면으로 즐기려면 브라우저에서 `F11`을 누르세요.
## 연출 목록
- 💙 가짜 블루스크린 — 윈도우 오류 화면 흉내
- 🦠 가짜 바이러스 경고 — 몇 초 뒤 "ㅋㅋ 장난이야" + 폭죽으로 반전
- 💥 화면 깨짐 — 유리 깨진 효과
- 📺 글리치 — 지직거리는 화면 (자동 종료)
- 🎊 폭죽 / 색종이 — 축하 연출
- 🔊 효과음 — 삑 소리
## 닫기
어떤 연출이든 **아무 키나 누르거나, ESC, 또는 화면 클릭**으로 즉시 닫힙니다. 되돌릴 수 없는 동작은 하나도 없습니다.
## 설계 원칙
- 창을 숨기지 않습니다. 상대가 모르게 돌리는 용도가 아니라, 창을 띄워놓고 함께 즐기는 파티용입니다.
- 파일 쓰기, 자동 실행 등록, 외부 전송 없음. 순수 프런트엔드(HTML 한 파일).

295
index.html Normal file
View File

@@ -0,0 +1,295 @@
<!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>