Refine contact and trust UI

This commit is contained in:
2026-05-16 14:07:03 +01:00
parent 256c6ffb97
commit 4d3d9fd8d6
6 changed files with 24 additions and 21 deletions

View File

@@ -24,6 +24,7 @@ All notable changes to the New Optic website will be documented in this file.
### Changed
- Refined contact and trust UI: stat labels are now accented above values, French WhatsApp CTA says "Discuter sur WhatsApp", hours information is emphasized, and mobile navbar shows WhatsApp without opening the menu.
- Updated customer-corrected business facts: New Optic is active since around 2004, does not offer eye exams, and offers contact lenses plus colored contact lenses.
- Optimized mobile rendering by disabling heavy blur, fixed backgrounds, ambient animations, and WebGL glass below desktop widths.
- Changed mobile CSS to reference only the compressed mobile background by default, keeping the full PNG desktop-only.

View File

@@ -29,7 +29,7 @@ export default function ContactSection({ t, whatsappUrl }: { t: Messages; whatsa
<Info label={t.contact.phoneLabel} value={business.phone} icon={<Phone size={18} />} />
<Info label={t.contact.whatsappLabel} value={business.whatsapp} icon={<Phone size={18} />} />
<Info label={t.contact.addressLabel} value={business.displayAddress} icon={<MapPin size={18} />} wide />
<Info label={t.contact.hoursLabel} value={t.contact.hoursValue} icon={<QrCode size={18} />} />
<Info label={t.contact.hoursLabel} value={t.contact.hoursValue} icon={<QrCode size={18} />} wide accent />
</div>
</div>
@@ -48,14 +48,14 @@ export default function ContactSection({ t, whatsappUrl }: { t: Messages; whatsa
);
}
function Info({ label, value, icon, wide = false }: { label: string; value: string; icon: React.ReactNode; wide?: boolean }) {
function Info({ label, value, icon, wide = false, accent = false }: { label: string; value: string; icon: React.ReactNode; wide?: boolean; accent?: boolean }) {
return (
<div className={`rounded-[1.6rem] border border-ink/8 bg-smoke/80 p-5 ${wide ? "sm:col-span-2" : ""}`}>
<div className={`rounded-[1.6rem] border p-5 ${wide ? "sm:col-span-2" : ""} ${accent ? "border-optical/20 bg-optical/10 sm:p-6" : "border-ink/8 bg-smoke/80"}`}>
<div className="mb-3 flex items-center gap-2 text-optical">
{icon}
<p className="text-xs font-semibold uppercase tracking-[0.18em]">{label}</p>
</div>
<p className="text-sm font-semibold leading-7 text-ink/72">{value}</p>
<p className={`${accent ? "text-lg leading-8 text-ink sm:text-xl" : "text-sm leading-7 text-ink/72"} font-semibold`}>{value}</p>
</div>
);
}

View File

@@ -24,12 +24,12 @@ export default function Navbar({ locale, onLocaleChange, t, whatsappUrl }: { loc
return (
<header className="fixed inset-x-0 top-0 z-50 px-4 pt-4 sm:px-6">
<nav className="glass mx-auto flex max-w-7xl items-center justify-between rounded-full px-4 py-3 sm:px-5" aria-label="Main navigation">
<nav className="glass mx-auto flex max-w-7xl items-center justify-between gap-2 rounded-full px-3 py-3 sm:px-5" aria-label="Main navigation">
<a href="#home" onClick={(event) => scrollToSection(event, "#home")} className="relative z-[3] flex items-center gap-3" aria-label="New Optic home">
<span className="relative grid size-10 place-items-center overflow-hidden rounded-full bg-white shadow-sm">
<Image src={business.assets.logo} alt="New Optic logo" fill sizes="40px" className="object-contain p-1" priority />
</span>
<span className="text-sm font-semibold tracking-[-0.02em]">{business.name}</span>
<span className="hidden text-sm font-semibold tracking-[-0.02em] xs:inline sm:inline">{business.name}</span>
</a>
<div className="relative z-[3] hidden items-center gap-7 lg:flex">
@@ -47,9 +47,14 @@ export default function Navbar({ locale, onLocaleChange, t, whatsappUrl }: { loc
</PhysicsButton>
</div>
<button type="button" onClick={() => setOpen((value) => !value)} className="relative z-[3] grid size-11 place-items-center rounded-full bg-white/70 text-ink md:hidden" aria-label={t.nav.menu} aria-expanded={open}>
{open ? <X size={18} /> : <Menu size={18} />}
</button>
<div className="relative z-[3] flex items-center gap-2 md:hidden">
<PhysicsButton href={whatsappUrl} external className="rounded-full bg-ink px-4 py-2.5 text-xs font-semibold text-white shadow-soft transition-colors hover:bg-optical sm:px-5 sm:text-sm">
{t.nav.cta}
</PhysicsButton>
<button type="button" onClick={() => setOpen((value) => !value)} className="grid size-11 place-items-center rounded-full bg-white/70 text-ink" aria-label={t.nav.menu} aria-expanded={open}>
{open ? <X size={18} /> : <Menu size={18} />}
</button>
</div>
</nav>
<AnimatePresence>
@@ -70,9 +75,6 @@ export default function Navbar({ locale, onLocaleChange, t, whatsappUrl }: { loc
</div>
<div className="mt-4 flex items-center justify-between gap-3">
<LanguageSwitcher locale={locale} onLocaleChange={onLocaleChange} />
<PhysicsButton href={whatsappUrl} external className="rounded-full bg-ink px-5 py-3 text-sm font-semibold text-white">
{t.nav.cta}
</PhysicsButton>
</div>
</motion.div>
) : null}

View File

@@ -11,8 +11,8 @@ export default function TrustSection({ t }: { t: Messages }) {
<motion.div className="mt-12 grid gap-4 md:grid-cols-3" initial={false} whileInView="show" viewport={{ once: true, amount: 0.08 }} variants={{ show: { transition: { staggerChildren: 0.12 } } }}>
{t.trust.stats.map((stat) => (
<motion.div key={stat.value} variants={{ show: { opacity: 1, y: 0, scale: 1 } }} transition={{ duration: 0.65, ease: [0.22, 1, 0.36, 1] }} whileHover={{ y: -6 }} className="rounded-[2rem] border border-white/10 bg-white/[0.06] p-6 text-center backdrop-blur">
<p className="mb-4 text-xs font-semibold uppercase tracking-[0.22em] text-optical/90">{stat.label}</p>
<p className="text-5xl font-semibold tracking-[-0.06em] text-white">{stat.value}</p>
<p className="mt-3 text-sm leading-6 text-white/58">{stat.label}</p>
</motion.div>
))}
</motion.div>

View File

@@ -68,9 +68,9 @@ export const en = {
eyebrow: "Reputation",
title: "A neighborhood optician with lasting standards.",
stats: [
{ value: "2004", label: "local presence since around" },
{ value: "100%", label: "authentic frames" },
{ value: "360°", label: "guidance, lenses, frames and support" }
{ value: "2004", label: "Local presence since around" },
{ value: "100%", label: "Authentic frames" },
{ value: "360°", label: "Guidance, lenses, frames and support" }
],
body: "No exaggerated claims. New Optic earns trust through guidance, consistent service and a selection of authentic products."
},

View File

@@ -68,9 +68,9 @@ export const fr = {
eyebrow: "Reputation",
title: "Un opticien de quartier avec une exigence durable.",
stats: [
{ value: "2004", label: "presence locale depuis autour de" },
{ value: "100%", label: "montures originales" },
{ value: "360°", label: "conseil, verres, montures et suivi" }
{ value: "2004", label: "Présence locale depuis autour de" },
{ value: "100%", label: "Montures originales" },
{ value: "360°", label: "Conseil, verres, montures et suivi" }
],
body: "Pas de promesses exagérées. New Optic construit la relation par le conseil, la régularité du service et une sélection de produits authentiques."
},
@@ -78,7 +78,7 @@ export const fr = {
eyebrow: "Contact",
title: "Passez en magasin ou contactez-nous directement.",
body: "Une question sur vos verres, une reparation ou une nouvelle monture ? Envoyez un message WhatsApp, appelez, ou venez nous voir a Massira 1, Temara.",
whatsapp: "Ecrire sur WhatsApp",
whatsapp: "Discuter sur WhatsApp",
call: "Appeler maintenant",
visit: "Ouvrir l'itineraire",
qr: "Scannez pour ouvrir l'adresse",
@@ -86,7 +86,7 @@ export const fr = {
whatsappLabel: "WhatsApp",
addressLabel: "Adresse",
hoursLabel: "Horaires",
hoursValue: "Horaires non publies en ligne. Contactez le magasin avant de vous deplacer.",
hoursValue: "Horaires non publiés en ligne. Contactez le magasin avant de vous déplacer.",
whatsappMessage: "Bonjour New Optic, je souhaite avoir des informations sur vos lunettes et services."
},
footer: {