1+ /**
2+ * @fileoverview Theme transitions with animations for Socket CLI.
3+ */
4+
5+ import { stdout } from 'node:process'
6+
7+ import colors from 'yoctocolors-cjs'
8+
9+ import { getContextTheme , getTheme , getThemeByName , getTransitionConfig , setTheme } from './theme.mts'
10+
11+ import type { Theme } from './theme.mts'
12+
13+ /**
14+ * Current context stack
15+ */
16+ const contextStack : string [ ] = [ ]
17+
18+ /**
19+ * Animation frame duration in milliseconds
20+ */
21+ const FRAME_DURATION = 50
22+
23+
24+ /**
25+ * Create transition animation frames
26+ */
27+ function * createTransitionFrames (
28+ fromTheme : Theme ,
29+ toTheme : Theme ,
30+ message : string
31+ ) : Generator < string , void , unknown > {
32+ const config = getTransitionConfig ( )
33+ const duration = config . duration || 500
34+ const steps = Math . floor ( duration / FRAME_DURATION )
35+
36+ for ( let i = 0 ; i <= steps ; i ++ ) {
37+ const progress = i / steps
38+
39+ // Create a gradual color shift effect
40+ let output = ''
41+
42+ // Apply different effects based on progress
43+ if ( progress < 0.2 ) {
44+ // Fade out phase
45+ output = colors . dim ( fromTheme . primary ( message ) )
46+ } else if ( progress < 0.4 ) {
47+ // Glitch effect
48+ const glitched = message . split ( '' ) . map ( ( char ) => {
49+ if ( Math . random ( ) < 0.3 ) {
50+ return colors . gray ( char )
51+ }
52+ return fromTheme . primary ( char )
53+ } ) . join ( '' )
54+ output = glitched
55+ } else if ( progress < 0.6 ) {
56+ // Color mixing phase
57+ output = colors . strikethrough ( fromTheme . primary ( message ) )
58+ } else if ( progress < 0.8 ) {
59+ // Glitch into new color
60+ const glitched = message . split ( '' ) . map ( ( char ) => {
61+ if ( Math . random ( ) < 0.3 ) {
62+ return toTheme . primary ( char )
63+ }
64+ return colors . gray ( char )
65+ } ) . join ( '' )
66+ output = glitched
67+ } else {
68+ // Fade in phase
69+ output = toTheme . primary ( message )
70+ }
71+
72+ yield output
73+ }
74+ }
75+
76+ /**
77+ * Animate theme transition
78+ */
79+ async function animateTransition (
80+ fromTheme : Theme ,
81+ toTheme : Theme ,
82+ message : string = '⚡ Switching context...'
83+ ) : Promise < void > {
84+ const frames = createTransitionFrames ( fromTheme , toTheme , message )
85+
86+ // Clear line
87+ stdout . write ( '\r\x1b[K' )
88+
89+ for ( const frame of frames ) {
90+ stdout . write ( `\r${ frame } ` )
91+ await new Promise ( resolve => setTimeout ( resolve , FRAME_DURATION ) )
92+ }
93+
94+ // Clear the transition message
95+ stdout . write ( '\r\x1b[K' )
96+ }
97+
98+ /**
99+ * Push a new context and transition theme
100+ */
101+ export async function pushContext (
102+ context : 'python' | 'firewall' | 'coana' ,
103+ animate : boolean = true
104+ ) : Promise < void > {
105+ const currentTheme = getTheme ( )
106+ const targetThemeName = getContextTheme ( context )
107+ const config = getTransitionConfig ( )
108+
109+ if ( ! targetThemeName ) {
110+ return
111+ }
112+
113+ contextStack . push ( context )
114+
115+ if ( animate && config . enabled ) {
116+ const targetTheme = getThemeByName ( targetThemeName )
117+ if ( targetTheme ) {
118+ const animation = config . animations [ context ]
119+ const message = animation ?. enterMessage ||
120+ ( context === 'python' ? '🐍 Entering Python context...' : '🔥 Activating Socket Firewall...' )
121+
122+ await animateTransition ( currentTheme , targetTheme , message )
123+ }
124+ }
125+
126+ setTheme ( targetThemeName )
127+ }
128+
129+ /**
130+ * Pop context and restore previous theme
131+ */
132+ export async function popContext ( animate : boolean = true ) : Promise < void > {
133+ if ( contextStack . length === 0 ) {
134+ return
135+ }
136+
137+ const currentTheme = getTheme ( )
138+ const poppedContext = contextStack . pop ( )
139+ const config = getTransitionConfig ( )
140+
141+ const previousContext = contextStack . length > 0
142+ ? contextStack [ contextStack . length - 1 ] as 'python' | 'firewall' | 'coana'
143+ : 'default'
144+
145+ const targetThemeName = getContextTheme ( previousContext )
146+
147+ if ( animate && config . enabled ) {
148+ const targetTheme = getThemeByName ( targetThemeName )
149+ if ( targetTheme ) {
150+ const animation = poppedContext ? config . animations [ poppedContext ] : undefined
151+ const message = animation ?. exitMessage || '↩️ Returning to Socket CLI...'
152+ await animateTransition ( currentTheme , targetTheme , message )
153+ }
154+ }
155+
156+ setTheme ( targetThemeName )
157+ }
158+
159+ /**
160+ * Context-aware command wrapper
161+ */
162+ export async function withContext < T > (
163+ context : 'python' | 'firewall' | 'coana' ,
164+ callback : ( ) => Promise < T > ,
165+ animate : boolean = true
166+ ) : Promise < T > {
167+ await pushContext ( context , animate )
168+
169+ try {
170+ return await callback ( )
171+ } finally {
172+ await popContext ( animate )
173+ }
174+ }
175+
176+
177+ /**
178+ * Color wave animation for special events
179+ */
180+ export async function colorWave (
181+ text : string ,
182+ duration : number = 2000
183+ ) : Promise < void > {
184+ const waveColors = [
185+ colors . red ,
186+ colors . magenta ,
187+ colors . blue ,
188+ colors . cyan ,
189+ colors . green ,
190+ colors . yellow ,
191+ ]
192+
193+ const steps = Math . floor ( duration / FRAME_DURATION )
194+
195+ for ( let step = 0 ; step < steps ; step ++ ) {
196+ const chars = text . split ( '' ) . map ( ( char , i ) => {
197+ const colorIndex = ( i + step ) % waveColors . length
198+ return waveColors [ colorIndex ] ! ( char )
199+ } ) . join ( '' )
200+
201+ stdout . write ( `\r${ chars } ` )
202+ await new Promise ( resolve => setTimeout ( resolve , FRAME_DURATION ) )
203+ }
204+
205+ stdout . write ( '\r\x1b[K' )
206+ }
207+
208+ /**
209+ * Pulse animation for alerts
210+ */
211+ export async function pulse (
212+ text : string ,
213+ color : ( text : string ) => string ,
214+ pulses : number = 3
215+ ) : Promise < void > {
216+ for ( let i = 0 ; i < pulses ; i ++ ) {
217+ // Bright
218+ stdout . write ( `\r${ color ( text ) } ` )
219+ await new Promise ( resolve => setTimeout ( resolve , 200 ) )
220+
221+ // Dim
222+ stdout . write ( `\r${ colors . dim ( color ( text ) ) } ` )
223+ await new Promise ( resolve => setTimeout ( resolve , 200 ) )
224+ }
225+
226+ // Final bright state
227+ stdout . write ( `\r${ color ( text ) } \n` )
228+ }
0 commit comments