34 Commits

Author SHA1 Message Date
Unchained
ace1ac104e fix: cart delete mutation and console warnings
Some checks are pending
Build and Deploy / build (push) Waiting to run
- Fix checkoutLinesDelete mutation: use 'id' param and 'linesIds' instead of 'lineIds'
- Fix viewport metadata warning: move to separate viewport export in layout.tsx
- Add sizes prop to checkout Image with fill
- Fix CartDrawer init checkout useEffect to prevent re-render loops
- Various product detail improvements
2026-03-23 13:49:14 +02:00
Unchained
7f603c83e9 fix: correct checkoutLinesDelete parameter name lines -> lineIds
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-23 13:20:37 +02:00
Unchained
0e9ad28dcf Fix: remove priority attribute from regular img tag
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-23 11:18:29 +02:00
Unchained
70d6cfc9a7 Fix product images and add carousel; add transformation carousel on mobile
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-23 11:14:15 +02:00
Unchained
f3d60d3c5b Fix product images: use fill with aspect-square container
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-23 10:45:10 +02:00
Unchained
7ecd9c2e22 Fix product images on mobile: use explicit width/height instead of fill
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-22 21:27:38 +02:00
Unchained
e9b95c44b9 Fix hero section on mobile - use background image instead of broken video
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-22 21:19:47 +02:00
Unchained
8a418be7c3 Fix mobile responsiveness: viewport meta, standard Tailwind star colors
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-22 21:05:29 +02:00
Unchained
ba25261a3c Premium design updates: gold accents, improved sections, verified review badges, reordered homepage layout
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-22 17:08:06 +02:00
Unchained
77e19d841b Change review stars to gold color (#FFD700)
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-22 16:16:42 +02:00
Unchained
43d662b54e Add padding to header and ensure mobile menu works
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-22 12:42:38 +02:00
Unchained
625bd727d3 Fix product images showing full picture instead of cropped
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-22 12:05:40 +02:00
Unchained
44d938953b Center related products using flexbox instead of grid
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-22 11:00:44 +02:00
Unchained
97fc5f5f1d Fix JSX indentation in Similar Products section 2026-03-22 09:11:13 +02:00
Unchained
140d82c7f4 Center Similar Products grid on product pages 2026-03-22 09:04:35 +02:00
Unchained
80a388cd7c fix: Center the similar products grid on product pages 2026-03-22 09:00:01 +02:00
Unchained
c3bd0408f4 feat: Add newsletter section back to product pages 2026-03-22 08:57:05 +02:00
Unchained
7618cfa6df fix: Remove non-scrolling testimonials and newsletter from product pages 2026-03-22 08:54:29 +02:00
Unchained
0827147745 fix: Slow down reviews scroll tempo for readability 2026-03-22 08:47:06 +02:00
Unchained
c5e96718a4 feat: Add scrolling reviews marquee with 50 reviews
- Reviews scroll continuously (alternating left-to-right and right-to-left)
- 50 varied Serbian customer reviews praising Manoon products
- Mentions Anti-age Serum, Day Serum, Night Serum, Morning Glow, Anti-age Set
- Scroll effect similar to As Seen In banner
2026-03-22 08:43:25 +02:00
Unchained
7febe90b36 fix: Move Customer Reviews above AsSeenIn on product pages 2026-03-22 08:39:44 +02:00
Unchained
c723d72508 fix: Move BeforeAfterGallery to right after AsSeenIn on product pages 2026-03-22 08:37:12 +02:00
Unchained
bf6362d3ad feat: Add second transformation with side-by-side sliders
- First transformation: use_case_2 (4-6 weeks)
- Second transformation: use_case_3 (6-8 weeks)
- Both sliders displayed side by side on same line
2026-03-22 08:32:22 +02:00
Unchained
9e901d7dfe feat: Use actual moumoujus before/after images from MinIO
- Before: use_case_2.webp (wrinkled skin)
- After: use_case_2_1.webp (smooth skin)
- Both images from minio-api.nodecrew.me
2026-03-22 08:26:50 +02:00
Unchained
0e727b2648 feat: Add slider comparison to before/after gallery
- Drag slider to reveal before/after (like example screenshot)
- Timeline showing '4-6 Weeks'
- Stars rating with review count
- Verified Results badge
- Matches moumoujus.com style
2026-03-22 07:32:47 +02:00
Unchained
d6523deae5 feat: Add all homepage sections to product pages
Product pages now include:
- Product Benefits
- Product Reviews
- Trust Badges
- Before/After Gallery
- How It Works
- Testimonials
- Newsletter
2026-03-21 20:13:05 +02:00
Unchained
5216abbcc0 feat: Landing page design improvements
Based on landing-page-design skill principles:

Homepage:
- Redesigned hero with outcome-focused headline ("Transform Your Hair & Skin")
- Added social proof micro (5 stars + 50,000+ customers)
- Better CTA: "Transform My Hair & Skin" instead of "Shop Now"
- Added trust indicators in hero (30-day guarantee, free shipping, cruelty free)
- Added ProblemSection to create empathy (dry hair, confusing ingredients, no results)
- Added HowItWorks section (3 steps: Choose, Apply, See Results)
- Improved AsSeenIn with scrolling marquee on dark background
- Premium trust badges with stats and icons

Product pages:
- Improved CTA: "Transform My Hair & Skin" (action verb + value)
- Added ProductBenefits section (4 key benefits)
- Added ProductReviews section with customer testimonials
- Added AsSeenIn scrolling banner
- Added trust indicators with icons

Section order now follows proven conversion sequence:
1. Hero (headline + outcome + CTA)
2. Social Proof (trust badges, logos)
3. Problem (empathy)
4. Solution (products)
5. How It Works
6. Testimonials
7. Final CTA
2026-03-21 19:59:09 +02:00
Unchained
4af5412c76 feat: Add trust indicators to product page
- Add 30-day money-back guarantee, secure checkout, easy returns icons
- Reorganize product page layout with clearer trust messaging
- Update free shipping threshold messaging
2026-03-21 19:00:31 +02:00
Unchained
d381cba302 feat: Add social proof sections to homepage
- Add TrustBadges component with ratings, customer count, secure payment icons
- Add AsSeenIn media logos banner
- Add BeforeAfterGallery with interactive gallery
- Add TestimonialsSection (already existed, now integrated into homepage)
- Connect all sections in homepage page.tsx

Sections added to homepage flow:
1. HeroVideo
2. TrustBadges
3. AsSeenIn
4. Products Grid
5. BeforeAfterGallery
6. Brand Story
7. Benefits
8. TestimonialsSection
9. Newsletter
2026-03-21 18:58:33 +02:00
Unchained
26212dec1c fix: Apollo Client cache merge causing product duplication
Some checks failed
Build and Deploy / build (push) Has been cancelled
The merge function was concatenating products on each query, causing
4 products to become 8, then 12, etc. Changed to replace incoming
data instead of merging.
2026-03-21 18:04:11 +02:00
Unchained
2876a8f80e fix: Replace WooCommerce env vars with Saleor API URL
Some checks failed
Build and Deploy / build (push) Has been cancelled
- NEXT_PUBLIC_WOOCOMMERCE_URL → NEXT_PUBLIC_SALEOR_API_URL
- Remove WooCommerce consumer key/secret (not needed for Saleor public API)
- Saleor API is public, no authentication required
2026-03-21 17:58:13 +02:00
Unchained
93005af0a1 Remove playwright - testing tool only
Some checks failed
Build and Deploy / build (push) Has been cancelled
2026-03-21 17:26:46 +02:00
Unchained
0b4e3f89d1 Add playwright for visual testing 2026-03-21 17:25:45 +02:00
Unchained
ec287c85ea Fix CSS cascade layers and header layout
- Rewrite globals.css to work properly with Tailwind 4 cascade layers
  - Remove conflicting * { padding: 0 } reset that broke Tailwind utilities
  - Organize styles into @layer base, @layer components, @layer utilities
- Fix newsletter centering (was off due to CSS layer conflicts)
- Fix header overlap on products pages (proper pt-[72px] spacing)
- Add solid header background (bg-white/80) instead of transparent
- Fix logo/nav positioning on desktop

Verified fixes with Playwright screenshots at 1280x800 and 390x844
2026-03-21 17:21:00 +02:00
23 changed files with 1616 additions and 574 deletions

View File

@@ -72,21 +72,8 @@ spec:
env: env:
- name: NODE_ENV - name: NODE_ENV
value: "production" value: "production"
- name: NEXT_PUBLIC_WOOCOMMERCE_URL - name: NEXT_PUBLIC_SALEOR_API_URL
valueFrom: value: "https://api.manoonoils.com/graphql/"
secretKeyRef:
name: woocommerce-credentials
key: WOOCOMMERCE_URL
- name: NEXT_PUBLIC_WOOCOMMERCE_CONSUMER_KEY
valueFrom:
secretKeyRef:
name: woocommerce-credentials
key: WOOCOMMERCE_CONSUMER_KEY
- name: NEXT_PUBLIC_WOOCOMMERCE_CONSUMER_SECRET
valueFrom:
secretKeyRef:
name: woocommerce-credentials
key: WOOCOMMERCE_CONSUMER_SECRET
- name: NEXT_PUBLIC_SITE_URL - name: NEXT_PUBLIC_SITE_URL
value: "https://dev.manoonoils.com" value: "https://dev.manoonoils.com"
volumeMounts: volumeMounts:
@@ -117,21 +104,8 @@ spec:
value: "3000" value: "3000"
- name: HOSTNAME - name: HOSTNAME
value: "0.0.0.0" value: "0.0.0.0"
- name: NEXT_PUBLIC_WOOCOMMERCE_URL - name: NEXT_PUBLIC_SALEOR_API_URL
valueFrom: value: "https://api.manoonoils.com/graphql/"
secretKeyRef:
name: woocommerce-credentials
key: WOOCOMMERCE_URL
- name: NEXT_PUBLIC_WOOCOMMERCE_CONSUMER_KEY
valueFrom:
secretKeyRef:
name: woocommerce-credentials
key: WOOCOMMERCE_CONSUMER_KEY
- name: NEXT_PUBLIC_WOOCOMMERCE_CONSUMER_SECRET
valueFrom:
secretKeyRef:
name: woocommerce-credentials
key: WOOCOMMERCE_CONSUMER_SECRET
- name: NEXT_PUBLIC_SITE_URL - name: NEXT_PUBLIC_SITE_URL
value: "https://dev.manoonoils.com" value: "https://dev.manoonoils.com"
resources: resources:

View File

@@ -442,6 +442,7 @@ export default function CheckoutPage() {
src={line.variant.product.media[0].url} src={line.variant.product.media[0].url}
alt={line.variant.product.name} alt={line.variant.product.name}
fill fill
sizes="64px"
className="object-cover" className="object-cover"
/> />
)} )}

View File

