ELUSIVE.html

아래 코드는 콘솔에서 직접 실행되는 연극(scene)입니다.
페이지는 반응하지만, 실행은 오직 당신의 개입으로만 시작됩니다.

┌───── 관람 안내 ─────┐

EN
  1. Chrome 또는 Safari 브라우저에서 이 페이지를 열어주세요
  2. 키보드 단축키로 개발자 도구를 여세요
    Mac: Cmd + Option + I (세 키를 동시에)
    Windows: Ctrl + Shift + I (세 키를 동시에)
    → 화면 오른쪽이나 아래에 패널이 열립니다
  3. 열린 패널 상단의 탭 중 Console 을 클릭하세요
    → 빈 입력창이 보이면 성공입니다
  4. 아래 코드 블록의 코드 복사 버튼을 눌러 코드를 복사하세요
  5. Console 입력창을 클릭한 뒤 Cmd+V (Mac) 또는 Ctrl+V (Windows) 로 붙여넣고 Enter
  6. 다시 Console 입력창에 startScene() 을 직접 타이핑하고 Enter
  7. 콘솔이 건네는 말들을 읽어주세요 — 당신의 관심이 이 연극을 움직입니다
  8. 연극이 시작됩니다...

    // --- 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;", );
© 2026 김지우 Jiwoo Joy Kim. Licensed under CC BY-NC-SA 4.0.