@clef-sh/ui 0.1.13-beta.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +38 -0
  2. package/dist/client/assets/index-CVpAmirt.js +26 -0
  3. package/dist/client/favicon-96x96.png +0 -0
  4. package/dist/client/favicon.ico +0 -0
  5. package/dist/client/favicon.svg +16 -0
  6. package/dist/client/index.html +50 -0
  7. package/dist/client-lib/api.d.ts +3 -0
  8. package/dist/client-lib/api.d.ts.map +1 -0
  9. package/dist/client-lib/components/Button.d.ts +10 -0
  10. package/dist/client-lib/components/Button.d.ts.map +1 -0
  11. package/dist/client-lib/components/CopyButton.d.ts +6 -0
  12. package/dist/client-lib/components/CopyButton.d.ts.map +1 -0
  13. package/dist/client-lib/components/EnvBadge.d.ts +7 -0
  14. package/dist/client-lib/components/EnvBadge.d.ts.map +1 -0
  15. package/dist/client-lib/components/MatrixGrid.d.ts +13 -0
  16. package/dist/client-lib/components/MatrixGrid.d.ts.map +1 -0
  17. package/dist/client-lib/components/Sidebar.d.ts +16 -0
  18. package/dist/client-lib/components/Sidebar.d.ts.map +1 -0
  19. package/dist/client-lib/components/StatusDot.d.ts +6 -0
  20. package/dist/client-lib/components/StatusDot.d.ts.map +1 -0
  21. package/dist/client-lib/components/TopBar.d.ts +9 -0
  22. package/dist/client-lib/components/TopBar.d.ts.map +1 -0
  23. package/dist/client-lib/index.d.ts +12 -0
  24. package/dist/client-lib/index.d.ts.map +1 -0
  25. package/dist/client-lib/theme.d.ts +42 -0
  26. package/dist/client-lib/theme.d.ts.map +1 -0
  27. package/dist/server/api.d.ts +11 -0
  28. package/dist/server/api.d.ts.map +1 -0
  29. package/dist/server/api.js +1020 -0
  30. package/dist/server/api.js.map +1 -0
  31. package/dist/server/index.d.ts +12 -0
  32. package/dist/server/index.d.ts.map +1 -0
  33. package/dist/server/index.js +231 -0
  34. package/dist/server/index.js.map +1 -0
  35. package/package.json +74 -0
  36. package/src/client/App.tsx +205 -0
  37. package/src/client/api.test.tsx +94 -0
  38. package/src/client/api.ts +30 -0
  39. package/src/client/components/Button.tsx +52 -0
  40. package/src/client/components/CopyButton.test.tsx +43 -0
  41. package/src/client/components/CopyButton.tsx +36 -0
  42. package/src/client/components/EnvBadge.tsx +32 -0
  43. package/src/client/components/MatrixGrid.tsx +265 -0
  44. package/src/client/components/Sidebar.tsx +337 -0
  45. package/src/client/components/StatusDot.tsx +30 -0
  46. package/src/client/components/TopBar.tsx +50 -0
  47. package/src/client/index.html +50 -0
  48. package/src/client/index.ts +18 -0
  49. package/src/client/main.tsx +15 -0
  50. package/src/client/public/favicon-96x96.png +0 -0
  51. package/src/client/public/favicon.ico +0 -0
  52. package/src/client/public/favicon.svg +16 -0
  53. package/src/client/screens/BackendScreen.test.tsx +611 -0
  54. package/src/client/screens/BackendScreen.tsx +836 -0
  55. package/src/client/screens/DiffView.test.tsx +130 -0
  56. package/src/client/screens/DiffView.tsx +547 -0
  57. package/src/client/screens/GitLogView.test.tsx +113 -0
  58. package/src/client/screens/GitLogView.tsx +192 -0
  59. package/src/client/screens/ImportScreen.tsx +710 -0
  60. package/src/client/screens/LintView.test.tsx +143 -0
  61. package/src/client/screens/LintView.tsx +589 -0
  62. package/src/client/screens/MatrixView.test.tsx +138 -0
  63. package/src/client/screens/MatrixView.tsx +143 -0
  64. package/src/client/screens/NamespaceEditor.test.tsx +694 -0
  65. package/src/client/screens/NamespaceEditor.tsx +1122 -0
  66. package/src/client/screens/RecipientsScreen.tsx +696 -0
  67. package/src/client/screens/ScanScreen.test.tsx +323 -0
  68. package/src/client/screens/ScanScreen.tsx +523 -0
  69. package/src/client/screens/ServiceIdentitiesScreen.tsx +1398 -0
  70. package/src/client/theme.ts +48 -0
