diff --git a/next.config.js b/next.config.js
index b55722b..f27b9ca 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,5 +1,16 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
+ // Bundle zustand reliably — avoids missing ./vendor-chunks/zustand.js in dev
+ transpilePackages: ['zustand'],
+ async redirects() {
+ return [
+ {
+ source: '/index',
+ destination: '/',
+ permanent: true,
+ },
+ ];
+ },
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 2560],
diff --git a/package.json b/package.json
index c971b98..095a4e0 100644
--- a/package.json
+++ b/package.json
@@ -3,13 +3,15 @@
"version": "1.0.0",
"private": true,
"scripts": {
- "dev": "next dev",
- "build": "next build",
+ "clean": "rm -rf .next",
+ "dev": "bash scripts/dev.sh",
+ "build": "bash scripts/build.sh",
"start": "next start",
"lint": "next lint",
"annotate": "node docs/annotate-all.mjs",
"images:populate": "bash scripts/populate-site-images.sh",
- "images:fish": "bash scripts/update-fish-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'"
},
"dependencies": {
"clsx": "^2.1.0",
diff --git a/public/images/site/about.jpg b/public/images/site/about.jpg
index 032a9cc..00cbc70 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 c1e70f5..7286687 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 95921ba..68a15cf 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 f768e81..72b4ea9 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 aff6f88..72b4ea9 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 aff6f88..b1f2c21 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 f768e81..b1f2c21 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 126e98c..7286687 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 21afdfa..3119be2 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 eb31de8..80ec1f3 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 dfcdb2d..7ebfbf7 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 copy.jpg b/public/images/site/chicken-breast copy.jpg
new file mode 100644
index 0000000..c4110ee
Binary files /dev/null and b/public/images/site/chicken-breast copy.jpg differ
diff --git a/public/images/site/chicken-breast-2.jpg b/public/images/site/chicken-breast-2.jpg
index abd5ecd..e63fa49 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 d7de864..e63fa49 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 9029e8f..74cae3b 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-1.jpg b/public/images/site/chicken-whole-1.jpg
new file mode 100644
index 0000000..1309d47
Binary files /dev/null and b/public/images/site/chicken-whole-1.jpg differ
diff --git a/public/images/site/chicken-whole-2.jpg b/public/images/site/chicken-whole-2.jpg
index d7de864..1309d47 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 abd5ecd..1309d47 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 40c29ce..8429377 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 1fcbe16..d6a301d 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 51a2496..779efbb 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 b8f45e9..3371998 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 ed0aacf..97edff6 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 f8197a5..41585ad 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 77ee3ae..392e164 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 1635e44..6e5910b 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 copy.jpg b/public/images/site/lamb-leg copy.jpg
new file mode 100644
index 0000000..41fda1f
Binary files /dev/null and b/public/images/site/lamb-leg copy.jpg differ
diff --git a/public/images/site/lamb-leg.jpg b/public/images/site/lamb-leg.jpg
index 322ac2d..41fda1f 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 5e4b8c2..68a15cf 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 322ac2d..7ebfbf7 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 dfcdb2d..7ebfbf7 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 77ee3ae..392e164 100644
Binary files a/public/images/site/weekly-offers.jpg and b/public/images/site/weekly-offers.jpg differ
diff --git a/scripts/branded-beef-boneless.py b/scripts/branded-beef-boneless.py
new file mode 100644
index 0000000..8e136d2
--- /dev/null
+++ b/scripts/branded-beef-boneless.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+"""Prepare a sharp, full-frame beef boneless product photo for the shop."""
+
+from __future__ import annotations
+
+import argparse
+import urllib.request
+from pathlib import Path
+
+from PIL import Image, ImageEnhance, ImageFilter, ImageOps, ImageStat
+
+ROOT = Path(__file__).resolve().parents[1]
+SITE = ROOT / "public" / "images" / "site"
+OUT_PATH = SITE / "beef-boneless.jpg"
+SOURCE_CACHE = SITE / "_beef_boneless_src.jpg"
+
+# High-res premium raw boneless beef (Unsplash)
+PRIMARY_URL = (
+ "https://images.unsplash.com/photo-1603048297172-c92544798d5a"
+ "?auto=format&fit=crop&w=1800&h=1800&crop=center&q=95"
+)
+# Original product reference (lower res fallback)
+FALLBACK_URL = (
+ "https://www.hotcurrymarket.fi/wp-content/uploads/2025/01/"
+ "FRESH-BEEF-BONELESS-WITHOUT-FAT-1KG.jpg"
+)
+
+OUTPUT_SIZE = (1400, 1400)
+
+
+def download(url: str, dest: Path) -> None:
+ req = urllib.request.Request(
+ url,
+ headers={"User-Agent": "Mozilla/5.0 (compatible; Kottgard-site-builder/1.0)"},
+ )
+ with urllib.request.urlopen(req, timeout=60) as resp:
+ dest.write_bytes(resp.read())
+
+
+def trim_near_white(img: Image.Image, tolerance: int = 18) -> Image.Image:
+ """Remove excess light borders from the source photo."""
+ rgb = img.convert("RGB")
+ pixels = rgb.load()
+ w, h = rgb.size
+
+ def row_is_border(y: int) -> bool:
+ samples = [pixels[x, y] for x in range(0, w, max(1, w // 24))]
+ return all(min(p) >= 255 - tolerance for p in samples)
+
+ def col_is_border(x: int) -> bool:
+ samples = [pixels[x, y] for y in range(0, h, max(1, h // 24))]
+ return all(min(p) >= 255 - tolerance for p in samples)
+
+ top = 0
+ while top < h and row_is_border(top):
+ top += 1
+ bottom = h - 1
+ while bottom > top and row_is_border(bottom):
+ bottom -= 1
+ left = 0
+ while left < w and col_is_border(left):
+ left += 1
+ right = w - 1
+ while right > left and col_is_border(right):
+ right -= 1
+
+ if right - left > 80 and bottom - top > 80:
+ return rgb.crop((left, top, right + 1, bottom + 1))
+ return rgb
+
+
+def enhance_photo(img: Image.Image) -> Image.Image:
+ img = ImageOps.autocontrast(img, cutoff=0.5)
+ img = ImageEnhance.Brightness(img).enhance(1.03)
+ img = ImageEnhance.Contrast(img).enhance(1.08)
+ img = ImageEnhance.Color(img).enhance(1.1)
+ img = ImageEnhance.Sharpness(img).enhance(1.35)
+ return img.filter(ImageFilter.UnsharpMask(radius=1.0, percent=110, threshold=2))
+
+
+def crop_center_cover(img: Image.Image, size: tuple[int, int]) -> Image.Image:
+ tw, th = size
+ w, h = img.size
+ scale = max(tw / w, th / h)
+ nw, nh = int(w * scale), int(h * scale)
+ resized = img.resize((nw, nh), Image.Resampling.LANCZOS)
+ left = (nw - tw) // 2
+ top = (nh - th) // 2
+ return resized.crop((left, top, left + tw, top + th))
+
+
+def prepare_product_image(source: Path, output: Path = OUT_PATH) -> None:
+ beef = Image.open(source).convert("RGB")
+ beef = trim_near_white(beef)
+ beef = enhance_photo(beef)
+ # Full-frame crop — meat fills the product image (no tiny subject in white void)
+ beef = crop_center_cover(beef, OUTPUT_SIZE)
+ beef.save(output, "JPEG", quality=96, optimize=True, subsampling=0)
+ print(f"Saved product photo {OUTPUT_SIZE[0]}x{OUTPUT_SIZE[1]} → {output}")
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description="Prepare beef boneless product photo")
+ parser.add_argument("--url", default=PRIMARY_URL)
+ parser.add_argument("--input", type=Path)
+ parser.add_argument("--output", type=Path, default=OUT_PATH)
+ parser.add_argument("--use-cache", action="store_true")
+ parser.add_argument("--fallback", action="store_true", help="Use hot curry source")
+ args = parser.parse_args()
+
+ if args.input:
+ src = args.input
+ elif args.use_cache and SOURCE_CACHE.exists():
+ src = SOURCE_CACHE
+ print(f"Using cached source → {src}")
+ else:
+ url = FALLBACK_URL if args.fallback else args.url
+ print(f"Downloading source image…\n {url}")
+ try:
+ download(url, SOURCE_CACHE)
+ except Exception as exc:
+ print(f"Primary download failed ({exc}), trying fallback…")
+ download(FALLBACK_URL, SOURCE_CACHE)
+ src = SOURCE_CACHE
+
+ prepare_product_image(src, args.output)
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 0000000..462df67
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+# Production build must not reuse a dev .next folder.
+set -euo pipefail
+cd "$(dirname "$0")/.."
+
+pkill -f "next dev" 2>/dev/null || true
+rm -rf .next
+echo "Building production bundle (clean cache)…"
+exec npx next build
\ No newline at end of file
diff --git a/scripts/dev.sh b/scripts/dev.sh
new file mode 100755
index 0000000..34ec266
--- /dev/null
+++ b/scripts/dev.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+# Always start dev with a fresh .next cache — prevents missing vendor-chunks (zustand.js).
+set -euo pipefail
+cd "$(dirname "$0")/.."
+
+pkill -f "next dev" 2>/dev/null || true
+if lsof -ti:3000 >/dev/null 2>&1; then
+ lsof -ti:3000 | xargs kill -9 2>/dev/null || true
+ sleep 1
+fi
+
+rm -rf .next
+echo "Starting Next.js dev server (clean cache)…"
+exec npx next dev
\ No newline at end of file
diff --git a/scripts/process-all-site-images.py b/scripts/process-all-site-images.py
new file mode 100644
index 0000000..7cc1c36
--- /dev/null
+++ b/scripts/process-all-site-images.py
@@ -0,0 +1,213 @@
+#!/usr/bin/env python3
+"""Normalize site images: square canvas, HALAL badge + Kött Gård logo. Colors preserved."""
+
+from __future__ import annotations
+
+import argparse
+import shutil
+from pathlib import Path
+
+from PIL import Image, ImageDraw, ImageFont
+
+ROOT = Path(__file__).resolve().parents[1]
+SITE = ROOT / "public" / "images" / "site"
+BACKUP = SITE / "_originals"
+LOGO_PATH = SITE / "logo.jpeg"
+BRAND_NAME = "Kött Gård"
+
+SIZE = (1400, 1400)
+BG = (255, 255, 255)
+BURGUNDY = (139, 31, 31)
+GOLD = (212, 175, 55)
+
+SKIP = {"logo.jpeg", "README.txt"}
+SKIP_PATTERNS = (" copy", "-1.jpg") # duplicate uploads from user
+FILL_PRODUCT = 0.9
+FILL_PAGE = 0.94
+
+PAGE_IMAGES = {"hero.jpg", "about.jpg", "weekly-offers.jpg"}
+CATEGORY_IMAGES = {
+ "category-chicken.jpg",
+ "category-beef.jpg",
+ "category-lamb.jpg",
+ "category-fish.jpg",
+}
+
+
+def load_font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
+ paths = [
+ "/System/Library/Fonts/Supplemental/Arial Bold.ttf"
+ if bold
+ else "/System/Library/Fonts/Supplemental/Arial.ttf",
+ "/Library/Fonts/Arial.ttf",
+ ]
+ for p in paths:
+ try:
+ return ImageFont.truetype(p, size)
+ except OSError:
+ continue
+ return ImageFont.load_default()
+
+
+def preserve_colors(img: Image.Image) -> Image.Image:
+ """Keep original photo pixels — no contrast, brightness, or color tweaks."""
+ return img.convert("RGB")
+
+
+def prep_logo(diameter: int) -> Image.Image:
+ logo = Image.open(LOGO_PATH).convert("RGBA")
+ w, h = logo.size
+ side = min(w, h)
+ logo = logo.crop(((w - side) // 2, (h - side) // 2, (w + side) // 2, (h + side) // 2))
+ logo = logo.resize((diameter, diameter), Image.Resampling.LANCZOS)
+ mask = Image.new("L", (diameter, diameter), 0)
+ ImageDraw.Draw(mask).ellipse((0, 0, diameter - 1, diameter - 1), fill=255)
+ logo.putalpha(mask)
+ return logo
+
+
+def draw_halal_badge(canvas: Image.Image, x: int, y: int, size: int = 130) -> None:
+ layer = Image.new("RGBA", canvas.size, (0, 0, 0, 0))
+ draw = ImageDraw.Draw(layer)
+
+ draw.ellipse((x + 4, y + 5, x + size + 4, y + size + 5), fill=(0, 0, 0, 100))
+ draw.ellipse((x - 2, y - 2, x + size + 2, y + size + 2), fill=(*GOLD, 255))
+ draw.ellipse((x + 6, y + 6, x + size - 6, y + size - 6), fill=(255, 255, 255, 255))
+ draw.ellipse((x + 10, y + 10, x + size - 10, y + size - 10), outline=(*BURGUNDY, 255), width=4)
+
+ f_ar = load_font(24, bold=True)
+ f_halal = load_font(28, bold=True)
+ f_sub = load_font(11, bold=True)
+
+ ar = draw.textbbox((0, 0), "حلال", font=f_ar)
+ draw.text((x + (size - ar[2] + ar[0]) // 2, y + 22), "حلال", fill=(*BURGUNDY, 255), font=f_ar)
+ hl = draw.textbbox((0, 0), "HALAL", font=f_halal)
+ draw.text((x + (size - hl[2] + hl[0]) // 2, y + 52), "HALAL", fill=(*BURGUNDY, 255), font=f_halal)
+ ce = draw.textbbox((0, 0), "CERTIFIED", font=f_sub)
+ draw.text((x + (size - ce[2] + ce[0]) // 2, y + 92), "CERTIFIED", fill=(*GOLD, 255), font=f_sub)
+
+ canvas.alpha_composite(layer)
+
+
+def draw_logo_badge(canvas: Image.Image, logo: Image.Image, x: int, y: int) -> None:
+ d = logo.size[0]
+ layer = Image.new("RGBA", canvas.size, (0, 0, 0, 0))
+ draw = ImageDraw.Draw(layer)
+ draw.ellipse((x + 3, y + 4, x + d + 3, y + d + 4), fill=(0, 0, 0, 90))
+ draw.ellipse((x - 4, y - 4, x + d + 4, y + d + 4), fill=(255, 255, 255, 245), outline=(*GOLD, 255), width=3)
+ canvas.alpha_composite(layer)
+ canvas.alpha_composite(logo, (x, y))
+
+
+def draw_brand_name(canvas: Image.Image, x: int, y: int) -> None:
+ """Brand name pill beside the logo badge."""
+ layer = Image.new("RGBA", canvas.size, (0, 0, 0, 0))
+ draw = ImageDraw.Draw(layer)
+ font = load_font(22, bold=True)
+ bbox = draw.textbbox((0, 0), BRAND_NAME, font=font)
+ tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
+ pad_x, pad_y = 14, 8
+ box = (x - tw - pad_x * 2, y, x - 8, y + th + pad_y * 2)
+ draw.rounded_rectangle(box, radius=10, fill=(255, 255, 255, 235), outline=(*GOLD, 255), width=2)
+ draw.text((box[0] + pad_x, box[1] + pad_y - 2), BRAND_NAME, fill=(*BURGUNDY, 255), font=font)
+ canvas.alpha_composite(layer)
+
+
+def already_branded(path: Path) -> bool:
+ """Skip images already at catalog size (user uploads with logos baked in)."""
+ with Image.open(path) as img:
+ return img.size == SIZE
+
+
+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 compose(source: Path, dest: Path, logo: Image.Image) -> None:
+ photo = preserve_colors(Image.open(source))
+ fill = FILL_PAGE if dest.name in PAGE_IMAGES else FILL_PRODUCT
+ if dest.name in CATEGORY_IMAGES:
+ fill = 0.92
+
+ canvas = Image.new("RGBA", SIZE, (*BG, 255))
+ max_side = int(SIZE[0] * fill)
+ photo.thumbnail((max_side, max_side), Image.Resampling.LANCZOS)
+
+ shadow = Image.new("RGBA", photo.size, (0, 0, 0, 0))
+ sh_draw = ImageDraw.Draw(shadow)
+ sh_draw.rounded_rectangle(
+ (8, 12, photo.width - 8, photo.height - 4),
+ radius=18,
+ fill=(0, 0, 0, 35),
+ )
+ px = (SIZE[0] - photo.width) // 2
+ py = (SIZE[1] - photo.height) // 2 - 16
+ canvas.alpha_composite(shadow, (px, py + 6))
+ canvas.alpha_composite(photo.convert("RGBA"), (px, py))
+
+ margin = 28
+ draw_halal_badge(canvas, margin, margin)
+ logo_size = 108
+ logo_x = SIZE[0] - logo_size - margin
+ logo_y = SIZE[1] - logo_size - margin
+ draw_logo_badge(canvas, logo, logo_x, logo_y)
+ draw_brand_name(canvas, logo_x, logo_y + logo_size // 2 - 12)
+
+ canvas.convert("RGB").save(dest, "JPEG", quality=94, optimize=True, subsampling=0)
+
+
+def ensure_catalog_files() -> None:
+ pairs = [
+ ("beef-steak.jpg", "beef-steak-2.jpg"),
+ ("beef-nihari-2.jpg", "beef-nihari.jpg"),
+ ("category-lamb.jpg", "lamb-shoulder.jpg"),
+ ]
+ for target, source in pairs:
+ t, s = SITE / target, SITE / source
+ if not t.exists() and s.exists():
+ shutil.copy2(s, t)
+ print(f"Created missing {target} from {source}")
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description="Brand site images (colors preserved)")
+ parser.add_argument("--force", action="store_true", help="Re-process even if already 1400×1400")
+ parser.add_argument("--only", nargs="*", help="Process only these filenames")
+ args = parser.parse_args()
+
+ if not LOGO_PATH.exists():
+ raise SystemExit(f"Missing logo: {LOGO_PATH}")
+
+ BACKUP.mkdir(exist_ok=True)
+ ensure_catalog_files()
+
+ logo = prep_logo(108)
+ files = sorted(SITE.glob("*.jpg"))
+ processed = 0
+ skipped = 0
+
+ for path in files:
+ if should_skip(path):
+ continue
+ if args.only and path.name not in args.only:
+ continue
+ if not args.force and already_branded(path):
+ print(f"○ {path.name} (already branded, skipped)")
+ skipped += 1
+ continue
+
+ backup = BACKUP / path.name
+ if not backup.exists():
+ shutil.copy2(path, backup)
+ source = backup if backup.exists() else path
+ compose(source, path, logo)
+ print(f"✓ {path.name}")
+ processed += 1
+
+ print(f"\nDone — {processed} processed, {skipped} skipped. Originals in {BACKUP}")
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/src/app/home/page.tsx b/src/app/home/page.tsx
new file mode 100644
index 0000000..5faf43f
--- /dev/null
+++ b/src/app/home/page.tsx
@@ -0,0 +1,16 @@
+import type { Metadata } from 'next';
+import HomeView from '@/components/home/HomeView';
+import { SITE_NAME } from '@/lib/constants';
+
+export const metadata: Metadata = {
+ title: 'Home',
+ description:
+ 'Welcome to Kött Gård — premium 100% Halal meat delivery. Fresh chicken, beef, and lamb; frozen fish and seafood only.',
+ alternates: {
+ canonical: '/',
+ },
+};
+
+export default function HomeAliasPage() {
+ return
{t('featured.subtitle')}