diff --git a/apps/admin/src/pages/SystemSettingsPage.tsx b/apps/admin/src/pages/SystemSettingsPage.tsx index 63c1418..7a61001 100644 --- a/apps/admin/src/pages/SystemSettingsPage.tsx +++ b/apps/admin/src/pages/SystemSettingsPage.tsx @@ -17,12 +17,46 @@ const defaultThemeTokens = { mutedText: '#666666', linePrimary: '#00B900', linePrimaryHover: '#009900', - fontBody: 'system-ui, sans-serif', - fontDisplay: 'system-ui, sans-serif', + fontBody: '', + fontDisplay: '', shadowCard: '0 10px 25px rgba(0,0,0,0.05)', shadowCardHover: '0 20px 40px rgba(0,0,0,0.1)', } +const FONT_PRESETS = { + locale: { + label: 'Locale default', + body: '', + display: '', + }, + sarabun: { + label: 'Sarabun', + body: 'var(--font-sarabun, "Sarabun"), "Noto Sans Thai", ui-sans-serif, system-ui, sans-serif', + display: 'var(--font-sarabun, "Sarabun"), "Noto Sans Thai", ui-sans-serif, system-ui, sans-serif', + }, + kanit: { + label: 'Kanit', + body: 'var(--font-kanit, "Kanit"), "Noto Sans Thai", ui-sans-serif, system-ui, sans-serif', + display: 'var(--font-kanit, "Kanit"), "Noto Sans Thai", ui-sans-serif, system-ui, sans-serif', + }, +} as const + +type FontPresetKey = keyof typeof FONT_PRESETS + +function resolveFontPreset(themeTokens: Record) { + const body = themeTokens.fontBody?.trim() || '' + const display = themeTokens.fontDisplay?.trim() || '' + const fontBody = body || display + const fontDisplay = display || body + + if (!fontBody && !fontDisplay) return 'locale' + + if (fontBody === FONT_PRESETS.sarabun.body && fontDisplay === FONT_PRESETS.sarabun.display) return 'sarabun' + if (fontBody === FONT_PRESETS.kanit.body && fontDisplay === FONT_PRESETS.kanit.display) return 'kanit' + + return 'custom' +} + export default function SystemSettingsPage() { const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) @@ -49,6 +83,17 @@ export default function SystemSettingsPage() { const [footerSupportHeading, setFooterSupportHeading] = useState('') const [footerCopyrightText, setFooterCopyrightText] = useState('') const [themeTokens, setThemeTokens] = useState>(defaultThemeTokens) + const fontPreset = resolveFontPreset(themeTokens) + + const applyFontPreset = (preset: FontPresetKey | 'custom') => { + if (preset === 'custom') return + + setThemeTokens((current) => ({ + ...current, + fontBody: FONT_PRESETS[preset].body, + fontDisplay: FONT_PRESETS[preset].display, + })) + } useEffect(() => { fetch(`${API_BASE}/settings`) @@ -365,6 +410,25 @@ export default function SystemSettingsPage() {

Theme Tokens

+
+ + +

+ Select a ready-made font stack, or use custom overrides below when you need a special brand voice. +

+
+
{[ ['brandPrimary', 'Brand Primary'], @@ -391,8 +455,8 @@ export default function SystemSettingsPage() {
{[ - ['fontBody', 'Font Body'], - ['fontDisplay', 'Font Display'], + ['fontBody', 'Font Body Override'], + ['fontDisplay', 'Font Display Override'], ['shadowCard', 'Shadow Card'], ['shadowCardHover', 'Shadow Card Hover'], ].map(([key, label]) => ( @@ -403,6 +467,7 @@ export default function SystemSettingsPage() { value={themeTokens[key] || ''} onChange={e => setThemeTokens((current) => ({ ...current, [key]: e.target.value }))} className="admin-input w-full" + placeholder={key.startsWith('font') ? 'Leave blank to use locale preset' : undefined} />
))} diff --git a/apps/website/src/app/[lang]/layout.tsx b/apps/website/src/app/[lang]/layout.tsx index 235cfb3..49a4a10 100644 --- a/apps/website/src/app/[lang]/layout.tsx +++ b/apps/website/src/app/[lang]/layout.tsx @@ -1,4 +1,5 @@ import type { Metadata } from 'next'; +import { Kanit, Sarabun } from 'next/font/google'; import { notFound } from 'next/navigation'; import '../globals.css'; import Navbar from '@/components/Navbar'; @@ -10,6 +11,20 @@ import type { MenuItem, StoreSettings } from '@homaroma/types'; import { isSupportedLocale } from '@/lib/locales'; import type { CSSProperties } from 'react'; +const sarabun = Sarabun({ + subsets: ['latin', 'thai'], + display: 'swap', + weight: ['300', '400', '500', '600', '700'], + variable: '--font-sarabun', +}); + +const kanit = Kanit({ + subsets: ['latin', 'thai'], + display: 'swap', + weight: ['300', '400', '500', '600', '700'], + variable: '--font-kanit', +}); + export const metadata: Metadata = { title: 'HomAroma', description: 'Pure Origins, Artisan Craft, Zero Compromise', @@ -96,8 +111,8 @@ function buildThemeStyle(settings: StoreSettings | null): CSSProperties | undefi ['--color-muted-text', tokens.mutedText], ['--color-line-primary', tokens.linePrimary], ['--color-line-primary-hover', tokens.linePrimaryHover], - ['--font-body', tokens.fontBody], - ['--font-display', tokens.fontDisplay], + ['--font-body-active', tokens.fontBody], + ['--font-display-active', tokens.fontDisplay], ['--shadow-card', tokens.shadowCard], ['--shadow-card-hover', tokens.shadowCardHover], ] @@ -134,7 +149,7 @@ export default async function RootLayout({ const themeStyle = buildThemeStyle(settings) return ( - + diff --git a/apps/website/src/app/globals.css b/apps/website/src/app/globals.css index c55bc4d..e49276d 100644 --- a/apps/website/src/app/globals.css +++ b/apps/website/src/app/globals.css @@ -1,8 +1,8 @@ @import "tailwindcss"; @theme { - --font-body: var(--font-sarabun, ui-sans-serif), system-ui, sans-serif; - --font-display: var(--font-body); + --font-body: var(--font-body-active, var(--font-body-locale, ui-sans-serif)), system-ui, sans-serif; + --font-display: var(--font-display-active, var(--font-display-locale, var(--font-body-locale, ui-sans-serif))), system-ui, sans-serif; --font-sans: var(--font-body); --color-brand-primary: #FF6B6B; @@ -45,6 +45,37 @@ } @layer base { + :root, + html { + --font-body-locale: var(--font-sarabun, "Sarabun"), "Noto Sans Thai", ui-sans-serif, system-ui, sans-serif; + --font-display-locale: var(--font-body-locale); + } + + html[lang="en"] { + --font-body-locale: "Inter", "Segoe UI", ui-sans-serif, system-ui, sans-serif; + --font-display-locale: var(--font-body-locale); + } + + html[lang="ja"] { + --font-body-locale: "Hiragino Sans", "Yu Gothic", "Noto Sans JP", ui-sans-serif, system-ui, sans-serif; + --font-display-locale: var(--font-body-locale); + } + + html[lang="zh"] { + --font-body-locale: "PingFang SC", "Microsoft YaHei", "Noto Sans SC", ui-sans-serif, system-ui, sans-serif; + --font-display-locale: var(--font-body-locale); + } + + html[lang="ko"] { + --font-body-locale: "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", ui-sans-serif, system-ui, sans-serif; + --font-display-locale: var(--font-body-locale); + } + + html[lang="vi"] { + --font-body-locale: "Inter", "Segoe UI", ui-sans-serif, system-ui, sans-serif; + --font-display-locale: var(--font-body-locale); + } + body { @apply bg-surface-page text-text-main font-sans antialiased; background-image: radial-gradient(circle at top right, rgba(255,107,107,0.15), transparent 40%),