@backbay/glia-three 0.2.0-alpha.2

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 (172) hide show
  1. package/package.json +43 -0
  2. package/src/environment/AuroraLayer/AuroraLayer.stories.tsx +43 -0
  3. package/src/environment/AuroraLayer/AuroraLayer.tsx +200 -0
  4. package/src/environment/AuroraLayer/index.ts +2 -0
  5. package/src/environment/AuroraLayer/types.ts +30 -0
  6. package/src/environment/EnvironmentLayer/EnvironmentLayer.stories.tsx +262 -0
  7. package/src/environment/EnvironmentLayer/EnvironmentLayer.tsx +105 -0
  8. package/src/environment/EnvironmentLayer/index.ts +4 -0
  9. package/src/environment/EnvironmentLayer/presets.ts +128 -0
  10. package/src/environment/FogLayer/FogLayer.stories.tsx +83 -0
  11. package/src/environment/FogLayer/FogLayer.tsx +113 -0
  12. package/src/environment/FogLayer/index.ts +2 -0
  13. package/src/environment/FogLayer/types.ts +45 -0
  14. package/src/environment/VolumetricLight/VolumetricLight.stories.tsx +55 -0
  15. package/src/environment/VolumetricLight/VolumetricLight.tsx +188 -0
  16. package/src/environment/VolumetricLight/index.ts +2 -0
  17. package/src/environment/VolumetricLight/types.ts +33 -0
  18. package/src/environment/WeatherLayer/WeatherLayer.stories.tsx +348 -0
  19. package/src/environment/WeatherLayer/WeatherLayer.tsx +266 -0
  20. package/src/environment/WeatherLayer/cinematicCanvas.tsx +809 -0
  21. package/src/environment/WeatherLayer/colors.ts +27 -0
  22. package/src/environment/WeatherLayer/index.ts +4 -0
  23. package/src/environment/WeatherLayer/leafPresets.ts +12 -0
  24. package/src/environment/WeatherLayer/particles.ts +227 -0
  25. package/src/environment/WeatherLayer/types.ts +140 -0
  26. package/src/environment/index.ts +17 -0
  27. package/src/environment/shared/index.ts +2 -0
  28. package/src/environment/shared/noise.ts +10 -0
  29. package/src/environment/shared/performance.ts +33 -0
  30. package/src/environment/shared/types.ts +26 -0
  31. package/src/index.ts +2 -0
  32. package/src/lib/utils.ts +6 -0
  33. package/src/lib/vision-types.ts +84 -0
  34. package/src/three/AgentConsole/AgentConsole.stories.tsx +397 -0
  35. package/src/three/AgentConsole/AgentConsole.tsx +195 -0
  36. package/src/three/AgentConsole/ConsoleChat.tsx +237 -0
  37. package/src/three/AgentConsole/FocusConstellation.tsx +297 -0
  38. package/src/three/AgentConsole/GlyphAvatar.stories.tsx +110 -0
  39. package/src/three/AgentConsole/GlyphAvatar.tsx +117 -0
  40. package/src/three/AgentConsole/QuickActions.tsx +111 -0
  41. package/src/three/AgentConsole/index.ts +41 -0
  42. package/src/three/AgentConsole/types.ts +241 -0
  43. package/src/three/AmbientField/AmbientField.stories.tsx +290 -0
  44. package/src/three/AmbientField/AmbientField.tsx +307 -0
  45. package/src/three/AmbientField/BackbayFieldBus.ts +326 -0
  46. package/src/three/AmbientField/FieldProvider.tsx +83 -0
  47. package/src/three/AmbientField/index.ts +37 -0
  48. package/src/three/AmbientField/shaders/constellation.ts +384 -0
  49. package/src/three/AmbientField/types.ts +174 -0
  50. package/src/three/AttackGraph/AttackGraph.stories.tsx +144 -0
  51. package/src/three/AttackGraph/AttackGraph.tsx +325 -0
  52. package/src/three/AttackGraph/index.ts +19 -0
  53. package/src/three/AttackGraph/types.ts +97 -0
  54. package/src/three/AuditTrail/AuditTrail.stories.tsx +567 -0
  55. package/src/three/AuditTrail/AuditTrail.tsx +644 -0
  56. package/src/three/AuditTrail/index.ts +33 -0
  57. package/src/three/AuditTrail/types.ts +192 -0
  58. package/src/three/CrystallineOrganism/Breadcrumb.tsx +61 -0
  59. package/src/three/CrystallineOrganism/CrystallineOrganism.stories.tsx +509 -0
  60. package/src/three/CrystallineOrganism/CrystallineOrganism.tsx +273 -0
  61. package/src/three/CrystallineOrganism/LatticeEdge.tsx +69 -0
  62. package/src/three/CrystallineOrganism/OrganismLattice.tsx +159 -0
  63. package/src/three/CrystallineOrganism/OrganismParticles.tsx +148 -0
  64. package/src/three/CrystallineOrganism/OrganismShell.tsx +277 -0
  65. package/src/three/CrystallineOrganism/constants.ts +161 -0
  66. package/src/three/CrystallineOrganism/index.ts +17 -0
  67. package/src/three/CrystallineOrganism/layouts/hexGrid.ts +85 -0
  68. package/src/three/CrystallineOrganism/layouts/index.ts +1 -0
  69. package/src/three/CrystallineOrganism/types.ts +167 -0
  70. package/src/three/CrystallineOrganism/useOrganismEmotion.ts +84 -0
  71. package/src/three/FirewallBarrier/FirewallBarrier.stories.tsx +167 -0
  72. package/src/three/FirewallBarrier/FirewallBarrier.tsx +259 -0
  73. package/src/three/FirewallBarrier/index.ts +14 -0
  74. package/src/three/FirewallBarrier/types.ts +52 -0
  75. package/src/three/Glyph/GlyphObject.stories.tsx +577 -0
  76. package/src/three/Glyph/GlyphObject.tsx +422 -0
  77. package/src/three/Glyph/index.ts +10 -0
  78. package/src/three/Glyph/types.ts +36 -0
  79. package/src/three/Glyph/useGlyphController.ts +231 -0
  80. package/src/three/Glyph/useGlyphEmotion.ts +70 -0
  81. package/src/three/Graph3D/Graph3D.stories.tsx +269 -0
  82. package/src/three/Graph3D/Graph3D.tsx +248 -0
  83. package/src/three/Graph3D/GraphEdge.tsx +79 -0
  84. package/src/three/Graph3D/GraphNode.tsx +239 -0
  85. package/src/three/Graph3D/types.ts +66 -0
  86. package/src/three/Graph3D/utils.ts +204 -0
  87. package/src/three/IntelFeed/IntelFeed.stories.tsx +168 -0
  88. package/src/three/IntelFeed/IntelFeed.tsx +284 -0
  89. package/src/three/IntelFeed/index.ts +14 -0
  90. package/src/three/IntelFeed/types.ts +56 -0
  91. package/src/three/MetricsGalaxy/MetricsGalaxy.tsx +484 -0
  92. package/src/three/MetricsGalaxy/index.ts +6 -0
  93. package/src/three/MetricsGalaxy/types.ts +26 -0
  94. package/src/three/NetworkTopology/NetworkTopology.stories.tsx +184 -0
  95. package/src/three/NetworkTopology/NetworkTopology.tsx +421 -0
  96. package/src/three/NetworkTopology/index.ts +34 -0
  97. package/src/three/NetworkTopology/types.ts +128 -0
  98. package/src/three/ParticleField/ParticleField.stories.tsx +162 -0
  99. package/src/three/ParticleField/ParticleField.tsx +81 -0
  100. package/src/three/ParticleField/index.ts +1 -0
  101. package/src/three/PermissionMatrix/PermissionMatrix.stories.tsx +475 -0
  102. package/src/three/PermissionMatrix/PermissionMatrix.tsx +380 -0
  103. package/src/three/PermissionMatrix/index.ts +15 -0
  104. package/src/three/PermissionMatrix/types.ts +54 -0
  105. package/src/three/QuantumField/ConstellationField.tsx +238 -0
  106. package/src/three/QuantumField/FieldBus.ts +349 -0
  107. package/src/three/QuantumField/FieldLayer.tsx +430 -0
  108. package/src/three/QuantumField/FieldProvider.tsx +460 -0
  109. package/src/three/QuantumField/PcbField.tsx +406 -0
  110. package/src/three/QuantumField/QuantumField.stories.tsx +1155 -0
  111. package/src/three/QuantumField/TrailRTT.ts +212 -0
  112. package/src/three/QuantumField/WaterField.tsx +226 -0
  113. package/src/three/QuantumField/WaterSimRTT.ts +283 -0
  114. package/src/three/QuantumField/domMapping.ts +185 -0
  115. package/src/three/QuantumField/index.ts +110 -0
  116. package/src/three/QuantumField/styles/index.ts +9 -0
  117. package/src/three/QuantumField/styles/styleA.ts +526 -0
  118. package/src/three/QuantumField/styles/styleB.ts +1210 -0
  119. package/src/three/QuantumField/styles/styleC.ts +266 -0
  120. package/src/three/QuantumField/themes.ts +211 -0
  121. package/src/three/QuantumField/types.ts +380 -0
  122. package/src/three/SOCCommandCenter/SOCCommandCenter.stories.tsx +591 -0
  123. package/src/three/SOCCommandCenter/SOCCommandCenter.tsx +248 -0
  124. package/src/three/SOCCommandCenter/index.ts +26 -0
  125. package/src/three/SOCCommandCenter/types.ts +201 -0
  126. package/src/three/SecurityDashboard/SecurityDashboard.stories.tsx +508 -0
  127. package/src/three/SecurityDashboard/SecurityDashboard.tsx +507 -0
  128. package/src/three/SecurityDashboard/index.ts +37 -0
  129. package/src/three/SecurityDashboard/types.ts +143 -0
  130. package/src/three/SecurityShield/SecurityShield.stories.tsx +257 -0
  131. package/src/three/SecurityShield/SecurityShield.tsx +502 -0
  132. package/src/three/SecurityShield/index.ts +25 -0
  133. package/src/three/SecurityShield/types.ts +64 -0
  134. package/src/three/Sentinel/AvatarMode.tsx +578 -0
  135. package/src/three/Sentinel/AvatarRenderer.tsx +199 -0
  136. package/src/three/Sentinel/CameraPip.tsx +127 -0
  137. package/src/three/Sentinel/CardinalItem.tsx +83 -0
  138. package/src/three/Sentinel/CardinalMenu.tsx +370 -0
  139. package/src/three/Sentinel/DockedMiniOrb.tsx +146 -0
  140. package/src/three/Sentinel/RadialSubmenu.tsx +273 -0
  141. package/src/three/Sentinel/SentinelConversation.tsx +802 -0
  142. package/src/three/Sentinel/SentinelOrb.tsx +316 -0
  143. package/src/three/Sentinel/SentinelOverlay.tsx +146 -0
  144. package/src/three/Sentinel/SentinelProvider.tsx +145 -0
  145. package/src/three/Sentinel/SentinelTether.tsx +182 -0
  146. package/src/three/Sentinel/SigilPlaceholder.tsx +176 -0
  147. package/src/three/Sentinel/VerticalSubmenu.tsx +150 -0
  148. package/src/three/Sentinel/index.ts +145 -0
  149. package/src/three/Sentinel/sentinelStore.ts +196 -0
  150. package/src/three/Sentinel/types.ts +403 -0
  151. package/src/three/Sentinel/useCameraPermission.ts +153 -0
  152. package/src/three/Sentinel/useThrowPhysics.ts +220 -0
  153. package/src/three/SpatialWorkspace/CyntraWorkspace.tsx +84 -0
  154. package/src/three/SpatialWorkspace/JobCluster.tsx +281 -0
  155. package/src/three/SpatialWorkspace/NodeGraph.tsx +236 -0
  156. package/src/three/SpatialWorkspace/ReceiptOrbit.tsx +368 -0
  157. package/src/three/SpatialWorkspace/SpatialWorkspace.stories.tsx +547 -0
  158. package/src/three/SpatialWorkspace/SpatialWorkspace.tsx +428 -0
  159. package/src/three/SpatialWorkspace/TrustRings.tsx +228 -0
  160. package/src/three/SpatialWorkspace/adapters.ts +353 -0
  161. package/src/three/SpatialWorkspace/index.ts +85 -0
  162. package/src/three/SpatialWorkspace/nexusAdapter.ts +182 -0
  163. package/src/three/SpatialWorkspace/types.ts +389 -0
  164. package/src/three/ThreatRadar/ThreatRadar.stories.tsx +451 -0
  165. package/src/three/ThreatRadar/ThreatRadar.tsx +542 -0
  166. package/src/three/ThreatRadar/index.ts +8 -0
  167. package/src/three/ThreatRadar/types.ts +90 -0
  168. package/src/three/ThreeErrorBoundary/ThreeErrorBoundary.tsx +235 -0
  169. package/src/three/ThreeErrorBoundary/index.ts +5 -0
  170. package/src/three/index.ts +56 -0
  171. package/tsconfig.json +20 -0
  172. package/tsup.config.ts +21 -0
