studio/src/editor/ToolboxModal.module.css
min 5e1a0edf9b feat(studio): задача 17 — Toolbox переработан под Roblox Creator Store
Единый Toolbox вместо отдельной кнопки «Модель» в панели «Создать»:
- 4 верхние вкладки как в Roblox: Магазин / Инвентарь / Недавние / Советы.
- Магазин: главный экран с 6 плитками-категориями (3D-объекты / Эффекты /
  2D-картинки / Готовые механики / Плагины / Аудио) + ряд «Популярное» (FREE).
- Клик по категории → детальный список с поиском и подкатегориями; «← Категории».
- 3D-объекты = 700+ моделей; Эффекты = эмиттер/луч/указатель/свет/триггер;
  Готовые механики = 12 китов; 2D/Плагины/Аудио = «Скоро будет».
- Инвентарь = мои воксельные модели; Недавние = модели сообщества; Советы = гайд.
- TopRibbon: кнопка «Модель» → «Toolbox» (открывает магазин); вкладка «Модель»
  переименована в «Редактор моделей» (создание своих воксельных ассетов).
- CSS: topTabs/catGrid/catTile/trendRow/breadcrumb/soon/tips/freeBadge.

Вся прежняя логика моделей (lazy-load, лайки, thumbnails) сохранена внутри
новой структуры. Esc в категории → назад к плиткам.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 01:33:39 +03:00

581 lines
14 KiB
CSS

