import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { IOSDevice } from './frames/IOSDevice.jsx'; import { AndroidDevice } from './frames/AndroidDevice.jsx'; import { PhoneApp } from './PhoneApp.jsx'; const TWEAKS_DEFAULT = { homeVariant: 'cards', docVariant: 'rich', density: 'comfort', accent: 'teal', font: 'inter', layout: 'single', screen: 'home', device: 'ios', scale: 'auto', showIntro: true, }; const SCALE_OPTIONS = [ { id: 'auto', lb: 'Авто' }, { id: '0.5', lb: '50%' }, { id: '0.75', lb: '75%' }, { id: '1', lb: '100%' }, ]; const HOME_OPTIONS = [ { id: 'cards', lb: 'Карточки' }, { id: 'list', lb: 'Лента' }, { id: 'feed', lb: 'Таймлайн' }, ]; const DOC_OPTIONS = [ { id: 'rich', lb: 'Карточки+' }, { id: 'list', lb: 'Список' }, { id: 'photo', lb: 'Плитка' }, ]; const DENSITY_OPTIONS = [ { id: 'comfort', lb: 'Комф.' }, { id: 'compact', lb: 'Плотно' }, ]; const ACCENT_OPTIONS = [ { id: 'teal', lb: 'Тил', primary: '#1F8F85', darker: '#166B63', dark: '#0F4A44', p50: '#E3F4F2', p100: '#C7E8E4', p200: '#9BD6CE', warm50: '#FDF8EE', warm100: '#F5EDDF' }, { id: 'terra', lb: 'Терра', primary: '#C77A4C', darker: '#A65C33', dark: '#7F4426', p50: '#FBEFE4', p100: '#F5DDC9', p200: '#EBC19F', warm50: '#F4F7F3', warm100: '#E5ECE4' }, { id: 'marine', lb: 'Марин', primary: '#3C6EA8', darker: '#23538B', dark: '#193C66', p50: '#E4EDF8', p100: '#C8DAEE', p200: '#9DBDDE', warm50: '#FBF6EE', warm100: '#F2E8D5' }, ]; const FONT_OPTIONS = [ { id: 'manrope', lb: 'Manrope', base: '"Manrope", system-ui, sans-serif', narrow: '"Oswald", sans-serif' }, { id: 'inter', lb: 'Inter', base: '"Inter", system-ui, sans-serif', narrow: '"Oswald", sans-serif' }, { id: 'golos', lb: 'Golos', base: '"Golos Text", system-ui, sans-serif', narrow: '"Oswald", sans-serif' }, ]; const SCREEN_OPTIONS = [ { id: 'home', lb: 'Главная' }, { id: 'doctors', lb: 'Врачи' }, { id: 'doctor:syndaev', lb: 'Карточка врача' }, { id: 'booking-specs', lb: 'Запись: специализация' }, { id: 'booking-doctor:ent', lb: 'Запись: врач' }, { id: 'booking-time:syndaev', lb: 'Запись: время' }, { id: 'booking-confirm:syndaev:1:16:00', lb: 'Запись: подтверждение' }, { id: 'booking-success', lb: 'Запись: успех' }, { id: 'appts', lb: 'Приёмы' }, { id: 'appt:a1', lb: 'Детали приёма' }, { id: 'results', lb: 'Результаты' }, { id: 'result-audio', lb: 'Аудиограмма' }, { id: 'result:r2', lb: 'Эндоскопия носоглотки' }, { id: 'recovery', lb: 'Восстановление' }, { id: 'audiotest', lb: 'Тест слуха' }, { id: 'chat', lb: 'Чат' }, { id: 'profile', lb: 'Профиль' }, { id: 'qr', lb: 'QR' }, { id: 'telemed', lb: 'Телемед' }, { id: 'medcard', lb: 'Медкарта' }, { id: 'notifications', lb: 'Уведомления' }, ]; function applyTheme(tw) { const a = ACCENT_OPTIONS.find(x => x.id === tw.accent) || ACCENT_OPTIONS[0]; const f = FONT_OPTIONS.find(x => x.id === tw.font) || FONT_OPTIONS[0]; const r = document.documentElement.style; r.setProperty('--c-primary', a.primary); r.setProperty('--c-primary-darker', a.darker); r.setProperty('--c-primary-dark', a.dark); r.setProperty('--c-primary-50', a.p50); r.setProperty('--c-primary-100', a.p100); r.setProperty('--c-primary-200', a.p200); r.setProperty('--c-warm-50', a.warm50); r.setProperty('--c-warm-100', a.warm100); r.setProperty('--font-base', f.base); r.setProperty('--font-narrow', f.narrow); document.body.style.fontFamily = f.base; } function Phone({ device = 'ios', screen, ctx, label, sublabel }) { const content = ; const frame = device === 'android' ? {content} : {content}; if (!label) return frame; return (
{frame}
{label}
{sublabel &&
{sublabel}
}
); } function TweaksPanel({ tw, setTw, onClose }) { const group = (title, children) => (
{title}
{children}
); const opts = (options, key) => options.map(o => ( )); return (

Tweaks

{group('Экран', )} {group('Устройство', opts([{id:'ios',lb:'iOS'},{id:'android',lb:'Android'}], 'device'))} {tw.layout === 'single' && group('Масштаб', opts(SCALE_OPTIONS, 'scale'))} {group('Компоновка', opts([ { id:'single', lb:'1 телефон' }, { id:'home3', lb:'Главная ×3' }, { id:'flow', lb:'Флоу записи' }, { id:'variants', lb:'Все варианты' }, ], 'layout'))} {group('Главный экран', opts(HOME_OPTIONS, 'homeVariant'))} {group('Карточки врачей', opts(DOC_OPTIONS, 'docVariant'))} {group('Плотность', opts(DENSITY_OPTIONS, 'density'))} {group('Цвет', ACCENT_OPTIONS.map(a => ( )) )} {group('Шрифт', opts(FONT_OPTIONS, 'font'))}
); } function FitWrap({ children, w = 402, h = 874, userScale = 'auto' }) { const outerRef = useRef(null); const [autoScale, setAutoScale] = useState(1); useEffect(() => { const outer = outerRef.current; if (!outer) return; const stage = outer.parentElement; if (!stage) return; const measure = () => { const padding = 48; const availW = stage.clientWidth - padding; const availH = stage.clientHeight - padding; const s = Math.min(availW / w, availH / h, 1); setAutoScale(Math.max(s, 0.3)); }; measure(); const ro = new ResizeObserver(measure); ro.observe(stage); window.addEventListener('resize', measure); return () => { ro.disconnect(); window.removeEventListener('resize', measure); }; }, [w, h]); const scale = userScale === 'auto' ? autoScale : parseFloat(userScale); return (
{children}
); } export default function App() { const [tw, setTw] = useState(TWEAKS_DEFAULT); const [panelOpen, setPanelOpen] = useState(true); const [introVisible, setIntroVisible] = useState(tw.showIntro !== false); useEffect(() => { applyTheme(tw); }, [tw.accent, tw.font]); const ctx = { homeVariant: tw.homeVariant, docVariant: tw.docVariant, density: tw.density }; const content = useMemo(() => { if (tw.layout === 'home3') { return (
); } if (tw.layout === 'flow') { return (
); } if (tw.layout === 'variants') { return (
); } return ( ); }, [tw, ctx.homeVariant, ctx.docVariant, ctx.density]); const stageClass = tw.layout === 'single' ? 'stage' : 'stage grid-mode'; return (
{content}
{introVisible && (
📱 Клиника УГН · мобильный прототип · откройте Tweaks справа, чтобы переключать экраны и варианты.
)} {panelOpen && setPanelOpen(false)} />} {!panelOpen && ( )}
); }