PageSourceSearch

https://trywhm.net/cpsess0000000000/trywhm-hl.js

js trywhm.net collected 2026-10-05 22:48:40 UTC 13,230 bytes, 310 lines download raw bytes

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.