create-brainerce-store 1.51.0 → 1.52.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.
Files changed (170) hide show
  1. package/README.md +72 -0
  2. package/dist/index.js +9 -68
  3. package/messages/en.json +32 -0
  4. package/messages/he.json +32 -0
  5. package/package.json +1 -1
  6. package/templates/nextjs/base/.claude/commands/design.md +9 -0
  7. package/templates/nextjs/base/AI-GUIDE.md +52 -3
  8. package/templates/nextjs/base/CLAUDE.md +10 -0
  9. package/templates/nextjs/base/package.json.ejs +52 -39
  10. package/templates/nextjs/base/src/app/globals.css +36 -30
  11. package/templates/nextjs/base/src/components/checkout/payment-step.tsx +14 -10
  12. package/templates/nextjs/base/src/components/ui/accordion.tsx +53 -0
  13. package/templates/nextjs/base/src/components/ui/badge.tsx +32 -0
  14. package/templates/nextjs/base/src/components/ui/button.tsx +49 -0
  15. package/templates/nextjs/base/src/components/ui/card.tsx +55 -0
  16. package/templates/nextjs/base/src/components/ui/checkbox.tsx +28 -0
  17. package/templates/nextjs/base/src/components/ui/dialog.tsx +107 -0
  18. package/templates/nextjs/base/src/components/ui/input.tsx +22 -0
  19. package/templates/nextjs/base/src/components/ui/label.tsx +21 -0
  20. package/templates/nextjs/base/src/components/ui/radio-group.tsx +38 -0
  21. package/templates/nextjs/base/src/components/ui/select.tsx +153 -0
  22. package/templates/nextjs/base/src/components/ui/separator.tsx +26 -0
  23. package/templates/nextjs/base/src/components/ui/sheet.tsx +125 -0
  24. package/templates/nextjs/base/src/components/ui/skeleton.tsx +7 -0
  25. package/templates/nextjs/base/src/components/ui/tabs.tsx +55 -0
  26. package/templates/nextjs/base/src/components/ui/textarea.tsx +21 -0
  27. package/templates/nextjs/base/src/components/ui/tooltip.tsx +32 -0
  28. package/templates/nextjs/base/src/ui/cart/cart-bundle-offer.tsx +17 -11
  29. package/templates/nextjs/base/src/ui/cart/cart-item.tsx +2 -8
  30. package/templates/nextjs/base/src/ui/cart/cart-nudges.tsx +28 -39
  31. package/templates/nextjs/base/src/ui/cart/cart-summary.tsx +3 -1
  32. package/templates/nextjs/base/src/ui/cart/cart-upgrade-banner.tsx +8 -23
  33. package/templates/nextjs/base/src/ui/cart/cart-view.tsx +18 -27
  34. package/templates/nextjs/base/src/ui/cart/coupon-input.tsx +10 -21
  35. package/templates/nextjs/base/src/ui/cart/free-shipping-bar.tsx +2 -3
  36. package/templates/nextjs/base/src/ui/cart/reservation-countdown.tsx +95 -103
  37. package/templates/nextjs/base/src/ui/home/hero-section.tsx +4 -6
  38. package/templates/nextjs/base/src/ui/layout/announcement-bar.tsx.ejs +116 -125
  39. package/templates/nextjs/base/src/ui/layout/faq-section.tsx.ejs +90 -103
  40. package/templates/nextjs/base/src/ui/layout/header-account.tsx +2 -17
  41. package/templates/nextjs/base/src/ui/layout/language-switcher.tsx.ejs +80 -70
  42. package/templates/nextjs/base/src/ui/layout/site-footer.tsx.ejs +144 -164
  43. package/templates/nextjs/base/src/ui/layout/site-header.tsx.ejs +152 -182
  44. package/templates/nextjs/base/src/ui/product/customization-fields.tsx +456 -444
  45. package/templates/nextjs/base/src/ui/product/discount-badge.tsx +18 -22
  46. package/templates/nextjs/base/src/ui/product/frequently-bought-together.tsx +15 -27
  47. package/templates/nextjs/base/src/ui/product/modifier-group-selector.tsx +206 -175
  48. package/templates/nextjs/base/src/ui/product/price-display.tsx +3 -2
  49. package/templates/nextjs/base/src/ui/product/product-card.tsx +238 -271
  50. package/templates/nextjs/base/src/ui/product/product-client-section.tsx +455 -470
  51. package/templates/nextjs/base/src/ui/product/product-listing.tsx +96 -79
  52. package/templates/nextjs/base/src/ui/product/recommendation-section.tsx +102 -107
  53. package/templates/nextjs/base/src/ui/product/review-form.tsx +44 -43
  54. package/templates/nextjs/{designs/passepartout → base/src}/ui/product/reviews-section.tsx.ejs +108 -96
  55. package/templates/nextjs/base/src/ui/product/stock-badge.tsx +61 -63
  56. package/templates/nextjs/base/tailwind.config.ts +69 -42
  57. package/templates/nextjs/designs/{passepartout → atelier}/app-overlay/layout.tsx.ejs +23 -25
  58. package/templates/nextjs/designs/atelier/globals.css +396 -0
  59. package/templates/nextjs/designs/atelier/messages-patch/en.json +99 -0
  60. package/templates/nextjs/designs/atelier/messages-patch/he.json +99 -0
  61. package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/cart-bundle-offer.tsx +110 -111
  62. package/templates/nextjs/designs/atelier/ui/cart/cart-drawer.tsx +163 -0
  63. package/templates/nextjs/designs/atelier/ui/cart/cart-item.tsx +140 -0
  64. package/templates/nextjs/designs/atelier/ui/cart/cart-nudges.tsx +34 -0
  65. package/templates/nextjs/designs/atelier/ui/cart/cart-summary.tsx +107 -0
  66. package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/cart-upgrade-banner.tsx +123 -141
  67. package/templates/nextjs/designs/atelier/ui/cart/cart-view.tsx +147 -0
  68. package/templates/nextjs/designs/atelier/ui/cart/coupon-input.tsx +142 -0
  69. package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/free-shipping-bar.tsx +77 -62
  70. package/templates/nextjs/designs/{passepartout → atelier}/ui/cart/reservation-countdown.tsx +89 -103
  71. package/templates/nextjs/designs/atelier/ui/home/benefits-band.tsx +34 -0
  72. package/templates/nextjs/designs/atelier/ui/home/category-tiles.tsx +148 -0
  73. package/templates/nextjs/designs/atelier/ui/home/discount-banner-strip.tsx +27 -0
  74. package/templates/nextjs/designs/atelier/ui/home/editorial-band.tsx +73 -0
  75. package/templates/nextjs/designs/atelier/ui/home/featured-products-section.tsx +36 -0
  76. package/templates/nextjs/designs/atelier/ui/home/hero-section.tsx +148 -0
  77. package/templates/nextjs/designs/atelier/ui/home/home-client.tsx +54 -0
  78. package/templates/nextjs/designs/atelier/ui/home/newsletter-section.tsx +78 -0
  79. package/templates/nextjs/designs/atelier/ui/home/testimonials-band.tsx +46 -0
  80. package/templates/nextjs/designs/atelier/ui/layout/announcement-bar.tsx +118 -0
  81. package/templates/nextjs/designs/atelier/ui/layout/faq-section.tsx +96 -0
  82. package/templates/nextjs/designs/atelier/ui/layout/header-account.tsx +36 -0
  83. package/templates/nextjs/designs/atelier/ui/layout/header-cart.tsx +36 -0
  84. package/templates/nextjs/designs/atelier/ui/layout/language-switcher.tsx.ejs +137 -0
  85. package/templates/nextjs/designs/atelier/ui/layout/nav-url.ts +14 -0
  86. package/templates/nextjs/designs/atelier/ui/layout/rich-text-block.tsx +34 -0
  87. package/templates/nextjs/designs/atelier/ui/layout/site-footer.tsx.ejs +141 -0
  88. package/templates/nextjs/designs/atelier/ui/layout/site-header.tsx.ejs +138 -0
  89. package/templates/nextjs/designs/atelier/ui/product/customization-fields.tsx +466 -0
  90. package/templates/nextjs/designs/{passepartout → atelier}/ui/product/discount-badge.tsx +17 -22
  91. package/templates/nextjs/designs/{passepartout → atelier}/ui/product/frequently-bought-together.tsx +196 -205
  92. package/templates/nextjs/designs/atelier/ui/product/modifier-group-selector.tsx +193 -0
  93. package/templates/nextjs/designs/atelier/ui/product/price-display.tsx +69 -0
  94. package/templates/nextjs/designs/atelier/ui/product/product-card.tsx +260 -0
  95. package/templates/nextjs/designs/atelier/ui/product/product-client-section.tsx +471 -0
  96. package/templates/nextjs/designs/atelier/ui/product/product-grid.tsx +47 -0
  97. package/templates/nextjs/designs/atelier/ui/product/product-listing.tsx +260 -0
  98. package/templates/nextjs/designs/atelier/ui/product/recommendation-section.tsx +95 -0
  99. package/templates/nextjs/designs/{passepartout → atelier}/ui/product/review-form.tsx +267 -294
  100. package/templates/nextjs/designs/atelier/ui/product/reviews-section.tsx.ejs +148 -0
  101. package/templates/nextjs/designs/atelier/ui/product/stock-badge.tsx +66 -0
  102. package/templates/nextjs/designs/atelier/ui/product/variant-selector.tsx +206 -0
  103. package/templates/nextjs/designs/atelier/ui/shared/cdn-image.tsx +29 -0
  104. package/templates/nextjs/designs/{passepartout → atelier}/ui/shared/fly-to-cart.ts +86 -85
  105. package/templates/nextjs/designs/atelier/ui/shared/icons.tsx +269 -0
  106. package/templates/nextjs/designs/atelier/ui/shared/loading-spinner.tsx +64 -0
  107. package/templates/nextjs/designs/atelier/ui/shared/reveal.tsx +71 -0
  108. package/templates/nextjs/designs/atelier/ui/shared/select-menu.tsx +125 -0
  109. package/templates/nextjs/themes/luxury/globals.css +405 -399
  110. package/templates/nextjs/themes/minimal/globals.css +36 -30
  111. package/templates/nextjs/themes/playful/globals.css +406 -400
  112. package/templates/nextjs/ui-canvas/cart/cart-bundle-offer.tsx +2 -0
  113. package/templates/nextjs/ui-canvas/cart/cart-item.tsx +2 -0
  114. package/templates/nextjs/ui-canvas/cart/cart-nudges.tsx +2 -0
  115. package/templates/nextjs/ui-canvas/cart/cart-summary.tsx +2 -0
  116. package/templates/nextjs/ui-canvas/cart/cart-upgrade-banner.tsx +2 -0
  117. package/templates/nextjs/ui-canvas/cart/cart-view.tsx +2 -2
  118. package/templates/nextjs/ui-canvas/cart/coupon-input.tsx +2 -0
  119. package/templates/nextjs/ui-canvas/cart/free-shipping-bar.tsx +2 -0
  120. package/templates/nextjs/ui-canvas/cart/reservation-countdown.tsx +2 -0
  121. package/templates/nextjs/ui-canvas/home/discount-banner-strip.tsx +1 -1
  122. package/templates/nextjs/ui-canvas/home/featured-products-section.tsx +1 -1
  123. package/templates/nextjs/ui-canvas/home/hero-section.tsx +1 -1
  124. package/templates/nextjs/ui-canvas/home/home-client.tsx +1 -1
  125. package/templates/nextjs/ui-canvas/layout/announcement-bar.tsx.ejs +1 -1
  126. package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +2 -2
  127. package/templates/nextjs/ui-canvas/layout/header-account.tsx +1 -1
  128. package/templates/nextjs/ui-canvas/layout/language-switcher.tsx.ejs +2 -0
  129. package/templates/nextjs/ui-canvas/layout/rich-text-block.tsx.ejs +2 -0
  130. package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +2 -2
  131. package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +2 -2
  132. package/templates/nextjs/ui-canvas/product/customization-fields.tsx +2 -0
  133. package/templates/nextjs/ui-canvas/product/discount-badge.tsx +2 -0
  134. package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +2 -0
  135. package/templates/nextjs/ui-canvas/product/modifier-group-selector.tsx +2 -0
  136. package/templates/nextjs/ui-canvas/product/price-display.tsx +2 -0
  137. package/templates/nextjs/ui-canvas/product/product-card.tsx +1 -1
  138. package/templates/nextjs/ui-canvas/product/product-client-section.tsx +1 -1
  139. package/templates/nextjs/ui-canvas/product/product-grid.tsx +1 -1
  140. package/templates/nextjs/ui-canvas/product/product-listing.tsx +1 -1
  141. package/templates/nextjs/ui-canvas/product/recommendation-section.tsx +4 -0
  142. package/templates/nextjs/ui-canvas/product/review-form.tsx +29 -32
  143. package/templates/nextjs/ui-canvas/product/{reviews-section.tsx → reviews-section.tsx.ejs} +26 -8
  144. package/templates/nextjs/ui-canvas/product/stock-badge.tsx +2 -0
  145. package/templates/nextjs/ui-canvas/product/variant-selector.tsx +2 -0
  146. package/templates/nextjs/ui-canvas/shared/cdn-image.tsx +2 -0
  147. package/templates/nextjs/ui-canvas/shared/loading-spinner.tsx +2 -0
  148. package/templates/nextjs/base/src/ui/product/reviews-section.tsx +0 -85
  149. package/templates/nextjs/designs/passepartout/globals.css +0 -412
  150. package/templates/nextjs/designs/passepartout/messages-patch/en.json +0 -87
  151. package/templates/nextjs/designs/passepartout/messages-patch/he.json +0 -87
  152. package/templates/nextjs/designs/passepartout/ui/cart/cart-nudges.tsx +0 -39
  153. package/templates/nextjs/designs/passepartout/ui/cart/cart-view.tsx +0 -149
  154. package/templates/nextjs/designs/passepartout/ui/home/home-client.tsx +0 -401
  155. package/templates/nextjs/designs/passepartout/ui/layout/announcement-bar.tsx +0 -139
  156. package/templates/nextjs/designs/passepartout/ui/layout/cart-link.tsx +0 -45
  157. package/templates/nextjs/designs/passepartout/ui/layout/faq-section.tsx +0 -103
  158. package/templates/nextjs/designs/passepartout/ui/layout/site-footer.tsx +0 -159
  159. package/templates/nextjs/designs/passepartout/ui/layout/site-header.tsx.ejs +0 -155
  160. package/templates/nextjs/designs/passepartout/ui/layout/trust-icons.tsx +0 -60
  161. package/templates/nextjs/designs/passepartout/ui/layout/whatsapp-button.tsx +0 -34
  162. package/templates/nextjs/designs/passepartout/ui/product/price-display.tsx +0 -111
  163. package/templates/nextjs/designs/passepartout/ui/product/product-card.tsx +0 -265
  164. package/templates/nextjs/designs/passepartout/ui/product/product-client-section.tsx +0 -667
  165. package/templates/nextjs/designs/passepartout/ui/product/product-grid.tsx +0 -35
  166. package/templates/nextjs/designs/passepartout/ui/product/product-listing.tsx +0 -427
  167. package/templates/nextjs/designs/passepartout/ui/product/recommendation-section.tsx +0 -113
  168. package/templates/nextjs/designs/passepartout/ui/product/stock-badge.tsx +0 -52
  169. package/templates/nextjs/designs/passepartout/ui/product/variant-selector.tsx +0 -307
  170. package/templates/nextjs/designs/passepartout/ui/shared/loading-spinner.tsx +0 -33