@@ -2,61 +2,11 @@
/* ============================================ /* ============================================
MANOONOILS DESIGN SYSTEM MANOONOILS DESIGN SYSTEM
Inspired by premium skincare brands Tailwind 4 compatible - uses CSS layers
============================================ */ ============================================ */
:root {
/* Primary Colors */
--color-white: #ffffff;
--color-background: #fafafa;
--color-background-alt: #f5f5f5;
--color-foreground: #1a1a1a;
--color-foreground-muted: #666666;
--color-foreground-subtle: #999999;
/* Accent Colors */
--color-accent: #e8f0f5;
--color-accent-dark: #a8c5d8;
--color-accent-blue: #e8f0f5;
--color-gold: #c9a962;
--color-gold-light: #d4b978;
/* UI Colors */
--color-border: #e5e5e5;
--color-border-dark: #d1d1d1;
--color-cta: #000000;
--color-cta-hover: #333333;
--color-overlay: rgba(0, 0, 0, 0.4);
/* Spacing */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
--space-2xl: 48px;
--space-3xl: 64px;
--space-4xl: 96px;
--space-5xl: 128px;
/* Typography */
--font-display: 'DM Sans', sans-serif;
--font-body: 'Inter', sans-serif;
/* Transitions */
--transition-fast: 150ms ease;
--transition-base: 250ms ease;
--transition-slow: 350ms ease;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
}
@theme inline { @theme inline {
/* Colors - reference :root variables */ /* Colors - reference CSS variables */
--color-white: var(--color-white); --color-white: var(--color-white);
--color-background: var(--color-background); --color-background: var(--color-background);
--color-background-alt: var(--color-background-alt); --color-background-alt: var(--color-background-alt);
@@ -79,6 +29,43 @@
--font-body: var(--font-body); --font-body: var(--font-body);
} }
/* ============================================
CSS VARIABLES
============================================ */
:root {
--color-white: #ffffff;
--color-background: #fafafa;
--color-background-alt: #f5f5f5;
--color-foreground: #1a1a1a;
--color-foreground-muted: #666666;
--color-foreground-subtle: #999999;
--color-accent: #e8f0f5;
--color-accent-dark: #a8c5d8;
--color-accent-blue: #e8f0f5;
--color-gold: #c9a962;
--color-gold-light: #d4b978;
--color-border: #e5e5e5;
--color-border-dark: #d1d1d1;
--color-cta: #000000;
--color-cta-hover: #333333;
--color-overlay: rgba(0, 0, 0, 0.4);
--font-display: 'DM Sans', sans-serif;
--font-body: 'Inter', sans-serif;
--transition-fast: 150ms ease;
--transition-base: 250ms ease;
--transition-slow: 350ms ease;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
}
/* ============================================ /* ============================================
FONT IMPORTS FONT IMPORTS
============================================ */ ============================================ */
@@ -98,307 +85,276 @@
} }
/* ============================================ /* ============================================
BASE STYLES BASE STYLES (in Tailwind base layer)
============================================ */ ============================================ */
* { @layer base {
box-sizing: border-box; html {
margin: 0; scroll-behavior: smooth;
padding: 0; }
}
body {
html { background: var(--color-background);
scroll-behavior: smooth; color: var(--color-foreground);
} font-family: var(--font-body);
font-size: 16px;
body { line-height: 1.6;
background: var(--color-background); -webkit-font-smoothing: antialiased;
color: var(--color-foreground); -moz-osx-font-smoothing: grayscale;
font-family: var(--font-body); }
font-size: 16px;
line-height: 1.6; h1, h2, h3, h4, h5, h6 {
-webkit-font-smoothing: antialiased; font-family: var(--font-display);
-moz-osx-font-smoothing: grayscale; font-weight: 500;
line-height: 1.2;
letter-spacing: -0.02em;
}
h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
}
h2 {
font-size: clamp(1.5rem, 4vw, 2.5rem);
}
h3 {
font-size: clamp(1.25rem, 3vw, 1.75rem);
}
input, textarea, select {
font-family: var(--font-body);
font-size: 16px;
}
input:focus, textarea:focus, select:focus {
outline: none;
border-color: var(--color-foreground);
}
:focus-visible {
outline: 2px solid var(--color-foreground);
outline-offset: 2px;
}
} }
/* ============================================ /* ============================================
TYPOGRAPHY COMPONENTS
============================================ */ ============================================ */
h1, h2, h3, h4, h5, h6 { @layer components {
font-family: var(--font-display);
font-weight: 500;
line-height: 1.2;
letter-spacing: -0.02em;
}
h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
}
h2 {
font-size: clamp(1.5rem, 4vw, 2.5rem);
}
h3 {
font-size: clamp(1.25rem, 3vw, 1.75rem);
}
.text-display {
font-family: var(--font-display);
font-weight: 500;
letter-spacing: -0.02em;
}
.text-body {
font-family: var(--font-body);
}
.text-uppercase {
text-transform: uppercase;
letter-spacing: 0.05em;
}
.text-caption {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.1em;
font-weight: 500;
}
.text-muted {
color: var(--color-foreground-muted);
}
.text-subtle {
color: var(--color-foreground-subtle);
}
/* ============================================
UTILITY CLASSES
============================================ */
.container {
width: 100%;
max-width: 1400px;
margin-left: auto;
margin-right: auto;
padding-left: 24px;
padding-right: 24px;
}
@media (min-width: 640px) {
.container { .container {
padding-left: 32px; width: 100%;
padding-right: 32px; max-width: 1400px;
margin-left: auto;
margin-right: auto;
padding-left: 24px;
padding-right: 24px;
} }
}
@media (min-width: 640px) {
@media (min-width: 1024px) { .container {
.container { padding-left: 32px;
padding-left: 48px; padding-right: 32px;
padding-right: 48px; }
}
@media (min-width: 1024px) {
.container {
padding-left: 48px;
padding-right: 48px;
}
}
.container-narrow {
max-width: 1200px;
}
.container-wide {
max-width: 1600px;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 14px 32px;
font-size: 13px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
border: none;
cursor: pointer;
transition: all var(--transition-base);
}
.btn-primary {
background: var(--color-cta);
color: var(--color-white);
}
.btn-primary:hover {
background: var(--color-cta-hover);
}
.btn-secondary {
background: transparent;
color: var(--color-foreground);
border: 1px solid var(--color-border-dark);
}
.btn-secondary:hover {
background: var(--color-foreground);
color: var(--color-white);
border-color: var(--color-foreground);
}
.link-underline {
position: relative;
text-decoration: none;
}
.link-underline::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 1px;
background: currentColor;
transition: width var(--transition-base);
}
.link-underline:hover::after {
width: 100%;
}
.text-display {
font-family: var(--font-display);
font-weight: 500;
letter-spacing: -0.02em;
}
.text-body {
font-family: var(--font-body);
}
.text-uppercase {
text-transform: uppercase;
letter-spacing: 0.05em;
}
.text-caption {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.1em;
font-weight: 500;
}
.text-muted {
color: var(--color-foreground-muted);
}
.text-subtle {
color: var(--color-foreground-subtle);
}
.flex-center {
display: flex;
align-items: center;
justify-content: center;
} }
}
.container-narrow {
max-width: 1200px;
}
.container-wide {
max-width: 1600px;
}
/* Section spacing */
.section {
padding-top: var(--space-4xl);
padding-bottom: var(--space-4xl);
}
.section-sm {
padding-top: var(--space-2xl);
padding-bottom: var(--space-2xl);
}
/* Flex utilities */
.flex-center {
display: flex;
align-items: center;
justify-content: center;
} }
/* ============================================ /* ============================================
INTERACTIVE ELEMENTS UTILITIES
============================================ */ ============================================ */
/* Button Base */ @layer utilities {
.btn { .section {
display: inline-flex; padding-top: 96px;
align-items: center; padding-bottom: 96px;
justify-content: center; }
padding: 14px 32px;
font-size: 13px; .section-sm {
font-weight: 500; padding-top: 48px;
text-transform: uppercase; padding-bottom: 48px;
letter-spacing: 0.05em; }
border: none;
cursor: pointer; ::-webkit-scrollbar {
transition: all var(--transition-base); width: 8px;
} }
.btn-primary { ::-webkit-scrollbar-track {
background: var(--color-cta); background: var(--color-background-alt);
color: var(--color-white); }
}
::-webkit-scrollbar-thumb {
.btn-primary:hover { background: var(--color-border-dark);
background: var(--color-cta-hover); border-radius: 4px;
} }
.btn-secondary { ::-webkit-scrollbar-thumb:hover {
background: transparent; background: var(--color-foreground-muted);
color: var(--color-foreground); }
border: 1px solid var(--color-border-dark);
} .animate-fade-in {
animation: fadeIn var(--transition-slow) forwards;
.btn-secondary:hover { }
background: var(--color-foreground);
color: var(--color-white); .animate-slide-up {
border-color: var(--color-foreground); animation: slideUp var(--transition-slow) forwards;
} }
/* Link underline animation */ .animate-slide-in-right {
.link-underline { animation: slideInRight var(--transition-slow) forwards;
position: relative; }
text-decoration: none;
} @keyframes fadeIn {
from { opacity: 0; }
.link-underline::after { to { opacity: 1; }
content: ''; }
position: absolute;
bottom: -2px; @keyframes slideUp {
left: 0; from { opacity: 0; transform: translateY(20px); }
width: 0; to { opacity: 1; transform: translateY(0); }
height: 1px; }
background: currentColor;
transition: width var(--transition-base); @keyframes slideInRight {
} from { opacity: 0; transform: translateX(100%); }
to { opacity: 1; transform: translateX(0); }
.link-underline:hover::after { }
width: 100%;
@keyframes marquee {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
.animate-marquee {
animation: marquee 25s linear infinite;
}
.animate-marquee-slow {
animation: marquee 35s linear infinite;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
} }
/* ============================================ /* ============================================
FORM ELEMENTS REDUCED MOTION
============================================ */ ============================================ */
input, textarea, select {
font-family: var(--font-body);
font-size: 16px;
}
input:focus, textarea:focus, select:focus {
outline: none;
border-color: var(--color-foreground);
}
/* ============================================
SCROLLBAR
============================================ */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--color-background-alt);
}
::-webkit-scrollbar-thumb {
background: var(--color-border-dark);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--color-foreground-muted);
}
/* ============================================
ANIMATIONS
============================================ */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.animate-fade-in {
animation: fadeIn var(--transition-slow) forwards;
}
.animate-slide-up {
animation: slideUp var(--transition-slow) forwards;
}
.animate-slide-in-right {
animation: slideInRight var(--transition-slow) forwards;
}
/* Marquee Animations */
@keyframes marquee {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
.animate-marquee {
animation: marquee 25s linear infinite;
}
.animate-marquee-slow {
animation: marquee 35s linear infinite;
}
/* ============================================
ACCESSIBILITY
============================================ */
/* Focus visible styles */
:focus-visible {
outline: 2px solid var(--color-foreground);
outline-offset: 2px;
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
*, *,
*::before, *::before,
@@ -412,16 +368,3 @@ input:focus, textarea:focus, select:focus {
scroll-behavior: auto; scroll-behavior: auto;
} }
} }
/* Screen reader only */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}

View File

@@ -1,5 +1,5 @@
import "./globals.css"; import "./globals.css";
import type { Metadata } from "next"; import type { Metadata, Viewport } from "next";
import ErrorBoundary from "@/components/providers/ErrorBoundary"; import ErrorBoundary from "@/components/providers/ErrorBoundary";
export const metadata: Metadata = { export const metadata: Metadata = {
@@ -7,7 +7,7 @@ export const metadata: Metadata = {
default: "ManoonOils - Premium Natural Oils for Hair & Skin", default: "ManoonOils - Premium Natural Oils for Hair & Skin",
template: "%s | ManoonOils", template: "%s | ManoonOils",
}, },
description: "Discover our premium collection of natural oils for hair and skin care. Handmade with love.", description: "Discover our premium collection of natural oils for hair and skin care.",
robots: "index, follow", robots: "index, follow",
openGraph: { openGraph: {
title: "ManoonOils - Premium Natural Oils for Hair & Skin", title: "ManoonOils - Premium Natural Oils for Hair & Skin",
@@ -17,6 +17,12 @@ export const metadata: Metadata = {
}, },
}; };
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
maximumScale: 5,
};
// Suppress extension-caused hydration warnings // Suppress extension-caused hydration warnings
const suppressHydrationWarning = true; const suppressHydrationWarning = true;

