@@ -18,6 +18,8 @@ const LOGO_LINES = [
1818 ' --===-- -=- -= ' ,
1919] ;
2020
21+ const FRAME_COUNT = 48 ;
22+
2123/**
2224 * Horizontally scale a line of text by a factor (0..1).
2325 * At scale=1, the full line is shown. At scale=0, it collapses to center.
@@ -45,18 +47,37 @@ function scaleLineHorizontally(line: string, scale: number): string {
4547 * by cos(angle) to simulate spinning around the Y axis.
4648 */
4749function generateFrames ( ) : string [ ] [ ] {
48- const frameCount = 48 ;
4950 const frames : string [ ] [ ] = [ ] ;
50- for ( let i = 0 ; i < frameCount ; i ++ ) {
51- const angle = ( i / frameCount ) * 2 * Math . PI ;
51+ for ( let i = 0 ; i < FRAME_COUNT ; i ++ ) {
52+ const angle = ( i / FRAME_COUNT ) * 2 * Math . PI ;
5253 const scale = Math . abs ( Math . cos ( angle ) ) ;
5354 frames . push ( LOGO_LINES . map ( ( line ) => scaleLineHorizontally ( line , scale ) ) ) ;
5455 }
5556 return frames ;
5657}
5758
59+ /**
60+ * Render a single frame of the animation.
61+ */
62+ function renderFrame (
63+ frame : string [ ] ,
64+ lineCount : number ,
65+ isFirst : boolean ,
66+ color : ( s : string ) => string
67+ ) : void {
68+ if ( ! isFirst ) {
69+ process . stdout . write ( `\x1B[${ lineCount } A` ) ;
70+ }
71+ for ( const line of frame ) {
72+ process . stdout . write ( `\x1B[2K${ color ( line ) } \n` ) ;
73+ }
74+ }
75+
76+ /**
77+ * Interactive art command — loops until user presses q/Escape.
78+ * Used by `gt art`.
79+ */
5880export async function handleArt ( ) : Promise < void > {
59- // Require interactive terminal to avoid infinite loop in non-TTY contexts
6081 if ( ! process . stdin . isTTY ) {
6182 console . log ( chalk . yellow ( ' gt art requires an interactive terminal (TTY).' ) ) ;
6283 return ;
@@ -77,10 +98,7 @@ export async function handleArt(): Promise<void> {
7798 } ;
7899
79100 process . stdin . on ( 'data' , onKey ) ;
80-
81- // Hide cursor
82101 process . stdout . write ( '\x1B[?25l' ) ;
83-
84102 console . log ( chalk . dim ( '\n Press q or Escape to exit\n' ) ) ;
85103
86104 const lineCount = LOGO_LINES . length ;
@@ -93,7 +111,6 @@ export async function handleArt(): Promise<void> {
93111 console . log ( ) ;
94112 } ;
95113
96- // Ensure cleanup runs on unexpected termination
97114 const sigHandler = ( ) => {
98115 cleanup ( ) ;
99116 process . exit ( ) ;
@@ -103,18 +120,13 @@ export async function handleArt(): Promise<void> {
103120
104121 try {
105122 while ( running ) {
106- const frame = frames [ frameIndex % frames . length ] ;
107-
108- if ( frameIndex > 0 ) {
109- process . stdout . write ( `\x1B[${ lineCount } A` ) ;
110- }
111-
112- for ( const line of frame ) {
113- process . stdout . write ( `\x1B[2K${ chalk . white ( line ) } \n` ) ;
114- }
115-
123+ renderFrame (
124+ frames [ frameIndex % frames . length ] ,
125+ lineCount ,
126+ frameIndex === 0 ,
127+ chalk . white
128+ ) ;
116129 frameIndex ++ ;
117-
118130 await new Promise ( ( resolve ) => setTimeout ( resolve , 80 ) ) ;
119131 }
120132 } finally {
@@ -125,3 +137,47 @@ export async function handleArt(): Promise<void> {
125137
126138 console . log ( chalk . white ( ' ✨ General Translation' ) ) ;
127139}
140+
141+ /**
142+ * Brief intro animation — plays a fixed number of rotations then stops.
143+ * Used by `gt init` to greet users in interactive mode.
144+ * Falls back to a static logo if not running in a TTY.
145+ */
146+ export async function playIntroAnimation (
147+ rotations : number = 2
148+ ) : Promise < void > {
149+ if ( ! process . stdout . isTTY ) {
150+ // Non-TTY: just print the static logo
151+ console . log ( ) ;
152+ for ( const line of LOGO_LINES ) {
153+ console . log ( chalk . white ( line ) ) ;
154+ }
155+ console . log ( ) ;
156+ return ;
157+ }
158+
159+ const frames = generateFrames ( ) ;
160+ const totalFrames = FRAME_COUNT * rotations ;
161+ const lineCount = LOGO_LINES . length ;
162+
163+ process . stdout . write ( '\x1B[?25l' ) ;
164+ console . log ( ) ;
165+
166+ for ( let i = 0 ; i < totalFrames ; i ++ ) {
167+ renderFrame (
168+ frames [ i % frames . length ] ,
169+ lineCount ,
170+ i === 0 ,
171+ chalk . white
172+ ) ;
173+ await new Promise ( ( resolve ) => setTimeout ( resolve , 80 ) ) ;
174+ }
175+
176+ // Clear the animation and show cursor
177+ process . stdout . write ( `\x1B[${ lineCount } A` ) ;
178+ for ( let i = 0 ; i < lineCount ; i ++ ) {
179+ process . stdout . write ( '\x1B[2K\n' ) ;
180+ }
181+ process . stdout . write ( `\x1B[${ lineCount } A` ) ;
182+ process . stdout . write ( '\x1B[?25h' ) ;
183+ }
0 commit comments