1610 lines
40 KiB
CSS
1610 lines
40 KiB
CSS
/* ==========================================================================
|
||
01. Theme Tokens
|
||
--------------------------------------------------------------------------
|
||
Все ключевые цвета и размеры вынесены в CSS-переменные. JavaScript меняет
|
||
data-theme/data-accent и --sidebar-width, а остальная верстка реагирует
|
||
без прямого изменения inline-стилей у отдельных элементов.
|
||
========================================================================== */
|
||
|
||
:root {
|
||
color-scheme: dark;
|
||
--bg: #080a12;
|
||
--bg-soft: #111827;
|
||
--panel: rgba(15, 23, 42, 0.74);
|
||
--panel-strong: rgba(15, 23, 42, 0.92);
|
||
--surface: rgba(255, 255, 255, 0.055);
|
||
--text: #f8fafc;
|
||
--muted: #aeb8c8;
|
||
--border: rgba(255, 255, 255, 0.12);
|
||
--shadow: 0 24px 80px rgba(0, 0, 0, 0.34);
|
||
--accent: #8b5cf6;
|
||
--accent-2: #22d3ee;
|
||
--accent-rgb: 139, 92, 246;
|
||
--accent-angle: 135deg;
|
||
--card-x: 50%;
|
||
--card-y: 50%;
|
||
--sidebar-width: 390px;
|
||
--shell-max-width: 1560px;
|
||
--sidebar-max-width: 560px;
|
||
}
|
||
|
||
/* Светлая тема переопределяет только токены, структура стилей остается общей. */
|
||
html[data-theme="light"] {
|
||
color-scheme: light;
|
||
--bg: #f5f7fb;
|
||
--bg-soft: #ffffff;
|
||
--panel: rgba(255, 255, 255, 0.78);
|
||
--panel-strong: rgba(255, 255, 255, 0.96);
|
||
--surface: rgba(15, 23, 42, 0.045);
|
||
--text: #101827;
|
||
--muted: #5f6b7a;
|
||
--border: rgba(15, 23, 42, 0.12);
|
||
--shadow: 0 24px 70px rgba(56, 65, 91, 0.18);
|
||
}
|
||
|
||
/* Наборы акцентов. --accent-rgb нужен для прозрачных градиентов через rgba(). */
|
||
html[data-accent="cyan"] {
|
||
--accent: #06b6d4;
|
||
--accent-2: #3b82f6;
|
||
--accent-rgb: 6, 182, 212;
|
||
}
|
||
|
||
html[data-accent="lime"] {
|
||
--accent: #84cc16;
|
||
--accent-2: #14b8a6;
|
||
--accent-rgb: 132, 204, 22;
|
||
}
|
||
|
||
html[data-accent="amber"] {
|
||
--accent: #f59e0b;
|
||
--accent-2: #f43f5e;
|
||
--accent-rgb: 245, 158, 11;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
02. Base Reset And Page Background
|
||
--------------------------------------------------------------------------
|
||
Базовые правила страницы, плавная смена темы и фоновые декоративные слои.
|
||
========================================================================== */
|
||
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
html {
|
||
scroll-behavior: smooth;
|
||
}
|
||
|
||
body {
|
||
min-height: 100vh;
|
||
margin: 0;
|
||
overflow-x: hidden;
|
||
background:
|
||
radial-gradient(circle at top left, rgba(var(--accent-rgb), 0.24), transparent 36rem),
|
||
radial-gradient(circle at bottom right, rgba(34, 211, 238, 0.16), transparent 34rem),
|
||
linear-gradient(135deg, var(--bg), var(--bg-soft));
|
||
color: var(--text);
|
||
font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||
transition: background 220ms ease, color 220ms ease;
|
||
}
|
||
|
||
button,
|
||
input,
|
||
a {
|
||
font: inherit;
|
||
}
|
||
|
||
a {
|
||
color: inherit;
|
||
}
|
||
|
||
/* Фоновые "ambient" пятна создают глубину, но не перехватывают клики. */
|
||
.ambient {
|
||
position: fixed;
|
||
z-index: -1;
|
||
width: 30rem;
|
||
height: 30rem;
|
||
border-radius: 999px;
|
||
filter: blur(18px);
|
||
opacity: 0.42;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.ambient-one {
|
||
top: -10rem;
|
||
right: -8rem;
|
||
background: rgba(var(--accent-rgb), 0.46);
|
||
}
|
||
|
||
.ambient-two {
|
||
bottom: -13rem;
|
||
left: -10rem;
|
||
background: rgba(34, 211, 238, 0.3);
|
||
}
|
||
|
||
/* ==========================================================================
|
||
03. Application Layout
|
||
--------------------------------------------------------------------------
|
||
.shell ограничивает общую ширину, .app-layout делит экран на:
|
||
sidebar -> resizer -> catalog.
|
||
========================================================================== */
|
||
|
||
.shell {
|
||
width: min(var(--shell-max-width), calc(100% - 28px));
|
||
margin: 0 auto;
|
||
padding: 20px 0;
|
||
}
|
||
|
||
/* Левая колонка получает ширину из --sidebar-width, которую меняет JS. */
|
||
.app-layout {
|
||
display: grid;
|
||
grid-template-columns: var(--sidebar-width) 8px minmax(0, 1fr);
|
||
gap: 8px;
|
||
align-items: start;
|
||
}
|
||
|
||
/* Компактная шапка: название + поиск, без дерева и resizer. */
|
||
html[data-nav-layout="top"] .app-layout,
|
||
.app-layout[data-nav-layout="top"] {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
html[data-nav-layout="top"] .sidebar-resizer {
|
||
display: none;
|
||
}
|
||
|
||
html[data-nav-layout="top"] .side-panel {
|
||
position: static;
|
||
}
|
||
|
||
html[data-nav-layout="top"] .side-panel .hero-content {
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
gap: 12px 16px;
|
||
max-height: none;
|
||
min-height: auto;
|
||
overflow: visible;
|
||
padding: 14px 58px 14px 18px;
|
||
}
|
||
|
||
html[data-nav-layout="top"] .side-panel .hero-content > .eyebrow {
|
||
margin: 0;
|
||
flex: 0 1 auto;
|
||
max-width: min(100%, 360px);
|
||
}
|
||
|
||
html[data-nav-layout="top"] .search-field {
|
||
margin-top: 0;
|
||
flex: 1 1 220px;
|
||
max-width: min(100%, 560px);
|
||
}
|
||
|
||
html[data-nav-layout="top"] .nav-tree {
|
||
display: none;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
04. Sidebar Resizer
|
||
--------------------------------------------------------------------------
|
||
Узкая колонка между меню и каталогом. Pointer Events в script.js двигают
|
||
CSS-переменную --sidebar-width и сохраняют значение в localStorage.
|
||
========================================================================== */
|
||
|
||
.sidebar-resizer {
|
||
position: sticky;
|
||
top: 20px;
|
||
width: 8px;
|
||
height: calc(100vh - 40px);
|
||
border-radius: 999px;
|
||
cursor: col-resize;
|
||
background: transparent;
|
||
transition: background 160ms ease;
|
||
}
|
||
|
||
.sidebar-resizer::before {
|
||
content: "";
|
||
display: block;
|
||
width: 2px;
|
||
height: 100%;
|
||
margin: 0 auto;
|
||
border-radius: inherit;
|
||
background: var(--border);
|
||
}
|
||
|
||
.sidebar-resizer:hover::before,
|
||
body.is-resizing-sidebar .sidebar-resizer::before {
|
||
background: var(--accent);
|
||
}
|
||
|
||
/* Во время drag меняем курсор по всей странице и запрещаем выделение текста. */
|
||
body.is-resizing-sidebar {
|
||
cursor: col-resize;
|
||
user-select: none;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
05. Sidebar Panel
|
||
--------------------------------------------------------------------------
|
||
Sticky-панель слева: заголовок, шестеренка, поиск и дерево навигации.
|
||
========================================================================== */
|
||
|
||
.side-panel {
|
||
position: sticky;
|
||
top: 20px;
|
||
min-width: 0;
|
||
}
|
||
|
||
/* .hero остался как совместимый контейнерный класс после уплотнения шапки. */
|
||
.hero {
|
||
display: block;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
/* Общая "стеклянная" поверхность для главных панелей. */
|
||
.hero-content,
|
||
.control-panel,
|
||
.catalog-section,
|
||
.empty-state {
|
||
border: 1px solid var(--border);
|
||
background: var(--panel);
|
||
box-shadow: var(--shadow);
|
||
backdrop-filter: blur(22px);
|
||
}
|
||
|
||
.hero-content {
|
||
max-height: calc(100vh - 40px);
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
padding: clamp(16px, 2.6vw, 24px);
|
||
border-radius: 22px;
|
||
position: relative;
|
||
overflow: visible;
|
||
}
|
||
|
||
.side-panel .hero-content {
|
||
justify-content: flex-start;
|
||
overflow: auto;
|
||
}
|
||
|
||
/* Внутренний фон sidebar: градиент + hexagon pattern. */
|
||
.hero-content::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
border-radius: inherit;
|
||
background:
|
||
linear-gradient(120deg, rgba(var(--accent-rgb), 0.25), transparent 34%),
|
||
url("data:image/svg+xml,%3Csvg width='96' height='84' viewBox='0 0 96 84' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' stroke='white' stroke-opacity='.10' stroke-width='1.4'%3E%3Cpath d='M24 2 46 14v25L24 52 2 39V14L24 2Z'/%3E%3Cpath d='M72 32 94 44v25L72 82 50 69V44l22-12Z'/%3E%3C/g%3E%3C/svg%3E");
|
||
mask-image: linear-gradient(to bottom, black, transparent 88%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.hero-content > * {
|
||
position: relative;
|
||
}
|
||
|
||
/* Общий стиль маленьких uppercase-меток и номеров. */
|
||
.eyebrow,
|
||
.section-number,
|
||
.group-number,
|
||
.item-number {
|
||
margin: 0;
|
||
color: var(--accent);
|
||
font-size: 0.82rem;
|
||
font-weight: 800;
|
||
letter-spacing: 0.14em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
/* Нумерация управляется toggle-настройкой "Показывать нумерацию". */
|
||
html:not([data-show-numbers="true"]) .section-number,
|
||
html:not([data-show-numbers="true"]) .group-number,
|
||
html:not([data-show-numbers="true"]) .item-number {
|
||
display: none;
|
||
}
|
||
|
||
/* Теги в дереве управляются toggle-настройкой "Показывать теги дерева". */
|
||
html[data-show-tree-tags="false"] .tree-node small {
|
||
display: none;
|
||
}
|
||
|
||
/* Двухколоночный режим элементов внутри папок дерева. */
|
||
html[data-tree-two-column="true"] {
|
||
--shell-max-width: 1920px;
|
||
--sidebar-max-width: 760px;
|
||
}
|
||
|
||
html[data-tree-two-column="true"] .tree-items {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 2px 8px;
|
||
}
|
||
|
||
html[data-tree-two-column="true"] .tree-node.tree-file {
|
||
grid-template-columns: 16px minmax(0, 1fr);
|
||
min-height: 24px;
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
html[data-tree-two-column="true"] .tree-node.tree-file small {
|
||
display: none;
|
||
}
|
||
|
||
/* Подсветка совпадений поиска в дереве и в карточках. */
|
||
.search-mark {
|
||
border-radius: 4px;
|
||
padding: 0 2px;
|
||
color: white;
|
||
background: linear-gradient(var(--accent-angle), var(--accent), var(--accent-2));
|
||
}
|
||
|
||
/* ==========================================================================
|
||
06. Settings Popup
|
||
--------------------------------------------------------------------------
|
||
Настройки вынесены из sidebar в fixed popup, чтобы на мобильных устройствах
|
||
они не перекрывались потоком контента и всегда были поверх страницы.
|
||
========================================================================== */
|
||
|
||
.control-panel {
|
||
position: fixed;
|
||
z-index: 1001;
|
||
top: 24px;
|
||
right: 24px;
|
||
width: min(460px, calc(100% - 48px));
|
||
max-height: calc(100vh - 48px);
|
||
overflow: auto;
|
||
display: grid;
|
||
gap: 10px;
|
||
padding: 14px;
|
||
border-radius: 24px;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
pointer-events: none;
|
||
transform: translateY(-8px) scale(0.98);
|
||
transform-origin: top right;
|
||
transition:
|
||
opacity 180ms ease,
|
||
visibility 180ms ease,
|
||
transform 180ms ease;
|
||
}
|
||
|
||
/* Затемнение и blur-подложка за открытыми настройками. */
|
||
.settings-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 1000;
|
||
background: rgba(2, 6, 23, 0.42);
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
pointer-events: none;
|
||
backdrop-filter: blur(4px);
|
||
transition: opacity 180ms ease, visibility 180ms ease;
|
||
}
|
||
|
||
/* Состояния открытия popup и overlay. */
|
||
.settings-overlay.is-open {
|
||
opacity: 1;
|
||
visibility: visible;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.control-panel.is-open {
|
||
opacity: 1;
|
||
visibility: visible;
|
||
pointer-events: auto;
|
||
transform: translateY(0) scale(1);
|
||
}
|
||
|
||
/* Кнопки шестеренки и закрытия имеют один визуальный язык. */
|
||
.settings-button,
|
||
.settings-close {
|
||
border: 1px solid var(--border);
|
||
cursor: pointer;
|
||
color: var(--text);
|
||
background: var(--surface);
|
||
transition: transform 160ms ease, background 160ms ease, border-color 160ms ease;
|
||
}
|
||
|
||
.settings-button {
|
||
position: absolute;
|
||
z-index: 21;
|
||
top: 16px;
|
||
right: 16px;
|
||
width: 38px;
|
||
height: 38px;
|
||
display: grid;
|
||
place-items: center;
|
||
border-radius: 999px;
|
||
font-size: 1.18rem;
|
||
}
|
||
|
||
.settings-button:hover,
|
||
.settings-close:hover {
|
||
transform: translateY(-1px);
|
||
border-color: rgba(var(--accent-rgb), 0.5);
|
||
background: rgba(var(--accent-rgb), 0.12);
|
||
}
|
||
|
||
/* Заголовок popup-настроек. */
|
||
.settings-panel-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 14px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.settings-panel-head h2 {
|
||
margin: 4px 0 0;
|
||
font-size: 1.35rem;
|
||
letter-spacing: -0.04em;
|
||
}
|
||
|
||
.settings-close {
|
||
width: 34px;
|
||
height: 34px;
|
||
flex: 0 0 auto;
|
||
border-radius: 999px;
|
||
font-size: 1.25rem;
|
||
line-height: 1;
|
||
}
|
||
|
||
.settings-section {
|
||
display: grid;
|
||
gap: 10px;
|
||
}
|
||
|
||
.settings-section[hidden] {
|
||
display: none;
|
||
}
|
||
|
||
.settings-section-title {
|
||
margin: 0;
|
||
color: var(--text);
|
||
font-size: 0.82rem;
|
||
font-weight: 800;
|
||
letter-spacing: 0.05em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.settings-section-hint {
|
||
margin: 0;
|
||
color: var(--muted);
|
||
font-size: 0.82rem;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
07. Search Field And Control Inputs
|
||
--------------------------------------------------------------------------
|
||
Поиск живет в sidebar, а остальные контролы в popup. Здесь собраны общие
|
||
стили input, toggle-row, segmented buttons и accent swatches.
|
||
========================================================================== */
|
||
|
||
.search-field {
|
||
margin-top: 15px;
|
||
|
||
display: grid;
|
||
gap: 6px;
|
||
max-width: 720px;
|
||
position: relative;
|
||
}
|
||
|
||
.control-group > span {
|
||
color: var(--muted);
|
||
font-size: 0.82rem;
|
||
font-weight: 800;
|
||
}
|
||
|
||
/* Иконка поиска рисуется CSS-ом, чтобы не добавлять лишний DOM. */
|
||
.search-field::after {
|
||
content: "⌕";
|
||
position: absolute;
|
||
right: 11px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
color: var(--accent);
|
||
font-size: 1.05rem;
|
||
font-weight: 900;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.search-field input {
|
||
width: 100%;
|
||
min-height: 38px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 14px;
|
||
background: var(--surface);
|
||
color: var(--text);
|
||
outline: none;
|
||
padding: 0 34px 0 12px;
|
||
transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
|
||
}
|
||
|
||
.search-field input:focus {
|
||
border-color: rgba(var(--accent-rgb), 0.62);
|
||
box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.16);
|
||
background: var(--panel-strong);
|
||
}
|
||
|
||
/* Toggle-row используется для checkbox-настроек с описанием. */
|
||
.toggle-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 14px;
|
||
align-items: center;
|
||
min-height: 54px;
|
||
padding: 10px 12px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 18px;
|
||
background: var(--surface);
|
||
}
|
||
|
||
.toggle-row span {
|
||
display: grid;
|
||
gap: 2px;
|
||
}
|
||
|
||
.toggle-row strong {
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.toggle-row small {
|
||
color: var(--muted);
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* Кастомный checkbox выглядит как компактный switch. */
|
||
.toggle-row input {
|
||
appearance: none;
|
||
width: 48px;
|
||
height: 28px;
|
||
flex: 0 0 auto;
|
||
border: 1px solid var(--border);
|
||
border-radius: 999px;
|
||
background: rgba(148, 163, 184, 0.26);
|
||
cursor: pointer;
|
||
position: relative;
|
||
transition: background 160ms ease, border-color 160ms ease;
|
||
}
|
||
|
||
.toggle-row input::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: 3px;
|
||
left: 3px;
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 999px;
|
||
background: white;
|
||
transition: transform 160ms ease;
|
||
}
|
||
|
||
.toggle-row input:checked {
|
||
border-color: rgba(var(--accent-rgb), 0.6);
|
||
background: linear-gradient(var(--accent-angle), var(--accent), var(--accent-2));
|
||
}
|
||
|
||
.toggle-row input:checked::before {
|
||
transform: translateX(20px);
|
||
}
|
||
|
||
/* Контейнеры групп кнопок настроек. */
|
||
.control-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
min-height: 42px;
|
||
padding: 6px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 18px;
|
||
background: var(--surface);
|
||
}
|
||
|
||
.control-group > span {
|
||
min-width: 68px;
|
||
padding-left: 6px;
|
||
}
|
||
|
||
/* Для четырех режимов отображения кнопки складываются в компактную сетку. */
|
||
.view-group {
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.view-group > span {
|
||
width: 100%;
|
||
}
|
||
|
||
.view-group .segmented {
|
||
flex-basis: calc(50% - 3px);
|
||
}
|
||
|
||
/* Segmented buttons и color swatches имеют общий focus/hover baseline. */
|
||
.segmented,
|
||
.accent-swatch {
|
||
border: 0;
|
||
cursor: pointer;
|
||
transition: transform 160ms ease, background 160ms ease, color 160ms ease;
|
||
}
|
||
|
||
.segmented {
|
||
flex: 1;
|
||
min-height: 31px;
|
||
min-width: 0;
|
||
border-radius: 12px;
|
||
background: transparent;
|
||
color: var(--muted);
|
||
font-size: 0.84rem;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.segmented:hover,
|
||
.accent-swatch:hover {
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.segmented.is-active {
|
||
background: linear-gradient(var(--accent-angle), var(--accent), var(--accent-2));
|
||
color: white;
|
||
}
|
||
|
||
.accent-group {
|
||
flex-wrap: wrap;
|
||
justify-content: flex-start;
|
||
row-gap: 8px;
|
||
}
|
||
|
||
.accent-group > span {
|
||
width: 100%;
|
||
}
|
||
|
||
.accent-swatch {
|
||
width: 32px;
|
||
height: 32px;
|
||
border: 2px solid transparent;
|
||
border-radius: 999px;
|
||
box-shadow: inset 0 0 0 4px rgba(255, 255, 255, 0.18);
|
||
}
|
||
|
||
/* Акцентные кружки используют те же цвета, что и html[data-accent]. */
|
||
.accent-swatch[data-accent="violet"] {
|
||
background: linear-gradient(135deg, #8b5cf6, #22d3ee);
|
||
}
|
||
|
||
.accent-swatch[data-accent="cyan"] {
|
||
background: linear-gradient(135deg, #06b6d4, #3b82f6);
|
||
}
|
||
|
||
.accent-swatch[data-accent="lime"] {
|
||
background: linear-gradient(135deg, #84cc16, #14b8a6);
|
||
}
|
||
|
||
.accent-swatch[data-accent="amber"] {
|
||
background: linear-gradient(135deg, #f59e0b, #f43f5e);
|
||
}
|
||
|
||
.accent-swatch.is-active {
|
||
border-color: var(--text);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
/* Кнопка своего градиента: pill, пунктир, подпись и превию через CSS-переменные. */
|
||
.accent-swatch-custom {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
width: auto;
|
||
min-width: 58px;
|
||
height: 32px;
|
||
padding: 0 10px;
|
||
border: 2px dashed rgba(255, 255, 255, 0.42);
|
||
box-shadow: none;
|
||
background:
|
||
linear-gradient(45deg, rgba(255, 255, 255, 0.08) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, 0.08) 75%),
|
||
linear-gradient(45deg, rgba(255, 255, 255, 0.08) 25%, transparent 25%, transparent 75%, rgba(255, 255, 255, 0.08) 75%);
|
||
background-size: 8px 8px;
|
||
background-position: 0 0, 4px 4px;
|
||
}
|
||
|
||
html[data-theme="light"] .accent-swatch-custom {
|
||
border-color: rgba(15, 23, 42, 0.28);
|
||
background:
|
||
linear-gradient(45deg, rgba(15, 23, 42, 0.06) 25%, transparent 25%, transparent 75%, rgba(15, 23, 42, 0.06) 75%),
|
||
linear-gradient(45deg, rgba(15, 23, 42, 0.06) 25%, transparent 25%, transparent 75%, rgba(15, 23, 42, 0.06) 75%);
|
||
background-size: 8px 8px;
|
||
background-position: 0 0, 4px 4px;
|
||
}
|
||
|
||
.accent-swatch-custom::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 2px;
|
||
border-radius: inherit;
|
||
background: linear-gradient(
|
||
var(--custom-accent-angle, 135deg),
|
||
var(--custom-accent-from, #8b5cf6),
|
||
var(--custom-accent-to, #22d3ee)
|
||
);
|
||
}
|
||
|
||
.accent-swatch-label {
|
||
position: relative;
|
||
z-index: 1;
|
||
color: white;
|
||
font-size: 0.72rem;
|
||
font-weight: 900;
|
||
letter-spacing: 0.04em;
|
||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
|
||
}
|
||
|
||
.accent-swatch-custom.is-active {
|
||
border-style: solid;
|
||
border-color: var(--text);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
/* Панель настройки своего градиента появляется при data-accent="custom". */
|
||
.accent-custom-panel {
|
||
display: grid;
|
||
gap: 8px;
|
||
padding: 10px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 18px;
|
||
background: var(--surface);
|
||
}
|
||
|
||
.accent-custom-panel[hidden] {
|
||
display: none;
|
||
}
|
||
|
||
.accent-custom-title {
|
||
margin: 0;
|
||
color: var(--muted);
|
||
font-size: 0.78rem;
|
||
font-weight: 800;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.accent-custom-row {
|
||
display: grid;
|
||
grid-template-columns: 72px minmax(0, 1fr) auto;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: var(--muted);
|
||
font-size: 0.84rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.accent-custom-row input[type="range"] {
|
||
width: 100%;
|
||
accent-color: var(--accent);
|
||
}
|
||
|
||
.accent-custom-row input[type="color"] {
|
||
width: 100%;
|
||
height: 34px;
|
||
padding: 2px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
background: var(--panel);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.accent-custom-row:has(input[type="color"]) {
|
||
grid-template-columns: 72px 1fr;
|
||
}
|
||
|
||
.accent-custom-row output {
|
||
min-width: 3.2rem;
|
||
color: var(--accent);
|
||
font-size: 0.82rem;
|
||
font-weight: 800;
|
||
text-align: right;
|
||
}
|
||
|
||
.accent-custom-preview {
|
||
height: 36px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
background: linear-gradient(var(--accent-angle), var(--accent), var(--accent-2));
|
||
}
|
||
|
||
/* ==========================================================================
|
||
08. File-Tree Navigation
|
||
--------------------------------------------------------------------------
|
||
Левая навигация визуально имитирует дерево файлов:
|
||
details/summary дают нативное раскрытие, а CSS добавляет линии, caret,
|
||
"folder" для разделов/групп и "file" для конечных элементов.
|
||
========================================================================== */
|
||
|
||
.nav-tree {
|
||
display: grid;
|
||
gap: 2px;
|
||
min-height: 0;
|
||
padding: 4px 2px 0;
|
||
font-size: 0.86rem;
|
||
}
|
||
|
||
/* Details в дереве intentionally выглядят не как карточки, а как вложенный список. */
|
||
.tree-section,
|
||
.tree-group {
|
||
border: 0;
|
||
border-radius: 0;
|
||
background: transparent;
|
||
overflow: visible;
|
||
}
|
||
|
||
.tree-group {
|
||
position: relative;
|
||
margin: 1px 0 1px 18px;
|
||
padding-left: 10px;
|
||
border-left: 1px solid var(--border);
|
||
}
|
||
|
||
/* Summary получает caret, иконку папки и кликабельный текст. */
|
||
.tree-section > summary,
|
||
.tree-group > summary {
|
||
display: grid;
|
||
grid-template-columns: 16px minmax(0, 1fr);
|
||
gap: 3px;
|
||
align-items: center;
|
||
list-style: none;
|
||
cursor: pointer;
|
||
min-height: 28px;
|
||
padding: 1px 2px;
|
||
border-radius: 9px;
|
||
}
|
||
|
||
/* Нативный маркер details скрываем, так как рисуем свой caret. */
|
||
.tree-section > summary::-webkit-details-marker,
|
||
.tree-group > summary::-webkit-details-marker {
|
||
display: none;
|
||
}
|
||
|
||
/* Caret поворачивается через состояние [open] у details. */
|
||
.tree-caret {
|
||
width: 16px;
|
||
height: 24px;
|
||
display: grid;
|
||
place-items: center;
|
||
color: var(--muted);
|
||
}
|
||
|
||
.tree-caret::before {
|
||
content: "▾";
|
||
font-size: 0.72rem;
|
||
transition: transform 140ms ease;
|
||
}
|
||
|
||
.tree-section:not([open]) > summary .tree-caret::before,
|
||
.tree-group:not([open]) > summary .tree-caret::before {
|
||
transform: rotate(-90deg);
|
||
}
|
||
|
||
/* Конечный элемент дерева: иконка файла, название и optional теги. */
|
||
.tree-node {
|
||
min-width: 0;
|
||
width: 100%;
|
||
min-height: 26px;
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) auto;
|
||
gap: 8px;
|
||
align-items: center;
|
||
border: 0;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
color: var(--text);
|
||
background: transparent;
|
||
text-align: left;
|
||
font: inherit;
|
||
font-weight: 760;
|
||
}
|
||
|
||
.tree-node span {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Вертикальная линия вложенности дерева. */
|
||
.tree-node::before {
|
||
color: var(--accent);
|
||
opacity: 0.92;
|
||
}
|
||
|
||
/* Папки и файлы нарисованы CSS-иконками, чтобы не тянуть отдельные SVG. */
|
||
.tree-folder {
|
||
grid-template-columns: 18px minmax(0, 1fr);
|
||
}
|
||
|
||
.tree-folder::before {
|
||
content: "📁";
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.tree-file {
|
||
grid-template-columns: 18px minmax(0, 1fr) auto;
|
||
}
|
||
|
||
.tree-file::before {
|
||
content: "📄";
|
||
font-size: 0.86rem;
|
||
}
|
||
|
||
/* Контейнер элементов внутри группы дерева. */
|
||
.tree-items {
|
||
position: relative;
|
||
display: grid;
|
||
gap: 1px;
|
||
margin-left: 18px;
|
||
padding-left: 10px;
|
||
border-left: 1px solid var(--border);
|
||
}
|
||
|
||
.tree-node:hover,
|
||
.tree-section > summary:hover,
|
||
.tree-group > summary:hover {
|
||
background: rgba(var(--accent-rgb), 0.14);
|
||
}
|
||
|
||
/* Теги дерева усечены ellipsis, чтобы длинные списки не ломали sidebar. */
|
||
.tree-node small {
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
color: var(--accent);
|
||
font-size: 0.68rem;
|
||
font-weight: 800;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
09. Catalog Structure
|
||
--------------------------------------------------------------------------
|
||
Правая часть страницы. Разделы и подразделы тоже построены на details,
|
||
поэтому пользователь может сворачивать не только дерево, но и контент.
|
||
========================================================================== */
|
||
|
||
.catalog {
|
||
display: grid;
|
||
gap: 12px;
|
||
min-width: 0;
|
||
}
|
||
|
||
/* Главные glass-карточки разделов. */
|
||
.catalog-section {
|
||
border-radius: 24px;
|
||
overflow: hidden;
|
||
scroll-margin-top: 18px;
|
||
}
|
||
|
||
.catalog-section,
|
||
.catalog-group {
|
||
display: block;
|
||
}
|
||
|
||
/* Summary у разделов/групп заменяет заголовок и одновременно управляет collapse. */
|
||
.catalog-section > summary,
|
||
.catalog-group > summary {
|
||
list-style: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.catalog-section > summary::-webkit-details-marker,
|
||
.catalog-group > summary::-webkit-details-marker {
|
||
display: none;
|
||
}
|
||
|
||
/* Шапка раздела: номер, название, описание, счетчик и индикатор collapse. */
|
||
.section-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
align-items: flex-start;
|
||
padding: 18px;
|
||
border-bottom: 1px solid var(--border);
|
||
background:
|
||
radial-gradient(circle at top left, rgba(var(--accent-rgb), 0.14), transparent 22rem),
|
||
rgba(255, 255, 255, 0.035);
|
||
}
|
||
|
||
.section-meta,
|
||
.group-meta {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.section-head h2,
|
||
.group-head h3,
|
||
.nav-card h4,
|
||
.empty-state h2 {
|
||
margin: 0;
|
||
color: var(--text);
|
||
letter-spacing: -0.04em;
|
||
}
|
||
|
||
.section-head h2 {
|
||
margin-top: 5px;
|
||
font-size: clamp(1.35rem, 3.2vw, 2.15rem);
|
||
}
|
||
|
||
.section-head p:not(.section-number),
|
||
.group-head p:not(.group-number) {
|
||
max-width: 760px;
|
||
margin: 5px 0 0;
|
||
color: var(--muted);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* Счетчики помогают понять размер раздела без чтения всех карточек. */
|
||
.section-count {
|
||
white-space: nowrap;
|
||
border: 1px solid var(--border);
|
||
border-radius: 999px;
|
||
padding: 8px 12px;
|
||
color: var(--muted);
|
||
background: var(--surface);
|
||
font-size: 0.86rem;
|
||
font-weight: 800;
|
||
}
|
||
|
||
/* Стрелка сворачивания поворачивается через [open]. */
|
||
.collapse-indicator {
|
||
width: 34px;
|
||
height: 34px;
|
||
display: grid;
|
||
place-items: center;
|
||
border: 1px solid var(--border);
|
||
border-radius: 999px;
|
||
background: var(--surface);
|
||
color: var(--accent);
|
||
font-weight: 900;
|
||
transition: transform 160ms ease;
|
||
}
|
||
|
||
.catalog-section:not([open]) > .section-head .collapse-indicator,
|
||
.catalog-group:not([open]) > .group-head .collapse-indicator {
|
||
transform: rotate(-90deg);
|
||
}
|
||
|
||
/* Подразделы внутри секции. */
|
||
.section-groups {
|
||
display: grid;
|
||
gap: 12px;
|
||
padding: 12px;
|
||
}
|
||
|
||
/* Плоский список карточек без обёртки section/group. */
|
||
.catalog > .catalog-flat-grid {
|
||
padding: 12px;
|
||
}
|
||
|
||
/* Элементы раздела без промежуточного подраздела. */
|
||
.section-groups > .items-grid:first-child:last-child {
|
||
padding: 0;
|
||
}
|
||
|
||
/* Файлы сразу под разделом в дереве (без tree-group). */
|
||
.tree-items-direct {
|
||
margin-left: 18px;
|
||
padding-left: 10px;
|
||
border-left: 1px solid var(--border);
|
||
}
|
||
|
||
.catalog-group {
|
||
border: 1px solid var(--border);
|
||
border-radius: 18px;
|
||
background: rgba(255, 255, 255, 0.035);
|
||
overflow: hidden;
|
||
scroll-margin-top: 18px;
|
||
}
|
||
|
||
.group-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 14px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.group-head h3 {
|
||
margin-top: 4px;
|
||
font-size: 1.08rem;
|
||
}
|
||
|
||
.group-count {
|
||
width: 36px;
|
||
height: 36px;
|
||
display: grid;
|
||
place-items: center;
|
||
flex: 0 0 auto;
|
||
border-radius: 999px;
|
||
background: rgba(var(--accent-rgb), 0.14);
|
||
color: var(--accent);
|
||
font-weight: 900;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
10. Navigation Cards
|
||
--------------------------------------------------------------------------
|
||
Конечные кликабельные элементы каталога. Карточки используют CSS-переменные
|
||
--card-x/--card-y, которые JS обновляет при движении мыши для shine-эффекта.
|
||
========================================================================== */
|
||
|
||
.items-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 10px;
|
||
padding: 10px;
|
||
}
|
||
|
||
/* Базовая карточка с иконкой, контентом, тегами, стрелкой и shine-слоем. */
|
||
.nav-card {
|
||
--tilt-x: 0deg;
|
||
--tilt-y: 0deg;
|
||
position: relative;
|
||
min-height: 196px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
padding: 14px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 18px;
|
||
background: var(--panel);
|
||
color: var(--text);
|
||
text-decoration: none;
|
||
overflow: hidden;
|
||
transform-origin: center;
|
||
transform: perspective(900px) rotateX(var(--tilt-x)) rotateY(var(--tilt-y));
|
||
will-change: transform;
|
||
transition:
|
||
transform 260ms cubic-bezier(0.22, 1, 0.36, 1),
|
||
border-color 220ms ease,
|
||
background 220ms ease,
|
||
box-shadow 220ms ease;
|
||
scroll-margin-top: 18px;
|
||
}
|
||
|
||
/* Временная подсветка цели после клика в дереве. */
|
||
.catalog-section.is-focused,
|
||
.catalog-group.is-focused,
|
||
.nav-card.is-focused {
|
||
border-color: rgba(var(--accent-rgb), 0.75);
|
||
box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.16), var(--shadow);
|
||
}
|
||
|
||
/* Градиентный бордер/подсветка под курсором. */
|
||
.nav-card::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
border-radius: inherit;
|
||
background: radial-gradient(
|
||
circle at var(--card-x) var(--card-y),
|
||
rgba(var(--accent-rgb), 0.24),
|
||
transparent 34%
|
||
);
|
||
opacity: 0;
|
||
transition: opacity 180ms ease;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* Hover задается классом .is-hovered из JS, чтобы убрать скачки на границах. */
|
||
.nav-card.is-hovered,
|
||
.nav-card:focus-visible {
|
||
border-color: rgba(var(--accent-rgb), 0.52);
|
||
background: var(--panel-strong);
|
||
box-shadow: 0 22px 70px rgba(var(--accent-rgb), 0.16);
|
||
outline: none;
|
||
}
|
||
|
||
.nav-card.is-hovered::before,
|
||
.nav-card:focus-visible::before {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* Иконка сервиса берется из serviceMeta. */
|
||
.nav-card img {
|
||
width: 50px;
|
||
height: 50px;
|
||
object-fit: contain;
|
||
filter: drop-shadow(0 14px 24px rgba(0, 0, 0, 0.22));
|
||
}
|
||
|
||
.card-content {
|
||
position: relative;
|
||
display: grid;
|
||
gap: 5px;
|
||
}
|
||
|
||
.nav-card h4 {
|
||
font-size: 1rem;
|
||
line-height: 1.25;
|
||
}
|
||
|
||
/* Тип сервиса выделен акцентом и помогает быстро считывать назначение. */
|
||
.item-type {
|
||
margin: 0;
|
||
color: var(--accent);
|
||
font-size: 0.9rem;
|
||
font-weight: 750;
|
||
}
|
||
|
||
/* Теги показывают, по каким словам карточку удобно искать. */
|
||
.tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 5px;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.tags span {
|
||
border: 1px solid var(--border);
|
||
border-radius: 999px;
|
||
padding: 3px 7px;
|
||
background: rgba(var(--accent-rgb), 0.1);
|
||
color: var(--muted);
|
||
font-size: 0.76rem;
|
||
font-weight: 800;
|
||
}
|
||
|
||
/* Стрелка справа подчеркивает, что карточка ведет по ссылке. */
|
||
.arrow {
|
||
position: absolute;
|
||
right: 16px;
|
||
bottom: 14px;
|
||
width: 36px;
|
||
height: 36px;
|
||
display: grid;
|
||
place-items: center;
|
||
border-radius: 999px;
|
||
background: rgba(var(--accent-rgb), 0.14);
|
||
color: var(--accent);
|
||
font-size: 1.15rem;
|
||
transition: transform 180ms ease, background 180ms ease, color 180ms ease;
|
||
}
|
||
|
||
.nav-card.is-hovered .arrow,
|
||
.nav-card:focus-visible .arrow {
|
||
transform: translateX(4px);
|
||
background: linear-gradient(var(--accent-angle), var(--accent), var(--accent-2));
|
||
color: white;
|
||
}
|
||
|
||
/* Декоративный shine слой, положение которого обновляется pointermove. */
|
||
.shine {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: linear-gradient(
|
||
110deg,
|
||
transparent 0%,
|
||
rgba(255, 255, 255, 0.16) 42%,
|
||
transparent 64%
|
||
);
|
||
transform: translateX(-130%);
|
||
transition: transform 540ms ease;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.nav-card.is-hovered .shine,
|
||
.nav-card:focus-visible .shine {
|
||
transform: translateX(130%);
|
||
}
|
||
|
||
/* ==========================================================================
|
||
11. View Modes
|
||
--------------------------------------------------------------------------
|
||
Режимы отображения переключаются классами на .catalog:
|
||
cards-view, list-view, compact-cards-view, compact-list-view.
|
||
========================================================================== */
|
||
|
||
.catalog.list-view .items-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
/* В list-режимах карточка вытягивается в строку. */
|
||
.catalog.list-view .nav-card,
|
||
.catalog.compact-list-view .nav-card {
|
||
min-height: 96px;
|
||
display: grid;
|
||
grid-template-columns: 50px minmax(0, 1fr) 36px;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.catalog.list-view .arrow,
|
||
.catalog.compact-list-view .arrow {
|
||
position: static;
|
||
justify-self: end;
|
||
}
|
||
|
||
/* Compact modes скрывают второстепенную информацию и уменьшают отступы. */
|
||
.catalog.compact-cards-view .items-grid {
|
||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||
}
|
||
|
||
/* Compact list на широких экранах использует 3 колонки. */
|
||
.catalog.compact-list-view .items-grid {
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
}
|
||
|
||
.catalog.compact-cards-view .section-head,
|
||
.catalog.compact-list-view .section-head {
|
||
padding: 14px 16px;
|
||
}
|
||
|
||
/* В compact-режимах описания и теги скрыты ради плотности. */
|
||
.catalog.compact-cards-view .section-head p:not(.section-number),
|
||
.catalog.compact-list-view .section-head p:not(.section-number),
|
||
.catalog.compact-cards-view .group-head p:not(.group-number),
|
||
.catalog.compact-list-view .group-head p:not(.group-number),
|
||
.catalog.compact-cards-view .tags,
|
||
.catalog.compact-list-view .tags,
|
||
.catalog.compact-list-view .item-type {
|
||
display: none;
|
||
}
|
||
|
||
.catalog.compact-cards-view .section-head h2,
|
||
.catalog.compact-list-view .section-head h2 {
|
||
font-size: clamp(1.35rem, 3vw, 2rem);
|
||
}
|
||
|
||
.catalog.compact-cards-view .group-head,
|
||
.catalog.compact-list-view .group-head {
|
||
padding: 10px 12px;
|
||
align-items: center;
|
||
}
|
||
|
||
.catalog.compact-cards-view .group-head h3,
|
||
.catalog.compact-list-view .group-head h3 {
|
||
margin-top: 4px;
|
||
font-size: 1.05rem;
|
||
}
|
||
|
||
.catalog.compact-cards-view .nav-card {
|
||
min-height: 116px;
|
||
gap: 8px;
|
||
padding: 12px;
|
||
}
|
||
|
||
.catalog.compact-cards-view .nav-card img {
|
||
width: 44px;
|
||
height: 44px;
|
||
}
|
||
|
||
.catalog.compact-cards-view .nav-card h4,
|
||
.catalog.compact-list-view .nav-card h4 {
|
||
font-size: 0.98rem;
|
||
}
|
||
|
||
.catalog.compact-cards-view .item-type,
|
||
.catalog.compact-list-view .item-type {
|
||
font-size: 0.82rem;
|
||
}
|
||
|
||
.catalog.compact-cards-view .arrow {
|
||
width: 30px;
|
||
height: 30px;
|
||
right: 12px;
|
||
bottom: 12px;
|
||
}
|
||
|
||
.catalog.compact-list-view .nav-card {
|
||
min-height: 56px;
|
||
padding: 8px 10px;
|
||
grid-template-columns: 32px minmax(0, 1fr) 28px;
|
||
}
|
||
|
||
.catalog.compact-list-view .nav-card img {
|
||
width: 32px;
|
||
height: 32px;
|
||
}
|
||
|
||
.catalog.compact-list-view .item-number {
|
||
font-size: 0.72rem;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
12. Empty State
|
||
--------------------------------------------------------------------------
|
||
Показывается, когда фильтр поиска не оставил ни одной карточки.
|
||
========================================================================== */
|
||
|
||
.empty-state {
|
||
min-height: 280px;
|
||
display: grid;
|
||
place-items: center;
|
||
align-content: center;
|
||
gap: 12px;
|
||
padding: 36px;
|
||
border-radius: 30px;
|
||
text-align: center;
|
||
}
|
||
|
||
.empty-state img {
|
||
width: 74px;
|
||
height: 74px;
|
||
}
|
||
|
||
.empty-state p {
|
||
max-width: 480px;
|
||
margin: 0;
|
||
color: var(--muted);
|
||
}
|
||
|
||
/* ==========================================================================
|
||
13. Responsive Layout
|
||
--------------------------------------------------------------------------
|
||
Основной режим выбирается по типу ввода, а не по ширине экрана:
|
||
- touch / coarse pointer -> компактный "мобильный" layout;
|
||
- hover + fine pointer -> десктопный layout даже на широком touch-мониторе.
|
||
Узкое окно с мышью дополнительно поджимает сетку через max-width fallback.
|
||
========================================================================== */
|
||
|
||
/* Узкое окно на устройствах с мышью: sidebar и сетка карточек чуть компактнее. */
|
||
@media (hover: hover) and (pointer: fine) and (max-width: 1120px) {
|
||
.app-layout {
|
||
grid-template-columns: minmax(240px, min(var(--sidebar-width), 320px)) 8px minmax(0, 1fr);
|
||
}
|
||
|
||
.items-grid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.catalog.compact-cards-view .items-grid {
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
/* Touch-устройства: stacked layout, без resizer, крупнее touch-targets. */
|
||
@media (hover: none), (pointer: coarse) {
|
||
.shell {
|
||
width: min(var(--shell-max-width), calc(100% - 22px));
|
||
padding: 14px 0;
|
||
}
|
||
|
||
.app-layout {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.sidebar-resizer {
|
||
display: none;
|
||
}
|
||
|
||
.side-panel {
|
||
position: static;
|
||
}
|
||
|
||
.hero-content {
|
||
min-height: auto;
|
||
max-height: none;
|
||
padding: 16px 14px;
|
||
border-radius: 20px;
|
||
}
|
||
|
||
.nav-tree {
|
||
max-height: 320px;
|
||
overflow: auto;
|
||
}
|
||
|
||
/*
|
||
Двухколоночное дерево на touch не отключается полностью:
|
||
- в portrait — всегда, если включена настройка;
|
||
- в landscape — начиная с 520px ширины viewport.
|
||
*/
|
||
html[data-tree-two-column="true"] .tree-items {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
@media (min-width: 520px), (orientation: portrait) {
|
||
html[data-tree-two-column="true"] .tree-items {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
.control-panel,
|
||
.catalog-section,
|
||
.empty-state {
|
||
border-radius: 24px;
|
||
}
|
||
|
||
.control-panel {
|
||
top: 12px;
|
||
right: 12px;
|
||
width: calc(100vw - 24px);
|
||
max-height: calc(100vh - 24px);
|
||
}
|
||
|
||
.control-group {
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.control-group .segmented {
|
||
flex-basis: calc(50% - 4px);
|
||
}
|
||
|
||
.control-group > span {
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
|
||
.accent-group > span {
|
||
width: auto;
|
||
}
|
||
|
||
.section-head,
|
||
.group-head {
|
||
display: grid;
|
||
}
|
||
|
||
.section-count {
|
||
width: fit-content;
|
||
}
|
||
|
||
.section-groups {
|
||
padding: 8px;
|
||
}
|
||
|
||
.items-grid {
|
||
grid-template-columns: 1fr;
|
||
padding: 8px;
|
||
}
|
||
|
||
.catalog.compact-list-view .items-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.catalog.compact-cards-view .items-grid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.nav-card {
|
||
min-height: 180px;
|
||
}
|
||
|
||
.catalog.list-view .nav-card {
|
||
grid-template-columns: 48px minmax(0, 1fr);
|
||
min-height: 136px;
|
||
}
|
||
|
||
.catalog.compact-list-view .nav-card {
|
||
grid-template-columns: 42px minmax(0, 1fr);
|
||
min-height: 84px;
|
||
}
|
||
|
||
.catalog.list-view .arrow,
|
||
.catalog.compact-list-view .arrow {
|
||
position: absolute;
|
||
}
|
||
|
||
.nav-card img {
|
||
width: 50px;
|
||
height: 50px;
|
||
}
|
||
}
|
||
|
||
/* ==========================================================================
|
||
14. Reduced Motion
|
||
--------------------------------------------------------------------------
|
||
Уважает системную настройку пользователя и отключает анимации/плавный скролл.
|
||
========================================================================== */
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
*,
|
||
*::before,
|
||
*::after {
|
||
scroll-behavior: auto !important;
|
||
transition-duration: 0.01ms !important;
|
||
animation-duration: 0.01ms !important;
|
||
}
|
||
}
|