|
8 | 8 | // Configuration |
9 | 9 | // ============================================================================ |
10 | 10 |
|
11 | | - const COLORS = { |
12 | | - // Tachyon logo-inspired colors |
13 | | - tachyonBlue: 0x306998, |
14 | | - tachyonGold: 0xd4a910, |
15 | | - // Docs-matching colors |
16 | | - background: 0xffffff, |
17 | | - panelBg: 0xffffff, |
18 | | - borderLight: 0xe1e4e8, |
19 | | - borderHighlight: 0x306998, |
20 | | - textPrimary: 0x0d0d0d, |
21 | | - textSecondary: 0x505050, |
22 | | - textDim: 0x6e6e6e, |
23 | | - // Function colors - blue/gold theme |
24 | | - funcMain: 0x306998, |
25 | | - funcFibonacci: 0xd4a910, |
26 | | - active: 0xfff9e6, |
27 | | - activeText: 0x856404, |
28 | | - success: 0x388e3c, |
29 | | - warning: 0xe65100, |
30 | | - error: 0xc62828, |
31 | | - info: 0x306998, |
32 | | - samplingAccent: 0x00897b, |
33 | | - tracingAccent: 0xd4a910, |
34 | | - overheadLow: 0x388e3c, |
35 | | - overheadMedium: 0xe65100, |
36 | | - overheadHigh: 0xc62828, |
37 | | - }; |
38 | | - |
39 | 11 | const TIMINGS = { |
40 | | - frameSlideIn: 500, |
41 | | - frameSlideOut: 300, |
42 | | - frameFadeOut: 200, |
43 | | - sampleFlash: 200, |
44 | 12 | sampleIntervalMin: 100, |
45 | 13 | sampleIntervalMax: 500, |
46 | 14 | sampleIntervalDefault: 200, |
47 | 15 | sampleToFlame: 600, |
48 | | - flameGrowth: 300, |
49 | | - hookDelay: 10, |
50 | | - eventLightDuration: 150, |
51 | | - speeds: [0.1, 0.25, 0.5, 1, 2, 5], |
52 | 16 | defaultSpeed: 0.05, |
53 | 17 | }; |
54 | 18 |
|
55 | | - const LAYOUT = { |
56 | | - frameWidth: 200, |
57 | | - frameHeight: 40, |
58 | | - frameSpacing: 6, |
59 | | - frameRadius: 4, |
60 | | - codePanelWidth: 0.3, |
61 | | - stackPanelWidth: 0.4, |
62 | | - timelinePanelWidth: 0.3, |
63 | | - flameNodeHeight: 30, |
64 | | - flameMaxDepth: 20, |
65 | | - }; |
| 19 | + const LAYOUT = { frameSpacing: 6 }; |
66 | 20 |
|
67 | | - // ============================================================================ |
68 | | - // Color Utilities |
69 | | - // ============================================================================ |
| 21 | + // Function name to color mapping |
| 22 | + const FUNCTION_COLORS = { |
| 23 | + main: "#306998", |
| 24 | + fibonacci: "#D4A910", |
| 25 | + add: "#E65100", |
| 26 | + multiply: "#7B1FA2", |
| 27 | + calculate: "#D4A910", |
| 28 | + }; |
| 29 | + const DEFAULT_FUNCTION_COLOR = "#306998"; |
70 | 30 |
|
71 | | - function hexToCSS(hex) { |
72 | | - return "#" + hex.toString(16).padStart(6, "0").toUpperCase(); |
73 | | - } |
| 31 | + // Easing functions - cubic-bezier approximations |
| 32 | + const EASING_MAP = { |
| 33 | + linear: "linear", |
| 34 | + easeOutQuad: "cubic-bezier(0.25, 0.46, 0.45, 0.94)", |
| 35 | + easeOutCubic: "cubic-bezier(0.215, 0.61, 0.355, 1)", |
| 36 | + }; |
74 | 37 |
|
75 | 38 | function getFunctionColor(funcName) { |
76 | | - // Blue for main, gold for other functions - matching Tachyon logo |
77 | | - if (funcName === "main") return hexToCSS(COLORS.tachyonBlue); |
78 | | - if (funcName === "fibonacci") return hexToCSS(COLORS.tachyonGold); |
79 | | - if (funcName === "add") return "#E65100"; // Orange |
80 | | - if (funcName === "multiply") return "#7B1FA2"; // Purple |
81 | | - if (funcName === "calculate") return hexToCSS(COLORS.tachyonGold); |
82 | | - return hexToCSS(COLORS.tachyonBlue); |
| 39 | + return FUNCTION_COLORS[funcName] || DEFAULT_FUNCTION_COLOR; |
83 | 40 | } |
84 | 41 |
|
85 | 42 | // ============================================================================ |
|
94 | 51 | to(element, props, duration, easing = "easeOutQuad", onComplete = null) { |
95 | 52 | this.killAnimationsOf(element); |
96 | 53 |
|
97 | | - // Cubic-bezier approximations of Robert Penner's easing equations. |
98 | | - // See: https://easings.net/ for visual references. |
99 | | - // Format: cubic-bezier(x1, y1, x2, y2) defines control points for the curve. |
100 | | - const easingMap = { |
101 | | - linear: "linear", |
102 | | - easeInQuad: "cubic-bezier(0.55, 0.085, 0.68, 0.53)", |
103 | | - easeOutQuad: "cubic-bezier(0.25, 0.46, 0.45, 0.94)", |
104 | | - easeInOutQuad: "cubic-bezier(0.455, 0.03, 0.515, 0.955)", |
105 | | - easeInCubic: "cubic-bezier(0.55, 0.055, 0.675, 0.19)", |
106 | | - easeOutCubic: "cubic-bezier(0.215, 0.61, 0.355, 1)", |
107 | | - easeInOutCubic: "cubic-bezier(0.645, 0.045, 0.355, 1)", |
108 | | - easeOutElastic: "cubic-bezier(0.68, -0.55, 0.265, 1.55)", |
109 | | - easeOutBack: "cubic-bezier(0.175, 0.885, 0.32, 1.275)", |
110 | | - easeOutBounce: "cubic-bezier(0.68, -0.25, 0.265, 1.25)", |
111 | | - }; |
112 | | - |
113 | | - const cssEasing = easingMap[easing] || easingMap.easeOutQuad; |
| 54 | + const cssEasing = EASING_MAP[easing] || EASING_MAP.easeOutQuad; |
114 | 55 |
|
115 | 56 | const transformProps = {}; |
116 | 57 | const otherProps = {}; |
117 | 58 |
|
118 | 59 | for (const [key, value] of Object.entries(props)) { |
119 | | - if (key === "position" || key === "x" || key === "y") { |
120 | | - if (key === "position") { |
121 | | - if (typeof value.x === "number") transformProps.x = value.x; |
122 | | - if (typeof value.y === "number") transformProps.y = value.y; |
123 | | - } else if (key === "x") { |
124 | | - transformProps.x = value; |
125 | | - } else if (key === "y") { |
126 | | - transformProps.y = value; |
127 | | - } |
| 60 | + if (key === "position") { |
| 61 | + if (typeof value.x === "number") transformProps.x = value.x; |
| 62 | + if (typeof value.y === "number") transformProps.y = value.y; |
| 63 | + } else if (key === "x" || key === "y") { |
| 64 | + transformProps[key] = value; |
128 | 65 | } else if (key === "scale") { |
129 | 66 | transformProps.scale = value; |
130 | 67 | } else if (key === "alpha" || key === "opacity") { |
|
136 | 73 |
|
137 | 74 | const computedStyle = getComputedStyle(element); |
138 | 75 | const matrix = new DOMMatrix(computedStyle.transform); |
| 76 | + const currentScale = Math.sqrt( |
| 77 | + matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21, |
| 78 | + ); |
139 | 79 |
|
140 | | - if (transformProps.x === undefined) transformProps.x = matrix.m41; |
141 | | - if (transformProps.y === undefined) transformProps.y = matrix.m42; |
142 | | - if (transformProps.scale === undefined) { |
143 | | - transformProps.scale = Math.sqrt( |
144 | | - matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21, |
145 | | - ); |
146 | | - } |
| 80 | + transformProps.x ??= matrix.m41; |
| 81 | + transformProps.y ??= matrix.m42; |
| 82 | + transformProps.scale ??= currentScale; |
147 | 83 |
|
148 | 84 | const initialTransform = this._buildTransformString( |
149 | 85 | matrix.m41, |
150 | 86 | matrix.m42, |
151 | | - Math.sqrt(matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21), |
| 87 | + currentScale, |
152 | 88 | ); |
153 | 89 |
|
154 | 90 | const finalTransform = this._buildTransformString( |
155 | | - transformProps.x !== undefined ? transformProps.x : matrix.m41, |
156 | | - transformProps.y !== undefined ? transformProps.y : matrix.m42, |
157 | | - transformProps.scale !== undefined ? transformProps.scale : 1, |
| 91 | + transformProps.x, |
| 92 | + transformProps.y, |
| 93 | + transformProps.scale, |
158 | 94 | ); |
159 | 95 |
|
160 | 96 | const initialKeyframe = { transform: initialTransform }; |
|
337 | 273 | } |
338 | 274 |
|
339 | 275 | _highlightSyntax(line) { |
340 | | - let highlighted = line |
| 276 | + return line |
341 | 277 | .replace(/&/g, "&") |
342 | 278 | .replace(/</g, "<") |
343 | | - .replace(/>/g, ">"); |
344 | | - highlighted = highlighted.replace( |
345 | | - /(f?"[^"]*"|f?'[^']*')/g, |
346 | | - '<span class="string">$1</span>', |
347 | | - ); |
348 | | - highlighted = highlighted.replace( |
349 | | - /(#.*$)/g, |
350 | | - '<span class="comment">$1</span>', |
351 | | - ); |
352 | | - const keywords = |
353 | | - /\b(def|if|elif|else|return|for|in|range|print|__name__|__main__)\b/g; |
354 | | - highlighted = highlighted.replace( |
355 | | - keywords, |
356 | | - '<span class="keyword">$1</span>', |
357 | | - ); |
358 | | - highlighted = highlighted.replace( |
359 | | - /<span class="keyword">def<\/span>\s+(\w+)/g, |
360 | | - '<span class="keyword">def</span> <span class="function">$1</span>', |
361 | | - ); |
362 | | - highlighted = highlighted.replace( |
363 | | - /\b(\d+)\b/g, |
364 | | - '<span class="number">$1</span>', |
365 | | - ); |
366 | | - return highlighted; |
| 279 | + .replace(/>/g, ">") |
| 280 | + .replace(/(f?"[^"]*"|f?'[^']*')/g, '<span class="string">$1</span>') |
| 281 | + .replace(/(#.*$)/g, '<span class="comment">$1</span>') |
| 282 | + .replace( |
| 283 | + /\b(def|if|elif|else|return|for|in|range|print|__name__|__main__)\b/g, |
| 284 | + '<span class="keyword">$1</span>', |
| 285 | + ) |
| 286 | + .replace( |
| 287 | + /<span class="keyword">def<\/span>\s+(\w+)/g, |
| 288 | + '<span class="keyword">def</span> <span class="function">$1</span>', |
| 289 | + ) |
| 290 | + .replace(/\b(\d+)\b/g, '<span class="number">$1</span>'); |
367 | 291 | } |
368 | 292 |
|
369 | 293 | highlightLine(lineNumber) { |
|
390 | 314 | } |
391 | 315 | } |
392 | 316 |
|
393 | | - _scrollToLine(lineElement) { |
394 | | - const containerRect = this.codeContainer.getBoundingClientRect(); |
395 | | - const lineRect = lineElement.getBoundingClientRect(); |
396 | | - const isAbove = lineRect.top < containerRect.top + 50; |
397 | | - const isBelow = lineRect.bottom > containerRect.bottom - 50; |
398 | | - |
399 | | - if (isAbove || isBelow) { |
400 | | - // Scroll within the container only, not the page |
401 | | - const lineTop = lineElement.offsetTop; |
402 | | - const containerHeight = this.codeContainer.clientHeight; |
403 | | - const targetScroll = |
404 | | - lineTop - containerHeight / 2 + lineElement.offsetHeight / 2; |
405 | | - this.codeContainer.scrollTop = Math.max(0, targetScroll); |
406 | | - } |
407 | | - } |
408 | | - |
409 | 317 | reset() { |
410 | 318 | this.highlightLine(null); |
411 | 319 | this.codeContainer.scrollTop = 0; |
|
432 | 340 | this.element.className = "stack-frame"; |
433 | 341 | this.element.dataset.function = functionName; |
434 | 342 |
|
435 | | - const bg = document.createElement("div"); |
436 | | - bg.className = "stack-frame-bg"; |
437 | | - bg.style.backgroundColor = this.color; |
438 | | - this.element.appendChild(bg); |
| 343 | + this.bgElement = document.createElement("div"); |
| 344 | + this.bgElement.className = "stack-frame-bg"; |
| 345 | + this.bgElement.style.backgroundColor = this.color; |
| 346 | + this.element.appendChild(this.bgElement); |
439 | 347 |
|
440 | 348 | this.textElement = document.createElement("span"); |
441 | 349 | this.textElement.className = "stack-frame-text"; |
|
451 | 359 | } |
452 | 360 |
|
453 | 361 | destroy() { |
454 | | - if (this.element.parentNode) { |
455 | | - this.element.parentNode.removeChild(this.element); |
456 | | - } |
| 362 | + this.element.parentNode?.removeChild(this.element); |
457 | 363 | } |
458 | 364 |
|
459 | 365 | updateLine(lineno) { |
|
464 | 370 | setActive(isActive) { |
465 | 371 | if (this.isActive === isActive) return; |
466 | 372 | this.isActive = isActive; |
467 | | - const bg = this.element.querySelector(".stack-frame-bg"); |
468 | | - bg.style.opacity = isActive ? "1.0" : "0.9"; |
| 373 | + this.bgElement.style.opacity = isActive ? "1.0" : "0.9"; |
469 | 374 | } |
470 | 375 |
|
471 | 376 | _onHover() { |
472 | | - const bg = this.element.querySelector(".stack-frame-bg"); |
473 | | - bg.style.opacity = "0.8"; |
| 377 | + this.bgElement.style.opacity = "0.8"; |
474 | 378 | } |
475 | 379 |
|
476 | 380 | _onHoverOut() { |
477 | | - const bg = this.element.querySelector(".stack-frame-bg"); |
478 | | - bg.style.opacity = this.isActive ? "1.0" : "0.9"; |
| 381 | + this.bgElement.style.opacity = this.isActive ? "1.0" : "0.9"; |
479 | 382 | } |
480 | 383 |
|
481 | 384 | flash(duration = 150) { |
|
960 | 863 | this.container.appendChild(this.flashOverlay); |
961 | 864 | } |
962 | 865 |
|
963 | | - isAnimating() { |
964 | | - return this.flyingAnimationInProgress; |
965 | | - } |
966 | | - |
967 | 866 | triggerSamplingEffect(stackViz, samplingPanel, currentTime, trace) { |
968 | 867 | if (this.flyingAnimationInProgress) return; |
969 | 868 |
|
|
0 commit comments