create-nymbal-app 0.0.0 → 0.2.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 (159) hide show
  1. package/bin/create-nymbal-app.mjs +9 -0
  2. package/dist/index.d.ts +37 -0
  3. package/dist/index.js +419 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +41 -7
  6. package/scaffold/project/astro/src/pages/index.astro +57 -0
  7. package/scaffold/templates/astro/README.md +26 -0
  8. package/scaffold/templates/astro/_dot.env.example +6 -0
  9. package/scaffold/templates/astro/_dot.gitignore +16 -0
  10. package/scaffold/templates/astro/_dot.node-version +1 -0
  11. package/scaffold/templates/astro/_dot.npmrc +1 -0
  12. package/scaffold/templates/astro/astro.config.mjs +14 -0
  13. package/scaffold/templates/astro/package.json +59 -0
  14. package/scaffold/templates/astro/src/components/AddressForm.astro +107 -0
  15. package/scaffold/templates/astro/src/components/Breadcrumb.astro +20 -0
  16. package/scaffold/templates/astro/src/components/CategoryNav.astro +27 -0
  17. package/scaffold/templates/astro/src/components/CheckoutSteps.astro +29 -0
  18. package/scaffold/templates/astro/src/components/Footer.astro +108 -0
  19. package/scaffold/templates/astro/src/components/Header.astro +192 -0
  20. package/scaffold/templates/astro/src/components/HeroSection.astro +17 -0
  21. package/scaffold/templates/astro/src/components/ImageGallery.astro +42 -0
  22. package/scaffold/templates/astro/src/components/NewsletterSignup.astro +39 -0
  23. package/scaffold/templates/astro/src/components/OrderSummary.astro +53 -0
  24. package/scaffold/templates/astro/src/components/Pagination.astro +13 -0
  25. package/scaffold/templates/astro/src/components/PriceDisplay.astro +25 -0
  26. package/scaffold/templates/astro/src/components/ProductCard.astro +44 -0
  27. package/scaffold/templates/astro/src/components/RelatedProducts.astro +28 -0
  28. package/scaffold/templates/astro/src/components/ReviewForm.astro +100 -0
  29. package/scaffold/templates/astro/src/components/ReviewList.astro +41 -0
  30. package/scaffold/templates/astro/src/components/SkipNav.astro +3 -0
  31. package/scaffold/templates/astro/src/components/SortDropdown.astro +26 -0
  32. package/scaffold/templates/astro/src/components/StockBadge.astro +32 -0
  33. package/scaffold/templates/astro/src/env.d.ts +2 -0
  34. package/scaffold/templates/astro/src/layouts/AccountLayout.astro +168 -0
  35. package/scaffold/templates/astro/src/layouts/BaseLayout.astro +55 -0
  36. package/scaffold/templates/astro/src/lib/api.ts +89 -0
  37. package/scaffold/templates/astro/src/lib/client.ts +13 -0
  38. package/scaffold/templates/astro/src/lib/format.ts +17 -0
  39. package/scaffold/templates/astro/src/lib/order-status.ts +14 -0
  40. package/scaffold/templates/astro/src/lib/seo.ts +62 -0
  41. package/scaffold/templates/astro/src/pages/404.astro +18 -0
  42. package/scaffold/templates/astro/src/pages/500.astro +17 -0
  43. package/scaffold/templates/astro/src/pages/account/addresses.astro +110 -0
  44. package/scaffold/templates/astro/src/pages/account/index.astro +67 -0
  45. package/scaffold/templates/astro/src/pages/account/login.astro +55 -0
  46. package/scaffold/templates/astro/src/pages/account/orders/[orderNumber].astro +108 -0
  47. package/scaffold/templates/astro/src/pages/account/orders.astro +81 -0
  48. package/scaffold/templates/astro/src/pages/account/profile.astro +131 -0
  49. package/scaffold/templates/astro/src/pages/account/register.astro +94 -0
  50. package/scaffold/templates/astro/src/pages/cart.astro +16 -0
  51. package/scaffold/templates/astro/src/pages/category/[slug].astro +73 -0
  52. package/scaffold/templates/astro/src/pages/checkout.astro +100 -0
  53. package/scaffold/templates/astro/src/pages/fragments/footer.astro +5 -0
  54. package/scaffold/templates/astro/src/pages/fragments/header.astro +5 -0
  55. package/scaffold/templates/astro/src/pages/fragments/nav.astro +9 -0
  56. package/scaffold/templates/astro/src/pages/fragments/product/[slug].astro +14 -0
  57. package/scaffold/templates/astro/src/pages/index.astro +49 -0
  58. package/scaffold/templates/astro/src/pages/order/[orderNumber].astro +127 -0
  59. package/scaffold/templates/astro/src/pages/product/[slug].astro +107 -0
  60. package/scaffold/templates/astro/src/pages/products/index.astro +62 -0
  61. package/scaffold/templates/astro/src/pages/search.astro +37 -0
  62. package/scaffold/templates/astro/src/scripts/account-guard.ts +22 -0
  63. package/scaffold/templates/astro/src/scripts/auth-forms.ts +89 -0
  64. package/scaffold/templates/astro/src/scripts/cart-page.ts +143 -0
  65. package/scaffold/templates/astro/src/scripts/checkout.ts +277 -0
  66. package/scaffold/templates/astro/src/scripts/image-gallery.ts +22 -0
  67. package/scaffold/templates/astro/src/scripts/init-client.ts +16 -0
  68. package/scaffold/templates/astro/src/scripts/order-status.ts +27 -0
  69. package/scaffold/templates/astro/src/scripts/pdp.ts +67 -0
  70. package/scaffold/templates/astro/src/scripts/plp.ts +109 -0
  71. package/scaffold/templates/astro/src/scripts/search.ts +145 -0
  72. package/scaffold/templates/astro/src/styles/global.css +362 -0
  73. package/scaffold/templates/astro/src/styles/pages/account.css +331 -0
  74. package/scaffold/templates/astro/src/styles/pages/cart.css +243 -0
  75. package/scaffold/templates/astro/src/styles/pages/checkout.css +270 -0
  76. package/scaffold/templates/astro/src/styles/pages/confirmation.css +142 -0
  77. package/scaffold/templates/astro/src/styles/pages/error.css +69 -0
  78. package/scaffold/templates/astro/src/styles/pages/home.css +269 -0
  79. package/scaffold/templates/astro/src/styles/pages/pdp.css +272 -0
  80. package/scaffold/templates/astro/src/styles/pages/plp.css +213 -0
  81. package/scaffold/templates/astro/src/styles/pages/search.css +152 -0
  82. package/scaffold/templates/astro/tsconfig.json +3 -0
  83. package/scaffold/templates/nextjs/README.md +26 -0
  84. package/scaffold/templates/nextjs/_dot.env.example +6 -0
  85. package/scaffold/templates/nextjs/_dot.gitignore +17 -0
  86. package/scaffold/templates/nextjs/_dot.node-version +1 -0
  87. package/scaffold/templates/nextjs/_dot.npmrc +1 -0
  88. package/scaffold/templates/nextjs/app/account/addresses/page.tsx +10 -0
  89. package/scaffold/templates/nextjs/app/account/layout.tsx +57 -0
  90. package/scaffold/templates/nextjs/app/account/login/page.tsx +23 -0
  91. package/scaffold/templates/nextjs/app/account/orders/[orderNumber]/page.tsx +192 -0
  92. package/scaffold/templates/nextjs/app/account/orders/page.tsx +123 -0
  93. package/scaffold/templates/nextjs/app/account/page.tsx +59 -0
  94. package/scaffold/templates/nextjs/app/account/profile/page.tsx +10 -0
  95. package/scaffold/templates/nextjs/app/account/register/page.tsx +10 -0
  96. package/scaffold/templates/nextjs/app/cart/page.tsx +10 -0
  97. package/scaffold/templates/nextjs/app/category/[slug]/page.tsx +58 -0
  98. package/scaffold/templates/nextjs/app/checkout/page.tsx +10 -0
  99. package/scaffold/templates/nextjs/app/error.tsx +49 -0
  100. package/scaffold/templates/nextjs/app/globals.css +473 -0
  101. package/scaffold/templates/nextjs/app/layout.tsx +29 -0
  102. package/scaffold/templates/nextjs/app/not-found.tsx +38 -0
  103. package/scaffold/templates/nextjs/app/order/[orderNumber]/page.tsx +127 -0
  104. package/scaffold/templates/nextjs/app/page.tsx +63 -0
  105. package/scaffold/templates/nextjs/app/product/[slug]/page.tsx +111 -0
  106. package/scaffold/templates/nextjs/app/products/page.tsx +40 -0
  107. package/scaffold/templates/nextjs/app/search/page.tsx +24 -0
  108. package/scaffold/templates/nextjs/components/client/AddressManager.tsx +181 -0
  109. package/scaffold/templates/nextjs/components/client/AuthGuard.tsx +55 -0
  110. package/scaffold/templates/nextjs/components/client/CartDrawerToggle.tsx +39 -0
  111. package/scaffold/templates/nextjs/components/client/CartPage.tsx +120 -0
  112. package/scaffold/templates/nextjs/components/client/CheckoutForm.tsx +523 -0
  113. package/scaffold/templates/nextjs/components/client/ImageGallery.tsx +68 -0
  114. package/scaffold/templates/nextjs/components/client/LoginForm.tsx +89 -0
  115. package/scaffold/templates/nextjs/components/client/MiniCartWrapper.tsx +12 -0
  116. package/scaffold/templates/nextjs/components/client/NewsletterSignup.tsx +74 -0
  117. package/scaffold/templates/nextjs/components/client/NymbalSetup.tsx +24 -0
  118. package/scaffold/templates/nextjs/components/client/OrderStatusRefresh.tsx +48 -0
  119. package/scaffold/templates/nextjs/components/client/ProductGrid.tsx +148 -0
  120. package/scaffold/templates/nextjs/components/client/ProductInteractive.tsx +61 -0
  121. package/scaffold/templates/nextjs/components/client/ProfileForm.tsx +112 -0
  122. package/scaffold/templates/nextjs/components/client/RegisterForm.tsx +131 -0
  123. package/scaffold/templates/nextjs/components/client/ReviewForm.tsx +116 -0
  124. package/scaffold/templates/nextjs/components/client/SearchBarWrapper.tsx +27 -0
  125. package/scaffold/templates/nextjs/components/client/SearchResults.tsx +141 -0
  126. package/scaffold/templates/nextjs/components/client/SortDropdown.tsx +35 -0
  127. package/scaffold/templates/nextjs/components/server/Breadcrumb.tsx +36 -0
  128. package/scaffold/templates/nextjs/components/server/CategoryNav.tsx +29 -0
  129. package/scaffold/templates/nextjs/components/server/CheckoutSteps.tsx +60 -0
  130. package/scaffold/templates/nextjs/components/server/Footer.module.css +64 -0
  131. package/scaffold/templates/nextjs/components/server/Footer.tsx +79 -0
  132. package/scaffold/templates/nextjs/components/server/Header.module.css +147 -0
  133. package/scaffold/templates/nextjs/components/server/Header.tsx +102 -0
  134. package/scaffold/templates/nextjs/components/server/HeroSection.tsx +18 -0
  135. package/scaffold/templates/nextjs/components/server/OrderSummary.tsx +79 -0
  136. package/scaffold/templates/nextjs/components/server/PriceDisplay.tsx +31 -0
  137. package/scaffold/templates/nextjs/components/server/ProductCard.tsx +59 -0
  138. package/scaffold/templates/nextjs/components/server/RelatedProducts.tsx +31 -0
  139. package/scaffold/templates/nextjs/components/server/ReviewList.tsx +62 -0
  140. package/scaffold/templates/nextjs/components/server/SkipNav.tsx +7 -0
  141. package/scaffold/templates/nextjs/components/server/StockBadge.tsx +19 -0
  142. package/scaffold/templates/nextjs/lib/api.ts +89 -0
  143. package/scaffold/templates/nextjs/lib/client.ts +23 -0
  144. package/scaffold/templates/nextjs/lib/format.ts +7 -0
  145. package/scaffold/templates/nextjs/lib/order-status.ts +14 -0
  146. package/scaffold/templates/nextjs/lib/seo.ts +73 -0
  147. package/scaffold/templates/nextjs/next.config.mjs +9 -0
  148. package/scaffold/templates/nextjs/package.json +64 -0
  149. package/scaffold/templates/nextjs/styles/pages/account.module.css +377 -0
  150. package/scaffold/templates/nextjs/styles/pages/cart.module.css +240 -0
  151. package/scaffold/templates/nextjs/styles/pages/checkout.module.css +404 -0
  152. package/scaffold/templates/nextjs/styles/pages/confirmation.module.css +237 -0
  153. package/scaffold/templates/nextjs/styles/pages/error.module.css +132 -0
  154. package/scaffold/templates/nextjs/styles/pages/home.module.css +285 -0
  155. package/scaffold/templates/nextjs/styles/pages/pdp.module.css +319 -0
  156. package/scaffold/templates/nextjs/styles/pages/plp.module.css +280 -0
  157. package/scaffold/templates/nextjs/styles/pages/search.module.css +192 -0
  158. package/scaffold/templates/nextjs/tsconfig.json +41 -0
  159. package/README.md +0 -4
