import React, { useState, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { jwtDecode } from 'jwt-decode'; import { useAuth } from '../auth/AuthContext.jsx'; import * as Kubikon3DApi from '../api/Kubikon3DService'; import cl from './KubikonStudio.module.css'; import RublocsLogo from '../components/RublocsLogo/RublocsLogo'; import useDeviceType from '../hooks/useDeviceType'; import KubikonDesktopOnlyStub from './KubikonDesktopOnlyStub'; import Icon from '../editor/Icon'; import { DOCS } from './docsData'; import { GAMES, GAME_GROUPS } from './docsGames'; import { LESSONS, hasLesson } from './docsLessons'; import { buildGameProject } from './docsGamesBuilders'; import DocIcon from './docsIcons'; /** * KubikonDocs — вика редактора Рублокс. * URL: /docs * * Навигация (внутренний state activeChapter, без роутинга): * - null → главная вики: разделы A-J + сетка 50 игр-уроков * - 'lesson:' → страница урока конкретной игры * - → отдельная страница раздела A-J * * Все карточки обучалок (разделы вики и игры-уроки) — на одной * главной странице; отдельной страницы «раздел K» больше нет. * * Контент разделов A-J — docsData.jsx, каталог игр — docsGames.js, * тексты уроков — docsLessons.jsx, билдеры играбельных версий — * docsGamesBuilders.js. Иконки — самописные SVG (docsIcons.jsx). */ function getCurrentUserId() { try { const token = localStorage.getItem('Authorization'); return token ? jwtDecode(token).id : null; } catch { return null; } } // Звёзды сложности — ряд SVG-иконок const Stars = ({ n }) => ( {Array.from({ length: n }).map((_, i) => ( ))} ); const KubikonDocs = () => { const navigate = useNavigate(); const { isLoading } = useAuth(); const { isDesktop } = useDeviceType(); const mainRef = useRef(null); // null = главная вики, 'lesson:' = урок, иначе id раздела из DOCS const [activeChapter, setActiveChapter] = useState(null); // при смене раздела — прокрутка вверх useEffect(() => { if (mainRef.current) mainRef.current.scrollTo({ top: 0 }); }, [activeChapter]); if (!isDesktop) { return ; } const chapter = DOCS.find((c) => c.id === activeChapter) || null; // если открыт урок — activeChapter имеет вид 'lesson:' const lessonId = typeof activeChapter === 'string' && activeChapter.startsWith('lesson:') ? activeChapter.slice(7) : null; const lessonGame = lessonId ? GAMES.find((g) => g.id === lessonId) : null; return (
{/* === Левая боковая панель === */} {/* === Основной контент === */}

{' '} {lessonGame ? `Урок ${lessonGame.num}. ${lessonGame.title}` : chapter ? chapter.title : 'Вики редактора'}

{lessonGame ? 'Урок-игра — собери по шагам' : chapter ? `Раздел вики · ${chapter.sections.length} тем` : 'Учебник по созданию 3D-игр в Рублоксе'}

{activeChapter !== null ? ( ) : ( )}
{/* ── ГЛАВНАЯ ВИКИ — разделы A-J + 50 игр-уроков ── */} {activeChapter === null && ( setActiveChapter('lesson:' + id)} /> )} {/* ── СТРАНИЦА УРОКА ── */} {lessonGame && ( )} {/* ── ОТДЕЛЬНАЯ СТРАНИЦА РАЗДЕЛА A-J ── */} {chapter && ( )}
); }; // ══════════════════════════════════════════════════════════════════ // Сетка 50 игр-уроков, сгруппированная по GAME_GROUPS. // Общий блок: используется и на главной вики, и в разделе K. // ══════════════════════════════════════════════════════════════════ const GamesGrid = ({ onOpenLesson }) => ( <> {GAME_GROUPS.map((grp) => { const games = GAMES.filter((g) => g.group === grp.id); return (

{grp.title}

{grp.hint}

{games.map((g) => { const ready = hasLesson(g.id); return ( ); })}
); })} ); // ══════════════════════════════════════════════════════════════════ // Главная вики — разделы A-J + сетка 50 игр-уроков на одной странице // ══════════════════════════════════════════════════════════════════ const WikiHome = ({ onOpen, onOpenLesson }) => { const totalSections = DOCS.reduce((s, c) => s + c.sections.length, 0); return ( <> {/* Hero */}
Вики

