|
5 | 5 | * (background grid, panes, minimap, handles) to our --bg-inset instrument |
6 | 6 | * surface and (b) style the custom node/edge classes the component renders. |
7 | 7 | * |
8 | | - * Token-only colours (tokens.css). All motion is keyed off --dur-* tokens, which |
9 | | - * the global prefers-reduced-motion block zeros — so reduced motion collapses |
10 | | - * every transition/animation here without a local media query. |
| 8 | + * Token-only colours (tokens.css). Token-driven motion is keyed off --dur-*, |
| 9 | + * which the global prefers-reduced-motion block zeros. The ONE exception is |
| 10 | + * React Flow's native edge animation (animated edges), which runs on the |
| 11 | + * library's own keyframes — it gets an explicit prefers-reduced-motion guard at |
| 12 | + * the bottom of this file. |
11 | 13 | */ |
12 | 14 |
|
13 | 15 | /* ---- the wrapper that holds the whole map ---- */ |
|
67 | 69 | box-shadow: 0 0 0 1px var(--accent); |
68 | 70 | } |
69 | 71 |
|
70 | | -/* Hidden centred handle: edges attach here, but FloatingEdge draws |
71 | | - * centre-to-centre so position is cosmetic. Must visually disappear AND not |
72 | | - * eat clicks. !important beats React Flow's base .react-flow__handle (a 6×6 |
73 | | - * #333/#141414 dot with a border). */ |
| 72 | +/* ---- level-of-detail: a status dot replaces the chip when zoomed out (dense |
| 73 | + * 120-service view), so the 168px boxes stop occluding the routed edges. The |
| 74 | + * dot is centred in the node box (dagre's edge waypoints still meet the box |
| 75 | + * border) and carries the status hue. ---- */ |
| 76 | +.dotNode { |
| 77 | + display: flex; |
| 78 | + align-items: center; |
| 79 | + justify-content: center; |
| 80 | + width: 100%; |
| 81 | + height: 100%; |
| 82 | + cursor: pointer; |
| 83 | +} |
| 84 | +.dotNode::before { |
| 85 | + content: ''; |
| 86 | + width: 18px; |
| 87 | + height: 18px; |
| 88 | + border-radius: 50%; |
| 89 | + background: var(--node-color); |
| 90 | + box-shadow: 0 0 0 2px var(--bg-inset); |
| 91 | + transition: box-shadow var(--dur-2) var(--ease); |
| 92 | +} |
| 93 | +/* Inspected / blast-root dot: accent ring, matching the chip's selected ring. */ |
| 94 | +.dotSelected::before { |
| 95 | + box-shadow: |
| 96 | + 0 0 0 2px var(--bg-inset), |
| 97 | + 0 0 0 4px var(--accent); |
| 98 | +} |
| 99 | + |
| 100 | +/* Connection anchor, kept INVISIBLE. React Flow needs a source/target handle |
| 101 | + * per node, but the floating edge computes the real border attachment point, so |
| 102 | + * the handle dot itself must not show — otherwise edgeless nodes sprout a stray |
| 103 | + * connector. opacity:0 keeps the element measured so edge routing still resolves |
| 104 | + * its position; it never eats clicks. */ |
74 | 105 | .handle { |
75 | | - position: absolute !important; |
76 | | - left: 50% !important; |
77 | | - top: 50% !important; |
78 | | - width: 1px !important; |
79 | | - height: 1px !important; |
80 | | - min-width: 0 !important; |
81 | | - min-height: 0 !important; |
82 | | - border: 0 !important; |
83 | | - background: transparent !important; |
84 | | - opacity: 0 !important; |
85 | | - pointer-events: none !important; |
86 | | - transform: translate(-50%, -50%); |
| 106 | + width: 6px; |
| 107 | + height: 6px; |
| 108 | + min-width: 0; |
| 109 | + min-height: 0; |
| 110 | + border: 0; |
| 111 | + background: transparent; |
| 112 | + opacity: 0; |
| 113 | + pointer-events: none; |
87 | 114 | } |
88 | 115 |
|
89 | 116 | /* Status dot — the same hue as the chip outline. */ |
|
115 | 142 | color: var(--text-2); |
116 | 143 | } |
117 | 144 |
|
118 | | -/* ---- custom edge: FloatingEdge renders a raw <path className="edge ..."> ---- */ |
119 | | -/* Hairline link, legible on both themes against the field. --edge-w is set |
120 | | - * inline from the call count. */ |
121 | | -.edge { |
122 | | - fill: none; |
123 | | - stroke: var(--text-3); |
124 | | - stroke-width: var(--edge-w, 1.5); |
125 | | - opacity: 0.75; |
126 | | -} |
127 | | - |
128 | | -/* Failing edge: --crit, full strength, marching dashes that drain toward the |
129 | | - * target. The march runs on calc(var(--dur-3) * 6) — so reduced motion |
130 | | - * (--dur-3: 0ms) yields a 0s duration and the dashes stop, leaving a static |
131 | | - * red dashed line. */ |
132 | | -.edgeFailing { |
133 | | - stroke: var(--crit); |
134 | | - opacity: 1; |
135 | | - stroke-dasharray: 4 4; |
136 | | - animation: march calc(var(--dur-3) * 6) linear infinite; |
137 | | -} |
138 | | - |
139 | | -@keyframes march { |
140 | | - to { |
141 | | - /* one full dash + gap (4 + 4) → seamless loop. */ |
142 | | - stroke-dashoffset: -8; |
143 | | - } |
144 | | -} |
145 | | - |
146 | 145 | /* ============================================================================ |
147 | 146 | * React Flow base overrides (file-scope :global — these target React Flow's |
148 | 147 | * OWN DOM, which is a sibling of our chips, not nested under any module class). |
|
188 | 187 | stroke-width: 1; |
189 | 188 | } |
190 | 189 |
|
| 190 | +/* ============================================================================ |
| 191 | + * React Flow Controls + MiniMap — themed to the dark token instrument surface |
| 192 | + * so the map reads as a first-class graph tool, not the library's default |
| 193 | + * white chrome. (file-scope :global — these target React Flow's own DOM.) |
| 194 | + * ==========================================================================*/ |
| 195 | + |
| 196 | +/* Zoom / fit control cluster: a hairline token-themed stack, not a white box. */ |
| 197 | +.controls { |
| 198 | + border: 1px solid var(--stroke-1); |
| 199 | + border-radius: var(--radius-2); |
| 200 | + overflow: hidden; |
| 201 | + background: var(--bg-raised); |
| 202 | + box-shadow: none; |
| 203 | +} |
| 204 | +.controls :global(.react-flow__controls-button) { |
| 205 | + width: 28px; |
| 206 | + height: 28px; |
| 207 | + border: none; |
| 208 | + border-bottom: 1px solid var(--stroke-1); |
| 209 | + background: var(--bg-raised); |
| 210 | + color: var(--text-2); |
| 211 | + fill: var(--text-2); |
| 212 | + transition: |
| 213 | + background var(--dur-1) var(--ease), |
| 214 | + color var(--dur-1) var(--ease); |
| 215 | +} |
| 216 | +.controls :global(.react-flow__controls-button):last-child { |
| 217 | + border-bottom: none; |
| 218 | +} |
| 219 | +.controls :global(.react-flow__controls-button):hover { |
| 220 | + background: var(--bg-overlay); |
| 221 | + color: var(--text-1); |
| 222 | + fill: var(--text-1); |
| 223 | +} |
| 224 | +.controls :global(.react-flow__controls-button svg) { |
| 225 | + fill: currentColor; |
| 226 | +} |
| 227 | + |
| 228 | +/* Minimap: dark inset panel, hairline border; the out-of-view area dimmed by a |
| 229 | + * token overlay; dots carry the same status hue as the chips. */ |
| 230 | +.minimap { |
| 231 | + border: 1px solid var(--stroke-1); |
| 232 | + border-radius: var(--radius-2); |
| 233 | + background: var(--bg-inset); |
| 234 | + overflow: hidden; |
| 235 | +} |
| 236 | +.minimap :global(.react-flow__minimap-mask) { |
| 237 | + fill: var(--bg-inset); |
| 238 | + opacity: 0.6; |
| 239 | +} |
| 240 | +/* Status fills override React Flow's base .react-flow__minimap-node fill. */ |
| 241 | +.miniCrit { |
| 242 | + fill: var(--crit) !important; |
| 243 | +} |
| 244 | +.miniWarn { |
| 245 | + fill: var(--warn) !important; |
| 246 | +} |
| 247 | +.miniOk { |
| 248 | + fill: var(--ok) !important; |
| 249 | +} |
| 250 | +.miniUnknown { |
| 251 | + fill: var(--unknown) !important; |
| 252 | +} |
| 253 | + |
| 254 | +/* React Flow's native animated edges run on the library's own keyframes (not a |
| 255 | + * --dur token), so the global reduced-motion rule can't reach them. Stop the |
| 256 | + * marching dashes for users who ask for reduced motion — leaving a static |
| 257 | + * dashed edge and dropping the continuous edge-layer repaint for them. */ |
| 258 | +@media (prefers-reduced-motion: reduce) { |
| 259 | + :global(.react-flow__edge.animated path) { |
| 260 | + animation: none; |
| 261 | + } |
| 262 | +} |
| 263 | + |
0 commit comments