PageSourceSearch

https://creativestrategies.com/assets/js/navigation-prefetch.js?v=208da23294

js creativestrategies.com collected 2026-09-24 18:54:09 UTC 8,661 bytes, 164 lines download raw bytes

1const publicPages = new Set([
2    '/', '/research/', '/team/', '/about-creative-strategies/', '/analysis-services/',
3    '/market-intelligence/', '/strategic-investement-insights-program/',
4    '/strategic-investment-insights-program/', '/creative-strategies-history-in-silicon-valley/',
5    '/delight-scale/', '/what-is-the-delight-scale/', '/why-use-the-delight-scale/'
6]);
7const reservedSegments = new Set([
8    'ghost', 'admin', 'wp-admin', 'wp-json', 'api', 'members', 'member', 'account',
9    'accounts', 'portal', 'signin', 'sign-in', 'signup', 'sign-up', 'login', 'logout',
10    'signout', 'sign-out', 'register', 'subscribe', 'unsubscribe', 'subscription',
11    'subscriptions', 'membership', 'checkout', 'billing', 'payment', 'payments',
12    'auth', 'authentication', 'reset-password', 'forgot-password', 'preview', 'private',
13    'rss', 'feed', 'feeds', 'index', 'research-index', 'search-index', 'search',
14    'contact', 'contact-us', 'newsletter', 'join-the-newsletter', 'form', 'forms',
15    'download', 'downloads', 'cdn-cgi', 'content', 'assets', 'sitemap', 'sitemaps'
16]);
17const optOutSelector = '[data-no-prefetch], [data-prefetch="false"], [data-no-speculation], .no-prefetch, .do-not-prefetch, [data-portal], [data-ghost-search], [hidden], [inert], [aria-hidden="true"], form';
18const dwellMs = 200;
19const maxPrefetches = 4;
20const maxActivePrefetches = 2;
21const lifetimeMs = 30_000;
22const initialized = new WeakMap();
23
24export function publicNavigationUrl(link, location) {
25    if (!link || link.isConnected === false || link.hasAttribute('download') || link.closest(optOutSelector)) return null;
26    const target = (link.getAttribute('target') || '').toLowerCase();
27    const rel = (link.getAttribute('rel') || '').toLowerCase().split(/\s+/);
28    if ((target && target !== '_self') || rel.some(value => value === 'external' || value === 'nofollow')) return null;
29    const href = link.getAttribute('href');
30    if (!href || /[?#%\\\s]/.test(href)) return null;
31
32    let url;
33    let current;
34    try {
35        current = new URL(location.href);
36        url = new URL(href, link.baseURI || current);
37    } catch { return null; }
38    if (!['http:', 'https:'].includes(url.protocol) || url.origin !== current.origin ||
39        url.username || url.password || url.search || url.hash) return null;
40
41    const path = url.pathname.endsWith('/') ? url.pathname : `${url.pathname}/`;
42    const currentPath = current.pathname.endsWith('/') ? current.pathname : `${current.pathname}/`;
43    if (path === currentPath || !/^\/(?:[a-z0-9-]+\/)*$/.test(path)) return null;
44    const segments = path.split('/').filter(Boolean);
45    if (segments.some(segment => reservedSegments.has(segment))) return null;
46    const researchPage = /^\/research\/(?:[a-z0-9-]+\/|page\/[1-9]\d*\/)$/.test(path);
47    const legacyArticle = segments.length === 1 &&
48        Boolean(link.closest('[data-research-source="cs"]') || link.matches('a.report-card'));
49    return publicPages.has(path) || researchPage || legacyArticle ? url.href : null;
50}
51
52export function canWarmNavigation(document, navigator = {}) {
53    const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
54    return document.visibilityState !== 'hidden' && !document.prerendering && navigator.onLine !== false &&
55        !connection?.saveData && !['slow-2g', '2g', '3g'].includes(connection?.effectiveType) &&
56        !(connection?.downlink > 0 && connection.downlink < 1.5);
57}
58
59export function initializeNavigationPrefetch(document, window) {
60    if (initialized.has(document)) return initialized.get(document);
61    const prefetched = new Set();
62    const active = new Map();
63    const pending = new Map();
64    const listeners = [];
65    let researchIntent = false;
66    const supportsPrefetch = window.HTMLScriptElement?.supports?.('speculationrules') === true;
67
68    function removePrefetch(url) {
69        const entry = active.get(url);
70        if (!entry) return;
71        window.clearTimeout(entry.timer);
72        entry.script.remove();
73        active.delete(url);
74    }
75
76    function cancelPending(source) {
77        window.clearTimeout(pending.get(source)?.timer);
78        pending.delete(source);
79    }
80
81    function cancelAll() {
82        for (const source of pending.keys()) cancelPending(source);
83        for (const url of active.keys()) removePrefetch(url);
84    }
85
86    function approach(link, source) {
87        if (!canWarmNavigation(document, window.navigator)) return;
88        const url = publicNavigationUrl(link, window.location);
89        if (!url) return;
90        if (!researchIntent && new URL(url).pathname.replace(/\/$/, '') === '/research') {
91            researchIntent = true;
92            document.dispatchEvent(new window.CustomEvent('cs:research-intent', {detail: {url, source}}));
93        }
94        if (!supportsPrefetch || prefetched.has(url) || prefetched.size >= maxPrefetches) return;
95        if (active.size >= maxActivePrefetches) removePrefetch(active.keys().next().value);
96
97        const script = document.createElement('script');
98        script.type = 'speculationrules';
99        script.dataset.csNavigationPrefetch = '';
100        // This one-URL rule is inserted only after sustained hover/focus or a primary press.
101        // Prefetch downloads HTML without running the destination's analytics or member scripts.
102        script.textContent = JSON.stringify({prefetch: [{urls: [url], eagerness: 'immediate'}]});
103        document.head.appendChild(script);
104        prefetched.add(url);
105        active.set(url, {script, timer: window.setTimeout(() => removePrefetch(url), lifetimeMs)});
106    }
107
108    function anchor(target) { return target?.closest?.('a[href]'); }
109    function permittedEvent(event) {
110        return event.isTrusted !== false && !event.defaultPrevented && !event.altKey && !event.ctrlKey &&
111            !event.metaKey && !event.shiftKey && event.isPrimary !== false && !(event.button > 0);
112    }
113    function schedule(event, source) {
114        if (!permittedEvent(event) || !canWarmNavigation(document, window.navigator)) return;
115        if (source === 'hover' && event.pointerType === 'touch') return;
116        const link = anchor(event.target);
117        if (!link || link === anchor(event.relatedTarget) || pending.get(source)?.link === link) return;
118        cancelPending(source);
119        if (!publicNavigationUrl(link, window.location)) return;
120        const timer = window.setTimeout(() => {
121            pending.delete(source);
122            approach(link, source);
123        }, dwellMs);
124        pending.set(source, {link, timer});
125    }
126    function depart(event, source) {
127        const link = anchor(event.target);
128        if (pending.get(source)?.link === link && link !== anchor(event.relatedTarget)) cancelPending(source);
129    }
130    function press(event) {
131        if (!permittedEvent(event) || event.touches?.length > 1) return;
132        const link = anchor(event.target);
133        for (const source of pending.keys()) cancelPending(source);
134        approach(link, event.pointerType === 'touch' || event.type === 'touchstart' ? 'touch' : 'pointer');
135    }
136    function listen(target, type, listener) {
137        target.addEventListener(type, listener, {passive: true});
138        listeners.push(() => target.removeEventListener(type, listener));
139    }
140
141    listen(document, window.PointerEvent ? 'pointerover' : 'mouseover', event => schedule(event, 'hover'));
142    listen(document, window.PointerEvent ? 'pointerout' : 'mouseout', event => depart(event, 'hover'));
143    listen(document, 'focusin', event => schedule(event, 'focus'));
144    listen(document, 'focusout', event => depart(event, 'focus'));
145    listen(document, window.PointerEvent ? 'pointerdown' : 'mousedown', press);
146    if (!window.PointerEvent) listen(document, 'touchstart', press);
147    listen(document, 'visibilitychange', () => { if (!canWarmNavigation(document, window.navigator)) cancelAll(); });
148    const connection = window.navigator?.connection || window.navigator?.mozConnection || window.navigator?.webkitConnection;
149    if (connection?.addEventListener) listen(connection, 'change', () => {
150        if (!canWarmNavigation(document, window.navigator)) cancelAll();
151    });
152    listen(window, 'pagehide', () => { cancelAll(); prefetched.clear(); });
153    listen(window, 'pageshow', event => { if (event.persisted) researchIntent = false; });
154
155    const destroy = () => {
156        cancelAll();
157        listeners.forEach(remove => remove());
158        initialized.delete(document);
159    };
160    initialized.set(document, destroy);
161    return destroy;
162}
163
164if (typeof document !== 'undefined' && typeof window !== 'undefined') initializeNavigationPrefetch(document, window);

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.