@stacksjs/defaults 0.70.54

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 (286) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +2 -0
  3. package/package.json +20 -0
  4. package/resources/assets/fonts/013717e9.woff +0 -0
  5. package/resources/assets/fonts/03355817.woff2 +0 -0
  6. package/resources/assets/fonts/0380de7e.woff2 +0 -0
  7. package/resources/assets/fonts/0ba5d32a.woff +0 -0
  8. package/resources/assets/fonts/0bd57ddf.woff2 +0 -0
  9. package/resources/assets/fonts/19de0a60.woff2 +0 -0
  10. package/resources/assets/fonts/20b87e84.woff +0 -0
  11. package/resources/assets/fonts/4bbc67e1.woff +0 -0
  12. package/resources/assets/fonts/576fb0d7.woff +0 -0
  13. package/resources/assets/fonts/5ea10ee5.woff2 +0 -0
  14. package/resources/assets/fonts/6413fc0a.woff +0 -0
  15. package/resources/assets/fonts/72c114cf.woff +0 -0
  16. package/resources/assets/fonts/73c57e73.woff2 +0 -0
  17. package/resources/assets/fonts/813dca6c.woff +0 -0
  18. package/resources/assets/fonts/8a9b0995.woff2 +0 -0
  19. package/resources/assets/fonts/90b4832f.woff +0 -0
  20. package/resources/assets/fonts/9394ab85.woff2 +0 -0
  21. package/resources/assets/fonts/9b3c7d7f.woff2 +0 -0
  22. package/resources/assets/fonts/Monaco.ttf +0 -0
  23. package/resources/assets/fonts/b1b36d74.woff +0 -0
  24. package/resources/assets/fonts/b5d74798.woff2 +0 -0
  25. package/resources/assets/fonts/b9f81a4d.woff +0 -0
  26. package/resources/assets/fonts/c9784008.woff2 +0 -0
  27. package/resources/assets/fonts/cf692227.woff +0 -0
  28. package/resources/assets/fonts/d1c6ab51.woff2 +0 -0
  29. package/resources/assets/fonts/e2eb62fc.woff +0 -0
  30. package/resources/assets/fonts/fe21f8d8.woff2 +0 -0
  31. package/resources/assets/fonts/nps/CampmateScript-Regular.woff2 +0 -0
  32. package/resources/assets/fonts/nps/NPS_2026-variable.woff2 +0 -0
  33. package/resources/assets/fonts/nps/RedwoodSerif-Regular.woff2 +0 -0
  34. package/resources/assets/fonts/nps/SequoiaSans-Regular.woff2 +0 -0
  35. package/resources/assets/fonts/nps/Switchback-Regular.woff2 +0 -0
  36. package/resources/assets/images/app-icon.png +0 -0
  37. package/resources/assets/images/bg-img.png +0 -0
  38. package/resources/assets/images/park-ridge.svg +15 -0
  39. package/resources/assets/images/topography.svg +1 -0
  40. package/resources/assets/scripts/main.ts +2153 -0
  41. package/resources/assets/styles/README.md +19 -0
  42. package/resources/assets/styles/docs/main.css +1 -0
  43. package/resources/assets/styles/docs/overrides.css +1 -0
  44. package/resources/assets/styles/docs/vars.css +8 -0
  45. package/resources/assets/styles/main.css +1419 -0
  46. package/resources/components/Button.stx +70 -0
  47. package/resources/components/Buttons/BaseButton.stx +39 -0
  48. package/resources/components/Buttons/Counter.stx +28 -0
  49. package/resources/components/Buttons/ToggleDark.stx +11 -0
  50. package/resources/components/Container.stx +10 -0
  51. package/resources/components/Dashboard/.gitkeep +0 -0
  52. package/resources/components/Dashboard/Auth/ForgotPassword.stx +127 -0
  53. package/resources/components/Dashboard/Auth/Login.stx +212 -0
  54. package/resources/components/Dashboard/Auth/Register.stx +306 -0
  55. package/resources/components/Dashboard/Avatar.stx +123 -0
  56. package/resources/components/Dashboard/Billing/ActivePlan.stx +57 -0
  57. package/resources/components/Dashboard/Billing/CardBrands.stx +32 -0
  58. package/resources/components/Dashboard/Billing/CardForm.stx +34 -0
  59. package/resources/components/Dashboard/Billing/OneTimePayment.stx +28 -0
  60. package/resources/components/Dashboard/Billing/PaymentForm.stx +33 -0
  61. package/resources/components/Dashboard/Billing/PaymentMethod.stx +66 -0
  62. package/resources/components/Dashboard/Billing/PaymentMethodList.stx +52 -0
  63. package/resources/components/Dashboard/Billing/Plans.stx +136 -0
  64. package/resources/components/Dashboard/Buttons/AppButton.stx +48 -0
  65. package/resources/components/Dashboard/Buttons/IconButton.stx +20 -0
  66. package/resources/components/Dashboard/Card.stx +78 -0
  67. package/resources/components/Dashboard/Chart.stx +75 -0
  68. package/resources/components/Dashboard/CodeEditor.stx +41 -0
  69. package/resources/components/Dashboard/Commerce/BulkActionBar.stx +70 -0
  70. package/resources/components/Dashboard/Commerce/CategoriesTable.stx +133 -0
  71. package/resources/components/Dashboard/Commerce/CouponsTable.stx +161 -0
  72. package/resources/components/Dashboard/Commerce/CustomersTable.stx +142 -0
  73. package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +80 -0
  74. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryTable.stx +85 -0
  75. package/resources/components/Dashboard/Commerce/Delivery/DriversTable.stx +99 -0
  76. package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysTable.stx +92 -0
  77. package/resources/components/Dashboard/Commerce/Delivery/LicenseTemplatesTable.stx +88 -0
  78. package/resources/components/Dashboard/Commerce/Delivery/Pagination.stx +122 -0
  79. package/resources/components/Dashboard/Commerce/Delivery/SearchFilter.stx +23 -0
  80. package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsTable.stx +80 -0
  81. package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesTable.stx +59 -0
  82. package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesTable.stx +71 -0
  83. package/resources/components/Dashboard/Commerce/Delivery/TabNavigation.stx +36 -0
  84. package/resources/components/Dashboard/Commerce/Forms/CouponsForm.stx +191 -0
  85. package/resources/components/Dashboard/Commerce/OrdersTable.stx +131 -0
  86. package/resources/components/Dashboard/Commerce/ProductsTable.stx +170 -0
  87. package/resources/components/Dashboard/CreateRecordModal.stx +363 -0
  88. package/resources/components/Dashboard/DashboardLayout.stx +155 -0
  89. package/resources/components/Dashboard/Deployments/ActivityFeed.stx +54 -0
  90. package/resources/components/Dashboard/Deployments/DeployScript.stx +23 -0
  91. package/resources/components/Dashboard/Deployments/DeploymentHistory.stx +97 -0
  92. package/resources/components/Dashboard/Deployments/DeploymentList.stx +94 -0
  93. package/resources/components/Dashboard/Deployments/LiveTerminalOutput.stx +18 -0
  94. package/resources/components/Dashboard/Elements/Dropdown.stx +57 -0
  95. package/resources/components/Dashboard/Email/EmailActivityChart.stx +111 -0
  96. package/resources/components/Dashboard/Email/EmailCompose.stx +72 -0
  97. package/resources/components/Dashboard/Email/EmailDetail.stx +105 -0
  98. package/resources/components/Dashboard/Email/EmailList.stx +117 -0
  99. package/resources/components/Dashboard/Email/EmailSearch.stx +40 -0
  100. package/resources/components/Dashboard/Email/EmailSidebar.stx +133 -0
  101. package/resources/components/Dashboard/GlobalSearch.stx +124 -0
  102. package/resources/components/Dashboard/MobileSidebar.stx +47 -0
  103. package/resources/components/Dashboard/Modals/BaseModal.stx +50 -0
  104. package/resources/components/Dashboard/Modals/Popups/Alert.stx +112 -0
  105. package/resources/components/Dashboard/Modals/Popups/Toast.stx +70 -0
  106. package/resources/components/Dashboard/Modals/ToastWrapper.stx +35 -0
  107. package/resources/components/Dashboard/Monitoring/ErrorDetailModal.stx +228 -0
  108. package/resources/components/Dashboard/Monitoring/ErrorsTable.stx +184 -0
  109. package/resources/components/Dashboard/Navbar.stx +194 -0
  110. package/resources/components/Dashboard/NavbarModern.stx +188 -0
  111. package/resources/components/Dashboard/NotificationErrorModal.stx +105 -0
  112. package/resources/components/Dashboard/NotificationStatusBadge.stx +20 -0
  113. package/resources/components/Dashboard/Pagination.stx +90 -0
  114. package/resources/components/Dashboard/Queue/QueueTable.stx +127 -0
  115. package/resources/components/Dashboard/Settings/Forms/AIForm.stx +126 -0
  116. package/resources/components/Dashboard/Settings/Forms/AnalyticsForm.stx +52 -0
  117. package/resources/components/Dashboard/Settings/Forms/AppForm.stx +157 -0
  118. package/resources/components/Dashboard/Settings/Forms/CLIForm.stx +66 -0
  119. package/resources/components/Dashboard/Settings/Forms/CacheForm.stx +253 -0
  120. package/resources/components/Dashboard/Settings/Forms/CloudForm.stx +441 -0
  121. package/resources/components/Dashboard/Settings/Forms/DNSForm.stx +307 -0
  122. package/resources/components/Dashboard/Settings/Forms/DatabaseForm.stx +168 -0
  123. package/resources/components/Dashboard/Settings/Forms/EmailForm.stx +100 -0
  124. package/resources/components/Dashboard/Settings/Forms/FileSystemForm.stx +259 -0
  125. package/resources/components/Dashboard/Settings/Forms/HashingForm.stx +119 -0
  126. package/resources/components/Dashboard/Settings/Forms/LibraryForm.stx +389 -0
  127. package/resources/components/Dashboard/Settings/Forms/QueueForm.stx +219 -0
  128. package/resources/components/Dashboard/Settings/Forms/SearchEngineForm.stx +48 -0
  129. package/resources/components/Dashboard/Settings/Forms/SecurityForm.stx +143 -0
  130. package/resources/components/Dashboard/Settings/Forms/ServicesForm.stx +194 -0
  131. package/resources/components/Dashboard/Settings/Forms/StorageForm.stx +191 -0
  132. package/resources/components/Dashboard/Settings/Forms/TeamsForm.stx +33 -0
  133. package/resources/components/Dashboard/Settings/SettingsFormManager.stx +181 -0
  134. package/resources/components/Dashboard/SettingsHeader.stx +88 -0
  135. package/resources/components/Dashboard/SettingsSidebar.stx +234 -0
  136. package/resources/components/Dashboard/Sidebar.stx +23 -0
  137. package/resources/components/Dashboard/SidebarModern.stx +249 -0
  138. package/resources/components/Dashboard/Skeleton/LoadingCard.stx +15 -0
  139. package/resources/components/Dashboard/Skeleton/LoadingDetails.stx +26 -0
  140. package/resources/components/Dashboard/Table.stx +82 -0
  141. package/resources/components/Dashboard/TerminalContainer.stx +97 -0
  142. package/resources/components/Dashboard/Tooltip.stx +166 -0
  143. package/resources/components/Dashboard/Transaction/index.stx +88 -0
  144. package/resources/components/Dashboard/UI/ActivityTable.stx +54 -0
  145. package/resources/components/Dashboard/UI/Avatar.stx +102 -0
  146. package/resources/components/Dashboard/UI/Badge.stx +66 -0
  147. package/resources/components/Dashboard/UI/Button.stx +73 -0
  148. package/resources/components/Dashboard/UI/Card.stx +51 -0
  149. package/resources/components/Dashboard/UI/ChartCard.stx +36 -0
  150. package/resources/components/Dashboard/UI/Checkbox.stx +74 -0
  151. package/resources/components/Dashboard/UI/ConfirmDialog.stx +74 -0
  152. package/resources/components/Dashboard/UI/DataTable.stx +45 -0
  153. package/resources/components/Dashboard/UI/Dropdown.stx +72 -0
  154. package/resources/components/Dashboard/UI/DropdownItem.stx +80 -0
  155. package/resources/components/Dashboard/UI/EmptyState.stx +50 -0
  156. package/resources/components/Dashboard/UI/FilterBar.stx +48 -0
  157. package/resources/components/Dashboard/UI/Input.stx +77 -0
  158. package/resources/components/Dashboard/UI/Modal.stx +75 -0
  159. package/resources/components/Dashboard/UI/PageHeader.stx +69 -0
  160. package/resources/components/Dashboard/UI/PageLayout.stx +84 -0
  161. package/resources/components/Dashboard/UI/Pagination.stx +126 -0
  162. package/resources/components/Dashboard/UI/QuickLinks.stx +31 -0
  163. package/resources/components/Dashboard/UI/RouterLink.stx +31 -0
  164. package/resources/components/Dashboard/UI/Select.stx +84 -0
  165. package/resources/components/Dashboard/UI/ServiceHealth.stx +38 -0
  166. package/resources/components/Dashboard/UI/StatsCard.stx +79 -0
  167. package/resources/components/Dashboard/UI/Table.stx +143 -0
  168. package/resources/components/Dashboard/UI/Tabs.stx +45 -0
  169. package/resources/components/Dashboard/UI/Textarea.stx +93 -0
  170. package/resources/components/Dashboard/UI/Toggle.stx +84 -0
  171. package/resources/components/Dashboard/UI/WindowControls.stx +135 -0
  172. package/resources/components/Dashboard/Widget.stx +89 -0
  173. package/resources/components/Dashboard/WindowControls.stx +119 -0
  174. package/resources/components/Dashboard/index.ts +106 -0
  175. package/resources/components/Docs/Demo/ComboboxCode.md +114 -0
  176. package/resources/components/Docs/Demo/ComboboxDemo.stx +91 -0
  177. package/resources/components/Docs/Demo/DialogCode.md +42 -0
  178. package/resources/components/Docs/Demo/DialogDemo.stx +76 -0
  179. package/resources/components/Docs/Demo/DocsPlayground.stx +39 -0
  180. package/resources/components/Docs/Demo/DropdownCode.md +109 -0
  181. package/resources/components/Docs/Demo/DropdownDemo.stx +82 -0
  182. package/resources/components/Docs/Demo/Hero.stx +100 -0
  183. package/resources/components/Docs/Demo/ListboxCode.md +87 -0
  184. package/resources/components/Docs/Demo/ListboxDemo.stx +59 -0
  185. package/resources/components/Docs/Demo/NotificationCode.md +173 -0
  186. package/resources/components/Docs/Demo/NotificationDemo.stx +99 -0
  187. package/resources/components/Docs/Demo/PopoverCode.md +161 -0
  188. package/resources/components/Docs/Demo/PopoverDemo.stx +96 -0
  189. package/resources/components/Docs/Demo/RadioGroupCode.md +98 -0
  190. package/resources/components/Docs/Demo/RadioGroupDemo.stx +53 -0
  191. package/resources/components/Docs/Demo/StepperCode.md +216 -0
  192. package/resources/components/Docs/Demo/StepperDemo.stx +137 -0
  193. package/resources/components/Docs/Demo/SwitchCode.md +24 -0
  194. package/resources/components/Docs/Demo/SwitchDemo.stx +28 -0
  195. package/resources/components/Docs/Home.stx +7 -0
  196. package/resources/components/Docs/HomeContributors.stx +23 -0
  197. package/resources/components/Docs/HomeSponsors.stx +17 -0
  198. package/resources/components/Docs/HomeTeam.stx +18 -0
  199. package/resources/components/Docs/TeamMember.stx +79 -0
  200. package/resources/components/Email/EmailButton.stx +32 -0
  201. package/resources/components/Email/EmailDivider.stx +20 -0
  202. package/resources/components/Email/EmailImage.stx +24 -0
  203. package/resources/components/Email/EmailLayout.stx +55 -0
  204. package/resources/components/Email/EmailLink.stx +22 -0
  205. package/resources/components/Email/EmailSection.stx +20 -0
  206. package/resources/components/Email/EmailText.stx +30 -0
  207. package/resources/components/Forum/ForumBreadcrumb.stx +38 -0
  208. package/resources/components/Forum/ForumCategory.stx +16 -0
  209. package/resources/components/Forum/ForumCategoryItem.stx +46 -0
  210. package/resources/components/Forum/ForumLayout.stx +36 -0
  211. package/resources/components/Forum/ForumPagination.stx +39 -0
  212. package/resources/components/Forum/ForumPost.stx +71 -0
  213. package/resources/components/Forum/ForumReplyForm.stx +76 -0
  214. package/resources/components/Forum/ForumSidebar.stx +86 -0
  215. package/resources/components/Forum/ForumTopicItem.stx +77 -0
  216. package/resources/components/Forum/ForumTopicList.stx +23 -0
  217. package/resources/components/HelloWorld.stx +253 -0
  218. package/resources/components/Logo.stx +10 -0
  219. package/resources/components/Marketing/CallToAction.stx +30 -0
  220. package/resources/components/Marketing/ComingSoon.stx +155 -0
  221. package/resources/components/Marketing/Faqs.stx +99 -0
  222. package/resources/components/Marketing/Feature.stx +32 -0
  223. package/resources/components/Marketing/Fields.stx +28 -0
  224. package/resources/components/Marketing/Footer.stx +33 -0
  225. package/resources/components/Marketing/Header.stx +51 -0
  226. package/resources/components/Marketing/Hero.stx +46 -0
  227. package/resources/components/Marketing/Layout.stx +26 -0
  228. package/resources/components/Marketing/NavLink.stx +13 -0
  229. package/resources/components/Marketing/Plan.stx +43 -0
  230. package/resources/components/Marketing/Pricing.stx +120 -0
  231. package/resources/components/Marketing/PrimaryFeatures.stx +80 -0
  232. package/resources/components/Marketing/PrimaryFeatures2.stx +98 -0
  233. package/resources/components/Marketing/SecondaryFeatures.stx +80 -0
  234. package/resources/components/Marketing/Testimonials.stx +120 -0
  235. package/resources/components/Pagination.stx +149 -0
  236. package/resources/components/Playground.stx +9 -0
  237. package/resources/components/README.md +87 -0
  238. package/resources/components/Storefront/AnnouncementBar.stx +17 -0
  239. package/resources/components/Storefront/CartDrawer.stx +227 -0
  240. package/resources/components/Storefront/CheckoutSteps.stx +50 -0
  241. package/resources/components/Storefront/Footer.stx +81 -0
  242. package/resources/components/Storefront/Header.stx +66 -0
  243. package/resources/components/Storefront/OrderSummary.stx +79 -0
  244. package/resources/components/Storefront/ProductCard.stx +63 -0
  245. package/resources/components/Storefront/ProductGallery.stx +88 -0
  246. package/resources/components/Storefront/QuantitySelector.stx +97 -0
  247. package/resources/components/index.ts +13 -0
  248. package/resources/emails/email-verification.stx +46 -0
  249. package/resources/emails/layouts/base.html +113 -0
  250. package/resources/emails/password-changed.stx +55 -0
  251. package/resources/emails/password-reset.stx +43 -0
  252. package/resources/emails/subscription-confirmation.stx +99 -0
  253. package/resources/emails/welcome.stx +48 -0
  254. package/resources/functions/README.md +28 -0
  255. package/resources/functions/counter.ts +13 -0
  256. package/resources/functions/dark.ts +4 -0
  257. package/resources/functions/dashboard/analytics.ts +238 -0
  258. package/resources/functions/dashboard/config-io.ts +316 -0
  259. package/resources/functions/dashboard/data.ts +375 -0
  260. package/resources/functions/dashboard/library.ts +250 -0
  261. package/resources/functions/dashboard/sidebar.ts +537 -0
  262. package/resources/functions/dashboard/system.ts +150 -0
  263. package/resources/layouts/403.stx +188 -0
  264. package/resources/layouts/404.stx +34 -0
  265. package/resources/layouts/README.md +14 -0
  266. package/resources/layouts/dashboard/default.stx +20 -0
  267. package/resources/layouts/dashboard/guest.stx +10 -0
  268. package/resources/layouts/default.stx +3 -0
  269. package/resources/layouts/maintenance.stx +11 -0
  270. package/resources/layouts/storefront.stx +73 -0
  271. package/resources/layouts/system-tray/default.stx +3 -0
  272. package/resources/modules/.gitkeep +0 -0
  273. package/resources/plugins/.gitkeep +0 -0
  274. package/resources/plugins/preloader.ts +308 -0
  275. package/resources/types/README.md +3 -0
  276. package/resources/views/cart.stx +149 -0
  277. package/resources/views/checkout/contact.stx +101 -0
  278. package/resources/views/checkout/payment.stx +114 -0
  279. package/resources/views/checkout/shipping.stx +120 -0
  280. package/resources/views/coming-soon.stx +9 -0
  281. package/resources/views/dashboard/custom-page.stx +45 -0
  282. package/resources/views/dashboard/index.stx +24 -0
  283. package/resources/views/emails/welcome.stx +0 -0
  284. package/resources/views/errors/tester.stx +26 -0
  285. package/resources/views/index.stx +2865 -0
  286. package/resources/views/orders/[id].stx +130 -0
