feat: implement locale-aware routing with [locale] dynamic segments
Some checks failed
Build and Deploy / build (push) Has been cancelled

WARNING: This change breaks existing SEO URLs for Serbian locale.

Changes:
- Migrated from separate locale folders (src/app/en/, src/app/de/, etc.)
  to [locale] dynamic segments (src/app/[locale]/)
- Serbian is now at /sr/ instead of / (root)
- English at /en/, German at /de/, French at /fr/
- All components updated to generate locale-aware links
- Root / now redirects to /sr (307 temporary redirect)

SEO Impact:
- Previously indexed Serbian URLs (/, /products, /about, /contact)
  will now return 404 or redirect to /sr/* URLs
- This is a breaking change for SEO - Serbian pages should ideally
  remain at root (/) with only non-default locales getting prefix
- Consider implementing 301 redirects from old URLs to maintain
  search engine rankings

Technical Notes:
- next-intl v4 with [locale] structure requires ALL locales to
  have the prefix (cannot have default locale at root)
- Alternative approach would be separate folder structure per locale
This commit is contained in:
Unchained
2026-03-23 20:59:33 +02:00
parent 5bd1a0f167
commit 92b6c830e1
47 changed files with 2175 additions and 2881 deletions

View File

@@ -1,6 +1,7 @@
"use client";
import { motion } from "framer-motion";
import { useTranslations } from "next-intl";
const mediaLogos = [
{ name: "VOGUE", style: "serif" },
@@ -15,10 +16,10 @@ const mediaLogos = [
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
<span
className={`
text-xl md:text-2xl tracking-[0.15em] text-white font-bold
${isSerif ? 'font-serif italic' : 'font-sans uppercase'}
@@ -34,6 +35,8 @@ function LogoItem({ name }: { name: string }) {
}
export default function AsSeenIn() {
const t = useTranslations("AsSeenIn");
return (
<section className="py-12 bg-[#1a1a1a] overflow-hidden border-y border-white/10">
<div className="container mx-auto px-4 mb-8">
@@ -44,19 +47,14 @@ export default function AsSeenIn() {
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
As Featured In
{t("title")}
</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"
@@ -72,11 +70,9 @@ export default function AsSeenIn() {
},
}}
>
{/* 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} />
))}
@@ -85,4 +81,4 @@ export default function AsSeenIn() {
</div>
</section>
);
}
}

View File

@@ -2,6 +2,7 @@
import { motion } from "framer-motion";
import { useState, useRef } from "react";
import { useTranslations, useLocale } from "next-intl";
const results = [
{
@@ -25,6 +26,7 @@ const results = [
];
function BeforeAfterSlider({ result }: { result: typeof results[0] }) {
const t = useTranslations("BeforeAfterGallery");
const [sliderPosition, setSliderPosition] = useState(50);
const containerRef = useRef<HTMLDivElement>(null);
@@ -44,34 +46,30 @@ function BeforeAfterSlider({ result }: { result: typeof results[0] }) {
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"
alt="After"
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"
alt="Before"
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%)' }}
@@ -83,16 +81,14 @@ function BeforeAfterSlider({ result }: { result: typeof results[0] }) {
</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
{t("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
{t("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">
@@ -113,18 +109,19 @@ function BeforeAfterSlider({ result }: { result: typeof results[0] }) {
</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>
<span className="text-xs text-green-700 font-medium">{t("verified")}</span>
</div>
</div>
);
}
export default function BeforeAfterGallery() {
const t = useTranslations("BeforeAfterGallery");
const locale = useLocale();
const [selectedIndex, setSelectedIndex] = useState(0);
const goToPrev = () => {
@@ -146,14 +143,13 @@ export default function BeforeAfterGallery() {
transition={{ duration: 0.6 }}
>
<span className="text-xs uppercase tracking-[0.2em] text-[#666666] mb-3 block">
Real Results
{t("realResults")}
</span>
<h2 className="text-3xl md:text-4xl font-medium mb-4">
See the Transformation
{t("seeTransformation")}
</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
@@ -169,7 +165,6 @@ export default function BeforeAfterGallery() {
))}
</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
@@ -182,28 +177,26 @@ export default function BeforeAfterGallery() {
</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"
aria-label="Previous"
>
<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"
aria-label="Next"
>
<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
@@ -212,13 +205,12 @@ export default function BeforeAfterGallery() {
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}`}
aria-label={`Go to ${index + 1}`}
/>
))}
</div>
</div>
{/* CTA */}
<motion.div
className="text-center mt-12"
initial={{ opacity: 0, y: 20 }}
@@ -227,13 +219,13 @@ export default function BeforeAfterGallery() {
transition={{ duration: 0.6, delay: 0.4 }}
>
<a
href="/products"
href={`/${locale}/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
{t("startTransformation")}
</a>
</motion.div>
</div>
</section>
);
}
}

View File

@@ -2,8 +2,10 @@
import { motion } from "framer-motion";
import Link from "next/link";
import { useLocale } from "next-intl";
export default function Hero() {
const locale = useLocale();
return (
<section className="relative h-screen min-h-[600px] flex items-center justify-center overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-b from-background-ice/50 to-background" />
@@ -48,7 +50,7 @@ export default function Hero() {
transition={{ duration: 0.8, delay: 0.8 }}
>
<Link
href="/en/products"
href={`/${locale}/products`}
className="inline-block px-10 py-4 bg-foreground text-white text-lg tracking-wide hover:bg-accent-dark transition-colors duration-300"
>
Shop Now

View File

@@ -5,8 +5,13 @@ import Link from "next/link";
import { useTranslations } from "next-intl";
import { ChevronDown } from "lucide-react";
export default function HeroVideo() {
interface HeroVideoProps {
locale?: string;
}
export default function HeroVideo({ locale = "sr" }: HeroVideoProps) {
const t = useTranslations("Home.hero");
const localePath = `/${locale}`;
const scrollToContent = () => {
const element = document.getElementById("main-content");
@@ -84,13 +89,13 @@ export default function HeroVideo() {
className="flex flex-col sm:flex-row items-center justify-center gap-4"
>
<Link
href="/products"
href={`${localePath}/products`}
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"
>
{t("ctaButton")}
</Link>
<Link
href="/about"
href={`${localePath}/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"
>
{t("learnStory")}

View File

@@ -1,41 +1,12 @@
"use client";
import { motion } from "framer-motion";
import { useTranslations, useLocale } from "next-intl";
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>
),
},
];
const t = useTranslations("HowItWorks");
const locale = useLocale();
const steps = t.raw("steps") as Array<{ title: string; description: string }>;
return (
<section className="py-24 bg-gradient-to-b from-white to-[#faf9f7]">
@@ -48,10 +19,10 @@ export default function HowItWorks() {
transition={{ duration: 0.6 }}
>
<span className="text-xs uppercase tracking-[0.3em] text-[#c9a962] mb-4 block font-medium">
Simple Process
{t("title")}
</span>
<h2 className="text-4xl md:text-5xl font-medium text-[#1a1a1a]">
How ManoonOils Works
{t("subtitle")}
</h2>
<div className="w-24 h-1 bg-gradient-to-r from-[#c9a962] to-[#FFD700] mx-auto mt-6 rounded-full" />
</motion.div>
@@ -66,11 +37,10 @@ export default function HowItWorks() {
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
<motion.div
className="absolute inset-y-0 left-0 w-2 bg-[#FFD700] rounded-full"
initial={{ scaleX: 0 }}
whileInView={{ scaleX: 1 }}
@@ -80,21 +50,33 @@ export default function HowItWorks() {
/>
</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>
<span className="text-white text-lg font-bold">0{index + 1}</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}
{index === 0 && (
<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>
)}
{index === 1 && (
<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>
)}
{index === 2 && (
<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>
)}
</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}
@@ -104,7 +86,6 @@ export default function HowItWorks() {
))}
</div>
{/* CTA */}
<motion.div
className="text-center mt-20"
initial={{ opacity: 0 }}
@@ -113,10 +94,10 @@ export default function HowItWorks() {
transition={{ duration: 0.6, delay: 0.3 }}
>
<a
href="/products"
href={`/${locale}/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>
<span>{t("startTransformation")}</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>
@@ -125,4 +106,4 @@ export default function HowItWorks() {
</div>
</section>
);
}
}

View File

@@ -4,6 +4,7 @@ import { motion } from "framer-motion";
import { Star, ShoppingBag } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useLocale } from "next-intl";
import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore";
import type { Product } from "@/types/saleor";
import { getProductPrice, getProductImage, formatPrice, parseDescription } from "@/lib/saleor";
@@ -13,6 +14,7 @@ interface NewHeroProps {
}
export default function NewHero({ featuredProduct }: NewHeroProps) {
const locale = useLocale();
const { addLine, openCart } = useSaleorCheckoutStore();
const handleAddToCart = async () => {
@@ -150,13 +152,13 @@ export default function NewHero({ featuredProduct }: NewHeroProps) {
<div className="flex gap-4 justify-end">
<Link
href="/products"
href={`/${locale}/products`}
className="inline-block bg-[#1A1A1A] text-white px-8 py-4 text-sm tracking-wide hover:bg-[#1A1A1A]/90 transition-colors"
>
Shop Collection
</Link>
<Link
href="/about"
href={`/${locale}/about`}
className="inline-block border border-[#1A1A1A] text-[#1A1A1A] px-8 py-4 text-sm tracking-wide hover:bg-[#1A1A1A] hover:text-white transition-colors"
>
Our Story
@@ -168,7 +170,7 @@ export default function NewHero({ featuredProduct }: NewHeroProps) {
{/* Mobile CTA */}
<div className="lg:hidden relative z-10 px-6 pb-12">
<Link
href="/products"
href={`/${locale}/products`}
className="block w-full bg-[#1A1A1A] text-white text-center py-4 text-sm tracking-wide"
>
Shop Now

View File

@@ -2,15 +2,16 @@
import { motion } from "framer-motion";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { ArrowRight } from "lucide-react";
export default function NewsletterSection() {
const t = useTranslations("Newsletter");
const [email, setEmail] = useState("");
const [status, setStatus] = useState<"idle" | "success" | "error">("idle");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// TODO: Connect to newsletter service
setStatus("success");
setEmail("");
};
@@ -26,9 +27,7 @@ export default function NewsletterSection() {
transition={{ duration: 0.6 }}
className="font-serif italic text-4xl lg:text-5xl xl:text-[3.5rem] text-[#1A1A1A] tracking-tight leading-[1.1] mb-6"
>
Get 10% off your
<br />
first order
{t("stayConnected")}
</motion.h2>
<motion.p
@@ -38,8 +37,7 @@ export default function NewsletterSection() {
transition={{ duration: 0.6, delay: 0.1 }}
className="text-[#4A4A4A] mb-8"
>
Join the ManoonOils community and receive exclusive offers,
skincare tips, and early access to new products.
{t("newsletterText")}
</motion.p>
<motion.form
@@ -54,7 +52,7 @@ export default function NewsletterSection() {
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
placeholder={t("emailPlaceholder")}
required
className="flex-1 px-4 py-3 border border-[#1A1A1A]/10 rounded-[4px] text-sm focus:outline-none focus:border-[#1A1A1A]/30 transition-colors"
/>
@@ -62,7 +60,7 @@ export default function NewsletterSection() {
type="submit"
className="inline-flex items-center justify-center gap-2 bg-[#1A1A1A] text-white px-6 py-3 text-sm font-medium hover:bg-[#1A1A1A]/90 transition-colors rounded-[4px]"
>
Subscribe
{t("subscribe")}
<ArrowRight className="w-4 h-4" />
</button>
</motion.form>
@@ -73,7 +71,7 @@ export default function NewsletterSection() {
animate={{ opacity: 1 }}
className="text-sm text-emerald-600 mt-4"
>
Thank you! Check your email for your discount code.
Hvala vam! Proverite email za vaš kod za popust.
</motion.p>
)}
@@ -84,11 +82,10 @@ export default function NewsletterSection() {
transition={{ duration: 0.6, delay: 0.3 }}
className="text-xs text-[#4A4A4A]/60 mt-4"
>
By subscribing, you agree to our Privacy Policy. Unsubscribe
anytime.
Prijavom prihvatate našu Politiku privatnosti. Možete se odjaviti bilo kada.
</motion.p>
</div>
</div>
</section>
);
}
}

View File

@@ -1,8 +1,12 @@
"use client";
import { motion } from "framer-motion";
import { useTranslations } from "next-intl";
export default function ProblemSection() {
const t = useTranslations("ProblemSection");
const problems = t.raw("problems") as Array<{ problem: string; description: string }>;
return (
<section className="py-24 bg-gradient-to-b from-[#fefcfb] to-[#faf9f7]">
<div className="container mx-auto px-4">
@@ -14,47 +18,19 @@ export default function ProblemSection() {
transition={{ duration: 0.6 }}
>
<span className="text-xs uppercase tracking-[0.3em] text-[#c9a962] mb-4 block font-medium">
The Problem
{t("title")}
</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?
{t("subtitle")}
</h2>
<p className="text-[#666666] text-lg max-w-xl mx-auto">
You deserve better than products filled with harsh chemicals and empty promises
{t("description")}
</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) => (
{problems.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"
@@ -64,11 +40,24 @@ export default function ProblemSection() {
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}
{index === 0 && (
<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>
)}
{index === 1 && (
<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>
)}
{index === 2 && (
<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>
)}
</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>
@@ -78,4 +67,4 @@ export default function ProblemSection() {
</div>
</section>
);
}
}

View File

@@ -1,51 +1,11 @@
"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",
},
];
import { useTranslations } from "next-intl";
export default function TrustBadges() {
const t = useTranslations("TrustBadges");
return (
<section className="py-16 bg-gradient-to-b from-[#fefcfb] to-[#faf9f7]">
<div className="container mx-auto px-4">
@@ -56,32 +16,103 @@ export default function TrustBadges() {
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
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: 0 }}
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]">
<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>
</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">
4.9/5
</p>
<p className="text-sm font-semibold text-[#1a1a1a] mt-1">
{t("averageRating")}
</p>
<p className="text-xs text-[#888888] mt-0.5">
{t("basedOnReviews")}
</p>
</motion.div>
<motion.div
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: 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]">
<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>
</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">
50,000+
</p>
<p className="text-sm font-semibold text-[#1a1a1a] mt-1">
{t("happyCustomers")}
</p>
<p className="text-xs text-[#888888] mt-0.5">
{t("worldwide")}
</p>
</motion.div>
<motion.div
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: 0.2 }}
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]">
<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>
</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">
100%
</p>
<p className="text-sm font-semibold text-[#1a1a1a] mt-1">
{t("naturalIngredients")}
</p>
<p className="text-xs text-[#888888] mt-0.5">
{t("noAdditives")}
</p>
</motion.div>
<motion.div
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: 0.3 }}
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]">
<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>
</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">
Free
</p>
<p className="text-sm font-semibold text-[#1a1a1a] mt-1">
{t("freeShipping")}
</p>
<p className="text-xs text-[#888888] mt-0.5">
{t("ordersOver")}
</p>
</motion.div>
</motion.div>
</div>
</section>
);
}
}