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.
- package/README.md +159 -176
- package/package.json +2 -2
- package/src/components/index.ts +16 -202
- package/src/components/lib/buttons/NeonBorderButton.astro +162 -0
- package/src/components/lib/buttons/RippleButton.astro +199 -0
- package/src/components/lib/buttons/SlideRevealButton.astro +159 -0
- package/src/components/lib/buttons/index.ts +4 -0
- package/src/components/lib/cards/GithubRepoCard.astro +176 -0
- package/src/components/lib/cards/PricingCompareMiniCard.astro +196 -0
- package/src/components/lib/cards/StatsTrendCard.astro +149 -0
- package/src/components/lib/cards/index.ts +3 -0
- package/src/components/lib/charts/ActivityRings.astro +105 -0
- package/src/components/lib/charts/BoxPlotChart.astro +109 -0
- package/src/components/lib/charts/BubbleChart.astro +101 -0
- package/src/components/lib/charts/BulletChart.astro +160 -0
- package/src/components/lib/charts/BumpChart.astro +92 -0
- package/src/components/lib/charts/CandlestickChart.astro +105 -0
- package/src/components/lib/charts/ChartFrame.astro +373 -0
- package/src/components/lib/charts/DivergingBarChart.astro +98 -0
- package/src/components/lib/charts/DumbbellChart.astro +92 -0
- package/src/components/lib/charts/ForecastLineChart.astro +125 -0
- package/src/components/lib/charts/FunnelChart.astro +122 -0
- package/src/components/lib/charts/GaugeChart.astro +111 -0
- package/src/components/lib/charts/GoalProgressChart.astro +97 -0
- package/src/components/lib/charts/GroupedBarChart.astro +125 -0
- package/src/components/lib/charts/HeatmapChart.astro +131 -0
- package/src/components/lib/charts/HistogramChart.astro +111 -0
- package/src/components/lib/charts/HorizontalBarChart.astro +155 -0
- package/src/components/lib/charts/KpiStatTile.astro +116 -0
- package/src/components/lib/charts/KpiTrendGrid.astro +60 -0
- package/src/components/lib/charts/LineChart.astro +160 -0
- package/src/components/lib/charts/LinearProgressGauge.astro +136 -0
- package/src/components/lib/charts/LollipopChart.astro +100 -0
- package/src/components/lib/charts/MeterGauge.astro +122 -0
- package/src/components/lib/charts/MetricDeltaTable.astro +125 -0
- package/src/components/lib/charts/PieChart.astro +135 -0
- package/src/components/lib/charts/PolarAreaChart.astro +85 -0
- package/src/components/lib/charts/PopulationPyramid.astro +122 -0
- package/src/components/lib/charts/PunchCardChart.astro +89 -0
- package/src/components/lib/charts/RadarChart.astro +94 -0
- package/src/components/lib/charts/RadarMetricChart.astro +168 -0
- package/src/components/lib/charts/RadialBarChart.astro +124 -0
- package/src/components/lib/charts/RangeAreaChart.astro +113 -0
- package/src/components/lib/charts/RetentionCohortTable.astro +126 -0
- package/src/components/lib/charts/SankeyChart.astro +128 -0
- package/src/components/lib/charts/ScatterPlot.astro +139 -0
- package/src/components/lib/charts/SegmentedBarChart.astro +119 -0
- package/src/components/lib/charts/SlopeChart.astro +105 -0
- package/src/components/lib/charts/SparklineChart.astro +87 -0
- package/src/components/lib/charts/StackedAreaChart.astro +130 -0
- package/src/components/lib/charts/StackedBarChart.astro +136 -0
- package/src/components/lib/charts/SunburstChart.astro +103 -0
- package/src/components/lib/charts/TreemapChart.astro +94 -0
- package/src/components/lib/charts/WaffleChart.astro +121 -0
- package/src/components/lib/charts/WaterfallChart.astro +118 -0
- package/src/components/lib/charts/chart-tooltip.ts +75 -0
- package/src/components/lib/charts/chart-utils.ts +271 -0
- package/src/components/lib/charts/index.ts +46 -0
- package/src/components/lib/data/ActivityHeatmapGrid.astro +157 -0
- package/src/components/lib/data/MetricComparisonRow.astro +109 -0
- package/src/components/lib/data/StatusTimelineFeed.astro +168 -0
- package/src/components/lib/data/index.ts +3 -0
- package/src/components/lib/feedback/ProgressRingCounter.astro +118 -0
- package/src/components/lib/feedback/ToastNotificationGroup.astro +173 -0
- package/src/components/lib/feedback/index.ts +2 -0
- package/src/components/lib/forms/GlassInlineToggleGroup.astro +161 -0
- package/src/components/lib/forms/PasswordStrengthInput.astro +242 -0
- package/src/components/lib/forms/PhoneCountryInput.astro +145 -0
- package/src/components/lib/forms/TagsAutocompleteInput.astro +249 -0
- package/src/components/lib/forms/index.ts +5 -0
- package/src/components/lib/layout/DiagonalSectionDivider.astro +69 -0
- package/src/components/lib/layout/HolyGrailGrid.astro +125 -0
- package/src/components/lib/layout/index.ts +3 -1
- package/src/components/lib/navigation/QuickJumpDock.astro +125 -0
- package/src/components/lib/navigation/StepBreadcrumbBar.astro +151 -0
- package/src/components/lib/navigation/SubNavbarPills.astro +102 -0
- package/src/components/lib/navigation/index.ts +3 -0
- package/src/components/lib/overlays/CookiePreferencesDrawer.astro +226 -0
- package/src/components/lib/overlays/FloatingFeedbackWidget.astro +203 -0
- package/src/components/lib/overlays/ImageCompareSlider.astro +172 -0
- package/src/components/lib/overlays/index.ts +3 -0
- package/src/components/lib/sections/BlogGridSection.astro +131 -0
- package/src/components/lib/sections/CareersOpenings.astro +139 -0
- package/src/components/lib/sections/ChangelogSection.astro +144 -0
- package/src/components/lib/sections/ContactSplitMap.astro +139 -0
- package/src/components/lib/sections/CtaNewsletterSection.astro +163 -0
- package/src/components/lib/sections/CtaSplitImage.astro +147 -0
- package/src/components/lib/sections/FaqTwoColumn.astro +119 -0
- package/src/components/lib/sections/FeatureAlternatingRows.astro +190 -0
- package/src/components/lib/sections/FeatureIconGrid.astro +155 -0
- package/src/components/lib/sections/FeatureTabsShowcase.astro +180 -0
- package/src/components/lib/sections/FooterMegaSection.astro +155 -0
- package/src/components/lib/sections/HeroDashboardPreview.astro +180 -0
- package/src/components/lib/sections/HeroMinimalCentered.astro +175 -0
- package/src/components/lib/sections/HeroShowcaseSplit.astro +304 -0
- package/src/components/lib/sections/HeroTerminal.astro +251 -0
- package/src/components/lib/sections/HeroWaitlist.astro +229 -0
- package/src/components/lib/sections/HowItWorksSteps.astro +178 -0
- package/src/components/lib/sections/IntegrationsGrid.astro +167 -0
- package/src/components/lib/sections/LogoCloudSection.astro +96 -0
- package/src/components/lib/sections/LogoWallGrid.astro +91 -0
- package/src/components/lib/sections/PricingSinglePlan.astro +157 -0
- package/src/components/lib/sections/PricingToggleCards.astro +171 -0
- package/src/components/lib/sections/StatsCounterBand.astro +122 -0
- package/src/components/lib/sections/StatsDashboardSection.astro +83 -0
- package/src/components/lib/sections/TestimonialCarousel.astro +165 -0
- package/src/components/lib/sections/TestimonialFeatured.astro +120 -0
- package/src/components/lib/sections/TestimonialGridSection.astro +188 -0
- package/src/components/lib/sections/index.ts +29 -0
- package/src/components/lib/typography/AnimatedCounterText.astro +102 -0
- package/src/components/lib/typography/CurvedPathHeading.astro +86 -0
- package/src/components/lib/typography/GradientTextMarquee.astro +105 -0
- package/src/components/lib/typography/HackerDecodeText.astro +85 -0
- package/src/components/lib/typography/index.ts +4 -0
- package/src/styles/global.css +18 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* FaqTwoColumn — FAQ section with a sticky intro column (heading + support CTA)
|
|
4
|
+
* and native <details> accordions on the right. Works without JavaScript.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} title - Section heading.
|
|
7
|
+
* @param {string} subtitle - Intro copy.
|
|
8
|
+
* @param {Array<{q: string, a: string}>} items - Questions and answers.
|
|
9
|
+
* @param {{label: string, href: string}} support - Support link.
|
|
10
|
+
* @param {boolean} exclusive - Only one item open at a time (uses the `name` attribute).
|
|
11
|
+
* @param {string} variant - Item style (bordered|cards|plain).
|
|
12
|
+
* @param {string} size - Vertical rhythm (sm|md|lg).
|
|
13
|
+
* @param {string} className - Extra CSS class.
|
|
14
|
+
*/
|
|
15
|
+
interface Props {
|
|
16
|
+
title?: string;
|
|
17
|
+
subtitle?: string;
|
|
18
|
+
items?: Array<{ q: string; a: string }>;
|
|
19
|
+
support?: { label: string; href: string };
|
|
20
|
+
exclusive?: boolean;
|
|
21
|
+
variant?: 'bordered' | 'cards' | 'plain';
|
|
22
|
+
size?: 'sm' | 'md' | 'lg';
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
title = 'Frequently asked questions',
|
|
28
|
+
subtitle = 'Everything you need to know about the kit. Can’t find an answer? Our team replies within a day.',
|
|
29
|
+
items = [
|
|
30
|
+
{ q: 'Do I need to install a package?', a: 'No. Every component is a standalone .astro file. Copy it into your project, or install the package if you prefer imports.' },
|
|
31
|
+
{ q: 'Do the charts need a JavaScript library?', a: 'No. The SVG charts render at build time. A tiny shared script only powers hover tooltips, and every chart includes a data table fallback.' },
|
|
32
|
+
{ q: 'Is it accessible?', a: 'Components target WCAG 2.2 AA: semantic HTML, visible focus states, keyboard support and contrast of at least 4.5:1 for text.' },
|
|
33
|
+
{ q: 'Can I use it in commercial projects?', a: 'Yes. The kit is MIT licensed. Use it for client work, SaaS products and anything else.' },
|
|
34
|
+
{ q: 'How do I change the colors?', a: 'Edit the CSS custom properties in global.css. Every component reads from the same tokens, so one change updates everything.' },
|
|
35
|
+
],
|
|
36
|
+
support = { label: 'Contact support', href: '#' },
|
|
37
|
+
exclusive = true,
|
|
38
|
+
variant = 'bordered',
|
|
39
|
+
size = 'md',
|
|
40
|
+
className = '',
|
|
41
|
+
} = Astro.props;
|
|
42
|
+
|
|
43
|
+
const uid = Math.random().toString(36).slice(2, 8);
|
|
44
|
+
const id = `ftc-${uid}`;
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
<section class={`ftc ftc--${variant} ftc--${size} ${className}`} aria-labelledby={id}>
|
|
48
|
+
<div class="ftc__inner">
|
|
49
|
+
<div class="ftc__intro">
|
|
50
|
+
<h2 id={id} class="ftc__title">{title}</h2>
|
|
51
|
+
<p class="ftc__subtitle">{subtitle}</p>
|
|
52
|
+
{support && (
|
|
53
|
+
<a class="ftc__support" href={support.href}>
|
|
54
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /></svg>
|
|
55
|
+
{support.label}
|
|
56
|
+
</a>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
<div class="ftc__list">
|
|
60
|
+
{items.map((it, i) => (
|
|
61
|
+
<details class="ftc__item" name={exclusive ? `faq-${uid}` : undefined} open={i === 0}>
|
|
62
|
+
<summary class="ftc__q">
|
|
63
|
+
<span>{it.q}</span>
|
|
64
|
+
<svg class="ftc__chev" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
|
|
65
|
+
</summary>
|
|
66
|
+
<div class="ftc__a"><p>{it.a}</p></div>
|
|
67
|
+
</details>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</section>
|
|
72
|
+
|
|
73
|
+
<style>
|
|
74
|
+
.ftc {
|
|
75
|
+
--ftc-py: 5.5rem;
|
|
76
|
+
padding: var(--ftc-py) var(--sp-6, 1.5rem);
|
|
77
|
+
background: var(--c-bg, #050505);
|
|
78
|
+
}
|
|
79
|
+
.ftc--sm { --ftc-py: 3.5rem; }
|
|
80
|
+
.ftc--lg { --ftc-py: 7.5rem; }
|
|
81
|
+
.ftc__inner { max-width: 1120px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1.6fr; gap: clamp(2rem, 6vw, 5rem); align-items: start; }
|
|
82
|
+
.ftc__intro { position: sticky; top: 6rem; }
|
|
83
|
+
.ftc__title { margin: 0 0 1rem; font-size: clamp(1.9rem, 4vw, 2.5rem); font-weight: 800; letter-spacing: -0.03em; line-height: 1.12; color: var(--c-text-1, #fff); }
|
|
84
|
+
.ftc__subtitle { margin: 0 0 1.75rem; font-size: 1rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); }
|
|
85
|
+
.ftc__support { display: inline-flex; align-items: center; gap: 0.5rem; min-height: 46px; padding: 0 1.25rem; border-radius: var(--r-md, 12px); border: 1px solid rgba(255, 255, 255, 0.14); color: var(--c-text-1, #fff); font-weight: 600; text-decoration: none; transition: border-color 0.2s ease; }
|
|
86
|
+
.ftc__support:hover { border-color: rgba(255, 255, 255, 0.32); color: var(--c-text-1, #fff); }
|
|
87
|
+
.ftc__support:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
|
|
88
|
+
.ftc__list { display: grid; }
|
|
89
|
+
.ftc--cards .ftc__list { gap: 0.75rem; }
|
|
90
|
+
.ftc--bordered .ftc__item { border-bottom: 1px solid rgba(255, 255, 255, 0.08); }
|
|
91
|
+
.ftc--bordered .ftc__item:first-child { border-top: 1px solid rgba(255, 255, 255, 0.08); }
|
|
92
|
+
.ftc--cards .ftc__item { border-radius: var(--r-md, 14px); background: rgba(255, 255, 255, 0.025); border: 1px solid var(--c-border, rgba(255, 255, 255, 0.07)); padding-inline: 1.25rem; }
|
|
93
|
+
.ftc__q {
|
|
94
|
+
display: flex;
|
|
95
|
+
justify-content: space-between;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: 1rem;
|
|
98
|
+
min-height: 64px;
|
|
99
|
+
padding: 1rem 0;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
list-style: none;
|
|
102
|
+
font-size: 1.0625rem;
|
|
103
|
+
font-weight: 600;
|
|
104
|
+
color: var(--c-text-1, #fff);
|
|
105
|
+
}
|
|
106
|
+
.ftc__q::-webkit-details-marker { display: none; }
|
|
107
|
+
.ftc__q:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; border-radius: 6px; }
|
|
108
|
+
.ftc__chev { flex-shrink: 0; color: var(--c-text-2, #a3a3a3); transition: transform 0.25s ease; }
|
|
109
|
+
.ftc__item[open] .ftc__chev { transform: rotate(45deg); color: var(--c-primary-light, #d4a017); }
|
|
110
|
+
.ftc__a { padding: 0 2.5rem 1.25rem 0; }
|
|
111
|
+
.ftc__a p { margin: 0; font-size: 0.9875rem; line-height: 1.7; color: var(--c-text-2, #a3a3a3); }
|
|
112
|
+
.ftc__item[open] .ftc__a { animation: ftc-in 0.25s ease; }
|
|
113
|
+
@keyframes ftc-in { from { opacity: 0; transform: translateY(-4px); } }
|
|
114
|
+
@media (max-width: 820px) {
|
|
115
|
+
.ftc__inner { grid-template-columns: 1fr; }
|
|
116
|
+
.ftc__intro { position: static; }
|
|
117
|
+
}
|
|
118
|
+
@media (prefers-reduced-motion: reduce) { .ftc__chev { transition: none; } .ftc__item[open] .ftc__a { animation: none; } }
|
|
119
|
+
</style>
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* FeatureAlternatingRows — Zig-zag feature section: text and a visual alternate
|
|
4
|
+
* sides on each row. Visuals are pure-CSS illustrations (no images to load).
|
|
5
|
+
*
|
|
6
|
+
* @param {string} title - Section heading (visually hidden if empty-styled).
|
|
7
|
+
* @param {Array<{eyebrow: string, title: string, text: string, bullets: string[], visual: 'chart'|'code'|'stack'}>} rows - Feature rows.
|
|
8
|
+
* @param {boolean} startReversed - Put the visual on the left in the first row.
|
|
9
|
+
* @param {string} variant - Visual frame (glass|outline|plain).
|
|
10
|
+
* @param {string} size - Vertical rhythm (sm|md|lg).
|
|
11
|
+
* @param {string} className - Extra CSS class.
|
|
12
|
+
*/
|
|
13
|
+
interface Row { eyebrow: string; title: string; text: string; bullets: string[]; visual: 'chart' | 'code' | 'stack' }
|
|
14
|
+
interface Props {
|
|
15
|
+
title?: string;
|
|
16
|
+
rows?: Row[];
|
|
17
|
+
startReversed?: boolean;
|
|
18
|
+
variant?: 'glass' | 'outline' | 'plain';
|
|
19
|
+
size?: 'sm' | 'md' | 'lg';
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
title = 'How the kit helps your team',
|
|
25
|
+
rows = [
|
|
26
|
+
{
|
|
27
|
+
eyebrow: 'Insights',
|
|
28
|
+
title: 'Dashboards your stakeholders actually read',
|
|
29
|
+
text: 'Drop-in SVG charts with tooltips, legends and table fallbacks. No chart library to configure.',
|
|
30
|
+
bullets: ['45 chart types', 'Colorblind-safe palette', 'Keyboard-accessible marks'],
|
|
31
|
+
visual: 'chart',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
eyebrow: 'Developer experience',
|
|
35
|
+
title: 'Typed props, sensible defaults',
|
|
36
|
+
text: 'Every component ships a TypeScript Props interface and renders beautifully with zero configuration.',
|
|
37
|
+
bullets: ['Strict TypeScript', 'JSDoc on every prop', 'Copy-paste friendly'],
|
|
38
|
+
visual: 'code',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
eyebrow: 'Design system',
|
|
42
|
+
title: 'One token file to re-theme everything',
|
|
43
|
+
text: 'Swap colors, radii and spacing in global.css and every section updates consistently.',
|
|
44
|
+
bullets: ['CSS custom properties', 'Dark-first', '8pt spacing grid'],
|
|
45
|
+
visual: 'stack',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
startReversed = false,
|
|
49
|
+
variant = 'glass',
|
|
50
|
+
size = 'md',
|
|
51
|
+
className = '',
|
|
52
|
+
} = Astro.props;
|
|
53
|
+
|
|
54
|
+
const id = `far-${Math.random().toString(36).slice(2, 8)}`;
|
|
55
|
+
const bars = [38, 62, 48, 80, 66, 92, 74];
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
<section class={`far far--${variant} far--${size} ${className}`} aria-labelledby={id}>
|
|
59
|
+
<h2 id={id} class="far__sr">{title}</h2>
|
|
60
|
+
<div class="far__inner">
|
|
61
|
+
{rows.map((r, i) => {
|
|
62
|
+
const reversed = startReversed ? i % 2 === 0 : i % 2 === 1;
|
|
63
|
+
return (
|
|
64
|
+
<article class={`far__row ${reversed ? 'far__row--rev' : ''}`}>
|
|
65
|
+
<div class="far__copy">
|
|
66
|
+
<p class="far__eyebrow">{r.eyebrow}</p>
|
|
67
|
+
<h3 class="far__title">{r.title}</h3>
|
|
68
|
+
<p class="far__text">{r.text}</p>
|
|
69
|
+
<ul class="far__bullets">
|
|
70
|
+
{r.bullets.map((b) => (
|
|
71
|
+
<li>
|
|
72
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
|
|
73
|
+
{b}
|
|
74
|
+
</li>
|
|
75
|
+
))}
|
|
76
|
+
</ul>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="far__visual" aria-hidden="true">
|
|
79
|
+
{r.visual === 'chart' && (
|
|
80
|
+
<div class="far__chart">
|
|
81
|
+
{bars.map((h, bi) => <span style={`--h:${h}%; --d:${bi * 80}ms`}></span>)}
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
{r.visual === 'code' && (
|
|
85
|
+
<pre class="far__code"><code><span class="k">interface</span> <span class="t">Props</span> {'{'}
|
|
86
|
+
title?: <span class="t">string</span>;
|
|
87
|
+
variant?: <span class="s">'glass'</span> | <span class="s">'solid'</span>;
|
|
88
|
+
size?: <span class="s">'sm'</span> | <span class="s">'md'</span> | <span class="s">'lg'</span>;
|
|
89
|
+
{'}'}
|
|
90
|
+
|
|
91
|
+
<span class="c">// Sensible defaults, zero config</span>
|
|
92
|
+
<<span class="t">LineChart</span> /></code></pre>
|
|
93
|
+
)}
|
|
94
|
+
{r.visual === 'stack' && (
|
|
95
|
+
<div class="far__stack">
|
|
96
|
+
<span style="--c:var(--chart-1, #3987e5)">--chart-1</span>
|
|
97
|
+
<span style="--c:var(--c-primary-light, #d4a017)">--c-primary</span>
|
|
98
|
+
<span style="--c:var(--chart-3, #199e70)">--chart-pos</span>
|
|
99
|
+
<span style="--c:var(--chart-7, #9085e9)">--r-lg: 20px</span>
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
</article>
|
|
104
|
+
);
|
|
105
|
+
})}
|
|
106
|
+
</div>
|
|
107
|
+
</section>
|
|
108
|
+
|
|
109
|
+
<style>
|
|
110
|
+
.far {
|
|
111
|
+
--far-py: 5.5rem;
|
|
112
|
+
padding: var(--far-py) var(--sp-6, 1.5rem);
|
|
113
|
+
background: var(--c-bg, #050505);
|
|
114
|
+
}
|
|
115
|
+
.far--sm { --far-py: 3.5rem; }
|
|
116
|
+
.far--lg { --far-py: 7.5rem; }
|
|
117
|
+
.far__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
118
|
+
.far__inner { max-width: 1120px; margin: 0 auto; display: grid; gap: clamp(4rem, 8vw, 7rem); }
|
|
119
|
+
.far__row { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(2rem, 5vw, 5rem); align-items: center; }
|
|
120
|
+
.far__row--rev .far__copy { order: 2; }
|
|
121
|
+
.far__eyebrow { margin: 0 0 0.75rem; font-size: 0.8125rem; font-weight: 650; letter-spacing: 0.08em; text-transform: uppercase; color: var(--c-primary-light, #d4a017); }
|
|
122
|
+
.far__title { margin: 0 0 1rem; font-size: clamp(1.6rem, 3vw, 2.25rem); font-weight: 750; letter-spacing: -0.025em; line-height: 1.15; color: var(--c-text-1, #fff); }
|
|
123
|
+
.far__text { margin: 0 0 1.5rem; font-size: 1.0625rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); }
|
|
124
|
+
.far__bullets { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.7rem; }
|
|
125
|
+
.far__bullets li { display: flex; align-items: center; gap: 0.6rem; color: var(--c-text-1, #fff); font-size: 0.9375rem; }
|
|
126
|
+
.far__bullets svg { color: var(--chart-pos, #34c38f); flex-shrink: 0; }
|
|
127
|
+
|
|
128
|
+
.far__visual {
|
|
129
|
+
aspect-ratio: 4 / 3;
|
|
130
|
+
border-radius: var(--r-xl, 24px);
|
|
131
|
+
display: grid;
|
|
132
|
+
place-items: center;
|
|
133
|
+
padding: 2rem;
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
}
|
|
136
|
+
.far--glass .far__visual {
|
|
137
|
+
background: radial-gradient(circle at 30% 20%, rgba(255, 255, 255, 0.06), transparent 60%), rgba(255, 255, 255, 0.02);
|
|
138
|
+
border: 1px solid var(--c-border, rgba(255, 255, 255, 0.07));
|
|
139
|
+
}
|
|
140
|
+
.far--outline .far__visual { border: 1px dashed rgba(255, 255, 255, 0.14); }
|
|
141
|
+
.far__chart { display: flex; align-items: flex-end; gap: 10px; width: 100%; height: 70%; }
|
|
142
|
+
.far__chart span {
|
|
143
|
+
flex: 1;
|
|
144
|
+
height: var(--h);
|
|
145
|
+
border-radius: 6px 6px 0 0;
|
|
146
|
+
background: linear-gradient(to top, rgba(57, 135, 229, 0.35), var(--chart-1, #3987e5));
|
|
147
|
+
transform-origin: bottom;
|
|
148
|
+
animation: far-grow 0.8s cubic-bezier(0.4, 0, 0.2, 1) both;
|
|
149
|
+
animation-delay: var(--d);
|
|
150
|
+
}
|
|
151
|
+
@keyframes far-grow { from { transform: scaleY(0); } }
|
|
152
|
+
.far__code {
|
|
153
|
+
margin: 0;
|
|
154
|
+
width: 100%;
|
|
155
|
+
padding: 1.5rem;
|
|
156
|
+
border-radius: var(--r-md, 12px);
|
|
157
|
+
background: #0b0b0d;
|
|
158
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
159
|
+
font-family: var(--font-mono, monospace);
|
|
160
|
+
font-size: 0.8125rem;
|
|
161
|
+
line-height: 1.75;
|
|
162
|
+
color: var(--c-text-2, #c4c4c4);
|
|
163
|
+
overflow-x: auto;
|
|
164
|
+
}
|
|
165
|
+
.far__code .k { color: #c792ea; }
|
|
166
|
+
.far__code .t { color: #82aaff; }
|
|
167
|
+
.far__code .s { color: #c3e88d; }
|
|
168
|
+
.far__code .c { color: #6b7280; }
|
|
169
|
+
.far__stack { display: grid; gap: 0.75rem; width: 100%; max-width: 320px; }
|
|
170
|
+
.far__stack span {
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
gap: 0.75rem;
|
|
174
|
+
padding: 0.85rem 1rem;
|
|
175
|
+
border-radius: var(--r-md, 12px);
|
|
176
|
+
background: rgba(255, 255, 255, 0.03);
|
|
177
|
+
border: 1px solid rgba(255, 255, 255, 0.07);
|
|
178
|
+
font-family: var(--font-mono, monospace);
|
|
179
|
+
font-size: 0.8125rem;
|
|
180
|
+
color: var(--c-text-1, #fff);
|
|
181
|
+
}
|
|
182
|
+
.far__stack span::before { content: ''; width: 20px; height: 20px; border-radius: 6px; background: var(--c); }
|
|
183
|
+
.far__stack span:nth-child(2) { margin-left: 1.5rem; }
|
|
184
|
+
.far__stack span:nth-child(3) { margin-left: 3rem; }
|
|
185
|
+
@media (max-width: 820px) {
|
|
186
|
+
.far__row { grid-template-columns: 1fr; }
|
|
187
|
+
.far__row--rev .far__copy { order: 0; }
|
|
188
|
+
}
|
|
189
|
+
@media (prefers-reduced-motion: reduce) { .far__chart span { animation: none; } }
|
|
190
|
+
</style>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* FeatureIconGrid — Classic 3-column feature grid with SVG icons, titles,
|
|
4
|
+
* descriptions and optional "learn more" links. Cards light up on hover/focus.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} eyebrow - Small label above the heading.
|
|
7
|
+
* @param {string} title - Section heading.
|
|
8
|
+
* @param {string} subtitle - Section description.
|
|
9
|
+
* @param {Array<{icon: string, title: string, text: string, href?: string}>} features - Feature cards.
|
|
10
|
+
* `icon` is one of: bolt, shield, layers, chart, globe, code, sparkles, lock, users.
|
|
11
|
+
* @param {number} columns - Max columns on desktop (2|3|4).
|
|
12
|
+
* @param {string} variant - Card style (card|plain|bordered).
|
|
13
|
+
* @param {string} size - Vertical rhythm (sm|md|lg).
|
|
14
|
+
* @param {string} className - Extra CSS class.
|
|
15
|
+
*/
|
|
16
|
+
interface Feature { icon: string; title: string; text: string; href?: string }
|
|
17
|
+
interface Props {
|
|
18
|
+
eyebrow?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
subtitle?: string;
|
|
21
|
+
features?: Feature[];
|
|
22
|
+
columns?: 2 | 3 | 4;
|
|
23
|
+
variant?: 'card' | 'plain' | 'bordered';
|
|
24
|
+
size?: 'sm' | 'md' | 'lg';
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const icons: Record<string, string> = {
|
|
29
|
+
bolt: '<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>',
|
|
30
|
+
shield: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="m9 12 2 2 4-4"/>',
|
|
31
|
+
layers: '<path d="m12 2 10 5-10 5L2 7l10-5z"/><path d="m2 17 10 5 10-5"/><path d="m2 12 10 5 10-5"/>',
|
|
32
|
+
chart: '<path d="M3 3v18h18"/><path d="m19 9-5 5-4-4-3 3"/>',
|
|
33
|
+
globe: '<circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20z"/>',
|
|
34
|
+
code: '<path d="m16 18 6-6-6-6M8 6l-6 6 6 6"/>',
|
|
35
|
+
sparkles: '<path d="M12 3l1.9 5.8L20 10l-6.1 1.2L12 17l-1.9-5.8L4 10l6.1-1.2z"/><path d="M19 17v4M17 19h4"/>',
|
|
36
|
+
lock: '<rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
|
|
37
|
+
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/>',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
eyebrow = 'Features',
|
|
42
|
+
title = 'Everything you need, nothing you don’t',
|
|
43
|
+
subtitle = 'A focused toolkit that keeps your pages fast, accessible and on-brand.',
|
|
44
|
+
features = [
|
|
45
|
+
{ icon: 'bolt', title: 'Zero runtime JS', text: 'Components render to static HTML and CSS. Interactivity is progressive and tiny.', href: '#' },
|
|
46
|
+
{ icon: 'shield', title: 'Accessible by default', text: 'Semantic markup, focus rings, ARIA where it matters and 4.5:1 contrast everywhere.', href: '#' },
|
|
47
|
+
{ icon: 'layers', title: 'Design tokens', text: 'Colors, spacing and radii come from CSS custom properties you can re-theme in one file.' },
|
|
48
|
+
{ icon: 'chart', title: 'SVG charts', text: '45 dependency-free charts with tooltips, legends and data-table fallbacks.' },
|
|
49
|
+
{ icon: 'globe', title: 'i18n ready', text: 'Every string is a prop. Ship English, Spanish and Russian docs out of the box.' },
|
|
50
|
+
{ icon: 'code', title: 'Copy & paste', text: 'No package lock-in. Grab the source from the docs and own every line.' },
|
|
51
|
+
],
|
|
52
|
+
columns = 3,
|
|
53
|
+
variant = 'card',
|
|
54
|
+
size = 'md',
|
|
55
|
+
className = '',
|
|
56
|
+
} = Astro.props;
|
|
57
|
+
|
|
58
|
+
const id = `fig-${Math.random().toString(36).slice(2, 8)}`;
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
<section class={`fig fig--${variant} fig--${size} ${className}`} aria-labelledby={id} style={`--fig-cols:${columns}`}>
|
|
62
|
+
<div class="fig__inner">
|
|
63
|
+
<header class="fig__head">
|
|
64
|
+
<p class="fig__eyebrow">{eyebrow}</p>
|
|
65
|
+
<h2 id={id} class="fig__title">{title}</h2>
|
|
66
|
+
<p class="fig__subtitle">{subtitle}</p>
|
|
67
|
+
</header>
|
|
68
|
+
<ul class="fig__grid">
|
|
69
|
+
{features.map((f) => (
|
|
70
|
+
<li class="fig__card">
|
|
71
|
+
<span class="fig__icon" aria-hidden="true">
|
|
72
|
+
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" set:html={icons[f.icon] ?? icons.sparkles}></svg>
|
|
73
|
+
</span>
|
|
74
|
+
<h3 class="fig__card-title">{f.title}</h3>
|
|
75
|
+
<p class="fig__text">{f.text}</p>
|
|
76
|
+
{f.href && (
|
|
77
|
+
<a class="fig__link" href={f.href}>
|
|
78
|
+
Learn more<span class="fig__sr"> about {f.title}</span>
|
|
79
|
+
<svg width="14" height="14" 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>
|
|
80
|
+
</a>
|
|
81
|
+
)}
|
|
82
|
+
</li>
|
|
83
|
+
))}
|
|
84
|
+
</ul>
|
|
85
|
+
</div>
|
|
86
|
+
</section>
|
|
87
|
+
|
|
88
|
+
<style>
|
|
89
|
+
.fig {
|
|
90
|
+
--fig-py: 5.5rem;
|
|
91
|
+
padding: var(--fig-py) var(--sp-6, 1.5rem);
|
|
92
|
+
background: var(--c-bg, #050505);
|
|
93
|
+
}
|
|
94
|
+
.fig--sm { --fig-py: 3.5rem; }
|
|
95
|
+
.fig--lg { --fig-py: 7.5rem; }
|
|
96
|
+
.fig__inner { max-width: 1180px; margin: 0 auto; }
|
|
97
|
+
.fig__head { max-width: 640px; margin: 0 auto 3.5rem; text-align: center; }
|
|
98
|
+
.fig__eyebrow { margin: 0 0 0.75rem; font-size: 0.8125rem; font-weight: 650; letter-spacing: 0.08em; text-transform: uppercase; color: var(--c-primary-light, #d4a017); }
|
|
99
|
+
.fig__title { margin: 0 0 1rem; font-size: clamp(1.9rem, 4vw, 2.75rem); font-weight: 800; letter-spacing: -0.03em; line-height: 1.12; color: var(--c-text-1, #fff); text-wrap: balance; }
|
|
100
|
+
.fig__subtitle { margin: 0; font-size: 1.0625rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
|
|
101
|
+
.fig__grid {
|
|
102
|
+
list-style: none;
|
|
103
|
+
margin: 0;
|
|
104
|
+
padding: 0;
|
|
105
|
+
display: grid;
|
|
106
|
+
grid-template-columns: repeat(auto-fill, minmax(min(100%, calc((100% - (var(--fig-cols) - 1) * 1.25rem) / var(--fig-cols))), 1fr));
|
|
107
|
+
gap: 1.25rem;
|
|
108
|
+
}
|
|
109
|
+
.fig__card {
|
|
110
|
+
position: relative;
|
|
111
|
+
padding: 1.75rem;
|
|
112
|
+
border-radius: var(--r-lg, 20px);
|
|
113
|
+
transition: border-color 0.25s ease, background 0.25s ease, transform 0.25s ease;
|
|
114
|
+
}
|
|
115
|
+
.fig--card .fig__card {
|
|
116
|
+
background: linear-gradient(160deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01));
|
|
117
|
+
border: 1px solid var(--c-border, rgba(255, 255, 255, 0.07));
|
|
118
|
+
}
|
|
119
|
+
.fig--bordered .fig__card { border: 1px solid rgba(255, 255, 255, 0.1); }
|
|
120
|
+
.fig--plain .fig__card { padding: 0.5rem 0; }
|
|
121
|
+
.fig--card .fig__card:hover, .fig--card .fig__card:focus-within,
|
|
122
|
+
.fig--bordered .fig__card:hover, .fig--bordered .fig__card:focus-within {
|
|
123
|
+
border-color: rgba(212, 160, 23, 0.35);
|
|
124
|
+
transform: translateY(-3px);
|
|
125
|
+
}
|
|
126
|
+
.fig__icon {
|
|
127
|
+
display: grid;
|
|
128
|
+
place-items: center;
|
|
129
|
+
width: 46px;
|
|
130
|
+
height: 46px;
|
|
131
|
+
margin-bottom: 1.25rem;
|
|
132
|
+
border-radius: var(--r-md, 12px);
|
|
133
|
+
background: var(--c-primary-glow, rgba(184, 134, 11, 0.12));
|
|
134
|
+
border: 1px solid rgba(212, 160, 23, 0.25);
|
|
135
|
+
color: var(--c-primary-light, #d4a017);
|
|
136
|
+
}
|
|
137
|
+
.fig__card-title { margin: 0 0 0.5rem; font-size: 1.125rem; font-weight: 650; color: var(--c-text-1, #fff); letter-spacing: -0.01em; }
|
|
138
|
+
.fig__text { margin: 0; font-size: 0.9375rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
|
|
139
|
+
.fig__link {
|
|
140
|
+
display: inline-flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
gap: 0.35rem;
|
|
143
|
+
min-height: 44px;
|
|
144
|
+
margin-top: 0.5rem;
|
|
145
|
+
font-size: 0.875rem;
|
|
146
|
+
font-weight: 600;
|
|
147
|
+
color: var(--c-primary-light, #d4a017);
|
|
148
|
+
text-decoration: none;
|
|
149
|
+
}
|
|
150
|
+
.fig__link svg { transition: transform 0.2s ease; }
|
|
151
|
+
.fig__link:hover svg { transform: translateX(3px); }
|
|
152
|
+
.fig__link:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; border-radius: 4px; }
|
|
153
|
+
.fig__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
154
|
+
@media (prefers-reduced-motion: reduce) { .fig__card, .fig__link svg { transition: none; } }
|
|
155
|
+
</style>
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* FeatureTabsShowcase — Interactive feature section using the WAI-ARIA tabs
|
|
4
|
+
* pattern (arrow keys, Home/End) to switch between product capabilities.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} title - Section heading.
|
|
7
|
+
* @param {string} subtitle - Section description.
|
|
8
|
+
* @param {Array<{label: string, title: string, text: string, points: string[], stat: string, statLabel: string}>} tabs - Tab panels.
|
|
9
|
+
* @param {string} variant - Tab style (pills|underline|cards).
|
|
10
|
+
* @param {string} size - Vertical rhythm (sm|md|lg).
|
|
11
|
+
* @param {string} className - Extra CSS class.
|
|
12
|
+
*/
|
|
13
|
+
interface Tab { label: string; title: string; text: string; points: string[]; stat: string; statLabel: string }
|
|
14
|
+
interface Props {
|
|
15
|
+
title?: string;
|
|
16
|
+
subtitle?: string;
|
|
17
|
+
tabs?: Tab[];
|
|
18
|
+
variant?: 'pills' | 'underline' | 'cards';
|
|
19
|
+
size?: 'sm' | 'md' | 'lg';
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
title = 'One platform, every workflow',
|
|
25
|
+
subtitle = 'Switch between capabilities to see how teams use the kit.',
|
|
26
|
+
tabs = [
|
|
27
|
+
{ label: 'Marketing', title: 'Launch pages in an afternoon', text: 'Assemble hero, features, pricing and FAQ sections into conversion-ready landing pages.', points: ['24 new section blocks', 'SEO-friendly markup', 'Instant static builds'], stat: '3.4×', statLabel: 'faster page launches' },
|
|
28
|
+
{ label: 'Product', title: 'In-app analytics without a chart library', text: 'Render KPI tiles, funnels and cohort tables straight from your data at build time.', points: ['45 SVG charts', 'Tooltips & table view', 'Zero client JS'], stat: '0 kB', statLabel: 'chart runtime shipped' },
|
|
29
|
+
{ label: 'Engineering', title: 'Accessible components you can trust', text: 'Every component is keyboard navigable and screen-reader tested, with visible focus states.', points: ['WCAG 2.2 AA', 'Typed props', 'Reduced-motion aware'], stat: '100', statLabel: 'Lighthouse accessibility' },
|
|
30
|
+
],
|
|
31
|
+
variant = 'pills',
|
|
32
|
+
size = 'md',
|
|
33
|
+
className = '',
|
|
34
|
+
} = Astro.props;
|
|
35
|
+
|
|
36
|
+
const uid = Math.random().toString(36).slice(2, 8);
|
|
37
|
+
const id = `fts-${uid}`;
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
<section class={`fts fts--${variant} fts--${size} ${className}`} aria-labelledby={id}>
|
|
41
|
+
<div class="fts__inner">
|
|
42
|
+
<header class="fts__head">
|
|
43
|
+
<h2 id={id} class="fts__title">{title}</h2>
|
|
44
|
+
<p class="fts__subtitle">{subtitle}</p>
|
|
45
|
+
</header>
|
|
46
|
+
|
|
47
|
+
<div class="fts__tabs" data-fts>
|
|
48
|
+
<div class="fts__list" role="tablist" aria-label={title}>
|
|
49
|
+
{tabs.map((t, i) => (
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
role="tab"
|
|
53
|
+
class="fts__tab"
|
|
54
|
+
id={`${id}-tab-${i}`}
|
|
55
|
+
aria-controls={`${id}-panel-${i}`}
|
|
56
|
+
aria-selected={i === 0 ? 'true' : 'false'}
|
|
57
|
+
tabindex={i === 0 ? 0 : -1}
|
|
58
|
+
>{t.label}</button>
|
|
59
|
+
))}
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{tabs.map((t, i) => (
|
|
63
|
+
<div class="fts__panel" role="tabpanel" id={`${id}-panel-${i}`} aria-labelledby={`${id}-tab-${i}`} tabindex="0" hidden={i !== 0}>
|
|
64
|
+
<div class="fts__copy">
|
|
65
|
+
<h3 class="fts__panel-title">{t.title}</h3>
|
|
66
|
+
<p class="fts__text">{t.text}</p>
|
|
67
|
+
<ul class="fts__points">
|
|
68
|
+
{t.points.map((p) => (
|
|
69
|
+
<li><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>{p}</li>
|
|
70
|
+
))}
|
|
71
|
+
</ul>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="fts__stat">
|
|
74
|
+
<span class="fts__stat-value">{t.stat}</span>
|
|
75
|
+
<span class="fts__stat-label">{t.statLabel}</span>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</section>
|
|
82
|
+
|
|
83
|
+
<script>
|
|
84
|
+
document.querySelectorAll<HTMLElement>('[data-fts]').forEach((root) => {
|
|
85
|
+
const tabs = Array.from(root.querySelectorAll<HTMLButtonElement>('[role="tab"]'));
|
|
86
|
+
const select = (next: HTMLButtonElement, focus = true) => {
|
|
87
|
+
tabs.forEach((t) => {
|
|
88
|
+
const on = t === next;
|
|
89
|
+
t.setAttribute('aria-selected', String(on));
|
|
90
|
+
t.tabIndex = on ? 0 : -1;
|
|
91
|
+
const panel = document.getElementById(t.getAttribute('aria-controls') || '');
|
|
92
|
+
if (panel) panel.hidden = !on;
|
|
93
|
+
});
|
|
94
|
+
if (focus) next.focus();
|
|
95
|
+
};
|
|
96
|
+
tabs.forEach((tab, i) => {
|
|
97
|
+
tab.addEventListener('click', () => select(tab, false));
|
|
98
|
+
tab.addEventListener('keydown', (e) => {
|
|
99
|
+
const map: Record<string, number> = { ArrowRight: i + 1, ArrowLeft: i - 1, Home: 0, End: tabs.length - 1 };
|
|
100
|
+
if (!(e.key in map)) return;
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
select(tabs[(map[e.key] + tabs.length) % tabs.length]);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<style>
|
|
109
|
+
.fts {
|
|
110
|
+
--fts-py: 5.5rem;
|
|
111
|
+
padding: var(--fts-py) var(--sp-6, 1.5rem);
|
|
112
|
+
background: var(--c-bg, #050505);
|
|
113
|
+
}
|
|
114
|
+
.fts--sm { --fts-py: 3.5rem; }
|
|
115
|
+
.fts--lg { --fts-py: 7.5rem; }
|
|
116
|
+
.fts__inner { max-width: 1080px; margin: 0 auto; }
|
|
117
|
+
.fts__head { text-align: center; max-width: 640px; margin: 0 auto 2.5rem; }
|
|
118
|
+
.fts__title { margin: 0 0 0.75rem; font-size: clamp(1.9rem, 4vw, 2.75rem); font-weight: 800; letter-spacing: -0.03em; color: var(--c-text-1, #fff); }
|
|
119
|
+
.fts__subtitle { margin: 0; font-size: 1.0625rem; color: var(--c-text-2, #a3a3a3); }
|
|
120
|
+
.fts__list { display: flex; justify-content: center; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 2rem; }
|
|
121
|
+
.fts__tab {
|
|
122
|
+
min-height: 44px;
|
|
123
|
+
padding: 0 1.25rem;
|
|
124
|
+
border: 1px solid transparent;
|
|
125
|
+
border-radius: var(--r-full, 999px);
|
|
126
|
+
background: transparent;
|
|
127
|
+
color: var(--c-text-2, #a3a3a3);
|
|
128
|
+
font: inherit;
|
|
129
|
+
font-size: 0.9375rem;
|
|
130
|
+
font-weight: 600;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
|
|
133
|
+
}
|
|
134
|
+
.fts__tab:hover { color: var(--c-text-1, #fff); }
|
|
135
|
+
.fts__tab:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
|
|
136
|
+
.fts--pills .fts__list { width: fit-content; margin-inline: auto; padding: 0.35rem; border-radius: var(--r-full, 999px); background: rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.07); }
|
|
137
|
+
.fts--pills .fts__tab[aria-selected='true'] { background: var(--c-text-1, #fff); color: #0a0a0a; }
|
|
138
|
+
.fts--underline .fts__list { border-bottom: 1px solid rgba(255, 255, 255, 0.08); gap: 1.5rem; }
|
|
139
|
+
.fts--underline .fts__tab { border-radius: 0; padding: 0 0.25rem; border-bottom: 2px solid transparent; margin-bottom: -1px; }
|
|
140
|
+
.fts--underline .fts__tab[aria-selected='true'] { color: var(--c-text-1, #fff); border-bottom-color: var(--c-primary-light, #d4a017); }
|
|
141
|
+
.fts--cards .fts__tab { border-radius: var(--r-md, 12px); border-color: rgba(255, 255, 255, 0.08); }
|
|
142
|
+
.fts--cards .fts__tab[aria-selected='true'] { border-color: var(--c-primary-light, #d4a017); color: var(--c-text-1, #fff); background: var(--c-primary-glow, rgba(184, 134, 11, 0.12)); }
|
|
143
|
+
|
|
144
|
+
.fts__panel {
|
|
145
|
+
display: grid;
|
|
146
|
+
grid-template-columns: 1.4fr 1fr;
|
|
147
|
+
gap: 2rem;
|
|
148
|
+
align-items: center;
|
|
149
|
+
padding: clamp(1.5rem, 4vw, 3rem);
|
|
150
|
+
border-radius: var(--r-xl, 24px);
|
|
151
|
+
background: linear-gradient(160deg, rgba(255, 255, 255, 0.045), rgba(255, 255, 255, 0.01));
|
|
152
|
+
border: 1px solid var(--c-border, rgba(255, 255, 255, 0.07));
|
|
153
|
+
animation: fts-in 0.35s ease;
|
|
154
|
+
}
|
|
155
|
+
.fts__panel[hidden] { display: none; }
|
|
156
|
+
.fts__panel:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 3px; }
|
|
157
|
+
@keyframes fts-in { from { opacity: 0; transform: translateY(6px); } }
|
|
158
|
+
.fts__panel-title { margin: 0 0 0.75rem; font-size: clamp(1.4rem, 3vw, 1.875rem); font-weight: 750; letter-spacing: -0.02em; color: var(--c-text-1, #fff); }
|
|
159
|
+
.fts__text { margin: 0 0 1.25rem; line-height: 1.65; color: var(--c-text-2, #a3a3a3); }
|
|
160
|
+
.fts__points { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.6rem; }
|
|
161
|
+
.fts__points li { display: flex; align-items: center; gap: 0.55rem; color: var(--c-text-1, #fff); font-size: 0.9375rem; }
|
|
162
|
+
.fts__points svg { color: var(--chart-pos, #34c38f); }
|
|
163
|
+
.fts__stat {
|
|
164
|
+
display: flex;
|
|
165
|
+
flex-direction: column;
|
|
166
|
+
align-items: center;
|
|
167
|
+
justify-content: center;
|
|
168
|
+
gap: 0.5rem;
|
|
169
|
+
min-height: 200px;
|
|
170
|
+
border-radius: var(--r-lg, 20px);
|
|
171
|
+
background: radial-gradient(circle at 50% 30%, var(--c-primary-glow, rgba(184, 134, 11, 0.16)), transparent 70%), rgba(255, 255, 255, 0.02);
|
|
172
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
173
|
+
text-align: center;
|
|
174
|
+
padding: 1.5rem;
|
|
175
|
+
}
|
|
176
|
+
.fts__stat-value { font-size: clamp(2.75rem, 6vw, 4rem); font-weight: 800; letter-spacing: -0.04em; color: var(--c-text-1, #fff); font-variant-numeric: tabular-nums; line-height: 1; }
|
|
177
|
+
.fts__stat-label { font-size: 0.875rem; color: var(--c-text-2, #a3a3a3); }
|
|
178
|
+
@media (max-width: 760px) { .fts__panel { grid-template-columns: 1fr; } }
|
|
179
|
+
@media (prefers-reduced-motion: reduce) { .fts__panel { animation: none; } }
|
|
180
|
+
</style>
|