@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.
- package/LICENSE.md +21 -0
- package/README.md +2 -0
- package/package.json +20 -0
- package/resources/assets/fonts/013717e9.woff +0 -0
- package/resources/assets/fonts/03355817.woff2 +0 -0
- package/resources/assets/fonts/0380de7e.woff2 +0 -0
- package/resources/assets/fonts/0ba5d32a.woff +0 -0
- package/resources/assets/fonts/0bd57ddf.woff2 +0 -0
- package/resources/assets/fonts/19de0a60.woff2 +0 -0
- package/resources/assets/fonts/20b87e84.woff +0 -0
- package/resources/assets/fonts/4bbc67e1.woff +0 -0
- package/resources/assets/fonts/576fb0d7.woff +0 -0
- package/resources/assets/fonts/5ea10ee5.woff2 +0 -0
- package/resources/assets/fonts/6413fc0a.woff +0 -0
- package/resources/assets/fonts/72c114cf.woff +0 -0
- package/resources/assets/fonts/73c57e73.woff2 +0 -0
- package/resources/assets/fonts/813dca6c.woff +0 -0
- package/resources/assets/fonts/8a9b0995.woff2 +0 -0
- package/resources/assets/fonts/90b4832f.woff +0 -0
- package/resources/assets/fonts/9394ab85.woff2 +0 -0
- package/resources/assets/fonts/9b3c7d7f.woff2 +0 -0
- package/resources/assets/fonts/Monaco.ttf +0 -0
- package/resources/assets/fonts/b1b36d74.woff +0 -0
- package/resources/assets/fonts/b5d74798.woff2 +0 -0
- package/resources/assets/fonts/b9f81a4d.woff +0 -0
- package/resources/assets/fonts/c9784008.woff2 +0 -0
- package/resources/assets/fonts/cf692227.woff +0 -0
- package/resources/assets/fonts/d1c6ab51.woff2 +0 -0
- package/resources/assets/fonts/e2eb62fc.woff +0 -0
- package/resources/assets/fonts/fe21f8d8.woff2 +0 -0
- package/resources/assets/fonts/nps/CampmateScript-Regular.woff2 +0 -0
- package/resources/assets/fonts/nps/NPS_2026-variable.woff2 +0 -0
- package/resources/assets/fonts/nps/RedwoodSerif-Regular.woff2 +0 -0
- package/resources/assets/fonts/nps/SequoiaSans-Regular.woff2 +0 -0
- package/resources/assets/fonts/nps/Switchback-Regular.woff2 +0 -0
- package/resources/assets/images/app-icon.png +0 -0
- package/resources/assets/images/bg-img.png +0 -0
- package/resources/assets/images/park-ridge.svg +15 -0
- package/resources/assets/images/topography.svg +1 -0
- package/resources/assets/scripts/main.ts +2153 -0
- package/resources/assets/styles/README.md +19 -0
- package/resources/assets/styles/docs/main.css +1 -0
- package/resources/assets/styles/docs/overrides.css +1 -0
- package/resources/assets/styles/docs/vars.css +8 -0
- package/resources/assets/styles/main.css +1419 -0
- package/resources/components/Button.stx +70 -0
- package/resources/components/Buttons/BaseButton.stx +39 -0
- package/resources/components/Buttons/Counter.stx +28 -0
- package/resources/components/Buttons/ToggleDark.stx +11 -0
- package/resources/components/Container.stx +10 -0
- package/resources/components/Dashboard/.gitkeep +0 -0
- package/resources/components/Dashboard/Auth/ForgotPassword.stx +127 -0
- package/resources/components/Dashboard/Auth/Login.stx +212 -0
- package/resources/components/Dashboard/Auth/Register.stx +306 -0
- package/resources/components/Dashboard/Avatar.stx +123 -0
- package/resources/components/Dashboard/Billing/ActivePlan.stx +57 -0
- package/resources/components/Dashboard/Billing/CardBrands.stx +32 -0
- package/resources/components/Dashboard/Billing/CardForm.stx +34 -0
- package/resources/components/Dashboard/Billing/OneTimePayment.stx +28 -0
- package/resources/components/Dashboard/Billing/PaymentForm.stx +33 -0
- package/resources/components/Dashboard/Billing/PaymentMethod.stx +66 -0
- package/resources/components/Dashboard/Billing/PaymentMethodList.stx +52 -0
- package/resources/components/Dashboard/Billing/Plans.stx +136 -0
- package/resources/components/Dashboard/Buttons/AppButton.stx +48 -0
- package/resources/components/Dashboard/Buttons/IconButton.stx +20 -0
- package/resources/components/Dashboard/Card.stx +78 -0
- package/resources/components/Dashboard/Chart.stx +75 -0
- package/resources/components/Dashboard/CodeEditor.stx +41 -0
- package/resources/components/Dashboard/Commerce/BulkActionBar.stx +70 -0
- package/resources/components/Dashboard/Commerce/CategoriesTable.stx +133 -0
- package/resources/components/Dashboard/Commerce/CouponsTable.stx +161 -0
- package/resources/components/Dashboard/Commerce/CustomersTable.stx +142 -0
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +80 -0
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryTable.stx +85 -0
- package/resources/components/Dashboard/Commerce/Delivery/DriversTable.stx +99 -0
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysTable.stx +92 -0
- package/resources/components/Dashboard/Commerce/Delivery/LicenseTemplatesTable.stx +88 -0
- package/resources/components/Dashboard/Commerce/Delivery/Pagination.stx +122 -0
- package/resources/components/Dashboard/Commerce/Delivery/SearchFilter.stx +23 -0
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsTable.stx +80 -0
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesTable.stx +59 -0
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesTable.stx +71 -0
- package/resources/components/Dashboard/Commerce/Delivery/TabNavigation.stx +36 -0
- package/resources/components/Dashboard/Commerce/Forms/CouponsForm.stx +191 -0
- package/resources/components/Dashboard/Commerce/OrdersTable.stx +131 -0
- package/resources/components/Dashboard/Commerce/ProductsTable.stx +170 -0
- package/resources/components/Dashboard/CreateRecordModal.stx +363 -0
- package/resources/components/Dashboard/DashboardLayout.stx +155 -0
- package/resources/components/Dashboard/Deployments/ActivityFeed.stx +54 -0
- package/resources/components/Dashboard/Deployments/DeployScript.stx +23 -0
- package/resources/components/Dashboard/Deployments/DeploymentHistory.stx +97 -0
- package/resources/components/Dashboard/Deployments/DeploymentList.stx +94 -0
- package/resources/components/Dashboard/Deployments/LiveTerminalOutput.stx +18 -0
- package/resources/components/Dashboard/Elements/Dropdown.stx +57 -0
- package/resources/components/Dashboard/Email/EmailActivityChart.stx +111 -0
- package/resources/components/Dashboard/Email/EmailCompose.stx +72 -0
- package/resources/components/Dashboard/Email/EmailDetail.stx +105 -0
- package/resources/components/Dashboard/Email/EmailList.stx +117 -0
- package/resources/components/Dashboard/Email/EmailSearch.stx +40 -0
- package/resources/components/Dashboard/Email/EmailSidebar.stx +133 -0
- package/resources/components/Dashboard/GlobalSearch.stx +124 -0
- package/resources/components/Dashboard/MobileSidebar.stx +47 -0
- package/resources/components/Dashboard/Modals/BaseModal.stx +50 -0
- package/resources/components/Dashboard/Modals/Popups/Alert.stx +112 -0
- package/resources/components/Dashboard/Modals/Popups/Toast.stx +70 -0
- package/resources/components/Dashboard/Modals/ToastWrapper.stx +35 -0
- package/resources/components/Dashboard/Monitoring/ErrorDetailModal.stx +228 -0
- package/resources/components/Dashboard/Monitoring/ErrorsTable.stx +184 -0
- package/resources/components/Dashboard/Navbar.stx +194 -0
- package/resources/components/Dashboard/NavbarModern.stx +188 -0
- package/resources/components/Dashboard/NotificationErrorModal.stx +105 -0
- package/resources/components/Dashboard/NotificationStatusBadge.stx +20 -0
- package/resources/components/Dashboard/Pagination.stx +90 -0
- package/resources/components/Dashboard/Queue/QueueTable.stx +127 -0
- package/resources/components/Dashboard/Settings/Forms/AIForm.stx +126 -0
- package/resources/components/Dashboard/Settings/Forms/AnalyticsForm.stx +52 -0
- package/resources/components/Dashboard/Settings/Forms/AppForm.stx +157 -0
- package/resources/components/Dashboard/Settings/Forms/CLIForm.stx +66 -0
- package/resources/components/Dashboard/Settings/Forms/CacheForm.stx +253 -0
- package/resources/components/Dashboard/Settings/Forms/CloudForm.stx +441 -0
- package/resources/components/Dashboard/Settings/Forms/DNSForm.stx +307 -0
- package/resources/components/Dashboard/Settings/Forms/DatabaseForm.stx +168 -0
- package/resources/components/Dashboard/Settings/Forms/EmailForm.stx +100 -0
- package/resources/components/Dashboard/Settings/Forms/FileSystemForm.stx +259 -0
- package/resources/components/Dashboard/Settings/Forms/HashingForm.stx +119 -0
- package/resources/components/Dashboard/Settings/Forms/LibraryForm.stx +389 -0
- package/resources/components/Dashboard/Settings/Forms/QueueForm.stx +219 -0
- package/resources/components/Dashboard/Settings/Forms/SearchEngineForm.stx +48 -0
- package/resources/components/Dashboard/Settings/Forms/SecurityForm.stx +143 -0
- package/resources/components/Dashboard/Settings/Forms/ServicesForm.stx +194 -0
- package/resources/components/Dashboard/Settings/Forms/StorageForm.stx +191 -0
- package/resources/components/Dashboard/Settings/Forms/TeamsForm.stx +33 -0
- package/resources/components/Dashboard/Settings/SettingsFormManager.stx +181 -0
- package/resources/components/Dashboard/SettingsHeader.stx +88 -0
- package/resources/components/Dashboard/SettingsSidebar.stx +234 -0
- package/resources/components/Dashboard/Sidebar.stx +23 -0
- package/resources/components/Dashboard/SidebarModern.stx +249 -0
- package/resources/components/Dashboard/Skeleton/LoadingCard.stx +15 -0
- package/resources/components/Dashboard/Skeleton/LoadingDetails.stx +26 -0
- package/resources/components/Dashboard/Table.stx +82 -0
- package/resources/components/Dashboard/TerminalContainer.stx +97 -0
- package/resources/components/Dashboard/Tooltip.stx +166 -0
- package/resources/components/Dashboard/Transaction/index.stx +88 -0
- package/resources/components/Dashboard/UI/ActivityTable.stx +54 -0
- package/resources/components/Dashboard/UI/Avatar.stx +102 -0
- package/resources/components/Dashboard/UI/Badge.stx +66 -0
- package/resources/components/Dashboard/UI/Button.stx +73 -0
- package/resources/components/Dashboard/UI/Card.stx +51 -0
- package/resources/components/Dashboard/UI/ChartCard.stx +36 -0
- package/resources/components/Dashboard/UI/Checkbox.stx +74 -0
- package/resources/components/Dashboard/UI/ConfirmDialog.stx +74 -0
- package/resources/components/Dashboard/UI/DataTable.stx +45 -0
- package/resources/components/Dashboard/UI/Dropdown.stx +72 -0
- package/resources/components/Dashboard/UI/DropdownItem.stx +80 -0
- package/resources/components/Dashboard/UI/EmptyState.stx +50 -0
- package/resources/components/Dashboard/UI/FilterBar.stx +48 -0
- package/resources/components/Dashboard/UI/Input.stx +77 -0
- package/resources/components/Dashboard/UI/Modal.stx +75 -0
- package/resources/components/Dashboard/UI/PageHeader.stx +69 -0
- package/resources/components/Dashboard/UI/PageLayout.stx +84 -0
- package/resources/components/Dashboard/UI/Pagination.stx +126 -0
- package/resources/components/Dashboard/UI/QuickLinks.stx +31 -0
- package/resources/components/Dashboard/UI/RouterLink.stx +31 -0
- package/resources/components/Dashboard/UI/Select.stx +84 -0
- package/resources/components/Dashboard/UI/ServiceHealth.stx +38 -0
- package/resources/components/Dashboard/UI/StatsCard.stx +79 -0
- package/resources/components/Dashboard/UI/Table.stx +143 -0
- package/resources/components/Dashboard/UI/Tabs.stx +45 -0
- package/resources/components/Dashboard/UI/Textarea.stx +93 -0
- package/resources/components/Dashboard/UI/Toggle.stx +84 -0
- package/resources/components/Dashboard/UI/WindowControls.stx +135 -0
- package/resources/components/Dashboard/Widget.stx +89 -0
- package/resources/components/Dashboard/WindowControls.stx +119 -0
- package/resources/components/Dashboard/index.ts +106 -0
- package/resources/components/Docs/Demo/ComboboxCode.md +114 -0
- package/resources/components/Docs/Demo/ComboboxDemo.stx +91 -0
- package/resources/components/Docs/Demo/DialogCode.md +42 -0
- package/resources/components/Docs/Demo/DialogDemo.stx +76 -0
- package/resources/components/Docs/Demo/DocsPlayground.stx +39 -0
- package/resources/components/Docs/Demo/DropdownCode.md +109 -0
- package/resources/components/Docs/Demo/DropdownDemo.stx +82 -0
- package/resources/components/Docs/Demo/Hero.stx +100 -0
- package/resources/components/Docs/Demo/ListboxCode.md +87 -0
- package/resources/components/Docs/Demo/ListboxDemo.stx +59 -0
- package/resources/components/Docs/Demo/NotificationCode.md +173 -0
- package/resources/components/Docs/Demo/NotificationDemo.stx +99 -0
- package/resources/components/Docs/Demo/PopoverCode.md +161 -0
- package/resources/components/Docs/Demo/PopoverDemo.stx +96 -0
- package/resources/components/Docs/Demo/RadioGroupCode.md +98 -0
- package/resources/components/Docs/Demo/RadioGroupDemo.stx +53 -0
- package/resources/components/Docs/Demo/StepperCode.md +216 -0
- package/resources/components/Docs/Demo/StepperDemo.stx +137 -0
- package/resources/components/Docs/Demo/SwitchCode.md +24 -0
- package/resources/components/Docs/Demo/SwitchDemo.stx +28 -0
- package/resources/components/Docs/Home.stx +7 -0
- package/resources/components/Docs/HomeContributors.stx +23 -0
- package/resources/components/Docs/HomeSponsors.stx +17 -0
- package/resources/components/Docs/HomeTeam.stx +18 -0
- package/resources/components/Docs/TeamMember.stx +79 -0
- package/resources/components/Email/EmailButton.stx +32 -0
- package/resources/components/Email/EmailDivider.stx +20 -0
- package/resources/components/Email/EmailImage.stx +24 -0
- package/resources/components/Email/EmailLayout.stx +55 -0
- package/resources/components/Email/EmailLink.stx +22 -0
- package/resources/components/Email/EmailSection.stx +20 -0
- package/resources/components/Email/EmailText.stx +30 -0
- package/resources/components/Forum/ForumBreadcrumb.stx +38 -0
- package/resources/components/Forum/ForumCategory.stx +16 -0
- package/resources/components/Forum/ForumCategoryItem.stx +46 -0
- package/resources/components/Forum/ForumLayout.stx +36 -0
- package/resources/components/Forum/ForumPagination.stx +39 -0
- package/resources/components/Forum/ForumPost.stx +71 -0
- package/resources/components/Forum/ForumReplyForm.stx +76 -0
- package/resources/components/Forum/ForumSidebar.stx +86 -0
- package/resources/components/Forum/ForumTopicItem.stx +77 -0
- package/resources/components/Forum/ForumTopicList.stx +23 -0
- package/resources/components/HelloWorld.stx +253 -0
- package/resources/components/Logo.stx +10 -0
- package/resources/components/Marketing/CallToAction.stx +30 -0
- package/resources/components/Marketing/ComingSoon.stx +155 -0
- package/resources/components/Marketing/Faqs.stx +99 -0
- package/resources/components/Marketing/Feature.stx +32 -0
- package/resources/components/Marketing/Fields.stx +28 -0
- package/resources/components/Marketing/Footer.stx +33 -0
- package/resources/components/Marketing/Header.stx +51 -0
- package/resources/components/Marketing/Hero.stx +46 -0
- package/resources/components/Marketing/Layout.stx +26 -0
- package/resources/components/Marketing/NavLink.stx +13 -0
- package/resources/components/Marketing/Plan.stx +43 -0
- package/resources/components/Marketing/Pricing.stx +120 -0
- package/resources/components/Marketing/PrimaryFeatures.stx +80 -0
- package/resources/components/Marketing/PrimaryFeatures2.stx +98 -0
- package/resources/components/Marketing/SecondaryFeatures.stx +80 -0
- package/resources/components/Marketing/Testimonials.stx +120 -0
- package/resources/components/Pagination.stx +149 -0
- package/resources/components/Playground.stx +9 -0
- package/resources/components/README.md +87 -0
- package/resources/components/Storefront/AnnouncementBar.stx +17 -0
- package/resources/components/Storefront/CartDrawer.stx +227 -0
- package/resources/components/Storefront/CheckoutSteps.stx +50 -0
- package/resources/components/Storefront/Footer.stx +81 -0
- package/resources/components/Storefront/Header.stx +66 -0
- package/resources/components/Storefront/OrderSummary.stx +79 -0
- package/resources/components/Storefront/ProductCard.stx +63 -0
- package/resources/components/Storefront/ProductGallery.stx +88 -0
- package/resources/components/Storefront/QuantitySelector.stx +97 -0
- package/resources/components/index.ts +13 -0
- package/resources/emails/email-verification.stx +46 -0
- package/resources/emails/layouts/base.html +113 -0
- package/resources/emails/password-changed.stx +55 -0
- package/resources/emails/password-reset.stx +43 -0
- package/resources/emails/subscription-confirmation.stx +99 -0
- package/resources/emails/welcome.stx +48 -0
- package/resources/functions/README.md +28 -0
- package/resources/functions/counter.ts +13 -0
- package/resources/functions/dark.ts +4 -0
- package/resources/functions/dashboard/analytics.ts +238 -0
- package/resources/functions/dashboard/config-io.ts +316 -0
- package/resources/functions/dashboard/data.ts +375 -0
- package/resources/functions/dashboard/library.ts +250 -0
- package/resources/functions/dashboard/sidebar.ts +537 -0
- package/resources/functions/dashboard/system.ts +150 -0
- package/resources/layouts/403.stx +188 -0
- package/resources/layouts/404.stx +34 -0
- package/resources/layouts/README.md +14 -0
- package/resources/layouts/dashboard/default.stx +20 -0
- package/resources/layouts/dashboard/guest.stx +10 -0
- package/resources/layouts/default.stx +3 -0
- package/resources/layouts/maintenance.stx +11 -0
- package/resources/layouts/storefront.stx +73 -0
- package/resources/layouts/system-tray/default.stx +3 -0
- package/resources/modules/.gitkeep +0 -0
- package/resources/plugins/.gitkeep +0 -0
- package/resources/plugins/preloader.ts +308 -0
- package/resources/types/README.md +3 -0
- package/resources/views/cart.stx +149 -0
- package/resources/views/checkout/contact.stx +101 -0
- package/resources/views/checkout/payment.stx +114 -0
- package/resources/views/checkout/shipping.stx +120 -0
- package/resources/views/coming-soon.stx +9 -0
- package/resources/views/dashboard/custom-page.stx +45 -0
- package/resources/views/dashboard/index.stx +24 -0
- package/resources/views/emails/welcome.stx +0 -0
- package/resources/views/errors/tester.stx +26 -0
- package/resources/views/index.stx +2865 -0
- 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;">© {{ 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
|
+
© {{ year }} {{ appName }}. All rights reserved.
|
|
105
|
+
</p>
|
|
106
|
+
</td>
|
|
107
|
+
</tr>
|
|
108
|
+
</table>
|
|
109
|
+
</td>
|
|
110
|
+
</tr>
|
|
111
|
+
</table>
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|