(() => { 'use strict'; const PRIZE_DATA_KEY = 'jf-wheel-2026-mask-v1'; function encryptPrizeData(value) { const data = new TextEncoder().encode(JSON.stringify(value)); const key = new TextEncoder().encode(PRIZE_DATA_KEY); let binary = ''; for (let index = 0; index < data.length; index++) { binary += String.fromCharCode(data[index] ^ key[index % key.length]); } return 'jf-mask-v1:' + btoa(binary); } function decryptPrizeData(value) { if (typeof value !== 'string' || !value.startsWith('jf-mask-v1:')) { throw new Error('Fortune: invalid encoded prize data.'); } const binary = atob(value.slice('jf-mask-v1:'.length)); const key = new TextEncoder().encode(PRIZE_DATA_KEY); const data = Uint8Array.from(binary, (character, index) => character.charCodeAt(0) ^ key[index % key.length]); return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(data)); } function createFortuneConfetti(root, motion) { const canvas = document.querySelector('[data-fortune-confetti]') || document.createElement('canvas'); const context = canvas.getContext('2d'); if (!context) return { play() {}, stop() {} }; canvas.classList.add('fortune-confetti'); canvas.setAttribute('data-fortune-confetti', ''); canvas.setAttribute('aria-hidden', 'true'); canvas.hidden = true; if (!document.getElementById('fortune-confetti-styles')) { const styles = document.createElement('style'); styles.id = 'fortune-confetti-styles'; styles.textContent = ` [data-fortune-confetti] { position: fixed; inset: 0; z-index: 2147483000; display: block; width: 100%; height: 100%; pointer-events: none; } [data-fortune-confetti][hidden] { display: none !important; } @media (prefers-reduced-motion: reduce) { [data-fortune-confetti] { display: none !important; } } `; document.head.appendChild(styles); } document.body.appendChild(canvas); function isVisible() { if (!root.isConnected || !root.getClientRects().length) return false; const host = root.closest('[data-modal]') || root.closest('.fortune-wrapper') || root; if (host.hasAttribute('data-modal') && !host.classList.contains('is-active')) return false; const style = getComputedStyle(host); return style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity) > 0; } const colors = ['#39ba39', '#ff5934', '#ffc700', '#00abc2', '#ff82b2', '#9a1550']; const DURATION = 4200; const MOBILE_PARTICLES = 68; const MOBILE_SCALE = 1.3; const DESKTOP_PARTICLES = 104; let frame = 0; let activeRoot = null; let width = 0; let height = 0; function stop() { cancelAnimationFrame(frame); frame = 0; activeRoot = null; context.clearRect(0, 0, width, height); canvas.hidden = true; } function play() { stop(); if (motion.matches || document.hidden || !isVisible()) return; const result = root.querySelector('[data-fortune="info-result"]'); const wheel = root.querySelector('.fortune__spin-wrapper') || root.querySelector('[data-fortune="wheel"]'); if (!result || !wheel) return; const wheelBox = wheel.getBoundingClientRect(); const resultBox = result.getBoundingClientRect(); if (!resultBox.width || !resultBox.height) return; width = document.documentElement.clientWidth; height = window.innerHeight; if (resultBox.top >= height && wheelBox.top >= height) return; if (resultBox.bottom <= 0 && wheelBox.bottom <= 0) return; const ratio = Math.min(window.devicePixelRatio || 1, 2); canvas.width = Math.ceil(width * ratio); canvas.height = Math.ceil(height * ratio); context.setTransform(ratio, 0, 0, ratio, 0, 0); canvas.hidden = false; activeRoot = root; const sideBySide = resultBox.left >= wheelBox.right - 24; const headingBox = (result.querySelector('.fortune__title') || result).getBoundingClientRect(); const belowWheel = !sideBySide && wheelBox.width > 0 && wheelBox.bottom > 0 && wheelBox.top < height; const scale = Math.min(1.25, Math.max(0.85, width / 1100)) * (width < 768 ? MOBILE_SCALE : 1); const count = width < 768 ? MOBILE_PARTICLES : DESKTOP_PARTICLES; const started = performance.now(); const particles = Array.from({ length: count }, (_, index) => { const horizontal = Math.random() * 1.7 - 0.85; let x; let y; if (belowWheel) { // The lower arc follows the wheel, so paper emerges under its rim. x = wheelBox.left + wheelBox.width * (0.5 + horizontal * 0.5); y = wheelBox.top + wheelBox.height * (0.5 + 0.48 * Math.sqrt(1 - horizontal ** 2)); } else { x = resultBox.left + resultBox.width * (0.07 + Math.random() * 0.86); y = Math.max(12, headingBox.top - 30) + Math.random() * 12; } return { x: Math.max(5, Math.min(width - 5, x)), y: Math.max(-10, Math.min(height * 0.8, y)), born: started + (index < count * 0.6 ? Math.random() * 150 : 220 + Math.random() * 360), life: 2.8 + Math.random() * 0.7, vx: (Math.random() - 0.5) * 85, vy: -25 - Math.random() * 65, gravity: 135 + Math.random() * 90, width: (2.5 + Math.random() * 2.5) * scale, height: (6 + Math.random() * 6) * scale, phase: Math.random() * Math.PI * 2, rotation: Math.random() * Math.PI, spin: (Math.random() - 0.5) * 9, sway: 8 + Math.random() * 16, color: colors[index % colors.length] }; }); function draw(now) { context.clearRect(0, 0, width, height); if (now - started > DURATION || !isVisible()) { stop(); return; } for (const particle of particles) { const time = (now - particle.born) / 1000; if (time < 0 || time > particle.life) continue; const x = particle.x + particle.vx * time + Math.sin(time * 5 + particle.phase) * particle.sway * time; const y = particle.y + particle.vy * time + particle.gravity * time * time * 0.5; if (y > height + 20) continue; const fadeIn = Math.min(1, time / 0.12); const fadeOut = Math.min(1, (particle.life - time) / 0.7); context.save(); context.globalAlpha = fadeIn * fadeOut; context.fillStyle = particle.color; context.translate(x, y); context.rotate(particle.rotation + time * particle.spin); context.scale(1, 0.2 + Math.abs(Math.cos(time * 8 + particle.phase)) * 0.8); context.fillRect(-particle.width / 2, -particle.height / 2, particle.width, particle.height); context.restore(); } frame = requestAnimationFrame(draw); } frame = requestAnimationFrame(draw); } if ('IntersectionObserver' in window) { const visibility = new IntersectionObserver(entries => { if (activeRoot && entries.some(entry => !entry.isIntersecting)) stop(); }); visibility.observe(root); } document.addEventListener('visibilitychange', () => { if (document.hidden) stop(); }); window.addEventListener('resize', stop, { passive: true }); window.addEventListener('pagehide', stop); motion.addEventListener('change', () => { if (motion.matches) stop(); }); return { play, stop }; } function initFortune() { const STORAGE_KEY = 'justfood:fortune:v3'; const DISMISSED_KEY = 'justfood:fortune:dismissed:v3'; const ENDPOINT = 'https://sendsay.ru/form/justfood/7/'; const root = document.querySelector('.fortune'); if (!root) { console.warn('Fortune: не найден контейнер .fortune.'); return; } const wheel = root.querySelector('[data-fortune="wheel"]'); const start = root.querySelector('[data-fortune="info-start"]'); const result = root.querySelector('[data-fortune="info-result"]'); if (!wheel || !start || !result) { console.warn('Fortune: нужны элементы data-fortune="wheel", "info-start" и "info-result".', { wheel, start, result }); return; } const button = start.querySelector('.fortune__button'); const prizeText = result.querySelector('[data-fortune="prize-text"]'); const form = result.querySelector('form'); if (!button || !prizeText || !form) { console.warn('Fortune: не найдены кнопка .fortune__button, data-fortune="prize-text" или форма.', { button, prizeText, form }); return; } if (form.dataset.fortuneInitialized) return; form.dataset.fortuneInitialized = 'true'; function ensureElement(parent, selector, create) { const existing = parent.querySelector(selector); if (existing) return existing; const element = create(); parent.appendChild(element); return element; } function makeMessage(name, className = '') { const element = document.createElement('p'); element.dataset.fortune = name; element.className = className; return element; } const condition = ensureElement(result, '[data-fortune="prize-condition"]', () => { return makeMessage('prize-condition', 'fortune__prize-condition'); }); prizeText.insertAdjacentElement('afterend', condition); const status = ensureElement(root, '[data-fortune="status"]', () => makeMessage('status', 'visually-hidden')); status.setAttribute('role', 'status'); status.setAttribute('aria-live', 'polite'); // Webflow may not contain the demo's visually-hidden CSS. status.style.cssText = 'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);'; const formStatus = ensureElement(form.parentElement, '[data-fortune="form-status"]', () => makeMessage('form-status')); formStatus.setAttribute('role', 'status'); formStatus.setAttribute('aria-live', 'polite'); formStatus.style.margin = '0'; const success = ensureElement(form.parentElement, '.w-form-done', () => { const element = document.createElement('div'); element.className = 'w-form-done'; element.textContent = 'Форма успешно отправлена. Проверьте электронную почту.'; return element; }); const failure = ensureElement(form.parentElement, '.w-form-fail', () => { const element = document.createElement('div'); element.className = 'w-form-fail'; return element; }); failure.setAttribute('role', 'alert'); const prizeInput = ensureElement(form, '[data-luck="prize"], input[name="q241"]', () => { const input = document.createElement('input'); input.type = 'hidden'; input.dataset.luck = 'prize'; input.readOnly = true; return input; }); prizeInput.name = 'q241'; prizeInput.setAttribute('data-name', 'q241'); const emailInput = form.querySelector('input[type="email"]'); if (emailInput) { emailInput.name = '_member_email'; emailInput.setAttribute('data-name', '_member_email'); } // Visibility must also work when only this JS is inserted in Webflow. const resultDisplay = result.style.display && result.style.display !== 'none' ? result.style.display : getComputedStyle(start).display; const formDisplay = form.style.display === 'none' ? '' : form.style.display; start.hidden = false; result.hidden = true; result.style.display = 'none'; success.style.display = 'none'; failure.style.display = 'none'; root.dataset.state = 'start'; const motion = window.matchMedia('(prefers-reduced-motion: reduce)'); const confetti = createFortuneConfetti(root, motion); // Sector 0 is top-right. The remaining sectors proceed clockwise. const ENCODED_PRIZES = 'jf-mask-v1:MR0PAwERCQkPCBIDBg1dUUNL/cfh1LaQpuu05LyT4oIS5pC90VO7kqaPutT89bjbtOz9j+G55pRNscS7naaLutb9wEpJRw9CVlUQDA8hNDAgD1oTHQNEEAARR1YdHhJBU04ZDgFJF0IdSAVCGQwMEQVCXBIIFA8QTQhJWR9FBgMPTUpXVUz9huCP5pi92FO7mKaFu+0Np9W11UwYEuCN56292aL6/crh1LafV7jStdwNAwgSAR9dQZHpkFQdSAVCEw1HX05+eXl2fWxYQ19JWhNYDQ5ZVVJXUEAPQVVRQkIfQ0ldAVRSBQhJHhwMCgIPCBIQSwEWQwcCWRpUSFwPp8m137yV4oTijP3dQUZbHVbT6NsPW0oGCghIEAoQdGIjNCBeHUYTRkRaEgECDRgPCAICGg8eBBAfQgQTUFUBVQsKCwhERlldWA9XQ1EWAQ0THg9ZGw1HX079ruCH56y87qLtDaaEutL8/Ei110z9jeCM5pm90aLr/cjh0EQBVQsKAQkPCBJhY30oMyM5ZCwTRkRaEgECDRgPCAAeFF4IAgcEX1QLXUoPFAcLAQVZW19cFBdPQw5HVlRFAxJBEkpfR46loAIFEw293KPbDaaMutP9w7jQtdf8vBDigf3Zsc26raaPutT9ybjcRbyY4oTjvQ0HFAAfSxleDkQBVQsKAQkPCBIAAWcrQ19JWhNYDQ5ZVVJQSU5eV1NGWV9PW0ZHDxVeBAJEAwEKC04XEBJPGlZPFRofQRMTUET91rjftdT9huCI5p1NUFNbHUYRiOSQVURHBgNJVxIIFBxdUUM5eDQTRkRaEgECDRgPCAEHGg8eBBAfQgQTUFQBVQsKCwhERlldWA9XQ1FHDwZDDxBEGB0WJgNJV0MQDHZPUENbHaaRusX95ko4GEBWEERbQkEIQ0lJ/eLh1EbP//pXXEkN4o3ihg293qPe/Pbh2Lemp9FFtdv9guCI5p291lO7lVYEWlYNp9m127yQ47Pjt/3TscFL/cvh2kb9xbnntdL8suCM5pRPTVEIQhJUSFwPOz0mLjUPHhJFU0QKCQdJF0cERkReEgsRCh4PCAEeFE4CDxcCWR9eBEQXVUoYSRcPRllGWkhPW1FZDUYBWkb9xrjbtdH8seGz5pO901O7kKaBSrafpuq1272t4o7ijw291qPb/czh2raaVURHBgNJVxIIFB9dUUMneDV6SEoPAA0MAgRZEAoABgFPEhYIWRlDSFwdW0oGCgJJW0RbWUNPW1FJUCs='; const prizes = decryptPrizeData(ENCODED_PRIZES); let submitting = false; let storageFailed = false; let spinning = false; let finished = false; let rotation = 0; let selectedPrize = null; let wheelAnimation = null; let contentAnimation = null; let autoOpenTimer = null; let dismissed = false; function isFortuneDismissed() { if (dismissed) return true; try { return localStorage.getItem(DISMISSED_KEY) !== null; } catch (error) { return true; } } function rememberFortuneDismissal() { dismissed = true; window.clearTimeout(autoOpenTimer); autoOpenTimer = null; try { localStorage.setItem(DISMISSED_KEY, '1'); } catch (error) { // Retain the in-memory dismissal even if browser storage becomes unavailable. } } const modal = root.closest('[data-modal]') || root.closest('.fortune-wrapper') || root; function isFortuneOpen() { if (!modal.isConnected || modal.hidden || modal.getAttribute('aria-hidden') === 'true') return false; if (modal.hasAttribute('data-modal')) return modal.classList.contains('is-active'); const style = getComputedStyle(modal); return style.display !== 'none' && style.visibility !== 'hidden' && modal.getClientRects().length > 0; } // Only remember the user's action; the page's modal manager still closes the window. document.addEventListener('click', event => { if (!(event.target instanceof Element)) return; const control = event.target.closest('[data-close-modal], [data-fortune="close"], [data-fortune-close]'); if (control && modal.contains(control)) rememberFortuneDismissal(); }, true); let wasOpen = isFortuneOpen(); const modalVisibility = new MutationObserver(records => { const open = isFortuneOpen(); const hadOpenClass = modal.hasAttribute('data-modal') && records.some(record => record.attributeName === 'class' && record.oldValue?.split(/\s+/).includes('is-active') ); if ((wasOpen || hadOpenClass) && !open) rememberFortuneDismissal(); wasOpen = open; }); modalVisibility.observe(modal, { attributes: true, attributeFilter: ['class', 'style', 'hidden', 'aria-hidden'], attributeOldValue: true }); const normalize = angle => ((angle % 360) + 360) % 360; const random = () => crypto.getRandomValues(new Uint32Array(1))[0] / 0x100000000; function drawPrize() { const total = prizes.reduce((sum, prize) => sum + prize.weight, 0); const value = random() * total; let limit = 0; for (const prize of prizes) { limit += prize.weight; if (prize.weight > 0 && value < limit) return prize; } return prizes.filter(prize => prize.weight > 0).at(-1); } function progress(time) { return 1 - (1 - time) ** 5 * (1 + 5 * time); } function updateButton() { const disabled = spinning || finished || storageFailed; button.setAttribute('aria-disabled', String(disabled)); button.classList.toggle('is-disable', disabled); if ('disabled' in button) button.disabled = disabled; } function storageError(error) { storageFailed = true; status.classList.remove('visually-hidden'); status.style.cssText = ''; status.textContent = 'Не удалось сохранить результат в браузере. Разрешите хранение данных и обновите страницу.'; console.error('Fortune: ошибка хранилища.', error); updateButton(); } function readState() { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return null; const saved = JSON.parse(raw); const prize = prizes.find(item => saved?.version === 3 && item.sector === saved.sector && (item.code === saved.code || item.previousCodes?.includes(saved.code)) && item.weight > 0); if (!prize) throw new Error('Некорректный сохранённый приз'); return { ...prize, status: saved.status === 'submitted' ? 'submitted' : 'pending' }; } function saveState(prize) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify({ version: 3, sector: prize.sector, code: prize.code, status: prize.status })); return true; } catch (error) { storageError(error); return false; } } function formatPrizeMessage(prize) { return prize ? `Ты выиграл «${prize.title}» — промокод «${prize.code}»` : ''; } function renderForm() { const submitted = selectedPrize?.status === 'submitted'; prizeInput.value = formatPrizeMessage(selectedPrize); form.hidden = submitted; form.style.display = submitted ? 'none' : formDisplay; success.style.display = submitted ? 'block' : 'none'; failure.style.display = 'none'; formStatus.textContent = ''; } function restoreState(prize) { selectedPrize = prize; finished = true; rotation = normalize(180 - (292.5 + prize.sector * 45)); wheel.style.transform = `rotate(${rotation}deg)`; void showResult(prize, false); updateButton(); } async function showResult(prize, animate = true) { prizeText.textContent = prize.title; condition.textContent = prize.condition; condition.hidden = !prize.condition; condition.style.display = prize.condition ? '' : 'none'; form.dataset.promocode = prize.code; form.dataset.prize = prize.title; renderForm(); if (animate && !motion.matches) { contentAnimation = start.animate( [{ opacity: 1, transform: 'translateY(0)' }, { opacity: 0, transform: 'translateY(-0.5rem)' }], { duration: 180, easing: 'ease-in', fill: 'forwards' } ); await contentAnimation.finished; } start.hidden = true; start.style.display = 'none'; result.hidden = false; result.style.display = resultDisplay; root.dataset.state = 'result'; if (animate) confetti.play(); contentAnimation?.cancel(); contentAnimation = null; if (animate && !motion.matches) { contentAnimation = result.animate( [{ opacity: 0, transform: 'translateY(0.5rem)' }, { opacity: 1, transform: 'translateY(0)' }], { duration: 260, easing: 'ease-out' } ); await contentAnimation.finished; contentAnimation = null; } status.textContent = `Ваш приз: ${prize.title}`; if (animate) { const heading = result.querySelector('.fortune__title'); if (heading && heading.getClientRects().length && getComputedStyle(heading).visibility !== 'hidden') { heading.tabIndex = -1; heading.focus({ preventScroll: true }); } } } async function spin(event) { event.preventDefault(); if (spinning || finished || submitting || storageFailed) return; try { const saved = readState(); if (saved) { restoreState(saved); return; } } catch (error) { storageError(error); return; } // Reserve before animation: reloading must not award another prize. selectedPrize = { ...drawPrize(), status: 'pending' }; if (!saveState(selectedPrize)) { selectedPrize = null; return; } finished = true; spinning = true; root.dataset.state = 'spinning'; updateButton(); button.setAttribute('aria-busy', 'true'); status.textContent = 'Колесо вращается. Разыгрываем подарок.'; try { const sectorCenter = 292.5 + selectedPrize.sector * 45; const insideSector = (random() - 0.5) * 20; const targetAngle = normalize(180 - sectorCenter + insideSector); const target = rotation + (6 + Math.floor(random() * 2)) * 360 + normalize(targetAngle - normalize(rotation)); if (!motion.matches) { const frames = Array.from({ length: 121 }, (_, index) => { const t = index / 120; return { offset: t, transform: `rotate(${rotation + (target - rotation) * progress(t)}deg)` }; }); wheelAnimation = wheel.animate(frames, { duration: 6500 + random() * 900, easing: 'linear', fill: 'forwards' }); await wheelAnimation.finished; } rotation = normalize(target); wheel.style.transform = `rotate(${rotation}deg)`; wheelAnimation?.cancel(); wheelAnimation = null; finished = true; await showResult(selectedPrize); } catch (error) { wheelAnimation?.cancel(); contentAnimation?.cancel(); wheelAnimation = null; contentAnimation = null; wheel.style.transform = `rotate(${rotation}deg)`; // Keep the reserved prize even if the browser cancels the animation. rotation = normalize(180 - (292.5 + selectedPrize.sector * 45)); wheel.style.transform = `rotate(${rotation}deg)`; await showResult(selectedPrize, false); } finally { spinning = false; button.removeAttribute('aria-busy'); updateButton(); } } button.addEventListener('click', spin); button.addEventListener('keydown', event => { if (event.key === ' ') { event.preventDefault(); button.click(); } }); motion.addEventListener('change', () => { if (!motion.matches) return; if (wheelAnimation?.playState === 'running') wheelAnimation.finish(); if (contentAnimation?.playState === 'running') contentAnimation.finish(); }); // Own submission so Webflow's delegated handler cannot send a duplicate. form.addEventListener('submit', async event => { event.preventDefault(); event.stopImmediatePropagation(); if (submitting || spinning || storageFailed) return; if (!selectedPrize) { formStatus.textContent = 'Сначала разыграйте приз.'; return; } if (selectedPrize.status === 'submitted' || !form.reportValidity()) return; prizeInput.value = formatPrizeMessage(selectedPrize); const data = Object.fromEntries(new FormData(form).entries()); data[prizeInput.name] = prizeInput.value; submitting = true; form.setAttribute('aria-busy', 'true'); failure.style.display = 'none'; const controls = Array.from(form.querySelectorAll('button, input[type="submit"]')); const disabledBefore = controls.map(control => control.disabled); controls.forEach(control => { control.disabled = true; }); // formStatus.textContent = 'Отправляем заявку…'; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 20000); try { const response = await fetch(ENDPOINT, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(data), signal: controller.signal }); const reply = await response.json(); if (!response.ok) throw new Error(`Сервис вернул HTTP ${response.status}.`); if (!reply || typeof reply !== 'object' || Array.isArray(reply)) throw new Error('Неожиданный ответ сервиса.'); if (reply.errors && (!Array.isArray(reply.errors) || reply.errors.length > 0)) { throw new Error(Array.isArray(reply.errors) ? reply.errors.map(error => error?.explain || 'Ошибка обработки формы').join(', ') : 'Ошибка обработки формы'); } selectedPrize = { ...selectedPrize, status: 'submitted' }; const persisted = saveState(selectedPrize); renderForm(); if (!persisted) formStatus.textContent = 'Форма отправлена, но сохранить подтверждение в браузере не удалось. Не отправляйте её повторно.'; } catch (error) { failure.textContent = `Не удалось подтвердить отправку. ${error.name === 'AbortError' ? 'Превышено время ожидания.' : error.message} Приз сохранён. Если заявка уже принята сервисом, повторная отправка может создать дубликат.`; failure.style.display = 'block'; formStatus.textContent = ''; } finally { clearTimeout(timeout); submitting = false; form.removeAttribute('aria-busy'); controls.forEach((control, index) => { control.disabled = disabledBefore[index]; }); } }, true); window.addEventListener('storage', event => { if (event.key === DISMISSED_KEY && event.newValue !== null) { dismissed = true; window.clearTimeout(autoOpenTimer); autoOpenTimer = null; return; } if (event.key !== STORAGE_KEY && event.key !== null) return; if (spinning || submitting) return; try { const saved = readState(); if (saved) restoreState(saved); } catch (error) { storageError(error); } }); try { const saved = readState(); if (saved) restoreState(saved); else { const probe = `${STORAGE_KEY}:probe`; localStorage.setItem(probe, '1'); localStorage.removeItem(probe); } } catch (error) { storageError(error); } updateButton(); function scheduleFortuneAutoOpen() { if (autoOpenTimer !== null || finished || storageFailed || isFortuneDismissed()) return; autoOpenTimer = window.setTimeout(() => { autoOpenTimer = null; if (finished || spinning || submitting || storageFailed || isFortuneDismissed()) return; try { // Recheck at opening time: a prize may have been saved in another tab. if (localStorage.getItem(STORAGE_KEY) !== null) return; } catch (error) { return; } if (typeof window.modals?.open === 'function') { window.modals.open('fortune'); } }, 7000); } if (document.readyState === 'complete') { scheduleFortuneAutoOpen(); } else { window.addEventListener('load', scheduleFortuneAutoOpen, { once: true }); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initFortune, { once: true }); } else { initFortune(); } })();