@@ -1114,83 +1114,85 @@ function renderOverlay(highlightSelector, title, message, showNextBtn, isFinish,
11141114 border : '1px solid var(--color-border, rgba(226,232,240,0.8))' ,
11151115 fontFamily : 'system-ui, -apple-system, sans-serif' ,
11161116 lineHeight : '1.5' ,
1117+ boxSizing : 'border-box' ,
1118+ overflowY : 'auto' ,
1119+ maxHeight : 'calc(100vh - 60px)' ,
11171120 opacity : '0' ,
11181121 transform : prefersReducedMotion ( ) ? 'none' : 'translateY(8px)' ,
11191122 transition : prefersReducedMotion ( ) ? 'none' : 'opacity 300ms var(--ease-emphasized-decel, ease), transform 300ms var(--ease-emphasized-decel, ease), left 300ms var(--ease-emphasized-decel, ease), top 300ms var(--ease-emphasized-decel, ease)' ,
11201123 cursor : 'default' ,
11211124 } ) ;
11221125
1123- if ( mobile ) {
1124- const isLandscape = window . matchMedia ( '(orientation: landscape)' ) . matches ;
1125- if ( isLandscape ) {
1126- // Landscape: position modal under header, avoiding any open panel
1127- const headerH = document . getElementById ( 'app-header' ) ?. offsetHeight || 48 ;
1128- const topPos = ( headerH + 8 ) + 'px' ;
1129-
1130- // Detect which panels are open and compute available space
1131- const quizPanel = document . getElementById ( 'quiz-panel' ) ;
1132- const videoPanel = document . getElementById ( 'video-panel' ) ;
1133- const quizOpen = quizPanel ?. classList . contains ( 'open' ) ;
1134- const videoOpen = videoPanel ?. classList . contains ( 'open' ) ;
1135- const toggleWidth = 36 ; // drawer toggle tab extends past panel edge
1136-
1137- // Determine best side: prefer positionHint, then place away from open panels
1138- const hintRight = positionHint === 'right' || positionHint === 'video-final' ;
1139- const hintLeft = positionHint === 'left' || positionHint === 'quiz-final' ;
1140- let placeLeft = hintLeft ;
1141- let placeRight = hintRight ;
1142- if ( ! placeLeft && ! placeRight ) {
1143- // No hint: place opposite the open panel, or left by default
1144- placeLeft = ! videoOpen ;
1145- placeRight = videoOpen && ! quizOpen ;
1146- }
1126+ // Mobile landscape positioning — extracted so it can be re-run after panel transitions
1127+ function positionMobileLandscape ( m ) {
1128+ const headerH = document . getElementById ( 'app-header' ) ?. offsetHeight || 48 ;
1129+ const topPos = ( headerH + 8 ) + 'px' ;
1130+ m . style . maxHeight = `calc(100vh - ${ headerH + 12 } px)` ;
1131+ const quizPanel = document . getElementById ( 'quiz-panel' ) ;
1132+ const videoPanel = document . getElementById ( 'video-panel' ) ;
1133+ const quizOpen = quizPanel ?. classList . contains ( 'open' ) ;
1134+ const videoOpen = videoPanel ?. classList . contains ( 'open' ) ;
1135+ const toggleWidth = 36 ;
1136+
1137+ const hintRight = positionHint === 'right' || positionHint === 'video-final' ;
1138+ const hintLeft = positionHint === 'left' || positionHint === 'quiz-final' ;
1139+ let placeLeft = hintLeft ;
1140+ let placeRight = hintRight ;
1141+ if ( ! placeLeft && ! placeRight ) {
1142+ placeLeft = ! videoOpen ;
1143+ placeRight = videoOpen && ! quizOpen ;
1144+ }
11471145
1148- // Calculate available width on each side (accounting for panel + toggle)
1149- let availLeft = window . innerWidth ;
1150- let availRight = window . innerWidth ;
1151- if ( quizOpen && quizPanel ) {
1152- // Quiz panel is on the RIGHT — constrains left space and right space
1153- const qpRect = quizPanel . getBoundingClientRect ( ) ;
1154- availLeft = Math . min ( availLeft , qpRect . left - toggleWidth ) ;
1155- availRight = Math . min ( availRight , window . innerWidth - qpRect . left ) ;
1156- }
1157- if ( videoOpen && videoPanel ) {
1158- // Video panel is on the LEFT — constrains right space and left space
1159- const vpRect = videoPanel . getBoundingClientRect ( ) ;
1160- availRight = Math . min ( availRight , window . innerWidth - vpRect . right - toggleWidth ) ;
1161- availLeft = Math . min ( availLeft , vpRect . right ) ;
1162- }
1146+ let availLeft = window . innerWidth ;
1147+ let availRight = window . innerWidth ;
1148+ if ( quizOpen && quizPanel ) {
1149+ const qpRect = quizPanel . getBoundingClientRect ( ) ;
1150+ availLeft = Math . min ( availLeft , qpRect . left - toggleWidth ) ;
1151+ availRight = Math . min ( availRight , window . innerWidth - qpRect . left ) ;
1152+ }
1153+ if ( videoOpen && videoPanel ) {
1154+ const vpRect = videoPanel . getBoundingClientRect ( ) ;
1155+ availRight = Math . min ( availRight , window . innerWidth - vpRect . right - toggleWidth ) ;
1156+ availLeft = Math . min ( availLeft , vpRect . right ) ;
1157+ }
11631158
1164- // If preferred side has no room, flip; if neither side has room, overlay on top
1165- const minUsable = 160 ;
1166- if ( placeLeft && availLeft < minUsable && availRight > availLeft ) {
1167- placeLeft = false ; placeRight = true ;
1168- } else if ( placeRight && availRight < minUsable && availLeft > availRight ) {
1169- placeRight = false ; placeLeft = true ;
1170- }
1159+ const minUsable = 160 ;
1160+ if ( placeLeft && availLeft < minUsable && availRight > availLeft ) {
1161+ placeLeft = false ; placeRight = true ;
1162+ } else if ( placeRight && availRight < minUsable && availLeft > availRight ) {
1163+ placeRight = false ; placeLeft = true ;
1164+ }
11711165
1172- const chosenAvail = placeLeft ? availLeft : availRight ;
1173- if ( chosenAvail < minUsable ) {
1174- // Both sides blocked (both panels open) — center overlay on top
1175- Object . assign ( modal . style , {
1176- maxWidth : `${ MODAL_MAX_WIDTH } px` , borderRadius : '12px' ,
1177- top : topPos , left : '50%' , right : 'auto' ,
1178- transform : 'translateX(-50%)' ,
1179- } ) ;
1166+ const chosenAvail = placeLeft ? availLeft : availRight ;
1167+ if ( chosenAvail < minUsable ) {
1168+ Object . assign ( m . style , {
1169+ width : `${ MODAL_MAX_WIDTH } px` , maxWidth : '90vw' , borderRadius : '12px' ,
1170+ top : topPos , left : '50%' , right : 'auto' ,
1171+ transform : 'translateX(-50%)' ,
1172+ } ) ;
1173+ } else {
1174+ const usableW = Math . max ( Math . min ( MODAL_MAX_WIDTH , chosenAvail - 24 ) , minUsable ) ;
1175+ Object . assign ( m . style , {
1176+ width : usableW + 'px' , maxWidth : usableW + 'px' , borderRadius : '12px' ,
1177+ top : topPos ,
1178+ } ) ;
1179+ if ( placeRight ) {
1180+ m . style . right = '12px' ;
1181+ m . style . left = 'auto' ;
11801182 } else {
1181- const maxW = Math . min ( MODAL_MAX_WIDTH , chosenAvail - 24 ) ;
1182- Object . assign ( modal . style , {
1183- maxWidth : Math . max ( maxW , minUsable ) + 'px' , borderRadius : '12px' ,
1184- top : topPos ,
1185- } ) ;
1186- if ( placeRight ) {
1187- modal . style . right = '12px' ;
1188- modal . style . left = 'auto' ;
1189- } else {
1190- modal . style . left = '12px' ;
1191- modal . style . right = 'auto' ;
1192- }
1183+ m . style . left = '12px' ;
1184+ m . style . right = 'auto' ;
11931185 }
1186+ }
1187+ }
1188+
1189+ if ( mobile ) {
1190+ const isLandscape = window . matchMedia ( '(orientation: landscape)' ) . matches ;
1191+ if ( isLandscape ) {
1192+ positionMobileLandscape ( modal ) ;
1193+ // Defer reposition to catch panel CSS transitions
1194+ setTimeout ( ( ) => positionMobileLandscape ( modal ) , 350 ) ;
1195+ setTimeout ( ( ) => positionMobileLandscape ( modal ) , 600 ) ;
11941196 } else {
11951197 // Portrait: bottom sheet or top bar
11961198 const highlightInBottom = highlightEl && highlightEl . getBoundingClientRect ( ) . bottom > window . innerHeight * 0.6 ;
@@ -1353,6 +1355,8 @@ function makeDraggable(modal) {
13531355 if ( e . target . closest ( 'button, a' ) ) return ; // don't drag on buttons
13541356 e . preventDefault ( ) ;
13551357 const rect = modal . getBoundingClientRect ( ) ;
1358+ // Lock width so text doesn't reflow when right/bottom are cleared
1359+ modal . style . width = rect . width + 'px' ;
13561360 _dragState = { startX : e . clientX , startY : e . clientY , origLeft : rect . left , origTop : rect . top } ;
13571361 modal . style . transition = 'none' ;
13581362 handle . style . cursor = 'grabbing' ;
@@ -1382,6 +1386,8 @@ function makeDraggable(modal) {
13821386 if ( e . target . closest ( 'button, a' ) ) return ;
13831387 const touch = e . touches [ 0 ] ;
13841388 const rect = modal . getBoundingClientRect ( ) ;
1389+ // Lock width so text doesn't reflow when right/bottom are cleared
1390+ modal . style . width = rect . width + 'px' ;
13851391 _dragState = { startX : touch . clientX , startY : touch . clientY , origLeft : rect . left , origTop : rect . top } ;
13861392 modal . style . transition = 'none' ;
13871393
0 commit comments