@anubis609/astroanimate-core 0.1.2
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/LICENSE +21 -0
- package/README.md +212 -0
- package/dist/components/AnimatedBorderButton/AnimatedBorderButton.astro +129 -0
- package/dist/components/AnimatedBorderButton/index.js +3 -0
- package/dist/components/AnimatedBorderButton/index.js.map +1 -0
- package/dist/components/AnimatedButton/AnimatedButton.astro +299 -0
- package/dist/components/AnimatedButton/index.js +3 -0
- package/dist/components/AnimatedButton/index.js.map +1 -0
- package/dist/components/AnimatedCard/AnimatedCard.astro +832 -0
- package/dist/components/AnimatedCard/index.js +3 -0
- package/dist/components/AnimatedCard/index.js.map +1 -0
- package/dist/components/AnimatedTabs/AnimatedTabs.astro +348 -0
- package/dist/components/AnimatedTabs/index.js +3 -0
- package/dist/components/AnimatedTabs/index.js.map +1 -0
- package/dist/components/ArrowCTAButton/ArrowCTAButton.astro +159 -0
- package/dist/components/ArticleCard/ArticleCard.astro +208 -0
- package/dist/components/CardStack/CardStack.astro +444 -0
- package/dist/components/CardStack/index.js +3 -0
- package/dist/components/CardStack/index.js.map +1 -0
- package/dist/components/CountUp/CountUp.astro +89 -0
- package/dist/components/CountUp/index.js +3 -0
- package/dist/components/CountUp/index.js.map +1 -0
- package/dist/components/Dock/Dock.astro +567 -0
- package/dist/components/Dock/DockItem.astro +135 -0
- package/dist/components/Dropdown/Dropdown.astro +264 -0
- package/dist/components/ExpandableCard/ExpandableCard.astro +402 -0
- package/dist/components/ExpandableCard/index.js +3 -0
- package/dist/components/ExpandableCard/index.js.map +1 -0
- package/dist/components/FadeInText/FadeInText.astro +314 -0
- package/dist/components/FadeInText/index.js +3 -0
- package/dist/components/FadeInText/index.js.map +1 -0
- package/dist/components/FillHoverButton/FillHoverButton.astro +125 -0
- package/dist/components/GitHubShineButton/GitHubShineButton.astro +208 -0
- package/dist/components/GlassCard/GlassCard.astro +245 -0
- package/dist/components/GlassCard/index.js +3 -0
- package/dist/components/GlassCard/index.js.map +1 -0
- package/dist/components/GridDotsBackground/GridDotsBackground.astro +144 -0
- package/dist/components/HighlightText/HighlightText.astro +106 -0
- package/dist/components/InfiniteMarquee/InfiniteMarquee.astro +339 -0
- package/dist/components/JobCard/JobCard.astro +230 -0
- package/dist/components/LiquidGlassCard/LiquidGlassCard.astro +569 -0
- package/dist/components/Loader/Loader.astro +156 -0
- package/dist/components/Loader/index.js +3 -0
- package/dist/components/Loader/index.js.map +1 -0
- package/dist/components/NewsletterPopupCard/NewsletterPopupCard.astro +331 -0
- package/dist/components/ProductReviewCard/ProductReviewCard.astro +188 -0
- package/dist/components/ProgressBar/ProgressBar.astro +137 -0
- package/dist/components/ProgressBar/index.js +3 -0
- package/dist/components/ProgressBar/index.js.map +1 -0
- package/dist/components/RevealImage/RevealImage.astro +160 -0
- package/dist/components/RevealImage/index.js +3 -0
- package/dist/components/RevealImage/index.js.map +1 -0
- package/dist/components/ScaleIn/ScaleIn.astro +231 -0
- package/dist/components/ScaleIn/index.js +3 -0
- package/dist/components/ScaleIn/index.js.map +1 -0
- package/dist/components/SlidingOverlayButton/SlidingOverlayButton.astro +126 -0
- package/dist/components/StaggerTextButton/StaggerTextButton.astro +132 -0
- package/dist/components/Tooltip/Tooltip.astro +255 -0
- package/dist/components/Tooltip/index.js +3 -0
- package/dist/components/Tooltip/index.js.map +1 -0
- package/dist/components/TypewriterText/TypewriterText.astro +380 -0
- package/dist/components/TypewriterText/index.js +3 -0
- package/dist/components/TypewriterText/index.js.map +1 -0
- package/dist/components/index.js +33 -0
- package/dist/components/index.js.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/countup.js +90 -0
- package/dist/internal/countup.js.map +1 -0
- package/dist/internal/dropdown.js +166 -0
- package/dist/internal/dropdown.js.map +1 -0
- package/dist/internal/fadein.js +116 -0
- package/dist/internal/fadein.js.map +1 -0
- package/dist/internal/guards.js +12 -0
- package/dist/internal/guards.js.map +1 -0
- package/dist/internal/tabs.js +140 -0
- package/dist/internal/tabs.js.map +1 -0
- package/package.json +229 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
/**
|
|
4
|
+
* Tooltip content text.
|
|
5
|
+
*/
|
|
6
|
+
content: string;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Tooltip position.
|
|
10
|
+
* @default "top"
|
|
11
|
+
*/
|
|
12
|
+
position?: "top" | "bottom" | "left" | "right";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Animation variant.
|
|
16
|
+
* "fade" - Simple fade and zoom
|
|
17
|
+
* "spring" - Bouncy pop entrance
|
|
18
|
+
* "float" - Gently floats up from below
|
|
19
|
+
* @default "spring"
|
|
20
|
+
*/
|
|
21
|
+
variant?: "fade" | "spring" | "float";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Opt-in to advanced JS-enhanced transitions.
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
enhance?: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Transition duration in ms.
|
|
31
|
+
* @default 200
|
|
32
|
+
*/
|
|
33
|
+
duration?: number;
|
|
34
|
+
|
|
35
|
+
class?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
content,
|
|
40
|
+
position = "top",
|
|
41
|
+
variant = "spring",
|
|
42
|
+
enhance = false,
|
|
43
|
+
duration = 200,
|
|
44
|
+
class: className = "",
|
|
45
|
+
} = Astro.props;
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
<style>
|
|
49
|
+
.astro-tooltip-wrapper {
|
|
50
|
+
position: relative;
|
|
51
|
+
display: inline-block;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.astro-tooltip {
|
|
55
|
+
position: absolute;
|
|
56
|
+
z-index: 50;
|
|
57
|
+
pointer-events: none;
|
|
58
|
+
|
|
59
|
+
/* Aesthetics */
|
|
60
|
+
padding: 0.5rem 0.75rem;
|
|
61
|
+
background: rgba(15, 23, 42, 0.9);
|
|
62
|
+
backdrop-filter: blur(8px);
|
|
63
|
+
-webkit-backdrop-filter: blur(8px);
|
|
64
|
+
color: white;
|
|
65
|
+
font-size: 0.875rem;
|
|
66
|
+
font-weight: 500;
|
|
67
|
+
white-space: nowrap;
|
|
68
|
+
border-radius: 0.5rem;
|
|
69
|
+
box-shadow:
|
|
70
|
+
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
|
71
|
+
0 4px 6px -2px rgba(0, 0, 0, 0.05),
|
|
72
|
+
inset 0 0 0 1px rgba(255, 255, 255, 0.1);
|
|
73
|
+
|
|
74
|
+
/* Default Hidden State */
|
|
75
|
+
opacity: 0;
|
|
76
|
+
visibility: hidden;
|
|
77
|
+
transition:
|
|
78
|
+
opacity var(--tooltip-duration) ease,
|
|
79
|
+
transform var(--tooltip-duration) var(--tooltip-easing);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* Triangle Arrow */
|
|
83
|
+
.astro-tooltip::after {
|
|
84
|
+
content: "";
|
|
85
|
+
position: absolute;
|
|
86
|
+
border: 6px solid transparent;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Position Variations */
|
|
90
|
+
[data-position="top"] {
|
|
91
|
+
bottom: 100%; left: 50%;
|
|
92
|
+
transform: translate(-50%, 10px);
|
|
93
|
+
margin-bottom: 0.75rem;
|
|
94
|
+
}
|
|
95
|
+
[data-position="top"]::after {
|
|
96
|
+
top: 100%; left: 50%;
|
|
97
|
+
transform: translateX(-50%);
|
|
98
|
+
border-top-color: rgba(15, 23, 42, 0.9);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
[data-position="bottom"] {
|
|
102
|
+
top: 100%; left: 50%;
|
|
103
|
+
transform: translate(-50%, -10px);
|
|
104
|
+
margin-top: 0.75rem;
|
|
105
|
+
}
|
|
106
|
+
[data-position="bottom"]::after {
|
|
107
|
+
bottom: 100%; left: 50%;
|
|
108
|
+
transform: translateX(-50%);
|
|
109
|
+
border-bottom-color: rgba(15, 23, 42, 0.9);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
[data-position="left"] {
|
|
113
|
+
right: 100%; top: 50%;
|
|
114
|
+
transform: translate(10px, -50%);
|
|
115
|
+
margin-right: 0.75rem;
|
|
116
|
+
}
|
|
117
|
+
[data-position="left"]::after {
|
|
118
|
+
left: 100%; top: 50%;
|
|
119
|
+
transform: translateY(-50%);
|
|
120
|
+
border-left-color: rgba(15, 23, 42, 0.9);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
[data-position="right"] {
|
|
124
|
+
left: 100%; top: 50%;
|
|
125
|
+
transform: translate(-10px, -50%);
|
|
126
|
+
margin-left: 0.75rem;
|
|
127
|
+
}
|
|
128
|
+
[data-position="right"]::after {
|
|
129
|
+
right: 100%; top: 50%;
|
|
130
|
+
transform: translateY(-50%);
|
|
131
|
+
border-right-color: rgba(15, 23, 42, 0.9);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Visibility on Hover (CSS Only) */
|
|
135
|
+
.astro-tooltip-wrapper:hover .astro-tooltip {
|
|
136
|
+
opacity: 1;
|
|
137
|
+
visibility: visible;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* NO-JS / NO-ENHANCE */
|
|
141
|
+
[data-variant="fade"] { --tooltip-easing: ease; }
|
|
142
|
+
[data-variant="spring"] { --tooltip-easing: cubic-bezier(0.34, 1.56, 0.64, 1); }
|
|
143
|
+
[data-variant="float"] { --tooltip-easing: cubic-bezier(0.23, 1, 0.32, 1); }
|
|
144
|
+
|
|
145
|
+
.astro-tooltip-wrapper:hover [data-position="top"] { transform: translate(-50%, 0); }
|
|
146
|
+
.astro-tooltip-wrapper:hover [data-position="bottom"] { transform: translate(-50%, 0); }
|
|
147
|
+
.astro-tooltip-wrapper:hover [data-position="left"] { transform: translate(0, -50%); }
|
|
148
|
+
.astro-tooltip-wrapper:hover [data-position="right"] { transform: translate(0, -50%); }
|
|
149
|
+
|
|
150
|
+
/* Reduced Motion */
|
|
151
|
+
@media (prefers-reduced-motion: reduce) {
|
|
152
|
+
.astro-tooltip {
|
|
153
|
+
transition: opacity var(--tooltip-duration) ease !important;
|
|
154
|
+
transform: translate(-50%, 0) !important;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
</style>
|
|
158
|
+
|
|
159
|
+
<div
|
|
160
|
+
class:list={["astro-tooltip-wrapper", className]}
|
|
161
|
+
data-astro-tooltip-wrapper
|
|
162
|
+
data-enhance={enhance ? "true" : "false"}
|
|
163
|
+
style={`--tooltip-duration: ${duration}ms;`}
|
|
164
|
+
>
|
|
165
|
+
<slot />
|
|
166
|
+
<div
|
|
167
|
+
class="astro-tooltip"
|
|
168
|
+
data-astro-tooltip
|
|
169
|
+
data-position={position}
|
|
170
|
+
data-variant={variant}
|
|
171
|
+
role="tooltip"
|
|
172
|
+
>
|
|
173
|
+
{content}
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{enhance && (
|
|
178
|
+
<script is:inline>
|
|
179
|
+
(function () {
|
|
180
|
+
if (typeof window === "undefined") return;
|
|
181
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
182
|
+
|
|
183
|
+
var initialized = new WeakMap();
|
|
184
|
+
|
|
185
|
+
function canEnhance() {
|
|
186
|
+
if (typeof window === "undefined") return false;
|
|
187
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function enhanceTooltip(root) {
|
|
194
|
+
if (!canEnhance()) return;
|
|
195
|
+
if (!(root instanceof HTMLElement)) return;
|
|
196
|
+
if (initialized.has(root)) return;
|
|
197
|
+
|
|
198
|
+
var tooltip = root.querySelector('[data-astro-tooltip]');
|
|
199
|
+
if (!tooltip || !(tooltip instanceof HTMLElement)) return;
|
|
200
|
+
|
|
201
|
+
var handleMouseMove = function (e) {
|
|
202
|
+
var rect = root.getBoundingClientRect();
|
|
203
|
+
var x = e.clientX - rect.left;
|
|
204
|
+
var y = e.clientY - rect.top;
|
|
205
|
+
|
|
206
|
+
var centerX = rect.width / 2;
|
|
207
|
+
var centerY = rect.height / 2;
|
|
208
|
+
var moveX = (x - centerX) / 10;
|
|
209
|
+
var moveY = (y - centerY) / 10;
|
|
210
|
+
|
|
211
|
+
tooltip.style.setProperty("--js-offset-x", moveX + "px");
|
|
212
|
+
tooltip.style.setProperty("--js-offset-y", moveY + "px");
|
|
213
|
+
|
|
214
|
+
var position = tooltip.dataset.position;
|
|
215
|
+
var baseTransform = "";
|
|
216
|
+
if (position === "top" || position === "bottom") baseTransform = "translateX(-50%)";
|
|
217
|
+
if (position === "left" || position === "right") baseTransform = "translateY(-50%)";
|
|
218
|
+
|
|
219
|
+
tooltip.style.transform = baseTransform + " translate(" + moveX + "px, " + moveY + "px)";
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
var resetTooltip = function () {
|
|
223
|
+
var position = tooltip.dataset.position;
|
|
224
|
+
var baseTransform = "";
|
|
225
|
+
if (position === "top" || position === "bottom") baseTransform = "translateX(-50%)";
|
|
226
|
+
if (position === "left" || position === "right") baseTransform = "translateY(-50%)";
|
|
227
|
+
|
|
228
|
+
tooltip.style.transform = baseTransform;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
root.addEventListener("mousemove", handleMouseMove);
|
|
232
|
+
root.addEventListener("mouseleave", resetTooltip);
|
|
233
|
+
|
|
234
|
+
initialized.set(root, true);
|
|
235
|
+
|
|
236
|
+
var cleanupObserver = new MutationObserver(function () {
|
|
237
|
+
if (!document.contains(root)) {
|
|
238
|
+
root.removeEventListener("mousemove", handleMouseMove);
|
|
239
|
+
root.removeEventListener("mouseleave", resetTooltip);
|
|
240
|
+
cleanupObserver.disconnect();
|
|
241
|
+
initialized.delete(root);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
cleanupObserver.observe(document.body, { childList: true, subtree: true });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
document
|
|
249
|
+
.querySelectorAll('[data-astro-tooltip-wrapper][data-enhance="true"]')
|
|
250
|
+
.forEach(function (element) {
|
|
251
|
+
enhanceTooltip(element);
|
|
252
|
+
});
|
|
253
|
+
})();
|
|
254
|
+
</script>
|
|
255
|
+
)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
---
|
|
2
|
+
// ✅ CRITERION 5: No astro:* events — standard Astro component only
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
/**
|
|
6
|
+
* Array of strings to cycle through.
|
|
7
|
+
* Purpose: Provides the texts the typewriter will cycle through.
|
|
8
|
+
* Type: string[]
|
|
9
|
+
* Default: (required)
|
|
10
|
+
* Affects no-JS experience: YES — first item is shown as static fallback
|
|
11
|
+
* Requires `enhance` prop: NO for static display; YES for typewriter cycling
|
|
12
|
+
*/
|
|
13
|
+
texts: string[];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Opt-in to progressive JS enhancement.
|
|
17
|
+
* Purpose: When true, enables the typewriter cycling behavior.
|
|
18
|
+
* Type: boolean
|
|
19
|
+
* Default: false
|
|
20
|
+
* Affects no-JS experience: NO — false means static text
|
|
21
|
+
* Requires `enhance` prop: N/A
|
|
22
|
+
*/
|
|
23
|
+
enhance?: boolean;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Milliseconds per character during typing phase.
|
|
27
|
+
* Type: number
|
|
28
|
+
* Default: 70
|
|
29
|
+
* Affects no-JS: NO
|
|
30
|
+
* Requires enhance: YES
|
|
31
|
+
*/
|
|
32
|
+
typeSpeed?: number;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Milliseconds per character during erasing phase.
|
|
36
|
+
* Type: number
|
|
37
|
+
* Default: 40
|
|
38
|
+
* Affects no-JS: NO
|
|
39
|
+
* Requires enhance: YES
|
|
40
|
+
*/
|
|
41
|
+
eraseSpeed?: number;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Milliseconds to pause after fully typing a string.
|
|
45
|
+
* Type: number
|
|
46
|
+
* Default: 1800
|
|
47
|
+
* Affects no-JS: NO
|
|
48
|
+
* Requires enhance: YES
|
|
49
|
+
*/
|
|
50
|
+
pauseAfter?: number;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Milliseconds to pause when fully erased before next string.
|
|
54
|
+
* Type: number
|
|
55
|
+
* Default: 500
|
|
56
|
+
* Affects no-JS: NO
|
|
57
|
+
* Requires enhance: YES
|
|
58
|
+
*/
|
|
59
|
+
pauseEmpty?: number;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether to show a blinking cursor.
|
|
63
|
+
* Type: boolean
|
|
64
|
+
* Default: true
|
|
65
|
+
* Affects no-JS: YES — cursor is CSS-only, visible without JS
|
|
66
|
+
* Requires enhance: NO
|
|
67
|
+
*/
|
|
68
|
+
showCursor?: boolean;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Cursor character string.
|
|
72
|
+
* Type: string
|
|
73
|
+
* Default: "|"
|
|
74
|
+
* Affects no-JS: YES — rendered as static HTML
|
|
75
|
+
* Requires enhance: NO
|
|
76
|
+
*/
|
|
77
|
+
cursor?: string;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Custom class applied to the root element.
|
|
81
|
+
* Type: string
|
|
82
|
+
* Default: ""
|
|
83
|
+
*/
|
|
84
|
+
mainClassName?: string;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether to loop through texts continuously or stop after one cycle.
|
|
88
|
+
* Type: boolean
|
|
89
|
+
* Default: true
|
|
90
|
+
* Affects no-JS: NO
|
|
91
|
+
* Requires enhance: YES
|
|
92
|
+
*/
|
|
93
|
+
loop?: boolean;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Whether to erase text before typing the next phrase.
|
|
97
|
+
* When false, phrases are typed sequentially without erasing.
|
|
98
|
+
* Type: boolean
|
|
99
|
+
* Default: true
|
|
100
|
+
* Affects no-JS: NO
|
|
101
|
+
* Requires enhance: YES
|
|
102
|
+
*/
|
|
103
|
+
erase?: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const {
|
|
107
|
+
texts,
|
|
108
|
+
enhance = false,
|
|
109
|
+
typeSpeed = 70,
|
|
110
|
+
eraseSpeed = 40,
|
|
111
|
+
pauseAfter = 1800,
|
|
112
|
+
pauseEmpty = 500,
|
|
113
|
+
showCursor = true,
|
|
114
|
+
cursor = "|",
|
|
115
|
+
mainClassName = "",
|
|
116
|
+
loop = true,
|
|
117
|
+
erase = true,
|
|
118
|
+
} = Astro.props;
|
|
119
|
+
|
|
120
|
+
// ✅ CRITERION 1: Meaningful content for no-JS — first string is the static fallback
|
|
121
|
+
const staticFallback = texts[0] ?? "";
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
<style>
|
|
125
|
+
/* ✅ CRITERION 7: All layout and visual state is CSS — JS only sets data-ready */
|
|
126
|
+
|
|
127
|
+
[data-typewriter] {
|
|
128
|
+
display: inline-flex;
|
|
129
|
+
align-items: baseline;
|
|
130
|
+
gap: 0.05em;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* ──────────────────────────────────────────────────────────────────────────
|
|
134
|
+
* ✅ CRITERION 1: Static display — visible before JS runs.
|
|
135
|
+
* The display span shows the first text. .typewriter-static is visible.
|
|
136
|
+
* .typewriter-dynamic is hidden until data-ready is set.
|
|
137
|
+
* ────────────────────────────────────────────────────────────────────────── */
|
|
138
|
+
|
|
139
|
+
/* Static fallback content — shown when JS has not run */
|
|
140
|
+
.typewriter-static {
|
|
141
|
+
display: inline;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* Dynamic display span — hidden by default, shown when JS activates */
|
|
145
|
+
.typewriter-dynamic {
|
|
146
|
+
display: none;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* When JS activates: swap static → dynamic */
|
|
150
|
+
[data-typewriter][data-ready="true"] .typewriter-static {
|
|
151
|
+
display: none;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
[data-typewriter][data-ready="true"] .typewriter-dynamic {
|
|
155
|
+
display: inline;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* ──────────────────────────────────────────────────────────────────────────
|
|
159
|
+
* Cursor element — CSS-driven, no JS required
|
|
160
|
+
* ────────────────────────────────────────────────────────────────────────── */
|
|
161
|
+
|
|
162
|
+
.typewriter-cursor {
|
|
163
|
+
display: inline-block;
|
|
164
|
+
margin-left: 0.05em;
|
|
165
|
+
/* ✅ CRITERION 6: cursor-blink animation ONLY runs under no-preference */
|
|
166
|
+
/* Without this media query, cursor is a static character */
|
|
167
|
+
opacity: 1;
|
|
168
|
+
color: inherit;
|
|
169
|
+
font-weight: inherit;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* ✅ CRITERION 6: ALL CSS animations wrapped in @media (prefers-reduced-motion: no-preference) */
|
|
173
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
174
|
+
.typewriter-cursor {
|
|
175
|
+
animation: cursor-blink 0.8s step-end infinite;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@keyframes cursor-blink {
|
|
179
|
+
0%,
|
|
180
|
+
100% {
|
|
181
|
+
opacity: 1;
|
|
182
|
+
}
|
|
183
|
+
50% {
|
|
184
|
+
opacity: 0;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* ✅ CRITERION 6: Under reduced motion — cursor is static, no blink */
|
|
190
|
+
@media (prefers-reduced-motion: reduce) {
|
|
191
|
+
.typewriter-cursor {
|
|
192
|
+
animation: none !important;
|
|
193
|
+
opacity: 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
</style>
|
|
197
|
+
|
|
198
|
+
<!-- ✅ CRITERION 1: Meaningful HTML visible without JS — static fallback is rendered here -->
|
|
199
|
+
<!-- ✅ CRITERION 3: No hard-coded IDs — all queries use data-attribute selectors -->
|
|
200
|
+
<span
|
|
201
|
+
class:list={["typewriter-root", mainClassName]}
|
|
202
|
+
data-typewriter
|
|
203
|
+
data-enhance={enhance ? "true" : "false"}
|
|
204
|
+
data-texts={JSON.stringify(texts)}
|
|
205
|
+
data-type-speed={typeSpeed}
|
|
206
|
+
data-erase-speed={eraseSpeed}
|
|
207
|
+
data-pause-after={pauseAfter}
|
|
208
|
+
data-pause-empty={pauseEmpty}
|
|
209
|
+
data-loop={loop ? "true" : "false"}
|
|
210
|
+
data-erase={erase ? "true" : "false"}
|
|
211
|
+
aria-label={staticFallback}
|
|
212
|
+
aria-live="polite"
|
|
213
|
+
aria-atomic="true"
|
|
214
|
+
>
|
|
215
|
+
<!-- ✅ CRITERION 1: First word visible without JS via .typewriter-static -->
|
|
216
|
+
<span class="typewriter-static" aria-hidden="true">{staticFallback}</span>
|
|
217
|
+
|
|
218
|
+
<!-- Dynamic output — JS writes here; hidden until data-ready="true" -->
|
|
219
|
+
<span class="typewriter-dynamic" data-typewriter-display aria-hidden="true"></span>
|
|
220
|
+
|
|
221
|
+
<!-- ✅ CRITERION 1 & 7: Cursor is CSS-only — visible and blinks without JS -->
|
|
222
|
+
{showCursor && (
|
|
223
|
+
<span class="typewriter-cursor" aria-hidden="true">{cursor}</span>
|
|
224
|
+
)}
|
|
225
|
+
</span>
|
|
226
|
+
|
|
227
|
+
<!-- 🔑 JS inlined so it runs when component is consumed from node_modules (scripts there are not bundled) -->
|
|
228
|
+
{enhance && (
|
|
229
|
+
<script is:inline>
|
|
230
|
+
(function () {
|
|
231
|
+
// ✅ CRITERION 6: PRIMARY matchMedia check in .astro script block
|
|
232
|
+
// This check MUST appear here — cannot be delegated to guards.ts alone.
|
|
233
|
+
// Reviewer can see it here: enhancer is ONLY called in the else branch.
|
|
234
|
+
if (typeof window === "undefined") return;
|
|
235
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
236
|
+
// Reduced motion preferred — no JS typewriter enhancement runs.
|
|
237
|
+
// The static first-word fallback (CSS-only) remains visible.
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ✅ CRITERION 3: WeakMap — one timer handle per root element, no global state
|
|
242
|
+
var initialized = new WeakMap();
|
|
243
|
+
|
|
244
|
+
function enhanceTypewriterText(root) {
|
|
245
|
+
// ✅ CRITERION 6: Secondary reduced-motion guard
|
|
246
|
+
if (typeof window === "undefined") return;
|
|
247
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
248
|
+
|
|
249
|
+
// ✅ CRITERION 3: Prevent duplicate initialization
|
|
250
|
+
if (initialized.has(root)) return;
|
|
251
|
+
|
|
252
|
+
// ─── Read configuration from data attributes ───────────────────────────────
|
|
253
|
+
var textsRaw = root.dataset.texts ?? "[]";
|
|
254
|
+
var texts = [];
|
|
255
|
+
try {
|
|
256
|
+
texts = JSON.parse(textsRaw);
|
|
257
|
+
} catch (e) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (texts.length === 0) return;
|
|
261
|
+
|
|
262
|
+
var typeSpeed = Number(root.dataset.typeSpeed ?? "70"); // ms / char (typing)
|
|
263
|
+
var eraseSpeed = Number(root.dataset.eraseSpeed ?? "40"); // ms / char (erasing)
|
|
264
|
+
var pauseAfter = Number(root.dataset.pauseAfter ?? "1800"); // ms pause when fully typed
|
|
265
|
+
var pauseEmpty = Number(root.dataset.pauseEmpty ?? "500"); // ms pause when fully erased
|
|
266
|
+
var loop = root.dataset.loop !== "false"; // true by default
|
|
267
|
+
var erase = root.dataset.erase !== "false"; // true by default
|
|
268
|
+
|
|
269
|
+
// ─── DOM reference ─────────────────────────────────────────────────────────
|
|
270
|
+
var displayEl = root.querySelector("[data-typewriter-display]");
|
|
271
|
+
if (!displayEl) return;
|
|
272
|
+
|
|
273
|
+
// ✅ CRITERION 7: Only data-attribute + textContent mutations — no JS styling
|
|
274
|
+
// ✅ CRITERION 3: dataset mutations happen AFTER all guards
|
|
275
|
+
root.dataset.ready = "true";
|
|
276
|
+
|
|
277
|
+
// ─── State ─────────────────────────────────────────────────────────────────
|
|
278
|
+
var textIndex = 0;
|
|
279
|
+
var charIndex = 0;
|
|
280
|
+
var phase = "typing"; // "typing" | "erasing" | "pauseAfter" | "pauseEmpty"
|
|
281
|
+
var timerId = -1;
|
|
282
|
+
var pauseTimeoutId = -1;
|
|
283
|
+
var accumulatedText = ""; // Used when erase=false to build up text
|
|
284
|
+
|
|
285
|
+
// ─── Core scheduler ────────────────────────────────────────────────────────
|
|
286
|
+
// Uses a single self-rescheduling setTimeout so speed can vary per phase.
|
|
287
|
+
// This is cleaner than setInterval + frame-skipping.
|
|
288
|
+
|
|
289
|
+
var schedule = function (delay) {
|
|
290
|
+
// ✅ CRITERION 3: timerId tracked — clearTimeout called in cleanup
|
|
291
|
+
timerId = window.setTimeout(tick, delay);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
var tick = function () {
|
|
295
|
+
var current = texts[textIndex] ?? "";
|
|
296
|
+
|
|
297
|
+
if (phase === "typing") {
|
|
298
|
+
if (charIndex < current.length) {
|
|
299
|
+
charIndex += 1;
|
|
300
|
+
// ✅ CRITERION 7: JS mutates only textContent, not style
|
|
301
|
+
if (erase) {
|
|
302
|
+
displayEl.textContent = current.slice(0, charIndex);
|
|
303
|
+
} else {
|
|
304
|
+
accumulatedText = accumulatedText + current[charIndex - 1];
|
|
305
|
+
displayEl.textContent = accumulatedText;
|
|
306
|
+
}
|
|
307
|
+
schedule(typeSpeed);
|
|
308
|
+
} else {
|
|
309
|
+
phase = "pauseAfter";
|
|
310
|
+
schedule(pauseAfter);
|
|
311
|
+
}
|
|
312
|
+
} else if (phase === "pauseAfter") {
|
|
313
|
+
if (erase) {
|
|
314
|
+
phase = "erasing";
|
|
315
|
+
schedule(eraseSpeed);
|
|
316
|
+
} else {
|
|
317
|
+
// Skip erasing phase when erase=false
|
|
318
|
+
phase = "pauseEmpty";
|
|
319
|
+
schedule(pauseEmpty);
|
|
320
|
+
}
|
|
321
|
+
} else if (phase === "erasing") {
|
|
322
|
+
if (charIndex > 0) {
|
|
323
|
+
charIndex -= 1;
|
|
324
|
+
displayEl.textContent = current.slice(0, charIndex);
|
|
325
|
+
schedule(eraseSpeed);
|
|
326
|
+
} else {
|
|
327
|
+
phase = "pauseEmpty";
|
|
328
|
+
schedule(pauseEmpty);
|
|
329
|
+
}
|
|
330
|
+
} else if (phase === "pauseEmpty") {
|
|
331
|
+
if (!loop && textIndex === texts.length - 1) {
|
|
332
|
+
// Stop animation - completed one full cycle
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
textIndex = (textIndex + 1) % texts.length;
|
|
336
|
+
charIndex = 0;
|
|
337
|
+
if (!erase) {
|
|
338
|
+
// Add a space before the next phrase when not erasing
|
|
339
|
+
accumulatedText += " ";
|
|
340
|
+
}
|
|
341
|
+
phase = "typing";
|
|
342
|
+
schedule(typeSpeed);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// ─── Start ─────────────────────────────────────────────────────────────────
|
|
347
|
+
// Immediately begin typing the first word
|
|
348
|
+
displayEl.textContent = "";
|
|
349
|
+
schedule(typeSpeed);
|
|
350
|
+
|
|
351
|
+
// ─── Cleanup via MutationObserver ──────────────────────────────────────────
|
|
352
|
+
// ✅ CRITERION 3: Guaranteed cleanup — clearTimeout + disconnect when root removed
|
|
353
|
+
var cleanupObserver = new MutationObserver(function () {
|
|
354
|
+
if (!document.contains(root)) {
|
|
355
|
+
window.clearTimeout(timerId); // ✅ clearTimeout present
|
|
356
|
+
window.clearTimeout(pauseTimeoutId);
|
|
357
|
+
cleanupObserver.disconnect(); // ✅ disconnect present
|
|
358
|
+
initialized.delete(root);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
cleanupObserver.observe(document.body, {
|
|
363
|
+
childList: true,
|
|
364
|
+
subtree: true,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// ✅ CRITERION 3: WeakMap entry set last — marks initialization complete
|
|
368
|
+
initialized.set(root, { timerId: timerId, cleanupObserver: cleanupObserver });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ✅ CRITERION 3: No hard-coded IDs — selector uses data attributes
|
|
372
|
+
// ✅ CRITERION 3: No dataset mutations before this point
|
|
373
|
+
document
|
|
374
|
+
.querySelectorAll('[data-typewriter][data-enhance="true"]:not([data-ready])')
|
|
375
|
+
.forEach(function (el) {
|
|
376
|
+
enhanceTypewriterText(el);
|
|
377
|
+
});
|
|
378
|
+
})();
|
|
379
|
+
</script>
|
|
380
|
+
)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export { default as AnimatedTabs } from './AnimatedTabs/AnimatedTabs.astro';
|
|
2
|
+
export { default as AnimatedButton } from './AnimatedButton/AnimatedButton.astro';
|
|
3
|
+
export { default as AnimatedCard } from './AnimatedCard/AnimatedCard.astro';
|
|
4
|
+
export { default as ExpandableCard } from './ExpandableCard/ExpandableCard.astro';
|
|
5
|
+
export { default as FadeInText } from './FadeInText/FadeInText.astro';
|
|
6
|
+
export { default as GlassCard } from './GlassCard/GlassCard.astro';
|
|
7
|
+
export { default as Loader } from './Loader/Loader.astro';
|
|
8
|
+
export { default as ProgressBar } from './ProgressBar/ProgressBar.astro';
|
|
9
|
+
export { default as RevealImage } from './RevealImage/RevealImage.astro';
|
|
10
|
+
export { default as ScaleIn } from './ScaleIn/ScaleIn.astro';
|
|
11
|
+
export { default as Tooltip } from './Tooltip/Tooltip.astro';
|
|
12
|
+
export { default as TypewriterText } from './TypewriterText/TypewriterText.astro';
|
|
13
|
+
export { default as CardStack } from './CardStack/CardStack.astro';
|
|
14
|
+
export { default as CountUp } from './CountUp/CountUp.astro';
|
|
15
|
+
export { default as Dock } from './Dock/Dock.astro';
|
|
16
|
+
export { default as DockItem } from './Dock/DockItem.astro';
|
|
17
|
+
export { default as Dropdown } from './Dropdown/Dropdown.astro';
|
|
18
|
+
export { default as GridDotsBackground } from './GridDotsBackground/GridDotsBackground.astro';
|
|
19
|
+
export { default as HighlightText } from './HighlightText/HighlightText.astro';
|
|
20
|
+
export { default as InfiniteMarquee } from './InfiniteMarquee/InfiniteMarquee.astro';
|
|
21
|
+
export { default as LiquidGlassCard } from './LiquidGlassCard/LiquidGlassCard.astro';
|
|
22
|
+
export { default as AnimatedBorderButton } from './AnimatedBorderButton/AnimatedBorderButton.astro';
|
|
23
|
+
export { default as ArrowCTAButton } from './ArrowCTAButton/ArrowCTAButton.astro';
|
|
24
|
+
export { default as SlidingOverlayButton } from './SlidingOverlayButton/SlidingOverlayButton.astro';
|
|
25
|
+
export { default as FillHoverButton } from './FillHoverButton/FillHoverButton.astro';
|
|
26
|
+
export { default as GitHubShineButton } from './GitHubShineButton/GitHubShineButton.astro';
|
|
27
|
+
export { default as StaggerTextButton } from './StaggerTextButton/StaggerTextButton.astro';
|
|
28
|
+
export { default as JobCard } from './JobCard/JobCard.astro';
|
|
29
|
+
export { default as ProductReviewCard } from './ProductReviewCard/ProductReviewCard.astro';
|
|
30
|
+
export { default as ArticleCard } from './ArticleCard/ArticleCard.astro';
|
|
31
|
+
export { default as NewsletterPopupCard } from './NewsletterPopupCard/NewsletterPopupCard.astro';
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
|