studio/src/editor/KubikonEditor.module.css
min a9a9668071
All checks were successful
CI / Lint (pull_request) Successful in 1m10s
CI / Build (pull_request) Successful in 2m7s
CI / Secret scan (pull_request) Successful in 24s
CI / PR size check (pull_request) Successful in 7s
CI / Deploy to S1 + S2 (pull_request) Has been skipped
feat(studio): компактный UI + fullscreen + desktop-guard
UI (ближе к Roblox Studio):
- интерфейс уменьшен ~10% (шрифты/паддинги/topbar 56→50);
- правая панель шире (280→320), компактные строки в «Объектах сцены»;
- перетаскиваемая граница между списком объектов и свойствами (доля в
  localStorage).

Fullscreen редактора:
- кнопка «На весь экран» в шапке + автовход при первом клике (браузер
  требует user gesture);
- иконки fullscreen/fullscreen-exit.

Desktop-guard (window.__RUBLOX_DESKTOP__):
- в Electron-приложении авто-fullscreen отключён (окно и так на весь
  экран): не дёргаем FS при автовходе, при «Запустить», кнопка FS скрыта.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 20:17:58 +03:00

880 lines
20 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.

/* ========== KUBIKON EDITOR — тёмный стиль (как Roblox Studio) ==========
Layout: верхняя панель + workspace (slim-left + viewport + slim-right) + status bar.
Тёмная палитра в стиле Roblox Studio: почти-чёрные панели, светлый
текст, синий акцент Рублокса.
*/
.editor {
/* Тёмная основа (как панели Roblox Studio) */
--bg-darkest: #1b1b1b; /* самый тёмный фон рабочей зоны */
--bg-dark: #252525; /* панели */
--bg-mid: #2e2e2e; /* приподнятые блоки */
--bg-light: #383838; /* hover / активные */
/* Акценты Рублокса */
--accent: #4f74ff;
--accent-bright: #6d8aff;
--accent2: #4f74ff;
--wood: #4a4a4a;
--stone: #5a5a5a;
/* Текст */
--text: #e8e8ea;
--text-dim: #9a9a9e;
/* Границы */
--border: #3a3a3a;
/* Семантика */
--danger: #ef4444;
font-family: "Roboto Condensed", system-ui, -apple-system, sans-serif;
display: grid;
/* UI уменьшен на ~10% (ближе к Roblox Studio): topbar 56→50, status 28→26. */
grid-template-rows: 50px auto 1fr 26px;
height: 100vh;
width: 100%;
max-width: 100%;
background: var(--bg-darkest);
color: var(--text);
overflow: hidden;
box-sizing: border-box;
font-size: 13px;
}
.editor *,
.editor *::before,
.editor *::after {
box-sizing: border-box;
}
/* === ВЕРХНЯЯ ПАНЕЛЬ === */
.topBar {
display: flex;
align-items: center;
gap: 12px;
padding: 0 14px;
background: linear-gradient(180deg, var(--bg-dark) 0%, var(--bg-darkest) 100%);
border-bottom: 2px solid var(--border);
z-index: 10;
}
.backBtn {
padding: 8px 14px;
background: var(--bg-mid);
border: 2px solid var(--border);
border-radius: 6px;
color: var(--text);
font-size: 13px;
cursor: pointer;
transition: all 0.15s;
}
.backBtn:hover {
background: var(--bg-light);
border-color: var(--wood);
}
.projectName {
flex: 1;
display: flex;
justify-content: center;
}
.nameInput {
background: transparent;
border: 1px solid transparent;
border-radius: 4px;
padding: 6px 12px;
color: var(--text);
font-size: 16px;
text-align: center;
width: 280px;
transition: all 0.15s;
}
.nameInput:hover {
background: var(--bg-mid);
border-color: var(--border);
}
.nameInput:focus {
background: var(--bg-mid);
border-color: var(--accent2);
outline: none;
}
.topActions {
display: flex;
gap: 10px;
align-items: center;
}
.saveStatus {
font-size: 12px;
color: var(--text-dim);
padding: 6px 12px;
border-radius: 4px;
background: var(--bg-mid);
border: 1px solid var(--border);
user-select: none;
transition: all 0.15s;
min-width: 110px;
text-align: center;
}
.saveStatus_saved {
color: var(--accent-bright);
border-color: var(--accent);
}
.saveStatus_saving {
color: var(--accent2);
border-color: var(--wood);
}
.saveStatus_dirty {
color: var(--accent2);
border-color: var(--wood);
}
.saveStatus_error {
color: #f08080;
border-color: var(--danger);
background: rgba(168, 50, 50, 0.15);
}
.toolbarBtn {
padding: 7px 13px;
background: var(--bg-mid);
border: 2px solid var(--border);
border-radius: 6px;
color: var(--text);
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
font-weight: 600;
}
.toolbarBtn:hover {
background: var(--bg-light);
border-color: var(--accent2);
}
.playBtn {
background: linear-gradient(135deg, #22d97a 0%, #0f9d56 100%);
border-color: transparent;
color: #fff;
box-shadow: 0 6px 16px rgba(34, 217, 122, 0.40);
}
.playBtn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 24px rgba(34, 217, 122, 0.50);
}
.stopBtn {
background: linear-gradient(135deg, #ff6f7a 0%, #c0303f 100%);
border-color: transparent;
color: #fff;
box-shadow: 0 6px 16px rgba(239, 68, 68, 0.40);
}
.stopBtn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 24px rgba(239, 68, 68, 0.50);
}
.playBadge {
position: absolute;
top: 12px;
right: 12px;
background: linear-gradient(135deg, #ec4899 0%, #ef4444 100%);
color: #fff;
padding: 8px 16px;
border-radius: 999px;
font-size: 12px;
font-weight: 800;
letter-spacing: 1.5px;
text-transform: uppercase;
box-shadow: 0 8px 20px rgba(239, 68, 68, 0.45);
animation: playBadgePulse 2s ease-in-out infinite;
pointer-events: none;
}
@keyframes playBadgePulse {
0%, 100% { opacity: 0.95; transform: scale(1); }
50% { opacity: 1; transform: scale(1.04); }
}
/* === WORKSPACE === */
.workspace {
display: grid;
/* Правая панель шире (280→320) — длинные имена объектов не обрезаются,
больше места под список и свойства. Левая чуть уже (240→224). */
grid-template-columns: 224px minmax(0, 1fr) 320px;
overflow: hidden;
min-height: 0;
}
/* === ПАНЕЛИ === */
.leftPanel, .rightPanel {
background: var(--bg-dark);
overflow-y: auto;
display: flex;
flex-direction: column;
}
.leftPanel {
border-right: 2px solid var(--border);
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.rightPanel {
border-left: 2px solid var(--border);
overflow: hidden; /* секции скроллятся внутри себя, панель — нет */
}
/* Секция правой панели (список объектов / свойства) — занимает свою долю
высоты, скроллится независимо. Доля регулируется сплиттером. */
.rightSection {
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
/* Перетаскиваемая граница между списком объектов и свойствами. */
.rightSplitter {
height: 6px;
flex-shrink: 0;
cursor: row-resize;
background: var(--bg-mid);
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
position: relative;
transition: background 0.12s;
}
.rightSplitter::before {
/* визуальная «ручка» — три точки по центру */
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 28px;
height: 2px;
border-radius: 2px;
background: var(--text-dim);
opacity: 0.5;
}
.rightSplitter:hover {
background: var(--accent);
}
.rightSplitter:hover::before {
background: #fff;
opacity: 0.9;
}
.panelTitle {
padding: 7px 14px;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1.3px;
color: var(--text-dim);
background: var(--bg-mid);
border-bottom: 1px solid var(--border);
border-top: 1px solid var(--border);
font-weight: 800;
flex-shrink: 0;
}
.panelTitle:first-child {
border-top: none;
}
/* === ИНСТРУМЕНТЫ === */
.toolList {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
padding: 12px;
}
.toolBtn {
aspect-ratio: 1;
background: var(--bg-mid);
border: 2px solid var(--border);
border-radius: 6px;
color: var(--text);
font-size: 22px;
cursor: pointer;
transition: all 0.12s;
display: flex;
align-items: center;
justify-content: center;
}
.toolBtn:hover {
background: var(--bg-light);
border-color: var(--accent2);
transform: translateY(-1px);
}
.toolActive {
background: linear-gradient(135deg, #3357ff 0%, #1e2da5 100%);
border-color: transparent;
box-shadow: 0 6px 16px rgba(51, 87, 255, 0.40);
color: #fff;
transform: translateY(-1px);
}
.toolActive:hover {
background: var(--accent-bright);
border-color: var(--accent-bright);
}
/* === ПАЛИТРА БЛОКОВ === */
.blocksGrid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: min-content;
align-content: start;
gap: 6px;
padding: 12px;
overflow-y: auto;
flex: 1 1 auto;
min-height: 200px;
}
.blockSwatch {
aspect-ratio: 1;
border: 1px solid var(--border);
border-radius: 8px;
cursor: pointer;
transition: all 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
overflow: hidden;
padding: 0;
position: relative;
background: var(--bg-mid);
}
.blockSwatch:hover {
transform: scale(1.08) translateY(-2px);
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent), 0 8px 16px rgba(51, 87, 255, 0.20);
z-index: 1;
}
.blockSwatchActive {
border-color: var(--accent);
box-shadow: 0 0 0 2px var(--accent), 0 0 16px rgba(51, 87, 255, 0.45);
transform: scale(1.04);
}
.blockSwatchImg {
width: 100%;
height: 100%;
display: block;
image-rendering: pixelated;
image-rendering: crisp-edges;
object-fit: cover;
}
.activeBlockInfo {
padding: 10px 16px;
font-size: 12px;
color: var(--text-dim);
background: var(--bg-darkest);
border-top: 1px solid var(--border);
text-align: center;
}
.activeBlockInfo b {
color: var(--accent-bright);
}
.searchBox {
padding: 8px 12px;
background: var(--bg-darkest);
border-bottom: 1px solid var(--border);
}
.searchInput {
width: 100%;
padding: 6px 10px;
background: var(--bg-mid);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text);
font-size: 12px;
outline: none;
transition: border-color 0.15s;
}
.searchInput:focus {
border-color: var(--accent2);
}
.searchInput::placeholder {
color: var(--text-dim);
}
.categoryTabs {
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 8px;
background: var(--bg-darkest);
border-bottom: 1px solid var(--border);
max-height: 110px;
overflow-y: auto;
}
.categoryTab {
padding: 4px 10px;
background: var(--bg-mid);
border: 1px solid var(--border);
border-radius: 12px;
color: var(--text-dim);
font-size: 11px;
cursor: pointer;
transition: all 0.12s;
white-space: nowrap;
}
.categoryTab:hover {
color: var(--text);
border-color: var(--wood);
}
.categoryTabActive {
background: linear-gradient(135deg, #3357ff 0%, #1e2da5 100%);
border-color: transparent;
color: #fff;
box-shadow: 0 4px 10px rgba(51, 87, 255, 0.32);
}
.categoryTabActive:hover {
color: #fff;
border-color: transparent;
transform: translateY(-1px);
}
.blocksEmpty {
grid-column: 1 / -1;
text-align: center;
padding: 20px;
color: var(--text-dim);
font-size: 12px;
}
/* === Палитра-табы (Блоки / Модели) === */
.paletteTabs {
display: flex;
gap: 0;
padding: 0;
background: var(--bg-darkest);
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
}
.paletteTab {
flex: 1;
padding: 10px 8px;
background: var(--bg-dark);
border: none;
border-right: 1px solid var(--border);
color: var(--text-dim);
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
font-weight: 600;
}
.paletteTab:last-child {
border-right: none;
}
.paletteTab:hover {
background: var(--bg-mid);
color: var(--text);
}
.paletteTabActive {
background: var(--bg-mid);
color: var(--accent);
box-shadow: inset 0 -3px 0 var(--accent);
font-weight: 800;
}
.paletteTabActive:hover {
background: var(--bg-mid);
color: var(--accent);
}
/* === Заголовок левой панели ===
* Заменяет старые табы Блоки/Примитивы/Модели — теперь выбор источника
* делается через TopRibbon, а в этой панели мы только показываем
* название активного раздела + иконку. */
.paletteHeader {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
background: linear-gradient(180deg, var(--bg-dark) 0%, var(--bg-darkest) 100%);
border-bottom: 1px solid var(--border);
user-select: none;
-webkit-user-select: none;
}
.paletteHeaderIcon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 6px;
background: rgba(51, 87, 255, 0.10);
color: var(--accent);
flex-shrink: 0;
}
.paletteHeaderTitle {
font-size: 13px;
font-weight: 700;
color: var(--text);
letter-spacing: 0.1px;
}
/* === Сетка моделей === */
.modelsGrid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-auto-rows: min-content;
align-content: start;
gap: 6px;
padding: 12px;
overflow-y: auto;
flex: 1 1 auto;
min-height: 200px;
}
.modelCard {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 10px 6px;
background: var(--bg-mid);
border: 2px solid var(--border);
border-radius: 6px;
color: var(--text);
cursor: pointer;
transition: transform 0.12s, border-color 0.12s, box-shadow 0.12s;
}
.modelCard:hover {
transform: translateY(-2px);
border-color: var(--accent);
box-shadow: 0 8px 16px rgba(15, 23, 42, 0.10);
}
.modelCardActive {
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent), 0 6px 16px rgba(51, 87, 255, 0.25);
background: rgba(79,116,255,0.16);
}
.modelIcon {
font-size: 26px;
line-height: 1;
}
.modelName {
font-size: 10px;
text-align: center;
line-height: 1.15;
color: var(--text-dim);
}
.modelCardActive .modelName {
color: var(--text);
}
/* === VIEWPORT === */
.viewport {
position: relative;
background: #0a0e1a;
overflow: hidden;
min-width: 0;
min-height: 0;
height: 100%;
}
.canvas {
width: 100%;
height: 100%;
display: block;
outline: none;
touch-action: none;
}
.viewportHint {
position: absolute;
bottom: 14px;
left: 50%;
transform: translateX(-50%);
background: rgba(20, 24, 45, 0.78);
border: 1px solid rgba(255, 255, 255, 0.10);
border-radius: 999px;
padding: 6px 14px;
font-size: 11px;
color: rgba(241, 245, 251, 0.82);
font-weight: 600;
letter-spacing: 0.2px;
pointer-events: none;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.30);
}
/* === HIERARCHY === */
.hierarchy {
flex: 1;
overflow-y: auto;
padding: 8px 0;
}
.hierarchyItem {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 16px;
cursor: pointer;
color: var(--text);
font-size: 13px;
transition: background 0.12s;
}
.hierarchyItem:hover {
background: var(--bg-mid);
}
/* === INSPECTOR === */
.inspector {
flex: 1;
padding: 12px 16px;
overflow-y: auto;
}
.inspectorEmpty {
color: var(--text-dim);
font-size: 13px;
text-align: center;
padding: 24px 8px;
line-height: 1.5;
border: 1px dashed var(--border);
border-radius: 6px;
}
/* === STATUS BAR === */
.statusBar {
display: flex;
align-items: center;
gap: 24px;
padding: 0 16px;
background: var(--bg-darkest);
border-top: 1px solid var(--border);
color: var(--text-dim);
font-size: 11px;
letter-spacing: 0.5px;
}
.statusItem {
display: flex;
align-items: center;
gap: 6px;
}
/* === АДАПТИВ === */
@media (max-width: 1100px) {
.workspace {
grid-template-columns: 200px minmax(0, 1fr) 240px;
}
}
@media (max-width: 800px) {
.workspace {
grid-template-columns: 60px minmax(0, 1fr);
}
.rightPanel {
display: none;
}
.blocksGrid {
grid-template-columns: repeat(2, 1fr);
}
}
/* === Тулбокс-вкладка моделей === */
.modelsTabContent {
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px;
flex: 1 1 auto;
overflow-y: auto;
}
.toolboxButton {
display: flex;
align-items: center;
gap: 12px;
padding: 14px;
background: linear-gradient(135deg, #3357ff 0%, #1e2da5 100%);
border: 1px solid transparent;
border-radius: 12px;
color: #fff;
cursor: pointer;
transition: all 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
width: 100%;
text-align: left;
box-shadow: 0 8px 20px rgba(51, 87, 255, 0.32);
font-family: inherit;
}
.toolboxButton:hover {
transform: translateY(-3px);
box-shadow: 0 14px 28px rgba(51, 87, 255, 0.45);
}
.toolboxButtonIcon {
font-size: 36px;
line-height: 1;
}
.toolboxButtonText {
flex: 1;
}
.toolboxButtonTitle {
font-size: 14px;
font-weight: 700;
line-height: 1.2;
}
.toolboxButtonSub {
font-size: 11px;
opacity: 0.85;
margin-top: 2px;
}
.activeModelCard {
background: rgba(79,116,255,0.16);
border: 1px solid var(--accent);
border-radius: 12px;
padding: 12px 14px;
box-shadow: 0 4px 12px rgba(51, 87, 255, 0.10);
}
.activeModelLabel {
font-size: 10px;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.6px;
margin-bottom: 4px;
}
.activeModelName {
font-size: 13px;
color: var(--text);
font-weight: 600;
}
.activeModelCat {
font-size: 11px;
color: var(--text-dim);
margin-top: 2px;
}
.activeModelChangeBtn {
margin-top: 10px;
width: 100%;
padding: 10px;
background: linear-gradient(135deg, #3357ff 0%, #1e2da5 100%);
color: #fff;
border: 1px solid transparent;
border-radius: 10px;
font-size: 12px;
font-weight: 800;
cursor: pointer;
transition: all 200ms ease;
box-shadow: 0 4px 12px rgba(51, 87, 255, 0.30);
font-family: inherit;
}
.activeModelChangeBtn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 24px rgba(51, 87, 255, 0.45);
}
/* === История недавно использованных моделей === */
.recentModelsBlock {
background: rgba(79,116,255,0.16);
border: 1px solid var(--accent);
border-radius: 14px;
padding: 14px 12px 16px;
margin-bottom: 10px;
box-shadow: 0 2px 8px rgba(51, 87, 255, 0.08);
}
.recentModelsGrid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin-top: 10px;
}
.recentThumbCard {
aspect-ratio: 1 / 1;
background: var(--bg-mid);
border: 1px solid var(--border);
border-radius: 10px;
padding: 6px;
cursor: pointer;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
transition: all 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
min-height: 64px;
}
.recentThumbCard:hover {
border-color: var(--accent);
transform: translateY(-2px) scale(1.05);
box-shadow: 0 8px 16px rgba(51, 87, 255, 0.22);
}
.recentThumbCardActive {
border-color: var(--accent);
box-shadow: 0 0 0 2px var(--accent), 0 6px 16px rgba(51, 87, 255, 0.35);
transform: scale(1.04);
}
.recentThumbImg {
width: 100%;
height: 100%;
object-fit: contain;
pointer-events: none;
}
.recentThumbIcon {
font-size: 24px;
line-height: 1;
}