GuglielmoTor commited on
Commit
bb15901
·
verified ·
1 Parent(s): 0c06300

Update sync_animation.html

Browse files
Files changed (1) hide show
  1. sync_animation.html +240 -135
sync_animation.html CHANGED
@@ -6,40 +6,44 @@
6
  <title>CMD Ingaze Reveal Animation</title>
7
  <style>
8
  /* Styles for the animation when embedded */
9
- html, body { /* Apply to html as well for full height */
10
  background-color: #000000; /* Black background for the animation area */
11
  color: #00FF00; /* Bright green text */
12
  font-family: 'Courier New', Courier, monospace; /* Classic CMD font */
13
- margin: 0;
14
- padding: 0;
15
- overflow: hidden;
16
- height: 100%; /* Make body fill the iframe height */
17
- display: flex;
18
- justify-content: center;
19
- align-items: center;
20
  }
21
 
22
  #cmd-animation-container {
23
- padding: 10px; /* Adjusted padding */
24
- border: 2px solid #00AA00;
25
- background-color: #080808;
26
- box-shadow: 0 0 10px #00FF00;
27
- border-radius: 8px;
28
- box-sizing: border-box;
29
- max-width: calc(100% - 20px); /* Max width with some margin */
30
- margin: 10px; /* Margin around container */
31
- /* Height will be determined by content or flex centering */
32
  }
33
 
34
  pre#cmd-animation {
35
- white-space: pre;
36
- text-align: left;
37
- font-size: 13px; /* Slightly increased font size */
38
- line-height: 1.2; /* Adjusted line height */
39
- margin: 0;
40
- overflow-x: auto;
41
- color: #00FF00; /* Explicitly set text color here too */
42
- background-color: #080808; /* Ensure pre background matches container */
 
 
 
 
43
  }
44
  </style>
45
  </head>
@@ -49,29 +53,30 @@
49
  </div>
50
 
51
  <script>
52
- console.log("SYNC ANIMATION: Script execution started."); // LOG 1
 
53
 
54
  // --- Configuration ---
55
- const scene_width = 65;
56
- const scene_height = 22;
57
- const frameDuration = 150;
58
- const MAX_DATA_PARTICLES = 100;
59
- const FLOOR_LEVEL = scene_height - 1;
60
 
61
  // --- ASCII Art Definitions ---
62
  const stick_man_art_normal = [" O ", " /|\\ ", " / \\ "];
63
  const stick_man_art_walk_1 = [" O ", " /|\\ ", " / > "];
64
  const stick_man_art_walk_2 = [" O ", " /|\\ ", " < \\ "];
65
  const stick_man_art_surprise = [" \\O/ ", " | ", " / \\ "];
66
- const stick_man_art_gone = [" ", " ", " "];
67
- const linked_in_art = ["+----+", "|i n |", "+----+"];
68
- const linked_in_art_hit = ["x----x", "|BOOM|", "x----x"];
69
- const lightning_strike_segment = "\\";
70
- const explosion_art_frames = [
71
  [" * ", " *#* ", " * "],[" @!% ", " @*#*& ", " X%O "],
72
  [" $*~+@ ", "$#@!%*&"," X*O#~ ", " !+%$ "],[" ~ ", " +*% ", " $ "]
73
  ];
