PageSourceSearch

https://www.theusualnext.com/assets/js/landing-inquiry.js?v=1789327360

js theusualnext.com collected 2026-09-24 09:06:46 UTC 23,218 bytes, 511 lines download raw bytes

1const dialog = document.querySelector('[data-inquiry-dialog]');
2
3if (dialog instanceof HTMLDialogElement) {
4    const form = dialog.querySelector('[data-inquiry-form]');
5    const header = dialog.querySelector('.landing-inquiry__header');
6    const success = dialog.querySelector('[data-inquiry-success]');
7    const error = dialog.querySelector('[data-inquiry-error]');
8    const submits = [...dialog.querySelectorAll('[data-inquiry-submit]')];
9    const submit = submits[0];
10    const requestedContext = dialog.querySelector('[data-inquiry-requested-context]');
11    const program = dialog.querySelector('[name="soma_program"], [name="story_room_program"]');
12    const somaRole = dialog.querySelector('[name="soma_role"]');
13    const somaRequestType = dialog.querySelector('[name="soma_request_type"]');
14    const somaVersion = dialog.querySelector('[name="soma_version"]');
15    const inquiryDetails = dialog.querySelector('[data-inquiry-details]');
16    const pageContext = requestedContext?.value || '';
17    const selection = dialog.querySelector('[data-inquiry-selection]');
18    const duration = dialog.querySelector('[data-inquiry-duration]');
19    const price = dialog.querySelector('[data-inquiry-price]');
20    const dailyPrice = dialog.querySelector('[data-inquiry-daily-price]');
21    const startDate = dialog.querySelector('[data-inquiry-start-date]');
22    const endDate = dialog.querySelector('[data-inquiry-end-date]');
23    const optionPrices = dialog.querySelector('[data-inquiry-option-prices]');
24    const panel = dialog.querySelector('.landing-inquiry__panel');
25    const initialPrice = price?.textContent || '';
26    const inquiryHistoryKey = 'landingInquiryOpen';
27    const mobileInquiry = window.matchMedia('(max-width: 42rem)');
28    const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
29    const attributionKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'utm_id', 'partner_ref'];
30    let opener = null;
31    let swipe = null;
32    let swipeTimer = null;
33    let inquiryStage = 'closed';
34    let inquiryCompleted = false;
35    let inquiryCloseTracked = true;
36    let inquiryDetailsTracked = false;
37    let inquiryEntryPoint = 'direct';
38
39    const quotePath = /\/(?:preventivo|richiedi-preventivo|quote|devis|angebot)\/?$/i;
40    const optionPrice = (select) => select?.selectedOptions?.[0]?.dataset.price?.trim() || '';
41    const selectedOption = (select) => select?.selectedOptions?.[0];
42    const pageUrl = () => `${window.location.pathname}${window.location.search}`;
43    const inquiryUrl = () => `${pageUrl()}#richiesta`;
44    const inquiryTarget = () => form?.dataset.inquiryOfferingKey || form?.dataset.inquiryKey || 'general_inquiry';
45    const inquiryContext = (action = '') => ({
46        offering_kind: form?.dataset.inquiryOfferingKind || 'project',
47        offering_key: inquiryTarget(),
48        market: form?.dataset.inquiryMarket || '',
49        locale: form?.dataset.inquiryLocale || '',
50        entry_point: inquiryEntryPoint,
51        request_type: somaRequestType?.value || '',
52        product_variant: somaVersion?.value === 'Dome 6' ? 'dome_6' : (somaVersion?.value === 'Room' ? 'room' : ''),
53        commercial_program: program?.value || '',
54        action,
55    });
56    const trackInquiry = (eventName, action = '') => {
57        const context = inquiryContext(action);
58        if (typeof window.tunCommerceTrack === 'function') {
59            window.tunCommerceTrack(eventName, inquiryTarget(), context);
60            return;
61        }
62        window.tunAnalyticsTrack?.(eventName, {
63            interaction_target: inquiryTarget(),
64            ...context,
65        });
66    };
67    const trackInquiryClose = () => {
68        if (inquiryCloseTracked || inquiryCompleted || inquiryStage === 'closed') return;
69        inquiryCloseTracked = true;
70        trackInquiry('inquiry_closed', inquiryStage);
71    };
72    const updatePeriodConstraints = () => {
73        if (!startDate || !endDate) return;
74        if (startDate.value) endDate.min = startDate.value;
75        else endDate.removeAttribute('min');
76    };
77    const markInquiryStarted = () => {
78        if (inquiryStage !== 'opened') return;
79        inquiryStage = 'started';
80        trackInquiry('inquiry_started', 'form_interaction');
81    };
82
83    const resetSwipe = () => {
84        window.clearTimeout(swipeTimer);
85        swipeTimer = null;
86        swipe = null;
87        dialog.removeAttribute('data-inquiry-dragging');
88        dialog.removeAttribute('data-inquiry-settling');
89        panel?.style.removeProperty('--inquiry-swipe-x');
90    };
91
92    const settleSwipe = (offset, callback) => {
93        if (!panel) {
94            callback?.();
95            return;
96        }
97
98        dialog.removeAttribute('data-inquiry-dragging');
99        dialog.setAttribute('data-inquiry-settling', '');
100        panel.style.setProperty('--inquiry-swipe-x', `${offset}px`);
101        swipeTimer = window.setTimeout(() => {
102            if (callback) {
103                swipeTimer = null;
104                callback();
105                return;
106            }
107            resetSwipe();
108        }, reducedMotion.matches ? 0 : 230);
109    };
110
111    const close = () => {
112        if (!dialog.open) return;
113        if (window.history.state?.[inquiryHistoryKey]) {
114            window.history.back();
115            return;
116        }
117        dialog.close();
118    };
119
120    const updatePrice = () => {
121        const selectedPrice = optionPrice(selection) || optionPrice(duration) || initialPrice;
122        if (dailyPrice) dailyPrice.textContent = selectedPrice;
123
124        if (price && form?.dataset.inquiryPricing === 'daily-duration') {
125            const item = selectedOption(selection);
126            const unitAmount = Number(item?.dataset.unitAmount || '');
127            const currency = item?.dataset.currency || '';
128            const start = Date.parse(`${startDate?.value || ''}T00:00:00Z`);
129            const end = Date.parse(`${endDate?.value || ''}T00:00:00Z`);
130            const days = Number.isFinite(start) && Number.isFinite(end) && end >= start
131                ? Math.round((end - start) / 86400000) + 1
132                : 0;
133
134            if (days > 0 && Number.isFinite(unitAmount) && unitAmount > 0 && currency) {
135                const amount = new Intl.NumberFormat(form.dataset.inquiryLocale || undefined, {
136                    maximumFractionDigits: 2,
137                }).format(unitAmount * days);
138                const dayLabel = days === 1 ? form.dataset.inquiryDaySingular : form.dataset.inquiryDayPlural;
139                price.textContent = `${currency} ${amount} · ${days} ${dayLabel}`;
140            } else {
141                price.textContent = form.dataset.inquiryTotalPlaceholder || selectedPrice;
142            }
143        } else if (price) {
144            price.textContent = selectedPrice;
145        }
146        if (!optionPrices) return;
147
148        const selected = [...dialog.querySelectorAll('[data-inquiry-option]:checked')]
149            .map((input) => ({ label: input.closest('label')?.querySelector('span')?.textContent?.trim(), price: input.dataset.price?.trim() }))
150            .filter((item) => item.price);
151
152        optionPrices.replaceChildren(...selected.map((item) => {
153            const row = document.createElement('li');
154            row.textContent = `${item.label}: ${item.price}`;
155            return row;
156        }));
157        optionPrices.hidden = selected.length === 0;
158    };
159
160    const selectionFromUrl = (href) => {
161        try {
162            const url = new URL(href, window.location.href);
163            return {
164                key: url.searchParams.get('bundle') || url.searchParams.get('product') || url.searchParams.get('service') || '',
165                context: [...url.searchParams.entries()].map(([key, value]) => `${key}: ${value}`).join(' · '),
166            };
167        } catch {
168            return { key: '', context: '' };
169        }
170    };
171
172    document.querySelectorAll('a[href]').forEach((link) => {
173        const source = link.getAttribute('href') || '';
174        let url;
175        try { url = new URL(source, window.location.href); } catch { return; }
176
177        if (url.origin === window.location.origin && quotePath.test(url.pathname)) {
178            const parsed = selectionFromUrl(source);
179            link.dataset.inquiryOpen = '';
180            link.dataset.inquirySelection ||= parsed.key;
181            link.dataset.inquiryContext ||= parsed.context;
182            link.setAttribute('href', '#richiesta');
183        }
184    });
185
186    const open = (trigger, explicitContext = '') => {
187        const wasOpen = dialog.open;
188        opener = trigger instanceof HTMLElement ? trigger : null;
189        inquiryEntryPoint = trigger?.dataset.inquirySource || trigger?.dataset.analyticsAction || 'direct';
190        const key = trigger?.dataset.inquirySelection || '';
191        const optionKey = trigger?.dataset.inquiryOptionKey || '';
192        const context = explicitContext || trigger?.dataset.inquiryContext || trigger?.textContent?.trim() || '';
193        const matchingSelect = [selection, duration].find((select) => select && [...select.options].some((option) => option.value === key));
194        if (matchingSelect && key) matchingSelect.value = key;
195        if (trigger?.hasAttribute('data-inquiry-clear-selection')) {
196            if (selection) selection.value = '';
197            if (duration) duration.value = '';
198        }
199        if (optionKey) {
200            const option = dialog.querySelector(`[data-inquiry-option][value="${CSS.escape(optionKey)}"]`);
201            if (option) option.checked = true;
202        }
203        if (requestedContext) requestedContext.value = [pageContext, context].filter(Boolean).join(' · ');
204        if (program) {
205            program.value = trigger?.dataset.inquiryProgram || '';
206        }
207        const inquirySomaRole = trigger?.dataset.inquirySomaRole || '';
208        if (somaRole && inquirySomaRole && [...somaRole.options].some((option) => option.value === inquirySomaRole)) {
209            somaRole.value = inquirySomaRole;
210        }
211        if (somaRequestType) {
212            somaRequestType.value = trigger?.dataset.inquirySomaRequestType || 'assessment';
213        }
214        const inquirySomaVersion = trigger?.dataset.inquirySomaVersion || '';
215        if (somaVersion && inquirySomaVersion && [...somaVersion.options].some((option) => option.value === inquirySomaVersion)) {
216            somaVersion.value = inquirySomaVersion;
217        }
218        const inquirySomaPartnerModel = trigger?.dataset.inquirySomaPartnerModel || '';
219        const somaPartnerModel = dialog.querySelector('[name="soma_partner_model"]');
220        if (somaPartnerModel && inquirySomaPartnerModel && [...somaPartnerModel.options].some((option) => option.value === inquirySomaPartnerModel)) {
221            somaPartnerModel.value = inquirySomaPartnerModel;
222        }
223        try {
224            const scenario = JSON.parse(trigger?.dataset.inquirySomaScenario || '{}');
225            Object.entries(scenario).forEach(([name, value]) => {
226                if (!name.startsWith('soma_')) return;
227                const field = dialog.querySelector(`[name="${CSS.escape(name)}"]`);
228                if (field instanceof HTMLInputElement || field instanceof HTMLSelectElement) field.value = String(value);
229            });
230        } catch {
231            // An invalid optional prefill must never prevent the inquiry from opening.
232        }
233        if (error) { error.hidden = true; error.textContent = ''; }
234        updatePeriodConstraints();
235        updatePrice();
236        if (!dialog.open) {
237            window.history.pushState(
238                { ...window.history.state, [inquiryHistoryKey]: true },
239                '',
240                inquiryUrl(),
241            );
242            dialog.showModal();
243        }
244        if (!wasOpen) {
245            inquiryStage = 'opened';
246            inquiryCompleted = false;
247            inquiryCloseTracked = false;
248            inquiryDetailsTracked = false;
249            trackInquiry('inquiry_opened', 'open');
250            window.tunCommercialAnalytics?.trackFormOpen(form, 'landing_inquiry');
251        }
252        document.dispatchEvent(new CustomEvent('tun:turnstile-needed'));
253    };
254
255    selection?.addEventListener('change', updatePrice);
256    duration?.addEventListener('change', updatePrice);
257    startDate?.addEventListener('change', () => {
258        updatePeriodConstraints();
259        updatePrice();
260    });
261    endDate?.addEventListener('change', updatePrice);
262    dialog.querySelectorAll('[data-inquiry-option]').forEach((input) => input.addEventListener('change', updatePrice));
263    inquiryDetails?.addEventListener('toggle', () => {
264        if (!inquiryDetails.open || inquiryDetailsTracked || inquiryStage === 'closed') return;
265        inquiryDetailsTracked = true;
266        trackInquiry('inquiry_details_opened', 'optional_details');
267    });
268    dialog.querySelector('[data-inquiry-options-toggle]')?.addEventListener('click', (event) => {
269        dialog.querySelectorAll('[data-inquiry-option-extra]').forEach((option) => { option.hidden = false; });
270        event.currentTarget.setAttribute('aria-expanded', 'true');
271        event.currentTarget.hidden = true;
272    });
273
274    document.addEventListener('click', (event) => {
275        const trigger = event.target.closest('[data-inquiry-open], [data-ep-form-open], a[href="#richiesta"]');
276        if (!trigger) return;
277        event.preventDefault();
278        open(trigger);
279    });
280
281    document.querySelectorAll('[data-inquiry-source-form]').forEach((sourceForm) => {
282        sourceForm.addEventListener('submit', (event) => {
283            event.preventDefault();
284            if (!sourceForm.reportValidity()) return;
285
286            const details = [...new FormData(sourceForm).entries()]
287                .filter(([name, value]) => name !== '_token' && String(value).trim() !== '')
288                .map(([name, value]) => `${name}: ${String(value).trim()}`)
289                .join(' · ');
290            open(sourceForm, [sourceForm.dataset.inquiryContext, details].filter(Boolean).join(' · '));
291        });
292    });
293
294    if (window.location.hash === '#richiesta') {
295        window.history.replaceState(
296            { ...window.history.state, [inquiryHistoryKey]: false },
297            '',
298            pageUrl(),
299        );
300        open(null);
301    }
302
303    dialog.querySelectorAll('[data-inquiry-close]').forEach((button) => button.addEventListener('click', close));
304    dialog.addEventListener('keydown', (event) => {
305        if (!['corporate-brand-experience', 'partner-project'].includes(dialog.dataset.inquiryVariant) || event.key !== 'Tab' || !dialog.open) return;
306
307        const focusable = [...dialog.querySelectorAll('a[href], button, input, select, textarea, summary, [tabindex]')]
308            .filter((element) => !element.hasAttribute('disabled')
309                && element.getAttribute('tabindex') !== '-1'
310                && !(element.closest('details:not([open])') && element.tagName !== 'SUMMARY')
311                && element.getClientRects().length > 0);
312        if (!focusable.length) return;
313
314        const first = focusable[0];
315        const last = focusable[focusable.length - 1];
316        if (event.shiftKey && (document.activeElement === first || !dialog.contains(document.activeElement))) {
317            event.preventDefault();
318            last.focus();
319        } else if (!event.shiftKey && document.activeElement === last) {
320            event.preventDefault();
321            first.focus();
322        }
323    });
324    dialog.addEventListener('cancel', (event) => {
325        event.preventDefault();
326        close();
327    });
328    window.addEventListener('popstate', (event) => {
329        if (event.state?.[inquiryHistoryKey]) {
330            if (!dialog.open) dialog.showModal();
331            return;
332        }
333        if (dialog.open) dialog.close();
334    });
335    dialog.addEventListener('close', () => {
336        trackInquiryClose();
337        inquiryStage = 'closed';
338        resetSwipe();
339        opener?.focus();
340    });
341    window.addEventListener('pagehide', trackInquiryClose, { once: true });
342
343    form?.addEventListener('input', markInquiryStarted);
344    form?.addEventListener('change', markInquiryStarted);
345    form?.addEventListener('invalid', (event) => {
346        if (event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement || event.target instanceof HTMLTextAreaElement) {
347            event.target.setAttribute('aria-invalid', 'true');
348        }
349    }, true);
350    form?.addEventListener('input', (event) => {
351        if (event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement || event.target instanceof HTMLTextAreaElement) {
352            if (event.target.checkValidity()) event.target.removeAttribute('aria-invalid');
353        }
354    });
355
356    panel?.addEventListener('touchstart', (event) => {
357        if (!mobileInquiry.matches || event.touches.length !== 1) return;
358        const touch = event.touches[0];
359        swipe = {
360            startX: touch.clientX,
361            startY: touch.clientY,
362            offset: 0,
363            startedAt: window.performance.now(),
364            dragging: false,
365        };
366    }, { passive: true });
367
368    panel?.addEventListener('touchmove', (event) => {
369        if (!swipe || event.touches.length !== 1) return;
370        const touch = event.touches[0];
371        const deltaX = touch.clientX - swipe.startX;
372        const deltaY = touch.clientY - swipe.startY;
373
374        if (!swipe.dragging) {
375            if (Math.abs(deltaY) > 10 && Math.abs(deltaY) > deltaX) {
376                swipe = null;
377                return;
378            }
379            if (deltaX <= 10 || deltaX <= Math.abs(deltaY) * 1.15) return;
380            swipe.dragging = true;
381            dialog.setAttribute('data-inquiry-dragging', '');
382        }
383
384        event.preventDefault();
385        swipe.offset = Math.max(0, deltaX);
386        panel.style.setProperty('--inquiry-swipe-x', `${swipe.offset}px`);
387    }, { passive: false });
388
389    const finishSwipe = () => {
390        if (!swipe?.dragging || !panel) {
391            swipe = null;
392            return;
393        }
394
395        const elapsed = Math.max(1, window.performance.now() - swipe.startedAt);
396        const velocity = swipe.offset / elapsed;
397        const shouldClose = swipe.offset >= Math.max(72, panel.clientWidth * .24)
398            || (swipe.offset >= 48 && velocity > .5);
399
400        if (shouldClose) {
401            settleSwipe(window.innerWidth, close);
402            return;
403        }
404        settleSwipe(0);
405    };
406
407    panel?.addEventListener('touchend', finishSwipe, { passive: true });
408    panel?.addEventListener('touchcancel', () => {
409        if (swipe?.dragging) settleSwipe(0);
410        else swipe = null;
411    }, { passive: true });
412
413    form?.addEventListener('submit', async (event) => {
414        event.preventDefault();
415        markInquiryStarted();
416        inquiryStage = 'submit_attempted';
417        trackInquiry('inquiry_submit_attempted', 'submit');
418        if (!form.reportValidity()) {
419            inquiryStage = 'validation_failed';
420            trackInquiry('inquiry_validation_failed', 'client_validation');
421            window.tunCommercialAnalytics?.trackFormError(form, 'landing_inquiry');
422            return;
423        }
424        if (!submit) return;
425        const originalLabels = submits.map((button) => button.textContent);
426        form.setAttribute('aria-busy', 'true');
427        submits.forEach((button) => {
428            button.disabled = true;
429            button.textContent = form.dataset.sendingLabel;
430        });
431        if (error) { error.hidden = true; error.textContent = ''; }
432
433        try {
434            await window.tunAnalyticsAttribution?.();
435            const formData = new FormData(form);
436            const currentQuery = new URLSearchParams(window.location.search);
437            attributionKeys.forEach((key) => {
438                const value = currentQuery.get(key)?.trim();
439                if (value && !formData.has(key)) formData.append(key, value);
440            });
441            const response = await fetch(form.action, {
442                method: 'POST',
443                body: formData,
444                headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
445            });
446            const data = await response.json().catch(() => ({}));
447            if (!response.ok) {
448                throw new Error(form.dataset.errorMessage);
449            }
450
451            if (header) header.hidden = true;
452            form.hidden = true;
453            success.hidden = false;
454            success.textContent = data.message;
455            success.focus();
456            inquiryCompleted = true;
457            inquiryCloseTracked = true;
458            inquiryStage = 'confirmed';
459            const offeringKey = form.dataset.inquiryOfferingKey || form.dataset.inquiryKey || 'general_inquiry';
460            const offeringKind = form.dataset.inquiryOfferingKind || 'project';
461            const requestType = somaRequestType?.value || '';
462            const conversionType = {
463                business_case: 'business_case_request',
464                demo: 'demo_request',
465                partner_project: 'partner_project',
466                lighthouse_candidate: 'lighthouse_candidate',
467            }[requestType] || (dialog.dataset.inquiryVariant === 'availability' ? 'availability_request' : 'landing_inquiry');
468            window.tunAnalyticsTrack?.('lead_confirmation_viewed', {
469                transaction_id: data.transaction_id || '',
470                conversion_type: conversionType,
471                landing_key: form.dataset.inquiryKey || 'general_inquiry',
472                offering_kind: offeringKind,
473                offering_key: offeringKey,
474                market: form.dataset.inquiryMarket || '',
475                locale: form.dataset.inquiryLocale || '',
476                entry_point: inquiryEntryPoint,
477                request_type: requestType,
478                product_variant: somaVersion?.value === 'Dome 6' ? 'dome_6' : (somaVersion?.value === 'Room' ? 'room' : ''),
479                commercial_program: program?.value || '',
480                source_system: 'website_form',
481            });
482            window.tunCommercialAnalytics?.trackFormSuccess(
483                form,
484                `landing-inquiry:${data.transaction_id || data.reference || ''}`,
485                'landing_inquiry',
486            );
487            form.dispatchEvent(new CustomEvent('tun:inquiry-confirmed', {
488                bubbles: true,
489                detail: {
490                    reference: data.reference || '',
491                    transaction_id: data.transaction_id || '',
492                    offering_key: offeringKey,
493                },
494            }));
495        } catch (failure) {
496            inquiryStage = 'submit_failed';
497            trackInquiry('inquiry_submit_failed', 'request_failed');
498            window.tunCommercialAnalytics?.trackFormError(form, 'landing_inquiry');
499            error.textContent = failure.message || form.dataset.errorMessage;
500            error.hidden = false;
501            error.focus?.();
502        } finally {
503            form.setAttribute('aria-busy', 'false');
504            submits.forEach((button, index) => {
505                button.disabled = false;
506                button.textContent = originalLabels[index];
507            });
508        }
509    });
510
511}

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.