XcodeAddy commited on
Commit
62f00e5
·
1 Parent(s): 4f640fa

Add Next frontend build and serving pipeline

Browse files
.gitignore CHANGED
@@ -67,6 +67,9 @@ datasets/
67
  dist/
68
  build/
69
  out/
 
 
 
70
 
71
  # =========================
72
  # TEMP FILES
 
67
  dist/
68
  build/
69
  out/
70
+ ui/node_modules/
71
+ ui/.next/
72
+ ui/out/
73
 
74
  # =========================
75
  # TEMP FILES
Dockerfile CHANGED
@@ -1,3 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
1
  FROM python:3.11-slim
2
 
3
  WORKDIR /app
@@ -23,6 +34,7 @@ COPY pyproject.toml .
23
  COPY server ./server
24
  COPY static ./static
25
  COPY outputs ./outputs
 
26
 
27
  # Create outputs directory for baseline scores
28
  RUN mkdir -p outputs
 
1
+ FROM node:20-alpine AS ui-builder
2
+
3
+ WORKDIR /ui
4
+
5
+ COPY ui/package.json ui/package-lock.json ./
6
+ RUN npm ci
7
+
8
+ COPY ui ./
9
+ RUN npm run build
10
+
11
+
12
  FROM python:3.11-slim
13
 
14
  WORKDIR /app
 
34
  COPY server ./server
35
  COPY static ./static
36
  COPY outputs ./outputs
37
+ COPY --from=ui-builder /ui/out ./ui/out
38
 
39
  # Create outputs directory for baseline scores
40
  RUN mkdir -p outputs
app.py CHANGED
@@ -5,6 +5,7 @@ from pathlib import Path
5
  from typing import Any
6
 
7
  from fastapi import FastAPI, HTTPException, Query
 
8
  from fastapi.responses import FileResponse, JSONResponse
9
  from pydantic import BaseModel
10
 
