PageSourceSearch

https://www.dynamicdreamz.com/wp-content/themes/dynamicdreamz/assets/js/lead-source-tracker.js?ver=1790361890

js dynamicdreamz.com collected 2026-09-25 18:45:41 UTC 38,288 bytes, 809 lines download raw bytes

1/**
2 * ============================================================================
3 * lead-source-tracker.js
4 * ============================================================================
5 *
6 * Zero-dependency, UTM-independent lead source attribution engine.
7 *
8 * @version 2.2.0
9 * @author  Dynamic Dreamz
10 * @license MIT
11 */
12(function (window, document) {
13    'use strict';
14
15    if (window.__DDZ_LEAD_SOURCE_TRACKER_LOADED__) {
16        return;
17    }
18    window.__DDZ_LEAD_SOURCE_TRACKER_LOADED__ = true;
19
20    // ------------------------------------------------------------------------
21    // 1. CONFIGURATION
22    // ------------------------------------------------------------------------
23
24    const CONFIG = Object.freeze({
25        VERSION: '2.2.0',
26        SESSION_KEY: 'ddz_visit_attribution',
27        LEGACY_STORAGE_KEY: 'ddz_lead_attribution',
28        DEBUG_NAMESPACE: 'DDZTracker',
29        CF7_FORM_SELECTOR: 'form.wpcf7-form, form[data-wpcf7]',
30        DIRECT_SOURCE: 'Direct',
31        DIRECT_MEDIUM: 'Direct'
32    });
33
34    const CF7_FIELD_NAMES = Object.freeze([
35        'utm_source',
36        'utm_medium',
37        'first_source',
38        'first_medium',
39        'referrer',
40        'landing_page',
41        'submission_page'
42    ]);
43
44    // ------------------------------------------------------------------------
45    // 2. PLATFORM DATABASE
46    // ------------------------------------------------------------------------
47
48    const REVIEW_DIRECTORIES = [
49        { domains: ['clutch.co'], source: 'Clutch', medium: 'Review Directory', category: 'Review' },
50        { domains: ['goodfirms.co'], source: 'GoodFirms', medium: 'Review Directory', category: 'Review' },
51        { domains: ['designrush.com'], source: 'DesignRush', medium: 'Review Directory', category: 'Review' },
52        { domains: ['trustpilot.com'], source: 'Trustpilot', medium: 'Review Directory', category: 'Review' },
53        { domains: ['themanifest.com'], source: 'Manifest', medium: 'Review Directory', category: 'Review' },
54        { domains: ['agencyspotter.com'], source: 'Agency Spotter', medium: 'Review Directory', category: 'Review' },
55        { domains: ['techbehemoths.com'], source: 'TechBehemoths', medium: 'Review Directory', category: 'Review' },
56        { domains: ['sortlist.com', 'sortlist.co.uk', 'sortlist.us'], source: 'Sortlist', medium: 'Review Directory', category: 'Review' },
57        { domains: ['topdevelopers.co'], source: 'TopDevelopers', medium: 'Review Directory', category: 'Review' },
58        { domains: ['extract.co'], source: 'Extract', medium: 'Review Directory', category: 'Review' },
59        { domains: ['selectedfirms.co'], source: 'SelectedFirms', medium: 'Review Directory', category: 'Review' },
60        { domains: ['itfirms.co'], source: 'IT Firms', medium: 'Review Directory', category: 'Review' },
61        { domains: ['superbcompanies.com'], source: 'SuperbCompanies', medium: 'Review Directory', category: 'Review' },
62        { domains: ['softwareworld.co'], source: 'SoftwareWorld', medium: 'Review Directory', category: 'Review' },
63        { domains: ['digitalagencynetwork.com'], source: 'Digital Agency Network', medium: 'Review Directory', category: 'Review' },
64        { domains: ['g2.com'], source: 'G2', medium: 'Review Directory', category: 'Review' },
65        { domains: ['capterra.com'], source: 'Capterra', medium: 'Review Directory', category: 'Review' },
66        { domains: ['bark.com'], source: 'Bark', medium: 'Review Directory', category: 'Review' },
67        { domains: ['upcity.com'], source: 'UpCity', medium: 'Review Directory', category: 'Review' },
68        { domains: ['getapp.com'], source: 'GetApp', medium: 'Review Directory', category: 'Review' },
69        { domains: ['trustradius.com'], source: 'TrustRadius', medium: 'Review Directory', category: 'Review' },
70        { domains: ['softwareadvice.com'], source: 'Software Advice', medium: 'Review Directory', category: 'Review' },
71        { domains: ['crozdesk.com'], source: 'Crozdesk', medium: 'Review Directory', category: 'Review' },
72        { domains: ['financesonline.com'], source: 'FinancesOnline', medium: 'Review Directory', category: 'Review' },
73        { domains: ['provenexpert.com'], source: 'ProvenExpert', medium: 'Review Directory', category: 'Review' },
74        { domains: ['threebestrated.com'], source: 'ThreeBestRated', medium: 'Review Directory', category: 'Review' },
75        { domains: ['yelp.com'], source: 'Yelp', medium: 'Review Directory', category: 'Review' },
76        { domains: ['bbb.org'], source: 'Better Business Bureau', medium: 'Review Directory', category: 'Review' },
77        { domains: ['angi.com'], source: 'Angi', medium: 'Review Directory', category: 'Review' },
78        { domains: ['homeadvisor.com'], source: 'HomeAdvisor', medium: 'Review Directory', category: 'Review' },
79        { domains: ['thumbtack.com'], source: 'Thumbtack', medium: 'Review Directory', category: 'Review' },
80        { domains: ['houzz.com'], source: 'Houzz', medium: 'Review Directory', category: 'Review' },
81        { domains: ['saashub.com'], source: 'SaaSHub', medium: 'Review Directory', category: 'Review' },
82        { domains: ['alternativeto.net'], source: 'AlternativeTo', medium: 'Review Directory', category: 'Review' },
83        { domains: ['slant.co'], source: 'Slant', medium: 'Review Directory', category: 'Review' },
84        { domains: ['stackshare.io'], source: 'StackShare', medium: 'Review Directory', category: 'Review' },
85        { domains: ['glassdoor.com'], source: 'Glassdoor', medium: 'Review Directory', category: 'Review' }
86    ];
87
88    const FREELANCE_PLATFORMS = [
89        { domains: ['upwork.com'], source: 'Upwork', medium: 'Freelance Marketplace', category: 'Freelance' },
90        { domains: ['fiverr.com'], source: 'Fiverr', medium: 'Freelance Marketplace', category: 'Freelance' },
91        { domains: ['freelancer.com'], source: 'Freelancer', medium: 'Freelance Marketplace', category: 'Freelance' },
92        { domains: ['peopleperhour.com'], source: 'PeoplePerHour', medium: 'Freelance Marketplace', category: 'Freelance' },
93        { domains: ['guru.com'], source: 'Guru', medium: 'Freelance Marketplace', category: 'Freelance' },
94        { domains: ['toptal.com'], source: 'Toptal', medium: 'Freelance Marketplace', category: 'Freelance' },
95        { domains: ['99designs.com'], source: '99designs', medium: 'Freelance Marketplace', category: 'Freelance' },
96        { domains: ['designcrowd.com'], source: 'DesignCrowd', medium: 'Freelance Marketplace', category: 'Freelance' },
97        { domains: ['behance.net'], source: 'Behance', medium: 'Portfolio Network', category: 'Freelance' },
98        { domains: ['dribbble.com'], source: 'Dribbble', medium: 'Portfolio Network', category: 'Freelance' },
99        { domains: ['flexjobs.com'], source: 'FlexJobs', medium: 'Job Board', category: 'Freelance' },
100        { domains: ['weworkremotely.com'], source: 'We Work Remotely', medium: 'Job Board', category: 'Freelance' },
101        { domains: ['remoteok.com'], source: 'RemoteOK', medium: 'Job Board', category: 'Freelance' },
102        { domains: ['wellfound.com'], source: 'Wellfound', medium: 'Job Board', category: 'Freelance' },
103        { domains: ['angel.co'], source: 'AngelList', medium: 'Job Board', category: 'Freelance' },
104        { domains: ['simplyhired.com'], source: 'SimplyHired', medium: 'Job Board', category: 'Freelance' },
105        { domains: ['indeed.com'], source: 'Indeed', medium: 'Job Board', category: 'Freelance' },
106        { domains: ['servicescape.com'], source: 'ServiceScape', medium: 'Freelance Marketplace', category: 'Freelance' },
107        { domains: ['contently.com'], source: 'Contently', medium: 'Freelance Marketplace', category: 'Freelance' }
108    ];
109
110    const AI_PLATFORMS = [
111        { domains: ['chat.openai.com', 'chatgpt.com'], source: 'ChatGPT', medium: 'AI Referral', category: 'AI' },
112        { domains: ['perplexity.ai'], source: 'Perplexity', medium: 'AI Referral', category: 'AI' },
113        { domains: ['claude.ai'], source: 'Claude', medium: 'AI Referral', category: 'AI' },
114        { domains: ['gemini.google.com'], source: 'Gemini', medium: 'AI Referral', category: 'AI' },
115        { domains: ['copilot.microsoft.com'], source: 'Microsoft Copilot', medium: 'AI Referral', category: 'AI' },
116        { domains: ['you.com'], source: 'You.com', medium: 'AI Referral', category: 'AI' },
117        { domains: ['phind.com'], source: 'Phind', medium: 'AI Referral', category: 'AI' },
118        { domains: ['poe.com'], source: 'Poe', medium: 'AI Referral', category: 'AI' },
119        { domains: ['character.ai'], source: 'Character.AI', medium: 'AI Referral', category: 'AI' },
120        { domains: ['meta.ai'], source: 'Meta AI', medium: 'AI Referral', category: 'AI' },
121        { domains: ['grok.com', 'x.ai'], source: 'Grok', medium: 'AI Referral', category: 'AI' },
122        { domains: ['chat.deepseek.com'], source: 'DeepSeek', medium: 'AI Referral', category: 'AI' },
123        { domains: ['chat.mistral.ai', 'mistral.ai'], source: 'Mistral Le Chat', medium: 'AI Referral', category: 'AI' },
124        { domains: ['huggingface.co'], source: 'HuggingChat', medium: 'AI Referral', category: 'AI' },
125        { domains: ['pi.ai'], source: 'Pi.ai', medium: 'AI Referral', category: 'AI' }
126    ];
127
128    const SEARCH_ENGINES = [
129        { domains: ['maps.google.com'], source: 'Google Maps', medium: 'Organic Search', category: 'Search' },
130        { domains: ['news.google.com'], source: 'Google News', medium: 'Organic Search', category: 'Search' },
131        { domains: ['images.google.com'], source: 'Google Images', medium: 'Organic Search', category: 'Search' },
132        { domains: ['discover.google.com'], source: 'Google Discover', medium: 'Organic Search', category: 'Search' },
133        { domains: ['google.com', 'google.co.uk', 'google.ca', 'google.com.au', 'google.de', 'google.fr', 'google.co.in'], source: 'Google', medium: 'Organic Search', category: 'Search' },
134        { domains: ['bing.com'], source: 'Bing', medium: 'Organic Search', category: 'Search' },
135        { domains: ['search.yahoo.com', 'yahoo.com'], source: 'Yahoo', medium: 'Organic Search', category: 'Search' },
136        { domains: ['duckduckgo.com'], source: 'DuckDuckGo', medium: 'Organic Search', category: 'Search' },
137        { domains: ['search.brave.com'], source: 'Brave Search', medium: 'Organic Search', category: 'Search' },
138        { domains: ['yandex.com', 'yandex.ru'], source: 'Yandex', medium: 'Organic Search', category: 'Search' },
139        { domains: ['baidu.com'], source: 'Baidu', medium: 'Organic Search', category: 'Search' },
140        { domains: ['naver.com'], source: 'Naver', medium: 'Organic Search', category: 'Search' },
141        { domains: ['ecosia.org'], source: 'Ecosia', medium: 'Organic Search', category: 'Search' },
142        { domains: ['startpage.com'], source: 'Startpage', medium: 'Organic Search', category: 'Search' },
143        { domains: ['search.aol.com'], source: 'AOL Search', medium: 'Organic Search', category: 'Search' },
144        { domains: ['ask.com'], source: 'Ask.com', medium: 'Organic Search', category: 'Search' },
145        { domains: ['wolframalpha.com'], source: 'WolframAlpha', medium: 'Organic Search', category: 'Search' },
146        { domains: ['qwant.com'], source: 'Qwant', medium: 'Organic Search', category: 'Search' },
147        { domains: ['sogou.com'], source: 'Sogou', medium: 'Organic Search', category: 'Search' },
148        { domains: ['seznam.cz'], source: 'Seznam', medium: 'Organic Search', category: 'Search' }
149    ];
150
151    const SOCIAL_PLATFORMS = [
152        { domains: ['linkedin.com'], source: 'LinkedIn', medium: 'Social', category: 'Social' },
153        { domains: ['facebook.com', 'fb.com'], source: 'Facebook', medium: 'Social', category: 'Social' },
154        { domains: ['instagram.com'], source: 'Instagram', medium: 'Social', category: 'Social' },
155        { domains: ['threads.net'], source: 'Threads', medium: 'Social', category: 'Social' },
156        { domains: ['x.com', 'twitter.com', 't.co'], source: 'X', medium: 'Social', category: 'Social' },
157        { domains: ['pinterest.com'], source: 'Pinterest', medium: 'Social', category: 'Social' },
158        { domains: ['reddit.com'], source: 'Reddit', medium: 'Social', category: 'Social' },
159        { domains: ['tiktok.com'], source: 'TikTok', medium: 'Social', category: 'Social' },
160        { domains: ['youtube.com', 'youtu.be'], source: 'YouTube', medium: 'Social', category: 'Social' },
161        { domains: ['quora.com'], source: 'Quora', medium: 'Social', category: 'Social' },
162        { domains: ['snapchat.com'], source: 'Snapchat', medium: 'Social', category: 'Social' },
163        { domains: ['tumblr.com'], source: 'Tumblr', medium: 'Social', category: 'Social' },
164        { domains: ['whatsapp.com', 'wa.me'], source: 'WhatsApp', medium: 'Social', category: 'Social' },
165        { domains: ['telegram.org', 't.me'], source: 'Telegram', medium: 'Social', category: 'Social' },
166        { domains: ['discord.com', 'discord.gg'], source: 'Discord', medium: 'Social', category: 'Social' },
167        { domains: ['mastodon.social'], source: 'Mastodon', medium: 'Social', category: 'Social' },
168        { domains: ['bsky.app'], source: 'Bluesky', medium: 'Social', category: 'Social' },
169        { domains: ['wechat.com'], source: 'WeChat', medium: 'Social', category: 'Social' },
170        { domains: ['line.me'], source: 'Line', medium: 'Social', category: 'Social' },
171        { domains: ['viber.com'], source: 'Viber', medium: 'Social', category: 'Social' },
172        { domains: ['messenger.com'], source: 'Facebook Messenger', medium: 'Social', category: 'Social' },
173        { domains: ['vk.com'], source: 'VK', medium: 'Social', category: 'Social' }
174    ];
175
176    const DEVELOPER_PLATFORMS = [
177        { domains: ['github.com'], source: 'GitHub', medium: 'Developer Community', category: 'Developer' },
178        { domains: ['gitlab.com'], source: 'GitLab', medium: 'Developer Community', category: 'Developer' },
179        { domains: ['stackoverflow.com'], source: 'StackOverflow', medium: 'Developer Community', category: 'Developer' },
180        { domains: ['dev.to'], source: 'Dev.to', medium: 'Developer Community', category: 'Developer' },
181        { domains: ['hashnode.com'], source: 'Hashnode', medium: 'Developer Community', category: 'Developer' },
182        { domains: ['medium.com'], source: 'Medium', medium: 'Developer Community', category: 'Developer' },
183        { domains: ['bitbucket.org'], source: 'Bitbucket', medium: 'Developer Community', category: 'Developer' },
184        { domains: ['codepen.io'], source: 'CodePen', medium: 'Developer Community', category: 'Developer' },
185        { domains: ['producthunt.com'], source: 'Product Hunt', medium: 'Developer Community', category: 'Developer' },
186        { domains: ['news.ycombinator.com'], source: 'Hacker News', medium: 'Developer Community', category: 'Developer' },
187        { domains: ['sourceforge.net'], source: 'SourceForge', medium: 'Developer Community', category: 'Developer' },
188        { domains: ['npmjs.com'], source: 'npm', medium: 'Developer Community', category: 'Developer' },
189        { domains: ['freecodecamp.org'], source: 'freeCodeCamp', medium: 'Developer Community', category: 'Developer' }
190    ];
191
192    const EMAIL_PLATFORMS = [
193        { domains: ['mail.google.com'], source: 'Gmail', medium: 'Email', category: 'Email' },
194        { domains: ['outlook.com', 'outlook.live.com', 'outlook.office.com'], source: 'Outlook', medium: 'Email', category: 'Email' },
195        { domains: ['mail.yahoo.com'], source: 'Yahoo Mail', medium: 'Email', category: 'Email' },
196        { domains: ['mail.proton.me', 'protonmail.com'], source: 'ProtonMail', medium: 'Email', category: 'Email' },
197        { domains: ['mail.zoho.com'], source: 'Zoho Mail', medium: 'Email', category: 'Email' },
198        { domains: ['mail.aol.com'], source: 'AOL Mail', medium: 'Email', category: 'Email' },
199        { domains: ['icloud.com'], source: 'iCloud Mail', medium: 'Email', category: 'Email' },
200        { domains: ['mail.ru'], source: 'Mail.ru', medium: 'Email', category: 'Email' },
201        { domains: ['gmx.com'], source: 'GMX Mail', medium: 'Email', category: 'Email' }
202    ];
203
204    const CMS_PLATFORMS = [
205        { domains: ['wordpress.com'], source: 'WordPress.com', medium: 'CMS Referral', category: 'CMS' },
206        { domains: ['wordpress.org'], source: 'WordPress.org', medium: 'CMS Referral', category: 'CMS' },
207        { domains: ['shopify.com'], source: 'Shopify', medium: 'CMS Referral', category: 'CMS' },
208        { domains: ['wix.com'], source: 'Wix', medium: 'CMS Referral', category: 'CMS' },
209        { domains: ['squarespace.com'], source: 'Squarespace', medium: 'CMS Referral', category: 'CMS' },
210        { domains: ['webflow.com'], source: 'Webflow', medium: 'CMS Referral', category: 'CMS' },
211        { domains: ['weebly.com'], source: 'Weebly', medium: 'CMS Referral', category: 'CMS' },
212        { domains: ['joomla.org'], source: 'Joomla', medium: 'CMS Referral', category: 'CMS' },
213        { domains: ['drupal.org'], source: 'Drupal', medium: 'CMS Referral', category: 'CMS' },
214        { domains: ['bigcommerce.com'], source: 'BigCommerce', medium: 'CMS Referral', category: 'CMS' },
215        { domains: ['godaddy.com'], source: 'GoDaddy Website Builder', medium: 'CMS Referral', category: 'CMS' },
216        { domains: ['elementor.com'], source: 'Elementor', medium: 'CMS Referral', category: 'CMS' },
217        { domains: ['hubspot.com'], source: 'HubSpot', medium: 'CMS Referral', category: 'CMS' }
218    ];
219
220    const MISC_PLATFORMS = [
221        { domains: ['crunchbase.com'], source: 'Crunchbase', medium: 'Business Directory', category: 'Directory' },
222        { domains: ['craigslist.org'], source: 'Craigslist', medium: 'Classifieds', category: 'Directory' },
223        { domains: ['blogspot.com'], source: 'Blogger', medium: 'Blog Referral', category: 'Blog' },
224        { domains: ['substack.com'], source: 'Substack', medium: 'Blog Referral', category: 'Blog' },
225        { domains: ['notion.site'], source: 'Notion', medium: 'Referral', category: 'Productivity' },
226        { domains: ['slack.com'], source: 'Slack', medium: 'Referral', category: 'Productivity' },
227        { domains: ['teams.microsoft.com'], source: 'Microsoft Teams', medium: 'Referral', category: 'Productivity' },
228        { domains: ['zoom.us'], source: 'Zoom', medium: 'Referral', category: 'Productivity' },
229        { domains: ['amazon.com'], source: 'Amazon', medium: 'Marketplace', category: 'Marketplace' },
230        { domains: ['appsumo.com'], source: 'AppSumo', medium: 'Marketplace', category: 'Marketplace' },
231        { domains: ['play.google.com'], source: 'Google Play', medium: 'App Store', category: 'App Store' },
232        { domains: ['apps.apple.com'], source: 'Apple App Store', medium: 'App Store', category: 'App Store' },
233        { domains: ['business.google.com'], source: 'Google Business Profile', medium: 'Local Listing', category: 'Local' },
234        { domains: ['bingplaces.com'], source: 'Bing Places', medium: 'Local Listing', category: 'Local' },
235        { domains: ['foursquare.com'], source: 'Foursquare', medium: 'Local Listing', category: 'Local' },
236        { domains: ['waze.com'], source: 'Waze', medium: 'Local Listing', category: 'Local' }
237    ];
238
239    const PLATFORM_DATABASE = Object.freeze(
240        [].concat(
241            REVIEW_DIRECTORIES,
242            FREELANCE_PLATFORMS,
243            AI_PLATFORMS,
244            SEARCH_ENGINES,
245            SOCIAL_PLATFORMS,
246            DEVELOPER_PLATFORMS,
247            EMAIL_PLATFORMS,
248            CMS_PLATFORMS,
249            MISC_PLATFORMS
250        ).map((entry) => Object.freeze({
251            domains: Object.freeze(entry.domains.slice()),
252            source: entry.source,
253            medium: entry.medium,
254            category: entry.category
255        }))
256    );
257
258    const DOMAIN_LOOKUP = (function buildDomainLookup(database) {
259        const pairs = [];
260        database.forEach((platform) => {
261            platform.domains.forEach((domain) => {
262                pairs.push({ domain, platform });
263            });
264        });
265        pairs.sort((a, b) => b.domain.length - a.domain.length);
266        return Object.freeze(pairs);
267    }(PLATFORM_DATABASE));
268
269    const PAID_CLICK_ID_PARAMS = Object.freeze([
270        { param: 'gclid', source: 'Google Ads', medium: 'Paid Search' },
271        { param: 'gclsrc', source: 'Google Ads', medium: 'Paid Search' },
272        { param: 'fbclid', source: 'Facebook Ads', medium: 'Paid Social' },
273        { param: 'msclkid', source: 'Microsoft Ads', medium: 'Paid Search' },
274        { param: 'ttclid', source: 'TikTok Ads', medium: 'Paid Social' },
275        { param: 'li_fat_id', source: 'LinkedIn Ads', medium: 'Paid Social' }
276    ]);
277
278    const BOT_PATTERNS = Object.freeze([
279        /googlebot/i,
280        /bingbot/i,
281        /facebookexternalhit|facebot/i,
282        /semrush/i,
283        /ahrefs/i,
284        /mj12bot/i,
285        /dotbot/i,
286        /yandexbot/i,
287        /slackbot/i,
288        /discordbot/i,
289        /twitterbot/i,
290        /linkedinbot/i
291    ]);
292
293    // ------------------------------------------------------------------------
294    // 3. UTILITIES
295    // ------------------------------------------------------------------------
296
297    const Utils = Object.freeze({
298
299        getCurrentURL() {
300            return window.location.href;
301        },
302
303        getReferrer() {
304            return document.referrer || '';
305        },
306
307        safeParseURL(url) {
308            if (!url) return null;
309            try {
310                return new URL(url);
311            } catch (error) {
312                return null;
313            }
314        },
315
316        normalizeHostname(hostname) {
317            return (hostname || '')
318                .toLowerCase()
319                .replace(/^www\./, '')
320                .replace(/^m\./, '');
321        },
322
323        hostnameMatches(hostname, domain) {
324            const normalizedHost = Utils.normalizeHostname(hostname);
325            const normalizedDomain = Utils.normalizeHostname(domain);
326            return normalizedHost === normalizedDomain
327                || normalizedHost.endsWith(`.${normalizedDomain}`);
328        },
329
330        normalizeLabel(value) {
331            return (value || '').toString().trim().toLowerCase();
332        },
333
334        getSiteHostname() {
335            return Utils.normalizeHostname(window.location.hostname);
336        },
337
338        isSameSite(url) {
339            const parsed = Utils.safeParseURL(url);
340            if (!parsed || !parsed.hostname) return false;
341            return Utils.normalizeHostname(parsed.hostname) === Utils.getSiteHostname();
342        },
343
344        isExternalReferrer(referrerUrl) {
345            if (!referrerUrl) return false;
346            return !Utils.isSameSite(referrerUrl);
347        },
348
349        hasUTMParams(url) {
350            const utm = Utils.extractUTMParams(url);
351            return Boolean(utm.utm_source || utm.utm_medium);
352        },
353
354        isNewPlatformVisit(referrerUrl, currentUrl) {
355            return Utils.isExternalReferrer(referrerUrl)
356                || Utils.hasUTMParams(currentUrl)
357                || Utils.hasUTMParams(referrerUrl);
358        },
359
360        buildAttributionRecord(referrerUrl, currentUrl, landingPage) {
361            const detection = PlatformDetector.detect(referrerUrl, currentUrl);
362            const utm = Utils.resolveUTMParams([currentUrl, referrerUrl]);
363            const source = Utils.normalizeLabel(detection.source);
364            const medium = Utils.normalizeLabel(detection.medium);
365
366            return {
367                utm_source: Utils.normalizeLabel(utm.utm_source || source),
368                utm_medium: Utils.normalizeLabel(utm.utm_medium || medium),
369                first_source: source,
370                first_medium: medium,
371                referrer: referrerUrl || '',
372                landing_page: landingPage || currentUrl
373            };
374        },
375
376        isBot() {
377            const ua = navigator.userAgent || '';
378            return BOT_PATTERNS.some((pattern) => pattern.test(ua));
379        },
380
381        extractUTMParams(url) {
382            const parsed = Utils.safeParseURL(url);
383            if (!parsed) {
384                return { utm_source: '', utm_medium: '' };
385            }
386            return {
387                utm_source: parsed.searchParams.get('utm_source') || '',
388                utm_medium: parsed.searchParams.get('utm_medium') || ''
389            };
390        },
391
392        mergeUTMParams(primary, fallback) {
393            return {
394                utm_source: primary.utm_source || fallback.utm_source || '',
395                utm_medium: primary.utm_medium || fallback.utm_medium || ''
396            };
397        },
398
399        resolveUTMParams(urls) {
400            const empty = { utm_source: '', utm_medium: '' };
401            return urls.reduce((merged, url) => {
402                return Utils.mergeUTMParams(merged, Utils.extractUTMParams(url));
403            }, empty);
404        }
405    });
406
407    // ------------------------------------------------------------------------
408    // 4. VISIT STORE (session-scoped, refreshes on each new platform visit)
409    // ------------------------------------------------------------------------
410
411    class VisitStore {
412
413        constructor(key) {
414            this.key = key;
415        }
416
417        load() {
418            try {
419                const raw = window.sessionStorage.getItem(this.key);
420                return raw ? JSON.parse(raw) : null;
421            } catch (error) {
422                return null;
423            }
424        }
425
426        save(record) {
427            try {
428                window.sessionStorage.setItem(this.key, JSON.stringify(record));
429            } catch (error) {
430                // Fail silently.
431            }
432        }
433
434        clear() {
435            try {
436                window.sessionStorage.removeItem(this.key);
437            } catch (error) {
438                // Fail silently.
439            }
440        }
441
442        static clearLegacyStorage() {
443            try {
444                window.localStorage.removeItem(CONFIG.LEGACY_STORAGE_KEY);
445            } catch (error) {
446                // Fail silently.
447            }
448            try {
449                document.cookie = `${CONFIG.LEGACY_STORAGE_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax`;
450            } catch (error) {
451                // Fail silently.
452            }
453        }
454    }
455
456    // ------------------------------------------------------------------------
457    // 5. PLATFORM DETECTOR
458    // ------------------------------------------------------------------------
459
460    class PlatformDetector {
461
462        static detect(referrerUrl, currentUrl) {
463            const utmMatch = PlatformDetector._matchUTM(currentUrl)
464                || PlatformDetector._matchUTM(referrerUrl);
465            if (utmMatch) {
466                return Object.assign({ referrerUrl }, utmMatch);
467            }
468
469            const paidMatch = PlatformDetector._matchPaidClickId(currentUrl);
470            if (paidMatch) {
471                return Object.assign({ referrerUrl }, paidMatch);
472            }
473
474            const referrerMatch = PlatformDetector._matchReferrer(referrerUrl);
475            if (referrerMatch) {
476                return Object.assign({ referrerUrl }, referrerMatch);
477            }
478
479            return {
480                referrerUrl,
481                source: CONFIG.DIRECT_SOURCE,
482                medium: CONFIG.DIRECT_MEDIUM,
483                category: 'Direct'
484            };
485        }
486
487        static _matchUTM(url) {
488            const parsed = Utils.safeParseURL(url);
489            if (!parsed) return null;
490
491            const utmSource = parsed.searchParams.get('utm_source');
492            if (!utmSource) return null;
493
494            return {
495                source: utmSource,
496                medium: parsed.searchParams.get('utm_medium') || 'Campaign',
497                category: 'Campaign'
498            };
499        }
500
501        static _matchPaidClickId(currentUrl) {
502            const parsed = Utils.safeParseURL(currentUrl);
503            if (!parsed) return null;
504
505            const found = PAID_CLICK_ID_PARAMS.find((entry) => parsed.searchParams.has(entry.param));
506            if (!found) return null;
507
508            return { source: found.source, medium: found.medium, category: 'Paid' };
509        }
510
511        static _matchReferrer(referrerUrl) {
512            const parsed = Utils.safeParseURL(referrerUrl);
513            if (!parsed || !parsed.hostname) return null;
514
515            const match = DOMAIN_LOOKUP.find((entry) => (
516                Utils.hostnameMatches(parsed.hostname, entry.domain)
517            ));
518
519            return match
520                ? {
521                    source: match.platform.source,
522                    medium: match.platform.medium,
523                    category: match.platform.category
524                }
525                : null;
526        }
527    }
528
529    // ------------------------------------------------------------------------
530    // 6. ATTRIBUTION TRACKER
531    // ------------------------------------------------------------------------
532
533    class AttributionTracker {
534
535        constructor() {
536            this.store = new VisitStore(CONFIG.SESSION_KEY);
537            this.visitData = null;
538            this.isBot = Utils.isBot();
539            VisitStore.clearLegacyStorage();
540        }
541
542        captureVisit(referrerUrl, currentUrl) {
543            const existing = this.visitData || this.store.load();
544            const isNewVisit = Utils.isNewPlatformVisit(referrerUrl, currentUrl);
545            const landingPage = isNewVisit || !existing
546                ? currentUrl
547                : existing.landing_page;
548
549            this.visitData = Utils.buildAttributionRecord(referrerUrl, currentUrl, landingPage);
550            this.store.save(this.visitData);
551            return this.visitData;
552        }
553
554        init() {
555            if (this.isBot) return null;
556
557            const referrerUrl = Utils.getReferrer();
558            const currentUrl = Utils.getCurrentURL();
559            const existing = this.store.load();
560
561            if (Utils.isNewPlatformVisit(referrerUrl, currentUrl) || !existing) {
562                return this.captureVisit(referrerUrl, currentUrl);
563            }
564
565            this.visitData = existing;
566            return this.visitData;
567        }
568
569        getFullAttributionData() {
570            if (this.isBot) return null;
571
572            const currentUrl = Utils.getCurrentURL();
573            const referrerUrl = Utils.getReferrer();
574
575            if (Utils.isNewPlatformVisit(referrerUrl, currentUrl)) {
576                this.captureVisit(referrerUrl, currentUrl);
577            } else {
578                this.visitData = this.visitData || this.store.load() || this.captureVisit(referrerUrl, currentUrl);
579            }
580
581            if (!this.visitData) return null;
582
583            const liveUtm = Utils.resolveUTMParams([currentUrl, referrerUrl]);
584            const source = this.visitData.first_source;
585            const medium = this.visitData.first_medium;
586
587            return {
588                utm_source: Utils.normalizeLabel(liveUtm.utm_source || this.visitData.utm_source || source),
589                utm_medium: Utils.normalizeLabel(liveUtm.utm_medium || this.visitData.utm_medium || medium),
590                first_source: source,
591                first_medium: medium,
592                referrer: this.visitData.referrer,
593                landing_page: this.visitData.landing_page,
594                submission_page: currentUrl
595            };
596        }
597
598        reset() {
599            try {
600                this.store.clear();
601                this.visitData = null;
602                VisitStore.clearLegacyStorage();
603            } catch (error) {
604                // Fail silently.
605            }
606        }
607    }
608
609    // ------------------------------------------------------------------------
610    // 7. CONTACT FORM 7 INTEGRATION
611    // ------------------------------------------------------------------------
612
613    class CF7Integration {
614
615        constructor(tracker) {
616            this.tracker = tracker;
617            this._observer = null;
618        }
619
620        populateAllForms() {
621            try {
622                const forms = document.querySelectorAll(CONFIG.CF7_FORM_SELECTOR);
623                forms.forEach((form) => this.populateForm(form));
624            } catch (error) {
625                // Fail silently.
626            }
627        }
628
629        populateForm(form) {
630            try {
631                if (!form) return;
632
633                const data = this.tracker.getFullAttributionData();
634                if (!data) return;
635
636                CF7_FIELD_NAMES.forEach((fieldName) => {
637                    const input = form.querySelector(`[name="${fieldName}"]`);
638                    if (input) {
639                        input.value = data[fieldName] != null ? data[fieldName] : '';
640                    }
641                });
642            } catch (error) {
643                // Fail silently.
644            }
645        }
646
647        _resolveFormFromEvent(event) {
648            if (!event) return null;
649            if (event.target && event.target.matches && event.target.matches(CONFIG.CF7_FORM_SELECTOR)) {
650                return event.target;
651            }
652            if (event.detail && event.detail.contactFormId) {
653                const unit = document.querySelector(`.wpcf7[data-wpcf7-id="${event.detail.contactFormId}"]`);
654                if (unit) {
655                    const form = unit.querySelector('form');
656                    if (form) return form;
657                }
658            }
659            return null;
660        }
661
662        _handleFormEvent(event) {
663            const form = this._resolveFormFromEvent(event);
664            if (form) {
665                this.populateForm(form);
666                return;
667            }
668            this.populateAllForms();
669        }
670
671        bindEvents() {
672            this.populateAllForms();
673
674            document.addEventListener('wpcf7beforesubmit', (event) => {
675                this._handleFormEvent(event);
676            });
677
678            document.addEventListener('wpcf7submit', (event) => {
679                this._handleFormEvent(event);
680            });
681
682            document.addEventListener('wpcf7mailsent', (event) => {
683                this._handleFormEvent(event);
684            });
685
686            document.addEventListener('submit', (event) => {
687                const form = event.target;
688                if (form && form.matches && form.matches(CONFIG.CF7_FORM_SELECTOR)) {
689                    this.populateForm(form);
690                }
691            }, true);
692
693            this._observeDynamicForms();
694        }
695
696        _observeDynamicForms() {
697            if (typeof MutationObserver === 'undefined') return;
698
699            try {
700                this._observer = new MutationObserver((mutations) => {
701                    const hasNewForms = mutations.some((mutation) => {
702                        return Array.from(mutation.addedNodes).some((node) => {
703                            if (node.nodeType !== 1) return false;
704                            if (node.matches && node.matches(CONFIG.CF7_FORM_SELECTOR)) return true;
705                            return node.querySelector && node.querySelector(CONFIG.CF7_FORM_SELECTOR);
706                        });
707                    });
708                    if (hasNewForms) {
709                        this.populateAllForms();
710                    }
711                });
712
713                this._observer.observe(document.documentElement, {
714                    childList: true,
715                    subtree: true
716                });
717            } catch (error) {
718                // Fail silently.
719            }
720        }
721
722        destroy() {
723            if (this._observer) {
724                this._observer.disconnect();
725                this._observer = null;
726            }
727        }
728    }
729
730    // ------------------------------------------------------------------------
731    // 8. DEBUG API — window.DDZTracker
732    // ------------------------------------------------------------------------
733
734    class DebugAPI {
735
736        constructor(tracker, cf7) {
737            this._tracker = tracker;
738            this._cf7 = cf7;
739        }
740
741        debug() {
742            const data = this._tracker.getFullAttributionData();
743            /* eslint-disable no-console */
744            console.groupCollapsed('%c[DDZTracker] Debug Snapshot', 'color:#6c5ce7;font-weight:bold;');
745            console.log('Version:', CONFIG.VERSION);
746            console.log('Is bot:', this._tracker.isBot);
747            console.log('Raw referrer:', Utils.getReferrer() || '(none — direct visit)');
748            console.log('Storage:', this.storage());
749            if (data) console.table(data);
750            console.groupEnd();
751            /* eslint-enable no-console */
752            return data;
753        }
754
755        show() {
756            const data = this._tracker.getFullAttributionData();
757            /* eslint-disable no-console */
758            if (data) console.table(data);
759            /* eslint-enable no-console */
760            return data;
761        }
762
763        clear() {
764            this._tracker.reset();
765            /* eslint-disable no-console */
766            console.info('[DDZTracker] Attribution data cleared.');
767            /* eslint-enable no-console */
768        }
769
770        version() {
771            return CONFIG.VERSION;
772        }
773
774        storage() {
775            return {
776                backend: 'sessionStorage',
777                key: CONFIG.SESSION_KEY,
778                hasData: Boolean(this._tracker.store.load()),
779                platformCount: PLATFORM_DATABASE.length,
780                isBot: this._tracker.isBot
781            };
782        }
783    }
784
785    // ------------------------------------------------------------------------
786    // 9. BOOTSTRAP
787    // ------------------------------------------------------------------------
788
789    function bootstrap() {
790        try {
791            const tracker = new AttributionTracker();
792            tracker.init();
793
794            const cf7 = new CF7Integration(tracker);
795            cf7.bindEvents();
796
797            window[CONFIG.DEBUG_NAMESPACE] = Object.freeze(new DebugAPI(tracker, cf7));
798        } catch (error) {
799            // Never break the website.
800        }
801    }
802
803    if (document.readyState === 'loading') {
804        document.addEventListener('DOMContentLoaded', bootstrap);
805    } else {
806        bootstrap();
807    }
808
809}(window, document));

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.