Merge branch 'baru' of https://git.abbauf.com/Magang-2025/Undangan into baru
This commit is contained in:
commit
d243546a2c
@ -1,158 +1,171 @@
|
||||
<!-- components/forms/KhitanForm.vue -->
|
||||
<template>
|
||||
<div class="h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden">
|
||||
<div class="min-h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden">
|
||||
<!-- Background Pattern -->
|
||||
<div class="absolute inset-0 bg-pattern opacity-30"></div>
|
||||
<div class="absolute inset-0 bg-pattern opacity-20"></div>
|
||||
|
||||
<!-- Decorative Lanterns -->
|
||||
<div class="absolute top-8 left-8 animate-sway">
|
||||
<div class="w-16 h-20 relative">
|
||||
<!-- Lantern Chain -->
|
||||
<div class="absolute top-0 left-1/2 w-0.5 h-8 bg-yellow-400 transform -translate-x-1/2"></div>
|
||||
<!-- Lantern Body -->
|
||||
<div class="absolute bottom-0 w-full">
|
||||
<svg viewBox="0 0 64 80" class="w-full h-full">
|
||||
<defs>
|
||||
<linearGradient id="lanternGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#FFD700"/>
|
||||
<stop offset="50%" style="stop-color:#FFA500"/>
|
||||
<stop offset="100%" style="stop-color:#FFD700"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<ellipse cx="32" cy="15" rx="28" ry="12" fill="url(#lanternGradient)"/>
|
||||
<rect x="8" y="12" width="48" height="40" rx="24" fill="url(#lanternGradient)" opacity="0.9"/>
|
||||
<ellipse cx="32" cy="55" rx="28" ry="12" fill="url(#lanternGradient)"/>
|
||||
<rect x="20" y="20" width="24" height="4" rx="2" fill="#1e40af" opacity="0.6"/>
|
||||
<rect x="20" y="30" width="24" height="4" rx="2" fill="#1e40af" opacity="0.6"/>
|
||||
<rect x="20" y="40" width="24" height="4" rx="2" fill="#1e40af" opacity="0.6"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute top-8 right-8 animate-sway animation-delay-1000">
|
||||
<div class="w-16 h-20 relative">
|
||||
<!-- Lantern Chain -->
|
||||
<div class="absolute top-0 left-1/2 w-0.5 h-8 bg-yellow-400 transform -translate-x-1/2"></div>
|
||||
<!-- Lantern Body -->
|
||||
<div class="absolute bottom-0 w-full">
|
||||
<svg viewBox="0 0 64 80" class="w-full h-full">
|
||||
<!-- pakai gradient yang sama -->
|
||||
<use href="#lanternGradient"></use>
|
||||
<ellipse cx="32" cy="15" rx="28" ry="12" fill="url(#lanternGradient)"/>
|
||||
<rect x="8" y="12" width="48" height="40" rx="24" fill="url(#lanternGradient)" opacity="0.9"/>
|
||||
<ellipse cx="32" cy="55" rx="28" ry="12" fill="url(#lanternGradient)"/>
|
||||
<rect x="20" y="20" width="24" height="4" rx="2" fill="#1e40af" opacity="0.6"/>
|
||||
<rect x="20" y="30" width="24" height="4" rx="2" fill="#1e40af" opacity="0.6"/>
|
||||
<rect x="20" y="40" width="24" height="4" rx="2" fill="#1e40af" opacity="0.6"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="absolute top-4 left-4 md:top-8 md:left-8 animate-sway">
|
||||
<Lantern />
|
||||
</div>
|
||||
<div class="absolute top-4 right-4 md:top-8 md:right-8 animate-sway animation-delay-1000">
|
||||
<Lantern />
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="relative z-10 h-full flex items-center justify-center px-6">
|
||||
<div class="text-center max-w-6xl mx-auto">
|
||||
|
||||
<div class="relative z-10 min-h-screen flex items-center justify-center px-4 md:px-8 py-12">
|
||||
<div class="text-center max-w-5xl mx-auto">
|
||||
<!-- Bismillah -->
|
||||
<div class="mb-8 animate-fade-in-down">
|
||||
<h1 class="text-yellow-400 text-2xl md:text-3xl font-bold mb-6 arabic-text">
|
||||
<div class="mb-10 animate-fade-in-down">
|
||||
<h1 class="text-yellow-400 text-xl md:text-3xl lg:text-4xl font-bold mb-6 arabic-text">
|
||||
بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Event Description -->
|
||||
<div class="mb-8 animate-fade-in-up animation-delay-300">
|
||||
<p class="text-white text-base md:text-lg leading-relaxed max-w-4xl mx-auto mb-6">
|
||||
<div class="mb-12 animate-fade-in-up animation-delay-300">
|
||||
<p class="text-white text-sm md:text-base lg:text-lg leading-relaxed max-w-3xl mx-auto">
|
||||
Dengan memohon rahmat dan ridho Allah Subhanahu wa Ta'ala, kami<br>
|
||||
mengundang Bapak/Ibu/Saudara/i untuk hadir dan berbagi kebahagiaan<br>
|
||||
dalam acara Khitanan putra kami tercinta, yang insya Allah akan<br>
|
||||
diselenggarakan pada :
|
||||
diselenggarakan pada:
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Event Details Container -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
||||
|
||||
<!-- Date & Countdown -->
|
||||
<!-- Event Details -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 md:gap-8 mb-12">
|
||||
<!-- Acara Khitanan -->
|
||||
<div class="animate-fade-in-left animation-delay-600">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-8 border border-yellow-400/30">
|
||||
<h3 class="text-yellow-400 text-2xl font-bold mb-6 font-script">
|
||||
Jum'at/Sabtu
|
||||
<div class="bg-white/10 backdrop-blur-md rounded-2xl p-6 md:p-8 border border-yellow-400/30 shadow-lg">
|
||||
<h3 class="text-yellow-400 text-xl md:text-2xl font-bold mb-4 font-script">
|
||||
Khitanan
|
||||
</h3>
|
||||
|
||||
<div class="text-white text-3xl md:text-4xl font-bold mb-8">
|
||||
{{ eventDate }}
|
||||
<div class="text-white text-2xl md:text-3xl lg:text-4xl font-bold mb-6">
|
||||
{{ eventDate }} ({{ eventDay }})
|
||||
</div>
|
||||
|
||||
<!-- Countdown Timer -->
|
||||
<div class="grid grid-cols-4 gap-4 mb-6">
|
||||
<div
|
||||
v-for="(item, index) in countdownItems"
|
||||
:key="index"
|
||||
class="bg-blue-800/50 rounded-lg p-4 text-center border border-yellow-400/20"
|
||||
>
|
||||
<div class="text-yellow-400 text-2xl md:text-3xl font-bold">
|
||||
{{ item.value }}
|
||||
</div>
|
||||
<div class="text-white text-sm uppercase tracking-wider">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add to Calendar Button -->
|
||||
<button
|
||||
@click="addToCalendar"
|
||||
class="bg-yellow-400 hover:bg-yellow-500 text-blue-900 px-6 py-3 rounded-full font-semibold transition-all duration-300 transform hover:scale-105"
|
||||
>
|
||||
Add to Calendar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Location & Map -->
|
||||
<div class="animate-fade-in-right animation-delay-600">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-8 border border-yellow-400/30">
|
||||
<div class="text-white mb-6">
|
||||
<p class="text-lg mb-2">Pukul {{ eventTime }}</p>
|
||||
<p class="text-lg mb-2">{{ eventLocation }}</p>
|
||||
<p class="text-base opacity-80">{{ eventAddress }}</p>
|
||||
<p class="text-sm md:text-base mb-2">🕓 {{ eventTime }}</p>
|
||||
<p class="text-sm md:text-base mb-2">📍 {{ eventLocation }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Map Placeholder -->
|
||||
<div class="bg-gray-300 rounded-lg h-32 mb-6 relative overflow-hidden">
|
||||
<!-- Map -->
|
||||
<div class="bg-gray-300 rounded-lg h-40 md:h-48 relative overflow-hidden mb-6">
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-blue-400 to-blue-600 opacity-50"></div>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<Icon name="lucide:map-pin" class="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<img
|
||||
src="https://via.placeholder.com/400x200/4299e1/ffffff?text=Map+Preview"
|
||||
:src="eventMap"
|
||||
alt="Location Map"
|
||||
class="w-full h-full object-cover opacity-80"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Direction Button -->
|
||||
<button
|
||||
@click="openMap"
|
||||
class="bg-yellow-400 hover:bg-yellow-500 text-blue-900 px-6 py-3 rounded-full font-semibold transition-all duration-300 transform hover:scale-105"
|
||||
<!-- Countdown -->
|
||||
<div class="grid grid-cols-4 gap-3 md:gap-4 mb-6">
|
||||
<div
|
||||
v-for="(item, index) in countdownItemsAcara"
|
||||
:key="index"
|
||||
class="bg-blue-800/50 rounded-lg p-3 md:p-4 text-center border border-yellow-400/20"
|
||||
>
|
||||
Direction
|
||||
<div class="text-yellow-400 text-xl md:text-2xl font-bold">
|
||||
{{ item.value }}
|
||||
</div>
|
||||
<div class="text-white text-xs md:text-sm uppercase tracking-wider">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add to Calendar -->
|
||||
<button
|
||||
@click="addToCalendar"
|
||||
class="bg-yellow-400 hover:bg-yellow-500 text-blue-900 px-6 py-2 md:py-3 rounded-full font-semibold transition-all duration-300 transform hover:scale-105"
|
||||
>
|
||||
Add to Calendar
|
||||
</button>
|
||||
<!-- Map Link -->
|
||||
<div class="mt-4">
|
||||
<a
|
||||
v-if="eventAddress"
|
||||
:href="eventAddress"
|
||||
target="_blank"
|
||||
class="text-yellow-400 text-sm md:text-base underline hover:text-yellow-300"
|
||||
>
|
||||
Lihat di Google Maps
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Acara Syukuran -->
|
||||
<div class="animate-fade-in-right animation-delay-600">
|
||||
<div class="bg-white/10 backdrop-blur-md rounded-2xl p-6 md:p-8 border border-yellow-400/30 shadow-lg">
|
||||
<h3 class="text-yellow-400 text-xl md:text-2xl font-bold mb-4 font-script">
|
||||
Syukuran
|
||||
</h3>
|
||||
<div class="text-white text-2xl md:text-3xl lg:text-4xl font-bold mb-6">
|
||||
{{ syukuranDate }} ({{ syukuranDay }})
|
||||
</div>
|
||||
<div class="text-white mb-6">
|
||||
<p class="text-sm md:text-base mb-2">🕓 {{ syukuranTime }}</p>
|
||||
<p class="text-sm md:text-base mb-2">📍 {{ syukuranAddress }}</p>
|
||||
</div>
|
||||
<!-- Map -->
|
||||
<div class="bg-gray-300 rounded-lg h-40 md:h-48 relative overflow-hidden mb-6">
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-blue-400 to-blue-600 opacity-50"></div>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<Icon name="lucide:map-pin" class="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<img
|
||||
:src="syukuranMapImage"
|
||||
alt="Syukuran Location Map"
|
||||
class="w-full h-full object-cover opacity-80"
|
||||
/>
|
||||
</div>
|
||||
<!-- Countdown -->
|
||||
<div class="grid grid-cols-4 gap-3 md:gap-4 mb-6">
|
||||
<div
|
||||
v-for="(item, index) in countdownItemsSyukuran"
|
||||
:key="index"
|
||||
class="bg-blue-800/50 rounded-lg p-3 md:p-4 text-center border border-yellow-400/20"
|
||||
>
|
||||
<div class="text-yellow-400 text-xl md:text-2xl font-bold">
|
||||
{{ item.value }}
|
||||
</div>
|
||||
<div class="text-white text-xs md:text-sm uppercase tracking-wider">
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add to Calendar -->
|
||||
<button
|
||||
@click="addToCalendar"
|
||||
class="bg-yellow-400 hover:bg-yellow-500 text-blue-900 px-6 py-2 md:py-3 rounded-full font-semibold transition-all duration-300 transform hover:scale-105"
|
||||
>
|
||||
Add to Calendar
|
||||
</button>
|
||||
<!-- Map Link -->
|
||||
<div class="mt-4">
|
||||
<a
|
||||
v-if="syukuranMap"
|
||||
:href="syukuranMap"
|
||||
target="_blank"
|
||||
class="text-yellow-400 text-sm md:text-base underline hover:text-yellow-300"
|
||||
>
|
||||
Lihat di Google Maps
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Decorative Elements -->
|
||||
<!-- Decorative Element -->
|
||||
<div class="absolute inset-0 pointer-events-none overflow-hidden">
|
||||
<div class="absolute top-0 left-1/2 transform -translate-x-1/2">
|
||||
<svg viewBox="0 0 200 100" class="w-48 h-24 text-yellow-400 opacity-20">
|
||||
<path d="M100 20 Q120 0 140 20 Q160 40 140 60 Q120 40 100 60 Q80 40 60 60 Q40 40 60 20 Q80 0 100 20" fill="currentColor"/>
|
||||
<svg viewBox="0 0 200 100" class="w-40 md:w-48 h-20 md:h-24 text-yellow-400 opacity-20">
|
||||
<path
|
||||
d="M100 20 Q120 0 140 20 Q160 40 140 60 Q120 40 100 60 Q80 40 60 60 Q40 40 60 20 Q80 0 100 20"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@ -162,86 +175,188 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
|
||||
// Props
|
||||
// Props sesuai dengan parent dan JSON
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
hari_tanggal_acara: { type: String, default: '' },
|
||||
waktu: { type: String, default: '' },
|
||||
alamat: { type: String, default: '' },
|
||||
link_gmaps: { type: String, default: '' },
|
||||
hitung_mundur_mulai: { type: String, default: '' },
|
||||
hari_tanggal_syukuran: { type: String, default: '' },
|
||||
waktu_syukuran: { type: String, default: '' },
|
||||
alamat_syukuran: { type: String, default: '' },
|
||||
link_gmaps_syukuran: { type: String, default: '' },
|
||||
hitung_mundur_selesai: { type: String, default: '' },
|
||||
nama_panggilan: { type: String, default: '' }
|
||||
})
|
||||
|
||||
// computed agar template tidak merah
|
||||
const eventDate = computed(() => props.data.eventDate || '20-21 Juni 2025')
|
||||
const eventTime = computed(() => props.data.eventTime || '09.00 WIB s.d Selesai')
|
||||
const eventLocation = computed(() => props.data.eventLocation || 'TMC Mangrove, Tanjung Pasir')
|
||||
const eventAddress = computed(() => props.data.eventAddress || 'Desa Tanjung Pasir')
|
||||
/* ==========================
|
||||
🎯 EVENT KHITAN
|
||||
========================== */
|
||||
const eventDate = computed(() => props.hari_tanggal_acara || '2025-10-31')
|
||||
const eventDay = computed(() => new Date(eventDate.value).toLocaleDateString('id-ID', { weekday: 'long' }))
|
||||
const eventTime = computed(() => props.waktu || '11:11')
|
||||
const eventLocation = computed(() => props.alamat || 'Tempat belum ditentukan')
|
||||
const eventAddress = computed(() => props.link_gmaps || '')
|
||||
const eventMap = computed(() => `https://maps.googleapis.com/maps/api/staticmap?center=${encodeURIComponent(eventLocation.value)}&zoom=15&size=400x200&markers=color:red|${encodeURIComponent(eventLocation.value)}`)
|
||||
|
||||
const childPhoto = computed(() => props.data.childPhoto || '/images/khitan/child-photo.jpg')
|
||||
const childName = computed(() => props.data.childName || 'Satria Huda Dinata')
|
||||
const childTitle = computed(() => props.data.childTitle || 'SATRIA HUDA DINATA')
|
||||
const childSubtitle = computed(() => props.data.childSubtitle || 'Putra Ke Dua Dari')
|
||||
const fatherName = computed(() => props.data.fatherName || 'Bpk H. Munawar Huda, S.H.')
|
||||
const motherName = computed(() => props.data.motherName || 'Ibu Hj. Dinah, A.M.Keb')
|
||||
/* ==========================
|
||||
🎉 SYUKURAN
|
||||
========================== */
|
||||
const syukuranDate = computed(() => props.hari_tanggal_syukuran || '')
|
||||
const syukuranDay = computed(() => syukuranDate.value ? new Date(syukuranDate.value).toLocaleDateString('id-ID', { weekday: 'long' }) : '')
|
||||
const syukuranTime = computed(() => props.waktu_syukuran || '')
|
||||
const syukuranAddress = computed(() => props.alamat_syukuran || '')
|
||||
const syukuranMap = computed(() => props.link_gmaps_syukuran || '')
|
||||
const syukuranMapImage = computed(() => `https://maps.googleapis.com/maps/api/staticmap?center=${encodeURIComponent(syukuranAddress.value)}&zoom=15&size=400x200&markers=color:red|${encodeURIComponent(syukuranAddress.value)}`)
|
||||
|
||||
// countdown
|
||||
const countdown = ref({ days: 7, hours: 11, minutes: 21, seconds: 45 })
|
||||
let countdownInterval = null
|
||||
/* ==========================
|
||||
⏱️ COUNTDOWN
|
||||
========================== */
|
||||
const countdownAcara = ref({ days: 0, hours: 0, minutes: 0, seconds: 0 })
|
||||
const countdownSyukuran = ref({ days: 0, hours: 0, minutes: 0, seconds: 0 })
|
||||
const countdownInterval = ref(null)
|
||||
|
||||
const countdownItems = computed(() => [
|
||||
{ value: String(countdown.value.days).padStart(2, '0'), label: 'D' },
|
||||
{ value: String(countdown.value.hours).padStart(2, '0'), label: 'H' },
|
||||
{ value: String(countdown.value.minutes).padStart(2, '0'), label: 'M' },
|
||||
{ value: String(countdown.value.seconds).padStart(2, '0'), label: 'S' }
|
||||
const countdownItemsAcara = computed(() => [
|
||||
{ value: String(countdownAcara.value.days).padStart(2, '0'), label: 'D' },
|
||||
{ value: String(countdownAcara.value.hours).padStart(2, '0'), label: 'H' },
|
||||
{ value: String(countdownAcara.value.minutes).padStart(2, '0'), label: 'M' },
|
||||
{ value: String(countdownAcara.value.seconds).padStart(2, '0'), label: 'S' }
|
||||
])
|
||||
|
||||
const countdownItemsSyukuran = computed(() => [
|
||||
{ value: String(countdownSyukuran.value.days).padStart(2, '0'), label: 'D' },
|
||||
{ value: String(countdownSyukuran.value.hours).padStart(2, '0'), label: 'H' },
|
||||
{ value: String(countdownSyukuran.value.minutes).padStart(2, '0'), label: 'M' },
|
||||
{ value: String(countdownSyukuran.value.seconds).padStart(2, '0'), label: 'S' }
|
||||
])
|
||||
|
||||
const updateCountdown = () => {
|
||||
const eventDateObj = new Date('2025-06-20T09:00:00')
|
||||
const now = new Date()
|
||||
const diff = eventDateObj.getTime() - now.getTime()
|
||||
|
||||
// Countdown untuk acara khitanan
|
||||
if (props.hitung_mundur_mulai) {
|
||||
const eventDateObj = new Date(props.hitung_mundur_mulai)
|
||||
const diff = eventDateObj - now
|
||||
if (diff > 0) {
|
||||
countdown.value = {
|
||||
countdownAcara.value = {
|
||||
days: Math.floor(diff / (1000 * 60 * 60 * 24)),
|
||||
hours: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
|
||||
minutes: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
|
||||
seconds: Math.floor((diff % (1000 * 60)) / 1000)
|
||||
hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
|
||||
minutes: Math.floor((diff / (1000 * 60)) % 60),
|
||||
seconds: Math.floor((diff / 1000) % 60)
|
||||
}
|
||||
} else {
|
||||
countdown.value = { days: 0, hours: 0, minutes: 0, seconds: 0 }
|
||||
if (countdownInterval) clearInterval(countdownInterval)
|
||||
countdownAcara.value = { days: 0, hours: 0, minutes: 0, seconds: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
// Countdown untuk acara syukuran
|
||||
if (props.hitung_mundur_selesai) {
|
||||
const syukuranDateObj = new Date(props.hitung_mundur_selesai)
|
||||
const diff = syukuranDateObj - now
|
||||
if (diff > 0) {
|
||||
countdownSyukuran.value = {
|
||||
days: Math.floor(diff / (1000 * 60 * 60 * 24)),
|
||||
hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
|
||||
minutes: Math.floor((diff / (1000 * 60)) % 60),
|
||||
seconds: Math.floor((diff / 1000) % 60)
|
||||
}
|
||||
} else {
|
||||
countdownSyukuran.value = { days: 0, hours: 0, minutes: 0, seconds: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================
|
||||
📅 ACTIONS
|
||||
========================== */
|
||||
const addToCalendar = () => {
|
||||
const eventDetails = {
|
||||
title: `Tasyakuran Khitan ${childName.value}`,
|
||||
start: '20250620T090000Z',
|
||||
end: '20250621T170000Z',
|
||||
description: 'Undangan Tasyakuran Khitan',
|
||||
location: eventLocation.value
|
||||
}
|
||||
const url = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(eventDetails.title)}&dates=${eventDetails.start}/${eventDetails.end}&details=${encodeURIComponent(eventDetails.description)}&location=${encodeURIComponent(eventDetails.location)}`
|
||||
const title = `Tasyakuran Khitan ${props.nama_panggilan || 'Putra Kami'}`
|
||||
const start = new Date(props.hitung_mundur_mulai).toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'
|
||||
const end = new Date(new Date(props.hitung_mundur_mulai).getTime() + 3 * 60 * 60 * 1000).toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'
|
||||
|
||||
const url = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(title)}&dates=${start}/${end}&location=${encodeURIComponent(eventLocation.value)}`
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
const openMap = () => {
|
||||
const location = encodeURIComponent(eventLocation.value)
|
||||
window.open(`https://maps.google.com/maps?q=${location}`, '_blank')
|
||||
}
|
||||
|
||||
const handleImageError = (e) => {
|
||||
e.target.src = 'https://via.placeholder.com/128x128/4299e1/ffffff?text=Photo'
|
||||
}
|
||||
|
||||
/* ==========================
|
||||
⏳ LIFECYCLE
|
||||
========================== */
|
||||
onMounted(() => {
|
||||
updateCountdown()
|
||||
countdownInterval = setInterval(updateCountdown, 1000)
|
||||
countdownInterval.value = setInterval(updateCountdown, 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (countdownInterval) clearInterval(countdownInterval)
|
||||
if (countdownInterval.value) {
|
||||
clearInterval(countdownInterval.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ... tetap sama dengan punyamu, kecuali hapus selector .grid-cols-1.lg\:grid-cols-2 */
|
||||
/* Animations & bg-pattern */
|
||||
.bg-pattern {
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><defs><pattern id='pattern' x='0' y='0' width='20' height='20' patternUnits='userSpaceOnUse'><path d='M10 5 L15 10 L10 15 L5 10 Z' fill='none' stroke='rgba(255,255,255,0.1)' stroke-width='0.5'/></pattern></defs><rect width='100' height='100' fill='url(%23pattern)'/></svg>");
|
||||
}
|
||||
|
||||
.animate-sway {
|
||||
animation: sway 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes sway {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
.animate-fade-in-down {
|
||||
animation: fadeInDown 1s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-fade-in-up {
|
||||
animation: fadeInUp 1s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-fade-in-left {
|
||||
animation: fadeInLeft 1s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-fade-in-right {
|
||||
animation: fadeInRight 1s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
from { opacity: 0; transform: translateY(-20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes fadeInLeft {
|
||||
from { opacity: 0; transform: translateX(-20px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
@keyframes fadeInRight {
|
||||
from { opacity: 0; transform: translateX(20px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
.animation-delay-300 {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.animation-delay-600 {
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
.animation-delay-1000 {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.arabic-text {
|
||||
font-family: 'Noto Naskh Arabic', serif;
|
||||
}
|
||||
</style>
|
||||
@ -1,12 +1,10 @@
|
||||
<!-- components/shared/Gallery.vue -->
|
||||
<template>
|
||||
<div class="h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden">
|
||||
<div class="min-h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden">
|
||||
<!-- Background Pattern -->
|
||||
<div class="absolute inset-0 bg-pattern opacity-30"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="relative z-10 h-full flex flex-col items-center justify-center px-6 py-12">
|
||||
|
||||
<div class="relative z-10 flex flex-col items-center justify-center px-6 py-12">
|
||||
<!-- Gallery Title -->
|
||||
<div class="text-center mb-8 animate-fade-in-down">
|
||||
<h1 class="text-yellow-400 text-4xl md:text-5xl font-bold mb-6 font-script">
|
||||
@ -21,51 +19,55 @@
|
||||
|
||||
<!-- Photo Gallery Grid -->
|
||||
<div class="flex-1 max-w-4xl mx-auto w-full animate-fade-in-up animation-delay-300">
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-4 h-full max-h-96">
|
||||
|
||||
<!-- Large Photo (spans 2 rows on medium+ screens) -->
|
||||
<div class="md:col-span-1 md:row-span-2 relative group cursor-pointer" @click="openModal(0)">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-2xl overflow-hidden h-full border border-yellow-400/30 hover:border-yellow-400/60 transition-all duration-300">
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<!-- Foto pertama (besar) -->
|
||||
<div
|
||||
v-if="images.length > 0"
|
||||
class="md:col-span-1 md:row-span-2 relative group cursor-pointer"
|
||||
@click="openModal(0)"
|
||||
>
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-2xl overflow-hidden border border-yellow-400/30 hover:border-yellow-400/60 transition-all duration-300">
|
||||
<img
|
||||
:src="galleryImages[0]?.src || placeholderImage"
|
||||
:alt="galleryImages[0]?.alt || 'Gallery Image 1'"
|
||||
:src="images[0]"
|
||||
:alt="`Gallery Image 1`"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<!-- Overlay -->
|
||||
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors duration-300 flex items-center justify-center">
|
||||
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 flex items-center justify-center transition-colors duration-300">
|
||||
<Icon name="lucide:zoom-in" class="w-8 h-8 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Medium Photos -->
|
||||
<!-- Foto lainnya -->
|
||||
<div
|
||||
v-for="(image, index) in galleryImages.slice(1, 5)"
|
||||
v-for="(image, index) in images.slice(1)"
|
||||
:key="index + 1"
|
||||
class="relative group cursor-pointer"
|
||||
@click="openModal(index + 1)"
|
||||
>
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-xl overflow-hidden h-full border border-yellow-400/30 hover:border-yellow-400/60 transition-all duration-300">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-xl overflow-hidden border border-yellow-400/30 hover:border-yellow-400/60 transition-all duration-300">
|
||||
<img
|
||||
:src="image.src || placeholderImage"
|
||||
:alt="image.alt || `Gallery Image ${index + 2}`"
|
||||
:src="image"
|
||||
:alt="`Gallery Image ${index + 2}`"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<!-- Overlay -->
|
||||
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors duration-300 flex items-center justify-center">
|
||||
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 flex items-center justify-center transition-colors duration-300">
|
||||
<Icon name="lucide:zoom-in" class="w-6 h-6 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Jika tidak ada foto -->
|
||||
<div v-if="images.length === 0" class="col-span-full text-center py-8 bg-white/10 rounded-xl text-white/70">
|
||||
Belum ada foto untuk ditampilkan.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Modal for Image Preview -->
|
||||
<!-- Modal Preview -->
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="selectedImage !== null"
|
||||
@ -73,7 +75,7 @@
|
||||
@click="closeModal"
|
||||
>
|
||||
<div class="relative max-w-4xl max-h-full" @click.stop>
|
||||
<!-- Close Button -->
|
||||
<!-- Tombol close -->
|
||||
<button
|
||||
@click="closeModal"
|
||||
class="absolute -top-12 right-0 text-white hover:text-yellow-400 transition-colors duration-300"
|
||||
@ -81,14 +83,15 @@
|
||||
<Icon name="lucide:x" class="w-8 h-8" />
|
||||
</button>
|
||||
|
||||
<!-- Image -->
|
||||
<!-- Gambar besar -->
|
||||
<img
|
||||
:src="galleryImages[selectedImage]?.src || placeholderImage"
|
||||
:alt="galleryImages[selectedImage]?.alt || 'Gallery Image'"
|
||||
:src="images[selectedImage]"
|
||||
:alt="`Gallery Image ${selectedImage + 1}`"
|
||||
class="max-w-full max-h-full object-contain rounded-lg shadow-2xl"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
|
||||
<!-- Navigation Arrows -->
|
||||
<!-- Navigasi kiri-kanan -->
|
||||
<button
|
||||
v-if="selectedImage > 0"
|
||||
@click.stop="navigateImage(-1)"
|
||||
@ -98,88 +101,38 @@
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="selectedImage < galleryImages.length - 1"
|
||||
v-if="selectedImage < images.length - 1"
|
||||
@click.stop="navigateImage(1)"
|
||||
class="absolute right-4 top-1/2 transform -translate-y-1/2 bg-white/20 backdrop-blur-sm hover:bg-white/30 text-white p-2 rounded-full transition-all duration-300"
|
||||
>
|
||||
<Icon name="lucide:chevron-right" class="w-6 h-6" />
|
||||
</button>
|
||||
|
||||
<!-- Image Counter -->
|
||||
<div class="absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black/50 backdrop-blur-sm text-white px-3 py-1 rounded-full text-sm">
|
||||
{{ selectedImage + 1 }} / {{ galleryImages.length }}
|
||||
<!-- Counter -->
|
||||
<div class="absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black/50 text-white px-3 py-1 rounded-full text-sm">
|
||||
{{ selectedImage + 1 }} / {{ images.length }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<!-- Floating Decorative Elements -->
|
||||
<div class="absolute inset-0 pointer-events-none overflow-hidden">
|
||||
<!-- Top ornament -->
|
||||
<div class="absolute top-8 left-1/2 transform -translate-x-1/2 opacity-20">
|
||||
<svg viewBox="0 0 100 50" class="w-24 h-12 text-yellow-400">
|
||||
<path d="M50 10 Q60 0 70 10 Q80 20 70 30 Q60 20 50 30 Q40 20 30 30 Q20 20 30 10 Q40 0 50 10" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Bottom ornament -->
|
||||
<div class="absolute bottom-8 left-1/2 transform -translate-x-1/2 opacity-20">
|
||||
<svg viewBox="0 0 100 50" class="w-24 h-12 text-yellow-400">
|
||||
<path d="M50 40 Q40 50 30 40 Q20 30 30 20 Q40 30 50 20 Q60 30 70 20 Q80 30 70 40 Q60 50 50 40" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Side decorations -->
|
||||
<div class="absolute top-1/2 left-4 transform -translate-y-1/2 opacity-10">
|
||||
<div class="w-1 h-20 bg-yellow-400 rounded-full"></div>
|
||||
</div>
|
||||
<div class="absolute top-1/2 right-4 transform -translate-y-1/2 opacity-10">
|
||||
<div class="w-1 h-20 bg-yellow-400 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
images: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
// Reactive data
|
||||
// State
|
||||
const selectedImage = ref(null)
|
||||
const placeholderImage = 'https://via.placeholder.com/400x400/4299e1/ffffff?text=Photo'
|
||||
const placeholderImage = '/pria.jpg'
|
||||
|
||||
// Gallery images - replace with actual images
|
||||
const galleryImages = ref([
|
||||
{
|
||||
src: '/images/khitan/child-portrait-1.jpg',
|
||||
alt: 'Satria Huda Dinata Portrait 1'
|
||||
},
|
||||
{
|
||||
src: '/images/khitan/child-portrait-2.jpg',
|
||||
alt: 'Satria Huda Dinata Portrait 2'
|
||||
},
|
||||
{
|
||||
src: '/images/khitan/child-portrait-3.jpg',
|
||||
alt: 'Satria Huda Dinata Portrait 3'
|
||||
},
|
||||
{
|
||||
src: '/images/khitan/child-portrait-4.jpg',
|
||||
alt: 'Satria Huda Dinata Portrait 4'
|
||||
},
|
||||
{
|
||||
src: '/images/khitan/child-portrait-5.jpg',
|
||||
alt: 'Satria Huda Dinata Portrait 5'
|
||||
}
|
||||
])
|
||||
|
||||
// Methods
|
||||
// Modal Control
|
||||
const openModal = (index) => {
|
||||
selectedImage.value = index
|
||||
document.body.style.overflow = 'hidden'
|
||||
@ -192,7 +145,7 @@ const closeModal = () => {
|
||||
|
||||
const navigateImage = (direction) => {
|
||||
const newIndex = selectedImage.value + direction
|
||||
if (newIndex >= 0 && newIndex < galleryImages.value.length) {
|
||||
if (newIndex >= 0 && newIndex < props.images.length) {
|
||||
selectedImage.value = newIndex
|
||||
}
|
||||
}
|
||||
@ -201,33 +154,19 @@ const handleImageError = (event) => {
|
||||
event.target.src = placeholderImage
|
||||
}
|
||||
|
||||
// Keyboard Control
|
||||
const handleKeyPress = (event) => {
|
||||
if (selectedImage.value !== null) {
|
||||
switch (event.key) {
|
||||
case 'Escape':
|
||||
closeModal()
|
||||
break
|
||||
case 'ArrowLeft':
|
||||
navigateImage(-1)
|
||||
break
|
||||
case 'ArrowRight':
|
||||
navigateImage(1)
|
||||
break
|
||||
case 'Escape': closeModal(); break
|
||||
case 'ArrowLeft': navigateImage(-1); break
|
||||
case 'ArrowRight': navigateImage(1); break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
// Initialize gallery images from props if available
|
||||
if (props.data?.gallery && props.data.gallery.length > 0) {
|
||||
galleryImages.value = props.data.gallery.map((src, index) => ({
|
||||
src,
|
||||
alt: `Gallery Image ${index + 1}`
|
||||
}))
|
||||
}
|
||||
|
||||
// Add keyboard listener
|
||||
window.addEventListener('keydown', handleKeyPress)
|
||||
})
|
||||
|
||||
@ -238,114 +177,7 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Background Pattern */
|
||||
.bg-pattern {
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="islamic" x="0" y="0" width="25" height="25" patternUnits="userSpaceOnUse"><g fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"><path d="M12.5 0 L25 12.5 L12.5 25 L0 12.5 Z M6.25 6.25 L18.75 6.25 L18.75 18.75 L6.25 18.75 Z"/><circle cx="12.5" cy="12.5" r="3"/></g></pattern></defs><rect width="100%" height="100%" fill="url(%23islamic)"/></svg>');
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes fadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in-down {
|
||||
animation: fadeInDown 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-fade-in-up {
|
||||
animation: fadeInUp 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
/* Animation Delays */
|
||||
.animation-delay-300 {
|
||||
animation-delay: 0.3s;
|
||||
opacity: 0;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
/* Custom fonts */
|
||||
.font-script {
|
||||
font-family: 'Times New Roman', serif;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Gallery Grid Adjustments */
|
||||
.grid {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.grid > div {
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.grid > div:first-child {
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.grid > div:not(:first-child) {
|
||||
min-height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.fixed.inset-0 {
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
.text-4xl.md\:text-5xl {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.px-6 {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.gap-4 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.max-h-96 {
|
||||
max-height: 20rem;
|
||||
}
|
||||
|
||||
.grid > div {
|
||||
min-height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.md\:col-span-1 {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.md\:row-span-2 {
|
||||
grid-row: span 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,63 +1,91 @@
|
||||
<template>
|
||||
<div class="h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden">
|
||||
<div
|
||||
class="h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden"
|
||||
>
|
||||
<!-- Main Content -->
|
||||
<div class="relative z-10 min-h-screen flex items-center justify-center px-4 md:px-8 py-12">
|
||||
<div class="text-center max-w-4xl mx-auto">
|
||||
<!-- Title -->
|
||||
<div class="mb-10 animate-fade-in-down">
|
||||
<h1 class="text-yellow-400 text-3xl md:text-5xl lg:text-6xl font-bold mb-4 md:mb-6 font-script">
|
||||
Introduction
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Background Pattern -->
|
||||
<div class="absolute inset-0 bg-pattern opacity-30"></div>
|
||||
|
||||
<!-- Top Navigation Tabs -->
|
||||
<div class="absolute top-6 left-1/2 transform -translate-x-1/2 bg-white/20 backdrop-blur-sm rounded-full px-6 py-2 flex space-x-8 z-10">
|
||||
<span class="text-yellow-400 font-semibold cursor-pointer">Introduction</span>
|
||||
<span class="text-blue-100 cursor-pointer">Event</span>
|
||||
<span class="text-blue-100 cursor-pointer">Gallery</span>
|
||||
<span class="text-blue-100 cursor-pointer">Say</span>
|
||||
<span class="text-blue-100 cursor-pointer">Thank You</span>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="relative z-10 h-full flex flex-col items-center justify-center px-6 text-center">
|
||||
|
||||
<div
|
||||
class="relative z-10 h-full flex flex-col items-center justify-center px-6 text-center"
|
||||
>
|
||||
<!-- Child Photo -->
|
||||
<div class="w-48 h-60 overflow-hidden rounded-t-3xl mx-auto mb-6">
|
||||
<img :src="formData.childPhoto || '/pria.jpg'" alt="Child" class="w-full h-full object-cover"/>
|
||||
<div class="w-48 h-60 overflow-hidden rounded-t-3xl mx-auto mb-6 shadow-lg">
|
||||
<img
|
||||
:src="getFullPhotoUrl(childPhoto)"
|
||||
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">{{ formData.nama_panggilan || 'SATRIA HUDA DINATA' }}</h1>
|
||||
<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">
|
||||
{{ form.nama_lengkap || 'Nama Anak' }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Child Order & Parents -->
|
||||
<div class="text-white/80 text-lg mb-2">
|
||||
Putra Ke Dua Dari
|
||||
<!-- Child Order -->
|
||||
<div v-if="form.anak_ke" class="text-white/80 text-lg mb-2">
|
||||
Putra ke {{ form.anak_ke }} dari
|
||||
</div>
|
||||
<div v-else class="text-white/80 text-lg mb-2">
|
||||
Putra dari
|
||||
</div>
|
||||
|
||||
<!-- Parents -->
|
||||
<div class="text-yellow-400 font-semibold text-xl">
|
||||
{{ formData.fatherName || 'Bpk H. Munawar Huda, S.H.' }} & {{ formData.motherName || 'Ibu Hj. Dinah, A.M.Keb' }}
|
||||
{{ form.nama_bapak || 'Nama Ayah' }} & {{ form.nama_ibu || 'Nama Ibu' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useRuntimeConfig } from '#app'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
form: {
|
||||
type: Object,
|
||||
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)
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const backendUrl = config.public.apiBaseUrl
|
||||
|
||||
// ============ FOTO ANAK ============
|
||||
const childPhoto = computed(() => {
|
||||
const fotoPertama = props.form?.foto?.[3]
|
||||
return fotoPertama || ''
|
||||
})
|
||||
|
||||
// ============ FORMAT URL FOTO ============
|
||||
const getFullPhotoUrl = (photo) => {
|
||||
if (!photo) return '/pria.jpg'
|
||||
if (photo.startsWith('http')) return photo
|
||||
if (photo.startsWith('/storage')) return `${backendUrl}${photo}`
|
||||
return `${backendUrl}/storage/${photo}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Background Islamic Pattern */
|
||||
.bg-pattern {
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="islamic" x="0" y="0" width="25" height="25" patternUnits="userSpaceOnUse"><g fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"><path d="M12.5 0 L25 12.5 L12.5 25 L0 12.5 Z M6.25 6.25 L18.75 6.25 L18.75 18.75 L6.25 18.75 Z"/><circle cx="12.5" cy="12.5" r="3"/></g></pattern></defs><rect width="100%" height="100%" fill="url(%23islamic)"/></svg>');
|
||||
}
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
<!-- components/templates/khitan/KhitanA.vue -->
|
||||
<template>
|
||||
<div class="h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden">
|
||||
<!-- Background Islamic Pattern -->
|
||||
<div class="h-screen w-full relative bg-blue-900 overflow-hidden">
|
||||
<div class="absolute inset-0 bg-pattern opacity-30"></div>
|
||||
|
||||
<!-- Decorative Elements -->
|
||||
<div class="absolute top-0 left-0 w-full h-full">
|
||||
<!-- Top decorative curves -->
|
||||
<div class="absolute top-0 left-0 w-full">
|
||||
<svg viewBox="0 0 1200 300" class="w-full h-auto">
|
||||
<defs>
|
||||
@ -20,7 +16,6 @@
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Bottom decorative curves -->
|
||||
<div class="absolute bottom-0 left-0 w-full">
|
||||
<svg viewBox="0 0 1200 300" class="w-full h-auto">
|
||||
<path d="M0,150 C300,250 600,50 1200,150 L1200,300 L0,300 Z" fill="url(#goldGradient)" opacity="0.3"/>
|
||||
@ -28,13 +23,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="relative z-10 h-full flex items-center justify-center px-6">
|
||||
<div class="text-center max-w-4xl mx-auto">
|
||||
<!-- Islamic Symbol -->
|
||||
<div class="mb-8 animate-fade-in-down">
|
||||
<div class="mx-auto w-32 h-32 relative">
|
||||
<!-- Crescent and Star -->
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<svg viewBox="0 0 100 100" class="w-full h-full text-yellow-400">
|
||||
<defs>
|
||||
@ -46,16 +38,13 @@
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<!-- Crescent -->
|
||||
<path d="M35 20 A 25 25 0 1 1 35 80 A 15 15 0 1 0 35 20" fill="currentColor" filter="url(#glow)"/>
|
||||
<!-- Star -->
|
||||
<polygon points="65,25 67,35 77,35 69,42 72,52 65,45 58,52 61,42 53,35 63,35" fill="currentColor" filter="url(#glow)"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Invitation Title -->
|
||||
<div class="mb-12 animate-fade-in-up animation-delay-300">
|
||||
<h1 class="text-yellow-400 text-3xl md:text-4xl font-bold mb-4 tracking-wide">
|
||||
Undangan
|
||||
@ -68,37 +57,30 @@
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- Guest Information -->
|
||||
<div class="mb-12 animate-fade-in-up animation-delay-600">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 mb-6 border border-yellow-400/30">
|
||||
<p class="text-yellow-400 text-lg mb-2">{{ data?.guestTitle || 'Kepada Yth.' }}</p>
|
||||
<p class="text-white text-xl font-semibold">{{ data?.guestName || 'Muzaki Parsaoran' }}</p>
|
||||
<p class="text-white text-xl font-semibold">{{ data?.guestName || '' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Open Invitation Button -->
|
||||
<div class="animate-fade-in-up animation-delay-900">
|
||||
<button
|
||||
@click="$emit('next-page')"
|
||||
class="group relative px-8 py-4 bg-gradient-to-r from-yellow-400 to-yellow-500 text-blue-900 font-bold text-lg rounded-full shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-300 overflow-hidden"
|
||||
>
|
||||
<!-- Button background animation -->
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-yellow-300 to-yellow-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||
|
||||
<!-- Button content -->
|
||||
<span class="relative flex items-center space-x-2">
|
||||
<span>OPEN INVITATION</span>
|
||||
<Icon name="lucide:chevron-right" class="w-5 h-5 group-hover:translate-x-1 transition-transform duration-300" />
|
||||
</span>
|
||||
|
||||
<!-- Shine effect -->
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent transform -skew-x-12 -translate-x-full group-hover:translate-x-full transition-transform duration-700"></div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Decorative Elements Around Button -->
|
||||
<div class="absolute inset-0 pointer-events-none">
|
||||
<!-- Floating particles -->
|
||||
<div class="absolute top-1/4 left-1/4 animate-float">
|
||||
<div class="w-2 h-2 bg-yellow-400 rounded-full opacity-60"></div>
|
||||
</div>
|
||||
@ -112,7 +94,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Corner Decorations -->
|
||||
<div class="absolute top-4 left-4 w-16 h-16 border-l-2 border-t-2 border-yellow-400 opacity-50"></div>
|
||||
<div class="absolute top-4 right-4 w-16 h-16 border-r-2 border-t-2 border-yellow-400 opacity-50"></div>
|
||||
<div class="absolute bottom-4 left-4 w-16 h-16 border-l-2 border-b-2 border-yellow-400 opacity-50"></div>
|
||||
|
||||
@ -1,31 +1,33 @@
|
||||
```vue
|
||||
<template>
|
||||
<div class="h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden">
|
||||
<div class="min-h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden">
|
||||
<!-- Background Pattern -->
|
||||
<div class="absolute inset-0 bg-pattern opacity-30"></div>
|
||||
<div class="absolute inset-0 bg-pattern opacity-20"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="relative z-10 h-full flex items-center justify-center px-6">
|
||||
<div class="relative z-10 min-h-screen flex items-center justify-center px-4 md:px-8 py-12">
|
||||
<div class="text-center max-w-4xl mx-auto">
|
||||
<!-- Title -->
|
||||
<div class="mb-12 animate-fade-in-down">
|
||||
<h1 class="text-yellow-400 text-4xl md:text-6xl font-bold mb-6 font-script">Terimakasih</h1>
|
||||
<p class="text-white text-lg md:text-xl leading-relaxed max-w-3xl mx-auto">
|
||||
Kami mengucapkan terima kasih atas kehadiran serta doa restu
|
||||
yang diberikan. Semoga Allah SWT senantiasa melimpahkan
|
||||
rahmat dan keberkahan kepada kita semua.
|
||||
<div class="mb-10 animate-fade-in-down">
|
||||
<h1 class="text-yellow-400 text-3xl md:text-5xl lg:text-6xl font-bold mb-4 md:mb-6 font-script">
|
||||
Terima Kasih
|
||||
</h1>
|
||||
<p class="text-white text-sm md:text-base lg:text-lg leading-relaxed max-w-3xl mx-auto">
|
||||
Kami mengucapkan terima kasih atas kehadiran serta doa restu yang diberikan untuk
|
||||
<span class="text-yellow-400 font-semibold">{{ jsonData.nama_panggilan || 'putra kami' }}</span>.
|
||||
Semoga Allah SWT senantiasa melimpahkan rahmat dan keberkahan kepada kita semua.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Family Info -->
|
||||
<div class="mb-12 animate-fade-in-up animation-delay-300">
|
||||
<h2 class="text-yellow-400 text-2xl md:text-3xl font-bold mb-8">Kami Keluarga Besar Dari</h2>
|
||||
<div class="space-y-6">
|
||||
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 border border-yellow-400/30 animate-fade-in-up animation-delay-500">
|
||||
<div class="text-yellow-400 text-lg font-semibold mb-2">
|
||||
{{ data.fatherName || 'Bpk H. Munawar Huda, S.H.' }} & {{ data.motherName || 'Ibu Hj. Dinah, A.M.Keb' }}
|
||||
</div>
|
||||
<div class="text-white/80 text-sm">
|
||||
{{ data.fatherDescription || '(Anggota DPRD Provinsi Banten Fraksi Partai Demokrat)' }}
|
||||
<h2 class="text-yellow-400 text-xl md:text-2xl lg:text-3xl font-bold mb-6 md:mb-8">
|
||||
Kami Keluarga Besar Dari
|
||||
</h2>
|
||||
<div class="space-y-4 md:space-y-6">
|
||||
<div class="bg-white/10 backdrop-blur-md rounded-2xl p-4 md:p-6 border border-yellow-400/30 shadow-lg animate-fade-in-up animation-delay-500">
|
||||
<div class="text-yellow-400 text-base md:text-lg font-semibold mb-2">
|
||||
{{ jsonData.nama_bapak || 'Bpk H. Munawar Huda, S.H.' }} & {{ jsonData.nama_ibu || 'Ibu Hj. Dinah, A.M.Keb' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -33,26 +35,45 @@
|
||||
|
||||
<!-- Final Message -->
|
||||
<div class="animate-fade-in-up animation-delay-1200">
|
||||
<div class="bg-gradient-to-r from-yellow-400/20 to-yellow-600/20 backdrop-blur-sm rounded-2xl p-8 border border-yellow-400/50">
|
||||
<p class="text-white text-lg md:text-xl font-medium mb-4 italic">
|
||||
<div class="bg-gradient-to-r from-yellow-400/20 to-yellow-600/20 backdrop-blur-md rounded-2xl p-6 md:p-8 border border-yellow-400/50 shadow-lg">
|
||||
<p class="text-white text-base md:text-lg font-medium mb-4 italic">
|
||||
"Dan Allah telah mengeluarkan kamu dari perut ibumu dalam keadaan tidak mengetahui sesuatupun..."
|
||||
</p>
|
||||
<p class="text-yellow-400 font-semibold">- QS. An-Nahl: 78</p>
|
||||
<p class="text-yellow-400 text-sm md:text-base font-semibold">- QS. An-Nahl: 78</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Social -->
|
||||
<div class="mt-12 animate-fade-in-up animation-delay-1500 flex justify-center space-x-6">
|
||||
<a href="https://instagram.com/abbauftech" target="_blank" rel="noopener noreferrer"
|
||||
class="rounded-full p-3 backdrop-blur-sm border border-yellow-400/30 hover:border-yellow-400/60 hover:bg-white/20 transition-all duration-300 transform hover:scale-110"
|
||||
aria-label="Instagram">
|
||||
<Icon name="lucide:instagram" class="w-6 h-6 text-yellow-400" />
|
||||
<!-- Social Media Links -->
|
||||
<div class="mt-10 md:mt-12 animate-fade-in-up animation-delay-1500 flex justify-center space-x-4 md:space-x-6">
|
||||
<a
|
||||
v-if="jsonData.link_instagram"
|
||||
:href="jsonData.link_instagram"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="rounded-full p-2 md:p-3 backdrop-blur-md border border-yellow-400/30 hover:border-yellow-400/60 hover:bg-white/20 transition-all duration-300 transform hover:scale-110"
|
||||
aria-label="Instagram"
|
||||
>
|
||||
<Icon name="lucide:instagram" class="w-5 h-5 md:w-6 md:h-6 text-yellow-400" />
|
||||
</a>
|
||||
<a href="#" class="rounded-full p-3 backdrop-blur-sm border border-yellow-400/30 hover:border-yellow-400/60 hover:bg-white/20 transition-all duration-300 transform hover:scale-110" aria-label="Facebook">
|
||||
<Icon name="lucide:facebook" class="w-6 h-6 text-yellow-400" />
|
||||
<a
|
||||
v-if="jsonData.link_facebook"
|
||||
:href="jsonData.link_facebook"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="rounded-full p-2 md:p-3 backdrop-blur-md border border-yellow-400/30 hover:border-yellow-400/60 hover:bg-white/20 transition-all duration-300 transform hover:scale-110"
|
||||
aria-label="Facebook"
|
||||
>
|
||||
<Icon name="lucide:facebook" class="w-5 h-5 md:w-6 md:h-6 text-yellow-400" />
|
||||
</a>
|
||||
<a href="#" class="rounded-full p-3 backdrop-blur-sm border border-yellow-400/30 hover:border-yellow-400/60 hover:bg-white/20 transition-all duration-300 transform hover:scale-110" aria-label="WhatsApp">
|
||||
<Icon name="lucide:message-circle" class="w-6 h-6 text-yellow-400" />
|
||||
<a
|
||||
v-if="jsonData.link_twitter"
|
||||
:href="jsonData.link_twitter"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="rounded-full p-2 md:p-3 backdrop-blur-md border border-yellow-400/30 hover:border-yellow-400/60 hover:bg-white/20 transition-all duration-300 transform hover:scale-110"
|
||||
aria-label="Twitter"
|
||||
>
|
||||
<Icon name="lucide:twitter" class="w-5 h-5 md:w-6 md:h-6 text-yellow-400" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -66,10 +87,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
childName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
jsonData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
@ -84,13 +109,12 @@ const currentYear = computed(() => new Date().getFullYear())
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><defs><pattern id='pattern' x='0' y='0' width='20' height='20' patternUnits='userSpaceOnUse'><path d='M10 5 L15 10 L10 15 L5 10 Z' fill='none' stroke='rgba(255,255,255,0.1)' stroke-width='0.5'/></pattern></defs><rect width='100' height='100' fill='url(%23pattern)'/></svg>");
|
||||
}
|
||||
|
||||
/* Tambahkan animasi sesuai kebutuhan */
|
||||
.animate-fade-in-up {
|
||||
animation: fadeInUp 1s forwards;
|
||||
animation: fadeInUp 1s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-fade-in-down {
|
||||
animation: fadeInDown 1s forwards;
|
||||
animation: fadeInDown 1s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
@ -102,4 +126,21 @@ const currentYear = computed(() => new Date().getFullYear())
|
||||
from { opacity: 0; transform: translateY(-20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.animation-delay-300 {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.animation-delay-500 {
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
.animation-delay-1200 {
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
|
||||
.animation-delay-1500 {
|
||||
animation-delay: 1.5s;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
@ -1,6 +1,8 @@
|
||||
<!-- components/undangan/undangan-khitan-premium.vue -->
|
||||
<template>
|
||||
<div class="min-h-screen bg-gradient-to-b from-blue-100 via-blue-200 to-blue-300 relative overflow-hidden">
|
||||
<div
|
||||
class="min-h-screen bg-gradient-to-b from-blue-100 via-blue-200 to-blue-300 relative overflow-hidden"
|
||||
>
|
||||
<!-- ================= NAVIGATION ================= -->
|
||||
<nav
|
||||
v-if="currentSection !== 'landing'"
|
||||
@ -9,21 +11,11 @@
|
||||
<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('say')" :class="navClass('say')">Guest Book</button>
|
||||
</li>
|
||||
<li>
|
||||
<button @click="switchSection('thanks')" :class="navClass('thanks')">Thanks</button>
|
||||
</li>
|
||||
<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('say')" :class="navClass('say')">Guest Book</button></li>
|
||||
<li><button @click="switchSection('thanks')" :class="navClass('thanks')">Thanks</button></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@ -32,6 +24,7 @@
|
||||
<button @click="toggleMusic" class="bg-blue-600 p-3 rounded-full text-white shadow-lg">
|
||||
{{ isPlaying ? '⏸️' : '▶️' }}
|
||||
</button>
|
||||
<audio ref="audioPlayer" :src="musicUrl" loop></audio>
|
||||
</div>
|
||||
|
||||
<!-- ================= MAIN CONTENT ================= -->
|
||||
@ -49,18 +42,26 @@
|
||||
<!-- Introduction -->
|
||||
<KhitanIntroduction
|
||||
v-if="currentSection === 'introduction'"
|
||||
:data="formData"
|
||||
:form="formData"
|
||||
/>
|
||||
|
||||
|
||||
<!-- Event -->
|
||||
<KhitanEvent
|
||||
<KhitanEvent
|
||||
v-if="currentSection === 'event'"
|
||||
:hari_tanggal_acara="formData.hari_tanggal_acara"
|
||||
:waktu="formData.waktu"
|
||||
:alamat="formData.alamat"
|
||||
:link_gmaps="formData.link_gmaps"
|
||||
:hitung_mundur="formData.hitung_mundur"
|
||||
/>
|
||||
:hitung_mundur_mulai="formData.hitung_mundur_mulai"
|
||||
|
||||
:hari_tanggal_syukuran="formData.hari_tanggal_syukuran"
|
||||
:waktu_syukuran="formData.waktu_syukuran"
|
||||
:alamat_syukuran="formData.alamat_syukuran"
|
||||
:link_gmaps_syukuran="formData.link_gmaps_syukuran"
|
||||
:hitung_mundur_selesai="formData.hitung_mundur_selesai"
|
||||
/>
|
||||
|
||||
|
||||
<!-- Gallery -->
|
||||
<KhitanGallery
|
||||
@ -76,18 +77,15 @@
|
||||
@addMessage="addMessage"
|
||||
/>
|
||||
|
||||
|
||||
<!-- Thank You -->
|
||||
<KhitanThankYou
|
||||
v-if="currentSection === 'thanks'"
|
||||
:childName="formData.nama_panggilan"
|
||||
:jsonData="formData"
|
||||
/>
|
||||
<KhitanThankYou v-if="currentSection === 'thanks'" :childName="formData.nama_panggilan" :jsonData="formData" />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRuntimeConfig } from '#app'
|
||||
|
||||
// ================== IMPORT KOMPONEN ==================
|
||||
@ -112,15 +110,27 @@ const formData = computed(() => props.data.form || {})
|
||||
|
||||
// ================== GALERI ==================
|
||||
const galleryImages = computed(() => {
|
||||
const f = formData.value.foto
|
||||
|
||||
|
||||
if (Array.isArray(f)) {
|
||||
return f.map(img => `${backendUrl}/storage/${img}`)
|
||||
}
|
||||
|
||||
// Jika masih bentuk lama (foto_1, foto_2, dst.)
|
||||
return [
|
||||
formData.value.foto_1,
|
||||
formData.value.foto_2,
|
||||
formData.value.foto_3,
|
||||
formData.value.foto_4,
|
||||
formData.value.foto_5
|
||||
]
|
||||
.filter(Boolean)
|
||||
.map(f => `${backendUrl}/${f}`)
|
||||
].filter(Boolean).map(img => `${backendUrl}/${img}`)
|
||||
})
|
||||
|
||||
// Foto utama diambil dari foto pertama
|
||||
const mainPhoto = computed(() => {
|
||||
const firstPhoto = formData.value.foto?.[0]
|
||||
return firstPhoto ? `${backendUrl}/${firstPhoto}` : ''
|
||||
})
|
||||
|
||||
// ================== NAVIGASI SECTION ==================
|
||||
@ -128,8 +138,21 @@ const currentSection = ref('landing')
|
||||
const switchSection = (s) => (currentSection.value = s)
|
||||
|
||||
// ================== MUSIK ==================
|
||||
const audioPlayer = ref(null)
|
||||
const isPlaying = ref(false)
|
||||
const toggleMusic = () => (isPlaying.value = !isPlaying.value)
|
||||
const musicUrl = computed(() =>
|
||||
formData.value.link_music ? `${backendUrl}/${formData.value.link_music}` : ''
|
||||
)
|
||||
|
||||
const toggleMusic = () => {
|
||||
if (!audioPlayer.value) return
|
||||
if (isPlaying.value) {
|
||||
audioPlayer.value.pause()
|
||||
} else {
|
||||
audioPlayer.value.play()
|
||||
}
|
||||
isPlaying.value = !isPlaying.value
|
||||
}
|
||||
|
||||
// ================== GUEST BOOK ==================
|
||||
const messages = ref([])
|
||||
@ -140,15 +163,9 @@ 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>
|
||||
/* Animasi transisi antar section */
|
||||
main {
|
||||
transition: all 0.7s ease-in-out;
|
||||
}
|
||||
|
||||
BIN
proyek-frontend/public/khitan-bg.png
Normal file
BIN
proyek-frontend/public/khitan-bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
Loading…
Reference in New Issue
Block a user