create-brainerce-store 1.21.0 → 1.23.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.
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "create-brainerce-store",
34
- version: "1.21.0",
34
+ version: "1.22.0",
35
35
  description: "Scaffold a production-ready e-commerce storefront connected to Brainerce",
36
36
  bin: {
37
37
  "create-brainerce-store": "dist/index.js"
@@ -220,8 +220,16 @@ async function runInteractive(defaults) {
220
220
  description: "Clean, neutral design with Inter font",
221
221
  value: "minimal"
222
222
  },
223
- { title: "Luxury (coming soon)", value: "luxury", disabled: true },
224
- { title: "Playful (coming soon)", value: "playful", disabled: true }
223
+ {
224
+ title: "Luxury",
225
+ description: "Dark, sophisticated design with Cormorant Garamond serif font",
226
+ value: "luxury"
227
+ },
228
+ {
229
+ title: "Playful",
230
+ description: "Warm, vibrant design with Nunito rounded font",
231
+ value: "playful"
232
+ }
225
233
  ],
226
234
  initial: 0
227
235
  },
@@ -260,8 +268,23 @@ var import_ejs = __toESM(require("ejs"));
260
268
  function getDirection(language) {
261
269
  return language === "he" ? "rtl" : "ltr";
262
270
  }
263
- function getFontConfig(language) {
264
- if (language === "he") {
271
+ function getFontConfig(language, theme) {
272
+ const isHebrew = language === "he";
273
+ if (theme === "luxury") {
274
+ const subsets = isHebrew ? "['latin']" : "['latin']";
275
+ return {
276
+ fontImport: "import { Cormorant_Garamond } from 'next/font/google';",
277
+ fontVariable: `const font = Cormorant_Garamond({ weight: ['400', '500', '600', '700'], subsets: ${subsets} });`
278
+ };
279
+ }
280
+ if (theme === "playful") {
281
+ const subsets = isHebrew ? "['latin']" : "['latin']";
282
+ return {
283
+ fontImport: "import { Nunito } from 'next/font/google';",
284
+ fontVariable: `const font = Nunito({ subsets: ${subsets} });`
285
+ };
286
+ }
287
+ if (isHebrew) {
265
288
  return {
266
289
  fontImport: "import { Rubik } from 'next/font/google';",
267
290
  fontVariable: "const font = Rubik({ subsets: ['hebrew', 'latin'] });"
@@ -288,7 +311,7 @@ async function scaffold(options) {
288
311
  throw new Error(`Template "${framework}" not found at ${baseDir}`);
289
312
  }
290
313
  const direction = getDirection(options.language);
291
- const fontConfig = getFontConfig(options.language);
314
+ const fontConfig = getFontConfig(options.language, theme);
292
315
  const ogLocale = options.language === "he" ? "he_IL" : "en_US";
293
316
  const isMultiLocale = options.i18n?.enabled === true && options.i18n.supportedLocales.length > 1;
294
317
  const supportedLocales = options.i18n?.supportedLocales || [options.language];
@@ -303,7 +326,7 @@ async function scaffold(options) {
303
326
  fontImport: fontConfig.fontImport,
304
327
  fontVariable: fontConfig.fontVariable,
305
328
  ogLocale,
306
- apiBaseUrl: "https://api.brainerce.com",
329
+ apiBaseUrl: options.apiBaseUrl || "https://api.brainerce.com",
307
330
  i18nEnabled: isMultiLocale,
308
331
  supportedLocales: JSON.stringify(supportedLocales),
309
332
  defaultLocale
@@ -387,47 +410,39 @@ async function copyWithEjs(srcDir, destDir, vars) {
387
410
  }
388
411
 
389
412
  // src/fetch-store-info.ts
390
- var import_https = __toESM(require("https"));
391
- var import_http = __toESM(require("http"));
392
413
  async function fetchStoreInfo(connectionId, baseUrl = "https://api.brainerce.com") {
393
414
  const url = `${baseUrl}/api/vc/${connectionId}/info`;
394
- return new Promise((resolve, reject) => {
395
- const client = url.startsWith("https") ? import_https.default : import_http.default;
396
- const req = client.get(url, { timeout: 1e4 }, (res) => {
397
- let data = "";
398
- res.on("data", (chunk) => {
399
- data += chunk;
400
- });
401
- res.on("end", () => {
402
- if (res.statusCode && res.statusCode >= 400) {
403
- if (res.statusCode === 404) {
404
- reject(new Error(`Connection ID "${connectionId}" not found. Check your dashboard.`));
405
- } else {
406
- reject(new Error(`API returned status ${res.statusCode}`));
407
- }
408
- return;
409
- }
410
- try {
411
- const json = JSON.parse(data);
412
- resolve({
413
- name: json.name || json.storeName || "My Store",
414
- currency: json.currency || "USD",
415
- language: json.language || "en",
416
- ...json.i18n ? { i18n: json.i18n } : {}
417
- });
418
- } catch {
419
- reject(new Error("Invalid response from API"));
420
- }
421
- });
422
- });
423
- req.on("error", (err) => {
424
- reject(new Error(`Failed to connect to Brainerce API: ${err.message}`));
425
- });
426
- req.on("timeout", () => {
427
- req.destroy();
428
- reject(new Error("Request timed out"));
429
- });
430
- });
415
+ const controller = new AbortController();
416
+ const timeout = setTimeout(() => controller.abort(), 1e4);
417
+ let res;
418
+ try {
419
+ res = await fetch(url, { signal: controller.signal });
420
+ } catch (err) {
421
+ if (err.name === "AbortError") {
422
+ throw new Error("Request timed out");
423
+ }
424
+ throw new Error(`Failed to connect to Brainerce API: ${err.message}`);
425
+ } finally {
426
+ clearTimeout(timeout);
427
+ }
428
+ if (res.status === 404) {
429
+ throw new Error(`Connection ID "${connectionId}" not found. Check your dashboard.`);
430
+ }
431
+ if (!res.ok) {
432
+ throw new Error(`API returned status ${res.status}`);
433
+ }
434
+ let json;
435
+ try {
436
+ json = await res.json();
437
+ } catch {
438
+ throw new Error("Invalid response from API");
439
+ }
440
+ return {
441
+ name: json.name || json.storeName || "My Store",
442
+ currency: json.currency || "USD",
443
+ language: json.language || "en",
444
+ ...json.i18n ? { i18n: json.i18n } : {}
445
+ };
431
446
  }
432
447
 
433
448
  // src/utils/logger.ts
@@ -479,9 +494,9 @@ function createSpinner(text) {
479
494
  var pkg = require_package();
480
495
  async function checkForUpdate(name, current) {
481
496
  try {
482
- const https2 = require("https");
497
+ const https = require("https");
483
498
  return await new Promise((resolve) => {
484
- const req = https2.get(
499
+ const req = https.get(
485
500
  `https://registry.npmjs.org/${name}/latest`,
486
501
  { timeout: 3e3 },
487
502
  (res) => {
@@ -508,7 +523,10 @@ async function checkForUpdate(name, current) {
508
523
  }
509
524
  }
510
525
  var program = new import_commander.Command();
511
- program.name("create-brainerce-store").description("Scaffold a production-ready e-commerce storefront connected to Brainerce").version(pkg.version).argument("[project-name]", "Name for the project directory").option("--connection-id <id>", "Brainerce vibe-coded connection ID (vc_*)").option("--language <lang>", "Store language (en, he)").option("--framework <framework>", "Framework to use", "nextjs").option("--theme <theme>", "Theme to apply", "minimal").option("--pkg-manager <manager>", "Package manager (npm, pnpm, yarn, bun)").option("--no-git", "Skip git initialization").option("--no-install", "Skip dependency installation").action(async (projectNameArg, options) => {
526
+ program.name("create-brainerce-store").description("Scaffold a production-ready e-commerce storefront connected to Brainerce").version(pkg.version).argument("[project-name]", "Name for the project directory").option("--connection-id <id>", "Brainerce vibe-coded connection ID (vc_*)").option(
527
+ "--api-url <url>",
528
+ "Brainerce API base URL (overrides BRAINERCE_API_URL env, defaults to https://api.brainerce.com)"
529
+ ).option("--language <lang>", "Store language (en, he)").option("--framework <framework>", "Framework to use", "nextjs").option("--theme <theme>", "Theme to apply", "minimal").option("--pkg-manager <manager>", "Package manager (npm, pnpm, yarn, bun)").option("--no-git", "Skip git initialization").option("--no-install", "Skip dependency installation").action(async (projectNameArg, options) => {
512
530
  try {
513
531
  logger.banner(pkg.version);
514
532
  const updateCheck = checkForUpdate(pkg.name, pkg.version);
@@ -519,6 +537,7 @@ program.name("create-brainerce-store").description("Scaffold a production-ready
519
537
  scaffoldInPlace = true;
520
538
  }
521
539
  let connectionId = options.connectionId;
540
+ const apiBaseUrl = (options.apiUrl || process.env.BRAINERCE_API_URL || "https://api.brainerce.com").replace(/\/$/, "");
522
541
  let language = options.language;
523
542
  let framework = options.framework;
524
543
  let theme = options.theme;
@@ -555,7 +574,7 @@ program.name("create-brainerce-store").description("Scaffold a production-ready
555
574
  spinner.start();
556
575
  let storeInfo;
557
576
  try {
558
- storeInfo = await fetchStoreInfo(connectionId);
577
+ storeInfo = await fetchStoreInfo(connectionId, apiBaseUrl);
559
578
  const i18nStatus = storeInfo.i18n?.enabled && storeInfo.i18n.supportedLocales.length > 1 ? ` | i18n: ${storeInfo.i18n.supportedLocales.join(", ")}` : "";
560
579
  spinner.succeed(
561
580
  `Store: "${storeInfo.name}" | ${storeInfo.currency} | ${storeInfo.language}${i18nStatus}`
@@ -575,6 +594,7 @@ program.name("create-brainerce-store").description("Scaffold a production-ready
575
594
  await scaffold({
576
595
  projectName,
577
596
  connectionId,
597
+ apiBaseUrl,
578
598
  framework,
579
599
  theme,
580
600
  storeName: storeInfo.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-brainerce-store",
3
- "version": "1.21.0",
3
+ "version": "1.23.0",
4
4
  "description": "Scaffold a production-ready e-commerce storefront connected to Brainerce",
5
5
  "bin": {
6
6
  "create-brainerce-store": "dist/index.js"
@@ -534,15 +534,45 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
534
534
 
535
535
  if (sdk.renderType === 'iframe') {
536
536
  return (
537
- <div className={cn('py-4', className)}>
538
- <iframe
539
- src={paymentIntent.clientSecret}
540
- className="w-full border-0"
541
- style={{ minHeight: '500px' }}
542
- title={t('payment')}
543
- allow="payment"
544
- />
545
- </div>
537
+ <>
538
+ {/* Modal overlay */}
539
+ <div className="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/50 py-6 backdrop-blur-sm">
540
+ <div className="relative mx-4 w-full max-w-md rounded-2xl bg-white shadow-2xl">
541
+ {/* Close button */}
542
+ <button
543
+ onClick={() => {
544
+ window.location.href = `/checkout?checkout_id=${checkoutId}&canceled=true`;
545
+ }}
546
+ className="absolute end-3 top-3 z-10 flex h-8 w-8 items-center justify-center rounded-full bg-white/80 text-gray-500 shadow-sm transition-colors hover:bg-gray-100 hover:text-gray-700"
547
+ aria-label="Close"
548
+ >
549
+ <svg
550
+ width="14"
551
+ height="14"
552
+ viewBox="0 0 14 14"
553
+ fill="none"
554
+ stroke="currentColor"
555
+ strokeWidth="2"
556
+ strokeLinecap="round"
557
+ >
558
+ <path d="M1 1l12 12M13 1L1 13" />
559
+ </svg>
560
+ </button>
561
+ <iframe
562
+ src={paymentIntent.clientSecret}
563
+ className="w-full rounded-2xl border-0"
564
+ style={{ height: '80vh' }}
565
+ title={t('payment')}
566
+ allow="payment"
567
+ />
568
+ </div>
569
+ </div>
570
+ {/* Placeholder so the checkout layout doesn't collapse */}
571
+ <div className={cn('flex flex-col items-center justify-center py-12', className)}>
572
+ <LoadingSpinner size="lg" />
573
+ <p className="text-muted-foreground mt-4 text-sm">{t('preparingPayment')}</p>
574
+ </div>
575
+ </>
546
576
  );
547
577
  }
548
578
 
@@ -0,0 +1,399 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 20 14% 4%;
8
+ --foreground: 38 25% 86%;
9
+ --primary: 38 60% 55%;
10
+ --primary-foreground: 20 14% 4%;
11
+ --secondary: 20 10% 10%;
12
+ --secondary-foreground: 38 25% 86%;
13
+ --muted: 20 10% 12%;
14
+ --muted-foreground: 30 10% 50%;
15
+ --accent: 38 60% 55%;
16
+ --accent-foreground: 20 14% 4%;
17
+ --destructive: 0 72% 51%;
18
+ --destructive-foreground: 38 25% 86%;
19
+ --border: 30 10% 16%;
20
+ --radius: 0.25rem;
21
+
22
+ /* Luxury-specific tokens */
23
+ --gold: 38 60% 55%;
24
+ --gold-light: 38 50% 72%;
25
+ --gold-dark: 38 70% 40%;
26
+ --surface: 20 12% 7%;
27
+ --surface-elevated: 20 10% 10%;
28
+ }
29
+
30
+ * {
31
+ @apply border-border;
32
+ }
33
+
34
+ body {
35
+ @apply bg-background text-foreground antialiased;
36
+ letter-spacing: 0.01em;
37
+ line-height: 1.7;
38
+ }
39
+
40
+ /* Luxury typography — editorial serif hierarchy */
41
+ h1,
42
+ h2,
43
+ h3,
44
+ h4 {
45
+ letter-spacing: 0.04em;
46
+ font-weight: 500;
47
+ line-height: 1.2;
48
+ }
49
+
50
+ h1 {
51
+ text-transform: uppercase;
52
+ letter-spacing: 0.1em;
53
+ font-weight: 400;
54
+ line-height: 1.1;
55
+ }
56
+
57
+ h2 {
58
+ text-transform: uppercase;
59
+ letter-spacing: 0.06em;
60
+ }
61
+
62
+ /* Elegant link underline animation */
63
+ a:not([class*="bg-"]) {
64
+ position: relative;
65
+ transition: color 0.3s ease, opacity 0.3s ease;
66
+ }
67
+
68
+ main a:not([class*="bg-"]):not([class*="group"])::after {
69
+ content: '';
70
+ position: absolute;
71
+ bottom: -1px;
72
+ left: 0;
73
+ width: 0;
74
+ height: 1px;
75
+ background: hsl(var(--gold));
76
+ transition: width 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
77
+ }
78
+
79
+ main a:not([class*="bg-"]):not([class*="group"]):hover::after {
80
+ width: 100%;
81
+ }
82
+
83
+ /* Refined text selection */
84
+ ::selection {
85
+ background: hsl(var(--gold) / 0.3);
86
+ color: hsl(38 25% 96%);
87
+ }
88
+
89
+ /* Subtle body texture overlay */
90
+ body::before {
91
+ content: '';
92
+ position: fixed;
93
+ inset: 0;
94
+ background: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.015'/%3E%3C/svg%3E");
95
+ pointer-events: none;
96
+ z-index: 9999;
97
+ }
98
+
99
+ /* Custom scrollbar */
100
+ ::-webkit-scrollbar {
101
+ width: 5px;
102
+ }
103
+
104
+ ::-webkit-scrollbar-track {
105
+ background: hsl(20 14% 4%);
106
+ }
107
+
108
+ ::-webkit-scrollbar-thumb {
109
+ background: hsl(30 10% 18%);
110
+ border-radius: 3px;
111
+ }
112
+
113
+ ::-webkit-scrollbar-thumb:hover {
114
+ background: hsl(var(--gold) / 0.5);
115
+ }
116
+
117
+ /* Smooth focus states */
118
+ :focus-visible {
119
+ outline: 1px solid hsl(var(--gold) / 0.5);
120
+ outline-offset: 3px;
121
+ }
122
+ }
123
+
124
+ @layer components {
125
+ /* ─── Buttons ─── */
126
+
127
+ /* Primary CTA — gold with shimmer sweep */
128
+ button[class*="bg-primary"],
129
+ a[class*="bg-primary"] {
130
+ position: relative;
131
+ overflow: hidden;
132
+ font-weight: 500;
133
+ letter-spacing: 0.08em;
134
+ text-transform: uppercase;
135
+ font-size: 0.78rem;
136
+ border: 1px solid hsl(var(--gold) / 0.3);
137
+ transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
138
+ }
139
+
140
+ button[class*="bg-primary"]:hover,
141
+ a[class*="bg-primary"]:hover {
142
+ box-shadow:
143
+ 0 0 20px hsl(var(--gold) / 0.2),
144
+ 0 4px 16px hsl(0 0% 0% / 0.3);
145
+ border-color: hsl(var(--gold) / 0.6);
146
+ opacity: 1 !important;
147
+ }
148
+
149
+ button[class*="bg-primary"]::after,
150
+ a[class*="bg-primary"]::after {
151
+ content: '';
152
+ position: absolute;
153
+ top: -50%;
154
+ left: -75%;
155
+ width: 50%;
156
+ height: 200%;
157
+ background: linear-gradient(
158
+ 90deg,
159
+ transparent,
160
+ hsl(var(--gold-light) / 0.12),
161
+ hsl(var(--gold-light) / 0.2),
162
+ transparent
163
+ );
164
+ transform: skewX(-20deg);
165
+ transition: left 0.7s ease;
166
+ pointer-events: none;
167
+ }
168
+
169
+ button[class*="bg-primary"]:hover::after,
170
+ a[class*="bg-primary"]:hover::after {
171
+ left: 130%;
172
+ }
173
+
174
+ /* Secondary/ghost buttons — thin border, minimal */
175
+ [class*="bg-secondary"] {
176
+ border: 1px solid hsl(var(--border));
177
+ transition: all 0.3s ease;
178
+ letter-spacing: 0.04em;
179
+ text-transform: uppercase;
180
+ font-size: 0.8rem;
181
+ }
182
+
183
+ [class*="bg-secondary"]:hover {
184
+ border-color: hsl(var(--gold) / 0.4);
185
+ background: hsl(var(--surface-elevated)) !important;
186
+ }
187
+
188
+ /* ─── Product Cards ─── */
189
+
190
+ [class*="group"][class*="border"] {
191
+ transition: all 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
192
+ border-color: hsl(var(--border) / 0.5);
193
+ background: hsl(var(--surface));
194
+ }
195
+
196
+ [class*="group"][class*="border"]:hover {
197
+ border-color: hsl(var(--gold) / 0.25);
198
+ box-shadow:
199
+ 0 8px 40px hsl(0 0% 0% / 0.5),
200
+ 0 0 0 1px hsl(var(--gold) / 0.08);
201
+ }
202
+
203
+ /* Product image — slow cinematic zoom */
204
+ [class*="group-hover\\:scale-105"] {
205
+ transition: transform 1s cubic-bezier(0.25, 0.46, 0.45, 0.94) !important;
206
+ }
207
+
208
+ /* Product image overlay on hover — subtle dark gradient */
209
+ [class*="group"] [class*="aspect-square"] {
210
+ position: relative;
211
+ }
212
+
213
+ [class*="group"]:hover [class*="aspect-square"]::after {
214
+ content: '';
215
+ position: absolute;
216
+ inset: 0;
217
+ background: linear-gradient(
218
+ to top,
219
+ hsl(20 14% 4% / 0.3),
220
+ transparent 50%
221
+ );
222
+ pointer-events: none;
223
+ transition: opacity 0.5s ease;
224
+ }
225
+
226
+ /* ─── Form Elements ─── */
227
+
228
+ input[class*="border-border"],
229
+ select[class*="border-border"],
230
+ textarea[class*="border-border"] {
231
+ background: hsl(var(--surface)) !important;
232
+ transition: all 0.3s ease;
233
+ }
234
+
235
+ input[class*="border-border"]:focus,
236
+ select[class*="border-border"]:focus,
237
+ textarea[class*="border-border"]:focus {
238
+ border-color: hsl(var(--gold) / 0.5) !important;
239
+ box-shadow: 0 0 0 3px hsl(var(--gold) / 0.08);
240
+ background: hsl(var(--surface-elevated)) !important;
241
+ }
242
+
243
+ /* Placeholder text */
244
+ input::placeholder,
245
+ textarea::placeholder {
246
+ letter-spacing: 0.03em;
247
+ font-size: 0.85rem;
248
+ }
249
+
250
+ /* ─── Navigation & Header ─── */
251
+
252
+ header[class*="sticky"] {
253
+ backdrop-filter: blur(16px) saturate(1.2);
254
+ background: hsl(20 14% 4% / 0.8) !important;
255
+ border-bottom: 1px solid hsl(var(--gold) / 0.08) !important;
256
+ }
257
+
258
+ /* Nav links */
259
+ nav a {
260
+ letter-spacing: 0.06em;
261
+ text-transform: uppercase;
262
+ font-size: 0.75rem;
263
+ font-weight: 500;
264
+ }
265
+
266
+ /* ─── Badges & Tags ─── */
267
+
268
+ [class*="bg-muted"][class*="text-xs"] {
269
+ text-transform: uppercase;
270
+ letter-spacing: 0.1em;
271
+ font-size: 0.6rem;
272
+ font-weight: 600;
273
+ border: 1px solid hsl(var(--border));
274
+ }
275
+
276
+ /* Sale badges — dark red, refined */
277
+ [class*="bg-destructive"] {
278
+ background: hsl(0 60% 38%) !important;
279
+ font-weight: 600;
280
+ letter-spacing: 0.06em;
281
+ text-transform: uppercase;
282
+ font-size: 0.65rem;
283
+ }
284
+
285
+ /* ─── Price Display ─── */
286
+
287
+ /* Gold gradient on prices */
288
+ [class*="font-bold"][class*="text-lg"],
289
+ [class*="font-bold"][class*="text-xl"],
290
+ [class*="font-bold"][class*="text-2xl"] {
291
+ background: linear-gradient(
292
+ 135deg,
293
+ hsl(var(--gold-light)),
294
+ hsl(var(--gold)),
295
+ hsl(var(--gold-dark))
296
+ );
297
+ -webkit-background-clip: text;
298
+ -webkit-text-fill-color: transparent;
299
+ background-clip: text;
300
+ }
301
+
302
+ /* Strikethrough original prices */
303
+ [class*="line-through"] {
304
+ opacity: 0.4;
305
+ }
306
+
307
+ /* ─── Dividers & Separators ─── */
308
+
309
+ hr {
310
+ border-color: hsl(var(--border) / 0.3);
311
+ }
312
+
313
+ /* Decorative gold separator */
314
+ main > section + section {
315
+ border-top: 1px solid hsl(var(--gold) / 0.06);
316
+ }
317
+
318
+ /* ─── Cart & Checkout ─── */
319
+
320
+ /* Sticky summary panel */
321
+ [class*="sticky"][class*="top-"] {
322
+ background: hsl(var(--surface));
323
+ border: 1px solid hsl(var(--border) / 0.5);
324
+ }
325
+ }
326
+
327
+ /* ─── Animations ─── */
328
+
329
+ /* Elegant fade-in-up */
330
+ @keyframes luxury-fade-in {
331
+ from {
332
+ opacity: 0;
333
+ transform: translateY(16px);
334
+ }
335
+ to {
336
+ opacity: 1;
337
+ transform: translateY(0);
338
+ }
339
+ }
340
+
341
+ main > * {
342
+ animation: luxury-fade-in 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
343
+ }
344
+
345
+ main > *:nth-child(2) {
346
+ animation-delay: 0.1s;
347
+ }
348
+
349
+ main > *:nth-child(3) {
350
+ animation-delay: 0.2s;
351
+ }
352
+
353
+ main > *:nth-child(4) {
354
+ animation-delay: 0.3s;
355
+ }
356
+
357
+ /* Staggered grid item reveal */
358
+ @keyframes luxury-grid-item {
359
+ from {
360
+ opacity: 0;
361
+ transform: translateY(12px) scale(0.98);
362
+ }
363
+ to {
364
+ opacity: 1;
365
+ transform: translateY(0) scale(1);
366
+ }
367
+ }
368
+
369
+ [class*="grid"] > [class*="group"] {
370
+ animation: luxury-grid-item 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
371
+ }
372
+
373
+ [class*="grid"] > [class*="group"]:nth-child(2) {
374
+ animation-delay: 0.08s;
375
+ }
376
+
377
+ [class*="grid"] > [class*="group"]:nth-child(3) {
378
+ animation-delay: 0.16s;
379
+ }
380
+
381
+ [class*="grid"] > [class*="group"]:nth-child(4) {
382
+ animation-delay: 0.24s;
383
+ }
384
+
385
+ [class*="grid"] > [class*="group"]:nth-child(5) {
386
+ animation-delay: 0.32s;
387
+ }
388
+
389
+ [class*="grid"] > [class*="group"]:nth-child(6) {
390
+ animation-delay: 0.4s;
391
+ }
392
+
393
+ [class*="grid"] > [class*="group"]:nth-child(7) {
394
+ animation-delay: 0.48s;
395
+ }
396
+
397
+ [class*="grid"] > [class*="group"]:nth-child(8) {
398
+ animation-delay: 0.56s;
399
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "Luxury",
3
+ "description": "Dark, sophisticated design with Cormorant Garamond serif font and warm gold accents",
4
+ "font": {
5
+ "family": "Cormorant Garamond",
6
+ "import": "next/font/google"
7
+ },
8
+ "colors": {
9
+ "background": "20 14% 4%",
10
+ "foreground": "38 25% 86%",
11
+ "primary": "38 60% 55%",
12
+ "primary-foreground": "20 14% 4%",
13
+ "secondary": "20 10% 10%",
14
+ "secondary-foreground": "38 25% 86%",
15
+ "muted": "20 10% 12%",
16
+ "muted-foreground": "30 10% 50%",
17
+ "accent": "38 60% 55%",
18
+ "accent-foreground": "20 14% 4%",
19
+ "destructive": "0 72% 51%",
20
+ "border": "30 10% 16%"
21
+ },
22
+ "radius": "0.25rem"
23
+ }
@@ -0,0 +1,400 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 0 0% 99%;
8
+ --foreground: 260 25% 16%;
9
+ --primary: 330 75% 56%;
10
+ --primary-foreground: 0 0% 100%;
11
+ --secondary: 260 50% 94%;
12
+ --secondary-foreground: 260 25% 16%;
13
+ --muted: 270 30% 95%;
14
+ --muted-foreground: 260 10% 45%;
15
+ --accent: 280 60% 60%;
16
+ --accent-foreground: 0 0% 100%;
17
+ --destructive: 0 84% 60%;
18
+ --destructive-foreground: 0 0% 100%;
19
+ --border: 270 20% 88%;
20
+ --radius: 1rem;
21
+
22
+ /* Playful-specific tokens */
23
+ --pink: 330 75% 56%;
24
+ --purple: 280 60% 60%;
25
+ --lavender: 260 50% 94%;
26
+ --peach: 20 80% 88%;
27
+ --mint: 160 50% 88%;
28
+ }
29
+
30
+ * {
31
+ @apply border-border;
32
+ }
33
+
34
+ body {
35
+ @apply bg-background text-foreground antialiased;
36
+ line-height: 1.65;
37
+ }
38
+
39
+ /* Playful typography — friendly, bold, tight */
40
+ h1,
41
+ h2,
42
+ h3,
43
+ h4 {
44
+ font-weight: 700;
45
+ letter-spacing: -0.02em;
46
+ line-height: 1.2;
47
+ }
48
+
49
+ h1 {
50
+ font-weight: 800;
51
+ letter-spacing: -0.03em;
52
+ }
53
+
54
+ /* Fun colorful selection */
55
+ ::selection {
56
+ background: hsl(var(--pink) / 0.25);
57
+ color: hsl(260 25% 16%);
58
+ }
59
+
60
+ /* Colorful focus rings */
61
+ :focus-visible {
62
+ outline: 2.5px solid hsl(var(--purple));
63
+ outline-offset: 3px;
64
+ border-radius: var(--radius);
65
+ }
66
+
67
+ /* Playful scrollbar */
68
+ ::-webkit-scrollbar {
69
+ width: 8px;
70
+ }
71
+
72
+ ::-webkit-scrollbar-track {
73
+ background: hsl(var(--lavender));
74
+ }
75
+
76
+ ::-webkit-scrollbar-thumb {
77
+ background: linear-gradient(
78
+ 180deg,
79
+ hsl(var(--pink)),
80
+ hsl(var(--purple))
81
+ );
82
+ border-radius: 999px;
83
+ }
84
+ }
85
+
86
+ @layer components {
87
+ /* ─── Buttons ─── */
88
+
89
+ /* Primary CTA — gradient with bounce and glow */
90
+ button[class*="bg-primary"],
91
+ a[class*="bg-primary"] {
92
+ font-weight: 700;
93
+ letter-spacing: 0.01em;
94
+ background: linear-gradient(
95
+ 135deg,
96
+ hsl(var(--pink)),
97
+ hsl(330 80% 52%),
98
+ hsl(var(--purple))
99
+ ) !important;
100
+ background-size: 200% 200%;
101
+ box-shadow:
102
+ 0 4px 14px hsl(var(--pink) / 0.3),
103
+ 0 2px 6px hsl(var(--purple) / 0.15);
104
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
105
+ border: none;
106
+ }
107
+
108
+ button[class*="bg-primary"]:hover,
109
+ a[class*="bg-primary"]:hover {
110
+ transform: translateY(-3px) scale(1.03);
111
+ box-shadow:
112
+ 0 8px 28px hsl(var(--pink) / 0.4),
113
+ 0 4px 12px hsl(var(--purple) / 0.2);
114
+ background-position: 100% 0;
115
+ opacity: 1 !important;
116
+ }
117
+
118
+ button[class*="bg-primary"]:active,
119
+ a[class*="bg-primary"]:active {
120
+ transform: translateY(0) scale(0.97);
121
+ box-shadow: 0 2px 8px hsl(var(--pink) / 0.2);
122
+ }
123
+
124
+ /* Secondary buttons — soft pill with hover pop */
125
+ [class*="bg-secondary"] {
126
+ border-radius: 999px !important;
127
+ font-weight: 600;
128
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
129
+ }
130
+
131
+ [class*="bg-secondary"]:hover {
132
+ background: hsl(var(--lavender)) !important;
133
+ transform: translateY(-2px);
134
+ box-shadow: 0 4px 16px hsl(var(--purple) / 0.12);
135
+ }
136
+
137
+ /* ─── Product Cards ─── */
138
+
139
+ [class*="group"][class*="border"] {
140
+ border-radius: var(--radius);
141
+ overflow: hidden;
142
+ border: 2px solid hsl(var(--border));
143
+ background: white;
144
+ transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
145
+ }
146
+
147
+ [class*="group"][class*="border"]:hover {
148
+ transform: translateY(-6px) rotate(-0.5deg);
149
+ border-color: hsl(var(--pink) / 0.3);
150
+ box-shadow:
151
+ 0 16px 40px hsl(var(--purple) / 0.12),
152
+ 0 6px 16px hsl(var(--pink) / 0.1);
153
+ }
154
+
155
+ /* Product image hover — fun overshoot zoom */
156
+ [class*="group-hover\\:scale-105"] {
157
+ transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) !important;
158
+ }
159
+
160
+ [class*="group"]:hover [class*="group-hover\\:scale-105"] {
161
+ transform: scale(1.08) !important;
162
+ }
163
+
164
+ /* ─── Form Elements ─── */
165
+
166
+ input[class*="border-border"],
167
+ select[class*="border-border"],
168
+ textarea[class*="border-border"] {
169
+ border-radius: var(--radius);
170
+ border-width: 2px;
171
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
172
+ }
173
+
174
+ input[class*="border-border"]:focus,
175
+ select[class*="border-border"]:focus,
176
+ textarea[class*="border-border"]:focus {
177
+ border-color: hsl(var(--purple)) !important;
178
+ box-shadow: 0 0 0 4px hsl(var(--purple) / 0.1);
179
+ transform: scale(1.01);
180
+ }
181
+
182
+ input::placeholder,
183
+ textarea::placeholder {
184
+ color: hsl(var(--muted-foreground) / 0.6);
185
+ }
186
+
187
+ /* ─── Navigation & Header ─── */
188
+
189
+ header[class*="sticky"] {
190
+ border-bottom: none !important;
191
+ box-shadow: 0 4px 20px hsl(var(--purple) / 0.06);
192
+ background: hsl(0 0% 99% / 0.9) !important;
193
+ backdrop-filter: blur(10px);
194
+ }
195
+
196
+ /* Nav link hover — colorful underline */
197
+ nav a {
198
+ position: relative;
199
+ font-weight: 600;
200
+ transition: color 0.2s ease;
201
+ }
202
+
203
+ nav a::after {
204
+ content: '';
205
+ position: absolute;
206
+ bottom: -2px;
207
+ left: 50%;
208
+ width: 0;
209
+ height: 3px;
210
+ background: linear-gradient(90deg, hsl(var(--pink)), hsl(var(--purple)));
211
+ border-radius: 999px;
212
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
213
+ transform: translateX(-50%);
214
+ }
215
+
216
+ nav a:hover::after {
217
+ width: 100%;
218
+ }
219
+
220
+ nav a:hover {
221
+ color: hsl(var(--pink));
222
+ }
223
+
224
+ /* ─── Badges & Tags ─── */
225
+
226
+ /* Category/filter pills */
227
+ [class*="bg-muted"][class*="text-xs"] {
228
+ border-radius: 999px;
229
+ font-weight: 700;
230
+ padding-left: 0.85rem;
231
+ padding-right: 0.85rem;
232
+ transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
233
+ }
234
+
235
+ [class*="bg-muted"][class*="text-xs"]:hover {
236
+ background: hsl(var(--lavender)) !important;
237
+ transform: scale(1.05);
238
+ }
239
+
240
+ /* Sale/discount badges — fun wiggle + pill */
241
+ [class*="bg-destructive"] {
242
+ border-radius: 999px;
243
+ font-weight: 800;
244
+ box-shadow: 0 2px 8px hsl(0 84% 60% / 0.3);
245
+ animation: playful-wiggle 3s ease-in-out infinite;
246
+ }
247
+
248
+ /* ─── Price Display ─── */
249
+
250
+ [class*="font-bold"][class*="text-lg"],
251
+ [class*="font-bold"][class*="text-xl"],
252
+ [class*="font-bold"][class*="text-2xl"] {
253
+ color: hsl(var(--pink));
254
+ }
255
+
256
+ /* Strikethrough — more playful */
257
+ [class*="line-through"] {
258
+ opacity: 0.4;
259
+ text-decoration-color: hsl(var(--purple) / 0.4);
260
+ text-decoration-thickness: 2px;
261
+ }
262
+
263
+ /* ─── Dividers ─── */
264
+
265
+ hr {
266
+ border: none;
267
+ height: 3px;
268
+ background: linear-gradient(
269
+ 90deg,
270
+ transparent,
271
+ hsl(var(--lavender)),
272
+ hsl(var(--pink) / 0.2),
273
+ hsl(var(--lavender)),
274
+ transparent
275
+ );
276
+ border-radius: 999px;
277
+ }
278
+
279
+ /* ─── Cart & Checkout ─── */
280
+
281
+ /* Sticky summary — playful card */
282
+ [class*="sticky"][class*="top-"] {
283
+ border-radius: var(--radius);
284
+ border: 2px solid hsl(var(--border));
285
+ box-shadow: 0 4px 20px hsl(var(--purple) / 0.06);
286
+ }
287
+
288
+ /* Quantity buttons */
289
+ button[class*="h-8"][class*="w-8"],
290
+ button[class*="h-10"][class*="w-10"] {
291
+ border-radius: 999px !important;
292
+ transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
293
+ }
294
+
295
+ button[class*="h-8"][class*="w-8"]:hover,
296
+ button[class*="h-10"][class*="w-10"]:hover {
297
+ transform: scale(1.1);
298
+ background: hsl(var(--lavender)) !important;
299
+ }
300
+
301
+ /* ─── Images ─── */
302
+
303
+ /* Product images — rounded */
304
+ [class*="aspect-square"] {
305
+ border-radius: calc(var(--radius) - 2px);
306
+ overflow: hidden;
307
+ }
308
+ }
309
+
310
+ /* ─── Animations ─── */
311
+
312
+ /* Bouncy slide-up entrance */
313
+ @keyframes playful-slide-up {
314
+ from {
315
+ opacity: 0;
316
+ transform: translateY(24px) scale(0.96);
317
+ }
318
+ 50% {
319
+ transform: translateY(-6px) scale(1.01);
320
+ }
321
+ to {
322
+ opacity: 1;
323
+ transform: translateY(0) scale(1);
324
+ }
325
+ }
326
+
327
+ /* Wiggle for discount badges */
328
+ @keyframes playful-wiggle {
329
+ 0%,
330
+ 85%,
331
+ 100% {
332
+ transform: rotate(0deg);
333
+ }
334
+ 90% {
335
+ transform: rotate(-3deg);
336
+ }
337
+ 95% {
338
+ transform: rotate(3deg);
339
+ }
340
+ }
341
+
342
+ /* Pop-in for grid items */
343
+ @keyframes playful-pop-in {
344
+ from {
345
+ opacity: 0;
346
+ transform: scale(0.9) translateY(16px);
347
+ }
348
+ 60% {
349
+ transform: scale(1.02) translateY(-2px);
350
+ }
351
+ to {
352
+ opacity: 1;
353
+ transform: scale(1) translateY(0);
354
+ }
355
+ }
356
+
357
+ main > * {
358
+ animation: playful-slide-up 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
359
+ }
360
+
361
+ main > *:nth-child(2) {
362
+ animation-delay: 0.06s;
363
+ }
364
+
365
+ main > *:nth-child(3) {
366
+ animation-delay: 0.12s;
367
+ }
368
+
369
+ /* Staggered grid items — playful pop */
370
+ [class*="grid"] > [class*="group"] {
371
+ animation: playful-pop-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
372
+ }
373
+
374
+ [class*="grid"] > [class*="group"]:nth-child(2) {
375
+ animation-delay: 0.06s;
376
+ }
377
+
378
+ [class*="grid"] > [class*="group"]:nth-child(3) {
379
+ animation-delay: 0.12s;
380
+ }
381
+
382
+ [class*="grid"] > [class*="group"]:nth-child(4) {
383
+ animation-delay: 0.18s;
384
+ }
385
+
386
+ [class*="grid"] > [class*="group"]:nth-child(5) {
387
+ animation-delay: 0.24s;
388
+ }
389
+
390
+ [class*="grid"] > [class*="group"]:nth-child(6) {
391
+ animation-delay: 0.3s;
392
+ }
393
+
394
+ [class*="grid"] > [class*="group"]:nth-child(7) {
395
+ animation-delay: 0.36s;
396
+ }
397
+
398
+ [class*="grid"] > [class*="group"]:nth-child(8) {
399
+ animation-delay: 0.42s;
400
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "Playful",
3
+ "description": "Warm, vibrant design with Nunito rounded font and energetic pink-purple palette",
4
+ "font": {
5
+ "family": "Nunito",
6
+ "import": "next/font/google"
7
+ },
8
+ "colors": {
9
+ "background": "0 0% 99%",
10
+ "foreground": "260 25% 16%",
11
+ "primary": "330 75% 56%",
12
+ "primary-foreground": "0 0% 100%",
13
+ "secondary": "260 50% 94%",
14
+ "secondary-foreground": "260 25% 16%",
15
+ "muted": "270 30% 95%",
16
+ "muted-foreground": "260 10% 45%",
17
+ "accent": "280 60% 60%",
18
+ "accent-foreground": "0 0% 100%",
19
+ "destructive": "0 84% 60%",
20
+ "border": "270 20% 88%"
21
+ },
22
+ "radius": "1rem"
23
+ }