Fix page workspace controls and glass overlays

This commit is contained in:
DCCONSTRUCTIONS 2026-06-28 18:33:58 +03:00
parent 2777b69eb3
commit 7be6104b6b
4 changed files with 129 additions and 79 deletions

View File

@ -6,8 +6,9 @@
- База: светлая Apple-like система, много воздуха, мягкие панели, скругления и спокойный серый фон.
- Главный акцент: тёмный neutral active `#202124 -> #111113`, как у активного этапа и основных submit/action-кнопок.
- Вторичный слой: white glass surfaces/controls через `--nodedc-glass-*`, без отдельных цветных акцентов.
- Вторичный слой: white glass surfaces/controls через `--nodedc-glass-*`, без отдельных цветных акцентов. Glass-панели должны оставаться прозрачными: материал строится на низкой alpha-подложке, `backdrop-filter` и outline-gradient, а не на глухой белой заливке.
- Избегаем случайной зелени/оранжевого/голубого/фиолета, если это не специально введённый статус.
- Точки ошибок в preview-сайте: розовый marker `#ff4fb8`, активный `#d81b7d`.
## Кнопки

View File

@ -1059,7 +1059,7 @@ type WorkspaceTarget = {
};
type WorkspacePreviewMode = "desktop" | "mobile";
type WorkspaceDrawerPanel = "fields" | "issues" | "scope";
type WorkspaceDrawerPanel = "fields" | "issues";
const WORKSPACE_PREVIEW_MODES: Array<{ id: WorkspacePreviewMode; label: string }> = [
{ id: "desktop", label: "Desktop" },
@ -3522,7 +3522,11 @@ export function App() {
null;
const activeWorkspaceMarkers = activeWorkspaceKey ? (workspacePreviewMarkers[activeWorkspaceKey] ?? []) : [];
const activeWorkspacePreviewMode = activeWorkspaceKey ? (workspacePreviewModes[activeWorkspaceKey] ?? "desktop") : "desktop";
const activeWorkspaceDrawers = activeWorkspaceKey ? (workspaceDrawers[activeWorkspaceKey] ?? []) : [];
const activeWorkspaceDrawers = activeWorkspaceKey
? (workspaceDrawers[activeWorkspaceKey] ?? []).filter(
(drawer): drawer is WorkspaceDrawerPanel => drawer === "fields" || drawer === "issues"
)
: [];
const isWorkspacePageMenuOpen = activeWorkspaceKey ? Boolean(workspacePageMenus[activeWorkspaceKey]) : false;
const previewWorkspaceScopeId = workspacePreviewScopeIds[project.id] ?? activeWorkspaceScopeItem?.scopeId ?? null;
const previewWorkspaceScopeItem =
@ -4775,7 +4779,7 @@ export function App() {
<div className="workspace-current-page">
<div>
<span>
Активная зона {activeWorkspacePageIndex + 1}/{selectedWorkspaceScopeItems.length} · выбранный scope
Активная страница {activeWorkspacePageIndex + 1}/{selectedWorkspaceScopeItems.length}
</span>
<strong>{activeWorkspaceScopeItem.title}</strong>
<small>{activeWorkspaceScopeItem.pathLabel}</small>
@ -4785,7 +4789,7 @@ export function App() {
<div className="workspace-cockpit-toolbar">
<div className="workspace-preview-status">
<span>Замечаний на активной странице: {activeWorkspaceIssues.length}</span>
<span>В выбранном scope: {selectedCoverageIssueCount}</span>
<span>В выбранных страницах: {selectedCoverageIssueCount}</span>
<span>Warning: {activeWorkspaceIssueCounts.warnings} · Info: {activeWorkspaceIssueCounts.info}</span>
</div>
<div className="workspace-current-page-actions">
@ -4807,7 +4811,7 @@ export function App() {
{isWorkspacePageMenuOpen ? (
<div className="workspace-page-menu" role="menu">
<div className="workspace-page-menu-head">
<strong>Страницы и секции scope</strong>
<strong>Страницы и секции</strong>
<small>Переключает preview, ошибки и поля рабочей зоны.</small>
</div>
{selectedWorkspaceScopeItems.length > 0 ? (
@ -4826,7 +4830,8 @@ export function App() {
onClick={() => {
setWorkspacePageMenus((currentMenus) => ({
...currentMenus,
[activeWorkspaceKey]: false
[activeWorkspaceKey]: false,
[pageWorkspaceKey]: true
}));
void handleOpenPageWorkspace(project, scopeItem, { silent: true });
}}
@ -4843,21 +4848,12 @@ export function App() {
</div>
) : (
<small className="workspace-page-menu-empty">
В выбранном scope пока нет рабочих страниц или секций.
В выбранном наборе пока нет рабочих страниц или секций.
</small>
)}
</div>
) : null}
</div>
<button
className={activeWorkspaceDrawers.includes("scope") ? "active" : undefined}
onClick={() => toggleWorkspaceDrawer(activeWorkspaceKey, "scope")}
type="button"
>
<FileText size={14} />
Scope
<em>{selectedWorkspaceScopeItems.length}</em>
</button>
<button
className={activeWorkspaceDrawers.includes("issues") ? "active" : undefined}
onClick={() => toggleWorkspaceDrawer(activeWorkspaceKey, "issues")}
@ -4958,25 +4954,17 @@ export function App() {
<div className="workspace-drawer-header">
<div>
<span>
{activeWorkspaceDrawer === "scope"
? "Выбранный scope"
: activeWorkspaceDrawer === "fields"
? "Поля для правки"
: "Аудит страницы"}
{activeWorkspaceDrawer === "fields" ? "Поля для правки" : "Аудит страницы"}
</span>
<strong>
{activeWorkspaceDrawer === "scope"
? `${selectedWorkspaceScopeItems.length} в scope`
: activeWorkspaceDrawer === "fields"
? `${activeWorkspaceTargets.length} полей`
: `${activeWorkspaceIssues.length} замечаний`}
{activeWorkspaceDrawer === "fields"
? `${activeWorkspaceTargets.length} полей`
: `${activeWorkspaceIssues.length} замечаний`}
</strong>
<small>
{activeWorkspaceDrawer === "scope"
? "Дальше анализируются только выбранные страницы и секции."
: activeWorkspaceDrawer === "fields"
? "Клик по точке или карточке открывает связанное поле."
: "Список относится к активной странице, не ко всему сайту."}
{activeWorkspaceDrawer === "fields"
? "Клик по точке или карточке открывает связанное поле."
: "Список относится к активной странице, не ко всему сайту."}
</small>
</div>
<button
@ -4988,31 +4976,7 @@ export function App() {
<X size={16} />
</button>
</div>
{activeWorkspaceDrawer === "scope" ? (
<div className="workspace-scope-list">
{selectedWorkspaceScopeItems.map((scopeItem, pageIndex) => {
const pageWorkspaceKey = getWorkspaceKey(project.id, scopeItem.scopeId);
const isOpening = busyWorkspaceKey === pageWorkspaceKey;
const isActivePage = activeWorkspaceScopeItem?.scopeId === scopeItem.scopeId;
const pageIssues = getPageIssues(scanSummary, scopeItem.pageId);
return (
<button
className={isActivePage ? "active" : undefined}
disabled={isOpening}
key={`workspace-scope-${scopeItem.scopeId}`}
onClick={() => void handleOpenPageWorkspace(project, scopeItem)}
type="button"
>
<span>{pageIndex + 1}</span>
<strong>{scopeItem.title}</strong>
<small>{scopeItem.pathLabel}</small>
<em>{pageIssues.length} замечаний</em>
</button>
);
})}
</div>
) : activeWorkspaceDrawer === "fields" ? (
{activeWorkspaceDrawer === "fields" ? (
<div className="workspace-fields-drawer">
{activeWorkspaceTargets.length > 0 ? (
activeWorkspaceTargets.map((target) => {

View File

@ -15,25 +15,25 @@
--accent-soft: rgba(0, 0, 0, 0.08);
--warning-bg: #e8e8e8;
--danger-bg: #d8d8d8;
--nodedc-glass-panel-bg: rgba(255, 255, 255, 0.76);
--nodedc-glass-panel-bg-strong: rgba(255, 255, 255, 0.9);
--nodedc-glass-panel-bg-soft: rgba(255, 255, 255, 0.6);
--nodedc-glass-panel-surface: linear-gradient(180deg, rgba(255, 255, 255, 0.76) 0%, rgba(255, 255, 255, 0.44) 100%), var(--nodedc-glass-panel-bg);
--nodedc-glass-panel-surface-strong: linear-gradient(180deg, rgba(255, 255, 255, 0.86) 0%, rgba(255, 255, 255, 0.52) 100%), var(--nodedc-glass-panel-bg-strong);
--nodedc-glass-section-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.58) 0%, rgba(255, 255, 255, 0.28) 100%), rgba(244, 244, 246, 0.62);
--nodedc-glass-control-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.68) 0%, rgba(255, 255, 255, 0.36) 100%), rgba(244, 244, 246, 0.74);
--nodedc-glass-control-hover: linear-gradient(180deg, rgba(255, 255, 255, 0.78) 0%, rgba(255, 255, 255, 0.44) 100%), rgba(232, 232, 235, 0.82);
--nodedc-glass-panel-bg: rgba(255, 255, 255, 0.18);
--nodedc-glass-panel-bg-strong: rgba(255, 255, 255, 0.26);
--nodedc-glass-panel-bg-soft: rgba(255, 255, 255, 0.12);
--nodedc-glass-panel-surface: linear-gradient(180deg, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0.1) 100%), var(--nodedc-glass-panel-bg);
--nodedc-glass-panel-surface-strong: linear-gradient(180deg, rgba(255, 255, 255, 0.38) 0%, rgba(255, 255, 255, 0.14) 100%), var(--nodedc-glass-panel-bg-strong);
--nodedc-glass-section-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.22) 0%, rgba(255, 255, 255, 0.08) 100%), rgba(255, 255, 255, 0.12);
--nodedc-glass-control-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.24) 0%, rgba(255, 255, 255, 0.08) 100%), rgba(255, 255, 255, 0.12);
--nodedc-glass-control-hover: linear-gradient(180deg, rgba(255, 255, 255, 0.34) 0%, rgba(255, 255, 255, 0.12) 100%), rgba(255, 255, 255, 0.2);
--nodedc-glass-control-active: linear-gradient(180deg, rgba(32, 32, 34, 0.98), rgba(20, 20, 22, 0.96));
--nodedc-glass-outline: rgba(24, 32, 29, 0.1);
--nodedc-glass-outline-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.86), rgba(24, 32, 29, 0.08));
--nodedc-glass-panel-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.75), 0 22px 72px rgba(24, 32, 29, 0.16);
--nodedc-glass-control-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
--nodedc-glass-blur: blur(44px) saturate(1.12);
--nodedc-glass-panel-blur: blur(64px) saturate(1.24) brightness(1.02);
--nodedc-glass-outline-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.34) 28%, rgba(24, 32, 29, 0.1) 100%);
--nodedc-glass-panel-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.42), 0 18px 60px rgba(24, 32, 29, 0.14);
--nodedc-glass-control-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.34);
--nodedc-glass-blur: blur(24px) saturate(128%) brightness(1.05);
--nodedc-glass-panel-blur: blur(64px) saturate(150%) brightness(1.08);
--nodedc-header-dropdown-bg: var(--nodedc-glass-panel-surface-strong);
--nodedc-header-dropdown-item-bg: var(--nodedc-glass-control-bg);
--nodedc-header-dropdown-item-hover-bg: var(--nodedc-glass-control-hover);
--nodedc-header-dropdown-item-active-bg: rgba(24, 32, 29, 0.09);
--nodedc-header-dropdown-item-active-bg: rgba(255, 255, 255, 0.24);
--nodedc-header-dropdown-item-active-color: var(--ink);
font-family: "Aptos", "Segoe UI", sans-serif;
font-synthesis: none;
@ -1362,10 +1362,10 @@ input {
place-items: center;
padding: 0;
color: #fff;
background: var(--accent);
background: #ff4fb8;
border: 2px solid rgba(255, 255, 255, 0.92);
border-radius: 999px;
box-shadow: 0 8px 18px rgba(242, 122, 26, 0.24);
box-shadow: 0 8px 18px rgba(255, 79, 184, 0.3);
cursor: pointer;
font-size: 12px;
font-weight: 750;
@ -1374,8 +1374,8 @@ input {
}
.workspace-preview-markers button.active {
background: var(--ink);
box-shadow: 0 0 0 4px rgba(242, 122, 26, 0.18);
background: #d81b7d;
box-shadow: 0 0 0 4px rgba(255, 79, 184, 0.18);
}
.workspace-subheading {
@ -7440,12 +7440,14 @@ input[type="checkbox"] {
}
.stage-3 .workspace-preview-markers button {
background: var(--accent) !important;
background: #ff4fb8 !important;
box-shadow: 0 8px 18px rgba(255, 79, 184, 0.3) !important;
transform: none !important;
}
.stage-3 .workspace-preview-markers button.active {
background: var(--ink) !important;
background: #d81b7d !important;
box-shadow: 0 0 0 4px rgba(255, 79, 184, 0.18) !important;
}
.stage-6 .project-setup,
@ -7566,6 +7568,89 @@ input[type="checkbox"] {
box-shadow: var(--nodedc-glass-panel-shadow) !important;
backdrop-filter: var(--nodedc-glass-panel-blur) !important;
-webkit-backdrop-filter: var(--nodedc-glass-panel-blur) !important;
isolation: isolate;
}
.workspace-page-menu,
.workspace-drawer,
.semantic-panel,
.semantic-page-plan-panel,
.semantic-brief-panel,
.semantic-landing-panel,
.semantic-verdict-panel,
.semantic-warning-list,
.semantic-gap-panel,
.semantic-seeds,
.semantic-ai-contract,
.semantic-style-panel,
.semantic-action-list,
.semantic-cluster-detail,
.market-panel,
.market-heading,
.market-state-card,
.market-wordstat-card,
.market-provider,
.market-columns section,
.market-next-actions {
overflow: hidden !important;
}
.semantic-panel,
.semantic-page-plan-panel,
.semantic-brief-panel,
.semantic-landing-panel,
.semantic-verdict-panel,
.semantic-warning-list,
.semantic-gap-panel,
.semantic-seeds,
.semantic-ai-contract,
.semantic-style-panel,
.semantic-action-list,
.semantic-cluster-detail,
.market-panel,
.market-heading,
.market-state-card,
.market-wordstat-card,
.market-provider,
.market-columns section,
.market-next-actions {
position: relative;
}
.workspace-page-menu::after,
.workspace-drawer::after,
.semantic-panel::after,
.semantic-page-plan-panel::after,
.semantic-brief-panel::after,
.semantic-landing-panel::after,
.semantic-verdict-panel::after,
.semantic-warning-list::after,
.semantic-gap-panel::after,
.semantic-seeds::after,
.semantic-ai-contract::after,
.semantic-style-panel::after,
.semantic-action-list::after,
.semantic-cluster-detail::after,
.market-panel::after,
.market-heading::after,
.market-state-card::after,
.market-wordstat-card::after,
.market-provider::after,
.market-columns section::after,
.market-next-actions::after {
content: "";
position: absolute;
inset: 0;
z-index: 2;
padding: 1px;
pointer-events: none;
border-radius: inherit;
background: var(--nodedc-glass-outline-gradient);
-webkit-mask:
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
}
.workspace-drawer-header,

View File

@ -523,10 +523,10 @@ function buildPreviewBridgeScript() {
marker.style.placeItems = "center";
marker.style.padding = "0";
marker.style.color = "#fff";
marker.style.background = "#202124";
marker.style.background = "#ff4fb8";
marker.style.border = "2px solid rgba(255,255,255,.95)";
marker.style.borderRadius = "999px";
marker.style.boxShadow = "0 6px 16px rgba(24,32,29,.24)";
marker.style.boxShadow = "0 6px 16px rgba(255,79,184,.32)";
marker.style.cursor = "pointer";
marker.style.font = "700 12px/1 Inter, Arial, sans-serif";
marker.style.pointerEvents = "auto";