diff --git a/package.json b/package.json index 095a4e0..31a0293 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "annotate": "node docs/annotate-all.mjs", "images:populate": "bash scripts/populate-site-images.sh", "images:fish": "bash scripts/update-fish-images.sh", - "images:brand": "bash -c '.venv-images/bin/python scripts/process-all-site-images.py 2>/dev/null || python3 scripts/process-all-site-images.py'" + "images:brand": "bash -c '.venv-images/bin/python scripts/process-all-site-images.py 2>/dev/null || python3 scripts/process-all-site-images.py'", + "images:optimize": "bash -c '.venv-images/bin/python scripts/optimize-site-images.py 2>/dev/null || python3 scripts/optimize-site-images.py'" }, "dependencies": { "clsx": "^2.1.0", diff --git a/public/images/site/_originals/about.jpg b/public/images/site/_originals/about.jpg new file mode 100644 index 0000000..00cbc70 Binary files /dev/null and b/public/images/site/_originals/about.jpg differ diff --git a/public/images/site/_originals/beef-boneless.jpg b/public/images/site/_originals/beef-boneless.jpg new file mode 100644 index 0000000..7286687 Binary files /dev/null and b/public/images/site/_originals/beef-boneless.jpg differ diff --git a/public/images/site/_originals/beef-mince.jpg b/public/images/site/_originals/beef-mince.jpg new file mode 100644 index 0000000..68a15cf Binary files /dev/null and b/public/images/site/_originals/beef-mince.jpg differ diff --git a/public/images/site/_originals/beef-nihari-2.jpg b/public/images/site/_originals/beef-nihari-2.jpg new file mode 100644 index 0000000..72b4ea9 Binary files /dev/null and b/public/images/site/_originals/beef-nihari-2.jpg differ diff --git a/public/images/site/_originals/beef-nihari.jpg b/public/images/site/_originals/beef-nihari.jpg new file mode 100644 index 0000000..72b4ea9 Binary files /dev/null and b/public/images/site/_originals/beef-nihari.jpg differ diff --git a/public/images/site/_originals/beef-steak-2.jpg b/public/images/site/_originals/beef-steak-2.jpg new file mode 100644 index 0000000..b1f2c21 Binary files /dev/null and b/public/images/site/_originals/beef-steak-2.jpg differ diff --git a/public/images/site/_originals/beef-steak.jpg b/public/images/site/_originals/beef-steak.jpg new file mode 100644 index 0000000..b1f2c21 Binary files /dev/null and b/public/images/site/_originals/beef-steak.jpg differ diff --git a/public/images/site/_originals/category-beef.jpg b/public/images/site/_originals/category-beef.jpg new file mode 100644 index 0000000..7286687 Binary files /dev/null and b/public/images/site/_originals/category-beef.jpg differ diff --git a/public/images/site/_originals/category-chicken.jpg b/public/images/site/_originals/category-chicken.jpg new file mode 100644 index 0000000..3119be2 Binary files /dev/null and b/public/images/site/_originals/category-chicken.jpg differ diff --git a/public/images/site/_originals/category-fish.jpg b/public/images/site/_originals/category-fish.jpg new file mode 100644 index 0000000..80ec1f3 Binary files /dev/null and b/public/images/site/_originals/category-fish.jpg differ diff --git a/public/images/site/_originals/category-lamb.jpg b/public/images/site/_originals/category-lamb.jpg new file mode 100644 index 0000000..7ebfbf7 Binary files /dev/null and b/public/images/site/_originals/category-lamb.jpg differ diff --git a/public/images/site/_originals/chicken-breast copy.jpg b/public/images/site/_originals/chicken-breast copy.jpg new file mode 100644 index 0000000..c4110ee Binary files /dev/null and b/public/images/site/_originals/chicken-breast copy.jpg differ diff --git a/public/images/site/_originals/chicken-breast-2.jpg b/public/images/site/_originals/chicken-breast-2.jpg new file mode 100644 index 0000000..e63fa49 Binary files /dev/null and b/public/images/site/_originals/chicken-breast-2.jpg differ diff --git a/public/images/site/_originals/chicken-breast.jpg b/public/images/site/_originals/chicken-breast.jpg new file mode 100644 index 0000000..e63fa49 Binary files /dev/null and b/public/images/site/_originals/chicken-breast.jpg differ diff --git a/public/images/site/_originals/chicken-thighs.jpg b/public/images/site/_originals/chicken-thighs.jpg new file mode 100644 index 0000000..74cae3b Binary files /dev/null and b/public/images/site/_originals/chicken-thighs.jpg differ diff --git a/public/images/site/_originals/chicken-whole-1.jpg b/public/images/site/_originals/chicken-whole-1.jpg new file mode 100644 index 0000000..1309d47 Binary files /dev/null and b/public/images/site/_originals/chicken-whole-1.jpg differ diff --git a/public/images/site/_originals/chicken-whole-2.jpg b/public/images/site/_originals/chicken-whole-2.jpg new file mode 100644 index 0000000..1309d47 Binary files /dev/null and b/public/images/site/_originals/chicken-whole-2.jpg differ diff --git a/public/images/site/_originals/chicken-whole.jpg b/public/images/site/_originals/chicken-whole.jpg new file mode 100644 index 0000000..1309d47 Binary files /dev/null and b/public/images/site/_originals/chicken-whole.jpg differ diff --git a/public/images/site/_originals/chicken-wings.jpg b/public/images/site/_originals/chicken-wings.jpg new file mode 100644 index 0000000..8429377 Binary files /dev/null and b/public/images/site/_originals/chicken-wings.jpg differ diff --git a/public/images/site/_originals/fish-basa.jpg b/public/images/site/_originals/fish-basa.jpg new file mode 100644 index 0000000..d6a301d Binary files /dev/null and b/public/images/site/_originals/fish-basa.jpg differ diff --git a/public/images/site/_originals/fish-prawns.jpg b/public/images/site/_originals/fish-prawns.jpg new file mode 100644 index 0000000..779efbb Binary files /dev/null and b/public/images/site/_originals/fish-prawns.jpg differ diff --git a/public/images/site/_originals/fish-rohu.jpg b/public/images/site/_originals/fish-rohu.jpg new file mode 100644 index 0000000..3371998 Binary files /dev/null and b/public/images/site/_originals/fish-rohu.jpg differ diff --git a/public/images/site/_originals/fish-salmon-2.jpg b/public/images/site/_originals/fish-salmon-2.jpg new file mode 100644 index 0000000..97edff6 Binary files /dev/null and b/public/images/site/_originals/fish-salmon-2.jpg differ diff --git a/public/images/site/_originals/fish-salmon.jpg b/public/images/site/_originals/fish-salmon.jpg new file mode 100644 index 0000000..41585ad Binary files /dev/null and b/public/images/site/_originals/fish-salmon.jpg differ diff --git a/public/images/site/_originals/hero.jpg b/public/images/site/_originals/hero.jpg new file mode 100644 index 0000000..392e164 Binary files /dev/null and b/public/images/site/_originals/hero.jpg differ diff --git a/public/images/site/_originals/lamb-chops.jpg b/public/images/site/_originals/lamb-chops.jpg new file mode 100644 index 0000000..6e5910b Binary files /dev/null and b/public/images/site/_originals/lamb-chops.jpg differ diff --git a/public/images/site/_originals/lamb-leg copy.jpg b/public/images/site/_originals/lamb-leg copy.jpg new file mode 100644 index 0000000..41fda1f Binary files /dev/null and b/public/images/site/_originals/lamb-leg copy.jpg differ diff --git a/public/images/site/_originals/lamb-leg.jpg b/public/images/site/_originals/lamb-leg.jpg new file mode 100644 index 0000000..41fda1f Binary files /dev/null and b/public/images/site/_originals/lamb-leg.jpg differ diff --git a/public/images/site/_originals/lamb-mince.jpg b/public/images/site/_originals/lamb-mince.jpg new file mode 100644 index 0000000..68a15cf Binary files /dev/null and b/public/images/site/_originals/lamb-mince.jpg differ diff --git a/public/images/site/_originals/lamb-shoulder-2.jpg b/public/images/site/_originals/lamb-shoulder-2.jpg new file mode 100644 index 0000000..7ebfbf7 Binary files /dev/null and b/public/images/site/_originals/lamb-shoulder-2.jpg differ diff --git a/public/images/site/_originals/lamb-shoulder.jpg b/public/images/site/_originals/lamb-shoulder.jpg new file mode 100644 index 0000000..7ebfbf7 Binary files /dev/null and b/public/images/site/_originals/lamb-shoulder.jpg differ diff --git a/public/images/site/_originals/weekly-offers.jpg b/public/images/site/_originals/weekly-offers.jpg new file mode 100644 index 0000000..392e164 Binary files /dev/null and b/public/images/site/_originals/weekly-offers.jpg differ diff --git a/public/images/site/about.jpg b/public/images/site/about.jpg index 00cbc70..a0246fb 100644 Binary files a/public/images/site/about.jpg and b/public/images/site/about.jpg differ diff --git a/public/images/site/beef-boneless.jpg b/public/images/site/beef-boneless.jpg index 7286687..6403650 100644 Binary files a/public/images/site/beef-boneless.jpg and b/public/images/site/beef-boneless.jpg differ diff --git a/public/images/site/beef-mince.jpg b/public/images/site/beef-mince.jpg index 68a15cf..9fa7d10 100644 Binary files a/public/images/site/beef-mince.jpg and b/public/images/site/beef-mince.jpg differ diff --git a/public/images/site/beef-nihari-2.jpg b/public/images/site/beef-nihari-2.jpg index 72b4ea9..b833eae 100644 Binary files a/public/images/site/beef-nihari-2.jpg and b/public/images/site/beef-nihari-2.jpg differ diff --git a/public/images/site/beef-nihari.jpg b/public/images/site/beef-nihari.jpg index 72b4ea9..b833eae 100644 Binary files a/public/images/site/beef-nihari.jpg and b/public/images/site/beef-nihari.jpg differ diff --git a/public/images/site/beef-steak-2.jpg b/public/images/site/beef-steak-2.jpg index b1f2c21..739cbce 100644 Binary files a/public/images/site/beef-steak-2.jpg and b/public/images/site/beef-steak-2.jpg differ diff --git a/public/images/site/beef-steak.jpg b/public/images/site/beef-steak.jpg index b1f2c21..739cbce 100644 Binary files a/public/images/site/beef-steak.jpg and b/public/images/site/beef-steak.jpg differ diff --git a/public/images/site/category-beef.jpg b/public/images/site/category-beef.jpg index 7286687..6403650 100644 Binary files a/public/images/site/category-beef.jpg and b/public/images/site/category-beef.jpg differ diff --git a/public/images/site/category-chicken.jpg b/public/images/site/category-chicken.jpg index 3119be2..55579ce 100644 Binary files a/public/images/site/category-chicken.jpg and b/public/images/site/category-chicken.jpg differ diff --git a/public/images/site/category-fish.jpg b/public/images/site/category-fish.jpg index 80ec1f3..4281564 100644 Binary files a/public/images/site/category-fish.jpg and b/public/images/site/category-fish.jpg differ diff --git a/public/images/site/category-lamb.jpg b/public/images/site/category-lamb.jpg index 7ebfbf7..2aef5c2 100644 Binary files a/public/images/site/category-lamb.jpg and b/public/images/site/category-lamb.jpg differ diff --git a/public/images/site/chicken-breast-2.jpg b/public/images/site/chicken-breast-2.jpg index e63fa49..5df2269 100644 Binary files a/public/images/site/chicken-breast-2.jpg and b/public/images/site/chicken-breast-2.jpg differ diff --git a/public/images/site/chicken-breast.jpg b/public/images/site/chicken-breast.jpg index e63fa49..5df2269 100644 Binary files a/public/images/site/chicken-breast.jpg and b/public/images/site/chicken-breast.jpg differ diff --git a/public/images/site/chicken-thighs.jpg b/public/images/site/chicken-thighs.jpg index 74cae3b..d6c64d6 100644 Binary files a/public/images/site/chicken-thighs.jpg and b/public/images/site/chicken-thighs.jpg differ diff --git a/public/images/site/chicken-whole-2.jpg b/public/images/site/chicken-whole-2.jpg index 1309d47..f2168fa 100644 Binary files a/public/images/site/chicken-whole-2.jpg and b/public/images/site/chicken-whole-2.jpg differ diff --git a/public/images/site/chicken-whole.jpg b/public/images/site/chicken-whole.jpg index 1309d47..f2168fa 100644 Binary files a/public/images/site/chicken-whole.jpg and b/public/images/site/chicken-whole.jpg differ diff --git a/public/images/site/chicken-wings.jpg b/public/images/site/chicken-wings.jpg index 8429377..2befd78 100644 Binary files a/public/images/site/chicken-wings.jpg and b/public/images/site/chicken-wings.jpg differ diff --git a/public/images/site/fish-basa.jpg b/public/images/site/fish-basa.jpg index d6a301d..8ee5ddd 100644 Binary files a/public/images/site/fish-basa.jpg and b/public/images/site/fish-basa.jpg differ diff --git a/public/images/site/fish-prawns.jpg b/public/images/site/fish-prawns.jpg index 779efbb..dd499c1 100644 Binary files a/public/images/site/fish-prawns.jpg and b/public/images/site/fish-prawns.jpg differ diff --git a/public/images/site/fish-rohu.jpg b/public/images/site/fish-rohu.jpg index 3371998..d886f50 100644 Binary files a/public/images/site/fish-rohu.jpg and b/public/images/site/fish-rohu.jpg differ diff --git a/public/images/site/fish-salmon-2.jpg b/public/images/site/fish-salmon-2.jpg index 97edff6..29906a6 100644 Binary files a/public/images/site/fish-salmon-2.jpg and b/public/images/site/fish-salmon-2.jpg differ diff --git a/public/images/site/fish-salmon.jpg b/public/images/site/fish-salmon.jpg index 41585ad..a97e136 100644 Binary files a/public/images/site/fish-salmon.jpg and b/public/images/site/fish-salmon.jpg differ diff --git a/public/images/site/hero.jpg b/public/images/site/hero.jpg index 392e164..61b283c 100644 Binary files a/public/images/site/hero.jpg and b/public/images/site/hero.jpg differ diff --git a/public/images/site/lamb-chops.jpg b/public/images/site/lamb-chops.jpg index 6e5910b..4ea6e57 100644 Binary files a/public/images/site/lamb-chops.jpg and b/public/images/site/lamb-chops.jpg differ diff --git a/public/images/site/lamb-leg.jpg b/public/images/site/lamb-leg.jpg index 41fda1f..f7de4be 100644 Binary files a/public/images/site/lamb-leg.jpg and b/public/images/site/lamb-leg.jpg differ diff --git a/public/images/site/lamb-mince.jpg b/public/images/site/lamb-mince.jpg index 68a15cf..9fa7d10 100644 Binary files a/public/images/site/lamb-mince.jpg and b/public/images/site/lamb-mince.jpg differ diff --git a/public/images/site/lamb-shoulder-2.jpg b/public/images/site/lamb-shoulder-2.jpg index 7ebfbf7..2aef5c2 100644 Binary files a/public/images/site/lamb-shoulder-2.jpg and b/public/images/site/lamb-shoulder-2.jpg differ diff --git a/public/images/site/lamb-shoulder.jpg b/public/images/site/lamb-shoulder.jpg index 7ebfbf7..2aef5c2 100644 Binary files a/public/images/site/lamb-shoulder.jpg and b/public/images/site/lamb-shoulder.jpg differ diff --git a/public/images/site/weekly-offers.jpg b/public/images/site/weekly-offers.jpg index 392e164..61b283c 100644 Binary files a/public/images/site/weekly-offers.jpg and b/public/images/site/weekly-offers.jpg differ diff --git a/scripts/optimize-site-images.py b/scripts/optimize-site-images.py new file mode 100644 index 0000000..2a09c70 --- /dev/null +++ b/scripts/optimize-site-images.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +"""Compress site images to <= 100 KB while preserving original colors.""" + +from __future__ import annotations + +import argparse +import io +import shutil +from pathlib import Path + +from PIL import Image + +ROOT = Path(__file__).resolve().parents[1] +SITE = ROOT / "public" / "images" / "site" +BACKUP = SITE / "_originals" +SKIP = {"logo.jpeg", "README.txt"} +SKIP_PATTERNS = (" copy", "-1.jpg") + + +def should_skip(path: Path) -> bool: + if path.name in SKIP or path.name.startswith("_"): + return True + return any(p in path.name for p in SKIP_PATTERNS) + + +def encode_jpeg(img: Image.Image, quality: int) -> bytes: + buf = io.BytesIO() + img.save(buf, format="JPEG", quality=quality, optimize=True, subsampling=2) + return buf.getvalue() + + +def compress_to_target(img: Image.Image, max_bytes: int) -> tuple[Image.Image, int]: + working = img.convert("RGB") + scale = 1.0 + + while scale >= 0.35: + if scale < 1.0: + w, h = working.size + nw, nh = max(1, int(w * scale)), max(1, int(h * scale)) + candidate = working.resize((nw, nh), Image.Resampling.LANCZOS) + else: + candidate = working + + lo, hi = 20, 92 + best_q = lo + best_data = encode_jpeg(candidate, lo) + + while lo <= hi: + mid = (lo + hi) // 2 + data = encode_jpeg(candidate, mid) + if len(data) <= max_bytes: + best_q = mid + best_data = data + lo = mid + 1 + else: + hi = mid - 1 + + if len(best_data) <= max_bytes: + return Image.open(io.BytesIO(best_data)).convert("RGB"), best_q + + scale *= 0.88 + + data = encode_jpeg(candidate, 20) + return Image.open(io.BytesIO(data)).convert("RGB"), 20 + + +def optimize_file(path: Path, max_bytes: int, dry_run: bool) -> tuple[int, int]: + before = path.stat().st_size + if before <= max_bytes: + return before, before + + with Image.open(path) as img: + out, quality = compress_to_target(img, max_bytes) + + if dry_run: + buf = io.BytesIO() + out.save(buf, format="JPEG", quality=quality, optimize=True, subsampling=2) + return before, len(buf.getvalue()) + + out.save(path, format="JPEG", quality=quality, optimize=True, subsampling=2) + return before, path.stat().st_size + + +def main() -> None: + parser = argparse.ArgumentParser(description="Optimize site images to <= 100 KB") + parser.add_argument("--max-kb", type=int, default=100) + parser.add_argument("--dry-run", action="store_true") + args = parser.parse_args() + + max_bytes = args.max_kb * 1024 + BACKUP.mkdir(exist_ok=True) + + files = sorted(SITE.glob("*.jpg")) + sorted(SITE.glob("*.jpeg")) + changed = 0 + + for path in files: + if should_skip(path): + continue + + backup = BACKUP / path.name + if not backup.exists() and not args.dry_run: + shutil.copy2(path, backup) + + before, after = optimize_file(path, max_bytes, args.dry_run) + if after < before or before > max_bytes: + changed += 1 + print( + f"{'~' if args.dry_run else '✓'} {path.name}: " + f"{before // 1024} KB → {after // 1024} KB" + ) + else: + print(f"○ {path.name}: {before // 1024} KB (ok)") + + print(f"\nDone — {changed} optimized, originals in {BACKUP}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 28970f8..9f4fb98 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -1,6 +1,7 @@ 'use client'; import Link from 'next/link'; +import AboutIntro from '@/components/about/AboutIntro'; import { ShieldCheck, Leaf, Award, Truck, Phone, Mail, MapPin } from 'lucide-react'; import { useTranslation } from '@/hooks/useTranslation'; import { @@ -25,6 +26,8 @@ export default function AboutPage() { + +

{t('about.ourStory')}

diff --git a/src/app/globals.css b/src/app/globals.css index 261975f..514c8cb 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -63,4 +63,13 @@ .gold-accent { @apply bg-gradient-to-r from-gold-400 to-gold-500 bg-clip-text text-transparent; } + + .scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; + } + + .scrollbar-hide::-webkit-scrollbar { + display: none; + } } \ No newline at end of file diff --git a/src/components/home/AboutPreview.tsx b/src/components/about/AboutIntro.tsx similarity index 83% rename from src/components/home/AboutPreview.tsx rename to src/components/about/AboutIntro.tsx index 73f4824..541c627 100644 --- a/src/components/home/AboutPreview.tsx +++ b/src/components/about/AboutIntro.tsx @@ -1,12 +1,10 @@ 'use client'; import AppImage from '@/components/ui/AppImage'; -import { ArrowRight } from 'lucide-react'; -import Button from '@/components/ui/Button'; import { useTranslation } from '@/hooks/useTranslation'; import { PAGE_IMAGES } from '@/infrastructure/images'; -export default function AboutPreview() { +export default function AboutIntro() { const { t } = useTranslation(); const stats = [ @@ -17,7 +15,7 @@ export default function AboutPreview() { ]; return ( -
+
@@ -29,7 +27,7 @@ export default function AboutPreview() {

{t('aboutPreview.p2')}

{t('aboutPreview.p3')}

-
+
{stats.map((stat) => (
))}
- -
diff --git a/src/components/home/CTA.tsx b/src/components/home/CTA.tsx index 510af54..3c60287 100644 --- a/src/components/home/CTA.tsx +++ b/src/components/home/CTA.tsx @@ -9,9 +9,9 @@ export default function CTA() { const { t } = useTranslation(); return ( -
+
-
+
diff --git a/src/components/home/ContactPreview.tsx b/src/components/home/ContactPreview.tsx index 3a39a9a..a6b8658 100644 --- a/src/components/home/ContactPreview.tsx +++ b/src/components/home/ContactPreview.tsx @@ -1,14 +1,14 @@ 'use client'; -import { Phone, MapPin, Clock, MessageCircle, ExternalLink } from 'lucide-react'; +import { Phone, MapPin, Clock, MessageCircle, Facebook, Instagram } from 'lucide-react'; import Button from '@/components/ui/Button'; -import Logo from '@/components/ui/Logo'; import { SITE_ADDRESS, SITE_PHONE_DISPLAY, SITE_HOURS, WHATSAPP_URL, - MAPS_URL, + FACEBOOK_URL, + INSTAGRAM_URL, } from '@/lib/constants'; import { useTranslation } from '@/hooks/useTranslation'; @@ -17,93 +17,98 @@ export default function ContactPreview() { const telHref = `tel:+46725855050`; return ( -
-
-
-
- - {t('contact.label')} - -

- {t('contact.title')} -

+
+
+
+
-
    -
  • - -
    -

    - {t('contact.addressLabel')} -

    -

    {SITE_ADDRESS}

    -
    -
  • -
  • - -
    -

    - {t('contact.phoneLabel')} -

    - - {SITE_PHONE_DISPLAY} - -
    -
  • -
  • - -
    -

    - {t('contact.hoursLabel')} -

    -

    - {t('contact.hoursValue', { hours: SITE_HOURS })} -

    -
    -
  • -
+
+ + {t('contact.label')} + +

+ {t('contact.title')} +

-
- - - +
    +
  • + +
    +

    + {t('contact.addressLabel')} +

    +

    {SITE_ADDRESS}

    -
+ +
  • + +
    +

    + {t('contact.phoneLabel')} +

    + + {SITE_PHONE_DISPLAY} + +
    +
  • +
  • + +
    +

    + {t('contact.hoursLabel')} +

    +

    + {t('contact.hoursValue', { hours: SITE_HOURS })} +

    +
    +
  • + -
    - - -
    -

    {t('site.name')}

    -

    {SITE_ADDRESS}

    -
    - - {t('contact.openMaps')} - - -
    +
    + + + +
    + +
    +

    + {t('social.label')} +

    +
    + {[ + { href: FACEBOOK_URL, icon: Facebook, label: t('social.facebook') }, + { href: INSTAGRAM_URL, icon: Instagram, label: t('social.instagram') }, + { href: WHATSAPP_URL, icon: MessageCircle, label: t('social.whatsapp') }, + ].map(({ href, icon: Icon, label }) => ( + + + {label} + + ))}
    diff --git a/src/components/home/FeaturedProducts.tsx b/src/components/home/FeaturedProducts.tsx index 427cee3..f6f9731 100644 --- a/src/components/home/FeaturedProducts.tsx +++ b/src/components/home/FeaturedProducts.tsx @@ -1,22 +1,72 @@ 'use client'; +import { useCallback, useEffect, useRef, useState } from 'react'; import ProductCard from '@/components/product/ProductCard'; -import { getFeaturedProducts } from '@/lib/products'; +import { products } from '@/lib/products'; import Button from '@/components/ui/Button'; -import { ArrowRight } from 'lucide-react'; +import { ArrowRight, ChevronLeft, ChevronRight } from 'lucide-react'; import { useTranslation } from '@/hooks/useTranslation'; import { localizeProduct } from '@/lib/product-i18n'; +import { isRtl } from '@/i18n'; +import { cn } from '@/lib/utils'; + +const AUTO_SCROLL_MS = 4500; export default function FeaturedProducts() { - const { t } = useTranslation(); - const featured = getFeaturedProducts() - .slice(0, 4) - .map((p) => localizeProduct(p, t)); + const { t, locale } = useTranslation(); + const rtl = isRtl(locale); + const trackRef = useRef(null); + const [paused, setPaused] = useState(false); + + const allProducts = products.map((p) => localizeProduct(p, t)); + + const getScrollStep = useCallback(() => { + const track = trackRef.current; + if (!track) return 320; + const card = track.querySelector('[data-slide]'); + if (!card) return 320; + const gap = 24; + return card.offsetWidth + gap; + }, []); + + const scroll = useCallback( + (direction: 'next' | 'prev') => { + const track = trackRef.current; + if (!track) return; + + const step = getScrollStep(); + const forward = direction === 'next'; + const delta = forward ? step : -step; + const scrollDelta = rtl ? -delta : delta; + + const maxScroll = track.scrollWidth - track.clientWidth; + const atEnd = track.scrollLeft >= maxScroll - 8; + const atStart = track.scrollLeft <= 8; + + if (forward && atEnd) { + track.scrollTo({ left: rtl ? maxScroll : 0, behavior: 'smooth' }); + return; + } + if (!forward && atStart) { + track.scrollTo({ left: rtl ? 0 : maxScroll, behavior: 'smooth' }); + return; + } + + track.scrollBy({ left: scrollDelta, behavior: 'smooth' }); + }, + [getScrollStep, rtl] + ); + + useEffect(() => { + if (paused) return; + const id = window.setInterval(() => scroll('next'), AUTO_SCROLL_MS); + return () => window.clearInterval(id); + }, [paused, scroll]); return ( -
    +
    -
    +
    {t('featured.label')} @@ -30,10 +80,49 @@ export default function FeaturedProducts() {
    -
    - {featured.map((product) => ( - - ))} +
    setPaused(true)} + onMouseLeave={() => setPaused(false)} + onFocus={() => setPaused(true)} + onBlur={() => setPaused(false)} + > +
    +
    + +
    + {allProducts.map((product) => ( +
    + +
    + ))} +
    + +
    + + +
    diff --git a/src/components/home/Hero.tsx b/src/components/home/Hero.tsx index 129aeca..fb50e30 100644 --- a/src/components/home/Hero.tsx +++ b/src/components/home/Hero.tsx @@ -3,6 +3,7 @@ import AppImage from '@/components/ui/AppImage'; import Button from '@/components/ui/Button'; import Logo from '@/components/ui/Logo'; +import TrustBadges from '@/components/home/TrustBadges'; import { ArrowRight, ShieldCheck, MapPin, Clock } from 'lucide-react'; import { useTranslation } from '@/hooks/useTranslation'; import { PAGE_IMAGES } from '@/infrastructure/images'; @@ -28,8 +29,8 @@ export default function Hero() {
    -
    -
    +
    +
    @@ -57,6 +58,10 @@ export default function Hero() { {t('hero.subtitle')}

    +
    + +
    +
    @@ -84,6 +89,10 @@ export default function Hero() {
    + +
    + +
    diff --git a/src/components/home/HomeView.tsx b/src/components/home/HomeView.tsx index 85bba46..38c48c5 100644 --- a/src/components/home/HomeView.tsx +++ b/src/components/home/HomeView.tsx @@ -1,27 +1,17 @@ import Hero from '@/components/home/Hero'; -import TrustBadges from '@/components/home/TrustBadges'; -import AboutPreview from '@/components/home/AboutPreview'; import CategoryGrid from '@/components/home/CategoryGrid'; import FeaturedProducts from '@/components/home/FeaturedProducts'; import HowItWorks from '@/components/home/HowItWorks'; import WeeklyOffers from '@/components/home/WeeklyOffers'; -import SocialFollow from '@/components/home/SocialFollow'; -import ContactPreview from '@/components/home/ContactPreview'; -import CTA from '@/components/home/CTA'; export default function HomeView() { return ( <> - - - - - ); } \ No newline at end of file diff --git a/src/components/home/TrustBadges.tsx b/src/components/home/TrustBadges.tsx index 3ac95f4..4af5b53 100644 --- a/src/components/home/TrustBadges.tsx +++ b/src/components/home/TrustBadges.tsx @@ -1,13 +1,46 @@ 'use client'; import Link from 'next/link'; -import { ShieldCheck, Leaf, Award } from 'lucide-react'; +import { ShieldCheck, Leaf, Award, type LucideIcon } from 'lucide-react'; import { useTranslation } from '@/hooks/useTranslation'; +import { isRtl } from '@/i18n'; +import { cn } from '@/lib/utils'; + +interface BadgeItem { + icon: LucideIcon; + title: string; + description: string; + href: string; +} + +function BadgeCard({ badge }: { badge: BadgeItem }) { + const Icon = badge.icon; + + return ( + +
    + +
    +
    +

    + {badge.title} +

    +

    + {badge.description} +

    +
    + + ); +} export default function TrustBadges() { - const { t } = useTranslation(); + const { t, locale } = useTranslation(); + const rtl = isRtl(locale); - const badges = [ + const badges: BadgeItem[] = [ { icon: ShieldCheck, title: t('trust.halal'), @@ -28,27 +61,25 @@ export default function TrustBadges() { }, ]; + const loop = [...badges, ...badges]; + return ( -
    -
    -
    - {badges.map((badge) => ( - -
    - -
    -

    - {badge.title} -

    -

    {badge.description}

    - +
    +
    +
    +
    + +
    + {loop.map((badge, i) => ( + ))}
    -
    +
    ); } \ No newline at end of file diff --git a/src/components/home/WeeklyOffers.tsx b/src/components/home/WeeklyOffers.tsx index b38bdc3..434766d 100644 --- a/src/components/home/WeeklyOffers.tsx +++ b/src/components/home/WeeklyOffers.tsx @@ -14,9 +14,9 @@ export default function WeeklyOffers() { const fmt = (n: number) => formatPrice(n, getFormatLocale(locale)); return ( -
    +
    -
    +
    {t('offers.label')} @@ -112,7 +112,7 @@ export default function WeeklyOffers() {