studio/src/editor/GameSettingsModal.module.css
МИН 31adbf151b Initial public release: Студия Рублокса v1.0
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)
2026-05-27 23:41:10 +03:00

482 lines
10 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ========== 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);
}