feat(themes): add Swiss Brutalist restaurant design system with chapter numbers and high-contrast grid

This commit is contained in:
AyrisAI
2026-08-20 03:35:44 +03:00
parent 83fa4236ea
commit de8bd506b8
5 changed files with 266 additions and 149 deletions
+12 -2
View File
@@ -155,14 +155,24 @@ export const restaurantsRoutes: FastifyPluginAsync = async (app) => {
return reply.code(400).send({ message: "themeKey is required" }); return reply.code(400).send({ message: "themeKey is required" });
} }
const THEME_NAMES: Record<string, string> = {
swiss: "Swiss Brutalist",
artisan: "Amber Artisan",
elegant: "Elegant Gold",
modern: "Modern Sapphire",
dark: "Luxury Dark",
minimal: "Nordic Minimal",
classic: "Classic Bistro",
};
let themeId = themeData?.id; let themeId = themeData?.id;
if (!themeId) { if (!themeId) {
const { data: newTheme } = await supabase const { data: newTheme } = await supabase
.from("themes") .from("themes")
.insert({ .insert({
key: themeKey, key: themeKey,
name: themeKey === "artisan" ? "Amber Artisan" : themeKey, name: THEME_NAMES[themeKey] || themeKey,
config: { primaryColor: "#E5A855", background: "#121316", productLayout: "card", categoryLayout: "tabs" }, config: { primaryColor: themeKey === "swiss" ? "#000000" : "#E5A855", background: themeKey === "swiss" ? "#FFFFFF" : "#121316", productLayout: "card", categoryLayout: "tabs" },
}) })
.select("id") .select("id")
.maybeSingle(); .maybeSingle();
@@ -31,6 +31,18 @@ export interface ThemeOption {
} }
export const THEME_OPTIONS: ThemeOption[] = [ export const THEME_OPTIONS: ThemeOption[] = [
{
key: "swiss",
name: "Swiss Brutalist",
category: "Modern Sanat & Konsept",
color: "#000000",
badgeBg: "#000000",
badgeText: "#FFFFFF",
bgPreview: "#FFFFFF",
accent: "Monokrom Siyah & Kobalt Mavi",
desc: "Brutalist İsviçre tipografisi, 8px kalın siyah kenarlıklar, bölüm numaraları (Ø1, Ø2) ve monokrom ızgara.",
suitableFor: "Çağdaş restoranlar, sanat galerisi kafeleri, avangard mutfaklar, konsept bistrolar",
},
{ {
key: "artisan", key: "artisan",
name: "Amber Artisan", name: "Amber Artisan",
+85 -9
View File
@@ -315,7 +315,7 @@ export function PublicMenuClient({
<div className="bg-stone-900 text-white px-5 py-4 border-b border-stone-800 animate-slide-up"> <div className="bg-stone-900 text-white px-5 py-4 border-b border-stone-800 animate-slide-up">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<p className="text-xs font-bold uppercase tracking-wider text-stone-400"> <p className="text-xs font-bold uppercase tracking-wider text-stone-400">
Canlı Tema Önizleme (6 Şablon) Canlı Tema Önizleme (7 Şablon)
</p> </p>
<button <button
type="button" type="button"
@@ -325,7 +325,7 @@ export function PublicMenuClient({
Kapat Kapat
</button> </button>
</div> </div>
<div className="grid grid-cols-3 sm:grid-cols-6 gap-2"> <div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-7 gap-2">
{Object.values(THEME_PRESETS).map((preset) => { {Object.values(THEME_PRESETS).map((preset) => {
const isSelected = selectedThemeKey === preset.key; const isSelected = selectedThemeKey === preset.key;
return ( return (
@@ -422,13 +422,37 @@ export function PublicMenuClient({
</button> </button>
</div> </div>
) : ( ) : (
filteredCategories.map((category) => ( filteredCategories.map((category, catIndex) => {
const chapterNum = String(catIndex + 1).padStart(2, "0");
const isSwiss = theme.key === "swiss";
return (
<section <section
key={category.id} key={category.id}
id={`category-${category.id}`} id={`category-${category.id}`}
className="scroll-mt-28 space-y-3.5" className={isSwiss ? "scroll-mt-28 space-y-0" : "scroll-mt-28 space-y-3.5"}
> >
{/* Sticky Category Heading Banner */} {/* Category Heading Banner */}
{isSwiss ? (
<div className="sticky top-[58px] z-20 bg-white border-t-8 border-b-4 border-black py-4 px-3 flex items-end justify-between transition-all">
<div>
<span className="text-[11px] font-black uppercase tracking-widest text-[#1D4ED8] block mb-1">
CHAPTER {chapterNum} // SECTION
</span>
<h2 className="text-2xl sm:text-3xl font-black uppercase tracking-tighter text-black leading-none">
{category.name}
</h2>
{category.description && (
<p className="text-xs text-stone-600 mt-1 font-medium line-clamp-1">
{category.description}
</p>
)}
</div>
<div className="text-4xl sm:text-5xl font-black italic text-[#1D4ED8] leading-none tracking-tighter select-none">
Ø{catIndex + 1}
</div>
</div>
) : (
<div <div
className="sticky top-[58px] z-20 flex items-center justify-between py-2.5 px-3 -mx-3 rounded-2xl backdrop-blur-xl border-b transition-all shadow-sm" className="sticky top-[58px] z-20 flex items-center justify-between py-2.5 px-3 -mx-3 rounded-2xl backdrop-blur-xl border-b transition-all shadow-sm"
style={{ style={{
@@ -456,19 +480,70 @@ export function PublicMenuClient({
{category.menu_items.length} Ürün {category.menu_items.length} Ürün
</span> </span>
</div> </div>
)}
{/* Items Container: Grid / Card / List Layout based on Theme */}
{/* Items Container: Grid / Card / List Layout based on Theme */} {/* Items Container: Grid / Card / List Layout based on Theme */}
<div <div
className={ className={
theme.key === "artisan" isSwiss
? "divide-y-2 divide-black border-b-2 border-black"
: theme.key === "artisan"
? "grid grid-cols-2 gap-3 sm:gap-4" ? "grid grid-cols-2 gap-3 sm:gap-4"
: theme.config.productLayout === "list" : theme.config.productLayout === "list"
? "space-y-3" ? "space-y-3"
: "grid grid-cols-1 gap-3.5" : "grid grid-cols-1 gap-3.5"
} }
> >
{category.menu_items.map((item) => { {category.menu_items.map((item, itemIndex) => {
if (isSwiss) {
const itemCode = `${String.fromCharCode(65 + catIndex)}${String(itemIndex + 1).padStart(2, "0")}`;
return (
<article
key={item.id}
onClick={() => setSelectedItem(item)}
className="group bg-white hover:bg-stone-50 cursor-pointer flex flex-col sm:flex-row transition-all duration-200"
>
<div className="flex-1 p-4 flex flex-col justify-between">
<div>
<div className="flex items-center gap-2 mb-1.5">
<span className="text-xs font-black uppercase tracking-widest text-[#1D4ED8]">
[{itemCode}]
</span>
<h3 className="font-black text-lg sm:text-xl uppercase tracking-tighter text-black group-hover:text-[#1D4ED8] transition-colors leading-tight">
{item.name}
</h3>
</div>
{item.description && (
<p className="text-xs text-stone-600 leading-relaxed font-normal line-clamp-2">
{item.description}
</p>
)}
</div>
<div className="mt-4 pt-2 flex items-baseline justify-between border-t border-stone-200">
<span className="text-[10px] font-black uppercase tracking-widest text-stone-500">
[ORGANIC] [HOUSE-MADE]
</span>
<span className="text-xl sm:text-2xl font-black text-black tracking-tight">
{item.price.toFixed(0)}
</span>
</div>
</div>
{item.image_url && (
<div className="sm:w-36 sm:min-w-[9rem] aspect-square sm:aspect-auto border-t sm:border-t-0 sm:border-l-2 border-black bg-stone-100 overflow-hidden flex-shrink-0">
<img
src={item.image_url}
alt={item.name}
className="w-full h-full object-cover grayscale contrast-125 group-hover:scale-105 group-hover:grayscale-0 transition-all duration-300"
loading="lazy"
/>
</div>
)}
</article>
);
}
if (theme.key === "artisan") { if (theme.key === "artisan") {
return ( return (
<article <article
@@ -591,7 +666,8 @@ export function PublicMenuClient({
})} })}
</div> </div>
</section> </section>
)) );
})
)} )}
</main> </main>
+19
View File
@@ -16,6 +16,25 @@ export interface ThemePreset {
} }
export const THEME_PRESETS: Record<string, ThemePreset> = { export const THEME_PRESETS: Record<string, ThemePreset> = {
swiss: {
key: "swiss",
name: "Swiss Brutalist",
fontFamily: "heading",
config: {
primaryColor: "#000000",
background: "#FFFFFF",
productLayout: "card",
categoryLayout: "tabs",
},
cardBg: "#FFFFFF",
cardBorder: "#000000",
textPrimary: "#000000",
textSecondary: "#666666",
accentBg: "#F0F0F0",
headerGradient: "linear-gradient(180deg, #000000 0%, #111111 100%)",
badgeBg: "#000000",
badgeText: "#FFFFFF",
},
artisan: { artisan: {
key: "artisan", key: "artisan",
name: "Amber Artisan", name: "Amber Artisan",