@@ -0,0 +1,337 @@
1
+ import React from "react";
2
+ import { theme } from "../theme";
3
+ import type { ClefManifest, MatrixStatus, GitStatus as GitStatusType } from "@clef-sh/core";
4
+
5
+ export type ViewName =
6
+ | "matrix"
7
+ | "editor"
8
+ | "diff"
9
+ | "lint"
10
+ | "scan"
11
+ | "import"
12
+ | "recipients"
13
+ | "identities"
14
+ | "backend"
15
+ | "history";
16
+
17
+ interface SidebarProps {
18
+ activeView: ViewName;
19
+ setView: (view: ViewName) => void;
20
+ activeNs: string;
21
+ setNs: (ns: string) => void;
22
+ manifest: ClefManifest | null;
23
+ matrixStatuses: MatrixStatus[];
24
+ gitStatus: GitStatusType | null;
25
+ lintErrorCount: number;
26
+ scanIssueCount: number;
27
+ }
28
+
29
+ export function Sidebar({
30
+ activeView,
31
+ setView,
32
+ activeNs,
33
+ setNs,
34
+ manifest,
35
+ matrixStatuses,
36
+ gitStatus,
37
+ lintErrorCount,
38
+ scanIssueCount,
39
+ }: SidebarProps) {
40
+ const uncommittedCount = gitStatus
41
+ ? gitStatus.staged.length + gitStatus.unstaged.length + gitStatus.untracked.length
42
+ : 0;
43
+
44
+ const namespaces = manifest?.namespaces ?? [];
45
+
46
+ return (
47
+ <div
48
+ style={{
49
+ width: 220,
50
+ minHeight: "100vh",
51
+ background: theme.surface,
52
+ borderRight: `1px solid ${theme.border}`,
53
+ display: "flex",
54
+ flexDirection: "column",
55
+ flexShrink: 0,
56
+ }}
57
+ >
58
+ {/* Logo */}
59
+ <div
60
+ style={{
61
+ padding: "20px 20px 16px",
62
+ borderBottom: `1px solid ${theme.border}`,
63
+ display: "flex",
64
+ alignItems: "center",
65
+ gap: 10,
66
+ }}
67
+ >
68
+ <div
69
+ style={{
70
+ width: 30,
71
+ height: 30,
72
+ background: theme.accentDim,
73
+ border: `1px solid ${theme.accent}44`,
74
+ borderRadius: 7,
75
+ display: "flex",
76
+ alignItems: "center",
77
+ justifyContent: "center",
78
+ color: theme.accent,
79
+ fontSize: 15,
80
+ }}
81
+ >
82
+ {"\u266A"}
83
+ </div>
84
+ <div>
85
+ <div
86
+ style={{
87
+ fontFamily: theme.sans,
88
+ fontWeight: 700,
89
+ fontSize: 16,
90
+ color: theme.text,
91
+ letterSpacing: "-0.02em",
92
+ }}
93
+ >
94
+ clef
95
+ </div>
96
+ <div
97
+ style={{
98
+ fontFamily: theme.mono,
99
+ fontSize: 9,
100
+ color: theme.textMuted,
101
+ marginTop: -1,
102
+ }}
103
+ >
104
+ {manifest?.sops.default_backend ?? "local"} / main
105
+ </div>
106
+ </div>
107
+ </div>
108
+
109
+ {/* Nav */}
110
+ <div style={{ padding: "12px 10px", flex: 1 }}>
111
+ <NavItem
112
+ icon={"\u229E"}
113
+ label="Matrix"
114
+ active={activeView === "matrix"}
115
+ onClick={() => setView("matrix")}
116
+ />
117
+ <NavItem
118
+ icon={"\u21C4"}
119
+ label="Diff"
120
+ active={activeView === "diff"}
121
+ onClick={() => setView("diff")}
122
+ />
123
+ <NavItem
124
+ icon={"\u2714"}
125
+ label="Lint"
126
+ active={activeView === "lint"}
127
+ onClick={() => setView("lint")}
128
+ badge={lintErrorCount > 0 ? String(lintErrorCount) : undefined}
129
+ badgeColor={theme.red}
130
+ />
131
+ <NavItem
132
+ icon={"\u2315"}
133
+ label="Scan"
134
+ active={activeView === "scan"}
135
+ onClick={() => setView("scan")}
136
+ badge={scanIssueCount > 0 ? String(scanIssueCount) : undefined}
137
+ badgeColor={theme.yellow}
138
+ />
139
+ <NavItem
140
+ icon={"\u2B06"}
141
+ label="Import"
142
+ active={activeView === "import"}
143
+ onClick={() => setView("import")}
144
+ />
145
+ <NavItem
146
+ icon={"\u2662"}
147
+ label="Recipients"
148
+ active={activeView === "recipients"}
149
+ onClick={() => setView("recipients")}
150
+ />
151
+ <NavItem
152
+ icon={"\u2699"}
153
+ label="Service IDs"
154
+ active={activeView === "identities"}
155
+ onClick={() => setView("identities")}
156
+ badge={
157
+ manifest?.service_identities?.length
158
+ ? String(manifest.service_identities.length)
159
+ : undefined
160
+ }
161
+ badgeColor={theme.purple}
162
+ />
163
+ <NavItem
164
+ icon={"\u21BB"}
165
+ label="Backend"
166
+ active={activeView === "backend"}
167
+ onClick={() => setView("backend")}
168
+ />
169
+ <NavItem
170
+ icon={"\u23F1"}
171
+ label="History"
172
+ active={activeView === "history"}
173
+ onClick={() => setView("history")}
174
+ />
175
+
176
+ <div style={{ marginTop: 20, marginBottom: 6, padding: "0 8px" }}>
177
+ <span
178
+ style={{
179
+ fontFamily: theme.sans,
180
+ fontSize: 10,
181
+ fontWeight: 600,
182
+ color: theme.textDim,
183
+ letterSpacing: "0.1em",
184
+ textTransform: "uppercase",
185
+ }}
186
+ >
187
+ Namespaces
188
+ </span>
189
+ </div>
190
+
191
+ {namespaces.map((ns) => {
192
+ const hasIssue = matrixStatuses.some(
193
+ (s) => s.cell.namespace === ns.name && s.issues.length > 0,
194
+ );
195
+ return (
196
+ <NavItem
197
+ key={ns.name}
198
+ icon={
199
+ <span
200
+ style={{
201
+ fontFamily: theme.mono,
202
+ fontSize: 10,
203
+ color: theme.textMuted,
204
+ }}
205
+ >
206
+ //
207
+ </span>
208
+ }
209
+ label={ns.name}
210
+ active={activeView === "editor" && activeNs === ns.name}
211
+ onClick={() => {
212
+ setView("editor");
213
+ setNs(ns.name);
214
+ }}
215
+ badge={hasIssue ? "!" : undefined}
216
+ badgeColor={theme.yellow}
217
+ />
218
+ );
219
+ })}
220
+ </div>
221
+
222
+ {/* Footer */}
223
+ <div style={{ padding: "12px 16px", borderTop: `1px solid ${theme.border}` }}>
224
+ <div
225
+ style={{
226
+ display: "flex",
227
+ alignItems: "center",
228
+ gap: 6,
229
+ color: theme.textMuted,
230
+ }}
231
+ >
232
+ <span style={{ fontFamily: theme.mono, fontSize: 10 }}>
233
+ {uncommittedCount} uncommitted
234
+ </span>
235
+ </div>
236
+ <div style={{ marginTop: 5 }}>
237
+ <div
238
+ style={{
239
+ display: "flex",
240
+ alignItems: "center",
241
+ gap: 6,
242
+ color: theme.green,
243
+ }}
244
+ >
245
+ <span
246
+ style={{
247
+ display: "inline-block",
248
+ width: 6,
249
+ height: 6,
250
+ borderRadius: "50%",
251
+ background: theme.green,
252
+ boxShadow: `0 0 5px ${theme.green}`,
253
+ }}
254
+ />
255
+ <span style={{ fontFamily: theme.mono, fontSize: 10 }}>
256
+ {manifest?.sops.default_backend ?? "age"} key loaded
257
+ </span>
258
+ </div>
259
+ </div>
260
+ </div>
261
+ </div>
262
+ );
263
+ }
264
+
265
+ interface NavItemProps {
266
+ icon: React.ReactNode;
267
+ label: string;
268
+ active: boolean;
269
+ onClick: () => void;
270
+ badge?: string;
271
+ badgeColor?: string;
272
+ }
273
+
274
+ function NavItem({ icon, label, active, onClick, badge, badgeColor }: NavItemProps) {
275
+ return (
276
+ <div
277
+ role="button"
278
+ tabIndex={0}
279
+ data-testid={`nav-${label.toLowerCase()}`}
280
+ onClick={onClick}
281
+ onKeyDown={(e) => {
282
+ if (e.key === "Enter") onClick();
283
+ }}
284
+ style={{
285
+ display: "flex",
286
+ alignItems: "center",
287
+ gap: 9,
288
+ padding: "7px 10px",
289
+ borderRadius: 6,
290
+ cursor: "pointer",
291
+ background: active ? theme.accentDim : "transparent",
292
+ border: active ? `1px solid ${theme.accent}22` : "1px solid transparent",
293
+ marginBottom: 2,
294
+ transition: "all 0.12s",
295
+ position: "relative",
296
+ }}
297
+ >
298
+ <span
299
+ style={{
300
+ color: active ? theme.accent : theme.textMuted,
301
+ display: "flex",
302
+ alignItems: "center",
303
+ width: 14,
304
+ }}
305
+ >
306
+ {icon}
307
+ </span>
308
+ <span
309
+ style={{
310
+ fontFamily: theme.sans,
311
+ fontSize: 13,
312
+ fontWeight: active ? 600 : 400,
313
+ color: active ? theme.accent : theme.text,
314
+ flex: 1,
315
+ }}
316
+ >
317
+ {label}
318
+ </span>
319
+ {badge && badgeColor && (
320
+ <span
321
+ style={{
322
+ fontFamily: theme.mono,
323
+ fontSize: 9,
324
+ fontWeight: 700,
325
+ color: badgeColor,
326
+ background: `${badgeColor}20`,
327
+ border: `1px solid ${badgeColor}44`,
328
+ borderRadius: 3,
329
+ padding: "1px 5px",
330
+ }}
331
+ >
332
+ {badge}
333
+ </span>
334
+ )}
335
+ </div>
336
+ );
337
+ }
@@ -0,0 +1,30 @@
1
+ import React from "react";
2
+ import { theme } from "../theme";
3
+
4
+ interface StatusDotProps {
5
+ status: string;
6
+ }
7
+
8
+ const STATUS_COLORS: Record<string, string> = {
9
+ ok: theme.green,
10
+ missing_keys: theme.red,
11
+ schema_warn: theme.yellow,
12
+ sops_error: theme.red,
13
+ };
14
+
15
+ export function StatusDot({ status }: StatusDotProps) {
16
+ const color = STATUS_COLORS[status] ?? theme.textMuted;
17
+ return (
18
+ <span
19
+ data-testid="status-dot"
20
+ style={{
21
+ display: "inline-block",
22
+ width: 7,
23
+ height: 7,
24
+ borderRadius: "50%",
25
+ background: color,
26
+ boxShadow: `0 0 6px ${color}88`,
27
+ }}
28
+ />
29
+ );
30
+ }
@@ -0,0 +1,50 @@
1
+ import React from "react";
2
+ import { theme } from "../theme";
3
+
4
+ interface TopBarProps {
5
+ title: string;
6
+ subtitle?: string;
7
+ actions?: React.ReactNode;
8
+ }
9
+
10
+ export function TopBar({ title, subtitle, actions }: TopBarProps) {
11
+ return (
12
+ <div
13
+ style={{
14
+ height: 54,
15
+ borderBottom: `1px solid ${theme.border}`,
16
+ display: "flex",
17
+ alignItems: "center",
18
+ padding: "0 24px",
19
+ gap: 16,
20
+ flexShrink: 0,
21
+ }}
22
+ >
23
+ <div style={{ flex: 1 }}>
24
+ <div
25
+ style={{
26
+ fontFamily: theme.sans,
27
+ fontWeight: 600,
28
+ fontSize: 14,
29
+ color: theme.text,
30
+ }}
31
+ >
32
+ {title}
33
+ </div>
34
+ {subtitle && (
35
+ <div
36
+ style={{
37
+ fontFamily: theme.mono,
38
+ fontSize: 10,
39
+ color: theme.textMuted,
40
+ marginTop: 1,
41
+ }}
42
+ >
43
+ {subtitle}
44
+ </div>
45
+ )}
46
+ </div>
47
+ <div style={{ display: "flex", gap: 8 }}>{actions}</div>
48
+ </div>
49
+ );
50
+ }
@@ -0,0 +1,50 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Clef — Secrets Manager</title>
7
+ <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
8
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
9
+ <link rel="shortcut icon" href="/favicon.ico" />
10
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
12
+ <link
13
+ href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600;700&display=swap"
14
+ rel="stylesheet"
15
+ />
16
+ <style>
17
+ * {
18
+ box-sizing: border-box;
19
+ margin: 0;
20
+ padding: 0;
21
+ }
22
+ body {
23
+ background: #0a0b0d;
24
+ color: #e8eaf0;
25
+ font-family: "DM Sans", system-ui, sans-serif;
26
+ }
27
+ ::-webkit-scrollbar {
28
+ width: 6px;
29
+ height: 6px;
30
+ }
31
+ ::-webkit-scrollbar-track {
32
+ background: transparent;
33
+ }
34
+ ::-webkit-scrollbar-thumb {
35
+ background: #1e2330;
36
+ border-radius: 3px;
37
+ }
38
+ select option {
39
+ background: #111318;
40
+ }
41
+ input::placeholder {
42
+ color: #3d4455;
43
+ }
44
+ </style>
45
+ </head>
46
+ <body>
47
+ <div id="root"></div>
48
+ <script type="module" src="./main.tsx"></script>
49
+ </body>
50
+ </html>
@@ -0,0 +1,18 @@
1
+ // Public API — components, design tokens, and API helpers for building custom Clef UIs.
2
+
3
+ // Design system
4
+ export { theme, ENV_COLORS, SEVERITY_META, CATEGORY_META } from "./theme";
5
+ export type { ThemeColor } from "./theme";
6
+
7
+ // Components
8
+ export { Button } from "./components/Button";
9
+ export { CopyButton } from "./components/CopyButton";
10
+ export { EnvBadge } from "./components/EnvBadge";
11
+ export { MatrixGrid } from "./components/MatrixGrid";
12
+ export type { MatrixGridProps } from "./components/MatrixGrid";
13
+ export { Sidebar } from "./components/Sidebar";
14
+ export { StatusDot } from "./components/StatusDot";
15
+ export { TopBar } from "./components/TopBar";
16
+
17
+ // Types
18
+ export type { ViewName } from "./components/Sidebar";
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import App from "./App";
4
+ import { initToken } from "./api";
5
+
6
+ initToken();
7
+
8
+ const root = document.getElementById("root");
9
+ if (root) {
10
+ ReactDOM.createRoot(root).render(
11
+ <React.StrictMode>
12
+ <App />
13
+ </React.StrictMode>,
14
+ );
15
+ }
Binary file
@@ -0,0 +1,16 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" style="background: transparent; background-color: transparent; color-scheme: light dark;" width="11px" height="30px" viewBox="0 0 11 30" content="&lt;mxfile host=&quot;app.diagrams.net&quot; agent=&quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36&quot; version=&quot;29.6.1&quot;&gt;
2
+ &lt;diagram name=&quot;Page-1&quot; id=&quot;wkVNsO5_5GyDIFuGEuFO&quot;&gt;
3
+ &lt;mxGraphModel dx=&quot;322&quot; dy=&quot;155&quot; grid=&quot;1&quot; gridSize=&quot;10&quot; guides=&quot;1&quot; tooltips=&quot;1&quot; connect=&quot;1&quot; arrows=&quot;1&quot; fold=&quot;1&quot; page=&quot;1&quot; pageScale=&quot;1&quot; pageWidth=&quot;850&quot; pageHeight=&quot;1100&quot; math=&quot;0&quot; shadow=&quot;0&quot;&gt;
4
+ &lt;root&gt;
5
+ &lt;mxCell id=&quot;0&quot; /&gt;
6
+ &lt;mxCell id=&quot;1&quot; parent=&quot;0&quot; /&gt;
7
+ &lt;mxCell id=&quot;3N7BDEdEHleT6AeHqIXh-2&quot; parent=&quot;1&quot; style=&quot;shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;verticalAlign=top;aspect=fixed;imageAspect=0;editableCssRules=.*;image=data:image/svg+xml,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTUuMTg1NDg3NzQ3MTkyMzgzIiBzdHJva2U9IiNGMEE1MDAiIGZpbGw9IiNGMEE1MDAiIHZlcnNpb249IjEuMSIgaGVpZ2h0PSI0MC43NjgyMTEzNjQ3NDYwOTQiIHZpZXdCb3g9IjAuMDAwMDIyODg4MTgzNTkzNzUgLTAuMDAwNDAwMDY2Mzc1NzMyNDIxOSAxNS4xODU0ODc3NDcxOTIzODMgNDAuNzY4MjExMzY0NzQ2MDk0Ij4mI3hhOzxwYXRoIGQ9Im0xMi4wNDkgMy41Mjk2YzAuMzA1IDMuMTI2My0yLjAxOSA1LjY1NjMtNC4wNzcyIDcuNzAxNC0wLjkzNDkgMC44OTctMC4xNTUgMC4xNDgtMC42NDM3IDAuNTk0LTAuMTAyMi0wLjQ3OS0wLjI5ODYtMS43MzEtMC4yODAyLTIuMTEgMC4xMzA0LTIuNjkzOSAyLjMxOTgtNi41ODc1IDQuMjM4MS04LjAyMzYgMC4zMDkgMC41NzY3IDAuNTYzIDAuNjIzMSAwLjc2MyAxLjgzODJ6bTAuNjUxIDE2LjE0MmMtMS4yMzItMC45MDYtMi44NS0xLjE0NC00LjMzMzYtMC44ODUtMC4xOTEzLTEuMjU1LTAuMzgyNy0yLjUxLTAuNTc0LTMuNzY0IDIuMzUwNi0yLjMyOSA0LjkwNjYtNS4wMzIyIDUuMDQwNi04LjUzOTQgMC4wNTktMi4yMzItMC4yNzYtNC42NzE0LTEuNjc4LTYuNDgzNi0xLjcwMDQgMC4xMjgyMy0yLjg5OTUgMi4xNTYtMy44MDE5IDMuNDE2NS0xLjQ4ODkgMi42NzA1LTEuMTQxNCA1LjkxNjktMC41NyA4Ljc5NjUtMC44MDk0IDAuOTUyLTEuOTI5NiAxLjc0My0yLjcyNzQgMi43MzQtMi4zNTYxIDIuMzA4LTQuNDA4NSA1LjQzLTQuMDA0NiA4Ljg3OCAwLjE4MzMyIDMuMzM0IDIuNTg5NCA2LjQzNCA1Ljg3MDIgNy4yMjcgMS4yNDU3IDAuMzE1IDIuNTYzOSAwLjM0NiAzLjgyNDEgMC4wOTkgMC4yMTk5IDIuMjUgMS4wMjY2IDQuNjI5IDAuMDkyNSA2LjgxMy0wLjcwMDcgMS41OTgtMi43ODc1IDMuMDA0LTQuMzMyNSAyLjE5Mi0wLjU5OTQtMC4zMTYtMC4xMTM3LTAuMDUxLTAuNDc4LTAuMjUyIDEuMDY5OC0wLjI1NyAxLjk5OTYtMS4wMzYgMi4yNi0xLjU2NSAwLjgzNzgtMS40NjQtMC4zOTk4LTMuNjM5LTIuMTU1NC0zLjM1OC0yLjI2MiAwLjA0Ni0zLjE5MDQgMy4xNC0xLjczNTYgNC42ODUgMS4zNDY4IDEuNTIgMy44MzMgMS4zMTIgNS40MzAxIDAuMzE4IDEuODEyNS0xLjE4IDIuMDM5NS0zLjU0NCAxLjgzMjUtNS41NjItMC4wNy0wLjY3OC0wLjQwMy0yLjY3LTAuNDQ0LTMuMzg3IDAuNjk3LTAuMjQ5IDAuMjA5LTAuMDU5IDEuMTkzLTAuNDQ5IDIuNjYtMS4wNTMgNC4zNTctNC4yNTkgMy41OTQtNy4xMjItMC4zMTgtMS40NjktMS4wNDQtMi45MTQtMi4zMDItMy43OTJ6bTAuNTYxIDUuNzU3YzAuMjE0IDEuOTkxLTEuMDUzIDQuMzIxLTMuMDc5IDQuOTYtMC4xMzYtMC43OTUtMC4xNzItMS4wMTEtMC4yNjI2LTEuNDc1LTAuNDgyMi0yLjQ2LTAuNzQ0LTQuOTg3LTEuMTE2LTcuNDgxIDEuNjI0Ni0wLjE2OCAzLjQ1NzYgMC41NDMgNC4wMjI2IDIuMTg0IDAuMjQ0IDAuNTc3IDAuMzQzIDEuMTk3IDAuNDM1IDEuODEyem0tNS4xNDg2IDUuMTk2Yy0yLjU0NDEgMC4xNDEtNC45OTk1LTEuNTk1LTUuNjM0My00LjA4MS0wLjc0OS0yLjE1My0wLjUyODMtNC42MyAwLjgyMDctNi41MDQgMS4xMTUxLTEuNzAyIDIuNjA2NS0zLjEwNSA0LjAyODYtNC41NDMgMC4xODMgMS4xMjcgMC4zNjYgMi4yNTQgMC41NDkgMy4zODItMi45OTA2IDAuNzgyLTUuMDA0NiA0LjcyNS0zLjIxNSA3LjQ1MSAwLjUzMjQgMC43NjQgMS45NzY1IDIuMjIzIDIuNzY1NSAxLjYzNC0xLjEwMi0wLjY4My0yLjAwMzMtMS44NTktMS44MDk1LTMuMjI3LTAuMDgyMS0xLjI4MiAxLjM2OTktMi45MTEgMi42NTEzLTMuMTk4IDAuNDM4NCAyLjg2OSAwLjk0MTMgNi4wNzMgMS4zNzk3IDguOTQzLTAuNTA1NCAwLjEtMS4wMjExIDAuMTQzLTEuNTM2IDAuMTQzeiIvPiYjeGE7PC9zdmc+;&quot; value=&quot;&quot; vertex=&quot;1&quot;&gt;
8
+ &lt;mxGeometry height=&quot;30&quot; width=&quot;10.98&quot; x=&quot;450&quot; y=&quot;220&quot; as=&quot;geometry&quot; /&gt;
9
+ &lt;/mxCell&gt;
10
+ &lt;/root&gt;
11
+ &lt;/mxGraphModel&gt;
12
+ &lt;/diagram&gt;
13
+ &lt;/mxfile&gt;
14
+ "><defs></defs><g><g data-cell-id="0"><g data-cell-id="1"><g data-cell-id="3N7BDEdEHleT6AeHqIXh-2"><g><image x="0" y="0" width="10.98" height="30" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTUuMTg1NDg3NzQ3MTkyMzgzIiBzdHJva2U9IiNGMEE1MDAiIGZpbGw9IiNGMEE1MDAiIHZlcnNpb249IjEuMSIgaGVpZ2h0PSI0MC43NjgyMTEzNjQ3NDYwOTQiIHZpZXdCb3g9IjAuMDAwMDIyODg4MTgzNTkzNzUgLTAuMDAwNDAwMDY2Mzc1NzMyNDIxOSAxNS4xODU0ODc3NDcxOTIzODMgNDAuNzY4MjExMzY0NzQ2MDk0Ij4mI3hhOzxwYXRoIGQ9Im0xMi4wNDkgMy41Mjk2YzAuMzA1IDMuMTI2My0yLjAxOSA1LjY1NjMtNC4wNzcyIDcuNzAxNC0wLjkzNDkgMC44OTctMC4xNTUgMC4xNDgtMC42NDM3IDAuNTk0LTAuMTAyMi0wLjQ3OS0wLjI5ODYtMS43MzEtMC4yODAyLTIuMTEgMC4xMzA0LTIuNjkzOSAyLjMxOTgtNi41ODc1IDQuMjM4MS04LjAyMzYgMC4zMDkgMC41NzY3IDAuNTYzIDAuNjIzMSAwLjc2MyAxLjgzODJ6bTAuNjUxIDE2LjE0MmMtMS4yMzItMC45MDYtMi44NS0xLjE0NC00LjMzMzYtMC44ODUtMC4xOTEzLTEuMjU1LTAuMzgyNy0yLjUxLTAuNTc0LTMuNzY0IDIuMzUwNi0yLjMyOSA0LjkwNjYtNS4wMzIyIDUuMDQwNi04LjUzOTQgMC4wNTktMi4yMzItMC4yNzYtNC42NzE0LTEuNjc4LTYuNDgzNi0xLjcwMDQgMC4xMjgyMy0yLjg5OTUgMi4xNTYtMy44MDE5IDMuNDE2NS0xLjQ4ODkgMi42NzA1LTEuMTQxNCA1LjkxNjktMC41NyA4Ljc5NjUtMC44MDk0IDAuOTUyLTEuOTI5NiAxLjc0My0yLjcyNzQgMi43MzQtMi4zNTYxIDIuMzA4LTQuNDA4NSA1LjQzLTQuMDA0NiA4Ljg3OCAwLjE4MzMyIDMuMzM0IDIuNTg5NCA2LjQzNCA1Ljg3MDIgNy4yMjcgMS4yNDU3IDAuMzE1IDIuNTYzOSAwLjM0NiAzLjgyNDEgMC4wOTkgMC4yMTk5IDIuMjUgMS4wMjY2IDQuNjI5IDAuMDkyNSA2LjgxMy0wLjcwMDcgMS41OTgtMi43ODc1IDMuMDA0LTQuMzMyNSAyLjE5Mi0wLjU5OTQtMC4zMTYtMC4xMTM3LTAuMDUxLTAuNDc4LTAuMjUyIDEuMDY5OC0wLjI1NyAxLjk5OTYtMS4wMzYgMi4yNi0xLjU2NSAwLjgzNzgtMS40NjQtMC4zOTk4LTMuNjM5LTIuMTU1NC0zLjM1OC0yLjI2MiAwLjA0Ni0zLjE5MDQgMy4xNC0xLjczNTYgNC42ODUgMS4zNDY4IDEuNTIgMy44MzMgMS4zMTIgNS40MzAxIDAuMzE4IDEuODEyNS0xLjE4IDIuMDM5NS0zLjU0NCAxLjgzMjUtNS41NjItMC4wNy0wLjY3OC0wLjQwMy0yLjY3LTAuNDQ0LTMuMzg3IDAuNjk3LTAuMjQ5IDAuMjA5LTAuMDU5IDEuMTkzLTAuNDQ5IDIuNjYtMS4wNTMgNC4zNTctNC4yNTkgMy41OTQtNy4xMjItMC4zMTgtMS40NjktMS4wNDQtMi45MTQtMi4zMDItMy43OTJ6bTAuNTYxIDUuNzU3YzAuMjE0IDEuOTkxLTEuMDUzIDQuMzIxLTMuMDc5IDQuOTYtMC4xMzYtMC43OTUtMC4xNzItMS4wMTEtMC4yNjI2LTEuNDc1LTAuNDgyMi0yLjQ2LTAuNzQ0LTQuOTg3LTEuMTE2LTcuNDgxIDEuNjI0Ni0wLjE2OCAzLjQ1NzYgMC41NDMgNC4wMjI2IDIuMTg0IDAuMjQ0IDAuNTc3IDAuMzQzIDEuMTk3IDAuNDM1IDEuODEyem0tNS4xNDg2IDUuMTk2Yy0yLjU0NDEgMC4xNDEtNC45OTk1LTEuNTk1LTUuNjM0My00LjA4MS0wLjc0OS0yLjE1My0wLjUyODMtNC42MyAwLjgyMDctNi41MDQgMS4xMTUxLTEuNzAyIDIuNjA2NS0zLjEwNSA0LjAyODYtNC41NDMgMC4xODMgMS4xMjcgMC4zNjYgMi4yNTQgMC41NDkgMy4zODItMi45OTA2IDAuNzgyLTUuMDA0NiA0LjcyNS0zLjIxNSA3LjQ1MSAwLjUzMjQgMC43NjQgMS45NzY1IDIuMjIzIDIuNzY1NSAxLjYzNC0xLjEwMi0wLjY4My0yLjAwMzMtMS44NTktMS44MDk1LTMuMjI3LTAuMDgyMS0xLjI4MiAxLjM2OTktMi45MTEgMi42NTEzLTMuMTk4IDAuNDM4NCAyLjg2OSAwLjk0MTMgNi4wNzMgMS4zNzk3IDguOTQzLTAuNTA1NCAwLjEtMS4wMjExIDAuMTQzLTEuNTM2IDAuMTQzeiIvPiYjeGE7PC9zdmc+" preserveAspectRatio="none"></image></g></g></g></g></g><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
15
+ @media (prefers-color-scheme: dark) { :root { filter: none; } }
16
+ </style></svg>