View File

@@ -3,6 +3,12 @@ import Header from "@/components/layout/Header";
import Footer from "@/components/layout/Footer"; import Footer from "@/components/layout/Footer";
import HeroVideo from "@/components/home/HeroVideo"; import HeroVideo from "@/components/home/HeroVideo";
import ProductCard from "@/components/product/ProductCard"; import ProductCard from "@/components/product/ProductCard";
import TrustBadges from "@/components/home/TrustBadges";
import AsSeenIn from "@/components/home/AsSeenIn";
import ProductReviews from "@/components/product/ProductReviews";
import BeforeAfterGallery from "@/components/home/BeforeAfterGallery";
import ProblemSection from "@/components/home/ProblemSection";
import HowItWorks from "@/components/home/HowItWorks";
export const metadata = { export const metadata = {
title: "ManoonOils - Premium Natural Oils for Hair & Skin", title: "ManoonOils - Premium Natural Oils for Hair & Skin",
@@ -30,6 +36,21 @@ export default async function Homepage() {
{/* Hero Section with Video Background */} {/* Hero Section with Video Background */}
<HeroVideo /> <HeroVideo />
{/* As Seen In */}
<AsSeenIn />
{/* Testimonials Section */}
<ProductReviews />
{/* Trust Badges */}
<TrustBadges />
{/* Problem Section - Create empathy */}
<ProblemSection />
{/* Before/After Gallery */}
<BeforeAfterGallery />
{/* Main Content */} {/* Main Content */}
<div id="main-content" className="scroll-mt-[72px] lg:scroll-mt-[72px]"> <div id="main-content" className="scroll-mt-[72px] lg:scroll-mt-[72px]">
{/* Products Grid Section */} {/* Products Grid Section */}
@@ -69,6 +90,9 @@ export default async function Homepage() {
</section> </section>
)} )}
{/* How It Works */}
<HowItWorks />
{/* Brand Story Section */} {/* Brand Story Section */}
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-[#f8f9fa]"> <section className="py-24 px-4 sm:px-6 lg:px-8 bg-[#f8f9fa]">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
@@ -108,40 +132,58 @@ export default async function Homepage() {
</section> </section>
{/* Benefits Section */} {/* Benefits Section */}
<section className="py-24 px-4 sm:px-6 lg:px-8 bg-white"> <section className="py-24 px-4 sm:px-6 lg:px-8 bg-gradient-to-b from-white to-[#faf9f7]">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
<div className="text-center mb-16"> <div className="text-center mb-16">
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block"> <span className="text-xs uppercase tracking-[0.3em] text-[#c9a962] mb-4 block font-medium">
Why Choose Us Why Choose Us
</span> </span>
<h2 className="text-3xl md:text-4xl font-medium"> <h2 className="text-3xl md:text-4xl lg:text-5xl font-medium text-[#1a1a1a]">
The Manoon Difference The Manoon Difference
</h2> </h2>
<div className="w-24 h-1 bg-gradient-to-r from-[#c9a962] to-[#FFD700] mx-auto mt-6 rounded-full" />
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 lg:gap-12"> <div className="grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8">
{[ {[
{ {
title: "100% Natural", title: "100% Natural",
description: "Pure, cold-pressed oils with no additives or preservatives. Just nature's goodness.", description: "Pure, cold-pressed oils with no additives or preservatives. Just nature's goodness.",
icon: (
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="#7eb89e"/>
<path stroke="#7eb89e" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
),
}, },
{ {
title: "Handcrafted", title: "Handcrafted",
description: "Each batch is carefully prepared by hand to ensure the highest quality.", description: "Each batch is carefully prepared by hand to ensure the highest quality.",
icon: (
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
<path stroke="#c9a962" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" d="M15.182 15.182a4.5 4.5 0 01-6.364 0M21 12a9 9 0 11-18 0 9 9 0 0118 0zM9.75 9.75c0 .414-.168.75-.375.75S9 10.164 9 9.75 9.168 9 9.375 9s.375.336.375.75zm-.375 0h.008v.015h-.008V9.75zm5.625 0c0 .414-.168.75-.375.75s-.375-.336-.375-.75.168-.75.375-.75.375.336.375.75zm-.375 0h.008v.015h-.008V9.75z"/>
</svg>
),
}, },
{ {
title: "Sustainable", title: "Sustainable",
description: "Ethically sourced ingredients and eco-friendly packaging for a better planet.", description: "Ethically sourced ingredients and eco-friendly packaging for a better planet.",
icon: (
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
<path stroke="#e8967a" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" d="M12.75 3.03v.568c0 .334.148.65.405.864l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 01-1.161.886l-.143.048a1.107 1.107 0 00-.57 1.664c.369.555.169 1.307-.427 1.605L9 13.125l.423 1.059a.956.956 0 11-1.652.928l-.714-.093a1.125 1.125 0 00-1.906.172L4.5 15.75l-.612.153M12.75 3.031l.002-.004m0 0a8.955 8.955 0 00-4.943.834 8.974 8.974 0 004.943.834m4.943-.834a8.955 8.955 0 00-4.943-.834c2.687 0 5.18.948 7.161 2.664a8.974 8.974 0 014.943-.834z"/>
</svg>
),
}, },
].map((benefit, index) => ( ].map((benefit, index) => (
<div key={index} className="text-center"> <div
<div className="w-16 h-16 mx-auto mb-6 rounded-full bg-[#e8f0f5] flex items-center justify-center"> key={index}
<span className="text-2xl font-medium text-[#1a1a1a]"> className="relative text-center p-8 bg-white rounded-3xl shadow-lg border border-[#f0ede8] hover:shadow-2xl hover:border-[#c9a962]/30 transition-all duration-500 group"
{String(index + 1).padStart(2, '0')} >
</span> <div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-md border border-[#e8e4dc] group-hover:border-[#c9a962]/50 transition-colors duration-300">
{benefit.icon}
</div> </div>
<h3 className="text-xl font-medium mb-3">{benefit.title}</h3> <h3 className="text-xl font-semibold text-[#1a1a1a] mb-3">{benefit.title}</h3>
<p className="text-[#666666]">{benefit.description}</p> <p className="text-sm text-[#666666] leading-relaxed">{benefit.description}</p>
</div> </div>
))} ))}
</div> </div>
@@ -162,15 +204,15 @@ export default async function Homepage() {
Subscribe to receive exclusive offers, beauty tips, and be the first to know about new products. Subscribe to receive exclusive offers, beauty tips, and be the first to know about new products.
</p> </p>
{/* Newsletter Form - Centered */} {/* Newsletter Form - Centered */}
<form className="flex flex-col sm:flex-row items-center justify-center w-full sm:w-auto gap-0"> <form className="flex flex-col sm:flex-row items-stretch justify-center max-w-md mx-auto gap-0">
<input <input
type="email" type="email"
placeholder="Enter your email" placeholder="Enter your email"
className="w-full sm:w-64 md:w-80 px-5 h-14 bg-white/10 border border-white/20 border-b-0 sm:border-b border-r-0 sm:border-r border-white/20 text-white placeholder:text-white/50 focus:border-white focus:outline-none transition-colors text-base text-center sm:text-left" className="flex-1 min-w-0 px-5 h-14 bg-white/10 border border-white/20 border-b-0 sm:border-b border-r-0 sm:border-r border-white/20 text-white placeholder:text-white/50 focus:border-white focus:outline-none transition-colors text-base text-center sm:text-left rounded-t sm:rounded-l sm:rounded-tr-none"
/> />
<button <button
type="submit" type="submit"
className="w-full sm:w-auto px-8 h-14 bg-white text-black text-sm uppercase tracking-[0.1em] font-medium hover:bg-white/90 transition-colors whitespace-nowrap flex-shrink-0" className="px-8 h-14 bg-white text-black text-sm uppercase tracking-[0.1em] font-medium hover:bg-white/90 transition-colors whitespace-nowrap flex-shrink-0 rounded-b sm:rounded-r sm:rounded-bl-none"
> >
Subscribe Subscribe
</button> </button>

View File

@@ -24,7 +24,7 @@ export default async function ProductsPage({ params }: ProductsPageProps) {
<main className="min-h-screen bg-white"> <main className="min-h-screen bg-white">
{/* Page Header */} {/* Page Header */}
<div className="pt-[140px] lg:pt-[160px]"> <div className="pt-[72px] lg:pt-[72px]">
<div className="border-b border-[#e5e5e5]"> <div className="border-b border-[#e5e5e5]">
<div className="container py-8 md:py-12"> <div className="container py-8 md:py-12">
<div className="flex flex-col md:flex-row md:items-end md:justify-between gap-4"> <div className="flex flex-col md:flex-row md:items-end md:justify-between gap-4">

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
@@ -27,11 +27,15 @@ export default function CartDrawer() {
const lines = getLines(); const lines = getLines();
const total = getTotal(); const total = getTotal();
const lineCount = getLineCount(); const lineCount = getLineCount();
const [initialized, setInitialized] = useState(false);
// Initialize checkout on mount // Initialize checkout on mount (only once)
useEffect(() => { useEffect(() => {
initCheckout(); if (!initialized) {
}, [initCheckout]); initCheckout();
setInitialized(true);
}
}, [initialized]);
// Lock body scroll when cart is open // Lock body scroll when cart is open
useEffect(() => { useEffect(() => {

View File

@@ -0,0 +1,88 @@
"use client";
import { motion } from "framer-motion";
const mediaLogos = [
{ name: "VOGUE", style: "serif" },
{ name: "Allure", style: "sans" },
{ name: "ELLE", style: "serif" },
{ name: "COSMOPOLITAN", style: "serif" },
{ name: "Bazaar", style: "serif" },
{ name: "GLAMOUR", style: "serif" },
{ name: "WOMEN'S HEALTH", style: "sans" },
{ name: "Shape", style: "sans" },
];
function LogoItem({ name }: { name: string }) {
const isSerif = name === "VOGUE" || name === "ELLE" || name === "COSMOPOLITAN" || name === "Bazaar" || name === "GLAMOUR";
return (
<div className="flex items-center justify-center px-10 py-4 grayscale opacity-40 hover:grayscale-0 hover:opacity-100 transition-all duration-500 flex-shrink-0">
<span
className={`
text-xl md:text-2xl tracking-[0.15em] text-white font-bold
${isSerif ? 'font-serif italic' : 'font-sans uppercase'}
`}
style={{
textShadow: '0 0 20px rgba(255,255,255,0.1)',
}}
>
{name}
</span>
</div>
);
}
export default function AsSeenIn() {
return (
<section className="py-12 bg-[#1a1a1a] overflow-hidden border-y border-white/10">
<div className="container mx-auto px-4 mb-8">
<motion.p
className="text-center text-[10px] uppercase tracking-[0.4em] text-[#c9a962] font-bold"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
As Featured In
</motion.p>
</div>
{/* Scrolling Marquee */}
<div className="relative">
{/* Left gradient fade */}
<div className="absolute left-0 top-0 bottom-0 w-32 bg-gradient-to-r from-[#1a1a1a] to-transparent z-10 pointer-events-none" />
{/* Right gradient fade */}
<div className="absolute right-0 top-0 bottom-0 w-32 bg-gradient-to-l from-[#1a1a1a] to-transparent z-10 pointer-events-none" />
{/* Marquee container */}
<div className="flex overflow-hidden">
<motion.div
className="flex items-center gap-16"
animate={{
x: [0, -50 + "%"],
}}
transition={{
x: {
repeat: Infinity,
repeatType: "loop",
duration: 30,
ease: "linear",
},
}}
>
{/* First set of logos */}
{mediaLogos.map((logo, index) => (
<LogoItem key={`first-${index}`} name={logo.name} />
))}
{/* Duplicate for seamless loop */}
{mediaLogos.map((logo, index) => (
<LogoItem key={`second-${index}`} name={logo.name} />
))}
</motion.div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,239 @@
"use client";
import { motion } from "framer-motion";
import { useState, useRef } from "react";
const results = [
{
id: 1,
name: "Facial Skin Transformation",
beforeImg: "https://minio-api.nodecrew.me/saleor/marketing/use_case_2.webp",
afterImg: "https://minio-api.nodecrew.me/saleor/marketing/use_case_2_1.webp",
timeline: "4-6 Weeks",
rating: 5,
reviewCount: 2847,
},
{
id: 2,
name: "Skin Radiance Transformation",
beforeImg: "https://minio-api.nodecrew.me/saleor/marketing/use_case_3.webp",
afterImg: "https://minio-api.nodecrew.me/saleor/marketing/use_case_3_1.webp",
timeline: "6-8 Weeks",
rating: 5,
reviewCount: 1856,
},
];
function BeforeAfterSlider({ result }: { result: typeof results[0] }) {
const [sliderPosition, setSliderPosition] = useState(50);
const containerRef = useRef<HTMLDivElement>(null);
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
setSliderPosition(Math.max(0, Math.min(100, x)));
};
const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => {
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = ((e.touches[0].clientX - rect.left) / rect.width) * 100;
setSliderPosition(Math.max(0, Math.min(100, x)));
};
return (
<div className="flex-1 min-w-0">
{/* Before/After Slider */}
<div
ref={containerRef}
className="relative aspect-[4/3] rounded-2xl overflow-hidden shadow-2xl cursor-ew-resize select-none"
onMouseMove={handleMouseMove}
onTouchMove={handleTouchMove}
>
{/* After Image */}
<img
src={result.afterImg}
alt="After - Smooth skin"
className="absolute inset-0 w-full h-full object-cover"
/>
{/* Before Image (clipped) */}
<div
className="absolute inset-0 overflow-hidden"
style={{ width: `${sliderPosition}%` }}
>
<img
src={result.beforeImg}
alt="Before - Wrinkled skin"
className="absolute inset-0 h-full object-cover"
style={{ width: `${100 / (sliderPosition / 100)}%`, maxWidth: 'none' }}
/>
</div>
{/* Slider Handle */}
<div
className="absolute top-0 bottom-0 w-1 bg-white shadow-lg cursor-ew-resize"
style={{ left: `${sliderPosition}%`, transform: 'translateX(-50%)' }}
>
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-10 h-10 bg-white rounded-full shadow-lg flex items-center justify-center">
<svg className="w-5 h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l4-4 4 4m0 6l-4 4-4-4" />
</svg>
</div>
</div>
{/* Labels */}
<div className="absolute top-3 left-3 bg-black/70 text-white px-3 py-1.5 rounded-full text-xs font-medium backdrop-blur-sm">
BEFORE
</div>
<div className="absolute top-3 right-3 bg-black/70 text-white px-3 py-1.5 rounded-full text-xs font-medium backdrop-blur-sm">
AFTER
</div>
</div>
{/* Timeline and Rating */}
<div className="flex items-center justify-center gap-4 mt-4">
<div className="flex items-center gap-1.5">
<svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="text-xs font-medium">{result.timeline}</span>
</div>
<div className="flex items-center gap-1.5">
<div className="flex">
{[1, 2, 3, 4, 5].map((star) => (
<svg key={star} className="w-4 h-4 fill-yellow-400 text-yellow-400" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
))}
</div>
<span className="text-xs text-[#666666]">({result.reviewCount.toLocaleString()})</span>
</div>
</div>
{/* Verified Badge */}
<div className="flex items-center justify-center gap-1.5 mt-2">
<svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
<span className="text-xs text-green-700 font-medium">Verified</span>
</div>
</div>
);
}
export default function BeforeAfterGallery() {
const [selectedIndex, setSelectedIndex] = useState(0);
const goToPrev = () => {
setSelectedIndex(prev => prev === 0 ? results.length - 1 : prev - 1);
};
const goToNext = () => {
setSelectedIndex(prev => prev === results.length - 1 ? 0 : prev + 1);
};
return (
<section className="py-24 bg-[#faf9f7]">
<div className="container mx-auto px-4">
<motion.div
className="text-center mb-12"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
Real Results
</span>
<h2 className="text-3xl md:text-4xl font-medium mb-4">
See the Transformation
</h2>
</motion.div>
{/* Desktop: Two transformations side by side */}
<div className="hidden md:flex gap-6 max-w-6xl mx-auto">
{results.map((result, index) => (
<motion.div
key={result.id}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: index * 0.1 }}
className="flex-1 min-w-0"
>
<BeforeAfterSlider result={result} />
</motion.div>
))}
</div>
{/* Mobile: Carousel with one transformation at a time */}
<div className="md:hidden relative max-w-md mx-auto">
<div className="overflow-hidden">
<motion.div
key={selectedIndex}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
>
<BeforeAfterSlider result={results[selectedIndex]} />
</motion.div>
</div>
{/* Carousel Navigation */}
<button
onClick={goToPrev}
className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-2 w-10 h-10 bg-white rounded-full shadow-lg flex items-center justify-center"
aria-label="Previous transformation"
>
<svg className="w-5 h-5 text-gray-700" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</button>
<button
onClick={goToNext}
className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-2 w-10 h-10 bg-white rounded-full shadow-lg flex items-center justify-center"
aria-label="Next transformation"
>
<svg className="w-5 h-5 text-gray-700" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</button>
{/* Dot Indicators */}
<div className="flex justify-center gap-2 mt-6">
{results.map((_, index) => (
<button
key={index}
onClick={() => setSelectedIndex(index)}
className={`w-2 h-2 rounded-full transition-all ${
selectedIndex === index ? "bg-black w-4" : "bg-gray-300"
}`}
aria-label={`Go to transformation ${index + 1}`}
/>
))}
</div>
</div>
{/* CTA */}
<motion.div
className="text-center mt-12"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.4 }}
>
<a
href="/products"
className="inline-block px-10 py-4 bg-black text-white text-[13px] uppercase tracking-[0.15em] font-semibold hover:bg-[#333] transition-colors"
>
Start Your Transformation
</a>
</motion.div>
</div>
</section>
);
}

View File

@@ -13,85 +13,113 @@ export default function HeroVideo() {
}; };
return ( return (
<section className="relative h-screen w-full overflow-hidden"> <section className="relative min-h-screen w-full overflow-hidden">
{/* Video Background */} {/* Background Image with Overlay */}
<div className="absolute inset-0">
<video
autoPlay
muted
loop
playsInline
poster="/images/hero-poster.jpg"
className="w-full h-full object-cover"
>
{/* Placeholder - Add actual video files when available */}
{/* <source src="/videos/hero.webm" type="video/webm" /> */}
{/* <source src="/videos/hero.mp4" type="video/mp4" /> */}
</video>
{/* Gradient Overlay */}
<div className="absolute inset-0 bg-gradient-to-b from-black/30 via-black/20 to-black/50" />
</div>
{/* Fallback Background (shown when video isn't loaded) */}
<div <div
className="absolute inset-0 bg-cover bg-center bg-no-repeat" className="absolute inset-0 bg-cover bg-center bg-no-repeat"
style={{ style={{
backgroundImage: `url('https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?q=80&w=2574&auto=format&fit=crop')`, backgroundImage: `url('https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?q=80&w=2574&auto=format&fit=crop')`,
}} }}
> >
<div className="absolute inset-0 bg-black/40" /> <div className="absolute inset-0 bg-gradient-to-b from-black/50 via-black/40 to-black/70" />
</div> </div>
{/* Content */} {/* Content */}
<div className="relative z-10 h-full flex flex-col items-center justify-center text-center text-white px-4"> <div className="relative z-10 min-h-screen flex flex-col items-center justify-center text-center text-white px-4 py-20">
<motion.div <motion.div
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.3 }} transition={{ duration: 0.8, delay: 0.3 }}
className="max-w-4xl mx-auto" className="max-w-4xl mx-auto"
> >
{/* Tagline */} {/* Social Proof Micro */}
<motion.span
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.5 }}
className="inline-block text-xs md:text-sm uppercase tracking-[0.3em] mb-6 text-white/90"
>
Premium Organic Oils
</motion.span>
{/* Main Heading */}
<motion.h1
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.7 }}
className="text-5xl md:text-7xl lg:text-8xl font-medium mb-6 tracking-tight"
>
ManoonOils
</motion.h1>
{/* Subtitle */}
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.9 }}
className="text-lg md:text-xl text-white/80 mb-10 font-light max-w-xl mx-auto"
>
For hair and skin care
</motion.p>
{/* CTA Button */}
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 1.1 }} transition={{ duration: 0.6, delay: 0.4 }}
className="flex items-center justify-center gap-2 mb-6"
>
<div className="flex">
{[1, 2, 3, 4, 5].map((star) => (
<svg key={star} className="w-4 h-4 fill-yellow-400 text-yellow-400" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
))}
</div>
<span className="text-sm text-white/80">
Loved by 50,000+ customers worldwide
</span>
</motion.div>
{/* Main Heading - Outcome Focused */}
<motion.h1
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.5 }}
className="text-4xl md:text-6xl lg:text-7xl font-medium mb-6 tracking-tight leading-tight"
>
Transform Your Hair & Skin
<br />
<span className="text-white/90">with 100% Natural Oils</span>
</motion.h1>
{/* Subtitle - Expands on how */}
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.7 }}
className="text-lg md:text-xl text-white/80 mb-8 font-light max-w-2xl mx-auto leading-relaxed"
>
Cold-pressed, organic oils handcrafted with love.
No additives, no preservativesjust nature&apos;s purest goodness for your daily beauty ritual.
</motion.p>
{/* CTA Button - Action verb + value */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.9 }}
className="flex flex-col sm:flex-row items-center justify-center gap-4"
> >
<Link <Link
href="/products" href="/products"
className="inline-block px-10 py-4 bg-white text-black text-[13px] uppercase tracking-[0.1em] font-medium hover:bg-white/90 transition-colors duration-300" className="inline-block px-10 py-4 bg-white text-black text-[13px] uppercase tracking-[0.15em] font-semibold hover:bg-white/90 transition-all duration-300 hover:scale-105 shadow-lg hover:shadow-xl"
> >
Shop Now Transform My Hair & Skin
</Link> </Link>
<Link
href="/about"
className="inline-block px-10 py-4 border border-white/50 text-white text-[13px] uppercase tracking-[0.15em] font-medium hover:bg-white/10 transition-all duration-300"
>
Learn Our Story
</Link>
</motion.div>
{/* Trust Indicators */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.2, duration: 0.8 }}
className="flex flex-wrap items-center justify-center gap-6 mt-12 text-sm text-white/60"
>
<div className="flex items-center gap-2">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
<span>30-Day Money Back</span>
</div>
<div className="flex items-center gap-2">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
<span>Free Shipping Over 3,000 RSD</span>
</div>
<div className="flex items-center gap-2">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
<span>Cruelty Free</span>
</div>
</motion.div> </motion.div>
</motion.div> </motion.div>
</div> </div>
@@ -102,7 +130,7 @@ export default function HeroVideo() {
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ delay: 1.5, duration: 0.8 }} transition={{ delay: 1.5, duration: 0.8 }}
onClick={scrollToContent} onClick={scrollToContent}
className="absolute bottom-10 left-1/2 -translate-x-1/2 text-white/80 hover:text-white transition-colors cursor-pointer" className="absolute bottom-10 left-1/2 -translate-x-1/2 text-white/60 hover:text-white transition-colors cursor-pointer"
aria-label="Scroll to content" aria-label="Scroll to content"
> >
<motion.div <motion.div

