@elizaos/app-core 2.0.0-alpha.71 → 2.0.0-alpha.73
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 +4 -4
- package/src/App.tsx +7 -1
- package/src/components/AvatarLoader.tsx +1 -0
- package/src/components/CharacterRoster.tsx +1 -0
- package/src/components/CompanionSceneHost.tsx +21 -12
- package/src/components/OnboardingWizard.tsx +36 -7
- package/src/components/onboarding/ConnectionStep.tsx +8 -1
- package/src/components/onboarding/IdentityStep.tsx +110 -62
- package/src/styles/onboarding-game.css +226 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elizaos/app-core",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.73",
|
|
4
4
|
"description": "Shared application core for Milady shells and white-label apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
"@capacitor/haptics": "8.0.0",
|
|
69
69
|
"@capacitor/keyboard": "8.0.0",
|
|
70
70
|
"@capacitor/preferences": "^8.0.1",
|
|
71
|
-
"@elizaos/autonomous": "2.0.0-alpha.
|
|
72
|
-
"@elizaos/ui": "2.0.0-alpha.
|
|
71
|
+
"@elizaos/autonomous": "2.0.0-alpha.73",
|
|
72
|
+
"@elizaos/ui": "2.0.0-alpha.73",
|
|
73
73
|
"@sparkjsdev/spark": "^0.1.10",
|
|
74
74
|
"lucide-react": "^0.575.0",
|
|
75
75
|
"three": "^0.182.0",
|
|
@@ -87,5 +87,5 @@
|
|
|
87
87
|
"typescript": "^5.9.3",
|
|
88
88
|
"vitest": "^4.0.18"
|
|
89
89
|
},
|
|
90
|
-
"gitHead": "
|
|
90
|
+
"gitHead": "036d15051d6c06cca03eb0f4e3c65a18801d8360"
|
|
91
91
|
}
|
package/src/App.tsx
CHANGED
|
@@ -219,8 +219,14 @@ export function App() {
|
|
|
219
219
|
shellMode === "native" &&
|
|
220
220
|
(isCharacterTab(effectiveTab) || isCharacterTab(tab));
|
|
221
221
|
const companionShellVisible = shellMode === "companion";
|
|
222
|
+
// Don't initialize the 3D scene while the system is still booting — this
|
|
223
|
+
// prevents VrmEngine's Three.js setup from blocking the JS thread and
|
|
224
|
+
// delaying WebSocket agent-status updates (which would freeze the loader).
|
|
222
225
|
const companionSceneActive =
|
|
223
|
-
COMPANION_ENABLED &&
|
|
226
|
+
COMPANION_ENABLED &&
|
|
227
|
+
!onboardingLoading &&
|
|
228
|
+
agentStatus?.state !== "starting" &&
|
|
229
|
+
(companionShellVisible || characterSceneVisible);
|
|
224
230
|
const contextMenu = useContextMenu();
|
|
225
231
|
|
|
226
232
|
useStreamPopoutNavigation(setTab);
|
|
@@ -24,6 +24,7 @@ export const CHARACTER_PRESET_META: Record<
|
|
|
24
24
|
"hehe~": { name: "Rin", avatarIndex: 5, voicePresetId: "gigi" },
|
|
25
25
|
"...": { name: "Ryu", avatarIndex: 6, voicePresetId: "daniel" },
|
|
26
26
|
"lmao kms": { name: "Satoshi", avatarIndex: 7, voicePresetId: "callum" },
|
|
27
|
+
"bruh": { name: "Yuki", avatarIndex: 8, voicePresetId: "echo" },
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
/* ── Types ────────────────────────────────────────────────────────────── */
|
|
@@ -156,6 +156,13 @@ function CompanionSceneSurface({
|
|
|
156
156
|
companionZoomHydratedRef.current = true;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
// Lazy-mount VrmStage: only initialize the 3D engine once the scene is
|
|
160
|
+
// actually needed (first time active becomes true). This prevents the WebGL
|
|
161
|
+
// context and asset loads from firing in native/chat mode on startup.
|
|
162
|
+
const hasEverBeenActiveRef = useRef(active);
|
|
163
|
+
if (active) hasEverBeenActiveRef.current = true;
|
|
164
|
+
const shouldMountVrm = hasEverBeenActiveRef.current;
|
|
165
|
+
|
|
159
166
|
const setCompanionZoom = useCallback((value: number) => {
|
|
160
167
|
const nextZoom = clampCompanionZoom(value);
|
|
161
168
|
companionZoomRef.current = nextZoom;
|
|
@@ -447,18 +454,20 @@ function CompanionSceneSurface({
|
|
|
447
454
|
>
|
|
448
455
|
<div className="absolute inset-0 z-0 bg-cover opacity-60 bg-[radial-gradient(circle_at_10%_20%,rgba(255,255,255,0.03)_0%,transparent_40%),radial-gradient(circle_at_80%_80%,rgba(0,225,255,0.05)_0%,transparent_40%)] pointer-events-none" />
|
|
449
456
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
457
|
+
{shouldMountVrm && (
|
|
458
|
+
<VrmStage
|
|
459
|
+
active={active}
|
|
460
|
+
vrmPath={vrmPath}
|
|
461
|
+
worldUrl={worldUrl}
|
|
462
|
+
fallbackPreviewUrl={fallbackPreviewUrl}
|
|
463
|
+
preloadAvatars={preloadAvatars}
|
|
464
|
+
cameraProfile="companion"
|
|
465
|
+
onEngineReady={handleStageEngineReady}
|
|
466
|
+
onLayerEngineReady={handleStageLayerEngineReady}
|
|
467
|
+
playWaveOnAvatarChange
|
|
468
|
+
t={t}
|
|
469
|
+
/>
|
|
470
|
+
)}
|
|
462
471
|
|
|
463
472
|
<div
|
|
464
473
|
aria-hidden="true"
|
|
@@ -19,14 +19,19 @@ import { OnboardingStepNav } from "./onboarding/OnboardingStepNav";
|
|
|
19
19
|
import { PermissionsStep } from "./onboarding/PermissionsStep";
|
|
20
20
|
import { RpcStep } from "./onboarding/RpcStep";
|
|
21
21
|
|
|
22
|
+
const FORCE_VRM =
|
|
23
|
+
typeof window !== "undefined" &&
|
|
24
|
+
new URLSearchParams(window.location.search).get("test_force_vrm") === "1";
|
|
25
|
+
|
|
22
26
|
const DISABLE_ONBOARDING_VRM =
|
|
23
|
-
|
|
24
|
-
String(import.meta.env.VITE_E2E_DISABLE_VRM ?? "") === "
|
|
27
|
+
!FORCE_VRM &&
|
|
28
|
+
(String(import.meta.env.VITE_E2E_DISABLE_VRM ?? "").toLowerCase() === "true" ||
|
|
29
|
+
String(import.meta.env.VITE_E2E_DISABLE_VRM ?? "") === "1");
|
|
25
30
|
|
|
26
31
|
export function OnboardingWizard() {
|
|
27
32
|
const branding = useBranding();
|
|
28
33
|
const isEliza = branding.appName === "Eliza";
|
|
29
|
-
const disableVrm = DISABLE_ONBOARDING_VRM || isEliza;
|
|
34
|
+
const disableVrm = !FORCE_VRM && (DISABLE_ONBOARDING_VRM || isEliza);
|
|
30
35
|
const [revealStarted, setRevealStarted] = useState(disableVrm);
|
|
31
36
|
|
|
32
37
|
const {
|
|
@@ -36,6 +41,7 @@ export function OnboardingWizard() {
|
|
|
36
41
|
uiLanguage,
|
|
37
42
|
uiTheme,
|
|
38
43
|
setState,
|
|
44
|
+
handleOnboardingNext,
|
|
39
45
|
t,
|
|
40
46
|
} = useApp();
|
|
41
47
|
|
|
@@ -62,10 +68,25 @@ export function OnboardingWizard() {
|
|
|
62
68
|
};
|
|
63
69
|
}, [uiTheme]);
|
|
64
70
|
|
|
71
|
+
// Auto-advance past the wakeUp splash once the VRM reveal animation starts,
|
|
72
|
+
// or after a 4-second timeout to prevent getting stuck when VRM fails to load.
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (onboardingStep !== "wakeUp") return;
|
|
75
|
+
if (revealStarted) {
|
|
76
|
+
handleOnboardingNext();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const timer = setTimeout(() => {
|
|
80
|
+
setRevealStarted(true);
|
|
81
|
+
}, 4000);
|
|
82
|
+
return () => clearTimeout(timer);
|
|
83
|
+
}, [onboardingStep, revealStarted, handleOnboardingNext]);
|
|
84
|
+
|
|
65
85
|
function renderStep() {
|
|
66
86
|
switch (onboardingStep) {
|
|
67
87
|
case "identity":
|
|
68
|
-
|
|
88
|
+
// Rendered outside the panel in OnboardingWizard JSX
|
|
89
|
+
return null;
|
|
69
90
|
case "connection":
|
|
70
91
|
return <ConnectionStep />;
|
|
71
92
|
case "rpc":
|
|
@@ -108,6 +129,7 @@ export function OnboardingWizard() {
|
|
|
108
129
|
)}
|
|
109
130
|
|
|
110
131
|
<div
|
|
132
|
+
data-testid="onboarding-ui-overlay"
|
|
111
133
|
style={{
|
|
112
134
|
position: "absolute",
|
|
113
135
|
inset: 0,
|
|
@@ -210,9 +232,16 @@ export function OnboardingWizard() {
|
|
|
210
232
|
{/* ── Standard overlaid UI — step nav + content panel ── */}
|
|
211
233
|
<div className="onboarding-ui-overlay">
|
|
212
234
|
<OnboardingStepNav />
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
235
|
+
{onboardingStep === "identity" ? (
|
|
236
|
+
/* Identity step renders full-width at the bottom — no glass panel */
|
|
237
|
+
<div className="ob-identity-overlay">
|
|
238
|
+
<IdentityStep />
|
|
239
|
+
</div>
|
|
240
|
+
) : (
|
|
241
|
+
<OnboardingPanel step={onboardingStep}>
|
|
242
|
+
{renderStep()}
|
|
243
|
+
</OnboardingPanel>
|
|
244
|
+
)}
|
|
216
245
|
</div>
|
|
217
246
|
</div>
|
|
218
247
|
</div>
|
|
@@ -9,6 +9,7 @@ import { isNative } from "@elizaos/app-core/platform";
|
|
|
9
9
|
import { getProviderLogo } from "@elizaos/app-core/providers";
|
|
10
10
|
import { useApp } from "@elizaos/app-core/state";
|
|
11
11
|
import { openExternalUrl } from "@elizaos/app-core/utils";
|
|
12
|
+
import { ONBOARDING_PROVIDER_CATALOG } from "@elizaos/autonomous/contracts/onboarding";
|
|
12
13
|
import { useEffect, useState } from "react";
|
|
13
14
|
|
|
14
15
|
function formatRequestError(err: unknown): string {
|
|
@@ -151,7 +152,13 @@ export function ConnectionStep() {
|
|
|
151
152
|
setState("onboardingOpenRouterModel", modelId);
|
|
152
153
|
};
|
|
153
154
|
|
|
154
|
-
|
|
155
|
+
// Use the static provider catalog shipped in the frontend bundle — no API
|
|
156
|
+
// call needed. Runtime overrides from `onboardingOptions` (if available)
|
|
157
|
+
// take precedence so the server can still augment the list when present.
|
|
158
|
+
const catalogProviders: ProviderOption[] =
|
|
159
|
+
(onboardingOptions?.providers as ProviderOption[] | undefined)?.length
|
|
160
|
+
? (onboardingOptions!.providers as ProviderOption[])
|
|
161
|
+
: ([...ONBOARDING_PROVIDER_CATALOG] as unknown as ProviderOption[]);
|
|
155
162
|
// Merge custom providers from branding (app-injected) with the catalog.
|
|
156
163
|
// Custom providers are appended; duplicates (by id) are skipped.
|
|
157
164
|
const customProviders = branding.customProviders ?? [];
|
|
@@ -1,29 +1,36 @@
|
|
|
1
|
-
import { client } from "@elizaos/app-core/api";
|
|
2
1
|
import { useApp } from "@elizaos/app-core/state";
|
|
2
|
+
import { getVrmPreviewUrl } from "@elizaos/app-core/state";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import {
|
|
5
|
-
CharacterRoster,
|
|
6
5
|
CHARACTER_PRESET_META,
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
SLANT_CLIP,
|
|
7
|
+
INSET_CLIP,
|
|
9
8
|
} from "../CharacterRoster";
|
|
10
9
|
|
|
10
|
+
/* ── Hardcoded frontend presets — no server needed ─────────────── */
|
|
11
|
+
|
|
12
|
+
const FRONTEND_PRESETS = Object.entries(CHARACTER_PRESET_META).map(
|
|
13
|
+
([catchphrase, meta]) => ({
|
|
14
|
+
id: catchphrase,
|
|
15
|
+
name: meta.name,
|
|
16
|
+
avatarIndex: meta.avatarIndex,
|
|
17
|
+
voicePresetId: meta.voicePresetId,
|
|
18
|
+
catchphrase,
|
|
19
|
+
}),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
type PresetEntry = (typeof FRONTEND_PRESETS)[number];
|
|
23
|
+
|
|
11
24
|
export function IdentityStep() {
|
|
12
25
|
const {
|
|
13
|
-
onboardingOptions,
|
|
14
26
|
onboardingStyle,
|
|
15
27
|
handleOnboardingNext,
|
|
16
28
|
setState,
|
|
17
29
|
t,
|
|
18
30
|
} = useApp();
|
|
19
31
|
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const rosterEntries = useMemo(
|
|
24
|
-
() => resolveRosterEntries(styles).slice(0, 4),
|
|
25
|
-
[styles],
|
|
26
|
-
);
|
|
32
|
+
const entries = FRONTEND_PRESETS;
|
|
33
|
+
const selectedId = onboardingStyle || entries[0]?.catchphrase || "";
|
|
27
34
|
|
|
28
35
|
/* ── Import / restore state ─────────────────────────────────────── */
|
|
29
36
|
const [showImport, setShowImport] = useState(false);
|
|
@@ -34,6 +41,22 @@ export function IdentityStep() {
|
|
|
34
41
|
const [importSuccess, setImportSuccess] = useState<string | null>(null);
|
|
35
42
|
const importBusyRef = useRef(false);
|
|
36
43
|
|
|
44
|
+
const handleSelect = useCallback(
|
|
45
|
+
(entry: PresetEntry) => {
|
|
46
|
+
setState("onboardingStyle", entry.catchphrase);
|
|
47
|
+
setState("onboardingName", entry.name);
|
|
48
|
+
setState("selectedVrmIndex", entry.avatarIndex);
|
|
49
|
+
},
|
|
50
|
+
[setState],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Auto-select the first one if nothing is selected yet
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!onboardingStyle && entries.length > 0) {
|
|
56
|
+
handleSelect(entries[0]);
|
|
57
|
+
}
|
|
58
|
+
}, [onboardingStyle, entries, handleSelect]);
|
|
59
|
+
|
|
37
60
|
const handleImportAgent = useCallback(async () => {
|
|
38
61
|
if (importBusyRef.current || importBusy) return;
|
|
39
62
|
if (!importFile) {
|
|
@@ -49,6 +72,8 @@ export function IdentityStep() {
|
|
|
49
72
|
setImportBusy(true);
|
|
50
73
|
setImportError(null);
|
|
51
74
|
setImportSuccess(null);
|
|
75
|
+
// Dynamic import to avoid hard dependency on client when server is absent
|
|
76
|
+
const { client } = await import("@elizaos/app-core/api");
|
|
52
77
|
const fileBuffer = await importFile.arrayBuffer();
|
|
53
78
|
const result = await client.importAgent(importPassword, fileBuffer);
|
|
54
79
|
const counts = result.counts;
|
|
@@ -75,25 +100,6 @@ export function IdentityStep() {
|
|
|
75
100
|
}
|
|
76
101
|
}, [importBusy, importFile, importPassword, t]);
|
|
77
102
|
|
|
78
|
-
const handleSelect = useCallback(
|
|
79
|
-
(entry: CharacterRosterEntry) => {
|
|
80
|
-
setState("onboardingStyle", entry.id);
|
|
81
|
-
const meta = CHARACTER_PRESET_META[entry.id];
|
|
82
|
-
if (meta) {
|
|
83
|
-
setState("onboardingName", meta.name);
|
|
84
|
-
setState("selectedVrmIndex", meta.avatarIndex);
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
[setState],
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
// Auto-select the first one if nothing is selected yet
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
if (!onboardingStyle && rosterEntries.length > 0) {
|
|
93
|
-
handleSelect(rosterEntries[0]);
|
|
94
|
-
}
|
|
95
|
-
}, [onboardingStyle, rosterEntries, handleSelect]);
|
|
96
|
-
|
|
97
103
|
/* ── Import UI ──────────────────────────────────────────────────── */
|
|
98
104
|
if (showImport) {
|
|
99
105
|
return (
|
|
@@ -166,38 +172,80 @@ export function IdentityStep() {
|
|
|
166
172
|
);
|
|
167
173
|
}
|
|
168
174
|
|
|
169
|
-
/* ──
|
|
175
|
+
/* ── Overwatch-style character select — full-width bottom bar ──── */
|
|
176
|
+
const selected = entries.find((e) => e.catchphrase === selectedId);
|
|
177
|
+
|
|
170
178
|
return (
|
|
171
|
-
<div className="
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
179
|
+
<div className="ob-identity">
|
|
180
|
+
{/* Selected character info — floats above the roster */}
|
|
181
|
+
<div className="ob-identity-info">
|
|
182
|
+
<div className="ob-identity-name">{selected?.name ?? ""}</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{/* ── Roster bar ── */}
|
|
186
|
+
<div className="ob-identity-roster">
|
|
187
|
+
{entries.map((entry) => {
|
|
188
|
+
const isSelected = selectedId === entry.catchphrase;
|
|
189
|
+
return (
|
|
190
|
+
<button
|
|
191
|
+
key={entry.catchphrase}
|
|
192
|
+
type="button"
|
|
193
|
+
className={`ob-identity-card ${isSelected ? "ob-identity-card--active" : ""}`}
|
|
194
|
+
onClick={() => handleSelect(entry)}
|
|
195
|
+
data-testid={`onboarding-preset-${entry.catchphrase}`}
|
|
196
|
+
>
|
|
197
|
+
<div
|
|
198
|
+
className={`ob-identity-card-frame ${isSelected ? "ob-identity-card-frame--active" : ""}`}
|
|
199
|
+
style={{ clipPath: SLANT_CLIP }}
|
|
200
|
+
>
|
|
201
|
+
<div
|
|
202
|
+
className="ob-identity-card-inner"
|
|
203
|
+
style={{ clipPath: SLANT_CLIP }}
|
|
204
|
+
>
|
|
205
|
+
{isSelected && (
|
|
206
|
+
<div
|
|
207
|
+
className="pointer-events-none absolute -inset-3 bg-yellow-300/15 blur-xl"
|
|
208
|
+
style={{ clipPath: SLANT_CLIP }}
|
|
209
|
+
/>
|
|
210
|
+
)}
|
|
211
|
+
<img
|
|
212
|
+
src={getVrmPreviewUrl(entry.avatarIndex)}
|
|
213
|
+
alt={entry.name}
|
|
214
|
+
draggable={false}
|
|
215
|
+
className={`ob-identity-card-img ${isSelected ? "ob-identity-card-img--active" : ""}`}
|
|
216
|
+
/>
|
|
217
|
+
<div className="ob-identity-card-label">
|
|
218
|
+
<div
|
|
219
|
+
className={`ob-identity-card-name ${isSelected ? "ob-identity-card-name--active" : ""}`}
|
|
220
|
+
style={{ clipPath: INSET_CLIP }}
|
|
221
|
+
>
|
|
222
|
+
{entry.name}
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</button>
|
|
228
|
+
);
|
|
229
|
+
})}
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
{/* ── Actions row ── */}
|
|
233
|
+
<div className="ob-identity-actions">
|
|
234
|
+
<button
|
|
235
|
+
className="onboarding-confirm-btn"
|
|
236
|
+
onClick={() => handleOnboardingNext()}
|
|
237
|
+
type="button"
|
|
238
|
+
>
|
|
239
|
+
Continue
|
|
240
|
+
</button>
|
|
241
|
+
<button
|
|
242
|
+
type="button"
|
|
243
|
+
onClick={() => setShowImport(true)}
|
|
244
|
+
className="ob-identity-restore"
|
|
245
|
+
>
|
|
246
|
+
{t("onboarding.restoreFromBackup")}
|
|
247
|
+
</button>
|
|
248
|
+
</div>
|
|
201
249
|
</div>
|
|
202
250
|
);
|
|
203
251
|
}
|
|
@@ -31,8 +31,9 @@
|
|
|
31
31
|
.onboarding-bg-overlay {
|
|
32
32
|
position: absolute;
|
|
33
33
|
inset: 0;
|
|
34
|
-
background:
|
|
34
|
+
background: none;
|
|
35
35
|
z-index: 1;
|
|
36
|
+
pointer-events: none;
|
|
36
37
|
}
|
|
37
38
|
@keyframes onboarding-bg-drift {
|
|
38
39
|
0% {
|
|
@@ -719,6 +720,212 @@
|
|
|
719
720
|
letter-spacing: 0.05em;
|
|
720
721
|
}
|
|
721
722
|
|
|
723
|
+
/* === IDENTITY STEP — Overwatch-style bottom character select === */
|
|
724
|
+
|
|
725
|
+
/* Full-screen overlay container for identity step — no glass panel */
|
|
726
|
+
.ob-identity-overlay {
|
|
727
|
+
flex: 1;
|
|
728
|
+
display: flex;
|
|
729
|
+
align-items: flex-end;
|
|
730
|
+
justify-content: center;
|
|
731
|
+
padding-bottom: 0;
|
|
732
|
+
pointer-events: auto;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/* Root identity layout — stacks info, roster, actions at the bottom */
|
|
736
|
+
.ob-identity {
|
|
737
|
+
display: flex;
|
|
738
|
+
flex-direction: column;
|
|
739
|
+
align-items: center;
|
|
740
|
+
gap: 12px;
|
|
741
|
+
width: 100%;
|
|
742
|
+
animation: onboarding-content-fade-in 0.6s ease both;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/* Selected character info */
|
|
746
|
+
.ob-identity-info {
|
|
747
|
+
text-align: center;
|
|
748
|
+
animation: onboarding-content-fade-in 0.5s ease 0.1s both;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.ob-identity-name {
|
|
752
|
+
font-size: 28px;
|
|
753
|
+
font-weight: 700;
|
|
754
|
+
letter-spacing: 0.12em;
|
|
755
|
+
text-transform: uppercase;
|
|
756
|
+
color: rgba(240, 238, 250, 0.95);
|
|
757
|
+
text-shadow:
|
|
758
|
+
0 0 30px rgba(240, 185, 11, 0.3),
|
|
759
|
+
0 2px 12px rgba(3, 5, 10, 0.65);
|
|
760
|
+
transition: all 0.3s ease;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
.ob-identity-catchphrase {
|
|
764
|
+
font-size: 14px;
|
|
765
|
+
font-style: italic;
|
|
766
|
+
color: rgba(240, 185, 11, 0.7);
|
|
767
|
+
letter-spacing: 0.05em;
|
|
768
|
+
margin-top: 2px;
|
|
769
|
+
text-shadow: 0 2px 10px rgba(3, 5, 10, 0.55);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
.ob-identity-hint {
|
|
773
|
+
font-size: 12px;
|
|
774
|
+
color: rgba(240, 238, 250, 0.45);
|
|
775
|
+
margin-top: 8px;
|
|
776
|
+
letter-spacing: 0.04em;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/* ── Roster bar — horizontal row pinned to bottom ── */
|
|
780
|
+
.ob-identity-roster {
|
|
781
|
+
display: flex;
|
|
782
|
+
align-items: flex-end;
|
|
783
|
+
justify-content: center;
|
|
784
|
+
gap: 0;
|
|
785
|
+
width: 100%;
|
|
786
|
+
max-width: 900px;
|
|
787
|
+
padding: 0 2rem;
|
|
788
|
+
animation: ob-roster-slide-up 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) 0.15s both;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
@keyframes ob-roster-slide-up {
|
|
792
|
+
from {
|
|
793
|
+
opacity: 0;
|
|
794
|
+
transform: translateY(40px);
|
|
795
|
+
}
|
|
796
|
+
to {
|
|
797
|
+
opacity: 1;
|
|
798
|
+
transform: translateY(0);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/* Individual character card */
|
|
803
|
+
.ob-identity-card {
|
|
804
|
+
position: relative;
|
|
805
|
+
flex: 1 1 0;
|
|
806
|
+
max-width: 110px;
|
|
807
|
+
min-width: 0;
|
|
808
|
+
text-align: center;
|
|
809
|
+
transition: all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
810
|
+
cursor: pointer;
|
|
811
|
+
border: none;
|
|
812
|
+
background: none;
|
|
813
|
+
padding: 0;
|
|
814
|
+
margin: 0 -4px;
|
|
815
|
+
opacity: 0.5;
|
|
816
|
+
transform: translateY(12px);
|
|
817
|
+
filter: brightness(0.7);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
.ob-identity-card:hover {
|
|
821
|
+
opacity: 0.85;
|
|
822
|
+
transform: translateY(4px);
|
|
823
|
+
filter: brightness(0.9);
|
|
824
|
+
z-index: 5;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.ob-identity-card--active {
|
|
828
|
+
opacity: 1;
|
|
829
|
+
z-index: 10;
|
|
830
|
+
transform: translateY(-8px);
|
|
831
|
+
filter: brightness(1);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.ob-identity-card--active:hover {
|
|
835
|
+
transform: translateY(-8px);
|
|
836
|
+
filter: brightness(1);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/* Frame border */
|
|
840
|
+
.ob-identity-card-frame {
|
|
841
|
+
position: relative;
|
|
842
|
+
aspect-ratio: 14 / 18;
|
|
843
|
+
width: 100%;
|
|
844
|
+
padding: 2px;
|
|
845
|
+
transition: all 0.25s;
|
|
846
|
+
background: rgba(255, 255, 255, 0.08);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
.ob-identity-card-frame--active {
|
|
850
|
+
background: linear-gradient(180deg, rgba(240, 185, 11, 0.9), rgba(240, 185, 11, 0.5));
|
|
851
|
+
box-shadow:
|
|
852
|
+
0 0 24px rgba(240, 185, 11, 0.35),
|
|
853
|
+
0 4px 16px rgba(0, 0, 0, 0.3);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/* Inner image container */
|
|
857
|
+
.ob-identity-card-inner {
|
|
858
|
+
position: relative;
|
|
859
|
+
height: 100%;
|
|
860
|
+
width: 100%;
|
|
861
|
+
overflow: hidden;
|
|
862
|
+
background: rgba(10, 14, 20, 0.5);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
.ob-identity-card-img {
|
|
866
|
+
height: 100%;
|
|
867
|
+
width: 100%;
|
|
868
|
+
object-fit: cover;
|
|
869
|
+
object-position: center top;
|
|
870
|
+
transition: transform 0.25s ease-out;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.ob-identity-card-img--active {
|
|
874
|
+
transform: scale(1.06);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/* Name label at bottom of card */
|
|
878
|
+
.ob-identity-card-label {
|
|
879
|
+
position: absolute;
|
|
880
|
+
inset-inline: 0;
|
|
881
|
+
bottom: 0;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
.ob-identity-card-name {
|
|
885
|
+
padding: 3px 6px;
|
|
886
|
+
font-size: clamp(8px, 1.2vw, 11px);
|
|
887
|
+
font-weight: 600;
|
|
888
|
+
letter-spacing: 0.06em;
|
|
889
|
+
color: rgba(255, 255, 255, 0.8);
|
|
890
|
+
background: rgba(0, 0, 0, 0.55);
|
|
891
|
+
white-space: nowrap;
|
|
892
|
+
overflow: hidden;
|
|
893
|
+
text-overflow: ellipsis;
|
|
894
|
+
text-transform: uppercase;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
.ob-identity-card-name--active {
|
|
898
|
+
background: rgba(0, 0, 0, 0.75);
|
|
899
|
+
color: rgba(240, 185, 11, 0.95);
|
|
900
|
+
box-shadow: inset 0 1px 0 rgba(240, 185, 11, 0.15);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/* ── Actions row ── */
|
|
904
|
+
.ob-identity-actions {
|
|
905
|
+
display: flex;
|
|
906
|
+
flex-direction: column;
|
|
907
|
+
align-items: center;
|
|
908
|
+
gap: 8px;
|
|
909
|
+
padding-bottom: 24px;
|
|
910
|
+
animation: onboarding-content-fade-in 0.5s ease 0.3s both;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
.ob-identity-restore {
|
|
914
|
+
background: transparent;
|
|
915
|
+
border: none;
|
|
916
|
+
color: rgba(240, 238, 250, 0.35);
|
|
917
|
+
font-size: 11px;
|
|
918
|
+
cursor: pointer;
|
|
919
|
+
text-decoration: underline;
|
|
920
|
+
font-family: inherit;
|
|
921
|
+
padding: 4px 8px;
|
|
922
|
+
transition: color 0.3s;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
.ob-identity-restore:hover {
|
|
926
|
+
color: rgba(240, 238, 250, 0.65);
|
|
927
|
+
}
|
|
928
|
+
|
|
722
929
|
/* === RESPONSIVE === */
|
|
723
930
|
@media (max-width: 768px) {
|
|
724
931
|
.onboarding-ui-overlay {
|
|
@@ -748,4 +955,22 @@
|
|
|
748
955
|
.onboarding-right {
|
|
749
956
|
padding: 16px;
|
|
750
957
|
}
|
|
958
|
+
|
|
959
|
+
/* Identity bottom bar responsive */
|
|
960
|
+
.ob-identity-roster {
|
|
961
|
+
padding: 0 0.5rem;
|
|
962
|
+
max-width: 100%;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
.ob-identity-card {
|
|
966
|
+
max-width: 80px;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
.ob-identity-name {
|
|
970
|
+
font-size: 20px;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
.ob-identity-actions {
|
|
974
|
+
padding-bottom: 16px;
|
|
975
|
+
}
|
|
751
976
|
}
|