astro-component-kit 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +159 -176
  2. package/package.json +2 -2
  3. package/src/components/index.ts +16 -202
  4. package/src/components/lib/buttons/NeonBorderButton.astro +162 -0
  5. package/src/components/lib/buttons/RippleButton.astro +199 -0
  6. package/src/components/lib/buttons/SlideRevealButton.astro +159 -0
  7. package/src/components/lib/buttons/index.ts +4 -0
  8. package/src/components/lib/cards/GithubRepoCard.astro +176 -0
  9. package/src/components/lib/cards/PricingCompareMiniCard.astro +196 -0
  10. package/src/components/lib/cards/StatsTrendCard.astro +149 -0
  11. package/src/components/lib/cards/index.ts +3 -0
  12. package/src/components/lib/charts/ActivityRings.astro +105 -0
  13. package/src/components/lib/charts/BoxPlotChart.astro +109 -0
  14. package/src/components/lib/charts/BubbleChart.astro +101 -0
  15. package/src/components/lib/charts/BulletChart.astro +160 -0
  16. package/src/components/lib/charts/BumpChart.astro +92 -0
  17. package/src/components/lib/charts/CandlestickChart.astro +105 -0
  18. package/src/components/lib/charts/ChartFrame.astro +373 -0
  19. package/src/components/lib/charts/DivergingBarChart.astro +98 -0
  20. package/src/components/lib/charts/DumbbellChart.astro +92 -0
  21. package/src/components/lib/charts/ForecastLineChart.astro +125 -0
  22. package/src/components/lib/charts/FunnelChart.astro +122 -0
  23. package/src/components/lib/charts/GaugeChart.astro +111 -0
  24. package/src/components/lib/charts/GoalProgressChart.astro +97 -0
  25. package/src/components/lib/charts/GroupedBarChart.astro +125 -0
  26. package/src/components/lib/charts/HeatmapChart.astro +131 -0
  27. package/src/components/lib/charts/HistogramChart.astro +111 -0
  28. package/src/components/lib/charts/HorizontalBarChart.astro +155 -0
  29. package/src/components/lib/charts/KpiStatTile.astro +116 -0
  30. package/src/components/lib/charts/KpiTrendGrid.astro +60 -0
  31. package/src/components/lib/charts/LineChart.astro +160 -0
  32. package/src/components/lib/charts/LinearProgressGauge.astro +136 -0
  33. package/src/components/lib/charts/LollipopChart.astro +100 -0
  34. package/src/components/lib/charts/MeterGauge.astro +122 -0
  35. package/src/components/lib/charts/MetricDeltaTable.astro +125 -0
  36. package/src/components/lib/charts/PieChart.astro +135 -0
  37. package/src/components/lib/charts/PolarAreaChart.astro +85 -0
  38. package/src/components/lib/charts/PopulationPyramid.astro +122 -0
  39. package/src/components/lib/charts/PunchCardChart.astro +89 -0
  40. package/src/components/lib/charts/RadarChart.astro +94 -0
  41. package/src/components/lib/charts/RadarMetricChart.astro +168 -0
  42. package/src/components/lib/charts/RadialBarChart.astro +124 -0
  43. package/src/components/lib/charts/RangeAreaChart.astro +113 -0
  44. package/src/components/lib/charts/RetentionCohortTable.astro +126 -0
  45. package/src/components/lib/charts/SankeyChart.astro +128 -0
  46. package/src/components/lib/charts/ScatterPlot.astro +139 -0
  47. package/src/components/lib/charts/SegmentedBarChart.astro +119 -0
  48. package/src/components/lib/charts/SlopeChart.astro +105 -0
  49. package/src/components/lib/charts/SparklineChart.astro +87 -0
  50. package/src/components/lib/charts/StackedAreaChart.astro +130 -0
  51. package/src/components/lib/charts/StackedBarChart.astro +136 -0
  52. package/src/components/lib/charts/SunburstChart.astro +103 -0
  53. package/src/components/lib/charts/TreemapChart.astro +94 -0
  54. package/src/components/lib/charts/WaffleChart.astro +121 -0
  55. package/src/components/lib/charts/WaterfallChart.astro +118 -0
  56. package/src/components/lib/charts/chart-tooltip.ts +75 -0
  57. package/src/components/lib/charts/chart-utils.ts +271 -0
  58. package/src/components/lib/charts/index.ts +46 -0
  59. package/src/components/lib/data/ActivityHeatmapGrid.astro +157 -0
  60. package/src/components/lib/data/MetricComparisonRow.astro +109 -0
  61. package/src/components/lib/data/StatusTimelineFeed.astro +168 -0
  62. package/src/components/lib/data/index.ts +3 -0
  63. package/src/components/lib/feedback/ProgressRingCounter.astro +118 -0
  64. package/src/components/lib/feedback/ToastNotificationGroup.astro +173 -0
  65. package/src/components/lib/feedback/index.ts +2 -0
  66. package/src/components/lib/forms/GlassInlineToggleGroup.astro +161 -0
  67. package/src/components/lib/forms/PasswordStrengthInput.astro +242 -0
  68. package/src/components/lib/forms/PhoneCountryInput.astro +145 -0
  69. package/src/components/lib/forms/TagsAutocompleteInput.astro +249 -0
  70. package/src/components/lib/forms/index.ts +5 -0
  71. package/src/components/lib/layout/DiagonalSectionDivider.astro +69 -0
  72. package/src/components/lib/layout/HolyGrailGrid.astro +125 -0
  73. package/src/components/lib/layout/index.ts +3 -1
  74. package/src/components/lib/navigation/QuickJumpDock.astro +125 -0
  75. package/src/components/lib/navigation/StepBreadcrumbBar.astro +151 -0
  76. package/src/components/lib/navigation/SubNavbarPills.astro +102 -0
  77. package/src/components/lib/navigation/index.ts +3 -0
  78. package/src/components/lib/overlays/CookiePreferencesDrawer.astro +226 -0
  79. package/src/components/lib/overlays/FloatingFeedbackWidget.astro +203 -0
  80. package/src/components/lib/overlays/ImageCompareSlider.astro +172 -0
  81. package/src/components/lib/overlays/index.ts +3 -0
  82. package/src/components/lib/sections/BlogGridSection.astro +131 -0
  83. package/src/components/lib/sections/CareersOpenings.astro +139 -0
  84. package/src/components/lib/sections/ChangelogSection.astro +144 -0
  85. package/src/components/lib/sections/ContactSplitMap.astro +139 -0
  86. package/src/components/lib/sections/CtaNewsletterSection.astro +163 -0
  87. package/src/components/lib/sections/CtaSplitImage.astro +147 -0
  88. package/src/components/lib/sections/FaqTwoColumn.astro +119 -0
  89. package/src/components/lib/sections/FeatureAlternatingRows.astro +190 -0
  90. package/src/components/lib/sections/FeatureIconGrid.astro +155 -0
  91. package/src/components/lib/sections/FeatureTabsShowcase.astro +180 -0
  92. package/src/components/lib/sections/FooterMegaSection.astro +155 -0
  93. package/src/components/lib/sections/HeroDashboardPreview.astro +180 -0
  94. package/src/components/lib/sections/HeroMinimalCentered.astro +175 -0
  95. package/src/components/lib/sections/HeroShowcaseSplit.astro +304 -0
  96. package/src/components/lib/sections/HeroTerminal.astro +251 -0
  97. package/src/components/lib/sections/HeroWaitlist.astro +229 -0
  98. package/src/components/lib/sections/HowItWorksSteps.astro +178 -0
  99. package/src/components/lib/sections/IntegrationsGrid.astro +167 -0
  100. package/src/components/lib/sections/LogoCloudSection.astro +96 -0
  101. package/src/components/lib/sections/LogoWallGrid.astro +91 -0
  102. package/src/components/lib/sections/PricingSinglePlan.astro +157 -0
  103. package/src/components/lib/sections/PricingToggleCards.astro +171 -0
  104. package/src/components/lib/sections/StatsCounterBand.astro +122 -0
  105. package/src/components/lib/sections/StatsDashboardSection.astro +83 -0
  106. package/src/components/lib/sections/TestimonialCarousel.astro +165 -0
  107. package/src/components/lib/sections/TestimonialFeatured.astro +120 -0
  108. package/src/components/lib/sections/TestimonialGridSection.astro +188 -0
  109. package/src/components/lib/sections/index.ts +29 -0
  110. package/src/components/lib/typography/AnimatedCounterText.astro +102 -0
  111. package/src/components/lib/typography/CurvedPathHeading.astro +86 -0
  112. package/src/components/lib/typography/GradientTextMarquee.astro +105 -0
  113. package/src/components/lib/typography/HackerDecodeText.astro +85 -0
  114. package/src/components/lib/typography/index.ts +4 -0
  115. package/src/styles/global.css +18 -1
