@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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +212 -0
  3. package/dist/components/AnimatedBorderButton/AnimatedBorderButton.astro +129 -0
  4. package/dist/components/AnimatedBorderButton/index.js +3 -0
  5. package/dist/components/AnimatedBorderButton/index.js.map +1 -0
  6. package/dist/components/AnimatedButton/AnimatedButton.astro +299 -0
  7. package/dist/components/AnimatedButton/index.js +3 -0
  8. package/dist/components/AnimatedButton/index.js.map +1 -0
  9. package/dist/components/AnimatedCard/AnimatedCard.astro +832 -0
  10. package/dist/components/AnimatedCard/index.js +3 -0
  11. package/dist/components/AnimatedCard/index.js.map +1 -0
  12. package/dist/components/AnimatedTabs/AnimatedTabs.astro +348 -0
  13. package/dist/components/AnimatedTabs/index.js +3 -0
  14. package/dist/components/AnimatedTabs/index.js.map +1 -0
  15. package/dist/components/ArrowCTAButton/ArrowCTAButton.astro +159 -0
  16. package/dist/components/ArticleCard/ArticleCard.astro +208 -0
  17. package/dist/components/CardStack/CardStack.astro +444 -0
  18. package/dist/components/CardStack/index.js +3 -0
  19. package/dist/components/CardStack/index.js.map +1 -0
  20. package/dist/components/CountUp/CountUp.astro +89 -0
  21. package/dist/components/CountUp/index.js +3 -0
  22. package/dist/components/CountUp/index.js.map +1 -0
  23. package/dist/components/Dock/Dock.astro +567 -0
  24. package/dist/components/Dock/DockItem.astro +135 -0
  25. package/dist/components/Dropdown/Dropdown.astro +264 -0
  26. package/dist/components/ExpandableCard/ExpandableCard.astro +402 -0
  27. package/dist/components/ExpandableCard/index.js +3 -0
  28. package/dist/components/ExpandableCard/index.js.map +1 -0
  29. package/dist/components/FadeInText/FadeInText.astro +314 -0
  30. package/dist/components/FadeInText/index.js +3 -0
  31. package/dist/components/FadeInText/index.js.map +1 -0
  32. package/dist/components/FillHoverButton/FillHoverButton.astro +125 -0
  33. package/dist/components/GitHubShineButton/GitHubShineButton.astro +208 -0
  34. package/dist/components/GlassCard/GlassCard.astro +245 -0
  35. package/dist/components/GlassCard/index.js +3 -0
  36. package/dist/components/GlassCard/index.js.map +1 -0
  37. package/dist/components/GridDotsBackground/GridDotsBackground.astro +144 -0
  38. package/dist/components/HighlightText/HighlightText.astro +106 -0
  39. package/dist/components/InfiniteMarquee/InfiniteMarquee.astro +339 -0
  40. package/dist/components/JobCard/JobCard.astro +230 -0
  41. package/dist/components/LiquidGlassCard/LiquidGlassCard.astro +569 -0
  42. package/dist/components/Loader/Loader.astro +156 -0
  43. package/dist/components/Loader/index.js +3 -0
  44. package/dist/components/Loader/index.js.map +1 -0
  45. package/dist/components/NewsletterPopupCard/NewsletterPopupCard.astro +331 -0
  46. package/dist/components/ProductReviewCard/ProductReviewCard.astro +188 -0
  47. package/dist/components/ProgressBar/ProgressBar.astro +137 -0
  48. package/dist/components/ProgressBar/index.js +3 -0
  49. package/dist/components/ProgressBar/index.js.map +1 -0
  50. package/dist/components/RevealImage/RevealImage.astro +160 -0
  51. package/dist/components/RevealImage/index.js +3 -0
  52. package/dist/components/RevealImage/index.js.map +1 -0
  53. package/dist/components/ScaleIn/ScaleIn.astro +231 -0
  54. package/dist/components/ScaleIn/index.js +3 -0
  55. package/dist/components/ScaleIn/index.js.map +1 -0
  56. package/dist/components/SlidingOverlayButton/SlidingOverlayButton.astro +126 -0
  57. package/dist/components/StaggerTextButton/StaggerTextButton.astro +132 -0
  58. package/dist/components/Tooltip/Tooltip.astro +255 -0
  59. package/dist/components/Tooltip/index.js +3 -0
  60. package/dist/components/Tooltip/index.js.map +1 -0
  61. package/dist/components/TypewriterText/TypewriterText.astro +380 -0
  62. package/dist/components/TypewriterText/index.js +3 -0
  63. package/dist/components/TypewriterText/index.js.map +1 -0
  64. package/dist/components/index.js +33 -0
  65. package/dist/components/index.js.map +1 -0
  66. package/dist/index.js +31 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/internal/countup.js +90 -0
  69. package/dist/internal/countup.js.map +1 -0
  70. package/dist/internal/dropdown.js +166 -0
  71. package/dist/internal/dropdown.js.map +1 -0
  72. package/dist/internal/fadein.js +116 -0
  73. package/dist/internal/fadein.js.map +1 -0
  74. package/dist/internal/guards.js +12 -0
  75. package/dist/internal/guards.js.map +1 -0
  76. package/dist/internal/tabs.js +140 -0
  77. package/dist/internal/tabs.js.map +1 -0
  78. 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,3 @@
1
+ export { default } from './Tooltip.astro';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -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,3 @@
1
+ export { default } from './TypewriterText.astro';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -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":[]}