Научись создавать игры

Полный учебник по редактору Рублокс. {DOCS.length} разделов, {' '}{totalSections} тем и 50 игр-уроков. Каждый пример можно скопировать к себе в игру — и он заработает.

{/* Сетка разделов A-J */}
Разделы вики
{DOCS.map((c, i) => ( ))}
{/* Раздел K — 50 игр-уроков прямо на этой же странице */}
Практика · K. 50 игр-уроков

Лучший способ научиться — собирать настоящие игры. Игры идут от простого к сложному. Уроки с готовыми играми отмечены зелёным — их можно открыть прямо сейчас.

); }; // ══════════════════════════════════════════════════════════════════ // Отдельная страница раздела A-J // ══════════════════════════════════════════════════════════════════ const ChapterPage = ({ chapter, mainRef }) => { const [activeSec, setActiveSec] = useState(chapter.sections[0]?.id); // подсветка активной темы при скролле useEffect(() => { setActiveSec(chapter.sections[0]?.id); const main = mainRef.current; if (!main) return; const onScroll = () => { const top = main.scrollTop + 120; let cur = chapter.sections[0]?.id; for (const s of chapter.sections) { const el = main.querySelector(`#sec-${s.id}`); if (el && el.offsetTop <= top) cur = s.id; } setActiveSec(cur); }; main.addEventListener('scroll', onScroll); return () => main.removeEventListener('scroll', onScroll); }, [chapter, mainRef]); const scrollToSec = (id) => { const main = mainRef.current; const el = main && main.querySelector(`#sec-${id}`); if (el) main.scrollTo({ top: el.offsetTop - 16, behavior: 'smooth' }); }; return (
{/* Оглавление раздела */} {/* Контент раздела */}
{chapter.sections.map((s) => (

{s.title}

{s.body}
))}
); }; // ══════════════════════════════════════════════════════════════════ // Страница урока — текст инструкции + кнопка «Открыть игру в редакторе» // ══════════════════════════════════════════════════════════════════ const LessonPage = ({ game, navigate }) => { const lesson = LESSONS[game.id]; // 'idle' | 'creating' | 'error' const [state, setState] = useState('idle'); // Создаёт НОВУЮ копию игры-урока на текущем пользователе и // открывает её в редакторе. Оригинал при этом ВСЕГДА цел. const openInEditor = async () => { const userId = getCurrentUserId(); if (!userId) { setState('error'); return; } setState('creating'); try { // project_data копии берём двумя способами: // - у обычных уроков (1-50) — собираем из билдера; // - у разбора готовых игр (g5) — ЗАГРУЖАЕМ project_data // оригинала из БД и копируем его (оригинал не трогаем!). let projectDataStr; if (game.openProjectId) { const orig = await Kubikon3DApi.getProjectWithRetry(game.openProjectId, userId); const pd = orig && orig.data && orig.data.project_data; if (!pd) { setState('error'); return; } // project_data может прийти строкой или объектом — нормализуем в строку. projectDataStr = typeof pd === 'string' ? pd : JSON.stringify(pd); } else { const project = buildGameProject(game.id); if (!project) { setState('error'); return; } projectDataStr = JSON.stringify(project); } const res = await Kubikon3DApi.createProject(userId, { user_id: userId, title: 'Моя копия: ' + game.title, description: 'Игра-урок из вики Рублокса. Это твоя копия — меняй как хочешь, оригинал не пострадает.', genre: 'other', thumbnail: '', is_public: false, project_data: projectDataStr, }); const newId = res.data && res.data.id; if (newId) navigate('/edit/' + newId); else setState('error'); } catch (e) { console.error('[LessonPage] openInEditor error:', e); setState('error'); } }; return (
{/* Hero урока */}
Урок {game.num}

{game.title}

{game.desc}

