/* ============================================================ Learn.jsx — личный кабинет обучения (плеер курса) · дерево программы (модули → уроки) с отметками пройденного · видео-плеер (файл/MP4 или встраиваемый YouTube/Vimeo) · прогресс и «Продолжить обучение» с места остановки ============================================================ */ function LearnGate() { const { t } = useT(); const [open, setOpen] = React.useState(false); return (

{t("learn.gate")}

setOpen(true)}>{t("learn.gateBtn")}
setOpen(false)} initialMode="login" onAuthed={() => setOpen(false)} />
); } function LessonRow({ lesson, active, done, onClick }) { return ( ); } function VideoPane({ lesson, phone, onCompleted }) { const { t } = useT(); const vidRef = React.useRef(null); const lastSave = React.useRef(0); const v = parseVideo(lesson.video); // при смене урока — восстановить позицию для файла-видео React.useEffect(() => { if (v.type !== "file") return; const el = vidRef.current; if (!el) return; const saved = (userProgress(phone)[lesson.id] || {}).t || 0; const onMeta = () => { if (saved > 0 && saved < (el.duration || 1e9)) { try { el.currentTime = saved; } catch (e) {} } }; el.addEventListener("loadedmetadata", onMeta); return () => el.removeEventListener("loadedmetadata", onMeta); }, [lesson.id, v.type, phone]); const onTime = () => { const el = vidRef.current; if (!el) return; const now = Date.now(); if (now - lastSave.current > 4000) { // сохраняем позицию не чаще раза в 4с lastSave.current = now; setLessonProgress(phone, lesson.id, { t: Math.floor(el.currentTime) }); } }; const onEnded = () => { setLessonProgress(phone, lesson.id, { completed: true, t: 0 }); onCompleted(); }; if (v.type === "none") { return (
{t("learn.noVideo")}
); } if (v.type === "embed") { return (