1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>Xlift â Get customers on autopilot from X</title> 7 <meta name="description" content="Xlift manages your accounts, suggests topics, and helps you launch authentic replies on X." /> 8 <link rel="icon" type="image/svg+xml" href="/logo.svg" /> 9 <link rel="stylesheet" href="/style.css" /> 10 <!-- Google Analytics (GA4) -->
vendor: 66 bytes, lines 10-11
10 11 <script async src="https://www.googletagmanager.com/gtag/js?id=
11G-CJGYNR1N4B
vendor: 14 bytes, lines 11-12
11"></script> 12
12<script> 13
vendor: 139 bytes, lines 13-16
13window.dataLayer = window.dataLayer || []; 14 function gtag(){dataLayer.push(arguments);} 15 gtag('js', new Date()); 16 gtag('config', '
16G-CJGYNR1N4B
vendor: 6 bytes, lines 16-17
16'); 17
17</script>
17 18</head> 19<body> 20 <div id="page-preloader" aria-hidden="true"> 21 <img src="/logo.svg" alt="" class="preloader-logo" /> 22 </div> 23
23<script> 24 (function () { 25 var done = function () { 26 var el = document.getElementById('page-preloader'); 27 if (el) el.classList.add('is-hidden'); 28 }; 29 if (document.readyState === 'complete') done(); 30 else window.addEventListener('load', done); 31 })(); 32 </script>
32 33 <header class="nav"> 34 <a href="#" class="brand"> 35 <img src="/logo.svg" alt="Xlift" class="brand-mark" /> 36 <span class="brand-name">Xlift</span> 37 </a> 38 <nav class="nav-links"> 39 <a href="#features">Features</a> 40 <a href="#toolkit">Toolkit</a> 41 <a href="#modes">Modes</a> 42 <a href="#pricing">Pricing</a> 43 <a href="https://app.xlift.ai/signup" class="btn btn-primary btn-sm">Get started</a> 44 </nav> 45 </header> 46 47 <main> 48 <section class="hero"> 49 <div class="hero-inner"> 50 <img src="/logo.svg" alt="" class="hero-mark" /> 51 <h1 class="hero-title"> 52 Get customers on autopilot from <span class="x-mark">X</span>. 53 </h1> 54 <p class="hero-sub"> 55 Track your brand, schedule posts, edit AI drafts, and reply across 56 posts and DMs â semi-auto when you want control, full auto when you don't. 57 </p> 58 <div class="hero-ctas"> 59 <a href="https://app.xlift.ai/signup" class="btn btn-primary">Get started</a> 60 <a href="#how" class="btn btn-ghost">See how it works â</a> 61 </div> 62 </div> 63 </section> 64 65 <section class="stats" aria-label="Xlift by the numbers"> 66 <div class="stats-inner"> 67 <div> 68 <span class="stat-num">10k+</span> 69 <span class="stat-label">Replies drafted & sent</span> 70 </div> 71 <div> 72 <span class="stat-num">98%</span> 73 <span class="stat-label">Account-safety rate</span> 74 </div> 75 <div> 76 <span class="stat-num">4.8/5</span> 77 <span class="stat-label">Founder rating, in beta</span> 78 </div> 79 <div> 80 <span class="stat-num"><2s</span> 81 <span class="stat-label">From open to draft</span> 82 </div> 83 </div> 84 </section> 85 86 <section class="features" id="features"> 87 <div class="section-head"> 88 <span class="section-eyebrow"><span class="num">01</span> Features</span> 89 <h2>Everything you need to grow on X</h2> 90 <p>One toolkit for tracking, posting, replying, and chatting â all in your voice.</p> 91 </div> 92 93 <div class="features-grid"> 94 <article class="feature"> 95 <div class="feature-icon"> 96 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 97 <circle cx="12" cy="12" r="2"/> 98 <path d="M16.2 7.8a6 6 0 0 1 0 8.5"/> 99 <path d="M7.8 16.2a6 6 0 0 1 0-8.5"/> 100 <path d="M19 5a10 10 0 0 1 0 14"/> 101 <path d="M5 19a10 10 0 0 1 0-14"/> 102 </svg> 103 </div> 104 <h3>Track your brand</h3> 105 <p>Mentions, sentiment, and the conversations shaping your reputation â all in one feed.</p> 106 <div class="demo-ticker" aria-hidden="true"> 107 <div class="demo-hdr"> 108 <span class="live-dot"><span class="live-label">LIVE</span></span> 109 <span class="counter"><span class="num" id="demo-mentions-count">247</span><span class="delta">â12 24h</span></span> 110 </div> 111 <div class="demo-ticker-feed"> 112 <div class="demo-ticker-track"> 113 <div class="demo-mention"> 114 <span class="demo-avatar">JK</span> 115 <div> 116 <div class="who"><b>@jared_k</b> <time>12s</time></div> 117 <span class="quote">"loving @xlift's draft quality"</span> 118 </div> 119 <span class="sent sent--pos" title="positive"></span> 120 </div> 121 <div class="demo-mention"> 122 <span class="demo-avatar">DM</span> 123 <div> 124 <div class="who"><b>@diana_m</b> <time>47s</time></div> 125 <span class="quote">"@xlift is wild for indie SaaS"</span> 126 </div> 127 <span class="sent sent--pos"></span> 128 </div> 129 <div class="demo-mention"> 130 <span class="demo-avatar">RP</span> 131 <div> 132 <div class="who"><b>@rachel_p</b> <time>2m</time></div>
133 <span class="quote">"swapped my VA for @xlift"</span> 134 </div> 135 <span class="sent sent--pos"></span> 136 </div> 137 <div class="demo-mention"> 138 <span class="demo-avatar">AS</span> 139 <div> 140 <div class="who"><b>@aron_s</b> <time>3m</time></div> 141 <span class="quote">"not sure about the auto mode"</span> 142 </div> 143 <span class="sent sent--neu"></span> 144 </div> 145 <div class="demo-mention"> 146 <span class="demo-avatar">TS</span> 147 <div> 148 <div class="who"><b>@tom_s</b> <time>5m</time></div> 149 <span class="quote">"my account survived. iykyk"</span> 150 </div> 151 <span class="sent sent--pos"></span> 152 </div> 153 <!-- duplicate for seamless loop --> 154 <div class="demo-mention"> 155 <span class="demo-avatar">JK</span> 156 <div> 157 <div class="who"><b>@jared_k</b> <time>12s</time></div> 158 <span class="quote">"loving @xlift's draft quality"</span> 159 </div> 160 <span class="sent sent--pos"></span> 161 </div> 162 <div class="demo-mention"> 163 <span class="demo-avatar">DM</span> 164 <div> 165 <div class="who"><b>@diana_m</b> <time>47s</time></div> 166 <span class="quote">"@xlift is wild for indie SaaS"</span> 167 </div> 168 <span class="sent sent--pos"></span> 169 </div> 170 <div class="demo-mention"> 171 <span class="demo-avatar">RP</span> 172 <div> 173 <div class="who"><b>@rachel_p</b> <time>2m</time></div> 174 <span class="quote">"swapped my VA for @xlift"</span> 175 </div> 176 <span class="sent sent--pos"></span> 177 </div> 178 <div class="demo-mention"> 179 <span class="demo-avatar">AS</span> 180 <div> 181 <div class="who"><b>@aron_s</b> <time>3m</time></div> 182 <span class="quote">"not sure about the auto mode"</span> 183 </div> 184 <span class="sent sent--neu"></span> 185 </div> 186 <div class="demo-mention"> 187 <span class="demo-avatar">TS</span> 188 <div> 189 <div class="who"><b>@tom_s</b> <time>5m</time></div> 190 <span class="quote">"my account survived. iykyk"</span> 191 </div> 192 <span class="sent sent--pos"></span> 193 </div> 194 </div> 195 </div> 196 <div class="demo-ticker-spark"> 197 <span style="--h:30%; --d:0s"></span> 198 <span style="--h:55%; --d:.1s"></span> 199 <span style="--h:38%; --d:.2s"></span> 200 <span style="--h:72%; --d:.3s"></span> 201 <span style="--h:48%; --d:.4s"></span> 202 <span style="--h:88%; --d:.5s"></span> 203 <span style="--h:65%; --d:.6s"></span> 204 <span style="--h:42%; --d:.7s"></span> 205 <span style="--h:60%; --d:.8s"></span> 206 <span style="--h:35%; --d:.9s"></span> 207 <span style="--h:78%; --d:1.0s"></span> 208 <span style="--h:55%; --d:1.1s"></span> 209 </div> 210 </div> 211 </article> 212 213 <article class="feature"> 214 <div class="feature-icon"> 215 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 216 <rect x="3" y="5" width="18" height="16" rx="2"/> 217 <path d="M3 9h18"/> 218 <path d="M8 3v4"/> 219 <path d="M16 3v4"/> 220 <circle cx="12" cy="14.5" r="2.5"/> 221 <path d="M12 13v1.5l1 1"/> 222 </svg> 223 </div> 224 <h3>Schedule posts</h3> 225 <p>Queue tweets and time them right. Ship a steady drumbeat without living inside
225X.</p> 226 <div class="demo-queue" aria-hidden="true"> 227 <div class="demo-hdr"> 228 <span>Next post in</span> 229 </div> 230 <div class="demo-queue-count"> 231 <span class="big" id="demo-countdown">14:32</span> 232 <span class="small">23 queued this week</span> 233 </div> 234 <div class="demo-queue-list"> 235 <div class="demo-queue-row is-next"> 236 <span class="demo-q-time">9:00 AM</span> 237 <span class="demo-q-text">"Shipped: voice profilesâ¦"</span> 238 <span class="demo-q-dot"></span> 239 </div> 240 <div class="demo-queue-row"> 241 <span class="demo-q-time">12:30 PM</span> 242 <span class="demo-q-text">"Hot take on indie growth"</span> 243 <span class="demo-q-dot"></span> 244 </div> 245 <div class="demo-queue-row"> 246 <span class="demo-q-time">5:00 PM</span> 247 <span class="demo-q-text">"Reply roundup of the day"</span> 248 <span class="demo-q-dot"></span> 249 </div> 250 </div> 251 </div> 252 </article> 253 254 <article class="feature"> 255 <div class="feature-icon"> 256 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 257 <path d="M14.5 4.5l5 5L9 20H4v-5z"/> 258 <path d="M13 6l5 5"/> 259 <path d="M19 4l1 1"/> 260 <path d="M21 7l-1-1"/> 261 <path d="M17 3l1 1"/> 262 </svg> 263 </div> 264 <h3>Editable AI drafts</h3> 265 <p>Every reply, post, and DM starts as a draft. Tweak it, send it â never raw bot output.</p> 266 <div class="demo-draft" aria-hidden="true"> 267 <div class="demo-hdr"><span>Draft preview</span></div> 268 <div class="demo-draft-body"> 269 <span class="demo-draft-text"></span><span class="demo-draft-caret"></span> 270 </div> 271 <div class="demo-draft-footer"> 272 <span class="demo-voice-check">Sounds like you</span> 273 <div class="demo-actions"> 274 <span class="demo-act">Edit</span> 275 <span class="demo-act demo-act--primary">Send</span> 276 </div> 277 </div> 278 </div> 279 </article> 280 281 <article class="feature"> 282 <div class="feature-icon"> 283 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 284 <rect x="2.5" y="8" width="19" height="8" rx="4"/> 285 <circle cx="8" cy="12" r="2.5" fill="currentColor"/> 286 <circle cx="16" cy="12" r="2.5"/> 287 </svg> 288 </div> 289 <h3>Auto & semi-auto</h3> 290 <p>Hand over the wheel or stay hands-on. Pick your mode per account, per task.</p> 291 <div class="demo-mode" aria-hidden="true"> 292 <div class="demo-toggle"> 293 <div class="demo-toggle-track"> 294 <span class="demo-toggle-thumb"></span> 295 <span class="demo-toggle-label demo-toggle-label--l">Semi</span> 296 <span class="demo-toggle-label demo-toggle-label--r">Auto</span> 297 </div> 298 </div> 299 <div class="demo-rules"> 300 <div class="demo-rule-set demo-rule-set--semi"> 301 <div class="demo-rule">Match your tone</div> 302 <div class="demo-rule">Review every send</div> 303 <div class="demo-rule is-off">Auto-publish</div> 304 </div> 305 <div class="demo-rule-set demo-rule-set--auto"> 306 <div class="demo-rule">Match your tone</div> 307 <div class="demo-rule">Topic guardrails</div> 308 <div class="demo-rule">Auto-publish</div> 309 </div> 310 </div> 311 </div> 312 </article> 313 314 <article class="feature"> 315 <div class="feature-icon"> 316 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 317 <path d="M21 12c0 4-4 7-9 7-1.4 0-2.8-.2-4-.7L3 20l1.5-3.5C3.5 15.3 3 13.7 3 12c0-4 4-7 9-7s9 3 9 7z"/> 318 <path d="M8 12h.01"/> 319 <path d="M12 12h.01"/> 320 <path d="M16 12h.01"/> 321 </svg> 322 </div> 323 <h3>Smart replies</h3> 324 <p>On-brand responses drafted the moment a conversation lights up. Keep showing up, faster.</p> 325 <div class="demo-replies" aria-hidden="true"> 326 <div class="demo-tweet">
327 <span class="demo-avatar">@</span> 328 <div> 329 <div class="head"><b>@founder</b> · 2m</div> 330 <div class="body">What's everyone using for X growth that actually works?</div> 331 </div> 332 </div> 333 <div class="demo-replies-list"> 334 <div class="demo-replies-label">Suggested replies</div> 335 <div class="demo-chip-item">Built mine differently â here's the thread</div> 336 <div class="demo-chip-item">Curious how you measure "working"?</div> 337 <div class="demo-chip-item">+1 for indie folks â DM you my stack</div> 338 </div> 339 </div> 340 </article> 341 342 <article class="feature"> 343 <div class="feature-icon"> 344 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 345 <path d="M3 5h18v11H8l-5 4z"/> 346 <path d="M8 9h8"/> 347 <path d="M8 12.5h5"/> 348 </svg> 349 </div> 350 <h3>Craft DMs in seconds</h3> 351 <p>Reply to leads, customers, and partners without writing the same message twice.</p> 352 <div class="demo-inbox" aria-hidden="true"> 353 <div class="demo-hdr"> 354 <b>Inbox</b> 355 <span><span id="demo-inbox-count">47</span> new</span> 356 </div> 357 <div class="demo-thread-list"> 358 <div class="demo-thread is-typing"> 359 <span class="demo-avatar">MB</span> 360 <div> 361 <div class="who"><span>@maria_b</span> <time>now</time></div> 362 <div class="preview"> 363 Typing reply 364 <span class="demo-typing-row"><span></span><span></span><span></span></span> 365 </div> 366 </div> 367 <span class="badge badge--hot">2</span> 368 </div> 369 <div class="demo-thread"> 370 <span class="demo-avatar">AP</span> 371 <div> 372 <div class="who"><span>@alex_p</span> <time>3m</time></div> 373 <div class="preview">"can you send the agency demo?"</div> 374 </div> 375 <span class="badge">1</span> 376 </div> 377 <div class="demo-thread"> 378 <span class="demo-avatar">SW</span> 379 <div> 380 <div class="who"><span>@sarah_w</span> <time>8m</time></div> 381 <div class="preview">"works perfectly â thanks!"</div> 382 </div> 383 <span class="badge">â</span> 384 </div> 385 </div> 386 </div> 387 </article> 388 </div> 389 </section> 390 391 <section class="modes" id="modes"> 392 <div class="section-head"> 393 <span class="section-eyebrow"><span class="num">02</span> Modes</span> 394 <h2><span class="tilt-word" data-tilt="two">Two</span> modes. You stay in <span class="tilt-word" data-tilt="control">control</span>.</h2> 395 <p>Pick the level of automation that fits the moment â switch any time.</p> 396 </div> 397 398 <div class="modes-grid"> 399 <article class="mode"> 400 <div class="mode-tag">Semi-auto</div> 401 <h3>Draft â review â send</h3> 402 <p>Xlift drafts the post, the reply, the DM. You read it, tweak it, send it. Nothing goes out without your touch.</p> 403 <ul class="mode-list"> 404 <li>Hand-on every send</li> 405 <li>Edit drafts in your voice</li> 406 <li>Best when reputation matters most</li> 407 </ul> 408 </article> 409 410 <article class="mode mode-auto"> 411 <div class="mode-tag">Full auto</div> 412 <h3>Rules â drafts â ship</h3> 413 <p>Set the topics, the tone, the guardrails. Xlift drafts and sends on its own â pausing for anything off-script.</p> 414 <ul class="mode-list"> 415 <li>Hands-off coverage 24/7</li> 416 <li>Topic + tone guardrails you define</li> 417 <li>Best for reach without burnout</li> 418 </ul> 419 </article> 420 </div> 421 </section> 422 423 <section class="toolkit" id="toolkit"> 424 <div class="section-head">
425 <span class="section-eyebrow"><span class="num">03</span> The toolkit</span> 426 <h2>The full toolkit, not just a reply button</h2> 427 <p>Most "AI for X" tools stop at one trick. Xlift runs your whole presence â inbox, prospecting, triggers, analytics, audit. Everything you'd hire an intern to do, on your terms.</p> 428 </div> 429 430 <div class="toolkit-grid"> 431 <article class="toolkit-card"> 432 <div class="toolkit-icon"> 433 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 434 <path d="M3 5h18v11H8l-5 4z"/> 435 <circle cx="8.5" cy="10.5" r="1" fill="currentColor"/> 436 <circle cx="12.5" cy="10.5" r="1" fill="currentColor"/> 437 <circle cx="16.5" cy="10.5" r="1" fill="currentColor"/> 438 </svg> 439 </div> 440 <h3>Unified inbox</h3> 441 <p>Every DM, every account, one feed. Triage with our AI sorting "needs reply" from "noise" â answer where it matters, ignore where it doesn't. No more tab-juggling between brands.</p> 442 </article> 443 444 <article class="toolkit-card"> 445 <div class="toolkit-icon"> 446 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 447 <path d="M12 2v6"/> 448 <path d="M12 16v6"/> 449 <path d="M4.93 4.93l4.24 4.24"/> 450 <path d="M14.83 14.83l4.24 4.24"/> 451 <path d="M2 12h6"/> 452 <path d="M16 12h6"/> 453 <path d="M4.93 19.07l4.24-4.24"/> 454 <path d="M14.83 9.17l4.24-4.24"/> 455 </svg> 456 </div> 457 <h3>Smart triggers</h3> 458 <p>"When a new follower mentions SaaS, send DM." "When @competitor announces a feature, draft a reply." Build the rules once â Xlift watches X for you 24/7 and acts the moment it matters.</p> 459 </article> 460 461 <article class="toolkit-card"> 462 <div class="toolkit-icon"> 463 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 464 <circle cx="9" cy="8" r="3.5"/> 465 <path d="M2.5 19c.7-3.5 3.4-5.5 6.5-5.5s5.8 2 6.5 5.5"/> 466 <path d="M18 8h4"/> 467 <path d="M20 6v4"/> 468 </svg> 469 </div> 470 <h3>Prospect lists</h3> 471 <p>Build target audiences from X searches, replies to a tweet, or any competitor's follower list. Engage them on a cadence, track who's warm, convert without ever leaving X.</p> 472 </article> 473 474 <article class="toolkit-card"> 475 <div class="toolkit-icon"> 476 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 477 <path d="M3 3v18h18"/> 478 <path d="M7 14l4-4 4 3 5-6"/> 479 </svg> 480 </div> 481 <h3>Growth analytics</h3> 482 <p>Which replies pulled engagement? Which times work for your audience? Which topic flips the algorithm? Xlift shows you what's working so you stop guessing and start doubling down.</p> 483 </article> 484 485 <article class="toolkit-card"> 486 <div class="toolkit-icon"> 487 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 488 <path d="M12 2l8 4v6c0 5-3.5 9-8 10-4.5-1-8-5-8-10V6z"/> 489 <path d="M9 12l2 2 4-4"/> 490 </svg> 491 </div> 492 <h3>Account health</h3> 493 <p>Daily caps per action type. Working-hour limits. Pacing that mimics human rhythm. Per-account health scores that flag risk before X does. The "won't get my account suspended" feature.</p> 494 </article> 495 496 <article class="toolkit-card"> 497 <div class="toolkit-icon"> 498 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 499 <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> 500 <path d="M14 2v6h6"/> 501 <path d="M8 13h8"/> 502 <path d="M8 17h6"/> 503 <path d="M8 9h2"/> 504 </svg> 505 </div> 506 <h3>Full audit trail</h3> 507 <p>Every action Xlift took on your behalf â when, why, the exact text sent. Filter, search, export. Nothing happens in your name without a receipt you can review.</p> 508 </article> 509 </div> 510 </section> 511 512 <section class="safety" id="safety"> 513 <div class="section-head">
514 <span class="section-eyebrow"><span class="num">04</span> Safety</span> 515 <h2>Built to keep your account alive</h2> 516 <p>X has rules. Suspensions are real. Xlift defaults to caution â and lets you tighten the leash even more.</p> 517 </div> 518 519 <div class="safety-grid"> 520 <article class="safety-pill"> 521 <div class="safety-pill-icon"> 522 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 523 <rect x="3" y="11" width="18" height="11" rx="2"/> 524 <path d="M7 11V7a5 5 0 0 1 10 0v4"/> 525 </svg> 526 </div> 527 <div> 528 <h4>Encrypted cookie vault</h4> 529 <p>X session tokens encrypted at rest. Stored tokens are never sent to an AI. Wiped on disconnect.</p> 530 </div> 531 </article> 532 533 <article class="safety-pill"> 534 <div class="safety-pill-icon"> 535 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 536 <circle cx="12" cy="12" r="9"/> 537 <path d="M12 7v5l3 2"/> 538 </svg> 539 </div> 540 <div> 541 <h4>Working-hour limits</h4> 542 <p>Activity only during your normal hours, in your timezone. Weekends throttled. No 3am replies that scream bot.</p> 543 </div> 544 </article> 545 546 <article class="safety-pill"> 547 <div class="safety-pill-icon"> 548 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 549 <path d="M12 2L4 6v6c0 5 3.5 9 8 10 4.5-1 8-5 8-10V6z"/> 550 </svg> 551 </div> 552 <div> 553 <h4>Hard rules you write</h4> 554 <p>"Never say AI-powered." "Never mention competitors." Xlift respects them absolutely â no overrides, no exceptions.</p> 555 </div> 556 </article> 557 558 <article class="safety-pill"> 559 <div class="safety-pill-icon"> 560 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 561 <rect x="6" y="4" width="4" height="16" rx="1"/> 562 <rect x="14" y="4" width="4" height="16" rx="1"/> 563 </svg> 564 </div> 565 <div> 566 <h4>One-click pause</h4> 567 <p>Going off-script for a week? Paying down rate-limit risk? Pause everything in a tap. Resume when you want.</p> 568 </div> 569 </article> 570 571 <article class="safety-pill"> 572 <div class="safety-pill-icon"> 573 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 574 <circle cx="12" cy="12" r="9"/> 575 <path d="M4.93 4.93l14.14 14.14"/> 576 </svg> 577 </div> 578 <div> 579 <h4>Blocklist</h4> 580 <p>Accounts you'll never engage with. Keywords you'll never touch. Topics that aren't yours. Xlift stays out.</p> 581 </div> 582 </article> 583 584 <article class="safety-pill"> 585 <div class="safety-pill-icon"> 586 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> 587 <path d="M12 3v18"/> 588 <path d="M3 12h18"/> 589 <circle cx="12" cy="12" r="9"/> 590 </svg> 591 </div> 592 <div> 593 <h4>Bring your own AI key</h4> 594 <p>Use Xlift's shared Gemini key, or paste your own. Your prompts, your billing, your privacy posture.</p> 595 </div> 596 </article> 597 </div> 598 </section> 599 600 <section class="pull-quote" aria-label="The pitch"> 601 <span class="pull-quote-mark">"</span> 602 <p class="pull-quote-body"> 603 Most "AI for X" tools sound like ChatGPT in a wig. <em>Xlift sounds like you</em> â 604 because you taught it who you are, what you sell, and what you'd never say. 605 </p> 606 <p class="pull-quote-attr">The Xlift difference</p> 607 </section> 608 609 <section class="testimonials" id="proof"> 610 <div class="section-head">
611 <span class="section-eyebrow"><span class="num">05</span> Built for builders</span> 612 <h2>Founders are quietly outshipping their team â using Xlift.</h2> 613 <p>Indie operators and small teams growing on X without losing nights and weekends.</p> 614 </div> 615 <div class="testimonials-grid"> 616 <article class="testimonial"> 617 <p class="testimonial-quote">Replaced the VA I was paying $1.4k/mo for X engagement. Xlift's drafts sound more like me than the VA's did. Cancel-anytime cost me half a day's MRR.</p> 618 <div class="testimonial-author"> 619 <span class="testimonial-avatar">DM</span> 620 <div> 621 <div class="testimonial-name">Diana M.</div> 622 <div class="testimonial-role">Founder, indie SaaS · 12k followers</div> 623 </div> 624 </div> 625 </article> 626 627 <article class="testimonial"> 628 <p class="testimonial-quote">Triggers + prospect lists is the killer combo. I haven't manually scrolled X in 3 weeks. My DMs are answered, my replies are landing, and I shipped two features in the time I would've spent doomscrolling.</p> 629 <div class="testimonial-author"> 630 <span class="testimonial-avatar">JK</span> 631 <div> 632 <div class="testimonial-name">Jared K.</div> 633 <div class="testimonial-role">CTO, AI tooling startup · 38k followers</div> 634 </div> 635 </div> 636 </article> 637 638 <article class="testimonial"> 639 <p class="testimonial-quote">Tried 4 other "AI for X" tools first. They all wrote like ChatGPT pretending to be a human. Xlift's brand profile actually captures my voice â direct, no fluff, no emoji vomit.</p> 640 <div class="testimonial-author"> 641 <span class="testimonial-avatar">RP</span> 642 <div> 643 <div class="testimonial-name">Rachel P.</div> 644 <div class="testimonial-role">Solo founder, dev tools · 6k followers</div> 645 </div> 646 </div> 647 </article> 648 649 <article class="testimonial"> 650 <p class="testimonial-quote">My account survived. That's the headline. Tried similar tools, got suspended twice. Xlift's rate caps and working-hour limits are the difference between a tool that works and one that gets you banned.</p> 651 <div class="testimonial-author"> 652 <span class="testimonial-avatar">TS</span> 653 <div> 654 <div class="testimonial-name">Tom S.</div> 655 <div class="testimonial-role">Marketing lead · agency, 4 brands</div> 656 </div> 657 </div> 658 </article> 659 </div> 660 </section> 661 662 <section class="peek" aria-hidden="true"> 663 <img src="/logo.svg" class="peek-mascot" alt="" /> 664 <div class="peek-line"></div> 665 </section> 666 667 <section class="how" id="how"> 668 <div class="section-head"> 669 <span class="section-eyebrow"><span class="num">06</span> How it works</span> 670 <h2>Three steps to a louder presence</h2> 671 </div> 672 <ol class="steps"> 673 <li> 674 <span class="step-num">1</span> 675 <div> 676 <h4>Sign up for Xlift</h4> 677 <p>Create your account and connect the X accounts you want to grow.</p> 678 </div> 679 </li> 680 <li> 681 <span class="step-num">2</span> 682 <div> 683 <h4>Pick your topics</h4> 684 <p>Tell Xlift what you care about. It watches X for the conversations that match.</p> 685 </div> 686 </li> 687 <li> 688 <span class="step-num">3</span> 689 <div> 690 <h4>Reply with intention</h4> 691 <p>Get drafts in your voice. Tweak, send, and build a brand one real reply at a time.</p> 692 </div> 693 </li> 694 </ol> 695 </section> 696 697 <section class="pricing" id="pricing"> 698 <div class="section-head"> 699 <span class="section-eyebrow"><span class="num">07</span> Pricing</span> 700 <h2>Simple pricing. Pick your reach.</h2> 701 <p>Start small or go all-in. Cancel any time, no questions.</p> 702 </div> 703 704 <div class="pricing-grid"> 705 <article class="plan"> 706 <div class="plan-head"> 707 <h3>Starter</h3> 708 <p class="plan-tagline">Solo operators getting consistent.</p> 709 </div> 710 <div class="plan-price">
711 <span class="plan-amount">$39</span> 712 <span class="plan-cadence">/ month</span> 713 </div> 714 <ul class="plan-list"> 715 <li>1 X account</li> 716 <li>Compose, threads, intent feed</li> 717 <li>Assisted DM Co-pilot</li> 718 <li>Auto-Posts, DM Outreach, Follow/Unfollow</li> 719 <li>3 triggers · 1 smart list · 1 competitor watch</li> 720 <li>Lead scraper + AI scoring</li> 721 <li>Bulk DM from intent</li> 722 </ul> 723 <a href="https://app.xlift.ai/signup" class="btn btn-ghost btn-block">Start with Starter</a> 724 </article> 725 726 <article class="plan plan-featured"> 727 <div class="plan-badge">Most popular</div> 728 <div class="plan-head"> 729 <h3>Pro</h3> 730 <p class="plan-tagline">Daily outreach across a few accounts.</p> 731 </div> 732 <div class="plan-price"> 733 <span class="plan-amount">$99</span> 734 <span class="plan-cadence">/ month</span> 735 </div> 736 <ul class="plan-list"> 737 <li>Up to 5 X accounts</li> 738 <li>Everything in Starter</li> 739 <li>Full-auto DM Co-pilot</li> 740 <li>Auto-Engage · Bait · Sequence campaigns</li> 741 <li>10 triggers · 5 smart lists · 3 competitor watches</li> 742 <li>Audience snapshots + Discover (30/day)</li> 743 <li>Priority chat support</li> 744 </ul> 745 <a href="https://app.xlift.ai/signup" class="btn btn-invert btn-block">Go Pro</a> 746 </article> 747 748 <article class="plan"> 749 <div class="plan-head"> 750 <h3>Agency</h3> 751 <p class="plan-tagline">Agencies + volume operators.</p> 752 </div> 753 <div class="plan-price"> 754 <span class="plan-amount">$249</span> 755 <span class="plan-cadence">/ month</span> 756 </div> 757 <ul class="plan-list"> 758 <li>Up to 15 X accounts</li> 759 <li>Everything in Pro</li> 760 <li>Unlimited triggers, smart lists, competitors</li> 761 <li>Unlimited Discover</li> 762 <li>Higher daily caps per account</li> 763 <li>Dedicated success manager</li> 764 </ul> 765 <a href="https://app.xlift.ai/signup" class="btn btn-ghost btn-block">Choose Agency</a> 766 </article> 767 </div> 768 <p class="pricing-foot"> 769 Need more accounts? Top up extra slots at $15/mo each from the in-app Billing page. AI credit packs ($15 for 500) when you need more generations. 770 </p> 771 </section> 772 773 <section class="cta" id="install"> 774 <img src="/logo.svg" alt="" class="cta-float cta-float--1" aria-hidden="true" /> 775 <img src="/logo.svg" alt="" class="cta-float cta-float--2" aria-hidden="true" /> 776 <img src="/logo.svg" alt="" class="cta-float cta-float--3" aria-hidden="true" /> 777 <img src="/logo.svg" alt="" class="cta-float cta-float--4" aria-hidden="true" /> 778 <div class="cta-inner"> 779 <img src="/logo.svg" alt="" class="cta-mark" /> 780 <h2>Ready to show up where it matters?</h2> 781 <p>Sign up for Xlift and start growing on X today.</p> 782 <a href="https://app.xlift.ai/signup" class="btn btn-primary btn-lg">Get started â from $39/mo</a> 783 </div> 784 </section> 785 </main> 786 787 <footer class="footer"> 788 <div class="footer-articles"> 789 <div class="footer-articles-head">Xlift for</div> 790 <ul class="footer-articles-grid"> 791 <li><a href="/for/indie-saas-founders.html">Indie SaaS founders</a></li> 792 <li><a href="/for/shopify-dev-shops.html">Shopify dev shops</a></li> 793 <li><a href="/for/b2b-marketers.html">B2B marketers</a></li> 794 <li><a href="/for/solo-agencies.html">Solo agencies</a></li> 795 <li><a href="/for/content-creators.html">Content creators</a></li> 796 <li><a href="/for/ai-tooling-startups.html">AI tooling startups</a></li> 797 <li><a href="/for/dtc-brands.html">DTC brands</a></li> 798 <li><a href="/for/community-builders.html">Community builders</a></li> 799 <li><a href="/for/solo-consultants.html">Solo consultants</a></li> 800 <li><a href="/for/newsletter-writers.html">Newsletter writers</a></li> 801 <li><a href="/for/course-creators.html">Course creators</a></li> 802 <li><a href="/for/coaches-and-advisors.html">Coaches and advisors</a></li> 803 <li><a href="/for/vcs-and-scouts.html">VCs and scouts</a></li> 804 <li><a href="/for/recruiters.html">Recruiters</a></li> 805 <li><a href="/for/sales-teams.html">Sales teams</a></li> 806 <li><a href="/for/web3-founders.html">Web3 founders</a></li> 807 <li><a href="/for/open-source-maintainers.html">Open source maintainers</a></li> 808 <li><a href="/for/bootcamp-grads.html">Bootcamp grads</a></li> 809 <li><a href="/for/designers.html">Designers</a></li> 810 <li><a href="/for/developers.html">Developers</a></li> 811 </ul> 812 </div> 813 <div class="footer-articles"> 814 <div class="footer-articles-head">
814Resources</div> 815 <ul class="footer-articles-grid"> 816 <li><a href="/blog/how-to-grow-on-x-without-sounding-like-a-bot.html">How to grow on X without sounding like a bot</a></li> 817 <li><a href="/blog/2026-guide-to-x-dm-outreach.html">The 2026 guide to X DM outreach</a></li> 818 <li><a href="/blog/best-time-to-post-on-x.html">Best time to post on X (and why "morning" is wrong)</a></li> 819 <li><a href="/blog/ai-replies-that-dont-read-like-chatgpt.html">AI replies that don't read like ChatGPT</a></li> 820 <li><a href="/blog/avoiding-x-account-suspensions.html">Avoiding X account suspensions: a safety playbook</a></li> 821 <li><a href="/blog/x-growth-for-indie-saas-founders.html">X growth for indie SaaS founders</a></li> 822 <li><a href="/blog/reply-strategy-flywheel.html">Reply strategy: the underrated growth flywheel</a></li> 823 <li><a href="/blog/building-brand-voice-for-ai.html">Building a brand voice an AI can actually use</a></li> 824 <li><a href="/blog/smart-triggers-respond-fast.html">Smart triggers: responding to mentions in seconds</a></li> 825 <li><a href="/blog/multi-account-x-management.html">Multi-account X management for agencies</a></li> 826 <li><a href="/blog/x-automation-safety-checklist.html">The X automation safety checklist</a></li> 827 <li><a href="/blog/from-100-to-10k-followers.html">From 100 to 10k followers on X: the playbook</a></li> 828 <li><a href="/blog/tweet-length-short-long-thread.html">Tweet length: short, long, or thread?</a></li> 829 <li><a href="/blog/scheduling-tweets-queue-strategy.html">Scheduling tweets: the queue strategy that works</a></li> 830 <li><a href="/blog/x-cold-dms-rules-and-templates.html">X cold DMs: rules, templates, and red flags</a></li> 831 <li><a href="/blog/hands-on-vs-hands-off-auto-mode.html">Hands-on vs hands-off: choosing your auto mode</a></li> 832 <li><a href="/blog/working-hours-pacing-safe-automation.html">Working hours and pacing for safe automation</a></li> 833 <li><a href="/blog/xlift-vs-hypefury-vs-tweet-hunter.html">Xlift vs Hypefury vs Tweet Hunter: honest comparison</a></li> 834 <li><a href="/blog/why-your-hot-takes-arent-landing.html">Why your hot takes aren't landing</a></li> 835 <li><a href="/blog/ai-in-your-x-workflow.html">The honest case for AI in your X workflow</a></li> 836 </ul> 837 </div> 838 <div class="footer-inner"> 839 <div class="footer-brand"> 840 <img src="/logo.svg" alt="Xlift" class="footer-mark" /> 841 <span>Xlift</span> 842 </div> 843 <div class="footer-links"> 844 <a href="/terms.html">Terms</a> 845 <a href="/privacy.html">Privacy</a> 846 <a href="mailto:[email protected]">[email protected]</a> 847 </div> 848 <p class="footer-meta">© 2026 Xlift · Built for people who want to grow on X without losing their voice.</p> 849 </div> 850 </footer> 851 852
852<script> 853 // âââ Use-case hero swap ââââââââââââââââââââââââââââââââââââââââââââ 854 // ?for=<slug> in the URL rewrites the hero title + subtitle. Same 855 // landing page, segmented copy. Each entry's `h1` keeps the 856 // <span class="x-mark">X</span> pill for visual consistency. 857 const USE_CASES = { 858 'indie-saas-founders': { 859 h1: 'Grow your indie SaaS on <span class="x-mark">X</span>.', 860 sub: "Reply to the conversations where your buyers actually hang out â without becoming a full-time content creator.", 861 }, 862 'shopify-dev-shops': { 863 h1: 'Win Shopify clients on <span class="x-mark">X</span>.', 864 sub: "Track agency conversations, jump into competitor mentions, and turn replies into inbound leads.", 865 }, 866 'b2b-marketers': { 867 h1: 'Run X campaigns that actually convert.', 868 sub: "Brand tracking, lead scraping, DM outreach â all in your voice, all on autopilot when you want.", 869 }, 870 'solo-agencies': { 871 h1: 'Run your solo agency on <span class="x-mark">X</span>.', 872 sub: "Reply to leads, send DMs, ship content â without hiring a content manager you can't afford.", 873 }, 874 'content-creators': { 875 h1: 'Show up on <span class="x-mark">X</span> without burning out.', 876 sub: "Drafts in your voice, replies on schedule, audit log of everything. Be present without being online.", 877 }, 878 'ai-tooling-startups': { 879 h1: 'Grow your AI startup on <span class="x-mark">X</span>.', 880 sub: "Talk to founders in your niche, get noticed by builders, and convert browsers into early users.", 881 }, 882 'dtc-brands': { 883 h1: 'Build your DTC brand on <span class="x-mark">X</span>.', 884 sub: "Reply to customer questions in seconds, catch every brand mention, turn fans into superfans.", 885 }, 886 'community-builders': { 887 h1: 'Grow your community on <span class="x-mark">X</span>.', 888 sub: "Spot every mention. Welcome every new member. Build the relationships that actually compound.", 889 }, 890 'solo-consultants': { 891 h1: 'Win clients on <span class="x-mark">X</span> without cold pitching.', 892 sub: "Reply your way to inbound. Catch keyword mentions. Convert without sounding like a sales bot.", 893 }, 894 'newsletter-writers': { 895 h1: 'Grow your newsletter on <span class="x-mark">X</span>.', 896 sub: "Turn replies into subscribers. Surface conversations where your topic matters. Without all-day scrolling.", 897 }, 898 'course-creators': { 899 h1: 'Sell your course on <span class="x-mark">X</span>.', 900 sub: "Show up where your students hang out, reply to questions, convert lurkers â without pitching every tweet.", 901 }, 902 'coaches-and-advisors': { 903 h1: 'Build your coaching practice on <span class="x-mark">X</span>.', 904 sub: "Get visible without performing. Reply with substance. Earn clients through real conversation.", 905 }, 906 'vcs-and-scouts': { 907 h1: 'Find founders worth backing on <span class="x-mark">X</span>.', 908 sub: "Scrape leads, track competitor moves, DM with intent. The intel layer for venture work on X.", 909 }, 910 'recruiters': { 911 h1: 'Hire on <span class="x-mark">X</span> without cold spam.', 912 sub: "Find talent through real engagement. DMs that get replies. Pipeline without burning your account.", 913 }, 914 'sales-teams': { 915 h1: 'Run <span class="x-mark">X</span> outreach at scale, safely.', 916 sub: "Brand-voiced DMs, audit logs, opt-out detection. Modern social selling that won't get you suspended.", 917 }, 918 'web3-founders': { 919 h1: 'Build your web3 brand on <span class="x-mark">X</span>.', 920 sub: "Engage your community 24/7. Reply to the moments that matter. Without sounding like an alpha bot.", 921 }, 922 'open-source-maintainers': { 923 h1: 'Grow your OSS project on <span class="x-mark">X</span>.', 924 sub: "Catch every mention of your library. Reply to discu
924ssion threads. Convert browsers into contributors.", 925 }, 926 'bootcamp-grads': { 927 h1: 'Launch your career on <span class="x-mark">X</span>.', 928 sub: "Get noticed by hiring managers. Reply your way into networks. Build a public portfolio of takes.", 929 }, 930 'designers': { 931 h1: 'Grow your design audience on <span class="x-mark">X</span>.', 932 sub: "Show your work, engage in critique threads, convert followers into clients â without becoming a content creator.", 933 }, 934 'developers': { 935 h1: 'Build your dev brand on <span class="x-mark">X</span>.', 936 sub: "Reply to technical discussions, share your work, turn followers into hires or customers. Stay in flow.", 937 }, 938 }; 939 (function () { 940 const params = new URLSearchParams(window.location.search); 941 const slug = params.get('for'); 942 if (!slug) return; 943 const uc = USE_CASES[slug]; 944 if (!uc) return; 945 const titleEl = document.querySelector('.hero-title'); 946 const subEl = document.querySelector('.hero-sub'); 947 if (titleEl) titleEl.innerHTML = uc.h1; 948 if (subEl) subEl.textContent = uc.sub; 949 // Also update <title> so the tab + share previews reflect the variant 950 const niceLabel = slug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); 951 document.title = 'Xlift for ' + niceLabel; 952 })(); 953 954 const peekSection = document.querySelector('.peek'); 955 const peekMascot = document.querySelector('.peek-mascot'); 956 const peekLine = document.querySelector('.peek-line'); 957 const revealPeek = () => { 958 peekMascot && peekMascot.classList.add('is-in'); 959 peekLine && peekLine.classList.add('is-in'); 960 }; 961 if (peekSection && 'IntersectionObserver' in window) { 962 const obs = new IntersectionObserver((entries) => { 963 entries.forEach((e) => { 964 if (e.isIntersecting) { 965 revealPeek(); 966 obs.disconnect(); 967 } 968 }); 969 }, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' }); 970 obs.observe(peekSection); 971 } else { 972 revealPeek(); 973 } 974 975 // Mascot rain on the pull-quote section. Spawns 20 mascots that drop 976 // from above the section, accelerate under gravity, overshoot, bounce, 977 // and settle at the bottom. Fires once when the section enters view. 978 const pullQuote = document.querySelector('.pull-quote'); 979 function spawnDropMascots(container, count) { 980 const rect = container.getBoundingClientRect(); 981 const sectionH = rect.height; 982 for (let i = 0; i < count; i++) { 983 const m = document.createElement('img'); 984 m.src = 'logo.svg'; 985 m.alt = ''; 986 m.className = 'drop-mascot'; 987 const size = 22 + Math.random() * 26; // 22â48px 988 const endYPx = sectionH - size - 12; // sit slightly above bottom 989 m.style.setProperty('--size', size + 'px'); 990 m.style.setProperty('--x', (Math.random() * 92 + 1) + '%'); 991 m.style.setProperty('--end-y', endYPx + 'px'); 992 m.style.setProperty('--rot', (Math.random() * 540 - 270) + 'deg'); 993 m.style.setProperty('--dur', (1.6 + Math.random() * 1.1).toFixed(2) + 's'); 994 m.style.setProperty('--delay', (Math.random() * 1.3).toFixed(2) + 's'); 995 container.appendChild(m); 996 } 997 } 998 if (pullQuote && 'IntersectionObserver' in window) { 999 const obs2 = new IntersectionObserver((entries) => { 1000 entries.forEach((e) => { 1001 if (e.isIntersecting) { 1002 spawnDropMascots(pullQuote, 20); 1003 obs2.disconnect(); 1004 } 1005 }); 1006 }, { threshold: 0.25 }); 1007 obs2.observe(pullQuote); 1008 } 1009 1010 // âââ Random mascot easter eggs ââââââââââââââââââââââââââââââââââââ 1011 // Every 5â11 seconds, pick a currently-visible card and have a mascot
1012 // peek out from one of its four corners. The card sits z-index 1, the 1013 // mascot z-index 0 within the same section â so part of the mascot is 1014 // visibly hidden behind the card, the rest pokes out. 1015 const peekRandSelector = [ 1016 '.features-grid > .feature', 1017 '.modes-grid > .mode', 1018 '.pricing-grid > .plan', 1019 '.toolkit-grid > .toolkit-card', 1020 '.safety-grid > .safety-pill', 1021 '.testimonials-grid > .testimonial', 1022 ].join(', '); 1023 1024 function isInViewportSlack(el) { 1025 const r = el.getBoundingClientRect(); 1026 // Slack: cards just-above or just-below the viewport are eligible, 1027 // so a peek can already be there when the user scrolls into a section. 1028 return r.bottom > -200 && r.top < window.innerHeight + 200; 1029 } 1030 1031 // Mole-style peek: every spawn pops UP out of a "hole" along a random 1032 // card's top edge, holds, then drops back down into the same hole. 1033 // Pure vertical motion â same path up & down. The card's solid bg 1034 // (z-index 1) covers the mascot (z-index 0) in its tucked state. 1035 function spawnRandomPeek() { 1036 const all = Array.from(document.querySelectorAll(peekRandSelector)); 1037 const visible = all.filter(isInViewportSlack); 1038 if (!visible.length) return; 1039 const host = visible[Math.floor(Math.random() * visible.length)]; 1040 const section = host.closest('section'); 1041 if (!section) return; 1042 1043 const sectionRect = section.getBoundingClientRect(); 1044 const hostRect = host.getBoundingClientRect(); 1045 const relTop = hostRect.top - sectionRect.top; 1046 const relLeft = hostRect.left - sectionRect.left; 1047 const w = hostRect.width; 1048 1049 const size = 40 + Math.random() * 20; // 40â60px 1050 const outside = size * 0.7; // ~70% of the mascot pokes out above the card edge 1051 1052 // X: random position along the card's top edge, with side margins so 1053 // the mole isn't cut off by adjacent cards / section edges. 1054 const xMargin = 16; 1055 const left = relLeft + xMargin + Math.random() * Math.max(0, w - size - xMargin * 2); 1056 const top = relTop - outside; // popped-up resting Y 1057 const hiddenY = outside + 4; // distance below to be fully tucked 1058 1059 // Subtle head tilt only â mole is curious, not crooked. 1060 const rot = (Math.random() * 12 - 6).toFixed(1);
1060 // ±6° 1061 1062 const mascot = document.createElement('img'); 1063 mascot.src = 'logo.svg'; 1064 mascot.alt = ''; 1065 mascot.className = 'peek-rand'; 1066 // Dark-on-dark cards need an inverted mascot so it reads. 1067 const hostBg = getComputedStyle(host).backgroundColor; 1068 const isDark = /rgb\(\s*(0|1[0-9]|2[0-9])\s*,/.test(hostBg); 1069 if (isDark) mascot.classList.add('peek-rand-dark'); 1070 1071 mascot.style.top = top + 'px'; 1072 mascot.style.left = left + 'px'; 1073 mascot.style.setProperty('--size', size + 'px'); 1074 mascot.style.setProperty('--rot', rot + 'deg'); 1075 mascot.style.setProperty('--hidden-y', hiddenY + 'px'); 1076 section.appendChild(mascot); 1077 1078 const visibleMs = 1800 + Math.random() * 1600; // 1.8â3.4s peeked out 1079 setTimeout(() => { 1080 mascot.classList.add('is-leaving'); 1081 setTimeout(() => mascot.remove(), 500); 1082 }, visibleMs); 1083 } 1084 1085 function scheduleNextPeek() { 1086 const delay = 5000 + Math.random() * 6000; // 5â11s 1087 setTimeout(() => { 1088 spawnRandomPeek(); 1089 scheduleNextPeek(); 1090 }, delay); 1091 } 1092 // First peek a touch later so the page settles before easter eggs start. 1093 if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) { 1094 setTimeout(scheduleNextPeek, 3500); 1095 } 1096 1097 // âââ Modes section choreography âââââââââââââââââââââââââââââââââââââ 1098 // Once the Modes section enters view, a mascot pops up on the left 1099 // card, jumps onto the word "Two" (title tilts clockwise â that side 1100 // is heavier), waits, jumps onto "control" (title tilts the other 1101 // way), waits, then hops off and fades. 1102 const modesSection = document.querySelector('.modes'); 1103 function waitMs(ms) { return new Promise((r) => setTimeout(r, ms)); } 1104 1105 function getStandPos(el, size, overlap = 6) { 1106 const r = el.getBoundingClientRect(); 1107 return { 1108 x: window.scrollX + r.left + r.width / 2 - size / 2, 1109 // Stand on TOP of the element; overlap by a few px so it reads as 1110 // physically resting on the surface, not floating above it. 1111 y: window.scrollY + r.top - size + overlap, 1112 }; 1113 } 1114 1115 async function jumpArc(mascot, from, to, opts = {}) { 1116 // Smooth natural-jump parabola. Instead of a single midpoint 1117 // keyframe + opposing easings (which forms a triangle/sharp shape), 1118 // sample the curve at many points and let linear interpolation 1119 // between them trace the actual parabola. 1120 const apexH = opts.apex || 80; 1121 const fromRot = opts.fromRot ?? 0; 1122 const toRot = opts.toRot ?? 0; 1123 const steps = 14; 1124 const kfs = []; 1125 for (let i = 0; i <= steps; i++) { 1126 const t = i / steps; 1127 // Linear horizontal + linear baseline Y, minus parabolic lift. 1128 const x = from.x + (to.x - from.x) * t; 1129 const baselineY = from.y + (to.y - from.y) * t; 1130 const lift = apexH * (1 - 4 * Math.pow(t - 0.5, 2)); // 0 at ends, apexH at t=0.5 1131 const y = baselineY - Math.max(0, lift); 1132 // Rotation interpolated linearly + small forward lean at apex. 1133 const rotLerp = fromRot + (toRot - fromRot) * t; 1134 const lean = -10 * Math.max(0, lift / apexH); // peaks at apex, eases to 0 at ends 1135 // Tiny squash on the last 8% of the path so the landing reads. 1136 const scale = (t > 0.92) ? (1 - (t - 0.92) * 0.6) : 1; 1137 kfs.push({ 1138 transform: `translate(${x.toFixed(1)}px, ${y.toFixed(1)}px) rotate(${(rotLerp + lean).toFixed(1)}deg) scale(${scale.toFixed(3)})`, 1139 offset: t, 1140 }); 1141 } 1142 // Final settle keyframe (un-squash back to scale 1) 1143 kfs[kfs.length - 1] = { 1144 transform: `translate(${to.x.toFixed(1)}px, ${to.y.toFixed(1)}px) rotate(${toRot}deg) scale(1)`, 1145 offset: 1, 1146 }; 1147 const anim = mascot.animate(kfs, { 1148 duration: opts.duration || 760, 1149 easing: 'linear', // the keyframe samples ARE the curve 1150 fill: 'forwards', 1151 }); 1152 await anim.finished;
1153 try { anim.commitStyles(); anim.cancel(); } 1154 catch { mascot.style.transform = `translate(${to.x}px, ${to.y}px) rotate(${toRot}deg) scale(1)`; } 1155 } 1156 1157 async function runModesChoreography() { 1158 if (!modesSection) return; 1159 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; 1160 1161 const h2 = modesSection.querySelector('h2'); 1162 const leftCard = modesSection.querySelector('.modes-grid > .mode'); 1163 const twoWord = h2 && h2.querySelector('[data-tilt="two"]'); 1164 const controlWord = h2 && h2.querySelector('[data-tilt="control"]'); 1165 if (!h2 || !leftCard || !twoWord || !controlWord) return; 1166 1167 const SIZE = 56; 1168 const mascot = document.createElement('img'); 1169 mascot.src = 'logo.svg'; 1170 mascot.alt = ''; 1171 mascot.className = 'modes-mascot'; 1172 document.body.appendChild(mascot); 1173 1174 // Snapshot positions ONCE up front (so layout shifts during animation 1175 // don't break things â the section is in view, won't reflow on us). 1176 // overlap = how much the mascot's bounding box dips below its target's 1177 // top edge. The SVG has invisible bottom padding inside its viewBox, 1178 // so we push deeper into the word than the card to keep the "feet" 1179 // visually planted on the text. 1180 let pLeft = getStandPos(leftCard, SIZE, 8); 1181 let pTwo = getStandPos(twoWord, SIZE, 18); 1182 let pControl = getStandPos(controlWord, SIZE, 18); 1183 1184 // 1. Pop in on the left card â fade + spring-scale 1185 mascot.style.transform = `translate(${pLeft.x}px, ${pLeft.y}px) scale(0.3)`; 1186 mascot.style.opacity = '0'; 1187 await new Promise(requestAnimationFrame); 1188 const popIn = mascot.animate([ 1189 { transform: `translate(${pLeft.x}px, ${pLeft.y}px) rotate(0deg) scale(0.3)`, opacity: 0 }, 1190 { transform: `translate(${pLeft.x}px, ${pLeft.y - 6}px) rotate(0deg) scale(1.12)`, opacity: 1, offset: 0.6 }, 1191 { transform: `translate(${pLeft.x}px, ${pLeft.y}px) rotate(0deg) scale(1)`, opacity: 1 }, 1192 ], { duration: 580, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)', fill: 'forwards' }); 1193 await popIn.finished; 1194 try { popIn.commitStyles(); popIn.cancel(); } 1195 catch { mascot.style.transform = `translate(${pLeft.x}px, ${pLeft.y}px) rotate(0deg) scale(1)`; mascot.style.opacity = '1'; } 1196 mascot.style.opacity = '1'; 1197 1198 // 2. Hold on the card â little look-around tilt 1199 const lookAround = mascot.animate([ 1200 { transform: `translate(${pLeft.x}px, ${pLeft.y}px) rotate(0deg)` }, 1201 { transform: `translate(${pLeft.x}px, ${pLeft.y}px) rotate(-12deg)`, offset: 0.4 }, 1202 { transform: `translate(${pLeft.x}px, ${pLeft.y}px) rotate(8deg)`, offset: 0.75 }, 1203 { transform: `translate(${pLeft.x}px, ${pLeft.y}px) rotate(0deg)` }, 1204 ], { duration: 900, fill: 'forwards' }); 1205 await lookAround.finished; 1206 try { lookAround.commitStyles(); lookAround.cancel(); } catch {} 1207 1208 await waitMs(150); 1209 1210 // 3. Jump onto "Two" â left side of the title dips under the weight 1211 // (counter-clockwise rotation; LEFT goes DOWN) 1212 await jumpArc(mascot, pLeft, pTwo, { apex: 95, duration: 760 }); 1213 h2.classList.add('tilt-counter'); 1214 1215 // 4. Bobble on Two 1216 const bobble1 = mascot.animate([ 1217 { transform: `translate(${pTwo.x}px, ${pTwo.y}px) rotate(0deg)` }, 1218 { transform: `translate(${pTwo.x}px, ${pTwo.y - 4}px) rotate(-4deg)`, offset: 0.5 }, 1219 { transform: `translate(${pTwo.x}px, ${pTwo.y}px) rotate(0deg)` }, 1220 ], { duration: 600, fill: 'forwards' }); 1221 await bobble1.finished; 1222 try { bobble1.commitStyles(); bobble1.cancel(); } catch {} 1223 1224 await waitMs(700); 1225 1226 // 5. Untilt the title before the next jump (so the next tilt reads) 1227 h2.classList.remove('tilt-counter'); 1228 await waitMs(200); 1229 1230 // 6. Re-measure "control" â the title shift after the tilt settle 1231 // could have moved the word a hair. Tightens the landing. 1232 pControl = getStandPos(controlWord, SIZE, 18); 1233 1234 // 7. Jump onto "control" â right side dips under the weight 1235 // (clockwise rotation; RIGHT goes DOWN). The MAIN side-to-side 1236 // jump â lower apex so it reads as a normal jump arc, not a 1237 // sharp pop. 1238 await jumpArc(mascot, pTwo, pControl, { apex: 80, duration: 820 }); 1239 h2.classList.add('tilt-clockwise'); 1240 1241 const bobble2 = mascot.animate([ 1242 { transform: `translate(${pControl.x}px, ${pControl.y}px) rotate(0deg)` }, 1243 { transform: `translate(${pControl.x}px, ${pControl.y - 4}px) rotate(4deg)`, offset: 0.5 }, 1244 { transform: `translate(${pControl.x}px, ${pControl.y}px) rotate(0deg)` }, 1245 ], { duration: 600, fill: 'forwards' }); 1246 await bobble2.finished;
1247 try { bobble2.commitStyles(); bobble2.cancel(); } catch {} 1248 1249 await waitMs(900); 1250 1251 // 8. Untilt the title before the next jump 1252 h2.classList.remove('tilt-clockwise'); 1253 await waitMs(180); 1254 1255 // 9. Jump from "control" onto the right card (FULL AUTO, dark). 1256 // Mascot stays its native black â it lands on a black surface 1257 // so it's briefly hidden, then slides off to the right where it 1258 // re-emerges against the section background and exits off-screen. 1259 const rightCard = modesSection.querySelectorAll('.modes-grid > .mode')[1]; 1260 if (rightCard) { 1261 const pRight = getStandPos(rightCard, SIZE, 8); 1262 await jumpArc(mascot, pControl, pRight, { apex: 100, duration: 780 }); 1263 1264 await waitMs(300); 1265 1266 // 10. Final exit: pure horizontal slide right, off the page. 1267 const offscreenX = window.scrollX + window.innerWidth + SIZE + 40; 1268 const exit = mascot.animate([ 1269 { transform: `translate(${pRight.x}px, ${pRight.y}px) rotate(0deg) scale(1)`, opacity: 1 }, 1270 { transform: `translate(${offscreenX}px, ${pRight.y}px) rotate(0deg) scale(1)`, opacity: 1 }, 1271 ], { duration: 900, easing: 'cubic-bezier(0.4, 0, 0.7, 0.2)', fill: 'forwards' }); 1272 await exit.finished; 1273 } else { 1274 // Fallback if the right card isn't there for some reason. 1275 const offscreenX = window.scrollX + window.innerWidth + SIZE + 40; 1276 const exit = mascot.animate([ 1277 { transform: `translate(${pControl.x}px, ${pControl.y}px) rotate(0deg) scale(1)`, opacity: 1 }, 1278 { transform: `translate(${offscreenX}px, ${pControl.y}px) rotate(0deg) scale(1)`, opacity: 1 }, 1279 ], { duration: 900, easing: 'cubic-bezier(0.4, 0, 0.7, 0.2)', fill: 'forwards' }); 1280 await exit.finished; 1281 } 1282 mascot.remove(); 1283 } 1284 1285 if (modesSection && 'IntersectionObserver' in window) { 1286 const modesObs = new IntersectionObserver((entries) => { 1287 entries.forEach((e) => { 1288 if (e.isIntersecting) { 1289 // Let the section finish entering / settle, then go. 1290 setTimeout(runModesChoreography, 400); 1291 modesObs.disconnect(); 1292 } 1293 }); 1294 }, { threshold: 0.45 }); 1295 modesObs.observe(modesSection); 1296 } 1297 1298 // âââ Pricing section: one jump from Starter to Pro âââââââââââââââ 1299 // Pop in on the Starter plan, look around, then one clean hop onto 1300 // the featured Pro card â visually selling the upgrade. Same exit 1301 // motion as Modes: slide right off the page. 1302 const pricingSection = document.querySelector('.pricing'); 1303 1304 async function runPricingChoreography() { 1305 if (!pricingSection) return; 1306 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; 1307 1308 const plans = pricingSection.querySelectorAll('.pricing-grid > .plan'); 1309 if (plans.length < 2) return; 1310 const fromPlan = plans[0]; // Starter (left) 1311 const toPlan = plans[1]; // Pro (center, featured) 1312 1313 const SIZE = 56; 1314 const mascot = document.createElement('img'); 1315 mascot.src = 'logo.svg'; 1316 mascot.alt = ''; 1317 mascot.className = 'modes-mascot'; // reuses positioning + filter styles 1318 document.body.appendChild(mascot); 1319 1320 const pFrom = getStandPos(fromPlan, SIZE, 8); 1321 const pTo = getStandPos(toPlan, SIZE, 8); 1322 1323 // 1. Pop in on Starter 1324 mascot.style.transform = `translate(${pFrom.x}px, ${pFrom.y}px) scale(0.3)`; 1325 mascot.style.opacity = '0'; 1326 await new Promise(requestAnimationFrame); 1327 const popIn = mascot.animate([ 1328 { transform: `translate(${pFrom.x}px, ${pFrom.y}px) rotate(0deg) scale(0.3)`, opacity: 0 }, 1329 { transform: `translate(${pFrom.x}px, ${pFrom.y - 6}px) rotate(0deg) scale(1.12)`, opacity: 1, offset: 0.6 }, 1330 { transform: `translate(${pFrom.x}px, ${pFrom.y}px) rotate(0deg) scale(1)`, opacity: 1 }, 1331 ], { duration: 580, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)', fill: 'forwards' }); 1332 await popIn.finished; 1333 try { popIn.commitStyles(); popIn.cancel(); } 1334 catch { mascot.style.transform = `translate(${pFrom.x}
1334px, ${pFrom.y}px) rotate(0deg) scale(1)`; mascot.style.opacity = '1'; } 1335 mascot.style.opacity = '1'; 1336 1337 // 2. Quick look-around 1338 const lookAround = mascot.animate([ 1339 { transform: `translate(${pFrom.x}px, ${pFrom.y}px) rotate(0deg)` }, 1340 { transform: `translate(${pFrom.x}px, ${pFrom.y}px) rotate(-10deg)`, offset: 0.4 }, 1341 { transform: `translate(${pFrom.x}px, ${pFrom.y}px) rotate(8deg)`, offset: 0.75 }, 1342 { transform: `translate(${pFrom.x}px, ${pFrom.y}px) rotate(0deg)` }, 1343 ], { duration: 850, fill: 'forwards' }); 1344 await lookAround.finished; 1345 try { lookAround.commitStyles(); lookAround.cancel(); } catch {} 1346 1347 await waitMs(200); 1348 1349 // 3. One smooth parabolic jump from Starter â Pro 1350 await jumpArc(mascot, pFrom, pTo, { apex: 110, duration: 820 }); 1351 1352 // 4. Brief hold on Pro (mascot is briefly hidden by the black 1353 // background of the featured card â same as Modes) 1354 await waitMs(500); 1355 1356 // 5. Slide right off the page (consistent with Modes exit) 1357 const offscreenX = window.scrollX + window.innerWidth + SIZE + 40; 1358 const exit = mascot.animate([ 1359 { transform: `translate(${pTo.x}px, ${pTo.y}px) rotate(0deg) scale(1)`, opacity: 1 }, 1360 { transform: `translate(${offscreenX}px, ${pTo.y}px) rotate(0deg) scale(1)`, opacity: 1 }, 1361 ], { duration: 900, easing: 'cubic-bezier(0.4, 0, 0.7, 0.2)', fill: 'forwards' }); 1362 await exit.finished; 1363 mascot.remove(); 1364 } 1365 1366 if (pricingSection && 'IntersectionObserver' in window) { 1367 const pricingObs = new IntersectionObserver((entries) => { 1368 entries.forEach((e) => { 1369 if (e.isIntersecting) { 1370 setTimeout(runPricingChoreography, 400); 1371 pricingObs.disconnect(); 1372 } 1373 }); 1374 }, { threshold: 0.45 }); 1375 pricingObs.observe(pricingSection); 1376 } 1377 1378 // âââ Smart Triggers card: battle scene âââââââââââââââââââââââââââ 1379 // A sentry mascot stands guard inside the Smart triggers card. 1380 // Trigger tokens (keywords / mentions / etc.) fall from above; the 1381 // sentry jumps up to intercept each one and knock it off-screen. 1382 // Loops while the card is in view. 1383 const TRIGGER_TOKENS = [ 1384 '@competitor', 'SaaS', '#launch', '@founder', 1385 '#indie', 'reply needed', 'ð mention', 'DM lead', 1386 '#shipping', 'new follower', '#building', '@growth', 1387 'hot take', '#shipping', 'AI tools', 1388 ]; 1389 const triggersCard = document.querySelector('.toolkit-grid > .toolkit-card:nth-child(2)'); 1390 1391 async function startTriggersBattle(card) { 1392 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; 1393 1394 // Mount sentry 1395 const sentry = document.createElement('img'); 1396 sentry.src = 'logo.svg'; 1397 sentry.alt = ''; 1398 sentry.className = 'triggers-sentry'; 1399 card.appendChild(sentry); 1400 1401 // Place sentry at the initial home position (centered, around 50% Y). 1402 function home() { 1403 const r = card.getBoundingClientRect(); 1404 const SS = 48; 1405 return { 1406 cardW: r.width, 1407 cardH: r.height, 1408 SS, 1409 homeX: r.width / 2 - SS / 2, 1410 homeY: r.height * 0.5 - SS / 2, 1411 }; 1412 } 1413 const h0 = home(); 1414 sentry.style.transform = `translate(${h0.homeX}px, ${h0.homeY}px)`; 1415 1416 // Battle loop 1417 while (document.body.contains(card)) { 1418 if (isInViewportSlack(card)) { 1419 await spawnTriggerAttack(card, sentry); 1420 } 1421 await waitMs(900 + Math.random() * 1500); 1422 } 1423 } 1424 1425 async function spawnTriggerAttack(card, sentry) { 1426 const r = card.getBoundingClientRect(); 1427 const cardW = r.width; 1428 const cardH = r.height; 1429 const SS = 48; 1430 const homeX = cardW / 2 - SS / 2; 1431 const homeY = cardH * 0.5 - SS / 2; 1432 1433 // Spawn token 1434 const text = TRIGGER_TOKENS[Math.floor(Math.random() * TRIGGER_TOKENS.length)]; 1435 const token = document.createElement('div'); 1436 token.className = 'triggers-token'; 1437 token.textContent = text; 1438 card.appendChild(token); 1439 1440 // Measure after first paint, then position it 1441 await new Promise(requestAnimationFrame); 1442 const tokenW = token.offsetWidth; 1443 const tokenH = token.offsetHeight; 1444 const margin = 20; 1445 const tokenX = margin + Math.random() * Math.max(0, cardW - tokenW - margin * 2); 1446 token.style.left = tokenX + 'px'; 1447 token.style.top = (-tokenH - 6) + 'px'; 1448 1449 // Where the strike happens (Y) 1450 const strikeY = Math.max(40, cardH * 0.34);
1451 // The fall ends with the token sitting at strikeY (its TOP at strikeY). 1452 // Token starts at top: -tokenH-6, so to move its top to strikeY: 1453 const fallDistance = strikeY - (-tokenH - 6); 1454 const fallDuration = 1100; 1455 const fall = token.animate([ 1456 { transform: 'translateY(0)' }, 1457 { transform: `translateY(${fallDistance}px)` }, 1458 ], { duration: fallDuration, easing: 'linear', fill: 'forwards' }); 1459 1460 // Sentry takeoff timed to meet token mid-strike. 1461 const takeoffDelay = 650; 1462 setTimeout(() => { 1463 const sentryHitX = tokenX + tokenW / 2 - SS / 2; 1464 const goingRight = sentryHitX > homeX; 1465 const tilt = goingRight ? 22 : -22; 1466 const apexX = (homeX + sentryHitX) / 2; 1467 const apexY = strikeY - 26; 1468 sentry.animate([ 1469 { transform: `translate(${homeX}px, ${homeY}px) rotate(0deg) scale(1)` }, 1470 { transform: `translate(${apexX}px, ${apexY}px) rotate(${(tilt * 0.5).toFixed(1)}deg) scale(1.05)`, 1471 offset: 0.4 }, 1472 { transform: `translate(${sentryHitX}px, ${strikeY - SS / 2 + 8}px) rotate(${tilt}deg) scale(1.1)`, 1473 offset: 0.5 }, 1474 { transform: `translate(${apexX}px, ${apexY + 24}px) rotate(${(tilt * 0.35).toFixed(1)}deg) scale(1)`, 1475 offset: 0.72 }, 1476 { transform: `translate(${homeX}px, ${homeY}px) rotate(0deg) scale(1)` }, 1477 ], { duration: 760, easing: 'cubic-bezier(0.45, 0, 0.55, 1)', fill: 'forwards' }); 1478 }, takeoffDelay); 1479 1480 // At strike moment (sentry at 50% of jump): take out the token. 1481 await waitMs(takeoffDelay + 380); 1482 try { fall.cancel(); } catch {} 1483 1484 const tokenCenterX = tokenX + tokenW / 2; 1485 const sentryCenterX = homeX + SS / 2; 1486 const dir = tokenCenterX < sentryCenterX ? -1 : 1; 1487 token.animate([ 1488 { transform: `translateY(${fallDistance}px) translateX(0) rotate(0deg)`, opacity: 1 }, 1489 { transform: `translateY(${fallDistance + 20}px) translateX(${dir * 30}px) rotate(${dir * 30}deg)`, opacity: 1, offset: 0.3 }, 1490 { transform: `translateY(${fallDistance + 140}px) translateX(${dir * 220}px) rotate(${dir * 280}deg)`, opacity: 0 }, 1491 ], { duration: 700, easing: 'cubic-bezier(0.55, 0.085, 0.68, 0.53)', fill: 'forwards' }); 1492 1493 await waitMs(800); 1494 token.remove(); 1495 } 1496 1497 if (triggersCard && 'IntersectionObserver' in window) { 1498 const tbObs = new IntersectionObserver((entries) => { 1499 entries.forEach((e) => { 1500 if (e.isIntersecting) { 1501 startTriggersBattle(triggersCard); 1502 tbObs.disconnect(); 1503 } 1504 }); 1505 }, { threshold: 0.3 }); 1506 tbObs.observe(triggersCard); 1507 } 1508 1509 // âââ Feature card demos: typing draft + cycling smart-reply chips â 1510 // Typing draft (card 3 â Editable AI drafts) 1511 (function () { 1512 const target = document.querySelector('.demo-draft-text'); 1513 if (!target) return; 1514 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { 1515 target.textContent = "Shipped a tiny feature today â anyone want to try it?"; 1516 return; 1517 } 1518 const phrases = [ 1519 "Shipped a tiny feature today â anyone want to try it?", 1520 "Hot take: most 'AI for X' tools sound like ChatGPT in a wig.", 1521 "Indie SaaS, what's your #1 growth hack right now?", 1522 ]; 1523 let p = 0, c = 0, deleting = false; 1524 function step() { 1525 const phrase = phrases[p]; 1526 if (!deleting) { 1527 c++; 1528 target.textContent = phrase.slice(0, c); 1529 if (c >= phrase.length) { 1530 setTimeout(() => { deleting = true; step(); }, 1600); 1531 return; 1532 } 1533 setTimeout(step, 28 + Math.random() * 55); 1534 } else { 1535 c--; 1536 target.textContent = phrase.slice(0, c); 1537 if (c <= 0) { 1538 deleting = false; 1539 p = (p + 1) % phrases.length; 1540 setTimeout(step, 300); 1541 return; 1542 } 1543 setTimeout(step, 14); 1544 } 1545 } 1546 // Defer start so the card has time to layout + viewport check is cheap 1547 const card3 = target.closest('.feature'); 1548 if (card3 && 'IntersectionObserver' in window) { 1549 let started = false;
1550 const o = new IntersectionObserver((entries) => { 1551 entries.forEach((e) => { 1552 if (e.isIntersecting && !started) { started = true; step(); o.disconnect(); } 1553 }); 1554 }, { threshold: 0.4 }); 1555 o.observe(card3); 1556 } else { 1557 step(); 1558 } 1559 })(); 1560 1561 // Cycling chip selection (card 5 â Smart replies) 1562 (function () { 1563 const chips = document.querySelectorAll('.demo-chip-item'); 1564 if (!chips.length) return; 1565 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { 1566 chips[0].classList.add('is-active'); 1567 return; 1568 } 1569 const card5 = chips[0].closest('.feature'); 1570 let started = false; 1571 function start() { 1572 if (started) return; 1573 started = true; 1574 let i = 0; 1575 chips[0].classList.add('is-active'); 1576 setInterval(() => { 1577 chips.forEach((c) => c.classList.remove('is-active')); 1578 i = (i + 1) % chips.length; 1579 chips[i].classList.add('is-active'); 1580 }, 1500); 1581 } 1582 if (card5 && 'IntersectionObserver' in window) { 1583 const o = new IntersectionObserver((entries) => { 1584 entries.forEach((e) => { if (e.isIntersecting) { start(); o.disconnect(); } }); 1585 }, { threshold: 0.4 }); 1586 o.observe(card5); 1587 } else { 1588 start(); 1589 } 1590 })(); 1591 1592 // Mention counter â slow tick up like a real live ops board 1593 (function () { 1594 const el = document.getElementById('demo-mentions-count'); 1595 if (!el) return; 1596 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; 1597 const card1 = el.closest('.feature'); 1598 let started = false; 1599 function start() { 1600 if (started) return; 1601 started = true; 1602 let n = parseInt(el.textContent, 10) || 247; 1603 setInterval(() => { 1604 n += Math.floor(Math.random() * 3) + 1; // +1..+3 1605 el.textContent = n.toLocaleString(); 1606 }, 1800 + Math.random() * 1200); 1607 } 1608 if (card1 && 'IntersectionObserver' in window) { 1609 const o = new IntersectionObserver((entries) => { 1610 entries.forEach((e) => { if (e.isIntersecting) { start(); o.disconnect(); } }); 1611 }, { threshold: 0.4 }); 1612 o.observe(card1); 1613 } else { start(); } 1614 })(); 1615 1616 // Queue countdown â mm:ss ticking down, resets every cycle 1617 (function () { 1618 const el = document.getElementById('demo-countdown'); 1619 if (!el) return; 1620 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; 1621 const card2 = el.closest('.feature'); 1622 let started = false; 1623 function start() { 1624 if (started) return; 1625 started = true; 1626 let total = 14 * 60 + 32; // 14:32 1627 setInterval(() => { 1628 total -= 1; 1629 if (total < 0) total = 14 * 60 + 32; // loop 1630 const m = Math.floor(total / 60); 1631 const s = total % 60; 1632 el.textContent = m + ':' + (s < 10 ? '0' + s : s); 1633 }, 1000); 1634 } 1635 if (card2 && 'IntersectionObserver' in window) { 1636 const o = new IntersectionObserver((entries) => { 1637 entries.forEach((e) => { if (e.isIntersecting) { start(); o.disconnect(); } }); 1638 }, { threshold: 0.4 }); 1639 o.observe(card2); 1640 } else { start(); } 1641 })(); 1642 1643 // Inbox unread count â slowly ticks up 1644 (function () { 1645 const el = document.getElementById('demo-inbox-count'); 1646 if (!el) return; 1647 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; 1648 const card6 = el.closest('.feature'); 1649 let started = false; 1650 function start() { 1651 if (started) return; 1652 started = true; 1653 let n = parseInt(el.textContent, 10) || 47; 1654 setInterval(() => { 1655 // Sometimes +1, sometimes hold (so it doesn't race away) 1656 if (Math.random() < 0.55) { 1657 n += 1; 1658 el.textContent = n; 1659 } 1660 }, 2200 + Math.random() * 1400); 1661 } 1662 if (card6 && 'IntersectionObserver' in window) { 1663 const o = new IntersectionObserver((entries) => {
1664 entries.forEach((e) => { if (e.isIntersecting) { start(); o.disconnect(); } }); 1665 }, { threshold: 0.4 }); 1666 o.observe(card6); 1667 } else { start(); } 1668 })(); 1669 </script>
1669 1670</body> 1671</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.