From 59f4a1bb9006df19efbce36e48fac949a287c1ed Mon Sep 17 00:00:00 2001 From: Mayank Mohapatra <125661248+Mayank77maruti@users.noreply.github.com> Date: Sat, 22 Feb 2025 16:09:02 +0000 Subject: [PATCH] try --- d2layouts/d2cycle/layout.go | 96 ++++++----- .../txtar/cycle-diagram/dagre/board.exp.json | 24 +++ .../txtar/cycle-diagram/dagre/sketch.exp.svg | 152 +++++++++--------- .../txtar/cycle-diagram/elk/board.exp.json | 24 +++ .../txtar/cycle-diagram/elk/sketch.exp.svg | 152 +++++++++--------- 5 files changed, 259 insertions(+), 189 deletions(-) diff --git a/d2layouts/d2cycle/layout.go b/d2layouts/d2cycle/layout.go index c63eb2039..531464f0b 100644 --- a/d2layouts/d2cycle/layout.go +++ b/d2layouts/d2cycle/layout.go @@ -33,12 +33,13 @@ func Layout(ctx context.Context, g *d2graph.Graph, layout d2graph.LayoutGraph) e positionObjects(objects, radius) for _, edge := range g.Edges { - createCircularArc(edge) + createCircularArc(edge, radius) } return nil } +// calculateRadius determines the radius of the circular layout based on the number and size of objects. func calculateRadius(objects []*d2graph.Object) float64 { numObjects := float64(len(objects)) maxSize := 0.0 @@ -50,9 +51,10 @@ func calculateRadius(objects []*d2graph.Object) float64 { return math.Max(minRadius, MIN_RADIUS) } +// positionObjects arranges objects in a circular pattern around the origin. func positionObjects(objects []*d2graph.Object, radius float64) { numObjects := float64(len(objects)) - angleOffset := -math.Pi / 2 + angleOffset := -math.Pi / 2 // Start at the top of the circle for i, obj := range objects { angle := angleOffset + (2*math.Pi*float64(i)/numObjects) @@ -65,7 +67,8 @@ func positionObjects(objects []*d2graph.Object, radius float64) { } } -func createCircularArc(edge *d2graph.Edge) { +// createCircularArc generates a curved edge route with corrected arrow orientation. +func createCircularArc(edge *d2graph.Edge, radius float64) { if edge.Src == nil || edge.Dst == nil { return } @@ -73,32 +76,26 @@ func createCircularArc(edge *d2graph.Edge) { srcCenter := edge.Src.Center() dstCenter := edge.Dst.Center() - srcAngle := math.Atan2(srcCenter.Y, srcCenter.X) - dstAngle := math.Atan2(dstCenter.Y, dstCenter.X) - if dstAngle < srcAngle { - dstAngle += 2 * math.Pi - } + // Generate initial arc path from source center to destination center + path := generateArcPoints(srcCenter, dstCenter, radius, ARC_STEPS) - arcRadius := math.Hypot(srcCenter.X, srcCenter.Y) - - path := make([]*geo.Point, 0, ARC_STEPS+1) - for i := 0; i <= ARC_STEPS; i++ { - t := float64(i) / float64(ARC_STEPS) - angle := srcAngle + t*(dstAngle-srcAngle) - x := arcRadius * math.Cos(angle) - y := arcRadius * math.Sin(angle) - path = append(path, geo.NewPoint(x, y)) - } - path[0] = srcCenter - path[len(path)-1] = dstCenter - - // Clamp endpoints to the boundaries of the source and destination boxes. + // Clamp endpoints to the boundaries of the source and destination boxes _, newSrc := clampPointOutsideBox(edge.Src.Box, path, 0) _, newDst := clampPointOutsideBoxReverse(edge.Dst.Box, path, len(path)-1) path[0] = newSrc path[len(path)-1] = newDst - // Trim redundant path points that fall inside node boundaries. + // Add a point before newDst along the tangent direction to correct arrow orientation + if len(path) >= 2 { + dstAngle := math.Atan2(newDst.Y, newDst.X) + tangent := geo.NewPoint(-math.Sin(dstAngle), math.Cos(dstAngle)) + ε := 0.01 * radius // Small offset, e.g., 1% of radius + preDst := geo.NewPoint(newDst.X-ε*tangent.X, newDst.Y-ε*tangent.Y) + // Insert preDst before newDst + path = append(path[:len(path)-1], preDst, newDst) + } + + // Trim redundant path points that fall inside node boundaries path = trimPathPoints(path, edge.Src.Box) path = trimPathPoints(path, edge.Dst.Box) @@ -106,9 +103,34 @@ func createCircularArc(edge *d2graph.Edge) { edge.IsCurve = true } +// generateArcPoints creates points along a circular arc from src to dst. +func generateArcPoints(src, dst *geo.Point, radius float64, steps int) []*geo.Point { + // Calculate angles relative to the center (0,0) + srcAngle := math.Atan2(src.Y, src.X) + dstAngle := math.Atan2(dst.Y, dst.X) -// clampPointOutsideBox walks forward along the path until it finds a point outside the box, -// then replaces the point with a precise intersection. + // Ensure the arc goes the shorter way + if dstAngle < srcAngle { + dstAngle += 2 * math.Pi + } + angleDiff := dstAngle - srcAngle + if angleDiff > math.Pi { + dstAngle -= 2 * math.Pi + } + + // Generate points along the arc + path := make([]*geo.Point, 0, steps+1) + for i := 0; i <= steps; i++ { + t := float64(i) / float64(steps) + angle := srcAngle + t*(dstAngle-srcAngle) + x := radius * math.Cos(angle) + y := radius * math.Sin(angle) + path = append(path, geo.NewPoint(x, y)) + } + return path +} + +// clampPointOutsideBox finds the first point outside the box and computes the precise intersection. func clampPointOutsideBox(box *geo.Box, path []*geo.Point, startIdx int) (int, *geo.Point) { if startIdx >= len(path)-1 { return startIdx, path[startIdx] @@ -131,7 +153,7 @@ func clampPointOutsideBox(box *geo.Box, path []*geo.Point, startIdx int) (int, * return len(path)-1, path[len(path)-1] } -// clampPointOutsideBoxReverse works similarly but in reverse order. +// clampPointOutsideBoxReverse works similarly but traverses the path in reverse. func clampPointOutsideBoxReverse(box *geo.Box, path []*geo.Point, endIdx int) (int, *geo.Point) { if endIdx <= 0 { return endIdx, path[endIdx] @@ -154,8 +176,7 @@ func clampPointOutsideBoxReverse(box *geo.Box, path []*geo.Point, endIdx int) (i return 0, path[0] } -// findPreciseIntersection calculates intersection points between seg and all four sides of the box, -// then returns the intersection closest to seg.Start. +// findPreciseIntersection calculates the closest intersection between a segment and box boundaries. func findPreciseIntersection(box *geo.Box, seg geo.Segment) *geo.Point { intersections := []struct { point *geo.Point @@ -170,9 +191,9 @@ func findPreciseIntersection(box *geo.Box, seg geo.Segment) *geo.Point { dx := seg.End.X - seg.Start.X dy := seg.End.Y - seg.Start.Y - // Check vertical boundaries. + // Check vertical boundaries if dx != 0 { - // Left boundary. + // Left boundary t := (left - seg.Start.X) / dx if t >= 0 && t <= 1 { y := seg.Start.Y + t*dy @@ -183,7 +204,7 @@ func findPreciseIntersection(box *geo.Box, seg geo.Segment) *geo.Point { }{geo.NewPoint(left, y), t}) } } - // Right boundary. + // Right boundary t = (right - seg.Start.X) / dx if t >= 0 && t <= 1 { y := seg.Start.Y + t*dy @@ -196,9 +217,9 @@ func findPreciseIntersection(box *geo.Box, seg geo.Segment) *geo.Point { } } - // Check horizontal boundaries. + // Check horizontal boundaries if dy != 0 { - // Top boundary. + // Top boundary t := (top - seg.Start.Y) / dy if t >= 0 && t <= 1 { x := seg.Start.X + t*dx @@ -209,7 +230,7 @@ func findPreciseIntersection(box *geo.Box, seg geo.Segment) *geo.Point { }{geo.NewPoint(x, top), t}) } } - // Bottom boundary. + // Bottom boundary t = (bottom - seg.Start.Y) / dy if t >= 0 && t <= 1 { x := seg.Start.X + t*dx @@ -226,14 +247,14 @@ func findPreciseIntersection(box *geo.Box, seg geo.Segment) *geo.Point { return nil } - // Sort intersections by t (distance from seg.Start) and return the closest. + // Sort intersections by t (distance from seg.Start) and return the closest sort.Slice(intersections, func(i, j int) bool { return intersections[i].t < intersections[j].t }) return intersections[0].point } -// trimPathPoints removes intermediate points that fall inside the given box while preserving endpoints. +// trimPathPoints removes intermediate points inside the box while retaining endpoints. func trimPathPoints(path []*geo.Point, box *geo.Box) []*geo.Point { if len(path) <= 2 { return path @@ -248,7 +269,7 @@ func trimPathPoints(path []*geo.Point, box *geo.Box) []*geo.Point { return trimmed } -// boxContains uses strict inequalities so that points exactly on the boundary are considered outside. +// boxContains checks if a point is strictly inside the box (boundary points are outside). func boxContains(b *geo.Box, p *geo.Point) bool { return p.X > b.TopLeft.X && p.X < b.TopLeft.X+b.Width && @@ -256,6 +277,7 @@ func boxContains(b *geo.Box, p *geo.Point) bool { p.Y < b.TopLeft.Y+b.Height } +// positionLabelsIcons sets default positions for labels and icons on objects. func positionLabelsIcons(obj *d2graph.Object) { if obj.Icon != nil && obj.IconPosition == nil { if len(obj.ChildrenArray) > 0 { diff --git a/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json b/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json index 78ed2dffe..1453daf86 100644 --- a/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json @@ -867,6 +867,10 @@ "x": 197.02099609375, "y": -34.3849983215332 }, + { + "x": 196.9219970703125, + "y": -34.97200012207031 + }, { "x": 197.2519989013672, "y": -33 @@ -1231,6 +1235,10 @@ "x": 28.18000030517578, "y": 198.00399780273438 }, + { + "x": 28.48200035095215, + "y": 197.96499633789062 + }, { "x": 26.5, "y": 198.22999572753906 @@ -1595,6 +1603,10 @@ "x": -197.02099609375, "y": 34.3849983215332 }, + { + "x": -196.9219970703125, + "y": 34.97200012207031 + }, { "x": -197.2519989013672, "y": 33 @@ -1975,6 +1987,10 @@ "x": 701.875, "y": 115.77300262451172 }, + { + "x": 702.10302734375, + "y": 115.11499786376953 + }, { "x": 701.4329833984375, "y": 116.9990005493164 @@ -2339,6 +2355,10 @@ "x": 364.3710021972656, "y": 183.8260040283203 }, + { + "x": 364.97198486328125, + "y": 184.4929962158203 + }, { "x": 363.6419982910156, "y": 183 @@ -2743,6 +2763,10 @@ "x": 1003.2860107421875, "y": 197.53700256347656 }, + { + "x": 1000.4819946289062, + "y": 197.9530029296875 + }, { "x": 998.5, "y": 198.21800231933594 diff --git a/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg b/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg index 015478d74..be500c952 100644 --- a/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg @@ -1,9 +1,9 @@ -abcdabcab + .d2-1694830323 .fill-N1{fill:#0A0F25;} + .d2-1694830323 .fill-N2{fill:#676C7E;} + .d2-1694830323 .fill-N3{fill:#9499AB;} + .d2-1694830323 .fill-N4{fill:#CFD2DD;} + .d2-1694830323 .fill-N5{fill:#DEE1EB;} + .d2-1694830323 .fill-N6{fill:#EEF1F8;} + .d2-1694830323 .fill-N7{fill:#FFFFFF;} + .d2-1694830323 .fill-B1{fill:#0D32B2;} + .d2-1694830323 .fill-B2{fill:#0D32B2;} + .d2-1694830323 .fill-B3{fill:#E3E9FD;} + .d2-1694830323 .fill-B4{fill:#E3E9FD;} + .d2-1694830323 .fill-B5{fill:#EDF0FD;} + .d2-1694830323 .fill-B6{fill:#F7F8FE;} + .d2-1694830323 .fill-AA2{fill:#4A6FF3;} + .d2-1694830323 .fill-AA4{fill:#EDF0FD;} + .d2-1694830323 .fill-AA5{fill:#F7F8FE;} + .d2-1694830323 .fill-AB4{fill:#EDF0FD;} + .d2-1694830323 .fill-AB5{fill:#F7F8FE;} + .d2-1694830323 .stroke-N1{stroke:#0A0F25;} + .d2-1694830323 .stroke-N2{stroke:#676C7E;} + .d2-1694830323 .stroke-N3{stroke:#9499AB;} + .d2-1694830323 .stroke-N4{stroke:#CFD2DD;} + .d2-1694830323 .stroke-N5{stroke:#DEE1EB;} + .d2-1694830323 .stroke-N6{stroke:#EEF1F8;} + .d2-1694830323 .stroke-N7{stroke:#FFFFFF;} + .d2-1694830323 .stroke-B1{stroke:#0D32B2;} + .d2-1694830323 .stroke-B2{stroke:#0D32B2;} + .d2-1694830323 .stroke-B3{stroke:#E3E9FD;} + .d2-1694830323 .stroke-B4{stroke:#E3E9FD;} + .d2-1694830323 .stroke-B5{stroke:#EDF0FD;} + .d2-1694830323 .stroke-B6{stroke:#F7F8FE;} + .d2-1694830323 .stroke-AA2{stroke:#4A6FF3;} + .d2-1694830323 .stroke-AA4{stroke:#EDF0FD;} + .d2-1694830323 .stroke-AA5{stroke:#F7F8FE;} + .d2-1694830323 .stroke-AB4{stroke:#EDF0FD;} + .d2-1694830323 .stroke-AB5{stroke:#F7F8FE;} + .d2-1694830323 .background-color-N1{background-color:#0A0F25;} + .d2-1694830323 .background-color-N2{background-color:#676C7E;} + .d2-1694830323 .background-color-N3{background-color:#9499AB;} + .d2-1694830323 .background-color-N4{background-color:#CFD2DD;} + .d2-1694830323 .background-color-N5{background-color:#DEE1EB;} + .d2-1694830323 .background-color-N6{background-color:#EEF1F8;} + .d2-1694830323 .background-color-N7{background-color:#FFFFFF;} + .d2-1694830323 .background-color-B1{background-color:#0D32B2;} + .d2-1694830323 .background-color-B2{background-color:#0D32B2;} + .d2-1694830323 .background-color-B3{background-color:#E3E9FD;} + .d2-1694830323 .background-color-B4{background-color:#E3E9FD;} + .d2-1694830323 .background-color-B5{background-color:#EDF0FD;} + .d2-1694830323 .background-color-B6{background-color:#F7F8FE;} + .d2-1694830323 .background-color-AA2{background-color:#4A6FF3;} + .d2-1694830323 .background-color-AA4{background-color:#EDF0FD;} + .d2-1694830323 .background-color-AA5{background-color:#F7F8FE;} + .d2-1694830323 .background-color-AB4{background-color:#EDF0FD;} + .d2-1694830323 .background-color-AB5{background-color:#F7F8FE;} + .d2-1694830323 .color-N1{color:#0A0F25;} + .d2-1694830323 .color-N2{color:#676C7E;} + .d2-1694830323 .color-N3{color:#9499AB;} + .d2-1694830323 .color-N4{color:#CFD2DD;} + .d2-1694830323 .color-N5{color:#DEE1EB;} + .d2-1694830323 .color-N6{color:#EEF1F8;} + .d2-1694830323 .color-N7{color:#FFFFFF;} + .d2-1694830323 .color-B1{color:#0D32B2;} + .d2-1694830323 .color-B2{color:#0D32B2;} + .d2-1694830323 .color-B3{color:#E3E9FD;} + .d2-1694830323 .color-B4{color:#E3E9FD;} + .d2-1694830323 .color-B5{color:#EDF0FD;} + .d2-1694830323 .color-B6{color:#F7F8FE;} + .d2-1694830323 .color-AA2{color:#4A6FF3;} + .d2-1694830323 .color-AA4{color:#EDF0FD;} + .d2-1694830323 .color-AA5{color:#F7F8FE;} + .d2-1694830323 .color-AB4{color:#EDF0FD;} + .d2-1694830323 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-1694830323);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-1694830323);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-1694830323);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-1694830323);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-1694830323);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-1694830323);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-1694830323);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-1694830323);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-1694830323);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-1694830323);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-1694830323);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-1694830323);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-1694830323);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-1694830323);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-1694830323);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-1694830323);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-1694830323);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-1694830323);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>abcdabcab diff --git a/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json b/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json index 6eb058a84..d03145822 100644 --- a/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json +++ b/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json @@ -867,6 +867,10 @@ "x": 209.02099609375, "y": -22.385000228881836 }, + { + "x": 208.9219970703125, + "y": -22.972000122070312 + }, { "x": 209.2519989013672, "y": -21 @@ -1231,6 +1235,10 @@ "x": 40.18000030517578, "y": 210.00399780273438 }, + { + "x": 40.481998443603516, + "y": 209.96499633789062 + }, { "x": 38.5, "y": 210.22999572753906 @@ -1595,6 +1603,10 @@ "x": -185.02099609375, "y": 46.3849983215332 }, + { + "x": -184.9219970703125, + "y": 46.97200012207031 + }, { "x": -185.2519989013672, "y": 45 @@ -1975,6 +1987,10 @@ "x": 674.375, "y": 127.77300262451172 }, + { + "x": 674.60302734375, + "y": 127.11499786376953 + }, { "x": 673.9329833984375, "y": 128.99899291992188 @@ -2339,6 +2355,10 @@ "x": 336.8710021972656, "y": 195.8260040283203 }, + { + "x": 337.47198486328125, + "y": 196.4929962158203 + }, { "x": 336.1419982910156, "y": 195 @@ -2743,6 +2763,10 @@ "x": 936.197021484375, "y": 209.53700256347656 }, + { + "x": 933.3920288085938, + "y": 209.9530029296875 + }, { "x": 931.4099731445312, "y": 210.21800231933594 diff --git a/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg b/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg index 21e65f752..a167e907c 100644 --- a/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg +++ b/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg @@ -1,9 +1,9 @@ -abcdabcab + .d2-2388635244 .fill-N1{fill:#0A0F25;} + .d2-2388635244 .fill-N2{fill:#676C7E;} + .d2-2388635244 .fill-N3{fill:#9499AB;} + .d2-2388635244 .fill-N4{fill:#CFD2DD;} + .d2-2388635244 .fill-N5{fill:#DEE1EB;} + .d2-2388635244 .fill-N6{fill:#EEF1F8;} + .d2-2388635244 .fill-N7{fill:#FFFFFF;} + .d2-2388635244 .fill-B1{fill:#0D32B2;} + .d2-2388635244 .fill-B2{fill:#0D32B2;} + .d2-2388635244 .fill-B3{fill:#E3E9FD;} + .d2-2388635244 .fill-B4{fill:#E3E9FD;} + .d2-2388635244 .fill-B5{fill:#EDF0FD;} + .d2-2388635244 .fill-B6{fill:#F7F8FE;} + .d2-2388635244 .fill-AA2{fill:#4A6FF3;} + .d2-2388635244 .fill-AA4{fill:#EDF0FD;} + .d2-2388635244 .fill-AA5{fill:#F7F8FE;} + .d2-2388635244 .fill-AB4{fill:#EDF0FD;} + .d2-2388635244 .fill-AB5{fill:#F7F8FE;} + .d2-2388635244 .stroke-N1{stroke:#0A0F25;} + .d2-2388635244 .stroke-N2{stroke:#676C7E;} + .d2-2388635244 .stroke-N3{stroke:#9499AB;} + .d2-2388635244 .stroke-N4{stroke:#CFD2DD;} + .d2-2388635244 .stroke-N5{stroke:#DEE1EB;} + .d2-2388635244 .stroke-N6{stroke:#EEF1F8;} + .d2-2388635244 .stroke-N7{stroke:#FFFFFF;} + .d2-2388635244 .stroke-B1{stroke:#0D32B2;} + .d2-2388635244 .stroke-B2{stroke:#0D32B2;} + .d2-2388635244 .stroke-B3{stroke:#E3E9FD;} + .d2-2388635244 .stroke-B4{stroke:#E3E9FD;} + .d2-2388635244 .stroke-B5{stroke:#EDF0FD;} + .d2-2388635244 .stroke-B6{stroke:#F7F8FE;} + .d2-2388635244 .stroke-AA2{stroke:#4A6FF3;} + .d2-2388635244 .stroke-AA4{stroke:#EDF0FD;} + .d2-2388635244 .stroke-AA5{stroke:#F7F8FE;} + .d2-2388635244 .stroke-AB4{stroke:#EDF0FD;} + .d2-2388635244 .stroke-AB5{stroke:#F7F8FE;} + .d2-2388635244 .background-color-N1{background-color:#0A0F25;} + .d2-2388635244 .background-color-N2{background-color:#676C7E;} + .d2-2388635244 .background-color-N3{background-color:#9499AB;} + .d2-2388635244 .background-color-N4{background-color:#CFD2DD;} + .d2-2388635244 .background-color-N5{background-color:#DEE1EB;} + .d2-2388635244 .background-color-N6{background-color:#EEF1F8;} + .d2-2388635244 .background-color-N7{background-color:#FFFFFF;} + .d2-2388635244 .background-color-B1{background-color:#0D32B2;} + .d2-2388635244 .background-color-B2{background-color:#0D32B2;} + .d2-2388635244 .background-color-B3{background-color:#E3E9FD;} + .d2-2388635244 .background-color-B4{background-color:#E3E9FD;} + .d2-2388635244 .background-color-B5{background-color:#EDF0FD;} + .d2-2388635244 .background-color-B6{background-color:#F7F8FE;} + .d2-2388635244 .background-color-AA2{background-color:#4A6FF3;} + .d2-2388635244 .background-color-AA4{background-color:#EDF0FD;} + .d2-2388635244 .background-color-AA5{background-color:#F7F8FE;} + .d2-2388635244 .background-color-AB4{background-color:#EDF0FD;} + .d2-2388635244 .background-color-AB5{background-color:#F7F8FE;} + .d2-2388635244 .color-N1{color:#0A0F25;} + .d2-2388635244 .color-N2{color:#676C7E;} + .d2-2388635244 .color-N3{color:#9499AB;} + .d2-2388635244 .color-N4{color:#CFD2DD;} + .d2-2388635244 .color-N5{color:#DEE1EB;} + .d2-2388635244 .color-N6{color:#EEF1F8;} + .d2-2388635244 .color-N7{color:#FFFFFF;} + .d2-2388635244 .color-B1{color:#0D32B2;} + .d2-2388635244 .color-B2{color:#0D32B2;} + .d2-2388635244 .color-B3{color:#E3E9FD;} + .d2-2388635244 .color-B4{color:#E3E9FD;} + .d2-2388635244 .color-B5{color:#EDF0FD;} + .d2-2388635244 .color-B6{color:#F7F8FE;} + .d2-2388635244 .color-AA2{color:#4A6FF3;} + .d2-2388635244 .color-AA4{color:#EDF0FD;} + .d2-2388635244 .color-AA5{color:#F7F8FE;} + .d2-2388635244 .color-AB4{color:#EDF0FD;} + .d2-2388635244 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-2388635244);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-2388635244);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-2388635244);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-2388635244);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-2388635244);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-2388635244);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-2388635244);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-2388635244);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-2388635244);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-2388635244);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-2388635244);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-2388635244);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-2388635244);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-2388635244);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-2388635244);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-2388635244);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-2388635244);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-2388635244);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]>abcdabcab