1<!DOCTYPE html> 2<html lang="en" class="scroll-smooth"> 3 4 5 6<head> 7 <meta charset="UTF-8"> 8 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 9 <title>How Cadientâs SmartTexting⢠helped a large regional grocery store reduce hiring time by 41%</title> 10 <meta name="description" content="Find out how SmartTexting helped a grocery chain reduce hiring time by 41% and improve candidate engagement. Explore now!"> 11 <link rel="canonical" href="https://cadient.ai/resources/case-studies/how-cadients-smarttexting-helped-a-large-regional-grocery-store-reduce-hiring-time-by-41/"> 12 <meta property="og:type" content="article"> 13 <meta property="og:title" content="How Cadientâs SmartTexting⢠helped a large regional grocery store reduce hiring time by 41%"> 14 <meta property="og:description" content="Find out how SmartTexting helped a grocery chain reduce hiring time by 41% and improve candidate engagement. Explore now!"> 15 <meta property="og:url" content="https://cadient.ai/resources/case-studies/how-cadients-smarttexting-helped-a-large-regional-grocery-store-reduce-hiring-time-by-41/"> 16 <meta name="twitter:card" content="summary_large_image"> 17 <link rel="icon" type="image/png" href="/static/images/favicon_cadientai.png"> 18 <link rel="stylesheet" href="/static/css/tailwind.css?v=6b6fbc49b0"> 19 <link rel="preconnect" href="https://fonts.googleapis.com"> 20 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 21 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" media="print" onload="this.media='all'"> 22 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet" media="print" onload="this.media='all'"> 23 <style> 24 body { font-family: 'Inter', sans-serif; } 25 .ss-orange { color: #fe4a23; } 26 .ss-bg-orange { background-color: #fe4a23; } 27 .ss-bg-orange:hover { background-color: #d93d1a; } 28 .ss-cream { background-color: #fcfcfa; } 29 .ss-box { background-color: #f2f2f2; } 30 .ss-dark { background-color: #141414; } 31 .ss-dark:hover { background-color: #2a2a2a; } 32 </style> 33 34 35 36 37 38
39<script> 40 window.dataLayer = window.dataLayer || []; 41 function gtag() { dataLayer.push(arguments); } 42 (function () { 43 // Single source: app.py parses GTM_CONTAINER_IDS once into the GTM_IDS 44 // Jinja global, which _gtm_noscript.html reads too â see the note there. 45 // Fails closed (no containers) if it is somehow undefined. 46 var GTM_IDS = ["GTM-WWP5NR36", "GTM-PDRS4M23"]; 47 // Configured, not hardcoded, and each vendor is skipped below when its 48 // ID is blank. ANALYTICS_ENABLED stays the master switch for the whole 49 // stack; these decide WHICH vendors that switch turns on. 50 // 51 // That distinction is what makes a non-prod environment testable. These 52 // two used to be literals, so the only way to exercise any vendor on dev 53 // was ANALYTICS_ENABLED=true, which also pointed dev traffic at the 54 // PRODUCTION GA4 property and Clarity project â quietly polluting the 55 // baseline the SEO programme is measured against. Now dev can enable the 56 // stack with these blank and run a single vendor in isolation. 57 // 58 // The defaults are the live prod IDs, so an environment that sets 59 // neither behaves exactly as before. 60 var GA_ID = "G-1T86EMJJXX"; 61 var CLARITY_ID = "vfab1wc1af"; 62 var started = false; 63 64 // ââ Embedded (iframe) awareness ââââââââââââââââââââââââââââââââââââââââ 65 // Set by pages that are normally rendered inside an iframe on one of our 66 // own pages â the scheduler at /lp/schedule. Such a page is a WIDGET, not 67 // a destination: the visitor never navigated to /lp/schedule, so a 68 // page_view against that URL is a phantom hit that inflates its views and 69 // distorts engagement for the property (LEAD_AUDIT.md F19). 70 // 71 // The old fix was to load NOTHING when embedded. That killed the phantom 72 // page_view but also blinded us on the same page's direct loads â and 73 // /lp/schedule has 19 real hrefs pointing at it, plus cross-origin 74 // WordPress embeds where the parent may carry no tags of ours at all. So 75 // it traded a known over-count for an unknown blind spot on the surface 76 // that books demos. 77 // 78 // What is actually unwanted is the automatic page_view, not the stack. So 79 // load everything either way and decide the page_view at RUNTIME, which 80 // is also the only place the answer is knowable: whether we are framed is 81 // not a fact the server has. 82 // 83 // IMPORTANT â this only covers the page_view that OUR gtag config sends. 84 // If a GTM container holds a GA4 tag firing on All Pages, that tag sends 85 // its own and no template change can stop it. `embedded` is pushed to the 86 // dataLayer BEFORE gtm.js loads so the container can carry a trigger 87 // exception on it; until someone adds that exception in the GTM UI, the 88 // container's own page_view still fires here. See LEAD_AUDIT.md F19 and 89 // the unresolved two-container question above. 90 var embedded = false && (function () { 91 try { return window.self !== window.top; } catch (e) { return true; } 92 })(); 93 94 function loadAnalytics() { 95 if (started) return; started = true; 96 // Google Tag Manager 97 dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' }); 98 if (embedded) { dataLayer.push({ embedded: true }); } 99 GTM_IDS.forEach(function (id) { 100 var s = document.createElement('script'); 101 s.async = true; 102 s.src = 'https://www.googletagmanager.com/gtm.js?id=' + id; 103 document.head.appendChild(s); 104 }); 105 // Google Analytics (gtag.js). Skipped entirely without an ID â 106 // loading gtag.js with an empty id still opens a connection to 107 // Google and registers a property-less client, so the guard has to 108 // be here rather than relying on GA to ignore the config call. 109 if (GA_ID) { 110 var g = document.createElement('script'); 111 g.async = true; 112 g.src = 'https://www.googletagmanager.com/gtag/js?i
112d=' + GA_ID; 113 document.head.appendChild(g); 114 gtag('js', new Date()); 115 // send_page_view:false keeps gtag() live for event hits (a booking is 116 // still measurable) while omitting the phantom navigation hit. 117 gtag('config', GA_ID, { send_page_view: !embedded }); 118 } 119 // Microsoft Clarity 120 if (CLARITY_ID) { 121 (function (c, l, a, r, i, t, y) { 122 c[a] = c[a] || function () { (c[a].q = c[a].q || []).push(arguments) }; 123 t = l.createElement(r); t.async = 1; t.src = 'https://www.clarity.ms/tag/' + i; 124 y = l.getElementsByTagName(r)[0]; y.parentNode.insertBefore(t, y); 125 })(window, document, 'clarity', 'script', CLARITY_ID); 126 } 127 } 128 var evts = ['scroll', 'mousemove', 'touchstart', 'keydown', 'click']; 129 function onFirst() { 130 evts.forEach(function (e) { window.removeEventListener(e, onFirst); }); 131 loadAnalytics(); 132 } 133 evts.forEach(function (e) { window.addEventListener(e, onFirst, { passive: true }); }); 134 window.addEventListener('load', function () { 135 if ('requestIdleCallback' in window) { requestIdleCallback(loadAnalytics, { timeout: 4000 }); } 136 else { setTimeout(loadAnalytics, 3000); } 137 }); 138 })(); 139</script>
139 140 141 142
143<script> 144 (function () { 145 // Queue stub first and synchronously: it is the whole point of the 146 // vendor's snippet shape. Anything measured before the SDK lands is 147 // held here and replayed, so deferring the network load cannot lose an 148 // event. 149 window.oaiq = window.oaiq || function () { 150 (window.oaiq.q = window.oaiq.q || []).push(arguments); 151 }; 152 153 var PIXEL_ID = "QnvACeEzLpeAcz9ok3djRz"; 154 155 // Same question the page_view asks: are we the scheduler running 156 // inside one of our own pages? If so the parent owns measurement. 157 var embedded = (function () { 158 try { return window.self !== window.top; } catch (e) { return true; } 159 })(); 160 if (embedded) return; 161 162 oaiq('init', { pixelId: PIXEL_ID }); 163 164 var loaded = false; 165 function loadPixel() { 166 if (loaded) return; loaded = true; 167 var s = document.createElement('script'); 168 s.async = true; 169 s.src = 'https://bzrcdn.openai.com/sdk/oaiq.min.js'; 170 document.head.appendChild(s); 171 } 172 var evts = ['scroll', 'mousemove', 'touchstart', 'keydown', 'click']; 173 function onFirst() { 174 evts.forEach(function (e) { window.removeEventListener(e, onFirst); }); 175 loadPixel(); 176 } 177 evts.forEach(function (e) { window.addEventListener(e, onFirst, { passive: true }); }); 178 window.addEventListener('load', function () { 179 if ('requestIdleCallback' in window) { requestIdleCallback(loadPixel, { timeout: 4000 }); } 180 else { setTimeout(loadPixel, 3000); } 181 }); 182 183 // Our event names -> OpenAI's taxonomy. Both are `customer_action`; 184 // neither carries an amount, because a booked demo has no transaction 185 // value we can state honestly. Adding an invented one would flow 186 // straight into their bidding. 187 var EVENTS = { 188 demo_booking_success: 'appointment_scheduled', 189 playbook_request_success: 'lead_created' 190 }; 191 192 // Fired at most once per event name per page: the two channels below 193 // can both be live on one page, and a booking must not be reported 194 // twice because it arrived by two routes. 195 var sent = {}; 196 197 function measureConversion(name, detail) { 198 try { 199 var mapped = EVENTS[name]; 200 if (!mapped || sent[mapped]) return; 201 sent[mapped] = true; 202 var options = {}; 203 // event_id is what lets a future Conversions API call dedupe 204 // against this one. The playbook flows have a real lead id to 205 // key on; the iframe route has none on the parent, so it goes 206 // without and cannot be deduped server-side until the slot is 207 // threaded back. Sharing one id across the two event NAMES is 208 // fine â the dedup key is (pixel, event name, event_id). 209 if (detail && detail.eventId) options.event_id = String(detail.eventId); 210 oaiq('measure', mapped, { type: 'customer_action' }, options); 211 } catch (e) { /* measurement must never break the page */ } 212 } 213 window.cadientMeasureConversion = measureConversion; 214 215 // Route 1: the scheduler iframe announcing a confirmed booking. Same 216 // message the ad consultants' own listener consumes. Origin-checked â 217 // the frame posts to '*' because it is embedded cross-origin by the 218 // WordPress estate, so the check belongs here, on the receiving end. 219 window.addEventListener('message', function (e) { 220 if (e.origin !== window.location.origin && 221 e.origin !== 'https://cadient.ai' && 222 e.origin !== 'https://dev.cadient.ai') return; 223 if (!e.data || e.data.event !== 'demo_booking_success') return; 224 measureConversion('demo_booking_success', e.data); 225 }); 226 })(); 227</script>
227 228 229 230<!-- Warmly â visitor de-anonymisation, i.e. analytics, so it rides the same 231 gate as everything above it including suppress_analytics. --> 232 233
234<script id="warmly-script-loader" src="https://opps-widget.getwarmly.com/warmly.js?clientId=5a34a1084b3b8a03e3901742e03c280b" defer></script>
234 235 236 237 238 239<!-- Chat widget â loaded only after the first user interaction, so it stays off 240 the initial load (no PageSpeed/Speed-Index hit during the lab test, which 241 doesn't interact). Once loaded, the widget's own open-delay behaviour is 242 unchanged. -->
243<script> 244 (function () { 245 // ââ Suppress the widget's auto-open on mobile ââââââââââââââââââââââââââ 246 // On a phone the panel covers the whole screen, so auto-opening hides the 247 // page the visitor came to read. The bubble stays fully functional; only 248 // the unprompted open is blocked. Desktop behaviour is untouched. 249 // 250 // This used to pre-seed sessionStorage['cadient-chat-widget-dismissed'], 251 // which was a GUESS at widget.js's internals and verifiably did nothing â 252 // tested against the live script with that key set, and the panel still 253 // auto-opened. widget.js is third-party, exposes no config for this, and 254 // contains no matchMedia/innerWidth at all, so it has no concept of 255 // mobile. 256 // 257 // So we gate on the one thing that is observable and stable: the widget 258 // opens by adding class "open" to #cadient-chat-widget-frame. When that 259 // happens unprompted we close it again by clicking the widget's OWN 260 // bubble, not by stripping the class. 261 // 262 // That distinction matters and cost a bug: removing the class directly 263 // hides the panel but leaves the widget's internal isOpen === true, so the 264 // visitor's first real tap on the bubble only toggles that stale flag back 265 // to false and appears to do nothing â they'd have to tap twice. Driving 266 // the widget's own toggle keeps its state and the DOM in step, so the 267 // first tap opens the panel properly (verified both ways against the live 268 // script). 269 // 270 // The pointerdown listener is on capture, so `userAsked` is always set 271 // before the widget's click handler runs â no race. Our own .click() 272 // dispatches no pointerdown, so it can't trip that flag. After the first 273 // genuine tap we stop interfering entirely. autoCloses is capped at 3 as a 274 // loop guard in case a future widget build reopens itself. 275 try { 276 if (window.matchMedia('(max-width: 767.98px)').matches) { 277 var userAsked = false, autoCloses = 0; 278 document.addEventListener('pointerdown', function (e) { 279 if (e.target && e.target.closest && 280 e.target.closest('#cadient-chat-widget-bubble')) userAsked = true; 281 }, true); 282 new MutationObserver(function () { 283 if (userAsked || autoCloses >= 3) return; 284 var f = document.getElementById('cadient-chat-widget-frame'); 285 var b = document.getElementById('cadient-chat-widget-bubble'); 286 if (f && b && f.classList.contains('open')) { autoCloses++; b.click(); } 287 }).observe(document.documentElement, 288 { subtree: true, childList: true, attributeFilter: ['class'] }); 289 } 290 } catch (e) { /* very old browser â fall through, widget just behaves as before */ } 291 292 // ââ Give the widget's bubble a landmark ââââââââââââââââââââââââââââââââ 293 // widget.js appends #cadient-chat-widget-bubble straight to <body>, so 294 // its text/icon content sits outside every landmark (main/nav/etc.) â 295 // an axe "region" violation. We don't control widget.js, so once the 296 // bubble shows up we label it as its own landmark region instead of 297 // waiting on a third-party fix. 298 function labelChatBubbleLandmark() { 299 var b = document.getElementById('cadient-chat-widget-bubble'); 300 if (b && !b.hasAttribute('role')) { 301 b.setAttribute('role', 'region'); 302 b.setAttribute('aria-label', 'Chat with us'); 303 } 304 return !!b; 305 } 306 var chatBubbleObserver = new MutationObserver(function () { 307 if (labelChatBubbleLandmark()) chatBubbleObserver.disconnect(); 308 }); 309 // documentElement, NOT document.body. This include is pulled into <head> 310 // (see smart_automation/base.html and every other layout that uses it), 311 // and <body> does not exist yet at that point â so this line threw 312 // "parameter 1 is not of type 'Node'" on every page load of every page in 313 // the product, and the label this observer exists to apply was never 314 // applied. The uncaught exception was the visible symptom; the silent 315 // accessibility regression was the actual cost. 316 // 317 // The observer twenty lines above already got this right. subtree:true
318 // means watching the root covers the widget's append to <body> exactly as 319 // watching <body> would, and it works before <body> is parsed. 320 chatBubbleObserver.observe(document.documentElement, { childList: true, subtree: true }); 321 // The bubble can also already be in the DOM if this include is ever moved 322 // below the widget script: a MutationObserver only reports changes AFTER 323 // it starts, so an element that is already there never fires it. Cheap to 324 // check once, and it removes the ordering dependency entirely. 325 if (labelChatBubbleLandmark()) chatBubbleObserver.disconnect(); 326 327 var loaded = false; 328 function loadWidget() { 329 if (loaded) return; loaded = true; 330 var s = document.createElement('script'); 331 s.src = "https://consultant-api-dev.cadient.ai/static/widget.js?key=cadient_test_key_abc123"; 332 s.defer = true; 333 // loadWidget only ever runs from a user-interaction handler, so <body> 334 // is parsed by now even though this include sits in <head>. 335 document.body.appendChild(s); 336 } 337 // Deliberate-engagement signals only â no 'mousemove' (fires near-instantly 338 // on desktop) and no 'click' (redundant with 'pointerdown'). 339 var evts = ['scroll', 'pointerdown', 'touchstart', 'keydown']; 340 function onFirst() { 341 evts.forEach(function (e) { window.removeEventListener(e, onFirst); }); 342 loadWidget(); 343 } 344 evts.forEach(function (e) { window.addEventListener(e, onFirst, { passive: true, once: true }); }); 345 })(); 346</script>
346 347 348</head> 349 350<body class="bg-white text-slate-900 flex flex-col min-h-screen">
vendor: 75 bytes, lines 350-354
350 351 352 353 354<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
354GTM-WWP5NR36
vendor: 151 bytes, lines 354-357
354" 355height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> 356 357<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
357GTM-PDRS4M23
vendor: 86 bytes, lines 357-361
357" 358height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> 359 360 361
362<style> 363/* Mobile menu: keep the scroll inside the panel. 364 The panel is max-h + overflow-y-auto, so on a phone its link list scrolls. 365 Without this, reaching the end of that list hands the remaining gesture to 366 the document â you keep swiping, the page moves behind the open menu, and 367 closing it reveals you are somewhere else entirely. overscroll-behavior 368 contains the chain at the panel's boundary. 369 Applied here rather than in the 28 per-page toggle scripts, none of which 370 need to change. */ 371#mobile-menu { overscroll-behavior: contain; } 372 373/* Mobile menu geometry. 374 ------------------------------------------------------------------------ 375 Earlier versions capped the panel with calc(100vh - Npx), then 376 calc(100svh - Npx). Both derive the bottom edge from numbers that are wrong on 377 a phone: N is a guess at the header height (not one value â 80px on the blog 378 header, 90px on /smart-refer, 80px on /smart-shield) and 100vh is the LARGE 379 viewport, 60-120px taller than what is visible with the browser toolbar shown. 380 Any error there puts the panel's bottom edge outside the visible area, and the 381 tail then cannot be scrolled to: scrolling moves content to the panel's own 382 bottom edge and that edge is itself off-screen. The panel reports 383 scrollTop === scrollHeight - clientHeight while its last child is invisible. 384 385 The script sets an exact height from the panel's measured position and 386 visualViewport.height, so nothing here has to be right. 387 388 Note bottom:0 is deliberately NOT used. It reads as the obvious fix, but 389 _product_nav.html's <nav> carries backdrop-blur, and backdrop-filter makes an 390 element a containing block for fixed descendants â bottom:0 there resolves 391 against the nav rather than the viewport and collapses the panel to ~33px. 392 Measuring getBoundingClientRect() sidesteps that: it is in viewport 393 coordinates whatever the containing block is. 394 395 The max-height below is the no-JS floor only. */ 396#mobile-menu { 397 position: fixed; 398 top: 80px; 399 left: 0; 400 right: 0; 401 max-height: calc(100vh - 100px); 402 max-height: calc(100svh - 100px); 403 overflow-y: auto; 404 z-index: 50; 405 /* 5rem, not 1rem. This is scroll slack, and it is load-bearing. 406 The JS sizes the panel to end at the reported viewport bottom 407 (visualViewport.height, or innerHeight). Nothing guarantees that number 408 equals the area actually on screen: DevTools reports a 932px device while 409 displaying ~884px of it, and mobile browsers can over-report the same way 410 with a toolbar overlaying content. When they disagree, the bottom band of 411 the panel is unreachable â the panel is already at scrollTop === maxScroll, 412 so scrolling cannot recover it, and the last child sat only 17px above the 413 box bottom (= the old 1rem padding). 414 415 Bottom padding on an overflow container IS included in scrollHeight, so at 416 the scroll end the last child now stops ~65px above the box bottom instead 417 of 17px, without the panel needing to know the true visible height. 418 419 65px is chosen against a measured 48px discrepancy (a 932px-reported device 420 displaying ~884px), leaving ~17px of margin. Don't trim this much further: 421 below ~50px it stops covering that case and the last CTA becomes unreachable 422 again, which is the bug this exists to prevent. 423 424 env() adds the iPhone home-indicator inset on top, which no viewport unit 425 accounts for. */ 426 padding-bottom: calc(4rem + env(safe-area-inset-bottom, 0px)); 427} 428 429 /* Solutions mega-menu â matches page design language (brand orange accent) */ 430 /* Visibility lives here (not in any single page) so the dropdown behaves 431 correctly on EVERY page that includes this header, not just the homepage. */ 432 .solutions-menu { display: none; } 433 /* Invisible hover bridge across the few px between the trigger's bottom edge 434 and the panel's top, so moving the pointer down does not close the menu. */ 435 .solutions-menu::before { content: ''; position: absolute; left: 0; right: 0; top: -12px; height: 12px; }
436 .solutions-trigger:hover .solutions-menu, 437 .solutions-menu:hover { display: block; } 438 .sol-link { color:#374151; transition: background-color .15s ease, color .15s ease; } 439 .sol-link:hover { background-color:#fff7f5; color:#fe4a23; } 440 .sol-new { font-size:9px; font-weight:700; letter-spacing:.04em; color:#fff; background:#fe4a23; padding:2px 6px; border-radius:4px; line-height:1; } 441 /* Solutions mega-menu promo card â one product called out beside the three 442 Recruit/Hire/Retain columns, e.g. Cadient Talent's MCP while it's new. 443 Peach/orange to match the brand accent, not the neutral list styling 444 above, so it reads as a callout rather than a fourth column of links. */ 445 .mcp-promo-card { 446 display: flex; flex-direction: column; height: 100%; 447 background: #FFF3EE; border: 1px solid #FFD9C2; border-radius: 14px; 448 padding: 16px; text-decoration: none; transition: border-color .15s ease, background-color .15s ease; 449 } 450 .mcp-promo-card:hover { background: #FFEBE1; border-color: #fe4a23; } 451 .mcp-promo-top { display: flex; align-items: center; justify-content: space-between; } 452 .mcp-promo-title { margin: 10px 0 4px; font-weight: 700; font-size: 14.5px; color: #111827; } 453 .mcp-promo-desc { margin: 0; font-size: 12.5px; color: #6b7280; line-height: 1.45; flex: 1; } 454 .mcp-promo-link { margin-top: 10px; font-size: 12.5px; font-weight: 600; color: #fe4a23; } 455 /* Mobile variant of the same promo. The sheet is one narrow column, so this 456 lays out horizontally (mark beside the copy) instead of the desktop card's 457 vertical stack, and sits at the top of the Solutions group. */ 458 .mcp-promo-m { 459 display: flex; align-items: flex-start; gap: 12px; 460 margin: 8px 16px 4px; padding: 14px; 461 background: #FFF3EE; border: 1px solid #FFD9C2; border-radius: 12px; 462 text-decoration: none; 463 } 464 .mcp-promo-m > img { flex: none; width: 34px; height: 34px; } 465 .mcp-promo-m-head { display: flex; align-items: center; gap: 8px; } 466 .mcp-promo-m-title { margin: 0; font-weight: 700; font-size: 14.5px; color: #111827; } 467 .mcp-promo-m-desc { margin: 4px 0 0; font-size: 12.5px; color: #6b7280; line-height: 1.45; } 468 .mcp-promo-m-link { display: inline-block; margin-top: 6px; font-size: 12.5px; font-weight: 600; color: #fe4a23; } 469 .solutions-trigger .fa-chevron-down { transition: transform .2s ease; } 470 .solutions-trigger:hover .fa-chevron-down { transform: rotate(180deg); } 471 472 /* Media dropdown â "Cadient in the Press" card, same white/orange 473 hover pattern as the Solutions mega-menu above. */ 474 .media-menu { display: none; } 475 .media-trigger:hover .media-menu, 476 .media-menu:hover { display: block; } 477 .media-trigger .fa-chevron-down { transition: transform .2s ease; } 478 .media-trigger:hover .fa-chevron-down { transform: rotate(180deg); } 479 .media-item:not(:last-child) { border-bottom: 1px solid #f3f4f6; } 480 .media-item:hover { background-color:#fff7f5; } 481</style><div id="site-sticky-top" style="position:sticky; top:0; z-index:50;"> 482<style> 483 /* position:relative, not sticky: blog/_header.html wraps this banner and 484 the <header> that follows it in ONE shared position:sticky container, 485 so the pair sticks together as a unit. (An earlier attempt made this 486 element sticky on its own, alongside the header's own independent 487 sticky â two sticky siblings are supposed to stack, but body's Tailwind 488 flex layout on several pages made the header render pinned under the 489 banner instead of below it.) Keep this position:relative; the sticky 490 behavior lives one level up now. */ 491 #promo-banner { position:relative; background:linear-gradient(90deg,#fb923c,#fe4a23); color:#fff; font-size:14px; } 492 /* The whole banner is the link, with no separate CTA pill. The icon used to 493 be a flex sibling of the message paragraph; when the row didn't fit 494 and wrapped, the icon (a tiny flex item) got shoved onto its own 495 line above the text instead of sitting beside the first word, adding a 496 3rd line of near-empty height on top of the message's own 2 lines. 497 Putting the icon INSIDE the paragraph as inline content means the 498 whole thing wraps as ordinary text, so it costs no more height than 499 the message text itself needs. */ 500 #promo-banner .promo-link { 501 display:block; color:inherit; text-decoration:none; 502 max-width:80rem; margin:0 auto; padding:10px 40px 10px 16px; 503 text-align:center; 504 } 505 #promo-banner .promo-link:hover .promo-msg, 506 #promo-banner .promo-link:focus-visible .promo-msg { text-decoration:underline; } 507 #promo-banner .promo-msg { margin:0; font-weight:500; font-size:13px; line-height:1.35; } 508 #promo-banner .promo-msg i { font-size:11px; } 509 #promo-close { 510 position:absolute; right:12px; top:50%; transform:translateY(-50%); 511 background:transparent; border:0; color:rgba(255,255,255,.8); cursor:pointer; padding:4px; 512 line-height:1; z-index:1; 513 } 514 #promo-close:hover { color:#fff; } 515 @media (min-width: 640px) { 516 #promo-banner .promo-link { text-align:left; padding-left:24px; padding-right:48px; } 517 #promo-banner .promo-msg { font-size:14px; } 518 #promo-close { right:24px; } 519 } 520</style> 521<div id="promo-banner"> 522 <a class="promo-link" href="/mcp"> 523 <p class="promo-msg"><i class="fa-solid fa-wand-magic-sparkles" aria-hidden="true"></i> Get the first look at <strong>Cadient Talent MCP</strong> - your ATS, now connected to AI <i class="fa-solid fa-arrow-right" aria-hidden="true"></i></p> 524 </a> 525 <button type="button" id="promo-close" aria-label="Dismiss announcement"> 526 <i class="fa-solid fa-xmark" aria-hidden="true"></i> 527 </button> 528</div>
529<script> 530(function () { 531 /* Bump this version to re-show the bar to people who dismissed the last one. */ 532 var DISMISS_KEY = 'cadient_promo_banner_dismissed_v2'; 533 var banner = document.getElementById('promo-banner'); 534 if (!banner) return; 535 try { 536 if (localStorage.getItem(DISMISS_KEY)) { banner.remove(); return; } 537 } catch (e) {} 538 var closeBtn = document.getElementById('promo-close'); 539 if (closeBtn) { 540 closeBtn.addEventListener('click', function () { 541 banner.remove(); 542 try { localStorage.setItem(DISMISS_KEY, '1'); } catch (e) {} 543 }); 544 } 545})(); 546</script>
546 547 548<!-- HEADER --> 549<header class="bg-white border-b border-slate-200"><div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative"> 550 <div class="flex justify-between items-center h-20"> 551 <!-- Logo --> 552 <div class="flex-shrink-0 flex items-center"> 553 <a href="/"><img src="/static/img/logo.webp" width="300" height="93" class="w-32" 554 alt="Cadient"></a> 555 </div> 556 557 <!-- Desktop Nav --> 558 <nav class="hidden md:flex flex-1 items-center justify-center space-x-10 lg:space-x-12"> 559 <div class="solutions-trigger"> 560 <a href="/solutions" 561 class="flex items-center gap-1 text-slate-600 hover:text-cadient-blue font-medium transition-colors py-6"> 562 Solutions <i class="fa-solid fa-chevron-down text-xs mt-0.5"></i> 563 </a> 564 <div 565 class="solutions-menu absolute top-full left-1/2 -translate-x-1/2 bg-white rounded-2xl border border-gray-100 z-50 p-7" 566 style="width: 940px; max-width: calc(100vw - 32px); box-shadow: 0 20px 50px -12px rgba(17,24,39,0.18);"> 567 <div style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)) 240px; gap: 32px;"> 568 <!-- Recruit --> 569 <div> 570 <p class="text-xs font-semibold uppercase tracking-widest text-gray-400 mb-3">Recruit</p> 571 <div class="space-y-0.5"> 572 <a href="/smart-automation" class="sol-link flex items-center gap-2 px-2.5 py-2 rounded-lg text-sm">SmartAutomation <span class="sol-new">*New</span></a> 573 <a href="/smart-refer/" class="sol-link flex items-center gap-2 px-2.5 py-2 rounded-lg text-sm">SmartRefer <span class="sol-new">NEW</span></a> 574 <a href="/smart-source" class="sol-link flex items-center gap-2 px-2.5 py-2 rounded-lg text-sm">SmartSource <span class="sol-new">NEW</span></a> 575 <a href="/smart-registry" class="sol-link flex items-center gap-2 px-2.5 py-2 rounded-lg text-sm">SmartRegistry <span class="sol-new">NEW</span></a> 576 <a href="/smartsuite/smartmatch/" class="sol-link block px-2.5 py-2 rounded-lg text-sm">SmartMatch</a> 577 <a href="/smartsuite/smartscreen/" class="sol-link block px-2.5 py-2 rounded-lg text-sm">SmartScreen</a> 578 <a href="/smartsuite/smarttexting/" class="sol-link block px-2.5 py-2 rounded-lg text-sm">SmartTexting</a> 579 </div> 580 </div> 581 <!-- Hire --> 582 <div> 583 <p class="text-xs font-semibold uppercase tracking-widest text-gray-400 mb-3">Hire</p> 584 <div class="space-y-0.5"> 585 <a href="/smart-shield" class="sol-link flex items-center gap-2 px-2.5 py-2 rounded-lg text-sm">SmartShield <span class="sol-new">NEW</span></a> 586 <a href="/smartsuite/smarthire/" class="sol-link block px-2.5 py-2 rounded-lg text-sm">SmartHire</a> 587 <a href="/smartsuite/smartinterview/" class="sol-link block px-2.5 py-2 rounded-lg text-sm">SmartInterview</a> 588 <a href="/smartsuite/smarttexting/" class="sol-link block px-2.5 py-2 rounded-lg text-sm">SmartTexting</a> 589 <a href="/smartsuite/smartoffer/" class="sol-link block px-2.5 py-2 rounded-lg text-sm">SmartOffer</a> 590 </div> 591 </div> 592 <!-- Retain --> 593 <div> 594 <p class="text-xs font-semibold uppercase tracking-widest text-gray-400 mb-3">Retain</p> 595 <div class="space-y-0.5"> 596 <a href="/smart-communicate/" class="sol-link flex items-center gap-2 px-2.5 py-2 rounded-lg text-sm">SmartCommunicate <span class="sol-new">NEW</span></a> 597 <a href="/smartsuite/smarttenure/" class="sol-link block px-2.5 py-2 rounded-lg text-sm">SmartTenure</a> 598 <a href="/smartsuite/smartfeedback/" class="sol-link block px-2.5 py-2 rounded-lg text-sm">SmartFeedback</a> 599 <a href="/smartsuite/smarttexting/" class="sol-link block px-2.5 py-2 rounded-lg text-sm">SmartTexting</a> 600 </div> 601 </div> 602 <!-- MCP promo --> 603 <a href="/mcp" class="mcp-promo-card"> 604 <div class="mcp-promo-top"> 605 <img src="/static/img/logo-mark.webp" alt="" width="28" height="28">
606 <span class="sol-new">NEW</span> 607 </div> 608 <p class="mcp-promo-title">Cadient Talent’s MCP</p> 609 <p class="mcp-promo-desc">Ask your AI assistant about your hiring data.</p> 610 <span class="mcp-promo-link">Learn more →</span> 611 </a> 612 </div> 613 <div class="mt-5 pt-4 border-t border-gray-100"> 614 <a href="/lp/schedule" rel="nofollow" 615 class="trigger-contact-sales flex items-center justify-center gap-2 text-sm font-medium hover:underline" style="color:#fe4a23;">Not 616 sure where to start? <strong>Book Consult â</strong></a> 617 </div> 618 </div> 619 </div> 620 <a href="/why-cadient" 621 class="text-slate-600 hover:text-cadient-blue font-medium transition-colors">Why Cadient</a> 622 <a href="/industries/" 623 class="text-slate-600 hover:text-cadient-blue font-medium transition-colors">Industries</a> 624 <a href="/playbooks/" 625 class="text-slate-600 hover:text-cadient-blue font-medium transition-colors">Playbooks</a> 626 <a href="/article" class="text-slate-600 hover:text-cadient-blue font-medium transition-colors">Blog</a> 627 <div class="media-trigger relative"> 628 <a href="/press/article" 629 class="flex items-center gap-1 text-slate-600 hover:text-cadient-blue font-medium transition-colors py-6"> 630 Media <i class="fa-solid fa-chevron-down text-xs mt-0.5"></i> 631 </a> 632 <div class="media-menu absolute top-full right-0 bg-white rounded-2xl border border-gray-100 z-50 p-5" 633 style="width: 360px; box-shadow: 0 20px 50px -12px rgba(17,24,39,0.18);"> 634 <p class="text-[10px] font-bold uppercase tracking-widest text-gray-400 mb-2">Cadient in the Press</p> 635 636 <a href="/article/usatoday-ai-resume-human-touch" 637 class="media-item block px-2.5 py-3 -mx-2.5 rounded-lg"> 638 <div class="flex items-center gap-2 mb-1"> 639 <span class="text-[10px] font-bold uppercase tracking-wide" style="color:#fe4a23;">USA TODAY</span> 640 <span class="text-[10px] text-gray-400">Sep 18, 2026</span> 641 </div> 642 <p class="text-sm font-semibold text-gray-900 leading-snug"> 643 Bot vs. bot. Why your AI-written résumé needs a human touch 644 </p> 645 </a> 646 647 <a href="/article/authority-magazine-bill-mastin-sticky-tech-products" 648 class="media-item block px-2.5 py-3 -mx-2.5 rounded-lg"> 649 <div class="flex items-center gap-2 mb-1"> 650 <span class="text-[10px] font-bold uppercase tracking-wide" style="color:#fe4a23;">Authority Magazine</span> 651 <span class="text-[10px] text-gray-400">Aug 6, 2026</span> 652 </div> 653 <p class="text-sm font-semibold text-gray-900 leading-snug"> 654 Bill Mastin of Cadient on 5 tricks for creating âstickyâ tech products 655 </p> 656 </a> 657 658 <a href="/article/forbes-entry-level-jobs-college-graduates" 659 class="media-item block px-2.5 py-3 -mx-2.5 rounded-lg"> 660 <div class="flex items-center gap-2 mb-1"> 661 <span class="text-[10px] font-bold uppercase tracking-wide" style="color:#fe4a23;">Forbes</span>
662 <span class="text-[10px] text-gray-400">Aug 2, 2026</span> 663 </div> 664 <p class="text-sm font-semibold text-gray-900 leading-snug"> 665 What the changes in entry-level jobs mean for new college graduates 666 </p> 667 </a> 668 669 <a href="/article/hr-techseries-hiring-infrastructure-analysis" 670 class="media-item block px-2.5 py-3 -mx-2.5 rounded-lg"> 671 <div class="flex items-center gap-2 mb-1"> 672 <span class="text-[10px] font-bold uppercase tracking-wide" style="color:#fe4a23;">HR TechSeries</span> 673 <span class="text-[10px] text-gray-400">Jul 6, 2026</span> 674 </div> 675 <p class="text-sm font-semibold text-gray-900 leading-snug"> 676 Analysis of 1,386 employers reveals hiring infrastructure is holding companies back 677 </p> 678 </a> 679 680 <a href="/article/hrdive-candidates-overuse-ai" 681 class="media-item block px-2.5 py-3 -mx-2.5 rounded-lg"> 682 <div class="flex items-center gap-2 mb-1"> 683 <span class="text-[10px] font-bold uppercase tracking-wide" style="color:#fe4a23;">HR Dive</span> 684 <span class="text-[10px] text-gray-400">Jul 1, 2026</span> 685 </div> 686 <p class="text-sm font-semibold text-gray-900 leading-snug"> 687 What happens when candidates overuse AI â and what recruiters can do 688 </p> 689 </a> 690 691 <a href="/article/issuewire-ai-resume-verification-crisis" 692 class="media-item block px-2.5 py-3 -mx-2.5 rounded-lg"> 693 <div class="flex items-center gap-2 mb-1"> 694 <span class="text-[10px] font-bold uppercase tracking-wide" style="color:#fe4a23;">IssueWire</span> 695 <span class="text-[10px] text-gray-400">Jun 3, 2026</span> 696 </div> 697 <p class="text-sm font-semibold text-gray-900 leading-snug"> 698 Cadient report: rise of AI-generated resumes has precipitated a new hiring verification crisis 699 </p> 700 </a> 701 702 <div class="mt-2 pt-3 border-t border-gray-100"> 703 <a href="/press/article" 704 class="text-xs font-bold" style="color:#fe4a23;">All media coverage →</a> 705 </div> 706 </div> 707 </div> 708 </nav> 709 710 <!-- CTA --> 711 <div class="hidden md:flex items-center gap-4"> 712 713 714 <a href="/auth/login?next=/resources/case-studies/how-cadients-smarttexting-helped-a-large-regional-grocery-store-reduce-hiring-time-by-41/?" rel="nofollow" 715 class="text-slate-600 hover:text-cadient-blue font-medium transition-colors">Log In</a> 716 <a href="/lp/schedule?source=header&page=/resources/case-studies/how-cadients-smarttexting-helped-a-large-regional-grocery-store-reduce-hiring-time-by-41/" rel="nofollow" 717 onclick="if(window.openPlaybookScheduler){event.preventDefault();window.openPlaybookScheduler({inline:true,role:'expert'});}" 718 class="inline-flex items-center text-white px-6 py-2.5 rounded-full font-semibold transition-colors shadow-md" 719 style="background-color:#fe4a23; box-shadow:0 4px 14px -4px rgba(254,74,35,.5);" 720 onmouseover="this.style.backgroundColor='#d93d1a'" 721 onmouseout="this.style.backgroundColor='#fe4a23'">Book session</a> 722 723 </div> 724 725 <!-- Mobile: optional CTA + menu button --> 726 <div class="md:hidden flex items-center gap-3"> 727 <a href="/lp/schedule?source=header&page=/resources/case-studies/how-cadients-smarttexting-helped-a-large-regional-grocery-store-reduce-hiring-time-by-41/" rel="nofollow" 728 onclick="if(window.openPlaybookScheduler){event.preventDefault();window.openPlaybookScheduler({inline:true,role:'expert'});}" 729 class="inline-flex items-center whitespace-nowrap rounded-lg border px-3 py-1.5 text-xs font-semibold transition-colors" 730 style="border-color:#fe4a23; color:#fe4a23;">Book session</a> 731 732 <button id="mobile-menu-btn" aria-label="Open menu" aria-expanded="false" class="text-slate-600 hover:text-slate-900 focus:outline-none"> 733 <i class="fa-solid fa-bars text-2xl" aria-hidden="true"></i> 734 </button> 735 </div> 736 </div> 737 </div> 738 739 <!-- Mobile Menu --> 740 <div id="mobile-menu" 741 class="hidden md:hidden bg-white border-b border-slate-100 p-4 absolute w-full z-50 max-h-[calc(100vh-80px)] overflow-y-auto"> 742 <div class="py-2 border-b border-slate-100"> 743 <a href="/solutions" 744 class="flex items-center justify-between px-4 py-2 text-sm font-semibold text-slate-500 uppercase hover:bg-slate-50 rounded-md">Solutions
745 <span aria-hidden="true" class="font-normal">→</span></a><a href="/mcp" class="mcp-promo-m"> 746 <img src="/static/img/logo-mark.webp" alt="" width="34" height="34"> 747 <span> 748 <span class="mcp-promo-m-head"> 749 <span class="sol-new">NEW</span> 750 <p class="mcp-promo-m-title">Cadient Talent’s MCP</p> 751 </span> 752 <p class="mcp-promo-m-desc">Connect your ATS to AI.</p> 753 <span class="mcp-promo-m-link">Learn more →</span> 754 </span> 755 </a> 756 757 <p class="px-4 pt-2 pb-1 text-[11px] font-bold uppercase tracking-wider text-slate-400">Recruit</p> 758 <div class="pl-4 space-y-1"> 759 <a href="/smart-automation" class="flex items-center justify-between px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartAutomation <span class="text-[10px] font-bold text-white px-2 py-0.5 rounded-full" style="background:#fe4a23;">*New</span></a> 760 <a href="/smart-refer/" class="flex items-center justify-between px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartRefer <span class="text-[10px] font-bold text-white px-2 py-0.5 rounded-full" style="background:#fe4a23;">NEW</span></a> 761 <a href="/smart-source" class="flex items-center justify-between px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartSource <span class="text-[10px] font-bold text-white px-2 py-0.5 rounded-full" style="background:#fe4a23;">NEW</span></a> 762 <a href="/smart-registry" class="flex items-center justify-between px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartRegistry <span class="text-[10px] font-bold text-white px-2 py-0.5 rounded-full" style="background:#fe4a23;">NEW</span></a> 763 <a href="/smartsuite/smartmatch/" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartMatch</a> 764 <a href="/smartsuite/smartscreen/" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartScreen</a> 765 <a href="/smartsuite/smarttexting/" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartTexting</a> 766 </div> 767 768 <p class="px-4 pt-2 pb-1 text-[11px] font-bold uppercase tracking-wider text-slate-400">Hire</p> 769 <div class="pl-4 space-y-1"> 770 <a href="/smart-shield" class="flex items-center justify-between px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartShield <span class="text-[10px] font-bold text-white px-2 py-0.5 rounded-full" style="background:#fe4a23;">NEW</span></a> 771 <a href="/smartsuite/smarthire/" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartHire</a> 772 <a href="/smartsuite/smartinterview/" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartInterview</a> 773 <a href="/smartsuite/smarttexting/" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartTexting</a> 774 <a href="/smartsuite/smartoffer/" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartOffer</a> 775 </div> 776 777 <p class="px-4 pt-2 pb-1 text-[11px] font-bold uppercase tracking-wider text-slate-400">Retain</p> 778 <div class="pl-4 space-y-1"> 779 <a href="/smart-communicate/" class="flex items-center justify-between px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartCommunicate <span class="text-[10px] font-bold text-white px-2 py-0.5 rounded-full" style="background:#fe4a23;">NEW</span></a> 780 <a href="/smartsuite/smarttenure/" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartTenure</a> 781 <a href="/smartsuite/smartfeedback/" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartFeedback</a> 782 <a href="/smartsuite/smarttexting/" class="block px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 hover:text-cadient-blue rounded-md">SmartTexting</a> 783 </div> 784 </div> 785 <a href="/why-cadient" 786 class="block px-4 py-3 text-slate-600 font-medium hover:bg-slate-50 hover:text-cadient-blue">Why Cadient</a> 787 <a href="/industries/" 788 class="block px-4 py-3 text-slate-600 font-medium hover:bg-slate-50 hover:text-cadient-blue">Industries</a> 789 <a href="/playbooks/" 790 class="block px-4 py-3 text-slate-600 font-medium hover:bg-slate-50 hover:text-cadient-blue">Playbooks</a> 791 <a href="/article" 792 class="block px-4 py-3 text-slate-600 font-medium hover:bg-slate-50 hover:text-cadient-blue">Blog</a> 793 <div class="py-2 border-t border-slate-100"><a href="/press/article" 794 class="flex items-center justify-between px-4 py-2 text-sm font-semibold text-slate-500 uppercase hover:bg-slate-50 rounded-md">Media
795 <span aria-hidden="true" class="font-normal">→</span></a> 796 <div class="pl-4 space-y-1"> 797 798 <a href="/article/usatoday-ai-resume-human-touch" class="block px-4 py-2 rounded-md hover:bg-slate-50"> 799 <span class="block text-[10px] font-bold uppercase tracking-wide" style="color:#fe4a23;">USA TODAY · Sep 18, 2026</span> 800 <span class="block text-sm text-slate-700 leading-snug">Bot vs. bot. Why your AI-written résumé needs a human touch</span> 801 </a> 802 803 <a href="/article/authority-magazine-bill-mastin-sticky-tech-products" class="block px-4 py-2 rounded-md hover:bg-slate-50"> 804 <span class="block text-[10px] font-bold uppercase tracking-wide" style="color:#fe4a23;">Authority Magazine · Aug 6, 2026</span> 805 <span class="block text-sm text-slate-700 leading-snug">Bill Mastin of Cadient on 5 tricks for creating âstickyâ tech products</span> 806 </a> 807 808 <a href="/article/forbes-entry-level-jobs-college-graduates" class="block px-4 py-2 rounded-md hover:bg-slate-50"> 809 <span class="block text-[10px] font-bold uppercase tracking-wide" style="color:#fe4a23;">Forbes · Aug 2, 2026</span> 810 <span class="block text-sm text-slate-700 leading-snug">What the changes in entry-level jobs mean for new college graduates</span> 811 </a> 812 813 <a href="/article/hr-techseries-hiring-infrastructure-analysis" class="block px-4 py-2 rounded-md hover:bg-slate-50"> 814 <span class="block text-[10px] font-bold uppercase tracking-wide" style="color:#fe4a23;">HR TechSeries · Jul 6, 2026</span> 815 <span class="block text-sm text-slate-700 leading-snug">Analysis of 1,386 employers reveals hiring infrastructure is holding companies back</span> 816 </a> 817 818 <a href="/article/hrdive-candidates-overuse-ai" class="block px-4 py-2 rounded-md hover:bg-slate-50"> 819 <span class="block text-[10px] font-bold uppercase tracking-wide" style="color:#fe4a23;">HR Dive · Jul 1, 2026</span> 820 <span class="block text-sm text-slate-700 leading-snug">What happens when candidates overuse AI â and what recruiters can do</span> 821 </a> 822 823 <a href="/article/issuewire-ai-resume-verification-crisis" class="block px-4 py-2 rounded-md hover:bg-slate-50"> 824 <span class="block text-[10px] font-bold uppercase tracking-wide" style="color:#fe4a23;">IssueWire · Jun 3, 2026</span> 825 <span class="block text-sm text-slate-700 leading-snug">Cadient report: rise of AI-generated resumes has precipitated a new hiring verification crisis</span> 826 </a> 827 828 <a href="/press/article" class="block px-4 py-2 text-sm font-bold" style="color:#fe4a23;">All media coverage →</a> 829 </div> 830 </div> 831 <div class="mt-4 pt-4 border-t border-slate-100 px-4 flex flex-col gap-3"> 832 833 <a href="/lp/schedule?source=header&page=/resources/case-studies/how-cadients-smarttexting-helped-a-large-regional-grocery-store-reduce-hiring-time-by-41/" rel="nofollow" 834 onclick="if(window.openPlaybookScheduler){event.preventDefault();window.openPlaybookScheduler({inline:true,role:'expert'});}" 835 class="block px-4 py-3 text-center text-white font-semibold rounded-lg shadow-sm" 836 style="background-color:#fe4a23;">Book session</a> 837 838 <a href="/auth/login?next=/resources/case-studies/how-cadients-smarttexting-helped-a-large-regional-grocery-store-reduce-hiring-time-by-41/?" rel="nofollow" 839 class="block px-4 py-3 text-center text-slate-600 font-medium border border-slate-200 rounded-lg hover:bg-slate-50">Log 840 In</a> 841 842 843 </div> 844 </div> 845</header> 846</div> 847
848<script> 849/* Mobile menu: exact height, measured in viewport coordinates. 850 ------------------------------------------------------------------------ 851 Two inputs, both asked for rather than assumed: 852 853 getBoundingClientRect().top where the panel actually starts. Read AFTER 854 setting `top`, because `top` resolves against 855 the containing block and _product_nav.html's 856 <nav> has backdrop-blur, which makes it one â 857 there, top:91px lands at viewport y=101. The 858 rect is always viewport-relative, so measuring 859 after the write self-corrects for that. 860 visualViewport.height the genuinely visible height, excluding browser 861 chrome and any on-screen keyboard. This is the 862 number vh/svh/dvh only approximate, and the 863 reason the panel used to run off the bottom of 864 the screen with the toolbar showing. 865 866 height, not max-height: the panel then ends exactly at the visible bottom, so 867 every child is reachable by scrolling. 868 869 Re-runs on visualViewport resize, so showing/hiding the URL bar while the menu 870 is open re-fits it instead of clipping it. */ 871(function () { 872 var MIN = 160; 873 var writing = false; 874 875 function panel() { return document.getElementById('mobile-menu'); } 876 877 function isOpen() { 878 var m = panel(); 879 /* Not offsetParent: that is null for position:fixed elements, which the 880 panel is â the guard silently bailed every time on blog/_header.html and 881 only appeared to work on _product_nav.html, where the nav's 882 backdrop-filter containing block makes it non-null. Check display. */ 883 return !!m && getComputedStyle(m).display !== 'none'; 884 } 885 886 function place() { 887 var m = panel(); 888 if (!m || !isOpen()) return; 889 var bar = m.parentElement; 890 if (!bar) return; 891 writing = true; 892 893 /* Intent: start just below the header bar. */ 894 var barBottom = Math.round(bar.getBoundingClientRect().bottom); 895 var wantTop = Math.max(0, barBottom) + 'px'; 896 if (m.style.top !== wantTop) m.style.top = wantTop; 897 898 /* Reality: where that actually put it. getBoundingClientRect is relative to 899 the LAYOUT viewport, and a fixed element is positioned against that too â 900 but visualViewport.height measures the VISUAL viewport. On a phone those 901 two diverge (offsetTop > 0) once the browser shifts the visible area 902 within the layout viewport, so the two numbers have to be brought into 903 the same coordinate space before subtracting. Mixing them made the panel 904 come out short by offsetTop. */ 905 var vv = window.visualViewport; 906 var visible = vv ? vv.height : window.innerHeight; 907 var offset = vv ? vv.offsetTop : 0; 908 var topInVisual = m.getBoundingClientRect().top - offset; 909 910 var wantH = Math.max(MIN, Math.round(visible - topInVisual)) + 'px'; 911 if (m.style.height !== wantH) m.style.height = wantH; /* only on change: this 912 runs every frame */ 913 if (m.style.maxHeight !== 'none') m.style.maxHeight = 'none'; 914 writing = false; 915 } 916 917 /* Re-measure every frame for as long as the panel is open. 918 ---------------------------------------------------------------- 919 This replaced an event-driven approach (visualViewport resize + 920 orientationchange + window resize) that was correct whenever it ran but 921 depended on being *told* the viewport changed. On a phone the browser 922 toolbar sliding in shrinks visualViewport.height, and the last resize event 923 can arrive before the transition settles â so a height computed one frame 924 too early stays in place and the tail of the panel ends up below the visible 925 area. Nothing re-runs, and it looks intermittent because it depends on 926 toolbar state and scroll position at the moment the menu was opened. 927 928 A frame loop cannot be out of date by more than one frame, whatever the 929 browser does or doesn't report. It only runs while the menu is open â a 930 short, idle, user-initiated state â and the writes above are guarded
930so a 931 steady state performs no style mutations at all. */ 932 var raf = null; 933 function loop() { 934 if (!isOpen()) { raf = null; return; } 935 place(); 936 raf = requestAnimationFrame(loop); 937 } 938 function startLoop() { 939 place(); /* immediately, so nothing paints mis-sized */ 940 if (raf === null) raf = requestAnimationFrame(loop); 941 } 942 943 var m = panel(); 944 if (m) { 945 /* ~28 per-page scripts open this panel, all via classList.toggle('hidden'). 946 Observing the attribute catches every one without editing any of them. */ 947 new MutationObserver(function () { 948 if (!writing) startLoop(); 949 }).observe(m, { attributes: true, attributeFilter: ['class'] }); 950 } 951 952 var btn = document.getElementById('mobile-menu-btn'); 953 if (btn) btn.addEventListener('click', startLoop); 954 955 /* The frame loop is the real mechanism; these only matter when frames are not 956 being served. rAF is paused in a hidden tab, so a device rotated or resized 957 while backgrounded would otherwise restore mis-sized. */ 958 document.addEventListener('visibilitychange', startLoop); 959 window.addEventListener('orientationchange', startLoop); 960 window.addEventListener('resize', startLoop); 961 startLoop(); 962})(); 963</script>
963<style> 964 /* ââ Utilities this partial owns outright â DO NOT move back to classes ââ 965 Tailwind's scanner does not follow Jinja includes, and this partial is not 966 listed in tailwind.config.js's `content` array, so every utility it is the 967 only user of gets purged out of static/css/tailwind.css. The classes are 968 still on the markup below (they resolve on the Play-CDN pages, which 969 generate at runtime); these rules are what makes them true on the purged 970 bundle â /solutions, home, blog, playbooks, why-cadient, smartsuite, 971 resources, industries, comparisons, mcp, roi-calculator. 972 973 The one that mattered: `z-[110]` was missing, so the overlay computed to 974 z-index:auto. #site-sticky-top (blog/_header.html, z-index:50) painted 975 over it, and so did the page's own `position:relative` cards â this 976 partial is included from the header, i.e. BEFORE <main> in the DOM, so 977 two z-index:auto positioned elements resolve on source order and the 978 cards win. The open modal rendered underneath the page. 979 980 Declared here rather than fixed in the config on purpose: it holds on 981 both delivery paths and cannot regress the next time a partial is added 982 without being scanned. */ 983 #pb-scheduler-modal{z-index:110} /* z-[110] */ 984 #pb-scheduler-modal [data-role="sched-panel"]{max-height:92vh} /* max-h-[92vh] */ 985 #pb-scheduler-modal [data-role="sched-skip"]{ 986 border-bottom-left-radius:1rem;border-bottom-right-radius:1rem} /* rounded-b-2xl */ 987 #pb-scheduler-modal #pb-scheduler-frame{border:0} /* border-0 */ 988 #pb-scheduler-modal #pb-scheduler-close:hover{color:#374151} /* hover:text-gray-700 */ 989 @media (min-width:768px){ 990 #pb-scheduler-modal [data-role="sched-form"], 991 #pb-scheduler-modal [data-role="sched-thanks"], 992 #pb-scheduler-modal [data-role="sched-skip"]{ 993 padding-left:1.5rem;padding-right:1.5rem} /* md:px-6 */ 994 } 995 996 /* ââ Panel transition âââââââââââââââââââââââââââââââââââââââââââââââ 997 The modal had no transition at all â a straight display:flex/none. Both 998 presentations now animate off .is-open, which the JS toggles a reflow after 999 display is set. */ 1000 #pb-scheduler-modal [data-role="sched-panel"]{ 1001 transition:transform .30s cubic-bezier(.32,.72,0,1),opacity .22s ease} 1002 #pb-scheduler-modal{opacity:0;transition:opacity .22s ease} 1003 #pb-scheduler-modal.is-open{opacity:1} 1004 1005 /* Desktop: a slight rise, nothing more. */ 1006 #pb-scheduler-modal [data-role="sched-panel"]{transform:translateY(10px)} 1007 #pb-scheduler-modal.is-open [data-role="sched-panel"]{transform:none} 1008 1009 /* Inline SVG, not Font Awesome: this partial is included by blog pages 1010 (which load FA) AND by the product dashboards (which use Lucide), so a 1011 hardcoded FA class rendered as an empty box in half the app. 1em keeps 1012 them sized by the surrounding font-size, exactly like the icon font. */ 1013 .pbs-ico{width:1em;height:1em;display:inline-block;vertical-align:-0.125em;flex-shrink:0} 1014 .pbs-ico-sm{width:.75em;height:.75em} 1015 .pbs-ico-lg{width:1.125em;height:1.125em} 1016 1017 /* Own the body's scrollbar. The panel body carries Tailwind's 1018 .overflow-y-auto, and smart-refer/dashboard.html styles THAT CLASS 1019 globally for its dark sidebar â a #0f172a track with a periwinkle-edged 1020 thumb â so the modal rendered a near-black scrollbar inside a white 1021 dialog on that page and a default grey one everywhere else. Keyed on the 1022 id + attribute so it outranks the page's class selector wherever this 1023 partial is embedded. */ 1024 #pb-scheduler-modal [data-role="sched-body"]{ 1025 scrollbar-width:thin;scrollbar-color:#d4d4d8 transparent} 1026 #pb-scheduler-modal [data-role="sched-body"]::-webkit-scrollbar{width:8px} 1027 #pb-scheduler-modal [data-role="sched-body"]::-webkit-scrollbar-track{ 1028 background:transparent} 1029 #pb-scheduler-modal [data-role="sched-body"]::-webkit-scrollbar-thumb{ 1030 background:#d4d4d8;border-radius:9999px; 1031 border:2px solid transparent;background-clip:content-box} 1032 #pb-scheduler-modal [data-role="sched-body"]::-webkit-scrollbar-thumb:hover{ 1033 background:#a1a1aa;background-clip:content-box} 1034 1035 /* Grip is mobile-only; on desktop the sheet metaphor doesn't apply. */ 1036 .pbs-grip{display:none} 1037 1038 /* ââ Step 2 (borrowed form) âââââââââââââââââââââââââââââââââââââââââ 1039 Two things kept step 2 reading as a different popup rather than the 1040 same one: 1041 1. HEIGHT. Hiding the 560px iframe collapsed the panel to the form's own 1042 height â a ~290px drop, and a different aspect ratio. showFormPane 1043 pins this pane (and the header) to the height step 1 measured, so the 1044 panel does not change size at all between steps. Content is anchored 1045 to the TOP of that space: centring it left the form floating in the 1046 middle of a tall box, which looked worse than the jump. 1047 2. WIDTH. The panel is 672px; a form stretched across all of it reads as 1048 a clumsier, unrelated dialog. The column keeps the fields at the same 1049 measure they had in the standalone card. */ 1050 /* Centred, not top-anchored: with the session block in place the column is 1051 ~420px of the ~600px pane, so the remaining space splits into two modest 1052 margins and reads as breathing room. (Centring was wrong when the form
1053 was alone â 340px of content left it floating.) */ 1054 /* Biased above centre. True centring measured even, but read bottom-heavy: 1055 the header block above adds visual weight the maths doesn't account for, 1056 so the optical centre sits higher than the geometric one. The extra 1057 bottom padding shrinks the box the content centres within, lifting it. */ 1058 #pb-scheduler-modal [data-role="sched-form"]{ 1059 padding-top:1.5rem;padding-bottom:4.5rem; 1060 display:flex;flex-direction:column;align-items:center;justify-content:center} 1061 .pbs-formcol{width:100%;max-width:28rem} 1062 1063 /* Asset card. Deliberately NOT shaped like the panel header above it: that 1064 is a round accent medallion + bold heading + grey subline on white, and 1065 repeating the pattern here made the step look like the heading had been 1066 pasted in twice. This is a tinted, bordered card with a portrait document 1067 tile, a micro eyebrow and body-weight text â a thing being described, 1068 rather than a second title. */ 1069 .pbs-asset{width:100%;margin-bottom:1.75rem;padding:1rem 1.125rem; 1070 background:#fffaf8;border:1px solid #ffe0d6;border-radius:.875rem} 1071 .pbs-asset-main{display:flex;align-items:flex-start;gap:.875rem} 1072 .pbs-doc{width:2.75rem;height:3.5rem;flex-shrink:0;border-radius:.375rem; 1073 display:flex;align-items:center;justify-content:center; 1074 background:#ffffff;border:1px solid #ffd5c8;color:#fe4a23;font-size:1.125rem; 1075 box-shadow:0 1px 2px rgba(17,17,17,.05)} 1076 .pbs-asset-txt{min-width:0;flex:1} 1077 .pbs-eyebrow{margin:0 0 .25rem;font-size:.6875rem;font-weight:700; 1078 letter-spacing:.08em;text-transform:uppercase;color:#c2410c} 1079 .pbs-asset-title{margin:0;font-size:.9375rem;font-weight:600;color:#111827; 1080 line-height:1.35} 1081 .pbs-asset-blurb{margin:.25rem 0 0;font-size:.8125rem;line-height:1.5;color:#6b7280} 1082 .pbs-asset-slot{display:flex;align-items:center;gap:.5rem; 1083 margin:.875rem 0 0;padding-top:.75rem;border-top:1px solid #ffe0d6; 1084 font-size:.8125rem;font-weight:600;color:#374151} 1085 .pbs-asset-slot svg{color:#fe4a23} 1086 .pbs-change{margin-left:auto;font-size:.75rem;font-weight:400;color:#9ca3af; 1087 text-decoration:underline;text-underline-offset:2px;transition:color .15s ease} 1088 .pbs-change:hover{color:#4b5563} 1089 1090 /* Scale the borrowed form up for the roomier panel. Scoped to this modal, 1091 so the standalone card (used when the panel can't host the form) keeps 1092 its original compact proportions. */ 1093 /* The form's own legend and helper text ("Send it to your work email" / 1094 "We'll send the playbook straight to you") are redundant in here: the 1095 panel header already asks the question and the fields are labelled. They 1096 stay in the standalone card, which has no header to lean on. */ 1097 #pb-scheduler-modal .pbf-legend, 1098 #pb-scheduler-modal .pbf-help{display:none} 1099 #pb-scheduler-modal .pbf-field{margin-bottom:.875rem;padding:0 1.125rem; 1100 border-radius:.875rem} 1101 #pb-scheduler-modal .pbf-field input{padding:1.0625rem 0;font-size:.9375rem} 1102 #pb-scheduler-modal .pbf-ico{font-size:1rem} 1103 #pb-scheduler-modal .pbf-submit{margin-top:.75rem;padding:1.0625rem 1.25rem; 1104 font-size:.9375rem;border-radius:.875rem} 1105 #pb-scheduler-modal .pbf-privacy{margin-top:1.25rem;font-size:.8125rem} 1106 1107 @media (max-width: 767.98px){ 1108 /* ââ Bottom sheet ââââââââââââââââââââââââââââââââââââââââââââââ 1109 Anchored to the bottom edge, full width, square at the bottom. */ 1110 #pb-scheduler-modal{align-items:flex-end;padding:0} 1111 #pb-scheduler-modal [data-role="sched-panel"]{ 1112 max-width:none;width:100%; 1113 border-radius:1.25rem 1.25rem 0 0; 1114 transform:translateY(100%); 1115 /* EXPLICIT height, not max-height. The body is flex:1 containing an 1116 iframe at height:100%, and a percentage needs a resolved parent 1117 height to compute against â with max-height alone the iframe 1118 collapses to its min-height and the time slots vanish below the 1119 fold. That was the "slots are not visible" bug on the sheet this 1120 replaces. 1121 vh first as the fallback, then dvh, which tracks the mobile URL bar 1122 collapsing where vh does not. */ 1123 height:calc(92vh - 40px); 1124 height:calc(92dvh - 40px); 1125 max-height:none; 1126 /* Keeps the last row of slots clear of the iOS home indicator. */ 1127 padding-bottom:env(safe-area-inset-bottom,0px)} 1128 #pb-scheduler-modal.is-open [data-role="sched-panel"]{transform:translateY(0)} 1129 1130 /* The sheet's bottom corners are square, so the pinned skip footer must 1131 be too â it is the panel's last child and would otherwise round 1132 against a straight edge. */ 1133 #pb-scheduler-modal [data-role="sched-skip"]{border-radius:0} 1134 1135 /* Follows the finger during a drag â instant, no easing. */ 1136 #pb-scheduler-modal [data-role="sched-panel"].is-dragging{transition:none} 1137 1138 .pbs-grip{display:flex;align-items:center;justify-content:center; 1139 padding:10px 0 2px;flex-shrink:0;cursor:grab;touch-action:none}
1140 .pbs-grip span{display:block;width:40px;height:4px;border-radius:999px; 1141 background:#D7D3CE} 1142 .pbs-grip:active{cursor:grabbing} 1143 /* The header's own top padding is redundant under the grip. */ 1144 #pb-scheduler-modal [data-role="sched-panel"] > div:nth-of-type(2){ 1145 padding-top:.5rem !important} 1146 } 1147 1148 @media (prefers-reduced-motion: reduce){ 1149 #pb-scheduler-modal, 1150 #pb-scheduler-modal [data-role="sched-panel"]{transition:none} 1151 #pb-scheduler-modal [data-role="sched-panel"]{transform:none} 1152 } 1153</style> 1154 <!-- ââââââââââââââââââââââââââââââââââââââââââââââââââââââ 1155 Scheduler modal â opened automatically after a successful download 1156 capture (mirrors the playbook category pages). Embeds /lp/schedule 1157 and forwards source/topic/email through to /lp/capture. 1158 ââââââââââââââââââââââââââââââââââââââââââââââââââââââ --> 1159 <div id="pb-scheduler-modal" class="fixed inset-0 z-[110] items-center justify-center px-4 py-6" 1160 style="display:none; background-color:rgba(17,17,17,0.6);"> 1161 <div data-role="sched-panel" 1162 class="bg-white rounded-2xl shadow-2xl max-w-2xl w-full relative flex flex-col max-h-[92vh]"><div data-role="sched-grip" class="pbs-grip" aria-hidden="true"><span></span></div> 1163 <div class="px-6 pb-3 border-b border-gray-100 flex-shrink-0" style="padding-top:1.75rem;"> 1164 <div class="flex items-start gap-3"> 1165 <span data-role="sched-headicon" 1166 class="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5" 1167 style="background-color:#fff2ef;"> 1168 <svg class="pbs-ico" style="color:#fe4a23;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/><path d="m9 16 2 2 4-4"/></svg> 1169 </span> 1170 <div data-role="sched-headtext" class="flex-1 min-w-0"><h2 data-role="sched-head" class="text-lg md:text-xl font-bold text-gray-900 leading-snug"> 1171 Want a quick walkthrough? 1172 </h2> 1173 <p data-role="sched-sub" class="text-sm text-gray-500 mt-1 leading-relaxed"> 1174 <span data-role="sched-topic">Your playbook</span> is on its way to your inbox. Grab 30 1175 minutes with our team to see how it applies to your hiring â no pressure. 1176 </p> 1177 </div> 1178 <button type="button" id="pb-scheduler-close" aria-label="Close" 1179 class="flex-shrink-0 w-9 h-9 flex items-center justify-center rounded-full text-gray-400 hover:text-gray-700 transition" 1180 style="margin-top:-0.25rem; margin-right:-0.5rem;"> 1181 <svg class="pbs-ico pbs-ico-lg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg> 1182 </button> 1183 </div> 1184 </div> 1185 <div class="flex-1 overflow-y-auto" data-role="sched-body"> 1186 <iframe id="pb-scheduler-frame" title="Schedule a session" loading="lazy" class="w-full block border-0" 1187 style="height:560px;" src="about:blank"></iframe><div data-role="sched-form" class="px-5 md:px-6 pb-6" style="display:none;"> 1188 1189 <div class="pbs-formcol"> 1190 1191 <div data-role="sched-summary" class="pbs-asset"> 1192 <div class="pbs-asset-main"> 1193
1194 <span class="pbs-doc" aria-hidden="true"> 1195 <svg class="pbs-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M12 12v5"/><path d="m9.5 14.5 2.5 2.5 2.5-2.5"/></svg> 1196 </span> 1197 <div class="pbs-asset-txt"> 1198 <p class="pbs-eyebrow">You'll receive</p> 1199 <p class="pbs-asset-title" data-role="sched-assettitle">Your playbook</p> 1200 <p class="pbs-asset-blurb" data-role="sched-assetblurb"></p> 1201 </div> 1202 </div> 1203 <p class="pbs-asset-slot" data-role="sched-slotrow" style="display:none;"> 1204 <svg class="pbs-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/><path d="m9 16 2 2 4-4"/></svg> 1205 <span data-role="sched-slottext"></span> 1206 <button type="button" data-role="sched-slotchange" class="pbs-change">change</button> 1207 </p> 1208 </div> 1209 <div data-role="sched-formhost"></div> 1210 </div> 1211 </div><div data-role="sched-thanks" class="px-5 md:px-6 py-2" style="display:none;"></div> 1212 </div><div data-role="sched-skip" 1213 class="px-5 md:px-6 py-4 border-t border-gray-100 bg-white text-center flex-shrink-0 rounded-b-2xl" 1214 style="display:none;"> 1215 <button type="button" data-role="sched-skip-btn" 1216 class="inline-flex items-center gap-1.5 text-sm font-semibold transition" 1217 style="color:#fe4a23;" 1218 onmouseover="this.style.color='#d93d1a';" 1219 onmouseout="this.style.color='#fe4a23';"> 1220 <span data-role="sched-skip-label">Just email me the playbook</span> 1221 <svg class="pbs-ico pbs-ico-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M12 5l7 7-7 7"/></svg> 1222 </button> 1223 </div> 1224 </div> 1225 </div> 1226 1227
1227<script> 1228 (function () { 1229 var modal = document.getElementById('pb-scheduler-modal'); 1230 if (!modal) return; 1231 var frame = document.getElementById('pb-scheduler-frame'); 1232 var topicEl = modal.querySelector('[data-role="sched-topic"]'); 1233 1234 window.openPlaybookScheduler = function (ctx) { 1235 ctx = ctx || {}; 1236 // Re-opening while a form is still borrowed (the chip's "change", 1237 // or a second entry point): give it back first, or the restore 1238 // target would become this panel's own host and the node would 1239 // be stranded here when the modal closes. 1240 restoreFormPane(); 1241 resetThanks(); 1242 // Carry context through the scheduler to /lp/capture. 1243 // 1244 // Seeded from the PARENT page's query string, not empty. This 1245 // used to be `new URLSearchParams()`, so a visitor who landed 1246 // on ?utm_source=google&gclid=... and opened this modal sent 1247 // an iframe URL with no attribution on it at all. The comment 1248 // inside lp-schedule.html says "Preserve existing query params 1249 // (UTMs)" â but inside an iframe `window.location.search` is 1250 // the iframe's own URL, i.e. whatever we build here. So the 1251 // UTMs were dropped at this boundary and /lp/capture rendered 1252 // five blank hidden inputs (LEAD_AUDIT.md F6). 1253 // 1254 // Whitelisted rather than copied wholesale: the parent URL can 1255 // carry things that must not end up in an iframe src (?email=, 1256 // tokens, one-time codes). 1257 var CARRY = ['utm_source', 'utm_medium', 'utm_campaign', 1258 'utm_content', 'utm_term', 1259 'gclid', 'gbraid', 'wbraid', 'fbclid', 1260 'msclkid', 'li_fat_id', 'ttclid']; 1261 var parentParams = new URLSearchParams(window.location.search); 1262 var params = new URLSearchParams(); 1263 CARRY.forEach(function (k) { 1264 var v = parentParams.get(k); 1265 if (v) params.set(k, v); 1266 }); 1267 // The page the visitor is converting FROM. Distinct from the 1268 // first-touch landing URL the server reads out of the cookie â 1269 // this is the conversion page, and /lp/capture forwards it to 1270 // Zoho as Referral_Link. 1271 params.set('referral_page', window.location.href); 1272 // ctx.source lets a caller label its own lead source; the two 1273 // playbook values stay the default so existing callers are unchanged. 1274 params.set('source', ctx.source || (ctx.inline ? 'playbook_inline' : 'playbook')); 1275 if (ctx.page) params.set('topic', ctx.page); 1276 if (ctx.email) params.set('email', ctx.email); 1277 // Scheduler-first: we don't know who this is yet, so the iframe 1278 // must hand the slot back instead of trying to book it against a 1279 // lead that doesn't exist. See lp-schedule.html. 1280 slotHandler = (typeof ctx.onSlot === 'function') ? ctx.onSlot : null; 1281 if (slotHandler) params.set('flow', 'slot_first'); 1282 // Optional: host the caller's form in this same panel for step 2 1283 // instead of handing back to a second modal. 1284 // Adopted for the scheduler-first flow (ctx.onSlot) and for 1285 // re-opening a finished one (ctx.startAtForm), which has no 1286 // calendar step and therefore no onSlot. 1287 formPane = ((slotHandler || ctx.startAtForm) && 1288 ctx.formPane && ctx.formPane.nodeType === 1) 1289 ? ctx.formPane : null; 1290 formHead = ctx.formHead || 'Where should we send it?'; 1291 formSub = ctx.formSub || ''; 1292 // What they're getting, named per playbook. textContent, so 1293 // editor-supplied category copy can never inject markup. 1294 assetTitle = ctx.assetTitle || ctx.offer || 'Your playbook'; 1295 assetBlurb = ctx.assetBlurb || ''; 1296 if (frame) { resetFrameHeight(); frame.src = '/lp/schedule?' + params.toString(); } 1297 1298 // The skip is meaningful when something waits behind this modal 1299 // (a scheduler-first form) or when the caller says the whole 1300 // prompt is optional â the onboarding greeting has no second 1301 // step, it just needs a way out that isn't the X.
1302 dismissHandler = (typeof ctx.onDismiss === 'function') ? ctx.onDismiss : null; 1303 if (skipWrap) { 1304 skipWrap.style.display = (slotHandler || ctx.dismissible) ? '' : 'none'; 1305 } 1306 // "playbook" is wrong on the hub, where the asset is whatever the 1307 // widget is offering â let the caller name it. textContent, so a 1308 // caller's own copy can never inject markup. 1309 if (skipLabel) skipLabel.textContent = ctx.skipLabel || 'Just email me the playbook'; 1310 1311 // Header + subhead adapt to the entry point. The download flow keeps 1312 // its "on its way to your inbox" confirmation; a cold mid-article 1313 // click (ctx.inline) gets a neutral "talk to an expert" framing. 1314 var headEl = modal.querySelector('[data-role="sched-head"]'); 1315 var subEl = modal.querySelector('[data-role="sched-sub"]'); 1316 if (ctx.head || ctx.sub) { 1317 // Caller-supplied copy, for entry points that need their own 1318 // confirmation â e.g. the Solutions Lab brief, which opens this 1319 // modal to say the brief was emailed and then show slots. 1320 // textContent, not innerHTML: this text can originate from a 1321 // page's own JS and must never be able to inject markup. 1322 if (ctx.head && headEl) headEl.textContent = ctx.head; 1323 if (ctx.sub && subEl) subEl.textContent = ctx.sub; 1324 } else if (ctx.inline) { 1325 if (headEl) headEl.textContent = 'Talk to a Cadient ' + (ctx.role || 'expert'); 1326 if (subEl) subEl.textContent = 1327 'Book 30 minutes to talk through ' + (ctx.topic || 'your hiring') + 1328 ' â no pitch, just answers.'; 1329 } else { 1330 if (headEl) headEl.textContent = 'Want a quick walkthrough?'; 1331 if (subEl) subEl.innerHTML = 1332 '<span data-role="sched-topic">Your playbook</span> is on its way to ' + 1333 'your inbox. Grab 30 minutes with our team to see how it applies to ' + 1334 'your hiring â no pressure.'; 1335 var t = modal.querySelector('[data-role="sched-topic"]'); 1336 if (t) t.textContent = ctx.offer 1337 ? ('Your playbook, â' + ctx.offer + ',â') 1338 : 'Your playbook'; 1339 } 1340 // A cold open (sidebar card, header CTA) is not part of the 1341 // download flow, so closing must not fire its callbacks â 1342 // otherwise dismissing this would surface a "check your inbox" 1343 // card for a download the visitor never requested. 1344 coldOpen = !!ctx.inline; 1345 // Per-open callback, from the copy this partial absorbed. Takes 1346 // precedence over the window.__pb* globals below: it lets the 1347 // originating widget decide its own end state, which the globals 1348 // can't do when more than one widget is on the page. 1349 currentResolve = (typeof ctx.onResolve === 'function') ? ctx.onResolve : null; 1350 // Every entry point ends on the confirmation now, so this defaults 1351 // rather than being opt-in. A caller overrides it only to add flow 1352 // context â the playbook forms append products=1 and an article 1353 // slug, which is what makes the two product cards appear; without 1354 // that the confirmation renders without the products section. 1355 // 1356 // Relative paths only, so a caller can't point this at another 1357 // origin and have its markup injected into the modal. 1358 thankYouUrl = (typeof ctx.thankYou === 'string' && 1359 ctx.thankYou.charAt(0) === '/' && 1360 ctx.thankYou.charAt(1) !== '/') 1361 ? ctx.thankYou 1362 : '/playbooks/thank-you'; 1363 booked = false;
1364 handoffDone = false; 1365 openModalUI(); 1366 1367 // ctx.startAtForm: re-opening a flow that already finished, so 1368 // there is nothing to book â jump straight to the form pane and 1369 // never load the calendar. Keeps the confirmation in THIS panel 1370 // at its usual width, instead of a second, smaller dialog. 1371 if (ctx.startAtForm && formPane) { 1372 if (frame) { resetFrameHeight(); frame.src = 'about:blank'; } 1373 handoffDone = true; 1374 slotHandler = null; 1375 showFormPane(ctx.slot || null, true); 1376 } 1377 }; 1378 1379 var booked = false; 1380 var coldOpen = false; 1381 var currentResolve = null; 1382 var thankYouUrl = null; 1383 var thanksShown = false; 1384 // Scheduler-first state. slotHandler is set per open by ctx.onSlot; 1385 // handoffDone marks that we've already passed control back to the 1386 // caller (via a slot pick or the skip), so the close that follows 1387 // must not also fire the dismissal callbacks â the flow is 1388 // continuing in the caller's form, not ending here. 1389 var slotHandler = null; 1390 var handoffDone = false; 1391 var skipWrap = modal.querySelector('[data-role="sched-skip"]'); 1392 var skipBtn = modal.querySelector('[data-role="sched-skip-btn"]'); 1393 var skipLabel = modal.querySelector('[data-role="sched-skip-label"]'); 1394 // In-panel step 2. formPane is the caller's node while we're 1395 // borrowing it; formHome remembers exactly where to put it back 1396 // (parent + the sibling it sat before), so restoring can't 1397 // reorder the page. 1398 var formWrap = modal.querySelector('[data-role="sched-form"]'); 1399 var formHost = modal.querySelector('[data-role="sched-formhost"]'); 1400 var assetTitleEl = modal.querySelector('[data-role="sched-assettitle"]'); 1401 var assetBlurbEl = modal.querySelector('[data-role="sched-assetblurb"]'); 1402 var slotRow = modal.querySelector('[data-role="sched-slotrow"]'); 1403 var slotText = modal.querySelector('[data-role="sched-slottext"]'); 1404 var slotChange = modal.querySelector('[data-role="sched-slotchange"]'); 1405 var formPane = null, formHome = null, formHead = '', formSub = ''; 1406 var assetTitle = '', assetBlurb = ''; 1407 // Set by ctx.onDismiss. Fires once, however the visitor leaves â 1408 // skip link, X, Escape or backdrop â so a one-time prompt is 1409 // never shown twice because they closed it the "wrong" way. 1410 var dismissHandler = null; 1411 var thanksEl = modal.querySelector('[data-role="sched-thanks"]'); 1412 // The header BAR stays while the confirmation shows â it carries 1413 // the close button, and hiding the whole block left the visitor with 1414 // no X at all. Only the icon and the heading text go, since the 1415 // confirmation has its own heading. 1416 var headBlock = modal.querySelector('[data-role="sched-head"]') 1417 ? modal.querySelector('[data-role="sched-head"]').closest('.border-b') 1418 : null; 1419 var headIcon = modal.querySelector('[data-role="sched-headicon"]'); 1420 var headText = modal.querySelector('[data-role="sched-headtext"]');function showFormPane(slot, noPin) { 1421 if (!formPane || !formHost) return false; 1422 // Lock step 2 to the exact height step 1 occupied, measured 1423 // rather than hardcoded â the calendar's height varies with 1424 // the slot grid, and the CSS min-height alone still left the 1425 // panel shrinking by however tall the skip block was. Desktop 1426 // only: the mobile sheet is already a fixed height, and 1427 // forcing this there would push the submit button off-screen. 1428 // Hold the panel at exactly the size step 1 occupied. Measured, 1429 // not hardcoded: the calendar's height varies with the slot 1430 // grid. Both halves matter â the body (which loses the 560px 1431 // iframe and the skip block) and the header (whose two-line 1432 // subhead becomes one line), or the panel still shifts. 1433 // noPin: no calendar step preceded this open, so there is no 1434 // height to match â let the pane size to its content. 1435 var bodyEl = modal.querySelector('[data-role="sched-body"]'); 1436 if (!noPin && bodyEl && formWrap && window.innerWidth >= 768) { 1437 // The skip footer lives OUTSIDE the body and is hidden a 1438 // few lines below, so its height has to be folded into 1439 // the pin or the panel shrinks by exactly that much when 1440 // step 2 appears. 1441 var footerH = (skipWrap && skipWrap.offsetParent !== null) 1442 ? skipWrap.offsetHeight : 0; 1443 formWrap.style.minHeight = (bodyEl.offsetHeight + footerH) + 'px';
1444 if (headBlock) headBlock.style.minHeight = headBlock.offsetHeight + 'px'; 1445 } 1446 formHome = { parent: formPane.parentNode, before: formPane.nextSibling }; 1447 formHost.appendChild(formPane); 1448 if (frame) frame.style.display = 'none'; 1449 if (skipWrap) skipWrap.style.display = 'none'; 1450 if (assetTitleEl) assetTitleEl.textContent = assetTitle; 1451 if (assetBlurbEl) { 1452 assetBlurbEl.textContent = assetBlurb; 1453 // No category description configured â drop the line 1454 // rather than leaving an empty gap under the title. 1455 assetBlurbEl.style.display = assetBlurb ? '' : 'none'; 1456 } 1457 1458 // The summary block itself is always shown; only the booked 1459 // slot line is conditional. 1460 if (slotRow) { 1461 if (slot && slotText) { 1462 slotText.textContent = slot; 1463 slotRow.style.display = ''; 1464 } else { 1465 slotRow.style.display = 'none'; 1466 } 1467 } 1468 var headEl = modal.querySelector('[data-role="sched-head"]'); 1469 var subEl = modal.querySelector('[data-role="sched-sub"]'); 1470 if (headEl) headEl.textContent = formHead; 1471 if (subEl) subEl.textContent = formSub; 1472 formWrap.style.display = ''; 1473 var body = modal.querySelector('[data-role="sched-body"]'); 1474 if (body) body.scrollTop = 0; 1475 1476 // Focus the first empty field so the visitor can just type. 1477 var firstInput = formPane.querySelector('input[type="email"], input[type="text"]:not([tabindex="-1"])'); 1478 if (firstInput) setTimeout(function () { firstInput.focus(); }, 120); 1479 return true; 1480 }function restoreFormPane() { 1481 if (formPane && formHome && formHome.parent) { 1482 formHome.parent.insertBefore(formPane, formHome.before); 1483 } 1484 formPane = null; 1485 formHome = null; 1486 if (formWrap) { formWrap.style.display = 'none'; formWrap.style.minHeight = ''; } 1487 if (headBlock) headBlock.style.minHeight = ''; 1488 if (slotRow) slotRow.style.display = 'none'; 1489 // Drop any leftover animation state, or the next open would 1490 // start pinned to the previous step's height. 1491 if (panel) panel.style.height = ''; 1492 } 1493 1494 // "change" on the chip: back to the calendar, same panel. 1495 if (slotChange) slotChange.addEventListener('click', function () { 1496 if (typeof window.__pbReopenScheduler === 'function') window.__pbReopenScheduler(); 1497 }); 1498 1499 function handoff(slot, tz) { 1500 if (handoffDone || !slotHandler) return; 1501 handoffDone = true; 1502 var fn = slotHandler; 1503 slotHandler = null;
1504 // With a form pane the panel STAYS OPEN and swaps its body; 1505 // without one we fall back to the original behaviour of 1506 // closing and letting the caller open its own surface. 1507 if (!showFormPane(slot)) closeModalUI(); 1508 fn(slot || null, tz || null); 1509 } 1510 1511 function fireDismiss() { 1512 if (!dismissHandler) return false; 1513 var fn = dismissHandler; 1514 dismissHandler = null; // once only 1515 try { fn(); } catch (e) { /* caller's problem, not ours */ } 1516 return true; 1517 } 1518 1519 if (skipBtn) skipBtn.addEventListener('click', function () { 1520 // A dismissible prompt with no form behind it: record the 1521 // dismissal and close, rather than handing off to a step 1522 // that doesn't exist. 1523 if (!slotHandler && dismissHandler) { fireDismiss(); closeScheduler(); return; } 1524 handoff(null, null); 1525 }); 1526 1527 function closeScheduler() { 1528 // However this prompt is being left â skip, X, Escape, 1529 // backdrop â it counts as shown. 1530 fireDismiss(); 1531 // Step 2 is showing in this panel: close for real and give the 1532 // borrowed form back to the page. No end-of-flow callbacks â 1533 // the visitor abandoned, they didn't finish. 1534 if (handoffDone) { 1535 closeModalUI(); 1536 setTimeout(function () { restoreFormPane(); resetThanks(); }, 320); 1537 return; 1538 } 1539 // Scheduler-first WITHOUT an in-panel form: dismissing is 1540 // treated as the skip, so the visitor still reaches the 1541 // caller's own form rather than losing the download.
1542 // With a form pane we don't do that â the panel would appear 1543 // to ignore the X. The skip link is the route to step 2. 1544 if (slotHandler && !formPane) { handoff(null, null); return; } 1545 if (slotHandler) { slotHandler = null; formPane = null; } 1546 1547 closeModalUI(); 1548 1549 var resolve = currentResolve; 1550 var didBook = booked; 1551 currentResolve = null; 1552 booked = false; 1553 // Cleared here too, so a later cold open can't inherit a 1554 // thank-you target from a dismissed download flow. 1555 thankYouUrl = null; 1556 resetThanks(); 1557 1558 // An explicit per-open callback is honoured whether or not this 1559 // was a cold open. A caller that passed onResolve has asked to be 1560 // told the outcome; coldOpen exists to suppress the GLOBAL __pb* 1561 // download fallbacks below, which is what it was added for. 1562 //
1563 // Without this the Solutions Lab could never learn that a booking 1564 // happened â it opens inline â so its done state went on offering 1565 // "Impatient? Grab a slot on our calendar" to someone who had just 1566 // grabbed one. 1567 if (resolve) { 1568 coldOpen = false; 1569 resolve(didBook); 1570 return; 1571 } 1572 if (coldOpen) { 1573 // Not part of a download flow and no explicit callback, so the 1574 // globals must not fire â otherwise dismissing this would 1575 // surface a "check your inbox" card for a download the visitor 1576 // never requested. 1577 coldOpen = false; 1578 return; 1579 } 1580 if (didBook) { 1581 // A booking completed inside the iframe and its confirmation 1582 // already showed â close the underlying download modal too, so 1583 // we don't re-surface the "check your inbox" card. The flow 1584 // ends on the booking confirmation (single success message). 1585 if (window.__pbCloseDownloadModal) window.__pbCloseDownloadModal(); 1586 } else { 1587 // Visitor dismissed the walkthrough without booking â now show 1588 // the inline download confirmation underneath. 1589 if (window.__pbShowDownloadSuccess) window.__pbShowDownloadSuccess(); 1590 } 1591 } 1592 1593 // ââ Show / hide âââââââââââââââââââââââââââââââââââââââââââ 1594 // Two steps, because the panel now animates: display first so the 1595 // element has a box, then .is-in on the next paint to trigger the 1596 // transition. The reflow between them is a synchronous 1597 // `void offsetWidth`, NOT requestAnimationFrame â rAF is paused in 1598 // hidden and throttled tabs, which would leave the sheet stuck at 1599 // opacity 0 with no error. That exact bug cost a debugging session 1600 // on the consult sheet this replaces. 1601 var panel = modal.querySelector('[data-role="sched-panel"]') || 1602 modal.firstElementChild; 1603 var closeT = null; 1604 1605 1606 1607 var IFRAME_BASE_H = 560; 1608 var frameRO = null; 1609 1610 function fitFrameToContent() { 1611 if (!frame) return; 1612 var doc; 1613 try { doc = frame.contentDocument; } catch (e) { return; } 1614 if (!doc || !doc.body) return; 1615 var h = Math.max(doc.documentElement ? doc.documentElement.scrollHeight : 0, 1616 doc.body.scrollHeight, 0); 1617 if (h > 0) frame.style.height = h + 'px'; 1618 } 1619 1620 function watchFrameHeight() { 1621 var doc, win; 1622 try { doc = frame && frame.contentDocument; win = frame && frame.contentWindow; } 1623 catch (e) { return; } 1624 if (!doc || !doc.body) return; 1625 fitFrameToContent(); 1626 if (frameRO) { frameRO.disconnect(); frameRO = null; } 1627 1628 // The grid grows and shrinks as the visitor changes date or 1629 // the morning/evening tab, so one measurement is never 1630 // enough. Note the constructor: it must be the IFRAME's 1631 // ResizeObserver, not ours. Observations belong to the 1632 // document that created the observer, so a parent-window 1633 // observer watching a node inside the child silently never 1634 // fires â the box stayed at its 560px placeholder while the 1635 // content underneath grew to 605. 1636 if (win && typeof win.ResizeObserver === 'function') { 1637 frameRO = new win.ResizeObserver(fitFrameToContent); 1638 frameRO.observe(doc.body); 1639 } 1640 1641 // Safety net for the first render: the slot grid arrives 1642 // from a fetch after load, so the measurement above can land 1643 // before there is anything to measure. Cheap, bounded, and 1644 // stops as soon as the height settles. 1645 var polls = 0, lastH = -1; 1646 (function settle() { 1647 if (polls++ > 12) return; // ~3s 1648 fitFrameToContent();
1649 var h = frame ? parseInt(frame.style.height, 10) : 0; 1650 if (h === lastH && polls > 3) return; // stable 1651 lastH = h; 1652 setTimeout(settle, 250); 1653 })(); 1654 } 1655 1656 if (frame) frame.addEventListener('load', watchFrameHeight); 1657 1658 // Back to the placeholder height before a new document loads, so 1659 // a tall previous render doesn't leave a gap under a short one. 1660 function resetFrameHeight() { 1661 if (frameRO) { frameRO.disconnect(); frameRO = null; } 1662 if (frame) frame.style.height = IFRAME_BASE_H + 'px'; 1663 } 1664 1665 function ensureIconFont() { 1666 if (document.getElementById('pbs-iconfont')) return; 1667 if (document.querySelector('link[href*="font-awesome"],link[href*="fontawesome"]')) return; 1668 var l = document.createElement('link'); 1669 l.id = 'pbs-iconfont'; 1670 l.rel = 'stylesheet'; 1671 l.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css'; 1672 document.head.appendChild(l); 1673 } 1674 1675 function openModalUI() { 1676 // Fetched early so the stylesheet has arrived by the time a 1677 // booking completes and the confirmation renders. 1678 ensureIconFont(); 1679 clearTimeout(closeT); 1680 modal.style.display = 'flex'; 1681 document.body.style.overflow = 'hidden'; 1682 void modal.offsetWidth; 1683 modal.classList.add('is-open'); 1684 } 1685 1686 function closeModalUI() { 1687 modal.classList.remove('is-open'); 1688 // Keep it displayed until the slide-out finishes. The timeout is a 1689 // fallback rather than the mechanism: transitionend does not fire 1690 // if the property never animates (reduced-motion, or a browser 1691 // that ignores the transition), which would leave the modal 1692 // permanently covering the page. 1693 clearTimeout(closeT); 1694 closeT = setTimeout(function () { 1695 modal.style.display = 'none'; 1696 document.body.style.overflow = ''; 1697 if (panel) panel.style.transform = ''; // clear any drag offset 1698 }, 300); 1699 } 1700 1701 function isOpen() { return modal.classList.contains('is-open'); } 1702 1703 var closeBtn = document.getElementById('pb-scheduler-close'); 1704 if (closeBtn) closeBtn.addEventListener('click', closeScheduler); 1705 modal.addEventListener('click', function (e) { 1706 if (e.target === modal) closeScheduler(); 1707 }); 1708 // Keyed off the class, not style.display: display is now 'flex' for 1709 // the whole slide-out too, so the old test would have fired a second 1710 // close mid-animation. 1711 document.addEventListener('keydown', function (e) { 1712 if (e.key === 'Escape' && isOpen()) closeScheduler(); 1713 }); 1714 // After a successful booking the embedded confirmation posts 1715 // 'closeModal' â mark it booked and close the flow cleanly. 1716 // 1717 // It also posts a structured 'cadient:booked' immediately, carrying 1718 // the slot. We act on that ONLY when the caller asked for a 1719 // thank-you redirect: reacting to it otherwise would close the 1720 // modal at once and cut short the in-frame confirmation that every 1721 // other entry point currently ends on. 1722 window.addEventListener('message', function (e) { 1723 if (e.data === 'closeModal') { 1724 // Once our own confirmation is up, this 3s-delayed message 1725 // from the iframe must not tear the modal down underneath 1726 // it â the visitor is reading the thank-you. 1727 if (thanksShown) return; 1728 booked = true; closeScheduler(); return; 1729 } 1730 1731 if (!e.data || e.origin !== window.location.origin) return; 1732 1733 // Scheduler-first: the iframe hands the picked slot back 1734 // instead of booking it, because there's no lead yet. The 1735 // caller holds it and commits the booking with its form. 1736 if (e.data.type === 'cadient:slot') { 1737 handoff(e.data.slot, e.data.timeZone); 1738 return; 1739 } 1740 1741 // Booked: stamp the prompt now rather than waiting for the 1742 // close, so a tab crash mid-confirmation can't re-greet 1743 // someone who has a meeting on the calendar. 1744 if (e.data.type === 'cadient:booked') fireDismiss(); 1745 1746 // Structured payload. Same-origin only â the bare string above 1747 // stays permissive for the cross-origin WordPress embed, but 1748 // this one drives a fetch and a DOM swap. 1749 if (e.data.type !== 'cadient:booked') return; 1750 if (!thankYouUrl || thanksShown) return; 1751 1752 booked = true; 1753 showThanks(e.data.slot, e.data.timeZone); 1754 }); 1755 1756 // ââ Post-booking confirmation, in place of the scheduler ââ 1757 // Fetched rather than pre-rendered: the fragment depends on the 1758 // booked slot, which only exists once the booking is made, and on 1759 // the article's two featured products, which are resolved 1760 // server-side. One template serves this and the standalone page. 1761 function showThanks(slot, tz) { 1762 var url = thankYouUrl; 1763 if (slot) url += (url.indexOf('?') === -1 ? '?' : '&') + 1764 'slot=' + encodeURIComponent(slot); 1765 if (tz) url += '&tz=' + encodeURIComponent(tz); 1766 1767 fetch(url, { credentials: 'same-origin' }) 1768 .then(function (r) { if (!r.ok) throw new Error(r.status); return r.text(); }) 1769 .then(function (html) { 1770 if (!thanksEl) throw new Error('no host'); 1771 thanksEl.innerHTML = html; 1772 thanksShown = true; 1773 // style.display, NOT the hidden attribute: the iframe 1774 // carries Tailwind's `block`, and .block{display:block} 1775 // outranks the UA's [hidden]{display:none}. Setting 1776 // hidden left it laid out as a 560px blank gap above 1777 // the confirmation. 1778 if (frame) frame.style.display = 'none';
1779 // The modal's own heading duplicates the 1780 // confirmation's, so it goes â but the bar holding the 1781 // close button stays. 1782 if (headIcon) headIcon.style.display = 'none'; 1783 if (headText) headText.style.display = 'none'; 1784 if (headBlock) { 1785 headBlock.style.borderBottomColor = 'transparent'; 1786 headBlock.style.paddingBottom = '0'; 1787 } 1788 // The skip footer offered "Maybe later" on a booking 1789 // that had just succeeded â it read as though the 1790 // booking had not taken, and clicking it only closed 1791 // the modal. There is nothing left to skip here. 1792 if (skipWrap) skipWrap.style.display = 'none'; 1793 // A CTA pointing at the page we are already on should 1794 // close the modal, not reload it. Bound here because 1795 // innerHTML never runs
1795<script> in the fragment. 1796 var tyCta = thanksEl.querySelector('[data-role="ty-cta"]'); 1797 if (tyCta) { 1798 tyCta.addEventListener('click', function (ev) { 1799 var href = tyCta.getAttribute('href') || ''; 1800 if (href && href.split('?')[0] === window.location.pathname) { 1801 ev.preventDefault(); 1802 closeScheduler(); 1803 } 1804 }); 1805 } 1806 thanksEl.style.display = ''; 1807 var body = modal.querySelector('[data-role="sched-body"]'); 1808 if (body) body.scrollTop = 0; 1809 }) 1810 .catch(function () { 1811 // Fetch failed and the meeting is already booked, so 1812 // fall back to the iframe's own confirmation rather than 1813 // leaving a blank panel. It closes itself after 3s. 1814 thanksShown = false; 1815 }); 1816 } 1817 1818 // Put the modal back the way it was, so the next open shows the 1819 // scheduler and not a stale confirmation. 1820 1821 window.pbSchedulerShowFragment = function (html) { 1822 if (!thanksEl) return false; 1823 // Re-opening a finished flow: the panel is closed, so open it 1824 // here rather than letting the caller fall back to its own 1825 // (much smaller) card â that size flip is exactly what the 1826 // single-panel work was for. 1827 if (!isOpen()) { 1828 if (frame) { resetFrameHeight(); frame.src = 'about:blank'; } 1829 handoffDone = true; 1830 slotHandler = null; 1831 if (skipWrap) skipWrap.style.display = 'none'; 1832 openModalUI(); 1833 } 1834 if (formWrap) formWrap.style.display = 'none'; 1835 if (frame) frame.style.display = 'none'; 1836 if (headIcon) headIcon.style.display = 'none'; 1837 if (headText) headText.style.display = 'none'; 1838 if (headBlock) { 1839 headBlock.style.borderBottomColor = 'transparent'; 1840 headBlock.style.paddingBottom = '0'; 1841 } 1842 thanksEl.innerHTML = html; 1843 thanksEl.style.display = ''; 1844 thanksShown = true; 1845 var body = modal.querySelector('[data-role="sched-body"]'); 1846 if (body) body.scrollTop = 0; 1847 return true; 1848 }; 1849 1850 // Whether the panel is currently hosting a caller's form. 1851 window.pbSchedulerHasFormPane = function () { return !!formPane; }; 1852 1853 function resetThanks() { 1854 thanksShown = false; 1855 if (thanksEl) { thanksEl.style.display = 'none'; thanksEl.innerHTML = ''; } 1856 if (formWrap) formWrap.style.display = 'none'; 1857 if (frame) frame.style.display = ''; 1858 if (headIcon) headIcon.style.display = ''; 1859 if (headText) headText.style.display = ''; 1860 if (headBlock) { 1861 headBlock.style.borderBottomColor = ''; 1862 headBlock.style.paddingBottom = ''; 1863 } 1864 } 1865 1866 // ââ Drag to dismiss âââââââââââââââââââââââââââââââââââââââ 1867 // Handle only, and that is a constraint rather than a choice: the 1868 // panel body is a same-origin iframe, and pointer events inside a 1869 // nested document never reach this one, so a drag begun over the 1870 // scheduler cannot be tracked. Hence the visible grip. 1871 // 1872 // Pointer Events, not touchstart/touchmove: one code path covers 1873 // touch, pen and mouse, and setPointerCapture keeps delivering moves 1874 // if the finger slides outside the handle â which it will, because 1875 // the gesture is downward and the handle is 14px tall. 1876 var grip = modal.querySelector('[data-role="sched-grip"]'); 1877 if (grip && panel && window.PointerEvent) { 1878 var startY = 0, dy = 0, dragging = false;
1879 var DISMISS_PX = 90; // past this, let go and it closes 1880 1881 grip.addEventListener('pointerdown', function (e) { 1882 dragging = true; startY = e.clientY; dy = 0; 1883 panel.classList.add('is-dragging'); 1884 grip.setPointerCapture(e.pointerId); 1885 }); 1886 1887 grip.addEventListener('pointermove', function (e) { 1888 if (!dragging) return; 1889 dy = e.clientY - startY; 1890 // Downward only. Allowing an upward drag would lift the sheet 1891 // off the bottom edge and expose the backdrop beneath it. 1892 if (dy < 0) dy = 0; 1893 panel.style.transform = 'translateY(' + dy + 'px)'; 1894 }); 1895 1896 function endDrag() { 1897 if (!dragging) return; 1898 dragging = false; 1899 panel.classList.remove('is-dragging'); 1900 if (dy > DISMISS_PX) { 1901 // Hand back to the normal close path so the end-state 1902 // callbacks still run â a dismissed sheet has to behave 1903 // exactly like pressing the à or Escape. 1904 panel.style.transform = ''; 1905 closeScheduler(); 1906 } else { 1907 panel.style.transform = ''; // springs back via the transition 1908 } 1909 dy = 0; 1910 } 1911 grip.addEventListener('pointerup', endDrag); 1912 grip.addEventListener('pointercancel', endDrag); 1913 } 1914 }) (); 1915 </script>
1915 1916
1917<script> 1918 const menuBtn = document.getElementById('mobile-menu-btn'); 1919 const mobileMenu = document.getElementById('mobile-menu'); 1920 if (menuBtn && mobileMenu) { 1921 menuBtn.addEventListener('click', () => mobileMenu.classList.toggle('hidden')); 1922 } 1923</script>
1923 1924 1925<main id="main-content"> 1926 1927 <!-- ââââââââââ HERO ââââââââââ --> 1928 <section class="px-6 py-16 md:py-20"> 1929 <div class="max-w-4xl mx-auto text-center"> 1930 <p class="text-sm font-semibold uppercase tracking-widest ss-orange mb-5">Case Study</p> 1931 <h1 class="text-4xl md:text-5xl lg:text-6xl font-extrabold leading-[1.1] tracking-tight"> 1932 How Cadientâs SmartTextingâ¢<br>helped a large regional grocery store reduce hiring time by 41% 1933 </h1> 1934 </div> 1935 </section> 1936 1937 1938 <!-- ââââââââââ STATS ââââââââââ --> 1939 <section class="ss-cream px-6 py-12"> 1940 <div class="max-w-5xl mx-auto grid sm:grid-cols-3 gap-8 text-center"> 1941 1942 <div> 1943 1944 <div class="text-4xl md:text-5xl font-extrabold ss-orange">55%</div> 1945 1946 <p class="text-slate-600 font-medium mt-2">Increase in Opt-in Automation Efficiency</p> 1947 </div> 1948 1949 <div> 1950 1951 <div class="text-4xl md:text-5xl font-extrabold ss-orange">64%</div> 1952 1953 <p class="text-slate-600 font-medium mt-2">Increase in response rates</p> 1954 </div> 1955 1956 <div> 1957 1958 <div class="text-4xl md:text-5xl font-extrabold ss-orange">41%</div> 1959 1960 <p class="text-slate-600 font-medium mt-2">Faster Hiring Cycle</p> 1961 </div> 1962 1963 </div> 1964 </section> 1965 1966 1967 1968 <!-- ââââââââââ ABOUT THE CLIENT ââââââââââ --> 1969 <section class="px-6 py-16 md:py-20"> 1970 <div class="max-w-3xl mx-auto"> 1971 <h2 class="text-2xl md:text-3xl font-extrabold tracking-tight mb-6">About the Client</h2> 1972 1973 <p class="text-lg text-slate-600 leading-relaxed mb-4">For the longest time, a leading regional grocery store struggled with keeping its applicant pool engaged throughout the hiring journey.</p> 1974 1975 <p class="text-lg text-slate-600 leading-relaxed mb-4">This became a major barrier to hiring quality candidates, which negatively impacted overall business growth.</p> 1976 1977 <p class="text-lg text-slate-600 leading-relaxed mb-4">When Cadient stepped in, its texting solutions transformed the clientâs applicant pool, delivering multi-pronged benefits through improved communication and engagement.</p> 1978 1979 1980 </div> 1981 </section> 1982 1983 1984 1985 <!-- ââââââââââ CHALLENGES ââââââââââ --> 1986 <section class="ss-cream px-6 py-16 md:py-20"> 1987 <div class="max-w-3xl mx-auto"> 1988 <h2 class="text-2xl md:text-3xl font-extrabold tracking-tight mb-4">Challenges</h2> 1989 <p class="text-lg text-slate-600 leading-relaxed mb-6">Before using Cadientâs texting solutions, the client faced several HR obstacles:</p> 1990 <ul class="space-y-3"> 1991 1992 <li class="flex items-start gap-3"> 1993 <i class="fa-solid fa-circle-xmark text-slate-400 mt-1 flex-shrink-0"></i> 1994 <span class="text-slate-700">Difficulty reaching applicants and getting timely responses</span> 1995 </li> 1996 1997 <li class="flex items-start gap-3"> 1998 <i class="fa-solid fa-circle-xmark text-slate-400 mt-1 flex-shrink-0"></i> 1999 <span class="text-slate-700">Trouble scheduling interviews</span> 2000 </li> 2001 2002 <li class="flex items-start gap-3"> 2003 <i class="fa-solid fa-circle-xmark text-slate-400 mt-1 flex-shrink-0"></i> 2004 <span class="text-slate-700">Low attendance at job fairs and events</span> 2005 </li> 2006 2007 <li class="flex items-start gap-3"> 2008 <i class="fa-solid fa-circle-xmark text-slate-400 mt-1 flex-shrink-0"></i>
2009 <span class="text-slate-700">6% average email response rateâtoo low to sustain interest</span> 2010 </li> 2011 2012 <li class="flex items-start gap-3"> 2013 <i class="fa-solid fa-circle-xmark text-slate-400 mt-1 flex-shrink-0"></i> 2014 <span class="text-slate-700">Candidates primarily applied via mobile, but traditional methods lacked mobile responsiveness</span> 2015 </li> 2016 2017 <li class="flex items-start gap-3"> 2018 <i class="fa-solid fa-circle-xmark text-slate-400 mt-1 flex-shrink-0"></i> 2019 <span class="text-slate-700">Even small negative experiences often deterred top candidates</span> 2020 </li> 2021 2022 </ul> 2023 <p class="text-lg text-slate-600 leading-relaxed mt-6">The client was operating in a high-volume hourly hiring market, where fast, effective communication was critical.</p> 2024 </div> 2025 </section> 2026 2027 2028 2029 <!-- ââââââââââ SOLUTION ââââââââââ --> 2030 <section class="px-6 py-16 md:py-20"> 2031 <div class="max-w-3xl mx-auto"> 2032 <h2 class="text-2xl md:text-3xl font-extrabold tracking-tight mb-6">The Solution</h2> 2033 <p class="text-lg text-slate-600 leading-relaxed mb-6">The client upgraded its hiring process by integrating Cadientâs texting tools into its Applicant Tracking System (ATS). Compared to legacy methods, this approach delivered:</p> 2034 2035 <h3 class="font-bold text-lg mb-3">Key Solutions Implemented:</h3> 2036 <ul class="space-y-3 mb-6"> 2037 2038 <li class="flex items-start gap-3"> 2039 <i class="fa-solid fa-circle-check ss-orange mt-1 flex-shrink-0"></i> 2040 <span class="text-slate-700">Cadient Texting: Real-time, one-on-one communication</span> 2041 </li> 2042 2043 <li class="flex items-start gap-3"> 2044 <i class="fa-solid fa-circle-check ss-orange mt-1 flex-shrink-0"></i> 2045 <span class="text-slate-700">Text Apply: Easy application via text, building a candidate pipeline</span> 2046 </li> 2047 2048 <li class="flex items-start gap-3"> 2049 <i class="fa-solid fa-circle-check ss-orange mt-1 flex-shrink-0"></i> 2050 <span class="text-slate-700">Text Notify: Automated messaging for status updates and scheduling</span> 2051 </li> 2052 2053 </ul> 2054 2055 2056 2057 <blockquote class="border-l-4 ss-border-orange border-orange-500 pl-6 my-8 text-xl italic text-slate-700 leading-relaxed"> 2058 âWe were impressed by Cadientâs focus on our unique data and their ability to reduce turnover through intelligent automation.â 2059 </blockquote> 2060 2061 2062 <div class="ss-box rounded-2xl p-6 md:p-8 mt-8"> 2063 <h3 class="font-bold text-lg mb-3">Automation & Intelligence:</h3> 2064 <ul class="space-y-2"> 2065 2066 <li class="flex items-start gap-3"> 2067 <i class="fa-solid fa-circle-check ss-orange mt-1 flex-shrink-0"></i> 2068 <span class="text-slate-700">Cadientâs SmartTenure, powered by Decision Point, uses machine learning to identify high-tenure candidates</span> 2069 </li> 2070 2071 <li class="flex items-start gap-3"> 2072 <i class="fa-solid fa-circle-check ss-orange mt-1 flex-shrink-0"></i> 2073 <span class="text-slate-700">Text Notify automates messages to candidatesâ preferred devices</span> 2074 </li> 2075 2076 <li class="flex items-start gap-3"> 2077 <i class="fa-solid fa-circle-check ss-orange mt-1 flex-shrink-0"></i> 2078 <span class="text-slate-700">
2078Cadientâs end-to-end Talent Acquisition system includes texting, selection tools, and real-time analytics</span> 2079 </li> 2080 2081 </ul> 2082 2083 </div> 2084 2085 </div> 2086 </section> 2087 2088 2089 2090 <!-- ââââââââââ OUTCOME ââââââââââ --> 2091 <section class="ss-cream px-6 py-16 md:py-20"> 2092 <div class="max-w-3xl mx-auto"> 2093 <h2 class="text-2xl md:text-3xl font-extrabold tracking-tight mb-4">The Outcome</h2> 2094 <p class="text-lg text-slate-600 leading-relaxed mb-6">Cadientâs texting solutions brought about a transformative shift in the clientâs recruitment process. By replacing outdated, low-response methods with real-time, text-based interactions, the company unlocked immediate improvements in speed, communication, and applicant satisfaction.</p> 2095 2096 <div class="mb-6"> 2097 <h3 class="font-bold text-lg mb-3">Key Outcomes Achieved:</h3> 2098 <ul class="space-y-2"> 2099 2100 <li class="flex items-start gap-3"> 2101 <i class="fa-solid fa-circle-check ss-orange mt-1 flex-shrink-0"></i> 2102 <span class="text-slate-700">41% faster hiring cycle</span> 2103 </li> 2104 2105 <li class="flex items-start gap-3"> 2106 <i class="fa-solid fa-circle-check ss-orange mt-1 flex-shrink-0"></i> 2107 <span class="text-slate-700">64% average text response rate â most within 90 seconds</span> 2108 </li> 2109 2110 <li class="flex items-start gap-3"> 2111 <i class="fa-solid fa-circle-check ss-orange mt-1 flex-shrink-0"></i> 2112 <span class="text-slate-700">55% increase in opt-in automation efficiency</span> 2113 </li> 2114 2115 </ul> 2116 </div> 2117 2118 <p class="text-lg text-slate-600 leading-relaxed">Overall, the company now operates with a more agile, data-driven hiring process, ensuring that top candidates remain engaged and are brought onboard fasterâultimately supporting better customer service and business performance.</p> 2119 </div> 2120 </section> 2121 2122 2123 <!-- ââââââââââ CLOSING CTA ââââââââââ --> 2124 <section class="px-6 py-16 md:py-20"> 2125 <div class="max-w-5xl mx-auto"> 2126 <div class="ss-box rounded-[40px] p-8 md:p-12 text-center"> 2127 <h2 class="text-3xl md:text-4xl font-extrabold tracking-tight mb-8 max-w-2xl mx-auto">See what Cadient can do for your hiring team.</h2> 2128 <a href="#" class="trigger-contact-sales inline-flex items-center gap-3 ss-dark text-white font-semibold px-8 py-4 rounded-xl transition-colors"> 2129 Talk to an Expert <i class="fa-solid fa-arrow-up-right ss-orange text-sm"></i> 2130 </a> 2131 </div> 2132 </div> 2133 </section> 2134 2135</main> 2136 2137<!-- FOOTER --> 2138<style>.text-cadient-blue{color:#0077b6}.hover\:text-cadient-blue:hover{color:#0077b6}footer summary::-webkit-details-marker{display:none}footer summary{list-style:none}.ftr-more-chev{transition:transform .2s ease}footer details[open] .ftr-more-chev{transform:rotate(90deg)}</style> 2139<footer class="bg-slate-900 text-slate-300 py-16"> 2140 <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> 2141 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-y-12 gap-x-16"> 2142 2143 <!-- Brand â page-specific when a product context is set, generic Cadient brand otherwise --> 2144 <div class="space-y-4"> 2145 2146 <a href="/"><img src="/static/img/logo.webp" width="300" height="93" 2147 class="w-32 brightness-0 invert opacity-90 hover:opacity-100 transition-opacity" 2148 alt="Cadient"></a> 2149 <p class="text-sm text-slate-400 leading-relaxed">Transforming the way businesses hire with precision,
2150 speed, and intelligence.</p> 2151 2152 <div class="flex space-x-4 pt-2"> 2153 <a href="https://www.linkedin.com/company/cadient-talent/" aria-label="Cadient on LinkedIn" 2154 class="text-slate-400 hover:text-white transition-colors"><svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.14 1.45-2.14 2.94v5.67H9.34V9h3.42v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.45v6.29zM5.34 7.43a2.07 2.07 0 1 1 0-4.14 2.07 2.07 0 0 1 0 4.14zM7.12 20.45H3.56V9h3.56v11.45zM22.22 0H1.77C.79 0 0 .77 0 1.73v20.54C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.73V1.73C24 .77 23.2 0 22.22 0z"/></svg></a> 2155 <a href="https://x.com/CadientTalent" aria-label="Cadient on X (Twitter)" class="text-slate-400 hover:text-white transition-colors"><svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg></a> 2156 <a href="https://www.facebook.com/cadienttalent/" aria-label="Cadient on Facebook" 2157 class="text-slate-400 hover:text-white transition-colors"><svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M24 12.07C24 5.4 18.63 0 12 0S0 5.4 0 12.07c0 6.02 4.39 11.01 10.13 11.93v-8.44H7.08v-3.49h3.05V9.41c0-3.02 1.79-4.69 4.53-4.69 1.31 0 2.68.24 2.68.24v2.97h-1.51c-1.49 0-1.96.93-1.96 1.89v2.25h3.33l-.53 3.49h-2.8v8.44C19.61 23.08 24 18.09 24 12.07z"/></svg></a> 2158 </div> 2159 </div> 2160 2161 <!-- Solutions â mirrors the header mega-menu (Recruit / Hire / Retain) --> 2162 <div class="lg:col-span-3"> 2163 <h2 class="text-white font-semibold mb-4 text-sm uppercase tracking-wider">Solutions</h2> 2164 <div class="grid grid-cols-2 sm:grid-cols-3 gap-x-8 gap-y-8"> 2165 <!-- Recruit --> 2166 <div> 2167 <p class="text-[11px] font-semibold uppercase tracking-widest text-slate-500 mb-3">Recruit</p> 2168 <ul class="space-y-2.5 text-[13px] text-slate-400"> 2169 <li><a href="/smart-automation" class="hover:text-cadient-blue transition-colors">SmartAutomation</a></li> 2170 <li><a href="/smart-refer/" class="hover:text-cadient-blue transition-colors">SmartRefer</a></li> 2171 <li><a href="/smart-source" class="hover:text-cadient-blue transition-colors">SmartSource</a></li> 2172 <li><a href="/smart-registry" class="hover:text-cadient-blue transition-colors">SmartRegistry</a></li> 2173 <li><a href="/smartsuite/smartmatch/" class="hover:text-cadient-blue transition-colors">SmartMatch</a></li> 2174 <li><a href="/smartsuite/smartscreen/" class="hover:text-cadient-blue transition-colors">SmartScreen</a></li> 2175 <li><a href="/smartsuite/smarttexting/" class="hover:text-cadient-blue transition-colors">SmartTexting</a></li> 2176 </ul> 2177 </div> 2178 <!-- Hire --> 2179 <div> 2180 <p class="text-[11px] font-semibold uppercase tracking-widest text-slate-500 mb-3">Hire</p> 2181 <ul class="space-y-2.5 text-[13px] text-slate-400"> 2182 <li><a href="/smart-shield" class="hover:text-cadient-blue transition-colors">SmartShield</a></li> 2183 <li><a href="/smartsuite/smarthire/" class="hover:text-cadient-blue transition-colors">SmartHire</a></li> 2184 <li><a href="/smartsuite/smartinterview/" class="hover:text-cadient-blue transition-colors">SmartInterview</a></li> 2185 <li><a href="/smartsuite/smarttexting/" class="hover:text-cadient-blue transition-colors">SmartTexting</a></li> 2186 <li><a href="/smartsuite/smartoffer/" class="hover:text-cadient-blue transition-colors">SmartOffer</a></li> 2187 </ul> 2188 </div> 2189 <!-- Retain --> 2190 <div> 2191 <p class="text-[11px] font-semibold uppercase tracking-widest text-slate-500 mb-3">Retain</p> 2192 <ul class="space-y-2.5 text-[13px] text-slate-400"> 2193 <li><a href="/smart-communicate/" class="hover:text-cadient-blue transition-colors">SmartCommunicate</a></li> 2194 <li><a href="/smartsuite/smarttenure/" class="hover:text-cadient-blue transition-colors">SmartTenure</a></li> 2195 <li><a href="/smartsuite/smartfeedback/" class="hover:text-cadient-blue transition-colors">SmartFeedback</a></li> 2196 <li><a href="/smartsuite/smarttexting/" class="hover:text-cadient-blue transition-colors">SmartTexting</a></li> 2197 </ul> 2198 </div> 2199 </div> 2200 </div> 2201 2202 <!-- Company --> 2203 <div> 2204 <h2 class="text-white font-semibold mb-4 text-sm uppercase tracking-wider">Company</h2> 2205 <ul class="space-y-3 text-sm"> 2206 <li><a href="/about" 2207 class="hover:text-cadient-blue transition-colors">About Us</a></li> 2208 <li><a href="/lp/schedule" rel="nofollow" 2209 class="trigger-contact-sales hover:text-cadient-blue transition-colors">Contact</a></li> 2210 <li><a href="/article" class="hover:text-cadient-blue transition-colors">Blog</a></li> 2211 </ul> 2212 </div> 2213 2214 </div> 2215 2216 <div 2217 class="border-t border-slate-800 mt-12 pt-8 flex flex-col md:flex-row justify-between items-center text-sm text-slate-500"> 2218 <p>© 2026 Cadient Talent. All rights reserved.</p> 2219 <a href="/privacy-policy" class="hover:text-white mt-4 md:mt-0">Privacy Policy</a> 2220 </div> 2221 </div> 2222</footer> 2223 2224<!-- Contact Sales Modal Partial --> 2225<style> 2226 /* Explicit sizing so the modal renders correctly regardless of whether the 2227 host page ships full Tailwind (smart-refer/CDN) or purged Tailwind (blog). 2228 Without this, the card has no width below the sm: breakpoint and collapses 2229 to the iframe's 300px intrinsic width, cutting off the scheduler. */ 2230 #contactSalesModal .csm-card { 2231 width: 100%; 2232 max-width: 42rem; 2233 } 2234 2235 #contactSalesModal .csm-frame { 2236 display: block; 2237 width: 100%; 2238 height: 650px; 2239 } 2240 2241 @media (max-width: 640px) { 2242 #contactSalesModal .csm-wrap { 2243 padding: 0.5rem; 2244 } 2245 2246 /* Fill the phone viewport instead of shrinking to the iframe default. */ 2247 #contactSalesModal .csm-card { 2248 max-width: 100%; 2249 } 2250 } 2251</style> 2252<div id="contactSalesModal" class="fixed inset-0 z-50 hidden overflow-y-auto"> 2253 <!-- Backdrop --> 2254 <div class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm transition-opacity" id="modalBackdrop"></div> 2255 2256 <!-- Modal Content --> 2257 <div class="csm-wrap flex min-h-full items-center justify-center p-4 text-center sm:p-0"> 2258 <div 2259 class="csm-card relative transform overflow-hidden rounded-2xl bg-white text-left shadow-2xl transition-all sm:my-8"> 2260 <div class="bg-white"> 2261 <div class="flex items-center justify-between px-6 py-4 border-b border-slate-100"> 2262 <h3 class="text-xl font-bold text-slate-900">Schedule a Demo</h3> 2263 <button type="button" class="text-slate-400 hover:text-slate-600 transition-colors" 2264 id="closeModalBtn" aria-label="Close"> 2265 <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg> 2266 </button> 2267 </div> 2268 2269 <div class="p-0"> 2270 <iframe id="schedulerIframe" src="" title="Schedule a demo with Cadient" class="csm-frame border-none"></iframe> 2271 </div> 2272 </div> 2273 </div> 2274 </div> 2275</div> 2276
2277<script> 2278 (function () { 2279 const modal = document.getElementById('contactSalesModal'); 2280 const closeBtn = document.getElementById('closeModalBtn'); 2281 const backdrop = document.getElementById('modalBackdrop'); 2282 const iframe = document.getElementById('schedulerIframe'); 2283 2284 function openContactSalesModal() { 2285 modal.classList.remove('hidden'); 2286 document.body.style.overflow = 'hidden'; 2287 iframe.src = '/lp/schedule'; 2288 } 2289 2290 function closeContactSalesModal() { 2291 modal.classList.add('hidden'); 2292 document.body.style.overflow = ''; 2293 iframe.src = ''; 2294 } 2295 2296 // Event listeners 2297 if (closeBtn) { 2298 closeBtn.addEventListener('click', closeContactSalesModal); 2299 } 2300 2301 if (backdrop) { 2302 backdrop.addEventListener('click', closeContactSalesModal); 2303 } 2304 2305 // Expose globally for other scripts to call 2306 window.openContactSalesModal = openContactSalesModal; 2307 window.closeContactSalesModal = closeContactSalesModal; 2308 2309 // Support for elements with trigger class 2310 document.addEventListener('click', function (e) { 2311 if (e.target.matches('.trigger-contact-sales') || e.target.closest('.trigger-contact-sales')) { 2312 e.preventDefault(); 2313 openContactSalesModal(); 2314 } 2315 }); 2316 })(); 2317</script>
2317 2318 2319</body> 2320 2321</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.