@ibgib/space-gib 0.0.32 → 0.0.34
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/dist/client/bootstrap.mjs +24 -51
- package/dist/client/chunk-25PXC4HP.mjs +9914 -0
- package/dist/client/chunk-RXWLL2AI.mjs +30 -0
- package/dist/client/css/activity-bar.css +417 -0
- package/dist/client/css/base.css +167 -0
- package/dist/client/css/components.css +218 -0
- package/dist/client/css/cosmic.css +557 -0
- package/dist/client/css/dev-tools.css +163 -0
- package/dist/client/css/landing.css +184 -0
- package/dist/client/css/layout.css +896 -0
- package/dist/client/css/onboarding.css +181 -0
- package/dist/client/css/repos.css +740 -0
- package/dist/client/css/variables.css +64 -0
- package/dist/client/index.html +202 -31
- package/dist/client/index.mjs +31 -31
- package/dist/client/script.mjs +1 -1
- package/dist/client/style.css +11 -1056
- package/dist/respec-gib.node.mjs +5 -0
- package/dist/server/server.mjs +6879 -3109
- package/package.json +6 -6
- package/src/client/AUTO-GENERATED-version.mts +1 -1
- package/src/client/cosmos/seed-cosmos-workspace.mts +69 -0
- package/src/client/css/activity-bar.css +417 -0
- package/src/client/css/base.css +167 -0
- package/src/client/css/components.css +218 -0
- package/src/client/css/cosmic.css +557 -0
- package/src/client/css/dev-tools.css +163 -0
- package/src/client/css/landing.css +184 -0
- package/src/client/css/layout.css +896 -0
- package/src/client/css/onboarding.css +181 -0
- package/src/client/css/repos.css +740 -0
- package/src/client/css/variables.css +64 -0
- package/src/client/dev-tools/vcs-workspace.mts +3 -3
- package/src/client/index.html +202 -31
- package/src/client/style.css +11 -1056
- package/src/client/ui/component/chat-view/IMPLEMENTATION.md +48 -0
- package/src/client/ui/component/chat-view/chat-view.css +381 -0
- package/src/client/ui/component/chat-view/chat-view.html +104 -0
- package/src/client/ui/component/chat-view/chat-view.mts +277 -0
- package/src/client/ui/component/chat-view/index.mts +9 -0
- package/src/client/ui/component/clone/IMPLEMENTATION.md +68 -0
- package/src/client/ui/component/clone/clone-constants.mts +16 -0
- package/src/client/ui/component/clone/clone-types.mts +60 -0
- package/src/client/ui/component/clone/clone.css +383 -0
- package/src/client/ui/component/clone/clone.html +89 -0
- package/src/client/ui/component/clone/clone.mts +620 -0
- package/src/client/ui/component/clone/index.mts +11 -0
- package/src/client/ui/component/code-editor/IMPLEMENTATION.md +89 -0
- package/src/client/ui/component/code-editor/code-editor.css +275 -0
- package/src/client/ui/component/code-editor/code-editor.html +54 -0
- package/src/client/ui/component/code-editor/code-editor.mts +427 -0
- package/src/client/ui/component/code-editor/index.mts +9 -0
- package/src/client/ui/component/cosmos-navigator/IMPLEMENTATION.md +31 -0
- package/src/client/ui/component/cosmos-navigator/cosmos-navigator.css +864 -0
- package/src/client/ui/component/cosmos-navigator/cosmos-navigator.html +97 -0
- package/src/client/ui/component/cosmos-navigator/cosmos-navigator.mts +1084 -0
- package/src/client/ui/component/cosmos-navigator/index.mts +18 -0
- package/src/client/ui/component/file-explorer/IMPLEMENTATION.md +42 -0
- package/src/client/ui/component/file-explorer/file-explorer.css +256 -0
- package/src/client/ui/component/file-explorer/file-explorer.html +75 -0
- package/src/client/ui/component/file-explorer/file-explorer.mts +346 -0
- package/src/client/ui/component/file-explorer/index.mts +9 -0
- package/src/client/ui/component/nested-chat/IMPLEMENTATION.md +18 -0
- package/src/client/ui/component/nested-chat/chat/IMPLEMENTATION.md +14 -0
- package/src/client/ui/component/nested-chat/chat/chat-constants.mts +14 -0
- package/src/client/ui/component/nested-chat/chat/chat-helpers.mts +66 -0
- package/src/client/ui/component/nested-chat/chat/chat.css +654 -0
- package/src/client/ui/component/nested-chat/chat/chat.html +90 -0
- package/src/client/ui/component/nested-chat/chat/chat.mts +646 -0
- package/src/client/ui/component/nested-chat/chat/index.mts +7 -0
- package/src/client/ui/component/nested-chat/index.mts +14 -0
- package/src/client/ui/component/nested-chat/nested-chat-constants.mts +12 -0
- package/src/client/ui/component/nested-chat/nested-chat-types.mts +53 -0
- package/src/client/ui/component/nested-chat/nested-chat.css +142 -0
- package/src/client/ui/component/nested-chat/nested-chat.html +11 -0
- package/src/client/ui/component/nested-chat/nested-chat.mts +393 -0
- package/src/client/ui/component/notes-explorer/index.mts +9 -0
- package/src/client/ui/component/notes-explorer/notes-explorer.css +313 -0
- package/src/client/ui/component/notes-explorer/notes-explorer.html +136 -0
- package/src/client/ui/component/notes-explorer/notes-explorer.mts +240 -0
- package/src/client/ui/component/notes-inspector/index.mts +9 -0
- package/src/client/ui/component/notes-inspector/notes-inspector.css +244 -0
- package/src/client/ui/component/notes-inspector/notes-inspector.html +97 -0
- package/src/client/ui/component/notes-inspector/notes-inspector.mts +152 -0
- package/src/client/ui/component/pitch/IMPLEMENTATION.md +48 -0
- package/src/client/ui/component/pitch/index.mts +9 -0
- package/src/client/ui/component/pitch/pitch.css +295 -0
- package/src/client/ui/component/pitch/pitch.html +97 -0
- package/src/client/ui/component/pitch/pitch.mts +135 -0
- package/src/client/ui/component/timeline/IMPLEMENTATION.md +33 -0
- package/src/client/ui/component/timeline/index.mts +9 -0
- package/src/client/ui/component/timeline/timeline.css +264 -0
- package/src/client/ui/component/timeline/timeline.html +23 -0
- package/src/client/ui/component/timeline/timeline.mts +317 -0
- package/src/client/ui/component/timeline-item-details/IMPLEMENTATION.md +28 -0
- package/src/client/ui/component/timeline-item-details/index.mts +9 -0
- package/src/client/ui/component/timeline-item-details/timeline-item-details.css +286 -0
- package/src/client/ui/component/timeline-item-details/timeline-item-details.html +64 -0
- package/src/client/ui/component/timeline-item-details/timeline-item-details.mts +194 -0
- package/src/client/ui/router/index.mts +3 -0
- package/src/client/ui/router/space-gib-router-helpers.mts +272 -0
- package/src/client/ui/router/space-gib-router-helpers.respec.mts +353 -0
- package/src/client/ui/router/space-gib-router-service.mts +186 -0
- package/src/client/ui/router/space-gib-router-types.mts +93 -0
- package/src/client/ui/shell/cosmic-big-bang.mts +844 -0
- package/src/client/ui/shell/space-gib-shell-constants.mts +7 -0
- package/src/client/ui/shell/space-gib-shell-service.mts +1728 -31
- package/src/server/server.mts +20 -1
- package/tsconfig.test.json +5 -1
- package/dist/client/chunk-AX6BROMT.mjs +0 -1
- package/dist/client/chunk-CIQUGV7U.mjs +0 -25
- package/dist/client/chunk-YPCHYDKL.mjs +0 -3102
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
import thisCss from "./cosmos-navigator.css";
|
|
2
|
+
import thisHtml from "./cosmos-navigator.html";
|
|
3
|
+
|
|
4
|
+
import { ROOT_ADDR } from "@ibgib/ts-gib/dist/V1/constants.mjs";
|
|
5
|
+
import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
|
|
6
|
+
import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
|
|
7
|
+
import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
8
|
+
import {
|
|
9
|
+
IbGibDynamicComponentMetaBase, IbGibDynamicComponentInstanceBase,
|
|
10
|
+
} from "@ibgib/web-gib/dist/ui/component/ibgib-dynamic-component-bases.mjs";
|
|
11
|
+
import {
|
|
12
|
+
ElementsBase, IbGibDynamicComponentInstance,
|
|
13
|
+
IbGibDynamicComponentInstanceInitOpts,
|
|
14
|
+
} from "@ibgib/web-gib/dist/ui/component/component-types.mjs";
|
|
15
|
+
import { getComponentCtorArg } from "@ibgib/web-gib/dist/app-bootstrap/init-orchestration.mjs";
|
|
16
|
+
import { seedInMemoryCosmosWorkspace } from "../../../cosmos/seed-cosmos-workspace.mjs";
|
|
17
|
+
|
|
18
|
+
export const COSMOS_NAVIGATOR_COMPONENT_NAME = 'space-gib-cosmos-navigator';
|
|
19
|
+
|
|
20
|
+
export interface AttachedSpace {
|
|
21
|
+
id: string;
|
|
22
|
+
type: 'code' | 'text';
|
|
23
|
+
name: string;
|
|
24
|
+
shortLabel: string;
|
|
25
|
+
icon: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CosmosInfo {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
spaces: AttachedSpace[];
|
|
32
|
+
createdAt: number;
|
|
33
|
+
isTemporary?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Metadata factory for space-gib-cosmos-navigator Web Component.
|
|
38
|
+
*/
|
|
39
|
+
export class CosmosNavigatorComponentMeta extends IbGibDynamicComponentMetaBase {
|
|
40
|
+
protected lc: string = `[${CosmosNavigatorComponentMeta.name}]`;
|
|
41
|
+
|
|
42
|
+
routeRegExp?: RegExp = new RegExp(`^${COSMOS_NAVIGATOR_COMPONENT_NAME}$`);
|
|
43
|
+
componentName = COSMOS_NAVIGATOR_COMPONENT_NAME;
|
|
44
|
+
|
|
45
|
+
constructor() {
|
|
46
|
+
super(getComponentCtorArg());
|
|
47
|
+
if (!customElements.get(this.componentName)) {
|
|
48
|
+
customElements.define(this.componentName, CosmosNavigatorComponentInstance);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async createInstance({
|
|
53
|
+
path,
|
|
54
|
+
ibGibAddr
|
|
55
|
+
}: {
|
|
56
|
+
path: string;
|
|
57
|
+
ibGibAddr: IbGibAddr;
|
|
58
|
+
}): Promise<IbGibDynamicComponentInstance<IbGib_V1>> {
|
|
59
|
+
const component = new CosmosNavigatorComponentInstance();
|
|
60
|
+
await component.initialize({
|
|
61
|
+
ibGibAddr,
|
|
62
|
+
meta: this,
|
|
63
|
+
html: thisHtml,
|
|
64
|
+
css: [thisCss],
|
|
65
|
+
});
|
|
66
|
+
return component;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* References to DOM elements within the component's Shadow Root.
|
|
72
|
+
*/
|
|
73
|
+
export interface CosmosNavigatorElements extends ElementsBase {
|
|
74
|
+
rootEl: HTMLDivElement;
|
|
75
|
+
canvasEl: HTMLCanvasElement;
|
|
76
|
+
polycosmosStageEl: HTMLDivElement;
|
|
77
|
+
singularityContainerEl: HTMLDivElement;
|
|
78
|
+
btnSingularityEl: HTMLButtonElement;
|
|
79
|
+
singularityLabelEl: HTMLSpanElement;
|
|
80
|
+
orbitTrackEl: HTMLDivElement;
|
|
81
|
+
orbitingCosmosesContainerEl: HTMLDivElement;
|
|
82
|
+
activeCosmosStageEl: HTMLDivElement;
|
|
83
|
+
nodePitchWrapperEl: HTMLDivElement;
|
|
84
|
+
btnNodePitchEl: HTMLButtonElement;
|
|
85
|
+
nodeIdentityWrapperEl: HTMLDivElement;
|
|
86
|
+
btnNodeIdentityEl: HTMLButtonElement;
|
|
87
|
+
nodeSuperspaceWrapperEl: HTMLDivElement;
|
|
88
|
+
btnNodeSuperspaceEl: HTMLButtonElement;
|
|
89
|
+
activeCosmosTitleCaptionEl: HTMLSpanElement;
|
|
90
|
+
activeCosmosTempBadgeEl?: HTMLSpanElement;
|
|
91
|
+
superspaceSatellitesEl: HTMLDivElement;
|
|
92
|
+
btnSatCodeEl: HTMLButtonElement;
|
|
93
|
+
btnSatTextEl: HTMLButtonElement;
|
|
94
|
+
nodeAgentWrapperEl: HTMLDivElement;
|
|
95
|
+
btnNodeAgentEl: HTMLButtonElement;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Concrete implementation of the unified Cosmos & Polycosmos Navigator component.
|
|
100
|
+
*/
|
|
101
|
+
export class CosmosNavigatorComponentInstance
|
|
102
|
+
extends IbGibDynamicComponentInstanceBase<IbGib_V1, CosmosNavigatorElements>
|
|
103
|
+
implements IbGibDynamicComponentInstance<IbGib_V1, CosmosNavigatorElements> {
|
|
104
|
+
|
|
105
|
+
protected lc: string = `[${CosmosNavigatorComponentInstance.name}]`;
|
|
106
|
+
|
|
107
|
+
private get el(): CosmosNavigatorElements {
|
|
108
|
+
if (!this.elements) {
|
|
109
|
+
throw new Error(`[${CosmosNavigatorComponentInstance.name}] elements not initialized.`);
|
|
110
|
+
}
|
|
111
|
+
return this.elements;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// State
|
|
115
|
+
private cosmoses: CosmosInfo[] = [];
|
|
116
|
+
private activeCosmosId: string | null = null;
|
|
117
|
+
private zoomState: 'polycosmos' | 'cosmos' = 'polycosmos';
|
|
118
|
+
private initialOnboardingDone: boolean = false;
|
|
119
|
+
private isSpaceLaunching: boolean = false;
|
|
120
|
+
|
|
121
|
+
// Canvas & Starfield State
|
|
122
|
+
private ctx: CanvasRenderingContext2D | null = null;
|
|
123
|
+
private animFrameId: number | null = null;
|
|
124
|
+
private resizeObserver: ResizeObserver | null = null;
|
|
125
|
+
private stars: Array<{ x: number; y: number; r: number; alpha: number; speed: number; phase: number }> = [];
|
|
126
|
+
private particles: Array<{
|
|
127
|
+
x: number;
|
|
128
|
+
y: number;
|
|
129
|
+
vx: number;
|
|
130
|
+
vy: number;
|
|
131
|
+
r: number;
|
|
132
|
+
color: string;
|
|
133
|
+
alpha: number;
|
|
134
|
+
decay: number;
|
|
135
|
+
}> = [];
|
|
136
|
+
private shockwaveRadius = 0;
|
|
137
|
+
private shockwaveAlpha = 0;
|
|
138
|
+
|
|
139
|
+
constructor() {
|
|
140
|
+
super();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
override async initialize(opts: IbGibDynamicComponentInstanceInitOpts): Promise<void> {
|
|
144
|
+
await super.initialize(opts);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
override async disconnected(): Promise<void> {
|
|
148
|
+
const lc = `${this.lc}[${this.disconnected.name}]`;
|
|
149
|
+
try {
|
|
150
|
+
this.clearAutoDockTimer();
|
|
151
|
+
if (this.animFrameId) {
|
|
152
|
+
cancelAnimationFrame(this.animFrameId);
|
|
153
|
+
this.animFrameId = null;
|
|
154
|
+
}
|
|
155
|
+
if (this.resizeObserver) {
|
|
156
|
+
this.resizeObserver.disconnect();
|
|
157
|
+
this.resizeObserver = null;
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
protected override async connectedCallback_reconnectComponent(): Promise<void> {
|
|
165
|
+
await super.connectedCallback_reconnectComponent();
|
|
166
|
+
this.ensureStars();
|
|
167
|
+
this.renderCurrentState();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
override async created(): Promise<void> {
|
|
171
|
+
const lc = `${this.lc}[${this.created.name}]`;
|
|
172
|
+
try {
|
|
173
|
+
const shadow = this.shadowRoot!;
|
|
174
|
+
this.elements = {
|
|
175
|
+
rootEl: shadow.getElementById('cosmos-navigator-root') as HTMLDivElement,
|
|
176
|
+
canvasEl: shadow.getElementById('starfield-canvas') as HTMLCanvasElement,
|
|
177
|
+
polycosmosStageEl: shadow.getElementById('polycosmos-stage') as HTMLDivElement,
|
|
178
|
+
singularityContainerEl: shadow.getElementById('singularity-container') as HTMLDivElement,
|
|
179
|
+
btnSingularityEl: shadow.getElementById('btn-singularity') as HTMLButtonElement,
|
|
180
|
+
singularityLabelEl: shadow.getElementById('singularity-label') as HTMLSpanElement,
|
|
181
|
+
orbitTrackEl: shadow.getElementById('orbit-track') as HTMLDivElement,
|
|
182
|
+
orbitingCosmosesContainerEl: shadow.getElementById('orbiting-cosmoses-container') as HTMLDivElement,
|
|
183
|
+
activeCosmosStageEl: shadow.getElementById('active-cosmos-stage') as HTMLDivElement,
|
|
184
|
+
nodePitchWrapperEl: shadow.getElementById('node-pitch-wrapper') as HTMLDivElement,
|
|
185
|
+
btnNodePitchEl: shadow.getElementById('btn-node-pitch') as HTMLButtonElement,
|
|
186
|
+
nodeIdentityWrapperEl: shadow.getElementById('node-identity-wrapper') as HTMLDivElement,
|
|
187
|
+
btnNodeIdentityEl: shadow.getElementById('btn-node-identity') as HTMLButtonElement,
|
|
188
|
+
nodeSuperspaceWrapperEl: shadow.getElementById('node-superspace-wrapper') as HTMLDivElement,
|
|
189
|
+
btnNodeSuperspaceEl: shadow.getElementById('btn-node-superspace') as HTMLButtonElement,
|
|
190
|
+
activeCosmosTitleCaptionEl: shadow.getElementById('active-cosmos-title-caption') as HTMLSpanElement,
|
|
191
|
+
activeCosmosTempBadgeEl: shadow.getElementById('active-cosmos-temp-badge') as HTMLSpanElement,
|
|
192
|
+
superspaceSatellitesEl: shadow.getElementById('superspace-satellites') as HTMLDivElement,
|
|
193
|
+
btnSatCodeEl: shadow.getElementById('btn-sat-code') as HTMLButtonElement,
|
|
194
|
+
btnSatTextEl: shadow.getElementById('btn-sat-text') as HTMLButtonElement,
|
|
195
|
+
nodeAgentWrapperEl: shadow.getElementById('node-agent-wrapper') as HTMLDivElement,
|
|
196
|
+
btnNodeAgentEl: shadow.getElementById('btn-node-agent') as HTMLButtonElement,
|
|
197
|
+
contentEl: shadow.getElementById('cosmos-navigator-root') as HTMLDivElement,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
this.loadState();
|
|
201
|
+
this.initCanvas();
|
|
202
|
+
this.bindHandlers();
|
|
203
|
+
this.renderCurrentState();
|
|
204
|
+
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private loadState(): void {
|
|
211
|
+
// In-memory only for Phase 1 testing; backing ibgibs will handle persistence in Phase 2
|
|
212
|
+
this.cosmoses = [];
|
|
213
|
+
this.activeCosmosId = null;
|
|
214
|
+
this.zoomState = 'polycosmos';
|
|
215
|
+
|
|
216
|
+
// Clear any stale keys from earlier runs so reload presents a clean slate
|
|
217
|
+
try {
|
|
218
|
+
localStorage.removeItem('space-gib-cosmoses');
|
|
219
|
+
localStorage.removeItem('space-gib-active-cosmos-id');
|
|
220
|
+
localStorage.removeItem('space-gib-mode');
|
|
221
|
+
} catch {
|
|
222
|
+
// ignore
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private saveState(): void {
|
|
227
|
+
// In-memory only for Phase 1 testing; backing ibgibs will handle persistence in Phase 2
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private initCanvas(): void {
|
|
231
|
+
if (!this.el.canvasEl) return;
|
|
232
|
+
this.ctx = this.el.canvasEl.getContext('2d');
|
|
233
|
+
if (!this.ctx) return;
|
|
234
|
+
|
|
235
|
+
this.resizeCanvas();
|
|
236
|
+
|
|
237
|
+
if (typeof window !== 'undefined' && 'ResizeObserver' in window && this.el.rootEl) {
|
|
238
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
239
|
+
this.resizeCanvas();
|
|
240
|
+
});
|
|
241
|
+
this.resizeObserver.observe(this.el.rootEl);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
window.addEventListener('resize', () => this.resizeCanvas());
|
|
245
|
+
|
|
246
|
+
this.initStars();
|
|
247
|
+
this.startCanvasLoop();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
public ensureStars(): void {
|
|
251
|
+
if (!this.animFrameId) {
|
|
252
|
+
this.startCanvasLoop();
|
|
253
|
+
}
|
|
254
|
+
this.resizeCanvas();
|
|
255
|
+
if (this.stars.length === 0) {
|
|
256
|
+
this.initStars();
|
|
257
|
+
}
|
|
258
|
+
setTimeout(() => {
|
|
259
|
+
this.resizeCanvas();
|
|
260
|
+
if (this.stars.length === 0) {
|
|
261
|
+
this.initStars();
|
|
262
|
+
}
|
|
263
|
+
}, 50);
|
|
264
|
+
setTimeout(() => {
|
|
265
|
+
this.resizeCanvas();
|
|
266
|
+
}, 200);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private resizeCanvas(): void {
|
|
270
|
+
if (!this.el.canvasEl) return;
|
|
271
|
+
const parent = this.el.rootEl || this.el.canvasEl.parentElement;
|
|
272
|
+
const parentW = parent && parent.clientWidth > 0 ? parent.clientWidth : window.innerWidth;
|
|
273
|
+
const parentH = parent && parent.clientHeight > 0 ? parent.clientHeight : 500;
|
|
274
|
+
const targetW = Math.max(300, Math.floor(parentW));
|
|
275
|
+
const targetH = Math.max(300, Math.floor(parentH));
|
|
276
|
+
|
|
277
|
+
if (this.el.canvasEl.width !== targetW || this.el.canvasEl.height !== targetH) {
|
|
278
|
+
this.el.canvasEl.width = targetW;
|
|
279
|
+
this.el.canvasEl.height = targetH;
|
|
280
|
+
if (this.stars.length === 0) {
|
|
281
|
+
this.initStars();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private initStars(): void {
|
|
287
|
+
if (!this.el.canvasEl) return;
|
|
288
|
+
const w = Math.max(300, this.el.canvasEl.width);
|
|
289
|
+
const h = Math.max(300, this.el.canvasEl.height);
|
|
290
|
+
this.stars = [];
|
|
291
|
+
const count = Math.max(100, Math.min(260, Math.floor((w * h) / 3600)));
|
|
292
|
+
for (let i = 0; i < count; i++) {
|
|
293
|
+
this.stars.push({
|
|
294
|
+
x: Math.random() * w,
|
|
295
|
+
y: Math.random() * h,
|
|
296
|
+
r: Math.random() * 1.6 + 0.4,
|
|
297
|
+
alpha: Math.random() * 0.75 + 0.25,
|
|
298
|
+
speed: Math.random() * 0.008 + 0.003,
|
|
299
|
+
phase: Math.random() * Math.PI * 2,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private startCanvasLoop(): void {
|
|
305
|
+
if (this.animFrameId) return;
|
|
306
|
+
|
|
307
|
+
const loop = () => {
|
|
308
|
+
this.renderCanvasFrame();
|
|
309
|
+
this.animFrameId = requestAnimationFrame(loop);
|
|
310
|
+
};
|
|
311
|
+
this.animFrameId = requestAnimationFrame(loop);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private renderCanvasFrame(): void {
|
|
315
|
+
if (!this.ctx || !this.el.canvasEl) return;
|
|
316
|
+
const ctx = this.ctx;
|
|
317
|
+
const w = this.el.canvasEl.width;
|
|
318
|
+
const h = this.el.canvasEl.height;
|
|
319
|
+
|
|
320
|
+
ctx.clearRect(0, 0, w, h);
|
|
321
|
+
|
|
322
|
+
// 1. Draw starfield
|
|
323
|
+
for (let i = 0; i < this.stars.length; i++) {
|
|
324
|
+
const s = this.stars[i];
|
|
325
|
+
s.phase += s.speed;
|
|
326
|
+
const currentAlpha = Math.max(0.1, Math.min(1.0, s.alpha + Math.sin(s.phase) * 0.35));
|
|
327
|
+
|
|
328
|
+
ctx.fillStyle = `rgba(255, 255, 255, ${currentAlpha})`;
|
|
329
|
+
ctx.beginPath();
|
|
330
|
+
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
|
|
331
|
+
ctx.fill();
|
|
332
|
+
|
|
333
|
+
// Subtle drift
|
|
334
|
+
s.y -= 0.08;
|
|
335
|
+
if (s.y < 0) {
|
|
336
|
+
s.y = h;
|
|
337
|
+
s.x = Math.random() * w;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 2. Shockwave ring (Big Bang explosion)
|
|
342
|
+
if (this.shockwaveAlpha > 0.01) {
|
|
343
|
+
ctx.strokeStyle = `rgba(167, 139, 250, ${this.shockwaveAlpha})`;
|
|
344
|
+
ctx.lineWidth = 4 * this.shockwaveAlpha;
|
|
345
|
+
ctx.beginPath();
|
|
346
|
+
ctx.arc(w / 2, h / 2, this.shockwaveRadius, 0, Math.PI * 2);
|
|
347
|
+
ctx.stroke();
|
|
348
|
+
|
|
349
|
+
// Secondary golden/amber inner shockwave ring
|
|
350
|
+
ctx.strokeStyle = `rgba(251, 191, 36, ${this.shockwaveAlpha * 0.75})`;
|
|
351
|
+
ctx.lineWidth = 2.5 * this.shockwaveAlpha;
|
|
352
|
+
ctx.beginPath();
|
|
353
|
+
ctx.arc(w / 2, h / 2, Math.max(0, this.shockwaveRadius - 16), 0, Math.PI * 2);
|
|
354
|
+
ctx.stroke();
|
|
355
|
+
|
|
356
|
+
this.shockwaveRadius += 10;
|
|
357
|
+
// Slower decay: lasts ~1 second longer than before
|
|
358
|
+
this.shockwaveAlpha *= 0.975;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 3. Explosion Particles
|
|
362
|
+
for (let i = this.particles.length - 1; i >= 0; i--) {
|
|
363
|
+
const p = this.particles[i];
|
|
364
|
+
p.x += p.vx;
|
|
365
|
+
p.y += p.vy;
|
|
366
|
+
p.vx *= 0.978;
|
|
367
|
+
p.vy *= 0.978;
|
|
368
|
+
p.alpha -= p.decay;
|
|
369
|
+
|
|
370
|
+
if (p.alpha <= 0.01) {
|
|
371
|
+
this.particles.splice(i, 1);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
ctx.fillStyle = p.color.replace('rgb', 'rgba').replace(')', `, ${p.alpha})`);
|
|
376
|
+
ctx.beginPath();
|
|
377
|
+
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
|
|
378
|
+
ctx.fill();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
public triggerBigBangExplosion(): void {
|
|
383
|
+
if (!this.el.canvasEl) return;
|
|
384
|
+
const w = this.el.canvasEl.width;
|
|
385
|
+
const h = this.el.canvasEl.height;
|
|
386
|
+
const cx = w / 2;
|
|
387
|
+
const cy = h / 2;
|
|
388
|
+
|
|
389
|
+
this.shockwaveRadius = 10;
|
|
390
|
+
this.shockwaveAlpha = 1.0;
|
|
391
|
+
|
|
392
|
+
this.particles = [];
|
|
393
|
+
const colors = [
|
|
394
|
+
'rgb(255, 255, 255)',
|
|
395
|
+
'rgb(167, 139, 250)',
|
|
396
|
+
'rgb(124, 92, 252)',
|
|
397
|
+
'rgb(56, 189, 248)',
|
|
398
|
+
'rgb(236, 72, 153)',
|
|
399
|
+
'rgb(251, 191, 36)',
|
|
400
|
+
];
|
|
401
|
+
|
|
402
|
+
const particleCount = 140;
|
|
403
|
+
for (let i = 0; i < particleCount; i++) {
|
|
404
|
+
const angle = Math.random() * Math.PI * 2;
|
|
405
|
+
const speed = Math.random() * 11 + 2.5;
|
|
406
|
+
this.particles.push({
|
|
407
|
+
x: cx,
|
|
408
|
+
y: cy,
|
|
409
|
+
vx: Math.cos(angle) * speed,
|
|
410
|
+
vy: Math.sin(angle) * speed,
|
|
411
|
+
r: Math.random() * 3.5 + 1.2,
|
|
412
|
+
color: colors[Math.floor(Math.random() * colors.length)],
|
|
413
|
+
alpha: 1.0,
|
|
414
|
+
// Slower decay: lasts ~1 second longer
|
|
415
|
+
decay: Math.random() * 0.007 + 0.005,
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Staged sequence for the Big Bang initiation:
|
|
422
|
+
* 1. Singularity compresses with high velocity spinning firecracker rotation (1000ms).
|
|
423
|
+
* 2. Explosion triggers: shockwave & particles burst outward across the canvas.
|
|
424
|
+
* 3. Dispatches 'space-gib-big-bang-exploded' so the Shell can fade in header & footer.
|
|
425
|
+
* 4. Cosmos & escaping buttons blossom out with the explosion shockwave (120ms).
|
|
426
|
+
*/
|
|
427
|
+
public triggerBigBangSequence(onComplete?: () => void): void {
|
|
428
|
+
const btn = this.el.btnSingularityEl;
|
|
429
|
+
if (!btn) {
|
|
430
|
+
this.triggerBigBangExplosion();
|
|
431
|
+
onComplete?.();
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Stage 1: Gravitational compression with high-velocity firecracker rotation (1000ms)
|
|
436
|
+
btn.classList.add('compressing');
|
|
437
|
+
|
|
438
|
+
setTimeout(() => {
|
|
439
|
+
// Stage 2: Explosion burst (BOOM!)
|
|
440
|
+
btn.classList.remove('compressing');
|
|
441
|
+
btn.classList.add('exploded');
|
|
442
|
+
|
|
443
|
+
this.triggerBigBangExplosion();
|
|
444
|
+
|
|
445
|
+
// Dispatch event to Shell: the Big Bang explosion has hit!
|
|
446
|
+
this.dispatchEvent(new CustomEvent('space-gib-big-bang-exploded', {
|
|
447
|
+
bubbles: true,
|
|
448
|
+
composed: true,
|
|
449
|
+
}));
|
|
450
|
+
|
|
451
|
+
// Stage 3: Cosmos and buttons emerge directly with the explosion shockwave
|
|
452
|
+
setTimeout(() => {
|
|
453
|
+
onComplete?.();
|
|
454
|
+
}, 120);
|
|
455
|
+
|
|
456
|
+
setTimeout(() => {
|
|
457
|
+
btn.classList.remove('exploded');
|
|
458
|
+
}, 600);
|
|
459
|
+
}, 1000);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private bindHandlers(): void {
|
|
463
|
+
// Singularity Button click (Empty state Big Bang OR Polycosmos New Cosmos)
|
|
464
|
+
this.el.btnSingularityEl.addEventListener('click', () => {
|
|
465
|
+
this.triggerBigBangSequence(async () => {
|
|
466
|
+
if (this.cosmoses.length === 0) {
|
|
467
|
+
try {
|
|
468
|
+
const seeded = await seedInMemoryCosmosWorkspace();
|
|
469
|
+
if (!this.cosmoses.some(c => c.id === seeded.id)) {
|
|
470
|
+
this.cosmoses.push(seeded);
|
|
471
|
+
}
|
|
472
|
+
this.selectCosmos(seeded.id);
|
|
473
|
+
} catch (seedErr) {
|
|
474
|
+
console.error(`${this.lc} Failed to seed in-memory cosmos: ${extractErrorMsg(seedErr)}`);
|
|
475
|
+
const fallback = this.createCosmos('welcome-cosmos');
|
|
476
|
+
this.selectCosmos(fallback.id);
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
const nextIndex = this.cosmoses.length + 1;
|
|
480
|
+
const newCosmos = this.createCosmos(`cosmos-${nextIndex}`);
|
|
481
|
+
this.selectCosmos(newCosmos.id);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Satellites Click (Code Space & Text Space)
|
|
488
|
+
this.el.btnSatCodeEl.addEventListener('click', (e) => {
|
|
489
|
+
e.stopPropagation();
|
|
490
|
+
this.handleSpaceClick('code');
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
this.el.btnSatTextEl.addEventListener('click', (e) => {
|
|
494
|
+
e.stopPropagation();
|
|
495
|
+
this.handleSpaceClick('text');
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// Hover over satellites pauses the orbit & cosmos galaxy spin (drifting buttons keep moving)
|
|
499
|
+
[this.el.btnSatCodeEl, this.el.btnSatTextEl].forEach(btn => {
|
|
500
|
+
btn?.addEventListener('mouseenter', () => {
|
|
501
|
+
this.el.activeCosmosStageEl?.classList.add('satellites-hovered');
|
|
502
|
+
});
|
|
503
|
+
btn?.addEventListener('mouseleave', () => {
|
|
504
|
+
this.el.activeCosmosStageEl?.classList.remove('satellites-hovered');
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// Pitch Node Click (💰)
|
|
509
|
+
this.el.btnNodePitchEl?.addEventListener('click', (e) => {
|
|
510
|
+
e.stopPropagation();
|
|
511
|
+
this.handlePitchClick();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Identity Node Click
|
|
515
|
+
this.el.btnNodeIdentityEl.addEventListener('click', (e) => {
|
|
516
|
+
e.stopPropagation();
|
|
517
|
+
this.handleIdentityClick();
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// Agent Chat Click
|
|
521
|
+
this.el.btnNodeAgentEl.addEventListener('click', (e) => {
|
|
522
|
+
e.stopPropagation();
|
|
523
|
+
this.handleAgentClick();
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
private launchProjectile({
|
|
528
|
+
sourceWrapperEl,
|
|
529
|
+
sourceBtnEl,
|
|
530
|
+
targetEl,
|
|
531
|
+
fallbackTarget,
|
|
532
|
+
emoji,
|
|
533
|
+
isPitch = false,
|
|
534
|
+
projectileType,
|
|
535
|
+
duration = 1600,
|
|
536
|
+
onApproach,
|
|
537
|
+
onDocked,
|
|
538
|
+
}: {
|
|
539
|
+
sourceWrapperEl: HTMLElement;
|
|
540
|
+
sourceBtnEl: HTMLElement;
|
|
541
|
+
targetEl?: HTMLElement | null;
|
|
542
|
+
fallbackTarget?: { x: number; y: number; width?: number; height?: number };
|
|
543
|
+
emoji: string;
|
|
544
|
+
isPitch?: boolean;
|
|
545
|
+
projectileType?: 'pitch' | 'identity' | 'agent' | 'code' | 'text';
|
|
546
|
+
duration?: number;
|
|
547
|
+
onApproach?: () => void;
|
|
548
|
+
onDocked: () => void;
|
|
549
|
+
}): void {
|
|
550
|
+
const fromRect = sourceBtnEl.getBoundingClientRect();
|
|
551
|
+
let toRect: { left: number; top: number; width: number; height: number };
|
|
552
|
+
|
|
553
|
+
if (targetEl) {
|
|
554
|
+
const r = targetEl.getBoundingClientRect();
|
|
555
|
+
if (r.width > 0 && r.height > 0) {
|
|
556
|
+
toRect = { left: r.left, top: r.top, width: r.width, height: r.height };
|
|
557
|
+
} else if (fallbackTarget) {
|
|
558
|
+
toRect = { left: fallbackTarget.x, top: fallbackTarget.y, width: fallbackTarget.width || 44, height: fallbackTarget.height || 44 };
|
|
559
|
+
} else {
|
|
560
|
+
toRect = { left: r.left, top: r.top, width: 44, height: 44 };
|
|
561
|
+
}
|
|
562
|
+
} else if (fallbackTarget) {
|
|
563
|
+
toRect = { left: fallbackTarget.x, top: fallbackTarget.y, width: fallbackTarget.width || 44, height: fallbackTarget.height || 44 };
|
|
564
|
+
} else {
|
|
565
|
+
onDocked();
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Hide source wrapper in the canvas stage so only the flying projectile is seen
|
|
570
|
+
sourceWrapperEl.classList.add('node-is-escaping');
|
|
571
|
+
|
|
572
|
+
// Create the flying projectile on document.body (above all panels, headers, overflow:hidden)
|
|
573
|
+
const projectile = document.createElement('div');
|
|
574
|
+
const isPitchProj = isPitch || projectileType === 'pitch';
|
|
575
|
+
projectile.className = `cosmos-flying-projectile ${isPitchProj ? 'pitch-projectile' : ''}`;
|
|
576
|
+
projectile.style.position = 'fixed';
|
|
577
|
+
projectile.style.left = `${fromRect.left}px`;
|
|
578
|
+
projectile.style.top = `${fromRect.top}px`;
|
|
579
|
+
projectile.style.width = `${fromRect.width}px`;
|
|
580
|
+
projectile.style.height = `${fromRect.height}px`;
|
|
581
|
+
projectile.style.zIndex = '999999';
|
|
582
|
+
projectile.style.pointerEvents = 'none';
|
|
583
|
+
projectile.style.margin = '0';
|
|
584
|
+
projectile.style.borderRadius = '50%';
|
|
585
|
+
projectile.style.display = 'flex';
|
|
586
|
+
projectile.style.alignItems = 'center';
|
|
587
|
+
projectile.style.justifyContent = 'center';
|
|
588
|
+
projectile.style.willChange = 'transform, opacity';
|
|
589
|
+
|
|
590
|
+
if (projectileType === 'code') {
|
|
591
|
+
projectile.style.background = 'radial-gradient(circle, rgba(14, 165, 233, 0.45) 0%, rgba(15, 23, 42, 0.98) 100%)';
|
|
592
|
+
projectile.style.border = '2px solid rgba(56, 189, 248, 0.95)';
|
|
593
|
+
projectile.style.boxShadow = '0 0 28px rgba(56, 189, 248, 0.85), inset 0 0 14px rgba(56, 189, 248, 0.45)';
|
|
594
|
+
projectile.style.color = '#38bdf8';
|
|
595
|
+
projectile.style.fontSize = '1.25rem';
|
|
596
|
+
projectile.style.fontWeight = '700';
|
|
597
|
+
projectile.style.fontFamily = 'monospace';
|
|
598
|
+
} else if (projectileType === 'text') {
|
|
599
|
+
projectile.style.background = 'radial-gradient(circle, rgba(167, 139, 250, 0.45) 0%, rgba(15, 23, 42, 0.98) 100%)';
|
|
600
|
+
projectile.style.border = '2px solid rgba(167, 139, 250, 0.95)';
|
|
601
|
+
projectile.style.boxShadow = '0 0 28px rgba(167, 139, 250, 0.85), inset 0 0 14px rgba(167, 139, 250, 0.45)';
|
|
602
|
+
projectile.style.fontSize = '1.45rem';
|
|
603
|
+
} else if (isPitchProj) {
|
|
604
|
+
projectile.style.background = 'radial-gradient(circle, rgba(251, 191, 36, 0.3) 0%, rgba(217, 119, 6, 0.95) 100%)';
|
|
605
|
+
projectile.style.border = '2px solid rgba(251, 191, 36, 0.9)';
|
|
606
|
+
projectile.style.boxShadow = '0 0 28px rgba(251, 191, 36, 0.75), inset 0 0 14px rgba(251, 191, 36, 0.4)';
|
|
607
|
+
projectile.style.fontSize = '1.35rem';
|
|
608
|
+
} else {
|
|
609
|
+
projectile.style.background = 'radial-gradient(circle, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.98) 100%)';
|
|
610
|
+
projectile.style.border = '2px solid rgba(124, 92, 252, 0.85)';
|
|
611
|
+
projectile.style.boxShadow = '0 0 28px rgba(124, 92, 252, 0.7), inset 0 0 15px rgba(124, 92, 252, 0.3)';
|
|
612
|
+
projectile.style.fontSize = '1.5rem';
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const emojiSpan = document.createElement('span');
|
|
616
|
+
emojiSpan.textContent = emoji;
|
|
617
|
+
projectile.appendChild(emojiSpan);
|
|
618
|
+
|
|
619
|
+
document.body.appendChild(projectile);
|
|
620
|
+
|
|
621
|
+
if (onApproach && duration > 350) {
|
|
622
|
+
window.setTimeout(() => {
|
|
623
|
+
onApproach?.();
|
|
624
|
+
}, duration - 350);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const deltaX = (toRect.left + toRect.width / 2) - (fromRect.left + fromRect.width / 2);
|
|
628
|
+
const deltaY = (toRect.top + toRect.height / 2) - (fromRect.top + fromRect.height / 2);
|
|
629
|
+
const targetScale = Math.min(toRect.width / fromRect.width, toRect.height / fromRect.height) || 0.85;
|
|
630
|
+
|
|
631
|
+
// Smooth trajectory: fully visible throughout the flight
|
|
632
|
+
const anim = projectile.animate([
|
|
633
|
+
{
|
|
634
|
+
transform: 'translate(0px, 0px) scale(1)',
|
|
635
|
+
opacity: 1,
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
transform: `translate(${deltaX * 0.45}px, ${deltaY * 0.35}px) scale(${1 + (targetScale - 1) * 0.2})`,
|
|
639
|
+
opacity: 1,
|
|
640
|
+
offset: 0.45,
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
transform: `translate(${deltaX}px, ${deltaY}px) scale(${targetScale})`,
|
|
644
|
+
opacity: 1,
|
|
645
|
+
offset: 0.9,
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
transform: `translate(${deltaX}px, ${deltaY}px) scale(${targetScale})`,
|
|
649
|
+
opacity: 0,
|
|
650
|
+
offset: 1.0,
|
|
651
|
+
}
|
|
652
|
+
], {
|
|
653
|
+
duration,
|
|
654
|
+
easing: 'cubic-bezier(0.22, 1, 0.36, 1)',
|
|
655
|
+
fill: 'forwards',
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
anim.onfinish = () => {
|
|
659
|
+
projectile.remove();
|
|
660
|
+
if (projectileType === 'code' || projectileType === 'text') {
|
|
661
|
+
sourceWrapperEl.classList.remove('node-is-escaping');
|
|
662
|
+
}
|
|
663
|
+
onDocked();
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
public handlePitchClick(isAuto: boolean = false): void {
|
|
668
|
+
this.initialOnboardingDone = true;
|
|
669
|
+
if (!this.el.nodePitchWrapperEl || !this.el.btnNodePitchEl) return;
|
|
670
|
+
if (this.el.nodePitchWrapperEl.classList.contains('node-is-escaping')) return;
|
|
671
|
+
|
|
672
|
+
const targetEl = document.getElementById('btn-header-pitch');
|
|
673
|
+
const fallbackTarget = {
|
|
674
|
+
x: window.innerWidth / 2 - 26,
|
|
675
|
+
y: 8,
|
|
676
|
+
width: 52,
|
|
677
|
+
height: 52,
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
this.launchProjectile({
|
|
681
|
+
sourceWrapperEl: this.el.nodePitchWrapperEl,
|
|
682
|
+
sourceBtnEl: this.el.btnNodePitchEl,
|
|
683
|
+
targetEl,
|
|
684
|
+
fallbackTarget,
|
|
685
|
+
emoji: '💰',
|
|
686
|
+
isPitch: true,
|
|
687
|
+
duration: 1600,
|
|
688
|
+
onDocked: () => {
|
|
689
|
+
this.dispatchEvent(new CustomEvent('space-gib-pitch-docked', {
|
|
690
|
+
bubbles: true,
|
|
691
|
+
composed: true,
|
|
692
|
+
detail: { isAuto },
|
|
693
|
+
}));
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
public handleIdentityClick(isAuto: boolean = false): void {
|
|
699
|
+
this.initialOnboardingDone = true;
|
|
700
|
+
if (!this.el.nodeIdentityWrapperEl || !this.el.btnNodeIdentityEl) return;
|
|
701
|
+
if (this.el.nodeIdentityWrapperEl.classList.contains('node-is-escaping')) return;
|
|
702
|
+
|
|
703
|
+
const targetEl = document.getElementById('identity-header-container');
|
|
704
|
+
const fallbackTarget = {
|
|
705
|
+
x: window.innerWidth - 180,
|
|
706
|
+
y: 8,
|
|
707
|
+
width: 140,
|
|
708
|
+
height: 40,
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
this.launchProjectile({
|
|
712
|
+
sourceWrapperEl: this.el.nodeIdentityWrapperEl,
|
|
713
|
+
sourceBtnEl: this.el.btnNodeIdentityEl,
|
|
714
|
+
targetEl,
|
|
715
|
+
fallbackTarget,
|
|
716
|
+
emoji: '👤',
|
|
717
|
+
isPitch: false,
|
|
718
|
+
duration: 1600,
|
|
719
|
+
onDocked: () => {
|
|
720
|
+
this.dispatchEvent(new CustomEvent('space-gib-identity-docked', {
|
|
721
|
+
bubbles: true,
|
|
722
|
+
composed: true,
|
|
723
|
+
detail: { isAuto },
|
|
724
|
+
}));
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
public handleAgentClick(isAuto: boolean = false): void {
|
|
730
|
+
this.initialOnboardingDone = true;
|
|
731
|
+
if (!this.el.nodeAgentWrapperEl || !this.el.btnNodeAgentEl) return;
|
|
732
|
+
if (this.el.nodeAgentWrapperEl.classList.contains('node-is-escaping')) return;
|
|
733
|
+
|
|
734
|
+
const targetEl = document.getElementById('btn-footer-agent');
|
|
735
|
+
const fallbackTarget = {
|
|
736
|
+
x: window.innerWidth - 60,
|
|
737
|
+
y: window.innerHeight - 44,
|
|
738
|
+
width: 44,
|
|
739
|
+
height: 44,
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
this.launchProjectile({
|
|
743
|
+
sourceWrapperEl: this.el.nodeAgentWrapperEl,
|
|
744
|
+
sourceBtnEl: this.el.btnNodeAgentEl,
|
|
745
|
+
targetEl,
|
|
746
|
+
fallbackTarget,
|
|
747
|
+
emoji: '🤖',
|
|
748
|
+
isPitch: false,
|
|
749
|
+
projectileType: 'agent',
|
|
750
|
+
duration: 1600,
|
|
751
|
+
onDocked: () => {
|
|
752
|
+
this.dispatchEvent(new CustomEvent('space-gib-agent-docked', {
|
|
753
|
+
bubbles: true,
|
|
754
|
+
composed: true,
|
|
755
|
+
detail: { isAuto },
|
|
756
|
+
}));
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
public resetDriftAnimations(): void {
|
|
762
|
+
if (this.el.nodePitchWrapperEl) {
|
|
763
|
+
this.el.nodePitchWrapperEl.classList.remove('node-is-escaping');
|
|
764
|
+
this.el.nodePitchWrapperEl.style.opacity = '';
|
|
765
|
+
this.el.nodePitchWrapperEl.style.pointerEvents = '';
|
|
766
|
+
this.el.nodePitchWrapperEl.style.animation = 'none';
|
|
767
|
+
void this.el.nodePitchWrapperEl.offsetHeight; // trigger reflow
|
|
768
|
+
this.el.nodePitchWrapperEl.style.animation = '';
|
|
769
|
+
}
|
|
770
|
+
if (this.el.nodeIdentityWrapperEl) {
|
|
771
|
+
this.el.nodeIdentityWrapperEl.classList.remove('node-is-escaping');
|
|
772
|
+
this.el.nodeIdentityWrapperEl.style.opacity = '';
|
|
773
|
+
this.el.nodeIdentityWrapperEl.style.pointerEvents = '';
|
|
774
|
+
this.el.nodeIdentityWrapperEl.style.animation = 'none';
|
|
775
|
+
void this.el.nodeIdentityWrapperEl.offsetHeight; // trigger reflow
|
|
776
|
+
this.el.nodeIdentityWrapperEl.style.animation = '';
|
|
777
|
+
}
|
|
778
|
+
if (this.el.nodeAgentWrapperEl) {
|
|
779
|
+
this.el.nodeAgentWrapperEl.classList.remove('node-is-escaping');
|
|
780
|
+
this.el.nodeAgentWrapperEl.style.opacity = '';
|
|
781
|
+
this.el.nodeAgentWrapperEl.style.pointerEvents = '';
|
|
782
|
+
this.el.nodeAgentWrapperEl.style.animation = 'none';
|
|
783
|
+
void this.el.nodeAgentWrapperEl.offsetHeight; // trigger reflow
|
|
784
|
+
this.el.nodeAgentWrapperEl.style.animation = '';
|
|
785
|
+
}
|
|
786
|
+
if (this.el.nodeSuperspaceWrapperEl) {
|
|
787
|
+
this.el.nodeSuperspaceWrapperEl.style.animation = 'none';
|
|
788
|
+
void this.el.nodeSuperspaceWrapperEl.offsetHeight; // trigger reflow
|
|
789
|
+
this.el.nodeSuperspaceWrapperEl.style.animation = '';
|
|
790
|
+
}
|
|
791
|
+
if (this.el.superspaceSatellitesEl) {
|
|
792
|
+
const wrappers = this.el.superspaceSatellitesEl.querySelectorAll<HTMLElement>('.satellite-wrapper');
|
|
793
|
+
this.el.superspaceSatellitesEl.style.animation = 'none';
|
|
794
|
+
wrappers.forEach(w => w.style.animation = 'none');
|
|
795
|
+
void this.el.superspaceSatellitesEl.offsetHeight; // trigger reflow
|
|
796
|
+
this.el.superspaceSatellitesEl.style.animation = '';
|
|
797
|
+
wrappers.forEach(w => w.style.animation = '');
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
private autoDockTimer?: number;
|
|
802
|
+
|
|
803
|
+
public startAutoDockTimer(): void {
|
|
804
|
+
this.clearAutoDockTimer();
|
|
805
|
+
this.autoDockTimer = window.setTimeout(() => {
|
|
806
|
+
console.log(`${this.lc} 8-second auto-dock timer triggered.`);
|
|
807
|
+
this.initialOnboardingDone = true;
|
|
808
|
+
this.handlePitchClick(true);
|
|
809
|
+
setTimeout(() => this.handleIdentityClick(true), 250);
|
|
810
|
+
setTimeout(() => this.handleAgentClick(true), 500);
|
|
811
|
+
}, 8000);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
public clearAutoDockTimer(): void {
|
|
815
|
+
if (this.autoDockTimer) {
|
|
816
|
+
clearTimeout(this.autoDockTimer);
|
|
817
|
+
this.autoDockTimer = undefined;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
public handleSpaceClick(type: 'code' | 'text'): AttachedSpace | null {
|
|
822
|
+
if (!this.activeCosmosId) return null;
|
|
823
|
+
const activeCosmos = this.getActiveCosmos();
|
|
824
|
+
if (!activeCosmos) { return null; }
|
|
825
|
+
if (this.isSpaceLaunching) return null;
|
|
826
|
+
|
|
827
|
+
this.clearAutoDockTimer();
|
|
828
|
+
this.initialOnboardingDone = true;
|
|
829
|
+
this.isSpaceLaunching = true;
|
|
830
|
+
|
|
831
|
+
const count = activeCosmos.spaces.filter(s => s.type === type).length;
|
|
832
|
+
const existingSpacesCount = activeCosmos.spaces.length;
|
|
833
|
+
const newSpace: AttachedSpace = {
|
|
834
|
+
id: `${type}-space-${count + 1}-${Date.now()}`,
|
|
835
|
+
type,
|
|
836
|
+
name: type === 'code' ? `Code Space ${count + 1}` : `Text Space ${count + 1}`,
|
|
837
|
+
shortLabel: type === 'code' ? `Code ${count + 1}` : `Text ${count + 1}`,
|
|
838
|
+
icon: type === 'code' ? '</>' : '📝',
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
activeCosmos.spaces.push(newSpace);
|
|
842
|
+
this.saveState();
|
|
843
|
+
|
|
844
|
+
const sourceBtnEl = type === 'code' ? this.el.btnSatCodeEl : this.el.btnSatTextEl;
|
|
845
|
+
const sourceWrapperEl = (sourceBtnEl?.closest('.satellite-wrapper') as HTMLElement) || sourceBtnEl;
|
|
846
|
+
|
|
847
|
+
const headerH = document.getElementById('main-header')?.getBoundingClientRect().height || 60;
|
|
848
|
+
const targetTop = headerH + 128 + (existingSpacesCount * 50);
|
|
849
|
+
const fallbackTarget = {
|
|
850
|
+
x: 5,
|
|
851
|
+
y: targetTop,
|
|
852
|
+
width: 42,
|
|
853
|
+
height: 42,
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
if (!sourceBtnEl || !sourceWrapperEl) {
|
|
857
|
+
this.isSpaceLaunching = false;
|
|
858
|
+
this.dispatchEvent(new CustomEvent('space-gib-space-added', {
|
|
859
|
+
detail: {
|
|
860
|
+
cosmosId: activeCosmos.id,
|
|
861
|
+
space: newSpace,
|
|
862
|
+
cosmos: activeCosmos,
|
|
863
|
+
},
|
|
864
|
+
bubbles: true,
|
|
865
|
+
composed: true,
|
|
866
|
+
}));
|
|
867
|
+
return newSpace;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Make activity bar visible BEFORE the zoom flight starts
|
|
871
|
+
window.dispatchEvent(new CustomEvent('space-gib-activity-bar-expand-requested', {
|
|
872
|
+
bubbles: true,
|
|
873
|
+
composed: true,
|
|
874
|
+
}));
|
|
875
|
+
|
|
876
|
+
setTimeout(() => {
|
|
877
|
+
this.launchProjectile({
|
|
878
|
+
sourceWrapperEl,
|
|
879
|
+
sourceBtnEl,
|
|
880
|
+
fallbackTarget,
|
|
881
|
+
emoji: type === 'code' ? '</>' : '📝',
|
|
882
|
+
projectileType: type,
|
|
883
|
+
duration: 1400,
|
|
884
|
+
onDocked: () => {
|
|
885
|
+
this.isSpaceLaunching = false;
|
|
886
|
+
sourceWrapperEl.classList.remove('node-is-escaping');
|
|
887
|
+
this.dispatchEvent(new CustomEvent('space-gib-space-added', {
|
|
888
|
+
detail: {
|
|
889
|
+
cosmosId: activeCosmos.id,
|
|
890
|
+
space: newSpace,
|
|
891
|
+
cosmos: activeCosmos,
|
|
892
|
+
},
|
|
893
|
+
bubbles: true,
|
|
894
|
+
composed: true,
|
|
895
|
+
}));
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
}, 180);
|
|
899
|
+
|
|
900
|
+
return newSpace;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
public createCosmos(name?: string, isTemporary?: boolean): CosmosInfo {
|
|
904
|
+
const id = `cosmos-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
|
|
905
|
+
const cosmosName = name || `cosmos-${this.cosmoses.length + 1}`;
|
|
906
|
+
const hasIdentity = Boolean((globalThis as any).ibgib?.identity?.domainIdentity);
|
|
907
|
+
const temp = isTemporary !== undefined ? isTemporary : !hasIdentity;
|
|
908
|
+
const newCosmos: CosmosInfo = {
|
|
909
|
+
id,
|
|
910
|
+
name: cosmosName,
|
|
911
|
+
isTemporary: temp,
|
|
912
|
+
spaces: [],
|
|
913
|
+
createdAt: Date.now(),
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
this.cosmoses.push(newCosmos);
|
|
917
|
+
this.activeCosmosId = id;
|
|
918
|
+
this.saveState();
|
|
919
|
+
return newCosmos;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
public selectCosmos(cosmosId: string): void {
|
|
923
|
+
const cosmos = this.cosmoses.find(c => c.id === cosmosId || c.name === cosmosId);
|
|
924
|
+
if (!cosmos) { return; }
|
|
925
|
+
|
|
926
|
+
this.activeCosmosId = cosmos.id;
|
|
927
|
+
this.saveState();
|
|
928
|
+
this.setZoomState('cosmos', cosmos.id);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
public async ensureCosmos(cosmosId: string): Promise<CosmosInfo> {
|
|
932
|
+
let existing = this.cosmoses.find(c => c.id === cosmosId || c.name === cosmosId);
|
|
933
|
+
if (existing) { return existing; }
|
|
934
|
+
|
|
935
|
+
if (cosmosId === 'welcome-cosmos' || cosmosId === 'my-cosmos') {
|
|
936
|
+
const seeded = await seedInMemoryCosmosWorkspace();
|
|
937
|
+
if (!this.cosmoses.some(c => c.id === seeded.id)) {
|
|
938
|
+
this.cosmoses.push(seeded);
|
|
939
|
+
}
|
|
940
|
+
return seeded;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
return this.createCosmos(cosmosId);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
public setZoomState(state: 'polycosmos' | 'cosmos', cosmosId?: string): void {
|
|
947
|
+
if (cosmosId) {
|
|
948
|
+
this.activeCosmosId = cosmosId;
|
|
949
|
+
this.saveState();
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
this.zoomState = state;
|
|
953
|
+
this.renderCurrentState();
|
|
954
|
+
|
|
955
|
+
if (state === 'polycosmos') {
|
|
956
|
+
this.dispatchEvent(new CustomEvent('space-gib-polycosmos-activated', {
|
|
957
|
+
detail: { cosmoses: this.cosmoses, activeCosmosId: this.activeCosmosId },
|
|
958
|
+
bubbles: true,
|
|
959
|
+
composed: true,
|
|
960
|
+
}));
|
|
961
|
+
} else {
|
|
962
|
+
const activeCosmos = this.getActiveCosmos();
|
|
963
|
+
if (activeCosmos) {
|
|
964
|
+
this.dispatchEvent(new CustomEvent('space-gib-cosmos-selected', {
|
|
965
|
+
detail: { cosmos: activeCosmos },
|
|
966
|
+
bubbles: true,
|
|
967
|
+
composed: true,
|
|
968
|
+
}));
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
private renderCurrentState(): void {
|
|
974
|
+
if (this.zoomState === 'polycosmos') {
|
|
975
|
+
this.clearAutoDockTimer();
|
|
976
|
+
this.el.rootEl.classList.remove('zoom-cosmos');
|
|
977
|
+
this.el.rootEl.classList.add('zoom-polycosmos');
|
|
978
|
+
|
|
979
|
+
this.el.polycosmosStageEl.classList.remove('hidden');
|
|
980
|
+
this.el.activeCosmosStageEl.classList.add('hidden');
|
|
981
|
+
|
|
982
|
+
if (this.cosmoses.length === 0) {
|
|
983
|
+
// Primordial 0-state
|
|
984
|
+
this.el.orbitTrackEl.classList.add('hidden');
|
|
985
|
+
this.el.singularityLabelEl.textContent = '';
|
|
986
|
+
} else {
|
|
987
|
+
// Multi-cosmos Polycosmos
|
|
988
|
+
this.el.orbitTrackEl.classList.remove('hidden');
|
|
989
|
+
this.el.singularityLabelEl.textContent = '+ New Cosmos';
|
|
990
|
+
this.renderOrbitNodes();
|
|
991
|
+
}
|
|
992
|
+
} else {
|
|
993
|
+
// Zoomed-in Cosmos
|
|
994
|
+
this.el.rootEl.classList.remove('zoom-polycosmos');
|
|
995
|
+
this.el.rootEl.classList.add('zoom-cosmos');
|
|
996
|
+
|
|
997
|
+
this.el.polycosmosStageEl.classList.add('hidden');
|
|
998
|
+
this.el.activeCosmosStageEl.classList.remove('hidden');
|
|
999
|
+
|
|
1000
|
+
const showInitialNodes = !this.initialOnboardingDone && this.cosmoses.length <= 1;
|
|
1001
|
+
|
|
1002
|
+
if (this.el.nodePitchWrapperEl) {
|
|
1003
|
+
this.el.nodePitchWrapperEl.style.display = showInitialNodes ? '' : 'none';
|
|
1004
|
+
}
|
|
1005
|
+
if (this.el.nodeIdentityWrapperEl) {
|
|
1006
|
+
this.el.nodeIdentityWrapperEl.style.display = showInitialNodes ? '' : 'none';
|
|
1007
|
+
}
|
|
1008
|
+
if (this.el.nodeAgentWrapperEl) {
|
|
1009
|
+
this.el.nodeAgentWrapperEl.style.display = showInitialNodes ? '' : 'none';
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (showInitialNodes) {
|
|
1013
|
+
this.resetDriftAnimations();
|
|
1014
|
+
this.startAutoDockTimer();
|
|
1015
|
+
} else {
|
|
1016
|
+
this.clearAutoDockTimer();
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const activeCosmos = this.getActiveCosmos();
|
|
1020
|
+
if (activeCosmos) {
|
|
1021
|
+
this.el.activeCosmosTitleCaptionEl.textContent = activeCosmos.name;
|
|
1022
|
+
if (activeCosmos.isTemporary) {
|
|
1023
|
+
this.el.activeCosmosTempBadgeEl?.classList.remove('hidden');
|
|
1024
|
+
} else {
|
|
1025
|
+
this.el.activeCosmosTempBadgeEl?.classList.add('hidden');
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
private renderOrbitNodes(): void {
|
|
1032
|
+
const container = this.el.orbitingCosmosesContainerEl;
|
|
1033
|
+
if (!container) { return; }
|
|
1034
|
+
|
|
1035
|
+
container.innerHTML = '';
|
|
1036
|
+
const N = this.cosmoses.length;
|
|
1037
|
+
if (N === 0) { return; }
|
|
1038
|
+
|
|
1039
|
+
const radius = 210; // orbit radius
|
|
1040
|
+
const cx = 240;
|
|
1041
|
+
const cy = 240;
|
|
1042
|
+
|
|
1043
|
+
for (let i = 0; i < N; i++) {
|
|
1044
|
+
const c = this.cosmoses[i];
|
|
1045
|
+
const angle = (i / N) * 2 * Math.PI - Math.PI / 2;
|
|
1046
|
+
const x = Math.round(cx + Math.cos(angle) * radius);
|
|
1047
|
+
const y = Math.round(cy + Math.sin(angle) * radius);
|
|
1048
|
+
|
|
1049
|
+
const btn = document.createElement('button');
|
|
1050
|
+
btn.type = 'button';
|
|
1051
|
+
btn.className = `orbit-cosmos-node ${c.id === this.activeCosmosId ? 'is-active' : ''}`;
|
|
1052
|
+
btn.style.left = `${x}px`;
|
|
1053
|
+
btn.style.top = `${y}px`;
|
|
1054
|
+
btn.title = `Enter ${c.name} (${c.spaces.length} spaces)`;
|
|
1055
|
+
|
|
1056
|
+
btn.innerHTML = `
|
|
1057
|
+
<span class="orbit-cosmos-icon">🌌</span>
|
|
1058
|
+
<span class="orbit-cosmos-name">${c.name}</span>
|
|
1059
|
+
<span class="orbit-cosmos-badge">${c.spaces.length} space${c.spaces.length === 1 ? '' : 's'}</span>
|
|
1060
|
+
`;
|
|
1061
|
+
|
|
1062
|
+
btn.addEventListener('click', (e) => {
|
|
1063
|
+
e.stopPropagation();
|
|
1064
|
+
this.selectCosmos(c.id);
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
container.appendChild(btn);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
public getCosmoses(): CosmosInfo[] {
|
|
1072
|
+
return [...this.cosmoses];
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
public getActiveCosmos(): CosmosInfo | null {
|
|
1076
|
+
if (!this.activeCosmosId) return this.cosmoses[0] || null;
|
|
1077
|
+
return this.cosmoses.find(c => c.id === this.activeCosmosId) || this.cosmoses[0] || null;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
public getZoomState(): 'polycosmos' | 'cosmos' {
|
|
1081
|
+
return this.zoomState;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|