@@ -0,0 +1,577 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Canvas } from "@react-three/fiber";
3
+ import { OrbitControls } from "@react-three/drei";
4
+ import { useEffect, useMemo, useState } from "react";
5
+ import type { AnchorState, AVO } from "@backbay/glia-agent/emotion";
6
+ import { ANCHOR_STATES, computeVisualState, getAnimationWeights, useEmotion } from "@backbay/glia-agent/emotion";
7
+ import { GlyphObject } from "./GlyphObject";
8
+ import type { GlyphOneShot, GlyphState, GlyphVariant } from "./types";
9
+
10
+ const GLYPH_STATES: GlyphState[] = [
11
+ "idle",
12
+ "listening",
13
+ "thinking",
14
+ "responding",
15
+ "success",
16
+ "error",
17
+ "sleep",
18
+ ];
19
+
20
+ const ONE_SHOTS: GlyphOneShot[] = ["responding", "success", "error"];
21
+
22
+ function GlyphViewport({
23
+ children,
24
+ height = 520,
25
+ }: {
26
+ children: React.ReactNode;
27
+ height?: number;
28
+ }) {
29
+ return (
30
+ <div
31
+ style={{
32
+ width: "100%",
33
+ height,
34
+ background: "linear-gradient(to bottom, #050812, #0a0a0f)",
35
+ border: "1px solid #1b2233",
36
+ borderRadius: 16,
37
+ overflow: "hidden",
38
+ }}
39
+ >
40
+ <Canvas camera={{ position: [0, 0, 4], fov: 50 }} dpr={[1, 2]} gl={{ antialias: true, alpha: true }}>
41
+ <ambientLight intensity={0.25} />
42
+ <pointLight position={[5, 5, 5]} intensity={0.55} color="#ffd700" />
43
+ <pointLight position={[-5, -5, -5]} intensity={0.45} color="#00f0ff" />
44
+ {children}
45
+ <OrbitControls enableDamping dampingFactor={0.05} minDistance={2} maxDistance={10} />
46
+ </Canvas>
47
+ </div>
48
+ );
49
+ }
50
+
51
+ function Pill({
52
+ active,
53
+ label,
54
+ onClick,
55
+ tone = "violet",
56
+ }: {
57
+ active: boolean;
58
+ label: string;
59
+ onClick: () => void;
60
+ tone?: "violet" | "emerald" | "amber" | "red";
61
+ }) {
62
+ const colors = {
63
+ violet: { fg: "#a78bfa", border: "#a78bfa" },
64
+ emerald: { fg: "#34d399", border: "#34d399" },
65
+ amber: { fg: "#fbbf24", border: "#fbbf24" },
66
+ red: { fg: "#fb7185", border: "#fb7185" },
67
+ } as const;
68
+
69
+ const c = colors[tone];
70
+
71
+ return (
72
+ <button
73
+ onClick={onClick}
74
+ style={{
75
+ padding: "6px 10px",
76
+ borderRadius: 999,
77
+ border: `1px solid ${active ? c.border : "#2a3347"}`,
78
+ background: active ? "#141a2a" : "#0e1220",
79
+ color: active ? c.fg : "#94a3b8",
80
+ fontSize: 11,
81
+ cursor: "pointer",
82
+ fontFamily: "JetBrains Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
83
+ }}
84
+ >
85
+ {label}
86
+ </button>
87
+ );
88
+ }
89
+
90
+ function Panel({
91
+ title,
92
+ children,
93
+ }: {
94
+ title: string;
95
+ children: React.ReactNode;
96
+ }) {
97
+ return (
98
+ <div
99
+ style={{
100
+ background: "#0b1020",
101
+ border: "1px solid #1b2233",
102
+ borderRadius: 16,
103
+ padding: 16,
104
+ }}
105
+ >
106
+ <div
107
+ style={{
108
+ fontFamily: "JetBrains Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
109
+ fontSize: 12,
110
+ color: "#e2e8f0",
111
+ marginBottom: 12,
112
+ }}
113
+ >
114
+ {title}
115
+ </div>
116
+ {children}
117
+ </div>
118
+ );
119
+ }
120
+
121
+ function LegacyClipReview() {
122
+ const [state, setState] = useState<GlyphState>("idle");
123
+ const [variant, setVariant] = useState<GlyphVariant>("sentinel");
124
+ const [particles, setParticles] = useState(false);
125
+
126
+ const playState = (next: GlyphState) => {
127
+ if (!ONE_SHOTS.includes(next as GlyphOneShot)) {
128
+ setState(next);
129
+ return;
130
+ }
131
+
132
+ setState((prev) => {
133
+ if (prev !== next) return next;
134
+ // Force a replay by bouncing through an idle frame.
135
+ setTimeout(() => setState(next), 0);
136
+ return "idle";
137
+ });
138
+ };
139
+
140
+ return (
141
+ <div
142
+ style={{
143
+ display: "grid",
144
+ gridTemplateColumns: "1fr 360px",
145
+ gap: 20,
146
+ padding: 24,
147
+ height: "100vh",
148
+ background: "#050812",
149
+ color: "#e2e8f0",
150
+ }}
151
+ >
152
+ <GlyphViewport>
153
+ <GlyphObject
154
+ state={state}
155
+ enableBlending={false}
156
+ variant={variant}
157
+ enableParticles={particles}
158
+ />
159
+ </GlyphViewport>
160
+
161
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
162
+ <Panel title="Baked Clips (Legacy Mode)">
163
+ <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
164
+ {GLYPH_STATES.map((s) => (
165
+ <Pill
166
+ key={s}
167
+ active={s === state}
168
+ label={s}
169
+ onClick={() => playState(s)}
170
+ tone={s === "error" ? "red" : ONE_SHOTS.includes(s as GlyphOneShot) ? "emerald" : "violet"}
171
+ />
172
+ ))}
173
+ </div>
174
+
175
+ <div style={{ marginTop: 12, fontSize: 11, color: "#94a3b8", lineHeight: 1.4 }}>
176
+ One-shots (responding/success/error) play once. Click the active pill again to replay.
177
+ </div>
178
+ </Panel>
179
+
180
+ <Panel title="View">
181
+ <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
182
+ <label style={{ fontSize: 11, color: "#94a3b8" }}>
183
+ Variant
184
+ <select
185
+ value={variant}
186
+ onChange={(e) => setVariant(e.target.value as GlyphVariant)}
187
+ style={{
188
+ display: "block",
189
+ width: "100%",
190
+ marginTop: 6,
191
+ padding: "8px 10px",
192
+ background: "#0e1220",
193
+ border: "1px solid #2a3347",
194
+ borderRadius: 10,
195
+ color: "#e2e8f0",
196
+ }}
197
+ >
198
+ <option value="sentinel">sentinel</option>
199
+ <option value="console">console</option>
200
+ <option value="graph">graph</option>
201
+ <option value="minimal">minimal</option>
202
+ </select>
203
+ </label>
204
+
205
+ <label style={{ fontSize: 11, color: "#94a3b8" }}>
206
+ Particles
207
+ <button
208
+ onClick={() => setParticles((v) => !v)}
209
+ style={{
210
+ display: "block",
211
+ width: "100%",
212
+ marginTop: 6,
213
+ padding: "8px 10px",
214
+ background: particles ? "#0f2a20" : "#0e1220",
215
+ border: `1px solid ${particles ? "#34d399" : "#2a3347"}`,
216
+ borderRadius: 10,
217
+ color: particles ? "#34d399" : "#94a3b8",
218
+ cursor: "pointer",
219
+ }}
220
+ >
221
+ {particles ? "enabled" : "disabled"}
222
+ </button>
223
+ </label>
224
+ </div>
225
+ </Panel>
226
+
227
+ <Panel title="Motion Cues">
228
+ <div style={{ fontSize: 11, color: "#94a3b8", lineHeight: 1.5 }}>
229
+ <div>
230
+ <span style={{ color: "#a78bfa" }}>Listening</span>: calmer ring orbit, subtle inhale, slight lean.
231
+ </div>
232
+ <div>
233
+ <span style={{ color: "#a78bfa" }}>Thinking</span>: stronger core pulses, ring wobble, tighter intent.
234
+ </div>
235
+ <div>
236
+ <span style={{ color: "#34d399" }}>Responding / Success</span>: outward projection (rings expand + core push).
237
+ </div>
238
+ <div>
239
+ <span style={{ color: "#fb7185" }}>Error</span>: sharper alarm (root shake + brow snap).
240
+ </div>
241
+ <div>
242
+ <span style={{ color: "#a78bfa" }}>Sleep</span>: minimal motion, contracted rings, eyes closed.
243
+ </div>
244
+ </div>
245
+ </Panel>
246
+ </div>
247
+ </div>
248
+ );
249
+ }
250
+
251
+ function OneShotRepeater({
252
+ clip,
253
+ base,
254
+ intervalMs = 2400,
255
+ }: {
256
+ clip: GlyphOneShot;
257
+ base: AVO;
258
+ intervalMs?: number;
259
+ }) {
260
+ const [nonce, setNonce] = useState(0);
261
+ const visual = useMemo(() => computeVisualState(base), [base]);
262
+
263
+ useEffect(() => {
264
+ const id = window.setInterval(() => setNonce((n) => n + 1), intervalMs);
265
+ return () => window.clearInterval(id);
266
+ }, [intervalMs]);
267
+
268
+ return (
269
+ <GlyphObject
270
+ state="idle"
271
+ enableBlending={true}
272
+ dimensions={base}
273
+ visualState={visual}
274
+ oneShot={clip}
275
+ oneShotNonce={nonce}
276
+ variant="sentinel"
277
+ enableParticles={false}
278
+ />
279
+ );
280
+ }
281
+
282
+ function EmotionBlendStudio() {
283
+ const [variant, setVariant] = useState<GlyphVariant>("sentinel");
284
+ const [particles, setParticles] = useState(true);
285
+ const [oneShot, setOneShot] = useState<GlyphOneShot | undefined>(undefined);
286
+ const [oneShotNonce, setOneShotNonce] = useState(0);
287
+ const [selectedAnchor, setSelectedAnchor] = useState<AnchorState>("idle");
288
+ const [mode, setMode] = useState<"anchors" | "manual">("anchors");
289
+
290
+ const { dimensions, baseDimensions, visualState, goTo, set, isTransitioning } = useEmotion({
291
+ initialAnchor: "idle",
292
+ microExpressions: true,
293
+ });
294
+
295
+ const trigger = (clip: GlyphOneShot) => {
296
+ setOneShot(clip);
297
+ setOneShotNonce((n) => n + 1);
298
+ };
299
+
300
+ const setManual = (key: keyof AVO, value: number) => {
301
+ set({ [key]: value });
302
+ };
303
+
304
+ const weights = useMemo(() => getAnimationWeights(baseDimensions), [baseDimensions]);
305
+
306
+ return (
307
+ <div
308
+ style={{
309
+ display: "grid",
310
+ gridTemplateColumns: "1fr 420px",
311
+ gap: 20,
312
+ padding: 24,
313
+ height: "100vh",
314
+ background: "#050812",
315
+ color: "#e2e8f0",
316
+ }}
317
+ >
318
+ <GlyphViewport>
319
+ <GlyphObject
320
+ state="idle"
321
+ enableBlending={true}
322
+ variant={variant}
323
+ enableParticles={particles}
324
+ dimensions={dimensions}
325
+ visualState={visualState}
326
+ oneShot={oneShot}
327
+ oneShotNonce={oneShotNonce}
328
+ />
329
+ </GlyphViewport>
330
+
331
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
332
+ <Panel title="Emotion + Blending">
333
+ <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
334
+ <Pill
335
+ active={mode === "anchors"}
336
+ label="anchors"
337
+ onClick={() => {
338
+ setMode("anchors");
339
+ goTo(selectedAnchor);
340
+ }}
341
+ tone="violet"
342
+ />
343
+ <Pill active={mode === "manual"} label="manual AVO" onClick={() => setMode("manual")} tone="amber" />
344
+ </div>
345
+
346
+ <div style={{ marginTop: 12, display: "flex", flexWrap: "wrap", gap: 8 }}>
347
+ {(["idle", "listening", "thinking", "dormant"] as const).map((a) => (
348
+ <Pill
349
+ key={a}
350
+ active={selectedAnchor === a}
351
+ label={a}
352
+ onClick={() => {
353
+ setSelectedAnchor(a);
354
+ setMode("anchors");
355
+ goTo(a);
356
+ }}
357
+ tone="emerald"
358
+ />
359
+ ))}
360
+ {(["satisfied", "responding", "error"] as const).map((a) => (
361
+ <Pill
362
+ key={a}
363
+ active={selectedAnchor === a}
364
+ label={a}
365
+ onClick={() => {
366
+ setSelectedAnchor(a);
367
+ setMode("anchors");
368
+ goTo(a);
369
+ }}
370
+ tone={a === "error" ? "red" : "emerald"}
371
+ />
372
+ ))}
373
+ </div>
374
+
375
+ <div style={{ marginTop: 12, fontSize: 11, color: isTransitioning ? "#fbbf24" : "#94a3b8" }}>
376
+ {isTransitioning ? "● transitioning…" : "○ stable"}
377
+ </div>
378
+
379
+ {mode === "manual" && (
380
+ <div style={{ marginTop: 14, display: "grid", gap: 12 }}>
381
+ {(["arousal", "valence", "openness"] as const).map((k) => (
382
+ <label key={k} style={{ fontSize: 11, color: "#94a3b8" }}>
383
+ {k} <span style={{ color: "#e2e8f0" }}>{baseDimensions[k].toFixed(2)}</span>
384
+ <input
385
+ type="range"
386
+ min={0}
387
+ max={1}
388
+ step={0.01}
389
+ value={baseDimensions[k]}
390
+ onChange={(e) => setManual(k, Number.parseFloat(e.target.value))}
391
+ style={{ width: "100%" }}
392
+ />
393
+ </label>
394
+ ))}
395
+ </div>
396
+ )}
397
+ </Panel>
398
+
399
+ <Panel title="One‑Shots (Overlay)">
400
+ <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
401
+ <Pill active={false} label="responding" onClick={() => trigger("responding")} tone="emerald" />
402
+ <Pill active={false} label="success" onClick={() => trigger("success")} tone="emerald" />
403
+ <Pill active={false} label="error" onClick={() => trigger("error")} tone="red" />
404
+ </div>
405
+ <div style={{ marginTop: 10, fontSize: 11, color: "#94a3b8", lineHeight: 1.5 }}>
406
+ These are baked one-shots layered on top of blended loop clips, while the procedural AVO layer still updates materials + micro-expressions.
407
+ </div>
408
+ </Panel>
409
+
410
+ <Panel title="Loop Weights (Derived)">
411
+ <div
412
+ style={{
413
+ display: "grid",
414
+ gridTemplateColumns: "repeat(2, 1fr)",
415
+ gap: 10,
416
+ fontFamily: "JetBrains Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
417
+ fontSize: 11,
418
+ color: "#94a3b8",
419
+ }}
420
+ >
421
+ {(["idle", "listening", "thinking", "sleep"] as const).map((k) => (
422
+ <div key={k} style={{ padding: 10, background: "#0e1220", borderRadius: 10, border: "1px solid #2a3347" }}>
423
+ <div style={{ color: "#e2e8f0" }}>{k}</div>
424
+ <div style={{ marginTop: 6 }}>{(weights[k] * 100).toFixed(1)}%</div>
425
+ </div>
426
+ ))}
427
+ </div>
428
+ </Panel>
429
+
430
+ <Panel title="View">
431
+ <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
432
+ <label style={{ fontSize: 11, color: "#94a3b8" }}>
433
+ Variant
434
+ <select
435
+ value={variant}
436
+ onChange={(e) => setVariant(e.target.value as GlyphVariant)}
437
+ style={{
438
+ display: "block",
439
+ width: "100%",
440
+ marginTop: 6,
441
+ padding: "8px 10px",
442
+ background: "#0e1220",
443
+ border: "1px solid #2a3347",
444
+ borderRadius: 10,
445
+ color: "#e2e8f0",
446
+ }}
447
+ >
448
+ <option value="sentinel">sentinel</option>
449
+ <option value="console">console</option>
450
+ <option value="graph">graph</option>
451
+ <option value="minimal">minimal</option>
452
+ </select>
453
+ </label>
454
+
455
+ <label style={{ fontSize: 11, color: "#94a3b8" }}>
456
+ Particles
457
+ <button
458
+ onClick={() => setParticles((v) => !v)}
459
+ style={{
460
+ display: "block",
461
+ width: "100%",
462
+ marginTop: 6,
463
+ padding: "8px 10px",
464
+ background: particles ? "#0f2a20" : "#0e1220",
465
+ border: `1px solid ${particles ? "#34d399" : "#2a3347"}`,
466
+ borderRadius: 10,
467
+ color: particles ? "#34d399" : "#94a3b8",
468
+ cursor: "pointer",
469
+ }}
470
+ >
471
+ {particles ? "enabled" : "disabled"}
472
+ </button>
473
+ </label>
474
+ </div>
475
+ </Panel>
476
+ </div>
477
+ </div>
478
+ );
479
+ }
480
+
481
+ const meta: Meta = {
482
+ title: "Primitives/3D/Agent/Glyph",
483
+ parameters: {
484
+ layout: "fullscreen",
485
+ backgrounds: { default: "dark" },
486
+ },
487
+ };
488
+
489
+ export default meta;
490
+
491
+ type Story = StoryObj;
492
+
493
+ export const LegacyClips: Story = {
494
+ render: () => <LegacyClipReview />,
495
+ };
496
+
497
+ export const EmotionStudio: Story = {
498
+ render: () => <EmotionBlendStudio />,
499
+ };
500
+
501
+ export const IdleLoop: Story = {
502
+ name: "Clip / Idle (Loop)",
503
+ render: () => (
504
+ <div style={{ padding: 24, background: "#050812", minHeight: "100vh" }}>
505
+ <GlyphViewport>
506
+ <GlyphObject state="idle" enableBlending={false} variant="sentinel" enableParticles={false} />
507
+ </GlyphViewport>
508
+ </div>
509
+ ),
510
+ };
511
+
512
+ export const ListeningLoop: Story = {
513
+ name: "Clip / Listening (Loop)",
514
+ render: () => (
515
+ <div style={{ padding: 24, background: "#050812", minHeight: "100vh" }}>
516
+ <GlyphViewport>
517
+ <GlyphObject state="listening" enableBlending={false} variant="sentinel" enableParticles={false} />
518
+ </GlyphViewport>
519
+ </div>
520
+ ),
521
+ };
522
+
523
+ export const ThinkingLoop: Story = {
524
+ name: "Clip / Thinking (Loop)",
525
+ render: () => (
526
+ <div style={{ padding: 24, background: "#050812", minHeight: "100vh" }}>
527
+ <GlyphViewport>
528
+ <GlyphObject state="thinking" enableBlending={false} variant="sentinel" enableParticles={false} />
529
+ </GlyphViewport>
530
+ </div>
531
+ ),
532
+ };
533
+
534
+ export const SleepLoop: Story = {
535
+ name: "Clip / Sleep (Loop)",
536
+ render: () => (
537
+ <div style={{ padding: 24, background: "#050812", minHeight: "100vh" }}>
538
+ <GlyphViewport>
539
+ <GlyphObject state="sleep" enableBlending={false} variant="sentinel" enableParticles={false} />
540
+ </GlyphViewport>
541
+ </div>
542
+ ),
543
+ };
544
+
545
+ export const RespondingOneShot: Story = {
546
+ name: "Clip / Responding (One‑Shot)",
547
+ render: () => (
548
+ <div style={{ padding: 24, background: "#050812", minHeight: "100vh" }}>
549
+ <GlyphViewport>
550
+ <OneShotRepeater clip="responding" base={ANCHOR_STATES.responding} />
551
+ </GlyphViewport>
552
+ </div>
553
+ ),
554
+ };
555
+
556
+ export const SuccessOneShot: Story = {
557
+ name: "Clip / Success (One‑Shot)",
558
+ render: () => (
559
+ <div style={{ padding: 24, background: "#050812", minHeight: "100vh" }}>
560
+ <GlyphViewport>
561
+ <OneShotRepeater clip="success" base={ANCHOR_STATES.satisfied} />
562
+ </GlyphViewport>
563
+ </div>
564
+ ),
565
+ };
566
+
567
+ export const ErrorOneShot: Story = {
568
+ name: "Clip / Error (One‑Shot)",
569
+ render: () => (
570
+ <div style={{ padding: 24, background: "#050812", minHeight: "100vh" }}>
571
+ <GlyphViewport>
572
+ <OneShotRepeater clip="error" base={ANCHOR_STATES.error} intervalMs={2200} />
573
+ </GlyphViewport>
574
+ </div>
575
+ ),
576
+ };
577
+