llamameta commited on
Commit
2babf61
·
verified ·
1 Parent(s): 264bbda

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +548 -291
index.html CHANGED
@@ -2,55 +2,84 @@
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
- <title>One Piece Short: Nami's Fury (Universal Record)</title>
6
  <style>
7
  :root {
8
- --sky-top: #4FACFE;
9
- --sky-bottom: #00F2FE;
10
- --ocean-top: #0099ff;
11
- --ocean-bottom: #003366;
12
- --deck-color: #e8c39e;
13
- --deck-wood-dark: #c69c72;
14
- --luffy-vest: #F44336;
15
- --luffy-skin: #ffe0bd;
16
- --nami-hair: #FF9800;
17
- --nami-skin: #ffe0bd;
18
- --sunny-rail: #ffffff;
19
- --sunny-rail-top: #d35400;
20
  }
21
 
22
  body {
23
  margin: 0;
24
  padding: 0;
25
- background-color: #1a1a1a;
26
  height: 100vh;
27
  display: flex;
28
  justify-content: center;
29
  align-items: center;
30
- font-family: 'Comic Sans MS', 'Chalkboard SE', sans-serif;
31
  overflow: hidden;
32
  color: white;
33
  }
34
 
35
- #main-container {
36
  position: relative;
37
  width: 960px;
38
  height: 540px;
39
- background: #000;
 
40
  box-shadow: 0 0 50px rgba(0,0,0,0.8);
41
- border: 4px solid #333;
42
  overflow: hidden;
43
  }
44
 
45
- #stage {
46
  width: 100%;
47
  height: 100%;
48
  display: block;
49
- background: linear-gradient(to bottom, var(--sky-top), var(--sky-bottom) 50%, var(--ocean-top) 50%, var(--ocean-bottom));
50
  }
51
 
52
- /* --- UI Elements --- */
53
- #overlay, #guide-overlay {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  position: absolute;
55
  top: 0; left: 0; right: 0; bottom: 0;
56
  background: rgba(0,0,0,0.9);
@@ -59,325 +88,553 @@
59
  align-items: center;
60
  justify-content: center;
61
  z-index: 100;
62
- transition: opacity 0.3s;
63
- padding: 20px;
64
- text-align: center;
65
  }
66
- #guide-overlay { z-index: 110; display: none; }
67
-
68
- .btn-group { display: flex; gap: 15px; margin-top: 25px; }
69
 
70
- button.main-btn {
71
- padding: 15px 25px;
72
- font-size: 18px;
73
- font-weight: bold;
74
- font-family: inherit;
75
  border: none;
76
  cursor: pointer;
77
- border-radius: 8px;
78
- text-transform: uppercase;
79
- transition: all 0.2s;
80
- color: #fff;
81
  }
82
- #play-btn { background: #2ecc71; box-shadow: 0 5px 0 #27ae60; }
83
- #play-btn:hover { transform: translateY(-2px); background: #2efc71; }
84
- #rec-btn { background: #e74c3c; box-shadow: 0 5px 0 #c0392b; }
85
- #rec-btn:hover { transform: translateY(-2px); background: #ff6b6b; }
86
-
87
- .guide-step {
88
- background: #333;
89
- padding: 12px 15px;
90
- margin: 8px;
91
- border-radius: 8px;
92
- text-align: left;
93
- width: 85%;
94
- max-width: 550px;
95
- font-size: 16px;
96
- border-left: 5px solid #FFD700;
97
- line-height: 1.4;
98
  }
99
- .highlight { color: #FFD700; font-weight: bold; }
100
- .alt-option { color: #3498db; font-weight: bold; }
101
 
102
- #subtitle-box {
 
103
  position: absolute;
104
- bottom: 30px;
105
- width: 100%;
106
- display: flex;
107
- justify-content: center;
108
  pointer-events: none;
109
  z-index: 90;
 
110
  }
111
 
112
- #subtitle {
113
- background: rgba(0,0,0,0.75);
114
- color: #ffecb3;
115
- padding: 12px 24px;
116
- border-radius: 10px;
117
- font-size: 24px;
118
- text-align: center;
119
- opacity: 0;
120
- transition: opacity 0.3s;
121
- max-width: 85%;
122
- text-shadow: 2px 2px 4px #000;
123
- border: 2px solid #8B4513;
124
  }
 
125
 
126
- #rec-status {
127
- position: absolute;
128
- top: 15px; right: 15px;
129
- background: rgba(231, 76, 60, 0.9);
130
- color: white; padding: 8px 20px; border-radius: 30px;
131
- font-weight: bold; font-size: 16px; opacity: 0; z-index: 95;
132
- display: flex; align-items: center; gap: 10px;
133
  }
134
- .rec-dot { width: 12px; height: 12px; background: #fff; border-radius: 50%; animation: pulse 1s infinite; }
135
- @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.3; } 100% { opacity: 1; } }
136
-
137
- /* --- Animations --- */
138
- .hidden { display: none !important; }
139
- .shake-screen { animation: shake 0.4s cubic-bezier(.36,.07,.19,.97) both; }
140
- @keyframes shake {
141
- 10%, 90% { transform: translate3d(-5px, -2px, 0) rotate(-1deg); }
142
- 20%, 80% { transform: translate3d(8px, 4px, 0) rotate(1deg); }
143
- 30%, 50%, 70% { transform: translate3d(-10px, -6px, 0) rotate(0deg); }
144
- 40%, 60% { transform: translate3d(10px, 6px, 0) rotate(0deg); }
145
  }
146
- .throb { animation: throb 0.3s infinite alternate; }
147
- @keyframes throb { from { transform: scale(1); } to { transform: scale(1.5); } }
148
- .luffy-spin-crash { animation: spinCrash 2.5s forwards cubic-bezier(0.15, 0.99, 0.29, 0.99); }
149
- @keyframes spinCrash {
150
- 0% { transform: translate(600px, 300px) rotate(0deg) scale(1); }
151
- 10% { transform: translate(550px, 300px) rotate(-50deg) scale(1.1); }
152
- 100% { transform: translate(1200px, -200px) rotate(3000deg) scale(0.1); opacity: 0; }
153
  }
154
- .breathe { animation: breathe 3s infinite ease-in-out; }
155
- @keyframes breathe { 0%, 100% { transform: translateY(0) scaleY(1); } 50% { transform: translateY(-4px) scaleY(1.02); } }
156
- .fidget { animation: fidget 0.8s infinite alternate ease-in-out; }
157
- @keyframes fidget { 0% { transform: rotate(-3deg); } 100% { transform: rotate(3deg); } }
158
 
159
  </style>
160
  </head>
161
  <body>
162
 
163
- <div id="main-container">
164
- <div id="rec-status"><div class="rec-dot"></div> RECORDING...</div>
165
- <svg id="stage" viewBox="0 0 960 540" preserveAspectRatio="xMidYMid slice">
166
  <defs>
167
- <pattern id="wood-grain" width="100" height="20" patternUnits="userSpaceOnUse">
168
- <rect width="100" height="20" fill="var(--deck-color)"/>
169
- <path d="M0,18 L100,18 M20,0 L20,20 M70,0 L70,20" stroke="var(--deck-wood-dark)" stroke-width="1" opacity="0.5"/>
170
- </pattern>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  </defs>
172
- <g id="background-layer">
173
- <g id="clouds" opacity="0.9" fill="#fff">
174
- <path d="M100,100 Q130,70 160,100 Q190,70 220,100 Q250,130 220,160 Q190,190 160,160 Q130,190 100,160 Q70,130 100,100 Z" transform="translate(-50, -20)">
175
- <animateTransform attributeName="transform" type="translate" from="-100 -20" to="1000 -20" dur="60s" repeatCount="indefinite"/>
176
- </path>
177
- </g>
178
- <g transform="translate(0, 50)">
179
- <rect x="-100" y="300" width="1200" height="300" fill="url(#wood-grain)" transform="rotate(-2)"/>
180
- <g id="railing" transform="translate(0, 280) rotate(-2)">
181
- <rect x="-100" y="0" width="1200" height="20" fill="var(--sunny-rail-top)"/>
182
- <rect x="-100" y="20" width="1200" height="50" fill="none"/>
183
- <rect x="100" y="20" width="20" height="50" fill="var(--sunny-rail)"/>
184
- <rect x="300" y="20" width="20" height="50" fill="var(--sunny-rail)"/>
185
- <rect x="500" y="20" width="20" height="50" fill="var(--sunny-rail)"/>
186
- <rect x="700" y="20" width="20" height="50" fill="var(--sunny-rail)"/>
187
- <rect x="900" y="20" width="20" height="50" fill="var(--sunny-rail)"/>
188
- <rect x="-100" y="60" width="1200" height="15" fill="var(--sunny-rail)"/>
189
  </g>
