PageSourceSearch

https://eatthistours.com/wp-content/plugins/ett-customizations/assets/ett-deposit-timer.js?ver=1790253892

js eatthistours.com collected 2026-09-26 08:24:24 UTC 10,318 bytes, 249 lines download raw bytes

1/**
2 * ETT Deposit Timer - persistent countdown widget.
3 *
4 * Draws the deposit hold countdown on EVERY page of the site while a hold is live, so the
5 * customer keeps seeing it even after browsing away from the payment page.
6 *
7 * Rendered entirely client-side, on purpose: the live site runs a full-page cache, so any
8 * server-rendered markup could be baked into cached HTML and shown to the wrong visitor.
9 * This script ships on every page and decides for itself, from the per-browser cookie,
10 * whether to draw anything.
11 *
12 * The cookie is only a hint for drawing. The real deadline lives on the booking, and
13 * ett_deposit_status (admin-ajax, never cached) is the authority: it re-syncs the
14 * remaining time on load and performs the cancellation once the hold is genuinely up.
15 * Editing the cookie can therefore change what this browser shows, but never what is
16 * charged or cancelled.
17 */
18(function () {
19	'use strict';
20
21	var CFG = window.ettDepositTimer || {};
22	var COLLAPSE_KEY = 'ettDepositTimerCollapsed';
23	var el = null, clock = null, bodyEl = null, toggleEl = null;
24	var remaining = 0, ticker = null, endsAt = 0, lastSync = 0;
25
26	// ── helpers ──────────────────────────────────────────────────────────────
27
28	function pad(n) { return (n < 10 ? '0' : '') + n; }
29
30	function readCookie() {
31		var name = (CFG.cookie || 'ett_deposit_timer') + '=';
32		var parts = document.cookie ? document.cookie.split(';') : [];
33		for (var i = 0; i < parts.length; i++) {
34			var c = parts[i].replace(/^\s+/, '');
35			if (c.indexOf(name) !== 0) continue;
36			try {
37				return JSON.parse(atob(decodeURIComponent(c.substring(name.length))));
38			} catch (e) {
39				return null;
40			}
41		}
42		return null;
43	}
44
45	function onPaymentFlow() {
46		var p = window.location.pathname || '';
47		var q = window.location.search || '';
48		return /\/(checkout|cart|order-received|order-pay)/i.test(p) ||
49			(CFG.payFlag && q.indexOf(CFG.payFlag) !== -1);
50	}
51
52	function money(n) {
53		return String(Math.round(n)).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
54	}
55
56	function post(action, cb) {
57		if (!CFG.ajaxUrl) return;
58		try {
59			var xhr = new XMLHttpRequest();
60			xhr.open('POST', CFG.ajaxUrl, true);
61			xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
62			xhr.withCredentials = true;
63			xhr.onload = function () {
64				var res = null;
65				try { res = JSON.parse(xhr.responseText); } catch (e) {}
66				cb(res && res.success ? res.data : null);
67			};
68			xhr.onerror = function () { cb(null); };
69			xhr.send('action=' + encodeURIComponent(action));
70		} catch (e) { cb(null); }
71	}
72
73	// ── rendering ────────────────────────────────────────────────────────────
74
75	function remove() {
76		if (ticker) { clearInterval(ticker); ticker = null; }
77		if (el && el.parentNode) { el.parentNode.removeChild(el); }
78		el = null;
79	}
80
81	function shellStyle(accent) {
82		return 'position:fixed;right:16px;bottom:16px;z-index:999998;width:320px;' +
83			'max-width:calc(100vw - 32px);background:#fff;border:1px solid #f0b3ad;' +
84			'border-top:4px solid ' + accent + ';border-radius:8px;' +
85			'box-shadow:0 6px 24px rgba(0,0,0,0.22);font-family:sans-serif;color:#222;overflow:hidden;';
86	}
87
88	function renderExpired() {
89		remove();
90		var wa = CFG.whatsapp || '';
91		var box = document.createElement('div');
92		box.className = 'ett-deposit-timer-expired';
93		box.style.cssText = shellStyle('#8a1c1c');
94		box.innerHTML =
95			'<div style="padding:14px;">' +
96				'<div style="font-weight:700;font-size:14px;color:#8a1c1c;margin-bottom:6px;">Booking hold expired</div>' +
97				'<div style="font-size:12.5px;line-height:1.55;">We could not confirm your deposit in time, so your ' +
98				'booking has been suspended. If you had any trouble paying, message us on WhatsApp ' +
99				'<a href="' + (CFG.waLink || '#') + '" target="_blank" rel="noopener noreferrer" ' +
100				'style="color:#8a1c1c;font-weight:700;">
100' + wa + '</a> ' +
101				'and we will get your places back.</div>' +
102			'</div>';
103		document.body.appendChild(box);
104		el = box;
105	}
106
107	function render(data) {
108		remove();
109		var wa     = CFG.whatsapp || '';
110		var url    = data.url || '';
111		var amount = parseFloat(data.amount || 0);
112		var away   = !onPaymentFlow();
113
114		var box = document.createElement('div');
115		box.className = 'ett-deposit-timer';
116		box.style.cssText = shellStyle('#c0392b');
117		box.innerHTML =
118			'<div class="ett-dt-head" style="display:flex;align-items:center;justify-content:space-between;' +
119				'padding:10px 14px;background:#fdecea;border-bottom:1px solid #f0b3ad;cursor:pointer;">' +
120				'<span style="font-weight:700;font-size:13px;color:#8a1c1c;">Deposit required to hold your booking</span>' +
121				'<span class="ett-dt-toggle" style="font-size:16px;line-height:1;color:#8a1c1c;padding-left:8px;">&minus;</span>' +
122			'</div>' +
123			'<div class="ett-dt-body" style="padding:14px;">' +
124				'<div style="text-align:center;margin-bottom:10px;">' +
125					'<div style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:#666;">Time remaining</div>' +
126					'<div class="ett-dt-clock" style="font-size:30px;font-weight:800;color:#c0392b;line-height:1.2;">--:--</div>' +
127				'</div>' +
128				'<div style="font-size:12.5px;line-height:1.55;color:#333;">Your booking will be ' +
129					'<strong>automatically revoked</strong> if the deposit' +
130					(amount > 0 ? ' of <strong>' + money(amount) + ' GEL</strong>' : '') +
131					' is not paid before the timer runs out.</div>' +
132				(away && url ?
133					'<div class="ett-dt-away" style="margin-top:10px;padding:9px 11px;background:#fff8e6;' +
134					'border:1px solid #e8c98a;border-radius:5px;font-size:12.5px;line-height:1.5;">' +
135					'You have left the payment page. <a href="' + url + '" style="color:#8a1c1c;font-weight:700;">' +
136					'Return to payment</a> to finish your deposit.</div>' : '') +
137				(away && url ? '<a href="' + url + '" style="display:block;margin-top:12px;padding:11px 14px;' +
138					'background:#c0392b;color:#fff;border-radius:5px;font-weight:700;font-size:14px;' +
139					'text-align:center;text-decoration:none;">Pay deposit now</a>' : '') +
140				'<div style="margin-top:11px;padding-top:10px;border-top:1px solid #eee;font-size:12px;' +
141					'line-height:1.5;color:#555;">Trouble paying? Contact us on WhatsApp ' +
142					'<a href="' + (CFG.waLink || '#') + '" target="_blank" rel="noopener noreferrer" ' +
143					'style="color:#8a1c1c;font-weight:700;">' + wa + '</a>' +
144					' and we will help.</div>' +
145			'</div>';
146
147		document.body.appendChild(box);
148		el       = box;
149		clock    = box.querySelector('.ett-dt-clock');
150		bodyEl   = box.querySelector('.ett-dt-body');
151		toggleEl = box.querySelector('.ett-dt-toggle');
152
153		// Collapsed state persists across pages, so the widget can be tucked away without
154		// ever disappearing entirely.
155		var collapsed = false;
156		try { collapsed = sessionStorage.getItem(COLLAPSE_KEY) === '1'; } catch (e) {}
157		applyCollapsed(collapsed);
158
159		var head = box.querySelector('.ett-dt-head');
160		if (head) {
161			head.addEventListener('click', function () {
162				collapsed = !collapsed;
163				applyCollapsed(collapsed);
164				try { sessionStorage.setItem(COLLAPSE_KEY, collapsed ? '1' : '0'); } catch (e) {}
165			});
166		}
167
168		paint();
169		ticker = setInterval(tick, 1000);
170	}
171
172	function applyCollapsed(collapsed) {
173		if (!bodyEl || !toggleEl) return;
174		bodyEl.style.display = collapsed ? 'none' : '';
175		toggleEl.innerHTML = collapsed ? '&plus;' : '&minus;';
176	}
177
178	function paint() {
179		if (!clock) return;
180		if (remaining <= 0) { clock.textContent = '00:00'; return; }
181		clock.textContent = pad(Math.floor(remaining / 60)) + ':' + pad(remaining % 60);
182		if (remaining <= 300) { clock.style.color = '#8a1c1c'; }
183	}
184
185	/**
186	 * Derive the remaining time from a fixed end timestamp rather than counting ticks down.
187	 * A backgrounded tab throttles (or entirely suspends) setInterval, so a decrementing
188	 * counter silently loses time and looks paused. Reading the clock each tick means the
189	 * display is always the true remaining time the moment the tab is looked at again.
190	 */
191	function tick() {
192		remaining = Math.max(0, Math.round((endsAt - Date.now()) / 1000));
193		paint();
194		if (remaining <= 0) {
195			if (ticker) { clearInterval(ticker); ticker = null; }
196			// Time is up locally: the server decides (it may have been paid meanwhile).
197			post('ett_deposit_status', function (d) {
198				if (d && d.status === 'active') { start(d); return; }
199				if (d && d.status === 'paid')   { remove(); return; }
200				renderExpired();
201			});
202		}
203	}
204
205	/** Re-sync against the server, which owns the real deadline. */
206	function sync(force) {
207		var now = Date.now();
208		if (!force && now - lastSync < 5000) return; // avoid hammering on rapid tab flips
209		lastSync = now;
210		post('ett_deposit_status', function (d) {
211			if (!d) return;
212			if (d.status === 'active')  { start(d); }
213			else if (d.status === 'paid')    { remove(); }
214			else if (d.status === 'expired') { renderExpired(); }
215		});
216	}
217
218	function start(data) {
219		remaining = parseInt(data.remaining, 10);
220		if (isNaN(remaining) || remaining <= 0) { renderExpired(); return; }
221		endsAt = Date.now() + (remaining * 1000);
222		render(data);
223	}
224
225	// ── boot ─────────────────────────────────────────────────────────────────
226
227	function init() {
228		if (!readCookie()) return; // no hold for this browser: draw nothing
229
230		// Always confirm with the server before drawing, so a stale cookie (paid in another
231		// tab, cancelled by an agent, deadline already gone) never shows a live countdown.
232		post('ett_deposit_status', function (d) {
233			if (!d) return;
234			if (d.status === 'active')  { start(d); }
235			else if (d.status === 'expired') { renderExpired(); }
236		});
237	}
238
239	document.addEventListener('visibilitychange', function () {
240		if (document.visibilityState === 'visible' && el) { sync(false); }
241	});
242	window.addEventListener('focus', function () { if (el) { sync(false); } });
243
244	if (document.readyState === 'loading') {
245		document.addEventListener('DOMContentLoaded', init);
246	} else {
247		init();
248	}
249})();

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.