universal-dashboard/examples/shared/styles.css

2539 lines
77 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.

/* ==========================================================================
ecosystem-panel / styles.css
--------------------------------------------------------------------------
ОГЛАВЛЕНИЕ (поиск по «==» или номеру секции):
01 Theme Tokens — цвета, layout, pattern tiles, pattern mapping, accents
02 Base Reset And Page Background
03 Application Layout — sidebar / top bar / resizers
04 Catalog Area
05 Sidebar Panel — hero, patterns ::before/::after, layer isolation
06 Settings Popup — sections, pattern swatches, actions footer
07 Search Field And Control Inputs
08 File-Tree Navigation
09 Catalog Sections And Groups
10 Navigation Cards
11 Catalog View Modes
12 Responsive Rules
13 Touch And Reduced Motion
14 Print
========================================================================== */
/* ==========================================================================
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;
--top-bar-height: 140px;
--shell-max-width: 1920px;
--sidebar-max-width: 800px;
--catalog-cards-columns: 3;
--catalog-list-columns: 1;
--catalog-compact-cards-columns: 5;
--catalog-compact-list-columns: 3;
/* --- Pattern: цвет штриха/точки в CSS- и SVG-узорах --- */
--pattern-color: rgba(255, 255, 255, 0.16);
/* --- Pattern tiles: повторяющаяся текстура (image + background-size) --- */
--pattern-tile-hex-image: 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='.18' 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");
--pattern-tile-hex-size: 96px 84px;
--pattern-tile-dots-image: radial-gradient(circle at center, var(--pattern-color) 0.95px, transparent 1px);
--pattern-tile-dots-size: 18px 18px;
--pattern-tile-grid-image:
linear-gradient(var(--pattern-color) 1px, transparent 1px),
linear-gradient(90deg, var(--pattern-color) 1px, transparent 1px);
--pattern-tile-grid-size: 26px 26px;
--pattern-tile-diagonal-image: repeating-linear-gradient(
-45deg,
transparent,
transparent 10px,
var(--pattern-color) 10px,
var(--pattern-color) 11px
);
--pattern-tile-diagonal-size: auto;
--pattern-tile-cross-image:
repeating-linear-gradient(
0deg,
transparent,
transparent 14px,
var(--pattern-color) 14px,
var(--pattern-color) 15px
),
repeating-linear-gradient(
90deg,
transparent,
transparent 14px,
var(--pattern-color) 14px,
var(--pattern-color) 15px
);
--pattern-tile-cross-size: auto;
--pattern-tile-mesh-image: radial-gradient(
circle at 1px 1px,
rgba(var(--accent-rgb), 0.22) 1px,
transparent 0
);
--pattern-tile-mesh-size: 20px 20px;
--pattern-tile-hex-sm-image: url("data:image/svg+xml,%3Csvg width='48' height='42' viewBox='0 0 48 42' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' stroke='white' stroke-opacity='.18' stroke-width='1'%3E%3Cpath d='M12 1 23 7v12L12 26 1 19V7L12 1Z'/%3E%3Cpath d='M36 16 47 22v12L36 41 25 34V22l11-6Z'/%3E%3C/g%3E%3C/svg%3E");
--pattern-tile-hex-sm-size: 48px 42px;
--pattern-tile-dots-sm-image: radial-gradient(circle at center, var(--pattern-color) 0.6px, transparent 0.6px);
--pattern-tile-dots-sm-size: 10px 10px;
--pattern-tile-dots-lg-image: radial-gradient(circle at center, var(--pattern-color) 1.35px, transparent 1.35px);
--pattern-tile-dots-lg-size: 28px 28px;
--pattern-tile-tri-sm-image: url("data:image/svg+xml,%3Csvg width='20' height='18' viewBox='0 0 20 18' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='none' stroke='white' stroke-opacity='.2' stroke-width='1' d='M10 1 19 17 H1 Z'/%3E%3C/svg%3E");
--pattern-tile-tri-sm-size: 20px 18px;
--pattern-tile-tri-lg-image: url("data:image/svg+xml,%3Csvg width='40' height='35' viewBox='0 0 40 35' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='none' stroke='white' stroke-opacity='.18' stroke-width='1.2' d='M20 2 38 33 H2 Z'/%3E%3C/svg%3E");
--pattern-tile-tri-lg-size: 40px 35px;
--pattern-tile-diamond-image: url("data:image/svg+xml,%3Csvg width='22' height='22' viewBox='0 0 22 22' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='none' stroke='white' stroke-opacity='.18' stroke-width='1' d='M11 1 21 11 11 21 1 11 Z'/%3E%3C/svg%3E");
--pattern-tile-diamond-size: 22px 22px;
--settings-panel-width: 460px;
/* --- Pattern layer isolation (preference patternIsolation → JS) --- */
--panel-layer-blur: 8px;
--panel-layer-opacity: 0.27;
--panel-layer-fill: rgba(15, 23, 42, var(--panel-layer-opacity));
}
/* Светлая тема переопределяет только токены, структура стилей остается общей. */
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);
--pattern-color: rgba(15, 23, 42, 0.13);
--pattern-tile-hex-image: 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='%23101827' stroke-opacity='.14' 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");
--pattern-tile-hex-sm-image: url("data:image/svg+xml,%3Csvg width='48' height='42' viewBox='0 0 48 42' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' stroke='%23101827' stroke-opacity='.14' stroke-width='1'%3E%3Cpath d='M12 1 23 7v12L12 26 1 19V7L12 1Z'/%3E%3Cpath d='M36 16 47 22v12L36 41 25 34V22l11-6Z'/%3E%3C/g%3E%3C/svg%3E");
--pattern-tile-tri-sm-image: url("data:image/svg+xml,%3Csvg width='20' height='18' viewBox='0 0 20 18' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='none' stroke='%23101827' stroke-opacity='.16' stroke-width='1' d='M10 1 19 17 H1 Z'/%3E%3C/svg%3E");
--pattern-tile-tri-lg-image: url("data:image/svg+xml,%3Csvg width='40' height='35' viewBox='0 0 40 35' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='none' stroke='%23101827' stroke-opacity='.14' stroke-width='1.2' d='M20 2 38 33 H2 Z'/%3E%3C/svg%3E");
--pattern-tile-diamond-image: url("data:image/svg+xml,%3Csvg width='22' height='22' viewBox='0 0 22 22' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='none' stroke='%23101827' stroke-opacity='.14' stroke-width='1' d='M11 1 21 11 11 21 1 11 Z'/%3E%3C/svg%3E");
--pattern-tile-mesh-image: radial-gradient(
circle at 1px 1px,
rgba(var(--accent-rgb), 0.16) 1px,
transparent 0
);
--panel-layer-fill: rgba(255, 255, 255, var(--panel-layer-opacity));
}
/*
* Mapping встроенных пресетов: html[data-panel-pattern-{target}="{id}"]
* → --panel-pattern-image-{target} и --panel-pattern-size-{target}.
* Цели: nav | section | group | item. Пресет custom задаётся inline из script.js.
*/
html[data-panel-pattern-nav="hex"] { --panel-pattern-image-nav: var(--pattern-tile-hex-image); --panel-pattern-size-nav: var(--pattern-tile-hex-size); }
html[data-panel-pattern-nav="dots"] { --panel-pattern-image-nav: var(--pattern-tile-dots-image); --panel-pattern-size-nav: var(--pattern-tile-dots-size); }
html[data-panel-pattern-nav="grid"] { --panel-pattern-image-nav: var(--pattern-tile-grid-image); --panel-pattern-size-nav: var(--pattern-tile-grid-size); }
html[data-panel-pattern-nav="diagonal"] { --panel-pattern-image-nav: var(--pattern-tile-diagonal-image); --panel-pattern-size-nav: var(--pattern-tile-diagonal-size); }
html[data-panel-pattern-nav="cross"] { --panel-pattern-image-nav: var(--pattern-tile-cross-image); --panel-pattern-size-nav: var(--pattern-tile-cross-size); }
html[data-panel-pattern-nav="mesh"] { --panel-pattern-image-nav: var(--pattern-tile-mesh-image); --panel-pattern-size-nav: var(--pattern-tile-mesh-size); }
html[data-panel-pattern-nav="hex-sm"] { --panel-pattern-image-nav: var(--pattern-tile-hex-sm-image); --panel-pattern-size-nav: var(--pattern-tile-hex-sm-size); }
html[data-panel-pattern-nav="dots-sm"] { --panel-pattern-image-nav: var(--pattern-tile-dots-sm-image); --panel-pattern-size-nav: var(--pattern-tile-dots-sm-size); }
html[data-panel-pattern-nav="dots-lg"] { --panel-pattern-image-nav: var(--pattern-tile-dots-lg-image); --panel-pattern-size-nav: var(--pattern-tile-dots-lg-size); }
html[data-panel-pattern-nav="tri-sm"] { --panel-pattern-image-nav: var(--pattern-tile-tri-sm-image); --panel-pattern-size-nav: var(--pattern-tile-tri-sm-size); }
html[data-panel-pattern-nav="tri-lg"] { --panel-pattern-image-nav: var(--pattern-tile-tri-lg-image); --panel-pattern-size-nav: var(--pattern-tile-tri-lg-size); }
html[data-panel-pattern-nav="diamond"] { --panel-pattern-image-nav: var(--pattern-tile-diamond-image); --panel-pattern-size-nav: var(--pattern-tile-diamond-size); }
html[data-panel-pattern-section="hex"] { --panel-pattern-image-section: var(--pattern-tile-hex-image); --panel-pattern-size-section: var(--pattern-tile-hex-size); }
html[data-panel-pattern-section="dots"] { --panel-pattern-image-section: var(--pattern-tile-dots-image); --panel-pattern-size-section: var(--pattern-tile-dots-size); }
html[data-panel-pattern-section="grid"] { --panel-pattern-image-section: var(--pattern-tile-grid-image); --panel-pattern-size-section: var(--pattern-tile-grid-size); }
html[data-panel-pattern-section="diagonal"] { --panel-pattern-image-section: var(--pattern-tile-diagonal-image); --panel-pattern-size-section: var(--pattern-tile-diagonal-size); }
html[data-panel-pattern-section="cross"] { --panel-pattern-image-section: var(--pattern-tile-cross-image); --panel-pattern-size-section: var(--pattern-tile-cross-size); }
html[data-panel-pattern-section="mesh"] { --panel-pattern-image-section: var(--pattern-tile-mesh-image); --panel-pattern-size-section: var(--pattern-tile-mesh-size); }
html[data-panel-pattern-section="hex-sm"] { --panel-pattern-image-section: var(--pattern-tile-hex-sm-image); --panel-pattern-size-section: var(--pattern-tile-hex-sm-size); }
html[data-panel-pattern-section="dots-sm"] { --panel-pattern-image-section: var(--pattern-tile-dots-sm-image); --panel-pattern-size-section: var(--pattern-tile-dots-sm-size); }
html[data-panel-pattern-section="dots-lg"] { --panel-pattern-image-section: var(--pattern-tile-dots-lg-image); --panel-pattern-size-section: var(--pattern-tile-dots-lg-size); }
html[data-panel-pattern-section="tri-sm"] { --panel-pattern-image-section: var(--pattern-tile-tri-sm-image); --panel-pattern-size-section: var(--pattern-tile-tri-sm-size); }
html[data-panel-pattern-section="tri-lg"] { --panel-pattern-image-section: var(--pattern-tile-tri-lg-image); --panel-pattern-size-section: var(--pattern-tile-tri-lg-size); }
html[data-panel-pattern-section="diamond"] { --panel-pattern-image-section: var(--pattern-tile-diamond-image); --panel-pattern-size-section: var(--pattern-tile-diamond-size); }
html[data-panel-pattern-group="hex"] { --panel-pattern-image-group: var(--pattern-tile-hex-image); --panel-pattern-size-group: var(--pattern-tile-hex-size); }
html[data-panel-pattern-group="dots"] { --panel-pattern-image-group: var(--pattern-tile-dots-image); --panel-pattern-size-group: var(--pattern-tile-dots-size); }
html[data-panel-pattern-group="grid"] { --panel-pattern-image-group: var(--pattern-tile-grid-image); --panel-pattern-size-group: var(--pattern-tile-grid-size); }
html[data-panel-pattern-group="diagonal"] { --panel-pattern-image-group: var(--pattern-tile-diagonal-image); --panel-pattern-size-group: var(--pattern-tile-diagonal-size); }
html[data-panel-pattern-group="cross"] { --panel-pattern-image-group: var(--pattern-tile-cross-image); --panel-pattern-size-group: var(--pattern-tile-cross-size); }
html[data-panel-pattern-group="mesh"] { --panel-pattern-image-group: var(--pattern-tile-mesh-image); --panel-pattern-size-group: var(--pattern-tile-mesh-size); }
html[data-panel-pattern-group="hex-sm"] { --panel-pattern-image-group: var(--pattern-tile-hex-sm-image); --panel-pattern-size-group: var(--pattern-tile-hex-sm-size); }
html[data-panel-pattern-group="dots-sm"] { --panel-pattern-image-group: var(--pattern-tile-dots-sm-image); --panel-pattern-size-group: var(--pattern-tile-dots-sm-size); }
html[data-panel-pattern-group="dots-lg"] { --panel-pattern-image-group: var(--pattern-tile-dots-lg-image); --panel-pattern-size-group: var(--pattern-tile-dots-lg-size); }
html[data-panel-pattern-group="tri-sm"] { --panel-pattern-image-group: var(--pattern-tile-tri-sm-image); --panel-pattern-size-group: var(--pattern-tile-tri-sm-size); }
html[data-panel-pattern-group="tri-lg"] { --panel-pattern-image-group: var(--pattern-tile-tri-lg-image); --panel-pattern-size-group: var(--pattern-tile-tri-lg-size); }
html[data-panel-pattern-group="diamond"] { --panel-pattern-image-group: var(--pattern-tile-diamond-image); --panel-pattern-size-group: var(--pattern-tile-diamond-size); }
html[data-panel-pattern-item="hex"] { --panel-pattern-image-item: var(--pattern-tile-hex-image); --panel-pattern-size-item: var(--pattern-tile-hex-size); }
html[data-panel-pattern-item="dots"] { --panel-pattern-image-item: var(--pattern-tile-dots-image); --panel-pattern-size-item: var(--pattern-tile-dots-size); }
html[data-panel-pattern-item="grid"] { --panel-pattern-image-item: var(--pattern-tile-grid-image); --panel-pattern-size-item: var(--pattern-tile-grid-size); }
html[data-panel-pattern-item="diagonal"] { --panel-pattern-image-item: var(--pattern-tile-diagonal-image); --panel-pattern-size-item: var(--pattern-tile-diagonal-size); }
html[data-panel-pattern-item="cross"] { --panel-pattern-image-item: var(--pattern-tile-cross-image); --panel-pattern-size-item: var(--pattern-tile-cross-size); }
html[data-panel-pattern-item="mesh"] { --panel-pattern-image-item: var(--pattern-tile-mesh-image); --panel-pattern-size-item: var(--pattern-tile-mesh-size); }
html[data-panel-pattern-item="hex-sm"] { --panel-pattern-image-item: var(--pattern-tile-hex-sm-image); --panel-pattern-size-item: var(--pattern-tile-hex-sm-size); }
html[data-panel-pattern-item="dots-sm"] { --panel-pattern-image-item: var(--pattern-tile-dots-sm-image); --panel-pattern-size-item: var(--pattern-tile-dots-sm-size); }
html[data-panel-pattern-item="dots-lg"] { --panel-pattern-image-item: var(--pattern-tile-dots-lg-image); --panel-pattern-size-item: var(--pattern-tile-dots-lg-size); }
html[data-panel-pattern-item="tri-sm"] { --panel-pattern-image-item: var(--pattern-tile-tri-sm-image); --panel-pattern-size-item: var(--pattern-tile-tri-sm-size); }
html[data-panel-pattern-item="tri-lg"] { --panel-pattern-image-item: var(--pattern-tile-tri-lg-image); --panel-pattern-size-item: var(--pattern-tile-tri-lg-size); }
html[data-panel-pattern-item="diamond"] { --panel-pattern-image-item: var(--pattern-tile-diamond-image); --panel-pattern-size-item: var(--pattern-tile-diamond-size); }
/* Наборы акцентов. --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) 6px minmax(0, 1fr);
gap: 4px;
align-items: start;
}
html[data-nav-layout="sidebar"][data-show-resize-handles="false"] .app-layout {
grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
}
/* Компактная шапка: название + поиск, без дерева и 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;
display: flex;
flex-direction: column;
gap: 0;
}
html[data-nav-layout="top"] .side-panel .hero-content {
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
align-content: flex-start;
justify-content: flex-start;
gap: 12px 16px;
height: var(--top-bar-height);
min-height: var(--top-bar-height);
max-height: var(--top-bar-height);
overflow: auto;
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);
align-self: center;
padding-right: 0;
}
.side-panel .hero-content > .eyebrow#page-title {
padding-right: 46px;
line-height: 1.35;
}
html[data-nav-layout="top"] .search-field {
margin-top: 0;
flex: 1 1 220px;
max-width: min(100%, 560px);
}
html[data-show-tree="false"] .nav-tree {
display: none;
}
html[data-nav-layout="top"][data-show-tree="true"] .nav-tree {
flex: 1 1 100%;
width: 100%;
max-height: none;
min-height: 0;
overflow: auto;
margin-top: 4px;
padding-top: 10px;
border-top: 1px solid var(--border);
}
html[data-show-tree="false"] .side-panel .hero-content {
max-height: none;
}
.sidebar-resizer {
position: sticky;
top: 20px;
width: 6px;
height: calc(100vh - 40px);
margin: 0 -1px;
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;
}
/* ==========================================================================
04b. Top Bar Resizer
--------------------------------------------------------------------------
Горизонтальная ручка под шапкой в режиме navLayout: top.
========================================================================== */
.top-bar-resizer {
display: none;
width: 100%;
height: 8px;
margin-top: 8px;
margin-bottom: 6px;
border-radius: 999px;
cursor: row-resize;
background: transparent;
transition: background 160ms ease;
flex: 0 0 auto;
}
html[data-nav-layout="top"] .top-bar-resizer:not([hidden]) {
display: block;
}
.top-bar-resizer::before {
content: "";
display: block;
width: 100%;
height: 2px;
margin: 3px auto;
border-radius: inherit;
background: var(--border);
}
.top-bar-resizer:hover::before,
body.is-resizing-top-bar .top-bar-resizer::before {
background: var(--accent);
}
body.is-resizing-top-bar {
cursor: row-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);
position: relative;
overflow: hidden;
}
/*
* Разделение слоёв узоров: полупрозрачная подложка + backdrop-filter.
* JS: applyPatternIsolationPreference() → --panel-layer-blur / --panel-layer-opacity.
* Сильнее всего влияет на .catalog-section (доп. blur), затем group и .nav-card.
*/
.catalog-section,
.catalog-group,
.nav-card {
background:
linear-gradient(var(--panel-layer-fill), var(--panel-layer-fill)),
var(--panel);
}
.catalog-section {
backdrop-filter: blur(calc(14px + var(--panel-layer-blur)));
-webkit-backdrop-filter: blur(calc(14px + var(--panel-layer-blur)));
}
.catalog-group,
.nav-card {
backdrop-filter: blur(var(--panel-layer-blur));
-webkit-backdrop-filter: blur(var(--panel-layer-blur));
}
.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;
overflow: visible;
}
.side-panel .hero-content {
justify-content: flex-start;
overflow: auto;
}
/*
* Декоративные слои узора на блоках каталога и навигации.
* ::before — акцентный градиент (кроме .nav-card).
* ::after — повторяющаяся текстура; маска по высоте/переходу из CSS-переменных JS.
* Контент поверх узора: z-index: 1 у summary / .collapse-anim-body / детей .hero-content.
*/
/* Акcentный градиент под узором — отдельно для каждого типа блоков. */
html:not([data-panel-pattern-nav="none"]) .hero-content::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: linear-gradient(120deg, rgba(var(--accent-rgb), 1), transparent 38%);
opacity: var(--panel-gradient-nav-strength);
mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-gradient-nav-fade-start), transparent var(--panel-gradient-nav-height));
-webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-gradient-nav-fade-start), transparent var(--panel-gradient-nav-height));
pointer-events: none;
z-index: 0;
}
html:not([data-panel-pattern-section="none"]) .catalog-section::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: linear-gradient(120deg, rgba(var(--accent-rgb), 1), transparent 38%);
opacity: var(--panel-gradient-section-strength);
mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-gradient-section-fade-start), transparent var(--panel-gradient-section-height));
-webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-gradient-section-fade-start), transparent var(--panel-gradient-section-height));
pointer-events: none;
z-index: 0;
}
html:not([data-panel-pattern-group="none"]) .catalog-group::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: linear-gradient(120deg, rgba(var(--accent-rgb), 1), transparent 38%);
opacity: var(--panel-gradient-group-strength);
mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-gradient-group-fade-start), transparent var(--panel-gradient-group-height));
-webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-gradient-group-fade-start), transparent var(--panel-gradient-group-height));
pointer-events: none;
z-index: 0;
}
html:not([data-panel-pattern-nav="none"]) .hero-content::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
z-index: 0;
opacity: var(--panel-pattern-nav-opacity);
background-image: var(--panel-pattern-image-nav);
background-size: var(--panel-pattern-size-nav);
background-repeat: repeat;
mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-pattern-nav-fade-start), transparent var(--panel-pattern-nav-height));
-webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-pattern-nav-fade-start), transparent var(--panel-pattern-nav-height));
}
html:not([data-panel-pattern-section="none"]) .catalog-section::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
z-index: 0;
opacity: var(--panel-pattern-section-opacity);
background-image: var(--panel-pattern-image-section);
background-size: var(--panel-pattern-size-section);
background-repeat: repeat;
mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-pattern-section-fade-start), transparent var(--panel-pattern-section-height));
-webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-pattern-section-fade-start), transparent var(--panel-pattern-section-height));
}
html:not([data-panel-pattern-group="none"]) .catalog-group::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
z-index: 0;
opacity: var(--panel-pattern-group-opacity);
background-image: var(--panel-pattern-image-group);
background-size: var(--panel-pattern-size-group);
background-repeat: repeat;
mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-pattern-group-fade-start), transparent var(--panel-pattern-group-height));
-webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-pattern-group-fade-start), transparent var(--panel-pattern-group-height));
}
html:not([data-panel-pattern-item="none"]) .nav-card::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
z-index: 0;
opacity: var(--panel-pattern-item-opacity);
background-image: var(--panel-pattern-image-item);
background-size: var(--panel-pattern-size-item);
background-repeat: repeat;
mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-pattern-item-fade-start), transparent var(--panel-pattern-item-height));
-webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 var(--panel-pattern-item-fade-start), transparent var(--panel-pattern-item-height));
}
.catalog-section > summary,
.catalog-section > .section-groups,
.catalog-section > .collapse-anim-body,
.catalog-group > summary,
.catalog-group > .items-grid,
.catalog-group > .collapse-anim-body {
position: relative;
z-index: 1;
}
.hero-content > * {
position: relative;
z-index: 1;
}
/* Общий стиль маленьких 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;
}
/* Теги и тип в дереве управляются настройками showTreeTags / showTreeType. */
html[data-show-tree-tags="false"] .tree-tags {
display: none;
}
html[data-show-tree-type="false"] .tree-file-type {
display: none;
}
html[data-show-tree-tags="false"][data-show-tree-type="false"] .tree-file-meta {
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(var(--settings-panel-width), 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: center;
}
.settings-panel-eyebrow {
margin: 0;
flex: 1 1 auto;
min-width: 0;
}
.settings-close {
width: 34px;
height: 34px;
flex: 0 0 auto;
border-radius: 999px;
font-size: 1.25rem;
line-height: 1;
}
.settings-width-row {
grid-template-columns: minmax(0, 1fr) auto;
grid-template-rows: auto auto;
gap: 6px 10px;
}
.settings-width-row span {
grid-column: 1;
grid-row: 1;
}
.settings-width-row output {
grid-column: 2;
grid-row: 1;
}
.settings-width-row input[type="range"] {
grid-column: 1 / -1;
grid-row: 2;
}
.settings-section {
display: grid;
gap: 10px;
}
.settings-section[hidden],
.settings-section-collapsible[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;
}
.settings-section-hint code {
font-size: 0.78rem;
}
/* Footer popup ⚙: сброс / экспорт / импорт preferences (script.js → settings-actions). */
.settings-actions {
margin-top: 4px;
padding-top: 12px;
border-top: 1px solid var(--border);
display: grid;
gap: 10px;
}
.settings-actions-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.settings-action-button {
flex: 1 1 calc(33.333% - 6px);
min-width: 0;
min-height: 36px;
padding: 0 10px;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--surface);
color: var(--text);
font-size: 0.84rem;
font-weight: 800;
cursor: pointer;
transition: transform 160ms ease, background 160ms ease, border-color 160ms ease;
}
.settings-action-button:hover {
transform: translateY(-1px);
border-color: rgba(var(--accent-rgb), 0.5);
background: rgba(var(--accent-rgb), 0.12);
}
.settings-action-button[data-settings-reset] {
color: #ef4444;
}
.settings-action-button[data-settings-reset]:hover {
border-color: rgba(239, 68, 68, 0.45);
background: rgba(239, 68, 68, 0.12);
}
.catalog-columns-group {
display: grid;
gap: 8px;
}
.settings-section-collapsible {
border: 1px solid var(--border);
border-radius: 18px;
padding: 10px 12px;
background: var(--surface);
}
.settings-section-collapsible > summary {
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
cursor: pointer;
user-select: none;
}
.settings-section-collapsible > summary::-webkit-details-marker {
display: none;
}
.settings-section-summary .settings-section-title {
margin: 0;
}
.settings-section-summary:hover .collapse-indicator {
border-color: rgba(var(--accent-rgb), 0.42);
background: linear-gradient(160deg, rgba(var(--accent-rgb), 0.22), rgba(255, 255, 255, 0.05));
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.08),
0 8px 20px rgba(var(--accent-rgb), 0.12);
}
html[data-collapse-animation="instant"] [data-collapse-duration-row] {
display: none;
}
html[data-collapse-animation="animated"] .collapse-anim-body {
min-height: 0;
overflow: hidden;
}
.settings-section-body {
display: grid;
gap: 10px;
padding-top: 10px;
}
.layout-limit-row {
position: relative;
grid-template-columns: minmax(128px, 1.5fr) minmax(0, 2fr) auto;
padding: 8px 10px;
border: 1px solid var(--border);
border-radius: 14px;
background: rgba(255, 255, 255, 0.02);
cursor: help;
}
.layout-limit-row[data-tooltip]::after {
content: attr(data-tooltip);
position: absolute;
z-index: 30;
left: 8px;
right: 8px;
bottom: calc(100% + 6px);
top: auto;
padding: 8px 10px;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--panel-strong);
color: var(--text);
font-size: 0.78rem;
font-weight: 500;
line-height: 1.45;
box-shadow: var(--shadow);
opacity: 0;
visibility: hidden;
pointer-events: none;
transform: translateY(4px);
transition:
opacity 140ms ease,
visibility 140ms ease,
transform 140ms ease;
}
.layout-limit-row[data-tooltip]:hover::after,
.layout-limit-row[data-tooltip]:focus-within::after {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.layout-limit-row span {
line-height: 1.25;
}
/* ==========================================================================
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);
}
.pattern-group {
align-items: stretch;
}
/*
* Блок узоров в ⚙ → Оформление.
* .pattern-swatches — контейнер; кнопки генерирует renderPatternSwatches() в script.js.
* .pattern-custom-panel — поля «Свой» (URL + размер плитки), создаёт ensurePatternCustomPanels().
* .pattern-isolation-row — слайдер «Разделение слоёв».
*/
.pattern-targets-panel {
display: grid;
gap: 10px;
width: 100%;
}
.pattern-target-panel .settings-section-body {
display: grid;
gap: 10px;
padding-top: 10px;
}
.pattern-swatches {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px;
width: 100%;
}
.pattern-swatch {
min-height: 48px;
border: 1px solid var(--border);
border-radius: 14px;
background: var(--surface);
color: var(--muted);
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
cursor: pointer;
position: relative;
overflow: hidden;
padding: 0;
}
.pattern-swatch::before {
content: "";
position: absolute;
inset: 0;
opacity: 0.95;
pointer-events: none;
}
.pattern-swatch[data-panel-pattern="none"]::before {
background: linear-gradient(135deg, rgba(var(--accent-rgb), 0.12), transparent 70%);
}
.pattern-swatch[data-panel-pattern="hex"]::before {
background-image: 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='.18' 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");
background-size: 48px 42px;
}
html[data-theme="light"] .pattern-swatch[data-panel-pattern="hex"]::before {
background-image: 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='%23101827' stroke-opacity='.14' 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");
}
.pattern-swatch[data-panel-pattern="hex-sm"]::before {
background-image: var(--pattern-tile-hex-sm-image);
background-size: 24px 21px;
}
.pattern-swatch[data-panel-pattern="dots-sm"]::before {
background-image: var(--pattern-tile-dots-sm-image);
background-size: 8px 8px;
}
.pattern-swatch[data-panel-pattern="dots-lg"]::before {
background-image: var(--pattern-tile-dots-lg-image);
background-size: 18px 18px;
}
.pattern-swatch[data-panel-pattern="tri-sm"]::before {
background-image: var(--pattern-tile-tri-sm-image);
background-size: 16px 14px;
}
.pattern-swatch[data-panel-pattern="tri-lg"]::before {
background-image: var(--pattern-tile-tri-lg-image);
background-size: 28px 24px;
}
.pattern-swatch[data-panel-pattern="diamond"]::before {
background-image: var(--pattern-tile-diamond-image);
background-size: 16px 16px;
}
.pattern-swatch[data-panel-pattern="custom"]::before {
background:
linear-gradient(135deg, rgba(var(--accent-rgb), 0.18), transparent 62%),
repeating-linear-gradient(
-45deg,
transparent,
transparent 5px,
rgba(var(--accent-rgb), 0.12) 5px,
rgba(var(--accent-rgb), 0.12) 6px
);
}
.pattern-swatch[data-panel-pattern="custom"].has-custom-preview::before {
background-image: var(--pattern-swatch-preview);
background-size: 18px 18px;
}
.pattern-swatch[data-panel-pattern="dots"]::before {
background-image: radial-gradient(circle at center, var(--pattern-color) 0.95px, transparent 1px);
background-size: 12px 12px;
}
.pattern-swatch[data-panel-pattern="grid"]::before {
background-image:
linear-gradient(var(--pattern-color) 1px, transparent 1px),
linear-gradient(90deg, var(--pattern-color) 1px, transparent 1px);
background-size: 16px 16px;
}
.pattern-swatch[data-panel-pattern="diagonal"]::before {
background-image: repeating-linear-gradient(
-45deg,
transparent,
transparent 7px,
var(--pattern-color) 7px,
var(--pattern-color) 8px
);
}
.pattern-swatch[data-panel-pattern="cross"]::before {
background-image:
repeating-linear-gradient(
0deg,
transparent,
transparent 9px,
var(--pattern-color) 9px,
var(--pattern-color) 10px
),
repeating-linear-gradient(
90deg,
transparent,
transparent 9px,
var(--pattern-color) 9px,
var(--pattern-color) 10px
);
}
.pattern-swatch[data-panel-pattern="mesh"]::before {
background-image: radial-gradient(
circle at 1px 1px,
rgba(var(--accent-rgb), 0.24) 1px,
transparent 0
);
background-size: 12px 12px;
}
.pattern-swatch-label {
position: relative;
z-index: 1;
display: grid;
place-items: end start;
width: 100%;
height: 100%;
padding: 6px 8px;
background: linear-gradient(to top, rgba(0, 0, 0, 0.28), transparent 58%);
color: #f8fafc;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
html[data-theme="light"] .pattern-swatch-label {
background: linear-gradient(to top, rgba(255, 255, 255, 0.82), transparent 58%);
color: #101827;
text-shadow: none;
}
.pattern-swatch.is-active {
border-color: rgba(var(--accent-rgb), 0.72);
box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.18);
}
.pattern-tuning {
display: grid;
gap: 12px;
width: 100%;
}
.pattern-tuning .accent-custom-row {
grid-template-columns: minmax(0, 1fr) auto;
grid-template-rows: auto auto;
gap: 6px 10px;
}
.pattern-tuning .accent-custom-row span {
grid-column: 1;
grid-row: 1;
}
.pattern-tuning .accent-custom-row output {
grid-column: 2;
grid-row: 1;
}
.pattern-tuning .accent-custom-row input[type="range"] {
grid-column: 1 / -1;
grid-row: 2;
}
.pattern-isolation-row {
grid-template-columns: minmax(120px, 1fr) minmax(0, 1.4fr) auto;
}
.pattern-custom-panel {
display: grid;
gap: 8px;
padding: 10px;
border: 1px solid var(--border);
border-radius: 18px;
background: var(--surface);
}
.pattern-custom-panel[hidden] {
display: none;
}
.pattern-custom-panel input[type="url"],
.pattern-custom-panel input[type="text"] {
width: 100%;
min-height: 34px;
padding: 6px 10px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--panel);
color: var(--text);
font: inherit;
font-size: 0.82rem;
}
.pattern-custom-image-row {
grid-template-columns: minmax(72px, 0.8fr) minmax(0, 1.6fr);
}
.pattern-custom-image-row input[type="url"],
.pattern-custom-image-row input[type="text"] {
width: 100%;
min-height: 34px;
padding: 6px 10px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--panel);
color: var(--text);
font: inherit;
font-size: 0.82rem;
}
/* Панель настройки своего градиента появляется при 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: 22px minmax(0, 1fr);
gap: 6px;
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: 22px;
height: 22px;
display: grid;
place-items: center;
border-radius: 8px;
color: var(--muted);
background: rgba(var(--accent-rgb), 0.06);
border: 1px solid transparent;
transition:
background 140ms ease,
border-color 140ms ease,
color 140ms ease;
}
.tree-section > summary:hover .tree-caret,
.tree-group > summary:hover .tree-caret {
color: var(--accent);
border-color: rgba(var(--accent-rgb), 0.18);
background: rgba(var(--accent-rgb), 0.12);
}
.tree-caret::before {
content: "";
display: block;
width: 13px;
height: 13px;
background: currentColor;
mask: url("data:image/svg+xml,%3Csvg width='13' height='13' viewBox='0 0 13 13' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3.5 5 6.5 8 9.5 5' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
-webkit-mask: url("data:image/svg+xml,%3Csvg width='13' height='13' viewBox='0 0 13 13' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3.5 5 6.5 8 9.5 5' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
transform: rotate(0deg);
transform-origin: center;
transition: transform var(--collapse-duration, 240ms) cubic-bezier(0.4, 0, 0.2, 1);
}
.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: 20px minmax(0, 1fr);
align-items: start;
gap: 8px;
}
.tree-file::before {
content: none;
}
.tree-file-body {
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.tree-file-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tree-file-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
min-width: 0;
}
html[data-tree-meta-layout="inline"] .tree-file-body {
flex-direction: row;
align-items: center;
gap: 8px;
}
html[data-tree-meta-layout="inline"] .tree-file-title {
flex: 1 1 auto;
}
html[data-tree-meta-layout="inline"] .tree-file-meta {
flex: 0 1 auto;
justify-content: flex-end;
}
html[data-tree-meta-layout="stacked"] .tree-file-body {
align-items: stretch;
}
.tree-file-type {
margin: 0;
color: var(--accent);
font-size: 0.72rem;
font-weight: 750;
white-space: nowrap;
}
.tree-tags.tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 0;
}
.tree-tags.tags span {
border: 1px solid var(--border);
border-radius: 999px;
padding: 2px 6px;
background: rgba(var(--accent-rgb), 0.1);
color: var(--muted);
font-size: 0.68rem;
font-weight: 800;
}
html:not([data-tree-columns="1"]) .tree-file {
font-size: 0.8rem;
}
html:not([data-tree-columns="1"]) .tree-node-icon {
width: 16px;
height: 16px;
border-radius: 4px;
}
.tree-node-icon {
width: 18px;
height: 18px;
border-radius: 5px;
object-fit: cover;
display: block;
}
/* Контейнер элементов внутри группы дерева. */
.tree-items {
position: relative;
display: grid;
grid-template-columns: repeat(var(--tree-columns, 1), minmax(0, 1fr));
gap: 2px 8px;
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);
}
/* ==========================================================================
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;
}
/* Стрелка сворачивания — аккуратный chevron в стеклянной кнопке. */
.collapse-indicator {
position: relative;
flex-shrink: 0;
width: 36px;
height: 36px;
display: grid;
place-items: center;
border-radius: 12px;
border: 1px solid rgba(var(--accent-rgb), 0.22);
background: linear-gradient(160deg, rgba(var(--accent-rgb), 0.14), rgba(255, 255, 255, 0.03));
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
color: var(--accent);
font-size: 0;
transition:
border-color 160ms ease,
background 160ms ease,
box-shadow 160ms ease,
transform 160ms ease;
}
.collapse-indicator::before {
content: "";
display: block;
width: 16px;
height: 16px;
background: currentColor;
mask: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.25 6.25 8 10l3.75-3.75' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
-webkit-mask: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.25 6.25 8 10l3.75-3.75' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
transform: rotate(0deg);
transform-origin: center;
transition: transform var(--collapse-duration, 240ms) cubic-bezier(0.4, 0, 0.2, 1);
}
.catalog-section:not([open]) > .section-head .collapse-indicator::before,
.catalog-group:not([open]) > .group-head .collapse-indicator::before,
.settings-section-collapsible:not([open]) > .settings-section-summary .collapse-indicator::before {
transform: rotate(-90deg);
}
.section-head:hover .collapse-indicator,
.group-head:hover .collapse-indicator {
border-color: rgba(var(--accent-rgb), 0.42);
background: linear-gradient(160deg, rgba(var(--accent-rgb), 0.22), rgba(255, 255, 255, 0.05));
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.08),
0 8px 20px rgba(var(--accent-rgb), 0.12);
}
html[data-theme="light"] .collapse-indicator {
background: linear-gradient(160deg, rgba(var(--accent-rgb), 0.1), rgba(255, 255, 255, 0.72));
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85);
}
.catalog-section:not([open]) > .section-head .collapse-indicator,
.catalog-group:not([open]) > .group-head .collapse-indicator {
transform: none;
}
/* Подразделы внутри секции. */
.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 {
position: relative;
border: 1px solid var(--border);
border-radius: 18px;
background:
linear-gradient(var(--panel-layer-fill), var(--panel-layer-fill)),
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;
gap: 10px;
padding: 10px;
}
.catalog.cards-view .items-grid {
grid-template-columns: repeat(var(--catalog-cards-columns, 3), minmax(0, 1fr));
}
/* Базовая карточка с иконкой, контентом, тегами, стрелкой и 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:
linear-gradient(var(--panel-layer-fill), var(--panel-layer-fill)),
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;
z-index: 1;
}
/* Hover задается классом .is-hovered из JS, чтобы убрать скачки на границах. */
.nav-card.is-hovered,
.nav-card:focus-visible {
border-color: rgba(var(--accent-rgb), 0.52);
background:
linear-gradient(var(--panel-layer-fill), var(--panel-layer-fill)),
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 {
position: relative;
z-index: 1;
width: 50px;
height: 50px;
object-fit: contain;
filter: drop-shadow(0 14px 24px rgba(0, 0, 0, 0.22));
}
.card-content {
position: relative;
z-index: 1;
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;
z-index: 1;
width: 36px;
height: 36px;
display: grid;
place-items: center;
border-radius: 12px;
border: 1px solid rgba(var(--accent-rgb), 0.18);
background: linear-gradient(160deg, rgba(var(--accent-rgb), 0.12), rgba(255, 255, 255, 0.03));
color: var(--accent);
font-size: 0;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
transition:
transform 180ms ease,
background 180ms ease,
color 180ms ease,
border-color 180ms ease,
box-shadow 180ms ease;
}
.arrow::before {
content: "";
display: block;
width: 16px;
height: 16px;
background: currentColor;
mask: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 4 10 8 6 12' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
-webkit-mask: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6 4 10 8 6 12' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
.nav-card.is-hovered .arrow,
.nav-card:focus-visible .arrow {
transform: translateX(3px);
border-color: rgba(var(--accent-rgb), 0.35);
background: linear-gradient(var(--accent-angle), var(--accent), var(--accent-2));
color: white;
box-shadow: 0 10px 24px rgba(var(--accent-rgb), 0.24);
}
/* Декоративный shine слой, положение которого обновляется pointermove. */
.shine {
position: absolute;
inset: 0;
z-index: 2;
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: repeat(var(--catalog-list-columns, 1), minmax(0, 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(var(--catalog-compact-cards-columns, 5), minmax(0, 1fr));
}
/* Compact list на широких экранах использует настраиваемое число колонок. */
.catalog.compact-list-view .items-grid {
grid-template-columns: repeat(var(--catalog-compact-list-columns, 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)) 6px minmax(0, 1fr);
}
.catalog.cards-view .items-grid {
grid-template-columns: repeat(min(var(--catalog-cards-columns, 3), 2), minmax(0, 1fr));
}
.catalog.compact-cards-view .items-grid {
grid-template-columns: repeat(min(var(--catalog-compact-cards-columns, 5), 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,
.top-bar-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;
}
.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;
}
.catalog.cards-view .items-grid {
grid-template-columns: repeat(min(var(--catalog-cards-columns, 3), 1), minmax(0, 1fr));
padding: 8px;
}
.catalog.list-view .items-grid {
grid-template-columns: repeat(min(var(--catalog-list-columns, 1), 1), minmax(0, 1fr));
padding: 8px;
}
.catalog.compact-list-view .items-grid {
grid-template-columns: repeat(min(var(--catalog-compact-list-columns, 3), 1), minmax(0, 1fr));
padding: 8px;
}
.catalog.compact-cards-view .items-grid {
grid-template-columns: repeat(min(var(--catalog-compact-cards-columns, 5), 2), minmax(0, 1fr));
padding: 8px;
}
.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;
}
}