create-brainerce-store 1.54.0 → 1.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,8 +8,8 @@
8
8
  },
9
9
  "home": {
10
10
  "heroEyebrow": "New collection · Handcrafted",
11
- "heroTitle": "Jewelry that tells your story",
12
- "heroLead": "Delicate gold and silver designs, handcrafted with love — made to be with you through the small moments and the big ones.",
11
+ "heroTitle": "Pieces that tell your story",
12
+ "heroLead": "Thoughtfully designed, handcrafted with love — made to be with you through the small moments and the big ones.",
13
13
  "heroCtaSecondary": "Browse categories",
14
14
  "heroTrust": "Happy customers nationwide",
15
15
  "benefit1Title": "Fast home delivery",
@@ -17,37 +17,37 @@
17
17
  "benefit2Title": "Secure payment",
18
18
  "benefit2Desc": "Encrypted checkout, highest standard",
19
19
  "benefit3Title": "Quality materials",
20
- "benefit3Desc": "Gold, 925 silver and genuine stones",
20
+ "benefit3Desc": "Carefully sourced, built to last",
21
21
  "benefit4Title": "Easy returns",
22
22
  "benefit4Desc": "30 days to exchange or return, no questions",
23
23
  "featuredEyebrow": "Our picks",
24
24
  "featuredSubtitle": "The pieces our customers love most — hand-picked from the new collection.",
25
25
  "categoriesEyebrow": "Categories",
26
26
  "categoriesTitle": "Find the perfect piece",
27
- "categoriesSubtitle": "From delicate rings to statement necklaceseach one speaks its own language.",
27
+ "categoriesSubtitle": "From everyday favorites to statement piecesthere's something for everyone.",
28
28
  "catRings": "Rings",
29
29
  "catNecklaces": "Necklaces",
30
30
  "catEarrings": "Earrings",
31
31
  "catBracelets": "Bracelets",
32
32
  "catCta": "Shop the category",
33
33
  "storyEyebrow": "Our studio",
34
- "storyTitle": "Made by hand, worn by heart",
35
- "storyP1": "Every piece begins as a small sketch on paper — and ends as delicate handwork in our studio. We choose every stone and every link with care, so the piece that reaches you is exactly as we imagined it.",
36
- "storyP2": "We believe in jewelry that lasts: real materials, meticulous finish and full warranty on every piece. That is what love for the small details looks like.",
34
+ "storyTitle": "Made with care, made to last",
35
+ "storyP1": "Every piece begins as a small sketch on paper — and ends as delicate handwork in our studio. We choose every material and every detail with care, so the piece that reaches you is exactly as we imagined it.",
36
+ "storyP2": "We believe in pieces that last: real materials, meticulous finish and full warranty on every piece. That is what love for the small details looks like.",
37
37
  "storyCta": "Discover the collection",
38
38
  "storyStat1Value": "Handmade",
39
39
  "storyStat1Label": "Every piece is made in our studio",
40
- "storyStat2Value": "925 Silver",
41
- "storyStat2Label": "Genuine materials only",
40
+ "storyStat2Value": "Hand-finished",
41
+ "storyStat2Label": "Every piece checked before it ships",
42
42
  "testimonialsEyebrow": "Customer stories",
43
43
  "testimonialsTitle": "They already fell in love",
44
- "testimonial1Quote": "The necklace arrived in gorgeous packaging and looks even better than the photos. I got compliments on day one.",
44
+ "testimonial1Quote": "It arrived in gorgeous packaging and looks even better than the photos. I got compliments on day one.",
45
45
  "testimonial1Name": "Noa L.",
46
46
  "testimonial1Meta": "Tel Aviv",
47
- "testimonial2Quote": "I bought earrings as a gift for my mom and she has not taken them off since. Amazing quality and truly personal service.",
47
+ "testimonial2Quote": "I bought this as a gift for my mom and she has not put it down since. Amazing quality and truly personal service.",
48
48
  "testimonial2Name": "Shira B.",
49
49
  "testimonial2Meta": "Haifa",
50
- "testimonial3Quote": "The ring fits perfectly, the work is delicate and precise. You can feel every detail was made with love. Highly recommend.",
50
+ "testimonial3Quote": "It's exactly as described, the work is delicate and precise. You can feel every detail was made with love. Highly recommend.",
51
51
  "testimonial3Name": "Dana K.",
52
52
  "testimonial3Meta": "Jerusalem",
53
53
  "newsletterTitle": "Join our club",
@@ -88,7 +88,7 @@
88
88
  "shippingAtCheckout": "Shipping calculated at checkout"
89
89
  },
