1302 lines
63 KiB
Vue
1302 lines
63 KiB
Vue
<script setup lang="ts">
|
|
import { Head } from '@inertiajs/vue3';
|
|
import {
|
|
Search,
|
|
ShoppingBag,
|
|
X,
|
|
Plus,
|
|
Minus,
|
|
ArrowRight,
|
|
Star,
|
|
Check,
|
|
Info,
|
|
ChevronRight,
|
|
Eye,
|
|
Phone,
|
|
MapPin,
|
|
Mail,
|
|
Filter
|
|
} from '@lucide/vue';
|
|
import { ref, computed } from 'vue';
|
|
import { CatalogSortOption } from '@/constants/catalog-sort-option';
|
|
import RupiahInput from '@/components/form/rupiah-input/RupiahInput.vue';
|
|
import Select from '@/components/ui/select/Select.vue';
|
|
import SelectContent from '@/components/ui/select/SelectContent.vue';
|
|
import SelectItem from '@/components/ui/select/SelectItem.vue';
|
|
import SelectTrigger from '@/components/ui/select/SelectTrigger.vue';
|
|
import SelectValue from '@/components/ui/select/SelectValue.vue';
|
|
|
|
interface Price {
|
|
id: number;
|
|
variant_id: number;
|
|
type: string;
|
|
price: number;
|
|
price_formatted: string;
|
|
type_label: string;
|
|
}
|
|
|
|
interface MediaItem {
|
|
id: number;
|
|
url: string;
|
|
thumb_url: string;
|
|
}
|
|
|
|
interface Variant {
|
|
id: number;
|
|
product_id: number;
|
|
name: string;
|
|
stock: number;
|
|
prices: Price[];
|
|
images: MediaItem[];
|
|
}
|
|
|
|
interface Category {
|
|
id: number;
|
|
name: string;
|
|
slug: string;
|
|
}
|
|
|
|
interface Product {
|
|
id: number;
|
|
name: string;
|
|
slug: string;
|
|
description: string | null;
|
|
is_active: boolean;
|
|
categories: Category[];
|
|
variants: Variant[];
|
|
}
|
|
|
|
interface MediaItem {
|
|
id: number;
|
|
url: string;
|
|
thumb_url: string;
|
|
}
|
|
|
|
interface HomepageData {
|
|
hero_image_url: string | null;
|
|
about_image_url: string | null;
|
|
gallery_images: MediaItem[];
|
|
}
|
|
|
|
const props = defineProps<{
|
|
categories: Category[];
|
|
products: Product[];
|
|
appName: string;
|
|
aboutApp: string;
|
|
contactEmail: string;
|
|
contactPhone: string;
|
|
contactAddress: string;
|
|
logoUrl: string | null;
|
|
instagramUrl: string | null;
|
|
facebookUrl: string | null;
|
|
tiktokUrl: string | null;
|
|
homepage: HomepageData;
|
|
}>();
|
|
|
|
// Search & Catalog Filter State
|
|
const searchInput = ref('');
|
|
const selectedCategory = ref<string | null>(null);
|
|
const minPrice = ref<number | null>(null);
|
|
const maxPrice = ref<number | null>(null);
|
|
const sortBy = ref<string>(CatalogSortOption.DEFAULT);
|
|
const showFilters = ref(false);
|
|
|
|
// Hardcoded order steps
|
|
const orderSteps = [
|
|
{ title: 'Pilih Produk', description: 'Jelajahi katalog kami dan pilih produk yang Anda sukai.' },
|
|
{ title: 'Hubungi Kami', description: 'Hubungi kami melalui WhatsApp untuk konfirmasi pesanan.' },
|
|
{ title: 'Pembayaran', description: 'Lakukan pembayaran melalui transfer bank atau metode lainnya.' },
|
|
{ title: 'Pesanan Dikirim', description: 'Pesanan Anda akan segera dikirim ke alamat tujuan.' },
|
|
];
|
|
|
|
// Hardcoded about features
|
|
const aboutFeatures = [
|
|
'Kain Premium & Adem',
|
|
'Jahitan Rapi & Berkualitas',
|
|
'Motif Eksklusif & Unik',
|
|
'Pengiriman ke Seluruh Indonesia',
|
|
];
|
|
|
|
const toggleCategory = (slug: string) => {
|
|
selectedCategory.value = selectedCategory.value === slug ? null : slug;
|
|
};
|
|
|
|
// Helper to get ecer price of a product for filtering/sorting
|
|
const getPrimaryEcerPrice = (product: Product): number => {
|
|
const ecerPrices = product.variants.flatMap(v =>
|
|
v.prices.filter(p => p.type === 'ecer').map(p => p.price)
|
|
);
|
|
|
|
if (ecerPrices.length > 0) {
|
|
return Math.min(...ecerPrices);
|
|
}
|
|
|
|
const allPrices = product.variants.flatMap(v => v.prices.map(p => p.price));
|
|
|
|
return allPrices.length > 0 ? Math.min(...allPrices) : 0;
|
|
};
|
|
|
|
// Filtered and Sorted Products
|
|
const filteredProducts = computed(() => {
|
|
const result = props.products.filter(product => {
|
|
// Search filter
|
|
const matchesSearch = product.name.toLowerCase().includes(searchInput.value.toLowerCase()) ||
|
|
(product.description && product.description.toLowerCase().includes(searchInput.value.toLowerCase()));
|
|
|
|
// Category filter
|
|
const matchesCategory = !selectedCategory.value ||
|
|
product.categories.some(cat => cat.slug === selectedCategory.value);
|
|
|
|
// Price filter
|
|
const price = getPrimaryEcerPrice(product);
|
|
const matchesMinPrice = minPrice.value === null || price >= minPrice.value;
|
|
const matchesMaxPrice = maxPrice.value === null || price <= maxPrice.value;
|
|
|
|
return matchesSearch && matchesCategory && matchesMinPrice && matchesMaxPrice;
|
|
});
|
|
|
|
// Sorting
|
|
if (sortBy.value === CatalogSortOption.PRICE_ASC) {
|
|
result.sort((a, b) => getPrimaryEcerPrice(a) - getPrimaryEcerPrice(b));
|
|
} else if (sortBy.value === CatalogSortOption.PRICE_DESC) {
|
|
result.sort((a, b) => getPrimaryEcerPrice(b) - getPrimaryEcerPrice(a));
|
|
} else if (sortBy.value === CatalogSortOption.NAME_ASC) {
|
|
result.sort((a, b) => a.name.localeCompare(b.name));
|
|
}
|
|
|
|
return result;
|
|
});
|
|
|
|
// Image Resolver
|
|
const getProductImage = (product: Product, indexOffset = 0) => {
|
|
for (const variant of product.variants) {
|
|
if (variant.images && variant.images.length > 0) {
|
|
return variant.images[0].url;
|
|
}
|
|
}
|
|
|
|
// Fallbacks based on category slug
|
|
const firstCat = product.categories?.[0]?.slug || '';
|
|
const fallbacks: Record<string, string[]> = {
|
|
'daster': [
|
|
'https://images.unsplash.com/photo-1595777457583-95e059d581b8?w=800&auto=format&fit=crop&q=80',
|
|
'https://images.unsplash.com/photo-1572804013309-59a88b7e92f1?w=800&auto=format&fit=crop&q=80'
|
|
],
|
|
'setelan-celana': [
|
|
'https://images.unsplash.com/photo-1539109136881-3be0616acf4b?w=800&auto=format&fit=crop&q=80',
|
|
'https://images.unsplash.com/photo-1618244972963-dbee1a7edc95?w=800&auto=format&fit=crop&q=80'
|
|
],
|
|
'atasan': [
|
|
'https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?w=800&auto=format&fit=crop&q=80',
|
|
'https://images.unsplash.com/photo-1532244769068-b223ede82d7b?w=800&auto=format&fit=crop&q=80'
|
|
],
|
|
'bawahan': [
|
|
'https://images.unsplash.com/photo-1583496661160-fb4886b36ca7?w=800&auto=format&fit=crop&q=80',
|
|
'https://images.unsplash.com/photo-1551163943-3f6a855d1153?w=800&auto=format&fit=crop&q=80'
|
|
],
|
|
'busui': [
|
|
'https://images.unsplash.com/photo-1509631179647-0177331693ae?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] || [
|
|
'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'
|
|
];
|
|
|
|
return categoryUrls[indexOffset % categoryUrls.length];
|
|
};
|
|
|
|
// Price Formatting (Indonesian Rupiah)
|
|
const formatPrice = (price: number) => {
|
|
return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(price);
|
|
};
|
|
|
|
// Price Range Resolver
|
|
const getProductPriceRange = (product: Product) => {
|
|
const prices = product.variants.flatMap(v =>
|
|
v.prices.filter(p => p.type === 'ecer').map(p => p.price)
|
|
);
|
|
|
|
if (prices.length === 0) {
|
|
const fallbackPrices = product.variants.flatMap(v => v.prices.map(p => p.price));
|
|
|
|
if (fallbackPrices.length === 0) {
|
|
return 'Rp 0';
|
|
}
|
|
|
|
const min = Math.min(...fallbackPrices);
|
|
const max = Math.max(...fallbackPrices);
|
|
|
|
return min === max ? formatPrice(min) : `${formatPrice(min)} - ${formatPrice(max)}`;
|
|
}
|
|
|
|
const min = Math.min(...prices);
|
|
const max = Math.max(...prices);
|
|
|
|
return min === max ? formatPrice(min) : `${formatPrice(min)} - ${formatPrice(max)}`;
|
|
};
|
|
|
|
const getTotalStock = (product: Product) => {
|
|
return product.variants.reduce((acc, variant) => acc + variant.stock, 0);
|
|
};
|
|
|
|
const featuredProducts = computed(() => {
|
|
return props.products.slice(0, 3);
|
|
});
|
|
|
|
|
|
// Cart System
|
|
interface CartItem {
|
|
product: Product;
|
|
variant: Variant;
|
|
price: Price;
|
|
quantity: number;
|
|
}
|
|
|
|
const cart = ref<CartItem[]>([]);
|
|
const isCartOpen = ref(false);
|
|
|
|
const addToCart = (product: Product, variant: Variant, price: Price) => {
|
|
const existingIndex = cart.value.findIndex(item => item.variant.id === variant.id && item.price.type === price.type);
|
|
|
|
if (existingIndex > -1) {
|
|
if (cart.value[existingIndex].quantity < variant.stock) {
|
|
cart.value[existingIndex].quantity += 1;
|
|
}
|
|
} else {
|
|
cart.value.push({
|
|
product,
|
|
variant,
|
|
price,
|
|
quantity: 1
|
|
});
|
|
}
|
|
|
|
isCartOpen.value = true;
|
|
};
|
|
|
|
const removeFromCart = (variantId: number, priceType: string) => {
|
|
cart.value = cart.value.filter(item => !(item.variant.id === variantId && item.price.type === priceType));
|
|
};
|
|
|
|
const updateCartQuantity = (variantId: number, priceType: string, change: number) => {
|
|
const item = cart.value.find(item => item.variant.id === variantId && item.price.type === priceType);
|
|
|
|
if (item) {
|
|
const newQty = item.quantity + change;
|
|
|
|
if (newQty > 0 && newQty <= item.variant.stock) {
|
|
item.quantity = newQty;
|
|
}
|
|
}
|
|
};
|
|
|
|
const cartTotal = computed(() => {
|
|
return cart.value.reduce((total, item) => total + (item.price.price * item.quantity), 0);
|
|
});
|
|
|
|
const cartCount = computed(() => {
|
|
return cart.value.reduce((count, item) => count + item.quantity, 0);
|
|
});
|
|
|
|
// Centered Modal State
|
|
const activeProduct = ref<Product | null>(null);
|
|
const activeVariant = ref<Variant | null>(null);
|
|
const activePriceType = ref<string>('ecer');
|
|
|
|
const openQuickView = (product: Product) => {
|
|
activeProduct.value = product;
|
|
activeVariant.value = product.variants[0] || null;
|
|
activePriceType.value = 'ecer';
|
|
};
|
|
|
|
const closeQuickView = () => {
|
|
activeProduct.value = null;
|
|
activeVariant.value = null;
|
|
};
|
|
|
|
const selectedPrice = computed(() => {
|
|
if (!activeVariant.value) {
|
|
return null;
|
|
}
|
|
|
|
return activeVariant.value.prices.find(p => p.type === activePriceType.value) || activeVariant.value.prices[0] || null;
|
|
});
|
|
|
|
// Checkout Mock / WhatsApp redirect
|
|
const isCheckingOut = ref(false);
|
|
|
|
const handleWhatsAppCheckout = () => {
|
|
if (cart.value.length === 0) {
|
|
return;
|
|
}
|
|
|
|
isCheckingOut.value = true;
|
|
|
|
// Construct WhatsApp message
|
|
let message = `Halo ${props.appName}, saya ingin memesan produk berikut:\n\n`;
|
|
cart.value.forEach((item, index) => {
|
|
message += `${index + 1}. *${item.product.name}*\n`;
|
|
message += ` - Varian/Ukuran: ${item.variant.name}\n`;
|
|
message += ` - Jenis Harga: ${item.price.type_label}\n`;
|
|
message += ` - Jumlah: ${item.quantity} pcs\n`;
|
|
message += ` - Subtotal: ${formatPrice(item.price.price * item.quantity)}\n\n`;
|
|
});
|
|
message += `*Total Pembayaran: ${formatPrice(cartTotal.value)}*\n\n`;
|
|
message += `Mohon info untuk detail rekening transfer dan pengirimannya. Terima kasih!`;
|
|
|
|
const formattedPhone = props.contactPhone.replace(/[^0-9]/g, '');
|
|
const finalPhone = formattedPhone.startsWith('0') ? '62' + formattedPhone.slice(1) : formattedPhone;
|
|
const waUrl = `https://wa.me/${finalPhone || '6281234567890'}?text=${encodeURIComponent(message)}`;
|
|
|
|
setTimeout(() => {
|
|
isCheckingOut.value = false;
|
|
window.open(waUrl, '_blank');
|
|
}, 800);
|
|
};
|
|
|
|
const resetPriceFilters = () => {
|
|
minPrice.value = null;
|
|
maxPrice.value = null;
|
|
sortBy.value = CatalogSortOption.DEFAULT;
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<Head :title="`${appName} | Galeri Pakaian Modern & Sejuk`">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous" />
|
|
</Head>
|
|
|
|
<div class="min-h-screen bg-gradient-to-tr from-amber-50/20 via-[#FDFDFC] to-yellow-50/30 text-slate-800 transition-colors duration-300 font-sans scroll-smooth">
|
|
|
|
<!-- Sticky Header & Navbar -->
|
|
<header class="sticky top-0 z-40 bg-white/70 backdrop-blur-lg border-b border-amber-100/50 transition-all">
|
|
<div class="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
|
|
|
|
<!-- Logo Frame from settings -->
|
|
<div class="flex items-center space-x-3">
|
|
<a href="#" class="flex items-center space-x-2">
|
|
<img
|
|
v-if="logoUrl"
|
|
:src="logoUrl"
|
|
:alt="appName"
|
|
class="h-10 w-auto object-contain p-1 rounded-lg shadow-sm"
|
|
/>
|
|
<span v-else class="font-serif text-2xl font-bold tracking-[0.1em] text-amber-600">
|
|
{{ appName }}
|
|
</span>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Navigation Links (Indonesian) -->
|
|
<nav class="hidden lg:flex items-center space-x-8 text-xs font-semibold uppercase tracking-widest text-slate-600">
|
|
<a href="#hero" class="hover:text-amber-600 transition-colors">Beranda</a>
|
|
<a href="#catalog" class="hover:text-amber-600 transition-colors">Produk</a>
|
|
<a href="#about" class="hover:text-amber-600 transition-colors">Tentang Kami</a>
|
|
<a href="#order-guide" class="hover:text-amber-600 transition-colors">Cara Pesan</a>
|
|
<a href="#contact" class="hover:text-amber-600 transition-colors">Hubungi Kami</a>
|
|
</nav>
|
|
|
|
<!-- Action Utilities (Cart) -->
|
|
<div class="flex items-center space-x-4">
|
|
<!-- Cart button -->
|
|
<button
|
|
@click="isCartOpen = true"
|
|
class="relative p-2 rounded-full hover:bg-amber-50 text-slate-600 transition-colors cursor-pointer"
|
|
aria-label="Keranjang Belanja"
|
|
>
|
|
<ShoppingBag class="w-4.5 h-4.5 text-amber-600" />
|
|
<span
|
|
v-if="cartCount > 0"
|
|
class="absolute -top-0.5 -right-0.5 bg-amber-600 text-white text-[9px] font-bold w-4.5 h-4.5 flex items-center justify-center rounded-full shadow-sm animate-bounce"
|
|
>
|
|
{{ cartCount }}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Hero Section: Split Screen Visual Masterpiece (Fresh Aqua Palette) -->
|
|
<section id="hero" class="relative min-h-[calc(100vh-80px)] flex flex-col justify-between overflow-hidden">
|
|
<!-- Split Background Grid with Fresh Cyan Gradients -->
|
|
<div class="absolute inset-0 grid grid-cols-1 lg:grid-cols-2 pointer-events-none z-0">
|
|
<div class="bg-white/40/30"></div>
|
|
<div class="bg-gradient-to-tr from-amber-100/30 via-yellow-100/20 to-orange-50/30 border-l border-amber-100/20"></div>
|
|
</div>
|
|
|
|
<!-- 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">
|
|
|
|
<!-- 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="inline-flex items-center space-x-2 bg-amber-500/10/10 text-[10px] text-amber-700 uppercase tracking-widest px-3 py-1 rounded-full w-fit font-bold shadow-sm">
|
|
<span>Koleksi Segar 2026</span>
|
|
</div>
|
|
|
|
<h1 class="text-5xl md:text-6xl font-serif leading-[1.1] tracking-tight font-light select-none text-slate-800">
|
|
Ekspresikan <br />
|
|
Gaya <span class="font-normal italic text-amber-600 bg-amber-100/40/20 px-2 rounded-lg">Segar Anda</span>.
|
|
</h1>
|
|
|
|
<p class="text-sm text-slate-500 max-w-md leading-relaxed font-light">
|
|
Temukan koleksi busana terbaru kami yang dirancang untuk kenyamanan dan gaya sehari-hari Anda.
|
|
</p>
|
|
|
|
<div class="flex items-center space-x-4 pt-4">
|
|
<a
|
|
href="#catalog"
|
|
class="inline-flex items-center justify-center bg-amber-600 text-white px-8 py-3.5 text-xs font-semibold uppercase tracking-widest rounded-xl shadow-lg shadow-amber-200/50 hover:bg-amber-500 hover:-translate-y-0.5 transition-all duration-300 group cursor-pointer"
|
|
>
|
|
Beli Sekarang
|
|
<ArrowRight class="w-3.5 h-3.5 ml-2 transform group-hover:translate-x-1 transition-transform" />
|
|
</a>
|
|
<a
|
|
href="#about"
|
|
class="inline-flex items-center justify-center border border-amber-200 text-xs font-semibold uppercase tracking-widest px-8 py-3.5 rounded-xl hover:bg-amber-500/5 hover:border-amber-400 transition-all cursor-pointer"
|
|
>
|
|
Tentang Kami
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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">
|
|
|
|
<!-- 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="text-[14vw] lg:text-[160px] font-serif font-bold leading-none tracking-tight flex opacity-10[0.03]">
|
|
<span class="text-slate-900">DST</span>
|
|
<span class="text-amber-600">Collection</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Glowing Radial Background and Animated Morphing Blobs -->
|
|
<div class="absolute inset-0 -z-10 flex justify-center items-center pointer-events-none select-none">
|
|
<div class="absolute w-[350px] h-[350px] rounded-full bg-gradient-to-tr from-amber-400/25 via-yellow-400/20 to-orange-300/25/15/20/15 blur-3xl animate-blob"></div>
|
|
<div class="absolute w-[400px] h-[400px] rounded-full bg-gradient-to-br from-rose-300/20 via-amber-200/20 to-orange-300/20/15/15/15 blur-3xl animate-blob animation-delay-2000"></div>
|
|
</div>
|
|
|
|
<!-- Main Model Central Image (With Floating Animation) -->
|
|
<div class="relative w-[310px] h-[440px] lg:w-[390px] lg:h-[540px] rounded-[2rem] overflow-hidden z-10 transition-transform duration-500 hover:scale-[1.02] animate-float-model">
|
|
<img
|
|
:src="homepage.hero_image_url || 'https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=1000&auto=format&fit=crop&q=80'"
|
|
alt="DSTCLO Model Unggulan"
|
|
class="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
|
|
<!-- FLOATING HERO TAG 1: Product Showcase -->
|
|
<div
|
|
v-if="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 p-3 rounded-2xl shadow-lg border border-amber-100/40 cursor-pointer transform hover:-translate-y-1 transition-all animate-float-tag-1"
|
|
>
|
|
<img
|
|
:src="getProductImage(featuredProducts[0])"
|
|
alt="Preview Produk"
|
|
class="w-10 h-10 object-cover rounded-lg"
|
|
/>
|
|
<div class="text-left">
|
|
<h4 class="text-[9px] font-bold text-amber-600 uppercase tracking-widest">Terpopuler</h4>
|
|
<p class="text-[11px] font-semibold truncate max-w-[100px]">{{ featuredProducts[0].name }}</p>
|
|
<span class="text-[11px] font-bold text-amber-600">{{ getProductPriceRange(featuredProducts[0]) }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-center w-6 h-6 rounded-full bg-amber-50/50 text-amber-600">
|
|
<Plus class="w-3.5 h-3.5" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FLOATING HERO TAG 2: Rating -->
|
|
<div
|
|
v-if="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 p-3 rounded-2xl shadow-lg border border-amber-100/40 cursor-pointer transform hover:-translate-y-1 transition-all animate-float-tag-2"
|
|
>
|
|
<img
|
|
:src="getProductImage(featuredProducts[1], 1)"
|
|
alt="Preview Produk"
|
|
class="w-10 h-10 object-cover rounded-lg"
|
|
/>
|
|
<div class="text-left">
|
|
<div class="flex items-center text-amber-400 space-x-0.5 mb-0.5">
|
|
<Star class="w-2.5 h-2.5 fill-current" v-for="i in 5" :key="i" />
|
|
</div>
|
|
<p class="text-[11px] font-semibold truncate max-w-[100px]">{{ featuredProducts[1].name }}</p>
|
|
<span class="text-[10px] font-semibold text-emerald-500">Stok Tersedia</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Scroll Indicator -->
|
|
<div class="max-w-7xl mx-auto px-6 w-full flex items-center justify-between pb-8 z-10 text-xs font-semibold text-slate-500">
|
|
<div class="flex items-center space-x-3">
|
|
<span class="text-amber-600">#DSTCollection</span>
|
|
<span class="w-1 h-1 rounded-full bg-amber-300"></span>
|
|
<span>Bahan Adem & Lembut</span>
|
|
</div>
|
|
<a href="#catalog" class="flex items-center space-x-2 hover:text-amber-600 transition-colors">
|
|
<span>Lihat katalog lengkap</span>
|
|
<ChevronRight class="w-3.5 h-3.5 transform rotate-90 text-amber-600" />
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Catalog Section with Price Filters & Categories -->
|
|
<section id="catalog" class="max-w-7xl mx-auto px-6 py-20 border-t border-amber-100/30">
|
|
|
|
<div class="space-y-6 mb-12">
|
|
<div class="text-center space-y-2">
|
|
<h2 class="text-xs uppercase tracking-[0.3em] font-bold text-amber-600">Katalog Eksklusif</h2>
|
|
<h3 class="text-3xl font-serif text-slate-800">Koleksi Busana Pilihan</h3>
|
|
<p class="text-sm text-slate-500 max-w-lg mx-auto font-light">
|
|
Temukan busana favorit Anda dari koleksi terbaru kami.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Interactive Filters & Search Controls bar -->
|
|
<div class="bg-white/80 backdrop-blur-md p-6 rounded-3xl shadow-sm border border-amber-100/30 flex flex-col md:flex-row items-center gap-4 justify-between">
|
|
|
|
<!-- Search Input -->
|
|
<div class="relative w-full md:w-80">
|
|
<input
|
|
v-model="searchInput"
|
|
type="text"
|
|
placeholder="Cari produk..."
|
|
class="w-full bg-slate-50 border border-slate-200 text-xs px-4 py-3 pr-10 rounded-xl outline-none focus:border-amber-500 text-slate-700 transition-all"
|
|
/>
|
|
<Search class="absolute right-3 top-3.5 w-4 h-4 text-slate-400" />
|
|
</div>
|
|
|
|
<!-- Category Chips -->
|
|
<div class="flex flex-wrap gap-2 w-full md:w-auto justify-center">
|
|
<button
|
|
@click="selectedCategory = null"
|
|
:class="[
|
|
'px-4 py-2 text-[11px] font-semibold uppercase tracking-wider rounded-xl border transition-all cursor-pointer',
|
|
!selectedCategory
|
|
? 'bg-amber-600 text-white border-transparent shadow-md shadow-amber-200/50'
|
|
: 'bg-transparent text-slate-600 border-slate-200 hover'
|
|
]"
|
|
>
|
|
Semua
|
|
</button>
|
|
<button
|
|
v-for="category in categories"
|
|
:key="category.id"
|
|
@click="toggleCategory(category.slug)"
|
|
:class="[
|
|
'px-4 py-2 text-[11px] font-semibold uppercase tracking-wider rounded-xl border transition-all cursor-pointer',
|
|
selectedCategory === category.slug
|
|
? 'bg-amber-600 text-white border-transparent shadow-md shadow-amber-200/50'
|
|
: 'bg-transparent text-slate-600 border-slate-200 hover'
|
|
]"
|
|
>
|
|
{{ category.name }}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Filter panel toggler -->
|
|
<button
|
|
@click="showFilters = !showFilters"
|
|
class="flex items-center space-x-2 px-4 py-2.5 rounded-xl border border-slate-200 text-xs font-semibold text-slate-600 hover:border-amber-400 cursor-pointer transition-colors"
|
|
>
|
|
<Filter class="w-3.5 h-3.5" />
|
|
<span>Filter & Urutkan</span>
|
|
<span v-if="minPrice || maxPrice || sortBy !== 'default'" class="w-2 h-2 rounded-full bg-amber-600"></span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Expandable Filter Panel -->
|
|
<div
|
|
v-if="showFilters"
|
|
class="bg-white/50/40 p-6 rounded-2xl border border-amber-100/20 grid grid-cols-1 md:grid-cols-3 gap-6 animate-fade-in-up"
|
|
>
|
|
<!-- Price Range -->
|
|
<div class="space-y-2">
|
|
<label class="text-[11px] uppercase tracking-wider font-bold text-slate-500">Rentang Harga</label>
|
|
<div class="flex items-center space-x-2">
|
|
<RupiahInput
|
|
v-model="minPrice"
|
|
placeholder="Min"
|
|
class="w-full bg-white text-slate-700"
|
|
/>
|
|
<span class="text-xs text-slate-400">s/d</span>
|
|
<RupiahInput
|
|
v-model="maxPrice"
|
|
placeholder="Max"
|
|
class="w-full bg-white text-slate-700"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sort Options -->
|
|
<div class="space-y-2">
|
|
<label class="text-[11px] uppercase tracking-wider font-bold text-slate-500">Urutkan Berdasarkan</label>
|
|
<Select v-model="sortBy">
|
|
<SelectTrigger class="w-full bg-white border border-slate-200 px-3 py-2 text-xs rounded-lg outline-none focus:border-amber-500 text-slate-700 h-10">
|
|
<SelectValue placeholder="Pilih urutan" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem :value="CatalogSortOption.DEFAULT">Default (Urutan Utama)</SelectItem>
|
|
<SelectItem :value="CatalogSortOption.PRICE_ASC">Harga Terendah</SelectItem>
|
|
<SelectItem :value="CatalogSortOption.PRICE_DESC">Harga Tertinggi</SelectItem>
|
|
<SelectItem :value="CatalogSortOption.NAME_ASC">Nama Produk (A-Z)</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<!-- Reset Trigger -->
|
|
<div class="flex items-end">
|
|
<button
|
|
@click="resetPriceFilters"
|
|
class="w-full bg-slate-100 hover:bg-slate-200 text-slate-700 py-2.5 rounded-lg text-xs font-semibold uppercase tracking-wider transition-colors cursor-pointer"
|
|
>
|
|
Atur Ulang Filter
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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-for="(product, idx) in filteredProducts"
|
|
:key="product.id"
|
|
class="group relative flex flex-col space-y-4"
|
|
>
|
|
<!-- Product Image Container -->
|
|
<div class="relative aspect-[3/4] w-full overflow-hidden rounded-2xl bg-amber-50/50 shadow-md transition-shadow hover">
|
|
<img
|
|
:src="getProductImage(product, idx)"
|
|
:alt="product.name"
|
|
class="h-full w-full object-cover object-center transition-transform duration-700 ease-out group-hover:scale-105"
|
|
/>
|
|
<!-- Subtle Gradient Overlay -->
|
|
<div class="absolute inset-0 bg-gradient-to-t from-amber-950/20 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
|
|
<!-- Absolute Status Badges -->
|
|
<div class="absolute top-4 left-4 flex flex-col space-y-2">
|
|
<span
|
|
v-if="getTotalStock(product) === 0"
|
|
class="bg-red-500 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md"
|
|
>
|
|
Habis
|
|
</span>
|
|
<span
|
|
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"
|
|
>
|
|
Menipis
|
|
</span>
|
|
<span
|
|
v-else
|
|
class="bg-amber-600 text-white text-[9px] font-bold uppercase tracking-wider px-3 py-1 rounded-md"
|
|
>
|
|
Terbaru
|
|
</span>
|
|
</div>
|
|
|
|
<!-- 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">
|
|
<button
|
|
@click="openQuickView(product)"
|
|
class="w-full flex items-center justify-center bg-white/95 backdrop-blur-sm text-slate-800 hover:bg-amber-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"
|
|
>
|
|
<Eye class="w-4 h-4 mr-2" />
|
|
Lihat Detail
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Product Details Info -->
|
|
<div class="flex flex-col space-y-1">
|
|
<!-- Category Tags -->
|
|
<span class="text-[10px] font-bold uppercase tracking-widest text-amber-600">
|
|
{{ product.categories.map(c => c.name).join(', ') }}
|
|
</span>
|
|
|
|
<!-- Title -->
|
|
<h4 class="text-base font-semibold text-slate-700 line-clamp-1 group-hover transition-colors">
|
|
{{ product.name }}
|
|
</h4>
|
|
|
|
<!-- Price -->
|
|
<p class="text-sm font-bold text-amber-600">
|
|
{{ getProductPriceRange(product) }}
|
|
</p>
|
|
|
|
<!-- Stock Indicator -->
|
|
<span class="text-[11px] text-slate-400 font-light mt-0.5">
|
|
Ukuran tersedia: {{ product.variants.map(v => v.name).join(', ') }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty Product State -->
|
|
<div v-else class="text-center py-20 space-y-3">
|
|
<Info class="w-12 h-12 text-amber-300 mx-auto" />
|
|
<h3 class="text-lg font-medium text-slate-700">Produk Tidak Ditemukan</h3>
|
|
<p class="text-sm text-slate-400">
|
|
Mohon maaf, kami tidak menemukan pakaian yang cocok dengan kata kunci pencarian Anda.
|
|
</p>
|
|
<button
|
|
@click="searchInput = ''; selectedCategory = null; resetPriceFilters()"
|
|
class="mt-2 text-xs font-bold uppercase tracking-widest text-amber-600 underline underline-offset-4 cursor-pointer"
|
|
>
|
|
Reset Pencarian
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Gallery / Lookbook Section -->
|
|
<section id="gallery" class="w-full bg-gradient-to-br from-amber-50/40 via-yellow-50/20 to-orange-50/30 py-24 border-t border-b border-amber-100/30">
|
|
<div class="max-w-7xl mx-auto px-6 space-y-12">
|
|
<!-- Header -->
|
|
<div class="text-center space-y-3 max-w-lg mx-auto">
|
|
<span class="text-xs uppercase tracking-[0.3em] font-bold text-amber-600">Galeri Kami</span>
|
|
<h3 class="text-4xl font-serif leading-tight">Koleksi Lookbook</h3>
|
|
<p class="text-sm text-slate-500 leading-relaxed font-light">
|
|
Lihat koleksi busana kami dalam balutan gaya yang elegan.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Gallery Grid -->
|
|
<div v-if="homepage.gallery_images.length > 0" class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
<div
|
|
v-for="(image, index) in homepage.gallery_images"
|
|
:key="image.id"
|
|
class="group relative aspect-[3/4] rounded-2xl overflow-hidden shadow-md border-2 border-white cursor-pointer transition-all duration-300 hover:shadow-xl hover:-translate-y-1"
|
|
>
|
|
<img
|
|
:src="image.url"
|
|
:alt="`Lookbook ${Number(index) + 1}`"
|
|
class="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
|
|
/>
|
|
<div class="absolute inset-0 bg-gradient-to-t from-amber-950/40 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="text-center py-12">
|
|
<p class="text-sm text-slate-400">Belum ada foto lookbook yang ditambahkan.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Cara Pesan Section -->
|
|
<section id="order-guide" class="max-w-7xl mx-auto px-6 py-20 border-b border-amber-100/30">
|
|
<div class="text-center space-y-3 mb-16">
|
|
<h3 class="text-xs uppercase tracking-[0.3em] font-bold text-amber-600">Panduan</h3>
|
|
<h4 class="text-3xl font-serif">Cara Pemesanan</h4>
|
|
<p class="text-sm text-slate-500 max-w-md mx-auto font-light">
|
|
Ikuti langkah-langkah mudah berikut untuk melakukan pemesanan.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
|
<div
|
|
v-for="(step, index) in orderSteps"
|
|
:key="index"
|
|
class="bg-white/50 p-6 rounded-2xl border border-amber-100/30 shadow-sm relative overflow-hidden text-center group hover:-translate-y-1 transition-transform"
|
|
>
|
|
<div class="absolute -top-4 -right-4 w-16 h-16 bg-amber-100/50/50 rounded-full flex items-center justify-center text-amber-600 font-bold text-xl font-serif">{{ index + 1 }}</div>
|
|
<div class="w-12 h-12 rounded-xl bg-amber-100/30 text-amber-600 flex items-center justify-center mx-auto mb-4">
|
|
<ShoppingBag v-if="index === 0" class="w-5 h-5" />
|
|
<Star v-else-if="index === 1" class="w-5 h-5" />
|
|
<Plus v-else-if="index === 2" class="w-5 h-5" />
|
|
<Phone v-else class="w-5 h-5" />
|
|
</div>
|
|
<h5 class="font-bold text-base mb-2">{{ step.title }}</h5>
|
|
<p class="text-xs text-slate-400 leading-relaxed font-light">
|
|
{{ step.description }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- About Us Section -->
|
|
<section id="about" class="max-w-7xl mx-auto px-6 py-20 border-b border-amber-100/30">
|
|
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 items-center">
|
|
<!-- 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">
|
|
<img
|
|
:src="homepage.about_image_url || 'https://images.unsplash.com/photo-1595777457583-95e059d581b8?w=800&auto=format&fit=crop&q=80'"
|
|
alt="Tentang Kami"
|
|
class="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
<!-- Column Right -->
|
|
<div class="lg:col-span-6 space-y-6">
|
|
<span class="text-xs uppercase tracking-[0.3em] font-bold text-amber-600">Tentang Kami</span>
|
|
<h3 class="text-3xl font-serif">Kami Menghadirkan Busana Berkualitas</h3>
|
|
<p class="text-sm text-slate-500 leading-relaxed font-light whitespace-pre-line">
|
|
{{ aboutApp || 'Kami adalah rumah produksi busana dan batik lokal berkualitas tinggi. Berfokus pada keindahan motif, ketepatan detail jahitan, dan pemilihan kain adem yang mengedepankan aspek fungsionalitas dan estetika.' }}
|
|
</p>
|
|
<div v-for="(feature, index) in aboutFeatures" :key="index" class="flex items-center space-x-3 text-xs text-slate-500 font-semibold">
|
|
<Check class="w-4 h-4 text-emerald-500" />
|
|
<span>{{ feature }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Contact Section -->
|
|
<section id="contact" class="max-w-7xl mx-auto px-6 py-20">
|
|
<div class="bg-white/60 backdrop-blur-md border border-amber-100/30 p-8 rounded-3xl grid grid-cols-1 lg:grid-cols-2 gap-12 items-center shadow-sm">
|
|
<!-- Left -->
|
|
<div class="space-y-6">
|
|
<span class="text-xs uppercase tracking-[0.3em] font-bold text-amber-600">Hubungi Kami</span>
|
|
<h3 class="text-3xl font-serif">Mari Berdiskusi</h3>
|
|
<p class="text-sm text-slate-500 leading-relaxed font-light">
|
|
Jangan ragu untuk menghubungi kami jika Anda memiliki pertanyaan atau ingin melakukan pemesanan.
|
|
</p>
|
|
|
|
<div class="space-y-4">
|
|
<div v-if="contactPhone" class="flex items-center space-x-3 text-sm">
|
|
<div class="w-8 h-8 rounded-lg bg-amber-100/40 text-amber-600 flex items-center justify-center">
|
|
<Phone class="w-4 h-4" />
|
|
</div>
|
|
<span>{{ contactPhone }}</span>
|
|
</div>
|
|
<div v-if="contactEmail" class="flex items-center space-x-3 text-sm">
|
|
<div class="w-8 h-8 rounded-lg bg-amber-100/40 text-amber-600 flex items-center justify-center">
|
|
<Mail class="w-4 h-4" />
|
|
</div>
|
|
<span>{{ contactEmail }}</span>
|
|
</div>
|
|
<div v-if="contactAddress" class="flex items-center space-x-3 text-sm">
|
|
<div class="w-8 h-8 rounded-lg bg-amber-100/40 text-amber-600 flex items-center justify-center">
|
|
<MapPin class="w-4 h-4" />
|
|
</div>
|
|
<span>{{ contactAddress }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Form/Quick Message Mock -->
|
|
<div class="bg-slate-50 p-6 rounded-2xl border border-slate-200 space-y-4">
|
|
<h4 class="font-bold text-base">Kirim Pesan Cepat</h4>
|
|
<div class="space-y-3">
|
|
<input type="text" placeholder="Nama Anda" class="w-full text-xs px-4 py-3 bg-white border border-slate-200 rounded-lg outline-none focus:border-amber-500 text-slate-700" />
|
|
<textarea placeholder="Tulis pesan Anda di sini..." rows="4" class="w-full text-xs px-4 py-3 bg-white border border-slate-200 rounded-lg outline-none focus:border-amber-500 text-slate-700 scrollbar-thin"></textarea>
|
|
|
|
<!-- WhatsApp Direct Send Button -->
|
|
<a
|
|
:href="`https://wa.me/${contactPhone.replace(/[^0-9]/g, '') || '6281234567890'}`"
|
|
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"
|
|
>
|
|
<Phone class="w-4 h-4 mr-2" />
|
|
Kirim Melalui WhatsApp
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Brand Footer -->
|
|
<footer class="bg-white text-slate-600 py-16 border-t border-amber-100/30">
|
|
<div class="max-w-7xl mx-auto px-6 grid grid-cols-1 md:grid-cols-4 gap-8">
|
|
<!-- Brand Profile -->
|
|
<div class="space-y-4">
|
|
<div class="flex items-center space-x-2">
|
|
<img
|
|
v-if="logoUrl"
|
|
:src="logoUrl"
|
|
:alt="appName"
|
|
class="h-8 w-auto object-contain p-0.5 rounded shadow-sm"
|
|
/>
|
|
<span v-else class="font-serif text-xl font-bold tracking-widest text-black text-amber-600">
|
|
{{ appName }}
|
|
</span>
|
|
</div>
|
|
<p class="text-xs text-slate-400 leading-relaxed font-light">
|
|
Rumah produksi busana dan batik lokal berkualitas tinggi. Menghadirkan desain eksklusif untuk gaya sehari-hari Anda.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Shop Links -->
|
|
<div class="space-y-3">
|
|
<h4 class="text-xs uppercase font-bold text-slate-800 tracking-widest">Koleksi Kami</h4>
|
|
<ul class="space-y-2 text-xs">
|
|
<li><a href="#catalog" class="hover transition-colors">Batik Daster</a></li>
|
|
<li><a href="#catalog" class="hover transition-colors">Setelan Celana</a></li>
|
|
<li><a href="#catalog" class="hover transition-colors">Daster Menyusui (Busui)</a></li>
|
|
<li><a href="#catalog" class="hover transition-colors">Atasan & Bawahan</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Info links -->
|
|
<div class="space-y-3">
|
|
<h4 class="text-xs uppercase font-bold text-slate-800 tracking-widest">Tautan</h4>
|
|
<ul class="space-y-2 text-xs">
|
|
<li><a href="#about" class="hover transition-colors">Tentang Kami</a></li>
|
|
<li><a href="#order-guide" class="hover transition-colors">Cara Pemesanan</a></li>
|
|
<li><a href="#contact" class="hover transition-colors">Hubungi Kami</a></li>
|
|
<li><a href="/admin/dashboard" class="hover transition-colors font-semibold">Login Dashboard Admin</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Socials -->
|
|
<div class="space-y-3">
|
|
<h4 class="text-xs uppercase font-bold text-slate-800 tracking-widest">Sosial Media</h4>
|
|
<p class="text-xs text-slate-400 font-light">Ikuti sosial media kami untuk mendapatkan update produk terbaru.</p>
|
|
<div class="flex space-x-3 text-xs">
|
|
<a v-if="instagramUrl" :href="instagramUrl" target="_blank" class="text-slate-400 hover">Instagram</a>
|
|
<a v-if="facebookUrl" :href="facebookUrl" target="_blank" class="text-slate-400 hover">Facebook</a>
|
|
<a v-if="tiktokUrl" :href="tiktokUrl" target="_blank" class="text-slate-400 hover">TikTok</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="max-w-7xl mx-auto px-6 mt-12 pt-8 border-t border-amber-100/30 flex flex-col sm:flex-row justify-between items-center text-[10px] text-slate-400 font-light space-y-4 sm:space-y-0">
|
|
<p>© {{ new Date().getFullYear() }} {{ appName }}. Hak Cipta Dilindungi.</p>
|
|
</div>
|
|
</footer>
|
|
|
|
<!-- CENTERED DETAIL MODAL (MODA DETAIL) -->
|
|
<div
|
|
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"
|
|
@click.self="closeQuickView"
|
|
>
|
|
<div
|
|
class="w-full max-w-3xl bg-[#FDFDFC] 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-amber-100/30 scrollbar-thin"
|
|
>
|
|
<!-- Image Side -->
|
|
<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 border border-slate-200/50/50 shadow-sm relative">
|
|
<img
|
|
:src="getProductImage(activeProduct)"
|
|
:alt="activeProduct.name"
|
|
class="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Info Side -->
|
|
<div class="w-full md:w-1/2 flex flex-col justify-between space-y-6 text-left">
|
|
<div class="space-y-4">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<span class="text-[10px] font-bold uppercase tracking-widest text-amber-600">
|
|
{{ activeProduct.categories.map(c => c.name).join(', ') }}
|
|
</span>
|
|
<h3 class="text-2xl font-serif mt-0.5 text-slate-800">{{ activeProduct.name }}</h3>
|
|
</div>
|
|
<button
|
|
@click="closeQuickView"
|
|
class="p-1.5 rounded-full hover:bg-slate-100 transition-colors cursor-pointer"
|
|
>
|
|
<X class="w-5 h-5 text-slate-500" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Description (Scrollbar thin applied) -->
|
|
<div class="space-y-1.5">
|
|
<h4 class="text-[10px] uppercase font-bold tracking-widest text-slate-400">Deskripsi</h4>
|
|
<p class="text-xs text-slate-500 font-light leading-relaxed max-h-32 overflow-y-auto scrollbar-thin pr-1">
|
|
{{ activeProduct.description || 'Pakaian batik modern dan nyaman hasil karya perajin terbaik kami. Menggunakan serat kain pilihan yang awet dan menyejukkan kulit.' }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Variant Select -->
|
|
<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>
|
|
<div class="flex flex-wrap gap-1.5">
|
|
<button
|
|
v-for="variant in activeProduct.variants"
|
|
:key="variant.id"
|
|
@click="activeVariant = variant"
|
|
:class="[
|
|
'px-3 py-1.5 rounded-xl text-xs font-semibold uppercase tracking-wider border cursor-pointer transition-all',
|
|
activeVariant?.id === variant.id
|
|
? 'bg-amber-600 text-white border-transparent shadow-md shadow-amber-200/30'
|
|
: 'bg-transparent text-slate-600 border-slate-200/60'
|
|
]"
|
|
>
|
|
{{ variant.name }}
|
|
<span class="text-[9px] opacity-70 ml-1">({{ variant.stock }} pcs)</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Price Tiers -->
|
|
<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>
|
|
<div class="grid grid-cols-2 gap-1.5 max-h-36 overflow-y-auto scrollbar-thin pr-1">
|
|
<button
|
|
v-for="price in activeVariant.prices"
|
|
:key="price.id"
|
|
@click="activePriceType = price.type"
|
|
:class="[
|
|
'px-3 py-2 text-left rounded-xl border transition-all cursor-pointer',
|
|
activePriceType === price.type
|
|
? 'border-amber-500 bg-amber-500/5/5'
|
|
: 'border-slate-200/60 hover'
|
|
]"
|
|
>
|
|
<span class="block text-[8px] uppercase tracking-wider font-bold text-slate-400">
|
|
{{ price.type_label }}
|
|
</span>
|
|
<span class="block text-xs font-bold mt-0.5 text-amber-600">
|
|
{{ formatPrice(price.price) }}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cart Call to Action -->
|
|
<div class="pt-4 border-t border-slate-100/50 space-y-4">
|
|
<div class="flex justify-between items-center text-xs">
|
|
<div>
|
|
<span class="text-[10px] uppercase font-bold tracking-widest text-slate-400">Harga Terpilih</span>
|
|
<div class="text-xl font-bold text-amber-600 mt-0.5">
|
|
{{ selectedPrice ? formatPrice(selectedPrice.price) : 'Pilih ukuran' }}
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<span class="text-[10px] uppercase font-bold tracking-widest text-slate-400">Sisa Stok</span>
|
|
<div class="font-semibold text-slate-700 mt-0.5">
|
|
{{ activeVariant ? `${activeVariant.stock} pcs` : 'Pilih ukuran' }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
v-if="activeVariant && selectedPrice"
|
|
@click="addToCart(activeProduct, activeVariant, selectedPrice)"
|
|
:disabled="activeVariant.stock <= 0"
|
|
:class="[
|
|
'w-full py-3.5 text-xs font-bold uppercase tracking-widest rounded-xl transition-all cursor-pointer shadow-md',
|
|
activeVariant.stock > 0
|
|
? 'bg-amber-600 text-white hover:bg-amber-500 shadow-amber-200/50'
|
|
: 'bg-slate-100 text-slate-400 cursor-not-allowed shadow-none'
|
|
]"
|
|
>
|
|
{{ activeVariant.stock > 0 ? 'Masukkan ke Keranjang' : 'Stok Habis' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FLOATING CART SLIDE-OVER SHEET -->
|
|
<div
|
|
v-if="isCartOpen"
|
|
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"
|
|
>
|
|
<div
|
|
class="w-full max-w-md h-full bg-[#FDFDFC] p-6 shadow-2xl flex flex-col justify-between overflow-y-auto transform translate-x-0 transition-transform duration-300 border-l border-amber-100/30"
|
|
>
|
|
<div class="space-y-6">
|
|
<!-- Header -->
|
|
<div class="flex justify-between items-center">
|
|
<div class="flex items-center space-x-2">
|
|
<ShoppingBag class="w-5 h-5 text-amber-600" />
|
|
<h3 class="text-lg font-serif text-slate-800">Keranjang Belanja ({{ cartCount }})</h3>
|
|
</div>
|
|
<button
|
|
@click="isCartOpen = false"
|
|
class="p-2 rounded-full hover:bg-slate-100 transition-colors cursor-pointer"
|
|
>
|
|
<X class="w-5 h-5 text-slate-500" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Cart List -->
|
|
<div v-if="cart.length > 0" class="space-y-4 divide-y divide-slate-100/45 max-h-[calc(100vh-280px)] overflow-y-auto pr-1 scrollbar-thin">
|
|
<div
|
|
v-for="item in cart"
|
|
:key="`${item.variant.id}-${item.price.type}`"
|
|
class="flex space-x-4 pt-4 first:pt-0"
|
|
>
|
|
<img
|
|
:src="getProductImage(item.product)"
|
|
:alt="item.product.name"
|
|
class="w-16 h-20 object-cover rounded-xl bg-slate-50 border border-slate-200/50/50"
|
|
/>
|
|
|
|
<div class="flex-1 flex flex-col justify-between">
|
|
<div class="space-y-1">
|
|
<div class="flex justify-between items-start">
|
|
<h4 class="text-xs font-semibold text-slate-700 line-clamp-1">
|
|
{{ item.product.name }}
|
|
</h4>
|
|
<button
|
|
@click="removeFromCart(item.variant.id, item.price.type)"
|
|
class="text-slate-400 hover:text-red-500 p-1 cursor-pointer transition-colors"
|
|
>
|
|
<X class="w-3.5 h-3.5" />
|
|
</button>
|
|
</div>
|
|
<p class="text-[10px] text-slate-400">
|
|
Ukuran: <strong class="text-slate-700 font-semibold">{{ item.variant.name }}</strong> • Tipe: <strong class="text-slate-700 font-semibold">{{ item.price.type_label }}</strong>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex justify-between items-center mt-2">
|
|
<!-- Quantity Stepper -->
|
|
<div class="flex items-center space-x-2 border border-slate-200 rounded-lg px-2 py-0.5">
|
|
<button
|
|
@click="updateCartQuantity(item.variant.id, item.price.type, -1)"
|
|
class="p-1 text-slate-500 hover:text-black:text-white cursor-pointer"
|
|
>
|
|
<Minus class="w-3 h-3" />
|
|
</button>
|
|
<span class="text-xs font-semibold px-1 text-slate-800">{{ item.quantity }}</span>
|
|
<button
|
|
@click="updateCartQuantity(item.variant.id, item.price.type, 1)"
|
|
class="p-1 text-slate-500 hover:text-black:text-white cursor-pointer"
|
|
>
|
|
<Plus class="w-3 h-3" />
|
|
</button>
|
|
</div>
|
|
<span class="text-xs font-bold text-amber-600">
|
|
{{ formatPrice(item.price.price * item.quantity) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty Cart -->
|
|
<div v-else class="text-center py-20 space-y-4">
|
|
<ShoppingBag class="w-12 h-12 text-amber-200 mx-auto" />
|
|
<h4 class="text-sm font-semibold text-slate-700">Keranjang Anda masih kosong</h4>
|
|
<p class="text-xs text-slate-400 max-w-xs mx-auto font-light leading-relaxed">
|
|
Jelajahi produk batik dan pakaian pilihan kami untuk menambahkan item ke dalam keranjang.
|
|
</p>
|
|
<button
|
|
@click="isCartOpen = false"
|
|
class="bg-amber-600 text-white px-6 py-2.5 text-xs font-semibold uppercase tracking-widest rounded-xl hover:opacity-90 transition-all cursor-pointer shadow-md shadow-amber-200/50"
|
|
>
|
|
Lanjutkan Belanja
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer Summary Card -->
|
|
<div v-if="cart.length > 0" class="border-t border-amber-100/30 pt-6 space-y-4">
|
|
<div class="space-y-1.5">
|
|
<div class="flex justify-between text-xs text-slate-500">
|
|
<span>Subtotal</span>
|
|
<span>{{ formatPrice(cartTotal) }}</span>
|
|
</div>
|
|
<div class="flex justify-between text-xs text-slate-500">
|
|
<span>Biaya Kirim</span>
|
|
<span class="text-emerald-500 font-medium">GRATIS</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm font-bold text-slate-800 pt-1.5 border-t border-slate-100/30">
|
|
<span>Estimasi Total</span>
|
|
<span class="text-amber-600">{{ formatPrice(cartTotal) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
@click="handleWhatsAppCheckout"
|
|
: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"
|
|
>
|
|
<span v-if="isCheckingOut" class="inline-block border-2 border-current border-t-transparent animate-spin rounded-full w-4 h-4 mr-2"></span>
|
|
<Phone class="w-4 h-4 mr-2" />
|
|
{{ isCheckingOut ? 'Mengalihkan...' : 'Pesan Via WhatsApp' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
/* Smooth scrolling */
|
|
html {
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
/* Custom marquee animation */
|
|
@keyframes marquee {
|
|
0% {
|
|
transform: translateX(0%);
|
|
}
|
|
100% {
|
|
transform: translate3d(-50%, 0, 0);
|
|
}
|
|
}
|
|
|
|
.animate-marquee {
|
|
display: inline-block;
|
|
animation: marquee 25s linear infinite;
|
|
}
|
|
|
|
/* Float Animation */
|
|
@keyframes float-hero {
|
|
0%, 100% {
|
|
transform: translateY(0px);
|
|
}
|
|
50% {
|
|
transform: translateY(-12px);
|
|
}
|
|
}
|
|
|
|
.animate-float-model {
|
|
animation: float-hero 6s infinite ease-in-out;
|
|
}
|
|
|
|
.animate-float-tag-1 {
|
|
animation: float-hero 5s infinite ease-in-out;
|
|
}
|
|
|
|
.animate-float-tag-2 {
|
|
animation: float-hero 7s infinite ease-in-out;
|
|
}
|
|
|
|
/* Blob morphing background animation */
|
|
@keyframes blob-bounce {
|
|
0%, 100% {
|
|
transform: translate(0px, 0px) scale(1);
|
|
}
|
|
33% {
|
|
transform: translate(30px, -40px) scale(1.12);
|
|
}
|
|
66% {
|
|
transform: translate(-20px, 20px) scale(0.95);
|
|
}
|
|
}
|
|
|
|
.animate-blob {
|
|
animation: blob-bounce 14s infinite alternate ease-in-out;
|
|
}
|
|
|
|
.animation-delay-2000 {
|
|
animation-delay: 2s;
|
|
}
|
|
|
|
/* Animation classes */
|
|
@keyframes fadeInUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translate3d(0, 20px, 0);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translate3d(0, 0, 0);
|
|
}
|
|
}
|
|
|
|
.animate-fade-in-up {
|
|
animation: fadeInUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
|
}
|
|
</style>
|