PageSourceSearch

https://www.theusualnext.com/assets/js/content-engagement.js?v=1787402893

js theusualnext.com collected 2026-09-24 09:06:35 UTC 23,667 bytes, 482 lines download raw bytes

1(() => {
2    'use strict';
3
4    if (typeof window.tunAnalyticsTrack !== 'function') return;
5
6    const main = document.querySelector('main');
7    if (!main) return;
8
9    const schemaVersion = 'content_v2';
10    const registered = new WeakSet();
11    const videoBound = new WeakSet();
12    const targets = new Map();
13    const visibleTargets = new Set();
14    const heatmapMilestones = new Set([2, 5, 10, 20, 40, 60, 120]);
15    const downloadExtensionPattern = /\.(?:7z|apk|appimage|csv|deb|dmg|docx?|exe|gz|ino|ipa|iso|msi|odp|ods|odt|pdf|pkg|pptx?|rar|rpm|tar|tgz|txt|xlsx?|zip)$/i;
16    const isMeasuredScope = (element) => element.closest('main') === main
17        || Boolean(element.closest('[data-analytics-sticky]'));
18
19    const safeKey = (value, fallback) => {
20        const normalized = String(value || '')
21            .toLowerCase()
22            .normalize('NFD')
23            .replace(/[\u0300-\u036f]/g, '')
24            .replace(/[^a-z0-9]+/g, '_')
25            .replace(/^_+|_+$/g, '')
26            .slice(0, 80);
27
28        return normalized || fallback;
29    };
30    const track = (name, target, properties = {}) => window.tunAnalyticsTrack(name, {
31        analytics_schema_version: schemaVersion,
32        interaction_target: target,
33        ...properties,
34    });
35    const mediaKey = (element, fallback) => {
36        const explicit = element.dataset.analyticsMedia
37            || element.closest?.('[data-analytics-media]')?.dataset.analyticsMedia;
38        if (explicit) return safeKey(explicit, fallback);
39
40        const sourceElement = element.matches?.('img, video, iframe')
41            ? element
42            : element.querySelector?.('img, video, iframe');
43        try {
44            const source = sourceElement?.currentSrc
45                || sourceElement?.src
46                || sourceElement?.getAttribute?.('src')
47                || getComputedStyle(element).backgroundImage.match(/url\(["']?([^"')]+)["']?\)/)?.[1]
48                || '';
49            const filename = new URL(source, window.location.href).pathname.split('/').pop()?.replace(/\.[a-z0-9]+$/i, '');
50            return safeKey(filename, fallback);
51        } catch (_error) {
52            return fallback;
53        }
54    };
55    const sectionKey = (section, index) => {
56        const heading = section.querySelector(':scope > h1, :scope > h2, :scope > h3, :scope header h1, :scope header h2, :scope header h3');
57        const labelledBy = section.getAttribute('aria-labelledby');
58        const label = section.dataset.analyticsSection
59            || section.id
60            || (labelledBy ? document.getElementById(labelledBy)?.textContent : '')
61            || heading?.textContent;
62
63        return `${String(index + 1).padStart(2, '0')}_${safeKey(label, 'section')}`.slice(0, 80);
64    };
65    const clampPercent = (value) => Math.max(0, Math.min(100, Math.round(value * 100) / 100));
66    const downloadContext = (link, url) => {
67        const container = link.closest?.('[data-download-kind], [data-download-product-key], [data-download-asset-key]');
68        const source = { ...container?.dataset, ...link.dataset };
69        const filename = decodeURIComponent(url.pathname.split('/').pop() || '').slice(0, 120);
70        const extension = filename.includes('.') ? filename.split('.').pop().toLowerCase().slice(0, 12) : '';
71        const pagePath = window.location.pathname;
72        const inferredKind = /\/apps(?:\/|$)/.test(pagePath) || ['apk', 'appimage', 'deb', 'dmg', 'exe', 'ipa', 'msi', 'pkg', 'rpm'].includes(extension)
73            ? 'software'
74            : /\/lab\/(?:pubblicazioni|papers)(?:\/|$)/.test(pagePath) || container?.classList?.contains('paper-record')
75                ? 'paper'
76                : extension === 'ino'
77                    ? 'firmware'
78                    : 'file';
79        const versionMatch = filename.match(/(?:^|[-_\s])v?(\d+(?:\.\d+){1,3}(?:[-_]\d+)?)(?=[-_.\s]|$)/i);
80        const productKey = source.downloadProductKey || '';
81        const assetName = source.downloadAssetName || source.downloadProductName || filename.replace(/\.[^.]+$/, '') || 'download';
82
83        return {
84            asset_kind: safeKey(source.downloadKind || inferredKind, 'file'),
85            asset_key: safeKey(source.downloadAssetKey || productKey || filename, 'download'),
86            asset_name: String(assetName).slice(0, 120),
87            asset_version: String(source.downloadVersion || versionMatch?.[1] || '').slice(0, 40),
88            asset_host: url.hostname.slice(0, 120),
89            asset_path: url.pathname.slice(0, 240),
90            file_extension: extension,
91        };
92    };
93    const elementPath = (element) => {
94        const parts = [];
95        let current = element;
96
97        while (current && current !== document.body && parts.length < 5) {
98            const tag = current.tagName?.toLowerCase();
99            if (!tag) break;
100
101            if (current.id) {
102                parts.unshift(`${tag}_${safeKey(current.id, 'element')}`);
103                break;
104            }
105
106            const siblings = current.parentElement
107                ? [...current.parentElement.children].filter((sibling) => sibling.tagName === current.tagName)
108                : [];
109            const position = siblings.length > 1 ? `_${siblings.indexOf(current) + 1}` : '';
110            parts.unshift(`${tag}${position}`);
111            current = current.parentElement;
112
113            if (current?.tagName?.toLowerCase() === 'main') break;
114        }
115
116        return parts.join('_');
117    };
118    const elementKey = (element) => {
119        const explicit = element.closest?.('[data-analytics-action], [data-analytics-media], [data-commerce-target], [data-cf-target]');
120        const explicitValue = explicit?.dataset.analyticsAction
121            || explicit?.dataset.analyticsMedia
122            || explicit?.dataset.commerceTarget
123            || explicit?.dataset.cfTarget;
124
125        if (explicitValue) return `${explicit.tagName.toLowerCase()}_${safeKey(explicitValue, 'element')}`.slice(0, 120);
126
127        const interactive = element.closest?.('a[href], button, input, select, textarea, summary, [role="button"]');
128        if (interactive?.id) return `${interactive.tagName.toLowerCase()}_${safeKey(interactive.id, 'element')}`.slice(0, 120);
129
130        const link = interactive?.matches?.('a[href]') ? interactive : null;
131        if (link) {
132            try {
133                const destination = new URL(link.getAttribute('href') || '', window.location.href);
134                const target = destination.origin === window.location.origin ? destination.pathname : destination.hostname;
135                return `a_${safeKey(target, 'link')}_${safeKey(elementPath(link), 'position')}`.slice(0, 120);
136            } catch (_error) {
137                return `a_link_${safeKey(elementPath(link), 'position')}`.slice(0, 120);
138            }
139        }
140
141        if (interactive) return safeKey(elementPath(interactive), interactive.tagName.toLowerCase()).slice(0, 120);
142
143        const section = element.closest?.('[data-analytics-section]');
144        if (section?.dataset.analyticsSection) {
145            return `section_${safeKey(section.dataset.analyticsSection, 'section')}`.slice(0, 120);
146        }
147
148        const identified = element.closest?.('[id]');
149        if (identified?.id) return `${identified.tagName.toLowerCase()}_${safeKey(identified.id, 'element')}`.slice(0, 120);
150
151        return safeKey(elementPath(element), element.tagName?.toLowerCase() || 'element').slice(0, 120);
152    };
153    const geometry = (element, point = null) => {
154        const rect = element.getBoundingClientRect();
155        const pageWidth = Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth || 0, window.innerWidth, 1);
156        const pageHeight = Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight || 0, window.innerHeight, 1);
157        const clientX = Number.isFinite(point?.clientX) && point.clientX > 0 ? point.clientX : rect.left + (rect.width / 2);
158        const clientY = Number.isFinite(point?.clientY) && point.clientY > 0 ? point.clientY : rect.top + (rect.height / 2);
159
160        return {
161            element_x_percent: clampPercent(((clientX - rect.left) / Math.max(rect.width, 1)) * 100),
162            element_y_percent: clampPercent(((clientY - rect.top) / Math.max(rect.height, 1)) * 100),
163            page_x_percent: clampPercent(((clientX + window.scrollX) / pageWidth) * 100),
164            page_y_percent: clampPercent(((clientY + window.scrollY) / pageHeight) * 100),
165            viewport_width: Math.round(window.innerWidth),
166            viewport_height: Math.round(window.innerHeight),
167            page_width: Math.round(pageWidth),
168            page_height: Math.round(pageHeight),
169        };
170    };
171
172    const attentionObserver = 'IntersectionObserver' in window
173        ? new IntersectionObserver((entries) => {
174            entries.forEach((entry) => {
175                const requiredPixels = Math.min(120, entry.boundingClientRect.height * .25);
176                if (entry.isIntersecting && entry.intersectionRect.height >= requiredPixels && document.visibilityState !== 'hidden') {
177                    visibleTargets.add(entry.target);
178                } else {
179                    visibleTargets.delete(entry.target);
180                }
181            });
182        }, { threshold: [0, .25, .5] })
183        : null;
184
185    const registerAttention = (element, key, type, milestones, properties = {}) => {
186        if (registered.has(element)) return;
187
188        registered.add(element);
189        element.dataset.analyticsMeasured = 'true';
190        targets.set(element, { key, type, milestones, properties, seconds: 0, sent: new Set() });
191        attentionObserver?.observe(element);
192    };
193    const bindVideo = (video, target) => {
194        if (videoBound.has(video)) return;
195        videoBound.add(video);
196
197        const milestones = new Set();
198        video.addEventListener('play', () => {
199            if (milestones.has('played')) return;
200            milestones.add('played');
201            track('video_played', target, { content_type: 'video' });
202        });
203        video.addEventListener('timeupdate', () => {
204            if (!Number.isFinite(video.duration) || video.duration <= 0) return;
205            const progress = Math.floor((video.currentTime / video.duration) * 100);
206            [25, 50, 75].forEach((milestone) => {
207                if (progress < milestone || milestones.has(milestone)) return;
208                milestones.add(milestone);
209                track('video_progress', target, { content_type: 'video', percent: milestone });
210            });
211        });
212        video.addEventListener('ended', () => {
213            if (milestones.has(100)) return;
214            milestones.add(100);
215            track('video_progress', target, { content_type: 'video', percent: 100 });
216        });
217    };
218
219    const registerTree = (root) => {
220        if (!(root instanceof Element)) return;
221        const query = (selector) => [
222            ...(root.matches(selector) ? [root] : []),
223            ...root.querySelectorAll(selector),
224        ];
225
226        const sections = query('section, article[id], [data-analytics-section]')
227            .filter((section, index, list) => isMeasuredScope(section) && list.indexOf(section) === index);
228        const allSections = [...main.querySelectorAll('section, article[id], [data-analytics-section]')];
229        sections.forEach((section) => {
230            const key = sectionKey(section, Math.max(0, allSections.indexOf(section)));
231            section.dataset.analyticsSection ||= key;
232            registerAttention(section, key, 'section', [
233                { name: 'content_section_viewed', seconds: 2 },
234                { name: 'content_section_engaged', seconds: 8 },
235            ]);
236        });
237
238        const candidates = query('img:not([data-analytics-ignore]), video, iframe[src*="youtube"], iframe[src*="youtu.be"], iframe[src*="vimeo"], [data-analytics-media]');
239        const mediaElements = [...new Set(candidates.map((candidate) => candidate.closest('[data-analytics-media]') || candidate))]
240            .filter((element) => isMeasuredScope(element));
241
242        mediaElements.forEach((element, index) => {
243            if (registered.has(element)) return;
244            const nativeVideo = element.matches('video') ? element : element.querySelector('video');
245            const embeddedVideo = element.matches('iframe') ? element : element.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"], iframe[src*="vimeo"]');
246            const image = element.matches('img') ? element : element.querySelector('img');
247            const type = nativeVideo ? 'video' : embeddedVideo ? 'embedded_video' : 'image';
248
249            if (type === 'image' && !element.dataset.analyticsMedia) {
250                const box = (image || element).getBoundingClientRect();
251                if (box.width < 160 || box.height < 100) {
252                    image?.addEventListener('load', () => registerTree(element), { once: true });
253                    return;
254                }
255            }
256
257            const key = mediaKey(element, `${type}_${index + 1}`);
258            element.dataset.analyticsMedia ||= key;
259            registerAttention(element, key, type, type === 'embedded_video'
260                ? [{ name: 'video_embed_viewed', seconds: 2 }]
261                : type === 'image'
262                    ? [{ name: 'image_viewed', seconds: 2 }, { name: 'image_engaged', seconds: 8 }]
263                    : []);
264            if (nativeVideo) bindVideo(nativeVideo, key);
265        });
266
267        query('a[href], button, [role="button"], [data-inquiry-open], [data-lightbox-item]')
268            .filter((element) => isMeasuredScope(element))
269            .forEach((element) => {
270                element.dataset.analyticsAction ||= elementKey(element);
271                let actionRole = element.matches('[data-inquiry-open], [data-commerce-target], [data-cf-target]')
272                    ? 'commercial'
273                    : 'navigation';
274                const href = element.getAttribute('href') || '';
275                if (href) {
276                    try {
277                        const destination = new URL(href, window.location.href);
278                        const isDirectContact = ['mailto:', 'tel:'].includes(destination.protocol)
279                            || /wa\.me|whatsapp\.com|apps\.apple\.com|play\.google\.com/i.test(destination.hostname);
280                        const isDownload = element.hasAttribute('download')
281                            || element.hasAttribute('data-analytics-download')
282                            || downloadExtensionPattern.test(destination.pathname);
283                        const isCommercialPath = destination.origin === window.location.origin
284                            && destination.pathname !== window.location.pathname
285                            && !href.startsWith('#');
286                        if (isDirectContact || isDownload || isCommercialPath) actionRole = 'commercial';
287                    } catch (_error) {
288                        actionRole = 'navigation';
289                    }
290                }
291                registerAttention(element, element.dataset.analyticsAction, 'action', [
292                    { name: 'content_action_viewed', seconds: 2 },
293                ], { action_role: actionRole });
294            });
295    };
296
297    registerTree(main);
298    document.querySelectorAll('[data-analytics-sticky]').forEach(registerTree);
299
300    const pageType = main.dataset.growthPageType || (/^\/(?:it|en-it|it-ch|fr-ch|de-ch|en-ch)\/?$/.test(window.location.pathname)
301        ? 'homepage'
302        : document.querySelector('[data-inquiry-form], [data-inquiry-open], [data-commerce-surface]')
303            ? 'commercial'
304            : 'content');
305    track('content_page_view', pageType, {
306        page_type: pageType,
307        section_count: main.querySelectorAll('[data-analytics-section]').length,
308        image_count: main.querySelectorAll('img').length,
309        video_count: main.querySelectorAll('video, iframe[src*="youtube"], iframe[src*="youtu.be"], iframe[src*="vimeo"]').length,
310    });
311    track('heatmap_page_ready', pageType, {
312        page_type: pageType,
313        measured_element_count: targets.size,
314    });
315
316    const dynamicContentObserver = new MutationObserver((mutations) => {
317        mutations.forEach((mutation) => {
318            mutation.addedNodes.forEach((node) => {
319                if (node instanceof Element) registerTree(node);
320            });
321        });
322    });
323    dynamicContentObserver.observe(main, { childList: true, subtree: true });
324
325    const heatmapTimer = window.setInterval(() => {
326        if (document.visibilityState === 'hidden') return;
327
328        visibleTargets.forEach((element) => {
329            const state = targets.get(element);
330            if (!state) return;
331
332            state.seconds += 1;
333            state.milestones.forEach(({ name, seconds }) => {
334                if (state.seconds !== seconds || state.sent.has(name)) return;
335                state.sent.add(name);
336                track(name, state.key, { content_type: state.type, engagement_seconds: seconds, ...state.properties });
337            });
338
339            if (heatmapMilestones.has(state.seconds)) {
340                track('heatmap_attention', state.key, {
341                    content_type: state.type,
342                    interval_seconds: state.seconds,
343                    ...geometry(element),
344                });
345            }
346        });
347    }, 1000);
348
349    let lastClick = null;
350    document.addEventListener('click', (event) => {
351        const element = event.target.closest?.('main *, [data-analytics-sticky], [data-analytics-sticky] *');
352        if (!element) return;
353
354        const key = elementKey(element);
355        const now = window.performance.now();
356        const clientX = Number.isFinite(event.clientX) ? event.clientX : 0;
357        const clientY = Number.isFinite(event.clientY) ? event.clientY : 0;
358        const distance = lastClick ? Math.hypot(clientX - lastClick.clientX, clientY - lastClick.clientY) : Number.POSITIVE_INFINITY;
359        const sameSequence = lastClick && now - lastClick.time <= 800 && (lastClick.key === key || distance <= 48);
360        const sequence = sameSequence ? lastClick.sequence + 1 : 1;
361        lastClick = { time: now, clientX, clientY, key, sequence };
362
363        const interactive = Boolean(element.closest('a[href], button, input, select, textarea, summary, [role="button"], [data-inquiry-open], [data-lightbox-item]'));
364        track('heatmap_click', key, {
365            element_tag: element.tagName.toLowerCase(),
366            click_kind: interactive ? 'interactive' : 'dead',
367            click_sequence: sequence,
368            rapid_click: sequence >= 3,
369            ...geometry(element, event),
370        });
371    });
372
373    let maximumScrollDepth = 0;
374    const updateMaximumScrollDepth = () => {
375        const scrollableHeight = document.documentElement.scrollHeight - window.innerHeight;
376        maximumScrollDepth = scrollableHeight <= 0
377            ? 100
378            : Math.max(maximumScrollDepth, clampPercent((window.scrollY / scrollableHeight) * 100));
379    };
380    let scrollScheduled = false;
381    window.addEventListener('scroll', () => {
382        if (scrollScheduled) return;
383        scrollScheduled = true;
384        window.requestAnimationFrame(() => {
385            scrollScheduled = false;
386            updateMaximumScrollDepth();
387        });
388    }, { passive: true });
389    window.addEventListener('pagehide', () => {
390        updateMaximumScrollDepth();
391        track('heatmap_scroll', pageType, {
392            max_scroll_percent: maximumScrollDepth,
393            viewport_width: Math.round(window.innerWidth),
394            viewport_height: Math.round(window.innerHeight),
395            page_width: Math.round(Math.max(document.documentElement.scrollWidth, window.innerWidth)),
396            page_height: Math.round(Math.max(document.documentElement.scrollHeight, window.innerHeight)),
397        });
398        window.clearInterval(heatmapTimer);
399        dynamicContentObserver.disconnect();
400        attentionObserver?.disconnect();
401    }, { once: true });
402    updateMaximumScrollDepth();
403
404    document.addEventListener('click', (event) => {
405        const link = event.target.closest?.('main a[href], main button[data-inquiry-open], main [data-lightbox-item], [data-analytics-sticky] a[href], [data-analytics-sticky] button[data-inquiry-open], [data-analytics-sticky] [data-lightbox-item]');
406        if (!link) return;
407
408        const href = link.getAttribute('href') || '';
409        let kind = link.matches('[data-inquiry-open]') ? 'inquiry' : 'action';
410        let target = link.dataset.analyticsAction || link.dataset.cfTarget || link.dataset.commerceTarget || '';
411        let destinationPath = '';
412
413        if (href) {
414            try {
415                const url = new URL(href, window.location.href);
416                destinationPath = url.origin === window.location.origin ? url.pathname : '';
417                if (url.protocol === 'mailto:') {
418                    kind = 'email';
419                    target ||= 'email';
420                } else if (url.protocol === 'tel:') {
421                    kind = 'phone';
422                    target ||= 'phone';
423                } else if (/wa\.me$|whatsapp\.com$/i.test(url.hostname)) {
424                    kind = 'whatsapp';
425                    target ||= 'whatsapp';
426                } else if (/apps\.apple\.com$|play\.google\.com$/i.test(url.hostname)) {
427                    kind = 'store';
428                    target ||= url.hostname;
429                } else if (link.hasAttribute('download')
430                    || link.hasAttribute('data-analytics-download')
431                    || downloadExtensionPattern.test(url.pathname)) {
432                    kind = 'download';
433                    target ||= url.pathname;
434                    destinationPath = url.pathname;
435                } else if (url.origin !== window.location.origin) {
436                    kind = 'external';
437                    target ||= url.hostname;
438                } else {
439                    kind = 'internal';
440                    target ||= url.pathname;
441                }
442            } catch (_error) {
443                target ||= 'action';
444            }
445        }
446
447        const actionTarget = safeKey(target, 'action');
448        const internalCommercialPath = kind === 'internal'
449            && destinationPath
450            && destinationPath !== window.location.pathname
451            && !href.startsWith('#');
452        const actionRole = ['email', 'phone', 'whatsapp', 'inquiry', 'download', 'store'].includes(kind)
453            || Boolean(link.dataset.commerceTarget || link.dataset.cfTarget)
454            || internalCommercialPath
455            ? 'commercial'
456            : 'navigation';
457
458        track('content_action_clicked', actionTarget, {
459            action_type: kind,
460            action_role: actionRole,
461            destination_path: destinationPath,
462        });
463
464        if (kind === 'download') {
465            const url = new URL(href, window.location.href);
466            track('download_started', actionTarget, {
467                ...downloadContext(link, url),
468                delivery_channel: 'direct',
469                confirmation: 'click',
470            });
471        } else if (kind === 'store') {
472            const url = new URL(href, window.location.href);
473            track('store_opened', actionTarget, {
474                ...downloadContext(link, url),
475                asset_kind: 'software',
476                store_host: url.hostname,
477                delivery_channel: /play\.google\.com$/i.test(url.hostname) ? 'play_store' : 'app_store',
478                confirmation: 'navigation',
479            });
480        }
481    });
482})();

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.