astro-component-kit 0.4.1 → 0.5.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.
@@ -0,0 +1,777 @@
1
+ // src/data/registry/typography.ts
2
+ import type { ComponentDoc } from './types';
3
+
4
+ export const typography: ComponentDoc[] = [
5
+ {
6
+ slug: 'gradient-heading',
7
+ name: 'Gradient Heading',
8
+ description: 'A punchy hero heading with a customizable multi-color gradient text effect.',
9
+ category: 'typography',
10
+ tags: ['typography', 'heading', 'gradient'],
11
+ featured: true,
12
+ code: `---
13
+ interface Props { level?: 1 | 2 | 3 | 4 | 5 | 6; from?: string; to?: string; }
14
+ const { level = 1, from = '#6366f1', to = '#c084fc' } = Astro.props;
15
+ const Tag = \`h\${level}\` as any;
16
+ ---
17
+ <Tag class="grad-txt" style={\`--from: \${from}; --to: \${to}\`}><slot /></Tag>
18
+ <style>
19
+ .grad-txt { display: inline-block; font-weight: 900; background: linear-gradient(135deg, var(--from), var(--to)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; line-height: 1.2; margin: 0; }
20
+ </style>
21
+ `,
22
+ usage: `<GradientHeading level={2} from="#6366f1" to="#c084fc">Content</GradientHeading>`,
23
+ },
24
+ {
25
+ slug: 'cyberpunk-glitch-text',
26
+ name: 'Glitch Text',
27
+ description: 'High-intensity cyberpunk glitch effect for attention-grabbing titles.',
28
+ category: 'typography',
29
+ tags: ['typography', 'glitch', 'cyberpunk'],
30
+ code: `---
31
+ interface Props { text: string; }
32
+ const { text } = Astro.props;
33
+ ---
34
+ <div class="glitch-text" data-text={text}>{text}</div>
35
+ <style>
36
+ .glitch-text { position: relative; font-size: 3rem; font-weight: 900; color: #fff; text-transform: uppercase; }
37
+ .glitch-text::before, .glitch-text::after { content: attr(data-text); position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
38
+ .glitch-text::before { left: 2px; text-shadow: -2px 0 #ff003c; clip: rect(44px, 450px, 56px, 0); animation: glitch-anim 5s infinite linear alternate-reverse; }
39
+ .glitch-text::after { left: -2px; text-shadow: -2px 0 #00e0ff; clip: rect(12px, 450px, 80px, 0); animation: glitch-anim 2s infinite linear alternate-reverse; }
40
+ @keyframes glitch-anim {
41
+ 0% { clip: rect(10px, 9999px, 20px, 0); transform: skew(0.5deg); }
42
+ 20% { clip: rect(40px, 9999px, 50px, 0); }
43
+ 40% { clip: rect(80px, 9999px, 90px, 0); }
44
+ 60% { clip: rect(20px, 9999px, 30px, 0); }
45
+ 80% { clip: rect(60px, 9999px, 70px, 0); }
46
+ 100% { clip: rect(0, 9999px, 10px, 0); transform: skew(-0.5deg); }
47
+ }
48
+ @media (max-width: 768px) { .glitch-text { font-size: 2rem; } }
49
+ </style>
50
+ `,
51
+ usage: `<CyberpunkGlitchText text="Sample text">Content</CyberpunkGlitchText>`,
52
+ },
53
+ {
54
+ slug: 'glass-block-quote',
55
+ name: 'Glass Blockquote',
56
+ description: 'A stylish quote block with a blurred background and thick accent border.',
57
+ category: 'typography',
58
+ tags: ['typography', 'quote', 'glass'],
59
+ code: `---
60
+ ---
61
+ <blockquote class="glass-quote">
62
+ <div class="q-ico">“</div>
63
+ <p><slot /></p>
64
+ <footer>— Team Astro Components</footer>
65
+ </blockquote>
66
+ <style>
67
+ .glass-quote { position: relative; padding: 2.5rem; background: rgba(255,255,255,0.03); border-left: 4px solid #6366f1; border-radius: 0 20px 20px 0; backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.05); }
68
+ .q-ico { position: absolute; top: 0px; left: 1rem; font-size: 5rem; color: rgba(99,102,241,0.1); font-family: serif; line-height: 1; }
69
+ p { font-size: 1.2rem; color: #e2e8f0; font-style: italic; line-height: 1.6; margin: 0 0 1rem; position: relative; z-index: 1; }
70
+ footer { color: #64748b; font-size: 0.9rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; }
71
+ </style>
72
+ `,
73
+ usage: `<GlassBlockQuote>Content</GlassBlockQuote>`,
74
+ },
75
+ {
76
+ slug: 'typewriter-text',
77
+ name: 'Typewriter Effect',
78
+ description: 'Pure CSS typewriter animation for sequential text reveal.',
79
+ category: 'typography',
80
+ tags: ['typography', 'animation', 'typewriter'],
81
+ code: `---
82
+ interface Props { text: string; speed?: string; }
83
+ const { text, speed = '3s' } = Astro.props;
84
+ ---
85
+ <div class="tw-wrap">
86
+ <span class="tw-text" style={\`--len: \${text.length}; --speed: \${speed}\`}>
87
+ {text}
88
+ </span>
89
+ </div>
90
+ <style>
91
+ .tw-wrap { display: inline-block; }
92
+ .tw-text { display: block; overflow: hidden; white-space: nowrap; border-right: 3px solid #6366f1; color: #fff; font-family: monospace; font-size: 1.5rem; font-weight: 700; width: 0; animation: typing var(--speed) steps(var(--len)) forwards, blink 0.75s step-end infinite; }
93
+ @keyframes typing { from { width: 0 } to { width: 100% } }
94
+ @keyframes blink { from, to { border-color: transparent } 50% { border-color: #6366f1 } }
95
+ </style>
96
+ `,
97
+ usage: `<TypewriterText text="Sample text" speed="3s">Content</TypewriterText>`,
98
+ },
99
+ {
100
+ slug: 'drop-cap-paragraph',
101
+ name: 'Drop Cap Paragraph',
102
+ description: 'Editorial-style paragraph with a large, stylized first letter.',
103
+ category: 'typography',
104
+ tags: ['typography', 'editorial', 'paragraph'],
105
+ code: `---
106
+ ---
107
+ <p class="drop-p"><slot /></p>
108
+ <style>
109
+ .drop-p { color: #94a3b8; line-height: 1.8; font-size: 1.1rem; }
110
+ .drop-p::first-letter { float: left; font-size: 4rem; font-weight: 900; line-height: 0.8; margin-right: 0.5rem; margin-top: 0.5rem; color: #fff; background: linear-gradient(135deg, #6366f1, #c084fc); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
111
+ </style>
112
+ `,
113
+ usage: `<DropCapParagraph>Content</DropCapParagraph>`,
114
+ },
115
+ {
116
+ slug: 'neon-text-highlight',
117
+ name: 'Neon Highlight',
118
+ description: 'Custom text highlight with a glowing neon border effect.',
119
+ category: 'typography',
120
+ tags: ['typography', 'highlight', 'neon'],
121
+ code: `---
122
+ ---
123
+ <span class="neon-hi"><slot /></span>
124
+ <style>
125
+ .neon-hi { color: #fff; background: rgba(99,102,241,0.2); padding: 0.1em 0.3em; border-radius: 4px; box-shadow: 0 0 10px rgba(99,102,241,0.3); border: 1px solid rgba(99,102,241,0.4); }
126
+ </style>
127
+ `,
128
+ usage: `<NeonTextHighlight>Content</NeonTextHighlight>`,
129
+ },
130
+ {
131
+ slug: 'kbd-indicator',
132
+ name: 'KBD Key Cap',
133
+ description: 'Stylized keyboard shortcut indicator with a tactile UI look.',
134
+ category: 'typography',
135
+ tags: ['typography', 'utility', 'keyboard'],
136
+ code: `---
137
+ ---
138
+ <kbd class="tau-kbd"><slot /></kbd>
139
+ <style>
140
+ .tau-kbd { background: #1e293b; border: 1px solid #334155; border-bottom-width: 3px; border-radius: 6px; padding: 2px 8px; color: #f1f5f9; font-family: sans-serif; font-weight: 700; font-size: 0.75rem; display: inline-flex; align-items: center; justify-content: center; min-width: 24px; box-shadow: 0 2px 0 rgba(0,0,0,0.4); vertical-align: middle; }
141
+ </style>
142
+ `,
143
+ usage: `<KbdIndicator>Content</KbdIndicator>`,
144
+ },
145
+ {
146
+ slug: 'animated-underline-link',
147
+ name: 'Smart Underline Link',
148
+ description: 'A link with a smooth underline animation that expands from the center.',
149
+ category: 'typography',
150
+ tags: ['typography', 'link', 'navigation'],
151
+ code: `---
152
+ interface Props { href: string; }
153
+ const { href } = Astro.props;
154
+ ---
155
+ <a href={href} class="smart-link"><slot /></a>
156
+ <style>
157
+ .smart-link { color: #818cf8; text-decoration: none; position: relative; font-weight: 600; padding: 2px 0; }
158
+ .smart-link::after { content: ""; position: absolute; bottom: -2px; left: 50%; width: 0; height: 2px; background: #6366f1; transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
159
+ .smart-link:hover::after { width: 100%; left: 0; }
160
+ </style>
161
+ `,
162
+ usage: `<AnimatedUnderlineLink href="#">Content</AnimatedUnderlineLink>`,
163
+ },
164
+ {
165
+ slug: 'code-inline-glass',
166
+ name: 'Inline Code (Glass)',
167
+ description: 'Translucent inline code snippet for technical documentation.',
168
+ category: 'typography',
169
+ tags: ['typography', 'code', 'glass'],
170
+ code: `---
171
+ ---
172
+ <code class="glass-code"><slot /></code>
173
+ <style>
174
+ .glass-code { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08); padding: 0.2rem 0.4rem; border-radius: 6px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; color: #e2e8f0; font-size: 0.9em; }
175
+ </style>
176
+ `,
177
+ usage: `<CodeInlineGlass>Content</CodeInlineGlass>`,
178
+ },
179
+ {
180
+ slug: 'stroke-heading-outline',
181
+ name: 'Outlined Heading',
182
+ description: 'A bold empty heading with only the stroke/outline visible.',
183
+ category: 'typography',
184
+ tags: ['typography', 'heading', 'outline'],
185
+ code: `---
186
+ ---
187
+ <h2 class="stroke-h"><slot /></h2>
188
+ <style>
189
+ .stroke-h { font-size: 4rem; font-weight: 900; color: transparent; -webkit-text-stroke: 1px rgba(255,255,255,0.2); text-transform: uppercase; letter-spacing: 0.05em; transition: 0.3s; margin: 0; }
190
+ .stroke-h:hover { -webkit-text-stroke-color: #6366f1; }
191
+ @media (max-width: 768px) { .stroke-h { font-size: 2.5rem; } }
192
+ </style>
193
+ `,
194
+ usage: `<StrokeHeadingOutline>Content</StrokeHeadingOutline>`,
195
+ },
196
+ {
197
+ slug: 'floating-text-label',
198
+ name: 'Floating Label',
199
+ description: 'Small text label that floats above a title or section.',
200
+ category: 'typography',
201
+ tags: ['typography', 'label', 'layout'],
202
+ code: `---
203
+ ---
204
+ <div class="f-label-wrap">
205
+ <span class="f-label">PREMIUM</span>
206
+ <slot />
207
+ </div>
208
+ <style>
209
+ .f-label-wrap { position: relative; padding-top: 1.5rem; }
210
+ .f-label { position: absolute; top: 0; left: 0; font-size: 0.7rem; font-weight: 800; color: #6366f1; letter-spacing: 0.2em; text-transform: uppercase; }
211
+ </style>
212
+ `,
213
+ usage: `<FloatingTextLabel>Content</FloatingTextLabel>`,
214
+ },
215
+ {
216
+ slug: 'shadow-reveal-text',
217
+ name: 'Text Shadow Reveal',
218
+ description: 'Heading with a subtle double shadow that adds depth.',
219
+ category: 'typography',
220
+ tags: ['typography', 'heading', 'shadow'],
221
+ code: `---
222
+ ---
223
+ <h2 class="rev-text"><slot /></h2>
224
+ <style>
225
+ .rev-text { font-size: 3.5rem; font-weight: 900; color: #fff; text-shadow: 4px 4px 0px rgba(99,102,241,0.3), 8px 8px 0px rgba(0,0,0,0.5); margin: 0; line-height: 1.1; }
226
+ @media (max-width: 768px) { .rev-text { font-size: 2rem; } }
227
+ </style>
228
+ `,
229
+ usage: `<ShadowRevealText>Content</ShadowRevealText>`,
230
+ },
231
+ {
232
+ slug: 'letter-spacing-wide',
233
+ name: 'Cinematic Typography',
234
+ description: 'Text with extreme letter spacing for a luxury or cinematic look.',
235
+ category: 'typography',
236
+ tags: ['typography', 'luxury', 'headers'],
237
+ code: `---
238
+ ---
239
+ <div class="cinema-txt"><slot /></div>
240
+ <style>
241
+ .cinema-txt { color: #fff; font-size: 0.8rem; font-weight: 600; letter-spacing: 0.8em; text-transform: uppercase; text-align: center; line-height: 1.5; }
242
+ </style>
243
+ `,
244
+ usage: `<LetterSpacingWide>Content</LetterSpacingWide>`,
245
+ },
246
+ {
247
+ slug: 'monospace-stat-line',
248
+ name: 'Monospace Metrics',
249
+ description: 'Technical line for displaying raw metrics or logs.',
250
+ category: 'typography',
251
+ tags: ['typography', 'metrics', 'code'],
252
+ code: `---
253
+ ---
254
+ <div class="mono-stat"><slot /></div>
255
+ <style>
256
+ .mono-stat { background: #000; color: #00ff00; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; padding: 0.75rem 1.25rem; border-left: 3px solid #00ff00; font-size: 0.8rem; border-radius: 4px; border: 1px solid rgba(0, 255, 0, 0.1); }
257
+ </style>
258
+ `,
259
+ usage: `<MonospaceStatLine>Content</MonospaceStatLine>`,
260
+ },
261
+ {
262
+ slug: 'background-clip-mask',
263
+ name: 'Image Masked Text',
264
+ description: 'Text that uses an image as its background fill.',
265
+ category: 'typography',
266
+ tags: ['typography', 'mask', 'heading'],
267
+ code: `---
268
+ ---
269
+ <h1 class="mask-txt"><slot /></h1>
270
+ <style>
271
+ .mask-txt { font-size: 8rem; font-weight: 900; background: url('https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=1000&q=80'); background-size: cover; background-position: center; -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin: 0; line-height: 1; text-align: center; }
272
+ @media (max-width: 768px) { .mask-txt { font-size: 4rem; } }
273
+ </style>
274
+ `,
275
+ usage: `<BackgroundClipMask>Content</BackgroundClipMask>`,
276
+ },
277
+ {
278
+ slug: 'glass-text-reveal',
279
+ name: 'Glass Text Shimmer',
280
+ description: 'Text with a moving translucent shimmer effect.',
281
+ category: 'typography',
282
+ tags: ['typography', 'shimmer', 'glass'],
283
+ code: `---
284
+ ---
285
+ <h3 class="shimmer-txt"><slot /></h3>
286
+ <style>
287
+ .shimmer-txt { font-size: 2rem; color: rgba(255,255,255,0.2); background: linear-gradient(90deg, transparent, #fff, transparent); background-size: 200% 100%; -webkit-background-clip: text; animation: shimmer-anim 3s infinite linear; margin: 0; font-weight: 800; }
288
+ @keyframes shimmer-anim { 0% { background-position: -100% 0; } 100% { background-position: 100% 0; } }
289
+ </style>
290
+ `,
291
+ usage: `<GlassTextReveal>Content</GlassTextReveal>`,
292
+ },
293
+ {
294
+ slug: 'minimal-list-markers',
295
+ name: 'Custom List Markers',
296
+ description: 'Unordered list with clean, styled bullet points.',
297
+ category: 'typography',
298
+ tags: ['typography', 'list', 'ux'],
299
+ code: `---
300
+ ---
301
+ <ul class="custom-list">
302
+ <slot />
303
+ </ul>
304
+ <style>
305
+ .custom-list { list-style: none; padding: 0; }
306
+ .custom-list :global(li) { position: relative; padding-left: 1.5rem; margin-bottom: 0.85rem; color: #94a3b8; font-size: 1rem; line-height: 1.5; }
307
+ .custom-list :global(li::before) { content: "→"; position: absolute; left: 0; color: #6366f1; font-weight: 900; }
308
+ </style>
309
+ `,
310
+ usage: `<MinimalListMarkers>Content</MinimalListMarkers>`,
311
+ },
312
+ {
313
+ slug: 'text-3d-layer',
314
+ name: '3D Stacked Text',
315
+ description: 'Text layers that create a three-dimensional depth effect.',
316
+ category: 'typography',
317
+ tags: ['typography', '3d', 'creative'],
318
+ code: `---
319
+ ---
320
+ <div class="stack-3d"><slot /></div>
321
+ <style>
322
+ .stack-3d { font-size: 4rem; font-weight: 900; color: #fff; position: relative; text-shadow: 1px 1px #6366f1, 2px 2px #6366f1, 3px 3px #6366f1, 4px 4px #6366f1; line-height: 1.1; }
323
+ @media (max-width: 768px) { .stack-3d { font-size: 2.5rem; } }
324
+ </style>
325
+ `,
326
+ usage: `<Text3DLayer>3D TEXT</Text3DLayer>`,
327
+ },
328
+ {
329
+ slug: 'gradient-underline-link',
330
+ name: 'Gradient Link',
331
+ description: 'Interactive link with a multi-color gradient underline.',
332
+ category: 'typography',
333
+ tags: ['typography', 'link', 'gradient'],
334
+ code: `---
335
+ ---
336
+ <a href="#" class="grad-link"><slot /></a>
337
+ <style>
338
+ .grad-link { text-decoration: none; color: #fff; border-bottom: 3px solid transparent; border-image: linear-gradient(135deg, #6366f1, #c084fc); border-image-slice: 1; padding-bottom: 2px; font-weight: 700; transition: 0.3s; }
339
+ .grad-link:hover { opacity: 0.8; padding-bottom: 5px; }
340
+ </style>
341
+ `,
342
+ usage: `<GradientUnderlineLink>Content</GradientUnderlineLink>`,
343
+ },
344
+ {
345
+ slug: 'faded-vertical-text',
346
+ name: 'Side Header Text',
347
+ description: 'Vertical text used for sidebars or decorative layout edges.',
348
+ category: 'typography',
349
+ tags: ['typography', 'vertical', 'decorative'],
350
+ code: `---
351
+ ---
352
+ <div class="v-txt"><slot /></div>
353
+ <style>
354
+ .v-txt { writing-mode: vertical-rl; transform: rotate(180deg); color: #334155; font-size: 0.7rem; font-weight: 800; letter-spacing: 0.4em; text-transform: uppercase; white-space: nowrap; }
355
+ </style>
356
+ `,
357
+ usage: `<FadedVerticalText>Content</FadedVerticalText>`,
358
+ },
359
+ {
360
+ slug: 'animated-counter-text',
361
+ name: 'Animated Counter Text',
362
+ description: 'Smooth number counter animation that counts up from 0 to target value on scroll into viewport.',
363
+ category: 'typography',
364
+ tags: ["typography","counter","animated","numbers","kpi"],
365
+ featured: true,
366
+ code: `---
367
+ /**
368
+ * AnimatedCounterText — Kinetic statistic counter display with animated number progression and unit suffixes.
369
+ *
370
+ * @param {number} target - Numerical target value (e.g. 99, 120, 2400). Default is 120.
371
+ * @param {string} prefix - Optional string prefix (e.g. '$', '+').
372
+ * @param {string} suffix - Optional string suffix (e.g. '%', 'k', '+'). Default is '+'.
373
+ * @param {string} label - Descriptive text below the counter.
374
+ * @param {string} color - Text accent color. Default is '#d4a017'.
375
+ */
376
+ interface Props {
377
+ target?: number;
378
+ prefix?: string;
379
+ suffix?: string;
380
+ label?: string;
381
+ color?: string;
382
+ }
383
+
384
+ const {
385
+ target = 120,
386
+ prefix = '',
387
+ suffix = '+',
388
+ label = 'Components Ready to Ship',
389
+ color = '#d4a017',
390
+ } = Astro.props;
391
+ ---
392
+
393
+ <div class="stat-counter" data-counter-wrap style={\`--counter-color: \${color};\`}>
394
+ <div class="stat-counter__number">
395
+ {prefix && <span class="affix">{prefix}</span>}
396
+ <span class="value" data-counter-target={target}>0</span>
397
+ {suffix && <span class="affix">{suffix}</span>}
398
+ </div>
399
+ {label && <p class="stat-counter__label">{label}</p>}
400
+ </div>
401
+
402
+ <style>
403
+ .stat-counter {
404
+ display: inline-flex;
405
+ flex-direction: column;
406
+ align-items: center;
407
+ font-family: var(--font-sans, system-ui, sans-serif);
408
+ user-select: none;
409
+ }
410
+
411
+ .stat-counter__number {
412
+ display: inline-flex;
413
+ align-items: baseline;
414
+ font-size: clamp(2.5rem, 6vw, 4.2rem);
415
+ font-weight: 900;
416
+ line-height: 1;
417
+ letter-spacing: -0.04em;
418
+ color: var(--counter-color);
419
+ text-shadow: 0 0 24px rgba(212, 160, 23, 0.35);
420
+ }
421
+
422
+ .stat-counter__label {
423
+ font-size: 0.85rem;
424
+ font-weight: 600;
425
+ color: var(--c-text-2, #a3a3a3);
426
+ margin-top: 6px;
427
+ text-transform: uppercase;
428
+ letter-spacing: 0.05em;
429
+ }
430
+ </style>
431
+
432
+ <script>
433
+ function setupCounters() {
434
+ const observer = new IntersectionObserver((entries) => {
435
+ entries.forEach((entry) => {
436
+ if (!entry.isIntersecting) return;
437
+ const targetEl = entry.target.querySelector('[data-counter-target]') as HTMLElement;
438
+ if (!targetEl || targetEl.getAttribute('data-counted')) return;
439
+
440
+ targetEl.setAttribute('data-counted', 'true');
441
+ const finalVal = parseInt(targetEl.getAttribute('data-counter-target') || '0', 10);
442
+ const duration = 1500;
443
+ const start = performance.now();
444
+
445
+ function step(now: number) {
446
+ const progress = Math.min((now - start) / duration, 1);
447
+ // Ease out cubic
448
+ const eased = 1 - Math.pow(1 - progress, 3);
449
+ targetEl.textContent = Math.floor(eased * finalVal).toLocaleString();
450
+
451
+ if (progress < 1) {
452
+ requestAnimationFrame(step);
453
+ } else {
454
+ targetEl.textContent = finalVal.toLocaleString();
455
+ }
456
+ }
457
+
458
+ requestAnimationFrame(step);
459
+ });
460
+ }, { threshold: 0.2 });
461
+
462
+ document.querySelectorAll('[data-counter-wrap]').forEach((el) => observer.observe(el));
463
+ }
464
+
465
+ setupCounters();
466
+ document.addEventListener('astro:page-load', setupCounters);
467
+ </script>
468
+ `,
469
+ usage: `<AnimatedCounterText value={15420} prefix="$" suffix="+" duration={1800} />`,
470
+ },
471
+ {
472
+ slug: 'gradient-text-marquee',
473
+ name: 'Gradient Text Marquee',
474
+ description: 'Continuous smooth looping horizontal marquee ribbon featuring metallic gradient typography.',
475
+ category: 'typography',
476
+ tags: ["typography","marquee","gradient","loop","ticker"],
477
+ featured: true,
478
+ code: `---
479
+ /**
480
+ * GradientTextMarquee — Continuous kinetic typographic marquee banner with background-clip gold gradient.
481
+ *
482
+ * @param {string[]} phrases - Array of words or slogans to scroll continuously.
483
+ * @param {string} speed - Animation duration string (e.g. '25s'). Default is '20s'.
484
+ * @param {boolean} reverse - Whether the marquee moves in reverse direction.
485
+ */
486
+ interface Props {
487
+ phrases?: string[];
488
+ speed?: string;
489
+ reverse?: boolean;
490
+ }
491
+
492
+ const {
493
+ phrases = [
494
+ 'PERFORMANCE FIRST',
495
+ 'ZERO DEPENDENCIES',
496
+ 'ACCESSIBLE WCAG AA',
497
+ 'ASTRO 5 READY',
498
+ 'DARK GLASS MORPHISM',
499
+ ],
500
+ speed = '20s',
501
+ reverse = false,
502
+ } = Astro.props;
503
+ ---
504
+
505
+ <div class="grad-marquee" style={\`--marquee-speed: \${speed};\`} aria-label="Animated slogan marquee">
506
+ <div class:list={['grad-marquee__track', { 'is-reverse': reverse }]}>
507
+ {phrases.map((phrase) => (
508
+ <span class="grad-marquee__item">
509
+ <span class="text">{phrase}</span>
510
+ <span class="dot" aria-hidden="true">✦</span>
511
+ </span>
512
+ ))},
513
+ {/* Duplicate loop */}
514
+ {phrases.map((phrase) => (
515
+ <span class="grad-marquee__item" aria-hidden="true">
516
+ <span class="text">{phrase}</span>
517
+ <span class="dot" aria-hidden="true">✦</span>
518
+ </span>
519
+ ))}
520
+ </div>
521
+ </div>
522
+
523
+ <style>
524
+ .grad-marquee {
525
+ position: relative;
526
+ width: 100%;
527
+ overflow: hidden;
528
+ padding: var(--sp-4, 1rem) 0;
529
+ user-select: none;
530
+ mask-image: linear-gradient(90deg, transparent, #000 15%, #000 85%, transparent);
531
+ -webkit-mask-image: linear-gradient(90deg, transparent, #000 15%, #000 85%, transparent);
532
+ font-family: var(--font-sans, system-ui, sans-serif);
533
+ }
534
+
535
+ .grad-marquee__track {
536
+ display: flex;
537
+ align-items: center;
538
+ gap: 2rem;
539
+ width: max-content;
540
+ animation: marquee-roll var(--marquee-speed) linear infinite;
541
+ }
542
+
543
+ .grad-marquee__track:hover {
544
+ animation-play-state: paused;
545
+ }
546
+
547
+ .grad-marquee__track.is-reverse {
548
+ animation-direction: reverse;
549
+ }
550
+
551
+ .grad-marquee__item {
552
+ display: inline-flex;
553
+ align-items: center;
554
+ gap: 2rem;
555
+ }
556
+
557
+ .grad-marquee__item .text {
558
+ font-size: clamp(1.8rem, 4vw, 3.2rem);
559
+ font-weight: 900;
560
+ letter-spacing: 0.05em;
561
+ text-transform: uppercase;
562
+ background: linear-gradient(135deg, #ffffff 0%, #fef3c7 40%, #d4a017 80%, #b8860b 100%);
563
+ -webkit-background-clip: text;
564
+ -webkit-text-fill-color: transparent;
565
+ background-clip: text;
566
+ white-space: nowrap;
567
+ }
568
+
569
+ .grad-marquee__item .dot {
570
+ color: var(--c-primary-light, #d4a017);
571
+ font-size: 1.2rem;
572
+ }
573
+
574
+ @keyframes marquee-roll {
575
+ from { transform: translateX(0); }
576
+ to { transform: translateX(-50%); }
577
+ }
578
+
579
+ @media (prefers-reduced-motion: reduce) {
580
+ .grad-marquee__track { animation: none; overflow-x: auto; }
581
+ }
582
+ </style>
583
+ `,
584
+ usage: `<GradientTextMarquee text="ASTRO COMPONENTS • MODERN UI • ZERO RUNTIME • ACCESSIBLE • " speed="normal" />`,
585
+ },
586
+ {
587
+ slug: 'curved-path-heading',
588
+ name: 'Curved Path Heading',
589
+ description: 'SVG textOnPath arc headline for playful badges, stamp seals, and creative hero section headers.',
590
+ category: 'typography',
591
+ tags: ["typography","curved","path","svg","heading"],
592
+ featured: false,
593
+ code: `---
594
+ /**
595
+ * CurvedPathHeading — Typographic heading flowing along an SVG circular or arched trajectory.
596
+ *
597
+ * @param {string} text - Headline text content. Default is 'ASTRO COMPONENTS KIT • ZERO RUNTIME DEPENDENCIES •'.
598
+ * @param {number} size - Pixel container diameter. Default is 260.
599
+ * @param {boolean} animate - Whether the badge rotates continuously. Default is true.
600
+ */
601
+ interface Props {
602
+ text?: string;
603
+ size?: number;
604
+ animate?: boolean;
605
+ }
606
+
607
+ const {
608
+ text = 'ASTRO COMPONENTS KIT • ZERO RUNTIME • PURE CSS •',
609
+ size = 260,
610
+ animate = true,
611
+ } = Astro.props;
612
+
613
+ const pathId = \`curve-path-\${Math.random().toString(36).substring(2, 8)}\`;
614
+ ---
615
+
616
+ <div class:list={['curved-heading', { 'is-animated': animate }]} style={\`--curved-size: \${size}px;\`}>
617
+ <svg viewBox="0 0 300 300" width={size} height={size}>
618
+ <defs>
619
+ <path
620
+ id={pathId}
621
+ d="M 150, 150 m -110, 0 a 110,110 0 1,1 220,0 a 110,110 0 1,1 -220,0"
622
+ />
623
+ </defs>
624
+ <text class="curved-heading__text">
625
+ <textPath href={\`#\${pathId}\`} startOffset="0%">
626
+ {text}
627
+ </textPath>
628
+ </text>
629
+ </svg>
630
+ <div class="curved-heading__core">✦</div>
631
+ </div>
632
+
633
+ <style>
634
+ .curved-heading {
635
+ position: relative;
636
+ width: var(--curved-size);
637
+ height: var(--curved-size);
638
+ display: inline-flex;
639
+ align-items: center;
640
+ justify-content: center;
641
+ font-family: var(--font-sans, system-ui, sans-serif);
642
+ user-select: none;
643
+ }
644
+
645
+ .curved-heading.is-animated svg {
646
+ animation: curved-spin 20s linear infinite;
647
+ }
648
+
649
+ .curved-heading__text {
650
+ font-size: 13.5px;
651
+ font-weight: 800;
652
+ letter-spacing: 0.18em;
653
+ text-transform: uppercase;
654
+ fill: var(--c-primary-light, #d4a017);
655
+ }
656
+
657
+ .curved-heading__core {
658
+ position: absolute;
659
+ width: 48px;
660
+ height: 48px;
661
+ border-radius: 50%;
662
+ background: rgba(184, 134, 11, 0.12);
663
+ border: 1px solid rgba(184, 134, 11, 0.3);
664
+ color: var(--c-accent, #fef3c7);
665
+ display: grid;
666
+ place-items: center;
667
+ font-size: 1.2rem;
668
+ }
669
+
670
+ @keyframes curved-spin {
671
+ from { transform: rotate(0deg); }
672
+ to { transform: rotate(360deg); }
673
+ }
674
+
675
+ @media (prefers-reduced-motion: reduce) {
676
+ .curved-heading.is-animated svg { animation: none; }
677
+ }
678
+ </style>
679
+ `,
680
+ usage: `<CurvedPathHeading text="PREMIUM ASTRO UI LIBRARY • " radius={120} />`,
681
+ },
682
+ {
683
+ slug: 'hacker-decode-text',
684
+ name: 'Hacker Decode Text',
685
+ description: 'Matrix/Cyberpunk scrambled text reveal effect that rapidly randomizes glyphs before resolving into the final string.',
686
+ category: 'typography',
687
+ tags: ["typography","cyberpunk","decode","glitch","matrix"],
688
+ featured: false,
689
+ code: `---
690
+ /**
691
+ * HackerDecodeText — Cyberpunk Matrix-style randomized character deciphering effect on hover.
692
+ *
693
+ * @param {string} text - Final decoded text string. Default is 'CYBERNETIC SYSTEM READY'.
694
+ * @param {string} tag - HTML heading or paragraph tag. Default is 'h3'.
695
+ * @param {string} className - Optional CSS class names.
696
+ */
697
+ interface Props {
698
+ text?: string;
699
+ tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'p' | 'span';
700
+ className?: string;
701
+ }
702
+
703
+ const {
704
+ text = 'CYBERNETIC PROTOCOL ONLINE',
705
+ tag: Tag = 'h3',
706
+ className = '',
707
+ } = Astro.props;
708
+ ---
709
+
710
+ <Tag
711
+ class:list={['hacker-text', className]}
712
+ data-hacker-text={text}
713
+ title="Hover to decode"
714
+ >
715
+ {text}
716
+ </Tag>
717
+
718
+ <style>
719
+ .hacker-text {
720
+ font-family: var(--font-mono, 'JetBrains Mono', monospace);
721
+ font-size: clamp(1.2rem, 3vw, 2rem);
722
+ font-weight: 800;
723
+ color: var(--c-primary-light, #d4a017);
724
+ letter-spacing: 0.08em;
725
+ cursor: default;
726
+ user-select: none;
727
+ transition: text-shadow 0.2s ease;
728
+ }
729
+
730
+ .hacker-text:hover {
731
+ text-shadow: 0 0 12px rgba(212, 160, 23, 0.7);
732
+ }
733
+ </style>
734
+
735
+ <script>
736
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;:,.<>?';
737
+
738
+ function setupHackerText() {
739
+ document.querySelectorAll('[data-hacker-text]').forEach((el) => {
740
+ if (el.getAttribute('data-hacker-init')) return;
741
+ el.setAttribute('data-hacker-init', 'true');
742
+
743
+ const original = el.getAttribute('data-hacker-text') || el.textContent || '';
744
+ let interval: any = null;
745
+
746
+ el.addEventListener('pointerenter', () => {
747
+ let iteration = 0;
748
+ clearInterval(interval);
749
+
750
+ interval = setInterval(() => {
751
+ el.textContent = original
752
+ .split('')
753
+ .map((char, index) => {
754
+ if (char === ' ') return ' ';
755
+ if (index < iteration) {
756
+ return original[index];
757
+ }
758
+ return chars[Math.floor(Math.random() * chars.length)];
759
+ })
760
+ .join('');
761
+
762
+ if (iteration >= original.length) {
763
+ clearInterval(interval);
764
+ }
765
+ iteration += 1 / 2;
766
+ }, 30);
767
+ });
768
+ });
769
+ }
770
+
771
+ setupHackerText();
772
+ document.addEventListener('astro:page-load', setupHackerText);
773
+ </script>
774
+ `,
775
+ usage: `<HackerDecodeText text="CYBERNETIC_INTERFACE_INITIALIZED" speed={40} />`,
776
+ },
777
+ ];