Update PWA assets and enhance UI components for improved user experience

- Replaced outdated favicon and app icons with new PNG assets for better scalability and visual quality.
- Updated index.html and manifest.json to reflect new icon paths and improve PWA support.
- Added a script in package.json to generate PWA icons automatically.
- Enhanced AppLauncherOverlay.vue with a refresh button for better user interaction.
- Improved SplashScreen.vue with new transition effects for a more engaging user experience.
This commit is contained in:
Dorian
2026-02-18 10:10:12 +00:00
parent d6ecf5ea2f
commit e6fb1d20be
17 changed files with 790 additions and 145 deletions

View File

@@ -22,6 +22,23 @@
</svg>
</div>
<span class="flex-1 truncate text-sm font-medium text-white/90">{{ store.title || 'App' }}</span>
<button
type="button"
class="flex items-center justify-center w-9 h-9 rounded-lg hover:bg-white/15 text-white/70 hover:text-white transition-colors disabled:opacity-70"
aria-label="Refresh"
:disabled="isRefreshing"
@click="refreshIframe"
>
<svg
class="w-5 h-5 transition-transform duration-300"
:class="{ 'animate-spin': isRefreshing }"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
</button>
<button
ref="closeBtnRef"
type="button"
@@ -41,10 +58,11 @@
<iframe
ref="iframeRef"
v-if="store.url"
:key="iframeRefreshKey"
:src="store.url"
class="absolute inset-0 w-full h-full border-0 iframe-scrollbar-hide"
title="App content"
@load="injectScrollbarHideIfSameOrigin"
@load="onIframeLoad"
/>
</div>
</div>
@@ -60,6 +78,18 @@ import { useAppLauncherStore } from '@/stores/appLauncher'
const store = useAppLauncherStore()
const closeBtnRef = ref<HTMLButtonElement | null>(null)
const iframeRef = ref<HTMLIFrameElement | null>(null)
const iframeRefreshKey = ref(0)
const isRefreshing = ref(false)
function refreshIframe() {
isRefreshing.value = true
iframeRefreshKey.value++
}
function onIframeLoad() {
injectScrollbarHideIfSameOrigin()
isRefreshing.value = false
}
function injectScrollbarHideIfSameOrigin() {
try {
@@ -95,6 +125,8 @@ watch(
(open) => {
if (open) {
closeBtnRef.value?.focus()
} else {
isRefreshing.value = false
}
}
)

View File

@@ -99,14 +99,21 @@
<!-- Tap to start - logo + "Enter the Exit" behind (like screensaver) -->
<div
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 cursor-pointer overflow-hidden"
:class="tapStartTransitioning ? 'tap-overlay-zoom-out' : 'bg-black/40'"
@click="handleTapToStart"
>
<div class="tap-to-start-content relative flex items-center justify-center">
<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">
<div class="tap-to-start-content relative flex items-center justify-center perspective-1000">
<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 transition-opacity duration-300"
:class="{ 'opacity-0': tapStartTransitioning }"
>
Enter the Exit
</span>
<div class="tap-to-start-logo absolute">
<div
class="tap-to-start-logo absolute"
:class="{ 'tap-logo-launch': tapStartTransitioning }"
>
<ScreensaverLogo />
</div>
</div>
@@ -144,6 +151,7 @@ const BLINK_AFTER_TYPING = 1500
const showSplash = ref(true)
const showTapToStart = ref(true)
const tapStartTransitioning = ref(false)
const backgroundOpacity = ref(0)
const alienIntroComplete = ref(false)
const fadeAlienIntro = ref(false)
@@ -232,10 +240,15 @@ watch([showWelcome, showLogo], ([welcome, logo]) => {
const seenIntro = localStorage.getItem('neode_intro_seen') === '1'
function handleTapToStart() {
if (!showTapToStart.value) return
if (!showTapToStart.value || tapStartTransitioning.value) return
resumeAudioContext()
showTapToStart.value = false
startAlienIntro()
tapStartTransitioning.value = true
// Logo: grow (150ms) then zoom out to background (850ms). Total 1s.
setTimeout(() => {
showTapToStart.value = false
tapStartTransitioning.value = false
startAlienIntro()
}, 1000)
}
function handleSkipClick() {
@@ -573,6 +586,32 @@ onBeforeUnmount(() => {
transform: scale(1.15);
}
/* Tap to start - logo grow then zoom out to background */
.tap-overlay-zoom-out {
background-color: rgba(0, 0, 0, 0.4);
transition: background-color 0.6s cubic-bezier(0.4, 0, 0.2, 1);
animation: tap-overlay-fade 1s ease-out forwards;
}
@keyframes tap-overlay-fade {
0% { background-color: rgba(0, 0, 0, 0.4); }
30% { background-color: rgba(0, 0, 0, 0.35); }
100% { background-color: rgba(0, 0, 0, 0); }
}
.perspective-1000 {
perspective: 1000px;
}
.tap-logo-launch {
animation: tap-logo-launch 1s cubic-bezier(0.22, 1, 0.36, 1) forwards;
transform-origin: center center;
will-change: transform, opacity;
}
@keyframes tap-logo-launch {
0% { transform: scale(1); opacity: 1; }
15% { transform: scale(1.2); opacity: 1; }
25% { transform: scale(1.15); opacity: 1; }
100% { transform: scale(0); opacity: 0; }
}
/* Tap to start - "Enter the Exit" big behind logo */
.tap-to-start-content {
min-height: 12rem;