@@ -0,0 +1,165 @@
1
+ ---
2
+ /**
3
+ * TestimonialCarousel — Scroll-snap testimonial carousel with previous/next
4
+ * buttons, keyboard support and no autoplay (respects user control).
5
+ *
6
+ * @param {string} title - Section heading.
7
+ * @param {Array<{quote: string, name: string, role: string, rating?: number}>} items - Testimonials.
8
+ * @param {string} variant - Card style (glass|solid|quote).
9
+ * @param {string} size - Vertical rhythm (sm|md|lg).
10
+ * @param {string} className - Extra CSS class.
11
+ */
12
+ interface Item { quote: string; name: string; role: string; rating?: number }
13
+ interface Props {
14
+ title?: string;
15
+ items?: Item[];
16
+ variant?: 'glass' | 'solid' | 'quote';
17
+ size?: 'sm' | 'md' | 'lg';
18
+ className?: string;
19
+ }
20
+
21
+ const {
22
+ title = 'Loved by builders worldwide',
23
+ items = [
24
+ { quote: 'We rebuilt our marketing site in two days. The sections are gorgeous and Lighthouse went straight to 100.', name: 'Alicia Gómez', role: 'Head of Growth, Northwind', rating: 5 },
25
+ { quote: 'The SVG charts replaced a 200 kB library in our dashboard. Same features, zero runtime.', name: 'Marcus Lee', role: 'Staff Engineer, Lumen', rating: 5 },
26
+ { quote: 'Finally a component kit that takes accessibility seriously. Our audit came back clean.', name: 'Priya Natarajan', role: 'Accessibility Lead, Civic', rating: 5 },
27
+ { quote: 'Copy, paste, tweak tokens, done. It feels like a design system we built ourselves.', name: 'Tom Becker', role: 'Founder, Driftly', rating: 4 },
28
+ { quote: 'Our agency ships every client landing page on top of this kit now.', name: 'Sofía Ruiz', role: 'Creative Director, Studio Alba', rating: 5 },
29
+ ],
30
+ variant = 'glass',
31
+ size = 'md',
32
+ className = '',
33
+ } = Astro.props;
34
+
35
+ const id = `tc-${Math.random().toString(36).slice(2, 8)}`;
36
+ const initials = (n: string) => n.split(' ').map((p) => p[0]).slice(0, 2).join('');
37
+ const hues = ['var(--chart-1, #3987e5)', 'var(--chart-2, #d95926)', 'var(--chart-3, #199e70)', 'var(--chart-5, #d55181)', 'var(--chart-7, #9085e9)'];
38
+ ---
39
+
40
+ <section class={`tc tc--${variant} tc--${size} ${className}`} aria-labelledby={id} aria-roledescription="carousel" data-tc>
41
+ <div class="tc__inner">
42
+ <header class="tc__head">
43
+ <h2 id={id} class="tc__title">{title}</h2>
44
+ <div class="tc__controls">
45
+ <button type="button" class="tc__btn" data-dir="-1" aria-label="Previous testimonials">
46
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg>
47
+ </button>
48
+ <button type="button" class="tc__btn" data-dir="1" aria-label="Next testimonials">
49
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M9 18l6-6-6-6" /></svg>
50
+ </button>
51
+ </div>
52
+ </header>
53
+
54
+ <ul class="tc__track" tabindex="0" aria-label="Testimonials, scroll horizontally">
55
+ {items.map((t, i) => (
56
+ <li class="tc__slide" aria-roledescription="slide" aria-label={`${i + 1} of ${items.length}`}>
57
+ <figure class="tc__card">
58
+ {t.rating && (
59
+ <div class="tc__stars" role="img" aria-label={`${t.rating} out of 5 stars`}>
60
+ {Array.from({ length: 5 }, (_, s) => (
61
+ <svg width="16" height="16" viewBox="0 0 24 24" aria-hidden="true" class={(t.rating ?? 0) > s ? 'is-on' : ''}><path d="m12 2 3.1 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.8 21l1.2-6.8-5-4.9 6.9-1z" fill="currentColor" /></svg>
62
+ ))}
63
+ </div>
64
+ )}
65
+ <blockquote class="tc__quote"><p>“{t.quote}”</p></blockquote>
66
+ <figcaption class="tc__author">
67
+ <span class="tc__avatar" style={`background:${hues[i % hues.length]}`} aria-hidden="true">{initials(t.name)}</span>
68
+ <span><strong>{t.name}</strong><span class="tc__role">{t.role}</span></span>
69
+ </figcaption>
70
+ </figure>
71
+ </li>
72
+ ))}
73
+ </ul>
74
+ </div>
75
+ </section>
76
+
77
+ <script>
78
+ document.querySelectorAll<HTMLElement>('[data-tc]').forEach((root) => {
79
+ const track = root.querySelector<HTMLElement>('.tc__track');
80
+ const btns = root.querySelectorAll<HTMLButtonElement>('.tc__btn');
81
+ if (!track) return;
82
+ const update = () => {
83
+ const max = track.scrollWidth - track.clientWidth - 2;
84
+ btns[0].disabled = track.scrollLeft <= 2;
85
+ btns[1].disabled = track.scrollLeft >= max;
86
+ };
87
+ const step = () => (track.querySelector<HTMLElement>('.tc__slide')?.offsetWidth ?? 300) + 20;
88
+ btns.forEach((b) => b.addEventListener('click', () => {
89
+ const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
90
+ track.scrollBy({ left: Number(b.dataset.dir) * step(), behavior: reduce ? 'auto' : 'smooth' });
91
+ }));
92
+ track.addEventListener('scroll', update, { passive: true });
93
+ window.addEventListener('resize', update);
94
+ update();
95
+ });
96
+ </script>
97
+
98
+ <style>
99
+ .tc {
100
+ --tc-py: 5.5rem;
101
+ padding: var(--tc-py) var(--sp-6, 1.5rem);
102
+ background: var(--c-bg, #050505);
103
+ overflow: hidden;
104
+ }
105
+ .tc--sm { --tc-py: 3.5rem; }
106
+ .tc--lg { --tc-py: 7.5rem; }
107
+ .tc__inner { max-width: 1180px; margin: 0 auto; }
108
+ .tc__head { display: flex; justify-content: space-between; align-items: flex-end; gap: 1.5rem; margin-bottom: 2rem; }
109
+ .tc__title { margin: 0; font-size: clamp(1.75rem, 4vw, 2.5rem); font-weight: 800; letter-spacing: -0.03em; color: var(--c-text-1, #fff); max-width: 560px; }
110
+ .tc__controls { display: flex; gap: 0.5rem; flex-shrink: 0; }
111
+ .tc__btn {
112
+ display: grid;
113
+ place-items: center;
114
+ width: 48px;
115
+ height: 48px;
116
+ border-radius: 50%;
117
+ border: 1px solid rgba(255, 255, 255, 0.14);
118
+ background: rgba(255, 255, 255, 0.03);
119
+ color: var(--c-text-1, #fff);
120
+ cursor: pointer;
121
+ transition: background 0.2s ease, border-color 0.2s ease, opacity 0.2s ease;
122
+ }
123
+ .tc__btn:hover:not(:disabled) { background: rgba(255, 255, 255, 0.08); border-color: rgba(255, 255, 255, 0.3); }
124
+ .tc__btn:disabled { opacity: 0.35; cursor: not-allowed; }
125
+ .tc__btn:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
126
+ .tc__track {
127
+ list-style: none;
128
+ margin: 0;
129
+ padding: 0.25rem 0 1rem;
130
+ display: grid;
131
+ grid-auto-flow: column;
132
+ grid-auto-columns: min(380px, 85%);
133
+ gap: 20px;
134
+ overflow-x: auto;
135
+ scroll-snap-type: x mandatory;
136
+ scrollbar-width: none;
137
+ }
138
+ .tc__track::-webkit-scrollbar { display: none; }
139
+ .tc__track:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 4px; border-radius: 12px; }
140
+ .tc__slide { scroll-snap-align: start; }
141
+ .tc__card {
142
+ height: 100%;
143
+ margin: 0;
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 1.25rem;
147
+ padding: 1.75rem;
148
+ border-radius: var(--r-lg, 20px);
149
+ box-sizing: border-box;
150
+ }
151
+ .tc--glass .tc__card { background: linear-gradient(160deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.015)); border: 1px solid var(--c-border, rgba(255, 255, 255, 0.07)); }
152
+ .tc--solid .tc__card { background: var(--c-bg-2, #111); border: 1px solid rgba(255, 255, 255, 0.05); }
153
+ .tc--quote .tc__card { border-left: 2px solid var(--c-primary-light, #d4a017); border-radius: 0; padding-left: 1.5rem; }
154
+ .tc__stars { display: flex; gap: 2px; color: rgba(255, 255, 255, 0.15); }
155
+ .tc__stars .is-on { color: var(--c-primary-light, #d4a017); }
156
+ .tc__quote { margin: 0; flex: 1; }
157
+ .tc__quote p { margin: 0; font-size: 1.0625rem; line-height: 1.6; color: var(--c-text-1, #fff); }
158
+ .tc__author { display: flex; align-items: center; gap: 0.75rem; font-size: 0.875rem; }
159
+ .tc__author strong { display: block; color: var(--c-text-1, #fff); font-weight: 650; }
160
+ .tc__role { display: block; color: var(--c-text-2, #a3a3a3); font-size: 0.8125rem; }
161
+ .tc__avatar { display: grid; place-items: center; width: 40px; height: 40px; border-radius: 50%; color: #fff; font-weight: 700; font-size: 0.8125rem; flex-shrink: 0; }
162
+ @media (max-width: 560px) {
163
+ .tc__head { flex-direction: column; align-items: flex-start; }
164
+ }
165
+ </style>
@@ -0,0 +1,120 @@
1
+ ---
2
+ /**
3
+ * TestimonialFeatured — A single large customer quote paired with the company's
4
+ * headline results (case-study style), plus a link to the full story.
5
+ *
6
+ * @param {string} quote - The testimonial text.
7
+ * @param {string} name - Author name.
8
+ * @param {string} role - Author role and company.
9
+ * @param {string} company - Company wordmark text.
10
+ * @param {Array<{value: string, label: string}>} metrics - Outcome stats.
11
+ * @param {{label: string, href: string}} link - Case study link.
12
+ * @param {string} variant - Layout style (split|centered|card).
13
+ * @param {string} size - Vertical rhythm (sm|md|lg).
14
+ * @param {string} className - Extra CSS class.
15
+ */
16
+ interface Props {
17
+ quote?: string;
18
+ name?: string;
19
+ role?: string;
20
+ company?: string;
21
+ metrics?: Array<{ value: string; label: string }>;
22
+ link?: { label: string; href: string };
23
+ variant?: 'split' | 'centered' | 'card';
24
+ size?: 'sm' | 'md' | 'lg';
25
+ className?: string;
26
+ }
27
+
28
+ const {
29
+ quote = 'We replaced three tools and a chart library with this kit. Our product pages load twice as fast and our conversion rate has never been higher.',
30
+ name = 'Elena Vasquez',
31
+ role = 'VP of Marketing, Orbitly',
32
+ company = 'Orbitly',
33
+ metrics = [
34
+ { value: '2.1×', label: 'faster page loads' },
35
+ { value: '+38%', label: 'trial conversion' },
36
+ { value: '−64%', label: 'front-end bundle size' },
37
+ ],
38
+ link = { label: 'Read the case study', href: '#' },
39
+ variant = 'split',
40
+ size = 'md',
41
+ className = '',
42
+ } = Astro.props;
43
+
44
+ const initials = name.split(' ').map((p) => p[0]).slice(0, 2).join('');
45
+ ---
46
+
47
+ <section class={`tf tf--${variant} tf--${size} ${className}`} aria-label={`Customer story: ${company}`}>
48
+ <div class="tf__inner">
49
+ <figure class="tf__figure">
50
+ <p class="tf__company" aria-hidden="true">
51
+ <svg width="22" height="22" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" /><circle cx="12" cy="12" r="4" fill="currentColor" /></svg>
52
+ {company}
53
+ </p>
54
+ <svg class="tf__mark" width="48" height="36" viewBox="0 0 48 36" aria-hidden="true"><path d="M0 36V22C0 9 7 2 20 0l2 5C14 7 11 12 11 18h9v18H0zm28 0V22C28 9 35 2 48 0l2 5c-8 2-11 7-11 13h9v18H28z" fill="currentColor" /></svg>
55
+ <blockquote class="tf__quote"><p>{quote}</p></blockquote>
56
+ <figcaption class="tf__author">
57
+ <span class="tf__avatar" aria-hidden="true">{initials}</span>
58
+ <span><strong>{name}</strong><span class="tf__role">{role}</span></span>
59
+ </figcaption>
60
+ </figure>
61
+
62
+ <div class="tf__side">
63
+ <dl class="tf__metrics">
64
+ {metrics.map((mt) => (
65
+ <div class="tf__metric">
66
+ <dt>{mt.label}</dt>
67
+ <dd>{mt.value}</dd>
68
+ </div>
69
+ ))}
70
+ </dl>
71
+ {link && (
72
+ <a class="tf__link" href={link.href}>
73
+ {link.label}
74
+ <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>
75
+ </a>
76
+ )}
77
+ </div>
78
+ </div>
79
+ </section>
80
+
81
+ <style>
82
+ .tf {
83
+ --tf-py: 5.5rem;
84
+ padding: var(--tf-py) var(--sp-6, 1.5rem);
85
+ background: var(--c-bg, #050505);
86
+ }
87
+ .tf--sm { --tf-py: 3.5rem; }
88
+ .tf--lg { --tf-py: 7.5rem; }
89
+ .tf__inner { max-width: 1120px; margin: 0 auto; display: grid; grid-template-columns: 1.6fr 1fr; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
90
+ .tf--card .tf__inner {
91
+ padding: clamp(2rem, 5vw, 3.5rem);
92
+ border-radius: var(--r-xl, 28px);
93
+ background: radial-gradient(circle at 0% 0%, var(--c-primary-glow, rgba(184, 134, 11, 0.14)), transparent 55%), rgba(255, 255, 255, 0.025);
94
+ border: 1px solid var(--c-border, rgba(255, 255, 255, 0.08));
95
+ }
96
+ .tf--centered .tf__inner { grid-template-columns: 1fr; text-align: center; max-width: 860px; }
97
+ .tf--centered .tf__author, .tf--centered .tf__company { justify-content: center; }
98
+ .tf--centered .tf__mark { margin-inline: auto; }
99
+ .tf__figure { margin: 0; }
100
+ .tf__company { display: flex; align-items: center; gap: 0.5rem; margin: 0 0 2rem; font-size: 1.125rem; font-weight: 750; letter-spacing: -0.02em; color: var(--c-text-1, #fff); }
101
+ .tf__mark { display: block; color: var(--c-primary-light, #d4a017); opacity: 0.8; margin-bottom: 1.25rem; }
102
+ .tf__quote { margin: 0 0 2rem; }
103
+ .tf__quote p { margin: 0; font-size: clamp(1.35rem, 2.6vw, 1.875rem); font-weight: 550; line-height: 1.4; letter-spacing: -0.015em; color: var(--c-text-1, #fff); text-wrap: pretty; }
104
+ .tf__author { display: flex; align-items: center; gap: 0.85rem; }
105
+ .tf__author strong { display: block; color: var(--c-text-1, #fff); }
106
+ .tf__role { display: block; font-size: 0.875rem; color: var(--c-text-2, #a3a3a3); }
107
+ .tf__avatar { display: grid; place-items: center; width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, var(--chart-7, #9085e9), var(--chart-1, #3987e5)); color: #fff; font-weight: 700; }
108
+ .tf__side { display: grid; gap: 1.5rem; }
109
+ .tf__metrics { margin: 0; display: grid; gap: 1px; border-radius: var(--r-lg, 20px); overflow: hidden; background: rgba(255, 255, 255, 0.07); }
110
+ .tf--centered .tf__metrics { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
111
+ .tf__metric { display: flex; flex-direction: column-reverse; gap: 0.25rem; padding: 1.25rem 1.5rem; background: var(--c-bg-1, #0a0a0a); }
112
+ .tf__metric dt { font-size: 0.875rem; color: var(--c-text-2, #a3a3a3); }
113
+ .tf__metric dd { margin: 0; font-size: 2rem; font-weight: 800; letter-spacing: -0.03em; color: var(--c-text-1, #fff); font-variant-numeric: tabular-nums; }
114
+ .tf__link { display: inline-flex; align-items: center; gap: 0.45rem; min-height: 44px; font-weight: 650; color: var(--c-primary-light, #d4a017); text-decoration: none; }
115
+ .tf--centered .tf__link { justify-self: center; }
116
+ .tf__link svg { transition: transform 0.2s ease; }
117
+ .tf__link:hover svg { transform: translateX(3px); }
118
+ .tf__link:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; border-radius: 4px; }
119
+ @media (max-width: 820px) { .tf__inner { grid-template-columns: 1fr; } }
120
+ </style>
@@ -0,0 +1,188 @@
1
+ ---
2
+ /**
3
+ * TestimonialGridSection — Bento grid showcase of verified customer reviews with dark glass styling and star ratings.
4
+ *
5
+ * @param {string} eyebrow - Small uppercase category lead. Default is 'WALL OF LOVE'.
6
+ * @param {string} title - Section main headline. Default is 'Trusted by innovative engineering teams'.
7
+ * @param {string} subtitle - Subtitle description.
8
+ */
9
+ interface Props {
10
+ eyebrow?: string;
11
+ title?: string;
12
+ subtitle?: string;
13
+ }
14
+
15
+ const {
16
+ eyebrow = 'WALL OF LOVE',
17
+ title = 'Trusted by thousands of Astro builders',
18
+ subtitle = 'Discover why developers, indie hackers, and creative agencies rely on Astro Components Kit for production websites.',
19
+ } = Astro.props;
20
+
21
+ const testimonials = [
22
+ {
23
+ name: 'Elena Rostova',
24
+ role: 'Principal Architect @ Veloce Labs',
25
+ avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&auto=format&fit=crop&q=80',
26
+ quote: 'The pure zero-JS philosophy with Astro 5 is game-changing. Our Core Web Vitals score hit 100 on every audited route without tweaking hydration.',
27
+ stars: 5,
28
+ highlight: true,
29
+ },
30
+ {
31
+ name: 'Marcus Vance',
32
+ role: 'Founder @ HyperSync',
33
+ avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&auto=format&fit=crop&q=80',
34
+ quote: 'Copying and pasting self-contained Astro components directly shaved two full sprints off our SaaS launch timeline.',
35
+ stars: 5,
36
+ },
37
+ {
38
+ name: 'Sofia Chen',
39
+ role: 'Senior UI/UX Designer',
40
+ avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&auto=format&fit=crop&q=80',
41
+ quote: 'The glassmorphic dark palette with gold accents feels extraordinarily premium right out of the box. Highly recommended.',
42
+ stars: 5,
43
+ },
44
+ ];
45
+ ---
46
+
47
+ <section class="testi-section">
48
+ <div class="testi-section__header">
49
+ <span class="testi-section__eyebrow">{eyebrow}</span>
50
+ <h2 class="testi-section__title">{title}</h2>
51
+ <p class="testi-section__subtitle">{subtitle}</p>
52
+ </div>
53
+
54
+ <div class="testi-section__grid">
55
+ {testimonials.map((t) => (
56
+ <article class:list={['testi-card', { 'testi-card--highlight': t.highlight }]}>
57
+ <div class="testi-card__stars" aria-label={`${t.stars} out of 5 stars`}>
58
+ {Array.from({ length: t.stars }).map(() => (
59
+ <span>★</span>
60
+ ))}
61
+ </div>
62
+ <blockquote class="testi-card__quote">
63
+ "{t.quote}"
64
+ </blockquote>
65
+ <div class="testi-card__author">
66
+ <img src={t.avatar} alt={t.name} class="testi-card__avatar" loading="lazy" />
67
+ <div class="testi-card__meta">
68
+ <h5 class="testi-card__name">{t.name}</h5>
69
+ <span class="testi-card__role">{t.role}</span>
70
+ </div>
71
+ </div>
72
+ </article>
73
+ ))}
74
+ </div>
75
+ </section>
76
+
77
+ <style>
78
+ .testi-section {
79
+ padding: var(--sp-12, 3rem) var(--sp-6, 1.5rem);
80
+ max-width: 1200px;
81
+ margin: 0 auto;
82
+ font-family: var(--font-sans, system-ui, sans-serif);
83
+ }
84
+
85
+ .testi-section__header {
86
+ text-align: center;
87
+ max-width: 640px;
88
+ margin: 0 auto 3rem auto;
89
+ }
90
+
91
+ .testi-section__eyebrow {
92
+ display: inline-block;
93
+ font-size: 0.75rem;
94
+ font-weight: 800;
95
+ letter-spacing: 0.12em;
96
+ color: var(--c-primary-light, #d4a017);
97
+ padding: 0.35rem 0.8rem;
98
+ border: 1px solid rgba(184, 134, 11, 0.3);
99
+ border-radius: var(--r-sm, 8px);
100
+ margin-bottom: 1rem;
101
+ text-transform: uppercase;
102
+ }
103
+
104
+ .testi-section__title {
105
+ font-size: clamp(1.8rem, 4vw, 2.8rem);
106
+ font-weight: 900;
107
+ color: var(--c-text-1, #ffffff);
108
+ letter-spacing: -0.03em;
109
+ margin: 0 0 1rem 0;
110
+ }
111
+
112
+ .testi-section__subtitle {
113
+ font-size: 0.95rem;
114
+ color: var(--c-text-2, #a3a3a3);
115
+ line-height: 1.6;
116
+ margin: 0;
117
+ }
118
+
119
+ .testi-section__grid {
120
+ display: grid;
121
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
122
+ gap: 1.5rem;
123
+ }
124
+
125
+ .testi-card {
126
+ display: flex;
127
+ flex-direction: column;
128
+ padding: var(--sp-8, 2rem);
129
+ background: rgba(255, 255, 255, 0.02);
130
+ backdrop-filter: blur(16px);
131
+ border: 1px solid rgba(255, 255, 255, 0.08);
132
+ border-radius: var(--r-xl, 24px);
133
+ transition: transform 0.3s ease, border-color 0.3s ease;
134
+ }
135
+
136
+ .testi-card:hover {
137
+ transform: translateY(-4px);
138
+ border-color: rgba(184, 134, 11, 0.3);
139
+ }
140
+
141
+ .testi-card--highlight {
142
+ background: radial-gradient(circle at top left, rgba(184, 134, 11, 0.1), rgba(255, 255, 255, 0.02) 70%);
143
+ border-color: rgba(184, 134, 11, 0.4);
144
+ }
145
+
146
+ .testi-card__stars {
147
+ color: #fbbf24;
148
+ font-size: 1rem;
149
+ letter-spacing: 2px;
150
+ margin-bottom: 1rem;
151
+ }
152
+
153
+ .testi-card__quote {
154
+ font-size: 0.95rem;
155
+ color: var(--c-text-1, #ffffff);
156
+ line-height: 1.65;
157
+ margin: 0 0 1.5rem 0;
158
+ flex: 1;
159
+ font-style: italic;
160
+ }
161
+
162
+ .testi-card__author {
163
+ display: flex;
164
+ align-items: center;
165
+ gap: 12px;
166
+ margin-top: auto;
167
+ }
168
+
169
+ .testi-card__avatar {
170
+ width: 44px;
171
+ height: 44px;
172
+ border-radius: 50%;
173
+ object-fit: cover;
174
+ border: 2px solid rgba(184, 134, 11, 0.4);
175
+ }
176
+
177
+ .testi-card__name {
178
+ font-size: 0.92rem;
179
+ font-weight: 700;
180
+ color: var(--c-text-1, #ffffff);
181
+ margin: 0 0 2px 0;
182
+ }
183
+
184
+ .testi-card__role {
185
+ font-size: 0.75rem;
186
+ color: var(--c-text-3, #737373);
187
+ }
188
+ </style>
@@ -26,3 +26,32 @@ export { default as FaqAccordionSection } from './FaqAccordionSection.astro';
26
26
  export { default as StatsMetricShowcase } from './StatsMetricShowcase.astro';
27
27
  export { default as TeamGridSection } from './TeamGridSection.astro';
28
28
  export { default as ContactGlassForm } from './ContactGlassForm.astro';
29
+ export { default as TestimonialGridSection } from './TestimonialGridSection.astro';
30
+ export { default as LogoCloudSection } from './LogoCloudSection.astro';
31
+ export { default as HeroShowcaseSplit } from './HeroShowcaseSplit.astro';
32
+
33
+ // Additional sections (v0.4)
34
+ export { default as HeroMinimalCentered } from './HeroMinimalCentered.astro';
35
+ export { default as HeroTerminal } from './HeroTerminal.astro';
36
+ export { default as HeroWaitlist } from './HeroWaitlist.astro';
37
+ export { default as HeroDashboardPreview } from './HeroDashboardPreview.astro';
38
+ export { default as FeatureIconGrid } from './FeatureIconGrid.astro';
39
+ export { default as FeatureAlternatingRows } from './FeatureAlternatingRows.astro';
40
+ export { default as FeatureTabsShowcase } from './FeatureTabsShowcase.astro';
41
+ export { default as HowItWorksSteps } from './HowItWorksSteps.astro';
42
+ export { default as IntegrationsGrid } from './IntegrationsGrid.astro';
43
+ export { default as PricingToggleCards } from './PricingToggleCards.astro';
44
+ export { default as PricingSinglePlan } from './PricingSinglePlan.astro';
45
+ export { default as TestimonialCarousel } from './TestimonialCarousel.astro';
46
+ export { default as TestimonialFeatured } from './TestimonialFeatured.astro';
47
+ export { default as CtaNewsletterSection } from './CtaNewsletterSection.astro';
48
+ export { default as CtaSplitImage } from './CtaSplitImage.astro';
49
+ export { default as StatsCounterBand } from './StatsCounterBand.astro';
50
+ export { default as StatsDashboardSection } from './StatsDashboardSection.astro';
51
+ export { default as CareersOpenings } from './CareersOpenings.astro';
52
+ export { default as FaqTwoColumn } from './FaqTwoColumn.astro';
53
+ export { default as ContactSplitMap } from './ContactSplitMap.astro';
54
+ export { default as LogoWallGrid } from './LogoWallGrid.astro';
55
+ export { default as BlogGridSection } from './BlogGridSection.astro';
56
+ export { default as ChangelogSection } from './ChangelogSection.astro';
57
+ export { default as FooterMegaSection } from './FooterMegaSection.astro';
@@ -0,0 +1,102 @@
1
+ ---
2
+ /**
3
+ * AnimatedCounterText — Kinetic statistic counter display with animated number progression and unit suffixes.
4
+ *
5
+ * @param {number} target - Numerical target value (e.g. 99, 120, 2400). Default is 120.
6
+ * @param {string} prefix - Optional string prefix (e.g. '$', '+').
7
+ * @param {string} suffix - Optional string suffix (e.g. '%', 'k', '+'). Default is '+'.
8
+ * @param {string} label - Descriptive text below the counter.
9
+ * @param {string} color - Text accent color. Default is '#d4a017'.
10
+ */
11
+ interface Props {
12
+ target?: number;
13
+ prefix?: string;
14
+ suffix?: string;
15
+ label?: string;
16
+ color?: string;
17
+ }
18
+
19
+ const {
20
+ target = 120,
21
+ prefix = '',
22
+ suffix = '+',
23
+ label = 'Components Ready to Ship',
24
+ color = '#d4a017',
25
+ } = Astro.props;
26
+ ---
27
+
28
+ <div class="stat-counter" data-counter-wrap style={`--counter-color: ${color};`}>
29
+ <div class="stat-counter__number">
30
+ {prefix && <span class="affix">{prefix}</span>}
31
+ <span class="value" data-counter-target={target}>0</span>
32
+ {suffix && <span class="affix">{suffix}</span>}
33
+ </div>
34
+ {label && <p class="stat-counter__label">{label}</p>}
35
+ </div>
36
+
37
+ <style>
38
+ .stat-counter {
39
+ display: inline-flex;
40
+ flex-direction: column;
41
+ align-items: center;
42
+ font-family: var(--font-sans, system-ui, sans-serif);
43
+ user-select: none;
44
+ }
45
+
46
+ .stat-counter__number {
47
+ display: inline-flex;
48
+ align-items: baseline;
49
+ font-size: clamp(2.5rem, 6vw, 4.2rem);
50
+ font-weight: 900;
51
+ line-height: 1;
52
+ letter-spacing: -0.04em;
53
+ color: var(--counter-color);
54
+ text-shadow: 0 0 24px rgba(212, 160, 23, 0.35);
55
+ }
56
+
57
+ .stat-counter__label {
58
+ font-size: 0.85rem;
59
+ font-weight: 600;
60
+ color: var(--c-text-2, #a3a3a3);
61
+ margin-top: 6px;
62
+ text-transform: uppercase;
63
+ letter-spacing: 0.05em;
64
+ }
65
+ </style>
66
+
67
+ <script>
68
+ function setupCounters() {
69
+ const observer = new IntersectionObserver((entries) => {
70
+ entries.forEach((entry) => {
71
+ if (!entry.isIntersecting) return;
72
+ const targetEl = entry.target.querySelector('[data-counter-target]') as HTMLElement;
73
+ if (!targetEl || targetEl.getAttribute('data-counted')) return;
74
+
75
+ targetEl.setAttribute('data-counted', 'true');
76
+ const finalVal = parseInt(targetEl.getAttribute('data-counter-target') || '0', 10);
77
+ const duration = 1500;
78
+ const start = performance.now();
79
+
80
+ function step(now: number) {
81
+ const progress = Math.min((now - start) / duration, 1);
82
+ // Ease out cubic
83
+ const eased = 1 - Math.pow(1 - progress, 3);
84
+ targetEl.textContent = Math.floor(eased * finalVal).toLocaleString();
85
+
86
+ if (progress < 1) {
87
+ requestAnimationFrame(step);
88
+ } else {
89
+ targetEl.textContent = finalVal.toLocaleString();
90
+ }
91
+ }
92
+
93
+ requestAnimationFrame(step);
94
+ });
95
+ }, { threshold: 0.2 });
96
+
97
+ document.querySelectorAll('[data-counter-wrap]').forEach((el) => observer.observe(el));
98
+ }
99
+
100
+ setupCounters();
101
+ document.addEventListener('astro:page-load', setupCounters);
102
+ </script>