mid coding commit

This commit is contained in:
zazawowow
2026-01-24 22:59:20 +00:00
parent 64cc3bc7fb
commit 731cd67cfb
2228 changed files with 135554 additions and 18 deletions
+41
View File
@@ -0,0 +1,41 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>
+105
View File
@@ -0,0 +1,105 @@
<template>
<Transition name="fade">
<div
v-if="showUpdatePrompt"
class="fixed bottom-4 left-4 right-4 md:left-auto md:right-4 md:w-96 z-[9999]"
>
<div class="glass-card p-4 flex items-center gap-4">
<div class="flex-1">
<p class="text-white/90 text-sm font-medium mb-1">Update Available</p>
<p class="text-white/70 text-xs">A new version is available. Click to update.</p>
</div>
<button
@click="handleUpdate"
class="glass-button px-4 py-2 rounded-lg text-sm font-medium transition-all hover:bg-black/70 hover:border-white/30"
>
Update
</button>
<button
@click="dismissUpdate"
class="text-white/50 hover:text-white/80 transition-colors p-2"
aria-label="Dismiss"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</Transition>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const showUpdatePrompt = ref(false)
let updateCallback: (() => Promise<void>) | null = null
onMounted(() => {
// Listen for service worker updates
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('controllerchange', () => {
// Service worker updated, reload the page
window.location.reload()
})
// Check for updates periodically
const checkForUpdates = async () => {
const registration = await navigator.serviceWorker.getRegistration()
if (registration) {
await registration.update()
}
}
// Check for updates every 5 minutes
setInterval(checkForUpdates, 5 * 60 * 1000)
// Listen for updatefound event
navigator.serviceWorker.getRegistration().then((registration) => {
if (registration) {
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing
if (newWorker) {
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// New service worker installed, show update prompt
showUpdatePrompt.value = true
updateCallback = async () => {
if (newWorker.state === 'installed' && registration.waiting) {
// Skip waiting and activate the new service worker
registration.waiting.postMessage({ type: 'SKIP_WAITING' })
}
}
}
})
}
})
}
})
}
})
function dismissUpdate() {
showUpdatePrompt.value = false
}
async function handleUpdate() {
if (updateCallback) {
await updateCallback()
}
}
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateY(20px);
}
</style>
+495
View File
@@ -0,0 +1,495 @@
<template>
<Transition name="splash-fade">
<div v-if="showSplash" class="fixed inset-0 z-[2000] flex items-center justify-center bg-black" style="will-change: opacity, transform;">
<!-- Video background - shown during Welcome Noderunner and Logo (seamless, no zoom) -->
<video
v-if="showWelcome || showLogo"
ref="videoElement"
class="absolute inset-0 w-full h-full object-cover"
:style="{ opacity: backgroundOpacity, transform: 'scale(1)', transition: 'opacity 1.2s ease-out' }"
autoplay
loop
muted
playsinline
preload="auto"
poster="/assets/img/bg-4.jpg"
>
<source src="/assets/video/video-intro.mp4?v=7" type="video/mp4">
<!-- Fallback to image if video fails -->
<div
class="absolute inset-0"
:style="{
backgroundImage: 'url(/assets/img/bg-4.jpg)',
backgroundSize: 'auto 100vh',
backgroundPosition: 'center top',
backgroundRepeat: 'no-repeat',
}"
/>
</video>
<!-- Static image background - shown during alien intro -->
<div
v-else
class="absolute inset-0"
:style="{
backgroundImage: 'url(/assets/img/bg-4.jpg)',
backgroundSize: 'auto 100vh',
backgroundPosition: 'center top',
backgroundRepeat: 'no-repeat',
opacity: backgroundOpacity,
transform: 'scale(1)',
transition: 'opacity 1.2s ease-out',
}"
/>
<!-- Alien Intro -->
<Transition name="fade">
<div
v-if="!alienIntroComplete"
class="absolute inset-0 z-10 flex items-center justify-center transition-opacity duration-800"
:class="{ 'opacity-0': fadeAlienIntro }"
>
<div class="font-mono text-white px-4 sm:px-5 max-w-[95vw] sm:max-w-[90vw] md:max-w-[1200px] text-base sm:text-lg md:text-[24px] leading-relaxed break-words">
<div v-if="showLine1" class="flex items-start mb-4 sm:mb-6 opacity-0" :class="{ 'opacity-100': showLine1 }">
<span class="text-[#00ffff] mr-3 sm:mr-6 flex-shrink-0">></span>
<span class="text-white break-words" :class="{ 'typing-text': typingLine1 }">
In the future there will be 3 types of humans
</span>
</div>
<div v-if="showLine2" class="flex items-start mb-4 sm:mb-6 opacity-0" :class="{ 'opacity-100': showLine2 }">
<span class="text-[#00ffff] mr-3 sm:mr-6 flex-shrink-0">></span>
<span class="text-white break-words" :class="{ 'typing-text': typingLine2 }">
Government Employees
</span>
</div>
<div v-if="showLine3" class="flex items-start mb-4 sm:mb-6 opacity-0" :class="{ 'opacity-100': showLine3 }">
<span class="text-[#00ffff] mr-3 sm:mr-6 flex-shrink-0">></span>
<span class="text-white break-words" :class="{ 'typing-text': typingLine3 }">
Corporate Employees
</span>
</div>
<div v-if="showLine4" class="flex items-start mb-8 sm:mb-12 opacity-0" :class="{ 'opacity-100': showLine4 }">
<span class="text-[#00ffff] mr-3 sm:mr-6 flex-shrink-0">></span>
<span class="text-white break-words" :class="{ 'typing-text': typingLine4 }">
And Noderunners...
</span>
</div>
</div>
</div>
</Transition>
<!-- Welcome Message -->
<Transition name="welcome-fade">
<div
v-if="showWelcome"
class="absolute inset-0 z-[15] flex items-center justify-center font-mono text-3xl sm:text-4xl md:text-5xl px-4"
:class="{ 'welcome-fade-out': fadeWelcome }"
>
<div class="typing-container">
<span class="text-white" :class="{ 'typing-text': typingWelcome }">
Welcome Noderunner
</span>
</div>
</div>
</Transition>
<!-- Logo - Archipelago logo for splash -->
<Transition name="logo-zoom">
<div v-if="showLogo" class="relative z-20 logo-container">
<img
src="/assets/img/logo-archipelago.svg"
alt="Archipelago"
class="w-[min(80vw,900px)] max-w-[90vw] h-auto filter drop-shadow-[0_6px_24px_rgba(0,0,0,0.35)] m-5 object-contain logo-zoom-bounce"
/>
</div>
</Transition>
<!-- Skip Button -->
<button
v-if="!alienIntroComplete"
@click="skipIntro"
class="absolute bottom-8 right-8 z-20 bg-black/60 border border-white/30 text-white/70 font-mono text-xs px-4 py-2 rounded backdrop-blur-[10px] hover:bg-black/80 hover:text-white/90 hover:border-white/50 hover:-translate-y-0.5 active:translate-y-0 transition-all duration-300"
>
Skip Intro
</button>
</div>
</Transition>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
const emit = defineEmits<{
complete: []
}>()
const showSplash = ref(true)
const backgroundOpacity = ref(0)
const alienIntroComplete = ref(false)
const fadeAlienIntro = ref(false)
const showWelcome = ref(false)
const fadeWelcome = ref(false)
const typingWelcome = ref(false)
const showLogo = ref(false)
const showLine1 = ref(false)
const showLine2 = ref(false)
const showLine3 = ref(false)
const showLine4 = ref(false)
const typingLine1 = ref(false)
const typingLine2 = ref(false)
const typingLine3 = ref(false)
const typingLine4 = ref(false)
const videoElement = ref<HTMLVideoElement | null>(null)
// Ensure video plays continuously from Welcome Noderunner through logo
watch([showWelcome, showLogo], ([welcome, logo]) => {
if ((welcome || logo) && videoElement.value) {
// Ensure video is playing and doesn't pause
if (videoElement.value.paused) {
videoElement.value.play().catch(err => {
console.warn('Video autoplay failed:', err)
})
}
// Keep video playing - prevent any pauses
videoElement.value.addEventListener('pause', (e) => {
if (welcome || logo) {
e.preventDefault()
videoElement.value?.play()
}
}, { once: false })
}
})
// Start video as soon as welcome appears
watch(showWelcome, (isShowing) => {
if (isShowing && videoElement.value) {
// Start video immediately when welcome appears
videoElement.value.play().catch(err => {
console.warn('Video autoplay failed on welcome:', err)
})
}
})
// Store video currentTime continuously and before unmounting for seamless transition
watch(showSplash, (isShowing) => {
if (!isShowing && videoElement.value) {
// Store current video time for seamless transition
const currentTime = videoElement.value.currentTime
const wasPlaying = !videoElement.value.paused
sessionStorage.setItem('video_intro_currentTime', currentTime.toString())
sessionStorage.setItem('video_intro_wasPlaying', wasPlaying.toString())
// Store video playback rate to maintain smooth playback
sessionStorage.setItem('video_intro_playbackRate', videoElement.value.playbackRate.toString())
}
})
// Continuously update video time while playing (for more accurate restoration)
let videoTimeUpdateInterval: number | null = null
watch([showWelcome, showLogo], ([welcome, logo]) => {
if ((welcome || logo) && videoElement.value) {
// Update stored time every 50ms for better accuracy and smoother transition
videoTimeUpdateInterval = window.setInterval(() => {
if (videoElement.value && !videoElement.value.paused) {
sessionStorage.setItem('video_intro_currentTime', videoElement.value.currentTime.toString())
sessionStorage.setItem('video_intro_wasPlaying', 'true')
sessionStorage.setItem('video_intro_playbackRate', videoElement.value.playbackRate.toString())
}
}, 50) // More frequent updates for smoother transition
} else {
if (videoTimeUpdateInterval) {
clearInterval(videoTimeUpdateInterval)
videoTimeUpdateInterval = null
}
}
})
// Check if user has seen intro
const seenIntro = localStorage.getItem('neode_intro_seen') === '1'
function skipIntro() {
// Jump to "Welcome Noderunner" part
alienIntroComplete.value = true
fadeAlienIntro.value = true
showWelcome.value = true
typingWelcome.value = true
// Stop alien intro typing animations
typingLine1.value = false
typingLine2.value = false
typingLine3.value = false
typingLine4.value = false
// Start background fade in at 0.3 opacity when welcome appears
setTimeout(() => {
backgroundOpacity.value = 0.3
}, 0)
// Continue with welcome fade out after typing (2s) + cursor continues (1.5s) + 3 blinks (1.35s)
setTimeout(() => {
fadeWelcome.value = true
typingWelcome.value = false
}, 4850)
// Show logo - no zoom, just fade
setTimeout(() => {
showLogo.value = true
// Keep background at 0.3 opacity during logo display
}, 5500)
// Hide welcome after logo starts appearing
setTimeout(() => {
showWelcome.value = false
}, 6000)
// Fade background to full opacity just before completing (for smooth transition to modal)
setTimeout(() => {
backgroundOpacity.value = 1
}, 9000)
// Complete splash with smooth transition - wait for zoom to complete
setTimeout(() => {
// Add a small delay to ensure smooth transition
setTimeout(() => {
showSplash.value = false
document.body.classList.add('splash-complete')
localStorage.setItem('neode_intro_seen', '1')
emit('complete')
}, 500)
}, 9500)
}
function startAlienIntro() {
// Line 1 - types and blinks
setTimeout(() => {
showLine1.value = true
typingLine1.value = true
}, 500)
// Line 2 - wait for line 1 typing (4s) + blinking (1.5s)
setTimeout(() => {
typingLine1.value = false
showLine2.value = true
typingLine2.value = true
}, 6000)
// Line 3 - wait for line 2 typing (4s) + blinking (1.5s)
setTimeout(() => {
typingLine2.value = false
showLine3.value = true
typingLine3.value = true
}, 11500)
// Line 4 - wait for line 3 typing (4s) + blinking (1.5s)
setTimeout(() => {
typingLine3.value = false
showLine4.value = true
typingLine4.value = true
}, 17000)
// Fade out alien intro - wait for line 4 typing (4s) + blinking (1.5s)
setTimeout(() => {
typingLine4.value = false
fadeAlienIntro.value = true
}, 22500)
// Show welcome and start video immediately
setTimeout(() => {
alienIntroComplete.value = true
showWelcome.value = true
typingWelcome.value = true
// Start video immediately when welcome appears
if (videoElement.value) {
videoElement.value.play().catch(err => {
console.warn('Video autoplay failed on welcome:', err)
})
}
}, 23300)
// Start background fade in at 0.3 opacity when welcome appears
setTimeout(() => {
backgroundOpacity.value = 0.3
}, 23300)
// Fade out welcome - typing (2s) + cursor continues (1.5s) + 3 blinks (1.35s) = 4.85s
setTimeout(() => {
fadeWelcome.value = true
typingWelcome.value = false
}, 28150)
// Show logo - background stays at 0.3 opacity
setTimeout(() => {
showLogo.value = true
}, 29000)
// Hide welcome after logo starts appearing
setTimeout(() => {
showWelcome.value = false
}, 30500)
// Fade background to full opacity just before completing (for smooth transition to modal)
setTimeout(() => {
backgroundOpacity.value = 1
}, 33000)
// Complete splash with smooth transition
setTimeout(() => {
// Store final video time right before unmounting
if (videoElement.value && !videoElement.value.paused) {
sessionStorage.setItem('video_intro_currentTime', videoElement.value.currentTime.toString())
sessionStorage.setItem('video_intro_wasPlaying', 'true')
}
showSplash.value = false
document.body.classList.add('splash-complete')
localStorage.setItem('neode_intro_seen', '1')
emit('complete')
}, 34500)
}
onMounted(() => {
if (seenIntro) {
// Skip intro if already seen
showSplash.value = false
document.body.classList.add('splash-complete')
emit('complete')
} else {
// Play intro
startAlienIntro()
}
})
</script>
<style scoped>
.splash-fade-enter-active {
transition: opacity 0.5s ease;
}
.splash-fade-leave-active {
transition: opacity 1s ease-out, transform 1s ease-out;
}
.splash-fade-enter-from,
.splash-fade-leave-to {
opacity: 0;
}
.splash-fade-leave-to {
transform: scale(1.1);
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* Welcome message fade out */
.welcome-fade-enter-active {
transition: opacity 0.8s ease-out;
}
.welcome-fade-leave-active {
transition: opacity 0.6s ease-in;
}
.welcome-fade-enter-from,
.welcome-fade-leave-to {
opacity: 0;
}
.welcome-fade-out {
opacity: 0;
transition: opacity 0.6s ease-in;
}
/* Logo zoom bounce animation - smooth and buttery */
.logo-zoom-enter-active {
transition: all 1s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.logo-zoom-leave-active {
transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}
.logo-zoom-enter-from {
opacity: 0;
transform: scale(0.7);
}
.logo-zoom-enter-to {
opacity: 1;
transform: scale(1);
}
.logo-zoom-leave-from {
opacity: 1;
transform: scale(1);
}
.logo-zoom-leave-to {
opacity: 0;
transform: scale(1.05);
}
.logo-zoom-bounce {
animation: logoZoomBounce 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}
@keyframes logoZoomBounce {
0% {
transform: scale(0.85);
opacity: 0;
}
50% {
transform: scale(1.02);
opacity: 0.9;
}
75% {
transform: scale(0.98);
opacity: 0.95;
}
100% {
transform: scale(1);
opacity: 1;
}
}
/* Container to keep the typing text centered */
.typing-container {
display: flex;
justify-content: center;
align-items: center;
min-width: 0;
}
/* Ensure text wraps smoothly on mobile */
.font-mono {
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
}
/* Smooth line transitions for mobile */
@media (max-width: 640px) {
.font-mono span {
display: inline-block;
max-width: 100%;
}
}
/* Background zoom transition - matches OnboardingWrapper style */
.bg-zoom-transition {
transition: transform 1.5s cubic-bezier(0.4, 0, 0.2, 1), opacity 1.2s ease-out;
transform: scale(1);
transform-origin: center center;
will-change: transform, opacity;
}
.bg-zoom-transition.bg-zoom-in {
transform: scale(1.15);
}
</style>