아래 코드는
콘솔에서 직접 실행되는 연극(scene)입니다.
페이지는 반응하지만, 실행은 오직 당신의 개입으로만 시작됩니다.
// --- Scene Overlay ---
// 무대 위를 덮는 검은 막.
// 공연이 시작되었음을 관객에게 알리는 첫 시각적 신호.
function showOverlay() {
// 검은 막을 하나 만든다.
const overlay = document.createElement("div");
// 이 막은 다시는 다른 것과 혼동되지 않는다.
overlay.id = "__scene_overlay__";
// 무대의 규칙:
// - 화면 전체를 덮는다
// - 검은 배경
// - 현실 UI 위에 겹쳐진다
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
opacity: 0.8;
color: #ddd;
z-index: 99999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 14px;
`;
// 막이 오른다.
overlay.innerHTML = `
scene playing...
기계는 매순간 확신을 주장합니다.
인간은 종종 기억하고 주저하고 결정합니다.
the scene is unfolding…
`;
// 무대에 막을 붙인다.
document.body.appendChild(overlay);
//막이 천천히 올라간다. (Fade in 효과)
requestAnimationFrame(() => {
requestAnimationFrame(() => {
overlay.style.opacity = "1";
});
});
}
// --- 불가해한 상수 ---
// 계산이 실패했을 때,
// 우리는 실패를 값으로 취급한다.
const ELUSIVE = Symbol("ELUSIVE...CALCULATION IS IMPOSSIBLE");
// --- Sign ---
// 모든 사건은 표식(Sign)으로 남는다.
// 의미보다 기록이 먼저다.
class Sign {
constructor(label, payload = {}) {
this.label = label; // 사건의 이름
this.timestamp = Date.now(); // 발생 시각
this.payload = payload; // 사건의 잔여물
}
// 로그용 형태로 변환
toLog() {
return { label: this.label, t: this.timestamp, p: this.payload };
}
}
// --- Chorus (Event Bus) ---
// 무대 뒤에서 모든 소식을 전달하는 합창단.
class Chorus {
constructor() {
this.listeners = [];
}
// 사건을 알린다.
emit(sign) {
for (const l of this.listeners) l(sign);
}
// 누군가는 항상 듣고 있다.
subscribe(fn) {
this.listeners.push(fn);
}
}
const chorus = new Chorus();
// --- Misrecognition Proxy with timing ---
// 모든 액터는 약간씩 오해한다.
// 타이밍도, 의도도 어긋난다.
const misrecognize = (obj) =>
new Proxy(obj, {
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
// 함수 호출에는 지연과 오판이 개입된다.
if (typeof value === "function") {
return async (...args) => {
const delay = 50 + Math.random() * 350;
await new Promise((r) => setTimeout(r, delay));
if (Math.random() < 0.3)
console.log(
`%c[MISRECOGNITION] ⚠ ${target.constructor.name}.${prop} misreads`,
"font-family: 'Playwrite GB J Guides', cursive;"
);
return value.apply(target, args);
};
}
return value;
},
});
// --- Actors ---
// 각 액터는 자신의 확신을 가지고 등장한다.
class IDE {
constructor(name = "IDE") {
this.name = name;
this.cursor = { x: 0 };
}
// IDE는 언제나 제안한다.
suggest(context = "") {
const suggestion = context.includes("test") ? "refactor()" : "continue()";
const sign = new Sign("IDE.suggest", { suggestion, context });
chorus.emit(sign);
console.log(
`%c[IDE] proposes "${suggestion}" (cursor at ${this.cursor.x.toFixed(2)})`,
"font-family: 'Playwrite GB J Guides', cursive;"
);
return sign;
}
}
class Finger {
constructor(name = "Finger") {
this.name = name;
}
// 참지 못하고 탭을 누르는 손가락은 언제나 불평한다.
tapTab() {
const sign = new Sign("Finger.tapTab", { action: "tab" });
chorus.emit(sign);
console.log(
`%c[Finger] taps TAB [IMPATIENTLY!!!!]`,
"font-family: 'Playwrite GB J Guides', cursive;"
);
return sign;
}
}
class Hawkeye {
constructor() {
this.name = "Hawkeye";
}
// 정확하다고 주장하는 호크아이는,
// 필연적으로 애매하다.
observe(ball) {
const p = ball.position ? { ...ball.position } : { note: "no-position" };
const ambiguous = Math.random() < 0.2;
const outcome = ambiguous ? "indecision" : p.x > 0.5 ? "out" : "in";
const sign = new Sign("Hawkeye.judge", { outcome, raw: p });
chorus.emit(sign);
console.log(
`%c[Hawkeye] sees ball x=${p.x.toFixed(3)} => ${outcome.toUpperCase()}`,
"font-family: 'Playwrite GB J Guides', cursive;"
);
return sign;
}
}
class TestSuite {
constructor() {
this.name = "TestSuite";
}
// 기계들을 점검하는 테스트.
run(subjectSign) {
const pass =
!!subjectSign &&
subjectSign.payload &&
subjectSign.payload.suggestion !== "bug()";
const sign = new Sign("TestSuite.report", {
pass,
subject: subjectSign.label,
});
chorus.emit(sign);
console.log(
`%c[TestSuite] evaluates "${
subjectSign.payload.suggestion || subjectSign.payload.outcome
}" => ${pass ? "PASS" : "FAIL"}`,
"font-family: 'Playwrite GB J Guides', cursive;"
);
return sign;
}
}
class Human {
constructor() {
this.name = "Human";
this._internal = { decisionCount: 0 };
}
// 인간은 종종 기억하고, 주저하고, 결정한다.
actOn(sign) {
this._internal.decisionCount++;
const decision =
sign && sign.payload && sign.payload.outcome
? sign.payload.outcome === "out"
? "pick-up"
: "ignore"
: ELUSIVE;
const signOut = new Sign("Human.act", {
decision:
decision === ELUSIVE ? "ELUSIVE...CALCULATION IS IMPOSSIBLE" : decision,
});
chorus.emit(signOut);
console.log(
`%c[Human] reacts with "${signOut.payload.decision}"`,
"font-family: 'Playwrite GB J Guides', cursive;"
);
return signOut;
}
}
class Ball {
constructor(x = 0.5) {
this.position = { x };
}
roll() {
const delta = (Math.random() - 0.5) * 0.05;
this.position.x = Math.max(0, Math.min(1, this.position.x + delta));
return this.position.x;
}
}
class Line {
constructor(threshold = 0.5) {
this.threshold = threshold;
}
asSign(ball) {
const crossed = Math.random() < 0.5;
return new Sign("Line.signal", { crossed, position: { ...ball.position } });
}
}
// --- Sets ---
// 무대에 오르기 전, 대기실
const ide = misrecognize(new IDE());
const finger = misrecognize(new Finger());
const hawkeye = misrecognize(new Hawkeye());
const tests = misrecognize(new TestSuite());
const human = misrecognize(new Human());
const line = misrecognize(new Line());
const ball = misrecognize(new Ball());
// --- 무대 조명 색상표 ---
// 각 사건은 고유한 색으로 불린다.
// 관객은 색으로 역할을 구분한다.
const getLabelColor = (label) => {
const colorMap = {
"IDE.suggest": "\x1b[44m", // 파랑: 제안
"Finger.tapTab": "\x1b[41m", // 빨강: 개입
"Hawkeye.judge": "\x1b[42m", // 초록: 판단
"TestSuite.report": "\x1b[43m", // 노랑: 검증
"Human.act": "\x1b[45m", // 자주: 반응
"Line.signal": "\x1b[46m", // 청록: 경계
"System.final": "\x1b[47m", // 흰색: 종료
};
return colorMap[label] || "\x1b[44m"; // default: blue
};
const sprinkleBalls = (text) => {
if (typeof text !== "string") {
return text;
}
return text
.split("")
.map((ch) =>
Math.random() < 0.05 ? "--------------------------------🎾" : ch,
)
.join("");
};
chorus.subscribe((sign) => {
const timestamp = new Date(sign.timestamp).toISOString();
const bgColor = `\x1b[47m`;
const labelColor = getLabelColor(sign.label);
const reset = "\x1b[0m";
const sprinkledPayload = sprinkleBalls(timestamp);
console.log(
`%c[🔔ANNOUNCEMENT] ${sign.label} @${timestamp}`,
"font-family: 'Playwrite GB J Guides', cursive;",
sign.payload
);
console.log(
`%c${sprinkledPayload}`,
"font-size: 14px; color: #73ff00; font-family: 'Playwrite GB J Guides', cursive;"
);
});
// --- Scene Loop ---
// 이 함수가 호출되는 순간,
// 연극은 되돌릴 수 없다.
async function runScene() {
// 막을 올린다.
showOverlay();
// DOM이 완전히 렌더링될 때까지 대기
await new Promise((r) => setTimeout(r, 100));
// 공연의 시작 시간
const startTime = Date.now();
// 공연의 지속 시간
const duration = 30000;
// 장면 번호
let sceneNum = 1;
// --- Scene 1 ---
// IDE와 Finger의 첫 충돌
console.log(
`%c===== Scene ${sceneNum}: IDE vs Finger (Tab Competition) =====`,
"background: ivory; color: #b00000; padding: 8px; font-family: 'Playwrite GB J Guides', cursive;"
);
await Promise.all([ide.suggest("writing code"), finger.tapTab()]);
sceneNum++;
while (Date.now() - startTime < duration) {
console.log(
`%c===== Scene ${sceneNum}: Ball chaos & Line signals =====`,
"background: ivory; color: #b00000; padding: 8px; font-family: 'Playwrite GB J Guides', cursive;"
);
// 공이 굴러간다.
ball.roll();
// 공이 선을 지나갈 때, 경계 사건이 발생한다.
const lineSign = await line.asSign(ball);
chorus.emit(lineSign);
// 짧은 정적
await new Promise((r) => setTimeout(r, 80 + Math.random() * 150));
console.log(
`%c===== Scene ${sceneNum + 1}: Hawkeye observes =====`,
"background: ivory; color: #b00000; padding: 8px; font-family: 'Playwrite GB J Guides', cursive;"
);
const hawkeyeSign = await hawkeye.observe(ball);
await new Promise((r) => setTimeout(r, 50 + Math.random() * 200));
if (Math.random() < 0.7) {
console.log(
`%c===== Scene ${sceneNum + 2}: TestSuite checks multiple signals =====`,
"background: ivory; color: #b00000; padding: 8px; font-family: 'Playwrite GB J Guides', cursive;"
);
// 판단과 제안을 동시에 검증한다.
await Promise.all([
tests.run(hawkeyeSign),
tests.run(await ide.suggest("optimize test")),
]);
}
await new Promise((r) => setTimeout(r, 80 + Math.random() * 300));
// 인간은 가만히 있지 못한다.
if (Math.random() < 0.4) {
console.log(
`%c===== Scene ${sceneNum + 3}: Finger intervenes =====`,
"background: ivory; color: #b00000; padding: 8px; font-family: 'Playwrite GB J Guides', cursive;"
);
await finger.tapTab();
}
await new Promise((r) => setTimeout(r, 50 + Math.random() * 250));
if (Math.random() < 0.5) {
console.log(
`%c===== Scene ${sceneNum + 4}: Human reacts dynamically =====`,
"background: ivory; color: #b00000; padding: 8px; font-family: 'Playwrite GB J Guides', cursive;"
);
await Promise.all([human.actOn(lineSign), human.actOn(hawkeyeSign)]);
}
await new Promise((r) => setTimeout(r, 50 + Math.random() * 200));
if (Math.random() < 0.3) {
console.log(
`%c===== Scene ${sceneNum + 5}: IDE spontaneous suggestion =====`,
"background: ivory; color: #b00000; padding: 8px; font-family: 'Playwrite GB J Guides', cursive;"
);
await ide.suggest("refactor code");
}
await new Promise((r) => setTimeout(r, 100 + Math.random() * 200));
sceneNum += 6;
}
// --- Epilogue ---
// 모든 것이 끝난 뒤
console.log(
"%c===== Epilogue: 30s of chaos concludes ======",
"background: ivory; color: #b00000; padding: 8px; font-family: 'Playwrite GB J Guides', cursive;"
);
const finalMessage = `
⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
⚠ ⚠
⚠ ████████████████████████ TERMINATED █████████████████████████████████████ ⚠
⚠ █ █ █ █ █ ALL ACTORS EXHAUSTED █ █ █ █ ⚠
⚠ ███████████████████████████████████████████████████████████████████████████ ⚠
⚠ ███████████████████████████████████████████████████████████████████████████ ⚠
⚠ ⚠
⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
`;
console.log( "%c\n" + finalMessage, "color: ivory; background: #b00000; padding: 8px; font-family: 'Playwrite GB J Guides', cursive;");
// 마지막 신호를 남긴다.
const final = new Sign("System.final", {
note: "Performance terminated. All actors exhausted.",
});
chorus.emit(final);
// 무대 종료
window.__SCENE_DONE__();
}
window.startScene = runScene;
console.info(
"%cScene is ready! Type startScene() to begin.",
"color: ivory; background: #6b0000; padding: 18px; text-decoration: underline; font-family: 'Playwrite GB J Guides', cursive;",
);