90
90
  "footer": {
91
- "tagline": "Handcrafted jewelry, designed with love in Israel.",
91
+ "tagline": "{{storeName}} handcrafted with love in Israel.",
92
92
  "followUs": "Follow us",
93
93
  "quickLinksTitle": "Quick links",
94
94
  "linkProducts": "All products",
@@ -8,8 +8,8 @@
8
8
  },
9
9
  "home": {
10
10
  "heroEyebrow": "קולקציה חדשה · עבודת יד",
11
- "heroTitle": "תכשיטים שמספרים את הסיפור שלך",
12
- "heroLead": "עיצובים עדינים מזהב ומכסף, בעבודת יד ובאהבה — נוצרו ללוות אתכן ברגעים הקטנים והגדולים.",
11
+ "heroTitle": "פריטים שמספרים את הסיפור שלך",
12
+ "heroLead": "עיצובים עדינים, בעבודת יד ובאהבה — נוצרו ללוות אתכן ברגעים הקטנים והגדולים.",
13
13
  "heroCtaSecondary": "לצפייה בקטגוריות",
14
14
  "heroTrust": "לקוחות מרוצות ברחבי הארץ",
15
15
  "benefit1Title": "משלוח מהיר עד הבית",
@@ -17,37 +17,37 @@
17
17
  "benefit2Title": "תשלום מאובטח",
18
18
  "benefit2Desc": "סליקה מוצפנת בתקן המחמיר ביותר",
19
19
  "benefit3Title": "חומרים איכותיים",
20
- "benefit3Desc": "זהב, כסף 925 ואבני חן אמיתיות",
20
+ "benefit3Desc": "נבחרים בקפידה, עמידים לאורך זמן",
21
21
  "benefit4Title": "החזרה קלה",
22
22
  "benefit4Desc": "30 יום להחלפה או החזרה, בלי שאלות",
23
23
  "featuredEyebrow": "הנבחרים שלנו",
24
24
  "featuredSubtitle": "הפריטים שהלקוחות שלנו הכי אוהבות — נבחרו בקפידה מהקולקציה החדשה.",
25
25
  "categoriesEyebrow": "קטגוריות",
26
- "categoriesTitle": "מצאו את התכשיט המושלם",
27
- "categoriesSubtitle": "מטבעות עדינות ועד שרשראות בולטות — לכל אחת יש את השפה שלה.",
26
+ "categoriesTitle": "מצאו את הפריט המושלם",
27
+ "categoriesSubtitle": "מהפריטים העדינים ועד הבולטים — לכל אחת יש את השפה שלה.",
28
28
  "catRings": "טבעות",
29
29
  "catNecklaces": "שרשראות",
30
30
  "catEarrings": "עגילים",
31
31
  "catBracelets": "צמידים",
32
32
  "catCta": "לצפייה בקטגוריה",
33
33
  "storyEyebrow": "הסטודיו שלנו",
34
- "storyTitle": "נוצר ביד, נלבש בלב",
35
- "storyP1": "כל תכשיט אצלנו מתחיל בסקיצה קטנה על נייר — ונגמר בעבודת יד עדינה בסטודיו. אנחנו בוחרים כל אבן וכל חוליה בקפידה, כדי שהתכשיט שיגיע אליכן יהיה בדיוק כמו שדמיינו אותו.",
36
- "storyP2": "אנחנו מאמינים בתכשיטים שנשארים: חומרים אמיתיים, גימור מוקפד ואחריות מלאה על כל פריט. ככה נראית אהבה לפרטים הקטנים.",
34
+ "storyTitle": "נוצר ביד, נאהב בלב",
35
+ "storyP1": "כל פריט אצלנו מתחיל בסקיצה קטנה על נייר — ונגמר בעבודת יד עדינה בסטודיו. אנחנו בוחרים כל חומר וכל פרט בקפידה, כדי שהפריט שיגיע אליכן יהיה בדיוק כמו שדמיינו אותו.",
36
+ "storyP2": "אנחנו מאמינים בפריטים שנשארים: חומרים אמיתיים, גימור מוקפד ואחריות מלאה על כל פריט. ככה נראית אהבה לפרטים הקטנים.",
37
37
  "storyCta": "לגלות את הקולקציה",
38
38
  "storyStat1Value": "עבודת יד",
39
39
  "storyStat1Label": "כל פריט נוצר בסטודיו שלנו",
40
- "storyStat2Value": "כסף 925",
41
- "storyStat2Label": "חומרים אמיתיים בלבד",
40
+ "storyStat2Value": "גימור מוקפד",
41
+ "storyStat2Label": "כל פריט נבדק לפני המשלוח",
42
42
  "testimonialsEyebrow": "לקוחות מספרות",
43
43
  "testimonialsTitle": "הן כבר התאהבו",
44
- "testimonial1Quote": "השרשרת הגיעה באריזה מהממת ונראית אפילו יותר טוב מבתמונות. קיבלתי מחמאות כבר ביום הראשון.",
44
+ "testimonial1Quote": "הפריט הגיע באריזה מהממת ונראה אפילו יותר טוב מבתמונות. קיבלתי מחמאות כבר ביום הראשון.",
45
45
  "testimonial1Name": "נועה ל.",
46
46
  "testimonial1Meta": "תל אביב",
47
- "testimonial2Quote": "קניתי עגילים במתנה לאמא שלי והיא לא מורידה אותם מאז. איכות מדהימה ושירות סופר אישי.",
47
+ "testimonial2Quote": "קניתי מתנה לאמא שלי והיא לא מפסיקה להשתמש בו מאז. איכות מדהימה ושירות סופר אישי.",
48
48
  "testimonial2Name": "שירה ב.",
49
49
  "testimonial2Meta": "חיפה",
50
- "testimonial3Quote": "הטבעת בדיוק במידה, העבודה עדינה ומוקפדת. מרגישים שכל פרט נעשה באהבה. ממליצה בחום.",
50
+ "testimonial3Quote": "בדיוק כמו שתואר, העבודה עדינה ומוקפדת. מרגישים שכל פרט נעשה באהבה. ממליצה בחום.",
51
51
  "testimonial3Name": "דנה כ.",
52
52
  "testimonial3Meta": "ירושלים",
53
53
  "newsletterTitle": "הצטרפו למועדון שלנו",
@@ -68,7 +68,7 @@
68
68
  "productDetail": {
69
69
  "quantityLabel": "כמות",
70
70
  "shippingNote": "משלוח מהיר עד הבית · החזרה קלה עד 30 יום",
71
- "guaranteeNote": "אחריות מלאה על כל תכשיט",
71
+ "guaranteeNote": "אחריות מלאה על כל פריט",
72
72
  "pickOne": "בחרו אחד",
73
73
  "upTo": "עד {max}",
74
74
  "pickExactly": "בחרו בדיוק {min}",
@@ -88,7 +88,7 @@
88
88
  "shippingAtCheckout": "משלוח יחושב בקופה"
89
89
  },
90
90
  "footer": {
91
- "tagline": "תכשיטים בעבודת יד, מעוצבים באהבה בישראל.",
91
+ "tagline": "{{storeName}} עבודת יד, מעוצב באהבה בישראל.",
92
92
  "followUs": "עקבו אחרינו",
93
93
  "quickLinksTitle": "ניווט מהיר",
94
94
  "linkProducts": "כל המוצרים",
@@ -75,7 +75,7 @@ export function CartDrawer() {
75
75
  onClick={close}
76
76
  aria-hidden="true"
77
77
  className={cn(
78
- 'absolute inset-0 bg-foreground/35 backdrop-blur-[2px] transition-opacity duration-300 motion-reduce:transition-none',
78
+ 'bg-foreground/35 absolute inset-0 backdrop-blur-[2px] transition-opacity duration-300 motion-reduce:transition-none',
79
79
  open ? 'opacity-100' : 'opacity-0'
80
80
  )}
81
81
  />
@@ -86,14 +86,14 @@ export function CartDrawer() {
86
86
  aria-modal="true"
87
87
  aria-label={t('title')}
88
88
  className={cn(
89
- 'absolute inset-y-0 end-0 flex w-full max-w-md flex-col bg-background shadow-2xl transition-transform duration-300 ease-out motion-reduce:transition-none sm:rounded-s-3xl sm:border-s',
89
+ 'bg-background absolute inset-y-0 end-0 flex w-full max-w-md flex-col shadow-2xl transition-transform duration-300 ease-out motion-reduce:transition-none sm:rounded-s-3xl sm:border-s',
90
90
  open ? 'translate-x-0' : 'ltr:translate-x-full rtl:-translate-x-full'
91
91
  )}
92
92
  >
93
93
  <header className="flex items-center gap-3 border-b px-5 py-4">
94
- <h2 className="font-display text-xl font-semibold text-foreground">{t('title')}</h2>
94
+ <h2 className="font-display text-foreground text-xl font-semibold">{t('title')}</h2>
95
95
  {itemCount > 0 && (
96
- <span className="flex h-6 min-w-6 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-bold text-primary-foreground">
96
+ <span className="bg-primary text-primary-foreground flex h-6 min-w-6 items-center justify-center rounded-full px-1.5 text-xs font-bold">
97
97
  {itemCount}
98
98
  </span>
99
99
  )}
@@ -102,7 +102,7 @@ export function CartDrawer() {
102
102
  type="button"
103
103
  onClick={close}
104
104
  aria-label={tc('close')}
105
- className="ms-auto flex h-10 w-10 items-center justify-center rounded-full text-foreground transition-colors hover:bg-secondary"
105
+ className="text-foreground hover:bg-secondary ms-auto flex h-10 w-10 items-center justify-center rounded-full transition-colors"
106
106
  >
107
107
  <IconX size={20} />
108
108
  </button>
@@ -110,11 +110,11 @@ export function CartDrawer() {
110
110
 
111
111
  {items.length === 0 ? (
112
112
  <div className="flex flex-1 flex-col items-center justify-center gap-3 px-6 text-center">
113
- <span className="flex h-16 w-16 items-center justify-center rounded-full bg-secondary text-muted-foreground">
113
+ <span className="bg-secondary text-muted-foreground flex h-16 w-16 items-center justify-center rounded-full">
114
114
  <IconBag size={24} />
115
115
  </span>
116
- <p className="font-display text-lg font-semibold text-foreground">{t('emptyTitle')}</p>
117
- <p className="max-w-xs text-sm text-muted-foreground">{t('emptySubtitle')}</p>
116
+ <p className="font-display text-foreground text-lg font-semibold">{t('emptyTitle')}</p>
117
+ <p className="text-muted-foreground max-w-xs text-sm">{t('emptySubtitle')}</p>
118
118
  <Link href="/products" onClick={close} className="btn-primary btn-sm mt-2">
119
119
  {tc('continueShopping')}
120
120
  </Link>
@@ -132,25 +132,25 @@ export function CartDrawer() {
132
132
  </ul>
133
133
  </div>
134
134
 
135
- <footer className="border-t bg-secondary/40 px-5 py-4">
135
+ <footer className="bg-secondary/40 border-t px-5 py-4">
136
136
  <div className="flex items-center justify-between text-sm">
137
137
  <span className="text-muted-foreground">{tc('subtotal')}</span>
138
- <strong className="text-base text-foreground">
138
+ <strong className="text-foreground text-base">
139
139
  {formatPrice(totals.subtotal, { currency }) as string}
140
140
  </strong>
141
141
  </div>
142
142
  {totals.discount > 0 && (
143
- <div className="mt-1 flex items-center justify-between text-sm text-primary">
143
+ <div className="text-primary mt-1 flex items-center justify-between text-sm">
144
144
  <span>{tc('discount')}</span>
145
145
  <span>-{formatPrice(totals.discount, { currency }) as string}</span>
146
146
  </div>
147
147
  )}
148
- <p className="mt-1 text-xs text-muted-foreground">{t('shippingAtCheckout')}</p>
148
+ <p className="text-muted-foreground mt-1 text-xs">{t('shippingAtCheckout')}</p>
149
149
  <div className="mt-4 grid gap-2">
150
- <Link href="/checkout" onClick={close} className="btn-primary w-full">
150
+ <Link href="/checkout" onClick={close} className="btn-primary btn-lg w-full">
151
151
  {t('proceedToCheckout')}
152
152
  </Link>
153
- <Link href="/cart" onClick={close} className="btn-outline w-full">
153
+ <Link href="/cart" onClick={close} className="btn-outline btn-lg w-full">
154
154
  {t('viewCart')}
155
155
  </Link>
156
156
  </div>
@@ -1,12 +1,12 @@
1
1
  'use client';
2
2
 
3
3
  import { useTranslations } from '@/core/lib/translations';
4
- import { IconTruck, IconShield, IconGem, IconReturn } from '@/ui/shared/icons';
4
+ import { IconTruck, IconShield, IconBadgeCheck, IconReturn } from '@/ui/shared/icons';
5
5
 
6
6
  const BENEFITS = [
7
7
  { icon: IconTruck, titleKey: 'benefit1Title', descKey: 'benefit1Desc' },
8
8
  { icon: IconShield, titleKey: 'benefit2Title', descKey: 'benefit2Desc' },
9
- { icon: IconGem, titleKey: 'benefit3Title', descKey: 'benefit3Desc' },
9
+ { icon: IconBadgeCheck, titleKey: 'benefit3Title', descKey: 'benefit3Desc' },
10
10
  { icon: IconReturn, titleKey: 'benefit4Title', descKey: 'benefit4Desc' },
11
11
  ] as const;
12
12
 
@@ -19,12 +19,12 @@ export function BenefitsBand() {
19
19
  <ul className="container-page grid grid-cols-2 gap-x-6 gap-y-8 pb-10 pt-16 lg:grid-cols-4">
20
20
  {BENEFITS.map(({ icon: Icon, titleKey, descKey }) => (
21
21
  <li key={titleKey} className="flex items-start gap-3.5">
22
- <span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-secondary text-primary">
22
+ <span className="bg-secondary text-primary flex h-11 w-11 shrink-0 items-center justify-center rounded-full">
23
23
  <Icon size={24} />
24
24
  </span>
25
25
  <div>
26
- <p className="text-sm font-semibold text-foreground">{t(titleKey)}</p>
27
- <p className="mt-0.5 text-[13px] leading-5 text-muted-foreground">{t(descKey)}</p>
26
+ <p className="text-foreground text-sm font-semibold">{t(titleKey)}</p>
27
+ <p className="text-muted-foreground mt-0.5 text-[13px] leading-5">{t(descKey)}</p>
28
28
  </div>
29
29
  </li>
30
30
  ))}
@@ -35,10 +35,10 @@ export function CategoryTiles({ products }: { products: Product[] }) {
35
35
  const tiles: Tile[] = [];
36
36
 
37
37
  // Prefer real category data when the catalog has it.
38
- const byCategory = new Map<string, { name: string; items: Product[] }>();
38
+ const byCategory = new Map<string, { name: string; slug: string | null; items: Product[] }>();
39
39
  for (const p of products) {
40
40
  for (const cat of p.categories ?? []) {
41
- const entry = byCategory.get(cat.id) ?? { name: cat.name, items: [] };
41
+ const entry = byCategory.get(cat.id) ?? { name: cat.name, slug: cat.slug ?? null, items: [] };
42
42
  entry.items.push(p);
43
43
  byCategory.set(cat.id, entry);
44
44
  }
@@ -50,15 +50,21 @@ export function CategoryTiles({ products }: { products: Product[] }) {
50
50
  [...items].reverse().find((p) => p.images?.[0]?.url)?.images?.[0];
51
51
 
52
52
  if (byCategory.size >= 2) {
53
- for (const [id, { name, items }] of Array.from(byCategory.entries()).slice(0, 4)) {
53
+ for (const [id, { name, slug, items }] of Array.from(byCategory.entries()).slice(0, 4)) {
54
54
  const img = pickImage(items);
55
55
  if (!img?.url) continue;
56
+ // Prefer the real category landing page (SEO-indexed, has its own
57
+ // written description) — this is the only place in the storefront that
58
+ // links to it, so without a real slug it'd be an orphaned page. Fall
59
+ // back to the filtered grid for categories with no slug yet.
56
60
  tiles.push({
57
61
  key: id,
58
62
  label: name,
59
63
  imageUrl: img.url,
60
64
  imageAlt: img.alt || name,
61
- href: `/products?category=${encodeURIComponent(id)}`,
65
+ href: slug
66
+ ? `/category/${encodeURIComponent(slug)}`
67
+ : `/products?category=${encodeURIComponent(id)}`,
62
68
  count: items.length,
63
69
  });
64
70
  }
@@ -90,7 +96,7 @@ export function CategoryTiles({ products }: { products: Product[] }) {
90
96
  <div className="mb-8 max-w-xl lg:mb-10">
91
97
  <span className="eyebrow">{t('categoriesEyebrow')}</span>
92
98
  <h2 className="text-4xl sm:text-5xl">{t('categoriesTitle')}</h2>
93
- <p className="mt-3 text-muted-foreground">{t('categoriesSubtitle')}</p>
99
+ <p className="text-muted-foreground mt-3">{t('categoriesSubtitle')}</p>
94
100
  </div>
95
101
 
96
102
  <ul
@@ -109,11 +115,8 @@ export function CategoryTiles({ products }: { products: Product[] }) {
109
115
  : undefined
110
116
  }
111
117
  >
112
- <Link
113
- href={tile.href}
114
- className="group card card-hover block overflow-hidden"
115
- >
116
- <span className="relative block aspect-square overflow-hidden bg-secondary">
118
+ <Link href={tile.href} className="card card-hover group block overflow-hidden">
119
+ <span className="bg-secondary relative block aspect-square overflow-hidden">
117
120
  <Image
118
121
  src={tile.imageUrl}
119
122
  alt={tile.imageAlt}
@@ -124,16 +127,16 @@ export function CategoryTiles({ products }: { products: Product[] }) {
124
127
  </span>
125
128
  <span className="flex items-center justify-between gap-2 px-4 py-3.5">
126
129
  <span>
127
- <span className="block text-[15px] font-semibold text-foreground">
130
+ <span className="text-foreground block text-[15px] font-semibold">
128
131
  {tile.label}
129
132
  </span>
130
- <span className="block text-xs text-muted-foreground">
133
+ <span className="text-muted-foreground block text-xs">
131
134
  {tile.count} {tile.count === 1 ? tc('product') : tc('products')}
132
135
  </span>
133
136
  </span>
134
137
  <span
135
138
  aria-hidden="true"
136
- className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-secondary text-foreground transition-colors group-hover:bg-primary group-hover:text-primary-foreground"
139
+ className="bg-secondary text-foreground group-hover:bg-primary group-hover:text-primary-foreground flex h-8 w-8 shrink-0 items-center justify-center rounded-full transition-colors"
137
140
  >
138
141
  <IconArrowEnd size={16} className="rtl-flip" />
139
142
  </span>
@@ -22,7 +22,7 @@ import {
22
22
  IconMinus,
23
23
  IconPlus,
24
24
  IconTruck,
25
- IconGem,
25
+ IconBadgeCheck,
26
26
  IconDownload,
27
27
  } from '@/ui/shared/icons';
28
28
 
@@ -159,7 +159,10 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
159
159
  {/* Image Gallery */}
160
160
  <figure className="lg:sticky lg:top-24 lg:self-start">
161
161
  {/* `relative` + `aspect-square` are layout-critical for next/image fill */}
162
- <div data-pdp-gallery className="relative aspect-square overflow-hidden rounded-3xl border bg-secondary shadow-[0_28px_60px_-30px_hsl(var(--primary)/0.3)]">
162
+ <div
163
+ data-pdp-gallery
164
+ className="bg-secondary relative aspect-square overflow-hidden rounded-3xl border shadow-[0_28px_60px_-30px_hsl(var(--primary)/0.3)]"
165
+ >
163
166
  {mainImageUrl ? (
164
167
  <Image
165
168
  src={mainImageUrl}
@@ -184,10 +187,10 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
184
187
  onClick={() => setSelectedImageIndex(idx)}
185
188
  aria-pressed={selectedImageIndex === idx}
186
189
  className={cn(
187
- 'relative h-[4.5rem] w-[4.5rem] overflow-hidden rounded-lg border-2 bg-secondary transition-colors',
190
+ 'bg-secondary relative h-[4.5rem] w-[4.5rem] overflow-hidden rounded-lg border-2 transition-colors',
188
191
  selectedImageIndex === idx
189
192
  ? 'border-primary'
190
- : 'border-transparent hover:border-border'
193
+ : 'hover:border-border border-transparent'
191
194
  )}
192
195
  >
193
196
  <Image
@@ -220,9 +223,9 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
220
223
  {/* Brand */}
221
224
  {(product as { brands?: Array<{ id: string; name: string }> }).brands &&
222
225
  (product as { brands: Array<{ id: string; name: string }> }).brands.length > 0 && (
223
- <p className="mb-1 text-sm text-muted-foreground">
226
+ <p className="text-muted-foreground mb-1 text-sm">
224
227
  {t('by')}{' '}
225
- <span className="font-medium text-foreground">
228
+ <span className="text-foreground font-medium">
226
229
  {(product as { brands: Array<{ id: string; name: string }> }).brands
227
230
  .map((b) => b.name)
228
231
  .join(', ')}
@@ -237,7 +240,7 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
237
240
  {(product as unknown as { tags?: Array<{ id: string; name: string }> }).tags &&
238
241
  (product as unknown as { tags: Array<{ id: string; name: string }> }).tags.length >
239
242
  0 && (
240
- <ul className="mt-2 flex flex-wrap gap-1.5 text-xs text-muted-foreground">
243
+ <ul className="text-muted-foreground mt-2 flex flex-wrap gap-1.5 text-xs">
241
244
  {(product as unknown as { tags: Array<{ id: string; name: string }> }).tags.map(
242
245
  (tag) => (
243
246
  <li key={tag.id}>#{tag.name}</li>
@@ -265,11 +268,11 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
265
268
 
266
269
  {/* Downloadable files info */}
267
270
  {product.isDownloadable && product.downloads && product.downloads.length > 0 && (
268
- <section className="mt-6 rounded-xl border bg-secondary/60 p-4">
271
+ <section className="bg-secondary/60 mt-6 rounded-xl border p-4">
269
272
  <h2 className="text-sm font-semibold">
270
273
  {t('filesIncluded').replace('{count}', String(product.downloads.length))}
271
274
  </h2>
272
- <ul className="mt-2 space-y-1.5 text-sm text-muted-foreground">
275
+ <ul className="text-muted-foreground mt-2 space-y-1.5 text-sm">
273
276
  {product.downloads.map((file: DownloadFile) => (
274
277
  <li key={file.id} className="flex items-center gap-2">
275
278
  <IconDownload size={16} />
@@ -322,7 +325,7 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
322
325
  />
323
326
  ))}
324
327
  {modifierError && (
325
- <p role="alert" className="text-sm font-medium text-destructive">
328
+ <p role="alert" className="text-destructive text-sm font-medium">
326
329
  {modifierError}
327
330
  </p>
328
331
  )}
@@ -340,7 +343,7 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
340
343
  type="button"
341
344
  onClick={() => setQuantity((q) => Math.max(1, q - 1))}
342
345
  aria-label={t('decreaseQuantity')}
343
- className="flex h-full w-11 items-center justify-center rounded-s-full text-foreground transition-colors hover:bg-secondary disabled:opacity-40"
346
+ className="text-foreground hover:bg-secondary flex h-full w-11 items-center justify-center rounded-s-full transition-colors disabled:opacity-40"
344
347
  disabled={quantity <= 1}
345
348
  >
346
349
  <IconMinus size={16} />
@@ -352,7 +355,7 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
352
355
  type="button"
353
356
  onClick={() => setQuantity((q) => q + 1)}
354
357
  aria-label={t('increaseQuantity')}
355
- className="flex h-full w-11 items-center justify-center rounded-e-full text-foreground transition-colors hover:bg-secondary"
358
+ className="text-foreground hover:bg-secondary flex h-full w-11 items-center justify-center rounded-e-full transition-colors"
356
359
  >
357
360
  <IconPlus size={16} />
358
361
  </button>
@@ -360,7 +363,10 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
360
363
 
361
364
  <button
362
365
  type="button"
363
- onClick={() => { flyToCart(firstVisible('[data-pdp-gallery]')); handleAddToCart(); }}
366
+ onClick={() => {
367
+ flyToCart(firstVisible('[data-pdp-gallery]'));
368
+ handleAddToCart();
369
+ }}
364
370
  disabled={!canPurchase || addingToCart}
365
371
  className={cn(
366
372
  'btn-primary btn-lg flex-1 sm:min-w-64 sm:flex-none',
@@ -379,7 +385,7 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
379
385
  </div>
380
386
 
381
387
  {/* Reassurance row */}
382
- <ul className="mt-6 space-y-2 border-t pt-5 text-sm text-muted-foreground">
388
+ <ul className="text-muted-foreground mt-6 space-y-2 border-t pt-5 text-sm">
383
389
  <li className="flex items-center gap-2.5">
384
390
  <span className="text-primary">
385
391
  <IconTruck size={20} />
@@ -388,7 +394,7 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
388
394
  </li>
389
395
  <li className="flex items-center gap-2.5">
390
396
  <span className="text-primary">
391
- <IconGem size={20} />
397
+ <IconBadgeCheck size={20} />
392
398
  </span>
393
399
  {t('guaranteeNote')}
394
400
  </li>
@@ -396,7 +402,7 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
396
402
 
397
403
  {/* Download after purchase note */}
398
404
  {product.isDownloadable && (
399
- <p className="mt-4 text-sm text-muted-foreground">{t('downloadAfterPurchase')}</p>
405
+ <p className="text-muted-foreground mt-4 text-sm">{t('downloadAfterPurchase')}</p>
400
406
  )}
401
407
 
402
408
  {/* Description */}
@@ -424,7 +430,7 @@ export function ProductClientSection({ product: initialProduct }: ProductClientS
424
430
  <tr key={field.id} className="odd:bg-secondary/50">
425
431
  <th
426
432
  scope="row"
427
- className="w-1/3 px-4 py-3 text-start font-medium text-muted-foreground"
433
+ className="text-muted-foreground w-1/3 px-4 py-3 text-start font-medium"
428
434
  >
429
435
  {field.definitionName}
430
436
  </th>
@@ -5,7 +5,7 @@ import { Link } from '@/core/lib/navigation';
5
5
  import { useTranslations } from '@/core/lib/translations';
6
6
  import { ProductCard } from '@/ui/product/product-card';
7
7
  import { cn } from '@/core/lib/utils';
8
- import { IconGem } from '@/ui/shared/icons';
8
+ import { IconSparkle } from '@/ui/shared/icons';
9
9
 
10
10
  interface ProductGridProps {
11
11
  products: Product[];
@@ -20,11 +20,11 @@ export function ProductGrid({ products, className }: ProductGridProps) {
20
20
  if (products.length === 0) {
21
21
  return (
22
22
  <div className="card mx-auto flex max-w-md flex-col items-center gap-3 px-8 py-14 text-center">
23
- <span className="flex h-14 w-14 items-center justify-center rounded-full bg-secondary text-primary">
24
- <IconGem size={28} />
23
+ <span className="bg-secondary text-primary flex h-14 w-14 items-center justify-center rounded-full">
24
+ <IconSparkle size={28} />
25
25
  </span>
26
- <p className="font-display text-xl text-foreground">{t('emptyTitle')}</p>
27
- <p className="text-sm text-muted-foreground">{t('emptyBody')}</p>
26
+ <p className="font-display text-foreground text-xl">{t('emptyTitle')}</p>
27
+ <p className="text-muted-foreground text-sm">{t('emptyBody')}</p>
28
28
  <Link href="/products" className="btn-soft btn-sm mt-2">
29
29
  {tc('viewAll')}
30
30
  </Link>
@@ -33,12 +33,7 @@ export function ProductGrid({ products, className }: ProductGridProps) {
33
33
  }
34
34
 
35
35
  return (
36
- <div
37
- className={cn(
38
- 'grid grid-cols-2 gap-3 sm:gap-5 lg:grid-cols-4 lg:gap-6',
39
- className
40
- )}
41
- >
36
+ <div className={cn('grid grid-cols-2 gap-3 sm:gap-5 lg:grid-cols-4 lg:gap-6', className)}>
42
37
  {products.map((product) => (
43
38
  <ProductCard key={product.id} product={product} />
44
39
  ))}