362 lines
14 KiB
Vue
362 lines
14 KiB
Vue
<template>
|
||
<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-20"></div>
|
||
|
||
<!-- Decorative Lanterns -->
|
||
<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 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-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-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:
|
||
</p>
|
||
</div>
|
||
|
||
<!-- 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-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-2xl md:text-3xl lg:text-4xl font-bold mb-6">
|
||
{{ eventDate }} ({{ eventDay }})
|
||
</div>
|
||
<div class="text-white mb-6">
|
||
<p class="text-sm md:text-base mb-2">🕓 {{ eventTime }}</p>
|
||
<p class="text-sm md:text-base mb-2">📍 {{ eventLocation }}</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="eventMap"
|
||
alt="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 countdownItemsAcara"
|
||
: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="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 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-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>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||
|
||
// Props sesuai dengan parent dan JSON
|
||
const props = defineProps({
|
||
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: '' }
|
||
})
|
||
|
||
/* ==========================
|
||
🎯 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)}`)
|
||
|
||
/* ==========================
|
||
🎉 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 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 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 now = new Date()
|
||
|
||
// Countdown untuk acara khitanan
|
||
if (props.hitung_mundur_mulai) {
|
||
const eventDateObj = new Date(props.hitung_mundur_mulai)
|
||
const diff = eventDateObj - now
|
||
if (diff > 0) {
|
||
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 }
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ==========================
|
||
📅 ACTIONS
|
||
========================== */
|
||
const addToCalendar = () => {
|
||
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')
|
||
}
|
||
|
||
/* ==========================
|
||
⏳ LIFECYCLE
|
||
========================== */
|
||
onMounted(() => {
|
||
updateCountdown()
|
||
countdownInterval.value = setInterval(updateCountdown, 1000)
|
||
})
|
||
onUnmounted(() => {
|
||
if (countdownInterval.value) {
|
||
clearInterval(countdownInterval.value)
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 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> |