diff --git a/ui/desktop/package-lock.json b/ui/desktop/package-lock.json index 425d25e220..008375acf1 100644 --- a/ui/desktop/package-lock.json +++ b/ui/desktop/package-lock.json @@ -38,7 +38,6 @@ "electron-updater": "^6.6.2", "electron-window-state": "^5.0.3", "express": "^5.1.0", - "gsap": "^3.13.0", "lodash": "^4.17.21", "lucide-react": "^0.546.0", "react": "^19.2.0", @@ -11550,12 +11549,6 @@ "dev": true, "license": "MIT" }, - "node_modules/gsap": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.13.0.tgz", - "integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==", - "license": "Standard 'no charge' license: https://gsap.com/standard-license." - }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", diff --git a/ui/desktop/package.json b/ui/desktop/package.json index 5530a27394..ee19c02908 100644 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -68,7 +68,6 @@ "electron-updater": "^6.6.2", "electron-window-state": "^5.0.3", "express": "^5.1.0", - "gsap": "^3.13.0", "lodash": "^4.17.21", "lucide-react": "^0.546.0", "react": "^19.2.0", diff --git a/ui/desktop/src/hooks/use-text-animator.tsx b/ui/desktop/src/hooks/use-text-animator.tsx index bc8291a4cc..6f848b92b2 100644 --- a/ui/desktop/src/hooks/use-text-animator.tsx +++ b/ui/desktop/src/hooks/use-text-animator.tsx @@ -1,4 +1,3 @@ -import { gsap } from 'gsap'; import SplitType from 'split-type'; import { useEffect, useRef } from 'react'; @@ -110,6 +109,8 @@ export class TextAnimator { textElement: HTMLElement; splitter!: TextSplitter; originalChars!: string[]; + activeAnimations: globalThis.Animation[] = []; + activeTimeouts: ReturnType[] = []; constructor(textElement: HTMLElement) { if (!textElement || !(textElement instanceof HTMLElement)) { @@ -124,7 +125,7 @@ export class TextAnimator { this.splitter = new TextSplitter(this.textElement, { splitTypeTypes: ['words', 'chars'], }); - this.originalChars = this.splitter.getChars().map((char) => char.innerHTML); + this.originalChars = this.splitter.getChars().map((char) => char.textContent || ''); } animate() { @@ -133,64 +134,83 @@ export class TextAnimator { const chars = this.splitter.getChars(); chars.forEach((char, position) => { - const initialHTML = char.innerHTML; - let repeatCount = 0; + const initialText = char.textContent || ''; - // Set initial state - gsap.set(char, { - opacity: 1, - display: 'inline-block', - position: 'relative', - }); + char.style.opacity = '1'; + char.style.display = 'inline-block'; + char.style.position = 'relative'; - gsap.fromTo( - char, + const animation = char.animate( + [ + { + opacity: 1, + color: '#666', + fontFamily: 'Cash Sans Mono', + fontWeight: '300', + }, + { + opacity: 0.5, + color: '#999', + }, + { + opacity: 1, + color: 'inherit', + fontFamily: 'inherit', + fontWeight: 'inherit', + }, + ], { - opacity: 1, - }, - { - duration: 0.1, // Increased duration - ease: 'power2.out', - onStart: () => { - gsap.set(char, { - fontFamily: 'Cash Sans Mono', - fontWeight: 300, - color: '#666', // Add color change - }); - }, - onComplete: () => { - gsap.set(char, { - innerHTML: initialHTML, - color: '', - fontFamily: '', - opacity: 1, - }); - }, - repeat: 2, // Reduced repeats - onRepeat: () => { - repeatCount++; - if (repeatCount === 1) { - gsap.set(char, { - opacity: 0.5, - color: '#999', - }); - } - }, - repeatRefresh: true, - repeatDelay: 0.05, // Increased delay - delay: position * 0.03, // Reduced delay between chars - innerHTML: () => lettersAndSymbols[Math.floor(Math.random() * lettersAndSymbols.length)], - opacity: 1, + duration: 300, // Total duration for all iterations + easing: 'ease-in-out', + delay: position * 30, // Stagger the start of each animation + iterations: 1, } ); + + this.activeAnimations.push(animation); + + let iteration = 0; + const maxIterations = 2; + + const animateCharacterChange = () => { + if (iteration < maxIterations) { + char.textContent = + lettersAndSymbols[Math.floor(Math.random() * lettersAndSymbols.length)]; + const timeoutId = setTimeout(animateCharacterChange, 100); + this.activeTimeouts.push(timeoutId); + iteration++; + } else { + char.textContent = initialText; + } + }; + + const timeoutId = setTimeout(animateCharacterChange, position * 30); + this.activeTimeouts.push(timeoutId); + + animation.onfinish = () => { + char.textContent = initialText; + char.style.color = ''; + char.style.fontFamily = ''; + char.style.opacity = '1'; + }; }); } reset() { + // Clear all timeouts + this.activeTimeouts.forEach((timeoutId) => clearTimeout(timeoutId)); + this.activeTimeouts = []; + + // Cancel all animations + this.activeAnimations.forEach((animation) => animation.cancel()); + this.activeAnimations = []; + + // Reset text content const chars = this.splitter.getChars(); chars.forEach((char, index) => { - gsap.killTweensOf(char); - char.innerHTML = this.originalChars[index]; + if (this.originalChars[index]) { + char.textContent = this.originalChars[index]; + } }); } }