2026-02-18 03:06:14 +01:00
|
|
|
<template>
|
2026-03-07 11:28:55 +01:00
|
|
|
<div class="min-h-screen bg-white">
|
2026-03-07 14:59:23 +01:00
|
|
|
<!-- Mobile header -->
|
|
|
|
|
<header class="sticky top-0 bg-white z-40 border-b border-gray-200 lg:hidden">
|
2026-03-07 14:21:06 +01:00
|
|
|
<div class="max-w-7xl mx-auto px-4 py-2 flex items-center justify-between gap-4">
|
2026-03-07 14:03:53 +01:00
|
|
|
<Menu />
|
|
|
|
|
<h1 class="text-lg md:text-2xl whitespace-nowrap">
|
2026-03-07 11:28:55 +01:00
|
|
|
<NuxtLink to='/' class="hover:underline">michael winter</NuxtLink>
|
|
|
|
|
</h1>
|
2026-03-07 14:03:53 +01:00
|
|
|
<div class="flex-1 text-right text-lg md:text-xl">{{ pageTitle }}</div>
|
2026-02-18 03:06:14 +01:00
|
|
|
</div>
|
2026-03-07 11:28:55 +01:00
|
|
|
</header>
|
2026-02-18 03:06:14 +01:00
|
|
|
|
2026-03-07 14:59:23 +01:00
|
|
|
<!-- Desktop header -->
|
|
|
|
|
<header class="sticky top-0 bg-white z-40 border-b border-gray-200 hidden lg:block">
|
|
|
|
|
<div class="w-full px-4 py-2 flex items-center justify-between">
|
|
|
|
|
<h1 class="text-lg md:text-2xl whitespace-nowrap">
|
|
|
|
|
<NuxtLink to='/' class="hover:underline">michael winter</NuxtLink>
|
|
|
|
|
</h1>
|
|
|
|
|
<div class="text-lg md:text-xl">{{ pageTitle }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<div class="flex">
|
|
|
|
|
<!-- Sidebar for wide screens -->
|
2026-03-07 15:08:52 +01:00
|
|
|
<aside class="hidden lg:block w-48 flex-shrink-0 border-r border-gray-200 p-4 pt-12 sticky self-start top-12 z-20">
|
2026-03-07 14:59:23 +01:00
|
|
|
<nav class="space-y-4 ml-4">
|
|
|
|
|
<div>
|
|
|
|
|
<p class="text-xs text-gray-400 uppercase tracking-wider mb-2">Works</p>
|
|
|
|
|
<div class="space-y-2 ml-2">
|
|
|
|
|
<NuxtLink to="/pieces" class="block hover:underline">pieces</NuxtLink>
|
|
|
|
|
<NuxtLink to="/writings" class="block hover:underline">writings</NuxtLink>
|
|
|
|
|
<NuxtLink to="/albums" class="block hover:underline">albums</NuxtLink>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<p class="text-xs text-gray-400 uppercase tracking-wider mb-2">Events</p>
|
|
|
|
|
<div class="space-y-2 ml-2">
|
|
|
|
|
<NuxtLink to="/performances" class="block hover:underline">performances</NuxtLink>
|
|
|
|
|
<NuxtLink to="/lectures" class="block hover:underline">lectures</NuxtLink>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<NuxtLink to="/about" class="block hover:underline">about</NuxtLink>
|
|
|
|
|
<a href="https://unboundedpress.org/code" target="_blank" class="block hover:underline">code</a>
|
|
|
|
|
</nav>
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<main class="max-w-7xl mx-auto px-4 py-8">
|
|
|
|
|
<slot />
|
|
|
|
|
</main>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-18 03:06:14 +01:00
|
|
|
|
2026-03-07 11:28:55 +01:00
|
|
|
<footer class="fixed bottom-0 bg-white border-t border-gray-200 p-2 w-full flex justify-center z-30">
|
|
|
|
|
<ClientOnly>
|
|
|
|
|
<iframe
|
|
|
|
|
v-if="audioPlayerStore.soundcloud_trackid !== 'undefined'"
|
2026-03-07 14:59:23 +01:00
|
|
|
class="w-64"
|
2026-03-07 11:28:55 +01:00
|
|
|
height="20px"
|
|
|
|
|
scrolling="no"
|
|
|
|
|
frameborder="no"
|
|
|
|
|
:src="'https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/' + audioPlayerStore.soundcloud_trackid + '&inverse=false&auto_play=true&show_user=false'"
|
|
|
|
|
></iframe>
|
|
|
|
|
</ClientOnly>
|
|
|
|
|
</footer>
|
|
|
|
|
|
|
|
|
|
<Modal v-model="modalStore.isOpen" :maxHeight="modalStore.type === 'image' && modalStore.soundcloudUrl ? 'calc(85vh + 60px)' : '85vh'">
|
2026-03-08 11:05:50 +01:00
|
|
|
<div class="flex flex-col h-full overflow-hidden">
|
2026-03-07 17:41:49 +01:00
|
|
|
<ImageSlider v-if="modalStore.type === 'image'" :bucket="modalStore.bucket" :gallery="modalStore.gallery" :initialIndex="modalStore.initialIndex"></ImageSlider>
|
2026-02-19 02:09:33 +01:00
|
|
|
<div v-if="modalStore.type === 'image' && modalStore.soundcloudUrl" class="flex justify-center mt-2">
|
2026-02-27 09:15:37 +01:00
|
|
|
<ClientOnly>
|
2026-03-07 14:59:23 +01:00
|
|
|
<iframe :src="modalStore.soundcloudUrl" class="w-64" height="20px" scrolling="no" frameborder="no" allow="autoplay"></iframe>
|
2026-02-27 09:15:37 +01:00
|
|
|
</ClientOnly>
|
2026-02-19 02:09:33 +01:00
|
|
|
</div>
|
2026-03-08 11:05:50 +01:00
|
|
|
<div v-if="modalStore.type === 'video'" class="w-full flex items-center justify-center p-4">
|
|
|
|
|
<div class="w-full aspect-video">
|
|
|
|
|
<ClientOnly>
|
|
|
|
|
<iframe
|
|
|
|
|
:src="'https://player.vimeo.com/video/' + modalStore.vimeo_trackid"
|
|
|
|
|
width="100%"
|
|
|
|
|
height="100%"
|
|
|
|
|
frameborder="0"
|
|
|
|
|
webkitallowfullscreen
|
|
|
|
|
mozallowfullscreen
|
|
|
|
|
allowfullscreen
|
|
|
|
|
class="w-full h-full"
|
|
|
|
|
></iframe>
|
|
|
|
|
</ClientOnly>
|
|
|
|
|
</div>
|
2026-02-19 03:51:50 +01:00
|
|
|
</div>
|
2026-03-08 11:05:50 +01:00
|
|
|
<div v-if="modalStore.type === 'document'" class="w-full flex flex-col h-[70vh]">
|
2026-02-27 09:15:37 +01:00
|
|
|
<ClientOnly>
|
2026-03-05 11:17:28 +01:00
|
|
|
<iframe :src="modalStore.iframeUrl" width="100%" height="100%" frameborder="0" class="flex-grow"></iframe>
|
2026-02-27 09:15:37 +01:00
|
|
|
</ClientOnly>
|
2026-02-19 01:37:29 +01:00
|
|
|
</div>
|
2026-03-08 11:05:50 +01:00
|
|
|
<div v-if="modalStore.type === 'pdf'" class="flex flex-col h-[70vh]">
|
2026-02-27 09:15:37 +01:00
|
|
|
<ClientOnly>
|
2026-03-08 11:05:50 +01:00
|
|
|
<iframe :src="modalStore.pdfUrl + '#toolbar=1&navpanes=0&sidebar=0'" width="100%" height="100%" frameborder="0" class="flex-grow"></iframe>
|
2026-02-27 09:15:37 +01:00
|
|
|
</ClientOnly>
|
2026-02-19 01:58:44 +01:00
|
|
|
<div v-if="modalStore.soundcloudUrl" class="flex justify-center mt-2">
|
2026-02-27 09:15:37 +01:00
|
|
|
<ClientOnly>
|
2026-03-07 14:59:23 +01:00
|
|
|
<iframe :src="modalStore.soundcloudUrl" class="w-64" height="20px" scrolling="no" frameborder="no" allow="autoplay"></iframe>
|
2026-02-27 09:15:37 +01:00
|
|
|
</ClientOnly>
|
2026-02-19 01:58:44 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-19 03:51:50 +01:00
|
|
|
<div v-if="modalStore.type === 'pdf' || modalStore.type === 'image' || modalStore.type === 'document'" class="absolute bottom-2 right-2 z-10">
|
|
|
|
|
<a :href="modalStore.type === 'pdf' ? modalStore.pdfUrl : modalStore.type === 'image' ? '/' + modalStore.bucket + '/' + modalStore.gallery[0]?.image : modalStore.type === 'document' ? modalStore.iframeUrl : undefined" target="_blank" rel="noopener noreferrer" class="p-2 bg-gray-600 rounded-lg inline-flex items-center justify-center pointer-events-auto">
|
2026-02-19 01:32:33 +01:00
|
|
|
<Icon name="mdi:open-in-new" class="w-5 h-5 text-white" />
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
2026-03-08 11:05:50 +01:00
|
|
|
</div>
|
2026-03-07 11:28:55 +01:00
|
|
|
</Modal>
|
|
|
|
|
</div>
|
2026-02-18 03:06:14 +01:00
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { useAudioPlayerStore } from "@/stores/AudioPlayerStore"
|
|
|
|
|
import { useModalStore } from "@/stores/ModalStore"
|
2026-03-07 14:03:53 +01:00
|
|
|
import { onMounted, computed } from "vue"
|
2026-02-18 03:06:14 +01:00
|
|
|
|
|
|
|
|
const audioPlayerStore = useAudioPlayerStore()
|
|
|
|
|
const modalStore = useModalStore()
|
|
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
2026-03-07 14:03:53 +01:00
|
|
|
const pageTitle = computed(() => {
|
|
|
|
|
const path = route.path
|
|
|
|
|
if (path === '/' || path === '/pieces') return 'pieces'
|
|
|
|
|
if (path === '/writings') return 'writings'
|
|
|
|
|
if (path === '/albums') return 'albums'
|
|
|
|
|
if (path === '/performances') return 'performances'
|
|
|
|
|
if (path === '/lectures') return 'lectures'
|
|
|
|
|
if (path === '/about') return 'about'
|
|
|
|
|
if (path.startsWith('/works/')) return 'works'
|
|
|
|
|
return ''
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-27 09:15:37 +01:00
|
|
|
onMounted(() => {
|
|
|
|
|
if(route.params.files == 'scores') {
|
|
|
|
|
useFetch('/api/works', {
|
|
|
|
|
transform: (works) => {
|
|
|
|
|
return works.find(w => w.score === route.params.filename)
|
|
|
|
|
}
|
|
|
|
|
}).then(({ data }) => {
|
|
|
|
|
if(data.value?.soundcloud_trackid){
|
|
|
|
|
audioPlayerStore.setSoundCloudTrackID(data.value.soundcloud_trackid)
|
|
|
|
|
} else {
|
|
|
|
|
audioPlayerStore.clearSoundCloudTrackID()
|
|
|
|
|
}
|
|
|
|
|
})
|
2026-02-18 03:06:14 +01:00
|
|
|
}
|
2026-02-27 09:15:37 +01:00
|
|
|
})
|
2026-02-18 03:06:14 +01:00
|
|
|
</script>
|