/* global React, ReactDOM */ const { useState, useEffect } = React; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "#c8ff2e", "glitch": true, "marqueeSpeed": 55, "showFooterMarks": true, "screenStart": "home" }/*EDITMODE-END*/; function useEditMode(){ const [active, setActive] = useState(false); useEffect(()=>{ const onMsg = (e) => { const d = e.data || {}; if (d.type === '__activate_edit_mode') setActive(true); else if (d.type === '__deactivate_edit_mode') setActive(false); }; window.addEventListener('message', onMsg); window.parent.postMessage({ type: '__edit_mode_available' }, '*'); return () => window.removeEventListener('message', onMsg); }, []); return [active, setActive]; } function useTweaksLocal(defaults){ const [t, setT] = useState(defaults); const set = (k, v) => { let patch; if (typeof k === 'object') patch = k; else patch = { [k]: v }; setT(prev => ({ ...prev, ...patch })); window.parent.postMessage({ type: '__edit_mode_set_keys', edits: patch }, '*'); }; return [t, set]; } const MARQUEE_FALLBACK = [ { text: '◇ NOKK.717 // ASTANA, KZ', tone: 'acid' }, { text: 'tattoo / digital / dystopia' }, { text: '● booking open · jul → sep', tone: 'acid' }, { text: '"i see you in the reflection"' }, { text: '✕ no copies / no public faces', tone: 'blood' }, { text: 'ANGEL ENGINE / R.A.D.I.D.' }, { text: 'free sketches in /эскизы' }, { text: 'studio: by appointment only', tone: 'blood' }, { text: '+7 7XX XXX XX XX' }, ]; const API_ORIGIN = window.NOKK_API || ((['127.0.0.1', 'localhost'].includes(window.location.hostname)) ? 'http://127.0.0.1:8001/api' : '/api'); // Список валидных экранов SPA. Используется для парсинга ?screen=... из URL // (deeplinks из бота: tattooastana.kz/?screen=about → сразу открыть About). const VALID_SCREENS = ['home','portfolio','about','services','faq','sketch','book']; function readScreenFromUrl(){ try { const p = new URLSearchParams(window.location.search); const s = p.get('screen'); if (s && VALID_SCREENS.includes(s)) { const focus = parseInt(p.get('focus') || '', 10); const params = Number.isFinite(focus) ? { focus } : {}; return { name: s, params }; } } catch (_) {} return null; } function App(){ const [t, setT] = useTweaksLocal(TWEAK_DEFAULTS); const [editOn, setEditOn] = useEditMode(); const [screen, setScreen] = useState(() => readScreenFromUrl() || { name: t.screenStart || 'home', params: {} } ); const [marqueeItems, setMarqueeItems] = useState(MARQUEE_FALLBACK); const [settings, setSettings] = useState({}); useEffect(()=>{ fetch(API_ORIGIN + '/marquee', { headers: { Accept: 'application/json' } }) .then(r => r.json()) .then(json => { if (json.data && json.data.length) setMarqueeItems(json.data); }) .catch(() => {}); fetch(API_ORIGIN + '/settings', { headers: { Accept: 'application/json' } }) .then(r => r.json()) .then(json => { if (json && typeof json === 'object') setSettings(json); }) .catch(() => {}); }, []); const floatingBtnText = settings['booking.btn_floating'] || '▸ ЗАПИСАТЬСЯ НА СЕАНС'; const bookingOpen = String(settings['contacts.booking_open'] ?? '1') !== '0'; const closedText = settings['booking.btn_floating_closed'] || '◌ запись временно закрыта'; // Apply accent live useEffect(()=>{ document.documentElement.style.setProperty('--acid', t.accent); }, [t.accent]); const go = (name, params={}) => { setScreen({ name, params }); window.scrollTo({ top: 0, behavior: 'instant' }); // Обновляем URL чтобы deeplinks работали и работала кнопка "Назад" в браузере try { const url = new URL(window.location.href); if (name === 'home') { url.searchParams.delete('screen'); url.searchParams.delete('focus'); } else { url.searchParams.set('screen', name); if (params.focus != null) url.searchParams.set('focus', String(params.focus)); else url.searchParams.delete('focus'); } window.history.pushState({ screen: name, params }, '', url.toString()); } catch (_) {} }; // Кнопка «Назад» в браузере → восстанавливаем экран из state useEffect(() => { const onPop = () => { const s = readScreenFromUrl(); setScreen(s || { name: t.screenStart || 'home', params: {} }); window.scrollTo({ top: 0, behavior: 'instant' }); }; window.addEventListener('popstate', onPop); return () => window.removeEventListener('popstate', onPop); }, [t.screenStart]); const Screens = window.Screens; const C = window.Components; return (