Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ec8812c6e2 | |||
| cdd6f1b103 | |||
| ec9bc7552f | |||
| c6b649ef33 | |||
| 5fab9c95ac | |||
| e168a646a1 | |||
| d93f862cba | |||
| ebec21f1ac | |||
| 539437b824 | |||
| 6549b03d9a | |||
| b77883a16c | |||
| e69e20f753 | |||
| 9186d90aac | |||
| 3bed358990 | |||
| c6143d7d13 | |||
| 4ccfaf7b92 | |||
| 24beb5aa5e | |||
| 0ac37261a8 | |||
| ad070a21a5 | |||
| a5d9eed843 | |||
| d64bb10fa8 | |||
| 5a91d8ebc1 | |||
| 819a47fad7 | |||
| 2def6b683a | |||
| 53e72cb46a | |||
| 4d3d9fd8d6 | |||
| 256c6ffb97 | |||
| 48f8387dd4 | |||
| 19661417a6 | |||
| 2fddb47166 |
127
Agent.MD
Normal file
127
Agent.MD
Normal file
@@ -0,0 +1,127 @@
|
||||
# Agent Guide
|
||||
|
||||
## Project
|
||||
|
||||
New Optic is a multilingual premium marketing website for a trusted local optical shop in Temara, Morocco.
|
||||
|
||||
The site is built with:
|
||||
|
||||
- Next.js App Router
|
||||
- React
|
||||
- TypeScript
|
||||
- Tailwind CSS
|
||||
- Framer Motion
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
npm run build
|
||||
npm run start
|
||||
```
|
||||
|
||||
Use `npm run build` before considering changes complete.
|
||||
|
||||
## Important Files
|
||||
|
||||
- `config/business.ts` contains editable business details, phone numbers, map URL, and asset paths.
|
||||
- `messages/fr.ts` contains French copy. French is the default language.
|
||||
- `messages/ar.ts` contains Arabic copy and must remain natural/readable with proper RTL support.
|
||||
- `messages/en.ts` contains English copy.
|
||||
- `components/SiteShell.tsx` controls active language and page composition.
|
||||
- `components/Navbar.tsx` contains the sticky navigation and smooth-scroll behavior.
|
||||
- `components/PhysicsButton.tsx` contains the magnetic/stretch CTA button behavior.
|
||||
- `components/LiquidGlass.tsx` loads `liquidGL` only on desktop.
|
||||
- `app/globals.css` contains global visual styling and mobile performance overrides.
|
||||
|
||||
## Design Direction
|
||||
|
||||
Keep the visual language premium, minimal, Apple-inspired, and calm.
|
||||
|
||||
Prefer:
|
||||
|
||||
- breathable spacing
|
||||
- high contrast typography
|
||||
- soft glass surfaces
|
||||
- restrained gradients
|
||||
- subtle motion
|
||||
- clear conversion CTAs
|
||||
|
||||
Avoid:
|
||||
|
||||
- loud neon effects
|
||||
- generic template sections
|
||||
- fake testimonials
|
||||
- overhyped luxury wording
|
||||
- heavy mobile effects
|
||||
|
||||
## Multilingual Rules
|
||||
|
||||
- French is the default language.
|
||||
- Arabic must use RTL layout correctly.
|
||||
- Do not translate word-for-word. Keep copy natural in each language.
|
||||
- When adding new copy, update all three translation files.
|
||||
|
||||
## Mobile Performance Rules
|
||||
|
||||
Mobile performance is important.
|
||||
|
||||
- Do not load `liquidGL` or `html2canvas` on mobile.
|
||||
- Avoid `backdrop-filter`, large blur layers, fixed backgrounds, and continuous animations below `1024px`.
|
||||
- Use optimized images for mobile where possible.
|
||||
- Keep touch interactions lightweight.
|
||||
|
||||
## Business Facts
|
||||
|
||||
Use only verified or provided facts:
|
||||
|
||||
- Business name: New Optic
|
||||
- Location: Temara, Morocco
|
||||
- Area: Massira I / Rue Boujdour
|
||||
- Phone: `05376-03279`
|
||||
- WhatsApp: `+212 662-872002`
|
||||
- Google Maps link: `https://maps.app.goo.gl/LeuFER6h887Cp5aG9`
|
||||
- Trusted local optical shop since around 2004
|
||||
- Offers repairs, prescription glasses, sunglasses, kids glasses, budget frames, contact lenses, and colored contact lenses
|
||||
- Does not offer eye exams
|
||||
- Offers original/authentic frames only
|
||||
- Known for fair prices and local reputation
|
||||
|
||||
Do not invent opening hours, testimonials, certifications, or brand partnerships.
|
||||
|
||||
## Git / Release
|
||||
|
||||
Remote repository:
|
||||
|
||||
```txt
|
||||
http://192.168.1.160:3000/oimwiodev/New-Optic.git
|
||||
```
|
||||
|
||||
Main branch:
|
||||
|
||||
```txt
|
||||
main
|
||||
```
|
||||
|
||||
Current release tag:
|
||||
|
||||
```txt
|
||||
v1.0.0
|
||||
```
|
||||
|
||||
## Commit Workflow
|
||||
|
||||
When making project changes, commit them locally and push to the Gitea remote unless the user explicitly asks not to.
|
||||
|
||||
Before committing:
|
||||
|
||||
- Run `npm run build` for code changes.
|
||||
- Review `git status` and `git diff`.
|
||||
- Do not commit secrets or local environment files.
|
||||
|
||||
Default push target:
|
||||
|
||||
```txt
|
||||
origin main
|
||||
```
|
||||
@@ -12,22 +12,23 @@ New Optic
|
||||
Trusted local optical shop.
|
||||
|
||||
## Core Business Summary
|
||||
New Optic is a local optical business in Temara, Morocco, serving customers with professional and simple service since around 2011.
|
||||
New Optic is a local optical business in Temara, Morocco, serving customers with professional and simple service since around 2004.
|
||||
|
||||
The business offers eye exams, repairs prescription glasses and sunglasses, and sells eyewear including prescription glasses, sunglasses, kids glasses, and budget frames.
|
||||
The business repairs prescription glasses and sunglasses, and sells eyewear including prescription glasses, sunglasses, kids glasses, budget frames, contact lenses, and colored contact lenses.
|
||||
|
||||
New Optic also takes care of the full glasses process, from helping customers with lenses and frames to providing warranty as part of the service.
|
||||
|
||||
A key selling point is that New Optic offers only original, authentic frames and does not sell fake products.
|
||||
|
||||
## Main Services
|
||||
- Eye exams
|
||||
- Repair of vision/prescription glasses
|
||||
- Repair of sunglasses
|
||||
- Sale of prescription glasses
|
||||
- Sale of sunglasses
|
||||
- Sale of kids glasses
|
||||
- Sale of budget frames
|
||||
- Sale of contact lenses
|
||||
- Sale of colored contact lenses
|
||||
- Help choosing lenses and frames
|
||||
- Complete glasses service from lenses to frame to warranty
|
||||
- Warranty as part of the service generally
|
||||
@@ -40,7 +41,7 @@ The website and business messaging should emphasize these points in this general
|
||||
3. Trust
|
||||
4. Original/authentic frames
|
||||
5. Professional service
|
||||
6. Eye exams
|
||||
6. Contact lenses and colored contact lenses
|
||||
7. Repair service
|
||||
8. Complete glasses service
|
||||
|
||||
@@ -63,7 +64,8 @@ The business should come across as:
|
||||
- Strong local reputation built over time
|
||||
- Offers only original/authentic frames
|
||||
- Provides repairs as well as sales
|
||||
- Offers eye exams on-site
|
||||
- Offers contact lenses and colored contact lenses
|
||||
- Does not offer eye exams
|
||||
- Handles the glasses process from lenses and frames to warranty
|
||||
- Serves both adults and children
|
||||
|
||||
@@ -112,7 +114,7 @@ When generating content for New Optic, AI should:
|
||||
- Emphasize fair prices in a classy and professional way
|
||||
- Highlight local reputation and trust
|
||||
- Mention original/authentic frames when relevant
|
||||
- Mention eye exams and repair service clearly
|
||||
- Mention repairs, contact lenses, colored contact lenses, and complete eyewear support clearly
|
||||
- Present New Optic as a full-service optical shop
|
||||
- Keep wording simple and useful
|
||||
- Write in a way that makes sense for a local business website
|
||||
@@ -138,16 +140,18 @@ If AI helps build or write a website for New Optic, it should prioritize:
|
||||
|
||||
## Business Facts to Reuse
|
||||
- Business name: New Optic
|
||||
- Established: around 2011
|
||||
- Established: around 2004
|
||||
- Business type: optical store / eyewear shop
|
||||
- Location: Temara, Morocco
|
||||
- Area mentioned by user: Massira / Massira II context
|
||||
- Offers eye exams: yes
|
||||
- Offers eye exams: no
|
||||
- Offers repairs: yes
|
||||
- Sells prescription glasses: yes
|
||||
- Sells sunglasses: yes
|
||||
- Sells kids glasses: yes
|
||||
- Sells budget frames: yes
|
||||
- Sells contact lenses: yes
|
||||
- Sells colored contact lenses: yes
|
||||
- Offers original/authentic frames only: yes
|
||||
- Warranty: yes, as part of the service generally
|
||||
|
||||
@@ -157,10 +161,10 @@ If AI helps build or write a website for New Optic, it should prioritize:
|
||||
- Phone: 05376-03279
|
||||
|
||||
## Recommended Short Brand Description
|
||||
New Optic is a trusted local optical shop in Temara, Morocco, offering eye exams, glasses repair, and a wide range of eyewear with fair prices, professional service, and original frames.
|
||||
New Optic is a trusted local optical shop in Temara, Morocco, offering glasses repair, contact lenses, colored contact lenses, and a wide range of eyewear with fair prices, professional service, and original frames.
|
||||
|
||||
## Recommended Longer Brand Description
|
||||
New Optic is a trusted local optical shop based in Temara, Morocco, serving the community since around 2011. The business offers eye exams, repair services for glasses and sunglasses, and a selection of prescription glasses, sunglasses, kids glasses, and budget frames.
|
||||
New Optic is a trusted local optical shop based in Temara, Morocco, serving the community since around 2004. The business offers repair services for glasses and sunglasses, contact lenses, colored contact lenses, and a selection of prescription glasses, sunglasses, kids glasses, and budget frames.
|
||||
|
||||
New Optic is known for fair prices, strong local reputation, and trustworthy service. The business also stands out by offering only original, authentic frames and by supporting customers through the full eyewear process, from lenses and frames to warranty.
|
||||
|
||||
|
||||
63
CHANGELOG.md
Normal file
63
CHANGELOG.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to the New Optic website will be documented in this file.
|
||||
|
||||
## Unreleased
|
||||
|
||||
### 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".
|
||||
- Tightened mobile sizing for the WhatsApp nav pill and hero trust badge.
|
||||
- Balanced mobile navbar controls so the language switcher, WhatsApp CTA, and menu do not bunch up on the right.
|
||||
- Reduced the mobile gap between the reputation section and contact panel.
|
||||
- Let the mobile WhatsApp nav CTA stretch into available navbar space.
|
||||
- Restored mobile glass blur and premium highlight layers instead of flattening glass surfaces.
|
||||
- Added a dedicated stronger blur treatment for the mobile top navigation bar.
|
||||
|
||||
## [1.0.0] - 2026-05-16
|
||||
|
||||
### Added
|
||||
|
||||
- Built the full multilingual New Optic marketing website.
|
||||
- Added French, Arabic, and English language support.
|
||||
- Added RTL layout support for Arabic.
|
||||
- Added sticky glass navbar with mobile menu and smooth section scrolling.
|
||||
- Added premium hero, about, services, collections, trust, contact, and footer sections.
|
||||
- Added central editable business configuration in `config/business.ts`.
|
||||
- Added structured translation files in `messages/`.
|
||||
- Added WhatsApp, phone, map, and QR-code contact CTAs.
|
||||
- Added Google Maps QR code asset.
|
||||
- Added product collection imagery for prescription glasses, sunglasses, kids glasses, and budget frames.
|
||||
- Added desktop-only `liquidGL` progressive enhancement for glass panels.
|
||||
- Added magnetic/stretch CTA button interactions.
|
||||
- Added mobile performance optimizations, including a compressed mobile background image.
|
||||
- Added SEO metadata and local business JSON-LD.
|
||||
- Added `Agent.MD` for future development guidance.
|
||||
|
||||
### Changed
|
||||
|
||||
- Fixed desktop hero image visibility by removing liquid glass from image-bearing containers, while applying liquid glass to navbar/buttons and adding a stronger CSS glass fallback.
|
||||
- Re-enabled liquid glass on desktop PCs using user-agent and pointer capability detection, while keeping it disabled on phones and tablets.
|
||||
- Changed trust stat label accent from blue to a softer neutral white tone.
|
||||
- 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.
|
||||
- Added mobile `content-visibility` containment so offscreen sections do less rendering work on low-end devices.
|
||||
- Removed the WebGL liquid glass layer from the contact QR panel so the QR code renders immediately and remains scannable.
|
||||
- Reworked collection cards from abstract placeholders to image-led product cards.
|
||||
- Improved dark-section heading contrast.
|
||||
- Corrected French typography and accents.
|
||||
|
||||
### Verified
|
||||
|
||||
- `npm run build` passes.
|
||||
- Initial Gitea release created as `v1.0.0`.
|
||||
|
||||
[1.0.0]: http://192.168.1.160:3000/oimwiodev/New-Optic/releases/tag/v1.0.0
|
||||
108
app/globals.css
108
app/globals.css
@@ -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;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -20,13 +22,11 @@ body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(251, 250, 248, 0.86) 0%, rgba(246, 245, 242, 0.92) 45%, rgba(255, 255, 255, 0.94) 100%),
|
||||
url('/assets/New-optic-BG.png') center top / cover fixed,
|
||||
radial-gradient(circle at top left, rgba(49, 95, 143, 0.13), transparent 34rem),
|
||||
radial-gradient(circle at 82% 12%, rgba(216, 221, 229, 0.9), transparent 24rem),
|
||||
linear-gradient(180deg, rgba(251, 250, 248, 0.9) 0%, rgba(246, 245, 242, 0.94) 45%, rgba(255, 255, 255, 0.96) 100%),
|
||||
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;
|
||||
}
|
||||
@@ -69,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;
|
||||
@@ -82,7 +86,40 @@ a {
|
||||
background: rgba(255, 255, 255, 0.68);
|
||||
border: 1px solid rgba(255, 255, 255, 0.72);
|
||||
box-shadow: 0 18px 60px rgba(17, 19, 23, 0.08);
|
||||
backdrop-filter: blur(24px);
|
||||
backdrop-filter: blur(24px) saturate(1.22);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(1.22);
|
||||
}
|
||||
|
||||
.premium-glass::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0.06) 42%, rgba(255, 255, 255, 0.2)),
|
||||
radial-gradient(circle at 18% 0%, rgba(255, 255, 255, 0.55), transparent 34%),
|
||||
radial-gradient(circle at 82% 100%, rgba(49, 95, 143, 0.12), transparent 38%);
|
||||
opacity: 0.72;
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
.premium-glass::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 1px;
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8),
|
||||
inset 0 -18px 32px rgba(49, 95, 143, 0.08),
|
||||
inset 18px 0 34px rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.nav-glass {
|
||||
background: rgba(255, 255, 255, 0.62);
|
||||
backdrop-filter: blur(30px) saturate(1.32);
|
||||
-webkit-backdrop-filter: blur(30px) saturate(1.32);
|
||||
}
|
||||
|
||||
.hairline {
|
||||
@@ -137,32 +174,40 @@ a {
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
body {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(251, 250, 248, 0.9) 0%, rgba(246, 245, 242, 0.94) 45%, rgba(255, 255, 255, 0.96) 100%),
|
||||
url('/assets/New-optic-BG-mobile.webp') center top / cover scroll,
|
||||
linear-gradient(180deg, #fbfaf8 0%, #f6f5f2 42%, #ffffff 100%);
|
||||
}
|
||||
|
||||
body::before,
|
||||
body::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.floating-sheen {
|
||||
animation: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.glass,
|
||||
[class*="backdrop-blur"] {
|
||||
backdrop-filter: none !important;
|
||||
-webkit-backdrop-filter: none !important;
|
||||
}
|
||||
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
box-shadow: 0 10px 30px rgba(17, 19, 23, 0.07);
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
box-shadow: 0 14px 34px rgba(17, 19, 23, 0.08);
|
||||
backdrop-filter: blur(18px) saturate(1.24);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(1.24);
|
||||
}
|
||||
|
||||
.nav-glass {
|
||||
background: rgba(255, 255, 255, 0.58);
|
||||
border-color: rgba(255, 255, 255, 0.86);
|
||||
box-shadow:
|
||||
0 14px 36px rgba(17, 19, 23, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.78);
|
||||
backdrop-filter: blur(26px) saturate(1.38);
|
||||
-webkit-backdrop-filter: blur(26px) saturate(1.38);
|
||||
}
|
||||
|
||||
main > section {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 1px 780px;
|
||||
}
|
||||
|
||||
main > section:first-child {
|
||||
content-visibility: visible;
|
||||
contain-intrinsic-size: auto;
|
||||
}
|
||||
|
||||
* {
|
||||
transition-duration: 160ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,3 +217,14 @@ a {
|
||||
box-shadow: 0 10px 24px rgba(17, 19, 23, 0.08) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(251, 250, 248, 0.86) 0%, rgba(246, 245, 242, 0.92) 45%, rgba(255, 255, 255, 0.94) 100%),
|
||||
url('/assets/New-optic-BG.png') center top / cover fixed,
|
||||
radial-gradient(circle at top left, rgba(49, 95, 143, 0.13), transparent 34rem),
|
||||
radial-gradient(circle at 82% 12%, rgba(216, 221, 229, 0.9), transparent 24rem),
|
||||
linear-gradient(180deg, #fbfaf8 0%, #f6f5f2 42%, #ffffff 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -7,7 +7,7 @@ import PhysicsButton from "./PhysicsButton";
|
||||
|
||||
export default function ContactSection({ t, whatsappUrl }: { t: Messages; whatsappUrl: string }) {
|
||||
return (
|
||||
<AnimatedSection id="contact" className="px-4 py-20 sm:px-6">
|
||||
<AnimatedSection id="contact" className="px-4 pb-16 pt-6 sm:px-6 sm:py-20">
|
||||
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-[1.08fr_0.92fr]">
|
||||
<div className="rounded-[3rem] bg-white p-8 shadow-glass sm:p-12">
|
||||
<p className="mb-4 text-xs font-semibold uppercase tracking-[0.28em] text-optical/75">{t.contact.eyebrow}</p>
|
||||
@@ -29,11 +29,11 @@ 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>
|
||||
|
||||
<div className="glass liquidGL-pane flex flex-col justify-between rounded-[3rem] p-7">
|
||||
<div className="glass flex flex-col justify-between rounded-[3rem] p-7">
|
||||
<div className="relative z-[3] aspect-square overflow-hidden rounded-[2.2rem] bg-white p-8 shadow-sm">
|
||||
<span className="floating-sheen" />
|
||||
<Image src={business.assets.mapQr} alt={t.contact.qr} fill sizes="(min-width: 1024px) 38vw, 90vw" className="object-contain p-10" />
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@ export default function HeroSection({ t, whatsappUrl }: { t: Messages; whatsappU
|
||||
<motion.div className="absolute left-1/2 top-24 hidden h-72 w-72 -translate-x-1/2 rounded-full bg-optical/10 blur-3xl lg:block" animate={canAnimateIntro ? { scale: [1, 1.16, 1], opacity: [0.55, 0.88, 0.55] } : undefined} transition={{ duration: 8, repeat: Infinity, ease: "easeInOut" }} />
|
||||
<div className="mx-auto grid max-w-7xl items-center gap-12 lg:grid-cols-[1fr_0.92fr]">
|
||||
<motion.div initial={canAnimateIntro ? { opacity: 0, y: 24 } : false} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.8, ease: [0.22, 1, 0.36, 1] }} className="relative z-10 text-center lg:text-start">
|
||||
<div className="mx-auto mb-6 inline-flex items-center gap-2 rounded-full border border-ink/10 bg-white/65 px-4 py-2 text-xs font-semibold uppercase tracking-[0.18em] text-ink/60 shadow-sm backdrop-blur lg:mx-0">
|
||||
<ShieldCheck size={15} className="text-optical" />
|
||||
{t.hero.eyebrow}
|
||||
<div className="mx-auto mb-5 inline-flex max-w-full items-center gap-1.5 rounded-full border border-ink/10 bg-white/65 px-3 py-1.5 text-[10px] font-semibold uppercase tracking-[0.12em] text-ink/60 shadow-sm backdrop-blur max-[374px]:px-2.5 max-[374px]:text-[9px] max-[374px]:tracking-[0.08em] sm:mb-6 sm:gap-2 sm:px-4 sm:py-2 sm:text-xs sm:tracking-[0.18em] lg:mx-0">
|
||||
<ShieldCheck className="size-3.5 shrink-0 text-optical sm:size-[15px]" />
|
||||
<span className="whitespace-nowrap leading-4">{t.hero.eyebrow}</span>
|
||||
</div>
|
||||
<h1 className="mx-auto max-w-4xl text-5xl font-semibold tracking-[-0.075em] text-ink sm:text-7xl lg:mx-0 lg:text-8xl">{t.hero.title}</h1>
|
||||
<p className="mx-auto mt-6 max-w-2xl text-lg leading-8 text-ink/64 sm:text-xl lg:mx-0">{t.hero.subtitle}</p>
|
||||
@@ -65,7 +65,7 @@ export default function HeroSection({ t, whatsappUrl }: { t: Messages; whatsappU
|
||||
className="relative mx-auto w-full max-w-xl lg:max-w-none lg:will-change-transform"
|
||||
>
|
||||
<div className="absolute -inset-6 hidden rounded-[3rem] bg-gradient-to-br from-optical/14 via-white/0 to-silver/45 blur-2xl lg:block" />
|
||||
<div className="glass relative overflow-hidden rounded-[2.6rem] p-3">
|
||||
<div className="glass premium-glass relative overflow-hidden rounded-[2.6rem] p-3">
|
||||
<span className="floating-sheen" />
|
||||
<div className="relative aspect-[4/3] overflow-hidden rounded-[2rem] bg-silver/40">
|
||||
<Image src={business.assets.hero} alt={t.hero.imageAlt} fill sizes="(min-width: 1024px) 45vw, 92vw" className="object-cover" priority />
|
||||
@@ -76,7 +76,7 @@ export default function HeroSection({ t, whatsappUrl }: { t: Messages; whatsappU
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.22em] text-optical/80">{business.name}</p>
|
||||
<p className="mt-1 text-sm font-semibold text-ink">Temara, Morocco</p>
|
||||
</div>
|
||||
<div className="grid size-12 place-items-center rounded-full bg-ink text-xs font-semibold text-white">2011</div>
|
||||
<div className="grid size-12 place-items-center rounded-full bg-ink text-xs font-semibold text-white">{business.established}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import { languages, type Locale } from "@/config/business";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export default function LanguageSwitcher({ locale, onLocaleChange }: { locale: Locale; onLocaleChange: (locale: Locale) => void }) {
|
||||
export default function LanguageSwitcher({ locale, onLocaleChange, className, buttonClassName }: { locale: Locale; onLocaleChange: (locale: Locale) => void; className?: string; buttonClassName?: string }) {
|
||||
return (
|
||||
<div className="flex rounded-full border border-ink/10 bg-white/65 p-1 shadow-sm backdrop-blur-xl" aria-label="Language switcher">
|
||||
<div className={cn("flex rounded-full border border-ink/10 bg-white/65 p-1 shadow-sm backdrop-blur-xl", className)} aria-label="Language switcher">
|
||||
{languages.map((language) => (
|
||||
<button
|
||||
key={language.code}
|
||||
@@ -13,6 +13,7 @@ export default function LanguageSwitcher({ locale, onLocaleChange }: { locale: L
|
||||
onClick={() => onLocaleChange(language.code)}
|
||||
className={cn(
|
||||
"rounded-full px-3 py-1.5 text-xs font-semibold transition-all duration-300",
|
||||
buttonClassName,
|
||||
locale === language.code ? "bg-ink text-white shadow-sm" : "text-ink/55 hover:text-ink"
|
||||
)}
|
||||
aria-pressed={locale === language.code}
|
||||
|
||||
@@ -15,7 +15,12 @@ declare global {
|
||||
export default function LiquidGlass() {
|
||||
useEffect(() => {
|
||||
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
||||
if (!window.matchMedia("(min-width: 1024px)").matches) return;
|
||||
|
||||
const userAgent = navigator.userAgent || "";
|
||||
const isMobileUserAgent = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|Tablet/i.test(userAgent);
|
||||
const hasCoarsePrimaryPointer = window.matchMedia("(pointer: coarse)").matches;
|
||||
if (isMobileUserAgent || hasCoarsePrimaryPointer) return;
|
||||
if (!document.querySelector(".liquidGL-pane")) return;
|
||||
|
||||
let cancelled = false;
|
||||
let attempts = 0;
|
||||
@@ -64,7 +69,7 @@ export default function LiquidGlass() {
|
||||
shadow: true,
|
||||
specular: true,
|
||||
reveal: "fade",
|
||||
tilt: window.innerWidth >= 768,
|
||||
tilt: true,
|
||||
tiltFactor: 3.5,
|
||||
magnify: 1.01
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -23,60 +26,114 @@ 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">
|
||||
<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">
|
||||
<header className="fixed inset-x-0 top-0 z-50 px-4 pt-4 max-[374px]:px-2.5 sm:px-6">
|
||||
<nav className="glass premium-glass nav-glass relative mx-auto flex max-w-7xl items-center justify-start gap-2 overflow-hidden rounded-full px-3 py-3 max-[374px]:px-2 max-[374px]:py-2.5 md:justify-between sm:px-5" aria-label="Main navigation">
|
||||
<a href="#home" onClick={(event) => scrollToSection(event, "#home")} className="relative z-[3] flex shrink-0 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 max-[374px]:size-9">
|
||||
<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">
|
||||
{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>
|
||||
|
||||
<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 min-w-0 flex-1 items-center gap-1.5 max-[374px]:gap-0.5 sm:gap-2 md:hidden">
|
||||
<LanguageSwitcher locale={locale} onLocaleChange={onLocaleChange} className="w-[132px] shrink-0 bg-white/72 backdrop-blur-xl max-[374px]:w-[106px] max-[374px]:p-0.5" buttonClassName="flex-1 px-0.5 max-[374px]:px-0 max-[374px]:py-1.5 sm:px-3" />
|
||||
<PhysicsButton href={whatsappUrl} external className={`${navCtaClassName} min-w-0 flex-1 px-2.5 py-2 text-[11px] max-[374px]:px-1.5 max-[374px]:py-1.5 max-[374px]:text-[9px] sm:px-5 sm:py-2.5 sm:text-sm`}>
|
||||
{t.nav.cta}
|
||||
</PhysicsButton>
|
||||
<motion.button
|
||||
type="button"
|
||||
onClick={() => setOpen((value) => !value)}
|
||||
whileTap={{ scale: 0.94 }}
|
||||
transition={{ duration: 0.08 }}
|
||||
className="grid size-11 shrink-0 place-items-center rounded-full bg-white/70 text-ink outline-none transition max-[374px]:size-9 focus-visible:ring-2 focus-visible:ring-optical/45 focus-visible:ring-offset-2"
|
||||
aria-label={t.nav.menu}
|
||||
aria-expanded={open}
|
||||
>
|
||||
{open ? <X size={18} /> : <Menu size={18} />}
|
||||
</motion.button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<AnimatePresence>
|
||||
{open ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -12, scale: 0.98 }}
|
||||
initial={{ opacity: 0, y: -8, scale: 0.985 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: -12, scale: 0.98 }}
|
||||
transition={{ duration: 0.24 }}
|
||||
className="glass mx-auto mt-3 max-w-7xl rounded-[2rem] p-4 md:hidden"
|
||||
exit={{ opacity: 0, y: -6, scale: 0.99 }}
|
||||
transition={{ duration: 0.13, ease: "easeOut" }}
|
||||
className="relative mx-auto mt-3 max-w-7xl overflow-hidden rounded-[2rem] border border-ink/8 bg-white p-4 shadow-glass md:hidden"
|
||||
>
|
||||
<div className="grid gap-2">
|
||||
{t.nav.links.map((link) => (
|
||||
<a key={link.href} href={link.href} onClick={(event) => scrollToSection(event, link.href)} className="rounded-2xl px-4 py-3 text-sm font-semibold text-ink/72 hover:bg-white">
|
||||
<motion.a
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
onClick={(event) => scrollToSection(event, link.href)}
|
||||
initial={{ opacity: 0, x: locale === "ar" ? -8 : 8 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.1, ease: "easeOut" }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
className="rounded-2xl px-4 py-3 text-sm font-semibold text-ink/72 hover:bg-smoke"
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</motion.a>
|
||||
))}
|
||||
</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}
|
||||
</AnimatePresence>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function PhysicsButton({ href, children, className, external = fa
|
||||
whileHover={{ scale: 1.035 }}
|
||||
style={{ x, y, scaleX, scaleY, rotate }}
|
||||
transition={{ type: "spring", stiffness: 520, damping: 18, mass: 0.45 }}
|
||||
className={cn("group relative inline-flex touch-manipulation select-none items-center justify-center overflow-hidden will-change-transform", className)}
|
||||
className={cn("premium-glass group relative inline-flex touch-manipulation select-none items-center justify-center overflow-hidden will-change-transform", className)}
|
||||
>
|
||||
<motion.span
|
||||
className="pointer-events-none absolute inset-0 rounded-full bg-white/15 opacity-0 blur-xl transition-opacity duration-300 group-hover:opacity-100"
|
||||
|
||||
@@ -5,14 +5,14 @@ import SectionHeader from "./SectionHeader";
|
||||
|
||||
export default function TrustSection({ t }: { t: Messages }) {
|
||||
return (
|
||||
<AnimatedSection id="trust" className="px-4 py-20 sm:px-6">
|
||||
<AnimatedSection id="trust" className="px-4 pb-8 pt-16 sm:px-6 sm:py-20">
|
||||
<div className="mx-auto max-w-7xl overflow-hidden rounded-[3rem] bg-ink p-8 text-white shadow-glass sm:p-12 lg:p-16">
|
||||
<SectionHeader eyebrow={t.trust.eyebrow} title={t.trust.title} body={t.trust.body} tone="dark" />
|
||||
<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-white/64">{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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const business = {
|
||||
name: "New Optic",
|
||||
established: "2011",
|
||||
established: "2004",
|
||||
city: "Temara",
|
||||
country: "Morocco",
|
||||
phone: "05376-03279",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const ar = {
|
||||
meta: {
|
||||
title: "نيو أوبتيك تمارة | نظارات أصلية وخدمة موثوقة منذ 2011",
|
||||
description: "نيو أوبتيك في تمارة يقدم نظارات أصلية، فحص النظر، إصلاح النظارات، نظارات طبية وشمسية ونظارات الأطفال مع أثمنة منصفة وخدمة مهنية."
|
||||
title: "نيو أوبتيك تمارة | نظارات أصلية وخدمة موثوقة منذ 2004",
|
||||
description: "نيو أوبتيك في تمارة يقدم نظارات أصلية، إصلاح النظارات، عدسات لاصقة وعدسات ملونة، نظارات طبية وشمسية ونظارات الأطفال مع أثمنة منصفة."
|
||||
},
|
||||
nav: {
|
||||
links: [
|
||||
@@ -15,25 +15,25 @@ export const ar = {
|
||||
menu: "القائمة"
|
||||
},
|
||||
hero: {
|
||||
eyebrow: "نظاراتي محلي في تمارة منذ 2011",
|
||||
eyebrow: "نظاراتي محلي في تمارة منذ 2004",
|
||||
title: "نيو أوبتيك، نظاراتي ديال الثقة في تمارة.",
|
||||
subtitle: "نظارات أصلية، خدمة مهنية، أثمنة منصفة ومواكبة كاملة حتى ما بعد الشراء.",
|
||||
primary: "تواصل معنا",
|
||||
secondary: "شاهد الخدمات",
|
||||
note: "فحص النظر، إصلاح النظارات، نظارات طبية وشمسية ونظارات الأطفال.",
|
||||
note: "إصلاح النظارات، عدسات لاصقة وعدسات ملونة، نظارات طبية وشمسية ونظارات الأطفال.",
|
||||
imageAlt: "محل نيو أوبتيك واختيار النظارات في تمارة"
|
||||
},
|
||||
about: {
|
||||
eyebrow: "عنوان محلي معروف",
|
||||
title: "خدمة بصرية واضحة، مهنية وموثوقة.",
|
||||
body: "منذ حوالي 2011، يرافق نيو أوبتيك زبناء تمارة بطريقة بسيطة: نصيحة واضحة، نظارات أصلية، أثمنة منصفة وخدمة متابعة بعد الشراء.",
|
||||
body: "منذ حوالي 2004، يرافق نيو أوبتيك زبناء تمارة بطريقة بسيطة: نصيحة واضحة، نظارات أصلية، أثمنة منصفة وخدمة متابعة بعد الشراء.",
|
||||
cards: ["سمعة محلية قوية", "نظارات أصلية فقط", "مواكبة من اختيار الإطار حتى الضمان"]
|
||||
},
|
||||
services: {
|
||||
eyebrow: "الخدمات",
|
||||
title: "كل ما تحتاجه لنظاراتك في مكان واحد.",
|
||||
items: [
|
||||
{ title: "فحص النظر", text: "فحص واضح يساعد في اختيار العدسات والتصحيح المناسب." },
|
||||
{ title: "عدسات لاصقة", text: "عدسات لاصقة وعدسات ملونة متوفرة في المحل، مع نصيحة لاختيار المناسب." },
|
||||
{ title: "إصلاح النظارات", text: "تعديل وإصلاح النظارات الطبية والشمسية حسب حالة الإطار." },
|
||||
{ title: "نظارات طبية", text: "إطارات أصلية وعدسات مناسبة مع نصيحة حتى الاختيار النهائي." },
|
||||
{ title: "نظارات شمسية", text: "نظارات شمسية أصلية للراحة والحماية والاستعمال اليومي." },
|
||||
@@ -68,7 +68,7 @@ export const ar = {
|
||||
eyebrow: "السمعة",
|
||||
title: "نظاراتي قريب من الزبون ومعايير خدمة ثابتة.",
|
||||
stats: [
|
||||
{ value: "2011", label: "حضور محلي منذ حوالي" },
|
||||
{ value: "2004", label: "حضور محلي منذ حوالي" },
|
||||
{ value: "100%", label: "إطارات أصلية" },
|
||||
{ value: "360°", label: "نصيحة، عدسات، إطارات ومتابعة" }
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const en = {
|
||||
meta: {
|
||||
title: "New Optic in Temara | Trusted local optician since 2011",
|
||||
description: "New Optic in Temara offers authentic frames, eye exams, repairs, prescription glasses, sunglasses and kids eyewear with fair prices and professional service."
|
||||
title: "New Optic in Temara | Trusted local optician since 2004",
|
||||
description: "New Optic in Temara offers authentic frames, repairs, contact lenses, colored contact lenses, prescription glasses, sunglasses and kids eyewear with fair prices."
|
||||
},
|
||||
nav: {
|
||||
links: [
|
||||
@@ -15,25 +15,25 @@ export const en = {
|
||||
menu: "Menu"
|
||||
},
|
||||
hero: {
|
||||
eyebrow: "Local optician in Temara since 2011",
|
||||
eyebrow: "Local optician in Temara since 2004",
|
||||
title: "Your trusted optician in Temara.",
|
||||
subtitle: "Authentic frames, professional service, fair prices and complete support for your eyewear.",
|
||||
primary: "Contact us",
|
||||
secondary: "Explore services",
|
||||
note: "Eye exams, repairs, prescription glasses, sunglasses and kids eyewear.",
|
||||
note: "Repairs, contact lenses, colored contact lenses, prescription glasses, sunglasses and kids eyewear.",
|
||||
imageAlt: "New Optic shop and eyewear selection in Temara"
|
||||
},
|
||||
about: {
|
||||
eyebrow: "A trusted local address",
|
||||
title: "Optical service that is clear, professional and reliable.",
|
||||
body: "Since around 2011, New Optic has supported customers in Temara with a simple promise: honest guidance, authentic frames, fair prices and reliable after-sales support.",
|
||||
body: "Since around 2004, New Optic has supported customers in Temara with a simple promise: honest guidance, authentic frames, fair prices and reliable after-sales support.",
|
||||
cards: ["Strong local reputation", "Original frames only", "Guidance from frame to warranty"]
|
||||
},
|
||||
services: {
|
||||
eyebrow: "Services",
|
||||
title: "Everything essential for your eyewear, in one place.",
|
||||
items: [
|
||||
{ title: "Eye exams", text: "Clear vision checks to guide lens choices and correction needs." },
|
||||
{ title: "Contact lenses", text: "Contact lenses and colored contact lenses available in store, with guidance on suitable choices." },
|
||||
{ title: "Repairs", text: "Adjustment and repair for prescription glasses and sunglasses depending on frame condition." },
|
||||
{ title: "Prescription glasses", text: "Authentic frames, suitable lenses and patient support through the final choice." },
|
||||
{ title: "Sunglasses", text: "Authentic sunglasses for comfort, protection and everyday style." },
|
||||
@@ -68,9 +68,9 @@ export const en = {
|
||||
eyebrow: "Reputation",
|
||||
title: "A neighborhood optician with lasting standards.",
|
||||
stats: [
|
||||
{ value: "2011", 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."
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const fr = {
|
||||
meta: {
|
||||
title: "New Optic a Temara | Opticien de confiance depuis 2011",
|
||||
description: "New Optic a Temara propose montures originales, examens de vue, reparations, lunettes de vue, solaires et enfants avec un service professionnel et des prix justes."
|
||||
title: "New Optic a Temara | Opticien de confiance depuis 2004",
|
||||
description: "New Optic a Temara propose montures originales, reparations, lentilles de contact, lentilles de couleur, lunettes de vue, solaires et enfants avec des prix justes."
|
||||
},
|
||||
nav: {
|
||||
links: [
|
||||
@@ -15,25 +15,25 @@ export const fr = {
|
||||
menu: "Menu"
|
||||
},
|
||||
hero: {
|
||||
eyebrow: "Opticien local a Temara depuis 2011",
|
||||
eyebrow: "Opticien local a Temara depuis 2004",
|
||||
title: "Votre opticien de confiance a Temara.",
|
||||
subtitle: "Montures originales, service professionnel, prix justes et accompagnement complet pour vos lunettes.",
|
||||
primary: "Nous contacter",
|
||||
secondary: "Voir les services",
|
||||
note: "Examens de vue, reparations, lunettes de vue, solaires et enfants.",
|
||||
note: "Reparations, lentilles de contact, lentilles de couleur, lunettes de vue, solaires et enfants.",
|
||||
imageAlt: "Interieur et selection de lunettes chez New Optic a Temara"
|
||||
},
|
||||
about: {
|
||||
eyebrow: "Une adresse locale reconnue",
|
||||
title: "Un service optique simple, professionnel et fiable.",
|
||||
body: "Depuis autour de 2011, 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: {
|
||||
eyebrow: "Services",
|
||||
title: "Tout l'essentiel pour vos lunettes, au meme endroit.",
|
||||
items: [
|
||||
{ title: "Examen de vue", text: "Un controle clair pour mieux orienter le choix des verres et de la correction." },
|
||||
{ title: "Lentilles de contact", text: "Des lentilles de contact et lentilles de couleur disponibles en magasin, avec conseil sur le choix adapte." },
|
||||
{ title: "Reparation", text: "Ajustement et reparation de lunettes de vue et solaires selon l'etat de la monture." },
|
||||
{ title: "Lunettes de vue", text: "Montures originales, verres adaptes et accompagnement dans le choix final." },
|
||||
{ title: "Lunettes solaires", text: "Des solaires authentiques pour le confort, la protection et le style quotidien." },
|
||||
@@ -68,9 +68,9 @@ export const fr = {
|
||||
eyebrow: "Reputation",
|
||||
title: "Un opticien de quartier avec une exigence durable.",
|
||||
stats: [
|
||||
{ value: "2011", 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" },
|
||||
{ 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: {
|
||||
|
||||
@@ -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