Compare commits
4 Commits
a5d9eed843
...
3D
| Author | SHA1 | Date | |
|---|---|---|---|
| 539437b824 | |||
| 6549b03d9a | |||
| 0ac37261a8 | |||
| ad070a21a5 |
@@ -6,9 +6,13 @@ All notable changes to the New Optic website will be documented in this file.
|
||||
|
||||
### Changed
|
||||
|
||||
- Added a New York-style display font stack for large latin headings while keeping SF-style UI text.
|
||||
- Switched the latin typography to an SF Pro-style system font stack for a more Apple-like feel.
|
||||
- Removed the desktop liquid glass WebGL layer from navbar and CTA controls so text remains readable and glass surfaces do not show dark rendering artifacts while scrolling.
|
||||
- Replaced the mobile hamburger dropdown with a fast, solid menu animation without bounce or stretch effects.
|
||||
- Kept the language switcher visible in the mobile top navigation bar.
|
||||
- Added cursor-reactive stretch motion to desktop nav links and matched the glossy dark WhatsApp CTA style on mobile.
|
||||
- Corrected the French 2004 trust wording to use "depuis" without "autour de".
|
||||
|
||||
## [1.0.0] - 2026-05-16
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
color-scheme: light;
|
||||
--bg: #f6f5f2;
|
||||
--ink: #111317;
|
||||
--font-sans: "SF Pro Text", "SF Pro Display", -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
--font-display: "New York Large", "New York", "NewYork", ui-serif, Georgia, Cambria, "Times New Roman", serif;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -24,7 +26,7 @@ body {
|
||||
url('/assets/New-optic-BG-mobile.webp') center top / cover scroll,
|
||||
linear-gradient(180deg, #fbfaf8 0%, #f6f5f2 42%, #ffffff 100%);
|
||||
color: var(--ink);
|
||||
font-family: var(--font-sans), system-ui, sans-serif;
|
||||
font-family: var(--font-sans);
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
@@ -67,6 +69,10 @@ body[dir="rtl"] {
|
||||
font-family: var(--font-arabic), system-ui, sans-serif;
|
||||
}
|
||||
|
||||
body:not([dir="rtl"]) :where(h1, h2) {
|
||||
font-family: var(--font-display);
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter, Noto_Kufi_Arabic } from "next/font/google";
|
||||
import { Noto_Kufi_Arabic } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { business } from "@/config/business";
|
||||
import { fr } from "@/messages/fr";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"], variable: "--font-sans", display: "swap" });
|
||||
const arabic = Noto_Kufi_Arabic({ subsets: ["arabic"], variable: "--font-arabic", display: "swap" });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -28,7 +27,7 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
||||
return (
|
||||
<html lang="fr" className={`${inter.variable} ${arabic.variable} scroll-smooth`}>
|
||||
<html lang="fr" className={`${arabic.variable} scroll-smooth`}>
|
||||
<body>
|
||||
{children}
|
||||
</body>
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { AnimatePresence, motion, useMotionValue, useSpring, useTransform } from "framer-motion";
|
||||
import { Menu, X } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { type MouseEvent, useState } from "react";
|
||||
import { type MouseEvent, type PointerEvent, useState } from "react";
|
||||
import { business, type Locale } from "@/config/business";
|
||||
import type { Messages } from "@/messages";
|
||||
import LanguageSwitcher from "./LanguageSwitcher";
|
||||
import PhysicsButton from "./PhysicsButton";
|
||||
|
||||
const navCtaClassName =
|
||||
"rounded-full border border-white/45 bg-[linear-gradient(180deg,#565b63_0%,#20242a_48%,#111317_100%)] font-semibold text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.38),inset_0_-10px_18px_rgba(0,0,0,0.18),0_14px_32px_rgba(17,19,23,0.18)] transition-colors hover:bg-[linear-gradient(180deg,#476a95_0%,#264b73_54%,#173655_100%)]";
|
||||
|
||||
export default function Navbar({ locale, onLocaleChange, t, whatsappUrl }: { locale: Locale; onLocaleChange: (locale: Locale) => void; t: Messages; whatsappUrl: string }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
@@ -34,22 +37,20 @@ export default function Navbar({ locale, onLocaleChange, t, whatsappUrl }: { loc
|
||||
|
||||
<div className="relative z-[3] hidden items-center gap-7 lg:flex">
|
||||
{t.nav.links.map((link) => (
|
||||
<a key={link.href} href={link.href} onClick={(event) => scrollToSection(event, link.href)} className="text-sm font-medium text-ink/58 transition hover:text-ink">
|
||||
{link.label}
|
||||
</a>
|
||||
<StretchNavLink key={link.href} href={link.href} label={link.label} onClick={(event) => scrollToSection(event, link.href)} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="relative z-[3] hidden items-center gap-3 md:flex">
|
||||
<LanguageSwitcher locale={locale} onLocaleChange={onLocaleChange} />
|
||||
<PhysicsButton href={whatsappUrl} external className="rounded-full bg-ink px-5 py-2.5 text-sm font-semibold text-white shadow-soft transition-colors hover:bg-optical">
|
||||
<PhysicsButton href={whatsappUrl} external className={`${navCtaClassName} px-5 py-2.5`}>
|
||||
{t.nav.cta}
|
||||
</PhysicsButton>
|
||||
</div>
|
||||
|
||||
<div className="relative z-[3] flex items-center gap-2 md:hidden">
|
||||
<LanguageSwitcher locale={locale} onLocaleChange={onLocaleChange} className="bg-white/80 backdrop-blur-none" buttonClassName="px-2 sm:px-3" />
|
||||
<PhysicsButton href={whatsappUrl} external className="rounded-full bg-ink px-3 py-2.5 text-xs font-semibold text-white shadow-soft transition-colors hover:bg-optical max-[374px]:hidden sm:px-5 sm:text-sm">
|
||||
<PhysicsButton href={whatsappUrl} external className={`${navCtaClassName} px-3 py-2.5 text-xs max-[374px]:hidden sm:px-5 sm:text-sm`}>
|
||||
{t.nav.cta}
|
||||
</PhysicsButton>
|
||||
<motion.button
|
||||
@@ -97,3 +98,42 @@ export default function Navbar({ locale, onLocaleChange, t, whatsappUrl }: { loc
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
function StretchNavLink({ href, label, onClick }: { href: string; label: string; onClick: (event: MouseEvent<HTMLAnchorElement>) => void }) {
|
||||
const pullX = useMotionValue(0);
|
||||
const pullY = useMotionValue(0);
|
||||
const x = useSpring(pullX, { stiffness: 260, damping: 16, mass: 0.3 });
|
||||
const y = useSpring(pullY, { stiffness: 260, damping: 16, mass: 0.3 });
|
||||
const scaleX = useTransform(x, [-18, 0, 18], [1.12, 1, 1.12]);
|
||||
const scaleY = useTransform(y, [-12, 0, 12], [0.94, 1, 0.94]);
|
||||
|
||||
function handlePointerMove(event: PointerEvent<HTMLAnchorElement>) {
|
||||
if (event.pointerType === "touch") return;
|
||||
|
||||
const bounds = event.currentTarget.getBoundingClientRect();
|
||||
const localX = event.clientX - bounds.left - bounds.width / 2;
|
||||
const localY = event.clientY - bounds.top - bounds.height / 2;
|
||||
|
||||
pullX.set(Math.max(-18, Math.min(18, localX * 0.42)));
|
||||
pullY.set(Math.max(-12, Math.min(12, localY * 0.38)));
|
||||
}
|
||||
|
||||
function release() {
|
||||
pullX.set(0);
|
||||
pullY.set(0);
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.a
|
||||
href={href}
|
||||
onClick={onClick}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerLeave={release}
|
||||
onPointerCancel={release}
|
||||
style={{ x, y, scaleX, scaleY }}
|
||||
className="inline-flex min-h-9 items-center rounded-full px-1 text-sm font-medium text-ink/58 will-change-transform transition-colors hover:text-ink"
|
||||
>
|
||||
{label}
|
||||
</motion.a>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export const fr = {
|
||||
about: {
|
||||
eyebrow: "Une adresse locale reconnue",
|
||||
title: "Un service optique simple, professionnel et fiable.",
|
||||
body: "Depuis autour de 2004, New Optic accompagne les clients de Temara avec une approche claire: bien conseiller, proposer des montures authentiques, garder des prix justes et assurer le suivi apres l'achat.",
|
||||
body: "Depuis 2004, New Optic accompagne les clients de Temara avec une approche claire: bien conseiller, proposer des montures authentiques, garder des prix justes et assurer le suivi apres l'achat.",
|
||||
cards: ["Reputation locale solide", "Montures originales uniquement", "Conseil de la monture a la garantie"]
|
||||
},
|
||||
services: {
|
||||
@@ -68,7 +68,7 @@ export const fr = {
|
||||
eyebrow: "Reputation",
|
||||
title: "Un opticien de quartier avec une exigence durable.",
|
||||
stats: [
|
||||
{ value: "2004", label: "Présence locale depuis autour de" },
|
||||
{ value: "2004", label: "Présence locale depuis" },
|
||||
{ value: "100%", label: "Montures originales" },
|
||||
{ value: "360°", label: "Conseil, verres, montures et suivi" }
|
||||
],
|
||||
|
||||
@@ -11,7 +11,8 @@ const config: Config = {
|
||||
optical: "#315f8f"
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["var(--font-sans)", "Inter", "system-ui", "sans-serif"],
|
||||
sans: ["var(--font-sans)"],
|
||||
display: ["var(--font-display)"],
|
||||
arabic: ["var(--font-arabic)", "system-ui", "sans-serif"]
|
||||
},
|
||||
boxShadow: {
|
||||
|
||||
Reference in New Issue
Block a user