@staticcanvas/typeflow 0.34.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 (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +637 -0
  3. package/branding/dist/ico/typeflow-logo-16x16.ico +0 -0
  4. package/branding/dist/ico/typeflow-logo-256x256.ico +0 -0
  5. package/branding/dist/ico/typeflow-logo-32x32.ico +0 -0
  6. package/branding/dist/ico/typeflow-logo-48x48.ico +0 -0
  7. package/branding/dist/png/typeflow-logo-1024x1024.png +0 -0
  8. package/branding/dist/png/typeflow-logo-128x128.png +0 -0
  9. package/branding/dist/png/typeflow-logo-16x16.png +0 -0
  10. package/branding/dist/png/typeflow-logo-256x256.png +0 -0
  11. package/branding/dist/png/typeflow-logo-512x512.png +0 -0
  12. package/branding/dist/png/typeflow-logo-64x64.png +0 -0
  13. package/branding/dist/webp/typeflow-logo-1024x1024.webp +0 -0
  14. package/branding/dist/webp/typeflow-logo-128x128.webp +0 -0
  15. package/branding/dist/webp/typeflow-logo-16x16.webp +0 -0
  16. package/branding/dist/webp/typeflow-logo-256x256.webp +0 -0
  17. package/branding/dist/webp/typeflow-logo-32x32.webp +0 -0
  18. package/branding/dist/webp/typeflow-logo-48x48.webp +0 -0
  19. package/branding/dist/webp/typeflow-logo-512x512.webp +0 -0
  20. package/branding/dist/webp/typeflow-logo-64x64.webp +0 -0
  21. package/branding/gen-logo.ps1 +14 -0
  22. package/branding/typeflow-logo.svg +162 -0
  23. package/dist/typeflow/LICENSE +21 -0
  24. package/dist/typeflow/README.md +231 -0
  25. package/dist/typeflow/branding/dist/ico/typeflow-logo-16x16.ico +0 -0
  26. package/dist/typeflow/branding/dist/ico/typeflow-logo-256x256.ico +0 -0
  27. package/dist/typeflow/branding/dist/ico/typeflow-logo-32x32.ico +0 -0
  28. package/dist/typeflow/branding/dist/ico/typeflow-logo-48x48.ico +0 -0
  29. package/dist/typeflow/branding/dist/png/typeflow-logo-1024x1024.png +0 -0
  30. package/dist/typeflow/branding/dist/png/typeflow-logo-128x128.png +0 -0
  31. package/dist/typeflow/branding/dist/png/typeflow-logo-16x16.png +0 -0
  32. package/dist/typeflow/branding/dist/png/typeflow-logo-256x256.png +0 -0
  33. package/dist/typeflow/branding/dist/png/typeflow-logo-512x512.png +0 -0
  34. package/dist/typeflow/branding/dist/png/typeflow-logo-64x64.png +0 -0
  35. package/dist/typeflow/branding/dist/webp/typeflow-logo-1024x1024.webp +0 -0
  36. package/dist/typeflow/branding/dist/webp/typeflow-logo-128x128.webp +0 -0
  37. package/dist/typeflow/branding/dist/webp/typeflow-logo-16x16.webp +0 -0
  38. package/dist/typeflow/branding/dist/webp/typeflow-logo-256x256.webp +0 -0
  39. package/dist/typeflow/branding/dist/webp/typeflow-logo-32x32.webp +0 -0
  40. package/dist/typeflow/branding/dist/webp/typeflow-logo-48x48.webp +0 -0
  41. package/dist/typeflow/branding/dist/webp/typeflow-logo-512x512.webp +0 -0
  42. package/dist/typeflow/branding/dist/webp/typeflow-logo-64x64.webp +0 -0
  43. package/dist/typeflow/branding/gen-logo.ps1 +14 -0
  44. package/dist/typeflow/branding/typeflow-logo.svg +162 -0
  45. package/dist/typeflow/dist/typeflow-caret.cjs +47 -0
  46. package/dist/typeflow/dist/typeflow-caret.cjs.map +1 -0
  47. package/dist/typeflow/dist/typeflow-caret.esm.js +47 -0
  48. package/dist/typeflow/dist/typeflow-caret.esm.js.map +1 -0
  49. package/dist/typeflow/dist/typeflow-caret.esm.min.js +2 -0
  50. package/dist/typeflow/dist/typeflow-caret.esm.min.js.map +1 -0
  51. package/dist/typeflow/dist/typeflow-caret.js +47 -0
  52. package/dist/typeflow/dist/typeflow-caret.js.map +1 -0
  53. package/dist/typeflow/dist/typeflow-caret.min.js +2 -0
  54. package/dist/typeflow/dist/typeflow-caret.min.js.map +1 -0
  55. package/dist/typeflow/dist/typeflow-caret.mjs +47 -0
  56. package/dist/typeflow/dist/typeflow-caret.mjs.map +1 -0
  57. package/dist/typeflow/dist/typeflow-debug.cjs +109 -0
  58. package/dist/typeflow/dist/typeflow-debug.cjs.map +1 -0
  59. package/dist/typeflow/dist/typeflow-debug.esm.js +109 -0
  60. package/dist/typeflow/dist/typeflow-debug.esm.js.map +1 -0
  61. package/dist/typeflow/dist/typeflow-debug.esm.min.js +2 -0
  62. package/dist/typeflow/dist/typeflow-debug.esm.min.js.map +1 -0
  63. package/dist/typeflow/dist/typeflow-debug.js +109 -0
  64. package/dist/typeflow/dist/typeflow-debug.js.map +1 -0
  65. package/dist/typeflow/dist/typeflow-debug.min.js +2 -0
  66. package/dist/typeflow/dist/typeflow-debug.min.js.map +1 -0
  67. package/dist/typeflow/dist/typeflow-debug.mjs +109 -0
  68. package/dist/typeflow/dist/typeflow-debug.mjs.map +1 -0
  69. package/dist/typeflow/dist/typeflow-extchars.cjs +66 -0
  70. package/dist/typeflow/dist/typeflow-extchars.cjs.map +1 -0
  71. package/dist/typeflow/dist/typeflow-extchars.esm.js +66 -0
  72. package/dist/typeflow/dist/typeflow-extchars.esm.js.map +1 -0
  73. package/dist/typeflow/dist/typeflow-extchars.esm.min.js +2 -0
  74. package/dist/typeflow/dist/typeflow-extchars.esm.min.js.map +1 -0
  75. package/dist/typeflow/dist/typeflow-extchars.js +66 -0
  76. package/dist/typeflow/dist/typeflow-extchars.js.map +1 -0
  77. package/dist/typeflow/dist/typeflow-extchars.min.js +2 -0
  78. package/dist/typeflow/dist/typeflow-extchars.min.js.map +1 -0
  79. package/dist/typeflow/dist/typeflow-extchars.mjs +66 -0
  80. package/dist/typeflow/dist/typeflow-extchars.mjs.map +1 -0
  81. package/dist/typeflow/dist/typeflow-keystroke.cjs +47 -0
  82. package/dist/typeflow/dist/typeflow-keystroke.cjs.map +1 -0
  83. package/dist/typeflow/dist/typeflow-keystroke.esm.js +47 -0
  84. package/dist/typeflow/dist/typeflow-keystroke.esm.js.map +1 -0
  85. package/dist/typeflow/dist/typeflow-keystroke.esm.min.js +2 -0
  86. package/dist/typeflow/dist/typeflow-keystroke.esm.min.js.map +1 -0
  87. package/dist/typeflow/dist/typeflow-keystroke.js +47 -0
  88. package/dist/typeflow/dist/typeflow-keystroke.js.map +1 -0
  89. package/dist/typeflow/dist/typeflow-keystroke.min.js +2 -0
  90. package/dist/typeflow/dist/typeflow-keystroke.min.js.map +1 -0
  91. package/dist/typeflow/dist/typeflow-keystroke.mjs +47 -0
  92. package/dist/typeflow/dist/typeflow-keystroke.mjs.map +1 -0
  93. package/dist/typeflow/dist/typeflow-metrics.cjs +76 -0
  94. package/dist/typeflow/dist/typeflow-metrics.cjs.map +1 -0
  95. package/dist/typeflow/dist/typeflow-metrics.esm.js +76 -0
  96. package/dist/typeflow/dist/typeflow-metrics.esm.js.map +1 -0
  97. package/dist/typeflow/dist/typeflow-metrics.esm.min.js +2 -0
  98. package/dist/typeflow/dist/typeflow-metrics.esm.min.js.map +1 -0
  99. package/dist/typeflow/dist/typeflow-metrics.js +76 -0
  100. package/dist/typeflow/dist/typeflow-metrics.js.map +1 -0
  101. package/dist/typeflow/dist/typeflow-metrics.min.js +2 -0
  102. package/dist/typeflow/dist/typeflow-metrics.min.js.map +1 -0
  103. package/dist/typeflow/dist/typeflow-metrics.mjs +76 -0
  104. package/dist/typeflow/dist/typeflow-metrics.mjs.map +1 -0
  105. package/dist/typeflow/dist/typeflow-parallel.cjs +3 -0
  106. package/dist/typeflow/dist/typeflow-parallel.cjs.map +1 -0
  107. package/dist/typeflow/dist/typeflow-parallel.esm.js +3 -0
  108. package/dist/typeflow/dist/typeflow-parallel.esm.js.map +1 -0
  109. package/dist/typeflow/dist/typeflow-parallel.esm.min.js +2 -0
  110. package/dist/typeflow/dist/typeflow-parallel.esm.min.js.map +1 -0
  111. package/dist/typeflow/dist/typeflow-parallel.js +3 -0
  112. package/dist/typeflow/dist/typeflow-parallel.js.map +1 -0
  113. package/dist/typeflow/dist/typeflow-parallel.min.js +2 -0
  114. package/dist/typeflow/dist/typeflow-parallel.min.js.map +1 -0
  115. package/dist/typeflow/dist/typeflow-parallel.mjs +3 -0
  116. package/dist/typeflow/dist/typeflow-parallel.mjs.map +1 -0
  117. package/dist/typeflow/dist/typeflow-webaudio.cjs +81 -0
  118. package/dist/typeflow/dist/typeflow-webaudio.cjs.map +1 -0
  119. package/dist/typeflow/dist/typeflow-webaudio.esm.js +81 -0
  120. package/dist/typeflow/dist/typeflow-webaudio.esm.js.map +1 -0
  121. package/dist/typeflow/dist/typeflow-webaudio.esm.min.js +2 -0
  122. package/dist/typeflow/dist/typeflow-webaudio.esm.min.js.map +1 -0
  123. package/dist/typeflow/dist/typeflow-webaudio.js +81 -0
  124. package/dist/typeflow/dist/typeflow-webaudio.js.map +1 -0
  125. package/dist/typeflow/dist/typeflow-webaudio.min.js +2 -0
  126. package/dist/typeflow/dist/typeflow-webaudio.min.js.map +1 -0
  127. package/dist/typeflow/dist/typeflow-webaudio.mjs +81 -0
  128. package/dist/typeflow/dist/typeflow-webaudio.mjs.map +1 -0
  129. package/dist/typeflow/dist/typeflow.cjs +277 -0
  130. package/dist/typeflow/dist/typeflow.cjs.map +1 -0
  131. package/dist/typeflow/dist/typeflow.esm.js +277 -0
  132. package/dist/typeflow/dist/typeflow.esm.js.map +1 -0
  133. package/dist/typeflow/dist/typeflow.esm.min.js +2 -0
  134. package/dist/typeflow/dist/typeflow.esm.min.js.map +1 -0
  135. package/dist/typeflow/dist/typeflow.js +277 -0
  136. package/dist/typeflow/dist/typeflow.js.map +1 -0
  137. package/dist/typeflow/dist/typeflow.min.js +2 -0
  138. package/dist/typeflow/dist/typeflow.min.js.map +1 -0
  139. package/dist/typeflow/dist/typeflow.mjs +277 -0
  140. package/dist/typeflow/dist/typeflow.mjs.map +1 -0
  141. package/dist/typeflow/jsr.jsonc +27 -0
  142. package/dist/typeflow/package.json +79 -0
  143. package/dist/typeflow/src/typeflow-audio-presets.js +137 -0
  144. package/dist/typeflow/src/typeflow-caret.d.ts +65 -0
  145. package/dist/typeflow/src/typeflow-caret.js +303 -0
  146. package/dist/typeflow/src/typeflow-debug.d.ts +34 -0
  147. package/dist/typeflow/src/typeflow-debug.js +223 -0
  148. package/dist/typeflow/src/typeflow-extchars.d.ts +28 -0
  149. package/dist/typeflow/src/typeflow-extchars.js +132 -0
  150. package/dist/typeflow/src/typeflow-keystroke.d.ts +28 -0
  151. package/dist/typeflow/src/typeflow-keystroke.js +220 -0
  152. package/dist/typeflow/src/typeflow-metrics.d.ts +38 -0
  153. package/dist/typeflow/src/typeflow-metrics.js +556 -0
  154. package/dist/typeflow/src/typeflow-parallel.d.ts +36 -0
  155. package/dist/typeflow/src/typeflow-parallel.js +157 -0
  156. package/dist/typeflow/src/typeflow-webaudio.d.ts +83 -0
  157. package/dist/typeflow/src/typeflow-webaudio.js +517 -0
  158. package/dist/typeflow/src/typeflow.d.ts +328 -0
  159. package/dist/typeflow/src/typeflow.js +2119 -0
  160. package/jsr.jsonc +27 -0
  161. package/package.json +133 -0
  162. package/src/typeflow-audio-presets.js +137 -0
  163. package/src/typeflow-caret.d.ts +65 -0
  164. package/src/typeflow-caret.js +303 -0
  165. package/src/typeflow-debug.d.ts +34 -0
  166. package/src/typeflow-debug.js +223 -0
  167. package/src/typeflow-extchars.d.ts +28 -0
  168. package/src/typeflow-extchars.js +132 -0
  169. package/src/typeflow-keystroke.d.ts +28 -0
  170. package/src/typeflow-keystroke.js +220 -0
  171. package/src/typeflow-metrics.d.ts +38 -0
  172. package/src/typeflow-metrics.js +556 -0
  173. package/src/typeflow-parallel.d.ts +36 -0
  174. package/src/typeflow-parallel.js +157 -0
  175. package/src/typeflow-webaudio.d.ts +83 -0
  176. package/src/typeflow-webaudio.js +517 -0
  177. package/src/typeflow.d.ts +328 -0
  178. package/src/typeflow.js +2119 -0
@@ -0,0 +1,556 @@
1
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
2
+ * @description TypeFlow Metrics - Real-time Performance HUD & Diagnostic Tooltip.
3
+ * Mounts an accessible, zero-CLS floating metrics badge/window in the viewport
4
+ * tracking active animations, FPS, typed/erased units, render latency, and heap memory.
5
+ *
6
+ * @module @staticcanvas/typeflow/metrics
7
+ */
8
+
9
+ export class TypeFlowMetrics {
10
+ static instance = null;
11
+
12
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
13
+ * @description Constructs and initializes a TypeFlowMetrics tracker.
14
+ * @param {Object} [options={}] - Configuration options.
15
+ *
16
+ * -------------
17
+ *
18
+ * @Properties
19
+ * @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.
20
+ * @param {boolean} [options.detailed=false] - Whether to start with the expanded modal open.
21
+ * @param {boolean} [options.autoTrack=true] - Whether to observe global DOM mutations and animations.
22
+ * @param {number} [options.updateInterval=250] - Metric polling interval in milliseconds.
23
+ * @param {number} [options.zIndex=99999] - CSS z-index for floating badge.
24
+ *
25
+ * -------------
26
+ *
27
+ * @example
28
+ * const metrics = new TypeFlowMetrics({ position: 'bottom-right', detailed: true });
29
+ */
30
+ constructor(options = {}) {
31
+ this.options = {
32
+ position: 'bottom-right',
33
+ detailed: false,
34
+ autoTrack: true,
35
+ updateInterval: 250,
36
+ zIndex: 99999,
37
+ ...options,
38
+ };
39
+
40
+ this.stats = {
41
+ fps: 60,
42
+ activeInstances: 0,
43
+ totalTyped: 0,
44
+ totalErased: 0,
45
+ renderLatencyMs: 0,
46
+ domMutations: 0,
47
+ interruptions: 0,
48
+ startTime: performance.now(),
49
+ lastFrameTime: performance.now(),
50
+ frameCount: 0,
51
+ history: [],
52
+ };
53
+
54
+ this.container = null;
55
+ this.isExpanded = Boolean(this.options.detailed);
56
+ this.rafId = null;
57
+ this.intervalId = null;
58
+ this.observer = null;
59
+
60
+ if (typeof window !== 'undefined') {
61
+ this.init();
62
+ }
63
+ }
64
+
65
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
66
+ * @description Mounts the singleton floating HUD metrics badge into the active browser document.
67
+ * @param {Object} [options={}] - Custom configuration options.
68
+ * @returns {TypeFlowMetrics} The active singleton instance.
69
+ *
70
+ * -------------
71
+ *
72
+ * @Properties
73
+ * @param {'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'} [options.position='bottom-right'] - Screen corner location.
74
+ * @param {boolean} [options.detailed=false] - Whether to open expanded view initially.
75
+ *
76
+ * -------------
77
+ *
78
+ * @example
79
+ * TypeFlowMetrics.mount({ position: 'bottom-right' });
80
+ */
81
+ static mount(options = {}) {
82
+ if (!TypeFlowMetrics.instance) {
83
+ TypeFlowMetrics.instance = new TypeFlowMetrics(options);
84
+ }
85
+ return TypeFlowMetrics.instance;
86
+ }
87
+
88
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
89
+ * @description Unmounts the singleton floating HUD metrics badge and stops all listeners.
90
+ * @returns {void}
91
+ *
92
+ * -------------
93
+ *
94
+ * @example
95
+ * TypeFlowMetrics.unmount();
96
+ */
97
+ static unmount() {
98
+ if (TypeFlowMetrics.instance) {
99
+ TypeFlowMetrics.instance.destroy();
100
+ TypeFlowMetrics.instance = null;
101
+ }
102
+ }
103
+
104
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
105
+ * @description Returns a snapshot object of current performance metrics.
106
+ * @returns {Object | null} Metric snapshot data or null if not mounted.
107
+ *
108
+ * -------------
109
+ *
110
+ * @example
111
+ * const stats = TypeFlowMetrics.getStats();
112
+ * console.log('Active animations:', stats?.activeInstances);
113
+ */
114
+ static getStats() {
115
+ return TypeFlowMetrics.instance ? TypeFlowMetrics.instance.getSnapshot() : null;
116
+ }
117
+
118
+ init() {
119
+ this.createDom();
120
+ this.startFpsTracker();
121
+ this.startDomMutationObserver();
122
+
123
+ this.intervalId = setInterval(() => {
124
+ this.updateMetrics();
125
+ }, this.options.updateInterval);
126
+ }
127
+
128
+ createDom() {
129
+ if (this.container || typeof document === 'undefined') return;
130
+
131
+ this.container = document.createElement('aside');
132
+ this.container.id = 'typeflow-metrics-hud';
133
+ this.container.setAttribute('aria-label', 'TypeFlow Performance Metrics HUD');
134
+ this.container.setAttribute('role', 'region');
135
+
136
+ const posStyles = {
137
+ 'bottom-right': 'bottom: 16px; right: 16px;',
138
+ 'bottom-left': 'bottom: 16px; left: 16px;',
139
+ 'top-right': 'top: 16px; right: 16px;',
140
+ 'top-left': 'top: 16px; left: 16px;',
141
+ };
142
+
143
+ const style = document.createElement('style');
144
+ style.textContent = `
145
+ #typeflow-metrics-hud {
146
+ position: fixed;
147
+ ${posStyles[this.options.position] || posStyles['bottom-right']}
148
+ z-index: ${this.options.zIndex};
149
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
150
+ font-size: 12px;
151
+ color: #f1f5f9;
152
+ user-select: none;
153
+ box-sizing: border-box;
154
+ transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
155
+ }
156
+ #typeflow-metrics-hud * {
157
+ box-sizing: border-box;
158
+ }
159
+ .tf-hud-pill {
160
+ display: inline-flex;
161
+ align-items: center;
162
+ gap: 8px;
163
+ padding: 6px 12px;
164
+ background: rgba(15, 23, 42, 0.85);
165
+ backdrop-filter: blur(12px);
166
+ -webkit-backdrop-filter: blur(12px);
167
+ border: 1px solid rgba(255, 255, 255, 0.12);
168
+ border-radius: 9999px;
169
+ box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.5), 0 0 15px rgba(255, 107, 43, 0.15);
170
+ cursor: pointer;
171
+ transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
172
+ }
173
+ .tf-hud-pill:hover {
174
+ transform: translateY(-2px);
175
+ border-color: rgba(255, 107, 43, 0.5);
176
+ box-shadow: 0 6px 24px -2px rgba(0, 0, 0, 0.6), 0 0 20px rgba(255, 107, 43, 0.3);
177
+ }
178
+ .tf-hud-dot {
179
+ width: 8px;
180
+ height: 8px;
181
+ border-radius: 50%;
182
+ background: #10b981;
183
+ box-shadow: 0 0 8px #10b981;
184
+ transition: background 0.3s ease;
185
+ }
186
+ .tf-hud-dot.active {
187
+ background: #ff6b2b;
188
+ box-shadow: 0 0 10px #ff6b2b;
189
+ animation: tf-hud-pulse 1.5s infinite;
190
+ }
191
+ @keyframes tf-hud-pulse {
192
+ 0%, 100% { transform: scale(1); opacity: 1; }
193
+ 50% { transform: scale(1.25); opacity: 0.7; }
194
+ }
195
+ .tf-hud-mono {
196
+ font-family: 'JetBrains Mono', monospace;
197
+ font-weight: 600;
198
+ letter-spacing: -0.02em;
199
+ }
200
+ .tf-hud-label {
201
+ color: #94a3b8;
202
+ font-size: 11px;
203
+ }
204
+ .tf-hud-modal {
205
+ display: none;
206
+ width: 290px;
207
+ background: rgba(15, 23, 42, 0.94);
208
+ backdrop-filter: blur(16px);
209
+ -webkit-backdrop-filter: blur(16px);
210
+ border: 1px solid rgba(255, 255, 255, 0.12);
211
+ border-radius: 12px;
212
+ box-shadow: 0 20px 40px -4px rgba(0, 0, 0, 0.6), 0 0 25px rgba(255, 107, 43, 0.15);
213
+ padding: 14px;
214
+ margin-top: 8px;
215
+ animation: tf-hud-fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);
216
+ }
217
+ @keyframes tf-hud-fade-in {
218
+ from { opacity: 0; transform: translateY(6px); }
219
+ to { opacity: 1; transform: translateY(0); }
220
+ }
221
+ .tf-hud-modal.open {
222
+ display: block;
223
+ }
224
+ .tf-hud-header {
225
+ display: flex;
226
+ align-items: center;
227
+ justify-content: space-between;
228
+ padding-bottom: 10px;
229
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
230
+ margin-bottom: 10px;
231
+ }
232
+ .tf-hud-title {
233
+ font-family: 'Lexend', sans-serif;
234
+ font-weight: 600;
235
+ font-size: 13px;
236
+ background: linear-gradient(135deg, #ff6b2b, #38bdf8);
237
+ -webkit-background-clip: text;
238
+ -webkit-text-fill-color: transparent;
239
+ }
240
+ .tf-hud-close {
241
+ background: none;
242
+ border: none;
243
+ color: #94a3b8;
244
+ cursor: pointer;
245
+ padding: 2px 6px;
246
+ border-radius: 4px;
247
+ font-size: 14px;
248
+ line-height: 1;
249
+ }
250
+ .tf-hud-close:hover {
251
+ background: rgba(255, 255, 255, 0.1);
252
+ color: #fff;
253
+ }
254
+ .tf-hud-grid {
255
+ display: grid;
256
+ grid-template-columns: 1fr 1fr;
257
+ gap: 8px;
258
+ margin-bottom: 10px;
259
+ }
260
+ .tf-hud-card {
261
+ background: rgba(30, 41, 59, 0.6);
262
+ border: 1px solid rgba(255, 255, 255, 0.05);
263
+ border-radius: 8px;
264
+ padding: 8px 10px;
265
+ }
266
+ .tf-hud-card-label {
267
+ font-size: 10px;
268
+ color: #64748b;
269
+ text-transform: uppercase;
270
+ letter-spacing: 0.05em;
271
+ margin-bottom: 2px;
272
+ }
273
+ .tf-hud-card-val {
274
+ font-family: 'JetBrains Mono', monospace;
275
+ font-size: 14px;
276
+ font-weight: 600;
277
+ color: #e2e8f0;
278
+ }
279
+ .tf-hud-accent {
280
+ color: #ff6b2b;
281
+ }
282
+ .tf-hud-cyan {
283
+ color: #38bdf8;
284
+ }
285
+ .tf-hud-green {
286
+ color: #10b981;
287
+ }
288
+ .tf-hud-footer {
289
+ display: flex;
290
+ align-items: center;
291
+ justify-content: space-between;
292
+ padding-top: 8px;
293
+ border-top: 1px solid rgba(255, 255, 255, 0.06);
294
+ font-size: 10px;
295
+ color: #64748b;
296
+ }
297
+ .tf-hud-btn {
298
+ background: rgba(255, 255, 255, 0.06);
299
+ border: 1px solid rgba(255, 255, 255, 0.1);
300
+ color: #cbd5e1;
301
+ padding: 3px 8px;
302
+ border-radius: 4px;
303
+ font-size: 10px;
304
+ cursor: pointer;
305
+ transition: all 0.15s ease;
306
+ }
307
+ .tf-hud-btn:hover {
308
+ background: rgba(255, 107, 43, 0.2);
309
+ border-color: rgba(255, 107, 43, 0.4);
310
+ color: #ff6b2b;
311
+ }
312
+ `;
313
+
314
+ const pill = document.createElement('div');
315
+ pill.className = 'tf-hud-pill';
316
+ pill.setAttribute('role', 'button');
317
+ pill.setAttribute('tabindex', '0');
318
+ pill.innerHTML = `
319
+ <span class="tf-hud-dot" id="tf-hud-dot"></span>
320
+ <span class="tf-hud-mono" id="tf-hud-fps">60 FPS</span>
321
+ <span class="tf-hud-label">•</span>
322
+ <span class="tf-hud-mono tf-hud-accent" id="tf-hud-active">0 Active</span>
323
+ `;
324
+
325
+ const modal = document.createElement('div');
326
+ modal.className = `tf-hud-modal ${this.isExpanded ? 'open' : ''}`;
327
+ modal.id = 'tf-hud-modal';
328
+ modal.innerHTML = `
329
+ <div class="tf-hud-header">
330
+ <span class="tf-hud-title">TypeFlow Metrics</span>
331
+ <button class="tf-hud-close" id="tf-hud-close" aria-label="Close Metrics Window">✕</button>
332
+ </div>
333
+ <div class="tf-hud-grid">
334
+ <div class="tf-hud-card">
335
+ <div class="tf-hud-card-label">Frame Rate</div>
336
+ <div class="tf-hud-card-val tf-hud-green" id="tf-hud-m-fps">60.0 FPS</div>
337
+ </div>
338
+ <div class="tf-hud-card">
339
+ <div class="tf-hud-card-label">Active Anim</div>
340
+ <div class="tf-hud-card-val tf-hud-accent" id="tf-hud-m-active">0</div>
341
+ </div>
342
+ <div class="tf-hud-card">
343
+ <div class="tf-hud-card-label">Typed Units</div>
344
+ <div class="tf-hud-card-val tf-hud-cyan" id="tf-hud-m-typed">0</div>
345
+ </div>
346
+ <div class="tf-hud-card">
347
+ <div class="tf-hud-card-label">Erased Units</div>
348
+ <div class="tf-hud-card-val" id="tf-hud-m-erased">0</div>
349
+ </div>
350
+ <div class="tf-hud-card">
351
+ <div class="tf-hud-card-label">DOM Mut.</div>
352
+ <div class="tf-hud-card-val" id="tf-hud-m-mut">0</div>
353
+ </div>
354
+ <div class="tf-hud-card">
355
+ <div class="tf-hud-card-label">Heap Memory</div>
356
+ <div class="tf-hud-card-val tf-hud-mono" id="tf-hud-m-mem">-- MB</div>
357
+ </div>
358
+ </div>
359
+ <div class="tf-hud-footer">
360
+ <span id="tf-hud-uptime">Uptime: 0s</span>
361
+ <div style="display:flex;gap:4px;">
362
+ <button class="tf-hud-btn" id="tf-hud-copy">Copy JSON</button>
363
+ <button class="tf-hud-btn" id="tf-hud-reset">Reset</button>
364
+ </div>
365
+ </div>
366
+ `;
367
+
368
+ this.container.appendChild(style);
369
+ this.container.appendChild(pill);
370
+ this.container.appendChild(modal);
371
+ document.body.appendChild(this.container);
372
+
373
+ pill.addEventListener('click', () => this.toggleModal());
374
+ pill.addEventListener('keydown', (e) => {
375
+ if (e.key === 'Enter' || e.key === ' ') {
376
+ e.preventDefault();
377
+ this.toggleModal();
378
+ }
379
+ });
380
+
381
+ modal.querySelector('#tf-hud-close')?.addEventListener('click', () => this.toggleModal(false));
382
+ modal.querySelector('#tf-hud-reset')?.addEventListener('click', () => this.resetStats());
383
+ modal.querySelector('#tf-hud-copy')?.addEventListener('click', () => this.copyStats());
384
+ }
385
+
386
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
387
+ * @description Toggles or sets the visibility of the expanded metrics window modal.
388
+ * @param {boolean} [forceState] - Optional explicit state boolean.
389
+ * @returns {void}
390
+ *
391
+ * -------------
392
+ *
393
+ * @example
394
+ * hud.toggleModal(true); // Open
395
+ * hud.toggleModal(false); // Close
396
+ */
397
+ toggleModal(forceState) {
398
+ this.isExpanded = forceState !== undefined ? forceState : !this.isExpanded;
399
+ const modal = this.container?.querySelector('#tf-hud-modal');
400
+ if (modal) {
401
+ modal.classList.toggle('open', this.isExpanded);
402
+ }
403
+ }
404
+
405
+ startFpsTracker() {
406
+ let lastTime = performance.now();
407
+ let frames = 0;
408
+
409
+ const tick = (now) => {
410
+ frames++;
411
+ if (now >= lastTime + 1000) {
412
+ this.stats.fps = Math.round((frames * 1000) / (now - lastTime));
413
+ frames = 0;
414
+ lastTime = now;
415
+ }
416
+ this.rafId = requestAnimationFrame(tick);
417
+ };
418
+ this.rafId = requestAnimationFrame(tick);
419
+ }
420
+
421
+ startDomMutationObserver() {
422
+ if (typeof MutationObserver === 'undefined' || typeof document === 'undefined') return;
423
+ this.observer = new MutationObserver((mutations) => {
424
+ let relevant = 0;
425
+ for (const m of mutations) {
426
+ if (m.target === this.container || this.container?.contains(m.target)) continue;
427
+ if (m.target.nodeType === 1 && m.target.classList?.contains('tf-cursor')) continue;
428
+ relevant++;
429
+ }
430
+ this.stats.domMutations += relevant;
431
+ });
432
+
433
+ this.observer.observe(document.body, {
434
+ childList: true,
435
+ subtree: true,
436
+ characterData: true,
437
+ });
438
+ }
439
+
440
+ recordTyped(count = 1) {
441
+ this.stats.totalTyped += count;
442
+ }
443
+
444
+ recordErased(count = 1) {
445
+ this.stats.totalErased += count;
446
+ }
447
+
448
+ recordInterruption() {
449
+ this.stats.interruptions += 1;
450
+ }
451
+
452
+ updateMetrics() {
453
+ if (!this.container) return;
454
+
455
+ if (typeof window !== 'undefined' && window.TypeFlow?.active) {
456
+ this.stats.activeInstances = window.TypeFlow.active.size;
457
+ }
458
+
459
+ const dot = this.container.querySelector('#tf-hud-dot');
460
+ const fpsEl = this.container.querySelector('#tf-hud-fps');
461
+ const activeEl = this.container.querySelector('#tf-hud-active');
462
+
463
+ if (dot) dot.classList.toggle('active', this.stats.activeInstances > 0);
464
+ if (fpsEl) fpsEl.textContent = `${this.stats.fps} FPS`;
465
+ if (activeEl) activeEl.textContent = `${this.stats.activeInstances} Active`;
466
+
467
+ if (this.isExpanded) {
468
+ const mFps = this.container.querySelector('#tf-hud-m-fps');
469
+ const mActive = this.container.querySelector('#tf-hud-m-active');
470
+ const mTyped = this.container.querySelector('#tf-hud-m-typed');
471
+ const mErased = this.container.querySelector('#tf-hud-m-erased');
472
+ const mMut = this.container.querySelector('#tf-hud-m-mut');
473
+ const mMem = this.container.querySelector('#tf-hud-m-mem');
474
+ const mUptime = this.container.querySelector('#tf-hud-uptime');
475
+
476
+ if (mFps) mFps.textContent = `${this.stats.fps}.0 FPS`;
477
+ if (mActive) mActive.textContent = `${this.stats.activeInstances}`;
478
+ if (mTyped) mTyped.textContent = this.stats.totalTyped.toLocaleString();
479
+ if (mErased) mErased.textContent = this.stats.totalErased.toLocaleString();
480
+ if (mMut) mMut.textContent = this.stats.domMutations.toLocaleString();
481
+
482
+ if (mMem) {
483
+ if (performance.memory?.usedJSHeapSize) {
484
+ const mb = (performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(1);
485
+ mMem.textContent = `${mb} MB`;
486
+ } else {
487
+ mMem.textContent = 'Native';
488
+ }
489
+ }
490
+
491
+ if (mUptime) {
492
+ const sec = Math.floor((performance.now() - this.stats.startTime) / 1000);
493
+ mUptime.textContent = `Uptime: ${sec}s`;
494
+ }
495
+ }
496
+ }
497
+
498
+ resetStats() {
499
+ this.stats.totalTyped = 0;
500
+ this.stats.totalErased = 0;
501
+ this.stats.domMutations = 0;
502
+ this.stats.interruptions = 0;
503
+ this.stats.startTime = performance.now();
504
+ this.updateMetrics();
505
+ }
506
+
507
+ copyStats() {
508
+ const json = JSON.stringify(this.getSnapshot(), null, 2);
509
+ if (navigator.clipboard?.writeText) {
510
+ navigator.clipboard.writeText(json);
511
+ const btn = this.container?.querySelector('#tf-hud-copy');
512
+ if (btn) {
513
+ const prev = btn.textContent;
514
+ btn.textContent = 'Copied!';
515
+ setTimeout(() => {
516
+ btn.textContent = prev;
517
+ }, 1500);
518
+ }
519
+ }
520
+ }
521
+
522
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
523
+ * @description Returns a snapshot object of lifetime metrics.
524
+ * @returns {Object} Metric data snapshot.
525
+ */
526
+ getSnapshot() {
527
+ return {
528
+ timestamp: new Date().toISOString(),
529
+ fps: this.stats.fps,
530
+ activeInstances: this.stats.activeInstances,
531
+ totalTyped: this.stats.totalTyped,
532
+ totalErased: this.stats.totalErased,
533
+ domMutations: this.stats.domMutations,
534
+ interruptions: this.stats.interruptions,
535
+ uptimeSeconds: Math.floor((performance.now() - this.stats.startTime) / 1000),
536
+ };
537
+ }
538
+
539
+ /** * * * * * * * * * * * * * * * * * * * * * * * * * * **
540
+ * @description Cleans up observers, timers, and removes the HUD from the DOM.
541
+ * @returns {void}
542
+ */
543
+ destroy() {
544
+ if (this.rafId) cancelAnimationFrame(this.rafId);
545
+ if (this.intervalId) clearInterval(this.intervalId);
546
+ if (this.observer) this.observer.disconnect();
547
+ if (this.container?.parentNode) {
548
+ this.container.parentNode.removeChild(this.container);
549
+ }
550
+ this.container = null;
551
+ }
552
+ }
553
+
554
+ if (typeof window !== 'undefined') {
555
+ window.TypeFlowMetrics = TypeFlowMetrics;
556
+ }
@@ -0,0 +1,36 @@
1
+ import type { TypeFlowEngine, TypeFlowResult, TypeFlowStep } from './typeflow.js';
2
+
3
+ export interface TypeFlowParallelConfig {
4
+ concurrency?: number;
5
+ settlement?: 'fail-fast' | 'all-settled';
6
+ target?: HTMLElement | string;
7
+ }
8
+ export type TypeFlowParallelStep =
9
+ | TypeFlowStep
10
+ | { kind: 'parallel'; steps: TypeFlowParallelStep[]; options?: TypeFlowParallelConfig };
11
+ export type TypeFlowParallelStepResult =
12
+ | { status: 'fulfilled' | 'interrupted'; value: TypeFlowResult | TypeFlowParallelResult }
13
+ | { status: 'rejected'; reason: unknown };
14
+ export interface TypeFlowParallelResult extends TypeFlowResult {
15
+ text: '';
16
+ results: TypeFlowParallelStepResult[];
17
+ }
18
+ export interface TypeFlowParallelController {
19
+ stop(): void;
20
+ promise: Promise<TypeFlowParallelResult>;
21
+ }
22
+ export interface ExperimentalTypeFlowEngine extends TypeFlowEngine {
23
+ parallel(
24
+ steps: TypeFlowParallelStep[],
25
+ options?: TypeFlowParallelConfig
26
+ ): TypeFlowParallelController;
27
+ }
28
+ export function parallelStep(
29
+ steps: TypeFlowParallelStep[],
30
+ options?: TypeFlowParallelConfig
31
+ ): TypeFlowParallelStep;
32
+ export function createParallel(engine: TypeFlowEngine): ExperimentalTypeFlowEngine['parallel'];
33
+ export function installParallel(
34
+ engine: TypeFlowEngine,
35
+ sequenceHelpers?: object
36
+ ): ExperimentalTypeFlowEngine;