astro-component-kit 0.3.1 → 0.4.1

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 (249) hide show
  1. package/README.md +133 -36
  2. package/package.json +17 -4
  3. package/src/components/index.ts +18 -202
  4. package/src/components/lib/Galaxy3D.astro +249 -137
  5. package/src/components/lib/app/AppSidebar.astro +249 -0
  6. package/src/components/lib/app/AppTopbar.astro +284 -0
  7. package/src/components/lib/app/ArrivalsBoard.astro +176 -0
  8. package/src/components/lib/app/AuthCard.astro +213 -0
  9. package/src/components/lib/app/DataTable.astro +346 -0
  10. package/src/components/lib/app/GuestProfileCard.astro +133 -0
  11. package/src/components/lib/app/HousekeepingBoard.astro +189 -0
  12. package/src/components/lib/app/PageHeader.astro +157 -0
  13. package/src/components/lib/app/PlanUsageCard.astro +112 -0
  14. package/src/components/lib/app/RoomRack.astro +207 -0
  15. package/src/components/lib/app/SettingsSection.astro +96 -0
  16. package/src/components/lib/app/SwitchRow.astro +71 -0
  17. package/src/components/lib/app/TeamMembers.astro +190 -0
  18. package/src/components/lib/app/app-icons.ts +52 -0
  19. package/src/components/lib/app/index.ts +19 -0
  20. package/src/components/lib/buttons/NeonBorderButton.astro +162 -0
  21. package/src/components/lib/buttons/RippleButton.astro +199 -0
  22. package/src/components/lib/buttons/SlideRevealButton.astro +159 -0
  23. package/src/components/lib/buttons/index.ts +4 -0
  24. package/src/components/lib/cards/GithubRepoCard.astro +176 -0
  25. package/src/components/lib/cards/PricingCompareMiniCard.astro +196 -0
  26. package/src/components/lib/cards/StatsTrendCard.astro +149 -0
  27. package/src/components/lib/cards/index.ts +3 -0
  28. package/src/components/lib/charts/ActivityRings.astro +105 -0
  29. package/src/components/lib/charts/AreaChart.astro +4 -4
  30. package/src/components/lib/charts/BarChart.astro +3 -3
  31. package/src/components/lib/charts/BoxPlotChart.astro +109 -0
  32. package/src/components/lib/charts/BubbleChart.astro +101 -0
  33. package/src/components/lib/charts/BulletChart.astro +160 -0
  34. package/src/components/lib/charts/BumpChart.astro +92 -0
  35. package/src/components/lib/charts/CandlestickChart.astro +105 -0
  36. package/src/components/lib/charts/ChartFrame.astro +373 -0
  37. package/src/components/lib/charts/DivergingBarChart.astro +98 -0
  38. package/src/components/lib/charts/DonutChart.astro +2 -2
  39. package/src/components/lib/charts/DumbbellChart.astro +92 -0
  40. package/src/components/lib/charts/ForecastLineChart.astro +125 -0
  41. package/src/components/lib/charts/FunnelChart.astro +122 -0
  42. package/src/components/lib/charts/GaugeChart.astro +111 -0
  43. package/src/components/lib/charts/GoalProgressChart.astro +97 -0
  44. package/src/components/lib/charts/GroupedBarChart.astro +125 -0
  45. package/src/components/lib/charts/HeatmapChart.astro +131 -0
  46. package/src/components/lib/charts/HistogramChart.astro +111 -0
  47. package/src/components/lib/charts/HorizontalBarChart.astro +155 -0
  48. package/src/components/lib/charts/KpiStatTile.astro +116 -0
  49. package/src/components/lib/charts/KpiTrendGrid.astro +60 -0
  50. package/src/components/lib/charts/LineChart.astro +160 -0
  51. package/src/components/lib/charts/LinearProgressGauge.astro +136 -0
  52. package/src/components/lib/charts/LollipopChart.astro +100 -0
  53. package/src/components/lib/charts/MeterGauge.astro +122 -0
  54. package/src/components/lib/charts/MetricDeltaTable.astro +125 -0
  55. package/src/components/lib/charts/PieChart.astro +135 -0
  56. package/src/components/lib/charts/PolarAreaChart.astro +85 -0
  57. package/src/components/lib/charts/PopulationPyramid.astro +122 -0
  58. package/src/components/lib/charts/PunchCardChart.astro +89 -0
  59. package/src/components/lib/charts/RadarChart.astro +94 -0
  60. package/src/components/lib/charts/RadarMetricChart.astro +168 -0
  61. package/src/components/lib/charts/RadialBarChart.astro +124 -0
  62. package/src/components/lib/charts/RangeAreaChart.astro +113 -0
  63. package/src/components/lib/charts/RetentionCohortTable.astro +126 -0
  64. package/src/components/lib/charts/SankeyChart.astro +128 -0
  65. package/src/components/lib/charts/ScatterPlot.astro +139 -0
  66. package/src/components/lib/charts/SegmentedBarChart.astro +119 -0
  67. package/src/components/lib/charts/SlopeChart.astro +105 -0
  68. package/src/components/lib/charts/SparklineChart.astro +87 -0
  69. package/src/components/lib/charts/StackedAreaChart.astro +130 -0
  70. package/src/components/lib/charts/StackedBarChart.astro +136 -0
  71. package/src/components/lib/charts/SunburstChart.astro +103 -0
  72. package/src/components/lib/charts/TreemapChart.astro +94 -0
  73. package/src/components/lib/charts/WaffleChart.astro +121 -0
  74. package/src/components/lib/charts/WaterfallChart.astro +118 -0
  75. package/src/components/lib/charts/chart-tooltip.ts +75 -0
  76. package/src/components/lib/charts/chart-utils.ts +271 -0
  77. package/src/components/lib/charts/index.ts +46 -0
  78. package/src/components/lib/data/ActivityHeatmapGrid.astro +157 -0
  79. package/src/components/lib/data/MetricComparisonRow.astro +109 -0
  80. package/src/components/lib/data/StatusTimelineFeed.astro +168 -0
  81. package/src/components/lib/data/index.ts +3 -0
  82. package/src/components/lib/ecommerce/CartDrawer.astro +483 -0
  83. package/src/components/lib/ecommerce/CartPageList.astro +290 -0
  84. package/src/components/lib/ecommerce/CartToast.astro +276 -0
  85. package/src/components/lib/ecommerce/CategoryTiles.astro +143 -0
  86. package/src/components/lib/ecommerce/CheckoutForm.astro +423 -0
  87. package/src/components/lib/ecommerce/CountdownBanner.astro +230 -0
  88. package/src/components/lib/ecommerce/FilterBar.astro +278 -0
  89. package/src/components/lib/ecommerce/FilterSidebar.astro +405 -0
  90. package/src/components/lib/ecommerce/LiveActivityToast.astro +213 -0
  91. package/src/components/lib/ecommerce/OrderSummary.astro +295 -0
  92. package/src/components/lib/ecommerce/ProductBuyBox.astro +247 -0
  93. package/src/components/lib/ecommerce/ProductCardMinimal.astro +229 -0
  94. package/src/components/lib/ecommerce/ProductGallery.astro +145 -0
  95. package/src/components/lib/ecommerce/ProductGrid.astro +118 -0
  96. package/src/components/lib/ecommerce/ProductMedia.astro +113 -0
  97. package/src/components/lib/ecommerce/ProductRating.astro +78 -0
  98. package/src/components/lib/ecommerce/ProductToolbar.astro +93 -0
  99. package/src/components/lib/ecommerce/PromoBar.astro +145 -0
  100. package/src/components/lib/ecommerce/StockIndicator.astro +127 -0
  101. package/src/components/lib/ecommerce/StoreHeader.astro +446 -0
  102. package/src/components/lib/ecommerce/StoreHero.astro +260 -0
  103. package/src/components/lib/ecommerce/StoreProductCard.astro +307 -0
  104. package/src/components/lib/ecommerce/TrustBadges.astro +101 -0
  105. package/src/components/lib/ecommerce/VariantPicker.astro +145 -0
  106. package/src/components/lib/ecommerce/cart-store.ts +151 -0
  107. package/src/components/lib/ecommerce/catalog-filter.ts +62 -0
  108. package/src/components/lib/ecommerce/index.ts +34 -0
  109. package/src/components/lib/ecommerce/product-icons.ts +28 -0
  110. package/src/components/lib/ecommerce/types.ts +70 -0
  111. package/src/components/lib/feedback/ProgressRingCounter.astro +118 -0
  112. package/src/components/lib/feedback/ToastNotificationGroup.astro +173 -0
  113. package/src/components/lib/feedback/index.ts +2 -0
  114. package/src/components/lib/forms/AnimatedInput.astro +4 -2
  115. package/src/components/lib/forms/CustomRadioGroup.astro +3 -1
  116. package/src/components/lib/forms/GlassInlineToggleGroup.astro +161 -0
  117. package/src/components/lib/forms/PasswordStrengthInput.astro +242 -0
  118. package/src/components/lib/forms/PhoneCountryInput.astro +145 -0
  119. package/src/components/lib/forms/TagsAutocompleteInput.astro +249 -0
  120. package/src/components/lib/forms/index.ts +5 -0
  121. package/src/components/lib/index.ts +2 -0
  122. package/src/components/lib/layout/BentoGrid3.astro +6 -4
  123. package/src/components/lib/layout/CenterFixedBox.astro +2 -2
  124. package/src/components/lib/layout/CenteredContainer.astro +4 -2
  125. package/src/components/lib/layout/ClusterHorizontal.astro +2 -2
  126. package/src/components/lib/layout/DiagonalSectionDivider.astro +69 -0
  127. package/src/components/lib/layout/FeatureHero.astro +4 -2
  128. package/src/components/lib/layout/FooterCenteredMinimal.astro +4 -2
  129. package/src/components/lib/layout/FullBleedSection.astro +2 -2
  130. package/src/components/lib/layout/GalleryGrid4.astro +2 -2
  131. package/src/components/lib/layout/HolyGrailGrid.astro +127 -0
  132. package/src/components/lib/layout/LandingSectionSplit.astro +5 -3
  133. package/src/components/lib/layout/MasonryGrid.astro +2 -2
  134. package/src/components/lib/layout/MinimalFooterGrid.astro +1 -1
  135. package/src/components/lib/layout/ScrollSnapSection.astro +1 -1
  136. package/src/components/lib/layout/SidebarLayoutWrapper.astro +4 -2
  137. package/src/components/lib/layout/SplitScreenLayout.astro +3 -3
  138. package/src/components/lib/layout/StepLayoutVertical.astro +1 -1
  139. package/src/components/lib/layout/StickySidebarWrapper.astro +4 -2
  140. package/src/components/lib/layout/index.ts +3 -1
  141. package/src/components/lib/navigation/QuickJumpDock.astro +125 -0
  142. package/src/components/lib/navigation/StepBreadcrumbBar.astro +151 -0
  143. package/src/components/lib/navigation/SubNavbarPills.astro +102 -0
  144. package/src/components/lib/navigation/index.ts +3 -0
  145. package/src/components/lib/overlays/CookiePreferencesDrawer.astro +226 -0
  146. package/src/components/lib/overlays/FloatingFeedbackWidget.astro +203 -0
  147. package/src/components/lib/overlays/ImageCompareSlider.astro +172 -0
  148. package/src/components/lib/overlays/index.ts +3 -0
  149. package/src/components/lib/sections/AgencyContactBrief.astro +194 -0
  150. package/src/components/lib/sections/AgencyFooterCta.astro +94 -0
  151. package/src/components/lib/sections/AgencyHeader.astro +161 -0
  152. package/src/components/lib/sections/AgencyHeroKinetic.astro +172 -0
  153. package/src/components/lib/sections/AgencyServicesList.astro +113 -0
  154. package/src/components/lib/sections/AgencyTeamGrid.astro +111 -0
  155. package/src/components/lib/sections/AgencyWorkGrid.astro +147 -0
  156. package/src/components/lib/sections/BlogGridSection.astro +133 -0
  157. package/src/components/lib/sections/ButtonsShowcase.astro +20 -18
  158. package/src/components/lib/sections/CardsShowcase.astro +28 -26
  159. package/src/components/lib/sections/CareersOpenings.astro +141 -0
  160. package/src/components/lib/sections/ChangelogSection.astro +151 -0
  161. package/src/components/lib/sections/ContactGlassForm.astro +10 -8
  162. package/src/components/lib/sections/ContactSplitMap.astro +141 -0
  163. package/src/components/lib/sections/CtaAppDownload.astro +13 -11
  164. package/src/components/lib/sections/CtaGlowBanner.astro +12 -10
  165. package/src/components/lib/sections/CtaNewsletterSection.astro +165 -0
  166. package/src/components/lib/sections/CtaSplitImage.astro +149 -0
  167. package/src/components/lib/sections/DataShowcase.astro +23 -21
  168. package/src/components/lib/sections/FaqAccordionSection.astro +8 -8
  169. package/src/components/lib/sections/FaqTwoColumn.astro +121 -0
  170. package/src/components/lib/sections/FeatureAlternatingRows.astro +192 -0
  171. package/src/components/lib/sections/FeatureBentoGrid.astro +13 -11
  172. package/src/components/lib/sections/FeatureIconGrid.astro +155 -0
  173. package/src/components/lib/sections/FeatureTabsShowcase.astro +182 -0
  174. package/src/components/lib/sections/FeatureTimelineFlow.astro +8 -6
  175. package/src/components/lib/sections/FeedbackShowcase.astro +15 -13
  176. package/src/components/lib/sections/FooterMegaSection.astro +157 -0
  177. package/src/components/lib/sections/FormsShowcase.astro +22 -20
  178. package/src/components/lib/sections/HeroDashboardPreview.astro +182 -0
  179. package/src/components/lib/sections/HeroGlass.astro +22 -20
  180. package/src/components/lib/sections/HeroGradient.astro +15 -13
  181. package/src/components/lib/sections/HeroMinimalCentered.astro +175 -0
  182. package/src/components/lib/sections/HeroShowcaseSplit.astro +306 -0
  183. package/src/components/lib/sections/HeroSplit.astro +13 -11
  184. package/src/components/lib/sections/HeroTerminal.astro +253 -0
  185. package/src/components/lib/sections/HeroWaitlist.astro +231 -0
  186. package/src/components/lib/sections/HowItWorksSteps.astro +180 -0
  187. package/src/components/lib/sections/IntegrationsGrid.astro +167 -0
  188. package/src/components/lib/sections/LayoutShowcase.astro +21 -19
  189. package/src/components/lib/sections/LogoCloudSection.astro +96 -0
  190. package/src/components/lib/sections/LogoWallGrid.astro +93 -0
  191. package/src/components/lib/sections/NavigationShowcase.astro +15 -13
  192. package/src/components/lib/sections/OverlaysShowcase.astro +21 -19
  193. package/src/components/lib/sections/PricingComparisonMatrix.astro +15 -13
  194. package/src/components/lib/sections/PricingSinglePlan.astro +159 -0
  195. package/src/components/lib/sections/PricingTierCards.astro +11 -9
  196. package/src/components/lib/sections/PricingToggleCards.astro +171 -0
  197. package/src/components/lib/sections/ShopCategoryHero.astro +103 -0
  198. package/src/components/lib/sections/ShopCollectionMosaic.astro +131 -0
  199. package/src/components/lib/sections/ShopFooterWordmark.astro +100 -0
  200. package/src/components/lib/sections/ShopHeaderEditorial.astro +396 -0
  201. package/src/components/lib/sections/ShopHeroEditorial.astro +151 -0
  202. package/src/components/lib/sections/ShopLookbook.astro +250 -0
  203. package/src/components/lib/sections/ShopNewsletterEditorial.astro +128 -0
  204. package/src/components/lib/sections/ShopProductRail.astro +150 -0
  205. package/src/components/lib/sections/ShopReviewsEditorial.astro +133 -0
  206. package/src/components/lib/sections/ShopValuesMarquee.astro +91 -0
  207. package/src/components/lib/sections/StatsCounterBand.astro +124 -0
  208. package/src/components/lib/sections/StatsDashboardSection.astro +85 -0
  209. package/src/components/lib/sections/StatsMetricShowcase.astro +9 -7
  210. package/src/components/lib/sections/TeamGridSection.astro +12 -10
  211. package/src/components/lib/sections/TestimonialCarousel.astro +167 -0
  212. package/src/components/lib/sections/TestimonialFeatured.astro +122 -0
  213. package/src/components/lib/sections/TestimonialGridSection.astro +188 -0
  214. package/src/components/lib/sections/TestimonialWallGlass.astro +11 -9
  215. package/src/components/lib/sections/TypographyShowcase.astro +10 -8
  216. package/src/components/lib/sections/index.ts +39 -0
  217. package/src/components/lib/templates/ecommerce/StoreCategoryPage.astro +160 -0
  218. package/src/components/lib/templates/ecommerce/StoreCheckoutPage.astro +98 -0
  219. package/src/components/lib/templates/ecommerce/StoreHomePage.astro +191 -0
  220. package/src/components/lib/templates/ecommerce/StoreLayout.astro +169 -0
  221. package/src/components/lib/templates/ecommerce/StoreProductPage.astro +165 -0
  222. package/src/components/lib/templates/ecommerce/data.ts +187 -0
  223. package/src/components/lib/templates/ecommerce/index.ts +14 -0
  224. package/src/components/lib/templates/ecommerce-editorial/EditorialBagPage.astro +52 -0
  225. package/src/components/lib/templates/ecommerce-editorial/EditorialCheckoutPage.astro +123 -0
  226. package/src/components/lib/templates/ecommerce-editorial/EditorialHomePage.astro +102 -0
  227. package/src/components/lib/templates/ecommerce-editorial/EditorialLayout.astro +216 -0
  228. package/src/components/lib/templates/ecommerce-editorial/EditorialProductPage.astro +205 -0
  229. package/src/components/lib/templates/ecommerce-editorial/EditorialShopPage.astro +112 -0
  230. package/src/components/lib/templates/ecommerce-editorial/data.ts +125 -0
  231. package/src/components/lib/templates/ecommerce-editorial/index.ts +16 -0
  232. package/src/components/lib/templates/saas-hotel/AppLayout.astro +83 -0
  233. package/src/components/lib/templates/saas-hotel/DashboardPage.astro +66 -0
  234. package/src/components/lib/templates/saas-hotel/GuestsPage.astro +65 -0
  235. package/src/components/lib/templates/saas-hotel/LandingPage.astro +213 -0
  236. package/src/components/lib/templates/saas-hotel/ReportsPage.astro +63 -0
  237. package/src/components/lib/templates/saas-hotel/ReservationsPage.astro +49 -0
  238. package/src/components/lib/templates/saas-hotel/RoomsPage.astro +48 -0
  239. package/src/components/lib/templates/saas-hotel/SettingsPage.astro +115 -0
  240. package/src/components/lib/templates/saas-hotel/SignInPage.astro +50 -0
  241. package/src/components/lib/templates/saas-hotel/data.ts +229 -0
  242. package/src/components/lib/templates/saas-hotel/index.ts +19 -0
  243. package/src/components/lib/templates/saas-hotel/lobby-theme.css +90 -0
  244. package/src/components/lib/typography/AnimatedCounterText.astro +102 -0
  245. package/src/components/lib/typography/CurvedPathHeading.astro +86 -0
  246. package/src/components/lib/typography/GradientTextMarquee.astro +105 -0
  247. package/src/components/lib/typography/HackerDecodeText.astro +85 -0
  248. package/src/components/lib/typography/index.ts +4 -0
  249. package/src/styles/global.css +113 -1
