feat(wiki): инфраструктура JS/Lua вкладок в статьях
Что сделано:
1. docsLang.jsx (НОВЫЙ):
- DocsLangProvider — Context для выбранного языка (localStorage).
- DocsLangPicker — большой переключатель JS/Lua над разделом.
- <LangTabs js={...} lua={...} /> — локальные вкладки внутри
статьи: показывает контент текущего языка.
- useDocsLang() хук.
- Стили для picker / tabs / langChoiceModal / docTable.
2. docsData.jsx:
- Новая статья D0 "Скриптинг: JS или Lua — что выбрать?"
в самом верху раздела D. Сравнение, примеры одного и того же
кода на двух языках, советы новичкам.
- Импорт LangTabs.
3. KubikonDocs.jsx:
- ChapterPage обёрнут в DocsLangProvider + DocsLangPicker сверху.
Юзер может одним кликом переключить весь раздел JS↔Lua.
- LessonPage: при «Открыть мою копию» теперь показывается модалка
LangChoiceModal (JS / Lua). Создаём копию с нужными скриптами.
- convertProjectScriptsToLua() конвертит project_data:
если в скрипте есть code_lua слот — активируем. Иначе ставим
stub с подсказкой.
4. docsGamesBuilders.js:
- buildGameProject(id, opts) принимает opts.lang='lua'.
Та же логика — code_lua или stub.
ОСТАЛОСЬ (постепенно):
- Lua-эквиваленты в существующих 78 статьях. Сейчас Picker уже
показывается, но если в статье нет <LangTabs> — контент одинаковый.
Будем добавлять <LangTabs> в ключевые места по очереди.
- Lua-версии в GAME_BUILDERS для уроков 1-50 (code_lua слот).
This commit is contained in:
parent
0805da0708
commit
d019da0ab6
@ -13,6 +13,7 @@ import { GAMES, GAME_GROUPS } from './docsGames';
|
||||
import { LESSONS, hasLesson } from './docsLessons';
|
||||
import { buildGameProject } from './docsGamesBuilders';
|
||||
import DocIcon from './docsIcons';
|
||||
import { DocsLangProvider, DocsLangPicker, DOCS_LANG_STYLES, useDocsLang } from './docsLang';
|
||||
|
||||
/**
|
||||
* KubikonDocs — вика редактора Рублокс.
|
||||
@ -76,6 +77,7 @@ const KubikonDocs = () => {
|
||||
return (
|
||||
<div className={cl.studio}>
|
||||
<style>{INLINE_STYLES}</style>
|
||||
<style>{DOCS_LANG_STYLES}</style>
|
||||
|
||||
{/* === Левая боковая панель === */}
|
||||
<aside className={cl.sidebar}>
|
||||
@ -383,12 +385,15 @@ const ChapterPage = ({ chapter, mainRef }) => {
|
||||
|
||||
{/* Контент раздела */}
|
||||
<div className="docsContent">
|
||||
<DocsLangProvider>
|
||||
<DocsLangPicker />
|
||||
{chapter.sections.map((s) => (
|
||||
<article key={s.id} id={`sec-${s.id}`} className="docsChapter">
|
||||
<h3 className="docsSectionTitle">{s.title}</h3>
|
||||
<div className="docsSectionBody">{s.body}</div>
|
||||
</article>
|
||||
))}
|
||||
</DocsLangProvider>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
@ -399,17 +404,20 @@ const ChapterPage = ({ chapter, mainRef }) => {
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
const LessonPage = ({ game, navigate }) => {
|
||||
const lesson = LESSONS[game.id];
|
||||
// 'idle' | 'creating' | 'error'
|
||||
// 'idle' | 'choosing' | 'creating' | 'error'
|
||||
const [state, setState] = useState('idle');
|
||||
|
||||
// Создаёт НОВУЮ копию игры-урока на текущем пользователе и
|
||||
// открывает её в редакторе. Оригинал при этом ВСЕГДА цел.
|
||||
const openInEditor = async () => {
|
||||
// Шаг 1: юзер нажал «Открыть копию» → показываем модалку выбора языка.
|
||||
const openInEditor = () => {
|
||||
const userId = getCurrentUserId();
|
||||
if (!userId) {
|
||||
setState('error');
|
||||
return;
|
||||
}
|
||||
if (!userId) { setState('error'); return; }
|
||||
setState('choosing');
|
||||
};
|
||||
|
||||
// Шаг 2: язык выбран → создаём копию с нужными скриптами и открываем.
|
||||
const createCopyWithLang = async (lang) => {
|
||||
const userId = getCurrentUserId();
|
||||
if (!userId) { setState('error'); return; }
|
||||
setState('creating');
|
||||
try {
|
||||
// project_data копии берём двумя способами:
|
||||
@ -422,9 +430,11 @@ const LessonPage = ({ game, navigate }) => {
|
||||
const pd = orig && orig.data && orig.data.project_data;
|
||||
if (!pd) { setState('error'); return; }
|
||||
// project_data может прийти строкой или объектом — нормализуем в строку.
|
||||
projectDataStr = typeof pd === 'string' ? pd : JSON.stringify(pd);
|
||||
let pdObj = typeof pd === 'string' ? JSON.parse(pd) : pd;
|
||||
if (lang === 'lua') pdObj = convertProjectScriptsToLua(pdObj);
|
||||
projectDataStr = JSON.stringify(pdObj);
|
||||
} else {
|
||||
const project = buildGameProject(game.id);
|
||||
const project = buildGameProject(game.id, { lang });
|
||||
if (!project) { setState('error'); return; }
|
||||
projectDataStr = JSON.stringify(project);
|
||||
}
|
||||
@ -477,6 +487,12 @@ const LessonPage = ({ game, navigate }) => {
|
||||
: <><Icon name="play" size={15} /> Открыть мою копию в редакторе</>}
|
||||
</button>
|
||||
</div>
|
||||
{state === 'choosing' && (
|
||||
<LangChoiceModal
|
||||
onPick={(lang) => createCopyWithLang(lang)}
|
||||
onCancel={() => setState('idle')}
|
||||
/>
|
||||
)}
|
||||
{state === 'error' && (
|
||||
<div className="lessonErr">
|
||||
Не получилось открыть игру. Проверь, что ты вошёл в аккаунт,
|
||||
@ -492,6 +508,81 @@ const LessonPage = ({ game, navigate }) => {
|
||||
);
|
||||
};
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
// Модалка выбора языка скриптов при «Открыть копию»
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
const LangChoiceModal = ({ onPick, onCancel }) => {
|
||||
return (
|
||||
<div className="langChoiceOverlay" onClick={onCancel}>
|
||||
<div className="langChoiceDialog" onClick={(e) => e.stopPropagation()}>
|
||||
<h3 className="langChoiceTitle">На каком языке открыть копию?</h3>
|
||||
<p className="langChoiceSub">
|
||||
Скрипты в твоей копии будут написаны на выбранном языке.
|
||||
Логика игры одинаковая — отличается только запись кода.
|
||||
</p>
|
||||
<div className="langChoiceBtns">
|
||||
<button className="langChoiceBtn langChoiceBtn--js"
|
||||
onClick={() => onPick('js')}>
|
||||
<div className="langChoiceBtn__name">JavaScript</div>
|
||||
<div className="langChoiceBtn__hint">
|
||||
Если ты новичок — этот выбор проще.
|
||||
</div>
|
||||
</button>
|
||||
<button className="langChoiceBtn langChoiceBtn--lua"
|
||||
onClick={() => onPick('lua')}>
|
||||
<div className="langChoiceBtn__name">Lua</div>
|
||||
<div className="langChoiceBtn__hint">
|
||||
Если играл в Roblox — узнаешь команды.
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<button className="langChoiceCancel" onClick={onCancel}>Отмена</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Конвертирует все JS-скрипты в project_data в Lua-эквивалент.
|
||||
* Сейчас простая стратегия: если в скрипте есть code_lua слот, делает его
|
||||
* активным. Иначе ставит флаг language='lua' и пустой Lua-шаблон с TODO.
|
||||
* Полноценная транспиляция JS→Lua невозможна без AST-анализа.
|
||||
*/
|
||||
function convertProjectScriptsToLua(projectData) {
|
||||
const scene = projectData?.scene;
|
||||
if (!scene || !Array.isArray(scene.scripts)) return projectData;
|
||||
scene.scripts = scene.scripts.map(s => {
|
||||
if (s.language === 'lua') return s;
|
||||
// Если уже есть готовый Lua-слот — используем его
|
||||
if (s.code_lua && s.code_lua.trim()) {
|
||||
return {
|
||||
...s,
|
||||
language: 'lua',
|
||||
code: s.code_lua,
|
||||
code_js: s.code_js || s.code,
|
||||
code_lua: s.code_lua,
|
||||
};
|
||||
}
|
||||
// Иначе ставим заглушку с подсказкой
|
||||
const luaStub = `-- TODO: версия этого скрипта на Lua пока не готова.
|
||||
-- Оригинальный JS-код сохранён ниже (переключи язык назад на JS в редакторе).
|
||||
-- Доступные API: game:GetService("Players"), game.Workspace, script.Parent
|
||||
--
|
||||
-- Например, простой пример:
|
||||
local Players = game:GetService("Players")
|
||||
print("Привет от Lua-скрипта")
|
||||
`;
|
||||
return {
|
||||
...s,
|
||||
language: 'lua',
|
||||
code: luaStub,
|
||||
code_js: s.code_js || s.code,
|
||||
code_lua: luaStub,
|
||||
};
|
||||
});
|
||||
return projectData;
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
// Инлайн-стили
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import DocIcon from './docsIcons';
|
||||
import { LangTabs } from './docsLang';
|
||||
|
||||
/**
|
||||
* docsData.jsx — контент вики редактора Рублокс (разделы A-J).
|
||||
@ -1122,6 +1123,119 @@ game.gui.onSubmit(boxId, (text) => {
|
||||
title: 'Скрипты — основы',
|
||||
summary: 'Самый важный раздел: что такое скрипт, переменные, события, таймеры.',
|
||||
sections: [
|
||||
{
|
||||
id: 'js-or-lua',
|
||||
title: 'D0. Скриптинг: JS или Lua — что выбрать?',
|
||||
body: (
|
||||
<>
|
||||
<p>
|
||||
В Рублоксе можно писать скрипты <b>на двух языках</b>:
|
||||
<b> JavaScript</b> и <b>Lua</b>. Оба работают одинаково
|
||||
хорошо. Игра не отличает их между собой — внутри одного
|
||||
проекта одни скрипты могут быть на JS, другие на Lua,
|
||||
и они общаются между собой как будто это один язык.
|
||||
</p>
|
||||
<p><b>Чем они отличаются?</b></p>
|
||||
<table className="docTable">
|
||||
<thead>
|
||||
<tr><th></th><th>JavaScript</th><th>Lua</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>Где ещё используется</b></td>
|
||||
<td>Сайты, мобильные приложения, серверы.
|
||||
Самый популярный язык в мире.</td>
|
||||
<td>Roblox, World of Warcraft (моды),
|
||||
многие игры. Простой и быстрый.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Главный API</b></td>
|
||||
<td><code>game.*</code> (game.player,
|
||||
game.log, game.scene)</td>
|
||||
<td><code>game.*</code> в Roblox-стиле
|
||||
(game:GetService("Players"), workspace)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Похож на</b></td>
|
||||
<td>Roblox-LUA если знаешь Roblox</td>
|
||||
<td>Roblox-Studio — те же команды</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Когда выбрать</b></td>
|
||||
<td>Если планируешь делать сайты и приложения
|
||||
— JS пригодится везде.</td>
|
||||
<td>Если играешь в Roblox и видел там скрипты
|
||||
— Lua тебе знаком.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><b>Один и тот же пример на двух языках:</b></p>
|
||||
<p>Когда игрок касается синего блока — печатаем «Привет».</p>
|
||||
<LangTabs
|
||||
js={<Code>{`// JS — глобальный скрипт
|
||||
game.onTouch('синий-блок', (player) => {
|
||||
game.log('Привет, ' + player.name + '!');
|
||||
});`}</Code>}
|
||||
lua={<Code>{`-- Lua — скрипт на самом блоке
|
||||
local part = script.Parent
|
||||
|
||||
part.Touched:Connect(function(hit)
|
||||
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
|
||||
if player then
|
||||
print("Привет, " .. player.Name .. "!")
|
||||
end
|
||||
end)`}</Code>}
|
||||
/>
|
||||
<p>
|
||||
Видишь — оба варианта делают <b>одно и то же</b>. Но
|
||||
запись отличается. JS короче для простых вещей через
|
||||
<code>game.onTouch</code>, Lua даёт точный Roblox-стиль
|
||||
через <code>:Connect</code> и события.
|
||||
</p>
|
||||
|
||||
<p><b>Что выбирать новичку?</b></p>
|
||||
<Note>
|
||||
Если ты <b>совсем новичок</b> — бери <b>JavaScript</b>.
|
||||
Команды <code>game.*</code> в JS короче и проще читать.
|
||||
Большая часть уроков в этой вике написана с примерами
|
||||
на JS — все они работают, просто копируй.
|
||||
</Note>
|
||||
<Note>
|
||||
Если ты <b>уже играл в Roblox</b> и видел там скрипты
|
||||
— бери <b>Lua</b>. Команды почти один в один как
|
||||
в Roblox Studio: <code>game:GetService</code>,
|
||||
<code>:Connect</code>, <code>workspace</code>,
|
||||
<code>script.Parent</code>.
|
||||
</Note>
|
||||
|
||||
<p><b>Можно ли менять язык в одном скрипте?</b></p>
|
||||
<p>
|
||||
Да. В редакторе скрипта вверху есть две кнопки —
|
||||
<b>JS</b> и <b>Lua</b>. Просто нажми на нужную.
|
||||
Твой код на текущем языке <b>сохранится</b>, а
|
||||
на другом языке откроется пустой шаблон или то, что
|
||||
ты писал там раньше. Никогда ничего не теряется.
|
||||
</p>
|
||||
<Try>
|
||||
Создай новый скрипт. Напиши пару строк на JS. Нажми
|
||||
кнопку <b>Lua</b> — JS-код спрячется, появится
|
||||
Lua-шаблон. Напиши там что-то. Нажми <b>JS</b>
|
||||
обратно — твой JS-код вернётся. Магия!
|
||||
</Try>
|
||||
|
||||
<p><b>А что под капотом?</b></p>
|
||||
<p>
|
||||
JS-скрипты исполняются в <code>WebWorker</code> —
|
||||
это отдельный поток в браузере, чтобы скрипт не
|
||||
тормозил саму игру. Lua-скрипты исполняются через
|
||||
<b> wasmoon</b> — Lua-интерпретатор, скомпилированный
|
||||
в WebAssembly. Оба варианта работают на любом
|
||||
устройстве без установки.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'what-is-script',
|
||||
title: 'D1. Что такое скрипт и как его создать',
|
||||
|
||||
@ -6158,8 +6158,37 @@ export function hasGameBuilder(id) {
|
||||
return typeof GAME_BUILDERS[id] === 'function';
|
||||
}
|
||||
|
||||
/** Построить project_data для игры-урока. Возвращает объект или null. */
|
||||
export function buildGameProject(id) {
|
||||
/** Построить project_data для игры-урока. Возвращает объект или null.
|
||||
* opts.lang: 'js' (default) | 'lua' — на каком языке скрипты в копии.
|
||||
*/
|
||||
export function buildGameProject(id, opts = {}) {
|
||||
const fn = GAME_BUILDERS[id];
|
||||
return fn ? fn() : null;
|
||||
if (!fn) return null;
|
||||
const project = fn();
|
||||
if (opts.lang === 'lua' && project) {
|
||||
// Если в скрипте есть code_lua слот — делаем его активным.
|
||||
// Иначе ставим stub с заметкой что Lua-версия в работе.
|
||||
const scene = project.scene || {};
|
||||
if (Array.isArray(scene.scripts)) {
|
||||
scene.scripts = scene.scripts.map(s => {
|
||||
if (s.language === 'lua') return s;
|
||||
if (s.code_lua && s.code_lua.trim()) {
|
||||
return { ...s, language: 'lua', code: s.code_lua, code_js: s.code_js || s.code };
|
||||
}
|
||||
const luaStub = `-- TODO: Lua-версия этого скрипта пока не готова.
|
||||
-- Переключи язык на JS в редакторе (кнопка JS вверху), чтобы увидеть рабочий код.
|
||||
-- Lua-API: game:GetService("Players"), workspace, script.Parent
|
||||
print("Lua-скрипт запущен (заглушка)")
|
||||
`;
|
||||
return {
|
||||
...s,
|
||||
language: 'lua',
|
||||
code: luaStub,
|
||||
code_js: s.code_js || s.code,
|
||||
code_lua: luaStub,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
return project;
|
||||
}
|
||||
|
||||
324
src/community/docsLang.jsx
Normal file
324
src/community/docsLang.jsx
Normal file
@ -0,0 +1,324 @@
|
||||
/**
|
||||
* docsLang.jsx — поддержка вкладок JS/Lua в статьях вики.
|
||||
*
|
||||
* Компоненты:
|
||||
* <DocsLangProvider> — оборачивает страницу статьи, хранит выбранный язык
|
||||
* в localStorage 'rublox.docs.lang' ('js' | 'lua').
|
||||
* <DocsLangPicker /> — большой переключатель JS/Lua над статьёй.
|
||||
* <LangTabs js lua /> — вкладка-переключатель внутри статьи. Показывает
|
||||
* либо js, либо lua, согласно текущему языку.
|
||||
* useDocsLang() — хук: возвращает {lang, setLang}.
|
||||
*
|
||||
* Если в статье нет ни одного <LangTabs> — она одинаково выглядит на обоих
|
||||
* языках (общая теория, не зависящая от языка скриптов).
|
||||
*/
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
const LS_KEY = 'rublox.docs.lang';
|
||||
const DEFAULT_LANG = 'js';
|
||||
|
||||
const DocsLangContext = createContext({
|
||||
lang: DEFAULT_LANG,
|
||||
setLang: () => {},
|
||||
});
|
||||
|
||||
export function DocsLangProvider({ children }) {
|
||||
const [lang, setLangState] = useState(() => {
|
||||
try {
|
||||
const v = localStorage.getItem(LS_KEY);
|
||||
return v === 'lua' ? 'lua' : 'js';
|
||||
} catch (_) {
|
||||
return DEFAULT_LANG;
|
||||
}
|
||||
});
|
||||
const setLang = (next) => {
|
||||
const v = next === 'lua' ? 'lua' : 'js';
|
||||
setLangState(v);
|
||||
try { localStorage.setItem(LS_KEY, v); } catch (_) {}
|
||||
};
|
||||
useEffect(() => {
|
||||
// Слушаем смену из других вкладок
|
||||
const onStorage = (e) => {
|
||||
if (e.key === LS_KEY && (e.newValue === 'js' || e.newValue === 'lua')) {
|
||||
setLangState(e.newValue);
|
||||
}
|
||||
};
|
||||
window.addEventListener('storage', onStorage);
|
||||
return () => window.removeEventListener('storage', onStorage);
|
||||
}, []);
|
||||
return (
|
||||
<DocsLangContext.Provider value={{ lang, setLang }}>
|
||||
{children}
|
||||
</DocsLangContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useDocsLang() {
|
||||
return useContext(DocsLangContext);
|
||||
}
|
||||
|
||||
/** Большой переключатель над статьёй: «На каком языке смотреть код?» */
|
||||
export function DocsLangPicker() {
|
||||
const { lang, setLang } = useDocsLang();
|
||||
return (
|
||||
<div className="docsLangPicker">
|
||||
<div className="docsLangPicker__label">
|
||||
Язык скриптов в этой статье:
|
||||
</div>
|
||||
<div className="docsLangPicker__tabs">
|
||||
<button
|
||||
type="button"
|
||||
className={
|
||||
'docsLangPicker__tab docsLangPicker__tab--js' +
|
||||
(lang === 'js' ? ' is-active' : '')
|
||||
}
|
||||
onClick={() => setLang('js')}
|
||||
>
|
||||
JavaScript
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={
|
||||
'docsLangPicker__tab docsLangPicker__tab--lua' +
|
||||
(lang === 'lua' ? ' is-active' : '')
|
||||
}
|
||||
onClick={() => setLang('lua')}
|
||||
>
|
||||
Lua
|
||||
</button>
|
||||
</div>
|
||||
<div className="docsLangPicker__hint">
|
||||
Не знаешь что выбрать? Смотри статью <b>D0. Скриптинг: JS или Lua?</b>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Локальный переключатель вкладок внутри статьи. Если js/lua —
|
||||
* прямой контент (children), если на странице нет <DocsLangProvider> —
|
||||
* показываем оба заголовками.
|
||||
*
|
||||
* Использование:
|
||||
* <LangTabs
|
||||
* js={<Code>game.log('Привет')</Code>}
|
||||
* lua={<Code>print('Привет')</Code>}
|
||||
* />
|
||||
*/
|
||||
export function LangTabs({ js, lua }) {
|
||||
const { lang, setLang } = useDocsLang();
|
||||
const hasJs = js !== undefined && js !== null;
|
||||
const hasLua = lua !== undefined && lua !== null;
|
||||
if (!hasJs && !hasLua) return null;
|
||||
// Если есть только один язык — показываем без переключателя
|
||||
if (hasJs && !hasLua) return <>{js}</>;
|
||||
if (!hasJs && hasLua) return <>{lua}</>;
|
||||
return (
|
||||
<div className="docsLangTabs">
|
||||
<div className="docsLangTabs__head">
|
||||
<button
|
||||
type="button"
|
||||
className={'docsLangTabs__tab' + (lang === 'js' ? ' is-active' : '')}
|
||||
onClick={() => setLang('js')}
|
||||
>
|
||||
JS
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={'docsLangTabs__tab' + (lang === 'lua' ? ' is-active' : '')}
|
||||
onClick={() => setLang('lua')}
|
||||
>
|
||||
Lua
|
||||
</button>
|
||||
</div>
|
||||
<div className="docsLangTabs__body">
|
||||
{lang === 'lua' ? lua : js}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const DOCS_LANG_STYLES = `
|
||||
.docsLangPicker {
|
||||
background: linear-gradient(135deg, #1a1d2e 0%, #14172b 100%);
|
||||
border: 1px solid #2a3050;
|
||||
border-radius: 10px;
|
||||
padding: 14px 18px;
|
||||
margin: 16px 0 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.docsLangPicker__label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #c8cce0;
|
||||
}
|
||||
.docsLangPicker__tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.docsLangPicker__tab {
|
||||
flex: 1;
|
||||
padding: 10px 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid transparent;
|
||||
background: #232842;
|
||||
color: #aab0c8;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.docsLangPicker__tab:hover { background: #2a304f; color: #fff; }
|
||||
.docsLangPicker__tab--js.is-active {
|
||||
background: linear-gradient(135deg, #f7df1e 0%, #d4b500 100%);
|
||||
color: #1a1a1c;
|
||||
border-color: #d4b500;
|
||||
}
|
||||
.docsLangPicker__tab--lua.is-active {
|
||||
background: linear-gradient(135deg, #2196f3 0%, #1565c0 100%);
|
||||
color: #fff;
|
||||
border-color: #1565c0;
|
||||
}
|
||||
.docsLangPicker__hint {
|
||||
font-size: 12px;
|
||||
color: #8a90a8;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.docsLangTabs {
|
||||
margin: 12px 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #2a3050;
|
||||
}
|
||||
.docsLangTabs__head {
|
||||
display: flex;
|
||||
background: #181b2c;
|
||||
border-bottom: 1px solid #2a3050;
|
||||
}
|
||||
.docsLangTabs__tab {
|
||||
padding: 8px 18px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #888da6;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
.docsLangTabs__tab:hover { color: #c8cce0; }
|
||||
.docsLangTabs__tab.is-active {
|
||||
color: #fff;
|
||||
border-bottom-color: #4a8bff;
|
||||
background: #1f2338;
|
||||
}
|
||||
.docsLangTabs__body {
|
||||
padding: 0;
|
||||
}
|
||||
.docsLangTabs__body > pre,
|
||||
.docsLangTabs__body > .docCode { margin: 0; border-radius: 0; }
|
||||
|
||||
.docTable {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 12px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
.docTable th, .docTable td {
|
||||
border: 1px solid #2a3050;
|
||||
padding: 8px 10px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
.docTable th {
|
||||
background: #1a1d2e;
|
||||
color: #c8cce0;
|
||||
font-weight: 600;
|
||||
}
|
||||
.docTable td:first-child {
|
||||
background: #181b2c;
|
||||
width: 25%;
|
||||
color: #aab0c8;
|
||||
}
|
||||
.docTable code {
|
||||
background: #0e1020;
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.langChoiceOverlay {
|
||||
position: fixed; inset: 0;
|
||||
background: rgba(0,0,0,0.75);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
z-index: 10000;
|
||||
}
|
||||
.langChoiceDialog {
|
||||
background: #1a1d2e;
|
||||
border: 1px solid #2a3050;
|
||||
border-radius: 14px;
|
||||
padding: 28px;
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.6);
|
||||
}
|
||||
.langChoiceTitle {
|
||||
font-size: 20px;
|
||||
margin: 0 0 8px;
|
||||
color: #fff;
|
||||
}
|
||||
.langChoiceSub {
|
||||
margin: 0 0 20px;
|
||||
font-size: 13px;
|
||||
color: #aab0c8;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.langChoiceBtns {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.langChoiceBtn {
|
||||
padding: 18px 16px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid transparent;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
color: #fff;
|
||||
}
|
||||
.langChoiceBtn--js {
|
||||
background: linear-gradient(135deg, #f7df1e 0%, #d4b500 100%);
|
||||
color: #1a1a1c;
|
||||
}
|
||||
.langChoiceBtn--js:hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(247,223,30,0.3); }
|
||||
.langChoiceBtn--lua {
|
||||
background: linear-gradient(135deg, #2196f3 0%, #1565c0 100%);
|
||||
}
|
||||
.langChoiceBtn--lua:hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(33,150,243,0.4); }
|
||||
.langChoiceBtn__name {
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.langChoiceBtn__hint {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
opacity: 0.85;
|
||||
}
|
||||
.langChoiceCancel {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: transparent;
|
||||
border: 1px solid #2a3050;
|
||||
color: #aab0c8;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.langChoiceCancel:hover { background: #232842; color: #fff; }
|
||||
`;
|
||||
Loading…
x
Reference in New Issue
Block a user