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 →</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’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 · 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 · 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 · 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’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’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="" />© 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">✕</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">–</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 →</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.