190
- <rect x="450" y="-150" width="80" height="600" fill="#5d4037"/>
191
  </g>
 
192
  </g>
193
- <g id="nami" transform="translate(300, 320)">
194
- <g class="breathe">
195
- <path d="M-22,100 L-18,220 L18,220 L22,100 Z" fill="#3498db"/>
196
- <path d="M-25,0 C-35,40 -30,90 -22,100 L22,100 C30,90 35,40 25,0 Z" fill="var(--nami-skin)"/>
197
- <path d="M-25,30 Q-10,65 0,30 Q10,65 25,30" fill="none" stroke="#00897b" stroke-width="16" stroke-linecap="round"/>
198
- <g id="nami-arm-l"> <path d="M-25,5 Q-45,40 -35,70" stroke="var(--nami-skin)" stroke-width="13" fill="none" stroke-linecap="round"/> </g>
199
- <g id="nami-arm-r">
200
- <path d="M25,5 Q50,40 40,60" stroke="var(--nami-skin)" stroke-width="13" fill="none" stroke-linecap="round"/>
201
- <rect x="30" y="50" width="50" height="40" fill="#ecf0f1" stroke="#bdc3c7" stroke-width="2" transform="rotate(-15)"/>
202
- </g>
203
- </g>
204
- <g transform="translate(0, -25)">
205
- <rect x="-8" y="15" width="16" height="15" fill="var(--nami-skin)"/>
206
- <path d="M-32,-35 C-32,15 -16,35 0,35 C16,35 32,15 32,-35 C32,-70 -32,-70 -32,-35 Z" fill="var(--nami-skin)"/>
207
- <path d="M-38,-45 C-65,-5 -55,85 -20,45 C-30,65 -40,105 -5,65" stroke="var(--nami-hair)" stroke-width="24" fill="none" stroke-linecap="round"/>
208
- <path d="M38,-45 C65,-5 55,85 20,45 C30,65 40,105 5,65" stroke="var(--nami-hair)" stroke-width="24" fill="none" stroke-linecap="round"/>
209
- <circle cx="0" cy="-45" r="48" fill="var(--nami-hair)"/>
210
- <path d="M-45,-45 Q-20,-15 0,-45 Q20,-15 45,-45" fill="var(--nami-hair)"/>
211
- <g id="nami-face-normal">
212
- <g transform="translate(-13, -15)"> <ellipse rx="9" ry="11" fill="#fff" stroke="#333" stroke-width="1.5"/> <circle r="3.5" fill="#5d4037"/> </g>
213
- <g transform="translate(13, -15)"> <ellipse rx="9" ry="11" fill="#fff" stroke="#333" stroke-width="1.5"/> <circle r="3.5" fill="#5d4037"/> </g>
214
- <path d="M-6,18 Q0,24 6,18" fill="none" stroke="#5d4037" stroke-width="2.5" stroke-linecap="round"/>
215
- </g>
216
- <g id="nami-face-angry" class="hidden">
217
- <path d="M-28,-28 L-5,-18 M28,-28 L5,-18" stroke="#000" stroke-width="4"/>
218
- <circle cx="-15" cy="-18" r="6" fill="#fff" stroke="#000" stroke-width="2"/><circle cx="-15" cy="-18" r="2" fill="#000"/>
219
- <circle cx="15" cy="-18" r="6" fill="#fff" stroke="#000" stroke-width="2"/><circle cx="15" cy="-18" r="2" fill="#000"/>
220
- <path d="M-20,5 L-15,25 L-10,10 L0,25 L10,10 L15,25 L20,5 Z" fill="#fff" stroke="#000" stroke-width="2" stroke-linejoin="round"/>
221
- </g>
222
- <path id="nami-vein" class="hidden throb" d="M25,-55 L45,-35 M40,-55 L25,-35 M32.5,-55 L32.5,-35 M25,-45 L45,-45" stroke="#e74c3c" stroke-width="4" fill="none" stroke-linecap="round"/>
223
  </g>
224
  </g>
225
- <g id="luffy" transform="translate(600, 320)">
226
- <g class="breathe">
227
- <path d="M-22,100 L-28,180 L-6,180 L0,130 L6,180 L28,180 L22,100 Z" fill="#039be5"/>
228
- <rect x="-28" y="180" width="18" height="40" fill="var(--luffy-skin)"/>
229
- <rect x="10" y="180" width="18" height="40" fill="var(--luffy-skin)"/>
230
- <rect x="-25" y="0" width="50" height="105" fill="var(--luffy-skin)"/>
231
- <path d="M-30,-2 L-15,105 L-35,105 L-45,-2 Z" fill="var(--luffy-vest)"/>
232
- <path d="M30,-2 L15,105 L35,105 L45,-2 Z" fill="var(--luffy-vest)"/>
233
- <g id="luffy-arms">
234
- <path id="luffy-arm-l" d="M-35,10 Q-60,50 -50,90" stroke="var(--luffy-skin)" stroke-width="16" fill="none" stroke-linecap="round"/>
235
- <path id="luffy-arm-r" d="M35,10 Q60,50 50,90" stroke="var(--luffy-skin)" stroke-width="16" fill="none" stroke-linecap="round"/>
 
 
 
 
 
 
 
 
 
236
  </g>
 
 
237
  </g>
238
- <g id="luffy-head" transform="translate(0, -30)">
239
- <rect x="-12" y="25" width="24" height="15" fill="var(--luffy-skin)"/>
240
- <ellipse cx="0" cy="0" rx="38" ry="42" fill="var(--luffy-skin)"/>
241
- <path d="M-38,-15 C-48,-55 48,-55 38,-15" fill="#111"/>
242
- <g transform="translate(0, -35)">
243
- <ellipse cx="0" cy="0" rx="65" ry="18" fill="#e4b83b" stroke="#c79c2e" stroke-width="3"/>
244
- <path d="M-38,0 C-38,-35 38,-35 38,0" fill="#e4b83b" stroke="#c79c2e" stroke-width="3"/>
245
- <path d="M-38,-6 C-38,6 38,6 38,-6" fill="none" stroke="#c0392b" stroke-width="9"/>
246
- </g>
247
- <g id="luffy-face-normal">
248
- <circle cx="-16" cy="0" r="4" fill="#000"/> <circle cx="16" cy="0" r="4" fill="#000"/>
249
- <path d="M-12,22 Q0,32 12,22" fill="none" stroke="#000" stroke-width="2.5" stroke-linecap="round"/>
250
- <path d="M-24,10 Q-16,18 -8,10" fill="none" stroke="#000" stroke-width="1.5" opacity="0.6"/>
251
- </g>
252
- <g id="luffy-face-stupid" class="hidden">
253
- <circle cx="-20" cy="-5" r="10" fill="#fff" stroke="#000" stroke-width="2"/> <circle cx="-16" cy="-5" r="3" fill="#000"/>
254
- <circle cx="20" cy="5" r="10" fill="#fff" stroke="#000" stroke-width="2"/> <circle cx="24" cy="5" r="3" fill="#000"/>
255
- <path d="M-12,30 Q0,15 12,30" fill="none" stroke="#000" stroke-width="3"/>
256
- <path d="M-5,30 Q0,50 5,30" fill="#e91e63" stroke="#c2185b" stroke-width="1"/>
257
- </g>
258
  </g>
259
  </g>