{/* Кнопка открыть игру */}
Хочешь сразу посмотреть готовую игру?
Открой её в редакторе — создастся твоя личная копия. Меняй её как хочешь, нажимай «Играть» — оригинал не пострадает.
{state === 'error' && (
Не получилось открыть игру. Проверь, что ты вошёл в аккаунт, и попробуй ещё раз.
)} {/* Тело урока */}
{lesson.body}
); }; // ══════════════════════════════════════════════════════════════════ // Инлайн-стили // ══════════════════════════════════════════════════════════════════ const INLINE_STYLES = ` .docsHero { background: linear-gradient(135deg, #3357ff 0%, #6d28d9 50%, #ec4899 100%); background-size: 200% 200%; animation: docsGradient 16s ease-in-out infinite; border-radius: 28px; padding: 36px 40px; margin-bottom: 28px; color: #fff; display: flex; align-items: center; gap: 24px; box-shadow: 0 24px 56px rgba(51, 87, 255, 0.28); } @keyframes docsGradient { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } } .docsHeroContent { flex: 1; min-width: 0; } .docsHeroBadge { display: inline-flex; align-items: center; gap: 6px; background: rgba(255, 255, 255, 0.20); border: 1px solid rgba(255, 255, 255, 0.30); color: #fff; padding: 5px 14px; border-radius: 999px; font-size: 11px; font-weight: 800; letter-spacing: 1.4px; text-transform: uppercase; margin-bottom: 12px; } .docsHeroTitle { margin: 0 0 10px; font-size: 34px; font-weight: 900; color: #fff; letter-spacing: -1px; line-height: 1.08; text-shadow: 0 2px 12px rgba(0, 0, 0, 0.18); } .docsHeroDesc { margin: 0; font-size: 15px; color: rgba(255, 255, 255, 0.94); line-height: 1.55; max-width: 660px; } .docsHeroEmoji { flex-shrink: 0; color: #fff; opacity: 0.92; animation: docsFloat 6s ease-in-out infinite; filter: drop-shadow(0 12px 24px rgba(0, 0, 0, 0.20)); } @keyframes docsFloat { 0%, 100% { transform: translateY(0) rotate(-2deg); } 50% { transform: translateY(-10px) rotate(3deg); } } /* === Метка-заголовок секции на главной вики === ВАЖНО: прямые потомки .main центрируются правилом .main > * { max-width:1240px; margin-left/right:auto }. Для display:block-элемента margin:auto НЕ центрирует, пока у него нет ширины МЕНЬШЕ доступной — блок просто тянется во всю ширину .main (весь экран), а margin:auto схлопывается в 0. Поэтому метка уезжала левее hero/сетки. display:block здесь оставляем, но центрирование делаем явно: width:100% от бокса с max-width:1240px (наследуется от .main > *) + margin:auto центрирует сам бокс. Так левый край метки совпадает с левым краем .docsHero и .wikiGrid. */ .wikiSectionLabel { display: block; width: 100%; max-width: 1240px; margin: 8px auto 14px; font-size: 12px; font-weight: 800; color: #94a3b8; text-transform: uppercase; letter-spacing: 1.4px; } /* === Сетка карточек разделов === */ .wikiGrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; margin-bottom: 30px; } .wikiCard { position: relative; text-align: left; background: #fff; border: 1px solid #e5e7eb; border-radius: 18px; padding: 22px 20px 18px; cursor: pointer; transition: transform 200ms cubic-bezier(0.34,1.56,0.64,1), box-shadow 200ms ease, border-color 200ms ease; font-family: inherit; } .wikiCard:hover { transform: translateY(-4px); box-shadow: 0 16px 36px rgba(15, 23, 42, 0.12); border-color: #3357ff; } .wikiCard__icon { color: #3357ff; margin-bottom: 10px; line-height: 0; } .wikiCard__letter { position: absolute; top: 18px; right: 18px; width: 30px; height: 30px; border-radius: 9px; background: linear-gradient(135deg, #3357ff 0%, #1e2da5 100%); color: #fff; font-size: 15px; font-weight: 900; display: flex; align-items: center; justify-content: center; } .wikiCard__title { font-size: 17px; font-weight: 900; color: #0f172a; margin-bottom: 6px; letter-spacing: -0.3px; } .wikiCard__desc { font-size: 13px; color: #64748b; line-height: 1.5; margin-bottom: 12px; } .wikiCard__meta { font-size: 12.5px; font-weight: 800; color: #3357ff; } /* === Вводный текст к блоку 50 игр-уроков === центрируется так же, как .wikiSectionLabel (см. комментарий выше). */ .wikiGamesIntro { display: block; width: 100%; max-width: 1240px; margin: -6px auto 18px; font-size: 14px; line-height: 1.6; color: #64748b; } /* === Body раздела: TOC + контент === */ .docsBody { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 28px; align-items: flex-start; } .docsToc { position: sticky; top: 8px; background: #fff; border: 1px solid #e5e7eb; border-radius: 16px; padding: 12px 8px; box-shadow: 0 4px 16px rgba(15, 23, 42, 0.04); } .docsTocTitle { display: flex; align-items: center; gap: 8px; padding: 6px 12px 10px; font-size: 13px; font-weight: 900; color: #0f172a; border-bottom: 1px solid #eef2f7; margin-bottom: 6px; } .docsTocTitle svg { color: #3357ff; flex-shrink: 0; } .docsTocItem { display: block; width: 100%; padding: 8px 12px; background: transparent; border: none; border-radius: 9px; color: #475569; font-size: 12.5px; font-weight: 700; cursor: pointer; text-align: left; transition: all 160ms ease; font-family: inherit; line-height: 1.4; } .docsTocItem:hover { background: #f1f5f9; color: #0f172a; } .docsTocItemActive, .docsTocItemActive:hover { background: linear-gradient(135deg, #3357ff 0%, #1e2da5 100%); color: #fff; } /* === Контент раздела === */ .docsContent { min-width: 0; display: flex; flex-direction: column; gap: 18px; } .docsChapter { background: #fff; border: 1px solid #e5e7eb; border-radius: 18px; padding: 24px 28px; box-shadow: 0 4px 16px rgba(15, 23, 42, 0.04); scroll-margin-top: 16px; } .docsSectionTitle { margin: 0 0 14px; font-size: 20px; font-weight: 900; color: #0f172a; letter-spacing: -0.4px; padding-bottom: 12px; border-bottom: 1px solid #eef2f7; } .docsSectionBody { color: #334155; font-size: 14.5px; line-height: 1.65; } .docsSectionBody p { margin: 0 0 12px; } .docsSectionBody p:last-child { margin-bottom: 0; } .docsSectionBody ul, .docsSectionBody ol { margin: 0 0 12px; padding-left: 22px; } .docsSectionBody li { margin-bottom: 8px; line-height: 1.55; } .docsSectionBody li:last-child { margin-bottom: 0; } .docsSectionBody b { color: #0f172a; font-weight: 800; } .docsSectionBody h4 { font-family: inherit; } .docsSectionBody code { background: #e0e8ff; color: #3357ff; padding: 2px 7px; border-radius: 6px; font-family: Consolas, Menlo, "Courier New", monospace; font-size: 13px; font-weight: 700; } /* kbd */ .docsSectionBody .kbd, .kbd { display: inline-block; background: #f1f5f9; border: 1px solid #cbd5e1; border-radius: 5px; padding: 1px 8px; font-family: "Roboto Condensed", system-ui, sans-serif; font-size: 12px; font-weight: 800; color: #475569; margin: 0 1px; } /* Код-блок */ .docCode { background: #0f172a; color: #e2e8f0; border-radius: 12px; padding: 14px 16px; margin: 12px 0 14px; overflow-x: auto; font-family: Consolas, Menlo, "Courier New", monospace; font-size: 13px; line-height: 1.6; } .docCode code { background: none; color: inherit; padding: 0; font-weight: 500; font-size: 13px; white-space: pre; } /* Скриншот интерфейса с подписью. Картинка вписывается в рамку: не шире контейнера и не выше 360px, узкие вертикальные скрины (иерархия) не растягиваются на всю высоту. */ .docShot { margin: 14px auto; border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; background: #f8fafc; box-shadow: 0 4px 14px rgba(15, 23, 42, 0.06); width: fit-content; max-width: 100%; } .docShot img { display: block; max-width: 100%; max-height: 360px; width: auto; height: auto; } .docShot figcaption { padding: 8px 14px; font-size: 12.5px; font-weight: 600; color: #64748b; background: #fff; border-top: 1px solid #eef2f7; text-align: center; } /* широкоформатные скрины (обзор, лента) — во всю ширину */ .docShot--wide { width: 100%; } .docShot--wide img { width: 100%; max-height: none; } /* Плашка «куда писать скрипт» */ .docScriptKind { display: flex; gap: 10px; align-items: flex-start; border-radius: 12px; padding: 12px 14px; margin: 12px 0; font-size: 13px; line-height: 1.55; } .docScriptKind__ico { flex-shrink: 0; line-height: 0; margin-top: 1px; } .docScriptKind--global { background: #eef2ff; border: 1px solid #c7d2fe; color: #3730a3; } .docScriptKind--object { background: #fef3c7; border: 1px solid #fde68a; color: #92400e; } .docScriptKind code { background: rgba(255,255,255,0.6); } /* Шаг инструкции */ .docStep { display: flex; gap: 12px; align-items: flex-start; margin: 0 0 10px; } .docStep__num { flex-shrink: 0; width: 26px; height: 26px; border-radius: 50%; background: linear-gradient(135deg, #3357ff 0%, #1e2da5 100%); color: #fff; font-size: 13px; font-weight: 900; display: flex; align-items: center; justify-content: center; } .docStep__body { flex: 1; padding-top: 2px; } /* Жёлтая плашка-подсказка */ .docNote { display: flex; gap: 10px; background: #fffbeb; border: 1px solid #fde68a; border-left: 4px solid #f59e0b; border-radius: 10px; padding: 12px 14px; margin: 12px 0; font-size: 13.5px; line-height: 1.55; color: #78350f; } .docNote__ico { flex-shrink: 0; line-height: 0; margin-top: 1px; color: #b45309; } .docNote code { background: #fef3c7; color: #92400e; } /* Зелёная плашка «попробуй сам» */ .docTry { display: flex; gap: 10px; background: #f0fdf4; border: 1px solid #bbf7d0; border-left: 4px solid #16a34a; border-radius: 10px; padding: 12px 14px; margin: 12px 0; font-size: 13.5px; line-height: 1.55; color: #14532d; } .docTry__ico { flex-shrink: 0; line-height: 0; margin-top: 1px; color: #16a34a; } /* Подзаголовок в справочнике */ .docRefHead { display: flex; align-items: center; gap: 8px; margin: 22px 0 10px; font-size: 15px; font-weight: 800; color: #0f172a; padding-bottom: 6px; border-bottom: 2px solid #e0e8ff; } .docRefHead svg { color: #3357ff; flex-shrink: 0; } .docRefHead:first-child { margin-top: 0; } /* Таблицы */ .docTable { width: 100%; border-collapse: separate; border-spacing: 0; background: #fafbfd; border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; margin: 10px 0 12px; } .docTable td { padding: 9px 14px; border-bottom: 1px solid #eef2f7; font-size: 13px; color: #334155; vertical-align: middle; line-height: 1.5; } .docTable tr:last-child td { border-bottom: none; } .docTable td:first-child { width: 220px; font-weight: 700; background: #fff; border-right: 1px solid #eef2f7; } /* === Раздел K — игры === */ .gamesGroup { margin-bottom: 28px; } .gamesGroup__head { margin-bottom: 14px; } .gamesGroup__title { margin: 0 0 4px; font-size: 20px; font-weight: 900; color: #e8edf5; letter-spacing: -0.4px; } .gamesGroup__hint { margin: 0; font-size: 13px; color: #9aa3b8; } /* Звёзды сложности — ряд SVG */ .docStars { display: inline-flex; align-items: center; gap: 1px; color: #f59e0b; vertical-align: middle; } .docStars svg { fill: #f59e0b; stroke: #f59e0b; } .gamesGrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 14px; } .gameCard { background: #1c2231; border: none; border-radius: 16px; padding: 0; overflow: hidden; display: flex; flex-direction: column; transition: transform 200ms ease, box-shadow 200ms ease; font-family: inherit; text-align: left; } /* карточка с готовым уроком — кликабельна */ .gameCard--ready { cursor: pointer; } .gameCard--ready:hover { box-shadow: 0 0 0 2px #16a34a; } /* карточка без урока — приглушена, клик отключён */ .gameCard:disabled { opacity: 0.62; cursor: default; } .gameCard:disabled:hover { transform: none; box-shadow: none; } .gameCard:hover { transform: translateY(-3px); box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35); } /* Превью игры — картинка 16:9 с номером и звёздами поверх, впритык к краям карточки сверху и по бокам. */ .gameCard__preview { position: relative; width: 100%; aspect-ratio: 16 / 9; margin: 0; background: #0f1320; overflow: hidden; } .gameCard__img { display: block; width: 100%; height: 100%; object-fit: cover; } .gameCard--ready:hover .gameCard__img { transform: scale(1.04); } .gameCard__img { transition: transform 300ms ease; } /* плейсхолдер, если урока ещё нет */ .gameCard__noimg { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; color: #3357ff; } /* плашки поверх превью */ .gameCard__num { position: absolute; top: 8px; left: 8px; background: rgba(15, 23, 42, 0.72); color: #fff; font-size: 11.5px; font-weight: 900; padding: 3px 8px; border-radius: 7px; } .gameCard__stars { position: absolute; top: 8px; right: 8px; background: rgba(15, 23, 42, 0.72); padding: 3px 7px; border-radius: 7px; line-height: 0; } .gameCard__stars .docStars svg { fill: #fbbf24; stroke: #fbbf24; } .gameCard__body { display: flex; flex-direction: column; flex: 1; padding: 14px 16px 16px; } .gameCard__title { font-size: 15px; font-weight: 900; color: #e8edf5; margin-bottom: 5px; letter-spacing: -0.2px; } .gameCard__desc { font-size: 12.5px; color: #9aa3b8; line-height: 1.5; margin-bottom: 10px; flex: 1; } .gameCard__mechanics { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; } .gameCard__tag { font-size: 10.5px; font-weight: 700; background: #262d3d; color: #b6becc; padding: 2px 7px; border-radius: 5px; } .gameCard__foot { border-top: 1px solid #262d3d; padding-top: 8px; } .gameCard__soon { font-size: 11.5px; font-weight: 800; color: #6c7691; } .gameCard__ready { font-size: 11.5px; font-weight: 800; color: #4ade80; } /* === Страница урока === */ .lessonWrap { display: flex; flex-direction: column; gap: 16px; } .lessonHero { display: flex; align-items: center; gap: 20px; background: linear-gradient(135deg, #16a34a 0%, #0d9488 100%); border-radius: 22px; padding: 26px 30px; color: #fff; box-shadow: 0 18px 44px rgba(13, 148, 136, 0.26); } .lessonHero__icon { flex-shrink: 0; width: 76px; height: 76px; border-radius: 18px; background: rgba(255,255,255,0.16); color: #fff; display: flex; align-items: center; justify-content: center; } .lessonHero__body { flex: 1; min-width: 0; } .lessonHero__num { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase; opacity: 0.9; margin-bottom: 6px; } .lessonHero__num .docStars svg { fill: #fde68a; stroke: #fde68a; } .lessonHero__title { margin: 0 0 6px; font-size: 28px; font-weight: 900; letter-spacing: -0.6px; } .lessonHero__desc { margin: 0; font-size: 14px; line-height: 1.5; opacity: 0.94; } /* блок «открыть игру» */ .lessonOpen { display: flex; align-items: center; gap: 18px; background: #fff; border: 1px solid #e5e7eb; border-left: 4px solid #16a34a; border-radius: 14px; padding: 18px 20px; } .lessonOpen__text { flex: 1; font-size: 13.5px; line-height: 1.55; color: #334155; } .lessonOpen__btn { flex-shrink: 0; display: inline-flex; align-items: center; gap: 8px; background: linear-gradient(135deg, #16a34a 0%, #0d9488 100%); color: #fff; font-size: 14px; font-weight: 900; border: none; border-radius: 10px; padding: 13px 22px; cursor: pointer; font-family: inherit; } .lessonOpen__btn:hover { transform: translateY(-2px); } .lessonOpen__btn:disabled { opacity: 0.6; cursor: default; transform: none; } .lessonErr { background: #fef2f2; border: 1px solid #fecaca; border-radius: 10px; padding: 12px 16px; font-size: 13px; color: #991b1b; } /* тело урока */ .lessonBody { padding: 26px 30px; } .lessonH { margin: 24px 0 12px; font-size: 19px; font-weight: 900; color: #0f172a; letter-spacing: -0.3px; padding-bottom: 8px; border-bottom: 2px solid #d1fae5; } .lessonBody .docsSectionBody > :first-child { margin-top: 0; } .lessonH:first-child { margin-top: 0; } /* Адаптив */ @media (max-width: 980px) { .docsBody { grid-template-columns: 1fr; } .docsToc { position: static; } .docsHero { flex-direction: column; padding: 28px 24px; text-align: center; } .docsHeroEmoji { font-size: 64px; } .docsChapter { padding: 20px; } } `; export default KubikonDocs;