@@ -0,0 +1,16 @@
1
+ node_modules/
2
+ dist/
3
+ build/
4
+ .astro/
5
+ .next/
6
+ coverage/
7
+ *.log
8
+ .DS_Store
9
+ .env
10
+ .env.local
11
+ .env.*.local
12
+ *.tsbuildinfo
13
+ nymbal.db
14
+ nymbal.db-journal
15
+ nymbal.db-shm
16
+ nymbal.db-wal
@@ -0,0 +1 @@
1
+ 22.23.3
@@ -0,0 +1 @@
1
+ engine-strict=true
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'astro/config'
2
+ import node from '@astrojs/node'
3
+
4
+ // NYMBAL_STOREFRONT_PORT lets `nymbal dev` (and a developer) pick a free port
5
+ // when the default 4321 is taken by another project. The `astro dev --port`
6
+ // flag still overrides this, so `nymbal dev` (which passes --port explicitly)
7
+ // always wins; this default just keeps a bare `pnpm dev` from hard-binding 4321.
8
+ const port = Number(process.env.NYMBAL_STOREFRONT_PORT) || 4321
9
+
10
+ export default defineConfig({
11
+ output: 'server',
12
+ adapter: node({ mode: 'standalone' }),
13
+ server: { port },
14
+ })
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@nymbalhq/template-astro",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "A Nymbal commerce project",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "engines": {
9
+ "node": ">=22"
10
+ },
11
+ "scripts": {
12
+ "dev": "astro dev",
13
+ "build": "astro build",
14
+ "preview": "astro preview",
15
+ "typecheck": "astro check",
16
+ "migrate": "nymbal migrate",
17
+ "seed": "nymbal seed"
18
+ },
19
+ "dependencies": {
20
+ "nymbal": "0.2.0",
21
+ "@nymbalhq/config": "0.2.0",
22
+ "@nymbalhq/platform": "0.2.0",
23
+ "@nymbalhq/stack": "0.2.0",
24
+ "@astrojs/node": "^11.1.8",
25
+ "@nymbalhq/sdk": "0.2.0",
26
+ "@nymbalhq/types": "0.2.0",
27
+ "@nymbalhq/web-components": "0.2.0",
28
+ "@stripe/stripe-js": "^4.0.0",
29
+ "astro": "^7.4.0"
30
+ },
31
+ "devDependencies": {
32
+ "@astrojs/check": "^0.9.10",
33
+ "typescript": "^5.6.3"
34
+ },
35
+ "pnpm": {
36
+ "overrides": {
37
+ "nymbal": "0.2.0",
38
+ "@nymbalhq/config": "0.2.0",
39
+ "@nymbalhq/http": "0.2.0",
40
+ "@nymbalhq/platform": "0.2.0",
41
+ "@nymbalhq/react": "0.2.0",
42
+ "@nymbalhq/sdk": "0.2.0",
43
+ "@nymbalhq/stack": "0.2.0",
44
+ "@nymbalhq/types": "0.2.0",
45
+ "@nymbalhq/web-components": "0.2.0"
46
+ }
47
+ },
48
+ "overrides": {
49
+ "nymbal": "0.2.0",
50
+ "@nymbalhq/config": "0.2.0",
51
+ "@nymbalhq/http": "0.2.0",
52
+ "@nymbalhq/platform": "0.2.0",
53
+ "@nymbalhq/react": "0.2.0",
54
+ "@nymbalhq/sdk": "0.2.0",
55
+ "@nymbalhq/stack": "0.2.0",
56
+ "@nymbalhq/types": "0.2.0",
57
+ "@nymbalhq/web-components": "0.2.0"
58
+ }
59
+ }
@@ -0,0 +1,107 @@
1
+ ---
2
+ import '../styles/pages/checkout.css'
3
+
4
+ interface Props {
5
+ id: string
6
+ }
7
+
8
+ const { id } = Astro.props
9
+ ---
10
+
11
+ <div class="address-grid" data-testid={`${id}-form`}>
12
+ <div class="form-group">
13
+ <label for={`${id}-firstName`}>First Name</label>
14
+ <input
15
+ type="text"
16
+ id={`${id}-firstName`}
17
+ name="firstName"
18
+ required
19
+ autocomplete="given-name"
20
+ data-testid={`${id}-firstName`}
21
+ />
22
+ </div>
23
+
24
+ <div class="form-group">
25
+ <label for={`${id}-lastName`}>Last Name</label>
26
+ <input
27
+ type="text"
28
+ id={`${id}-lastName`}
29
+ name="lastName"
30
+ required
31
+ autocomplete="family-name"
32
+ data-testid={`${id}-lastName`}
33
+ />
34
+ </div>
35
+
36
+ <div class="form-group full-width">
37
+ <label for={`${id}-addressLine1`}>Address Line 1</label>
38
+ <input
39
+ type="text"
40
+ id={`${id}-addressLine1`}
41
+ name="addressLine1"
42
+ required
43
+ autocomplete="address-line1"
44
+ data-testid={`${id}-addressLine1`}
45
+ />
46
+ </div>
47
+
48
+ <div class="form-group full-width">
49
+ <label for={`${id}-addressLine2`}>Address Line 2 (optional)</label>
50
+ <input
51
+ type="text"
52
+ id={`${id}-addressLine2`}
53
+ name="addressLine2"
54
+ autocomplete="address-line2"
55
+ data-testid={`${id}-addressLine2`}
56
+ />
57
+ </div>
58
+
59
+ <div class="form-group">
60
+ <label for={`${id}-city`}>City</label>
61
+ <input
62
+ type="text"
63
+ id={`${id}-city`}
64
+ name="city"
65
+ required
66
+ autocomplete="address-level2"
67
+ data-testid={`${id}-city`}
68
+ />
69
+ </div>
70
+
71
+ <div class="form-group">
72
+ <label for={`${id}-region`}>State / County</label>
73
+ <input
74
+ type="text"
75
+ id={`${id}-region`}
76
+ name="region"
77
+ required
78
+ autocomplete="address-level1"
79
+ data-testid={`${id}-region`}
80
+ />
81
+ </div>
82
+
83
+ <div class="form-group">
84
+ <label for={`${id}-postalCode`}>Postcode</label>
85
+ <input
86
+ type="text"
87
+ id={`${id}-postalCode`}
88
+ name="postalCode"
89
+ required
90
+ autocomplete="postal-code"
91
+ data-testid={`${id}-postalCode`}
92
+ />
93
+ </div>
94
+
95
+ <div class="form-group">
96
+ <label for={`${id}-country`}>Country (2-letter code)</label>
97
+ <input
98
+ type="text"
99
+ id={`${id}-country`}
100
+ name="country"
101
+ required
102
+ autocomplete="country"
103
+ placeholder="GB"
104
+ data-testid={`${id}-country`}
105
+ />
106
+ </div>
107
+ </div>
@@ -0,0 +1,20 @@
1
+ ---
2
+ interface Props {
3
+ items: Array<{ label: string; href?: string }>
4
+ }
5
+
6
+ const { items } = Astro.props
7
+ ---
8
+
9
+ <nav class="pdp-breadcrumbs" aria-label="Breadcrumb" data-testid="breadcrumb">
10
+ {items.map((item, index) => (
11
+ <>
12
+ {index > 0 && <span class="separator" aria-hidden="true">/</span>}
13
+ {item.href && index < items.length - 1 ? (
14
+ <a href={item.href}>{item.label}</a>
15
+ ) : (
16
+ <span class="current" aria-current="page">{item.label}</span>
17
+ )}
18
+ </>
19
+ ))}
20
+ </nav>
@@ -0,0 +1,27 @@
1
+ ---
2
+ import '../styles/pages/home.css'
3
+ import type { Category } from '@nymbalhq/types'
4
+
5
+ interface Props {
6
+ categories: Category[]
7
+ }
8
+
9
+ const { categories } = Astro.props
10
+ ---
11
+
12
+ <div class="category-grid">
13
+ {categories.map((cat) => (
14
+ <a
15
+ href={`/category/${cat.slug}`}
16
+ class="category-card"
17
+ data-testid={`category-${cat.slug}`}
18
+ >
19
+ <span class="category-card-name">{cat.name}</span>
20
+ {cat.description && (
21
+ <span class="category-card-description" style="font-size: var(--nymbal-font-size-xs, 0.75rem); color: var(--nymbal-color-text-muted, #71717a); margin-top: var(--nymbal-spacing-xs, 0.25rem);">
22
+ {cat.description}
23
+ </span>
24
+ )}
25
+ </a>
26
+ ))}
27
+ </div>
@@ -0,0 +1,29 @@
1
+ ---
2
+ import '../styles/pages/checkout.css'
3
+
4
+ interface Props {
5
+ currentStep: 'contact' | 'shipping' | 'payment'
6
+ }
7
+
8
+ const { currentStep } = Astro.props
9
+ const steps = ['contact', 'shipping', 'payment'] as const
10
+ const stepLabels = { contact: 'Contact', shipping: 'Shipping', payment: 'Payment' }
11
+ const currentIndex = steps.indexOf(currentStep)
12
+ ---
13
+
14
+ <div class="checkout-steps" data-testid="checkout-steps">
15
+ {steps.map((step, index) => {
16
+ const isCompleted = index < currentIndex
17
+ const isActive = step === currentStep
18
+ const stepClass = isCompleted ? 'checkout-step completed' : isActive ? 'checkout-step active' : 'checkout-step'
19
+ const dividerClass = index < currentIndex ? 'checkout-step-divider completed' : 'checkout-step-divider'
20
+ return (
21
+ <>
22
+ {index > 0 && <div class={dividerClass} aria-hidden="true"></div>}
23
+ <div class={stepClass} data-testid={`checkout-step-${step}`}>
24
+ <span>{stepLabels[step]}</span>
25
+ </div>
26
+ </>
27
+ )
28
+ })}
29
+ </div>
@@ -0,0 +1,108 @@
1
+ ---
2
+ const currentYear = new Date().getFullYear()
3
+ ---
4
+ <footer class="site-footer" data-testid="site-footer">
5
+ <div class="footer-container container">
6
+ <div class="footer-grid">
7
+ <div class="footer-column">
8
+ <h2 class="footer-heading">Shop</h2>
9
+ <ul class="footer-links">
10
+ <li><a href="/products">All Products</a></li>
11
+ <li><a href="/category/apparel">Apparel</a></li>
12
+ <li><a href="/category/home">Home</a></li>
13
+ <li><a href="/category/electronics">Electronics</a></li>
14
+ <li><a href="/category/books">Books</a></li>
15
+ </ul>
16
+ </div>
17
+
18
+ <div class="footer-column">
19
+ <h2 class="footer-heading">Account</h2>
20
+ <ul class="footer-links">
21
+ <li><a href="/account/login">Login</a></li>
22
+ <li><a href="/account/register">Register</a></li>
23
+ <li><a href="/account/orders">Orders</a></li>
24
+ </ul>
25
+ </div>
26
+
27
+ <div class="footer-column">
28
+ <h2 class="footer-heading">About</h2>
29
+ <p class="footer-about">
30
+ Nymbal is a modern headless commerce platform built for developers.
31
+ Fast, flexible, and fully customisable.
32
+ </p>
33
+ </div>
34
+ </div>
35
+
36
+ <div class="footer-bottom">
37
+ <p>&copy; {currentYear} Nymbal. All rights reserved.</p>
38
+ </div>
39
+ </div>
40
+ </footer>
41
+
42
+ <style>
43
+ .site-footer {
44
+ background-color: var(--nymbal-color-primary, #18181b);
45
+ color: var(--nymbal-color-text-inverse, #fff);
46
+ margin-top: auto;
47
+ }
48
+
49
+ .footer-container {
50
+ padding-block: 3rem var(--nymbal-spacing-lg, 1.5rem);
51
+ }
52
+
53
+ .footer-grid {
54
+ display: grid;
55
+ grid-template-columns: 1fr;
56
+ gap: var(--nymbal-spacing-xl, 2rem);
57
+ margin-bottom: var(--nymbal-spacing-xl, 2rem);
58
+ }
59
+
60
+ @media (min-width: 641px) {
61
+ .footer-grid {
62
+ grid-template-columns: repeat(3, 1fr);
63
+ }
64
+ }
65
+
66
+ .footer-heading {
67
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
68
+ font-weight: var(--nymbal-font-weight-bold, 700);
69
+ text-transform: uppercase;
70
+ letter-spacing: 0.05em;
71
+ margin-bottom: var(--nymbal-spacing-md, 1rem);
72
+ color: var(--nymbal-color-text-inverse, #fff);
73
+ }
74
+
75
+ .footer-links {
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: var(--nymbal-spacing-sm, 0.5rem);
79
+ }
80
+
81
+ .footer-links a {
82
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
83
+ color: rgba(255, 255, 255, 0.7);
84
+ text-decoration: none;
85
+ transition: color var(--nymbal-transition-fast, 150ms) ease;
86
+ }
87
+
88
+ .footer-links a:hover {
89
+ color: var(--nymbal-color-text-inverse, #fff);
90
+ }
91
+
92
+ .footer-about {
93
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
94
+ color: rgba(255, 255, 255, 0.7);
95
+ line-height: 1.6;
96
+ max-width: 28ch;
97
+ }
98
+
99
+ .footer-bottom {
100
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
101
+ padding-top: var(--nymbal-spacing-lg, 1.5rem);
102
+ }
103
+
104
+ .footer-bottom p {
105
+ font-size: var(--nymbal-font-size-xs, 0.75rem);
106
+ color: rgba(255, 255, 255, 0.5);
107
+ }
108
+ </style>
@@ -0,0 +1,192 @@
1
+ ---
2
+ ---
3
+ <header class="site-header" data-testid="site-header">
4
+ <div class="header-container container">
5
+ <a href="/" class="header-logo">Nymbal</a>
6
+
7
+ <label class="hamburger" for="nav-toggle" aria-label="Toggle navigation">
8
+ <span class="hamburger-line"></span>
9
+ <span class="hamburger-line"></span>
10
+ <span class="hamburger-line"></span>
11
+ </label>
12
+ <input type="checkbox" id="nav-toggle" class="nav-toggle visually-hidden" aria-label="Toggle navigation" />
13
+
14
+ <div class="header-nav-wrapper">
15
+ <nav class="header-nav" aria-label="Main navigation">
16
+ <a href="/category/apparel" class="nav-link">Apparel</a>
17
+ <a href="/category/home" class="nav-link">Home</a>
18
+ <a href="/category/electronics" class="nav-link">Electronics</a>
19
+ <a href="/category/books" class="nav-link">Books</a>
20
+ <a href="/products" class="nav-link">All Products</a>
21
+ </nav>
22
+
23
+ <div class="header-actions">
24
+ <nymbal-search-bar placeholder="Search products..." debounce-ms="300"></nymbal-search-bar>
25
+ <a href="/account" class="header-account-link" aria-label="Account">
26
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
27
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
28
+ <circle cx="12" cy="7" r="4" />
29
+ </svg>
30
+ </a>
31
+ <nymbal-mini-cart></nymbal-mini-cart>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </header>
36
+
37
+ <style>
38
+ .site-header {
39
+ position: sticky;
40
+ top: 0;
41
+ z-index: var(--nymbal-z-index-sticky, 100);
42
+ background-color: var(--nymbal-color-surface, #fff);
43
+ border-bottom: 1px solid var(--nymbal-color-border, #e4e4e7);
44
+ }
45
+
46
+ .header-container {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: var(--nymbal-spacing-md, 1rem);
50
+ padding-block: var(--nymbal-spacing-sm, 0.5rem);
51
+ flex-wrap: wrap;
52
+ }
53
+
54
+ .header-logo {
55
+ font-size: var(--nymbal-font-size-lg, 1.125rem);
56
+ font-weight: var(--nymbal-font-weight-bold, 700);
57
+ color: var(--nymbal-color-primary, #18181b);
58
+ text-decoration: none;
59
+ letter-spacing: -0.02em;
60
+ flex-shrink: 0;
61
+ }
62
+
63
+ .header-logo:hover {
64
+ color: var(--nymbal-color-accent, #2563eb);
65
+ }
66
+
67
+ /* Hamburger toggle */
68
+ .hamburger {
69
+ display: flex;
70
+ flex-direction: column;
71
+ justify-content: center;
72
+ gap: 4px;
73
+ width: 28px;
74
+ height: 28px;
75
+ cursor: pointer;
76
+ margin-left: auto;
77
+ padding: 2px;
78
+ }
79
+
80
+ .hamburger-line {
81
+ display: block;
82
+ width: 100%;
83
+ height: 2px;
84
+ background-color: var(--nymbal-color-primary, #18181b);
85
+ border-radius: 1px;
86
+ transition: transform var(--nymbal-transition-fast, 150ms) ease,
87
+ opacity var(--nymbal-transition-fast, 150ms) ease;
88
+ }
89
+
90
+ .nav-toggle:checked ~ .header-nav-wrapper {
91
+ display: flex;
92
+ }
93
+
94
+ .nav-toggle:checked ~ .hamburger .hamburger-line:nth-child(1) {
95
+ transform: translateY(6px) rotate(45deg);
96
+ }
97
+
98
+ .nav-toggle:checked ~ .hamburger .hamburger-line:nth-child(2) {
99
+ opacity: 0;
100
+ }
101
+
102
+ .nav-toggle:checked ~ .hamburger .hamburger-line:nth-child(3) {
103
+ transform: translateY(-6px) rotate(-45deg);
104
+ }
105
+
106
+ .header-nav-wrapper {
107
+ display: none;
108
+ flex-direction: column;
109
+ width: 100%;
110
+ gap: var(--nymbal-spacing-md, 1rem);
111
+ padding-block: var(--nymbal-spacing-sm, 0.5rem);
112
+ }
113
+
114
+ .header-nav {
115
+ display: flex;
116
+ flex-direction: column;
117
+ gap: var(--nymbal-spacing-xs, 0.25rem);
118
+ }
119
+
120
+ .nav-link {
121
+ display: block;
122
+ padding: var(--nymbal-spacing-xs, 0.25rem) var(--nymbal-spacing-sm, 0.5rem);
123
+ font-size: var(--nymbal-font-size-sm, 0.875rem);
124
+ font-weight: var(--nymbal-font-weight-medium, 500);
125
+ color: var(--nymbal-color-text, #18181b);
126
+ text-decoration: none;
127
+ border-radius: var(--nymbal-radius-sm, 0.25rem);
128
+ transition: background-color var(--nymbal-transition-fast, 150ms) ease,
129
+ color var(--nymbal-transition-fast, 150ms) ease;
130
+ }
131
+
132
+ .nav-link:hover {
133
+ background-color: var(--nymbal-color-border, #e4e4e7);
134
+ color: var(--nymbal-color-accent, #2563eb);
135
+ }
136
+
137
+ .header-actions {
138
+ display: flex;
139
+ align-items: center;
140
+ gap: var(--nymbal-spacing-sm, 0.5rem);
141
+ }
142
+
143
+ .header-account-link {
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ width: 36px;
148
+ height: 36px;
149
+ color: var(--nymbal-color-text, #18181b);
150
+ text-decoration: none;
151
+ border-radius: var(--nymbal-radius-full, 9999px);
152
+ transition: background-color var(--nymbal-transition-fast, 150ms) ease,
153
+ color var(--nymbal-transition-fast, 150ms) ease;
154
+ }
155
+
156
+ .header-account-link:hover {
157
+ background-color: var(--nymbal-color-border, #e4e4e7);
158
+ color: var(--nymbal-color-accent, #2563eb);
159
+ }
160
+
161
+ /* Desktop layout */
162
+ @media (min-width: 769px) {
163
+ .hamburger {
164
+ display: none;
165
+ }
166
+
167
+ .header-container {
168
+ flex-wrap: nowrap;
169
+ padding-block: var(--nymbal-spacing-md, 1rem);
170
+ }
171
+
172
+ .header-nav-wrapper {
173
+ display: flex;
174
+ flex-direction: row;
175
+ align-items: center;
176
+ justify-content: space-between;
177
+ width: auto;
178
+ flex: 1;
179
+ padding-block: 0;
180
+ gap: var(--nymbal-spacing-lg, 1.5rem);
181
+ }
182
+
183
+ .header-nav {
184
+ flex-direction: row;
185
+ gap: var(--nymbal-spacing-xs, 0.25rem);
186
+ }
187
+
188
+ .nav-link {
189
+ padding: var(--nymbal-spacing-xs, 0.25rem) var(--nymbal-spacing-sm, 0.5rem);
190
+ }
191
+ }
192
+ </style>
@@ -0,0 +1,17 @@
1
+ ---
2
+ import '../styles/pages/home.css'
3
+ ---
4
+
5
+ <section class="hero" data-testid="hero-section">
6
+ <div class="hero-content">
7
+ <h1 class="hero-title">Curated for quality. Built to last.</h1>
8
+ <p class="hero-subtitle">
9
+ Discover thoughtfully crafted products designed for everyday life.
10
+ Every item in our collection is selected for its quality, durability, and timeless design.
11
+ </p>
12
+ <div class="hero-actions">
13
+ <a href="/products" class="btn btn-primary">Shop Now</a>
14
+ <a href="/category/apparel" class="btn btn-secondary">Browse Categories</a>
15
+ </div>
16
+ </div>
17
+ </section>
@@ -0,0 +1,42 @@
1
+ ---
2
+ import '../styles/pages/pdp.css'
3
+
4
+ interface Props {
5
+ images: Array<{ url: string; alt: string }>
6
+ }
7
+
8
+ const { images } = Astro.props
9
+ const mainImage = images[0] ?? { url: 'https://picsum.photos/seed/placeholder/600/600', alt: 'Product image' }
10
+ ---
11
+
12
+ <div class="pdp-gallery" data-testid="product-gallery">
13
+ <img
14
+ src={mainImage.url}
15
+ alt={mainImage.alt}
16
+ class="pdp-main-image"
17
+ id="gallery-main-image"
18
+ width="600"
19
+ height="600"
20
+ />
21
+ {images.length > 1 && (
22
+ <div class="pdp-thumbnails" id="gallery-thumbnails">
23
+ {images.map((img, i) => (
24
+ <img
25
+ src={img.url}
26
+ alt={img.alt}
27
+ class={`pdp-thumbnail ${i === 0 ? 'active' : ''}`}
28
+ data-index={i}
29
+ data-url={img.url}
30
+ data-alt={img.alt}
31
+ width="80"
32
+ height="80"
33
+ loading="lazy"
34
+ />
35
+ ))}
36
+ </div>
37
+ )}
38
+ </div>
39
+
40
+ <script>
41
+ import '../scripts/image-gallery.js'
42
+ </script>
@@ -0,0 +1,39 @@
1
+ ---
2
+ import '../styles/pages/home.css'
3
+ ---
4
+
5
+ <section class="newsletter section" data-testid="newsletter-signup">
6
+ <div class="container">
7
+ <h2 class="newsletter-title">Stay in the loop</h2>
8
+ <p class="newsletter-subtitle">Subscribe for new arrivals, exclusive offers, and more.</p>
9
+ <form class="newsletter-form" id="newsletter-form" data-testid="newsletter-form">
10
+ <input
11
+ type="email"
12
+ name="email"
13
+ placeholder="Your email address"
14
+ required
15
+ aria-label="Email address"
16
+ data-testid="newsletter-email"
17
+ />
18
+ <button type="submit" class="btn btn-primary" data-testid="newsletter-submit">
19
+ Subscribe
20
+ </button>
21
+ </form>
22
+ <p class="newsletter-success" id="newsletter-success" style="display:none; margin-top: var(--nymbal-spacing-md, 1rem); font-size: var(--nymbal-font-size-sm, 0.875rem); color: rgba(255,255,255,0.8);">
23
+ Thank you for subscribing!
24
+ </p>
25
+ </div>
26
+ </section>
27
+
28
+ <script>
29
+ const form = document.getElementById('newsletter-form') as HTMLFormElement | null
30
+ const success = document.getElementById('newsletter-success')
31
+
32
+ form?.addEventListener('submit', (e) => {
33
+ e.preventDefault()
34
+ if (form && success) {
35
+ form.style.display = 'none'
36
+ success.style.display = 'block'
37
+ }
38
+ })
39
+ </script>