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,113 @@
1
+ ---
2
+ /**
3
+ * AgencyServicesList — Numbered, expandable service rows: big service names, short pitch,
4
+ * deliverable chips, typical timeline and "from" price, with a hover/focus reveal.
5
+ *
6
+ * @param {string} eyebrow - Small label. Default 'What we do'.
7
+ * @param {string} title - Section heading. Default 'Services that move the needle'.
8
+ * @param {Array<{name: string, pitch: string, deliverables?: string[], timeline?: string, from?: string, href?: string}>} services - Rows.
9
+ * @param {number} open - Index opened by default (-1 for none). Default 0.
10
+ * @param {boolean} exclusive - Opening one row closes the others. Default true.
11
+ * @param {string} variant - Style (lines|cards). Default 'lines'.
12
+ * @param {string} className - Extra class.
13
+ *
14
+ * Accessibility: native <details>/<summary> rows (keyboard and screen-reader friendly); each row
15
+ * has a heading; the decorative number and arrow are aria-hidden.
16
+ */
17
+ interface Service { name: string; pitch: string; deliverables?: string[]; timeline?: string; from?: string; href?: string }
18
+ interface Props {
19
+ eyebrow?: string;
20
+ title?: string;
21
+ services?: Service[];
22
+ open?: number;
23
+ exclusive?: boolean;
24
+ variant?: 'lines' | 'cards';
25
+ className?: string;
26
+ }
27
+
28
+ const {
29
+ eyebrow = 'What we do',
30
+ title = 'Services that move the needle',
31
+ services = [
32
+ { name: 'Brand identity', pitch: 'Positioning, naming, logo systems and guidelines that make you unmistakable.', deliverables: ['Strategy workshop', 'Visual identity', 'Brand book', 'Launch kit'], timeline: '6–8 weeks', from: '€18k' },
33
+ { name: 'Websites', pitch: 'Fast, accessible Astro sites with a CMS your team will actually enjoy.', deliverables: ['UX & content', 'Design system', 'Astro build', 'CMS & analytics'], timeline: '8–12 weeks', from: '€24k' },
34
+ { name: 'Growth marketing', pitch: 'Performance campaigns, SEO and CRO run by people who read the numbers.', deliverables: ['Paid social', 'SEO', 'A/B testing', 'Monthly reporting'], timeline: 'Monthly', from: '€6k / mo' },
35
+ { name: 'Content & social', pitch: 'Always-on content engines: shoots, short video, copy and community.', deliverables: ['Editorial calendar', 'Production', 'Community'], timeline: 'Monthly', from: '€4k / mo' },
36
+ ],
37
+ open = 0,
38
+ exclusive = true,
39
+ variant = 'lines',
40
+ className = '',
41
+ } = Astro.props;
42
+ ---
43
+
44
+ <div class="cq-agency-services">
45
+ <section class:list={['asl', `asl--${variant}`, className]} aria-labelledby="asl-title" data-services data-exclusive={exclusive ? '' : undefined}>
46
+ <header class="asl__head">
47
+ <p class="asl__eyebrow">{eyebrow}</p>
48
+ <h2 class="asl__title" id="asl-title">{title}</h2>
49
+ </header>
50
+ <div class="asl__list">
51
+ {services.map((s, i) => (
52
+ <details class="asl__row" open={i === open}>
53
+ <summary class="asl__summary">
54
+ <span class="asl__num" aria-hidden="true">{String(i + 1).padStart(2, '0')}</span>
55
+ <h3 class="asl__name">{s.name}</h3>
56
+ {s.from && <span class="asl__from">from {s.from}</span>}
57
+ <span class="asl__arrow" aria-hidden="true">↗</span>
58
+ </summary>
59
+ <div class="asl__body">
60
+ <p class="asl__pitch">{s.pitch}</p>
61
+ {s.deliverables && <ul class="asl__chips">{s.deliverables.map((d) => <li>{d}</li>)}</ul>}
62
+ <p class="asl__meta">{s.timeline && <span>Timeline · <strong>{s.timeline}</strong></span>}{s.href && <a href={s.href}>Learn more →</a>}</p>
63
+ </div>
64
+ </details>
65
+ ))}
66
+ </div>
67
+ </section>
68
+ </div>
69
+
70
+ <script>
71
+ document.querySelectorAll<HTMLElement>('[data-services][data-exclusive]:not([data-bound])').forEach((root) => {
72
+ root.dataset.bound = '';
73
+ const rows = Array.from(root.querySelectorAll<HTMLDetailsElement>('details'));
74
+ rows.forEach((d) => d.addEventListener('toggle', () => { if (d.open) rows.forEach((o) => { if (o !== d) o.open = false; }); }));
75
+ });
76
+ </script>
77
+
78
+ <style>
79
+ .cq-agency-services { container-type: inline-size; }
80
+ .asl { color: var(--c-text-1, #fff); }
81
+ .asl__head { display: grid; gap: var(--sp-2, 0.5rem); margin-bottom: var(--sp-6, 1.5rem); }
82
+ .asl__eyebrow { margin: 0; font-size: 0.8rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: var(--c-text-2, #a3a3a3); }
83
+ .asl__title { margin: 0; max-width: 18ch; font-size: clamp(2.2rem, 6cqi, 4.2rem); font-weight: 900; letter-spacing: -0.05em; line-height: 0.95; }
84
+ .asl__list { border-top: 1.5px solid currentColor; }
85
+ .asl--cards .asl__list { display: grid; gap: var(--sp-3, 0.75rem); border: 0; }
86
+ .asl__row { border-bottom: 1.5px solid var(--c-border, rgba(255, 255, 255, 0.15)); }
87
+ .asl--cards .asl__row { border: 1px solid var(--c-border, rgba(255, 255, 255, 0.12)); border-radius: 24px; padding: 0 var(--sp-5, 1.25rem); }
88
+ .asl__summary { display: grid; grid-template-columns: 56px minmax(0, 1fr) auto 48px; align-items: center; gap: var(--sp-4, 1rem); min-height: 96px; cursor: pointer; list-style: none; }
89
+ .asl__summary::-webkit-details-marker { display: none; }
90
+ .asl__summary:focus-visible { outline: 2px solid var(--agency-accent, var(--c-primary-light, #d4a017)); outline-offset: 4px; border-radius: 12px; }
91
+ .asl__num { font-size: 0.85rem; font-weight: 700; color: var(--c-text-3, #737373); font-variant-numeric: tabular-nums; }
92
+ .asl__name { margin: 0; font-size: clamp(1.6rem, 4.4cqi, 3rem); font-weight: 800; letter-spacing: -0.045em; line-height: 1; transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
93
+ .asl__summary:hover .asl__name { transform: translateX(10px); }
94
+ .asl__from { padding: 6px 12px; border-radius: 999px; border: 1px solid var(--c-border, rgba(255, 255, 255, 0.2)); font-size: 0.82rem; font-weight: 700; color: var(--c-text-2, #a3a3a3); white-space: nowrap; }
95
+ .asl__arrow { display: grid; place-items: center; width: 48px; height: 48px; border-radius: 50%; border: 1.5px solid currentColor; font-size: 1.1rem; transition: transform 0.3s ease, background 0.3s ease, color 0.3s ease; }
96
+ .asl__row[open] .asl__arrow { transform: rotate(90deg); background: var(--agency-accent, var(--c-primary-light, #d4a017)); color: var(--c-on-primary, #050505); border-color: transparent; }
97
+ .asl__body { display: grid; gap: var(--sp-4, 1rem); padding: 0 0 var(--sp-6, 1.5rem) calc(56px + var(--sp-4, 1rem)); animation: asl-in 0.35s ease-out; }
98
+ @keyframes asl-in { from { opacity: 0; transform: translateY(-6px); } }
99
+ .asl__pitch { margin: 0; max-width: 46rem; font-size: 1.1rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
100
+ .asl__chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; }
101
+ .asl__chips li { padding: 8px 14px; border-radius: 999px; background: rgb(var(--ink-rgb, 255 255 255) / 0.07); font-size: 0.85rem; font-weight: 600; }
102
+ .asl__meta { display: flex; flex-wrap: wrap; gap: var(--sp-5, 1.25rem); margin: 0; font-size: 0.88rem; color: var(--c-text-2, #a3a3a3); }
103
+ .asl__meta strong { color: var(--c-text-1, #fff); }
104
+ .asl__meta a { color: var(--c-text-1, #fff); font-weight: 700; }
105
+
106
+ @container (max-width: 640px) {
107
+ .asl__summary { grid-template-columns: 36px minmax(0, 1fr) 44px; min-height: 80px; }
108
+ .asl__from { display: none; }
109
+ .asl__arrow { width: 44px; height: 44px; }
110
+ .asl__body { padding-left: 0; }
111
+ }
112
+ @media (prefers-reduced-motion: reduce) { .asl__name, .asl__arrow, .asl__body { transition: none; animation: none; } }
113
+ </style>
@@ -0,0 +1,111 @@
1
+ ---
2
+ /**
3
+ * AgencyTeamGrid — Team portraits with name, role, location, a personal "fun fact" revealed on
4
+ * hover/focus and social links, plus an optional "we're hiring" card.
5
+ *
6
+ * @param {string} eyebrow - Small label. Default 'The studio'.
7
+ * @param {string} title - Heading. Default 'Small team, senior people'.
8
+ * @param {string} text - Intro paragraph.
9
+ * @param {Array<{name: string, role: string, location?: string, fact?: string, photo?: string, links?: {label: string, href: string}[]}>} people - Team members.
10
+ * @param {{title: string, text: string, href: string, label?: string}} hiring - Hiring card (optional).
11
+ * @param {number} columns - Desktop columns (3–4). Default 4.
12
+ * @param {string} variant - Style (portrait|compact). Default 'portrait'.
13
+ * @param {string} className - Extra class.
14
+ *
15
+ * Accessibility: each person is an article with a heading; the fun fact is real text (visible on
16
+ * hover and on keyboard focus of the card); social links have descriptive labels.
17
+ */
18
+ interface Person { name: string; role: string; location?: string; fact?: string; photo?: string; links?: Array<{ label: string; href: string }> }
19
+ interface Props {
20
+ eyebrow?: string;
21
+ title?: string;
22
+ text?: string;
23
+ people?: Person[];
24
+ hiring?: { title: string; text: string; href: string; label?: string };
25
+ columns?: 3 | 4;
26
+ variant?: 'portrait' | 'compact';
27
+ className?: string;
28
+ }
29
+
30
+ const {
31
+ eyebrow = 'The studio',
32
+ title = 'Small team, senior people',
33
+ text = 'No juniors hidden behind account managers. You work directly with the people who do the work.',
34
+ people = [
35
+ { name: 'Marta Ibáñez', role: 'Founder & creative director', location: 'Madrid', fact: 'Designed her first logo at 12 — for her cat.', links: [{ label: 'LinkedIn', href: '#' }] },
36
+ { name: 'Jonas Weber', role: 'Head of design', location: 'Berlin', fact: 'Owns 41 typefaces, uses three.', links: [{ label: 'Dribbble', href: '#' }] },
37
+ { name: 'Aisha Bello', role: 'Growth lead', location: 'Lisbon', fact: 'Ran a marathon and a product launch the same week.', links: [{ label: 'LinkedIn', href: '#' }] },
38
+ { name: 'Leo Martins', role: 'Lead developer', location: 'Remote', fact: 'Ships to production from a sailboat.', links: [{ label: 'GitHub', href: '#' }] },
39
+ ],
40
+ hiring = { title: 'We are hiring', text: 'Senior designers and Astro developers — remote in Europe.', href: '#', label: 'See open roles' },
41
+ columns = 4,
42
+ variant = 'portrait',
43
+ className = '',
44
+ } = Astro.props;
45
+
46
+ const hues = [80, 280, 20, 190, 330, 140];
47
+ const initials = (n: string) => n.split(/\s+/).slice(0, 2).map((p) => p[0]).join('');
48
+ ---
49
+
50
+ <div class="cq-agency-team">
51
+ <section class:list={['atg', `atg--${variant}`, className]} style={`--cols: ${columns}`} aria-labelledby="atg-title">
52
+ <header class="atg__head">
53
+ <p class="atg__eyebrow">{eyebrow}</p>
54
+ <h2 class="atg__title" id="atg-title">{title}</h2>
55
+ {text && <p class="atg__text">{text}</p>}
56
+ </header>
57
+ <div class="atg__grid">
58
+ {people.map((p, i) => (
59
+ <article class="atg__person" tabindex="0" aria-labelledby={`atg-p-${i}`}>
60
+ <div class="atg__photo" style={`--h: ${hues[i % hues.length]}`}>
61
+ {p.photo ? <img src={p.photo} alt={`Portrait of ${p.name}`} loading="lazy" decoding="async" /> : <span class="atg__initials" aria-hidden="true">{initials(p.name)}</span>}
62
+ {p.fact && <p class="atg__fact"><span>Fun fact</span>{p.fact}</p>}
63
+ </div>
64
+ <h3 class="atg__name" id={`atg-p-${i}`}>{p.name}</h3>
65
+ <p class="atg__role">{p.role}{p.location && <span> · {p.location}</span>}</p>
66
+ {p.links && <p class="atg__links">{p.links.map((l) => <a href={l.href} aria-label={`${p.name} on ${l.label}`}>{l.label}</a>)}</p>}
67
+ </article>
68
+ ))}
69
+ {hiring && (
70
+ <a class="atg__hiring" href={hiring.href}>
71
+ <strong>{hiring.title}</strong>
72
+ <span>{hiring.text}</span>
73
+ <em>{hiring.label ?? 'Open roles'} ↗</em>
74
+ </a>
75
+ )}
76
+ </div>
77
+ </section>
78
+ </div>
79
+
80
+ <style>
81
+ .cq-agency-team { container-type: inline-size; }
82
+ .atg { color: var(--c-text-1, #fff); }
83
+ .atg__head { display: grid; gap: var(--sp-2, 0.5rem); max-width: 46rem; margin-bottom: var(--sp-8, 2rem); }
84
+ .atg__eyebrow { margin: 0; font-size: 0.8rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: var(--c-text-2, #a3a3a3); }
85
+ .atg__title { margin: 0; font-size: clamp(2.2rem, 6cqi, 4.2rem); font-weight: 900; letter-spacing: -0.05em; line-height: 0.95; }
86
+ .atg__text { margin: var(--sp-2, 0.5rem) 0 0; font-size: 1.05rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
87
+ .atg__grid { display: grid; grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); gap: clamp(1rem, 2.5cqi, 1.75rem); }
88
+ .atg__person { display: grid; gap: 4px; outline: none; }
89
+ .atg__photo { position: relative; aspect-ratio: 4 / 5; border-radius: 24px; overflow: hidden; background: radial-gradient(circle at 30% 25%, hsl(var(--h) 80% 70%), transparent 60%), hsl(var(--h) 40% 22%); margin-bottom: var(--sp-3, 0.75rem); }
90
+ .atg--compact .atg__photo { aspect-ratio: 1; }
91
+ .atg__photo img { width: 100%; height: 100%; object-fit: cover; }
92
+ .atg__initials { position: absolute; inset: 0; display: grid; place-items: center; font-size: clamp(3rem, 9cqi, 5.5rem); font-weight: 900; letter-spacing: -0.06em; color: rgba(255, 255, 255, 0.9); }
93
+ .atg__fact { position: absolute; inset: auto 10px 10px; margin: 0; padding: var(--sp-3, 0.75rem) var(--sp-4, 1rem); border-radius: 16px; background: var(--agency-accent, var(--c-primary-light, #d4a017)); color: var(--c-on-primary, #050505); font-size: 0.88rem; font-weight: 600; line-height: 1.4; opacity: 0; transform: translateY(12px) rotate(-1.5deg); transition: opacity 0.3s ease, transform 0.35s cubic-bezier(0.16, 1, 0.3, 1); }
94
+ .atg__fact span { display: block; font-size: 0.68rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; opacity: 0.7; }
95
+ .atg__person:hover .atg__fact, .atg__person:focus-within .atg__fact, .atg__person:focus .atg__fact { opacity: 1; transform: rotate(-1.5deg); }
96
+ .atg__person:focus-visible .atg__photo { outline: 3px solid var(--agency-accent, var(--c-primary-light, #d4a017)); outline-offset: 3px; }
97
+ .atg__name { margin: 0; font-size: 1.2rem; font-weight: 800; letter-spacing: -0.02em; }
98
+ .atg__role { margin: 0; font-size: 0.88rem; color: var(--c-text-2, #a3a3a3); }
99
+ .atg__links { display: flex; gap: var(--sp-3, 0.75rem); margin: 4px 0 0; }
100
+ .atg__links a { min-height: 32px; display: inline-flex; align-items: center; font-size: 0.85rem; font-weight: 700; color: var(--c-text-1, #fff); text-decoration: underline; text-underline-offset: 3px; }
101
+ .atg__links a:focus-visible, .atg__hiring:focus-visible { outline: 2px solid var(--agency-accent, var(--c-primary-light, #d4a017)); outline-offset: 3px; }
102
+ .atg__hiring { display: flex; flex-direction: column; justify-content: flex-end; gap: var(--sp-2, 0.5rem); aspect-ratio: 4 / 5; padding: var(--sp-6, 1.5rem); border-radius: 24px; border: 2px dashed var(--c-border, rgba(255, 255, 255, 0.25)); color: inherit; text-decoration: none; transition: background 0.3s ease, border-color 0.3s ease; }
103
+ .atg__hiring:hover { background: var(--agency-accent, var(--c-primary-light, #d4a017)); border-color: transparent; color: var(--c-on-primary, #050505); }
104
+ .atg__hiring strong { font-size: 1.8rem; font-weight: 900; letter-spacing: -0.04em; line-height: 1; }
105
+ .atg__hiring span { font-size: 0.92rem; opacity: 0.8; }
106
+ .atg__hiring em { font-style: normal; font-weight: 800; }
107
+ @container (max-width: 900px) { .atg__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
108
+ @container (max-width: 460px) { .atg__grid { grid-template-columns: 1fr; } .atg__hiring { aspect-ratio: auto; min-height: 200px; } }
109
+ @media (hover: none) { .atg__fact { position: static; opacity: 1; transform: none; margin: var(--sp-2, 0.5rem); } .atg__photo { display: flex; flex-direction: column; justify-content: flex-end; } }
110
+ @media (prefers-reduced-motion: reduce) { .atg__fact, .atg__hiring { transition: none; } }
111
+ </style>
@@ -0,0 +1,147 @@
1
+ ---
2
+ /**
3
+ * AgencyWorkGrid — Case-study grid with category filter chips: large art tiles, client, title,
4
+ * services and a headline result, in an alternating big/small rhythm.
5
+ *
6
+ * @param {string} eyebrow - Small label. Default 'Selected work'.
7
+ * @param {string} title - Heading. Default 'Work we are proud of'.
8
+ * @param {Array<{slug: string, client: string, title: string, category: string, services?: string[], result?: string, year?: string, hue?: number, image?: string, href?: string}>} projects - Case studies.
9
+ * @param {boolean} filters - Show category chips. Default true.
10
+ * @param {{label: string, href: string}} link - "All work" link (optional).
11
+ * @param {string} variant - Layout (mosaic|grid). Default 'mosaic'.
12
+ * @param {string} className - Extra class.
13
+ *
14
+ * Accessibility: each card is one link with a heading; filter chips are toggle buttons and the
15
+ * number of visible projects is announced; art is decorative.
16
+ */
17
+ interface Project { slug: string; client: string; title: string; category: string; services?: string[]; result?: string; year?: string; hue?: number; image?: string; href?: string }
18
+ interface Props {
19
+ eyebrow?: string;
20
+ title?: string;
21
+ projects?: Project[];
22
+ filters?: boolean;
23
+ link?: { label: string; href: string };
24
+ variant?: 'mosaic' | 'grid';
25
+ className?: string;
26
+ }
27
+
28
+ const {
29
+ eyebrow = 'Selected work',
30
+ title = 'Work we are proud of',
31
+ projects = [
32
+ { slug: 'nordlys', client: 'Nordlys', title: 'A Nordic skincare brand that sold out its launch', category: 'Brand', services: ['Identity', 'Packaging'], result: '+212% preorders', year: '2026', hue: 190 },
33
+ { slug: 'pace', client: 'Pace', title: 'A running app redesign that doubled retention', category: 'Product', services: ['UX', 'UI'], result: '2× day-30 retention', year: '2025', hue: 80 },
34
+ { slug: 'casa-alba', client: 'Casa Alba', title: 'Direct bookings for a boutique hotel group', category: 'Web', services: ['Website', 'SEO'], result: '+64% direct revenue', year: '2025', hue: 25 },
35
+ { slug: 'fintrack', client: 'Fintrack', title: 'Paid social that halved the cost per signup', category: 'Growth', services: ['Paid social', 'CRO'], result: '−48% CPA', year: '2026', hue: 280 },
36
+ ],
37
+ filters = true,
38
+ link = { label: 'All case studies', href: '#' },
39
+ variant = 'mosaic',
40
+ className = '',
41
+ } = Astro.props;
42
+
43
+ const cats = Array.from(new Set(projects.map((p) => p.category)));
44
+ ---
45
+
46
+ <div class="cq-agency-work">
47
+ <section class:list={['awg', `awg--${variant}`, className]} aria-labelledby="awg-title" data-work>
48
+ <header class="awg__head">
49
+ <div>
50
+ <p class="awg__eyebrow">{eyebrow}</p>
51
+ <h2 class="awg__title" id="awg-title">{title}</h2>
52
+ </div>
53
+ {link && <a class="awg__all" href={link.href}>{link.label} <span aria-hidden="true">↗</span></a>}
54
+ </header>
55
+ {filters && cats.length > 1 && (
56
+ <div class="awg__chips" role="group" aria-label="Filter by discipline">
57
+ <button type="button" aria-pressed="true" data-cat="">All <sup>{projects.length}</sup></button>
58
+ {cats.map((c) => <button type="button" aria-pressed="false" data-cat={c}>{c} <sup>{projects.filter((p) => p.category === c).length}</sup></button>)}
59
+ </div>
60
+ )}
61
+ <p class="sr-only" aria-live="polite" data-announce></p>
62
+ <ul class="awg__grid">
63
+ {projects.map((p, i) => (
64
+ <li class:list={['awg__item', { 'awg__item--wide': variant === 'mosaic' && i % 3 === 0 }]} data-cat={p.category}>
65
+ <a class="awg__card" href={p.href ?? '#'}>
66
+ <span class="awg__art" style={`--h: ${p.hue ?? 80}`} aria-hidden="true">
67
+ {p.image ? <img src={p.image} alt="" loading="lazy" decoding="async" /> : <span class="awg__glyph">{p.client.slice(0, 1)}</span>}
68
+ {p.result && <span class="awg__result">{p.result}</span>}
69
+ </span>
70
+ <span class="awg__meta"><span>{p.client}</span>{p.year && <span>{p.year}</span>}</span>
71
+ <h3 class="awg__name">{p.title}</h3>
72
+ {p.services && <span class="awg__tags">{p.services.map((s) => <span>{s}</span>)}</span>}
73
+ </a>
74
+ </li>
75
+ ))}
76
+ </ul>
77
+ </section>
78
+ </div>
79
+
80
+ <script>
81
+ document.querySelectorAll<HTMLElement>('[data-work]:not([data-bound])').forEach((root) => {
82
+ root.dataset.bound = '';
83
+ const chips = Array.from(root.querySelectorAll<HTMLButtonElement>('[data-cat]')).filter((b) => b.tagName === 'BUTTON');
84
+ const items = Array.from(root.querySelectorAll<HTMLElement>('li[data-cat]'));
85
+ const announce = root.querySelector<HTMLElement>('[data-announce]')!;
86
+ chips.forEach((c) => c.addEventListener('click', () => {
87
+ const cat = c.dataset.cat || '';
88
+ chips.forEach((x) => x.setAttribute('aria-pressed', String(x === c)));
89
+ let n = 0;
90
+ items.forEach((it) => { const show = !cat || it.dataset.cat === cat; it.hidden = !show; if (show) n++; });
91
+ announce.textContent = `${n} ${n === 1 ? 'project' : 'projects'} shown`;
92
+ }));
93
+ });
94
+ </script>
95
+
96
+ <style>
97
+ .cq-agency-work { container-type: inline-size; }
98
+ .awg { color: var(--c-text-1, #fff); }
99
+ .awg__head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--sp-4, 1rem); margin-bottom: var(--sp-5, 1.25rem); }
100
+ .awg__eyebrow { margin: 0 0 var(--sp-2, 0.5rem); font-size: 0.8rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: var(--c-text-2, #a3a3a3); }
101
+ .awg__title { margin: 0; font-size: clamp(2.2rem, 6cqi, 4.2rem); font-weight: 900; letter-spacing: -0.05em; line-height: 0.95; }
102
+ .awg__all { display: inline-flex; align-items: center; gap: 6px; min-height: 48px; padding: 0 1.2rem; border-radius: 999px; border: 1.5px solid currentColor; color: inherit; font-weight: 800; text-decoration: none; white-space: nowrap; }
103
+ .awg__all:hover { background: var(--c-text-1, #fff); color: var(--c-bg, #050505); }
104
+ .awg__chips { display: flex; gap: 6px; margin-bottom: var(--sp-6, 1.5rem); overflow-x: auto; scrollbar-width: none; }
105
+ .awg__chips button { min-height: 42px; padding: 0 1rem; border-radius: 999px; border: 1.5px solid var(--c-border, rgba(255, 255, 255, 0.2)); background: transparent; color: var(--c-text-1, #fff); font: inherit; font-size: 0.9rem; font-weight: 700; white-space: nowrap; cursor: pointer; }
106
+ .awg__chips sup { font-size: 0.65rem; opacity: 0.7; }
107
+ .awg__chips [aria-pressed='true'] { background: var(--c-text-1, #fff); color: var(--c-bg, #050505); border-color: var(--c-text-1, #fff); }
108
+
109
+ .awg__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(1rem, 3cqi, 2rem); margin: 0; padding: 0; list-style: none; }
110
+ .awg--grid .awg__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
111
+ .awg__item[hidden] { display: none; }
112
+ .awg__item--wide { grid-column: 1 / -1; }
113
+ .awg__card { display: grid; gap: var(--sp-2, 0.5rem); color: inherit; text-decoration: none; }
114
+ .awg__art {
115
+ position: relative;
116
+ display: grid;
117
+ place-items: center;
118
+ aspect-ratio: 4 / 3;
119
+ border-radius: 24px;
120
+ overflow: hidden;
121
+ background:
122
+ radial-gradient(circle at 25% 25%, hsl(var(--h) 85% 65%), transparent 55%),
123
+ radial-gradient(circle at 80% 80%, hsl(calc(var(--h) + 50) 80% 55%), transparent 55%),
124
+ hsl(var(--h) 45% 18%);
125
+ transition: border-radius 0.4s ease;
126
+ }
127
+ .awg__item--wide .awg__art { aspect-ratio: 21 / 9; }
128
+ .awg__art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
129
+ .awg__glyph { font-size: clamp(6rem, 22cqi, 16rem); font-weight: 900; letter-spacing: -0.08em; line-height: 1; color: rgba(255, 255, 255, 0.92); mix-blend-mode: overlay; transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1); }
130
+ .awg__card:hover .awg__glyph { transform: scale(1.08) rotate(-4deg); }
131
+ .awg__card:hover .awg__art { border-radius: 48px; }
132
+ .awg__result { position: absolute; left: 16px; bottom: 16px; padding: 8px 14px; border-radius: 999px; background: var(--agency-accent, var(--c-primary-light, #d4a017)); color: var(--c-on-primary, #050505); font-size: 0.88rem; font-weight: 800; transform: rotate(-2deg); }
133
+ .awg__meta { display: flex; justify-content: space-between; margin-top: var(--sp-2, 0.5rem); font-size: 0.85rem; font-weight: 700; color: var(--c-text-2, #a3a3a3); }
134
+ .awg__name { margin: 0; font-size: clamp(1.25rem, 2.6cqi, 1.8rem); font-weight: 800; letter-spacing: -0.03em; line-height: 1.15; }
135
+ .awg__card:hover .awg__name { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 4px; }
136
+ .awg__tags { display: flex; flex-wrap: wrap; gap: 6px; }
137
+ .awg__tags span { padding: 4px 10px; border-radius: 999px; border: 1px solid var(--c-border, rgba(255, 255, 255, 0.18)); font-size: 0.78rem; color: var(--c-text-2, #a3a3a3); }
138
+ .awg :is(a, button):focus-visible { outline: 2px solid var(--agency-accent, var(--c-primary-light, #d4a017)); outline-offset: 4px; }
139
+ .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
140
+
141
+ @container (max-width: 720px) {
142
+ .awg__grid, .awg--grid .awg__grid { grid-template-columns: 1fr; }
143
+ .awg__item--wide .awg__art { aspect-ratio: 4 / 3; }
144
+ .awg__head { flex-direction: column; align-items: flex-start; }
145
+ }
146
+ @media (prefers-reduced-motion: reduce) { .awg__glyph, .awg__art { transition: none; } }
147
+ </style>
@@ -0,0 +1,133 @@
1
+ ---
2
+ /**
3
+ * BlogGridSection — Latest articles section with one featured post and a grid of
4
+ * smaller posts. Covers are CSS gradients by default (pass `image` for photos).
5
+ *
6
+ * @param {string} title - Section heading.
7
+ * @param {{label: string, href: string}} viewAll - Link to the full blog.
8
+ * @param {Array<{title: string, excerpt: string, category: string, date: string, readTime: string, author: string, href: string, image?: string}>} posts - Posts (first is featured).
9
+ * @param {string} variant - Card style (cards|minimal|overlay).
10
+ * @param {string} size - Vertical rhythm (sm|md|lg).
11
+ * @param {string} className - Extra CSS class.
12
+ */
13
+ interface Post { title: string; excerpt: string; category: string; date: string; readTime: string; author: string; href: string; image?: string }
14
+ interface Props {
15
+ title?: string;
16
+ viewAll?: { label: string; href: string };
17
+ posts?: Post[];
18
+ variant?: 'cards' | 'minimal' | 'overlay';
19
+ size?: 'sm' | 'md' | 'lg';
20
+ className?: string;
21
+ }
22
+
23
+ const {
24
+ title = 'From the blog',
25
+ viewAll = { label: 'View all posts', href: '#' },
26
+ posts = [
27
+ { title: 'Building zero-JS charts with SVG and Astro', excerpt: 'How we replaced a 200 kB charting library with build-time SVG, without losing tooltips or accessibility.', category: 'Engineering', date: '2026-10-28', readTime: '8 min', author: 'Marcus Lee', href: '#' },
28
+ { title: 'Designing for dark mode first', excerpt: 'Contrast, elevation and color tokens that hold up at night.', category: 'Design', date: '2026-10-21', readTime: '5 min', author: 'Sofía Ruiz', href: '#' },
29
+ { title: 'The accessibility checklist we run on every component', excerpt: 'Focus, keyboard, contrast and screen readers — our exact process.', category: 'Accessibility', date: '2026-10-14', readTime: '6 min', author: 'Priya Natarajan', href: '#' },
30
+ { title: 'Landing page anatomy: 7 sections that convert', excerpt: 'A data-backed look at hero, proof, pricing and FAQ placement.', category: 'Growth', date: '2026-10-07', readTime: '7 min', author: 'Alicia Gómez', href: '#' },
31
+ ],
32
+ variant = 'cards',
33
+ size = 'md',
34
+ className = '',
35
+ } = Astro.props;
36
+
37
+ const id = `bgs-${Math.random().toString(36).slice(2, 8)}`;
38
+ const gradients = [
39
+ 'radial-gradient(circle at 20% 20%, rgba(57,135,229,.65), transparent 55%), radial-gradient(circle at 80% 80%, rgba(144,133,233,.55), transparent 50%), #101018',
40
+ 'radial-gradient(circle at 70% 30%, color-mix(in srgb, var(--c-primary-light, #d4a017) 60%, transparent), transparent 50%), #15120a',
41
+ 'radial-gradient(circle at 30% 70%, rgba(25,158,112,.6), transparent 55%), #0b1411',
42
+ 'radial-gradient(circle at 60% 40%, rgba(213,81,129,.55), transparent 55%), #150b10',
43
+ ];
44
+ const fmtDate = (d: string) => new Date(`${d}T00:00:00Z`).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' });
45
+ ---
46
+
47
+ <div class="cq-blog-grid-section-cq">
48
+ <section class={`bgs bgs--${variant} bgs--${size} ${className}`} aria-labelledby={id}>
49
+ <div class="bgs__inner">
50
+ <header class="bgs__head">
51
+ <h2 id={id} class="bgs__title">{title}</h2>
52
+ {viewAll && (
53
+ <a class="bgs__all" href={viewAll.href}>
54
+ {viewAll.label}
55
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
56
+ </a>
57
+ )}
58
+ </header>
59
+ <ul class="bgs__grid">
60
+ {posts.map((p, i) => (
61
+ <li class:list={['bgs__post', { 'bgs__post--featured': i === 0 }]}>
62
+ <article class="bgs__card">
63
+ <div class="bgs__cover" style={p.image ? undefined : `background:${gradients[i % gradients.length]}`}>
64
+ {p.image && <img src={p.image} alt="" loading="lazy" decoding="async" />}
65
+ </div>
66
+ <div class="bgs__body">
67
+ <p class="bgs__meta">
68
+ <span class="bgs__cat">{p.category}</span>
69
+ <time datetime={p.date}>{fmtDate(p.date)}</time>
70
+ <span aria-hidden="true">·</span>
71
+ <span>{p.readTime} read</span>
72
+ </p>
73
+ <h3 class="bgs__post-title"><a href={p.href}>{p.title}</a></h3>
74
+ <p class="bgs__excerpt">{p.excerpt}</p>
75
+ <p class="bgs__author">By {p.author}</p>
76
+ </div>
77
+ </article>
78
+ </li>
79
+ ))}
80
+ </ul>
81
+ </div>
82
+ </section>
83
+ </div>
84
+ <style>
85
+ .cq-blog-grid-section-cq { container-type: inline-size; }
86
+ .bgs {
87
+ --bgs-py: 5.5rem;
88
+ padding: var(--bgs-py) var(--sp-6, 1.5rem);
89
+ background: var(--c-bg, #050505);
90
+ }
91
+ .bgs--sm { --bgs-py: 3.5rem; }
92
+ .bgs--lg { --bgs-py: 7.5rem; }
93
+ .bgs__inner { max-width: 1180px; margin: 0 auto; }
94
+ .bgs__head { display: flex; justify-content: space-between; align-items: flex-end; gap: 1rem; margin-bottom: 2rem; flex-wrap: wrap; }
95
+ .bgs__title { margin: 0; font-size: clamp(1.75rem, 4vw, 2.5rem); font-weight: 800; letter-spacing: -0.03em; color: var(--c-text-1, #fff); }
96
+ .bgs__all { display: inline-flex; align-items: center; gap: 0.4rem; min-height: 44px; font-weight: 600; color: var(--c-primary-light, #d4a017); text-decoration: none; }
97
+ .bgs__all:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; border-radius: 4px; }
98
+ .bgs__grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.25rem; }
99
+ .bgs__post--featured { grid-column: 1 / -1; }
100
+ .bgs__card { position: relative; height: 100%; display: flex; flex-direction: column; border-radius: var(--r-lg, 20px); overflow: hidden; transition: transform 0.25s ease, border-color 0.25s ease; }
101
+ .bgs--cards .bgs__card { background: rgb(var(--ink-rgb, 255 255 255) / 0.025); border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.07)); }
102
+ .bgs__card:hover, .bgs__card:focus-within { transform: translateY(-3px); border-color: rgb(var(--ink-rgb, 255 255 255) / 0.18); }
103
+ .bgs__post--featured .bgs__card { display: grid; grid-template-columns: 1.2fr 1fr; }
104
+ .bgs__cover { aspect-ratio: 16 / 9; position: relative; }
105
+ .bgs__post--featured .bgs__cover { aspect-ratio: auto; min-height: 280px; }
106
+ .bgs__cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
107
+ .bgs--minimal .bgs__cover { border-radius: var(--r-lg, 20px); }
108
+ .bgs__body { padding: 1.5rem; display: flex; flex-direction: column; gap: 0.6rem; }
109
+ .bgs--minimal .bgs__body { padding: 1.25rem 0 0; }
110
+ .bgs__post--featured .bgs__body { justify-content: center; padding: clamp(1.5rem, 4vw, 2.75rem); }
111
+ .bgs--overlay .bgs__card { min-height: 320px; }
112
+ .bgs--overlay .bgs__cover { position: absolute; inset: 0; aspect-ratio: auto; }
113
+ .bgs--overlay .bgs__cover::after { content: ''; position: absolute; inset: 0; background: linear-gradient(to top, rgba(0, 0, 0, 0.85), transparent 70%); }
114
+ .bgs--overlay .bgs__body { position: relative; margin-top: auto; }
115
+ .bgs--overlay .bgs__post--featured .bgs__card { display: flex; }
116
+ .bgs__meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0; font-size: 0.8125rem; color: var(--c-text-2, #a3a3a3); }
117
+ .bgs__cat { padding: 0.15rem 0.55rem; border-radius: 999px; background: rgb(var(--ink-rgb, 255 255 255) / 0.07); color: var(--c-text-1, #fff); font-weight: 600; font-size: 0.75rem; }
118
+ .bgs__post-title { margin: 0; font-size: 1.1875rem; font-weight: 700; line-height: 1.3; letter-spacing: -0.015em; }
119
+ .bgs__post--featured .bgs__post-title { font-size: clamp(1.5rem, 3vw, 2rem); }
120
+ .bgs__post-title a { color: var(--c-text-1, #fff); text-decoration: none; }
121
+ .bgs__post-title a::after { content: ''; position: absolute; inset: 0; }
122
+ .bgs__post-title a:focus-visible { outline: none; }
123
+ .bgs__card:has(a:focus-visible) { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 3px; }
124
+ .bgs__excerpt { margin: 0; font-size: 0.9375rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
125
+ .bgs__author { margin: auto 0 0; padding-top: 0.5rem; font-size: 0.8125rem; color: var(--c-text-3, #8a8a8a); }
126
+ @container (max-width: 900px) {
127
+ .bgs__grid { grid-template-columns: 1fr 1fr; }
128
+ .bgs__post--featured .bgs__card { grid-template-columns: 1fr; }
129
+ .bgs__post--featured .bgs__cover { min-height: 200px; }
130
+ }
131
+ @container (max-width: 600px) { .bgs__grid { grid-template-columns: 1fr; } }
132
+ @media (prefers-reduced-motion: reduce) { .bgs__card { transition: none; } }
133
+ </style>
@@ -8,6 +8,7 @@ interface Props {
8
8
  const { title = "Buttons", subtitle = "Interactive button components with premium styles and animations." } = Astro.props;
9
9
  ---
10
10
 
11
+ <div class="cq-buttons-showcase-cq">
11
12
  <section class="buttons-showcase">
12
13
  <div class="container">
13
14
  <div class="showcase-header">
@@ -34,8 +35,9 @@ const { title = "Buttons", subtitle = "Interactive button components with premiu
34
35
  </div>
35
36
  </div>
36
37
  </section>
37
-
38
+ </div>
38
39
  <style>
40
+ .cq-buttons-showcase-cq { container-type: inline-size; }
39
41
  .buttons-showcase {
40
42
  padding: var(--sp-16) 0;
41
43
  position: relative;
@@ -52,8 +54,8 @@ const { title = "Buttons", subtitle = "Interactive button components with premiu
52
54
  text-transform: uppercase;
53
55
  letter-spacing: 0.1em;
54
56
  color: var(--c-primary-light);
55
- background: rgba(184,134,11,0.1);
56
- border: 1px solid rgba(184,134,11,0.25);
57
+ background: color-mix(in srgb, var(--c-primary, #b8860b) 10%, transparent);
58
+ border: 1px solid color-mix(in srgb, var(--c-primary, #b8860b) 25%, transparent);
57
59
  border-radius: var(--r-full);
58
60
  margin-bottom: var(--sp-4);
59
61
  }
@@ -75,7 +77,7 @@ const { title = "Buttons", subtitle = "Interactive button components with premiu
75
77
  padding: var(--sp-12);
76
78
  background: rgba(10, 10, 10, 0.6);
77
79
  backdrop-filter: blur(20px);
78
- border: 1px solid rgba(255,255,255,0.08);
80
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
79
81
  border-radius: var(--r-xl);
80
82
  margin-bottom: var(--sp-6);
81
83
  box-shadow: 0 20px 60px rgba(0,0,0,0.4);
@@ -107,37 +109,37 @@ const { title = "Buttons", subtitle = "Interactive button components with premiu
107
109
  }
108
110
  .btn--primary {
109
111
  background: linear-gradient(135deg, var(--c-primary-light), var(--c-primary));
110
- color: #0a0a0a;
111
- box-shadow: 0 4px 20px rgba(184,134,11,0.3);
112
+ color: var(--c-on-primary, #0a0a0a);
113
+ box-shadow: 0 4px 20px color-mix(in srgb, var(--c-primary, #b8860b) 30%, transparent);
112
114
  }
113
115
  .btn--primary:hover {
114
116
  transform: translateY(-2px);
115
- box-shadow: 0 8px 30px rgba(184,134,11,0.4);
117
+ box-shadow: 0 8px 30px color-mix(in srgb, var(--c-primary, #b8860b) 40%, transparent);
116
118
  }
117
119
  .btn--glass {
118
- background: rgba(255,255,255,0.06);
120
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.06);
119
121
  backdrop-filter: blur(12px);
120
122
  color: var(--c-text-1);
121
- border: 1px solid rgba(255,255,255,0.12);
123
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.12);
122
124
  }
123
125
  .btn--glass:hover {
124
- background: rgba(255,255,255,0.1);
125
- border-color: rgba(184,134,11,0.3);
126
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.1);
127
+ border-color: color-mix(in srgb, var(--c-primary, #b8860b) 30%, transparent);
126
128
  }
127
129
  .btn--glow {
128
130
  background: transparent;
129
131
  color: var(--c-primary-light);
130
132
  border: 1px solid var(--c-primary);
131
- box-shadow: 0 0 12px rgba(184,134,11,0.2);
133
+ box-shadow: 0 0 12px color-mix(in srgb, var(--c-primary, #b8860b) 20%, transparent);
132
134
  }
133
135
  .btn--glow:hover {
134
- box-shadow: 0 0 24px rgba(184,134,11,0.5);
136
+ box-shadow: 0 0 24px color-mix(in srgb, var(--c-primary, #b8860b) 50%, transparent);
135
137
  transform: translateY(-2px);
136
138
  }
137
139
  .btn--shimmer {
138
140
  background: linear-gradient(110deg, #b8860b 0%, #d4a017 40%, #fef3c7 50%, #d4a017 60%, #b8860b 100%);
139
141
  background-size: 200% 100%;
140
- color: #0a0a0a;
142
+ color: var(--c-on-primary, #0a0a0a);
141
143
  animation: shimmer 3s linear infinite;
142
144
  }
143
145
  @keyframes shimmer {
@@ -147,10 +149,10 @@ const { title = "Buttons", subtitle = "Interactive button components with premiu
147
149
  .btn--neumorphic {
148
150
  background: #1a1a1a;
149
151
  color: var(--c-text-1);
150
- box-shadow: 4px 4px 12px rgba(0,0,0,0.5), -4px -4px 12px rgba(255,255,255,0.05);
152
+ box-shadow: 4px 4px 12px rgba(0,0,0,0.5), -4px -4px 12px rgb(var(--ink-rgb, 255 255 255) / 0.05);
151
153
  }
152
154
  .btn--neumorphic:hover {
153
- box-shadow: 2px 2px 8px rgba(0,0,0,0.4), -2px -2px 8px rgba(255,255,255,0.04);
155
+ box-shadow: 2px 2px 8px rgba(0,0,0,0.4), -2px -2px 8px rgb(var(--ink-rgb, 255 255 255) / 0.04);
154
156
  }
155
157
  .btn--ghost {
156
158
  background: transparent;
@@ -165,7 +167,7 @@ const { title = "Buttons", subtitle = "Interactive button components with premiu
165
167
  .usage-code {
166
168
  text-align: center;
167
169
  padding: var(--sp-4);
168
- background: rgba(255,255,255,0.02);
170
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.02);
169
171
  border-radius: var(--r-md);
170
172
  }
171
173
  .usage-code code {
@@ -174,7 +176,7 @@ const { title = "Buttons", subtitle = "Interactive button components with premiu
174
176
  color: var(--c-primary-light);
175
177
  }
176
178
 
177
- @media (max-width: 640px) {
179
+ @container (max-width: 640px) {
178
180
  .button-grid { gap: var(--sp-3); }
179
181
  .btn { min-width: 100px; padding: 0.6rem 1.2rem; font-size: 0.85rem; }
180
182
  .preview-stage { padding: var(--sp-8) var(--sp-4); }