PageSourceSearch

https://www.xlift.ai/for/shopify-dev-shops

html xlift.ai collected 2026-10-05 13:20:05 UTC 77,192 bytes, 1,692 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 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 &amp; 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">&lt;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.