@@ -22,6 +22,7 @@ export default function Receiver() {
2222 isDecoding,
2323 isComplete,
2424 decodedText,
25+ fileResult,
2526 } = useReceiver ( ) ;
2627
2728 const toggleListen = ( ) => {
@@ -46,6 +47,15 @@ export default function Receiver() {
4647 URL . revokeObjectURL ( url ) ;
4748 } ;
4849
50+ const downloadAsFile = ( ) => {
51+ if ( ! fileResult ) return ;
52+ const a = document . createElement ( 'a' ) ;
53+ a . href = fileResult . url ;
54+ // Don't revoke the URL here: the image preview still needs it
55+ a . download = fileResult . name || `acoustic-file-${ Date . now ( ) } ` ;
56+ a . click ( ) ;
57+ } ;
58+
4959 // Current microphone permission / status label text
5060 const getStatusLabel = ( ) => {
5161 switch ( status . type ) {
@@ -118,7 +128,7 @@ export default function Receiver() {
118128 </ motion . div >
119129 ) }
120130
121- { /* Success banner with decoded payload */ }
131+ { /* Success banner — adapts for text vs binary file */ }
122132 { isComplete && (
123133 < motion . div
124134 initial = { { opacity : 0 , scale : 0.95 } }
@@ -131,32 +141,52 @@ export default function Receiver() {
131141 < CheckCircle size = { 24 } />
132142 </ div >
133143 < div >
134- < h2 className = "text-lg font-bold text-white" > Payload Reconstructed</ h2 >
135- < p className = "text-sm text-textMuted" > CRC32 integrity verified. { decodedText . length } characters decoded.</ p >
144+ < h2 className = "text-lg font-bold text-white" >
145+ { fileResult ? 'File Reconstructed' : 'Payload Reconstructed' }
146+ </ h2 >
147+ < p className = "text-sm text-textMuted" >
148+ { fileResult
149+ ? `CRC32 verified · ${ ( fileResult . size / 1024 ) . toFixed ( 1 ) } KB · ${ fileResult . mime } `
150+ : `CRC32 integrity verified. ${ decodedText . length } characters decoded.` }
151+ </ p >
136152 </ div >
137153 </ div >
138154 < div className = "flex gap-2" >
155+ { ! fileResult && (
156+ < button
157+ onClick = { copyToClipboard }
158+ className = "glass-button text-xs py-1.5 px-3 flex items-center gap-1"
159+ >
160+ < Copy size = { 14 } /> Copy
161+ </ button >
162+ ) }
139163 < button
140- onClick = { copyToClipboard }
141- className = "glass-button text-xs py-1.5 px-3 flex items-center gap-1"
142- >
143- < Copy size = { 14 } /> Copy
144- </ button >
145- < button
146- onClick = { downloadAsText }
164+ onClick = { fileResult ? downloadAsFile : downloadAsText }
147165 className = "glass-button bg-white text-black hover:bg-white/90 flex items-center gap-2 font-bold text-xs"
148166 >
149167 < Download size = { 14 } /> Download
150168 </ button >
151169 </ div >
152170 </ div >
153- { /* Decoded text preview */ }
154- < div className = "bg-black/40 rounded-xl border border-white/10 p-4 font-mono text-sm text-primary/90 max-h-32 overflow-y-auto" >
155- { decodedText }
156- </ div >
171+ { fileResult ? (
172+ fileResult . mime . startsWith ( 'image/' ) ? (
173+ < img
174+ src = { fileResult . url }
175+ alt = { fileResult . name }
176+ className = "max-h-64 max-w-full rounded-xl border border-white/10 mx-auto block object-contain"
177+ />
178+ ) : (
179+ < div className = "bg-black/40 rounded-xl border border-white/10 p-4 font-mono text-sm text-textMuted text-center" >
180+ { fileResult . name } · { ( fileResult . size / 1024 ) . toFixed ( 1 ) } KB
181+ </ div >
182+ )
183+ ) : (
184+ < div className = "bg-black/40 rounded-xl border border-white/10 p-4 font-mono text-sm text-primary/90 max-h-32 overflow-y-auto" >
185+ { decodedText }
186+ </ div >
187+ ) }
157188 </ motion . div >
158189 ) }
159-
160190 < div className = "grid grid-cols-1 lg:grid-cols-3 gap-6" >
161191 { /* Left Column: Spectrum Visualizer */ }
162192 < div className = "lg:col-span-2 space-y-6" >
@@ -236,9 +266,13 @@ export default function Receiver() {
236266 { isComplete ? (
237267 < >
238268 < FileText size = { 40 } className = "text-primary" />
239- < p className = "text-lg font-medium" > Payload Ready</ p >
269+ < p className = "text-lg font-medium" >
270+ { fileResult ? 'File Ready' : 'Payload Ready' }
271+ </ p >
240272 < p className = "text-xs text-textMuted font-mono" >
241- { decodedText . length } chars · text/plain · CRC32 OK
273+ { fileResult
274+ ? `${ fileResult . name } · ${ ( fileResult . size / 1024 ) . toFixed ( 1 ) } KB · CRC32 OK`
275+ : `${ decodedText . length } chars · text/plain · CRC32 OK` }
242276 </ p >
243277 </ >
244278 ) : (
0 commit comments