/* ============================================================ resume_doc.jsx — сам документ резюме. Лист всегда белый и на светлом фоне: так он выглядит в PDF, который человек отправит работодателю, независимо от темы приложения. Никаких таблиц, рамок и колонок — их не читает ATS. ============================================================ */ const PAPER_W = 300; // ширина «листа» в макете, пропорция ≈ A4 function CVSection({ title, children, tight }) { return (
{title}
{children}
); } /* Фото: настоящее мок-фото — так документ выглядит как готовый продукт. */ function CVPhoto({ on, src }) { if (!on) return null; return (
); } /* CVDocument — один лист. cv — результат buildCV(), docLang — язык документа ('en' | 'ru' | 'uz'), t — строки интерфейса (для русских подписей разделов). */ function CVDocument({ cv, docLang, t }) { const S = docLang === 'en' ? CV_EN : { summary: t.cv_summary, exp: t.cv_exp, skills: t.cv_skills, certs: t.cv_certs, langs: t.cv_langs, pers: t.cv_pers, ready: t.cv_ready, present: t.cv_present }; const period = docLang === 'en' ? `${2026 - (cv.years || 1)} – present` : `${2026 - (cv.years || 1)} – ${S.present}`; return (
{/* шапка */}
{cv.name}
{cv.title}
{[cv.city, cv.age ? (docLang === 'en' ? `${cv.age} years old` : `${cv.age}`) : null, cv.phone] .filter(Boolean).join(' · ')}
{cv.summary}
{cv.title} {period}
{cv.place}
{cv.skills.join(' · ')}
{cv.certs.length > 0 && ( {cv.certs.map((c, i) =>
— {c}
)}
)} {cv.langs.map((l, i) => (
{l.name}{l.level}
))}
); } /* Лист в масштабе: высоту меряем по факту, чтобы длинный документ не вылезал за пределы карточки. */ function CVPaper({ cv, docLang, t, scale = 1, blurFrom = null }) { const inner = useRef(null); const [h, setH] = useState(PAPER_W * 1.414); useLayoutEffect(() => { if (inner.current) setH(inner.current.getBoundingClientRect().height / scale); }, [cv, docLang, scale]); return (
{blurFrom != null && (
)}
); } Object.assign(window, { CVDocument, CVPaper, PAPER_W });