@@ -648,6 +648,99 @@ if (stickyFilterBar && heroSection) {
648648
649649 renderRecentSearches ( ) ;
650650
651+ // ── Central Dynamic Auto-Scaling (ResizeObserver) ─────────────────
652+ var modalResizeObserver = null ;
653+
654+ function applyModalScaling ( ) {
655+ var modalContent = document . querySelector ( '.modal-content' ) ;
656+ var modalBody = document . getElementById ( 'modalBody' ) ;
657+ if ( ! modalContent || ! modalBody ) return ;
658+
659+ // Reset scroll position to top to avoid viewport clippings during calculations
660+ modalContent . scrollTop = 0 ;
661+ modalBody . scrollTop = 0 ;
662+
663+ // Hide scrollbars on the container
664+ modalContent . style . overflow = 'hidden' ;
665+
666+ // Reset inline styles to capture natural dimensions
667+ modalBody . style . transform = '' ;
668+ modalBody . style . transformOrigin = '' ;
669+ modalBody . style . width = '' ;
670+ modalBody . style . height = '' ;
671+
672+ var targetEl = Array . from ( modalBody . children ) . find ( function ( el ) {
673+ return el . tagName . toLowerCase ( ) !== 'style' ;
674+ } ) || modalBody . firstElementChild ;
675+ if ( ! targetEl ) return ;
676+
677+ targetEl . style . transform = '' ;
678+ targetEl . style . transformOrigin = '' ;
679+
680+ var computedStyle = window . getComputedStyle ( modalContent ) ;
681+ var paddingTop = parseFloat ( computedStyle . paddingTop ) || 32 ;
682+ var paddingBottom = parseFloat ( computedStyle . paddingBottom ) || 32 ;
683+ var paddingLeft = parseFloat ( computedStyle . paddingLeft ) || 32 ;
684+ var paddingRight = parseFloat ( computedStyle . paddingRight ) || 32 ;
685+
686+ var availableHeight = modalContent . clientHeight - paddingTop - paddingBottom ;
687+ var availableWidth = modalContent . clientWidth - paddingLeft - paddingRight ;
688+
689+ var contentHeight = targetEl . scrollHeight ;
690+ var contentWidth = targetEl . scrollWidth ;
691+
692+ if ( contentHeight <= 0 || contentWidth <= 0 ) return ;
693+
694+ var zoom = 1 ;
695+ var heightZoom = availableHeight / contentHeight ;
696+ var widthZoom = availableWidth / contentWidth ;
697+
698+ zoom = Math . min ( heightZoom , widthZoom ) ;
699+ if ( zoom > 1 ) {
700+ zoom = 1 ;
701+ }
702+
703+ // Apply scale transform and origins
704+ targetEl . style . transform = 'scale(' + zoom + ')' ;
705+ targetEl . style . transformOrigin = 'top center' ;
706+
707+ // Constrain wrapper block size to prevent scroll triggering
708+ modalBody . style . height = ( contentHeight * zoom ) + 'px' ;
709+ modalBody . style . width = '100%' ;
710+ modalBody . style . display = 'flex' ;
711+ modalBody . style . flexDirection = 'column' ;
712+ modalBody . style . alignItems = 'center' ;
713+ }
714+
715+ function initModalScaling ( ) {
716+ applyModalScaling ( ) ;
717+
718+ if ( modalResizeObserver ) {
719+ modalResizeObserver . disconnect ( ) ;
720+ }
721+
722+ var modalBody = document . getElementById ( 'modalBody' ) ;
723+ var targetEl = modalBody ? Array . from ( modalBody . children ) . find ( function ( el ) {
724+ return el . tagName . toLowerCase ( ) !== 'style' ;
725+ } ) || modalBody . firstElementChild : null ;
726+ if ( targetEl ) {
727+ modalResizeObserver = new ResizeObserver ( function ( ) {
728+ requestAnimationFrame ( applyModalScaling ) ;
729+ } ) ;
730+ modalResizeObserver . observe ( targetEl ) ;
731+ }
732+
733+ window . addEventListener ( 'resize' , applyModalScaling ) ;
734+ }
735+
736+ function destroyModalScaling ( ) {
737+ if ( modalResizeObserver ) {
738+ modalResizeObserver . disconnect ( ) ;
739+ modalResizeObserver = null ;
740+ }
741+ window . removeEventListener ( 'resize' , applyModalScaling ) ;
742+ }
743+
651744 // ── Focus Trap for Modal ──────────────────────────────────────────
652745 function getFocusableElements ( root ) {
653746 var selector =
@@ -668,9 +761,9 @@ if (stickyFilterBar && heroSection) {
668761 var first = focusables [ 0 ] ;
669762 var last = focusables [ focusables . length - 1 ] ;
670763 if ( e . shiftKey && document . activeElement === first ) {
671- e . preventDefault ( ) ; last . focus ( ) ;
764+ e . preventDefault ( ) ; last . focus ( { preventScroll : true } ) ;
672765 } else if ( ! e . shiftKey && document . activeElement === last ) {
673- e . preventDefault ( ) ; first . focus ( ) ;
766+ e . preventDefault ( ) ; first . focus ( { preventScroll : true } ) ;
674767 }
675768 } ;
676769 document . addEventListener ( 'keydown' , handler , true ) ;
@@ -710,25 +803,37 @@ if (stickyFilterBar && heroSection) {
710803 if ( typeof initializeProject === 'function' ) initializeProject ( name ) ;
711804 } ) ;
712805
806+ // Initialize reactive scale calculations
807+ initModalScaling ( ) ;
808+
713809 removeTrap = trapFocus ( modal ) ;
714810 var focusables = getFocusableElements ( modalBody ) ;
715811 var firstFocusable = focusables [ 0 ] || modalClose ;
716812 if ( firstFocusable && typeof firstFocusable . focus === 'function' ) {
717- firstFocusable . focus ( ) ;
813+ firstFocusable . focus ( { preventScroll : true } ) ;
718814 }
719815 }
720816
721817 function closeProjectSafe ( ) {
722818 if ( ! modal || ! modal . classList . contains ( 'active' ) ) return ;
819+
820+ destroyModalScaling ( ) ;
821+
723822 modal . classList . remove ( 'active' ) ;
724823 modal . setAttribute ( 'aria-hidden' , 'true' ) ;
725824 document . body . style . paddingRight = '' ;
726825 document . body . style . overflow = '' ;
727826 setMainInert ( false ) ;
728827 if ( removeTrap ) { removeTrap ( ) ; removeTrap = null ; }
729- if ( modalBody ) modalBody . innerHTML = '' ;
828+ if ( modalBody ) {
829+ modalBody . innerHTML = '' ;
830+ modalBody . style . transform = '' ;
831+ modalBody . style . transformOrigin = '' ;
832+ modalBody . style . width = '' ;
833+ modalBody . style . height = '' ;
834+ }
730835 if ( lastFocusedElement && typeof lastFocusedElement . focus === 'function' ) {
731- lastFocusedElement . focus ( ) ;
836+ lastFocusedElement . focus ( { preventScroll : true } ) ;
732837 }
733838 lastFocusedElement = null ;
734839 }
0 commit comments