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