1/* trywhm-hl.js â Variant D pulse highlight for WHM "Create a New Account" card */ 2(function () { 3 'use strict'; 4 5 var STORAGE_KEY = 'trywhm_hl_shown_v1'; 6 var COOKIE_NAME = 'trywhm_hl_shown'; 7 var VARIANT = 'D'; 8 9 // ââ URL param âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 10 var params = new URLSearchParams(window.location.search); 11 var hlParam = params.get('hl'); 12 var forced = hlParam === 'force'; 13 var dbg = hlParam === 'debug'; 14 15 function log() { 16 if (dbg) { 17 var args = Array.prototype.slice.call(arguments); 18 console.log.apply(console, ['[trywhm-hl]'].concat(args)); 19 } 20 } 21 22 23 // ââ Session ID ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 24 function generateUUID() { 25 if (window.crypto && crypto.randomUUID) return crypto.randomUUID(); 26 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 27 var r = Math.random() * 16 | 0; 28 return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); 29 }); 30 } 31 32 var sessionId = generateUUID(); 33 window.__trywhm_hl = window.__trywhm_hl || {}; 34 window.__trywhm_hl.sessionId = sessionId; 35 36 // ââ Analytics âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 37 // Resolve receiver once at init time 38 function resolveTracker() { 39 if (window.mandatoryDataLayer && Array.isArray(window.mandatoryDataLayer)) { 40 return function (name, payload) { window.mandatoryDataLayer.push(Object.assign({ event: name }, payload)); }; 41 } 42 if (window.dataLayer && Array.isArray(window.dataLayer)) { 43 return function (name, payload) { window.dataLayer.push(Object.assign({ event: name }, payload)); }; 44 } 45 if (typeof window.__trywhm_hl_track === 'function') { 46 return function (name, payload) { window.__trywhm_hl_track(name, payload); }; 47 } 48 return null; 49 } 50 51 var tracker = resolveTracker(); 52 53 function track(eventName, payload) { 54 var p = Object.assign({ variant: VARIANT, session_id: sessionId }, payload); 55 if (forced) p.forced = true; 56 if (tracker) tracker(eventName, p); 57 log('track', eventName, p); 58 } 59 60 // ââ Storage âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 61 function storageGet() { 62 try { return localStorage.getItem(STORAGE_KEY); } catch (e) { return null; } 63 } 64 65 function storageSet() { 66 try { 67 localStorage.setItem(STORAGE_KEY, new Date().toISOString()); 68 } catch (e) { 69 document.cookie = COOKIE_NAME + '=1; SameSite=Lax; Max-Age=2592000; path=/'; 70 } 71 } 72 73 function storageClear() { 74 try { 75 Object.keys(localStorage).forEach(function (k) { 76 if (k.indexOf('trywhm_hl_') === 0) localStorage.removeItem(k); 77 }); 78 } catch (e) {} 79 document.cookie = COOKIE_NAME + '=; SameSite=Lax; Max-Age=0; path=/'; 80 } 81 82 // ââ Early exits âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 83 if (hlParam === 'reset') { storageClear(); log('storage cleared, exiting'); return; } 84 if (hlParam === 'off') { log('hl=off, exiting'); return; } 85 if (!forced && storageGet()) { log('already shown, exiting'); return; } 86 87 // ââ State âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 88 var cardEl, rootEl, pulseEl, strips, startTime, setupDone, dismissed; 89 90 // ââ Find target card ââââââââââââââââââââââââââââââââââââââââââââââââââââââ 91 function findCard() { 92 var section = document.getElementById('top_tools'); 93 log('findCard: #top_tools =', section); 94 95 // Search within #top_tools if available, otherwise search full document 96 var candidates = section 97 ? section.querySelectorAll('a') 98 : document.querySelectorAll('a'); 99 100 log('findCard: candidates count =', candidates.length); 101 102 for (var i = 0; i < candidates.length; i++) { 103 var text = candidates[i].textContent.toLowerCase(); 104 if (text.indexOf('create a new account') !== -1) {
105 log('findCard: found by text match', candidates[i]); 106 return candidates[i]; 107 } 108 // Also match by href containing "createaccount" 109 var href = (candidates[i].getAttribute('href') || '').toLowerCase(); 110 if (href.indexOf('createaccount') !== -1) { 111 log('findCard: found by href match', candidates[i]); 112 return candidates[i]; 113 } 114 } 115 116 log('findCard: not found. Total <a> on page:', document.querySelectorAll('a').length); 117 return null; 118 } 119 120 // ââ Position a catcher strip ââââââââââââââââââââââââââââââââââââââââââââââ 121 function posStrip(el, left, top, width, height) { 122 el.style.left = left + 'px'; 123 el.style.top = top + 'px'; 124 el.style.width = Math.max(0, width) + 'px'; 125 el.style.height = Math.max(0, height) + 'px'; 126 } 127 128 // ââ Reposition overlay to match card ââââââââââââââââââââââââââââââââââââââ 129 function reposition() { 130 if (!cardEl || !pulseEl) return; 131 var r = cardEl.getBoundingClientRect(); 132 var x = r.left, y = r.top, w = r.width, h = r.height; 133 var vw = window.innerWidth, vh = window.innerHeight; 134 135 // Pulse root over the card 136 pulseEl.style.left = x + 'px'; 137 pulseEl.style.top = y + 'px'; 138 pulseEl.style.width = w + 'px'; 139 pulseEl.style.height = h + 'px'; 140 141 // 4 catcher strips â viewport minus card rect (card area has no strip â clicks pass through) 142 posStrip(strips[0], 0, 0, vw, y); // top 143 posStrip(strips[1], 0, y + h, vw, vh - y - h); // bottom 144 posStrip(strips[2], 0, y, x, h); // left 145 posStrip(strips[3], x + w, y, vw - x - w, h); // right 146 } 147 148 // ââ Build overlay âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 149 function setup() { 150 if (setupDone) return; 151 setupDone = true; 152 153 cardEl = findCard(); 154 if (!cardEl) { log('card not found'); return; } 155 log('card found', cardEl); 156 157 startTime = Date.now(); 158 dismissed = false; 159 160 // Root container 161 rootEl = document.createElement('div'); 162 rootEl.id = 'trywhm-hl-root'; 163 rootEl.setAttribute('role', 'status'); 164 rootEl.setAttribute('aria-live', 'polite'); 165 166 // 4 catcher strips 167 strips = [0, 1, 2, 3].map(function () { 168 var s = document.createElement('div'); 169 s.className = 'trywhm-hl-catcher'; 170 s.addEventListener('click', function () { dismiss('outside_click'); }); 171 rootEl.appendChild(s); 172 return s; 173 }); 174 175 // Pulse root (sits over the card) 176 pulseEl = document.createElement('div'); 177 pulseEl.className = 'trywhm-hl-pulse-root'; 178 179 // 3 rings 180 for (var i = 1; i <= 3; i++) { 181 var ring = document.createElement('div'); 182 ring.className = 'trywhm-hl-ring trywhm-hl-ring-' + i; 183 pulseEl.appendChild(ring); 184 } 185 186 // Chip with dot + label + close button 187 var chip = document.createElement('div'); 188 chip.className = 'trywhm-hl-chip'; 189 190 var dot = document.createElement('span'); 191 dot.className = 'trywhm-hl-chip-dot'; 192 193 var label = document.createElement('span'); 194 label.className = 'trywhm-hl-chip-text'; 195 label.textContent = 'Start here'; 196 197 var closeBtn = document.createElement('button'); 198 closeBtn.className = 'trywhm-hl-chip-close'; 199 closeBtn.setAttribute('aria-label', 'ÐакÑÑÑÑ Ð¿Ð¾Ð´ÑказкÑ'); 200 closeBtn.setAttribute('tabindex', '0'); 201 closeBtn.textContent = 'â'; 202 closeBtn.addEventListener('click', function (e) { 203 e.stopPropagation();
204 dismiss('chip_x'); 205 }); 206 207 chip.appendChild(dot); 208 chip.appendChild(label); 209 chip.appendChild(closeBtn); 210 pulseEl.appendChild(chip); 211 rootEl.appendChild(pulseEl); 212 document.body.appendChild(rootEl); 213 214 // Click on the card itself (capture so it fires before anchor navigation) 215 cardEl.addEventListener('click', onCardClick, true); 216 217 // Keyboard 218 document.addEventListener('keydown', onKeydown); 219 220 // Reposition on resize/scroll 221 window.addEventListener('resize', reposition, { passive: true }); 222 window.addEventListener('scroll', reposition, { passive: true, capture: true }); 223 224 reposition(); 225 226 var r = cardEl.getBoundingClientRect(); 227 track('highlight_shown', { 228 viewport: { w: window.innerWidth, h: window.innerHeight }, 229 target_rect: { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) } 230 }); 231 } 232 233 // ââ Event handlers ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 234 function onCardClick() { 235 track('target_clicked', { time_to_click_ms: Date.now() - startTime }); 236 dismiss('target_clicked'); 237 // No preventDefault â native <a> navigation must fire after this handler 238 } 239 240 function onKeydown(e) { 241 if (e.key === 'Escape') dismiss('escape'); 242 } 243 244 // ââ Dismiss with fade-out âââââââââââââââââââââââââââââââââââââââââââââââââ 245 function dismiss(reason) { 246 if (dismissed || !rootEl) return; 247 dismissed = true; 248 log('dismiss, reason=', reason); 249 250 track('highlight_dismissed', { 251 reason: reason, 252 time_visible_ms: Date.now() - startTime 253 }); 254 255 storageSet(); 256 257 rootEl.classList.add('trywhm-hl-fade-out'); 258 var el = rootEl; 259 setTimeout(function () { 260 if (el && el.parentNode) el.parentNode.removeChild(el); 261 }, 220); 262 263 // Clean up listeners 264 document.removeEventListener('keydown', onKeydown); 265 window.removeEventListener('resize', reposition); 266 window.removeEventListener('scroll', reposition, { capture: true }); 267 if (cardEl) cardEl.removeEventListener('click', onCardClick, true); 268 269 rootEl = null; 270 } 271 272 // ââ Boot ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 273 // The page uses Angular web components that initialize asynchronously after 274 // DOMContentLoaded. We poll with rAF until the card appears with non-zero 275 // dimensions (layout complete), up to 8 seconds. This handles both fast 276 // and slow-loading environments without a fixed setTimeout guess. 277 function waitForCard() { 278 var deadline = Date.now() + 8000; 279 280 function poll() { 281 if (setupDone) return; 282 var el = findCard(); 283 if (el) { 284 var r = el.getBoundingClientRect(); 285 if (r.width > 0 && r.height > 0) { 286 log('card visible, running setup'); 287 setup(); 288 return; 289 } 290 log('card in DOM but zero-size, retrying...'); 291 } else { 292 log('card not in DOM yet, retrying...'); 293 } 294 if (Date.now() < deadline) { 295 requestAnimationFrame(poll); 296 } else { 297 log('timed out waiting for card'); 298 } 299 } 300 301 requestAnimationFrame(poll); 302 } 303 304 if (document.readyState === 'loading') { 305 document.addEventListener('DOMContentLoaded', waitForCard, { once: true }); 306 } else { 307 waitForCard(); 308 } 309 310}());
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.