// Shared components for WordyTrainer landing // Provides: WordyLogo, PopupCard, DesktopWithPopup, Eyebrow, Section, IconSlot const { useState, useEffect, useRef } = React; /* ---------- Logo ---------- */ function WordyMark({ size = 36 }) { // The W-in-orange-circle mark. Drawn in SVG so it stays crisp on any background. return ( ); } function WordyLogo({ size = 32, color = 'var(--ink)', variant = 'brand' }) { // Brand variant uses the official PNG (W mark + "Wordy the app" wordmark). // It contains its own colour, so we don't recolour it. if (variant === 'brand') { // The PNG is 250×61 — a ratio of ~4.1. We size by height so it lines up // with adjacent buttons. `size` here means desired height in px. return ( Wordy the app ); } // Mark-only fallback (legacy) return (
WordyTrainer
); } // Brand logo helper — wraps the PNG with light/dark-aware backdrop tweaks. // On dark sections the PNG sits fine (it has its own white-glow circle), // but text portion ("Wordy the app") is dark — we apply a light/dark // inversion via filter only when explicitly asked. function WordyBrandLogo({ height = 32, onDark = false }) { return ( Wordy the app ); } /* ---------- Pop-up card (hero metaphor) ---------- */ function PopupCard({ word = 'seat', transcription = '[siːt]', translation = 'сиденье', meta, onDismiss, compact = false }) { return (
{word} {transcription}
{translation}
{meta && (
{meta}
)}
); } /* ---------- Desktop frame with pop-up overlaid (hero visual) ---------- */ function DesktopWithPopup({ screenshot, popup, popupPos = 'br' }) { // popupPos: 'br' | 'bl' | 'tr' | 'tl' const posMap = { br: { right: '6%', bottom: '10%' }, bl: { left: '6%', bottom: '10%' }, tr: { right: '6%', top: '14%' }, tl: { left: '6%', top: '14%' }, }; return (
WordyTrainer
{screenshot && ( WordyTrainer screen )} {popup && (
{popup}
)}
); } /* ---------- Section chrome ---------- */ function Eyebrow({ children, color }) { return
{children}
; } function Section({ children, id, screenLabel, bg, style, pad = true }) { return (
{children}
); } /* ---------- Simple icon slots (line style) ---------- */ function LineIcon({ name, size = 22, color = 'currentColor' }) { const stroke = { stroke: color, strokeWidth: 1.6, fill: 'none', strokeLinecap: 'round', strokeLinejoin: 'round' }; const map = { sparkles: , bell: , upload: , globe: , chat: , wand: , clock: , repeat: , check: , arrowRight: , }; return {map[name] || map.check}; } /* ---------- Animated rotating popup (used in hero) ---------- */ function RotatingPopup({ words, interval = 3400, compact }) { const [i, setI] = useState(0); useEffect(() => { const t = setInterval(() => setI((x) => (x + 1) % words.length), interval); return () => clearInterval(t); }, [words.length, interval]); const w = words[i]; return (
); } /* ---------- Language dropdown ---------- */ // Single shared dropdown reused by all four landings. // `theme` controls colours so it sits naturally on light / dark / purple / terminal navs. // Themes: 'light' (default), 'dark', 'purple', 'terminal'. function LangDropdown({ lang, onLang, theme = 'light' }) { const [open, setOpen] = useState(false); const ref = useRef(null); const langs = window.WORDY_LANGS || [{ code: 'en', label: 'English', native: 'English', flag: '🇬🇧' }]; const current = langs.find(l => l.code === lang) || langs[0]; useEffect(() => { if (!open) return; const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; const onKey = (e) => { if (e.key === 'Escape') setOpen(false); }; document.addEventListener('mousedown', onDoc); document.addEventListener('keydown', onKey); return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); }; }, [open]); // Theme tokens const T = { light: { bg: 'var(--bg-soft)', border: '1px solid var(--line)', color: 'var(--ink)', menuBg: '#fff', menuBorder: '1px solid var(--line)', menuShadow: '0 12px 32px rgba(0,0,0,0.10)', hover: 'var(--bg-soft)', activeBg: 'var(--ink)', activeColor: '#fff', radius: 999, mradius: 12, }, dark: { bg: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.12)', color: '#fff', menuBg: '#1B1A20', menuBorder: '1px solid rgba(255,255,255,0.10)', menuShadow: '0 12px 32px rgba(0,0,0,0.45)', hover: 'rgba(255,255,255,0.06)', activeBg: '#fff', activeColor: 'var(--ink)', radius: 999, mradius: 12, }, purple: { bg: '#F5F2FF', border: '1px solid #E5DDFB', color: '#5B21B6', menuBg: '#fff', menuBorder: '1px solid #E5DDFB', menuShadow: '0 12px 32px rgba(91,33,182,0.14)', hover: '#F5F2FF', activeBg: '#7C3AED', activeColor: '#fff', radius: 999, mradius: 12, }, terminal: { bg: 'transparent', border: '1.5px solid #1A1814', color: '#1A1814', menuBg: '#FAF6EC', menuBorder: '1.5px solid #1A1814', menuShadow: '4px 4px 0 0 #1A1814', hover: '#F0EAD8', activeBg: '#1A1814', activeColor: '#FAF6EC', radius: 2, mradius: 2, }, }[theme] || {}; const isMono = theme === 'terminal'; const fontFam = isMono ? 'var(--font-mono)' : 'var(--font-mono)'; return (
{open && ( )}
); } /* ---------- Export to global scope for other scripts ---------- */ Object.assign(window, { WordyMark, WordyLogo, WordyBrandLogo, PopupCard, DesktopWithPopup, Eyebrow, Section, LineIcon, RotatingPopup, LangDropdown, });