1<!DOCTYPE html> 2<html lang="en" class="theme-wi"> 3 4<head> 5 <!-- Google tag (gtag.js) -->
vendor: 68 bytes, lines 5-6
5 6 <script async src="https://www.googletagmanager.com/gtag/js?id=
6G-D3W0C32KLH
vendor: 16 bytes, lines 6-7
6"></script> 7
7<script> 8
vendor: 176 bytes, lines 8-15
8window.dataLayer = window.dataLayer || []; 9 10 function gtag() { 11 dataLayer.push(arguments); 12 } 13 gtag("js", new Date()); 14 15 gtag("config", "
15G-D3W0C32KLH
vendor: 8 bytes, lines 15-16
15"); 16
16</script>
16 17 18 <!-- JSON-LD Schema Markup --> 19
19<script type="application/ld+json"> 20 { 21 "@context": "https://schema.org", 22 "@type": "Organization", 23 "name": "AlignWave", 24 "alternateName": ["Align Wave"], 25 "url": "https://alignwave.ai/", 26 "logo": "https://alignwave.ai/assets/images/logo.svg", 27 "description": "Workforce Intelligence from AlignWave: AI-driven insights to hire the best, reduce turnover, and improve retentionâfrom driver hiring to frontline teams.", 28 "sameAs": [ 29 "https://www.linkedin.com/company/axamai", 30 "https://twitter.com/axamai", 31 "https://github.com/axamai" 32 ], 33 "contactPoint": { 34 "@type": "ContactPoint", 35 "contactType": "customer service", 36 "url": "https://alignwave.ai/app/request" 37 } 38 } 39 </script>
39 40
40<script type="application/ld+json"> 41 { 42 "@context": "https://schema.org", 43 "@type": "WebSite", 44 "url": "https://alignwave.ai/", 45 "name": "AlignWave", 46 "description": "Workforce Intelligence â AI for better decisions with your existing and new talent" 47 } 48 </script>
48 49
49<script type="application/ld+json"> 50 { 51 "@context": "https://schema.org", 52 "@type": "FAQPage", 53 "mainEntity": [{ 54 "@type": "Question", 55 "name": "What is AlignWave?", 56 "acceptedAnswer": { 57 "@type": "Answer", 58 "text": "AlignWave is a workforce intelligence platform that helps companies discover the patterns in their own data â and use that intelligence to hire, retain, and develop the people most likely to succeed in their specific environment." 59 } 60 }, 61 { 62 "@type": "Question", 63 "name": "What makes AlignWave different from other hiring assessments?", 64 "acceptedAnswer": { 65 "@type": "Answer", 66 "text": "AlignWave builds a company-specific alignment profile from your own workforce â not a generalized industry benchmark. Every candidate is measured against the people who actually succeed at your organization, under your operating conditions." 67 } 68 }, 69 { 70 "@type": "Question", 71 "name": "Who is AlignWave built for?", 72 "acceptedAnswer": { 73 "@type": "Answer", 74 "text": "AlignWave is built for mid-market employers starting with logistics and trucking companies â organizations with measurable workforce costs and the operational data needed to build company-specific intelligence." 75 } 76 } 77 ] 78 } 79 </script>
79 80 81 <!-- Required Meta Tags --> 82 <meta charset="UTF-8" /> 83 <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 84 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 85 <meta name="robots" content="index, follow" /> 86 <meta name="author" content="AlignWave" /> 87 <meta name="format-detection" content="telephone=no" /> 88 89 <!-- Title --> 90 <title> 91 Workforce Intelligence | AlignWave â AI for better decisions with your talent 92 </title> 93 94 <!-- SEO Meta Description --> 95 <meta name="description" content="Workforce Intelligence from AlignWave: use AI-driven insights to hire the best, reduce turnover, and improve retentionâfrom driver hiring to frontline teams." /> 96 <meta property="og:description" content="Workforce Intelligence from AlignWave: use AI-driven insights to hire the best, reduce turnover, and improve retention." /> 97 <link rel="canonical" href="https://alignwave.ai/" /> 98 <meta name="keywords" content="workforce intelligence, AI hiring, predictive hiring, behavioral fit, retention, driver hiring, talent analytics, alignwave" /> 99 100 <!-- Open Graph / Facebook --> 101 <meta property="og:type" content="website" /> 102 <meta property="og:url" content="https://alignwave.ai/" /> 103 <meta property="og:title" content="Workforce Intelligence | AlignWave" /> 104 <meta property="og:description" content="Workforce Intelligence from alignwave.ai: use AI-driven insights to hire the best, reduce turnover, and improve retention." /> 105 <meta property="og:image" content="https://alignwave.ai/assets/images/screens/screen-1.png" /> 106 107 <!-- Twitter --> 108 <meta name="twitter:card" content="summary_large_image" /> 109 <meta name="twitter:url" content="https://alignwave.ai/" /> 110 <meta name="twitter:title" content="Workforce Intelligence | AlignWave" /> 111 <meta name="twitter:description" content="Workforce Intelligence from AlignWave: AI-driven insights to hire the best, reduce turnover, and improve retention." /> 112 <meta name="twitter:image" content="https://alignwave.ai/assets/images/screens/screen-1.png" /> 113 114 <!-- Favicon --> 115 <link rel="icon" href="/assets/images/icon.png?v=2" type="image/png" /> 116 117 <!-- Font: Plus Jakarta Sans (Workforce Intelligence theme) --> 118 <link rel="preconnect" href="https://fonts.googleapis.com" /> 119 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 120 <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet" /> 121 <!-- Three.js import map â resolves bare specifiers for the 3D graph --> 122
122<script type="importmap"> 123 { 124 "imports": { 125 "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js", 126 "three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/" 127 } 128 } 129 </script>
129 130 <!-- Stylesheets --> 131 <link rel="stylesheet" href="/assets/css/plugins.css" /> 132 <link rel="stylesheet" href="/assets/css/style.css" /> 133 <link rel="stylesheet" href="/assets/css/theme-redesign.css" /> 134 <!-- Font Awesome --> 135 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" /> 136</head> 137 138<body class="theme-wi"> 139 <header class="site-header" id="site-header"> 140 <div class="header-inner"> 141 <a href="/" class="logo"> 142 <img src="/assets/images/logo.png" alt="AlignWave" /> 143 </a> 144 <nav class="nav-links" aria-label="Main navigation"> 145 <a href="/#assessment">How it works</a> 146 <a href="/#report-preview">The Result</a> 147 <a href="/team">Team</a> 148 <a href="/app">Login</a> 149 <a href="/app/register" class="btn-cta">Get Started</a> 150 </nav> 151 <button type="button" class="header-hamburger" aria-label="Open menu" aria-expanded="false" aria-controls="mobile-menu" id="header-hamburger-btn"> 152 <span class="hamburger-line"></span> 153 <span class="hamburger-line"></span> 154 <span class="hamburger-line"></span> 155 </button> 156 </div> 157 <div class="mobile-menu-overlay" id="mobile-menu-overlay" aria-hidden="true"></div> 158 <div class="mobile-menu" id="mobile-menu" aria-label="Mobile navigation" aria-hidden="true"> 159 <a href="/#assessment" class="mobile-menu-link">How it works</a> 160 <a href="/#report-preview" class="mobile-menu-link">The Result</a> 161 <a href="/team" class="mobile-menu-link">Team</a> 162 <a href="/app" class="mobile-menu-link">Login</a> 163 <a href="/app/register" class="mobile-menu-link mobile-menu-cta">Get Started</a> 164 </div> 165 </header> 166
166<script> 167 (function() { 168 var header = document.getElementById('site-header'); 169 var btn = document.getElementById('header-hamburger-btn'); 170 var overlay = document.getElementById('mobile-menu-overlay'); 171 var menu = document.getElementById('mobile-menu'); 172 if (!header || !btn || !overlay || !menu) return; 173 174 function openMenu() { 175 header.classList.add('menu-open'); 176 btn.setAttribute('aria-expanded', 'true'); 177 btn.setAttribute('aria-label', 'Close menu'); 178 overlay.setAttribute('aria-hidden', 'false'); 179 menu.setAttribute('aria-hidden', 'false'); 180 document.body.style.overflow = 'hidden'; 181 } 182 183 function closeMenu() { 184 header.classList.remove('menu-open'); 185 btn.setAttribute('aria-expanded', 'false'); 186 btn.setAttribute('aria-label', 'Open menu'); 187 overlay.setAttribute('aria-hidden', 'true'); 188 menu.setAttribute('aria-hidden', 'true'); 189 document.body.style.overflow = ''; 190 } 191 192 function toggleMenu() { 193 if (header.classList.contains('menu-open')) closeMenu(); 194 else openMenu(); 195 } 196 btn.addEventListener('click', toggleMenu); 197 overlay.addEventListener('click', closeMenu); 198 menu.querySelectorAll('.mobile-menu-link').forEach(function(link) { 199 link.addEventListener('click', closeMenu); 200 }); 201 })(); 202 </script>
202 203 204 205 <main> 206 <section class="hero"> 207 <!-- 208 <div class="hero-video" aria-hidden="true"> 209 <video autoplay muted loop playsinline poster="/assets/video/background.jpg"> 210 <source src="/assets/video/background.webm" type="video/webm" /> 211 </video> 212 </div> 213 --> 214 <canvas id="hero-wave-canvas" class="hero-canvas" aria-hidden="true"></canvas> 215 <div class="hero-bg" aria-hidden="true"></div> 216 <div class="hero-inner"> 217 <h1 class="hero-badge">Workforce Intelligence</h1> 218 <h2>People and companies have patterns — let's align them.</h2> 219 <h3 class="subline"> 220 AlignWave is a workforce intelligence platform that connects the signals that emerge from your people and your organization—uncovering 221 patterns that improve how you hire, market, train, and manage quality. 222 </h3> 223 <a href="#assessment" class="btn-cta hero-scroll-cta" id="hero-scroll-btn"> 224 What Is Workforce Intelligence? ↓ 225 </a> 226
226<script> 227 (function() { 228 var btn = document.getElementById('hero-scroll-btn'); 229 if (!btn) return; 230 btn.addEventListener('click', function(e) { 231 e.preventDefault(); 232 var target = document.getElementById('assessment'); 233 if (target) target.scrollIntoView({ 234 behavior: 'smooth' 235 }); 236 }); 237 })(); 238 </script>
238 239 </div> 240
240<script> 241 (function() { 242 var canvas = document.getElementById('hero-wave-canvas'); 243 if (!canvas) return; 244 var ctx = canvas.getContext('2d'); 245 var W = 0, 246 H = 0; 247 248 function resize() { 249 W = canvas.width = canvas.offsetWidth; 250 H = canvas.height = canvas.offsetHeight; 251 } 252 window.addEventListener('resize', resize); 253 resize(); 254 255 // === World / grid === 256 var COLS = 64; 257 var ROWS = 48; 258 var GRID_W = 4200; 259 var Z_NEAR = 230; 260 var Z_FAR = 2700; 261 var Z_RANGE = Z_FAR - Z_NEAR; 262 263 // Very slow scroll â matches original video's gentle drift 264 var SCROLL_SPEED = 35; 265 var FOG_START = 480; 266 var FOG_END = 2200; 267 268 // Camera just above water; horizon near vertical center 269 var FOV = 300; 270 var CAM_Y = 22; 271 var HORIZON = 0.47; 272 273 // Gentle, slow-rolling waves â 4 superimposed components for variety 274 var AMP = 130; 275 var AMP_RANGE = AMP * 1.6; 276 277 function waveH(wx, wz, t) { 278 return AMP * Math.sin(wx * 0.0090 + wz * 0.013 + t * 0.062) + 279 AMP * 0.38 * Math.sin(-wx * 0.016 + wz * 0.008 + t * 0.042) + 280 AMP * 0.22 * Math.sin(wx * 0.006 - wz * 0.018 + t * 0.027) + 281 AMP * 0.12 * Math.sin(wx * 0.022 + wz * 0.005 + t * 0.019); 282 } 283 284 // === Particle grid â z scrolls toward viewer each frame and wraps === 285 var particles = []; 286 for (var r = 0; r < ROWS; r++) { 287 for (var c = 0; c < COLS; c++) { 288 particles.push({ 289 wx: (c / (COLS - 1) - 0.5) * GRID_W, 290 wz: Z_NEAR + (r / (ROWS - 1)) * Z_RANGE, 291 row: r, 292 col: c 293 }); 294 } 295 } 296 297 // Row/col lookup for drawing connections 298 var pIdx = {}; 299 for (var i = 0; i < particles.length; i++) { 300 pIdx[particles[i].row + '_' + particles[i].col] = i; 301 } 302 303 function projPt(wx, wy, wz) { 304 var sc = FOV / wz; 305 return { 306 x: wx * sc + W * 0.5, 307 y: (CAM_Y - wy) * sc + H * HORIZON, 308 sc: sc 309 }; 310 } 311 312 function getFog(wz) { 313 if (wz <= FOG_START) return 1; 314 if (wz >= FOG_END) return 0; 315 return (FOG_END - wz) / (FOG_END - FOG_START); 316 } 317 318 // === Slice events === 319 // Each slice = thin z-band covering 30-65% of x, showing the wave cross-section in blue. 320 // 4 depth zones each run their own independent timer â guarantees constant spread, 321 // no gaps and no clustering. 322 var SLICE_THICK = 65; 323 var slices = []; 324 325 var ZONES = [{ 326 zMin: 270, 327 zMax: 540 328 }, // very near â large, prominent 329 { 330 zMin: 540, 331 zMax: 900 332 }, // near-mid 333 { 334 zMin: 900, 335 zMax: 1320 336 }, // mid 337 { 338 zMin: 1320, 339 zMax: 1720 340 }, // mid-far 341 ]; 342 // Independent timer per zone (ms). Stagger so they don't all fire at once. 343 var zTimers = [1600, 400, 2800, 1100]; 344 var zIntervals = [3200, 3600, 3000, 3800]; // initial intervals (randomised after first fire) 345 346 function spawnSliceInZone(zone) { 347 var coverage = 0.30 + Math.random() * 0.35; 348 var halfW = coverage * GRID_W * 0.5; 349 var cx = (Math.random() - 0.5) * GRID_W * 0.52; 350 var wz = zone.zMin + Math.random() * (zone.zMax - zone.zMin); 351 slices.push({ 352 wz: wz, 353 x0: cx - halfW, 354 x1: cx + halfW, 355 life: 0, 356 maxLife: 2200 + Math.random() * 1600, // 2.2-3.8s 357 alpha: 0 358 }); 359 } 360 361 // Pre-seed one slice per zone with staggered lifetimes so we're never empty at start 362 for (var i = 0; i < ZONES.length; i++) { 363 spawnSliceInZone(ZONES[i]); 364 slices[i].life = i * 900; // stagger: 0s, 0.9s, 1.8s, 2.7s into their lives 365 } 366 367 // === Draw loop === 368 var lastT = 0; 369 var animId; 370 371 function draw(ts) { 372 var t = ts * 0.001; 373 var dt = Math.min(t - lastT, 0.05); 374 lastT = t; 375 ctx.clearRect(0, 0, W, H); 376 377 // Scroll particles toward viewer; wrap at the near clipping plane 378 for (var i = 0; i < particles.length; i++) { 379 particles[i].wz -= SCROLL_SPEED * dt; 380 if (particles[i].wz < Z_NEAR) particles[i].wz += Z_RANGE; 381 } 382 383 // Tick each zone's independent timer; spawn when due 384 for (var z = 0; z < ZONES.length; z++) { 385 zTimers[z] += dt * 1000; 386 if (zTimers[z] >= zIntervals[z]) { 387 spawnSliceInZone(ZONES[z]); 388 zTimers[z] = 0; 389 zIntervals[z] = 2800 + Math.random() * 2000; // 2.8-4.8s between repeats 390 } 391 } 392 393 // Update slice lifetimes and alpha; remove expired ones 394 for (var i = slices.length - 1; i >= 0; i--) { 395 var sl = slices[i]; 396 sl.wz -= SCROLL_SPEED * dt; 397 sl.life += dt * 1000; 398 var fade = 480; 399 sl.alpha = sl.life < fade ? sl.life / fade : 400 sl.life > sl.maxLife - fade ? Math.max(0, (sl.maxLife - sl.life) / fade) : 401 1; 402 if (sl.life >= sl.maxLife || sl.wz < Z_NEAR - 50) slices.splice(i, 1); 403 } 404 405 // Compute per-particle state (wave height, projection, fog, slice membership) 406 var st = new Array(particles.length); 407 for (var i = 0; i < particles.length; i++) { 408 var p = particles[i]; 409 var fv = getFog(p.wz); 410 var h = fv > 0.005 ? waveH(p.wx, p.wz, t) : 0; 411 var pr = projPt(p.wx, h, p.wz); 412 var nh = Math.max(0, Math.min(1, (h + AMP_RANGE) / (AMP_RANGE * 2))); 413 414 // Find the strongest slice this particle belongs to 415 var ss = 0; 416 for (var j = 0; j < slices.length; j++) { 417 var sl = slices[j]; 418 var dz = Math.abs(p.wz - sl.wz); 419 if (dz > SLICE_THICK || p.wx < sl.x0 || p.wx > sl.x1) continue; 420 var str = (1 - dz / SLICE_THICK) * sl.alpha; 421 if (str > ss) ss = str; 422 } 423 st[i] = { 424 pr: pr, 425 fv: fv, 426 nh: nh, 427 ss: ss 428 }; 429 } 430 431 // --- Smooth spline through each highlighted row in each slice --- 432 // Collects all lit points in a row, then draws one quadratic Bezier curve 433 // through them â no jagged segments, just a flowing wave-profile line. 434 ctx.lineWidth = 0.9; 435 for (var j = 0; j < slices.length; j++) { 436 var sl = slices[j]; 437 if (sl.alpha < 0.02) continue; 438 439 for (var r = 0; r < ROWS; r++) { 440 // All particles in a row share the same wz (they scroll in lockstep) 441 var rowWz = particles[r * COLS].wz; 442 var dz = Math.abs(rowWz - sl.wz); 443 if (dz > SLICE_THICK) continue; 444 445 var slStr = (1 - dz / SLICE_THICK) * sl.alpha; 446 var rowFog = getFog(rowWz); 447 if (rowFog < 0.05) continue; 448 449 // Collect projected positions for particles within the slice's x-range 450 var pts = []; 451 for (var c = 0; c < COLS; c++) { 452 var idx = r * COLS + c; 453 var p = particles[idx]; 454 if (p.wx < sl.x0 || p.wx > sl.x1) continue; 455 pts.push({ 456 x: st[idx].pr.x, 457 y: st[idx].pr.y, 458 nh: st[idx].nh 459 }); 460 } 461 if (pts.length < 2) continue; 462 463 // Derive line color from average wave height across the row segment 464 var avgNh = 0; 465 for (var k = 0; k < pts.length; k++) avgNh += pts[k].nh; 466 avgNh /= pts.length; 467 var rr = Math.round(147 - avgNh * 118); 468 var gg = Math.round(197 - avgNh * 119); 469 var bb = Math.round(253 - avgNh * 37); 470 var la = slStr * rowFog * 0.62; 471 472 // Catmull-Rom spline converted to cubic Bezier segments. 473 // Unlike quadratic Bezier, this passes exactly through every point. 474 ctx.beginPath(); 475 ctx.moveTo(pts[0].x, pts[0].y); 476 if (pts.length === 2) { 477 ctx.lineTo(pts[1].x, pts[1].y); 478 } else { 479 for (var k = 0; k < pts.length - 1; k++) { 480 var p0 = pts[k > 0 ? k - 1 : 0]; 481 var p1 = pts[k]; 482 var p2 = pts[k + 1]; 483 var p3 = pts[k + 2 < pts.length ? k + 2 : pts.length - 1]; 484 ctx.bezierCurveTo( 485 p1.x + (p2.x - p0.x) / 6, p1.y + (p2.y - p0.y) / 6, 486 p2.x - (p3.x - p1.x) / 6, p2.y - (p3.y - p1.y) / 6, 487 p2.x, p2.y 488 ); 489 } 490 } 491 ctx.strokeStyle = 'rgba(' + rr + ',' + gg + ',' + bb + ',' + la.toFixed(3) + ')'; 492 ctx.stroke(); 493 } 494 } 495 496 // --- Draw particles (far-to-near via row order: row 0 = Z_NEAR) --- 497 // Iterate rows from ROWS-1 (far) to 0 (near) for painter's order 498 var rowOrder = []; 499 for (var r = ROWS - 1; r >= 0; r--) rowOrder.push(r); 500 501 for (var ri = 0; ri < rowOrder.length; ri++) { 502 var r = rowOrder[ri]; 503 for (var c = 0; c < COLS; c++) { 504 var idx = r * COLS + c; 505 var s = st[idx]; 506 var pr = s.pr; 507 if (s.fv < 0.005) continue; 508 if (pr.x < -60 || pr.x > W + 60 || pr.y < -90 || pr.y > H + 90) continue; 509 510 var radius = Math.max(0.20, Math.min(1.8, (0.32 + s.nh * 0.50) * pr.sc * 4.2)); 511 512 if (s.ss > 0.01) { 513 // Blue cross-section: #93c5fd (light) at trough â #1d4ed8 (darker blue) at crest 514 var bA = s.fv * s.ss * (0.45 + s.nh * 0.55); 515 var rr = Math.round(147 - s.nh * 118); // 147â29 516 var gg = Math.round(197 - s.nh * 119); // 197â78 517 var bb = Math.round(253 - s.nh * 37); // 253â216 518 if (s.ss > 0.45 && s.nh > 0.50) { 519 ctx.shadowColor = 'rgba(' + rr + ',' + gg + ',' + bb + ',0.35)'; 520 ctx.shadowBlur = radius * 1.8; 521 } else { 522 ctx.shadowBlur = 0; 523 } 524 ctx.beginPath(); 525 ctx.arc(pr.x, pr.y, radius * (1 + s.ss * 0.30), 0, 6.2832); 526 ctx.fillStyle = 'rgba(' + rr + ',' + gg + ',' + bb + ',' + bA.toFixed(3) + ')'; 527 ctx.fill(); 528 } else { 529 // White base â barely visible, depth-driven opacity 530 var wA = s.fv * (0.07 + s.nh * 0.20); 531 ctx.shadowBlur = 0; 532 ctx.beginPath(); 533 ctx.arc(pr.x, pr.y, radius, 0, 6.2832); 534 ctx.fillStyle = 'rgba(255,255,255,' + Math.min(0.42, wA).toFixed(3) + ')'; 535 ctx.fill(); 536 } 537 } 538 } 539 540 ctx.shadowBlur = 0; 541 animId = requestAnimationFrame(draw); 542 } 543 544 animId = requestAnimationFrame(draw); 545 546 document.addEventListener('visibilitychange', function() { 547 if (document.hidden) cancelAnimationFrame(animId); 548 else animId = requestAnimationFrame(draw); 549 }); 550 })(); 551 </script>
551 552 </section> 553 554 <section class="section assessment-section" id="assessment"> 555 <div class="section-inner"> 556 <div class="assessment-split"> 557 <div class="assessment-copy" data-aos="fade-right" data-aos-duration="700"> 558 <div class="section-label">What Is Workforce Intelligence?</div> 559 <h2 class="section-title">Questions that uncover patterns.</h2> 560 <p class="section-desc"> 561 Workforce intelligence starts with your own people's data, not generic industry benchmarks—and it starts with the assessment. Every question is purpose-built to reveal two things: behavioral fit and functional experience. In under 10 minutes, candidates surface the instincts, working habits, and role-specific judgment your top performers share—things a résumé will never show you. What comes next is the insight: a full report, scored against your own best performers. 562 </p> 563 <ul class="assessment-bullet-list"> 564 <li> 565 <span class="bullet-dot"></span> 566 Quick, focused questions built around your actual role 567 </li> 568 <li> 569 <span class="bullet-dot"></span> 570 Measures real behaviors: judgment, safety instincts, adaptability under pressure 571 </li> 572 <li> 573 <span class="bullet-dot"></span> 574 Mobile-friendlyâcandidates complete it from any device, anywhere 575 </li> 576 <li> 577 <span class="bullet-dot"></span> 578 No specialized training needed for your team to run it 579 </li> 580 </ul> 581 </div> 582 <div class="assessment-card" data-aos="fade-left" data-aos-duration="700" data-aos-delay="100"> 583 584 <!-- Meanwhile overlay --> 585 <div class="acard-meanwhile"> 586 <div class="acard-meanwhile-inner"> 587 <div class="acard-meanwhile-spinner"></div> 588 <p>Meanwhile, on your hiring team’s screen…</p> 589 </div> 590 </div> 591 592 <!-- State 1: Candidate Assessment (18 of 18) --> 593 <div class="acard-state acard-state--candidate is-active"> 594 <div class="acard-header"> 595 <span class="acard-progress-text">Question 18 of 18</span> 596 <div class="acard-progress-bar"> 597 <div class="acard-progress-fill" style="width:100%"></div> 598 </div> 599 </div> 600 <p class="acard-question">When you receive an unexpected change to your schedule, how do you typically respond?</p> 601 <ul class="acard-options"> 602 <li class="acard-option"> 603 <span class="acard-dot"></span> 604 <span>I review the new plan carefully before making any moves</span> 605 </li> 606 <li class="acard-option selected"> 607 <span class="acard-dot"></span> 608 <span>I adapt quickly and handle it as the situation unfolds</span> 609 </li> 610 <li class="acard-option"> 611 <span class="acard-dot"></span> 612 <span>I confirm with my supervisor to understand the new expectations</span> 613 </li> 614 <li class="acard-option">
615 <span class="acard-dot"></span> 616 <span>I finish my current task first, then address the change</span> 617 </li> 618 </ul> 619 <button class="acard-next btn-cta acard-submit" type="button">Submit Assessment →</button> 620 <div class="acard-hint" aria-hidden="true"> 621 <svg class="acard-hint-arrow" viewBox="0 0 46 46" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 622 <path d="M 40 40 C 32 28, 22 20, 8 8" /> 623 <polyline points="8,20 8,8 20,8" /> 624 </svg> 625 <span class="acard-hint-text">Try it out</span> 626 </div> 627 </div> 628 629 <!-- State 2: Admin Inbox --> 630 <div class="acard-state acard-state--admin"> 631 <div class="acard-admin-header"> 632 <div class="acard-admin-title-row"> 633 <span class="acard-admin-title">Submissions</span> 634 <span class="acard-admin-badge">● 1 New</span> 635 </div> 636 <div class="acard-admin-role">OTR Driver â Southeast Region</div> 637 </div> 638 <div class="acard-admin-list"> 639 <div class="acard-admin-row acard-admin-row--read"> 640 <div class="acard-admin-avatar">MT</div> 641 <div class="acard-admin-info"> 642 <span class="acard-admin-name">Marcus T.</span> 643 <span class="acard-admin-meta">2 days ago · Reviewed</span> 644 </div> 645 </div> 646 <div class="acard-admin-row acard-admin-row--read"> 647 <div class="acard-admin-avatar">JL</div> 648 <div class="acard-admin-info"> 649 <span class="acard-admin-name">Jordan L.</span> 650 <span class="acard-admin-meta">5 days ago · Reviewed</span> 651 </div> 652 </div> 653 <div class="acard-admin-row acard-admin-row--new"> 654 <div class="acard-admin-avatar">AR</div> 655 <div class="acard-admin-info"> 656 <span class="acard-admin-name">Alex R.</span> 657 <span class="acard-admin-meta acard-admin-meta--time">Just now</span> 658 </div> 659 <span class="acard-new-badge">NEW</span> 660 <button class="acard-view-report" type="button">View Report →</button> 661 </div> 662 </div> 663 <div class="acard-hint acard-admin-hint" aria-hidden="true"> 664 <span class="acard-hint-text">click here</span> 665 <svg class="acard-hint-arrow" viewB
665ox="0 0 46 46" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 666 <path d="M 6 40 C 14 28, 24 20, 38 8" /> 667 <polyline points="38,20 38,8 26,8" /> 668 </svg> 669 </div> 670 </div> 671 672 <!-- State 3: Report --> 673 <div class="acard-state acard-state--report"> 674 <div class="acard-report-top"> 675 <div class="acard-report-avatar">AR</div> 676 <div class="acard-report-meta"> 677 <span class="acard-report-name">Alex R.</span> 678 <span class="acard-report-role">OTR Driver — Southeast Region · Just now</span> 679 </div> 680 </div> 681 <div class="acard-retention"> 682 <div class="acard-retention-stat"> 683 <span class="acard-retention-pct">89%</span> 684 <span class="acard-retention-lbl">90-Day Retention</span> 685 </div> 686 <div class="acard-retention-divider"></div> 687 <div class="acard-retention-stat"> 688 <span class="acard-retention-pct">74%</span> 689 <span class="acard-retention-lbl">1-Year Retention</span> 690 </div> 691 </div> 692 <div class="acard-chips"> 693 <span class="acard-chip">Reliable</span> 694 <span class="acard-chip">Safety-Aware</span> 695 <span class="acard-chip">Steady Under Pressure</span> 696 </div> 697 <div class="acard-bars"> 698 <div class="acard-bar-row"> 699 <span class="acard-bar-label">Reliability</span> 700 <div class="acard-bar-track"> 701 <div class="acard-bar-fill acard-bar-fill--70"></div> 702 </div> 703 <span class="acard-bar-pct">70%</span> 704 </div> 705 <div class="acard-bar-row"> 706 <span class="acard-bar-label">Composure</span> 707 <div class="acard-bar-track"> 708 <div class="acard-bar-fill acard-bar-fill--92"></div> 709 </div> 710 <span class="acard-bar-pct">92%</span> 711 </div> 712 <div class="acard-bar-row"> 713 <span class="acard-bar-label">Adaptability</span> 714 <div class="acard-bar-track"> 715 <div class="acard-bar-fill acard-bar-fill--88"></div> 716 </div> 717 <span class="acard-bar-pct">88%</span> 718 </div> 719 <div class="acard-bars-fade"> 720 <div class="acard-bar-row"> 721 <span class="acard-bar-label">Safety</span> 722 <div class="acard-bar-track"> 723 <div class="acard-bar-fill acard-bar-fill--81"></div> 724 </div> 725 <span class="acard-bar-pct">81%</span> 726 </div> 727 <div class="acard-bar-row">
728 <span class="acard-bar-label">Judgment</span> 729 <div class="acard-bar-track"> 730 <div class="acard-bar-fill acard-bar-fill--76"></div> 731 </div> 732 <span class="acard-bar-pct">76%</span> 733 </div> 734 </div> 735 </div> 736 <div class="acard-chat"> 737 <div class="acard-chat-header"> 738 <svg class="acard-chat-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true"> 739 <path d="M8 1l1.5 3 3.5.5-2.5 2.5.5 3.5L8 9l-3 1.5.5-3.5L3 4.5 6.5 4 8 1z" fill="#2563eb" /> 740 </svg> 741 <span class="acard-chat-title">AI Insights</span> 742 </div> 743 <div class="acard-chat-messages"> 744 <div class="acard-msg acard-msg--ai"> 745 Composure and adaptability are strong retention signals. Reliability at 70% is trainable — three of your top drivers showed the same profile at hire. 746 </div> 747 <div class="acard-msg acard-msg--user"> 748 Are there similar drivers already on the team? 749 </div> 750 </div> 751 </div> 752 <div class="acard-cta-sep"></div> 753 <a href="/app/register" class="acard-next btn-cta acard-demo-cta">See this in your dashboard →</a> 754 <div class="acard-hint acard-report-hint" aria-hidden="true"> 755 <svg class="acard-hint-arrow" viewB
755ox="0 0 46 46" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 756 <path d="M 40 40 C 32 28, 22 20, 8 8" /> 757 <polyline points="8,20 8,8 20,8" /> 758 </svg> 759 <span class="acard-hint-text">Give it a try</span> 760 </div> 761 <button class="acard-replay" type="button">↻ Replay</button> 762 </div> 763 764 </div> 765 </div> 766 </div> 767 </section> 768 769 <section class="section report-preview-section" id="report-preview"> 770 <div class="section-inner"> 771 <div class="section-label">The Result</div> 772 <h2 class="section-title">Every report is workforce intelligence in action.</h2> 773 <p class="section-desc"> 774 The assessment is the first step. What comes back is the real value: retention odds, behavioral fit, and culture alignment—scored against the people already succeeding on your team, not generic industry benchmarks. This is the intelligence behind every hiring decision. 775 </p> 776 <div class="rpreview-grid"> 777 778 <!-- ROW 1: Progress bar outputs --> 779 780 <div class="rpreview-card rpreview-card--featured" data-aos="fade-up" data-aos-duration="600" data-aos-delay="0"> 781 <div class="rpreview-card-header"> 782 <div class="rpreview-icon"> 783 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> 784 <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" /> 785 <polyline points="9 12 11 14 15 10" /> 786 </svg> 787 </div> 788 <h3>Retention Prediction</h3> 789 </div> 790 <p>AI-modeled probabilities built from similar employees in your own workforce. Know who’s built to stay before day one.</p> 791 <div class="rpreview-example"> 792 <div class="rpex-bar-row"> 793 <span class="rpex-bar-label">90-Day</span> 794 <div class="rpex-bar-track"> 795 <div class="rpex-bar-fill rpex-bar-fill--blue" style="width:89%"></div> 796 </div> 797 <span class="rpex-bar-value">89%</span> 798 </div> 799 <div class="rpex-bar-row"> 800 <span class="rpex-bar-label">1-Year</span> 801 <div class="rpex-bar-track"> 802 <div class="rpex-bar-fill" style="width:74%"></div> 803 </div> 804 <span class="rpex-bar-value">74%</span> 805 </div> 806 <div class="rpex-footnote">Est. tenure · ~34 months</div> 807 </div> 808 </div> 809 810 <div class="rpreview-card rpreview-card--featured" data-aos="fade-up" data-aos-duration="600" data-aos-delay="80"> 811 <div class="rpreview-card-header"> 812 <div class="rpreview-icon"> 813 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> 814 <line x1="18" y1="20" x2="18" y2="10" /> 815 <line x1="12" y1="20" x2="12" y2="4" /> 816 <line x1="6" y1="20" x2="6" y2="14" /> 817 <line x1="2" y1="20" x2="22" y2="20" /> 818 </svg> 819 </div> 820 <h3>Behavioral Profile</h3> 821 </div> 822 <p>8 dimensions scored across personal and professional context—from leadership drive to adaptability under pressure.</p> 823 <div class="rpreview-example"> 824 <div class="rpex-dim-row">
825 <span class="rpex-dim-label">Consistency & Reliability</span> 826 <div class="rpex-bar-track"> 827 <div class="rpex-bar-fill rpex-bar-fill--dim-high" style="width:82%"></div> 828 </div> 829 <span class="rpex-dim-score">8.2</span> 830 </div> 831 <div class="rpex-dim-row"> 832 <span class="rpex-dim-label">Adaptability & Change</span> 833 <div class="rpex-bar-track"> 834 <div class="rpex-bar-fill rpex-bar-fill--dim-low" style="width:69%"></div> 835 </div> 836 <span class="rpex-dim-score">6.9</span> 837 </div> 838 <div class="rpex-footnote rpex-footnote--top">+ 6 more dimensions scored</div> 839 </div> 840 </div> 841 842 <div class="rpreview-card rpreview-card--featured" data-aos="fade-up" data-aos-duration="600" data-aos-delay="160"> 843 <div class="rpreview-card-header"> 844 <div class="rpreview-icon"> 845 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> 846 <circle cx="12" cy="12" r="10" /> 847 <circle cx="12" cy="12" r="6" /> 848 <circle cx="12" cy="12" r="2" /> 849 </svg> 850 </div> 851 <h3>Role & Culture Fit</h3> 852 </div> 853 <p>Two separate scores comparing the candidate against your actual workforce—not generic benchmarks.</p> 854 <div class="rpreview-example"> 855 <div class="rpex-bar-row"> 856 <span class="rpex-bar-label">Role Fit</span> 857 <div class="rpex-bar-track"> 858 <div class="rpex-bar-fill rpex-bar-fill--blue" style="width:84%"></div> 859 </div> 860 <span class="rpex-bar-value">84%</span> 861 </div> 862 <div class="rpex-bar-row"> 863 <span class="rpex-bar-label">Culture Fit</span> 864 <div class="rpex-bar-track"> 865 <div class="rpex-bar-fill" style="width:78%"></div> 866 </div> 867 <span class="rpex-bar-value">78%</span> 868 </div> 869 <div class="rpex-footnote">Compared against 24 active employees</div> 870 </div> 871 </div> 872 873 <!-- ROW 2 --> 874 875 <div class="rpreview-card" data-aos="fade-up" data-aos-duration="600" data-aos-delay="0"> 876 <div class="rpreview-card-header"> 877 <div class="rpreview-icon"> 878 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> 879 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /> 880 <circle cx="12" cy="7" r="4" /> 881 </svg> 882 </div> 883 <h3>Social Preference</h3> 884 </div> 885 <p>How the candidate shows up at work, makes decisions, and relates to others—mapped to a profile type that predicts cultural alignment.</p> 886 <div class="rpreview-example"> 887 <div class="rpex-archetype-row">
888 <span class="rpex-archetype-tag rpex-archetype-tag--primary">Primary</span> 889 <span class="rpex-archetype-name">Leader</span> 890 </div> 891 <p class="rpex-archetype-desc">Driven, accountable, and self-directed. Takes ownership and sets the pace for those around them.</p> 892 <div class="rpreview-chips"> 893 <span class="rpreview-chip">Adaptable</span> 894 <span class="rpreview-chip">Dedicated</span> 895 <span class="rpreview-chip">Self-Starter</span> 896 <span class="rpreview-chip">Accountable</span> 897 <span class="rpreview-chip">Resilient</span> 898 </div> 899 </div> 900 </div> 901 902 <div class="rpreview-card" data-aos="fade-up" data-aos-duration="600" data-aos-delay="80"> 903 <div class="rpreview-card-header"> 904 <div class="rpreview-icon"> 905 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> 906 <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /> 907 <polyline points="14 2 14 8 20 8" /> 908 <line x1="16" y1="13" x2="8" y2="13" /> 909 <line x1="16" y1="17" x2="8" y2="17" /> 910 </svg> 911 </div> 912 <h3>Summary</h3> 913 </div> 914 <p>A written narrative your team can act on: strengths, development areas, and where personal and professional patterns diverge.</p> 915 <div class="rpreview-example rpreview-example--quote"> 916 <p class="rpex-quote">“Strong leadership instincts and steady composure align well with your top-retaining drivers. Low early-exit risk across the board. Communication is a development area—set clear expectations early and this candidate should onboard smoothly.”</p> 917 </div> 918 </div> 919 920 <div class="rpreview-card" data-aos="fade-up" data-aos-duration="600" data-aos-delay="160"> 921 <div class="rpreview-card-header"> 922 <div class="rpreview-icon"> 923 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> 924 <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /> 925 </svg> 926 </div> 927 <h3>AI Chat</h3> 928 </div> 929 <p>The system surfaces what it finds—then you ask. Explore patterns, flag training gaps, or query your workforce.</p> 930 <div class="rpreview-example rpex-chat"> 931 <div class="rpex-bubble-row"> 932 <div class="rpex-avatar"><img src="/assets/images/icon.png" alt="" /></div> 933 <div class="rpex-bubble rpex-bubble--system">Reliability is above 90%. Communication is slightly below your top drivers.</div> 934 </div> 935 <div class="rpex-bubble rpex-bubble--user">Can they be coached on that?</div> 936 <div class="rpex-bubble-row"> 937 <div class="rpex-avatar"><img src="/assets/images/icon.png" alt="" /></div> 938 <div class="rpex-bubble rpex-bubble--system">Yes—it’s worked well with others. Set clear expectations early.</div> 939 </div> 940 </div> 941 </div> 942 943 </div> 944 </div> 945 </section> 946 947 <section class="section turnover-section" id="turnover"> 948 <div class="section-inner"> 949 <div class="section-label">The Problem</div> 950 <h2 class="section-title">Driver turnover is your most expensive recurring cost.</h2> 951 <p class="section-desc"> 952 The logistics industry sees roughly 100% driver turnover every year. Most companies accept it as unavoidable. It isn’t. 953 </p> 954 955 <div class="turnover-stat-row" data-aos="fade-up" data-aos-duration="600"> 956 <div class="turnover-stat">
957 <span class="turnover-stat-value">92%</span> 958 <span class="turnover-stat-label">Annual driver turnover<br>logistics industry average</span> 959 </div> 960 <div class="turnover-stat"> 961 <span class="turnover-stat-value">$8k+</span> 962 <span class="turnover-stat-label">Average replacement cost<br>per driver hired</span> 963 </div> 964 <div class="turnover-stat"> 965 <span class="turnover-stat-value">3–4 wks</span> 966 <span class="turnover-stat-label">Before a new hire<br>reaches full productivity</span> 967 </div> 968 <div class="turnover-stat"> 969 <span class="turnover-stat-value">40%+</span> 970 <span class="turnover-stat-label">Of drivers who leave<br>do so within 90 days</span> 971 </div> 972 </div> 973 974 </div> 975 </section> 976 977 <section class="section" id="features"> 978 <div class="section-inner"> 979 <div class="section-label">The Solution</div> 980 <div class="turnover-solve"> 981 <div class="turnover-solve-copy" data-aos="fade-right" data-aos-duration="600"> 982 <div class="turnover-copy-body"> 983 <h2 class="section-title">What you get with Workforce Intelligence</h2> 984 <p> 985 Turnover isn’t random. The behavior, work habits, and quality that predict how long someone stays show up before day one. AlignWave surfaces them in a 10-minute assessment. 986 </p> 987 <p> 988 Every report is measured against your own workforce, not an industry average. The more you use it, the better it understands what works for your company—putting things in your favor on every hire you make. 989 </p> 990 </div> 991 <a href="/app/register" class="btn-ghost-dark">Start my first assessment →</a> 992 </div> 993 <div class="turnover-solve-features" data-aos="fade-left" data-aos-duration="600" data-aos-delay="100"> 994 <div class="turnover-solve-feature"> 995 <div class="tsf-dot"></div> 996 <div class="turnover-solve-feature-text"> 997 <strong>90-day & 1-year retention</strong> 998 <span>See how likely someone is to stay, before they’re ever on the job—not after turnover already cost you.</span> 999 </div> 1000 </div> 1001 <div class="turnover-solve-feature"> 1002 <div class="tsf-dot"></div> 1003 <div class="turnover-solve-feature-text"> 1004 <strong>Matched against your workforce</strong> 1005 <span>Candidates are compared to the people already succeeding on your team—including how long they’ve stayed—not a generic industry number.</span> 1006 </div> 1007 </div> 1008 <div class="turnover-solve-feature"> 1009 <div class="tsf-dot"></div> 1010 <div class="turnover-solve-feature-text"> 1011 <strong>Behavior, not just experience</strong> 1012 <span>Years behind the wheel don’t predict longevity. The habits and instincts that do—that’s what we measure.</span> 1013 </div> 1014 </div> 1015 </div> 1016 </div> 1017 </div> 1018 </section> 1019 1020 <section class="section" id="workforce-network"> 1021 <div class="section-inner"> 1022 <div class="network-split"> 1023 <div class="network-copy" data-aos="fade-right" data-aos-duration="700"> 1024 <div class="section-label">Graph3D</div> 1025 <h2 class="section-title"> 1026 Your company has a story to tell 1027 </h2> 1028 <p class="section-desc"> 1029 Top performers glow at the center of their groups, connections show 1030 who works well together, and work groups form naturally around 1031 shared strengths. New candidates fit in among 1032 them, and every connection says something about where the team is 1033 headed. Some roles pull in the same direction—others stand 1034 apart, and either one is worth understanding about how the company 1035 actually works. 1036 </p> 1037 <ul class="network-bullets"> 1038 <li>
1039 <span class="bullet-dot"></span> 1040 Top performers naturally cluster together, revealing what success 1041 looks like in each role 1042 </li> 1043 <li> 1044 <span class="bullet-dot"></span> 1045 New candidates are scored and placed against your actual 1046 workforceânot generic benchmarks 1047 </li> 1048 <li> 1049 <span class="bullet-dot"></span> 1050 Identify those who need more development and build a stronger, more 1051 cohesive team over time 1052 </li> 1053 </ul> 1054 </div> 1055 <div class="network-canvas-outer" data-aos="fade-left" data-aos-duration="700" data-aos-delay="100"> 1056 <div id="graph-tooltip" class="graph-tooltip"> 1057 <span class="gtt-name" id="gtt-name"></span> 1058 <span class="gtt-role" id="gtt-role"></span> 1059 <div class="gtt-sep"></div> 1060 <div class="gtt-score-row"> 1061 <span class="gtt-score-label">Fit Score</span> 1062 <div class="gtt-score-bar"> 1063 <div class="gtt-score-fill" id="gtt-fill"></div> 1064 </div> 1065 <span class="gtt-score-val" id="gtt-val"></span> 1066 </div> 1067 <div class="gtt-meta-row"> 1068 <span class="gtt-tenure" id="gtt-tenure"></span> 1069 <span class="gtt-dollar" id="gtt-dollar"></span> 1070 </div> 1071 <div class="gtt-conns" id="gtt-conns"></div> 1072 </div> 1073 <div class="network-canvas-wrap" id="graph-wrap"> 1074 <div class="graph-live-badge"> 1075 <span class="graph-live-dot"></span>Live 1076 </div> 1077 <div class="graph-hint-bar" id="graph-hint-bar"> 1078 Drag to rotate • Scroll to zoom • 1079 Click node to inspect 1080 </div> 1081 <div class="graph-legend"> 1082 <span class="gleg gleg-all active">All</span> 1083 <span class="gleg gleg-yellow">Top performers</span> 1084 <span class="gleg gleg-blue">Strong matches</span> 1085 </div> 1086 </div> 1087 </div> 1088 </div> 1089 </div> 1090 </section> 1091 1092
1092<script type="module"> 1093 import * as THREE from "three"; 1094 import { 1095 OrbitControls 1096 } from "three/addons/controls/OrbitControls.js"; 1097 1098 (function() { 1099 const wrap = document.getElementById("graph-wrap"); 1100 if (!wrap) return; 1101 1102 function r(s) { 1103 const x = Math.sin(s + 1.7) * 43758.5453; 1104 return x - Math.floor(x); 1105 } 1106 1107 // ââ Node dataset â 5 employees + 5 candidates per cluster (30 total, 50/50) â 1108 const NODES = [ 1109 // ââ Cluster 0: Top performers ââââââââââââââââââââââââââââââââââââââââââ 1110 { 1111 name: "M. Torres", 1112 role: "Senior Driver", 1113 score: 91, 1114 ci: 0, 1115 tenure: 7.2, 1116 dollar: 5, 1117 isCandidate: false 1118 }, 1119 { 1120 name: "S. Kaur", 1121 role: "Fleet Lead", 1122 score: 88, 1123 ci: 0, 1124 tenure: 5.8, 1125 dollar: 5, 1126 isCandidate: false 1127 }, 1128 { 1129 name: "J. Rivera", 1130 role: "Route Specialist", 1131 score: 85, 1132 ci: 0, 1133 tenure: 4.1, 1134 dollar: 4, 1135 isCandidate: false 1136 }, 1137 { 1138 name: "P. Monroe", 1139 role: "Senior Driver", 1140 score: 82, 1141 ci: 0, 1142 tenure: 3.5, 1143 dollar: 4, 1144 isCandidate: false 1145 }, 1146 { 1147 name: "D. Walsh", 1148 role: "Fleet Supervisor", 1149 score: 79, 1150 ci: 0, 1151 tenure: 6.0, 1152 dollar: 4, 1153 isCandidate: false 1154 }, 1155 { 1156 name: "R. Chen", 1157 role: "Candidate", 1158 score: 90, 1159 ci: 0, 1160 tenure: 0, 1161 dollar: 5, 1162 isCandidate: true 1163 }, 1164 { 1165 name: "A. Davis", 1166 role: "Candidate", 1167 score: 86, 1168 ci: 0, 1169 tenure: 0, 1170 dollar: 4, 1171 isCandidate: true 1172 }, 1173 { 1174 name: "M. Park", 1175 role: "Candidate", 1176 score: 83, 1177 ci: 0, 1178 tenure: 0, 1179 dollar: 4, 1180 isCandidate: true 1181 }, 1182 { 1183 name: "T. Singh", 1184 role: "Candidate", 1185 score: 80, 1186 ci: 0, 1187 tenure: 0, 1188 dollar: 4, 1189 isCandidate: true 1190 }, 1191 { 1192 name: "W. Lee", 1193 role: "Candidate", 1194 score: 77, 1195 ci: 0, 1196 tenure: 0, 1197 dollar: 4, 1198 isCandidate: true 1199 }, 1200 // ââ Cluster 1: Strong matches ââââââââââââââââââââââââââââââââââââââââââ 1201 { 1202 name: "C. Burns", 1203 role: "Driver", 1204 score: 72, 1205 ci: 1, 1206 tenure: 2.8, 1207 dollar: 3, 1208 isCandidate: false 1209 }, 1210 { 1211 name: "D. Lim", 1212 role: "Driver", 1213 score: 68, 1214 ci: 1, 1215 tenure: 1.5, 1216 dollar: 3, 1217 isCandidate: false 1218 }, 1219 { 1220 name: "N. Abbas", 1221 role: "Fleet Coordinator", 1222 score: 65, 1223 ci: 1, 1224 tenure: 2.1, 1225 dollar: 3, 1226 isCandidate: false 1227 }, 1228 { 1229 name: "E. Reyes", 1230 role: "Driver", 1231 score: 62, 1232 ci: 1, 1233 tenure: 1.8, 1234 dollar: 2, 1235 isCandidate: false 1236 }, 1237 { 1238 name: "F. Mills", 1239 role: "Driver Trainee", 1240 score: 59, 1241 ci: 1, 1242 tenure: 0.9, 1243 dollar: 2, 1244 isCandidate: false 1245 }, 1246 { 1247 name: "K. Martinez", 1248 role: "Candidate", 1249 score: 71, 1250 ci: 1, 1251 tenure: 0, 1252 dollar: 3, 1253 isCandidate: true 1254 }, 1255 { 1256 name: "B. Nguyen", 1257 role: "Candidate", 1258 score: 67, 1259 ci: 1, 1260 tenure: 0, 1261 dollar: 3, 1262 isCandidate: true 1263 }, 1264 { 1265 name: "T. Osei", 1266 role: "Candidate", 1267 score: 64, 1268 ci: 1, 1269 tenure: 0, 1270 dollar: 2, 1271 isCandidate: true 1272 }, 1273 { 1274 name: "U. Patel", 1275 role: "Candidate", 1276 score: 61, 1277 ci: 1, 1278 tenure: 0, 1279 dollar: 2, 1280 isCandidate: true 1281 }, 1282 { 1283 name: "V. Kim", 1284 role: "Candidate", 1285 score: 57, 1286 ci: 1, 1287 tenure: 0, 1288 dollar: 2, 1289 isCandidate: true 1290 }, 1291 // ââ Cluster 2: Developing âââââââââââââââââââââââââââââââââââââââââââââ 1292 { 1293 name: "G. Abbott", 1294 role: "Driver Trainee", 1295 score: 44, 1296 ci: 2, 1297 tenure: 1.1, 1298 dollar: 2, 1299 isCandidate: false 1300 }, 1301 { 1302 name: "H. Santos", 1303 role: "Driver Trainee", 1304 score: 40, 1305 ci: 2, 1306 tenure: 0.8, 1307 dollar: 1, 1308 isCandidate: false 1309 }, 1310 { 1311 name: "I. Petrov", 1312 role: "Driver", 1313 score: 37, 1314 ci: 2, 1315 tenure: 0.5, 1316 dollar: 1, 1317 isCandidate: false 1318 }, 1319 { 1320 name: "J. Costa", 1321 role: "Driver Trainee", 1322 score: 33, 1323 ci: 2, 1324 tenure: 0.4, 1325 dollar: 1, 1326 isCandidate: false 1327 }, 1328 { 1329 name: "K. Bell", 1330 role: "Trainee", 1331 score: 30, 1332 ci: 2, 1333 tenure: 0.3, 1334 dollar: 1, 1335 isCandidate: false 1336 }, 1337 { 1338 name: "L. Grant", 1339 role: "Candidate", 1340 score: 43, 1341 ci: 2, 1342 tenure: 0, 1343 dollar: 1, 1344 isCandidate: true 1345 }, 1346 { 1347 name: "M. Stone", 1348 role: "Candidate", 1349 score: 39, 1350 ci: 2, 1351 tenure: 0, 1352 dollar: 1, 1353 isCandidate: true 1354 }, 1355 { 1356 name: "N. Fox", 1357 role: "Candidate", 1358 score: 36, 1359 ci: 2, 1360 tenure: 0, 1361 dollar: 1, 1362 isCandidate: true 1363 }, 1364 { 1365 name: "O. Webb", 1366 role: "Candidate", 1367 score: 32, 1368 ci: 2, 1369 tenure: 0, 1370 dollar: 1, 1371 isCandidate: true 1372 }, 1373 { 1374 name: "Q. Hart", 1375 role: "Candidate", 1376 score: 28, 1377 ci: 2, 1378 tenure: 0, 1379 dollar: 1, 1380 isCandidate: true 1381 }, 1382 ]; 1383 1384 const CLUSTERS = [{ 1385 col: 0x7a8fa5, 1386 cx: 0.80, 1387 cy: 0.28, 1388 cz: 0.05, 1389 spread: 0.52 1390 }, 1391 { 1392 col: 0x60a5fa, 1393 cx: -0.68, 1394 cy: -0.12, 1395 cz: 0.18, 1396 spread: 0.54 1397 }, 1398 { 1399 col: 0xb8c8d2, 1400 cx: 0.05, 1401 cy: -0.58, 1402 cz: -0.28, 1403 spread: 0.58 1404 }, 1405 ]; 1406 1407 const C_ACTIVE = 0xe6f85e; 1408 const C_NEIGHBOR = 0x0062fb; 1409 const C_DIM = 0xeaeef2; 1410 1411 function lightenHex(hex, amt) { 1412 const rv = (hex >> 16) & 0xff, 1413 gv = (hex >> 8) & 0xff, 1414 bv = hex & 0xff;
1415 const m = (c) => Math.round(c + (255 - c) * amt); 1416 return (m(rv) << 16) | (m(gv) << 8) | m(bv); 1417 } 1418 1419 function scaleHex(hex, amt) { 1420 const rv = (hex >> 16) & 0xff, 1421 gv = (hex >> 8) & 0xff, 1422 bv = hex & 0xff; 1423 const m = (c) => Math.round(c * amt); 1424 return (m(rv) << 16) | (m(gv) << 8) | m(bv); 1425 } 1426 const nodeBaseHex = NODES.map((nd) => 1427 nd.isCandidate ? lightenHex(CLUSTERS[nd.ci].col, 0.34) : CLUSTERS[nd.ci].col, 1428 ); 1429 1430 // ââ Positions âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1431 let seed = 0; 1432 const pos = NODES.map((n) => { 1433 const cl = CLUSTERS[n.ci]; 1434 const phi = Math.acos(1 - 2 * r(seed++)); 1435 const th = r(seed++) * Math.PI * 2; 1436 const rad = r(seed++) * cl.spread; 1437 return new THREE.Vector3( 1438 cl.cx + rad * Math.sin(phi) * Math.cos(th), 1439 cl.cy + rad * Math.sin(phi) * Math.sin(th), 1440 cl.cz + rad * Math.cos(phi), 1441 ); 1442 }); 1443 1444 // ââ Edges + adjacency âââââââââââââââââââââââââââââââââââââââââââââââââââââ 1445 const edges = []; 1446 const adj = NODES.map(() => []); 1447 for (let i = 0; i < NODES.length; i++) { 1448 for (let j = i + 1; j < NODES.length; j++) { 1449 const same = NODES[i].ci === NODES[j].ci; 1450 if (r(i * 37 + j * 11 + 7) < (same ? 0.35 : 0.03)) { 1451 edges.push([i, j]); 1452 adj[i].push(j); 1453 adj[j].push(i); 1454 } 1455 } 1456 } 1457 1458 // ââ Scene âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1459 const scene = new THREE.Scene(); 1460 scene.background = new THREE.Color(0xffffff); 1461 scene.fog = new THREE.FogExp2(0xffffff, 0.022); 1462 1463 const W = wrap.clientWidth || 500; 1464 const H = wrap.clientHeight || 500; 1465 const camera = new THREE.PerspectiveCamera(55, W / H, 0.05, 60); 1466 camera.position.set(0, 0, 2.8); 1467 1468 // ââ Renderer â same pipeline as the app âââââââââââââââââââââââââââââââââââ 1469 const renderer = new THREE.WebGLRenderer({ 1470 antialias: true 1471 }); 1472 renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); 1473 renderer.setSize(W, H); 1474 renderer.setClearColor(0xffffff, 1); 1475 renderer.outputColorSpace = THREE.SRGBColorSpace; 1476 renderer.toneMapping = THREE.ACESFilmicToneMapping; 1477 renderer.toneMappingExposure = 1.0; 1478 wrap.insertBefore(renderer.domElement, wrap.firstChild); 1479 1480 // ââ Orbit controls ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1481 const controls = new OrbitControls(camera, renderer.domElement); 1482 controls.enableDamping = true; 1483 controls.dampingFactor = 0.065; 1484 controls.enablePan = false; 1485 controls.minDistance = 1.5; 1486 controls.maxDistance = 10; 1487 controls.autoRotate = false;
1488 controls.autoRotateSpeed = 3.0; 1489 controls.zoomToCursor = true; 1490 1491 const hintBar = document.getElementById("graph-hint-bar"); 1492 let userIdleTimer = null; 1493 1494 controls.addEventListener("start", () => { 1495 controls.autoRotate = false; 1496 if (hintBar) hintBar.classList.add("faded"); 1497 }); 1498 controls.addEventListener("end", () => {}); 1499 setTimeout(() => { 1500 if (hintBar) hintBar.classList.add("faded"); 1501 }, 5000); 1502 1503 // Start rotating when section scrolls into view 1504 const networkSection = document.getElementById("workforce-network"); 1505 if (networkSection && "IntersectionObserver" in window) { 1506 const obs = new IntersectionObserver((entries) => { 1507 if (entries[0].isIntersecting) { 1508 controls.autoRotate = true; 1509 obs.disconnect(); 1510 } 1511 }, { 1512 threshold: 0.2 1513 }); 1514 obs.observe(networkSection); 1515 } else { 1516 controls.autoRotate = true; 1517 } 1518 1519 // ââ Lighting â matches app âââââââââââââââââââââââââââââââââââââââââââââââââ 1520 scene.add(new THREE.AmbientLight(0xffffff, 1.05)); 1521 const keyLight = new THREE.DirectionalLight(0xffffff, 0.85); 1522 keyLight.position.set(4, 6, 5); 1523 scene.add(keyLight); 1524 const fillLight = new THREE.DirectionalLight(0xffffff, 0.35); 1525 fillLight.position.set(-3, -1, -4); 1526 scene.add(fillLight); 1527 1528 // ââ Nodes â shiny spheres with MeshPhongMaterial ââââââââââââââââââââââââââ 1529 function nRad(nd) { 1530 const base = 0.022 + (nd.score / 100) * 0.016; 1531 return nd.isCandidate ? base * 0.88 : base; 1532 } 1533 1534 const meshes = []; 1535 NODES.forEach((nd, i) => { 1536 const m = new THREE.Mesh( 1537 new THREE.SphereGeometry(nRad(nd), 24, 16), 1538 new THREE.MeshPhongMaterial({ 1539 color: nodeBaseHex[i], 1540 shininess: 180, 1541 specular: 0xeeeeee, 1542 emissive: 0x000000, 1543 }), 1544 ); 1545 m.position.copy(pos[i]); 1546 m.userData.idx = i; 1547 scene.add(m); 1548 meshes.push(m); 1549 }); 1550 1551 // ââ Cylinder edge helpers âââââââââââââââââââââââââââââââââââââââââââââââââ 1552 const EDGE_RADIUS = 0.005; 1553 const _cylDummy = new THREE.Object3D(); 1554 const _cylUp = new THREE.Vector3(0, 1, 0); 1555 const _cylQ = new THREE.Quaternion(); 1556 const _cylDir = new THREE.Vector3(); 1557 1558 function setCylinderMatrix(from, to) { 1559 const dx = to.x - from.x, 1560 dy = to.y - from.y, 1561 dz = to.z - from.z; 1562 const len = Math.sqrt(dx * dx + dy * dy + dz * dz); 1563 if (len < 0.0001) { 1564 _cylDummy.scale.set(0, 0, 0); 1565 _cylDummy.updateMatrix(); 1566 return; 1567 } 1568 _cylDummy.position.set((from.x + to.x) * 0.5, (from.y + to.y) * 0.5, (from.z + to.z) * 0.5); 1569 _cylDir.set(dx / len, dy / len, dz / len); 1570 _cylQ.setFromUnitVectors(_cylUp, _cylDir); 1571 _cylDummy.quaternion.copy(_cylQ);
1572 _cylDummy.scale.set(1, len, 1); 1573 _cylDummy.updateMatrix(); 1574 } 1575 1576 // ââ Base edge mesh â all connections as gray cylinders ââââââââââââââââââââ 1577 const edgeGeo = new THREE.CylinderGeometry(EDGE_RADIUS, EDGE_RADIUS, 1, 6); 1578 const edgeMat = new THREE.MeshPhongMaterial({ 1579 shininess: 60, 1580 specular: 0x888888 1581 }); 1582 const edgeIM = new THREE.InstancedMesh(edgeGeo, edgeMat, edges.length); 1583 edgeIM.instanceMatrix.setUsage(THREE.DynamicDrawUsage); 1584 edges.forEach(([i, j], k) => { 1585 setCylinderMatrix(pos[i], pos[j]); 1586 edgeIM.setMatrixAt(k, _cylDummy.matrix); 1587 edgeIM.setColorAt(k, new THREE.Color(0xd1d5db)); 1588 }); 1589 edgeIM.instanceMatrix.needsUpdate = true; 1590 edgeIM.instanceColor.needsUpdate = true; 1591 scene.add(edgeIM); 1592 1593 // ââ Selection edge mesh â only connected edges, built on demand âââââââââââ 1594 let selEdgeMesh = null; 1595 1596 function clearSelEdgeMesh() { 1597 if (selEdgeMesh) { 1598 scene.remove(selEdgeMesh); 1599 selEdgeMesh.geometry.dispose(); 1600 selEdgeMesh.material.dispose(); 1601 selEdgeMesh = null; 1602 } 1603 } 1604 1605 function buildSelEdgeMesh(idx) { 1606 clearSelEdgeMesh(); 1607 const connEdges = edges.filter(([i, j]) => i === idx || j === idx); 1608 if (!connEdges.length) return; 1609 const geo = new THREE.CylinderGeometry(EDGE_RADIUS, EDGE_RADIUS, 1, 6); 1610 selEdgeMesh = new THREE.InstancedMesh( 1611 geo, 1612 new THREE.MeshPhongMaterial({ 1613 color: C_NEIGHBOR, 1614 emissive: scaleHex(C_NEIGHBOR, 0.6), 1615 shininess: 60, 1616 specular: 0x888888, 1617 }), 1618 connEdges.length, 1619 ); 1620 connEdges.forEach(([i, j], k) => { 1621 setCylinderMatrix(pos[i], pos[j]); 1622 selEdgeMesh.setMatrixAt(k, _cylDummy.matrix); 1623 }); 1624 selEdgeMesh.instanceMatrix.needsUpdate = true; 1625 scene.add(selEdgeMesh); 1626 } 1627 1628 // ââ Color helpers âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1629 const cActive = new THREE.Color(C_ACTIVE); 1630 const cNeighbor = new THREE.Color(C_NEIGHBOR); 1631 const cDim = new THREE.Color(C_DIM); 1632 const cActiveEmissive = new THREE.Color(scaleHex(C_ACTIVE, 0.65)); 1633 const cNeighborEmissive = new THREE.Color(scaleHex(C_NEIGHBOR, 0.6)); 1634 const cNoEmissive = new THREE.Color(0x000000); 1635 1636 let selectedIdx = -1; 1637 let actCI = -1; 1638 1639 function getNodeColor(i) { 1640 if (actCI >= 0) return NODES[i].ci === actCI ? cActive.clone() : cDim.clone(); 1641 if (selectedIdx < 0) return new THREE.Color(nodeBaseHex[i]); 1642 if (i === selectedIdx) return cActive.clone(); 1643 if (adj[selectedIdx].includes(i)) return cNeighbor.clone(); 1644 return cDim.clone(); 1645 } 1646 1647 // Emissive glow keeps the highlight color true to the brand hex regardless 1648 // of light angle/tone mapping â only active/neighbor nodes glow. 1649 function getNodeEmissive(i) { 1650 if (actCI >= 0) return NODES[i].ci === actCI ? cActiveEmissive : cNoEmissive; 1651 if (selectedIdx < 0) return cNoEmissive; 1652 if (i === selectedIdx) return cActiveEmissive; 1653 if (adj[selectedIdx].includes(i)) return cNeighborEmissive; 1654 return cNoEmissive; 1655 } 1656 1657 function refreshColors() {
1658 meshes.forEach((m, i) => { 1659 m.material.color.copy(getNodeColor(i)); 1660 m.material.emissive.copy(getNodeEmissive(i)); 1661 }); 1662 if (selectedIdx >= 0) { 1663 edgeIM.visible = false; 1664 buildSelEdgeMesh(selectedIdx); 1665 } else { 1666 clearSelEdgeMesh(); 1667 edgeIM.visible = false; 1668 } 1669 } 1670 1671 // ââ Screen-space hit detection ââââââââââââââââââââââââââââââââââââââââââââ 1672 const HIT_PX = 18; 1673 const _v = new THREE.Vector3(); 1674 1675 function findNodeAtScreen(cx, cy) { 1676 camera.updateMatrixWorld(); 1677 const rect = wrap.getBoundingClientRect(); 1678 const mx = cx - rect.left, 1679 my = cy - rect.top; 1680 const limSq = HIT_PX * HIT_PX; 1681 let bestIdx = -1, 1682 bestSq = limSq; 1683 for (let i = 0; i < NODES.length; i++) { 1684 _v.copy(pos[i]).project(camera); 1685 if (_v.z > 1) continue; 1686 const sx = (_v.x + 1) * 0.5 * rect.width; 1687 const sy = (-_v.y + 1) * 0.5 * rect.height; 1688 const d2 = (mx - sx) ** 2 + (my - sy) ** 2; 1689 if (d2 < bestSq) { 1690 bestSq = d2; 1691 bestIdx = i; 1692 } 1693 } 1694 return bestIdx; 1695 } 1696 1697 // ââ Info card âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1698 const tip = document.getElementById("graph-tooltip"); 1699 const ttName = document.getElementById("gtt-name"); 1700 const ttRole = document.getElementById("gtt-role"); 1701 const ttFill = document.getElementById("gtt-fill"); 1702 const ttVal = document.getElementById("gtt-val"); 1703 const ttTen = document.getElementById("gtt-tenure"); 1704 const ttDol = document.getElementById("gtt-dollar"); 1705 const ttConns = document.getElementById("gtt-conns"); 1706 1707 function scoreColor(s) { 1708 if (s >= 85) return "#3b82f6"; 1709 if (s >= 65) return "#93c5fd"; 1710 return "#b8c8d2"; 1711 } 1712 1713 function tenureStr(t) { 1714 if (t === 0) return "New candidate"; 1715 if (t < 1) return Math.round(t * 12) + " mo. tenure"; 1716 return t.toFixed(1) + " yrs tenure"; 1717 } 1718 1719 function dollarHTML(rank) { 1720 let s = ""; 1721 for (let i = 1; i <= 5; i++) 1722 s += `<span class="${i <= rank ? "gtt-d-on" : "gtt-d-off"}">$</span>`; 1723 return s; 1724 } 1725 1726 function showCard(idx) { 1727 if (!tip) return; 1728 const nd = NODES[idx]; 1729 ttName.textContent = nd.name; 1730 ttRole.textContent = nd.isCandidate ? "New Candidate" : nd.role; 1731 ttRole.style.color = nd.isCandidate ? "#917200" : ""; 1732 ttFill.style.width = nd.score + "%"; 1733 ttFill.style.background = scoreColor(nd.score); 1734 ttVal.textContent = nd.score; 1735 ttTen.textContent = tenureStr(nd.tenure); 1736 ttDol.innerHTML = dollarHTML(nd.dollar); 1737 const cc = adj[idx].length; 1738 ttConns.textContent = cc + " connection" + (cc !== 1 ? "s" : ""); 1739 tip.style.opacity = "1"; 1740 } 1741 1742 function hideCard() { 1743 if (tip) tip.style.opacity = "0"; 1744 } 1745 1746 // ââ Pointer interaction âââââââââââââââââââââââââââââââââââââââââââââââââââ 1747 let pdX = 0, 1748 pdY = 0, 1749 hovIdx = -1; 1750 1751 wrap.addEventListener("pointermove", (e) => { 1752 const idx = findNodeAtScreen(e.clientX, e.clientY); 1753 if (idx !== hovIdx) { 1754 hovIdx = idx; 1755 if (idx >= 0) { 1756 showCard(idx); 1757 wrap.style.cursor = "pointer"; 1758 } else { 1759 if (selectedIdx >= 0) showCard(selectedIdx); 1760 else hideCard(); 1761 wrap.style.cursor = ""; 1762 } 1763 } 1764 }); 1765 1766 wrap.addEventListener("pointerleave", () => { 1767 hovIdx = -1; 1768 if (selectedIdx >= 0) showCard(selectedIdx); 1769 else hideCard(); 1770 wrap.style.cursor = ""; 1771 }); 1772 1773 wrap.addEventListener("pointerdown", (e) => { 1774 pdX = e.clientX; 1775 pdY = e.clientY; 1776 }); 1777 1778 wrap.addEventListener("click", (e) => { 1779 if ((e.clientX - pdX) ** 2 + (e.clientY - pdY) ** 2 > 25) return; 1780 if (e.target.closest(".graph-legend")) return; 1781 const idx = hovIdx >= 0 ? hovIdx : findNodeAtScreen(e.clientX, e.clientY); 1782 if (idx >= 0) { 1783 stopAutoPlay(); 1784 scheduleResume(); 1785 actCI = -1; 1786 syncLegActive(); 1787 selectedIdx = selectedIdx === idx ? -1 : idx; 1788 refreshColors(); 1789 if (selectedIdx >= 0) showCard(selectedIdx); 1790 else hideCard(); 1791 } else { 1792 selectedIdx = -1; 1793 if (actCI < 0) refreshColors(); 1794 hideCard(); 1795 } 1796 }); 1797 1798 const legEls = wrap.querySelectorAll(".gleg"); 1799 const CHIP_CI = [-1, 0, 1]; 1800 1801 function syncLegActive() {
1802 legEls.forEach((l, k) => l.classList.toggle("active", CHIP_CI[k] === actCI)); 1803 } 1804 1805 legEls.forEach((el, i) => { 1806 el.addEventListener("click", () => { 1807 stopAutoPlay(); 1808 scheduleResume(); 1809 selectedIdx = -1; 1810 hovIdx = -1; 1811 const chipCI = CHIP_CI[i]; 1812 actCI = chipCI === -1 || actCI === chipCI ? -1 : chipCI; 1813 syncLegActive(); 1814 refreshColors(); 1815 hideCard(); 1816 }); 1817 }); 1818 1819 // ââ Auto-play âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1820 let autoPlayTimer = null, 1821 autoPlayIdx = -1, 1822 autoPlayActive = false; 1823 1824 function autoPlayNext() { 1825 if (!autoPlayActive) return; 1826 const jump = Math.round(NODES.length / 3); 1827 autoPlayIdx = (autoPlayIdx + 1) % NODES.length; 1828 const nodeIdx = Math.floor(autoPlayIdx / 3) + (autoPlayIdx % 3) * jump; 1829 actCI = -1; 1830 syncLegActive(); 1831 selectedIdx = nodeIdx; 1832 refreshColors(); 1833 showCard(nodeIdx); 1834 autoPlayTimer = setTimeout(autoPlayNext, 2000); 1835 } 1836 1837 function startAutoPlay() { 1838 autoPlayActive = true; 1839 autoPlayIdx = -1; 1840 clearTimeout(autoPlayTimer); 1841 autoPlayNext(); 1842 } 1843 1844 function stopAutoPlay() { 1845 autoPlayActive = false; 1846 clearTimeout(autoPlayTimer); 1847 autoPlayTimer = null; 1848 } 1849 1850 function scheduleResume() { 1851 clearTimeout(userIdleTimer); 1852 userIdleTimer = setTimeout(() => { 1853 controls.autoRotate = true; 1854 startAutoPlay(); 1855 }, 60000); 1856 } 1857 1858 // ââ Animation loop ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1859 function animate() { 1860 requestAnimationFrame(animate); 1861 controls.update(); 1862 renderer.render(scene, camera); 1863 } 1864 animate(); 1865 startAutoPlay(); 1866 1867 // ââ Camera position by canvas size ââââââââââââââââââââââââââââââââââââââââ 1868 function applyCameraForSize(w) { 1869 if (w < 600) { 1870 camera.position.set(0, 0, 2.1); 1871 controls.target.set(0, -0.1, 0); 1872 } else { 1873 camera.position.set(0, 0, 2.8); 1874 controls.target.set(0, 0, 0); 1875 } 1876 controls.update(); 1877 } 1878 applyCameraForSize(W); 1879 1880 // ââ Resize ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1881 new ResizeObserver(() => { 1882 const w = wrap.clientWidth, 1883 h = wrap.clientHeight; 1884 if (w > 0 && h > 0) { 1885 renderer.setSize(w, h); 1886 camera.aspect = w / h; 1887 camera.updateProjectionMatrix(); 1888 applyCameraForSize(w); 1889 } 1890 }).observe(wrap); 1891 })(); 1892 </script>
1892 1893 1894 <section class="section" id="geotrend"> 1895 <div class="section-inner"> 1896 1897 <!-- Text above map --> 1898 <div class="geotrend-header" data-aos="fade-up" data-aos-duration="700"> 1899 <div class="section-label">GeoTrend</div> 1900 <h2 class="section-title"> 1901 Learn trends and groups by region 1902 </h2> 1903 <p class="section-desc"> 1904 The same relationships show up differently depending on where people are. This map traces those patterns by location: how people in one region relate to each other, where certain roles or strengths cluster, and how that compares from one area to the next. 1905 </p> 1906 </div> 1907 1908 <!-- Cards above map --> 1909 <div class="geotrend-footer" data-aos="fade-up" data-aos-duration="700" data-aos-delay="150"> 1910 <div class="geotrend-cards"> 1911 <div class="geotrend-card"> 1912 <div class="geotrend-card-header"> 1913 <div class="geotrend-card-icon"> 1914 <i class="fa-solid fa-route"></i> 1915 </div> 1916 <h4 class="geotrend-card-title">Corridor Intelligence</h4> 1917 </div> 1918 <p class="geotrend-card-desc">Identify high-retention corridors and source more candidates from proven regions.</p> 1919 </div> 1920 <div class="geotrend-card"> 1921 <div class="geotrend-card-header"> 1922 <div class="geotrend-card-icon"> 1923 <i class="fa-solid fa-bell"></i> 1924 </div> 1925 <h4 class="geotrend-card-title">Early Warning</h4> 1926 </div> 1927 <p class="geotrend-card-desc">Spot early-exit clusters by region before they become a costly pattern.</p> 1928 </div> 1929 <div class="geotrend-card"> 1930 <div class="geotrend-card-header"> 1931 <div class="geotrend-card-icon"> 1932 <i class="fa-solid fa-ranking-star"></i> 1933 </div> 1934 <h4 class="geotrend-card-title">Geographic Scoring</h4> 1935 </div> 1936 <p class="geotrend-card-desc">Score new candidates based on the geographic profile of your top performers.</p> 1937 </div> 1938 </div> 1939 </div> 1940 1941 <!-- Full-width map --> 1942 <div class="map-canvas-outer" data-aos="fade-up" data-aos-duration="700" data-aos-delay="100"> 1943 <div class="network-canvas-wrap map-canvas-wrap map-wide" id="map-wrap"> 1944 <div class="graph-live-badge map-live-badge"> 1945 <span class="graph-live-dot"></span>Live 1946 </div> 1947 1948 <div class="map-legend-bar"> 1949 <span class="mleg mleg-all active">All</span> 1950 <span class="mleg mleg-top">Top performers</span> 1951 <span class="mleg mleg-strong">Strong matches</span> 1952 </div> 1953 1954 <div id="geo-map"></div> 1955 </div> 1956 1957 <div id="map-info-card" class="graph-tooltip"> 1958 <span class="gtt-name" id="mic-name"></span> 1959 <span class="gtt-role" id="mic-type"></span> 1960 <div class="mic-location" id="mic-location"></div> 1961 <div class="gtt-sep"></div> 1962 <div class="gtt-score-row"> 1963 <span class="gtt-score-label">Retention</span> 1964 <div class="gtt-score-bar"> 1965 <div class="gtt-score-fill" id="mic-fill"></div> 1966 </div>
1967 <span class="gtt-score-val" id="mic-val"></span> 1968 </div> 1969 <div class="gtt-meta-row"> 1970 <span class="gtt-tenure" id="mic-tenure"></span> 1971 <span class="mic-status" id="mic-status"></span> 1972 </div> 1973 </div> 1974 </div> 1975 1976 </div> 1977 </section> 1978 1979
1979<script> 1980 (function() { 1981 var PINS = [ 1982 // Active employees 1983 { 1984 name: 'J. Martinez', 1985 city: 'Dallas', 1986 state: 'TX', 1987 lat: 32.7767, 1988 lng: -96.7970, 1989 score: 94, 1990 isCandidate: false, 1991 tenure: 3.2 1992 }, 1993 { 1994 name: 'S. Johnson', 1995 city: 'Chicago', 1996 state: 'IL', 1997 lat: 41.8781, 1998 lng: -87.6298, 1999 score: 87, 2000 isCandidate: false, 2001 tenure: 2.1 2002 }, 2003 { 2004 name: 'L. Chen', 2005 city: 'Denver', 2006 state: 'CO', 2007 lat: 39.7392, 2008 lng: -104.9903, 2009 score: 92, 2010 isCandidate: false, 2011 tenure: 4.5 2012 }, 2013 { 2014 name: 'J. Wilson', 2015 city: 'Columbus', 2016 state: 'OH', 2017 lat: 39.9612, 2018 lng: -82.9988, 2019 score: 91, 2020 isCandidate: false, 2021 tenure: 5.1 2022 }, 2023 { 2024 name: 'C. Thomas', 2025 city: 'Kansas City', 2026 state: 'MO', 2027 lat: 39.0997, 2028 lng: -94.5786, 2029 score: 83, 2030 isCandidate: false, 2031 tenure: 3.7 2032 }, 2033 { 2034 name: 'T. Thompson', 2035 city: 'Houston', 2036 state: 'TX', 2037 lat: 29.7604, 2038 lng: -95.3698, 2039 score: 89, 2040 isCandidate: false, 2041 tenure: 2.8 2042 }, 2043 { 2044 name: 'R. Nelson', 2045 city: 'Minneapolis', 2046 state: 'MN', 2047 lat: 44.9778, 2048 lng: -93.2650, 2049 score: 88, 2050 isCandidate: false, 2051 tenure: 1.9 2052 }, 2053 { 2054 name: 'B. Harris', 2055 city: 'Louisville', 2056 state: 'KY', 2057 lat: 38.2527, 2058 lng: -85.7585, 2059 score: 86, 2060 isCandidate: false, 2061 tenure: 3.3 2062 }, 2063 { 2064 name: 'D. Clark', 2065 city: 'St. Louis', 2066 state: 'MO', 2067 lat: 38.6270, 2068 lng: -90.1994, 2069 score: 84, 2070 isCandidate: false, 2071 tenure: 2.6 2072 }, 2073 { 2074 name: 'M. White', 2075 city: 'Detroit', 2076 state: 'MI', 2077 lat: 42.3314, 2078 lng: -83.0458, 2079 score: 82, 2080 isCandidate: false, 2081 tenure: 4.1 2082 }, 2083 { 2084 name: 'K. Lewis', 2085 city: 'Pittsburgh', 2086 state: 'PA', 2087 lat: 40.4406, 2088 lng: -79.9959, 2089 score: 79, 2090 isCandidate: false, 2091 tenure: 1.4 2092 }, 2093 { 2094 name: 'P. Walker', 2095 city: 'Oklahoma City', 2096 state: 'OK', 2097 lat: 35.4676, 2098 lng: -97.5164, 2099 score: 77, 2100 isCandidate: false, 2101 tenure: 2.9 2102 }, 2103 { 2104 name: 'A. Hill', 2105 city: 'Cincinnati', 2106 state: 'OH', 2107 lat: 39.1031, 2108 lng: -84.5120, 2109 score: 75, 2110 isCandidate: false, 2111 tenure: 1.7 2112 }, 2113 { 2114 name: 'G. Hernandez', 2115 city: 'San Antonio', 2116 state: 'TX', 2117 lat: 29.4241, 2118 lng: -98.4936, 2119 score: 93, 2120 isCandidate: false, 2121 tenure: 4.8 2122 }, 2123 // Candidates 2124 { 2125 name: 'M. Williams', 2126 city: 'Atlanta', 2127 state: 'GA', 2128 lat: 33.7490, 2129 lng: -84.3880, 2130 score: 76, 2131 isCandidate: true, 2132 tenure: 0 2133 }, 2134 { 2135 name: 'R. Davis', 2136 city: 'Nashville', 2137 state: 'TN', 2138 lat: 36.1627, 2139 lng: -86.7816, 2140 score: 68, 2141 isCandidate: true, 2142 tenure: 0 2143 }, 2144 { 2145 name: 'M. Garcia', 2146 city: 'Phoenix', 2147 state: 'AZ', 2148 lat: 33.4484, 2149 lng: -112.0740, 2150 score: 85, 2151 isCandidate: true, 2152 tenure: 0 2153 }, 2154 { 2155 name: 'A. Brown', 2156 city: 'Memphis', 2157 state: 'TN', 2158 lat: 35.1495, 2159 lng: -90.0490, 2160 score: 72, 2161 isCandidate: true, 2162 tenure: 0 2163 }, 2164 { 2165 name: 'K. Taylor', 2166 city: 'Indianapolis', 2167 state: 'IN', 2168 lat: 39.7684, 2169 lng: -86.1581, 2170 score: 63, 2171 isCandidate: true, 2172 tenure: 0 2173 }, 2174 {
2175 name: 'D. Anderson', 2176 city: 'Charlotte', 2177 state: 'NC', 2178 lat: 35.2271, 2179 lng: -80.8431, 2180 score: 58, 2181 isCandidate: true, 2182 tenure: 0 2183 }, 2184 { 2185 name: 'J. Scott', 2186 city: 'Salt Lake City', 2187 state: 'UT', 2188 lat: 40.7608, 2189 lng: -111.8910, 2190 score: 81, 2191 isCandidate: true, 2192 tenure: 0 2193 }, 2194 { 2195 name: 'N. Young', 2196 city: 'Omaha', 2197 state: 'NE', 2198 lat: 41.2565, 2199 lng: -95.9345, 2200 score: 74, 2201 isCandidate: true, 2202 tenure: 0 2203 }, 2204 { 2205 name: 'C. Moore', 2206 city: 'Jackson', 2207 state: 'MS', 2208 lat: 32.2988, 2209 lng: -90.1848, 2210 score: 61, 2211 isCandidate: true, 2212 tenure: 0 2213 }, 2214 { 2215 name: 'L. Wright', 2216 city: 'Cleveland', 2217 state: 'OH', 2218 lat: 41.4993, 2219 lng: -81.6944, 2220 score: 78, 2221 isCandidate: true, 2222 tenure: 0 2223 }, 2224 { 2225 name: 'H. King', 2226 city: 'Des Moines', 2227 state: 'IA', 2228 lat: 41.5868, 2229 lng: -93.6250, 2230 score: 70, 2231 isCandidate: true, 2232 tenure: 0 2233 }, 2234 { 2235 name: 'F. Lee', 2236 city: 'Little Rock', 2237 state: 'AR', 2238 lat: 34.7465, 2239 lng: -92.2896, 2240 score: 66, 2241 isCandidate: true, 2242 tenure: 0 2243 }, 2244 { 2245 name: 'X. Martinez', 2246 city: 'Albuquerque', 2247 state: 'NM', 2248 lat: 35.0844, 2249 lng: -106.6504, 2250 score: 73, 2251 isCandidate: true, 2252 tenure: 0 2253 }, 2254 ]; 2255 2256 var CONNECTIONS = [ 2257 [5, 13, 1, 11], // 0 Dallas â Houston, San Antonio, Chicago, OKC 2258 [0, 3, 6, 7], // 1 Chicago â Dallas, Columbus, Minneapolis, Louisville 2259 [4, 20, 26, 11], // 2 Denver â Kansas City, Salt Lake, Albuquerque, OKC 2260 [1, 9, 12, 23], // 3 Columbus â Chicago, Detroit, Cincinnati, Cleveland 2261 [8, 2, 11, 21], // 4 Kansas City â St. Louis, Denver, OKC, Omaha 2262 [0, 13, 22, 17], // 5 Houston â Dallas, San Antonio, Jackson, Memphis 2263 [1, 21, 24, 4], // 6 Minneapolis â Chicago, Omaha, Des Moines, Kansas City 2264 [1, 12, 15, 8], // 7 Louisville â Chicago, Cincinnati, Nashville, St. Louis 2265 [4, 7, 25, 17], // 8 St. Louis â Kansas City, Louisville, Little Rock, Memphis 2266 [1, 3, 23, 10], // 9 Detroit â Chicago, Columbus, Cleveland, Pittsburgh 2267 [3, 9, 23, 19], // 10 Pittsburgh â Columbus, Detroit, Cleveland, Charlotte 2268 [0, 4, 2, 25], // 11 OKC â Dallas, Kansas City, Denver, Little Rock 2269 [7, 3, 15, 18], // 12 Cincinnati â Louisville, Columbus, Nashville, Indianapolis 2270 [0, 5, 11, 2], // 13 San Antonio â Dallas, Houston, OKC, Denver 2271 [15, 19, 17, 12], // 14 Atlanta â Nashville, Charlotte, Memphis, Cincinnati 2272 [7, 14, 17, 12], // 15 Nashville â Louisville, Atlanta, Memphis, Cincinnati 2273 [2, 26, 20, 11], // 16 Phoenix â Denver, Albuquerque, Salt Lake, OKC 2274 [8, 14, 15, 22], // 17 Memphis â St. Louis, Atlanta, Nashville, Jackson 2275 [1, 7, 3, 12], // 18 Indianapolisâ Chicago, Louisville, Columbus, Cincinnati 2276 [14, 10, 15, 23], // 19 Charlotte â Atlanta, Pittsburgh, Nashville, Cleveland 2277 [2, 16, 26, 21], // 20 Salt Lake â Denver, Phoenix, Albuquerque, Omaha 2278 [4, 6, 24, 20], // 21 Omaha â Kansas City, Minneapolis, Des Moines, Salt Lake 2279 [17, 5, 14, 25], // 22 Jackson â Memphis, Houston, Atlanta, Little Rock 2280 [3, 9, 10, 18], // 23 Cleveland â Columbus, Detroit, Pittsburgh, Indianapolis 2281 [4, 6, 21, 8], // 24 Des Moines â Kansas City, Minneapolis, Omaha, St. Louis
2282 [8, 17, 22, 11], // 25 Little Rock â St. Louis, Memphis, Jackson, OKC 2283 [16, 2, 11, 20], // 26 Albuquerque â Phoenix, Denver, OKC, Salt Lake 2284 ]; 2285 2286 var MAP_STYLES = [{ 2287 elementType: 'geometry', 2288 stylers: [{ 2289 color: '#212121' 2290 }] 2291 }, 2292 { 2293 elementType: 'labels.icon', 2294 stylers: [{ 2295 visibility: 'off' 2296 }] 2297 }, 2298 { 2299 elementType: 'labels.text.fill', 2300 stylers: [{ 2301 color: '#757575' 2302 }] 2303 }, 2304 { 2305 elementType: 'labels.text.stroke', 2306 stylers: [{ 2307 color: '#212121' 2308 }] 2309 }, 2310 { 2311 featureType: 'administrative', 2312 elementType: 'geometry', 2313 stylers: [{ 2314 visibility: 'off' 2315 }] 2316 }, 2317 { 2318 featureType: 'administrative.country', 2319 elementType: 'labels.text.fill', 2320 stylers: [{ 2321 color: '#9e9e9e' 2322 }] 2323 }, 2324 { 2325 featureType: 'administrative.locality', 2326 elementType: 'labels.text.fill', 2327 stylers: [{ 2328 color: '#bdbdbd' 2329 }] 2330 }, 2331 { 2332 featureType: 'administrative.land_parcel', 2333 stylers: [{ 2334 visibility: 'off' 2335 }] 2336 }, 2337 { 2338 featureType: 'poi', 2339 elementType: 'labels.text.fill', 2340 stylers: [{ 2341 color: '#757575' 2342 }] 2343 }, 2344 { 2345 featureType: 'poi.park', 2346 elementType: 'geometry', 2347 stylers: [{ 2348 color: '#181818' 2349 }] 2350 }, 2351 { 2352 featureType: 'poi.park', 2353 elementType: 'labels.text.fill', 2354 stylers: [{ 2355 color: '#616161' 2356 }] 2357 }, 2358 { 2359 featureType: 'poi.park', 2360 elementType: 'labels.text.stroke', 2361 stylers: [{ 2362 color: '#1b1b1b' 2363 }] 2364 }, 2365 { 2366 featureType: 'road', 2367 elementType: 'geometry.fill', 2368 stylers: [{ 2369 color: '#2c2c2c' 2370 }] 2371 }, 2372 { 2373 featureType: 'road', 2374 elementType: 'labels.text.fill', 2375 stylers: [{ 2376 color: '#8a8a8a' 2377 }] 2378 }, 2379 { 2380 featureType: 'road.arterial', 2381 elementType: 'geometry', 2382 stylers: [{ 2383 color: '#373737' 2384 }] 2385 }, 2386 { 2387 featureType: 'road.highway', 2388 elementType: 'geometry', 2389 stylers: [{ 2390 color: '#3c3c3c' 2391 }] 2392 }, 2393 { 2394 featureType: 'road.highway.controlled_access', 2395 elementType: 'geometry', 2396 stylers: [{ 2397 color: '#4e4e4e' 2398 }] 2399 }, 2400 { 2401 featureType: 'road.local', 2402 elementType: 'labels.text.fill', 2403 stylers: [{ 2404 color: '#616161' 2405 }] 2406 }, 2407 { 2408 featureType: 'transit', 2409 elementType: 'labels.text.fill', 2410 stylers: [{ 2411 color: '#757575' 2412 }] 2413 }, 2414 { 2415 featureType: 'water', 2416 elementType: 'geometry', 2417 stylers: [{ 2418 color: '#000000' 2419 }] 2420 }, 2421 { 2422 featureType: 'water', 2423 elementType: 'labels.text.fill', 2424 stylers: [{ 2425 color: '#3d3d3d' 2426 }] 2427 }, 2428 ]; 2429 2430 // Graph3D color palette 2431 var C_DEFAULT = '#7a8fa5'; // Graph3D cluster grey (base node color) 2432 var C_SELECTED = '#e5e65c'; // Graph3D C_ACTIVE 2433 var C_NEIGHBOR = '#93c5fd'; // Graph3D C_NEIGHBOR 2434 var C_EDGE = '#93c5fd'; 2435 2436 function pinColor(score) { 2437 if (score >= 85) return '#3b82f6'; 2438 if (score >= 65) return '#93c5fd'; 2439 return '#b8c8d2'; 2440 } 2441 2442 // viewBox 0 0 90 90 â tip at y=90, head center at y=27, matches the app's pin SVG exactly 2443 function makePinUrl(color) { 2444 var svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 90">' + 2445 '<path fill="' + color + '" stroke="rgba(255,255,255,0.55)" stroke-width="2.5" d="M45,0C29.897,0,17.611,12.287,17.611,27.389C17.611,46.616,43.853,66.607,45,90c1.147-23.393,27.389-43.384,27.389-62.611C72.389,12.287,60.103,0,45,0z"/>' + 2446 '<circle fill="rgba(0,0,0,0.5)" cx="45" cy="27" r="12"/>' + 2447 '</svg>'; 2448 return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg); 2449 } 2450 2451 function tenureStr(t) { 2452 if (t === 0) return 'New candidate'; 2453 if (t < 1) return Math.round(t * 12) + ' mo. tenure'; 2454 return t.toFixed(1) + ' yrs tenure'; 2455 } 2456 2457 var card = document.getElementById('map-info-card'); 2458 var micName = document.getElementById('mic-name'); 2459 var micType = document.getElementById('mic-type'); 2460 var micLoc = document.getElementById('mic-location'); 2461 var micFill = document.getElementById('mic-fill'); 2462 var micVal = document.getElementById('mic-val'); 2463 var micTen = document.getElementById('mic-tenure'); 2464 var micStat = document.getElementById('mic-status'); 2465 2466 var gmap, markers = [], 2467 lines = [], 2468 selectedIdx = -1; 2469 var activeGroup = -1; 2470 var autoActive = false;
2471 var autoTimer = null; 2472 var idleTimer = null; 2473 var autoIdx = 0; 2474 2475 // Maps chip index â group: -1=All, 0=Top performers, 1=Strong matches 2476 var CHIP_GROUPS = [-1, 0, 1]; 2477 var chipEls = document.querySelectorAll('.map-legend-bar .mleg'); 2478 2479 // Score â group (mirrors graph3D clusters) 2480 function pinGroup(score) { 2481 if (score >= 80) return 0; // Top performers 2482 if (score >= 60) return 1; // Strong matches 2483 return 2; // Developing â in map, no chip 2484 } 2485 2486 function syncChipActive() { 2487 chipEls.forEach(function(el, k) { 2488 el.classList.toggle('active', CHIP_GROUPS[k] === activeGroup); 2489 }); 2490 } 2491 2492 function getFilteredPins() { 2493 if (activeGroup < 0) return PINS.map(function(_, i) { 2494 return i; 2495 }); 2496 return PINS.reduce(function(acc, p, i) { 2497 if (pinGroup(p.score) === activeGroup) acc.push(i); 2498 return acc; 2499 }, []); 2500 } 2501 2502 // Highlight all pins in the active group immediately (called on chip click) 2503 function applyGroupHighlight() { 2504 PINS.forEach(function(p, i) { 2505 var inGroup = activeGroup < 0 || pinGroup(p.score) === activeGroup; 2506 setMarkerIcon(i, activeGroup >= 0 && inGroup ? C_SELECTED : C_DEFAULT, 18); 2507 markers[i].setOpacity(inGroup ? 1 : 0.08); 2508 markers[i].setZIndex(inGroup ? 2 : 1); 2509 }); 2510 } 2511 2512 function stopAutoPlay() { 2513 autoActive = false; 2514 clearTimeout(autoTimer); 2515 autoTimer = null; 2516 } 2517 2518 function startAutoPlay() { 2519 activeGroup = -1; 2520 syncChipActive(); 2521 autoActive = true; 2522 clearTimeout(autoTimer); 2523 autoNext(); 2524 } 2525 2526 function scheduleResume() { 2527 clearTimeout(idleTimer); 2528 idleTimer = setTimeout(startAutoPlay, 60000); 2529 } 2530 2531 chipEls.forEach(function(el, k) { 2532 el.addEventListener('click', function() { 2533 stopAutoPlay(); 2534 scheduleResume(); 2535 var chipGroup = CHIP_GROUPS[k]; 2536 activeGroup = (chipGroup < 0 || activeGroup === chipGroup) ? -1 : chipGroup; 2537 syncChipActive(); 2538 if (card) card.style.opacity = '0'; 2539 selectedIdx = -1; 2540 if (gmap) applyGroupHighlight(); 2541 }); 2542 }); 2543 2544 function setMarkerIcon(markerIdx, color, size) { 2545 markers[markerIdx].setIcon({ 2546 url: makePinUrl(color), 2547 scaledSize: new google.maps.Size(size, size), 2548 anchor: new google.maps.Point(size / 2, size), 2549 }); 2550 } 2551 2552 function selectPin(idx) { 2553 var connSet = {}; 2554 CONNECTIONS[idx].forEach(function(ci) { 2555 connSet[ci] = true; 2556 }); 2557 2558 // Clear old lines 2559 lines.forEach(function(l) { 2560 l.setMap(null); 2561 }); 2562 lines = []; 2563 2564 // Reset all markers: selected â yellow, connected â blue, others â grey dimmed 2565 PINS.forEach(function(p, i) { 2566 var isConn = connSet[i]; 2567 var isSel = (i === idx); 2568 var inGroup = activeGroup < 0 || pinGroup(p.score) === activeGroup; 2569 var baseOp = inGroup ? (activeGroup >= 0 ? 0.55 : 0.3) : 0.08; 2570 markers[i].setOpacity(isSel || isConn ? 1 : baseOp); 2571 markers[i].setZIndex(isSel ? 10 : isConn ? 5 : 1); 2572 if (!isSel) setMarkerIcon(i, isConn ? C_NEIGHBOR : (inGroup && activeGroup >= 0 ? C_SELECTED : C_DEFAULT), 18); 2573 }); 2574 2575 selectedIdx = idx; 2576 var pin = PINS[idx]; 2577 2578 // Selected pin: Graph3D yellow, larger 2579 setMarkerIcon(idx, C_SELECTED, 24); 2580 2581 // Connection lines: Graph3D blue
2582 CONNECTIONS[idx].forEach(function(connIdx) { 2583 var conn = PINS[connIdx]; 2584 lines.push(new google.maps.Polyline({ 2585 path: [{ 2586 lat: pin.lat, 2587 lng: pin.lng 2588 }, { 2589 lat: conn.lat, 2590 lng: conn.lng 2591 }], 2592 geodesic: true, 2593 strokeColor: C_EDGE, 2594 strokeOpacity: 0.8, 2595 strokeWeight: 2, 2596 map: gmap, 2597 })); 2598 }); 2599 2600 // Update info card with fade 2601 if (card) { 2602 card.style.transition = 'opacity 0.15s'; 2603 card.style.opacity = '0'; 2604 setTimeout(function() { 2605 var c = pinColor(pin.score); 2606 micName.textContent = pin.name; 2607 micType.textContent = pin.isCandidate ? 'New Candidate' : 'Active Employee'; 2608 micType.style.color = pin.isCandidate ? '#93c5fd' : ''; 2609 micLoc.textContent = pin.city + ', ' + pin.state; 2610 micFill.style.width = pin.score + '%'; 2611 micFill.style.background = c; 2612 micVal.textContent = pin.score; 2613 micTen.textContent = tenureStr(pin.tenure); 2614 micStat.textContent = pin.isCandidate ? 'Candidate' : 'Active'; 2615 micStat.style.color = pin.isCandidate ? '#93c5fd' : 'rgba(255,255,255,0.55)'; 2616 card.style.opacity = '1'; 2617 }, 150); 2618 } 2619 } 2620 2621 function autoNext() { 2622 if (!autoActive) return; 2623 var pins = getFilteredPins(); 2624 if (pins.length) { 2625 selectPin(pins[autoIdx % pins.length]); 2626 autoIdx++; 2627 } 2628 autoTimer = setTimeout(autoNext, 3000); 2629 } 2630 2631 window._geoTrendInit = function() { 2632 var container = document.getElementById('geo-map'); 2633 if (!container) return; 2634 2635 var initialZoom = container.clientWidth < 600 ? 3 : 5; 2636 gmap = new google.maps.Map(container, { 2637 center: { 2638 lat: 38.5, 2639 lng: -97.0 2640 }, 2641 zoom: initialZoom, 2642 gestureHandling: 'none', 2643 zoomControl: false, 2644 mapTypeControl: false, 2645 streetViewControl: false, 2646 fullscreenControl: false, 2647 clickableIcons: false, 2648 styles: MAP_STYLES, 2649 }); 2650 2651 PINS.forEach(function(pin, i) { 2652 var marker = new google.maps.Marker({ 2653 position: { 2654 lat: pin.lat, 2655 lng: pin.lng 2656 }, 2657 map: gmap, 2658 icon: { 2659 url: makePinUrl(C_DEFAULT), 2660 scaledSize: new google.maps.Size(18, 18), 2661 anchor: new google.maps.Point(9, 18), 2662 }, 2663 clickable: true, 2664 title: pin.name, 2665 }); 2666 google.maps.event.addListener(marker, 'click', (function(idx) { 2667 return function() { 2668 stopAutoPlay(); 2669 scheduleResume(); 2670 selectPin(idx); 2671 }; 2672 })(i)); 2673 markers.push(marker); 2674 }); 2675 2676 setTimeout(function() { 2677 google.maps.event.trigger(gmap, 'resize'); 2678 startAutoPlay(); 2679 }, 150); 2680 }; 2681 2682 var wrap = document.getElementById('map-wrap'); 2683 var started = false;
2684 if (!wrap) return; 2685 2686 function start() { 2687 if (started) return; 2688 started = true; 2689 2690 if (window.google && window.google.maps) { 2691 window._geoTrendInit(); 2692 return; 2693 } 2694 2695 var script = document.createElement('script'); 2696 script.src = 'https://maps.googleapis.com/maps/api/js?key=AIzaSyC6PEbXEh7ON2LGWfLmnqlU4QHxCJvpQJQ&callback=_geoTrendInit'; 2697 script.async = true; 2698 script.defer = true; 2699 document.head.appendChild(script); 2700 } 2701 2702 if ('IntersectionObserver' in window) { 2703 var obs = new IntersectionObserver(function(entries) { 2704 if (entries[0].isIntersecting) { 2705 start(); 2706 obs.disconnect(); 2707 } 2708 }, { 2709 threshold: 0.2 2710 }); 2711 obs.observe(wrap); 2712 } else { 2713 start(); 2714 } 2715 })(); 2716 </script>
2716 2717 2718 <section class="users-strip" id="who-uses-it"> 2719 <span class="closing-users-label">Who Uses It</span> 2720 2721 <!-- Scrolling marquee row --> 2722 <div class="closing-marquee-outer"> 2723 <div class="closing-marquee-track closing-marquee-track--single"> 2724 <span class="closing-chip">Fleet Operators</span> 2725 <span class="closing-chip">Staffing Agencies</span> 2726 <span class="closing-chip">Logistics Companies</span> 2727 <span class="closing-chip">Transportation Firms</span> 2728 <span class="closing-chip">HR Teams</span> 2729 <span class="closing-chip">Warehousing Operations</span> 2730 <span class="closing-chip">Driver Management</span> 2731 <span class="closing-chip">Frontline Hiring</span> 2732 <span class="closing-chip">Third-Party Logistics</span> 2733 <span class="closing-chip">Private Fleets</span> 2734 <span class="closing-chip">Workforce Planning</span> 2735 <span class="closing-chip">Operations Leaders</span> 2736 <span class="closing-chip">Regional Carriers</span> 2737 <span class="closing-chip">Delivery Networks</span> 2738 <span class="closing-chip">Talent Acquisition</span> 2739 <span class="closing-chip">Human Resources</span> 2740 <span class="closing-chip">Fleet Operators</span> 2741 <span class="closing-chip">Staffing Agencies</span> 2742 <span class="closing-chip">Logistics Companies</span> 2743 <span class="closing-chip">Transportation Firms</span> 2744 <span class="closing-chip">HR Teams</span> 2745 <span class="closing-chip">Warehousing Operations</span> 2746 <span class="closing-chip">Driver Management</span> 2747 <span class="closing-chip">Frontline Hiring</span> 2748 <span class="closing-chip">Third-Party Logistics</span> 2749 <span class="closing-chip">Private Fleets</span> 2750 <span class="closing-chip">Workforce Planning</span> 2751 <span class="closing-chip">Operations Leaders</span> 2752 <span class="closing-chip">Regional Carriers</span> 2753 <span class="closing-chip">Delivery Networks</span> 2754 <span class="closing-chip">Talent Acquisition</span> 2755 <span class="closing-chip">Human Resources</span> 2756 <span class="closing-chip">Fleet Operators</span> 2757 <span class="closing-chip">Staffing Agencies</span> 2758 <span class="closing-chip">Logistics Companies</span> 2759 <span class="closing-chip">Transportation Firms</span> 2760 <span class="closing-chip">HR Teams</span> 2761 <span class="closing-chip">Warehousing Operations</span> 2762 <span class="closing-chip">Driver Management</span> 2763 <span class="closing-chip">Frontline Hiring</span> 2764 <span class="closing-chip">Third-Party Logistics</span> 2765 <span class="closing-chip">Private Fleets</span> 2766 <span class="closing-chip">Workforce Planning</span> 2767 <span class="closing-chip">Operations Leaders</span> 2768 <span class="closing-chip">Regional Carriers</span> 2769 <span class="closing-chip">Delivery Networks</span> 2770 <span class="closing-chip">Talent Acquisition</span> 2771 <span class="closing-chip">Human Resources</span> 2772 </div> 2773 </div> 2774 </section> 2775 2776 <section class="section wi-section" id="workforce-intelligence"> 2777 <div class="section-inner"> 2778 <div class="section-label">Continuous Learning</div> 2779 <h2 class="section-title">Every hire makes the system smarter.</h2> 2780 <p class="section-desc"> 2781 AlignWave doesn't use generic benchmarks. It's a system built on your roles, your people, and your outcomes—and the more you use it, the more it reflects what's actually working for your company. 2782 </p> 2783 <div class="wi-tiles"> 2784 <div class="wi-tile" data-aos="fade-up" data-aos-duration="600"> 2785 <div class="wi-tile-header"> 2786 <div class="wi-tile-icon"><i class="fa-solid fa-chart-line" aria-hidden="true"></i></div>
2787 <span class="wi-tile-num">01</span> 2788 </div> 2789 <h3>Analyze your best performers</h3> 2790 <p>Your top employees define the benchmark. We identify the behavioral patterns that explain their success so you can find more people like them—for every role you hire into.</p> 2791 </div> 2792 <div class="wi-tile" data-aos="fade-up" data-aos-duration="600" data-aos-delay="100"> 2793 <div class="wi-tile-header"> 2794 <div class="wi-tile-icon"><i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i></div> 2795 <span class="wi-tile-num">02</span> 2796 </div> 2797 <h3>Detect what drives retention</h3> 2798 <p>As your team grows, patterns emerge across tenure, location, and team structure. The system surfaces the behavioral traits that keep people on the job—before you see the turnover.</p> 2799 </div> 2800 <div class="wi-tile" data-aos="fade-up" data-aos-duration="600" data-aos-delay="200"> 2801 <div class="wi-tile-header"> 2802 <div class="wi-tile-icon"><i class="fa-solid fa-arrow-trend-up" aria-hidden="true"></i></div> 2803 <span class="wi-tile-num">03</span> 2804 </div> 2805 <h3>Get sharper with every hire</h3> 2806 <p>Each new hire’s outcome feeds back in. The longer you use AlignWave, the more it reflects what’s actually working for your company—not an industry average.</p> 2807 </div> 2808 </div> 2809 </div> 2810 </section> 2811 2812 <section class="section closing-section" id="closing"> 2813 2814 <!-- Who benefits + CTA --> 2815 <div class="section-inner"> 2816 <div class="closing-split"> 2817 <div class="closing-benefits-cards"> 2818 <div class="closing-benefit-card"> 2819 <div class="closing-benefit-header"> 2820 <div class="closing-benefit-icon"><i class="fa-solid fa-user-tie" aria-hidden="true"></i></div> 2821 <h4>HR Teams</h4> 2822 </div> 2823 <p>Hiring is the obvious part. HR also sees which campaigns are working, and knows exactly which drivers to talk to when it’s time to review practices or dig into an issue.</p> 2824 </div> 2825 <div class="closing-benefit-card"> 2826 <div class="closing-benefit-header"> 2827 <div class="closing-benefit-icon"><i class="fa-solid fa-bullhorn" aria-hidden="true"></i></div> 2828 <h4>Marketing</h4> 2829 </div> 2830 <p>See which profiles perform best in which roles, and point recruiting campaigns at the people most likely to succeed in them.</p> 2831 </div> 2832 <div class="closing-benefit-card"> 2833 <div class="closing-benefit-header"> 2834 <div class="closing-benefit-icon"><i class="fa-solid fa-graduation-cap" aria-hidden="true"></i></div> 2835 <h4>Training</h4> 2836 </div> 2837 <p>Know who you’re training before they start, and shape the process around a role or a specific group instead of a generic course.</p> 2838 </div> 2839 <div class="closing-benefit-card"> 2840 <div class="closing-benefit-header"> 2841 <div class="closing-benefit-icon"><i class="fa-solid fa-truck" aria-hidden="true"></i></div> 2842 <h4>Drivers</h4> 2843 </div> 2844 <p>Onboarding and support are built around who they are from day one—not a one-size-fits-all process.</p> 2845 </div> 2846 </div> 2847 <div class="closing-cta"> 2848 <div class="section-label">Who it’s built for</div> 2849 <h2 class="closing-headline">Who Benefits.</h2> 2850 <p class="closing-desc">Stop relying on instinct and tenure alone. AlignWave gives your hiring team the behavioral data to hire with confidence—and build a workforce that actually lasts.</p> 2851 <p class="closing-desc">Every team that touches a driver works from the same picture. Recruiters know where to look, trainers know who they’re teaching, and managers know who to support before small issues turn into turnover.</p> 2852 <div class="closing-actions"> 2853 <a href="/app/register" class="btn-ghost-dark">Let’s put your company on track to scale →</a> 2854 </div> 2855 </div> 2856 </div> 2857 </div> 2858 2859 </section> 2860 2861 </main> 2862 2863 <footer class="site-footer"> 2864 <div class="footer-inner">
2865 <span>© <span id="current-year">2025</span> AlignWave</span> 2866 <div> 2867 <a href="#">Privacy</a> 2868 <a href="#">Terms</a> 2869 <a href="/app/request">Contact</a> 2870 </div> 2871 </div> 2872 </footer> 2873
2873<script> 2874 (function() { 2875 var y = document.getElementById("current-year"); 2876 if (y) y.textContent = new Date().getFullYear(); 2877 })(); 2878 </script>
2878 2879 2880 <!-- JS --> 2881
2881<script src="/assets/js/plugins.js"></script>
2881 2882
2882<script src="/assets/js/main.js"></script>
2882 2883 2884</body> 2885 2886</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.