astro-component-kit 0.3.1 → 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 +33 -14
- 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,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* ChangelogSection — Release notes timeline with version tags, dates and
|
|
4
|
+
* categorised entries (new / improved / fixed), each labelled with text + icon.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} title - Section heading.
|
|
7
|
+
* @param {string} subtitle - Section description.
|
|
8
|
+
* @param {Array<{version: string, date: string, title: string, changes: Array<{type: 'new'|'improved'|'fixed', text: string}>}>} releases - Releases, newest first.
|
|
9
|
+
* @param {{label: string, href: string}} subscribe - Optional RSS/updates link.
|
|
10
|
+
* @param {string} variant - Layout (timeline|cards|compact).
|
|
11
|
+
* @param {string} size - Vertical rhythm (sm|md|lg).
|
|
12
|
+
* @param {string} className - Extra CSS class.
|
|
13
|
+
*/
|
|
14
|
+
interface Change { type: 'new' | 'improved' | 'fixed'; text: string }
|
|
15
|
+
interface Release { version: string; date: string; title: string; changes: Change[] }
|
|
16
|
+
interface Props {
|
|
17
|
+
title?: string;
|
|
18
|
+
subtitle?: string;
|
|
19
|
+
releases?: Release[];
|
|
20
|
+
subscribe?: { label: string; href: string };
|
|
21
|
+
variant?: 'timeline' | 'cards' | 'compact';
|
|
22
|
+
size?: 'sm' | 'md' | 'lg';
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
title = 'Changelog',
|
|
28
|
+
subtitle = 'New components, improvements and fixes, shipped every week.',
|
|
29
|
+
releases = [
|
|
30
|
+
{ version: 'v0.4.0', date: '2026-11-03', title: 'SVG charts & 24 new sections', changes: [
|
|
31
|
+
{ type: 'new', text: '40 zero-dependency SVG charts with tooltips and table fallbacks.' },
|
|
32
|
+
{ type: 'new', text: 'Waitlist, terminal and dashboard hero sections.' },
|
|
33
|
+
{ type: 'improved', text: 'Chart color tokens validated for color-vision deficiency.' },
|
|
34
|
+
] },
|
|
35
|
+
{ version: 'v0.3.1', date: '2026-10-27', title: 'Localized SEO', changes: [
|
|
36
|
+
{ type: 'improved', text: 'Meta titles and descriptions in English, Spanish and Russian.' },
|
|
37
|
+
{ type: 'fixed', text: 'Code blocks no longer appear in search snippets.' },
|
|
38
|
+
] },
|
|
39
|
+
{ version: 'v0.3.0', date: '2026-10-12', title: 'Notes & categories', changes: [
|
|
40
|
+
{ type: 'new', text: 'Browse-by-category section on the home page.' },
|
|
41
|
+
{ type: 'fixed', text: 'Category titles resolve correctly in every locale.' },
|
|
42
|
+
] },
|
|
43
|
+
],
|
|
44
|
+
subscribe = { label: 'Subscribe via RSS', href: '#' },
|
|
45
|
+
variant = 'timeline',
|
|
46
|
+
size = 'md',
|
|
47
|
+
className = '',
|
|
48
|
+
} = Astro.props;
|
|
49
|
+
|
|
50
|
+
const id = `cls-${Math.random().toString(36).slice(2, 8)}`;
|
|
51
|
+
const typeLabel = { new: 'New', improved: 'Improved', fixed: 'Fixed' };
|
|
52
|
+
const typeIcon = {
|
|
53
|
+
new: '<path d="M12 5v14M5 12h14"/>',
|
|
54
|
+
improved: '<path d="M12 19V5M5 12l7-7 7 7"/>',
|
|
55
|
+
fixed: '<path d="M20 6 9 17l-5-5"/>',
|
|
56
|
+
};
|
|
57
|
+
const fmtDate = (d: string) => new Date(`${d}T00:00:00Z`).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric', timeZone: 'UTC' });
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
<section class={`cls cls--${variant} cls--${size} ${className}`} aria-labelledby={id}>
|
|
61
|
+
<div class="cls__inner">
|
|
62
|
+
<header class="cls__head">
|
|
63
|
+
<div>
|
|
64
|
+
<h2 id={id} class="cls__title">{title}</h2>
|
|
65
|
+
<p class="cls__subtitle">{subtitle}</p>
|
|
66
|
+
</div>
|
|
67
|
+
{subscribe && (
|
|
68
|
+
<a class="cls__subscribe" href={subscribe.href}>
|
|
69
|
+
<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="M4 11a9 9 0 0 1 9 9M4 4a16 16 0 0 1 16 16" /><circle cx="5" cy="19" r="1" fill="currentColor" /></svg>
|
|
70
|
+
{subscribe.label}
|
|
71
|
+
</a>
|
|
72
|
+
)}
|
|
73
|
+
</header>
|
|
74
|
+
<ol class="cls__list">
|
|
75
|
+
{releases.map((r) => (
|
|
76
|
+
<li class="cls__release">
|
|
77
|
+
<div class="cls__when">
|
|
78
|
+
<span class="cls__version">{r.version}</span>
|
|
79
|
+
<time datetime={r.date}>{fmtDate(r.date)}</time>
|
|
80
|
+
</div>
|
|
81
|
+
<article class="cls__body">
|
|
82
|
+
<h3 class="cls__release-title">{r.title}</h3>
|
|
83
|
+
<ul class="cls__changes">
|
|
84
|
+
{r.changes.map((c) => (
|
|
85
|
+
<li>
|
|
86
|
+
<span class={`cls__type cls__type--${c.type}`}>
|
|
87
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" set:html={typeIcon[c.type]}></svg>
|
|
88
|
+
{typeLabel[c.type]}
|
|
89
|
+
</span>
|
|
90
|
+
<span>{c.text}</span>
|
|
91
|
+
</li>
|
|
92
|
+
))}
|
|
93
|
+
</ul>
|
|
94
|
+
</article>
|
|
95
|
+
</li>
|
|
96
|
+
))}
|
|
97
|
+
</ol>
|
|
98
|
+
</div>
|
|
99
|
+
</section>
|
|
100
|
+
|
|
101
|
+
<style>
|
|
102
|
+
.cls {
|
|
103
|
+
--cls-py: 5.5rem;
|
|
104
|
+
padding: var(--cls-py) var(--sp-6, 1.5rem);
|
|
105
|
+
background: var(--c-bg, #050505);
|
|
106
|
+
}
|
|
107
|
+
.cls--sm { --cls-py: 3.5rem; }
|
|
108
|
+
.cls--lg { --cls-py: 7.5rem; }
|
|
109
|
+
.cls__inner { max-width: 920px; margin: 0 auto; }
|
|
110
|
+
.cls__head { display: flex; justify-content: space-between; align-items: flex-end; gap: 1rem; flex-wrap: wrap; margin-bottom: 3rem; }
|
|
111
|
+
.cls__title { margin: 0 0 0.5rem; font-size: clamp(1.9rem, 4vw, 2.75rem); font-weight: 800; letter-spacing: -0.03em; color: var(--c-text-1, #fff); }
|
|
112
|
+
.cls__subtitle { margin: 0; font-size: 1.0625rem; color: var(--c-text-2, #a3a3a3); }
|
|
113
|
+
.cls__subscribe { display: inline-flex; align-items: center; gap: 0.45rem; min-height: 44px; padding: 0 1rem; border-radius: var(--r-md, 12px); border: 1px solid rgba(255, 255, 255, 0.14); color: var(--c-text-1, #fff); font-size: 0.875rem; font-weight: 600; text-decoration: none; }
|
|
114
|
+
.cls__subscribe svg { color: #f59e0b; }
|
|
115
|
+
.cls__subscribe:hover { border-color: rgba(255, 255, 255, 0.3); color: var(--c-text-1, #fff); }
|
|
116
|
+
.cls__subscribe:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
|
|
117
|
+
.cls__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2.5rem; }
|
|
118
|
+
.cls__release { display: grid; grid-template-columns: 180px 1fr; gap: 2rem; position: relative; }
|
|
119
|
+
.cls--timeline .cls__release::before { content: ''; position: absolute; left: 196px; top: 10px; bottom: -2.5rem; width: 1px; background: rgba(255, 255, 255, 0.08); }
|
|
120
|
+
.cls--timeline .cls__release:last-child::before { bottom: 0; }
|
|
121
|
+
.cls--timeline .cls__body { position: relative; padding-left: 2rem; }
|
|
122
|
+
.cls--timeline .cls__body::before { content: ''; position: absolute; left: -21px; top: 7px; width: 9px; height: 9px; border-radius: 50%; background: var(--c-primary-light, #d4a017); box-shadow: 0 0 0 4px var(--c-bg, #050505); }
|
|
123
|
+
.cls--cards .cls__body { padding: 1.5rem; border-radius: var(--r-lg, 18px); background: rgba(255, 255, 255, 0.025); border: 1px solid var(--c-border, rgba(255, 255, 255, 0.07)); }
|
|
124
|
+
.cls--compact .cls__list { gap: 1.5rem; }
|
|
125
|
+
.cls--compact .cls__release { grid-template-columns: 1fr; gap: 0.5rem; }
|
|
126
|
+
.cls--compact .cls__when { flex-direction: row; align-items: center; }
|
|
127
|
+
.cls__when { display: flex; flex-direction: column; gap: 0.4rem; align-items: flex-start; }
|
|
128
|
+
.cls__version { padding: 0.2rem 0.6rem; border-radius: 6px; background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.1); font-family: var(--font-mono, monospace); font-size: 0.8125rem; color: var(--c-text-1, #fff); }
|
|
129
|
+
.cls__when time { font-size: 0.8125rem; color: var(--c-text-2, #a3a3a3); }
|
|
130
|
+
.cls__release-title { margin: 0 0 1rem; font-size: 1.25rem; font-weight: 700; color: var(--c-text-1, #fff); letter-spacing: -0.015em; }
|
|
131
|
+
.cls__changes { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.75rem; }
|
|
132
|
+
.cls__changes li { display: flex; align-items: flex-start; gap: 0.75rem; font-size: 0.9375rem; line-height: 1.55; color: var(--c-text-2, #c4c4c4); }
|
|
133
|
+
.cls__type { display: inline-flex; align-items: center; gap: 0.3rem; flex-shrink: 0; min-width: 5.5rem; padding: 0.15rem 0.5rem; border-radius: 999px; font-size: 0.75rem; font-weight: 650; }
|
|
134
|
+
.cls__type--new { background: rgba(57, 135, 229, 0.14); color: #a9cdf7; }
|
|
135
|
+
.cls__type--improved { background: rgba(144, 133, 233, 0.16); color: #c9c3f5; }
|
|
136
|
+
.cls__type--fixed { background: rgba(52, 195, 143, 0.14); color: #7ee2b8; }
|
|
137
|
+
@media (max-width: 680px) {
|
|
138
|
+
.cls__release { grid-template-columns: 1fr; gap: 0.75rem; }
|
|
139
|
+
.cls--timeline .cls__release::before { display: none; }
|
|
140
|
+
.cls--timeline .cls__body { padding-left: 0; }
|
|
141
|
+
.cls--timeline .cls__body::before { display: none; }
|
|
142
|
+
.cls__when { flex-direction: row; align-items: center; }
|
|
143
|
+
}
|
|
144
|
+
</style>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* ContactSplitMap — Contact section with office cards and contact channels on
|
|
4
|
+
* one side and a stylised SVG dot-map with location pins on the other.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} title - Section heading.
|
|
7
|
+
* @param {string} subtitle - Section description.
|
|
8
|
+
* @param {Array<{city: string, address: string, x: number, y: number}>} offices - Offices with map coords (0–100).
|
|
9
|
+
* @param {Array<{label: string, value: string, href: string, icon: 'mail'|'phone'|'chat'}>} channels - Contact channels.
|
|
10
|
+
* @param {string} variant - Map style (dots|grid|plain).
|
|
11
|
+
* @param {string} size - Vertical rhythm (sm|md|lg).
|
|
12
|
+
* @param {string} className - Extra CSS class.
|
|
13
|
+
*/
|
|
14
|
+
interface Office { city: string; address: string; x: number; y: number }
|
|
15
|
+
interface Channel { label: string; value: string; href: string; icon: 'mail' | 'phone' | 'chat' }
|
|
16
|
+
interface Props {
|
|
17
|
+
title?: string;
|
|
18
|
+
subtitle?: string;
|
|
19
|
+
offices?: Office[];
|
|
20
|
+
channels?: Channel[];
|
|
21
|
+
variant?: 'dots' | 'grid' | 'plain';
|
|
22
|
+
size?: 'sm' | 'md' | 'lg';
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
title = 'Talk to a human',
|
|
28
|
+
subtitle = 'We’re distributed across three time zones, so someone is usually awake.',
|
|
29
|
+
offices = [
|
|
30
|
+
{ city: 'San Francisco', address: '548 Market St, CA 94104', x: 17, y: 40 },
|
|
31
|
+
{ city: 'Barcelona', address: 'Carrer de Pallars 99, 08018', x: 49, y: 36 },
|
|
32
|
+
{ city: 'Singapore', address: '1 Raffles Place, 048616', x: 77, y: 58 },
|
|
33
|
+
],
|
|
34
|
+
channels = [
|
|
35
|
+
{ label: 'Email', value: 'hello@example.com', href: 'mailto:hello@example.com', icon: 'mail' },
|
|
36
|
+
{ label: 'Phone', value: '+1 (555) 010-2030', href: 'tel:+15550102030', icon: 'phone' },
|
|
37
|
+
{ label: 'Live chat', value: 'Mon–Fri, 9am–6pm', href: '#', icon: 'chat' },
|
|
38
|
+
],
|
|
39
|
+
variant = 'dots',
|
|
40
|
+
size = 'md',
|
|
41
|
+
className = '',
|
|
42
|
+
} = Astro.props;
|
|
43
|
+
|
|
44
|
+
const id = `csm-${Math.random().toString(36).slice(2, 8)}`;
|
|
45
|
+
const icon = {
|
|
46
|
+
mail: '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 6L2 7"/>',
|
|
47
|
+
phone: '<path d="M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2.1 4.2 2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1 1 .4 1.9.7 2.8a2 2 0 0 1-.5 2.1L8 9.9a16 16 0 0 0 6 6l1.3-1.3a2 2 0 0 1 2.1-.4c.9.3 1.8.6 2.8.7a2 2 0 0 1 1.7 2z"/>',
|
|
48
|
+
chat: '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Very rough continent silhouettes as a coarse dot grid (deterministic).
|
|
52
|
+
const land = (x: number, y: number) =>
|
|
53
|
+
(x > 8 && x < 30 && y > 22 && y < 52) ||
|
|
54
|
+
(x > 22 && x < 34 && y > 52 && y < 88) ||
|
|
55
|
+
(x > 44 && x < 58 && y > 22 && y < 46) ||
|
|
56
|
+
(x > 46 && x < 60 && y > 46 && y < 80) ||
|
|
57
|
+
(x > 58 && x < 88 && y > 20 && y < 56) ||
|
|
58
|
+
(x > 78 && x < 92 && y > 68 && y < 84);
|
|
59
|
+
const dots: Array<[number, number]> = [];
|
|
60
|
+
for (let y = 6; y < 94; y += 3) for (let x = 2; x < 98; x += 2) if (land(x, y)) dots.push([x, y]);
|
|
61
|
+
|
|
62
|
+
// The map box is 16:11, so the SVG uses the same aspect ratio and scales 0–100 coords onto it.
|
|
63
|
+
const VW = 160;
|
|
64
|
+
const VH = 110;
|
|
65
|
+
const SX = VW / 100;
|
|
66
|
+
const SY = VH / 100;
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
<section class={`csm csm--${variant} csm--${size} ${className}`} aria-labelledby={id}>
|
|
70
|
+
<div class="csm__inner">
|
|
71
|
+
<div class="csm__info">
|
|
72
|
+
<h2 id={id} class="csm__title">{title}</h2>
|
|
73
|
+
<p class="csm__subtitle">{subtitle}</p>
|
|
74
|
+
<ul class="csm__channels">
|
|
75
|
+
{channels.map((c) => (
|
|
76
|
+
<li>
|
|
77
|
+
<a class="csm__channel" href={c.href}>
|
|
78
|
+
<span class="csm__icon" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" set:html={icon[c.icon]}></svg></span>
|
|
79
|
+
<span><span class="csm__label">{c.label}</span><span class="csm__value">{c.value}</span></span>
|
|
80
|
+
</a>
|
|
81
|
+
</li>
|
|
82
|
+
))}
|
|
83
|
+
</ul>
|
|
84
|
+
<ul class="csm__offices">
|
|
85
|
+
{offices.map((o) => (
|
|
86
|
+
<li><address><strong>{o.city}</strong>{o.address}</address></li>
|
|
87
|
+
))}
|
|
88
|
+
</ul>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="csm__map" role="img" aria-label={`Map showing offices in ${offices.map((o) => o.city).join(', ')}`}>
|
|
91
|
+
<svg viewBox={`0 0 ${VW} ${VH}`} aria-hidden="true">
|
|
92
|
+
{variant !== 'plain' && dots.map(([x, y]) => (
|
|
93
|
+
variant === 'grid'
|
|
94
|
+
? <rect x={x * SX - 0.8} y={y * SY - 0.8} width="1.6" height="1.6" fill="rgba(255,255,255,0.14)" />
|
|
95
|
+
: <circle cx={x * SX} cy={y * SY} r="0.75" fill="rgba(255,255,255,0.18)" />
|
|
96
|
+
))}
|
|
97
|
+
{offices.map((o) => (
|
|
98
|
+
<g>
|
|
99
|
+
<circle class="csm__ping" cx={o.x * SX} cy={o.y * SY} r="3" fill="none" stroke="var(--c-primary-light, #d4a017)" stroke-width="0.4" />
|
|
100
|
+
<circle cx={o.x * SX} cy={o.y * SY} r="1.6" fill="var(--c-primary-light, #d4a017)" />
|
|
101
|
+
</g>
|
|
102
|
+
))}
|
|
103
|
+
</svg>
|
|
104
|
+
{offices.map((o) => (
|
|
105
|
+
<span class="csm__pin" style={`left:${o.x}%; top:${o.y}%`} aria-hidden="true">{o.city}</span>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</section>
|
|
110
|
+
|
|
111
|
+
<style>
|
|
112
|
+
.csm {
|
|
113
|
+
--csm-py: 5.5rem;
|
|
114
|
+
padding: var(--csm-py) var(--sp-6, 1.5rem);
|
|
115
|
+
background: var(--c-bg, #050505);
|
|
116
|
+
}
|
|
117
|
+
.csm--sm { --csm-py: 3.5rem; }
|
|
118
|
+
.csm--lg { --csm-py: 7.5rem; }
|
|
119
|
+
.csm__inner { max-width: 1180px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1.3fr; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
|
|
120
|
+
.csm__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); }
|
|
121
|
+
.csm__subtitle { margin: 0 0 2rem; font-size: 1.0625rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
|
|
122
|
+
.csm__channels { list-style: none; margin: 0 0 2rem; padding: 0; display: grid; gap: 0.6rem; }
|
|
123
|
+
.csm__channel { display: flex; align-items: center; gap: 0.9rem; min-height: 60px; padding: 0.6rem 0.9rem; border-radius: var(--r-md, 14px); border: 1px solid var(--c-border, rgba(255, 255, 255, 0.07)); background: rgba(255, 255, 255, 0.02); color: inherit; text-decoration: none; transition: border-color 0.2s ease, background 0.2s ease; }
|
|
124
|
+
.csm__channel:hover { border-color: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.04); }
|
|
125
|
+
.csm__channel:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
|
|
126
|
+
.csm__icon { display: grid; place-items: center; width: 40px; height: 40px; border-radius: 10px; background: var(--c-primary-glow, rgba(184, 134, 11, 0.14)); color: var(--c-primary-light, #d4a017); flex-shrink: 0; }
|
|
127
|
+
.csm__label { display: block; font-size: 0.75rem; color: var(--c-text-3, #8a8a8a); }
|
|
128
|
+
.csm__value { display: block; font-size: 0.9375rem; font-weight: 600; color: var(--c-text-1, #fff); }
|
|
129
|
+
.csm__offices { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; }
|
|
130
|
+
.csm__offices address { font-style: normal; font-size: 0.8125rem; line-height: 1.5; color: var(--c-text-2, #a3a3a3); }
|
|
131
|
+
.csm__offices strong { display: block; font-size: 0.9375rem; color: var(--c-text-1, #fff); }
|
|
132
|
+
.csm__map { position: relative; aspect-ratio: 16 / 11; border-radius: var(--r-xl, 24px); border: 1px solid var(--c-border, rgba(255, 255, 255, 0.07)); background: radial-gradient(circle at 50% 40%, rgba(255, 255, 255, 0.04), transparent 70%), var(--c-bg-1, #0a0a0a); overflow: hidden; }
|
|
133
|
+
.csm__map svg { position: absolute; inset: 0; width: 100%; height: 100%; }
|
|
134
|
+
.csm__ping { transform-box: fill-box; transform-origin: center; animation: csm-ping 2.4s ease-out infinite; }
|
|
135
|
+
@keyframes csm-ping { from { transform: scale(0.6); opacity: 1; } to { transform: scale(2.4); opacity: 0; } }
|
|
136
|
+
.csm__pin { position: absolute; transform: translate(-50%, calc(-100% - 12px)); padding: 0.2rem 0.55rem; border-radius: 6px; background: rgba(10, 10, 12, 0.85); border: 1px solid rgba(255, 255, 255, 0.12); font-size: 0.6875rem; font-weight: 600; color: var(--c-text-1, #fff); white-space: nowrap; }
|
|
137
|
+
@media (max-width: 860px) { .csm__inner { grid-template-columns: 1fr; } }
|
|
138
|
+
@media (prefers-reduced-motion: reduce) { .csm__ping { animation: none; } }
|
|
139
|
+
</style>
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* CtaNewsletterSection — Newsletter signup band with benefits list, accessible
|
|
4
|
+
* email form, inline validation and privacy note.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} title - Heading.
|
|
7
|
+
* @param {string} subtitle - Supporting copy.
|
|
8
|
+
* @param {string[]} perks - Short benefit bullets.
|
|
9
|
+
* @param {string} action - Form action URL.
|
|
10
|
+
* @param {string} buttonLabel - Submit label.
|
|
11
|
+
* @param {string} privacy - Privacy note under the form.
|
|
12
|
+
* @param {string} variant - Band style (gradient|glass|bordered).
|
|
13
|
+
* @param {string} size - Vertical rhythm (sm|md|lg).
|
|
14
|
+
* @param {string} className - Extra CSS class.
|
|
15
|
+
*/
|
|
16
|
+
interface Props {
|
|
17
|
+
title?: string;
|
|
18
|
+
subtitle?: string;
|
|
19
|
+
perks?: string[];
|
|
20
|
+
action?: string;
|
|
21
|
+
buttonLabel?: string;
|
|
22
|
+
privacy?: string;
|
|
23
|
+
variant?: 'gradient' | 'glass' | 'bordered';
|
|
24
|
+
size?: 'sm' | 'md' | 'lg';
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
title = 'Get new components every week',
|
|
30
|
+
subtitle = 'One short email with fresh sections, charts and Astro tips. Join 12,000+ developers.',
|
|
31
|
+
perks = ['Weekly, never spam', 'Early access to new blocks', 'Unsubscribe in one click'],
|
|
32
|
+
action = '#',
|
|
33
|
+
buttonLabel = 'Subscribe',
|
|
34
|
+
privacy = 'We respect your privacy. Read our privacy policy.',
|
|
35
|
+
variant = 'gradient',
|
|
36
|
+
size = 'md',
|
|
37
|
+
className = '',
|
|
38
|
+
} = Astro.props;
|
|
39
|
+
|
|
40
|
+
const id = `cns-${Math.random().toString(36).slice(2, 8)}`;
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
<section class={`cns cns--${variant} cns--${size} ${className}`} aria-labelledby={id}>
|
|
44
|
+
<div class="cns__box">
|
|
45
|
+
<div class="cns__copy">
|
|
46
|
+
<span class="cns__icon" aria-hidden="true">
|
|
47
|
+
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2" /><path d="m22 7-10 6L2 7" /></svg>
|
|
48
|
+
</span>
|
|
49
|
+
<h2 id={id} class="cns__title">{title}</h2>
|
|
50
|
+
<p class="cns__subtitle">{subtitle}</p>
|
|
51
|
+
<ul class="cns__perks">
|
|
52
|
+
{perks.map((p) => (
|
|
53
|
+
<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>
|
|
54
|
+
))}
|
|
55
|
+
</ul>
|
|
56
|
+
</div>
|
|
57
|
+
<form class="cns__form" action={action} method="post" novalidate>
|
|
58
|
+
<label class="cns__label" for={`${id}-email`}>Email address</label>
|
|
59
|
+
<div class="cns__field">
|
|
60
|
+
<input class="cns__input" id={`${id}-email`} name="email" type="email" required autocomplete="email" placeholder="you@company.com" aria-describedby={`${id}-msg ${id}-privacy`} />
|
|
61
|
+
<button class="cns__btn" type="submit">{buttonLabel}</button>
|
|
62
|
+
</div>
|
|
63
|
+
<p id={`${id}-msg`} class="cns__msg" role="status" aria-live="polite"></p>
|
|
64
|
+
<p id={`${id}-privacy`} class="cns__privacy">{privacy}</p>
|
|
65
|
+
</form>
|
|
66
|
+
</div>
|
|
67
|
+
</section>
|
|
68
|
+
|
|
69
|
+
<script>
|
|
70
|
+
document.querySelectorAll<HTMLFormElement>('.cns__form').forEach((form) => {
|
|
71
|
+
const input = form.querySelector<HTMLInputElement>('.cns__input');
|
|
72
|
+
const msg = form.querySelector<HTMLElement>('.cns__msg');
|
|
73
|
+
form.addEventListener('submit', (e) => {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
if (!input || !msg) return;
|
|
76
|
+
if (!input.value || !input.checkValidity()) {
|
|
77
|
+
input.setAttribute('aria-invalid', 'true');
|
|
78
|
+
msg.dataset.state = 'error';
|
|
79
|
+
msg.textContent = 'Please enter a valid email address.';
|
|
80
|
+
input.focus();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
input.removeAttribute('aria-invalid');
|
|
84
|
+
msg.dataset.state = 'ok';
|
|
85
|
+
msg.textContent = 'Thanks! Please confirm your subscription in your inbox.';
|
|
86
|
+
input.value = '';
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<style>
|
|
92
|
+
.cns {
|
|
93
|
+
--cns-py: 5rem;
|
|
94
|
+
padding: var(--cns-py) var(--sp-6, 1.5rem);
|
|
95
|
+
background: var(--c-bg, #050505);
|
|
96
|
+
}
|
|
97
|
+
.cns--sm { --cns-py: 3rem; }
|
|
98
|
+
.cns--lg { --cns-py: 7rem; }
|
|
99
|
+
.cns__box {
|
|
100
|
+
max-width: 1080px;
|
|
101
|
+
margin: 0 auto;
|
|
102
|
+
display: grid;
|
|
103
|
+
grid-template-columns: 1.2fr 1fr;
|
|
104
|
+
gap: 2.5rem;
|
|
105
|
+
align-items: center;
|
|
106
|
+
padding: clamp(2rem, 5vw, 3.5rem);
|
|
107
|
+
border-radius: var(--r-xl, 28px);
|
|
108
|
+
}
|
|
109
|
+
.cns--gradient .cns__box {
|
|
110
|
+
background: radial-gradient(circle at 100% 0%, rgba(144, 133, 233, 0.22), transparent 55%), radial-gradient(circle at 0% 100%, var(--c-primary-glow, rgba(184, 134, 11, 0.2)), transparent 55%), #0c0c10;
|
|
111
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
112
|
+
}
|
|
113
|
+
.cns--glass .cns__box { background: rgba(255, 255, 255, 0.035); border: 1px solid var(--c-border, rgba(255, 255, 255, 0.08)); backdrop-filter: blur(16px); }
|
|
114
|
+
.cns--bordered .cns__box { border: 1px solid rgba(255, 255, 255, 0.14); }
|
|
115
|
+
.cns__icon { display: grid; place-items: center; width: 48px; height: 48px; margin-bottom: 1.25rem; border-radius: 14px; background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.1); color: var(--c-text-1, #fff); }
|
|
116
|
+
.cns__title { margin: 0 0 0.75rem; font-size: clamp(1.6rem, 3.5vw, 2.25rem); font-weight: 800; letter-spacing: -0.03em; line-height: 1.15; color: var(--c-text-1, #fff); }
|
|
117
|
+
.cns__subtitle { margin: 0 0 1.25rem; font-size: 1rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); }
|
|
118
|
+
.cns__perks { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; }
|
|
119
|
+
.cns__perks li { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.875rem; color: var(--c-text-1, #fff); }
|
|
120
|
+
.cns__perks svg { color: var(--chart-pos, #34c38f); }
|
|
121
|
+
.cns__label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: 600; color: var(--c-text-1, #fff); }
|
|
122
|
+
.cns__field { display: flex; gap: 0.5rem; }
|
|
123
|
+
.cns__input {
|
|
124
|
+
flex: 1;
|
|
125
|
+
min-width: 0;
|
|
126
|
+
min-height: 50px;
|
|
127
|
+
padding: 0 1rem;
|
|
128
|
+
border-radius: var(--r-md, 12px);
|
|
129
|
+
border: 1px solid rgba(255, 255, 255, 0.14);
|
|
130
|
+
background: rgba(0, 0, 0, 0.35);
|
|
131
|
+
color: var(--c-text-1, #fff);
|
|
132
|
+
font: inherit;
|
|
133
|
+
font-size: 1rem;
|
|
134
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
135
|
+
}
|
|
136
|
+
.cns__input::placeholder { color: var(--c-text-3, #737373); }
|
|
137
|
+
.cns__input:focus { outline: none; border-color: var(--c-primary-light, #d4a017); box-shadow: 0 0 0 3px var(--c-primary-glow, rgba(184, 134, 11, 0.25)); }
|
|
138
|
+
.cns__input[aria-invalid='true'] { border-color: var(--chart-neg, #f06a6a); }
|
|
139
|
+
.cns__btn {
|
|
140
|
+
min-height: 50px;
|
|
141
|
+
padding: 0 1.4rem;
|
|
142
|
+
border: 0;
|
|
143
|
+
border-radius: var(--r-md, 12px);
|
|
144
|
+
background: var(--c-text-1, #fff);
|
|
145
|
+
color: #0a0a0a;
|
|
146
|
+
font: inherit;
|
|
147
|
+
font-weight: 650;
|
|
148
|
+
cursor: pointer;
|
|
149
|
+
transition: background 0.2s ease;
|
|
150
|
+
}
|
|
151
|
+
.cns__btn:hover { background: var(--c-accent, #fef3c7); }
|
|
152
|
+
.cns__btn:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
|
|
153
|
+
.cns__msg { min-height: 1.4rem; margin: 0.6rem 0 0; font-size: 0.875rem; }
|
|
154
|
+
.cns__msg[data-state='error'] { color: #ff9b9b; }
|
|
155
|
+
.cns__msg[data-state='ok'] { color: #7ee2b8; }
|
|
156
|
+
.cns__privacy { margin: 0.25rem 0 0; font-size: 0.8125rem; color: var(--c-text-3, #8a8a8a); }
|
|
157
|
+
@media (max-width: 820px) {
|
|
158
|
+
.cns__box { grid-template-columns: 1fr; }
|
|
159
|
+
}
|
|
160
|
+
@media (max-width: 460px) {
|
|
161
|
+
.cns__field { flex-direction: column; }
|
|
162
|
+
}
|
|
163
|
+
</style>
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* CtaSplitImage — Conversion banner split into copy + CTA and a decorative
|
|
4
|
+
* visual panel (CSS gradient mesh with floating stat chips). Optional real image.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} title - Heading.
|
|
7
|
+
* @param {string} text - Supporting copy.
|
|
8
|
+
* @param {{label: string, href: string}} primary - Primary CTA.
|
|
9
|
+
* @param {{label: string, href: string}} secondary - Secondary CTA.
|
|
10
|
+
* @param {string} image - Optional image URL (replaces the CSS visual).
|
|
11
|
+
* @param {string} imageAlt - Alt text when an image is used.
|
|
12
|
+
* @param {Array<{value: string, label: string}>} chips - Floating stat chips on the visual.
|
|
13
|
+
* @param {string} variant - Panel style (mesh|solid|outline).
|
|
14
|
+
* @param {string} size - Vertical rhythm (sm|md|lg).
|
|
15
|
+
* @param {string} className - Extra CSS class.
|
|
16
|
+
*/
|
|
17
|
+
interface Props {
|
|
18
|
+
title?: string;
|
|
19
|
+
text?: string;
|
|
20
|
+
primary?: { label: string; href: string };
|
|
21
|
+
secondary?: { label: string; href: string };
|
|
22
|
+
image?: string;
|
|
23
|
+
imageAlt?: string;
|
|
24
|
+
chips?: Array<{ value: string; label: string }>;
|
|
25
|
+
variant?: 'mesh' | 'solid' | 'outline';
|
|
26
|
+
size?: 'sm' | 'md' | 'lg';
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const {
|
|
31
|
+
title = 'Ready to ship your best page yet?',
|
|
32
|
+
text = 'Join thousands of teams building fast, accessible Astro sites. Free forever for personal projects.',
|
|
33
|
+
primary = { label: 'Get started free', href: '#' },
|
|
34
|
+
secondary = { label: 'Talk to sales', href: '#' },
|
|
35
|
+
image,
|
|
36
|
+
imageAlt = '',
|
|
37
|
+
chips = [
|
|
38
|
+
{ value: '99.9%', label: 'uptime' },
|
|
39
|
+
{ value: '< 50ms', label: 'TTFB' },
|
|
40
|
+
{ value: '100', label: 'Lighthouse' },
|
|
41
|
+
],
|
|
42
|
+
variant = 'mesh',
|
|
43
|
+
size = 'md',
|
|
44
|
+
className = '',
|
|
45
|
+
} = Astro.props;
|
|
46
|
+
|
|
47
|
+
const id = `csi-${Math.random().toString(36).slice(2, 8)}`;
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
<section class={`csi csi--${variant} csi--${size} ${className}`} aria-labelledby={id}>
|
|
51
|
+
<div class="csi__box">
|
|
52
|
+
<div class="csi__copy">
|
|
53
|
+
<h2 id={id} class="csi__title">{title}</h2>
|
|
54
|
+
<p class="csi__text">{text}</p>
|
|
55
|
+
<div class="csi__actions">
|
|
56
|
+
<a class="csi__btn csi__btn--primary" href={primary.href}>{primary.label}</a>
|
|
57
|
+
{secondary && <a class="csi__btn csi__btn--ghost" href={secondary.href}>{secondary.label}</a>}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="csi__visual">
|
|
61
|
+
{image ? (
|
|
62
|
+
<img src={image} alt={imageAlt} loading="lazy" decoding="async" />
|
|
63
|
+
) : (
|
|
64
|
+
<div class="csi__mesh" aria-hidden="true">
|
|
65
|
+
{chips.map((c, i) => (
|
|
66
|
+
<span class="csi__chip" style={`--i:${i}`}><strong>{c.value}</strong>{c.label}</span>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</section>
|
|
73
|
+
|
|
74
|
+
<style>
|
|
75
|
+
.csi {
|
|
76
|
+
--csi-py: 5rem;
|
|
77
|
+
padding: var(--csi-py) var(--sp-6, 1.5rem);
|
|
78
|
+
background: var(--c-bg, #050505);
|
|
79
|
+
}
|
|
80
|
+
.csi--sm { --csi-py: 3rem; }
|
|
81
|
+
.csi--lg { --csi-py: 7rem; }
|
|
82
|
+
.csi__box {
|
|
83
|
+
max-width: 1180px;
|
|
84
|
+
margin: 0 auto;
|
|
85
|
+
display: grid;
|
|
86
|
+
grid-template-columns: 1fr 1fr;
|
|
87
|
+
border-radius: var(--r-xl, 28px);
|
|
88
|
+
overflow: hidden;
|
|
89
|
+
border: 1px solid var(--c-border, rgba(255, 255, 255, 0.08));
|
|
90
|
+
background: var(--c-bg-1, #0a0a0a);
|
|
91
|
+
}
|
|
92
|
+
.csi--outline .csi__box { background: transparent; border-color: rgba(255, 255, 255, 0.15); }
|
|
93
|
+
.csi__copy { padding: clamp(2rem, 6vw, 4rem); display: flex; flex-direction: column; justify-content: center; }
|
|
94
|
+
.csi__title { margin: 0 0 1rem; font-size: clamp(1.75rem, 4vw, 2.75rem); font-weight: 800; letter-spacing: -0.03em; line-height: 1.1; color: var(--c-text-1, #fff); text-wrap: balance; }
|
|
95
|
+
.csi__text { margin: 0 0 2rem; font-size: 1.0625rem; line-height: 1.6; color: var(--c-text-2, #a3a3a3); max-width: 460px; }
|
|
96
|
+
.csi__actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }
|
|
97
|
+
.csi__btn { display: inline-flex; align-items: center; min-height: 48px; padding: 0 1.5rem; border-radius: var(--r-md, 12px); font-weight: 650; text-decoration: none; transition: background 0.2s ease, border-color 0.2s ease; }
|
|
98
|
+
.csi__btn--primary { background: var(--c-primary-light, #d4a017); color: #0a0a0a; }
|
|
99
|
+
.csi__btn--primary:hover { background: var(--c-accent, #fef3c7); color: #0a0a0a; }
|
|
100
|
+
.csi__btn--ghost { border: 1px solid rgba(255, 255, 255, 0.15); color: var(--c-text-1, #fff); }
|
|
101
|
+
.csi__btn--ghost:hover { border-color: rgba(255, 255, 255, 0.32); color: var(--c-text-1, #fff); }
|
|
102
|
+
.csi__btn:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 3px; }
|
|
103
|
+
.csi__visual { position: relative; min-height: 320px; }
|
|
104
|
+
.csi__visual img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
105
|
+
.csi__mesh {
|
|
106
|
+
position: absolute;
|
|
107
|
+
inset: 0;
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
justify-content: center;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: 1rem;
|
|
113
|
+
padding: 2rem;
|
|
114
|
+
}
|
|
115
|
+
.csi--mesh .csi__mesh {
|
|
116
|
+
background:
|
|
117
|
+
radial-gradient(circle at 20% 20%, rgba(212, 160, 23, 0.55), transparent 45%),
|
|
118
|
+
radial-gradient(circle at 80% 30%, rgba(144, 133, 233, 0.55), transparent 45%),
|
|
119
|
+
radial-gradient(circle at 50% 90%, rgba(57, 135, 229, 0.5), transparent 50%),
|
|
120
|
+
#121218;
|
|
121
|
+
}
|
|
122
|
+
.csi--solid .csi__mesh { background: linear-gradient(135deg, #1a1408, #0c0c10); }
|
|
123
|
+
.csi--outline .csi__mesh { background: repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.03) 0 12px, transparent 12px 24px); }
|
|
124
|
+
.csi__chip {
|
|
125
|
+
display: inline-flex;
|
|
126
|
+
align-items: baseline;
|
|
127
|
+
gap: 0.5rem;
|
|
128
|
+
padding: 0.75rem 1.1rem;
|
|
129
|
+
border-radius: var(--r-md, 14px);
|
|
130
|
+
background: rgba(10, 10, 12, 0.6);
|
|
131
|
+
border: 1px solid rgba(255, 255, 255, 0.14);
|
|
132
|
+
backdrop-filter: blur(12px);
|
|
133
|
+
color: var(--c-text-2, #c4c4c4);
|
|
134
|
+
font-size: 0.875rem;
|
|
135
|
+
animation: csi-float 6s ease-in-out infinite;
|
|
136
|
+
animation-delay: calc(var(--i) * -2s);
|
|
137
|
+
}
|
|
138
|
+
.csi__chip:nth-child(2) { transform: translateX(2.5rem); }
|
|
139
|
+
.csi__chip:nth-child(3) { transform: translateX(-2rem); }
|
|
140
|
+
.csi__chip strong { font-size: 1.25rem; color: var(--c-text-1, #fff); font-variant-numeric: tabular-nums; }
|
|
141
|
+
@keyframes csi-float { 50% { translate: 0 -8px; } }
|
|
142
|
+
@media (max-width: 820px) {
|
|
143
|
+
.csi__box { grid-template-columns: 1fr; }
|
|
144
|
+
.csi__visual { min-height: 240px; }
|
|
145
|
+
}
|
|
146
|
+
@media (prefers-reduced-motion: reduce) { .csi__chip { animation: none; } }
|
|
147
|
+
</style>
|