studio/src/editor/ModelEditorScreen.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

750 lines
15 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.

/* ========================================================================
ModelEditorScreen — тёмная тема (под весь UI редактора Рублокса).
Палитра:
#1b1b1b / #252525 / #2e2e2e — фоны панелей
#e8e8ea — основной текст
#9a9a9e — приглушённый текст
#4f74ff / #3a57d8 — синий брэнд
======================================================================== */
.screen {
position: fixed;
inset: 0;
z-index: 5000;
background: #1b1b1b;
display: flex;
flex-direction: column;
font-family: "Roboto Condensed", system-ui, -apple-system, sans-serif;
color: #e8e8ea;
animation: meSlideIn 0.22s cubic-bezier(0.34, 1.4, 0.64, 1);
}
@keyframes meSlideIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* === Шапка — синий gradient (как у Game Settings и Toolbox) === */
.header {
display: flex;
align-items: center;
gap: 16px;
padding: 12px 22px;
background: linear-gradient(135deg, #4f74ff 0%, #3a57d8 100%);
color: #fff;
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
flex-shrink: 0;
}
.backBtn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
background: rgba(255, 255, 255, 0.18);
border: 1px solid rgba(255, 255, 255, 0.28);
border-radius: 10px;
color: #fff;
font-size: 13px;
font-weight: 700;
cursor: pointer;
transition: all 150ms ease;
font-family: inherit;
flex-shrink: 0;
}
.backBtn:hover {
background: rgba(255, 255, 255, 0.30);
transform: translateX(-2px);
}
.titleBlock {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.titleInput {
background: transparent;
border: none;
border-bottom: 1px dashed rgba(255, 255, 255, 0.28);
color: #fff;
font-size: 17px;
font-weight: 800;
padding: 2px 0;
outline: none;
font-family: inherit;
width: 100%;
max-width: 380px;
letter-spacing: -0.2px;
}
.titleInput:focus {
border-bottom-color: rgba(255, 255, 255, 0.85);
}
.titleInput::placeholder {
color: rgba(255, 255, 255, 0.55);
}
.title {
font-size: 17px;
font-weight: 800;
letter-spacing: -0.2px;
display: flex;
align-items: center;
gap: 8px;
}
.subtitle {
font-size: 12px;
color: rgba(255, 255, 255, 0.82);
display: flex;
align-items: center;
gap: 8px;
}
.dirtyDot {
color: #ffd166;
font-size: 20px;
line-height: 0;
}
.actions {
display: flex;
gap: 10px;
align-items: center;
flex-shrink: 0;
}
.savedNote {
font-size: 12px;
font-weight: 700;
color: #d2f5d8;
}
.errorNote {
font-size: 12px;
font-weight: 700;
color: #ffd0d0;
max-width: 260px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Кнопки Undo/Redo/Reference */
.iconBtn {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 7px 12px;
background: rgba(255, 255, 255, 0.16);
border: 1px solid rgba(255, 255, 255, 0.24);
border-radius: 9px;
color: #fff;
font-size: 12px;
font-weight: 700;
cursor: pointer;
transition: all 150ms ease;
font-family: inherit;
}
.iconBtn:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.28);
transform: translateY(-1px);
}
.iconBtn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.iconBtnActive {
background: rgba(255, 209, 102, 0.30);
border-color: rgba(255, 209, 102, 0.75);
color: #fff7d0;
}
.iconBtnActive:hover {
background: rgba(255, 209, 102, 0.40);
}
.saveBtn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 18px;
background: #fff;
border: none;
border-radius: 10px;
color: #3a57d8;
font-size: 13px;
font-weight: 800;
cursor: pointer;
transition: all 150ms ease;
font-family: inherit;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
}
.saveBtn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.45);
}
.saveBtn:disabled {
opacity: 0.5;
cursor: not-allowed;
box-shadow: none;
}
/* === Workspace: sidebar + canvas === */
.workspace {
flex: 1;
display: flex;
overflow: hidden;
}
/* === Sidebar — тёмная === */
.sidebar {
width: 280px;
flex-shrink: 0;
background: #252525;
border-right: 1px solid #3a3a3a;
overflow-y: auto;
padding: 14px;
display: flex;
flex-direction: column;
gap: 12px;
box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.3);
}
.section {
background: #1b1b1b;
border: 1px solid #3a3a3a;
border-radius: 10px;
padding: 10px 12px;
}
.sectionTitle {
font-size: 10px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.8px;
color: #9a9a9e;
margin-bottom: 8px;
}
/* Tools grid */
.toolsGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
.toolBtn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
padding: 10px 6px;
background: #2e2e2e;
border: 1px solid #3a3a3a;
border-radius: 8px;
color: #e8e8ea;
font-size: 11px;
font-weight: 700;
cursor: pointer;
transition: all 150ms ease;
font-family: inherit;
}
.toolBtn:hover {
background: rgba(79, 116, 255, 0.18);
border-color: #4f74ff;
color: #6d8aff;
}
.toolBtnActive {
background: linear-gradient(135deg, #4f74ff 0%, #3a57d8 100%);
border-color: transparent;
color: #fff;
box-shadow: 0 4px 12px rgba(79, 116, 255, 0.36);
}
.toolBtnActive:hover {
background: linear-gradient(135deg, #4f74ff 0%, #3a57d8 100%);
color: #fff;
}
/* === Material mode tabs (Цвет / Текстура) === */
.matModeTabs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
margin-bottom: 10px;
padding: 3px;
background: #1b1b1b;
border-radius: 8px;
}
.matModeTab {
padding: 7px 0;
background: transparent;
border: none;
border-radius: 6px;
color: #9a9a9e;
font-size: 12px;
font-weight: 700;
cursor: pointer;
transition: all 150ms ease;
font-family: inherit;
}
.matModeTab:hover {
color: #e8e8ea;
background: rgba(255, 255, 255, 0.06);
}
.matModeTabActive {
background: linear-gradient(135deg, #4f74ff 0%, #3a57d8 100%);
color: #fff;
box-shadow: 0 2px 6px rgba(79, 116, 255, 0.32);
}
.matModeTabActive:hover {
background: linear-gradient(135deg, #4f74ff 0%, #3a57d8 100%);
color: #fff;
}
/* === Texture grid === */
.textureGrid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 4px;
}
.textureSwatch {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
padding: 4px;
background: #2e2e2e;
border: 2px solid #3a3a3a;
border-radius: 6px;
color: #e8e8ea;
font-size: 10px;
font-weight: 700;
cursor: pointer;
transition: all 150ms ease;
font-family: inherit;
}
.textureSwatch img {
width: 100%;
height: 36px;
object-fit: cover;
image-rendering: pixelated;
image-rendering: crisp-edges;
border-radius: 3px;
}
.textureSwatch span {
line-height: 1.1;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.textureSwatch:hover {
border-color: #4f74ff;
color: #6d8aff;
}
.textureSwatchActive {
border-color: #f59e0b;
box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.35);
}
.textureSwatchActive:hover {
border-color: #f59e0b;
}
/* === Palette === */
.paletteGrid {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 4px;
margin-bottom: 8px;
}
.colorSwatch {
aspect-ratio: 1;
border: 2px solid #4a4a4a;
border-radius: 5px;
cursor: pointer;
padding: 0;
transition: transform 100ms ease, border-color 100ms ease;
}
.colorSwatch:hover {
transform: scale(1.12);
border-color: #4f74ff;
}
.colorSwatchActive {
border-color: #f59e0b;
box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.4);
transform: scale(1.06);
}
.colorPickerRow {
display: flex;
align-items: center;
gap: 8px;
margin-top: 4px;
}
.colorPicker {
width: 36px;
height: 30px;
padding: 0;
border: 1px solid #4a4a4a;
border-radius: 6px;
background: #2e2e2e;
cursor: pointer;
}
.colorHex {
font-family: ui-monospace, monospace;
font-size: 12px;
color: #9a9a9e;
letter-spacing: 0.5px;
}
/* === Подсказки внутри секций === */
.hintText {
font-size: 11px;
color: #9a9a9e;
line-height: 1.5;
margin-top: 8px;
}
.muted {
color: #9a9a9e;
font-weight: 600;
font-size: 11px;
letter-spacing: 0;
text-transform: none;
}
/* === Слайдер для Y-уровня === */
.slider {
width: 100%;
margin: 6px 0 0;
accent-color: #4f74ff;
cursor: pointer;
}
/* === Кнопки выбора оси X/Y/Z === */
.axisRow {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 4px;
margin-bottom: 4px;
}
.axisBtn {
padding: 8px 0;
background: #2e2e2e;
border: 1px solid #3a3a3a;
border-radius: 6px;
color: #e8e8ea;
font-size: 13px;
font-weight: 800;
cursor: pointer;
transition: all 150ms ease;
font-family: inherit;
letter-spacing: 0.5px;
}
.axisBtn:hover {
background: rgba(79, 116, 255, 0.18);
border-color: #4f74ff;
color: #6d8aff;
}
.axisBtnActive {
background: linear-gradient(135deg, #4f74ff 0%, #3a57d8 100%);
border-color: transparent;
color: #fff;
}
/* === Size buttons === */
.sizeRow {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 4px;
}
.sizeBtn {
padding: 8px 0;
background: #2e2e2e;
border: 1px solid #3a3a3a;
border-radius: 6px;
color: #e8e8ea;
font-size: 12px;
font-weight: 700;
cursor: pointer;
transition: all 150ms ease;
font-family: inherit;
}
.sizeBtn:hover {
background: rgba(79, 116, 255, 0.18);
border-color: #4f74ff;
color: #6d8aff;
}
.sizeBtnActive {
background: linear-gradient(135deg, #4f74ff 0%, #3a57d8 100%);
border-color: transparent;
color: #fff;
}
/* === Danger button === */
.dangerBtn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
width: 100%;
padding: 9px;
background: rgba(239, 68, 68, 0.16);
border: 1px solid rgba(239, 68, 68, 0.4);
border-radius: 8px;
color: #ff6b6b;
font-size: 12px;
font-weight: 700;
cursor: pointer;
transition: all 150ms ease;
font-family: inherit;
}
.dangerBtn:hover:not(:disabled) {
background: #ef4444;
color: #fff;
border-color: #ef4444;
}
.dangerBtn:disabled {
opacity: 0.35;
cursor: not-allowed;
}
/* === Hint block === */
.hintBlock {
margin-top: auto;
padding: 10px 12px;
background: #1b1b1b;
border: 1px dashed #4a4a4a;
border-radius: 8px;
color: #9a9a9e;
font-size: 11px;
line-height: 1.6;
}
.hintBlock b {
color: #e8e8ea;
}
/* === Canvas wrap — тёмный фон 3D-вьюпорта === */
.canvasWrap {
flex: 1;
position: relative;
overflow: hidden;
background: #141414;
}
.canvas {
width: 100%;
height: 100%;
display: block;
outline: none;
cursor: crosshair;
}
/* === Оверлей загрузки существующей модели === */
.loadingOverlay {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
background: rgba(20, 20, 20, 0.82);
backdrop-filter: blur(2px);
z-index: 20;
}
.loadingSpinner {
width: 42px;
height: 42px;
border: 4px solid #3a3a3a;
border-top-color: #4f74ff;
border-radius: 50%;
animation: modelEditorSpin 0.8s linear infinite;
}
@keyframes modelEditorSpin {
to { transform: rotate(360deg); }
}
.loadingText {
font-size: 14px;
font-weight: 600;
color: #9a9a9e;
}
/* === Режим интерактивного выделения превью === */
.previewOverlay {
position: absolute;
inset: 0;
z-index: 25;
cursor: crosshair;
user-select: none;
}
/* Затемняющий слой — виден пока область не выделена. */
.previewDim {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.55);
pointer-events: none;
}
/* Рамка выделения: внутри — чисто (модель видна), снаружи — затемнено
через огромную box-shadow ("вырез окошка"). */
.previewSelBox {
position: absolute;
border: 2px solid #4f74ff;
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
pointer-events: none;
border-radius: 2px;
}
.previewHint {
position: absolute;
top: 22px;
left: 50%;
transform: translateX(-50%);
padding: 8px 18px;
background: rgba(10, 10, 10, 0.85);
color: #fff;
font-size: 14px;
font-weight: 600;
border-radius: 8px;
pointer-events: none;
white-space: nowrap;
}
.previewActions {
position: absolute;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
}
.previewCancelBtn,
.previewCreateBtn {
padding: 9px 22px;
font-size: 14px;
font-weight: 600;
border-radius: 8px;
border: none;
cursor: pointer;
}
.previewCancelBtn {
background: #2e2e2e;
color: #e8e8ea;
border: 1px solid #4a4a4a;
}
.previewCancelBtn:hover {
background: #383838;
}
.previewCreateBtn {
background: #4f74ff;
color: #fff;
}
.previewCreateBtn:hover {
background: #6d8aff;
}
.previewCreateBtn:disabled {
background: #4a4a4a;
cursor: not-allowed;
}
/* === Placeholder для smooth-режима (этап 7) === */
.body {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background: #1b1b1b;
}
.placeholder {
max-width: 540px;
padding: 40px 32px;
text-align: center;
background: #252525;
border: 1px solid #3a3a3a;
border-radius: 18px;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
}
.placeholderIcon {
font-size: 80px;
margin-bottom: 16px;
opacity: 0.7;
}
.placeholderTitle {
font-size: 22px;
font-weight: 800;
color: #e8e8ea;
margin-bottom: 14px;
letter-spacing: -0.3px;
}
.placeholderText {
font-size: 14px;
color: #9a9a9e;
line-height: 1.55;
margin-bottom: 12px;
}
.placeholderText kbd {
display: inline-block;
padding: 1px 7px;
background: #1b1b1b;
border: 1px solid #4a4a4a;
border-radius: 5px;
font-family: ui-monospace, monospace;
font-size: 12px;
color: #e8e8ea;
}