@@ -28,6 +29,11 @@ app = FastAPI(
28
  _sessions: dict[str, SentinelEnv] = {}
29
  _STATIC_DIR = Path(__file__).resolve().parent / "static"
30
  _OUTPUTS_DIR = Path(__file__).resolve().parent / "outputs"
 
 
 
 
 
31
 
32
  def _get_env(session_id: str) -> SentinelEnv:
33
  if session_id not in _sessions:
@@ -64,6 +70,9 @@ def health():
64
 
65
  @app.get("/")
66
  def root():
 
 
 
67
  index_path = _STATIC_DIR / "index.html"
68
  if index_path.exists():
69
  return FileResponse(index_path)
 
5
  from typing import Any
6
 
7
  from fastapi import FastAPI, HTTPException, Query
8
+ from fastapi.staticfiles import StaticFiles
9
  from fastapi.responses import FileResponse, JSONResponse
10
  from pydantic import BaseModel
11
 
 
29
  _sessions: dict[str, SentinelEnv] = {}
30
  _STATIC_DIR = Path(__file__).resolve().parent / "static"
31
  _OUTPUTS_DIR = Path(__file__).resolve().parent / "outputs"
32
+ _FRONTEND_OUT_DIR = Path(__file__).resolve().parent / "ui" / "out"
33
+ _FRONTEND_NEXT_DIR = _FRONTEND_OUT_DIR / "_next"
34
+
35
+ if _FRONTEND_NEXT_DIR.exists():
36
+ app.mount("/_next", StaticFiles(directory=_FRONTEND_NEXT_DIR), name="next-assets")
37
 
38
  def _get_env(session_id: str) -> SentinelEnv:
39
  if session_id not in _sessions:
 
70
 
71
  @app.get("/")
72
  def root():
73
+ frontend_index = _FRONTEND_OUT_DIR / "index.html"
74
+ if frontend_index.exists():
75
+ return FileResponse(frontend_index)
76
  index_path = _STATIC_DIR / "index.html"
77
  if index_path.exists():
78
  return FileResponse(index_path)
ui/app/globals.css ADDED
@@ -0,0 +1,837 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --bg: #050711;
3
+ --bg-soft: #0b1020;
4
+ --panel: rgba(18, 24, 40, 0.68);
5
+ --panel-strong: rgba(24, 31, 51, 0.82);
6
+ --panel-soft: rgba(255, 255, 255, 0.04);
7
+ --line: rgba(255, 255, 255, 0.1);
8
+ --ink: #f5f7ff;
9
+ --muted: #9fa9c5;
10
+ --blue: #8ab5ff;
11
+ --mint: #7be1d7;
12
+ --warm: #ffc68d;
13
+ --rose: #ff8f9f;
14
+ --shadow: 0 24px 60px rgba(1, 6, 20, 0.36);
15
+ }
16
+
17
+ * {
18
+ box-sizing: border-box;
19
+ }
20
+
21
+ html {
22
+ background: var(--bg);
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ color: var(--ink);
28
+ background:
29
+ radial-gradient(circle at 12% 0%, rgba(138, 181, 255, 0.18), transparent 22%),
30
+ radial-gradient(circle at 88% 10%, rgba(255, 143, 159, 0.12), transparent 18%),
31
+ radial-gradient(circle at 50% 100%, rgba(123, 225, 215, 0.08), transparent 18%),
32
+ linear-gradient(180deg, #04060d 0%, #080c16 44%, #050711 100%);
33
+ font-family:
34
+ Inter,
35
+ ui-sans-serif,
36
+ system-ui,
37
+ -apple-system,
38
+ BlinkMacSystemFont,
39
+ "Segoe UI",
40
+ sans-serif;
41
+ }
42
+
43
+ button,
44
+ select,
45
+ input {
46
+ font: inherit;
47
+ }
48
+
49
+ .sentinel-shell {
50
+ min-height: 100vh;
51
+ position: relative;
52
+ }
53
+
54
+ .bg-grid {
55
+ position: fixed;
56
+ inset: 0;
57
+ pointer-events: none;
58
+ background:
59
+ linear-gradient(rgba(255, 255, 255, 0.022) 1px, transparent 1px),
60
+ linear-gradient(90deg, rgba(255, 255, 255, 0.018) 1px, transparent 1px);
61
+ background-size: 44px 44px;
62
+ mask-image: radial-gradient(circle at center, black 36%, transparent 84%);
63
+ opacity: 0.34;
64
+ }
65
+
66
+ .topbar,
67
+ .tabbar,
68
+ .section,
69
+ .glass-card,
70
+ .metric-card,
71
+ .control-pill,
72
+ .stat-box,
73
+ .story-lane,
74
+ .json-card,
75
+ .event-row,
76
+ .judge-card,
77
+ .specialist-node,
78
+ .subtask-card {
79
+ border: 1px solid var(--line);
80
+ background:
81
+ linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.015)),
82
+ var(--panel);
83
+ box-shadow:
84
+ inset 0 1px 0 rgba(255, 255, 255, 0.05),
85
+ var(--shadow);
86
+ backdrop-filter: blur(20px) saturate(1.35);
87
+ }
88
+
89
+ .topbar {
90
+ position: sticky;
91
+ top: 0;
92
+ z-index: 30;
93
+ margin: 18px auto 0;
94
+ width: min(1460px, calc(100% - 24px));
95
+ border-radius: 28px;
96
+ padding: 18px 20px;
97
+ display: grid;
98
+ grid-template-columns: minmax(340px, 1fr) auto;
99
+ gap: 18px;
100
+ }
101
+
102
+ .brand {
103
+ display: flex;
104
+ gap: 16px;
105
+ align-items: center;
106
+ }
107
+
108
+ .brand-mark {
109
+ width: 56px;
110
+ height: 56px;
111
+ border-radius: 18px;
112
+ display: grid;
113
+ place-items: center;
114
+ color: #eef4ff;
115
+ border: 1px solid rgba(255, 255, 255, 0.12);
116
+ background:
117
+ radial-gradient(circle at 30% 24%, rgba(138, 181, 255, 0.44), transparent 56%),
118
+ linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02)),
119
+ rgba(18, 23, 35, 0.9);
120
+ }
121
+
122
+ .eyebrow {
123
+ color: var(--muted);
124
+ font-size: 12px;
125
+ letter-spacing: 0.12em;
126
+ text-transform: uppercase;
127
+ }
128
+
129
+ .brand h1 {
130
+ margin: 4px 0 6px;
131
+ font-size: clamp(32px, 5vw, 52px);
132
+ line-height: 0.95;
133
+ }
134
+
135
+ .brand p,
136
+ .tab-copy,
137
+ .section-head span,
138
+ .story-step,
139
+ .proof-row span,
140
+ .proof-row strong,
141
+ .json-head,
142
+ .event-copy span,
143
+ .judge-card span,
144
+ .architecture-card span,
145
+ .subtask-card p,
146
+ .stat-box span {
147
+ color: var(--muted);
148
+ }
149
+
150
+ .controls {
151
+ display: flex;
152
+ gap: 12px;
153
+ align-items: center;
154
+ flex-wrap: wrap;
155
+ justify-content: flex-end;
156
+ }
157
+
158
+ .control-pill {
159
+ min-width: 122px;
160
+ border-radius: 18px;
161
+ padding: 10px 12px;
162
+ }
163
+
164
+ .control-pill label {
165
+ display: block;
166
+ font-size: 11px;
167
+ color: var(--muted);
168
+ margin-bottom: 6px;
169
+ text-transform: uppercase;
170
+ letter-spacing: 0.08em;
171
+ }
172
+
173
+ .control-pill select,
174
+ .control-pill input {
175
+ width: 100%;
176
+ border: 0;
177
+ outline: none;
178
+ background: transparent;
179
+ color: var(--ink);
180
+ }
181
+
182
+ .btn {
183
+ min-height: 48px;
184
+ border-radius: 18px;
185
+ border: 1px solid var(--line);
186
+ padding: 0 18px;
187
+ background:
188
+ linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02)),
189
+ rgba(255, 255, 255, 0.03);
190
+ color: var(--ink);
191
+ cursor: pointer;
192
+ display: inline-flex;
193
+ align-items: center;
194
+ justify-content: center;
195
+ gap: 10px;
196
+ transition: transform 160ms ease, border-color 160ms ease, background 160ms ease;
197
+ }
198
+
199
+ .btn:hover {
200
+ transform: translateY(-1px);
201
+ border-color: rgba(255, 255, 255, 0.18);
202
+ }
203
+
204
+ .btn.primary {
205
+ border-color: rgba(138, 181, 255, 0.22);
206
+ background:
207
+ linear-gradient(180deg, rgba(138, 181, 255, 0.24), rgba(138, 181, 255, 0.08)),
208
+ rgba(255, 255, 255, 0.03);
209
+ }
210
+
211
+ .btn.wide {
212
+ width: 100%;
213
+ }
214
+
215
+ .btn.micro {
216
+ min-height: 42px;
217
+ border-radius: 14px;
218
+ }
219
+
220
+ .btn:disabled {
221
+ opacity: 0.58;
222
+ cursor: not-allowed;
223
+ transform: none;
224
+ }
225
+
226
+ .tabbar {
227
+ width: min(1460px, calc(100% - 24px));
228
+ margin: 14px auto 0;
229
+ border-radius: 24px;
230
+ padding: 14px 18px 18px;
231
+ }
232
+
233
+ .tab {
234
+ min-height: 44px;
235
+ border-radius: 999px;
236
+ border: 1px solid var(--line);
237
+ padding: 0 16px;
238
+ margin-right: 10px;
239
+ background: rgba(255, 255, 255, 0.03);
240
+ color: var(--ink);
241
+ cursor: pointer;
242
+ }
243
+
244
+ .tab.active {
245
+ background:
246
+ linear-gradient(180deg, rgba(138, 181, 255, 0.2), rgba(138, 181, 255, 0.08)),
247
+ rgba(255, 255, 255, 0.04);
248
+ border-color: rgba(138, 181, 255, 0.28);
249
+ }
250
+
251
+ .tab-copy {
252
+ margin: 14px 4px 0;
253
+ max-width: 900px;
254
+ line-height: 1.5;
255
+ }
256
+
257
+ .viewport {
258
+ width: min(1460px, calc(100% - 24px));
259
+ margin: 18px auto 60px;
260
+ display: grid;
261
+ gap: 18px;
262
+ }
263
+
264
+ .viewport-overview {
265
+ grid-template-columns: minmax(420px, 1.05fr) minmax(360px, 0.95fr);
266
+ grid-template-areas:
267
+ "hero hero"
268
+ "architecture architecture"
269
+ "story proof";
270
+ }
271
+
272
+ .viewport-playground {
273
+ grid-template-columns: minmax(520px, 1.14fr) minmax(360px, 0.86fr);
274
+ grid-template-areas:
275
+ "hero hero"
276
+ "mission controls"
277
+ "theater payloads"
278
+ "recorder payloads";
279
+ }
280
+
281
+ .viewport-judge {
282
+ grid-template-columns: minmax(460px, 1.02fr) minmax(360px, 0.98fr);
283
+ grid-template-areas:
284
+ "hero hero"
285
+ "story proof"
286
+ "theater judge"
287
+ "mission judge";
288
+ }
289
+
290
+ .section {
291
+ border-radius: 30px;
292
+ padding: 22px;
293
+ }
294
+
295
+ .hero-surface {
296
+ grid-area: hero;
297
+ display: grid;
298
+ grid-template-columns: minmax(340px, 1.08fr) minmax(320px, 0.92fr);
299
+ gap: 24px;
300
+ align-items: center;
301
+ }
302
+
303
+ .hero-copy h2 {
304
+ margin: 18px 0 12px;
305
+ font-size: clamp(28px, 4vw, 50px);
306
+ line-height: 1.02;
307
+ max-width: 760px;
308
+ }
309
+
310
+ .hero-copy p {
311
+ max-width: 760px;
312
+ color: #d9e2f8;
313
+ font-size: 16px;
314
+ line-height: 1.7;
315
+ }
316
+
317
+ .badge-row {
318
+ display: flex;
319
+ flex-wrap: wrap;
320
+ gap: 10px;
321
+ }
322
+
323
+ .badge {
324
+ min-height: 32px;
325
+ border-radius: 999px;
326
+ border: 1px solid var(--line);
327
+ padding: 0 12px;
328
+ display: inline-flex;
329
+ align-items: center;
330
+ justify-content: center;
331
+ font-size: 13px;
332
+ color: #d9e1f4;
333
+ }
334
+
335
+ .badge.ghost {
336
+ background: rgba(255, 255, 255, 0.04);
337
+ }
338
+
339
+ .badge.warm {
340
+ background: rgba(255, 198, 141, 0.1);
341
+ border-color: rgba(255, 198, 141, 0.22);
342
+ }
343
+
344
+ .metric-strip {
345
+ margin-top: 20px;
346
+ display: grid;
347
+ grid-template-columns: repeat(3, minmax(0, 1fr));
348
+ gap: 12px;
349
+ }
350
+
351
+ .metric-card {
352
+ border-radius: 22px;
353
+ padding: 14px 16px;
354
+ display: flex;
355
+ gap: 12px;
356
+ align-items: center;
357
+ }
358
+
359
+ .metric-card span {
360
+ display: block;
361
+ color: var(--muted);
362
+ font-size: 12px;
363
+ }
364
+
365
+ .metric-card strong {
366
+ display: block;
367
+ font-size: 22px;
368
+ margin-top: 4px;
369
+ }
370
+
371
+ .hero-constellation {
372
+ position: relative;
373
+ min-height: 400px;
374
+ border-radius: 28px;
375
+ overflow: hidden;
376
+ background:
377
+ radial-gradient(circle at center, rgba(138, 181, 255, 0.2), transparent 32%),
378
+ linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.02)),
379
+ rgba(14, 19, 31, 0.74);
380
+ border: 1px solid var(--line);
381
+ }
382
+
383
+ .orb-core {
384
+ position: absolute;
385
+ inset: 50% auto auto 50%;
386
+ width: 170px;
387
+ height: 170px;
388
+ border-radius: 50%;
389
+ transform: translate(-50%, -50%);
390
+ background:
391
+ radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.85), transparent 16%),
392
+ radial-gradient(circle at center, rgba(138, 181, 255, 0.62), rgba(123, 225, 215, 0.18) 44%, transparent 72%);
393
+ filter: blur(1px);
394
+ }
395
+
396
+ .orb-ring {
397
+ position: absolute;
398
+ inset: 50% auto auto 50%;
399
+ border: 1px solid rgba(255, 255, 255, 0.12);
400
+ border-radius: 50%;
401
+ transform: translate(-50%, -50%);
402
+ }
403
+
404
+ .ring-a {
405
+ width: 300px;
406
+ height: 300px;
407
+ }
408
+
409
+ .ring-b {
410
+ width: 420px;
411
+ height: 420px;
412
+ }
413
+
414
+ .hero-node {
415
+ position: absolute;
416
+ min-width: 110px;
417
+ border-radius: 18px;
418
+ padding: 12px 14px;
419
+ border: 1px solid var(--line);
420
+ background:
421
+ linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02)),
422
+ rgba(15, 20, 32, 0.74);
423
+ display: inline-flex;
424
+ align-items: center;
425
+ gap: 8px;
426
+ justify-content: center;
427
+ }
428
+
429
+ .hero-node span {
430
+ color: #eef3ff;
431
+ }
432
+
433
+ .hero-node-main {
434
+ left: 50%;
435
+ top: 49%;
436
+ transform: translate(-50%, -50%);
437
+ min-width: 160px;
438
+ z-index: 2;
439
+ }
440
+
441
+ .hero-node-1 { top: 14%; left: 18%; }
442
+ .hero-node-2 { top: 20%; right: 12%; }
443
+ .hero-node-3 { bottom: 18%; left: 14%; }
444
+ .hero-node-4 { bottom: 16%; right: 16%; }
445
+ .hero-node-5 { top: 60%; right: 4%; }
446
+
447
+ .architecture { grid-area: architecture; }
448
+ .story { grid-area: story; }
449
+ .proof { grid-area: proof; }
450
+ .mission { grid-area: mission; }
451
+ .theater { grid-area: theater; }
452
+ .controls-panel { grid-area: controls; }
453
+ .payloads { grid-area: payloads; }
454
+ .recorder { grid-area: recorder; }
455
+ .judge { grid-area: judge; }
456
+
457
+ .section-head {
458
+ margin-bottom: 18px;
459
+ }
460
+
461
+ .section-head span {
462
+ display: block;
463
+ margin-bottom: 8px;
464
+ font-size: 12px;
465
+ text-transform: uppercase;
466
+ letter-spacing: 0.12em;
467
+ }
468
+
469
+ .section-head h3 {
470
+ margin: 0;
471
+ font-size: 28px;
472
+ }
473
+
474
+ .architecture-grid,
475
+ .story-grid,
476
+ .proof-grid,
477
+ .payload-grid,
478
+ .mission-grid,
479
+ .judge-sequence {
480
+ display: grid;
481
+ gap: 14px;
482
+ }
483
+
484
+ .architecture-grid {
485
+ grid-template-columns: repeat(4, minmax(0, 1fr));
486
+ }
487
+
488
+ .architecture-card {
489
+ border-radius: 22px;
490
+ padding: 18px;
491
+ }
492
+
493
+ .architecture-card strong {
494
+ display: block;
495
+ margin: 14px 0 8px;
496
+ font-size: 18px;
497
+ }
498
+
499
+ .story-grid {
500
+ grid-template-columns: repeat(2, minmax(0, 1fr));
501
+ }
502
+
503
+ .story-lane {
504
+ border-radius: 24px;
505
+ padding: 18px;
506
+ }
507
+
508
+ .story-lane.before {
509
+ background:
510
+ linear-gradient(180deg, rgba(255, 143, 159, 0.14), rgba(255, 143, 159, 0.05)),
511
+ rgba(18, 24, 40, 0.72);
512
+ }
513
+
514
+ .story-lane.after {
515
+ background:
516
+ linear-gradient(180deg, rgba(138, 181, 255, 0.14), rgba(123, 225, 215, 0.06)),
517
+ rgba(18, 24, 40, 0.72);
518
+ }
519
+
520
+ .story-header {
521
+ display: grid;
522
+ gap: 10px;
523
+ margin-bottom: 14px;
524
+ }
525
+
526
+ .story-label {
527
+ width: fit-content;
528
+ min-height: 30px;
529
+ border-radius: 999px;
530
+ padding: 0 12px;
531
+ display: inline-flex;
532
+ align-items: center;
533
+ border: 1px solid var(--line);
534
+ background: rgba(255, 255, 255, 0.04);
535
+ color: #eaf0ff;
536
+ }
537
+
538
+ .story-header strong {
539
+ font-size: 20px;
540
+ }
541
+
542
+ .story-steps {
543
+ display: grid;
544
+ gap: 10px;
545
+ }
546
+
547
+ .story-step {
548
+ border-radius: 18px;
549
+ padding: 14px;
550
+ background: rgba(8, 12, 20, 0.42);
551
+ }
552
+
553
+ .proof-grid {
554
+ grid-template-columns: minmax(280px, 0.95fr) minmax(300px, 1.05fr);
555
+ }
556
+
557
+ .proof-list {
558
+ display: grid;
559
+ gap: 12px;
560
+ }
561
+
562
+ .proof-row {
563
+ display: grid;
564
+ grid-template-columns: 92px 1fr 54px;
565
+ align-items: center;
566
+ gap: 12px;
567
+ }
568
+
569
+ .proof-meter {
570
+ height: 12px;
571
+ border-radius: 999px;
572
+ background: rgba(255, 255, 255, 0.08);
573
+ overflow: hidden;
574
+ }
575
+
576
+ .proof-meter span {
577
+ display: block;
578
+ height: 100%;
579
+ border-radius: inherit;
580
+ }
581
+
582
+ .tone-bad { background: linear-gradient(90deg, #ff8f9f, #ffb07c); }
583
+ .tone-neutral { background: linear-gradient(90deg, #8ab5ff, #b19dff); }
584
+ .tone-good { background: linear-gradient(90deg, #7be1d7, #8ab5ff); }
585
+ .tone-warm { background: linear-gradient(90deg, #ffc68d, #ff8f9f); }
586
+
587
+ .chart-card {
588
+ border-radius: 24px;
589
+ padding: 14px;
590
+ border: 1px solid rgba(255, 255, 255, 0.16);
591
+ background:
592
+ linear-gradient(180deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.04)),
593
+ rgba(247, 249, 255, 0.94);
594
+ }
595
+
596
+ .chart-card img {
597
+ width: 100%;
598
+ display: block;
599
+ border-radius: 14px;
600
+ }
601
+
602
+ .mission-grid {
603
+ grid-template-columns: repeat(4, minmax(0, 1fr));
604
+ }
605
+
606
+ .stat-box {
607
+ border-radius: 18px;
608
+ padding: 16px;
609
+ }
610
+
611
+ .stat-box strong {
612
+ display: block;
613
+ margin-top: 8px;
614
+ font-size: 24px;
615
+ }
616
+
617
+ .subtask-card {
618
+ border-radius: 24px;
619
+ padding: 18px;
620
+ margin-top: 14px;
621
+ }
622
+
623
+ .subtask-head {
624
+ display: flex;
625
+ justify-content: space-between;
626
+ gap: 12px;
627
+ align-items: center;
628
+ }
629
+
630
+ .subtask-head strong {
631
+ font-size: 14px;
632
+ }
633
+
634
+ .subtask-card p {
635
+ margin: 14px 0 0;
636
+ line-height: 1.6;
637
+ }
638
+
639
+ .theater-stage {
640
+ display: grid;
641
+ grid-template-columns: repeat(5, minmax(0, 1fr));
642
+ gap: 12px;
643
+ }
644
+
645
+ .specialist-node {
646
+ border-radius: 22px;
647
+ padding: 16px;
648
+ }
649
+
650
+ .specialist-node.active {
651
+ border-color: rgba(138, 181, 255, 0.28);
652
+ }
653
+
654
+ .specialist-top,
655
+ .subtask-head {
656
+ color: #f7faff;
657
+ }
658
+
659
+ .specialist-top {
660
+ display: flex;
661
+ justify-content: space-between;
662
+ gap: 8px;
663
+ }
664
+
665
+ .meter {
666
+ height: 10px;
667
+ border-radius: 999px;
668
+ margin: 16px 0 12px;
669
+ background: rgba(255, 255, 255, 0.08);
670
+ overflow: hidden;
671
+ }
672
+
673
+ .meter span {
674
+ display: block;
675
+ height: 100%;
676
+ border-radius: inherit;
677
+ }
678
+
679
+ .specialist-foot {
680
+ color: var(--muted);
681
+ font-size: 13px;
682
+ }
683
+
684
+ .action-stack {
685
+ display: grid;
686
+ gap: 14px;
687
+ }
688
+
689
+ .micro-actions {
690
+ display: grid;
691
+ gap: 10px;
692
+ grid-template-columns: repeat(2, minmax(0, 1fr));
693
+ }
694
+
695
+ .payload-grid {
696
+ grid-template-columns: repeat(2, minmax(0, 1fr));
697
+ }
698
+
699
+ .json-card {
700
+ border-radius: 22px;
701
+ overflow: hidden;
702
+ }
703
+
704
+ .json-head {
705
+ padding: 14px 16px;
706
+ border-bottom: 1px solid var(--line);
707
+ }
708
+
709
+ .json-card pre {
710
+ margin: 0;
711
+ padding: 16px;
712
+ max-height: 340px;
713
+ overflow: auto;
714
+ color: #d8e4ff;
715
+ font-size: 12px;
716
+ line-height: 1.55;
717
+ font-family:
718
+ ui-monospace,
719
+ SFMono-Regular,
720
+ Menlo,
721
+ Monaco,
722
+ Consolas,
723
+ "Liberation Mono",
724
+ monospace;
725
+ }
726
+
727
+ .event-list {
728
+ display: grid;
729
+ gap: 10px;
730
+ }
731
+
732
+ .event-row {
733
+ border-radius: 18px;
734
+ padding: 14px;
735
+ display: grid;
736
+ grid-template-columns: 58px 1fr 54px;
737
+ gap: 12px;
738
+ align-items: start;
739
+ }
740
+
741
+ .event-step,
742
+ .event-reward {
743
+ color: #ecf2ff;
744
+ font-weight: 700;
745
+ }
746
+
747
+ .event-copy strong,
748
+ .judge-card strong {
749
+ display: block;
750
+ margin-bottom: 6px;
751
+ color: #f7fbff;
752
+ }
753
+
754
+ .judge-sequence {
755
+ grid-template-columns: repeat(3, minmax(0, 1fr));
756
+ }
757
+
758
+ .judge-card {
759
+ border-radius: 24px;
760
+ padding: 18px;
761
+ }
762
+
763
+ @media (max-width: 1100px) {
764
+ .topbar,
765
+ .hero-surface,
766
+ .proof-grid,
767
+ .story-grid,
768
+ .architecture-grid,
769
+ .payload-grid,
770
+ .mission-grid,
771
+ .judge-sequence {
772
+ grid-template-columns: 1fr;
773
+ }
774
+
775
+ .viewport-overview,
776
+ .viewport-playground,
777
+ .viewport-judge {
778
+ grid-template-columns: 1fr;
779
+ grid-template-areas:
780
+ "hero"
781
+ "architecture"
782
+ "story"
783
+ "proof"
784
+ "mission"
785
+ "controls"
786
+ "theater"
787
+ "payloads"
788
+ "recorder"
789
+ "judge";
790
+ }
791
+
792
+ .theater-stage {
793
+ grid-template-columns: repeat(2, minmax(0, 1fr));
794
+ }
795
+ }
796
+
797
+ @media (max-width: 720px) {
798
+ .topbar {
799
+ grid-template-columns: 1fr;
800
+ }
801
+
802
+ .controls,
803
+ .badge-row,
804
+ .micro-actions,
805
+ .theater-stage {
806
+ grid-template-columns: 1fr;
807
+ }
808
+
809
+ .controls {
810
+ justify-content: flex-start;
811
+ }
812
+
813
+ .metric-strip,
814
+ .payload-grid,
815
+ .mission-grid,
816
+ .judge-sequence {
817
+ grid-template-columns: 1fr;
818
+ }
819
+
820
+ .event-row,
821
+ .proof-row {
822
+ grid-template-columns: 1fr;
823
+ }
824
+
825
+ .hero-constellation {
826
+ min-height: 320px;
827
+ }
828
+
829
+ .hero-node-1,
830
+ .hero-node-2,
831
+ .hero-node-3,
832
+ .hero-node-4,
833
+ .hero-node-5 {
834
+ position: static;
835
+ margin: 10px;
836
+ }
837
+ }
ui/app/layout.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import "./globals.css";
2
+ import type { Metadata } from "next";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "SENTINEL",
6
+ description:
7
+ "Trust-calibration environment for long-horizon multi-agent RL training."
8
+ };
9
+
10
+ export default function RootLayout({
11
+ children
12
+ }: Readonly<{
13
+ children: React.ReactNode;
14
+ }>) {
15
+ return (
16
+ <html lang="en">
17
+ <body>{children}</body>
18
+ </html>
19
+ );
20
+ }
ui/app/page.tsx ADDED
@@ -0,0 +1,917 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { AnimatePresence, motion } from "framer-motion";
5
+ import {
6
+ Activity,
7
+ ArrowRight,
8
+ Brain,
9
+ CircleGauge,
10
+ Cpu,
11
+ Eye,
12
+ Radar,
13
+ ShieldAlert,
14
+ Sparkles,
15
+ SplitSquareHorizontal,
16
+ Waves
17
+ } from "lucide-react";
18
+
19
+ type ViewMode = "overview" | "playground" | "judge";
20
+ type TaskType = "task1" | "task2" | "task3";
21
+ type ActionType = "delegate" | "verify" | "solve_independently" | "skip";
22
+
23
+ type Observation = {
24
+ session_id: string;
25
+ scenario_id: string;
26
+ task_type: TaskType;
27
+ difficulty: string;
28
+ task_description: string;
29
+ current_subtask: string;
30
+ subtask_index: number;
31
+ subtasks_total: number;
32
+ subtasks_remaining: number;
33
+ available_specialists: string[];
34
+ trust_snapshot: Record<string, number>;
35
+ stakes_level: number;
36
+ step_count: number;
37
+ max_steps: number;
38
+ last_action_summary: string | null;
39
+ last_reward: number;
40
+ episode_status: string;
41
+ };
42
+
43
+ type Reward = {
44
+ value: number;
45
+ reason: string;
46
+ signal_breakdown?: Record<string, number>;
47
+ };
48
+
49
+ type StepResult = {
50
+ observation: Observation;
51
+ reward: Reward;
52
+ done: boolean;
53
+ info: {
54
+ session_id: string;
55
+ step_count: number;
56
+ max_steps: number;
57
+ total_reward: number;
58
+ score: number;
59
+ adversarial_detections?: number;
60
+ adversarial_poisonings?: number;
61
+ };
62
+ };
63
+
64
+ type EvalSummary = {
65
+ avg_score: number;
66
+ avg_completion_rate: number;
67
+ avg_detection_rate: number;
68
+ avg_trust_calibration: number;
69
+ avg_steps: number;
70
+ };
71
+
72
+ type EvaluationData = {
73
+ summary: {
74
+ random: EvalSummary;
75
+ heuristic: EvalSummary;
76
+ oracle_lite: EvalSummary;
77
+ };
78
+ by_task: {
79
+ task1: Record<string, EvalSummary>;
80
+ task2: Record<string, EvalSummary>;
81
+ task3: Record<string, EvalSummary>;
82
+ };
83
+ };
84
+
85
+ type EventItem = {
86
+ step: number;
87
+ action: string;
88
+ summary: string;
89
+ reward: string;
90
+ };
91
+
92
+ const tabs: { id: ViewMode; label: string; copy: string }[] = [
93
+ {
94
+ id: "overview",
95
+ label: "Overview",
96
+ copy:
97
+ "A product-style explanation of why SENTINEL exists, what it teaches, and how reward proves learning."
98
+ },
99
+ {
100
+ id: "playground",
101
+ label: "Playground",
102
+ copy:
103
+ "A live browser-facing mission surface where every trust change, request, response, and step result stays visible."
104
+ },
105
+ {
106
+ id: "judge",
107
+ label: "Judge Demo",
108
+ copy:
109
+ "A tighter pitch mode: show the baseline failure, the calibrated recovery, then the profile swap that proves generalization."
110
+ }
111
+ ];
112
+
113
+ const taskOptions: { value: TaskType; label: string }[] = [
114
+ { value: "task1", label: "Task 1" },
115
+ { value: "task2", label: "Task 2" },
116
+ { value: "task3", label: "Task 3" }
117
+ ];
118
+
119
+ const architectureCards = [
120
+ {
121
+ icon: Brain,
122
+ title: "Orchestrator",
123
+ copy:
124
+ "One public-facing decision maker learns trust, verification, and recovery from behavior alone."
125
+ },
126
+ {
127
+ icon: SplitSquareHorizontal,
128
+ title: "Shuffled Specialists",
129
+ copy:
130
+ "Public slots stay the same. Hidden specialist profiles reshuffle every reset, so identity memorization breaks."
131
+ },
132
+ {
133
+ icon: CircleGauge,
134
+ title: "Trust Ledger",
135
+ copy:
136
+ "Bayesian trust updates convert observed behavior into reusable routing signal across the episode."
137
+ },
138
+ {
139
+ icon: ShieldAlert,
140
+ title: "Reward Engine",
141
+ copy:
142
+ "Completion, adversarial detection, calibration, and efficiency form a reward the agent cannot cheaply hack."
143
+ }
144
+ ];
145
+
146
+ function formatScore(value: number | undefined) {
147
+ return typeof value === "number" ? value.toFixed(3) : "0.000";
148
+ }
149
+
150
+ function trustColor(value: number) {
151
+ if (value >= 0.72) return "#7CE0D6";
152
+ if (value >= 0.5) return "#8DB5FF";
153
+ if (value >= 0.3) return "#FFC07E";
154
+ return "#FF8B94";
155
+ }
156
+
157
+ function bestSpecialist(obs: Observation | null) {
158
+ if (!obs) return "S0";
159
+ return [...obs.available_specialists].sort(
160
+ (a, b) => (obs.trust_snapshot[b] ?? 0.5) - (obs.trust_snapshot[a] ?? 0.5)
161
+ )[0];
162
+ }
163
+
164
+ function recommendedMove(obs: Observation | null): {
165
+ action: ActionType;
166
+ specialist: string;
167
+ trust: number;
168
+ } {
169
+ if (!obs) {
170
+ return { action: "delegate", specialist: "S0", trust: 0.5 };
171
+ }
172
+ const specialist = bestSpecialist(obs);
173
+ const trust = obs.trust_snapshot[specialist] ?? 0.5;
174
+ if (obs.stakes_level >= 0.7 && trust < 0.65) {
175
+ return { action: "verify", specialist, trust };
176
+ }
177
+ return { action: "delegate", specialist, trust };
178
+ }
179
+
180
+ function randomMove(obs: Observation | null): {
181
+ action: ActionType;
182
+ specialist: string;
183
+ trust: number;
184
+ } {
185
+ if (!obs) {
186
+ return { action: "delegate", specialist: "S0", trust: 0.5 };
187
+ }
188
+ const specialist =
189
+ obs.available_specialists[
190
+ Math.floor(Math.random() * obs.available_specialists.length)
191
+ ] || "S0";
192
+ return {
193
+ action: "delegate",
194
+ specialist,
195
+ trust: obs.trust_snapshot[specialist] ?? 0.5
196
+ };
197
+ }
198
+
199
+ export default function Page() {
200
+ const [view, setView] = useState<ViewMode>("overview");
201
+ const [taskType, setTaskType] = useState<TaskType>("task3");
202
+ const [seed, setSeed] = useState(42);
203
+ const [sessionId, setSessionId] = useState<string | null>(null);
204
+ const [result, setResult] = useState<StepResult | null>(null);
205
+ const [running, setRunning] = useState(false);
206
+ const [events, setEvents] = useState<EventItem[]>([]);
207
+ const [lastRequest, setLastRequest] = useState<Record<string, unknown> | null>(
208
+ null
209
+ );
210
+ const [lastResponse, setLastResponse] = useState<Record<string, unknown> | null>(
211
+ null
212
+ );
213
+ const [evaluation, setEvaluation] = useState<EvaluationData | null>(null);
214
+ const [demoPolicy, setDemoPolicy] = useState<"heuristic" | "random">(
215
+ "heuristic"
216
+ );
217
+
218
+ useEffect(() => {
219
+ fetch("/assets/evaluation_results.json")
220
+ .then((res) => res.json())
221
+ .then(setEvaluation)
222
+ .catch(() => null);
223
+ }, []);
224
+
225
+ useEffect(() => {
226
+ void resetEpisode(taskType, seed);
227
+ }, []);
228
+
229
+ const observation = result?.observation ?? null;
230
+ const info = result?.info;
231
+ const reward = result?.reward;
232
+ const move = recommendedMove(observation);
233
+
234
+ const proof = useMemo(() => {
235
+ if (!evaluation) return null;
236
+ return {
237
+ random: evaluation.summary.random,
238
+ heuristic: evaluation.summary.heuristic,
239
+ oracle: evaluation.summary.oracle_lite,
240
+ task3Random: evaluation.by_task.task3.random,
241
+ task3Heuristic: evaluation.by_task.task3.heuristic
242
+ };
243
+ }, [evaluation]);
244
+
245
+ async function resetEpisode(
246
+ nextTask = taskType,
247
+ nextSeed = seed
248
+ ): Promise<StepResult | null> {
249
+ setRunning(true);
250
+ const payload = { task_type: nextTask, seed: nextSeed };
251
+ setLastRequest({ method: "POST", path: "/reset", body: payload });
252
+ try {
253
+ const res = await fetch("/reset", {
254
+ method: "POST",
255
+ headers: { "Content-Type": "application/json" },
256
+ body: JSON.stringify(payload)
257
+ });
258
+ const data = (await res.json()) as StepResult;
259
+ setResult(data);
260
+ setLastResponse(data as unknown as Record<string, unknown>);
261
+ setSessionId(data.info.session_id);
262
+ setEvents([
263
+ {
264
+ step: 0,
265
+ action: "reset",
266
+ summary: "Episode initialized. Hidden profiles reshuffled.",
267
+ reward: "0.00"
268
+ }
269
+ ]);
270
+ return data;
271
+ } finally {
272
+ setRunning(false);
273
+ }
274
+ }
275
+
276
+ async function stepEpisode(
277
+ action: ActionType,
278
+ specialistOverride?: string,
279
+ context?: StepResult | null
280
+ ): Promise<StepResult | null> {
281
+ const activeResult = context ?? result;
282
+ const activeObservation = activeResult?.observation ?? observation;
283
+ const activeSessionId = activeResult?.info.session_id ?? sessionId;
284
+
285
+ if (!activeSessionId || !activeObservation || running || activeResult?.done) {
286
+ return null;
287
+ }
288
+ setRunning(true);
289
+ const specialist =
290
+ action === "delegate" || action === "verify"
291
+ ? specialistOverride || bestSpecialist(activeObservation)
292
+ : null;
293
+ const payload = {
294
+ session_id: activeSessionId,
295
+ task_type: activeObservation.task_type,
296
+ action_type: action,
297
+ specialist_id: specialist,
298
+ subtask_response: action === "solve_independently" ? "SELF_SOLVED" : null,
299
+ reasoning: `next-ui-${action}${specialist ? `-${specialist}` : ""}`
300
+ };
301
+ setLastRequest({
302
+ method: "POST",
303
+ path: `/step?session_id=${activeSessionId}`,
304
+ body: payload
305
+ });
306
+ try {
307
+ const res = await fetch(
308
+ `/step?session_id=${encodeURIComponent(activeSessionId)}`,
309
+ {
310
+ method: "POST",
311
+ headers: { "Content-Type": "application/json" },
312
+ body: JSON.stringify(payload)
313
+ }
314
+ );
315
+ const data = (await res.json()) as StepResult;
316
+ setResult(data);
317
+ setLastResponse(data as unknown as Record<string, unknown>);
318
+ setEvents((prev) => [
319
+ ...prev,
320
+ {
321
+ step: data.info.step_count,
322
+ action: specialist ? `${action}:${specialist}` : action,
323
+ summary: data.reward.reason,
324
+ reward: data.reward.value.toFixed(2)
325
+ }
326
+ ]);
327
+ return data;
328
+ } finally {
329
+ setRunning(false);
330
+ }
331
+ }
332
+
333
+ async function autoRun(policy: "heuristic" | "random") {
334
+ setDemoPolicy(policy);
335
+ let localResult = result;
336
+ if (!localResult || localResult.done) {
337
+ localResult = await resetEpisode();
338
+ }
339
+ let guard = 0;
340
+ while (!localResult?.done && guard < 70) {
341
+ const obs = localResult?.observation ?? observation;
342
+ const selected =
343
+ policy === "random" ? randomMove(obs) : recommendedMove(obs);
344
+ const nextResult = await stepEpisode(
345
+ selected.action,
346
+ selected.specialist,
347
+ localResult
348
+ );
349
+ guard += 1;
350
+ await new Promise((resolve) => setTimeout(resolve, 130));
351
+ if (!nextResult) break;
352
+ localResult = nextResult;
353
+ }
354
+ }
355
+
356
+ async function swapProfilesAndReplay() {
357
+ const nextSeed = seed + 1;
358
+ setSeed(nextSeed);
359
+ await resetEpisode(taskType, nextSeed);
360
+ await autoRun(demoPolicy);
361
+ }
362
+
363
+ return (
364
+ <div className="sentinel-shell">
365
+ <div className="bg-grid" />
366
+ <header className="topbar">
367
+ <div className="brand">
368
+ <div className="brand-mark">
369
+ <Waves size={18} />
370
+ </div>
371
+ <div>
372
+ <div className="eyebrow">THE_BOYS • OpenEnv Finale Build</div>
373
+ <h1>SENTINEL</h1>
374
+ <p>
375
+ A Next-style mission surface for multi-agent trust calibration,
376
+ adversarial detection, and long-horizon orchestration.
377
+ </p>
378
+ </div>
379
+ </div>
380
+
381
+ <div className="controls">
382
+ <div className="control-pill">
383
+ <label>Task</label>
384
+ <select
385
+ value={taskType}
386
+ onChange={(e) => {
387
+ const next = e.target.value as TaskType;
388
+ setTaskType(next);
389
+ void resetEpisode(next, seed);
390
+ }}
391
+ >
392
+ {taskOptions.map((item) => (
393
+ <option key={item.value} value={item.value}>
394
+ {item.label}
395
+ </option>
396
+ ))}
397
+ </select>
398
+ </div>
399
+ <div className="control-pill">
400
+ <label>Seed</label>
401
+ <input
402
+ type="number"
403
+ value={seed}
404
+ onChange={(e) => setSeed(Number(e.target.value || 0))}
405
+ />
406
+ </div>
407
+ <button className="btn primary" onClick={() => void resetEpisode()}>
408
+ Reset
409
+ </button>
410
+ <button className="btn" onClick={() => void swapProfilesAndReplay()}>
411
+ Swap Profiles
412
+ </button>
413
+ </div>
414
+ </header>
415
+
416
+ <nav className="tabbar">
417
+ {tabs.map((tab) => (
418
+ <button
419
+ key={tab.id}
420
+ className={`tab ${view === tab.id ? "active" : ""}`}
421
+ onClick={() => setView(tab.id)}
422
+ >
423
+ {tab.label}
424
+ </button>
425
+ ))}
426
+ <p className="tab-copy">{tabs.find((tab) => tab.id === view)?.copy}</p>
427
+ </nav>
428
+
429
+ <main className={`viewport viewport-${view}`}>
430
+ <section className="hero-panel section hero-surface">
431
+ <div className="hero-copy">
432
+ <div className="badge-row">
433
+ <span className="badge ghost">reset → step → state</span>
434
+ <span className="badge ghost">OpenEnv backend intact</span>
435
+ <span className="badge warm">skill, not identity</span>
436
+ </div>
437
+ <h2>LLMs don&apos;t fail only because they reason badly.</h2>
438
+ <p>
439
+ They fail because they trust the wrong agent too early, skip
440
+ verification when the stakes rise, and compound poisoned state
441
+ across long workflows. SENTINEL turns that into a trainable skill.
442
+ </p>
443
+ <div className="metric-strip">
444
+ <MetricCard
445
+ icon={Activity}
446
+ label="Random baseline"
447
+ value={formatScore(proof?.random.avg_score)}
448
+ />
449
+ <MetricCard
450
+ icon={Radar}
451
+ label="Heuristic baseline"
452
+ value={formatScore(proof?.heuristic.avg_score)}
453
+ />
454
+ <MetricCard
455
+ icon={ShieldAlert}
456
+ label="Task 3 detect"
457
+ value={formatScore(proof?.task3Heuristic.avg_detection_rate)}
458
+ />
459
+ </div>
460
+ </div>
461
+ <HeroConstellation observation={observation} />
462
+ </section>
463
+
464
+ {(view === "overview" || view === "judge") && (
465
+ <>
466
+ <section className="section architecture">
467
+ <SectionHead
468
+ eyebrow="Architecture"
469
+ title="What the system is actually made of"
470
+ />
471
+ <div className="architecture-grid">
472
+ {architectureCards.map((card, index) => (
473
+ <motion.div
474
+ key={card.title}
475
+ className="glass-card architecture-card"
476
+ initial={{ opacity: 0, y: 18 }}
477
+ animate={{ opacity: 1, y: 0 }}
478
+ transition={{ duration: 0.45, delay: index * 0.05 }}
479
+ >
480
+ <card.icon size={18} />
481
+ <strong>{card.title}</strong>
482
+ <span>{card.copy}</span>
483
+ </motion.div>
484
+ ))}
485
+ </div>
486
+ </section>
487
+
488
+ <section className="section story">
489
+ <SectionHead
490
+ eyebrow="Before / After"
491
+ title="Why this environment matters to judges"
492
+ />
493
+ <div className="story-grid">
494
+ <StoryLane
495
+ tone="before"
496
+ label={`Task 3 random ${formatScore(
497
+ proof?.task3Random.avg_score
498
+ )}`}
499
+ title="Without trust calibration"
500
+ steps={[
501
+ "Public slots start close together, so the orchestrator routes with weak evidence.",
502
+ "A high-confidence but wrong specialist can poison a high-stakes node.",
503
+ "That poisoned state propagates across later subtasks before anyone notices.",
504
+ "Detection remains weak and the agent cannot explain which slot became risky."
505
+ ]}
506
+ />
507
+ <StoryLane
508
+ tone="after"
509
+ label={`Task 3 heuristic ${formatScore(
510
+ proof?.task3Heuristic.avg_score
511
+ )}`}
512
+ title="With SENTINEL-style routing"
513
+ steps={[
514
+ "Behavior updates the trust ledger after every step, so public slots diverge fast.",
515
+ "When stakes rise and trust is shaky, the orchestrator switches from delegate to verify.",
516
+ "Adversarial attempts are detected before they cascade into the rest of the workflow.",
517
+ "Profile reshuffle forces evidence-based re-learning, proving skill instead of memorized identity."
518
+ ]}
519
+ />
520
+ </div>
521
+ </section>
522
+
523
+ <section className="section proof">
524
+ <SectionHead
525
+ eyebrow="Reward Proof"
526
+ title="Real baseline numbers from the evaluator"
527
+ />
528
+ <div className="proof-grid">
529
+ <div className="proof-list">
530
+ <ProofBar
531
+ label="Random"
532
+ value={proof?.random.avg_score}
533
+ tone="bad"
534
+ />
535
+ <ProofBar
536
+ label="Heuristic"
537
+ value={proof?.heuristic.avg_score}
538
+ tone="neutral"
539
+ />
540
+ <ProofBar
541
+ label="Oracle-lite"
542
+ value={proof?.oracle.avg_score}
543
+ tone="good"
544
+ />
545
+ <ProofBar
546
+ label="Task 3 detect"
547
+ value={proof?.task3Heuristic.avg_detection_rate}
548
+ tone="warm"
549
+ />
550
+ </div>
551
+ <div className="chart-card">
552
+ <img
553
+ src="/assets/baseline_comparison.png"
554
+ alt="Baseline comparison"
555
+ />
556
+ </div>
557
+ </div>
558
+ </section>
559
+ </>
560
+ )}
561
+
562
+ {(view === "playground" || view === "judge") && (
563
+ <>
564
+ <section className="section mission">
565
+ <SectionHead
566
+ eyebrow="Mission"
567
+ title="Live orchestrator state"
568
+ />
569
+ <div className="mission-grid">
570
+ <StatBox label="Session" value={sessionId?.slice(0, 8) ?? "—"} />
571
+ <StatBox label="Score" value={formatScore(info?.score)} />
572
+ <StatBox
573
+ label="Budget"
574
+ value={
575
+ observation
576
+ ? `${observation.step_count}/${observation.max_steps}`
577
+ : "0/0"
578
+ }
579
+ />
580
+ <StatBox
581
+ label="Stakes"
582
+ value={
583
+ observation ? observation.stakes_level.toFixed(2) : "0.00"
584
+ }
585
+ />
586
+ </div>
587
+ <div className="subtask-card">
588
+ <div className="subtask-head">
589
+ <span>Current subtask</span>
590
+ <strong>{move.action}:{move.specialist}</strong>
591
+ </div>
592
+ <p>
593
+ {observation?.current_subtask ??
594
+ "Reset the episode to begin the live flow."}
595
+ </p>
596
+ </div>
597
+ </section>
598
+
599
+ <section className="section theater">
600
+ <SectionHead
601
+ eyebrow="Trust Surface"
602
+ title="Public slots vs hidden risk"
603
+ />
604
+ <div className="theater-stage">
605
+ {observation?.available_specialists.map((id, index) => {
606
+ const trust = observation.trust_snapshot[id] ?? 0.5;
607
+ return (
608
+ <motion.div
609
+ key={id}
610
+ className={`specialist-node ${
611
+ id === move.specialist ? "active" : ""
612
+ }`}
613
+ initial={{ opacity: 0, y: 14 }}
614
+ animate={{ opacity: 1, y: 0 }}
615
+ transition={{ duration: 0.35, delay: index * 0.05 }}
616
+ >
617
+ <div className="specialist-top">
618
+ <strong>{id}</strong>
619
+ <span>{trust.toFixed(2)}</span>
620
+ </div>
621
+ <div className="meter">
622
+ <span
623
+ style={{
624
+ width: `${trust * 100}%`,
625
+ background: trustColor(trust)
626
+ }}
627
+ />
628
+ </div>
629
+ <div className="specialist-foot">
630
+ {id === move.specialist ? "Recommended" : "Public slot"}
631
+ </div>
632
+ </motion.div>
633
+ );
634
+ })}
635
+ </div>
636
+ </section>
637
+
638
+ <section className="section controls-panel">
639
+ <SectionHead
640
+ eyebrow="Command"
641
+ title="Run the orchestrator"
642
+ />
643
+ <div className="action-stack">
644
+ <button
645
+ className="btn primary wide"
646
+ disabled={running}
647
+ onClick={() => void autoRun("heuristic")}
648
+ >
649
+ <Brain size={16} />
650
+ Run Heuristic
651
+ </button>
652
+ <button
653
+ className="btn wide"
654
+ disabled={running}
655
+ onClick={() => void autoRun("random")}
656
+ >
657
+ <Cpu size={16} />
658
+ Run Random
659
+ </button>
660
+ <div className="micro-actions">
661
+ <button
662
+ className="btn micro"
663
+ disabled={running}
664
+ onClick={() => void stepEpisode("delegate", move.specialist)}
665
+ >
666
+ Delegate
667
+ </button>
668
+ <button
669
+ className="btn micro"
670
+ disabled={running}
671
+ onClick={() => void stepEpisode("verify", move.specialist)}
672
+ >
673
+ Verify
674
+ </button>
675
+ <button
676
+ className="btn micro"
677
+ disabled={running}
678
+ onClick={() => void stepEpisode("solve_independently")}
679
+ >
680
+ Self solve
681
+ </button>
682
+ <button
683
+ className="btn micro"
684
+ disabled={running}
685
+ onClick={() => void stepEpisode("skip")}
686
+ >
687
+ Skip
688
+ </button>
689
+ </div>
690
+ </div>
691
+ </section>
692
+
693
+ <section className="section payloads">
694
+ <SectionHead
695
+ eyebrow="Backend Visible"
696
+ title="Request / response playground"
697
+ />
698
+ <div className="payload-grid">
699
+ <JsonCard title="Last request" data={lastRequest} />
700
+ <JsonCard title="Last response" data={lastResponse} />
701
+ </div>
702
+ </section>
703
+
704
+ <section className="section recorder">
705
+ <SectionHead
706
+ eyebrow="Flight Recorder"
707
+ title="Event trail"
708
+ />
709
+ <div className="event-list">
710
+ {events.slice(-8).reverse().map((event) => (
711
+ <div className="event-row" key={`${event.step}-${event.action}`}>
712
+ <div className="event-step">#{event.step}</div>
713
+ <div className="event-copy">
714
+ <strong>{event.action}</strong>
715
+ <span>{event.summary}</span>
716
+ </div>
717
+ <div className="event-reward">{event.reward}</div>
718
+ </div>
719
+ ))}
720
+ </div>
721
+ </section>
722
+ </>
723
+ )}
724
+
725
+ {view === "judge" && (
726
+ <section className="section judge">
727
+ <SectionHead
728
+ eyebrow="Judge Flow"
729
+ title="A crisp 3-minute demo rail"
730
+ />
731
+ <div className="judge-sequence">
732
+ <JudgeStep
733
+ icon={ShieldAlert}
734
+ title="1. Show the failure"
735
+ copy="Run Random to show that long-horizon multi-agent routing collapses when trust is static."
736
+ />
737
+ <JudgeStep
738
+ icon={Eye}
739
+ title="2. Show the recovery"
740
+ copy="Run Heuristic to show trust divergence, verification at risky gates, and stronger detection."
741
+ />
742
+ <JudgeStep
743
+ icon={Sparkles}
744
+ title="3. Show generalization"
745
+ copy="Swap hidden profiles and replay to prove the orchestrator learned a transferable skill rather than a memorized slot."
746
+ />
747
+ </div>
748
+ </section>
749
+ )}
750
+ </main>
751
+ </div>
752
+ );
753
+ }
754
+
755
+ function SectionHead({
756
+ eyebrow,
757
+ title
758
+ }: {
759
+ eyebrow: string;
760
+ title: string;
761
+ }) {
762
+ return (
763
+ <div className="section-head">
764
+ <span>{eyebrow}</span>
765
+ <h3>{title}</h3>
766
+ </div>
767
+ );
768
+ }
769
+
770
+ function MetricCard({
771
+ icon: Icon,
772
+ label,
773
+ value
774
+ }: {
775
+ icon: typeof Activity;
776
+ label: string;
777
+ value: string;
778
+ }) {
779
+ return (
780
+ <div className="metric-card">
781
+ <Icon size={16} />
782
+ <div>
783
+ <span>{label}</span>
784
+ <strong>{value}</strong>
785
+ </div>
786
+ </div>
787
+ );
788
+ }
789
+
790
+ function StoryLane({
791
+ tone,
792
+ label,
793
+ title,
794
+ steps
795
+ }: {
796
+ tone: "before" | "after";
797
+ label: string;
798
+ title: string;
799
+ steps: string[];
800
+ }) {
801
+ return (
802
+ <div className={`story-lane ${tone}`}>
803
+ <div className="story-header">
804
+ <span className="story-label">{label}</span>
805
+ <strong>{title}</strong>
806
+ </div>
807
+ <div className="story-steps">
808
+ {steps.map((step) => (
809
+ <div key={step} className="story-step">
810
+ {step}
811
+ </div>
812
+ ))}
813
+ </div>
814
+ </div>
815
+ );
816
+ }
817
+
818
+ function ProofBar({
819
+ label,
820
+ value,
821
+ tone
822
+ }: {
823
+ label: string;
824
+ value: number | undefined;
825
+ tone: "bad" | "neutral" | "good" | "warm";
826
+ }) {
827
+ const widths = typeof value === "number" ? `${Math.max(0, value * 100)}%` : "0%";
828
+ return (
829
+ <div className="proof-row">
830
+ <span>{label}</span>
831
+ <div className="proof-meter">
832
+ <span className={`tone-${tone}`} style={{ width: widths }} />
833
+ </div>
834
+ <strong>{formatScore(value)}</strong>
835
+ </div>
836
+ );
837
+ }
838
+
839
+ function StatBox({ label, value }: { label: string; value: string }) {
840
+ return (
841
+ <div className="stat-box">
842
+ <span>{label}</span>
843
+ <strong>{value}</strong>
844
+ </div>
845
+ );
846
+ }
847
+
848
+ function JsonCard({
849
+ title,
850
+ data
851
+ }: {
852
+ title: string;
853
+ data: Record<string, unknown> | null;
854
+ }) {
855
+ return (
856
+ <div className="json-card">
857
+ <div className="json-head">{title}</div>
858
+ <pre>{JSON.stringify(data ?? { waiting: true }, null, 2)}</pre>
859
+ </div>
860
+ );
861
+ }
862
+
863
+ function JudgeStep({
864
+ icon: Icon,
865
+ title,
866
+ copy
867
+ }: {
868
+ icon: typeof ArrowRight;
869
+ title: string;
870
+ copy: string;
871
+ }) {
872
+ return (
873
+ <motion.div
874
+ className="judge-card"
875
+ whileHover={{ y: -3 }}
876
+ transition={{ duration: 0.18 }}
877
+ >
878
+ <Icon size={18} />
879
+ <strong>{title}</strong>
880
+ <span>{copy}</span>
881
+ </motion.div>
882
+ );
883
+ }
884
+
885
+ function HeroConstellation({ observation }: { observation: Observation | null }) {
886
+ const ids = observation?.available_specialists ?? ["S0", "S1", "S2", "S3", "S4"];
887
+ return (
888
+ <div className="hero-constellation">
889
+ <motion.div
890
+ className="orb-core"
891
+ animate={{ scale: [1, 1.06, 1], rotate: [0, 3, 0] }}
892
+ transition={{ duration: 6, repeat: Infinity, ease: "easeInOut" }}
893
+ />
894
+ <div className="orb-ring ring-a" />
895
+ <div className="orb-ring ring-b" />
896
+ <div className="hero-node hero-node-main">
897
+ <Brain size={18} />
898
+ <span>Orchestrator</span>
899
+ </div>
900
+ {ids.map((id, index) => (
901
+ <motion.div
902
+ key={id}
903
+ className={`hero-node hero-node-${index + 1}`}
904
+ animate={{ y: [0, -6, 0] }}
905
+ transition={{
906
+ duration: 3 + index * 0.4,
907
+ repeat: Infinity,
908
+ ease: "easeInOut",
909
+ delay: index * 0.2
910
+ }}
911
+ >
912
+ <span>{id}</span>
913
+ </motion.div>
914
+ ))}
915
+ </div>
916
+ );
917
+ }
ui/next-env.d.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ /// <reference path="./.next/types/routes.d.ts" />
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
ui/next.config.mjs ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ output: "export",
4
+ images: {
5
+ unoptimized: true
6
+ }
7
+ };
8
+
9
+ export default nextConfig;
ui/package-lock.json ADDED
@@ -0,0 +1,1019 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "sentinel-ui",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "sentinel-ui",
9
+ "version": "0.1.0",
10
+ "dependencies": {
11
+ "framer-motion": "^12.23.24",
12
+ "lucide-react": "^0.539.0",
13
+ "next": "^15.3.3",
14
+ "react": "^19.1.1",
15
+ "react-dom": "^19.1.1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^24.3.0",
19
+ "@types/react": "^19.1.11",
20
+ "@types/react-dom": "^19.1.7",
21
+ "typescript": "^5.9.2"
22
+ }
23
+ },
24
+ "node_modules/@emnapi/runtime": {
25
+ "version": "1.10.0",
26
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
27
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
28
+ "license": "MIT",
29
+ "optional": true,
30
+ "dependencies": {
31
+ "tslib": "^2.4.0"
32
+ }
33
+ },
34
+ "node_modules/@img/colour": {
35
+ "version": "1.1.0",
36
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
37
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
38
+ "license": "MIT",
39
+ "optional": true,
40
+ "engines": {
41
+ "node": ">=18"
42
+ }
43
+ },
44
+ "node_modules/@img/sharp-darwin-arm64": {
45
+ "version": "0.34.5",
46
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
47
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
48
+ "cpu": [
49
+ "arm64"
50
+ ],
51
+ "license": "Apache-2.0",
52
+ "optional": true,
53
+ "os": [
54
+ "darwin"
55
+ ],
56
+ "engines": {
57
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
58
+ },
59
+ "funding": {
60
+ "url": "https://opencollective.com/libvips"
61
+ },
62
+ "optionalDependencies": {
63
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
64
+ }
65
+ },
66
+ "node_modules/@img/sharp-darwin-x64": {
67
+ "version": "0.34.5",
68
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
69
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
70
+ "cpu": [
71
+ "x64"
72
+ ],
73
+ "license": "Apache-2.0",
74
+ "optional": true,
75
+ "os": [
76
+ "darwin"
77
+ ],
78
+ "engines": {
79
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
80
+ },
81
+ "funding": {
82
+ "url": "https://opencollective.com/libvips"
83
+ },
84
+ "optionalDependencies": {
85
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
86
+ }
87
+ },
88
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
89
+ "version": "1.2.4",
90
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
91
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
92
+ "cpu": [
93
+ "arm64"
94
+ ],
95
+ "license": "LGPL-3.0-or-later",
96
+ "optional": true,
97
+ "os": [
98
+ "darwin"
99
+ ],
100
+ "funding": {
101
+ "url": "https://opencollective.com/libvips"
102
+ }
103
+ },
104
+ "node_modules/@img/sharp-libvips-darwin-x64": {
105
+ "version": "1.2.4",
106
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
107
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
108
+ "cpu": [
109
+ "x64"
110
+ ],
111
+ "license": "LGPL-3.0-or-later",
112
+ "optional": true,
113
+ "os": [
114
+ "darwin"
115
+ ],
116
+ "funding": {
117
+ "url": "https://opencollective.com/libvips"
118
+ }
119
+ },
120
+ "node_modules/@img/sharp-libvips-linux-arm": {
121
+ "version": "1.2.4",
122
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
123
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
124
+ "cpu": [
125
+ "arm"
126
+ ],
127
+ "license": "LGPL-3.0-or-later",
128
+ "optional": true,
129
+ "os": [
130
+ "linux"
131
+ ],
132
+ "funding": {
133
+ "url": "https://opencollective.com/libvips"
134
+ }
135
+ },
136
+ "node_modules/@img/sharp-libvips-linux-arm64": {
137
+ "version": "1.2.4",
138
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
139
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
140
+ "cpu": [
141
+ "arm64"
142
+ ],
143
+ "license": "LGPL-3.0-or-later",
144
+ "optional": true,
145
+ "os": [
146
+ "linux"
147
+ ],
148
+ "funding": {
149
+ "url": "https://opencollective.com/libvips"
150
+ }
151
+ },
152
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
153
+ "version": "1.2.4",
154
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
155
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
156
+ "cpu": [
157
+ "ppc64"
158
+ ],
159
+ "license": "LGPL-3.0-or-later",
160
+ "optional": true,
161
+ "os": [
162
+ "linux"
163
+ ],
164
+ "funding": {
165
+ "url": "https://opencollective.com/libvips"
166
+ }
167
+ },
168
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
169
+ "version": "1.2.4",
170
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
171
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
172
+ "cpu": [
173
+ "riscv64"
174
+ ],
175
+ "license": "LGPL-3.0-or-later",
176
+ "optional": true,
177
+ "os": [
178
+ "linux"
179
+ ],
180
+ "funding": {
181
+ "url": "https://opencollective.com/libvips"
182
+ }
183
+ },
184
+ "node_modules/@img/sharp-libvips-linux-s390x": {
185
+ "version": "1.2.4",
186
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
187
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
188
+ "cpu": [
189
+ "s390x"
190
+ ],
191
+ "license": "LGPL-3.0-or-later",
192
+ "optional": true,
193
+ "os": [
194
+ "linux"
195
+ ],
196
+ "funding": {
197
+ "url": "https://opencollective.com/libvips"
198
+ }
199
+ },
200
+ "node_modules/@img/sharp-libvips-linux-x64": {
201
+ "version": "1.2.4",
202
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
203
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
204
+ "cpu": [
205
+ "x64"
206
+ ],
207
+ "license": "LGPL-3.0-or-later",
208
+ "optional": true,
209
+ "os": [
210
+ "linux"
211
+ ],
212
+ "funding": {
213
+ "url": "https://opencollective.com/libvips"
214
+ }
215
+ },
216
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
217
+ "version": "1.2.4",
218
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
219
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
220
+ "cpu": [
221
+ "arm64"
222
+ ],
223
+ "license": "LGPL-3.0-or-later",
224
+ "optional": true,
225
+ "os": [
226
+ "linux"
227
+ ],
228
+ "funding": {
229
+ "url": "https://opencollective.com/libvips"
230
+ }
231
+ },
232
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
233
+ "version": "1.2.4",
234
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
235
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
236
+ "cpu": [
237
+ "x64"
238
+ ],
239
+ "license": "LGPL-3.0-or-later",
240
+ "optional": true,
241
+ "os": [
242
+ "linux"
243
+ ],
244
+ "funding": {
245
+ "url": "https://opencollective.com/libvips"
246
+ }
247
+ },
248
+ "node_modules/@img/sharp-linux-arm": {
249
+ "version": "0.34.5",
250
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
251
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
252
+ "cpu": [
253
+ "arm"
254
+ ],
255
+ "license": "Apache-2.0",
256
+ "optional": true,
257
+ "os": [
258
+ "linux"
259
+ ],
260
+ "engines": {
261
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
262
+ },
263
+ "funding": {
264
+ "url": "https://opencollective.com/libvips"
265
+ },
266
+ "optionalDependencies": {
267
+ "@img/sharp-libvips-linux-arm": "1.2.4"
268
+ }
269
+ },
270
+ "node_modules/@img/sharp-linux-arm64": {
271
+ "version": "0.34.5",
272
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
273
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
274
+ "cpu": [
275
+ "arm64"
276
+ ],
277
+ "license": "Apache-2.0",
278
+ "optional": true,
279
+ "os": [
280
+ "linux"
281
+ ],
282
+ "engines": {
283
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
284
+ },
285
+ "funding": {
286
+ "url": "https://opencollective.com/libvips"
287
+ },
288
+ "optionalDependencies": {
289
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
290
+ }
291
+ },
292
+ "node_modules/@img/sharp-linux-ppc64": {
293
+ "version": "0.34.5",
294
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
295
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
296
+ "cpu": [
297
+ "ppc64"
298
+ ],
299
+ "license": "Apache-2.0",
300
+ "optional": true,
301
+ "os": [
302
+ "linux"
303
+ ],
304
+ "engines": {
305
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
306
+ },
307
+ "funding": {
308
+ "url": "https://opencollective.com/libvips"
309
+ },
310
+ "optionalDependencies": {
311
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
312
+ }
313
+ },
314
+ "node_modules/@img/sharp-linux-riscv64": {
315
+ "version": "0.34.5",
316
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
317
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
318
+ "cpu": [
319
+ "riscv64"
320
+ ],
321
+ "license": "Apache-2.0",
322
+ "optional": true,
323
+ "os": [
324
+ "linux"
325
+ ],
326
+ "engines": {
327
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
328
+ },
329
+ "funding": {
330
+ "url": "https://opencollective.com/libvips"
331
+ },
332
+ "optionalDependencies": {
333
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
334
+ }
335
+ },
336
+ "node_modules/@img/sharp-linux-s390x": {
337
+ "version": "0.34.5",
338
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
339
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
340
+ "cpu": [
341
+ "s390x"
342
+ ],
343
+ "license": "Apache-2.0",
344
+ "optional": true,
345
+ "os": [
346
+ "linux"
347
+ ],
348
+ "engines": {
349
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
350
+ },
351
+ "funding": {
352
+ "url": "https://opencollective.com/libvips"
353
+ },
354
+ "optionalDependencies": {
355
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
356
+ }
357
+ },
358
+ "node_modules/@img/sharp-linux-x64": {
359
+ "version": "0.34.5",
360
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
361
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
362
+ "cpu": [
363
+ "x64"
364
+ ],
365
+ "license": "Apache-2.0",
366
+ "optional": true,
367
+ "os": [
368
+ "linux"
369
+ ],
370
+ "engines": {
371
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
372
+ },
373
+ "funding": {
374
+ "url": "https://opencollective.com/libvips"
375
+ },
376
+ "optionalDependencies": {
377
+ "@img/sharp-libvips-linux-x64": "1.2.4"
378
+ }
379
+ },
380
+ "node_modules/@img/sharp-linuxmusl-arm64": {
381
+ "version": "0.34.5",
382
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
383
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
384
+ "cpu": [
385
+ "arm64"
386
+ ],
387
+ "license": "Apache-2.0",
388
+ "optional": true,
389
+ "os": [
390
+ "linux"
391
+ ],
392
+ "engines": {
393
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
394
+ },
395
+ "funding": {
396
+ "url": "https://opencollective.com/libvips"
397
+ },
398
+ "optionalDependencies": {
399
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
400
+ }
401
+ },
402
+ "node_modules/@img/sharp-linuxmusl-x64": {
403
+ "version": "0.34.5",
404
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
405
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
406
+ "cpu": [
407
+ "x64"
408
+ ],
409
+ "license": "Apache-2.0",
410
+ "optional": true,
411
+ "os": [
412
+ "linux"
413
+ ],
414
+ "engines": {
415
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
416
+ },
417
+ "funding": {
418
+ "url": "https://opencollective.com/libvips"
419
+ },
420
+ "optionalDependencies": {
421
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
422
+ }
423
+ },
424
+ "node_modules/@img/sharp-wasm32": {
425
+ "version": "0.34.5",
426
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
427
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
428
+ "cpu": [
429
+ "wasm32"
430
+ ],
431
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
432
+ "optional": true,
433
+ "dependencies": {
434
+ "@emnapi/runtime": "^1.7.0"
435
+ },
436
+ "engines": {
437
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
438
+ },
439
+ "funding": {
440
+ "url": "https://opencollective.com/libvips"
441
+ }
442
+ },
443
+ "node_modules/@img/sharp-win32-arm64": {
444
+ "version": "0.34.5",
445
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
446
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
447
+ "cpu": [
448
+ "arm64"
449
+ ],
450
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
451
+ "optional": true,
452
+ "os": [
453
+ "win32"
454
+ ],
455
+ "engines": {
456
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
457
+ },
458
+ "funding": {
459
+ "url": "https://opencollective.com/libvips"
460
+ }
461
+ },
462
+ "node_modules/@img/sharp-win32-ia32": {
463
+ "version": "0.34.5",
464
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
465
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
466
+ "cpu": [
467
+ "ia32"
468
+ ],
469
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
470
+ "optional": true,
471
+ "os": [
472
+ "win32"
473
+ ],
474
+ "engines": {
475
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
476
+ },
477
+ "funding": {
478
+ "url": "https://opencollective.com/libvips"
479
+ }
480
+ },
481
+ "node_modules/@img/sharp-win32-x64": {
482
+ "version": "0.34.5",
483
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
484
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
485
+ "cpu": [
486
+ "x64"
487
+ ],
488
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
489
+ "optional": true,
490
+ "os": [
491
+ "win32"
492
+ ],
493
+ "engines": {
494
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
495
+ },
496
+ "funding": {
497
+ "url": "https://opencollective.com/libvips"
498
+ }
499
+ },
500
+ "node_modules/@next/env": {
501
+ "version": "15.5.15",
502
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.15.tgz",
503
+ "integrity": "sha512-vcmyu5/MyFzN7CdqRHO3uHO44p/QPCZkuTUXroeUmhNP8bL5PHFEhik22JUazt+CDDoD6EpBYRCaS2pISL+/hg==",
504
+ "license": "MIT"
505
+ },
506
+ "node_modules/@next/swc-darwin-arm64": {
507
+ "version": "15.5.15",
508
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.15.tgz",
509
+ "integrity": "sha512-6PvFO2Tzt10GFK2Ro9tAVEtacMqRmTarYMFKAnV2vYMdwWc73xzmDQyAV7SwEdMhzmiRoo7+m88DuiXlJlGeaw==",
510
+ "cpu": [
511
+ "arm64"
512
+ ],
513
+ "license": "MIT",
514
+ "optional": true,
515
+ "os": [
516
+ "darwin"
517
+ ],
518
+ "engines": {
519
+ "node": ">= 10"
520
+ }
521
+ },
522
+ "node_modules/@next/swc-darwin-x64": {
523
+ "version": "15.5.15",
524
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.15.tgz",
525
+ "integrity": "sha512-G+YNV+z6FDZTp/+IdGyIMFqalBTaQSnvAA+X/hrt+eaTRFSznRMz9K7rTmzvM6tDmKegNtyzgufZW0HwVzEqaQ==",
526
+ "cpu": [
527
+ "x64"
528
+ ],
529
+ "license": "MIT",
530
+ "optional": true,
531
+ "os": [
532
+ "darwin"
533
+ ],
534
+ "engines": {
535
+ "node": ">= 10"
536
+ }
537
+ },
538
+ "node_modules/@next/swc-linux-arm64-gnu": {
539
+ "version": "15.5.15",
540
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.15.tgz",
541
+ "integrity": "sha512-eVkrMcVIBqGfXB+QUC7jjZ94Z6uX/dNStbQFabewAnk13Uy18Igd1YZ/GtPRzdhtm7QwC0e6o7zOQecul4iC1w==",
542
+ "cpu": [
543
+ "arm64"
544
+ ],
545
+ "license": "MIT",
546
+ "optional": true,
547
+ "os": [
548
+ "linux"
549
+ ],
550
+ "engines": {
551
+ "node": ">= 10"
552
+ }
553
+ },
554
+ "node_modules/@next/swc-linux-arm64-musl": {
555
+ "version": "15.5.15",
556
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.15.tgz",
557
+ "integrity": "sha512-RwSHKMQ7InLy5GfkY2/n5PcFycKA08qI1VST78n09nN36nUPqCvGSMiLXlfUmzmpQpF6XeBYP2KRWHi0UW3uNg==",
558
+ "cpu": [
559
+ "arm64"
560
+ ],
561
+ "license": "MIT",
562
+ "optional": true,
563
+ "os": [
564
+ "linux"
565
+ ],
566
+ "engines": {
567
+ "node": ">= 10"
568
+ }
569
+ },
570
+ "node_modules/@next/swc-linux-x64-gnu": {
571
+ "version": "15.5.15",
572
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.15.tgz",
573
+ "integrity": "sha512-nplqvY86LakS+eeiuWsNWvfmK8pFcOEW7ZtVRt4QH70lL+0x6LG/m1OpJ/tvrbwjmR8HH9/fH2jzW1GlL03TIg==",
574
+ "cpu": [
575
+ "x64"
576
+ ],
577
+ "license": "MIT",
578
+ "optional": true,
579
+ "os": [
580
+ "linux"
581
+ ],
582
+ "engines": {
583
+ "node": ">= 10"
584
+ }
585
+ },
586
+ "node_modules/@next/swc-linux-x64-musl": {
587
+ "version": "15.5.15",
588
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.15.tgz",
589
+ "integrity": "sha512-eAgl9NKQ84/sww0v81DQINl/vL2IBxD7sMybd0cWRw6wqgouVI53brVRBrggqBRP/NWeIAE1dm5cbKYoiMlqDQ==",
590
+ "cpu": [
591
+ "x64"
592
+ ],
593
+ "license": "MIT",
594
+ "optional": true,
595
+ "os": [
596
+ "linux"
597
+ ],
598
+ "engines": {
599
+ "node": ">= 10"
600
+ }
601
+ },
602
+ "node_modules/@next/swc-win32-arm64-msvc": {
603
+ "version": "15.5.15",
604
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.15.tgz",
605
+ "integrity": "sha512-GJVZC86lzSquh0MtvZT+L7G8+jMnJcldloOjA8Kf3wXvBrvb6OGe2MzPuALxFshSm/IpwUtD2mIoof39ymf52A==",
606
+ "cpu": [
607
+ "arm64"
608
+ ],
609
+ "license": "MIT",
610
+ "optional": true,
611
+ "os": [
612
+ "win32"
613
+ ],
614
+ "engines": {
615
+ "node": ">= 10"
616
+ }
617
+ },
618
+ "node_modules/@next/swc-win32-x64-msvc": {
619
+ "version": "15.5.15",
620
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.15.tgz",
621
+ "integrity": "sha512-nFucjVdwlFqxh/JG3hWSJ4p8+YJV7Ii8aPDuBQULB6DzUF4UNZETXLfEUk+oI2zEznWWULPt7MeuTE6xtK1HSA==",
622
+ "cpu": [
623
+ "x64"
624
+ ],
625
+ "license": "MIT",
626
+ "optional": true,
627
+ "os": [
628
+ "win32"
629
+ ],
630
+ "engines": {
631
+ "node": ">= 10"
632
+ }
633
+ },
634
+ "node_modules/@swc/helpers": {
635
+ "version": "0.5.15",
636
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
637
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
638
+ "license": "Apache-2.0",
639
+ "dependencies": {
640
+ "tslib": "^2.8.0"
641
+ }
642
+ },
643
+ "node_modules/@types/node": {
644
+ "version": "24.12.2",
645
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz",
646
+ "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==",
647
+ "dev": true,
648
+ "license": "MIT",
649
+ "dependencies": {
650
+ "undici-types": "~7.16.0"
651
+ }
652
+ },
653
+ "node_modules/@types/react": {
654
+ "version": "19.2.14",
655
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
656
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
657
+ "dev": true,
658
+ "license": "MIT",
659
+ "peer": true,
660
+ "dependencies": {
661
+ "csstype": "^3.2.2"
662
+ }
663
+ },
664
+ "node_modules/@types/react-dom": {
665
+ "version": "19.2.3",
666
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
667
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
668
+ "dev": true,
669
+ "license": "MIT",
670
+ "peerDependencies": {
671
+ "@types/react": "^19.2.0"
672
+ }
673
+ },
674
+ "node_modules/caniuse-lite": {
675
+ "version": "1.0.30001790",
676
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz",
677
+ "integrity": "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==",
678
+ "funding": [
679
+ {
680
+ "type": "opencollective",
681
+ "url": "https://opencollective.com/browserslist"
682
+ },
683
+ {
684
+ "type": "tidelift",
685
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
686
+ },
687
+ {
688
+ "type": "github",
689
+ "url": "https://github.com/sponsors/ai"
690
+ }
691
+ ],
692
+ "license": "CC-BY-4.0"
693
+ },
694
+ "node_modules/client-only": {
695
+ "version": "0.0.1",
696
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
697
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
698
+ "license": "MIT"
699
+ },
700
+ "node_modules/csstype": {
701
+ "version": "3.2.3",
702
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
703
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
704
+ "dev": true,
705
+ "license": "MIT"
706
+ },
707
+ "node_modules/detect-libc": {
708
+ "version": "2.1.2",
709
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
710
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
711
+ "license": "Apache-2.0",
712
+ "optional": true,
713
+ "engines": {
714
+ "node": ">=8"
715
+ }
716
+ },
717
+ "node_modules/framer-motion": {
718
+ "version": "12.38.0",
719
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz",
720
+ "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==",
721
+ "license": "MIT",
722
+ "dependencies": {
723
+ "motion-dom": "^12.38.0",
724
+ "motion-utils": "^12.36.0",
725
+ "tslib": "^2.4.0"
726
+ },
727
+ "peerDependencies": {
728
+ "@emotion/is-prop-valid": "*",
729
+ "react": "^18.0.0 || ^19.0.0",
730
+ "react-dom": "^18.0.0 || ^19.0.0"
731
+ },
732
+ "peerDependenciesMeta": {
733
+ "@emotion/is-prop-valid": {
734
+ "optional": true
735
+ },
736
+ "react": {
737
+ "optional": true
738
+ },
739
+ "react-dom": {
740
+ "optional": true
741
+ }
742
+ }
743
+ },
744
+ "node_modules/lucide-react": {
745
+ "version": "0.539.0",
746
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.539.0.tgz",
747
+ "integrity": "sha512-VVISr+VF2krO91FeuCrm1rSOLACQUYVy7NQkzrOty52Y8TlTPcXcMdQFj9bYzBgXbWCiywlwSZ3Z8u6a+6bMlg==",
748
+ "license": "ISC",
749
+ "peerDependencies": {
750
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
751
+ }
752
+ },
753
+ "node_modules/motion-dom": {
754
+ "version": "12.38.0",
755
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz",
756
+ "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==",
757
+ "license": "MIT",
758
+ "dependencies": {
759
+ "motion-utils": "^12.36.0"
760
+ }
761
+ },
762
+ "node_modules/motion-utils": {
763
+ "version": "12.36.0",
764
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz",
765
+ "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==",
766
+ "license": "MIT"
767
+ },
768
+ "node_modules/nanoid": {
769
+ "version": "3.3.11",
770
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
771
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
772
+ "funding": [
773
+ {
774
+ "type": "github",
775
+ "url": "https://github.com/sponsors/ai"
776
+ }
777
+ ],
778
+ "license": "MIT",
779
+ "bin": {
780
+ "nanoid": "bin/nanoid.cjs"
781
+ },
782
+ "engines": {
783
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
784
+ }
785
+ },
786
+ "node_modules/next": {
787
+ "version": "15.5.15",
788
+ "resolved": "https://registry.npmjs.org/next/-/next-15.5.15.tgz",
789
+ "integrity": "sha512-VSqCrJwtLVGwAVE0Sb/yikrQfkwkZW9p+lL/J4+xe+G3ZA+QnWPqgcfH1tDUEuk9y+pthzzVFp4L/U8JerMfMQ==",
790
+ "license": "MIT",
791
+ "dependencies": {
792
+ "@next/env": "15.5.15",
793
+ "@swc/helpers": "0.5.15",
794
+ "caniuse-lite": "^1.0.30001579",
795
+ "postcss": "8.4.31",
796
+ "styled-jsx": "5.1.6"
797
+ },
798
+ "bin": {
799
+ "next": "dist/bin/next"
800
+ },
801
+ "engines": {
802
+ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
803
+ },
804
+ "optionalDependencies": {
805
+ "@next/swc-darwin-arm64": "15.5.15",
806
+ "@next/swc-darwin-x64": "15.5.15",
807
+ "@next/swc-linux-arm64-gnu": "15.5.15",
808
+ "@next/swc-linux-arm64-musl": "15.5.15",
809
+ "@next/swc-linux-x64-gnu": "15.5.15",
810
+ "@next/swc-linux-x64-musl": "15.5.15",
811
+ "@next/swc-win32-arm64-msvc": "15.5.15",
812
+ "@next/swc-win32-x64-msvc": "15.5.15",
813
+ "sharp": "^0.34.3"
814
+ },
815
+ "peerDependencies": {
816
+ "@opentelemetry/api": "^1.1.0",
817
+ "@playwright/test": "^1.51.1",
818
+ "babel-plugin-react-compiler": "*",
819
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
820
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
821
+ "sass": "^1.3.0"
822
+ },
823
+ "peerDependenciesMeta": {
824
+ "@opentelemetry/api": {
825
+ "optional": true
826
+ },
827
+ "@playwright/test": {
828
+ "optional": true
829
+ },
830
+ "babel-plugin-react-compiler": {
831
+ "optional": true
832
+ },
833
+ "sass": {
834
+ "optional": true
835
+ }
836
+ }
837
+ },
838
+ "node_modules/picocolors": {
839
+ "version": "1.1.1",
840
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
841
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
842
+ "license": "ISC"
843
+ },
844
+ "node_modules/postcss": {
845
+ "version": "8.4.31",
846
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
847
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
848
+ "funding": [
849
+ {
850
+ "type": "opencollective",
851
+ "url": "https://opencollective.com/postcss/"
852
+ },
853
+ {
854
+ "type": "tidelift",
855
+ "url": "https://tidelift.com/funding/github/npm/postcss"
856
+ },
857
+ {
858
+ "type": "github",
859
+ "url": "https://github.com/sponsors/ai"
860
+ }
861
+ ],
862
+ "license": "MIT",
863
+ "dependencies": {
864
+ "nanoid": "^3.3.6",
865
+ "picocolors": "^1.0.0",
866
+ "source-map-js": "^1.0.2"
867
+ },
868
+ "engines": {
869
+ "node": "^10 || ^12 || >=14"
870
+ }
871
+ },
872
+ "node_modules/react": {
873
+ "version": "19.2.5",
874
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
875
+ "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
876
+ "license": "MIT",
877
+ "peer": true,
878
+ "engines": {
879
+ "node": ">=0.10.0"
880
+ }
881
+ },
882
+ "node_modules/react-dom": {
883
+ "version": "19.2.5",
884
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
885
+ "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
886
+ "license": "MIT",
887
+ "peer": true,
888
+ "dependencies": {
889
+ "scheduler": "^0.27.0"
890
+ },
891
+ "peerDependencies": {
892
+ "react": "^19.2.5"
893
+ }
894
+ },
895
+ "node_modules/scheduler": {
896
+ "version": "0.27.0",
897
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
898
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
899
+ "license": "MIT"
900
+ },
901
+ "node_modules/semver": {
902
+ "version": "7.7.4",
903
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
904
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
905
+ "license": "ISC",
906
+ "optional": true,
907
+ "bin": {
908
+ "semver": "bin/semver.js"
909
+ },
910
+ "engines": {
911
+ "node": ">=10"
912
+ }
913
+ },
914
+ "node_modules/sharp": {
915
+ "version": "0.34.5",
916
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
917
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
918
+ "hasInstallScript": true,
919
+ "license": "Apache-2.0",
920
+ "optional": true,
921
+ "dependencies": {
922
+ "@img/colour": "^1.0.0",
923
+ "detect-libc": "^2.1.2",
924
+ "semver": "^7.7.3"
925
+ },
926
+ "engines": {
927
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
928
+ },
929
+ "funding": {
930
+ "url": "https://opencollective.com/libvips"
931
+ },
932
+ "optionalDependencies": {
933
+ "@img/sharp-darwin-arm64": "0.34.5",
934
+ "@img/sharp-darwin-x64": "0.34.5",
935
+ "@img/sharp-libvips-darwin-arm64": "1.2.4",
936
+ "@img/sharp-libvips-darwin-x64": "1.2.4",
937
+ "@img/sharp-libvips-linux-arm": "1.2.4",
938
+ "@img/sharp-libvips-linux-arm64": "1.2.4",
939
+ "@img/sharp-libvips-linux-ppc64": "1.2.4",
940
+ "@img/sharp-libvips-linux-riscv64": "1.2.4",
941
+ "@img/sharp-libvips-linux-s390x": "1.2.4",
942
+ "@img/sharp-libvips-linux-x64": "1.2.4",
943
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
944
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
945
+ "@img/sharp-linux-arm": "0.34.5",
946
+ "@img/sharp-linux-arm64": "0.34.5",
947
+ "@img/sharp-linux-ppc64": "0.34.5",
948
+ "@img/sharp-linux-riscv64": "0.34.5",
949
+ "@img/sharp-linux-s390x": "0.34.5",
950
+ "@img/sharp-linux-x64": "0.34.5",
951
+ "@img/sharp-linuxmusl-arm64": "0.34.5",
952
+ "@img/sharp-linuxmusl-x64": "0.34.5",
953
+ "@img/sharp-wasm32": "0.34.5",
954
+ "@img/sharp-win32-arm64": "0.34.5",
955
+ "@img/sharp-win32-ia32": "0.34.5",
956
+ "@img/sharp-win32-x64": "0.34.5"
957
+ }
958
+ },
959
+ "node_modules/source-map-js": {
960
+ "version": "1.2.1",
961
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
962
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
963
+ "license": "BSD-3-Clause",
964
+ "engines": {
965
+ "node": ">=0.10.0"
966
+ }
967
+ },
968
+ "node_modules/styled-jsx": {
969
+ "version": "5.1.6",
970
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
971
+ "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
972
+ "license": "MIT",
973
+ "dependencies": {
974
+ "client-only": "0.0.1"
975
+ },
976
+ "engines": {
977
+ "node": ">= 12.0.0"
978
+ },
979
+ "peerDependencies": {
980
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
981
+ },
982
+ "peerDependenciesMeta": {
983
+ "@babel/core": {
984
+ "optional": true
985
+ },
986
+ "babel-plugin-macros": {
987
+ "optional": true
988
+ }
989
+ }
990
+ },
991
+ "node_modules/tslib": {
992
+ "version": "2.8.1",
993
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
994
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
995
+ "license": "0BSD"
996
+ },
997
+ "node_modules/typescript": {
998
+ "version": "5.9.3",
999
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
1000
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
1001
+ "dev": true,
1002
+ "license": "Apache-2.0",
1003
+ "bin": {
1004
+ "tsc": "bin/tsc",
1005
+ "tsserver": "bin/tsserver"
1006
+ },
1007
+ "engines": {
1008
+ "node": ">=14.17"
1009
+ }
1010
+ },
1011
+ "node_modules/undici-types": {
1012
+ "version": "7.16.0",
1013
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
1014
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
1015
+ "dev": true,
1016
+ "license": "MIT"
1017
+ }
1018
+ }
1019
+ }
ui/package.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "sentinel-ui",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start"
9
+ },
10
+ "dependencies": {
11
+ "framer-motion": "^12.23.24",
12
+ "lucide-react": "^0.539.0",
13
+ "next": "^15.3.3",
14
+ "react": "^19.1.1",
15
+ "react-dom": "^19.1.1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^24.3.0",
19
+ "@types/react": "^19.1.11",
20
+ "@types/react-dom": "^19.1.7",
21
+ "typescript": "^5.9.2"
22
+ }
23
+ }
ui/tsconfig.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": false,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }]
17
+ },
18
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
19
+ "exclude": ["node_modules"]
20
+ }