/* * React Flow's default style.css ships opinionated defaults for the * `.react-flow__node-group` selector — fixed width (150px), inner padding, * a translucent grey fill, a 1px solid border and a 3px radius. They * conflict with our hierarchical layout where Dagre already sizes and * positions the group container; the actual visual (border + label) is * drawn by our own `GroupNode` React component inside the wrapper. * * IMPORTANT: do NOT override width/height here — React Flow sets them * inline from the node `style` (which we compute in `layoutGraph`). We * only neutralise the cosmetic defaults so our inner `Box` is the only * thing the user actually sees. */ .react-flow__node-group { background-color: transparent !important; background: transparent !important; border: none !important; border-radius: 0 !important; padding: 0 !important; /* Groups are visual containers, not interaction targets. Edges and * children must remain clickable. */ pointer-events: none !important; } .react-flow__node-group.selectable:hover, .react-flow__node-group.selectable.selected, .react-flow__node-group.selectable:focus, .react-flow__node-group.selectable:focus-visible { box-shadow: none !important; } /* ────────────────────────────────────────────────────────────────────────── * Granularity transitions * * When the user drags the granularity slider, React Flow rebuilds the * scene with new node positions and (sometimes) new node identities. We * use three coordinated CSS effects to make the change feel smooth: * * 1. `transition: transform` on every node so any node that survives the * re-layout (stable id) glides to its new spot instead of teleporting. * 2. `transition` on `width`/`height` so group containers grow/shrink * fluidly when they collapse a layer's interior or reveal it. * 3. A short `nodeFadeIn` keyframe on mount so newly created nodes (the * children that "explode" out of a cluster, or the cluster that * replaces them on the way up) fade in instead of popping. * * Edges can't tween their SVG `d` attribute reliably, so we just fade * them in on mount via `edgeFadeIn`. * * The animation durations match the curve used by Material Design's * "standard" easing and stay short enough that the slider still feels * responsive when scrubbing quickly. * ────────────────────────────────────────────────────────────────────── */ .react-flow__node { transition: transform 450ms cubic-bezier(0.16, 1, 0.3, 1), width 350ms cubic-bezier(0.16, 1, 0.3, 1), height 350ms cubic-bezier(0.16, 1, 0.3, 1); animation: nodeFadeIn 280ms ease-out both; } /* Group containers shouldn't visibly fade in — the children inside already * carry the animation. Animating their opacity makes the inner nodes feel * "doubled up" during the transition. */ .react-flow__node-group { animation: none; } @keyframes nodeFadeIn { from { opacity: 0; } to { opacity: 1; } } .react-flow__edges { /* Fade the entire edges layer when the topology changes so that paths * which jump to wildly different routes don't look broken mid-animation. */ transition: opacity 220ms ease-out; } .react-flow__edge { animation: edgeFadeIn 320ms ease-out both; } @keyframes edgeFadeIn { from { opacity: 0; } to { opacity: 1; } } /* Respect the OS reduced-motion preference. */ @media (prefers-reduced-motion: reduce) { .react-flow__node, .react-flow__edges, .react-flow__edge { transition: none !important; animation: none !important; } }