Minions
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
BIN
proyek-frontend/public/minion01.png
Normal file
|
After Width: | Height: | Size: 274 KiB |
BIN
proyek-frontend/public/minion02.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
proyek-frontend/public/minion03f.png
Normal file
|
After Width: | Height: | Size: 408 KiB |
BIN
proyek-frontend/public/minion05.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
proyek-frontend/public/minion1.png
Normal file
|
After Width: | Height: | Size: 462 KiB |
BIN
proyek-frontend/public/minion2.png
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
proyek-frontend/public/minion3.png
Normal file
|
After Width: | Height: | Size: 282 KiB |
BIN
proyek-frontend/public/minion3h.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
proyek-frontend/public/minion4.png
Normal file
|
After Width: | Height: | Size: 991 KiB |
BIN
proyek-frontend/public/minion5e.png
Normal file
|
After Width: | Height: | Size: 672 KiB |