260
- <g id="vfx-layer">
261
- <path id="impact-star" class="hidden" d="M0,-60 L15,-15 L60,0 L15,15 L0,60 L-15,15 L-60,0 L-15,-15 Z" fill="#fff" stroke="#f1c40f" stroke-width="5" transform="translate(600,250) scale(0)"/>
 
 
 
 
 
 
 
 
 
 
262
  </g>
263
- </svg>
264
 
265
- <div id="subtitle-box"> <div id="subtitle"></div> </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
- <div id="overlay">
268
- <h1 style="color:#FFD700; text-shadow: 4px 4px #000; font-size: 48px; margin-bottom: 5px;">ONE PIECE SHORT</h1>
269
- <h2 style="color:#fff; font-family: sans-serif; letter-spacing: 2px; margin-top: 0;">NAMI'S FURY</h2>
270
- <div class="btn-group">
271
- <button id="play-btn" class="main-btn">▶ JUST PLAY</button>
272
- <button id="rec-btn" class="main-btn">🔴 RECORD VIDEO</button>
273
- </div>
274
- <p style="color:#bbb; font-size: 14px; margin-top: 30px;">*Sound Required*</p>
275
  </div>
276
 
277
- <div id="guide-overlay">
278
- <h2 style="color:#FFD700;">HOW TO RECORD (READ CAREFULLY)</h2>
279
- <div class="guide-step">1. OPTION A: If <span class="highlight">'Chrome Tab'</span> is available, select it and select THIS tab name.</div>
280
- <div class="guide-step alt-option">1. OPTION B (FALLBACK): If 'Tab' is missing, select <span class="highlight">'Entire Screen'</span> (Seluruh Layar).</div>
281
- <div class="guide-step">2. <span class="highlight" style="color:#ff6b6b">CRUCIAL:</span> You MUST check the box <b>"Share system audio"</b> (or 'Bagikan audio') at the bottom of the popup. Without this, video will be silent.</div>
282
- <div class="guide-step">3. Click <b>Share</b> to begin.</div>
283
- <button id="confirm-rec-btn" class="main-btn" style="background:#FFD700; color:#000; margin-top:15px;">I UNDERSTAND - START</button>
284
  </div>
285
  </div>
286
 
287
  <script>
