PageSourceSearch

https://club.dirtstore.co.uk/embed.js

js dirtstore.co.uk collected 2026-10-07 15:52:33 UTC 16,176 bytes, 404 lines download raw bytes

1;(function () {
2	'use strict'
3
4	var script = document.currentScript
5	var DEFAULT_APP_URL = 'https://club.dirtstore.co.uk'
6
7	// data-* attributes are author-controlled, but harden the values that flow
8	// into DOM sinks anyway: appUrl/iconUrl become an iframe src, an <img> src and
9	// a window.open target (a javascript:/data: URL there would execute), and
10	// buttonColor is interpolated into a <style> block (a crafted value could
11	// break out and inject CSS rules).
12	function safeHttpUrl(value, fallback) {
13		if (!value) return fallback
14		var v = String(value).trim()
15		// Guard against placeholder strings that slip in when an install snippet
16		// stringifies a null/undefined value, e.g. data-icon-url="null". Without
17		// this, new URL('null', origin) resolves to <origin>/null — a valid https
18		// URL that 404s, so the trigger would show a broken <img> instead of
19		// falling back to the built-in icon.
20		if (v === '' || v === 'null' || v === 'undefined') return fallback
21		try {
22			var u = new URL(v, window.location.href)
23			// Returning u.href (not the raw value) normalises + percent-encodes,
24			// so quotes can't break out of the attribute we concatenate it into.
25			if (u.protocol === 'http:' || u.protocol === 'https:') return u.href
26		} catch (e) {
27			/* fall through */
28		}
29		return fallback
30	}
31	function safeColor(value, fallback) {
32		if (value && /^(#[0-9a-fA-F]{3,8}|rgba?\([\d.,\s%]+\)|[a-zA-Z]{1,30})$/.test(value)) return value
33		return fallback
34	}
35
36	var appUrl = safeHttpUrl(script && script.getAttribute('data-app-url'), DEFAULT_APP_URL).replace(/\/+$/, '')
37	var position = (script && script.getAttribute('data-position')) || 'bottom-right'
38	var buttonColor = safeColor(script && script.getAttribute('data-color'), '#ff0000')
39	var iconChoice = (script && script.getAttribute('data-icon')) || 'star'
40	var iconUrl = safeHttpUrl(script && script.getAttribute('data-icon-url'), null)
41	var iconSvg = script && script.getAttribute('data-icon-svg')
42
43	if (window.Shopify && window.Shopify.designMode) return
44
45	if (document.getElementById('cds-embed-root')) return
46
47	var css = document.createElement('style')
48	css.id = 'cds-embed-styles'
49	css.textContent = [
50		'#cds-trigger {',
51		'  all: unset;',
52		'  position: fixed;',
53		'  z-index: 2147483646;',
54		'  width: 56px;',
55		'  height: 56px;',
56		'  border-radius: 50%;',
57		'  cursor: pointer;',
58		'  display: flex;',
59		'  align-items: center;',
60		'  justify-content: center;',
61		'  color: #fff;',
62		'  background: ' + buttonColor + ';',
63		'  box-shadow: 0 4px 20px rgba(0,0,0,0.3);',
64		'  transition: transform 0.25s cubic-bezier(0.4,0,0.2,1), box-shadow 0.25s ease;',
65		position === 'bottom-left' ? '  bottom: 20px; left: 20px;' : '  bottom: 20px; right: 20px;',
66		'}',
67		'#cds-trigger:hover {',
68		'  transform: scale(1.08);',
69		'  box-shadow: 0 6px 28px rgba(0,0,0,0.4);',
70		'}',
71		'#cds-trigger:active { transform: scale(0.95); }',
72		'#cds-trigger.cds-is-open { transform: rotate(90deg); }',
73		'#cds-trigger.cds-is-open:hover { transform: rotate(90deg) scale(1.08); }',
74		'#cds-trigger svg {',
75		'  width: 26px;',
76		'  height: 26px;',
77		'  fill: #fff;',
78		'}',
79		'#cds-trigger img {',
80		'  width: 28px;',
81		'  height: 28px;',
82		'  border-radius: 6px;',
83		'  object-fit: contain;',
84		'}',
85
86		'#cds-panel {',
87		'  all: unset;',
88		'  position: fixed;',
89		'  z-index: 2147483647;',
90		'  display: flex;',
91		'  flex-direction: column;',
92		'  background: #0b0b0b;',
93		'  border-radius: 20px;',
94		'  overflow: hidden;',
95		'  box-shadow: 0 12px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.08);',
96		'  opacity: 0;',
97		'  transform: translateY(16px) scale(0.97);',
98		'  transition: opacity 0.2s ease, transform 0.3s cubic-bezier(0.4,0,0.2,1);',
99		'  pointer-events: none;',
100		position === 'bottom-left'
101			? '  transform-origin: bottom left; bottom: 88px; left: 20px;'
102			: '  transform-origin: bottom right; bottom: 88px; right: 20px;',
103		'  width: 400px;',
104		'  height: 680px;',
105		'  max-height: calc(100vh - 108px);',
106		'}',
107		'#cds-panel.cds-open {',
108		'  opacity: 1;',
109		'  transform: translateY(0) scale(1);',
110		'  pointer-events: auto;',
111		'}',
112
113		'#cds-panel .cds-header {',
114		'  all: unset;',
115		'  display: flex;',
116		'  align-items: center;',
117		'  justify-content: space-between;',
118		'  padding: 12px 16px;',
119		'  border-bottom: 1px solid rgba(255,255,255,0.08);',
120		'  background: #141414;',
121		'  flex-shrink: 0;',
122		'}',
123		'#cds-panel .cds-header-title {',
124		'  all: unset;',
125		'  display: flex;',
126		'  align-items: center;',
127		'  gap: 10px;',
128		'  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;',
129		'  font-size: 14px;',
130		'  font-weight: 600;',
131		'  color: #f5f5f5;',
132		'}',
133		'#cds-panel .cds-header-title img { width: 24px; height: 24px; border-radius: 6px; }',
134		'#cds-panel .cds-header-actions { all: unset; display: flex; gap: 4px; }',
135		'#cds-panel .cds-header-btn {',
136		'  all: unset;',
137		'  width: 32px;',
138		'  height: 32px;',
139		'  border-radius: 8px;',
140		'  cursor: pointer;',
141		'  display: flex;',
142		'  align-items: center;',
143		'  justify-content: center;',
144		'  color: #fff;',
145		'  transition: background 0.15s, color 0.15s;',
146		'}',
147		'#cds-panel .cds-header-btn:hover { background: rgba(255,255,255,0.08); color: #f5f5f5; }',
148		'#cds-panel .cds-header-btn svg { width: 16px; height: 16px; fill: currentColor; }',
149
150		'#cds-panel .cds-iframe-wrapper {',
151		'  flex: 1;',
152		'  display: flex;',
153		'  flex-direction: column;',
154		'  min-height: 0;',
155		'}',
156		'#cds-panel .cds-iframe {',
157		'  all: unset;',
158		'  flex: 1;',
159		'  display: block;',
160		'  width: 100%;',
161		'  border: none;',
162		'  background: #0b0b0b;',
163		'  -webkit-overflow-scrolling: touch;',
164		'  overscroll-behavior: contain;',
165		'}',
166
167		'#cds-backdrop {',
168		'  display: none;',
169		'  position: fixed;',
170		'  inset: 0;',
171		'  z-index: 2147483645;',
172		'  background: rgba(0,0,0,0.5);',
173		'  backdrop-filter: blur(4px);',
174		'  -webkit-backdrop-filter: blur(4px);',
175		'  opacity: 0;',
176		'  transition: opacity 0.25s ease;',
177		'  pointer-events: none;',
178		'}',
179		'#cds-backdrop.cds-open {',
180		'  opacity: 1;',
181		'  pointer-events: auto;',
182		'}',
183
184		'html.cds-scroll-lock {',
185		'  overflow: hidden !important;',
186		'}',
187		'body.cds-scroll-lock {',
188		'  position: fixed;',
189		'  overflow: hidden !important;',
190		'  width: 100%;',
191		'  overscroll-behavior: none;',
192		'}',
193
194		'@media (max-width: 480px) {',
195		'  #cds-panel {',
196		'    inset: 0 !important;',
197		'    width: 100% !important;',
198		'    height: 100% !important;',
199		'    max-height: 100% !important;',
200		'    border-radius: 0 !important;',
201		'  }',
202		'  #cds-trigger { width: 50px; height: 50px; bottom: 16px; right: 16px; }',
203		'  #cds-trigger svg { width: 22px; height: 22px; }',
204		'}'
205	].join('\n')
206	document.head.appendChild(css)
207
208	var icons = {
209		star: '<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path d="M234.5,114.38l-45.1,39.36,13.51,58.6a16,16,0,0,1-23.84,17.34l-51.11-31-51,31a16,16,0,0,1-23.84-17.34L66.61,153.8,21.5,114.38a16,16,0,0,1,9.11-28.06l59.46-5.15,23.21-55.36a15.95,15.95,0,0,1,29.44,0h0L166,81.17l59.44,5.15a16,16,0,0,1,9.11,28.06Z"/></svg>',
210		gift: '<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path d="M216,72H180.92c.39-.33.79-.65,1.17-1A29.53,29.53,0,0,0,192,49.57,32.62,32.62,0,0,0,158.44,16,29.53,29.53,0,0,0,137,25.91a54.94,54.94,0,0,0-9,14.48,54.94,54.94,0,0,0-9-14.48A29.53,29.53,0,0,0,97.56,16,32.62,32.62,0,0,0,64,49.57,29.53,29.53,0,0,0,73.91,71c.38.33.78.65,1.17,1H40A16,16,0,0,0,24,88v32a16,16,0,0,0,16,16v64a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V136a16,16,0,0,0,16-16V88A16,16,0,0,0,216,72ZM149,36.51a13.69,13.69,0,0,1,10-4.5h.49A16.62,16.62,0,0,1,176,49.08a13.69,13.69,0,0,1-4.5,10c-9.49,8.4-25.24,11.36-35,12.4C137.7,60.89,141,45.5,149,36.51ZM80,49.08A16.62,16.62,0,0,1,96.56,32h.49a13.69,13.69,0,0,1,10,4.5c8.05,9,11.35,24.38,12.56,35.09-9.76-1-25.51-4-35-12.4A13.69,13.69,0,0,1,80,49.08ZM40,88H120v32H40Zm16,48h64v64H56Zm144,64H136V136h64Zm16-80H136V88h80Z"/></svg>',
211		coin: '<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm0-144a56,56,0,1,0,56,56A56.06,56.06,0,0,0,128,72Zm0,96a40,40,0,1,1,40-40A40,40,0,0,1,128,168Z"/></svg>',
212		trophy: '<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path d="M232,64H208V48a8,8,0,0,0-8-8H56a8,8,0,0,0-8,8V64H24A16,16,0,0,0,8,80v32a56.06,56.06,0,0,0,56,56h1.11A56.06,56.06,0,0,0,112,206.41V224H88a8,8,0,0,0,0,16h80a8,8,0,0,0,0-16H144V206.41A56.06,56.06,0,0,0,190.89,168H192a56.06,56.06,0,0,0,56-56V80A16,16,0,0,0,232,64ZM64,152a40,40,0,0,1-40-40V80H48v40a56.06,56.06,0,0,0,24,45.94A40.18,40.18,0,0,1,64,152Zm128,0a40.18,40.18,0,0,1-8,13.94A56.06,56.06,0,0,0,208,120V80h24v32A40,40,0,0,1,192,152Z"/></svg>',
213		tag: '<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path d="M243.31,136,144,36.69A15.86,15.86,0,0,0,132.69,32H40a8,8,0,0,0-8,8v92.69A15.86,15.86,0,0,0,36.69,144L136,243.31a16,16,0,0,0,22.63,0l84.68-84.68a16,16,0,0,0,0-22.63ZM84,96A12,12,0,1,1,96,84,12,12,0,0,1,84,96Z"/></svg>',
214		heart: '<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path d="M240,94c0,70-103.79,126.66-108.21,129a8,8,0,0,1-7.58,0C119.79,220.66,16,164,16,94A62.07,62.07,0,0,1,78,32c20.65,0,38.73,8.88,50,23.89C139.27,40.88,157.35,32,178,32A62.07,62.07,0,0,1,240,94Z"/></svg>',
215		percent:
216			'<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path d="M205.66,61.64a8,8,0,0,1,0,11.32l-128,128a8,8,0,0,1-11.32-11.32l128-128A8,8,0,0,1,205.66,61.64ZM50.54,101.44a36,36,0,1,1,50.92,0A36,36,0,0,1,50.54,101.44Zm11.31-39.61A20,20,0,1,0,90.15,90.15,20,20,0,0,0,61.85,61.83Zm132.71,92.73a36,36,0,1,1-50.92,0A36,36,0,0,1,194.56,154.56Zm-11.31,39.61a20,20,0,1,0-28.3-28.32,20,20,0,0,0,28.3,28.32Z"/></svg>',
217		sparkle:
218			'<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path d="M208,144a15.78,15.78,0,0,1-10.42,14.94l-51.65,19-19,51.61a15.92,15.92,0,0,1-29.88,0L78,177.91l-51.62-19a15.92,15.92,0,0,1,0-29.88l51.65-19,19-51.61a15.92,15.92,0,0,1,29.88,0l19,51.65,51.61,19A15.78,15.78,0,0,1,208,144ZM152,48h16V64a8,8,0,0,0,16,0V48h16a8,8,0,0,0,0-16H184V16a8,8,0,0,0-16,0V32H152a8,8,0,0,0,0,16Zm88,48H232V88a8,8,0,0,0-16,0v8H208a8,8,0,0,0,0,16h8v8a8,8,0,0,0,16,0v-8h8a8,8,0,0,0,0-16Z"/></svg>'
219	}
220
221	var triggerIcon
222	if (iconSvg) {
223		triggerIcon = iconSvg
224	} else if (iconUrl) {
225		triggerIcon =
226			'<img src="' + iconUrl + '" alt="" style="width:28px;height:28px;border-radius:6px;object-fit:contain;" />'
227	} else {
228		triggerIcon = icons[iconChoice] || icons.star
229	}
230
231	var closeIcon =
232		'<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z" fill="currentColor"/></svg>'
233	var expandIcon =
234		'<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path d="M224,104a8,8,0,0,1-16,0V59.32l-66.33,66.34a8,8,0,0,1-11.32-11.32L196.68,48H152a8,8,0,0,1,0-16h64a8,8,0,0,1,8,8Zm-40,24a8,8,0,0,0-8,8v72H48V80h72a8,8,0,0,0,0-16H48A16,16,0,0,0,32,80V208a16,16,0,0,0,16,16H176a16,16,0,0,0,16-16V136A8,8,0,0,0,184,128Z" fill="currentColor"/></svg>'
235
236	var root = document.createElement('div')
237	root.id = 'cds-embed-root'
238	root.style.cssText = 'position:fixed;top:0;left:0;width:0;height:0;overflow:visible;z-index:2147483645;'
239
240	var backdrop = document.createElement('div')
241	backdrop.id = 'cds-backdrop'
242	root.appendChild(backdrop)
243
244	var panel = document.createElement('div')
245	panel.id = 'cds-panel'
246	panel.style.visibility = 'hidden'
247	panel.innerHTML = [
248		'<div class="cds-header">',
249		'  <div class="cds-header-title">',
250		// /logo-mark.png, not /cds.png: the latter is a 4995x4995 (~900KB) asset and
251		// this renders it at 24px on every storefront page that loads the embed.
252		'    <img src="' + appUrl + '/logo-mark.png" alt="" width="24" height="24" />',
253		'    <span>Club Dirt Store</span>',
254		'  </div>',
255		'  <div class="cds-header-actions">',
256		'    <button class="cds-header-btn cds-expand-btn" title="Open in new tab">' + expandIcon + '</button>',
257		'    <button class="cds-header-btn cds-close-btn" title="Close">' + closeIcon + '</button>',
258		'  </div>',
259		'</div>'
260	].join('')
261
262	var iframe = document.createElement('iframe')
263	iframe.className = 'cds-iframe'
264	iframe.setAttribute('allow', 'clipboard-write')
265	iframe.setAttribute('loading', 'lazy')
266	iframe.setAttribute('scrolling', 'yes')
267	var iframeWrapper = document.createElement('div')
268	iframeWrapper.className = 'cds-iframe-wrapper'
269	iframeWrapper.appendChild(iframe)
270	panel.appendChild(iframeWrapper)
271	root.appendChild(panel)
272
273	panel.querySelector('.cds-close-btn').addEventListener('click', doClose)
274	panel.querySelector('.cds-expand-btn').addEventListener('click', function () {
275		window.open(appUrl + '/widget', '_blank')
276		doClose()
277	})
278	backdrop.addEventListener('click', doClose)
279	backdrop.addEventListener(
280		'touchmove',
281		function (e) {
282			if (isOpen) e.preventDefault()
283		},
284		{ passive: false }
285	)
286
287	var trigger = document.createElement('button')
288	trigger.id = 'cds-trigger'
289	trigger.setAttribute('aria-label', 'Open Club Dirt Store rewards')
290	trigger.innerHTML = triggerIcon
291	trigger.addEventListener('click', toggle)
292	root.appendChild(trigger)
293
294	document.body.appendChild(root)
295
296	var isOpen = false
297	var _bodyScrollX = 0
298	var _bodyScrollY = 0
299	var _scrollLocked = false
300
301	function isMobile() {
302		return window.innerWidth <= 480
303	}
304
305	function lockScroll() {
306		if (_scrollLocked) return
307		_scrollLocked = true
308		_bodyScrollX = window.pageXOffset
309		_bodyScrollY = window.pageYOffset
310		document.body.style.top = '-' + _bodyScrollY + 'px'
311		document.body.classList.add('cds-scroll-lock')
312		document.documentElement.classList.add('cds-scroll-lock')
313	}
314
315	function unlockScroll() {
316		if (!_scrollLocked) return
317		_scrollLocked = false
318		document.body.classList.remove('cds-scroll-lock')
319		document.documentElement.classList.remove('cds-scroll-lock')
320		document.body.style.top = ''
321		window.scrollTo(_bodyScrollX, _bodyScrollY)
322	}
323
324	function doOpen() {
325		if (isOpen) return
326		isOpen = true
327		panel.style.visibility = 'visible'
328		if (isMobile()) {
329			lockScroll()
330			backdrop.style.display = 'block'
331		}
332		if (!iframe.src) {
333			iframe.src = appUrl + '/widget?embed=1'
334		}
335		void panel.offsetHeight
336		panel.classList.add('cds-open')
337		backdrop.classList.add('cds-open')
338		trigger.classList.add('cds-is-open')
339		trigger.innerHTML = closeIcon
340	}
341
342	function doClose() {
343		if (!isOpen) return
344		isOpen = false
345		unlockScroll()
346		panel.classList.remove('cds-open')
347		backdrop.classList.remove('cds-open')
348		trigger.classList.remove('cds-is-open')
349		trigger.innerHTML = triggerIcon
350		trigger.focus({ preventScroll: true })
351		setTimeout(function () {
352			if (!isOpen) {
353				panel.style.visibility = 'hidden'
354				backdrop.style.display = ''
355			}
356		}, 350)
357	}
358
359	function toggle() {
360		isOpen ? doClose() : doOpen()
361	}
362
363	window.addEventListener('resize', function () {
364		if (_scrollLocked && !isMobile()) unlockScroll()
365		if (isOpen && isMobile() && !_scrollLocked) lockScroll()
366	})
367
368	document.addEventListener('keydown', function (e) {
369		if (e.key === 'Escape' && isOpen) doClose()
370	})
371
372	// The widget (inside the iframe) can ask us to take the customer to a store
373	// page, e.g. "My Account". Only trust messages from our own iframe origin, and
374	// only follow http(s) URLs, before closing the panel and navigating the host.
375	var appOrigin
376	try {
377		appOrigin = new URL(appUrl).origin
378	} catch (e) {
379		appOrigin = null
380	}
381	window.addEventListener('message', function (e) {
382		if (!appOrigin || e.origin !== appOrigin) return
383		var data = e.data
384		if (!data || data.type !== 'cds:navigate' || typeof data.url !== 'string') return
385		var target
386		try {
387			target = new URL(data.url)
388		} catch (err) {
389			return
390		}
391		if (target.protocol !== 'http:' && target.protocol !== 'https:') return
392		doClose()
393		window.location.href = target.href
394	})
395
396	window.ClubDirtStore = {
397		open: doOpen,
398		close: doClose,
399		toggle: toggle,
400		ready: true,
401		appUrl: appUrl
402	}
403	window.dispatchEvent(new CustomEvent('cds:ready'))
404})()

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.