1(function () { 2 'use strict'; 3 4 var GOOGLE_STORE_CONVERSION = 'AW-17488655629/FsszCMPn7_AbEI3qnpNB'; 5 var userAgent = navigator.userAgent || ''; 6 var isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1; 7 var isIOS = /iPad|iPhone|iPod/i.test(userAgent) || isIPadOS; 8 var isAndroid = /Android/i.test(userAgent); 9 var platform = isIOS ? 'ios' : (isAndroid ? 'android' : null); 10 11 function placementFor(link) { 12 if (link.dataset.placement) return link.dataset.placement; 13 if (link.closest('.sticky-download-bar')) return 'sticky'; 14 if (link.closest('.download, .final-cta')) return 'download'; 15 if (link.closest('.hero')) return 'hero'; 16 return 'header'; 17 } 18 19 // Only campaign metadata, never click IDs, arbitrary query values or user IDs. 20 // Page-local by design: no cookies, storage or attribution across visits. 21 function metaAttribution() { 22 if (window.kanjidonAdvertisingConsentGranted !== true 23 || navigator.globalPrivacyControl === true || navigator.doNotTrack === '1') return null; 24 try { 25 var params = new URL(window.location.href).searchParams; 26 var keys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term']; 27 if (keys.some(function (key) { return params.getAll(key).length > 1; })) return null; 28 var source = params.get('utm_source'); 29 if (['fb', 'ig', 'msg', 'an', 'th', 'facebook', 'instagram', 'messenger', 'threads'].indexOf(source) < 0 30 || params.get('utm_medium') !== 'paid_social' 31 || params.get('utm_campaign') !== 'meta_jlpt_video_6lang') return null; 32 var content = params.get('utm_content') || ''; 33 if (!/^(it|fr|de|en|es|pt)_[0-9]{10,20}$/.test(content)) return null; 34 var term = params.get('utm_term') || ''; 35 return { source: source, content: content, 36 term: ['feed', 'story', 'stories', 'reels', 'facebook_feed', 'instagram_feed', 37 'instagram_stories', 'facebook_stories', 'instagram_reels', 'facebook_reels', 38 'marketplace', 'search', 'video_feeds', 'explore', 'threads_feed'].indexOf(term.toLowerCase()) >= 0 ? term : '' }; 39 } catch (_) { return null; } 40 } 41 42 function destinationFor(targetPlatform, placement) { 43 var meta = metaAttribution(); 44 if (targetPlatform === 'ios') return 'https://apps.apple.com/app/apple-store/id6747951805?pt=121509463&ct=' 45 + (meta ? 'meta_jlpt_video_6lang' : 'website_install') + '&mt=8'; 46 var campaign = 'utm_source=kanjidon.com&utm_medium=website&utm_campaign=website_install&utm_content=' + placement; 47 if (meta) { 48 campaign = 'utm_source=' + encodeURIComponent(meta.source) 49 + '&utm_medium=paid_social&utm_campaign=meta_jlpt_video_6lang' 50 + '&utm_content=' + encodeURIComponent(meta.content); 51 if (meta.term) campaign += '&utm_term=' + encodeURIComponent(meta.term); 52 } 53 return 'https://play.google.com/store/apps/details?id=com.davidemoscato.kanjidon' + '&referrer=' + encodeURIComponent(campaign); 54 } 55 56 function recordGoogleStoreConversion() { 57 if (window.kanjidonAdvertisingConsentGranted !== true || typeof window.gtag !== 'function') return; 58 window.gtag('event', 'conversion', { 59 send_to: GOOGLE_STORE_CONVERSION, 60 value: 1.0, 61 currency: 'EUR', 62 event_timeout: 1000 63 }); 64 } 65 66 function recordClick(targetPlatform, placement) { 67 recordGoogleStoreConversion(); 68 if (typeof window.kanjidonMeasureOpenAI === 'function') { 69 window.kanjidonMeasureOpenAI('store_click_' + targetPlatform, placement); 70 } 71 if (!window.fetch) return; 72 window.fetch('/go/' + targetPlatform + '/' + placement + '/', { 73 method: 'GET', 74 cache: 'no-store', 75 credentials: 'omit', 76 keepalive: true 77 }).catch(function () {}); 78 } 79 80 function configureStoreLink(link, targetPlatform) { 81 if (link.dataset.storeRoutingReady === 'true') return; 82 var placement = placementFor(link); 83 function refreshDestination() { link.href = destinationFor(targetPlatform, placement); } 84 refreshDestination(); 85 link.dataset.storeRoutingReady = 'true'; 86 if (platform) link.removeAttribute('target'); 87 // Re-read consent at interaction time, including keyboard, middle-click and copying links. 88 ['pointerdown', 'focus', 'contextmenu', 'auxclick'].forEach(function (type) { 89 link.addEventListener(type, refreshDestination); 90 }); 91 link.addEventListener('click', function () { 92 refreshDestination(); 93 recordClick(targetPlatform, placement); 94 }); 95 } 96 97 document.querySelectorAll('[data-smart-download]').forEach(function (link) { 98 link.addEventListener('click', function () {
99 if (typeof window.kanjidonMeasureOpenAI === 'function') { 100 window.kanjidonMeasureOpenAI('download_click', placementFor(link)); 101 } 102 }); 103 if (platform) configureStoreLink(link, platform); 104 else link.href = '#download'; 105 }); 106 107 // Editorial articles also link to third-party apps. Route and measure only Kanjidon. 108 function isKanjidonStoreLink(link, targetPlatform) { 109 try { 110 var url = new URL(link.href, window.location.href); 111 if (targetPlatform === 'ios') { 112 return url.hostname === 'apps.apple.com' && /\/id6747951805(?:\/|$)/.test(url.pathname); 113 } 114 return url.hostname === 'play.google.com' && url.pathname === '/store/apps/details' 115 && url.searchParams.get('id') === 'com.davidemoscato.kanjidon'; 116 } catch (_) { return false; } 117 } 118 119 document.querySelectorAll('a[href*="apps.apple.com"]').forEach(function (link) { 120 if (isKanjidonStoreLink(link, 'ios')) configureStoreLink(link, 'ios'); 121 }); 122 123 document.querySelectorAll('a[href*="play.google.com/store/apps/details"]').forEach(function (link) { 124 if (isKanjidonStoreLink(link, 'android')) configureStoreLink(link, 'android'); 125 }); 126})();
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.