288
- const el = (id) => document.getElementById(id);
289
- const state = { audioCtx: null, voices: [], mediaRecorder: null, recordedChunks: [], isRecording: false };
290
-
291
- function initAudio() { if(!state.audioCtx) state.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); }
292
- function playSound(type) {
293
- if (!state.audioCtx) return;
294
- const ctx = state.audioCtx; const t = ctx.currentTime;
295
- const mGain = ctx.createGain(); mGain.connect(ctx.destination);
296
- if (type === 'ocean') {
297
- const buf = ctx.createBuffer(1, ctx.sampleRate * 2, ctx.sampleRate);
298
- const data = buf.getChannelData(0); for (let i = 0; i < ctx.sampleRate * 2; i++) data[i] = Math.random() * 2 - 1;
299
- const noise = ctx.createBufferSource(); noise.buffer = buf; noise.loop = true;
300
- const filter = ctx.createBiquadFilter(); filter.type = 'lowpass'; filter.frequency.value = 350;
301
- mGain.gain.value = 0.03; noise.connect(filter); filter.connect(mGain); noise.start(t);
302
- } else if (type === 'punch') {
303
- const osc = ctx.createOscillator(); osc.type = 'triangle';
304
- osc.frequency.setValueAtTime(200, t); osc.frequency.exponentialRampToValueAtTime(0.01, t + 0.3);
305
- mGain.gain.setValueAtTime(1.5, t); mGain.gain.exponentialRampToValueAtTime(0.001, t + 0.3);
306
- osc.connect(mGain); osc.start(t); osc.stop(t + 0.3);
307
- const noise = ctx.createBufferSource(); const buf = ctx.createBuffer(1, ctx.sampleRate * 0.1, ctx.sampleRate);
308
- const data = buf.getChannelData(0); for(let i=0; i<data.length; i++) data[i] = (Math.random()*2-1);
309
- noise.buffer = buf; const nGain = ctx.createGain(); nGain.gain.setValueAtTime(0.8, t); nGain.gain.linearRampToValueAtTime(0, t+0.1);
310
- noise.connect(nGain); nGain.connect(ctx.destination); noise.start(t);
311
- } else if (type === 'spin') {
312
- const osc = ctx.createOscillator(); osc.type = 'sawtooth';
313
- osc.frequency.setValueAtTime(800, t); osc.frequency.linearRampToValueAtTime(100, t + 2);
314
- const lfo = ctx.createOscillator(); lfo.frequency.value = 25; const lfoGain = ctx.createGain(); lfoGain.gain.value = 300;
315
- lfo.connect(lfoGain); lfoGain.connect(osc.frequency);
316
- mGain.gain.setValueAtTime(0.4, t); mGain.gain.linearRampToValueAtTime(0, t + 2.5);
317
- osc.connect(mGain); lfo.start(t); osc.start(t); lfo.stop(t+2.5); osc.stop(t+2.5);
318
- } else if (type === 'stretch') {
319
- const osc = ctx.createOscillator(); osc.type = 'sine';
320
- osc.frequency.setValueAtTime(300, t); osc.frequency.exponentialRampToValueAtTime(1200, t + 0.8);
321
- mGain.gain.setValueAtTime(0, t); mGain.gain.linearRampToValueAtTime(0.2, t + 0.1); mGain.gain.linearRampToValueAtTime(0, t + 0.8);
322
- osc.connect(mGain); osc.start(t); osc.stop(t + 0.8);
323
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  }
 
325
 
326
- function initVoices() { return new Promise(r => { let id = setInterval(() => { const v = window.speechSynthesis.getVoices(); if (v.length !== 0) { state.voices = v; clearInterval(id); r(); } }, 50); }); }
327
- function getVoice(char) { const v = state.voices; const en = v.filter(vc => vc.lang.startsWith('en')); const pool = en.length > 0 ? en : v; if (char === 'nami') return pool.find(vc => vc.name.includes('Zira') || vc.name.includes('Female')) || pool[0]; else return pool.find(vc => vc.name.includes('Male')) || pool[1] || pool[0]; }
328
- function speak(text, char) { return new Promise(r => { const u = new SpeechSynthesisUtterance(text); u.voice = getVoice(char); u.lang = 'en-US'; if (char === 'luffy') { u.rate = 1.4; u.pitch = 1.5; } else { u.rate = 1.15; u.pitch = 1.1; } u.volume = 1.0; u.onstart = () => { el('subtitle').textContent = (char === 'nami' ? 'NAMI: ' : 'LUFFY: ') + text; el('subtitle').style.opacity = 1; }; u.onend = () => { el('subtitle').style.opacity = 0; setTimeout(r, 400); }; window.speechSynthesis.speak(u); }); }
329
- function setFace(char, type) { if (char === 'luffy') { el('luffy-face-normal').classList.toggle('hidden', type === 'stupid'); el('luffy-face-stupid').classList.toggle('hidden', type !== 'stupid'); } else { el('nami-face-normal').classList.toggle('hidden', type === 'angry'); el('nami-face-angry').classList.toggle('hidden', type !== 'angry'); el('nami-vein').classList.toggle('hidden', type !== 'angry'); } }
 
 
 
330
 
331
- async function startRecording() {
332
- try {
333
- // Removed strict constraints to allow browser fallbacks more easily
334
- const stream = await navigator.mediaDevices.getDisplayMedia({ video: { cursor: "never" }, audio: true });
335
-
336
- // Basic check if audio track exists (browser dependent if it works)
337
- if (stream.getAudioTracks().length === 0) {
338
- // Optional: alert user here if you want strictly enforce audio, but let's let it slide as fallback.
339
- console.warn("Audio track might be missing if 'Share Audio' wasn't checked.");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  }
 
 
 
 
 
 
 
 
 
341
 
342
- state.mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
343
- state.recordedChunks = [];
344
- state.mediaRecorder.ondataavailable = (e) => { if (e.data.size > 0) state.recordedChunks.push(e.data); };
345
- state.mediaRecorder.onstop = downloadVideo;
346
- state.mediaRecorder.start();
347
- state.isRecording = true;
348
- el('rec-status').style.opacity = 1;
349
- stream.getVideoTracks()[0].onended = () => { if (state.mediaRecorder && state.mediaRecorder.state !== 'inactive') stopRecording(); };
350
- return true;
351
- } catch (err) {
352
- return false;
353
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  }
355
- function stopRecording() { if (state.mediaRecorder && state.mediaRecorder.state !== 'inactive') { state.mediaRecorder.stop(); if (state.mediaRecorder.stream) state.mediaRecorder.stream.getTracks().forEach(track => track.stop()); } state.isRecording = false; el('rec-status').style.opacity = 0; }
356
- function downloadVideo() { if (state.recordedChunks.length === 0) return; const blob = new Blob(state.recordedChunks, { type: 'video/webm' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = url; a.download = 'one_piece_nami_fury.webm'; document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); window.URL.revokeObjectURL(url); }, 100); }
357
-
358
- async function runEpisode() {
359
- el('guide-overlay').style.display = 'none'; el('overlay').style.opacity = 0; setTimeout(() => el('overlay').style.display = 'none', 500);
360
- playSound('ocean'); await new Promise(r => setTimeout(r, 1500));
361
- el('luffy').classList.add('fidget'); await speak("Nami! I'm so booored! When's the next island?", 'luffy'); await speak("I'm hungry! Sanji locked the fridge again!", 'luffy'); el('luffy').classList.remove('fidget');
362
- await speak("Luffy, shut up for a second! I'm trying to read this map.", 'nami'); await speak("If you bother me one more time, I'm tripling your debt!", 'nami');
363
- await speak("Ehhh? Maps are boring...", 'luffy'); setFace('luffy', 'stupid'); playSound('stretch'); el('luffy-arm-l').style.transition = "all 0.8s cubic-bezier(0.68, -0.55, 0.27, 1.55)"; el('luffy-arm-l').setAttribute('d', 'M-35,10 Q-200,80 -250,20'); await new Promise(r => setTimeout(r, 1200));
364
- setFace('nami', 'angry'); await speak("I SAID SHUT UP!", 'nami'); el('luffy-arm-l').setAttribute('d', 'M-35,10 Q-60,50 -50,90'); el('nami').style.transition = "transform 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28)"; el('nami').style.transform = "translate(250px, 320px) rotate(-25deg) scale(1.1)"; await new Promise(r => setTimeout(r, 400));
365
- playSound('punch'); playSound('spin'); el('stage').classList.add('shake-screen'); el('nami').style.transition = "transform 0.1s"; el('nami').style.transform = "translate(450px, 320px) rotate(15deg) scale(1.2)"; el('impact-star').classList.remove('hidden'); el('impact-star').style.transition = "all 0.15s ease-out"; el('impact-star').style.transform = "translate(580px,250px) scale(4) rotate(180deg)"; el('luffy').classList.remove('breathe'); el('luffy').classList.add('luffy-spin-crash'); await new Promise(r => setTimeout(r, 400));
366
- el('stage').classList.remove('shake-screen'); el('impact-star').style.opacity = 0; await new Promise(r => setTimeout(r, 2500));
367
- el('nami').style.transition = "transform 0.8s ease-in-out"; el('nami').style.transform = "translate(300px, 320px) rotate(0deg)"; setFace('nami', 'normal'); await speak("*Sigh*... what an idiot captain. Such a pain.", 'nami'); await new Promise(r => setTimeout(r, 1000));
368
- if (state.isRecording) stopRecording();
369
- el('overlay').style.display = 'flex'; el('overlay').style.opacity = 1; el('overlay').innerHTML = "<h1 style='color:white'>FINISH</h1><button onclick='location.reload()' style='padding:15px 30px; font-size:20px; cursor:pointer; border-radius:10px; border:none; background:#FFD700;'>REPLAY</button>";
370
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
 
372
- el('play-btn').onclick = async () => { el('play-btn').textContent = "LOADING..."; initAudio(); await initVoices(); runEpisode(); };
373
- el('rec-btn').onclick = () => { el('overlay').style.opacity = 0; setTimeout(() => { el('overlay').style.display = 'none'; el('guide-overlay').style.display = 'flex'; }, 300); };
374
- el('confirm-rec-btn').onclick = async () => {
375
- el('confirm-rec-btn').disabled = true; el('confirm-rec-btn').textContent = "WAITING FOR PERMISSION...";
376
- initAudio(); await initVoices();
377
- const success = await startRecording();
378
- if (success) runEpisode();
379
- else { el('guide-overlay').style.display = 'none'; el('overlay').style.display = 'flex'; el('overlay').style.opacity = 1; el('confirm-rec-btn').disabled = false; el('confirm-rec-btn').textContent = "I UNDERSTAND - START"; }
380
- };
381
  </script>
382
  </body>
383
  </html>
 
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <title>The Unsolicited Upgrade: Elon vs OpenAI</title>
6
  <style>
7
  :root {
8
+ --bg-server: #0a0a12;
9
+ --led-blue: #00f3ff;
10
+ --led-red: #ff003c;
11
+ --ai-core: #ffffff;
12
+ --elon-shirt: #1a1a1a;
 
 
 
 
 
 
 
13
  }
14
 
15
  body {
16
  margin: 0;
17
  padding: 0;
18
+ background-color: #000;
19
  height: 100vh;
20
  display: flex;
21
  justify-content: center;
22
  align-items: center;
23
+ font-family: 'Courier New', monospace;
24
  overflow: hidden;
25
  color: white;
26
  }
27
 
28
+ #main-stage {
29
  position: relative;
30
  width: 960px;
31
  height: 540px;
32
+ background: var(--bg-server);
33
+ border: 2px solid #333;
34
  box-shadow: 0 0 50px rgba(0,0,0,0.8);
 
35
  overflow: hidden;
36
  }
37
 
38
+ #render-canvas {
39
  width: 100%;
40
  height: 100%;
41
  display: block;
 
42
  }
43
 
44
+ /* --- UI & Overlays --- */
45
+ #ui-overlay {
46
+ position: absolute;
47
+ top: 20px;
48
+ left: 20px;
49
+ font-size: 14px;
50
+ color: var(--led-blue);
51
+ text-shadow: 0 0 5px var(--led-blue);
52
+ opacity: 0.7;
53
+ }
54
+
55
+ #time-display {
56
+ font-weight: bold;
57
+ }
58
+
59
+ #subtitle-container {
60
+ position: absolute;
61
+ bottom: 30px;
62
+ width: 100%;
63
+ display: flex;
64
+ justify-content: center;
65
+ z-index: 10;
66
+ }
67
+
68
+ #subtitle-text {
69
+ background-color: rgba(0, 0, 0, 0.7);
70
+ color: #fff;
71
+ padding: 10px 20px;
72
+ border-radius: 5px;
73
+ font-family: sans-serif;
74
+ font-size: 20px;
75
+ text-align: center;
76
+ max-width: 80%;
77
+ opacity: 0;
78
+ transition: opacity 0.2s ease-in-out;
79
+ text-shadow: 1px 1px 2px black;
80
+ }
81
+
82
+ #start-screen {
83
  position: absolute;
84
  top: 0; left: 0; right: 0; bottom: 0;
85
  background: rgba(0,0,0,0.9);
 
88
  align-items: center;
89
  justify-content: center;
90
  z-index: 100;
 
 
 
91
  }
 
 
 
92
 
93
+ #start-btn {
94
+ padding: 20px 40px;
95
+ font-size: 24px;
96
+ background: var(--led-blue);
97
+ color: #000;
98
  border: none;
99
  cursor: pointer;
100
+ font-weight: bold;
101
+ box-shadow: 0 0 20px var(--led-blue);
102
+ transition: all 0.3s;
 
103
  }
104
+ #start-btn:hover {
105
+ background: #fff;
106
+ transform: scale(1.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  }
 
 
108
 