View File

@@ -0,0 +1,128 @@
"use client";
import { motion } from "framer-motion";
export default function HowItWorks() {
const steps = [
{
number: "01",
title: "Choose Your Oil",
description: "Select from our collection of pure, cold-pressed oils formulated for your specific hair and skin needs.",
icon: (
<svg className="w-8 h-8" viewBox="0 0 24 24" fill="none" stroke="#c9a962" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 10.5V6a3.75 3.75 0 10-7.5 0v4.5m11.356-1.993l1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 01-1.12-1.243l1.264-12A1.125 1.125 0 015.513 7.5h12.974c.576 0 1.059.435 1.119 1.007z" />
</svg>
),
},
{
number: "02",
title: "Apply Daily",
description: "Massage a few drops into damp hair or skin. Our oils absorb instantly—never greasy, always nourishing.",
icon: (
<svg className="w-8 h-8" viewBox="0 0 24 24" fill="none" stroke="#c9a962" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
</svg>
),
},
{
number: "03",
title: "See Results",
description: "Experience transformation in 4-6 weeks. Shinier hair, radiant skin, and confidence that glows.",
icon: (
<svg className="w-8 h-8" viewBox="0 0 24 24" fill="#FFD700">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
),
},
];
return (
<section className="py-24 bg-gradient-to-b from-white to-[#faf9f7]">
<div className="container mx-auto px-4">
<motion.div
className="text-center mb-20"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<span className="text-xs uppercase tracking-[0.3em] text-[#c9a962] mb-4 block font-medium">
Simple Process
</span>
<h2 className="text-4xl md:text-5xl font-medium text-[#1a1a1a]">
How ManoonOils Works
</h2>
<div className="w-24 h-1 bg-gradient-to-r from-[#c9a962] to-[#FFD700] mx-auto mt-6 rounded-full" />
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 lg:gap-16 max-w-6xl mx-auto">
{steps.map((step, index) => (
<motion.div
key={index}
className="relative text-center group"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.15 }}
>
{/* Connector line (not on last item) */}
{index < steps.length - 1 && (
<div className="hidden md:block absolute top-16 left-[55%] w-[90%] h-[2px]">
<div className="absolute inset-0 bg-gradient-to-r from-[#c9a962]/40 to-transparent rounded-full" />
<motion.div
className="absolute inset-y-0 left-0 w-2 bg-[#FFD700] rounded-full"
initial={{ scaleX: 0 }}
whileInView={{ scaleX: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: 0.5 + index * 0.2 }}
style={{ originX: 0 }}
/>
</div>
)}
{/* Step card */}
<div className="relative p-8 bg-white rounded-3xl shadow-lg border border-[#f0ede8] hover:shadow-2xl hover:border-[#c9a962]/30 transition-all duration-500">
{/* Number badge */}
<div className="absolute -top-5 left-1/2 -translate-x-1/2">
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#c9a962] to-[#FFD700] flex items-center justify-center shadow-lg">
<span className="text-white text-lg font-bold">{step.number}</span>
</div>
</div>
{/* Icon */}
<div className="w-20 h-20 mx-auto mt-4 mb-6 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center border border-[#e8e4dc] group-hover:border-[#c9a962]/50 transition-colors duration-300">
{step.icon}
</div>
<h3 className="text-xl font-semibold text-[#1a1a1a] mb-3">{step.title}</h3>
<p className="text-[#666666] text-sm leading-relaxed max-w-xs mx-auto">
{step.description}
</p>
</div>
</motion.div>
))}
</div>
{/* CTA */}
<motion.div
className="text-center mt-20"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.3 }}
>
<a
href="/products"
className="group relative inline-flex items-center gap-3 px-12 py-5 bg-gradient-to-r from-[#1a1a1a] to-[#333333] text-white text-[13px] uppercase tracking-[0.2em] font-semibold hover:from-[#c9a962] hover:to-[#FFD700] transition-all duration-500 rounded-full shadow-lg hover:shadow-xl"
>
<span>Start Your Transformation</span>
<svg className="w-4 h-4 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
<path strokeLinecap="round" strokeLinejoin="round" d="M17.25 8.25L21 12m0 0l-3.75 3.75M21 12H3" />
</svg>
</a>
</motion.div>
</div>
</section>
);
}

