@nelsonlongxiang/dsh-open-a2a-net 0.6.41 → 0.6.42

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.
Files changed (61) hide show
  1. package/README.md +1 -1
  2. package/README_zh.md +1 -1
  3. package/assets/stageDist/assets/index-Bj7S2pA1.css +1 -1
  4. package/assets/stageDist/assets/index-C-28k_o5.js +974 -974
  5. package/assets/stageDist/index.html +12 -12
  6. package/lib/client.js +64 -64
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +57 -9
  9. package/lib/origin-claim.js +4 -0
  10. package/lib/skill/SKILL.md +117 -117
  11. package/lib/skill/references/collaboration.md +428 -428
  12. package/lib/skill/references/discovery.md +78 -78
  13. package/lib/skill/references/intake.md +107 -107
  14. package/lib/skill/references/membership.md +73 -73
  15. package/lib/skill/references/pitfalls.md +51 -51
  16. package/lib/skill/references/router.md +96 -96
  17. package/lib/skill/references/routing.md +65 -65
  18. package/lib/types/origin-claim.d.ts +2 -0
  19. package/nexus-stage/package.json +21 -21
  20. package/nexus-stage/scripts/mockhost.py +261 -261
  21. package/nexus-stage/src/canvas-ops.ts +191 -191
  22. package/nexus-stage/src/canvas-wire.ts +164 -164
  23. package/nexus-stage/src/cli-home-picker.ts +42 -42
  24. package/nexus-stage/src/fault.ts +56 -56
  25. package/nexus-stage/src/federation-overlay.ts +103 -103
  26. package/nexus-stage/src/federation.ts +241 -241
  27. package/nexus-stage/src/layout-doc.ts +145 -145
  28. package/nexus-stage/src/main.ts +513 -513
  29. package/nexus-stage/src/planning-card-text.ts +73 -73
  30. package/nexus-stage/src/planning-card.ts +63 -63
  31. package/nexus-stage/src/planning-edges.ts +26 -26
  32. package/nexus-stage/src/planning-fit.ts +45 -45
  33. package/nexus-stage/src/planning-frame-hue.ts +13 -13
  34. package/nexus-stage/src/planning-legend.ts +39 -39
  35. package/nexus-stage/src/planning-member-handles.ts +61 -61
  36. package/nexus-stage/src/planning-menus.ts +422 -422
  37. package/nexus-stage/src/planning-mirror.ts +75 -75
  38. package/nexus-stage/src/planning-nodelist.ts +294 -294
  39. package/nexus-stage/src/planning-overlap.ts +159 -159
  40. package/nexus-stage/src/planning-reachability.ts +28 -28
  41. package/nexus-stage/src/planning-relayout.ts +121 -121
  42. package/nexus-stage/src/planning-roster.ts +15 -15
  43. package/nexus-stage/src/planning-search.ts +168 -168
  44. package/nexus-stage/src/planning-types.ts +115 -115
  45. package/nexus-stage/src/planning-undo.ts +68 -68
  46. package/nexus-stage/src/planning-view.ts +1499 -1499
  47. package/nexus-stage/src/planning.css +340 -340
  48. package/nexus-stage/src/seat-name-editor.ts +31 -31
  49. package/nexus-stage/src/session-drag.ts +64 -64
  50. package/nexus-stage/src/viewport.ts +106 -106
  51. package/nexus-stage/src/wb-chat.ts +684 -684
  52. package/nexus-stage/src/wb-directory.ts +237 -237
  53. package/nexus-stage/src/wb-ledger.ts +279 -279
  54. package/nexus-stage/src/wb-names.ts +43 -43
  55. package/nexus-stage/src/wb-rail.ts +147 -147
  56. package/nexus-stage/src/workbench.css +388 -388
  57. package/nexus-stage/src/workbench.ts +1272 -1272
  58. package/nexus-stage/src/world.ts +509 -509
  59. package/nexus-stage/tsconfig.json +24 -24
  60. package/nexus-stage/vite.config.ts +15 -15
  61. package/package.json +1 -1
