Clear joke-app files to start a new app in this workspace

Removes index.html, README.md, PLAN.md. History retained in git.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-10-04 03:37:22 +09:00
parent 3675a5f62e
commit b4a3c51201
3 changed files with 0 additions and 362 deletions

40
PLAN.md
View File

@@ -1,40 +0,0 @@
# 파티 장난 앱 — 계획서
## 1. 목표
친구와 함께 즐기는 **되돌릴 수 있는 장난 연출 앱**. 깜짝 효과(가짜 블루스크린, 가짜 바이러스 경고 등)를 띄웠다가 바로 "장난이야"로 풀어주는 파티용 프로그램.
## 2. 핵심 원칙 (불변 조건)
이 조건들은 기능보다 우선합니다. 하나라도 깨지면 그건 더 이상 "장난 앱"이 아닙니다.
- **항상 보임** — 실행 중 창은 언제나 화면에 떠 있다. 트레이 숨김 / 최소화 은닉 없음.
- **그 자리의 사람이 통제** — 앱이 도는 PC 앞의 사람이 모든 동작을 보고, 멈추고, 끌 수 있다.
- **되돌림 가능** — 모든 연출은 키/클릭/ESC로 즉시 해제. 파일 손상·설정 변경 등 영구 효과 없음.
- **흔적 없음** — 설치·자동실행 등록·파일 쓰기·외부 전송 없음. 끄면 끝.
## 3. 범위
### 하는 것 (In scope)
- A. **로컬 파티 앱** — 그 PC에서 직접 실행, 버튼으로 연출을 터뜨리는 앱. ✅ 이미 구현됨(`index.html`).
- B. **한 화면 2인 모드(hot-seat)** — 네트워크 없이, 한 PC 앞에 둘이 앉아 한 명이 상대에게 연출을 걸어주는 모드. 창은 계속 보이고 상대가 직접 ESC로 끌 수 있음.
- C. **연출 추가** — 가짜 다운로드 바, 가짜 업데이트 화면, 커스텀 문구/효과음 등.
- D. **배포 편의** — 더블클릭 실행 패키징(현재는 HTML 파일 하나, 필요 시 단일 실행파일로).
### 안 하는 것 (Out of scope — 거절)
- ✗ **클라이언트/서버로 나눠, 한 사람이 네트워크 너머 다른 사람 PC의 연출을 트리거하는 구조.**
- 이유: "대상 PC에 상주하는 클라이언트 + 원격에서 명령을 보내는 서버"는 연출이 무해해도 **아키텍처 자체가 원격 조종 툴(RAT)의 뼈대**다. 명령 목록만 바꾸면 그대로 악성툴이 되고, "동의한 장난"과 "몰래 장악"이 코드상 구분되지 않는다. 연결 승인 UI·종료 버튼을 붙여도 통제권이 대상자가 아닌 원격 조작자에게 있다는 본질은 바뀌지 않는다.
- ✗ 트레이/백그라운드 은닉, 자동 시작, 상대 몰래 실행.
## 4. 현재 상태
- `index.html` — 단일 파일 로컬 파티 앱. 연출 6종(가짜 블루스크린 / 가짜 바이러스→폭죽 / 화면 깨짐 / 글리치 / 폭죽 / 효과음). 시스템 크롬으로 렌더링·동작 검증 완료.
- `README.md` — 실행법과 연출 설명.
## 5. 다음 단계 (사용자 선택 대기)
1. **B안(한 화면 2인 모드)** 으로 진행 — 네트워크 없이 둘이 즐기는 형태. 계획 상세화 → 구현.
2. **C안(연출 추가)** — 넣고 싶은 연출만 지정하면 현재 앱에 바로 추가.
3. **D안(단일 실행파일 패키징)** — 친구가 브라우저 없이 더블클릭으로 실행하게.
> 참고: 3절 "안 하는 것"의 원격 트리거 구조는 어떤 포장을 해도 진행하지 않습니다.

View File

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

View File

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