1<!DOCTYPE html> 2<html lang="en"> 3<head> 4<meta charset="UTF-8"> 5<meta name="viewport" content="width=900, initial-scale=1.0"> 6<title>Soulor AI â Ad</title> 7<link rel="icon" type="image/png" href="/icon-192.png"> 8<link rel="apple-touch-icon" href="/icon-512.png"> 9<link rel="preconnect" href="https://fonts.googleapis.com"> 10<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"> 11<style> 12*{box-sizing:border-box;margin:0;padding:0} 13:root{ 14 --bg:#0a0a14; 15 --surface:#12121e; 16 --surface2:#1a1a2e; 17 --border:rgba(255,255,255,0.08); 18 --accent:#7c3aed; 19 --accent2:#a855f7; 20 --text:#ffffff; 21 --text2:rgba(255,255,255,0.55); 22 --bubble-ai:rgba(99,102,241,0.18); 23 --bubble-user:rgba(124,58,237,0.5); 24} 25 26html,body{ 27 width:100%; height:100%; 28 overflow:hidden; 29 background:#000; 30 font-family:'Inter',sans-serif; 31} 32body{ 33 display:flex; 34 align-items:center; 35 justify-content:center; 36 padding:16px; 37 background: 38 radial-gradient(circle at 20% 20%, rgba(124,58,237,0.28), transparent 28%), 39 radial-gradient(circle at 80% 18%, rgba(59,130,246,0.2), transparent 24%), 40 radial-gradient(circle at 50% 100%, rgba(236,72,153,0.16), transparent 34%), 41 #030308; 42} 43 44/* ââ Outer container ââ */ 45.stage{ 46 /* flex-shrink:0 so a viewport narrower than 900px scales the stage down 47 uniformly (scaleStage) instead of letting the flex row squeeze its width 48 while its absolutely-positioned children keep their 900px coordinates â 49 which crops the phone's right edge. Matters standalone at small windows, 50 and in the landing hero, which frames this page at 820px. */ 51 width:900px; height:620px; flex-shrink:0; 52 background:var(--bg); 53 position:relative; overflow:hidden; 54 background: 55 radial-gradient(ellipse 120% 50% at 50% 0%, rgba(109,40,217,0.35) 0%, transparent 60%), 56 radial-gradient(ellipse 80% 40% at 80% 100%, rgba(236,72,153,0.2) 0%, transparent 55%), 57 #0a0a14; 58 transform-origin:center center; 59 border-radius:32px; 60 box-shadow: 61 0 0 0 1px rgba(255,255,255,0.04), 62 0 40px 120px rgba(0,0,0,0.88), 63 0 0 100px rgba(124,58,237,0.14); 64} 65.stage::before{ 66 content:''; 67 position:absolute; inset:-20%; 68 background: 69 radial-gradient(circle at 25% 25%, rgba(124,58,237,0.18), transparent 22%), 70 radial-gradient(circle at 75% 30%, rgba(96,165,250,0.16), transparent 24%), 71 radial-gradient(circle at 55% 75%, rgba(236,72,153,0.12), transparent 26%); 72 filter:blur(30px); 73 animation:auroraShift 12s ease-in-out infinite alternate; 74 pointer-events:none; 75} 76@keyframes auroraShift{ 77 from{transform:translate3d(-8px,-10px,0) scale(1)} 78 to{transform:translate3d(10px,12px,0) scale(1.06)} 79} 80.orb{ 81 position:absolute; border-radius:50%; filter:blur(8px); 82 opacity:0.55; pointer-events:none; z-index:1; 83 animation:orbFloat 10s ease-in-out infinite alternate; 84} 85.orb.one{ 86 width:110px; height:110px; top:50px; left:30px; 87 background:radial-gradient(circle at 35% 35%, rgba(167,139,250,0.9), rgba(124,58,237,0.05) 72%); 88} 89.orb.two{ 90 width:140px; height:140px; right:30px; bottom:50px; 91 background:radial-gradient(circle at 35% 35%, rgba(244,114,182,0.7), rgba(236,72,153,0.04) 72%); 92 animation-duration:13s; 93} 94@keyframes orbFloat{ 95 from{transform:translateY(-8px) translateX(-6px)} 96 to{transform:translateY(10px) translateX(8px)} 97} 98.phone-halo{ 99 position:absolute; 100 width:300px; height:300px; 101 left:300px; top:160px; 102 border-radius:50%; 103 background:radial-gradient(circle, rgba(124,58,237,0.26), rgba(124,58,237,0.04) 58%, transparent 72%); 104 filter:blur(16px); 105 z-index:1; 106 pointer-events:none; 107} 108 109/* ââ Caption overlays ââ */ 110.caption{ 111 position:absolute; z-index:50; 112 left:0; right:0; 113 text-align:center; 114 padding:0 28px; 115 opacity:0; transition:opacity 0.4s; 116 pointer-events:none; 117} 118.caption.top{ top:16px; } 119.caption.bottom{ bottom:8px; } 120.caption.mid{ top:50%; transform:translateY(-50%); } 121.caption.hero-center{ 122 top:50%; 123 transform:translateY(-54%); 124 padding:0 34px; 125} 126.caption h1{ 127 font-size:42px; font-weight:900; line-height:1.1; 128 letter-spacing:-0.03em; color:#fff; 129 text-shadow:0 2px 20px rgba(0,0,0,0.8); 130} 131.caption h2{ 132 font-size:34px; font-weight:800; line-height:1.2; 133 letter-spacing:-0.02em; color:#fff; 134 text-shadow:0 2px 20px rgba(0,0,0,0.8); 135} 136.caption p{ 137 font-size:18px; font-weight:600; color:rgba(255,255,255,0.8); 138 margin-top:8px; text-shadow:0 1px 10px rgba(0,0,0,0.8); 139} 140.caption .tag{ 141 display:inline-block; background:rgba(124,58,237,0.8); 142 border-radius:999px; padding:5px 16px; 143 font-size:13px; font-weight:700; letter-spacing:0.04em; 144 text-transform:uppercase; color:#fff; margin-bottom:12px; 145} 146.caption.hero-center .tag{ 147 margin-bottom:18px; 148} 149.caption.hero-center h1{ 150 font-size:56px; 151 line-height:0.98; 152} 153.caption.show{ opacity:1; } 154.caption-card{ 155 max-width:620px; 156 margin:0 auto; 157 padding:32px 44px; 158 border-radius:24px; 159 background:linear-gradient(180deg, rgba(11,11,22,0.78), rgba(18,18,34,0.62)); 160 border:1px solid rgba(255,255,255,0.08); 161 box-shadow: 162 0 24px 60px rgba(0,0,0,0.45), 163 inset 0 1px 0 rgba(255,255,255,0.06); 164 backdrop-filter:blur(12px); 165} 166.caption-card .tag{ margin-bottom:10px; } 167.caption-card p{ margin-top:10px; } 168 169/* ââ Phone frame ââ */ 170.phone{ 171 position:absolute; 172 width:740px; height:540px; 173 left:80px; top:40px; 174 border-radius:28px; 175 background:var(--surface); 176 border:2px solid rgba(255,255,255,0.12); 177 box-shadow: 178 0 0 0 1px rgba(255,255,255,0.05), 179 0 40px 80px rgba(0,0,0,0.8), 180 inset 0 1px 0 rgba(255,255,255,0.1); 181 overflow:hidden; 182 opacity:0; transform:translateY(40px) scale(0.96); 183 transition:opacity 0.5s, transform 0.5s cubic-bezier(0.22,1,0.36,1); 184 z-index:2; 185} 186.phone.show{ opacity:1; transform:translateY(0) scale(1); } 187 188/* ââ Views inside phone ââ */ 189.view{ 190 position:absolute; inset:0; 191 display:flex; flex-direction:column; 192 opacity:0; transition:opacity 0.3s; 193} 194.view.active{ opacity:1; } 195 196/* ââ Topbar ââ */ 197.topbar{ 198 display:flex; align-items:center; gap:10px; 199 padding:18px 20px 12px; 200 border-bottom:1px solid var(--border); 201 background:var(--surface); 202 flex-shrink:0; 203} 204.tb-back{ 205 width:28px; height:28px; display:flex; align-items:center; justify-content:center; 206 color:rgba(255,255,255,0.5); font-size:18px; 207} 208.tb-avatar{ 209 width:38px; height:38px; border-radius:50%; 210 background:linear-gradient(135deg,#8b5cf6,#4f46e5); 211 display:flex; align-items:center; justify-content:center; 212 font-size:17px; flex-shrink:0; 213 box-shadow:0 0 0 2px rgba(99,102,241,0.4); 214 position:relative; 215 overflow:hidden; 216} 217.tb-avatar::after{ 218 content:''; 219 position:absolute; inset:1px; 220 border-radius:50%; 221 background:radial-gradient(circle at 35% 30%, rgba(255,255,255,0.28), transparent 38%); 222} 223.avatar-photo{ 224 width:100%; 225 height:100%; 226 object-fit:cover; 227 position:relative; 228 z-index:1; 229} 230.tb-info{ flex:1; } 231.tb-name{ font-size:15px; font-weight:700; color:#fff; } 232.tb-status{ font-size:11px; color:rgba(255,255,255,0.45); margin-top:1px; } 233.tb-actions{ display:flex; gap:6px; } 234.tb-btn{ 235 width:32px; height:32px; border-radius:50%; 236 background:rgba(255,255,255,0.06); border:none; 237 display:flex; align-items:center; justify-content:center; 238 color:rgba(255,255,255,0.6); cursor:pointer; 239} 240 241/* ââ Chat area ââ */ 242.chat-area{ 243 flex:1; overflow:hidden; 244 padding:16px 14px; display:flex; flex-direction:column; gap:10px; 245} 246.msg-row{ display:flex; opacity:0; transform:translateY(22px) scale(0.4); transform-origin:bottom center; transition:opacity 0.5s, transform 0.5s cubic-bezier(.34,1.56,.64,1); } 247.msg-row.ai{ justify-content:flex-start; transform-origin:bottom left; } 248.msg-row.user{ justify-content:flex-end; transform-origin:bottom right; } 249.msg-row.show{ opacity:1; transform:translateY(0) scale(1); } 250 251.bubble{
252 max-width:min(75%, 420px); border-radius:18px; 253 padding:10px 14px; font-size:14px; line-height:1.5; 254} 255.bubble.ai{ 256 background:var(--bubble-ai); 257 border:1px solid rgba(99,102,241,0.2); 258 color:rgba(255,255,255,0.92); 259 border-bottom-left-radius:5px; 260} 261.bubble.user{ 262 background:var(--bubble-user); 263 color:#fff; font-weight:500; 264 border-bottom-right-radius:5px; 265} 266 267.typing{ display:flex; gap:4px; align-items:center; padding:12px 16px; opacity:0; transition:opacity 0.3s; } 268.typing.show{ opacity:1; } 269.typing span{ 270 width:7px; height:7px; border-radius:50%; 271 background:rgba(99,102,241,0.7); 272 animation:typingDot 1.2s ease-in-out infinite; 273} 274.typing span:nth-child(2){ animation-delay:0.2s; } 275.typing span:nth-child(3){ animation-delay:0.4s; } 276@keyframes typingDot{ 0%,60%,100%{transform:translateY(0);opacity:0.4} 30%{transform:translateY(-5px);opacity:1} } 277 278.voice-card{ 279 display:flex; 280 align-items:center; 281 gap:10px; 282 min-width:190px; 283 background:#17172a; 284 border:1px solid rgba(255,255,255,0.08); 285 border-radius:18px; 286 border-bottom-left-radius:5px; 287 padding:10px 14px; 288 color:rgba(255,255,255,0.8); 289 font-size:11px; 290 box-shadow:0 10px 26px rgba(0,0,0,0.18); 291} 292.voice-play{ 293 width:34px; height:34px; border-radius:50%; 294 background:var(--accent); 295 display:flex; align-items:center; justify-content:center; 296 color:#fff; font-size:12px; flex-shrink:0; 297} 298.voice-wave{ 299 display:flex; align-items:center; gap:3px; flex:1; height:22px; 300} 301.voice-bar{ 302 width:3px; border-radius:999px; 303 background:linear-gradient(180deg,#c4b5fd,#8b9cfb); 304 animation:voiceWave 1.1s ease-in-out infinite; 305} 306.voice-bar:nth-child(1){ height:8px; animation-delay:0s; } 307.voice-bar:nth-child(2){ height:14px; animation-delay:0.1s; } 308.voice-bar:nth-child(3){ height:10px; animation-delay:0.2s; } 309.voice-bar:nth-child(4){ height:16px; animation-delay:0.3s; } 310.voice-bar:nth-child(5){ height:9px; animation-delay:0.15s; } 311.voice-bar:nth-child(6){ height:13px; animation-delay:0.25s; } 312.voice-time{ font-size:11.5px; color:rgba(255,255,255,0.45); font-variant-numeric:tabular-nums; white-space:nowrap; flex-shrink:0; } 313@keyframes voiceWave{ 314 0%,100%{transform:scaleY(0.72);opacity:0.7} 315 50%{transform:scaleY(1.08);opacity:1} 316} 317 318.stack{ 319 max-width:min(75%, 420px); 320 display:flex; 321 flex-direction:column; 322 gap:6px; 323} 324.stack .bubble.ai{ 325 width:fit-content; 326} 327.meme-card{ 328 position:relative; 329 overflow:hidden; 330 background:#120f20; 331 border:1px solid rgba(255,255,255,0.08); 332 border-radius:14px; 333 width:148px; 334 min-height:126px; 335 display:flex; 336 flex-direction:column; 337 align-items:stretch; 338 justify-content:flex-end; 339 gap:0; 340 color:#fff; 341 box-shadow: 342 inset 0 1px 0 rgba(255,255,255,0.06), 343 0 14px 28px rgba(0,0,0,0.24); 344} 345.meme-thumb{ 346 width:100%; 347 height:100%; 348 object-fit:cover; 349 position:absolute; 350 inset:0; 351} 352.meme-overlay{ 353 position:relative; 354 z-index:1; 355 margin-top:auto; 356 padding:10px 10px 8px; 357 background:linear-gradient(180deg, rgba(5,5,10,0) 0%, rgba(5,5,10,0.74) 45%, rgba(5,5,10,0.92) 100%); 358} 359.meme-label{ 360 font-size:10px; 361 font-weight:700; 362 letter-spacing:0.08em; 363 color:rgba(255,255,255,0.66); 364 text-transform:uppercase; 365} 366.meme-cap{ 367 font-size:12px; 368 font-weight:700; 369 color:rgba(255,255,255,0.94); 370 text-align:left; 371 margin-top:4px; 372 line-height:1.25; 373} 374.meme-credit{ 375 margin-top:6px; 376 font-size:9px; 377 font-weight:700; 378 letter-spacing:0.06em; 379 color:rgba(255,255,255,0.52); 380 text-transform:uppercase; 381} 382 383/* ââ Mode bar ââ */ 384.mode-bar{ 385 display:flex; gap:6px; padding:10px 14px; 386 border-top:1px solid var(--border); 387 background:var(--surface); 388 flex-shrink:0; 389} 390.mode-tag{ 391 display:flex; align-items:center; gap:5px; 392 padding:6px 12px; border-radius:999px; 393 font-size:12px; font-weight:600; 394 background:rgba(99,102,241,0.15); border:1px solid rgba(99,102,241,0.3); 395 color:#a5b4fc; 396} 397.mode-tag.sim{ background:rgba(236,72,153,0.15); border-color:rgba(236,72,153,0.3); color:#f9a8d4; } 398 399/* ââ Switch button ââ */ 400.switch-btn{ 401 position:absolute; bottom:82px; right:16px; 402 background:linear-gradient(135deg,var(--accent),var(--accent2)); 403 border:none; border-radius:22px; padding:10px 18px; 404 font-size:13px; font-weight:700; color:#fff; 405 display:flex; align-items:center; gap:7px; 406 box-shadow:0 4px 20px rgba(124,58,237,0.5); 407 opacity:0; transform:scale(0.25); 408 transition:opacity 0.45s, transform 0.45s cubic-bezier(.34,1.56,.64,1); 409 cursor:pointer; z-index:10; 410} 411.switch-btn.show{ opacity:1; transform:scale(1); } 412.switch-btn.tap{ transform:scale(0.88); } 413 414/* ââ Exaggerated pulse pop, for send-btn and brand-cta on demand ââ */ 415@keyframes btnPulse{ 0%{transform:scale(1)} 40%{transform:scale(1.45)} 100%{transform:scale(1)} } 416.pulse{ animation:btnPulse 0.5s cubic-bezier(.34,1.56,.64,1); } 417 418/* ââ Phone-wide punch/zoom for emphasis at key beats ââ */ 419@keyframes phonePunch{ 0%{transform:scale(1)} 45%{transform:scale(1.045)} 100%{transform:scale(1)} } 420.phone.punch{ animation:phonePunch 0.55s cubic-bezier(.34,1.56,.64,1); } 421 422/* ââ Ripple ââ */ 423.ripple{ 424 position:absolute; border-radius:50%; 425 background:rgba(124,58,237,0.4); 426 width:60px; height:60px; 427 margin-left:-30px; margin-top:-30px; 428 animation:rippleAnim 0.6s ease-out forwards; 429 pointer-events:none; z-index:20; 430} 431@keyframes rippleAnim{ from{opacity:1;transform:scale(0)} to{opacity:0;transform:scale(2.5)} } 432 433/* ââ Simulation view ââ */ 434.sim-header{ 435 background:linear-gradient(135deg,rgba(236,72,153,0.15),rgba(124,58,237,0.1)); 436 padding:18px 20px 14px; 437 border-bottom:1px solid var(--border); 438 flex-shrink:0; 439} 440.sim-title{ font-size:16px; font-weight:800; color:#fff; } 441.sim-sub{ font-size:12px; color:rgba(255,255,255,0.45); margin-top:3px; } 442 443.sim-topic{ 444 margin:10px 14px 4px; 445 background:rgba(255,255,255,0.04); 446 border-radius:12px; padding:10px 14px; 447 font-size:12px; color:rgba(255,255,255,0.5); flex-shrink:0; 448 border-left:3px solid rgba(124,58,237,0.6); 449 font-style:italic; 450} 451 452.sim-list{ flex:1; overflow:hidden; padding:8px 10px; display:grid; grid-template-columns:1fr 1fr; grid-auto-rows:min-content; gap:8px 10px; } 453 454.analyst{ 455 display:flex; align-items:flex-start; gap:10px; 456 padding:10px 12px; border-radius:14px; 457 background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.06); 458 opacity:0; transform:translateX(16px); 459 transition:opacity 0.4s, transform 0.4s cubic-bezier(0.22,1,0.36,1); 460} 461.analyst.show{ opacity:1; transform:translateX(0); } 462.analyst-dot{ width:9px; height:9px; border-radius:50%; flex-shrink:0; margin-top:5px; } 463.analyst-name{ font-size:12px; font-weight:700; color:rgba(255,255,255,0.9); margin-bottom:3px; } 464.analyst-text{ font-size:12px; color:rgba(255,255,255,0.55); line-height:1.45; } 465 466.sim-back-btn{ 467 margin:6px 10px 10px; 468 background:rgba(99,102,241,0.15); border:1px solid rgba(99,102,241,0.3); 469 border-radius:12px; padding:10px 14px; 470 font-size:13px; font-weight:700; color:#a5b4fc; 471 display:flex; align-items:center; justify-content:center; gap:7px; 472 opacity:0; transform:translateY(8px); 473 transition:opacity 0.4s, transform 0.4s; 474 flex-shrink:0; 475} 476.sim-back-btn.show{ opacity:1; transform:translateY(0); } 477 478/* ââ Input bar ââ */ 479.input-bar{ 480 display:flex; align-items:center; gap:8px; 481 padding:10px 14px 14px; 482 border-top:1px solid var(--border); 483 background:var(--surface); 484 flex-shrink:0; 485} 486.input-field{ 487 flex:1; background:rgba(255,255,255,0.06); 488 border:1px solid rgba(255,255,255,0.1); border-radius:22px; 489 padding:9px 16px; font-size:13px; color:rgba(255,255,255,0.4); 490} 491.send-btn{ 492 width:36px; height:36px; border-radius:50%; 493 background:var(--accent); border:none; 494 display:flex; align-items:center; justify-content:center; 495 color:#fff; 496} 497 498/* ââ Branding screen ââ */ 499.brand-screen{ 500 position:absolute; inset:0; z-index:40; 501 display:flex; flex-direction:column; align-items:center; justify-content:center; 502 background: 503 radial-gradient(ellipse 100% 60% at 50% 30%, rgba(124,58,237,0.5) 0%, transparent 65%), 504 #0a0a14; 505 opacity:0; transition:opacity 0.6s; 506} 507.brand-screen.show{ opacity:1; } 508.brand-logo{ 509 width:80px; height:80px; border-radius:22px; 510 background:linear-gradient(135deg,#6366f1,#7c3aed); 511 display:flex; align-items:center; justify-content:center; 512 margin-bottom:20px; 513 box-shadow:0 0 40px rgba(124,58,237,0.6); 514 overflow:hidden; 515}
516.brand-logo img{ 517 width:100%; 518 height:100%; 519 object-fit:cover; 520} 521.brand-name{ font-size:36px; font-weight:900; letter-spacing:-0.03em; color:#fff; margin-bottom:8px; } 522.brand-sub{ font-size:15px; color:rgba(255,255,255,0.5); margin-bottom:32px; text-align:center; padding:0 40px; } 523.brand-url{ 524 font-size:12px; color:rgba(255,255,255,0.3); 525 font-weight:600; letter-spacing:0.04em; 526} 527.brand-cta{ 528 margin-top:28px; background:linear-gradient(135deg,var(--accent),var(--accent2)); 529 border:none; border-radius:999px; padding:14px 36px; 530 font-size:16px; font-weight:800; color:#fff; 531 box-shadow:0 4px 24px rgba(124,58,237,0.5); 532 cursor:pointer; 533 text-decoration:none; 534 display:inline-flex; 535 align-items:center; 536 justify-content:center; 537 transform:scale(0.5); 538 transition:transform 0.5s 0.15s cubic-bezier(.34,1.56,.64,1); 539} 540.brand-screen.show .brand-cta{ transform:scale(1); } 541 542/* ââ Progress dots ââ */ 543.progress-bar{ 544 position:absolute; bottom:22px; left:50%; transform:translateX(-50%); 545 display:flex; gap:6px; z-index:60; 546} 547.prog-dot{ 548 width:6px; height:6px; border-radius:50%; 549 background:rgba(255,255,255,0.2); transition:all 0.3s; 550} 551.prog-dot.active{ background:#fff; width:18px; border-radius:3px; } 552 553/* ââ Transition overlay ââ */ 554.trans-overlay{ 555 position:absolute; inset:0; z-index:35; 556 background:rgba(10,10,20,0.95); 557 opacity:0; pointer-events:none; 558 transition:opacity 0.25s; 559}
560.trans-overlay.flash{ opacity:1; } 561 562/* ââ Step label ââ */ 563.step-label{ 564 position:absolute; top:24px; left:50%; transform:translateX(-50%); 565 display:flex; align-items:center; gap:6px; z-index:45; 566 opacity:0; transition:opacity 0.4s; white-space:nowrap; 567} 568.step-label.show{ opacity:1; } 569.step-dot{ width:6px; height:6px; border-radius:50%; background:var(--accent); } 570.step-text{ font-size:11px; font-weight:700; letter-spacing:0.08em; text-transform:uppercase; color:rgba(255,255,255,0.5); } 571</style> 572</head> 573<body> 574<div class="stage" id="stage"> 575 576 <!-- Progress dots --> 577 <div class="progress-bar"> 578 <div class="prog-dot active" id="d0"></div> 579 <div class="prog-dot" id="d1"></div> 580 <div class="prog-dot" id="d2"></div> 581 <div class="prog-dot" id="d3"></div> 582 <div class="prog-dot" id="d4"></div> 583 </div> 584 585 <!-- Transition overlay --> 586 <div class="trans-overlay" id="transOverlay"></div> 587 588 <div class="orb one"></div> 589 <div class="orb two"></div> 590 <div class="phone-halo"></div> 591 592 <!-- Step label --> 593 <div class="step-label" id="stepLabel"> 594 <div class="step-dot"></div> 595 <div class="step-text" id="stepText">Companion Mode</div> 596 </div> 597 598 <!-- Caption: hook --> 599 <div class="caption hero-center" id="captionHook"> 600 <div class="tag" id="copyHookTag">Soulor AI</div> 601 <h1 id="copyHookTitle">An AI that knows<br>your context.</h1> 602 </div> 603 604 <!-- Caption: switch --> 605 <div class="caption mid" id="captionSwitch"> 606 <div class="caption-card"> 607 <div class="tag" id="copySwitchTag">Simulation Mode</div> 608 <h2 id="copySwitchTitle">Need real-world advice?<br>Get five honest takes on the same moment.</h2> 609 <p><span id="copySwitchSub" style="color:#a78bfa">One tap opens 5 perspectives.</span></p> 610 </div> 611 </div> 612 613 <!-- Phone --> 614 <div class="phone" id="phone"> 615 616 <!-- VIEW 1: Companion chat --> 617 <div class="view active" id="viewCompanion"> 618 <div class="topbar"> 619 <div class="tb-back">â¹</div> 620 <div class="tb-avatar"><img class="avatar-photo" src="/default-avatar.png" alt="0816"></div> 621 <div class="tb-info"> 622 <div class="tb-name">0816</div> 623 <div class="tb-status" id="copyActiveNow">Active now</div> 624 </div> 625 <div class="tb-actions"> 626 <div class="tb-btn"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/></svg></div> 627 <div class="tb-btn"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div> 628 </div> 629 </div> 630 631 <div class="chat-area" id="chatArea"> 632 <div class="msg-row user" id="m0"><div class="bubble user" id="copyUserMsg1">saw my friends posted hangout pics and i wasn't invited ð¶</div></div> 633 <div class="msg-row ai" id="mTyping1"> 634 <div class="bubble ai typing show" id="typing1"><span></span><span></span><span></span></div> 635 </div> 636 <div class="msg-row ai" id="m1"> 637 <div class="stack"> 638 <div class="bubble ai" id="copyAiMsg1">ow... that hurts.<br>that feels deliberate.</div> 639 <div class="voice-card"> 640 <div class="voice-play"> 641 <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg> 642 </div> 643 <div class="voice-wave" aria-hidden="true"> 644 <span class="voice-bar"></span> 645 <span class="voice-bar"></span> 646 <span class="voice-bar"></span> 647 <span class="voice-bar"></span> 648 <span class="voice-bar"></span> 649 <span class="voice-bar"></span> 650 </div> 651 <div class="voice-time">0:12</div> 652 </div> 653 <div class="meme-card"> 654 <img class="meme-thumb" src="https://media.giphy.com/media/l0HlvtIPzPdt2usKs/giphy.gif" alt="Reaction meme from Giphy"> 655 <div class="meme-overlay"> 656 <div class="meme-label" id="copyMemeLabel">Meme</div> 657 <div class="meme-cap" id="copyMemeCap">me processing the group chat</div> 658 <div class="meme-credit">Powered by Giphy</div> 659 </div> 660 </div> 661 </div> 662 </div> 663 <div class="msg-row user" id="m2"><div class="bubble user" id="copyUserMsg2">looks planned and they definitely know me well enough to invite me ð¤</div></div> 664 </div> 665 666 <div class="mode-bar"> 667 <div class="mode-tag"> 668 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
669 <span id="copyCompanionModeTag">Companion</span> 670 </div> 671 </div> 672 <div class="input-bar"> 673 <div class="input-field" id="copyMessagePlaceholder">Message 0816â¦</div> 674 <div class="send-btn" id="sendBtn"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M2 21l21-9L2 3v7l15 2-15 2z"/></svg></div> 675 </div> 676 </div> 677 678 <!-- VIEW 2: Simulation --> 679 <div class="view" id="viewSim"> 680 <div class="sim-header"> 681 <div style="display:flex;align-items:center;gap:10px"> 682 <div style="width:36px;height:36px;border-radius:10px;background:rgba(236,72,153,0.2);display:flex;align-items:center;justify-content:center"> 683 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#f472b6" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/></svg> 684 </div> 685 <div> 686 <div class="sim-title" id="copySimPanelTitle">Simulation Panel</div> 687 <div class="sim-sub" id="copySimPanelSub">5 perspectives on your situation</div> 688 </div> 689 </div> 690 </div> 691 692 <div class="sim-topic" id="copySimTopic">"Friends hung out without me and posted it"</div> 693 694 <div class="sim-list"> 695 <div class="analyst" id="a0"> 696 <div class="analyst-dot" style="background:#fbbf24"></div> 697 <div><div class="analyst-name" id="copyAnalyst0Name">Optimist âï¸</div><div class="analyst-text" id="copyAnalyst0Text">Maybe it was last-minute and they felt bad about it. One honest conversation could clear the air.</div></div> 698 </div> 699 <div class="analyst" id="a1"> 700 <div class="analyst-dot" style="background:#94a3b8"></div> 701 <div><div class="analyst-name" id="copyAnalyst1Name">Cynic ð§ï¸</div><div class="analyst-text" id="copyAnalyst1Text">If it happened once and they posted it, it wasn't an accident. Pay attention to the pattern.</div></div> 702 </div> 703 <div class="analyst" id="a2"> 704 <div class="analyst-dot" style="background:#4ade80"></div> 705 <div><div class="analyst-name" id="copyAnalyst2Name">Mentor ð§</div><div class="analyst-text" id="copyAnalyst2Text">Don't ghost or spiral. Just say "hey, saw the pics â would've loved to join." Simple and direct.</div></div> 706 </div> 707 <div class="analyst" id="a3"> 708 <div class="analyst-dot" style="background:#60a5fa"></div> 709 <div><div class="analyst-name" id="copyAnalyst3Name">Observer ðï¸</div><div class="analyst-text" id="copyAnalyst3Text">The fact they posted it publicly says something. They either forgot you or wanted you to see it.</div></div> 710 </div> 711 <div class="analyst" id="a4"> 712 <div class="analyst-dot" style="background:#c084fc"></div> 713 <div><div class="analyst-name" id="copyAnalyst4Name">Gossiper ð¬</div><div class="analyst-text" id="copyAnalyst4Text">Someone in that group definitely thought about whether to invite you. Someone made that call.</div></div> 714 </div> 715 </div> 716 717 <div class="sim-back-btn" id="simBackBtn"> 718 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg> 719 <span id="copyBackTo0816">Back to 0816</span> 720 </div> 721 </div> 722 723 <!-- Switch button (floats over companion view) --> 724 <div class="switch-btn" id="switchBtn"> 725 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/></svg> 726 <span id="copySimulationBtn">Simulation</span> 727 </div> 728 729 </div><!-- end phone --> 730 731 <!-- Branding screen --> 732 <div class="brand-screen" id="brandScreen"> 733 <div class="brand-logo"> 734 <img src="/icon-192.png" alt="Soulor AI icon"> 735 </div> 736 <div class="brand-name" id="copyBrandName">Soulor AI</div> 737 <div class="brand-sub" id="copyBrandSub">Talk it out. Then see the whole picture.</div> 738 <a class="brand-cta" href="/app" id="copyBrandCta">Try it free â</a> 739 <div class="brand-url" style="margin-top:20px">vercel.app</div> 740 </div> 741 742</div><!-- end stage --> 743
744<script> 745const $ = id => document.getElementById(id); 746const show = id => $(id)?.classList.add('show'); 747const hide = id => $(id)?.classList.remove('show'); 748const delay = ms => new Promise(r => setTimeout(r, ms)); 749const AD_LOCALE = location.pathname === '/ad2' ? 'zh' : 'en'; 750const AD_COPY = { 751 en: { 752 pageTitle: 'Soulor AI â Ad', 753 stepCompanion: 'Companion Mode', 754 stepSimulation: 'Simulation Mode', 755 hookTag: 'Soulor AI', 756 hookTitle: 'An AI that knows<br>your context.', 757 switchTag: 'Simulation Mode', 758 switchTitle: 'Need real-world advice?<br>Get five honest takes on the same moment.', 759 switchSub: 'One tap opens 5 perspectives.', 760 activeNow: 'Active now', 761 userMsg1: "saw my friends posted hangout pics and i wasn't invited ð¶", 762 aiMsg1: 'ow... that hurts.<br>that feels deliberate.', 763 memeCap: 'me processing the group chat', 764 memeLabel: 'Meme', 765 userMsg2: 'looks planned and they definitely know me well enough to invite me ð¤', 766 companionModeTag: 'Companion', 767 messagePlaceholder: 'Message 0816â¦', 768 simPanelTitle: 'Simulation Panel', 769 simPanelSub: '5 perspectives on your situation', 770 simTopic: '"Friends hung out without me and posted it"', 771 analyst0Name: 'Optimist âï¸', 772 analyst0Text: 'Maybe it was last-minute and they felt bad about it. One honest conversation could clear the air.', 773 analyst1Name: 'Cynic ð§ï¸', 774 analyst1Text: "If it happened once and they posted it, it wasn't an accident. Pay attention to the pattern.", 775 analyst2Name: 'Mentor ð§', 776 analyst2Text: 'Don\'t ghost or spiral. Just say "hey, saw the pics â would\'ve loved to join." Simple and direct.', 777 analyst3Name: 'Observer ðï¸', 778 analyst3Text: 'The fact they posted it publicly says something. They either forgot you or wanted you to see it.', 779 analyst4Name: 'Gossiper ð¬', 780 analyst4Text: 'Someone in that group definitely thought about whether to invite you. Someone made that call.', 781 backTo0816: 'Back to 0816', 782 simulationBtn: 'Simulation', 783 brandName: 'Soulor AI', 784 brandSub: 'Talk it out. Then see the whole picture.', 785 brandCta: 'Try it free â', 786 switchBack: 'So â which one hit different? ð¤', 787 }, 788 zh: { 789 pageTitle: 'Soulor AI â ä¸æå¹¿å', 790 stepCompanion: 'éªä¼´æ¨¡å¼', 791 stepSimulation: 'æ¨¡ææ¨¡å¼', 792 hookTag: 'Soulor AI', 793 hookTitle: 'ä¸ä¸ªçæ£æä½ <br>å¤å¢ç AIã', 794 switchTag: 'æ¨¡ææ¨¡å¼', 795 switchTitle: 'æ³å¬ç°å®ä¸ç¹ç建议ï¼<br>åä¸ä»¶äºï¼ç»ä½ äºç§çå®è§è§ã', 796 switchSub: 'ä¸ç¹è¿å ¥ 5 ç§ä¸å解读ã', 797 activeNow: 'å¨çº¿ä¸', 798 userMsg1: 'ææååäºèä¼ç §çï¼ä½æ ¹æ¬æ²¡å«æ ð¶', 799 aiMsg1: 'åâ¦â¦è¿å¾ä¼¤äººã<br>çèµ·æ¥ççåæ æçã', 800 memeCap: 'ææ£å¨ç¯çåæè¿ä¸ªç¾¤è', 801 memeLabel: 'æ¢å¾', 802 userMsg2: 'çèµ·æ¥å°±æ¯æè®¡åçï¼èä¸ä»ä»¬ææç¥é该å«ä¸æ ð¤', 803 companionModeTag: 'éªä¼´', 804 messagePlaceholder: 'ç» 0816 åæ¶æ¯â¦', 805 simPanelTitle: '模æé¢æ¿', 806 simPanelSub: 'å ³äºè¿ä»¶äºç 5 ç§è§è§', 807 simTopic: 'âæå们èçæèä¼ï¼è¿ååºæ¥äºâ', 808 analyst0Name: 'ä¹è§æ´¾ âï¸', 809 analyst0Text: 'ä¹è®¸æ¯ä¸´æ¶å³å®çï¼ä»ä»¬åæ¥ææè¯å°æ²¡å«ä½ ã䏿¬¡å¦ç½çæ²éï¼å¯è½å°±è½è¯´å¼ã', 810 analyst1Name: 'æçæ´¾ ð§ï¸', 811 analyst1Text: 'å¦æäºæ åçäºï¼è¿å ¬å¼ååºæ¥ï¼é£å°±ä¸å¤ªåæå¤ãéç¹è¦çè¿æ¯ä¸æ¯ä¸ä¸ªæ¨¡å¼ã', 812 analyst2Name: '坼叿´¾ ð§', 813 analyst2Text: 'å«ç´æ¥æ¶å¤±ï¼ä¹å«å å´©æºãå°±ç®å说ä¸å¥ï¼âæçå°ç §çäºï¼æ¬æ¥ä¹å¾æ³ä¸èµ·å»ãâç´æ¥ä½ä¸å¤±å寸ã', 814 analyst3Name: 'è§å¯æ´¾ ðï¸', 815 analyst3Text: 'ä»ä»¬æ¢ç¶å ¬å¼åäºï¼è¯´æè¿ä»¶äºæ¬èº«å°±ææåº¦ãå¯è½æ¯å¿äºä½ ï¼ä¹å¯è½æ¯é»è®¤ä½ ä¼çå°ã', 816 analyst4Name: 'å «å¦æ´¾ ð¬', 817 analyst4Text: 'é£ä¸ç¾¤äººéä¸å®æäººæ³è¿âè¦ä¸è¦å«ä½ âãåªæ¯æåï¼æäººåäºä¸å«ä½ çå³å®ã', 818 backTo0816: 'åå° 0816', 819 simulationBtn: '模æ', 820 brandName: 'Soulor AI', 821 brandSub: 'å æè¯è¯´åºæ¥ï¼åçæ¸ æ´ä»¶äºã', 822 brandCta: 'å è´¹è¯è¯ â', 823 switchBack: 'æä»¥å¢ï¼åªä¸ç§è¯´æ³ææ³ä½ ï¼ ð¤', 824 }, 825}; 826 827function applyAdLocale() { 828 const copy = AD_COPY[AD_LOCALE] || AD_COPY.en; 829 document.title = copy.pageTitle; 830 const htmlMap = { 831 copyHookTitle: copy.hookTitle, 832 copySwitchTitle: copy.switchTitle, 833 copyAiMsg1: copy.aiMsg1, 834 }; 835 const textMap = { 836 stepText: copy.stepCompanion, 837 copyHookTag: copy.hookTag, 838 copySwitchTag: copy.switchTag, 839 copySwitchSub: copy.switchSub, 840 copyActiveNow: copy.activeNow, 841 copyUserMsg1: copy.userMsg1, 842 copyMemeCap: copy.memeCap, 843 copyMemeLabel: copy.memeLabel, 844 copyUserMsg2: copy.userMsg2, 845 copyCompanionModeTag: copy.companionModeTag, 846 copyMessagePlaceholder: copy.messagePlaceholder, 847 copySimPanelTitle: copy.simPanelTitle, 848 copySimPanelSub: copy.simPanelSub, 849 copySimTopic: copy.simTopic, 850 copyAnalyst0Name: copy.analyst0Name, 851 copyAnalyst0Text: copy.analyst0Text, 852 copyAnalyst1Name: copy.analyst1Name, 853 copyAnalyst1Text: copy.analyst1Text, 854 copyAnalyst2Name: copy.analyst2Name, 855 copyAnalyst2Text: copy.analyst2Text, 856 copyAnalyst3Name: copy.analyst3Name, 857 copyAnalyst3Text: copy.analyst3Text, 858 copyAnalyst4Name: copy.analyst4Name, 859 copyAnalyst4Text: copy.analyst4Text, 860 copyBackTo0816: copy.backTo0816, 861 copySimulationBtn: copy.simulationBtn, 862 copyBrandName: copy.brandName, 863 copyBrandSub: copy.brandSub, 864 copyBrandCta: copy.brandCta, 865 };
866 Object.entries(htmlMap).forEach(([id, value]) => { 867 const el = $(id); 868 if (el) el.innerHTML = value; 869 }); 870 Object.entries(textMap).forEach(([id, value]) => { 871 const el = $(id); 872 if (el) el.textContent = value; 873 }); 874} 875 876function scaleStage() { 877 const stage = $('stage'); 878 if (!stage) return; 879 const scale = Math.min(window.innerWidth / 900, window.innerHeight / 620, 1); 880 stage.style.transform = `scale(${scale})`; 881} 882 883function setDot(n) { 884 for(let i=0;i<5;i++) { 885 const d = $('d'+i); 886 d.classList.toggle('active', i===n); 887 } 888} 889 890function flash() { 891 const o = $('transOverlay'); 892 o.classList.add('flash'); 893 return delay(250).then(() => o.classList.remove('flash')); 894} 895 896// Brief exaggerated pop/punch on an element â classes aren't `forwards`, and 897// need removing after each play so the same class can retrigger the keyframe 898// animation next time (CSS animations only fire when the class is *added*, 899// not while merely present). 900function punch(el) { 901 el.classList.add('punch'); 902 return delay(600).then(() => el.classList.remove('punch')); 903} 904function pulse(el) { 905 el.classList.add('pulse'); 906 return delay(550).then(() => el.classList.remove('pulse')); 907} 908 909// Punches the camera in on a specific element until it dominates the frame, 910// holds, then eases back out â rather than everything staying pinned at one 911// small fixed phone size the whole time. Scales `.phone` itself (inline 912// style, not a class), anchored at the target element's on-phone position via 913// `transform-origin` â but a pure origin-anchored scale only keeps that one 914// point fixed; it doesn't move the target toward the frame's center, so a 915// target near an edge grows "in place" and its far side clips out of 916// `.stage`'s overflow:hidden. Fixed by pairing the origin with an explicit 917// translate that carries the target's center to the phone's own center as 918// part of the same transform, so the enlarged target always ends up fully 919// inside the frame regardless of where it started. 920// 921// Scale is computed from the target's own size, not a fixed constant â a 922// small round button and a wide message bubble have very different 923// width/height ratios to the phone frame, so each ends up at whatever scale 924// actually fills `fillFraction` of the frame on its tighter axis, instead of 925// every element (regardless of shape) getting the same magic-number zoom. 926function zoomTo(targetEl, fillFraction = 0.82, holdMs = 550) { 927 const phone = $('phone'); 928 const pr = phone.getBoundingClientRect(); 929 const tr = targetEl.getBoundingClientRect(); 930 const originXpx = (tr.left + tr.width / 2) - pr.left; 931 const originYpx = (tr.top + tr.height / 2) - pr.top; 932 const dx = pr.width / 2 - originXpx; 933 const dy = pr.height / 2 - originYpx; 934 const scaleX = (pr.width * fillFraction) / tr.width; 935 const scaleY = (pr.height * fillFraction) / tr.height; 936 const scale = Math.min(scaleX, scaleY, 3.2); // capped so tiny elements (send-btn) don't blow up absurdly 937 phone.style.transition = 'transform 0.5s cubic-bezier(.4,0,.2,1)'; 938 phone.style.transformOrigin = `${originXpx}px ${originYpx}px`; 939 phone.style.transform = `translate(${dx}px, ${dy}px) scale(${scale})`; 940 return delay(holdMs).then(() => { 941 phone.style.transform = 'scale(1)'; 942 return delay(500); 943 }).then(() => { 944 phone.style.transition = ''; 945 phone.style.transformOrigin = ''; 946 phone.style.transform = ''; 947 }); 948} 949 950async function run() { 951 const copy = AD_COPY[AD_LOCALE] || AD_COPY.en; 952 // ââ ACT 0: Hook âââââââââââââââââââââââââââââ 953 setDot(0); 954 $('stepText').textContent = copy.stepCompanion; 955 show('captionHook'); 956 show('stepLabel'); 957 await delay(1500); 958 hide('captionHook'); 959 await delay(400); 960 961 // ââ ACT 1: Phone appears âââââââââââââââââââââ 962 show('phone'); 963 await delay(400); 964 965 // Messages appear one by one, each zoomed to fill the frame in turn as its 966 // moment comes â not everything staying pinned at one small fixed size. 967 // The zoom starts the instant the message starts appearing (not after), 968 // so appear and enlarge read as one motion instead of two separate beats. 969 pulse($('sendBtn')); 970 show('m0'); 971 await zoomTo(document.querySelector('#m0 .bubble')); 972 $('typing1').classList.remove('show'); 973 show('m1'); 974 await zoomTo(document.querySelector('#m1 .bubble')); 975 pulse($('sendBtn')); 976 show('m2'); 977 await zoomTo(document.querySelector('#m2 .bubble')); 978 await delay(200); 979 980 // Switch button appears and is zoomed at the same moment, before the tap 981 show('switchBtn'); 982 setDot(1); 983 await zoomTo($('switchBtn')); 984 985 // ââ ACT 2: Tap switch button ââââââââââââââââââ 986 setDot(2); 987 $('switchBtn').classList.add('tap'); 988 const ripple = document.createElement('div'); 989 ripple.className = 'ripple'; 990 const btn = $('switchBtn'); 991 const br = btn.getBoundingClientRect(); 992 const sr = $('stage').getBoundingClientRect(); 993 ripple.style.left = (br.left - sr.left + br.width/2) + 'px'; 994 ripple.style.top = (br.top - sr.top + br.height/2) + 'px'; 995 $('phone').appendChild(ripple); 996 await delay(120); 997 $('switchBtn').classList.remove('tap'); 998 await delay(280); 999 // Manual overlay control instead of flash(): the swap must happen only once 1000 // the overlay has actually finished fading to opaque (its own 0.25s 1001 // transition), and must stay covered through the .view opacity crossfade 1002 // (0.3s) before fading out â otherwise both views ghost through each other. 1003 $('transOverlay').classList.add('flash'); 1004 await delay(260); 1005 punch($('phone')); 1006 1007 // ââ ACT 3: Simulation panel âââââââââââââââââââ 1008 $('stepText').textContent = copy.stepSimulation; 1009 $('viewCompanion').classList.remove('active'); 1010 $('switchBtn').classList.remove('show'); 1011 $('viewSim').classList.add('active'); 1012 await delay(300); 1013 $('transOverlay').classList.remove('flash'); 1014 await delay(250); 1015 show('captionSwitch'); 1016 await delay(1050); 1017 hide('captionSwitch'); 1018 await delay(180); 1019 1020 // Enlarge sweeps across the analysts as each one appears: only the first 1021 // box is zoomed while it's the only one shown, then the zoom glides to 1022 // each new box the instant it appears, and only zooms back out once the 1023 // last box has had its moment â rather than jumping straight to one box 1024 // once all five are already sitting there at rest. Targets are measured 1025 // once up front (while the phone is still at rest scale) so each glide is 1026 // a clean transition between two known transforms, not a moving target. 1027 const phone = $('phone'); 1028 const pr0 = phone.getBoundingClientRect(); 1029 const analystTargets = [0,1,2,3,4].map(i => { 1030 const tr = $('a'+i).getBoundingClientRect(); 1031 const originXpx = (tr.left + tr.width / 2) - pr0.left; 1032 const originYpx = (tr.top + tr.height / 2) - pr0.top; 1033 const scale = Math.min((pr0.width * 0.82) / tr.width, (pr0.height * 0.82) / tr.height, 3.2); 1034 return { originXpx, originYpx, dx: pr0.width / 2 - originXpx, dy: pr0.height / 2 - originYpx, scale }; 1035 }); 1036 phone.style.transition = 'transform 0.5s cubic-bezier(.4,0,.2,1)'; 1037 for (let i = 0; i < 5; i++) { 1038 show('a'+i); 1039 const t = analystTargets[i]; 1040 phone.style.transformOrigin = `${t.originXpx}px ${t.originYpx}px`; 1041 phone.style.transform = `translate(${t.dx}px, ${t.dy}px) scale(${t.scale})`; 1042 await delay(420); 1043 } 1044 phone.style.transform = 'scale(1)'; 1045 await delay(450); 1046 phone.style.transition = ''; 1047 phone.style.transformOrigin = ''; 1048 phone.style.transform = ''; 1049 show('simBackBtn'); 1050 setDot(3); 1051 await delay(700); 1052 1053 // ââ ACT 4: Switch back âââââââââââââââââââââââââ 1054 setDot(4); 1055 $('transOverlay').classList.add('flash');
1055 // see ACT 3 comment above 1056 await delay(260); 1057 punch($('phone')); 1058 $('viewSim').classList.remove('active'); 1059 $('viewCompanion').classList.add('active'); 1060 $('stepText').textContent = copy.stepCompanion; 1061 await delay(300); 1062 $('transOverlay').classList.remove('flash'); 1063 await delay(250); 1064 1065 const switchBack = document.createElement('div'); 1066 switchBack.className = 'msg-row ai show'; 1067 switchBack.innerHTML = `<div class="bubble ai">${copy.switchBack}</div>`; 1068 $('chatArea').appendChild(switchBack); 1069 await delay(1400); 1070 1071 // ââ ACT 5: Branding âââââââââââââââââââââââââââ 1072 await flash(); 1073 show('brandScreen'); 1074 hide('stepLabel'); 1075 await delay(2500); 1076 1077 // ââ Loop ââââââââââââââââââââââââââââââââââââââ 1078 hide('brandScreen'); 1079 // Reset 1080 $('viewSim').classList.remove('active'); 1081 $('viewCompanion').classList.add('active'); 1082 switchBack.remove(); 1083 ['m0','m1','m2'].forEach(id => hide(id)); 1084 $('typing1').classList.add('show'); 1085 ['a0','a1','a2','a3','a4','simBackBtn'].forEach(id => hide(id)); 1086 hide('switchBtn'); 1087 hide('phone'); 1088 show('stepLabel'); 1089 await delay(300); 1090 run(); 1091} 1092 1093// Start 1094window.addEventListener('load', () => { 1095 applyAdLocale(); 1096 scaleStage(); 1097 delay(800).then(run); 1098}); 1099window.addEventListener('resize', scaleStage); 1100</script>
1100 1101</body> 1102</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.