/* ============================================================ Right panel — Style / Text / Typography / Background / Templates ============================================================ */ const { useState, useEffect, useRef } = React; /* ---------- Icons ---------- */ const Icon = ({ name, sm, lg }) => { const cls = `ico${sm?' ico-sm':''}${lg?' ico-lg':''}`; const paths = { 'plus': <>, 'search': <>, 'image': <>, 'upload': <>, 'undo': <>, 'redo': <>, 'history': <>, 'download': <>, 'sparkles': <>, 'check': <>, 'chevron': <>, 'chevron-d': <>, 'align-l': <>, 'align-c': <>, 'align-r': <>, 'pos-t': <>, 'pos-c': <>, 'pos-b': <>, 'square': <>, 'square-9-16':<>, 'square-3-4': <>, 'square-16-9':<>, 'close': <>, 'random': <>, 'duplicate': <>, 'palette': <>, 'gradient': <>, 'blocks': <>, 'pattern': <>, 'menu': <>, 'bold': <>, 'type': <>, }; return {paths[name]}; }; /* ---------- Reusable controls ---------- */ function Slider({ value, onChange, min=0, max=100, step=1, suffix='', label, showVal=true }) { return (
{label &&
{label}{showVal && {value}{suffix}}
} onChange(parseFloat(e.target.value))}/>
); } function Seg({ value, onChange, options }) { return (
{options.map(o => ( ))}
); } /* ---------- Color palettes ---------- */ const COLOR_PALETTE = [ '#FFFFFF','#F2EBDC','#F8F7F3','#F2DC1B','#FFB1A5','#9D9AFF','#5552E0','#0A0A0A', '#161616','#1F3D2E','#3B5BDB','#0B5394','#16A34A','#E2231A','#C9B66E','#7B3FBF', ]; const TEXT_COLOR_PALETTE = [ '#0A0A0A','#161616','#3C3A33','#5C5A53','#9B9A95','#FFFFFF','#F4ECDD','#C9B66E', '#5552E0','#3B5BDB','#16A34A','#E2231A','#9C2B2B','#1F3D2E','#F2C84B','#8B89F7', ]; function ColorChips({ value, onChange, palette = COLOR_PALETTE }) { return (
{palette.map(c => (
); } /* ============================================================ Tab: Style — preset cards ============================================================ */ function TabStyle({ presetId, setPresetId, content }) { return ( <>

Стиль обложки

8 пресетов
{PRESET_ORDER.map(id => ( ))}

Соотношение сторон

Меняется в верхней панели предпросмотра. Текущий пресет адаптируется к выбранному формату.

); } /* ============================================================ Tab: Text — content + decorations ============================================================ */ const DECO_USE = { magazine: ['issue', 'date', 'tagline'], book: ['date'], modern: ['brand', 'issue', 'tagline', 'date'], brutalist:['issue', 'date', 'badge'], editorial:['brand', 'tagline'], tech: ['brand', 'date', 'issue', 'readTime'], photo: ['brand', 'issue'], gradient: ['brand'], }; const DECO_LABELS = { brand: { label:'Бренд / название', ph:'BEAUTICARD', max:32 }, issue: { label:'Номер выпуска', ph:'№ 24', max:24 }, date: { label:'Дата', ph:'МАЙ · 2026', max:32 }, badge: { label:'Метка (на печати)', ph:'НОВОЕ', max:14 }, tagline: { label:'Слоган / линия', ph:'ДИЗАЙН · КУЛЬТУРА', max:64 }, readTime: { label:'Метка справа', ph:'ЧТЕНИЕ · 8 МИН', max:24 }, }; const DECO_DEFAULTS = { brand: 'BEAUTICARD', issue: '№ 24', date: 'МАЙ · 2026', badge: 'НОВОЕ', tagline: 'ДИЗАЙН · КУЛЬТУРА · ТЕХНО', readTime: 'ЧТЕНИЕ · 8 МИН', }; function TabText({ content, setContent, deco, setDeco, presetId }) { const ch = (k) => (e) => setContent({ ...content, [k]: e.target.value }); const chDeco = (k) => (e) => setDeco({ ...deco, [k]: e.target.value }); const usedFields = DECO_USE[presetId] || []; return ( <>

Содержание

setContent({ kicker:'', title:'', subtitle:'' })}>Очистить
Надпись над заголовком {content.kicker.length}/40
Заголовок {content.title.length}/120