// App.jsx — Ozimiz landing page // Structure: TopNav → Hero (with product peek card) → Proof strip → Features // → Dual-persona row → Locales/currencies → Pricing → FAQ → Final CTA → Footer const APP_ORIGIN = "https://app.ozimiz.net"; const LANG_MAP = { ru: "ru", kk: "kk-Cyrl", en: "en" }; function appUrl(lang, path) { const l = LANG_MAP[lang] || "ru"; return APP_ORIGIN + "/" + l + (path || ""); } Object.assign(window, { appUrl }); function useI18n() { const [lang, setLang] = React.useState(() => { try { return localStorage.getItem("ozimiz.lang") || "ru"; } catch(_) { return "ru"; } }); const [dark, setDark] = React.useState(() => { try { return localStorage.getItem("ozimiz.theme") === "dark"; } catch(_) { return false; } }); React.useEffect(() => { try { localStorage.setItem("ozimiz.lang", lang); } catch(_) {} document.documentElement.setAttribute("lang", lang === "kk" ? "kk" : lang); }, [lang]); React.useEffect(() => { try { localStorage.setItem("ozimiz.theme", dark ? "dark" : "light"); } catch(_) {} document.documentElement.setAttribute("data-theme", dark ? "dark" : "light"); }, [dark]); const t = window.I18N[lang] || window.I18N.ru; return { lang, setLang, dark, setDark, t }; } // Cross-origin session probe: if the visitor is already signed in at // app.ozimiz.net, swap the "sign in / start" CTAs for an "open app" link. function useAppSession() { const [user, setUser] = React.useState(null); const [ready, setReady] = React.useState(false); React.useEffect(() => { let cancelled = false; fetch(APP_ORIGIN + "/api/auth/get-session", { credentials: "include" }) .then(r => r.ok ? r.json() : null) .then(data => { if (!cancelled) setUser(data && data.user ? data.user : null); }) .catch(() => undefined) .finally(() => { if (!cancelled) setReady(true); }); return () => { cancelled = true; }; }, []); return { user, ready }; } Object.assign(window, { useAppSession }); function TopNav({ lang, setLang, dark, setDark, t }) { const { user, ready } = useAppSession(); const [langOpen, setLangOpen] = React.useState(false); const langCodes = ["ru", "kk", "en"]; const linkStyle = { font: "500 14px Manrope, sans-serif", color: "var(--fg-muted)", padding: "8px 12px", borderRadius: 8, textDecoration: "none", transition: "color var(--dur-base) var(--ease-standard), background var(--dur-base) var(--ease-standard)", }; return (
Ozimiz
{langOpen && (
setLangOpen(false)} style={{ position: "absolute", top: "calc(100% + 6px)", right: 0, background: "var(--surface-raised)", border: "1px solid var(--border)", borderRadius: 10, boxShadow: "var(--shadow-md)", padding: 4, minWidth: 160, zIndex: 50, }}> {langCodes.map(code => ( ))}
)}
{ready && user ? ( {t.nav.openApp} ) : ( <> {t.nav.signIn} {t.nav.start} )}
); } function Hero({ t, lang }) { return (
{t.hero.eyebrow}

{t.hero.title}

{t.hero.sub}

{t.hero.primaryCta} {t.hero.secondaryCta}
{t.hero.foot}
); } // A miniature of the actual product — pulled from the member kit's vocabulary. function HeroPeek({ t }) { return (
{/* Backplate */}
{/* Feed post */}
Айдана Жұмабекова
Модератор · 2ч
Закреп
Добавила модуль «Портфель для новичка». Вопросы — в комментариях, отвечу до вечера.
34 12
{/* Classroom module */}
Модуль 2 · Портфель для новичка
4 урока · 38 мин
2 / 4
{[ { t: "Во что инвестировать первые ₸100 000", m: "7 мин", done: true }, { t: "ETF на KASE: полный разбор", m: "12 мин", done: true, current: false }, { t: "Ребалансировка раз в квартал", m: "9 мин", done: false, current: true }, { t: "Как не сливать на панике", m: "10 мин", done: false, locked: true }, ].map((row, i) => (
{row.done && }
{row.t}
{row.m} {row.locked && }
))}
{/* Payment row */}
Оплата прошла · ioka.kz
Kaspi Gold · ··7821
24{"\u202F"}900{"\u00A0"}₸
); } const peekCard = { background: "var(--surface-raised)", border: "1px solid var(--border)", borderRadius: 14, padding: "14px 16px", boxShadow: "var(--shadow-sm)", }; // --- Button tokens --- // const primaryBtn = { display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8, padding: "10px 16px", background: "var(--brand-primary)", color: "var(--brand-primary-fg)", border: 0, borderRadius: 10, font: "600 14px Manrope, sans-serif", textDecoration: "none", cursor: "pointer", transition: "background var(--dur-base) var(--ease-standard)", }; const secondaryBtn = { display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8, padding: "10px 16px", background: "var(--surface-raised)", color: "var(--fg)", border: "1px solid var(--border)", borderRadius: 10, font: "600 14px Manrope, sans-serif", textDecoration: "none", cursor: "pointer", transition: "background var(--dur-base) var(--ease-standard)", }; const ghostBtn = { display: "inline-flex", alignItems: "center", gap: 6, padding: "8px 10px", background: "transparent", color: "var(--fg-muted)", border: "1px solid var(--border)", borderRadius: 8, font: "500 13px Manrope, sans-serif", cursor: "pointer", transition: "background var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard)", }; Object.assign(window, { useI18n, TopNav, Hero, primaryBtn, secondaryBtn, ghostBtn, peekCard });