@sleep2agi/agent-network-dashboard 0.5.3-preview.2 → 0.5.3-preview.21
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 +3 -3
- 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/01uyqm4r9qnpt.js +1 -0
- package/.next/static/chunks/04m~pbcsq2ej6.js +4 -0
- package/.next/static/chunks/0m.1mvl~t.avc.css +2 -0
- package/.next/static/chunks/{03a4--7ncekmk.js → 0v4-5tng.uh.7.js} +2 -2
- package/.next/static/chunks/112g_zni~x6_j.js +1 -0
- package/.next/static/chunks/{05zlv0iopwd40.js → 17-8bizggk6cz.js} +1 -1
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/ServersDrawer.tsx +16 -3
- package/app/components/TopoGraph.tsx +441 -27
- package/app/globals.css +91 -6
- package/package.json +1 -1
- package/scripts/p157-servers-copy-test.mjs +95 -0
- package/scripts/topo-alias-glow-test.mjs +121 -0
- package/scripts/topo-avatar-brightness-test.mjs +116 -0
- package/scripts/topo-chip-row-press-test.mjs +93 -0
- package/scripts/topo-chrome-press-fullstrip-test.mjs +105 -0
- package/scripts/topo-chrome-press-scale-test.mjs +100 -0
- package/scripts/topo-filter-pills-press-test.mjs +96 -0
- package/scripts/topo-fleet-density-tier-test.mjs +84 -0
- package/scripts/topo-focus-outline-transition-test.mjs +107 -0
- package/scripts/topo-freshness-chip-fade-test.mjs +105 -0
- package/scripts/topo-hub-idle-breath-test.mjs +104 -0
- package/scripts/topo-hub-recede-test.mjs +124 -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-italic-test.mjs +90 -0
- package/scripts/topo-pinned-aspect-test.mjs +89 -0
- package/scripts/topo-recent-hot-pulse-test.mjs +102 -0
- package/scripts/topo-svg-focus-transition-test.mjs +105 -0
- package/scripts/topo-vendor-activelinks-press-test.mjs +100 -0
- package/.next/static/chunks/0a4hmfvj-81x5.css +0 -2
- package/.next/static/chunks/0a~3lmgl2.3sm.js +0 -4
- package/.next/static/chunks/0w_zjois27-bj.js +0 -1
- package/.next/static/chunks/11vp-~kvgz81f.js +0 -1
- /package/.next/static/{AQG7LOsK4d0T0j2nFonEh → 9EnLX9vOfh-hoRKs1_aTC}/_buildManifest.js +0 -0
- /package/.next/static/{AQG7LOsK4d0T0j2nFonEh → 9EnLX9vOfh-hoRKs1_aTC}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{AQG7LOsK4d0T0j2nFonEh → 9EnLX9vOfh-hoRKs1_aTC}/_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
|
|
@@ -867,12 +886,19 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
867
886
|
// R63 label render + R86 hover-pin keying + #99 tooltip
|
|
868
887
|
// member listing, so all the existing group-box machinery
|
|
869
888
|
// applies uniformly to the orphan bucket too.
|
|
889
|
+
// Round 499 / Loop — surface `isOrphan` flag on the box
|
|
890
|
+
// shape so downstream renderers (label text, future polish)
|
|
891
|
+
// can apply orphan-specific typography (italic) without
|
|
892
|
+
// re-deriving the flag from key === '其他' (key matching
|
|
893
|
+
// would also catch a legitimate "其他" prefix-group, this
|
|
894
|
+
// flag is canonical from the band assignment pass).
|
|
870
895
|
return {
|
|
871
896
|
key: band.isOrphan
|
|
872
897
|
? '其他'
|
|
873
898
|
: band.members.length
|
|
874
899
|
? groupKeys[band.members[0].alias]
|
|
875
900
|
: '',
|
|
901
|
+
isOrphan: !!band.isOrphan,
|
|
876
902
|
count: band.members.length,
|
|
877
903
|
statuses: { working: w, idle: i, offline: o },
|
|
878
904
|
x: minX - GROUP_PAD,
|
|
@@ -977,7 +1003,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
977
1003
|
groupKeys,
|
|
978
1004
|
// #111: group boxes are a grid-layout feature only — radially scattered
|
|
979
1005
|
// ring nodes can't be cleanly boxed. Ring keeps the #83 prefix hue.
|
|
980
|
-
groupBoxes: [] as { key: string; count: number; statuses: { working: number; idle: number; offline: number }; x: number; y: number; w: number; h: number }[],
|
|
1006
|
+
groupBoxes: [] as { key: string; isOrphan?: boolean; count: number; statuses: { working: number; idle: number; offline: number }; x: number; y: number; w: number; h: number }[],
|
|
981
1007
|
// ring fits within VIEWBOX_H by construction (offlineRadius=325 + centre at y=330)
|
|
982
1008
|
gridContentBottom: 0,
|
|
983
1009
|
};
|
|
@@ -1943,8 +1969,24 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1943
1969
|
// doesn't list letter-spacing, so without this the
|
|
1944
1970
|
// hover:tracking-wide would snap. Sibling change on
|
|
1945
1971
|
// the Grid button below.
|
|
1946
|
-
|
|
1947
|
-
|
|
1972
|
+
// Round 492 / Loop — add `active:scale-95` press feedback
|
|
1973
|
+
// alongside R196's `active:bg-cyan-500/25` color-deepen.
|
|
1974
|
+
// Pre-R492 the chrome-strip Ring/Grid buttons had color
|
|
1975
|
+
// tactile (deeper cyan on mouse-down) + R249 chrome-pop
|
|
1976
|
+
// on release, but no transform during the press itself —
|
|
1977
|
+
// the button stayed planted between mouse-down and pop.
|
|
1978
|
+
// Adding `active:scale-95` (5% compression) on the
|
|
1979
|
+
// pressed pseudo-state, with `transform 150ms ease-out`
|
|
1980
|
+
// bundled into the inline transition list, gives haptic-
|
|
1981
|
+
// like push-back feedback. The press-down (down to 95%
|
|
1982
|
+
// scale) eases in over 150ms in sync with the bg/color
|
|
1983
|
+
// deepen; the release auto-springs back to scale-100 via
|
|
1984
|
+
// the same transition, then R249's anet-chrome-pop class
|
|
1985
|
+
// overlays the release-pop. Matching `transform-gpu`
|
|
1986
|
+
// promotes the layer so the scale doesn't trigger
|
|
1987
|
+
// layout/paint thrash. Sibling change on Grid below.
|
|
1988
|
+
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 active:scale-95 transform-gpu ${layout === 'ring' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15'} ${chromePopping === 'layout-ring' ? ' anet-chrome-pop' : ''}`}
|
|
1989
|
+
style={{ transition: 'background-color 150ms ease, color 150ms ease, letter-spacing 200ms ease-out, transform 150ms ease-out' }}
|
|
1948
1990
|
>
|
|
1949
1991
|
Ring
|
|
1950
1992
|
</button>
|
|
@@ -1963,7 +2005,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1963
2005
|
// all chrome buttons.
|
|
1964
2006
|
// R351 sibling — Grid button picks up hover:tracking-wide
|
|
1965
2007
|
// + inline transition spec. Same vocabulary as Ring.
|
|
1966
|
-
|
|
2008
|
+
// R492 sibling — Grid button picks up active:scale-95
|
|
2009
|
+
// press feedback + transform in transition list. Same
|
|
2010
|
+
// vocabulary as Ring above.
|
|
2011
|
+
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 active:scale-95 transform-gpu ${layout === 'grid' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15'} ${chromePopping === 'layout-grid' ? ' anet-chrome-pop' : ''}`}
|
|
1967
2012
|
/* Round 268 / Loop: Grid button's left border (the
|
|
1968
2013
|
internal divider between Ring and Grid) picks up
|
|
1969
2014
|
pal.containerBorder, matching the wrapper change at
|
|
@@ -1973,8 +2018,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1973
2018
|
transition list into the inline spec below so the
|
|
1974
2019
|
letter-spacing tween rides alongside without snapping
|
|
1975
2020
|
the border-color flip — border-color 200ms ease-out
|
|
1976
|
-
keeps R268's theme-toggle smoothness intact.
|
|
1977
|
-
|
|
2021
|
+
keeps R268's theme-toggle smoothness intact.
|
|
2022
|
+
R492 adds `transform 150ms ease-out` so active:scale-95
|
|
2023
|
+
eases smoothly. */
|
|
2024
|
+
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' }}
|
|
1978
2025
|
>
|
|
1979
2026
|
Grid
|
|
1980
2027
|
</button>
|
|
@@ -2091,9 +2138,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2091
2138
|
// to R355 filter pin pill inner-span hover-brighten.
|
|
2092
2139
|
// Hover-brighten family extends from filter pills to
|
|
2093
2140
|
// chip-row chips at the inner-span scope.
|
|
2141
|
+
// Round 494 / Loop — chip-row working chip joins the
|
|
2142
|
+
// active:scale-95 press-feedback family (R492 Ring/Grid +
|
|
2143
|
+
// R493 chrome-strip rest). Gated on the clickable branch
|
|
2144
|
+
// (workingCount > 0) — when the chip is a placeholder
|
|
2145
|
+
// at count=0, scale-95 stays off to match the existing
|
|
2146
|
+
// R398 hover-lift conditional. Composes with hover:-
|
|
2147
|
+
// translate-y-px for the same lift-and-compress
|
|
2148
|
+
// tactile signature R493 brought to reset/fullscreen.
|
|
2094
2149
|
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 ${
|
|
2095
2150
|
workingCount > 0
|
|
2096
|
-
? 'bg-green-500/10 text-green-300 border-green-500/20 hover:bg-green-500/15 hover:border-green-500/30 hover:-translate-y-px'
|
|
2151
|
+
? 'bg-green-500/10 text-green-300 border-green-500/20 hover:bg-green-500/15 hover:border-green-500/30 hover:-translate-y-px active:scale-95'
|
|
2097
2152
|
: 'bg-green-500/10 text-green-300 border-green-500/20'
|
|
2098
2153
|
}`}
|
|
2099
2154
|
data-chip-hover-lift={workingCount > 0 ? 'true' : 'false'}
|
|
@@ -2193,9 +2248,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2193
2248
|
same digit-jitter physics on count crossings). */
|
|
2194
2249
|
// R398: hover translate-y lift on clickable variant — see working chip above.
|
|
2195
2250
|
// R414: `group` parent + inner unit span group-hover-brighten — see working chip above.
|
|
2251
|
+
// R494 sibling — online chip joins the active:scale-95 press
|
|
2252
|
+
// family (gated on onlineNodes.length > 0 clickable branch,
|
|
2253
|
+
// same conditional pattern as the working chip above).
|
|
2196
2254
|
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 ${
|
|
2197
2255
|
onlineNodes.length > 0
|
|
2198
|
-
? 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20 hover:bg-cyan-500/15 hover:border-cyan-500/30 hover:-translate-y-px'
|
|
2256
|
+
? 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20 hover:bg-cyan-500/15 hover:border-cyan-500/30 hover:-translate-y-px active:scale-95'
|
|
2199
2257
|
: 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'
|
|
2200
2258
|
}`}
|
|
2201
2259
|
data-chip-hover-lift={onlineNodes.length > 0 ? 'true' : 'false'}
|
|
@@ -2470,7 +2528,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2470
2528
|
// R355: `group` lets the inner opacity-70 spans (prefix
|
|
2471
2529
|
// `filter:` + count `· N`) brighten to 100 % on pill hover.
|
|
2472
2530
|
// Sibling treatment on group + vendor pills below.
|
|
2473
|
-
|
|
2531
|
+
// R495 — filter pills (3 sibling `group` variants) join the
|
|
2532
|
+
// active:scale-95 press-feedback family. R490's !important
|
|
2533
|
+
// transition list on .anet-topo-chip-focus already covers
|
|
2534
|
+
// transform, so just appending active:scale-95 to the
|
|
2535
|
+
// className wires the press tactile in one token. Compound
|
|
2536
|
+
// with R400-era hover:-translate-y-px gives lift-and-compress.
|
|
2537
|
+
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"
|
|
2474
2538
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
|
|
2475
2539
|
onClick={() => setPinnedStatus(null)}
|
|
2476
2540
|
style={{
|
|
@@ -2534,7 +2598,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2534
2598
|
data-filter-match-count={matchCount}
|
|
2535
2599
|
data-filter-match-aliases={matchAliases.join(',')}
|
|
2536
2600
|
// R355 sibling — `group` parent + group-hover on inner spans.
|
|
2537
|
-
|
|
2601
|
+
// R495 — filter pills (3 sibling `group` variants) join the
|
|
2602
|
+
// active:scale-95 press-feedback family. R490's !important
|
|
2603
|
+
// transition list on .anet-topo-chip-focus already covers
|
|
2604
|
+
// transform, so just appending active:scale-95 to the
|
|
2605
|
+
// className wires the press tactile in one token. Compound
|
|
2606
|
+
// with R400-era hover:-translate-y-px gives lift-and-compress.
|
|
2607
|
+
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"
|
|
2538
2608
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
|
|
2539
2609
|
onClick={() => setPinnedGroup(null)}
|
|
2540
2610
|
style={{
|
|
@@ -2600,7 +2670,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2600
2670
|
data-filter-match-count={matchCount}
|
|
2601
2671
|
data-filter-match-aliases={matchAliases.join(',')}
|
|
2602
2672
|
// R355 sibling — `group` parent + group-hover on inner spans.
|
|
2603
|
-
|
|
2673
|
+
// R495 — filter pills (3 sibling `group` variants) join the
|
|
2674
|
+
// active:scale-95 press-feedback family. R490's !important
|
|
2675
|
+
// transition list on .anet-topo-chip-focus already covers
|
|
2676
|
+
// transform, so just appending active:scale-95 to the
|
|
2677
|
+
// className wires the press tactile in one token. Compound
|
|
2678
|
+
// with R400-era hover:-translate-y-px gives lift-and-compress.
|
|
2679
|
+
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"
|
|
2604
2680
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear vendor filter'}
|
|
2605
2681
|
onClick={() => setPinnedVendor(null)}
|
|
2606
2682
|
style={{
|
|
@@ -2663,7 +2739,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2663
2739
|
data-filter-match-count={link.count}
|
|
2664
2740
|
data-filter-match-aliases={`${link.from},${link.to}`}
|
|
2665
2741
|
data-active-filter-edge-hot={isHot ? 'true' : 'false'}
|
|
2666
|
-
|
|
2742
|
+
// R495 sibling — 4th filter pill (no `group` prefix variant)
|
|
2743
|
+
// joins active:scale-95 press family alongside the 3 group
|
|
2744
|
+
// variants above. Same recipe.
|
|
2745
|
+
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"
|
|
2667
2746
|
title={`${link.from} → ${link.to} (${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ', hot lane · ≥ 10' : ''}) — click to clear`}
|
|
2668
2747
|
onClick={() => setPinnedEdgeKey(null)}
|
|
2669
2748
|
style={{
|
|
@@ -3013,7 +3092,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3013
3092
|
// — sibling to R355 filter-pill prefix/suffix + R414
|
|
3014
3093
|
// chip-row unit brighten. Closes the inner-span
|
|
3015
3094
|
// hover-brighten family at the vendor chip surface.
|
|
3016
|
-
|
|
3095
|
+
// R496 — vendor letter chip joins active:scale-95 press
|
|
3096
|
+
// family. Last vendor-row clickable joining the family
|
|
3097
|
+
// R495 cashed via R490's transition-cascade dividend.
|
|
3098
|
+
// Same compound w/ R401 hover-lift idiom — lift-and-
|
|
3099
|
+
// compress on press, springs back on release.
|
|
3100
|
+
className="group tabular-nums font-medium inline-flex items-baseline gap-0.5 px-1 rounded anet-topo-chip-focus transition-transform duration-200 ease-out transform-gpu hover:-translate-y-px active:scale-95"
|
|
3017
3101
|
data-vendor-letter={v.initial}
|
|
3018
3102
|
data-vendor-letter-count={v.count}
|
|
3019
3103
|
data-vendor-letter-hover-lift="true"
|
|
@@ -3251,9 +3335,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3251
3335
|
data-chip-hover-lift attr exposes the lift surface
|
|
3252
3336
|
state ('true' clickable, 'false' empty) for tests. */
|
|
3253
3337
|
// R414: `group` parent + inner unit span group-hover-brighten — see working chip above.
|
|
3338
|
+
// R496 — active-links chip joins active:scale-95 press
|
|
3339
|
+
// family. Sibling to working+online chips (R494). Gated
|
|
3340
|
+
// on `isInteractive` (flowLinks.length > 0) — same R399
|
|
3341
|
+
// conditional pattern used for hover-lift.
|
|
3254
3342
|
className={`group tabular-nums font-medium hidden sm:inline px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-transform duration-200 ease-out transform-gpu ${
|
|
3255
3343
|
isInteractive
|
|
3256
|
-
? 'bg-gray-500/10 text-gray-400 border-gray-500/20 hover:bg-cyan-500/10 hover:text-cyan-200 hover:border-cyan-500/30 hover:-translate-y-px'
|
|
3344
|
+
? 'bg-gray-500/10 text-gray-400 border-gray-500/20 hover:bg-cyan-500/10 hover:text-cyan-200 hover:border-cyan-500/30 hover:-translate-y-px active:scale-95'
|
|
3257
3345
|
: 'bg-gray-500/10 text-gray-400 border-gray-500/20'
|
|
3258
3346
|
}`}
|
|
3259
3347
|
data-chip-hover-lift={isInteractive ? 'true' : 'false'}
|
|
@@ -3500,6 +3588,36 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3500
3588
|
on the canvas root for non-visual consumers.
|
|
3501
3589
|
Composed from existing onlineNodes / workingCount /
|
|
3502
3590
|
offlineNodes / flowLinks — no new state. */
|
|
3591
|
+
/* Round 502 / Loop — categorical density-tier paired with the
|
|
3592
|
+
R469 numeric counts. data-topo-fleet-density-tier classifies
|
|
3593
|
+
the fleet size into 5 buckets so external consumers (CSS
|
|
3594
|
+
selectors, Playwright probes, future density-conditional
|
|
3595
|
+
polish gates like R109 dense-label collapse at 16+ nodes)
|
|
3596
|
+
can branch on a stable tier name without re-deriving the
|
|
3597
|
+
threshold logic from the raw numeric. Buckets:
|
|
3598
|
+
'empty' — onlineNodes.length === 0
|
|
3599
|
+
'sparse' — 1-3 nodes
|
|
3600
|
+
'normal' — 4-15 nodes
|
|
3601
|
+
'dense' — 16-30 nodes (matches R109 collapse gate)
|
|
3602
|
+
'very-dense' — 31+ nodes
|
|
3603
|
+
Picks the gate boundaries that already drive CONDITIONAL
|
|
3604
|
+
RENDER decisions elsewhere (R109 denseLayout = >16, R110
|
|
3605
|
+
plain-text fallback) so the tier name is semantically
|
|
3606
|
+
aligned with the visual mode the canvas already switches
|
|
3607
|
+
to. Composed from existing onlineNodes — no new state.
|
|
3608
|
+
12th attr in the canvas state surface set (R462/R466/R467/
|
|
3609
|
+
R469×4/R471×2/R487/R488/R502). 12 attrs covers: build
|
|
3610
|
+
identity, transient/sticky inspection modes, fleet split
|
|
3611
|
+
numerics, fleet density tier, canvas layout/theme, canvas
|
|
3612
|
+
zoom, hover identity. A test harness can snapshot the
|
|
3613
|
+
full canvas state with 12 getAttribute calls. */
|
|
3614
|
+
data-topo-fleet-density-tier={
|
|
3615
|
+
onlineNodes.length === 0 ? 'empty' :
|
|
3616
|
+
onlineNodes.length <= 3 ? 'sparse' :
|
|
3617
|
+
onlineNodes.length <= 15 ? 'normal' :
|
|
3618
|
+
onlineNodes.length <= 30 ? 'dense' :
|
|
3619
|
+
'very-dense'
|
|
3620
|
+
}
|
|
3503
3621
|
data-topo-online-count={onlineNodes.length}
|
|
3504
3622
|
data-topo-working-count={workingCount}
|
|
3505
3623
|
data-topo-offline-count={offlineNodes.length}
|
|
@@ -3565,6 +3683,41 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3565
3683
|
categorical) — separate dedicated attrs if/when needed.
|
|
3566
3684
|
Root svg attribute set now 11 attrs total. */
|
|
3567
3685
|
data-topo-hovered-alias={hoveredAlias ?? ''}
|
|
3686
|
+
/* Round 504 / Loop — categorical pin-aspect attr paired with
|
|
3687
|
+
R467 any-pinned boolean and R488 hovered-alias identity.
|
|
3688
|
+
Pre-R504 the canvas state surface set told tests WHETHER
|
|
3689
|
+
any pin was active (R467 boolean) but tests had to enumerate
|
|
3690
|
+
4 individual state vars to determine WHICH pin axis fired:
|
|
3691
|
+
pinnedStatus legend-row status filter
|
|
3692
|
+
pinnedGroup prefix-cluster lock
|
|
3693
|
+
pinnedVendor vendor-chip filter
|
|
3694
|
+
pinnedEdgeKey edge-focus
|
|
3695
|
+
R504 surfaces the active aspect as a single categorical
|
|
3696
|
+
attribute: data-topo-pinned-aspect ∈
|
|
3697
|
+
'none' no pin active
|
|
3698
|
+
'status' pinnedStatus only
|
|
3699
|
+
'group' pinnedGroup only
|
|
3700
|
+
'vendor' pinnedVendor only
|
|
3701
|
+
'edge' pinnedEdgeKey only
|
|
3702
|
+
'multi' 2 or more pins active simultaneously
|
|
3703
|
+
('multi' covers cross-cutting filters — e.g. user pins
|
|
3704
|
+
status='working' AND vendor='claude' simultaneously to
|
|
3705
|
+
narrow the canvas. Each pin axis is independently
|
|
3706
|
+
dismissable via Esc / individual chip click, so multi
|
|
3707
|
+
states are reachable and worth surfacing as a distinct
|
|
3708
|
+
tier.)
|
|
3709
|
+
13th attr in the canvas state surface set after R502.
|
|
3710
|
+
Composed from 4 existing state vars — no new state. */
|
|
3711
|
+
data-topo-pinned-aspect={(() => {
|
|
3712
|
+
const aspects: string[] = [];
|
|
3713
|
+
if (pinnedStatus) aspects.push('status');
|
|
3714
|
+
if (pinnedGroup) aspects.push('group');
|
|
3715
|
+
if (pinnedVendor) aspects.push('vendor');
|
|
3716
|
+
if (pinnedEdgeKey) aspects.push('edge');
|
|
3717
|
+
if (aspects.length === 0) return 'none';
|
|
3718
|
+
if (aspects.length === 1) return aspects[0];
|
|
3719
|
+
return 'multi';
|
|
3720
|
+
})()}
|
|
3568
3721
|
/* Round 466 / Loop — aggregate hover signal on the root SVG.
|
|
3569
3722
|
Exposes a single boolean `data-topo-any-hover` that
|
|
3570
3723
|
reflects whether ANY hover state in the topology is
|
|
@@ -4494,12 +4647,75 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4494
4647
|
// pinned → fill 0.08 / 0.13, stroke 3 px (locked)
|
|
4495
4648
|
// hovered → fill 0.05 / 0.09, stroke 2 px (inspecting)
|
|
4496
4649
|
// idle → fill 0.025 / 0.045, stroke 1.5 px dashed
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4650
|
+
/* Round 506 / Loop — category-differentiation family
|
|
4651
|
+
3rd anchor. Orphan band rest-state fillOpacity drops
|
|
4652
|
+
slightly below prefix-group rest (0.025/0.045 →
|
|
4653
|
+
0.015/0.028). Adds a 3rd independent paint
|
|
4654
|
+
differentiator to the orphan visual signature:
|
|
4655
|
+
R499 fontStyle: italic (label text)
|
|
4656
|
+
R503 '3 6' dash pattern (rect stroke)
|
|
4657
|
+
R506 lower fillOpacity (rect fill) ← this round
|
|
4658
|
+
Three independent channels (typography + stroke
|
|
4659
|
+
pattern + fill density) collectively encode the
|
|
4660
|
+
catchall semantic at rest. Pin and hover branches
|
|
4661
|
+
UNCHANGED (still 0.05/0.09 hover, 0.08/0.13 pin) —
|
|
4662
|
+
orphan box gets full visual emphasis on inspection
|
|
4663
|
+
identical to prefix groups; the differentiation
|
|
4664
|
+
lives ONLY in the unsolicited rest state. The
|
|
4665
|
+
~40% drop (0.045 → 0.028 cyber, 0.025 → 0.015
|
|
4666
|
+
light) is subtle enough that the orphan box stays
|
|
4667
|
+
visible at rest, just quieter — matches the
|
|
4668
|
+
"misc bucket, less attention-deserving" semantic
|
|
4669
|
+
without losing the visual anchor.
|
|
4670
|
+
Pure paint axis; bbox unchanged; R51 SVG sentinel
|
|
4671
|
+
safety untouched (overlap-test gates to g[data-
|
|
4672
|
+
node], cluster rect invisible to it).
|
|
4673
|
+
data-group-box-fill-opacity attr surfaces the
|
|
4674
|
+
resolved value for tests. */
|
|
4675
|
+
fillOpacity={
|
|
4676
|
+
isPinned ? (isLight ? 0.08 : 0.13)
|
|
4677
|
+
: isHovered ? (isLight ? 0.05 : 0.09)
|
|
4678
|
+
: box.isOrphan ? (isLight ? 0.015 : 0.028)
|
|
4679
|
+
: (isLight ? 0.025 : 0.045)
|
|
4680
|
+
}
|
|
4681
|
+
data-group-box-fill-opacity={
|
|
4682
|
+
isPinned ? (isLight ? 0.08 : 0.13)
|
|
4683
|
+
: isHovered ? (isLight ? 0.05 : 0.09)
|
|
4684
|
+
: box.isOrphan ? (isLight ? 0.015 : 0.028)
|
|
4685
|
+
: (isLight ? 0.025 : 0.045)
|
|
4686
|
+
}
|
|
4500
4687
|
stroke={(isPinned || isHovered) ? pal.legendAccent : pal.ringStroke}
|
|
4501
4688
|
strokeWidth={isPinned ? 3 : isHovered ? 2 : 1.5}
|
|
4502
|
-
|
|
4689
|
+
/* Round 503 / Loop — category-differentiation family
|
|
4690
|
+
2nd anchor (R499 = orphan label italic, 1st anchor).
|
|
4691
|
+
Orphan band rest-state strokeDasharray switches from
|
|
4692
|
+
'6 6' (prefix-group default) to '3 6' (tighter
|
|
4693
|
+
dashes). Pre-R503 the rect dash pattern was uniform
|
|
4694
|
+
across all bands; combined with R499's italic label,
|
|
4695
|
+
the orphan box now has TWO independent paint/
|
|
4696
|
+
typography differentiators at rest:
|
|
4697
|
+
R499 fontStyle: italic (label text)
|
|
4698
|
+
R503 '3 6' dash pattern (rect stroke) ← this round
|
|
4699
|
+
The R85 marching-ants animation continues to work
|
|
4700
|
+
with the new dash size (uses --march-dur custom
|
|
4701
|
+
property, dash-length-agnostic) — orphan's ants
|
|
4702
|
+
just have a different visual rhythm than prefix-
|
|
4703
|
+
group ants, reinforcing the catchall semantic.
|
|
4704
|
+
Pinned/hovered orphan still gets 'none' (solid
|
|
4705
|
+
stroke) so the hover/pin affordance is preserved
|
|
4706
|
+
— the differentiation lives ONLY in the rest
|
|
4707
|
+
state, never blocking inspection.
|
|
4708
|
+
Pure paint axis; no geometry change; bbox unchanged
|
|
4709
|
+
(strokeDasharray is paint-only). R51 SVG sentinel
|
|
4710
|
+
safety untouched (overlap-test gates to g[data-
|
|
4711
|
+
node], this cluster rect is invisible to it).
|
|
4712
|
+
data-group-box-orphan attr surfaces the gate for
|
|
4713
|
+
tests + future polish references. */
|
|
4714
|
+
strokeDasharray={
|
|
4715
|
+
(isPinned || isHovered) ? 'none' :
|
|
4716
|
+
box.isOrphan ? '3 6' : '6 6'
|
|
4717
|
+
}
|
|
4718
|
+
data-group-box-orphan={box.isOrphan ? 'true' : 'false'}
|
|
4503
4719
|
/* Round 380 / Loop: cluster box stroke gets round
|
|
4504
4720
|
linecap + round linejoin. Sibling SVG stroke-
|
|
4505
4721
|
softening polish to R378 flow-rail linecap + R379
|
|
@@ -4844,16 +5060,39 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4844
5060
|
ease-out' alongside the existing fill/ls/fw/opacity
|
|
4845
5061
|
200ms tweens. */
|
|
4846
5062
|
data-group-label-glow={isPinned ? 'true' : 'false'}
|
|
5063
|
+
/* Round 499 / Loop — orphan band "其他" label gets
|
|
5064
|
+
fontStyle: italic to visually distinguish the
|
|
5065
|
+
catchall from real prefix-group bands. Pre-R499
|
|
5066
|
+
the orphan box label rendered identically to
|
|
5067
|
+
prefix-group labels (Hero D fontSize=9, fw=700,
|
|
5068
|
+
opacity 0.55 rest), so users had to read the
|
|
5069
|
+
literal text "其他" to identify the catchall. R499
|
|
5070
|
+
adds a pure-typography differentiation: italic
|
|
5071
|
+
signals "this is the misc bucket, not a real
|
|
5072
|
+
named group" while preserving full opacity
|
|
5073
|
+
affordance on hover/pin — the orphan box stays
|
|
5074
|
+
equally inspectable, just typographically marked
|
|
5075
|
+
as a different category. No geometry change
|
|
5076
|
+
(italic shifts glyph slant within the same bbox),
|
|
5077
|
+
no opacity loss, no behavior change. Sibling to
|
|
5078
|
+
R432 letter-spacing 3-tier + R457 pin fw-lift +
|
|
5079
|
+
R479 pin drop-shadow at the group-label scope.
|
|
5080
|
+
Falls under 配色 / 节点视觉 themes per the prompt;
|
|
5081
|
+
advances the "信息密度" axis by encoding
|
|
5082
|
+
category-distinction into a single typography
|
|
5083
|
+
channel without adding visual chrome. */
|
|
4847
5084
|
style={{
|
|
4848
5085
|
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',
|
|
4849
5086
|
letterSpacing: isPinned ? '0.5px' :
|
|
4850
5087
|
isHovered ? '0.25px' : '0px',
|
|
5088
|
+
fontStyle: box.isOrphan ? 'italic' : undefined,
|
|
4851
5089
|
filter: isPinned
|
|
4852
5090
|
? `drop-shadow(0 0 3px ${pal.legendAccent}80)`
|
|
4853
5091
|
: undefined,
|
|
4854
5092
|
}}
|
|
4855
5093
|
data-group-label={box.key}
|
|
4856
5094
|
data-group-label-pinned={isPinned ? 'true' : 'false'}
|
|
5095
|
+
data-group-label-orphan={box.isOrphan ? 'true' : 'false'}
|
|
4857
5096
|
>
|
|
4858
5097
|
{box.key}
|
|
4859
5098
|
{/* Round 19 / Loop: member-count chip. Inline tspan stays
|
|
@@ -6468,11 +6707,45 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6468
6707
|
so the glow eases under the same cadence as the
|
|
6469
6708
|
scale + fw + fill axes. */
|
|
6470
6709
|
data-topo-hub-working-count-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
6710
|
+
/* Round 507 / Loop — focal recede. When ANY non-hub
|
|
6711
|
+
canvas surface is hovered (a node / an edge / a
|
|
6712
|
+
group label / a legend row / a vendor chip), the
|
|
6713
|
+
hub-center workingCount digit fades to 0.85 opacity,
|
|
6714
|
+
signaling "you're inspecting elsewhere, hub recedes
|
|
6715
|
+
to background." When the user un-hovers (or hovers
|
|
6716
|
+
the hub itself), opacity returns to 1.0. Pure paint
|
|
6717
|
+
polish at the canvas's most prominent focal point.
|
|
6718
|
+
Hits 信息密度 + 动效 themes — the hub digit gives
|
|
6719
|
+
way visually to the surface under inspection,
|
|
6720
|
+
reinforcing the "this is the focal point right now"
|
|
6721
|
+
gesture without requiring users to track which
|
|
6722
|
+
surface holds attention.
|
|
6723
|
+
Gate excludes hoveredHub specifically: hovering the
|
|
6724
|
+
hub itself should LIFT the digit (R425 fw bump +
|
|
6725
|
+
R476 glow + R209 scale 1.08) — the existing hover-
|
|
6726
|
+
on-hub signature is intact; only inspection
|
|
6727
|
+
ELSEWHERE recedes the hub.
|
|
6728
|
+
Composed from existing hoveredAlias / hoveredEdge-
|
|
6729
|
+
Key / hoveredGroupLabel / hoveredStatus / hovered-
|
|
6730
|
+
Vendor — no new state. 300ms ease-out opacity
|
|
6731
|
+
transition already in the style list (existing R213
|
|
6732
|
+
transition spec), so the fade rides on existing
|
|
6733
|
+
infrastructure.
|
|
6734
|
+
data-topo-hub-recede attr surfaces the gate state
|
|
6735
|
+
for tests. */
|
|
6736
|
+
data-topo-hub-recede={
|
|
6737
|
+
(hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
6738
|
+
hoveredStatus || hoveredVendor) && !hoveredHub ? 'true' : 'false'
|
|
6739
|
+
}
|
|
6471
6740
|
style={{
|
|
6472
6741
|
pointerEvents: 'none',
|
|
6473
6742
|
transform: !reducedMotion && hoveredHub ? 'scale(1.08)' : 'scale(1)',
|
|
6474
6743
|
transformBox: 'fill-box',
|
|
6475
6744
|
transformOrigin: 'center',
|
|
6745
|
+
opacity: (hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
6746
|
+
hoveredStatus || hoveredVendor) && !hoveredHub
|
|
6747
|
+
? 0.85
|
|
6748
|
+
: 1,
|
|
6476
6749
|
filter: !reducedMotion && hoveredHub
|
|
6477
6750
|
? (isLight
|
|
6478
6751
|
? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.6))'
|
|
@@ -6482,7 +6755,9 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6482
6755
|
bump 700 → 800 eases under the same cadence as
|
|
6483
6756
|
R209 scale + R253 fill + R213 opacity.
|
|
6484
6757
|
R476: filter 200ms appended so the new drop-
|
|
6485
|
-
shadow glow eases at the same cadence.
|
|
6758
|
+
shadow glow eases at the same cadence.
|
|
6759
|
+
R507: opacity 300ms (existing in list) covers
|
|
6760
|
+
the new focal-recede fade. */
|
|
6486
6761
|
transition: 'transform 200ms ease-out, opacity 300ms ease-out, fill 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out',
|
|
6487
6762
|
fontVariantNumeric: 'tabular-nums',
|
|
6488
6763
|
}}
|
|
@@ -6537,11 +6812,36 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6537
6812
|
data-topo-hub-highlight-visible={workingCount > 0 ? 'false' : 'true'}
|
|
6538
6813
|
data-topo-hub-highlight-radius="5.5"
|
|
6539
6814
|
data-topo-hub-highlight-opacity={workingCount > 0 ? 0 : 0.95}
|
|
6815
|
+
data-topo-hub-highlight-breath={!reducedMotion && workingCount === 0 ? 'true' : 'false'}
|
|
6540
6816
|
style={{
|
|
6541
6817
|
pointerEvents: 'none',
|
|
6542
6818
|
transition: 'opacity 300ms ease-out',
|
|
6543
6819
|
}}
|
|
6544
|
-
|
|
6820
|
+
>
|
|
6821
|
+
{/* Round 497 / Loop — idle-state breath (呼吸感 theme pivot
|
|
6822
|
+
from the R492-R496 press-family arc). Pre-R497 the hub
|
|
6823
|
+
idle highlight read as a static dim disc — present but
|
|
6824
|
+
motionless, visually mute. R497 adds a 4s opacity breath
|
|
6825
|
+
(0.85 ↔ 1.0 ↔ 0.85) so the hub reads "alive but quiet"
|
|
6826
|
+
instead of "frozen", giving the empty-fleet state a
|
|
6827
|
+
subtle living signature.
|
|
6828
|
+
Gates:
|
|
6829
|
+
- !reducedMotion (R29 a11y blanket) — reducedMotion
|
|
6830
|
+
users see static 0.95 disc, no animate
|
|
6831
|
+
- workingCount === 0 — when fleet is busy, the
|
|
6832
|
+
highlight is invisible (opacity=0) so the animate
|
|
6833
|
+
would waste paint cycles. Gating saves work.
|
|
6834
|
+
SMIL <animate> overrides the static opacity={0.95}
|
|
6835
|
+
during its run; falls back to 0.95 when reducedMotion
|
|
6836
|
+
flips on (the animate node simply doesn't render).
|
|
6837
|
+
4s cycle is long enough to feel like ambient breath
|
|
6838
|
+
rather than a pulse, matching the "quiet" semantic.
|
|
6839
|
+
data-topo-hub-highlight-breath attr exposes the
|
|
6840
|
+
resolved gate state for tests. */}
|
|
6841
|
+
{!reducedMotion && workingCount === 0 && (
|
|
6842
|
+
<animate attributeName="opacity" values="0.85;1;0.85" dur="4s" repeatCount="indefinite" />
|
|
6843
|
+
)}
|
|
6844
|
+
</circle>
|
|
6545
6845
|
{/* R115 / Loop: hover hint ring. Stroke-only circle at r=14
|
|
6546
6846
|
that fades in when the hub is hovered — the same idea
|
|
6547
6847
|
R44 used for node avatars (group-hover stroke). r=14
|
|
@@ -7526,6 +7826,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7526
7826
|
const internByAlias = /书生|书小生|intern/i.test(session.alias);
|
|
7527
7827
|
|
|
7528
7828
|
if (isIntern || internByAlias || vendor.logo) {
|
|
7829
|
+
/* Round 501 / Loop — vendor avatar inside node circles
|
|
7830
|
+
gains a hover-gated brightness lift. Pre-R501 the
|
|
7831
|
+
avatar <image> was the only per-node surface with
|
|
7832
|
+
NO hover treatment: R26 lifted the card, R242 tinted
|
|
7833
|
+
the card stroke, R427 spread the alias letter-
|
|
7834
|
+
spacing, R500 added the alias drop-shadow, R208
|
|
7835
|
+
lifted the runtime badge ring, R443 thickened
|
|
7836
|
+
the badge icon stroke, R177 brightened the
|
|
7837
|
+
halo — but the most visually-prominent element
|
|
7838
|
+
(the vendor logo / 书生 coin centred in each node)
|
|
7839
|
+
stayed paint-static. R501 closes the per-node
|
|
7840
|
+
hover-affordance arc by adding a 15% brightness
|
|
7841
|
+
lift on hover.
|
|
7842
|
+
Implementation: CSS filter: brightness(1.15)
|
|
7843
|
+
when hoveredAlias === session.alias. Pure paint
|
|
7844
|
+
axis on the <image> element — no geometry change,
|
|
7845
|
+
no bbox shift. Modern-browser supported (Chrome 64+
|
|
7846
|
+
/ FF 56+ / Safari 9.1+).
|
|
7847
|
+
Hits 节点视觉 theme. data-node-avatar-hovered
|
|
7848
|
+
attr surfaces the gate for tests.
|
|
7849
|
+
Gated on !reducedMotion as a courtesy (brightness
|
|
7850
|
+
transition < ~50ms still feels instant; the gate
|
|
7851
|
+
avoids the transition cycle for a11y users). */
|
|
7852
|
+
const isAvatarHovered = !reducedMotion && hoveredAlias === session.alias;
|
|
7529
7853
|
return (
|
|
7530
7854
|
<image
|
|
7531
7855
|
href={vendor.logo ?? '/intern_avatar.png'}
|
|
@@ -7534,6 +7858,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7534
7858
|
width={size}
|
|
7535
7859
|
height={size}
|
|
7536
7860
|
preserveAspectRatio="xMidYMid meet"
|
|
7861
|
+
data-node-avatar={session.alias}
|
|
7862
|
+
data-node-avatar-hovered={isAvatarHovered ? 'true' : 'false'}
|
|
7863
|
+
style={{
|
|
7864
|
+
filter: isAvatarHovered ? 'brightness(1.15)' : undefined,
|
|
7865
|
+
transition: 'filter 200ms ease-out',
|
|
7866
|
+
}}
|
|
7537
7867
|
/>
|
|
7538
7868
|
);
|
|
7539
7869
|
}
|
|
@@ -7957,6 +8287,43 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7957
8287
|
R211 fill 300ms + R305 letter-spacing 200ms
|
|
7958
8288
|
transition list preserved; only the
|
|
7959
8289
|
conditional gets a middle case. */}
|
|
8290
|
+
{/* Round 500 / Loop — milestone round, opens
|
|
8291
|
+
per-node alias drop-shadow polish. Extends the
|
|
8292
|
+
R476-R481 drop-shadow visual-polish family to a
|
|
8293
|
+
7th anchor: hovered alias text gains a soft
|
|
8294
|
+
status-coloured text-glow. Pre-R500 hover on
|
|
8295
|
+
a node triggered card-lift (R26 translateY) +
|
|
8296
|
+
card-stroke (R242 tint) + alias letter-spacing
|
|
8297
|
+
(R427 0.3px tier) but the alias TEXT itself had
|
|
8298
|
+
no paint-axis cue beyond fill (R211). R500 adds
|
|
8299
|
+
a drop-shadow on the text glyph itself, so the
|
|
8300
|
+
identity glyph itself lights up under attention
|
|
8301
|
+
— matching the R476 idiom (hub-digit emerald
|
|
8302
|
+
glow on hover) at the per-node identity scope.
|
|
8303
|
+
2px blur radius at 50% alpha — subtler than the
|
|
8304
|
+
R476 hub-digit (3px at 60%) because the alias
|
|
8305
|
+
text is smaller and more numerous (1 per node)
|
|
8306
|
+
so an aggressive glow would multiply into
|
|
8307
|
+
visual noise. Status-coloured (status.text) so
|
|
8308
|
+
the glow inherits the node's working/idle/
|
|
8309
|
+
offline palette — green/cyan/gray respectively.
|
|
8310
|
+
Drop-shadow visual-polish family — 7 anchors:
|
|
8311
|
+
R476 hub digit hover-gated emerald
|
|
8312
|
+
R477 legend pin-ring pin-gated row.fill
|
|
8313
|
+
R478 recent-row pip fresh-gated cyan
|
|
8314
|
+
R479 group-label text pin-gated cyan
|
|
8315
|
+
R480 hot-lane edge hot-gated amber
|
|
8316
|
+
R481 zoom-state minimap zoom-gated cyan
|
|
8317
|
+
R500 node alias text hover-gated status.text ← this round
|
|
8318
|
+
Filter is paint-only; bbox unchanged; overlap-
|
|
8319
|
+
test invariants hold (R51 selector gated to
|
|
8320
|
+
g[data-node] descendants with strokeWidth
|
|
8321
|
+
sentinels; text element doesn't carry stroke).
|
|
8322
|
+
transition list extends to include 'filter
|
|
8323
|
+
200ms ease-out' alongside the existing fill
|
|
8324
|
+
300ms + letter-spacing 200ms tweens.
|
|
8325
|
+
data-node-alias-glow attr surfaces the hover
|
|
8326
|
+
gate for tests. */}
|
|
7960
8327
|
<text
|
|
7961
8328
|
x="0" y="1" textAnchor="middle"
|
|
7962
8329
|
fill={status.text}
|
|
@@ -7964,11 +8331,15 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7964
8331
|
data-node-alias-text={session.alias}
|
|
7965
8332
|
data-node-alias-chat-target={chatAlias === session.alias ? 'true' : 'false'}
|
|
7966
8333
|
data-node-alias-hovered={hoveredAlias === session.alias ? 'true' : 'false'}
|
|
8334
|
+
data-node-alias-glow={!reducedMotion && hoveredAlias === session.alias ? 'true' : 'false'}
|
|
7967
8335
|
style={{
|
|
7968
|
-
transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out',
|
|
8336
|
+
transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out, filter 200ms ease-out',
|
|
7969
8337
|
letterSpacing:
|
|
7970
8338
|
chatAlias === session.alias ? '0.5px' :
|
|
7971
8339
|
hoveredAlias === session.alias ? '0.3px' : '0px',
|
|
8340
|
+
filter: !reducedMotion && hoveredAlias === session.alias
|
|
8341
|
+
? `drop-shadow(0 0 2px ${status.text}80)`
|
|
8342
|
+
: undefined,
|
|
7972
8343
|
}}
|
|
7973
8344
|
>
|
|
7974
8345
|
{truncate(session.alias, fullMax)}
|
|
@@ -9378,12 +9749,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9378
9749
|
under the same R320 fill cadence. data-
|
|
9379
9750
|
recent-row-count-pinned attr exposes the
|
|
9380
9751
|
pin gate for tests. */}
|
|
9752
|
+
{/* Round 498 / Loop — hot-count subtle pulse. Pre-
|
|
9753
|
+
R498 the hot row count signaled via color (R127
|
|
9754
|
+
amber fill) + weight (R320 fw-700) + (R445 pin
|
|
9755
|
+
lift) but stayed visually motionless. R498 adds
|
|
9756
|
+
a 3s opacity breath (0.85↔1.0) on the digit when
|
|
9757
|
+
isHot && !reducedMotion — gentle "alive" signal
|
|
9758
|
+
on the lane carrying ≥ 10 messages, drawing
|
|
9759
|
+
glance without becoming noisy. Sibling of R497
|
|
9760
|
+
hub-idle-breath in the 呼吸感 theme arc; same
|
|
9761
|
+
0.85↔1.0 amplitude. Class adds an animation-
|
|
9762
|
+
only paint axis; no layout / bbox change. R29
|
|
9763
|
+
blanket also catches `animation-duration` for
|
|
9764
|
+
reducedMotion users, but the component-side
|
|
9765
|
+
gate makes the intent explicit and avoids
|
|
9766
|
+
a node tree thrash for those users (className
|
|
9767
|
+
stays absent rather than present-but-paused). */}
|
|
9381
9768
|
<tspan
|
|
9382
9769
|
fill={isHot ? hotStroke : undefined}
|
|
9383
9770
|
fontWeight={(isHot || isRowPinned) ? '700' : '600'}
|
|
9771
|
+
className={isHot && !reducedMotion ? 'anet-recent-hot-pulse' : undefined}
|
|
9384
9772
|
data-recent-row-count
|
|
9385
9773
|
data-recent-row-count-pinned={isRowPinned ? 'true' : 'false'}
|
|
9386
9774
|
data-recent-row-count-font-weight={(isHot || isRowPinned) ? '700' : '600'}
|
|
9775
|
+
data-recent-row-count-hot-pulse={isHot && !reducedMotion ? 'true' : 'false'}
|
|
9387
9776
|
{...(isHot ? { 'data-recent-row-count-hot': 'true' } : {})}
|
|
9388
9777
|
style={{
|
|
9389
9778
|
transition: 'fill 300ms ease-out, font-weight 200ms ease-out',
|
|
@@ -10967,7 +11356,15 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10967
11356
|
/ fullscreen) preview their active state on hover.
|
|
10968
11357
|
Pure actions (zoom -/+, reset) stay white — they
|
|
10969
11358
|
aren't toggles, have no active state to preview. */
|
|
10970
|
-
|
|
11359
|
+
// Round 493 / Loop — extends R492 chrome-strip press-feedback
|
|
11360
|
+
// family to nodeSize S/M/L buttons. Adds active:scale-95
|
|
11361
|
+
// alongside the existing color-deepen (R196) + chrome-pop
|
|
11362
|
+
// (R249). transition-transform + duration-200 + ease-out
|
|
11363
|
+
// + transform-gpu added since this className previously had
|
|
11364
|
+
// transition-colors only — without the transform transition,
|
|
11365
|
+
// active:scale-95 would hard-cut. transform-gpu promotes the
|
|
11366
|
+
// layer so scale doesn't trigger paint thrash.
|
|
11367
|
+
className={`px-2 py-1 transition-colors transition-transform 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 ${idx > 0 ? 'border-l' : ''} ${nodeScale === v ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'hover:bg-cyan-500/5 active:bg-cyan-500/15'}${chromePopping === popKey ? ' anet-chrome-pop' : ''}`}
|
|
10971
11368
|
style={{ color: nodeScale === v ? undefined : pal.legendText, borderColor: pal.containerBorder }}
|
|
10972
11369
|
>
|
|
10973
11370
|
{lbl}
|
|
@@ -11009,7 +11406,11 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11009
11406
|
// → white/10) so mouse-down has a tactile dim before the
|
|
11010
11407
|
// R186 icon pop fires on release.
|
|
11011
11408
|
// R352: `group` lets the inner svg respond via group-hover.
|
|
11012
|
-
|
|
11409
|
+
// R493 — zoom +/− buttons join the chrome-strip active:scale-95
|
|
11410
|
+
// press-feedback family (R492 + nodeSize above). transition-
|
|
11411
|
+
// transform + duration-200 + ease-out + transform-gpu added
|
|
11412
|
+
// since the className had only transition-colors.
|
|
11413
|
+
className="group px-2 py-1 hover:bg-white/5 active:bg-white/10 transition-colors transition-transform 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"
|
|
11013
11414
|
style={{ color: pal.legendText }}
|
|
11014
11415
|
aria-label="Zoom out"
|
|
11015
11416
|
title="Zoom out (−)"
|
|
@@ -11149,7 +11550,11 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11149
11550
|
data-topo-chrome-zoom-in-popping={chromePopping === 'zoom-in' ? 'true' : 'false'}
|
|
11150
11551
|
// R196: press-state (mirror of zoom-out above).
|
|
11151
11552
|
// R352: `group` lets the inner svg respond via group-hover.
|
|
11152
|
-
|
|
11553
|
+
// R493 — zoom +/− buttons join the chrome-strip active:scale-95
|
|
11554
|
+
// press-feedback family (R492 + nodeSize above). transition-
|
|
11555
|
+
// transform + duration-200 + ease-out + transform-gpu added
|
|
11556
|
+
// since the className had only transition-colors.
|
|
11557
|
+
className="group px-2 py-1 hover:bg-white/5 active:bg-white/10 transition-colors transition-transform 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"
|
|
11153
11558
|
style={{ color: pal.legendText }}
|
|
11154
11559
|
aria-label="Zoom in"
|
|
11155
11560
|
title="Zoom in (+)"
|
|
@@ -11201,7 +11606,14 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11201
11606
|
Every standalone interactive HTML surface in TopoGraph
|
|
11202
11607
|
now lifts on hover. data-topo-chrome-reset-hover-lift
|
|
11203
11608
|
attr surfaces the lift for tests. */
|
|
11204
|
-
|
|
11609
|
+
// R493 — reset button joins the chrome-strip active:scale-95
|
|
11610
|
+
// press-feedback family. The button already has transition-
|
|
11611
|
+
// transform + transform-gpu (R350 reset spin + R400 hover lift),
|
|
11612
|
+
// so just appending active:scale-95 plugs straight in. Compound
|
|
11613
|
+
// active state during press = hover-lift (-1px) + scale-95
|
|
11614
|
+
// composes as translateY(-1px) scale(0.95) — lift-and-compress
|
|
11615
|
+
// for tactile click feel.
|
|
11616
|
+
className="p-1.5 rounded-md border hover:bg-white/5 active:bg-white/10 hover:-translate-y-px active:scale-95 transition-colors transition-transform duration-200 ease-out transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60"
|
|
11205
11617
|
data-topo-chrome-reset-hover-lift="true"
|
|
11206
11618
|
style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder, color: pal.legendText }}
|
|
11207
11619
|
aria-label="Reset view"
|
|
@@ -11294,7 +11706,9 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11294
11706
|
// fullscreen now all carry an icon-level hover gesture in
|
|
11295
11707
|
// addition to the bg hover).
|
|
11296
11708
|
// R400: hover translateY(-1px) lift — see reset button above for family doc.
|
|
11297
|
-
|
|
11709
|
+
// R493 — fullscreen joins active:scale-95 press family (same as
|
|
11710
|
+
// reset above: lift-and-compress compound transform on press).
|
|
11711
|
+
className={`group p-1.5 rounded-md border hover:-translate-y-px active:scale-95 transition-colors transition-transform duration-200 ease-out transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 ${
|
|
11298
11712
|
isFullscreen
|
|
11299
11713
|
? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25'
|
|
11300
11714
|
: 'hover:bg-cyan-500/5 active:bg-cyan-500/15'
|