1/** 2 * Global Page View + Click Tracker 3 * Fires a /track-impression pixel on page load, then tracks all clicks 4 * on the page (with special handling for Resy buttons as conversions). 5 * Usage: Include this script on any page to track user interactions. 6 * Version: 2.3.0 7 */ 8(function() { 9 'use strict'; 10 11 // Prevent double initialization (e.g., in iframes) 12 if (window.rtTrackerInitialized) { 13 return; 14 } 15 window.rtTrackerInitialized = true; 16 // An internal reporting label does not change the public/SEO document title. 17 var titleOverride = document.querySelector('meta[name="tracking-page-title"]'); 18 var trackingTitle = titleOverride ? titleOverride.content : document.title; 19 20 // Don't track the password-protected reporting dashboard itself 21 var isDashboard = window.location.pathname.indexOf('/click-report') === 0; 22 23 // Fire an impression pixel on page load with UTM params, referrer, and title 24 if (!isDashboard) { 25 try { 26 var impParams = new URLSearchParams(window.location.search); 27 if (document.referrer) { 28 impParams.set('ref', document.referrer); 29 } 30 if (document.title) { 31 impParams.set('page_title', trackingTitle); 32 } 33 var impImg = new Image(); 34 impImg.src = '/track-impression?' + impParams.toString(); 35 } catch (e) { 36 // Tracking must never break the page 37 } 38 } 39 40 // Track a conversion (Resy button click) 41 function trackResyConversion(clickData) { 42 var params = new URLSearchParams(window.location.search); 43 if (document.referrer) { 44 params.set('ref', document.referrer); 45 } 46 params.set('url', window.location.href); 47 params.set('page_title', trackingTitle); 48 params.set('click_target', clickData.target); 49 params.set('click_text', clickData.text); 50 params.set('click_href', clickData.href); 51 52 // Use sendBeacon for reliability 53 if (navigator.sendBeacon) { 54 navigator.sendBeacon('/track-conversion?' + params.toString()); 55 } else { 56 var img = new Image(); 57 img.src = '/track-conversion?' + params.toString(); 58 } 59 } 60 61 // Get useful info about a clicked element 62 function getElementInfo(element) { 63 if (!element) { 64 return { 65 tag: '', 66 id: '', 67 className: '', 68 text: '', 69 href: '', 70 type: '' 71 }; 72 } 73 74 var label = element.getAttribute('data-track-label') || element.getAttribute('aria-label') || element.title || (element.textContent || '').trim(); 75 if (element.matches('a[href="#kg-menu-dialog"]')) { 76 label = 'Open ' + (element.dataset.menuKey || 'dinner') + ' menu'; 77 } 78 var menuDialog = element.closest('#kg-menu-dialog'); 79 if (menuDialog && !element.matches('[role="tab"]')) { 80 var selectedMenu = menuDialog.querySelector('[role="tab"][aria-selected="true"]'); 81 var pageCount = menuDialog.querySelector('.kg-menu-page-count'); 82 if (selectedMenu) label += ' â ' + selectedMenu.textContent.trim(); 83 if (pageCount && /(?:Next|Previous) menu page/.test(label)) label += ' (from ' + pageCount.textContent.trim() + ')'; 84 } 85 if (element.matches('[role="tab"][data-menu]')) label = 'View ' + element.textContent.trim() + ' menu'; 86 var info = { 87 tag: element.tagName || '', 88 id: element.id || '', 89 className: element.className || '', 90 text: label.substring(0, 120), 91 href: element.href || '', 92 type: element.type || '', 93 ariaLabel: (element.getAttribute && element.getAttribute('aria-label')) || '', 94 title: element.title || '' 95 }; 96 return info; 97 } 98 99 // A Resy click is a click on something that actually opens Resy: a link to 100 // resy.com, the Resy widget (embed.js swaps the resyButton-* link for a 101 // role="button" span labelled "Resy - Book Now"), or an element tagged 102 // data-reserve / data-track="resy". Ancestors are matched 103 // by these attributes only, never by their text, so a "View Our Menus" button 104 // that merely sits next to "Book on Resy" is not counted as a reservation. 105 var RESY_SELECTOR = 'a[href*="resy.com"],a[href*="kashkaval.com/reservations"],[id^="resyButton"],' + 106 '[aria-label*="Resy"],[class*="resy"],[data-reserve],[data-track="resy"]'; 107 108 function findResyElement(element) { 109 return element && element.closest ? element.closest(RESY_SELECTOR) : null; 110 } 111 112 // Track a general click (any clickable element) 113 function trackGeneralClick(clickData) { 114 var params = new URLSearchParams(window.location.search); 115 if (document.referrer) { 116 params.set('ref', document.referrer); 117 } 118 params.set('url', window.location.href); 119 params.set('page_title', trackingTitle); 120 params.set('click_target', clickData.target); 121 params.set('click_text', clickData.text); 122 params.set('click_href', clickData.href); 123 124 // Use sendBeacon for reliability 125 if (navigator.sendBeacon) { 126 navigator.sendBeacon('/track-click?' + params.toString()); 127 } else { 128 var img = new Image(); 129 img.src = '/track-click?' + params.toString(); 130 } 131 } 132 133 function describeTarget(info) { 134 return info.tag + (info.id ? '#' + info.id : '') + (info.className ? '.' + String(info.className).split(' ')[0] : ''); 135 } 136 137 // Global click handler 138 document.addEventListener('click', function(e) { 139 var target = e.target && e.target.closest && e.target.closest('a,button,input,[role="button"],[role="tab"],[oncl
139ick]'); 140 if (!target) return; 141 142 var info = getElementInfo(target); 143 144 // A Resy click is logged once, as a conversion, and not again as a general click 145 var resyElement = findResyElement(target); 146 if (resyElement) { 147 var resyInfo = getElementInfo(resyElement); 148 trackResyConversion({ 149 target: describeTarget(resyInfo), 150 text: resyInfo.text, 151 href: resyInfo.href 152 }); 153 return; 154 } 155 156 // Track all clicks on links, buttons, and interactive elements 157 if (info.tag === 'A' || info.tag === 'BUTTON' || 158 info.tag === 'INPUT' || info.href || 159 String(info.className).indexOf('btn') >= 0 || 160 target.onclick || target.getAttribute('onclick')) { 161 162 trackGeneralClick({ 163 target: describeTarget(info), 164 text: (info.text || info.ariaLabel || info.title || '').substring(0, 120), 165 href: info.href 166 }); 167 } 168 }, true); // Use capture phase to catch clicks early 169 170})();
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.