View File

@@ -0,0 +1,81 @@
"use client";
import { motion } from "framer-motion";
export default function ProblemSection() {
return (
<section className="py-24 bg-gradient-to-b from-[#fefcfb] to-[#faf9f7]">
<div className="container mx-auto px-4">
<motion.div
className="max-w-3xl mx-auto text-center"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<span className="text-xs uppercase tracking-[0.3em] text-[#c9a962] mb-4 block font-medium">
The Problem
</span>
<h2 className="text-3xl md:text-4xl lg:text-5xl font-medium mb-6 leading-tight text-[#1a1a1a]">
Tired of Hair & Skin Products That Don&apos;t Deliver?
</h2>
<p className="text-[#666666] text-lg max-w-xl mx-auto">
You deserve better than products filled with harsh chemicals and empty promises
</p>
<div className="w-16 h-1 bg-gradient-to-r from-[#c9a962] to-[#FFD700] mx-auto mt-8 rounded-full" />
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 lg:gap-8 max-w-5xl mx-auto mt-16">
{[
{
icon: (
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path stroke="#c9a962" strokeLinecap="round" strokeLinejoin="round" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
problem: "Dry, Damaged Hair",
description: "Products leave your hair brittle, frizzy, and breaking despite expensive treatments",
},
{
icon: (
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path stroke="#e8967a" strokeLinecap="round" strokeLinejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
),
problem: "Confusing Ingredients",
description: "Can't pronounce what's in your skincare. parabens, sulfates, synthetic fragrances—dangerous toxins",
},
{
icon: (
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none" strokeWidth="1.5">
<path stroke="#7eb89e" strokeLinecap="round" strokeLinejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
</svg>
),
problem: "No Real Results",
description: "Countless products promise miracles but deliver nothing but empty promises and wasted money",
},
].map((item, index) => (
<motion.div
key={index}
className="relative text-center p-8 bg-white rounded-3xl shadow-lg border border-[#f0ede8] hover:shadow-2xl hover:border-[#c9a962]/30 transition-all duration-500 group"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{ y: -5 }}
>
{/* Decorative top border */}
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-20 h-1 bg-gradient-to-r from-[#c9a962] to-[#FFD700] rounded-b-full opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
<div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-md border border-[#e8e4dc] group-hover:border-[#c9a962]/50 transition-colors duration-300">
{item.icon}
</div>
<h3 className="text-lg font-semibold text-[#1a1a1a] mb-3">{item.problem}</h3>
<p className="text-sm text-[#666666] leading-relaxed">{item.description}</p>
</motion.div>
))}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,87 @@
"use client";
import { motion } from "framer-motion";
const badges = [
{
icon: (
<svg className="w-6 h-6 text-yellow-400" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
),
stats: "4.9/5",
label: "Average Rating",
subtext: "Based on 1000+ reviews",
},
{
icon: (
<svg className="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="#c9a962" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
</svg>
),
stats: "50,000+",
label: "Happy Customers",
subtext: "Worldwide",
},
{
icon: (
<svg className="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="#7eb89e" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" />
</svg>
),
stats: "100%",
label: "Natural Ingredients",
subtext: "No additives",
},
{
icon: (
<svg className="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="#e8967a" strokeWidth="1.5">
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 18.75a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 01-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h1.125c.621 0 1.129-.504 1.09-1.124a17.902 17.902 0 00-3.213-9.193 2.056 2.056 0 00-1.58-.86H14.25M16.5 18.75h-2.25m0-11.177v-.958c0-.568-.422-1.048-.987-1.106a48.554 48.554 0 00-10.026 0 1.106 1.106 0 00-.987 1.106v7.635m12-6.677v6.677m0 4.5v-4.5m0 0h-12" />
</svg>
),
stats: "Free",
label: "Shipping",
subtext: "Orders over 3,000 RSD",
},
];
export default function TrustBadges() {
return (
<section className="py-16 bg-gradient-to-b from-[#fefcfb] to-[#faf9f7]">
<div className="container mx-auto px-4">
<motion.div
className="grid grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-6"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
{badges.map((badge, index) => (
<motion.div
key={index}
className="flex flex-col items-center text-center p-5 bg-white rounded-2xl shadow-md border border-[#f0ede8] hover:shadow-xl hover:border-[#c9a962]/30 transition-all duration-300"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.4, delay: index * 0.1 }}
whileHover={{ y: -3 }}
>
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-sm mb-4 border border-[#e8e4dc]">
{badge.icon}
</div>
<p className="text-2xl lg:text-3xl font-bold bg-gradient-to-r from-[#1a1a1a] to-[#4a4a4a] bg-clip-text text-transparent tracking-tight">
{badge.stats}
</p>
<p className="text-sm font-semibold text-[#1a1a1a] mt-1">
{badge.label}
</p>
<p className="text-xs text-[#888888] mt-0.5">
{badge.subtext}
</p>
</motion.div>
))}
</motion.div>
</div>
</section>
);
}

View File

@@ -53,68 +53,66 @@ export default function Header() {
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${ className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
scrolled scrolled
? "bg-white/95 backdrop-blur-md shadow-sm" ? "bg-white/95 backdrop-blur-md shadow-sm"
: "bg-transparent" : "bg-white/80 backdrop-blur-sm"
}`} }`}
> >
<div className="container"> <div className="relative flex items-center justify-between h-[72px] px-4 lg:px-6">
<div className="flex items-center justify-between h-[72px]"> {/* Mobile Menu Button */}
{/* Mobile Menu Button */} <button
className="lg:hidden p-2 -ml-2 hover:bg-black/5 rounded-full transition-colors"
onClick={() => setMobileMenuOpen(true)}
aria-label="Open menu"
>
<Menu className="w-5 h-5" />
</button>
{/* Left side - Desktop Nav */}
<nav className="hidden lg:flex items-center gap-10">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="text-[13px] uppercase tracking-[0.05em] text-[#1a1a1a] hover:text-[#666666] transition-colors relative group"
>
{link.label}
<span className="absolute -bottom-1 left-0 w-0 h-[1px] bg-current transition-all duration-300 group-hover:w-full" />
</Link>
))}
</nav>
{/* Logo - Centered (absolute on desktop, flex on mobile) */}
<Link href="/" className="flex-shrink-0 lg:absolute lg:left-1/2 lg:-translate-x-1/2">
<Image
src="https://minio-api.nodecrew.me/manoon-media/2024/09/cropped-manoon-logo_256x-1-1.png"
alt="ManoonOils"
width={150}
height={40}
className="h-7 w-auto object-contain"
priority
/>
</Link>
{/* Right side - Icons */}
<div className="flex items-center gap-1">
<button <button
className="lg:hidden p-2 -ml-2 hover:bg-black/5 rounded-full transition-colors" className="p-2 hover:bg-black/5 rounded-full transition-colors hidden sm:block"
onClick={() => setMobileMenuOpen(true)} aria-label="Account"
aria-label="Open menu"
> >
<Menu className="w-5 h-5" /> <User className="w-5 h-5" strokeWidth={1.5} />
</button> </button>
{/* Logo */} <button
<Link href="/" className="flex-shrink-0 lg:absolute lg:left-1/2 lg:-translate-x-1/2"> className="p-2 hover:bg-black/5 rounded-full transition-colors relative"
<Image onClick={toggleCart}
src="https://minio-api.nodecrew.me/manoon-media/2024/09/cropped-manoon-logo_256x-1-1.png" aria-label="Open cart"
alt="ManoonOils" >
width={150} <ShoppingBag className="w-5 h-5" strokeWidth={1.5} />
height={40} {itemCount > 0 && (
className="h-7 w-auto object-contain" <span className="absolute -top-0.5 -right-0.5 bg-black text-white text-[10px] w-[18px] h-[18px] rounded-full flex items-center justify-center font-medium">
priority {itemCount > 99 ? "99+" : itemCount}
/> </span>
</Link> )}
</button>
{/* Desktop Navigation - Centered */}
<nav className="hidden lg:flex items-center gap-10 mx-auto">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="text-[13px] uppercase tracking-[0.05em] text-[#1a1a1a] hover:text-[#666666] transition-colors relative group"
>
{link.label}
<span className="absolute -bottom-1 left-0 w-0 h-[1px] bg-current transition-all duration-300 group-hover:w-full" />
</Link>
))}
</nav>
{/* Icons */}
<div className="flex items-center gap-1">
<button
className="p-2 hover:bg-black/5 rounded-full transition-colors hidden sm:block"
aria-label="Account"
>
<User className="w-5 h-5" strokeWidth={1.5} />
</button>
<button
className="p-2 hover:bg-black/5 rounded-full transition-colors relative"
onClick={toggleCart}
aria-label="Open cart"
>
<ShoppingBag className="w-5 h-5" strokeWidth={1.5} />
{itemCount > 0 && (
<span className="absolute -top-0.5 -right-0.5 bg-black text-white text-[10px] w-[18px] h-[18px] rounded-full flex items-center justify-center font-medium">
{itemCount > 99 ? "99+" : itemCount}
</span>
)}
</button>
</div>
</div> </div>
</div> </div>
</header> </header>

