@rubytech/create-maxy-code 0.1.119 → 0.1.121

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,191 @@
1
+ /* Visitor-graph pixel (Task 357).
2
+ *
3
+ * Single IIFE. Plain JS. No build step. ~3 KB minified.
4
+ *
5
+ * Loaded by hosted listing pages:
6
+ *
7
+ * <script defer src="https://<brand>/v.js" data-site="<slug>"></script>
8
+ *
9
+ * Emits to POST /v/event:
10
+ *
11
+ * pageview — once per session per URL (idempotent within 5s for SPA nav)
12
+ * click — DOM clicks on elements with data-track="<label>", or
13
+ * anchors flagged data-track-gallery / data-track-floorplan
14
+ * scroll — roll-up: one event per milestone {25,50,75,100} crossed
15
+ * dwell — on visibility change to hidden / pagehide, sendBeacon
16
+ *
17
+ * Identity: the server resolves identity from the mxy_v cookie. The pixel
18
+ * sends visitorId from localStorage (mints one if absent) as a fallback so
19
+ * cross-tab and same-browser-no-cookie cases still cluster.
20
+ */
21
+ (function () {
22
+ if (typeof window === 'undefined' || typeof document === 'undefined') return
23
+ var script = document.currentScript
24
+ if (!script) return
25
+ var siteSlug = script.getAttribute('data-site') || ''
26
+ var endpoint = new URL('/v/event', script.src).toString()
27
+
28
+ var VISITOR_KEY = 'mxy_visitor_id'
29
+ var SESSION_KEY = 'mxy_session_id'
30
+ var SESSION_PV_KEY = 'mxy_session_pv'
31
+ var LAST_PV_KEY = 'mxy_last_pv'
32
+ var SCROLL_KEY = 'mxy_scroll_depth'
33
+ var DWELL_KEY = 'mxy_dwell_start'
34
+
35
+ function randomId() {
36
+ if (window.crypto && window.crypto.getRandomValues) {
37
+ var buf = new Uint8Array(16)
38
+ window.crypto.getRandomValues(buf)
39
+ return Array.prototype.map.call(buf, function (b) {
40
+ return ('0' + b.toString(16)).slice(-2)
41
+ }).join('')
42
+ }
43
+ return String(Date.now()) + '_' + Math.random().toString(36).slice(2, 10)
44
+ }
45
+
46
+ function getVisitorId() {
47
+ try {
48
+ var v = localStorage.getItem(VISITOR_KEY)
49
+ if (v) return v
50
+ var fresh = randomId()
51
+ localStorage.setItem(VISITOR_KEY, fresh)
52
+ return fresh
53
+ } catch (_e) {
54
+ return 'noltc-' + randomId()
55
+ }
56
+ }
57
+
58
+ function getSessionId() {
59
+ try {
60
+ var s = sessionStorage.getItem(SESSION_KEY)
61
+ if (s) return s
62
+ var fresh = randomId()
63
+ sessionStorage.setItem(SESSION_KEY, fresh)
64
+ return fresh
65
+ } catch (_e) {
66
+ return 'nots-' + randomId()
67
+ }
68
+ }
69
+
70
+ var visitorId = getVisitorId()
71
+ var sessionId = getSessionId()
72
+
73
+ function send(payload) {
74
+ payload.site = siteSlug
75
+ payload.vid = visitorId
76
+ payload.sid = sessionId
77
+ payload.url = location.href
78
+ payload.path = location.pathname
79
+ payload.title = (document.title || '').slice(0, 256)
80
+ payload.ts = Date.now()
81
+ try {
82
+ var body = JSON.stringify(payload)
83
+ if (navigator.sendBeacon) {
84
+ var blob = new Blob([body], { type: 'application/json' })
85
+ if (navigator.sendBeacon(endpoint, blob)) return
86
+ }
87
+ fetch(endpoint, {
88
+ method: 'POST',
89
+ headers: { 'Content-Type': 'application/json' },
90
+ body: body,
91
+ credentials: 'include',
92
+ keepalive: true,
93
+ }).catch(function () { /* fire-and-forget */ })
94
+ } catch (_e) {
95
+ /* silently drop — pixel must never break the page */
96
+ }
97
+ }
98
+
99
+ // ─────────── pageview (idempotent within 5s for SPA nav) ───────────
100
+ function emitPageview() {
101
+ try {
102
+ var last = sessionStorage.getItem(LAST_PV_KEY)
103
+ if (last) {
104
+ var parsed = JSON.parse(last)
105
+ if (parsed && parsed.url === location.href && Date.now() - parsed.ts < 5000) {
106
+ return
107
+ }
108
+ }
109
+ sessionStorage.setItem(LAST_PV_KEY, JSON.stringify({ url: location.href, ts: Date.now() }))
110
+ sessionStorage.setItem(SESSION_PV_KEY, randomId()) // PageView id for milestone roll-up
111
+ sessionStorage.setItem(SCROLL_KEY, '0')
112
+ } catch (_e) { /* localStorage / sessionStorage disabled */ }
113
+
114
+ var pvId = ''
115
+ try { pvId = sessionStorage.getItem(SESSION_PV_KEY) || '' } catch (_e) {}
116
+ send({
117
+ type: 'pageview',
118
+ pvId: pvId,
119
+ referrer: document.referrer || '',
120
+ })
121
+ }
122
+
123
+ // ─────────── click tracking ───────────
124
+ document.addEventListener('click', function (ev) {
125
+ var el = ev.target && (ev.target.closest ? ev.target.closest('[data-track]') : null)
126
+ if (!el) return
127
+ var label = el.getAttribute('data-track') || 'unknown'
128
+ var pvId = ''
129
+ try { pvId = sessionStorage.getItem(SESSION_PV_KEY) || '' } catch (_e) {}
130
+ send({
131
+ type: 'click',
132
+ label: label.slice(0, 64),
133
+ pvId: pvId,
134
+ href: el.getAttribute('href') || '',
135
+ })
136
+ }, true)
137
+
138
+ // ─────────── scroll milestones (roll-up) ───────────
139
+ var scrollScheduled = false
140
+ function onScroll() {
141
+ if (scrollScheduled) return
142
+ scrollScheduled = true
143
+ requestAnimationFrame(function () {
144
+ scrollScheduled = false
145
+ var doc = document.documentElement
146
+ var max = Math.max(doc.scrollHeight, document.body.scrollHeight) - window.innerHeight
147
+ if (max <= 0) return
148
+ var pct = Math.floor(((window.scrollY || doc.scrollTop) / max) * 100)
149
+ var threshold = pct >= 100 ? 100 : pct >= 75 ? 75 : pct >= 50 ? 50 : pct >= 25 ? 25 : 0
150
+ if (threshold === 0) return
151
+ try {
152
+ var prev = parseInt(sessionStorage.getItem(SCROLL_KEY) || '0', 10) || 0
153
+ if (threshold <= prev) return
154
+ sessionStorage.setItem(SCROLL_KEY, String(threshold))
155
+ } catch (_e) {}
156
+ var pvId = ''
157
+ try { pvId = sessionStorage.getItem(SESSION_PV_KEY) || '' } catch (_e) {}
158
+ send({ type: 'scroll', depth: threshold, pvId: pvId })
159
+ })
160
+ }
161
+ window.addEventListener('scroll', onScroll, { passive: true })
162
+
163
+ // ─────────── dwell on unload / visibility change ───────────
164
+ try { sessionStorage.setItem(DWELL_KEY, String(Date.now())) } catch (_e) {}
165
+ function emitDwell() {
166
+ var start = 0
167
+ try { start = parseInt(sessionStorage.getItem(DWELL_KEY) || '0', 10) || 0 } catch (_e) {}
168
+ if (!start) return
169
+ var ms = Date.now() - start
170
+ if (ms < 250) return // ignore drive-bys
171
+ var pvId = ''
172
+ try { pvId = sessionStorage.getItem(SESSION_PV_KEY) || '' } catch (_e) {}
173
+ send({ type: 'dwell', ms: ms, pvId: pvId })
174
+ }
175
+ document.addEventListener('visibilitychange', function () {
176
+ if (document.visibilityState === 'hidden') emitDwell()
177
+ })
178
+ window.addEventListener('pagehide', emitDwell)
179
+
180
+ // SPA navigation hooks — many listing sites are client-routed.
181
+ var origPushState = history.pushState
182
+ history.pushState = function () {
183
+ var r = origPushState.apply(this, arguments)
184
+ setTimeout(emitPageview, 0)
185
+ return r
186
+ }
187
+ window.addEventListener('popstate', emitPageview)
188
+
189
+ // Boot.
190
+ emitPageview()
191
+ })()