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>
|
<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 -->
|
<!-- Background Pattern -->
|
||||||
<div class="absolute inset-0 bg-pattern opacity-30"></div>
|
<div class="absolute inset-0 bg-pattern opacity-20"></div>
|
||||||
|
|
||||||
<!-- Decorative Lanterns -->
|
<!-- Decorative Lanterns -->
|
||||||
<div class="absolute top-8 left-8 animate-sway">
|
<div class="absolute top-4 left-4 md:top-8 md:left-8 animate-sway">
|
||||||
<div class="w-16 h-20 relative">
|
<Lantern />
|
||||||
<!-- 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>
|
||||||
|
<div class="absolute top-4 right-4 md:top-8 md:right-8 animate-sway animation-delay-1000">
|
||||||
<div class="absolute top-8 right-8 animate-sway animation-delay-1000">
|
<Lantern />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- 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-6xl mx-auto">
|
<div class="text-center max-w-5xl mx-auto">
|
||||||
|
|
||||||
<!-- Bismillah -->
|
<!-- Bismillah -->
|
||||||
<div class="mb-8 animate-fade-in-down">
|
<div class="mb-10 animate-fade-in-down">
|
||||||
<h1 class="text-yellow-400 text-2xl md:text-3xl font-bold mb-6 arabic-text">
|
<h1 class="text-yellow-400 text-xl md:text-3xl lg:text-4xl font-bold mb-6 arabic-text">
|
||||||
بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ
|
بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Event Description -->
|
<!-- Event Description -->
|
||||||
<div class="mb-8 animate-fade-in-up animation-delay-300">
|
<div class="mb-12 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">
|
<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>
|
Dengan memohon rahmat dan ridho Allah Subhanahu wa Ta'ala, kami<br>
|
||||||
mengundang Bapak/Ibu/Saudara/i untuk hadir dan berbagi kebahagiaan<br>
|
mengundang Bapak/Ibu/Saudara/i untuk hadir dan berbagi kebahagiaan<br>
|
||||||
dalam acara Khitanan putra kami tercinta, yang insya Allah akan<br>
|
dalam acara Khitanan putra kami tercinta, yang insya Allah akan<br>
|
||||||
diselenggarakan pada :
|
diselenggarakan pada:
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Event Details Container -->
|
<!-- Event Details -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 md:gap-8 mb-12">
|
||||||
|
<!-- Acara Khitanan -->
|
||||||
<!-- Date & Countdown -->
|
|
||||||
<div class="animate-fade-in-left animation-delay-600">
|
<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">
|
<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-2xl font-bold mb-6 font-script">
|
<h3 class="text-yellow-400 text-xl md:text-2xl font-bold mb-4 font-script">
|
||||||
Jum'at/Sabtu
|
Khitanan
|
||||||
</h3>
|
</h3>
|
||||||
|
<div class="text-white text-2xl md:text-3xl lg:text-4xl font-bold mb-6">
|
||||||
<div class="text-white text-3xl md:text-4xl font-bold mb-8">
|
{{ eventDate }} ({{ eventDay }})
|
||||||
{{ eventDate }}
|
|
||||||
</div>
|
</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">
|
<div class="text-white mb-6">
|
||||||
<p class="text-lg mb-2">Pukul {{ eventTime }}</p>
|
<p class="text-sm md:text-base mb-2">🕓 {{ eventTime }}</p>
|
||||||
<p class="text-lg mb-2">{{ eventLocation }}</p>
|
<p class="text-sm md:text-base mb-2">📍 {{ eventLocation }}</p>
|
||||||
<p class="text-base opacity-80">{{ eventAddress }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Map -->
|
||||||
<!-- Map Placeholder -->
|
<div class="bg-gray-300 rounded-lg h-40 md:h-48 relative overflow-hidden mb-6">
|
||||||
<div class="bg-gray-300 rounded-lg h-32 mb-6 relative overflow-hidden">
|
|
||||||
<div class="absolute inset-0 bg-gradient-to-r from-blue-400 to-blue-600 opacity-50"></div>
|
<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">
|
<div class="absolute inset-0 flex items-center justify-center">
|
||||||
<Icon name="lucide:map-pin" class="w-8 h-8 text-white" />
|
<Icon name="lucide:map-pin" class="w-8 h-8 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
src="https://via.placeholder.com/400x200/4299e1/ffffff?text=Map+Preview"
|
:src="eventMap"
|
||||||
alt="Location Map"
|
alt="Location Map"
|
||||||
class="w-full h-full object-cover opacity-80"
|
class="w-full h-full object-cover opacity-80"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Countdown -->
|
||||||
<!-- Direction Button -->
|
<div class="grid grid-cols-4 gap-3 md:gap-4 mb-6">
|
||||||
<button
|
<div
|
||||||
@click="openMap"
|
v-for="(item, index) in countdownItemsAcara"
|
||||||
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"
|
: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"
|
||||||
>
|
>
|
||||||
Direction
|
Add to Calendar
|
||||||
</button>
|
</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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Decorative Elements -->
|
<!-- Decorative Element -->
|
||||||
<div class="absolute inset-0 pointer-events-none overflow-hidden">
|
<div class="absolute inset-0 pointer-events-none overflow-hidden">
|
||||||
<div class="absolute top-0 left-1/2 transform -translate-x-1/2">
|
<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">
|
<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"/>
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -162,86 +175,188 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||||
|
|
||||||
// Props
|
// Props sesuai dengan parent dan JSON
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
hari_tanggal_acara: { type: String, default: '' },
|
||||||
type: Object,
|
waktu: { type: String, default: '' },
|
||||||
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')
|
🎯 EVENT KHITAN
|
||||||
const eventTime = computed(() => props.data.eventTime || '09.00 WIB s.d Selesai')
|
========================== */
|
||||||
const eventLocation = computed(() => props.data.eventLocation || 'TMC Mangrove, Tanjung Pasir')
|
const eventDate = computed(() => props.hari_tanggal_acara || '2025-10-31')
|
||||||
const eventAddress = computed(() => props.data.eventAddress || 'Desa Tanjung Pasir')
|
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')
|
🎉 SYUKURAN
|
||||||
const childTitle = computed(() => props.data.childTitle || 'SATRIA HUDA DINATA')
|
========================== */
|
||||||
const childSubtitle = computed(() => props.data.childSubtitle || 'Putra Ke Dua Dari')
|
const syukuranDate = computed(() => props.hari_tanggal_syukuran || '')
|
||||||
const fatherName = computed(() => props.data.fatherName || 'Bpk H. Munawar Huda, S.H.')
|
const syukuranDay = computed(() => syukuranDate.value ? new Date(syukuranDate.value).toLocaleDateString('id-ID', { weekday: 'long' }) : '')
|
||||||
const motherName = computed(() => props.data.motherName || 'Ibu Hj. Dinah, A.M.Keb')
|
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 })
|
⏱️ COUNTDOWN
|
||||||
let countdownInterval = null
|
========================== */
|
||||||
|
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(() => [
|
const countdownItemsAcara = computed(() => [
|
||||||
{ value: String(countdown.value.days).padStart(2, '0'), label: 'D' },
|
{ value: String(countdownAcara.value.days).padStart(2, '0'), label: 'D' },
|
||||||
{ value: String(countdown.value.hours).padStart(2, '0'), label: 'H' },
|
{ value: String(countdownAcara.value.hours).padStart(2, '0'), label: 'H' },
|
||||||
{ value: String(countdown.value.minutes).padStart(2, '0'), label: 'M' },
|
{ value: String(countdownAcara.value.minutes).padStart(2, '0'), label: 'M' },
|
||||||
{ value: String(countdown.value.seconds).padStart(2, '0'), label: 'S' }
|
{ 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 updateCountdown = () => {
|
||||||
const eventDateObj = new Date('2025-06-20T09:00:00')
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const diff = eventDateObj.getTime() - now.getTime()
|
|
||||||
if (diff > 0) {
|
// Countdown untuk acara khitanan
|
||||||
countdown.value = {
|
if (props.hitung_mundur_mulai) {
|
||||||
days: Math.floor(diff / (1000 * 60 * 60 * 24)),
|
const eventDateObj = new Date(props.hitung_mundur_mulai)
|
||||||
hours: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
|
const diff = eventDateObj - now
|
||||||
minutes: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
|
if (diff > 0) {
|
||||||
seconds: Math.floor((diff % (1000 * 60)) / 1000)
|
countdownAcara.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 {
|
||||||
|
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 }
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
countdown.value = { days: 0, hours: 0, minutes: 0, seconds: 0 }
|
|
||||||
if (countdownInterval) clearInterval(countdownInterval)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================
|
||||||
|
📅 ACTIONS
|
||||||
|
========================== */
|
||||||
const addToCalendar = () => {
|
const addToCalendar = () => {
|
||||||
const eventDetails = {
|
const title = `Tasyakuran Khitan ${props.nama_panggilan || 'Putra Kami'}`
|
||||||
title: `Tasyakuran Khitan ${childName.value}`,
|
const start = new Date(props.hitung_mundur_mulai).toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'
|
||||||
start: '20250620T090000Z',
|
const end = new Date(new Date(props.hitung_mundur_mulai).getTime() + 3 * 60 * 60 * 1000).toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z'
|
||||||
end: '20250621T170000Z',
|
|
||||||
description: 'Undangan Tasyakuran Khitan',
|
const url = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(title)}&dates=${start}/${end}&location=${encodeURIComponent(eventLocation.value)}`
|
||||||
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)}`
|
|
||||||
window.open(url, '_blank')
|
window.open(url, '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
const openMap = () => {
|
/* ==========================
|
||||||
const location = encodeURIComponent(eventLocation.value)
|
⏳ LIFECYCLE
|
||||||
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'
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
updateCountdown()
|
updateCountdown()
|
||||||
countdownInterval = setInterval(updateCountdown, 1000)
|
countdownInterval.value = setInterval(updateCountdown, 1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (countdownInterval) clearInterval(countdownInterval)
|
if (countdownInterval.value) {
|
||||||
|
clearInterval(countdownInterval.value)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* ... tetap sama dengan punyamu, kecuali hapus selector .grid-cols-1.lg\:grid-cols-2 */
|
/* Animations & bg-pattern */
|
||||||
</style>
|
.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>
|
<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 -->
|
<!-- Background Pattern -->
|
||||||
<div class="absolute inset-0 bg-pattern opacity-30"></div>
|
<div class="absolute inset-0 bg-pattern opacity-30"></div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- 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 -->
|
<!-- Gallery Title -->
|
||||||
<div class="text-center mb-8 animate-fade-in-down">
|
<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">
|
<h1 class="text-yellow-400 text-4xl md:text-5xl font-bold mb-6 font-script">
|
||||||
@ -18,54 +16,58 @@
|
|||||||
berharga dalam hidupku ini.
|
berharga dalam hidupku ini.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Photo Gallery Grid -->
|
<!-- Photo Gallery Grid -->
|
||||||
<div class="flex-1 max-w-4xl mx-auto w-full animate-fade-in-up animation-delay-300">
|
<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">
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||||
|
<!-- Foto pertama (besar) -->
|
||||||
<!-- Large Photo (spans 2 rows on medium+ screens) -->
|
<div
|
||||||
<div class="md:col-span-1 md:row-span-2 relative group cursor-pointer" @click="openModal(0)">
|
v-if="images.length > 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">
|
class="md:col-span-1 md:row-span-2 relative group cursor-pointer"
|
||||||
<img
|
@click="openModal(0)"
|
||||||
:src="galleryImages[0]?.src || placeholderImage"
|
>
|
||||||
:alt="galleryImages[0]?.alt || 'Gallery Image 1'"
|
<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="images[0]"
|
||||||
|
:alt="`Gallery Image 1`"
|
||||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
@error="handleImageError"
|
@error="handleImageError"
|
||||||
/>
|
/>
|
||||||
<!-- Overlay -->
|
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 flex items-center justify-center transition-colors duration-300">
|
||||||
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors duration-300 flex items-center justify-center">
|
|
||||||
<Icon name="lucide:zoom-in" class="w-8 h-8 text-white opacity-0 group-hover:opacity-100 transition-opacity 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Medium Photos -->
|
<!-- Foto lainnya -->
|
||||||
<div
|
<div
|
||||||
v-for="(image, index) in galleryImages.slice(1, 5)"
|
v-for="(image, index) in images.slice(1)"
|
||||||
:key="index + 1"
|
:key="index + 1"
|
||||||
class="relative group cursor-pointer"
|
class="relative group cursor-pointer"
|
||||||
@click="openModal(index + 1)"
|
@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
|
<img
|
||||||
:src="image.src || placeholderImage"
|
:src="image"
|
||||||
:alt="image.alt || `Gallery Image ${index + 2}`"
|
:alt="`Gallery Image ${index + 2}`"
|
||||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
@error="handleImageError"
|
@error="handleImageError"
|
||||||
/>
|
/>
|
||||||
<!-- Overlay -->
|
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 flex items-center justify-center transition-colors duration-300">
|
||||||
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors duration-300 flex items-center justify-center">
|
|
||||||
<Icon name="lucide:zoom-in" class="w-6 h-6 text-white opacity-0 group-hover:opacity-100 transition-opacity 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>
|
</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>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal for Image Preview -->
|
<!-- Modal Preview -->
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div
|
<div
|
||||||
v-if="selectedImage !== null"
|
v-if="selectedImage !== null"
|
||||||
@ -73,22 +75,23 @@
|
|||||||
@click="closeModal"
|
@click="closeModal"
|
||||||
>
|
>
|
||||||
<div class="relative max-w-4xl max-h-full" @click.stop>
|
<div class="relative max-w-4xl max-h-full" @click.stop>
|
||||||
<!-- Close Button -->
|
<!-- Tombol close -->
|
||||||
<button
|
<button
|
||||||
@click="closeModal"
|
@click="closeModal"
|
||||||
class="absolute -top-12 right-0 text-white hover:text-yellow-400 transition-colors duration-300"
|
class="absolute -top-12 right-0 text-white hover:text-yellow-400 transition-colors duration-300"
|
||||||
>
|
>
|
||||||
<Icon name="lucide:x" class="w-8 h-8" />
|
<Icon name="lucide:x" class="w-8 h-8" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Image -->
|
<!-- Gambar besar -->
|
||||||
<img
|
<img
|
||||||
:src="galleryImages[selectedImage]?.src || placeholderImage"
|
:src="images[selectedImage]"
|
||||||
:alt="galleryImages[selectedImage]?.alt || 'Gallery Image'"
|
:alt="`Gallery Image ${selectedImage + 1}`"
|
||||||
class="max-w-full max-h-full object-contain rounded-lg shadow-2xl"
|
class="max-w-full max-h-full object-contain rounded-lg shadow-2xl"
|
||||||
|
@error="handleImageError"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Navigation Arrows -->
|
<!-- Navigasi kiri-kanan -->
|
||||||
<button
|
<button
|
||||||
v-if="selectedImage > 0"
|
v-if="selectedImage > 0"
|
||||||
@click.stop="navigateImage(-1)"
|
@click.stop="navigateImage(-1)"
|
||||||
@ -96,90 +99,40 @@
|
|||||||
>
|
>
|
||||||
<Icon name="lucide:chevron-left" class="w-6 h-6" />
|
<Icon name="lucide:chevron-left" class="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="selectedImage < galleryImages.length - 1"
|
v-if="selectedImage < images.length - 1"
|
||||||
@click.stop="navigateImage(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"
|
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" />
|
<Icon name="lucide:chevron-right" class="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Image Counter -->
|
<!-- 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">
|
<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 }} / {{ galleryImages.length }}
|
{{ selectedImage + 1 }} / {{ images.length }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
|
||||||
// Props
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
images: {
|
||||||
type: Object,
|
type: Array,
|
||||||
default: () => ({})
|
default: () => []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Reactive data
|
// State
|
||||||
const selectedImage = ref(null)
|
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
|
// Modal Control
|
||||||
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
|
|
||||||
const openModal = (index) => {
|
const openModal = (index) => {
|
||||||
selectedImage.value = index
|
selectedImage.value = index
|
||||||
document.body.style.overflow = 'hidden'
|
document.body.style.overflow = 'hidden'
|
||||||
@ -192,7 +145,7 @@ const closeModal = () => {
|
|||||||
|
|
||||||
const navigateImage = (direction) => {
|
const navigateImage = (direction) => {
|
||||||
const newIndex = selectedImage.value + direction
|
const newIndex = selectedImage.value + direction
|
||||||
if (newIndex >= 0 && newIndex < galleryImages.value.length) {
|
if (newIndex >= 0 && newIndex < props.images.length) {
|
||||||
selectedImage.value = newIndex
|
selectedImage.value = newIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,33 +154,19 @@ const handleImageError = (event) => {
|
|||||||
event.target.src = placeholderImage
|
event.target.src = placeholderImage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keyboard Control
|
||||||
const handleKeyPress = (event) => {
|
const handleKeyPress = (event) => {
|
||||||
if (selectedImage.value !== null) {
|
if (selectedImage.value !== null) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Escape':
|
case 'Escape': closeModal(); break
|
||||||
closeModal()
|
case 'ArrowLeft': navigateImage(-1); break
|
||||||
break
|
case 'ArrowRight': navigateImage(1); break
|
||||||
case 'ArrowLeft':
|
|
||||||
navigateImage(-1)
|
|
||||||
break
|
|
||||||
case 'ArrowRight':
|
|
||||||
navigateImage(1)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
onMounted(() => {
|
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)
|
window.addEventListener('keydown', handleKeyPress)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -238,114 +177,7 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* Background Pattern */
|
|
||||||
.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="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>');
|
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>
|
</style>
|
||||||
@ -1,63 +1,91 @@
|
|||||||
<template>
|
<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 -->
|
<!-- Background Pattern -->
|
||||||
<div class="absolute inset-0 bg-pattern opacity-30"></div>
|
<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 -->
|
<!-- 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 -->
|
<!-- Child Photo -->
|
||||||
<div class="w-48 h-60 overflow-hidden rounded-t-3xl mx-auto mb-6">
|
<div class="w-48 h-60 overflow-hidden rounded-t-3xl mx-auto mb-6 shadow-lg">
|
||||||
<img :src="formData.childPhoto || '/pria.jpg'" alt="Child" class="w-full h-full object-cover"/>
|
<img
|
||||||
|
:src="getFullPhotoUrl(childPhoto)"
|
||||||
|
alt="Child"
|
||||||
|
class="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Child Name -->
|
<!-- Child Name -->
|
||||||
<div class="bg-blue-800/50 border border-yellow-400 rounded-md px-6 py-2 mb-4 inline-block">
|
<div
|
||||||
<h1 class="text-yellow-400 font-bold text-xl">{{ formData.nama_panggilan || 'SATRIA HUDA DINATA' }}</h1>
|
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>
|
</div>
|
||||||
|
|
||||||
<!-- Child Order & Parents -->
|
<!-- Child Order -->
|
||||||
<div class="text-white/80 text-lg mb-2">
|
<div v-if="form.anak_ke" class="text-white/80 text-lg mb-2">
|
||||||
Putra Ke Dua Dari
|
Putra ke {{ form.anak_ke }} dari
|
||||||
</div>
|
</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">
|
<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>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useRuntimeConfig } from '#app'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
form: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const formData = computed(() => {
|
|
||||||
const result = {}
|
const config = useRuntimeConfig()
|
||||||
props.data?.fields?.forEach(f => {
|
const backendUrl = config.public.apiBaseUrl
|
||||||
result[f.name] = f.value
|
|
||||||
})
|
// ============ FOTO ANAK ============
|
||||||
return result
|
const childPhoto = computed(() => {
|
||||||
})
|
const fotoPertama = props.form?.foto?.[3]
|
||||||
onMounted(() => {
|
return fotoPertama || ''
|
||||||
console.log("🧾 Introduction Data:", props.data)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ============ 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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* Background Islamic Pattern */
|
|
||||||
.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="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>');
|
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>
|
<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-blue-900 overflow-hidden">
|
||||||
<!-- Background Islamic Pattern -->
|
|
||||||
<div class="absolute inset-0 bg-pattern opacity-30"></div>
|
<div class="absolute inset-0 bg-pattern opacity-30"></div>
|
||||||
|
|
||||||
<!-- Decorative Elements -->
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full">
|
<div class="absolute top-0 left-0 w-full h-full">
|
||||||
<!-- Top decorative curves -->
|
|
||||||
<div class="absolute top-0 left-0 w-full">
|
<div class="absolute top-0 left-0 w-full">
|
||||||
<svg viewBox="0 0 1200 300" class="w-full h-auto">
|
<svg viewBox="0 0 1200 300" class="w-full h-auto">
|
||||||
<defs>
|
<defs>
|
||||||
@ -20,7 +16,6 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottom decorative curves -->
|
|
||||||
<div class="absolute bottom-0 left-0 w-full">
|
<div class="absolute bottom-0 left-0 w-full">
|
||||||
<svg viewBox="0 0 1200 300" class="w-full h-auto">
|
<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"/>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
|
||||||
<div class="relative z-10 h-full flex items-center justify-center px-6">
|
<div class="relative z-10 h-full flex items-center justify-center px-6">
|
||||||
<div class="text-center max-w-4xl mx-auto">
|
<div class="text-center max-w-4xl mx-auto">
|
||||||
<!-- Islamic Symbol -->
|
|
||||||
<div class="mb-8 animate-fade-in-down">
|
<div class="mb-8 animate-fade-in-down">
|
||||||
<div class="mx-auto w-32 h-32 relative">
|
<div class="mx-auto w-32 h-32 relative">
|
||||||
<!-- Crescent and Star -->
|
|
||||||
<div class="absolute inset-0 flex items-center justify-center">
|
<div class="absolute inset-0 flex items-center justify-center">
|
||||||
<svg viewBox="0 0 100 100" class="w-full h-full text-yellow-400">
|
<svg viewBox="0 0 100 100" class="w-full h-full text-yellow-400">
|
||||||
<defs>
|
<defs>
|
||||||
@ -46,16 +38,13 @@
|
|||||||
</feMerge>
|
</feMerge>
|
||||||
</filter>
|
</filter>
|
||||||
</defs>
|
</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)"/>
|
<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)"/>
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Invitation Title -->
|
|
||||||
<div class="mb-12 animate-fade-in-up animation-delay-300">
|
<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">
|
<h1 class="text-yellow-400 text-3xl md:text-4xl font-bold mb-4 tracking-wide">
|
||||||
Undangan
|
Undangan
|
||||||
@ -68,37 +57,30 @@
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Guest Information -->
|
|
||||||
<div class="mb-12 animate-fade-in-up animation-delay-600">
|
<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">
|
<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-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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Open Invitation Button -->
|
|
||||||
<div class="animate-fade-in-up animation-delay-900">
|
<div class="animate-fade-in-up animation-delay-900">
|
||||||
<button
|
<button
|
||||||
@click="$emit('next-page')"
|
@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"
|
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>
|
<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 class="relative flex items-center space-x-2">
|
||||||
<span>OPEN INVITATION</span>
|
<span>OPEN INVITATION</span>
|
||||||
<Icon name="lucide:chevron-right" class="w-5 h-5 group-hover:translate-x-1 transition-transform duration-300" />
|
<Icon name="lucide:chevron-right" class="w-5 h-5 group-hover:translate-x-1 transition-transform duration-300" />
|
||||||
</span>
|
</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>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Decorative Elements Around Button -->
|
|
||||||
<div class="absolute inset-0 pointer-events-none">
|
<div class="absolute inset-0 pointer-events-none">
|
||||||
<!-- Floating particles -->
|
|
||||||
<div class="absolute top-1/4 left-1/4 animate-float">
|
<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 class="w-2 h-2 bg-yellow-400 rounded-full opacity-60"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -112,7 +94,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 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 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>
|
<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>
|
<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 -->
|
<!-- Background Pattern -->
|
||||||
<div class="absolute inset-0 bg-pattern opacity-30"></div>
|
<div class="absolute inset-0 bg-pattern opacity-20"></div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- 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">
|
<div class="text-center max-w-4xl mx-auto">
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div class="mb-12 animate-fade-in-down">
|
<div class="mb-10 animate-fade-in-down">
|
||||||
<h1 class="text-yellow-400 text-4xl md:text-6xl font-bold mb-6 font-script">Terimakasih</h1>
|
<h1 class="text-yellow-400 text-3xl md:text-5xl lg:text-6xl font-bold mb-4 md:mb-6 font-script">
|
||||||
<p class="text-white text-lg md:text-xl leading-relaxed max-w-3xl mx-auto">
|
Terima Kasih
|
||||||
Kami mengucapkan terima kasih atas kehadiran serta doa restu
|
</h1>
|
||||||
yang diberikan. Semoga Allah SWT senantiasa melimpahkan
|
<p class="text-white text-sm md:text-base lg:text-lg leading-relaxed max-w-3xl mx-auto">
|
||||||
rahmat dan keberkahan kepada kita semua.
|
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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Family Info -->
|
<!-- Family Info -->
|
||||||
<div class="mb-12 animate-fade-in-up animation-delay-300">
|
<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>
|
<h2 class="text-yellow-400 text-xl md:text-2xl lg:text-3xl font-bold mb-6 md:mb-8">
|
||||||
<div class="space-y-6">
|
Kami Keluarga Besar Dari
|
||||||
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 border border-yellow-400/30 animate-fade-in-up animation-delay-500">
|
</h2>
|
||||||
<div class="text-yellow-400 text-lg font-semibold mb-2">
|
<div class="space-y-4 md:space-y-6">
|
||||||
{{ data.fatherName || 'Bpk H. Munawar Huda, S.H.' }} & {{ data.motherName || 'Ibu Hj. Dinah, A.M.Keb' }}
|
<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>
|
<div class="text-yellow-400 text-base md:text-lg font-semibold mb-2">
|
||||||
<div class="text-white/80 text-sm">
|
{{ jsonData.nama_bapak || 'Bpk H. Munawar Huda, S.H.' }} & {{ jsonData.nama_ibu || 'Ibu Hj. Dinah, A.M.Keb' }}
|
||||||
{{ data.fatherDescription || '(Anggota DPRD Provinsi Banten Fraksi Partai Demokrat)' }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -33,26 +35,45 @@
|
|||||||
|
|
||||||
<!-- Final Message -->
|
<!-- Final Message -->
|
||||||
<div class="animate-fade-in-up animation-delay-1200">
|
<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">
|
<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-lg md:text-xl font-medium mb-4 italic">
|
<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..."
|
"Dan Allah telah mengeluarkan kamu dari perut ibumu dalam keadaan tidak mengetahui sesuatupun..."
|
||||||
</p>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Social -->
|
<!-- Social Media Links -->
|
||||||
<div class="mt-12 animate-fade-in-up animation-delay-1500 flex justify-center space-x-6">
|
<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 href="https://instagram.com/abbauftech" target="_blank" rel="noopener noreferrer"
|
<a
|
||||||
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"
|
v-if="jsonData.link_instagram"
|
||||||
aria-label="Instagram">
|
:href="jsonData.link_instagram"
|
||||||
<Icon name="lucide:instagram" class="w-6 h-6 text-yellow-400" />
|
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>
|
||||||
<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">
|
<a
|
||||||
<Icon name="lucide:facebook" class="w-6 h-6 text-yellow-400" />
|
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>
|
||||||
<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">
|
<a
|
||||||
<Icon name="lucide:message-circle" class="w-6 h-6 text-yellow-400" />
|
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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -66,10 +87,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
childName: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
jsonData: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
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>");
|
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 {
|
.animate-fade-in-up {
|
||||||
animation: fadeInUp 1s forwards;
|
animation: fadeInUp 1s ease-out forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-fade-in-down {
|
.animate-fade-in-down {
|
||||||
animation: fadeInDown 1s forwards;
|
animation: fadeInDown 1s ease-out forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeInUp {
|
@keyframes fadeInUp {
|
||||||
@ -102,4 +126,21 @@ const currentYear = computed(() => new Date().getFullYear())
|
|||||||
from { opacity: 0; transform: translateY(-20px); }
|
from { opacity: 0; transform: translateY(-20px); }
|
||||||
to { opacity: 1; transform: translateY(0); }
|
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>
|
</style>
|
||||||
|
```
|
||||||
@ -1,6 +1,8 @@
|
|||||||
<!-- components/undangan/undangan-khitan-premium.vue -->
|
<!-- components/undangan/undangan-khitan-premium.vue -->
|
||||||
<template>
|
<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 ================= -->
|
<!-- ================= NAVIGATION ================= -->
|
||||||
<nav
|
<nav
|
||||||
v-if="currentSection !== 'landing'"
|
v-if="currentSection !== 'landing'"
|
||||||
@ -9,21 +11,11 @@
|
|||||||
<ul
|
<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"
|
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>
|
<li><button @click="switchSection('introduction')" :class="navClass('introduction')">Intro</button></li>
|
||||||
<button @click="switchSection('introduction')" :class="navClass('introduction')">Intro</button>
|
<li><button @click="switchSection('event')" :class="navClass('event')">Event</button></li>
|
||||||
</li>
|
<li><button @click="switchSection('gallery')" :class="navClass('gallery')">Gallery</button></li>
|
||||||
<li>
|
<li><button @click="switchSection('say')" :class="navClass('say')">Guest Book</button></li>
|
||||||
<button @click="switchSection('event')" :class="navClass('event')">Event</button>
|
<li><button @click="switchSection('thanks')" :class="navClass('thanks')">Thanks</button></li>
|
||||||
</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>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@ -32,6 +24,7 @@
|
|||||||
<button @click="toggleMusic" class="bg-blue-600 p-3 rounded-full text-white shadow-lg">
|
<button @click="toggleMusic" class="bg-blue-600 p-3 rounded-full text-white shadow-lg">
|
||||||
{{ isPlaying ? '⏸️' : '▶️' }}
|
{{ isPlaying ? '⏸️' : '▶️' }}
|
||||||
</button>
|
</button>
|
||||||
|
<audio ref="audioPlayer" :src="musicUrl" loop></audio>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ================= MAIN CONTENT ================= -->
|
<!-- ================= MAIN CONTENT ================= -->
|
||||||
@ -47,20 +40,28 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Introduction -->
|
<!-- Introduction -->
|
||||||
<KhitanIntroduction
|
<KhitanIntroduction
|
||||||
v-if="currentSection === 'introduction'"
|
v-if="currentSection === 'introduction'"
|
||||||
:data="formData"
|
:form="formData"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<!-- Event -->
|
<!-- Event -->
|
||||||
<KhitanEvent
|
<KhitanEvent
|
||||||
v-if="currentSection === 'event'"
|
v-if="currentSection === 'event'"
|
||||||
:hari_tanggal_acara="formData.hari_tanggal_acara"
|
:hari_tanggal_acara="formData.hari_tanggal_acara"
|
||||||
:waktu="formData.waktu"
|
:waktu="formData.waktu"
|
||||||
:alamat="formData.alamat"
|
:alamat="formData.alamat"
|
||||||
:link_gmaps="formData.link_gmaps"
|
: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 -->
|
<!-- Gallery -->
|
||||||
<KhitanGallery
|
<KhitanGallery
|
||||||
@ -76,18 +77,15 @@
|
|||||||
@addMessage="addMessage"
|
@addMessage="addMessage"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
<!-- Thank You -->
|
<!-- Thank You -->
|
||||||
<KhitanThankYou
|
<KhitanThankYou v-if="currentSection === 'thanks'" :childName="formData.nama_panggilan" :jsonData="formData" />
|
||||||
v-if="currentSection === 'thanks'"
|
|
||||||
:childName="formData.nama_panggilan"
|
|
||||||
:jsonData="formData"
|
|
||||||
/>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { useRuntimeConfig } from '#app'
|
import { useRuntimeConfig } from '#app'
|
||||||
|
|
||||||
// ================== IMPORT KOMPONEN ==================
|
// ================== IMPORT KOMPONEN ==================
|
||||||
@ -112,15 +110,27 @@ const formData = computed(() => props.data.form || {})
|
|||||||
|
|
||||||
// ================== GALERI ==================
|
// ================== GALERI ==================
|
||||||
const galleryImages = computed(() => {
|
const galleryImages = computed(() => {
|
||||||
return [
|
const f = formData.value.foto
|
||||||
formData.value.foto_1,
|
|
||||||
formData.value.foto_2,
|
|
||||||
formData.value.foto_3,
|
if (Array.isArray(f)) {
|
||||||
formData.value.foto_4,
|
return f.map(img => `${backendUrl}/storage/${img}`)
|
||||||
formData.value.foto_5
|
}
|
||||||
]
|
|
||||||
.filter(Boolean)
|
// Jika masih bentuk lama (foto_1, foto_2, dst.)
|
||||||
.map(f => `${backendUrl}/${f}`)
|
return [
|
||||||
|
formData.value.foto_1,
|
||||||
|
formData.value.foto_2,
|
||||||
|
formData.value.foto_3,
|
||||||
|
formData.value.foto_4,
|
||||||
|
formData.value.foto_5
|
||||||
|
].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 ==================
|
// ================== NAVIGASI SECTION ==================
|
||||||
@ -128,8 +138,21 @@ const currentSection = ref('landing')
|
|||||||
const switchSection = (s) => (currentSection.value = s)
|
const switchSection = (s) => (currentSection.value = s)
|
||||||
|
|
||||||
// ================== MUSIK ==================
|
// ================== MUSIK ==================
|
||||||
|
const audioPlayer = ref(null)
|
||||||
const isPlaying = ref(false)
|
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 ==================
|
// ================== GUEST BOOK ==================
|
||||||
const messages = ref([])
|
const messages = ref([])
|
||||||
@ -140,15 +163,9 @@ const navClass = (s) =>
|
|||||||
currentSection.value === s
|
currentSection.value === s
|
||||||
? 'text-blue-800 underline'
|
? 'text-blue-800 underline'
|
||||||
: 'hover:text-blue-700'
|
: 'hover:text-blue-700'
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
console.log("🧾 formData:", formData.value)
|
|
||||||
console.log("🖼️ galleryImages:", galleryImages.value)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* Animasi transisi antar section */
|
|
||||||
main {
|
main {
|
||||||
transition: all 0.7s ease-in-out;
|
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