const { useState, useEffect, useRef } = React; /* =========================================================== RESPONSIVE — viewport hook =========================================================== */ function useMediaQuery(query) { const [matches, setMatches] = useState(() => typeof window !== "undefined" ? window.matchMedia(query).matches : false ); useEffect(() => { const mql = window.matchMedia(query); const handler = (e) => setMatches(e.matches); setMatches(mql.matches); mql.addEventListener ? mql.addEventListener("change", handler) : mql.addListener(handler); return () => { mql.removeEventListener ? mql.removeEventListener("change", handler) : mql.removeListener(handler); }; }, [query]); return matches; } const MOBILE = "(max-width: 768px)"; const TABLET = "(max-width: 1080px)"; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "palette": ["#f7f0e2", "#2a1810", "#c1813c"], "showTraditional": true }/*EDITMODE-END*/; /* =========================================================== DATA =========================================================== */ const PRODUCTS = [ { id: 1, cat: "viennoiserie", name: "Croissant au beurre", subtitle: "feuilleté 72h", desc: "Pâte feuilletée pliée à la main, beurre fermier, sortie à 6h du matin.", price: 1.8, weight: "85g", tag: "Signature", img: "assets/croissants.jpg", color: "#caa86a" }, { id: 2, cat: "viennoiserie", name: "Pain au chocolat", subtitle: "double barre noire", desc: "Deux barres de chocolat noir 70%, feuilletage doré à cœur.", price: 2.2, weight: "95g", img: null, color: "#7a4a2a" }, { id: 3, cat: "viennoiserie", name: "Brioche pistache", subtitle: "filée à la main", desc: "Brioche tressée, crème de pistache, sucre perlé.", price: 3.5, weight: "180g", tag: "Nouveau", img: null, color: "#a8c478" }, { id: 4, cat: "sale", name: "Pâté thon", subtitle: "harissa douce, olives", desc: "Thon Méditerranée, œuf, harissa et olives — tout maison.", price: 3.5, weight: "200g", img: null, color: "#c4a978" }, { id: 5, cat: "sale", name: "Pâté fromage", subtitle: "triple fromage", desc: "Emmental, mozzarella, feta. Herbes du jardin, pâte brisée maison.", price: 3.2, weight: "200g", img: null, color: "#e8d49a" }, { id: 6, cat: "sale", name: "Pâté poulet", subtitle: "champignons & béchamel", desc: "Poulet rôti effiloché, champignons de Paris, béchamel.", price: 4.0, weight: "220g", img: null, color: "#b88a5a" }, { id: 7, cat: "oriental", name: "Mhalbiya", subtitle: "amandes & miel", desc: "Rouleaux croustillants garnis d'amandes pilées, parfumés au miel d'oranger.", price: 5.5, weight: "150g", tag: "Tradition", img: "assets/mhalbiya.jpg", color: "#b89858" }, { id: 8, cat: "oriental", name: "Deblas", subtitle: "feuilles d'or au miel", desc: "Pâte fine roulée en rose, frite, trempée dans le miel, parsemée de sésame.", price: 4.5, weight: "120g", tag: "Maison", img: "assets/deblas.jpg", color: "#d9a958" }, { id: 9, cat: "oriental", name: "Zlabia", subtitle: "miel & sésame doré", desc: "Spirales croustillantes au miel d'oranger, sésame doré.", price: 4.0, weight: "130g", img: "assets/zlabia.jpg", color: "#c98b3a" }, ]; const CATS = [ { id: "all", label: "Toute la carte" }, { id: "viennoiserie", label: "Viennoiseries" }, { id: "sale", label: "Salés" }, { id: "oriental", label: "Pâtisseries orientales" }, ]; /* =========================================================== ICONS — minimal hairline =========================================================== */ const Icon = { arrow: (p) => , arrowUR: (p) => , plus: (p) => , bag: (p) => , close: (p) => , menu: (p) => , pin: (p) => , clock: (p) => , phone: (p) => , insta: (p) => , fb: (p) => , wa: (p) => , }; /* =========================================================== ORNAMENT — small decorative SVG flourish =========================================================== */ function Flourish({ color = "currentColor", width = 96 }) { return ( ); } /* =========================================================== BRAND =========================================================== */ function CroissantMark({ size = 26, color = "currentColor" }) { return ( ); } function Brandmark({ size = "md", color }) { const c = color || "var(--ink)"; const fs = size === "lg" ? 32 : size === "sm" ? 16 : 22; return (
PASTICCINI
); } /* =========================================================== TOP BAR =========================================================== */ function TopBar() { const mob = useMediaQuery(MOBILE); return (
Ouvert · 06h – 20h {!mob && ·} {!mob && Livraison à Bizerte centre}
{!mob && (
« Pétri à la main, cuit chaque matin »
)}
72 432 100 {!mob && ( @pasticcini )}
); } /* =========================================================== NAV — centered, elegant =========================================================== */ function Nav({ cartCount, onCart, onNav }) { const [scrolled, setScrolled] = useState(false); const [menuOpen, setMenuOpen] = useState(false); const mob = useMediaQuery(MOBILE); useEffect(() => { const f = () => setScrolled(window.scrollY > 60); window.addEventListener("scroll", f); return () => window.removeEventListener("scroll", f); }, []); const left = [["La boutique", "shop"], ["Spécialités", "specialty"]]; const right = [["La maison", "about"], ["Nous trouver", "contact"]]; const all = [...left, ...right]; const go = (id) => { setMenuOpen(false); onNav(id); }; const linkStyle = { fontSize: 13, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--ink-2)" }; const iconBtn = { background: "transparent", border: "1px solid var(--line)", borderRadius: 999, width: 42, height: 42, display: "flex", alignItems: "center", justifyContent: "center", }; return (
{mob ? (
{ e.preventDefault(); go("top"); }}>
) : (
{ e.preventDefault(); onNav("top"); }}>
)} {mob && menuOpen && ( )}
); } /* =========================================================== HERO =========================================================== */ function Hero({ onNav }) { const mob = useMediaQuery(MOBILE); return (
{/* LEFT — TEXT */}
Pâtisserie · Bizerte · depuis 2018

L'art doux
du croissant,
du miel et de la patience.

Une maison de pâtisserie au cœur de Bizerte, où le feuilletage français rencontre la générosité tunisienne. Tout est fait à la main, cuit du jour, jamais conservé.

{/* TRUST */}
{[ ["72h", "de feuilletage"], ["100%", "fait maison"], ["4.9★", "312 avis Google"], ].map(([n, l], i) => (
{n}
{l}
))}
{/* RIGHT — PHOTO COMPOSITION */}
{/* Background frame */}
{/* Main photo */}
Croissants Pasticcini
{/* small photo overlay */}
Mhalbiya
{/* circular badge */}
fournée du
matin
à 6 heures
); } /* =========================================================== MARQUEE — italic caramel band =========================================================== */ function Marquee() { const mob = useMediaQuery(MOBILE); const items = ["Croissant au beurre", "Pâté thon harissa", "Mhalbiya aux amandes", "Brioche pistache", "Deblas au miel", "Pain au chocolat", "Zlabia sésame", "Pâté poulet"]; return (
{Array(3).fill(0).map((_, i) => (
{items.map((x, j) => ( {x} ))}
))}
); } /* =========================================================== PRODUCT CARD =========================================================== */ function ProductCard({ p, onAdd, size = "md" }) { const [hover, setHover] = useState(false); const aspect = size === "tall" ? "3/4" : size === "wide" ? "5/4" : "4/5"; return (
setHover(true)} onMouseLeave={() => setHover(false)} style={{ position: "relative" }} >
{p.img ? ( {p.name} ) : ( <>
{p.name}
douceur maison · photo à venir
)} {p.tag && (
{p.tag}
)} {/* hover add button */}

{p.name}

{p.subtitle}

{p.price.toFixed(1)} DT
); } /* =========================================================== CATALOGUE =========================================================== */ function Catalogue({ onAdd }) { const [filter, setFilter] = useState("all"); const mob = useMediaQuery(MOBILE); const tab = useMediaQuery(TABLET); const filtered = filter === "all" ? PRODUCTS : PRODUCTS.filter(p => p.cat === filter); return (
La carte

Notre vitrine du jour.

Neuf gourmandises, préparées chaque matin avant l'ouverture.

{/* FILTER */}
{CATS.map(c => ( ))}
{filtered.map(p => )}
); } /* =========================================================== SPECIALTIES — Tunisian sweets feature =========================================================== */ function Specialty({ onAdd }) { const mob = useMediaQuery(MOBILE); const tab = useMediaQuery(TABLET); const specs = PRODUCTS.filter(p => p.cat === "oriental"); return (
Spécialités tunisiennes

Les douceurs de la maison.

Recettes héritées des grands-mères de Bizerte. Le miel d'oranger, le sésame doré et les amandes pilées, dans un feuilleté patiemment roulé.

{specs.map((p, i) => ( ))}
); } function SpecCard({ p, idx, onAdd }) { const [hover, setHover] = useState(false); return (
setHover(true)} onMouseLeave={() => setHover(false)} style={{ position: "relative" }} >
{p.img && ( {p.name} )}
n° {String(idx + 1).padStart(2, "0")}

{p.name}

{p.subtitle}

{p.desc}

{p.price.toFixed(1)} DT / {p.weight}
); } /* =========================================================== ABOUT =========================================================== */ function About() { const mob = useMediaQuery(MOBILE); return (
{/* PHOTO COMPOSITION */}
Deblas
Zlabia
{/* italic caption */}
Dans notre atelier de Bizerte, juste avant l'aube.
{/* TEXT */}
La maison

Une famille,
un atelier,
une seule façon de faire.

Pasticcini est né en 2018 dans une cuisine modeste, à deux pas du Vieux Port. La maison a grandi sans jamais changer de méthode : tout est encore pétri à la main, le feuilletage repose toute la nuit, et chaque pièce est cuite le matin même.

Les viennoiseries empruntent à la rigueur française. Les pâtés rendent hommage à la générosité bizertine. Et nos pâtisseries orientales — mhalbiya, deblas, zlabia — viennent directement des cahiers de recettes de nos grands-mères.

« Le bon goût ne se presse pas. Il se mérite — au four, et au cœur. »
— Aïcha, chef pâtissière
); } /* =========================================================== TESTIMONIALS =========================================================== */ function Testimonials() { const reviews = [ { name: "Yasmine B.", from: "Bizerte", stars: 5, text: "Le seul endroit à Bizerte où je trouve un vrai croissant feuilleté. La pâte est aérienne, le beurre se sent à chaque bouchée." }, { name: "Mohamed K.", from: "Tunis", stars: 5, text: "Je fais une heure de route chaque samedi pour leurs pâtés thon et leurs mhalbiya. Indétrônables." }, { name: "Sophie L.", from: "Marseille", stars: 5, text: "On dirait un atelier de pâtisserie parisien posé en bord de Méditerranée. Un service adorable, une vitrine envoûtante." }, ]; const mob = useMediaQuery(MOBILE); const tab = useMediaQuery(TABLET); return (
Témoignages

On en parle autour du four.

{[1,2,3,4,5].map(i => )} 4.9/5 · 312 avis Google
{reviews.map((r, i) => (
"
{Array(r.stars).fill(0).map((_, j) => )}

{r.text}


{r.name[0]}
{r.name}
{r.from}
))}
); } /* =========================================================== CONTACT =========================================================== */ function Contact() { const mob = useMediaQuery(MOBILE); const [form, setForm] = useState({ name: "", contact: "", topic: "Commande sur mesure", message: "" }); const [sent, setSent] = useState(false); const submit = (e) => { e.preventDefault(); setSent(true); setTimeout(() => { setSent(false); setForm({ name: "", contact: "", topic: "Commande sur mesure", message: "" }); }, 3500); }; return (
Visite & contact

On vous attend à Bizerte.

{/* LEFT — info + map */}
{[ { icon: Icon.pin, h: "Adresse", l1: "12 Avenue Habib Bourguiba", l2: "Bizerte 7000 · Tunisie" }, { icon: Icon.clock, h: "Horaires", l1: "Lundi – Samedi · 06h00 – 20h00", l2: "Dimanche · 07h00 – 13h00" }, { icon: Icon.phone, h: "Téléphone & WhatsApp", l1: "+216 72 432 100", l2: "Disponible toute la journée" }, ].map((c, i) => (
0 ? "1px solid var(--line)" : "none", }}>
{c.h}
{c.l1}
{c.l2}
))}
{/* MAP */}
{/* roads */} {/* blocks */} {[[40,230],[80,260],[180,240],[220,260],[340,230],[380,265],[480,240],[520,260], [40,330],[120,330],[240,330],[360,330],[460,330],[540,330]].map(([x,y],i)=>( ))} {/* pin */} Pasticcini 12 AV. BOURGUIBA Ouvrir dans Maps
{/* RIGHT — FORM */}

