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

View File

@ -15,25 +15,25 @@
--accent-soft: rgba(0, 0, 0, 0.08); --accent-soft: rgba(0, 0, 0, 0.08);
--warning-bg: #e8e8e8; --warning-bg: #e8e8e8;
--danger-bg: #d8d8d8; --danger-bg: #d8d8d8;
--nodedc-glass-panel-bg: rgba(255, 255, 255, 0.76); --nodedc-glass-panel-bg: rgba(255, 255, 255, 0.18);
--nodedc-glass-panel-bg-strong: rgba(255, 255, 255, 0.9); --nodedc-glass-panel-bg-strong: rgba(255, 255, 255, 0.26);
--nodedc-glass-panel-bg-soft: rgba(255, 255, 255, 0.6); --nodedc-glass-panel-bg-soft: rgba(255, 255, 255, 0.12);
--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: 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.86) 0%, rgba(255, 255, 255, 0.52) 100%), var(--nodedc-glass-panel-bg-strong); --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.58) 0%, rgba(255, 255, 255, 0.28) 100%), rgba(244, 244, 246, 0.62); --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.68) 0%, rgba(255, 255, 255, 0.36) 100%), rgba(244, 244, 246, 0.74); --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.78) 0%, rgba(255, 255, 255, 0.44) 100%), rgba(232, 232, 235, 0.82); --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-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: 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-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.75), 0 22px 72px rgba(24, 32, 29, 0.16); --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.7); --nodedc-glass-control-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.34);
--nodedc-glass-blur: blur(44px) saturate(1.12); --nodedc-glass-blur: blur(24px) saturate(128%) brightness(1.05);
--nodedc-glass-panel-blur: blur(64px) saturate(1.24) brightness(1.02); --nodedc-glass-panel-blur: blur(64px) saturate(150%) brightness(1.08);
--nodedc-header-dropdown-bg: var(--nodedc-glass-panel-surface-strong); --nodedc-header-dropdown-bg: var(--nodedc-glass-panel-surface-strong);
--nodedc-header-dropdown-item-bg: var(--nodedc-glass-control-bg); --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-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); --nodedc-header-dropdown-item-active-color: var(--ink);
font-family: "Aptos", "Segoe UI", sans-serif; font-family: "Aptos", "Segoe UI", sans-serif;
font-synthesis: none; font-synthesis: none;
@ -1362,10 +1362,10 @@ input {
place-items: center; place-items: center;
padding: 0; padding: 0;
color: #fff; color: #fff;
background: var(--accent); background: #ff4fb8;
border: 2px solid rgba(255, 255, 255, 0.92); border: 2px solid rgba(255, 255, 255, 0.92);
border-radius: 999px; 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; cursor: pointer;
font-size: 12px; font-size: 12px;
font-weight: 750; font-weight: 750;
@ -1374,8 +1374,8 @@ input {
} }
.workspace-preview-markers button.active { .workspace-preview-markers button.active {
background: var(--ink); background: #d81b7d;
box-shadow: 0 0 0 4px rgba(242, 122, 26, 0.18); box-shadow: 0 0 0 4px rgba(255, 79, 184, 0.18);
} }
.workspace-subheading { .workspace-subheading {
@ -7440,12 +7440,14 @@ input[type="checkbox"] {
} }
.stage-3 .workspace-preview-markers button { .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; transform: none !important;
} }
.stage-3 .workspace-preview-markers button.active { .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, .stage-6 .project-setup,
@ -7566,6 +7568,89 @@ input[type="checkbox"] {
box-shadow: var(--nodedc-glass-panel-shadow) !important; box-shadow: var(--nodedc-glass-panel-shadow) !important;
backdrop-filter: var(--nodedc-glass-panel-blur) !important; backdrop-filter: var(--nodedc-glass-panel-blur) !important;
-webkit-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, .workspace-drawer-header,

View File

@ -523,10 +523,10 @@ function buildPreviewBridgeScript() {
marker.style.placeItems = "center"; marker.style.placeItems = "center";
marker.style.padding = "0"; marker.style.padding = "0";
marker.style.color = "#fff"; marker.style.color = "#fff";
marker.style.background = "#202124"; marker.style.background = "#ff4fb8";
marker.style.border = "2px solid rgba(255,255,255,.95)"; marker.style.border = "2px solid rgba(255,255,255,.95)";
marker.style.borderRadius = "999px"; 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.cursor = "pointer";
marker.style.font = "700 12px/1 Inter, Arial, sans-serif"; marker.style.font = "700 12px/1 Inter, Arial, sans-serif";
marker.style.pointerEvents = "auto"; marker.style.pointerEvents = "auto";