@@ -23,6 +23,8 @@ function isOptimizedHost(src: ImageProps['src']): boolean {
23
23
  *
24
24
  * DESIGN ME — shared image wrapper; render ALL catalog images through this
25
25
  * component (not raw next/image) so unknown hosts never crash the page.
26
+ *
27
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
26
28
  */
27
29
  export function CdnImage({ unoptimized, ...props }: ImageProps) {
28
30
  return <Image {...props} unoptimized={unoptimized ?? !isOptimizedHost(props.src)} />;
@@ -13,6 +13,8 @@ interface LoadingSpinnerProps {
13
13
  * text placeholder; replace with your own spinner/skeleton. Keep the
14
14
  * `role="status"` + sr-only label, and keep the `size` prop (callers across
15
15
  * the app pass sm/md/lg).
16
+ *
17
+ * Building blocks: shadcn/ui primitives in src/components/ui (Button, Card, Badge, Input, Select, Accordion, Dialog, Skeleton, ...) + lucide-react icons are installed and ready to compose with.
16
18
  */
17
19
  export function LoadingSpinner({ size = 'md', className }: LoadingSpinnerProps) {
18
20
  const t = useTranslations('common');
@@ -1,85 +0,0 @@
1
- import type { ProductReview } from 'brainerce';
2
- import { getServerClient } from '@/core/lib/brainerce';
3
- import { ReviewForm } from './review-form';
4
-
5
- interface ReviewsSectionProps {
6
- productId: string;
7
- initialReviews?: ProductReview[];
8
- /** Where to send users back to after submit (defaults to the current product URL). */
9
- returnUrl?: string;
10
- }
11
-
12
- /**
13
- * Renders the visible reviews for a product plus a submit form.
14
- *
15
- * Server component — fetches with the SDK on the server so reviews are in
16
- * the initial HTML payload (good for SEO; JSON-LD on the same page already
17
- * carries `aggregateRating` when reviewCount > 0).
18
- */
19
- export async function ReviewsSection({ productId, initialReviews }: ReviewsSectionProps) {
20
- let reviews: ProductReview[] = initialReviews ?? [];
21
-
22
- if (!initialReviews) {
23
- try {
24
- const result = await getServerClient().listProductReviews(productId, { page: 1, limit: 20 });
25
- reviews = result.data;
26
- } catch {
27
- // Reviews disabled at store level or network error — render the form anyway.
28
- reviews = [];
29
- }
30
- }
31
-
32
- return (
33
- <section className="mt-8" aria-labelledby="reviews-heading">
34
- <h2 id="reviews-heading" className="mb-4 text-xl font-semibold">
35
- Reviews
36
- </h2>
37
-
38
- {reviews.length === 0 ? (
39
- <p className="text-muted-foreground mb-6 text-sm">
40
- No reviews yet — be the first to share your experience.
41
- </p>
42
- ) : (
43
- <ul className="mb-6 space-y-4">
44
- {reviews.map((review) => (
45
- <ReviewCard key={review.id} review={review} />
46
- ))}
47
- </ul>
48
- )}
49
-
50
- <ReviewForm productId={productId} />
51
- </section>
52
- );
53
- }
54
-
55
- function ReviewCard({ review }: { review: ProductReview }) {
56
- return (
57
- <li className="rounded-md border p-4">
58
- <header className="flex flex-wrap items-center gap-2">
59
- <Stars rating={review.rating} />
60
- <span className="text-sm font-medium">{review.authorName}</span>
61
- {review.verifiedPurchase && (
62
- <span className="rounded bg-emerald-50 px-2 py-0.5 text-xs text-emerald-700">
63
- Verified purchase
64
- </span>
65
- )}
66
- <time className="text-muted-foreground ms-auto text-xs">
67
- {new Date(review.createdAt).toLocaleDateString()}
68
- </time>
69
- </header>
70
- {review.body && <p className="mt-2 whitespace-pre-line break-words text-sm">{review.body}</p>}
71
- </li>
72
- );
73
- }
74
-
75
- function Stars({ rating }: { rating: number }) {
76
- return (
77
- <span aria-label={`${rating} of 5 stars`} className="inline-flex">
78
- {[1, 2, 3, 4, 5].map((n) => (
79
- <span key={n} className={n <= rating ? 'text-yellow-500' : 'text-gray-300'}>
80
-
81
- </span>
82
- ))}
83
- </span>
84
- );
85
- }
@@ -1,412 +0,0 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
-
5
- /* ─────────────────────────────────────────────────────────────
6
- פספרטו · Passe-Partout — a warm gallery hang
7
- Two whites do all the work: the plaster wall (--background)
8
- and the paper mat (--surface), separated by a 1px hairline.
9
- Brass never appears larger than a pin.
10
- ───────────────────────────────────────────────────────────── */
11
-
12
- @layer base {
13
- :root {
14
- --background: 40 33% 97%; /* הקיר — warm plaster wall */
15
- --foreground: 30 8% 16%; /* דיו — warm ink, never pure black */
16
- --primary: 30 8% 16%; /* ink-filled buttons — gallery signage */
17
- --primary-foreground: 40 33% 97%;
18
- --secondary: 38 25% 94%;
19
- --secondary-foreground: 30 8% 16%;
20
- --muted: 38 25% 94%; /* plinth — placeholder fields, steppers */
21
- --muted-foreground: 30 6% 45%; /* כיתוב — placard gray */
22
- --accent: 38 32% 52%; /* פליז — brass, pin-scale only */
23
- --accent-foreground: 40 33% 97%;
24
- --destructive: 10 42% 44%; /* טרקוטה — sale text, never a red box */
25
- --destructive-foreground: 0 0% 100%;
26
- --border: 38 18% 88%; /* the hairline — always 1px */
27
- --radius: 0rem; /* frames are not rounded */
28
-
29
- --surface: 0 0% 100%; /* הפספרטו — paper mat behind every photo */
30
- --plinth: 38 25% 94%;
31
- }
32
-
33
- * {
34
- @apply border-border;
35
- }
36
-
37
- body {
38
- @apply bg-background text-foreground antialiased;
39
- font-size: 0.9375rem;
40
- line-height: 1.7;
41
- }
42
-
43
- /* Titles — Hebrew print serif. No uppercase, no tracking:
44
- Hebrew has no case and tracking distorts the serif. */
45
- h1,
46
- h2,
47
- h3,
48
- h4 {
49
- font-family: var(--font-serif), 'Frank Ruhl Libre', serif;
50
- font-weight: 400;
51
- line-height: 1.25;
52
- letter-spacing: 0;
53
- }
54
-
55
- h1 {
56
- font-weight: 300;
57
- line-height: 1.2;
58
- }
59
-
60
- /* The right-origin underline — every text link draws its 1px
61
- ink underline from the right, the RTL reading direction. */
62
- main a:not([class*='bg-']):not([class*='group']) {
63
- position: relative;
64
- transition: color 0.25s cubic-bezier(0.22, 1, 0.36, 1);
65
- }
66
-
67
- main a:not([class*='bg-']):not([class*='group'])::after {
68
- content: '';
69
- position: absolute;
70
- bottom: -1px;
71
- inset-inline: 0;
72
- height: 1px;
73
- background: hsl(var(--foreground));
74
- transform: scaleX(0);
75
- transform-origin: right;
76
- transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1);
77
- }
78
-
79
- main a:not([class*='bg-']):not([class*='group']):hover::after {
80
- transform: scaleX(1);
81
- }
82
-
83
- ::selection {
84
- background: hsl(var(--accent) / 0.2);
85
- color: hsl(var(--foreground));
86
- }
87
-
88
- ::-webkit-scrollbar {
89
- width: 5px;
90
- }
91
-
92
- ::-webkit-scrollbar-track {
93
- background: hsl(var(--background));
94
- }
95
-
96
- ::-webkit-scrollbar-thumb {
97
- background: hsl(var(--border));
98
- }
99
-
100
- ::-webkit-scrollbar-thumb:hover {
101
- background: hsl(var(--accent) / 0.6);
102
- }
103
-
104
- :focus-visible {
105
- outline: 1px solid hsl(var(--accent));
106
- outline-offset: 2px;
107
- }
108
-
109
- /* Prices and catalog numbers always read as tabular figures */
110
- [data-price],
111
- .tabular {
112
- font-variant-numeric: tabular-nums;
113
- }
114
- }
115
-
116
- @layer components {
117
- /* ─── Buttons — gallery signage ─── */
118
-
119
- button[class*='bg-primary'],
120
- a[class*='bg-primary'] {
121
- font-weight: 600;
122
- letter-spacing: 0.04em;
123
- font-size: 0.8125rem;
124
- border-radius: 0;
125
- transition: background-color 0.2s cubic-bezier(0.22, 1, 0.36, 1);
126
- }
127
-
128
- button[class*='bg-primary']:hover,
129
- a[class*='bg-primary']:hover {
130
- background: hsl(30 8% 24%) !important;
131
- opacity: 1 !important;
132
- }
133
-
134
- [class*='bg-secondary'] {
135
- border: 1px solid hsl(var(--border));
136
- background: transparent !important;
137
- font-size: 0.8125rem;
138
- letter-spacing: 0.02em;
139
- border-radius: 0;
140
- transition: border-color 0.2s ease, background-color 0.2s ease;
141
- }
142
-
143
- [class*='bg-secondary']:hover {
144
- border-color: hsl(var(--foreground));
145
- background: hsl(var(--muted)) !important;
146
- }
147
-
148
- /* ─── Product images — the breath ─── */
149
-
150
- [class*='group-hover\:scale-105'] {
151
- transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1) !important;
152
- }
153
-
154
- /* ─── Forms — hairline boxes on the mat ─── */
155
-
156
- input[class*='border'],
157
- select[class*='border'],
158
- textarea[class*='border'] {
159
- background: hsl(var(--surface));
160
- border-radius: 0;
161
- transition: border-color 0.2s ease;
162
- }
163
-
164
- input[class*='border']:focus,
165
- select[class*='border']:focus,
166
- textarea[class*='border']:focus {
167
- border-color: hsl(var(--foreground));
168
- box-shadow: none;
169
- }
170
-
171
- input::placeholder,
172
- textarea::placeholder {
173
- color: hsl(var(--muted-foreground) / 0.7);
174
- font-size: 0.875rem;
175
- }
176
-
177
- /* ─── Header — the gallery entrance ─── */
178
-
179
- header[class*='sticky'] {
180
- backdrop-filter: blur(8px);
181
- background: hsl(var(--background) / 0.92) !important;
182
- border-bottom: 1px solid hsl(var(--border)) !important;
183
- }
184
-
185
- nav a {
186
- font-size: 0.75rem;
187
- font-weight: 600;
188
- letter-spacing: 0.02em;
189
- }
190
-
191
- /* ─── Placards — quiet caption chips ─── */
192
-
193
- [class*='bg-muted'][class*='text-xs'] {
194
- background: transparent !important;
195
- border: 0;
196
- color: hsl(var(--muted-foreground));
197
- font-size: 0.6875rem;
198
- letter-spacing: 0.08em;
199
- padding-inline: 0;
200
- }
201
-
202
- /* Sale — terracotta text, never a red box */
203
- [class*='bg-destructive'] {
204
- background: transparent !important;
205
- color: hsl(var(--destructive)) !important;
206
- font-weight: 600;
207
- letter-spacing: 0.08em;
208
- font-size: 0.6875rem;
209
- padding-inline: 0;
210
- }
211
-
212
- [class*='line-through'] {
213
- opacity: 0.4;
214
- }
215
-
216
- hr {
217
- border-color: hsl(var(--border));
218
- }
219
-
220
- /* Sticky summary panels sit on the mat */
221
- [class*='sticky'][class*='top-'] {
222
- background: hsl(var(--surface));
223
- border: 1px solid hsl(var(--border));
224
- }
225
-
226
- /* ─── Gallery furniture ─── */
227
-
228
- /* Edge-fading hairline — the engraved section divider */
229
- .hairline-fade {
230
- height: 1px;
231
- max-width: 20rem;
232
- margin-inline: auto;
233
- background: linear-gradient(to left, transparent, hsl(var(--border)), transparent);
234
- }
235
-
236
- /* The brass tick — a 12px pin centered on a hairline */
237
- .brass-rule {
238
- position: relative;
239
- height: 1px;
240
- background: hsl(var(--border));
241
- }
242
-
243
- .brass-rule::after {
244
- content: '';
245
- position: absolute;
246
- top: 0;
247
- right: 50%;
248
- transform: translateX(50%);
249
- width: 12px;
250
- height: 1px;
251
- background: hsl(var(--accent));
252
- }
253
-
254
- /* Overline — the 11px placard caption */
255
- .placard {
256
- font-size: 0.6875rem;
257
- font-weight: 400;
258
- letter-spacing: 0.08em;
259
- color: hsl(var(--muted-foreground));
260
- }
261
-
262
- /* Paper grain — the wall has tooth. Sits above everything at ~2%
263
- opacity so photography and type feel printed, not rendered. */
264
- .paper-grain {
265
- position: fixed;
266
- inset: 0;
267
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.021'/%3E%3C/svg%3E");
268
- pointer-events: none;
269
- z-index: 9999;
270
- }
271
-
272
- /* A hung work casts a real shadow — soft, warm, falling down the wall */
273
- .hung {
274
- box-shadow:
275
- 0 24px 44px -28px hsl(28 30% 22% / 0.3),
276
- 0 6px 14px -10px hsl(28 30% 22% / 0.12);
277
- }
278
-
279
- /* Sunlight on the wall — huge soft washes of warm light */
280
- .wash {
281
- pointer-events: none;
282
- position: absolute;
283
- border-radius: 9999px;
284
- filter: blur(48px);
285
- }
286
-
287
- .wash-sand {
288
- background: radial-gradient(closest-side, hsl(34 60% 88% / 0.65), transparent);
289
- }
290
-
291
- .wash-blush {
292
- background: radial-gradient(closest-side, hsl(12 50% 88% / 0.5), transparent);
293
- }
294
-
295
- /* The breathing dot — the only loading state */
296
- .breathing-dot {
297
- width: 8px;
298
- height: 8px;
299
- border-radius: 9999px;
300
- background: hsl(var(--accent));
301
- animation: breathe 1.8s ease-in-out infinite;
302
- }
303
-
304
- /* Scroll reveal — sections rise 16px as they enter the viewport */
305
- [data-reveal] {
306
- opacity: 0;
307
- transform: translateY(16px);
308
- transition:
309
- opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1),
310
- transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
311
- }
312
-
313
- [data-reveal].revealed {
314
- opacity: 1;
315
- transform: none;
316
- }
317
-
318
- /* The marquee — a quiet engraved band that drifts with the reading
319
- direction; two identical halves make the loop seamless */
320
- .marquee {
321
- overflow: hidden;
322
- white-space: nowrap;
323
- }
324
-
325
- .marquee-track {
326
- display: inline-flex;
327
- animation: marquee-drift 36s linear infinite;
328
- }
329
-
330
- .marquee:hover .marquee-track {
331
- animation-play-state: paused;
332
- }
333
- }
334
-
335
- /* ─── Motion — nothing moves more than 8px or longer than 700ms ─── */
336
-
337
- @keyframes gallery-fade {
338
- from {
339
- opacity: 0;
340
- }
341
- to {
342
- opacity: 1;
343
- }
344
- }
345
-
346
- @keyframes breathe {
347
- 0%,
348
- 100% {
349
- opacity: 0.3;
350
- }
351
- 50% {
352
- opacity: 1;
353
- }
354
- }
355
-
356
- @keyframes cart-bump {
357
- 30% {
358
- transform: scale(1.3);
359
- }
360
- 60% {
361
- transform: scale(0.92);
362
- }
363
- }
364
-
365
- .cart-bump {
366
- animation: cart-bump 0.45s cubic-bezier(0.22, 1, 0.36, 1);
367
- }
368
-
369
- /* Touch feedback — every filled button settles 1px under the finger */
370
- button[class*='bg-primary']:active,
371
- a[class*='bg-primary']:active {
372
- transform: translateY(1px);
373
- }
374
-
375
- @keyframes marquee-drift {
376
- from {
377
- transform: translateX(0);
378
- }
379
- to {
380
- transform: translateX(50%);
381
- }
382
- }
383
-
384
- [dir='ltr'] .marquee-track {
385
- animation-name: marquee-drift-ltr;
386
- }
387
-
388
- @keyframes marquee-drift-ltr {
389
- from {
390
- transform: translateX(0);
391
- }
392
- to {
393
- transform: translateX(-50%);
394
- }
395
- }
396
-
397
- main {
398
- animation: gallery-fade 0.4s ease both;
399
- }
400
-
401
- [class*='grid'] > [class*='group'] {
402
- animation: gallery-fade 0.3s ease both;
403
- }
404
-
405
- @media (prefers-reduced-motion: reduce) {
406
- *,
407
- *::before,
408
- *::after {
409
- animation: none !important;
410
- transition: none !important;
411
- }
412
- }
@@ -1,87 +0,0 @@
1
- {
2
- "common": {
3
- "whatsappMessage": "Hi, I'd love more details about a product from your store"
4
- },
5
- "home": {
6
- "heroOverline": "New collection · chosen with care",
7
- "featuredKicker": "No. 02",
8
- "viewExhibition": "View the full exhibition",
9
- "trustShipping": "Fast home delivery",
10
- "trustWarranty": "Full warranty on every item",
11
- "trustGift": "Free gift wrapping",
12
- "trustSecure": "Secure payment",
13
- "ctaStory": "Our story",
14
- "marqueeHandmade": "Chosen with care, piece by piece",
15
- "marqueeOneOff": "Small, rotating editions",
16
- "collectionsKicker": "No. 01",
17
- "collections": "Collections",
18
- "onWallKicker": "No. 03",
19
- "onWall": "On the wall now",
20
- "voicesKicker": "No. 04",
21
- "voices": "Kind words",
22
- "quote1": "“The order arrived in a box that is a gift in itself. Meticulous service from the first moment.”",
23
- "name1": "Michal, Tel Aviv",
24
- "quote2": "“I was looking for a special gift and found exactly what I wanted — and then some.”",
25
- "name2": "Noa, Haifa",
26
- "quote3": "“Quality you can feel in your hands, and quick, kind answers to every question.”",
27
- "name3": "Dana, Jerusalem",
28
- "storyOverline": "Behind the scenes",
29
- "storyTitle": "Some things are not rushed.",
30
- "storyBody": "Every piece here is chosen or made with intention — in small series, with good materials and plenty of patience. We believe good things are made slowly.",
31
- "storyCta": "View the collection",
32
- "visitTitle": "We'd love to meet",
33
- "visitSub": "Personal service · quick answers to every question",
34
- "visitCta": "Contact us",
35
- "newsletterPlaceholder": "Your email",
36
- "newsletterCta": "Join",
37
- "newsletterHook": "Join the newsletter for 10% off your first order",
38
- "newsletterSuccess": "Welcome ✨ Your discount is on its way to your inbox"
39
- },
40
- "productDetail": {
41
- "itemNo": "Item no. {sku}",
42
- "aboutWork": "About this piece",
43
- "giftPanel": "Personalization",
44
- "trustLine": "Fast shipping · 14-day returns · Gift wrapping",
45
- "fbtKicker": "No. 02",
46
- "upsellsKicker": "No. 03",
47
- "relatedKicker": "No. 04",
48
- "installments": "Up to 12 interest-free payments",
49
- "inStockShip": "In stock · ships within a few business days",
50
- "shippingReturnsTitle": "Shipping & returns",
51
- "shippingReturnsBody": "Home delivery within a few business days. Exchange or return within 14 days of receiving your order, provided the item is unused and in its original packaging.",
52
- "careWarrantyTitle": "Warranty & service",
53
- "careWarrantyBody": "We stand behind every piece that leaves here. Ran into a problem? Write to us and we'll sort it out quickly — repair, replacement or refund."
54
- },
55
- "reviews": {
56
- "title": "Reviews",
57
- "noReviews": "No reviews yet — be the first to share your experience.",
58
- "verifiedPurchase": "Verified purchase",
59
- "loading": "Loading…",
60
- "signIn": "Sign in",
61
- "signInSuffix": "to leave a review for this product.",
62
- "onlyPurchasers": "Only customers who purchased this product can leave a review.",
63
- "reviewsDisabled": "Reviews are not enabled for this store.",
64
- "cannotReview": "You cannot review this product right now.",
65
- "confirmDelete": "Delete your review for this product?",
66
- "deleteFailed": "Could not delete your review. Please try again.",
67
- "startOver": "Want to start over? You can delete your review.",
68
- "deleting": "Deleting…",
69
- "deleteReview": "Delete review",
70
- "alreadySubmitted": "You've already submitted a review for this product.",
71
- "tooMany": "Too many submissions. Please try again in a few minutes.",
72
- "updateFailed": "Could not update your review. Please try again.",
73
- "submitFailed": "Could not submit your review. Please try again.",
74
- "thanksUpdated": "Thanks! Your review has been updated.",
75
- "thanksSubmitted": "Thanks! Your review has been submitted.",
76
- "editYourReview": "Edit your review",
77
- "writeReview": "Write a review",
78
- "yourRating": "Your rating",
79
- "starRating": "Star rating",
80
- "yourReview": "Your review",
81
- "optionalField": "optional",
82
- "saving": "Saving…",
83
- "submittingBtn": "Submitting…",
84
- "saveChanges": "Save changes",
85
- "submitReview": "Submit review"
86
- }
87
- }
@@ -1,87 +0,0 @@
1
- {
2
- "common": {
3
- "whatsappMessage": "היי, אשמח לפרטים על מוצר מהאתר"
4
- },
5
- "home": {
6
- "heroOverline": "אוסף חדש · נבחר בקפידה",
7
- "featuredKicker": "מס׳ 02",
8
- "viewExhibition": "לכל התערוכה",
9
- "trustShipping": "משלוח מהיר עד הבית",
10
- "trustWarranty": "אחריות מלאה על כל פריט",
11
- "trustGift": "אריזת מתנה חינם",
12
- "trustSecure": "תשלום מאובטח",
13
- "ctaStory": "הסיפור שלנו",
14
- "marqueeHandmade": "נבחר בקפידה, פריט-פריט",
15
- "marqueeOneOff": "מהדורות קטנות ומתחלפות",
16
- "collectionsKicker": "מס׳ 01",
17
- "collections": "האוספים",
18
- "onWallKicker": "מס׳ 03",
19
- "onWall": "על הקיר עכשיו",
20
- "voicesKicker": "מס׳ 04",
21
- "voices": "אומרים עלינו",
22
- "quote1": "״ההזמנה הגיעה באריזה שהיא בעצמה מתנה. שירות מוקפד מהרגע הראשון.״",
23
- "name1": "מיכל, תל אביב",
24
- "quote2": "״חיפשתי מתנה מיוחדת ומצאתי בדיוק את מה שרציתי — ואפילו יותר.״",
25
- "name2": "נועה, חיפה",
26
- "quote3": "״איכות שמרגישים בידיים, ומענה מהיר ואדיב לכל שאלה.״",
27
- "name3": "דנה, ירושלים",
28
- "storyOverline": "מאחורי הקלעים",
29
- "storyTitle": "יש דברים שלא ממהרים.",
30
- "storyBody": "כל פריט אצלנו נבחר או נוצר בתשומת לב — בסדרות קטנות, עם חומרים טובים והרבה סבלנות. אנחנו מאמינים שדברים טובים נעשים לאט.",
31
- "storyCta": "לצפייה באוסף",
32
- "visitTitle": "נשמח להכיר",
33
- "visitSub": "שירות אישי · מענה מהיר לכל שאלה",
34
- "visitCta": "צרו קשר",
35
- "newsletterPlaceholder": "האימייל שלכם",
36
- "newsletterCta": "הצטרפו",
37
- "newsletterHook": "הצטרפו לניוזלטר וקבלו 10% הנחה בקנייה הראשונה",
38
- "newsletterSuccess": "ברוכים הבאים ✨ ההטבה בדרך לתיבת המייל שלכם"
39
- },
40
- "productDetail": {
41
- "itemNo": "פריט מס׳ {sku}",
42
- "aboutWork": "על הפריט",
43
- "giftPanel": "התאמה אישית",
44
- "trustLine": "משלוח מהיר · החזרות עד 14 יום · אריזת מתנה",
45
- "fbtKicker": "מס׳ 02",
46
- "upsellsKicker": "מס׳ 03",
47
- "relatedKicker": "מס׳ 04",
48
- "installments": "עד 12 תשלומים ללא ריבית",
49
- "inStockShip": "במלאי · נשלח תוך ימי עסקים ספורים",
50
- "shippingReturnsTitle": "משלוחים והחזרות",
51
- "shippingReturnsBody": "משלוח עד הבית תוך ימי עסקים ספורים. החלפה או החזרה תוך 14 יום מקבלת ההזמנה, בתנאי שהפריט לא נעשה בו שימוש ובאריזתו המקורית.",
52
- "careWarrantyTitle": "אחריות ושירות",
53
- "careWarrantyBody": "אנחנו עומדים מאחורי כל פריט שיוצא מכאן. נתקלתם בבעיה? כתבו לנו ונדאג לפתרון מהיר — תיקון, החלפה או החזר."
54
- },
55
- "reviews": {
56
- "title": "ביקורות",
57
- "noReviews": "אין עדיין ביקורות — היו הראשונים לשתף.",
58
- "verifiedPurchase": "רכישה מאומתת",
59
- "loading": "טוען…",
60
- "signIn": "התחברו",
61
- "signInSuffix": "כדי לכתוב ביקורת על המוצר.",
62
- "onlyPurchasers": "רק לקוחות שרכשו את המוצר יכולים לכתוב ביקורת.",
63
- "reviewsDisabled": "ביקורות אינן פעילות בחנות זו.",
64
- "cannotReview": "לא ניתן לכתוב ביקורת על המוצר כרגע.",
65
- "confirmDelete": "למחוק את הביקורת שלך על המוצר?",
66
- "deleteFailed": "לא ניתן היה למחוק את הביקורת. נסו שוב.",
67
- "startOver": "רוצים להתחיל מחדש? אפשר למחוק את הביקורת.",
68
- "deleting": "מוחק…",
69
- "deleteReview": "מחיקת ביקורת",
70
- "alreadySubmitted": "כבר שלחתם ביקורת על המוצר הזה.",
71
- "tooMany": "יותר מדי נסיונות. נסו שוב בעוד כמה דקות.",
72
- "updateFailed": "לא ניתן היה לעדכן את הביקורת. נסו שוב.",
73
- "submitFailed": "לא ניתן היה לשלוח את הביקורת. נסו שוב.",
74
- "thanksUpdated": "תודה! הביקורת עודכנה.",
75
- "thanksSubmitted": "תודה! הביקורת נשלחה.",
76
- "editYourReview": "עריכת הביקורת",
77
- "writeReview": "כתיבת ביקורת",
78
- "yourRating": "הדירוג שלכם",
79
- "starRating": "דירוג כוכבים",
80
- "yourReview": "הביקורת שלכם",
81
- "optionalField": "לא חובה",
82
- "saving": "שומר…",
83
- "submittingBtn": "שולח…",
84
- "saveChanges": "שמירת שינויים",
85
- "submitReview": "שליחת ביקורת"
86
- }
87
- }