View File

@@ -0,0 +1,93 @@
"use client";
import { motion } from "framer-motion";
interface ProductBenefitsProps {
locale?: string;
}
const benefits = [
{
icon: (
<svg className="w-10 h-10" fill="none" viewBox="0 0 24 24" strokeWidth={1.5}>
<path stroke="#c9a962" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" />
<path stroke="#c9a962" strokeLinecap="round" strokeLinejoin="round" d="M15.75 10.5V6a3.75 3.75 0 10-7.5 0v4.5m11.356-1.993l1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 01-1.12-1.243l1.264-12A1.125 1.125 0 015.513 7.5h12.974c.576 0 1.059.435 1.119 1.007zM8.625 10.5a.375.375 0 11-.75 0 .375.375 0 01.75 0zm7.5 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" />
</svg>
),
title: "Pure & Natural",
description: "100% natural ingredients with no additives or preservatives",
},
{
icon: (
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" fill="#e8967a"/>
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" stroke="#c9a962" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
),
title: "Cruelty Free",
description: "Never tested on animals, ethically sourced ingredients",
},
{
icon: (
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="#7eb89e"/>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" stroke="#c9a962" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
),
title: "Made with Love",
description: "Handcrafted in small batches for maximum quality",
},
{
icon: (
<svg className="w-10 h-10" viewBox="0 0 24 24" fill="none">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" fill="#c9a962"/>
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" stroke="#b8944f" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
),
title: "Visible Results",
description: "See noticeable improvements in 4-6 weeks",
},
];
export default function ProductBenefits({ locale = "SR" }: ProductBenefitsProps) {
return (
<section className="py-20 bg-gradient-to-b from-white to-[#faf9f7]">
<div className="container mx-auto px-4">
<motion.div
className="text-center mb-12"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<span className="text-xs uppercase tracking-[0.2em] text-[#c9a962] mb-3 block font-medium">
{locale === "EN" ? "Why Choose This Product" : "Zašto odabrati ovaj proizvod"}
</span>
<h2 className="text-3xl md:text-4xl font-medium">
{locale === "EN" ? "The Manoon Difference" : "Manoon razlika"}
</h2>
</motion.div>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8 max-w-5xl mx-auto">
{benefits.map((benefit, index) => (
<motion.div
key={index}
className="text-center p-6 bg-white rounded-2xl shadow-md border border-[#f0ede8] hover:shadow-xl hover:border-[#c9a962]/30 transition-all duration-300"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.4, delay: index * 0.1 }}
whileHover={{ y: -5 }}
>
<div className="w-20 h-20 mx-auto mb-5 rounded-2xl bg-gradient-to-br from-[#faf9f7] to-[#f5f0e8] flex items-center justify-center shadow-sm border border-[#e8e4dc]">
{benefit.icon}
</div>
<h3 className="text-base font-medium mb-2 text-[#1a1a1a]">{benefit.title}</h3>
<p className="text-sm text-[#666666] leading-relaxed">{benefit.description}</p>
</motion.div>
))}
</div>
</div>
</section>
);
}

View File

