@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,182 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * SentinelTether
5
+ *
6
+ * An SVG line connecting the Sentinel orb to its taskbar origin.
7
+ * Visible during summoning phase, fades out during open phase.
8
+ */
9
+
10
+ import { motion } from 'framer-motion';
11
+ import React from 'react';
12
+
13
+ import { useSentinelStore } from './sentinelStore';
14
+
15
+ export const SentinelTether: React.FC = () => {
16
+ const phase = useSentinelStore((s) => s.phase);
17
+ const orbPosition = useSentinelStore((s) => s.orbPosition);
18
+ const taskbarOrigin = useSentinelStore((s) => s.taskbarOrigin);
19
+
20
+ const start = taskbarOrigin;
21
+ const end = orbPosition;
22
+ const dx = end.x - start.x;
23
+ const dy = end.y - start.y;
24
+ const distance = Math.hypot(dx, dy);
25
+ const safeDistance = distance || 1;
26
+ const normal = { x: -dy / safeDistance, y: dx / safeDistance };
27
+ const steps = Math.max(6, Math.min(24, Math.round(safeDistance / 28)));
28
+ const baseAmp = Math.min(5.5, Math.max(1.5, safeDistance * 0.015));
29
+ const jitterSeed = Math.sin((start.x + start.y) * 0.015);
30
+ const freq = 3 + Math.floor(safeDistance / 160);
31
+ const points = Array.from({ length: steps + 1 }, (_, index) => {
32
+ const t = steps === 0 ? 0 : index / steps;
33
+ let offset = 0;
34
+ if (index > 0 && index < steps) {
35
+ const phaseA = t * Math.PI * (2 + freq) + jitterSeed * 2.7;
36
+ const phaseB = t * Math.PI * (3.5 + freq * 1.4) + jitterSeed * 4.1;
37
+ const wobble = Math.sin(phaseA);
38
+ const chisel = Math.sign(Math.sin(phaseB));
39
+ const amp = baseAmp * (0.45 + 0.55 * Math.sin(t * Math.PI * 2 + jitterSeed));
40
+ offset = (wobble * 0.6 + chisel * 0.4) * amp;
41
+ offset = Math.round(offset * 2) / 2;
42
+ }
43
+ return {
44
+ x: start.x + dx * t + normal.x * offset,
45
+ y: start.y + dy * t + normal.y * offset,
46
+ };
47
+ });
48
+ const pointsAttr = points.map((point) => `${point.x},${point.y}`).join(' ');
49
+ const dash = Math.max(10, Math.min(24, safeDistance * 0.115));
50
+ const gap = Math.max(16, Math.min(34, safeDistance * 0.19));
51
+ const dashArray = `${dash} ${gap}`;
52
+ const sparkDashArray = `${Math.max(2.5, dash * 0.35)} ${Math.max(10, gap * 1.4)}`;
53
+
54
+ // Only render during summoning or open phases
55
+ if (phase === 'docked' || phase === 'dismissing' || phase === 'throwing' || phase === 'avatar') {
56
+ return null;
57
+ }
58
+
59
+ // Determine opacity based on phase
60
+ const targetOpacity = phase === 'summoning' ? 1 : 0;
61
+ const gradientId = 'sentinel-tether-gradient';
62
+ const filterId = 'sentinel-tether-glow';
63
+
64
+ return (
65
+ <svg
66
+ style={{
67
+ position: 'fixed',
68
+ inset: 0,
69
+ width: '100%',
70
+ height: '100%',
71
+ pointerEvents: 'none',
72
+ zIndex: 9998,
73
+ }}
74
+ >
75
+ <defs>
76
+ {/* Linear gradient from gold core to warm flare */}
77
+ <linearGradient
78
+ id={gradientId}
79
+ x1={taskbarOrigin.x}
80
+ y1={taskbarOrigin.y}
81
+ x2={orbPosition.x}
82
+ y2={orbPosition.y}
83
+ gradientUnits="userSpaceOnUse"
84
+ >
85
+ <stop offset="0%" stopColor="#9f6420" stopOpacity={0.95} />
86
+ <stop offset="15%" stopColor="#e1a341" stopOpacity={0.98} />
87
+ <stop offset="35%" stopColor="#ffd48a" stopOpacity={0.95} />
88
+ <stop offset="55%" stopColor="#fff0c9" stopOpacity={0.9} />
89
+ <stop offset="75%" stopColor="#efb95f" stopOpacity={0.78} />
90
+ <stop offset="100%" stopColor="#b9792a" stopOpacity={0.5} />
91
+ </linearGradient>
92
+
93
+ {/* Glow filter */}
94
+ <filter id={filterId} x="-90%" y="-90%" width="280%" height="280%">
95
+ <feGaussianBlur in="SourceGraphic" stdDeviation="3.6" result="blur" />
96
+ <feColorMatrix
97
+ type="matrix"
98
+ values="
99
+ 1 0 0 0 0
100
+ 0 0.85 0 0 0
101
+ 0 0 0.45 0 0
102
+ 0 0 0 1 0"
103
+ result="glow"
104
+ />
105
+ <feMerge>
106
+ <feMergeNode in="glow" />
107
+ <feMergeNode in="SourceGraphic" />
108
+ </feMerge>
109
+ </filter>
110
+ </defs>
111
+
112
+ <motion.polyline
113
+ points={pointsAttr}
114
+ stroke="rgba(212, 168, 75, 0.32)"
115
+ strokeWidth={4.1}
116
+ strokeLinecap="round"
117
+ strokeLinejoin="round"
118
+ fill="none"
119
+ filter={`url(#${filterId})`}
120
+ initial={{ opacity: 0.8 }}
121
+ animate={{ opacity: targetOpacity }}
122
+ transition={{ duration: 0.75, ease: 'easeOut' }}
123
+ />
124
+ <motion.polyline
125
+ points={pointsAttr}
126
+ stroke={`url(#${gradientId})`}
127
+ strokeWidth={2.4}
128
+ strokeLinecap="round"
129
+ strokeLinejoin="round"
130
+ fill="none"
131
+ filter={`url(#${filterId})`}
132
+ initial={{ opacity: 1 }}
133
+ animate={{ opacity: targetOpacity }}
134
+ transition={{ duration: 0.75, ease: 'easeOut' }}
135
+ />
136
+ <motion.polyline
137
+ points={pointsAttr}
138
+ stroke="rgba(255, 234, 184, 0.9)"
139
+ strokeWidth={1.05}
140
+ strokeLinecap="round"
141
+ strokeLinejoin="round"
142
+ fill="none"
143
+ strokeDasharray={dashArray}
144
+ filter={`url(#${filterId})`}
145
+ initial={{ opacity: 0.55, strokeDashoffset: 0 }}
146
+ animate={{ opacity: targetOpacity, strokeDashoffset: -safeDistance }}
147
+ transition={{
148
+ opacity: { duration: 0.75, ease: 'easeOut' },
149
+ strokeDashoffset: { duration: 7.8, repeat: Infinity, ease: 'linear' },
150
+ }}
151
+ />
152
+ <motion.polyline
153
+ points={pointsAttr}
154
+ stroke="rgba(255, 241, 210, 0.9)"
155
+ strokeWidth={0.8}
156
+ strokeLinecap="round"
157
+ strokeLinejoin="round"
158
+ fill="none"
159
+ strokeDasharray={sparkDashArray}
160
+ filter={`url(#${filterId})`}
161
+ initial={{ opacity: 0.35, strokeDashoffset: 0 }}
162
+ animate={{ opacity: targetOpacity, strokeDashoffset: -safeDistance * 1.6 }}
163
+ transition={{
164
+ opacity: { duration: 0.75, ease: 'easeOut' },
165
+ strokeDashoffset: { duration: 10.6, repeat: Infinity, ease: 'linear' },
166
+ }}
167
+ />
168
+ <motion.circle
169
+ cx={orbPosition.x}
170
+ cy={orbPosition.y}
171
+ r={5}
172
+ fill="rgba(212, 168, 75, 0.9)"
173
+ filter={`url(#${filterId})`}
174
+ initial={{ opacity: 0.6, scale: 1 }}
175
+ animate={{ opacity: targetOpacity, scale: [1, 1.18, 1] }}
176
+ transition={{ duration: 3.4, repeat: Infinity, ease: 'easeInOut' }}
177
+ />
178
+ </svg>
179
+ );
180
+ };
181
+
182
+ export default SentinelTether;
@@ -0,0 +1,176 @@
1
+ 'use client';
2
+
3
+ import { useFrame } from '@react-three/fiber';
4
+ import React, { useMemo, useRef } from 'react';
5
+ import * as THREE from 'three';
6
+ import type { Group } from 'three';
7
+
8
+ const RING_SEGMENTS = 64;
9
+ const TICK_COUNT = 16;
10
+ const RUNE_COUNT = 28;
11
+
12
+ export const SigilPlaceholder: React.FC<{ scale?: number }> = ({ scale = 1 }) => {
13
+ const groupRef = useRef<Group>(null);
14
+ const runeRef = useRef<Group>(null);
15
+ const shimmerRef = useRef<THREE.MeshBasicMaterial | null>(null);
16
+ const ticks = useMemo(() => Array.from({ length: TICK_COUNT }, (_, i) => i), []);
17
+ const runes = useMemo(
18
+ () =>
19
+ Array.from({ length: RUNE_COUNT }, (_, i) => {
20
+ const angle = (i / RUNE_COUNT) * Math.PI * 2;
21
+ const width = 0.028 + 0.014 * Math.abs(Math.sin(i * 1.7));
22
+ const height = 0.007 + 0.01 * Math.abs(Math.cos(i * 1.3));
23
+ const opacity = 0.18 + 0.12 * Math.abs(Math.sin(i * 0.9));
24
+ return { i, angle, width, height, opacity };
25
+ }),
26
+ []
27
+ );
28
+ const noiseTexture = useMemo(() => {
29
+ const size = 64;
30
+ const data = new Uint8Array(size * size * 4);
31
+ for (let i = 0; i < size * size; i += 1) {
32
+ const intensity = 130 + Math.floor(Math.random() * 125);
33
+ data[i * 4] = intensity;
34
+ data[i * 4 + 1] = Math.min(255, intensity + 40);
35
+ data[i * 4 + 2] = 30;
36
+ data[i * 4 + 3] = 120 + Math.floor(Math.random() * 120);
37
+ }
38
+ const tex = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
39
+ tex.wrapS = THREE.RepeatWrapping;
40
+ tex.wrapT = THREE.RepeatWrapping;
41
+ tex.repeat.set(4, 4);
42
+ tex.needsUpdate = true;
43
+ return tex;
44
+ }, []);
45
+
46
+ useFrame((_, delta) => {
47
+ if (!groupRef.current) return;
48
+ groupRef.current.rotation.z += delta * 0.25;
49
+ groupRef.current.rotation.x = 0.12;
50
+ if (runeRef.current) {
51
+ runeRef.current.rotation.z -= delta * 0.08;
52
+ }
53
+ if (shimmerRef.current?.map) {
54
+ shimmerRef.current.map.offset.x =
55
+ (shimmerRef.current.map.offset.x + delta * 0.08) % 1;
56
+ shimmerRef.current.map.offset.y =
57
+ (shimmerRef.current.map.offset.y + delta * 0.05) % 1;
58
+ shimmerRef.current.opacity = 0.12 + Math.sin(Date.now() * 0.002) * 0.03;
59
+ }
60
+ });
61
+
62
+ return (
63
+ <group ref={groupRef} scale={scale}>
64
+ <mesh rotation={[Math.PI / 2, 0, 0]}>
65
+ <ringGeometry args={[0.62, 0.66, RING_SEGMENTS]} />
66
+ <meshStandardMaterial
67
+ color="#d4a84b"
68
+ emissive="#d4a84b"
69
+ emissiveIntensity={0.35}
70
+ transparent
71
+ opacity={0.6}
72
+ metalness={0.4}
73
+ roughness={0.3}
74
+ toneMapped={false}
75
+ />
76
+ </mesh>
77
+ <mesh rotation={[Math.PI / 2, 0, 0]}>
78
+ <ringGeometry args={[0.44, 0.455, RING_SEGMENTS]} />
79
+ <meshStandardMaterial
80
+ color="#f2c96b"
81
+ emissive="#f2c96b"
82
+ emissiveIntensity={0.4}
83
+ transparent
84
+ opacity={0.7}
85
+ metalness={0.35}
86
+ roughness={0.35}
87
+ toneMapped={false}
88
+ />
89
+ </mesh>
90
+ <mesh rotation={[Math.PI / 2, 0, 0]}>
91
+ <ringGeometry args={[0.32, 0.7, RING_SEGMENTS]} />
92
+ <meshBasicMaterial
93
+ ref={shimmerRef}
94
+ map={noiseTexture}
95
+ color="#f2c96b"
96
+ transparent
97
+ opacity={0.14}
98
+ blending={THREE.AdditiveBlending}
99
+ depthWrite={false}
100
+ toneMapped={false}
101
+ />
102
+ </mesh>
103
+ <mesh rotation={[0, 0, 0]}>
104
+ <torusGeometry args={[0.22, 0.01, 12, 96]} />
105
+ <meshStandardMaterial
106
+ color="#6b8cbe"
107
+ emissive="#9bb4e3"
108
+ emissiveIntensity={0.35}
109
+ transparent
110
+ opacity={0.6}
111
+ metalness={0.2}
112
+ roughness={0.2}
113
+ toneMapped={false}
114
+ />
115
+ </mesh>
116
+ <mesh rotation={[Math.PI / 2, 0, 0]}>
117
+ <ringGeometry args={[0.12, 0.125, 32]} />
118
+ <meshStandardMaterial
119
+ color="#e6d3a3"
120
+ emissive="#f2c96b"
121
+ emissiveIntensity={0.4}
122
+ transparent
123
+ opacity={0.7}
124
+ metalness={0.4}
125
+ roughness={0.3}
126
+ toneMapped={false}
127
+ />
128
+ </mesh>
129
+ <group ref={runeRef}>
130
+ {runes.map((rune) => {
131
+ const x = Math.cos(rune.angle) * 0.52;
132
+ const y = Math.sin(rune.angle) * 0.52;
133
+ return (
134
+ <mesh
135
+ key={`rune-${rune.i}`}
136
+ position={[x, y, 0]}
137
+ rotation={[0, 0, rune.angle]}
138
+ >
139
+ <boxGeometry args={[rune.width, rune.height, 0.01]} />
140
+ <meshBasicMaterial
141
+ color="#d4a84b"
142
+ transparent
143
+ opacity={rune.opacity}
144
+ blending={THREE.AdditiveBlending}
145
+ depthWrite={false}
146
+ toneMapped={false}
147
+ />
148
+ </mesh>
149
+ );
150
+ })}
151
+ </group>
152
+ {ticks.map((i) => {
153
+ const angle = (i / TICK_COUNT) * Math.PI * 2;
154
+ const x = Math.cos(angle) * 0.74;
155
+ const y = Math.sin(angle) * 0.74;
156
+ return (
157
+ <mesh key={`tick-${i}`} position={[x, y, 0]} rotation={[0, 0, angle]}>
158
+ <boxGeometry args={[0.06, 0.012, 0.01]} />
159
+ <meshStandardMaterial
160
+ color="#d4a84b"
161
+ emissive="#d4a84b"
162
+ emissiveIntensity={0.3}
163
+ transparent
164
+ opacity={0.6}
165
+ metalness={0.3}
166
+ roughness={0.4}
167
+ toneMapped={false}
168
+ />
169
+ </mesh>
170
+ );
171
+ })}
172
+ </group>
173
+ );
174
+ };
175
+
176
+ export default SigilPlaceholder;
@@ -0,0 +1,150 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * VerticalSubmenu
5
+ *
6
+ * A vertical dropdown submenu that appears when hovering over a radial submenu item
7
+ * that has children. Supports scrolling for many items.
8
+ */
9
+
10
+ import { motion } from 'framer-motion';
11
+ import React, { useEffect, useState } from 'react';
12
+ import styled from 'styled-components';
13
+
14
+ import type { VerticalSubmenuProps, SubmenuItem } from './types';
15
+ import { useSentinelDependencies } from './SentinelProvider';
16
+
17
+ const ITEM_HEIGHT = 36;
18
+ const MAX_VISIBLE_ITEMS = 6;
19
+ const MENU_WIDTH = 160;
20
+ const PADDING_Y = 4;
21
+
22
+ const Container = styled(motion.div)<{ $openUpward: boolean }>`
23
+ position: fixed;
24
+ width: ${MENU_WIDTH}px;
25
+ max-height: ${ITEM_HEIGHT * MAX_VISIBLE_ITEMS + PADDING_Y * 2}px;
26
+ overflow-y: auto;
27
+ background: rgba(0, 0, 0, 0.85);
28
+ backdrop-filter: blur(12px);
29
+ border: 1px solid rgba(212, 168, 75, 0.25);
30
+ border-radius: 8px;
31
+ padding: ${PADDING_Y}px 0;
32
+ transform-origin: ${({ $openUpward }: { $openUpward: boolean }) => ($openUpward ? 'bottom center' : 'top center')};
33
+ z-index: 10000;
34
+
35
+ /* Custom scrollbar */
36
+ &::-webkit-scrollbar {
37
+ width: 4px;
38
+ }
39
+
40
+ &::-webkit-scrollbar-track {
41
+ background: transparent;
42
+ }
43
+
44
+ &::-webkit-scrollbar-thumb {
45
+ background: rgba(212, 168, 75, 0.3);
46
+ border-radius: 2px;
47
+ }
48
+
49
+ &::-webkit-scrollbar-thumb:hover {
50
+ background: rgba(212, 168, 75, 0.5);
51
+ }
52
+ `;
53
+
54
+ const ItemRow = styled(motion.button)`
55
+ width: 100%;
56
+ height: ${ITEM_HEIGHT}px;
57
+ padding: 8px 12px;
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 10px;
61
+ background: transparent;
62
+ border: none;
63
+ border-left: 2px solid transparent;
64
+ cursor: pointer;
65
+ outline: none;
66
+ transition:
67
+ background 0.15s ease,
68
+ border-color 0.15s ease;
69
+
70
+ &:not(:last-child) {
71
+ border-bottom: 1px solid rgba(212, 168, 75, 0.1);
72
+ }
73
+
74
+ &:hover {
75
+ background: rgba(212, 168, 75, 0.1);
76
+ border-left-color: rgba(212, 168, 75, 0.9);
77
+ }
78
+ `;
79
+
80
+ const ItemLabel = styled.span`
81
+ font-family: var(--font-mono);
82
+ font-size: 10px;
83
+ letter-spacing: 0.08em;
84
+ color: rgba(212, 168, 75, 0.9);
85
+ text-transform: uppercase;
86
+ white-space: nowrap;
87
+ overflow: hidden;
88
+ text-overflow: ellipsis;
89
+ `;
90
+
91
+ export const VerticalSubmenu: React.FC<VerticalSubmenuProps> = ({
92
+ items,
93
+ anchorPosition,
94
+ onItemClick,
95
+ }) => {
96
+ const [openUpward, setOpenUpward] = useState(false);
97
+ const { IconRenderer } = useSentinelDependencies();
98
+
99
+ useEffect(() => {
100
+ const menuHeight = Math.min(items.length, MAX_VISIBLE_ITEMS) * ITEM_HEIGHT + PADDING_Y * 2;
101
+ const spaceBelow = typeof window !== 'undefined'
102
+ ? window.innerHeight - anchorPosition.y
103
+ : 600 - anchorPosition.y;
104
+ setOpenUpward(spaceBelow < menuHeight + 20);
105
+ }, [anchorPosition.y, items.length]);
106
+
107
+ const windowHeight = typeof window !== 'undefined' ? window.innerHeight : 600;
108
+
109
+ const style: React.CSSProperties = {
110
+ left: anchorPosition.x - MENU_WIDTH / 2,
111
+ ...(openUpward
112
+ ? { bottom: windowHeight - anchorPosition.y }
113
+ : { top: anchorPosition.y }),
114
+ };
115
+
116
+ return (
117
+ <Container
118
+ $openUpward={openUpward}
119
+ style={style}
120
+ initial={{ scaleY: 0, opacity: 0 }}
121
+ animate={{ scaleY: 1, opacity: 1 }}
122
+ exit={{ scaleY: 0, opacity: 0 }}
123
+ transition={{ duration: 0.2, ease: [0.4, 0, 0.2, 1] }}
124
+ >
125
+ {items.map((item, index) => (
126
+ <ItemRow
127
+ key={item.id}
128
+ onClick={() => {
129
+ item.action?.();
130
+ onItemClick(item);
131
+ }}
132
+ initial={{ opacity: 0 }}
133
+ animate={{ opacity: 1 }}
134
+ transition={{ delay: index * 0.03, duration: 0.15 }}
135
+ >
136
+ {IconRenderer && (
137
+ <IconRenderer
138
+ name={item.icon}
139
+ size={16}
140
+ color="rgba(212, 168, 75, 0.9)"
141
+ />
142
+ )}
143
+ <ItemLabel>{item.label}</ItemLabel>
144
+ </ItemRow>
145
+ ))}
146
+ </Container>
147
+ );
148
+ };
149
+
150
+ export default VerticalSubmenu;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Sentinel - A floating orb UI component system
3
+ *
4
+ * The Sentinel is a radial menu system that can be summoned, thrown, and docked.
5
+ * It provides quick access to applications and features through a cardinal
6
+ * direction-based menu with optional radial and vertical submenus.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * import {
11
+ * SentinelProvider,
12
+ * SentinelOverlay,
13
+ * DockedMiniOrb,
14
+ * useSentinelStore,
15
+ * } from '@backbay/glia';
16
+ *
17
+ * // In your app root or layout:
18
+ * function App() {
19
+ * return (
20
+ * <SentinelProvider
21
+ * dependencies={{
22
+ * IconRenderer: MyIconComponent,
23
+ * GlyphRenderer: MyGlyphComponent,
24
+ * onOpenProcess: (id, opts) => openApp(id, opts),
25
+ * processMap: { console: 'terminal', lens: 'viewer' },
26
+ * }}
27
+ * >
28
+ * <SentinelOverlay />
29
+ * <DockedMiniOrb />
30
+ * {children}
31
+ * </SentinelProvider>
32
+ * );
33
+ * }
34
+ *
35
+ * // In your taskbar:
36
+ * function TaskbarItem() {
37
+ * const summon = useSentinelStore((s) => s.summon);
38
+ * const setTaskbarOrigin = useSentinelStore((s) => s.setTaskbarOrigin);
39
+ *
40
+ * const handleClick = (e: React.MouseEvent) => {
41
+ * const rect = e.currentTarget.getBoundingClientRect();
42
+ * setTaskbarOrigin({ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 });
43
+ * summon();
44
+ * };
45
+ *
46
+ * return <button onClick={handleClick}>Sentinel</button>;
47
+ * }
48
+ * ```
49
+ */
50
+
51
+ // -----------------------------------------------------------------------------
52
+ // Core Components
53
+ // -----------------------------------------------------------------------------
54
+
55
+ export { SentinelOverlay } from './SentinelOverlay';
56
+ export { SentinelOrb } from './SentinelOrb';
57
+ export { SentinelTether } from './SentinelTether';
58
+ export { DockedMiniOrb } from './DockedMiniOrb';
59
+
60
+ // -----------------------------------------------------------------------------
61
+ // Menu Components
62
+ // -----------------------------------------------------------------------------
63
+
64
+ export { CardinalMenu } from './CardinalMenu';
65
+ export { CardinalItem } from './CardinalItem';
66
+ export { RadialSubmenu } from './RadialSubmenu';
67
+ export { VerticalSubmenu } from './VerticalSubmenu';
68
+
69
+ // -----------------------------------------------------------------------------
70
+ // Conversation Components
71
+ // -----------------------------------------------------------------------------
72
+
73
+ export { SentinelConversation } from './SentinelConversation';
74
+
75
+ // -----------------------------------------------------------------------------
76
+ // Avatar Components
77
+ // -----------------------------------------------------------------------------
78
+
79
+ export { AvatarMode } from './AvatarMode';
80
+ export { AvatarRenderer } from './AvatarRenderer';
81
+ export { CameraPip } from './CameraPip';
82
+
83
+ // -----------------------------------------------------------------------------
84
+ // Hooks
85
+ // -----------------------------------------------------------------------------
86
+
87
+ export { useThrowPhysics } from './useThrowPhysics';
88
+ export { useCameraPermission } from './useCameraPermission';
89
+
90
+ // -----------------------------------------------------------------------------
91
+ // Provider and Context
92
+ // -----------------------------------------------------------------------------
93
+
94
+ export { SentinelProvider, useSentinelDependencies } from './SentinelProvider';
95
+
96
+ // -----------------------------------------------------------------------------
97
+ // Store
98
+ // -----------------------------------------------------------------------------
99
+
100
+ export { useSentinelStore, createSentinelStore, type SentinelStoreApi } from './sentinelStore';
101
+
102
+ // -----------------------------------------------------------------------------
103
+ // Types
104
+ // -----------------------------------------------------------------------------
105
+
106
+ export type {
107
+ // Core types
108
+ SentinelPhase,
109
+ Edge,
110
+ DockedPosition,
111
+ Cardinal,
112
+ ConversationState,
113
+ DocketItem,
114
+ OrbPosition,
115
+ SentinelState,
116
+
117
+ // Component props
118
+ SentinelDependencies,
119
+ SentinelOverlayProps,
120
+ SentinelOrbProps,
121
+ SentinelTetherProps,
122
+ CardinalMenuProps,
123
+ CardinalItemProps,
124
+ SubmenuItem,
125
+ ArcConstraint,
126
+ RadialSubmenuProps,
127
+ VerticalSubmenuProps,
128
+ DockedMiniOrbProps,
129
+ AvatarModeProps,
130
+ AvatarRendererProps,
131
+ CameraPipProps,
132
+
133
+ // Avatar types
134
+ AvatarMotionData,
135
+ AvatarSessionReturn,
136
+
137
+ // Hook types
138
+ UseCameraPermissionReturn,
139
+ ThrowPhysicsOptions,
140
+ ThrowPhysicsReturn,
141
+
142
+ // Provider types
143
+ SentinelProviderProps,
144
+ SentinelContextValue,
145
+ } from './types';