@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.
- package/package.json +43 -0
- package/src/environment/AuroraLayer/AuroraLayer.stories.tsx +43 -0
- package/src/environment/AuroraLayer/AuroraLayer.tsx +200 -0
- package/src/environment/AuroraLayer/index.ts +2 -0
- package/src/environment/AuroraLayer/types.ts +30 -0
- package/src/environment/EnvironmentLayer/EnvironmentLayer.stories.tsx +262 -0
- package/src/environment/EnvironmentLayer/EnvironmentLayer.tsx +105 -0
- package/src/environment/EnvironmentLayer/index.ts +4 -0
- package/src/environment/EnvironmentLayer/presets.ts +128 -0
- package/src/environment/FogLayer/FogLayer.stories.tsx +83 -0
- package/src/environment/FogLayer/FogLayer.tsx +113 -0
- package/src/environment/FogLayer/index.ts +2 -0
- package/src/environment/FogLayer/types.ts +45 -0
- package/src/environment/VolumetricLight/VolumetricLight.stories.tsx +55 -0
- package/src/environment/VolumetricLight/VolumetricLight.tsx +188 -0
- package/src/environment/VolumetricLight/index.ts +2 -0
- package/src/environment/VolumetricLight/types.ts +33 -0
- package/src/environment/WeatherLayer/WeatherLayer.stories.tsx +348 -0
- package/src/environment/WeatherLayer/WeatherLayer.tsx +266 -0
- package/src/environment/WeatherLayer/cinematicCanvas.tsx +809 -0
- package/src/environment/WeatherLayer/colors.ts +27 -0
- package/src/environment/WeatherLayer/index.ts +4 -0
- package/src/environment/WeatherLayer/leafPresets.ts +12 -0
- package/src/environment/WeatherLayer/particles.ts +227 -0
- package/src/environment/WeatherLayer/types.ts +140 -0
- package/src/environment/index.ts +17 -0
- package/src/environment/shared/index.ts +2 -0
- package/src/environment/shared/noise.ts +10 -0
- package/src/environment/shared/performance.ts +33 -0
- package/src/environment/shared/types.ts +26 -0
- package/src/index.ts +2 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/vision-types.ts +84 -0
- package/src/three/AgentConsole/AgentConsole.stories.tsx +397 -0
- package/src/three/AgentConsole/AgentConsole.tsx +195 -0
- package/src/three/AgentConsole/ConsoleChat.tsx +237 -0
- package/src/three/AgentConsole/FocusConstellation.tsx +297 -0
- package/src/three/AgentConsole/GlyphAvatar.stories.tsx +110 -0
- package/src/three/AgentConsole/GlyphAvatar.tsx +117 -0
- package/src/three/AgentConsole/QuickActions.tsx +111 -0
- package/src/three/AgentConsole/index.ts +41 -0
- package/src/three/AgentConsole/types.ts +241 -0
- package/src/three/AmbientField/AmbientField.stories.tsx +290 -0
- package/src/three/AmbientField/AmbientField.tsx +307 -0
- package/src/three/AmbientField/BackbayFieldBus.ts +326 -0
- package/src/three/AmbientField/FieldProvider.tsx +83 -0
- package/src/three/AmbientField/index.ts +37 -0
- package/src/three/AmbientField/shaders/constellation.ts +384 -0
- package/src/three/AmbientField/types.ts +174 -0
- package/src/three/AttackGraph/AttackGraph.stories.tsx +144 -0
- package/src/three/AttackGraph/AttackGraph.tsx +325 -0
- package/src/three/AttackGraph/index.ts +19 -0
- package/src/three/AttackGraph/types.ts +97 -0
- package/src/three/AuditTrail/AuditTrail.stories.tsx +567 -0
- package/src/three/AuditTrail/AuditTrail.tsx +644 -0
- package/src/three/AuditTrail/index.ts +33 -0
- package/src/three/AuditTrail/types.ts +192 -0
- package/src/three/CrystallineOrganism/Breadcrumb.tsx +61 -0
- package/src/three/CrystallineOrganism/CrystallineOrganism.stories.tsx +509 -0
- package/src/three/CrystallineOrganism/CrystallineOrganism.tsx +273 -0
- package/src/three/CrystallineOrganism/LatticeEdge.tsx +69 -0
- package/src/three/CrystallineOrganism/OrganismLattice.tsx +159 -0
- package/src/three/CrystallineOrganism/OrganismParticles.tsx +148 -0
- package/src/three/CrystallineOrganism/OrganismShell.tsx +277 -0
- package/src/three/CrystallineOrganism/constants.ts +161 -0
- package/src/three/CrystallineOrganism/index.ts +17 -0
- package/src/three/CrystallineOrganism/layouts/hexGrid.ts +85 -0
- package/src/three/CrystallineOrganism/layouts/index.ts +1 -0
- package/src/three/CrystallineOrganism/types.ts +167 -0
- package/src/three/CrystallineOrganism/useOrganismEmotion.ts +84 -0
- package/src/three/FirewallBarrier/FirewallBarrier.stories.tsx +167 -0
- package/src/three/FirewallBarrier/FirewallBarrier.tsx +259 -0
- package/src/three/FirewallBarrier/index.ts +14 -0
- package/src/three/FirewallBarrier/types.ts +52 -0
- package/src/three/Glyph/GlyphObject.stories.tsx +577 -0
- package/src/three/Glyph/GlyphObject.tsx +422 -0
- package/src/three/Glyph/index.ts +10 -0
- package/src/three/Glyph/types.ts +36 -0
- package/src/three/Glyph/useGlyphController.ts +231 -0
- package/src/three/Glyph/useGlyphEmotion.ts +70 -0
- package/src/three/Graph3D/Graph3D.stories.tsx +269 -0
- package/src/three/Graph3D/Graph3D.tsx +248 -0
- package/src/three/Graph3D/GraphEdge.tsx +79 -0
- package/src/three/Graph3D/GraphNode.tsx +239 -0
- package/src/three/Graph3D/types.ts +66 -0
- package/src/three/Graph3D/utils.ts +204 -0
- package/src/three/IntelFeed/IntelFeed.stories.tsx +168 -0
- package/src/three/IntelFeed/IntelFeed.tsx +284 -0
- package/src/three/IntelFeed/index.ts +14 -0
- package/src/three/IntelFeed/types.ts +56 -0
- package/src/three/MetricsGalaxy/MetricsGalaxy.tsx +484 -0
- package/src/three/MetricsGalaxy/index.ts +6 -0
- package/src/three/MetricsGalaxy/types.ts +26 -0
- package/src/three/NetworkTopology/NetworkTopology.stories.tsx +184 -0
- package/src/three/NetworkTopology/NetworkTopology.tsx +421 -0
- package/src/three/NetworkTopology/index.ts +34 -0
- package/src/three/NetworkTopology/types.ts +128 -0
- package/src/three/ParticleField/ParticleField.stories.tsx +162 -0
- package/src/three/ParticleField/ParticleField.tsx +81 -0
- package/src/three/ParticleField/index.ts +1 -0
- package/src/three/PermissionMatrix/PermissionMatrix.stories.tsx +475 -0
- package/src/three/PermissionMatrix/PermissionMatrix.tsx +380 -0
- package/src/three/PermissionMatrix/index.ts +15 -0
- package/src/three/PermissionMatrix/types.ts +54 -0
- package/src/three/QuantumField/ConstellationField.tsx +238 -0
- package/src/three/QuantumField/FieldBus.ts +349 -0
- package/src/three/QuantumField/FieldLayer.tsx +430 -0
- package/src/three/QuantumField/FieldProvider.tsx +460 -0
- package/src/three/QuantumField/PcbField.tsx +406 -0
- package/src/three/QuantumField/QuantumField.stories.tsx +1155 -0
- package/src/three/QuantumField/TrailRTT.ts +212 -0
- package/src/three/QuantumField/WaterField.tsx +226 -0
- package/src/three/QuantumField/WaterSimRTT.ts +283 -0
- package/src/three/QuantumField/domMapping.ts +185 -0
- package/src/three/QuantumField/index.ts +110 -0
- package/src/three/QuantumField/styles/index.ts +9 -0
- package/src/three/QuantumField/styles/styleA.ts +526 -0
- package/src/three/QuantumField/styles/styleB.ts +1210 -0
- package/src/three/QuantumField/styles/styleC.ts +266 -0
- package/src/three/QuantumField/themes.ts +211 -0
- package/src/three/QuantumField/types.ts +380 -0
- package/src/three/SOCCommandCenter/SOCCommandCenter.stories.tsx +591 -0
- package/src/three/SOCCommandCenter/SOCCommandCenter.tsx +248 -0
- package/src/three/SOCCommandCenter/index.ts +26 -0
- package/src/three/SOCCommandCenter/types.ts +201 -0
- package/src/three/SecurityDashboard/SecurityDashboard.stories.tsx +508 -0
- package/src/three/SecurityDashboard/SecurityDashboard.tsx +507 -0
- package/src/three/SecurityDashboard/index.ts +37 -0
- package/src/three/SecurityDashboard/types.ts +143 -0
- package/src/three/SecurityShield/SecurityShield.stories.tsx +257 -0
- package/src/three/SecurityShield/SecurityShield.tsx +502 -0
- package/src/three/SecurityShield/index.ts +25 -0
- package/src/three/SecurityShield/types.ts +64 -0
- package/src/three/Sentinel/AvatarMode.tsx +578 -0
- package/src/three/Sentinel/AvatarRenderer.tsx +199 -0
- package/src/three/Sentinel/CameraPip.tsx +127 -0
- package/src/three/Sentinel/CardinalItem.tsx +83 -0
- package/src/three/Sentinel/CardinalMenu.tsx +370 -0
- package/src/three/Sentinel/DockedMiniOrb.tsx +146 -0
- package/src/three/Sentinel/RadialSubmenu.tsx +273 -0
- package/src/three/Sentinel/SentinelConversation.tsx +802 -0
- package/src/three/Sentinel/SentinelOrb.tsx +316 -0
- package/src/three/Sentinel/SentinelOverlay.tsx +146 -0
- package/src/three/Sentinel/SentinelProvider.tsx +145 -0
- package/src/three/Sentinel/SentinelTether.tsx +182 -0
- package/src/three/Sentinel/SigilPlaceholder.tsx +176 -0
- package/src/three/Sentinel/VerticalSubmenu.tsx +150 -0
- package/src/three/Sentinel/index.ts +145 -0
- package/src/three/Sentinel/sentinelStore.ts +196 -0
- package/src/three/Sentinel/types.ts +403 -0
- package/src/three/Sentinel/useCameraPermission.ts +153 -0
- package/src/three/Sentinel/useThrowPhysics.ts +220 -0
- package/src/three/SpatialWorkspace/CyntraWorkspace.tsx +84 -0
- package/src/three/SpatialWorkspace/JobCluster.tsx +281 -0
- package/src/three/SpatialWorkspace/NodeGraph.tsx +236 -0
- package/src/three/SpatialWorkspace/ReceiptOrbit.tsx +368 -0
- package/src/three/SpatialWorkspace/SpatialWorkspace.stories.tsx +547 -0
- package/src/three/SpatialWorkspace/SpatialWorkspace.tsx +428 -0
- package/src/three/SpatialWorkspace/TrustRings.tsx +228 -0
- package/src/three/SpatialWorkspace/adapters.ts +353 -0
- package/src/three/SpatialWorkspace/index.ts +85 -0
- package/src/three/SpatialWorkspace/nexusAdapter.ts +182 -0
- package/src/three/SpatialWorkspace/types.ts +389 -0
- package/src/three/ThreatRadar/ThreatRadar.stories.tsx +451 -0
- package/src/three/ThreatRadar/ThreatRadar.tsx +542 -0
- package/src/three/ThreatRadar/index.ts +8 -0
- package/src/three/ThreatRadar/types.ts +90 -0
- package/src/three/ThreeErrorBoundary/ThreeErrorBoundary.tsx +235 -0
- package/src/three/ThreeErrorBoundary/index.ts +5 -0
- package/src/three/index.ts +56 -0
- package/tsconfig.json +20 -0
- 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';
|