@@ -0,0 +1,81 @@
1
+ <script>
2
+ interface FooterColumn {
3
+ title: string
4
+ links: { href: string, label: string }[]
5
+ }
6
+
7
+ interface FooterProps {
8
+ brandName?: string
9
+ brandMark?: string
10
+ tagline?: string
11
+ columns?: FooterColumn[]
12
+ byline?: string
13
+ techByline?: string
14
+ }
15
+
16
+ const {
17
+ brandName = 'Stacks Store',
18
+ brandMark = 's.',
19
+ tagline = 'One ingredient. Nothing else. Real food for real animals.',
20
+ columns = [
21
+ {
22
+ title: 'Shop',
23
+ links: [
24
+ { href: '/products', label: 'All treats' },
25
+ { href: '/cart', label: 'Cart' },
26
+ ],
27
+ },
28
+ {
29
+ title: 'Brand',
30
+ links: [
31
+ { href: '/about', label: 'Our standard' },
32
+ { href: '/about#sourcing', label: 'Sourcing' },
33
+ { href: '/about#testing', label: 'Testing' },
34
+ ],
35
+ },
36
+ {
37
+ title: 'Help',
38
+ links: [
39
+ { href: '/contact', label: 'Contact' },
40
+ { href: '/faq', label: 'FAQ' },
41
+ { href: '/shipping', label: 'Shipping & returns' },
42
+ ],
43
+ },
44
+ ],
45
+ byline = `© ${new Date().getFullYear()} Stacks Store. All rights reserved.`,
46
+ techByline = 'Built on Stacks · Bun · TypeScript · STX',
47
+ } = defineProps<FooterProps>()
48
+ </script>
49
+
50
+ <footer class="mt-24 bg-stone-100 border-stone-200 border-t">
51
+ <div class="grid gap-8 grid-cols-2 md:gap-12 md:grid-cols-4 mx-auto px-6 py-14 max-w-6xl text-sm">
52
+ <div class="col-span-2 md:col-span-1">
53
+ <a href="/" class="inline-flex gap-2 items-center rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-100 focus-visible:ring-stone-900 group">
54
+ <span class="inline-flex items-center justify-center h-9 w-9 font-bold text-sm text-stone-50 bg-stone-900 rounded-full transition-transform group-hover:scale-105">{{ brandMark }}</span>
55
+ <span class="font-bold text-base text-stone-900 tracking-tight">{{ brandName }}</span>
56
+ </a>
57
+ <p class="mt-4 leading-relaxed text-stone-600">{{ tagline }}</p>
58
+ </div>
59
+ @foreach (columns as col)
60
+ <div>
61
+ <p class="mb-3 font-semibold text-stone-900 text-xs tracking-wider uppercase">{{ col.title }}</p>
62
+ <ul class="space-y-2.5 text-stone-600">
63
+ @foreach (col.links as link)
64
+ <li>
65
+ <a
66
+ href="{{ link.href }}"
67
+ class="underline-offset-4 focus-visible:text-stone-900 focus-visible:underline hover:text-stone-900 rounded-sm focus-visible:outline-none transition-colors"
68
+ >{{ link.label }}</a>
69
+ </li>
70
+ @endforeach
71
+ </ul>
72
+ </div>
73
+ @endforeach
74
+ </div>
75
+ <div class="border-stone-200 border-t">
76
+ <div class="flex flex-col gap-2 items-center justify-between md:flex-row mx-auto px-6 py-6 max-w-6xl text-stone-500 text-xs">
77
+ <p>{{ byline }}</p>
78
+ <p>{{ techByline }}</p>
79
+ </div>
80
+ </div>
81
+ </footer>
@@ -0,0 +1,66 @@
1
+ <script>
2
+ interface NavLink {
3
+ href: string
4
+ label: string
5
+ }
6
+
7
+ interface HeaderProps {
8
+ brandName?: string
9
+ brandMark?: string
10
+ links?: NavLink[]
11
+ cartCount?: number
12
+ }
13
+
14
+ const {
15
+ brandName = 'Stacks Store',
16
+ brandMark = 's.',
17
+ links = [
18
+ { href: '/products', label: 'Shop' },
19
+ { href: '/about', label: 'Our Standard' },
20
+ ],
21
+ cartCount = 0,
22
+ } = defineProps<HeaderProps>()
23
+ </script>
24
+
25
+ <header class="sticky top-0 z-30 bg-stone-50/95 border-b border-stone-200 backdrop-blur">
26
+ <div class="flex gap-6 items-center justify-between mx-auto px-6 py-5 max-w-6xl">
27
+ <a
28
+ href="/"
29
+ data-stx-link
30
+ data-stx-exact-active-class="!text-stone-900"
31
+ class="flex gap-2 items-center rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-50 focus-visible:ring-stone-900 group"
32
+ aria-label="{{ brandName }} — home"
33
+ >
34
+ <span class="inline-flex items-center justify-center h-9 w-9 font-bold text-sm text-stone-50 bg-stone-900 rounded-full transition-transform group-hover:scale-105">{{ brandMark }}</span>
35
+ <span class="font-bold text-xl tracking-tight">{{ brandName }}</span>
36
+ </a>
37
+
38
+ <nav class="hidden md:flex gap-8 items-center font-medium text-sm text-stone-600" data-stx-nav>
39
+ @foreach (links as link)
40
+ <a
41
+ href="{{ link.href }}"
42
+ data-stx-link
43
+ data-stx-active-class="text-stone-900"
44
+ class="relative focus-visible:text-stone-900 hover:text-stone-900 focus-visible:outline-none transition-colors [&.text-stone-900]:after:-bottom-2 [&.text-stone-900]:after:absolute [&.text-stone-900]:after:bg-stone-900 [&.text-stone-900]:after:content-[''] [&.text-stone-900]:after:h-0.5 [&.text-stone-900]:after:left-0 [&.text-stone-900]:after:right-0 [&.text-stone-900]:after:rounded-full"
45
+ >{{ link.label }}</a>
46
+ @endforeach
47
+ </nav>
48
+
49
+ <div class="flex gap-3 items-center">
50
+ <a
51
+ href="/cart"
52
+ data-stx-link
53
+ data-stx-active-class="text-stone-900 border-stone-900"
54
+ class="inline-flex relative gap-2 items-center px-4 py-2 font-medium text-sm text-stone-700 hover:text-stone-900 border border-stone-300 rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-50 focus-visible:ring-stone-900 hover:border-stone-900 transition-colors"
55
+ aria-label="View cart"
56
+ >
57
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 00-3 3h15.75m-12.75-3h11.218c.51 0 .956-.343 1.087-.835l1.872-7.024A1.125 1.125 0 0019.797 5.25H5.106M7.5 14.25L5.106 5.25M6 20.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm12.75 0a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" /></svg>
58
+ <span>Cart</span>
59
+ <span
60
+ data-stx-cart-count
61
+ class="inline-flex items-center justify-center h-5 min-w-[20px] px-1.5 rounded-full bg-stone-900 text-[10px] font-bold text-stone-50 {{ cartCount > 0 ? '' : 'hidden' }}"
62
+ >{{ cartCount }}</span>
63
+ </a>
64
+ </div>
65
+ </div>
66
+ </header>
@@ -0,0 +1,79 @@
1
+ <script>
2
+ interface OrderSummaryItem {
3
+ name: string
4
+ qty: number
5
+ /** Pre-formatted line total label, e.g. "$22.00". Format at the call site. */
6
+ lineTotalLabel: string
7
+ }
8
+
9
+ interface OrderSummaryProps {
10
+ items?: OrderSummaryItem[]
11
+ /** Pre-formatted price labels — STX defineProps numerics aren't in scope
12
+ * for SSR template evaluation, so .toFixed() etc. has to happen at the
13
+ * caller's <script server> block. */
14
+ subtotalLabel: string
15
+ shippingLabel: string
16
+ totalLabel: string
17
+ /** Optional, pre-formatted "Add $X.XX more for free shipping." nudge.
18
+ * When empty/omitted the nudge is hidden — let the caller decide. */
19
+ freeShippingNote?: string
20
+ showItems?: boolean
21
+ ctaHref?: string
22
+ ctaLabel?: string
23
+ secondaryHref?: string
24
+ secondaryLabel?: string
25
+ sticky?: boolean
26
+ }
27
+
28
+ const {
29
+ items = [],
30
+ subtotalLabel,
31
+ shippingLabel,
32
+ totalLabel,
33
+ freeShippingNote = '',
34
+ showItems = false,
35
+ ctaHref = '',
36
+ ctaLabel = '',
37
+ secondaryHref = '',
38
+ secondaryLabel = '',
39
+ sticky = false,
40
+ } = defineProps<OrderSummaryProps>()
41
+ </script>
42
+
43
+ <aside class="rounded-2xl border border-stone-200 p-6 {{ sticky ? 'lg:sticky lg:top-28' : '' }}">
44
+ <p class="font-semibold text-stone-500 text-xs tracking-wider uppercase">Order summary</p>
45
+
46
+ @if (showItems && items.length > 0)
47
+ <ul class="mt-4 space-y-2 text-sm">
48
+ @foreach (items as item)
49
+ <li class="flex gap-3 justify-between">
50
+ <span class="text-stone-700">{{ item.qty }}× {{ item.name }}</span>
51
+ <span class="font-medium text-stone-900">{{ item.lineTotalLabel }}</span>
52
+ </li>
53
+ @endforeach
54
+ </ul>
55
+ <hr class="my-4 border-stone-200">
56
+ @endif
57
+
58
+ <dl class="space-y-3 text-sm {{ showItems ? '' : 'mt-4' }}">
59
+ <div class="flex justify-between"><dt class="text-stone-600">Subtotal</dt><dd class="font-semibold text-stone-900">{{ subtotalLabel }}</dd></div>
60
+ <div class="flex justify-between"><dt class="text-stone-600">Shipping</dt><dd class="font-semibold text-stone-900">{{ shippingLabel }}</dd></div>
61
+ @if (freeShippingNote)
62
+ <p class="px-3 py-2 text-emerald-700 text-xs bg-emerald-50 rounded-lg">{{ freeShippingNote }}</p>
63
+ @endif
64
+ </dl>
65
+
66
+ <hr class="my-4 border-stone-200">
67
+
68
+ <div class="flex justify-between text-base">
69
+ <p class="font-semibold text-stone-900">Total</p>
70
+ <p class="font-bold text-stone-900">{{ totalLabel }}</p>
71
+ </div>
72
+
73
+ @if (ctaHref && ctaLabel)
74
+ <a href="{{ ctaHref }}" class="block mt-6 px-6 py-3 w-full font-semibold text-center text-sm text-stone-50 bg-stone-900 hover:bg-stone-800 rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-50 focus-visible:ring-stone-900 transition-colors">{{ ctaLabel }}</a>
75
+ @endif
76
+ @if (secondaryHref && secondaryLabel)
77
+ <a href="{{ secondaryHref }}" class="block mt-3 px-6 py-3 w-full font-semibold text-center text-sm text-stone-900 border border-stone-300 rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-50 focus-visible:ring-stone-900 hover:border-stone-900 transition-colors">{{ secondaryLabel }}</a>
78
+ @endif
79
+ </aside>
@@ -0,0 +1,63 @@
1
+ <script>
2
+ interface ProductCardProps {
3
+ slug: string
4
+ name: string
5
+ /** Pre-formatted price label, e.g. "$22.00". Format at the call site
6
+ * — STX defineProps values aren't in scope for SSR template evaluation. */
7
+ priceLabel: string
8
+ image: string
9
+ ingredient?: string
10
+ weight?: string
11
+ badge?: string
12
+ speciesLabel?: string
13
+ }
14
+
15
+ const {
16
+ slug,
17
+ name,
18
+ priceLabel,
19
+ image,
20
+ ingredient = '',
21
+ weight = '',
22
+ badge = '',
23
+ speciesLabel = '',
24
+ } = defineProps<ProductCardProps>()
25
+ </script>
26
+
27
+ <a href="/products/{{ slug }}" class="block overflow-hidden bg-stone-50 border border-stone-200 rounded-2xl hover:border-stone-900 transition-colors group">
28
+ <div class="aspect-square overflow-hidden relative bg-stone-200">
29
+ {{-- decoding=async + loading=lazy so a long catalog grid below the fold
30
+ doesn't block first paint, and explicit width/height (matched to a
31
+ square aspect-ratio container above) keep CLS at 0 while images
32
+ resolve. The card is below the fold on every page that uses it
33
+ (home featured, /products grid) so lazy is always safe. --}}
34
+ <img
35
+ src="{{ image }}"
36
+ alt="{{ name }}"
37
+ width="600"
38
+ height="600"
39
+ loading="lazy"
40
+ decoding="async"
41
+ class="object-cover h-full w-full duration-500 transition-transform group-hover:scale-105"
42
+ >
43
+ @if (badge)
44
+ <span class="absolute left-3 top-3 px-2 py-0.5 font-semibold text-[10px] text-stone-900 tracking-wider uppercase bg-stone-50 rounded-full">{{ badge }}</span>
45
+ @endif
46
+ </div>
47
+ <div class="p-4">
48
+ <div class="flex items-center justify-between">
49
+ <span class="font-semibold text-[10px] text-stone-500 tracking-wider uppercase">{{ speciesLabel }}</span>
50
+ @if (weight)
51
+ <span class="text-stone-500 text-xs">{{ weight }}</span>
52
+ @endif
53
+ </div>
54
+ <h3 class="mt-2 font-semibold leading-snug text-stone-900">{{ name }}</h3>
55
+ @if (ingredient)
56
+ <p class="mt-1 text-stone-500 text-xs">{{ ingredient }}</p>
57
+ @endif
58
+ <div class="flex items-center justify-between mt-3">
59
+ <p class="font-bold text-stone-900">{{ priceLabel }}</p>
60
+ <span class="font-medium text-stone-500 text-xs group-hover:text-stone-900">View →</span>
61
+ </div>
62
+ </div>
63
+ </a>
@@ -0,0 +1,88 @@
1
+ <script>
2
+ interface ProductGalleryProps {
3
+ /** Pre-resolved URL for the hero image. STX SSR doesn't reliably
4
+ * evaluate `images[0]` against a `defineProps` array at template-
5
+ * render time, which left the rendered <img src=""> empty when we
6
+ * tried to derive `mainImage = images[0]` inside the component.
7
+ * Format at the call site, the way ProductCard takes priceLabel. */
8
+ image: string
9
+ /** Optional secondary images for thumbnails. The first thumb mirrors
10
+ * the hero so we don't show the same picture twice. */
11
+ thumbs?: string[]
12
+ alt?: string
13
+ }
14
+
15
+ const {
16
+ image = '',
17
+ thumbs = [],
18
+ alt = '',
19
+ } = defineProps<ProductGalleryProps>()
20
+ </script>
21
+
22
+ <div class="space-y-4" data-stx-gallery>
23
+ <!-- Hero image — above the fold on every product detail page, so we
24
+ set fetchpriority=high and skip lazy. width/height locks the
25
+ aspect-square box so layout doesn't shift while it decodes. -->
26
+ <div class="aspect-square overflow-hidden bg-stone-100 rounded-3xl">
27
+ <img
28
+ src="{{ image }}"
29
+ alt="{{ alt }}"
30
+ data-stx-gallery-main
31
+ width="900"
32
+ height="900"
33
+ fetchpriority="high"
34
+ decoding="async"
35
+ class="object-cover h-full w-full"
36
+ >
37
+ </div>
38
+
39
+ @if (thumbs.length > 0)
40
+ <!-- Thumbnails. Clicking swaps the hero src — vanilla DOM is fine
41
+ here because this is a self-contained image switcher with no
42
+ server round-trip. -->
43
+ <div class="grid gap-3 grid-cols-4">
44
+ @foreach (thumbs as thumb, i)
45
+ <button
46
+ type="button"
47
+ data-stx-gallery-thumb="{{ thumb }}"
48
+ class="aspect-square rounded-xl overflow-hidden bg-stone-100 border-2 {{ i === 0 ? 'border-stone-900' : 'border-transparent' }} hover:border-stone-900 transition-colors"
49
+ aria-label="View image {{ i + 1 }}"
50
+ >
51
+ <img src="{{ thumb }}" alt="" class="object-cover h-full w-full">
52
+ </button>
53
+ @endforeach
54
+ </div>
55
+ @endif
56
+ </div>
57
+
58
+ <script data-stx-scoped>
59
+ ;(() => {
60
+ // Document-delegated listener: scoping per-thumb scope to its own
61
+ // [data-stx-gallery] container fixes two prior issues —
62
+ // 1. Multiple galleries on a page (e.g. recommended-products carousel)
63
+ // previously all targeted the first <img data-stx-gallery-main>,
64
+ // so clicking a thumb in gallery B updated gallery A's hero.
65
+ // 2. SPA fragment swaps re-injected the scoped script and stacked
66
+ // a duplicate listener on every existing thumb.
67
+ if (window.__stxGalleryWired) return
68
+ window.__stxGalleryWired = true
69
+
70
+ document.addEventListener('click', (e) => {
71
+ const target = e.target instanceof Element ? e.target : null
72
+ if (!target) return
73
+ const thumb = target.closest('[data-stx-gallery-thumb]')
74
+ if (!thumb) return
75
+ const gallery = thumb.closest('[data-stx-gallery]')
76
+ if (!gallery) return
77
+ const main = gallery.querySelector('[data-stx-gallery-main]')
78
+ const src = thumb.getAttribute('data-stx-gallery-thumb')
79
+ if (src && main) main.setAttribute('src', src)
80
+ gallery.querySelectorAll('[data-stx-gallery-thumb]').forEach((t) => {
81
+ t.classList.remove('border-stone-900')
82
+ t.classList.add('border-transparent')
83
+ })
84
+ thumb.classList.remove('border-transparent')
85
+ thumb.classList.add('border-stone-900')
86
+ })
87
+ })()
88
+ </script>
@@ -0,0 +1,97 @@
1
+ <script>
2
+ interface QuantitySelectorProps {
3
+ name?: string
4
+ value?: number
5
+ min?: number
6
+ max?: number
7
+ className?: string
8
+ }
9
+
10
+ const {
11
+ name = 'quantity',
12
+ value = 1,
13
+ min = 1,
14
+ max = 99,
15
+ className = '',
16
+ } = defineProps<QuantitySelectorProps>()
17
+ </script>
18
+
19
+ <div
20
+ class="flex items-center rounded-full border border-stone-300 overflow-hidden {{ className }}"
21
+ data-stx-qty
22
+ data-min="{{ min }}"
23
+ data-max="{{ max }}"
24
+ >
25
+ <button type="button" class="px-4 py-3 text-stone-500 hover:text-stone-900 focus-visible:bg-stone-100 focus-visible:outline-none transition-colors" data-stx-qty-dec aria-label="Decrease quantity">−</button>
26
+ <input
27
+ name="{{ name }}"
28
+ value="{{ value }}"
29
+ type="number"
30
+ min="{{ min }}"
31
+ max="{{ max }}"
32
+ class="w-12 text-center bg-transparent focus:outline-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [appearance:textfield]"
33
+ >
34
+ <button type="button" class="px-4 py-3 text-stone-500 hover:text-stone-900 focus-visible:bg-stone-100 focus-visible:outline-none transition-colors" data-stx-qty-inc aria-label="Increase quantity">+</button>
35
+ </div>
36
+
37
+ <script data-stx-scoped>
38
+ ;(() => {
39
+ // Delegate one document-level click handler so:
40
+ // 1. Repeated SSR'd <script data-stx-scoped> blocks (one per
41
+ // QuantitySelector instance) don't stack listeners on the same
42
+ // buttons. Without delegation, two QuantitySelectors on a page
43
+ // = two listeners per button = doubled increments per click.
44
+ // 2. SPA fragment swaps that re-inject our scoped script don't
45
+ // keep multiplying handlers; the guard below early-returns.
46
+ if (window.__stxQtyWired) return
47
+ window.__stxQtyWired = true
48
+
49
+ // Default min/max chosen to match the component's defineProps fallbacks.
50
+ // STX SSR doesn't reliably propagate defineProps default values into
51
+ // template attribute interpolation, so the rendered HTML can carry
52
+ // empty `min=""` / `max=""` attributes. Number("") is 0, so without
53
+ // these fallbacks Math.min(0, qty+1) capped every increment at 0 and
54
+ // the +/- buttons appeared dead. Read either the data-* on the wrapper
55
+ // or the input attribute, whichever is non-empty.
56
+ function bound(wrap, kind, fallback) {
57
+ const fromData = wrap.getAttribute('data-' + kind)
58
+ if (fromData && fromData.trim() !== '') {
59
+ const n = Number(fromData)
60
+ if (Number.isFinite(n)) return n
61
+ }
62
+ const input = wrap.querySelector('input[type="number"]')
63
+ const fromAttr = input && input.getAttribute(kind)
64
+ if (fromAttr && fromAttr.trim() !== '') {
65
+ const n = Number(fromAttr)
66
+ if (Number.isFinite(n)) return n
67
+ }
68
+ return fallback
69
+ }
70
+
71
+ function step(wrap, delta) {
72
+ const input = wrap.querySelector('input[type="number"]')
73
+ if (!input) return
74
+ const min = bound(wrap, 'min', 1)
75
+ const max = bound(wrap, 'max', 99)
76
+ const current = Number(input.value)
77
+ const seed = Number.isFinite(current) ? current : min
78
+ const next = Math.max(min, Math.min(max, seed + delta))
79
+ if (next === seed) return
80
+ input.value = String(next)
81
+ input.dispatchEvent(new Event('input', { bubbles: true }))
82
+ input.dispatchEvent(new Event('change', { bubbles: true }))
83
+ }
84
+
85
+ document.addEventListener('click', (e) => {
86
+ const target = e.target instanceof Element ? e.target : null
87
+ if (!target) return
88
+ const dec = target.closest('[data-stx-qty-dec]')
89
+ const inc = target.closest('[data-stx-qty-inc]')
90
+ if (!dec && !inc) return
91
+ const wrap = (dec || inc).closest('[data-stx-qty]')
92
+ if (!wrap) return
93
+ e.preventDefault()
94
+ step(wrap, dec ? -1 : 1)
95
+ })
96
+ })()
97
+ </script>
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Stacks Dashboard Component Library
3
+ * ===================================
4
+ * Complete dashboard components for building modern admin interfaces.
5
+ */
6
+
7
+ // Layout Components
8
+ export { default as DashboardLayout } from './DashboardLayout.vue'
9
+ export { default as SidebarModern } from './SidebarModern.vue'
10
+ export { default as NavbarModern } from './NavbarModern.vue'
11
+
12
+ // UI Components (re-export from UI folder)
13
+ export * from './UI'
@@ -0,0 +1,46 @@
1
+ <script server>
2
+ // Email-verification message — sent after sign-up so the user
3
+ // confirms ownership of the address. Uses bundled
4
+ // <EmailLayout>/<EmailButton>/<EmailDivider>/<EmailLink>
5
+ // components (stacksjs/stacks#1901).
6
+ const appName = props.appName || 'Stacks'
7
+ const verificationUrl = props.verificationUrl || '#'
8
+ const expiryMinutes = props.expiryMinutes || 60
9
+ const userName = props.userName || 'there'
10
+ const primaryColor = props.primaryColor || '#3451b2'
11
+ const subject = props.subject || `Verify your email for ${appName}`
12
+ const year = props.year || new Date().getFullYear()
13
+ </script>
14
+
15
+ <EmailLayout title="{{ subject }}">
16
+ <template #header>
17
+ <tr>
18
+ <td style="background-color:{{ primaryColor }};padding:30px 40px;text-align:center;">
19
+ <h1 style="margin:0;color:#ffffff;font-size:24px;font-weight:600;">{{ appName }}</h1>
20
+ </td>
21
+ </tr>
22
+ </template>
23
+
24
+ <EmailText size="heading">Verify your email address</EmailText>
25
+ <EmailText>Hi {{ userName }}, please verify your email address to complete your <strong>{{ appName }}</strong> account setup. Click the button below to verify.</EmailText>
26
+
27
+ <EmailButton href="{{ verificationUrl }}" bg="{{ primaryColor }}">Verify Email Address</EmailButton>
28
+
29
+ <EmailText size="sm" color="#6b7280">This verification link will expire in <strong>{{ expiryMinutes }} minutes</strong>.</EmailText>
30
+ <EmailText size="sm" color="#6b7280">If you did not create an account, please ignore this email.</EmailText>
31
+
32
+ <EmailDivider />
33
+ <EmailText size="sm" color="#9ca3af" spacing="8px">If the button above doesn't work, copy and paste the following URL into your browser:</EmailText>
34
+ <p style="margin:0;word-break:break-all;">
35
+ <EmailLink href="{{ verificationUrl }}" color="{{ primaryColor }}">{{ verificationUrl }}</EmailLink>
36
+ </p>
37
+
38
+ <template #footer>
39
+ <tr>
40
+ <td style="padding:20px 40px;background-color:#f9fafb;border-top:1px solid #e5e7eb;">
41
+ <p style="margin:0;color:#6b7280;font-size:12px;text-align:center;">This is an automated message from {{ appName }}. Please do not reply to this email.</p>
42
+ <p style="margin:10px 0 0 0;color:#9ca3af;font-size:11px;text-align:center;">&copy; {{ year }} {{ appName }}. All rights reserved.</p>
43
+ </td>
44
+ </tr>
45
+ </template>
46
+ </EmailLayout>
@@ -0,0 +1,113 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
+ <title>{{ subject }}</title>
8
+ <!--[if mso]>
9
+ <noscript>
10
+ <xml>
11
+ <o:OfficeDocumentSettings>
12
+ <o:PixelsPerInch>96</o:PixelsPerInch>
13
+ </o:OfficeDocumentSettings>
14
+ </xml>
15
+ </noscript>
16
+ <![endif]-->
17
+ <style>
18
+ /* Reset styles */
19
+ body, table, td, p, a, li, blockquote {
20
+ -webkit-text-size-adjust: 100%;
21
+ -ms-text-size-adjust: 100%;
22
+ }
23
+ table, td {
24
+ mso-table-lspace: 0pt;
25
+ mso-table-rspace: 0pt;
26
+ }
27
+ img {
28
+ -ms-interpolation-mode: bicubic;
29
+ border: 0;
30
+ height: auto;
31
+ line-height: 100%;
32
+ outline: none;
33
+ text-decoration: none;
34
+ }
35
+ body {
36
+ height: 100% !important;
37
+ margin: 0 !important;
38
+ padding: 0 !important;
39
+ width: 100% !important;
40
+ }
41
+ a[x-apple-data-detectors] {
42
+ color: inherit !important;
43
+ text-decoration: none !important;
44
+ font-size: inherit !important;
45
+ font-family: inherit !important;
46
+ font-weight: inherit !important;
47
+ line-height: inherit !important;
48
+ }
49
+ /* Button styles */
50
+ .button {
51
+ display: inline-block;
52
+ padding: 14px 32px;
53
+ background-color: {{ primaryColor }};
54
+ color: #ffffff !important;
55
+ text-decoration: none;
56
+ border-radius: 6px;
57
+ font-weight: 600;
58
+ font-size: 16px;
59
+ text-align: center;
60
+ }
61
+ .button:hover {
62
+ background-color: {{ primaryColorDark }};
63
+ }
64
+ /* Responsive styles */
65
+ @media only screen and (max-width: 600px) {
66
+ .container {
67
+ width: 100% !important;
68
+ padding: 20px !important;
69
+ }
70
+ .content {
71
+ padding: 24px !important;
72
+ }
73
+ .button {
74
+ display: block !important;
75
+ width: 100% !important;
76
+ }
77
+ }
78
+ </style>
79
+ </head>
80
+ <body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f4f4f5; line-height: 1.6;">
81
+ <table role="presentation" cellpadding="0" cellspacing="0" style="width: 100%; border-collapse: collapse;">
82
+ <tr>
83
+ <td style="padding: 40px 20px;">
84
+ <table role="presentation" cellpadding="0" cellspacing="0" class="container" style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);">
85
+ <!-- Header -->
86
+ <tr>
87
+ <td style="background-color: {{ primaryColor }}; padding: 30px 40px; text-align: center;">
88
+ <h1 style="margin: 0; color: #ffffff; font-size: 24px; font-weight: 600;">{{ appName }}</h1>
89
+ </td>
90
+ </tr>
91
+ <!-- Content -->
92
+ <tr>
93
+ <td class="content" style="padding: 40px;">
94
+ {{ content }}
95
+ </td>
96
+ </tr>
97
+ <!-- Footer -->
98
+ <tr>
99
+ <td style="padding: 20px 40px; background-color: #f9fafb; border-top: 1px solid #e5e7eb;">
100
+ <p style="margin: 0; color: #6b7280; font-size: 12px; text-align: center;">
101
+ This is an automated message from {{ appName }}. Please do not reply to this email.
102
+ </p>
103
+ <p style="margin: 10px 0 0; color: #9ca3af; font-size: 11px; text-align: center;">
104
+ &copy; {{ year }} {{ appName }}. All rights reserved.
105
+ </p>
106
+ </td>
107
+ </tr>
108
+ </table>
109
+ </td>
110
+ </tr>
111
+ </table>
112
+ </body>
113
+ </html>