@@ -0,0 +1,143 @@
1
+ ---
2
+ /**
3
+ * CategoryTiles — Grid of shop categories with illustration, item count and arrow link.
4
+ *
5
+ * @param {string} title - Section heading (optional). Default 'Shop by category'.
6
+ * @param {string} subtitle - Supporting copy (optional).
7
+ * @param {Array<StoreCategory & { href: string, count?: number }>} categories - Tiles to show.
8
+ * @param {number} columns - Desktop columns (2–6). Default 4.
9
+ * @param {string} variant - Tile style (glass|image|minimal). Default 'glass'.
10
+ * @param {string} size - Tile height (sm|md|lg). Default 'md'.
11
+ * @param {string} countLabel - Suffix after the count. Default 'products'.
12
+ * @param {string} className - Extra class.
13
+ *
14
+ * Accessibility: each tile is a single link with a visible name; art is decorative.
15
+ */
16
+ import ProductMedia from './ProductMedia.astro';
17
+ import type { StoreCategory } from './types';
18
+
19
+ type Tile = StoreCategory & { href: string; count?: number };
20
+ interface Props {
21
+ title?: string;
22
+ subtitle?: string;
23
+ categories?: Tile[];
24
+ columns?: number;
25
+ variant?: 'glass' | 'image' | 'minimal';
26
+ size?: 'sm' | 'md' | 'lg';
27
+ countLabel?: string;
28
+ className?: string;
29
+ }
30
+
31
+ const {
32
+ title = 'Shop by category',
33
+ subtitle,
34
+ categories = [
35
+ { id: 'audio', name: 'Audio', href: '#', count: 18, icon: 'headphones', hue: 38 },
36
+ { id: 'wearables', name: 'Wearables', href: '#', count: 12, icon: 'watch', hue: 200 },
37
+ { id: 'bags', name: 'Bags', href: '#', count: 9, icon: 'backpack', hue: 150 },
38
+ { id: 'home', name: 'Home', href: '#', count: 24, icon: 'lamp', hue: 20 },
39
+ ],
40
+ columns = 4,
41
+ variant = 'glass',
42
+ size = 'md',
43
+ countLabel = 'products',
44
+ className = '',
45
+ } = Astro.props;
46
+
47
+ const cols = Math.min(6, Math.max(2, columns));
48
+ ---
49
+
50
+ <section class:list={['cat-tiles', `cat-tiles--${variant}`, `cat-tiles--${size}`, className]} style={`--ct-cols: ${cols}`}>
51
+ {(title || subtitle) && (
52
+ <header class="cat-tiles__head">
53
+ {title && <h2 class="cat-tiles__title">{title}</h2>}
54
+ {subtitle && <p class="cat-tiles__subtitle">{subtitle}</p>}
55
+ </header>
56
+ )}
57
+ <ul class="cat-tiles__grid">
58
+ {categories.map((c) => (
59
+ <li>
60
+ <a class="cat-tile is-hoverable" href={c.href}>
61
+ <ProductMedia icon={c.icon} hue={c.hue} ratio="auto" size="sm" variant={variant === 'minimal' ? 'flat' : 'glow'} className="cat-tile__media" />
62
+ <span class="cat-tile__text">
63
+ <span class="cat-tile__name">{c.name}</span>
64
+ {c.count !== undefined && <span class="cat-tile__count">{c.count} {countLabel}</span>}
65
+ </span>
66
+ <span class="cat-tile__arrow" aria-hidden="true">
67
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17 17 7M8 7h9v9"/></svg>
68
+ </span>
69
+ </a>
70
+ </li>
71
+ ))}
72
+ </ul>
73
+ </section>
74
+
75
+ <style>
76
+ .cat-tiles { container-type: inline-size; --ct-h: 220px; }
77
+ .cat-tiles--sm { --ct-h: 160px; }
78
+ .cat-tiles--lg { --ct-h: 280px; }
79
+ .cat-tiles__head { margin-bottom: var(--sp-6, 1.5rem); }
80
+ .cat-tiles__title { margin: 0; font-size: clamp(1.5rem, 3vw, 2rem); font-weight: 800; letter-spacing: -0.03em; color: var(--c-text-1, #fff); }
81
+ .cat-tiles__subtitle { margin: var(--sp-2, 0.5rem) 0 0; color: var(--c-text-2, #a3a3a3); }
82
+
83
+ .cat-tiles__grid {
84
+ display: grid;
85
+ grid-template-columns: repeat(var(--ct-cols), minmax(0, 1fr));
86
+ gap: var(--sp-4, 1rem);
87
+ margin: 0;
88
+ padding: 0;
89
+ list-style: none;
90
+ }
91
+
92
+ .cat-tile {
93
+ position: relative;
94
+ display: flex;
95
+ flex-direction: column;
96
+ justify-content: flex-end;
97
+ height: var(--ct-h);
98
+ padding: var(--sp-5, 1.25rem);
99
+ border-radius: var(--r-lg, 20px);
100
+ border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
101
+ overflow: hidden;
102
+ color: var(--c-text-1, #fff);
103
+ text-decoration: none;
104
+ isolation: isolate;
105
+ transition: border-color 0.25s ease, transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
106
+ }
107
+ .cat-tile:hover { border-color: rgba(212, 160, 23, 0.35); transform: translateY(-3px); color: var(--c-text-1, #fff); }
108
+ .cat-tile:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 3px; }
109
+ .cat-tile :global(.cat-tile__media) { position: absolute; inset: 0; height: 100%; z-index: -1; aspect-ratio: auto; }
110
+ .cat-tile :global(.cat-tile__media .p-media__art) { width: auto; height: 46%; margin-bottom: 18%; }
111
+ .cat-tile::after { content: ''; position: absolute; inset: 40% 0 0; background: linear-gradient(transparent, rgb(var(--paper-rgb, 5 5 5) / 0.85)); z-index: -1; }
112
+ .cat-tiles--minimal .cat-tile::after { display: none; }
113
+
114
+ .cat-tile__text { display: grid; gap: 2px; }
115
+ .cat-tile__name { font-size: 1.1rem; font-weight: 800; letter-spacing: -0.01em; }
116
+ .cat-tile__count { font-size: 0.8rem; color: var(--c-text-2, #a3a3a3); }
117
+ .cat-tile__arrow {
118
+ position: absolute;
119
+ top: var(--sp-4, 1rem);
120
+ right: var(--sp-4, 1rem);
121
+ display: grid;
122
+ place-items: center;
123
+ width: 36px;
124
+ height: 36px;
125
+ border-radius: 50%;
126
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.08);
127
+ backdrop-filter: blur(8px);
128
+ transition: background 0.25s ease, color 0.25s ease, transform 0.25s ease;
129
+ }
130
+ .cat-tile:hover .cat-tile__arrow { background: var(--c-primary-light, #d4a017); color: var(--c-on-primary, #050505); transform: rotate(45deg); }
131
+
132
+ @container (max-width: 900px) {
133
+ .cat-tiles__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
134
+ }
135
+ @container (max-width: 420px) {
136
+ .cat-tiles__grid { --ct-h: 170px; }
137
+ .cat-tiles__grid { gap: var(--sp-3, 0.75rem); }
138
+ .cat-tile { padding: var(--sp-4, 1rem); }
139
+ }
140
+ @media (prefers-reduced-motion: reduce) {
141
+ .cat-tile, .cat-tile__arrow { transition: none; }
142
+ }
143
+ </style>
@@ -0,0 +1,423 @@
1
+ ---
2
+ /**
3
+ * CheckoutForm — One-page checkout: contact, delivery address, shipping method and payment,
4
+ * built from AnimatedInput, ModernSelect and CustomRadioGroup. Shows an order confirmation
5
+ * (AnimatedCheckmark) on submit and clears the cart.
6
+ *
7
+ * Pair it with OrderSummary: the shipping radios drive its totals and the pay button shows
8
+ * the live total. Replace the demo submit handler with your payment provider.
9
+ *
10
+ * @param {string} id - Form id. Default 'checkout'.
11
+ * @param {ShippingRate[]} shippingRates - Shipping options (must match OrderSummary).
12
+ * @param {number} freeShippingThreshold - Shows "Free" on the first rate above this subtotal. Default 75.
13
+ * @param {Array<{label: string, value: string}>} countries - Country select options.
14
+ * @param {string} cartKey - Cart storage key. Default 'ack-cart'.
15
+ * @param {string} currency - ISO currency. Default 'USD'.
16
+ * @param {string} locale - Number format locale. Default 'en-US'.
17
+ * @param {string} continueHref - "Continue shopping" link on the confirmation. Default '#'.
18
+ * @param {boolean} demo - Simulates payment and never sends data anywhere. Default true.
19
+ * @param {string} variant - Section style (cards|plain). Default 'cards'.
20
+ * @param {string} className - Extra class.
21
+ *
22
+ * Accessibility: grouped fieldsets with legends, autocomplete hints on every field, native
23
+ * validation with focus moved to the first invalid field, confirmation heading receives focus.
24
+ * Emits `ack:order-placed` with `{ orderId, total }`.
25
+ */
26
+ import AnimatedInput from '../forms/AnimatedInput.astro';
27
+ import ModernSelect from '../forms/ModernSelect.astro';
28
+ import CustomRadioGroup from '../forms/CustomRadioGroup.astro';
29
+ import AnimatedCheckmark from '../feedback/AnimatedCheckmark.astro';
30
+ import { formatMoney } from './cart-store';
31
+ import type { ShippingRate } from './types';
32
+
33
+ interface Props {
34
+ id?: string;
35
+ shippingRates?: ShippingRate[];
36
+ freeShippingThreshold?: number;
37
+ countries?: Array<{ label: string; value: string }>;
38
+ cartKey?: string;
39
+ currency?: string;
40
+ locale?: string;
41
+ continueHref?: string;
42
+ demo?: boolean;
43
+ variant?: 'cards' | 'plain';
44
+ className?: string;
45
+ }
46
+
47
+ const {
48
+ id = 'checkout',
49
+ shippingRates = [
50
+ { id: 'standard', label: 'Standard', eta: '3–5 business days', price: 6 },
51
+ { id: 'express', label: 'Express', eta: '1–2 business days', price: 14 },
52
+ ],
53
+ freeShippingThreshold = 75,
54
+ countries = [
55
+ { label: 'United States', value: 'US' },
56
+ { label: 'Canada', value: 'CA' },
57
+ { label: 'United Kingdom', value: 'GB' },
58
+ { label: 'Spain', value: 'ES' },
59
+ { label: 'Germany', value: 'DE' },
60
+ { label: 'France', value: 'FR' },
61
+ ],
62
+ cartKey = 'ack-cart',
63
+ currency = 'USD',
64
+ locale = 'en-US',
65
+ continueHref = '#',
66
+ demo = true,
67
+ variant = 'cards',
68
+ className = '',
69
+ } = Astro.props;
70
+
71
+ const money = { currency, locale };
72
+ ---
73
+
74
+ <div class:list={['checkout', `checkout--${variant}`, className]} data-checkout data-key={cartKey} data-demo={demo ? '' : undefined} data-free={freeShippingThreshold}>
75
+ <form class="checkout__form" id={id} novalidate data-checkout-form>
76
+ <fieldset class="checkout__section">
77
+ <legend><span class="checkout__num" aria-hidden="true">1</span> Contact</legend>
78
+ <AnimatedInput label="Email" id={`${id}-email`} name="email" type="email" required autocomplete="email" />
79
+ <label class="checkout__check">
80
+ <input type="checkbox" name="newsletter" checked />
81
+ <span class="checkout__box" aria-hidden="true"></span>
82
+ Email me with news and offers
83
+ </label>
84
+ </fieldset>
85
+
86
+ <fieldset class="checkout__section">
87
+ <legend><span class="checkout__num" aria-hidden="true">2</span> Delivery</legend>
88
+ <ModernSelect label="Country / region" id={`${id}-country`} name="country" options={countries} required />
89
+ <div class="checkout__row">
90
+ <AnimatedInput label="First name" id={`${id}-first`} name="firstName" required autocomplete="given-name" />
91
+ <AnimatedInput label="Last name" id={`${id}-last`} name="lastName" required autocomplete="family-name" />
92
+ </div>
93
+ <AnimatedInput label="Address" id={`${id}-address`} name="address" required autocomplete="address-line1" />
94
+ <AnimatedInput label="Apartment, suite (optional)" id={`${id}-address2`} name="address2" autocomplete="address-line2" />
95
+ <div class="checkout__row checkout__row--3">
96
+ <AnimatedInput label="City" id={`${id}-city`} name="city" required autocomplete="address-level2" />
97
+ <AnimatedInput label="State" id={`${id}-state`} name="state" autocomplete="address-level1" />
98
+ <AnimatedInput label="ZIP / Postal" id={`${id}-zip`} name="zip" required autocomplete="postal-code" />
99
+ </div>
100
+ <AnimatedInput label="Phone (for delivery updates)" id={`${id}-phone`} name="phone" type="tel" autocomplete="tel" />
101
+ </fieldset>
102
+
103
+ <fieldset class="checkout__section">
104
+ <legend><span class="checkout__num" aria-hidden="true">3</span> Shipping method</legend>
105
+ <div class="checkout__rates">
106
+ {shippingRates.map((r, i) => (
107
+ <label class="checkout__rate">
108
+ <input type="radio" name="shipping" value={r.id} checked={i === 0} data-price={r.price} data-first={i === 0 ? '' : undefined} />
109
+ <span class="checkout__rate-dot" aria-hidden="true"></span>
110
+ <span class="checkout__rate-text">
111
+ <strong>{r.label}</strong>
112
+ <span>{r.eta}</span>
113
+ </span>
114
+ <span class="checkout__rate-price" data-rate-price>{r.price === 0 ? 'Free' : formatMoney(r.price, money)}</span>
115
+ </label>
116
+ ))}
117
+ </div>
118
+ </fieldset>
119
+
120
+ <fieldset class="checkout__section">
121
+ <legend><span class="checkout__num" aria-hidden="true">4</span> Payment</legend>
122
+ <p class="checkout__secure">
123
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
124
+ All transactions are secure and encrypted.
125
+ </p>
126
+ <CustomRadioGroup
127
+ name="payment"
128
+ horizontal
129
+ options={[
130
+ { label: 'Card', value: 'card', checked: true },
131
+ { label: 'PayPal', value: 'paypal' },
132
+ { label: 'Apple Pay', value: 'applepay' },
133
+ ]}
134
+ />
135
+ <div class="checkout__card" data-card-fields>
136
+ <div class="checkout__field">
137
+ <label for={`${id}-cc`}>Card number</label>
138
+ <input id={`${id}-cc`} name="cc" inputmode="numeric" autocomplete="cc-number" placeholder="1234 1234 1234 1234" required pattern="[0-9 ]{15,23}" maxlength="23" data-cc />
139
+ </div>
140
+ <div class="checkout__row">
141
+ <div class="checkout__field">
142
+ <label for={`${id}-exp`}>Expiry (MM/YY)</label>
143
+ <input id={`${id}-exp`} name="exp" inputmode="numeric" autocomplete="cc-exp" placeholder="MM/YY" required pattern="(0[1-9]|1[0-2])\/[0-9]{2}" maxlength="5" data-exp />
144
+ </div>
145
+ <div class="checkout__field">
146
+ <label for={`${id}-cvc`}>Security code</label>
147
+ <input id={`${id}-cvc`} name="cvc" inputmode="numeric" autocomplete="cc-csc" placeholder="CVC" required pattern="[0-9]{3,4}" maxlength="4" />
148
+ </div>
149
+ </div>
150
+ <div class="checkout__field">
151
+ <label for={`${id}-ccname`}>Name on card</label>
152
+ <input id={`${id}-ccname`} name="ccName" autocomplete="cc-name" required />
153
+ </div>
154
+ </div>
155
+ <p class="checkout__alt" data-alt-pay hidden>You will be redirected to complete the payment securely.</p>
156
+ </fieldset>
157
+
158
+ <p class="checkout__error" role="alert" data-error hidden></p>
159
+
160
+ <button class="checkout__pay" type="submit" data-pay>
161
+ <span class="checkout__spinner" aria-hidden="true"></span>
162
+ <span data-pay-label>Pay now</span>
163
+ </button>
164
+ {demo && <p class="checkout__demo">Demo checkout — no data leaves your browser. Try code <strong>ASTRO10</strong>.</p>}
165
+ </form>
166
+
167
+ <section class="checkout__done" data-done hidden aria-labelledby={`${id}-done-title`}>
168
+ <AnimatedCheckmark size={72} color="var(--c-success, #34d399)" />
169
+ <h2 id={`${id}-done-title`} tabindex="-1">Thank you for your order!</h2>
170
+ <p>Order <strong data-order-id>#—</strong> is confirmed. A receipt is on its way to <strong data-order-email>your inbox</strong>.</p>
171
+ <p class="checkout__done-total">Total paid: <strong data-order-total>—</strong></p>
172
+ <a class="checkout__pay checkout__pay--link" href={continueHref}>Continue shopping</a>
173
+ </section>
174
+ </div>
175
+
176
+ <script>
177
+ import { createCart } from './cart-store';
178
+
179
+ document.querySelectorAll<HTMLElement>('[data-checkout]:not([data-bound])').forEach((root) => {
180
+ root.dataset.bound = '';
181
+ const form = root.querySelector<HTMLFormElement>('[data-checkout-form]')!;
182
+ const done = root.querySelector<HTMLElement>('[data-done]')!;
183
+ const pay = root.querySelector<HTMLButtonElement>('[data-pay]')!;
184
+ const payLabel = root.querySelector<HTMLElement>('[data-pay-label]')!;
185
+ const error = root.querySelector<HTMLElement>('[data-error]')!;
186
+ const cardFields = root.querySelector<HTMLElement>('[data-card-fields]')!;
187
+ const altPay = root.querySelector<HTMLElement>('[data-alt-pay]')!;
188
+ const firstRatePrice = root.querySelector<HTMLElement>('[data-first] ~ [data-rate-price]');
189
+ const cart = createCart(root.dataset.key);
190
+ let total = { formatted: '', total: 0, count: 0 };
191
+ const firstRateText = firstRatePrice?.textContent || '';
192
+
193
+ document.addEventListener('ack:totals', (e) => {
194
+ const d = (e as CustomEvent).detail;
195
+ total = d;
196
+ payLabel.textContent = d.count ? `Pay ${d.formatted}` : 'Pay now';
197
+ if (firstRatePrice) firstRatePrice.textContent = d.freeStandard || (d.subtotal - d.discount) >= Number(root.dataset.free || 0) && Number(root.dataset.free) > 0 ? 'Free' : firstRateText;
198
+ });
199
+ document.dispatchEvent(new CustomEvent('ack:totals-request'));
200
+
201
+ // Payment method toggles the card fields (and their `required`).
202
+ const setMethod = () => {
203
+ const method = (form.elements.namedItem('payment') as RadioNodeList)?.value || 'card';
204
+ const isCard = method === 'card';
205
+ cardFields.hidden = !isCard;
206
+ altPay.hidden = isCard;
207
+ cardFields.querySelectorAll('input').forEach((i) => { i.required = isCard; });
208
+ };
209
+ form.addEventListener('change', (e) => {
210
+ if ((e.target as HTMLInputElement).name === 'payment') setMethod();
211
+ });
212
+ setMethod();
213
+
214
+ // Light input masks
215
+ form.querySelector<HTMLInputElement>('[data-cc]')?.addEventListener('input', (e) => {
216
+ const el = e.target as HTMLInputElement;
217
+ el.value = el.value.replace(/\D/g, '').slice(0, 19).replace(/(\d{4})(?=\d)/g, '$1 ');
218
+ });
219
+ form.querySelector<HTMLInputElement>('[data-exp]')?.addEventListener('input', (e) => {
220
+ const el = e.target as HTMLInputElement;
221
+ const digits = el.value.replace(/\D/g, '').slice(0, 4);
222
+ el.value = digits.length > 2 ? `${digits.slice(0, 2)}/${digits.slice(2)}` : digits;
223
+ });
224
+
225
+ form.addEventListener('submit', (e) => {
226
+ e.preventDefault();
227
+ error.hidden = true;
228
+ form.querySelectorAll('[aria-invalid]').forEach((n) => n.removeAttribute('aria-invalid'));
229
+ if (!cart.items().length) {
230
+ error.textContent = 'Your cart is empty — add a product before checking out.';
231
+ error.hidden = false;
232
+ return;
233
+ }
234
+ if (!form.checkValidity()) {
235
+ const invalid = Array.from(form.querySelectorAll<HTMLInputElement>('input, select')).filter((n) => !n.checkValidity());
236
+ invalid.forEach((n) => n.setAttribute('aria-invalid', 'true'));
237
+ error.textContent = `Please check ${invalid.length === 1 ? '1 field' : `${invalid.length} fields`} highlighted in red.`;
238
+ error.hidden = false;
239
+ invalid[0]?.focus();
240
+ return;
241
+ }
242
+ pay.disabled = true;
243
+ pay.classList.add('is-loading');
244
+ payLabel.textContent = 'Processing…';
245
+
246
+ const finish = () => {
247
+ const orderId = `#${Math.floor(100000 + Math.random() * 900000)}`;
248
+ const email = (form.elements.namedItem('email') as HTMLInputElement).value;
249
+ root.querySelector('[data-order-id]')!.textContent = orderId;
250
+ root.querySelector('[data-order-email]')!.textContent = email;
251
+ root.querySelector('[data-order-total]')!.textContent = total.formatted;
252
+ form.hidden = true;
253
+ done.hidden = false;
254
+ cart.clear();
255
+ done.querySelector<HTMLElement>('h2')?.focus();
256
+ document.dispatchEvent(new CustomEvent('ack:order-placed', { detail: { orderId, total: total.total } }));
257
+ window.scrollTo({ top: 0, behavior: 'smooth' });
258
+ };
259
+ // Demo mode: simulate the payment round-trip. Replace with your provider's SDK.
260
+ window.setTimeout(finish, root.hasAttribute('data-demo') ? 1100 : 0);
261
+ });
262
+ });
263
+ </script>
264
+
265
+ <style>
266
+ .checkout { container-type: inline-size; min-width: 0; }
267
+ .checkout__form { display: grid; gap: var(--sp-5, 1.25rem); }
268
+ .checkout__form[hidden], .checkout__done[hidden] { display: none; }
269
+
270
+ .checkout__section {
271
+ display: grid;
272
+ gap: var(--sp-3, 0.75rem);
273
+ min-width: 0;
274
+ margin: 0;
275
+ padding: 0;
276
+ border: 0;
277
+ }
278
+ .checkout--cards .checkout__section {
279
+ padding: var(--sp-6, 1.5rem);
280
+ border-radius: var(--r-lg, 20px);
281
+ border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
282
+ background: var(--c-surface, rgb(var(--ink-rgb, 255 255 255) / 0.02));
283
+ }
284
+ .checkout__section legend {
285
+ display: flex;
286
+ align-items: center;
287
+ gap: var(--sp-3, 0.75rem);
288
+ float: left;
289
+ width: 100%;
290
+ margin-bottom: var(--sp-2, 0.5rem);
291
+ font-size: 1.05rem;
292
+ font-weight: 800;
293
+ color: var(--c-text-1, #fff);
294
+ }
295
+ .checkout__section legend + * { clear: both; }
296
+ .checkout__num {
297
+ display: grid;
298
+ place-items: center;
299
+ width: 28px;
300
+ height: 28px;
301
+ border-radius: 50%;
302
+ background: rgba(212, 160, 23, 0.14);
303
+ color: var(--c-primary-light, #d4a017);
304
+ font-size: 0.82rem;
305
+ }
306
+
307
+ .checkout__row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3, 0.75rem); }
308
+ .checkout__row--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
309
+
310
+ /* Reused form components, tuned to the store palette */
311
+ .checkout :global(.anim-input:focus),
312
+ .checkout :global(.custom-select:focus) { border-color: var(--c-primary-light, #d4a017); box-shadow: 0 0 0 3px rgba(212, 160, 23, 0.14); background: rgba(212, 160, 23, 0.04); }
313
+ .checkout :global(.anim-input:focus ~ .anim-input-label),
314
+ .checkout :global(.anim-input:not(:placeholder-shown) ~ .anim-input-label) { color: var(--c-primary-light, #d4a017); }
315
+ .checkout :global(.anim-input-label),
316
+ .checkout :global(.select-label) { color: var(--c-text-2, #a3a3a3); }
317
+ .checkout :global([aria-invalid='true']) { border-color: var(--c-danger, #f87171) !important; box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.14) !important; }
318
+ .checkout :global(.radio-circle::after) { background: var(--c-primary-light, #d4a017); }
319
+ .checkout :global(.radio-item input:checked + .radio-circle) { border-color: var(--c-primary-light, #d4a017); box-shadow: 0 0 10px rgba(212, 160, 23, 0.25); }
320
+ .checkout :global(.radio-group-legend) { display: none; }
321
+
322
+ .checkout__check { position: relative; display: flex; align-items: center; gap: var(--sp-3, 0.75rem); min-height: 40px; font-size: 0.88rem; cursor: pointer; }
323
+ .checkout__check input { position: absolute; opacity: 0; width: 1px; height: 1px; }
324
+ .checkout__box { width: 18px; height: 18px; border-radius: 5px; border: 1.5px solid rgb(var(--ink-rgb, 255 255 255) / 0.25); flex-shrink: 0; transition: background 0.15s ease; }
325
+ .checkout__check input:checked + .checkout__box { background: var(--c-primary-light, #d4a017); border-color: var(--c-primary-light, #d4a017); box-shadow: inset 0 0 0 3px var(--c-bg-1, #0a0a0a); }
326
+ .checkout__check input:focus-visible + .checkout__box { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
327
+
328
+ .checkout__rates { display: grid; gap: var(--sp-2, 0.5rem); }
329
+ .checkout__rate {
330
+ position: relative;
331
+ display: flex;
332
+ align-items: center;
333
+ gap: var(--sp-3, 0.75rem);
334
+ min-height: 64px;
335
+ padding: var(--sp-3, 0.75rem) var(--sp-4, 1rem);
336
+ border-radius: var(--r-md, 12px);
337
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.1);
338
+ cursor: pointer;
339
+ transition: border-color 0.15s ease, background 0.15s ease;
340
+ }
341
+ .checkout__rate:hover { border-color: rgb(var(--ink-rgb, 255 255 255) / 0.22); }
342
+ .checkout__rate:has(input:checked) { border-color: var(--c-primary-light, #d4a017); background: rgba(212, 160, 23, 0.06); }
343
+ .checkout__rate input { position: absolute; opacity: 0; width: 1px; height: 1px; }
344
+ .checkout__rate-dot { width: 20px; height: 20px; border-radius: 50%; border: 2px solid rgb(var(--ink-rgb, 255 255 255) / 0.25); flex-shrink: 0; }
345
+ .checkout__rate input:checked + .checkout__rate-dot { border-color: var(--c-primary-light, #d4a017); box-shadow: inset 0 0 0 4px var(--c-bg-1, #0a0a0a); background: var(--c-primary-light, #d4a017); }
346
+ .checkout__rate input:focus-visible + .checkout__rate-dot { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
347
+ .checkout__rate-text { flex: 1; display: grid; gap: 1px; }
348
+ .checkout__rate-text strong { color: var(--c-text-1, #fff); font-size: 0.92rem; }
349
+ .checkout__rate-text span { font-size: 0.8rem; }
350
+ .checkout__rate-price { font-weight: 800; color: var(--c-text-1, #fff); }
351
+
352
+ .checkout__secure { display: flex; align-items: center; gap: var(--sp-2, 0.5rem); margin: 0; font-size: 0.82rem; color: var(--c-text-2, #a3a3a3); }
353
+ .checkout__card { display: grid; gap: var(--sp-3, 0.75rem); padding-top: var(--sp-2, 0.5rem); }
354
+ .checkout__card[hidden], .checkout__alt[hidden] { display: none; }
355
+ .checkout__field { display: grid; gap: var(--sp-2, 0.5rem); }
356
+ .checkout__field label { font-size: 0.82rem; font-weight: 600; color: var(--c-text-2, #a3a3a3); }
357
+ .checkout__field input {
358
+ width: 100%;
359
+ min-height: 50px;
360
+ padding: 0 var(--sp-4, 1rem);
361
+ border-radius: var(--r-md, 12px);
362
+ border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.1));
363
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.04);
364
+ color: var(--c-text-1, #fff);
365
+ font: inherit;
366
+ font-size: 0.95rem;
367
+ letter-spacing: 0.02em;
368
+ }
369
+ .checkout__field input::placeholder { color: var(--c-text-3, #737373); }
370
+ .checkout__field input:focus { outline: none; border-color: var(--c-primary-light, #d4a017); box-shadow: 0 0 0 3px rgba(212, 160, 23, 0.14); }
371
+ .checkout__alt { margin: 0; padding: var(--sp-4, 1rem); border-radius: var(--r-md, 12px); background: rgb(var(--ink-rgb, 255 255 255) / 0.03); font-size: 0.88rem; }
372
+
373
+ .checkout__error { margin: 0; padding: var(--sp-3, 0.75rem) var(--sp-4, 1rem); border-radius: var(--r-md, 12px); background: rgba(248, 113, 113, 0.1); border: 1px solid rgba(248, 113, 113, 0.3); color: var(--c-danger, #fca5a5); font-size: 0.88rem; }
374
+ .checkout__error[hidden] { display: none; }
375
+
376
+ .checkout__pay {
377
+ position: relative;
378
+ display: inline-flex;
379
+ align-items: center;
380
+ justify-content: center;
381
+ gap: var(--sp-2, 0.5rem);
382
+ width: 100%;
383
+ min-height: 56px;
384
+ border: 0;
385
+ border-radius: var(--r-full, 9999px);
386
+ background: var(--c-primary-light, #d4a017);
387
+ color: var(--c-on-primary, #050505);
388
+ font: inherit;
389
+ font-size: 1rem;
390
+ font-weight: 800;
391
+ text-decoration: none;
392
+ cursor: pointer;
393
+ transition: filter 0.2s ease, transform 0.2s ease;
394
+ }
395
+ .checkout__pay:hover { filter: brightness(1.08); color: var(--c-on-primary, #050505); }
396
+ .checkout__pay:disabled { cursor: progress; filter: saturate(0.6); }
397
+ .checkout__pay:focus-visible { outline: 2px solid var(--c-text-1, #fff); outline-offset: 3px; }
398
+ .checkout__spinner { display: none; width: 18px; height: 18px; border-radius: 50%; border: 2.5px solid rgba(0, 0, 0, 0.25); border-top-color: var(--c-on-primary, #050505); animation: co-spin 0.7s linear infinite; }
399
+ .checkout__pay.is-loading .checkout__spinner { display: block; }
400
+ @keyframes co-spin { to { transform: rotate(360deg); } }
401
+ .checkout__demo { margin: 0; text-align: center; font-size: 0.8rem; color: var(--c-text-2, #a3a3a3); }
402
+
403
+ .checkout__done {
404
+ display: grid;
405
+ justify-items: center;
406
+ gap: var(--sp-3, 0.75rem);
407
+ padding: var(--sp-12, 3rem) var(--sp-6, 1.5rem);
408
+ border-radius: var(--r-xl, 28px);
409
+ border: 1px solid rgba(52, 211, 153, 0.25);
410
+ background: radial-gradient(circle at 50% 0%, rgba(52, 211, 153, 0.1), transparent 60%), var(--c-surface, rgb(var(--ink-rgb, 255 255 255) / 0.02));
411
+ text-align: center;
412
+ }
413
+ .checkout__done h2 { margin: 0; font-size: 1.8rem; font-weight: 900; letter-spacing: -0.03em; color: var(--c-text-1, #fff); outline: none; }
414
+ .checkout__done p { margin: 0; max-width: 32rem; }
415
+ .checkout__done strong { color: var(--c-text-1, #fff); }
416
+ .checkout__pay--link { width: auto; padding: 0 var(--sp-8, 2rem); margin-top: var(--sp-4, 1rem); }
417
+
418
+ @container (max-width: 560px) {
419
+ .checkout--cards .checkout__section { padding: var(--sp-4, 1rem); }
420
+ .checkout__row, .checkout__row--3 { grid-template-columns: 1fr; }
421
+ }
422
+ @media (prefers-reduced-motion: reduce) { .checkout__spinner { animation-duration: 2s; } }
423
+ </style>