@@ -119,9 +119,19 @@ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
119119} /*EDITMODE-END*/ ;
120120
121121function App ( ) {
122+ // Detect OBS solo mode from URL: ?solo=1&variant=A
123+ const urlParams = new URL ( location . href ) . searchParams ;
124+ const isSolo = urlParams . get ( 'solo' ) === '1' ;
125+ const soloVariant = urlParams . get ( 'variant' ) || 'A' ;
126+
122127 const [ tweaks , setTweaks ] = React . useState ( ( ) => {
123- try { return { ...TWEAK_DEFAULTS , ...JSON . parse ( localStorage . getItem ( 'wfr-tweaks' ) || '{}' ) } ; }
124- catch { return TWEAK_DEFAULTS ; }
128+ try {
129+ const saved = JSON . parse ( localStorage . getItem ( 'wfr-tweaks' ) || '{}' ) ;
130+ const base = { ...TWEAK_DEFAULTS , ...saved } ;
131+ // In solo mode, force single layout and correct variant regardless of saved state
132+ if ( isSolo ) return { ...base , layout : 'single' , activeVariant : soloVariant } ;
133+ return base ;
134+ } catch { return TWEAK_DEFAULTS ; }
125135 } ) ;
126136 const [ editMode , setEditMode ] = React . useState ( false ) ;
127137 const [ panelOpen , setPanelOpen ] = React . useState ( true ) ;
@@ -196,70 +206,77 @@ function App() {
196206 if ( tweaks . layout === 'side-by-side' ) {
197207 return (
198208 < div style = { { display : 'flex' , gap : 40 , alignItems : 'flex-start' } } >
199- < Labeled title = "Variant A · BROADCAST BAR" >
200- { variantA }
201- </ Labeled >
202- < Labeled title = "Variant B · CORNER HUD" >
203- { variantB }
204- </ Labeled >
209+ < Labeled title = "Variant A · BROADCAST BAR" > { variantA } </ Labeled >
210+ < Labeled title = "Variant B · CORNER HUD" > { variantB } </ Labeled >
205211 </ div >
206212 ) ;
207213 }
214+ // Solo mode: render the overlay directly with no label wrapper, exact 1920×1080
215+ if ( isSolo ) {
216+ return tweaks . activeVariant === 'A' ? variantA : variantB ;
217+ }
208218 return (
209219 < Labeled title = { tweaks . activeVariant === 'A' ? 'BROADCAST BAR' : 'CORNER HUD' } >
210220 { tweaks . activeVariant === 'A' ? variantA : variantB }
211221 </ Labeled >
212222 ) ;
213223 } ;
214224
215- const natW = tweaks . layout === 'side-by-side' ? 1920 * 2 + 40 : 1920 ;
216- const natH = 1080 + 60 ;
225+ // Solo: exact 1920×1080, no scaling needed — OBS browser source is set to that size
226+ const natW = isSolo ? 1920 : ( tweaks . layout === 'side-by-side' ? 1920 * 2 + 40 : 1920 ) ;
227+ const natH = isSolo ? 1080 : 1080 + 60 ;
217228
218229 return (
219230 < div style = { {
220- minHeight : '100vh' , background : '#0a0a0b' , color : '#f4f4f5' ,
221- fontFamily : window . FONT . label , overflow : 'hidden' ,
231+ minHeight : '100vh' ,
232+ background : isSolo ? 'transparent' : '#0a0a0b' ,
233+ color : '#f4f4f5' , fontFamily : window . FONT . label , overflow : 'hidden' ,
222234 display : 'flex' , alignItems : 'center' , justifyContent : 'center' ,
223235 } } data-screen-label = "01 Stream Overlay Canvas" >
224236 < div style = { {
225- width : natW , transform : `scale(${ fitScale } )` , transformOrigin : 'center center' ,
237+ width : natW ,
238+ transform : isSolo ? 'none' : `scale(${ fitScale } )` ,
239+ transformOrigin : 'center center' ,
226240 } } >
227241 { renderCanvas ( ) }
228242 </ div >
229243
230- { /* Connection pill (top-left, always visible) */ }
231- < div style = { { position : 'fixed' , top : 12 , left : 12 , display : 'flex' , gap : 8 ,
232- alignItems : 'center' , background : 'rgba(0,0,0,0.7)' , border : '1px solid rgba(255,255,255,0.1)' ,
233- padding : '6px 12px' , fontFamily : window . FONT . mono , fontSize : 11 } } >
234- < span style = { {
235- width : 8 , height : 8 , borderRadius : 4 ,
236- background : wsStatus . mode === 'live' ? window . COL . green :
237- wsStatus . mode === 'sim' ? window . COL . accent :
238- wsStatus . mode === 'connecting' ? '#fb8' : window . COL . red ,
239- } } />
240- < span style = { { letterSpacing : 1.4 , textTransform : 'uppercase' } } >
241- { wsStatus . mode === 'sim' ? 'SIM DATA' :
242- wsStatus . mode === 'live' ? 'LIVE WS' :
243- wsStatus . mode === 'connecting' ? 'CONNECTING' : 'WS ERROR' }
244- </ span >
245- { wsStatus . url && < span style = { { opacity : 0.6 } } > · { wsStatus . url } </ span > }
246- </ div >
244+ { /* All dev UI hidden in solo/OBS mode */ }
245+ { ! isSolo && (
246+ < div style = { { position : 'fixed' , top : 12 , left : 12 , display : 'flex' , gap : 8 ,
247+ alignItems : 'center' , background : 'rgba(0,0,0,0.7)' , border : '1px solid rgba(255,255,255,0.1)' ,
248+ padding : '6px 12px' , fontFamily : window . FONT . mono , fontSize : 11 } } >
249+ < span style = { {
250+ width : 8 , height : 8 , borderRadius : 4 ,
251+ background : wsStatus . mode === 'live' ? window . COL . green :
252+ wsStatus . mode === 'sim' ? window . COL . accent :
253+ wsStatus . mode === 'connecting' ? '#fb8' : window . COL . red ,
254+ } } />
255+ < span style = { { letterSpacing : 1.4 , textTransform : 'uppercase' } } >
256+ { wsStatus . mode === 'sim' ? 'SIM DATA' :
257+ wsStatus . mode === 'live' ? 'LIVE WS' :
258+ wsStatus . mode === 'connecting' ? 'CONNECTING' : 'WS ERROR' }
259+ </ span >
260+ { wsStatus . url && < span style = { { opacity : 0.6 } } > · { wsStatus . url } </ span > }
261+ </ div >
262+ ) }
247263
248- { /* Settings panel toggle */ }
249- < button onClick = { ( ) => setPanelOpen ( ( o ) => ! o ) }
250- style = { {
251- position : 'fixed' , top : 12 , right : 12 , zIndex : 30 ,
252- background : 'rgba(0,0,0,0.7)' , color : '#fff' ,
253- border : '1px solid rgba(255,255,255,0.15)' ,
254- padding : '6px 14px' , fontFamily : window . FONT . mono , fontSize : 11 ,
255- letterSpacing : 1.4 , textTransform : 'uppercase' , cursor : 'pointer' ,
256- } } >
257- { panelOpen ? 'hide settings' : 'settings' }
258- </ button >
264+ { ! isSolo && (
265+ < button onClick = { ( ) => setPanelOpen ( ( o ) => ! o ) }
266+ style = { {
267+ position : 'fixed' , top : 12 , right : 12 , zIndex : 30 ,
268+ background : 'rgba(0,0,0,0.7)' , color : '#fff' ,
269+ border : '1px solid rgba(255,255,255,0.15)' ,
270+ padding : '6px 14px' , fontFamily : window . FONT . mono , fontSize : 11 ,
271+ letterSpacing : 1.4 , textTransform : 'uppercase' , cursor : 'pointer' ,
272+ } } >
273+ { panelOpen ? 'hide settings' : 'settings' }
274+ </ button >
275+ ) }
259276
260- < CanDebugPanel />
277+ { ! isSolo && < CanDebugPanel /> }
261278
262- { panelOpen && (
279+ { ! isSolo && panelOpen && (
263280 < div style = { {
264281 position : 'fixed' , bottom : 12 , right : 12 , width : 360 , zIndex : 25 ,
265282 background : 'rgba(12,12,14,0.96)' , border : '1px solid rgba(255,255,255,0.12)' ,
@@ -396,17 +413,6 @@ function Labeled({ title, children }) {
396413 ) ;
397414}
398415
399- // URL params let OBS load a single variant, bare, at full size
400- ( function applyUrlParams ( ) {
401- const u = new URL ( location . href ) ;
402- if ( u . searchParams . get ( 'solo' ) === '1' ) {
403- const v = u . searchParams . get ( 'variant' ) || 'A' ;
404- try {
405- const t = JSON . parse ( localStorage . getItem ( 'wfr-tweaks' ) || '{}' ) ;
406- localStorage . setItem ( 'wfr-tweaks' , JSON . stringify ( { ...t , layout : 'single' , activeVariant : v } ) ) ;
407- } catch { }
408- }
409- } ) ( ) ;
410416
411417const root = ReactDOM . createRoot ( document . getElementById ( 'root' ) ) ;
412418root . render ( < App /> ) ;
0 commit comments