' + escapeHtml(v.name) + '
' + (v.role ? '' + escapeHtml(v.role) + '
' : '') + (v.bio ? '' + escapeHtml(v.bio) + '
' : '') + '' + 'const zlib = require('zlib'); const CRC_TABLE = (() => { const table = new Int32Array(256); for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1); table[n] = c; } return table; })(); function crc32(buf) { let crc = 0xffffffff; for (let i = 0; i < buf.length; i++) crc = CRC_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8); return (crc ^ 0xffffffff) >>> 0; } function dosDateTime(d = new Date()) { return { time: (d.getHours() << 11) | (d.getMinutes() << 5) | Math.floor(d.getSeconds() / 2), date: ((Math.max(1980, d.getFullYear()) - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate(), }; } function createZipWriter() { const parts = []; const central = []; let count = 0; let offset = 0; function buildEntry(nameBuf, method, crc, compressed, plain, dt) { const local = Buffer.alloc(30); local.writeUInt32LE(0x04034b50, 0); local.writeUInt16LE(20, 4); local.writeUInt16LE(0x0800, 6); local.writeUInt16LE(method, 8); local.writeUInt16LE(dt.time, 10); local.writeUInt16LE(dt.date, 12); local.writeUInt32LE(crc, 14); local.writeUInt32LE(compressed, 18); local.writeUInt32LE(plain, 22); local.writeUInt16LE(nameBuf.length, 26); local.writeUInt16LE(0, 28); const cen = Buffer.alloc(46); cen.writeUInt32LE(0x02014b50, 0); cen.writeUInt16LE(20, 4); cen.writeUInt16LE(20, 6); cen.writeUInt16LE(0x0800, 8); cen.writeUInt16LE(method, 10); cen.writeUInt16LE(dt.time, 12); cen.writeUInt16LE(dt.date, 14); cen.writeUInt32LE(crc, 16); cen.writeUInt32LE(compressed, 20); cen.writeUInt32LE(plain, 24); cen.writeUInt16LE(nameBuf.length, 28); cen.writeUInt16LE(0, 30); cen.writeUInt16LE(0, 32); cen.writeUInt16LE(0, 34); cen.writeUInt16LE(0, 36); cen.writeUInt32LE(0, 38); cen.writeUInt32LE(offset, 42); return { local, cen, nameBuf }; } return { addFile(name, data, d) { const nameBuf = Buffer.from(name, 'utf8'); const dt = dosDateTime(d); const crc = crc32(data); const compressed = zlib.deflateRawSync(data, { level: 9 }); const e = buildEntry(nameBuf, 8, crc, compressed.length, data.length, dt); const chunk = Buffer.concat([e.local, e.nameBuf, compressed]); parts.push(chunk); central.push(Buffer.concat([e.cen, e.nameBuf])); offset += chunk.length; count++; }, addDir(name) { const nameBuf = Buffer.from(String(name).replace(/\/?$/, '/'), 'utf8'); const dt = dosDateTime(); const e = buildEntry(nameBuf, 0, 0, 0, 0, dt); const chunk = Buffer.concat([e.local, e.nameBuf]); parts.push(chunk); central.push(Buffer.concat([e.cen, e.nameBuf])); offset += chunk.length; count++; }, toBuffer() { const centralStart = offset; const centralBuf = Buffer.concat(central); const eocd = Buffer.alloc(22); eocd.writeUInt32LE(0x06054b50, 0); eocd.writeUInt16LE(0, 4); eocd.writeUInt16LE(0, 6); eocd.writeUInt16LE(count, 8); eocd.writeUInt16LE(count, 10); eocd.writeUInt32LE(centralBuf.length, 12); eocd.writeUInt32LE(centralStart, 16); eocd.writeUInt16LE(0, 20); return Buffer.concat([...parts, centralBuf, eocd]); }, }; } const MONTHS_GEN = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']; const MONTHS_SHORT = ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек']; function escapeHtml(str) { return String(str === null || str === undefined ? '' : str) .replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); } function asDate(iso) { if (!iso) return null; const d = new Date(iso); return Number.isNaN(d.getTime()) ? null : d; } function longDate(iso) { const d = asDate(iso); if (!d) return ''; return d.getDate() + ' ' + MONTHS_GEN[d.getMonth()] + ' ' + d.getFullYear(); } function monthYear(iso) { const d = asDate(iso); if (!d) return ''; return MONTHS_GEN[d.getMonth()] + ' ' + d.getFullYear(); } function rangeLabel(from, to) { const a = asDate(from); const b = asDate(to); if (!a && !b) return ''; if (!a) return 'до ' + monthYear(to); if (!b) return 'с ' + monthYear(from); if (a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth()) return monthYear(from); return MONTHS_SHORT[a.getMonth()] + ' ' + a.getFullYear() + ' — ' + MONTHS_SHORT[b.getMonth()] + ' ' + b.getFullYear(); } function bytes(n) { if (!Number.isFinite(n)) return ''; if (n < 1024) return n + ' Б'; if (n < 1024 * 1024) return (n / 1024).toFixed(1).replace(/\.0$/, '') + ' КБ'; return (n / (1024 * 1024)).toFixed(1).replace(/\.0$/, '') + ' МБ'; } function truncate(value, max) { const s = String(value || '').replace(/\s+/g, ' ').trim(); return s.length > max ? s.slice(0, max - 1) + '…' : s; } function firstLine(value, max) { const s = String(value || '').split('\n').map(x => x.trim()).filter(Boolean)[0] || ''; return truncate(s, max); } function plural(n, one, few, many) { const m10 = n % 10; const m100 = n % 100; if (m10 === 1 && m100 !== 11) return one; if (m10 >= 2 && m10 <= 4 && (m100 < 12 || m100 > 14)) return few; return many; } function initials(name) { const parts = String(name || '').trim().split(/\s+/).filter(Boolean); const a = parts[0] ? parts[0][0] : ''; const b = parts[1] ? parts[1][0] : ''; return (a + b).toUpperCase() || '?'; } function chipInit(name) { const parts = String(name || '').trim().split(/\s+/).filter(Boolean); if (!parts.length) return '—'; return parts[0] + (parts[1] ? ' ' + parts[1][0] + '.' : ''); } function str(value, max) { if (value === null || value === undefined) return null; const s = String(value).trim(); if (!s) return null; return max && s.length > max ? s.slice(0, max) : s; } function num(value) { const n = Number(value); return Number.isFinite(n) ? n : null; } function arr(value) { return Array.isArray(value) ? value : []; } function iconName(value, fallback) { const s = String(value || '').trim().toLowerCase(); return /^[a-z0-9-]{1,32}$/.test(s) ? s : fallback; } function safeHref(value) { const s = String(value || '').trim(); if (!s || s.length > 500) return null; if (/^(https?:\/\/|mailto:|tel:|\/|#)/i.test(s)) return s; if (/^[\w.+-]+@[\w-]+\.[\w.-]{2,}$/.test(s)) return 'mailto:' + s; return null; } const IMG_EXT = new Set(['JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'BMP', 'AVIF', 'SVG', 'ICO', 'JFIF', 'HEIC', 'HEIF']); const VID_EXT = new Set(['MP4', 'WEBM', 'MOV', 'M4V', 'OGV', 'MKV', 'MPEG', 'MPG', '3GP', 'AVI']); const EXT_ICONS = { PDF: 'file-text', DOC: 'file-text', DOCX: 'file-text', TXT: 'file-text', RTF: 'file-text', XLS: 'table-2', XLSX: 'table-2', CSV: 'table-2', PPT: 'presentation', PPTX: 'presentation', ZIP: 'folder-archive', RAR: 'folder-archive', '7Z': 'folder-archive', TAR: 'folder-archive', GZ: 'folder-archive', JS: 'file-code-2', TS: 'file-code-2', HTML: 'file-code-2', CSS: 'file-code-2', JSON: 'file-code-2', PY: 'file-code-2', JAVA: 'file-code-2', CS: 'file-code-2', CPP: 'file-code-2', C: 'file-code-2', PSD: 'image', AI: 'image', MP3: 'music', WAV: 'music', OGG: 'music', FLAC: 'music', FIG: 'figma', SKETCH: 'figma', BLEND: 'box', OBJ: 'box', STL: 'box', FBX: 'box', GLB: 'box', }; function extIcon(ext) { const e = String(ext || '').toUpperCase(); if (IMG_EXT.has(e)) return 'image'; if (VID_EXT.has(e)) return 'video'; return EXT_ICONS[e] || 'file'; } function iconForGroup(title) { const t = String(title || '').toLowerCase(); if (/вёрстк|верстк|css|html|стил|дизайн|ui|ux/.test(t)) return 'layout-panel-left'; if (/javascript|js|программ|код|разработ/.test(t)) return 'braces'; if (/vue|react|framework|компонент/.test(t)) return 'component'; if (/инструмент|git|сборк|figma|vite/.test(t)) return 'wrench'; if (/данн|sql|база|api/.test(t)) return 'database'; if (/робот|python|ии|ai|нейро/.test(t)) return 'bot'; return 'sparkles'; } function buildProfileView(data, opts) { const o = opts || {}; const showEntries = o.includeEntries !== false; const showPhotos = o.includePhotos !== false; const showFiles = o.includeFiles !== false; const showCaptions = o.includeCaptions !== false; const showDates = o.showDates !== false; const student = data.student || {}; const profile = (data.profile && typeof data.profile === 'object' && !Array.isArray(data.profile)) ? data.profile : {}; const entries = arr(data.entries); const dbModules = arr(data.modules); const photos = arr(data.photos); const files = arr(data.files); const name = str(data.name, 150) || str(student.name, 150) || 'Ученик'; const profContacts = arr(profile.contacts); const profAchievements = arr(profile.achievements); const gallery = []; const galleryIdx = new Map(); function galleryAdd(type, src, caption) { if (galleryIdx.has(src)) return galleryIdx.get(src); const idx = gallery.length; gallery.push({ type, src, caption: caption || '' }); galleryIdx.set(src, idx); return idx; } const photoMap = new Map(); const photoCards = []; if (showPhotos) { for (const p of photos) { const src = 'photos/' + p.stored; const g = galleryAdd('image', src, p.caption || ''); const card = { src, g, caption: showCaptions ? (str(p.caption, 160) || '') : '', date: showDates ? longDate(p.createdAt) : '', group: str(p.groupName, 80) || '', entryId: p.entryId, }; photoMap.set(p.stored, card); photoCards.push(card); } } const photoByEntry = new Map(); for (const p of photos) { if (!photoByEntry.has(p.entryId)) photoByEntry.set(p.entryId, []); photoByEntry.get(p.entryId).push(p); } const projectMap = new Map(); const projects = []; if (showFiles) { for (const f of files) { const ext = String(f.ext || '').toUpperCase(); const isImg = IMG_EXT.has(ext); const isVid = VID_EXT.has(ext); const href = 'files/' + f.saved; const card = { href, ext, icon: extIcon(ext), isImg, isVid, g: (isImg || isVid) ? galleryAdd(isVid ? 'video' : 'image', href, f.original) : -1, title: str(f.original, 160) || 'Файл', description: truncate(f.desc, 170) || (bytes(f.size) + (ext ? ' · ' + ext : '')), tags: [ext, str(f.moduleName, 70)].filter(Boolean), date: showDates ? longDate(f.createdAt) : '', size: bytes(f.size), entryId: f.entryId, }; projectMap.set(f.saved, card); projects.push(card); } } const fileByEntry = new Map(); for (const f of files) { if (!fileByEntry.has(f.entryId)) fileByEntry.set(f.entryId, []); fileByEntry.get(f.entryId).push(f); } const tasks = showEntries ? entries.map(e => { const st = e.ai_status; const ok = st === 'done' || st === 'skipped'; const bad = st === 'error'; return { icon: ok ? 'check' : (bad ? 'triangle-alert' : 'loader'), done: ok, statusLabel: ok ? (st === 'skipped' ? 'Принято' : 'Проверено') : (bad ? 'Ошибка проверки' : 'В проверке'), title: firstLine(e.description, 120) || 'Запись занятия', sub: [str(e.module_name, 90) || str(e.group_name, 90), showDates ? longDate(e.created_at) : ''].filter(Boolean).join(' · '), thumbs: (photoByEntry.get(e.id) || []).slice(0, 4).map(p => photoMap.get(p.stored)).filter(Boolean), files: (fileByEntry.get(e.id) || []).slice(0, 5).map(f => projectMap.get(f.saved)).filter(Boolean), }; }) : []; const groupSet = new Set(); if (student.group_name) groupSet.add(student.group_name); for (const e of entries) if (e.group_name) groupSet.add(e.group_name); const groups = [...groupSet]; const firstAt = entries.length ? entries[entries.length - 1].created_at : student.created_at; const days = (() => { const d = asDate(firstAt); if (!d) return 0; return Math.max(1, Math.round((Date.now() - d.getTime()) / 86400000)); })(); const eduMeta = new Map(); for (const m of arr(profile.education)) { const key = str(m && m.module, 200); if (key) eduMeta.set(key.toLowerCase(), m); } const MODULE_ICONS = ['file-code-2', 'braces', 'component', 'route', 'file-type-2', 'figma', 'layout-panel-left', 'wrench']; const education = dbModules.map((m, i) => { const meta = eduMeta.get(String(m.name || '').trim().toLowerCase()) || {}; const lessons = num(m.lessons_count) || 0; const done = num(m.entries_count) || 0; let progress = (meta.progress === null || meta.progress === undefined) ? null : num(meta.progress); if (progress === null && lessons > 0) progress = Math.max(1, Math.min(100, Math.round((done / lessons) * 100))); if (progress !== null) progress = Math.max(0, Math.min(100, Math.round(progress))); const teacher = str(meta.teacher, 150); return { icon: iconName(meta.icon, MODULE_ICONS[i % MODULE_ICONS.length]), code: 'MOD-' + String(m.id).padStart(2, '0'), title: str(m.name, 200) || 'Модуль', teacher: teacher ? 'Преподаватель: ' + teacher : null, period: rangeLabel(m.first_at, m.last_at), lessons: lessons || null, done, progress, grade: str(meta.grade, 90), }; }); const stats = []; if (showEntries) stats.push({ icon: 'clipboard-list', value: entries.length, suffix: plural(entries.length, 'запись', 'записи', 'записей'), label: 'Записей в журнале', hint: 'что делал ученик на занятиях' }); if (showPhotos) stats.push({ icon: 'image', value: photos.length, suffix: plural(photos.length, 'фото', 'фото', 'фото'), label: 'Фотографий', hint: 'снимки с занятий' }); if (showFiles) stats.push({ icon: 'folder-git-2', value: files.length, suffix: plural(files.length, 'работа', 'работы', 'работ'), label: 'Работ в портфолио', hint: 'файлы, прикреплённые к занятиям' }); stats.push({ icon: 'users-round', value: groups.length, suffix: plural(groups.length, 'группа', 'группы', 'групп'), label: 'Групп', hint: groups.join(' · ') || '—' }); stats.push({ icon: 'book-open-check', value: education.length, suffix: plural(education.length, 'тема', 'темы', 'тем'), label: 'Тем программы', hint: 'модули, которые проходил ученик' }); if (days && showEntries) stats.push({ icon: 'calendar-clock', value: days, suffix: plural(days, 'день', 'дня', 'дней'), label: 'В академии', hint: 'от первой записи в журнале' }); for (const s of arr(profile.stats)) { const label = str(s && s.label, 80); const value = (s && s.value !== undefined && s.value !== null) ? String(s.value).trim().slice(0, 20) : ''; if (!label || !value) continue; stats.push({ icon: iconName(s.icon, 'star'), value, raw: value, suffix: str(s.suffix, 20), label, hint: str(s.hint, 120), delta: str(s.delta, 60) }); } const skillGroups = []; if (arr(profile.skills).length) { const map = new Map(); for (const s of arr(profile.skills)) { const title = str(s && s.group, 80) || 'Навыки'; const itemName = str(s && s.name, 120); if (!itemName) continue; const value = (s.value === null || s.value === undefined) ? null : Math.max(0, Math.min(100, Math.round(Number(s.value) || 0))); if (!map.has(title)) map.set(title, []); map.get(title).push({ name: itemName, level: str(s.level, 40), value }); } for (const [title, items] of map) skillGroups.push({ title, icon: iconForGroup(title), items }); } else if (education.length) { skillGroups.push({ title: 'Пройденные темы', icon: 'sparkles', items: education.map(m => ({ name: m.title, level: m.progress === null ? null : (m.progress >= 100 ? 'Изучен' : 'В процессе'), value: m.progress })), }); } const mediaPool = projects.filter(p => p.isImg).map(p => p.src); const experience = arr(profile.experience).map((e, i) => ({ title: str(e && e.title, 160) || 'Кейс', company: str(e && e.company, 160), period: str(e && e.period, 80), date: str(e && e.date, 40) || (showDates ? longDate(e && e.createdAt) : ''), badge: str(e && e.badge, 40) || 'Кейс', description: str(e && e.description, 800), tags: arr(e && e.tags).map(t => str(t, 40)).filter(Boolean), media: mediaPool.length ? mediaPool[i % mediaPool.length] : null, })); const contacts = profContacts.map(c => ({ icon: iconName(c && c.icon, 'link'), label: str(c && c.label, 120), href: safeHref(c && c.href), })).filter(c => c.label); const autoAchievements = []; if (showEntries && entries.length) autoAchievements.push(entries.length + ' ' + plural(entries.length, 'запись', 'записи', 'записей') + ' в журнале занятий'); if (showPhotos && photos.length) autoAchievements.push(photos.length + ' ' + plural(photos.length, 'фотография', 'фотографии', 'фотографий') + ' в архиве занятий'); if (showFiles && files.length) autoAchievements.push(files.length + ' ' + plural(files.length, 'работа', 'работы', 'работ') + ' в портфолио'); if (education.length) autoAchievements.push('Пройдено тем программы: ' + education.length + ' — ' + education.slice(0, 3).map(m => m.title).join(', ')); const achievements = profAchievements.length ? profAchievements.map(a => str(a, 200)).filter(Boolean) : autoAchievements; const facts = []; const city = str(profile.city, 120) || str(student.branch_name, 120); if (city) facts.push({ icon: 'map-pin', text: city }); if (groups.length) facts.push({ icon: 'users-round', text: groups.join(' · ') }); const joined = str(profile.joined, 120) || (student.created_at ? 'В академии с ' + monthYear(student.created_at) : null); if (joined) facts.push({ icon: 'calendar-check', text: joined }); const mentor = str(profile.mentor, 150); if (mentor) facts.push({ icon: 'user-check', text: 'Наставник: ' + mentor }); if (education.length) facts.push({ icon: 'book-open', text: education.length + ' ' + plural(education.length, 'тема', 'темы', 'тем') + ' программы' }); const autoBio = []; if (groups.length) autoBio.push('Ученик группы ' + groups.join(', ') + '.'); if (entries.length) autoBio.push('В журнале ' + entries.length + ' ' + plural(entries.length, 'запись', 'записи', 'записей') + ' о занятиях.'); if (photos.length) autoBio.push('Фотографий: ' + photos.length + '.'); if (files.length) autoBio.push('Работ в портфолио: ' + files.length + '.'); const tagList = arr(profile.tags).map(t => str(t, 40)).filter(Boolean); const heroTags = tagList.length ? tagList : education.slice(0, 6).map(m => m.title); const avatar = showPhotos && photoCards.length ? photoCards[0] : null; const cover = showPhotos && photoCards.length > 1 ? photoCards[1] : null; const eduYears = education.map(m => m.period).filter(Boolean); const idLabel = student.id ? 'ID S-' + String(student.id).padStart(4, '0') : null; return { name, student, profile, entries, education, projects, photoCards, tasks, gallery, groups, stats, skillGroups, experience, contacts, achievements, facts, heroTags, avatar, cover, idLabel, role: str(profile.role, 200) || (education.length ? 'Ученик программы: ' + education[0].title : null), bio: str(profile.bio, 2000) || (autoBio.join(' ') || null), quote: str(profile.quote, 300), status: str(profile.status, 60) || 'Активно учится', statusNote: str(profile.status_note, 120) || (student.created_at ? 'с ' + monthYear(student.created_at) : ''), periodLabel: eduYears.length ? eduYears[0] + ' — ' + eduYears[eduYears.length - 1] : '', showEntries, showPhotos, showFiles, showCaptions, showDates, days, }; } function reportCss() { return ` :root{--bg:#080c1a;--bg-soft:#0d1424;--surface:rgba(255,255,255,.045);--surface-2:rgba(255,255,255,.03);--surface-hover:rgba(255,255,255,.08);--stroke:rgba(255,255,255,.09);--stroke-strong:rgba(255,255,255,.22);--text:#e9eeff;--muted:#96a3c6;--brand:#6d8cff;--brand-2:#a855f7;--accent:#22d3ee;--ok:#34d399;--warn:#fbbf24;--danger:#fb7185;--topbar-bg:rgba(8,12,26,.78);--radius:22px;--radius-sm:14px;--radius-xs:10px;--gap:18px;--max:1440px;--shadow:0 22px 60px rgba(3,7,22,.5);--shadow-soft:0 10px 30px rgba(3,7,22,.35)} [data-theme="light"]{--bg:#f2f5ff;--bg-soft:#fff;--surface:rgba(255,255,255,.92);--surface-2:rgba(15,23,50,.035);--surface-hover:#eef2ff;--stroke:rgba(20,30,70,.10);--stroke-strong:rgba(20,30,70,.24);--text:#101733;--muted:#5a678b;--brand:#3f5df0;--brand-2:#8b3ff0;--accent:#0891b2;--ok:#059669;--warn:#b45309;--danger:#e11d48;--topbar-bg:rgba(255,255,255,.82);--shadow:0 22px 55px rgba(30,45,95,.14);--shadow-soft:0 10px 26px rgba(30,45,95,.10)} *,*::before,*::after{box-sizing:border-box} *{margin:0;padding:0} html{scroll-behavior:smooth;scroll-padding-top:96px} body{min-height:100vh;font-family:'Inter',system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;font-size:16px;line-height:1.55;color:var(--text);background-color:var(--bg);background-image:radial-gradient(1100px 620px at 12% -8%,rgba(109,140,255,.24),transparent 62%),radial-gradient(900px 520px at 92% -4%,rgba(168,85,247,.20),transparent 58%),radial-gradient(800px 600px at 60% 108%,rgba(34,211,238,.12),transparent 60%);background-attachment:fixed;-webkit-font-smoothing:antialiased} a{color:inherit;text-decoration:none} button{font:inherit;color:inherit;background:none;border:0;cursor:pointer} ul,ol{list-style:none} svg{display:block;flex:none} ::selection{background:rgba(109,140,255,.35)} :focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:6px} ::-webkit-scrollbar{width:10px;height:10px} ::-webkit-scrollbar-track{background:transparent} ::-webkit-scrollbar-thumb{background:var(--stroke-strong);border-radius:999px} ::-webkit-scrollbar-thumb:hover{background:var(--brand)} @media (prefers-reduced-motion:reduce){*{animation-duration:.001ms !important;transition-duration:.001ms !important;scroll-behavior:auto !important}} .wrap{width:100%;max-width:var(--max);margin:0 auto;padding:0 24px} @media (max-width:640px){.wrap{padding:0 14px}} .page{padding-top:clamp(20px,3vw,38px);padding-bottom:90px;display:flex;flex-direction:column;gap:clamp(26px,3.4vw,52px)} .section{display:flex;flex-direction:column;gap:18px;scroll-margin-top:96px;min-width:0} .section__head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;flex-wrap:wrap} .section__head>*{min-width:0} .section__title{display:flex;align-items:center;gap:14px;font-size:clamp(1.3rem,2.2vw,1.95rem);font-weight:800;letter-spacing:-.02em;line-height:1.15;flex-wrap:wrap;min-width:0} .section__hint{color:var(--muted);font-size:.95rem;max-width:70ch} .icon-badge{width:46px;height:46px;flex:none;border-radius:var(--radius-sm);display:grid;place-items:center;background:linear-gradient(140deg,rgba(109,140,255,.26),rgba(168,85,247,.22));border:1px solid var(--stroke);color:var(--text);box-shadow:inset 0 1px 0 rgba(255,255,255,.08)} .icon-badge svg{width:22px;height:22px;stroke-width:2} .card{background:var(--surface);border:1px solid var(--stroke);border-radius:var(--radius);box-shadow:var(--shadow);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)} .topbar{position:sticky;top:0;z-index:60;background:var(--topbar-bg);border-bottom:1px solid var(--stroke);backdrop-filter:blur(20px) saturate(160%);-webkit-backdrop-filter:blur(20px) saturate(160%)} .topbar__inner{max-width:var(--max);margin:0 auto;padding:12px 24px;display:flex;align-items:center;gap:14px} @media (max-width:640px){.topbar__inner{padding:10px 14px}} .brand{display:flex;align-items:center;gap:12px;font-weight:800;letter-spacing:-.02em} .brand__logo{width:44px;height:44px;border-radius:14px;display:grid;place-items:center;background:linear-gradient(135deg,var(--brand),var(--brand-2));color:#fff;box-shadow:0 10px 24px rgba(109,140,255,.35)} .brand__logo svg{width:22px;height:22px;stroke-width:2.2} .brand__text small{display:block;font-size:.72rem;font-weight:600;color:var(--muted);letter-spacing:.06em;text-transform:uppercase} @media (max-width:540px){.brand__text{display:none}} .topbar__nav{display:flex;align-items:center;gap:4px;flex:1;justify-content:center} .nav-link{display:flex;align-items:center;gap:8px;padding:10px 15px;border-radius:999px;font-size:.94rem;font-weight:600;color:var(--muted);white-space:nowrap;transition:background .2s ease,color .2s ease} .nav-link svg{width:17px;height:17px;stroke-width:2} .nav-link:hover{background:var(--surface-hover);color:var(--text)} .nav-link[aria-current="true"]{background:linear-gradient(120deg,rgba(109,140,255,.22),rgba(168,85,247,.18));color:var(--text);box-shadow:inset 0 0 0 1px var(--stroke)} .topbar__actions{display:flex;align-items:center;gap:8px;margin-left:auto} .icon-btn{width:44px;height:44px;border-radius:14px;display:grid;place-items:center;background:var(--surface);border:1px solid var(--stroke);color:var(--text);transition:background .2s ease,transform .2s ease,border-color .2s ease} .icon-btn svg{width:19px;height:19px;stroke-width:2} .icon-btn:hover{background:var(--surface-hover);border-color:var(--stroke-strong);transform:translateY(-2px)} .icon-btn[aria-pressed="true"]{background:linear-gradient(120deg,rgba(109,140,255,.3),rgba(168,85,247,.26));border-color:var(--stroke-strong)} .user-chip{display:flex;align-items:center;gap:10px;padding:6px 14px 6px 6px;border-radius:999px;background:var(--surface);border:1px solid var(--stroke);font-weight:700;font-size:.9rem} .user-chip__ph{width:34px;height:34px;border-radius:50%;display:grid;place-items:center;flex:none;background:linear-gradient(135deg,rgba(109,140,255,.3),rgba(168,85,247,.28));border:1px dashed var(--stroke-strong);color:var(--muted)} .user-chip__ph svg{width:16px;height:16px} @media (max-width:1000px){.user-chip__name{display:none}} .topbar__burger{display:none} @media (max-width:1080px){.topbar__nav{position:absolute;top:100%;left:0;right:0;flex-direction:column;align-items:stretch;padding:10px 16px 18px;gap:4px;display:none;background:var(--bg-soft);border-bottom:1px solid var(--stroke);box-shadow:var(--shadow)}.topbar__nav.is-open{display:flex}.nav-link{padding:14px 16px;border-radius:var(--radius-sm);font-size:1rem}.topbar__burger{display:grid}} .btn{display:inline-flex;align-items:center;justify-content:center;gap:10px;padding:13px 20px;border-radius:var(--radius-sm);font-weight:700;font-size:.95rem;border:1px solid var(--stroke);background:var(--surface);color:var(--text);transition:background .2s ease,transform .2s ease,border-color .2s ease,box-shadow .2s ease} .btn svg{width:18px;height:18px;stroke-width:2} .btn:hover{background:var(--surface-hover);border-color:var(--stroke-strong);transform:translateY(-2px)} .btn--primary{background:linear-gradient(120deg,var(--brand),var(--brand-2));color:#fff;border-color:transparent;box-shadow:0 14px 30px rgba(109,140,255,.32)} .btn--primary:hover{box-shadow:0 18px 40px rgba(109,140,255,.42)} .btn--ghost{background:transparent} .btn--sm{padding:9px 14px;font-size:.85rem;border-radius:var(--radius-xs)} .btn--sm svg{width:16px;height:16px} .btn[disabled]{opacity:.45;cursor:not-allowed;transform:none} .chip{display:inline-flex;align-items:center;gap:7px;padding:7px 13px;border-radius:999px;background:var(--surface-2);border:1px solid var(--stroke);color:var(--muted);font-size:.82rem;font-weight:600;white-space:nowrap} .chip svg{width:14px;height:14px;stroke-width:2.2} .chip--brand{color:var(--text);background:linear-gradient(120deg,rgba(109,140,255,.18),rgba(168,85,247,.14))} .badge{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:999px;font-size:.74rem;font-weight:800;letter-spacing:.02em;text-transform:uppercase;border:1px solid var(--stroke)} .badge svg{width:13px;height:13px;stroke-width:2.4} .badge--ok{color:var(--ok);background:rgba(52,211,153,.14);border-color:rgba(52,211,153,.36)} .badge--progress{color:var(--warn);background:rgba(251,191,36,.14);border-color:rgba(251,191,36,.36)} .badge--info{color:var(--accent);background:rgba(34,211,238,.14);border-color:rgba(34,211,238,.36)} .badge--muted{color:var(--muted);background:var(--surface-2)} .meta{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:.84rem;font-weight:600;flex-wrap:wrap} .meta svg{width:15px;height:15px;stroke-width:2} .meta__dot{width:4px;height:4px;border-radius:50%;background:var(--stroke-strong);flex:none} .ph{position:relative;display:grid;place-items:center;gap:8px;overflow:hidden;background-color:var(--surface-2);background-image:repeating-linear-gradient(45deg,transparent 0 12px,rgba(127,150,255,.07) 12px 24px),linear-gradient(135deg,rgba(109,140,255,.16),rgba(168,85,247,.14));border:1px dashed var(--stroke-strong);color:var(--muted);text-align:center;padding:14px} .ph__icon{width:clamp(26px,3.4vw,42px);height:clamp(26px,3.4vw,42px);stroke-width:1.6;opacity:.85} .ph__label{font-size:.8rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase} .ph__size{position:absolute;right:12px;bottom:10px;padding:3px 9px;border-radius:999px;font-family:'JetBrains Mono',ui-monospace,monospace;font-size:.7rem;font-weight:500;background:rgba(6,10,24,.55);color:#e4eaff;border:1px solid var(--stroke)} [data-theme="light"] .ph__size{background:rgba(255,255,255,.86);color:#31386a} .ph--square{aspect-ratio:1/1} .ph--16x9{aspect-ratio:16/9} .ph--4x3{aspect-ratio:4/3} .ph--wide{aspect-ratio:21/9} .media{position:relative;display:block;width:100%;overflow:hidden;background:var(--surface-2)} .media img,.media video{width:100%;height:100%;object-fit:cover;display:block} .media--4x3{aspect-ratio:4/3} .media--16x9{aspect-ratio:16/9} .media--square{aspect-ratio:1/1} .media--wide{aspect-ratio:21/9} .media[data-g]{cursor:zoom-in} .hero{padding:clamp(20px,2.6vw,34px);overflow:hidden;position:relative} .hero__cover{border-radius:var(--radius-sm);margin-bottom:clamp(-72px,-5vw,-48px)} .hero__grid{display:grid;grid-template-columns:minmax(240px,380px) minmax(0,1fr);gap:clamp(18px,2.6vw,34px);align-items:end} @media (max-width:900px){.hero__grid{grid-template-columns:1fr;align-items:start}.hero__cover{display:none}} .hero__media{position:relative;padding-top:clamp(10px,2vw,26px)} .hero__avatar{border-radius:26px;overflow:hidden;box-shadow:var(--shadow)} .hero__status{position:absolute;left:14px;bottom:14px;padding:7px 12px;border-radius:999px;background:rgba(6,10,24,.6);backdrop-filter:blur(8px);border:1px solid var(--stroke);color:#eaf0ff;font-size:.78rem;font-weight:800;display:inline-flex;align-items:center;gap:7px} .hero__pulse{width:8px;height:8px;border-radius:50%;background:var(--ok);box-shadow:0 0 0 0 rgba(52,211,153,.7);animation:pulse 2s infinite} @keyframes pulse{70%{box-shadow:0 0 0 12px rgba(52,211,153,0)}100%{box-shadow:0 0 0 0 rgba(52,211,153,0)}} .hero__info{display:flex;flex-direction:column;gap:14px;min-width:0} .hero__eyebrow{display:flex;align-items:center;gap:10px;color:var(--muted);font-size:.82rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;flex-wrap:wrap} .hero__name{font-size:clamp(1.9rem,4.4vw,3.4rem);font-weight:800;letter-spacing:-.035em;line-height:1.05;overflow-wrap:anywhere} .hero__role{font-size:clamp(1rem,1.7vw,1.3rem);font-weight:600;color:var(--muted);max-width:60ch} .hero__tags{display:flex;flex-wrap:wrap;gap:8px} .hero__actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:4px} .hero__facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px;margin-top:8px} @media (max-width:640px){.hero__facts{grid-template-columns:repeat(2,minmax(0,1fr))}} .fact{display:flex;align-items:center;gap:10px;padding:12px 14px;border-radius:var(--radius-sm);background:var(--surface-2);border:1px solid var(--stroke);min-width:0} .fact svg{width:18px;height:18px;color:var(--brand);flex:none} .fact span{font-weight:600;font-size:.9rem;color:var(--muted);overflow-wrap:anywhere} .stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:var(--gap)} @media (max-width:620px){.stats{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}} .stat{padding:clamp(14px,1.6vw,20px);display:flex;flex-direction:column;gap:10px;min-width:0} .stat__top{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap} .stat__icon{width:46px;height:46px;border-radius:var(--radius-sm);display:grid;place-items:center;flex:none;background:linear-gradient(140deg,rgba(109,140,255,.22),rgba(168,85,247,.18));border:1px solid var(--stroke);color:var(--brand)} @media (max-width:620px){.stat__icon{width:38px;height:38px}} .stat__icon svg{width:21px;height:21px;stroke-width:2} .stat__value{font-size:clamp(1.7rem,3.2vw,2.6rem);font-weight:800;letter-spacing:-.04em;line-height:1} .stat__value small{font-size:.5em;font-weight:700;color:var(--muted);letter-spacing:0} .stat__label{font-size:.92rem;font-weight:700} .stat__hint{font-size:.78rem;color:var(--muted);font-weight:600;overflow-wrap:anywhere} .stat__delta{display:inline-flex;align-items:center;gap:5px;font-size:.78rem;font-weight:800;color:var(--ok)} .stat__delta svg{width:14px;height:14px;stroke-width:2.4} .modules{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:var(--gap)} @media (max-width:720px){.modules{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}} .module{padding:clamp(14px,1.7vw,20px);display:flex;flex-direction:column;gap:12px;min-width:0} .module__head{display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap} .module__head>div{min-width:0;flex:1 1 55%} .module__icon{width:46px;height:46px;border-radius:var(--radius-sm);display:grid;place-items:center;flex:none;background:var(--surface-2);border:1px solid var(--stroke);color:var(--accent)} .module__icon svg{width:22px;height:22px;stroke-width:2} @media (max-width:720px){.module__icon{width:38px;height:38px}.module__icon svg{width:18px;height:18px}} .module__title{font-size:1.05rem;font-weight:800;letter-spacing:-.01em;overflow-wrap:anywhere} .module__sub{font-size:.8rem;color:var(--muted);font-weight:600;overflow-wrap:anywhere} .module__code{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:.72rem;color:var(--muted)} .module__foot{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap} .module__grade{font-size:.8rem;font-weight:800;color:var(--ok)} .bar{height:8px;border-radius:999px;background:var(--surface-2);border:1px solid var(--stroke);overflow:hidden} .bar>i{display:block;height:100%;width:0;border-radius:inherit;background:linear-gradient(90deg,var(--brand),var(--accent));transition:width 1.1s cubic-bezier(.2,.8,.2,1)} .bar--warn>i{background:linear-gradient(90deg,var(--warn),var(--danger))} .bar--muted>i{background:linear-gradient(90deg,var(--muted),var(--stroke-strong))} .module__progress{display:flex;align-items:center;gap:10px} .module__progress b{font-size:.8rem;font-weight:800;white-space:nowrap} .rail-wrap{position:relative} .rail{display:flex;gap:var(--gap);overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;scroll-behavior:smooth;padding:4px 4px 16px;scrollbar-width:thin} .rail>*{scroll-snap-align:start;min-width:0} .rail--half>*{flex:0 0 calc(50% - var(--gap)/2)} .rail--third>*{flex:0 0 calc((100% - var(--gap) * 2) / 3)} @media (max-width:1100px){.rail--third>*{flex:0 0 calc(50% - var(--gap)/2)}} @media (max-width:720px){.rail{gap:12px;padding-bottom:12px}.rail--half>*,.rail--third>*{flex:0 0 calc(50% - 6px)}} .rail-controls{display:flex;align-items:center;gap:8px} .rail-progress{height:6px;border-radius:999px;background:var(--surface-2);border:1px solid var(--stroke);overflow:hidden;width:min(260px,40vw)} .rail-progress>i{display:block;height:100%;width:20%;border-radius:inherit;background:linear-gradient(90deg,var(--brand),var(--accent));transition:transform .25s ease,width .25s ease} .exp-card{display:flex;flex-direction:column;overflow:hidden} .exp-card__media{position:relative} .exp-card__logo{position:absolute;left:14px;bottom:-22px;width:56px;height:56px;border-radius:16px;display:grid;place-items:center;background:var(--bg-soft);border:1px solid var(--stroke);box-shadow:var(--shadow-soft);color:var(--brand)} .exp-card__logo svg{width:24px;height:24px;stroke-width:2} .exp-card__body{padding:32px clamp(14px,1.6vw,20px) clamp(14px,1.6vw,20px);display:flex;flex-direction:column;gap:10px;flex:1;min-width:0} .exp-card__title{font-size:clamp(1rem,1.5vw,1.2rem);font-weight:800;letter-spacing:-.02em;overflow-wrap:anywhere} .exp-card__company{font-size:.88rem;font-weight:700;color:var(--muted)} .exp-card__text{font-size:.88rem;color:var(--muted);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden} .exp-card__foot{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:auto;flex-wrap:wrap} .prj-card{display:flex;flex-direction:column;overflow:hidden;transition:transform .25s ease,box-shadow .25s ease} .prj-card:hover{transform:translateY(-6px)} .prj-card__body{padding:clamp(14px,1.6vw,18px);display:flex;flex-direction:column;gap:10px;flex:1;min-width:0} .prj-card__title{font-size:clamp(.98rem,1.4vw,1.15rem);font-weight:800;overflow-wrap:anywhere} .prj-card__text{font-size:.85rem;color:var(--muted);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden} .prj-card__tags{display:flex;flex-wrap:wrap;gap:6px} .prj-card__foot{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:auto;flex-wrap:wrap} .prj-card__stats{display:flex;align-items:center;gap:12px;color:var(--muted);font-size:.8rem;font-weight:700} .prj-card__stats span{display:inline-flex;align-items:center;gap:5px} .prj-card__stats svg{width:15px;height:15px} .photos{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px} @media (max-width:620px){.photos{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}} .photo-card{overflow:hidden;display:flex;flex-direction:column;min-width:0} .photo-card figcaption{padding:10px 12px;font-size:.8rem;font-weight:600;color:var(--muted);display:flex;flex-direction:column;gap:2px;min-width:0} .photo-card figcaption b{color:var(--text);font-weight:700;overflow-wrap:anywhere} .skills{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:var(--gap)} @media (max-width:720px){.skills{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}} .skill-group{padding:clamp(14px,1.6vw,20px);display:flex;flex-direction:column;gap:14px;min-width:0} .skill-group__head{display:flex;align-items:center;gap:10px;font-weight:800;font-size:1rem} .skill-group__head svg{width:19px;height:19px;color:var(--brand);flex:none} .skill-group__count{margin-left:auto;font-size:.76rem;font-weight:800;color:var(--muted);background:var(--surface-2);border:1px solid var(--stroke);padding:3px 9px;border-radius:999px;white-space:nowrap} .skill-list{display:flex;flex-direction:column;gap:11px} .skill{display:flex;flex-direction:column;gap:6px;min-width:0} .skill__top{display:flex;align-items:center;justify-content:space-between;gap:8px} .skill__name{font-size:.86rem;font-weight:700;overflow-wrap:anywhere} .skill__val{font-size:.74rem;font-weight:800;color:var(--muted);white-space:nowrap} .tasks{display:grid;grid-template-columns:repeat(auto-fit,minmax(340px,1fr));gap:12px} @media (max-width:760px){.tasks{grid-template-columns:1fr}} .task{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:14px;align-items:center;padding:14px 16px;transition:background .2s ease,border-color .2s ease} .task:hover{background:var(--surface-hover);border-color:var(--stroke-strong)} .task__check{width:42px;height:42px;border-radius:12px;display:grid;place-items:center;flex:none;background:rgba(52,211,153,.14);border:1px solid rgba(52,211,153,.32);color:var(--ok)} .task__check svg{width:20px;height:20px;stroke-width:2.4} .task__check--progress{background:rgba(251,191,36,.14);border-color:rgba(251,191,36,.32);color:var(--warn)} .task__check--error{background:rgba(251,113,133,.14);border-color:rgba(251,113,133,.32);color:var(--danger)} .task__main{min-width:0;display:flex;flex-direction:column;gap:6px} .task__title{font-size:.95rem;font-weight:800;overflow-wrap:anywhere} .task__sub{font-size:.79rem;color:var(--muted);font-weight:600;overflow-wrap:anywhere} .task__right{display:flex;flex-direction:column;align-items:flex-end;gap:6px} .task__extra{display:flex;flex-wrap:wrap;gap:6px} .task__extra a{display:inline-flex;align-items:center;gap:5px;font-size:.75rem;font-weight:700;color:var(--muted);border:1px solid var(--stroke);border-radius:999px;padding:3px 9px;background:var(--surface-2)} .task__extra a:hover{color:var(--text);border-color:var(--stroke-strong)} .task__extra a svg{width:12px;height:12px} .task__thumbs{display:flex;gap:6px;flex-wrap:wrap} .task__thumbs img{width:64px;height:64px;object-fit:cover;border-radius:10px;border:1px solid var(--stroke);cursor:zoom-in} @media (max-width:520px){.task{grid-template-columns:auto minmax(0,1fr)}.task__right{grid-column:2;align-items:flex-start}} .about{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:var(--gap)} @media (max-width:960px){.about{grid-template-columns:1fr}} .about__bio{padding:clamp(18px,2vw,26px);display:flex;flex-direction:column;gap:14px} .about__text{font-size:clamp(.95rem,1.2vw,1.05rem);color:var(--muted)} .about__text b{color:var(--text)} .about__quote{margin-top:auto;padding-left:16px;border-left:3px solid var(--brand);font-style:italic;font-weight:600;color:var(--text)} .about__side{display:grid;gap:var(--gap);grid-template-columns:1fr} @media (max-width:960px){.about__side{grid-template-columns:repeat(2,minmax(0,1fr))}} @media (max-width:640px){.about__side{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}} .about__card{padding:clamp(14px,1.6vw,20px);display:flex;flex-direction:column;gap:12px;min-width:0} .about__card h3{font-size:.95rem;font-weight:800;display:flex;align-items:center;gap:9px} .about__card h3 svg{width:18px;height:18px;color:var(--brand);flex:none} .about__photo{border-radius:var(--radius-sm);overflow:hidden} .about__photo img{width:100%;display:block;aspect-ratio:4/3;object-fit:cover} .contacts{display:flex;flex-wrap:wrap;gap:8px} .contact{display:inline-flex;align-items:center;gap:9px;padding:10px 13px;border-radius:var(--radius-xs);background:var(--surface-2);border:1px solid var(--stroke);font-size:.85rem;font-weight:700;transition:background .2s ease,transform .2s ease;overflow-wrap:anywhere} .contact:hover{background:var(--surface-hover);transform:translateY(-2px)} .contact svg{width:16px;height:16px;color:var(--brand);flex:none} .about__list{display:flex;flex-direction:column;gap:10px} .about__list li{display:flex;gap:10px;align-items:flex-start;font-size:.86rem;font-weight:600;color:var(--muted)} .about__list svg{width:17px;height:17px;color:var(--ok);margin-top:2px;flex:none} .empty{display:flex;flex-direction:column;align-items:center;gap:10px;padding:clamp(22px,3vw,38px);text-align:center;color:var(--muted);font-size:.9rem;font-weight:600} .empty svg{width:30px;height:30px;stroke-width:1.8;opacity:.8} .empty b{color:var(--text);font-weight:800;font-size:.98rem} .empty small{font-weight:600;opacity:.85;max-width:56ch} .footer{margin-top:10px;padding:26px 0 0;border-top:1px solid var(--stroke);display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;color:var(--muted);font-size:.85rem;font-weight:600} .footer__links{display:flex;gap:16px;flex-wrap:wrap} .footer__links a:hover{color:var(--text)} .toast{position:fixed;left:50%;bottom:26px;z-index:90;padding:12px 20px;border-radius:999px;background:var(--bg-soft);border:1px solid var(--stroke);box-shadow:var(--shadow);font-weight:700;font-size:.88rem;opacity:0;transform:translate(-50%,140%);transition:transform .3s cubic-bezier(.2,.8,.2,1),opacity .3s ease;pointer-events:none;max-width:88vw;text-align:center} .toast.is-show{transform:translate(-50%,0);opacity:1} .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap} .reveal{opacity:0;transform:translateY(18px);transition:opacity .6s ease,transform .6s cubic-bezier(.2,.8,.2,1)} .reveal.is-in{opacity:1;transform:none} .lb{position:fixed;inset:0;z-index:100;display:none;align-items:center;justify-content:center;background:rgba(4,7,18,.9);backdrop-filter:blur(6px);padding:24px} .lb.open{display:flex} .lb__media{max-width:min(1200px,92vw);max-height:86vh;display:grid;place-items:center} .lb__media img,.lb__media video{max-width:min(1200px,92vw);max-height:78vh;border-radius:var(--radius-sm);display:block} .lb__cap{position:absolute;left:50%;bottom:26px;transform:translateX(-50%);color:#dfe6ff;font-size:.85rem;font-weight:600;max-width:80vw;text-align:center} .lb__btn{position:absolute;width:48px;height:48px;border-radius:14px;display:grid;place-items:center;background:var(--surface);border:1px solid var(--stroke);color:#eaf0ff} .lb__btn svg{width:20px;height:20px} .lb__btn:hover{background:var(--surface-hover)} .lb__prev{left:18px;top:50%;transform:translateY(-50%)} .lb__next{right:18px;top:50%;transform:translateY(-50%)} .lb__close{right:18px;top:18px} .lb__count{position:absolute;left:18px;top:18px;padding:6px 12px;border-radius:999px;background:var(--surface);border:1px solid var(--stroke);color:#eaf0ff;font-size:.78rem;font-weight:700} @media (max-width:640px){.lb__prev{left:8px}.lb__next{right:8px}.lb__btn{width:40px;height:40px}} html.no-icons i[data-lucide]{display:inline-block;width:1em;height:1em;border-radius:4px;background:currentColor;opacity:.35;flex:none} html.no-icons .ph__icon{width:32px;height:32px} @media print{ :root{--bg:#fff;--bg-soft:#fff;--surface:#fff;--surface-2:#f5f7ff;--text:#101733;--muted:#48506e;--stroke:rgba(20,30,70,.2);--shadow:none;--shadow-soft:none} body{background-image:none !important} .topbar,.hero__actions,.rail-controls,.lb,.toast,.footer__links{display:none !important} .card{box-shadow:none;border-color:rgba(20,30,70,.25)} .rail{flex-wrap:wrap;overflow:visible} .rail--half>*,.rail--third>*{flex:0 0 calc(50% - var(--gap)/2)} .page{gap:22px;padding-bottom:0} .reveal{opacity:1 !important;transform:none !important} .media img{object-fit:contain} } @media (max-width:620px){.badge{font-size:.68rem;padding:4px 8px;gap:4px}.chip{font-size:.76rem;padding:6px 10px}.stat__delta,.stat__hint{font-size:.74rem}.stat__label{font-size:.85rem}.module__title{font-size:.95rem}.module__sub,.exp-card__company{font-size:.76rem}.skill__name{font-size:.8rem}.icon-badge{width:40px;height:40px}.fact span{font-size:.82rem}.btn{padding:11px 16px;font-size:.9rem}.rail-progress{width:min(150px,34vw)}} @media (max-width:400px){.stats{grid-template-columns:1fr}.about__side{grid-template-columns:1fr}} `; } function iconTag(name) { return ''; } function ratioMod(w, h) { if (w === h) return 'square'; const r = w / h; if (Math.abs(r - 16 / 9) < 0.02) return '16x9'; if (Math.abs(r - 4 / 3) < 0.02) return '4x3'; return 'wide'; } function phBlock(label, w, h, icon, style) { return '
' + escapeHtml(hint) + '
' : '') + '' + escapeHtml(hint) + '
' : '') + '' + escapeHtml(v.role) + '
' : '') + (v.bio ? '' + escapeHtml(v.bio) + '
' : '') + '' + '' + escapeHtml(String(s.value)) + (s.suffix ? ' ' + escapeHtml(s.suffix) + '' : '') + '
' + '' + escapeHtml(s.label) + '
' + (s.hint ? '' + escapeHtml(s.hint) + '
' : '') + '' + escapeHtml(p.description) + '
' : '') + (p.tags.length ? '' : '') + '' + escapeHtml(t.title) + '
' + (t.sub ? '' + escapeHtml(t.sub) + '
' : '') + '' + escapeHtml(v.bio) + '
' : 'Здесь появится рассказ ученика о себе. Добавьте текст в данных профиля.
') + (v.quote ? '«' + escapeHtml(v.quote) + '»
' : '') + (v.facts.length ? '' + escapeHtml(m.teacher) + '
' : '') + '' + escapeHtml(meta) + '
' + '' + escapeHtml([e.company, e.period].filter(Boolean).join(' · ')) + '
' : '') + (e.description ? '' + escapeHtml(e.description) + '
' : '') + (e.tags.length ? '' : '') + '