PageSourceSearch

https://toolmonsters.com/

html toolmonsters.com collected 2026-10-02 01:46:19 UTC 117,793 bytes, 2,157 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, viewport-fit=cover" />
6<title>Tool Monsters</title>
7<meta name="theme-color" content="#bfe0f5" />
8<meta name="description" content="Your next customers are already around you. We build GTM systems that turn your audience, network and market signals into sales conversations." />
9<meta property="og:type" content="website" />
10<meta property="og:site_name" content="Tool Monsters" />
11<meta property="og:title" content="Your next customers are already around you." />
12<meta property="og:description" content="We build GTM systems that turn your audience, network and market signals into sales conversations." />
13<meta property="og:url" content="https://toolmonsters.com/" />
14<meta property="og:image" content="https://toolmonsters.com/og.png" />
15<meta property="og:image:width" content="1200" />
16<meta property="og:image:height" content="630" />
17<meta name="twitter:card" content="summary_large_image" />
18<meta name="twitter:title" content="Your next customers are already around you." />
19<meta name="twitter:description" content="We build GTM systems that turn your audience, network and market signals into sales conversations." />
20<meta name="twitter:image" content="https://toolmonsters.com/og.png" />
21<link rel="preload" as="image" href="back.webp" />
22<link rel="preload" as="image" href="front.webp" media="(min-width: 641px)" />
23<link rel="preload" as="image" href="Mobile.webp" media="(max-width: 640px)" />
24<link rel="icon" type="image/png" href="fav.png" />
25<link rel="apple-touch-icon" href="fav.png" />
26<link rel="preconnect" href="https://fonts.googleapis.com" />
27<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
28<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,[email protected],400..800&family=Inter:wght@400;500&display=swap" rel="stylesheet" />
29<!-- Plausible: pageviews + custom events (quiz funnel). The stub queues events fired before the script loads. -->
30<script>window.plausible = window.plausible || function () { (window.plausible.q = window.plausible.q || []).push(arguments); };</script>
vendor: 1 bytes, line 30
30
31<script defer data-domain="toolmonsters.com" src="https://plausible.io/js/script.js"></script>
31
32<style>
33  * { margin: 0; padding: 0; box-sizing: border-box; }
34  body {
35    font-family: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
36    background: url("back.webp") center top / cover fixed no-repeat, #bfe0f5;
37  }
38
39  /* The intro is much taller than the viewport; the stage pins while we scroll
40     through the hero zoom AND the whole GTM story. */
41  .intro { position: relative; height: 600vh; }
42
43  .stage {
44    position: sticky;
45    top: 0;
46    height: 100vh;
47    height: 100svh;
48    overflow: hidden;
49  }
50
51  /* Sky (back.png) */
52  .sky {
53    position: absolute; inset: 0;
54    background: url("back.webp") center / cover no-repeat;
55    transform: scale(var(--sky-scale, 1));
56    transform-origin: center 35%;
57    will-change: transform;
58    z-index: 0;
59  }
60
61  /* Mountains (front.png), anchored to the bottom so we see its full bottom edge */
62  .hills {
63    position: absolute;
64    left: 50%; bottom: -8vh;     /* dropped a bit -> more open valley space for the content */
65    width: 104%;                 /* always a touch wider than the screen -> touches the side borders */
66    transform: translateX(-50%) scale(var(--hills-scale, 1));
67    transform-origin: center bottom;
68    opacity: var(--hills-o, 1);
69    will-change: transform, opacity;
70    pointer-events: none;
71    z-index: 2;
72  }
73  /* the mobile-specific art is hidden on desktop; the swap happens in the mobile @media */
74  .hills--mob { display: none; }
75
76  .title {
77    position: absolute; top: 30%; left: 50%;
78    transform: translate(-50%, calc(-50% + var(--title-y, 0)));
79    opacity: var(--title-o, 1);
80    will-change: transform, opacity;
81    z-index: 3; text-align: center; width: min(90vw, 820px); padding: 0 20px;
82  }
83  .title h1 {
84    font-family: "Bricolage Grotesque", sans-serif;
85    font-weight: 800; font-size: clamp(30px, 6.1vw, 72px);
86    line-height: 1.04; letter-spacing: -0.035em; color: #16314d;
87    text-wrap: balance;            /* even, predictable line breaks */
88  }
89  /* the payoff half of the headline carries the brand blue so it lands first */
90  .title h1 em { font-style: normal; color: #2f6fe0; }
91  .title p { margin-top: 20px; font-size: clamp(16px, 2.3vw, 22px); color: #2c4a66; line-height: 1.5; }
92  .title.gone { pointer-events: none; }
93
94  .btns { margin-top: 36px; display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
95  .btn {
96    display: inline-flex; align-items: center; justify-content: center;
97    font-family: "Bricolage Grotesque", sans-serif; font-weight: 600; font-size: clamp(15px, 1.7vw, 17px);
98    letter-spacing: -0.01em;
99    padding: 15px 30px; border-radius: 9px; border: none; cursor: pointer;
100    text-decoration: none; white-space: nowrap;
101    transition: transform .22s cubic-bezier(.2,.8,.2,1), background .2s ease, border-color .2s ease, box-shadow .25s ease;
102  }
103  .btn { will-change: transform; }
104  .btn:hover { transform: translateY(-2px) scale(1.02); }
105  .btn:active { transform: translateY(0) scale(1); transition-duration: .08s; }
106
107  /* Primary — flat blue; subtle lift + soft glow on hover */
108  .btn-primary { color: #fff; background: #2f6fe0; box-shadow: 0 0 0 0 rgba(47,103,216,0); }
109  .btn-primary:hover { background: #3576e6; box-shadow: 0 10px 20px -10px rgba(47,103,216,.55); }
110
111  /* Secondary — flat white; subtle lift + soft shadow on hover */
112  .btn-ghost { color: #16314d; background: #ffffff; border: 1px solid rgba(22,49,77,.12); box-shadow: 0 0 0 0 rgba(22,49,77,0); }
113  .btn-ghost:hover { background: #ffffff; border-color: rgba(22,49,77,.18); box-shadow: 0 10px 20px -12px rgba(22,49,77,.3); }
114
115  /* "Worked with" + scrolling logos, sitting in the valley from the start */
116  .worked {
117    position: absolute; left: 50%; top: 64%; z-index: 1;
118    transform: translateX(-50%);
119    width: min(760px, 92vw);
120    display: flex; flex-direction: column; align-items: center; gap: 16px;
121    opacity: var(--worked-o, 1);
122    pointer-events: none;
123  }
124  .worked-label {
125    font-family: "Bricolage Grotesque", sans-serif; font-weight: 600;
126    font-size: 13px; letter-spacing: .18em; text-transform: uppercase;
127    color: #16314d; opacity: .6;
128  }
129  .marquee {
130    position: relative; width: 100%; overflow: hidden;
131    -webkit-mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
132            mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
133  }
134  .track {
135    display: flex; align-items: center; width: max-content;
136    animation: scrollx 26s linear infinite;
137  }
138  /* shift by exactly one copy. Each logo carries its own trailing margin (instead
139     of flex `gap`) so the spacing after the last logo matches the rest -> the loop
140     is perfectly seamless with no empty stretch. */
141  @keyframes scrollx { to { transform: translateX(-33.3333%); } }   /* shift by exactly one of the 3 copies */
142
143  /* per-logo heights, tuned so they read as the same optical size */
144  .logo { width: auto; display: block; flex: 0 0 auto; opacity: .85; margin-right: 56px; }
145  .logo--fullenrich { height: 32px; }
146  .logo--gamma      { height: 28px; }
147  .logo--granola    { height: 32px; }
148  .logo--clickup    { height: 33px; }
149  .logo--tavus      { height: 21px; }
150  .logo--zapier     { height: 30px; }
151  .logo--wix        { height: 23px; }
152  .logo--base44     { height: 30px; }
153
154  /* fixed brand mark, always on top of the experience */
155  .brand {
156    position: fixed; top: 20px; left: 24px; z-index: 20;
157    display: inline-flex; align-items: center; gap: 10px; text-decoration: none;
158  }
159  .brand img {
160    width: 38px; height: 38px; border-radius: 12px; display: block;
161    box-shadow: 0 10px 22px -10px rgba(22,49,77,.55), 0 0 0 2px rgba(255,255,255,.65);
162  }
163  .brand span {
164    font-family: "Bricolage Grotesque", sans-serif; font-weight: 700; font-size: 17px;
165    letter-spacing: -.02em; color: #16314d; white-space: nowrap;
166  }
167
168  /* ===================== The story (inside the pinned stage) ===================== */
169  /* One evolving diagram, revealed as the mountains fade, then driven by scroll.
170     Moment 1: the four source groups. Moment 2: they connect to Tool Monsters.
171     Moment 3: real prospects. Moment 4: conversation -> pipeline. */
172  .story {
173    position: absolute; inset: 0; z-index: 1;
174    display: flex; align-items: center; justify-content: center;
175    opacity: var(--story-o, 0);
176    will-change: opacity;
177    pointer-events: none;
178  }
179  .story-inner {
180    position: relative; width: 980px; height: 620px; flex: none;
181    transform: scale(var(--story-scale, 1)); transform-origin: center center;
182  }
183  .layer { position: absolute; inset: 0; }
184
185  /* the big phrases sit above the diagram and crossfade into each other */
186  .phrase {
187    position: absolute; top: 7%; left: 50%; transform: translateX(-50%);
188    z-index: 4; width: min(92vw, 900px); text-align: center;
189    font-family: "Bricolage Grotesque", sans-serif; font-weight: 800;
190    font-size: clamp(28px, 5.2vw, 60px); letter-spacing: -.035em; color: #16314d;
191    line-height: 1.05;
192    opacity: 0; will-change: opacity; pointer-events: none;
193    text-wrap: balance;
194  }
195  .phrase--sub {
196    top: calc(7% + clamp(40px, 6.4vw, 76px));
197    font-family: "Inter", sans-serif; font-weight: 400;
198    font-size: clamp(15px, 1.8vw, 19px); letter-spacing: 0; color: #2c4a66;
199  }
200
201  /* elements the scroll driver fades in / out */
202  .sd { opacity: 0; will-change: opacity, transform; }
203
204  /* ---- the four source groups: label, official logo tiles, a quiet caption ---- */
205  .srcbox {
206    position: absolute; left: 140px; width: 224px;
207    background: #fff; border: 1px solid rgba(22,49,77,.1); border-radius: 16px;
208    padding: 13px 17px 12px;
209    box-shadow: 0 18px 36px -26px rgba(22,49,77,.55);
210  }
211  .srcbox strong {
212    display: block; font-family: "Bricolage Grotesque", sans-serif; font-weight: 700;
213    font-size: 11.5px; letter-spacing: .14em; text-transform: uppercase; color: #2f6fe0;
214    margin-bottom: 9px;
215  }
216  .srcbox em { display: block; font-style: normal; font-size: 11.5px; color: #6a7689; margin-top: 9px; }
217  .srcbox--1 { top: 60px; }
218  .srcbox--2 { top: 185px; }
219  .srcbox--3 { top: 310px; }
220  .srcbox--4 { top: 435px; }
221  .ltiles { display: flex; gap: 6px; flex-wrap: wrap; }
222  .lt {
223    width: 27px; height: 27px; border-radius: 8px; flex: 0 0 auto; overflow: hidden;
224    display: flex; align-items: center; justify-content: center; background: #fff;
225    box-shadow: 0 6px 13px -8px rgba(22,49,77,.55); border: 1px solid rgba(22,49,77,.08);
226  }
227  .lt img { width: 100%; height: 100%; object-fit: cover; display: block; }
228  .lt--fit { padding: 4px; }
229  .lt--fit img { object-fit: contain; }
230  .lt svg { display: block; }
231  .lt--word {
232    font-family: "Bricolage Grotesque", sans-serif; font-weight: 800; font-size: 13px;
233    color: #16314d; line-height: 1;
234  }
235  /* one wide diagram; the camera pans left as the story moves right:
236     sources -> buyers -> conversations -> pipeline */
237  .map-track { position: absolute; left: 0; top: 0; width: 2140px; height: 100%;
238               will-change: transform; }
239
240  /* thin connectors from the groups toward the center */
241  .wires { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
242  .wire { fill: none; stroke: #2f6fe0; stroke-width: 1.6; stroke-linecap: round;
243          stroke-dasharray: 100; stroke-dashoffset: 100; }
244
245  .dbox {
246    position: relative; z-index: 1;
247    background: #fff; border: 1px solid rgba(22,49,77,.1); border-radius: 14px;
248    padding: 16px 30px; text-align: center; white-space: nowrap;
249    font-family: "Bricolage Grotesque", sans-serif; font-weight: 700; font-size: 18px;
250    letter-spacing: -.02em; color: #16314d;
251    box-shadow: 0 22px 44px -30px rgba(22,49,77,.5);
252  }
253  /* final box, the one business outcome */
254  .dbox--final { background: #2f6fe0; border: none; color: #fff;
255    box-shadow: 0 24px 44px -22px rgba(47,111,224,.6); }
256
257  /* ---- the buyers: minimal person cards, a face and a name ---- */
258  .person {
259    position: absolute; left: 620px; width: 260px;
260    display: flex; align-items: center; gap: 15px; padding: 17px 20px;
261    background: #fff; border: 1px solid rgba(22,49,77,.1); border-radius: 18px;
262    box-shadow: 0 28px 54px -32px rgba(22,49,77,.55);
263  }
264  .person--1 { top: 140px; }
265  .person--2 { top: 268px; }
266  .person--3 { top: 396px; }
267  .person .avatar {
268    width: 44px; height: 44px; border-radius: 13px; flex: 0 0 auto;
269    object-fit: cover; display: block;
270    box-shadow: 0 0 0 1px rgba(22,49,77,.08);
271  }
272  .person b { display: block; font-family: "Bricolage Grotesque", sans-serif; font-weight: 700;
273              font-size: 16.5px; letter-spacing: -.01em; color: #16314d; }
274  .person .role { display: block; font-size: 13px; color: #6a7689; margin-top: 2px; }
275
276  /* ---- further right: three little conversations, then pipeline ---- */
277  .chat {
278    position: absolute; left: 1180px; width: 250px;
279    background: #fff; border: 1px solid rgba(22,49,77,.1); border-radius: 18px;
280    padding: 15px 16px 14px;
281    box-shadow: 0 28px 54px -32px rgba(22,49,77,.55);
282  }
283  .chat--1 { top: 78px; }
284  .chat--2 { top: 242px; }
285  .chat--3 { top: 406px; }
286  .chat-head {
287    display: flex; align-items: center; gap: 9px;
288    font-family: "Bricolage Grotesque", sans-serif; font-weight: 700; font-size: 13.5px; color: #16314d;
289    padding-bottom: 10px; border-bottom: 1px solid rgba(22,49,77,.08); margin-bottom: 12px;
290  }
291  .cav {
292    width: 26px;
292 height: 26px; border-radius: 8px; flex: 0 0 auto;
293    object-fit: cover; display: block;
294    box-shadow: 0 0 0 1px rgba(22,49,77,.08);
295  }
296  .bub {
297    width: fit-content; max-width: 86%; padding: 8px 12px;
298    font-size: 12.5px; line-height: 1.4; border-radius: 13px;
299  }
300  .bub--out { margin-left: auto; background: #2f6fe0; color: #fff; border-bottom-right-radius: 4px; }
301  .bub--in  { margin-right: auto; margin-top: 8px; background: #f1f5fa; color: #33425a; border-bottom-left-radius: 4px; }
302  .pipe-pos { position: absolute; left: 1730px; top: 282px; }
303  .dbox--pipeline { font-size: 24px; padding: 20px 44px; border-radius: 16px;
304    position: relative; overflow: hidden; }
305  /* soft breathing glow + one shine sweep once the pipeline lands */
306  .dbox--pipeline.lit { animation: pipeglow 2.6s ease-in-out infinite; }
307  .dbox--pipeline::after {
308    content: ""; position: absolute; top: 0; bottom: 0; left: -80%; width: 55%;
309    background: linear-gradient(105deg, transparent, rgba(255,255,255,.42), transparent);
310    transform: skewX(-18deg);
311  }
312  .dbox--pipeline.lit::after { animation: shine 2.6s ease-in-out infinite; }
313  @keyframes pipeglow {
314    0%, 100% { box-shadow: 0 24px 44px -22px rgba(47,111,224,.6), 0 0 0 0 rgba(47,111,224,.35); }
315    50%      { box-shadow: 0 24px 44px -22px rgba(47,111,224,.6), 0 0 0 14px rgba(47,111,224,0); }
316  }
317  @keyframes shine { 0% { left: -80%; } 55%, 100% { left: 130%; } }
318
319  /* one glowing dot flows the drawn lines (same dot as the old roadmap) */
320  .lpath--dot {
321    stroke: #fff; stroke-width: 9; stroke-dasharray: 0.01 100; stroke-linecap: round;
322    filter: drop-shadow(0 0 1px rgba(255,255,255,.9))
323            drop-shadow(0 0 3px rgba(47,111,224,1))
324            drop-shadow(0 0 8px rgba(47,111,224,.5));
325  }
326  .dot--1 { animation: seg1 3s linear infinite; }
327  .dot--2 { animation: seg2 3s linear infinite; }
328  .dot--3 { animation: seg3 3s linear infinite; }
329  @keyframes seg1 {
330    0%   { stroke-dashoffset: 100; opacity: 0; }
331    5%   { stroke-dashoffset: 86;  opacity: 1; }
332    29%  { stroke-dashoffset: 10;  opacity: 1; }
333    33%  { stroke-dashoffset: 0.4; opacity: 0; }
334    100% { stroke-dashoffset: 0.4; opacity: 0; }
335  }
336  @keyframes seg2 {
337    0%   { stroke-dashoffset: 100; opacity: 0; }
338    33%  { stroke-dashoffset: 100; opacity: 0; }
339    38%  { stroke-dashoffset: 86;  opacity: 1; }
340    62%  { stroke-dashoffset: 10;  opacity: 1; }
341    66%  { stroke-dashoffset: 0.4; opacity: 0; }
342    100% { stroke-dashoffset: 0.4; opacity: 0; }
343  }
344  @keyframes seg3 {
345    0%   { stroke-dashoffset: 100; opacity: 0; }
346    66%  { stroke-dashoffset: 100; opacity: 0; }
347    71%  { stroke-dashoffset: 86;  opacity: 1; }
348    96%  { stroke-dashoffset: 8;   opacity: 1; }
349    100% { stroke-dashoffset: 0.4; opacity: 0; }
350  }
351  /* corner CTA on the story */
352  .corner-cta {
353    position: absolute; bottom: 30px; right: 30px; z-index: 6;
354    display: inline-flex; align-items: center; gap: 8px;
355    font-family: "Bricolage Grotesque", sans-serif; font-weight: 600; font-size: 15px;
356    padding: 13px 22px; border-radius: 9px; text-decoration: none; color: #fff;
357    background: #2f6fe0; border: none; cursor: pointer;
358    box-shadow: 0 12px 24px -12px rgba(47,111,224,.55);
359    opacity: var(--story-o, 0);
360    transition: transform .15s ease, box-shadow .25s ease;
361  }
362  .corner-cta:hover { transform: translateY(-2px); background: #3576e6; box-shadow: 0 16px 30px -12px rgba(47,111,224,.65); }
363  .corner-cta svg { transition: transform .2s ease; }
364  .corner-cta:hover svg { transform: translateX(3px); }
365
366  /* mobile-only round "back to top" button (replaces the corner CTA on phones) */
367  .to-top {
368    display: none;
369    position: absolute; bottom: 18px; right: 18px; z-index: 6;
370    width: 48px; height: 48px; border-radius: 50%; border: none; cursor: pointer;
371    align-items: center; justify-content: center; color: #fff; background: #2f6fe0;
372    box-shadow: 0 12px 24px -12px rgba(47,111,224,.55);
373    opacity: var(--story-o, 0);
374    -webkit-tap-highlight-color: transparent;
375    transition: transform .15s ease;
376  }
377  .to-top:active { transform: translateY(1px) scale(.96); }
378
379  /* ===================== Sections after the stage ===================== */
380  /* gentle fade-up for section blocks as they enter */
381  .rise { opacity: 0; transform: translateY(18px);
382    transition: opacity .7s cubic-bezier(.2,.8,.2,1), transform .7s cubic-bezier(.2,.8,.2,1); }
383  .rise.in { opacity: 1; transform: none; }
384
385  .sec-eyebrow {
386    text-align: center; font-family: "Bricolage Grotesque", sans-serif; font-weight: 600;
387    font-size: 13px; letter-spacing: .18em; text-transform: uppercase; color: #2f6fe0; margin-bottom: 14px;
388  }
389  .sec-title {
390    text-align: center; font-family: "Bricolage Grotesque", sans-serif; font-weight: 700;
391    font-size: clamp(28px, 4vw, 40px); letter-spacing: -.02em; color: #16314d; margin: 0 auto 52px; max-width: 22ch;
392  }
393
394  /* ---- four workflows in the GPC diagram language, right after the story ---- */
395  .examples { position: relative; z-index: 5; padding: 80px 24px 40px; background: transparent; }
396  .ex-grid { max-width: 1080px; margin: 0 auto; display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; }
397  @media (max-width: 880px) { .ex-grid { grid-template-columns: 1fr; max-width: 520px; } }
398  .ex-card {
399    background: #fff; border: 1px solid rgba(22,49,77,.06); border-radius: 24px;
400    padding: 30px 30px 26px; display: flex; flex-direction: column; align-items: center;
401    box-shadow: 0 44px 80px -44px rgba(22,49,77,.55);
402    transition: transform .35s cubic-bezier(.2,.8,.2,1), box-shadow .35s ease;
403  }
404  .ex-card:hover {
405    transform: translateY(-5px);
406    box-shadow: 0 60px 100px -48px rgba(22,49,77,.65);
407  }
408  /* the card's subject is the loudest thing on it: people scan titles, not captions */
409  .ex-head {
410    align-self: stretch; margin-bottom: 24px; text-align: center;
411    font-family: "Bricolage Grotesque", sans-serif; font-weight: 800;
412  }
413  .ex-head .name {
414    display: block;
415    font-size: clamp(21px, 2.1vw, 27px); letter-spacing: -.03em; line-height: 1.05;
416    color: #16314d;
417  }
418  /* cards in a row share a height, so the diagram takes the leftover space
419     and sits centred instead of hugging the title */
420  .ex-card .gflow { margin-block: auto; }
421
422  .gflow { width: 100%; max-width: 430px; height: auto; display: block; overflow: visible; margin: 0 auto; }
423  .gcard { fill: #fff; stroke: rgba(22,49,77,.12);
424    filter: drop-shadow(0 2px 3px rgba(11,18,32,.05)) drop-shadow(0 12px 20px rgba(11,18,32,.08)); }
425  .gtile { fill: #fff;
426    filter: drop-shadow(0 2px 3px rgba(11,18,32,.06)) drop-shadow(0 10px 18px rgba(11,18,32,.12)); }
427  .gring { fill: none; stroke: rgba(22,49,77,.13); }
428  .gdark { fill: url(#gdgrad);
429    filter: drop-shadow(0 14px 22px rgba(20,22,45,.4)); }
430  .gline { fill: none; stroke-width: 1.6; stroke-linecap: round;
431    stroke-dasharray: 1; stroke-dashoffset: 1; }
432  .gtip  { fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
433  .ln-o { stroke: #FF6719; }        /* the Substack lane */
434  .ln-b { stroke: #0A66C2; }        /* the LinkedIn lane */
435  .ln-n { stroke: #93a8c0; }        /* neutral lane */
436  .gt-title { font-family: "Bricolage Grotesque", sans-serif; font-weight: 700; font-size: 14.5px;
437    letter-spacing: -.01em; fill: #16314d; text-anchor: middle; }
438  .gt-sub { font-family: "Inter", sans-serif; font-size: 11.5px; fill: #6a7689; text-anchor: middle; }
439  .gt-badge { font-family: "Bricolage Grotesque", sans-serif; font-weight: 600; font-size: 9.5px;
440    letter-spacing: .12em; fill: #fff; text-anchor: middle; }
441  .gbadge { fill: #16191F; }
442  .gt-term { font-family: "Bricolage Grotesque", sans-serif; font-weight: 700; font-size: 16px;
443    letter-spacing: -.01em; fill: #fff; text-anchor: middle; }
444  .gt-termchip { font-family: "Inter", sans-serif; font-size: 9.5px; fill: rgba(255,255,255,.78); text-anchor: middle; }
445  .gtermchip { fill: rgba(255,255,255,.12); }
446  .gt-more { font-family: "Bricolage Grotesque", sans-serif; font-weight: 700; font-size: 14px;
447    fill: #16314d; text-anchor: middle; }
448  .gt-more-sub { font-family: "Inter", sans-serif; font-size: 7px; letter-spacing: .02em; fill: #6a7689; text-anchor: middle; }
449  .gav { stroke: #fff; stroke-width: 2;
450    transform: scale(0); transform-box: fill-box; transform-origin: center; }
451  .gav--1 { fill: #79a8ff; } .gav--2 { fill: #4fc3a1; } .gav--3 { fill: #ff9d76; }
452  .gping { fill: none; stroke: #e5484d; stroke-width: 2;
453    transform-box: fill-box; transform-origin: center; opacity: 0; }
454
455  /* entrance: lines draw, nodes/tips rise, avatars pop, all when the card scrolls in */
456  .gnode { opacity: 0; transform: translateY(9px); }
457  .gtip { opacity: 0; }
458  .ex-card.in .gline { animation: gdraw .5s ease-in-out forwards; animation-delay: var(--d, 0s); }
459  .ex-card.in .gnode { animation: grise .5s cubic-bezier(.2,.8,.2,1) forwards; animation-delay: var(--d, 0s); }
460  .ex-card.in .gtip { animation: gfade .3s ease forwards; animation-delay: var(--d, 0s); }
461  .ex-card.in .gav { animation: gpop .45s cubic-bezier(.34,1.56,.64,1) forwards; animation-delay: var(--d, 1.3s); }
462  .ex-card.in .gping { animation: gping 2s ease-out 1.2s infinite; }
463  @keyframes gdraw { to { stroke-dashoffset: 0; } }
464  @keyframes grise { to { opacity: 1; transform: none; } }
465  @keyframes gfade { to { opacity: 1; } }
466  @keyframes gpop { to { transform: scale(1); } }
467  @keyframes gping { 0% { transform: scale(.55); opacity: .9; } 70%, 100% { transform: scale(2.4); opacity: 0; } }
468  /* the lines draw, then a single bead runs the system top to bottom.
469     It repeats on a 16s cycle, so the pass lasts about half a second and
470     the diagram sits still for roughly fifteen seconds in between. */
471  .gd {
472    stroke-width: 6; stroke-linecap: round;
473    stroke-dasharray: 0.01 100; stroke-dashoffset: 100; opacity: 0;
474  }
475  .gd.ln-n { stroke: #5b7896; }
476  .ex-card.in .gd {
477    animation: bead 16s linear infinite backwards;
478    animation-delay: calc(1.15s + var(--i) * .5s);
479  }
480  @keyframes bead {
481    0%    { stroke-dashoffset: 100; opacity: 0; }
482    0.4%  { opacity: 1; }
483    3.4%  { stroke-dashoffset: 0.5; opacity: 1; }
484    3.9%  { stroke-dashoffset: 0.5; opacity: 0; }
485    100%  { stroke-dashoffset: 0.5; opacity: 0; }
486  }
487
488
489  /* ===================== Footer, right under the workflows ===================== */
490  .foot-bar {
491    max-width: 1040px; margin: 64px auto 0; padding: 22px 0 26px; border-top: 1px solid rgba(22,49,77,.18);
492    display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;
493    color: #16314d; font-weight: 600; font-size: 14px;
494  }
495  .foot-bar a { color: #16314d; font-weight: 600; text-decoration: none; }
496  .foot-bar a:hover { opacity: .7; }
497  .foot-brand { display: inline-flex; align-items: center; gap: 9px; }
498  .foot-brand img { width: 26px; height: 26px; border-radius: 8px; display: block;
499    box-shadow: 0 6px 14px -8px rgba(22,49,77,.6); }
500
501  /* ===================== "Find my opportunities" modal ===================== */
502  .veil {
503    position: fixed; inset: 0; z-index: 50;
504    background: rgba(16,32,50,.5);
505    -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
506    display: none; padding: 20px 16px; overflow-y: auto;
507  }
508  .veil.open { display: flex; }
509  .qcard {
510    /* auto margins center it in the viewport and still allow scroll when tall */
511    margin: auto;
512    width: min(1040px, 100%); background: #fff; border-radius: 26px;
513    display: flex; overflow: hidden; position: relative;
514    box-shadow: 0 60px 120px -40px rgba(10,20,40,.6);
515    animation: qin .3s cubic-bezier(.2,.8,.2,1);
516  }
517  @keyframes qin { from { opacity: 0; transform: translateY(14px) scale(.985); } }
518  .qclose {
519    position: absolute; top: 16px; right: 16px; z-index: 3; width: 36px; height: 36px;
520    border: none; border-radius: 50%; background: #f1f5fa; color: #16314d; cursor: pointer;
521    display: flex; align-items: center; justify-content: center; font-size: 16px;
522  }
523  .qclose:hover { background: #e5ecf5; }
524
525  /* ---- left: the visitor's own system, built live as they answer ---- */
526  .qside {
527    width: 320px; flex: 0 0 auto; padding: 30px 26px 28px;
528    background: linear-gradient(180deg, #e9f4fd 0%, #d8ebfa 100%);
529    display: flex; flex-direction: column; justify-content: center;
530  }
531  .qside-label {
532    font-family: "Bricolage Grotesque", sans-serif; font-weight: 600;
533    font-size: 11.5px; letter-spacing: .16em; text-transform: uppercase; color: #2f6fe0;
534    margin-bottom: 16px; text-align: center;
535  }
536  /* centered so the tiles, the wire and the node share one axis */
537  .qtiles { display: flex; flex-wrap: wrap; justify-content: center; gap: 9px; min-height: 44px; }
538  .qtile {
539    width: 68px; background: #fff; border: 1px solid rgba(22,49,77,.08); border-radius: 12px;
540    padding: 9px 4px 7px; display: flex; flex-direction: column; align-items: center; gap: 4px;
541    box-shadow: 0 10px 20px -14px rgba(22,49,77,.5);
542    animation: qtilein .35s cubic-bezier(.34,1.56,.64,1);
543  }
544  @keyframes qtilein { from { opacity: 0; transform: scale(.6); } }
545  .qtile .cico { width: 26px;
545 height: 26px; border-radius: 8px; }
546  .qtile b {
547    font-family: "Bricolage Grotesque", sans-serif; font-weight: 600; font-size: 9.5px;
548    color: #16314d; letter-spacing: 0;
549  }
550  .qtile i { font-style: normal; font-size: 9.5px; font-weight: 600; color: #2f6fe0; min-height: 12px; }
551  .qtiles-empty { font-size: 12.5px; color: #6a8cad; line-height: 1.5; padding-top: 4px; text-align: center; }
552  .qwire {
553    width: 2px; height: 26px; background: #2f6fe0; border-radius: 2px;
554    align-self: center; margin: 12px 0;
555    transform: scaleY(0); transform-origin: top; transition: transform .4s ease;
556  }
557  .qwire.on { transform: none; }
558  .qnode {
559    background: linear-gradient(135deg, #2f3358 0%, #1c1e36 100%);
560    border-radius: 15px; padding: 13px 16px; text-align: center; color: #fff;
561    box-shadow: 0 18px 34px -18px rgba(20,22,45,.7);
562    opacity: 0; transform: translateY(8px); transition: opacity .4s ease, transform .4s ease;
563  }
564  .qnode.on { opacity: 1; transform: none; }
565  .qnode b {
566    display: block; font-family: "Bricolage Grotesque", sans-serif; font-weight: 800;
567    font-size: 23px; letter-spacing: -.02em; line-height: 1.1;
568  }
569  .qnode .qn-l { display: block; margin-top: 3px; font-size: 10.5px; color: rgba(255,255,255,.75); }
570  .qnode .qn-m {
571    display: none; margin-top: 7px; padding-top: 7px;
572    border-top: 1px solid rgba(255,255,255,.14);
573    font-family: "Bricolage Grotesque", sans-serif; font-weight: 700;
574    font-size: 13px; color: #9db8ff;
575  }
576  .qnode .qn-m.on { display: block; }
577  .qnode--conv {
578    background: #2f6fe0; margin-top: 0;
579    font-family: "Bricolage Grotesque", sans-serif; font-weight: 700; font-size: 14.5px;
580    box-shadow: 0 18px 34px -18px rgba(47,111,224,.7);
581  }
582
583  /* ---- right: the questions ---- */
584  .qmain { flex: 1; min-width: 0; padding: 42px 46px 34px; position: relative;
585           display: flex; flex-direction: column; justify-content: center; }
586  .qprogress { position: absolute; top: 0; left: 0; right: 0; height: 3px; background: rgba(22,49,77,.07); }
587  .qprogress i { display: block; height: 100%; width: 0; background: #2f6fe0; transition: width .4s ease; }
588  .qview { will-change: opacity, transform; }
589  .qstep-label {
590    font-family: "Bricolage Grotesque", sans-serif; font-weight: 600;
591    font-size: 12px; letter-spacing: .18em; text-transform: uppercase; color: #2f6fe0; margin-bottom: 10px;
592  }
593  .qcard h3 {
594    font-family: "Bricolage Grotesque", sans-serif; font-weight: 700;
595    font-size: clamp(21px, 3vw, 26px); letter-spacing: -.02em; color: #16314d; margin-bottom: 22px;
596    text-wrap: balance;
597  }
598  /* step 1: source cards with the official marks */
599  .srcgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
600  .srcopt {
601    position: relative; border: 1.5px solid rgba(22,49,77,.13); border-radius: 15px; background: #fff;
602    padding: 15px 8px 13px; cursor: pointer;
603    display: flex; flex-direction: column; align-items: center; gap: 9px;
604    font-family: "Bricolage Grotesque", sans-serif; font-weight: 600; font-size: 13.5px;
605    letter-spacing: -.01em; color: #16314d;
606    transition: border-color .15s ease, background .15s ease, transform .15s ease;
607  }
608  .srcopt:hover { border-color: rgba(47,111,224,.5); }
609  .srcopt:active { transform: scale(.97); }
610  .srcopt .cico { width: 30px; height: 30px; border-radius: 9px; }
611  .srcopt.on { border-color: #2f6fe0; background: #f0f6ff; }
612  .srcopt .tick {
613    position: absolute; top: 8px; right: 8px; width: 17px; height: 17px; border-radius: 50%;
614    background: #2f6fe0; color: #fff; display: none; align-items: center; justify-content: center;
615  }
616  .srcopt.on .tick { display: flex; }
617  .chips { display: flex; flex-wrap: wrap; gap: 9px; }
618  .chip {
619    display: inline-flex; align-items: center; gap: 8px;
620    border: 1.5px solid rgba(22,49,77,.14); border-radius: 999px; background: #fff;
621    padding: 10px 19px; cursor: pointer;
622    font-family: "Bricolage Grotesque", sans-serif; font-weight: 600; font-size: 14.5px;
623    letter-spacing: -.01em; color: #16314d;
624    transition: border-color .15s ease, background .15s ease, color .15s ease;
625  }
626  .chip:hover { border-color: rgba(47,111,224,.5); }
627  .chip.on { background: #2f6fe0; border-color: #2f6fe0; color: #fff; }
628  .cico {
629    width: 18px; height: 18px; border-radius: 5px; overflow: hidden; flex: 0 0 auto;
630    display: flex; align-items: center; justify-content: center; background: #fff;
631    box-shadow: 0 0 0 1px rgba(22,49,77,.08);
632  }
633  .cico img { width: 100%; height: 100%; object-fit: cover; display: block; }
634  .cico--fit { padding: 2px; }
635  .cico--fit img { object-fit: contain; }
636  .cico svg { display: block; }
637  .cico--line { color: #2f6fe0; background: #f0f6ff; }
638  .qfield { margin-bottom: 15px; }
639  .qfield label {
640    display: flex; align-items: center; gap: 8px;
641    font-family: "Bricolage Grotesque", sans-serif; font-weight: 600;
642    font-size: 13.5px; color: #16314d; margin-bottom: 6px;
643  }
644  .qfield label small { font-weight: 500; color: #6a7689; }
645  .qfield input {
646    width: 100%; padding: 12px 15px; border: 1.5px solid rgba(22,49,77,.14); border-radius: 11px;
647    font-family: "Inter", sans-serif; font-size: 15px; color: #16314d; outline: none;
648    transition: border-color .15s ease;
649  }
650  .qfield input:focus { border-color: #2f6fe0; }
651  .qsub {
652    font-family: "Bricolage Grotesque", sans-serif; font-weight: 700; font-size: 15px;
653    color: #16314d; margin: 22px 0 12px;
654  }
655  .qhint { font-size: 13.5px; color: #6a7689; line-height: 1.55; margin: -12px 0 18px; }
656  /* one-tap size chips under each reach input */
657  .szchips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
658  .szchip {
659    border: 1.5px solid rgba(22,49,77,.12); border-radius: 999px; background: #fff;
660    padding: 6px 13px; cursor: pointer;
661    font-family: "Bricolage Grotesque", sans-serif; font-weight: 600; font-size: 12.5px;
662    color: #16314d; transition: border-color .15s ease, background .15s ease, color .15s ease;
663  }
664  .szchip:hover { border-color: rgba(47,111,224,.5); }
665  .szchip.on { background: #2f6fe0; border-color: #2f6fe0; color: #fff; }
666  .qnav { display: flex; align-items: center; justify-content: space-between; margin-top: 28px; }
667  .qback { border: none; background: none; cursor: pointer; font-family: "Bricolage Grotesque", sans-serif;
668           font-weight: 600; font-size: 14.5px; color: #6a7689; padding: 8px 4px; }
669  .qback:hover { color: #16314d; }
670  .qback[hidden] { visibility: hidden; display: inline; }
671  .qerr { font-size: 13px; color: #c0392b; margin-top: 12px; display: none; }
672
673  /* ---- the scan: we show the work before we show the number ---- */
674  .scanrow {
675    display: flex; align-items: center; gap: 11px; padding: 13px 0;
676    border-bottom: 1px solid rgba(22,49,77,.07);
677    opacity: 0; transform: translateY(9px);
678    transition: opacity .35s ease, transform .35s cubic-bezier(.2,.8,.2,1);
679  }
680  .scanrow.in { opacity: 1; transform: none; }
681  .scanrow:last-of-type { border-bottom: none; }
682  .scanrow .cico { width: 22px; height: 22px; border-radius: 7px; }
683  .scanrow .sname { font-family: "Bricolage Grotesque", sans-serif; font-weight: 600;
684                    font-size: 14px; color: #16314d; }
685  .scanrow .swhat { font-size: 13px; color: #6a7689; }
686  .scanrow .stail { margin-left: auto; display: flex; align-items: center; padding-left: 10px; }
687  .spin {
688    width: 15px; height: 15px; border-radius: 50%;
689    border: 2px solid rgba(47,111,224,.22); border-top-color: #2f6fe0;
690    animation: spin .7s linear infinite;
691  }
692  @keyframes spin { to { transform: rotate(360deg); } }
693  .sres {
694    display: none; align-items: center; gap: 6px; white-space: nowrap;
695    font-family: "Bricolage Grotesque", sans-serif; font-weight: 700; font-size: 13.5px; color: #2f6fe0;
696    animation: qtilein .3s cubic-bezier(.34,1.56,.64,1);
697  }
698  .sres.on { display: flex; }
699  .sres svg { color: #30a46c; }
700
701  /* the people materialising in the side panel */
702  .swarm {
703    display: flex; flex-wrap: wrap; justify-content: center; gap: 5px;
704    max-width: 236px; align-self: center; margin-top: 14px;
705  }
706  .swarm i {
707    width: 13px; height: 13px; border-radius: 50%; display: block;
708    transform: scale(0); transition: transform .32s cubic-bezier(.34,1.56,.64,1);
709  }
710  .swarm i.on { transform: scale(1); }
711
712  /* the computed result: one hero number, one sentence, detail on demand */
713  .rhero {
714    padding: 26px 22px 24px; border-radius: 18px; text-align: center;
715    background: linear-gradient(135deg, #3a7bed 0%, #2f6fe0 45%, #245bce 100%); color: #fff;
716    box-shadow: 0 24px 48px -22px rgba(47,111,224,.75);
717  }
718  .rhero b {
719    display: block; font-family: "Bricolage Grotesque", sans-serif; font-weight: 800;
720    font-size: clamp(32px, 4.4vw, 44px); letter-spacing: -.025em; line-height: 1.02;
721  }
722  .rhero span { display: block; margin-top: 7px; font-size: 13px; color: rgba(255,255,255,.88); }
723  .rline {
724    margin-top: 18px; text-align: center; font-size: 15px; line-height: 1.55; color: #33425a;
725  }
726  .rline b { font-family: "Bricolage Grotesque", sans-serif; font-weight: 700; color: #16314d; }
727  .rtoggle {
728    display: block; margin: 14px auto 0; border: none; background: none; cursor: pointer;
729    font-family: "Bricolage Grotesque", sans-serif; font-weight: 600; font-size: 13.5px;
730    color: #2f6fe0; padding: 4px;
731  }
732  .rtoggle:hover { text-decoration: underline; }
733  .rrows { display: none; margin-top: 6px; }
734  .rrows.on { display: block; }
735  .rrow {
736    display: flex; align-items: center; gap: 11px; padding: 10px 0;
737    border-bottom: 1px solid rgba(22,49,77,.07); font-size: 13.5px; color: #33425a;
738  }
739  .rrow:last-of-type { border-bottom: none; }
740  .rrow .cico { width: 20px; height: 20px; border-radius: 6px; }
741  .rrow .rname { font-family: "Bricolage Grotesque", sans-serif; font-weight: 600; color: #16314d; }
742  .rrow b { margin-left: auto; font-family: "Bricolage Grotesque", sans-serif; font-weight: 700;
743            font-size: 13.5px; color: #2f6fe0; white-space: nowrap; padding-left: 10px; }
744  .rnote { margin-top: 12px; font-size: 11.5px; color: #8fa3b8; text-align: center; }
745
746  .qthanks { text-align: center; padding: 4px 0 0; }
747  .qthanks p { font-size: 14.5px; color: #5a6a80; line-height: 1.6; margin: 10px 0 18px; }
748  .qthanks iframe { width: 100%; height: 520px; border: 0; border-radius: 14px;
749    box-shadow: 0 0 0 1px rgba(22,49,77,.08); }
750
751  /* ===================== Mobile ===================== */
752  @media (max-width: 640px) {
753    /* background-attachment: fixed is janky / scales badly on iOS Safari */
754    body { background-attachment: scroll; }
755
756    /* the bigger headline is a taller block, so its centre sits lower and
757       clears the floating brand mark */
758    .title { top: 28%; width: 100vw; padding: 0 12px; }
759    /* as large as it can be while still wrapping to 2 lines, not 3 */
760    .title h1 { font-size: clamp(22px, 7.6vw, 34px); line-height: 1.08; }
761    .title p { margin-top: 13px; }
762    .btns { margin-top: 24px; gap: 10px; }
763    .btn { padding: 13px 22px; }
764
765    /* swap in the dedicated mobile artwork */
766    .hills--desk { display: none; }
767    .hills--mob { display: block; width: 100%; bottom: 0; }
768
769    /* "worked with" sits higher, logos a bit smaller, tighter spacing, faster scroll */
770    .worked { top: 49%; width: 94vw; gap: 10px; }
771    .worked .marquee { transform: scale(.84); }
772    .worked .logo { margin-right: 38px; }
773    .track { animation-duration: 16s; }
774
775    /* the story flows vertically on phones: no big wires, compact stacks */
776    .story-inner { width: 100%; height: 100%; transform: none; }
777    .phrase { top: max(9%, calc(env(safe-area-inset-top) + 46px)); font-size: clamp(25px, 7.8vw, 34px); padding: 0 18px; }
778    .phrase--sub { top: calc(max(9%, calc(env(safe-area-inset-top) + 46px)) + 46px); font-size: 14px; }
779    .wires { display: none; }
780
781    /* on phones only the monster mark floats, the wordmark would collide with content */
782    .brand { top: 14px; left: 14px; }
783    .brand img { width: 32px; height: 32px; border-radius: 10px; }
784    .brand span { display: none; }
785
786    /* the flow goes vertical on phones: chips + buyers first, then the
787       conversations + pipeline take over the same screen */
788    .layer--map { display: flex; align-items: center; justify-content: center; }
789    .map-track { position: static; width: auto; height: auto; }
790    .src-stage { display: flex; flex-direction: column; align-items: center; padding-top: 12vh; }
791    .chips6 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; width: min(92vw, 372px); }
792    .srcbox { position: static; width: auto; padding: 11px 13px 10px; border-radius: 13px; }
793    .srcbox strong { font-size: 10px; margin-bottom: 7px; }
794    .srcbox em { font-size: 10px; margin-top: 7px; }
795    .ltiles { gap: 5px; }
796    .lt { width: 23px; height: 23px; border-radius: 7px; }
797    .lt--fit { padding: 3px; }
798    .lt--word { font-size: 11px; }
799    .people3 { display: flex; flex-direction: column; gap: 11px; margin-top: 22px; }
800    .person { position: static; width: min(90vw, 320px); padding: 12px 16px; gap: 12px;
801              border-radius: 15px; }
802    .person .avatar { width: 38px; height: 38px; border-radius: 11px; }
803    .person b { font-size: 15px; }
804
805    .conv-stage { position: absolute; inset: 0; display: flex; flex-direction: column;
806                  align-items: center; justify-content: center; padding-top: 9vh; }
807    .chats { display: flex; flex-direction: column; gap: 10px; }
808    .chat { position: static; width: min(88vw, 300px); padding: 11px 13px 11px; border-radius: 15px; }
809    .chat-head { font-size: 12.5px; padding-bottom: 8px; margin-bottom: 9px; }
810    .cav { width: 22px; height: 22px; border-radius: 7px; font-size: 9.5px; }
811    .bub { font-size: 11.5px; padding: 7px 11px; }
812    .pipe-pos { position: static; margin-top: 20px; }
813    .dbox--pipeline { font-size: 20px; padding: 17px 36px; }
814
815    /* swap the wide CTA for a round back-to-top arrow */
816    .corner-cta { display: none; }
817    .to-top { display: inline-flex; }
818
819    /* the modal stacks: a slim live-system strip on top, questions below */
820    .qcard { flex-direction: column; border-radius: 20px; }
821    .qside { width: auto; flex-direction: row; align-items: center; gap: 12px;
822             padding: 14px 60px 14px 16px; }
823    .qside-label { display: none; }
824    .qtiles { flex: 1; gap: 6px; min-height: 0; overflow: hidden; max-height: 34px; }
825    .qtile { width: 34px; padding: 4px; border-radius: 9px; }
826    .qtile b, .qtile i { display: none; }
827    .qtiles-empty { font-size: 11px; padding-top: 0; }
828    .qwire, .qnode--conv, .swarm { display: none; }
829    .scanrow .swhat { display: none; }
830    .qnode { opacity: 1; transform: none; padding: 8px 13px; border-radius: 11px;
831             flex: 0 0 auto; }
832    .qnode b { font-size: 16px; }
833    .qnode .qn-l { font-size: 8.5px; margin-top: 1px; }
834    .qmain { padding: 24px 20px 22px; }
835    .srcgrid { grid-template-columns: repeat(3, 1fr); gap: 8px; }
836    .srcopt { padding: 12px 4px 10px; font-size: 12px; }
837    .rrow { font-size: 13px; }
838    .rrow .rsize { display: none; }
839    .rrow b { font-size: 13px; }
840    .qthanks iframe { height: 480px; }
841    /* one thumb-sized primary action per step */
842    .qnav .btn-primary { flex: 1; margin-left: 16px; justify-content: center; }
843    .examples { padding-top: 64px; }
844  }
845
846  /* desktop keeps the absolute one-wide-diagram layout */
847  @media (min-width: 641px) {
848    .src-stage, .chips6, .people3, .conv-stage, .chats { display: contents; }
849    .layer--map { display: block; }
850    /* the modal keeps one steady height so steps don't jump around */
851    .qcard { min-height: min(560px, 82vh); }
852  }
853
854  @media (hover: none) {
855    /* no sticky :hover state on touch devices */
856    .btn:hover, .corner-cta:hover { transform: none; }
857  }
858
859  @media (prefers-reduced-motion: reduce) {
860    .track { animation: none; }
861    .lpath--dot, .dot--1, .dot--2, .dot--3, .gd { animation: none; opacity: 0 !important; }
862    .dbox--pipeline.lit, .dbox--pipeline.lit::after, .ex-card.in .gping { animation: none; }
863    .ex-card.in .gline { animation-duration: .01s; }
864    .ex-card.in .gnode, .ex-card.in .gtip, .ex-card.in .gav { animation-duration: .01s; }
865    .rise { transition: none; opacity: 1; transform: none; }
866  }
867</style>
868</head>
869<body>
870
871  <a class="brand" href="#" onclick="window.scrollTo({top:0,behavior:'smooth'});return false;">
872    <img src="fav.png" alt="Tool Monsters" />
873    <span>Tool Monsters</span>
874  </a>
875
876  <div class="intro">
877    <div class="stage">
878      <div class="sky"></div>
879
880      <!-- "Worked with" + logo marquee, revealed in the valley -->
881      <div class="worked">
882        <p class="worked-label">Worked with</p>
883        <div class="marquee">
884          <div class="track">
885            <img class="logo logo--fullenrich" src="logos/fullenrich.svg" alt="FullEnrich" />
886            <img class="logo logo--gamma" src="logos/gamma.svg" alt="Gamma" />
887            <img class="logo logo--granola" src="logos/granola.png" alt="Granola" />
888            <img class="logo logo--clickup" src="logos/clickup.svg" alt="ClickUp" />
889            <img class="logo logo--tavus" src="logos/tavus.svg" alt="Tavus" />
890            <img class="logo logo--zapier" src="logos/zapier.svg" alt="Zapier" />
891            <img class="logo logo--wix" src="logos/wix.png" alt="Wix" />
892            <img class="logo logo--base44" src="logos/base44.png" alt="Base44" />
893            <!-- duplicate for a seamless loop -->
894            <img class="logo logo--fullenrich" src="logos/fullenrich.svg" alt="FullEnrich" />
895            <img class="logo logo--gamma" src="logos/gamma.svg" alt="Gamma" />
896            <img class="logo logo--granola" src="logos/granola.png" alt="Granola" />
897            <img class="logo logo--clickup" src="logos/clickup.svg" alt="ClickUp" />
898            <img class="logo logo--tavus" src="logos/tavus.svg" alt="Tavus" />
899            <img class="logo logo--zapier" src="logos/zapier.svg" alt="Zapier" />
900            <img class="logo logo--wix" src="logos/wix.png" alt="Wix" />
901            <img class="logo logo--base44" src="logos/base44.png" alt="Base44" />
902            <!-- third copy so one set is always wider than the viewport (no gap) -->
903            <img class="logo logo--fullenrich" src="logos/fullenrich.svg" alt="FullEnrich" />
904            <img class="logo logo--gamma" src="logos/gamma.svg" alt="Gamma" />
905            <img class="logo logo--granola" src="logos/granola.png" alt="Granola" />
906            <img class="logo logo--clickup" src="logos/clickup.svg" alt="ClickUp" />
907            <img class="logo logo--tavus" src="logos/tavus.svg" alt="Tavus" />
908            <img class="logo logo--zapier" src="logos/zapier.svg" alt="Zapier" />
909            <img class="logo logo--wix" src="logos/wix.png" alt="Wix" />
910            <img class="logo logo--base44" src="logos/base44.png" alt="Base44" />
911          </div>
912        </div>
913      </div>
914
915      <!-- mountains (desktop art + a dedicated mobile crop) -->
916      <img class="hills hills--desk" src="front.webp" alt="" />
917      <img class="hills hills--mob" src="Mobile.webp" alt="" />
918
919      <div class="title">
920        <h1>Your next customers <em>are already around you.</em></h1>
921        <p>We build GTM systems that turn your audience, network and market signals into sales conversations.
921</p>
922        <div class="btns">
923          <button class="btn btn-primary" data-open-quiz>Find my opportunities &rarr;</button>
924          <a class="btn btn-ghost" href="#how" id="see-how">See how it works</a>
925        </div>
926      </div>
927
928      <!-- the story phrases, crossfading above the diagram -->
929      <p class="phrase" id="ph1">They&rsquo;re already there.</p>
930      <p class="phrase phrase--sub" id="ph1sub">You already have the opportunities.</p>
931      <p class="phrase" id="ph2">We find the buyers.</p>
932      <p class="phrase" id="ph3">Then we start conversations.</p>
933      <p class="phrase" id="ph4">Pipeline.</p>
934
935      <!-- the story: one evolving diagram behind the mountains -->
936      <div class="story">
937        <div class="story-inner">
938
939          <!-- moments 1 + 2: six sources, then the buyers they hide -->
940          <div class="layer layer--map" id="layer-map">
941            <div class="map-track" id="map-track">
942              <svg class="wires" viewBox="0 0 2140 620" preserveAspectRatio="none" aria-hidden="true">
943                <path class="wire wa" pathLength="100" d="M364 110 C474 110, 510 182, 620 182" />
944                <path class="wire wa" pathLength="100" d="M364 235 C474 235, 510 310, 620 310" />
945                <path class="wire wa" pathLength="100" d="M364 360 C474 360, 510 310, 620 310" />
946                <path class="wire wa" pathLength="100" d="M364 485 C474 485, 510 438, 620 438" />
947                <path class="wire wb" pathLength="100" d="M880 182 C1000 182, 1060 144, 1180 144" />
948                <path class="wire wb" pathLength="100" d="M880 310 C1000 310, 1060 308, 1180 308" />
949                <path class="wire wb" pathLength="100" d="M880 438 C1000 438, 1060 472, 1180 472" />
950                <path class="wire wc" pathLength="100" d="M1430 144 C1550 144, 1610 296, 1730 296" />
951                <path class="wire wc" pathLength="100" d="M1430 308 C1550 308, 1610 310, 1730 310" />
952                <path class="wire wc" pathLength="100" d="M1430 472 C1550 472, 1610 324, 1730 324" />
953              </svg>
954
955              <div class="src-stage" id="src-stage">
956              <div class="chips6">
957                <div class="srcbox srcbox--1 sd" data-c="0">
958                  <strong>Audience</strong>
959                  <div class="ltiles">
960                    <span class="lt" title="Substack"><svg viewBox="0 0 24 24" width="15" height="15"><path fill="#FF6719" d="M2.5 3.5h19v2.7h-19zM2.5 8.3h19V11h-19zM2.5 13.1h19v7.9l-9.5-4.4-9.5 4.4z"/></svg></span>
961                    <span class="lt" title="beehiiv"><img src="logos/beehiiv.png" alt="beehiiv" /></span>
962                    <span class="lt" title="YouTube"><svg viewBox="0 0 24 24" width="17" height="17"><rect x="1" y="4.6" width="22" height="14.8" rx="4" fill="#FF0000"/><path d="M9.8 8.7v6.6L15.8 12z" fill="#fff"/></svg></span>
963                    <span class="lt" title="LinkedIn"><img src="logos/sq-linkedin.png" alt="LinkedIn" /></span>
964                  </div>
965                  <em>Subscribers, followers</em>
966                </div>
967                <div class="srcbox srcbox--2 sd" data-c="1">
968                  <strong>Network</strong>
969                  <div class="ltiles">
970                    <span class="lt" title="LinkedIn"><img src="logos/sq-linkedin.png" alt="LinkedIn" /></span>
971                    <span class="lt" title="X" style="background:#000"><svg viewBox="0 0 24 24" width="13" height="13"><path fill="#fff" d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg></span>
972                    <span class="lt lt--fit" title="Slack"><img src="logos/sq-slack.svg" alt="Slack" /></span>
973                  </div>
974                  <em>Connections, communities</em>
975                </div>
976                <div class="srcbox srcbox--3 sd" data-c="2">
977                  <strong>Relationships</strong>
978                  <div class="ltiles">
979                    <span class="lt lt--fit" title="Gmail"><img src="logos/sq-gmail.svg" alt="Gmail" /></span>
980                    <span class="lt" title="HubSpot"><img src="logos/sq-hubspot.png" alt="HubSpot" /></span>
981                    <span class="lt lt--fit" title="Fathom"><img src="logos/fathom.png" alt="Fathom" /></span>
982                    <span class="lt lt--fit" title="Salesforce"><img src="logos/sq-salesforce.svg" alt="Salesforce" /></span>
983                  </div>
984                  <em>Emails, CRM, calls</em>
985                </div>
986                <div class="srcbox srcbox--4 sd" data-c="3">
987                  <strong>Market signals</strong>
988                  <div class="ltiles">
989                    <span class="lt" title="LinkedIn"><img src="logos/sq-linkedin.png" alt="LinkedIn" /></span>
990                    <span class="lt lt--fit" title="Indeed"><svg viewBox="0 0 24 24" width="16" height="16"><path fill="#003A9B" d="M11.566 21.5633v-8.762c.2553.0231.5009.0346.758.0346 1.2225 0 2.3739-.3206 3.3506-.8928v9.6182c0 .8219-.1957 1.4287-.5757 1.8338-.378.4033-.8808.6049-1.491.6049-.6007 0-1.0766-.2016-1.468-.6183-.3781-.4032-.5739-1.01-.5739-1.8184zM11.589.5659c2.5447-.8929 5.4424-.8449 7.6186.987.405.3687.8673.8334 1.0515 1.3806.2207.6913-.7695-.073-.9057-.167-.71-.4532-1.4182-.8334-2.2127-1.0946C12.8614.3873 8.8122 2.709 6.2945 6.315c-1.0516 1.5939-1.7367 3.2721-2.299 5.1174-.0614.2017-.1094.4647-.2207.6413-.1113.2036-.048-.5453-.048-.5702.0845-.7623.2438-1.4997.4414-2.237C5.3292 5.3375 7.897 2.0655 11.5891.5658zm4.9281 7.0587c0 1.6686-1.353 3.0224-3.0205 3.0224-1.6677 0-3.0186-1.3538-3.0186-3.0224 0-1.6687 1.351-3.0224 3.0186-3.0224 1.6676 0 3.0205 1.3518 3.0205 3.0224Z"/></svg></span>
991                    <span class="lt lt--fit" title="Glassdoor"><svg viewBox="0 0 24 24" width="15" height="15"><path fill="#00A162" d="M14.1093.0006c-.0749-.0074-.1348.0522-.1348.127v3.451c0 .0673.0537.1194.121.127 2.619.172 4.6092.9501 4.6092 3.6814H13.086a.1343.1343 0 0 0-.1348.1347v8.9644c0 .0748.06.1347.1348.1347h10.0034c.0748 0 .1347-.0599.1347-.1347V7.342c0-2.2374-.7996-4.0558-2.4159-5.3279C19.3191.8469 17.0874.1428 14.1093.0006ZM.9107 7.387a.1342.1342 0 0 0-.1347.1347v8.9566c0 .0748.06.1347.1347.1347h5.6189c0 2.7313-1.9902 3.5094-4.6091 3.6815-.0674.0075-.1192.0596-.1192.127v3.451c0 .0747.06.1343.1348.1269 2.9781-.1422 5.2078-.8463 6.6969-2.0136 1.6163-1.272 2.4159-3.0905 2.4159-5.3278V7.5217a.1343.1343 0 0 0-.1348-.1347z"/></svg></span>
992                    <span class="lt lt--word" title="10times" style="color:#e0273a; font-size:11px">10</span>
993                  </div>
994                  <em>Competitors, hiring, events</em>
995                </div>
996              </div>
997
998              <div class="people3">
999                <div class="person person--1 sd" data-p="0">
1000                  <img class="avatar" src="people/elon.jpg" alt="Elon Musk" />
1001                  <div><b>Elon Musk</b><span class="role">CEO &middot; Tesla</span></div>
1002                </div>
1003                <div class="person person--2 sd" data-p="1">
1004                  <img class="avatar" src="people/melanie.jpg" alt="Melanie Perkins" />
1005                  <div><b>Melanie Perkins</b><span class="role">CEO &middot; Canva</span></div>
1006                </div>
1007                <div class="person person--3 sd" data-p="2">
1008                  <img class="avatar" src="people/sam.jpg" alt="Sam Altman" />
1009                  <div><b>Sam Altman</b><span class="role">CEO &middot; OpenAI</span></div>
1010                </div>
1011              </div>
1012              </div>
1013
1014              <div class="conv-stage" id="conv-stage">
1015              <div class="chats">
1016                <div class="chat chat--1 sd" data-ch="0">
1017                  <div class="chat-head"><img class="cav" src="people/elon.jpg" alt="" />Elon Musk</div>
1018                  <div class="bub bub--out sd">Saw the Optimus launch.</div>
1019                  <div class="bub bub--in sd">Good timing. Let&rsquo;s talk.</div>
1020                </div>
1021                <div class="chat chat--2 sd" data-ch="1">
1022                  <div class="chat-head"><img class="cav" src="people/melanie.jpg" alt="" />Melanie Perkins</div>
1023                  <div class="bub bub--out sd">Time to reconnect?</div>
1024                  <div class="bub bub--in sd">Yes. Book me in.</div>
1025                </div>
1026                <div class="chat chat--3 sd" data-ch="2">
1027                  <div class="chat-head"><img class="cav" src="people/sam.jpg" alt="" />Sam Altman</div>
1028                  <div class="bub bub--out sd">Saw the GPT-6 news.</div>
1029                  <div class="bub bub--in sd">Let&rsquo;s do a call.</div>
1030                </div>
1031              </div>
1032              <div class="pipe-pos">
1033                <div class="dbox dbox--final dbox--pipeline sd" id="pipe-box">Pipeline</div>
1034              </div>
1035              </div>
1036            </div>
1037          </div>
1038
1039        </div>
1040      </div>
1041
1042      <button class="corner-cta" data-open-quiz>Find my opportunities <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h13M12 6l6 6-6 6"/></svg></button>
1043      <button class="to-top" aria-label="Back to top" onclick="window.scrollTo({top:0,behavior:'smooth'})"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5M5 12l7-7 7 7"/></svg></button>
1044
1045    </div>
1046  </div>
1047
1048  <!-- ===================== Four tiny examples ===================== -->
1049  <section class="examples" id="examples">
1050    <p class="sec-eyebrow rise">Systems we build</p>
1051
1052    <svg width="0" height="0" style="position:absolute" aria-hidden="true"><defs>
1053      <linearGradient id="gdgrad" x1="0" y1="0" x2="1" y2="1">
1054        <stop offset="0" stop-color="#2f3358" /><stop offset="1" stop-color="#1c1e36" />
1055      </linearGradient>
1056    </defs></svg>
1057
1058    <div class="ex-grid">
1059
1060      <div class="ex-card">
1061        <div class="ex-head"><span class="name">Newsletter</span></div>
1062        <svg class="gflow gflow--l4" viewBox="0 0 430 486" role="img" aria-label="Newsletter workflow: 100K subscribers, identify people, ICP matches, conversations">
1063          <g class="gnode" style="--d:0s">
1064            <rect class="gtile" x="187" y="4" width="56" height="56" rx="16" />
1065            <path fill="#FF6719" d="M201 19h28v3.6h-28zM201 25.4h28V29h-28zM201 31.8h28v11.4l-14-6.4-14 6.4z"/>
1066            <rect class="gring" x="187" y="4" width="56" height="56" rx="16" />
1067          </g>
1068          <path class="gline ln-o" pathLength="1" style="--d:.15s" d="M215 66 V96" />
1069          <path class="gtip ln-o" style="--d:.32s" d="M210 91 L215 96 L220 91" />
1070          <g class="gnode" style="--d:.3s">
1071            <rect class="gcard" x="95" y="104" width="240" height="60" rx="14" />
1072            <text class="gt-title" x="215" y="130" data-count="100" data-suffix="K subscribers">100K subscribers</text>
1073            <text class="gt-sub" x="215" y="149">the audience you already own</text>
1074          </g>
1075          <path class="gline ln-o" pathLength="1" style="--d:.5s" d="M215 168 V198" />
1076          <path class="gtip ln-o" style="--d:.67s" d="M210 193 L215 198 L220 193" />
1077          <g class="gnode" style="--d:.65s">
1078            <rect class="gcard" x="95" y="206" width="240" height="60" rx="14" />
1079            <text class="gt-title" x="215" y="232">Identify people</text>
1080            <text class="gt-sub" x="215" y="251">readers become named leads</text>
1081          </g>
1082          <path class="gline ln-o" pathLength="1" style="--d:.85s" d="M215 270 V300" />
1083          <path class="gtip ln-o" style="--d:1.02s" d="M210 295 L215 300 L220 295" />
1084          <g class="gnode" style="--d:1s">
1085            <rect class="gcard" x="95" y="308" width="240" height="80" rx="14" />
1086            <text class="gt-title" x="215" y="334">ICP matches</text>
1087            <text class="gt-sub" x="215" y="353">the ones who fit what you sell</text>
1088            <circle class="gav gav--1" cx="201" cy="369" r="8" style="--d:1.35s" />
1089            <circle class="gav gav--2" cx="215" cy="369" r="8" style="--d:1.48s" />
1090            <circle class="gav gav--3" cx="229" cy="369" r="8" style="--d:1.61s" />
1091          </g>
1092          <path class="gline ln-n" pathLength="1" style="--d:1.2s" d="M215 392 V420" />
1093          <path class="gtip ln-n" style="--d:1.37s" d="M210 415 L215 420 L220 415" />
1094          <g class="gnode" style="--d:1.35s">
1095            <rect class="gdark" x="110" y="428" width="210" height="54" rx="16" />
1096            <text class="gt-term" x="215" y="452">Conversations</text>
1097            <rect class="gtermchip" x="157" y="460" width="116" height="15" rx="7.5" />
1098            <text class="gt-termchip" x="215" y="470.5">with the right people</text>
1099          </g>
1100        
1101        
1102          <g class="gdots">
1103          <path class="gd ln-o" style="--i:0" pathLength="100" fill="none" d="M215 66 V96" />
1104          <path class="gd ln-o" style="--i:1" pathLength="100" fill="none" d="M215 168 V198" />
1105          <path class="gd ln-o" style="--i:2" pathLength="100" fill="none" d="M215 270 V300" />
1106          <path class="gd ln-n" style="--i:3" pathLength="100" fill="none" d="M215 392 V420" />
1107          </g>
1108        </svg>
1109      </div>
1110
1111      <div class="ex-card">
1112        <div class="ex-head"><span class="name">Dormant relationships</span></div>
1113        <svg class="gflow gflow--l3" viewBox="0 0 430 400" role="img" aria-label="Dormant relationships workflow: email history and sales calls merge into a relationship graph, then conversations">
1114          <g class="gnode" style="--d:0s">
1115            <rect class="gtile" x="147" y="4" width="56" height="56" rx="16" />
1116            <image href="logos/sq-gmail.svg" x="160" y="17" width="30" height="30" />
1117            <rect class="gring" x="147" y="4" width="56" height="56" rx="16" />
1118          </g>
1119          <g class="gnode" style="--d:.1s">
1120            <rect class="gtile" x="227" y="4" width="56" height="56" rx="16" />
1121            <image href="logos/fathom.png" x="240" y="17" width="30" height="30" />
1122            <rect class="gring" x="227" y="4" width="56" height="56" rx="16" />
1123          </g>
1124          <path class="gline ln-n" pathLength="1" style="--d:.2s" d="M175 66 C175 88, 110 84, 110 112" />
1125          <path class="gline ln-n" pathLength="1" style="--d:.2s" d="M255 66 C255 88, 320 84, 320 112" />
1126          <path class="gtip ln-n" style="--d:.38s" d="M105 107 L110 112 L115 107" />
1127          <path class="gtip ln-n" style="--d:.38s" d="M315 107 L320 112 L325 107" />
1128          <g class="gnode" style="--d:.36s">
1129            <rect class="gcard" x="15" y="120" width="190" height="58" rx="14" />
1130            <text class="gt-title" x="110" y="145">Email history</text>
1131            <text class="gt-sub" x="110" y="164">threads, intros, old deals</text>
1132          </g>
1133          <g class="gnode" style="--d:.46s">
1134            <rect class="gcard" x="225" y="120" width="190" height="58" rx="14" />
1135            <text class="gt-title" x="320" y="145">Sales calls</text>
1136            <text class="gt-sub" x="320" y="164">transcripts and notes</text>
1137          </g>
1138          <path class="gline ln-n" pathLength="1" style="--d:.66s" d="M110 178 C110 204, 215 198, 215 224" />
1139          <path class="gline ln-n" pathLength="1" style="--d:.66s" d="M320 178 C320 204, 215 198, 215 224" />
1140          <path class="gtip ln-n" style="--d:.84s" d="M210 219 L215 224 L220 219" />
1141          <g class="gnode" style="--d:.82s">
1142            <rect class="gcard" x="95" y="232" width="240" height="80" rx="14" />
1143            <text class="gt-title" x="215" y="258">Relationship graph</text>
1144            <text class="gt-sub" x="215" y="277">everyone you already know</text>
1145            <circle class="gav gav--1" cx="201" cy="293" r="8" style="--d:1.35s" />
1146            <circle class="gav gav--2" cx="215" cy="293" r="8" style="--d:1.48s" />
1147            <circle class="gav gav--3" cx="229" cy="293" r="8" style="--d:1.61s" />
1148          </g>
1149          <path class="gline ln-n" pathLength="1" style="--d:1.02s" d="M215 316 V344" />
1150          <path class="gtip ln-n" style="--d:1.19s" d="M210 339 L215 344 L220 339" />
1151          <g class="gnode" style="--d:1.17s">
1152            <rect class="gdark" x="110" y="352" width="210" height="54" rx="16" />
1153            <text class="gt-term" x="215" y="376">Conversations</text>
1154            <rect class="gtermchip" x="167" y="384" width="96" height="15" rx="7.5" />
1155            <text class="gt-termchip" x="215" y="394.5">warm, not cold</text>
1156          </g>
1157        
1158        
1159          <g class="gdots">
1160          <path class="gd ln-n" style="--i:0" pathLength="100" fill="none" d="M175 66 C175 88, 110 84, 110 112" />
1161          <path class="gd ln-n" style="--i:0" pathLength="100" fill="none" d="M255 66 C255 88, 320 84, 320 112" />
1162          <path class="gd ln-n" style="--i:1" pathLength="100" fill="none" d="M110 178 C110 204, 215 198, 215 224" />
1163          <path class="gd ln-n" style="--i:1" pathLength="100" fill="none" d="M320 178 C320 204, 215 198, 215 224" />
1164          <path class="gd ln-n" style="--i:2" pathLength="100" fill="none" d="M215 316 V344" />
1165          </g>
1166        </svg>
1167      </div>
1168
1169      <div class="ex-card">
1170        <div class="ex-head"><span class="name">Competitor monitoring</span></div>
1171        <svg class="gflow gflow--l4" viewBox="0 0 430 470" role="img" aria-label="Competitor monitoring workflow: a monitored LinkedIn profile posts, relevant engagers become ICP matches, then conversations">
1172          <g class="gnode" style="--d:0s">
1173            <clipPath id="c3li"><rect x="187" y="8" width="56" height="56" rx="16" /></clipPath>
1174            <image href="logos/sq-linkedin.png" x="187" y="8" width="56" height="56" clip-path="url(#c3li)" />
1175            <rect class="gring" x="187" y="8" width="56" height="56" rx="16" />
1176            <circle cx="243" cy="10" r="5" fill="#e5484d" stroke="#fff" stroke-width="2" />
1177            <circle class="gping" cx="243" cy="10" r="5" />
1178          </g>
1179          <path class="gline ln-b" pathLength="1" style="--d:.2s" d="M215 70 V98" />
1180          <path class="gtip ln-b" style="--d:.37s" d="M210 93 L215 98 L220 93" />
1181          <g class="gnode" style="--d:.35s">
1182            <rect class="gcard" x="95" y="106" width="240" height="58" rx="14" />
1183            <text class="gt-title" x="215" y="131">New post</text>
1184            <text class="gt-sub" x="215" y="150">every morning, the last 24 h</text>
1185          </g>
1186          <path class="gline ln-b" pathLength="1" style="--d:.55s" d="M215 164 V192" />
1187          <path class="gtip ln-b" style="--d:.72s" d="M210 187 L215 192 L220 187" />
1188          <g class="gnode" style="--d:.7s">
1189            <rect class="gcard" x="95" y="200" width="240" height="80" rx="14" />
1190            <text class="gt-title" x="215" y="226">Relevant engagers</text>
1191            <text class="gt-sub" x="215" y="245">who liked, who commented</text>
1192            <circle class="gav gav--1" cx="201" cy="261" r="8" style="--d:1.4s" />
1193            <circle class="gav gav--2" cx="215" cy="261" r="8" style="--d:1.53s" />
1194            <circle class="gav gav--3" cx="229" cy="261" r="8" style="--d:1.66s" />
1195          </g>
1196          <path class="gline ln-b" pathLength="1" style="--d:.9s" d="M215 280 V308" />
1197          <path class="gtip ln-b" style="--d:1.07s" d="M210 303 L215 308 L220 303" />
1198          <g class="gnode" style="--d:1.05s">
1199            <rect class="gcard" x="95" y="316" width="240" height="58" rx="14" />
1200            <text class="gt-title" x="215" y="341">ICP matches</text>
1201            <text class="gt-sub" x="215" y="360">size, role, market fit</text>
1202          </g>
1203          <path class="gline ln-n" pathLength="1" style="--d:1.25s" d="M215 374 V402" />
1204          <path class="gtip ln-n" style="--d:1.42s" d="M210 397 L215 402 L220 397" />
1205          <g class="gnode" style="--d:1.4s">
1206            <rect class="gdark" x="110" y="410" width="210" height="54" rx="16" />
1207            <text class="gt-term" x="215" y="434">Conversations</text>
1208            <rect class="gtermchip" x="157" y="442" width="116" height="15" rx="7.5" />
1209            <text class="gt-termchip" x="215" y="452.5">with the right people</text>
1210          </g>
1211        
1212        
1213          <g class="gdots">
1214          <path class="gd ln-b" style="--i:0" pathLength="100" fill="none" d="M215 70 V98" />
1215          <path class="gd ln-b" style="--i:1" pathLength="100" fill="none" d="M215 164 V192" />
1216          <path class="gd ln-b" style="--i:2" pathLength="100" fill="none" d="M215 280 V308" />
1217          <path class="gd ln-n" style="--i:3" pathLength="100" fill="none" d="M215 374 V402" />
1218          </g>
1219        </svg>
1220      </div>
1221
1222      <div class="ex-card">
1223        <div class="ex-head"><span class="name">Hiring signals</span></div>
1224        <svg class="gflow gflow--l4" viewBox="0 0 430 470" role="img" aria-label="Hiring signals workflow: nine job boards feed job posting signals, the decision-maker is identified, then a conversation">
1225          <g class="gnode" style="--d:0s">
1226            <clipPath id="c4li"><rect x="86" y="6" width="48" height="48" rx="14" /></clipPath>
1227            <image href="logos/sq-linkedin.png" x="86" y="6" width="48" height="48" clip-path="url(#c4li)" />
1228            <rect class="gring" x="86" y="6" width="48" height="48" rx="14" />
1229          </g>
1230          <g class="gnode" style="--d:.08s">
1231            <rect class="gtile" x="156" y="6" width="48" height="48" rx="14" />
1232            <g transform="translate(166,16) scale(1.1667)"><path fill="#003A9B" d="M11.566 21.5633v-8.762c.2553.0231.5009.0346.758.0346 1.2225 0 2.3739-.3206 3.3506-.8928v9.6182c0 .8219-.1957 1.4287-.5757 1.8338-.378.4033-.8808.6049-1.491.6049-.6007 0-1.0766-.2016-1.468-.6183-.3781-.4032-.5739-1.01-.5739-1.8184zM11.589.5659c2.5447-.8929 5.4424-.8449 7.6186.987.405.3687.8673.8334 1.0515 1.3806.2207.6913-.7695-.073-.9057-.167-.71-.4532-1.4182-.8334-2.2127-1.0946C12.8614.3873 8.8122 2.709 6.2945 6.315c-1.0516 1.5939-1.7367 3.2721-2.299 5.1174-.0614.2017-.1094.4647-.2207.6413-.1113.2036-.048-.5453-.048-.5702.0845-.7623.2438-1.4997.4414-2.237C5.3292 5.3375 7.897 2.0655 11.5891.5658zm4.9281 7.0587c0 1.6686-1.353 3.0224-3.0205 3.0224-1.6677 0-3.0186-1.3538-3.0186-3.0224 0-1.6687 1.351-3.0224 3.0186-3.0224 1.6676 0 3.0205 1.3518 3.0205 3.0224Z"/></g>
1233            <rect class="gring" x="156" y="6" width="48" height="48" rx="14" />
1234          </g>
1235          <g class="gnode" style="--d:.16s">
1236            <rect class="gtile" x="226" y="6" width="48" height="48" rx="14" />
1237            <g transform="translate(238,18) scale(1)"><path fill="#00A162" d="M14.1093.0006c-.0749-.0074-.1348.0522-.1348.127v3.451c0 .0673.0537.1194.121.127 2.619.172 4.6092.9501 4.6092 3.6814H13.086a.1343.1343 0 0 0-.1348.1347v8.9644c0 .0748.06.1347.1348.1347h10.0034c.0748 0 .1347-.0599.1347-.1347V7.342c0-2.2374-.7996-4.0558-2.4159-5.3279C19.3191.8469 17.0874.1428 14.1093.0006ZM.9107 7.387a.1342.1342 0 0 0-.1347.1347v8.9566c0 .0748.06.1347.1347.1347h5.6189c0 2.7313-1.9902 3.5094-4.6091 3.6815-.0674.0075-.1192.0596-.1192.127v3.451c0 .0747.06.1343.1348.1269 2.9781-.1422 5.2078-.8463 6.6969-2.0136 1.6163-1.272 2.4159-3.0905 2.4159-5.3278V7.5217a.1343.1343 0 0 0-.1348-.1347z"/></g>
1238            <rect class="gring" x="226" y="6" width="48" height="48" rx="14" />
1239          </g>
1240          <g class="gnode" style="--d:.24s">
1241            <rect class="gtile" x="296" y="6" width="48" height="48" rx="14" />
1242            <rect class="gring" x="296" y="6" width="48" height="48" rx="14" />
1243            <text class="gt-more" x="320" y="30">+6</text>
1244            <text class="gt-more-sub" x="320" y="43">JOB BOARDS</text>
1245          </g>
1246          <path class="gline ln-n" pathLength="1" style="--d:.36s" d="M110 58 C110 80, 215 76, 215 100" />
1247          <path class="gline ln-n" pathLength="1" style="--d:.36s" d="M180 58 C180 82, 215 80, 215 100" />
1248          <path class="gline ln-n" pathLength="1" style="--d:.36s" d="M250 58 C250 82, 215 80, 215 100" />
1249          <path class="gline ln-n" pathLength="1" style="--d:.36s" d="M320 58 C320 80, 215 76, 215 100" />
1250          <path class="gtip ln-n" style="--d:.54s" d="M210 95 L215 100 L220 95" />
1251          <g class="gnode" style="--d:.52s">
1252            <rect class="gcard" x="95" y="108" width="240" height="58" rx="14" />
1253            <text class="gt-title" x="215" y="133">Job posting signals</text>
1254            <text class="gt-sub" x="215" y="152">every morning, jobs posted in the last 24 h</text>
1255          </g>
1256          <path class="gline ln-n" pathLength="1" style="--d:.72s" d="M215 166 V196" />
1257          <path class="gtip ln-n" style="--d:.89s" d="M210 191 L215 196 L220 191" />
1258          <g class="gnode" style="--d:.86s">
1259            <rect class="gcard" x="95" y="204" width="240" height="58" rx="14" />
1260            <text class="gt-title" x="215" y="229">Relevant company</text>
1261            <text class="gt-sub" x="215" y="248">the ones worth your time</text>
1262          </g>
1263          <path class="gline ln-n" pathLength="1" style="--d:1.06s" d="M215 262 V292" />
1264          <path class="gtip ln-n" style="--d:1.23s" d="M210 287 L215 292 L220 287" />
1265          <g class="gnode" style="--d:1.2s">
1266            <rect class="gcard" x="95" y="300" width="240" height="74" rx="14" />
1267            <text class="gt-title" x="215" y="326">Decision-maker</text>
1268            <text class="gt-sub" x="215" y="345">the person who signs</text>
1269            <circle class="gav gav--1" cx="215" cy="359" r="8" style="--d:1.65s" />
1270          </g>
1271          <path class="gline ln-n" pathLength="1" style="--d:1.4s" d="M215 374 V402" />
1272          <path class="gtip ln-n" style="--d:1.57s" d="M210 397 L215 402 L220 397" />
1273          <g class="gnode" style="--d:1.55s">
1274            <rect class="gdark" x="110" y="410" width="210" height="54" rx="16" />
1275            <text class="gt-term" x="215" y="434">Conversation</text>
1276            <rect class="gtermchip" x="152" y="442" width="126" height="15" rx="7.5" />
1277            <text class="gt-termchip" x="215" y="452.5">at exactly the right time</text>
1278          </g>
1279        
1280        
1281          <g class="gdots">
1282          <path class="gd ln-n" style="--i:0" pathLength="100" fill="none" d="M110 58 C110 80, 215 76, 215 100" />
1283          <path class="gd ln-n" style="--i:0" pathLength="100" fill="none" d="M180 58 C180 82, 215 80, 215 100" />
1284          <path class="gd ln-n" style="--i:0" pathLength="100" fill="none" d="M250 58 C250 82, 215 80, 215 100" />
1285          <path class="gd ln-n" style="--i:0" pathLength="100" fill="none" d="M320 58 C320 80, 215 76, 215 100" />
1286          <path class="gd ln-n" style="--i:1" pathLength="100" fill="none" d="M215 166 V196" />
1287          <path class="gd ln-n" style="--i:2" pathLength="100" fill="none" d="M215 262 V292" />
1288          <path class="gd ln-n" style="--i:3" pathLength="100" fill="none" d="M215 374 V402" />
1289          </g>
1290        </svg>
1291      </div>
1292
1293      <div class="ex-card">
1294        <div class="ex-head"><span class="name">LinkedIn export data</span></div>
1295        <svg class="gflow gflow--l3" viewBox="0 0 430 386" role="img" aria-label="LinkedIn export workflow: your connections export becomes ICP matches, then conversations">
1296          <g class="gnode" style="--d:0s">
1297            <clipPath id="c5li"><rect x="187" y="4" width="56" height="56" rx="16" /></clipPath>
1298            <image href="logos/sq-linkedin.png" x="187" y="4" width="56" height="56" clip-path="url(#c5li)" />
1299            <rect class="gring" x="187" y="4" width="56" height="56" rx="16" />
1300          </g>
1301          <path class="gline ln-b" pathLength="1" style="--d:.15s" d="M215 66 V96" />
1302          <path class="gtip ln-b" style="--d:.32s" d="M210 91 L215 96 L220 91" />
1303          <g class="gnode" style="--d:.3s">
1304            <rect class="gcard" x="95" y="104" width="240" height="60" rx="14" />
1305            <text class="gt-title" x="215" y="130">Connections export</text>
1306            <text class="gt-sub" x="215" y="149">your full 1st-degree list</text>
1307          </g>
1308          <path class="gline ln-b" pathLength="1" style="--d:.5s" d="M215 168 V198" />
1309          <path class="gtip ln-b" style="--d:.67s" d="M210 193 L215 198 L220 193" />
1310          <g class="gnode" style="--d:.65s">
1311            <rect class="gcard" x="95" y="206" width="240" height="80" rx="14" />
1312            <text class="gt-title" x="215" y="232">ICP matches</text>
1313            <text class="gt-sub" x="215" y="251">the buyers hiding in it</text>
1314            <circle class="gav gav--1" cx="201" cy="267" r="8" style="--d:1.15s" />
1315            <circle class="gav gav--2" cx="215" cy="267" r="8" style="--d:1.28s" />
1316            <circle class="gav gav--3" cx="229" cy="267" r="8" style="--d:1.41s" />
1317          </g>
1318          <path class="gline ln-n" pathLength="1" style="--d:.85s" d="M215 290 V318" />
1319          <path class="gtip ln-n" style="--d:1.02s" d="M210 313 L215 318 L220 313" />
1320          <g class="gnode" style="--d:1s">
1321            <rect class="gdark" x="110" y="326" width="210" height="54" rx="16" />
1322            <text class="gt-term" x="215" y="350">Conversations</text>
1323            <rect class="gtermchip" x="160" y="358" width="110" height="15" rx="7.5" />
1324            <text class="gt-termchip" x="215" y="368.5">already connected</text>
1325          </g>
1326        
1327        
1328          <g class="gdots">
1329          <path class="gd ln-b" style="--i:0" pathLength="100" fill="none" d="M215 66 V96" />
1330          <path class="gd ln-b" style="--i:1" pathLength="100" fill="none" d="M215 168 V198" />
1331          <path class="gd ln-n" style="--i:2" pathLength="100" fill="none" d="M215 290 V318" />
1332          </g>
1333        </svg>
1334      </div>
1335
1336      <div class="ex-card">
1337        <div class="ex-head"><span class="name">Conferences</span></div>
1338        <svg class="gflow gflow--l3" viewBox="0 0 430 386" role="img" aria-label="Conferences workflow: the conference calendar becomes speakers and attendees, then conversations before the event">
1339          <g class="gnode" style="--d:0s">
1340            <rect class="gtile" x="147" y="4" width="56" height="56" rx="16" />
1341            <text class="gt-more" x="175" y="32" style="fill:#e0273a; font-size:17px">10</text>
1342            <text class="gt-more-sub" x="175" y="46" style="font-size:8px">TIMES</text>
1343            <rect class="gring" x="147" y="4" width="56" height="56" rx="16" />
1344          </g>
1345          <g class="gnode" style="--d:.1s">
1346            <rect class="gtile" x="227" y="4" width="56" height="56" rx="16" style="fill:url(#gdgrad)" />
1347            <g fill="none" stroke="#fff" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
1348              <rect x="241" y="19" width="28" height="27" rx="3" />
1349              <path d="M261 15v6M249 15v6M241 28h28" />
1350            </g>
1351          </g>
1352          <path class="gline ln-n" pathLength="1" style="--d:.2s" d="M175 66 C175 86, 215 82, 215 96" />
1353          <path class="gline ln-n" pathLength="1" style="--d:.2s" d="M255 66 C255 86, 215 82, 215 96" />
1354          <path class="gtip ln-n" style="--d:.38s" d="M210 91 L215 96 L220 91" />
1355          <g class="gnode" style="--d:.36s">
1356            <rect class="gcard" x="95" y="104" width="240" height="60" rx="14" />
1357            <text class="gt-title" x="215" y="130">Conference calendar</text>
1358            <text class="gt-sub" x="215" y="149">your market, the next 6 months</text>
1359          </g>
1360          <path class="gline ln-n" pathLength="1" style="--d:.56s" d="M215 168 V198" />
1361          <path class="gtip ln-n" style="--d:.73s" d="M210 193 L215 198 L220 193" />
1362          <g class="gnode" style="--d:.71s">
1363            <rect class="gcard" x="95" y="206" width="240" height="80" rx="14" />
1364            <text class="gt-title" x="215" y="232">Speakers + attendees</text>
1365            <text class="gt-sub" x="215" y="251">name, title and company</text>
1366            <circle class="gav gav--1" cx="201" cy="267" r="8" style="--d:1.2s" />
1367            <circle class="gav gav--2" cx="215" cy="267" r="8" style="--d:1.33s" />
1368            <circle class="gav gav--3" cx="229" cy="267" r="8" style="--d:1.46s" />
1369          </g>
1370          <path class="gline ln-n" pathLength="1" style="--d:.91s" d="M215 290 V318" />
1371          <path class="gtip ln-n" style="--d:1.08s" d="M210 313 L215 318 L220 313" />
1372          <g class="gnode" style="--d:1.06s">
1373            <rect class="gdark" x="110" y="326" width="210" height="54" rx="16" />
1374            <text class="gt-term" x="215" y="350">Conversations</text>
1375            <rect class="gtermchip" x="163" y="358" width="104" height="15" rx="7.5" />
1376            <text class="gt-termchip" x="215" y="368.5">before the event</text>
1377          </g>
1378        
1379        
1380          <g class="gdots">
1381          <path class="gd ln-n" style="--i:0" pathLength="100" fill="none" d="M175 66 C175 86, 215 82, 215 96" />
1382          <path class="gd ln-n" style="--i:0" pathLength="100" fill="none" d="M255 66 C255 86, 215 82, 215 96" />
1383          <path class="gd ln-n" style="--i:1" pathLength="100" fill="none" d="M215 168 V198" />
1384          <path class="gd ln-n" style="--i:2" pathLength="100" fill="none" d="M215 290 V318" />
1385          </g>
1386        </svg>
1387      </div>
1388
1389    </div>
1390
1391    <div class="foot-bar">
1392      <span class="foot-brand"><img src="fav.png" alt="" />&copy; 2026 Tool Monsters</span>
1393      <span><a href="mailto:[email protected]">[email protected]</a></span>
1394    </div>
1395  </section>
1396
1397  <!-- ===================== "Find my opportunities" modal ===================== -->
1398  <div class="veil" id="veil" role="dialog" aria-modal="true" aria-label="Find my opportunities">
1399    <div class="qcard">
1400      <button class="qclose" id="qclose" aria-label="Close">&#10005;</button>
1401
1402      <!-- the visitor's own system, drawn live from their answers -->
1403      <aside class="qside" aria-hidden="true">
1404        <p class="qside-label">Your system</p>
1405        <div class="qtiles" id="qtiles"></div>
1406        <div class="swarm" id="qswarm"></div>
1407        <div class="qwire" id="qwire1"></div>
1408        <div class="qnode" id="qnode">
1409          <b id="qcount">&ndash;</b>
1410          <span class="qn-l" id="qcount-l">potential buyers around you</span>
1411          <span class="qn-m" id="qmoney"></span>
1412        </div>
1413        <div class="qwire" id="qwire2"></div>
1414        <div class="qnode qnode--conv" id="qconv">Conversations</div>
1415      </aside>
1416
1417      <div class="qmain">
1418        <div class="qprogress"><i id="qbar"></i></div>
1419        <div id="qbody"><!-- steps rendered here --></div>
1420      </div>
1421    </div>
1422  </div>
1423
1424  
1424<script>
1425    const intro = document.querySelector('.intro');
1426    const stage = document.querySelector('.stage');
1427    const heroTitle = document.querySelector('.title');
1428    const storyInner = document.querySelector('.story-inner');
1429
1430    function clamp(v, a, b) { return Math.min(Math.max(v, a), b); }
1431    // progress of p through the window [a, b]
1432    function sub(p, a, b) { return clamp((p - a) / (b - a), 0, 1); }
1433
1434    /* ---------- story element refs ---------- */
1435    const ph1 = document.getElementById('ph1');
1436    const ph1sub = document.getElementById('ph1sub');
1437    const ph2 = document.getElementById('ph2');
1438    const ph3 = document.getElementById('ph3');
1439    const ph4 = document.getElementById('ph4');
1440    const chips = [...document.querySelectorAll('[data-c]')];
1441    const wiresA = [...document.querySelectorAll('.wa')];
1442    const wiresB = [...document.querySelectorAll('.wb')];
1443    const wiresC = [...document.querySelectorAll('.wc')];
1444    const mapTrack = document.getElementById('map-track');
1445    const srcStage = document.getElementById('src-stage');
1446    const convStage = document.getElementById('conv-stage');
1447    const personCards = [...document.querySelectorAll('[data-p]')];
1448    const chatCards = [...document.querySelectorAll('[data-ch]')];
1449    const chatBubs = chatCards.map(c => [...c.querySelectorAll('.bub')]);
1450    const pipeBox = document.getElementById('pipe-box');
1451
1452    function fade(el, o, y) {
1453      el.style.opacity = o.toFixed(3);
1454      el.style.transform = y ? 'translateY(' + ((1 - o) * y).toFixed(1) + 'px)' : '';
1455    }
1456
1457    /* ---------- scene lengths (recomputed on resize) ----------
1458       hero zoom, then four beats of one left-to-right flow, then a short hold. */
1459    let heroScroll = 0, L1 = 0, L2 = 0, L3 = 0, L4 = 0, holdScroll = 0, totalScroll = 0;
1460    function measure() {
1461      const vh = window.innerHeight;
1462      heroScroll = Math.round(vh * 0.9);
1463      L1 = Math.round(vh * 0.85);
1464      L2 = Math.round(vh * 0.95);
1465      L3 = Math.round(vh * 0.95);
1466      L4 = Math.round(vh * 1.0);
1467      holdScroll = Math.round(vh * 0.4);
1468      totalScroll = heroScroll + L1 + L2 + L3 + L4 + holdScroll;
1469      intro.style.height = (totalScroll + vh) + 'px';
1470      // scale the story so the whole diagram always fits one screen
1471      if (window.innerWidth > 640) {
1472        const s = Math.min((window.innerWidth * 0.92) / 980, (vh * 0.82) / 620, 1);
1473        storyInner.style.setProperty('--story-scale', clamp(s, 0.3, 1).toFixed(3));
1474      } else {
1475        storyInner.style.removeProperty('--story-scale');
1476      }
1477    }
1478
1479    let target = 0, curr = 0, rafId = null;
1480    function readTarget() {
1481      target = clamp(-intro.getBoundingClientRect().top, 0, totalScroll);
1482    }
1483
1484    function apply(s) {
1485      /* --- hero zoom (unchanged mechanic) --- */
1486      const heroP = clamp(s / heroScroll, 0, 1);
1487      stage.style.setProperty('--title-y', (-heroP * 26) + 'vh');
1488      stage.style.setProperty('--title-o', clamp(1 - heroP * 2.2, 0, 1).toFixed(3));
1489      stage.style.setProperty('--hills-scale', (1.0 + heroP * 1.5).toFixed(4));
1490      stage.style.setProperty('--sky-scale', (1.0 + heroP * 0.35).toFixed(4));
1491      stage.style.setProperty('--hills-o', (1 - clamp((heroP - 0.6) / 0.28, 0, 1)).toFixed(4));
1492      stage.style.setProperty('--worked-o', clamp(1 - heroP * 2.2, 0, 1).toFixed(4));
1493      heroTitle.classList.toggle('gone', heroP > 0.3);
1494
1495      /* --- story reveal, right as the mountains vanish --- */
1496      stage.style.setProperty('--story-o', clamp((heroP - 0.55) / 0.35, 0, 1).toFixed(4));
1497
1498      const t = s - heroScroll;
1499      const p1 = clamp(t / L1, 0, 1);
1500      const p2 = clamp((t - L1) / L2, 0, 1);
1501      const p3 = clamp((t - L1 - L2) / L3, 0, 1);
1502      const p4 = clamp((t - L1 - L2 - L3) / L4, 0, 1);
1503      const ease = k => 1 - Math.pow(1 - k, 3);
1504
1505      /* beat 1: the four source groups pop in, one after another */
1506      chips.forEach((el, i) => fade(el, sub(p1, 0.06 + i * 0.16, 0.3 + i * 0.16), 16));
1507
1508      /* one continuous flow: each beat pans the camera further right */
1509      if (window.innerWidth > 640) {
1510        const tx = 240
1511          - 240 * ease(sub(p2, 0, 0.4))     // sources -> buyers
1512          - 540 * ease(sub(p3, 0, 0.42))    // buyers -> conversations
1513          - 610 * ease(sub(p4, 0, 0.45));   // conversations -> pipeline
1514        mapTrack.style.transform = 'translateX(' + tx.toFixed(1) + 'px)';
1515        srcStage.style.opacity = ''; srcStage.style.visibility = '';
1516        convStage.style.opacity = '';
1516 convStage.style.visibility = '';
1517      } else {
1518        /* phones: no pan, the conversations take over the same screen */
1519        mapTrack.style.transform = '';
1520        srcStage.style.opacity = (1 - sub(p3, 0, 0.25)).toFixed(3);
1521        srcStage.style.visibility = p3 >= 0.25 ? 'hidden' : 'visible';
1522        convStage.style.opacity = sub(p3, 0.15, 0.35).toFixed(3);
1523        convStage.style.visibility = p3 < 0.15 ? 'hidden' : 'visible';
1524      }
1525
1526      /* beat 2: wires fan out of the sources, the buyers appear */
1527      wiresA.forEach((w, i) => {
1528        w.style.strokeDashoffset = (100 * (1 - sub(p2, 0.12 + i * 0.05, 0.48 + i * 0.05))).toFixed(1);
1529      });
1530      personCards.forEach((el, i) => fade(el, sub(p2, 0.45 + i * 0.12, 0.68 + i * 0.12), 20));
1531
1532      /* beat 3: wires continue right, the chats open, messages land like real replies */
1533      wiresB.forEach((w, i) => {
1534        w.style.strokeDashoffset = (100 * (1 - sub(p3, 0.16 + i * 0.06, 0.46 + i * 0.06))).toFixed(1);
1535      });
1536      chatCards.forEach((el, i) => fade(el, sub(p3, 0.36 + i * 0.07, 0.52 + i * 0.07), 16));
1537      chatBubs.forEach((arr, i) => arr.forEach((b, j) =>
1538        fade(b, sub(p3, 0.52 + i * 0.05 + j * 0.15, 0.62 + i * 0.05 + j * 0.15), 8)));
1539
1540      /* beat 4: the three conversations wire into one pipeline */
1541      wiresC.forEach((w, i) => {
1542        w.style.strokeDashoffset = (100 * (1 - sub(p4, 0.2 + i * 0.06, 0.5 + i * 0.06))).toFixed(1);
1543      });
1544      const pipe = sub(p4, 0.52, 0.7);
1545      pipeBox.style.opacity = pipe.toFixed(3);
1546      pipeBox.style.transform = 'scale(' + (0.9 + pipe * 0.1).toFixed(3) + ')';
1547      pipeBox.classList.toggle('lit', pipe >= 0.99);
1548
1549      /* the phrases, crossfading in sync with the flow */
1550      const storyO = clamp((heroP - 0.55) / 0.35, 0, 1);
1551      ph1.style.opacity = (Math.min(sub(p1, 0.02, 0.16), 1 - sub(p2, 0, 0.2)) * storyO).toFixed(3);
1552      ph1sub.style.opacity = (Math.min(sub(p1, 0.3, 0.5), 1 - sub(p2, 0, 0.16)) * storyO).toFixed(3);
1553      ph2.style.opacity = (Math.min(sub(p2, 0.24, 0.42), 1 - sub(p3, 0, 0.18)) * storyO).toFixed(3);
1554      ph3.style.opacity = (Math.min(sub(p3, 0.3, 0.48), 1 - sub(p4, 0, 0.18)) * storyO).toFixed(3);
1555      ph4.style.opacity = (sub(p4, 0.4, 0.6) * storyO).toFixed(3);
1556    }
1557
1558    function loop() {
1559      curr += (target - curr) * 0.1;            // smoothing (lower = smoother)
1560      if (Math.abs(target - curr) < 0.5) curr = target;
1561      apply(curr);
1562      rafId = (curr !== target) ? requestAnimationFrame(loop) : null;
1563    }
1564
1565    function kick() { readTarget(); if (rafId === null) rafId = requestAnimationFrame(loop); }
1566
1567    // "See how it works" scrolls past the zoom, into the start of the story
1568    document.getElementById('see-how').addEventListener('click', (e) => {
1569      e.preventDefault();
1570      window.scrollTo({ top: heroScroll + Math.round(L1 * 0.45), behavior: 'smooth' });
1571    });
1572
1573    window.addEventListener('scroll', kick, { passive: true });
1574    window.addEventListener('resize', () => { measure(); kick(); });
1575    window.addEventListener('load', () => { measure(); readTarget(); curr = target; apply(curr); });
1576    if (document.fonts && document.fonts.ready) document.fonts.ready.then(() => { measure(); kick(); });
1577
1578    // initial settle
1579    measure(); readTarget(); curr = target; apply(curr);
1580
1581    /* ---------- examples + sections reveal on scroll ---------- */
1582    function runCounter(el) {
1583      const targetN = parseFloat(el.dataset.count);
1584      const suffix = el.dataset.suffix || '';
1585      const dur = 1300, t0 = performance.now();
1586      function frame(now) {
1587        const k = Math.min((now - t0) / dur, 1);
1588        const eased = 1 - Math.pow(1 - k, 3);           // easeOutCubic
1589        el.textContent = Math.round(targetN * eased) + suffix;
1590        if (k < 1) requestAnimationFrame(frame);
1591      }
1592      requestAnimationFrame(frame);
1593    }
1594    const exObserver = new IntersectionObserver((entries) => {
1595      entries.forEach(en => {
1596        if (!en.isIntersecting) return;
1597        en.target.classList.add('in');
1598        en.target.querySelectorAll('[data-count]').forEach(runCounter);
1599        exObserver.unobserve(en.target);
1600      });
1601    }, { threshold: 0.35 });
1602    document.querySelectorAll('.ex-card').forEach(c => exObserver.observe(c));
1603
1604    const riseObserver = new IntersectionObserver((entries) => {
1605      entries.forEach(en => { if (en.isIntersecting) { en.target.classList.add('in'); riseObserver.unobserve(en.target); } });
1606    }, { threshold: 0.25 });
1607    document.querySelectorAll('.rise').forEach(el => riseObserver.observe(el));
1608
1609    /* ===================== qualification modal ===================== */
1610    // answers land in [email protected] via FormSubmit (swap for any webhook URL)
1611    const LEAD_ENDPOINT = 'https://formsubmit.co/ajax/[email protected]';
1612    const CALENDLY = 'https://calendly.com/hello-toolmonsters/30min';
1613
1614    const ICONS = {
1615      linkedin:   '<span class="cico"><img src="logos/sq-linkedin.png" alt=""></span>',
1616      newsletter: '<span class="cico"><svg viewBox="0 0 24 24" width="13" height="13"><path fill="#FF6719" d="M2.5 3.5h19v2.7h-19zM2.5 8.3h19V11h-19zM2.5 13.1h19v7.9l-9.5-4.4-9.5 4.4z"/></svg></span>',
1617      youtube:    '<span class="cico"><svg viewBox="0 0 24 24" width="15" height="15"><rect x="1" y="4.6" width="22" height="14.8" rx="4" fill="#FF0000"/><path d="M9.8 8.7v6.6L15.8 12z" fill="#fff"/></svg></span>',
1618      emailcrm:   '<span class="cico cico--fit"><img src="logos/sq-gmail.svg" alt=""></span>',
1619      community:  '<span class="cico cico--line"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg></span>',
1620      other:      '<span class="cico cico--line"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18M3 12h18"/></svg></span>'
1621    };
1622    /* rough ICP-match ranges per source, the honest back-of-napkin version */
1623    const SOURCES = [
1624      { id: 'linkedin',   label: 'LinkedIn',    unit: 'people',      rate: [0.02, 0.04] },
1625      { id: 'newsletter', label: 'Newsletter',  unit: 'subscribers', rate: [0.02, 0.04] },
1626      { id: 'youtube',    label: 'YouTube',     unit: 'subscribers', rate: [0.005, 0.015] },
1627      { id: 'emailcrm',   label: 'Email / CRM', unit: 'contacts',    rate: [0.05, 0.10] },
1628      { id: 'community',  label: 'Community',   unit: 'members',     rate: [0.03, 0.06] },
1629      { id: 'other',      label: 'Other',       unit: 'people',      rate: [0.01, 0.03] }
1630    ];
1631    const EXTRAS = ['Competitors to monitor', 'Old email threads', 'Past leads / lost deals',
1632                    'Job postings in my market', 'Events / conferences', 'Not sure yet'];
1633    /* deal-value ranges behind the Q3 chips */
1634    const VAL_RANGE = {
1635      '< $10K': [5000, 10000], '$10–25K': [10000, 25000], '$25–50K': [25000, 50000],
1636      '$50–100K': [50000, 100000], '$100K+': [100000, 150000]
1637    };
1638    const SELLS = ['Consulting / Services', 'Agency', 'SaaS', 'Recruiting', 'Other'];
1639    const VALUES = ['< $10K', '$10–25K', '$25–50K', '$50–100K', '$100K+'];
1640
1641    const state = { sources: [], sizes: {}, sell: '', value: '', extras: [], contact: {} };
1642    let qstep = 1;
1643
1644    /* one funnel event per meaningful action; lands in Plausible (and GA4 if present) */
1645    function track(name, props) {
1646      try {
1647        if (window.plausible) window.plausible(name, props ? { props } : undefined);
1648        if (window.gtag) window.gtag('event', name, props || {});
1649      } catch (e) {}
1650    }
1651
1652    const veil = document.getElementById('veil');
1653    const qbody = document.getElementById('qbody');
1654    const qbar = document.getElementById('qbar');
1655    const qtiles = document.getElementById('qtiles');
1656    const qwire1 = document.getElementById('qwire1');
1657    const qwire2 = document.getElementById('qwire2');
1658    const qnodeEl = document.getElementById('qnode');
1659    const qcount = document.getElementById('qcount');
1660    const qconv = document.getElementById('qconv');
1661
1662    function parseSize(v) { return parseInt(String(v || '').replace(/[^0-9]/g, ''), 10) || 0; }
1663    function estimate() {
1664      let lo = 0, hi = 0, any = false;
1665      state.sources.forEach(id => {
1666        const src = SOURCES.find(x => x.id === id);
1667        const n = parseSize(state.sizes[id]);
1668        if (n > 0) { any = true; lo += n * src.rate[0]; hi += n * src.rate[1]; }
1669      });
1670      return { lo: Math.round(lo), hi: Math.round(hi), any };
1671    }
1672    function nice(v) {
1673      if (v >= 1000) return (Math.round(v / 100) * 100).toLocaleString('en-US');
1674      if (v >= 100) return String(Math.round(v / 10) * 10);
1675      return String(Math.max(Math.round(v), 1));
1676    }
1677    function fmtK(n) { return n >= 1000 ? (Math.round(n / 100) / 10) + 'K' : String(n); }
1678    function fmtMoney(v) {
1679      if (v >= 950000) return '$' + (Math.round(v / 100000) / 10) + 'M';
1680      if (v >= 1000) return '$' + Math.round(v / 1000) + 'K';
1681      return '$' + Math.round(v);
1682    }
1683    /* the honest wow: even if only 1% of those buyers become customers */
1684    function pipeline() {
1685      const est = estimate();
1686      const val = VAL_RANGE[state.value];
1687      if (!est.any || !val) return { ok: false };
1688      return { ok: true, lo: est.lo * 0.01 * val[0], hi: est.hi * 0.01 * val[1] };
1689    }
1690
1691    /* the counter in the dark node eases toward its new value */
1692    let shownCount = 0, countRaf = null;
1693    function setCount(target) {
1694      cancelAnimationFrame(countRaf);
1695      const from = shownCount, t0 = performance.now(), dur = 700;
1696      function frame(now) {
1697        const k = Math.min((now - t0) / dur, 1);
1698        const eased = 1 - Math.pow(1 - k, 3);
1699        shownCount = from + (target - from) * eased;
1700        qcount.textContent = '~' + Math.round(shownCount).toLocaleString('en-US');
1701        if (k < 1) countRaf = requestAnimationFrame(frame);
1702        else qcount.textContent = '~' + nice(target);
1703      }
1704      countRaf = requestAnimationFrame(frame);
1705    }
1706
1707    let tilesKey = null;
1708    function updateDiagram() {
1709      /* only rebuild the tiles when the source set changes, otherwise the
1710         pop animation replays on every keystroke */
1711      const key = state.sources.join(',');
1712      if (key !== tilesKey) {
1713        tilesKey = key;
1714        if (!state.sources.length) {
1715          qtiles.innerHTML = '<p class="qtiles-empty">Pick your sources and watch your system build itself.</p>';
1716        } else {
1717          qtiles.innerHTML = state.sources.map(id => {
1718            const src = SOURCES.find(x => x.id === id);
1719            return '<div class="qtile" data-tile="' + id + '">' + (ICONS[id] || '') + '<b>' + src.label + '</b><i></i></div>';
1720          }).join('');
1721        }
1722      }
1723      qtiles.querySelectorAll('.qtile').forEach(t => {
1724        const n = parseSize(state.sizes[t.dataset.tile]);
1725        t.querySelector('i').textContent = n ? fmtK(n) : '';
1726      });
1727      const on = state.sources.length > 0;
1728      qwire1.classList.toggle('on', on);
1729      qnodeEl.classList.toggle('on', on);
1730      const est = estimate();
1731      if (est.any) setCount(Math.round((est.lo + est.hi) / 2));
1732      else { shownCount = 0; qcount.textContent = '?'; }
1733      const pipe = pipeline();
1734      const qm = document.getElementById('qmoney');
1735      /* from the result on, the big number lives on the right instead */
1736      qm.classList.toggle('on', pipe.ok && qstep < 5);
1737      if (pipe.ok) qm.textContent = '≈ ' + fmtMoney(pipe.lo) + '–' + fmtMoney(pipe.hi) + ' pipeline';
1738      const conv = qstep >= 5;
1739      qwire2.classList.toggle('on', conv && on);
1740      qconv.classList.toggle('on', conv && on);
1741    }
1742
1743    function openQuiz(origin) {
1744      qstep = 1; clearSwarm();
1745      track('quiz_open', { origin: origin || 'cta' });
1746      renderQuiz(1); veil.classList.add('open'); document.body.style.overflow = 'hidden';
1747    }
1748    function closeQuiz() { veil.classList.remove('open'); document.body.style.overflow = ''; }
1749    document.querySelectorAll('[data-open-quiz]').forEach((b, i) =>
1750      b.addEventListener('click', () => openQuiz(i === 0 ? 'hero' : 'corner')));
1751
1752    // each workflow card opens the modal with its source already selected
1753    const CARD_PRESETS = [
1754      { sources: ['newsletter'] },                                                          // 01 newsletter
1755      { sources: ['emailcrm'], extras: ['Old email threads', 'Past leads / lost deals'] },  // 02 dormant relationships
1756      { sources: ['linkedin'], extras: ['Competitors to monitor'] },                        // 03 competitor monitoring
1757      { extras: ['Job postings in my market'] },                                            // 04 hiring signals
1758      { sources: ['linkedin'] },                                                            // 05 linkedin export
1759      { extras: ['Events / conferences'] }                                                  // 06 conferences
1760    ];
1761    document.querySelectorAll('.ex-card').forEach((card, i) => {
1762      card.style.cursor = 'pointer';
1763      card.addEventListener('click', () => {
1764        const preset = CARD_PRESETS[i] || {};
1765        state.sources = [...(preset.sources || [])];
1766        state.extras = [...(preset.extras || [])];
1767        state.sizes = {};
1768        openQuiz('card_' + String(i + 1).padStart(2, '0'));
1769      });
1770    });
1771
1772    document.getElementById('qclose').addEventListener('click', closeQuiz);
1773    veil.addEventListener('click', (e) => { if (e.target === veil) closeQuiz(); });
1774    document.addEventListener('keydown', (e) => {
1775      if (!veil.classList.contains('open')) return;
1776      if (e.key === 'Escape') closeQuiz();
1777      if (e.key === 'Enter' && !e.target.matches('a,button')) {
1778        const next = document.getElementById('qnext');
1779        if (next) { e.preventDefault(); next.click(); }
1780      }
1781    });
1782
1783    /* steps slide in; dir is +1 forward, -1 back */
1784    function mount(view, dir) {
1785      qbody.innerHTML = '';
1786      view.classList.add('qview');
1787      qbody.appendChild(view);
1788      view.style.opacity = '0';
1789      view.style.transform = 'translateX(' + (26 * dir) + 'px)';
1790      requestAnimationFrame(() => {
1791        view.style.transition = 'opacity .28s ease, transform .34s cubic-bezier(.2,.8,.2,1)';
1792        view.style.opacity = '1';
1793        view.style.transform = 'none';
1794      });
1795      qbar.style.width = (qstep / 7 * 100).toFixed(1) + '%';
1796      updateDiagram();
1797    }
1798
1799    function chipRow(items, selected, onToggle, single) {
1800      const wrap = document.createElement('div');
1801      wrap.className = 'chips';
1802      items.forEach(it => {
1803        const label = typeof it === 'string' ? it : it.label;
1804        const id = typeof it === 'string' ? it : it.id;
1805        const b = document.createElement('button');
1806        b.type = 'button'; b.className = 'chip' + (selected.includes(id) ? ' on' : '');
1807        b.innerHTML = (ICONS[id] || '') + '<span>' + label + '</span>';
1808        b.addEventListener('click', () => {
1809          if (single) { wrap.querySelectorAll('.chip').forEach(c => c.classList.remove('on')); b.classList.add('on'); }
1810          else b.classList.toggle('on');
1811          onToggle(id, b.classList.contains('on'));
1812        });
1813        wrap.appendChild(b);
1814      });
1815      return wrap;
1816    }
1817
1818    function nav(view, showBack, nextLabel, onNext, onBack) {
1819      const n = document.createElement('div'); n.className = 'qnav';
1820      const back = document.createElement('button'); back.className = 'qback'; back.textContent = '← Back';
1821      if (!showBack) back.hidden = true;
1822      back.addEventListener('click', onBack);
1823      const next = document.createElement('button'); next.className = 'btn btn-primary'; next.id = 'qnext';
1824      next.innerHTML = nextLabel;
1825      next.addEventListener('click', onNext);
1826      n.append(back, next);
1827      view.append(n);
1828    }
1829
1830    function err(msg) {
1831      const e = qbody.querySelector('.qerr');
1832      if (e) { e.textContent = msg; e.style.display = 'block'; }
1833    }
1834
1835    function head(view, label, title) {
1836      const l = document.createElement('p'); l.className = 'qstep-label'; l.textContent = label;
1837      const h = document.createElement('h3'); h.textContent = title;
1838      view.append(l, h);
1839    }
1840
1841    const SWARM_COLORS = ['linear-gradient(135deg,#79a8ff,#b9aaff)',
1842                          'linear-gradient(135deg,#4fc3a1,#2f9e8f)',
1843                          'linear-gradient(135deg,#ff9d76,#f2709c)'];
1844    function fillSwarm(n, dur) {
1845      const sw = document.getElementById('qswarm');
1846      sw.innerHTML = '';
1847      for (let i = 0; i < n; i++) {
1848        const d = document.createElement('i');
1849        d.style.background = SWARM_COLORS[i % SWARM_COLORS.length];
1850        sw.appendChild(d);
1851        setTimeout(() => d.classList.add('on'), Math.round((i / n) * dur));
1852      }
1853    }
1854    function clearSwarm() { document.getElementById('qswarm').innerHTML = ''; }
1855
1856    function renderQuiz(dir) {
1857      const view = document.createElement('div');
1858
1859      if (qstep === 1) {
1860        head(view, 'Step 1 of 4', 'Where do you already have reach?');
1861        const hint = document.createElement('p'); hint.className = 'qhint';
1862        hint.textContent = 'Pick everything that applies.';
1863        view.append(hint);
1864        const grid = document.createElement('div'); grid.className = 'srcgrid';
1865        SOURCES.forEach(src => {
1866          const b = document.createElement('button');
1867          b.type = 'button';
1868          b.className = 'srcopt' + (state.sources.includes(src.id) ? ' on' : '');
1869          b.innerHTML = ICONS[src.id] + '<span>' + src.label + '</span>'
1870            + '<span class="tick"><svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg></span>';
1871          b.addEventListener('click', () => {
1872            b.classList.toggle('on');
1873            if (b.classList.contains('on')) { if (!state.sources.includes(src.id)) state.sources.push(src.id); }
1874            else state.sources = state.sources.filter(x => x !== src.id);
1875            updateDiagram();
1876          });
1877          grid.appendChild(b);
1878        });
1879        view.append(grid);
1880        const e = document.createElement('p'); e.className = 'qerr'; view.append(e);
1881        nav(view, false, 'Continue →', () => {
1882          if (!state.sources.length) return err('Pick at least one.');
1883          qstep = 2; renderQuiz(1);
1884        });
1885      }
1886
1887      if (qstep === 2) {
1888        head(view, 'Step 2 of 4', 'Roughly how large?');
1889        const hint = document.createElement('p'); hint.className = 'qhint';
1890        hint.textContent = 'Ballpark is perfect. Tap a number or type your own.';
1891        view.append(hint);
1892        const QUICK = [1000, 5000, 10000, 25000, 50000, 100000];
1893        state.sources.forEach(id => {
1894          const src = SOURCES.find(x => x.id === id);
1895          const f = document.createElement('div'); f.className = 'qfield';
1896          const lab = document.createElement('label');
1897          lab.innerHTML = ICONS[id] + '<span>' + src.label + '</span> <small>' + src.unit + '</small>';
1898          const inp = document.createElement('input');
1899          inp.type = 'text'; inp.inputMode = 'numeric'; inp.placeholder = 'e.g. 25,000';
1900          const chips = document.createElement('div'); chips.className = 'szchips';
1901          function syncChips() {
1902            const n = parseSize(state.sizes[id]);
1903            chips.querySelectorAll('.szchip').forEach(c =>
1904              c.classList.toggle('on', parseInt(c.dataset.v, 10) === n));
1905          }
1906          function setSize(raw) {
1907            const n = parseSize(raw);
1908            state.sizes[id] = n ? String(n) : '';
1909            inp.value = n ? n.toLocaleString('en-US') : '';
1910            syncChips(); updateDiagram();
1911          }
1912          QUICK.forEach(v => {
1913            const c = document.createElement('button');
1914            c.type = 'button'; c.className = 'szchip'; c.dataset.v = v;
1915            c.textContent = v >= 1000 ? (v / 1000) + 'K' : v;
1916            c.addEventListener('click', () => setSize(String(v)));
1917            chips.appendChild(c);
1918          });
1919          inp.addEventListener('input', () => setSize(inp.value));
1920          setSize(state.sizes[id] || '');
1921          f.append(lab, inp, chips); view.append(f);
1922        });
1923        nav(view, true, 'Continue →', () => { qstep = 3; renderQuiz(1); }, () => { qstep = 1; renderQuiz(-1); });
1924        setTimeout(() => { const i = view.querySelector('input'); if (i) i.focus(); }, 380);
1925      }
1926
1927      if (qstep === 3) {
1928        head(view, 'Step 3 of 4', 'What do you sell?');
1929        const hint = document.createElement('p'); hint.className = 'qhint';
1930        hint.textContent = 'Two taps. We use it to size your pipeline.';
1931        view.append(hint);
1932        const advance = () => {
1933          if (qstep === 3 && state.sell && state.value)
1934            setTimeout(() => { if (qstep === 3) { qstep = 4; renderQuiz(1); } }, 320);
1935        };
1936        view.append(chipRow(SELLS, [state.sell], (id, on) => { state.sell = on ? id : ''; advance(); }, true));
1937        const sub2 = document.createElement('p'); sub2.className = 'qsub';
1938        sub2.textContent = 'Typical customer value';
1939        view.append(sub2);
1940        view.append(chipRow(VALUES, [state.value], (id, on) => { state.value = on ? id : ''; advance(); }, true));
1941        const e = document.createElement('p'); e.className = 'qerr'; view.append(e);
1942        nav(view, true, 'Continue →', () => {
1943          if (!state.sell) return err('Pick what you sell.');
1944          qstep = 4; renderQuiz(1);
1945        }, () => { qstep = 2; renderQuiz(-1); });
1946      }
1947
1948      if (qstep === 4) {
1949        head(view, 'Step 4 of 4', 'What else could point to buyers?');
1950        const hint = document.createElement('p'); hint.className = 'qhint';
1951        hint.textContent = 'Beyond your reach, buying signals hide in these. Pick what we should watch for you.';
1952        view.append(hint);
1953        view.append(chipRow(EXTRAS, state.extras, (id, on) => {
1954          if (on) { if (!state.extras.includes(id)) state.extras.push(id); }
1955          else state.extras = state.extras.filter(x => x !== id);
1956        }));
1957        nav(view, true, 'See my opportunities →', () => { qstep = 5; renderQuiz(1); }, () => { qstep = 3; renderQuiz(-1); });
1958      }
1959
1960      if (qstep === 5) {
1961        /* the scan: every source is read on screen before the number lands */
1962        head(view, 'Your opportunities', 'Reading what you already have\u2026');
1963        const est = estimate();
1964        const jobs = [];
1965        state.sources.forEach(id => {
1966          const src = SOURCES.find(x => x.id === id);
1967          const n = parseSize(state.sizes[id]);
1968          jobs.push({
1969            icon: ICONS[id], name: src.label,
1970            what: n ? 'reading ' + n.toLocaleString('en-US') + ' ' + src.unit : 'sizing it up',
1971            res: n ? '\u2248 ' + nice(n * src.rate[0]) + '\u2013' + nice(n * src.rate[1]) + ' buyers' : 'on our list'
1972          });
1973        });
1974        const sigs = state.extras.filter(x => x !== 'Not sure yet');
1975        if (sigs.length) jobs.push({
1976          icon: '<span class="cico cico--line"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg></span>',
1977          name: 'Signals', what: 'watching ' + sigs.length + (sigs.length > 1 ? ' sources' : ' source'),
1978          res: 'live'
1979        });
1980
1981        const rows = jobs.map(j => {
1982          const r = document.createElement('div'); r.className = 'scanrow';
1983          r.innerHTML = j.icon + '<span class="sname">' + j.name + '</span>'
1984            + '<span class="swhat">' + j.what + '</span>'
1985            + '<span class="stail"><span class="spin"></span>'
1986            + '<span class="sres"><svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>'
1987            + j.res + '</span></span>';
1988          view.append(r);
1989          return r;
1990        });
1991
1992        /* people materialise in the side panel while the rows resolve */
1993        const total = est.any ? Math.round((est.lo + est.hi) / 2) : 0;
1994        const dots = Math.min(28, Math.max(8, Math.round(total / 200) || 12));
1995        fillSwarm(dots, 260 + rows.length * 380);
1996
1997        const step = 380, settle = 700;
1998        rows.forEach((r, i) => {
1999          setTimeout(() => r.classList.add('in'), 60 + i * step);
2000          setTimeout(() => {
2001            const sp = r.querySelector('.spin'); if (sp) sp.remove();
2002            const rs = r.querySelector('.sres'); if (rs) rs.classList.add('on');
2003          }, 60 + i * step + settle);
2004        });
2005        setTimeout(() => {
2006          if (veil.classList.contains('open') && qstep === 5) renderResult();
2007        }, 60 + (rows.length - 1) * step + settle + 620);
2008      }
2009
2010      if (qstep === 6) {
2011        head(view, 'Last step', 'Where do we send it?');
2012        const hint = document.createElement('p'); hint.className = 'qhint';
2013        hint.textContent = 'You get your mapping within 48 h, and nothing else lands in your inbox.';
2014        view.append(hint);
2015        [['name', 'Name', 'text', ''], ['email', 'Work email', 'email', ''],
2016         ['company', 'Company', 'text', 'optional'], ['website', 'Website / LinkedIn', 'text', 'optional']].forEach(([id, label, type, opt]) => {
2017          const f = document.createElement('div'); f.className = 'qfield';
2018          const lab = document.createElement('label');
2019          lab.innerHTML = '<span>' + label + '</span>' + (opt ? ' <small>' + opt + '</small>' : '');
2020          const inp = document.createElement('input'); inp.type = type;
2021          inp.value = state.contact[id] || '';
2022          inp.addEventListener('input', () => { state.contact[id] = inp.value; });
2023          f.append(lab, inp); view.append(f);
2024        });
2025        setTimeout(() => { const i = view.querySelector('input'); if (i) i.focus(); }, 380);
2026        const e = document.createElement('p'); e.className = 'qerr'; view.append(e);
2027        nav(view, true, 'Send →', () => {
2028          if (!state.contact.name || !state.contact.email || !/.+@.+\..+/.test(state.contact.email))
2029            return err('Your name and a valid work email, and we’re good.');
2030          const est = estimate();
2031          if (LEAD_ENDPOINT) {
2032            const reach = state.sources.map(id => {
2033              const src = SOURCES.find(x => x.id === id);
2034              return src.label + (state.sizes[id] ? ' (' + state.sizes[id] + ')' : '');
2035            }).join(', ');
2036            fetch(LEAD_ENDPOINT, {
2037              method: 'POST',
2038              headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
2039              body: JSON.stringify({
2040                _subject: 'New lead: ' + state.contact.name + (state.contact.company ? ' (' + state.contact.company + ')' : ''),
2041                name: state.contact.name,
2042                company: state.contact.company || '',
2043                email: state.contact.email,
2044                website: state.contact.website || '',
2045                reach: reach,
2046                sells: state.sell,
2047                deal_value: state.value || '',
2048                also_has: state.extras.join(', '),
2049                estimated_buyers: est.any ? nice(est.lo) + '-' + nice(est.hi) : '',
2050                pipeline_potential: (function () { const p = pipeline();
2051                  return p.ok ? fmtMoney(p.lo) + '-' + fmtMoney(p.hi) : ''; })()
2052              }),
2053              keepalive: true
2054            }).catch(() => {});
2055          }
2056          track('quiz_submit', {
2057            sells: state.sell, deal_value: state.value || '',
2058            buyers: est.any ? nice(est.lo) + '-' + nice(est.hi) : ''
2059          });
2060          qstep = 7; renderQuiz(1);
2061        }, () => { qstep = 5; renderQuiz(-1); });
2062      }
2063
2064      if (qstep === 7) {
2065        const t = document.createElement('div'); t.className = 'qthanks';
2066        const cal = CALENDLY + '?hide_gdpr_banner=1&embed_type=Inline&embed_domain=' + location.hostname
2067                  + '&name=' + encodeURIComponent(state.contact.name || '')
2068                  + '&email=' + encodeURIComponent(state.contact.email || '');
2069        t.innerHTML = '<h3>Got it. Want to skip the wait?</h3>'
2070          + '<p>The mapping lands at ' + (state.contact.email || 'your email')
2071          + '. Grab a slot and we’ll walk through it together.</p>'
2072          + '<iframe src="' + cal + '" loading="lazy" title="Book a call"></iframe>'
2073          + '<p style="margin-top:12px;font-size:12.5px"><a href="' + CALENDLY
2074          + '" target="_blank" rel="noopener" style="color:#2f6fe0;font-weight:600">Calendar not loading? Open it in a new tab &rarr;</a></p>';
2075        view.append(t);
2076      }
2077
2078      mount(view, dir || 1);
2079      if ((dir || 1) > 0) track('quiz_step', { step: String(qstep) });
2080    }
2081
2082    /* the payoff, mounted once the scan finishes */
2083    function renderResult() {
2084      const view = document.createElement('div');
2085      const est = estimate();
2086      const pipe = pipeline();
2087      const names = state.sources.map(id => SOURCES.find(x => x.id === id).label);
2088      const listed = names.length > 1
2089        ? names.slice(0, -1).join(', ') + ' and ' + names[names.length - 1]
2090        : (names[0] || 'what you have');
2091      head(view, 'Your opportunities', est.any ? 'Here\u2019s what\u2019s already around you.' : 'Here\u2019s where we\u2019d look.');
2092
2093      if (est.any) {
2094        const hero = document.createElement('div'); hero.className = 'rhero';
2095        hero.innerHTML = pipe.ok
2096          ? '<b id="rhero-n">$0</b><span>in pipeline, if just 1% of them become customers</span>'
2097          : '<b id="rhero-n">0</b><span>potential buyers already around you</span>';
2098        view.append(hero);
2099      }
2100
2101      const line = document.createElement('p'); line.className = 'rline';
2102      line.innerHTML = est.any
2103        ? 'From <b>\u2248' + nice(est.lo) + '\u2013' + nice(est.hi) + '</b> potential buyers across ' + listed + '.'
2104        : 'We\u2019d start with ' + listed + ', then size it on your real data.';
2105      view.append(line);
2106
2107      if (est.any) {
2108        const rows = document.createElement('div'); rows.className = 'rrows';
2109        state.sources.forEach(id => {
2110          const src = SOURCES.find(x => x.id === id);
2111          const n = parseSize(state.sizes[id]);
2112          const r = document.createElement('div'); r.className = 'rrow';
2113          r.innerHTML = ICONS[id] + '<span class="rname">' + src.label + '</span>'
2114            + (n ? '<b>\u2248 ' + nice(n * src.rate[0]) + '\u2013' + nice(n * src.rate[1]) + ' buyers</b>'
2115                 : '<b>we\u2019ll size it</b>');
2116          rows.append(r);
2117        });
2118        const tog = document.createElement('button');
2119        tog.type = 'button'; tog.className = 'rtoggle'; tog.textContent = 'See the breakdown \u2193';
2120        tog.addEventListener('click', () => {
2121          const open = rows.classList.toggle('on');
2122          tog.textContent = open ? 'Hide the breakdown \u2191' : 'See the breakdown \u2193';
2123        });
2124        view.append(tog, rows);
2125
2126        const note = document.createElement('p'); note.className = 'rnote';
2127        note.textContent = 'Rough ranges. We refine them on your real data.';
2128        view.append(note);
2129      }
2130
2131      nav(view, true, 'Get my full mapping \u2192',
2132        () => { qstep = 6; renderQuiz(1); },
2133        () => { clearSwarm(); qstep = 4; renderQuiz(-1); });
2134
2135      mount(view, 1);
2136      track('quiz_result', { pipeline: pipe.ok ? fmtMoney(pipe.lo) + '-' + fmtMoney(pipe.hi) : '' });
2137
2138      if (est.any) {
2139        const b = view.querySelector('#rhero-n');
2140        const t0 = performance.now(), dur = 1300;
2141        (function frame(now) {
2142          const k = Math.min(((now || performance.now()) - t0) / dur, 1);
2143          const e = 1 - Math.pow(1 - k, 3);
2144          b.textContent = pipe.ok
2145            ? '\u2248 ' + fmtMoney(pipe.lo * e) + '\u2013' + fmtMoney(pipe.hi * e)
2146            : '\u2248 ' + nice(est.lo * e) + '\u2013' + nice(est.hi * e);
2147          if (k < 1) requestAnimationFrame(frame);
2148        })();
2149      }
2150    }
2151  </script>
2151
2152
2153  <!-- Warmly visitor de-anonymization -->
2154  
2154<script id="warmly-script-loader" src="https://opps-widget.getwarmly.com/warmly.js?clientId=1264090ee72dbb754952423b07bed648" defer></script>
2154
2155
2156</body>
2157</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.