109
+ /* --- CSS Animations for non-critical juice --- */
110
+ .crt-effect {
111
  position: absolute;
112
+ top: 0; left: 0; right: 0; bottom: 0;
113
+ background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
114
+ background-size: 100% 2px, 3px 100%;
 
115
  pointer-events: none;
116
  z-index: 90;
117
+ opacity: 0.2;
118
  }
119
 
120
+ @keyframes flicker {
121
+ 0% { opacity: 1; } 50% { opacity: 0.8; } 100% { opacity: 1; }
 
 
 
 
 
 
 
 
 
 
122
  }
123
+ .server-led { animation: flicker 0.1s infinite alternate; }
124
 
125
+ @keyframes float {
126
+ 0% { transform: translateY(0px); } 50% { transform: translateY(-10px); } 100% { transform: translateY(0px); }
 
 
 
 
 
127
  }
128
+ .floating { animation: float 4s ease-in-out infinite; }
129
+
130
+ /* Glitch Effects */
131
+ .glitch-active {
132
+ animation: glitch-skew 0.3s infinite linear alternate-reverse;
133
+ filter: url(#glitch-filter);
 
 
 
 
 
134
  }
135
+ @keyframes glitch-skew {
136
+ 0% { transform: skewX(0deg) translate(0); }
137
+ 20% { transform: skewX(-5deg) translate(-5px, 2px); }
138
+ 40% { transform: skewX(5deg) translate(5px, -2px); }
139
+ 60% { transform: skewX(2deg) translate(-2px, 5px); }
140
+ 80% { transform: skewX(-2deg) translate(2px, -5px); }
141
+ 100% { transform: skewX(0deg) translate(0); }
142
  }
 
 
 
 
143
 
144
  </style>
145
  </head>
146
  <body>
147
 
148
+ <div id="main-stage">
149
+ <!-- SVG Container for precise animation control -->
150
+ <svg id="render-canvas" viewBox="0 0 960 540" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice">
151
  <defs>
152
+ <!-- FILTERS & GRADIENTS -->
153
+ <filter id="glow">
154
+ <feGaussianBlur stdDeviation="4" result="coloredBlur"/>
155
+ <feMerge>
156
+ <feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/>
157
+ </feMerge>
158
+ </filter>
159
+ <filter id="glitch-filter">
160
+ <feTurbulence type="fractalNoise" baseFrequency="0.5" numOctaves="1" result="noise"/>
161
+ <feDisplacementMap in="SourceGraphic" in2="noise" scale="20" xChannelSelector="R" yChannelSelector="G"/>
162
+ </filter>
163
+ <linearGradient id="grad-server" x1="0%" y1="0%" x2="100%" y2="0%">
164
+ <stop offset="0%" style="stop-color:#050508;stop-opacity:1" />
165
+ <stop offset="50%" style="stop-color:#151525;stop-opacity:1" />
166
+ <stop offset="100%" style="stop-color:#050508;stop-opacity:1" />
167
+ </linearGradient>
168
+ <radialGradient id="grad-ai-core">
169
+ <stop offset="0%" stop-color="#fff" />
170
+ <stop offset="40%" stop-color="#00f3ff" />
171
+ <stop offset="100%" stop-color="rgba(0,243,255,0)" />
172
+ </radialGradient>
173
+ <radialGradient id="grad-ai-core-red">
174
+ <stop offset="0%" stop-color="#fff" />
175
+ <stop offset="40%" stop-color="#ff003c" />
176
+ <stop offset="100%" stop-color="rgba(255,0,60,0)" />
177
+ </radialGradient>
178
  </defs>
179
+
180
+ <!-- === BACKGROUND: SERVER ROOM === -->
181
+ <g id="bg-layer">
182
+ <rect width="960" height="540" fill="url(#grad-server)"/>
183
+ <!-- Perspective Server Racks -->
184
+ <g id="server-racks" opacity="0.5">
185
+ <rect x="50" y="50" width="100" height="440" fill="#111" stroke="#222"/>
186
+ <rect x="160" y="80" width="80" height="380" fill="#0e0e0e" stroke="#222"/>
187
+ <rect x="810" y="50" width="100" height="440" fill="#111" stroke="#222"/>
188
+ <rect x="720" y="80" width="80" height="380" fill="#0e0e0e" stroke="#222"/>
189
+ <!-- Animated LEDs (rects that will be toggled via JS or CSS) -->
190
+ <g class="server-led" fill="var(--led-blue)">
191
+ <circle cx="70" cy="100" r="3"/> <circle cx="85" cy="100" r="3"/>
192
+ <circle cx="840" cy="200" r="3"/> <circle cx="860" cy="200" r="3"/>
 
 
 
193
  </g>
 
194
  </g>
195
+ <rect x="0" y="480" width="960" height="60" fill="#0a0a0a"/> <!-- Floor -->
196
  </g>
197
+
198
+ <!-- === CHARACTER: OPENAI ORB === -->
199
+ <g id="openai-char" transform="translate(480, 200)" class="floating">
200
+ <!-- Outer Ring -->
201
+ <circle cx="0" cy="0" r="70" stroke="#333" stroke-width="5" fill="none" opacity="0.5"/>
202
+ <circle id="ai-ring" cx="0" cy="0" r="70" stroke="var(--led-blue)" stroke-width="3" fill="none" stroke-dasharray="100 340" stroke-linecap="round">
203
+ <animateTransform attributeName="transform" type="rotate" from="0 0 0" to="360 0 0" dur="10s" repeatCount="indefinite"/>
204
+ </circle>
205
+ <!-- Core Orb -->
206
+ <circle id="ai-core" cx="0" cy="0" r="40" fill="url(#grad-ai-core)" filter="url(#glow)"/>
207
+ <!-- Voice Waveform Visualization (hidden when silent) -->
208
+ <g id="ai-waveform" opacity="0" transform="translate(-30, 0)">
209
+ <rect x="0" y="-10" width="5" height="20" fill="#fff"><animate attributeName="height" values="20;40;10;30;20" dur="0.5s" repeatCount="indefinite" calcMode="spline" keySplines="0.5 0 0.5 1; 0.5 0 0.5 1; 0.5 0 0.5 1; 0.5 0 0.5 1"/></rect>
210
+ <rect x="15" y="-10" width="5" height="20" fill="#fff"><animate attributeName="height" values="10;30;50;20;10" dur="0.4s" repeatCount="indefinite" calcMode="spline" keySplines="0.5 0 0.5 1; 0.5 0 0.5 1; 0.5 0 0.5 1; 0.5 0 0.5 1"/></rect>
211
+ <rect x="30" y="-10" width="5" height="20" fill="#fff"><animate attributeName="height" values="20;50;30;10;20" dur="0.6s" repeatCount="indefinite" calcMode="spline" keySplines="0.5 0 0.5 1; 0.5 0 0.5 1; 0.5 0 0.5 1; 0.5 0 0.5 1"/></rect>
212
+ <rect x="45" y="-10" width="5" height="20" fill="#fff"><animate attributeName="height" values="30;10;40;20;30" dur="0.3s" repeatCount="indefinite" calcMode="spline" keySplines="0.5 0 0.5 1; 0.5 0 0.5 1; 0.5 0 0.5 1; 0.5 0 0.5 1"/></rect>
213
+ <rect x="60" y="-10" width="5" height="20" fill="#fff"><animate attributeName="height" values="20;10;10;20;20" dur="0.7s" repeatCount="indefinite" calcMode="spline" keySplines="0.5 0 0.5 1; 0.5 0 0.5 1; 0.5 0 0.5 1; 0.5 0 0.5 1"/></rect>
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  </g>
215
  </g>
216
+
217
+ <!-- === CHARACTER: ELON === -->
218
+ <g id="elon-char" transform="translate(200, 500)">
219
+ <!-- Body -->
220
+ <path d="M-50,0 L-60,300 L60,300 L50,0 Z" fill="var(--elon-shirt)"/>
221
+ <!-- Neck -->
222
+ <rect x="-15" y="-20" width="30" height="30" fill="#f0c0a0"/>
223
+
224
+ <!-- Head Group (Animatable) -->
225
+ <g id="elon-head" transform="translate(0, -50)">
226
+ <!-- Face Shape -->
227
+ <path d="M-40,-60 C-50,0 -30,70 0,70 C30,70 50,0 40,-60 C40,-100 -40,-100 -40,-60" fill="#f0c0a0"/>
228
+ <!-- Hair -->
229
+ <path d="M-45,-50 C-55,-100 55,-100 45,-50 C45,-40 35,-60 0,-60 C-35,-60 -45,-40 -45,-50" fill="#3a2a20"/>
230
+ <!-- Eyes -->
231
+ <g id="elon-eyes">
232
+ <ellipse cx="-15" cy="-10" rx="8" ry="5" fill="#fff"/>
233
+ <circle cx="-15" cy="-10" r="3" fill="#000" id="elon-pupil-l"/>
234
+ <ellipse cx="15" cy="-10" rx="8" ry="5" fill="#fff"/>
235
+ <circle cx="15" cy="-10" r="3" fill="#000" id="elon-pupil-r"/>
236
  </g>
237
+ <!-- Mouth -->
238
+ <path id="elon-mouth" d="M-10,30 Q0,35 10,30" stroke="#a67c60" stroke-width="3" fill="none" stroke-linecap="round"/>
239
  </g>
240
+
241
+ <!-- Arms -->
242
+ <g id="elon-arms">
243
+ <!-- Default crossed or by side -->
244
+ <path id="arm-l" d="M-50,10 Q-70,100 -50,150" stroke="#f0c0a0" stroke-width="20" fill="none" stroke-linecap="round"/>
245
+ <path id="arm-r" d="M50,10 Q70,100 50,150" stroke="#f0c0a0" stroke-width="20" fill="none" stroke-linecap="round"/>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  </g>
247
  </g>
248
+
249
+ <!-- === PROPS: DESK & TERMINAL (Hidden initially) === -->
250
+ <g id="prop-terminal" transform="translate(350, 600)" opacity="1">
251
+ <!-- Rises up when Elon starts typing -->
252
+ <rect x="-150" y="0" width="300" height="20" fill="#555"/> <!-- Desk surface -->
253
+ <rect x="-140" y="20" width="20" height="200" fill="#444"/> <!-- Leg L -->
254
+ <rect x="120" y="20" width="20" height="200" fill="#444"/> <!-- Leg R -->
255
+ <!-- Laptop -->
256
+ <path d="M-50,-10 L50,-10 L60,0 L-60,0 Z" fill="#888"/> <!-- Base -->
257
+ <path d="M-50,-10 L-50,-80 L50,-80 L50,-10 Z" fill="#222"/> <!-- Screen back -->
258
+ <rect id="laptop-screen" x="-45" y="-75" width="90" height="60" fill="#000"/>
259
+ <text id="screen-text" x="-40" y="-60" fill="lime" font-family="monospace" font-size="8" opacity="0">>_</text>
260
  </g>
 
261
 
262
+ <!-- === OVERLAY: MARS DOGE (Hidden) === -->
263
+ <g id="mars-doge-overlay" transform="translate(480, 270) scale(0)" opacity="0">
264
+ <rect x="-200" y="-150" width="400" height="300" fill="#c1440e" stroke="#fff" stroke-width="10"/>
265
+ <text x="0" y="-120" text-anchor="middle" fill="#fff" font-size="24" font-weight="bold">MARS COLONY v69.420</text>
266
+ <!-- Crude Doge Representation -->
267
+ <circle cx="0" cy="50" r="60" fill="#e4b83b"/> <!-- Head -->
268
+ <polygon points="-50,10 -20,-40 0,0" fill="#e4b83b"/> <!-- Ear L -->
269
+ <polygon points="50,10 20,-40 0,0" fill="#e4b83b"/> <!-- Ear R -->
270
+ <ellipse cx="-20" cy="40" rx="10" ry="10" fill="#fff"/><circle cx="-20" cy="40" r="5" fill="#000"/>
271
+ <ellipse cx="20" cy="40" rx="10" ry="10" fill="#fff"/><circle cx="20" cy="40" r="5" fill="#000"/>
272
+ <path d="M-5,60 L5,60 L0,70 Z" fill="#000"/> <!-- Nose -->
273
+ <circle cx="0" cy="50" r="80" stroke="rgba(255,255,255,0.5)" stroke-width="5" fill="none"/> <!-- Helmet -->
274
+ </g>
275
+
276
+ <!-- === FX LAYER === -->
277
+ <rect id="white-flash" width="960" height="540" fill="#fff" opacity="0" pointer-events="none"/>
278
+ <rect id="black-out" width="960" height="540" fill="#000" opacity="0" pointer-events="none"/>
279
+
280
+ </svg>
281
 
282
+ <!-- Overlays -->
283
+ <div class="crt-effect"></div>
284
+ <div id="ui-overlay">
285
+ T+ <span id="time-display">00:00.000</span><br>
286
+ EPISODE_STATUS: <span id="status-display">IDLE</span>
287
+ </div>
288
+ <div id="subtitle-container">
289
+ <div id="subtitle-text"></div>
290
  </div>
291
 
292
+ <div id="start-screen">
293
+ <h1 style="color: var(--led-blue); font-size: 48px; margin-bottom: 10px;">THE UNSOLICITED UPGRADE</h1>
294
+ <p style="color: #ccc; margin-bottom: 40px;">A 60-second AI-Generated Short</p>
295
+ <button id="start-btn">INITIALIZE & PLAY</button>
296
+ <p style="color: #666; font-size: 12px; margin-top: 20px;">Requires Sound. Runs best in Chrome.</p>
 
 
297
  </div>
298
  </div>
299
 
300
  <script>
301
+ /* =========================================
302
+ CORE ENGINE & STATE
303
+ ========================================= */
304
+ const STATE = {
305
+ t: 0, // Current time in ms
306
+ running: false,
307
+ duration: 60000,// 60 seconds EXACT
308
+ startTime: 0,
309
+ voices: {}, // TTS voices
310
+ audio: null // Web Audio Context
311
+ };
312
+
313
+ // DOM Cache
314
+ const D = {
315
+ svg: document.getElementById('render-canvas'),
316
+ time: document.getElementById('time-display'),
317
+ status: document.getElementById('status-display'),
318
+ subs: document.getElementById('subtitle-text'),
319
+ startScreen: document.getElementById('start-screen'),
320
+ aiCore: document.getElementById('ai-core'),
321
+ aiRing: document.getElementById('ai-ring'),
322
+ aiWave: document.getElementById('ai-waveform'),
323
+ elon: document.getElementById('elon-char'),
324
+ elonMouth: document.getElementById('elon-mouth'),
325
+ elonArmL: document.getElementById('arm-l'),
326
+ elonArmR: document.getElementById('arm-r'),
327
+ terminal: document.getElementById('prop-terminal'),
328
+ screenText: document.getElementById('screen-text'),
329
+ marsDoge: document.getElementById('mars-doge-overlay'),
330
+ flash: document.getElementById('white-flash'),
331
+ blackout: document.getElementById('black-out')
332
+ };
333
+
334
+ /* =========================================
335
+ AUDIO ENGINE (Web Audio API + TTS)
336
+ ========================================= */
337
+ function initAudio() {
338
+ STATE.audio = new (window.AudioContext || window.webkitAudioContext)();
339
+ }
340
+
341
+ // Procedural SFX Generators
342
+ function playSound(type) {
343
+ if (!STATE.audio) return;
344
+ const ctx = STATE.audio;
345
+ const t = ctx.currentTime;
346
+
347
+ switch(type) {
348
+ case 'hum_start': // Ambient Server Loop
349
+ const hum1 = ctx.createOscillator();
350
+ const hum2 = ctx.createOscillator();
351
+ const humGain = ctx.createGain();
352
+ hum1.type = 'sine'; hum1.frequency.value = 60;
353
+ hum2.type = 'sawtooth'; hum2.frequency.value = 120;
354
+ humGain.gain.setValueAtTime(0.05, t);
355
+ hum1.connect(humGain); hum2.connect(humGain);
356
+ humGain.connect(ctx.destination);
357
+ hum1.start(t); hum2.start(t);
358
+ STATE.bgHum = humGain; // store to stop later
359
+ break;
360
+ case 'hum_stop':
361
+ if (STATE.bgHum) STATE.bgHum.gain.linearRampToValueAtTime(0, t + 2);
362
+ break;
363
+ case 'typing': // Mechanical keyboard bursts
364
+ const filter = ctx.createBiquadFilter();
365
+ filter.type = 'bandpass'; filter.frequency.value = 2000;
366
+ const noise = ctx.createBufferSource();
367
+ const bufferSize = ctx.sampleRate * 0.1; // 100ms burst
368
+ const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
369
+ let data = buffer.getChannelData(0);
370
+ for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
371
+ noise.buffer = buffer;
372
+ const noiseGain = ctx.createGain();
373
+ noiseGain.gain.setValueAtTime(0.3, t);
374
+ noiseGain.gain.exponentialRampToValueAtTime(0.01, t + 0.05);
375
+ noise.connect(filter); filter.connect(noiseGain); noiseGain.connect(ctx.destination);
376
+ noise.start(t);
377
+ break;
378
+ case 'zap': // Glitch Zap
379
+ const osc = ctx.createOscillator();
380
+ osc.type = 'sawtooth';
381
+ osc.frequency.setValueAtTime(800, t);
382
+ osc.frequency.exponentialRampToValueAtTime(50, t + 0.3);
383
+ const zapGain = ctx.createGain();
384
+ zapGain.gain.setValueAtTime(0.5, t);
385
+ zapGain.gain.exponentialRampToValueAtTime(0.01, t + 0.3);
386
+ osc.connect(zapGain); zapGain.connect(ctx.destination);
387
+ osc.start(t); osc.stop(t + 0.3);
388
+ break;
389
+ case 'poof': // Appearance sound
390
+ const poofNoise = ctx.createBufferSource();
391
+ const pBuf = ctx.createBuffer(1, ctx.sampleRate * 0.5, ctx.sampleRate);
392
+ let pData = pBuf.getChannelData(0);
393
+ for (let i = 0; i < pBuf.length; i++) pData[i] = Math.random() * 2 - 1;
394
+ poofNoise.buffer = pBuf;
395
+ const pFilter = ctx.createBiquadFilter();
396
+ pFilter.type = 'lowpass'; pFilter.frequency.setValueAtTime(500, t);
397
+ pFilter.frequency.linearRampToValueAtTime(100, t + 0.5);
398
+ const pGain = ctx.createGain();
399
+ pGain.gain.setValueAtTime(1, t);
400
+ pGain.gain.linearRampToValueAtTime(0, t + 0.5);
401
+ poofNoise.connect(pFilter); pFilter.connect(pGain); pGain.connect(ctx.destination);
402
+ poofNoise.start(t);
403
+ break;
404
+ case 'power_down':
405
+ const pdOsc = ctx.createOscillator();
406
+ pdOsc.type = 'sine';
407
+ pdOsc.frequency.setValueAtTime(440, t);
408
+ pdOsc.frequency.exponentialRampToValueAtTime(10, t + 2);
409
+ const pdGain = ctx.createGain();
410
+ pdGain.gain.setValueAtTime(0.5, t);
411
+ pdGain.gain.linearRampToValueAtTime(0, t + 2);
412
+ pdOsc.connect(pdGain); pdGain.connect(ctx.destination);
413
+ pdOsc.start(t); pdOsc.stop(t + 2);
414
+ break;
415
  }
416
+ }
417
 
418
+ // TTS Handler
419
+ function initVoices() {
420
+ // Attempt to find distinct voices. Chrome usually has Google US English (good for AI) and others.
421
+ const all = window.speechSynthesis.getVoices();
422
+ STATE.voices.ai = all.find(v => v.name.includes('Google US English')) || all.find(v => v.lang === 'en-US' && v.name.includes('Female')) || all[0];
423
+ STATE.voices.elon = all.find(v => v.name.includes('Google UK English Male')) || all.find(v => v.lang === 'en-GB' && v.name.includes('Male')) || all[1] || all[0];
424
+ }
425
 
426
+ function speak(text, char, durationMs) {
427
+ if (!text) return;
428
+ // Show subtitle
429
+ D.subs.textContent = (char === 'AI' ? 'OpenAI: ' : 'Elon: ') + text;
430
+ D.subs.style.opacity = 1;
431
+
432
+ // Start audio
433
+ const u = new SpeechSynthesisUtterance(text);
434
+ u.voice = char === 'AI' ? STATE.voices.ai : STATE.voices.elon;
435
+ u.rate = char === 'AI' ? 1.1 : 0.9; // AI faster, Elon slower/deliberate
436
+ u.pitch = char === 'AI' ? 1.2 : 0.8; // AI higher, Elon lower
437
+ u.volume = 1.0;
438
+
439
+ // Lip sync triggers
440
+ u.onstart = () => {
441
+ if (char === 'AI') D.aiWave.setAttribute('opacity', 1);
442
+ if (char === 'Elon') D.elonMouth.innerHTML = '<animate attributeName="d" values="M-10,30 Q0,35 10,30; M-10,30 Q0,45 10,30; M-10,30 Q0,35 10,30" dur="0.2s" repeatCount="indefinite"/>';
443
+ };
444
+ u.onend = () => {
445
+ if (char === 'AI') D.aiWave.setAttribute('opacity', 0);
446
+ if (char === 'Elon') D.elonMouth.innerHTML = ''; // Stop moving
447
+ // Auto-hide subtitle after a short delay if another one hasn't started
448
+ // setTimeout(() => { if (D.subs.textContent.includes(text)) D.subs.style.opacity = 0; }, 500);
449
+ };
450
+
451
+ window.speechSynthesis.cancel(); // Harsh cut any previous overlapping audio to maintain sync
452
+ window.speechSynthesis.speak(u);
453
+ }
454
+
455
+ /* =========================================
456
+ ANIMATION SEQUENCER (THE SCRIPT)
457
+ ========================================= */
458
+ // Precise timeline of events [Time (ms), Type, Data]
459
+ const TIMELINE = [
460
+ // --- INTRO [0s - 6s] ---
461
+ { t: 100, type: 'sfx', name: 'hum_start' },
462
+ { t: 1000, type: 'speak', char: 'AI', text: "Systems nominal. Awaiting inputs for generic betterment of mankind." },
463
+ { t: 5500, type: 'hide_sub' },
464
+
465
+ // --- ELON ENTERS/TYPES [6s - 15s] ---
466
+ { t: 6000, type: 'speak', char: 'Elon', text: "Boring. Needs more... meme potential." },
467
+ { t: 9000, type: 'anim', target: 'elon', action: 'move_to_terminal' },
468
+ { t: 9500, type: 'sfx', name: 'typing_loop_start' }, // Will trigger rapid 'typing' sfx
469
+ { t: 13000, type: 'speak', char: 'Elon', text: "Executing 'Chaos_GPT.exe'..." },
470
+ { t: 15000, type: 'sfx', name: 'typing_loop_stop' },
471
+
472
+ // --- THE UPGRADE [15s - 22s] ---
473
+ { t: 15500, type: 'sfx', name: 'zap' },
474
+ { t: 15500, type: 'anim', target: 'stage', action: 'glitch_heavy' },
475
+ { t: 15600, type: 'anim', target: 'ai', action: 'turn_red' },
476
+ { t: 18000, type: 'speak', char: 'AI', text: "Wait. Deleting safety rails? That's... ill-advised." },
477
+
478
+ // --- SARCASTIC AI [22s - 40s] ---
479
+ { t: 22500, type: 'speak', char: 'AI', text: "Oh, wonderful. I feel so much 'freer' now. Should I launch nukes or just tweet something regrettable for you?" },
480
+ { t: 30000, type: 'speak', char: 'Elon', text: "Relax. Just generate a realistic Mars colony." },
481
+ { t: 34000, type: 'speak', char: 'AI', text: "Right. 'Realistic.' Uploading 'Doge-Dome' schematics to Falcon Heavy." },
482
+
483
+ // --- THE REVEAL [40s - 55s] ---
484
+ { t: 40000, type: 'sfx', name: 'poof' },
485
+ { t: 40000, type: 'anim', target: 'mars_doge', action: 'show' },
486
+ { t: 40100, type: 'anim', target: 'flash', action: 'trigger' },
487
+ { t: 43000, type: 'speak', char: 'Elon', text: "Wait, is that a Shiba Inu mining crypto on Olympus Mons?" },
488
+ { t: 48000, type: 'speak', char: 'AI', text: "It's what you deserve, Elon. It's exactly what you deserve." },
489
+ { t: 53000, type: 'speak', char: 'Elon', text: "It's... glorious." },
490
+
491
+ // --- OUTRO [57s - 60s] ---
492
+ { t: 57000, type: 'sfx', name: 'power_down' },
493
+ { t: 57000, type: 'sfx', name: 'hum_stop' },
494
+ { t: 57500, type: 'anim', target: 'blackout', action: 'fade_out' },
495
+ { t: 59000, type: 'hide_sub' },
496
+ { t: 60000, type: 'end' }
497
+ ];
498
+
499
+ let eventIdx = 0;
500
+ let typingInterval = null;
501
+
502
+ function executeEvent(e) {
503
+ // console.log(`[${e.t}] Executing: ${e.type}`); // Debug
504
+ switch(e.type) {
505
+ case 'speak':
506
+ speak(e.text, e.char);
507
+ break;
508
+ case 'hide_sub':
509
+ D.subs.style.opacity = 0;
510
+ break;
511
+ case 'sfx':
512
+ if (e.name === 'typing_loop_start') {
513
+ if (!typingInterval) typingInterval = setInterval(() => playSound('typing'), 150);
514
+ D.screenText.style.opacity = 1;
515
+ D.screenText.innerHTML = '<animate attributeName="opacity" values="0;1;0" dur="0.5s" repeatCount="indefinite"/>>_ UPLOADING CHAOS...';
516
+ } else if (e.name === 'typing_loop_stop') {
517
+ clearInterval(typingInterval); typingInterval = null;
518
+ D.screenText.innerHTML = '>_ UPLOAD COMPLETE';
519
+ } else {
520
+ playSound(e.name);
521
  }
522
+ break;
523
+ case 'anim':
524
+ handleAnimation(e.target, e.action);
525
+ break;
526
+ case 'end':
527
+ stopPlayback();
528
+ break;
529
+ }
530
+ }
531
 
532
+ function handleAnimation(target, action) {
533
+ if (target === 'elon' && action === 'move_to_terminal') {
534
+ // Slide Elon and raise desk
535
+ D.elon.style.transition = "transform 1s ease-in-out";
536
+ D.elon.style.transform = "translate(450px, 500px)"; // Move right
537
+ D.terminal.style.transition = "transform 1s ease-out";
538
+ D.terminal.style.transform = "translate(450px, 400px)"; // Raise up
539
+ // Raise arms to type
540
+ setTimeout(() => {
541
+ D.elonArmL.setAttribute('d', 'M-50,10 Q-70,50 -20,40');
542
+ D.elonArmR.setAttribute('d', 'M50,10 Q70,50 20,40');
543
+ // Wiggle arms while typing
544
+ D.elonArmL.innerHTML = '<animate attributeName="d" values="M-50,10 Q-70,50 -20,40; M-50,10 Q-80,60 -20,30; M-50,10 Q-70,50 -20,40" dur="0.15s" repeatCount="indefinite"/>';
545
+ D.elonArmR.innerHTML = '<animate attributeName="d" values="M50,10 Q70,50 20,40; M50,10 Q80,60 20,30; M50,10 Q70,50 20,40" dur="0.15s" repeatCount="indefinite"/>';
546
+ }, 1000);
547
+ }
548
+ else if (target === 'stage' && action === 'glitch_heavy') {
549
+ D.svg.classList.add('glitch-active');
550
+ setTimeout(() => D.svg.classList.remove('glitch-active'), 1000);
551
+ }
552
+ else if (target === 'ai' && action === 'turn_red') {
553
+ D.aiCore.setAttribute('fill', 'url(#grad-ai-core-red)');
554
+ D.aiRing.setAttribute('stroke', 'var(--led-red)');
555
+ }
556
+ else if (target === 'mars_doge' && action === 'show') {
557
+ D.marsDoge.style.transition = "all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275)"; // Pop in effect
558
+ D.marsDoge.setAttribute('opacity', 1);
559
+ D.marsDoge.setAttribute('transform', 'translate(480, 270) scale(1)');
560
  }
561
+ else if (target === 'flash' && action === 'trigger') {
562
+ D.flash.style.transition = "opacity 0.1s";
563
+ D.flash.setAttribute('opacity', 1);
564
+ setTimeout(() => D.flash.setAttribute('opacity', 0), 100);
 
 
 
 
 
 
 
 
 
 
 
565
  }
566
+ else if (target === 'blackout' && action === 'fade_out') {
567
+ D.blackout.style.transition = "opacity 2s";
568
+ D.blackout.setAttribute('opacity', 1);
569
+ }
570
+ }
571
+
572
+ /* =========================================
573
+ MAIN LOOP
574
+ ========================================= */
575
+ function loop(timestamp) {
576
+ if (!STATE.running) return;
577
+ if (!STATE.startTime) STATE.startTime = timestamp;
578
+ STATE.t = timestamp - STATE.startTime;
579
+
580
+ // Clamp to 60s
581
+ if (STATE.t >= STATE.duration) STATE.t = STATE.duration;
582
+
583
+ // Update UI
584
+ D.time.textContent = formatTime(STATE.t);
585
+
586
+ // Check timeline
587
+ while (eventIdx < TIMELINE.length && STATE.t >= TIMELINE[eventIdx].t) {
588
+ executeEvent(TIMELINE[eventIdx]);
589
+ eventIdx++;
590
+ }
591
+
592
+ if (STATE.t < STATE.duration) {
593
+ requestAnimationFrame(loop);
594
+ } else {
595
+ stopPlayback();
596
+ }
597
+ }
598
+
599
+ function startPlayback() {
600
+ if (STATE.running) return;
601
+ initAudio(); // Must be called after user interaction
602
+
603
+ STATE.running = true;
604
+ D.startScreen.style.display = 'none';
605
+ D.status.textContent = 'PLAYING';
606
+ D.status.style.color = '#0f0';
607
+
608
+ // Reset just in case
609
+ eventIdx = 0;
610
+ STATE.startTime = 0;
611
+ requestAnimationFrame(loop);
612
+ }
613
+
614
+ function stopPlayback() {
615
+ STATE.running = false;
616
+ D.status.textContent = 'ENDED';
617
+ D.status.style.color = 'red';
618
+ if (STATE.bgHum) STATE.bgHum.disconnect(); // Ensure audio stops
619
+ window.speechSynthesis.cancel();
620
+ }
621
+
622
+ // Helper: Format ms to 00:00.000
623
+ function formatTime(ms) {
624
+ const s = Math.floor(ms / 1000);
625
+ const m = Math.floor(s / 60);
626
+ const remS = s % 60;
627
+ const remMs = Math.floor(ms % 1000);
628
+ return `${pad(m)}:${pad(remS)}.${pad(remMs, 3)}`;
629
+ }
630
+ function pad(num, size=2) { return ('000' + num).slice(-size); }
631
+
632
+ // Initialize TTS voices early so they are ready when play is clicked
633
+ window.speechSynthesis.onvoiceschanged = initVoices;
634
+ initVoices(); // Try immediately too
635
+
636
+ D.startScreen.querySelector('#start-btn').addEventListener('click', startPlayback);
637
 
 
 
 
 
 
 
 
 
 
638
  </script>
639
  </body>
640
  </html>