// app.jsx — page router for the multi-page site. // Each HTML file sets ``. // We read that, pull the relevant screen from window.*, and render it full-bleed. window.__IS_LIVE_SITE = true; const { useState: _appS, useEffect: _appE, useMemo: _appM } = React; // ──────────────────────────────────────────────────────────── // Shared persistence — tweaks + favs + current listing live in localStorage // so they survive page navigation. // ──────────────────────────────────────────────────────────── const APP_KEYS = { tweaks: 'is:tweaks', favs: 'is:favs', listing: 'is:currentListing', authMode: 'is:authMode', }; function loadJson(key, fallback) { try { const v = localStorage.getItem(key); return v ? JSON.parse(v) : fallback; } catch { return fallback; } } function saveJson(key, v) { try { localStorage.setItem(key, JSON.stringify(v)); } catch {} } // ──────────────────────────────────────────────────────────── // Navigation helpers — real URLs so the site behaves like a site. // ──────────────────────────────────────────────────────────── function goTo(path) { window.location.href = path; } function goToListing(listing) { saveJson(APP_KEYS.listing, listing); window.location.href = 'ilan.html'; } function goToAuth(mode) { saveJson(APP_KEYS.authMode, mode || 'login'); window.location.href = 'giris.html'; } function goToCheckout(listing) { if (listing) saveJson(APP_KEYS.listing, listing); window.location.href = 'odeme.html'; } function goToDashboard() { window.location.href = 'panelim.html'; } function goToHome() { window.location.href = 'index.html'; } Object.assign(window, { goTo, goToListing, goToAuth, goToCheckout, goToDashboard, goToHome }); // ──────────────────────────────────────────────────────────── // TWEAKS defaults — the EDITMODE block is in each HTML shell, but // we also persist user changes to localStorage so they carry over. // ──────────────────────────────────────────────────────────── const DEFAULT_TWEAKS = { lang: 'tr', dark: false, palette: 'sunset', density: 'regular', layout: 'grid', }; // Minimal fallback useTweaks in case tweaks-panel.jsx version isn't enough — // we want localStorage sync across pages regardless of edit-mode. function useAppTweaks(defaults) { const [t, setT] = _appS(() => ({ ...defaults, ...loadJson(APP_KEYS.tweaks, {}) })); const setTweak = (k, v) => { setT(prev => { const next = { ...prev, [k]: v }; saveJson(APP_KEYS.tweaks, next); // also tell the host (in-app tweak panel support) try { window.parent.postMessage({ type: '__edit_mode_set_keys', edits: { [k]: v } }, '*'); } catch {} return next; }); }; return [t, setTweak]; } // ──────────────────────────────────────────────────────────── // Footer — shared across all pages // ──────────────────────────────────────────────────────────── function SiteFooter({ L, lang }) { const year = new Date().getFullYear(); const cols = lang === 'tr' ? [ { h: 'Pazaryeri', links: [['Keşfet', 'index.html'], ['Arama', 'arama.html'], ['Karşılaştır', 'karsilastir.html'], ['İlan Ver', 'ilan-ver.html']] }, { h: 'Hesabım', links: [['Giriş', 'giris.html'], ['Kayıt Ol', 'giris.html'], ['Panelim', 'panelim.html'], ['Hesap Devri', 'devir.html']] }, { h: 'Yardım', links: [['Aged Hesap Nedir?', 'aged-hesap-nedir.html'], ['Hakkında', 'hakkinda.html'], ['SSS', 'aged-hesap-nedir.html#sss']] }, { h: 'Hukuki', links: [['KVKK', 'kvkk.html'], ['Mesafeli Satış', 'mesafeli-satis.html'], ['Ön Bilgilendirme', 'on-bilgilendirme.html'], ['Koşullar', 'kosullar.html']] }, ] : [ { h: 'Marketplace', links: [['Explore', 'index.html'], ['Search', 'arama.html'], ['Compare', 'karsilastir.html'], ['Sell', 'ilan-ver.html']] }, { h: 'Account', links: [['Login', 'giris.html'], ['Sign up', 'giris.html'], ['My Panel', 'panelim.html'], ['Transfer', 'devir.html']] }, { h: 'Help', links: [['What is aged?', 'aged-hesap-nedir.html'], ['About', 'hakkinda.html'], ['FAQ', 'aged-hesap-nedir.html#sss']] }, { h: 'Legal', links: [['GDPR', 'kvkk.html'], ['Distance Sales', 'mesafeli-satis.html'], ['Pre-info', 'on-bilgilendirme.html'], ['Terms', 'kosullar.html']] }, ]; return ( ); } // Mobile bottom nav that actually navigates (for real site pages) function MobileBottomNav({ L, active }) { const items = [ { id: 'home', label: L.nav_explore, icon: 'grid', href: 'index.html' }, { id: 'search', label: lang => lang === 'tr' ? 'Ara' : 'Search', icon: 'search', href: 'arama.html' }, { id: 'fav', label: L.dash_fav, icon: 'heart', href: 'panelim.html' }, { id: 'me', label: lang => lang === 'tr' ? 'Hesap' : 'Account', icon: 'users', href: 'panelim.html' }, ]; return (
{items.map(it => { const lbl = typeof it.label === 'function' ? it.label('tr') : it.label; const isActive = active === it.id; return ( {lbl} ); })}
); } // ──────────────────────────────────────────────────────────── // Responsive helper — detect mobile viewport // ──────────────────────────────────────────────────────────── function useIsMobile(breakpoint = 820) { const [m, setM] = _appS(() => typeof window !== 'undefined' && window.innerWidth < breakpoint); _appE(() => { const onR = () => setM(window.innerWidth < breakpoint); window.addEventListener('resize', onR); return () => window.removeEventListener('resize', onR); }, [breakpoint]); return m; } // ──────────────────────────────────────────────────────────── // Page Router — reads and picks the screen // ──────────────────────────────────────────────────────────── function PageRouter() { const [t, setTweak] = useAppTweaks(DEFAULT_TWEAKS); const lang = t.lang; const setLang = (v) => setTweak('lang', v); const L = I18N[lang] || I18N.tr; const theme = _appM(() => themeVars({ dark: t.dark, palette: t.palette, density: t.density }), [t.dark, t.palette, t.density]); const [favs, setFavs] = _appS(() => loadJson(APP_KEYS.favs, ['l1', 'l4', 'l7'])); _appE(() => saveJson(APP_KEYS.favs, favs), [favs]); // URL'den filtreleri oku (paylaşılabilir aramalar) const filtersFromURL = () => { if (typeof window === 'undefined') return { age: 'any', niche: null, followers: null, priceMax: null }; const p = new URLSearchParams(window.location.search); return { age: p.get('age') || 'any', niche: p.get('niche') || null, followers: p.get('followers') || null, priceMax: p.get('priceMax') ? parseInt(p.get('priceMax')) : null, }; }; const sortFromURL = () => (typeof window !== 'undefined' && new URLSearchParams(window.location.search).get('sort')) || 'new'; const [filters, setFilters] = _appS(filtersFromURL); const [sort, setSort] = _appS(sortFromURL); // Filtreler değişince URL'i güncelle (replaceState ile sayfa yenilenmez) _appE(() => { if (typeof window === 'undefined') return; const params = new URLSearchParams(); if (filters.age && filters.age !== 'any') params.set('age', filters.age); if (filters.niche) params.set('niche', filters.niche); if (filters.followers) params.set('followers', filters.followers); if (filters.priceMax) params.set('priceMax', filters.priceMax); if (sort && sort !== 'new') params.set('sort', sort); const qs = params.toString(); const newURL = window.location.pathname + (qs ? '?' + qs : ''); window.history.replaceState({}, '', newURL); }, [filters, sort]); // API → mock fallback bridge // api-client.js sayfa yüklenince listings'i fetch'liyor; biz event'i dinleyip re-render tetikliyoruz. const [listingsTick, setListingsTick] = _appS(0); _appE(() => { const handler = () => setListingsTick(t => t + 1); window.addEventListener('listings:updated', handler); return () => window.removeEventListener('listings:updated', handler); }, []); const storedListing = loadJson(APP_KEYS.listing, null); const [currentListing, setCurrentListing] = _appS(() => { if (storedListing && LISTINGS.find(x => x.id === storedListing.id)) { return LISTINGS.find(x => x.id === storedListing.id); } return LISTINGS[0]; }); const onView = (l) => { saveJson(APP_KEYS.listing, l); window.location.href = 'ilan.html'; }; const onAuth = (m) => { saveJson(APP_KEYS.authMode, m || 'login'); window.location.href = 'giris.html'; }; const page = document.body.dataset.page || 'home'; const isMobile = useIsMobile(); // Apply theme at the document level so html bg + scrollbars pick it up _appE(() => { const el = document.documentElement; Object.entries(theme).forEach(([k, v]) => el.style.setProperty(k, v)); document.body.style.background = 'var(--bg)'; document.body.style.color = 'var(--ink)'; }, [theme]); const authMode = loadJson(APP_KEYS.authMode, 'login'); let content; if (page === 'home') { content = isMobile ? : setTweak('layout', v)} filters={filters} setFilters={setFilters} sort={sort} setSort={setSort} favs={favs} setFavs={setFavs} onView={onView} onAuth={onAuth}/>; } else if (page === 'detail') { content = isMobile ? : ; } else if (page === 'auth') { content = isMobile ? : saveJson(APP_KEYS.authMode, m)} onBack={goToHome}/>; } else if (page === 'dashboard') { content = isMobile ? : ; } else if (page === 'checkout') { content = isMobile ? window.history.back()}/> : window.history.back()} onAuth={onAuth}/>; } else if (page === 'success') { content = isMobile ? : ; } else if (page === 'admin') { content = ; } else if (page === 'sell') { content = isMobile ? : ; } else if (page === 'transfer') { content = ; } else if (page === 'compare') { content = ; } else if (page === 'seo') { content = ; } else if (page === 'search') { content = ; } else if (page === 'category') { content = ; } else if (page === 'reset') { content = ; } else if (page === 'verify-seller') { content = ; } else if (page === 'legal') { const tab = document.body.getAttribute('data-legal-tab') || 'kvkk'; content = ; } else if (page === 'about') { content = ; } else if (page === 'notfound') { content = ; } else { content = setTweak('layout', v)} filters={filters} setFilters={setFilters} sort={sort} setSort={setSort} favs={favs} setFavs={setFavs} onView={onView} onAuth={onAuth}/>; } // Mobile/desktop: footer on desktop only (mobile gets bottom nav on some pages) const showFooter = !isMobile && page !== 'auth' && page !== 'admin' && page !== 'sell' && page !== 'notfound'; return (
{content}
{showFooter && } {/* Tweaks panel works the same way it did in the canvas */} setTweak('lang', v)}/> setTweak('dark', v)}/> ({ label: PALETTES[k].name, value: k }))} onChange={v => setTweak('palette', v)}/> setTweak('density', v)}/> setTweak('layout', v)}/>
); } injectGlobalStyles(); // ──────────────────────────────────────────────────────────── // Live-site overrides — the .jsx files were built for a design canvas, // so MobileChrome renders a bezel and ScreenFrame clips overflow. On // the real site we want those to flow naturally. // ──────────────────────────────────────────────────────────── (function injectLiveOverrides() { if (document.getElementById('is-live-overrides')) return; const s = document.createElement('style'); s.id = 'is-live-overrides'; s.textContent = ` html, body { background: var(--bg); color: var(--ink); } body { margin: 0; min-height: 100vh; } /* ScreenFrame clips overflow for canvas — let it flow on a real page */ .is-surface { overflow: visible !important; height: auto !important; min-height: 100% !important; } /* Mobile screens were designed for a 390×844 bezel — let them flow on real pages */ [data-screen-label] { height: auto !important; overflow: visible !important; } /* Smooth anchor + reset focus rings we didn't pick */ a { color: inherit; } html { scroll-behavior: smooth; } `; document.head.appendChild(s); })(); // Mount — all HTML shells use #root ReactDOM.createRoot(document.getElementById('root')).render(); // ──────────────────────────────────────────────────────────── // PWA — Service Worker registration (production only) // Registers /sw.js once site is hosted; silently no-ops on file:// // ──────────────────────────────────────────────────────────── if ('serviceWorker' in navigator && location.protocol.startsWith('http')) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js') .then(reg => console.log('[PWA] SW registered:', reg.scope)) .catch(err => console.log('[PWA] SW registration failed:', err.message)); }); } // ──────────────────────────────────────────────────────────── // AdminGate — protects /yonetim.html with a simple password. // ⚠️ Client-side gate, not real auth — but stops casual visitors. // Sets a flag in localStorage so you don't re-enter every visit. // ──────────────────────────────────────────────────────────── const ADMIN_PASS = 'okxproject2026'; const ADMIN_KEY = 'is:admin'; function AdminGate({ L, lang, theme, isMobile }) { const [authed, setAuthed] = _appS(() => loadJson(ADMIN_KEY, false)); const [pw, setPw] = _appS(''); const [err, setErr] = _appS(false); if (!authed) { return (
{ e.preventDefault(); if (pw === ADMIN_PASS) { saveJson(ADMIN_KEY, true); setAuthed(true); } else { setErr(true); setTimeout(() => setErr(false), 1200); } }} className="is-card" style={{ width: '100%', maxWidth: 380, padding: 32, display: 'flex', flexDirection: 'column', gap: 16, animation: err ? 'shake .35s' : undefined, }}>

{lang === 'tr' ? 'Yönetim Paneli' : 'Admin Panel'}

{lang === 'tr' ? 'Devam etmek için şifrenizi girin.' : 'Enter your password to continue.'}

setPw(e.target.value)} autoFocus style={{ borderColor: err ? 'var(--danger)' : undefined }}/> ← {lang === 'tr' ? 'Siteye dön' : 'Back to site'}
); } return (
{isMobile ? : }
); } Object.assign(window, { AdminGate });