Open-source веб-студия для создания игр Рублокса, двойная лицензия AGPL-3.0 + Коммерческая. Главное: - Vite 5 + React 18 + Babylon 7.54.3 + Monaco Editor + Colyseus 0.16 - Самодостаточный движок ~28к строк (66 файлов): BlockManager, TerrainVoxelBuilder, ModelManager, DecoManager, PlayerController, ScriptSandboxWorker, MultiplayerSync, 30+ GD-гейммодов - Главный редактор KubikonEditor (~37к строк) + панели, ScriptEditor (Monaco) - Витрина игр (KubikonFeed, KubikonStudio, KubikonDocs, KubikonLearn) - Geometry Dash sub-app (GdMenu, GdShop, GdRules, GdCoverArt) - 10 admin-preview каталогов для дизайнеров (скины, музыка, порталы и т.д.) - Конфигурируемый бэкенд через VITE_API_BASE — работает со staging (dev-api.rublox.pro) без настройки - Standalone-режим (VITE_STANDALONE=true) — открыть пустой редактор без бэка - Полная документация (на русском): README, ARCHITECTURE, CONTRIBUTING, SECURITY, CHANGELOG - ESLint + Prettier + EditorConfig - Legal: LICENSE (AGPL-3.0), LICENSE-COMMERCIAL.md, CLA.md, COPYRIGHT.md - Issue templates: bug_report, feature_request, security_disclosure Перед публикацией: - Все импорты из minecraftia заменены на локальные - Все хардкоды URL (minecraftia-school.ru) и внутренних IP убраны → env - Admin-эндпоинты Kubikon3DService вырезаны (остаются в приватном репо) - AdminKubikonModeration не публикуется (модерация — в team.rublox.pro) - 93 МБ ассетов public/kubikon-assets вынесены в .gitignore (раздаются через release artifact)
482 lines
10 KiB
CSS
482 lines
10 KiB
CSS
/* ========== GAME SETTINGS / PUBLISH MODAL — синий wow-стиль Рублокса ========== */
|
||
|
||
.overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(7, 10, 20, 0.78);
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
z-index: 10000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20px;
|
||
animation: gsmOverlayFadeIn 0.18s ease;
|
||
font-family: "Roboto Condensed", system-ui, -apple-system, sans-serif;
|
||
}
|
||
|
||
@keyframes gsmOverlayFadeIn {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
|
||
.modal {
|
||
--bg-page: #252525;
|
||
--bg-soft: #1b1b1b;
|
||
--bg-input: #1b1b1b;
|
||
--bg-hover: #383838;
|
||
--accent: #4f74ff;
|
||
--accent-deep:#3a57d8;
|
||
--accent-soft:rgba(79, 116, 255, 0.18);
|
||
--text: #e8e8ea;
|
||
--text-dim: #9a9a9e;
|
||
--text-muted: #7a7a7e;
|
||
--border: #3a3a3a;
|
||
--border-strong: #4a4a4a;
|
||
--border-soft: #303030;
|
||
--danger: #ef4444;
|
||
--danger-bg: rgba(239, 68, 68, 0.16);
|
||
--success: #10b981;
|
||
--warning: #f59e0b;
|
||
--gradient-brand: linear-gradient(135deg, #4f74ff 0%, #3a57d8 100%);
|
||
|
||
background: var(--bg-page);
|
||
border: 1px solid var(--border);
|
||
border-radius: 22px;
|
||
box-shadow:
|
||
0 32px 80px rgba(0, 0, 0, 0.6),
|
||
0 0 0 1px rgba(79, 116, 255, 0.12);
|
||
width: 100%;
|
||
max-width: 640px;
|
||
max-height: 94vh;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
color: var(--text);
|
||
animation: gsmSlideIn 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
}
|
||
|
||
/* Форма растягивается на оставшуюся высоту модала; body внутри неё скроллится,
|
||
а footer остаётся прижатым к низу — всегда виден. */
|
||
.form {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
@keyframes gsmSlideIn {
|
||
from { opacity: 0; transform: translateY(20px) scale(0.94); }
|
||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 16px 24px;
|
||
flex-shrink: 0;
|
||
background: var(--gradient-brand);
|
||
color: #fff;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.header::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -30px; right: 40px;
|
||
width: 90px; height: 90px;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.10);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.header::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: -40px; left: 80px;
|
||
width: 70px; height: 70px;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.title {
|
||
margin: 0;
|
||
font-size: 20px;
|
||
color: #fff;
|
||
font-weight: 800;
|
||
letter-spacing: -0.3px;
|
||
position: relative;
|
||
z-index: 1;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.closeBtn {
|
||
width: 34px;
|
||
height: 34px;
|
||
background: rgba(255, 255, 255, 0.16);
|
||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||
border-radius: 10px;
|
||
color: #fff;
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
transition: all 150ms ease;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
z-index: 1;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.closeBtn:hover {
|
||
background: rgba(255, 255, 255, 0.28);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.body {
|
||
padding: 16px 24px;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
/* === Иконка === */
|
||
.iconRow {
|
||
display: flex;
|
||
gap: 16px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.iconPreview {
|
||
width: 76px;
|
||
height: 76px;
|
||
flex-shrink: 0;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
background: var(--bg-input);
|
||
border: 1px solid var(--border);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.06);
|
||
}
|
||
|
||
.iconPreview img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.iconPlaceholder {
|
||
font-size: 40px;
|
||
opacity: 0.4;
|
||
}
|
||
|
||
.iconActions {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.iconButtons {
|
||
display: flex;
|
||
gap: 6px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.actionBtn {
|
||
background: var(--bg-input);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
color: var(--text);
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
padding: 7px 12px;
|
||
cursor: pointer;
|
||
transition: all 150ms ease;
|
||
text-align: left;
|
||
font-family: inherit;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.actionBtn:hover {
|
||
background: var(--accent-soft);
|
||
border-color: var(--accent);
|
||
color: var(--accent);
|
||
}
|
||
|
||
.removeBtn {
|
||
background: var(--danger-bg);
|
||
border: 1px solid var(--danger);
|
||
border-radius: 8px;
|
||
color: var(--danger);
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
padding: 6px 10px;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
transition: all 150ms ease;
|
||
line-height: 1;
|
||
}
|
||
|
||
.removeBtn:hover {
|
||
background: var(--danger);
|
||
color: #fff;
|
||
}
|
||
|
||
/* === Поле === */
|
||
.field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
|
||
.fieldLabel {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
font-size: 11px;
|
||
color: var(--text-dim);
|
||
font-weight: 800;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.8px;
|
||
}
|
||
|
||
.required {
|
||
color: var(--danger);
|
||
}
|
||
|
||
.counter {
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
font-weight: 600;
|
||
text-transform: none;
|
||
letter-spacing: 0.2px;
|
||
}
|
||
|
||
.fieldHint {
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
margin-top: 4px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.input, .textarea {
|
||
background: var(--bg-input);
|
||
border: 1.5px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 8px 12px;
|
||
color: var(--text);
|
||
font-size: 14px;
|
||
font-family: inherit;
|
||
outline: none;
|
||
transition: all 150ms ease;
|
||
}
|
||
|
||
.input:focus, .textarea:focus {
|
||
border-color: var(--accent);
|
||
box-shadow: 0 0 0 3px rgba(79, 116, 255, 0.22);
|
||
background: var(--bg-hover);
|
||
}
|
||
|
||
.textarea {
|
||
resize: vertical;
|
||
min-height: 64px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
/* === Жанры === */
|
||
.genreGrid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||
gap: 8px;
|
||
}
|
||
|
||
.genreBtn {
|
||
background: var(--bg-input);
|
||
border: 1px solid var(--border);
|
||
border-radius: 9px;
|
||
color: var(--text);
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
padding: 7px 12px;
|
||
cursor: pointer;
|
||
transition: all 200ms ease;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.genreBtn:hover {
|
||
background: var(--accent-soft);
|
||
border-color: var(--accent);
|
||
color: var(--accent);
|
||
}
|
||
|
||
.genreBtnActive {
|
||
background: var(--gradient-brand);
|
||
border-color: transparent;
|
||
color: #fff;
|
||
box-shadow: 0 6px 16px rgba(51, 87, 255, 0.30);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.genreBtnActive:hover {
|
||
background: var(--gradient-brand);
|
||
color: #fff;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 20px rgba(51, 87, 255, 0.40);
|
||
}
|
||
|
||
/* === Toggle (публикация / мультиплеер) === */
|
||
.togglesRow {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 10px;
|
||
}
|
||
|
||
.toggleRow {
|
||
display: flex;
|
||
gap: 10px;
|
||
padding: 10px 12px;
|
||
background: var(--bg-input);
|
||
border: 1.5px solid var(--border);
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
transition: all 150ms ease;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.toggleRow:hover {
|
||
border-color: var(--accent);
|
||
background: var(--accent-soft);
|
||
}
|
||
|
||
.toggle {
|
||
width: 16px;
|
||
height: 16px;
|
||
margin-top: 2px;
|
||
cursor: pointer;
|
||
accent-color: var(--accent);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.toggleText {
|
||
flex: 1;
|
||
color: var(--text);
|
||
font-size: 12px;
|
||
min-width: 0;
|
||
}
|
||
|
||
/* Заголовок чекбокса — слева, без space-between (в отличие от .fieldLabel
|
||
у инпутов где справа стоит счётчик). */
|
||
.toggleTitle {
|
||
font-size: 11px;
|
||
color: var(--text-dim);
|
||
font-weight: 800;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.8px;
|
||
text-align: left;
|
||
}
|
||
|
||
/* Подсказка под заголовком: иконка слева от текста, в одну строку. */
|
||
.toggleHint {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
font-weight: 500;
|
||
margin-top: 3px;
|
||
}
|
||
|
||
.toggleHint svg {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* === Ошибка === */
|
||
.error {
|
||
color: var(--danger);
|
||
background: var(--danger-bg);
|
||
border: 1px solid rgba(239, 68, 68, 0.4);
|
||
border-radius: 10px;
|
||
padding: 10px 14px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* === Footer === */
|
||
.footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
padding: 14px 24px;
|
||
border-top: 1px solid var(--border-soft);
|
||
background: var(--bg-soft);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.secondaryBtn {
|
||
background: var(--bg-hover);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
color: var(--text);
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
padding: 10px 20px;
|
||
cursor: pointer;
|
||
transition: all 150ms ease;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.secondaryBtn:hover:not(:disabled) {
|
||
background: var(--bg-hover);
|
||
border-color: var(--border-strong);
|
||
}
|
||
|
||
.secondaryBtn:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.primaryBtn {
|
||
background: var(--gradient-brand);
|
||
border: 1px solid transparent;
|
||
border-radius: 10px;
|
||
color: #fff;
|
||
font-size: 14px;
|
||
padding: 10px 24px;
|
||
cursor: pointer;
|
||
font-weight: 800;
|
||
letter-spacing: 0.2px;
|
||
transition: all 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
box-shadow: 0 6px 16px rgba(51, 87, 255, 0.32);
|
||
font-family: inherit;
|
||
}
|
||
|
||
.primaryBtn:hover:not(:disabled) {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 12px 28px rgba(51, 87, 255, 0.45);
|
||
}
|
||
|
||
.primaryBtn:disabled {
|
||
opacity: 0.55;
|
||
cursor: not-allowed;
|
||
box-shadow: 0 4px 12px rgba(51, 87, 255, 0.20);
|
||
}
|