Une commande spéciale?

Mariage, baptême, anniversaire, événement d'entreprise — dites-nous tout, nous revenons sous 24h.

Sujet
{["Commande sur mesure", "Mariage", "Événement", "Question"].map(t => ( ))}
setForm({...form, name: v})} placeholder="Aïcha Ben Salem" /> setForm({...form, contact: v})} placeholder="vous@exemple.tn" /> setForm({...form, message: v})} placeholder="Décrivez-nous votre projet…" />
{[ { Ic: Icon.wa, l: "WhatsApp", href: "https://wa.me/21672432100" }, { Ic: Icon.insta, l: "Instagram", href: "https://instagram.com/pasticcini" }, { Ic: Icon.fb, l: "Facebook", href: "#" }, ].map((s, i) => ( {s.l} ))}
); } function Field({ label, value, onChange, placeholder, textarea }) { const In = textarea ? "textarea" : "input"; return (
{label}
onChange(e.target.value)} placeholder={placeholder} rows={textarea ? 4 : undefined} style={{ width: "100%", background: "transparent", border: "none", borderBottom: "1px solid var(--line)", padding: "10px 0", fontSize: 17, fontFamily: "inherit", color: "var(--ink)", outline: "none", resize: textarea ? "vertical" : "none", }} />
); } /* =========================================================== FOOTER =========================================================== */ function Footer() { const mob = useMediaQuery(MOBILE); return ( ); } /* =========================================================== CART DRAWER =========================================================== */ function CartDrawer({ open, items, onClose, onAdjust }) { const total = items.reduce((s, x) => s + x.p.price * x.q, 0); const count = items.reduce((s, x) => s + x.q, 0); if (!open) return null; return (
Panier
{count} {count <= 1 ? "article" : "articles"}
{items.length === 0 ? (
Votre panier est vide.
Ajoutez un croissant pour commencer la journée.
) : items.map(({ p, q }) => (
{p.img && {p.name}}
{p.name}
{p.subtitle}
{q}
{(p.price * q).toFixed(1)} DT
))}
{items.length > 0 && (
Total {total.toFixed(1)} DT
Commander par WhatsApp
Paiement à la boutique ou à la livraison
)}
); } const qBtn = { background: "var(--paper)", border: "1px solid var(--line)", borderRadius: "50%", width: 22, height: 22, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 13, lineHeight: 1, cursor: "pointer", padding: 0, color: "var(--ink)", }; /* =========================================================== APP =========================================================== */ function App() { const [cart, setCart] = useState([]); const [cartOpen, setCartOpen] = useState(false); const [toast, setToast] = useState(null); const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); useEffect(() => { const p = t.palette || TWEAK_DEFAULTS.palette; document.documentElement.style.setProperty("--bg", p[0]); document.documentElement.style.setProperty("--ink", p[1]); document.documentElement.style.setProperty("--caramel", p[2]); }, [t.palette]); const onAdd = (p) => { setCart(c => { const ex = c.find(x => x.p.id === p.id); if (ex) return c.map(x => x.p.id === p.id ? { ...x, q: x.q + 1 } : x); return [...c, { p, q: 1 }]; }); setToast(`✓ ${p.name} ajouté`); setTimeout(() => setToast(null), 1800); }; const onAdjust = (id, q) => { if (q <= 0) setCart(c => c.filter(x => x.p.id !== id)); else setCart(c => c.map(x => x.p.id === id ? { ...x, q } : x)); }; const onNav = (id) => { const el = document.getElementById(id); if (el) window.scrollTo({ top: id === "top" ? 0 : el.offsetTop - 80, behavior: "smooth" }); }; const count = cart.reduce((s, x) => s + x.q, 0); return ( <>