74
- const ingaze_logo_art = [
75
  "IIIII N N GGG AAA ZZZZZ EEEEE", " I NN N G A A Z E ",
76
  " I N N N G GG AAAAA Z EEE ", " I N NN G G A A Z E ",
77
  "IIIII N N GGG A A ZZZZZ EEEEE"
@@ -79,18 +84,27 @@
79
  const ingaze_logo_width = ingaze_logo_art[0].length;
80
  const ingaze_logo_height = ingaze_logo_art.length;
81
 
82
- let data_particles_state = [];
83
- let current_lightning_y_state = -1;
 
84
 
 
 
 
 
 
 
 
 
85
  function overlay(baseLines, artLines, startX, startY, ignoreSpaces = true) {
86
  artLines.forEach((artLine, i) => {
87
  const y = startY + i;
88
- if (y >= 0 && y < baseLines.length && artLine) {
89
  let baseLineArr = baseLines[y].split('');
90
  for (let j = 0; j < artLine.length; j++) {
91
  const x = startX + j;
92
- if (x >= 0 && x < baseLineArr.length) {
93
- if (ignoreSpaces && artLine[j] === ' ') continue;
94
  baseLineArr[x] = artLine[j];
95
  }
96
  }
@@ -99,13 +113,20 @@
99
  });
100
  }
101
 
 
 
 
 
 
 
 
102
  function drawLightningBoltInScene(sceneLines, tipX, tipY, length) {
103
  for (let i = 0; i < length; i++) {
104
- const y = tipY - i;
105
- const x = tipX + (i % 2 === 0 ? 0 : (i % 4 === 1 ? -1 : 1));
106
  if (y >= 0 && y < scene_height && x >= 0 && x < scene_width) {
107
- if (sceneLines[y][x] === ' ') {
108
- let lineArr = sceneLines[y].split('');
109
  lineArr[x] = lightning_strike_segment;
110
  sceneLines[y] = lineArr.join('');
111
  }
@@ -113,198 +134,282 @@
113
  }
114
  }
115
 
 
 
 
 
116
  function buildSceneFrame(stickmanArt, stickmanPos, showLogo, currentLogoArt, currentLogoPos,
117
- showLightningBolt, lightningTipX, lightningTipY, lightningLength,
118
- currentExplosionArt, explosionPos,
119
- showIngazeLogo, ingazeArt, ingazePos) {
 
120
  let sceneLines = Array(scene_height).fill(" ".repeat(scene_width));
 
 
121
  data_particles_state.forEach(p => {
122
- const px = Math.round(p.x); const py = Math.round(p.y);
 
123
  if (py >= 0 && py < sceneLines.length && px >= 0 && px < scene_width) {
124
- if (sceneLines[py][px] === ' ') {
125
- let lineArr = sceneLines[py].split(''); lineArr[px] = p.char; sceneLines[py] = lineArr.join('');
 
 
126
  }
127
  }
128
  });
 
 
129
  overlay(sceneLines, stickmanArt, stickmanPos.x, stickmanPos.y);
130
  if (showLogo) overlay(sceneLines, currentLogoArt, currentLogoPos.x, currentLogoPos.y);
131
  if (showLightningBolt && lightningTipY >=0) {
132
  drawLightningBoltInScene(sceneLines, lightningTipX, lightningTipY, lightningLength);
133
  }
134
  if (currentExplosionArt && currentExplosionArt.length > 0) {
135
- overlay(sceneLines, currentExplosionArt, explosionPos.x, explosionPos.y, false);
136
  }
137
  if (showIngazeLogo) overlay(sceneLines, ingazeArt, ingazePos.x, ingazePos.y);
138
- return sceneLines.join('\n');
 
139
  }
140
 
 
 
 
141
  function spawnNewDataParticle(logoX, logoY, logoWidth) {
142
- if (data_particles_state.length >= MAX_DATA_PARTICLES) return;
143
  const char = Math.random() > 0.5 ? '1' : '0';
144
- const x = logoX + 1 + Math.floor(Math.random() * (logoWidth - 2));
145
- const y = logoY + linked_in_art.length;
146
- const vy = 0.2 + Math.random() * 0.3;
147
  data_particles_state.push({ char, x, y, vy, active: true });
148
  }
149
 
 
 
 
 
150
  function generateAnimationFrames() {
151
- console.log("SYNC ANIMATION: generateAnimationFrames() called."); // LOG
152
  const sequence = [];
153
- data_particles_state = [];
154
- current_lightning_y_state = -1;
 
 
155
  const stickman_height = stick_man_art_normal.length;
156
  const stickman_width = stick_man_art_normal[0].length;
157
  const logo_width = linked_in_art[0].length;
158
  const logo_height = linked_in_art.length;
 
159
  const stickman_base_y = FLOOR_LEVEL - stickman_height;
160
- const logo_base_y = stickman_base_y;
 
161
  const stickman_initial_pos = { x: 2, y: stickman_base_y };
162
- const logo_initial_pos = { x: scene_width - logo_width - 15, y: logo_base_y };
163
  const stickman_walk_target_x = Math.max(stickman_initial_pos.x + 1, logo_initial_pos.x - stickman_width - 1);
164
- const ingaze_pos = { x: Math.floor((scene_width - ingaze_logo_width) / 2), y: Math.floor((scene_height - ingaze_logo_height) / 2)};
 
 
 
 
165
  const explosion_center_x = logo_initial_pos.x + Math.floor(logo_width / 2);
166
  const explosion_center_y = logo_initial_pos.y + Math.floor(logo_height / 2);
167
 
168
- // Phase 0: Stickman Walks
169
- const walk_frames = Math.max(10, Math.floor(Math.abs(stickman_walk_target_x - stickman_initial_pos.x) / 1.0) );
170
  let current_stickman_x = stickman_initial_pos.x;
171
- const walk_step_x = walk_frames > 0 ? (stickman_walk_target_x - stickman_initial_pos.x) / walk_frames : 0;
172
- for (let i = 0; i < walk_frames; i++) {
 
173
  current_stickman_x += walk_step_x;
174
- const walk_art = (i % 4 < 2) ? stick_man_art_walk_1 : stick_man_art_walk_2;
175
  sequence.push(buildSceneFrame(walk_art, { x: Math.round(current_stickman_x), y: stickman_base_y }, true, linked_in_art, logo_initial_pos, false,0,0,0, [], {}, false, [], {}));
176
  }
177
- current_stickman_x = stickman_walk_target_x;
178
  let current_stickman_pos = { x: Math.round(current_stickman_x), y: stickman_base_y };
179
 
180
- // Phase 1: Pause
181
- for (let i = 0; i < 3; i++) { sequence.push(buildSceneFrame(stick_man_art_normal, current_stickman_pos, true, linked_in_art, logo_initial_pos, false,0,0,0, [], {}, false, [], {})); }
 
 
182
 
183
- // Phase 2: Shaking & Data Fall
184
  let current_logo_pos_phase2 = { ...logo_initial_pos };
185
- const shake_frames = 20;
186
- for (let i = 0; i < shake_frames; i++) {
187
- let logoAdj = { x: 0, y: 0 }; if (i % 6 < 2) logoAdj = { x: 1, y: 0 }; else if (i % 6 < 4) logoAdj = { x: -1, y: 0 };
188
- current_logo_pos_phase2.x = logo_initial_pos.x + logoAdj.x;
189
- if (i % 3 === 0) spawnNewDataParticle(current_logo_pos_phase2.x, current_logo_pos_phase2.y, logo_width);
190
- data_particles_state.forEach(p => { if (p.active) { p.y += p.vy; if (p.y >= FLOOR_LEVEL) { p.y = FLOOR_LEVEL; p.vy = 0; p.active = false; } } });
 
 
 
 
 
 
 
 
191
  sequence.push(buildSceneFrame(stick_man_art_normal, current_stickman_pos, true, linked_in_art, current_logo_pos_phase2, false,0,0,0, [], {}, false, [], {}));
192
  }
193
- current_logo_pos_phase2 = { ...logo_initial_pos };
194
 
195
  // Phase 3: Data Settles, Stickman Surprised
196
- const settle_frames = 8;
197
- for (let i = 0; i < settle_frames; i++) {
198
- let stickmanArtPhase3 = (i < settle_frames / 2) ? stick_man_art_normal : stick_man_art_surprise;
199
- data_particles_state.forEach(p => { if (p.active) { p.y += p.vy; if (p.y >= FLOOR_LEVEL) { p.y = FLOOR_LEVEL; p.vy = 0; p.active = false; } } });
 
 
 
 
 
200
  sequence.push(buildSceneFrame(stickmanArtPhase3, current_stickman_pos, true, linked_in_art, logo_initial_pos, false,0,0,0, [], {}, false, [], {}));
201
  }
202
 
203
- // Phase 4: Lightning Strike
204
- const lightning_target_y = logo_initial_pos.y; const lightning_strike_frames = lightning_target_y + 2;
205
- const lightning_bolt_length = 4; const lightning_tip_x_strike = logo_initial_pos.x + Math.floor(logo_width / 2);
206
- for (let i = 0; i < lightning_strike_frames; i++) {
207
- current_lightning_y_state = i; let logo_art_to_use = linked_in_art;
208
- if (current_lightning_y_state >= lightning_target_y) { logo_art_to_use = linked_in_art_hit; current_lightning_y_state = lightning_target_y; }
 
 
 
 
 
 
 
209
  sequence.push(buildSceneFrame(stick_man_art_surprise, current_stickman_pos, true, logo_art_to_use, logo_initial_pos, true, lightning_tip_x_strike, current_lightning_y_state, lightning_bolt_length, [], {}, false, [], {}));
210
  }
211
- current_lightning_y_state = -1;
212
 
213
- // Phase 5: Explosion
214
- const explosion_duration_per_frame = 2; let stickman_art_explosion = stick_man_art_surprise;
 
215
  for (let i = 0; i < explosion_art_frames.length; i++) {
216
  const explosion_art = explosion_art_frames[i];
217
- const explosion_art_width = Math.max(...explosion_art.map(l => l.length)); const explosion_art_height = explosion_art.length;
218
- const current_explosion_pos = { x: explosion_center_x - Math.floor(explosion_art_width / 2), y: explosion_center_y - Math.floor(explosion_art_height / 2) };
 
 
 
 
219
  for (let j = 0; j < explosion_duration_per_frame; j++) {
220
- if (i === 0 && j === 0) { data_particles_state = []; stickman_art_explosion = stick_man_art_gone; }
 
 
 
221
  sequence.push(buildSceneFrame(stickman_art_explosion, current_stickman_pos, false, [], {}, false,0,0,0, explosion_art, current_explosion_pos, false, [], {}));
222
  }
223
  }
224
 
225
  // Phase 6: "Ingaze" Logo Reveal
226
- const ingaze_reveal_pause_frames = 4;
227
- for(let i=0; i < ingaze_reveal_pause_frames; i++) { sequence.push(buildSceneFrame(stick_man_art_gone, current_stickman_pos, false, [], {}, false,0,0,0, [], {}, false, [], {}));}
228
- const ingaze_display_frames = 25;
229
- for (let i = 0; i < ingaze_display_frames; i++) { sequence.push(buildSceneFrame(stick_man_art_gone, current_stickman_pos, false, [], {}, false,0,0,0, [], {}, true, ingaze_logo_art, ingaze_pos));}
230
-
231
- console.log(`SYNC ANIMATION: Generated ${sequence.length} frames.`); // LOG
 
 
 
 
232
  return sequence;
233
  }
234
 
 
235
  const animationElement = document.getElementById('cmd-animation');
236
  let currentFrameIndex = 0;
237
  let animationIntervalId;
238
- let allAnimationFrames = [];
239
 
 
 
 
240
  function runAnimation() {
241
- console.log("SYNC ANIMATION: runAnimation() called."); // LOG
242
  if (!animationElement) {
243
- console.error("SYNC ANIMATION ERROR: Animation element 'cmd-animation' not found in runAnimation.");
244
  return;
245
  }
246
  if (allAnimationFrames.length === 0) {
247
- console.error("SYNC ANIMATION ERROR: Animation frames are empty. Cannot play animation.");
248
- animationElement.textContent = "Error: No frames.";
249
  return;
250
  }
251
 
 
 
 
 
 
252
  animationIntervalId = setInterval(() => {
253
  if (allAnimationFrames[currentFrameIndex]) {
254
  animationElement.textContent = allAnimationFrames[currentFrameIndex];
255
  }
256
  currentFrameIndex++;
257
  if (currentFrameIndex >= allAnimationFrames.length) {
258
- currentFrameIndex = 0; // Loop animation
259
  }
260
  }, frameDuration);
261
- console.log("SYNC ANIMATION: Animation interval started."); // LOG
262
  }
263
-
 
 
 
264
  function initializeAnimation() {
265
- console.log("SYNC ANIMATION: initializeAnimation() called."); // LOG
266
  if (!animationElement) {
267
- console.warn("SYNC ANIMATION WARNING: Animation element not ready yet in initializeAnimation.");
268
- // It might be too early, try again shortly
269
- // setTimeout(initializeAnimation, 100); // Be careful with recursive setTimeout
270
- return;
 
271
  }
 
 
272
  try {
273
- console.log("SYNC ANIMATION: Attempting to generate frames..."); // LOG
274
- allAnimationFrames = generateAnimationFrames();
275
- if (allAnimationFrames.length > 0 && animationElement) {
276
- animationElement.textContent = allAnimationFrames[0];
277
- console.log("SYNC ANIMATION: First frame set. Calling runAnimation()."); // LOG
278
- runAnimation();
279
- } else if (animationElement) {
280
  animationElement.textContent = "Error: Could not generate animation frames.";
281
- console.error("SYNC ANIMATION ERROR: Could not generate frames or animationElement is null."); // LOG
282
  }
283
  } catch (error) {
284
- console.error("SYNC ANIMATION ERROR: Error during animation generation or initial play:", error); // LOG
285
- if(animationElement) animationElement.textContent = "Error initializing: " + error.message;
286
  }
287
  }
288
-
289
- console.log("SYNC ANIMATION: Adding DOMContentLoaded listener."); // LOG
290
- if (document.readyState === "complete" || document.readyState === "interactive") {
291
- console.log("SYNC ANIMATION: DOM already loaded or interactive. Calling initializeAnimation via setTimeout."); // LOG
292
- setTimeout(initializeAnimation, 0);
293
- } else {
294
- console.log("SYNC ANIMATION: DOM not yet loaded. Adding event listener for DOMContentLoaded."); // LOG
295
  document.addEventListener('DOMContentLoaded', () => {
296
- console.log("SYNC ANIMATION: DOMContentLoaded event fired."); // LOG
297
  initializeAnimation();
298
  });
 
 
 
 
299
  }
300
 
 
301
  window.addEventListener('unload', () => {
302
- console.log("SYNC ANIMATION: Unload event. Clearing interval."); // LOG
303
  if (animationIntervalId) {
304
  clearInterval(animationIntervalId);
305
  }
306
  });
307
- console.log("SYNC ANIMATION: Script execution finished."); // LOG
 
 
308
  </script>
309
  </body>
310
  </html>
 
6
  <title>CMD Ingaze Reveal Animation</title>
7
  <style>
8
  /* Styles for the animation when embedded */
9
+ body { /* Removed html selector here, apply to body */
10
  background-color: #000000; /* Black background for the animation area */
11
  color: #00FF00; /* Bright green text */
12
  font-family: 'Courier New', Courier, monospace; /* Classic CMD font */
13
+ margin: 0;
14
+ padding: 10px; /* Add some padding to the body itself for spacing */
15
+ /* overflow: hidden; Removed, let content dictate size */
16
+ /* height: 100%; Removed, problematic in iframes */
17
+ /* display: flex; Removed */
18
+ /* justify-content: center; Removed */
19
+ /* align-items: center; Removed */
20
  }
21
 
22
  #cmd-animation-container {
23
+ padding: 15px; /* Increased padding for better visual separation */
24
+ border: 2px solid #00AA00;
25
+ background-color: #080808; /* Dark background for the container */
26
+ box-shadow: 0 0 10px #00FF00;
27
+ border-radius: 8px;
28
+ box-sizing: border-box;
29
+ /* max-width: calc(100% - 20px); /* Retain max-width for responsiveness */
30
+ /* margin: 10px; /* Body padding handles spacing now */
31
+ text-align: center; /* Center the pre block if it's narrower */
32
  }
33
 
34
  pre#cmd-animation {
35
+ white-space: pre;
36
+ text-align: left; /* Text within pre should be left-aligned */
37
+ font-size: 13px;
38
+ line-height: 1.2;
39
+ margin: 0 auto; /* Center pre block if container is wider */
40
+ overflow-x: auto; /* Allow horizontal scroll if content is too wide */
41
+ color: #00FF00;
42
+ background-color: #080808; /* Match container background */
43
+ display: inline-block; /* Allow margin auto to work for centering */
44
+ min-width: 100px; /* Ensure it's not collapsed */
45
+ min-height: 50px; /* Ensure it's not collapsed */
46
+ /* The height will be determined by scene_height * line-height */
47
  }
48
  </style>
49
  </head>
 
53
  </div>
54
 
55
  <script>
56
+ // --- Start of Script ---
57
+ console.log("SYNC ANIMATION (Updated HTML): Script execution started.");
58
 
59
  // --- Configuration ---
60
+ const scene_width = 65; // Width of the animation scene in characters
61
+ const scene_height = 22; // Height of the animation scene in lines
62
+ const frameDuration = 150; // Duration of each frame in milliseconds
63
+ const MAX_DATA_PARTICLES = 100; // Maximum number of data particles
64
+ const FLOOR_LEVEL = scene_height - 1; // Y-coordinate of the floor
65
 
66
  // --- ASCII Art Definitions ---
67
  const stick_man_art_normal = [" O ", " /|\\ ", " / \\ "];
68
  const stick_man_art_walk_1 = [" O ", " /|\\ ", " / > "];
69
  const stick_man_art_walk_2 = [" O ", " /|\\ ", " < \\ "];
70
  const stick_man_art_surprise = [" \\O/ ", " | ", " / \\ "];
71
+ const stick_man_art_gone = [" ", " ", " "]; // Art for when stickman disappears
72
+ const linked_in_art = ["+----+", "|i n |", "+----+"]; // LinkedIn logo
73
+ const linked_in_art_hit = ["x----x", "|BOOM|", "x----x"]; // LinkedIn logo when hit
74
+ const lightning_strike_segment = "\\"; // Character for lightning
75
+ const explosion_art_frames = [ // Frames for explosion animation
76
  [" * ", " *#* ", " * "],[" @!% ", " @*#*& ", " X%O "],
77
  [" $*~+@ ", "$#@!%*&"," X*O#~ ", " !+%$ "],[" ~ ", " +*% ", " $ "]
78
  ];
79
+ const ingaze_logo_art = [ // ASCII art for "INGAZE" logo
80
  "IIIII N N GGG AAA ZZZZZ EEEEE", " I NN N G A A Z E ",
81
  " I N N N G GG AAAAA Z EEE ", " I N NN G G A A Z E ",
82
  "IIIII N N GGG A A ZZZZZ EEEEE"
 
84
  const ingaze_logo_width = ingaze_logo_art[0].length;
85
  const ingaze_logo_height = ingaze_logo_art.length;
86
 
87
+ // --- State Variables ---
88
+ let data_particles_state = []; // Array to hold current data particles
89
+ let current_lightning_y_state = -1; // Current Y position of lightning tip
90
 
91
+ /**
92
+ * Overlays ASCII art onto a base scene (array of strings).
93
+ * @param {string[]} baseLines - The base scene lines.
94
+ * @param {string[]} artLines - The ASCII art lines to overlay.
95
+ * @param {number} startX - The starting X coordinate for the art.
96
+ * @param {number} startY - The starting Y coordinate for the art.
97
+ * @param {boolean} [ignoreSpaces=true] - Whether to ignore spaces in the art.
98
+ */
99
  function overlay(baseLines, artLines, startX, startY, ignoreSpaces = true) {
100
  artLines.forEach((artLine, i) => {
101
  const y = startY + i;
102
+ if (y >= 0 && y < baseLines.length && artLine) { // Check if art line is within base scene bounds
103
  let baseLineArr = baseLines[y].split('');
104
  for (let j = 0; j < artLine.length; j++) {
105
  const x = startX + j;
106
+ if (x >= 0 && x < baseLineArr.length) { // Check if art character is within line bounds
107
+ if (ignoreSpaces && artLine[j] === ' ') continue; // Skip spaces if specified
108
  baseLineArr[x] = artLine[j];
109
  }
110
  }
 
113
  });
114
  }
115
 
116
+ /**
117
+ * Draws a lightning bolt segment in the scene.
118
+ * @param {string[]} sceneLines - The current scene lines.
119
+ * @param {number} tipX - The X coordinate of the lightning tip.
120
+ * @param {number} tipY - The Y coordinate of the lightning tip.
121
+ * @param {number} length - The length of the lightning bolt.
122
+ */
123
  function drawLightningBoltInScene(sceneLines, tipX, tipY, length) {
124
  for (let i = 0; i < length; i++) {
125
+ const y = tipY - i; // Lightning travels upwards from tip
126
+ const x = tipX + (i % 2 === 0 ? 0 : (i % 4 === 1 ? -1 : 1)); // Jagged path
127
  if (y >= 0 && y < scene_height && x >= 0 && x < scene_width) {
128
+ let lineArr = sceneLines[y].split('');
129
+ if (x < lineArr.length && lineArr[x] === ' ') { // Check bounds and if space is empty
130
  lineArr[x] = lightning_strike_segment;
131
  sceneLines[y] = lineArr.join('');
132
  }
 
134
  }
135
  }
136
 
137
+ /**
138
+ * Builds a single frame of the animation.
139
+ * @returns {string} The complete animation frame as a multi-line string.
140
+ */
141
  function buildSceneFrame(stickmanArt, stickmanPos, showLogo, currentLogoArt, currentLogoPos,
142
+ showLightningBolt, lightningTipX, lightningTipY, lightningLength,
143
+ currentExplosionArt, explosionPos,
144
+ showIngazeLogo, ingazeArt, ingazePos) {
145
+ // Initialize scene with empty lines
146
  let sceneLines = Array(scene_height).fill(" ".repeat(scene_width));
147
+
148
+ // Draw data particles
149
  data_particles_state.forEach(p => {
150
+ const px = Math.round(p.x);
151
+ const py = Math.round(p.y);
152
  if (py >= 0 && py < sceneLines.length && px >= 0 && px < scene_width) {
153
+ let lineArr = sceneLines[py].split('');
154
+ if (px < lineArr.length && lineArr[px] === ' ') { // Check bounds and if space is empty
155
+ lineArr[px] = p.char;
156
+ sceneLines[py] = lineArr.join('');
157
  }
158
  }
159
  });
160
+
161
+ // Overlay elements onto the scene
162
  overlay(sceneLines, stickmanArt, stickmanPos.x, stickmanPos.y);
163
  if (showLogo) overlay(sceneLines, currentLogoArt, currentLogoPos.x, currentLogoPos.y);
164
  if (showLightningBolt && lightningTipY >=0) {
165
  drawLightningBoltInScene(sceneLines, lightningTipX, lightningTipY, lightningLength);
166
  }
167
  if (currentExplosionArt && currentExplosionArt.length > 0) {
168
+ overlay(sceneLines, currentExplosionArt, explosionPos.x, explosionPos.y, false); // Don't ignore spaces for explosion
169
  }
170
  if (showIngazeLogo) overlay(sceneLines, ingazeArt, ingazePos.x, ingazePos.y);
171
+
172
+ return sceneLines.join('\n'); // Join lines into a single string for display
173
  }
174
 
175
+ /**
176
+ * Spawns a new data particle (0 or 1) falling from the LinkedIn logo.
177
+ */
178
  function spawnNewDataParticle(logoX, logoY, logoWidth) {
179
+ if (data_particles_state.length >= MAX_DATA_PARTICLES) return; // Limit particle count
180
  const char = Math.random() > 0.5 ? '1' : '0';
181
+ const x = logoX + 1 + Math.floor(Math.random() * (logoWidth - 2)); // Random X within logo
182
+ const y = logoY + linked_in_art.length; // Start below logo
183
+ const vy = 0.2 + Math.random() * 0.3; // Random vertical speed
184
  data_particles_state.push({ char, x, y, vy, active: true });
185
  }
186
 
187
+ /**
188
+ * Generates all frames for the entire animation sequence.
189
+ * @returns {string[]} An array of animation frames.
190
+ */
191
  function generateAnimationFrames() {
192
+ console.log("SYNC ANIMATION (Updated HTML): generateAnimationFrames() called.");
193
  const sequence = [];
194
+ data_particles_state = []; // Reset particles for each generation
195
+ current_lightning_y_state = -1; // Reset lightning state
196
+
197
+ // Define dimensions and positions
198
  const stickman_height = stick_man_art_normal.length;
199
  const stickman_width = stick_man_art_normal[0].length;
200
  const logo_width = linked_in_art[0].length;
201
  const logo_height = linked_in_art.length;
202
+
203
  const stickman_base_y = FLOOR_LEVEL - stickman_height;
204
+ const logo_base_y = stickman_base_y; // Align logo with stickman
205
+
206
  const stickman_initial_pos = { x: 2, y: stickman_base_y };
207
+ const logo_initial_pos = { x: scene_width - logo_width - 15, y: logo_base_y };
208
  const stickman_walk_target_x = Math.max(stickman_initial_pos.x + 1, logo_initial_pos.x - stickman_width - 1);
209
+
210
+ const ingaze_pos = {
211
+ x: Math.floor((scene_width - ingaze_logo_width) / 2),
212
+ y: Math.floor((scene_height - ingaze_logo_height) / 2)
213
+ };
214
  const explosion_center_x = logo_initial_pos.x + Math.floor(logo_width / 2);
215
  const explosion_center_y = logo_initial_pos.y + Math.floor(logo_height / 2);
216
 
217
+ // Phase 0: Stickman Walks towards LinkedIn logo
218
+ const walk_frames_count = Math.max(10, Math.floor(Math.abs(stickman_walk_target_x - stickman_initial_pos.x) / 1.0));
219
  let current_stickman_x = stickman_initial_pos.x;
220
+ const walk_step_x = walk_frames_count > 0 ? (stickman_walk_target_x - stickman_initial_pos.x) / walk_frames_count : 0;
221
+
222
+ for (let i = 0; i < walk_frames_count; i++) {
223
  current_stickman_x += walk_step_x;
224
+ const walk_art = (i % 4 < 2) ? stick_man_art_walk_1 : stick_man_art_walk_2; // Alternate walking art
225
  sequence.push(buildSceneFrame(walk_art, { x: Math.round(current_stickman_x), y: stickman_base_y }, true, linked_in_art, logo_initial_pos, false,0,0,0, [], {}, false, [], {}));
226
  }
227
+ current_stickman_x = stickman_walk_target_x; // Ensure exact target position
228
  let current_stickman_pos = { x: Math.round(current_stickman_x), y: stickman_base_y };
229
 
230
+ // Phase 1: Pause before interaction
231
+ for (let i = 0; i < 3; i++) {
232
+ sequence.push(buildSceneFrame(stick_man_art_normal, current_stickman_pos, true, linked_in_art, logo_initial_pos, false,0,0,0, [], {}, false, [], {}));
233
+ }
234
 
235
+ // Phase 2: Shaking LinkedIn logo & Data Fall
236
  let current_logo_pos_phase2 = { ...logo_initial_pos };
237
+ const shake_frames_count = 20;
238
+ for (let i = 0; i < shake_frames_count; i++) {
239
+ let logo_shake_adj = { x: 0, y: 0 };
240
+ if (i % 6 < 2) logo_shake_adj = { x: 1, y: 0 }; else if (i % 6 < 4) logo_shake_adj = { x: -1, y: 0 }; // Shake horizontally
241
+ current_logo_pos_phase2.x = logo_initial_pos.x + logo_shake_adj.x;
242
+
243
+ if (i % 3 === 0) spawnNewDataParticle(current_logo_pos_phase2.x, current_logo_pos_phase2.y, logo_width); // Spawn particles periodically
244
+
245
+ data_particles_state.forEach(p => { // Update particle positions
246
+ if (p.active) {
247
+ p.y += p.vy;
248
+ if (p.y >= FLOOR_LEVEL) { p.y = FLOOR_LEVEL; p.vy = 0; p.active = false; } // Particle hits floor
249
+ }
250
+ });
251
  sequence.push(buildSceneFrame(stick_man_art_normal, current_stickman_pos, true, linked_in_art, current_logo_pos_phase2, false,0,0,0, [], {}, false, [], {}));
252
  }
253
+ current_logo_pos_phase2 = { ...logo_initial_pos }; // Reset logo position
254
 
255
  // Phase 3: Data Settles, Stickman Surprised
256
+ const settle_frames_count = 8;
257
+ for (let i = 0; i < settle_frames_count; i++) {
258
+ let stickmanArtPhase3 = (i < settle_frames_count / 2) ? stick_man_art_normal : stick_man_art_surprise; // Stickman gets surprised
259
+ data_particles_state.forEach(p => { // Continue settling particles
260
+ if (p.active) {
261
+ p.y += p.vy;
262
+ if (p.y >= FLOOR_LEVEL) { p.y = FLOOR_LEVEL; p.vy = 0; p.active = false; }
263
+ }
264
+ });
265
  sequence.push(buildSceneFrame(stickmanArtPhase3, current_stickman_pos, true, linked_in_art, logo_initial_pos, false,0,0,0, [], {}, false, [], {}));
266
  }
267
 
268
+ // Phase 4: Lightning Strike on LinkedIn logo
269
+ const lightning_target_y = logo_initial_pos.y;
270
+ const lightning_strike_frames_count = lightning_target_y + 2; // Frames for lightning to reach logo
271
+ const lightning_bolt_length = 4;
272
+ const lightning_tip_x_strike = logo_initial_pos.x + Math.floor(logo_width / 2); // Strike center of logo
273
+
274
+ for (let i = 0; i < lightning_strike_frames_count; i++) {
275
+ current_lightning_y_state = i;
276
+ let logo_art_to_use = linked_in_art;
277
+ if (current_lightning_y_state >= lightning_target_y) { // Lightning hits
278
+ logo_art_to_use = linked_in_art_hit;
279
+ current_lightning_y_state = lightning_target_y; // Keep lightning at target
280
+ }
281
  sequence.push(buildSceneFrame(stick_man_art_surprise, current_stickman_pos, true, logo_art_to_use, logo_initial_pos, true, lightning_tip_x_strike, current_lightning_y_state, lightning_bolt_length, [], {}, false, [], {}));
282
  }
283
+ current_lightning_y_state = -1; // Reset lightning
284
 
285
+ // Phase 5: Explosion of LinkedIn logo
286
+ const explosion_duration_per_frame = 2; // How many animation frames each explosion art frame lasts
287
+ let stickman_art_explosion = stick_man_art_surprise;
288
  for (let i = 0; i < explosion_art_frames.length; i++) {
289
  const explosion_art = explosion_art_frames[i];
290
+ const explosion_art_width = Math.max(...explosion_art.map(l => l.length));
291
+ const explosion_art_height = explosion_art.length;
292
+ const current_explosion_pos = {
293
+ x: explosion_center_x - Math.floor(explosion_art_width / 2),
294
+ y: explosion_center_y - Math.floor(explosion_art_height / 2)
295
+ };
296
  for (let j = 0; j < explosion_duration_per_frame; j++) {
297
+ if (i === 0 && j === 0) { // First frame of explosion
298
+ data_particles_state = []; // Clear data particles
299
+ stickman_art_explosion = stick_man_art_gone; // Stickman disappears
300
+ }
301
  sequence.push(buildSceneFrame(stickman_art_explosion, current_stickman_pos, false, [], {}, false,0,0,0, explosion_art, current_explosion_pos, false, [], {}));
302
  }
303
  }
304
 
305
  // Phase 6: "Ingaze" Logo Reveal
306
+ const ingaze_reveal_pause_frames = 4; // Pause after explosion
307
+ for(let i=0; i < ingaze_reveal_pause_frames; i++) {
308
+ sequence.push(buildSceneFrame(stick_man_art_gone, current_stickman_pos, false, [], {}, false,0,0,0, [], {}, false, [], {}));
309
+ }
310
+ const ingaze_display_frames = 25; // How long Ingaze logo stays
311
+ for (let i = 0; i < ingaze_display_frames; i++) {
312
+ sequence.push(buildSceneFrame(stick_man_art_gone, current_stickman_pos, false, [], {}, false,0,0,0, [], {}, true, ingaze_logo_art, ingaze_pos));
313
+ }
314
+
315
+ console.log(`SYNC ANIMATION (Updated HTML): Generated ${sequence.length} frames.`);
316
  return sequence;
317
  }
318
 
319
+ // --- Animation Playback ---
320
  const animationElement = document.getElementById('cmd-animation');
321
  let currentFrameIndex = 0;
322
  let animationIntervalId;
323
+ let allAnimationFrames = []; // To store all generated frames
324
 
325
+ /**
326
+ * Runs the animation loop, updating the text content of the animation element.
327
+ */
328
  function runAnimation() {
329
+ console.log("SYNC ANIMATION (Updated HTML): runAnimation() called.");
330
  if (!animationElement) {
331
+ console.error("SYNC ANIMATION ERROR (Updated HTML): Animation element 'cmd-animation' not found in runAnimation.");
332
  return;
333
  }
334
  if (allAnimationFrames.length === 0) {
335
+ console.error("SYNC ANIMATION ERROR (Updated HTML): Animation frames are empty. Cannot play animation.");
336
+ animationElement.textContent = "Error: No frames generated.";
337
  return;
338
  }
339
 
340
+ // Clear any existing interval to prevent multiple loops if re-initialized
341
+ if (animationIntervalId) {
342
+ clearInterval(animationIntervalId);
343
+ }
344
+
345
  animationIntervalId = setInterval(() => {
346
  if (allAnimationFrames[currentFrameIndex]) {
347
  animationElement.textContent = allAnimationFrames[currentFrameIndex];
348
  }
349
  currentFrameIndex++;
350
  if (currentFrameIndex >= allAnimationFrames.length) {
351
+ currentFrameIndex = 0; // Loop the animation
352
  }
353
  }, frameDuration);
354
+ console.log("SYNC ANIMATION (Updated HTML): Animation interval started.");
355
  }
356
+
357
+ /**
358
+ * Initializes the animation: generates frames and starts playback.
359
+ */
360
  function initializeAnimation() {
361
+ console.log("SYNC ANIMATION (Updated HTML): initializeAnimation() called.");
362
  if (!animationElement) {
363
+ // This is a fallback; DOMContentLoaded should prevent this.
364
+ // If it still happens, the element might not be in the DOM when script runs.
365
+ console.warn("SYNC ANIMATION WARNING (Updated HTML): Animation element not ready yet in initializeAnimation. Will retry shortly...");
366
+ setTimeout(initializeAnimation, 50); // Retry after a short delay
367
+ return;
368
  }
369
+ animationElement.textContent = "Initializing frames..."; // Provide feedback
370
+
371
  try {
372
+ console.log("SYNC ANIMATION (Updated HTML): Attempting to generate frames...");
373
+ allAnimationFrames = generateAnimationFrames(); // Generate all frames
374
+ if (allAnimationFrames.length > 0) {
375
+ animationElement.textContent = allAnimationFrames[0]; // Display the first frame
376
+ console.log("SYNC ANIMATION (Updated HTML): First frame set. Calling runAnimation().");
377
+ runAnimation(); // Start the animation loop
378
+ } else {
379
  animationElement.textContent = "Error: Could not generate animation frames.";
380
+ console.error("SYNC ANIMATION ERROR (Updated HTML): Could not generate frames (frames array is empty).");
381
  }
382
  } catch (error) {
383
+ console.error("SYNC ANIMATION ERROR (Updated HTML): Error during animation generation or initial play:", error);
384
+ if(animationElement) animationElement.textContent = "Error initializing animation: " + error.message;
385
  }
386
  }
387
+
388
+ // --- Script Entry Point ---
389
+ console.log("SYNC ANIMATION (Updated HTML): Adding DOMContentLoaded listener or initializing directly.");
390
+
391
+ // Wait for the DOM to be fully loaded before initializing the animation
392
+ if (document.readyState === "loading") { // Document is still loading
 
393
  document.addEventListener('DOMContentLoaded', () => {
394
+ console.log("SYNC ANIMATION (Updated HTML): DOMContentLoaded event fired.");
395
  initializeAnimation();
396
  });
397
+ } else { // Document has already loaded (interactive or complete)
398
+ console.log("SYNC ANIMATION (Updated HTML): DOM already loaded or interactive. Calling initializeAnimation via setTimeout.");
399
+ // Use setTimeout to ensure this runs after the current script block and DOM is settled.
400
+ setTimeout(initializeAnimation, 0);
401
  }
402
 
403
+ // Cleanup when the page (or iframe content) is unloaded
404
  window.addEventListener('unload', () => {
405
+ console.log("SYNC ANIMATION (Updated HTML): Unload event. Clearing interval.");
406
  if (animationIntervalId) {
407
  clearInterval(animationIntervalId);
408
  }
409
  });
410
+
411
+ console.log("SYNC ANIMATION (Updated HTML): Script execution finished.");
412
+ // --- End of Script ---
413
  </script>
414
  </body>
415
  </html>