@sleep2agi/agent-network-dashboard 0.5.3-preview.12 → 0.5.3-preview.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +32 -32
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +12 -12
- package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +2 -2
- package/.next/server/app/admin.rsc +14 -14
- package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
- package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
- package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +14 -14
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +7 -7
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +14 -14
- package/.next/server/app/login.segments/_full.segment.rsc +14 -14
- package/.next/server/app/login.segments/_head.segment.rsc +4 -4
- package/.next/server/app/login.segments/_index.segment.rsc +7 -7
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/login.segments/login.segment.rsc +3 -3
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +2 -2
- package/.next/server/app/logs.rsc +14 -14
- package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +2 -2
- package/.next/server/app/messages.rsc +14 -14
- package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
- package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
- package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +2 -2
- package/.next/server/app/node.rsc +14 -14
- package/.next/server/app/node.segments/_full.segment.rsc +14 -14
- package/.next/server/app/node.segments/_head.segment.rsc +4 -4
- package/.next/server/app/node.segments/_index.segment.rsc +7 -7
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/node.segments/node.segment.rsc +3 -3
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +2 -2
- package/.next/server/app/nodes.rsc +14 -14
- package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
- package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +2 -2
- package/.next/server/app/server-logs.rsc +14 -14
- package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +2 -2
- package/.next/server/app/settings/networks.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +2 -2
- package/.next/server/app/settings/tokens.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +14 -14
- package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +2 -2
- package/.next/server/app/tasks.rsc +14 -14
- package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +4 -4
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/05-~ezd16iskp.css +2 -0
- package/.next/static/chunks/0_i2rayo5ufic.js +4 -0
- package/.next/static/chunks/{0f9~w1b6yn1dj.js → 0c7o0bzcd~3vc.js} +1 -1
- package/.next/static/chunks/{03a4--7ncekmk.js → 0v4-5tng.uh.7.js} +2 -2
- package/.next/static/chunks/14lqafir29vbd.js +1 -0
- package/.next/static/chunks/150r~1iole-z1.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/ServersDrawer.tsx +16 -3
- package/app/components/TopoGraph.tsx +3237 -138
- package/app/globals.css +58 -7
- package/package.json +4 -4
- package/scripts/p157-servers-copy-test.mjs +95 -0
- package/scripts/topo-active-chrome-hover-text-test.mjs +107 -0
- package/scripts/topo-alias-glow-test.mjs +121 -0
- package/scripts/topo-avatar-brightness-test.mjs +116 -0
- package/scripts/topo-avatar-drop-shadow-test.mjs +86 -0
- package/scripts/topo-avatar-fallback-hover-test.mjs +104 -0
- package/scripts/topo-avatar-fallback-rotate-test.mjs +92 -0
- package/scripts/topo-avatar-rotate-test.mjs +85 -0
- package/scripts/topo-avatar-scale-test.mjs +89 -0
- package/scripts/topo-brand-drop-shadow-test.mjs +71 -0
- package/scripts/topo-brand-logo-breath-test.mjs +102 -0
- package/scripts/topo-brand-logo-hover-brightness-test.mjs +105 -0
- package/scripts/topo-brand-logo-hover-rotate-test.mjs +93 -0
- package/scripts/topo-brand-logo-hover-test.mjs +85 -0
- package/scripts/topo-chip-row-digit-ls-test.mjs +135 -0
- package/scripts/topo-chip-row-member-alias-lit-test.mjs +154 -0
- package/scripts/topo-chip-row-tier-glow-brightness-test.mjs +99 -0
- package/scripts/topo-chip-row-unit-hover-tracking-test.mjs +124 -0
- package/scripts/topo-cluster-count-attr-test.mjs +80 -0
- package/scripts/topo-crescent-breath-test.mjs +104 -0
- package/scripts/topo-crescent-recede-test.mjs +111 -0
- package/scripts/topo-edge-badge-circle-brightness-test.mjs +82 -0
- package/scripts/topo-edge-badge-hover-glow-test.mjs +90 -0
- package/scripts/topo-edge-badge-text-brightness-test.mjs +83 -0
- package/scripts/topo-edge-particle-brightness-test.mjs +82 -0
- package/scripts/topo-edge-pill-glow-test.mjs +67 -0
- package/scripts/topo-edge-visible-brightness-test.mjs +84 -0
- package/scripts/topo-endpoint-ring-brightness-test.mjs +83 -0
- package/scripts/topo-filter-pill-glow-test.mjs +90 -0
- package/scripts/topo-fleet-density-tier-test.mjs +84 -0
- package/scripts/topo-flow-rail-brightness-test.mjs +80 -0
- package/scripts/topo-freshness-chip-fade-test.mjs +105 -0
- package/scripts/topo-fullscreen-attr-test.mjs +73 -0
- package/scripts/topo-fullscreen-brightness-test.mjs +84 -0
- package/scripts/topo-fullscreen-icon-rotate-test.mjs +93 -0
- package/scripts/topo-grid-content-bottom-attr-test.mjs +72 -0
- package/scripts/topo-group-box-brightness-test.mjs +84 -0
- package/scripts/topo-group-label-brightness-test.mjs +84 -0
- package/scripts/topo-group-label-hover-glow-test.mjs +86 -0
- package/scripts/topo-group-label-member-alias-hover-test.mjs +125 -0
- package/scripts/topo-group-pill-glow-test.mjs +76 -0
- package/scripts/topo-hub-digit-brightness-test.mjs +79 -0
- package/scripts/topo-hub-digit-ls-test.mjs +119 -0
- package/scripts/topo-hub-halo-brightness-test.mjs +80 -0
- package/scripts/topo-hub-halo-glow-test.mjs +96 -0
- package/scripts/topo-hub-highlight-amplify-test.mjs +139 -0
- package/scripts/topo-hub-highlight-brightness-test.mjs +84 -0
- package/scripts/topo-hub-highlight-fill-transition-test.mjs +84 -0
- package/scripts/topo-hub-highlight-glow-test.mjs +99 -0
- package/scripts/topo-hub-highlight-r-test.mjs +112 -0
- package/scripts/topo-hub-highlight-recede-test.mjs +144 -0
- package/scripts/topo-hub-highlight-theme-fill-test.mjs +83 -0
- package/scripts/topo-hub-hover-ring-brightness-test.mjs +79 -0
- package/scripts/topo-hub-hover-ring-glow-test.mjs +97 -0
- package/scripts/topo-hub-idle-breath-test.mjs +7 -2
- package/scripts/topo-hub-recede-test.mjs +124 -0
- package/scripts/topo-hub-spoke-brightness-test.mjs +77 -0
- package/scripts/topo-hub-spoke-glow-test.mjs +112 -0
- package/scripts/topo-layout-hover-fw-test.mjs +98 -0
- package/scripts/topo-layout-toggle-brightness-test.mjs +94 -0
- package/scripts/topo-legend-count-brightness-test.mjs +80 -0
- package/scripts/topo-legend-count-letter-spacing-test.mjs +108 -0
- package/scripts/topo-legend-label-fw-test.mjs +107 -0
- package/scripts/topo-legend-pin-ring-brightness-test.mjs +82 -0
- package/scripts/topo-legend-row-count-brightness-test.mjs +85 -0
- package/scripts/topo-legend-row-label-glow-test.mjs +102 -0
- package/scripts/topo-legend-swatch-glow-test.mjs +109 -0
- package/scripts/topo-legend-swatch-member-alias-match-test.mjs +139 -0
- package/scripts/topo-minimap-hover-glow-test.mjs +109 -0
- package/scripts/topo-more-footer-brightness-test.mjs +94 -0
- package/scripts/topo-node-alias-brightness-test.mjs +84 -0
- package/scripts/topo-node-sub-text-brightness-test.mjs +88 -0
- package/scripts/topo-nodesize-brightness-test.mjs +82 -0
- package/scripts/topo-nodesize-hover-fw-test.mjs +99 -0
- package/scripts/topo-orphan-box-dash-test.mjs +89 -0
- package/scripts/topo-orphan-fill-opacity-test.mjs +91 -0
- package/scripts/topo-orphan-label-opacity-test.mjs +98 -0
- package/scripts/topo-panel-count-hover-ls-test.mjs +87 -0
- package/scripts/topo-panel-row-brightness-test.mjs +116 -0
- package/scripts/topo-panel-title-brightness-test.mjs +98 -0
- package/scripts/topo-panel-title-glow-test.mjs +111 -0
- package/scripts/topo-pill-x-rotate-test.mjs +96 -0
- package/scripts/topo-pinned-aspect-test.mjs +89 -0
- package/scripts/topo-pip-brightness-test.mjs +85 -0
- package/scripts/topo-pressure-seg-glow-test.mjs +92 -0
- package/scripts/topo-pressure-seg-member-alias-match-test.mjs +133 -0
- package/scripts/topo-pressure-seg-motion-test.mjs +101 -0
- package/scripts/topo-recent-count-brightness-test.mjs +84 -0
- package/scripts/topo-recent-more-fw-test.mjs +126 -0
- package/scripts/topo-recent-row-fw-test.mjs +115 -0
- package/scripts/topo-recent-row-text-glow-test.mjs +86 -0
- package/scripts/topo-recent-ts-brightness-test.mjs +86 -0
- package/scripts/topo-reduced-motion-attr-test.mjs +69 -0
- package/scripts/topo-reset-brightness-test.mjs +83 -0
- package/scripts/topo-reset-icon-hover-scale-test.mjs +102 -0
- package/scripts/topo-runtime-badge-brightness-test.mjs +78 -0
- package/scripts/topo-runtime-badge-glow-test.mjs +108 -0
- package/scripts/topo-runtime-badge-rotate-test.mjs +85 -0
- package/scripts/topo-starfield-hue-test.mjs +109 -0
- package/scripts/topo-status-ring-brightness-test.mjs +84 -0
- package/scripts/topo-titleblock-h2-hover-fw-test.mjs +109 -0
- package/scripts/topo-titleblock-h2-hover-tracking-test.mjs +128 -0
- package/scripts/topo-titleblock-kicker-hover-test.mjs +134 -0
- package/scripts/topo-vendor-chip-glow-test.mjs +97 -0
- package/scripts/topo-vendor-pill-glow-test.mjs +98 -0
- package/scripts/topo-watermark-breath-test.mjs +100 -0
- package/scripts/topo-watermark-recede-test.mjs +114 -0
- package/scripts/topo-zoom-buttons-brightness-test.mjs +94 -0
- package/scripts/topo-zoom-level-brightness-test.mjs +83 -0
- package/scripts/topo-zoom-level-color-test.mjs +105 -0
- package/.next/static/chunks/0.yvha74oimu5.js +0 -1
- package/.next/static/chunks/0brgpp3pfwrqa.js +0 -4
- package/.next/static/chunks/0m.1mvl~t.avc.css +0 -2
- package/.next/static/chunks/10x1zyfvy0f.8.js +0 -1
- /package/.next/static/{U1wWdzjMSApioHvZY8ULS → hgZRsyyYc0vdfgiTQsHgJ}/_buildManifest.js +0 -0
- /package/.next/static/{U1wWdzjMSApioHvZY8ULS → hgZRsyyYc0vdfgiTQsHgJ}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{U1wWdzjMSApioHvZY8ULS → hgZRsyyYc0vdfgiTQsHgJ}/_ssgManifest.js +0 -0
|
@@ -240,11 +240,30 @@ function FreshnessChip({ sessions }: { sessions: unknown }) {
|
|
|
240
240
|
stale-onset to direct attention. */
|
|
241
241
|
if (!stale) return null;
|
|
242
242
|
return (
|
|
243
|
+
/* Round 505 / Loop — FreshnessChip mount animation. Pre-R505 the
|
|
244
|
+
chip popped into the chip-row instantly when SWR data crossed
|
|
245
|
+
the 10s stale threshold; users saw an abrupt amber pill appear
|
|
246
|
+
mid-row. R505 adds the existing `anet-fade-in` class so the
|
|
247
|
+
chip eases through opacity 0→1 over 150ms (R51 globals.css
|
|
248
|
+
keyframe) on first appearance. The chip itself only renders
|
|
249
|
+
when stale (R275 conditional), so the fade plays exactly when
|
|
250
|
+
the stale signal first arrives — perfectly aligned with the
|
|
251
|
+
semantic. Mount-once via React reconciliation (key not used
|
|
252
|
+
since FreshnessChip is a singleton in the parent).
|
|
253
|
+
a11y respected via R29 blanket — `@media (prefers-reduced-
|
|
254
|
+
motion: reduce)` neutralizes anet-fade-in to `animation:none`
|
|
255
|
+
(globals.css line 1083-1089 includes anet-fade-in in the
|
|
256
|
+
blanket list). Reduced-motion users see the chip pop instantly,
|
|
257
|
+
same as pre-R505 behavior — no regression.
|
|
258
|
+
Pure paint-axis addition (opacity animation, no geometry),
|
|
259
|
+
bbox unchanged. data-freshness-chip-mount-fade attr exposes
|
|
260
|
+
the gate for tests. */
|
|
243
261
|
<span
|
|
244
|
-
className={`${baseClass} ${colorClass}`}
|
|
262
|
+
className={`${baseClass} ${colorClass} anet-fade-in`}
|
|
245
263
|
title={stale ? `Last sync ${sec}s ago — SWR refresh may be lagging` : `Live data · refreshes every 5s · last sync ${sec}s ago`}
|
|
246
264
|
data-freshness-chip
|
|
247
265
|
data-freshness-chip-stale={stale ? 'true' : 'false'}
|
|
266
|
+
data-freshness-chip-mount-fade="true"
|
|
248
267
|
>
|
|
249
268
|
{/* Round 272 / Loop: swap prefix word to match color state so
|
|
250
269
|
text and color point the same way. Pre-R272 the chip read
|
|
@@ -1123,6 +1142,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1123
1142
|
// hover state says — either -8° (still hovering) or 0 (mouse left).
|
|
1124
1143
|
// 350th-round milestone polish.
|
|
1125
1144
|
const [hoveredReset, setHoveredReset] = useState(false);
|
|
1145
|
+
// R595: hover state for the chrome fullscreen button — drives the
|
|
1146
|
+
// brightness(1.15) filter on hover, sibling to hoveredReset above.
|
|
1147
|
+
// Same pattern as R593 hoveredZoomLevel + R594 hoveredReset; closes
|
|
1148
|
+
// the standalone chrome button pair (reset + fullscreen) at
|
|
1149
|
+
// brightness parity.
|
|
1150
|
+
const [hoveredFullscreen, setHoveredFullscreen] = useState(false);
|
|
1126
1151
|
// R135: panel-wide hover-elevation. The recent-signal + legend
|
|
1127
1152
|
// panels both already host clickable rows (R56/R116 recent rows,
|
|
1128
1153
|
// R55/R61 legend rows) and a clickable footer (R133), so the
|
|
@@ -1706,7 +1731,18 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1706
1731
|
width) = 166px total title-block width vs 168px pre-R298 —
|
|
1707
1732
|
no measurable layout shift, just a deliberate tighter
|
|
1708
1733
|
grouping. */}
|
|
1709
|
-
|
|
1734
|
+
{/* Round 554 / Loop — title-block wrapper picks up `group` so
|
|
1735
|
+
the H2 below can subscribe to `group-hover:tracking-tighter`.
|
|
1736
|
+
Pairs with R548/R549 brand-logo hover gestures: cursor
|
|
1737
|
+
sweeping anywhere across the title cluster fires the brand
|
|
1738
|
+
logo's scale-105 + rotate-6 + breath ↔ AND tightens the
|
|
1739
|
+
H2's tracking from -0.025em → -0.05em.
|
|
1740
|
+
Makes the title-block read as one coherent hover cluster —
|
|
1741
|
+
brand mark provides the loud gesture (scale + rotate), H2
|
|
1742
|
+
provides the subtle editorial gesture (kerning tighten).
|
|
1743
|
+
data-topo-section-titleblock-group attr surfaces the gate
|
|
1744
|
+
for tests. */}
|
|
1745
|
+
<div className="group flex items-center gap-2.5" data-topo-section-titleblock-group>
|
|
1710
1746
|
{/* Round 297 / Loop: brand-logo color picks up the 200ms ease-
|
|
1711
1747
|
out transition. Pre-R297 the moon glyph had theme-
|
|
1712
1748
|
conditional color (cyber #67e8f9 cyan ↔ light #0d9488
|
|
@@ -1730,13 +1766,162 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1730
1766
|
overpowering the h2 at text-lg/font-semibold (R286).
|
|
1731
1767
|
viewBox 32×32 unchanged so the inner crescent geometry
|
|
1732
1768
|
scales proportionally. */}
|
|
1769
|
+
{/* Round 548 / Loop — title-block brand logo gains subtle
|
|
1770
|
+
hover-scale gesture. Pre-R548 the 40×40 crescent was
|
|
1771
|
+
fully static — no hover affordance. R548 adds hover:
|
|
1772
|
+
scale-105 (Tailwind 4 emits as `scale: 1.05`) so the
|
|
1773
|
+
brand mark gently responds to attention as the user's
|
|
1774
|
+
cursor sweeps across the title block. 5% scale is
|
|
1775
|
+
intentionally subtle (vs R350 chrome icon hover-scale-
|
|
1776
|
+
110): the brand logo is a passive identity mark, not
|
|
1777
|
+
an interactive control, so the gesture stays small.
|
|
1778
|
+
cursor: default to clarify non-clickability (the SVG
|
|
1779
|
+
isn't a button; just a brand element).
|
|
1780
|
+
transition-transform duration-200 ease-out matches the
|
|
1781
|
+
dashboard's R350-family hover-scale cadence so the
|
|
1782
|
+
brand logo's response shares the same motion vocabulary
|
|
1783
|
+
as the chrome strip's icon scales. transform-gpu hint
|
|
1784
|
+
promotes the SVG to its own compositor layer for crisp
|
|
1785
|
+
edges during the tween.
|
|
1786
|
+
Brand-mark delight gesture family (1 anchor):
|
|
1787
|
+
R548 title-block brand logo hover:scale-105
|
|
1788
|
+
The crescent at canvas top-left (data-topo-brand-
|
|
1789
|
+
canvas-mark) and the watermark at bottom-left (data-
|
|
1790
|
+
topo-brand-watermark) are intentionally LEFT STATIC —
|
|
1791
|
+
both have pointerEvents:none and exist as ambient
|
|
1792
|
+
decoration with their own breath/recede dynamics
|
|
1793
|
+
(R519/R525/R526/R528). The title-block logo is the
|
|
1794
|
+
ONLY brand surface that's a candidate for hover affordance,
|
|
1795
|
+
since it sits in the chrome-band where the cursor
|
|
1796
|
+
naturally passes during normal use. */}
|
|
1797
|
+
{/* Round 549 / Loop — extends R548 (hover:scale-105) with a
|
|
1798
|
+
subtle hover:rotate-6 rotation. Pairs scale + rotate on
|
|
1799
|
+
the brand mark, same idiom as R547 added to the pill ×
|
|
1800
|
+
close buttons (scale-110 + rotate-12) but at gentler
|
|
1801
|
+
amounts: 105% vs 110% scale and 6° vs 12° rotation —
|
|
1802
|
+
brand marks want restraint vs interactive close buttons.
|
|
1803
|
+
|
|
1804
|
+
The crescent-moon shape (curved cutout via mask) reads
|
|
1805
|
+
visually distinct as it rotates — the asymmetric cutout
|
|
1806
|
+
swings into a fresh angle, telegraphing "this brand mark
|
|
1807
|
+
is alive without being loud". The 6° landing lands the
|
|
1808
|
+
moon's cusp pointing slightly NE rather than straight up,
|
|
1809
|
+
a small but legible reveal of the shape's geometry.
|
|
1810
|
+
|
|
1811
|
+
Tailwind 4 emits BOTH `scale: 1.05` AND `rotate: 6deg`
|
|
1812
|
+
as INDIVIDUAL CSS properties (not a combined transform
|
|
1813
|
+
string — see R547 banked pattern). The className keeps
|
|
1814
|
+
transition-transform so both axes ease at the same 200ms
|
|
1815
|
+
cadence; transform-gpu hint stays so the compositor
|
|
1816
|
+
promotes both axes to GPU layers.
|
|
1817
|
+
|
|
1818
|
+
data-topo-brand-logo-hover-rotate attr surfaces the
|
|
1819
|
+
landing rotation for tests. Pair with R548's
|
|
1820
|
+
data-topo-brand-logo-hover-scale attr — both attrs
|
|
1821
|
+
advertise the dual-axis hover signature. */}
|
|
1822
|
+
{/* Round 553 / Loop — title-block brand logo gains subtle
|
|
1823
|
+
idle opacity breath (~0.92 ↔ 1, 5s ease-in-out cycle).
|
|
1824
|
+
5th anchor in the 呼吸感 breath family, slotting into
|
|
1825
|
+
the ascending cadence ladder between hub idle (4s) and
|
|
1826
|
+
watermark (6s):
|
|
1827
|
+
row hot 3s
|
|
1828
|
+
hub idle 4s
|
|
1829
|
+
brand logo 5s ← this round
|
|
1830
|
+
watermark 6s
|
|
1831
|
+
crescent 7s
|
|
1832
|
+
Composes cleanly with R548 hover:scale-105 + R549
|
|
1833
|
+
hover:rotate-6 — opacity, scale, and rotate are
|
|
1834
|
+
independent CSS properties; the moon keeps breathing
|
|
1835
|
+
as it scales and rotates on hover. Layered effect
|
|
1836
|
+
reads as "this brand mark is alive even before you
|
|
1837
|
+
touch it, and lights up further on hover".
|
|
1838
|
+
Reduced-motion gate: component-side `!reducedMotion`
|
|
1839
|
+
toggles the className (canonical TopoGraph breath
|
|
1840
|
+
pattern); R29 globals.css blanket provides a
|
|
1841
|
+
defense-in-depth fallback (animation-duration →
|
|
1842
|
+
0.001ms under prefers-reduced-motion: reduce).
|
|
1843
|
+
data-topo-brand-logo-breath attr exposes the gate
|
|
1844
|
+
state for tests. */}
|
|
1845
|
+
{/* Round 557 / Loop — brand logo gains 4th hover axis:
|
|
1846
|
+
hover:brightness-110 (filter). Adds a chromatic axis
|
|
1847
|
+
to the brand-mark hover signature alongside R548
|
|
1848
|
+
scale, R549 rotate, R553 idle breath:
|
|
1849
|
+
R548 hover:scale-105 transform-scale
|
|
1850
|
+
R549 hover:rotate-6 transform-rotate
|
|
1851
|
+
R553 idle breath (5s) opacity (animation)
|
|
1852
|
+
R557 hover:brightness-110 filter ← this round
|
|
1853
|
+
All 4 axes ride on INDEPENDENT CSS properties (scale,
|
|
1854
|
+
rotate, opacity, filter) — they compose freely without
|
|
1855
|
+
clobbering each other. The cyan/teal crescent gains a
|
|
1856
|
+
soft +10% brightness boost on hover, layered on top of
|
|
1857
|
+
the existing scale + rotate lift + ongoing idle breath.
|
|
1858
|
+
Why +10% (vs more aggressive 125/150): brand mark wants
|
|
1859
|
+
restraint. The eye reads the brightness shift as "this
|
|
1860
|
+
mark lights up under attention" without crossing into
|
|
1861
|
+
"this mark is now glowing".
|
|
1862
|
+
Implementation: className extends transition-transform
|
|
1863
|
+
→ transition-[transform,filter] so the brightness
|
|
1864
|
+
tweens at the same 200ms ease-out cadence as the scale
|
|
1865
|
+
+ rotate axes — one motion-coherent 3-property hover
|
|
1866
|
+
lift on the className tier (plus the inline color 200ms
|
|
1867
|
+
transition for theme-toggle eases).
|
|
1868
|
+
Brand-mark family axis count: 4 hover-state axes
|
|
1869
|
+
cleanly factor across:
|
|
1870
|
+
geometry (scale + rotate)
|
|
1871
|
+
paint (opacity breath + brightness on hover)
|
|
1872
|
+
Cluster reads as "alive, lifting, and lighting up under
|
|
1873
|
+
attention" — three independent gesture vocabularies on
|
|
1874
|
+
one surface.
|
|
1875
|
+
data-topo-brand-logo-hover-brightness attr surfaces
|
|
1876
|
+
the landing value for tests. */}
|
|
1733
1877
|
<svg
|
|
1734
1878
|
width="40" height="40" viewBox="0 0 32 32" aria-hidden
|
|
1735
|
-
|
|
1879
|
+
/* R604 — brand 书生 logo gains hover-drop-shadow as 5th
|
|
1880
|
+
axis (4 hover + 1 always-on breath). Tailwind v4 arbitrary
|
|
1881
|
+
drop-shadow with currentColor — the glow inherits the
|
|
1882
|
+
brand-mark's own colour (light teal #0d9488 / cyber cyan
|
|
1883
|
+
#67e8f9 per inline style.color). 8px radius reads as a
|
|
1884
|
+
"lit up under attention" halo without overwhelming the
|
|
1885
|
+
40×40 logo footprint.
|
|
1886
|
+
|
|
1887
|
+
Tailwind v4 emits filter utilities through CSS-var system
|
|
1888
|
+
(--tw-drop-shadow, --tw-brightness) that combine in the
|
|
1889
|
+
`filter` shorthand — so hover:brightness-110 + hover:
|
|
1890
|
+
drop-shadow-[0_0_8px_currentColor] stack instead of
|
|
1891
|
+
clobbering each other. Same banked R564/R570 "halo +
|
|
1892
|
+
glow" pattern, now at the brand-mark scope.
|
|
1893
|
+
|
|
1894
|
+
Brand-logo hover signature now 5 axes:
|
|
1895
|
+
R548 hover:scale-105 transform-scale
|
|
1896
|
+
R549 hover:rotate-6 transform-rotate
|
|
1897
|
+
R553 idle breath (5s) opacity (always-on)
|
|
1898
|
+
R557 hover:brightness-110 filter brightness
|
|
1899
|
+
R604 hover:drop-shadow filter drop-shadow ← this round
|
|
1900
|
+
|
|
1901
|
+
Existing inline transition already covers 'filter 200ms
|
|
1902
|
+
ease-out' (R557 cadence) — both brightness AND drop-
|
|
1903
|
+
shadow ease at the same beat. */
|
|
1904
|
+
className={`shrink-0 transition-[transform,filter] duration-200 ease-out hover:scale-105 hover:rotate-6 hover:brightness-110 hover:drop-shadow-[0_0_8px_currentColor] transform-gpu${!reducedMotion ? ' anet-topo-brand-logo-breath' : ''}`}
|
|
1736
1905
|
data-topo-brand-logo
|
|
1906
|
+
data-topo-brand-logo-hover-scale="1.05"
|
|
1907
|
+
data-topo-brand-logo-hover-rotate="6deg"
|
|
1908
|
+
data-topo-brand-logo-hover-brightness="1.1"
|
|
1909
|
+
data-topo-brand-logo-hover-drop-shadow="0_0_8px_currentColor"
|
|
1910
|
+
data-topo-brand-logo-breath={!reducedMotion ? 'true' : 'false'}
|
|
1737
1911
|
style={{
|
|
1738
1912
|
color: isLight ? '#0d9488' : '#67e8f9',
|
|
1739
|
-
|
|
1913
|
+
cursor: 'default',
|
|
1914
|
+
// R557 — extend transition list to include filter (and
|
|
1915
|
+
// re-spec transform for cadence parity) so the new
|
|
1916
|
+
// hover:brightness-110 axis eases at 200ms alongside
|
|
1917
|
+
// the existing color 200ms (theme-toggle ease) and the
|
|
1918
|
+
// className-based hover:scale-105 / hover:rotate-6.
|
|
1919
|
+
// Inline transition is a shorthand and overrides the
|
|
1920
|
+
// className's transition-[transform,filter] — listing
|
|
1921
|
+
// all axes here ensures the eased property set covers
|
|
1922
|
+
// color (theme) + transform (scale + rotate) + filter
|
|
1923
|
+
// (brightness) at uniform 200ms ease-out.
|
|
1924
|
+
transition: 'color 200ms ease-out, transform 200ms ease-out, filter 200ms ease-out',
|
|
1740
1925
|
}}
|
|
1741
1926
|
>
|
|
1742
1927
|
<mask id="s2a-titleblock-moon-mask">
|
|
@@ -1784,7 +1969,40 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1784
1969
|
R300 marks the milestone of 25 rounds (R275-R300) of
|
|
1785
1970
|
continuous TopoGraph polish + codex's Vincent 5215/
|
|
1786
1971
|
5222 logo asset+integration work. */}
|
|
1787
|
-
|
|
1972
|
+
{/* Round 555 / Loop — kicker "Network Topology" gains group-
|
|
1973
|
+
hover affordance via the R554 wrapper's `group` flag,
|
|
1974
|
+
closing the title-block cluster's hover coverage at 3
|
|
1975
|
+
surfaces (brand logo + H2 + kicker).
|
|
1976
|
+
Picks up the small-label SPREAD direction (R554 banked
|
|
1977
|
+
"small labels SPREAD on hover / large headlines TIGHTEN"
|
|
1978
|
+
— kicker is xs uppercase, definitely a small label) plus
|
|
1979
|
+
a color brighten (text-gray-500 #6b7280 → text-gray-400
|
|
1980
|
+
#9ca3af).
|
|
1981
|
+
Spread: tracking-widest (0.1em rest) → 0.13em hover —
|
|
1982
|
+
+30% kerning bump. At text-xs (12px) the per-gap shift
|
|
1983
|
+
is 1.2px → 1.56px (+0.36px/gap), legible without
|
|
1984
|
+
overshooting the rest's tracking-widest editorial base.
|
|
1985
|
+
Color: text-gray-500 → text-gray-400 — one tier lighter,
|
|
1986
|
+
same idiom as R296 (kicker rest tone-up from gray-600 to
|
|
1987
|
+
gray-500), now extended at the hover-state tier.
|
|
1988
|
+
transition-[letter-spacing,color] duration-200 ease-out
|
|
1989
|
+
matches the 200ms cadence of R554 H2 ls + the rest of
|
|
1990
|
+
the hover-ls family (R344/R345/R347/R351/R420/R427/R431/
|
|
1991
|
+
R432/R434/R527/R539).
|
|
1992
|
+
Title-block cluster signature post-R555 (3 surfaces):
|
|
1993
|
+
brand logo loud scale + rotate + breath
|
|
1994
|
+
(R548/R549/R553)
|
|
1995
|
+
H2 subtle tracking-tighter
|
|
1996
|
+
(R554, editorial-tighten)
|
|
1997
|
+
kicker subtle tracking-spread + color lift
|
|
1998
|
+
(R555, data-spread) ← this round
|
|
1999
|
+
Two of the three surfaces are typographic; the brand
|
|
2000
|
+
logo carries the geometric+chromatic motion. Cluster
|
|
2001
|
+
reads as ONE coherent hover unit through three
|
|
2002
|
+
independent gesture vocabularies.
|
|
2003
|
+
data-topo-section-kicker-hover-tracking + -hover-color
|
|
2004
|
+
attrs expose the landing values for tests. */}
|
|
2005
|
+
<div className="text-xs uppercase text-gray-500 group-hover:text-gray-400 tracking-widest group-hover:tracking-[0.13em] transition-[letter-spacing,color] duration-200 ease-out leading-tight font-medium" data-topo-section-kicker data-topo-section-kicker-hover-tracking="0.13em" data-topo-section-kicker-hover-color="text-gray-400">Network Topology</div>
|
|
1788
2006
|
{/* Round 286 / Loop: title 'Command mesh' adopts tracking-tight
|
|
1789
2007
|
(-0.025em) to complement R285 kicker tracking-widest. Wide
|
|
1790
2008
|
eyebrow + tight headline is the conventional editorial
|
|
@@ -1796,7 +2014,62 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1796
2014
|
cumulatively legible across 12 characters. font-semibold
|
|
1797
2015
|
(600) stays — tracking-tight does the heavy lifting for
|
|
1798
2016
|
the editorial register. */}
|
|
1799
|
-
|
|
2017
|
+
{/* Round 554 / Loop — H2 "Command mesh" gains group-hover-
|
|
2018
|
+
gated tracking-tighter via the wrapper's R554 `group`
|
|
2019
|
+
flag. Pre-R554 the H2 was fully static (R286 tracking-
|
|
2020
|
+
tight only at rest). R554 adds an editorial-tighten
|
|
2021
|
+
gesture: when cursor sweeps anywhere across the title
|
|
2022
|
+
cluster (brand logo OR title text), the headline tightens
|
|
2023
|
+
from -0.025em → -0.05em.
|
|
2024
|
+
Inverts the typical hover-letter-spacing direction:
|
|
2025
|
+
small labels (chip counts, panel titles, edge digits)
|
|
2026
|
+
SPREAD on hover → "data telegraphing"
|
|
2027
|
+
large headlines (h2 Command mesh)
|
|
2028
|
+
TIGHTEN on hover → "editorial emphasis"
|
|
2029
|
+
Both directions are coherent design language — small
|
|
2030
|
+
data wants spacing for legibility; large headlines want
|
|
2031
|
+
tightening for designed-headline polish. Same idiom as
|
|
2032
|
+
the conventional editorial pairing of "wide kicker +
|
|
2033
|
+
tight headline" R285/R286 set up (kicker spreads 0.1em
|
|
2034
|
+
tracking-widest; headline tightens -0.025em tracking-
|
|
2035
|
+
tight) — R554 deepens that pairing's tighten side at
|
|
2036
|
+
the hover-state tier.
|
|
2037
|
+
At text-lg (18px) the shift is -0.45px → -0.9px per
|
|
2038
|
+
gap (~5.4px total tightening across "Command mesh" 12
|
|
2039
|
+
chars). Subtle but legible when the cursor sweeps in.
|
|
2040
|
+
transition-[letter-spacing] duration-200 ease-out
|
|
2041
|
+
matches the 200ms hover-ls cadence used at R344/R345/
|
|
2042
|
+
R347/R351/R420/R427/R431/R432/R434/R527/R539 family
|
|
2043
|
+
anchors.
|
|
2044
|
+
data-topo-section-title-hover-tracking attr surfaces
|
|
2045
|
+
the landing tracking class for tests. */}
|
|
2046
|
+
{/* Round 556 / Loop — H2 "Command mesh" gains a 2nd
|
|
2047
|
+
editorial-emphasis axis: group-hover:font-bold paired
|
|
2048
|
+
with R554's group-hover:tracking-tighter. Both lifts
|
|
2049
|
+
fire on the same R554 wrapper's `group` flag (hover
|
|
2050
|
+
anywhere in the title cluster → BOTH H2 axes intensify
|
|
2051
|
+
simultaneously).
|
|
2052
|
+
H2 hover signature post-R556 (2 typographic axes
|
|
2053
|
+
intensify together):
|
|
2054
|
+
rest font-semibold 600 + tracking-tight -0.025em
|
|
2055
|
+
hover font-bold 700 + tracking-tighter -0.05em
|
|
2056
|
+
Editorial emphasis through TWO axes — heavier AND
|
|
2057
|
+
tighter on hover. Mirrors the conventional "designed-
|
|
2058
|
+
headline emphasis" idiom (heavier + tighter = more
|
|
2059
|
+
authoritative; the eye reads both axes as intensifying
|
|
2060
|
+
the same semantic).
|
|
2061
|
+
Hover-fw family extension (6 anchors now):
|
|
2062
|
+
R416 chip-row count digit (chip group-hover)
|
|
2063
|
+
R420 chrome zoom-level (hover)
|
|
2064
|
+
R425 hub-center digit (hub hover)
|
|
2065
|
+
R520 +N more flows footer (recent panel hover)
|
|
2066
|
+
R521 chrome nodeSize S/M/L (inactive hover)
|
|
2067
|
+
R556 title-block H2 (cluster group-hover) ← this round
|
|
2068
|
+
Transition list extends to include 'font-weight 200ms
|
|
2069
|
+
ease-out' alongside the existing 'letter-spacing'
|
|
2070
|
+
200ms cadence. data-topo-section-title-hover-fw attr
|
|
2071
|
+
surfaces the landing weight for tests. */}
|
|
2072
|
+
<h2 className="text-lg text-white font-semibold group-hover:font-bold leading-tight tracking-tight group-hover:tracking-tighter transition-[letter-spacing,font-weight] duration-200 ease-out" data-topo-section-title data-topo-section-title-hover-tracking="tracking-tighter" data-topo-section-title-hover-fw="700">Command mesh</h2>
|
|
1800
2073
|
</div>
|
|
1801
2074
|
</div>
|
|
1802
2075
|
{/* Round 328 / Loop: chip-row strip wrapper gap 2 → 2.5
|
|
@@ -1966,8 +2239,75 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1966
2239
|
// overlays the release-pop. Matching `transform-gpu`
|
|
1967
2240
|
// promotes the layer so the scale doesn't trigger
|
|
1968
2241
|
// layout/paint thrash. Sibling change on Grid below.
|
|
1969
|
-
|
|
1970
|
-
|
|
2242
|
+
/* Round 522 / Loop — extends R521's typography-preview
|
|
2243
|
+
idiom (chrome nodeSize hover:font-medium 400 → 500) to
|
|
2244
|
+
the Ring/Grid layout toggle's inactive variant. Pre-
|
|
2245
|
+
R522 the inactive Ring/Grid had `hover:text-cyan-300
|
|
2246
|
+
hover:bg-cyan-500/5` (R270 color + bg previews of the
|
|
2247
|
+
active state) but no typography preview — the active
|
|
2248
|
+
variant uses `font-medium` (fw 500), inactive sat at
|
|
2249
|
+
default fw 400 even on hover. R522 adds `hover:font-
|
|
2250
|
+
medium` to the inactive Ring/Grid so the rest-vs-hover
|
|
2251
|
+
transition previews the typography state the click
|
|
2252
|
+
would commit to, matching the click commits's locked
|
|
2253
|
+
weight.
|
|
2254
|
+
font-weight 150ms appended to the transition list
|
|
2255
|
+
matching the existing 150ms color/bg cadence at this
|
|
2256
|
+
button — when hover lifts color (gray-400 → cyan-300)
|
|
2257
|
+
+ bg (transparent → cyan-500/5) + fw (400 → 500), all
|
|
2258
|
+
3 ease at the same 150ms beat.
|
|
2259
|
+
Hover-fw family extension (6 anchors): R416/R420/R425/
|
|
2260
|
+
R520/R521/R522. R522 closes the chrome toggle group
|
|
2261
|
+
typography preview at the last remaining toggle —
|
|
2262
|
+
layout (Ring/Grid). After R521 (nodeSize) + R522
|
|
2263
|
+
(layout), every multi-state chrome toggle has hover-
|
|
2264
|
+
fw preview on its inactive variant.
|
|
2265
|
+
data-topo-chrome-layout-hover-preview-fw="500" attr
|
|
2266
|
+
on inactive button exposes the polish for tests. */
|
|
2267
|
+
/* Round 552 / Loop — chrome active-variant gains hover:
|
|
2268
|
+
text-cyan-200, lifting text one brightness tier alongside
|
|
2269
|
+
the existing hover:bg-cyan-500/20 bg deepen. Coordinated
|
|
2270
|
+
4-anchor edit (replace_all touched 4 sibling lines sharing
|
|
2271
|
+
the identical active-variant className substring):
|
|
2272
|
+
Ring (this line) layout === 'ring'
|
|
2273
|
+
Grid (line ~2097) layout === 'grid'
|
|
2274
|
+
S/M/L (line ~12635) nodeScale === v
|
|
2275
|
+
Fscrn (line ~13030) isFullscreen
|
|
2276
|
+
Pre-R552 the active variant's hover state only deepened bg
|
|
2277
|
+
(cyan-500/15 → /20); text stayed planted at cyan-300. The
|
|
2278
|
+
inactive variant already lifts text on hover (text-gray-400
|
|
2279
|
+
→ text-cyan-300). R552 brings parity: active variant lifts
|
|
2280
|
+
text one tier brighter (cyan-300 → cyan-200) on hover,
|
|
2281
|
+
mirroring the inactive variant's "text brightens on hover"
|
|
2282
|
+
gesture at the next brightness step.
|
|
2283
|
+
Brightness ladder snapshot (cyan):
|
|
2284
|
+
cyan-400 brand chrome focus ring
|
|
2285
|
+
cyan-300 active-variant rest ←─┐
|
|
2286
|
+
│ +1 tier on hover
|
|
2287
|
+
cyan-200 active-variant hover ←─┘ (this round)
|
|
2288
|
+
Pure paint axis (text color); bbox/geometry unchanged.
|
|
2289
|
+
transition-colors already in the class list so the cyan-
|
|
2290
|
+
300 → cyan-200 swap eases at the existing 200ms cadence.
|
|
2291
|
+
hover-color brighten family extension at the chrome strip
|
|
2292
|
+
active-variant scope; sibling to the inactive variant's
|
|
2293
|
+
R163/R178/R179/R270 hover:text-cyan-300 idiom. */
|
|
2294
|
+
className={`px-2.5 py-1 focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:tracking-wide hover:brightness-[1.15] active:scale-95 transform-gpu ${layout === 'ring' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 hover:text-cyan-200 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15 hover:font-medium'} ${chromePopping === 'layout-ring' ? ' anet-chrome-pop' : ''}`}
|
|
2295
|
+
data-topo-chrome-layout-hover-preview-fw={layout === 'ring' ? null : '500'}
|
|
2296
|
+
data-topo-chrome-layout-ring-brightness-hover="1.15"
|
|
2297
|
+
/* R597 — Ring/Grid segmented buttons gain hover:
|
|
2298
|
+
brightness-[1.15] (37+38th anchors in per-element
|
|
2299
|
+
brightness family, 6+7th HTML). Paired-anchor round
|
|
2300
|
+
mirroring R596's zoom +/- closure at the second
|
|
2301
|
+
segmented control. Same segmented-unity rule
|
|
2302
|
+
(brightness = pure paint, no geometry break).
|
|
2303
|
+
Inline transition list extends with 'filter 150ms
|
|
2304
|
+
ease' so brightness eases under the existing R522
|
|
2305
|
+
150ms beat that bg/color/fw share (letter-spacing
|
|
2306
|
+
+ transform on their own 200ms/150ms cadences).
|
|
2307
|
+
R557 banked pattern: when an element has both
|
|
2308
|
+
inline transition AND new transition-driven axis,
|
|
2309
|
+
extend the INLINE list — not the className. */
|
|
2310
|
+
style={{ transition: 'background-color 150ms ease, color 150ms ease, letter-spacing 200ms ease-out, transform 150ms ease-out, font-weight 150ms ease, filter 150ms ease' }}
|
|
1971
2311
|
>
|
|
1972
2312
|
Ring
|
|
1973
2313
|
</button>
|
|
@@ -1989,7 +2329,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1989
2329
|
// R492 sibling — Grid button picks up active:scale-95
|
|
1990
2330
|
// press feedback + transform in transition list. Same
|
|
1991
2331
|
// vocabulary as Ring above.
|
|
1992
|
-
|
|
2332
|
+
/* Round 522 sibling — Grid button mirrors Ring above:
|
|
2333
|
+
inactive variant gains `hover:font-medium` typography
|
|
2334
|
+
preview + font-weight 150ms in inline transition list.
|
|
2335
|
+
Same idiom, same family (R522 chrome layout). */
|
|
2336
|
+
className={`px-2.5 py-1 border-l focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:tracking-wide hover:brightness-[1.15] active:scale-95 transform-gpu ${layout === 'grid' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 hover:text-cyan-200 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15 hover:font-medium'} ${chromePopping === 'layout-grid' ? ' anet-chrome-pop' : ''}`}
|
|
2337
|
+
data-topo-chrome-layout-hover-preview-fw={layout === 'grid' ? null : '500'}
|
|
2338
|
+
data-topo-chrome-layout-grid-brightness-hover="1.15"
|
|
1993
2339
|
/* Round 268 / Loop: Grid button's left border (the
|
|
1994
2340
|
internal divider between Ring and Grid) picks up
|
|
1995
2341
|
pal.containerBorder, matching the wrapper change at
|
|
@@ -2001,8 +2347,11 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2001
2347
|
the border-color flip — border-color 200ms ease-out
|
|
2002
2348
|
keeps R268's theme-toggle smoothness intact.
|
|
2003
2349
|
R492 adds `transform 150ms ease-out` so active:scale-95
|
|
2004
|
-
eases smoothly.
|
|
2005
|
-
|
|
2350
|
+
eases smoothly.
|
|
2351
|
+
R597 sibling — Grid button mirrors Ring above:
|
|
2352
|
+
hover:brightness-[1.15] + filter 150ms ease in
|
|
2353
|
+
the inline transition list. */
|
|
2354
|
+
style={{ borderColor: pal.containerBorder, transition: 'background-color 150ms ease, color 150ms ease, border-color 200ms ease-out, letter-spacing 200ms ease-out, transform 150ms ease-out, font-weight 150ms ease, filter 150ms ease' }}
|
|
2006
2355
|
>
|
|
2007
2356
|
Grid
|
|
2008
2357
|
</button>
|
|
@@ -2035,6 +2384,41 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2035
2384
|
// lists once for both chips to share.
|
|
2036
2385
|
const workingAliases = onlineNodes.filter(s => s.status === 'working').map(s => s.alias);
|
|
2037
2386
|
const onlineAliases = onlineNodes.map(s => s.alias);
|
|
2387
|
+
/* Round 565 (50-round milestone) / Loop — extend the
|
|
2388
|
+
inspection-overrides-encoding family to a 7th anchor at
|
|
2389
|
+
the chip-row chip scope. Computes the hovered alias's
|
|
2390
|
+
status tier (same idiom as R562/R563) so each chip's
|
|
2391
|
+
className can include the "lit" bg/border treatment when
|
|
2392
|
+
operator hovers a node matching its tier.
|
|
2393
|
+
Family progression — 7 anchors complete:
|
|
2394
|
+
R484 recent-row timestamp alias hover
|
|
2395
|
+
R485 edge particle opacity alias hover
|
|
2396
|
+
R486 minimap dot opacity alias hover
|
|
2397
|
+
R561 group-label + ants-gate member-alias hover
|
|
2398
|
+
R562 legend-swatch r + glow member-alias status match
|
|
2399
|
+
R563 pressure-seg brightness member-alias status match
|
|
2400
|
+
R565 chip-row chip bg/border member-alias status match ← this round
|
|
2401
|
+
Status-tier-match feedback now SATURATES across panel
|
|
2402
|
+
chrome at 4 surfaces simultaneously:
|
|
2403
|
+
minimap dot (R486)
|
|
2404
|
+
legend swatch (R562)
|
|
2405
|
+
pressure-seg (R563)
|
|
2406
|
+
chip-row chip (R565) ← this round
|
|
2407
|
+
When operator hovers a 'working' node alias, ALL FOUR
|
|
2408
|
+
surfaces light up in green; 'idle' → all four in teal;
|
|
2409
|
+
'offline' → all four in slate. The eye gets 4-way
|
|
2410
|
+
confirmation of "your inspected node is in this tier"
|
|
2411
|
+
across every persistent status-reference surface. */
|
|
2412
|
+
const hoveredAliasTierKey: 'working' | 'idle' | 'offline' | null = (() => {
|
|
2413
|
+
if (!hoveredAlias) return null;
|
|
2414
|
+
const s = onlineNodes.find(n => n.alias === hoveredAlias)
|
|
2415
|
+
?? offlineNodes.find(n => n.alias === hoveredAlias);
|
|
2416
|
+
if (!s) return null;
|
|
2417
|
+
if (s.status === 'working') return 'working';
|
|
2418
|
+
return offlineNodes.includes(s) ? 'offline' : 'idle';
|
|
2419
|
+
})();
|
|
2420
|
+
const isWorkingChipLit = hoveredAliasTierKey === 'working';
|
|
2421
|
+
const isOnlineChipLit = hoveredAliasTierKey === 'idle';
|
|
2038
2422
|
const truncate = (list: string[]) => {
|
|
2039
2423
|
const head = list.slice(0, 8).join(', ');
|
|
2040
2424
|
const tail = list.length > 8 ? ` + ${list.length - 8} more` : '';
|
|
@@ -2127,13 +2511,19 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2127
2511
|
// R398 hover-lift conditional. Composes with hover:-
|
|
2128
2512
|
// translate-y-px for the same lift-and-compress
|
|
2129
2513
|
// tactile signature R493 brought to reset/fullscreen.
|
|
2514
|
+
/* R565: when isWorkingChipLit (operator hovers a working
|
|
2515
|
+
node), chip stays in its "lit" bg-green-500/15 +
|
|
2516
|
+
border-green-500/30 state at rest. Same visual as
|
|
2517
|
+
hover; member-alias-matching pins the lift without
|
|
2518
|
+
requiring cursor on the chip. */
|
|
2130
2519
|
className={`group tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors transition-transform duration-200 ease-out transform-gpu ${
|
|
2131
2520
|
workingCount > 0
|
|
2132
|
-
? 'bg-green-500/
|
|
2521
|
+
? `${isWorkingChipLit ? 'bg-green-500/15 border-green-500/30' : 'bg-green-500/10 border-green-500/20'} text-green-300 hover:bg-green-500/15 hover:border-green-500/30 hover:-translate-y-px active:scale-95`
|
|
2133
2522
|
: 'bg-green-500/10 text-green-300 border-green-500/20'
|
|
2134
2523
|
}`}
|
|
2135
2524
|
data-chip-hover-lift={workingCount > 0 ? 'true' : 'false'}
|
|
2136
2525
|
data-chip-group-hover-brighten="true"
|
|
2526
|
+
data-working-chip-member-alias-lit={isWorkingChipLit ? 'true' : 'false'}
|
|
2137
2527
|
data-working-chip
|
|
2138
2528
|
data-working-chip-aliases={workingAliases.join(',')}
|
|
2139
2529
|
data-pin-mirror={pinnedStatus === 'working' ? 'true' : 'false'}
|
|
@@ -2213,7 +2603,26 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2213
2603
|
at the chip-count scope. Sibling edits on the
|
|
2214
2604
|
online + active-links chip digits below. data-
|
|
2215
2605
|
working-chip-digit attr exposes the digit span. */}
|
|
2216
|
-
|
|
2606
|
+
{/* Round 539 / Loop — chip-row digit gains group-hover:
|
|
2607
|
+
tracking-wide alongside the existing R362 group-
|
|
2608
|
+
hover:font-bold. Pre-R539 the chip digit lifted
|
|
2609
|
+
only on the font-weight axis (600 → 700 on chip
|
|
2610
|
+
hover); R539 adds the kerning axis (tracking
|
|
2611
|
+
normal → tracking-wide ≈ 0.025em ≈ 0.3px on a 12px
|
|
2612
|
+
digit) so hover lifts BOTH typography axes
|
|
2613
|
+
together — same idiom R420/R517 establish at the
|
|
2614
|
+
chrome zoom-level (letter-spacing + fontWeight
|
|
2615
|
+
hover delta) and R531/R530 mirror at the panel
|
|
2616
|
+
label scope. transition-[font-weight] extends to
|
|
2617
|
+
transition-[font-weight,letter-spacing] for the
|
|
2618
|
+
smooth dual-axis tween.
|
|
2619
|
+
Sibling treatment across the 3 chip-row digits
|
|
2620
|
+
(working / online / active-links) — single concept
|
|
2621
|
+
replicated at 3 surfaces by replace_all.
|
|
2622
|
+
Hover-letter-spacing family extension (12 anchors
|
|
2623
|
+
now): R344/R345/R347/R420/R427/R431/R432/R433/
|
|
2624
|
+
R434/R517/R518 + R539 (this round). */}
|
|
2625
|
+
<span className="font-semibold transition-[font-weight,letter-spacing] duration-200 group-hover:font-bold group-hover:tracking-wide" data-working-chip-digit>{workingCount}</span><span className="opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide" data-working-chip-unit> working</span>
|
|
2217
2626
|
</span>
|
|
2218
2627
|
<span
|
|
2219
2628
|
// Round 201 / Loop: online chip — mirror of the working
|
|
@@ -2232,13 +2641,18 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2232
2641
|
// R494 sibling — online chip joins the active:scale-95 press
|
|
2233
2642
|
// family (gated on onlineNodes.length > 0 clickable branch,
|
|
2234
2643
|
// same conditional pattern as the working chip above).
|
|
2644
|
+
/* R565: same lit-on-member-alias-match pattern as
|
|
2645
|
+
working chip — online chip routes hover to 'idle'
|
|
2646
|
+
tier (see onMouseEnter below), so its member-alias
|
|
2647
|
+
gate is `hoveredAliasTierKey === 'idle'`. */
|
|
2235
2648
|
className={`group tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors transition-transform duration-200 ease-out transform-gpu ${
|
|
2236
2649
|
onlineNodes.length > 0
|
|
2237
|
-
? 'bg-cyan-500/
|
|
2650
|
+
? `${isOnlineChipLit ? 'bg-cyan-500/15 border-cyan-500/30' : 'bg-cyan-500/10 border-cyan-500/20'} text-cyan-300 hover:bg-cyan-500/15 hover:border-cyan-500/30 hover:-translate-y-px active:scale-95`
|
|
2238
2651
|
: 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'
|
|
2239
2652
|
}`}
|
|
2240
2653
|
data-chip-hover-lift={onlineNodes.length > 0 ? 'true' : 'false'}
|
|
2241
2654
|
data-chip-group-hover-brighten="true"
|
|
2655
|
+
data-online-chip-member-alias-lit={isOnlineChipLit ? 'true' : 'false'}
|
|
2242
2656
|
data-online-chip
|
|
2243
2657
|
data-online-chip-aliases={onlineAliases.join(',')}
|
|
2244
2658
|
data-pin-mirror={pinnedStatus === 'idle' ? 'true' : 'false'}
|
|
@@ -2284,7 +2698,9 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2284
2698
|
>
|
|
2285
2699
|
{/* R337 sibling — online chip unit demotion. */}
|
|
2286
2700
|
{/* R362 sibling — online-chip digit gains font-semibold. */}
|
|
2287
|
-
|
|
2701
|
+
{/* R539 sibling — online chip digit. Same idiom as
|
|
2702
|
+
working chip above (group-hover:tracking-wide). */}
|
|
2703
|
+
<span className="font-semibold transition-[font-weight,letter-spacing] duration-200 group-hover:font-bold group-hover:tracking-wide" data-online-chip-digit>{onlineNodes.length}</span><span className="opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide" data-online-chip-unit> online</span>
|
|
2288
2704
|
</span>
|
|
2289
2705
|
</>
|
|
2290
2706
|
);
|
|
@@ -2299,6 +2715,31 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2299
2715
|
const o = offlineNodes.length;
|
|
2300
2716
|
const total = w + i + o;
|
|
2301
2717
|
if (total === 0) return null;
|
|
2718
|
+
/* Round 563 / Loop — inspection-overrides-encoding family
|
|
2719
|
+
6th anchor at the pressure-bar segment scope. When
|
|
2720
|
+
operator hovers a NODE ALIAS on the canvas, the segment
|
|
2721
|
+
matching that node's status tier lights up with its
|
|
2722
|
+
R210 brightness + R542 drop-shadow treatment — mirror
|
|
2723
|
+
of R562 legend-swatch pattern at the pressure-bar scope.
|
|
2724
|
+
Family progression (6 anchors):
|
|
2725
|
+
R484 recent-row timestamp alias hover
|
|
2726
|
+
R485 edge particle opacity alias hover
|
|
2727
|
+
R486 minimap dot opacity alias hover
|
|
2728
|
+
R561 group-label + ants-gate member-alias hover
|
|
2729
|
+
R562 legend-swatch r + glow member-alias status match
|
|
2730
|
+
R563 pressure-seg brightness member-alias status match ← this round
|
|
2731
|
+
Same status-tier-match computation as R562 (banked
|
|
2732
|
+
idiom): find the hovered alias's session, map to
|
|
2733
|
+
working/idle/offline tier, then per-segment check
|
|
2734
|
+
`hoveredAliasRowKey === key`. Computed once at IIFE
|
|
2735
|
+
scope, used inside the seg() closure. */
|
|
2736
|
+
const hoveredSession = hoveredAlias
|
|
2737
|
+
? (onlineNodes.find(s => s.alias === hoveredAlias) ?? offlineNodes.find(s => s.alias === hoveredAlias))
|
|
2738
|
+
: null;
|
|
2739
|
+
const hoveredAliasTierKey: 'working' | 'idle' | 'offline' | null = !hoveredSession ? null
|
|
2740
|
+
: hoveredSession.status === 'working' ? 'working'
|
|
2741
|
+
: offlineNodes.includes(hoveredSession) ? 'offline'
|
|
2742
|
+
: 'idle';
|
|
2302
2743
|
// Round 60 / Loop: each segment toggles a sticky filter via
|
|
2303
2744
|
// `pinnedStatus`. Click the working segment → all non-working
|
|
2304
2745
|
// nodes dim; click again → release. Segments share width with
|
|
@@ -2311,6 +2752,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2311
2752
|
const seg = (n: number, color: string, key: 'working' | 'idle' | 'offline', label: string) => {
|
|
2312
2753
|
if (n === 0) return null;
|
|
2313
2754
|
const isPinned = pinnedStatus === key;
|
|
2755
|
+
// R563: member-alias-matching flag — when operator hovers
|
|
2756
|
+
// a node alias whose status matches this segment's tier.
|
|
2757
|
+
const isMemberAliasMatching = hoveredAliasTierKey === key;
|
|
2758
|
+
const isSegLit = hoveredStatus === key || isMemberAliasMatching;
|
|
2314
2759
|
// R102: list the aliases that match this segment's bucket
|
|
2315
2760
|
// so the title answers WHICH n, not just HOW MANY. Closes
|
|
2316
2761
|
// the last "info-density gap" in the chip-row surfaces
|
|
@@ -2330,6 +2775,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2330
2775
|
data-pressure-seg={key}
|
|
2331
2776
|
data-pressure-seg-aliases={matchAliases.join(',')}
|
|
2332
2777
|
data-pressure-seg-hovered={hoveredStatus === key ? 'true' : 'false'}
|
|
2778
|
+
data-pressure-seg-member-alias-matching={isMemberAliasMatching ? 'true' : 'false'}
|
|
2779
|
+
data-pressure-seg-lit={isSegLit ? 'true' : 'false'}
|
|
2333
2780
|
role="button"
|
|
2334
2781
|
tabIndex={0}
|
|
2335
2782
|
aria-pressed={isPinned}
|
|
@@ -2373,7 +2820,47 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2373
2820
|
height: '100%',
|
|
2374
2821
|
cursor: 'pointer',
|
|
2375
2822
|
boxShadow: isPinned ? `inset 0 0 0 1px ${color}, inset 0 0 0 2px rgba(255,255,255,0.6)` : undefined,
|
|
2376
|
-
|
|
2823
|
+
/* Round 542 / Loop — pressure-bar segments gain
|
|
2824
|
+
drop-shadow tier-color glow on hover, stacked
|
|
2825
|
+
on R210 brightness(1.2). Sibling to R537 legend
|
|
2826
|
+
swatch + R541 vendor chip glow at the chip-row
|
|
2827
|
+
scope — three same-pattern surfaces (legend
|
|
2828
|
+
swatch / vendor chip / pressure segment) all
|
|
2829
|
+
radiate their identity color on hover.
|
|
2830
|
+
3rd anchor in the chip-row tier-color paint
|
|
2831
|
+
glow sub-family:
|
|
2832
|
+
R537 legend swatch row.fill (status hex)
|
|
2833
|
+
R541 vendor chip v.color (hsl via color-mix)
|
|
2834
|
+
R542 pressure seg color (status hex) ← this round
|
|
2835
|
+
Stacked filter syntax (brightness + drop-shadow
|
|
2836
|
+
in same filter declaration): `brightness(1.2)
|
|
2837
|
+
drop-shadow(...)`. CSS filter supports multiple
|
|
2838
|
+
functions; they apply left-to-right. Brightness
|
|
2839
|
+
boosts the segment's own color, drop-shadow
|
|
2840
|
+
paints the outer halo. Together: hovered seg
|
|
2841
|
+
looks "lit up" with both inner glow + outer
|
|
2842
|
+
halo in its tier color.
|
|
2843
|
+
Hue: `${color}99` hex+alpha (60%) — color here
|
|
2844
|
+
is a 6-char hex (e.g., '#22c55e' for working
|
|
2845
|
+
cyber, '#0d9488' for idle light), not hsl, so
|
|
2846
|
+
hex+alpha concat works (unlike R541 vendor
|
|
2847
|
+
which needed color-mix for hsl). Banked
|
|
2848
|
+
pattern: hex sources use hex+alpha; hsl/color()
|
|
2849
|
+
sources use color-mix.
|
|
2850
|
+
2px blur (vs R537's 3px) since pressure-seg is
|
|
2851
|
+
small (h-2 = 8px tall, variable width) — a
|
|
2852
|
+
smaller blur keeps the glow tight to the
|
|
2853
|
+
segment without bleeding into neighbors.
|
|
2854
|
+
filter is paint-only; bbox unchanged; R51
|
|
2855
|
+
overlap-test invariants hold. Transition list
|
|
2856
|
+
already includes `filter` (post-R524). */
|
|
2857
|
+
/* R563: filter lifts on EITHER direct hover OR member-
|
|
2858
|
+
alias-matching (operator inspecting a node whose
|
|
2859
|
+
status matches this segment's tier). Same R210
|
|
2860
|
+
brightness + R542 drop-shadow value across both
|
|
2861
|
+
gate sources — uniform visual response, distinct
|
|
2862
|
+
semantic gates. */
|
|
2863
|
+
filter: isSegLit ? `brightness(1.2) drop-shadow(0 0 2px ${color}99)` : undefined,
|
|
2377
2864
|
transition: 'width 220ms ease-out, box-shadow 150ms ease-out, filter 150ms ease-out',
|
|
2378
2865
|
}}
|
|
2379
2866
|
onClick={(e) => {
|
|
@@ -2518,6 +3005,33 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2518
3005
|
className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2519
3006
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
|
|
2520
3007
|
onClick={() => setPinnedStatus(null)}
|
|
3008
|
+
/* Round 543 / Loop — status filter pill gains always-on
|
|
3009
|
+
tier-color drop-shadow when rendered. Pre-R543 the
|
|
3010
|
+
pill carried bg-tint + tier-color text + border but
|
|
3011
|
+
no outer paint extent — it sat as a flat tinted chip
|
|
3012
|
+
in the chip row. R543 adds an outer glow at the
|
|
3013
|
+
pill's text color so the pill radiates a soft tier-
|
|
3014
|
+
colored halo signaling "this filter is active." Pin
|
|
3015
|
+
pill only renders when pinnedStatus is set (the JSX
|
|
3016
|
+
gate above), so the drop-shadow appearing reinforces
|
|
3017
|
+
the visual "active pin" state.
|
|
3018
|
+
Sibling pattern: R477 legend pin-ring also paints a
|
|
3019
|
+
pin-gated tier-color drop-shadow. Pin pill follows
|
|
3020
|
+
the same "pin-gated paint glow" semantics but at the
|
|
3021
|
+
chip-row scope vs the panel-row scope. The chip-row
|
|
3022
|
+
tier-color glow trio (R537/R541/R542 hover-gated)
|
|
3023
|
+
plus R543 (pin-gated, this round) closes the chip-
|
|
3024
|
+
row paint-glow family across BOTH gate types
|
|
3025
|
+
(hover for transient affordance, pin for sticky
|
|
3026
|
+
active-state visual).
|
|
3027
|
+
Hue: explicit tier color (extracted from the
|
|
3028
|
+
existing `color` ternary). 0x99 alpha (~60%) +
|
|
3029
|
+
3px blur. Stays inside the same color hierarchy
|
|
3030
|
+
as the pill's own text/border (currentColor).
|
|
3031
|
+
R543 status pill scope only — R543's pattern can
|
|
3032
|
+
future-extend to group/vendor/edge filter pills
|
|
3033
|
+
(3 more variants at lines 2683/2755/2824). Out of
|
|
3034
|
+
scope to keep R543 single-pill. */
|
|
2521
3035
|
style={{
|
|
2522
3036
|
background: pinnedStatus === 'working' ? (isLight ? '#05966914' : '#22c55e1f')
|
|
2523
3037
|
: pinnedStatus === 'idle' ? (isLight ? '#0d948814' : '#2dd4bf1f')
|
|
@@ -2527,6 +3041,11 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2527
3041
|
: (isLight ? '#475569' : '#9ca3af'),
|
|
2528
3042
|
borderColor: 'currentColor',
|
|
2529
3043
|
cursor: 'pointer',
|
|
3044
|
+
filter: `drop-shadow(0 0 3px ${
|
|
3045
|
+
pinnedStatus === 'working' ? (isLight ? '#047857' : '#86efac')
|
|
3046
|
+
: pinnedStatus === 'idle' ? (isLight ? '#0f766e' : '#5eead4')
|
|
3047
|
+
: (isLight ? '#475569' : '#9ca3af')
|
|
3048
|
+
}99)`,
|
|
2530
3049
|
}}
|
|
2531
3050
|
>
|
|
2532
3051
|
{/* Round 412 / Loop: filter pin pill VALUE picks up the
|
|
@@ -2540,7 +3059,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2540
3059
|
to R333/R335-R341/R362/R369/R389/R410. data-filter-
|
|
2541
3060
|
value attr surfaces the value span for tests.
|
|
2542
3061
|
4-pill replace family — status / group / vendor / edge. */}
|
|
2543
|
-
<span><span className="hidden sm:inline opacity-70 transition-opacity duration-200 group-hover:opacity-100" data-filter-prefix>filter: </span><span className="font-semibold" data-filter-value>{pinnedStatus}</span><span className="opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100" data-filter-pill-count> · {matchCount}</span></span>
|
|
3062
|
+
<span><span className="hidden sm:inline opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide" data-filter-prefix>filter: </span><span className="font-semibold" data-filter-value>{pinnedStatus}</span><span className="opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100" data-filter-pill-count> · {matchCount}</span></span>
|
|
2544
3063
|
<button
|
|
2545
3064
|
type="button"
|
|
2546
3065
|
aria-label={`Clear ${pinnedStatus} filter`}
|
|
@@ -2559,7 +3078,24 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2559
3078
|
inline-block is default for <button> so no display
|
|
2560
3079
|
tweak needed. replace_all covers all 4 filter pin
|
|
2561
3080
|
pills (status / group / vendor / edge) at once. */
|
|
2562
|
-
|
|
3081
|
+
/* Round 547 / Loop — extends pill × close-button hover
|
|
3082
|
+
gesture from scale-110 (R356) + opacity-70 to ALSO
|
|
3083
|
+
include rotate-12 on hover. Pre-R547 the × dimmed
|
|
3084
|
+
and grew on hover; R547 adds a 12° twist so the
|
|
3085
|
+
close action telegraphs "discarding/spinning away"
|
|
3086
|
+
with a small delight gesture. Composes with
|
|
3087
|
+
transition-transform (existing) — Tailwind's
|
|
3088
|
+
hover:rotate-12 + hover:scale-110 stack into one
|
|
3089
|
+
transform under the same 200ms ease-out tween.
|
|
3090
|
+
Applied to all 4 pill × buttons (status / group /
|
|
3091
|
+
vendor / edge) via replace_all since the className
|
|
3092
|
+
is identical. Closes the pill × hover gesture
|
|
3093
|
+
vocabulary at 3 axes:
|
|
3094
|
+
hover:opacity-70 paint dim
|
|
3095
|
+
hover:scale-110 geometry grow (R356)
|
|
3096
|
+
hover:rotate-12 geometry twist (R547, this round)
|
|
3097
|
+
Hover-gesture parity across the 4-pill family. */
|
|
3098
|
+
className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 hover:rotate-12 transform-gpu"
|
|
2563
3099
|
style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
|
|
2564
3100
|
>×</button>
|
|
2565
3101
|
</span>
|
|
@@ -2588,15 +3124,42 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2588
3124
|
className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2589
3125
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
|
|
2590
3126
|
onClick={() => setPinnedGroup(null)}
|
|
3127
|
+
/* Round 544 / Loop — extends R543 pin-active filter-pill
|
|
3128
|
+
drop-shadow pattern to the GROUP pill (2nd of 4 pill
|
|
3129
|
+
variants). Pre-R544 the group pill carried bg-tint +
|
|
3130
|
+
pal.legendAccent text/border but no outer paint glow.
|
|
3131
|
+
R544 adds the matching cyan-accent drop-shadow so the
|
|
3132
|
+
group pin pill radiates the same paint glow as R543
|
|
3133
|
+
status pill — pin-active visual signal at chip-row
|
|
3134
|
+
scope.
|
|
3135
|
+
Hue: pal.legendAccent (cyber #67e8f9 cyan-300 /
|
|
3136
|
+
light #0d9488 teal-600). Uses color-mix() syntax
|
|
3137
|
+
because pal.legendAccent may resolve to hex; same
|
|
3138
|
+
syntax works for both hex and hsl sources (banked
|
|
3139
|
+
R541 lesson). 60% alpha + 3px blur — same intensity
|
|
3140
|
+
as R543 status pill so the pin-active visual signal
|
|
3141
|
+
reads with matching brightness across pill variants.
|
|
3142
|
+
Pin-active tier-color paint glow sub-family
|
|
3143
|
+
(CLOSED progressively):
|
|
3144
|
+
R477 legend pin-ring (panel-row, row.fill)
|
|
3145
|
+
R543 status pill (chip-row, tier-color)
|
|
3146
|
+
R544 group pill (chip-row, legendAccent)
|
|
3147
|
+
← this round
|
|
3148
|
+
Out of scope: vendor pill (line ~2755) + edge pill
|
|
3149
|
+
(line ~2824) — can future-extend in subsequent
|
|
3150
|
+
rounds (R545/R546). Both use the same R543 idiom:
|
|
3151
|
+
always-on drop-shadow when rendered, color from the
|
|
3152
|
+
pill's existing text color. */
|
|
2591
3153
|
style={{
|
|
2592
3154
|
background: isLight ? '#67e8f914' : '#67e8f91f',
|
|
2593
3155
|
color: pal.legendAccent,
|
|
2594
3156
|
borderColor: 'currentColor',
|
|
2595
3157
|
cursor: 'pointer',
|
|
3158
|
+
filter: `drop-shadow(0 0 3px color-mix(in srgb, ${pal.legendAccent} 60%, transparent))`,
|
|
2596
3159
|
}}
|
|
2597
3160
|
>
|
|
2598
3161
|
{/* R412: see status pill above — filter value fw=600 data tier. */}
|
|
2599
|
-
<span><span className="hidden sm:inline opacity-70 transition-opacity duration-200 group-hover:opacity-100" data-filter-prefix>filter: </span><span className="font-semibold" data-filter-value>{pinnedGroup}</span><span className="opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100" data-filter-pill-count> · {matchCount}</span></span>
|
|
3162
|
+
<span><span className="hidden sm:inline opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide" data-filter-prefix>filter: </span><span className="font-semibold" data-filter-value>{pinnedGroup}</span><span className="opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100" data-filter-pill-count> · {matchCount}</span></span>
|
|
2600
3163
|
<button
|
|
2601
3164
|
type="button"
|
|
2602
3165
|
aria-label={`Clear group filter ${pinnedGroup}`}
|
|
@@ -2615,7 +3178,24 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2615
3178
|
inline-block is default for <button> so no display
|
|
2616
3179
|
tweak needed. replace_all covers all 4 filter pin
|
|
2617
3180
|
pills (status / group / vendor / edge) at once. */
|
|
2618
|
-
|
|
3181
|
+
/* Round 547 / Loop — extends pill × close-button hover
|
|
3182
|
+
gesture from scale-110 (R356) + opacity-70 to ALSO
|
|
3183
|
+
include rotate-12 on hover. Pre-R547 the × dimmed
|
|
3184
|
+
and grew on hover; R547 adds a 12° twist so the
|
|
3185
|
+
close action telegraphs "discarding/spinning away"
|
|
3186
|
+
with a small delight gesture. Composes with
|
|
3187
|
+
transition-transform (existing) — Tailwind's
|
|
3188
|
+
hover:rotate-12 + hover:scale-110 stack into one
|
|
3189
|
+
transform under the same 200ms ease-out tween.
|
|
3190
|
+
Applied to all 4 pill × buttons (status / group /
|
|
3191
|
+
vendor / edge) via replace_all since the className
|
|
3192
|
+
is identical. Closes the pill × hover gesture
|
|
3193
|
+
vocabulary at 3 axes:
|
|
3194
|
+
hover:opacity-70 paint dim
|
|
3195
|
+
hover:scale-110 geometry grow (R356)
|
|
3196
|
+
hover:rotate-12 geometry twist (R547, this round)
|
|
3197
|
+
Hover-gesture parity across the 4-pill family. */
|
|
3198
|
+
className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 hover:rotate-12 transform-gpu"
|
|
2619
3199
|
style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
|
|
2620
3200
|
>×</button>
|
|
2621
3201
|
</span>
|
|
@@ -2660,15 +3240,35 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2660
3240
|
className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2661
3241
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear vendor filter'}
|
|
2662
3242
|
onClick={() => setPinnedVendor(null)}
|
|
3243
|
+
/* Round 545 / Loop — extends pin-active filter-pill drop-
|
|
3244
|
+
shadow pattern to VENDOR pill (3rd of 4 pill variants
|
|
3245
|
+
after R543 status + R544 group). vendorColor is HSL
|
|
3246
|
+
format (banked R541 lesson — vendorDist.color sources
|
|
3247
|
+
from mono.text in vendorIdentity.ts, which is `hsl(...)`),
|
|
3248
|
+
so the filter uses color-mix() syntax — same as R544.
|
|
3249
|
+
60% alpha + 3px blur, matching R543/R544 intensity for
|
|
3250
|
+
consistent pin-active visual signal across all pill
|
|
3251
|
+
variants.
|
|
3252
|
+
Pin-active tier-color paint glow sub-family (progressive
|
|
3253
|
+
extension, 1 pill variant remaining):
|
|
3254
|
+
R477 legend pin-ring (panel-row, row.fill, hex+alpha)
|
|
3255
|
+
R543 status pill (chip-row, tier-color, hex+alpha)
|
|
3256
|
+
R544 group pill (chip-row, legendAccent, color-mix)
|
|
3257
|
+
R545 vendor pill (chip-row, vendorColor, color-mix)
|
|
3258
|
+
← this round
|
|
3259
|
+
Out of scope: edge pill (line ~2824 pre-R545, now ~2900+).
|
|
3260
|
+
Final 1/4 pill remaining for a future round closes the
|
|
3261
|
+
sub-family. */
|
|
2663
3262
|
style={{
|
|
2664
3263
|
background: `${vendorColor}1f`,
|
|
2665
3264
|
color: vendorColor,
|
|
2666
3265
|
borderColor: 'currentColor',
|
|
2667
3266
|
cursor: 'pointer',
|
|
3267
|
+
filter: `drop-shadow(0 0 3px color-mix(in srgb, ${vendorColor} 60%, transparent))`,
|
|
2668
3268
|
}}
|
|
2669
3269
|
>
|
|
2670
3270
|
{/* R412: see status pill above — filter value fw=600 data tier. */}
|
|
2671
|
-
<span><span className="hidden sm:inline opacity-70 transition-opacity duration-200 group-hover:opacity-100" data-filter-prefix>filter: </span><span className="font-semibold" data-filter-value>{pinnedVendor}</span><span className="opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100" data-filter-pill-count> · {matchCount}</span></span>
|
|
3271
|
+
<span><span className="hidden sm:inline opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide" data-filter-prefix>filter: </span><span className="font-semibold" data-filter-value>{pinnedVendor}</span><span className="opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100" data-filter-pill-count> · {matchCount}</span></span>
|
|
2672
3272
|
<button
|
|
2673
3273
|
type="button"
|
|
2674
3274
|
aria-label={`Clear vendor filter ${pinnedVendor}`}
|
|
@@ -2687,7 +3287,24 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2687
3287
|
inline-block is default for <button> so no display
|
|
2688
3288
|
tweak needed. replace_all covers all 4 filter pin
|
|
2689
3289
|
pills (status / group / vendor / edge) at once. */
|
|
2690
|
-
|
|
3290
|
+
/* Round 547 / Loop — extends pill × close-button hover
|
|
3291
|
+
gesture from scale-110 (R356) + opacity-70 to ALSO
|
|
3292
|
+
include rotate-12 on hover. Pre-R547 the × dimmed
|
|
3293
|
+
and grew on hover; R547 adds a 12° twist so the
|
|
3294
|
+
close action telegraphs "discarding/spinning away"
|
|
3295
|
+
with a small delight gesture. Composes with
|
|
3296
|
+
transition-transform (existing) — Tailwind's
|
|
3297
|
+
hover:rotate-12 + hover:scale-110 stack into one
|
|
3298
|
+
transform under the same 200ms ease-out tween.
|
|
3299
|
+
Applied to all 4 pill × buttons (status / group /
|
|
3300
|
+
vendor / edge) via replace_all since the className
|
|
3301
|
+
is identical. Closes the pill × hover gesture
|
|
3302
|
+
vocabulary at 3 axes:
|
|
3303
|
+
hover:opacity-70 paint dim
|
|
3304
|
+
hover:scale-110 geometry grow (R356)
|
|
3305
|
+
hover:rotate-12 geometry twist (R547, this round)
|
|
3306
|
+
Hover-gesture parity across the 4-pill family. */
|
|
3307
|
+
className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 hover:rotate-12 transform-gpu"
|
|
2691
3308
|
style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
|
|
2692
3309
|
>×</button>
|
|
2693
3310
|
</span>
|
|
@@ -2726,11 +3343,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2726
3343
|
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2727
3344
|
title={`${link.from} → ${link.to} (${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ', hot lane · ≥ 10' : ''}) — click to clear`}
|
|
2728
3345
|
onClick={() => setPinnedEdgeKey(null)}
|
|
3346
|
+
/* Round 546 / Loop — CLOSES pin-active filter-pill drop-
|
|
3347
|
+
shadow sub-family at the 4th and final pill variant
|
|
3348
|
+
(edge pill). R543 (status) + R544 (group) + R545
|
|
3349
|
+
(vendor) covered the first three; R546 closes at
|
|
3350
|
+
edge.
|
|
3351
|
+
Pin-active tier-color paint glow sub-family CLOSED
|
|
3352
|
+
(4 anchors):
|
|
3353
|
+
R477 legend pin-ring (panel-row, row.fill)
|
|
3354
|
+
R543 status pill (chip-row, tier-color text)
|
|
3355
|
+
R544 group pill (chip-row, legendAccent)
|
|
3356
|
+
R545 vendor pill (chip-row, vendorColor)
|
|
3357
|
+
R546 edge pill (chip-row, pal.flowEdge)
|
|
3358
|
+
← this round, family CLOSED
|
|
3359
|
+
All 4 filter pin pills now radiate paint glow in the
|
|
3360
|
+
same hue family as their text/border on render —
|
|
3361
|
+
pin-active visual signal uniform across the chip-row
|
|
3362
|
+
pill family.
|
|
3363
|
+
pal.flowEdge is theme-driven (dynamic); color-mix
|
|
3364
|
+
syntax safe-defaults regardless of resolved format
|
|
3365
|
+
(banked R541/R544/R545 pattern). 60% alpha + 3px blur
|
|
3366
|
+
— same intensity as R543/R544/R545 for consistent
|
|
3367
|
+
cross-pill visual signal. */
|
|
2729
3368
|
style={{
|
|
2730
3369
|
background: isLight ? `${pal.flowEdge}14` : `${pal.flowEdge}1f`,
|
|
2731
3370
|
color: pal.flowEdge,
|
|
2732
3371
|
borderColor: 'currentColor',
|
|
2733
3372
|
cursor: 'pointer',
|
|
3373
|
+
filter: `drop-shadow(0 0 3px color-mix(in srgb, ${pal.flowEdge} 60%, transparent))`,
|
|
2734
3374
|
}}
|
|
2735
3375
|
>
|
|
2736
3376
|
{/* R412: filter pin pill value (edge variant) picks up fw=600.
|
|
@@ -2783,7 +3423,24 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2783
3423
|
inline-block is default for <button> so no display
|
|
2784
3424
|
tweak needed. replace_all covers all 4 filter pin
|
|
2785
3425
|
pills (status / group / vendor / edge) at once. */
|
|
2786
|
-
|
|
3426
|
+
/* Round 547 / Loop — extends pill × close-button hover
|
|
3427
|
+
gesture from scale-110 (R356) + opacity-70 to ALSO
|
|
3428
|
+
include rotate-12 on hover. Pre-R547 the × dimmed
|
|
3429
|
+
and grew on hover; R547 adds a 12° twist so the
|
|
3430
|
+
close action telegraphs "discarding/spinning away"
|
|
3431
|
+
with a small delight gesture. Composes with
|
|
3432
|
+
transition-transform (existing) — Tailwind's
|
|
3433
|
+
hover:rotate-12 + hover:scale-110 stack into one
|
|
3434
|
+
transform under the same 200ms ease-out tween.
|
|
3435
|
+
Applied to all 4 pill × buttons (status / group /
|
|
3436
|
+
vendor / edge) via replace_all since the className
|
|
3437
|
+
is identical. Closes the pill × hover gesture
|
|
3438
|
+
vocabulary at 3 axes:
|
|
3439
|
+
hover:opacity-70 paint dim
|
|
3440
|
+
hover:scale-110 geometry grow (R356)
|
|
3441
|
+
hover:rotate-12 geometry twist (R547, this round)
|
|
3442
|
+
Hover-gesture parity across the 4-pill family. */
|
|
3443
|
+
className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 hover:rotate-12 transform-gpu"
|
|
2787
3444
|
style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
|
|
2788
3445
|
>×</button>
|
|
2789
3446
|
</span>
|
|
@@ -3105,6 +3762,41 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3105
3762
|
// for older browsers the chip falls back to its idle
|
|
3106
3763
|
// transparent bg (graceful degradation — the canvas-
|
|
3107
3764
|
// dim effect still fires regardless).
|
|
3765
|
+
/* Round 541 / Loop — vendor letter chip gains drop-
|
|
3766
|
+
shadow glow on hover/pin using its OWN vendor
|
|
3767
|
+
identity color (v.color). Sibling to R537 legend-
|
|
3768
|
+
swatch tier-color glow at the chip-row scope.
|
|
3769
|
+
Pre-R541 the vendor chip lifted on multiple
|
|
3770
|
+
axes (R354 inner-glyph scale-1.1, R202 bg color-
|
|
3771
|
+
mix tint, R180 box-shadow pin-mirror inset, R401
|
|
3772
|
+
hover-translate-y -1px, R496 active:scale-95
|
|
3773
|
+
press) but no paint-axis glow extending past
|
|
3774
|
+
the chip's bbox. R541 adds the outer glow at
|
|
3775
|
+
the paint axis so the vendor chip's identity
|
|
3776
|
+
color radiates beyond the chip on attention —
|
|
3777
|
+
same idiom as legend swatch tier-color glow.
|
|
3778
|
+
2-tier alpha ladder (mirrors R538 group-label):
|
|
3779
|
+
pin (committed) v.color 99 (~60%)
|
|
3780
|
+
hover (preview) v.color 66 (~40%)
|
|
3781
|
+
rest none
|
|
3782
|
+
Pin is brighter to distinguish locked vs preview
|
|
3783
|
+
at the paint axis. The R180 inset box-shadow
|
|
3784
|
+
(pin-mirror) and R541 outer drop-shadow compose
|
|
3785
|
+
at pin — inside chrome reads as "this is pinned"
|
|
3786
|
+
(inset white double-ring), outside paint reads
|
|
3787
|
+
as "vendor identity is locked" (vendor-colour
|
|
3788
|
+
outer glow). Hover gets only the outer glow.
|
|
3789
|
+
3px blur tuned to read as soft chip-halo without
|
|
3790
|
+
overwhelming adjacent chips in the chip row.
|
|
3791
|
+
filter property is in the .anet-topo-chip-focus
|
|
3792
|
+
class transition list (R524 banked fix), so the
|
|
3793
|
+
filter eases at 200ms naturally.
|
|
3794
|
+
Drop-shadow visual-polish family — R541 adds
|
|
3795
|
+
chip-row tier-color paint glow as another anchor
|
|
3796
|
+
in the same family pattern R537 established.
|
|
3797
|
+
data-vendor-glow attr ('pin' | 'hover' | 'false')
|
|
3798
|
+
exposes the gate state for tests. */
|
|
3799
|
+
data-vendor-glow={isPinned ? 'pin' : hoveredVendor === v.initial ? 'hover' : 'false'}
|
|
3108
3800
|
style={{
|
|
3109
3801
|
cursor: 'pointer',
|
|
3110
3802
|
backgroundColor: (hoveredVendor === v.initial && !isPinned)
|
|
@@ -3113,7 +3805,15 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3113
3805
|
boxShadow: isPinned
|
|
3114
3806
|
? `inset 0 0 0 1px ${v.color}, inset 0 0 0 2px rgba(255,255,255,0.45)`
|
|
3115
3807
|
: undefined,
|
|
3116
|
-
|
|
3808
|
+
/* R578 sibling — vendor chip stacks brightness(1.15)
|
|
3809
|
+
onto R541 drop-shadow. Closes chip-row tier-color
|
|
3810
|
+
glow trio at consistent stacked-filter pattern. */
|
|
3811
|
+
filter: isPinned
|
|
3812
|
+
? `drop-shadow(0 0 3px color-mix(in srgb, ${v.color} 60%, transparent)) brightness(1.15)`
|
|
3813
|
+
: hoveredVendor === v.initial
|
|
3814
|
+
? `drop-shadow(0 0 3px color-mix(in srgb, ${v.color} 40%, transparent)) brightness(1.15)`
|
|
3815
|
+
: undefined,
|
|
3816
|
+
transition: 'box-shadow 150ms ease-out, background-color 200ms ease-out, filter 200ms ease-out',
|
|
3117
3817
|
}}
|
|
3118
3818
|
onMouseEnter={() => setHoveredVendor(v.initial)}
|
|
3119
3819
|
onMouseLeave={() => setHoveredVendor(prev => prev === v.initial ? null : prev)}
|
|
@@ -3205,7 +3905,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3205
3905
|
since the glyph (R369 fw=600) stays at full
|
|
3206
3906
|
opacity. R333 :{count} format preserved. */}
|
|
3207
3907
|
<span
|
|
3208
|
-
className="text-gray-400 tabular-nums opacity-70 transition-opacity duration-200 group-hover:opacity-100"
|
|
3908
|
+
className="text-gray-400 tabular-nums opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide"
|
|
3209
3909
|
data-vendor-letter-count-suffix
|
|
3210
3910
|
>:{v.count}</span>
|
|
3211
3911
|
</span>
|
|
@@ -3355,7 +4055,9 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3355
4055
|
chip-internal-hierarchy arc. data-active-links-
|
|
3356
4056
|
chip-unit exposes the unit span for tests. */}
|
|
3357
4057
|
{/* R362 sibling — active-links chip digit gains font-semibold. */}
|
|
3358
|
-
|
|
4058
|
+
{/* R539 sibling — active-links chip digit. Same idiom
|
|
4059
|
+
as working + online above. */}
|
|
4060
|
+
<span className="font-semibold transition-[font-weight,letter-spacing] duration-200 group-hover:font-bold group-hover:tracking-wide" data-active-links-chip-digit>{flowLinks.length}</span><span className="opacity-70 transition-[opacity,letter-spacing] duration-200 group-hover:opacity-100 group-hover:tracking-wide" data-active-links-chip-unit> active link{flowLinks.length === 1 ? '' : 's'}</span>
|
|
3359
4061
|
{rel ? (() => {
|
|
3360
4062
|
// Round 161 / Loop: extend R160's recency-pip
|
|
3361
4063
|
// vocabulary up one scope — from per-flow row to
|
|
@@ -3569,6 +4271,36 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3569
4271
|
on the canvas root for non-visual consumers.
|
|
3570
4272
|
Composed from existing onlineNodes / workingCount /
|
|
3571
4273
|
offlineNodes / flowLinks — no new state. */
|
|
4274
|
+
/* Round 502 / Loop — categorical density-tier paired with the
|
|
4275
|
+
R469 numeric counts. data-topo-fleet-density-tier classifies
|
|
4276
|
+
the fleet size into 5 buckets so external consumers (CSS
|
|
4277
|
+
selectors, Playwright probes, future density-conditional
|
|
4278
|
+
polish gates like R109 dense-label collapse at 16+ nodes)
|
|
4279
|
+
can branch on a stable tier name without re-deriving the
|
|
4280
|
+
threshold logic from the raw numeric. Buckets:
|
|
4281
|
+
'empty' — onlineNodes.length === 0
|
|
4282
|
+
'sparse' — 1-3 nodes
|
|
4283
|
+
'normal' — 4-15 nodes
|
|
4284
|
+
'dense' — 16-30 nodes (matches R109 collapse gate)
|
|
4285
|
+
'very-dense' — 31+ nodes
|
|
4286
|
+
Picks the gate boundaries that already drive CONDITIONAL
|
|
4287
|
+
RENDER decisions elsewhere (R109 denseLayout = >16, R110
|
|
4288
|
+
plain-text fallback) so the tier name is semantically
|
|
4289
|
+
aligned with the visual mode the canvas already switches
|
|
4290
|
+
to. Composed from existing onlineNodes — no new state.
|
|
4291
|
+
12th attr in the canvas state surface set (R462/R466/R467/
|
|
4292
|
+
R469×4/R471×2/R487/R488/R502). 12 attrs covers: build
|
|
4293
|
+
identity, transient/sticky inspection modes, fleet split
|
|
4294
|
+
numerics, fleet density tier, canvas layout/theme, canvas
|
|
4295
|
+
zoom, hover identity. A test harness can snapshot the
|
|
4296
|
+
full canvas state with 12 getAttribute calls. */
|
|
4297
|
+
data-topo-fleet-density-tier={
|
|
4298
|
+
onlineNodes.length === 0 ? 'empty' :
|
|
4299
|
+
onlineNodes.length <= 3 ? 'sparse' :
|
|
4300
|
+
onlineNodes.length <= 15 ? 'normal' :
|
|
4301
|
+
onlineNodes.length <= 30 ? 'dense' :
|
|
4302
|
+
'very-dense'
|
|
4303
|
+
}
|
|
3572
4304
|
data-topo-online-count={onlineNodes.length}
|
|
3573
4305
|
data-topo-working-count={workingCount}
|
|
3574
4306
|
data-topo-offline-count={offlineNodes.length}
|
|
@@ -3634,6 +4366,119 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3634
4366
|
categorical) — separate dedicated attrs if/when needed.
|
|
3635
4367
|
Root svg attribute set now 11 attrs total. */
|
|
3636
4368
|
data-topo-hovered-alias={hoveredAlias ?? ''}
|
|
4369
|
+
/* Round 504 / Loop — categorical pin-aspect attr paired with
|
|
4370
|
+
R467 any-pinned boolean and R488 hovered-alias identity.
|
|
4371
|
+
Pre-R504 the canvas state surface set told tests WHETHER
|
|
4372
|
+
any pin was active (R467 boolean) but tests had to enumerate
|
|
4373
|
+
4 individual state vars to determine WHICH pin axis fired:
|
|
4374
|
+
pinnedStatus legend-row status filter
|
|
4375
|
+
pinnedGroup prefix-cluster lock
|
|
4376
|
+
pinnedVendor vendor-chip filter
|
|
4377
|
+
pinnedEdgeKey edge-focus
|
|
4378
|
+
R504 surfaces the active aspect as a single categorical
|
|
4379
|
+
attribute: data-topo-pinned-aspect ∈
|
|
4380
|
+
'none' no pin active
|
|
4381
|
+
'status' pinnedStatus only
|
|
4382
|
+
'group' pinnedGroup only
|
|
4383
|
+
'vendor' pinnedVendor only
|
|
4384
|
+
'edge' pinnedEdgeKey only
|
|
4385
|
+
'multi' 2 or more pins active simultaneously
|
|
4386
|
+
('multi' covers cross-cutting filters — e.g. user pins
|
|
4387
|
+
status='working' AND vendor='claude' simultaneously to
|
|
4388
|
+
narrow the canvas. Each pin axis is independently
|
|
4389
|
+
dismissable via Esc / individual chip click, so multi
|
|
4390
|
+
states are reachable and worth surfacing as a distinct
|
|
4391
|
+
tier.)
|
|
4392
|
+
13th attr in the canvas state surface set after R502.
|
|
4393
|
+
Composed from 4 existing state vars — no new state. */
|
|
4394
|
+
/* Round 512 / Loop — 14th canvas state attr. groupBoxes.length
|
|
4395
|
+
surfaces the count of cluster boxes currently rendered in
|
|
4396
|
+
grid layout (always 0 in ring). Paired with R502 categorical
|
|
4397
|
+
density tier + R469 fleet numerics for a complete cluster-
|
|
4398
|
+
cardinality surface:
|
|
4399
|
+
R469 data-topo-online-count node-count
|
|
4400
|
+
R502 data-topo-fleet-density-tier categorical
|
|
4401
|
+
R512 data-topo-cluster-count cluster-count ← this round
|
|
4402
|
+
Use cases:
|
|
4403
|
+
- Playwright: assert orphan-band existence by
|
|
4404
|
+
`cluster-count === N + 1` vs prefix-only `=== N`
|
|
4405
|
+
- external CSS: `[data-topo-cluster-count='1']` to apply
|
|
4406
|
+
single-cluster grid-specific layout adjustments
|
|
4407
|
+
- future polish gates: cluster-count > N could trigger
|
|
4408
|
+
dense-grid mode
|
|
4409
|
+
Composed from existing `groupBoxes.length` — no new state.
|
|
4410
|
+
Always renders (0 in ring layout, N in grid), so tests can
|
|
4411
|
+
rely on attribute presence + value. */
|
|
4412
|
+
data-topo-cluster-count={groupBoxes.length}
|
|
4413
|
+
/* Round 513 / Loop — 15th canvas state attr. Surfaces the
|
|
4414
|
+
user's prefers-reduced-motion preference directly on the
|
|
4415
|
+
root SVG so external CSS / Playwright tests can branch on
|
|
4416
|
+
a11y state without re-reading the media query.
|
|
4417
|
+
reducedMotion is already in component scope (R29 a11y
|
|
4418
|
+
blanket reads it via a useEffect listener); R513 just
|
|
4419
|
+
exposes it as a stable attribute handle.
|
|
4420
|
+
Use cases:
|
|
4421
|
+
- Playwright: assert reduced-motion gates from one attr
|
|
4422
|
+
read instead of mocking media-query state per test
|
|
4423
|
+
- External CSS hooks: `[data-topo-prefers-reduced-motion=
|
|
4424
|
+
"true"]` to apply paint-only overrides (e.g. mute
|
|
4425
|
+
hover glows entirely on a11y instead of just
|
|
4426
|
+
disabling transitions)
|
|
4427
|
+
- Future polish rounds: any motion-gated render can
|
|
4428
|
+
read this attr server-side without the media-query
|
|
4429
|
+
hydration mismatch risk
|
|
4430
|
+
'true' / 'false' string values (consistent with R466/R467
|
|
4431
|
+
boolean attrs). */
|
|
4432
|
+
data-topo-prefers-reduced-motion={reducedMotion ? 'true' : 'false'}
|
|
4433
|
+
/* Round 515 / Loop — 16th canvas state attr. Surfaces the
|
|
4434
|
+
fullscreen-mode state directly on root SVG so external
|
|
4435
|
+
consumers don't have to traverse the chrome strip's
|
|
4436
|
+
`data-topo-chrome-fullscreen-active` button attr (which
|
|
4437
|
+
measures the BUTTON state, not the canvas state — they
|
|
4438
|
+
agree, but reading from the root is semantically cleaner
|
|
4439
|
+
for canvas-state probes).
|
|
4440
|
+
Composed from existing isFullscreen React state (R103
|
|
4441
|
+
fullscreen toggle).
|
|
4442
|
+
Use cases:
|
|
4443
|
+
- Playwright: assert canvas mode in one attr read
|
|
4444
|
+
(paired with R471 data-topo-layout for ring/grid +
|
|
4445
|
+
R487 data-topo-zoom for zoom level + R513 reduced-
|
|
4446
|
+
motion for a11y mode = 4-axis canvas-mode probe)
|
|
4447
|
+
- External CSS: `[data-topo-fullscreen="true"]` to
|
|
4448
|
+
apply fullscreen-only paint adjustments outside the
|
|
4449
|
+
React tree (e.g. body-level scrollbar hide)
|
|
4450
|
+
'true' / 'false' string values (consistent with R466/
|
|
4451
|
+
R467/R513 boolean attrs). */
|
|
4452
|
+
data-topo-fullscreen={isFullscreen ? 'true' : 'false'}
|
|
4453
|
+
/* Round 516 / Loop — 17th canvas state attr. Surfaces the
|
|
4454
|
+
grid layout's content-bottom y-coordinate so tests can
|
|
4455
|
+
verify grid content doesn't extend past the viewBox or
|
|
4456
|
+
collide with chrome elements positioned below the canvas.
|
|
4457
|
+
Composed from existing gridContentBottom derived state
|
|
4458
|
+
(computed at line ~915 from gy0 + totalRows * cellH + 8).
|
|
4459
|
+
In ring layout, gridContentBottom is 0 (no grid). In grid
|
|
4460
|
+
layout it's the actual pixel y-coordinate where the
|
|
4461
|
+
cluster bands end.
|
|
4462
|
+
Use cases:
|
|
4463
|
+
- Playwright: assert grid layout doesn't exceed viewBox
|
|
4464
|
+
height (680) without re-computing the layout math
|
|
4465
|
+
- External CSS: `[data-topo-grid-content-bottom='0']` to
|
|
4466
|
+
distinguish ring-mode (no grid content) from grid-mode
|
|
4467
|
+
in CSS without parsing layout attr
|
|
4468
|
+
- Future polish gates: if cluster count grows large
|
|
4469
|
+
enough to push grid bottom past viewBox, can trigger
|
|
4470
|
+
a 'compact' mode automatically */
|
|
4471
|
+
data-topo-grid-content-bottom={gridContentBottom}
|
|
4472
|
+
data-topo-pinned-aspect={(() => {
|
|
4473
|
+
const aspects: string[] = [];
|
|
4474
|
+
if (pinnedStatus) aspects.push('status');
|
|
4475
|
+
if (pinnedGroup) aspects.push('group');
|
|
4476
|
+
if (pinnedVendor) aspects.push('vendor');
|
|
4477
|
+
if (pinnedEdgeKey) aspects.push('edge');
|
|
4478
|
+
if (aspects.length === 0) return 'none';
|
|
4479
|
+
if (aspects.length === 1) return aspects[0];
|
|
4480
|
+
return 'multi';
|
|
4481
|
+
})()}
|
|
3637
4482
|
/* Round 466 / Loop — aggregate hover signal on the root SVG.
|
|
3638
4483
|
Exposes a single boolean `data-topo-any-hover` that
|
|
3639
4484
|
reflects whether ANY hover state in the topology is
|
|
@@ -3876,7 +4721,38 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3876
4721
|
const x = ((seed * 13) % 1000);
|
|
3877
4722
|
const y = ((seed * 7) % 680);
|
|
3878
4723
|
const r = (i % 3 === 0) ? 1.2 : 0.7;
|
|
3879
|
-
|
|
4724
|
+
/* Round 523 / Loop — 配色 family extension to a 3rd anchor.
|
|
4725
|
+
Pre-R523 all 14 starfield dots painted at the same
|
|
4726
|
+
hardcoded `#a5b4fc` (indigo-300). The starfield's role
|
|
4727
|
+
is atmospheric depth (R45, R291 comment), but a flat
|
|
4728
|
+
single-hue field reads more like a regular dot grid
|
|
4729
|
+
than a star field — real starlight has color
|
|
4730
|
+
temperature variation (blue-white hot stars / yellow
|
|
4731
|
+
sun-like / cool red).
|
|
4732
|
+
R523 cycles a 3-color deterministic rotation based on
|
|
4733
|
+
`i % 3`:
|
|
4734
|
+
i % 3 === 0 → #a5b4fc indigo-300 (original, cool)
|
|
4735
|
+
i % 3 === 1 → #67e8f9 cyan-300 (cyber accent, hot)
|
|
4736
|
+
i % 3 === 2 → #cbd5e1 slate-300 (neutral, warm white)
|
|
4737
|
+
All three hues sit inside the cyber theme's palette
|
|
4738
|
+
family (indigo / cyan / slate) so the starfield reads
|
|
4739
|
+
varied-but-coherent rather than rainbow. At opacity
|
|
4740
|
+
0.5 (parent <g>) * 0.35-0.50 (per-dot) the temperature
|
|
4741
|
+
shifts are gentle but perceptible — closes the gap
|
|
4742
|
+
between 'dot grid' and 'star field'.
|
|
4743
|
+
配色 family extension (3 anchors): R509/R510 hub-
|
|
4744
|
+
highlight cross-theme fill + R523 starfield color
|
|
4745
|
+
temperature variation. Light theme unaffected
|
|
4746
|
+
(starfield gated `!isLight` so light theme stays
|
|
4747
|
+
clean per R45's original 'white surface stays clean'
|
|
4748
|
+
intent).
|
|
4749
|
+
Deterministic on `i` — no JS hydration mismatch,
|
|
4750
|
+
same SSR/client output. data-topo-starfield-dot-hue
|
|
4751
|
+
attr exposes the resolved hue category for tests. */
|
|
4752
|
+
const hues = ['#a5b4fc', '#67e8f9', '#cbd5e1'] as const;
|
|
4753
|
+
const hueNames = ['indigo', 'cyan', 'slate'] as const;
|
|
4754
|
+
const hueIdx = i % 3;
|
|
4755
|
+
return <circle key={i} cx={x} cy={y} r={r} fill={hues[hueIdx]} opacity={0.35 + (i % 4) * 0.05} data-topo-starfield-dot={i} data-topo-starfield-dot-hue={hueNames[hueIdx]} />;
|
|
3880
4756
|
})}
|
|
3881
4757
|
</g>
|
|
3882
4758
|
)}
|
|
@@ -4414,8 +5290,60 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4414
5290
|
data-topo-hub-spoke-stroke-width={spokeStrokeWidth}
|
|
4415
5291
|
data-topo-hub-spoke-stroke-width-active="2.25"
|
|
4416
5292
|
data-topo-hub-spoke-linecap="round"
|
|
5293
|
+
/* Round 533 / Loop — extends drop-shadow visual-polish
|
|
5294
|
+
family to a 9th anchor: hub spokes gain filter:drop-
|
|
5295
|
+
shadow glow on hub-hover. Subtle 1.5px cyan/teal blur
|
|
5296
|
+
applied across ALL spokes simultaneously when the
|
|
5297
|
+
user hovers the hub — the network mesh visually
|
|
5298
|
+
"lights up" in response to focal attention. Sibling
|
|
5299
|
+
to R476 hub-digit + R532 hub-highlight glow at the
|
|
5300
|
+
same gate (hoveredHub && !reducedMotion); together
|
|
5301
|
+
the three anchors (digit + highlight disc + spokes)
|
|
5302
|
+
form a unified focal-cluster glow that signals
|
|
5303
|
+
"you're focused on the hub" across geometry,
|
|
5304
|
+
paint, and mesh-extent axes.
|
|
5305
|
+
Theme-aware glow palette matches the spoke stroke
|
|
5306
|
+
family:
|
|
5307
|
+
light: rgba(13, 148, 136, 0.4) teal-600
|
|
5308
|
+
cyber: rgba(34, 211, 238, 0.4) cyan-400
|
|
5309
|
+
0.4 alpha keeps the glow subtle across N spokes
|
|
5310
|
+
(30+ at peak fleet sizes) — loud bloom across many
|
|
5311
|
+
edges would compete with the focal cluster itself.
|
|
5312
|
+
1.5px blur is conservative; tuned so each spoke
|
|
5313
|
+
gains a faint outer halo rather than a wide bloom.
|
|
5314
|
+
filter is paint-only; bbox unchanged; existing
|
|
5315
|
+
R241 transition list extends to 'filter 250ms
|
|
5316
|
+
ease-out' matching the spoke transition cadence
|
|
5317
|
+
(250ms, distinct from the 200ms hub-cluster
|
|
5318
|
+
cadence — spokes ease slightly slower since they
|
|
5319
|
+
respond to per-alias state, not just hub state).
|
|
5320
|
+
data-topo-hub-spoke-glow attr exposes the gate
|
|
5321
|
+
state for tests. */
|
|
5322
|
+
data-topo-hub-spoke-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
5323
|
+
/* Round 580 (65-round milestone) — hub-spokes complete
|
|
5324
|
+
the hub-cluster brightness coverage at 5/5 concentric
|
|
5325
|
+
elements. Stacks brightness(1.15) onto R533's drop-
|
|
5326
|
+
shadow — same R564/R570/R571/R572/R573/R574/R575/
|
|
5327
|
+
R577/R578/R579 stacked-filter pattern.
|
|
5328
|
+
Hub-cluster brightness now FULLY CLOSED:
|
|
5329
|
+
hub digit (R575) innermost typo
|
|
5330
|
+
hub-highlight (R574) middle disc
|
|
5331
|
+
hub-hover-ring (R579) outer ring boundary
|
|
5332
|
+
hub-halo (R577) outermost atmosphere
|
|
5333
|
+
hub-spokes (R580) mesh radial lines ← this round
|
|
5334
|
+
5 concentric elements + N mesh radial lines all lift
|
|
5335
|
+
uniformly through stacked drop-shadow + brightness on
|
|
5336
|
+
hub-hover. The hub focal cluster now responds as ONE
|
|
5337
|
+
unified motion-coherent paint pulse from center
|
|
5338
|
+
outward through every layer. */
|
|
5339
|
+
data-topo-hub-spoke-brightness={!reducedMotion && hoveredHub ? '1.15' : '1'}
|
|
4417
5340
|
style={{
|
|
4418
|
-
transition: 'stroke 250ms ease-out, stroke-width 250ms ease-out, opacity 250ms ease-out',
|
|
5341
|
+
transition: 'stroke 250ms ease-out, stroke-width 250ms ease-out, opacity 250ms ease-out, filter 250ms ease-out',
|
|
5342
|
+
filter: !reducedMotion && hoveredHub
|
|
5343
|
+
? (isLight
|
|
5344
|
+
? 'drop-shadow(0 0 1.5px rgba(13, 148, 136, 0.4)) brightness(1.15)'
|
|
5345
|
+
: 'drop-shadow(0 0 1.5px rgba(34, 211, 238, 0.4)) brightness(1.15)')
|
|
5346
|
+
: undefined,
|
|
4419
5347
|
...(isActiveSpoke ? {} : {
|
|
4420
5348
|
animationDelay: `${-(idx * 0.25)}s`,
|
|
4421
5349
|
// CSS var consumed by `.anet-topo-spoke-flow`
|
|
@@ -4436,6 +5364,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4436
5364
|
a node click. Restrained dashed container + group-name label. */}
|
|
4437
5365
|
{groupBoxes.map((box, boxIdx) => {
|
|
4438
5366
|
const isHovered = activeGroup === box.key;
|
|
5367
|
+
/* Round 561 / Loop — inspection-overrides-encoding family
|
|
5368
|
+
4th anchor. When operator hovers a NODE ALIAS on the
|
|
5369
|
+
canvas, the group-label that CONTAINS that node lifts
|
|
5370
|
+
to full opacity, signalling "this is the cluster
|
|
5371
|
+
whose member you're inspecting".
|
|
5372
|
+
Family progression (4 anchors now):
|
|
5373
|
+
R484 recent-row timestamp brightens on alias hover
|
|
5374
|
+
R485 edge particle opacity lifts on alias hover
|
|
5375
|
+
R486 minimap dot opacity lifts on alias hover
|
|
5376
|
+
R561 group-label opacity lifts on member-alias hover ← this round
|
|
5377
|
+
Pure paint axis (opacity only) — same restraint as
|
|
5378
|
+
R486. NOT bundled into the existing `isHovered` flag
|
|
5379
|
+
so the marching-ants live animation (gated on
|
|
5380
|
+
`!isPinned && !isHovered`) keeps running; the box
|
|
5381
|
+
rect stroke widen / fill brighten / letter-spacing
|
|
5382
|
+
/ filter glow gates stay tied to direct label
|
|
5383
|
+
hover/pin semantics.
|
|
5384
|
+
Mirror of R486's pattern: `hoveredAlias === s.alias`
|
|
5385
|
+
extends a focused opacity branch independent from
|
|
5386
|
+
the rest-state encoding (online/offline). R561 here:
|
|
5387
|
+
`groupKeys[hoveredAlias] === box.key` extends a
|
|
5388
|
+
cluster-awareness opacity branch independent from
|
|
5389
|
+
the existing isHovered / isPinned semantics. */
|
|
5390
|
+
const isMemberAliasHovered = !!hoveredAlias && groupKeys[hoveredAlias] === box.key;
|
|
4439
5391
|
// R68: distinguish "locked by click" from "currently hovered".
|
|
4440
5392
|
// R63 made pinned and hovered identical (both hit isHovered
|
|
4441
5393
|
// via activeGroup). A user with one team pinned should see at
|
|
@@ -4563,12 +5515,75 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4563
5515
|
// pinned → fill 0.08 / 0.13, stroke 3 px (locked)
|
|
4564
5516
|
// hovered → fill 0.05 / 0.09, stroke 2 px (inspecting)
|
|
4565
5517
|
// idle → fill 0.025 / 0.045, stroke 1.5 px dashed
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
5518
|
+
/* Round 506 / Loop — category-differentiation family
|
|
5519
|
+
3rd anchor. Orphan band rest-state fillOpacity drops
|
|
5520
|
+
slightly below prefix-group rest (0.025/0.045 →
|
|
5521
|
+
0.015/0.028). Adds a 3rd independent paint
|
|
5522
|
+
differentiator to the orphan visual signature:
|
|
5523
|
+
R499 fontStyle: italic (label text)
|
|
5524
|
+
R503 '3 6' dash pattern (rect stroke)
|
|
5525
|
+
R506 lower fillOpacity (rect fill) ← this round
|
|
5526
|
+
Three independent channels (typography + stroke
|
|
5527
|
+
pattern + fill density) collectively encode the
|
|
5528
|
+
catchall semantic at rest. Pin and hover branches
|
|
5529
|
+
UNCHANGED (still 0.05/0.09 hover, 0.08/0.13 pin) —
|
|
5530
|
+
orphan box gets full visual emphasis on inspection
|
|
5531
|
+
identical to prefix groups; the differentiation
|
|
5532
|
+
lives ONLY in the unsolicited rest state. The
|
|
5533
|
+
~40% drop (0.045 → 0.028 cyber, 0.025 → 0.015
|
|
5534
|
+
light) is subtle enough that the orphan box stays
|
|
5535
|
+
visible at rest, just quieter — matches the
|
|
5536
|
+
"misc bucket, less attention-deserving" semantic
|
|
5537
|
+
without losing the visual anchor.
|
|
5538
|
+
Pure paint axis; bbox unchanged; R51 SVG sentinel
|
|
5539
|
+
safety untouched (overlap-test gates to g[data-
|
|
5540
|
+
node], cluster rect invisible to it).
|
|
5541
|
+
data-group-box-fill-opacity attr surfaces the
|
|
5542
|
+
resolved value for tests. */
|
|
5543
|
+
fillOpacity={
|
|
5544
|
+
isPinned ? (isLight ? 0.08 : 0.13)
|
|
5545
|
+
: isHovered ? (isLight ? 0.05 : 0.09)
|
|
5546
|
+
: box.isOrphan ? (isLight ? 0.015 : 0.028)
|
|
5547
|
+
: (isLight ? 0.025 : 0.045)
|
|
5548
|
+
}
|
|
5549
|
+
data-group-box-fill-opacity={
|
|
5550
|
+
isPinned ? (isLight ? 0.08 : 0.13)
|
|
5551
|
+
: isHovered ? (isLight ? 0.05 : 0.09)
|
|
5552
|
+
: box.isOrphan ? (isLight ? 0.015 : 0.028)
|
|
5553
|
+
: (isLight ? 0.025 : 0.045)
|
|
5554
|
+
}
|
|
4569
5555
|
stroke={(isPinned || isHovered) ? pal.legendAccent : pal.ringStroke}
|
|
4570
5556
|
strokeWidth={isPinned ? 3 : isHovered ? 2 : 1.5}
|
|
4571
|
-
|
|
5557
|
+
/* Round 503 / Loop — category-differentiation family
|
|
5558
|
+
2nd anchor (R499 = orphan label italic, 1st anchor).
|
|
5559
|
+
Orphan band rest-state strokeDasharray switches from
|
|
5560
|
+
'6 6' (prefix-group default) to '3 6' (tighter
|
|
5561
|
+
dashes). Pre-R503 the rect dash pattern was uniform
|
|
5562
|
+
across all bands; combined with R499's italic label,
|
|
5563
|
+
the orphan box now has TWO independent paint/
|
|
5564
|
+
typography differentiators at rest:
|
|
5565
|
+
R499 fontStyle: italic (label text)
|
|
5566
|
+
R503 '3 6' dash pattern (rect stroke) ← this round
|
|
5567
|
+
The R85 marching-ants animation continues to work
|
|
5568
|
+
with the new dash size (uses --march-dur custom
|
|
5569
|
+
property, dash-length-agnostic) — orphan's ants
|
|
5570
|
+
just have a different visual rhythm than prefix-
|
|
5571
|
+
group ants, reinforcing the catchall semantic.
|
|
5572
|
+
Pinned/hovered orphan still gets 'none' (solid
|
|
5573
|
+
stroke) so the hover/pin affordance is preserved
|
|
5574
|
+
— the differentiation lives ONLY in the rest
|
|
5575
|
+
state, never blocking inspection.
|
|
5576
|
+
Pure paint axis; no geometry change; bbox unchanged
|
|
5577
|
+
(strokeDasharray is paint-only). R51 SVG sentinel
|
|
5578
|
+
safety untouched (overlap-test gates to g[data-
|
|
5579
|
+
node], this cluster rect is invisible to it).
|
|
5580
|
+
data-group-box-orphan attr surfaces the gate for
|
|
5581
|
+
tests + future polish references. */
|
|
5582
|
+
strokeDasharray={
|
|
5583
|
+
(isPinned || isHovered) ? 'none' :
|
|
5584
|
+
box.isOrphan ? '3 6' : '6 6'
|
|
5585
|
+
}
|
|
5586
|
+
data-group-box-orphan={box.isOrphan ? 'true' : 'false'}
|
|
4572
5587
|
/* Round 380 / Loop: cluster box stroke gets round
|
|
4573
5588
|
linecap + round linejoin. Sibling SVG stroke-
|
|
4574
5589
|
softening polish to R378 flow-rail linecap + R379
|
|
@@ -4598,6 +5613,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4598
5613
|
data-group-box-linecap="round"
|
|
4599
5614
|
data-group-box-linejoin="round"
|
|
4600
5615
|
data-group-box-geom-transition="x,y,width,height"
|
|
5616
|
+
data-group-box-brightness={(isPinned || isHovered) ? '1.15' : '1'}
|
|
4601
5617
|
// R85: ambient "marching ants" drift on the perimeter
|
|
4602
5618
|
// when this group has at least one working member, and
|
|
4603
5619
|
// neither pin nor hover is active (those treatments
|
|
@@ -4614,10 +5630,29 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4614
5630
|
// motion layers (hub / ring / group) keep a coherent
|
|
4615
5631
|
// tempo grammar. Default 12s when working=0 doesn't
|
|
4616
5632
|
// matter — the className is only applied when working>0.
|
|
4617
|
-
|
|
5633
|
+
/* Round 561 — marching-ants gate refined to halt only
|
|
5634
|
+
on direct-label-hover (or pin), NOT on member-alias-
|
|
5635
|
+
hover. Pre-R561 `isHovered` covered BOTH cases via
|
|
5636
|
+
the line 1044 fallback `hoveredGroup ?? (hoveredAlias
|
|
5637
|
+
→ groupKeys[hoveredAlias])` — so hovering ANY member
|
|
5638
|
+
node halted the ants, treating indirect inspection
|
|
5639
|
+
the same as direct attention.
|
|
5640
|
+
R561 differentiates: ants now keep running during
|
|
5641
|
+
member-alias hover (indirect / cluster-awareness
|
|
5642
|
+
inspection), halting ONLY on direct label hover or
|
|
5643
|
+
pin. The cluster's live signal stays alive while
|
|
5644
|
+
operator inspects member nodes — distinct visual
|
|
5645
|
+
telegraph for "directly attending this cluster"
|
|
5646
|
+
vs "inspecting one of its members".
|
|
5647
|
+
Gate uses `hoveredGroupLabel === box.key` directly
|
|
5648
|
+
(the LABEL hover state) instead of `isHovered`
|
|
5649
|
+
(which combines label-hover + member-alias-hover
|
|
5650
|
+
via activeGroup). data-group-box-live + the live
|
|
5651
|
+
className both flip on the same refined gate. */
|
|
5652
|
+
data-group-box-live={!isPinned && hoveredGroupLabel !== box.key && box.statuses.working > 0 ? 'true' : 'false'}
|
|
4618
5653
|
data-group-box-march-dur={marchDur}
|
|
4619
5654
|
data-group-box-lifted={(isPinned || isHovered) ? 'true' : 'false'}
|
|
4620
|
-
className={!isPinned &&
|
|
5655
|
+
className={!isPinned && hoveredGroupLabel !== box.key && box.statuses.working > 0 ? 'anet-topo-groupbox-live' : undefined}
|
|
4621
5656
|
// R142: drop-shadow filter when pinned or hovered. Box
|
|
4622
5657
|
// visually "rises off the canvas" — same vocabulary
|
|
4623
5658
|
// R18 KPI cards + R135 overlay panels use. Idle group
|
|
@@ -4628,6 +5663,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4628
5663
|
// is preserved.
|
|
4629
5664
|
filter={(isPinned || isHovered) ? 'url(#topo-groupbox-lift)' : undefined}
|
|
4630
5665
|
style={{
|
|
5666
|
+
/* R587 — group box gains stacked brightness(1.15)
|
|
5667
|
+
on hover/pin. 26th anchor in per-element
|
|
5668
|
+
brightness family, 19th in stacked-filter
|
|
5669
|
+
sub-pattern. Inline style.filter overrides the
|
|
5670
|
+
attribute filter (kept intact for the R142
|
|
5671
|
+
documentation trail); stacked syntax preserves
|
|
5672
|
+
the R142 url(#topo-groupbox-lift) SVG drop-shadow
|
|
5673
|
+
lift on hover/pin, with brightness layered on
|
|
5674
|
+
top.
|
|
5675
|
+
|
|
5676
|
+
Same R582/R583 stacked-filter pattern at the
|
|
5677
|
+
group-cluster scope.
|
|
5678
|
+
|
|
5679
|
+
Group box inspection signature now 6 layers
|
|
5680
|
+
(spans paint + stroke + geometry + filter):
|
|
5681
|
+
R68 fillOpacity 0.045 → 0.13 (cyber pin)
|
|
5682
|
+
R68 strokeWidth 1.5 → 2 → 3
|
|
5683
|
+
R68 stroke tint → legendAccent
|
|
5684
|
+
R503 strokeDasharray → 'none' on activation
|
|
5685
|
+
R142 filter → url(#topo-groupbox-lift)
|
|
5686
|
+
R587 filter stack → brightness(1.15) ← this round
|
|
5687
|
+
|
|
5688
|
+
Same existing transition list already includes
|
|
5689
|
+
'filter 200ms ease-out' (R142 cadence). No
|
|
5690
|
+
transition change needed. */
|
|
5691
|
+
filter: (isPinned || isHovered)
|
|
5692
|
+
? 'url(#topo-groupbox-lift) brightness(1.15)'
|
|
5693
|
+
: undefined,
|
|
4631
5694
|
/* Round 248 / Loop: append fill 200ms ease-out to
|
|
4632
5695
|
the existing R66 transition list. Pre-R248 the
|
|
4633
5696
|
rect's fill (isLight ? '#0f172a' (slate-900) :
|
|
@@ -4888,8 +5951,54 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4888
5951
|
fontSize="9"
|
|
4889
5952
|
fontFamily="monospace"
|
|
4890
5953
|
fontWeight={isPinned ? '800' : '700'}
|
|
4891
|
-
|
|
5954
|
+
/* Round 551 / Loop — category-differentiation family
|
|
5955
|
+
4th anchor. Orphan band ("其他" catchall) rest-state
|
|
5956
|
+
LABEL opacity drops 0.55 → 0.4 (~27% relative dim),
|
|
5957
|
+
mirroring R506's rect fillOpacity drop pattern at
|
|
5958
|
+
the label-paint tier. Adds a 4th independent
|
|
5959
|
+
channel to the orphan visual signature at rest:
|
|
5960
|
+
R499 fontStyle italic (typography style)
|
|
5961
|
+
R503 '3 6' dash pattern (rect stroke)
|
|
5962
|
+
R506 lower rect fill-opacity (rect fill)
|
|
5963
|
+
R551 lower label opacity (label paint) ← this round
|
|
5964
|
+
Four independent channels (typography style +
|
|
5965
|
+
stroke pattern + rect fill density + label paint
|
|
5966
|
+
density) collectively encode the catchall semantic
|
|
5967
|
+
at rest — orphan band reads as "misc bucket, less
|
|
5968
|
+
attention-deserving" through every available paint
|
|
5969
|
+
channel, no chrome / color / geometry change.
|
|
5970
|
+
Pin and hover branches UNCHANGED — orphan label
|
|
5971
|
+
restores to full opacity 1 on inspection, same as
|
|
5972
|
+
prefix groups. The differentiation lives ONLY in
|
|
5973
|
+
the unsolicited rest state. The ~27% drop (0.55 →
|
|
5974
|
+
0.4) is dimmer than R506's ~40% (rect could
|
|
5975
|
+
tolerate it; small 9px text needs more residual
|
|
5976
|
+
paint to stay legible) — orphan label stays
|
|
5977
|
+
readable when scanning, just clearly quieter.
|
|
5978
|
+
Pure paint axis; bbox unchanged; R51 SVG sentinel
|
|
5979
|
+
safety untouched (overlap-test gates to g[data-
|
|
5980
|
+
node], this group-label is invisible to it).
|
|
5981
|
+
transition list (R55/R432/R457/R479: fill, ls,
|
|
5982
|
+
fw, filter all 200ms) already eases opacity since
|
|
5983
|
+
`opacity 300ms ease-out` lives in the parent <text>
|
|
5984
|
+
CSS — wait, only filter/ls/fw/fill 200ms are
|
|
5985
|
+
listed. Need to add 'opacity 200ms ease-out' for
|
|
5986
|
+
smooth orphan opacity flip on pin/hover transitions
|
|
5987
|
+
(currently opacity 0.55 → 1 was snapping).
|
|
5988
|
+
data-group-label-opacity attr exposes the resolved
|
|
5989
|
+
value for tests. */
|
|
5990
|
+
/* Round 561 — opacity ladder gains inspection-
|
|
5991
|
+
overrides-encoding branch via isMemberAliasHovered.
|
|
5992
|
+
Resolution order (most-emphatic first):
|
|
5993
|
+
isPinned || isHovered → 1 (direct attention)
|
|
5994
|
+
isMemberAliasHovered → 1 (R561 inspection)
|
|
5995
|
+
box.isOrphan → 0.4 (R551 orphan rest)
|
|
5996
|
+
(default) → 0.55
|
|
5997
|
+
R484/R485/R486-family mirror. */
|
|
5998
|
+
opacity={isPinned || isHovered || isMemberAliasHovered ? 1 : box.isOrphan ? 0.4 : 0.55}
|
|
5999
|
+
data-group-label-opacity={isPinned || isHovered || isMemberAliasHovered ? 1 : box.isOrphan ? 0.4 : 0.55}
|
|
4892
6000
|
data-group-label-hovered={isHovered && !isPinned ? 'true' : 'false'}
|
|
6001
|
+
data-group-label-member-alias-hovered={isMemberAliasHovered ? 'true' : 'false'}
|
|
4893
6002
|
data-group-label-font-weight={isPinned ? '800' : '700'}
|
|
4894
6003
|
/* Round 479 / Loop — extend drop-shadow visual-polish
|
|
4895
6004
|
family to a 4th anchor: group-label parent text
|
|
@@ -4912,7 +6021,28 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4912
6021
|
transition list extends to include 'filter 200ms
|
|
4913
6022
|
ease-out' alongside the existing fill/ls/fw/opacity
|
|
4914
6023
|
200ms tweens. */
|
|
4915
|
-
|
|
6024
|
+
/* Round 538 / Loop — extends R479 group-label drop-
|
|
6025
|
+
shadow from pin-only to ALSO fire on hover, with
|
|
6026
|
+
a 2-tier alpha ladder matching the R432 letter-
|
|
6027
|
+
spacing 3-tier (hover at 0.25px / pin at 0.5px)
|
|
6028
|
+
pattern. Pre-R538 the paint axis was binary (lit
|
|
6029
|
+
on pin, dark on hover); R538 adds a softer hover
|
|
6030
|
+
glow that distinguishes from the stronger pin
|
|
6031
|
+
glow without losing the "active state lights up"
|
|
6032
|
+
gesture.
|
|
6033
|
+
2-tier alpha ladder:
|
|
6034
|
+
pin (committed) cyan 80 hex (~50% alpha)
|
|
6035
|
+
hover (preview) cyan 4d hex (~30% alpha)
|
|
6036
|
+
rest none
|
|
6037
|
+
Pin signature stays distinctively brighter, but
|
|
6038
|
+
hover now telegraphs paint-axis attention too.
|
|
6039
|
+
Sibling to R534 edge-badge hover-precedence
|
|
6040
|
+
extension at the drop-shadow family. R479 hue
|
|
6041
|
+
(pal.legendAccent) preserved across both tiers.
|
|
6042
|
+
data-group-label-glow attr upgraded from binary
|
|
6043
|
+
('true'/'false') to 3-value ('pin' | 'hover' |
|
|
6044
|
+
'false') so tests can distinguish gate cause. */
|
|
6045
|
+
data-group-label-glow={isPinned ? 'pin' : isHovered ? 'hover' : 'false'}
|
|
4916
6046
|
/* Round 499 / Loop — orphan band "其他" label gets
|
|
4917
6047
|
fontStyle: italic to visually distinguish the
|
|
4918
6048
|
catchall from real prefix-group bands. Pre-R499
|
|
@@ -4934,18 +6064,51 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4934
6064
|
advances the "信息密度" axis by encoding
|
|
4935
6065
|
category-distinction into a single typography
|
|
4936
6066
|
channel without adding visual chrome. */
|
|
6067
|
+
/* Round 571 / Loop — group-label parent text joins the
|
|
6068
|
+
per-element brightness consistency family (R501/
|
|
6069
|
+
R558/R564/R567/R570) at uniform +15%. 8th anchor.
|
|
6070
|
+
Stacks brightness(1.15) onto the existing R479/R538
|
|
6071
|
+
drop-shadow filter — same R564/R570 stacked filter
|
|
6072
|
+
pattern (drop-shadow + brightness in one CSS chain).
|
|
6073
|
+
Pre-R571 the group-label parent text lifted in 5
|
|
6074
|
+
axes on hover/pin (fill + ls 3-tier + fw on pin +
|
|
6075
|
+
drop-shadow + opacity) but the glyph chromatically
|
|
6076
|
+
stayed at flat fill brightness. R571 adds the
|
|
6077
|
+
brightness axis to the glyph itself for cross-
|
|
6078
|
+
element consistency with the rest of the per-
|
|
6079
|
+
element brightness family.
|
|
6080
|
+
Filter chain on isPinned: `drop-shadow(0 0 3px
|
|
6081
|
+
${pal.legendAccent}80) brightness(1.15)`.
|
|
6082
|
+
On isHovered (weaker tier): `drop-
|
|
6083
|
+
shadow(0 0 3px ${pal.legendAccent}4d) brightness(1.15)`.
|
|
6084
|
+
Rest: undefined.
|
|
6085
|
+
Per-element brightness family — 8 anchors at +15%:
|
|
6086
|
+
R501 vendor.logo image
|
|
6087
|
+
R558 vendor monogram
|
|
6088
|
+
R558 prefix-group fallback
|
|
6089
|
+
R564 alias text (stacked w/ DS)
|
|
6090
|
+
R567 node sub-text
|
|
6091
|
+
R570 edge-badge digit
|
|
6092
|
+
R571 group-label parent text ← this round
|
|
6093
|
+
transition list already includes 'filter 200ms ease-
|
|
6094
|
+
out' from R479 — no change needed. R432 ls + R457
|
|
6095
|
+
fw + R479 drop-shadow + R551 orphan opacity all
|
|
6096
|
+
preserved. */
|
|
4937
6097
|
style={{
|
|
4938
6098
|
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',
|
|
4939
6099
|
letterSpacing: isPinned ? '0.5px' :
|
|
4940
6100
|
isHovered ? '0.25px' : '0px',
|
|
4941
6101
|
fontStyle: box.isOrphan ? 'italic' : undefined,
|
|
4942
6102
|
filter: isPinned
|
|
4943
|
-
? `drop-shadow(0 0 3px ${pal.legendAccent}80)`
|
|
4944
|
-
:
|
|
6103
|
+
? `drop-shadow(0 0 3px ${pal.legendAccent}80) brightness(1.15)`
|
|
6104
|
+
: isHovered
|
|
6105
|
+
? `drop-shadow(0 0 3px ${pal.legendAccent}4d) brightness(1.15)`
|
|
6106
|
+
: undefined,
|
|
4945
6107
|
}}
|
|
4946
6108
|
data-group-label={box.key}
|
|
4947
6109
|
data-group-label-pinned={isPinned ? 'true' : 'false'}
|
|
4948
6110
|
data-group-label-orphan={box.isOrphan ? 'true' : 'false'}
|
|
6111
|
+
data-group-label-brightness={(isPinned || isHovered) ? '1.15' : '1'}
|
|
4949
6112
|
>
|
|
4950
6113
|
{box.key}
|
|
4951
6114
|
{/* Round 19 / Loop: member-count chip. Inline tspan stays
|
|
@@ -5407,6 +6570,19 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5407
6570
|
unchanged at the join with the arrow marker).
|
|
5408
6571
|
data-edge-visible-linecap attr exposes the value
|
|
5409
6572
|
for tests. */}
|
|
6573
|
+
{/* Round 582 — edge visible flow path joins per-element
|
|
6574
|
+
brightness family at 21st anchor. Stacks
|
|
6575
|
+
brightness(1.15) with the existing url(#topo-glow)
|
|
6576
|
+
SVG filter (cyber) or applies plain brightness
|
|
6577
|
+
(light). CSS filter accepts mixed url() + function
|
|
6578
|
+
values; inline style.filter overrides any
|
|
6579
|
+
attribute-level filter. Closes edge-tier brightness
|
|
6580
|
+
sub-family at 2 surfaces:
|
|
6581
|
+
R581 flow-rail (dashed underline) brightness
|
|
6582
|
+
R582 visible path (primary curve) brightness ← this round
|
|
6583
|
+
transition list extends to include 'filter 300ms
|
|
6584
|
+
ease-out' matching the existing opacity/sw/stroke
|
|
6585
|
+
cadence. */}
|
|
5410
6586
|
<path
|
|
5411
6587
|
d={path}
|
|
5412
6588
|
fill="none"
|
|
@@ -5414,15 +6590,18 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5414
6590
|
strokeWidth={renderWidth}
|
|
5415
6591
|
strokeLinecap="round"
|
|
5416
6592
|
opacity={Math.min(1, (isLight ? 0.22 : 0.28) * fresh * edgeOpacityMul)}
|
|
5417
|
-
filter={isLight ? undefined : 'url(#topo-glow)'}
|
|
5418
6593
|
markerEnd={`url(#${arrowId})`}
|
|
5419
6594
|
data-edge-visible={link.key}
|
|
5420
6595
|
data-edge-visible-linecap="round"
|
|
5421
6596
|
data-edge-visible-endpoint-hovered={isEndpointHoveredEdge ? 'true' : 'false'}
|
|
5422
6597
|
data-edge-visible-stroke-width={renderWidth}
|
|
6598
|
+
data-edge-visible-brightness={(isHoveredEdge || isEndpointHoveredEdge) ? '1.15' : '1'}
|
|
5423
6599
|
style={{
|
|
5424
6600
|
pointerEvents: 'none',
|
|
5425
|
-
transition: 'opacity 300ms ease-out, stroke-width 300ms ease-out, stroke 300ms ease-out',
|
|
6601
|
+
transition: 'opacity 300ms ease-out, stroke-width 300ms ease-out, stroke 300ms ease-out, filter 300ms ease-out',
|
|
6602
|
+
filter: (isHoveredEdge || isEndpointHoveredEdge)
|
|
6603
|
+
? (isLight ? 'brightness(1.15)' : 'url(#topo-glow) brightness(1.15)')
|
|
6604
|
+
: (isLight ? undefined : 'url(#topo-glow)'),
|
|
5426
6605
|
}}
|
|
5427
6606
|
/>
|
|
5428
6607
|
{/* Round 378 / Loop: edge flow-path dashed-rail picks
|
|
@@ -5472,7 +6651,23 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5472
6651
|
data-edge-flow-rail-linecap="round"
|
|
5473
6652
|
data-edge-flow-rail-stroke-width={(isHoveredEdge || isEndpointHoveredEdge) ? 1.5 : 1}
|
|
5474
6653
|
data-edge-flow-rail-lifted={(isHoveredEdge || isEndpointHoveredEdge) ? 'true' : 'false'}
|
|
5475
|
-
|
|
6654
|
+
/* Round 581 — flow-rail joins per-element brightness
|
|
6655
|
+
family at 20th anchor. Adds brightness(1.15) on
|
|
6656
|
+
edge hover or endpoint hover. Joins R437 sw-lift
|
|
6657
|
+
paint pattern at the dashed-underline tier — when
|
|
6658
|
+
an edge is in focus, the rail's stroke widens
|
|
6659
|
+
(R437) AND brightens (R581) together, reading
|
|
6660
|
+
as a coherent rail-lift gesture under the flow.
|
|
6661
|
+
transition list extends to include 'filter 300ms
|
|
6662
|
+
ease-out' matching the R245/R437 cadence on this
|
|
6663
|
+
surface. */
|
|
6664
|
+
data-edge-flow-rail-brightness={(isHoveredEdge || isEndpointHoveredEdge) ? '1.15' : '1'}
|
|
6665
|
+
style={{
|
|
6666
|
+
transition: 'opacity 300ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, filter 300ms ease-out',
|
|
6667
|
+
filter: (isHoveredEdge || isEndpointHoveredEdge)
|
|
6668
|
+
? 'brightness(1.15)'
|
|
6669
|
+
: undefined,
|
|
6670
|
+
}}
|
|
5476
6671
|
/>
|
|
5477
6672
|
{!reducedMotion && (
|
|
5478
6673
|
/* Round 103 / Loop: phase-stagger the particles so
|
|
@@ -5542,7 +6737,31 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5542
6737
|
data-edge-particle-lifted={(isHoveredEdge || isEndpointHoveredEdge) ? 'true' : 'false'}
|
|
5543
6738
|
data-edge-particle-opacity-rest={Math.min(1, fresh * edgeOpacityMul).toFixed(2)}
|
|
5544
6739
|
data-edge-particle-opacity-lifted={(isHoveredEdge || isEndpointHoveredEdge) ? 'true' : 'false'}
|
|
5545
|
-
|
|
6740
|
+
/* Round 583 — flow particle joins per-element
|
|
6741
|
+
brightness family at 22nd anchor. Adds
|
|
6742
|
+
brightness(1.15) on edge hover or endpoint
|
|
6743
|
+
hover, joining R485 opacity inspection-override
|
|
6744
|
+
+ R422 r-lift (4 → 4.5) + R164 hover-r-lift
|
|
6745
|
+
(4.5 → 5.5). Particle now has 4-axis active
|
|
6746
|
+
signature on edge inspection:
|
|
6747
|
+
R485 opacity (freshness → 1.0)
|
|
6748
|
+
R164 r 4.5 → 5.5
|
|
6749
|
+
R422 r-base 4 → 4.5 (visual-weight bump)
|
|
6750
|
+
R583 brightness(1.15) ← this round
|
|
6751
|
+
Particle becomes the brightest paint element
|
|
6752
|
+
along the edge during inspection. */
|
|
6753
|
+
data-edge-particle-brightness={(isHoveredEdge || isEndpointHoveredEdge) ? '1.15' : '1'}
|
|
6754
|
+
style={{
|
|
6755
|
+
transition: 'fill 200ms ease-out, opacity 200ms ease-out, r 200ms ease-out, filter 200ms ease-out',
|
|
6756
|
+
/* R583 — stack brightness(1.15) onto the existing
|
|
6757
|
+
url(#topo-glow) (cyber) or apply plain brightness
|
|
6758
|
+
(light). Inline style.filter overrides attribute
|
|
6759
|
+
filter; stacked syntax preserves the cyber glow
|
|
6760
|
+
on hover. Same R582 visible-path stack pattern. */
|
|
6761
|
+
filter: (isHoveredEdge || isEndpointHoveredEdge)
|
|
6762
|
+
? (isLight ? 'brightness(1.15)' : 'url(#topo-glow) brightness(1.15)')
|
|
6763
|
+
: undefined,
|
|
6764
|
+
}}
|
|
5546
6765
|
>
|
|
5547
6766
|
<animateMotion
|
|
5548
6767
|
dur={`${duration}s`}
|
|
@@ -6032,11 +7251,77 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6032
7251
|
data-edge-badge-opacity-rest={isLight ? 0.95 : 0.85}
|
|
6033
7252
|
data-edge-badge-opacity-hover="1"
|
|
6034
7253
|
data-edge-badge-opacity-active="1"
|
|
6035
|
-
data-edge-badge-glow={isHot ? '
|
|
7254
|
+
data-edge-badge-glow={(isHoveredEdge || isPinned) ? 'hover' : isHot ? 'hot' : 'false'}
|
|
7255
|
+
data-edge-badge-brightness={(isHoveredEdge || isPinned || isHot) ? '1.15' : '1'}
|
|
7256
|
+
/* Round 534 / Loop — extends edge-badge drop-shadow
|
|
7257
|
+
coverage from hot-only (R480 amber) to also fire
|
|
7258
|
+
on hover/pin with a cyan accent glow. Pre-R534
|
|
7259
|
+
the badge's hover/pin lifted r (R164 9 → 10.5)
|
|
7260
|
+
+ sw (R394 1.25 → 1.5) + opacity (R395/R396 →
|
|
7261
|
+
1.0), but the paint axis stayed at the badge's
|
|
7262
|
+
rest fill — no glow to telegraph "in focus" at
|
|
7263
|
+
the paint layer. R534 closes that 4-axis hover-
|
|
7264
|
+
lift parity by adding drop-shadow glow on
|
|
7265
|
+
(hovered || pinned).
|
|
7266
|
+
Precedence: (hover || pin) wins over isHot when
|
|
7267
|
+
BOTH true — interactive signal (user is
|
|
7268
|
+
inspecting) overrides informational signal
|
|
7269
|
+
(hot lane). When only isHot fires (no hover/
|
|
7270
|
+
pin) the amber R480 glow remains; the hover/
|
|
7271
|
+
pin case paints cyan/teal `pal.legendAccent`
|
|
7272
|
+
at 0x99 alpha (~60%) — bright enough to read
|
|
7273
|
+
as "lit" but won't overwhelm at small badge
|
|
7274
|
+
size (r=10.5).
|
|
7275
|
+
Edge-badge 4-axis hover-lift parity now:
|
|
7276
|
+
R164 r 9 → 10.5
|
|
7277
|
+
R394 stroke-wd 1.25 → 1.5
|
|
7278
|
+
R395 opacity rest → 1.0
|
|
7279
|
+
R534 filter none → drop-shadow glow ← this round
|
|
7280
|
+
Drop-shadow visual-polish family extension —
|
|
7281
|
+
edge-badge surface upgraded from single-gate
|
|
7282
|
+
(R480 isHot) to two-gate (isHot OR hover-pin).
|
|
7283
|
+
transition list already includes filter 200ms
|
|
7284
|
+
ease-out (R480). data-edge-badge-glow attr
|
|
7285
|
+
upgraded from `isHot ? true : false` to a
|
|
7286
|
+
3-value string: 'hot' | 'hover' | 'false' so
|
|
7287
|
+
tests can distinguish gate cause.
|
|
7288
|
+
R51 sentinel safety: badge is edge-internal
|
|
7289
|
+
(not g[data-node] ancestor); filter is paint-
|
|
7290
|
+
only; bbox unchanged. */
|
|
6036
7291
|
style={{
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
7292
|
+
/* R603 — edge-badge CIRCLE stacks brightness(1.15)
|
|
7293
|
+
onto the R534/R480 drop-shadow filter on hover/
|
|
7294
|
+
pin/hot. 5th hover axis on the badge ring
|
|
7295
|
+
(after R164 r, R394 sw, R395 opacity, R534
|
|
7296
|
+
filter drop-shadow → R603 filter brightness).
|
|
7297
|
+
|
|
7298
|
+
Brings the CIRCLE's brightness coverage to
|
|
7299
|
+
parity with the DIGIT's R570 brightness:
|
|
7300
|
+
both elements (ring + text) now brighten
|
|
7301
|
+
together on the same 3-state gate
|
|
7302
|
+
(hover/pin/hot). The whole edge-badge reads
|
|
7303
|
+
as one coherent "lit up under attention"
|
|
7304
|
+
unit at the brightness axis.
|
|
7305
|
+
|
|
7306
|
+
Banked R564/R570 stacked-filter pattern:
|
|
7307
|
+
`drop-shadow(...) brightness(1.15)` chains
|
|
7308
|
+
the SVG drop-shadow halo + brightness lift
|
|
7309
|
+
in one CSS filter chain on the same element.
|
|
7310
|
+
|
|
7311
|
+
Per-element brightness family extension —
|
|
7312
|
+
edge-badge circle joins as a stacked-filter
|
|
7313
|
+
anchor. Hot-only state gets amber drop-shadow
|
|
7314
|
+
+ brightness; hover/pin gets cyan/teal drop-
|
|
7315
|
+
shadow + brightness; rest gets neither.
|
|
7316
|
+
|
|
7317
|
+
transition list already includes 'filter
|
|
7318
|
+
200ms ease-out' (R534 cadence). No
|
|
7319
|
+
transition change needed. */
|
|
7320
|
+
filter: (isHoveredEdge || isPinned)
|
|
7321
|
+
? `drop-shadow(0 0 3px ${pal.legendAccent}99) brightness(1.15)`
|
|
7322
|
+
: isHot
|
|
7323
|
+
? `drop-shadow(0 0 3px ${hotStroke}80) brightness(1.15)`
|
|
7324
|
+
: undefined,
|
|
6040
7325
|
transition: 'r 180ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, fill 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',
|
|
6041
7326
|
}}
|
|
6042
7327
|
/>
|
|
@@ -6126,6 +7411,31 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6126
7411
|
data-edge-badge-text={link.key}
|
|
6127
7412
|
data-edge-badge-text-pin={(isPinned || isHot) ? 'true' : 'false'}
|
|
6128
7413
|
data-edge-badge-text-font-size="11"
|
|
7414
|
+
/* Round 570 / Loop — edge-badge digit joins the per-
|
|
7415
|
+
element brightness consistency family (R501/R558/
|
|
7416
|
+
R564/R567) at uniform +15%. 7th anchor.
|
|
7417
|
+
Gate: (isHoveredEdge || isPinned || isHot) — same
|
|
7418
|
+
3-tier set as R431 ls (with hover the mid step
|
|
7419
|
+
and pin/hot the strong step). Brightness lifts
|
|
7420
|
+
uniformly across all 3 active sub-states; the
|
|
7421
|
+
ls/fw axes still distinguish hover from pin/hot.
|
|
7422
|
+
Pure paint axis on the digit glyph; bbox
|
|
7423
|
+
unchanged. The R540 badge-circle drop-shadow
|
|
7424
|
+
sits on the parent CIRCLE element (separate
|
|
7425
|
+
filter); the digit's filter is independent and
|
|
7426
|
+
doesn't compound with circle filter (different
|
|
7427
|
+
SVG element).
|
|
7428
|
+
Per-element brightness family — 7 anchors at +15%:
|
|
7429
|
+
R501 vendor.logo image
|
|
7430
|
+
R558 vendor monogram
|
|
7431
|
+
R558 prefix-group fallback
|
|
7432
|
+
R564 alias text (stacked w/ DS)
|
|
7433
|
+
R567 node sub-text
|
|
7434
|
+
R570 edge-badge digit ← this round
|
|
7435
|
+
Plus runtime badge drop-shadow (R559) on same
|
|
7436
|
+
isNodeActive gate. data-edge-badge-text-brightness
|
|
7437
|
+
attr surfaces the lift for tests. */
|
|
7438
|
+
data-edge-badge-text-brightness={(isHoveredEdge || isPinned || isHot) ? '1.15' : '1'}
|
|
6129
7439
|
style={{
|
|
6130
7440
|
pointerEvents: 'none',
|
|
6131
7441
|
fontVariantNumeric: 'tabular-nums',
|
|
@@ -6144,7 +7454,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6144
7454
|
now): R344/R345/R347/R351/R420/R427/R431. */
|
|
6145
7455
|
letterSpacing: (isPinned || isHot) ? '0.4px' :
|
|
6146
7456
|
isHoveredEdge ? '0.2px' : '0px',
|
|
6147
|
-
|
|
7457
|
+
filter: (isHoveredEdge || isPinned || isHot)
|
|
7458
|
+
? 'brightness(1.15)'
|
|
7459
|
+
: undefined,
|
|
7460
|
+
transition: 'letter-spacing 300ms ease-out, font-weight 300ms ease-out, filter 300ms ease-out',
|
|
6148
7461
|
}}
|
|
6149
7462
|
>{link.count}</text>
|
|
6150
7463
|
</g>
|
|
@@ -6336,6 +7649,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6336
7649
|
data-hub-busyness={busy}
|
|
6337
7650
|
data-topo-hub-halo-radius={haloR}
|
|
6338
7651
|
data-topo-hub-halo-hovered={isHaloHovered ? 'true' : 'false'}
|
|
7652
|
+
data-topo-hub-halo-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
6339
7653
|
data-topo-hub-halo-trough={isLight ? troughLight : troughDark}
|
|
6340
7654
|
data-topo-hub-halo-peak={isLight ? peakLight : peakDark}
|
|
6341
7655
|
/* Round 253 / Loop: hub grounding halo fill transition
|
|
@@ -6347,10 +7661,59 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6347
7661
|
conflict.
|
|
6348
7662
|
R451: r as CSS property (R197/R198 idiom) so the
|
|
6349
7663
|
hover-radius tween eases smoothly under the same
|
|
6350
|
-
200ms cadence as fill.
|
|
7664
|
+
200ms cadence as fill.
|
|
7665
|
+
R536: extends hub-cluster glow QUARTET (R476 digit
|
|
7666
|
+
+ R532 disc + R535 ring + R533 spokes) to a 5th
|
|
7667
|
+
tier — the halo gains drop-shadow at the outermost
|
|
7668
|
+
concentric ring on hub-hover. 2px blur + 0.3 alpha
|
|
7669
|
+
keeps the halo's glow subtle since (a) the halo is
|
|
7670
|
+
the LARGEST hub element (r=22 hover) and a heavier
|
|
7671
|
+
glow would bleed visibly past the ring tier into
|
|
7672
|
+
the spoke origin, and (b) the halo already SMIL-
|
|
7673
|
+
animates opacity (R84/R244 breath), so the visible
|
|
7674
|
+
glow pulses with the breath — an atmospheric
|
|
7675
|
+
"breathing glow" idiom rather than a static rim.
|
|
7676
|
+
Hub-cluster glow QUINTET (R476/R532/R533/R535/R536):
|
|
7677
|
+
digit (typo center) 3px emerald 0.6
|
|
7678
|
+
disc (r=5.5/6) 3px emerald 0.6
|
|
7679
|
+
ring (r=14/17) 3px emerald 0.5
|
|
7680
|
+
halo (r=20/22) 2px emerald 0.3 ← this round
|
|
7681
|
+
spokes (mesh) 1.5px cyan/teal 0.4
|
|
7682
|
+
Emerald palette continues through the focal-disc
|
|
7683
|
+
family (digit/disc/ring/halo); spokes break out
|
|
7684
|
+
into cyan/teal at the mesh tier. The 4-step alpha
|
|
7685
|
+
ladder 0.6→0.6→0.5→0.3 reads as the focal cluster
|
|
7686
|
+
fading outward — the OUTERMOST emerald glow is the
|
|
7687
|
+
softest, the focal digit is the brightest.
|
|
7688
|
+
filter is paint-only; SMIL animate on opacity
|
|
7689
|
+
continues independently (attribute vs CSS-property
|
|
7690
|
+
non-conflicting). transition list extends to
|
|
7691
|
+
'filter 200ms ease-out' alongside fill + r.
|
|
7692
|
+
Drop-shadow visual-polish family extension (12
|
|
7693
|
+
anchors). preview.50 milestone round. data-topo-
|
|
7694
|
+
hub-halo-glow attr exposes the gate state. */
|
|
7695
|
+
data-topo-hub-halo-brightness={!reducedMotion && hoveredHub ? '1.15' : '1'}
|
|
7696
|
+
/* Round 577 — hub-halo joins per-element brightness
|
|
7697
|
+
family at 15th anchor. Stacks brightness(1.15)
|
|
7698
|
+
onto R536's hub-hover drop-shadow — closes the
|
|
7699
|
+
hub-cluster focal brightness coverage at 3
|
|
7700
|
+
concentric elements:
|
|
7701
|
+
hub digit (R575)
|
|
7702
|
+
hub-highlight (R574)
|
|
7703
|
+
hub-halo (R577) ← this round
|
|
7704
|
+
All 3 hub focal elements now lift through stacked
|
|
7705
|
+
drop-shadow + brightness on hub-hover. Halo is
|
|
7706
|
+
the OUTERMOST so a slight chromatic lift reads as
|
|
7707
|
+
the focal cluster intensifying its ambient glow
|
|
7708
|
+
outward. */
|
|
6351
7709
|
style={{
|
|
6352
7710
|
r: `${haloR}px`,
|
|
6353
|
-
|
|
7711
|
+
filter: !reducedMotion && hoveredHub
|
|
7712
|
+
? (isLight
|
|
7713
|
+
? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.3)) brightness(1.15)'
|
|
7714
|
+
: 'drop-shadow(0 0 2px rgba(52, 211, 153, 0.3)) brightness(1.15)')
|
|
7715
|
+
: undefined,
|
|
7716
|
+
transition: 'fill 200ms ease-out, r 200ms ease-out, filter 200ms ease-out',
|
|
6354
7717
|
} as React.CSSProperties}
|
|
6355
7718
|
>
|
|
6356
7719
|
{/* Round 244 / Loop: hub grounding halo breath gets
|
|
@@ -6510,6 +7873,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6510
7873
|
data-topo-hub-working-count={workingCount}
|
|
6511
7874
|
data-topo-hub-working-count-font-size="12"
|
|
6512
7875
|
data-topo-hub-working-count-hovered={hoveredHub ? 'true' : 'false'}
|
|
7876
|
+
data-topo-hub-working-count-brightness={!reducedMotion && hoveredHub ? '1.15' : '1'}
|
|
6513
7877
|
data-topo-hub-working-count-visible={workingCount > 0 ? 'true' : 'false'}
|
|
6514
7878
|
// Round 209 / Loop: hub workingCount digit scales 1.0 →
|
|
6515
7879
|
// 1.08 on hub-hover, matching R177's r 14→17 ring grow.
|
|
@@ -6560,22 +7924,109 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6560
7924
|
so the glow eases under the same cadence as the
|
|
6561
7925
|
scale + fw + fill axes. */
|
|
6562
7926
|
data-topo-hub-working-count-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
7927
|
+
/* Round 507 / Loop — focal recede. When ANY non-hub
|
|
7928
|
+
canvas surface is hovered (a node / an edge / a
|
|
7929
|
+
group label / a legend row / a vendor chip), the
|
|
7930
|
+
hub-center workingCount digit fades to 0.85 opacity,
|
|
7931
|
+
signaling "you're inspecting elsewhere, hub recedes
|
|
7932
|
+
to background." When the user un-hovers (or hovers
|
|
7933
|
+
the hub itself), opacity returns to 1.0. Pure paint
|
|
7934
|
+
polish at the canvas's most prominent focal point.
|
|
7935
|
+
Hits 信息密度 + 动效 themes — the hub digit gives
|
|
7936
|
+
way visually to the surface under inspection,
|
|
7937
|
+
reinforcing the "this is the focal point right now"
|
|
7938
|
+
gesture without requiring users to track which
|
|
7939
|
+
surface holds attention.
|
|
7940
|
+
Gate excludes hoveredHub specifically: hovering the
|
|
7941
|
+
hub itself should LIFT the digit (R425 fw bump +
|
|
7942
|
+
R476 glow + R209 scale 1.08) — the existing hover-
|
|
7943
|
+
on-hub signature is intact; only inspection
|
|
7944
|
+
ELSEWHERE recedes the hub.
|
|
7945
|
+
Composed from existing hoveredAlias / hoveredEdge-
|
|
7946
|
+
Key / hoveredGroupLabel / hoveredStatus / hovered-
|
|
7947
|
+
Vendor — no new state. 300ms ease-out opacity
|
|
7948
|
+
transition already in the style list (existing R213
|
|
7949
|
+
transition spec), so the fade rides on existing
|
|
7950
|
+
infrastructure.
|
|
7951
|
+
data-topo-hub-recede attr surfaces the gate state
|
|
7952
|
+
for tests. */
|
|
7953
|
+
data-topo-hub-recede={
|
|
7954
|
+
(hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
7955
|
+
hoveredStatus || hoveredVendor) && !hoveredHub ? 'true' : 'false'
|
|
7956
|
+
}
|
|
7957
|
+
/* Round 527 / Loop — focal-amplify family extension to a
|
|
7958
|
+
2nd anchor. R511 introduced focal-amplify at the hub-
|
|
7959
|
+
highlight disc (base opacity 0.95 → 1.0 on hover); R527
|
|
7960
|
+
extends to the hub-center workingCount digit with a
|
|
7961
|
+
letter-spacing tween 0 → 0.3px on hub-hover.
|
|
7962
|
+
Composes with existing 3-axis hub-hover signature on
|
|
7963
|
+
this element:
|
|
7964
|
+
R209 transform scale(1.08) geometry
|
|
7965
|
+
R425 fontWeight 700 → 800 typography weight
|
|
7966
|
+
R476 filter drop-shadow glow paint
|
|
7967
|
+
R527 letter-spacing 0 → 0.3px typography kerning ← this round
|
|
7968
|
+
tabular-nums (R225) preserved — each digit cell keeps
|
|
7969
|
+
fixed width; the inter-digit advance grows by 0.3px
|
|
7970
|
+
per gap. Single-digit counts (1-9) show no kerning
|
|
7971
|
+
effect; multi-digit counts (10+) show the spread as
|
|
7972
|
+
info-density signaling. Sibling to R427/R431/R432/
|
|
7973
|
+
R433/R434 (hover-letter-spacing family at panel-text
|
|
7974
|
+
scope) — R527 brings the same idiom to the canvas's
|
|
7975
|
+
most-read scalar.
|
|
7976
|
+
Reduced-motion gate matches R209 scale, R425 fw, R476
|
|
7977
|
+
filter — !reducedMotion gates the lift; reducedMotion
|
|
7978
|
+
users see static digit baseline regardless of hover.
|
|
7979
|
+
Focal-amplify family extension (2 anchors): R511 hub-
|
|
7980
|
+
highlight opacity / R527 hub-digit letter-spacing.
|
|
7981
|
+
transition list extends to include `letter-spacing
|
|
7982
|
+
200ms ease-out`, matching the cadence of the other
|
|
7983
|
+
hub-hover axes. data-topo-hub-working-count-letter-
|
|
7984
|
+
spacing attr exposes the resolved value for tests. */
|
|
7985
|
+
data-topo-hub-working-count-letter-spacing={!reducedMotion && hoveredHub ? '0.3px' : '0px'}
|
|
6563
7986
|
style={{
|
|
6564
7987
|
pointerEvents: 'none',
|
|
6565
7988
|
transform: !reducedMotion && hoveredHub ? 'scale(1.08)' : 'scale(1)',
|
|
6566
7989
|
transformBox: 'fill-box',
|
|
6567
7990
|
transformOrigin: 'center',
|
|
7991
|
+
opacity: (hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
7992
|
+
hoveredStatus || hoveredVendor) && !hoveredHub
|
|
7993
|
+
? 0.85
|
|
7994
|
+
: 1,
|
|
7995
|
+
/* Round 575 (60-round milestone) — hub digit joins per-
|
|
7996
|
+
element brightness family at 14th anchor. Stacks
|
|
7997
|
+
brightness(1.15) onto R476's hub-hover drop-shadow
|
|
7998
|
+
— same R564/R570/R571/R572/R573/R574 pattern (drop-
|
|
7999
|
+
shadow + brightness in one filter chain). Closes
|
|
8000
|
+
the hub-cluster focal-element brightness coverage
|
|
8001
|
+
symmetrically: hub digit + hub-highlight disc
|
|
8002
|
+
(R574) now BOTH have stacked filter on hub-hover.
|
|
8003
|
+
Hub digit hub-hover signature post-R575 — 5 active
|
|
8004
|
+
axes:
|
|
8005
|
+
R209 scale 1.08 (geometry)
|
|
8006
|
+
R425 fw 700 → 800 (typography)
|
|
8007
|
+
R527 ls 0 → 0.3px (typography)
|
|
8008
|
+
R476 drop-shadow glow (paint halo)
|
|
8009
|
+
R575 brightness(1.15) (paint glow) ← this round
|
|
8010
|
+
Hub-cluster focal cluster (digit + highlight) now
|
|
8011
|
+
has UNIFIED 5-axis hub-hover signature reading as
|
|
8012
|
+
one tightly-coupled motion-coherent lift. */
|
|
6568
8013
|
filter: !reducedMotion && hoveredHub
|
|
6569
8014
|
? (isLight
|
|
6570
|
-
? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.6))'
|
|
6571
|
-
: 'drop-shadow(0 0 3px rgba(52, 211, 153, 0.6))')
|
|
8015
|
+
? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.6)) brightness(1.15)'
|
|
8016
|
+
: 'drop-shadow(0 0 3px rgba(52, 211, 153, 0.6)) brightness(1.15)')
|
|
6572
8017
|
: undefined,
|
|
8018
|
+
letterSpacing: !reducedMotion && hoveredHub ? '0.3px' : '0px',
|
|
6573
8019
|
/* R425: font-weight 200ms appended so the hover fw
|
|
6574
8020
|
bump 700 → 800 eases under the same cadence as
|
|
6575
8021
|
R209 scale + R253 fill + R213 opacity.
|
|
6576
8022
|
R476: filter 200ms appended so the new drop-
|
|
6577
|
-
shadow glow eases at the same cadence.
|
|
6578
|
-
|
|
8023
|
+
shadow glow eases at the same cadence.
|
|
8024
|
+
R507: opacity 300ms (existing in list) covers
|
|
8025
|
+
the new focal-recede fade.
|
|
8026
|
+
R527: letter-spacing 200ms appended so the new
|
|
8027
|
+
hover-kerning bump eases at the same cadence
|
|
8028
|
+
as the other axes. */
|
|
8029
|
+
transition: 'transform 200ms ease-out, opacity 300ms ease-out, fill 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out, letter-spacing 200ms ease-out',
|
|
6579
8030
|
fontVariantNumeric: 'tabular-nums',
|
|
6580
8031
|
}}
|
|
6581
8032
|
>
|
|
@@ -6621,20 +8072,188 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6621
8072
|
+ R213 always-mount opacity-gate + pointerEvents:none
|
|
6622
8073
|
+ R365 r=5.5 all preserved. data-topo-hub-highlight-
|
|
6623
8074
|
opacity attr exposes the resolved value for tests. */}
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
8075
|
+
{/* Round 508 / Loop — focal-recede pattern 2nd anchor.
|
|
8076
|
+
Extends R507's hub-digit recede to the hub-highlight
|
|
8077
|
+
circle so the hub focal CLUSTER (digit at z-top + this
|
|
8078
|
+
idle-state highlight beneath) recedes as a unit when
|
|
8079
|
+
canvas attention is elsewhere. Computed once: a single
|
|
8080
|
+
non-hub-hover gate drives BOTH the digit (R507) AND
|
|
8081
|
+
this highlight (R508) so they always co-move.
|
|
8082
|
+
Recede multiplies the visible opacity by 0.85 — when
|
|
8083
|
+
workingCount===0 the rest opacity 0.95 becomes 0.81
|
|
8084
|
+
during external-hover; when workingCount>0 the
|
|
8085
|
+
opacity stays 0 (invisible) regardless of recede.
|
|
8086
|
+
Additionally, when recede is active the SMIL breath
|
|
8087
|
+
animation halts (animate node un-mounts) so the
|
|
8088
|
+
receded state reads as quietly static, not pulsing
|
|
8089
|
+
at 0.85↔1.0 against the recede multiplier (which
|
|
8090
|
+
would visually conflict — competing 15% drops). On
|
|
8091
|
+
un-hover the animate re-mounts and breath resumes.
|
|
8092
|
+
data-topo-hub-recede on both digit AND highlight
|
|
8093
|
+
provides a stable test handle for the unified-recede
|
|
8094
|
+
gate.
|
|
8095
|
+
Composed from existing hover state vars — no new
|
|
8096
|
+
state. Pure paint axis. */}
|
|
8097
|
+
{(() => {
|
|
8098
|
+
const hubRecede = !!((hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
8099
|
+
hoveredStatus || hoveredVendor) && !hoveredHub);
|
|
8100
|
+
/* Round 511 / Loop — hub-highlight gains a 3rd opacity tier
|
|
8101
|
+
(hovered-amplify). Pre-R511 the highlight had 2 visible
|
|
8102
|
+
states: rest (0.95) and recede (0.81 = 0.95 × 0.85).
|
|
8103
|
+
When the hub itself was hovered, the digit got R425 fw
|
|
8104
|
+
lift + R476 drop-shadow + R209 scale-1.08, but the
|
|
8105
|
+
highlight disc sibling stayed at 0.95 — the focal
|
|
8106
|
+
cluster lifted in 3 channels (typography/paint/scale)
|
|
8107
|
+
but the highlight didn't participate.
|
|
8108
|
+
R511 closes that asymmetry: when hoveredHub is true,
|
|
8109
|
+
highlight base opacity lifts to 1.0 (5% boost from
|
|
8110
|
+
rest 0.95). Cluster now lifts as a unit on hub-hover,
|
|
8111
|
+
just like it recedes as a unit on non-hub-hover
|
|
8112
|
+
(R508).
|
|
8113
|
+
3-state opacity ladder:
|
|
8114
|
+
hub-hovered: baseOpacity = 1.0 (R511 NEW)
|
|
8115
|
+
rest (no hover): baseOpacity = 0.95 (existing)
|
|
8116
|
+
non-hub canvas hov: baseOpacity × 0.85 = 0.81 (R508)
|
|
8117
|
+
Composes cleanly: hubRecede gate requires !hoveredHub,
|
|
8118
|
+
so the hovered-amplify and recede states are mutually
|
|
8119
|
+
exclusive (they can't both fire). breathActive
|
|
8120
|
+
continues to halt on either non-rest state (recede OR
|
|
8121
|
+
hub-hover would visually compete with the 0.85↔1
|
|
8122
|
+
breath — clean for the unit-lift semantic too). */
|
|
8123
|
+
const baseOpacity = workingCount > 0 ? 0
|
|
8124
|
+
: hoveredHub ? 1.0
|
|
8125
|
+
: 0.95;
|
|
8126
|
+
const resolvedOpacity = hubRecede ? baseOpacity * 0.85 : baseOpacity;
|
|
8127
|
+
const breathActive = !reducedMotion && workingCount === 0 && !hubRecede && !hoveredHub;
|
|
8128
|
+
/* Round 529 / Loop — focal-amplify family 3rd anchor.
|
|
8129
|
+
Hub-highlight gains geometric amplify r 5.5 → 6 on
|
|
8130
|
+
hub-hover, mirroring R451's hub-halo r 20 → 22 hover
|
|
8131
|
+
pattern. Pre-R529 the highlight had paint-axis
|
|
8132
|
+
amplify only (R511 opacity 0.95 → 1.0 on hub-hover);
|
|
8133
|
+
R529 adds geometric amplify so the focal disc
|
|
8134
|
+
BREATHES outward on hub attention, like the halo
|
|
8135
|
+
does. Composes with the existing 2-axis hub-hover
|
|
8136
|
+
lift on this element:
|
|
8137
|
+
R511 opacity 0.95 → 1.0 paint (focal-amplify 1st)
|
|
8138
|
+
R529 r 5.5 → 6 geometry (this round)
|
|
8139
|
+
Implementation matches R451: CSS `r` property
|
|
8140
|
+
(R197/R198 idiom) for smooth interpolation. SVG
|
|
8141
|
+
attribute `r="5.5"` provides SSR fallback and serves
|
|
8142
|
+
as default; inline style.r overrides for animated
|
|
8143
|
+
value. transition list extends to include `r 200ms
|
|
8144
|
+
ease-out`, matching the fill cadence (also 200ms);
|
|
8145
|
+
opacity transition stays at 300ms (existing).
|
|
8146
|
+
r 6 sits well inside the existing visual envelope
|
|
8147
|
+
(next-larger sibling r=10 hub core, r=14 hub hover
|
|
8148
|
+
ring). The 0.5px lift is +9% radius / +19% area —
|
|
8149
|
+
enough to read as 'lift' without breaching the core
|
|
8150
|
+
boundary or invalidating overlap-test invariants.
|
|
8151
|
+
SMIL animate on opacity continues independently
|
|
8152
|
+
(animateAttr='opacity' vs CSS-property r — non-
|
|
8153
|
+
conflicting, same pattern R451 noted for halo).
|
|
8154
|
+
Focal-amplify family extension (3 anchors):
|
|
8155
|
+
R511 hub-highlight opacity 0.95 → 1.0
|
|
8156
|
+
R527 hub-digit letter-spacing 0 → 0.3px
|
|
8157
|
+
R529 hub-highlight radius 5.5 → 6 ← this round
|
|
8158
|
+
data-topo-hub-highlight-radius attr now reports the
|
|
8159
|
+
dynamic value (was static '5.5'). */
|
|
8160
|
+
const highlightR = !reducedMotion && hoveredHub ? 6 : 5.5;
|
|
8161
|
+
return (
|
|
8162
|
+
<circle
|
|
8163
|
+
cx={cx} cy={cy} r="5.5"
|
|
8164
|
+
/* Round 509 / Loop — 配色 cross-theme cleanup. Pre-R509
|
|
8165
|
+
the hub-highlight fill was hardcoded `#d1fae5`
|
|
8166
|
+
(emerald-100, a pale tone). On the light theme this
|
|
8167
|
+
near-white green ran against a pale background at
|
|
8168
|
+
0.95 opacity — the disc was effectively invisible.
|
|
8169
|
+
Matches the existing R253 halo theme-inversion
|
|
8170
|
+
pattern (line ~6481): light theme picks the dark
|
|
8171
|
+
vibrant emerald (#10b981 emerald-600), dark theme
|
|
8172
|
+
keeps the pale emerald (#d1fae5 emerald-100). Both
|
|
8173
|
+
read at the same 0.95 opacity against their
|
|
8174
|
+
respective backdrops — light gets a saturated
|
|
8175
|
+
focal dot; dark keeps the soft glow signature.
|
|
8176
|
+
Pure paint axis (fill change only); bbox unchanged;
|
|
8177
|
+
R51 SVG sentinel safety untouched.
|
|
8178
|
+
transition list already includes `fill 200ms`?
|
|
8179
|
+
Actually the existing transition spec is `opacity
|
|
8180
|
+
300ms ease-out` — fill change on theme toggle
|
|
8181
|
+
will be instant. That's acceptable: theme toggle
|
|
8182
|
+
is a discrete event, and the halo (line 6500)
|
|
8183
|
+
already snaps fill on theme toggle the same way
|
|
8184
|
+
(`fill 200ms ease-out` was added later to halo
|
|
8185
|
+
via R253). Future round could add `fill 200ms`
|
|
8186
|
+
to highlight too if theme-switch flicker is
|
|
8187
|
+
noticed. */
|
|
8188
|
+
fill={isLight ? '#10b981' : '#d1fae5'}
|
|
8189
|
+
opacity={resolvedOpacity}
|
|
8190
|
+
data-topo-hub-highlight
|
|
8191
|
+
data-topo-hub-highlight-visible={workingCount > 0 ? 'false' : 'true'}
|
|
8192
|
+
data-topo-hub-highlight-radius={highlightR}
|
|
8193
|
+
data-topo-hub-highlight-opacity={resolvedOpacity}
|
|
8194
|
+
data-topo-hub-highlight-breath={breathActive ? 'true' : 'false'}
|
|
8195
|
+
data-topo-hub-highlight-recede={hubRecede ? 'true' : 'false'}
|
|
8196
|
+
data-topo-hub-highlight-hovered={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
8197
|
+
data-topo-hub-highlight-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
8198
|
+
/* Round 574 — hub-highlight joins per-element brightness
|
|
8199
|
+
family at 13th anchor. Stacks brightness(1.15) onto
|
|
8200
|
+
R532's drop-shadow filter — same R564/R570/R571/R572/
|
|
8201
|
+
R573 pattern (drop-shadow + brightness in one filter
|
|
8202
|
+
chain). Hub idle disc now has 3 active hub-hover
|
|
8203
|
+
axes: R511 opacity 0.95 → 1.0 + R529 r 5.5 → 6 +
|
|
8204
|
+
R574 brightness(1.15). data-topo-hub-highlight-
|
|
8205
|
+
brightness attr surfaces the lift. */
|
|
8206
|
+
data-topo-hub-highlight-brightness={!reducedMotion && hoveredHub ? '1.15' : '1'}
|
|
8207
|
+
/* Round 510 / Loop — R509 follow-on: theme-toggle fill
|
|
8208
|
+
ease. Pre-R510 the hub-highlight transition spec only
|
|
8209
|
+
listed `opacity 300ms ease-out`. When R509 introduced
|
|
8210
|
+
theme-conditional fill (#d1fae5 ↔ #10b981), the fill
|
|
8211
|
+
change SNAPPED on theme toggle because the transition
|
|
8212
|
+
list didn't include `fill`. R510 extends to `fill
|
|
8213
|
+
200ms ease-out` so theme cycles smoothly through the
|
|
8214
|
+
emerald palette. 200ms timing matches the R253 halo
|
|
8215
|
+
fill transition (line ~6500) — both hub-cluster
|
|
8216
|
+
theme transitions now share a cadence so the focal
|
|
8217
|
+
cluster (digit + highlight + halo) eases as a unit.
|
|
8218
|
+
R508's recede opacity transition unchanged (300ms);
|
|
8219
|
+
fill is independent.
|
|
8220
|
+
R529: r as CSS property (R197/R198 idiom) + `r
|
|
8221
|
+
200ms ease-out` appended to transition list so
|
|
8222
|
+
the new hub-hover radius lift (5.5 → 6) eases
|
|
8223
|
+
under the same fill cadence. SVG attr r="5.5"
|
|
8224
|
+
above provides SSR fallback; inline style.r
|
|
8225
|
+
wins the cascade for the dynamic value.
|
|
8226
|
+
R532: filter drop-shadow glow on hub-hover —
|
|
8227
|
+
sibling to R476 hub-digit drop-shadow at the
|
|
8228
|
+
same gate (hoveredHub && !reducedMotion). Two
|
|
8229
|
+
adjacent hub focal elements (digit + highlight
|
|
8230
|
+
disc) now BOTH glow on hub-hover, reading as
|
|
8231
|
+
one unified focal cluster. Emerald palette
|
|
8232
|
+
matches R476:
|
|
8233
|
+
light: drop-shadow(0 0 2px rgba(16,185,129,0.6)) emerald-500
|
|
8234
|
+
cyber: drop-shadow(0 0 3px rgba(52,211,153,0.6)) emerald-400
|
|
8235
|
+
filter is paint-only (bbox unchanged); SMIL
|
|
8236
|
+
animate on opacity continues independently
|
|
8237
|
+
(animateAttr='opacity' vs CSS-property filter
|
|
8238
|
+
— non-conflicting). transition list extends to
|
|
8239
|
+
'filter 200ms ease-out' alongside fill/r.
|
|
8240
|
+
Drop-shadow visual-polish family extension
|
|
8241
|
+
(8 anchors): R476 hub-digit / R477 legend pin-
|
|
8242
|
+
ring / R478 recent freshness / hot edge / group
|
|
8243
|
+
label / zoom-state / node alias + R532 hub-
|
|
8244
|
+
highlight (this round). data-topo-hub-highlight-
|
|
8245
|
+
glow attr exposes the gate state. */
|
|
8246
|
+
style={{
|
|
8247
|
+
pointerEvents: 'none',
|
|
8248
|
+
r: `${highlightR}px`,
|
|
8249
|
+
filter: !reducedMotion && hoveredHub
|
|
8250
|
+
? (isLight
|
|
8251
|
+
? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.6)) brightness(1.15)'
|
|
8252
|
+
: 'drop-shadow(0 0 3px rgba(52, 211, 153, 0.6)) brightness(1.15)')
|
|
8253
|
+
: undefined,
|
|
8254
|
+
transition: 'opacity 300ms ease-out, fill 200ms ease-out, r 200ms ease-out, filter 200ms ease-out',
|
|
8255
|
+
} as React.CSSProperties}
|
|
8256
|
+
>
|
|
6638
8257
|
{/* Round 497 / Loop — idle-state breath (呼吸感 theme pivot
|
|
6639
8258
|
from the R492-R496 press-family arc). Pre-R497 the hub
|
|
6640
8259
|
idle highlight read as a static dim disc — present but
|
|
@@ -6655,10 +8274,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6655
8274
|
rather than a pulse, matching the "quiet" semantic.
|
|
6656
8275
|
data-topo-hub-highlight-breath attr exposes the
|
|
6657
8276
|
resolved gate state for tests. */}
|
|
6658
|
-
{
|
|
8277
|
+
{breathActive && (
|
|
6659
8278
|
<animate attributeName="opacity" values="0.85;1;0.85" dur="4s" repeatCount="indefinite" />
|
|
6660
8279
|
)}
|
|
6661
8280
|
</circle>
|
|
8281
|
+
);
|
|
8282
|
+
})()}
|
|
6662
8283
|
{/* R115 / Loop: hover hint ring. Stroke-only circle at r=14
|
|
6663
8284
|
that fades in when the hub is hovered — the same idea
|
|
6664
8285
|
R44 used for node avatars (group-hover stroke). r=14
|
|
@@ -6724,13 +8345,54 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6724
8345
|
data-topo-hub-hover-ring-radius={hoveredHub ? 17 : 14}
|
|
6725
8346
|
data-topo-hub-hover-ring-stroke-width="1.75"
|
|
6726
8347
|
data-topo-hub-hover-ring-opacity={hoveredHub ? (isLight ? 0.85 : 0.8) : 0}
|
|
6727
|
-
/* Round
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
8348
|
+
/* Round 535 / Loop — completes the hub-cluster glow
|
|
8349
|
+
QUARTET by adding drop-shadow to the hub-hover-ring.
|
|
8350
|
+
Pre-R535 the hub-hover trio (R476 digit + R532 highlight
|
|
8351
|
+
disc + R533 spokes) glowed in unified emerald (digit/
|
|
8352
|
+
disc) + cyan/teal (spokes) on hub-hover, but the ring
|
|
8353
|
+
itself — the outermost solid emerald boundary at
|
|
8354
|
+
r=14→17 — stayed flat. R535 adds the matching emerald
|
|
8355
|
+
drop-shadow to the ring so the FULL hub-cluster glows
|
|
8356
|
+
across all four concentric surfaces on hub-hover:
|
|
8357
|
+
digit (typography center) drop-shadow 0 0 3px emerald
|
|
8358
|
+
highlight disc (r=5.5/6) drop-shadow 0 0 3px emerald
|
|
8359
|
+
hover-ring (r=14/17) drop-shadow 0 0 3px emerald ← this round
|
|
8360
|
+
spokes (mesh) drop-shadow 0 0 1.5px cyan/teal
|
|
8361
|
+
The ring is only visible on hub-hover (opacity=0 rest);
|
|
8362
|
+
adding drop-shadow at the same gate means the glow shows
|
|
8363
|
+
the moment the ring shows — no extra state needed.
|
|
8364
|
+
Same R476/R532 emerald palette since the ring sits
|
|
8365
|
+
inside the focal-disc tier (its color is also emerald
|
|
8366
|
+
#059669/#10b981).
|
|
8367
|
+
transition list extends to include 'filter 200ms ease-
|
|
8368
|
+
out' alongside the existing 180ms opacity/r — slight
|
|
8369
|
+
cadence mismatch (180 vs 200) is acceptable; the filter
|
|
8370
|
+
only appears AFTER the ring fades in via opacity, and
|
|
8371
|
+
the 200ms vs 180ms 20ms tail difference is below
|
|
8372
|
+
perceptual threshold.
|
|
8373
|
+
Drop-shadow visual-polish family extension (11 anchors):
|
|
8374
|
+
the hub-cluster glow quartet (R476/R532/R533/R535) plus
|
|
8375
|
+
the 7 non-hub anchors (R477/R478/R479/R480/R481/R483/
|
|
8376
|
+
R534) makes for a thoroughly polished glow vocabulary
|
|
8377
|
+
across the canvas.
|
|
8378
|
+
data-topo-hub-hover-ring-glow attr exposes the gate
|
|
8379
|
+
state for tests. */
|
|
8380
|
+
data-topo-hub-hover-ring-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
8381
|
+
/* Round 579 — hub-hover-ring joins per-element brightness
|
|
8382
|
+
family at 18th anchor. Stacks brightness(1.15) onto
|
|
8383
|
+
R535's drop-shadow — same R564/R570/R571/R572/R573/R574/
|
|
8384
|
+
R575/R577/R578 stacked-filter pattern. Closes hub-
|
|
8385
|
+
cluster brightness at 4 concentric elements (digit
|
|
8386
|
+
R575 + highlight R574 + halo R577 + hover-ring R579). */
|
|
8387
|
+
data-topo-hub-hover-ring-brightness={!reducedMotion && hoveredHub ? '1.15' : '1'}
|
|
6731
8388
|
style={{
|
|
6732
8389
|
pointerEvents: 'none',
|
|
6733
|
-
|
|
8390
|
+
filter: !reducedMotion && hoveredHub
|
|
8391
|
+
? (isLight
|
|
8392
|
+
? 'drop-shadow(0 0 3px rgba(16, 185, 129, 0.5)) brightness(1.15)'
|
|
8393
|
+
: 'drop-shadow(0 0 3px rgba(52, 211, 153, 0.5)) brightness(1.15)')
|
|
8394
|
+
: undefined,
|
|
8395
|
+
transition: 'opacity 180ms ease-out, r 180ms ease-out, stroke 200ms ease-out, filter 200ms ease-out',
|
|
6734
8396
|
}}
|
|
6735
8397
|
/>
|
|
6736
8398
|
</g>)}
|
|
@@ -7507,10 +9169,38 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7507
9169
|
data-edge-endpoint-active={isEndpoint ? 'true' : 'false'}
|
|
7508
9170
|
data-edge-endpoint-ring-stroke-width={isEndpoint ? 2.4 : 1.6}
|
|
7509
9171
|
data-edge-endpoint-ring-radius={endpointR}
|
|
9172
|
+
data-edge-endpoint-ring-brightness={isEndpoint ? '1.15' : '1'}
|
|
7510
9173
|
style={{
|
|
7511
9174
|
pointerEvents: 'none',
|
|
7512
9175
|
r: `${endpointR}px`,
|
|
7513
|
-
|
|
9176
|
+
/* R585 — endpoint emphasis ring gains filter
|
|
9177
|
+
brightness(1.15) when an edge endpoint
|
|
9178
|
+
activates. 24th anchor in per-element
|
|
9179
|
+
brightness family, and the FOURTH edge-tier
|
|
9180
|
+
paint layer:
|
|
9181
|
+
rail (R581)
|
|
9182
|
+
visible path (R582)
|
|
9183
|
+
flow particle (R583)
|
|
9184
|
+
endpoint ring (R585) ← this round
|
|
9185
|
+
Edge-tier brightness coverage closes at 4/4
|
|
9186
|
+
paint surfaces. The endpoint ring is the
|
|
9187
|
+
edge's affinity marker at the connected
|
|
9188
|
+
nodes — when an edge lights up, all four
|
|
9189
|
+
paint surfaces brighten together for a
|
|
9190
|
+
single coherent edge-active gesture spanning
|
|
9191
|
+
the curve + the node ends.
|
|
9192
|
+
Endpoint ring 4-axis active signature now:
|
|
9193
|
+
opacity R182 0 → 0.85/0.9
|
|
9194
|
+
sw R233 1.6 → 2.4
|
|
9195
|
+
r R442 +7 → +8
|
|
9196
|
+
brightness R585 — → 1.15 ← this round
|
|
9197
|
+
Plain brightness (no url-filter stack) since
|
|
9198
|
+
the endpoint ring has no rest-time filter
|
|
9199
|
+
attribute. Inline style.filter undefined at
|
|
9200
|
+
rest (no flicker; opacity=0 already hides
|
|
9201
|
+
the ring). */
|
|
9202
|
+
filter: isEndpoint ? 'brightness(1.15)' : undefined,
|
|
9203
|
+
transition: 'opacity 180ms ease-out, stroke-width 180ms ease-out, r 180ms ease-out, filter 180ms ease-out',
|
|
7514
9204
|
} as React.CSSProperties}
|
|
7515
9205
|
/>
|
|
7516
9206
|
);
|
|
@@ -7586,8 +9276,40 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7586
9276
|
data-node-status-ring={status.label}
|
|
7587
9277
|
data-node-status-ring-hovered={isRingHovered ? 'true' : 'false'}
|
|
7588
9278
|
data-node-status-ring-stroke-width={ringStrokeWidth}
|
|
9279
|
+
data-node-status-ring-brightness={isRingHovered ? '1.15' : '1'}
|
|
7589
9280
|
style={{
|
|
7590
|
-
|
|
9281
|
+
/* R584 — status ring gets brightness(1.15) on
|
|
9282
|
+
hover. 23rd anchor in per-element brightness
|
|
9283
|
+
family. Stacks with url(#topo-glow) on
|
|
9284
|
+
cyber+online to preserve the SVG glow filter;
|
|
9285
|
+
plain brightness on light or cyber+offline.
|
|
9286
|
+
Same R582/R583 stacked-filter pattern: inline
|
|
9287
|
+
style.filter overrides the attribute filter,
|
|
9288
|
+
stacked syntax preserves the glow on hover.
|
|
9289
|
+
|
|
9290
|
+
Per-node hover signature now 10 layers (added
|
|
9291
|
+
to the R438 stack):
|
|
9292
|
+
R26 group translateY -2px
|
|
9293
|
+
R217 stroke tint
|
|
9294
|
+
R142 drop-shadow boost
|
|
9295
|
+
R427 alias letter-spacing
|
|
9296
|
+
R428 sub-text letter-spacing
|
|
9297
|
+
R429 body opacity 0.94 → 1.0
|
|
9298
|
+
R430 hub-spoke α+
|
|
9299
|
+
R435 hub-spoke sw+
|
|
9300
|
+
R438 status-ring sw +0.5
|
|
9301
|
+
R584 status-ring brightness(1.15) ← this round
|
|
9302
|
+
|
|
9303
|
+
Per-element brightness family: 23 anchors.
|
|
9304
|
+
Stacked-filter sub-pattern: 17 anchors. */
|
|
9305
|
+
filter: isRingHovered
|
|
9306
|
+
? (isLight
|
|
9307
|
+
? 'brightness(1.15)'
|
|
9308
|
+
: (isOnline
|
|
9309
|
+
? 'url(#topo-glow) brightness(1.15)'
|
|
9310
|
+
: 'brightness(1.15)'))
|
|
9311
|
+
: undefined,
|
|
9312
|
+
transition: 'fill 300ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, filter 300ms ease-out',
|
|
7591
9313
|
}}
|
|
7592
9314
|
/>
|
|
7593
9315
|
);
|
|
@@ -7643,6 +9365,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7643
9365
|
const internByAlias = /书生|书小生|intern/i.test(session.alias);
|
|
7644
9366
|
|
|
7645
9367
|
if (isIntern || internByAlias || vendor.logo) {
|
|
9368
|
+
/* Round 501 / Loop — vendor avatar inside node circles
|
|
9369
|
+
gains a hover-gated brightness lift. Pre-R501 the
|
|
9370
|
+
avatar <image> was the only per-node surface with
|
|
9371
|
+
NO hover treatment: R26 lifted the card, R242 tinted
|
|
9372
|
+
the card stroke, R427 spread the alias letter-
|
|
9373
|
+
spacing, R500 added the alias drop-shadow, R208
|
|
9374
|
+
lifted the runtime badge ring, R443 thickened
|
|
9375
|
+
the badge icon stroke, R177 brightened the
|
|
9376
|
+
halo — but the most visually-prominent element
|
|
9377
|
+
(the vendor logo / 书生 coin centred in each node)
|
|
9378
|
+
stayed paint-static. R501 closes the per-node
|
|
9379
|
+
hover-affordance arc by adding a 15% brightness
|
|
9380
|
+
lift on hover.
|
|
9381
|
+
Implementation: CSS filter: brightness(1.15)
|
|
9382
|
+
when hoveredAlias === session.alias. Pure paint
|
|
9383
|
+
axis on the <image> element — no geometry change,
|
|
9384
|
+
no bbox shift. Modern-browser supported (Chrome 64+
|
|
9385
|
+
/ FF 56+ / Safari 9.1+).
|
|
9386
|
+
Hits 节点视觉 theme. data-node-avatar-hovered
|
|
9387
|
+
attr surfaces the gate for tests.
|
|
9388
|
+
Gated on !reducedMotion as a courtesy (brightness
|
|
9389
|
+
transition < ~50ms still feels instant; the gate
|
|
9390
|
+
avoids the transition cycle for a11y users). */
|
|
9391
|
+
const isAvatarHovered = !reducedMotion && hoveredAlias === session.alias;
|
|
7646
9392
|
return (
|
|
7647
9393
|
<image
|
|
7648
9394
|
href={vendor.logo ?? '/intern_avatar.png'}
|
|
@@ -7651,9 +9397,137 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7651
9397
|
width={size}
|
|
7652
9398
|
height={size}
|
|
7653
9399
|
preserveAspectRatio="xMidYMid meet"
|
|
9400
|
+
data-node-avatar={session.alias}
|
|
9401
|
+
data-node-avatar-hovered={isAvatarHovered ? 'true' : 'false'}
|
|
9402
|
+
data-node-avatar-rotate={isAvatarHovered ? '3' : '0'}
|
|
9403
|
+
data-node-avatar-scale={isAvatarHovered ? '1.05' : '1'}
|
|
9404
|
+
data-node-avatar-drop-shadow={isAvatarHovered ? `0 0 4px ${pal.legendAccent}99` : 'none'}
|
|
9405
|
+
style={{
|
|
9406
|
+
/* R600 milestone — node vendor avatar gains
|
|
9407
|
+
hover-rotate-3 via Tailwind v4 individual
|
|
9408
|
+
`rotate` CSS property. 6th anchor in hover-
|
|
9409
|
+
rotate idiom (R350 reset / R547 pill × /
|
|
9410
|
+
R549 brand logo / R576 fullscreen / R599
|
|
9411
|
+
runtime badge / R600 node avatar).
|
|
9412
|
+
|
|
9413
|
+
Same +3° "wobble awake" tilt as R576
|
|
9414
|
+
fullscreen + R599 runtime badge — the
|
|
9415
|
+
vendor logo (Claude/OpenAI/etc.) at the
|
|
9416
|
+
center of every node gently tilts under
|
|
9417
|
+
cursor. Most visible per-node element gains
|
|
9418
|
+
a rotation signal on top of R501's
|
|
9419
|
+
brightness lift.
|
|
9420
|
+
|
|
9421
|
+
transform-origin pinned to the node center
|
|
9422
|
+
(pos.x, pos.y) so the logo rotates around
|
|
9423
|
+
its own visual centre, not the <image>
|
|
9424
|
+
element's default bbox top-left.
|
|
9425
|
+
|
|
9426
|
+
Per-node hover signature now 11 axes:
|
|
9427
|
+
R26 group translateY -2px
|
|
9428
|
+
R217 stroke tint
|
|
9429
|
+
R142 drop-shadow boost
|
|
9430
|
+
R427 alias letter-spacing
|
|
9431
|
+
R428 sub-text letter-spacing
|
|
9432
|
+
R429 body opacity 0.94 → 1.0
|
|
9433
|
+
R430 hub-spoke α+
|
|
9434
|
+
R435 hub-spoke sw+
|
|
9435
|
+
R438 status-ring sw +0.5
|
|
9436
|
+
R584 status-ring brightness(1.15)
|
|
9437
|
+
R600 avatar rotate 0 → 3deg ← this round
|
|
9438
|
+
|
|
9439
|
+
Hover-rotate idiom family (6 anchors):
|
|
9440
|
+
R350 chrome reset icon -8°
|
|
9441
|
+
R547 chip pill × close (rotate)
|
|
9442
|
+
R549 brand 书生 logo (rotate)
|
|
9443
|
+
R576 chrome fullscreen icon +3°
|
|
9444
|
+
R599 node runtime badge +3°
|
|
9445
|
+
R600 node vendor avatar +3° ← milestone
|
|
9446
|
+
|
|
9447
|
+
transition list extends 'rotate 200ms
|
|
9448
|
+
ease-out' alongside the existing 'filter
|
|
9449
|
+
200ms ease-out' — both axes ride one beat. */
|
|
9450
|
+
rotate: isAvatarHovered ? '3deg' : '0deg',
|
|
9451
|
+
/* R602 — per-node avatar gains hover-scale-1.05.
|
|
9452
|
+
Mirrors R548 brand 书生 logo's scale-105 idiom
|
|
9453
|
+
at the per-node tier. Stacks with R600 rotate
|
|
9454
|
+
+ R501 brightness so the avatar gets a 3-axis
|
|
9455
|
+
hover signature (scale + rotate + brightness)
|
|
9456
|
+
matching the brand-logo polish vocabulary.
|
|
9457
|
+
Tailwind v4 individual `scale` CSS property
|
|
9458
|
+
(banked R547 sibling to `rotate`) — independent
|
|
9459
|
+
of SVG <image>'s x/y/width/height attributes;
|
|
9460
|
+
composes cleanly with rotate. transform-origin
|
|
9461
|
+
shared with R600 — pivots around node center.
|
|
9462
|
+
Pure paint axis: SVG bbox attributes unchanged
|
|
9463
|
+
at rest, so overlap-test invariant holds (the
|
|
9464
|
+
probe reads attributes, not visual bbox post-
|
|
9465
|
+
CSS transform). Scale only fires on hover. */
|
|
9466
|
+
scale: isAvatarHovered ? '1.05' : '1',
|
|
9467
|
+
transformOrigin: `${pos.x}px ${pos.y}px`,
|
|
9468
|
+
/* R605 — per-node avatar gains drop-shadow on
|
|
9469
|
+
hover, stacked with R501 brightness(1.15).
|
|
9470
|
+
4th hover axis on the avatar (brightness +
|
|
9471
|
+
rotate + scale + drop-shadow), bringing
|
|
9472
|
+
avatar coverage closer to brand 书生 logo's
|
|
9473
|
+
5-axis signature (only missing R553 always-
|
|
9474
|
+
on breath, which would be visually noisy
|
|
9475
|
+
across 30 nodes).
|
|
9476
|
+
|
|
9477
|
+
Color: pal.legendAccent at 0x99 alpha (~60%)
|
|
9478
|
+
— cyan/teal accent matches the edge-tier
|
|
9479
|
+
vocabulary (R534 edge-badge hover-glow,
|
|
9480
|
+
R478 recent-row pip glow, R479 group-label
|
|
9481
|
+
glow). The avatar joins the "lit by accent
|
|
9482
|
+
on attention" family at the per-node tier.
|
|
9483
|
+
|
|
9484
|
+
4px blur radius reads tight on the avatar's
|
|
9485
|
+
14-28px footprint — smaller than R604 brand
|
|
9486
|
+
logo's 8px (40×40 footprint) for size
|
|
9487
|
+
proportionality.
|
|
9488
|
+
|
|
9489
|
+
Same R582/R583 stacked-filter pattern.
|
|
9490
|
+
transition list already includes 'filter
|
|
9491
|
+
200ms ease-out' from R501; no transition
|
|
9492
|
+
change needed.
|
|
9493
|
+
|
|
9494
|
+
Per-node avatar hover signature now 4 axes:
|
|
9495
|
+
R501/R558 brightness 1 → 1.15
|
|
9496
|
+
R600/R601 rotate 0 → 3deg
|
|
9497
|
+
R602 scale 1 → 1.05
|
|
9498
|
+
R605 drop-shadow → pal.legendAccent ← this round */
|
|
9499
|
+
filter: isAvatarHovered
|
|
9500
|
+
? `drop-shadow(0 0 4px ${pal.legendAccent}99) brightness(1.15)`
|
|
9501
|
+
: undefined,
|
|
9502
|
+
transition: 'filter 200ms ease-out, rotate 200ms ease-out, scale 200ms ease-out',
|
|
9503
|
+
}}
|
|
7654
9504
|
/>
|
|
7655
9505
|
);
|
|
7656
9506
|
}
|
|
9507
|
+
/* Round 558 / Loop — closes the per-node avatar hover-
|
|
9508
|
+
affordance arc by extending R501's brightness lift
|
|
9509
|
+
(image-branch only) to the TWO remaining avatar
|
|
9510
|
+
variants: vendor monogram + prefix-group hue-hashed
|
|
9511
|
+
initial fallback. Pre-R558 only the vendor.logo
|
|
9512
|
+
image branch lifted on hover; the other two
|
|
9513
|
+
variants stayed paint-static under attention.
|
|
9514
|
+
|
|
9515
|
+
Per-node avatar hover-brightness family (3 anchors,
|
|
9516
|
+
all gated on !reducedMotion && hoveredAlias matches):
|
|
9517
|
+
R501 vendor.logo image filter on <image>
|
|
9518
|
+
R558 vendor monogram filter on wrapping <g>
|
|
9519
|
+
R558 prefix-group fallback filter on wrapping <g>
|
|
9520
|
+
|
|
9521
|
+
Implementation: each fallback branch returns a
|
|
9522
|
+
fragment with <circle> + <text> as siblings.
|
|
9523
|
+
Wrapping them in a single <g> with the filter
|
|
9524
|
+
centralizes the paint axis. Same brightness(1.15)
|
|
9525
|
+
value as R501 for cross-branch consistency. Same
|
|
9526
|
+
transition cadence (filter 200ms ease-out).
|
|
9527
|
+
|
|
9528
|
+
data-node-avatar-monogram-hovered + -fallback-
|
|
9529
|
+
hovered attrs surface the gates for tests. */
|
|
9530
|
+
const isAvatarFallbackHovered = !reducedMotion && hoveredAlias === session.alias;
|
|
7657
9531
|
if (vendor.id !== 'unknown') {
|
|
7658
9532
|
// Known model house, logo asset not in public/vendors/
|
|
7659
9533
|
// yet — vendor-tinted monogram stands in.
|
|
@@ -7673,7 +9547,43 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7673
9547
|
where less visual weight signals "we don't know
|
|
7674
9548
|
what this is" appropriately. */
|
|
7675
9549
|
return (
|
|
7676
|
-
|
|
9550
|
+
<g
|
|
9551
|
+
data-node-avatar-monogram={session.alias}
|
|
9552
|
+
data-node-avatar-monogram-hovered={isAvatarFallbackHovered ? 'true' : 'false'}
|
|
9553
|
+
data-node-avatar-monogram-rotate={isAvatarFallbackHovered ? '3' : '0'}
|
|
9554
|
+
data-node-avatar-monogram-scale={isAvatarFallbackHovered ? '1.05' : '1'}
|
|
9555
|
+
data-node-avatar-monogram-drop-shadow={isAvatarFallbackHovered ? `0 0 4px ${pal.legendAccent}99` : 'none'}
|
|
9556
|
+
style={{
|
|
9557
|
+
/* R601 — vendor-monogram fallback gains hover-
|
|
9558
|
+
rotate-3 mirroring R600's image-branch
|
|
9559
|
+
closure. Per-node avatar rotate coverage
|
|
9560
|
+
now 2/3 (image R600, monogram R601 here;
|
|
9561
|
+
prefix-group fallback completes 3/3 below).
|
|
9562
|
+
Same R558 closure-of-arc semantics applied
|
|
9563
|
+
to the rotate axis — what brightness did
|
|
9564
|
+
for the avatar 3-branch arc (R501 image +
|
|
9565
|
+
R558 monogram + R558 fallback), rotate
|
|
9566
|
+
now does (R600 + R601 monogram + R601
|
|
9567
|
+
fallback).
|
|
9568
|
+
transform-origin pinned to node center
|
|
9569
|
+
(pos.x, pos.y) — same idiom as R600. */
|
|
9570
|
+
rotate: isAvatarFallbackHovered ? '3deg' : '0deg',
|
|
9571
|
+
/* R602 sibling — vendor monogram fallback
|
|
9572
|
+
gains hover-scale-1.05 to match the image
|
|
9573
|
+
branch above. Per-node avatar scale coverage
|
|
9574
|
+
now 2/3 (image R602, monogram R602 here;
|
|
9575
|
+
prefix-group fallback completes 3/3 below). */
|
|
9576
|
+
scale: isAvatarFallbackHovered ? '1.05' : '1',
|
|
9577
|
+
transformOrigin: `${pos.x}px ${pos.y}px`,
|
|
9578
|
+
/* R605 sibling — monogram fallback gains
|
|
9579
|
+
stacked drop-shadow + brightness filter
|
|
9580
|
+
on hover, mirroring the image branch above. */
|
|
9581
|
+
filter: isAvatarFallbackHovered
|
|
9582
|
+
? `drop-shadow(0 0 4px ${pal.legendAccent}99) brightness(1.15)`
|
|
9583
|
+
: undefined,
|
|
9584
|
+
transition: 'filter 200ms ease-out, rotate 200ms ease-out, scale 200ms ease-out',
|
|
9585
|
+
}}
|
|
9586
|
+
>
|
|
7677
9587
|
<circle cx={pos.x} cy={pos.y} r={ar} fill={vendor.mono.bg} stroke={vendor.mono.ring} strokeWidth="1.5" />
|
|
7678
9588
|
{/* Round 284 / Loop: known-vendor monogram letter
|
|
7679
9589
|
swaps fontFamily monospace → system sans-serif.
|
|
@@ -7705,14 +9615,51 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7705
9615
|
>
|
|
7706
9616
|
{vendor.initial}
|
|
7707
9617
|
</text>
|
|
7708
|
-
|
|
9618
|
+
</g>
|
|
7709
9619
|
);
|
|
7710
9620
|
}
|
|
7711
9621
|
// Round 106 (issue #83): hue keyed to the prefix group,
|
|
7712
9622
|
// not the full alias — every 通信* node shares one color.
|
|
7713
9623
|
const c = aliasAvatarColors(groupKeys[session.alias] || session.alias);
|
|
7714
9624
|
return (
|
|
7715
|
-
|
|
9625
|
+
<g
|
|
9626
|
+
data-node-avatar-fallback={session.alias}
|
|
9627
|
+
data-node-avatar-fallback-hovered={isAvatarFallbackHovered ? 'true' : 'false'}
|
|
9628
|
+
data-node-avatar-fallback-rotate={isAvatarFallbackHovered ? '3' : '0'}
|
|
9629
|
+
data-node-avatar-fallback-scale={isAvatarFallbackHovered ? '1.05' : '1'}
|
|
9630
|
+
data-node-avatar-fallback-drop-shadow={isAvatarFallbackHovered ? `0 0 4px ${pal.legendAccent}99` : 'none'}
|
|
9631
|
+
style={{
|
|
9632
|
+
/* R601 — prefix-group hue-hashed initial fallback
|
|
9633
|
+
gains hover-rotate-3 closing per-node avatar
|
|
9634
|
+
rotate coverage at 3/3 branches (image R600 +
|
|
9635
|
+
monogram R601 + fallback R601 here).
|
|
9636
|
+
Same idiom across all 3 branches: rotate 3deg
|
|
9637
|
+
on hover, transform-origin pinned to node
|
|
9638
|
+
center, transition 'rotate 200ms ease-out'
|
|
9639
|
+
alongside the existing 'filter 200ms' filter
|
|
9640
|
+
transition from R558. Whatever the vendor-
|
|
9641
|
+
detection branch resolves to (image / monogram
|
|
9642
|
+
/ hue-hashed initial), the user gets the same
|
|
9643
|
+
+3° wobble-awake gesture on alias hover. */
|
|
9644
|
+
rotate: isAvatarFallbackHovered ? '3deg' : '0deg',
|
|
9645
|
+
/* R602 sibling — prefix-group hue-hashed fallback
|
|
9646
|
+
gains hover-scale-1.05 closing per-node avatar
|
|
9647
|
+
scale coverage at 3/3 branches (image + monogram
|
|
9648
|
+
+ fallback all share the same scale-1.05 +
|
|
9649
|
+
rotate-3deg + brightness-1.15 hover signature). */
|
|
9650
|
+
scale: isAvatarFallbackHovered ? '1.05' : '1',
|
|
9651
|
+
transformOrigin: `${pos.x}px ${pos.y}px`,
|
|
9652
|
+
/* R605 sibling — prefix-group hue-hashed fallback
|
|
9653
|
+
gains stacked drop-shadow + brightness filter
|
|
9654
|
+
on hover. Closes per-node avatar drop-shadow
|
|
9655
|
+
coverage at 3/3 branches (image + monogram +
|
|
9656
|
+
fallback all share the same stacked filter). */
|
|
9657
|
+
filter: isAvatarFallbackHovered
|
|
9658
|
+
? `drop-shadow(0 0 4px ${pal.legendAccent}99) brightness(1.15)`
|
|
9659
|
+
: undefined,
|
|
9660
|
+
transition: 'filter 200ms ease-out, rotate 200ms ease-out, scale 200ms ease-out',
|
|
9661
|
+
}}
|
|
9662
|
+
>
|
|
7716
9663
|
<circle cx={pos.x} cy={pos.y} r={ar} fill={c.bg} stroke={c.ring} strokeWidth="1" />
|
|
7717
9664
|
<text
|
|
7718
9665
|
x={pos.x}
|
|
@@ -7726,7 +9673,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7726
9673
|
>
|
|
7727
9674
|
{aliasInitial(session.alias)}
|
|
7728
9675
|
</text>
|
|
7729
|
-
|
|
9676
|
+
</g>
|
|
7730
9677
|
);
|
|
7731
9678
|
})()}
|
|
7732
9679
|
{/* Issue #96: runtime badge — small corner glyph marking the
|
|
@@ -7757,7 +9704,106 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7757
9704
|
// badge-active exposes the gate for tests.
|
|
7758
9705
|
const isNodeActive = !reducedMotion && hoveredAlias === session.alias;
|
|
7759
9706
|
return (
|
|
7760
|
-
|
|
9707
|
+
/* Round 559 / Loop — runtime badge outer <g> picks up
|
|
9708
|
+
a drop-shadow glow on node hover, using the runtime's
|
|
9709
|
+
own identity color (rt.color, hex). 16th anchor in
|
|
9710
|
+
the drop-shadow visual-polish family. Pairs with
|
|
9711
|
+
existing R208 ring r-lift + R443 icon sw-lift for
|
|
9712
|
+
a 3-axis runtime-badge hover signature now spanning
|
|
9713
|
+
geometry + stroke + paint glow:
|
|
9714
|
+
R208 ring r 7 → 8 (online)
|
|
9715
|
+
R208 ring sw 1.5 → 2
|
|
9716
|
+
R443 icon sw 2.4 → 2.8
|
|
9717
|
+
R559 outer filter none → drop-shadow(rt.color) ← this round
|
|
9718
|
+
Filter on the OUTER <g> applies uniformly to both
|
|
9719
|
+
the ring <circle> and the inner icon <path> —
|
|
9720
|
+
single paint-axis lift covers both layers in one
|
|
9721
|
+
motion-coherent gesture.
|
|
9722
|
+
Hue: `${rt.color}99` hex+alpha (60%) — rt.color is
|
|
9723
|
+
6-digit hex (#a78bfa / #38bdf8 / #34d399 / #fbbf24
|
|
9724
|
+
per lib/vendorIdentity.ts), so hex+alpha concat is
|
|
9725
|
+
safe (banked R541 pattern: hex sources use hex+
|
|
9726
|
+
alpha; only hsl/color()/dynamic sources need color-
|
|
9727
|
+
mix).
|
|
9728
|
+
2px blur reads tight on a small badge (r=7 online
|
|
9729
|
+
/ r=5.5 offline). transition list adds 'filter
|
|
9730
|
+
150ms ease-out' matching the R208 ring r/sw cadence
|
|
9731
|
+
at this surface.
|
|
9732
|
+
Drop-shadow visual-polish family extension (16
|
|
9733
|
+
anchors now): R476/R477/R478/R479/R480/R481 +
|
|
9734
|
+
R500/R532-R536/R537/R538/R540/R543-R546/R550 +
|
|
9735
|
+
R559 (this round).
|
|
9736
|
+
data-runtime-badge-glow attr surfaces the gate
|
|
9737
|
+
for tests. */
|
|
9738
|
+
<g
|
|
9739
|
+
data-runtime-badge-glow={isNodeActive ? 'true' : 'false'}
|
|
9740
|
+
data-runtime-badge-brightness={isNodeActive ? '1.15' : '1'}
|
|
9741
|
+
data-runtime-badge-rotate={isNodeActive ? '3' : '0'}
|
|
9742
|
+
style={{
|
|
9743
|
+
pointerEvents: 'none',
|
|
9744
|
+
/* R599 — runtime badge gains hover-rotate-3.
|
|
9745
|
+
5th anchor in hover-rotate idiom (R350 reset
|
|
9746
|
+
-8° / R547 pill × / R549 brand logo / R576
|
|
9747
|
+
fullscreen +3° / R599 runtime badge +3°).
|
|
9748
|
+
Same +3° tilt as R576 fullscreen icon — at the
|
|
9749
|
+
14×14 badge scale, a 3° rotation reads as a
|
|
9750
|
+
subtle "wobble awake under attention" gesture
|
|
9751
|
+
that doesn't overwhelm the small surface.
|
|
9752
|
+
Applied via CSS individual `rotate` property
|
|
9753
|
+
(Tailwind v4 banked R547 — independent of SVG
|
|
9754
|
+
transform attribute, composes cleanly with
|
|
9755
|
+
the inner <g>'s translate+scale transform).
|
|
9756
|
+
transform-origin pinned to the badge center
|
|
9757
|
+
(bx, by) so rotation pivots around the
|
|
9758
|
+
visual centre, not the outer <g>'s default
|
|
9759
|
+
bbox corner — keeps the ring and icon spinning
|
|
9760
|
+
in place rather than orbiting offset.
|
|
9761
|
+
Runtime badge hover signature now 6 axes —
|
|
9762
|
+
densest per-node element in TopoGraph:
|
|
9763
|
+
R208 ring r 7 → 8
|
|
9764
|
+
R208 ring sw 1.5 → 2
|
|
9765
|
+
R443 icon sw 2.4 → 2.8
|
|
9766
|
+
R559 outer filter none → drop-shadow(rt.color)
|
|
9767
|
+
R586 outer filter stack brightness(1.15)
|
|
9768
|
+
R599 outer rotate 0 → 3deg ← this round
|
|
9769
|
+
transition list extends with 'rotate 150ms
|
|
9770
|
+
ease-out' matching R586/R559 cadence at this
|
|
9771
|
+
surface. Six axes all ride one 150ms beat.
|
|
9772
|
+
Hover-rotate idiom family extension
|
|
9773
|
+
(5 anchors): R350 / R547 / R549 / R576 / R599. */
|
|
9774
|
+
rotate: isNodeActive ? '3deg' : '0deg',
|
|
9775
|
+
transformOrigin: `${bx}px ${by}px`,
|
|
9776
|
+
/* R586 — runtime badge outer <g> stacks
|
|
9777
|
+
brightness(1.15) onto the existing R559
|
|
9778
|
+
drop-shadow on node hover. 25th anchor in
|
|
9779
|
+
per-element brightness family, 18th in
|
|
9780
|
+
stacked-filter sub-pattern.
|
|
9781
|
+
|
|
9782
|
+
Runtime badge hover signature now CLOSED
|
|
9783
|
+
at 4 axes (geometry + stroke + paint glow
|
|
9784
|
+
+ paint brightness):
|
|
9785
|
+
R208 ring r 7 → 8
|
|
9786
|
+
R208 ring sw 1.5 → 2
|
|
9787
|
+
R443 icon sw 2.4 → 2.8
|
|
9788
|
+
R559 outer filter none → drop-shadow(rt.color)
|
|
9789
|
+
R586 outer filter stack brightness(1.15) ← this round
|
|
9790
|
+
|
|
9791
|
+
The drop-shadow + brightness stack is the
|
|
9792
|
+
banked R564/R570 "halo + glow" pattern —
|
|
9793
|
+
drop-shadow paints the colored halo, brightness
|
|
9794
|
+
lifts the underlying paint (ring stroke +
|
|
9795
|
+
icon path both gain ~15% luminance). Single
|
|
9796
|
+
CSS filter chain on the outer <g> covers
|
|
9797
|
+
both child layers uniformly.
|
|
9798
|
+
|
|
9799
|
+
Same R208/R443/R559 150ms cadence preserved
|
|
9800
|
+
via the existing transition. */
|
|
9801
|
+
filter: isNodeActive
|
|
9802
|
+
? `drop-shadow(0 0 2px ${rt.color}99) brightness(1.15)`
|
|
9803
|
+
: undefined,
|
|
9804
|
+
transition: 'filter 150ms ease-out, rotate 150ms ease-out',
|
|
9805
|
+
}}
|
|
9806
|
+
>
|
|
7761
9807
|
<circle
|
|
7762
9808
|
cx={bx} cy={by} r={br}
|
|
7763
9809
|
fill={pal.containerBg}
|
|
@@ -8074,6 +10120,43 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8074
10120
|
R211 fill 300ms + R305 letter-spacing 200ms
|
|
8075
10121
|
transition list preserved; only the
|
|
8076
10122
|
conditional gets a middle case. */}
|
|
10123
|
+
{/* Round 500 / Loop — milestone round, opens
|
|
10124
|
+
per-node alias drop-shadow polish. Extends the
|
|
10125
|
+
R476-R481 drop-shadow visual-polish family to a
|
|
10126
|
+
7th anchor: hovered alias text gains a soft
|
|
10127
|
+
status-coloured text-glow. Pre-R500 hover on
|
|
10128
|
+
a node triggered card-lift (R26 translateY) +
|
|
10129
|
+
card-stroke (R242 tint) + alias letter-spacing
|
|
10130
|
+
(R427 0.3px tier) but the alias TEXT itself had
|
|
10131
|
+
no paint-axis cue beyond fill (R211). R500 adds
|
|
10132
|
+
a drop-shadow on the text glyph itself, so the
|
|
10133
|
+
identity glyph itself lights up under attention
|
|
10134
|
+
— matching the R476 idiom (hub-digit emerald
|
|
10135
|
+
glow on hover) at the per-node identity scope.
|
|
10136
|
+
2px blur radius at 50% alpha — subtler than the
|
|
10137
|
+
R476 hub-digit (3px at 60%) because the alias
|
|
10138
|
+
text is smaller and more numerous (1 per node)
|
|
10139
|
+
so an aggressive glow would multiply into
|
|
10140
|
+
visual noise. Status-coloured (status.text) so
|
|
10141
|
+
the glow inherits the node's working/idle/
|
|
10142
|
+
offline palette — green/cyan/gray respectively.
|
|
10143
|
+
Drop-shadow visual-polish family — 7 anchors:
|
|
10144
|
+
R476 hub digit hover-gated emerald
|
|
10145
|
+
R477 legend pin-ring pin-gated row.fill
|
|
10146
|
+
R478 recent-row pip fresh-gated cyan
|
|
10147
|
+
R479 group-label text pin-gated cyan
|
|
10148
|
+
R480 hot-lane edge hot-gated amber
|
|
10149
|
+
R481 zoom-state minimap zoom-gated cyan
|
|
10150
|
+
R500 node alias text hover-gated status.text ← this round
|
|
10151
|
+
Filter is paint-only; bbox unchanged; overlap-
|
|
10152
|
+
test invariants hold (R51 selector gated to
|
|
10153
|
+
g[data-node] descendants with strokeWidth
|
|
10154
|
+
sentinels; text element doesn't carry stroke).
|
|
10155
|
+
transition list extends to include 'filter
|
|
10156
|
+
200ms ease-out' alongside the existing fill
|
|
10157
|
+
300ms + letter-spacing 200ms tweens.
|
|
10158
|
+
data-node-alias-glow attr surfaces the hover
|
|
10159
|
+
gate for tests. */}
|
|
8077
10160
|
<text
|
|
8078
10161
|
x="0" y="1" textAnchor="middle"
|
|
8079
10162
|
fill={status.text}
|
|
@@ -8081,12 +10164,43 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8081
10164
|
data-node-alias-text={session.alias}
|
|
8082
10165
|
data-node-alias-chat-target={chatAlias === session.alias ? 'true' : 'false'}
|
|
8083
10166
|
data-node-alias-hovered={hoveredAlias === session.alias ? 'true' : 'false'}
|
|
10167
|
+
data-node-alias-glow={!reducedMotion && hoveredAlias === session.alias ? 'true' : 'false'}
|
|
8084
10168
|
style={{
|
|
8085
|
-
transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out',
|
|
10169
|
+
transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out, filter 200ms ease-out',
|
|
8086
10170
|
letterSpacing:
|
|
8087
10171
|
chatAlias === session.alias ? '0.5px' :
|
|
8088
10172
|
hoveredAlias === session.alias ? '0.3px' : '0px',
|
|
10173
|
+
/* Round 564 / Loop — alias text filter stacks
|
|
10174
|
+
brightness(1.15) on top of R500's drop-shadow
|
|
10175
|
+
on hover. Mirrors R542 pressure-seg pattern
|
|
10176
|
+
(brightness + drop-shadow in one stacked
|
|
10177
|
+
filter declaration). Pre-R564 hover added
|
|
10178
|
+
only a drop-shadow halo around the glyph;
|
|
10179
|
+
post-R564 the glyph ALSO brightens, so the
|
|
10180
|
+
identity text reads as both "glowing" AND
|
|
10181
|
+
"lit up" under attention — dual paint axes
|
|
10182
|
+
through one filter chain.
|
|
10183
|
+
CSS filter supports multiple functions
|
|
10184
|
+
applied left-to-right. brightness(1.15)
|
|
10185
|
+
lifts the per-status text color (status.text:
|
|
10186
|
+
green/teal/slate per tier) by 15%; the drop-
|
|
10187
|
+
shadow then paints the outer halo in the
|
|
10188
|
+
status-tier hue. Together: the alias glyph
|
|
10189
|
+
both intensifies its identity color AND
|
|
10190
|
+
radiates outward in that same color.
|
|
10191
|
+
Same +15% brightness as R501 vendor logo
|
|
10192
|
+
avatar (banked per-node hover-brightness
|
|
10193
|
+
pattern). Consistent +15% across all per-
|
|
10194
|
+
node identity surfaces (logo, monogram,
|
|
10195
|
+
fallback avatar from R558, AND now alias
|
|
10196
|
+
text). Cross-element brightness consistency.
|
|
10197
|
+
data-node-alias-brightness attr surfaces
|
|
10198
|
+
the lift for tests. */
|
|
10199
|
+
filter: !reducedMotion && hoveredAlias === session.alias
|
|
10200
|
+
? `drop-shadow(0 0 2px ${status.text}80) brightness(1.15)`
|
|
10201
|
+
: undefined,
|
|
8089
10202
|
}}
|
|
10203
|
+
data-node-alias-brightness={!reducedMotion && hoveredAlias === session.alias ? '1.15' : '1'}
|
|
8090
10204
|
>
|
|
8091
10205
|
{truncate(session.alias, fullMax)}
|
|
8092
10206
|
</text>
|
|
@@ -8126,6 +10240,31 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8126
10240
|
alias > status hierarchy holds at the type
|
|
8127
10241
|
level. data-node-sub-text-font-weight attr
|
|
8128
10242
|
exposes the value for tests. */}
|
|
10243
|
+
{/* Round 567 / Loop — node sub-text joins the per-
|
|
10244
|
+
node hover-brightness consistency family. Pre-
|
|
10245
|
+
R567 sub-text had only fill brighten (R211) +
|
|
10246
|
+
ls 0→0.2 (R428); the alias text above lifted
|
|
10247
|
+
via R500 drop-shadow + R564 brightness(1.15)
|
|
10248
|
+
stacked. R567 adds brightness(1.15) to sub-
|
|
10249
|
+
text on the same isNodeActive gate so it
|
|
10250
|
+
chromatically lifts together with the alias.
|
|
10251
|
+
Per-node hover-brightness consistency family
|
|
10252
|
+
— 6 anchors at uniform +15%:
|
|
10253
|
+
R501 vendor.logo image filter on <image>
|
|
10254
|
+
R558 vendor monogram filter on <g>
|
|
10255
|
+
R558 prefix-group fallback filter on <g>
|
|
10256
|
+
R564 alias text (stacked w/ DS) brightness(1.15)
|
|
10257
|
+
R567 node sub-text brightness(1.15) ← this round
|
|
10258
|
+
(+ R559 runtime badge drop-shadow tier-color glow)
|
|
10259
|
+
Now every per-node identity surface (3 avatar
|
|
10260
|
+
variants + alias + sub-text + badge) lifts
|
|
10261
|
+
together on node hover with consistent visual
|
|
10262
|
+
response.
|
|
10263
|
+
Pure paint axis; bbox unchanged. transition
|
|
10264
|
+
list extends to include 'filter 200ms ease-
|
|
10265
|
+
out' matching R428 ls cadence at this scope.
|
|
10266
|
+
data-node-sub-text-brightness attr exposes
|
|
10267
|
+
the lift for tests. */}
|
|
8129
10268
|
<text
|
|
8130
10269
|
x="0" y={subY} textAnchor="middle"
|
|
8131
10270
|
fill={status.primary}
|
|
@@ -8134,9 +10273,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8134
10273
|
data-node-sub-text={session.alias}
|
|
8135
10274
|
data-node-sub-text-hovered={hoveredAlias === session.alias ? 'true' : 'false'}
|
|
8136
10275
|
data-node-sub-text-font-weight="500"
|
|
10276
|
+
data-node-sub-text-brightness={!reducedMotion && hoveredAlias === session.alias ? '1.15' : '1'}
|
|
8137
10277
|
style={{
|
|
8138
|
-
transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out',
|
|
10278
|
+
transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out, filter 200ms ease-out',
|
|
8139
10279
|
letterSpacing: hoveredAlias === session.alias ? '0.2px' : '0px',
|
|
10280
|
+
filter: !reducedMotion && hoveredAlias === session.alias
|
|
10281
|
+
? 'brightness(1.15)'
|
|
10282
|
+
: undefined,
|
|
8140
10283
|
}}
|
|
8141
10284
|
>
|
|
8142
10285
|
{status.label}{isOnline && sseCountFor != null ? ` sse:${sseCountFor}` : ''}
|
|
@@ -8613,7 +10756,55 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8613
10756
|
200ms ease-out' alongside R345's ls + R55's fill
|
|
8614
10757
|
200ms. data-recent-panel-title-fw exposes the
|
|
8615
10758
|
resolved weight for tests. */}
|
|
8616
|
-
|
|
10759
|
+
{/* Round 550 / Loop — drop-shadow visual-polish family
|
|
10760
|
+
extends to a 14th anchor: the recent-panel header
|
|
10761
|
+
title gains a soft pal.legendAccent glow when the
|
|
10762
|
+
panel has an active row (activeEdgeKey). Pre-R550
|
|
10763
|
+
the title's state-flip on active was 2-axis (R482
|
|
10764
|
+
fw 700→800 + R345 ls 0.3→0.4 when panel-hovered);
|
|
10765
|
+
R550 adds the paint axis so the title brightens
|
|
10766
|
+
paint-wise alongside the typographic tightening when
|
|
10767
|
+
a row inside its panel is locked.
|
|
10768
|
+
|
|
10769
|
+
Hue: pal.legendAccent + hex alpha '80' (~50%) — same
|
|
10770
|
+
strength as R479 group-label pin-glow at the parent
|
|
10771
|
+
panel-title scope. 2px blur reads soft; cyan accent
|
|
10772
|
+
ties the title visually to the active row's pin
|
|
10773
|
+
colour (cyber: cyan-300 / light: teal-600). Hex+alpha
|
|
10774
|
+
concat safe — pal.legendAccent is '#67e8f9' (cyber)
|
|
10775
|
+
or '#0d9488' (light), both 6-digit hex (banked R541:
|
|
10776
|
+
hex sources use hex+alpha; only hsl/color()/dynamic
|
|
10777
|
+
sources need color-mix).
|
|
10778
|
+
|
|
10779
|
+
Drop-shadow visual-polish family extension (14
|
|
10780
|
+
anchors now):
|
|
10781
|
+
R476 hub digit hover-gated emerald
|
|
10782
|
+
R477 legend pin-ring pin-gated row.fill
|
|
10783
|
+
R478 recent-row pip freshness cyan
|
|
10784
|
+
R479 group-label text pin-gated cyan
|
|
10785
|
+
R532-R536 hub-cluster glow QUINTET
|
|
10786
|
+
R537 legend swatch hover/pin row.fill
|
|
10787
|
+
R538 group-label hover-precedence
|
|
10788
|
+
R540 edge-badge text pin-gated cyan
|
|
10789
|
+
R543-R546 pin-active pill 4-variant arc
|
|
10790
|
+
R550 recent-panel title pin-gated cyan ← this round
|
|
10791
|
+
R550 legend-panel title pin-gated cyan ← sibling (next text below)
|
|
10792
|
+
|
|
10793
|
+
filter is paint-only; bbox unchanged; overlap-test
|
|
10794
|
+
invariants hold. transition list extends to include
|
|
10795
|
+
'filter 200ms ease-out' alongside R345 ls + R482 fw
|
|
10796
|
+
+ R55 fill 200ms — one motion-coherent 3-axis active-
|
|
10797
|
+
state lift.
|
|
10798
|
+
data-recent-panel-title-glow attr exposes the gate
|
|
10799
|
+
state for tests. */}
|
|
10800
|
+
{/* Round 573 / Loop — recent panel-title joins per-element
|
|
10801
|
+
brightness family at 11th anchor. Stacks brightness(1.15)
|
|
10802
|
+
onto R550's active-gated drop-shadow. Mirrors R572
|
|
10803
|
+
panel-row text pattern at the panel-TITLE tier — both
|
|
10804
|
+
panels now have brightness in their active signature
|
|
10805
|
+
at BOTH chrome tiers (title + row text), completing
|
|
10806
|
+
the panel paint-axis cascade. */}
|
|
10807
|
+
<text x="13" y="21" fill={pal.legendHeadline} fontSize="12" fontFamily="monospace" fontWeight={activeEdgeKey ? '800' : '700'} letterSpacing={hoveredPanel === 'recent' ? '0.4' : '0.3'} style={{ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out', filter: activeEdgeKey ? `drop-shadow(0 0 2px ${pal.legendAccent}80) brightness(1.15)` : undefined }} data-recent-panel-title data-recent-panel-title-fw={activeEdgeKey ? '800' : '700'} data-recent-panel-title-active={activeEdgeKey ? 'true' : 'false'} data-recent-panel-title-glow={activeEdgeKey ? 'true' : 'false'} data-recent-panel-title-brightness={activeEdgeKey ? '1.15' : '1'}>recent signal</text>
|
|
8617
10808
|
{/* R96: header count now matches what the rows show. Pre-R96
|
|
8618
10809
|
this read "X msgs" off the raw messages array, but the
|
|
8619
10810
|
rows below render DEDUPED flowLinks — so a fleet with 10
|
|
@@ -8688,21 +10879,59 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8688
10879
|
textAnchor="end"
|
|
8689
10880
|
fontSize="10"
|
|
8690
10881
|
fontFamily="monospace"
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
10882
|
+
/* Round 566 / Loop — recent-panel-count gains hover-
|
|
10883
|
+
state letter-spacing tween (0.2 → 0.4 on hovered-
|
|
10884
|
+
Panel === 'recent'). Pairs with R424 fw 600→700
|
|
10885
|
+
on the same gate. Count now has 2-axis hover
|
|
10886
|
+
signature (fw + ls), matching the panel title's
|
|
10887
|
+
R345 ls + R482 fw lift pattern at the panel-
|
|
10888
|
+
header data-tspan scope. R349 editorial 0.2
|
|
10889
|
+
baseline preserved at rest — only hover lifts.
|
|
10890
|
+
Sibling treatment on the legend-panel count
|
|
10891
|
+
below closes the panel-pair symmetry. */
|
|
10892
|
+
letterSpacing={hoveredPanel === 'recent' ? '0.4' : '0.2'}
|
|
10893
|
+
data-recent-panel-count-letter-spacing={hoveredPanel === 'recent' ? '0.4' : '0.2'}
|
|
10894
|
+
data-recent-panel-count-brightness={hoveredPanel === 'recent' ? '1.15' : '1'}
|
|
10895
|
+
style={{
|
|
10896
|
+
/* R589 — recent-signal panel count <text> root gains
|
|
10897
|
+
brightness(1.15) on hoveredPanel === 'recent'.
|
|
10898
|
+
28th anchor in per-element brightness family.
|
|
10899
|
+
Sibling to R588 at the legend panel; closes
|
|
10900
|
+
panel-pair brightness symmetry at the header-
|
|
10901
|
+
count scope.
|
|
10902
|
+
|
|
10903
|
+
Panel-pair title↔count brightness parity now
|
|
10904
|
+
complete:
|
|
10905
|
+
legend title pinnedStatus gate (R571 family)
|
|
10906
|
+
legend count hoveredPanel gate (R588)
|
|
10907
|
+
recent title activeEdgeKey gate (R571 family)
|
|
10908
|
+
recent count hoveredPanel gate (R589) ← this round
|
|
10909
|
+
|
|
10910
|
+
All 4 panel-header text elements respond on the
|
|
10911
|
+
brightness axis — full symmetric closure.
|
|
10912
|
+
|
|
10913
|
+
Recent-signal panel count hover signature now
|
|
10914
|
+
3 axes (mirrors R588 legend count closure):
|
|
10915
|
+
R311/R424 fontWeight 600 → 700
|
|
10916
|
+
R349/R566 letter-spacing 0.2 → 0.4
|
|
10917
|
+
R589 brightness 1 → 1.15 ← this round
|
|
10918
|
+
|
|
10919
|
+
Filter applied at <text> root (the parent here);
|
|
10920
|
+
the nested fw-changing tspan inherits the lift
|
|
10921
|
+
via SVG inheritance — unlike applying to tspan
|
|
10922
|
+
directly, which is unreliable cross-browser.
|
|
10923
|
+
|
|
10924
|
+
The R162 freshness-tinted fill (cyan/teal alpha
|
|
10925
|
+
1.0 → 0.30 by ageSec) gets the +15% multiplied
|
|
10926
|
+
in — fresh data (alpha=1) reads dramatically
|
|
10927
|
+
brighter on hover; stale data (alpha=0.30) gets
|
|
10928
|
+
a proportionally smaller absolute lift. Hover
|
|
10929
|
+
brightness is freshness-amplifying at this
|
|
10930
|
+
surface — coherent with the panel's "freshness
|
|
10931
|
+
is the primary signal" semantic. */
|
|
10932
|
+
filter: hoveredPanel === 'recent' ? 'brightness(1.15)' : undefined,
|
|
10933
|
+
transition: 'letter-spacing 200ms ease-out, filter 200ms ease-out',
|
|
10934
|
+
}}
|
|
8706
10935
|
>
|
|
8707
10936
|
{/* Round 225 / Loop: tabular-nums on the panel-header
|
|
8708
10937
|
flow-count tspan. The "{N} flows" string lives in
|
|
@@ -9303,11 +11532,38 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9303
11532
|
data-recent-row-freshness-radius={(isRowHovered || isRowPinned) ? 2.5 : 2.0}
|
|
9304
11533
|
data-recent-row-freshness-lifted={(isRowHovered || isRowPinned) ? 'true' : 'false'}
|
|
9305
11534
|
data-recent-row-freshness-glow={alpha > 0.7 ? 'true' : 'false'}
|
|
11535
|
+
data-recent-row-freshness-brightness={alpha > 0.7 ? '1.15' : '1'}
|
|
9306
11536
|
style={{
|
|
9307
11537
|
pointerEvents: 'none',
|
|
9308
11538
|
r: `${(isRowHovered || isRowPinned) ? 2.5 : 2.0}px`,
|
|
11539
|
+
/* R606 — recent-row freshness pip stacks
|
|
11540
|
+
brightness(1.15) onto R478's freshness-gated
|
|
11541
|
+
drop-shadow. Same alpha > 0.7 gate so both
|
|
11542
|
+
effects activate together when the signal
|
|
11543
|
+
is "live" (~30s window per R10 freshness
|
|
11544
|
+
ramp) and ease off in lockstep as data
|
|
11545
|
+
ages past ~45s.
|
|
11546
|
+
|
|
11547
|
+
Pip 4-axis freshness/hover signature now:
|
|
11548
|
+
R10 opacity tracks alpha (freshness)
|
|
11549
|
+
R447 r 2.0 → 2.5 (hover/pin)
|
|
11550
|
+
R478 drop-shadow on alpha > 0.7
|
|
11551
|
+
R606 brightness(1.15) on alpha > 0.7 ← this round
|
|
11552
|
+
|
|
11553
|
+
R478 + R606 share one filter chain on the
|
|
11554
|
+
same gate — banked R582/R583 stacked-filter
|
|
11555
|
+
pattern at the recent-row pip scope. The
|
|
11556
|
+
fresh signal reads ~15% brighter + glows
|
|
11557
|
+
cyan; the stale signal sits at flat
|
|
11558
|
+
legendAccent fill with no filter.
|
|
11559
|
+
|
|
11560
|
+
Hover brightness is freshness-gated here
|
|
11561
|
+
(vs hover-gated elsewhere) — matches the
|
|
11562
|
+
"this signal is live" semantic that R478
|
|
11563
|
+
established. data-recent-row-freshness-
|
|
11564
|
+
brightness attr exposes the gate for tests. */
|
|
9309
11565
|
filter: alpha > 0.7
|
|
9310
|
-
? `drop-shadow(0 0 3px ${pal.legendAccent}80)`
|
|
11566
|
+
? `drop-shadow(0 0 3px ${pal.legendAccent}80) brightness(1.15)`
|
|
9311
11567
|
: undefined,
|
|
9312
11568
|
transition: 'opacity 200ms ease-out, r 200ms ease-out, filter 200ms ease-out',
|
|
9313
11569
|
} as React.CSSProperties}
|
|
@@ -9357,11 +11613,46 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9357
11613
|
tier without disturbing the surrounding family
|
|
9358
11614
|
baseline. data-recent-row-text-font-weight attr
|
|
9359
11615
|
exposes the value for tests. */
|
|
9360
|
-
|
|
11616
|
+
/* Round 530 / Loop — extends hover-fw family
|
|
11617
|
+
(R416/R420/R425/R520/R521/R522, 6 anchors) to
|
|
11618
|
+
a 7th anchor: recent-row alias text gains
|
|
11619
|
+
fontWeight 500 → 600 on (isRowHovered ||
|
|
11620
|
+
isRowPinned). Pre-R530 R363 set fw=500
|
|
11621
|
+
statically; hover/pin lifted other axes
|
|
11622
|
+
(R55 fill brighten / R434 letter-spacing
|
|
11623
|
+
3-tier / R143 translateY / R104 row bg-
|
|
11624
|
+
tint / R474 cadence) but the fw stayed
|
|
11625
|
+
flat — same asymmetry R520 closed at the
|
|
11626
|
+
+N more footer.
|
|
11627
|
+
R530 mirrors R520's pattern at the row-
|
|
11628
|
+
text scope. Hover OR pin (isRowActive
|
|
11629
|
+
union) lifts fw to 600, matching the count
|
|
11630
|
+
tspan's cold-state tier (R320 fw=600), so
|
|
11631
|
+
on active state the alias label reads at
|
|
11632
|
+
the same data tier as the count it sits
|
|
11633
|
+
next to. Inner count tspan has its own
|
|
11634
|
+
explicit fontWeight (600 or 700 per R320/
|
|
11635
|
+
R445) so parent fw lift doesn't bleed
|
|
11636
|
+
(inheritance overridden).
|
|
11637
|
+
Hover-fw family extension (7 anchors):
|
|
11638
|
+
R416 chip-row count digit
|
|
11639
|
+
R420 chrome zoom-level
|
|
11640
|
+
R425 hub-center digit
|
|
11641
|
+
R520 +N more flows footer
|
|
11642
|
+
R521 chrome nodeSize S/M/L inactive
|
|
11643
|
+
R522 chrome layout Ring/Grid inactive
|
|
11644
|
+
R530 recent-row alias text ← this round
|
|
11645
|
+
transition list extends to include
|
|
11646
|
+
'font-weight 200ms ease-out', matching the
|
|
11647
|
+
R474 cadence of the existing fill +
|
|
11648
|
+
letter-spacing axes on this element.
|
|
11649
|
+
data-recent-row-text-font-weight attr
|
|
11650
|
+
flips '500' → '600' on isRowActive. */
|
|
11651
|
+
fontWeight={(isRowHovered || isRowPinned) ? '600' : '500'}
|
|
9361
11652
|
data-recent-row-text={link.key}
|
|
9362
11653
|
data-recent-row-text-pinned={isRowPinned ? 'true' : 'false'}
|
|
9363
11654
|
data-recent-row-text-hovered={!isRowPinned && isRowHovered ? 'true' : 'false'}
|
|
9364
|
-
data-recent-row-text-font-weight=
|
|
11655
|
+
data-recent-row-text-font-weight={(isRowHovered || isRowPinned) ? '600' : '500'}
|
|
9365
11656
|
/* Round 434 / Loop: recent-signal row text extends
|
|
9366
11657
|
from R220's pin-only letter-spacing (0 → 0.5 on
|
|
9367
11658
|
isRowPinned) to a 3-tier scale matching R433
|
|
@@ -9410,11 +11701,49 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9410
11701
|
spacing values unchanged; R363 fw + R55 fill
|
|
9411
11702
|
brighten unchanged — only the timing axis
|
|
9412
11703
|
shifts. */
|
|
11704
|
+
/* Round 568 / Loop — extends the drop-shadow visual-
|
|
11705
|
+
polish family (16 anchors after R559) to a 17th
|
|
11706
|
+
anchor at the recent-row text scope. Adds a soft
|
|
11707
|
+
pal.legendAccent glow on isRowHovered || isRow-
|
|
11708
|
+
Pinned, completing the row's hover/pin signature
|
|
11709
|
+
at 4 paint+typography axes:
|
|
11710
|
+
R55 fill brighten (fill)
|
|
11711
|
+
R434 letter-spacing 3-tier (typography kerning)
|
|
11712
|
+
R530 fontWeight 500 → 600 (typography weight)
|
|
11713
|
+
R568 drop-shadow glow (paint glow) ← this round
|
|
11714
|
+
Mirror of R550 panel-title pin-gated glow pattern,
|
|
11715
|
+
applied at the panel-ROW tier rather than panel-
|
|
11716
|
+
TITLE tier. Hue: pal.legendAccent + hex alpha 80
|
|
11717
|
+
(~50%) — same strength as R479 group-label /
|
|
11718
|
+
R550 panel-title glows for cross-element
|
|
11719
|
+
consistency. 2px blur (smaller than R478 pip's
|
|
11720
|
+
3px since text is fontSize 9 and a heavier blur
|
|
11721
|
+
would bleed into adjacent row text); blur 2px
|
|
11722
|
+
keeps the glow tight to the row's text glyphs.
|
|
11723
|
+
transition list extends to include 'filter
|
|
11724
|
+
200ms ease-out' matching the R474 200ms cadence
|
|
11725
|
+
of the existing 3 axes.
|
|
11726
|
+
data-recent-row-text-glow attr surfaces the
|
|
11727
|
+
gate for tests. */
|
|
9413
11728
|
data-recent-row-text-transition="200ms"
|
|
11729
|
+
data-recent-row-text-glow={(isRowHovered || isRowPinned) ? 'true' : 'false'}
|
|
11730
|
+
/* Round 572 / Loop — per-element brightness family
|
|
11731
|
+
9th anchor at recent-row text scope. Stacks
|
|
11732
|
+
brightness(1.15) onto R568's drop-shadow in
|
|
11733
|
+
one filter chain (same R564/R570/R571 pattern).
|
|
11734
|
+
Glyph BOTH glows (R568 drop-shadow halo) AND
|
|
11735
|
+
brightens (R572 inner lift) simultaneously.
|
|
11736
|
+
Cross-element brightness consistency: same +15%
|
|
11737
|
+
across alias / sub-text / edge-badge / group-
|
|
11738
|
+
label / and now recent-row text. */
|
|
11739
|
+
data-recent-row-text-brightness={(isRowHovered || isRowPinned) ? '1.15' : '1'}
|
|
9414
11740
|
style={{
|
|
9415
|
-
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out',
|
|
11741
|
+
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out',
|
|
9416
11742
|
letterSpacing: isRowPinned ? '0.5px' :
|
|
9417
11743
|
isRowHovered ? '0.25px' : '0px',
|
|
11744
|
+
filter: (isRowHovered || isRowPinned)
|
|
11745
|
+
? `drop-shadow(0 0 2px ${pal.legendAccent}80) brightness(1.15)`
|
|
11746
|
+
: undefined,
|
|
9418
11747
|
}}
|
|
9419
11748
|
>
|
|
9420
11749
|
{/* R138 / Loop: typography unification with the rest
|
|
@@ -9586,9 +11915,51 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9586
11915
|
data-recent-row-ts={link.key}
|
|
9587
11916
|
data-recent-row-ts-alpha={tsAlpha.toFixed(2)}
|
|
9588
11917
|
data-recent-row-ts-lifted={(isRowHovered || isRowPinned) ? 'true' : 'false'}
|
|
11918
|
+
data-recent-row-ts-brightness={(isRowHovered || isRowPinned) ? '1.15' : '1'}
|
|
9589
11919
|
style={{
|
|
9590
11920
|
pointerEvents: 'none',
|
|
9591
|
-
|
|
11921
|
+
/* R591 — recent-row timestamp gains filter
|
|
11922
|
+
brightness(1.15) on row hover/pin. 30th
|
|
11923
|
+
anchor in per-element brightness family.
|
|
11924
|
+
Closes recent-row 3-element brightness
|
|
11925
|
+
coverage:
|
|
11926
|
+
recent-row text (R572, parent <text>
|
|
11927
|
+
filter — count tspan
|
|
11928
|
+
inherits)
|
|
11929
|
+
recent-row ts (R591, sibling <text>
|
|
11930
|
+
— its own filter) ← this round
|
|
11931
|
+
|
|
11932
|
+
Symmetric with the legend-row R590 closure
|
|
11933
|
+
pattern: when a sibling text element can't
|
|
11934
|
+
inherit the row's brightness via ancestor
|
|
11935
|
+
filter, it needs its own filter. R590
|
|
11936
|
+
solved this for legend-count; R591 does
|
|
11937
|
+
the same for recent-ts.
|
|
11938
|
+
|
|
11939
|
+
Triple multiplicative interaction at this
|
|
11940
|
+
surface:
|
|
11941
|
+
opacity: tsAlpha (R191 freshness 1.0 →
|
|
11942
|
+
0.25 by ageSec) → 1.0 on hover/pin
|
|
11943
|
+
(R484)
|
|
11944
|
+
fill: pal.legendText (neutral gray)
|
|
11945
|
+
brightness: 1.0 → 1.15 on hover/pin
|
|
11946
|
+
|
|
11947
|
+
Stale-data hover semantic: a stale row's
|
|
11948
|
+
timestamp at tsAlpha=0.25 gets opacity 0.25
|
|
11949
|
+
× brightness 1.0 at rest, but jumps to
|
|
11950
|
+
opacity 1.0 × brightness 1.15 on hover —
|
|
11951
|
+
revealing the dim metadata at maximum
|
|
11952
|
+
legibility under attention. Hover brightness
|
|
11953
|
+
is freshness-overriding at this surface,
|
|
11954
|
+
same as R589's freshness-amplifying behavior
|
|
11955
|
+
at the recent-panel count above.
|
|
11956
|
+
|
|
11957
|
+
data-recent-row-ts-brightness attr exposes
|
|
11958
|
+
the gate for tests. */
|
|
11959
|
+
filter: (isRowHovered || isRowPinned)
|
|
11960
|
+
? 'brightness(1.15)'
|
|
11961
|
+
: undefined,
|
|
11962
|
+
transition: 'opacity 200ms ease-out, filter 200ms ease-out',
|
|
9592
11963
|
fontVariantNumeric: 'tabular-nums',
|
|
9593
11964
|
}}
|
|
9594
11965
|
>
|
|
@@ -9762,6 +12133,39 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9762
12133
|
stays as is, so the rest-vs-hover delta still
|
|
9763
12134
|
reads clearly. data-recent-panel-more-font-weight
|
|
9764
12135
|
attr exposes the value for tests. */}
|
|
12136
|
+
{/* Round 520 / Loop — extends the `+N more flows` footer
|
|
12137
|
+
to a 5-axis hover signature by adding fontWeight
|
|
12138
|
+
500 → 600 on hover. Pre-R520 the footer carried 4
|
|
12139
|
+
hover axes:
|
|
12140
|
+
R195 fill legendText → legendAccent
|
|
12141
|
+
R325 letter-spacing 0.2 → 0.3px (R344 tween)
|
|
12142
|
+
R325 opacity 0.55 → 0.85
|
|
12143
|
+
R133 underline none → underline
|
|
12144
|
+
R368 had set fontWeight 500 statically as a sibling
|
|
12145
|
+
to R363/R364/R366 small-text fw lift family — but
|
|
12146
|
+
the footer's hover state didn't carry a fontWeight
|
|
12147
|
+
DELTA the way other interactive surfaces do (chip-
|
|
12148
|
+
row counts R416, chrome zoom-level R420, hub digit
|
|
12149
|
+
R425). R520 adds the missing weight axis: fw 500
|
|
12150
|
+
→ 600 on hover, so the footer reads "thickening AND
|
|
12151
|
+
lighting up" under cursor — same idiom as the
|
|
12152
|
+
chrome zoom-level R420 / chip-row digit R416 hover-
|
|
12153
|
+
bold pattern, applied at the panel nav-action
|
|
12154
|
+
surface.
|
|
12155
|
+
data-recent-panel-more-font-weight attr value
|
|
12156
|
+
flips from '500' → '600' on hover (was static
|
|
12157
|
+
'500' pre-R520).
|
|
12158
|
+
Bonus closure — R475 panel-text cadence: pre-R520
|
|
12159
|
+
the footer's transition list had `opacity 150ms`
|
|
12160
|
+
while R475 unified panel-text transitions at
|
|
12161
|
+
200ms. R518 closed the same gap at legend-count.
|
|
12162
|
+
R520 closes the LAST panel-text 150ms holdout
|
|
12163
|
+
here AND adds the new font-weight 200ms axis. All
|
|
12164
|
+
4 transition properties (opacity / fill / letter-
|
|
12165
|
+
spacing / font-weight) now uniform 200ms at the
|
|
12166
|
+
footer — same cadence as legend-label / legend-
|
|
12167
|
+
count / recent-row alias / recent-row count /
|
|
12168
|
+
group-label. */}
|
|
9765
12169
|
<text
|
|
9766
12170
|
x="115" y="82"
|
|
9767
12171
|
textAnchor="middle"
|
|
@@ -9769,14 +12173,49 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9769
12173
|
fontSize="9"
|
|
9770
12174
|
fontFamily="monospace"
|
|
9771
12175
|
fontStyle="italic"
|
|
9772
|
-
fontWeight=
|
|
12176
|
+
fontWeight={hoveredRecentMore ? '600' : '500'}
|
|
9773
12177
|
letterSpacing={hoveredRecentMore ? '0.3' : '0.2'}
|
|
9774
12178
|
opacity={hoveredRecentMore ? 0.85 : 0.55}
|
|
9775
12179
|
textDecoration={hoveredRecentMore ? 'underline' : 'none'}
|
|
9776
12180
|
data-recent-panel-more={moreCount}
|
|
9777
12181
|
data-recent-panel-more-hovered={hoveredRecentMore ? 'true' : 'false'}
|
|
9778
|
-
data-recent-panel-more-font-weight=
|
|
9779
|
-
|
|
12182
|
+
data-recent-panel-more-font-weight={hoveredRecentMore ? '600' : '500'}
|
|
12183
|
+
data-recent-panel-more-transition="200ms"
|
|
12184
|
+
data-recent-panel-more-brightness={hoveredRecentMore ? '1.15' : '1'}
|
|
12185
|
+
style={{
|
|
12186
|
+
/* R592 — +N more flows footer gains filter
|
|
12187
|
+
brightness(1.15) on hover. 31st anchor in
|
|
12188
|
+
per-element brightness family. Closes the
|
|
12189
|
+
footer's hover signature at 6 axes — the
|
|
12190
|
+
densest hover signature on any topology
|
|
12191
|
+
surface:
|
|
12192
|
+
R195 fill legendText → legendAccent
|
|
12193
|
+
R325 letter-spacing 0.2 → 0.3 (R344 tween)
|
|
12194
|
+
R325 opacity 0.55 → 0.85
|
|
12195
|
+
R133 underline none → underline
|
|
12196
|
+
R520 fontWeight 500 → 600
|
|
12197
|
+
R592 brightness 1 → 1.15 ← this round
|
|
12198
|
+
|
|
12199
|
+
The footer is the recent-signal panel's
|
|
12200
|
+
primary nav affordance into /messages — when
|
|
12201
|
+
user hovers it, EVERYTHING about it shifts:
|
|
12202
|
+
color (cyan), size (fw), spacing (ls), opacity
|
|
12203
|
+
(0.85), decoration (underline), brightness
|
|
12204
|
+
(+15%). 6-axis hover signature reads as "this
|
|
12205
|
+
is the most actionable thing on the panel —
|
|
12206
|
+
click me".
|
|
12207
|
+
|
|
12208
|
+
Triple-paint multiplicative interaction:
|
|
12209
|
+
opacity 0.85 × cyan fill × brightness(1.15)
|
|
12210
|
+
— the cyan reads dramatically brighter than
|
|
12211
|
+
a plain fill swap would.
|
|
12212
|
+
|
|
12213
|
+
Existing transition list extends with 'filter
|
|
12214
|
+
200ms ease-out' matching the existing 200ms
|
|
12215
|
+
cadence across all 5 other axes. */
|
|
12216
|
+
filter: hoveredRecentMore ? 'brightness(1.15)' : undefined,
|
|
12217
|
+
transition: 'opacity 200ms ease-out, fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out',
|
|
12218
|
+
}}
|
|
9780
12219
|
>
|
|
9781
12220
|
{`+ ${moreCount}`}
|
|
9782
12221
|
<tspan opacity="0.7" data-recent-panel-more-unit>{` more flow${moreCount === 1 ? '' : 's'}`}</tspan>
|
|
@@ -9883,7 +12322,20 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9883
12322
|
data-legend-panel-title-fw + -active exposed for tests. */}
|
|
9884
12323
|
{/* R345 sibling — legend panel title same hover letter-
|
|
9885
12324
|
spacing tween 0.3 → 0.4 on panel hover. */}
|
|
9886
|
-
|
|
12325
|
+
{/* Round 550 sibling — legend-panel header title mirrors
|
|
12326
|
+
the recent-panel title above: drop-shadow glow on
|
|
12327
|
+
pin-gated active state (pinnedStatus). Same hue
|
|
12328
|
+
(pal.legendAccent + hex alpha 80), same 2px blur,
|
|
12329
|
+
same 200ms ease-out cadence. Family lifts to 15
|
|
12330
|
+
anchors with this sibling (counted as R550-sibling
|
|
12331
|
+
for accounting parity with R532-R536 hub-cluster
|
|
12332
|
+
glow quintet pattern — two co-shipping anchors
|
|
12333
|
+
under a single round number).
|
|
12334
|
+
data-legend-panel-title-glow attr added. */}
|
|
12335
|
+
{/* Round 573 sibling — legend panel-title 12th anchor.
|
|
12336
|
+
Same stacked filter pattern at the legend-panel-title
|
|
12337
|
+
scope. Both panel titles now lift in lockstep. */}
|
|
12338
|
+
<text x="13" y="21" fill={pal.legendHeadline} fontSize="12" fontFamily="monospace" fontWeight={pinnedStatus ? '800' : '700'} letterSpacing={hoveredPanel === 'legend' ? '0.4' : '0.3'} style={{ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out', filter: pinnedStatus ? `drop-shadow(0 0 2px ${pal.legendAccent}80) brightness(1.15)` : undefined }} data-legend-panel-title data-legend-panel-title-fw={pinnedStatus ? '800' : '700'} data-legend-panel-title-active={pinnedStatus ? 'true' : 'false'} data-legend-panel-title-glow={pinnedStatus ? 'true' : 'false'} data-legend-panel-title-brightness={pinnedStatus ? '1.15' : '1'}>legend</text>
|
|
9887
12339
|
{/* Round 257 / Loop: legend panel header count picks up the
|
|
9888
12340
|
symmetric 13L/13R inner-padding pattern from the recent-
|
|
9889
12341
|
signal panel. Pre-R257 the legend header was 13px from
|
|
@@ -9960,11 +12412,42 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9960
12412
|
// title 0.3. Pairs with the recent-signal panel count
|
|
9961
12413
|
// letter-spacing above so the two corner panels' header
|
|
9962
12414
|
// typography stays editorially symmetric.
|
|
9963
|
-
|
|
12415
|
+
/* Round 566 / Loop — legend panel-count gains hover-state
|
|
12416
|
+
letter-spacing tween (0.2 → 0.4 on hoveredPanel ===
|
|
12417
|
+
'legend'). Pairs with existing R310 fw 600→700 on the
|
|
12418
|
+
same gate — count now has 2-axis hover signature (fw
|
|
12419
|
+
+ ls), matching the panel title's R345 ls + R482 fw
|
|
12420
|
+
lift pattern at the panel-header data-tspan scope.
|
|
12421
|
+
Hover-letter-spacing family extension (R566 = 2 sibling
|
|
12422
|
+
anchors at recent + legend panel-count). */
|
|
12423
|
+
letterSpacing={hoveredPanel === 'legend' ? '0.4' : '0.2'}
|
|
9964
12424
|
data-legend-panel-count
|
|
9965
|
-
data-legend-panel-count-letter-spacing=
|
|
12425
|
+
data-legend-panel-count-letter-spacing={hoveredPanel === 'legend' ? '0.4' : '0.2'}
|
|
12426
|
+
data-legend-panel-count-brightness={hoveredPanel === 'legend' ? '1.15' : '1'}
|
|
9966
12427
|
style={{
|
|
9967
|
-
|
|
12428
|
+
/* R588 — legend panel count gains brightness(1.15) on
|
|
12429
|
+
panel hover. 27th anchor in per-element brightness
|
|
12430
|
+
family. Closes title↔count parity at the legend
|
|
12431
|
+
panel header — the title already has brightness
|
|
12432
|
+
(R571/R567 lineage on pinnedStatus gate at line
|
|
12433
|
+
~11926); the count now matches at the broader
|
|
12434
|
+
hoveredPanel gate.
|
|
12435
|
+
|
|
12436
|
+
Legend panel count hover signature now 3 axes:
|
|
12437
|
+
R310/R424 fontWeight 600 → 700
|
|
12438
|
+
R349/R566 letter-spacing 0.2 → 0.4
|
|
12439
|
+
R588 brightness 1 → 1.15 ← this round
|
|
12440
|
+
|
|
12441
|
+
The cyan numeral (pal.legendAccent) lifts ~15%
|
|
12442
|
+
alongside the typographic tightening — 3-axis
|
|
12443
|
+
panel-count hover signal at full visual presence.
|
|
12444
|
+
|
|
12445
|
+
Pure paint filter on <text> root element (NOT a
|
|
12446
|
+
nested tspan — SVG filter on tspan is unreliable
|
|
12447
|
+
cross-browser; the parent <text> takes the filter
|
|
12448
|
+
and inherits to children). */
|
|
12449
|
+
filter: hoveredPanel === 'legend' ? 'brightness(1.15)' : undefined,
|
|
12450
|
+
transition: 'fill 200ms ease-out, font-weight 200ms ease-out, letter-spacing 200ms ease-out, filter 200ms ease-out',
|
|
9968
12451
|
fontVariantNumeric: 'tabular-nums',
|
|
9969
12452
|
}}
|
|
9970
12453
|
>{sessions.length}<tspan opacity="0.7" data-legend-panel-count-unit> node{sessions.length === 1 ? '' : 's'}</tspan></text>
|
|
@@ -10041,6 +12524,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10041
12524
|
const isPinned = pinnedStatus === row.key;
|
|
10042
12525
|
const isRowHovered = hoveredStatus === row.key;
|
|
10043
12526
|
const isLifted = isRowHovered || isPinned;
|
|
12527
|
+
/* Round 562 / Loop — inspection-overrides-encoding family
|
|
12528
|
+
5th anchor at the legend-swatch scope. When operator
|
|
12529
|
+
hovers a NODE ALIAS on the canvas, the legend swatch
|
|
12530
|
+
that matches that node's status tier lifts r + drop-
|
|
12531
|
+
shadow (R197 + R537 axes) — telegraphing "your
|
|
12532
|
+
inspected node is in this status group".
|
|
12533
|
+
Mirror of R486 minimap-dot inspection-override at the
|
|
12534
|
+
legend-swatch scope.
|
|
12535
|
+
Family progression (5 anchors):
|
|
12536
|
+
R484 recent-row timestamp on alias hover
|
|
12537
|
+
R485 edge particle opacity on alias hover
|
|
12538
|
+
R486 minimap dot opacity to 1.0 on alias hover
|
|
12539
|
+
R561 group-label opacity-1 + ants-gate refinement
|
|
12540
|
+
R562 legend-swatch r+glow on member-alias-matching ← this round
|
|
12541
|
+
Restraint: ONLY swatch lifts, NOT label/fill/ls/fw
|
|
12542
|
+
axes. Direct row-hover gets full treatment; inspection
|
|
12543
|
+
signal gets swatch-only lift — distinct "lighter"
|
|
12544
|
+
visual register matching R561's ants-gate approach
|
|
12545
|
+
(indirect inspection ≠ direct attention). */
|
|
12546
|
+
const hoveredSession = hoveredAlias
|
|
12547
|
+
? (onlineNodes.find(s => s.alias === hoveredAlias) ?? offlineNodes.find(s => s.alias === hoveredAlias))
|
|
12548
|
+
: null;
|
|
12549
|
+
const hoveredAliasRowKey: 'working' | 'idle' | 'offline' | null = !hoveredSession ? null
|
|
12550
|
+
: hoveredSession.status === 'working' ? 'working'
|
|
12551
|
+
: offlineNodes.includes(hoveredSession) ? 'offline'
|
|
12552
|
+
: 'idle';
|
|
12553
|
+
const isMemberAliasMatching = hoveredAliasRowKey === row.key;
|
|
12554
|
+
const isSwatchLifted = isLifted || isMemberAliasMatching;
|
|
10044
12555
|
return (
|
|
10045
12556
|
<g
|
|
10046
12557
|
key={row.key}
|
|
@@ -10172,15 +12683,50 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10172
12683
|
R181 pin ring (6 + 0 stroke vs 8 - 0.75 inner
|
|
10173
12684
|
≈ 7.25). data-legend-swatch is unchanged so
|
|
10174
12685
|
R197 / R55 / R61 tests probe the same handle. */}
|
|
12686
|
+
{/* Round 537 / Loop — extends drop-shadow visual-polish
|
|
12687
|
+
family (12 anchors after R536) to a 13th anchor: the
|
|
12688
|
+
legend swatch gains drop-shadow glow on hover/pin
|
|
12689
|
+
using its OWN row fill color (working green / idle
|
|
12690
|
+
teal / offline slate). Pre-R537 the swatch lifted
|
|
12691
|
+
only r (R197/R295 6 → 7) on attention — geometry
|
|
12692
|
+
axis only, no paint glow. R537 adds the paint axis,
|
|
12693
|
+
composing with R181/R402 pin-ring (separate concen-
|
|
12694
|
+
tric circle in the same row.fill color) so on
|
|
12695
|
+
hover/pin the SWATCH AND its pin-ring both contri-
|
|
12696
|
+
bute to a unified tier-coloured glow signature.
|
|
12697
|
+
Hue: row.fill (status hex) concatenated with `99`
|
|
12698
|
+
hex alpha (~60%). Working green / idle teal /
|
|
12699
|
+
offline slate each glow in their OWN tier color
|
|
12700
|
+
— the legend acts as a color-keyed status mirror.
|
|
12701
|
+
3px blur reads soft; 60% alpha legible without
|
|
12702
|
+
overwhelming the swatch's own paint.
|
|
12703
|
+
Drop-shadow visual-polish family extension (13
|
|
12704
|
+
anchors). filter is paint-only; bbox unchanged.
|
|
12705
|
+
transition list extends to include 'filter 150ms
|
|
12706
|
+
ease-out', matching the existing R197 r 150ms
|
|
12707
|
+
cadence at this swatch. data-legend-swatch-glow
|
|
12708
|
+
attr exposes the gate state for tests. */}
|
|
10175
12709
|
<circle
|
|
10176
12710
|
cx="16" cy={row.y0}
|
|
10177
12711
|
r="6"
|
|
10178
12712
|
fill={row.fill}
|
|
10179
12713
|
data-legend-swatch={row.key}
|
|
10180
|
-
data-legend-swatch-state={isPinned ? 'pinned' : isRowHovered ? 'hover' : 'idle'}
|
|
12714
|
+
data-legend-swatch-state={isPinned ? 'pinned' : isRowHovered ? 'hover' : isMemberAliasMatching ? 'member-alias-matching' : 'idle'}
|
|
12715
|
+
data-legend-swatch-glow={isSwatchLifted ? 'true' : 'false'}
|
|
12716
|
+
data-legend-swatch-member-alias-matching={isMemberAliasMatching ? 'true' : 'false'}
|
|
12717
|
+
/* Round 578 — legend swatch joins per-element brightness
|
|
12718
|
+
family at 16th anchor. Stacks brightness(1.15) onto
|
|
12719
|
+
R537 drop-shadow. Closes chip-row tier-color glow
|
|
12720
|
+
trio at consistent stacked-filter pattern alongside
|
|
12721
|
+
R542 pressure-seg (already stacks brightness 1.2)
|
|
12722
|
+
and the sibling vendor chip (R578-sibling). */
|
|
12723
|
+
data-legend-swatch-brightness={isSwatchLifted ? '1.15' : '1'}
|
|
10181
12724
|
style={{
|
|
10182
|
-
r:
|
|
10183
|
-
|
|
12725
|
+
r: isSwatchLifted ? '7px' : '6px',
|
|
12726
|
+
filter: isSwatchLifted
|
|
12727
|
+
? `drop-shadow(0 0 3px ${row.fill}99) brightness(1.15)`
|
|
12728
|
+
: undefined,
|
|
12729
|
+
transition: 'r 150ms ease-out, filter 150ms ease-out',
|
|
10184
12730
|
} as React.CSSProperties}
|
|
10185
12731
|
/>
|
|
10186
12732
|
{/* R61 pinned-state ring — concentric stroke at r=8 in
|
|
@@ -10262,10 +12808,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10262
12808
|
data-legend-pin-ring-pinned={isPinned ? 'true' : 'false'}
|
|
10263
12809
|
data-legend-pin-ring-stroke-width="1.75"
|
|
10264
12810
|
data-legend-pin-ring-glow={isPinned ? 'true' : 'false'}
|
|
12811
|
+
data-legend-pin-ring-brightness={isPinned ? '1.15' : '1'}
|
|
10265
12812
|
style={{
|
|
10266
12813
|
pointerEvents: 'none',
|
|
12814
|
+
/* R607 — legend pin-ring stacks brightness(1.15)
|
|
12815
|
+
onto R477's pin-gated drop-shadow. Extends the
|
|
12816
|
+
pin-gated brightness family to a 3rd anchor:
|
|
12817
|
+
R571 group label text (pin-gated)
|
|
12818
|
+
R587 group cluster box (pin-gated)
|
|
12819
|
+
R607 legend pin-ring (pin-gated) ← this round
|
|
12820
|
+
Same banked R582/R583 stacked-filter pattern.
|
|
12821
|
+
Pin-ring 3-axis pin signature now:
|
|
12822
|
+
R181 opacity 0 → 1
|
|
12823
|
+
R477 drop-shadow → row.fill 88 alpha
|
|
12824
|
+
R607 brightness 1 → 1.15 ← this round
|
|
12825
|
+
The row.fill tier color (green working / teal
|
|
12826
|
+
idle / slate offline) brightens ~15% alongside
|
|
12827
|
+
the drop-shadow halo — the pin-ring reads as
|
|
12828
|
+
"lit + locked" instead of just "locked". The
|
|
12829
|
+
color signal (which tier you pinned) reads more
|
|
12830
|
+
vividly under the brightness lift.
|
|
12831
|
+
Existing 'filter 200ms ease-out' transition
|
|
12832
|
+
covers brightness ease at the same cadence. */
|
|
10267
12833
|
filter: isPinned
|
|
10268
|
-
? `drop-shadow(0 0 3px ${row.fill}88)`
|
|
12834
|
+
? `drop-shadow(0 0 3px ${row.fill}88) brightness(1.15)`
|
|
10269
12835
|
: undefined,
|
|
10270
12836
|
transition: 'opacity 150ms ease-out, filter 200ms ease-out',
|
|
10271
12837
|
}}
|
|
@@ -10312,11 +12878,42 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10312
12878
|
the value for tests. R219 letter-spacing pin
|
|
10313
12879
|
tween + R55 fill transition + R181 always-mount
|
|
10314
12880
|
pin ring all preserved. */
|
|
10315
|
-
|
|
12881
|
+
/* Round 531 / Loop — extends hover-fw family (R416/
|
|
12882
|
+
R420/R425/R520/R521/R522/R530, 7 anchors) to an
|
|
12883
|
+
8th anchor at the legend-row label. Pre-R531
|
|
12884
|
+
R364 set fw=500 statically; hover/pin lifted
|
|
12885
|
+
other axes (R55 fill brighten / R433 letter-
|
|
12886
|
+
spacing 3-tier / R181 pin ring) but the fw
|
|
12887
|
+
stayed flat. R531 mirrors R530's recent-row
|
|
12888
|
+
alias pattern at the legend-row label scope.
|
|
12889
|
+
Hover OR pin (hoveredStatus===row.key ||
|
|
12890
|
+
isPinned) lifts fw to 600, matching the
|
|
12891
|
+
legend-row count tier (R309 fw=600 / R446
|
|
12892
|
+
pin lift 600→700). Active label now reads at
|
|
12893
|
+
the count's data tier — sibling treatment to
|
|
12894
|
+
R530 recent-row.
|
|
12895
|
+
Hover-fw family extension (8 anchors):
|
|
12896
|
+
R416 chip-row count digit
|
|
12897
|
+
R420 chrome zoom-level
|
|
12898
|
+
R425 hub-center digit
|
|
12899
|
+
R520 +N more flows footer
|
|
12900
|
+
R521 chrome nodeSize S/M/L inactive
|
|
12901
|
+
R522 chrome layout Ring/Grid inactive
|
|
12902
|
+
R530 recent-row alias text
|
|
12903
|
+
R531 legend-row label ← this round
|
|
12904
|
+
Two panel-row label surfaces (R530 recent-
|
|
12905
|
+
row alias + R531 legend-row label) now have
|
|
12906
|
+
parallel hover-fw signatures. R475 cadence
|
|
12907
|
+
at 200ms already covers font-weight via the
|
|
12908
|
+
existing transition list extension at this
|
|
12909
|
+
element. data-legend-row-label-font-weight
|
|
12910
|
+
attr flips '500' → '600' on isActive (was
|
|
12911
|
+
static '500' pre-R531). */
|
|
12912
|
+
fontWeight={(hoveredStatus === row.key || isPinned) ? '600' : '500'}
|
|
10316
12913
|
data-legend-row-label={row.key}
|
|
10317
12914
|
data-legend-row-label-pinned={isPinned ? 'true' : 'false'}
|
|
10318
12915
|
data-legend-row-label-hovered={!isPinned && hoveredStatus === row.key ? 'true' : 'false'}
|
|
10319
|
-
data-legend-row-label-font-weight=
|
|
12916
|
+
data-legend-row-label-font-weight={(hoveredStatus === row.key || isPinned) ? '600' : '500'}
|
|
10320
12917
|
/* Round 433 / Loop: legend-row text extends from
|
|
10321
12918
|
R219's pin-only letter-spacing (0px → 0.5px on
|
|
10322
12919
|
isPinned) to a 3-tier scale matching the R432
|
|
@@ -10358,10 +12955,46 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10358
12955
|
unchanged; R55 fill brighten unchanged — only
|
|
10359
12956
|
the timing axis shifts. */
|
|
10360
12957
|
data-legend-row-label-transition="200ms"
|
|
12958
|
+
/* Round 569 / Loop — extends R568's panel-row drop-
|
|
12959
|
+
shadow pattern to the SIBLING legend-row label.
|
|
12960
|
+
Symmetric closure of the panel-row drop-shadow
|
|
12961
|
+
across both panel surfaces (recent + legend).
|
|
12962
|
+
Pre-R569 the legend-row label had 3 hover/pin
|
|
12963
|
+
axes (R55 fill + R433 ls 3-tier + R531 fw); R569
|
|
12964
|
+
adds the 4th paint axis to match R568 recent-
|
|
12965
|
+
row text exactly.
|
|
12966
|
+
Two-tier paint-axis cascade now SYMMETRIC across
|
|
12967
|
+
both side panels:
|
|
12968
|
+
recent panel title (R550) + row text (R568)
|
|
12969
|
+
legend panel title (R550-sibling) + label (R569) ← this round
|
|
12970
|
+
Each panel has glow at BOTH chrome tiers (title
|
|
12971
|
+
active + row hover/pin). The 4-axis row signature
|
|
12972
|
+
(fill + ls + fw + glow) is now identical at both
|
|
12973
|
+
panel-row text scopes — completes the panel-row
|
|
12974
|
+
text-treatment parity.
|
|
12975
|
+
Hue/blur/cadence: same as R568 (pal.legendAccent
|
|
12976
|
+
+ hex alpha 80, 2px blur, 200ms ease-out). Gate
|
|
12977
|
+
matches R531/R433 (hoveredStatus === row.key ||
|
|
12978
|
+
isPinned) — single boolean drives all 4 axes
|
|
12979
|
+
together for motion-coherent state-flip.
|
|
12980
|
+
Drop-shadow family extends to 18 anchors total
|
|
12981
|
+
(R568 was 17; R569 = 18).
|
|
12982
|
+
data-legend-row-label-glow attr added for tests. */
|
|
12983
|
+
data-legend-row-label-glow={(hoveredStatus === row.key || isPinned) ? 'true' : 'false'}
|
|
12984
|
+
/* Round 572 / Loop — sibling to recent-row text above.
|
|
12985
|
+
Stacks brightness(1.15) onto R569's drop-shadow at
|
|
12986
|
+
legend-row label scope (10th anchor in per-element
|
|
12987
|
+
brightness family). Matches recent-row text 4-axis
|
|
12988
|
+
signature exactly: fill + ls + fw + drop-shadow +
|
|
12989
|
+
brightness now BOTH panel-row text surfaces. */
|
|
12990
|
+
data-legend-row-label-brightness={(hoveredStatus === row.key || isPinned) ? '1.15' : '1'}
|
|
10361
12991
|
style={{
|
|
10362
|
-
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out',
|
|
12992
|
+
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out',
|
|
10363
12993
|
letterSpacing: isPinned ? '0.5px' :
|
|
10364
12994
|
hoveredStatus === row.key ? '0.25px' : '0px',
|
|
12995
|
+
filter: (hoveredStatus === row.key || isPinned)
|
|
12996
|
+
? `drop-shadow(0 0 2px ${pal.legendAccent}80) brightness(1.15)`
|
|
12997
|
+
: undefined,
|
|
10365
12998
|
}}
|
|
10366
12999
|
>{row.label}</text>
|
|
10367
13000
|
{/* R95: live count anchored to the right edge of the
|
|
@@ -10493,7 +13126,74 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10493
13126
|
data-legend-count-pinned={isPinned ? 'true' : 'false'}
|
|
10494
13127
|
data-legend-count-font-weight={isPinned ? '700' : '600'}
|
|
10495
13128
|
data-legend-count-fill={row.count > 0 && (hoveredStatus === row.key || isPinned) ? 'tier' : 'neutral'}
|
|
10496
|
-
|
|
13129
|
+
/* Round 518 / Loop — extends R433's 3-tier hover-
|
|
13130
|
+
letter-spacing tween from the legend-row LABEL
|
|
13131
|
+
(text at x=30) to the SIBLING legend-row COUNT
|
|
13132
|
+
digit (this text at x=215). Pre-R518 the row's
|
|
13133
|
+
label spread on hover/pin (R433: 0/0.25/0.5px)
|
|
13134
|
+
while the count digit at the row's right edge
|
|
13135
|
+
stayed dead-typographic — same row, two halves,
|
|
13136
|
+
asymmetric kerning gesture. R518 mirrors the
|
|
13137
|
+
3-tier scale at the count so the WHOLE row's
|
|
13138
|
+
typography reads as one unit under cursor: label
|
|
13139
|
+
+ count spread together at matching values.
|
|
13140
|
+
Tabular-nums (R225) makes the kerning still
|
|
13141
|
+
visible on 2-digit counts — each digit cell
|
|
13142
|
+
keeps its fixed width, but the inter-digit
|
|
13143
|
+
advance grows. R518 also closes R475's panel-
|
|
13144
|
+
row TEXT cadence at the count surface — R475
|
|
13145
|
+
lifted the label text transitions to 200ms but
|
|
13146
|
+
the count was missed; R518 lifts opacity / fill
|
|
13147
|
+
/ font-weight from 150 → 200ms AND adds the new
|
|
13148
|
+
letter-spacing axis at 200ms. One transition
|
|
13149
|
+
list, one cadence, one motion-coherent multi-
|
|
13150
|
+
axis hover/pin signature across the row.
|
|
13151
|
+
Hover-letter-spacing family extension (10
|
|
13152
|
+
anchors now): R344/R345/R347/R420/R427/R431/
|
|
13153
|
+
R432/R433/R517/R518. R518 closes the legend-
|
|
13154
|
+
row pair (label R433 + count R518). data-
|
|
13155
|
+
legend-count-letter-spacing attr exposes the
|
|
13156
|
+
resolved value for tests. */
|
|
13157
|
+
data-legend-count-letter-spacing={isPinned ? '0.5px' : hoveredStatus === row.key ? '0.25px' : '0px'}
|
|
13158
|
+
data-legend-count-transition="200ms"
|
|
13159
|
+
/* R590 — legend-row count gains filter brightness(1.15)
|
|
13160
|
+
on hover/pin. 29th anchor in per-element brightness
|
|
13161
|
+
family. Closes legend-row label↔count brightness
|
|
13162
|
+
parity within each row:
|
|
13163
|
+
legend label (R572, 10th anchor — stacks w/ DS)
|
|
13164
|
+
legend count (R590 ← this round, plain brightness)
|
|
13165
|
+
Plain brightness (no DS stack) because the count
|
|
13166
|
+
has its own multi-axis hover signature already
|
|
13167
|
+
(opacity 0.65 → 1.0, fill neutral → row.fill tier
|
|
13168
|
+
color, fw 600 → 700 on pin, letter-spacing 3-tier)
|
|
13169
|
+
— adding brightness amplifies the tier-color fill
|
|
13170
|
+
to its hover-locked brightest possible.
|
|
13171
|
+
Triple multiplicative interaction: opacity 1.0 ×
|
|
13172
|
+
tier-color fill × brightness(1.15) — the count
|
|
13173
|
+
reads as confidently "this is the active tier".
|
|
13174
|
+
Existing transition list extends with 'filter
|
|
13175
|
+
200ms ease-out' matching the existing 200ms
|
|
13176
|
+
cadence across opacity / fill / fw / letter-
|
|
13177
|
+
spacing.
|
|
13178
|
+
Legend-row scope now has parity with recent-row:
|
|
13179
|
+
both row text and count surfaces brighten +15%
|
|
13180
|
+
on row inspection. Recent-row count INHERITS its
|
|
13181
|
+
brightness via the parent <text>'s R572 filter
|
|
13182
|
+
(single ancestor filter covers all child tspans);
|
|
13183
|
+
legend-row count is a sibling <text> so it needs
|
|
13184
|
+
its OWN filter — R590 supplies that.
|
|
13185
|
+
data-legend-count-brightness attr exposes gate. */
|
|
13186
|
+
data-legend-count-brightness={(hoveredStatus === row.key || isPinned) ? '1.15' : '1'}
|
|
13187
|
+
style={{
|
|
13188
|
+
pointerEvents: 'none',
|
|
13189
|
+
filter: (hoveredStatus === row.key || isPinned)
|
|
13190
|
+
? 'brightness(1.15)'
|
|
13191
|
+
: undefined,
|
|
13192
|
+
transition: 'opacity 200ms ease-out, fill 200ms ease-out, font-weight 200ms ease-out, letter-spacing 200ms ease-out, filter 200ms ease-out',
|
|
13193
|
+
fontVariantNumeric: 'tabular-nums',
|
|
13194
|
+
letterSpacing: isPinned ? '0.5px' :
|
|
13195
|
+
hoveredStatus === row.key ? '0.25px' : '0px',
|
|
13196
|
+
}}
|
|
10497
13197
|
>{row.count}</text>
|
|
10498
13198
|
</g>
|
|
10499
13199
|
);
|
|
@@ -10552,6 +13252,74 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10552
13252
|
spacing as typographic intent. Stays well inside the
|
|
10553
13253
|
bottom-left corner; opacity 0.4 unchanged so the
|
|
10554
13254
|
watermark stays a watermark. */}
|
|
13255
|
+
{/* Round 519 / Loop — 呼吸感 family 3rd anchor. Pre-R519 the
|
|
13256
|
+
breath family had 2 anchors (R497 hub idle digit + R498
|
|
13257
|
+
recent-row hot pulse). Both signal active state — the
|
|
13258
|
+
digit when canvas is idle (no work pending), the recent
|
|
13259
|
+
row when fresh signal arrives. R519 adds a SLOW ambient
|
|
13260
|
+
breath to the brand watermark — present always, not gated
|
|
13261
|
+
on activity state. The watermark IS the canvas-corner
|
|
13262
|
+
register that says "the canvas is alive even when nothing
|
|
13263
|
+
is happening"; a 6s opacity pulse around its 0.4 mean
|
|
13264
|
+
(±0.08 swing → 0.32 ↔ 0.48) reads as ambient liveness
|
|
13265
|
+
rather than foreground signal.
|
|
13266
|
+
Why 6s (not R497's 4s): the breath family now spans
|
|
13267
|
+
activity registers (R497 4s — idle-focal: present and
|
|
13268
|
+
waiting; R498 ~3s — hot signal: just arrived) and now
|
|
13269
|
+
ambient register (R519 6s — corner watermark: always-on
|
|
13270
|
+
background). Slower cadence keeps the watermark in the
|
|
13271
|
+
background; ~10 pct slower than R497 keeps it out of
|
|
13272
|
+
phase so the two anchors never beat together visibly.
|
|
13273
|
+
Gate: !reducedMotion. Inside the prefers-reduced-motion
|
|
13274
|
+
media query, SMIL animate isn't covered by globals.css
|
|
13275
|
+
R29 (which only kills CSS animation property), so we
|
|
13276
|
+
gate at JSX level — when reducedMotion is true the
|
|
13277
|
+
<animate> child isn't mounted and opacity stays at the
|
|
13278
|
+
static 0.4. data-topo-brand-watermark-breath attr
|
|
13279
|
+
exposes the gate state for tests.
|
|
13280
|
+
呼吸感 family extension (3 anchors): R497 hub idle / R498
|
|
13281
|
+
recent-row hot / R519 brand watermark ambient. */}
|
|
13282
|
+
{/* Round 525 / Loop — focal-recede family 3rd anchor. R507
|
|
13283
|
+
receded the hub-center workingCount digit; R508 receded
|
|
13284
|
+
the hub-highlight disc; both fade to 0.85× when any non-
|
|
13285
|
+
hub canvas surface is hovered (alias / edge / group /
|
|
13286
|
+
status / vendor) — the "you're inspecting elsewhere"
|
|
13287
|
+
gesture. R525 extends the pattern to the brand watermark
|
|
13288
|
+
at canvas bottom-left, the always-on decorative brand
|
|
13289
|
+
element. Pre-R525 the watermark stayed at its R519
|
|
13290
|
+
breath baseline (0.32-0.48 SMIL pulse) regardless of
|
|
13291
|
+
canvas attention; post-R525 it fades to 70% wrapper
|
|
13292
|
+
opacity (effective 0.224-0.336 with breath) when canvas
|
|
13293
|
+
attention is elsewhere, matching the same focal-recede
|
|
13294
|
+
semantic R507/R508 establish at the hub focal cluster.
|
|
13295
|
+
Implementation: wrap the existing <text> in a <g>
|
|
13296
|
+
wrapper whose opacity multiplies with the inner text's
|
|
13297
|
+
SMIL-animated opacity. SVG opacity composes
|
|
13298
|
+
multiplicatively across the parent/child chain, so:
|
|
13299
|
+
normal: g.opacity=1.0 × text.opacity(SMIL 0.32-0.48) = 0.32-0.48
|
|
13300
|
+
recede: g.opacity=0.7 × text.opacity(SMIL 0.32-0.48) = 0.224-0.336
|
|
13301
|
+
SMIL on inner text continues running through both
|
|
13302
|
+
states; only the wrapper opacity flips. 300ms ease-out
|
|
13303
|
+
transition on wrapper (matches R508 hub-highlight recede
|
|
13304
|
+
transition).
|
|
13305
|
+
Gate matches R507/R508 — focal-recede is a UNIFIED
|
|
13306
|
+
non-hub-canvas-hover signal driving multiple anchors,
|
|
13307
|
+
so all three (hub digit / hub-highlight / brand
|
|
13308
|
+
watermark) fade together as the canvas's decorative
|
|
13309
|
+
register, leaving only the surface under inspection
|
|
13310
|
+
foregrounded.
|
|
13311
|
+
Focal-recede family extension (3 anchors): R507 hub
|
|
13312
|
+
digit / R508 hub-highlight / R525 brand watermark. */}
|
|
13313
|
+
<g
|
|
13314
|
+
opacity={(hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
13315
|
+
hoveredStatus || hoveredVendor) && !hoveredHub ? 0.7 : 1}
|
|
13316
|
+
data-topo-brand-watermark-wrapper
|
|
13317
|
+
data-topo-brand-watermark-recede={
|
|
13318
|
+
(hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
13319
|
+
hoveredStatus || hoveredVendor) && !hoveredHub ? 'true' : 'false'
|
|
13320
|
+
}
|
|
13321
|
+
style={{ pointerEvents: 'none', transition: 'opacity 300ms ease-out' }}
|
|
13322
|
+
>
|
|
10555
13323
|
<text
|
|
10556
13324
|
x="16" y="672"
|
|
10557
13325
|
fontSize="11" fontFamily="monospace" fontWeight="600"
|
|
@@ -10559,8 +13327,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10559
13327
|
fill={pal.legendText}
|
|
10560
13328
|
opacity="0.4"
|
|
10561
13329
|
data-topo-brand-watermark
|
|
13330
|
+
data-topo-brand-watermark-breath={reducedMotion ? 'false' : 'true'}
|
|
10562
13331
|
style={{ pointerEvents: 'none', transition: 'fill 200ms ease-out' }}
|
|
10563
|
-
>sleep2agi
|
|
13332
|
+
>sleep2agi{!reducedMotion && (
|
|
13333
|
+
<animate attributeName="opacity" values="0.32;0.48;0.32" dur="6s" repeatCount="indefinite" />
|
|
13334
|
+
)}</text>
|
|
13335
|
+
</g>
|
|
10564
13336
|
{/* v0.10.0 Hero 3 Wave 1 / RFC §3.I (Vincent 5215 + 通信龙
|
|
10565
13337
|
lead-autonomy Q4 dual-anchor minimal): canvas top-left
|
|
10566
13338
|
crescent moon brand mark, visible ONLY when the
|
|
@@ -10595,10 +13367,47 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10595
13367
|
the R175 panel-fade-in uses for cascade rhythm. data-
|
|
10596
13368
|
topo-brand-canvas-mark-visible exposes the gate for
|
|
10597
13369
|
tests. */}
|
|
13370
|
+
{/* Round 526 / Loop — focal-recede family 4th anchor.
|
|
13371
|
+
Symmetric polish to R525 (watermark recede). The
|
|
13372
|
+
brand crescent at canvas top-left is the second
|
|
13373
|
+
decorative brand element on the canvas; pre-R526 it
|
|
13374
|
+
stayed at flat opacity 0.35 (when visible) regardless
|
|
13375
|
+
of canvas attention. R526 multiplies its visible
|
|
13376
|
+
opacity by 0.7 when ANY non-hub canvas surface is
|
|
13377
|
+
hovered, matching R525's deeper-recede semantic for
|
|
13378
|
+
decorative brand elements (vs hub focal cluster's
|
|
13379
|
+
0.85× recede at R507/R508).
|
|
13380
|
+
Composes cleanly with existing flowLinks gate:
|
|
13381
|
+
normal, flowLinks=0: opacity = 0.35 * 1.0 = 0.350
|
|
13382
|
+
recede, flowLinks=0: opacity = 0.35 * 0.7 = 0.245
|
|
13383
|
+
invisible, flowLinks>0: opacity = 0.00 * any = 0.000
|
|
13384
|
+
Multiplicative chain means recede only matters when
|
|
13385
|
+
crescent is visible (quiet canvas, flowLinks=0) —
|
|
13386
|
+
exactly when canvas attention elsewhere should
|
|
13387
|
+
dim the decorative register. 300ms transition
|
|
13388
|
+
already covers both axes (the existing visibility
|
|
13389
|
+
opacity ramp + the new recede multiplier easing).
|
|
13390
|
+
Focal-recede family extension (4 anchors): R507 hub
|
|
13391
|
+
digit / R508 hub-highlight / R525 watermark / R526
|
|
13392
|
+
crescent (this round). Canvas brand surfaces (R525
|
|
13393
|
+
watermark + R526 crescent) now BOTH carry focal-
|
|
13394
|
+
recede at the same 0.7 multiplier, fading as a
|
|
13395
|
+
decorative pair when the canvas's focal attention
|
|
13396
|
+
shifts elsewhere.
|
|
13397
|
+
data-topo-brand-canvas-mark-recede attr exposes the
|
|
13398
|
+
gate state for tests. */}
|
|
10598
13399
|
<g
|
|
10599
|
-
opacity={flowLinks.length === 0 ? 0.35 : 0
|
|
13400
|
+
opacity={(flowLinks.length === 0 ? 0.35 : 0) * (
|
|
13401
|
+
(hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
13402
|
+
hoveredStatus || hoveredVendor) && !hoveredHub ? 0.7 : 1
|
|
13403
|
+
)}
|
|
10600
13404
|
data-topo-brand-canvas-mark
|
|
10601
13405
|
data-topo-brand-canvas-mark-visible={flowLinks.length === 0 ? 'true' : 'false'}
|
|
13406
|
+
data-topo-brand-canvas-mark-recede={
|
|
13407
|
+
(hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
13408
|
+
hoveredStatus || hoveredVendor) && !hoveredHub ? 'true' : 'false'
|
|
13409
|
+
}
|
|
13410
|
+
data-topo-brand-canvas-mark-breath={reducedMotion ? 'false' : 'true'}
|
|
10602
13411
|
style={{ pointerEvents: 'none', transition: 'opacity 300ms ease-out, fill 200ms ease-out' }}
|
|
10603
13412
|
>
|
|
10604
13413
|
<defs>
|
|
@@ -10608,11 +13417,44 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10608
13417
|
<circle cx="17.5" cy="13" r="10" fill="black" />
|
|
10609
13418
|
</mask>
|
|
10610
13419
|
</defs>
|
|
13420
|
+
{/* Round 528 / Loop — 呼吸感 family 4th anchor. Symmetric
|
|
13421
|
+
polish to R519 watermark ambient breath. The brand
|
|
13422
|
+
crescent at canvas top-left is the second decorative
|
|
13423
|
+
canvas brand surface; pre-R528 it stayed at the static
|
|
13424
|
+
composed opacity (wrapper 0.35 × no inner anim = flat).
|
|
13425
|
+
Post-R528 the inner <rect>'s fill-opacity breathes
|
|
13426
|
+
0.8 ↔ 1.0 on a 7s cycle, composing multiplicatively
|
|
13427
|
+
with the wrapper's recede gate:
|
|
13428
|
+
normal visible: 0.35 × (0.8-1.0) = 0.280-0.350
|
|
13429
|
+
recede visible: 0.245 × (0.8-1.0) = 0.196-0.245
|
|
13430
|
+
invisible: 0 × any = 0
|
|
13431
|
+
7s cadence intentionally OUT OF PHASE with R519
|
|
13432
|
+
watermark's 6s — the two ambient anchors never beat
|
|
13433
|
+
together visibly when both visible. R497 hub idle
|
|
13434
|
+
breath (4s) is the loudest; R498 recent-row hot pulse
|
|
13435
|
+
(~3s) is the most-active; R519 watermark (6s) +
|
|
13436
|
+
R528 crescent (7s) are the quietest ambient pair.
|
|
13437
|
+
呼吸感 family extension (4 anchors):
|
|
13438
|
+
R497 hub idle digit 4s active-idle register
|
|
13439
|
+
R498 recent-row hot pulse 3s active-fresh register
|
|
13440
|
+
R519 watermark ambient 6s ambient (always-on)
|
|
13441
|
+
R528 crescent ambient 7s ambient (quiet-only) ← this round
|
|
13442
|
+
SMIL <animate> on fill-opacity (not parent opacity) so
|
|
13443
|
+
the wrapper's React-controlled gate compositions stay
|
|
13444
|
+
intact. Gated on !reducedMotion at JSX level —
|
|
13445
|
+
reducedMotion users see the inner rect at default
|
|
13446
|
+
fill-opacity=1.0 (no SMIL mounted, wrapper's static
|
|
13447
|
+
composed opacity wins). data-topo-brand-canvas-mark-
|
|
13448
|
+
breath attr exposes the gate state. */}
|
|
10611
13449
|
<rect
|
|
10612
13450
|
x="16" y="16" width="28" height="28"
|
|
10613
13451
|
fill={pal.legendText}
|
|
10614
13452
|
mask="url(#s2a-canvas-corner-mask)"
|
|
10615
|
-
|
|
13453
|
+
>
|
|
13454
|
+
{!reducedMotion && (
|
|
13455
|
+
<animate attributeName="fill-opacity" values="0.8;1;0.8" dur="7s" repeatCount="indefinite" />
|
|
13456
|
+
)}
|
|
13457
|
+
</rect>
|
|
10616
13458
|
</g>
|
|
10617
13459
|
</svg>
|
|
10618
13460
|
|
|
@@ -10964,12 +13806,32 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10964
13806
|
(the minimap viewport is small, ~120×82 px).
|
|
10965
13807
|
Filter is paint-only — bbox unchanged. transition
|
|
10966
13808
|
list extends to include 'filter 200ms ease-out'
|
|
10967
|
-
so the glow eases when zoom crosses 1.5x.
|
|
10968
|
-
|
|
13809
|
+
so the glow eases when zoom crosses 1.5x.
|
|
13810
|
+
R540: extends the drop-shadow to also fire on
|
|
13811
|
+
hoveredMinimap with HOVER PRECEDENCE over zoom-
|
|
13812
|
+
state. Pre-R540 the viewport drop-shadow was
|
|
13813
|
+
zoom-only (single gate); R540 adds an
|
|
13814
|
+
interactional gate at lighter blur intensity.
|
|
13815
|
+
Hover wins when both true — interactional signal
|
|
13816
|
+
(user is inspecting) trumps informational signal
|
|
13817
|
+
(you're zoomed). Sibling to R534 edge-badge
|
|
13818
|
+
hover-precedence + R538 group-label hover-tier
|
|
13819
|
+
extensions.
|
|
13820
|
+
2-tier alpha ladder:
|
|
13821
|
+
hover (interactional) legendAccent 99 (~60%)
|
|
13822
|
+
zoom > 1.5 (info) legendAccent 80 (~50%)
|
|
13823
|
+
rest none
|
|
13824
|
+
data-topo-minimap-viewport-glow attr upgraded
|
|
13825
|
+
binary ('true'/'false') → 3-value ('hover' |
|
|
13826
|
+
'zoom' | 'false') so tests can distinguish
|
|
13827
|
+
gate cause. */
|
|
13828
|
+
data-topo-minimap-viewport-glow={hoveredMinimap ? 'hover' : view.zoom > 1.5 ? 'zoom' : 'false'}
|
|
10969
13829
|
style={{
|
|
10970
|
-
filter:
|
|
10971
|
-
? `drop-shadow(0 0 2px ${pal.legendAccent}
|
|
10972
|
-
:
|
|
13830
|
+
filter: hoveredMinimap
|
|
13831
|
+
? `drop-shadow(0 0 2px ${pal.legendAccent}99)`
|
|
13832
|
+
: view.zoom > 1.5
|
|
13833
|
+
? `drop-shadow(0 0 2px ${pal.legendAccent}80)`
|
|
13834
|
+
: undefined,
|
|
10973
13835
|
transition: smoothView
|
|
10974
13836
|
? 'x 280ms ease-out, y 280ms ease-out, width 280ms ease-out, height 280ms ease-out, stroke-width 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out'
|
|
10975
13837
|
: 'stroke-width 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',
|
|
@@ -11110,7 +13972,57 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11110
13972
|
// transition-colors only — without the transform transition,
|
|
11111
13973
|
// active:scale-95 would hard-cut. transform-gpu promotes the
|
|
11112
13974
|
// layer so scale doesn't trigger paint thrash.
|
|
11113
|
-
|
|
13975
|
+
/* Round 521 / Loop — extends R270's hover-preview pattern
|
|
13976
|
+
(inactive toggle hover previews the active state's
|
|
13977
|
+
visual register) to the TYPOGRAPHY axis at the chrome
|
|
13978
|
+
nodeSize S/M/L surface. Pre-R521 the inactive variant
|
|
13979
|
+
had `hover:bg-cyan-500/5` (R270 bg preview) but no
|
|
13980
|
+
typography preview — active variant uses `font-medium`
|
|
13981
|
+
(fw 500), inactive variant sat at default fw 400 even
|
|
13982
|
+
on hover.
|
|
13983
|
+
R521 adds `hover:font-medium` + `transition-[font-
|
|
13984
|
+
weight]` to the inactive variant so hovering an
|
|
13985
|
+
inactive S/M/L letter thickens the glyph 400 → 500,
|
|
13986
|
+
previewing the typography of the active state the
|
|
13987
|
+
click would commit to. Sibling to R421 chrome zoom-
|
|
13988
|
+
level fontWeight hover delta (rest 500 → hover 600)
|
|
13989
|
+
and R520 footer fontWeight hover (500 → 600) — same
|
|
13990
|
+
idiom: thicken-on-hover for chrome surfaces with a
|
|
13991
|
+
pre-commit gesture.
|
|
13992
|
+
`font-medium` (500) matches the ACTIVE variant's
|
|
13993
|
+
fw exactly — the inactive hover landing weight equals
|
|
13994
|
+
the active locked weight, so clicking commits to a
|
|
13995
|
+
typography state the eye already saw 'on the way in'.
|
|
13996
|
+
Hover-fw family extension (5 anchors now):
|
|
13997
|
+
R416 chip-row count digit rest 500 → hover 700/600
|
|
13998
|
+
R420 chrome zoom-level rest 500 → hover 600
|
|
13999
|
+
R425 hub-center digit rest 700 → hover 800
|
|
14000
|
+
R520 +N more flows footer rest 500 → hover 600
|
|
14001
|
+
R521 chrome nodeSize S/M/L inactive rest 400 → hover 500 ← this round
|
|
14002
|
+
Active variant `font-medium` unchanged so the rest-vs-
|
|
14003
|
+
active typography distinction stays intact when the
|
|
14004
|
+
user IS clicked-in (active stays at fw 500, inactive
|
|
14005
|
+
rest at fw 400, inactive hover preview at fw 500).
|
|
14006
|
+
data-topo-chrome-nodesize-hover-preview-fw="500" attr
|
|
14007
|
+
exposes the polish for tests. */
|
|
14008
|
+
/* R598 — nodeSize S/M/L segmented buttons gain hover:
|
|
14009
|
+
brightness-[1.15] (39+40+41st anchors, 8+9+10th HTML).
|
|
14010
|
+
Final segmented chrome control to close brightness
|
|
14011
|
+
coverage. After R598 all three segmented controls
|
|
14012
|
+
(zoom + layout + nodeSize) have full hover-brightness
|
|
14013
|
+
parity.
|
|
14014
|
+
Tailwind v4 arbitrary `[transition-property:color,
|
|
14015
|
+
background-color,transform,font-weight,filter]`
|
|
14016
|
+
replaces the chain of `transition-colors transition-
|
|
14017
|
+
transform transition-[font-weight]` so the filter
|
|
14018
|
+
property joins the existing 200ms cadence at the
|
|
14019
|
+
same beat. Segmented-unity rule (R400) preserved —
|
|
14020
|
+
brightness is pure paint, no geometric break.
|
|
14021
|
+
data-topo-chrome-nodesize-brightness-hover='1.15'
|
|
14022
|
+
attr documents the hover value for tests. */
|
|
14023
|
+
className={`px-2 py-1 [transition-property:color,background-color,transform,font-weight,filter] duration-200 ease-out transform-gpu active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:brightness-[1.15] ${idx > 0 ? 'border-l' : ''} ${nodeScale === v ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 hover:text-cyan-200 active:bg-cyan-500/25' : 'hover:bg-cyan-500/5 active:bg-cyan-500/15 hover:font-medium'}${chromePopping === popKey ? ' anet-chrome-pop' : ''}`}
|
|
14024
|
+
data-topo-chrome-nodesize-hover-preview-fw={nodeScale === v ? null : '500'}
|
|
14025
|
+
data-topo-chrome-nodesize-brightness-hover="1.15"
|
|
11114
14026
|
style={{ color: nodeScale === v ? undefined : pal.legendText, borderColor: pal.containerBorder }}
|
|
11115
14027
|
>
|
|
11116
14028
|
{lbl}
|
|
@@ -11148,6 +14060,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11148
14060
|
onClick={() => { popChrome('zoom-out'); zoomByDiscrete(1 / 1.2); }}
|
|
11149
14061
|
data-topo-chrome-zoom-out
|
|
11150
14062
|
data-topo-chrome-zoom-out-popping={chromePopping === 'zoom-out' ? 'true' : 'false'}
|
|
14063
|
+
data-topo-chrome-zoom-out-brightness-hover="1.15"
|
|
11151
14064
|
// R196: press-state deepens bg one tier above hover (white/5
|
|
11152
14065
|
// → white/10) so mouse-down has a tactile dim before the
|
|
11153
14066
|
// R186 icon pop fires on release.
|
|
@@ -11156,7 +14069,26 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11156
14069
|
// press-feedback family (R492 + nodeSize above). transition-
|
|
11157
14070
|
// transform + duration-200 + ease-out + transform-gpu added
|
|
11158
14071
|
// since the className had only transition-colors.
|
|
11159
|
-
|
|
14072
|
+
/* R596 — zoom-out segmented button gains hover:brightness-[1.15]
|
|
14073
|
+
(35th anchor in per-element brightness family, 4th HTML).
|
|
14074
|
+
Sibling to zoom-in below — paired-anchor round closing the
|
|
14075
|
+
entire zoom trio at brightness parity (R593 zoom-level
|
|
14076
|
+
readout + R596 zoom-out + zoom-in).
|
|
14077
|
+
|
|
14078
|
+
Segmented-control constraint preserved: brightness is pure
|
|
14079
|
+
paint (no geometry shift) so it doesn't break the R400
|
|
14080
|
+
segmented-unity rule (which only excludes geometric hover-
|
|
14081
|
+
lift like translateY). Each segment can brighten
|
|
14082
|
+
independently while the strip stays planted as one unit.
|
|
14083
|
+
|
|
14084
|
+
Tailwind v4 arbitrary `[transition-property:...]` replaces
|
|
14085
|
+
`transition-colors transition-transform` so the filter
|
|
14086
|
+
property joins the existing 200ms cadence — bg / color /
|
|
14087
|
+
transform / filter all ease in unison.
|
|
14088
|
+
|
|
14089
|
+
data-topo-chrome-zoom-out-brightness-hover='1.15' attr
|
|
14090
|
+
documents the hover value for tests. */
|
|
14091
|
+
className="group px-2 py-1 hover:bg-white/5 hover:brightness-[1.15] active:bg-white/10 [transition-property:color,background-color,transform,filter] duration-200 ease-out transform-gpu active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset"
|
|
11160
14092
|
style={{ color: pal.legendText }}
|
|
11161
14093
|
aria-label="Zoom out"
|
|
11162
14094
|
title="Zoom out (−)"
|
|
@@ -11248,13 +14180,66 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11248
14180
|
? 'true' : 'false'
|
|
11249
14181
|
}
|
|
11250
14182
|
data-topo-chrome-zoom-level-hover={hoveredZoomLevel ? 'true' : 'false'}
|
|
14183
|
+
/* Round 517 / Loop — extends the chrome zoom-level readout
|
|
14184
|
+
from 2-axis (R347 letter-spacing + R420 fontWeight) to
|
|
14185
|
+
3-axis hover signature by adding a color brighten to
|
|
14186
|
+
pal.legendHeadline. Pre-R517 the readout's color stayed
|
|
14187
|
+
at pal.legendText on hover; the digits got tighter
|
|
14188
|
+
kerning (0→0.5px) and heavier weight (500→600) but
|
|
14189
|
+
stayed the same legendText gray tone. R517 lifts color
|
|
14190
|
+
to legendHeadline on hover so the readout brightens
|
|
14191
|
+
into the headline tier at the same beat — matching the
|
|
14192
|
+
R55/R197/R239 hover-deepen-own-hue idiom that legend-
|
|
14193
|
+
row label + count carry at panel scope. Chrome strip's
|
|
14194
|
+
only data display now has full 3-axis hover signature
|
|
14195
|
+
(letter-spacing + fontWeight + color), parity with the
|
|
14196
|
+
chip-row chips' own hover-brighten pattern.
|
|
14197
|
+
Implementation: inline color uses the same hoveredZoom-
|
|
14198
|
+
Level state as R347/R420 — no new state. Transition
|
|
14199
|
+
already includes 'color 200ms ease-out' (R264) so the
|
|
14200
|
+
brighten eases under the same cadence as the kerning +
|
|
14201
|
+
weight tweens — one motion-coherent 3-axis lift.
|
|
14202
|
+
data-topo-chrome-zoom-level-color attr exposes the
|
|
14203
|
+
resolved color string for tests. */
|
|
14204
|
+
data-topo-chrome-zoom-level-color={hoveredZoomLevel ? 'headline' : 'text'}
|
|
14205
|
+
/* R593 — chrome zoom-level readout gains filter
|
|
14206
|
+
brightness(1.15) on hover. 32nd anchor in per-element
|
|
14207
|
+
brightness family. FIRST HTML-element brightness
|
|
14208
|
+
anchor outside the SVG canvas (the readout is an
|
|
14209
|
+
HTML <span>, not SVG).
|
|
14210
|
+
|
|
14211
|
+
Closes the chrome zoom-level readout's hover signature
|
|
14212
|
+
at 4 AXES:
|
|
14213
|
+
R347 letter-spacing 0 → 0.5px
|
|
14214
|
+
R420 fontWeight 500 → 600
|
|
14215
|
+
R517 color legendText → legendHeadline
|
|
14216
|
+
R593 brightness 1 → 1.15 ← this round
|
|
14217
|
+
|
|
14218
|
+
The chrome strip's only data display now has full
|
|
14219
|
+
4-axis hover signature — color brightens to headline
|
|
14220
|
+
tier, glyph thickens, kerning spreads, AND brightness
|
|
14221
|
+
lifts another +15% on top of the color swap.
|
|
14222
|
+
|
|
14223
|
+
Triple-paint multiplicative interaction:
|
|
14224
|
+
color (legendHeadline) × brightness(1.15) — the
|
|
14225
|
+
headline tier reads dramatically brighter than a
|
|
14226
|
+
plain color swap.
|
|
14227
|
+
|
|
14228
|
+
Transition list extends with 'filter 200ms ease-out'
|
|
14229
|
+
matching the existing 200ms cadence across all 4 axes
|
|
14230
|
+
— motion-coherent state-flip.
|
|
14231
|
+
|
|
14232
|
+
data-topo-chrome-zoom-level-brightness attr exposes
|
|
14233
|
+
the gate for tests. */
|
|
14234
|
+
data-topo-chrome-zoom-level-brightness={hoveredZoomLevel ? '1.15' : '1'}
|
|
11251
14235
|
onMouseEnter={() => setHoveredZoomLevel(true)}
|
|
11252
14236
|
onMouseLeave={() => setHoveredZoomLevel(false)}
|
|
11253
14237
|
style={{
|
|
11254
|
-
color: pal.legendText,
|
|
14238
|
+
color: hoveredZoomLevel ? pal.legendHeadline : pal.legendText,
|
|
11255
14239
|
borderColor: pal.containerBorder,
|
|
11256
14240
|
minWidth: 46,
|
|
11257
14241
|
display: 'inline-block',
|
|
14242
|
+
filter: hoveredZoomLevel ? 'brightness(1.15)' : undefined,
|
|
11258
14243
|
// R347: letter-spacing hover tween — extends R344/R345
|
|
11259
14244
|
// hover-letter-spacing family into the chrome strip.
|
|
11260
14245
|
letterSpacing: hoveredZoomLevel ? '0.5px' : '0',
|
|
@@ -11284,7 +14269,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11284
14269
|
on theme flip while siblings eased. Sibling treatment
|
|
11285
14270
|
to the nodeSize + zoom wrapper transitions added this
|
|
11286
14271
|
round. */
|
|
11287
|
-
transition: 'color 200ms ease-out, border-color 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out',
|
|
14272
|
+
transition: 'color 200ms ease-out, border-color 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out',
|
|
11288
14273
|
}}
|
|
11289
14274
|
title="Current zoom level"
|
|
11290
14275
|
>
|
|
@@ -11294,13 +14279,16 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11294
14279
|
onClick={() => { popChrome('zoom-in'); zoomByDiscrete(1.2); }}
|
|
11295
14280
|
data-topo-chrome-zoom-in
|
|
11296
14281
|
data-topo-chrome-zoom-in-popping={chromePopping === 'zoom-in' ? 'true' : 'false'}
|
|
14282
|
+
data-topo-chrome-zoom-in-brightness-hover="1.15"
|
|
11297
14283
|
// R196: press-state (mirror of zoom-out above).
|
|
11298
14284
|
// R352: `group` lets the inner svg respond via group-hover.
|
|
11299
14285
|
// R493 — zoom +/− buttons join the chrome-strip active:scale-95
|
|
11300
14286
|
// press-feedback family (R492 + nodeSize above). transition-
|
|
11301
14287
|
// transform + duration-200 + ease-out + transform-gpu added
|
|
11302
14288
|
// since the className had only transition-colors.
|
|
11303
|
-
|
|
14289
|
+
/* R596 sibling — zoom-in mirrors zoom-out above. 36th anchor
|
|
14290
|
+
in per-element brightness family, 5th HTML. */
|
|
14291
|
+
className="group px-2 py-1 hover:bg-white/5 hover:brightness-[1.15] active:bg-white/10 [transition-property:color,background-color,transform,filter] duration-200 ease-out transform-gpu active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset"
|
|
11304
14292
|
style={{ color: pal.legendText }}
|
|
11305
14293
|
aria-label="Zoom in"
|
|
11306
14294
|
title="Zoom in (+)"
|
|
@@ -11359,9 +14347,49 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11359
14347
|
// active state during press = hover-lift (-1px) + scale-95
|
|
11360
14348
|
// composes as translateY(-1px) scale(0.95) — lift-and-compress
|
|
11361
14349
|
// for tactile click feel.
|
|
11362
|
-
className="p-1.5 rounded-md border hover:bg-white/5 active:bg-white/10 hover:-translate-y-px active:scale-95
|
|
14350
|
+
className="p-1.5 rounded-md border hover:bg-white/5 active:bg-white/10 hover:-translate-y-px active:scale-95 transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60"
|
|
11363
14351
|
data-topo-chrome-reset-hover-lift="true"
|
|
11364
|
-
|
|
14352
|
+
/* R594 — chrome reset button gains filter brightness(1.15)
|
|
14353
|
+
on hoveredReset. 33rd anchor in per-element brightness
|
|
14354
|
+
family, 2nd HTML-element anchor (R593 zoom-level was
|
|
14355
|
+
the first).
|
|
14356
|
+
|
|
14357
|
+
Closes reset button hover signature at 5 axes:
|
|
14358
|
+
R400 button hover-lift translateY(-1px)
|
|
14359
|
+
R350 icon hover-rotate -8°
|
|
14360
|
+
R453 icon stroke-width 2.5 → 2.8
|
|
14361
|
+
R514 icon scale 1.0 → 1.10
|
|
14362
|
+
R594 button brightness 1 → 1.15 ← this round
|
|
14363
|
+
|
|
14364
|
+
The reset button now has the densest hover signature
|
|
14365
|
+
among the 2 standalone chrome buttons (reset + fullscreen
|
|
14366
|
+
from R400). When user hovers, the entire button lifts
|
|
14367
|
+
(-1px), brightens (+15%), AND its icon rotates (-8°),
|
|
14368
|
+
thickens (+0.3 sw), and scales up (+10%). Inside +
|
|
14369
|
+
outside motion-coherent.
|
|
14370
|
+
|
|
14371
|
+
Triple-paint multiplicative interaction at this surface:
|
|
14372
|
+
bg (hover:bg-white/5) × border (pal.containerBorder)
|
|
14373
|
+
× color (pal.legendText) ALL get brightness(1.15)
|
|
14374
|
+
multiplied in — the button's chrome stack uniformly
|
|
14375
|
+
brightens, reading as "this control is awake under
|
|
14376
|
+
your cursor".
|
|
14377
|
+
|
|
14378
|
+
Inline transition shorthand replaces the className-
|
|
14379
|
+
based `transition-colors transition-transform` (R557
|
|
14380
|
+
banked: when adding new transition-driven hover axes
|
|
14381
|
+
to an element, extend the INLINE list — inline
|
|
14382
|
+
overrides className). All 5 transition properties (bg
|
|
14383
|
+
/ color / border / transform / filter) now ride one
|
|
14384
|
+
200ms ease-out beat. */
|
|
14385
|
+
data-topo-chrome-reset-brightness={hoveredReset ? '1.15' : '1'}
|
|
14386
|
+
style={{
|
|
14387
|
+
background: pal.legendBox.fill,
|
|
14388
|
+
borderColor: pal.containerBorder,
|
|
14389
|
+
color: pal.legendText,
|
|
14390
|
+
filter: hoveredReset ? 'brightness(1.15)' : undefined,
|
|
14391
|
+
transition: 'color 200ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, transform 200ms ease-out, filter 200ms ease-out',
|
|
14392
|
+
}}
|
|
11365
14393
|
aria-label="Reset view"
|
|
11366
14394
|
title="Reset zoom + pan (0, or double-click the canvas)"
|
|
11367
14395
|
>
|
|
@@ -11407,8 +14435,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11407
14435
|
// owns transform during its 450ms run. transformOrigin
|
|
11408
14436
|
// 'center' so rotation pivots around the icon's centre
|
|
11409
14437
|
// (default would be top-left and the icon would arc).
|
|
14438
|
+
/* Round 514 / Loop — extends R352/R353 chrome icon hover-
|
|
14439
|
+
scale family to the reset button. Pre-R514 the reset
|
|
14440
|
+
icon had hover-rotate (-8°, R350) + hover-sw (2.5→2.8,
|
|
14441
|
+
R453) but no hover-scale, while zoom-out (R352), zoom-
|
|
14442
|
+
in (R352), and fullscreen (R353) icons all carried
|
|
14443
|
+
`group-hover:scale-110`. R514 brings the reset icon
|
|
14444
|
+
into the same 3-axis hover signature (rotate + sw +
|
|
14445
|
+
scale) as the rest of the chrome strip.
|
|
14446
|
+
Implementation: inline transform composes rotate +
|
|
14447
|
+
scale into one string. `transform: rotate(-8deg)
|
|
14448
|
+
scale(1.1)` on hover; `rotate(0) scale(1)` at rest.
|
|
14449
|
+
transformOrigin 'center' applies to both — rotation
|
|
14450
|
+
pivots around centre AND scale grows from centre.
|
|
14451
|
+
The Tailwind `group-hover:scale-110` approach can't
|
|
14452
|
+
work here because inline `style.transform` overrides
|
|
14453
|
+
className-based transforms; compose the multi-axis
|
|
14454
|
+
transform inline instead.
|
|
14455
|
+
Chrome icon hover gesture parity (post-R514):
|
|
14456
|
+
zoom-out scale-110 + sw-lift (R352/R454)
|
|
14457
|
+
zoom-in scale-110 + sw-lift (R352/R454)
|
|
14458
|
+
fullscreen scale-110 + sw-lift (R353/R455)
|
|
14459
|
+
reset scale-1.1 + sw-lift + rotate -8°
|
|
14460
|
+
(R514 + R453 + R350)
|
|
14461
|
+
reset gets the EXTRA rotate axis because R350's spin
|
|
14462
|
+
preview semantic is reset-specific — the rotation
|
|
14463
|
+
hints at the click-spin (R184) the button will fire. */
|
|
11410
14464
|
style={{
|
|
11411
|
-
transform: hoveredReset && !resetSpinning ? 'rotate(-8deg)' : 'rotate(0deg)',
|
|
14465
|
+
transform: hoveredReset && !resetSpinning ? 'rotate(-8deg) scale(1.1)' : 'rotate(0deg) scale(1)',
|
|
11412
14466
|
transformOrigin: 'center',
|
|
11413
14467
|
transition: 'transform 200ms ease-out, stroke-width 200ms ease-out',
|
|
11414
14468
|
}}
|
|
@@ -11432,9 +14486,15 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11432
14486
|
classes win specificity. */}
|
|
11433
14487
|
<button
|
|
11434
14488
|
onClick={() => { popChrome('fullscreen'); toggleFullscreen(); }}
|
|
14489
|
+
onMouseEnter={() => setHoveredFullscreen(true)}
|
|
14490
|
+
onMouseLeave={() => setHoveredFullscreen(false)}
|
|
14491
|
+
onFocus={() => setHoveredFullscreen(true)}
|
|
14492
|
+
onBlur={() => setHoveredFullscreen(false)}
|
|
11435
14493
|
data-topo-chrome-fullscreen
|
|
11436
14494
|
data-topo-chrome-fullscreen-active={isFullscreen ? 'true' : 'false'}
|
|
11437
14495
|
data-topo-chrome-fullscreen-popping={chromePopping === 'fullscreen' ? 'true' : 'false'}
|
|
14496
|
+
data-topo-chrome-fullscreen-hover={hoveredFullscreen ? 'true' : 'false'}
|
|
14497
|
+
data-topo-chrome-fullscreen-brightness={hoveredFullscreen ? '1.15' : '1'}
|
|
11438
14498
|
// R196: fullscreen also picks up press-state — active variant
|
|
11439
14499
|
// deepens cyan-500/20 → cyan-500/25 on press; non-active
|
|
11440
14500
|
// deepens white/5 → white/10.
|
|
@@ -11454,17 +14514,48 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11454
14514
|
// R400: hover translateY(-1px) lift — see reset button above for family doc.
|
|
11455
14515
|
// R493 — fullscreen joins active:scale-95 press family (same as
|
|
11456
14516
|
// reset above: lift-and-compress compound transform on press).
|
|
11457
|
-
className={`group p-1.5 rounded-md border hover:-translate-y-px active:scale-95
|
|
14517
|
+
className={`group p-1.5 rounded-md border hover:-translate-y-px active:scale-95 transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 ${
|
|
11458
14518
|
isFullscreen
|
|
11459
|
-
? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25'
|
|
14519
|
+
? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 hover:text-cyan-200 active:bg-cyan-500/25'
|
|
11460
14520
|
: 'hover:bg-cyan-500/5 active:bg-cyan-500/15'
|
|
11461
14521
|
}${chromePopping === 'fullscreen' ? ' anet-chrome-pop' : ''}`}
|
|
11462
14522
|
data-topo-chrome-fullscreen-hover-lift="true"
|
|
14523
|
+
/* R595 — chrome fullscreen button gains filter brightness(1.15)
|
|
14524
|
+
on hoveredFullscreen. 34th anchor in per-element brightness
|
|
14525
|
+
family, 3rd HTML-element anchor (R593 zoom-level + R594
|
|
14526
|
+
reset). Sibling to R594 — closes the standalone chrome
|
|
14527
|
+
button pair (reset + fullscreen) at full brightness parity.
|
|
14528
|
+
|
|
14529
|
+
Per the R400 family doc, reset + fullscreen are the only
|
|
14530
|
+
two standalone (non-segmented) chrome buttons. Both now
|
|
14531
|
+
share the same 5-axis hover signature pattern:
|
|
14532
|
+
button hover-lift translateY(-1px) R400
|
|
14533
|
+
icon hover-scale 1.0 → 1.10 R353 (full) / R514 (reset)
|
|
14534
|
+
icon stroke-width 2.5 → 2.8 R455 (full) / R453 (reset)
|
|
14535
|
+
icon hover-rotate R576 (full +3°) / R350 (reset -8°)
|
|
14536
|
+
button brightness 1 → 1.15 R595 (full) / R594 (reset)
|
|
14537
|
+
|
|
14538
|
+
Inline transition shorthand replaces the className-based
|
|
14539
|
+
`transition-colors transition-transform` (R557 banked:
|
|
14540
|
+
inline overrides className for transition-driven hover
|
|
14541
|
+
axes). 5 transition properties (bg / color / border /
|
|
14542
|
+
transform / filter) ride one 200ms ease-out beat.
|
|
14543
|
+
|
|
14544
|
+
Active-variant interaction: when isFullscreen=true the
|
|
14545
|
+
className applies cyan bg + cyan text. brightness(1.15)
|
|
14546
|
+
on top makes the active+hovered state read at maximum
|
|
14547
|
+
vivid cyan — the user knows they're poised to EXIT
|
|
14548
|
+
fullscreen with extra visual confirmation.
|
|
14549
|
+
|
|
14550
|
+
data-topo-chrome-fullscreen-brightness attr exposes
|
|
14551
|
+
the gate for tests. */
|
|
11463
14552
|
style={{
|
|
11464
14553
|
borderColor: pal.containerBorder,
|
|
11465
14554
|
...(isFullscreen
|
|
11466
14555
|
? {}
|
|
11467
14556
|
: { background: pal.legendBox.fill, color: pal.legendText }),
|
|
14557
|
+
filter: hoveredFullscreen ? 'brightness(1.15)' : undefined,
|
|
14558
|
+
transition: 'color 200ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, transform 200ms ease-out, filter 200ms ease-out',
|
|
11468
14559
|
}}
|
|
11469
14560
|
aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
|
|
11470
14561
|
title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
|
|
@@ -11490,12 +14581,20 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11490
14581
|
+ R455 fullscreen (this round). transition-[transform,
|
|
11491
14582
|
stroke-width] expands existing transition-transform
|
|
11492
14583
|
so the sw lift eases under R352 scale-110 cadence. */}
|
|
14584
|
+
{/* Round 576 / Loop — fullscreen icon picks up hover-rotate-3.
|
|
14585
|
+
Joins R350 reset / R547 pill × / R549 brand logo at 4th
|
|
14586
|
+
anchor in hover-rotate idiom. Corner arrows rotate 3° on
|
|
14587
|
+
hover — subtle "preparing to transform" gesture (enter
|
|
14588
|
+
or exit fullscreen). Tailwind v4 emits individual rotate
|
|
14589
|
+
property (banked R547) — sits alongside group-hover:
|
|
14590
|
+
scale-110 in independent rotate axis. */}
|
|
11493
14591
|
{isFullscreen ? (
|
|
11494
|
-
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden className="transition-[transform,stroke-width] duration-200 ease-out group-hover:scale-110 group-hover:[stroke-width:2.8] transform-gpu" data-topo-chrome-fullscreen-icon="exit">
|
|
14592
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden className="transition-[transform,stroke-width] duration-200 ease-out group-hover:scale-110 group-hover:rotate-3 group-hover:[stroke-width:2.8] transform-gpu" data-topo-chrome-fullscreen-icon="exit">
|
|
11495
14593
|
<path d="M8 3v4a1 1 0 0 1-1 1H3M21 8h-4a1 1 0 0 1-1-1V3M3 16h4a1 1 0 0 1 1 1v4M16 21v-4a1 1 0 0 1 1-1h4" />
|
|
11496
14594
|
</svg>
|
|
11497
14595
|
) : (
|
|
11498
|
-
|
|
14596
|
+
// R576 sibling — enter variant also picks up rotate-3.
|
|
14597
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden className="transition-[transform,stroke-width] duration-200 ease-out group-hover:scale-110 group-hover:rotate-3 group-hover:[stroke-width:2.8] transform-gpu" data-topo-chrome-fullscreen-icon="enter">
|
|
11499
14598
|
<path d="M3 8V5a2 2 0 0 1 2-2h3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M21 16v3a2 2 0 0 1-2 2h-3" />
|
|
11500
14599
|
</svg>
|
|
11501
14600
|
)}
|