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.
Files changed (115) hide show
  1. package/README.md +33 -14
  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,145 @@
1
+ ---
2
+ /**
3
+ * PhoneCountryInput — Accessible phone input with country dial code selector and dark glass styling.
4
+ *
5
+ * @param {string} id - HTML ID for the phone input.
6
+ * @param {string} label - Input label text. Default is 'Phone Number'.
7
+ * @param {string} name - Form field name. Default is 'phone'.
8
+ * @param {string} placeholder - Phone placeholder. Default is '612 345 678'.
9
+ * @param {boolean} required - HTML required field attribute.
10
+ */
11
+ interface Props {
12
+ id: string;
13
+ label?: string;
14
+ name?: string;
15
+ placeholder?: string;
16
+ required?: boolean;
17
+ }
18
+
19
+ const {
20
+ id = 'phone-number-input',
21
+ label = 'Phone Number',
22
+ name = 'phone',
23
+ placeholder = '612 345 678',
24
+ required = false,
25
+ } = Astro.props;
26
+
27
+ const countries = [
28
+ { code: '+34', flag: '🇪🇸', name: 'ES' },
29
+ { code: '+1', flag: '🇺🇸', name: 'US' },
30
+ { code: '+44', flag: '🇬🇧', name: 'UK' },
31
+ { code: '+49', flag: '🇩🇪', name: 'DE' },
32
+ { code: '+33', flag: '🇫🇷', name: 'FR' },
33
+ { code: '+52', flag: '🇲🇽', name: 'MX' },
34
+ { code: '+54', flag: '🇦🇷', name: 'AR' },
35
+ ];
36
+ ---
37
+
38
+ <div class="phone-group">
39
+ <label for={id} class="phone-group__label">{label}</label>
40
+
41
+ <div class="phone-group__wrap">
42
+ <div class="phone-group__prefix">
43
+ <select class="phone-group__select" aria-label="Country calling code" name={`${name}_country`}>
44
+ {countries.map((c) => (
45
+ <option value={c.code}>
46
+ {c.flag} {c.code}
47
+ </option>
48
+ ))}
49
+ </select>
50
+ <span class="phone-group__arrow" aria-hidden="true">▾</span>
51
+ </div>
52
+
53
+ <input
54
+ type="tel"
55
+ id={id}
56
+ name={name}
57
+ placeholder={placeholder}
58
+ required={required}
59
+ class="phone-group__input"
60
+ autocomplete="tel-national"
61
+ />
62
+ </div>
63
+ </div>
64
+
65
+ <style>
66
+ .phone-group {
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: var(--sp-2, 0.5rem);
70
+ width: 100%;
71
+ max-width: 380px;
72
+ font-family: var(--font-sans, system-ui, sans-serif);
73
+ }
74
+
75
+ .phone-group__label {
76
+ font-size: 0.85rem;
77
+ font-weight: 600;
78
+ color: var(--c-text-1, #ffffff);
79
+ }
80
+
81
+ .phone-group__wrap {
82
+ display: flex;
83
+ align-items: center;
84
+ background: rgba(255, 255, 255, 0.03);
85
+ backdrop-filter: blur(12px);
86
+ border: 1px solid rgba(255, 255, 255, 0.1);
87
+ border-radius: var(--r-md, 12px);
88
+ overflow: hidden;
89
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
90
+ }
91
+
92
+ .phone-group__wrap:focus-within {
93
+ border-color: rgba(184, 134, 11, 0.5);
94
+ box-shadow: 0 0 0 3px rgba(184, 134, 11, 0.15);
95
+ }
96
+
97
+ .phone-group__prefix {
98
+ position: relative;
99
+ display: flex;
100
+ align-items: center;
101
+ border-right: 1px solid rgba(255, 255, 255, 0.08);
102
+ background: rgba(255, 255, 255, 0.02);
103
+ }
104
+
105
+ .phone-group__select {
106
+ appearance: none;
107
+ -webkit-appearance: none;
108
+ background: transparent;
109
+ border: none;
110
+ color: var(--c-text-1, #ffffff);
111
+ font-size: 0.88rem;
112
+ font-weight: 600;
113
+ padding: 0.75rem 1.6rem 0.75rem 0.85rem;
114
+ cursor: pointer;
115
+ outline: none;
116
+ }
117
+
118
+ .phone-group__select option {
119
+ background: #111116;
120
+ color: #ffffff;
121
+ }
122
+
123
+ .phone-group__arrow {
124
+ position: absolute;
125
+ right: 0.5rem;
126
+ font-size: 0.7rem;
127
+ color: var(--c-text-3, #737373);
128
+ pointer-events: none;
129
+ }
130
+
131
+ .phone-group__input {
132
+ flex: 1;
133
+ background: transparent;
134
+ border: none;
135
+ padding: 0.75rem 1rem;
136
+ color: var(--c-text-1, #ffffff);
137
+ font-size: 0.92rem;
138
+ font-family: inherit;
139
+ outline: none;
140
+ }
141
+
142
+ .phone-group__input::placeholder {
143
+ color: rgba(255, 255, 255, 0.3);
144
+ }
145
+ </style>
@@ -0,0 +1,249 @@
1
+ ---
2
+ /**
3
+ * TagsAutocompleteInput — Tag entry field with pill badges, keyboard deletion, and suggestion dropdown.
4
+ *
5
+ * @param {string} id - HTML ID for the tag input.
6
+ * @param {string} label - Input label. Default is 'Tags'.
7
+ * @param {string[]} initialTags - Pre-populated list of tags. Default is ['Astro', 'TypeScript'].
8
+ * @param {string[]} suggestions - Autocomplete candidate options.
9
+ * @param {string} placeholder - Input placeholder. Default is 'Add tag & press Enter...'.
10
+ */
11
+ interface Props {
12
+ id: string;
13
+ label?: string;
14
+ initialTags?: string[];
15
+ suggestions?: string[];
16
+ placeholder?: string;
17
+ }
18
+
19
+ const {
20
+ id = 'tags-autocomplete',
21
+ label = 'Tags',
22
+ initialTags = ['Astro', 'TypeScript'],
23
+ suggestions = ['React', 'Tailwind', 'Three.js', 'SSR', 'Vite', 'GraphQL'],
24
+ placeholder = 'Add tag & press Enter...',
25
+ } = Astro.props;
26
+ ---
27
+
28
+ <div class="tags-field" data-tags-root>
29
+ <label for={id} class="tags-field__label">{label}</label>
30
+
31
+ <div class="tags-field__box" data-tags-box>
32
+ <div class="tags-field__list" data-tags-list>
33
+ {initialTags.map((tag) => (
34
+ <span class="tag-pill" data-tag={tag}>
35
+ <span class="tag-pill__name">{tag}</span>
36
+ <button type="button" class="tag-pill__remove" aria-label={`Remove tag ${tag}`} data-tag-remove>×</button>
37
+ </span>
38
+ ))}
39
+ </div>
40
+
41
+ <input
42
+ type="text"
43
+ id={id}
44
+ placeholder={placeholder}
45
+ class="tags-field__input"
46
+ data-tags-input
47
+ autocomplete="off"
48
+ />
49
+ </div>
50
+
51
+ <div class="tags-field__suggestions" data-tags-suggestions hidden>
52
+ {suggestions.map((sug) => (
53
+ <button type="button" class="sug-item" data-sug-val={sug}>
54
+ + {sug}
55
+ </button>
56
+ ))}
57
+ </div>
58
+ </div>
59
+
60
+ <style>
61
+ .tags-field {
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: var(--sp-2, 0.5rem);
65
+ width: 100%;
66
+ max-width: 440px;
67
+ font-family: var(--font-sans, system-ui, sans-serif);
68
+ position: relative;
69
+ }
70
+
71
+ .tags-field__label {
72
+ font-size: 0.85rem;
73
+ font-weight: 600;
74
+ color: var(--c-text-1, #ffffff);
75
+ }
76
+
77
+ .tags-field__box {
78
+ display: flex;
79
+ flex-wrap: wrap;
80
+ align-items: center;
81
+ gap: 6px;
82
+ padding: 0.5rem 0.75rem;
83
+ background: rgba(255, 255, 255, 0.03);
84
+ backdrop-filter: blur(12px);
85
+ border: 1px solid rgba(255, 255, 255, 0.1);
86
+ border-radius: var(--r-md, 12px);
87
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
88
+ min-height: 44px;
89
+ }
90
+
91
+ .tags-field__box:focus-within {
92
+ border-color: rgba(184, 134, 11, 0.5);
93
+ box-shadow: 0 0 0 3px rgba(184, 134, 11, 0.15);
94
+ }
95
+
96
+ .tags-field__list {
97
+ display: inline-flex;
98
+ flex-wrap: wrap;
99
+ gap: 6px;
100
+ }
101
+
102
+ .tag-pill {
103
+ display: inline-flex;
104
+ align-items: center;
105
+ gap: 6px;
106
+ padding: 0.25rem 0.65rem;
107
+ background: rgba(184, 134, 11, 0.15);
108
+ border: 1px solid rgba(184, 134, 11, 0.35);
109
+ border-radius: var(--r-full, 9999px);
110
+ font-size: 0.75rem;
111
+ font-weight: 600;
112
+ color: var(--c-accent, #fef3c7);
113
+ user-select: none;
114
+ animation: pill-pop 0.2s ease-out;
115
+ }
116
+
117
+ @keyframes pill-pop {
118
+ from { transform: scale(0.8); opacity: 0; }
119
+ to { transform: scale(1); opacity: 1; }
120
+ }
121
+
122
+ .tag-pill__remove {
123
+ background: transparent;
124
+ border: none;
125
+ color: var(--c-accent, #fef3c7);
126
+ font-size: 1rem;
127
+ line-height: 1;
128
+ cursor: pointer;
129
+ padding: 0 2px;
130
+ opacity: 0.7;
131
+ transition: opacity 0.2s;
132
+ }
133
+
134
+ .tag-pill__remove:hover {
135
+ opacity: 1;
136
+ color: #ef4444;
137
+ }
138
+
139
+ .tags-field__input {
140
+ flex: 1;
141
+ min-width: 140px;
142
+ background: transparent;
143
+ border: none;
144
+ color: var(--c-text-1, #ffffff);
145
+ font-size: 0.88rem;
146
+ outline: none;
147
+ padding: 0.2rem 0;
148
+ }
149
+
150
+ .tags-field__input::placeholder {
151
+ color: rgba(255, 255, 255, 0.3);
152
+ }
153
+
154
+ .tags-field__suggestions {
155
+ display: flex;
156
+ flex-wrap: wrap;
157
+ gap: 6px;
158
+ margin-top: 4px;
159
+ padding: 0.4rem;
160
+ background: rgba(12, 12, 16, 0.9);
161
+ border: 1px solid rgba(255, 255, 255, 0.08);
162
+ border-radius: var(--r-sm, 8px);
163
+ }
164
+
165
+ .sug-item {
166
+ background: rgba(255, 255, 255, 0.04);
167
+ border: 1px solid rgba(255, 255, 255, 0.08);
168
+ border-radius: var(--r-full, 9999px);
169
+ color: var(--c-text-2, #a3a3a3);
170
+ font-size: 0.72rem;
171
+ font-weight: 600;
172
+ padding: 0.2rem 0.6rem;
173
+ cursor: pointer;
174
+ transition: all 0.2s ease;
175
+ }
176
+
177
+ .sug-item:hover {
178
+ background: rgba(184, 134, 11, 0.2);
179
+ border-color: rgba(184, 134, 11, 0.4);
180
+ color: var(--c-text-1, #ffffff);
181
+ }
182
+ </style>
183
+
184
+ <script>
185
+ function setupTagsFields() {
186
+ document.querySelectorAll('[data-tags-root]').forEach((root) => {
187
+ const input = root.querySelector('[data-tags-input]') as HTMLInputElement;
188
+ const list = root.querySelector('[data-tags-list]') as HTMLElement;
189
+ const suggestions = root.querySelector('[data-tags-suggestions]') as HTMLElement;
190
+ if (!input || !list) return;
191
+
192
+ function addTag(text: string) {
193
+ const clean = text.trim();
194
+ if (!clean) return;
195
+
196
+ // Check duplicate
197
+ const exists = Array.from(list.querySelectorAll('.tag-pill__name')).some(
198
+ (el) => el.textContent?.toLowerCase() === clean.toLowerCase()
199
+ );
200
+ if (exists) return;
201
+
202
+ const pill = document.createElement('span');
203
+ pill.className = 'tag-pill';
204
+ pill.setAttribute('data-tag', clean);
205
+ pill.innerHTML = `
206
+ <span class="tag-pill__name">${clean}</span>
207
+ <button type="button" class="tag-pill__remove" aria-label="Remove tag ${clean}" data-tag-remove>×</button>
208
+ `;
209
+ list.appendChild(pill);
210
+ input.value = '';
211
+ }
212
+
213
+ list.addEventListener('click', (e) => {
214
+ const target = e.target as HTMLElement;
215
+ if (target.matches('[data-tag-remove]')) {
216
+ const pill = target.closest('.tag-pill');
217
+ if (pill) pill.remove();
218
+ }
219
+ });
220
+
221
+ input.addEventListener('keydown', (e) => {
222
+ if (e.key === 'Enter' || e.key === ',') {
223
+ e.preventDefault();
224
+ addTag(input.value);
225
+ } else if (e.key === 'Backspace' && !input.value) {
226
+ const last = list.querySelector('.tag-pill:last-child');
227
+ if (last) last.remove();
228
+ }
229
+ });
230
+
231
+ input.addEventListener('focus', () => {
232
+ if (suggestions) suggestions.hidden = false;
233
+ });
234
+
235
+ if (suggestions) {
236
+ suggestions.addEventListener('click', (e) => {
237
+ const btn = (e.target as HTMLElement).closest('[data-sug-val]') as HTMLElement;
238
+ if (btn) {
239
+ const val = btn.getAttribute('data-sug-val');
240
+ if (val) addTag(val);
241
+ }
242
+ });
243
+ }
244
+ });
245
+ }
246
+
247
+ setupTagsFields();
248
+ document.addEventListener('astro:page-load', setupTagsFields);
249
+ </script>
@@ -21,3 +21,8 @@ export { default as SearchReveal } from './SearchReveal.astro';
21
21
  export { default as SegmentedControl } from './SegmentedControl.astro';
22
22
  export { default as SimpleFileInput } from './SimpleFileInput.astro';
23
23
  export { default as StepperInput } from './StepperInput.astro';
24
+ export { default as GlassInlineToggleGroup } from './GlassInlineToggleGroup.astro';
25
+ export { default as PasswordStrengthInput } from './PasswordStrengthInput.astro';
26
+ export { default as PhoneCountryInput } from './PhoneCountryInput.astro';
27
+ export { default as TagsAutocompleteInput } from './TagsAutocompleteInput.astro';
28
+
@@ -0,0 +1,69 @@
1
+ ---
2
+ /**
3
+ * DiagonalSectionDivider — Angled SVG/clip-path section divider creating fluid diagonal page transitions.
4
+ *
5
+ * @param {'top' | 'bottom'} position - Attachment position. Default is 'bottom'.
6
+ * @param {'left-to-right' | 'right-to-left'} direction - Slant angle direction. Default is 'left-to-right'.
7
+ * @param {string} height - Height of the diagonal incline in pixels. Default is '60px'.
8
+ * @param {string} fillColor - Fill color for the divider polygon. Default is '#050505'.
9
+ */
10
+ interface Props {
11
+ position?: 'top' | 'bottom';
12
+ direction?: 'left-to-right' | 'right-to-left';
13
+ height?: string;
14
+ fillColor?: string;
15
+ }
16
+
17
+ const {
18
+ position = 'bottom',
19
+ direction = 'left-to-right',
20
+ height = '60px',
21
+ fillColor = '#050505',
22
+ } = Astro.props;
23
+
24
+ const isLtr = direction === 'left-to-right';
25
+ const points = isLtr
26
+ ? position === 'bottom' ? '0,100 100,0 100,100' : '0,0 100,0 100,100'
27
+ : position === 'bottom' ? '0,0 0,100 100,100' : '0,0 100,0 0,100';
28
+ ---
29
+
30
+ <div
31
+ class:list={['diagonal-divider', `diagonal-divider--${position}`]}
32
+ style={`--div-height: ${height}; --div-color: ${fillColor};`}
33
+ aria-hidden="true"
34
+ >
35
+ <svg
36
+ viewBox="0 0 100 100"
37
+ preserveAspectRatio="none"
38
+ class="diagonal-divider__svg"
39
+ >
40
+ <polygon points={points} fill={fillColor} />
41
+ </svg>
42
+ </div>
43
+
44
+ <style>
45
+ .diagonal-divider {
46
+ position: relative;
47
+ width: 100%;
48
+ height: var(--div-height);
49
+ overflow: hidden;
50
+ line-height: 0;
51
+ pointer-events: none;
52
+ z-index: 2;
53
+ }
54
+
55
+ .diagonal-divider--bottom {
56
+ margin-bottom: calc(-1 * var(--div-height));
57
+ }
58
+
59
+ .diagonal-divider--top {
60
+ margin-top: calc(-1 * var(--div-height));
61
+ }
62
+
63
+ .diagonal-divider__svg {
64
+ position: relative;
65
+ display: block;
66
+ width: 100%;
67
+ height: 100%;
68
+ }
69
+ </style>
@@ -0,0 +1,125 @@
1
+ ---
2
+ /**
3
+ * HolyGrailGrid — Production-grade CSS grid layout container with sticky header, dual sidebars, fluid content, and footer.
4
+ *
5
+ * @param {string} leftWidth - Left sidebar width. Default is '240px'.
6
+ * @param {string} rightWidth - Right sidebar width. Default is '220px'.
7
+ * @param {string} gap - Spacing gap between grid regions. Default is '1.5rem'.
8
+ * @param {string} className - Optional CSS class.
9
+ */
10
+ interface Props {
11
+ leftWidth?: string;
12
+ rightWidth?: string;
13
+ gap?: string;
14
+ className?: string;
15
+ }
16
+
17
+ const {
18
+ leftWidth = '240px',
19
+ rightWidth = '220px',
20
+ gap = '1.5rem',
21
+ className = '',
22
+ } = Astro.props;
23
+ ---
24
+
25
+ <div
26
+ class:list={['holy-grail', className]}
27
+ style={`--hg-left: ${leftWidth}; --hg-right: ${rightWidth}; --hg-gap: ${gap};`}
28
+ >
29
+ <header class="holy-grail__header">
30
+ <slot name="header" />
31
+ </header>
32
+
33
+ <div class="holy-grail__middle">
34
+ <aside class="holy-grail__left">
35
+ <slot name="left" />
36
+ </aside>
37
+
38
+ <main class="holy-grail__main">
39
+ <slot />
40
+ </main>
41
+
42
+ <aside class="holy-grail__right">
43
+ <slot name="right" />
44
+ </aside>
45
+ </div>
46
+
47
+ <footer class="holy-grail__footer">
48
+ <slot name="footer" />
49
+ </footer>
50
+ </div>
51
+
52
+ <style>
53
+ .holy-grail {
54
+ display: flex;
55
+ flex-direction: column;
56
+ min-height: 100vh;
57
+ width: 100%;
58
+ color: var(--c-text-1, #ffffff);
59
+ font-family: var(--font-sans, system-ui, sans-serif);
60
+ }
61
+
62
+ .holy-grail__header {
63
+ width: 100%;
64
+ padding: var(--sp-4, 1rem) var(--sp-6, 1.5rem);
65
+ background: rgba(10, 10, 14, 0.8);
66
+ backdrop-filter: blur(16px);
67
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
68
+ }
69
+
70
+ .holy-grail__middle {
71
+ display: grid;
72
+ grid-template-columns: var(--hg-left) 1fr var(--hg-right);
73
+ gap: var(--hg-gap);
74
+ flex: 1;
75
+ max-width: 1440px;
76
+ margin: 0 auto;
77
+ width: 100%;
78
+ padding: var(--sp-6, 1.5rem);
79
+ }
80
+
81
+ .holy-grail__left,
82
+ .holy-grail__right {
83
+ padding: var(--sp-4, 1rem);
84
+ background: rgba(255, 255, 255, 0.02);
85
+ border: 1px solid rgba(255, 255, 255, 0.06);
86
+ border-radius: var(--r-lg, 16px);
87
+ height: fit-content;
88
+ position: sticky;
89
+ top: 2rem;
90
+ }
91
+
92
+ .holy-grail__main {
93
+ padding: var(--sp-6, 1.5rem);
94
+ background: rgba(255, 255, 255, 0.015);
95
+ border: 1px solid rgba(255, 255, 255, 0.06);
96
+ border-radius: var(--r-lg, 16px);
97
+ min-height: 400px;
98
+ }
99
+
100
+ .holy-grail__footer {
101
+ width: 100%;
102
+ padding: var(--sp-6, 1.5rem);
103
+ background: rgba(10, 10, 14, 0.9);
104
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
105
+ margin-top: auto;
106
+ }
107
+
108
+ @media (max-width: 1024px) {
109
+ .holy-grail__middle {
110
+ grid-template-columns: var(--hg-left) 1fr;
111
+ }
112
+ .holy-grail__right {
113
+ display: none;
114
+ }
115
+ }
116
+
117
+ @media (max-width: 768px) {
118
+ .holy-grail__middle {
119
+ grid-template-columns: 1fr;
120
+ }
121
+ .holy-grail__left {
122
+ position: static;
123
+ }
124
+ }
125
+ </style>
@@ -20,4 +20,6 @@ export { default as CenterFixedBox } from './CenterFixedBox.astro';
20
20
  export { default as GalleryGrid4 } from './GalleryGrid4.astro';
21
21
  export { default as StepLayoutVertical } from './StepLayoutVertical.astro';
22
22
  export { default as LandingSectionSplit } from './LandingSectionSplit.astro';
23
- export { default as FooterCenteredMinimal } from './FooterCenteredMinimal.astro';
23
+ export { default as FooterCenteredMinimal } from './FooterCenteredMinimal.astro';
24
+ export { default as HolyGrailGrid } from './HolyGrailGrid.astro';
25
+ export { default as DiagonalSectionDivider } from './DiagonalSectionDivider.astro';