.overlay {
position: fixed;
inset: 0;
background: rgba(7, 10, 20, 0.78);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
font-family: "Roboto Condensed", system-ui, -apple-system, sans-serif;
animation: tbOverlayFadeIn 0.18s ease;
}
@keyframes tbOverlayFadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal {
background: var(--bg-dark);
border: 1px solid var(--border);
border-radius: 22px;
width: min(1300px, 96vw);
height: min(820px, 92vh);
display: flex;
flex-direction: column;
box-shadow:
0 32px 80px rgba(0, 0, 0, 0.6),
0 0 0 1px rgba(79, 116, 255, 0.12);
overflow: hidden;
animation: tbSlideIn 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes tbSlideIn {
from { opacity: 0; transform: translateY(20px) scale(0.96); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.header {
display: flex;
align-items: center;
gap: 14px;
padding: 22px 26px;
background: linear-gradient(135deg, var(--accent) 0%, var(--accent2) 100%);
color: #fff;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: -30px; right: 60px;
width: 90px; height: 90px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.10);
pointer-events: none;
}
.title {
margin: 0;
color: #fff;
font-size: 20px;
font-weight: 800;
letter-spacing: -0.3px;
position: relative;
z-index: 1;
}
.headerInfo {
flex: 1;
color: rgba(255, 255, 255, 0.78);
font-size: 13px;
font-weight: 600;
position: relative;
z-index: 1;
}
.closeBtn {
background: rgba(255, 255, 255, 0.16);
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 10px;
color: #fff;
width: 36px;
height: 36px;
cursor: pointer;
font-size: 20px;
font-weight: 700;
transition: all 150ms ease;
position: relative;
z-index: 1;
font-family: inherit;
}
.closeBtn:hover {
background: rgba(255, 255, 255, 0.28);
transform: scale(1.05);
color: #fff;
}
/* === Поиск === */
.searchBar {
padding: 12px 18px;
background: var(--bg-darkest);
border-bottom: 1px solid var(--border);
}
.searchInput {
width: 100%;
padding: 11px 16px;
background: var(--bg-mid);
border: 1.5px solid var(--border);
border-radius: 10px;
color: var(--text);
font-size: 14px;
font-weight: 500;
outline: none;
transition: all 150ms ease;
font-family: inherit;
}
.searchInput:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(79, 116, 255, 0.22);
background: var(--bg-light);
}
.searchInput::placeholder {
color: var(--text-dim);
}
/* === Корневые разделы (Стандартные / Мои / Сообщество) === */
.sectionTabs {
display: flex;
gap: 4px;
padding: 10px 18px 0;
background: var(--bg-darkest);
border-bottom: 1px solid var(--border);
}
.sectionTab {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 18px;
background: transparent;
border: none;
border-bottom: 3px solid transparent;
color: var(--text-dim);
font-size: 14px;
font-weight: 800;
cursor: pointer;
transition: all 180ms ease;
font-family: inherit;
letter-spacing: -0.2px;
}
.sectionTab:hover {
color: var(--accent-bright);
background: rgba(79, 116, 255, 0.10);
}
.sectionTabActive {
color: var(--accent-bright);
border-bottom-color: var(--accent-bright);
}
.sectionTabActive:hover {
background: rgba(79, 116, 255, 0.12);
}
/* === Категории === */
.categoryTabs {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 10px 18px;
background: var(--bg-darkest);
border-bottom: 1px solid var(--border);
max-height: 100px;
overflow-y: auto;
}
.categoryTab {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 14px;
background: var(--bg-mid);
border: 1px solid var(--border);
border-radius: 999px;
color: var(--text-dim);
font-size: 12px;
font-weight: 700;
cursor: pointer;
transition: all 200ms ease;
white-space: nowrap;
font-family: inherit;
}
.categoryTab:hover {
color: var(--accent-bright);
border-color: var(--accent);
background: rgba(79, 116, 255, 0.12);
}
.categoryTabActive {
background: linear-gradient(135deg, var(--accent) 0%, var(--accent2) 100%);
border-color: transparent;
color: #fff;
box-shadow: 0 4px 12px rgba(79, 116, 255, 0.36);
}
.categoryTabActive:hover {
background: linear-gradient(135deg, var(--accent) 0%, var(--accent2) 100%);
color: #fff;
transform: translateY(-1px);
}
.categoryCount {
background: rgba(255, 255, 255, 0.20);
padding: 1px 8px;
border-radius: 999px;
font-size: 11px;
font-weight: 800;
}
.categoryTab:not(.categoryTabActive) .categoryCount {
background: rgba(255, 255, 255, 0.08);
color: var(--text-dim);
}
/* === Сетка карточек === */
.grid {
flex: 1;
overflow-y: auto;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
grid-auto-rows: min-content;
gap: 8px;
padding: 14px;
align-content: start;
}
.empty {
grid-column: 1 / -1;
text-align: center;
color: var(--text-dim);
padding: 60px 20px;
font-size: 14px;
}
.card {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
padding: 14px 10px;
background: var(--bg-mid);
border: 1px solid var(--border);
border-radius: 12px;
color: var(--text);
cursor: pointer;
transition: all 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
min-height: 120px;
font-family: inherit;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
.card:hover {
transform: translateY(-3px);
border-color: var(--accent);
box-shadow: 0 12px 24px rgba(79, 116, 255, 0.25);
}
.cardActive {
border-color: var(--accent);
background: rgba(79, 116, 255, 0.18);
box-shadow: 0 0 0 2px var(--accent), 0 8px 20px rgba(79, 116, 255, 0.35);
}
.cardIcon {
font-size: 32px;
line-height: 1;
}
.cardName {
font-size: 11px;
line-height: 1.2;
text-align: center;
color: var(--text);
word-break: break-word;
}
.cardCat {
font-size: 10px;
color: var(--text-dim);
}
.cardActive .cardName {
color: var(--accent-bright);
font-weight: 800;
}
/* === Этап 6.3: социальная строка карточки (лайки + использования) === */
.cardSocial {
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
margin-top: 4px;
}
.cardLikeBtn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 8px;
font-size: 11px;
font-weight: 700;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--bg-light);
color: var(--text-dim);
cursor: pointer;
transition: all 0.12s;
}
.cardLikeBtn:hover {
border-color: #ff5577;
color: #ff5577;
}
.cardLikeActive {
border-color: #ff5577;
background: #ff5577;
color: #fff;
}
.cardLikeActive:hover {
color: #fff;
}
.cardUses {
display: inline-flex;
align-items: center;
gap: 3px;
font-size: 11px;
font-weight: 600;
color: var(--text-dim);
}
.cardBadge {
position: absolute;
top: 6px;
right: 6px;
font-size: 13px;
background: linear-gradient(135deg, var(--accent) 0%, var(--accent2) 100%);
color: #fff;
padding: 3px 8px;
border-radius: 999px;
font-weight: 800;
box-shadow: 0 2px 6px rgba(79, 116, 255, 0.36);
}
/* === Этап 6: hover-overlay для управления своими моделями === */
.userCard {
/* div вместо button — нужно убрать button-defaults */
background: var(--bg-mid);
user-select: none;
}
.userCard:focus {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.cardActions {
position: absolute;
top: 6px;
left: 6px;
display: flex;
gap: 4px;
opacity: 0;
transform: translateY(-4px);
transition: opacity 150ms ease, transform 150ms ease;
pointer-events: none;
}
.userCard:hover .cardActions,
.userCard:focus-within .cardActions {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.cardActionBtn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
background: var(--bg-light);
border: 1px solid var(--border);
border-radius: 7px;
color: var(--text);
cursor: pointer;
transition: all 120ms ease;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
font-family: inherit;
padding: 0;
}
.cardActionBtn:hover {
background: var(--accent);
border-color: var(--accent);
color: #fff;
transform: scale(1.08);
}
.cardActionDanger:hover {
background: #ef4444;
border-color: #ef4444;
color: #fff;
}
.cardThumb {
width: 100%;
height: 80px;
object-fit: contain;
/* Белый фон для превью моделей — модели и текстуры читаются лучше
на светлом, особенно тёмные/чёрные (камни, металл). 2026-05-27. */
background: #fff;
border-radius: 8px;
image-rendering: auto;
}
.cardIconPlaceholder {
width: 100%;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border-radius: 8px;
font-size: 36px;
color: var(--text-dim);
}
/* ====================== Roblox-style Toolbox (задача 17) ====================== */
.topTabs {
display: flex;
gap: 2px;
padding: 0 14px;
border-bottom: 1px solid var(--border, rgba(255,255,255,0.08));
flex: 0 0 auto;
}
.topTab {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
padding: 10px 4px 8px;
background: none;
border: none;
border-bottom: 2px solid transparent;
color: var(--text-dim, #9aa3b2);
font-size: 12px;
cursor: pointer;
transition: color .12s, border-color .12s;
}
.topTab:hover { color: var(--text, #e8ecf2); }
.topTabActive {
color: var(--accent, #4d6bff);
border-bottom-color: var(--accent, #4d6bff);
}
.breadcrumb {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 16px 4px;
flex: 0 0 auto;
}
.backBtn {
display: inline-flex;
align-items: center;
gap: 4px;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.1);
color: var(--text, #e8ecf2);
padding: 5px 10px;
border-radius: 8px;
font-size: 13px;
cursor: pointer;
}
.backBtn:hover { background: rgba(255,255,255,0.12); }
.crumbCurrent { font-weight: 700; color: var(--text, #e8ecf2); }
.storeHome { overflow-y: auto; padding: 12px 16px 18px; flex: 1; }
.sectionLabel {
display: flex; align-items: center; gap: 6px;
font-weight: 700; font-size: 14px; color: var(--text, #e8ecf2);
margin-bottom: 10px;
}
.catGrid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.catTile {
display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
padding: 16px;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.09);
border-radius: 12px;
cursor: pointer;
text-align: left;
transition: transform .1s, background .12s, border-color .12s;
}
.catTile:hover {
background: rgba(77,107,255,0.12);
border-color: var(--accent, #4d6bff);
transform: translateY(-2px);
}
.catTileIcon { color: var(--accent, #4d6bff); margin-bottom: 4px; }
.catTileLabel { font-weight: 700; font-size: 15px; color: var(--text, #e8ecf2); }
.catTileDesc { font-size: 11px; opacity: 0.7; line-height: 1.3; }
.trendRow {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.trendCard {
position: relative;
display: flex; flex-direction: column; align-items: center; gap: 8px;
padding: 14px 8px;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.09);
border-radius: 12px;
cursor: pointer;
transition: transform .1s, border-color .12s;
}
.trendCard:hover { transform: translateY(-2px); border-color: var(--accent, #4d6bff); }
.trendIcon {
width: 100%; height: 70px;
display: flex; align-items: center; justify-content: center;
background: linear-gradient(135deg, rgba(77,107,255,0.22), rgba(54,213,122,0.16));
border-radius: 8px;
color: var(--text, #e8ecf2);
}
.trendName { font-size: 12px; font-weight: 600; text-align: center; color: var(--text, #e8ecf2); }
.freeBadge {
position: absolute; top: 8px; right: 8px;
font-size: 9px; font-weight: 800; letter-spacing: 0.5px;
color: #36d57a;
background: rgba(54,213,122,0.14);
padding: 2px 6px; border-radius: 6px;
}
.soon {
flex: 1;
display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: 10px; padding: 40px;
color: var(--text-dim, #9aa3b2); text-align: center;
}
.soonTitle { font-size: 18px; font-weight: 700; color: var(--text, #e8ecf2); }
.soonText { font-size: 13px; max-width: 360px; opacity: 0.75; }
.tips {
overflow-y: auto; padding: 16px 22px; flex: 1;
color: var(--text, #e8ecf2); line-height: 1.55;
}
.tips h3 { margin: 4px 0 12px; font-size: 17px; }
.tips ul { margin: 0 0 14px; padding-left: 18px; }
.tips li { margin-bottom: 9px; font-size: 13px; }
.tips b { color: var(--accent, #6f8bff); }