This commit is contained in:
Muzakki Parsaoran Siregar 2025-10-20 09:43:00 +07:00
parent 764732a3b7
commit 6d68f5eaf3
17 changed files with 244 additions and 25 deletions

View File

@ -1,10 +1,11 @@
<template>
<section class="w-full max-w-6xl mx-auto text-center px-4">
<section class="relative w-full max-w-6xl mx-auto text-center px-4 overflow-visible">
<h1 class="text-orange-700 text-3xl md:text-4xl font-bold mb-8">
Galeri Foto
</h1>
<div v-if="images && images.length" class="grid grid-cols-2 md:grid-cols-3 gap-4">
<!-- Grid Galeri -->
<div v-if="images && images.length" class="grid grid-cols-2 md:grid-cols-3 gap-4 relative z-10">
<img
v-for="(img, index) in images"
:key="index"
@ -14,10 +15,24 @@
/>
</div>
<div v-else class="text-orange-800 bg-yellow-100 py-8 rounded-2xl shadow-inner">
<div v-else class="text-orange-800 bg-yellow-100 py-8 rounded-2xl shadow-inner relative z-10">
<p class="text-lg font-semibold">Belum ada foto untuk ditampilkan</p>
</div>
</section>
<!-- 🟡 Minion Kanan Atas (Gelantungan) -->
<img
src="/minion5e.png"
alt="Minion kanan"
class="fixed top-0 right-0 w-32 md:w-64 drop-shadow-xl animate-hang z-50 translate-x-[20%] -translate-y-[2%]"
/>
<!-- 🟡 Minion Kiri Bawah -->
<img
src="/minion4.png"
alt="Minion kiri"
class="fixed bottom-0 left-0 w-43 md:w-64 drop-shadow-xl animate-wave z-50 -translate-x-[15%] translate-y-[5%]"
/>
</template>
<script setup>
@ -27,13 +42,36 @@ const props = defineProps({
required: true
}
})
console.log('Gallery props images:', props.images)
</script>
<style scoped>
img {
border: 3px solid rgba(255, 255, 255, 0.6);
/* 🪄 Animasi Minion Gelantungan (kanan atas) */
@keyframes hang {
0%, 100% {
transform: rotate(4deg) translateY(0);
}
50% {
transform: rotate(-4deg) translateY(6px);
}
}
/* 🪄 Animasi Minion Melambai (kiri bawah) */
@keyframes wave {
0%, 100% {
transform: rotate(2deg);
}
50% {
transform: rotate(-3deg);
}
}
.animate-hang {
animation: hang 3s ease-in-out infinite;
transform-origin: top center;
}
.animate-wave {
animation: wave 4s ease-in-out infinite;
transform-origin: bottom left;
}
</style>

View File

@ -1,25 +1,42 @@
<template>
<section class="w-full max-w-6xl mx-auto">
<div class="flex flex-col lg:flex-row items-center justify-between gap-8">
<!-- Bagian kiri: Teks -->
<div class="flex-1 text-center lg:text-left">
<h1 class="text-orange-700 text-2xl md:text-3xl font-bold mb-6">
Ulang Tahun Ke -{{ age }}
</h1>
<h2 class="text-orange-800 text-3xl md:text-5xl font-bold mb-6">
{{ childName }}
</h2>
<h3 class="text-orange-700 text-xl md:text-2xl font-semibold mb-4">
Anak Ke -{{ childOrder }}
</h3>
<!-- Gambar Minion -->
<div class="flex justify-center lg:justify-start mb-6">
<img
:src="getFullPhotoUrl(minionImage)"
alt="Minion"
class="w-85 h-auto object-contain drop-shadow-lg hover:scale-110 transition-transform duration-300"
/>
</div>
<h4 class="text-orange-800 text-2xl md:text-3xl font-bold mb-8">
{{ parentNames }}
</h4>
</div>
<!-- Bagian kanan: Foto Anak -->
<div class="flex-1">
<div class="bg-yellow-300/60 rounded-3xl p-8 shadow-xl">
<img :src="getFullPhotoUrl(childPhoto)"
:alt="childName"
class="w-full h-80 object-cover rounded-2xl shadow-lg">
<img
:src="getFullPhotoUrl(childPhoto)"
:alt="childName"
class="w-full h-80 object-cover rounded-2xl shadow-lg"
/>
</div>
</div>
</div>
@ -35,11 +52,16 @@ const { age, childName, childOrder, parentNames, childPhoto } = defineProps({
childPhoto: [String, Array]
})
// 🟡 Lokasi file Minion kamu
const minionImage = '/minion2.png'
// Fungsi untuk mengatur URL gambar
const getFullPhotoUrl = (photo) => {
if (!photo) return '/assets/img/child-placeholder.jpg'
if (photo.startsWith('http')) return photo
if (photo.startsWith('/storage')) return `http://localhost:8000${photo}`
if (photo.startsWith('/')) return photo // untuk file lokal di /public/
return `http://localhost:8000/storage/${photo}`
}
</script>

View File

@ -5,7 +5,7 @@
<!-- Left Side (Image) -->
<div class="flex-1 relative mt-10 lg:mt-0 flex justify-center lg:justify-start">
<img
src="/pria.jpg"
src="/minion1.png"
alt="Character"
class="w-64 md:w-80 lg:w-96 drop-shadow-2xl animate-bounce-slow"
/>

View File

@ -2,7 +2,7 @@
<section
class="min-h-screen flex flex-col justify-center items-center text-center bg-gradient-to-br from-yellow-200 via-yellow-300 to-yellow-400 px-6 py-12 relative overflow-hidden"
>
<!-- Background Ornamen -->
<!-- Ornamen Cahaya -->
<div class="absolute inset-0 opacity-10 pointer-events-none">
<div class="absolute top-10 left-10 w-24 h-24 bg-orange-500 rounded-full blur-2xl"></div>
<div class="absolute bottom-10 right-10 w-32 h-32 bg-yellow-600 rounded-full blur-3xl"></div>
@ -16,9 +16,7 @@
Terima Kasih
</h1>
<p
class="text-orange-800 text-lg md:text-xl mb-8 leading-relaxed animate-fade-in delay-100"
>
<p class="text-orange-800 text-lg md:text-xl mb-8 leading-relaxed animate-fade-in delay-100">
Kehadiran dan doa restu Bapak/Ibu/Saudara/i merupakan kebahagiaan besar bagi kami.
</p>
@ -33,6 +31,20 @@
</p>
</div>
</div>
<!-- 🟡 Minion Kiri -->
<img
src="/minion03f.png"
alt="Minion kiri"
class="fixed bottom-[-5px] left-0 w-34 md:w-51 z-40 drop-shadow-xl animate-bounce-slow -translate-x-[-18%] transition-transform duration-500 ease-in-out hover:scale-110 hover:rotate-6 cursor-pointer"
/>
<!-- 🟡 Minion Kanan -->
<img
src="/minion01.png"
alt="Minion kanan"
class="fixed bottom-[20px] right-0 w-34 md:w-56 z-40 drop-shadow-xl animate-bounce-slow translate-x-[-16%] transition-transform duration-500 ease-in-out hover:scale-110 hover:-rotate-6 cursor-pointer"
/>
</section>
</template>
@ -46,6 +58,7 @@ defineProps({
</script>
<style scoped>
/* ✨ Animasi Fade-in Konten */
@keyframes fade-in {
from {
opacity: 0;
@ -65,4 +78,31 @@ defineProps({
.delay-200 {
animation-delay: 0.2s;
}
/* 🌀 Animasi Minion Kiri Melambai */
@keyframes wave {
0%, 100% {
transform: rotate(3deg);
}
50% {
transform: rotate(-3deg);
}
}
.animate-wave {
animation: wave 3s ease-in-out infinite;
transform-origin: bottom left;
}
/* 🪩 Animasi Minion Kanan Loncat Pelan */
@keyframes bounce-slow {
0%, 100% {
transform: translateY(0) scale(1);
}
50% {
transform: translateY(-10px) scale(1.02);
}
}
.animate-bounce-slow {
animation: bounce-slow 4s ease-in-out infinite;
}
</style>

View File

@ -18,12 +18,12 @@
<!-- Child Photo -->
<div class="w-48 h-60 overflow-hidden rounded-t-3xl mx-auto mb-6">
<img :src="data.childPhoto || '/pria.jpg'" alt="Child" class="w-full h-full object-cover"/>
<img :src="formData.childPhoto || '/pria.jpg'" alt="Child" class="w-full h-full object-cover"/>
</div>
<!-- Child Name -->
<div class="bg-blue-800/50 border border-yellow-400 rounded-md px-6 py-2 mb-4 inline-block">
<h1 class="text-yellow-400 font-bold text-xl">{{ data.childName || 'SATRIA HUDA DINATA' }}</h1>
<h1 class="text-yellow-400 font-bold text-xl">{{ formData.nama_panggilan || 'SATRIA HUDA DINATA' }}</h1>
</div>
<!-- Child Order & Parents -->
@ -31,7 +31,7 @@
Putra Ke Dua Dari
</div>
<div class="text-yellow-400 font-semibold text-xl">
{{ data.fatherName || 'Bpk H. Munawar Huda, S.H.' }} & {{ data.motherName || 'Ibu Hj. Dinah, A.M.Keb' }}
{{ formData.fatherName || 'Bpk H. Munawar Huda, S.H.' }} & {{ formData.motherName || 'Ibu Hj. Dinah, A.M.Keb' }}
</div>
</div>
</div>
@ -44,6 +44,16 @@ const props = defineProps({
default: () => ({})
}
})
const formData = computed(() => {
const result = {}
props.data?.fields?.forEach(f => {
result[f.name] = f.value
})
return result
})
onMounted(() => {
console.log("🧾 Introduction Data:", props.data)
})
</script>
<style scoped>

View File

@ -49,11 +49,7 @@
<!-- Introduction -->
<KhitanIntroduction
v-if="currentSection === 'introduction'"
:childName="formData.nama_lengkap"
:parentsName="`${formData.nama_bapak} & ${formData.nama_ibu}`"
:childOrder="formData.anak_ke"
:childPhoto="`${backendUrl}/${formData.foto_1}`"
:age="formData.umur_yang_dirayakan"
:data="formData"
/>
<!-- Event -->
@ -144,6 +140,11 @@ const navClass = (s) =>
currentSection.value === s
? 'text-blue-800 underline'
: 'hover:text-blue-700'
onMounted(() => {
console.log("🧾 formData:", formData.value)
console.log("🖼️ galleryImages:", galleryImages.value)
})
</script>
<style scoped>

View File

@ -0,0 +1,108 @@
<template>
<div class="min-h-screen bg-gradient-to-br from-yellow-200 via-yellow-300 to-yellow-400 relative overflow-hidden">
<!-- Navigation -->
<nav v-if="currentSection !== 'landing'" class="absolute top-4 left-1/2 transform -translate-x-1/2 z-20">
<ul
class="flex space-x-6 bg-white/40 backdrop-blur-md px-6 py-3 rounded-full shadow-md text-sm font-semibold text-gray-800">
<li><button @click="switchSection('introduction')" :class="navClass('introduction')">Intro</button></li>
<li><button @click="switchSection('event')" :class="navClass('event')">Event</button></li>
<li><button @click="switchSection('gallery')" :class="navClass('gallery')">Gallery</button></li>
<li><button @click="switchSection('thanks')" :class="navClass('thanks')">Thanks</button></li>
</ul>
</nav>
<main
class="relative z-10 min-h-screen flex items-center justify-center p-4 transition-all duration-700 ease-in-out">
<!-- Landing Section -->
<Landing
v-if="currentSection === 'landing'"
:childName="formData.nama_lengkap"
@open-invitation="switchSection('introduction')"
/>
<!-- Introduction Section -->
<Introduction
v-if="currentSection === 'introduction'"
:childName="formData.nama_lengkap"
:age="formData.umur_yang_dirayakan"
:childPhoto="photoUrl(formData.foto_1)"
/>
<!-- Event Section -->
<Event
v-if="currentSection === 'event'"
:hari_tanggal_acara="formData.hari_tanggal_acara"
:waktu="formData.waktu"
:alamat="formData.alamat"
/>
<!-- Gallery Section -->
<Gallery
v-if="currentSection === 'gallery'"
:images="galleryImages"
/>
<!-- Thank You Section -->
<ThankYou
v-if="currentSection === 'thanks'"
:childName="formData.nama_lengkap"
:jsonData="formData"
/>
</main>
</div>
</template>
<script setup>
import { ref, computed, watchEffect } from 'vue'
import { useRuntimeConfig } from '#app'
// Komponen dasar
import Landing from '~/components/templates/Ultah/Landing.vue'
import Introduction from '~/components/templates/Ultah/Introduction.vue'
import Event from '~/components/templates/Ultah/Event.vue'
import Gallery from '~/components/templates/Ultah/Gallery.vue'
import ThankYou from '~/components/templates/Ultah/ThankYou.vue'
// Props dari halaman /p/[code].vue
const props = defineProps({
data: { type: Object, required: true }
})
// Runtime config
const config = useRuntimeConfig()
const backendUrl = config.public.apiBaseUrl
// Data dari backend
const formData = computed(() => props.data.form || {})
// Fungsi pembentuk URL aman untuk gambar
const photoUrl = (path) => {
if (!path) return null
if (path.startsWith('storage/') || path.startsWith('uploads/')) {
return `${backendUrl}/${path}`
}
return `${backendUrl}/storage/${path}`
}
// Ambil foto_1 dan foto_2 untuk galeri
const galleryImages = computed(() => {
return [formData.value.foto_1, formData.value.foto_2]
.filter(Boolean)
.map(photoUrl)
})
// Navigasi antar section
const currentSection = ref('landing')
const switchSection = (s) => (currentSection.value = s)
// Style untuk navigasi aktif
const navClass = (s) =>
currentSection.value === s ? 'text-orange-700 underline' : 'hover:text-orange-600'
// Debugging
watchEffect(() => {
console.log('🎂 formData Starter:', formData.value)
console.log('🖼️ galleryImages:', galleryImages.value)
})
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 KiB