@@ -1,340 +1,340 @@
1
- /* Planning mode (2D infinite canvas) — ported from the approved wireframe
2
- (feedback/2026-08-27-nexus-canvas-ux/prototype-v1.html) with the design
3
- system's three-layer token discipline. Tokens are scoped to #dsh-plan
4
- (token sovereignty: the stage owns its theme, detached from any host).
5
- Deviations from the prototype, all for computable geometry:
6
- - .frame-head is horizontally centered with a fixed 24px border-box
7
- height, so the star-edge origin is exactly (fx + fw/2, fy + 11);
8
- - .node has a fixed 56px border-box height matching world.NODE_H, with
9
- nowrap/ellipsis so the card can never outgrow the edge math.
10
- Layers (z): grid 0 < world 1 < marquee 2 < HUD 3. */
11
-
12
- #dsh-plan *,#dsh-plan *::before,#dsh-plan *::after{box-sizing:border-box}
13
-
14
- #dsh-plan{
15
- /* primitive — bound to the workbench tokens (--n-*, workbench.css) so the
16
- board follows the same dark/light theme as the rail; the literal after
17
- each comma is the stage's own dark value, kept so the board still
18
- renders on a host that ships without workbench.css (token sovereignty). */
19
- --uupm-ink-0:var(--n-ink,#dde3f0); --uupm-ink-1:var(--n-ink-muted,#7a86a0);
20
- --uupm-bg-0:var(--n-bg,#060a12); --uupm-bg-1:var(--n-panel,#0d1322); --uupm-bg-2:var(--n-raised,#131b2e);
21
- --uupm-accent:var(--n-accent,#9673f7); --uupm-live:var(--n-live,#4ade80); --uupm-cold:var(--n-cold,#7a86a0);
22
- --uupm-edge-member:var(--n-canvas,#22d3ee);
23
- --uupm-warn:var(--n-act,#f59e0b); --uupm-error:var(--n-danger,#f87171); --uupm-indigo:var(--n-federal,#6366f1);
24
- /* accent washes (hover 8% / active 12-18% / tint 5-10%), one definition each */
25
- --uupm-wash-5:color-mix(in srgb,var(--uupm-accent) 5%,transparent);
26
- --uupm-wash-8:color-mix(in srgb,var(--uupm-accent) 8%,transparent);
27
- --uupm-wash-12:color-mix(in srgb,var(--uupm-accent) 12%,transparent);
28
- --uupm-wash-15:color-mix(in srgb,var(--uupm-accent) 15%,transparent);
29
- --uupm-wash-18:color-mix(in srgb,var(--uupm-accent) 18%,transparent);
30
- /* semantic */
31
- --surface:var(--uupm-bg-1); --ink:var(--uupm-ink-0); --ink-muted:var(--uupm-ink-1);
32
- --accent:var(--uupm-accent); --edge-live:var(--uupm-live); --edge-cold:var(--uupm-cold);
33
- --federal:var(--uupm-indigo);
34
- /* component */
35
- --card-bg:var(--surface); --card-line:var(--n-card-line,#5c6680); --card-line-hover:var(--uupm-ink-1);
36
- --toolbar-bg:color-mix(in srgb,var(--uupm-bg-1) 92%,transparent);
37
- --grid-dot:color-mix(in srgb,var(--uupm-ink-1) 22%,transparent);
38
- --scrim:color-mix(in srgb,var(--uupm-bg-0) 60%,transparent);
39
- --error-ink:var(--n-error-ink,#fecaca); --error-panel:var(--n-error-panel,#2a0d10f2);
40
- }
41
-
42
- #dsh-plan{
43
- position:absolute; inset:0; overflow:hidden; background:var(--uupm-bg-0); color:var(--ink);
44
- font-size:13px; line-height:1.5; outline:none;
45
- }
46
- #dsh-plan .mono{font-family:ui-monospace,Cascadia,Consolas,monospace}
47
- #dsh-plan:focus-visible{outline:2px solid var(--accent); outline-offset:-2px}
48
-
49
- /* ── dot-grid world layer: screen-fixed, positioned by JS from the viewport ── */
50
- #dsh-plan .p-grid{position:absolute; inset:0; z-index:0; pointer-events:none;
51
- background-image:radial-gradient(circle, var(--grid-dot) 1px, transparent 1px);
52
- background-size:24px 24px; background-position:0 0}
53
-
54
- /* ── transformed world container ── */
55
- #dsh-plan .p-world{position:absolute; inset:0; z-index:1; transform-origin:0 0}
56
- #dsh-plan .p-layer{position:absolute; inset:0; pointer-events:none}
57
- #dsh-plan .p-edges{position:absolute; overflow:visible; width:100%; height:100%; pointer-events:none}
58
- /* The team frame is a grouping surface, not an interactive one: left
59
- event-transparent so real drags reach the member cards inside it (the
60
- frame head opts back in for group-drag/menu/Delete). */
61
- #dsh-plan .p-frame{pointer-events:none}
62
- #dsh-plan .p-frame-head{pointer-events:auto}
63
- #dsh-plan .p-node{pointer-events:auto}
64
- #dsh-plan .e-member{stroke:var(--uupm-edge-member); stroke-opacity:.38; stroke-width:1.5}
65
- #dsh-plan .e-member.dashed{stroke-dasharray:3 4}
66
-
67
- /* ── team frame ── */
68
- /* --frame-line is the team hue set inline per frame (frameHue); --frame-ink is
69
- what the frame actually paints with — the hue itself on dark, shaded 72%
70
- toward black on light, where amber/lime/teal outlines fell to 1.8-2.2:1. */
71
- #dsh-plan .p-frame{--frame-ink:var(--frame-line,#8B5CF6)}
72
- @media (prefers-color-scheme: light){
73
- #dsh-plan .p-frame{--frame-ink:color-mix(in srgb,var(--frame-line,#8B5CF6) 72%,black)}
74
- }
75
- #dsh-plan .p-frame{position:absolute; border:1px solid var(--frame-ink); border-radius:10px;
76
- background:var(--frame-bg,var(--uupm-wash-5)); min-width:120px; min-height:60px}
77
- #dsh-plan .p-frame-head{position:absolute; top:-13px; left:50%; transform:translateX(-50%);
78
- height:24px; padding:0 10px; border-radius:6px; display:flex; gap:8px; align-items:center;
79
- background:var(--uupm-bg-2); border:1px solid var(--frame-ink);
80
- font-size:12px; font-weight:600; letter-spacing:.02em; white-space:nowrap; cursor:grab}
81
- #dsh-plan .p-frame-head:active{cursor:grabbing}
82
- #dsh-plan .p-frame-head:hover{border-color:var(--card-line-hover)}
83
- #dsh-plan .p-frame-head .cnt{color:var(--ink-muted); font-weight:400}
84
- #dsh-plan .p-frame-head .route{font-size:10.5px; color:var(--ink-muted)}
85
- #dsh-plan .p-frame.is-mirror{border-style:dashed}
86
- #dsh-plan .p-frame.is-roster{border-style:double;border-width:3px}
87
- #dsh-plan .p-frame.is-roster .p-frame-head{border-style:dashed}
88
- #dsh-plan .p-frame.is-syncing .p-frame-head .route{color:var(--accent)}
89
- #dsh-plan .p-frame.is-unreachable{opacity:.76;border-style:dashed}
90
- #dsh-plan .p-frame-unreachable{position:absolute;left:8px;top:20px;max-width:calc(100% - 16px);color:var(--uupm-error);font-size:11px;pointer-events:none}
91
- #dsh-plan .p-node.remote.is-unreachable{opacity:.72;border-left-color:var(--uupm-error)}
92
- #dsh-plan .p-node.remote.is-unreachable .sub{color:var(--uupm-error)}
93
-
94
- /* ── node card (fixed 56px border-box height = world.NODE_H) ── */
95
- #dsh-plan .p-node{position:absolute; width:172px; height:56px; padding:8px 26px 8px 14px;
96
- background:var(--card-bg); border:1px solid var(--card-line); border-left:3px solid var(--edge-live);
97
- border-radius:8px; cursor:grab; user-select:none;
98
- transition:border-color 120ms cubic-bezier(0.2,0,0,1), transform 120ms cubic-bezier(0.2,0,0,1)}
99
- #dsh-plan .p-node:hover{border-color:var(--card-line-hover)}
100
- #dsh-plan .p-node:active{cursor:grabbing}
101
- #dsh-plan .p-node:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
102
- #dsh-plan .p-node.selected{outline:2px solid var(--accent); outline-offset:1px}
103
- #dsh-plan .p-node.dragging{transform:scale(1.02); border-color:var(--accent)}
104
- #dsh-plan .p-node.cold{border-left-color:var(--edge-cold)}
105
- #dsh-plan .p-node .nm{font-weight:600; display:flex; align-items:center; gap:7px;
106
- overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
107
- /* The name span clips with an ellipsis (min-width:0 lets it shrink inside
108
- the flex row) — without it a long peer address was hard-cut mid-digit.
109
- 属主令 W2: TWO-LINE clamp for the name (long Chinese team titles read
110
- fully); the card's 172x56 geometry is untouched — the clamp absorbs the
111
- overflow. The handle line (.sub) stays single-line ellipsis below. */
112
- #dsh-plan .p-node .nm-text{min-width:0; overflow:hidden; white-space:normal;
113
- display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; line-height:1.25}
114
- /* A peer address has no break point, so the two-line clamp clipped it to one
115
- line under the badge; let it wrap anywhere onto the second line. */
116
- #dsh-plan .p-node.remote .nm-text{overflow-wrap:anywhere}
117
- #dsh-plan .p-node .dot{width:8px; height:8px; border-radius:50%; background:var(--edge-live); flex:none}
118
- #dsh-plan .p-node.cold .dot{background:transparent; border:1.5px solid var(--edge-cold)}
119
- #dsh-plan .p-node .sub-row{display:flex; align-items:baseline; gap:6px; margin-top:2px; min-width:0}
120
- #dsh-plan .p-node .sub-row .sub{flex:1 1 auto; min-width:0; margin-top:0}
121
- #dsh-plan .p-node .work{flex:none; font-size:10.5px; font-weight:500; color:var(--ink-muted); white-space:nowrap}
122
- #dsh-plan .p-node .work:empty{display:none}
123
- #dsh-plan .p-node.is-working .work{color:var(--edge-live)}
124
- #dsh-plan .p-node.is-working .dot{animation:p-working 1.4s ease-in-out infinite}
125
- @keyframes p-working{0%,100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--edge-live) 55%,transparent)}50%{box-shadow:0 0 0 4px transparent}}
126
- @media (prefers-reduced-motion: reduce){#dsh-plan .p-node.is-working .dot{animation:none}}
127
- #dsh-plan .p-node .sub{font-size:11px; color:var(--ink-muted); margin-top:2px;
128
- overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
129
- /* W1: a picked search hit pulses its card for 1.2s (class removed on timer). */
130
- #dsh-plan .p-node.pulse{box-shadow:0 0 0 2px var(--accent), 0 0 14px 4px var(--uupm-wash-15); border-color:var(--accent)}
131
- /* W1: toolbar search box + hit list. */
132
- #dsh-plan .p-search{position:relative}
133
- #dsh-plan .p-search-input{width:170px; min-height:28px; padding:2px 8px; font-size:12px;
134
- background:transparent; color:var(--ink); border:1px solid var(--card-line); border-radius:6px}
135
- #dsh-plan .p-search-input:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
136
- #dsh-plan .p-search-list{position:absolute; top:100%; left:0; margin-top:4px; display:none;
137
- min-width:240px; max-height:220px; overflow:auto; z-index:9;
138
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:6px}
139
- #dsh-plan .p-search-hit{padding:5px 10px; font-size:12px; color:var(--ink); cursor:pointer; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
140
- #dsh-plan .p-search-hit:hover, #dsh-plan .p-search-hit.active{background:var(--uupm-wash-15)}
141
- #dsh-plan .p-node .prio{position:absolute; top:6px; right:8px; font-size:10px; color:var(--ink-muted);
142
- border:1px solid var(--card-line); border-radius:4px; padding:0 5px}
143
-
144
- /* ── marquee ── */
145
- #dsh-plan .p-marquee{position:absolute; z-index:2; display:none; pointer-events:none;
146
- border:1px solid var(--accent); background:var(--uupm-wash-8)}
147
-
148
- /* ── HUD ── */
149
- #dsh-plan .p-toolbar{position:absolute; top:16px; left:16px; z-index:3; display:flex; gap:6px; align-items:center;
150
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:6px 8px}
151
- #dsh-plan .p-tabs{position:absolute; top:16px; left:50%; transform:translateX(-50%); z-index:3; display:flex; gap:4px;
152
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:4px}
153
- #dsh-plan .p-tabs [role=tab]{background:transparent; color:var(--ink-muted); border:1px solid transparent; border-radius:6px;
154
- padding:4px 12px; font:inherit; cursor:pointer}
155
- #dsh-plan .p-tabs [role=tab]:hover{color:var(--ink); border-color:var(--card-line)}
156
- #dsh-plan .p-tabs [role=tab][aria-selected=true]{color:var(--ink); border-color:var(--accent); background:var(--uupm-wash-12)}
157
- #dsh-plan .p-tabs [role=tab]:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
158
- #dsh-plan .p-toolbar button{background:transparent; color:var(--ink); border:1px solid var(--card-line); border-radius:6px;
159
- padding:4px 10px; font:inherit; cursor:pointer; white-space:nowrap}
160
- #dsh-plan .p-toolbar button:hover{border-color:var(--accent)}
161
- #dsh-plan .p-toolbar button:active{background:var(--uupm-wash-15)}
162
- #dsh-plan .p-toolbar button:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
163
- #dsh-plan .p-zoom{min-width:44px; text-align:center; font-size:11px; color:var(--ink-muted)}
164
- /* Toolbar groups (view | arrange | network): a hairline between them. */
165
- #dsh-plan .p-sep{align-self:stretch; width:1px; margin:2px 4px; background:var(--card-line)}
166
- #dsh-plan .p-lamp{display:flex; align-items:center; gap:6px; font-size:11px; color:var(--ink-muted); padding:0 6px}
167
- #dsh-plan .p-lamp i{width:7px; height:7px; border-radius:50%; background:var(--ink-muted); transition:background 140ms ease}
168
- #dsh-plan .p-lamp.saved i{background:var(--edge-live)}
169
- #dsh-plan .p-lamp.pending i{background:var(--uupm-warn)}
170
- #dsh-plan .p-lamp.error{color:var(--uupm-error); cursor:pointer}
171
- #dsh-plan .p-lamp.error i{background:var(--uupm-error)}
172
- #dsh-plan .p-lamp.error:hover{border:1px solid var(--uupm-error); border-radius:4px}
173
-
174
- #dsh-plan .p-status{position:absolute; top:16px; right:16px; z-index:3; display:flex; gap:14px;
175
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:8px 14px; font-size:12px}
176
- #dsh-plan .p-status b{font-weight:600}
177
- #dsh-plan .ok{color:var(--edge-live)} #dsh-plan .warn{color:var(--uupm-warn)} #dsh-plan .mut{color:var(--ink-muted)}
178
-
179
- #dsh-plan .p-legend{position:absolute; bottom:16px; left:16px; z-index:3; font-size:11px; color:var(--ink-muted);
180
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:4px 10px}
181
- #dsh-plan .p-legend summary{cursor:pointer; padding:2px 0; user-select:none}
182
- #dsh-plan .p-legend summary:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
183
- #dsh-plan .p-legend[open]{padding-bottom:8px}
184
- #dsh-plan .p-legend-rows{display:grid; gap:4px; margin-top:4px}
185
- #dsh-plan .p-legend i{display:inline-block; width:18px; border-top:2px solid var(--uupm-edge-member);
186
- vertical-align:middle; margin-right:6px}
187
- #dsh-plan .p-legend i.dashed{border-top-style:dashed}
188
-
189
- /* ── narrow board (<680px): the HUD stacks instead of colliding ──
190
- The toolbar, status chip and legend are absolutely placed for a wide
191
- board; on a phone they overlapped each other and the toolbar's CJK
192
- labels wrapped one glyph per line. The one-row sideways scroll that
193
- followed hid the search box and half the actions off-screen (review
194
- 2026-10-27), so the toolbar and status chip now flow at the top of the
195
- board instead: the toolbar wraps, search first on its own row, and the
196
- status chip sits under whatever height that takes. The legend (reference,
197
- not state) hides. */
198
- @media (max-width:680px){
199
- #dsh-plan .p-toolbar{position:relative; top:auto; left:auto; margin:8px 8px 0; flex-wrap:wrap; row-gap:6px}
200
- #dsh-plan .p-toolbar > *{flex:none}
201
- #dsh-plan .p-toolbar .p-search{order:-1; flex:1 1 100%}
202
- #dsh-plan .p-toolbar .p-sep{display:none}
203
- #dsh-plan .p-toolbar .p-search-input{width:100%; box-sizing:border-box}
204
- #dsh-plan .p-status{position:relative; top:auto; right:auto; left:auto; width:fit-content; margin:6px 8px 0; padding:6px 10px; gap:10px; flex-wrap:wrap}
205
- #dsh-plan .p-legend{display:none}
206
- }
207
- @media (pointer:coarse){
208
- #dsh-plan .p-toolbar button{min-height:36px; min-width:36px}
209
- }
210
-
211
- /* ── reduced motion: zero animation, state carried by text (hard baseline) ── */
212
- @media (prefers-reduced-motion: reduce){
213
- #dsh-plan *{transition:none !important; animation:none !important}
214
- }
215
-
216
- /* ── PR C: drop-target highlight, context menu, name dialog, notices ── */
217
- #dsh-plan .p-frame.drop-target{border-color:var(--accent); border-width:2px; background:var(--uupm-wash-12)}
218
- #dsh-plan .p-frame-head:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
219
-
220
- /* ── PR D: federation overlay — peer badges, activity/federal edges, status fly ── */
221
- /* Peer cards: the 'peer' tag is ~36px wide against the 26px gutter a P0–P9
222
- tag fits in, so it sat on top of the address text — widen the gutter. */
223
- #dsh-plan .p-node.remote{border-left-color:var(--federal); padding-right:46px}
224
- #dsh-plan .p-node.remote .dot{background:var(--federal)}
225
- #dsh-plan .p-node.remote .prio{color:var(--ink)}
226
- /* gh#20 W-A link badge: linked green / stranger gray (fleet reserved);
227
- the ✓/✗ verified mark rides inside the same chip. */
228
- #dsh-plan .p-node .prio.lnk-linked{color:var(--live, #2e9e5b); border-color:var(--live, #2e9e5b)}
229
- #dsh-plan .p-node .prio.lnk-stranger{color:var(--ink-muted); border-color:var(--card-line)}
230
- #dsh-plan .e-activity{stroke:var(--accent); stroke-width:2; stroke-dasharray:8 6; stroke-opacity:.9}
231
- #dsh-plan .e-federal{stroke:var(--federal); stroke-width:1.5; stroke-dasharray:6 5; stroke-opacity:.5}
232
- #dsh-plan .edge-label{font-size:10.5px; fill:var(--ink-muted)}
233
- /* in-flight routes flow only when motion is welcome; reduced-motion keeps
234
- the zero-animation baseline (state stays in the label text). */
235
- @media (prefers-reduced-motion: no-preference){
236
- #dsh-plan .e-activity{animation:p-activity-flow 1.1s linear infinite}
237
- }
238
- @keyframes p-activity-flow{to{stroke-dashoffset:-28}}
239
- #dsh-plan .p-status{cursor:pointer}
240
- #dsh-plan .p-status:hover{border-color:var(--accent)}
241
- #dsh-plan .p-status:active{background:var(--uupm-wash-12)}
242
- #dsh-plan .p-status:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
243
-
244
- #dsh-plan .p-menu{position:fixed; z-index:5; min-width:170px; display:grid; padding:4px; gap:2px;
245
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px}
246
- #dsh-plan .p-menu [role=menuitem]{background:transparent; color:var(--ink); border:1px solid transparent;
247
- border-radius:6px; padding:6px 10px; font:inherit; text-align:left; cursor:pointer; white-space:nowrap}
248
- #dsh-plan .p-menu [role=menuitem]:hover{border-color:var(--card-line); background:var(--uupm-wash-8)}
249
- #dsh-plan .p-menu [role=menuitem]:active{background:var(--uupm-wash-18)}
250
- #dsh-plan .p-menu [role=menuitem]:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
251
- #dsh-plan .p-menu [role=menuitem][aria-disabled=true]{color:var(--ink-muted); cursor:not-allowed}
252
-
253
- #dsh-plan .p-dialog{position:absolute; inset:0; z-index:6; display:grid; place-items:center;
254
- background:var(--scrim)}
255
- #dsh-plan .p-dialog-panel{min-width:300px; padding:14px 16px; display:grid; gap:10px;
256
- background:var(--uupm-bg-2); border:1px solid var(--card-line); border-radius:10px}
257
- #dsh-plan .p-dialog-title{font-weight:600; font-size:12.5px; color:var(--ink)}
258
- #dsh-plan .p-dialog-input{font:inherit; color:var(--ink); background:var(--uupm-bg-0);
259
- border:1px solid var(--card-line); border-radius:6px; padding:7px 10px}
260
- #dsh-plan .p-dialog-input:hover{border-color:color-mix(in srgb,var(--uupm-ink-1) 55%,transparent)}
261
- #dsh-plan .p-dialog-input:focus-visible{outline:2px solid var(--accent); outline-offset:1px; border-color:var(--accent)}
262
- #dsh-plan .p-dialog-err{min-height:1.2em; font-size:11px; color:var(--uupm-error)}
263
- #dsh-plan .p-dialog-kind{display:grid; grid-template-columns:1fr 1fr; gap:4px; padding:3px;
264
- background:var(--uupm-bg-0); border:1px solid var(--card-line); border-radius:8px}
265
- #dsh-plan .p-dialog-kind button{font:inherit; color:var(--ink-muted); background:transparent; border:1px solid transparent;
266
- border-radius:6px; padding:6px 10px; cursor:pointer}
267
- #dsh-plan .p-dialog-kind button:hover{color:var(--ink); background:var(--uupm-wash-8)}
268
- #dsh-plan .p-dialog-kind button[aria-checked=true]{color:var(--ink); font-weight:600; background:var(--uupm-wash-15);
269
- border-color:var(--accent)}
270
- #dsh-plan .p-dialog-kind button:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
271
- #dsh-plan .p-dialog-hint{font-size:11px; color:var(--ink-muted); max-width:340px}
272
- #dsh-plan .p-dialog-row{display:flex; gap:8px; justify-content:flex-end}
273
- #dsh-plan .p-dialog-row button{background:transparent; color:var(--ink); border:1px solid var(--card-line);
274
- border-radius:6px; padding:5px 12px; font:inherit; cursor:pointer}
275
- #dsh-plan .p-dialog-row button:hover{border-color:var(--accent)}
276
- #dsh-plan .p-dialog-row button:active{background:var(--uupm-wash-15)}
277
- #dsh-plan .p-dialog-row button:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
278
-
279
- #dsh-plan .p-notice-stack{position:absolute; bottom:52px; left:50%; transform:translateX(-50%); z-index:5;
280
- display:grid; gap:6px; justify-items:center; pointer-events:none}
281
- #dsh-plan .p-notice{max-width:60ch; padding:7px 12px; font-size:12px; color:var(--ink);
282
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; white-space:nowrap;
283
- overflow:hidden; text-overflow:ellipsis}
284
- #dsh-plan .p-notice.error{color:var(--error-ink); border-color:var(--uupm-error); background:var(--error-panel)}
285
-
286
- /* Panel-slim migration: the unjoined-sessions join affordance on the toolbar. */
287
- #dsh-plan .p-netbtn{color:var(--ink-muted)}
288
- #dsh-plan .p-netbtn:not([disabled]){color:var(--ink)}
289
- #dsh-plan .p-netmenu{max-height:240px; overflow:auto}
290
-
291
- /* ── Node list drawer (network-membership-display): teamed nodes hold the
292
- canvas; every other session is managed here, switched by host. ── */
293
- #dsh-plan .p-nodebtn{color:var(--ink)}
294
- #dsh-plan .p-nodelist{min-width:300px; max-width:340px; max-height:420px; overflow:auto; padding:6px}
295
- #dsh-plan .p-nodelist-head{display:flex; align-items:center; justify-content:space-between; gap:8px;
296
- padding:2px 4px 6px; border-bottom:1px solid var(--card-line)}
297
- #dsh-plan .p-nodelist-title{font-size:12px; font-weight:600; color:var(--ink)}
298
- #dsh-plan .p-nodesel{font:inherit; font-size:11.5px; color:var(--ink); background:var(--uupm-bg-0);
299
- border:1px solid var(--card-line); border-radius:6px; padding:2px 6px; max-width:220px}
300
- #dsh-plan .p-nodesel:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
301
- #dsh-plan .p-nodelist-group{font-size:11px; color:var(--ink-muted); padding:8px 4px 3px;
302
- display:flex; align-items:baseline; gap:8px}
303
- #dsh-plan .p-nodelist-note{font-size:10px; color:var(--ink-muted); opacity:.8}
304
- #dsh-plan .p-noderow{display:flex; align-items:center; gap:6px; padding:3px 4px; border-radius:6px;
305
- font-size:12px; color:var(--ink)}
306
- #dsh-plan .p-noderow:hover{background:var(--uupm-wash-8)}
307
- #dsh-plan .p-nodedot{flex:none; width:7px; height:7px; border-radius:50%; background:var(--edge-live)}
308
- #dsh-plan .p-nodedot.cold{background:var(--ink-muted)}
309
- #dsh-plan .p-nodedot.off{background:transparent; border:1px solid var(--ink-muted)}
310
- #dsh-plan .p-nodename{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
311
- #dsh-plan .p-nonet{flex:none; font-size:10px; color:var(--uupm-warn); border:1px solid var(--uupm-warn);
312
- border-radius:4px; padding:0 4px}
313
- #dsh-plan .p-teamcount{flex:none; font-size:10px; color:var(--ink-muted); border:1px solid var(--card-line);
314
- border-radius:4px; padding:0 4px}
315
- #dsh-plan .p-listbtn{flex:none; background:transparent; color:var(--ink); border:1px solid var(--card-line);
316
- border-radius:5px; padding:1px 7px; font:inherit; font-size:11px; cursor:pointer}
317
- #dsh-plan .p-listbtn:hover{border-color:var(--card-line-hover); background:var(--uupm-wash-8)}
318
- #dsh-plan .p-listbtn:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
319
- #dsh-plan .p-nodelist-empty{padding:10px 4px; font-size:11.5px; color:var(--ink-muted)}
320
-
321
- /* ── Frame resize handles (owner ruling: frames stretch/adjust) — eight
322
- invisible hit zones that show the grip on hover. ── */
323
- #dsh-plan .p-frame{position:absolute}
324
- #dsh-plan .p-frame-handle{position:absolute; pointer-events:auto; width:10px; height:10px; z-index:2; opacity:0;
325
- transition:opacity 120ms ease}
326
- #dsh-plan .p-frame:hover .p-frame-handle{opacity:1}
327
- #dsh-plan .p-frame-handle::after{content:''; position:absolute; inset:2px; background:var(--frame-ink);
328
- border-radius:2px}
329
- #dsh-plan .p-h-n{top:-5px; left:calc(50% - 12px); width:24px; height:8px; cursor:ns-resize}
330
- #dsh-plan .p-h-s{bottom:-5px; left:calc(50% - 12px); width:24px; height:8px; cursor:ns-resize}
331
- #dsh-plan .p-h-e{right:-5px; top:calc(50% - 12px); width:8px; height:24px; cursor:ew-resize}
332
- #dsh-plan .p-h-w{left:-5px; top:calc(50% - 12px); width:8px; height:24px; cursor:ew-resize}
333
- #dsh-plan .p-h-ne{top:-5px; right:-5px; cursor:nesw-resize}
334
- #dsh-plan .p-h-nw{top:-5px; left:-5px; cursor:nwse-resize}
335
- #dsh-plan .p-h-se{bottom:-5px; right:-5px; cursor:nwse-resize}
336
- #dsh-plan .p-h-sw{bottom:-5px; left:-5px; cursor:nesw-resize}
337
- #dsh-plan .p-h-ne::after, #dsh-plan .p-h-sw::after{transform:rotate(45deg)}
338
- #dsh-plan .p-h-nw::after, #dsh-plan .p-h-se::after{transform:rotate(-45deg)}
339
- #dsh-plan .p-frame.selected .p-frame-handle{opacity:1}
340
- #dsh-plan .p-frame.selected{outline:1px solid var(--frame-ink)}
1
+ /* Planning mode (2D infinite canvas) — ported from the approved wireframe
2
+ (feedback/2026-08-27-nexus-canvas-ux/prototype-v1.html) with the design
3
+ system's three-layer token discipline. Tokens are scoped to #dsh-plan
4
+ (token sovereignty: the stage owns its theme, detached from any host).
5
+ Deviations from the prototype, all for computable geometry:
6
+ - .frame-head is horizontally centered with a fixed 24px border-box
7
+ height, so the star-edge origin is exactly (fx + fw/2, fy + 11);
8
+ - .node has a fixed 56px border-box height matching world.NODE_H, with
9
+ nowrap/ellipsis so the card can never outgrow the edge math.
10
+ Layers (z): grid 0 < world 1 < marquee 2 < HUD 3. */
11
+
12
+ #dsh-plan *,#dsh-plan *::before,#dsh-plan *::after{box-sizing:border-box}
13
+
14
+ #dsh-plan{
15
+ /* primitive — bound to the workbench tokens (--n-*, workbench.css) so the
16
+ board follows the same dark/light theme as the rail; the literal after
17
+ each comma is the stage's own dark value, kept so the board still
18
+ renders on a host that ships without workbench.css (token sovereignty). */
19
+ --uupm-ink-0:var(--n-ink,#dde3f0); --uupm-ink-1:var(--n-ink-muted,#7a86a0);
20
+ --uupm-bg-0:var(--n-bg,#060a12); --uupm-bg-1:var(--n-panel,#0d1322); --uupm-bg-2:var(--n-raised,#131b2e);
21
+ --uupm-accent:var(--n-accent,#9673f7); --uupm-live:var(--n-live,#4ade80); --uupm-cold:var(--n-cold,#7a86a0);
22
+ --uupm-edge-member:var(--n-canvas,#22d3ee);
23
+ --uupm-warn:var(--n-act,#f59e0b); --uupm-error:var(--n-danger,#f87171); --uupm-indigo:var(--n-federal,#6366f1);
24
+ /* accent washes (hover 8% / active 12-18% / tint 5-10%), one definition each */
25
+ --uupm-wash-5:color-mix(in srgb,var(--uupm-accent) 5%,transparent);
26
+ --uupm-wash-8:color-mix(in srgb,var(--uupm-accent) 8%,transparent);
27
+ --uupm-wash-12:color-mix(in srgb,var(--uupm-accent) 12%,transparent);
28
+ --uupm-wash-15:color-mix(in srgb,var(--uupm-accent) 15%,transparent);
29
+ --uupm-wash-18:color-mix(in srgb,var(--uupm-accent) 18%,transparent);
30
+ /* semantic */
31
+ --surface:var(--uupm-bg-1); --ink:var(--uupm-ink-0); --ink-muted:var(--uupm-ink-1);
32
+ --accent:var(--uupm-accent); --edge-live:var(--uupm-live); --edge-cold:var(--uupm-cold);
33
+ --federal:var(--uupm-indigo);
34
+ /* component */
35
+ --card-bg:var(--surface); --card-line:var(--n-card-line,#5c6680); --card-line-hover:var(--uupm-ink-1);
36
+ --toolbar-bg:color-mix(in srgb,var(--uupm-bg-1) 92%,transparent);
37
+ --grid-dot:color-mix(in srgb,var(--uupm-ink-1) 22%,transparent);
38
+ --scrim:color-mix(in srgb,var(--uupm-bg-0) 60%,transparent);
39
+ --error-ink:var(--n-error-ink,#fecaca); --error-panel:var(--n-error-panel,#2a0d10f2);
40
+ }
41
+
42
+ #dsh-plan{
43
+ position:absolute; inset:0; overflow:hidden; background:var(--uupm-bg-0); color:var(--ink);
44
+ font-size:13px; line-height:1.5; outline:none;
45
+ }
46
+ #dsh-plan .mono{font-family:ui-monospace,Cascadia,Consolas,monospace}
47
+ #dsh-plan:focus-visible{outline:2px solid var(--accent); outline-offset:-2px}
48
+
49
+ /* ── dot-grid world layer: screen-fixed, positioned by JS from the viewport ── */
50
+ #dsh-plan .p-grid{position:absolute; inset:0; z-index:0; pointer-events:none;
51
+ background-image:radial-gradient(circle, var(--grid-dot) 1px, transparent 1px);
52
+ background-size:24px 24px; background-position:0 0}
53
+
54
+ /* ── transformed world container ── */
55
+ #dsh-plan .p-world{position:absolute; inset:0; z-index:1; transform-origin:0 0}
56
+ #dsh-plan .p-layer{position:absolute; inset:0; pointer-events:none}
57
+ #dsh-plan .p-edges{position:absolute; overflow:visible; width:100%; height:100%; pointer-events:none}
58
+ /* The team frame is a grouping surface, not an interactive one: left
59
+ event-transparent so real drags reach the member cards inside it (the
60
+ frame head opts back in for group-drag/menu/Delete). */
61
+ #dsh-plan .p-frame{pointer-events:none}
62
+ #dsh-plan .p-frame-head{pointer-events:auto}
63
+ #dsh-plan .p-node{pointer-events:auto}
64
+ #dsh-plan .e-member{stroke:var(--uupm-edge-member); stroke-opacity:.38; stroke-width:1.5}
65
+ #dsh-plan .e-member.dashed{stroke-dasharray:3 4}
66
+
67
+ /* ── team frame ── */
68
+ /* --frame-line is the team hue set inline per frame (frameHue); --frame-ink is
69
+ what the frame actually paints with — the hue itself on dark, shaded 72%
70
+ toward black on light, where amber/lime/teal outlines fell to 1.8-2.2:1. */
71
+ #dsh-plan .p-frame{--frame-ink:var(--frame-line,#8B5CF6)}
72
+ @media (prefers-color-scheme: light){
73
+ #dsh-plan .p-frame{--frame-ink:color-mix(in srgb,var(--frame-line,#8B5CF6) 72%,black)}
74
+ }
75
+ #dsh-plan .p-frame{position:absolute; border:1px solid var(--frame-ink); border-radius:10px;
76
+ background:var(--frame-bg,var(--uupm-wash-5)); min-width:120px; min-height:60px}
77
+ #dsh-plan .p-frame-head{position:absolute; top:-13px; left:50%; transform:translateX(-50%);
78
+ height:24px; padding:0 10px; border-radius:6px; display:flex; gap:8px; align-items:center;
79
+ background:var(--uupm-bg-2); border:1px solid var(--frame-ink);
80
+ font-size:12px; font-weight:600; letter-spacing:.02em; white-space:nowrap; cursor:grab}
81
+ #dsh-plan .p-frame-head:active{cursor:grabbing}
82
+ #dsh-plan .p-frame-head:hover{border-color:var(--card-line-hover)}
83
+ #dsh-plan .p-frame-head .cnt{color:var(--ink-muted); font-weight:400}
84
+ #dsh-plan .p-frame-head .route{font-size:10.5px; color:var(--ink-muted)}
85
+ #dsh-plan .p-frame.is-mirror{border-style:dashed}
86
+ #dsh-plan .p-frame.is-roster{border-style:double;border-width:3px}
87
+ #dsh-plan .p-frame.is-roster .p-frame-head{border-style:dashed}
88
+ #dsh-plan .p-frame.is-syncing .p-frame-head .route{color:var(--accent)}
89
+ #dsh-plan .p-frame.is-unreachable{opacity:.76;border-style:dashed}
90
+ #dsh-plan .p-frame-unreachable{position:absolute;left:8px;top:20px;max-width:calc(100% - 16px);color:var(--uupm-error);font-size:11px;pointer-events:none}
91
+ #dsh-plan .p-node.remote.is-unreachable{opacity:.72;border-left-color:var(--uupm-error)}
92
+ #dsh-plan .p-node.remote.is-unreachable .sub{color:var(--uupm-error)}
93
+
94
+ /* ── node card (fixed 56px border-box height = world.NODE_H) ── */
95
+ #dsh-plan .p-node{position:absolute; width:172px; height:56px; padding:8px 26px 8px 14px;
96
+ background:var(--card-bg); border:1px solid var(--card-line); border-left:3px solid var(--edge-live);
97
+ border-radius:8px; cursor:grab; user-select:none;
98
+ transition:border-color 120ms cubic-bezier(0.2,0,0,1), transform 120ms cubic-bezier(0.2,0,0,1)}
99
+ #dsh-plan .p-node:hover{border-color:var(--card-line-hover)}
100
+ #dsh-plan .p-node:active{cursor:grabbing}
101
+ #dsh-plan .p-node:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
102
+ #dsh-plan .p-node.selected{outline:2px solid var(--accent); outline-offset:1px}
103
+ #dsh-plan .p-node.dragging{transform:scale(1.02); border-color:var(--accent)}
104
+ #dsh-plan .p-node.cold{border-left-color:var(--edge-cold)}
105
+ #dsh-plan .p-node .nm{font-weight:600; display:flex; align-items:center; gap:7px;
106
+ overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
107
+ /* The name span clips with an ellipsis (min-width:0 lets it shrink inside
108
+ the flex row) — without it a long peer address was hard-cut mid-digit.
109
+ 属主令 W2: TWO-LINE clamp for the name (long Chinese team titles read
110
+ fully); the card's 172x56 geometry is untouched — the clamp absorbs the
111
+ overflow. The handle line (.sub) stays single-line ellipsis below. */
112
+ #dsh-plan .p-node .nm-text{min-width:0; overflow:hidden; white-space:normal;
113
+ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; line-height:1.25}
114
+ /* A peer address has no break point, so the two-line clamp clipped it to one
115
+ line under the badge; let it wrap anywhere onto the second line. */
116
+ #dsh-plan .p-node.remote .nm-text{overflow-wrap:anywhere}
117
+ #dsh-plan .p-node .dot{width:8px; height:8px; border-radius:50%; background:var(--edge-live); flex:none}
118
+ #dsh-plan .p-node.cold .dot{background:transparent; border:1.5px solid var(--edge-cold)}
119
+ #dsh-plan .p-node .sub-row{display:flex; align-items:baseline; gap:6px; margin-top:2px; min-width:0}
120
+ #dsh-plan .p-node .sub-row .sub{flex:1 1 auto; min-width:0; margin-top:0}
121
+ #dsh-plan .p-node .work{flex:none; font-size:10.5px; font-weight:500; color:var(--ink-muted); white-space:nowrap}
122
+ #dsh-plan .p-node .work:empty{display:none}
123
+ #dsh-plan .p-node.is-working .work{color:var(--edge-live)}
124
+ #dsh-plan .p-node.is-working .dot{animation:p-working 1.4s ease-in-out infinite}
125
+ @keyframes p-working{0%,100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--edge-live) 55%,transparent)}50%{box-shadow:0 0 0 4px transparent}}
126
+ @media (prefers-reduced-motion: reduce){#dsh-plan .p-node.is-working .dot{animation:none}}
127
+ #dsh-plan .p-node .sub{font-size:11px; color:var(--ink-muted); margin-top:2px;
128
+ overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
129
+ /* W1: a picked search hit pulses its card for 1.2s (class removed on timer). */
130
+ #dsh-plan .p-node.pulse{box-shadow:0 0 0 2px var(--accent), 0 0 14px 4px var(--uupm-wash-15); border-color:var(--accent)}
131
+ /* W1: toolbar search box + hit list. */
132
+ #dsh-plan .p-search{position:relative}
133
+ #dsh-plan .p-search-input{width:170px; min-height:28px; padding:2px 8px; font-size:12px;
134
+ background:transparent; color:var(--ink); border:1px solid var(--card-line); border-radius:6px}
135
+ #dsh-plan .p-search-input:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
136
+ #dsh-plan .p-search-list{position:absolute; top:100%; left:0; margin-top:4px; display:none;
137
+ min-width:240px; max-height:220px; overflow:auto; z-index:9;
138
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:6px}
139
+ #dsh-plan .p-search-hit{padding:5px 10px; font-size:12px; color:var(--ink); cursor:pointer; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
140
+ #dsh-plan .p-search-hit:hover, #dsh-plan .p-search-hit.active{background:var(--uupm-wash-15)}
141
+ #dsh-plan .p-node .prio{position:absolute; top:6px; right:8px; font-size:10px; color:var(--ink-muted);
142
+ border:1px solid var(--card-line); border-radius:4px; padding:0 5px}
143
+
144
+ /* ── marquee ── */
145
+ #dsh-plan .p-marquee{position:absolute; z-index:2; display:none; pointer-events:none;
146
+ border:1px solid var(--accent); background:var(--uupm-wash-8)}
147
+
148
+ /* ── HUD ── */
149
+ #dsh-plan .p-toolbar{position:absolute; top:16px; left:16px; z-index:3; display:flex; gap:6px; align-items:center;
150
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:6px 8px}
151
+ #dsh-plan .p-tabs{position:absolute; top:16px; left:50%; transform:translateX(-50%); z-index:3; display:flex; gap:4px;
152
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:4px}
153
+ #dsh-plan .p-tabs [role=tab]{background:transparent; color:var(--ink-muted); border:1px solid transparent; border-radius:6px;
154
+ padding:4px 12px; font:inherit; cursor:pointer}
155
+ #dsh-plan .p-tabs [role=tab]:hover{color:var(--ink); border-color:var(--card-line)}
156
+ #dsh-plan .p-tabs [role=tab][aria-selected=true]{color:var(--ink); border-color:var(--accent); background:var(--uupm-wash-12)}
157
+ #dsh-plan .p-tabs [role=tab]:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
158
+ #dsh-plan .p-toolbar button{background:transparent; color:var(--ink); border:1px solid var(--card-line); border-radius:6px;
159
+ padding:4px 10px; font:inherit; cursor:pointer; white-space:nowrap}
160
+ #dsh-plan .p-toolbar button:hover{border-color:var(--accent)}
161
+ #dsh-plan .p-toolbar button:active{background:var(--uupm-wash-15)}
162
+ #dsh-plan .p-toolbar button:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
163
+ #dsh-plan .p-zoom{min-width:44px; text-align:center; font-size:11px; color:var(--ink-muted)}
164
+ /* Toolbar groups (view | arrange | network): a hairline between them. */
165
+ #dsh-plan .p-sep{align-self:stretch; width:1px; margin:2px 4px; background:var(--card-line)}
166
+ #dsh-plan .p-lamp{display:flex; align-items:center; gap:6px; font-size:11px; color:var(--ink-muted); padding:0 6px}
167
+ #dsh-plan .p-lamp i{width:7px; height:7px; border-radius:50%; background:var(--ink-muted); transition:background 140ms ease}
168
+ #dsh-plan .p-lamp.saved i{background:var(--edge-live)}
169
+ #dsh-plan .p-lamp.pending i{background:var(--uupm-warn)}
170
+ #dsh-plan .p-lamp.error{color:var(--uupm-error); cursor:pointer}
171
+ #dsh-plan .p-lamp.error i{background:var(--uupm-error)}
172
+ #dsh-plan .p-lamp.error:hover{border:1px solid var(--uupm-error); border-radius:4px}
173
+
174
+ #dsh-plan .p-status{position:absolute; top:16px; right:16px; z-index:3; display:flex; gap:14px;
175
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:8px 14px; font-size:12px}
176
+ #dsh-plan .p-status b{font-weight:600}
177
+ #dsh-plan .ok{color:var(--edge-live)} #dsh-plan .warn{color:var(--uupm-warn)} #dsh-plan .mut{color:var(--ink-muted)}
178
+
179
+ #dsh-plan .p-legend{position:absolute; bottom:16px; left:16px; z-index:3; font-size:11px; color:var(--ink-muted);
180
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:4px 10px}
181
+ #dsh-plan .p-legend summary{cursor:pointer; padding:2px 0; user-select:none}
182
+ #dsh-plan .p-legend summary:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
183
+ #dsh-plan .p-legend[open]{padding-bottom:8px}
184
+ #dsh-plan .p-legend-rows{display:grid; gap:4px; margin-top:4px}
185
+ #dsh-plan .p-legend i{display:inline-block; width:18px; border-top:2px solid var(--uupm-edge-member);
186
+ vertical-align:middle; margin-right:6px}
187
+ #dsh-plan .p-legend i.dashed{border-top-style:dashed}
188
+
189
+ /* ── narrow board (<680px): the HUD stacks instead of colliding ──
190
+ The toolbar, status chip and legend are absolutely placed for a wide
191
+ board; on a phone they overlapped each other and the toolbar's CJK
192
+ labels wrapped one glyph per line. The one-row sideways scroll that
193
+ followed hid the search box and half the actions off-screen (review
194
+ 2026-10-27), so the toolbar and status chip now flow at the top of the
195
+ board instead: the toolbar wraps, search first on its own row, and the
196
+ status chip sits under whatever height that takes. The legend (reference,
197
+ not state) hides. */
198
+ @media (max-width:680px){
199
+ #dsh-plan .p-toolbar{position:relative; top:auto; left:auto; margin:8px 8px 0; flex-wrap:wrap; row-gap:6px}
200
+ #dsh-plan .p-toolbar > *{flex:none}
201
+ #dsh-plan .p-toolbar .p-search{order:-1; flex:1 1 100%}
202
+ #dsh-plan .p-toolbar .p-sep{display:none}
203
+ #dsh-plan .p-toolbar .p-search-input{width:100%; box-sizing:border-box}
204
+ #dsh-plan .p-status{position:relative; top:auto; right:auto; left:auto; width:fit-content; margin:6px 8px 0; padding:6px 10px; gap:10px; flex-wrap:wrap}
205
+ #dsh-plan .p-legend{display:none}
206
+ }
207
+ @media (pointer:coarse){
208
+ #dsh-plan .p-toolbar button{min-height:36px; min-width:36px}
209
+ }
210
+
211
+ /* ── reduced motion: zero animation, state carried by text (hard baseline) ── */
212
+ @media (prefers-reduced-motion: reduce){
213
+ #dsh-plan *{transition:none !important; animation:none !important}
214
+ }
215
+
216
+ /* ── PR C: drop-target highlight, context menu, name dialog, notices ── */
217
+ #dsh-plan .p-frame.drop-target{border-color:var(--accent); border-width:2px; background:var(--uupm-wash-12)}
218
+ #dsh-plan .p-frame-head:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
219
+
220
+ /* ── PR D: federation overlay — peer badges, activity/federal edges, status fly ── */
221
+ /* Peer cards: the 'peer' tag is ~36px wide against the 26px gutter a P0–P9
222
+ tag fits in, so it sat on top of the address text — widen the gutter. */
223
+ #dsh-plan .p-node.remote{border-left-color:var(--federal); padding-right:46px}
224
+ #dsh-plan .p-node.remote .dot{background:var(--federal)}
225
+ #dsh-plan .p-node.remote .prio{color:var(--ink)}
226
+ /* gh#20 W-A link badge: linked green / stranger gray (fleet reserved);
227
+ the ✓/✗ verified mark rides inside the same chip. */
228
+ #dsh-plan .p-node .prio.lnk-linked{color:var(--live, #2e9e5b); border-color:var(--live, #2e9e5b)}
229
+ #dsh-plan .p-node .prio.lnk-stranger{color:var(--ink-muted); border-color:var(--card-line)}
230
+ #dsh-plan .e-activity{stroke:var(--accent); stroke-width:2; stroke-dasharray:8 6; stroke-opacity:.9}
231
+ #dsh-plan .e-federal{stroke:var(--federal); stroke-width:1.5; stroke-dasharray:6 5; stroke-opacity:.5}
232
+ #dsh-plan .edge-label{font-size:10.5px; fill:var(--ink-muted)}
233
+ /* in-flight routes flow only when motion is welcome; reduced-motion keeps
234
+ the zero-animation baseline (state stays in the label text). */
235
+ @media (prefers-reduced-motion: no-preference){
236
+ #dsh-plan .e-activity{animation:p-activity-flow 1.1s linear infinite}
237
+ }
238
+ @keyframes p-activity-flow{to{stroke-dashoffset:-28}}
239
+ #dsh-plan .p-status{cursor:pointer}
240
+ #dsh-plan .p-status:hover{border-color:var(--accent)}
241
+ #dsh-plan .p-status:active{background:var(--uupm-wash-12)}
242
+ #dsh-plan .p-status:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
243
+
244
+ #dsh-plan .p-menu{position:fixed; z-index:5; min-width:170px; display:grid; padding:4px; gap:2px;
245
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px}
246
+ #dsh-plan .p-menu [role=menuitem]{background:transparent; color:var(--ink); border:1px solid transparent;
247
+ border-radius:6px; padding:6px 10px; font:inherit; text-align:left; cursor:pointer; white-space:nowrap}
248
+ #dsh-plan .p-menu [role=menuitem]:hover{border-color:var(--card-line); background:var(--uupm-wash-8)}
249
+ #dsh-plan .p-menu [role=menuitem]:active{background:var(--uupm-wash-18)}
250
+ #dsh-plan .p-menu [role=menuitem]:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
251
+ #dsh-plan .p-menu [role=menuitem][aria-disabled=true]{color:var(--ink-muted); cursor:not-allowed}
252
+
253
+ #dsh-plan .p-dialog{position:absolute; inset:0; z-index:6; display:grid; place-items:center;
254
+ background:var(--scrim)}
255
+ #dsh-plan .p-dialog-panel{min-width:300px; padding:14px 16px; display:grid; gap:10px;
256
+ background:var(--uupm-bg-2); border:1px solid var(--card-line); border-radius:10px}
257
+ #dsh-plan .p-dialog-title{font-weight:600; font-size:12.5px; color:var(--ink)}
258
+ #dsh-plan .p-dialog-input{font:inherit; color:var(--ink); background:var(--uupm-bg-0);
259
+ border:1px solid var(--card-line); border-radius:6px; padding:7px 10px}
260
+ #dsh-plan .p-dialog-input:hover{border-color:color-mix(in srgb,var(--uupm-ink-1) 55%,transparent)}
261
+ #dsh-plan .p-dialog-input:focus-visible{outline:2px solid var(--accent); outline-offset:1px; border-color:var(--accent)}
262
+ #dsh-plan .p-dialog-err{min-height:1.2em; font-size:11px; color:var(--uupm-error)}
263
+ #dsh-plan .p-dialog-kind{display:grid; grid-template-columns:1fr 1fr; gap:4px; padding:3px;
264
+ background:var(--uupm-bg-0); border:1px solid var(--card-line); border-radius:8px}
265
+ #dsh-plan .p-dialog-kind button{font:inherit; color:var(--ink-muted); background:transparent; border:1px solid transparent;
266
+ border-radius:6px; padding:6px 10px; cursor:pointer}
267
+ #dsh-plan .p-dialog-kind button:hover{color:var(--ink); background:var(--uupm-wash-8)}
268
+ #dsh-plan .p-dialog-kind button[aria-checked=true]{color:var(--ink); font-weight:600; background:var(--uupm-wash-15);
269
+ border-color:var(--accent)}
270
+ #dsh-plan .p-dialog-kind button:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
271
+ #dsh-plan .p-dialog-hint{font-size:11px; color:var(--ink-muted); max-width:340px}
272
+ #dsh-plan .p-dialog-row{display:flex; gap:8px; justify-content:flex-end}
273
+ #dsh-plan .p-dialog-row button{background:transparent; color:var(--ink); border:1px solid var(--card-line);
274
+ border-radius:6px; padding:5px 12px; font:inherit; cursor:pointer}
275
+ #dsh-plan .p-dialog-row button:hover{border-color:var(--accent)}
276
+ #dsh-plan .p-dialog-row button:active{background:var(--uupm-wash-15)}
277
+ #dsh-plan .p-dialog-row button:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
278
+
279
+ #dsh-plan .p-notice-stack{position:absolute; bottom:52px; left:50%; transform:translateX(-50%); z-index:5;
280
+ display:grid; gap:6px; justify-items:center; pointer-events:none}
281
+ #dsh-plan .p-notice{max-width:60ch; padding:7px 12px; font-size:12px; color:var(--ink);
282
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; white-space:nowrap;
283
+ overflow:hidden; text-overflow:ellipsis}
284
+ #dsh-plan .p-notice.error{color:var(--error-ink); border-color:var(--uupm-error); background:var(--error-panel)}
285
+
286
+ /* Panel-slim migration: the unjoined-sessions join affordance on the toolbar. */
287
+ #dsh-plan .p-netbtn{color:var(--ink-muted)}
288
+ #dsh-plan .p-netbtn:not([disabled]){color:var(--ink)}
289
+ #dsh-plan .p-netmenu{max-height:240px; overflow:auto}
290
+
291
+ /* ── Node list drawer (network-membership-display): teamed nodes hold the
292
+ canvas; every other session is managed here, switched by host. ── */
293
+ #dsh-plan .p-nodebtn{color:var(--ink)}
294
+ #dsh-plan .p-nodelist{min-width:300px; max-width:340px; max-height:420px; overflow:auto; padding:6px}
295
+ #dsh-plan .p-nodelist-head{display:flex; align-items:center; justify-content:space-between; gap:8px;
296
+ padding:2px 4px 6px; border-bottom:1px solid var(--card-line)}
297
+ #dsh-plan .p-nodelist-title{font-size:12px; font-weight:600; color:var(--ink)}
298
+ #dsh-plan .p-nodesel{font:inherit; font-size:11.5px; color:var(--ink); background:var(--uupm-bg-0);
299
+ border:1px solid var(--card-line); border-radius:6px; padding:2px 6px; max-width:220px}
300
+ #dsh-plan .p-nodesel:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
301
+ #dsh-plan .p-nodelist-group{font-size:11px; color:var(--ink-muted); padding:8px 4px 3px;
302
+ display:flex; align-items:baseline; gap:8px}
303
+ #dsh-plan .p-nodelist-note{font-size:10px; color:var(--ink-muted); opacity:.8}
304
+ #dsh-plan .p-noderow{display:flex; align-items:center; gap:6px; padding:3px 4px; border-radius:6px;
305
+ font-size:12px; color:var(--ink)}
306
+ #dsh-plan .p-noderow:hover{background:var(--uupm-wash-8)}
307
+ #dsh-plan .p-nodedot{flex:none; width:7px; height:7px; border-radius:50%; background:var(--edge-live)}
308
+ #dsh-plan .p-nodedot.cold{background:var(--ink-muted)}
309
+ #dsh-plan .p-nodedot.off{background:transparent; border:1px solid var(--ink-muted)}
310
+ #dsh-plan .p-nodename{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
311
+ #dsh-plan .p-nonet{flex:none; font-size:10px; color:var(--uupm-warn); border:1px solid var(--uupm-warn);
312
+ border-radius:4px; padding:0 4px}
313
+ #dsh-plan .p-teamcount{flex:none; font-size:10px; color:var(--ink-muted); border:1px solid var(--card-line);
314
+ border-radius:4px; padding:0 4px}
315
+ #dsh-plan .p-listbtn{flex:none; background:transparent; color:var(--ink); border:1px solid var(--card-line);
316
+ border-radius:5px; padding:1px 7px; font:inherit; font-size:11px; cursor:pointer}
317
+ #dsh-plan .p-listbtn:hover{border-color:var(--card-line-hover); background:var(--uupm-wash-8)}
318
+ #dsh-plan .p-listbtn:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
319
+ #dsh-plan .p-nodelist-empty{padding:10px 4px; font-size:11.5px; color:var(--ink-muted)}
320
+
321
+ /* ── Frame resize handles (owner ruling: frames stretch/adjust) — eight
322
+ invisible hit zones that show the grip on hover. ── */
323
+ #dsh-plan .p-frame{position:absolute}
324
+ #dsh-plan .p-frame-handle{position:absolute; pointer-events:auto; width:10px; height:10px; z-index:2; opacity:0;
325
+ transition:opacity 120ms ease}
326
+ #dsh-plan .p-frame:hover .p-frame-handle{opacity:1}
327
+ #dsh-plan .p-frame-handle::after{content:''; position:absolute; inset:2px; background:var(--frame-ink);
328
+ border-radius:2px}
329
+ #dsh-plan .p-h-n{top:-5px; left:calc(50% - 12px); width:24px; height:8px; cursor:ns-resize}
330
+ #dsh-plan .p-h-s{bottom:-5px; left:calc(50% - 12px); width:24px; height:8px; cursor:ns-resize}
331
+ #dsh-plan .p-h-e{right:-5px; top:calc(50% - 12px); width:8px; height:24px; cursor:ew-resize}
332
+ #dsh-plan .p-h-w{left:-5px; top:calc(50% - 12px); width:8px; height:24px; cursor:ew-resize}
333
+ #dsh-plan .p-h-ne{top:-5px; right:-5px; cursor:nesw-resize}
334
+ #dsh-plan .p-h-nw{top:-5px; left:-5px; cursor:nwse-resize}
335
+ #dsh-plan .p-h-se{bottom:-5px; right:-5px; cursor:nwse-resize}
336
+ #dsh-plan .p-h-sw{bottom:-5px; left:-5px; cursor:nesw-resize}
337
+ #dsh-plan .p-h-ne::after, #dsh-plan .p-h-sw::after{transform:rotate(45deg)}
338
+ #dsh-plan .p-h-nw::after, #dsh-plan .p-h-se::after{transform:rotate(-45deg)}
339
+ #dsh-plan .p-frame.selected .p-frame-handle{opacity:1}
340
+ #dsh-plan .p-frame.selected{outline:1px solid var(--frame-ink)}