1- import React , { useState } from 'react' ;
1+ import React , { useState , useRef , useCallback } from 'react' ;
22import { SubtitleSegment } from '@/types' ;
33import { useTheme } from '@/contexts/ThemeContext' ;
44import { useVideoProcessing } from '@/hooks/useVideoProcessing' ;
55import { useSubtitles } from '@/hooks/useSubtitles' ;
66import { generateSRTContent } from '@/lib/utils' ;
77import { Header } from '@/components/Header' ;
8- import { SettingsModal } from '@/components/SettingsModal ' ;
8+ import { MenuSheet } from '@/components/MenuSheet ' ;
99import { UploadZone , handleFileValidation } from '@/components/UploadZone' ;
1010import { VideoPlayer } from '@/components/VideoPlayer' ;
1111import { Transcript } from '@/components/Transcript' ;
@@ -16,8 +16,9 @@ export default function App() {
1616 const [ selectedLangCode , setSelectedLangCode ] = useState < string > ( 'en' ) ;
1717 const [ subtitles , setSubtitles ] = useState < SubtitleSegment [ ] | null > ( null ) ;
1818 const [ videoUrl , setVideoUrl ] = useState < string | null > ( null ) ;
19- const [ showSettings , setShowSettings ] = useState ( false ) ;
19+ const [ showMenu , setShowMenu ] = useState ( false ) ;
2020 const [ isTranslationMode , setIsTranslationMode ] = useState ( true ) ;
21+ const srtInputRef = useRef < HTMLInputElement > ( null ) ;
2122
2223 const { isDark, toggleTheme } = useTheme ( ) ;
2324 const { apiKey, setApiKey, isProcessing, error, setError, processVideo } = useVideoProcessing ( ) ;
@@ -26,10 +27,23 @@ export default function App() {
2627 // Initialize settings on mount
2728 React . useEffect ( ( ) => {
2829 if ( ! apiKey ) {
29- setShowSettings ( true ) ;
30+ setShowMenu ( true ) ;
3031 }
3132 } , [ ] ) ;
3233
34+ // Scroll to transcript section on mobile when video is loaded
35+ React . useEffect ( ( ) => {
36+ if ( videoUrl && window . innerWidth < 1024 ) {
37+ // Small delay to ensure the layout has updated
38+ setTimeout ( ( ) => {
39+ const transcriptSection = document . querySelector ( '[data-transcript-section]' ) ;
40+ if ( transcriptSection ) {
41+ transcriptSection . scrollIntoView ( { behavior : 'smooth' , block : 'start' } ) ;
42+ }
43+ } , 300 ) ;
44+ }
45+ } , [ videoUrl ] ) ;
46+
3347 const handleFileChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
3448 if ( e . target . files && e . target . files [ 0 ] ) {
3549 const selectedFile = e . target . files [ 0 ] ;
@@ -47,7 +61,7 @@ export default function App() {
4761 }
4862 } ;
4963
50- const handleProcess = async ( enableTranslation : boolean = true ) => {
64+ const handleProcess = useCallback ( async ( enableTranslation : boolean = true ) => {
5165 if ( ! file ) return ;
5266
5367 setIsTranslationMode ( enableTranslation ) ;
@@ -58,7 +72,10 @@ export default function App() {
5872 } catch {
5973 // Error is already handled in the hook
6074 }
61- } ;
75+ } , [ file , selectedLangCode , processVideo ] ) ;
76+
77+ const handleProcessSameLanguage = useCallback ( ( ) => handleProcess ( false ) , [ handleProcess ] ) ;
78+ const handleProcessTranslated = useCallback ( ( ) => handleProcess ( true ) , [ handleProcess ] ) ;
6279
6380 const downloadSRT = ( ) => {
6481 if ( ! subtitles ) return ;
@@ -86,82 +103,99 @@ export default function App() {
86103
87104 const handleSaveApiKey = ( key : string ) => {
88105 setApiKey ( key ) ;
89- if ( key ) setShowSettings ( false ) ;
106+ if ( key ) setShowMenu ( false ) ;
90107 } ;
91108
92109 const textPrimary = isDark ? 'text-white' : 'text-zinc-900' ;
93110 const textSecondary = isDark ? 'text-zinc-400' : 'text-zinc-500' ;
94111
95112 return (
96113 < div className = { `min-h-screen flex flex-col items-center p-6 md:p-8 w-full` } >
97- < Header isDark = { isDark } onSettingsClick = { ( ) => setShowSettings ( true ) } onThemeToggle = { toggleTheme } />
98-
99- { /* Hero Text */ }
100- < div className = "text-center max-w-4xl mx-auto mb-12 space-y-6" >
101- < h2 className = { `text-5xl md:text-6xl font-extrabold tracking-tight ${ textPrimary } ` } >
102- Subtitles that speak your language
103- </ h2 >
104- < p className = { `text-xl ${ textSecondary } max-w-2xl mx-auto leading-relaxed` } >
105- Generate accurate same-language subtitles or translate to any language. Powered by Gemini AI,
106- delivering perfectly timed captions in seconds.
107- </ p >
108- </ div >
109-
110- { /* Main Layout */ }
111- < div className = "w-full max-w-4xl transition-all duration-500 ease-in-out" >
112- { /* Video Player & Upload */ }
113- < div className = "space-y-6" >
114- { ! videoUrl ? (
115- < UploadZone isDark = { isDark } onFileChange = { handleFileChange } />
116- ) : (
117- < VideoPlayer
118- videoRef = { videoRef }
119- videoUrl = { videoUrl }
120- currentSubtitle = { currentSubtitle }
121- selectedLangCode = { selectedLangCode }
122- isDark = { isDark }
123- subtitles = { subtitles }
124- isProcessing = { isProcessing }
125- onLanguageChange = { setSelectedLangCode }
126- onProcessSameLanguage = { ( ) => handleProcess ( false ) }
127- onProcessTranslated = { ( ) => handleProcess ( true ) }
128- onRemove = { handleRemoveVideo }
129- onSRTUpload = { handleSRTUpload }
130- />
131- ) }
132-
133- { /* Status Messages */ }
134- < StatusMessages
135- isProcessing = { isProcessing }
136- error = { error }
137- selectedLangCode = { selectedLangCode }
138- isDark = { isDark }
139- isTranslationMode = { isTranslationMode }
140- />
141- </ div >
114+ < Header isDark = { isDark } onMenuClick = { ( ) => setShowMenu ( true ) } onThemeToggle = { toggleTheme } />
115+
116+ { /* Main Content Area */ }
117+ { ! videoUrl ? (
118+ < >
119+ { /* Hero Text */ }
120+ < div className = "text-center max-w-4xl mx-auto mb-12 space-y-6" >
121+ < h2 className = { `text-5xl md:text-6xl font-extrabold tracking-tight ${ textPrimary } ` } >
122+ Subtitles that speak your language
123+ </ h2 >
124+ < p className = { `text-xl ${ textSecondary } max-w-2xl mx-auto leading-relaxed` } >
125+ Generate accurate same-language subtitles or translate to any language. Powered by Gemini AI,
126+ delivering perfectly timed captions in seconds.
127+ </ p >
128+ </ div >
142129
143- { /* Transcript (Shows below video when generated) */ }
144- { subtitles && (
145- < div className = "mt-6" >
146- < Transcript
147- subtitles = { subtitles }
148- selectedLangCode = { selectedLangCode }
149- isDark = { isDark }
150- onSeekTo = { seekTo }
151- onDownloadSRT = { downloadSRT }
152- />
130+ { /* Upload Zone */ }
131+ < div className = "w-full max-w-4xl" >
132+ < UploadZone isDark = { isDark } onFileChange = { handleFileChange } />
153133 </ div >
154- ) }
155- </ div >
134+ </ >
135+ ) : (
136+ < div className = "w-full max-w-7xl" >
137+ { /* Two Column Layout: Video Player + Transcript */ }
138+ < div className = "grid grid-cols-1 lg:grid-cols-3 gap-6" >
139+ { /* Video Player Column (2/3 width) */ }
140+ < div className = "lg:col-span-2 space-y-6" >
141+ < VideoPlayer
142+ videoRef = { videoRef }
143+ videoUrl = { videoUrl }
144+ currentSubtitle = { currentSubtitle }
145+ selectedLangCode = { selectedLangCode }
146+ isDark = { isDark }
147+ subtitles = { subtitles }
148+ isProcessing = { isProcessing }
149+ onRemove = { handleRemoveVideo }
150+ />
151+ < StatusMessages
152+ isProcessing = { isProcessing }
153+ error = { error }
154+ selectedLangCode = { selectedLangCode }
155+ isDark = { isDark }
156+ isTranslationMode = { isTranslationMode }
157+ />
158+
159+ { /* Hero Text Below Video */ }
160+ < div className = "space-y-3" >
161+ < h3 className = { `text-2xl font-bold tracking-tight ${ textPrimary } ` } >
162+ Subtitles that speak your language
163+ </ h3 >
164+ < p className = { `text-base ${ textSecondary } leading-relaxed` } >
165+ Generate accurate same-language subtitles or translate to any language. Powered by Gemini AI,
166+ delivering perfectly timed captions in seconds.
167+ </ p >
168+ </ div >
169+ </ div >
170+
171+ { /* Transcript Panel (1/3 width) - Sticky */ }
172+ < div className = "lg:col-span-1" data-transcript-section >
173+ < Transcript
174+ subtitles = { subtitles }
175+ selectedLangCode = { selectedLangCode }
176+ isDark = { isDark }
177+ isProcessing = { isProcessing }
178+ onSeekTo = { seekTo }
179+ onDownloadSRT = { downloadSRT }
180+ onProcessSameLanguage = { handleProcessSameLanguage }
181+ onProcessTranslated = { handleProcessTranslated }
182+ onSRTUpload = { handleSRTUpload }
183+ onLanguageChange = { setSelectedLangCode }
184+ srtInputRef = { srtInputRef }
185+ />
186+ </ div >
187+ </ div >
188+ </ div >
189+ ) }
156190
157- { /* Settings Modal */ }
158- < SettingsModal
159- isOpen = { showSettings }
191+ { /* Menu Sheet */ }
192+ < MenuSheet
193+ isOpen = { showMenu }
160194 apiKey = { apiKey }
161195 isDark = { isDark }
162196 onApiKeyChange = { setApiKey }
163197 onSave = { ( ) => handleSaveApiKey ( apiKey ) }
164- onClose = { ( ) => setShowSettings ( false ) }
198+ onClose = { ( ) => setShowMenu ( false ) }
165199 />
166200 </ div >
167201 ) ;
0 commit comments