@@ -27,14 +27,13 @@ export default function ProductCard({ product, index = 0, locale = "SR" }: Produ
> >
<Link href={`/products/${localized.slug}`} className="group block"> <Link href={`/products/${localized.slug}`} className="group block">
{/* Image Container */} {/* Image Container */}
<div className="relative aspect-square bg-[#f8f9fa] overflow-hidden mb-4"> <div className="relative w-full aspect-square bg-[#f8f9fa] overflow-hidden mb-4">
{image ? ( {image ? (
<Image <img
src={image} src={image}
alt={localized.name} alt={localized.name}
fill className="w-full h-full object-cover object-center transition-transform duration-700 ease-out group-hover:scale-105"
className="object-cover transition-transform duration-700 ease-out group-hover:scale-105" loading="lazy"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw"
/> />
) : ( ) : (
<div className="absolute inset-0 flex items-center justify-center text-[#999999]"> <div className="absolute inset-0 flex items-center justify-center text-[#999999]">

View File

@@ -1,14 +1,21 @@
"use client"; "use client";
import { useState } from "react"; import { useState, useEffect } from "react";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import { ChevronDown, Star, Minus, Plus } from "lucide-react"; import { ChevronDown, Star, Minus, Plus } from "lucide-react";
import type { Product } from "@/types/saleor"; import type { Product } from "@/types/saleor";
import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore"; import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore";
import { getProductPrice, getLocalizedProduct } from "@/lib/saleor"; import { getProductPrice, getProductPriceAmount, getLocalizedProduct, formatPrice } from "@/lib/saleor";
import ProductCard from "@/components/product/ProductCard"; import ProductCard from "@/components/product/ProductCard";
import ProductBenefits from "@/components/product/ProductBenefits";
import ProductReviews from "@/components/product/ProductReviews";
import AsSeenIn from "@/components/home/AsSeenIn";
import TrustBadges from "@/components/home/TrustBadges";
import BeforeAfterGallery from "@/components/home/BeforeAfterGallery";
import HowItWorks from "@/components/home/HowItWorks";
import NewsletterSection from "@/components/home/NewsletterSection";
interface ProductDetailProps { interface ProductDetailProps {
product: Product; product: Product;
@@ -69,12 +76,12 @@ function StarRating({ rating = 5, count = 0 }: { rating?: number; count?: number
{[...Array(5)].map((_, i) => ( {[...Array(5)].map((_, i) => (
<Star <Star
key={i} key={i}
className={`w-4 h-4 ${i < rating ? 'fill-black text-black' : 'text-[#e5e5e5]'}`} className={`w-4 h-4 ${i < rating ? 'fill-yellow-400 text-yellow-400' : 'text-gray-300'}`}
/> />
))} ))}
</div> </div>
{count > 0 && ( {count > 0 && (
<span className="text-sm text-[#666666] ml-1">({count})</span> <span className="text-sm text-[#666666] ml-1">({count >= 1000 ? '1000+' : count})</span>
)} )}
</div> </div>
); );
@@ -84,8 +91,23 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
const [selectedImage, setSelectedImage] = useState(0); const [selectedImage, setSelectedImage] = useState(0);
const [quantity, setQuantity] = useState(1); const [quantity, setQuantity] = useState(1);
const [isAdding, setIsAdding] = useState(false); const [isAdding, setIsAdding] = useState(false);
const [urgencyIndex, setUrgencyIndex] = useState(0);
const { addLine, openCart } = useSaleorCheckoutStore(); const { addLine, openCart } = useSaleorCheckoutStore();
// Cycle through urgency messages
useEffect(() => {
const interval = setInterval(() => {
setUrgencyIndex(prev => (prev + 1) % 3);
}, 3000);
return () => clearInterval(interval);
}, []);
const urgencyMessages = [
{ icon: "🚀", text: "Hurry up! 500+ items sold in the last 3 days!" },
{ icon: "🛒", text: "In the carts of 2.5K people - buy before its gone!" },
{ icon: "👀", text: "7,562 people viewed this product in the last 24 hours!" },
];
const localized = getLocalizedProduct(product, locale); const localized = getLocalizedProduct(product, locale);
const variant = product.variants?.[0]; const variant = product.variants?.[0];
@@ -108,6 +130,8 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
const isAvailable = variant?.quantityAvailable > 0; const isAvailable = variant?.quantityAvailable > 0;
const price = getProductPrice(product); const price = getProductPrice(product);
const priceAmount = getProductPriceAmount(product);
const originalPrice = priceAmount > 0 ? formatPrice(Math.round(priceAmount * 1.30)) : null;
// Extract short description (first sentence or first 100 chars) // Extract short description (first sentence or first 100 chars)
const shortDescription = localized.description const shortDescription = localized.description
@@ -145,7 +169,7 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ duration: 0.6 }} transition={{ duration: 0.6 }}
className="flex gap-4" className="flex flex-col md:flex-row gap-4"
> >
{/* Thumbnails - Vertical on Desktop, Hidden on Mobile */} {/* Thumbnails - Vertical on Desktop, Hidden on Mobile */}
{images.length > 1 && ( {images.length > 1 && (
@@ -160,12 +184,10 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
: "border-transparent hover:border-[#999999]" : "border-transparent hover:border-[#999999]"
}`} }`}
> >
<Image <img
src={image.url} src={image.url}
alt={image.alt || localized.name} alt={image.alt || localized.name}
fill className="w-full h-full object-cover"
className="object-cover"
sizes="80px"
/> />
</button> </button>
))} ))}
@@ -173,24 +195,53 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
)} )}
{/* Main Image */} {/* Main Image */}
<div className="flex-1 relative aspect-square bg-[#f8f9fa] overflow-hidden"> <div className="relative w-full aspect-square bg-[#f8f9fa] overflow-hidden flex-1">
{images[selectedImage] && ( <img
<Image src={images[selectedImage].url}
src={images[selectedImage].url} alt={images[selectedImage].alt || localized.name}
alt={images[selectedImage].alt || localized.name} className="w-full h-full object-cover"
fill />
className="object-cover"
priority
sizes="(max-width: 1024px) 100vw, 50vw"
/>
)}
{/* Award Badge - Optional */} {/* Carousel Navigation - Mobile Only */}
<div className="absolute top-4 left-4"> {images.length > 1 && (
<div className="bg-black text-white text-[10px] uppercase tracking-[0.1em] px-3 py-1.5"> <>
{locale === "EN" ? "Bestseller" : "Najprodavanije"} {/* Left Arrow */}
</div> <button
</div> onClick={() => setSelectedImage(prev => prev === 0 ? images.length - 1 : prev - 1)}
className="absolute left-2 top-1/2 -translate-y-1/2 w-10 h-10 bg-white/80 hover:bg-white rounded-full flex items-center justify-center shadow-md transition-all hover:scale-110 md:hidden"
aria-label="Previous image"
>
<svg className="w-5 h-5 text-gray-700" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</button>
{/* Right Arrow */}
<button
onClick={() => setSelectedImage(prev => prev === images.length - 1 ? 0 : prev + 1)}
className="absolute right-2 top-1/2 -translate-y-1/2 w-10 h-10 bg-white/80 hover:bg-white rounded-full flex items-center justify-center shadow-md transition-all hover:scale-110 md:hidden"
aria-label="Next image"
>
<svg className="w-5 h-5 text-gray-700" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</button>
{/* Dot Indicators - Mobile Only */}
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 md:hidden">
{images.map((_, index) => (
<button
key={index}
onClick={() => setSelectedImage(index)}
className={`w-2 h-2 rounded-full transition-all ${
selectedImage === index ? "bg-white w-4" : "bg-white/50"
}`}
aria-label={`Go to image ${index + 1}`}
/>
))}
</div>
</>
)}
</div> </div>
</motion.div> </motion.div>
@@ -201,24 +252,65 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
transition={{ duration: 0.6, delay: 0.2 }} transition={{ duration: 0.6, delay: 0.2 }}
className="lg:pl-8" className="lg:pl-8"
> >
{/* Urgency Sales Banner */}
<motion.div
key={urgencyIndex}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.3 }}
className="bg-white/80 backdrop-blur-sm text-[#1a1a1a] py-3 rounded-lg mb-4 text-sm font-medium text-left"
>
<span className="mr-2">{urgencyMessages[urgencyIndex].icon}</span>
{urgencyMessages[urgencyIndex].text}
</motion.div>
{/* Product Name */} {/* Product Name */}
<h1 className="text-3xl md:text-4xl font-medium mb-4 tracking-tight"> <h1 className="text-3xl md:text-4xl font-medium mb-4 tracking-tight">
{localized.name} {localized.name}
</h1> </h1>
{/* Short Description */} {/* Short Description */}
<p className="text-[#666666] leading-relaxed mb-6"> <p className="text-[#666666] leading-relaxed mb-4">
{shortDescription} {shortDescription}
</p> </p>
{/* Price & Rating */} {/* Stock Warning - Static */}
<div className="flex items-center justify-between mb-8"> <div className="flex items-center justify-start gap-2 mb-6">
<span className="text-3xl font-medium"> <span className="relative flex h-3 w-3">
{price || (locale === "EN" ? "Contact for price" : "Kontaktirajte za cenu")} <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
</span> </span>
<StarRating rating={5} count={12} /> <span className="text-red-600 text-sm font-medium">Stocks are running out!</span>
</div> </div>
{/* Discount Price Display */}
{originalPrice && priceAmount > 0 && (
<div className="mb-4">
<div className="flex items-center gap-3 mb-2">
<span className="text-xl text-[#666666] line-through">
{originalPrice}
</span>
<span className="bg-[#b91c1c] text-white text-xs font-bold px-2 py-1 rounded">
-30%
</span>
</div>
<span className="text-3xl font-bold text-[#b91c1c]">
{price}
</span>
</div>
)}
{/* Price & Rating */}
{!originalPrice && (
<div className="flex items-center justify-between mb-8">
<span className="text-3xl font-medium">
{price || (locale === "EN" ? "Contact for price" : "Kontaktirajte za cenu")}
</span>
<StarRating rating={5} count={1000} />
</div>
)}
{/* Divider */} {/* Divider */}
<div className="border-t border-[#e5e5e5] mb-8" /> <div className="border-t border-[#e5e5e5] mb-8" />
@@ -270,16 +362,16 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
</div> </div>
</div> </div>
{/* Add to Cart Button */} {/* Add to Cart Button - Action verb + value */}
{isAvailable ? ( {isAvailable ? (
<button <button
onClick={handleAddToCart} onClick={handleAddToCart}
disabled={isAdding} disabled={isAdding}
className="w-full h-16 bg-black text-white text-base uppercase tracking-[0.15em] font-medium hover:bg-[#333333] active:bg-[#1a1a1a] transition-colors disabled:opacity-50 disabled:cursor-not-allowed mb-8" className="w-full h-16 bg-black text-white text-[13px] uppercase tracking-[0.15em] font-semibold hover:bg-[#333333] active:bg-[#1a1a1a] transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed mb-6 hover:scale-[1.02] shadow-lg hover:shadow-xl"
> >
{isAdding {isAdding
? (locale === "EN" ? "Adding..." : "Dodavanje...") ? (locale === "EN" ? "Adding..." : "Dodavanje...")
: (locale === "EN" ? "Add to Cart — Free Shipping" : "Dodaj u korpu — Besplatna dostava") : (locale === "EN" ? "Transform My Hair & Skin" : "Transformiši kosu i kožu")
} }
</button> </button>
) : ( ) : (
@@ -288,12 +380,45 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
</div> </div>
)} )}
{/* Free Shipping Note */} {/* Free Shipping Note - with urgency */}
<p className="text-center text-sm text-[#666666] mb-10"> <div className="flex items-center justify-center gap-2 mb-6">
{locale === "EN" <svg className="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
? "Free shipping on orders over 3,000 RSD" <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
: "Besplatna dostava za porudžbine preko 3.000 RSD"} </svg>
</p> <p className="text-sm text-[#666666]">
{locale === "EN"
? "Free shipping on orders over 3,000 RSD"
: "Besplatna dostava za porudžbine preko 3.000 RSD"}
</p>
</div>
{/* Trust Indicators */}
<div className="grid grid-cols-3 gap-4 mb-8 p-4 bg-[#f8f9fa] rounded-lg">
<div className="text-center">
<svg className="w-6 h-6 mx-auto mb-2 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
<p className="text-xs text-[#666666]">
{locale === "EN" ? "30-Day Guarantee" : "30-dnevna garancija"}
</p>
</div>
<div className="text-center">
<svg className="w-6 h-6 mx-auto mb-2 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
<p className="text-xs text-[#666666]">
{locale === "EN" ? "Secure Checkout" : "Sigurno plaćanje"}
</p>
</div>
<div className="text-center">
<svg className="w-6 h-6 mx-auto mb-2 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-8m15.357 8H15" />
</svg>
<p className="text-xs text-[#666666]">
{locale === "EN" ? "Easy Returns" : "Lak povrat"}
</p>
</div>
</div>
{/* Divider */} {/* Divider */}
<div className="border-t border-[#e5e5e5] mb-8" /> <div className="border-t border-[#e5e5e5] mb-8" />
@@ -353,6 +478,15 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
</div> </div>
</section> </section>
{/* Customer Reviews */}
<ProductReviews locale={locale} productName={localized.name} />
{/* As Featured In - Full Width */}
<AsSeenIn />
{/* Before/After Gallery */}
<BeforeAfterGallery />
{/* Related Products */} {/* Related Products */}
{relatedProducts && relatedProducts.length > 0 && ( {relatedProducts && relatedProducts.length > 0 && (
<section className="py-20 lg:py-28 bg-[#f8f9fa]"> <section className="py-20 lg:py-28 bg-[#f8f9fa]">
@@ -365,19 +499,32 @@ export default function ProductDetail({ product, relatedProducts, locale = "SR"
{locale === "EN" ? "Similar Products" : "Slični proizvodi"} {locale === "EN" ? "Similar Products" : "Slični proizvodi"}
</h2> </h2>
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8"> <div className="flex flex-wrap justify-center gap-6 lg:gap-8">
{relatedProducts.filter(p => p && p.id).slice(0, 4).map((relatedProduct, index) => ( {relatedProducts.filter(p => p && p.id).slice(0, 4).map((relatedProduct, index) => (
<ProductCard <div key={relatedProduct.id} className="w-full sm:w-[calc(50%-12px)] lg:w-[calc(25%-18px)]">
key={relatedProduct.id} <ProductCard
product={relatedProduct} product={relatedProduct}
index={index} index={index}
locale={locale} locale={locale}
/> />
</div>
))} ))}
</div> </div>
</div> </div>
</section> </section>
)} )}
{/* Product Benefits */}
<ProductBenefits locale={locale} />
{/* Trust Badges */}
<TrustBadges />
{/* How It Works */}
<HowItWorks />
{/* Newsletter */}
<NewsletterSection />
</> </>
); );
} }

View File

