-
Notifications
You must be signed in to change notification settings - Fork 56
Expand file tree
/
Copy pathflow-diagram-example.html
More file actions
757 lines (725 loc) · 88.9 KB
/
Copy pathflow-diagram-example.html
File metadata and controls
757 lines (725 loc) · 88.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Freight & Package Delivery Logistics Platform — Flow Diagram Example</title>
<script>try{const t=localStorage.getItem('theme');if(t==='light'||t==='dark')document.documentElement.dataset.theme=t;}catch(e){}</script>
<style>
/* "Big Tech docs" style · 2-pane app layout: LEFT diagram always FIT-TO-VIEW (whole system
visible at once) with zoom/pan · RIGHT sidebar with explainer cards. Light/dark follows the
OS (light-dark()). Inside the SVG ALWAYS write LIGHT-palette hex — the "color mapping" block
below remaps it per theme automatically. */
:root {
color-scheme: light dark;
--bg: light-dark(#f6f8fa, #0f141c); --panel: light-dark(#ffffff, #151c29);
--line: light-dark(#d1d5db, #394456); --line-soft: light-dark(#e5e7eb, #273143);
--text: light-dark(#1f2937, #e2e8f0); --text2: light-dark(#374151, #cbd5e1);
--muted: light-dark(#6b7280, #94a3b8); --dim: light-dark(#9ca3af, #64748b);
--di: light-dark(#2563eb, #60a5fa); /* OUT direction (request), solid line */
--ve: light-dark(#059669, #34d399); /* RETURN direction (response), ALWAYS dashed */
--sec: light-dark(#dc2626, #f87171); /* auth checkpoint / security */
--script: light-dark(#7c3aed, #a78bfa); /* scheduled / batch flow */
--pipe: light-dark(#d97706, #fbbf24); /* data pipeline */
--sky: light-dark(#0284c7, #38bdf8); /* data store / API */
--fx: light-dark(#c026d3, #e879f9); /* content generation */
--tint-di: light-dark(#eff6ff, #1a2740); --tint-sec: light-dark(#fef2f2, #321b24);
--tint-sky: light-dark(#f0f9ff, #14293a); --hl: light-dark(#fef9c3, #423c14);
}
:root[data-theme=light] { color-scheme: light; }
:root[data-theme=dark] { color-scheme: dark; }
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif; }
.app { height: 100vh; display: flex; flex-direction: column; gap: 10px; padding: 16px clamp(14px, 2vw, 36px); }
.head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
h1 { font-size: 19px; font-weight: 650; letter-spacing: -0.01em; margin: 0 0 4px; }
.sub { color: var(--muted); font-size: 12.5px; line-height: 1.55; }
.theme-btn { flex: none; display: inline-flex; align-items: center; justify-content: center;
width: 34px; height: 34px; padding: 0; border-radius: 10px; color: var(--text);
background: var(--panel); border: 1px solid var(--line-soft); cursor: pointer; }
.theme-btn svg { width: 16px; height: 16px; }
.theme-btn:hover { border-color: var(--line); }
.edit-btn { flex: none; font-size: 12.5px; line-height: 1; padding: 8px 12px; border-radius: 10px;
background: var(--panel); color: var(--text); border: 1px solid var(--line-soft); cursor: pointer; }
.edit-btn:hover { border-color: var(--line); }
.edit-btn.on { border-color: var(--di); color: var(--di); box-shadow: 0 0 0 1px var(--di) inset; }
/* Rearrange mode: boxes get a "move" cursor, traveling dots hidden while active (their path only
needs to refresh once the drag ends) */
svg.edit-mode g[data-flows] { cursor: move; }
svg.edit-mode .flow-dot { display: none; }
/* Chips = color legend + flow-select buttons */
.chips { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.chip { display: inline-flex; align-items: center; gap: 7px; padding: 6px 12px; font-size: 12.5px;
background: var(--panel); color: var(--text); border: 1px solid var(--line-soft);
border-radius: 999px; cursor: pointer; font-family: inherit; transition: all .15s; }
.chip:hover { border-color: var(--line); box-shadow: 0 1px 3px rgba(0,0,0,.08); }
.chip.on { border-color: currentColor; box-shadow: 0 0 0 1px currentColor inset; }
.chip .dot { width: 9px; height: 9px; border-radius: 50%; background: currentColor; }
.chip span { color: var(--text); }
.hint { font-size: 12px; color: var(--muted); margin-left: 4px; }
/* 2 panes: left = diagram (fit-to-view + zoom/pan), right = info sidebar */
.split { flex: 1; min-height: 0; display: flex; gap: 14px; }
.board { flex: 1; min-width: 0; position: relative; background: var(--panel);
border: 1px solid var(--line-soft); border-radius: 14px; box-shadow: 0 1px 3px rgba(16,24,40,.06); }
.canvas { position: absolute; inset: 0; overflow: auto; border-radius: 14px; display: flex; cursor: grab; }
.canvas.panning { cursor: grabbing; user-select: none; }
.canvas svg { margin: auto; display: block; flex: none; }
.zoombar { position: absolute; top: 10px; right: 18px; z-index: 5; display: flex; align-items: center; gap: 2px;
background: var(--panel); border: 1px solid var(--line-soft); border-radius: 10px;
padding: 3px 5px; box-shadow: 0 2px 8px rgba(0,0,0,.12); }
.zoombar button { border: 0; background: none; color: var(--text); font-size: 14px; width: 26px; height: 24px;
border-radius: 7px; cursor: pointer; }
.zoombar button:hover { background: var(--line-soft); }
.zoombar .pct { font-size: 11px; color: var(--muted); min-width: 42px; text-align: center; }
.side { width: min(380px, 34vw); flex: none; overflow: auto; }
/* Halo behind every label in the SVG — a line can pass close to text and the text stays legible */
svg text { paint-order: stroke; stroke: var(--panel); stroke-width: 3px; stroke-linejoin: round; }
/* SVG color remap per theme: markup always writes LIGHT-palette hex, CSS wins over the
presentation attribute. NEVER write var() inside an attribute, never invent a hex outside
this table. */
svg [stroke="#2563eb"] { stroke: var(--di); } svg [fill="#2563eb"] { fill: var(--di); }
svg [stroke="#059669"] { stroke: var(--ve); } svg [fill="#059669"] { fill: var(--ve); }
svg [stroke="#dc2626"] { stroke: var(--sec); } svg [fill="#dc2626"] { fill: var(--sec); }
svg [stroke="#7c3aed"] { stroke: var(--script); } svg [fill="#7c3aed"] { fill: var(--script); }
svg [stroke="#d97706"] { stroke: var(--pipe); } svg [fill="#d97706"] { fill: var(--pipe); }
svg [stroke="#0284c7"] { stroke: var(--sky); } svg [fill="#0284c7"] { fill: var(--sky); }
svg [stroke="#c026d3"] { stroke: var(--fx); } svg [fill="#c026d3"] { fill: var(--fx); }
svg [stroke="#d1d5db"] { stroke: var(--line); } svg [stroke="#e5e7eb"] { stroke: var(--line-soft); }
svg [fill="#ffffff"] { fill: var(--panel); } svg [fill="#eff6ff"] { fill: var(--tint-di); }
svg [fill="#fef2f2"] { fill: var(--tint-sec); } svg [fill="#f0f9ff"] { fill: var(--tint-sky); }
svg [fill="#1f2937"] { fill: var(--text); } svg [fill="#374151"] { fill: var(--text2); }
svg [fill="#6b7280"] { fill: var(--muted); }
/* Interaction: selecting a flow dims everything else, the arrows "flow" in their direction */
svg [data-flow] { cursor: pointer; }
svg :is(line, path)[data-flow]:hover { stroke-width: 3; }
svg .dim { opacity: .1; transition: opacity .25s; }
/* Traveling dot along the line (Kiali/Istio traffic-map convention — a dot moving on the edge,
not a fake "marching ants" dashed line) — only visible once its own flow is selected. See the
"Traveling dots" section below. */
svg .flow-dot { opacity: 0; transition: opacity .25s; }
svg .flow-dot.flowing { opacity: 1; }
@keyframes flow-ping { 0% { transform: scale(1); opacity: .35; } 65% { transform: scale(1.05); opacity: 0; } 100% { transform: scale(1.05); opacity: 0; } }
svg .flow-ping-ring { transform-box: fill-box; transform-origin: center; animation: flow-ping 2.6s ease-out infinite; pointer-events: none; }
svg .flow-ping { opacity: 0; }
svg .flow-ping.flowing { opacity: 1; }
@media (prefers-reduced-motion: reduce) { svg .flow-dot { display: none; }
svg .flow-ping { display: none; } }
/* Sidebar explainer cards — selecting a flow leaves only that flow's card visible */
.cols { display: flex; flex-direction: column; gap: 12px; }
.card { background: var(--panel); border: 1px solid var(--line-soft); border-radius: 12px;
padding: 15px 17px; font-size: 12.5px; line-height: 1.75; box-shadow: 0 1px 2px rgba(16,24,40,.05); }
.card.hidden { display: none; }
.card li.hl { background: var(--hl); border-radius: 4px; color: var(--text2); }
.card h3 { margin: 0 0 8px; font-size: 13.5px; font-weight: 650; display: flex; align-items: center; gap: 8px; }
.card .dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: none; }
.card ol, .card ul { margin: 0; padding-left: 18px; color: var(--muted); }
.card li { margin-bottom: 3px; }
.card b { color: var(--text); font-weight: 600; }
.n1 { color: var(--di); font-weight: 700; }
.n2 { color: var(--ve); font-weight: 700; }
.n3 { color: var(--script); font-weight: 700; }
.n4 { color: var(--pipe); font-weight: 700; }
.n5 { color: var(--sec); font-weight: 700; }
@media screen and (max-width: 900px) {
.app { height: auto; }
.split { flex-direction: column; }
.board { flex: none; height: 62vh; }
.side { width: auto; }
}
@media print {
.hint, .theme-btn, .zoombar { display: none; }
:root, :root[data-theme=dark] { color-scheme: light; }
svg .dim { opacity: 1; }
svg .flow-dot { display: none; }
.app { height: auto; }
.split { display: block; }
.board { position: static; box-shadow: none; }
.canvas { position: static; overflow: visible; }
.canvas svg { width: 100% !important; height: auto !important; }
.side { width: auto; overflow: visible; }
.card { break-inside: avoid; margin-top: 10px; }
.card.hidden { display: block; }
}
</style>
</head>
<body>
<div class="app">
<div class="head">
<div>
<h1>Freight & Package Delivery Logistics Platform — end-to-end flows</h1>
<div class="sub">Reference example for the flow-diagram skill · solid blue = OUT direction · dashed green = RETURN direction · red = ID/PIN checkpoint · small arc = two lines crossing, not joining</div>
</div>
<div style="display:flex; gap:8px;">
<button id="edit-toggle" class="edit-btn" title="Toggle drag-to-rearrange mode">✥ Rearrange</button>
<button id="theme-toggle" class="theme-btn" aria-label="Toggle light/dark mode"></button>
</div>
</div>
<div class="chips">
<button class="chip" data-flow="b" style="color: var(--di)"><i class="dot"></i><span>Booking → delivery (1→14)</span></button>
<button class="chip" data-flow="c" style="color: var(--sky)"><i class="dot"></i><span>Partner integration API (C)</span></button>
<button class="chip" data-flow="d" style="color: var(--pipe)"><i class="dot"></i><span>Analytics pipeline (D)</span></button>
<button class="chip" data-flow="s" style="color: var(--script)"><i class="dot"></i><span>Driver settlement job (S)</span></button>
<button class="chip" data-flow="m" style="color: var(--fx)"><i class="dot"></i><span>Document generation (M)</span></button>
<span class="hint">Click a flow to see it run end to end · Esc to clear · scroll to zoom · drag to pan</span>
</div>
<div class="split">
<section class="board">
<div class="zoombar">
<button data-z="out" title="Zoom out">−</button>
<span class="pct">100%</span>
<button data-z="in" title="Zoom in">+</button>
<button data-z="fit" title="Fit to view — see the whole system">⤢</button>
</div>
<div class="canvas" id="canvas">
<svg viewBox="0 0 1240 1200" xmlns="http://www.w3.org/2000/svg" font-family="inherit">
<!-- LAYOUT PLAN: box columns x=40/300/620/750/980 · rows y=40/230/390/530/660/730/830/990/1108
vertical corridors: x=545 (S2 db read/write) · x=560/584 (D5/D6 self-serve analytics query loop)
x=640 (M2 return) · x=670 (C5 write-back hop) · x=710 (M1 forward)
cross-group crossings (hops): C5 × (D5,D6) @ y=750 · D1 pipeline × (D5,D6) @ y=880 ·
M1 × M2 @ x=662–678
(the customer-facing left-margin return corridor from the S flow, used in an earlier
draft, was removed — the driver-settlement job has no customer-facing leg) -->
<defs>
<marker id="m-di" markerWidth="9" markerHeight="9" refX="7.5" refY="4.5" orient="auto"><path d="M1,1 L8,4.5 L1,8" fill="none" stroke="#2563eb" stroke-width="1.6"/></marker>
<marker id="m-ve" markerWidth="9" markerHeight="9" refX="7.5" refY="4.5" orient="auto"><path d="M1,1 L8,4.5 L1,8" fill="none" stroke="#059669" stroke-width="1.6"/></marker>
<marker id="m-sec" markerWidth="9" markerHeight="9" refX="7.5" refY="4.5" orient="auto"><path d="M1,1 L8,4.5 L1,8" fill="none" stroke="#dc2626" stroke-width="1.6"/></marker>
<marker id="m-sc" markerWidth="9" markerHeight="9" refX="7.5" refY="4.5" orient="auto"><path d="M1,1 L8,4.5 L1,8" fill="none" stroke="#7c3aed" stroke-width="1.6"/></marker>
<marker id="m-pi" markerWidth="9" markerHeight="9" refX="7.5" refY="4.5" orient="auto"><path d="M1,1 L8,4.5 L1,8" fill="none" stroke="#d97706" stroke-width="1.6"/></marker>
<!-- Brand logos (Simple Icons, CC0) — copied from assets/icons-library.html -->
<symbol id="ic-mongodb" viewBox="0 0 24 24"><path fill="#47A248" d="M17.193 9.555c-1.264-5.58-4.252-7.414-4.573-8.115-.28-.394-.53-.954-.735-1.44-.036.495-.055.685-.523 1.184-.723.566-4.438 3.682-4.74 10.02-.282 5.912 4.27 9.435 4.888 9.884l.07.05A73.49 73.49 0 0111.91 24h.481c.114-1.032.284-2.056.51-3.07.417-.296.604-.463.85-.693a11.342 11.342 0 003.639-8.464c.01-.814-.103-1.662-.197-2.218zm-5.336 8.195s0-8.291.275-8.29c.213 0 .49 10.695.49 10.695-.381-.045-.765-1.76-.765-2.405z"/></symbol>
<symbol id="ic-postgresql" viewBox="0 0 24 24"><path fill="#4169E1" d="M23.5594 14.7228a.5269.5269 0 0 0-.0563-.1191c-.139-.2632-.4768-.3418-1.0074-.2321-1.6533.3411-2.2935.1312-2.5256-.0191 1.342-2.0482 2.445-4.522 3.0411-6.8297.2714-1.0507.7982-3.5237.1222-4.7316a1.5641 1.5641 0 0 0-.1509-.235C21.6931.9086 19.8007.0248 17.5099.0005c-1.4947-.0158-2.7705.3461-3.1161.4794a9.449 9.449 0 0 0-.5159-.0816 8.044 8.044 0 0 0-1.3114-.1278c-1.1822-.0184-2.2038.2642-3.0498.8406-.8573-.3211-4.7888-1.645-7.2219.0788C.9359 2.1526.3086 3.8733.4302 6.3043c.0409.818.5069 3.334 1.2423 5.7436.4598 1.5065.9387 2.7019 1.4334 3.582.553.9942 1.1259 1.5933 1.7143 1.7895.4474.1491 1.1327.1441 1.8581-.7279.8012-.9635 1.5903-1.8258 1.9446-2.2069.4351.2355.9064.3625 1.39.3772a.0569.0569 0 0 0 .0004.0041 11.0312 11.0312 0 0 0-.2472.3054c-.3389.4302-.4094.5197-1.5002.7443-.3102.064-1.1344.2339-1.1464.8115-.0025.1224.0329.2309.0919.3268.2269.4231.9216.6097 1.015.6331 1.3345.3335 2.5044.092 3.3714-.6787-.017 2.231.0775 4.4174.3454 5.0874.2212.5529.7618 1.9045 2.4692 1.9043.2505 0 .5263-.0291.8296-.0941 1.7819-.3821 2.5557-1.1696 2.855-2.9059.1503-.8707.4016-2.8753.5388-4.1012.0169-.0703.0357-.1207.057-.1362.0007-.0005.0697-.0471.4272.0307a.3673.3673 0 0 0 .0443.0068l.2539.0223.0149.001c.8468.0384 1.9114-.1426 2.5312-.4308.6438-.2988 1.8057-1.0323 1.5951-1.6698zM2.371 11.8765c-.7435-2.4358-1.1779-4.8851-1.2123-5.5719-.1086-2.1714.4171-3.6829 1.5623-4.4927 1.8367-1.2986 4.8398-.5408 6.108-.13-.0032.0032-.0066.0061-.0098.0094-2.0238 2.044-1.9758 5.536-1.9708 5.7495-.0002.0823.0066.1989.0162.3593.0348.5873.0996 1.6804-.0735 2.9184-.1609 1.1504.1937 2.2764.9728 3.0892.0806.0841.1648.1631.2518.2374-.3468.3714-1.1004 1.1926-1.9025 2.1576-.5677.6825-.9597.5517-1.0886.5087-.3919-.1307-.813-.5871-1.2381-1.3223-.4796-.839-.9635-2.0317-1.4155-3.5126zm6.0072 5.0871c-.1711-.0428-.3271-.1132-.4322-.1772.0889-.0394.2374-.0902.4833-.1409 1.2833-.2641 1.4815-.4506 1.9143-1.0002.0992-.126.2116-.2687.3673-.4426a.3549.3549 0 0 0 .0737-.1298c.1708-.1513.2724-.1099.4369-.0417.156.0646.3078.26.3695.4752.0291.1016.0619.2945-.0452.4444-.9043 1.2658-2.2216 1.2494-3.1676 1.0128zm2.094-3.988-.0525.141c-.133.3566-.2567.6881-.3334 1.003-.6674-.0021-1.3168-.2872-1.8105-.8024-.6279-.6551-.9131-1.5664-.7825-2.5004.1828-1.3079.1153-2.4468.079-3.0586-.005-.0857-.0095-.1607-.0122-.2199.2957-.2621 1.6659-.9962 2.6429-.7724.4459.1022.7176.4057.8305.928.5846 2.7038.0774 3.8307-.3302 4.7363-.084.1866-.1633.3629-.2311.5454zm7.3637 4.5725c-.0169.1768-.0358.376-.0618.5959l-.146.4383a.3547.3547 0 0 0-.0182.1077c-.0059.4747-.054.6489-.115.8693-.0634.2292-.1353.4891-.1794 1.0575-.11 1.4143-.8782 2.2267-2.4172 2.5565-1.5155.3251-1.7843-.4968-2.0212-1.2217a6.5824 6.5824 0 0 0-.0769-.2266c-.2154-.5858-.1911-1.4119-.1574-2.5551.0165-.5612-.0249-1.9013-.3302-2.6462.0044-.2932.0106-.5909.019-.8918a.3529.3529 0 0 0-.0153-.1126 1.4927 1.4927 0 0 0-.0439-.208c-.1226-.4283-.4213-.7866-.7797-.9351-.1424-.059-.4038-.1672-.7178-.0869.067-.276.1831-.5875.309-.9249l.0529-.142c.0595-.16.134-.3257.213-.5012.4265-.9476 1.0106-2.2453.3766-5.1772-.2374-1.0981-1.0304-1.6343-2.2324-1.5098-.7207.0746-1.3799.3654-1.7088.5321a5.6716 5.6716 0 0 0-.1958.1041c.0918-1.1064.4386-3.1741 1.7357-4.4823a4.0306 4.0306 0 0 1 .3033-.276.3532.3532 0 0 0 .1447-.0644c.7524-.5706 1.6945-.8506 2.802-.8325.4091.0067.8017.0339 1.1742.081 1.939.3544 3.2439 1.4468 4.0359 2.3827.8143.9623 1.2552 1.9315 1.4312 2.4543-1.3232-.1346-2.2234.1268-2.6797.779-.9926 1.4189.543 4.1729 1.2811 5.4964.1353.2426.2522.4522.2889.5413.2403.5825.5515.9713.7787 1.2552.0696.087.1372.1714.1885.245-.4008.1155-1.1208.3825-1.0552 1.717-.0123.1563-.0423.4469-.0834.8148-.0461.2077-.0702.4603-.0994.7662zm.8905-1.6211c-.0405-.8316.2691-.9185.5967-1.0105a2.8566 2.8566 0 0 0 .135-.0406 1.202 1.202 0 0 0 .1342.103c.5703.3765 1.5823.4213 3.0068.1344-.2016.1769-.5189.3994-.9533.6011-.4098.1903-1.0957.333-1.7473.3636-.7197.0336-1.0859-.0807-1.1721-.151zm.5695-9.2712c-.0059.3508-.0542.6692-.1054 1.0017-.055.3576-.112.7274-.1264 1.1762-.0142.4368.0404.8909.0932 1.3301.1066.887.216 1.8003-.2075 2.7014a3.5272 3.5272 0 0 1-.1876-.3856c-.0527-.1276-.1669-.3326-.3251-.6162-.6156-1.1041-2.0574-3.6896-1.3193-4.7446.3795-.5427 1.3408-.5661 2.1781-.463zm.2284 7.0137a12.3762 12.3762 0 0 0-.0853-.1074l-.0355-.0444c.7262-1.1995.5842-2.3862.4578-3.4385-.0519-.4318-.1009-.8396-.0885-1.2226.0129-.4061.0666-.7543.1185-1.0911.0639-.415.1288-.8443.1109-1.3505.0134-.0531.0188-.1158.0118-.1902-.0457-.4855-.5999-1.938-1.7294-3.253-.6076-.7073-1.4896-1.4972-2.6889-2.0395.5251-.1066 1.2328-.2035 2.0244-.1859 2.0515.0456 3.6746.8135 4.8242 2.2824a.908.908 0 0 1 .0667.1002c.7231 1.3556-.2762 6.2751-2.9867 10.5405zm-8.8166-6.1162c-.025.1794-.3089.4225-.6211.4225a.5821.5821 0 0 1-.0809-.0056c-.1873-.026-.3765-.144-.5059-.3156-.0458-.0605-.1203-.178-.1055-.2844.0055-.0401.0261-.0985.0925-.1488.1182-.0894.3518-.1226.6096-.0867.3163.0441.6426.1938.6113.4186zm7.9305-.4114c.0111.0792-.049.201-.1531.3102-.0683.0717-.212.1961-.4079.2232a.5456.5456 0 0 1-.075.0052c-.2935 0-.5414-.2344-.5607-.3717-.024-.1765.2641-.3106.5611-.352.297-.0414.6111.0088.6356.1851z"/></symbol>
<symbol id="ic-n8n" viewBox="0 0 24 24"><path fill="#EA4B71" d="M21.4737 5.6842c-1.1772 0-2.1663.8051-2.4468 1.8947h-2.8955c-1.235 0-2.289.893-2.492 2.111l-.1038.623a1.263 1.263 0 0 1-1.246 1.0555H11.289c-.2805-1.0896-1.2696-1.8947-2.4468-1.8947s-2.1663.8051-2.4467 1.8947H4.973c-.2805-1.0896-1.2696-1.8947-2.4468-1.8947C1.1311 9.4737 0 10.6047 0 12s1.131 2.5263 2.5263 2.5263c1.1772 0 2.1663-.8051 2.4468-1.8947h1.4223c.2804 1.0896 1.2696 1.8947 2.4467 1.8947 1.1772 0 2.1663-.8051 2.4468-1.8947h1.0008a1.263 1.263 0 0 1 1.2459 1.0555l.1038.623c.203 1.218 1.257 2.111 2.492 2.111h.3692c.2804 1.0895 1.2696 1.8947 2.4468 1.8947 1.3952 0 2.5263-1.131 2.5263-2.5263s-1.131-2.5263-2.5263-2.5263c-1.1772 0-2.1664.805-2.4468 1.8947h-.3692a1.263 1.263 0 0 1-1.246-1.0555l-.1037-.623A2.52 2.52 0 0 0 13.9607 12a2.52 2.52 0 0 0 .821-1.4794l.1038-.623a1.263 1.263 0 0 1 1.2459-1.0555h2.8955c.2805 1.0896 1.2696 1.8947 2.4468 1.8947 1.3952 0 2.5263-1.131 2.5263-2.5263s-1.131-2.5263-2.5263-2.5263m0 1.2632a1.263 1.263 0 0 1 1.2631 1.2631 1.263 1.263 0 0 1-1.2631 1.2632 1.263 1.263 0 0 1-1.2632-1.2632 1.263 1.263 0 0 1 1.2632-1.2631M2.5263 10.7368A1.263 1.263 0 0 1 3.7895 12a1.263 1.263 0 0 1-1.2632 1.2632A1.263 1.263 0 0 1 1.2632 12a1.263 1.263 0 0 1 1.2631-1.2632m6.3158 0A1.263 1.263 0 0 1 10.1053 12a1.263 1.263 0 0 1-1.2632 1.2632A1.263 1.263 0 0 1 7.579 12a1.263 1.263 0 0 1 1.2632-1.2632m10.1053 3.7895a1.263 1.263 0 0 1 1.2631 1.2632 1.263 1.263 0 0 1-1.2631 1.2631 1.263 1.263 0 0 1-1.2632-1.2631 1.263 1.263 0 0 1 1.2632-1.2632"/></symbol>
<symbol id="ic-fastapi" viewBox="0 0 24 24"><path fill="#009688" d="M12 .0387C5.3729.0384.0003 5.3931 0 11.9988c-.001 6.6066 5.372 11.9628 12 11.9625 6.628.0003 12.001-5.3559 12-11.9625-.0003-6.6057-5.3729-11.9604-12-11.96m-.829 5.4153h7.55l-7.5805 5.3284h5.1828L5.279 18.5436q2.9466-6.5444 5.892-13.0896"/></symbol>
<symbol id="ic-discord" viewBox="0 0 24 24"><path fill="#5865F2" d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></symbol>
<symbol id="ic-apacheairflow" viewBox="0 0 24 24"><path fill="#017CEE" d="M17.195 16.822l4.002-4.102C23.55 10.308 23.934 5.154 24 .43a.396.396 0 0 0-.246-.373.392.392 0 0 0-.437.09l-6.495 6.658-4.102-4.003C10.309.45 5.154.066.43 0H.423a.397.397 0 0 0-.277.683l6.658 6.494-4.003 4.103C.45 13.692.065 18.846 0 23.57a.398.398 0 0 0 .683.282l6.494-6.657 3.934 3.837.17.165c2.41 2.353 7.565 2.737 12.288 2.803h.006a.397.397 0 0 0 .277-.683l-6.657-6.495zm-.409-9.476c.04.115.05.24.031.344-.17.96-1.593 2.538-4.304 3.87a.597.597 0 0 0-.08-.079c1.432-3.155 1.828-5.61 1.175-7.322l3.058 2.984.12.203zm-.131 9.44a.73.73 0 0 1-.347.031c-.96-.171-2.537-1.594-3.87-4.307a.656.656 0 0 0 .08-.078l-.001.001c3.155 1.432 5.61 1.83 7.324 1.174l-2.969 3.043M23.568.392a.05.05 0 0 1 .052-.011c.018.006.03.024.029.043-.065 4.655-.437 9.726-2.703 12.05-1.53 1.565-4.326 1.419-8.283-.377.006-.037.021-.07.02-.108 0-.044-.017-.082-.026-.123 2.83-1.39 4.315-3.037 4.506-4.115.057-.322-.009-.542-.102-.688l6.507-6.67V.392zM.393.43A.045.045 0 0 1 .382.38C.39.36.403.343.425.35c4.655.065 9.727.438 12.05 2.703l.002.002c1.56 1.527 1.415 4.323-.379 8.28-.033-.005-.062-.02-.097-.02h-.008c-.045.001-.084.019-.126.027-1.39-2.83-3.037-4.314-4.115-4.506-.323-.057-.542.01-.688.103L.393.43zm11.94 11.563a.331.331 0 0 1-.327.335H12a.332.332 0 0 1-.004-.661c.172.016.333.144.335.326h.002zm-5.12 4.661a.722.722 0 0 1-.03-.345c.17-.96 1.595-2.54 4.309-3.873.013.016.019.035.033.05.013.012.03.017.044.028-1.434 3.158-1.83 5.613-1.177 7.326l-3.041-2.967m-.006-9.659a.735.735 0 0 1 .345-.031c.961.17 2.54 1.594 3.871 4.306a.597.597 0 0 0-.079.08c-2.167-.983-4.007-1.484-5.498-1.484-.68 0-1.289.103-1.825.308L7.128 7.35M.43 23.607c-.018.018-.038.015-.052.01-.019-.007-.028-.021-.028-.043.065-4.654.437-9.725 2.703-12.049 1.527-1.565 4.325-1.419 8.286.378-.006.035-.02.067-.02.104 0 .043.018.083.026.124-2.831 1.391-4.317 3.04-4.51 4.117-.057.322.01.542.103.688L.43 23.607zm23.144.042c-4.655-.065-9.726-.437-12.05-2.703l-.005-.006c-1.56-1.526-1.412-4.322.383-8.279.033.005.064.02.098.02h.009c.043 0 .08-.018.122-.027 1.39 2.832 3.036 4.317 4.115 4.51.083.014.16.021.23.021a.776.776 0 0 0 .45-.133l6.68 6.516c.02.02.016.04.01.052a.042.042 0 0 1-.042.029z"/></symbol>
<symbol id="ic-grafana" viewBox="0 0 24 24"><path fill="#F46800" d="M23.02 10.59a8.578 8.578 0 0 0-.862-3.034 8.911 8.911 0 0 0-1.789-2.445c.337-1.342-.413-2.505-.413-2.505-1.292-.08-2.113.4-2.416.62-.052-.02-.102-.044-.154-.064-.22-.089-.446-.172-.677-.247-.231-.073-.47-.14-.711-.197a9.867 9.867 0 0 0-.875-.161C14.557.753 12.94 0 12.94 0c-1.804 1.145-2.147 2.744-2.147 2.744l-.018.093c-.098.029-.2.057-.298.088-.138.042-.275.094-.413.143-.138.055-.275.107-.41.166a8.869 8.869 0 0 0-1.557.87l-.063-.029c-2.497-.955-4.716.195-4.716.195-.203 2.658.996 4.33 1.235 4.636a11.608 11.608 0 0 0-.607 2.635C1.636 12.677.953 15.014.953 15.014c1.926 2.214 4.171 2.351 4.171 2.351.003-.002.006-.002.006-.005.285.509.615.994.986 1.446.156.19.32.371.488.548-.704 2.009.099 3.68.099 3.68 2.144.08 3.553-.937 3.849-1.173a9.784 9.784 0 0 0 3.164.501h.08l.055-.003.107-.002.103-.005.003.002c1.01 1.44 2.788 1.646 2.788 1.646 1.264-1.332 1.337-2.653 1.337-2.94v-.058c0-.02-.003-.039-.003-.06.265-.187.52-.387.758-.6a7.875 7.875 0 0 0 1.415-1.7c1.43.083 2.437-.885 2.437-.885-.236-1.49-1.085-2.216-1.264-2.354l-.018-.013-.016-.013a.217.217 0 0 1-.031-.02c.008-.092.016-.18.02-.27.011-.162.016-.323.016-.48v-.253l-.005-.098-.008-.135a1.891 1.891 0 0 0-.01-.13c-.003-.042-.008-.083-.013-.125l-.016-.124-.018-.122a6.215 6.215 0 0 0-2.032-3.73 6.015 6.015 0 0 0-3.222-1.46 6.292 6.292 0 0 0-.85-.048l-.107.002h-.063l-.044.003-.104.008a4.777 4.777 0 0 0-3.335 1.695c-.332.4-.592.84-.768 1.297a4.594 4.594 0 0 0-.312 1.817l.003.091c.005.055.007.11.013.164a3.615 3.615 0 0 0 .698 1.82 3.53 3.53 0 0 0 1.827 1.282c.33.098.66.14.971.137.039 0 .078 0 .114-.002l.063-.003c.02 0 .041-.003.062-.003.034-.002.065-.007.099-.01.007 0 .018-.003.028-.003l.031-.005.06-.008a1.18 1.18 0 0 0 .112-.02c.036-.008.072-.013.109-.024a2.634 2.634 0 0 0 .914-.415c.028-.02.056-.041.085-.065a.248.248 0 0 0 .039-.35.244.244 0 0 0-.309-.06l-.078.042c-.09.044-.184.083-.283.116a2.476 2.476 0 0 1-.475.096c-.028.003-.054.006-.083.006l-.083.002c-.026 0-.054 0-.08-.002l-.102-.006h-.012l-.024.006c-.016-.003-.031-.003-.044-.006-.031-.002-.06-.007-.091-.01a2.59 2.59 0 0 1-.724-.213 2.557 2.557 0 0 1-.667-.438 2.52 2.52 0 0 1-.805-1.475 2.306 2.306 0 0 1-.029-.444l.006-.122v-.023l.002-.031c.003-.021.003-.04.005-.06a3.163 3.163 0 0 1 1.352-2.29 3.12 3.12 0 0 1 .937-.43 2.946 2.946 0 0 1 .776-.101h.06l.07.002.045.003h.026l.07.005a4.041 4.041 0 0 1 1.635.49 3.94 3.94 0 0 1 1.602 1.662 3.77 3.77 0 0 1 .397 1.414l.005.076.003.075c.002.026.002.05.002.075 0 .024.003.052 0 .07v.065l-.002.073-.008.174a6.195 6.195 0 0 1-.08.639 5.1 5.1 0 0 1-.267.927 5.31 5.31 0 0 1-.624 1.13 5.052 5.052 0 0 1-3.237 2.014 4.82 4.82 0 0 1-.649.066l-.039.003h-.287a6.607 6.607 0 0 1-1.716-.265 6.776 6.776 0 0 1-3.4-2.274 6.75 6.75 0 0 1-.746-1.15 6.616 6.616 0 0 1-.714-2.596l-.005-.083-.002-.02v-.056l-.003-.073v-.096l-.003-.104v-.07l.003-.163c.008-.22.026-.45.054-.678a8.707 8.707 0 0 1 .28-1.355c.128-.444.286-.872.473-1.277a7.04 7.04 0 0 1 1.456-2.1 5.925 5.925 0 0 1 .953-.763c.169-.111.343-.213.524-.306.089-.05.182-.091.273-.135.047-.02.093-.042.138-.062a7.177 7.177 0 0 1 .714-.267l.145-.045c.049-.015.098-.026.148-.041.098-.029.197-.052.296-.076.049-.013.1-.02.15-.033l.15-.032.151-.028.076-.013.075-.01.153-.024c.057-.01.114-.013.171-.023l.169-.021c.036-.003.073-.008.106-.01l.073-.008.036-.003.042-.002c.057-.003.114-.008.171-.01l.086-.006h.023l.037-.003.145-.007a7.999 7.999 0 0 1 1.708.125 7.917 7.917 0 0 1 2.048.68 8.253 8.253 0 0 1 1.672 1.09l.09.077.089.078c.06.052.114.107.171.159.057.052.112.106.166.16.052.055.107.107.159.164a8.671 8.671 0 0 1 1.41 1.978c.012.026.028.052.04.078l.04.078.075.156c.023.051.05.1.07.153l.065.15a8.848 8.848 0 0 1 .45 1.34.19.19 0 0 0 .201.142.186.186 0 0 0 .172-.184c.01-.246.002-.532-.024-.856z"/></symbol>
</defs>
<!-- ===== 1. ARROWS (drawn FIRST — boxes painted later cover the overrun) ===== -->
<!-- b: Booking → delivery, steps 1..14 -->
<line data-flow="b" x1="220" y1="62" x2="292" y2="62" stroke="#2563eb" stroke-width="1.8" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="b"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M220,62 L292,62"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M220,62 L292,62"/></circle></g>
<line data-flow="b" x1="300" y1="90" x2="228" y2="90" stroke="#059669" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="b"><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M300,90 L228,90"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M300,90 L228,90"/></circle></g>
<line data-flow="b" x1="370" y1="104" x2="370" y2="222" stroke="#2563eb" stroke-width="1.8" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="b"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M370,104 L370,222"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M370,104 L370,222"/></circle></g>
<line data-flow="b" x1="430" y1="230" x2="430" y2="112" stroke="#059669" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="b"><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M430,230 L430,112"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M430,230 L430,112"/></circle></g>
<line data-flow="b" x1="330" y1="304" x2="180.42" y2="383.13" stroke="#2563eb" stroke-width="1.8" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="b"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.30s" begin="0.00s" repeatCount="indefinite" path="M330,304 L180.42,383.13"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.30s" begin="0.65s" repeatCount="indefinite" path="M330,304 L180.42,383.13"/></circle></g>
<line data-flow="b" x1="215" y1="390" x2="379.36" y2="310.72" stroke="#059669" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="b"><circle r="3.4" fill="#059669"><animateMotion dur="1.40s" begin="0.00s" repeatCount="indefinite" path="M215,390 L379.36,310.72"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.40s" begin="0.70s" repeatCount="indefinite" path="M215,390 L379.36,310.72"/></circle></g>
<line data-flow="b" x1="445" y1="304" x2="461.19" y2="382.08" stroke="#2563eb" stroke-width="1.8" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="b"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M445,304 L461.19,382.08"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M445,304 L461.19,382.08"/></circle></g>
<line data-flow="b" x1="520" y1="386" x2="491.44" y2="311.73" stroke="#059669" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="b"><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M520,386 L491.44,311.73"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M520,386 L491.44,311.73"/></circle></g>
<line data-flow="b" x1="420" y1="464" x2="420" y2="532" stroke="#2563eb" stroke-width="1.8" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="b"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M420,464 L420,532"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M420,464 L420,532"/></circle></g>
<line data-flow="b" x1="470" y1="540" x2="470" y2="472" stroke="#059669" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="b"><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M470,540 L470,472"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M470,540 L470,472"/></circle></g>
<line data-flow="b" x1="405" y1="610" x2="405" y2="722" stroke="#dc2626" stroke-width="1.8" marker-end="url(#m-sec)"/><g class="flow-dot" data-flow="b"><circle r="3.4" fill="#dc2626"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M405,610 L405,722"/></circle><circle r="3.4" fill="#dc2626"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M405,610 L405,722"/></circle></g>
<line data-flow="b" x1="455" y1="730" x2="455" y2="618" stroke="#059669" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="b"><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M455,730 L455,618"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M455,730 L455,618"/></circle></g>
<!-- s: driver settlement / payout job -->
<path data-flow="s" d="M500,262 C575,278 592,330 566,384" fill="none" stroke="#7c3aed" stroke-width="1.6" marker-end="url(#m-sc)"/><g class="flow-dot" data-flow="s"><circle r="3.4" fill="#7c3aed"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M500,262 C575,278 592,330 566,384"/></circle><circle r="3.4" fill="#7c3aed"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M500,262 C575,278 592,330 566,384"/></circle></g>
<path data-flow="s" d="M510,464 L545,498 L545,702 L514,724" fill="none" stroke="#7c3aed" stroke-width="1.6" marker-end="url(#m-sc)"/><g class="flow-dot" data-flow="s"><circle r="3.4" fill="#7c3aed"><animateMotion dur="2.24s" begin="0.00s" repeatCount="indefinite" path="M510,464 L545,498 L545,702 L514,724"/></circle><circle r="3.4" fill="#7c3aed"><animateMotion dur="2.24s" begin="1.12s" repeatCount="indefinite" path="M510,464 L545,498 L545,702 L514,724"/></circle></g>
<path data-flow="s" d="M395,464 L290,515 L290,690 L165,724" fill="none" stroke="#7c3aed" stroke-width="1.6" marker-end="url(#m-sc)"/><g class="flow-dot" data-flow="s"><circle r="3.4" fill="#7c3aed"><animateMotion dur="3.24s" begin="0.00s" repeatCount="indefinite" path="M395,464 L290,515 L290,690 L165,724"/></circle><circle r="3.4" fill="#7c3aed"><animateMotion dur="3.24s" begin="1.62s" repeatCount="indefinite" path="M395,464 L290,515 L290,690 L165,724"/></circle></g>
<path data-flow="s" d="M412,464 L312,522 L312,790 L170,824" fill="none" stroke="#7c3aed" stroke-width="1.4" stroke-dasharray="5 4" marker-end="url(#m-sc)"/><g class="flow-dot" data-flow="s"><circle r="3.4" fill="#7c3aed"><animateMotion dur="3.50s" begin="0.00s" repeatCount="indefinite" path="M412,464 L312,522 L312,790 L170,824"/></circle><circle r="3.4" fill="#7c3aed"><animateMotion dur="3.50s" begin="1.75s" repeatCount="indefinite" path="M412,464 L312,522 L312,790 L170,824"/></circle></g>
<!-- c: partner integration API (carrier rate quotes + customs/duties lookups) -->
<line data-flow="c" x1="700" y1="104" x2="797.58" y2="232.81" stroke="#2563eb" stroke-width="1.8" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="c"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.24s" begin="0.00s" repeatCount="indefinite" path="M700,104 L797.58,232.81"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.24s" begin="0.62s" repeatCount="indefinite" path="M700,104 L797.58,232.81"/></circle></g>
<line data-flow="c" x1="860" y1="240" x2="750.59" y2="111.05" stroke="#059669" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="c"><circle r="3.4" fill="#059669"><animateMotion dur="1.30s" begin="0.00s" repeatCount="indefinite" path="M860,240 L750.59,111.05"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.30s" begin="0.65s" repeatCount="indefinite" path="M860,240 L750.59,111.05"/></circle></g>
<line data-flow="c" x1="1060" y1="104" x2="896.14" y2="233.52" stroke="#2563eb" stroke-width="1.8" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="c"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.61s" begin="0.00s" repeatCount="indefinite" path="M1060,104 L896.14,233.52"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.61s" begin="0.81s" repeatCount="indefinite" path="M1060,104 L896.14,233.52"/></circle></g>
<line data-flow="c" x1="940" y1="265" x2="982" y2="265" stroke="#2563eb" stroke-width="1.6" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="c"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M940,265 L982,265"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M940,265 L982,265"/></circle></g>
<line data-flow="c" x1="990" y1="296" x2="948" y2="296" stroke="#059669" stroke-width="1.5" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="c"><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M990,296 L948,296"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M990,296 L948,296"/></circle></g>
<line data-flow="c" x1="590" y1="398" x2="742.59" y2="304.1" stroke="#2563eb" stroke-width="1.8" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="c"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.38s" begin="0.00s" repeatCount="indefinite" path="M590,398 L742.59,304.1"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.38s" begin="0.69s" repeatCount="indefinite" path="M590,398 L742.59,304.1"/></circle></g>
<line data-flow="c" x1="746" y1="330" x2="597.44" y2="417.96" stroke="#059669" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="c"><circle r="3.4" fill="#059669"><animateMotion dur="1.33s" begin="0.00s" repeatCount="indefinite" path="M746,330 L597.44,417.96"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.33s" begin="0.67s" repeatCount="indefinite" path="M746,330 L597.44,417.96"/></circle></g>
<!-- C5: gateway -> core DB (duty amount write-back if a lookup changes a shipment's declared value), hop over D5/D6 -->
<path data-flow="c" d="M790,314 L670,398 L670,750 L592,750 A8,8 0 0 0 576,750 L568,750 A8,8 0 0 0 552,750 L538,750" fill="none" stroke="#d97706" stroke-width="1.6" marker-end="url(#m-pi)"/><g class="flow-dot" data-flow="c"><circle r="3.4" fill="#d97706"><animateMotion dur="3.50s" begin="0.00s" repeatCount="indefinite" path="M790,314 L670,398 L670,750 L592,750 A8,8 0 0 0 576,750 L568,750 A8,8 0 0 0 552,750 L538,750"/></circle><circle r="3.4" fill="#d97706"><animateMotion dur="3.50s" begin="1.75s" repeatCount="indefinite" path="M790,314 L670,398 L670,750 L592,750 A8,8 0 0 0 576,750 L568,750 A8,8 0 0 0 552,750 L538,750"/></circle></g>
<!-- d: analytics pipeline (shipment/tracking events → warehouse → ops dashboard) -->
<line data-flow="d" x1="560" y1="464" x2="560" y2="982" stroke="#2563eb" stroke-width="1.8" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="d"><circle r="3.4" fill="#2563eb"><animateMotion dur="3.50s" begin="0.00s" repeatCount="indefinite" path="M560,464 L560,982"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="3.50s" begin="1.75s" repeatCount="indefinite" path="M560,464 L560,982"/></circle></g>
<line data-flow="d" x1="584" y1="990" x2="584" y2="472" stroke="#059669" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="d"><circle r="3.4" fill="#059669"><animateMotion dur="3.50s" begin="0.00s" repeatCount="indefinite" path="M584,990 L584,472"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="3.50s" begin="1.75s" repeatCount="indefinite" path="M584,990 L584,472"/></circle></g>
<path data-flow="d" d="M430,804 L430,880 L552,880 A8,8 0 0 1 568,880 L576,880 A8,8 0 0 1 592,880 L1020,880 L1020,982" fill="none" stroke="#d97706" stroke-width="1.6" stroke-dasharray="7 5" marker-end="url(#m-pi)"/><g class="flow-dot" data-flow="d"><circle r="3.4" fill="#d97706"><animateMotion dur="3.50s" begin="0.00s" repeatCount="indefinite" path="M430,804 L430,880 L552,880 A8,8 0 0 1 568,880 L576,880 A8,8 0 0 1 592,880 L1020,880 L1020,982"/></circle><circle r="3.4" fill="#d97706"><animateMotion dur="3.50s" begin="1.75s" repeatCount="indefinite" path="M430,804 L430,880 L552,880 A8,8 0 0 1 568,880 L576,880 A8,8 0 0 1 592,880 L1020,880 L1020,982"/></circle></g>
<path data-flow="d" d="M1140,314 L1215,368 L1215,930 L1054,984" fill="none" stroke="#d97706" stroke-width="1.6" stroke-dasharray="7 5" marker-end="url(#m-pi)"/><g class="flow-dot" data-flow="d"><circle r="3.4" fill="#d97706"><animateMotion dur="3.50s" begin="0.00s" repeatCount="indefinite" path="M1140,314 L1215,368 L1215,930 L1054,984"/></circle><circle r="3.4" fill="#d97706"><animateMotion dur="3.50s" begin="1.75s" repeatCount="indefinite" path="M1140,314 L1215,368 L1215,930 L1054,984"/></circle></g>
<line data-flow="d" x1="926" y1="1020" x2="898" y2="1020" stroke="#d97706" stroke-width="1.6" marker-end="url(#m-pi)"/><g class="flow-dot" data-flow="d"><circle r="3.4" fill="#d97706"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M926,1020 L898,1020"/></circle><circle r="3.4" fill="#d97706"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M926,1020 L898,1020"/></circle></g>
<line data-flow="d" x1="795" y1="1066" x2="795" y2="1100" stroke="#d97706" stroke-width="1.6" marker-end="url(#m-pi)"/><g class="flow-dot" data-flow="d"><circle r="3.4" fill="#d97706"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M795,1066 L795,1100"/></circle><circle r="3.4" fill="#d97706"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M795,1066 L795,1100"/></circle></g>
<line data-flow="d" x1="660" y1="1012" x2="692" y2="1012" stroke="#2563eb" stroke-width="1.5" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="d"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M660,1012 L692,1012"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M660,1012 L692,1012"/></circle></g>
<line data-flow="d" x1="696" y1="1040" x2="668" y2="1040" stroke="#059669" stroke-width="1.4" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="d"><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M696,1040 L668,1040"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M696,1040 L668,1040"/></circle></g>
<!-- m: shipping label & customs declaration document generation -->
<path data-flow="m" d="M590,448 L662,448 A8,8 0 0 1 678,448 L710,448 L710,556 L742,556" fill="none" stroke="#2563eb" stroke-width="1.8" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="m"><circle r="3.4" fill="#2563eb"><animateMotion dur="2.00s" begin="0.00s" repeatCount="indefinite" path="M590,448 L662,448 A8,8 0 0 1 678,448 L710,448 L710,556 L742,556"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="2.00s" begin="1.00s" repeatCount="indefinite" path="M590,448 L662,448 A8,8 0 0 1 678,448 L710,448 L710,556 L742,556"/></circle></g>
<path data-flow="m" d="M746,580 L682,580 A8,8 0 0 0 666,580 L640,580 L640,485 L596,464" fill="none" stroke="#059669" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="m"><circle r="3.4" fill="#059669"><animateMotion dur="1.92s" begin="0.00s" repeatCount="indefinite" path="M746,580 L682,580 A8,8 0 0 0 666,580 L640,580 L640,485 L596,464"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.92s" begin="0.96s" repeatCount="indefinite" path="M746,580 L682,580 A8,8 0 0 0 666,580 L640,580 L640,485 L596,464"/></circle></g>
<line data-flow="m" x1="845" y1="594" x2="845" y2="652" stroke="#2563eb" stroke-width="1.6" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="m"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M845,594 L845,652"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M845,594 L845,652"/></circle></g>
<line data-flow="m" x1="986" y1="548" x2="948" y2="548" stroke="#2563eb" stroke-width="1.6" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="m"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M986,548 L948,548"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M986,548 L948,548"/></circle></g>
<line data-flow="m" x1="940" y1="576" x2="982" y2="576" stroke="#059669" stroke-width="1.5" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="m"><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M940,576 L982,576"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M940,576 L982,576"/></circle></g>
<line data-flow="m" x1="944" y1="692" x2="982" y2="692" stroke="#2563eb" stroke-width="1.6" marker-end="url(#m-di)"/><g class="flow-dot" data-flow="m"><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M944,692 L982,692"/></circle><circle r="3.4" fill="#2563eb"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M944,692 L982,692"/></circle></g>
<line data-flow="m" x1="1080" y1="656" x2="1080" y2="602" stroke="#059669" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#m-ve)"/><g class="flow-dot" data-flow="m"><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.00s" repeatCount="indefinite" path="M1080,656 L1080,602"/></circle><circle r="3.4" fill="#059669"><animateMotion dur="1.20s" begin="0.60s" repeatCount="indefinite" path="M1080,656 L1080,602"/></circle></g>
<!-- ===== 2. REGIONS ===== -->
<rect x="40" y="180" width="620" height="480" rx="10" fill="none" stroke="#d97706" stroke-width="1.2" stroke-dasharray="7 5" opacity="0.55"/>
<text x="56" y="204" fill="#d97706" font-size="11" font-weight="700" opacity="0.9">Core platform — booking, verification & automation</text>
<rect x="720" y="180" width="470" height="220" rx="10" fill="none" stroke="#0284c7" stroke-width="1.2" stroke-dasharray="7 5" opacity="0.55"/>
<text x="950" y="212" fill="#0284c7" font-size="11" font-weight="700" opacity="0.9">Partner integration gateway</text>
<rect x="720" y="470" width="470" height="300" rx="10" fill="none" stroke="#c026d3" stroke-width="1.2" stroke-dasharray="7 5" opacity="0.55"/>
<text x="736" y="498" fill="#c026d3" font-size="11" font-weight="700" opacity="0.9">Shipping document generation</text>
<rect x="440" y="940" width="750" height="240" rx="10" fill="none" stroke="#d97706" stroke-width="1.2" stroke-dasharray="7 5" opacity="0.55"/>
<text x="620" y="966" fill="#d97706" font-size="11" font-weight="700" opacity="0.9">Shipment analytics warehouse — on-time delivery reporting</text>
<!-- ===== 3. BOXES ===== -->
<g data-flows="b">
<rect x="40" y="40" width="180" height="64" rx="10" fill="#ffffff" stroke="#d1d5db" stroke-width="1.3"/>
<text x="130" y="66" fill="#1f2937" font-size="11.5" font-weight="650" text-anchor="middle">Customer</text>
<text x="130" y="84" fill="#6b7280" font-size="9" text-anchor="middle">web & mobile app</text>
<g class="flow-ping" data-flow="b"><rect class="flow-ping-ring" x="40" y="40" width="180" height="64" rx="10" fill="none" stroke="#d1d5db" stroke-width="1.5"/></g></g>
<g data-flows="b">
<rect x="300" y="40" width="200" height="64" rx="10" fill="#ffffff" stroke="#2563eb" stroke-width="1.4"/>
<text x="400" y="66" fill="#1f2937" font-size="11.5" font-weight="650" text-anchor="middle">Booking Web / App</text>
<text x="400" y="84" fill="#6b7280" font-size="9" text-anchor="middle">shipment booking UI</text>
<g class="flow-ping" data-flow="b"><rect class="flow-ping-ring" x="300" y="40" width="200" height="64" rx="10" fill="none" stroke="#2563eb" stroke-width="1.5"/></g><g class="flow-ping" data-flow="b"><rect class="flow-ping-ring" x="300" y="40" width="200" height="64" rx="10" fill="none" stroke="#2563eb" stroke-width="1.5"/></g></g>
<g data-flows="c">
<rect x="620" y="40" width="160" height="64" rx="10" fill="#ffffff" stroke="#d1d5db" stroke-width="1.3"/>
<text x="700" y="66" fill="#1f2937" font-size="11.5" font-weight="650" text-anchor="middle">Carrier Partner Systems</text>
<text x="700" y="84" fill="#6b7280" font-size="9" text-anchor="middle">rate-quote integrations</text>
<g class="flow-ping" data-flow="c"><rect class="flow-ping-ring" x="620" y="40" width="160" height="64" rx="10" fill="none" stroke="#d1d5db" stroke-width="1.5"/></g></g>
<g data-flows="c">
<rect x="980" y="40" width="200" height="64" rx="10" fill="#ffffff" stroke="#d1d5db" stroke-width="1.3"/>
<text x="1080" y="66" fill="#1f2937" font-size="11.5" font-weight="650" text-anchor="middle">Customs & duties data feed</text>
<text x="1080" y="84" fill="#6b7280" font-size="9" text-anchor="middle">external authority feed</text>
</g>
<g data-flows="b s m">
<rect x="300" y="230" width="200" height="74" rx="10" fill="#eff6ff" stroke="#2563eb" stroke-width="1.7"/>
<text x="400" y="260" fill="#1f2937" font-size="13" font-weight="700" text-anchor="middle">Booking Service</text>
<text x="400" y="280" fill="#6b7280" font-size="9" text-anchor="middle">shipment orchestrator</text>
<g class="flow-ping" data-flow="b"><rect class="flow-ping-ring" x="300" y="230" width="200" height="74" rx="10" fill="none" stroke="#2563eb" stroke-width="1.5"/></g><g class="flow-ping" data-flow="b"><rect class="flow-ping-ring" x="300" y="230" width="200" height="74" rx="10" fill="none" stroke="#2563eb" stroke-width="1.5"/></g><g class="flow-ping" data-flow="b"><rect class="flow-ping-ring" x="300" y="230" width="200" height="74" rx="10" fill="none" stroke="#2563eb" stroke-width="1.5"/></g></g>
<g data-flows="b">
<rect x="60" y="390" width="200" height="84" rx="10" fill="#ffffff" stroke="#d1d5db" stroke-width="1.4"/>
<text x="160" y="420" fill="#1f2937" font-size="12" font-weight="700" text-anchor="middle">Route Assignment Engine</text>
<text x="160" y="438" fill="#6b7280" font-size="9" text-anchor="middle">carrier / driver matching</text>
<text x="160" y="454" fill="#6b7280" font-size="9" text-anchor="middle">recalculates ETA mid-transit</text>
<g class="flow-ping" data-flow="b"><rect class="flow-ping-ring" x="60" y="390" width="200" height="84" rx="10" fill="none" stroke="#d1d5db" stroke-width="1.5"/></g></g>
<g data-flows="b s c d m">
<rect x="390" y="390" width="200" height="74" rx="10" fill="#ffffff" stroke="#7c3aed" stroke-width="1.4"/>
<use href="#ic-n8n" x="402" y="419" width="16" height="16"/>
<text x="490" y="420" fill="#1f2937" font-size="12" font-weight="700" text-anchor="middle">n8n</text>
<text x="490" y="440" fill="#6b7280" font-size="9" text-anchor="middle">workflow automation · every integration</text>
<g class="flow-ping" data-flow="b"><rect class="flow-ping-ring" x="390" y="390" width="200" height="74" rx="10" fill="none" stroke="#7c3aed" stroke-width="1.5"/></g><g class="flow-ping" data-flow="b"><rect class="flow-ping-ring" x="390" y="390" width="200" height="74" rx="10" fill="none" stroke="#7c3aed" stroke-width="1.5"/></g><g class="flow-ping" data-flow="c"><rect class="flow-ping-ring" x="390" y="390" width="200" height="74" rx="10" fill="none" stroke="#7c3aed" stroke-width="1.5"/></g><g class="flow-ping" data-flow="d"><rect class="flow-ping-ring" x="390" y="390" width="200" height="74" rx="10" fill="none" stroke="#7c3aed" stroke-width="1.5"/></g><g class="flow-ping" data-flow="s"><rect class="flow-ping-ring" x="390" y="390" width="200" height="74" rx="10" fill="none" stroke="#7c3aed" stroke-width="1.5"/></g><g class="flow-ping" data-flow="m"><rect class="flow-ping-ring" x="390" y="390" width="200" height="74" rx="10" fill="none" stroke="#7c3aed" stroke-width="1.5"/></g></g>
<g data-flows="">
<rect x="60" y="530" width="200" height="70" rx="10" fill="#ffffff" stroke="#e5e7eb" stroke-width="1.2"/>
<text x="160" y="558" fill="#374151" font-size="11" font-weight="650" text-anchor="middle">Credentials Vault & Audit Log</text>
<text x="160" y="576" fill="#6b7280" font-size="9" text-anchor="middle">secrets · logs every flow</text>
</g>
<g data-flows="b">
<rect x="330" y="540" width="200" height="70" rx="10" fill="#ffffff" stroke="#dc2626" stroke-width="1.4"/>
<text x="430" y="568" fill="#1f2937" font-size="11.5" font-weight="700" text-anchor="middle">Pickup Verification</text>
<text x="430" y="586" fill="#6b7280" font-size="9" text-anchor="middle">ID/PIN checkpoint · driver scan</text>
<g class="flow-ping" data-flow="b"><rect class="flow-ping-ring" x="330" y="540" width="200" height="70" rx="10" fill="none" stroke="#dc2626" stroke-width="1.5"/></g><g class="flow-ping" data-flow="b"><rect class="flow-ping-ring" x="330" y="540" width="200" height="70" rx="10" fill="none" stroke="#dc2626" stroke-width="1.5"/></g></g>
<g data-flows="b s c d">
<rect x="330" y="730" width="200" height="74" rx="10" fill="#ffffff" stroke="#0284c7" stroke-width="1.4"/>
<use href="#ic-postgresql" x="342" y="744" width="16" height="16"/>
<text x="430" y="760" fill="#1f2937" font-size="12" font-weight="700" text-anchor="middle">Shipment & Tracking DB</text>
<text x="430" y="780" fill="#6b7280" font-size="9" text-anchor="middle">PostgreSQL — system of record</text>
<g class="flow-ping" data-flow="b"><rect class="flow-ping-ring" x="330" y="730" width="200" height="74" rx="10" fill="none" stroke="#0284c7" stroke-width="1.5"/></g><g class="flow-ping" data-flow="s"><rect class="flow-ping-ring" x="330" y="730" width="200" height="74" rx="10" fill="none" stroke="#0284c7" stroke-width="1.5"/></g><g class="flow-ping" data-flow="c"><rect class="flow-ping-ring" x="330" y="730" width="200" height="74" rx="10" fill="none" stroke="#0284c7" stroke-width="1.5"/></g></g>
<g data-flows="s">
<rect x="40" y="730" width="220" height="64" rx="10" fill="#ffffff" stroke="#059669" stroke-width="1.3"/>
<text x="150" y="754" fill="#1f2937" font-size="10.5" font-weight="650" text-anchor="middle">Payout Batch Ready</text>
<text x="150" y="772" fill="#6b7280" font-size="9" text-anchor="middle">driver settlement computed</text>
<g class="flow-ping" data-flow="s"><rect class="flow-ping-ring" x="40" y="730" width="220" height="64" rx="10" fill="none" stroke="#059669" stroke-width="1.5"/></g></g>
<g data-flows="s">
<rect x="40" y="830" width="220" height="64" rx="10" fill="#ffffff" stroke="#e5e7eb" stroke-width="1.2"/>
<use href="#ic-discord" x="52" y="854" width="16" height="16"/>
<text x="160" y="854" fill="#374151" font-size="10.5" font-weight="650" text-anchor="middle">Internal Ops Alerts</text>
<text x="160" y="872" fill="#6b7280" font-size="9" text-anchor="middle">job-failure alerts → on-call (Discord)</text>
<g class="flow-ping" data-flow="s"><rect class="flow-ping-ring" x="40" y="830" width="220" height="64" rx="10" fill="none" stroke="#e5e7eb" stroke-width="1.5"/></g></g>
<g data-flows="c">
<rect x="750" y="240" width="190" height="74" rx="10" fill="#ffffff" stroke="#0284c7" stroke-width="1.7"/>
<use href="#ic-fastapi" x="762" y="269" width="16" height="16"/>
<text x="845" y="268" fill="#1f2937" font-size="12" font-weight="700" text-anchor="middle">Partner API Gateway (FastAPI)</text>
<text x="845" y="288" fill="#6b7280" font-size="9" text-anchor="middle">single entry — normalizes I/O</text>
<g class="flow-ping" data-flow="c"><rect class="flow-ping-ring" x="750" y="240" width="190" height="74" rx="10" fill="none" stroke="#0284c7" stroke-width="1.5"/></g><g class="flow-ping" data-flow="c"><rect class="flow-ping-ring" x="750" y="240" width="190" height="74" rx="10" fill="none" stroke="#0284c7" stroke-width="1.5"/></g><g class="flow-ping" data-flow="c"><rect class="flow-ping-ring" x="750" y="240" width="190" height="74" rx="10" fill="none" stroke="#0284c7" stroke-width="1.5"/></g><g class="flow-ping" data-flow="c"><rect class="flow-ping-ring" x="750" y="240" width="190" height="74" rx="10" fill="none" stroke="#0284c7" stroke-width="1.5"/></g></g>
<g data-flows="c d">
<rect x="990" y="240" width="180" height="74" rx="10" fill="#ffffff" stroke="#0284c7" stroke-width="1.4"/>
<use href="#ic-mongodb" x="1002" y="269" width="16" height="16"/>
<text x="1080" y="268" fill="#1f2937" font-size="11.5" font-weight="700" text-anchor="middle">Partner Rate & Customs Store</text>
<text x="1080" y="288" fill="#6b7280" font-size="9" text-anchor="middle">MongoDB — quotes + duty lookups</text>
<g class="flow-ping" data-flow="c"><rect class="flow-ping-ring" x="990" y="240" width="180" height="74" rx="10" fill="none" stroke="#0284c7" stroke-width="1.5"/></g></g>
<g data-flows="m">
<rect x="750" y="530" width="190" height="64" rx="10" fill="#ffffff" stroke="#c026d3" stroke-width="1.3"/>
<text x="845" y="554" fill="#1f2937" font-size="10.5" font-weight="650" text-anchor="middle">Document Generation Service</text>
<text x="845" y="572" fill="#6b7280" font-size="9" text-anchor="middle">drafts labels & customs forms</text>
<g class="flow-ping" data-flow="m"><rect class="flow-ping-ring" x="750" y="530" width="190" height="64" rx="10" fill="none" stroke="#c026d3" stroke-width="1.5"/></g><g class="flow-ping" data-flow="m"><rect class="flow-ping-ring" x="750" y="530" width="190" height="64" rx="10" fill="none" stroke="#c026d3" stroke-width="1.5"/></g></g>
<g data-flows="m">
<rect x="990" y="530" width="180" height="64" rx="10" fill="#ffffff" stroke="#c026d3" stroke-width="1.3"/>
<text x="1080" y="554" fill="#1f2937" font-size="10.5" font-weight="650" text-anchor="middle">Customs Data Extraction Svc</text>
<text x="1080" y="572" fill="#6b7280" font-size="9" text-anchor="middle">pulls shipment fields for redraft</text>
<g class="flow-ping" data-flow="m"><rect class="flow-ping-ring" x="990" y="530" width="180" height="64" rx="10" fill="none" stroke="#c026d3" stroke-width="1.5"/></g><g class="flow-ping" data-flow="m"><rect class="flow-ping-ring" x="990" y="530" width="180" height="64" rx="10" fill="none" stroke="#c026d3" stroke-width="1.5"/></g></g>
<g data-flows="m">
<rect x="750" y="660" width="190" height="64" rx="10" fill="#ffffff" stroke="#c026d3" stroke-width="1.3"/>
<text x="845" y="684" fill="#1f2937" font-size="10.5" font-weight="650" text-anchor="middle">Draft Document Store</text>
<text x="845" y="702" fill="#6b7280" font-size="9" text-anchor="middle">pending-review shipment docs</text>
<g class="flow-ping" data-flow="m"><rect class="flow-ping-ring" x="750" y="660" width="190" height="64" rx="10" fill="none" stroke="#c026d3" stroke-width="1.5"/></g></g>
<g data-flows="m">
<rect x="990" y="660" width="180" height="64" rx="10" fill="#ffffff" stroke="#c026d3" stroke-width="1.3"/>
<text x="1080" y="684" fill="#1f2937" font-size="10.5" font-weight="650" text-anchor="middle">Document Review Queue</text>
<text x="1080" y="702" fill="#6b7280" font-size="9" text-anchor="middle">ops/compliance approves</text>
<g class="flow-ping" data-flow="m"><rect class="flow-ping-ring" x="990" y="660" width="180" height="64" rx="10" fill="none" stroke="#c026d3" stroke-width="1.5"/></g></g>
<g data-flows="d">
<rect x="480" y="990" width="180" height="76" rx="10" fill="#ffffff" stroke="#dc2626" stroke-width="1.4"/>
<text x="570" y="1018" fill="#1f2937" font-size="11" font-weight="700" text-anchor="middle">Ops-Safe Analytics Layer</text>
<text x="570" y="1038" fill="#6b7280" font-size="9" text-anchor="middle">read-only · mart only</text>
<g class="flow-ping" data-flow="d"><rect class="flow-ping-ring" x="480" y="990" width="180" height="76" rx="10" fill="none" stroke="#dc2626" stroke-width="1.5"/></g><g class="flow-ping" data-flow="d"><rect class="flow-ping-ring" x="480" y="990" width="180" height="76" rx="10" fill="none" stroke="#dc2626" stroke-width="1.5"/></g></g>
<g data-flows="d">
<rect x="700" y="990" width="190" height="76" rx="10" fill="#ffffff" stroke="#0284c7" stroke-width="1.4"/>
<use href="#ic-postgresql" x="712" y="1020" width="16" height="16"/>
<text x="795" y="1018" fill="#1f2937" font-size="11" font-weight="700" text-anchor="middle">Data Warehouse</text>
<text x="795" y="1038" fill="#6b7280" font-size="9" text-anchor="middle">PostgreSQL — staging → dim/fact → mart</text>
<g class="flow-ping" data-flow="d"><rect class="flow-ping-ring" x="700" y="990" width="190" height="76" rx="10" fill="none" stroke="#0284c7" stroke-width="1.5"/></g><g class="flow-ping" data-flow="d"><rect class="flow-ping-ring" x="700" y="990" width="190" height="76" rx="10" fill="none" stroke="#0284c7" stroke-width="1.5"/></g></g>
<g data-flows="d">
<rect x="930" y="990" width="180" height="76" rx="10" fill="#ffffff" stroke="#d97706" stroke-width="1.3"/>
<use href="#ic-apacheairflow" x="942" y="1020" width="16" height="16"/>
<text x="1020" y="1018" fill="#1f2937" font-size="11" font-weight="700" text-anchor="middle">Airflow ETL</text>
<text x="1020" y="1038" fill="#6b7280" font-size="9" text-anchor="middle">ingestion + transforms</text>
<g class="flow-ping" data-flow="d"><rect class="flow-ping-ring" x="930" y="990" width="180" height="76" rx="10" fill="none" stroke="#d97706" stroke-width="1.5"/></g><g class="flow-ping" data-flow="d"><rect class="flow-ping-ring" x="930" y="990" width="180" height="76" rx="10" fill="none" stroke="#d97706" stroke-width="1.5"/></g></g>
<g data-flows="d">
<rect x="700" y="1108" width="190" height="56" rx="10" fill="#ffffff" stroke="#2563eb" stroke-width="1.2"/>
<use href="#ic-grafana" x="712" y="1128" width="16" height="16"/>
<text x="795" y="1130" fill="#1f2937" font-size="10.5" font-weight="650" text-anchor="middle">Ops Dashboard (Grafana)</text>
<text x="795" y="1148" fill="#6b7280" font-size="9" text-anchor="middle">on-time delivery rate & metrics</text>
<g class="flow-ping" data-flow="d"><rect class="flow-ping-ring" x="700" y="1108" width="190" height="56" rx="10" fill="none" stroke="#2563eb" stroke-width="1.5"/></g></g>
<!-- ===== 4. STEP-NUMBER BADGES ===== -->
<g font-size="9" font-weight="700">
<g data-flow="b" data-step="1"><circle cx="258" cy="62" r="9" fill="#ffffff" stroke="#2563eb"/><text x="258" y="65.5" fill="#2563eb" text-anchor="middle">1</text></g>
<g data-flow="b" data-step="14"><circle cx="262" cy="90" r="9" fill="#ffffff" stroke="#059669"/><text x="262" y="93.5" fill="#059669" text-anchor="middle">14</text></g>
<g data-flow="b" data-step="2"><circle cx="370" cy="165" r="9" fill="#ffffff" stroke="#2563eb"/><text x="370" y="168.5" fill="#2563eb" text-anchor="middle">2</text></g>
<g data-flow="b" data-step="13"><circle cx="430" cy="165" r="9" fill="#ffffff" stroke="#059669"/><text x="430" y="168.5" fill="#059669" text-anchor="middle">13</text></g>
<g data-flow="b" data-step="3·11"><rect x="216" y="326" width="40" height="17" rx="8.5" fill="#ffffff" stroke="#2563eb"/><text x="236" y="338" fill="#2563eb" text-anchor="middle">3·11</text></g>
<text data-flow="b" x="214" y="316" fill="#2563eb" font-size="8.5" text-anchor="middle" font-weight="400">assign route · re-route</text>
<g data-flow="b" data-step="4·12"><rect x="308" y="354" width="40" height="17" rx="8.5" fill="#ffffff" stroke="#059669"/><text x="328" y="366" fill="#059669" text-anchor="middle">4·12</text></g>
<text data-flow="b" x="344" y="384" fill="#059669" font-size="8.5" text-anchor="middle" font-weight="400">driver set / ETA updated</text>
<g data-flow="b" data-step="5"><circle cx="448" cy="345" r="9" fill="#ffffff" stroke="#2563eb"/><text x="448" y="348.5" fill="#2563eb" text-anchor="middle">5</text></g>
<g data-flow="b" data-step="10"><circle cx="512" cy="345" r="9" fill="#ffffff" stroke="#059669"/><text x="512" y="348.5" fill="#059669" text-anchor="middle">10</text></g>
<g data-flow="b" data-step="6"><circle cx="420" cy="500" r="9" fill="#ffffff" stroke="#2563eb"/><text x="420" y="503.5" fill="#2563eb" text-anchor="middle">6</text></g>
<g data-flow="b" data-step="9"><circle cx="470" cy="500" r="9" fill="#ffffff" stroke="#059669"/><text x="470" y="503.5" fill="#059669" text-anchor="middle">9</text></g>
<g data-flow="b" data-step="7"><circle cx="405" cy="668" r="9" fill="#ffffff" stroke="#dc2626"/><text x="405" y="671.5" fill="#dc2626" text-anchor="middle">7</text></g>
<text data-flow="b" x="391" y="671" fill="#dc2626" font-size="8.5" text-anchor="end" font-weight="400">ID/PIN checkpoint</text>
<g data-flow="b" data-step="8"><circle cx="455" cy="668" r="9" fill="#ffffff" stroke="#059669"/><text x="455" y="671.5" fill="#059669" text-anchor="middle">8</text></g>
<g data-flow="s" data-step="S1"><rect x="570" y="296" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#7c3aed"/><text x="583" y="308" fill="#7c3aed" text-anchor="middle">S1</text></g>
<text data-flow="s" x="600" y="296" fill="#7c3aed" font-size="8.5" font-weight="400">cron</text>
<g data-flow="s" data-step="S2"><rect x="532" y="592" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#7c3aed"/><text x="545" y="604" fill="#7c3aed" text-anchor="middle">S2</text></g>
<g data-flow="s" data-step="S3"><rect x="277" y="572" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#7c3aed"/><text x="290" y="584" fill="#7c3aed" text-anchor="middle">S3</text></g>
<g data-flow="s" data-step="S4"><rect x="299" y="700" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#7c3aed"/><text x="312" y="712" fill="#7c3aed" text-anchor="middle">S4</text></g>
<g data-flow="c" data-step="C1"><circle cx="732" cy="176" r="9" fill="#ffffff" stroke="#2563eb"/><text x="732" y="179.5" fill="#2563eb" text-anchor="middle">C1</text></g>
<g data-flow="c" data-step="C2"><circle cx="826" cy="160" r="9" fill="#ffffff" stroke="#059669"/><text x="826" y="163.5" fill="#059669" text-anchor="middle">C2</text></g>
<text data-flow="c" x="852" y="160" fill="#059669" font-size="8.5" font-weight="400">quote returned</text>
<g data-flow="c" data-step="C3"><circle cx="988" cy="160" r="9" fill="#ffffff" stroke="#2563eb"/><text x="988" y="163.5" fill="#2563eb" text-anchor="middle">C3</text></g>
<g data-flow="c" data-step="C4"><rect x="952" y="220" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#0284c7"/><text x="965" y="232" fill="#0284c7" text-anchor="middle">C4</text></g>
<g data-flow="c" data-step="C6"><circle cx="660" cy="338" r="9" fill="#ffffff" stroke="#2563eb"/><text x="660" y="341.5" fill="#2563eb" text-anchor="middle">C6</text></g>
<text data-flow="c" x="650" y="322" fill="#2563eb" font-size="8.5" text-anchor="middle" font-weight="400">match rate table</text>
<g data-flow="c" data-step="C7"><circle cx="682" cy="386" r="9" fill="#ffffff" stroke="#059669"/><text x="682" y="389.5" fill="#059669" text-anchor="middle">C7</text></g>
<g data-flow="c" data-step="C5"><rect x="657" y="452" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#d97706"/><text x="670" y="464" fill="#d97706" text-anchor="middle">C5</text></g>
<text data-flow="c" x="656" y="710" fill="#d97706" font-size="8.5" font-weight="400" transform="rotate(-90 656,710)">duty writeback if flagged</text>
<g data-flow="d" data-step="D5"><rect x="547" y="900" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#2563eb"/><text x="560" y="912" fill="#2563eb" text-anchor="middle">D5</text></g>
<text data-flow="d" x="572" y="700" fill="#2563eb" font-size="8.5" font-weight="400" transform="rotate(-90 572,700)">query on-time stats</text>
<g data-flow="d" data-step="D6"><rect x="571" y="928" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#059669"/><text x="584" y="940" fill="#059669" text-anchor="middle">D6</text></g>
<text data-flow="d" x="596" y="700" fill="#059669" font-size="8.5" font-weight="400" transform="rotate(-90 596,700)">on-time metrics back</text>
<g data-flow="d" data-step="D1"><rect x="787" y="862" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#d97706"/><text x="800" y="874" fill="#d97706" text-anchor="middle">D1</text></g>
<text data-flow="d" x="836" y="874" fill="#d97706" font-size="8.5" font-weight="400">DB → Airflow ingest</text>
<g data-flow="d" data-step="D2"><rect x="1202" y="630" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#d97706"/><text x="1215" y="642" fill="#d97706" text-anchor="middle">D2</text></g>
<text data-flow="d" x="1204" y="740" fill="#d97706" font-size="8.5" font-weight="400" transform="rotate(-90 1204,740)">carrier & customs feeds</text>
<g data-flow="d" data-step="D3"><rect x="897" y="998" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#d97706"/><text x="910" y="1010" fill="#d97706" text-anchor="middle">D3</text></g>
<g data-flow="d" data-step="D4"><rect x="782" y="1078" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#d97706"/><text x="795" y="1090" fill="#d97706" text-anchor="middle">D4</text></g>
<g data-flow="m" data-step="M1"><rect x="614" y="431" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#2563eb"/><text x="627" y="443" fill="#2563eb" text-anchor="middle">M1</text></g>
<text data-flow="m" x="627" y="425" fill="#2563eb" font-size="8.5" text-anchor="middle" font-weight="400">generate documents</text>
<g data-flow="m" data-step="M2"><rect x="694" y="571" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#059669"/><text x="707" y="583" fill="#059669" text-anchor="middle">M2</text></g>
<text data-flow="m" x="628" y="540" fill="#059669" font-size="8.5" font-weight="400" transform="rotate(-90 628,540)">draft returns</text>
<g data-flow="m" data-step="M3"><rect x="855" y="616" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#2563eb"/><text x="868" y="628" fill="#2563eb" text-anchor="middle">M3</text></g>
<text data-flow="m" x="898" y="628" fill="#2563eb" font-size="8.5" font-weight="400">save draft</text>
<g data-flow="m" data-step="M4"><rect x="950" y="522" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#2563eb"/><text x="963" y="534" fill="#2563eb" text-anchor="middle">M4</text></g>
<g data-flow="m" data-step="M5"><rect x="950" y="586" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#059669"/><text x="963" y="598" fill="#059669" text-anchor="middle">M5</text></g>
<g data-flow="m" data-step="M6"><rect x="952" y="700" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#2563eb"/><text x="965" y="712" fill="#2563eb" text-anchor="middle">M6</text></g>
<g data-flow="m" data-step="M7"><rect x="1090" y="618" width="26" height="17" rx="8.5" fill="#ffffff" stroke="#059669"/><text x="1103" y="630" fill="#059669" text-anchor="middle">M7</text></g>
<text data-flow="m" x="1130" y="646" fill="#059669" font-size="8.5" text-anchor="middle" font-weight="400">approved → attach</text>
</g>
</svg>
</div>
</section>
<aside class="side">
<div class="cols">
<div class="card" data-flow="b">
<h3><span class="dot" style="background:var(--di)"></span>Booking → delivery (1→14)</h3>
<ol>
<li><span class="n1">1→2</span> Customer books a shipment on the <b>Booking Web/App</b> → <b>Booking Service</b></li>
<li><span class="n1">3</span>/<span class="n2">4</span> Booking Service asks the <b>Route Assignment Engine</b> to match a carrier/driver</li>
<li><span class="n1">5→7</span> Booking Service → <b>n8n</b> → <b>Pickup Verification</b> → DB (<span class="n5">ID/PIN checkpoint</span>)</li>
<li><span class="n2">8→10</span> Verified pickup + tracking update flows back: DB → Pickup Verification → n8n → Booking Service</li>
<li><span class="n1">11</span>/<span class="n2">12</span> Route Assignment Engine recalculates ETA mid-transit (loop 5→10 repeats on a re-route)</li>
<li><span class="n2">13→14</span> Delivery confirmation + tracking status shown on the app, then emailed to the Customer</li>
</ol>
</div>
<div class="card" data-flow="c">
<h3><span class="dot" style="background:var(--sky)"></span>Partner integration API (C1→C7)</h3>
<ul>
<li><span class="n1">C1</span> A carrier partner's rate-quote request → the <b>Partner API Gateway</b>; <span class="n2">C2</span> the quote is returned through the same gateway</li>
<li><span class="n1">C3</span> A customs & duties data feed enters through the same gateway — normalized on the way in</li>
<li><span class="n1">C4</span> Gateway reads/writes the <b>Partner Rate & Customs Store</b> — never accessed directly</li>
<li><span class="n4">C5</span> If a customs lookup changes a shipment's declared duty, the gateway <b>writes back to the core DB</b>, alongside the analytics sync</li>
<li><span class="n1">C6</span>/<span class="n2">C7</span> An n8n-based job matches the stored rate table and writes back an updated quote — both through the gateway's own endpoint</li>
</ul>
</div>
<div class="card" data-flow="d">
<h3><span class="dot" style="background:var(--pipe)"></span>Analytics pipeline (D1→D6)</h3>
<ul>
<li><span class="n1">D5</span>/<span class="n2">D6</span> Self-serve query: n8n → <b>Ops-Safe Analytics Layer</b> (read-only, mart only) → warehouse → on-time metrics back → n8n → Route Assignment Engine (used to weigh carrier choice)</li>
<li><span class="n4">D1</span> Shipment & Tracking DB → Airflow, incremental ingest</li>
<li><span class="n4">D2</span> Partner rate/customs store + external feeds → staging, tagged with source_system</li>
<li><span class="n4">D3→D4</span> Airflow ETL → Data Warehouse → Ops Dashboard</li>
</ul>
</div>
<div class="card" data-flow="s">
<h3><span class="dot" style="background:var(--script)"></span>Driver settlement job (S1→S4)</h3>
<ul>
<li><span class="n3">S1</span> Booking Service <b>schedules a nightly cron</b> → triggers the n8n settlement workflow</li>
<li><span class="n3">S2</span> n8n reads completed <b>shipment records</b> (service account) directly from the Shipment & Tracking DB to compute driver payouts</li>
<li><span class="n3">S3</span> Batch computed → the <b>payout-ready event</b> fires so finance can disburse</li>
<li><span class="n3">S4</span> Job failure → alert to <b>internal ops (Discord)</b> for the on-call engineer</li>
</ul>
</div>
<div class="card" data-flow="m">
<h3><span class="dot" style="background:var(--fx)"></span>Document generation (M1→M7) — 2 paths</h3>
<ul>
<li><b>Path 1 (via n8n):</b> a new booking or customs-flag event → n8n → <span class="n1">M1</span> calls the <b>Document Generation Service</b> → <span class="n2">M2</span> draft returns to n8n → <span class="n1">M3</span> saved to the Draft Document Store</li>
<li><b>Path 2 (data-driven redraft):</b> <span class="n1">M4</span> the Customs Data Extraction Service calls the Document Generation Service directly, <span class="n2">M5</span> draft comes straight back</li>
<li><span class="n1">M6</span> queued in the <b>Document Review Queue</b> → <span class="n2">M7</span> ops/compliance approves before it's attached to the shipment</li>
</ul>
</div>
<div class="card">
<h3><span class="dot" style="background:var(--sec)"></span>Locked-in principles</h3>
<ul>
<li>Only <b>Booking Service</b> calls the Route Assignment Engine; every downstream step routes through n8n in both directions</li>
<li>The Partner Rate/Customs Store is only read/written <b>through the Partner API Gateway</b>; the warehouse is only read <b>through the Ops-Safe Analytics Layer</b> (staging and the production DB are never queried directly)</li>
<li><span class="n5">ID/PIN checkpoint</span>: Pickup Verification sees exactly the shipment it's releasing, nothing more</li>
<li>The Vault/audit log records every flow; alerts split into two channels (business vs. technical)</li>
<li>Human review is mandatory before any generated shipping/customs document is attached to a shipment</li>
</ul>
</div>
</div>
</aside>
</div>
</div>
<script>
// Flow-select interaction + zoom/pan + theme — vanilla JS, self-contained.
(() => {
const svg = document.querySelector('svg');
const canvas = document.getElementById('canvas');
const chips = [...document.querySelectorAll('.chip[data-flow]')];
const flowEls = [...svg.querySelectorAll('[data-flow]')];
const nodeEls = [...svg.querySelectorAll('[data-flows]')];
const cards = [...document.querySelectorAll('.side .card')];
const side = document.querySelector('.side');
let cur = null;
function focus(f) {
cur = f;
flowEls.forEach(e => {
const mine = e.dataset.flow === f;
e.classList.toggle('dim', !!f && !mine);
if (e.matches('line, path') || e.classList.contains('flow-dot') || e.classList.contains('flow-ping')) e.classList.toggle('flowing', !!f && mine);
});
nodeEls.forEach(n => n.classList.toggle('dim', !!f && !n.dataset.flows.split(' ').includes(f)));
chips.forEach(c => c.classList.toggle('on', c.dataset.flow === f));
cards.forEach(card => {
card.classList.toggle('hidden', !!f && card.dataset.flow !== f);
card.querySelectorAll('li.hl').forEach(li => li.classList.remove('hl'));
});
side.scrollTop = 0;
}
chips.forEach(c => c.addEventListener('click', () => focus(cur === c.dataset.flow ? null : c.dataset.flow)));
// Zoom & pan: defaults to FIT TO VIEW (whole system visible); +/- or Ctrl+scroll to zoom in; drag to pan
const [,, vbW, vbH] = svg.getAttribute('viewBox').split(/\s+/).map(Number);
const pctEl = document.querySelector('.zoombar .pct');
let fitW = 0, zoom = 1;
const apply = () => {
svg.style.width = (fitW * zoom) + 'px';
svg.style.height = (fitW * zoom * vbH / vbW) + 'px';
pctEl.textContent = Math.round(zoom * 100) + '%';
};
const refit = () => {
const r = canvas.getBoundingClientRect();
fitW = Math.max(120, Math.min(r.width - 20, (r.height - 20) * vbW / vbH));
apply();
};
const setZoom = (z, clientX, clientY) => {
z = Math.min(8, Math.max(1, z));
if (z === zoom) return;
// Anchor by the RATIO of the cursor position inside the SVG — the SVG is centered via
// margin:auto so the margin has to be recomputed after zooming, not derived from the old
// scrollLeft (which is clamped to 0 while the content doesn't yet overflow the viewport).
const cb = canvas.getBoundingClientRect(), sb = svg.getBoundingClientRect();
const ax = clientX ?? cb.left + canvas.clientWidth / 2, ay = clientY ?? cb.top + canvas.clientHeight / 2;
const fx = (ax - sb.left) / sb.width, fy = (ay - sb.top) / sb.height;
zoom = z; apply();
const w = fitW * zoom, h = w * vbH / vbW;
canvas.scrollLeft = Math.max(0, (canvas.clientWidth - w) / 2) + fx * w - (ax - cb.left);
canvas.scrollTop = Math.max(0, (canvas.clientHeight - h) / 2) + fy * h - (ay - cb.top);
};
document.querySelector('.zoombar [data-z="in"]').addEventListener('click', () => setZoom(zoom * 1.25));
document.querySelector('.zoombar [data-z="out"]').addEventListener('click', () => setZoom(zoom / 1.25));
document.querySelector('.zoombar [data-z="fit"]').addEventListener('click', () => { zoom = 1; refit(); canvas.scrollTo(0, 0); });
canvas.addEventListener('wheel', e => {
e.preventDefault(); // scroll = zoom at the cursor (map-style) — trackpad pinch lands here too
setZoom(zoom * (e.deltaY < 0 ? 1.15 : 1 / 1.15), e.clientX, e.clientY);
}, { passive: false });
addEventListener('resize', refit);
refit();
// Drag to pan — distinguished from clicking to select a flow (only counts as a drag past 5px)
let drag = null, moved = false;
canvas.addEventListener('pointerdown', e => {
if (e.button !== 0) return; // left click only — right click opens a context menu that eats pointerup, leaving state stuck
drag = { x: e.clientX, y: e.clientY, sl: canvas.scrollLeft, st: canvas.scrollTop }; moved = false;
});
addEventListener('pointermove', e => {
if (!drag) return;
const dx = e.clientX - drag.x, dy = e.clientY - drag.y;
if (Math.abs(dx) + Math.abs(dy) > 5) { moved = true; canvas.classList.add('panning'); }
if (moved) { canvas.scrollLeft = drag.sl - dx; canvas.scrollTop = drag.st - dy; }
});
const endPan = () => { drag = null; canvas.classList.remove('panning'); };
addEventListener('pointerup', endPan);
addEventListener('pointercancel', endPan);
svg.addEventListener('click', e => {
if (moved) return;
const el = e.target.closest('[data-flow]');
if (!el) { focus(null); return; }
focus(el.dataset.flow);
const step = el.dataset.step; // clicking a badge highlights the matching line in the sidebar
if (step) {
// "5→7" in a card = steps 5, 6, 7 · a merged badge "3·11" = steps 3 and 11
const toks = t => t.trim().split(/[·\/]/).flatMap(p => {
const m = p.match(/^(\d+)→(\d+)$/);
return m ? Array.from({ length: +m[2] - +m[1] + 1 }, (_, i) => String(+m[1] + i)) : p.split('→');
});
const st = toks(step);
const card = document.querySelector(`.side .card[data-flow="${el.dataset.flow}"]`);
if (card) [...card.querySelectorAll('li')].forEach(li =>
li.classList.toggle('hl', [...li.querySelectorAll('span')].some(s => toks(s.textContent).some(x => st.includes(x)))));
}
});
canvas.addEventListener('click', e => { if (!moved && e.target === canvas) focus(null); });
document.addEventListener('keydown', e => { if (e.key === 'Escape') focus(null); });
addEventListener('beforeprint', () => focus(null)); // printing always shows the full, undimmed diagram
// Theme: defaults to the OS setting; the button overrides it and persists to localStorage
const tbtn = document.getElementById('theme-toggle');
const sysDark = matchMedia('(prefers-color-scheme: dark)');
const effTheme = () => document.documentElement.dataset.theme || (sysDark.matches ? 'dark' : 'light');
// Thin-line SVG icon (stroke="currentColor" — recolors with the theme just like text, no
// dependency on the OS's emoji font)
const ICON_SUN = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></svg>';
const ICON_MOON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"/></svg>';
const paintBtn = () => { tbtn.innerHTML = effTheme() === 'dark' ? ICON_SUN : ICON_MOON;
tbtn.title = effTheme() === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'; };
tbtn.addEventListener('click', () => { const n = effTheme() === 'dark' ? 'light' : 'dark';
document.documentElement.dataset.theme = n; try { localStorage.setItem('theme', n); } catch (e) {} paintBtn(); });
sysDark.addEventListener('change', paintBtn);
paintBtn();
// === Drag boxes to rearrange (the "✥ Rearrange" mode, OFF by default) ===
// v1: translates the whole box only; arrows anchored to that box (endpoint within 14 units of
// its edge at load time — matching the skill's "~8px gap" convention) stretch to follow, while
// the corridor waypoints in between STAY PUT (only the final segment touching the dragged box
// stretches — the rest of the route isn't redrawn).
// KNOWN LIMITS: badges/labels on a line do NOT follow along; a dashed region frame does NOT
// grow to keep containing a box dragged out of it — recenter it by eye if that happens.
// Traveling dots (.flow-dot) are hidden while rearranging; each dot's path is refreshed the
// moment the drag ends, so turning rearrange mode back off and reselecting the flow shows the
// dots running along the new position.
const editBtn = document.getElementById('edit-toggle');
const boxGs = [...svg.querySelectorAll('g[data-flows]')];
const arrowEls = [...svg.querySelectorAll('line[data-flow][marker-end], path[data-flow][marker-end]')];
const EDGE_THRESH = 14;
boxGs.forEach(g => {
const r = g.querySelector('rect');
if (!r) return;
g.__box0 = { x: +r.getAttribute('x'), y: +r.getAttribute('y'), w: +r.getAttribute('width'), h: +r.getAttribute('height') };
g.__dx = 0; g.__dy = 0;
});
const edgeGap = (b, px, py) => {
const dx = Math.max(b.x - px, px - (b.x + b.w), 0), dy = Math.max(b.y - py, py - (b.y + b.h), 0);
return Math.hypot(dx, dy);
};
// Read & cache each line's ORIGINAL 2 endpoints once (idempotent) — every later update is
// recomputed from that origin + the box's current delta, never accumulated frame over frame
// (which would drift after several drags).
function endpointsOf(el) {
if (el.__ep0) return el.__ep0;
if (el.tagName === 'line') {
el.__ep0 = [{ x: +el.getAttribute('x1'), y: +el.getAttribute('y1') }, { x: +el.getAttribute('x2'), y: +el.getAttribute('y2') }];
el.__set = (i, nx, ny) => { el.setAttribute(i === 0 ? 'x1' : 'x2', nx); el.setAttribute(i === 0 ? 'y1' : 'y2', ny); };
} else {
const toks0 = el.getAttribute('d').match(/[MLA][^MLA]*/g);
const first = toks0[0].slice(1).trim().split(/[\s,]+/).map(Number);
const lastN = toks0[toks0.length - 1].slice(1).trim().split(/[\s,]+/).map(Number);
el.__ep0 = [{ x: first[0], y: first[1] }, { x: lastN[lastN.length - 2], y: lastN[lastN.length - 1] }];
el.__set = (i, nx, ny) => {
const t = [...toks0];
if (i === 0) { t[0] = `M${nx},${ny}`; }
else {
const last = t[t.length - 1], cmd = last[0];
const n = last.slice(1).trim().split(/[\s,]+/).map(Number);
n[n.length - 2] = nx; n[n.length - 1] = ny;
t[t.length - 1] = cmd + n.join(',');
}
el.setAttribute('d', t.join(' '));
};
}
return el.__ep0;
}
// For each arrow, work out which box (if any) sits close enough to each endpoint — computed once
// at load time from the ORIGINAL coordinates (before anyone drags anything), so dragging never
// shifts this initial assignment.
const arrowAssoc = arrowEls.map(el => {
const eps = endpointsOf(el);
const assoc = eps.map(ep => {
let best = null, bestGap = EDGE_THRESH;
for (const g of boxGs) {
if (!g.__box0) continue;
const gap = edgeGap(g.__box0, ep.x, ep.y);
if (gap < bestGap) { bestGap = gap; best = g; }
}
return best;
});
const dotWrap = el.nextElementSibling && el.nextElementSibling.classList.contains('flow-dot') ? el.nextElementSibling : null;
return { el, assoc, dotWrap };
});
function pathDOf(el) {
if (el.tagName === 'line') return `M${el.getAttribute('x1')},${el.getAttribute('y1')} L${el.getAttribute('x2')},${el.getAttribute('y2')}`;
return el.getAttribute('d');
}
function refreshDots(dotWrap, d) {
if (!dotWrap) return;
dotWrap.querySelectorAll('animateMotion').forEach(am => am.setAttribute('path', d));
}
function moveArrowsFor(g) {
for (const { el, assoc, dotWrap } of arrowAssoc) {
const eps = endpointsOf(el);
let touched = false;
assoc.forEach((box, i) => {
if (box !== g) return;
touched = true;
el.__set(i, eps[i].x + g.__dx, eps[i].y + g.__dy);
});
if (touched) refreshDots(dotWrap, pathDOf(el));
}
}
let editMode = false;
editBtn.addEventListener('click', () => {
editMode = !editMode;
editBtn.classList.toggle('on', editMode);
svg.classList.toggle('edit-mode', editMode);
if (editMode) focus(null); // entering rearrange mode clears the current flow selection to avoid clutter
});
boxGs.forEach(g => {
let dragging = null;
g.addEventListener('pointerdown', e => {
if (!editMode || e.button !== 0 || !g.__box0) return;
e.stopPropagation(); // stop the canvas's own pan handler from also engaging while dragging a box
dragging = { x: e.clientX, y: e.clientY, dx0: g.__dx, dy0: g.__dy };
g.setPointerCapture(e.pointerId);
});
g.addEventListener('pointermove', e => {
if (!dragging) return;
const scale = vbW / (fitW * zoom); // screen px → viewBox units, scaled by the current zoom
g.__dx = dragging.dx0 + (e.clientX - dragging.x) * scale;
g.__dy = dragging.dy0 + (e.clientY - dragging.y) * scale;
g.setAttribute('transform', `translate(${g.__dx},${g.__dy})`);
moveArrowsFor(g);
});
const endDrag = () => { dragging = null; };
g.addEventListener('pointerup', endDrag);
g.addEventListener('pointercancel', endDrag);
});
})();
</script>
</body>
</html>