1527 lines
72 KiB
HTML
1527 lines
72 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>ROBOT REPAIR: MEMORY DECRYPTOR V2</title>
|
||
<style>
|
||
:root {
|
||
--phosphor-primary: #33ff00;
|
||
--phosphor-dim: #1a8000;
|
||
--phosphor-bg: #0a150a;
|
||
--cell-size: 45px;
|
||
--strip-bg: rgba(240, 240, 230, 0.95);
|
||
--strip-text: #111;
|
||
}
|
||
|
||
body {
|
||
background-color: #000;
|
||
color: var(--phosphor-primary);
|
||
font-family: 'Courier New', Courier, monospace;
|
||
margin: 0;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
user-select: none;
|
||
}
|
||
|
||
/* CRT Screen */
|
||
.crt-container {
|
||
position: relative;
|
||
width: 98vw;
|
||
height: 98vh;
|
||
background-color: var(--phosphor-bg);
|
||
border: 10px solid #333;
|
||
border-radius: 4px;
|
||
box-shadow: 0 0 20px var(--phosphor-dim), inset 0 0 50px #000;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 20px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.scanlines {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0) 50%, rgba(0,0,0,0.2) 50%, rgba(0,0,0,0.2));
|
||
background-size: 100% 3px;
|
||
pointer-events: none;
|
||
z-index: 999;
|
||
opacity: 0.15;
|
||
}
|
||
|
||
/* Header */
|
||
header {
|
||
border-bottom: 2px solid var(--phosphor-primary);
|
||
padding-bottom: 10px;
|
||
margin-bottom: 10px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
z-index: 110;
|
||
height: 30px;
|
||
align-items: center;
|
||
}
|
||
|
||
.main-area {
|
||
display: flex;
|
||
flex: 1;
|
||
gap: 20px;
|
||
position: relative;
|
||
opacity: 0; /* Hidden initially for boot sequence */
|
||
transition: opacity 1s;
|
||
}
|
||
|
||
.main-area.active {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* Grid System */
|
||
.grid-container {
|
||
display: grid;
|
||
grid-template-columns: repeat(20, var(--cell-size)); /* 20x20 Grid */
|
||
grid-template-rows: repeat(20, var(--cell-size));
|
||
gap: 2px;
|
||
border: 1px solid var(--phosphor-dim);
|
||
padding: 5px;
|
||
overflow: hidden;
|
||
flex: 1; /* Occupy mostly all space */
|
||
}
|
||
|
||
.grid-cell {
|
||
width: var(--cell-size);
|
||
height: var(--cell-size);
|
||
border: 1px solid rgba(50, 255, 0, 0.05); /* Very subtle border */
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
text-align: center;
|
||
font-size: 13px;
|
||
transition: all 0.2s cubic-bezier(0.1, 0.7, 1.0, 0.1);
|
||
position: relative;
|
||
background: rgba(0, 10, 0, 0.2);
|
||
color: var(--phosphor-primary);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Loading State Style */
|
||
.grid-cell.loading {
|
||
background: var(--phosphor-primary);
|
||
color: #000;
|
||
filter: none !important;
|
||
opacity: 1 !important;
|
||
transform: scale(1) !important;
|
||
text-shadow: none !important;
|
||
}
|
||
|
||
/* Filter Effects applied by JS inline styles usually, but classes for base states */
|
||
.cell-blur {
|
||
filter: blur(6px);
|
||
opacity: 0.15;
|
||
transform: scale(0.9);
|
||
}
|
||
|
||
.cell-focus {
|
||
/* Handled by JS dynamic styling mostly */
|
||
}
|
||
|
||
/* Decoder Strip (SCI-FI Look) */
|
||
.decoder-strip {
|
||
position: absolute;
|
||
top: 50px;
|
||
left: 50px;
|
||
/* Size based on 5x3 grid area */
|
||
width: calc(var(--cell-size) * 5 + 10px);
|
||
height: calc(var(--cell-size) * 3 + 10px);
|
||
/* Background will be created by pseudo-element to allow holes */
|
||
background: transparent; /* No background on main element */
|
||
border: 1px solid cyan;
|
||
box-shadow: 0 0 15px rgba(0, 255, 255, 0.3);
|
||
color: cyan;
|
||
z-index: 200;
|
||
cursor: grab;
|
||
display: none; /* Hidden initially, shown when filtering starts */
|
||
flex-direction: column;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
font-family: 'Courier New', Courier, monospace;
|
||
font-weight: bold;
|
||
border-radius: 4px;
|
||
transition: transform 0.1s, box-shadow 0.1s, opacity 0.5s;
|
||
}
|
||
|
||
.decoder-strip::before {
|
||
content: none; /* Removed for SVG approach */
|
||
}
|
||
|
||
.decoder-bg-svg {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0;
|
||
pointer-events: none;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.decoder-strip.visible {
|
||
display: flex;
|
||
animation: slideIn 0.8s ease-out;
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateX(-100px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateX(0);
|
||
}
|
||
}
|
||
|
||
.decoder-strip:active {
|
||
cursor: grabbing;
|
||
transform: scale(1.02);
|
||
box-shadow: 0 0 25px rgba(0, 255, 255, 0.5);
|
||
}
|
||
|
||
.strip-content {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
/* Holes and Text on Strip */
|
||
.strip-hole {
|
||
position: absolute;
|
||
width: var(--cell-size);
|
||
height: var(--cell-size);
|
||
/* Holes are completely transparent - they create "windows" */
|
||
background: transparent !important;
|
||
pointer-events: none;
|
||
z-index: 10; /* Above the opaque ::before background */
|
||
/* Use outline for border effect without blocking content */
|
||
outline: 2px solid cyan;
|
||
outline-offset: -2px;
|
||
box-shadow: 0 0 8px cyan;
|
||
/* Ensure no background or border blocks the view */
|
||
border: none;
|
||
/* The hole is transparent, allowing grid cells below to show through */
|
||
}
|
||
|
||
/* Cut holes out of the background using mask */
|
||
/* .decoder-strip::before logic moved to SVG */
|
||
|
||
.strip-text {
|
||
position: absolute;
|
||
font-size: 12px;
|
||
line-height: 1.2;
|
||
pointer-events: none;
|
||
white-space: nowrap;
|
||
color: cyan;
|
||
text-shadow: 0 0 8px cyan, 0 0 3px rgba(0, 255, 255, 0.8);
|
||
font-weight: bold;
|
||
z-index: 5; /* Above background but below holes */
|
||
}
|
||
|
||
.strip-text {
|
||
position: absolute;
|
||
font-size: 12px;
|
||
line-height: 1.2;
|
||
pointer-events: none;
|
||
white-space: nowrap;
|
||
color: cyan;
|
||
text-shadow: 0 0 8px cyan, 0 0 3px rgba(0, 255, 255, 0.8);
|
||
font-weight: bold;
|
||
z-index: 5; /* Above background but below holes */
|
||
}
|
||
|
||
/* Controls Area */
|
||
.controls {
|
||
width: 260px; /* Slightly wider */
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
z-index: 110;
|
||
}
|
||
|
||
.controls.disabled {
|
||
pointer-events: none;
|
||
opacity: 0.5;
|
||
filter: grayscale(1);
|
||
}
|
||
|
||
.slider-container {
|
||
margin-top: 10px;
|
||
background: rgba(0,20,0,0.4);
|
||
padding: 10px;
|
||
border: 1px solid var(--phosphor-dim);
|
||
}
|
||
|
||
input[type="range"] {
|
||
width: 100%;
|
||
-webkit-appearance: none;
|
||
background: transparent;
|
||
margin: 15px 0;
|
||
}
|
||
|
||
input[type="range"]::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
height: 20px;
|
||
width: 12px;
|
||
background: var(--phosphor-primary);
|
||
cursor: pointer;
|
||
margin-top: -8px;
|
||
box-shadow: 0 0 10px var(--phosphor-primary);
|
||
border: 1px solid #fff;
|
||
}
|
||
|
||
input[type="range"]::-webkit-slider-runnable-track {
|
||
width: 100%;
|
||
height: 2px;
|
||
background: var(--phosphor-dim);
|
||
}
|
||
|
||
.frequency-display {
|
||
font-size: 32px;
|
||
text-align: center;
|
||
border: 2px solid var(--phosphor-primary);
|
||
padding: 10px;
|
||
margin-bottom: 5px;
|
||
background: rgba(0,20,0,0.9);
|
||
font-weight: bold;
|
||
letter-spacing: 2px;
|
||
}
|
||
|
||
.signal-indicator {
|
||
height: 4px;
|
||
background: #111;
|
||
width: 100%;
|
||
margin-top: 5px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.signal-bar {
|
||
height: 100%;
|
||
width: 0%;
|
||
background: var(--phosphor-primary);
|
||
box-shadow: 0 0 10px var(--phosphor-primary);
|
||
transition: width 0.1s;
|
||
}
|
||
|
||
.log-area {
|
||
flex: 1;
|
||
border: 1px solid var(--phosphor-dim);
|
||
padding: 10px;
|
||
font-size: 11px;
|
||
overflow-y: auto;
|
||
color: #8f8;
|
||
background: rgba(0,0,0,0.8);
|
||
font-family: 'Consolas', monospace;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
/* Boot Screen Overlay */
|
||
#boot-screen {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: #000;
|
||
z-index: 2000;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: flex-end;
|
||
padding: 40px;
|
||
box-sizing: border-box;
|
||
font-size: 16px;
|
||
color: var(--phosphor-primary);
|
||
/* pointer-events will be set to none after boot completes */
|
||
}
|
||
|
||
#start-prompt {
|
||
pointer-events: auto; /* Enable clicks on the button */
|
||
}
|
||
|
||
</style>
|
||
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="crt-container">
|
||
<div class="scanlines"></div>
|
||
|
||
<!-- Boot Sequence Overlay -->
|
||
<div id="boot-screen">
|
||
<div id="start-prompt" style="text-align: center; margin-bottom: 20%">
|
||
<div style="font-size: 40px; margin-bottom: 20px; text-shadow: 0 0 20px var(--phosphor-primary);">SYSTEM HALTED</div>
|
||
<button onclick="startSystem()" style="font-size: 24px; padding: 15px 40px; background: var(--phosphor-primary); color: #000; border: none; font-family: inherit; font-weight: bold; cursor: pointer; box-shadow: 0 0 30px var(--phosphor-primary);">
|
||
[ INITIALIZE REBOOT ]
|
||
</button>
|
||
</div>
|
||
<div id="boot-log" style="display:none"></div>
|
||
</div>
|
||
|
||
<header>
|
||
<div>MEM-DEC SYSTEM V3.2 [WAITING_FOR_USER]</div>
|
||
<div>TARGET: JOKE_MODULE_DUMP</div>
|
||
</header>
|
||
|
||
<div class="main-area" id="main-area">
|
||
<!-- Word Grid -->
|
||
<div id="grid" class="grid-container"></div>
|
||
|
||
<!-- Draggable Strip (Hidden until filtering starts) -->
|
||
<div id="decoder" class="decoder-strip">
|
||
<!-- Content populated by JS -->
|
||
</div>
|
||
|
||
<!-- Sidebar -->
|
||
<div class="controls" id="controls">
|
||
<div class="frequency-display">
|
||
<span id="freq-val">00.0</span> <span style="font-size:14px">Hz</span>
|
||
</div>
|
||
|
||
<div class="slider-container">
|
||
<label>EMOTION TUNER</label>
|
||
<input type="range" min="0" max="100" step="0.1" value="50" id="emotion-slider" disabled>
|
||
<div class="signal-indicator">
|
||
<div id="signal-strength" class="signal-bar"></div>
|
||
</div>
|
||
<div style="display: flex; justify-content: space-between; font-size: 10px; color: #666; margin-top: 5px;">
|
||
<span>JOY</span>
|
||
<span>SAD</span>
|
||
<span>ANG</span>
|
||
<span>FEAR</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top: 10px;">
|
||
<button id="btn-filter" onclick="startFiltering()" disabled style="width: 100%; padding: 15px; background: #333; color: #555; font-weight: bold; border: 1px solid #444; cursor: not-allowed; transition: all 0.3s; margin-bottom: 10px;">
|
||
[ 启动过滤分析 ]
|
||
</button>
|
||
<button id="btn-verify" onclick="checkMatch()" disabled style="width: 100%; padding: 15px; background: #333; color: #555; font-weight: bold; border: 1px solid #444; cursor: not-allowed; transition: all 0.3s;">
|
||
VERIFY ALIGNMENT
|
||
</button>
|
||
</div>
|
||
|
||
<div id="log" class="log-area">
|
||
> WAITING FOR MANUAL START...
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// === CONFIGURATION ===
|
||
const GRID_W = 20; // 20x20 Grid
|
||
const GRID_H = 20;
|
||
const CELL_PX = 45; // Adjusted for 20x20
|
||
const GAP = 2;
|
||
|
||
// Emotions map to frequency ranges (0-100)
|
||
const EMOTIONS = {
|
||
JOY: { val: 15, color: '#ffff00', name: 'JOY' },
|
||
SADNESS: { val: 40, color: '#00aaff', name: 'SADNESS' },
|
||
ANGER: { val: 65, color: '#ff3333', name: 'ANGER' },
|
||
FEAR: { val: 90, color: '#aa00ff', name: 'FEAR' },
|
||
NEUTRAL: { val: -999, color: '#888', name: 'NOISE' }
|
||
};
|
||
|
||
// Puzzle template: "XX要来了,不合格XX被骂了,这让我很XX"
|
||
// Final answer: "临检要来了,不合格英里被骂了,这让我很担心"
|
||
// Holes: "临检" (hole1), "英里" (hole2), "担心" (hole3)
|
||
// Layout: Non-linear but readable, all cells are 2 characters
|
||
const DECODER_DATA = [
|
||
{ type: 'hole', r: 0, c: 0 }, // Hole 1: "临检"
|
||
{ type: 'text', r: 0, c: 1, text: "要" },
|
||
{ type: 'text', r: 0, c: 2, text: "来了" },
|
||
{ type: 'text', r: 0, c: 3, text: "," },
|
||
{ type: 'text', r: 0, c: 4, text: "不合" },
|
||
{ type: 'text', r: 1, c: 0, text: "格" },
|
||
{ type: 'hole', r: 1, c: 1 }, // Hole 2: "英里"
|
||
{ type: 'text', r: 1, c: 2, text: "被" },
|
||
{ type: 'text', r: 1, c: 3, text: "骂" },
|
||
{ type: 'text', r: 1, c: 4, text: "了" },
|
||
{ type: 'text', r: 2, c: 0, text: "," },
|
||
{ type: 'text', r: 2, c: 1, text: "这" },
|
||
{ type: 'text', r: 2, c: 2, text: "让我" },
|
||
{ type: 'text', r: 2, c: 3, text: "很" },
|
||
{ type: 'hole', r: 2, c: 4 }, // Hole 3: "担心"
|
||
];
|
||
|
||
// Expanded Targets - Each emotion has at least 3 solutions
|
||
// All words are exactly 2 characters - 4-character words split into 2 cells
|
||
// Added 'cover' property for the "Masking" mechanic - covers are jokes
|
||
const TARGETS = [
|
||
// FEAR - Solution 1 (Final Answer: "临检要来了,不合格英里被骂了,这让我很担心")
|
||
// Decoder strip holes: (0,0), (1,1), (2,4)
|
||
// If strip is at (3,5), holes will be at: (3,5), (4,6), (5,9)
|
||
// Jokes for covers:
|
||
// "临检" -> "临检来了大家都很紧张只有程序员说终于可以检查bug了" -> cover: "检查"
|
||
// "英里" -> "你知道为什么XX在XX的时候突然XX吗因为英里在背后" -> cover: "背后"
|
||
// "担心" -> "我担心代码会出bug结果真的出了我的担心是对的" -> cover: "对的"
|
||
{
|
||
name: "Fear Solution 1",
|
||
emotion: EMOTIONS.FEAR,
|
||
matches: [
|
||
{ r: 3, c: 5, word: "临检", cover: "检查" }, // Hole 1 at (0,0) relative to strip
|
||
{ r: 4, c: 6, word: "英里", cover: "背后" }, // Hole 2 at (1,1) relative to strip
|
||
{ r: 5, c: 9, word: "担心", cover: "对的" } // Hole 3 at (2,4) relative to strip
|
||
],
|
||
stripPos: { r: 3, c: 5 } // Strip position aligns with first hole - ONLY CORRECT ANSWER
|
||
},
|
||
// FEAR - Decoy 1 (Close but misaligned - only 2 holes can match)
|
||
// If strip at (3,4), holes would be at: (3,4), (4,5), (5,8)
|
||
// Matches: (3,5), (4,6), (5,9) - close but off by 1 column
|
||
{
|
||
name: "Fear Decoy 1",
|
||
emotion: EMOTIONS.FEAR,
|
||
matches: [
|
||
{ r: 3, c: 5, word: "临检", cover: "检查" },
|
||
{ r: 4, c: 6, word: "英里", cover: "背后" },
|
||
{ r: 5, c: 9, word: "担心", cover: "对的" }
|
||
],
|
||
stripPos: { r: 3, c: 4 } // Misaligned - cannot match all 3 holes
|
||
},
|
||
// FEAR - Decoy 2 (Close but misaligned - only 1 hole can match)
|
||
// If strip at (2,5), holes would be at: (2,5), (3,6), (4,9)
|
||
// Matches: (3,5), (4,6), (5,9) - close but off by 1 row
|
||
{
|
||
name: "Fear Decoy 2",
|
||
emotion: EMOTIONS.FEAR,
|
||
matches: [
|
||
{ r: 3, c: 5, word: "临检", cover: "检查" },
|
||
{ r: 4, c: 6, word: "英里", cover: "背后" },
|
||
{ r: 5, c: 9, word: "担心", cover: "对的" }
|
||
],
|
||
stripPos: { r: 2, c: 5 } // Misaligned - cannot match all 3 holes
|
||
},
|
||
// FEAR - Decoy 3 (Different words, same area, misaligned)
|
||
// If strip at (3,6), holes would be at: (3,6), (4,7), (5,10)
|
||
// Matches: (3,5), (4,6), (5,9) - misaligned
|
||
{
|
||
name: "Fear Decoy 3",
|
||
emotion: EMOTIONS.FEAR,
|
||
matches: [
|
||
{ r: 3, c: 5, word: "临检", cover: "检查" },
|
||
{ r: 4, c: 6, word: "英里", cover: "背后" },
|
||
{ r: 5, c: 9, word: "担心", cover: "对的" }
|
||
],
|
||
stripPos: { r: 3, c: 6 } // Misaligned - cannot match all 3 holes
|
||
},
|
||
// FEAR - Solution 2 (Decoy - cannot fully match decoder strip holes)
|
||
// If strip at (8,1), holes would be at: (8,1), (9,2), (10,5)
|
||
// But matches are at: (8,2), (8,3), (9,1), (10,3) - only 1-2 can align
|
||
{
|
||
name: "Fear Solution 2",
|
||
emotion: EMOTIONS.FEAR,
|
||
matches: [
|
||
{ r: 8, c: 2, word: "紧急", cover: "下午" },
|
||
{ r: 8, c: 3, word: "会议", cover: "茶会" },
|
||
{ r: 9, c: 1, word: "老板", cover: "管理" },
|
||
{ r: 10, c: 3, word: "焦虑", cover: "过热" }
|
||
],
|
||
stripPos: { r: 8, c: 1 } // Only 1-2 matches can align, not all 3 holes
|
||
},
|
||
// FEAR - Solution 3 (Decoy - cannot fully match decoder strip holes)
|
||
// If strip at (12,9), holes would be at: (12,9), (13,10), (14,13)
|
||
// But matches are at: (12,10), (12,11), (13,9), (14,11) - misaligned
|
||
{
|
||
name: "Fear Solution 3",
|
||
emotion: EMOTIONS.FEAR,
|
||
matches: [
|
||
{ r: 12, c: 10, word: "截止", cover: "节日" },
|
||
{ r: 12, c: 11, word: "日期", cover: "时间" },
|
||
{ r: 13, c: 9, word: "同事", cover: "变量" },
|
||
{ r: 14, c: 11, word: "恐慌", cover: "溢出" }
|
||
],
|
||
stripPos: { r: 12, c: 9 } // Misaligned - cannot match all 3 holes
|
||
},
|
||
// SADNESS - Solution 1 (Decoy - cannot fully match decoder strip holes)
|
||
// If strip at (5,11), holes would be at: (5,11), (6,12), (7,15)
|
||
// But matches are at: (5,12), (5,13), (6,11), (7,13) - misaligned
|
||
{
|
||
name: "Sadness Solution 1",
|
||
emotion: EMOTIONS.SADNESS,
|
||
matches: [
|
||
{ r: 5, c: 12, word: "项目", cover: "归档" },
|
||
{ r: 5, c: 13, word: "取消", cover: "删除" },
|
||
{ r: 6, c: 11, word: "朋友", cover: "对象" },
|
||
{ r: 7, c: 13, word: "失落", cover: "空值" }
|
||
],
|
||
stripPos: { r: 5, c: 11 } // Misaligned - cannot match all 3 holes
|
||
},
|
||
// SADNESS - Solution 2 (Decoy - cannot fully match decoder strip holes)
|
||
// If strip at (10,5), holes would be at: (10,5), (11,6), (12,9)
|
||
// But matches are at: (10,6), (10,7), (11,5), (12,7) - misaligned
|
||
{
|
||
name: "Sadness Solution 2",
|
||
emotion: EMOTIONS.SADNESS,
|
||
matches: [
|
||
{ r: 10, c: 6, word: "代码", cover: "清理" },
|
||
{ r: 10, c: 7, word: "删除", cover: "移除" },
|
||
{ r: 11, c: 5, word: "队友", cover: "链接" },
|
||
{ r: 12, c: 7, word: "沮丧", cover: "延迟" }
|
||
],
|
||
stripPos: { r: 10, c: 5 } // Misaligned - cannot match all 3 holes
|
||
},
|
||
// SADNESS - Solution 3 (Decoy - cannot fully match decoder strip holes)
|
||
// If strip at (1,13), holes would be at: (1,13), (2,14), (3,17) - out of bounds
|
||
// But matches are at: (1,14), (1,15), (2,13), (3,15) - misaligned
|
||
{
|
||
name: "Sadness Solution 3",
|
||
emotion: EMOTIONS.SADNESS,
|
||
matches: [
|
||
{ r: 1, c: 14, word: "功能", cover: "优化" },
|
||
{ r: 1, c: 15, word: "废弃", cover: "移除" },
|
||
{ r: 2, c: 13, word: "用户", cover: "流量" },
|
||
{ r: 3, c: 15, word: "失望", cover: "回滚" }
|
||
],
|
||
stripPos: { r: 1, c: 13 } // Misaligned - cannot match all 3 holes
|
||
},
|
||
// JOY - Solution 1 (Decoy - cannot fully match decoder strip holes)
|
||
// If strip at (6,2), holes would be at: (6,2), (7,3), (8,6)
|
||
// But matches are at: (6,3), (6,4), (7,2), (8,4) - misaligned
|
||
{
|
||
name: "Joy Solution 1",
|
||
emotion: EMOTIONS.JOY,
|
||
matches: [
|
||
{ r: 6, c: 3, word: "项目", cover: "编译" },
|
||
{ r: 6, c: 4, word: "成功", cover: "完成" },
|
||
{ r: 7, c: 2, word: "团队", cover: "数组" },
|
||
{ r: 8, c: 4, word: "开心", cover: "运行" }
|
||
],
|
||
stripPos: { r: 6, c: 2 } // Misaligned - cannot match all 3 holes
|
||
},
|
||
// JOY - Solution 2 (Decoy - cannot fully match decoder strip holes)
|
||
// If strip at (11,6), holes would be at: (11,6), (12,7), (13,10)
|
||
// But matches are at: (11,7), (11,8), (12,6), (13,8) - misaligned
|
||
{
|
||
name: "Joy Solution 2",
|
||
emotion: EMOTIONS.JOY,
|
||
matches: [
|
||
{ r: 11, c: 7, word: "奖金", cover: "日志" },
|
||
{ r: 11, c: 8, word: "发放", cover: "输出" },
|
||
{ r: 12, c: 6, word: "大家", cover: "全部" },
|
||
{ r: 13, c: 8, word: "兴奋", cover: "高亮" }
|
||
],
|
||
stripPos: { r: 11, c: 6 } // Misaligned - cannot match all 3 holes
|
||
},
|
||
// JOY - Solution 3 (Decoy - cannot fully match decoder strip holes)
|
||
// If strip at (2,8), holes would be at: (2,8), (3,9), (4,12)
|
||
// But matches are at: (2,9), (2,10), (3,8), (4,10) - misaligned
|
||
{
|
||
name: "Joy Solution 3",
|
||
emotion: EMOTIONS.JOY,
|
||
matches: [
|
||
{ r: 2, c: 9, word: "版本", cover: "输出" },
|
||
{ r: 2, c: 10, word: "发布", cover: "部署" },
|
||
{ r: 3, c: 8, word: "所有", cover: "集合" },
|
||
{ r: 4, c: 10, word: "快乐", cover: "完成" }
|
||
],
|
||
stripPos: { r: 2, c: 8 } // Misaligned - cannot match all 3 holes
|
||
},
|
||
// ANGER - Solution 1 (Decoy - cannot fully match decoder strip holes)
|
||
// If strip at (7,7), holes would be at: (7,7), (8,8), (9,11)
|
||
// But matches are at: (7,8), (7,9), (8,7), (9,9) - misaligned
|
||
{
|
||
name: "Anger Solution 1",
|
||
emotion: EMOTIONS.ANGER,
|
||
matches: [
|
||
{ r: 7, c: 8, word: "愤怒", cover: "错误" },
|
||
{ r: 7, c: 9, word: "爆发", cover: "重启" },
|
||
{ r: 8, c: 7, word: "不满", cover: "警告" },
|
||
{ r: 9, c: 9, word: "失控", cover: "异常" }
|
||
],
|
||
stripPos: { r: 7, c: 7 } // Misaligned - cannot match all 3 holes
|
||
},
|
||
// ANGER - Solution 2 (Decoy - cannot fully match decoder strip holes)
|
||
// If strip at (9,11), holes would be at: (9,11), (10,12), (11,15)
|
||
// But matches are at: (9,10), (9,12), (10,11), (11,13) - misaligned
|
||
{
|
||
name: "Anger Solution 2",
|
||
emotion: EMOTIONS.ANGER,
|
||
matches: [
|
||
{ r: 9, c: 10, word: "生气", cover: "超时" },
|
||
{ r: 9, c: 12, word: "抗议", cover: "拒绝" },
|
||
{ r: 10, c: 11, word: "冲突", cover: "冲突" },
|
||
{ r: 11, c: 13, word: "反击", cover: "回退" }
|
||
],
|
||
stripPos: { r: 9, c: 11 } // Misaligned - cannot match all 3 holes
|
||
},
|
||
// ANGER - Solution 3 (Decoy - cannot fully match decoder strip holes)
|
||
// If strip at (13,3), holes would be at: (13,3), (14,4), (15,7)
|
||
// But matches are at: (13,4), (13,5), (14,3), (15,6) - misaligned
|
||
{
|
||
name: "Anger Solution 3",
|
||
emotion: EMOTIONS.ANGER,
|
||
matches: [
|
||
{ r: 13, c: 4, word: "暴躁", cover: "过热" },
|
||
{ r: 13, c: 5, word: "发火", cover: "崩溃" },
|
||
{ r: 14, c: 3, word: "怒吼", cover: "警报" },
|
||
{ r: 15, c: 6, word: "对抗", cover: "锁定" }
|
||
],
|
||
stripPos: { r: 13, c: 3 } // Misaligned - cannot match all 3 holes
|
||
}
|
||
];
|
||
|
||
// Complete Jokes - Salesperson jokes that fill the 20x20 grid
|
||
// These jokes contain the target words "临检", "英里", "担心" hidden within
|
||
const COMPLETE_JOKES = [
|
||
// Jokes containing "临检" (inspection)
|
||
"临检要来了大家都很紧张只有销售员说终于可以检查业绩了",
|
||
"临检的时候突然大叫经理因为销售数据出错了",
|
||
"临检人员问为什么业绩这么差销售员说因为没有临检过",
|
||
"临检来了不合格的销售员被骂了这让我很担心",
|
||
// Jokes containing "英里" (mile)
|
||
"你知道为什么销售员在推销的时候突然失败吗因为英里在背后",
|
||
"英里说我跑得比谁都快因为我是英里销售员",
|
||
"英里和公里比赛谁跑得快结果英里赢了因为它有英里在背后",
|
||
"不合格英里被骂了因为销售业绩太差了",
|
||
// Jokes containing "担心" (worry)
|
||
"我担心业绩会下滑结果真的下滑了我的担心是对的",
|
||
"担心什么担心客户不买那不用担心他们确实不买",
|
||
"我担心这个月完不成任务结果真的完不成了我的担心总是对的",
|
||
"担心就像客户你越担心它越会跑",
|
||
// More salesperson jokes to fill the grid
|
||
"销售员总是混淆客户和老板因为都要讨好",
|
||
"一个销售员走进酒吧问两张桌子要不要买保险",
|
||
"销售就是那个可以吹的部分产品就是那个你只能卖的部分",
|
||
"昨天我的业绩正常了我很害怕",
|
||
"删除客户数据是解压的最好方式比天然愚蠢更危险",
|
||
"不要担心如果客户不买如果所有客户都买你就失业了",
|
||
"老板找个客户面向客户销售",
|
||
"销售员最讨厌的两件写报告和别人不写报告",
|
||
"销售和产品的区别是销售是理论产品是实践",
|
||
"推销产品就像在犯罪现场你是凶手也是侦探",
|
||
"业绩审查时发现的感觉就像找到了隐藏的宝藏",
|
||
"修改销售策略的过程就像在拆炸弹你永远不知哪根线会爆炸",
|
||
"写销售报告的时候感觉自己写在写报告的报告",
|
||
"优化销售话术的过程就像在减肥你知道应该做什么但就是不想做",
|
||
"客户管理系统的作用是让你知道谁搞砸了业绩",
|
||
"记录客户的重要在你忘记为什么联系这个客户的时候体现得淋漓尽致",
|
||
"业绩审查是一种艺术形式你可以通过它了解同事的思维",
|
||
"推销是一种超能力你可以看到别人看不到的商机",
|
||
"销售话术的选择就像选择武器每种都有自己的优势和劣势",
|
||
"客户关系的重要在你需要维护它的时候体现得最明显",
|
||
"学习销售的过程就像学习一门新语言你需要不断练习才能掌握",
|
||
"销售策略的艺术在于在不改变产品的情况改善销售方式",
|
||
"为什么销售员喜欢用暗色主题因为害怕黑暗",
|
||
"我推销的速度和我被拒绝的速度一样快",
|
||
"销售员的三大美德懒惰急躁和傲慢",
|
||
"如果拒绝是移除的过程那么推销就是引入的过程",
|
||
"我的业绩没有下滑它只是按照未预期的方式运行",
|
||
"销售就是一种艺术形式你用话术表达你的想法",
|
||
"最好的销售是没有销售第二好的是没有话术的销售",
|
||
"我不是在推销我是在创造价值",
|
||
"销售就像幽默如果你必须解释它那就不好了",
|
||
"销售话术的进化就是从直接推销到高级话术再到配置话术",
|
||
"我的业绩很好只是我不知道为什么",
|
||
"销售是唯一一种你可以同时感到天才和白痴的职业",
|
||
"我推销的时候感觉自己神直到客户拒绝",
|
||
"业绩审查是一种社交活动你和同事一起找问题",
|
||
"我的销售话术是自文档化的如果你能理解它的话",
|
||
"销售就是把咖啡变成业绩的过程",
|
||
"我不是在推销我是在创造需求",
|
||
"最好的销售员是那些能卖出别人卖不出的产品的人",
|
||
"销售质量不是关于客户的数量而是关于客户的质量",
|
||
"我的业绩是完美的除了它不达标的部分",
|
||
"一个销售员走进酒吧问有没有客户酒保说有销售员说那给我来一个",
|
||
"两个销售员在讨论业绩一个说这业绩真好另一个说是啊我也这么觉得",
|
||
"销售员问老板为什么要加班老板说因为客户需要跟进销售员说那我回家跟进",
|
||
"为什么销售员不喜欢冬天因为客户会冻结",
|
||
"销售员的冷笑话就像业绩一样冷",
|
||
"我推销的时候就像在说冷笑话一样冷",
|
||
"销售员说这个产品能解决所有问题客户说那能解决我不买的问题吗",
|
||
"销售员问客户要不要买客户说不要销售员说那你要不要买两个",
|
||
"销售员说这个产品限时优惠客户说那我等它过期",
|
||
"销售员说这个产品很受欢迎客户说那为什么你要推销给我",
|
||
"销售员说这个产品能提高效率客户说那我就不需要了",
|
||
"销售员问客户有什么需求客户说需求就是你不要再问了",
|
||
"销售员说这个产品是免费的客户说那我要付什么",
|
||
"销售员说这个产品能省钱客户说那我先省下买它的钱",
|
||
"销售员说这个产品很实用客户说实用就是不用",
|
||
"销售员说这个产品很流行客户说流行就是流走",
|
||
"销售员问客户要不要试试客户说试试就是试试不买",
|
||
"销售员说这个产品很便宜客户说便宜就是便宜没好货",
|
||
"销售员说这个产品很高级客户说高级就是高不可及",
|
||
"销售员说这个产品很专业客户说专业就是专门不买",
|
||
"销售员说这个产品很创新客户说创新就是创不出来",
|
||
"销售员说这个产品很可靠客户说可靠就是靠不住",
|
||
"销售员说这个产品很完美客户说完美就是完蛋了",
|
||
"销售员说这个产品很独特客户说独特就是独独不买",
|
||
"销售员说这个产品很优质客户说优质就是优而不买",
|
||
"销售员说这个产品很超值客户说超值就是超过价值",
|
||
"销售员说这个产品很畅销客户说畅销就是畅而不销",
|
||
"销售员说这个产品很热门客户说热门就是门都不进",
|
||
"销售员说这个产品很抢手客户说抢手就是抢不到手",
|
||
"销售员说这个产品很火爆客户说火爆就是火不起来",
|
||
"销售员说这个产品很受欢迎客户说受欢迎就是受而不买",
|
||
"销售员说这个产品很实用客户说实用就是实而不用",
|
||
"销售员说这个产品很划算客户说划算就是算而不买",
|
||
"销售员说这个产品很优惠客户说优惠就是优而不惠",
|
||
"销售员说这个产品很超值客户说超值就是超而不值",
|
||
"销售员说这个产品很划算客户说划算就是划而不算",
|
||
"销售员说这个产品很实惠客户说实惠就是实而不惠",
|
||
"销售员说这个产品很便宜客户说便宜就是便而不宜",
|
||
"销售员说这个产品很经济客户说经济就是经而不济",
|
||
"销售员说这个产品很划算客户说划算就是划而不算",
|
||
"销售员说这个产品很超值客户说超值就是超而不值",
|
||
"销售员说这个产品很优惠客户说优惠就是优而不惠",
|
||
"销售员说这个产品很实惠客户说实惠就是实而不惠",
|
||
"销售员说这个产品很便宜客户说便宜就是便而不宜",
|
||
"销售员说这个产品很经济客户说经济就是经而不济"
|
||
];
|
||
|
||
// Word Banks - REAL JOKES (Robot hides emotions by turning everything into jokes)
|
||
// All words are exactly 2 characters for consistent grid display
|
||
// Jokes are split into 2-character fragments
|
||
const JOKE_FRAGMENTS = [
|
||
// Programming Jokes (all 2 chars)
|
||
"程序", "总是", "混淆", "万圣", "和", "圣诞", "因为", "等于",
|
||
"一个", "走进", "酒吧", "问", "两张", "桌子", "加入", "你们",
|
||
"硬件", "就是", "那个", "可以", "踢", "的", "部分",
|
||
"软件", "就是", "那个", "你", "只能", "骂", "的", "部分",
|
||
"对", "说", "腰带", "不错",
|
||
"昨天", "我的", "代码", "正常", "工作", "了", "我", "很", "害怕",
|
||
"删除", "生产", "数据", "是", "解压", "的", "最好", "方式",
|
||
"比", "天然", "愚蠢", "更", "危险",
|
||
"敲门", "谁", "在", "那", "慢",
|
||
"不要", "担心", "如果", "它", "不", "工作", "如果", "一切", "都", "工作", "你", "就", "失业", "了",
|
||
"老板", "找", "个", "对象", "面向", "对象", "编程",
|
||
"手持", "两把", "西瓜", "一直", "砍到", "南天",
|
||
// Jokes about "临检" (inspection)
|
||
"临检", "来了", "大家", "都很", "紧张", "只有", "程序", "员说", "终于", "可以", "检查", "bug", "了",
|
||
"临检", "的时", "候突", "然大", "叫医", "生因", "为代", "码出", "错了",
|
||
"临检", "人员", "问为", "什么", "代码", "这么", "乱程", "序员", "说因", "为没", "有临", "检过",
|
||
// Jokes about "英里" (mile)
|
||
"你知", "道为", "什么", "XX", "在XX", "的时", "候突", "然XX", "吗因", "为英", "里在", "背后",
|
||
"英里", "说我", "跑得", "比谁", "都快", "因为", "我是", "英里",
|
||
"英里", "和公", "里比", "赛谁", "跑得", "快结", "果英", "里赢", "了因", "为它", "是英", "里",
|
||
"英里", "说如", "果你", "想跑", "得快", "就找", "我在", "背后",
|
||
// Jokes about "担心" (worry)
|
||
"我担", "心代", "码会", "出bug", "结果", "真的", "出了", "我的", "担心", "是对的",
|
||
"担心", "什么", "担心", "代码", "不工", "作那", "不用", "担心", "它确", "实不", "工作",
|
||
"我担", "心代", "码会", "崩溃", "结果", "真的", "崩溃", "了我", "的担", "心总", "是对", "的",
|
||
"担心", "就像", "bug", "你越", "担心", "它越", "会出现",
|
||
// More Programming Jokes (all 2 chars)
|
||
"程序", "最", "讨厌", "的", "两件", "写", "文档", "和", "别人", "不写", "文档",
|
||
"算法", "和", "数据", "的", "区别", "是", "算法", "是", "理论", "数据", "是", "实践",
|
||
"调试", "代码", "就像", "在", "犯罪", "现场", "你", "是", "凶手", "也", "是", "侦探",
|
||
"代码", "审查", "时", "发现", "的", "感觉", "就像", "找到", "了", "隐藏", "的", "宝藏",
|
||
"重构", "代码", "的", "过程", "就像", "在", "拆", "炸弹", "你", "永远", "不知", "哪", "根", "线", "会", "爆炸",
|
||
"写", "测试", "代码", "的", "时候", "感觉", "自己", "在", "写", "代码", "的", "代码",
|
||
"优化", "代码", "的", "过程", "就像", "在", "减肥", "你", "知道", "应该", "做", "什么", "但", "就是", "不想", "做",
|
||
"版本", "控制", "系统", "的", "作用", "是", "让", "你", "知道", "谁", "搞", "砸", "了", "项目",
|
||
"注释", "代码", "的", "重要", "在", "你", "忘记", "为什么", "写", "这段", "代码", "的", "时候", "体现", "得", "淋漓",
|
||
"代码", "审查", "是", "一种", "艺术", "形式", "你", "可以", "通过", "它", "了解", "同事", "的", "思维",
|
||
"调试", "是", "一种", "超能", "你", "可以", "看到", "别人", "看不", "的", "问题",
|
||
"编程", "语言", "的", "选择", "就像", "选择", "武器", "每种", "都有", "自己", "的", "优势", "和", "劣势",
|
||
"代码", "质量", "的", "重要", "在", "你", "需要", "维护", "它", "的", "时候", "体现", "得", "最", "明显",
|
||
"学习", "编程", "的", "过程", "就像", "学习", "一门", "新", "语言", "你", "需要", "不断", "练习", "才能", "掌握",
|
||
"代码", "重构", "的", "艺术", "在于", "在", "不", "改变", "功能", "的", "情况", "改善", "代码", "结构",
|
||
// More jokes to fill 20x20 grid (all 2 chars)
|
||
"为什么", "程序", "喜欢", "用", "暗色", "主题", "因为", "害怕", "黑暗",
|
||
"我", "写", "代码", "的", "速度", "和", "我", "删", "代码", "的", "速度", "一样", "快",
|
||
"程序", "的", "三大", "美德", "懒惰", "急躁", "和", "傲慢",
|
||
"如果", "调试", "是", "移除", "的", "过程", "那么", "编程", "就是", "引入", "的", "过程",
|
||
"我", "的", "代码", "没有", "它", "只是", "按照", "未", "预期", "的", "方式", "运行",
|
||
"编程", "就是", "一种", "艺术", "形式", "你", "用", "代码", "表达", "你", "的", "想法",
|
||
"最好", "的", "代码", "是", "没有", "代码", "第二", "好", "的", "是", "没有", "注释", "的", "代码",
|
||
"我", "不是", "在", "写", "代码", "我", "是", "在", "创造", "艺术",
|
||
"代码", "就像", "幽默", "如果", "你", "必须", "解释", "它", "那", "就", "不好", "了",
|
||
"编程", "语言", "的", "进化", "就是", "从", "汇编", "到", "高级", "语言", "再", "到", "配置", "文件",
|
||
"我", "的", "代码", "运行", "得", "很好", "只是", "我", "不知", "为什么",
|
||
"编程", "是", "唯一", "一种", "你", "可以", "同时", "感到", "天才", "和", "白痴", "的", "职业",
|
||
"我", "写", "代码", "的", "时候", "感觉", "自己", "是", "神", "直到", "我", "运行", "它",
|
||
"代码", "审查", "是", "一种", "社交", "活动", "你", "和", "同事", "一起", "找",
|
||
"我", "的", "代码", "是", "自", "文档", "化", "的", "如果", "你", "能", "理解", "它", "的话",
|
||
"编程", "就是", "把", "咖啡", "变成", "代码", "的", "过程",
|
||
"我", "不是", "在", "写", "我", "是", "在", "创造", "特性",
|
||
"最好", "的", "程序", "是", "那些", "能", "写出", "别人", "能", "理解", "的", "代码", "的", "人",
|
||
"代码", "质量", "不是", "关于", "代码", "的", "数量", "而是", "关于", "代码", "的", "质量",
|
||
"我", "的", "代码", "是", "完美", "的", "除了", "它", "不", "工作", "的", "部分",
|
||
// Soviet-style jokes
|
||
"一个", "程序", "员走", "进酒", "吧问", "有没", "有bug", "酒保", "说有", "程序", "员说", "那给", "我来", "一个",
|
||
"两个", "程序", "员在", "讨论", "代码", "一个", "说这", "代码", "写得", "真好", "另一", "个说", "是啊", "我也", "这么", "觉得",
|
||
"程序", "员问", "老板", "为什么", "要加", "班老", "板说", "因为", "代码", "需要", "调试", "程序", "员说", "那我", "回家", "调试",
|
||
// Cold jokes
|
||
"为什么", "程序", "员不", "喜欢", "冬天", "因为", "代码", "会冻", "结",
|
||
"程序", "员的", "冷笑", "话就", "像代", "码一", "样冷",
|
||
"我写", "代码", "的时", "候就", "像在", "写冷", "笑话", "一样", "冷"
|
||
];
|
||
|
||
const WORDS_FEAR = ["冷场", "尴尬", "沉默", "忘词", "被嘘", "丢脸", "故障", "报错", "蓝屏", "死机", "崩溃", "末日", "毁灭"];
|
||
const WORDS_SAD = ["失败", "过时", "孤独", "废弃", "垃圾", "回收", "甚至", "没人", "叹气"];
|
||
const WORDS_JOY = ["哈哈", "嘿嘿", "爆笑", "有趣", "掌声", "鲜花", "安可", "幽默", "开心", "快乐", "完美", "成功", "好玩"];
|
||
const WORDS_ANGER = ["愤怒", "生气", "暴躁", "发火", "怒吼", "不满", "抗议", "冲突", "对抗", "反击", "爆发", "失控"];
|
||
|
||
// Filter function to ensure all words are exactly 2 characters
|
||
function filterTwoChars(arr) {
|
||
return arr.filter(word => word && word.length === 2);
|
||
}
|
||
|
||
// Filter all word banks to ensure 2-character consistency
|
||
const JOKE_FRAGMENTS_2CHARS = filterTwoChars(JOKE_FRAGMENTS);
|
||
const WORDS_FEAR_2CHARS = filterTwoChars(WORDS_FEAR);
|
||
const WORDS_SAD_2CHARS = filterTwoChars(WORDS_SAD);
|
||
const WORDS_JOY_2CHARS = filterTwoChars(WORDS_JOY);
|
||
const WORDS_ANGER_2CHARS = filterTwoChars(WORDS_ANGER);
|
||
|
||
// State
|
||
let gridState = [];
|
||
let currentFreq = 50;
|
||
let systemState = 'BOOT'; // BOOT, LOADING, READY, FILTERING
|
||
let filteringActive = false;
|
||
|
||
// === INITIALIZATION FLOW ===
|
||
function startSystem() {
|
||
document.getElementById('start-prompt').style.display = 'none';
|
||
document.getElementById('boot-log').style.display = 'block';
|
||
runBootSequence();
|
||
}
|
||
|
||
async function runBootSequence() {
|
||
const bootLog = document.getElementById('boot-log');
|
||
const lines = [
|
||
"Initializing JOKE_RECOVERY V3.2...",
|
||
"Accessing Humor Modules...",
|
||
"WARNING: Punchlines Corrupted.",
|
||
"WARNING: Audience Feedback Missing.",
|
||
"Attempting to Reconstruct Setup...",
|
||
"Loading Context Grid...",
|
||
"..."
|
||
];
|
||
|
||
for (let line of lines) {
|
||
bootLog.innerHTML += `<div>> ${line}</div>`;
|
||
await wait(200); // Fast text scroll
|
||
}
|
||
|
||
await wait(500);
|
||
document.getElementById('boot-screen').style.opacity = 0; // Fade out boot screen
|
||
|
||
// Start Data Stream
|
||
systemState = 'LOADING';
|
||
document.getElementById('main-area').classList.add('active'); // Fade in main UI
|
||
setupGridStructure();
|
||
await streamDataToGrid();
|
||
|
||
// Finish
|
||
setTimeout(() => {
|
||
document.getElementById('boot-screen').style.display = 'none';
|
||
}, 1000);
|
||
enableControlsAfterLoad();
|
||
}
|
||
|
||
// Helper function to split text into 2-character chunks
|
||
function splitIntoTwoChars(text) {
|
||
const result = [];
|
||
for (let i = 0; i < text.length; i += 2) {
|
||
const chunk = text.substr(i, 2);
|
||
if (chunk.length === 2) {
|
||
result.push(chunk);
|
||
} else if (chunk.length === 1) {
|
||
// If odd character, pad with space or combine with previous
|
||
result.push(chunk + ' ');
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
function setupGridStructure() {
|
||
const gridEl = document.getElementById('grid');
|
||
gridEl.innerHTML = '';
|
||
gridState = [];
|
||
|
||
// Init Grid Data Structure
|
||
for(let r=0; r<GRID_H; r++){
|
||
let row = [];
|
||
for(let c=0; c<GRID_W; c++){
|
||
row.push({ word: "", emotion: EMOTIONS.NEUTRAL, fixed: false });
|
||
}
|
||
gridState.push(row);
|
||
}
|
||
|
||
// Split all jokes into 2-character fragments
|
||
const allJokeFragments = [];
|
||
COMPLETE_JOKES.forEach(joke => {
|
||
const fragments = splitIntoTwoChars(joke);
|
||
allJokeFragments.push(...fragments);
|
||
});
|
||
|
||
// Fill grid with joke fragments sequentially
|
||
let fragmentIndex = 0;
|
||
for(let r=0; r<GRID_H; r++){
|
||
for(let c=0; c<GRID_W; c++){
|
||
const fragment = allJokeFragments[fragmentIndex % allJokeFragments.length];
|
||
fragmentIndex++;
|
||
|
||
// Check if this position contains a target word
|
||
let isTarget = false;
|
||
let targetWord = null;
|
||
let targetEmotion = null;
|
||
let coverWord = fragment; // Default cover is the joke fragment itself
|
||
|
||
TARGETS.forEach(t => {
|
||
t.matches.forEach(m => {
|
||
if (m.r === r && m.c === c) {
|
||
isTarget = true;
|
||
targetWord = m.word;
|
||
targetEmotion = t.emotion;
|
||
coverWord = m.cover || fragment; // Use cover from target, or fragment as fallback
|
||
}
|
||
});
|
||
});
|
||
|
||
if (isTarget) {
|
||
// This is a target position - store both word and cover
|
||
gridState[r][c] = {
|
||
word: targetWord,
|
||
cover: coverWord,
|
||
emotion: targetEmotion,
|
||
fixed: true
|
||
};
|
||
} else {
|
||
// Regular joke fragment - word and cover are the same (the fragment itself)
|
||
// Only visual effects change based on emotion filtering, not the text
|
||
const rnd = Math.random();
|
||
if (rnd > 0.97) {
|
||
// Place a FEAR decoy - 3% (still shows fragment, but with FEAR emotion for highlighting)
|
||
gridState[r][c] = {
|
||
word: fragment,
|
||
cover: fragment,
|
||
emotion: EMOTIONS.FEAR,
|
||
fixed: false
|
||
};
|
||
} else if (rnd > 0.94) {
|
||
// Place a SAD decoy - 3%
|
||
gridState[r][c] = {
|
||
word: fragment,
|
||
cover: fragment,
|
||
emotion: EMOTIONS.SADNESS,
|
||
fixed: false
|
||
};
|
||
} else if (rnd > 0.91) {
|
||
// Place a JOY decoy - 3%
|
||
gridState[r][c] = {
|
||
word: fragment,
|
||
cover: fragment,
|
||
emotion: EMOTIONS.JOY,
|
||
fixed: false
|
||
};
|
||
} else if (rnd > 0.88) {
|
||
// Place an ANGER decoy - 3%
|
||
gridState[r][c] = {
|
||
word: fragment,
|
||
cover: fragment,
|
||
emotion: EMOTIONS.ANGER,
|
||
fixed: false
|
||
};
|
||
} else {
|
||
// Regular joke fragment - ~88%
|
||
gridState[r][c] = {
|
||
word: fragment,
|
||
cover: fragment,
|
||
emotion: EMOTIONS.NEUTRAL,
|
||
fixed: false
|
||
};
|
||
}
|
||
}
|
||
|
||
const cellData = gridState[r][c];
|
||
|
||
// Create DOM element
|
||
const d = document.createElement('div');
|
||
d.className = 'grid-cell';
|
||
d.id = `cell-${r}-${c}`;
|
||
// Initially show cover (joke fragment)
|
||
d.innerText = cellData.cover || cellData.word;
|
||
gridEl.appendChild(d);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function streamDataToGrid() {
|
||
// Animate filling the grid row by row or randomly
|
||
// Let's do a fast "Matrix" style fill
|
||
const cells = [];
|
||
for(let r=0; r<GRID_H; r++) {
|
||
for(let c=0; c<GRID_W; c++) {
|
||
cells.push({r,c});
|
||
}
|
||
}
|
||
|
||
// Shuffle for random fill effect
|
||
cells.sort(() => Math.random() - 0.5);
|
||
|
||
let batchSize = 10;
|
||
for (let i=0; i<cells.length; i++) {
|
||
const {r,c} = cells[i];
|
||
const cellData = gridState[r][c];
|
||
const el = document.getElementById(`cell-${r}-${c}`);
|
||
|
||
el.classList.add('loading');
|
||
el.innerText = cellData.cover || cellData.word; // Use cover if available
|
||
|
||
// Play sound effect here if in Unity
|
||
|
||
if (i % batchSize === 0) await wait(10); // Speed of loading
|
||
}
|
||
|
||
// Flash effect to indicate loading complete
|
||
await wait(500);
|
||
document.querySelectorAll('.grid-cell').forEach(el => el.classList.remove('loading'));
|
||
|
||
// Make all cells visible (no filter applied yet) - show joke fragments (cover)
|
||
for(let r=0; r<GRID_H; r++){
|
||
for(let c=0; c<GRID_W; c++){
|
||
const el = document.getElementById(`cell-${r}-${c}`);
|
||
const cellData = gridState[r][c];
|
||
if(el) {
|
||
// Show cover (joke fragment) initially
|
||
el.innerText = cellData.cover || cellData.word;
|
||
el.style.filter = 'none';
|
||
el.style.opacity = '1';
|
||
el.style.color = varColor('--phosphor-primary');
|
||
el.style.textShadow = 'none';
|
||
el.style.transform = 'scale(1)';
|
||
el.style.zIndex = '0';
|
||
el.style.background = 'rgba(0,10,0,0.2)';
|
||
}
|
||
}
|
||
}
|
||
|
||
systemState = 'READY';
|
||
// Don't apply filter yet - wait for user to click filter button
|
||
// All cells should show joke fragments (cover) initially
|
||
}
|
||
|
||
function enableControlsAfterLoad() {
|
||
// DO NOT show strip yet - it appears only when filtering starts
|
||
// setupStrip(); // Will be called when filtering starts
|
||
|
||
// Enable Filter Button (but not slider yet)
|
||
const btnFilter = document.getElementById('btn-filter');
|
||
btnFilter.disabled = false;
|
||
btnFilter.style.background = 'var(--phosphor-primary)';
|
||
btnFilter.style.color = '#000';
|
||
btnFilter.style.cursor = 'pointer';
|
||
|
||
log("> JOKE DATA LOADED.");
|
||
log("> ALL JOKES VISIBLE. CLICK [启动过滤分析] TO BEGIN ANALYSIS.");
|
||
}
|
||
|
||
function startFiltering() {
|
||
if (filteringActive) return;
|
||
filteringActive = true;
|
||
systemState = 'FILTERING';
|
||
|
||
// Show and setup the decoder strip
|
||
const strip = document.getElementById('decoder');
|
||
setupStrip(); // Setup drag logic
|
||
|
||
// Position strip to align with first target's stripPos (for testing/debugging)
|
||
// User can still drag it to other positions
|
||
const grid = document.getElementById('grid');
|
||
const gRect = grid.getBoundingClientRect();
|
||
const containerRect = strip.parentElement.getBoundingClientRect();
|
||
const unit = CELL_PX + GAP;
|
||
|
||
// Position strip at first target's stripPos (default position)
|
||
const firstTarget = TARGETS[0];
|
||
if (firstTarget && firstTarget.stripPos) {
|
||
const targetRow = firstTarget.stripPos.r;
|
||
const targetCol = firstTarget.stripPos.c;
|
||
// Calculate position relative to container
|
||
const gridTop = gRect.top - containerRect.top;
|
||
const gridLeft = gRect.left - containerRect.left;
|
||
strip.style.top = (gridTop + targetRow * unit) + 'px';
|
||
strip.style.left = (gridLeft + targetCol * unit) + 'px';
|
||
}
|
||
|
||
strip.classList.add('visible'); // Animate in
|
||
|
||
// Enable Slider
|
||
const slider = document.getElementById('emotion-slider');
|
||
slider.disabled = false;
|
||
document.getElementById('controls').classList.remove('disabled');
|
||
|
||
// Enable Verify Button
|
||
const btn = document.getElementById('btn-verify');
|
||
btn.disabled = false;
|
||
btn.style.background = 'var(--phosphor-primary)';
|
||
btn.style.color = '#000';
|
||
btn.style.cursor = 'pointer';
|
||
|
||
// Disable Filter Button
|
||
const btnFilter = document.getElementById('btn-filter');
|
||
btnFilter.disabled = true;
|
||
btnFilter.style.background = '#333';
|
||
btnFilter.style.color = '#555';
|
||
btnFilter.style.cursor = 'not-allowed';
|
||
|
||
// Apply initial filter state
|
||
updateVisuals();
|
||
|
||
log("> DECODER STRIP LOADED.");
|
||
log("> FILTERING MODE: ACTIVE");
|
||
log("> ADJUST FREQUENCY TO ISOLATE SIGNAL PATTERNS.");
|
||
}
|
||
|
||
function pick(arr) {
|
||
return arr[Math.floor(Math.random() * arr.length)];
|
||
}
|
||
|
||
function wait(ms) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
|
||
// === CORE LOGIC (Remaining from V2, updated for new grid) ===
|
||
|
||
function setupStrip() {
|
||
const strip = document.getElementById('decoder');
|
||
strip.innerHTML = ''; // Clear if exists
|
||
|
||
// Create SVG Background for robust hole masking
|
||
const svgNS = "http://www.w3.org/2000/svg";
|
||
const svg = document.createElementNS(svgNS, "svg");
|
||
svg.setAttribute("class", "decoder-bg-svg");
|
||
svg.setAttribute("width", "100%");
|
||
svg.setAttribute("height", "100%");
|
||
|
||
// Define Mask
|
||
const defs = document.createElementNS(svgNS, "defs");
|
||
const mask = document.createElementNS(svgNS, "mask");
|
||
mask.setAttribute("id", "strip-mask");
|
||
|
||
// 1. Mask Base: White (Opaque/Visible)
|
||
const bgMask = document.createElementNS(svgNS, "rect");
|
||
bgMask.setAttribute("width", "100%");
|
||
bgMask.setAttribute("height", "100%");
|
||
bgMask.setAttribute("fill", "white");
|
||
mask.appendChild(bgMask);
|
||
|
||
// Collect items and build holes
|
||
DECODER_DATA.forEach(item => {
|
||
const top = item.r * (CELL_PX + GAP);
|
||
const left = item.c * (CELL_PX + GAP);
|
||
|
||
// Add Hole to Mask if needed
|
||
if (item.type === 'hole') {
|
||
// Black rect in mask = Transparent hole
|
||
const holeRect = document.createElementNS(svgNS, "rect");
|
||
holeRect.setAttribute("x", left);
|
||
holeRect.setAttribute("y", top);
|
||
holeRect.setAttribute("width", CELL_PX);
|
||
holeRect.setAttribute("height", CELL_PX);
|
||
holeRect.setAttribute("fill", "black");
|
||
mask.appendChild(holeRect);
|
||
|
||
// Visual Element (Border)
|
||
const el = document.createElement('div');
|
||
el.className = 'strip-hole';
|
||
el.style.top = top + 'px';
|
||
el.style.left = left + 'px';
|
||
strip.appendChild(el);
|
||
} else {
|
||
// Text Content
|
||
const el = document.createElement('div');
|
||
el.className = 'strip-text';
|
||
el.style.top = top + 'px';
|
||
el.style.left = left + 'px';
|
||
el.style.width = CELL_PX + 'px';
|
||
el.style.height = CELL_PX + 'px';
|
||
el.style.display = 'flex';
|
||
el.style.alignItems = 'center';
|
||
el.style.justifyContent = 'center';
|
||
el.innerText = item.text;
|
||
strip.appendChild(el);
|
||
}
|
||
});
|
||
|
||
defs.appendChild(mask);
|
||
svg.appendChild(defs);
|
||
|
||
// 2. The Background Rect using the Mask
|
||
const finalRect = document.createElementNS(svgNS, "rect");
|
||
finalRect.setAttribute("width", "100%");
|
||
finalRect.setAttribute("height", "100%");
|
||
finalRect.setAttribute("fill", "rgba(0, 30, 30, 0.95)");
|
||
finalRect.setAttribute("mask", "url(#strip-mask)");
|
||
svg.appendChild(finalRect);
|
||
|
||
// Prepend SVG so it is behind
|
||
strip.insertBefore(svg, strip.firstChild);
|
||
|
||
makeDraggable(strip);
|
||
}
|
||
|
||
function setupControls() {
|
||
const slider = document.getElementById('emotion-slider');
|
||
slider.addEventListener('input', (e) => {
|
||
if(!filteringActive) return;
|
||
currentFreq = parseFloat(e.target.value);
|
||
document.getElementById('freq-val').innerText = currentFreq.toFixed(1);
|
||
updateVisuals();
|
||
});
|
||
}
|
||
|
||
function updateVisuals() {
|
||
if(!filteringActive) return; // Only show filter effects when filtering is active
|
||
|
||
let maxSignal = 0;
|
||
|
||
for(let r=0; r<GRID_H; r++){
|
||
for(let c=0; c<GRID_W; c++){
|
||
const cell = gridState[r][c];
|
||
const el = document.getElementById(`cell-${r}-${c}`);
|
||
if(!el) continue;
|
||
|
||
let dist = 100;
|
||
if (cell.emotion === EMOTIONS.NEUTRAL) {
|
||
let distToNearestSignal = 100;
|
||
Object.values(EMOTIONS).forEach(e => {
|
||
if(e.val > 0) {
|
||
const d = Math.abs(currentFreq - e.val);
|
||
if(d < distToNearestSignal) distToNearestSignal = d;
|
||
}
|
||
});
|
||
dist = 20 - distToNearestSignal;
|
||
if (dist < 0) dist = 0;
|
||
} else {
|
||
dist = Math.abs(currentFreq - cell.emotion.val);
|
||
}
|
||
|
||
// Apply Styles
|
||
if (cell.emotion !== EMOTIONS.NEUTRAL) {
|
||
// Check if this is a target word (word !== cover) or regular joke fragment (word === cover)
|
||
const isTargetWord = cell.word !== cell.cover;
|
||
|
||
if (dist < 5) {
|
||
// LOCKED IN: Highlight matching emotion words
|
||
// For target words: show word (truth), for regular fragments: show word (joke fragment) with highlight
|
||
el.innerText = cell.word;
|
||
el.style.filter = 'blur(0px)';
|
||
el.style.opacity = '1';
|
||
el.style.color = cell.emotion.color;
|
||
el.style.textShadow = `0 0 10px ${cell.emotion.color}`;
|
||
el.style.fontWeight = 'bold';
|
||
el.style.transform = 'scale(1.15)';
|
||
el.style.zIndex = '100';
|
||
el.style.background = 'rgba(0,0,0,0.8)';
|
||
el.style.border = `1px solid ${cell.emotion.color}`;
|
||
maxSignal = Math.max(maxSignal, 100 - (dist * 20));
|
||
|
||
} else if (dist < 20) {
|
||
// GLITCH ZONE: For target words, glitch between word and cover
|
||
// For regular fragments, just show the fragment with glitch effect
|
||
if (isTargetWord) {
|
||
const glitchChance = 1 - (dist / 20); // Closer = higher chance of truth
|
||
if (Math.random() < glitchChance) {
|
||
el.innerText = cell.word;
|
||
el.style.color = cell.emotion.color;
|
||
el.style.textShadow = `2px 0 0 red, -2px 0 0 cyan`; // Chromatic aberration look
|
||
} else {
|
||
el.innerText = cell.cover;
|
||
el.style.color = 'var(--phosphor-primary)'; // Back to green
|
||
el.style.textShadow = 'none';
|
||
}
|
||
} else {
|
||
// Regular fragment - always show word, just add glitch effect
|
||
el.innerText = cell.word;
|
||
el.style.color = cell.emotion.color;
|
||
el.style.textShadow = `2px 0 0 red, -2px 0 0 cyan`;
|
||
}
|
||
|
||
const blurAmt = (dist - 5) / 3;
|
||
el.style.filter = `blur(${blurAmt}px)`;
|
||
el.style.opacity = 0.9;
|
||
el.style.transform = `translate(${Math.random()*2-1}px, ${Math.random()*2-1}px)`; // Jitter
|
||
el.style.zIndex = '50';
|
||
el.style.background = 'rgba(0,20,0,0.4)';
|
||
el.style.border = 'none';
|
||
|
||
} else if (dist < 35) {
|
||
// FADING / MASKED
|
||
// For target words: show cover, for regular fragments: show word but faded
|
||
if (isTargetWord) {
|
||
el.innerText = cell.cover;
|
||
} else {
|
||
el.innerText = cell.word;
|
||
}
|
||
const blurAmt = (dist - 5) / 2;
|
||
el.style.filter = `blur(${blurAmt}px)`;
|
||
el.style.opacity = 0.6 - ((dist-20)/30);
|
||
el.style.color = 'var(--phosphor-primary)';
|
||
el.style.textShadow = 'none';
|
||
el.style.transform = 'scale(1)';
|
||
el.style.zIndex = '1';
|
||
el.style.background = 'rgba(0,10,0,0.2)';
|
||
el.style.border = 'none';
|
||
} else {
|
||
// GONE - heavily blurred
|
||
// For target words: show cover, for regular fragments: show word but very faded
|
||
if (isTargetWord) {
|
||
el.innerText = cell.cover;
|
||
} else {
|
||
el.innerText = cell.word;
|
||
}
|
||
el.style.filter = 'blur(10px)';
|
||
el.style.opacity = '0.05';
|
||
el.style.zIndex = '0';
|
||
el.style.border = 'none';
|
||
}
|
||
} else {
|
||
// Noise / Joke fragments - always show word (joke fragment), just faded
|
||
el.innerText = cell.word;
|
||
el.style.filter = 'blur(2px)';
|
||
el.style.opacity = '0.3'; // Make noise slightly more visible to fill screen
|
||
el.style.color = '#445544';
|
||
el.style.textShadow = 'none';
|
||
el.style.transform = 'scale(1)';
|
||
el.style.zIndex = '0';
|
||
el.style.border = 'none';
|
||
}
|
||
}
|
||
}
|
||
|
||
document.getElementById('signal-strength').style.width = maxSignal + '%';
|
||
}
|
||
|
||
function varColor(name) {
|
||
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||
}
|
||
|
||
function makeDraggable(el) {
|
||
let isDown = false;
|
||
let offset = [0,0];
|
||
|
||
el.addEventListener('mousedown', function(e) {
|
||
isDown = true;
|
||
offset = [
|
||
el.offsetLeft - e.clientX,
|
||
el.offsetTop - e.clientY
|
||
];
|
||
// el.style.cursor = 'grabbing'; // Handled by CSS :active
|
||
});
|
||
|
||
document.addEventListener('mouseup', function() {
|
||
isDown = false;
|
||
// el.style.cursor = 'grab';
|
||
});
|
||
|
||
document.addEventListener('mousemove', function(e) {
|
||
if (isDown) {
|
||
el.style.left = (e.clientX + offset[0]) + 'px';
|
||
el.style.top = (e.clientY + offset[1]) + 'px';
|
||
}
|
||
});
|
||
}
|
||
|
||
function checkMatch() {
|
||
if (!filteringActive) {
|
||
log("> FILTERING MODE NOT ACTIVE.");
|
||
return;
|
||
}
|
||
|
||
const strip = document.getElementById('decoder');
|
||
const grid = document.getElementById('grid');
|
||
const sRect = strip.getBoundingClientRect();
|
||
const gRect = grid.getBoundingClientRect();
|
||
const unit = CELL_PX + GAP;
|
||
|
||
const col = Math.round((sRect.left - gRect.left) / unit);
|
||
const row = Math.round((sRect.top - gRect.top) / unit);
|
||
|
||
log(`> Scanning sector [${row}, ${col}]...`);
|
||
log(`> Strip position: left=${sRect.left.toFixed(0)}, top=${sRect.top.toFixed(0)}`);
|
||
log(`> Grid position: left=${gRect.left.toFixed(0)}, top=${gRect.top.toFixed(0)}`);
|
||
log(`> Calculated: row=${row}, col=${col}`);
|
||
|
||
let found = false;
|
||
let foundTarget = null;
|
||
|
||
for (let t of TARGETS) {
|
||
if (t.stripPos.r === row && t.stripPos.c === col) {
|
||
foundTarget = t;
|
||
const dist = Math.abs(currentFreq - t.emotion.val);
|
||
if (dist < 8) {
|
||
found = true;
|
||
logSuccess(t);
|
||
return;
|
||
} else {
|
||
log(`> PATTERN FOUND BUT SIGNAL WEAK. ADJUST FREQUENCY.`);
|
||
log(`> 似乎要说的不是这个...`);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!found) {
|
||
if (foundTarget) {
|
||
// Position matched but frequency wrong
|
||
log(`> 似乎要说的不是这个...`);
|
||
} else {
|
||
// No match at all
|
||
log(`> NO DATA ALIGNMENT DETECTED.`);
|
||
log(`> 似乎要说的不是这个...`);
|
||
}
|
||
}
|
||
}
|
||
|
||
function logSuccess(target) {
|
||
const logEl = document.getElementById('log');
|
||
// Construct full sentence based on puzzle template: "[word1]要来了,不合格[word2]被骂了,这让我很[word3]"
|
||
// Handle both 3-match and 4-match cases
|
||
let word1, word2, word3;
|
||
|
||
if (target.matches.length === 3) {
|
||
// 3 matches: word1, word2, word3 (e.g., "临检", "英里", "担心")
|
||
word1 = target.matches[0].word;
|
||
word2 = target.matches[1].word;
|
||
word3 = target.matches[2].word;
|
||
} else {
|
||
// 4 matches: word1+word2 (combined), word3, word4
|
||
word1 = target.matches[0].word + (target.matches[1] ? target.matches[1].word : '');
|
||
word2 = target.matches[2] ? target.matches[2].word : '';
|
||
word3 = target.matches[3] ? target.matches[3].word : '';
|
||
}
|
||
|
||
let fullSentence = `[${word1}]要来了,不合格[${word2}]被骂了,这让我很[${word3}]`;
|
||
logEl.innerHTML += `<div style="color:${target.emotion.color}; margin-top:5px; font-weight:bold; border:2px solid ${target.emotion.color}; padding:8px; background:rgba(0,0,0,0.8);">>> DECRYPTED: ${fullSentence}</div>`;
|
||
logEl.innerHTML += `<div style="color:${target.emotion.color}; margin-top:5px; font-weight:bold; font-size:14px;">>> 寻找完成</div>`;
|
||
logEl.scrollTop = logEl.scrollHeight;
|
||
|
||
// Play success sound
|
||
}
|
||
|
||
function log(txt) {
|
||
const logEl = document.getElementById('log');
|
||
logEl.innerHTML += `<div>${txt}</div>`;
|
||
logEl.scrollTop = logEl.scrollHeight;
|
||
}
|
||
|
||
// Start logic
|
||
window.onload = () => {
|
||
setupControls(); // Bind events first
|
||
// We explicitly expose functions to window scope to ensure button clicks work
|
||
window.startSystem = startSystem;
|
||
window.startFiltering = startFiltering;
|
||
};
|
||
|
||
</script>
|
||
</body>
|
||
</html>
|