@@ -0,0 +1,181 @@
"use client";
import { motion } from "framer-motion";
interface ProductReviewsProps {
locale?: string;
productName?: string;
}
const reviews = [
{ id: 1, name: "Ana M.", location: "Belgrade", text: "Manoon Anti-age Serum transformed my skin in just 2 weeks!", rating: 5 },
{ id: 2, name: "Milica P.", location: "Novi Sad", text: "The best day serum I've ever used. My wrinkles are visibly reduced.", rating: 5 },
{ id: 3, name: "Jelena K.", location: "Belgrade", text: "Manoon night serum is pure magic. Wake up with glowing skin every morning.", rating: 5 },
{ id: 4, name: "Stefan R.", location: "Subotica", text: "The Anti-age Set is worth every dinar. My wife and I both use it.", rating: 5 },
{ id: 5, name: "Marija T.", location: "Kragujevac", text: "Finally found a serum that actually works! Manoon delivers on its promises.", rating: 5 },
{ id: 6, name: "Nikola V.", location: "Niš", text: "My fine lines are disappearing. This day serum is incredible.", rating: 5 },
{ id: 7, name: "Ivana L.", location: "Belgrade", text: "Manoon morning glow serum smells divine and works even better.", rating: 5 },
{ id: 8, name: "Dejan M.", location: "Novi Sad", text: "The night serum has transformed my skincare routine completely.", rating: 5 },
{ id: 9, name: "Sanja B.", location: "Kragujevac", text: "My skin looks 10 years younger after using Manoon for a month.", rating: 5 },
{ id: 10, name: "Marko J.", location: "Subotica", text: "The anti-age set makes a perfect gift. My mother loves it!", rating: 5 },
{ id: 11, name: "Petra D.", location: "Niš", text: "The texture of Manoon serum is so luxurious. Worth every penny.", rating: 5 },
{ id: 12, name: "Luka G.", location: "Belgrade", text: "Day serum absorbs instantly. No greasy feeling at all!", rating: 5 },
{ id: 13, name: "Maja S.", location: "Novi Sad", text: "My esthetician asked what I'm using. Manoon is now my secret!", rating: 5 },
{ id: 14, name: "Vladimir P.", location: "Kragujevac", text: "The night serum works while I sleep. Wake up to visibly smoother skin.", rating: 5 },
{ id: 15, name: "Katarina N.", location: "Subotica", text: "The Anti-age Set arrived beautifully packaged. Perfect for gifting.", rating: 5 },
{ id: 16, name: "Bojan R.", location: "Niš", text: "Been using Manoon for 3 months. My wrinkles are noticeably reduced.", rating: 5 },
{ id: 17, name: "Tamara F.", location: "Belgrade", text: "The day serum provides the perfect base under makeup.", rating: 5 },
{ id: 18, name: "Aleksandar K.", location: "Novi Sad", text: "Finally a Serbian brand that competes with luxury international brands!", rating: 5 },
{ id: 19, name: "Natalia M.", location: "Kragujevac", text: "My sensitive skin loves Manoon. No irritation at all.", rating: 5 },
{ id: 20, name: "Filip T.", location: "Subotica", text: "The anti-age serum is lightweight yet incredibly effective.", rating: 5 },
{ id: 21, name: "Andrea L.", location: "Niš", text: "Manoon night serum is my evening ritual. Skin looks amazing!", rating: 5 },
{ id: 22, name: "Ognjen P.", location: "Belgrade", text: "My friends keep asking what changed in my skincare routine.", rating: 5 },
{ id: 23, name: "Mila J.", location: "Novi Sad", text: "The Anti-age Set includes everything you need. Great value!", rating: 5 },
{ id: 24, name: "Dragan S.", location: "Kragujevac", text: "Even my husband noticed the difference. He now uses the day serum too!", rating: 5 },
{ id: 25, name: "Jovana V.", location: "Subotica", text: "The morning glow serum gives the most beautiful luminosity.", rating: 5 },
{ id: 26, name: "Stefan M.", location: "Niš", text: "Manoon products are now essential in my daily routine.", rating: 5 },
{ id: 27, name: "Ana R.", location: "Belgrade", text: "The night serum helped clear my complexion. Skin looks so healthy!", rating: 5 },
{ id: 28, name: "Nenad L.", location: "Novi Sad", text: "Anti-aging results visible within weeks. Highly recommend Manoon!", rating: 5 },
{ id: 29, name: "Sofija D.", location: "Kragujevac", text: "The texture is divine. Feels like a luxury spa treatment at home.", rating: 5 },
{ id: 30, name: "Velibor K.", location: "Subotica", text: "My crow's feet have diminished significantly. Thank you Manoon!", rating: 5 },
{ id: 31, name: "Irena M.", location: "Niš", text: "The Anti-age Set makes the perfect birthday gift for my mother.", rating: 5 },
{ id: 32, name: "Radoslav P.", location: "Belgrade", text: "Professional quality serum at an honest price. Serbian excellence!", rating: 5 },
{ id: 33, name: "Jelena B.", location: "Novi Sad", text: "My skin has never been this hydrated. Day serum is amazing!", rating: 5 },
{ id: 34, name: "Dimitrije S.", location: "Kragujevac", text: "The night serum is worth its weight in gold. Pure luxury!", rating: 5 },
{ id: 35, name: "Minela G.", location: "Subotica", text: "Manoon lives up to the hype. My skin looks refreshed and young.", rating: 5 },
{ id: 36, name: "Zoran T.", location: "Niš", text: "I've tried many serums. Manoon is by far the most effective.", rating: 5 },
{ id: 37, name: "Mirjana F.", location: "Belgrade", text: "The Anti-age Set transformed my mother's skincare routine completely.", rating: 5 },
{ id: 38, name: "Ivan J.", location: "Novi Sad", text: "Fast-acting serum with real results. I recommend Manoon to everyone.", rating: 5 },
{ id: 39, name: "Kristina P.", location: "Kragujevac", text: "The morning glow serum gives such a beautiful dewy finish.", rating: 5 },
{ id: 40, name: "Bratislav L.", location: "Subotica", text: "Noticeable results in just 2 weeks. This serum is the real deal!", rating: 5 },
{ id: 41, name: "Zorica M.", location: "Niš", text: "The night serum erased years from my face. Absolutely miraculous!", rating: 5 },
{ id: 42, name: "Patrik N.", location: "Belgrade", text: "Premium quality Serbian skincare that rivals international luxury brands.", rating: 5 },
{ id: 43, name: "Simona K.", location: "Novi Sad", text: "Manoon Anti-age Serum is the best investment in my skin ever.", rating: 5 },
{ id: 44, name: "Mladen D.", location: "Kragujevac", text: "The day serum absorbs in seconds. No waiting around!", rating: 5 },
{ id: 45, name: "Ljiljana R.", location: "Subotica", text: "Gifting the Anti-age Set to my sisters. They loved it!", rating: 5 },
{ id: 46, name: "Tomislav V.", location: "Niš", text: "My wrinkles are visibly reduced after using Manoon for a month.", rating: 5 },
{ id: 47, name: "Emilija S.", location: "Belgrade", text: "The night serum leaves my skin so soft and renewed every morning.", rating: 5 },
{ id: 48, name: "Andrija P.", location: "Novi Sad", text: "Manoon day serum is perfect under sunscreen. Essential duo!", rating: 5 },
{ id: 49, name: "Miona L.", location: "Kragujevac", text: "My skin looks radiant and youthful. Couldn't be happier with Manoon!", rating: 5 },
{ id: 50, name: "Slavko M.", location: "Subotica", text: "The Anti-age Set delivers visible results. True Serbian quality!", rating: 5 },
];
function ReviewCard({ review }: { review: typeof reviews[0] }) {
return (
<div className="flex-shrink-0 w-80 bg-white p-6 rounded-2xl shadow-sm border border-[#f0ede8] mx-3">
<div className="flex items-center gap-1 mb-3">
{[1, 2, 3, 4, 5].map((star) => (
<svg key={star} className="w-4 h-4 fill-yellow-400 text-yellow-400" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
))}
</div>
<p className="text-[#444444] text-sm leading-relaxed mb-4">"{review.text}"</p>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-full bg-[#1a1a1a] flex items-center justify-center text-white text-sm font-medium">
{review.name.charAt(0)}
</div>
<div>
<div className="flex items-center gap-1.5">
<p className="text-sm font-medium">{review.name}</p>
<svg className="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
<span className="text-xs text-green-700 font-medium">Verified</span>
</div>
<p className="text-xs text-[#888888]">{review.location}</p>
</div>
</div>
</div>
);
}
export default function ProductReviews({ locale = "SR", productName = "this product" }: ProductReviewsProps) {
return (
<section className="py-16 bg-[#faf9f7] overflow-hidden">
<div className="container mx-auto px-4 mb-8">
<motion.div
className="text-center"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
Customer Reviews
</span>
<h2 className="text-3xl md:text-4xl font-medium">
What Customers Say
</h2>
<div className="flex items-center justify-center gap-4 mt-4">
<span className="text-5xl font-bold text-[#1a1a1a]">4.9</span>
<div>
<div className="flex gap-0.5">
{[1, 2, 3, 4, 5].map((star) => (
<svg key={star} className="w-5 h-5 fill-yellow-400 text-yellow-400" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
))}
</div>
<p className="text-sm text-[#666666] mt-1">Based on 1000+ reviews</p>
</div>
</div>
</motion.div>
</div>
{/* Scrolling Reviews Marquee */}
<div className="relative">
{/* Left gradient fade */}
<div className="absolute left-0 top-0 bottom-0 w-20 bg-gradient-to-r from-[#faf9f7] to-transparent z-10 pointer-events-none" />
{/* Right gradient fade */}
<div className="absolute right-0 top-0 bottom-0 w-20 bg-gradient-to-l from-[#faf9f7] to-transparent z-10 pointer-events-none" />
{/* First row - left to right */}
<div className="flex overflow-hidden mb-4">
<motion.div
className="flex items-center gap-0"
animate={{
x: [0, -50 + "%"],
}}
transition={{
x: {
repeat: Infinity,
repeatType: "loop",
duration: 120,
ease: "linear",
},
}}
>
{[...reviews, ...reviews].map((review, index) => (
<ReviewCard key={`first-${index}-${review.id}`} review={review} />
))}
</motion.div>
</div>
{/* Second row - right to left */}
<div className="flex overflow-hidden">
<motion.div
className="flex items-center gap-0"
animate={{
x: [-50 + "%", 0],
}}
transition={{
x: {
repeat: Infinity,
repeatType: "loop",
duration: 120,
ease: "linear",
},
}}
>
{[...reviews.slice(25), ...reviews.slice(0, 25), ...reviews.slice(25), ...reviews.slice(0, 25)].map((review, index) => (
<ReviewCard key={`second-${index}-${review.id}`} review={review} />
))}
</motion.div>
</div>
</div>
</section>
);
}

View File

@@ -24,12 +24,8 @@ export const saleorClient = new ApolloClient({
fields: { fields: {
products: { products: {
keyArgs: ["channel", "filter"], keyArgs: ["channel", "filter"],
merge(existing, incoming) { merge(_existing, incoming) {
if (!existing) return incoming; return incoming;
return {
...incoming,
edges: [...existing.edges, ...incoming.edges],
};
}, },
}, },
}, },

View File

@@ -28,6 +28,7 @@ export {
getProducts, getProducts,
getProductBySlug, getProductBySlug,
getProductPrice, getProductPrice,
getProductPriceAmount,
getProductImage, getProductImage,
isProductAvailable, isProductAvailable,
formatPrice, formatPrice,

View File

@@ -50,8 +50,8 @@ export const CHECKOUT_LINES_UPDATE = gql`
`; `;
export const CHECKOUT_LINES_DELETE = gql` export const CHECKOUT_LINES_DELETE = gql`
mutation CheckoutLinesDelete($checkoutId: ID!, $lineIds: [ID!]!) { mutation CheckoutLinesDelete($id: ID!, $linesIds: [ID!]!) {
checkoutLinesDelete(checkoutId: $checkoutId, lines: $lineIds) { checkoutLinesDelete(id: $id, linesIds: $linesIds) {
checkout { checkout {
...CheckoutFragment ...CheckoutFragment
} }

View File

@@ -68,6 +68,11 @@ export function getProductPrice(product: Product): string {
); );
} }
export function getProductPriceAmount(product: Product): number {
const variant = product.variants?.[0];
return variant?.pricing?.price?.gross?.amount || 0;
}
export function getProductImage(product: Product): string { export function getProductImage(product: Product): string {
if (product.media && product.media.length > 0) { if (product.media && product.media.length > 0) {
return product.media[0].url; return product.media[0].url;
@@ -88,7 +93,8 @@ export function formatPrice(amount: number, currency: string = "RSD"): string {
return new Intl.NumberFormat("sr-RS", { return new Intl.NumberFormat("sr-RS", {
style: "currency", style: "currency",
currency: currency, currency: currency,
minimumFractionDigits: 0, minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount); }).format(amount);
} }

View File

@@ -221,8 +221,8 @@ export const useSaleorCheckoutStore = create<SaleorCheckoutStore>()(
const { data } = await saleorClient.mutate<CheckoutLinesDeleteResponse>({ const { data } = await saleorClient.mutate<CheckoutLinesDeleteResponse>({
mutation: CHECKOUT_LINES_DELETE, mutation: CHECKOUT_LINES_DELETE,
variables: { variables: {
checkoutId: checkout.id, id: checkout.id,
lineIds: [lineId], linesIds: [lineId],
}, },
}); });