feat(nds): improve fullscreen button and add volume slider

This commit is contained in:
2026-02-25 17:09:41 +01:00
parent 8c64f287e1
commit 39476d0b53
6 changed files with 91 additions and 52 deletions

View File

@@ -0,0 +1,79 @@
<script setup lang="ts">
import { useFullscreen } from "@vueuse/core";
const app = useAppStore();
const showFullscreenBtn = ref(true);
const { isFullscreen, toggle: toggleFullscreen } = useFullscreen();
const open = ref(false);
onMounted(() => {
if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
showFullscreenBtn.value = false;
}
});
</script>
<template>
<div class="fixed bottom-4 right-4 flex flex-col items-center">
<UPopover
v-model:open="open"
:content="{ side: 'top', align: 'center' }"
:ui="{ content: 'bg-transparent border-0 ring-0 shadow-none' }"
>
<template #default>
<UButton
:icon="
app.settings.volume === 0
? 'i-heroicons-speaker-x-mark'
: 'i-heroicons-speaker-wave'
"
color="neutral"
variant="link"
size="xl"
:class="[
'transition-opacity',
open
? 'opacity-100 text-highlighted'
: 'opacity-50 hover:opacity-100',
]"
@keydown.up.prevent="open = true"
/>
</template>
<template #content>
<div class="flex items-center justify-center h-24">
<USlider
:model-value="app.settings.volume * 100"
:min="0"
:max="100"
:step="2"
orientation="vertical"
color="neutral"
@update:model-value="
(v) => {
app.settings.volume = (v ?? 100) / 100;
app.save();
}
"
/>
</div>
</template>
</UPopover>
<UButton
v-if="showFullscreenBtn"
:icon="
isFullscreen
? 'i-heroicons-arrows-pointing-in'
: 'i-heroicons-arrows-pointing-out'
"
color="neutral"
variant="link"
size="xl"
class="opacity-50 hover:opacity-100 transition-opacity"
@click="toggleFullscreen"
/>
</div>
</template>

View File

@@ -12,8 +12,9 @@ const createAudio = (path: string) => {
return {
play: (volume = 1) => {
if (!source) return;
const app = useAppStore();
const audio = source.cloneNode() as HTMLAudioElement;
audio.volume = volume;
audio.volume = Math.min(1, volume * app.settings.volume);
audio.addEventListener("ended", () => audio.remove(), { once: true });
audio.play().catch(() => {});
},

View File

@@ -7,6 +7,6 @@ export const useClockTick = () => {
const s = now.getSeconds();
if (s === lastSecond) return;
lastSecond = s;
assets.audio.clockTick.play(s === 0 ? 1 : 0.7);
assets.audio.clockTick.play(s === 0 ? 2 : 1.4);
};
};

View File

@@ -13,6 +13,12 @@ export const useKeyDown = (callback: KeyDownCallback) => {
if (app.lagDetected) return;
const ndsButton = mapCodeToNDS(event.code);
if (
ndsButton &&
document.activeElement &&
document.activeElement !== document.body
)
return;
callback({
key: ndsButton ? `NDS_${ndsButton}` : event.key,
ndsButton,

View File

@@ -23,20 +23,9 @@ const bottomScreen = useTemplateRef("bottomScreen");
const topScreenCanvas = computed(() => topScreen.value?.canvas ?? null);
const bottomScreenCanvas = computed(() => bottomScreen.value?.canvas ?? null);
const isIOS = () => /iPad|iPhone|iPod/.test(navigator.userAgent);
const isTouchDevice = () =>
"ontouchstart" in window || navigator.maxTouchPoints > 0;
const showFullscreenBtn = ref(true);
const toggleFullscreen = () => {
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
document.documentElement.requestFullscreen();
}
};
const windowSize = useWindowSize();
watch([windowSize.width, windowSize.height], ([width, height]) => {
@@ -116,10 +105,6 @@ watch(
);
onMounted(async () => {
if (isIOS()) {
showFullscreenBtn.value = false;
}
if (isTouchDevice()) {
const landscape = window.matchMedia("(orientation: landscape)");
@@ -229,22 +214,7 @@ useKeyDown(async ({ key, repeated }) => {
?
</button>
<button
v-if="showFullscreenBtn"
class="fullscreen-btn"
@click="toggleFullscreen"
>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"
/>
</svg>
</button>
<Controls />
</div>
</template>
@@ -270,23 +240,4 @@ useKeyDown(async ({ key, repeated }) => {
.help-btn:hover {
opacity: 1;
}
.fullscreen-btn {
position: fixed;
bottom: 16px;
right: 16px;
width: 32px;
height: 32px;
padding: 4px;
background: none;
border: none;
color: #666;
cursor: pointer;
opacity: 0.5;
transition: opacity 0.2s;
}
.fullscreen-btn:hover {
opacity: 1;
}
</style>

View File

@@ -24,6 +24,7 @@ const settingsSchema = z.object({
row: z.number(),
}),
renderingMode: z.enum(["3d", "2d"]),
volume: z.number(),
});
type Settings = z.infer<typeof settingsSchema>;
@@ -31,6 +32,7 @@ type Settings = z.infer<typeof settingsSchema>;
const defaultSettings = (): Settings => ({
color: { col: 0, row: 0 },
renderingMode: "3d",
volume: 0.5,
});
export const useAppStore = defineStore("app", {