PageSourceSearch

https://www.xlift.ai/

html xlift.ai collected 2026-10-05 13:20:03 UTC 76,349 bytes, 1,671 lines download raw bytes

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 &amp; 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">&lt;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.