This commit is contained in:
Muzakki Parsaoran Siregar 2025-10-24 16:43:47 +07:00
parent f044a14fa2
commit 3b6063d8c5
9 changed files with 343 additions and 231 deletions

View File

@ -2,3 +2,4 @@
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
} }

View File

@ -12,7 +12,7 @@
</div> </div>
<!-- Main Content --> <!-- Main Content -->
<div class="relative z-10 min-h-screen flex items-center justify-center px-4 md:px-8 py-12"> <div class="relative z-10 min-h-screen flex items-center justify-center px-4 md:px-8 py-22">
<div class="text-center max-w-5xl mx-auto"> <div class="text-center max-w-5xl mx-auto">
<!-- Bismillah --> <!-- Bismillah -->
<div class="mb-10 animate-fade-in-down"> <div class="mb-10 animate-fade-in-down">
@ -158,17 +158,21 @@
</div> </div>
</div> </div>
<!-- Decorative Element --> <!-- Decorative Lanterns -->
<div class="absolute inset-0 pointer-events-none overflow-hidden"> <div class="absolute top-4 left-4 md:top-8 md:left-8 animate-sway">
<div class="absolute top-0 left-1/2 transform -translate-x-1/2"> <Lantern />
<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>
<div class="absolute top-4 right-4 md:top-8 md:right-8 animate-sway animation-delay-1000">
<Lantern />
</div>
<!-- Kubah Decorations -->
<div class="absolute top-0 left-8 md:left-32 w-14 md:w-34 kubah-swing animation-delay-300">
<img src="/kubah.png" alt="Kubah" class="w-full h-auto opacity-80" />
</div>
<div class="absolute top-0 right-8 md:right-22 w-14 md:w-44 kubah-swing animation-delay-600">
<img src="/kubah.png" alt="Kubah" class="w-full h-auto opacity-80" />
</div>
</div> </div>
</template> </template>
@ -294,11 +298,16 @@ onUnmounted(() => {
</script> </script>
<style scoped> <style scoped>
/* Animations & bg-pattern */ /* =========================
🎨 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='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>");
} }
/* =========================
General Animations
========================= */
.animate-sway { .animate-sway {
animation: sway 3s ease-in-out infinite; animation: sway 3s ease-in-out infinite;
} }
@ -308,20 +317,37 @@ onUnmounted(() => {
50% { transform: translateY(-10px); } 50% { transform: translateY(-10px); }
} }
/* =========================
🕌 Smooth Kubah Swing
========================= */
.kubah-swing {
animation: kubahSwingSmooth 6s cubic-bezier(0.45, 0, 0.55, 1) infinite;
transform-origin: top center;
}
@keyframes kubahSwingSmooth {
0% { transform: rotate(0deg); }
20% { transform: rotate(3deg); }
40% { transform: rotate(-3deg); }
60% { transform: rotate(3.5deg); }
80% { transform: rotate(-3deg); }
100% { transform: rotate(0deg); }
}
/* =========================
Fade Animations
========================= */
.animate-fade-in-down { .animate-fade-in-down {
animation: fadeInDown 1s ease-out forwards; animation: fadeInDown 0.5s ease-out forwards;
} }
.animate-fade-in-up { .animate-fade-in-up {
animation: fadeInUp 1s ease-out forwards; animation: fadeInUp 0.5s ease-out forwards;
} }
.animate-fade-in-left { .animate-fade-in-left {
animation: fadeInLeft 1s ease-out forwards; animation: fadeInLeft 0.5s ease-out forwards;
} }
.animate-fade-in-right { .animate-fade-in-right {
animation: fadeInRight 1s ease-out forwards; animation: fadeInRight 0.5s ease-out forwards;
} }
@keyframes fadeInDown { @keyframes fadeInDown {
@ -344,19 +370,17 @@ onUnmounted(() => {
to { opacity: 1; transform: translateX(0); } to { opacity: 1; transform: translateX(0); }
} }
.animation-delay-300 { /* =========================
animation-delay: 0.3s; Delay Utilities
} ========================= */
.animation-delay-300 { animation-delay: 0.3s; }
.animation-delay-600 { .animation-delay-600 { animation-delay: 0.6s; }
animation-delay: 0.6s; .animation-delay-1000 { animation-delay: 0.8s; }
}
.animation-delay-1000 {
animation-delay: 1s;
}
/* =========================
🕌 Font Arabic
========================= */
.arabic-text { .arabic-text {
font-family: 'Noto Naskh Arabic', serif; font-family: 'Noto Naskh Arabic', serif;
} }
</style> </style>

View File

@ -4,7 +4,7 @@
<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 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-18">
<!-- 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">
@ -17,40 +17,20 @@
</p> </p>
</div> </div>
<!-- Photo Gallery Grid --> <!-- Masonry-style Gallery -->
<div class="flex-1 max-w-4xl mx-auto w-full animate-fade-in-up animation-delay-300"> <div class="flex-1 max-w-5xl mx-auto w-full animate-fade-in-up animation-delay-300">
<div class="grid grid-cols-2 md:grid-cols-3 gap-4"> <div class="columns-2 md:columns-3 gap-4 space-y-4">
<!-- Foto pertama (besar) -->
<div <div
v-if="images.length > 0" v-for="(image, index) in images"
class="md:col-span-1 md:row-span-2 relative group cursor-pointer" :key="index"
@click="openModal(0)" class="relative group break-inside-avoid cursor-pointer"
@click="openModal(index)"
> >
<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"> <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"
@error="handleImageError"
/>
<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>
<!-- Foto lainnya -->
<div
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 border border-yellow-400/30 hover:border-yellow-400/60 transition-all duration-300">
<img <img
:src="image" :src="image"
:alt="`Gallery Image ${index + 2}`" :alt="`Gallery Image ${index + 1}`"
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" class="w-full h-auto object-cover group-hover:scale-105 transition-transform duration-300"
@error="handleImageError" @error="handleImageError"
/> />
<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 flex items-center justify-center transition-colors duration-300">
@ -180,4 +160,9 @@ onUnmounted(() => {
.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>');
} }
</style>
/* Masonry layout fix */
.break-inside-avoid {
break-inside: avoid;
}
</style>

View File

@ -1,11 +1,12 @@
<!-- components/shared/GuestBook.vue --> <!-- components/shared/GuestBook.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-x-hidden overflow-y-auto">
<!-- 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 items-center justify-center px-6 py-12"> <div class="relative z-10 h-full flex items-center justify-center px-6 py-18">
<div class="w-full max-w-6xl mx-auto"> <div class="w-full max-w-6xl mx-auto">
<!-- Title --> <!-- Title -->

View File

@ -2,7 +2,10 @@
<div <div
class="h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden" class="h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden"
> >
<!-- Main Content --> <!-- Background Pattern -->
<div class="absolute inset-0 bg-pattern opacity-30"></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="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 -->
@ -11,49 +14,44 @@
Introduction Introduction
</h1> </h1>
</div> </div>
<!-- Background Pattern --> <!-- Child Section -->
<div class="absolute inset-0 bg-pattern opacity-30"></div> <div class="relative z-10 h-full flex flex-col items-center justify-center px-6 text-center">
<!-- Child Photo -->
<div class="w-68 h-80 overflow-hidden rounded-t-3xl mx-auto mb-[-20px] shadow-lg">
<img
:src="getFullPhotoUrl(childPhoto)"
alt="Child"
class="w-full h-full object-cover"
/>
</div>
<!-- Main Content --> <!-- Child Name with nickname.png background (Tailwind version) -->
<div <div
class="relative z-10 h-full flex flex-col items-center justify-center px-6 text-center" class="relative mb-0 inline-flex items-center justify-center w-[420px] h-[110px] bg-[url('/nickname.png')] bg-contain bg-no-repeat bg-center"
> >
<!-- Child Photo --> <h1 class="text-yellow-400 font-bold text-xl md:text-2xl font-script drop-shadow-lg">
<div class="w-48 h-60 overflow-hidden rounded-t-3xl mx-auto mb-6 shadow-lg"> {{ form.nama_lengkap || 'Nama Anak' }}
<img </h1>
:src="getFullPhotoUrl(childPhoto)" </div>
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">
{{ form.nama_lengkap || 'Nama Anak' }}
</h1>
</div>
<!-- Child Order --> <!-- Child Order -->
<div v-if="form.anak_ke" class="text-white/80 text-lg mb-2"> <div v-if="form.anak_ke" class="text-white/80 text-lg mb-2">
Putra ke {{ form.anak_ke }} dari Putra ke {{ form.anak_ke }} dari
</div> </div>
<div v-else class="text-white/80 text-lg mb-2"> <div v-else class="text-white/80 text-lg mb-2">
Putra dari Putra dari
</div> </div>
<!-- Parents --> <!-- Parents -->
<div class="text-yellow-400 font-semibold text-xl"> <div class="text-yellow-400 font-semibold text-xl">
{{ form.nama_bapak || 'Nama Ayah' }} & {{ form.nama_ibu || 'Nama Ibu' }} {{ form.nama_bapak || 'Nama Ayah' }} & {{ form.nama_ibu || 'Nama Ibu' }}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</template> </template>
<script setup> <script setup>
@ -89,4 +87,20 @@ const getFullPhotoUrl = (photo) => {
.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>');
} }
/* Efek animasi halus muncul dari atas */
@keyframes fade-in-down {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-down {
animation: fade-in-down 1s ease-out;
}
</style> </style>

View File

@ -1,33 +1,55 @@
```vue
<template> <template>
<div class="min-h-screen w-full relative bg-gradient-to-br from-blue-900 via-blue-800 to-blue-900 overflow-hidden"> <div
<!-- Background Pattern --> 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> <div class="absolute inset-0 bg-pattern opacity-20"></div>
<!-- Main Content --> <!-- Kubah Decorations -->
<div class="absolute top-0 left-8 md:left-32 w-16 md:w-34 kubah-swing animation-delay-300">
<img src="/kubah.png" alt="Kubah" class="w-full h-auto opacity-80" />
</div>
<div class="absolute top-0 right-8 md:right-22 w-16 md:w-44 kubah-swing animation-delay-600">
<img src="/kubah.png" alt="Kubah" class="w-full h-auto opacity-80" />
</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="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-10 animate-fade-in-down"> <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"> <h1
class="text-yellow-400 text-3xl md:text-5xl lg:text-6xl font-bold mb-4 md:mb-6 font-script"
>
Terima Kasih Terima Kasih
</h1> </h1>
<p class="text-white text-sm md:text-base lg:text-lg leading-relaxed max-w-3xl mx-auto"> <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 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>. <span class="text-yellow-400 font-semibold">{{
jsonData.nama_panggilan || "putra kami"
}}</span>.
Semoga Allah SWT senantiasa melimpahkan rahmat dan keberkahan kepada kita semua. 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-xl md:text-2xl lg:text-3xl font-bold mb-6 md:mb-8"> <h2
class="text-yellow-400 text-xl md:text-2xl lg:text-3xl font-bold mb-6 md:mb-8"
>
Kami Keluarga Besar Dari Kami Keluarga Besar Dari
</h2> </h2>
<div class="space-y-4 md:space-y-6"> <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
<div class="text-yellow-400 text-base md:text-lg font-semibold mb-2"> 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"
{{ jsonData.nama_bapak || 'Bpk H. Munawar Huda, S.H.' }} & {{ jsonData.nama_ibu || 'Ibu Hj. Dinah, A.M.Keb' }} >
<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> </div>
</div> </div>
@ -35,91 +57,147 @@
<!-- 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-md rounded-2xl p-6 md:p-8 border border-yellow-400/50 shadow-lg"> <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"> <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 class="text-yellow-400 text-sm md:text-base font-semibold">
- QS. An-Nahl: 78
</p> </p>
<p class="text-yellow-400 text-sm md:text-base font-semibold">- QS. An-Nahl: 78</p>
</div> </div>
</div> </div>
<!-- Social Media Links --> <!-- 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"> <!-- Social Media Links -->
<a <div
v-if="jsonData.link_instagram" class="mt-10 md:mt-12 animate-fade-in-up animation-delay-1500 flex justify-center space-x-4 md:space-x-6"
:href="jsonData.link_instagram" >
target="_blank" <a
rel="noopener noreferrer" v-if="jsonData.link_instagram"
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" :href="jsonData.link_instagram"
aria-label="Instagram" target="_blank"
> rel="noopener noreferrer"
<Icon name="lucide:instagram" class="w-5 h-5 md:w-6 md:h-6 text-yellow-400" /> class="w-10 h-10 md:w-12 md:h-12 flex items-center justify-center rounded-full 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"
</a> aria-label="Instagram"
<a >
v-if="jsonData.link_facebook" <Icon
:href="jsonData.link_facebook" name="lucide:instagram"
target="_blank" class="w-5 h-5 md:w-6 md:h-6 text-yellow-400"
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" </a>
aria-label="Facebook"
> <a
<Icon name="lucide:facebook" class="w-5 h-5 md:w-6 md:h-6 text-yellow-400" /> v-if="jsonData.link_facebook"
</a> :href="jsonData.link_facebook"
<a target="_blank"
v-if="jsonData.link_twitter" rel="noopener noreferrer"
:href="jsonData.link_twitter" class="w-10 h-10 md:w-12 md:h-12 flex items-center justify-center rounded-full 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"
target="_blank" aria-label="Facebook"
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" <Icon
aria-label="Twitter" name="lucide:facebook"
> class="w-5 h-5 md:w-6 md:h-6 text-yellow-400"
<Icon name="lucide:twitter" class="w-5 h-5 md:w-6 md:h-6 text-yellow-400" /> />
</a> </a>
</div>
<a
v-if="jsonData.link_twitter"
:href="jsonData.link_twitter"
target="_blank"
rel="noopener noreferrer"
class="w-10 h-10 md:w-12 md:h-12 flex items-center justify-center rounded-full 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> </div>
</div> </div>
<!-- Copyright --> <!-- Footer -->
<div class="absolute bottom-4 left-1/2 transform -translate-x-1/2 z-10"> <div class="absolute bottom-4 left-1/2 transform -translate-x-1/2 z-10">
<p class="text-white/60 text-xs text-center">© {{ currentYear }} - Invitation Template</p> <p class="text-white/60 text-xs text-center">
© {{ currentYear }} - Invitation Template
</p>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { computed } from 'vue' import { computed } from "vue";
const props = defineProps({ const props = defineProps({
childName: { childName: {
type: String, type: String,
default: '' default: "",
}, },
jsonData: { jsonData: {
type: Object, type: Object,
default: () => ({}) default: () => ({}),
} },
}) });
const currentYear = computed(() => new Date().getFullYear()) const currentYear = computed(() => new Date().getFullYear());
</script> </script>
<style scoped> <style scoped>
/* Animations & bg-pattern */ /* =========================
🎨 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='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>");
} }
.animate-fade-in-up { /* =========================
animation: fadeInUp 1s ease-out forwards; General Animations
========================= */
.animate-sway {
animation: sway 3s ease-in-out infinite;
} }
@keyframes sway {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
/* =========================
🕌 Smooth Kubah Swing
========================= */
.kubah-swing {
animation: kubahSwingSmooth 6s cubic-bezier(0.45, 0, 0.55, 1) infinite;
transform-origin: top center;
}
@keyframes kubahSwingSmooth {
0% { transform: rotate(0deg); }
20% { transform: rotate(2.5deg); }
40% { transform: rotate(-2.5deg); }
60% { transform: rotate(2deg); }
80% { transform: rotate(-2deg); }
100% { transform: rotate(0deg); }
}
/* =========================
Fade Animations
========================= */
.animate-fade-in-down { .animate-fade-in-down {
animation: fadeInDown 1s ease-out forwards; animation: fadeInDown 1s ease-out forwards;
} }
.animate-fade-in-up {
@keyframes fadeInUp { animation: fadeInUp 1s ease-out forwards;
from { opacity: 0; transform: translateY(20px); } }
to { opacity: 1; transform: translateY(0); } .animate-fade-in-left {
animation: fadeInLeft 1s ease-out forwards;
}
.animate-fade-in-right {
animation: fadeInRight 1s ease-out forwards;
} }
@keyframes fadeInDown { @keyframes fadeInDown {
@ -127,20 +205,32 @@ const currentYear = computed(() => new Date().getFullYear())
to { opacity: 1; transform: translateY(0); } to { opacity: 1; transform: translateY(0); }
} }
.animation-delay-300 { @keyframes fadeInUp {
animation-delay: 0.3s; from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
} }
.animation-delay-500 { @keyframes fadeInLeft {
animation-delay: 0.5s; from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
} }
.animation-delay-1200 { @keyframes fadeInRight {
animation-delay: 1.2s; from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
} }
.animation-delay-1500 { /* =========================
animation-delay: 1.5s; Delay Utilities
========================= */
.animation-delay-300 { animation-delay: 0.3s; }
.animation-delay-600 { animation-delay: 0.6s; }
.animation-delay-1000 { animation-delay: 1s; }
/* =========================
🕌 Font Arabic
========================= */
.arabic-text {
font-family: 'Noto Naskh Arabic', serif;
} }
</style> </style>
```

View File

@ -31,7 +31,6 @@
<main <main
class="relative z-10 min-h-screen flex items-center justify-center p-4 transition-all duration-700 ease-in-out" class="relative z-10 min-h-screen flex items-center justify-center p-4 transition-all duration-700 ease-in-out"
> >
<!-- Landing Page -->
<KhitanA <KhitanA
v-if="currentSection === 'landing'" v-if="currentSection === 'landing'"
:childName="formData.nama_panggilan" :childName="formData.nama_panggilan"
@ -39,37 +38,30 @@
@next-page="switchSection('introduction')" @next-page="switchSection('introduction')"
/> />
<!-- Introduction --> <KhitanIntroduction
<KhitanIntroduction
v-if="currentSection === 'introduction'" v-if="currentSection === 'introduction'"
:form="formData" :form="formData"
/> />
<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_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"
/>
<!-- Event -->
<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_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 <KhitanGallery
v-if="currentSection === 'gallery'" v-if="currentSection === 'gallery'"
:images="galleryImages" :images="galleryImages"
/> />
<!-- Guest Book -->
<KhitanSay <KhitanSay
v-if="currentSection === 'say'" v-if="currentSection === 'say'"
:guestName="data.nama_tamu" :guestName="data.nama_tamu"
@ -77,18 +69,18 @@
@addMessage="addMessage" @addMessage="addMessage"
/> />
<KhitanThankYou
<!-- Thank You --> v-if="currentSection === 'thanks'"
<KhitanThankYou v-if="currentSection === 'thanks'" :childName="formData.nama_panggilan" :jsonData="formData" /> :childName="formData.nama_panggilan"
:jsonData="formData"
/>
</main> </main>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, computed, watch } from 'vue' import { ref, computed } from 'vue'
import { useRuntimeConfig } from '#app' import { useRuntimeConfig } from '#app'
// ================== IMPORT KOMPONEN ==================
import KhitanA from '~/components/templates/khitan/KhitanA.vue' import KhitanA from '~/components/templates/khitan/KhitanA.vue'
import KhitanIntroduction from '~/components/templates/khitan/Introduction.vue' import KhitanIntroduction from '~/components/templates/khitan/Introduction.vue'
import KhitanEvent from '~/components/templates/khitan/Event.vue' import KhitanEvent from '~/components/templates/khitan/Event.vue'
@ -96,48 +88,30 @@ import KhitanGallery from '~/components/templates/khitan/Gallery.vue'
import KhitanSay from '~/components/templates/khitan/GuestBook.vue' import KhitanSay from '~/components/templates/khitan/GuestBook.vue'
import KhitanThankYou from '~/components/templates/khitan/ThankYou.vue' import KhitanThankYou from '~/components/templates/khitan/ThankYou.vue'
// ================== PROPS ==================
const props = defineProps({ const props = defineProps({
data: { type: Object, required: true } data: { type: Object, required: true }
}) })
// ================== BACKEND CONFIG ==================
const config = useRuntimeConfig() const config = useRuntimeConfig()
const backendUrl = config.public.apiBaseUrl const backendUrl = config.public.apiBaseUrl
// ================== FORM DATA ==================
const formData = computed(() => props.data.form || {}) const formData = computed(() => props.data.form || {})
// ================== GALERI ==================
const galleryImages = computed(() => { const galleryImages = computed(() => {
const f = formData.value.foto const f = formData.value.foto
if (Array.isArray(f)) {
return f.map(img => `${backendUrl}/storage/${img}`)
if (Array.isArray(f)) { }
return f.map(img => `${backendUrl}/storage/${img}`) return [
} formData.value.foto_1,
formData.value.foto_2,
// Jika masih bentuk lama (foto_1, foto_2, dst.) formData.value.foto_3,
return [ formData.value.foto_4,
formData.value.foto_1, formData.value.foto_5
formData.value.foto_2, ].filter(Boolean).map(img => `${backendUrl}/${img}`)
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 ==================
const currentSection = ref('landing') const currentSection = ref('landing')
const switchSection = (s) => (currentSection.value = s) const switchSection = (s) => (currentSection.value = s)
// ================== MUSIK ==================
const audioPlayer = ref(null) const audioPlayer = ref(null)
const isPlaying = ref(false) const isPlaying = ref(false)
const musicUrl = computed(() => const musicUrl = computed(() =>
@ -154,11 +128,9 @@ const toggleMusic = () => {
isPlaying.value = !isPlaying.value isPlaying.value = !isPlaying.value
} }
// ================== GUEST BOOK ==================
const messages = ref([]) const messages = ref([])
const addMessage = (msg) => messages.value.push(msg) const addMessage = (msg) => messages.value.push(msg)
// ================== STYLE NAV ==================
const navClass = (s) => const navClass = (s) =>
currentSection.value === s currentSection.value === s
? 'text-blue-800 underline' ? 'text-blue-800 underline'
@ -166,6 +138,31 @@ const navClass = (s) =>
</script> </script>
<style scoped> <style scoped>
/* Pastikan benar-benar fullscreen */
html,
body,
:host,
div[min-h-screen],
.min-h-screen {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
/* Hilangkan white space akibat scroll */
body {
overflow-x: hidden;
background: transparent;
}
/* Pastikan gradient menutup seluruh layar */
div[min-h-screen] {
position: relative;
inset: 0;
}
/* Transisi section tetap */
main { main {
transition: all 0.7s ease-in-out; transition: all 0.7s ease-in-out;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB