Update favicon and enhance UI components for improved user experience

- Replaced PNG favicon with SVG for better scalability and visual quality across devices.
- Updated Vite configuration to include the new SVG favicon and adjusted asset paths.
- Enhanced various UI components with improved focus management and accessibility features.
- Introduced new styles to hide scrollbars while maintaining scroll functionality for a cleaner interface.
This commit is contained in:
Dorian
2026-02-17 22:10:38 +00:00
parent 8a32e36d85
commit b63612c5ae
21 changed files with 486 additions and 107 deletions
+19 -5
View File
@@ -1,8 +1,14 @@
<template>
<div class="logo-gradient-border flex-shrink-0 inline-block overflow-hidden" :class="sizeClass">
<div
class="flex-shrink-0 inline-block overflow-hidden"
:class="[
sizeClass,
!noBorder && 'logo-gradient-border'
]"
>
<!-- Neode logo - always white -->
<svg
class="block logo-svg"
class="block w-full h-full logo-svg"
viewBox="0 0 1024 1024"
fill="none"
xmlns="http://www.w3.org/2000/svg"
@@ -27,9 +33,18 @@
<script setup lang="ts">
const props = withDefaults(defineProps<{
size?: 'sm' | 'lg' | 'xl'
}>(), { size: 'sm' })
noBorder?: boolean
/** When true, fit to container (w-full h-full) instead of fixed size - for use inside logo-gradient-border */
fit?: boolean
}>(), { size: 'sm', noBorder: false, fit: false })
const sizeClass = props.size === 'xl' ? 'w-48 h-48 sm:w-64 sm:h-64 md:w-80 md:h-80' : props.size === 'lg' ? 'w-32 h-32 sm:w-48 sm:h-48' : 'w-14 h-14'
const sizeClass = props.fit
? 'w-full h-full max-w-full max-h-full'
: props.size === 'xl'
? 'w-48 h-48 sm:w-64 sm:h-64 md:w-80 md:h-80'
: props.size === 'lg'
? 'w-32 h-32 sm:w-48 sm:h-48'
: 'w-14 h-14'
// Parsed from favico-black.svg path - 20 rects
const rects = [
@@ -56,7 +71,6 @@ const rects = [
]
// Stagger delays (ms) - row-by-row top-to-bottom, left-to-right for a clean reveal
// Row 1 (y~318): 0,1,2,3 | Row 2 (y~396): 4,5 | Row 3 (y~476): 6-11 | Row 4 (y~555): 12-15 | Row 5 (y~634): 16-19
const delays = [0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900]
</script>
+21 -3
View File
@@ -36,13 +36,15 @@
<kbd class="hidden sm:inline-flex px-2 py-1 text-xs text-white/50 bg-white/10 rounded">Esc</kbd>
</div>
<!-- Iframe container -->
<div class="relative flex-1 min-h-0 bg-black/40">
<!-- Iframe container - overflow hidden to clip inner scrollbars -->
<div class="relative flex-1 min-h-0 bg-black/40 overflow-hidden">
<iframe
ref="iframeRef"
v-if="store.url"
:src="store.url"
class="absolute inset-0 w-full h-full border-0"
class="absolute inset-0 w-full h-full border-0 iframe-scrollbar-hide"
title="App content"
@load="injectScrollbarHideIfSameOrigin"
/>
</div>
</div>
@@ -57,6 +59,22 @@ import { useAppLauncherStore } from '@/stores/appLauncher'
const store = useAppLauncherStore()
const closeBtnRef = ref<HTMLButtonElement | null>(null)
const iframeRef = ref<HTMLIFrameElement | null>(null)
function injectScrollbarHideIfSameOrigin() {
try {
const doc = iframeRef.value?.contentDocument
if (!doc) return
const style = doc.createElement('style')
style.textContent = `
* { -ms-overflow-style: none; scrollbar-width: none; }
*::-webkit-scrollbar { display: none; }
`
doc.head.appendChild(style)
} catch {
/* Cross-origin: cannot access iframe document */
}
}
const panelClasses = [
'glass-card',
+9 -2
View File
@@ -8,6 +8,7 @@
>
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm"></div>
<div
ref="modalRef"
@click.stop
class="glass-card p-6 max-w-lg w-full relative z-10 max-h-[80vh] overflow-y-auto"
>
@@ -45,14 +46,20 @@
</template>
<script setup lang="ts">
defineProps<{
import { ref, computed } from 'vue'
import { useModalKeyboard } from '@/composables/useModalKeyboard'
const props = defineProps<{
show: boolean
title: string
content: string
relatedPath?: string
}>()
defineEmits<{
const emit = defineEmits<{
close: []
}>()
const modalRef = ref<HTMLElement | null>(null)
useModalKeyboard(modalRef, computed(() => props.show), () => emit('close'))
</script>
+55 -40
View File
@@ -1,39 +1,59 @@
<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>
<Teleport to="body">
<Transition name="modal">
<div
v-if="showUpdatePrompt"
class="fixed inset-0 z-[9999] flex items-center justify-center p-4"
@click.self="dismissUpdate"
>
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm"></div>
<div
ref="modalRef"
class="glass-card p-6 max-w-md w-full relative z-10"
@click.stop
>
<div class="flex items-start justify-between gap-4 mb-4">
<h3 class="text-xl font-semibold text-white">Update Available</h3>
<button
@click="dismissUpdate"
class="p-2 rounded-lg hover:bg-white/10 text-white/70 hover:text-white transition-colors"
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>
<p class="text-white/80 mb-6">
A new version of Archipelago is available. Update now to get the latest features and fixes.
</p>
<div class="flex gap-3 justify-end">
<button
@click="dismissUpdate"
class="px-4 py-2 glass-button rounded-lg text-sm font-medium"
>
Later
</button>
<button
@click="handleUpdate"
class="px-4 py-2 gradient-button rounded-lg text-sm font-medium"
>
Update Now
</button>
</div>
</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>
</Transition>
</Teleport>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useModalKeyboard } from '@/composables/useModalKeyboard'
const showUpdatePrompt = ref(false)
let updateCallback: (() => Promise<void>) | null = null
const modalRef = ref<HTMLElement | null>(null)
onMounted(() => {
// Listen for service worker updates
@@ -54,6 +74,13 @@ onMounted(() => {
// Check for updates every 5 minutes
setInterval(checkForUpdates, 5 * 60 * 1000)
// Check when user returns to tab (helps with cached PWA)
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
checkForUpdates()
}
})
// Listen for updatefound event
navigator.serviceWorker.getRegistration().then((registration) => {
if (registration) {
@@ -79,6 +106,8 @@ onMounted(() => {
}
})
useModalKeyboard(modalRef, showUpdatePrompt, dismissUpdate)
function dismissUpdate() {
showUpdatePrompt.value = false
}
@@ -89,17 +118,3 @@ async function handleUpdate() {
}
}
</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>
+7 -4
View File
@@ -19,8 +19,8 @@
/>
</div>
<!-- Logo in center -->
<div class="screensaver-logo-wrapper relative z-10">
<AnimatedLogo size="xl" />
<div class="screensaver-logo-wrapper">
<ScreensaverLogo />
</div>
</div>
</div>
@@ -30,7 +30,7 @@
<script setup lang="ts">
import { onMounted, onBeforeUnmount } from 'vue'
import AnimatedLogo from '@/components/AnimatedLogo.vue'
import ScreensaverLogo from '@/components/ScreensaverLogo.vue'
import { useScreensaverStore } from '@/stores/screensaver'
const store = useScreensaverStore()
@@ -100,12 +100,14 @@ onBeforeUnmount(() => {
}
}
/* Ring of segments around the logo - audio viz style */
/* Ring of segments around the logo - audio viz style (behind logo) */
.screensaver-viz-ring {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
--viz-radius: 140px;
}
@@ -175,6 +177,7 @@ onBeforeUnmount(() => {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 10;
filter: drop-shadow(0 0 40px rgba(255, 255, 255, 0.15));
}
</style>
@@ -0,0 +1,20 @@
<template>
<div class="logo-gradient-border screensaver-logo-cycle relative w-48 h-48 sm:w-64 sm:h-64 md:w-80 md:h-80 flex items-center justify-center overflow-hidden">
<!-- Squares logo -->
<div class="screensaver-logo-squares absolute inset-[3px] flex items-center justify-center">
<AnimatedLogo size="xl" no-border fit />
</div>
</div>
</template>
<script setup lang="ts">
import AnimatedLogo from '@/components/AnimatedLogo.vue'
</script>
<style scoped>
.screensaver-logo-squares {
opacity: 1;
position: relative;
z-index: 1;
}
</style>