203 lines
6.6 KiB
Vue
203 lines
6.6 KiB
Vue
<template>
|
||
<div
|
||
class="relative min-h-screen w-full bg-gradient-to-br from-red-900 via-red-800 to-blue-900 overflow-hidden text-white"
|
||
>
|
||
<!-- 🕸️ ANIMATED SPIDER-WEB BACKGROUND -->
|
||
<div class="absolute inset-0 opacity-15 animate-spin-slow">
|
||
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" class="w-full h-full">
|
||
<g stroke="white" stroke-width="0.4" fill="none">
|
||
<circle cx="250" cy="250" r="60" />
|
||
<circle cx="250" cy="250" r="120" />
|
||
<circle cx="250" cy="250" r="180" />
|
||
<circle cx="250" cy="250" r="240" />
|
||
<circle cx="250" cy="250" r="300" />
|
||
<line x1="250" y1="0" x2="250" y2="500" />
|
||
<line x1="0" y1="250" x2="500" y2="250" />
|
||
<line x1="125" y1="0" x2="375" y2="500" />
|
||
<line x1="375" y1="0" x2="125" y2="500" />
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
|
||
<!-- 💫 LIGHT ORBS -->
|
||
<div class="absolute top-10 left-10 w-32 h-32 bg-red-500/30 rounded-full blur-2xl"></div>
|
||
<div class="absolute bottom-20 right-10 w-24 h-24 bg-blue-500/25 rounded-full blur-xl"></div>
|
||
|
||
<!-- 🕷️ FLOATING ICONS -->
|
||
<div class="absolute top-16 right-16 text-6xl opacity-20 animate-bounce-slow">🕷️</div>
|
||
<div class="absolute bottom-20 left-16 text-6xl opacity-20 animate-bounce-slow delay-700">🕸️</div>
|
||
|
||
<!-- 🔻 NAVIGATION -->
|
||
<nav
|
||
v-if="currentSection !== 'landing'"
|
||
class="fixed top-6 left-1/2 transform -translate-x-1/2 z-30 animate-fade-in"
|
||
>
|
||
<ul
|
||
class="flex space-x-2 bg-black/70 backdrop-blur-lg px-6 py-3 rounded-full shadow-lg text-sm font-semibold text-white border border-red-500/60"
|
||
>
|
||
<li v-for="section in sections" :key="section.key">
|
||
<button
|
||
@click="switchSection(section.key)"
|
||
:class="navClass(section.key)"
|
||
class="px-3 py-1 rounded-full transition-all duration-300"
|
||
>
|
||
{{ section.label }}
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
</nav>
|
||
|
||
<!-- 🎵 MUSIC BUTTON -->
|
||
<div class="fixed bottom-6 left-6 z-40" v-if="currentSection !== 'landing'">
|
||
<button
|
||
@click="toggleMusic"
|
||
class="bg-gradient-to-r from-red-600 to-blue-600 hover:from-red-700 hover:to-blue-700 p-4 rounded-full shadow-2xl transition-all duration-300 hover:scale-110 animate-pulse-slow"
|
||
>
|
||
<span class="text-lg">{{ isPlaying ? '⏸️' : '▶️' }}</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 🌆 MAIN CONTENT -->
|
||
<main
|
||
class="relative z-20 h-screen w-screen flex items-center justify-center transition-all duration-700 ease-in-out overflow-hidden"
|
||
>
|
||
<transition name="fade" mode="out-in">
|
||
<component
|
||
:is="currentComponent"
|
||
:key="currentSection"
|
||
v-bind="componentProps"
|
||
@open-invitation="switchSection('introduction')"
|
||
@addMessage="addMessage"
|
||
/>
|
||
</transition>
|
||
</main>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue'
|
||
import { useRuntimeConfig } from '#app'
|
||
|
||
// 🧩 Import Semua Komponen
|
||
import Landing from '~/components/templates/UltahStarter/Landing.vue'
|
||
import Introduction from '~/components/templates/UltahStarter/Introduction.vue'
|
||
import Event from '~/components/templates/UltahStarter/Event.vue'
|
||
import Gallery from '~/components/templates/UltahStarter/Gallery.vue'
|
||
import ThankYou from '~/components/templates/UltahStarter/ThankYou.vue'
|
||
|
||
// Props dari induk
|
||
const props = defineProps({ data: Object })
|
||
|
||
// URL backend
|
||
const config = useRuntimeConfig()
|
||
const backendUrl = config.public.apiBaseUrl
|
||
|
||
// Data backend
|
||
const formData = computed(() => props.data.form || {})
|
||
|
||
// ✅ Gambar galeri
|
||
const galleryImages = computed(() => {
|
||
const f = formData.value
|
||
return [f.foto_1, f.foto_2, f.foto_3, f.foto_4, f.foto_5]
|
||
.filter(Boolean)
|
||
.map(img => `${backendUrl}/storage/${img.replace(/^public\//, '')}`)
|
||
})
|
||
|
||
// 🌍 Navigasi section
|
||
const currentSection = ref('landing')
|
||
const sections = [
|
||
{ key: 'introduction', label: 'Intro' },
|
||
{ key: 'event', label: 'Event' },
|
||
{ key: 'gallery', label: 'Gallery' },
|
||
{ key: 'thanks', label: 'Thanks' }
|
||
]
|
||
|
||
const switchSection = (section) => (currentSection.value = section)
|
||
|
||
// 🔊 Musik
|
||
const isPlaying = ref(false)
|
||
const toggleMusic = () => (isPlaying.value = !isPlaying.value)
|
||
|
||
// 💬 Buku tamu
|
||
const messages = ref([])
|
||
const addMessage = (msg) => messages.value.push(msg)
|
||
|
||
// 🎭 Komponen dinamis berdasarkan section
|
||
const currentComponent = computed(() => {
|
||
switch (currentSection.value) {
|
||
case 'landing': return Landing
|
||
case 'introduction': return Introduction
|
||
case 'event': return Event
|
||
case 'gallery': return Gallery
|
||
case 'guestbook': return GuestBook
|
||
case 'thanks': return ThankYou
|
||
default: return Landing
|
||
}
|
||
})
|
||
|
||
// 🎁 Props untuk tiap komponen
|
||
const componentProps = computed(() => ({
|
||
childName: formData.value.nama_panggilan,
|
||
guestName: props.data.nama_tamu,
|
||
age: formData.value.umur_yang_dirayakan,
|
||
childOrder: formData.value.anak_ke,
|
||
parentsName: `${formData.value.nama_bapak} & ${formData.value.nama_ibu}`,
|
||
childPhoto: formData.value.foto?.length
|
||
? `${backendUrl}/storage/${formData.value.foto[0]}`
|
||
: null,
|
||
hari_tanggal_acara: formData.value.hari_tanggal_acara,
|
||
waktu: formData.value.waktu,
|
||
alamat: formData.value.alamat,
|
||
link_gmaps: formData.value.link_gmaps,
|
||
hitung_mundur: formData.value.hitung_mundur,
|
||
images: galleryImages.value,
|
||
messages: messages.value,
|
||
jsonData: formData.value
|
||
}))
|
||
|
||
// Style aktif navigasi
|
||
const navClass = (key) =>
|
||
currentSection.value === key
|
||
? 'bg-gradient-to-r from-red-600 to-blue-600 text-white shadow-lg'
|
||
: 'hover:text-red-400 hover:bg-black/40'
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 🌟 Animations */
|
||
@keyframes spin-slow {
|
||
from { transform: rotate(0deg); }
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
@keyframes bounce-slow {
|
||
0%, 100% { transform: translateY(0); }
|
||
50% { transform: translateY(-10px); }
|
||
}
|
||
|
||
@keyframes pulse-slow {
|
||
0%, 100% { transform: scale(1); }
|
||
50% { transform: scale(1.1); }
|
||
}
|
||
|
||
/* ✨ Fade transition between sections */
|
||
.fade-enter-active, .fade-leave-active {
|
||
transition: opacity 0.8s ease, transform 0.8s ease;
|
||
}
|
||
.fade-enter-from, .fade-leave-to {
|
||
opacity: 0;
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
.animate-spin-slow { animation: spin-slow 40s linear infinite; }
|
||
.animate-bounce-slow { animation: bounce-slow 4s ease-in-out infinite; }
|
||
.animate-pulse-slow { animation: pulse-slow 3s ease-in-out infinite; }
|
||
|
||
.animate-fade-in {
|
||
animation: fade-in 0.6s ease-out;
|
||
}
|
||
@keyframes fade-in {
|
||
from { opacity: 0; transform: translateY(-10px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
</style>
|