-
+
handleSearch(e.target.value, 'subdistrict')}
onFocus={() => { setFocusedField('subdistrict'); handleSearch(value.subdistrict, 'subdistrict'); }}
onBlur={() => setTimeout(() => { if (focusedField === 'subdistrict') setFocusedField(null); }, 200)}
- className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-spa-forest outline-none transition-all"
+ className="w-full bg-pop-bg border border-pop-black/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-pop-black outline-none transition-all"
/>
{renderDropdown('subdistrict')}
-
+
handleSearch(e.target.value, 'district')}
onFocus={() => { setFocusedField('district'); handleSearch(value.district, 'district'); }}
onBlur={() => setTimeout(() => { if (focusedField === 'district') setFocusedField(null); }, 200)}
- className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-spa-forest outline-none transition-all"
+ className="w-full bg-pop-bg border border-pop-black/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-pop-black outline-none transition-all"
/>
{renderDropdown('district')}
-
+
handleSearch(e.target.value, 'province')}
onFocus={() => { setFocusedField('province'); handleSearch(value.province, 'province'); }}
onBlur={() => setTimeout(() => { if (focusedField === 'province') setFocusedField(null); }, 200)}
- className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-spa-forest outline-none transition-all"
+ className="w-full bg-pop-bg border border-pop-black/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-pop-black outline-none transition-all"
/>
{renderDropdown('province')}
-
+
handleSearch(e.target.value, 'zipcode')}
onFocus={() => { setFocusedField('zipcode'); handleSearch(value.zipcode, 'zipcode'); }}
onBlur={() => setTimeout(() => { if (focusedField === 'zipcode') setFocusedField(null); }, 200)}
- className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-spa-forest outline-none transition-all"
+ className="w-full bg-pop-bg border border-pop-black/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-pop-black outline-none transition-all"
/>
{renderDropdown('zipcode')}
diff --git a/apps/spa/src/index.css b/apps/spa/src/index.css
index 61ba6ed..a520f24 100644
--- a/apps/spa/src/index.css
+++ b/apps/spa/src/index.css
@@ -99,11 +99,11 @@
}
.ui-section-subtitle {
- @apply mx-auto max-w-2xl text-spa-stone;
+ @apply mx-auto max-w-2xl text-pop-black/60;
}
.ui-shell-card {
- @apply rounded-3xl border border-spa-stone/10 bg-spa-light shadow-brutal transition-all duration-300;
+ @apply rounded-3xl border border-pop-black/10 bg-night-900 shadow-brutal transition-all duration-300;
}
.ui-shell-card-hover {
@@ -115,31 +115,40 @@
}
.ui-button-primary {
- @apply inline-flex items-center justify-center rounded-full bg-spa-forest px-6 py-3 text-xs font-bold uppercase tracking-widest text-spa-light transition-colors hover:bg-spa-forest/90;
+ @apply inline-flex items-center justify-center rounded-full bg-pop-pink px-6 py-3 text-xs font-bold uppercase tracking-widest text-pop-bg transition-colors hover:opacity-90;
}
.ui-button-secondary {
- @apply inline-flex items-center justify-center rounded-full bg-spa-light px-6 py-3 text-xs font-bold uppercase tracking-widest text-spa-forest transition-colors hover:bg-spa-sage;
+ @apply inline-flex items-center justify-center rounded-full bg-pop-bg px-6 py-3 text-xs font-bold uppercase tracking-widest text-pop-black transition-colors hover:bg-pop-black/5;
}
.ui-input-shell {
- @apply w-full rounded-full border border-spa-light bg-spa-light px-6 py-4 text-sm text-spa-light outline-none transition placeholder:text-spa-light focus:border-spa-gold/80;
+ @apply w-full rounded-full border border-pop-black/20 bg-transparent px-6 py-4 text-sm text-pop-black outline-none transition placeholder:text-pop-black/50 focus:border-pop-pink;
}
.ui-muted-text {
- @apply text-spa-stone/70;
+ @apply text-pop-black/70;
}
.ui-hero-shell {
- @apply relative flex min-h-[90vh] items-center justify-center overflow-hidden bg-night-900 text-spa-light md:aspect-[21/9] md:min-h-0;
+ @apply relative flex min-h-[90vh] items-center justify-center overflow-hidden bg-night-900 text-pop-bg md:aspect-[21/9] md:min-h-0;
}
.ui-hero-overlay {
- @apply absolute inset-0 bg-neutral-900/40;
+ position: absolute;
+ inset: 0;
+ background: color-mix(in srgb, var(--color-pop-bg) 40%, transparent);
}
.ui-hero-overlay-strong {
- @apply absolute inset-0 bg-gradient-to-t from-neutral-900/90 via-neutral-900/10 to-neutral-900/30;
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(
+ to top,
+ color-mix(in srgb, var(--color-pop-bg) 90%, transparent),
+ color-mix(in srgb, var(--color-pop-bg) 10%, transparent),
+ color-mix(in srgb, var(--color-pop-bg) 30%, transparent)
+ );
}
.ui-hero-content {
@@ -147,11 +156,13 @@
}
.ui-hero-title {
- @apply font-serif text-5xl font-light leading-tight tracking-wide text-spa-light drop-shadow-md md:text-7xl lg:text-8xl;
+ @apply font-serif text-5xl font-light leading-tight tracking-wide drop-shadow-md md:text-7xl lg:text-8xl;
+ color: var(--color-pop-black);
}
.ui-hero-copy {
- @apply mx-auto max-w-2xl pb-8 pt-4 text-lg font-light text-spa-light drop-shadow-md md:text-xl;
+ @apply mx-auto max-w-2xl pb-8 pt-4 text-lg font-light drop-shadow-md md:text-xl;
+ color: var(--color-pop-black);
}
.ui-banner-shell {
@@ -159,39 +170,39 @@
}
.ui-banner-overlay {
- @apply absolute inset-0 bg-gradient-to-r from-spa-forest/80 via-spa-forest/40 to-transparent;
+ @apply absolute inset-0 bg-gradient-to-r from-pop-black/80 via-pop-black/40 to-transparent;
}
.ui-banner-content {
- @apply relative z-10 max-w-xl space-y-4 p-8 text-spa-light md:p-16;
+ @apply relative z-10 max-w-xl space-y-4 p-8 text-pop-bg md:p-16;
}
.ui-card-soft {
- @apply rounded-2xl border border-spa-stone/10 bg-spa-light shadow-sm;
+ @apply rounded-2xl border border-pop-black/10 bg-night-900 shadow-sm;
}
.ui-dark-shell {
- @apply bg-spa-forest px-6 py-20 text-spa-light;
+ @apply bg-pop-black px-6 py-20 text-pop-bg;
}
.ui-dark-card {
- @apply overflow-hidden rounded-3xl border border-spa-light bg-spa-light;
+ @apply overflow-hidden rounded-3xl border border-pop-black/10 bg-night-900;
}
.ui-dark-card-title {
- @apply text-xl font-serif font-light tracking-wide text-spa-light md:text-2xl;
+ @apply text-xl font-serif font-light tracking-wide text-pop-black md:text-2xl;
}
.ui-dark-card-copy {
- @apply text-sm font-light leading-relaxed text-spa-light/70;
+ @apply text-sm font-light leading-relaxed text-pop-black/70;
}
.ui-dark-link {
- @apply inline-flex items-center gap-2 text-xs font-bold uppercase tracking-widest text-spa-gold transition-colors hover:text-spa-light;
+ @apply inline-flex items-center gap-2 text-xs font-bold uppercase tracking-widest text-pop-pink transition-colors hover:text-pop-black;
}
.brutal-box {
- @apply bg-spa-light border border-spa-stone/20 rounded-2xl shadow-brutal transition-all duration-300;
+ @apply bg-night-900 border border-pop-black/20 rounded-2xl shadow-brutal transition-all duration-300;
}
.brutal-box-hover {
@@ -199,11 +210,11 @@
}
.brutal-button {
- @apply px-8 py-4 bg-pop-pink text-spa-light font-medium rounded-full shadow-brutal transition-all duration-300 hover:bg-pop-red hover:-translate-y-1 hover:shadow-brutal-hover;
+ @apply px-8 py-4 bg-pop-pink text-pop-bg font-medium rounded-full shadow-brutal transition-all duration-300 hover:opacity-90 hover:-translate-y-1 hover:shadow-brutal-hover;
}
.brutal-button-alt {
- @apply px-8 py-4 bg-spa-light text-pop-pink font-medium rounded-full border border-pop-pink shadow-brutal transition-all duration-300 hover:bg-pop-bg hover:-translate-y-1 hover:shadow-brutal-hover;
+ @apply px-8 py-4 bg-transparent text-pop-pink font-medium rounded-full border border-pop-pink shadow-brutal transition-all duration-300 hover:bg-pop-pink hover:text-pop-bg hover:-translate-y-1 hover:shadow-brutal-hover;
}
}
@@ -217,11 +228,11 @@
}
::-webkit-scrollbar-thumb {
- @apply bg-spa-sage rounded-full;
+ @apply bg-pop-black/30 rounded-full;
}
::-webkit-scrollbar-thumb:hover {
- @apply bg-spa-sage;
+ @apply bg-pop-black/50;
}
/* Smooth Scrolling */
@@ -243,3 +254,28 @@ html {
:lang(th) .font-serif {
font-family: 'Prompt', sans-serif !important;
}
+
+.ui-prose p {
+ @apply mb-4 leading-relaxed;
+}
+.ui-prose p:last-child {
+ @apply mb-0;
+}
+.ui-prose ul {
+ @apply list-inside list-disc mb-4 space-y-1;
+}
+.ui-prose ol {
+ @apply list-inside list-decimal mb-4 space-y-1;
+}
+.ui-prose h1, .ui-prose h2, .ui-prose h3, .ui-prose h4 {
+ @apply font-serif font-semibold mt-8 mb-4 text-pop-black;
+}
+.ui-prose a {
+ @apply text-pop-pink underline hover:opacity-80 transition-opacity;
+}
+.ui-prose strong {
+ @apply font-bold;
+}
+.ui-prose blockquote {
+ @apply border-l-4 border-pop-pink pl-4 italic opacity-80 my-4;
+}
diff --git a/apps/spa/src/pages/CustomPage.tsx b/apps/spa/src/pages/CustomPage.tsx
index ed63ec8..3d3e82f 100644
--- a/apps/spa/src/pages/CustomPage.tsx
+++ b/apps/spa/src/pages/CustomPage.tsx
@@ -65,7 +65,7 @@ export default function CustomPage() {
}, [slug, i18n.language]);
return (
-
+
{announcementSections.map((section) => (
-
+
) : error ? (
diff --git a/apps/website/src/app/globals.css b/apps/website/src/app/globals.css
index 4452b34..4908268 100644
--- a/apps/website/src/app/globals.css
+++ b/apps/website/src/app/globals.css
@@ -123,7 +123,13 @@
}
.ui-button-line {
- @apply inline-flex items-center justify-center rounded-full bg-line-primary px-4 py-2 text-xs font-bold uppercase tracking-widest text-surface-card shadow-sm transition-colors hover:bg-line-primary-hover;
+ @apply inline-flex items-center justify-center rounded-full px-4 py-2 text-xs font-bold uppercase tracking-widest shadow-sm transition-colors;
+ background-color: #00B900;
+ color: #ffffff;
+ }
+
+ .ui-button-line:hover {
+ background-color: #009900;
}
.ui-icon-button {
@@ -214,14 +220,14 @@
align-items: center;
justify-content: center;
overflow: hidden;
- background: var(--color-text-main);
- color: var(--color-surface-card);
+ background: var(--color-surface-page);
+ color: var(--color-text-main);
}
.ui-hero-overlay {
position: absolute;
inset: 0;
- background: color-mix(in srgb, var(--color-text-main) 40%, transparent);
+ background: color-mix(in srgb, var(--color-surface-page) 40%, transparent);
}
.ui-hero-overlay-strong {
@@ -229,14 +235,14 @@
inset: 0;
background: linear-gradient(
to top,
- color-mix(in srgb, var(--color-text-main) 90%, transparent),
- color-mix(in srgb, var(--color-text-main) 10%, transparent),
- color-mix(in srgb, var(--color-text-main) 30%, transparent)
+ color-mix(in srgb, var(--color-surface-page) 90%, transparent),
+ color-mix(in srgb, var(--color-surface-page) 10%, transparent),
+ color-mix(in srgb, var(--color-surface-page) 30%, transparent)
);
}
.ui-hero-title {
- color: var(--color-surface-card);
+ color: var(--color-text-main);
font-family: var(--font-display-locale);
font-size: 3rem;
font-weight: 300;
@@ -250,7 +256,7 @@
max-width: 42rem;
padding-top: 1rem;
padding-bottom: 2rem;
- color: rgb(255 255 255 / 0.9);
+ color: color-mix(in srgb, var(--color-text-main) 90%, transparent);
font-size: 1.125rem;
font-weight: 300;
line-height: 1.75rem;
@@ -258,28 +264,18 @@
}
.ui-hero-button {
- border-radius: 9999px;
- background: var(--color-surface-card);
- padding: 1rem 2rem;
- color: var(--color-text-main);
- font-size: 0.875rem;
- font-weight: 700;
- letter-spacing: 0.1em;
- text-transform: uppercase;
- transition: background-color 300ms ease, box-shadow 300ms ease;
+ @apply inline-flex items-center justify-center rounded-full bg-[#FDFDF9] px-6 py-3 text-xs font-bold uppercase tracking-widest text-[#1F3D2D] transition-colors hover:bg-[#EAEFEA];
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}
.ui-hero-link {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- padding: 1rem 2rem;
- color: var(--color-surface-card);
- font-size: 0.875rem;
- font-weight: 500;
- letter-spacing: 0.1em;
- text-transform: uppercase;
- transition: color 150ms ease;
+ @apply inline-flex items-center gap-2 text-xs font-bold uppercase tracking-widest transition-colors;
+ color: #D4AF37;
+ padding: 0.75rem 1.5rem;
+ }
+
+ .ui-hero-link:hover {
+ color: #FDFDF9;
}
.ui-banner-shell {
diff --git a/apps/website/src/components/ProductCard.tsx b/apps/website/src/components/ProductCard.tsx
index 4bd356f..2c2c739 100644
--- a/apps/website/src/components/ProductCard.tsx
+++ b/apps/website/src/components/ProductCard.tsx
@@ -1,11 +1,14 @@
"use client"
+import { useState } from 'react'
import Image from 'next/image'
import Link from './LocalizedLink'
+import { ShoppingBag } from 'lucide-react'
import type { Product } from '@homaroma/types'
import { getCategoryImage, formatPrice } from '@homaroma/types'
import { resolveCategoryName, resolveLocalized } from '../lib/localization'
import { useLang } from '../hooks/useLang'
+import { useCartStore } from '../store/cartStore'
import WishlistButton from './WishlistButton'
interface ProductCardProps {
@@ -14,14 +17,27 @@ interface ProductCardProps {
export default function ProductCard({ product }: ProductCardProps) {
const lang = useLang()
+ const { addToCart } = useCartStore()
+ const [selectedVariant, setSelectedVariant] = useState(product.variants?.[0] || null)
+
const categoryName = resolveCategoryName(product.category, lang)
const productName = resolveLocalized(product.name, lang) || product.scent.name
const imageUrl = product.images?.[0]?.url || product.category.imageUrl || getCategoryImage(categoryName)
- const firstPrice = product.variants[0]?.price ?? product.price
+ const displayPrice = selectedVariant?.price ?? product.variants[0]?.price ?? product.price
+
+ const addToCartLabels: Record
= {
+ th: 'เพิ่มลงตะกร้า',
+ en: 'Add to Cart',
+ ja: 'カートに追加',
+ zh: '加入购物车',
+ vi: 'Thêm vào giỏ',
+ ko: '장바구니에 담기'
+ }
+ const addToCartText = addToCartLabels[lang] || addToCartLabels.en
return (
-
-
+
+
{categoryName && (
-
+
{categoryName}
)}
-