feat: add logo display in AppSidebar and improve layout in Home.vue for better user experience

This commit is contained in:
Yoga Pangestu 2026-06-20 10:54:02 +07:00
parent 9df8fe1ce2
commit 6aa818e562
2 changed files with 169 additions and 163 deletions

View File

@ -92,7 +92,13 @@ const menuGroups: MenuGroup[] = [
<SidebarMenuItem> <SidebarMenuItem>
<SidebarMenuButton size="lg" as-child> <SidebarMenuButton size="lg" as-child>
<Link href="/admin/dashboard"> <Link href="/admin/dashboard">
<div class="grid flex-1 text-left text-sm leading-tight"> <img
v-if="page.props.logo_url"
:src="page.props.logo_url"
:alt="page.props.name"
class="h-8 w-auto object-contain"
/>
<div v-else class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ page.props.name }}</span> <span class="truncate font-semibold">{{ page.props.name }}</span>
</div> </div>
</Link> </Link>

View File

@ -1,17 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { import {
Search, Search,
ShoppingBag, ShoppingBag,
Sun, Sun,
Moon, Moon,
X, X,
Plus, Plus,
Minus, Minus,
ArrowRight, ArrowRight,
Star, Star,
Check, Check,
Info, Info,
ChevronRight, ChevronRight,
Eye, Eye,
Phone, Phone,
@ -107,7 +107,7 @@ const toggleCategory = (slug: string) => {
// Helper to get ecer price of a product for filtering/sorting // Helper to get ecer price of a product for filtering/sorting
const getPrimaryEcerPrice = (product: Product): number => { const getPrimaryEcerPrice = (product: Product): number => {
const ecerPrices = product.variants.flatMap(v => const ecerPrices = product.variants.flatMap(v =>
v.prices.filter(p => p.type === 'ecer').map(p => p.price) v.prices.filter(p => p.type === 'ecer').map(p => p.price)
); );
@ -126,16 +126,16 @@ const filteredProducts = computed(() => {
// Search filter // Search filter
const matchesSearch = product.name.toLowerCase().includes(searchInput.value.toLowerCase()) || const matchesSearch = product.name.toLowerCase().includes(searchInput.value.toLowerCase()) ||
(product.description && product.description.toLowerCase().includes(searchInput.value.toLowerCase())); (product.description && product.description.toLowerCase().includes(searchInput.value.toLowerCase()));
// Category filter // Category filter
const matchesCategory = !selectedCategory.value || const matchesCategory = !selectedCategory.value ||
product.categories.some(cat => cat.slug === selectedCategory.value); product.categories.some(cat => cat.slug === selectedCategory.value);
// Price filter // Price filter
const price = getPrimaryEcerPrice(product); const price = getPrimaryEcerPrice(product);
const matchesMinPrice = minPrice.value === null || price >= minPrice.value; const matchesMinPrice = minPrice.value === null || price >= minPrice.value;
const matchesMaxPrice = maxPrice.value === null || price <= maxPrice.value; const matchesMaxPrice = maxPrice.value === null || price <= maxPrice.value;
return matchesSearch && matchesCategory && matchesMinPrice && matchesMaxPrice; return matchesSearch && matchesCategory && matchesMinPrice && matchesMaxPrice;
}); });
@ -158,7 +158,7 @@ const getProductImage = (product: Product, indexOffset = 0) => {
return variant.images[0].url; return variant.images[0].url;
} }
} }
// Fallbacks based on category slug // Fallbacks based on category slug
const firstCat = product.categories?.[0]?.slug || ''; const firstCat = product.categories?.[0]?.slug || '';
const fallbacks: Record<string, string[]> = { const fallbacks: Record<string, string[]> = {
@ -183,12 +183,12 @@ const getProductImage = (product: Product, indexOffset = 0) => {
'https://images.unsplash.com/photo-1496747611176-843222e1e57c?w=800&auto=format&fit=crop&q=80' 'https://images.unsplash.com/photo-1496747611176-843222e1e57c?w=800&auto=format&fit=crop&q=80'
] ]
}; };
const categoryUrls = fallbacks[firstCat] || [ const categoryUrls = fallbacks[firstCat] || [
'https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=800&auto=format&fit=crop&q=80', 'https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=800&auto=format&fit=crop&q=80',
'https://images.unsplash.com/photo-1434389677669-e08b4cac3105?w=800&auto=format&fit=crop&q=80' 'https://images.unsplash.com/photo-1434389677669-e08b4cac3105?w=800&auto=format&fit=crop&q=80'
]; ];
return categoryUrls[indexOffset % categoryUrls.length]; return categoryUrls[indexOffset % categoryUrls.length];
}; };
@ -199,10 +199,10 @@ const formatPrice = (price: number) => {
// Price Range Resolver // Price Range Resolver
const getProductPriceRange = (product: Product) => { const getProductPriceRange = (product: Product) => {
const prices = product.variants.flatMap(v => const prices = product.variants.flatMap(v =>
v.prices.filter(p => p.type === 'ecer').map(p => p.price) v.prices.filter(p => p.type === 'ecer').map(p => p.price)
); );
if (prices.length === 0) { if (prices.length === 0) {
const fallbackPrices = product.variants.flatMap(v => v.prices.map(p => p.price)); const fallbackPrices = product.variants.flatMap(v => v.prices.map(p => p.price));
@ -215,7 +215,7 @@ return 'Rp 0';
return min === max ? formatPrice(min) : `${formatPrice(min)} - ${formatPrice(max)}`; return min === max ? formatPrice(min) : `${formatPrice(min)} - ${formatPrice(max)}`;
} }
const min = Math.min(...prices); const min = Math.min(...prices);
const max = Math.max(...prices); const max = Math.max(...prices);
@ -239,11 +239,11 @@ const seconds = ref(0);
const startCountdown = () => { const startCountdown = () => {
const now = new Date(); const now = new Date();
const target = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59); const target = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59);
const update = () => { const update = () => {
const current = new Date().getTime(); const current = new Date().getTime();
const diff = target.getTime() - current; const diff = target.getTime() - current;
if (diff <= 0) { if (diff <= 0) {
days.value = 0; days.value = 0;
hours.value = 0; hours.value = 0;
@ -252,13 +252,13 @@ const startCountdown = () => {
return; return;
} }
days.value = Math.floor(diff / (1000 * 60 * 60 * 24)); days.value = Math.floor(diff / (1000 * 60 * 60 * 24));
hours.value = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); hours.value = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
minutes.value = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); minutes.value = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
seconds.value = Math.floor((diff % (1000 * 60)) / 1000); seconds.value = Math.floor((diff % (1000 * 60)) / 1000);
}; };
update(); update();
const interval = setInterval(update, 1000); const interval = setInterval(update, 1000);
@ -354,7 +354,7 @@ return;
} }
isCheckingOut.value = true; isCheckingOut.value = true;
// Construct WhatsApp message // Construct WhatsApp message
let message = `Halo ${props.appName}, saya ingin memesan produk berikut:\n\n`; let message = `Halo ${props.appName}, saya ingin memesan produk berikut:\n\n`;
cart.value.forEach((item, index) => { cart.value.forEach((item, index) => {
@ -366,11 +366,11 @@ return;
}); });
message += `*Total Pembayaran: ${formatPrice(cartTotal.value)}*\n\n`; message += `*Total Pembayaran: ${formatPrice(cartTotal.value)}*\n\n`;
message += `Mohon info untuk detail rekening transfer dan pengirimannya. Terima kasih!`; message += `Mohon info untuk detail rekening transfer dan pengirimannya. Terima kasih!`;
const formattedPhone = props.contactPhone.replace(/[^0-9]/g, ''); const formattedPhone = props.contactPhone.replace(/[^0-9]/g, '');
const finalPhone = formattedPhone.startsWith('0') ? '62' + formattedPhone.slice(1) : formattedPhone; const finalPhone = formattedPhone.startsWith('0') ? '62' + formattedPhone.slice(1) : formattedPhone;
const waUrl = `https://wa.me/${finalPhone || '6281234567890'}?text=${encodeURIComponent(message)}`; const waUrl = `https://wa.me/${finalPhone || '6281234567890'}?text=${encodeURIComponent(message)}`;
setTimeout(() => { setTimeout(() => {
isCheckingOut.value = false; isCheckingOut.value = false;
window.open(waUrl, '_blank'); window.open(waUrl, '_blank');
@ -402,21 +402,21 @@ clearInterval(countdownInterval);
</Head> </Head>
<div class="min-h-screen bg-gradient-to-tr from-cyan-50/20 via-[#FDFDFC] to-sky-50/30 text-slate-800 transition-colors duration-300 dark:from-slate-950 dark:via-[#0E1726] dark:to-cyan-950/20 dark:text-slate-100 font-sans scroll-smooth"> <div class="min-h-screen bg-gradient-to-tr from-cyan-50/20 via-[#FDFDFC] to-sky-50/30 text-slate-800 transition-colors duration-300 dark:from-slate-950 dark:via-[#0E1726] dark:to-cyan-950/20 dark:text-slate-100 font-sans scroll-smooth">
<!-- Sticky Header & Navbar --> <!-- Sticky Header & Navbar -->
<header class="sticky top-0 z-40 bg-white/70 backdrop-blur-lg dark:bg-[#0E1726]/70 border-b border-cyan-100/50 dark:border-cyan-950/30 transition-all"> <header class="sticky top-0 z-40 bg-white/70 backdrop-blur-lg dark:bg-[#0E1726]/70 border-b border-cyan-100/50 dark:border-cyan-950/30 transition-all">
<div class="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between"> <div class="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
<!-- Logo Frame from settings --> <!-- Logo Frame from settings -->
<div class="flex items-center space-x-3"> <div class="flex items-center space-x-3">
<a href="#" class="flex items-center space-x-2"> <a href="#" class="flex items-center space-x-2">
<img <img
v-if="logoUrl" v-if="logoUrl"
:src="logoUrl" :src="logoUrl"
:alt="appName" :alt="appName"
class="h-10 w-auto object-contain bg-white/80 p-1 rounded-lg shadow-sm" class="h-10 w-auto object-contain p-1 rounded-lg shadow-sm"
/> />
<span class="font-serif text-2xl font-bold tracking-[0.1em] text-cyan-600 dark:text-cyan-400"> <span v-else class="font-serif text-2xl font-bold tracking-[0.1em] text-cyan-600 dark:text-cyan-400">
{{ appName }} {{ appName }}
</span> </span>
</a> </a>
@ -434,9 +434,9 @@ clearInterval(countdownInterval);
<!-- Action Utilities (Search, Dark Mode, Cart) --> <!-- Action Utilities (Search, Dark Mode, Cart) -->
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<!-- Theme Toggle --> <!-- Theme Toggle -->
<button <button
@click="toggleDarkMode" @click="toggleDarkMode"
class="p-2 rounded-full hover:bg-cyan-50 dark:hover:bg-cyan-950/20 text-slate-600 dark:text-slate-300 transition-colors cursor-pointer" class="p-2 rounded-full hover:bg-cyan-50 dark:hover:bg-cyan-950/20 text-slate-600 dark:text-slate-300 transition-colors cursor-pointer"
aria-label="Ubah Tema" aria-label="Ubah Tema"
> >
<Sun v-if="isDark" class="w-4.5 h-4.5 text-amber-400" /> <Sun v-if="isDark" class="w-4.5 h-4.5 text-amber-400" />
@ -444,14 +444,14 @@ clearInterval(countdownInterval);
</button> </button>
<!-- Cart button --> <!-- Cart button -->
<button <button
@click="isCartOpen = true" @click="isCartOpen = true"
class="relative p-2 rounded-full hover:bg-cyan-50 dark:hover:bg-cyan-950/20 text-slate-600 dark:text-slate-300 transition-colors cursor-pointer" class="relative p-2 rounded-full hover:bg-cyan-50 dark:hover:bg-cyan-950/20 text-slate-600 dark:text-slate-300 transition-colors cursor-pointer"
aria-label="Keranjang Belanja" aria-label="Keranjang Belanja"
> >
<ShoppingBag class="w-4.5 h-4.5 text-cyan-600 dark:text-cyan-400" /> <ShoppingBag class="w-4.5 h-4.5 text-cyan-600 dark:text-cyan-400" />
<span <span
v-if="cartCount > 0" v-if="cartCount > 0"
class="absolute -top-0.5 -right-0.5 bg-cyan-600 text-white dark:bg-cyan-400 dark:text-slate-950 text-[9px] font-bold w-4.5 h-4.5 flex items-center justify-center rounded-full shadow-sm animate-bounce" class="absolute -top-0.5 -right-0.5 bg-cyan-600 text-white dark:bg-cyan-400 dark:text-slate-950 text-[9px] font-bold w-4.5 h-4.5 flex items-center justify-center rounded-full shadow-sm animate-bounce"
> >
{{ cartCount }} {{ cartCount }}
@ -471,32 +471,32 @@ clearInterval(countdownInterval);
<!-- Main Layout Content --> <!-- Main Layout Content -->
<div class="max-w-7xl mx-auto px-6 w-full lg:grow grid grid-cols-1 lg:grid-cols-12 gap-8 items-center py-12 z-10"> <div class="max-w-7xl mx-auto px-6 w-full lg:grow grid grid-cols-1 lg:grid-cols-12 gap-8 items-center py-12 z-10">
<!-- Left Column: Serif Text and CTA --> <!-- Left Column: Serif Text and CTA -->
<div class="lg:col-span-5 flex flex-col justify-center space-y-6 lg:pr-6"> <div class="lg:col-span-5 flex flex-col justify-center space-y-6 lg:pr-6">
<div class="inline-flex items-center space-x-2 bg-cyan-500/10 dark:bg-cyan-400/10 text-[10px] text-cyan-700 dark:text-cyan-300 uppercase tracking-widest px-3 py-1 rounded-full w-fit font-bold shadow-sm"> <div class="inline-flex items-center space-x-2 bg-cyan-500/10 dark:bg-cyan-400/10 text-[10px] text-cyan-700 dark:text-cyan-300 uppercase tracking-widest px-3 py-1 rounded-full w-fit font-bold shadow-sm">
<span>Koleksi Segar 2026</span> <span>Koleksi Segar 2026</span>
</div> </div>
<h1 class="text-5xl md:text-6xl font-serif leading-[1.1] tracking-tight font-light select-none text-slate-800 dark:text-slate-100"> <h1 class="text-5xl md:text-6xl font-serif leading-[1.1] tracking-tight font-light select-none text-slate-800 dark:text-slate-100">
Ekspresikan <br /> Ekspresikan <br />
Gaya <span class="font-normal italic text-cyan-600 dark:text-cyan-400 bg-cyan-100/40 dark:bg-cyan-900/20 px-2 rounded-lg">Segar Anda</span>. Gaya <span class="font-normal italic text-cyan-600 dark:text-cyan-400 bg-cyan-100/40 dark:bg-cyan-900/20 px-2 rounded-lg">Segar Anda</span>.
</h1> </h1>
<p class="text-sm text-slate-500 dark:text-slate-300 max-w-md leading-relaxed font-light"> <p class="text-sm text-slate-500 dark:text-slate-300 max-w-md leading-relaxed font-light">
Selamat datang di {{ appName }}. Temukan keanggunan motif batik modern dan setelan pakaian santai berkualitas premium. Dirancang khusus dengan bahan adem yang menyejukkan aktivitas harian Anda. Selamat datang di {{ appName }}. Temukan keanggunan motif batik modern dan setelan pakaian santai berkualitas premium. Dirancang khusus dengan bahan adem yang menyejukkan aktivitas harian Anda.
</p> </p>
<div class="flex items-center space-x-4 pt-4"> <div class="flex items-center space-x-4 pt-4">
<a <a
href="#catalog" href="#catalog"
class="inline-flex items-center justify-center bg-cyan-600 text-white px-8 py-3.5 text-xs font-semibold uppercase tracking-widest rounded-xl shadow-lg shadow-cyan-200/50 hover:bg-cyan-500 hover:shadow-cyan-300/50 hover:-translate-y-0.5 transition-all duration-300 group dark:bg-cyan-500 dark:text-slate-950 dark:shadow-none dark:hover:bg-cyan-400 cursor-pointer" class="inline-flex items-center justify-center bg-cyan-600 text-white px-8 py-3.5 text-xs font-semibold uppercase tracking-widest rounded-xl shadow-lg shadow-cyan-200/50 hover:bg-cyan-500 hover:shadow-cyan-300/50 hover:-translate-y-0.5 transition-all duration-300 group dark:bg-cyan-500 dark:text-slate-950 dark:shadow-none dark:hover:bg-cyan-400 cursor-pointer"
> >
Beli Sekarang Beli Sekarang
<ArrowRight class="w-3.5 h-3.5 ml-2 transform group-hover:translate-x-1 transition-transform" /> <ArrowRight class="w-3.5 h-3.5 ml-2 transform group-hover:translate-x-1 transition-transform" />
</a> </a>
<a <a
href="#about" href="#about"
class="inline-flex items-center justify-center border border-cyan-200 text-xs font-semibold uppercase tracking-widest px-8 py-3.5 rounded-xl hover:bg-cyan-500/5 hover:border-cyan-400 transition-all dark:border-cyan-800 dark:hover:bg-cyan-950/20 cursor-pointer" class="inline-flex items-center justify-center border border-cyan-200 text-xs font-semibold uppercase tracking-widest px-8 py-3.5 rounded-xl hover:bg-cyan-500/5 hover:border-cyan-400 transition-all dark:border-cyan-800 dark:hover:bg-cyan-950/20 cursor-pointer"
> >
Tentang Kami Tentang Kami
@ -506,7 +506,7 @@ clearInterval(countdownInterval);
<!-- Center Column: Visual Model Showcase & Overlapping Titles (Cyan Aura) --> <!-- Center Column: Visual Model Showcase & Overlapping Titles (Cyan Aura) -->
<div class="lg:col-span-7 relative flex justify-center items-center h-[500px] lg:h-[650px] w-full"> <div class="lg:col-span-7 relative flex justify-center items-center h-[500px] lg:h-[650px] w-full">
<!-- Massive Overlapping Serif Heading (Behind Model) --> <!-- Massive Overlapping Serif Heading (Behind Model) -->
<div class="absolute inset-0 flex justify-center items-center pointer-events-none select-none z-0"> <div class="absolute inset-0 flex justify-center items-center pointer-events-none select-none z-0">
<div class="text-[14vw] lg:text-[160px] font-serif font-bold leading-none tracking-tight flex opacity-10 dark:opacity-[0.03]"> <div class="text-[14vw] lg:text-[160px] font-serif font-bold leading-none tracking-tight flex opacity-10 dark:opacity-[0.03]">
@ -523,9 +523,9 @@ clearInterval(countdownInterval);
<!-- Main Model Central Image (With Floating Animation) --> <!-- Main Model Central Image (With Floating Animation) -->
<div class="relative w-[310px] h-[440px] lg:w-[390px] lg:h-[540px] rounded-[2rem] overflow-hidden shadow-2xl shadow-cyan-100/50 dark:shadow-none z-10 transition-transform duration-500 hover:scale-[1.02] border-4 border-white dark:border-cyan-950/80 animate-float-model"> <div class="relative w-[310px] h-[440px] lg:w-[390px] lg:h-[540px] rounded-[2rem] overflow-hidden shadow-2xl shadow-cyan-100/50 dark:shadow-none z-10 transition-transform duration-500 hover:scale-[1.02] border-4 border-white dark:border-cyan-950/80 animate-float-model">
<img <img
src="https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=1000&auto=format&fit=crop&q=80" src="https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=1000&auto=format&fit=crop&q=80"
alt="DSTCLO Model Unggulan" alt="DSTCLO Model Unggulan"
class="w-full h-full object-cover" class="w-full h-full object-cover"
/> />
<!-- Subtle Gradient Overlay --> <!-- Subtle Gradient Overlay -->
@ -533,14 +533,14 @@ clearInterval(countdownInterval);
</div> </div>
<!-- FLOATING HERO TAG 1: Product Showcase --> <!-- FLOATING HERO TAG 1: Product Showcase -->
<div <div
v-if="featuredProducts[0]" v-if="featuredProducts[0]"
@click="openQuickView(featuredProducts[0])" @click="openQuickView(featuredProducts[0])"
class="absolute top-10 left-4 md:-left-6 z-20 flex items-center space-x-3 bg-white/90 backdrop-blur-md dark:bg-slate-900/90 p-3 rounded-2xl shadow-lg border border-cyan-100/40 dark:border-cyan-950/85 cursor-pointer transform hover:-translate-y-1 transition-all animate-float-tag-1" class="absolute top-10 left-4 md:-left-6 z-20 flex items-center space-x-3 bg-white/90 backdrop-blur-md dark:bg-slate-900/90 p-3 rounded-2xl shadow-lg border border-cyan-100/40 dark:border-cyan-950/85 cursor-pointer transform hover:-translate-y-1 transition-all animate-float-tag-1"
> >
<img <img
:src="getProductImage(featuredProducts[0])" :src="getProductImage(featuredProducts[0])"
alt="Preview Produk" alt="Preview Produk"
class="w-10 h-10 object-cover rounded-lg" class="w-10 h-10 object-cover rounded-lg"
/> />
<div class="text-left"> <div class="text-left">
@ -554,14 +554,14 @@ clearInterval(countdownInterval);
</div> </div>
<!-- FLOATING HERO TAG 2: Rating --> <!-- FLOATING HERO TAG 2: Rating -->
<div <div
v-if="featuredProducts[1]" v-if="featuredProducts[1]"
@click="openQuickView(featuredProducts[1])" @click="openQuickView(featuredProducts[1])"
class="absolute top-28 right-4 md:-right-6 z-20 flex items-center space-x-3 bg-white/90 backdrop-blur-md dark:bg-slate-900/90 p-3 rounded-2xl shadow-lg border border-cyan-100/40 dark:border-cyan-950/85 cursor-pointer transform hover:-translate-y-1 transition-all animate-float-tag-2" class="absolute top-28 right-4 md:-right-6 z-20 flex items-center space-x-3 bg-white/90 backdrop-blur-md dark:bg-slate-900/90 p-3 rounded-2xl shadow-lg border border-cyan-100/40 dark:border-cyan-950/85 cursor-pointer transform hover:-translate-y-1 transition-all animate-float-tag-2"
> >
<img <img
:src="getProductImage(featuredProducts[1], 1)" :src="getProductImage(featuredProducts[1], 1)"
alt="Preview Produk" alt="Preview Produk"
class="w-10 h-10 object-cover rounded-lg" class="w-10 h-10 object-cover rounded-lg"
/> />
<div class="text-left"> <div class="text-left">
@ -593,7 +593,7 @@ clearInterval(countdownInterval);
<!-- Catalog Section with Price Filters & Categories --> <!-- Catalog Section with Price Filters & Categories -->
<section id="catalog" class="max-w-7xl mx-auto px-6 py-20 border-t border-cyan-100/30 dark:border-cyan-950/20"> <section id="catalog" class="max-w-7xl mx-auto px-6 py-20 border-t border-cyan-100/30 dark:border-cyan-950/20">
<div class="space-y-6 mb-12"> <div class="space-y-6 mb-12">
<div class="text-center space-y-2"> <div class="text-center space-y-2">
<h2 class="text-xs uppercase tracking-[0.3em] font-bold text-cyan-600 dark:text-cyan-400">Katalog Eksklusif</h2> <h2 class="text-xs uppercase tracking-[0.3em] font-bold text-cyan-600 dark:text-cyan-400">Katalog Eksklusif</h2>
@ -605,13 +605,13 @@ clearInterval(countdownInterval);
<!-- Interactive Filters & Search Controls bar --> <!-- Interactive Filters & Search Controls bar -->
<div class="bg-white/80 dark:bg-[#111A2E]/80 backdrop-blur-md p-6 rounded-3xl shadow-sm border border-cyan-100/30 dark:border-cyan-950/30 flex flex-col md:flex-row items-center gap-4 justify-between"> <div class="bg-white/80 dark:bg-[#111A2E]/80 backdrop-blur-md p-6 rounded-3xl shadow-sm border border-cyan-100/30 dark:border-cyan-950/30 flex flex-col md:flex-row items-center gap-4 justify-between">
<!-- Search Input --> <!-- Search Input -->
<div class="relative w-full md:w-80"> <div class="relative w-full md:w-80">
<input <input
v-model="searchInput" v-model="searchInput"
type="text" type="text"
placeholder="Cari nama pakaian..." placeholder="Cari nama pakaian..."
class="w-full bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-cyan-950 text-xs px-4 py-3 pr-10 rounded-xl outline-none focus:border-cyan-500 dark:focus:border-cyan-400 text-slate-700 dark:text-white transition-all" class="w-full bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-cyan-950 text-xs px-4 py-3 pr-10 rounded-xl outline-none focus:border-cyan-500 dark:focus:border-cyan-400 text-slate-700 dark:text-white transition-all"
/> />
<Search class="absolute right-3 top-3.5 w-4 h-4 text-slate-400" /> <Search class="absolute right-3 top-3.5 w-4 h-4 text-slate-400" />
@ -619,25 +619,25 @@ clearInterval(countdownInterval);
<!-- Category Chips --> <!-- Category Chips -->
<div class="flex flex-wrap gap-2 w-full md:w-auto justify-center"> <div class="flex flex-wrap gap-2 w-full md:w-auto justify-center">
<button <button
@click="selectedCategory = null" @click="selectedCategory = null"
:class="[ :class="[
'px-4 py-2 text-[11px] font-semibold uppercase tracking-wider rounded-xl border transition-all cursor-pointer', 'px-4 py-2 text-[11px] font-semibold uppercase tracking-wider rounded-xl border transition-all cursor-pointer',
!selectedCategory !selectedCategory
? 'bg-cyan-600 text-white border-transparent shadow-md shadow-cyan-200/50 dark:bg-cyan-500 dark:text-slate-950 dark:shadow-none' ? 'bg-cyan-600 text-white border-transparent shadow-md shadow-cyan-200/50 dark:bg-cyan-500 dark:text-slate-950 dark:shadow-none'
: 'bg-transparent text-slate-600 border-slate-200 hover:border-cyan-500 dark:text-slate-300 dark:border-cyan-900/60 dark:hover:border-cyan-400' : 'bg-transparent text-slate-600 border-slate-200 hover:border-cyan-500 dark:text-slate-300 dark:border-cyan-900/60 dark:hover:border-cyan-400'
]" ]"
> >
Semua Semua
</button> </button>
<button <button
v-for="category in categories" v-for="category in categories"
:key="category.id" :key="category.id"
@click="toggleCategory(category.slug)" @click="toggleCategory(category.slug)"
:class="[ :class="[
'px-4 py-2 text-[11px] font-semibold uppercase tracking-wider rounded-xl border transition-all cursor-pointer', 'px-4 py-2 text-[11px] font-semibold uppercase tracking-wider rounded-xl border transition-all cursor-pointer',
selectedCategory === category.slug selectedCategory === category.slug
? 'bg-cyan-600 text-white border-transparent shadow-md shadow-cyan-200/50 dark:bg-cyan-500 dark:text-slate-950 dark:shadow-none' ? 'bg-cyan-600 text-white border-transparent shadow-md shadow-cyan-200/50 dark:bg-cyan-500 dark:text-slate-950 dark:shadow-none'
: 'bg-transparent text-slate-600 border-slate-200 hover:border-cyan-500 dark:text-slate-300 dark:border-cyan-900/60 dark:hover:border-cyan-400' : 'bg-transparent text-slate-600 border-slate-200 hover:border-cyan-500 dark:text-slate-300 dark:border-cyan-900/60 dark:hover:border-cyan-400'
]" ]"
> >
@ -646,7 +646,7 @@ clearInterval(countdownInterval);
</div> </div>
<!-- Filter panel toggler --> <!-- Filter panel toggler -->
<button <button
@click="showFilters = !showFilters" @click="showFilters = !showFilters"
class="flex items-center space-x-2 px-4 py-2.5 rounded-xl border border-slate-200 dark:border-cyan-900 text-xs font-semibold text-slate-600 dark:text-slate-300 hover:border-cyan-500 dark:hover:border-cyan-400 cursor-pointer transition-colors" class="flex items-center space-x-2 px-4 py-2.5 rounded-xl border border-slate-200 dark:border-cyan-900 text-xs font-semibold text-slate-600 dark:text-slate-300 hover:border-cyan-500 dark:hover:border-cyan-400 cursor-pointer transition-colors"
> >
@ -657,23 +657,23 @@ clearInterval(countdownInterval);
</div> </div>
<!-- Expandable Filter Panel --> <!-- Expandable Filter Panel -->
<div <div
v-if="showFilters" v-if="showFilters"
class="bg-white/50 dark:bg-slate-900/40 p-6 rounded-2xl border border-cyan-100/20 dark:border-cyan-950/20 grid grid-cols-1 md:grid-cols-3 gap-6 animate-fade-in-up" class="bg-white/50 dark:bg-slate-900/40 p-6 rounded-2xl border border-cyan-100/20 dark:border-cyan-950/20 grid grid-cols-1 md:grid-cols-3 gap-6 animate-fade-in-up"
> >
<!-- Price Range --> <!-- Price Range -->
<div class="space-y-2"> <div class="space-y-2">
<label class="text-[11px] uppercase tracking-wider font-bold text-slate-500 dark:text-slate-400">Rentang Harga</label> <label class="text-[11px] uppercase tracking-wider font-bold text-slate-500 dark:text-slate-400">Rentang Harga</label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<RupiahInput <RupiahInput
v-model="minPrice" v-model="minPrice"
placeholder="Min" placeholder="Min"
class="w-full bg-white dark:bg-slate-950 text-slate-700 dark:text-white" class="w-full bg-white dark:bg-slate-950 text-slate-700 dark:text-white"
/> />
<span class="text-xs text-slate-400">s/d</span> <span class="text-xs text-slate-400">s/d</span>
<RupiahInput <RupiahInput
v-model="maxPrice" v-model="maxPrice"
placeholder="Max" placeholder="Max"
class="w-full bg-white dark:bg-slate-950 text-slate-700 dark:text-white" class="w-full bg-white dark:bg-slate-950 text-slate-700 dark:text-white"
/> />
</div> </div>
@ -697,7 +697,7 @@ clearInterval(countdownInterval);
<!-- Reset Trigger --> <!-- Reset Trigger -->
<div class="flex items-end"> <div class="flex items-end">
<button <button
@click="resetPriceFilters" @click="resetPriceFilters"
class="w-full bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 text-slate-700 dark:text-slate-300 py-2.5 rounded-lg text-xs font-semibold uppercase tracking-wider transition-colors cursor-pointer" class="w-full bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 text-slate-700 dark:text-slate-300 py-2.5 rounded-lg text-xs font-semibold uppercase tracking-wider transition-colors cursor-pointer"
> >
@ -709,16 +709,16 @@ clearInterval(countdownInterval);
<!-- Product Grid --> <!-- Product Grid -->
<div v-if="filteredProducts.length > 0" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-x-8 gap-y-12"> <div v-if="filteredProducts.length > 0" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-x-8 gap-y-12">
<div <div
v-for="(product, idx) in filteredProducts" v-for="(product, idx) in filteredProducts"
:key="product.id" :key="product.id"
class="group relative flex flex-col space-y-4" class="group relative flex flex-col space-y-4"
> >
<!-- Product Image Container --> <!-- Product Image Container -->
<div class="relative aspect-[3/4] w-full overflow-hidden rounded-2xl bg-cyan-50/50 dark:bg-neutral-900 shadow-md transition-shadow hover:shadow-cyan-100/30"> <div class="relative aspect-[3/4] w-full overflow-hidden rounded-2xl bg-cyan-50/50 dark:bg-neutral-900 shadow-md transition-shadow hover:shadow-cyan-100/30">
<img <img
:src="getProductImage(product, idx)" :src="getProductImage(product, idx)"
:alt="product.name" :alt="product.name"
class="h-full w-full object-cover object-center transition-transform duration-700 ease-out group-hover:scale-105" class="h-full w-full object-cover object-center transition-transform duration-700 ease-out group-hover:scale-105"
/> />
<!-- Subtle Gradient Overlay --> <!-- Subtle Gradient Overlay -->
@ -726,20 +726,20 @@ clearInterval(countdownInterval);
<!-- Absolute Status Badges --> <!-- Absolute Status Badges -->
<div class="absolute top-4 left-4 flex flex-col space-y-2"> <div class="absolute top-4 left-4 flex flex-col space-y-2">
<span <span
v-if="getTotalStock(product) === 0" v-if="getTotalStock(product) === 0"
class="bg-red-500 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md" class="bg-red-500 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md"
> >
Habis Habis
</span> </span>
<span <span
v-else-if="getTotalStock(product) < 15" v-else-if="getTotalStock(product) < 15"
class="bg-amber-500 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md animate-pulse" class="bg-amber-500 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md animate-pulse"
> >
Menipis Menipis
</span> </span>
<span <span
v-else v-else
class="bg-cyan-600 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md dark:bg-cyan-500 dark:text-slate-950" class="bg-cyan-600 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md dark:bg-cyan-500 dark:text-slate-950"
> >
Terbaru Terbaru
@ -748,7 +748,7 @@ clearInterval(countdownInterval);
<!-- Hover Action Quick View Button --> <!-- Hover Action Quick View Button -->
<div class="absolute bottom-4 inset-x-4 flex justify-center opacity-0 transform translate-y-2 group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-300"> <div class="absolute bottom-4 inset-x-4 flex justify-center opacity-0 transform translate-y-2 group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-300">
<button <button
@click="openQuickView(product)" @click="openQuickView(product)"
class="w-full flex items-center justify-center bg-white/95 backdrop-blur-sm text-slate-800 hover:bg-cyan-600 hover:text-white px-4 py-3 rounded-xl shadow-md text-xs font-bold uppercase tracking-widest transition-all duration-200 cursor-pointer" class="w-full flex items-center justify-center bg-white/95 backdrop-blur-sm text-slate-800 hover:bg-cyan-600 hover:text-white px-4 py-3 rounded-xl shadow-md text-xs font-bold uppercase tracking-widest transition-all duration-200 cursor-pointer"
> >
@ -764,7 +764,7 @@ clearInterval(countdownInterval);
<span class="text-[10px] font-bold uppercase tracking-widest text-cyan-600 dark:text-cyan-400"> <span class="text-[10px] font-bold uppercase tracking-widest text-cyan-600 dark:text-cyan-400">
{{ product.categories.map(c => c.name).join(', ') }} {{ product.categories.map(c => c.name).join(', ') }}
</span> </span>
<!-- Title --> <!-- Title -->
<h4 class="text-base font-semibold text-slate-700 dark:text-neutral-200 line-clamp-1 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors"> <h4 class="text-base font-semibold text-slate-700 dark:text-neutral-200 line-clamp-1 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">
{{ product.name }} {{ product.name }}
@ -790,8 +790,8 @@ clearInterval(countdownInterval);
<p class="text-sm text-slate-400 dark:text-neutral-500"> <p class="text-sm text-slate-400 dark:text-neutral-500">
Mohon maaf, kami tidak menemukan pakaian yang cocok dengan kata kunci pencarian Anda. Mohon maaf, kami tidak menemukan pakaian yang cocok dengan kata kunci pencarian Anda.
</p> </p>
<button <button
@click="searchInput = ''; selectedCategory = null; resetPriceFilters()" @click="searchInput = ''; selectedCategory = null; resetPriceFilters()"
class="mt-2 text-xs font-bold uppercase tracking-widest text-cyan-600 dark:text-cyan-400 underline underline-offset-4 cursor-pointer" class="mt-2 text-xs font-bold uppercase tracking-widest text-cyan-600 dark:text-cyan-400 underline underline-offset-4 cursor-pointer"
> >
Reset Pencarian Reset Pencarian
@ -830,7 +830,7 @@ clearInterval(countdownInterval);
</div> </div>
</div> </div>
<a <a
href="#catalog" href="#catalog"
class="inline-flex items-center justify-center bg-cyan-600 text-white px-8 py-3.5 text-xs font-semibold uppercase tracking-widest rounded-xl shadow-lg shadow-cyan-200/50 hover:bg-cyan-500 transition-all dark:bg-cyan-500 dark:text-slate-950 cursor-pointer" class="inline-flex items-center justify-center bg-cyan-600 text-white px-8 py-3.5 text-xs font-semibold uppercase tracking-widest rounded-xl shadow-lg shadow-cyan-200/50 hover:bg-cyan-500 transition-all dark:bg-cyan-500 dark:text-slate-950 cursor-pointer"
> >
@ -842,32 +842,32 @@ clearInterval(countdownInterval);
<div class="lg:col-span-7 grid grid-cols-2 gap-4"> <div class="lg:col-span-7 grid grid-cols-2 gap-4">
<div class="space-y-4"> <div class="space-y-4">
<div class="aspect-[4/5] rounded-3xl overflow-hidden shadow-md border-2 border-white dark:border-cyan-950"> <div class="aspect-[4/5] rounded-3xl overflow-hidden shadow-md border-2 border-white dark:border-cyan-950">
<img <img
src="https://images.unsplash.com/photo-1595777457583-95e059d581b8?w=800&auto=format&fit=crop&q=80" src="https://images.unsplash.com/photo-1595777457583-95e059d581b8?w=800&auto=format&fit=crop&q=80"
alt="Daster Batik parang" alt="Daster Batik parang"
class="w-full h-full object-cover" class="w-full h-full object-cover"
/> />
</div> </div>
<div class="aspect-square rounded-3xl overflow-hidden shadow-md border-2 border-white dark:border-cyan-950"> <div class="aspect-square rounded-3xl overflow-hidden shadow-md border-2 border-white dark:border-cyan-950">
<img <img
src="https://images.unsplash.com/photo-1572804013309-59a88b7e92f1?w=800&auto=format&fit=crop&q=80" src="https://images.unsplash.com/photo-1572804013309-59a88b7e92f1?w=800&auto=format&fit=crop&q=80"
alt="Daster busui styling" alt="Daster busui styling"
class="w-full h-full object-cover" class="w-full h-full object-cover"
/> />
</div> </div>
</div> </div>
<div class="pt-8 space-y-4"> <div class="pt-8 space-y-4">
<div class="aspect-square rounded-3xl overflow-hidden shadow-md border-2 border-white dark:border-cyan-950"> <div class="aspect-square rounded-3xl overflow-hidden shadow-md border-2 border-white dark:border-cyan-950">
<img <img
src="https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=800&auto=format&fit=crop&q=80" src="https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=800&auto=format&fit=crop&q=80"
alt="Blouse Batik Modern" alt="Blouse Batik Modern"
class="w-full h-full object-cover" class="w-full h-full object-cover"
/> />
</div> </div>
<div class="aspect-[4/5] rounded-3xl overflow-hidden shadow-md border-2 border-white dark:border-cyan-950"> <div class="aspect-[4/5] rounded-3xl overflow-hidden shadow-md border-2 border-white dark:border-cyan-950">
<img <img
src="https://images.unsplash.com/photo-1539109136881-3be0616acf4b?w=800&auto=format&fit=crop&q=80" src="https://images.unsplash.com/photo-1539109136881-3be0616acf4b?w=800&auto=format&fit=crop&q=80"
alt="Setelan Celana Batik" alt="Setelan Celana Batik"
class="w-full h-full object-cover" class="w-full h-full object-cover"
/> />
</div> </div>
@ -939,9 +939,9 @@ clearInterval(countdownInterval);
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 items-center"> <div class="grid grid-cols-1 lg:grid-cols-12 gap-12 items-center">
<!-- Column Left --> <!-- Column Left -->
<div class="lg:col-span-6 relative aspect-square max-w-md mx-auto lg:max-w-none rounded-3xl overflow-hidden shadow-lg border-2 border-white dark:border-cyan-950"> <div class="lg:col-span-6 relative aspect-square max-w-md mx-auto lg:max-w-none rounded-3xl overflow-hidden shadow-lg border-2 border-white dark:border-cyan-950">
<img <img
src="https://images.unsplash.com/photo-1595777457583-95e059d581b8?w=800&auto=format&fit=crop&q=80" src="https://images.unsplash.com/photo-1595777457583-95e059d581b8?w=800&auto=format&fit=crop&q=80"
alt="Proses Pembuatan Batik" alt="Proses Pembuatan Batik"
class="w-full h-full object-cover" class="w-full h-full object-cover"
/> />
</div> </div>
@ -1007,10 +1007,10 @@ clearInterval(countdownInterval);
<div class="space-y-3"> <div class="space-y-3">
<input type="text" placeholder="Nama Anda" class="w-full text-xs px-4 py-3 bg-white dark:bg-slate-900 border border-slate-200 dark:border-cyan-950 rounded-lg outline-none focus:border-cyan-500 text-slate-700 dark:text-white" /> <input type="text" placeholder="Nama Anda" class="w-full text-xs px-4 py-3 bg-white dark:bg-slate-900 border border-slate-200 dark:border-cyan-950 rounded-lg outline-none focus:border-cyan-500 text-slate-700 dark:text-white" />
<textarea placeholder="Tulis pesan Anda di sini..." rows="4" class="w-full text-xs px-4 py-3 bg-white dark:bg-slate-900 border border-slate-200 dark:border-cyan-950 rounded-lg outline-none focus:border-cyan-500 text-slate-700 dark:text-white scrollbar-thin"></textarea> <textarea placeholder="Tulis pesan Anda di sini..." rows="4" class="w-full text-xs px-4 py-3 bg-white dark:bg-slate-900 border border-slate-200 dark:border-cyan-950 rounded-lg outline-none focus:border-cyan-500 text-slate-700 dark:text-white scrollbar-thin"></textarea>
<!-- WhatsApp Direct Send Button --> <!-- WhatsApp Direct Send Button -->
<a <a
:href="`https://wa.me/${contactPhone.replace(/[^0-9]/g, '') || '6281234567890'}`" :href="`https://wa.me/${contactPhone.replace(/[^0-9]/g, '') || '6281234567890'}`"
target="_blank" target="_blank"
class="w-full inline-flex items-center justify-center bg-emerald-600 hover:bg-emerald-500 text-white py-3 rounded-lg text-xs font-bold uppercase tracking-wider transition-colors" class="w-full inline-flex items-center justify-center bg-emerald-600 hover:bg-emerald-500 text-white py-3 rounded-lg text-xs font-bold uppercase tracking-wider transition-colors"
> >
@ -1028,13 +1028,13 @@ clearInterval(countdownInterval);
<!-- Brand Profile --> <!-- Brand Profile -->
<div class="space-y-4"> <div class="space-y-4">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<img <img
v-if="logoUrl" v-if="logoUrl"
:src="logoUrl" :src="logoUrl"
:alt="appName" :alt="appName"
class="h-8 w-auto object-contain bg-white p-0.5 rounded shadow-sm" class="h-8 w-auto object-contain bg-white p-0.5 rounded shadow-sm"
/> />
<span class="font-serif text-xl font-bold tracking-widest text-black dark:text-white text-cyan-600 dark:text-cyan-400"> <span v-else class="font-serif text-xl font-bold tracking-widest text-black dark:text-white text-cyan-600 dark:text-cyan-400">
{{ appName }} {{ appName }}
</span> </span>
</div> </div>
@ -1087,20 +1087,20 @@ clearInterval(countdownInterval);
</footer> </footer>
<!-- CENTERED DETAIL MODAL (MODA DETAIL) --> <!-- CENTERED DETAIL MODAL (MODA DETAIL) -->
<div <div
v-if="activeProduct" v-if="activeProduct"
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm transition-opacity duration-300" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm transition-opacity duration-300"
@click.self="closeQuickView" @click.self="closeQuickView"
> >
<div <div
class="w-full max-w-3xl bg-[#FDFDFC] dark:bg-[#0E1726] rounded-3xl p-6 md:p-8 shadow-2xl flex flex-col md:flex-row gap-8 max-h-[90vh] overflow-y-auto border border-cyan-100/30 dark:border-cyan-950/30 scrollbar-thin" class="w-full max-w-3xl bg-[#FDFDFC] dark:bg-[#0E1726] rounded-3xl p-6 md:p-8 shadow-2xl flex flex-col md:flex-row gap-8 max-h-[90vh] overflow-y-auto border border-cyan-100/30 dark:border-cyan-950/30 scrollbar-thin"
> >
<!-- Image Side --> <!-- Image Side -->
<div class="w-full md:w-1/2 flex flex-col space-y-3"> <div class="w-full md:w-1/2 flex flex-col space-y-3">
<div class="aspect-[3/4] rounded-2xl overflow-hidden bg-slate-50 dark:bg-neutral-900 border border-slate-200/50 dark:border-cyan-950/50 shadow-sm relative"> <div class="aspect-[3/4] rounded-2xl overflow-hidden bg-slate-50 dark:bg-neutral-900 border border-slate-200/50 dark:border-cyan-950/50 shadow-sm relative">
<img <img
:src="getProductImage(activeProduct)" :src="getProductImage(activeProduct)"
:alt="activeProduct.name" :alt="activeProduct.name"
class="w-full h-full object-cover" class="w-full h-full object-cover"
/> />
</div> </div>
@ -1116,8 +1116,8 @@ clearInterval(countdownInterval);
</span> </span>
<h3 class="text-2xl font-serif mt-0.5 text-slate-800 dark:text-white">{{ activeProduct.name }}</h3> <h3 class="text-2xl font-serif mt-0.5 text-slate-800 dark:text-white">{{ activeProduct.name }}</h3>
</div> </div>
<button <button
@click="closeQuickView" @click="closeQuickView"
class="p-1.5 rounded-full hover:bg-slate-100 dark:hover:bg-cyan-950/30 transition-colors cursor-pointer" class="p-1.5 rounded-full hover:bg-slate-100 dark:hover:bg-cyan-950/30 transition-colors cursor-pointer"
> >
<X class="w-5 h-5 text-slate-500" /> <X class="w-5 h-5 text-slate-500" />
@ -1136,14 +1136,14 @@ clearInterval(countdownInterval);
<div v-if="activeProduct.variants.length > 0" class="space-y-2"> <div v-if="activeProduct.variants.length > 0" class="space-y-2">
<h4 class="text-[10px] uppercase font-bold tracking-widest text-slate-400">Pilih Ukuran / Varian</h4> <h4 class="text-[10px] uppercase font-bold tracking-widest text-slate-400">Pilih Ukuran / Varian</h4>
<div class="flex flex-wrap gap-1.5"> <div class="flex flex-wrap gap-1.5">
<button <button
v-for="variant in activeProduct.variants" v-for="variant in activeProduct.variants"
:key="variant.id" :key="variant.id"
@click="activeVariant = variant" @click="activeVariant = variant"
:class="[ :class="[
'px-3 py-1.5 rounded-xl text-xs font-semibold uppercase tracking-wider border cursor-pointer transition-all', 'px-3 py-1.5 rounded-xl text-xs font-semibold uppercase tracking-wider border cursor-pointer transition-all',
activeVariant?.id === variant.id activeVariant?.id === variant.id
? 'bg-cyan-600 text-white border-transparent dark:bg-cyan-500 dark:text-slate-950 shadow-md shadow-cyan-200/30' ? 'bg-cyan-600 text-white border-transparent dark:bg-cyan-500 dark:text-slate-950 shadow-md shadow-cyan-200/30'
: 'bg-transparent text-slate-600 border-slate-200 dark:text-slate-300 dark:border-cyan-900/60' : 'bg-transparent text-slate-600 border-slate-200 dark:text-slate-300 dark:border-cyan-900/60'
]" ]"
> >
@ -1157,14 +1157,14 @@ clearInterval(countdownInterval);
<div v-if="activeVariant && activeVariant.prices.length > 0" class="space-y-2"> <div v-if="activeVariant && activeVariant.prices.length > 0" class="space-y-2">
<h4 class="text-[10px] uppercase font-bold tracking-widest text-slate-400">Saluran Harga (Grosir/Eceran)</h4> <h4 class="text-[10px] uppercase font-bold tracking-widest text-slate-400">Saluran Harga (Grosir/Eceran)</h4>
<div class="grid grid-cols-2 gap-1.5 max-h-36 overflow-y-auto scrollbar-thin pr-1"> <div class="grid grid-cols-2 gap-1.5 max-h-36 overflow-y-auto scrollbar-thin pr-1">
<button <button
v-for="price in activeVariant.prices" v-for="price in activeVariant.prices"
:key="price.id" :key="price.id"
@click="activePriceType = price.type" @click="activePriceType = price.type"
:class="[ :class="[
'px-3 py-2 text-left rounded-xl border transition-all cursor-pointer', 'px-3 py-2 text-left rounded-xl border transition-all cursor-pointer',
activePriceType === price.type activePriceType === price.type
? 'border-cyan-500 bg-cyan-500/5 dark:border-cyan-400 dark:bg-cyan-400/5' ? 'border-cyan-500 bg-cyan-500/5 dark:border-cyan-400 dark:bg-cyan-400/5'
: 'border-slate-200 dark:border-cyan-900/60 hover:border-cyan-500 dark:hover:border-cyan-400' : 'border-slate-200 dark:border-cyan-900/60 hover:border-cyan-500 dark:hover:border-cyan-400'
]" ]"
> >
@ -1196,7 +1196,7 @@ clearInterval(countdownInterval);
</div> </div>
</div> </div>
<button <button
v-if="activeVariant && selectedPrice" v-if="activeVariant && selectedPrice"
@click="addToCart(activeProduct, activeVariant, selectedPrice)" @click="addToCart(activeProduct, activeVariant, selectedPrice)"
:disabled="activeVariant.stock <= 0" :disabled="activeVariant.stock <= 0"
@ -1215,12 +1215,12 @@ clearInterval(countdownInterval);
</div> </div>
<!-- FLOATING CART SLIDE-OVER SHEET --> <!-- FLOATING CART SLIDE-OVER SHEET -->
<div <div
v-if="isCartOpen" v-if="isCartOpen"
class="fixed inset-0 z-50 flex items-center justify-end bg-black/60 backdrop-blur-sm transition-opacity duration-300" class="fixed inset-0 z-50 flex items-center justify-end bg-black/60 backdrop-blur-sm transition-opacity duration-300"
@click.self="isCartOpen = false" @click.self="isCartOpen = false"
> >
<div <div
class="w-full max-w-md h-full bg-[#FDFDFC] dark:bg-[#0E1726] p-6 shadow-2xl flex flex-col justify-between overflow-y-auto transform translate-x-0 transition-transform duration-300 border-l border-cyan-100/30 dark:border-cyan-950/30" class="w-full max-w-md h-full bg-[#FDFDFC] dark:bg-[#0E1726] p-6 shadow-2xl flex flex-col justify-between overflow-y-auto transform translate-x-0 transition-transform duration-300 border-l border-cyan-100/30 dark:border-cyan-950/30"
> >
<div class="space-y-6"> <div class="space-y-6">
@ -1230,8 +1230,8 @@ clearInterval(countdownInterval);
<ShoppingBag class="w-5 h-5 text-cyan-600 dark:text-cyan-400" /> <ShoppingBag class="w-5 h-5 text-cyan-600 dark:text-cyan-400" />
<h3 class="text-lg font-serif text-slate-800 dark:text-white">Keranjang Belanja ({{ cartCount }})</h3> <h3 class="text-lg font-serif text-slate-800 dark:text-white">Keranjang Belanja ({{ cartCount }})</h3>
</div> </div>
<button <button
@click="isCartOpen = false" @click="isCartOpen = false"
class="p-2 rounded-full hover:bg-slate-100 dark:hover:bg-cyan-950/30 transition-colors cursor-pointer" class="p-2 rounded-full hover:bg-slate-100 dark:hover:bg-cyan-950/30 transition-colors cursor-pointer"
> >
<X class="w-5 h-5 text-slate-500" /> <X class="w-5 h-5 text-slate-500" />
@ -1240,25 +1240,25 @@ clearInterval(countdownInterval);
<!-- Cart List --> <!-- Cart List -->
<div v-if="cart.length > 0" class="space-y-4 divide-y divide-slate-100 dark:divide-cyan-950/45 max-h-[calc(100vh-280px)] overflow-y-auto pr-1 scrollbar-thin"> <div v-if="cart.length > 0" class="space-y-4 divide-y divide-slate-100 dark:divide-cyan-950/45 max-h-[calc(100vh-280px)] overflow-y-auto pr-1 scrollbar-thin">
<div <div
v-for="item in cart" v-for="item in cart"
:key="`${item.variant.id}-${item.price.type}`" :key="`${item.variant.id}-${item.price.type}`"
class="flex space-x-4 pt-4 first:pt-0" class="flex space-x-4 pt-4 first:pt-0"
> >
<img <img
:src="getProductImage(item.product)" :src="getProductImage(item.product)"
:alt="item.product.name" :alt="item.product.name"
class="w-16 h-20 object-cover rounded-xl bg-slate-50 dark:bg-slate-900 border border-slate-200/50 dark:border-cyan-950/50" class="w-16 h-20 object-cover rounded-xl bg-slate-50 dark:bg-slate-900 border border-slate-200/50 dark:border-cyan-950/50"
/> />
<div class="flex-1 flex flex-col justify-between"> <div class="flex-1 flex flex-col justify-between">
<div class="space-y-1"> <div class="space-y-1">
<div class="flex justify-between items-start"> <div class="flex justify-between items-start">
<h4 class="text-xs font-semibold text-slate-700 dark:text-neutral-200 line-clamp-1"> <h4 class="text-xs font-semibold text-slate-700 dark:text-neutral-200 line-clamp-1">
{{ item.product.name }} {{ item.product.name }}
</h4> </h4>
<button <button
@click="removeFromCart(item.variant.id, item.price.type)" @click="removeFromCart(item.variant.id, item.price.type)"
class="text-slate-400 hover:text-red-500 p-1 cursor-pointer transition-colors" class="text-slate-400 hover:text-red-500 p-1 cursor-pointer transition-colors"
> >
<X class="w-3.5 h-3.5" /> <X class="w-3.5 h-3.5" />
@ -1268,18 +1268,18 @@ clearInterval(countdownInterval);
Ukuran: <strong class="text-slate-700 dark:text-neutral-300 font-semibold">{{ item.variant.name }}</strong> Tipe: <strong class="text-slate-700 dark:text-neutral-300 font-semibold">{{ item.price.type_label }}</strong> Ukuran: <strong class="text-slate-700 dark:text-neutral-300 font-semibold">{{ item.variant.name }}</strong> Tipe: <strong class="text-slate-700 dark:text-neutral-300 font-semibold">{{ item.price.type_label }}</strong>
</p> </p>
</div> </div>
<div class="flex justify-between items-center mt-2"> <div class="flex justify-between items-center mt-2">
<!-- Quantity Stepper --> <!-- Quantity Stepper -->
<div class="flex items-center space-x-2 border border-slate-200 dark:border-cyan-900 rounded-lg px-2 py-0.5"> <div class="flex items-center space-x-2 border border-slate-200 dark:border-cyan-900 rounded-lg px-2 py-0.5">
<button <button
@click="updateCartQuantity(item.variant.id, item.price.type, -1)" @click="updateCartQuantity(item.variant.id, item.price.type, -1)"
class="p-1 text-slate-500 hover:text-black dark:hover:text-white cursor-pointer" class="p-1 text-slate-500 hover:text-black dark:hover:text-white cursor-pointer"
> >
<Minus class="w-3 h-3" /> <Minus class="w-3 h-3" />
</button> </button>
<span class="text-xs font-semibold px-1 text-slate-800 dark:text-slate-100">{{ item.quantity }}</span> <span class="text-xs font-semibold px-1 text-slate-800 dark:text-slate-100">{{ item.quantity }}</span>
<button <button
@click="updateCartQuantity(item.variant.id, item.price.type, 1)" @click="updateCartQuantity(item.variant.id, item.price.type, 1)"
class="p-1 text-slate-500 hover:text-black dark:hover:text-white cursor-pointer" class="p-1 text-slate-500 hover:text-black dark:hover:text-white cursor-pointer"
> >
@ -1301,8 +1301,8 @@ clearInterval(countdownInterval);
<p class="text-xs text-slate-400 dark:text-neutral-500 max-w-xs mx-auto font-light leading-relaxed"> <p class="text-xs text-slate-400 dark:text-neutral-500 max-w-xs mx-auto font-light leading-relaxed">
Jelajahi produk batik dan pakaian pilihan kami untuk menambahkan item ke dalam keranjang. Jelajahi produk batik dan pakaian pilihan kami untuk menambahkan item ke dalam keranjang.
</p> </p>
<button <button
@click="isCartOpen = false" @click="isCartOpen = false"
class="bg-cyan-600 text-white dark:bg-cyan-500 dark:text-slate-950 px-6 py-2.5 text-xs font-semibold uppercase tracking-widest rounded-xl hover:opacity-90 transition-all cursor-pointer shadow-md shadow-cyan-200/50" class="bg-cyan-600 text-white dark:bg-cyan-500 dark:text-slate-950 px-6 py-2.5 text-xs font-semibold uppercase tracking-widest rounded-xl hover:opacity-90 transition-all cursor-pointer shadow-md shadow-cyan-200/50"
> >
Lanjutkan Belanja Lanjutkan Belanja
@ -1327,7 +1327,7 @@ clearInterval(countdownInterval);
</div> </div>
</div> </div>
<button <button
@click="handleWhatsAppCheckout" @click="handleWhatsAppCheckout"
:disabled="isCheckingOut" :disabled="isCheckingOut"
class="w-full flex items-center justify-center bg-emerald-600 text-white hover:bg-emerald-500 py-3.5 text-xs font-bold uppercase tracking-widest rounded-xl transition-all cursor-pointer shadow-md shadow-emerald-100/50" class="w-full flex items-center justify-center bg-emerald-600 text-white hover:bg-emerald-500 py-3.5 text-xs font-bold uppercase tracking-widest rounded-xl transition-all cursor-pointer shadow-md shadow-emerald-100/50"