Enhance audio and visual elements in the UI for improved user experience
- Added a new script in package.json to generate welcome speech audio for enhanced onboarding. - Updated SplashScreen.vue and OnboardingWrapper.vue to use the new intro background image and poster. - Modified Dashboard.vue and Login.vue to reflect changes in background images for consistency. - Removed outdated background images and updated references to ensure a cohesive visual theme. - Improved tap-to-start feature with new text and logo in SplashScreen.vue for better engagement. - Enhanced audio playback functionality in useLoginSounds.ts to include welcome speech.
@@ -183,7 +183,7 @@ mv video-intro-compressed.mp4 video-intro.mp4
|
|||||||
## Performance Tips
|
## Performance Tips
|
||||||
|
|
||||||
1. **Preload**: Add `preload="metadata"` to video tag (loads only metadata, not full video)
|
1. **Preload**: Add `preload="metadata"` to video tag (loads only metadata, not full video)
|
||||||
2. **Poster Image**: Add `poster="/assets/img/bg-4.jpg"` for instant display while loading
|
2. **Poster Image**: Add `poster="/assets/img/bg-intro.jpg"` for instant display while loading
|
||||||
3. **Lazy Load**: Consider loading video only when user reaches that screen
|
3. **Lazy Load**: Consider loading video only when user reaches that screen
|
||||||
4. **CDN**: Host video on CDN for faster delivery
|
4. **CDN**: Host video on CDN for faster delivery
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
"build:production": "NODE_ENV=production vue-tsc -b && vite build --mode production",
|
"build:production": "NODE_ENV=production vue-tsc -b && vite build --mode production",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"type-check": "vue-tsc --noEmit",
|
"type-check": "vue-tsc --noEmit",
|
||||||
"prebuild": "cp ../../loop-start.mp3 public/assets/audio/ 2>/dev/null || true"
|
"prebuild": "cp ../../loop-start.mp3 public/assets/audio/ 2>/dev/null || true",
|
||||||
|
"generate-welcome-speech": "node scripts/generate-welcome-speech.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dockerode": "^4.0.9",
|
"dockerode": "^4.0.9",
|
||||||
|
|||||||
82
neode-ui/public/assets/INTRO-ASSETS-REPLACE.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Replace Intro & Dashboard Backgrounds
|
||||||
|
|
||||||
|
To change the intro splash and dashboard tab backgrounds **without touching any code**, overwrite these files with your own assets. Use the exact names and locations below.
|
||||||
|
|
||||||
|
**Location:** All images go in `neode-ui/public/assets/img/`
|
||||||
|
**Format:** JPG recommended. Portrait or landscape; they use `background-size: cover` and `center center`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intro Background
|
||||||
|
|
||||||
|
| Filename | Used for |
|
||||||
|
|----------|----------|
|
||||||
|
| **`bg-intro.jpg`** | Intro splash (alien typing + video poster + fallback), Dashboard default |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intro Video
|
||||||
|
|
||||||
|
| Filename | Where | Used for |
|
||||||
|
|----------|-------|----------|
|
||||||
|
| **`video-intro.mp4`** | `neode-ui/public/assets/video/` | Welcome Noderunner + logo, onboarding, login |
|
||||||
|
|
||||||
|
**Format:** MP4 (H.264). Keep under ~5MB for web. See `VIDEO_COMPRESSION_GUIDE.md` for optimization.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dashboard Tab Backgrounds
|
||||||
|
|
||||||
|
| Filename | Tab |
|
||||||
|
|----------|-----|
|
||||||
|
| **`bg-home.jpg`** | Home |
|
||||||
|
| **`bg-web5.jpg`** | Web5 |
|
||||||
|
| **`bg-network.jpg`** | Server / Network |
|
||||||
|
| **`bg-settings.jpg`** | Settings |
|
||||||
|
| **`bg-myapps.jpg`** | My Apps |
|
||||||
|
| **`bg-appstore.jpg`** | App Store / Marketplace |
|
||||||
|
| **`bg-cloud.jpg`** | Cloud |
|
||||||
|
| **`bg-intro.jpg`** | Default (also intro) |
|
||||||
|
| **`bg-intro-3.jpg`** | Alternate layer during transitions |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intro Flow Backgrounds (onboarding)
|
||||||
|
|
||||||
|
| Filename | Used for |
|
||||||
|
|----------|----------|
|
||||||
|
| **`bg-intro-1.jpg`** | Onboarding done, login |
|
||||||
|
| **`bg-intro-2.jpg`** | Onboarding verify |
|
||||||
|
| **`bg-intro-3.jpg`** | Onboarding path, dashboard transition layer |
|
||||||
|
| **`bg-intro-4.jpg`** | Onboarding options |
|
||||||
|
| **`bg-intro-5.jpg`** | Onboarding did |
|
||||||
|
| **`bg-intro-6.jpg`** | Onboarding backup |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| Asset | Full path |
|
||||||
|
|-------|-----------|
|
||||||
|
| Intro image | `neode-ui/public/assets/img/bg-intro.jpg` |
|
||||||
|
| Intro video | `neode-ui/public/assets/video/video-intro.mp4` |
|
||||||
|
| Home | `neode-ui/public/assets/img/bg-home.jpg` |
|
||||||
|
| Web5 | `neode-ui/public/assets/img/bg-web5.jpg` |
|
||||||
|
| Network | `neode-ui/public/assets/img/bg-network.jpg` |
|
||||||
|
| Settings | `neode-ui/public/assets/img/bg-settings.jpg` |
|
||||||
|
| My Apps | `neode-ui/public/assets/img/bg-myapps.jpg` |
|
||||||
|
| App Store | `neode-ui/public/assets/img/bg-appstore.jpg` |
|
||||||
|
| Cloud | `neode-ui/public/assets/img/bg-cloud.jpg` |
|
||||||
|
| Default | `neode-ui/public/assets/img/bg-intro.jpg` |
|
||||||
|
| Transition | `neode-ui/public/assets/img/bg-intro-3.jpg` |
|
||||||
|
| Intro 1–6 | `neode-ui/public/assets/img/bg-intro-1.jpg` … `bg-intro-6.jpg` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Steps to Replace
|
||||||
|
|
||||||
|
1. Put your images in `neode-ui/public/assets/img/` with the exact filenames above.
|
||||||
|
2. Put your video in `neode-ui/public/assets/video/video-intro.mp4`.
|
||||||
|
3. Run `npm run build` (or deploy) so the new assets are included.
|
||||||
|
|
||||||
|
No code changes required.
|
||||||
23
neode-ui/public/assets/audio/README-welcome-speech.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Welcome Noderunner Speech
|
||||||
|
|
||||||
|
The intro plays a sci-fi female voice saying "Welcome Noderunner" as the text types in.
|
||||||
|
|
||||||
|
## Generate the audio (ElevenLabs)
|
||||||
|
|
||||||
|
1. Get a free API key at [elevenlabs.io](https://elevenlabs.io) (free tier: 10k chars/month)
|
||||||
|
2. Run:
|
||||||
|
```bash
|
||||||
|
cd neode-ui
|
||||||
|
ELEVENLABS_API_KEY=your_key npm run generate-welcome-speech
|
||||||
|
```
|
||||||
|
3. Commit `welcome-noderunner.mp3` to the repo
|
||||||
|
|
||||||
|
## Custom sci-fi voice
|
||||||
|
|
||||||
|
Browse [ElevenLabs Voice Library](https://elevenlabs.io/voice-library) and search for "sci-fi", "AI", "robot", or "character". Copy the voice ID from the URL or voice settings, then:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ELEVENLABS_API_KEY=your_key ELEVENLABS_VOICE_ID=voice_id npm run generate-welcome-speech
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended: "The Digital Oracle", "The Friendly AI Assistant", or similar character voices from the Synthetic/Character categories.
|
||||||
BIN
neode-ui/public/assets/audio/welcome-noderunner.mp3
Normal file
|
Before Width: | Height: | Size: 494 KiB |
|
Before Width: | Height: | Size: 648 KiB |
|
Before Width: | Height: | Size: 716 KiB After Width: | Height: | Size: 1019 KiB |
|
Before Width: | Height: | Size: 494 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 1019 KiB |
|
Before Width: | Height: | Size: 716 KiB After Width: | Height: | Size: 1016 KiB |
|
Before Width: | Height: | Size: 494 KiB After Width: | Height: | Size: 494 KiB |
|
Before Width: | Height: | Size: 494 KiB After Width: | Height: | Size: 494 KiB |
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 494 KiB After Width: | Height: | Size: 494 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 157 KiB |
BIN
neode-ui/public/assets/img/bg-intro.jpg
Normal file
|
After Width: | Height: | Size: 999 KiB |
|
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 999 KiB |
|
Before Width: | Height: | Size: 620 KiB After Width: | Height: | Size: 976 KiB |
|
Before Width: | Height: | Size: 702 KiB After Width: | Height: | Size: 901 KiB |
|
Before Width: | Height: | Size: 570 KiB After Width: | Height: | Size: 999 KiB |
77
neode-ui/scripts/generate-welcome-speech.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Generate "Welcome Noderunner" speech using ElevenLabs AI voice.
|
||||||
|
* Slower, softer, sci-fi style with reverb/echo effects.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ELEVENLABS_API_KEY=your_key node scripts/generate-welcome-speech.js
|
||||||
|
*
|
||||||
|
* Optional voice ID (browse https://elevenlabs.io/voice-library/sensual):
|
||||||
|
* ELEVENLABS_VOICE_ID=voice_id node scripts/generate-welcome-speech.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { writeFileSync, mkdirSync, readFileSync, unlinkSync } from 'fs'
|
||||||
|
import { dirname, join } from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import { execSync } from 'child_process'
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
const API_KEY = process.env.ELEVENLABS_API_KEY
|
||||||
|
// Sarah - mature, reassuring, confident female (softer than Rachel)
|
||||||
|
const VOICE_ID = process.env.ELEVENLABS_VOICE_ID || 'EXAVITQu4vr4xnSDxMaL'
|
||||||
|
const OUTPUT_PATH = join(__dirname, '../public/assets/audio/welcome-noderunner.mp3')
|
||||||
|
const RAW_PATH = join(__dirname, '../public/assets/audio/welcome-noderunner-raw.mp3')
|
||||||
|
|
||||||
|
if (!API_KEY) {
|
||||||
|
console.error('Set ELEVENLABS_API_KEY (get a free key at elevenlabs.io)')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slower (0.78), softer (higher stability 0.65), more expressive (style 0.6)
|
||||||
|
const res = await fetch(
|
||||||
|
`https://api.elevenlabs.io/v1/text-to-speech/${VOICE_ID}?output_format=mp3_44100_128`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'xi-api-key': API_KEY,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
text: 'Welcome Noderunner',
|
||||||
|
model_id: 'eleven_multilingual_v2',
|
||||||
|
voice_settings: {
|
||||||
|
stability: 0.65,
|
||||||
|
similarity_boost: 0.8,
|
||||||
|
style: 0.6,
|
||||||
|
use_speaker_boost: true,
|
||||||
|
speed: 0.7,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const err = await res.text()
|
||||||
|
console.error('ElevenLabs API error:', res.status, err)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const buf = Buffer.from(await res.arrayBuffer())
|
||||||
|
mkdirSync(dirname(OUTPUT_PATH), { recursive: true })
|
||||||
|
writeFileSync(RAW_PATH, buf)
|
||||||
|
|
||||||
|
// Add sci-fi reverb: dense short delays that blend (no distinct echo)
|
||||||
|
try {
|
||||||
|
execSync(
|
||||||
|
`ffmpeg -y -i "${RAW_PATH}" -af "aecho=0.6:0.15:25|45|70:0.55|0.45|0.35,highpass=f=80,equalizer=f=4000:t=q:w=1:g=-1" -q:a 2 "${OUTPUT_PATH}" 2>/dev/null`,
|
||||||
|
{ stdio: 'pipe' }
|
||||||
|
)
|
||||||
|
unlinkSync(RAW_PATH)
|
||||||
|
} catch {
|
||||||
|
writeFileSync(OUTPUT_PATH, buf)
|
||||||
|
try { unlinkSync(RAW_PATH) } catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Generated:', OUTPUT_PATH)
|
||||||
|
console.log('Add this file to git and deploy.')
|
||||||
@@ -12,14 +12,14 @@
|
|||||||
muted
|
muted
|
||||||
playsinline
|
playsinline
|
||||||
preload="auto"
|
preload="auto"
|
||||||
poster="/assets/img/bg-4.jpg"
|
poster="/assets/img/bg-intro.jpg"
|
||||||
>
|
>
|
||||||
<source src="/assets/video/video-intro.mp4?v=7" type="video/mp4">
|
<source src="/assets/video/video-intro.mp4?v=7" type="video/mp4">
|
||||||
<!-- Fallback to image if video fails -->
|
<!-- Fallback to image if video fails -->
|
||||||
<div
|
<div
|
||||||
class="absolute inset-0"
|
class="absolute inset-0"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: 'url(/assets/img/bg-4.jpg)',
|
backgroundImage: 'url(/assets/img/bg-intro.jpg)',
|
||||||
backgroundSize: 'auto 100vh',
|
backgroundSize: 'auto 100vh',
|
||||||
backgroundPosition: 'center top',
|
backgroundPosition: 'center top',
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
v-else
|
v-else
|
||||||
class="absolute inset-0"
|
class="absolute inset-0"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: 'url(/assets/img/bg-4.jpg)',
|
backgroundImage: 'url(/assets/img/bg-intro.jpg)',
|
||||||
backgroundSize: 'auto 100vh',
|
backgroundSize: 'auto 100vh',
|
||||||
backgroundPosition: 'center top',
|
backgroundPosition: 'center top',
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
@@ -96,15 +96,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<!-- Tap to start - required for audio (browser autoplay policy) -->
|
<!-- Tap to start - logo + "Enter the Exit" behind (like screensaver) -->
|
||||||
<div
|
<div
|
||||||
v-if="showTapToStart"
|
v-if="showTapToStart"
|
||||||
class="absolute inset-0 z-[100] flex items-center justify-center bg-black/40 cursor-pointer"
|
class="absolute inset-0 z-[100] flex items-center justify-center bg-black/40 cursor-pointer"
|
||||||
@click="handleTapToStart"
|
@click="handleTapToStart"
|
||||||
>
|
>
|
||||||
<p class="font-mono text-white/90 text-lg sm:text-xl px-6 py-4 rounded-lg border border-white/20 bg-black/30 backdrop-blur-sm">
|
<div class="tap-to-start-content relative flex items-center justify-center">
|
||||||
Tap to start
|
<span class="tap-to-start-text font-archipelago font-extrabold text-[rgba(0,0,0,0.35)] text-6xl sm:text-7xl md:text-8xl lg:text-9xl tracking-widest uppercase whitespace-nowrap select-none">
|
||||||
</p>
|
Enter the Exit
|
||||||
|
</span>
|
||||||
|
<div class="tap-to-start-logo absolute">
|
||||||
|
<ScreensaverLogo />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Skip Button -->
|
<!-- Skip Button -->
|
||||||
@@ -121,7 +126,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||||
import { playIntroTyping, playLoopStart, resumeAudioContext, startSynthwave, stopIntroTyping } from '@/composables/useLoginSounds'
|
import ScreensaverLogo from '@/components/ScreensaverLogo.vue'
|
||||||
|
import { playIntroTyping, playLoopStart, playWelcomeNoderunnerSpeech, resumeAudioContext, startSynthwave, stopIntroTyping } from '@/composables/useLoginSounds'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
complete: []
|
complete: []
|
||||||
@@ -250,7 +256,8 @@ function skipIntro() {
|
|||||||
stopIntroTyping()
|
stopIntroTyping()
|
||||||
playLoopStart()
|
playLoopStart()
|
||||||
startSynthwave()
|
startSynthwave()
|
||||||
|
playWelcomeNoderunnerSpeech()
|
||||||
|
|
||||||
// Stop alien intro typing and any playing typing sound
|
// Stop alien intro typing and any playing typing sound
|
||||||
stopIntroTyping()
|
stopIntroTyping()
|
||||||
isTypingLine1.value = false
|
isTypingLine1.value = false
|
||||||
@@ -357,6 +364,7 @@ function startAlienIntro() {
|
|||||||
stopIntroTyping()
|
stopIntroTyping()
|
||||||
playLoopStart()
|
playLoopStart()
|
||||||
startSynthwave()
|
startSynthwave()
|
||||||
|
playWelcomeNoderunnerSpeech()
|
||||||
if (videoElement.value) {
|
if (videoElement.value) {
|
||||||
videoElement.value.play().catch(err => {
|
videoElement.value.play().catch(err => {
|
||||||
console.warn('Video autoplay failed on welcome:', err)
|
console.warn('Video autoplay failed on welcome:', err)
|
||||||
@@ -564,5 +572,44 @@ onBeforeUnmount(() => {
|
|||||||
.bg-zoom-transition.bg-zoom-in {
|
.bg-zoom-transition.bg-zoom-in {
|
||||||
transform: scale(1.15);
|
transform: scale(1.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tap to start - "Enter the Exit" big behind logo */
|
||||||
|
.tap-to-start-content {
|
||||||
|
min-height: 12rem;
|
||||||
|
}
|
||||||
|
.tap-to-start-text {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.tap-to-start-logo {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.tap-to-start-logo {
|
||||||
|
filter: drop-shadow(0 0 40px rgba(255, 255, 255, 0.15));
|
||||||
|
}
|
||||||
|
.tap-to-start-logo :deep(.logo-gradient-border) {
|
||||||
|
width: 12rem;
|
||||||
|
height: 12rem;
|
||||||
|
}
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.tap-to-start-content {
|
||||||
|
min-height: 14rem;
|
||||||
|
}
|
||||||
|
.tap-to-start-logo :deep(.logo-gradient-border) {
|
||||||
|
width: 14rem;
|
||||||
|
height: 14rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.tap-to-start-content {
|
||||||
|
min-height: 16rem;
|
||||||
|
}
|
||||||
|
.tap-to-start-logo :deep(.logo-gradient-border) {
|
||||||
|
width: 16rem;
|
||||||
|
height: 16rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,18 @@ export function stopIntroTyping() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WELCOME_SPEECH_URL = '/assets/audio/welcome-noderunner.mp3'
|
||||||
|
|
||||||
|
/** Sci-fi female voice: "Welcome Noderunner" - plays when welcome text types in.
|
||||||
|
* Requires pre-recorded audio from ElevenLabs. Run:
|
||||||
|
* ELEVENLABS_API_KEY=your_key node neode-ui/scripts/generate-welcome-speech.js
|
||||||
|
* Browse sci-fi voices at elevenlabs.io/voice-library and set ELEVENLABS_VOICE_ID for custom voice. */
|
||||||
|
export function playWelcomeNoderunnerSpeech() {
|
||||||
|
const audio = new Audio(WELCOME_SPEECH_URL)
|
||||||
|
audio.volume = 0.9
|
||||||
|
audio.play().catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
/** Typing tick - for dashboard welcome typing (typing.mp3) */
|
/** Typing tick - for dashboard welcome typing (typing.mp3) */
|
||||||
let typingTickPool: HTMLAudioElement[] = []
|
let typingTickPool: HTMLAudioElement[] = []
|
||||||
const TYPING_TICK_POOL_SIZE = 5
|
const TYPING_TICK_POOL_SIZE = 5
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ const currentBackgroundImage = computed(() => {
|
|||||||
if (showAppStoreBackground.value) return 'bg-appstore.jpg'
|
if (showAppStoreBackground.value) return 'bg-appstore.jpg'
|
||||||
if (showCloudBackground.value) return 'bg-cloud.jpg'
|
if (showCloudBackground.value) return 'bg-cloud.jpg'
|
||||||
if (showHomeBackground.value) return 'bg-home.jpg'
|
if (showHomeBackground.value) return 'bg-home.jpg'
|
||||||
return 'bg-4.jpg'
|
return 'bg-intro.jpg'
|
||||||
})
|
})
|
||||||
|
|
||||||
const altBackgroundImage = computed(() => {
|
const altBackgroundImage = computed(() => {
|
||||||
@@ -352,7 +352,7 @@ const altBackgroundImage = computed(() => {
|
|||||||
if (showAppStoreBackground.value) return 'bg-appstore.jpg'
|
if (showAppStoreBackground.value) return 'bg-appstore.jpg'
|
||||||
if (showCloudBackground.value) return 'bg-cloud.jpg'
|
if (showCloudBackground.value) return 'bg-cloud.jpg'
|
||||||
if (showHomeBackground.value) return 'bg-home.jpg'
|
if (showHomeBackground.value) return 'bg-home.jpg'
|
||||||
return 'bg-3.jpg'
|
return 'bg-intro-3.jpg'
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check if overlay should be dark (0.8 opacity)
|
// Check if overlay should be dark (0.8 opacity)
|
||||||
@@ -1442,7 +1442,7 @@ aside:not(.sidebar-animate) .sidebar-logout-btn {
|
|||||||
will-change: transform, opacity;
|
will-change: transform, opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Default state - bg-4 visible, bg-3 hidden back */
|
/* Default state - bg-intro visible, bg-intro-3 hidden back */
|
||||||
.dashboard-view .bg-layer:first-of-type {
|
.dashboard-view .bg-layer:first-of-type {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateZ(0) scale(1);
|
transform: translateZ(0) scale(1);
|
||||||
|
|||||||
@@ -8,12 +8,8 @@
|
|||||||
>
|
>
|
||||||
<!-- Logo - half in, half out of container -->
|
<!-- Logo - half in, half out of container -->
|
||||||
<div class="absolute -top-10 left-1/2 -translate-x-1/2 z-10">
|
<div class="absolute -top-10 left-1/2 -translate-x-1/2 z-10">
|
||||||
<div class="logo-gradient-border">
|
<div class="logo-gradient-border w-20 h-20">
|
||||||
<img
|
<AnimatedLogo no-border fit />
|
||||||
src="/assets/img/favico.svg"
|
|
||||||
alt="Archipelago"
|
|
||||||
class="w-20 h-20"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -136,6 +132,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import AnimatedLogo from '@/components/AnimatedLogo.vue'
|
||||||
import { useAppStore } from '../stores/app'
|
import { useAppStore } from '../stores/app'
|
||||||
import { useLoginTransitionStore } from '../stores/loginTransition'
|
import { useLoginTransitionStore } from '../stores/loginTransition'
|
||||||
import { rpcClient } from '../api/rpc-client'
|
import { rpcClient } from '../api/rpc-client'
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
muted
|
muted
|
||||||
playsinline
|
playsinline
|
||||||
preload="auto"
|
preload="auto"
|
||||||
poster="/assets/img/bg-4.jpg"
|
poster="/assets/img/bg-intro.jpg"
|
||||||
style="width: 100%; height: 100%; object-fit: cover; object-position: center; position: absolute; inset: 0; transform: scale(1); transition: none;"
|
style="width: 100%; height: 100%; object-fit: cover; object-position: center; position: absolute; inset: 0; transform: scale(1); transition: none;"
|
||||||
@pause.prevent="handleVideoPause"
|
@pause.prevent="handleVideoPause"
|
||||||
@ended="handleVideoEnded"
|
@ended="handleVideoEnded"
|
||||||
@@ -66,7 +66,7 @@ import { useRoute } from 'vue-router'
|
|||||||
import { resumeAudioContext, startSynthwave } from '@/composables/useLoginSounds'
|
import { resumeAudioContext, startSynthwave } from '@/composables/useLoginSounds'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const currentBackground = ref('bg-4.jpg')
|
const currentBackground = ref('bg-intro.jpg')
|
||||||
const isGlitching = ref(false)
|
const isGlitching = ref(false)
|
||||||
const isTransitioning = ref(false)
|
const isTransitioning = ref(false)
|
||||||
const videoElement = ref<HTMLVideoElement | null>(null)
|
const videoElement = ref<HTMLVideoElement | null>(null)
|
||||||
@@ -83,19 +83,19 @@ const useVideoBackground = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Map each route to a specific background image
|
// Map each route to a specific background image
|
||||||
// Note: bg-4.jpg is used for splash and /onboarding/intro for seamless transition
|
// Note: bg-intro.jpg is used for splash and /onboarding/intro for seamless transition
|
||||||
const routeBackgrounds: Record<string, string> = {
|
const routeBackgrounds: Record<string, string> = {
|
||||||
'/onboarding/intro': 'bg-4.jpg', // Video will be used instead
|
'/onboarding/intro': 'bg-intro.jpg', // Video will be used instead
|
||||||
'/onboarding/options': 'bg-5.jpg',
|
'/onboarding/options': 'bg-intro-4.jpg',
|
||||||
'/onboarding/path': 'bg-3.jpg',
|
'/onboarding/path': 'bg-intro-3.jpg',
|
||||||
'/onboarding/did': 'bg-6.jpg',
|
'/onboarding/did': 'bg-intro-5.jpg',
|
||||||
'/onboarding/backup': 'bg-7.jpg',
|
'/onboarding/backup': 'bg-intro-6.jpg',
|
||||||
'/onboarding/verify': 'bg-2.jpg',
|
'/onboarding/verify': 'bg-intro-2.jpg',
|
||||||
'/onboarding/done': 'bg-1.jpg',
|
'/onboarding/done': 'bg-intro-1.jpg',
|
||||||
'/login': 'bg-4.jpg' // Video loops from splash (same as intro)
|
'/login': 'bg-intro.jpg' // Video loops from splash (same as intro)
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginBackground = 'bg-1.jpg'
|
const loginBackground = 'bg-intro-1.jpg'
|
||||||
|
|
||||||
// Restore video time from splash screen for seamless transition
|
// Restore video time from splash screen for seamless transition
|
||||||
function restoreVideoTime() {
|
function restoreVideoTime() {
|
||||||
@@ -247,7 +247,7 @@ watch(() => route.path, (newPath, oldPath) => {
|
|||||||
|
|
||||||
// Login route: set background immediately, no zoom, no transition (glitch is always-on)
|
// Login route: set background immediately, no zoom, no transition (glitch is always-on)
|
||||||
if (newPath === '/login') {
|
if (newPath === '/login') {
|
||||||
currentBackground.value = 'bg-1.jpg'
|
currentBackground.value = 'bg-intro-1.jpg'
|
||||||
isTransitioning.value = false
|
isTransitioning.value = false
|
||||||
isGlitching.value = false
|
isGlitching.value = false
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export default {
|
|||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ['Avenir Next', 'system-ui', 'sans-serif'],
|
sans: ['Avenir Next', 'system-ui', 'sans-serif'],
|
||||||
mono: ['Courier New', 'monospace'],
|
mono: ['Courier New', 'monospace'],
|
||||||
|
archipelago: ['Montserrat', 'sans-serif'],
|
||||||
},
|
},
|
||||||
backdropBlur: {
|
backdropBlur: {
|
||||||
'glass': '18px',
|
'glass': '18px',
|
||||||
|
|||||||