@@ -648,6 +648,95 @@ 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 firstChild = modalBody . firstElementChild ;
673+ if ( ! firstChild ) return ;
674+
675+ firstChild . style . transform = '' ;
676+ firstChild . style . transformOrigin = '' ;
677+
678+ var computedStyle = window . getComputedStyle ( modalContent ) ;
679+ var paddingTop = parseFloat ( computedStyle . paddingTop ) || 32 ;
680+ var paddingBottom = parseFloat ( computedStyle . paddingBottom ) || 32 ;
681+ var paddingLeft = parseFloat ( computedStyle . paddingLeft ) || 32 ;
682+ var paddingRight = parseFloat ( computedStyle . paddingRight ) || 32 ;
683+
684+ var availableHeight = modalContent . clientHeight - paddingTop - paddingBottom ;
685+ var availableWidth = modalContent . clientWidth - paddingLeft - paddingRight ;
686+
687+ var contentHeight = firstChild . scrollHeight ;
688+ var contentWidth = firstChild . scrollWidth ;
689+
690+ if ( contentHeight <= 0 || contentWidth <= 0 ) return ;
691+
692+ var zoom = 1 ;
693+ var heightZoom = availableHeight / contentHeight ;
694+ var widthZoom = availableWidth / contentWidth ;
695+
696+ zoom = Math . min ( heightZoom , widthZoom ) ;
697+ if ( zoom > 1 ) {
698+ zoom = 1 ;
699+ }
700+
701+ // Apply scale transform and origins
702+ firstChild . style . transform = 'scale(' + zoom + ')' ;
703+ firstChild . style . transformOrigin = 'top center' ;
704+
705+ // Constrain wrapper block size to prevent scroll triggering
706+ modalBody . style . height = ( contentHeight * zoom ) + 'px' ;
707+ modalBody . style . width = '100%' ;
708+ modalBody . style . display = 'flex' ;
709+ modalBody . style . flexDirection = 'column' ;
710+ modalBody . style . alignItems = 'center' ;
711+ }
712+
713+ function initModalScaling ( ) {
714+ applyModalScaling ( ) ;
715+
716+ if ( modalResizeObserver ) {
717+ modalResizeObserver . disconnect ( ) ;
718+ }
719+
720+ var modalBody = document . getElementById ( 'modalBody' ) ;
721+ var firstChild = modalBody ? modalBody . firstElementChild : null ;
722+ if ( firstChild ) {
723+ modalResizeObserver = new ResizeObserver ( function ( ) {
724+ requestAnimationFrame ( applyModalScaling ) ;
725+ } ) ;
726+ modalResizeObserver . observe ( firstChild ) ;
727+ }
728+
729+ window . addEventListener ( 'resize' , applyModalScaling ) ;
730+ }
731+
732+ function destroyModalScaling ( ) {
733+ if ( modalResizeObserver ) {
734+ modalResizeObserver . disconnect ( ) ;
735+ modalResizeObserver = null ;
736+ }
737+ window . removeEventListener ( 'resize' , applyModalScaling ) ;
738+ }
739+
651740 // ── Focus Trap for Modal ──────────────────────────────────────────
652741 function getFocusableElements ( root ) {
653742 var selector =
@@ -668,9 +757,9 @@ if (stickyFilterBar && heroSection) {
668757 var first = focusables [ 0 ] ;
669758 var last = focusables [ focusables . length - 1 ] ;
670759 if ( e . shiftKey && document . activeElement === first ) {
671- e . preventDefault ( ) ; last . focus ( ) ;
760+ e . preventDefault ( ) ; last . focus ( { preventScroll : true } ) ;
672761 } else if ( ! e . shiftKey && document . activeElement === last ) {
673- e . preventDefault ( ) ; first . focus ( ) ;
762+ e . preventDefault ( ) ; first . focus ( { preventScroll : true } ) ;
674763 }
675764 } ;
676765 document . addEventListener ( 'keydown' , handler , true ) ;
@@ -710,25 +799,37 @@ if (stickyFilterBar && heroSection) {
710799 if ( typeof initializeProject === 'function' ) initializeProject ( name ) ;
711800 } ) ;
712801
802+ // Initialize reactive scale calculations
803+ initModalScaling ( ) ;
804+
713805 removeTrap = trapFocus ( modal ) ;
714806 var focusables = getFocusableElements ( modalBody ) ;
715807 var firstFocusable = focusables [ 0 ] || modalClose ;
716808 if ( firstFocusable && typeof firstFocusable . focus === 'function' ) {
717- firstFocusable . focus ( ) ;
809+ firstFocusable . focus ( { preventScroll : true } ) ;
718810 }
719811 }
720812
721813 function closeProjectSafe ( ) {
722814 if ( ! modal || ! modal . classList . contains ( 'active' ) ) return ;
815+
816+ destroyModalScaling ( ) ;
817+
723818 modal . classList . remove ( 'active' ) ;
724819 modal . setAttribute ( 'aria-hidden' , 'true' ) ;
725820 document . body . style . paddingRight = '' ;
726821 document . body . style . overflow = '' ;
727822 setMainInert ( false ) ;
728823 if ( removeTrap ) { removeTrap ( ) ; removeTrap = null ; }
729- if ( modalBody ) modalBody . innerHTML = '' ;
824+ if ( modalBody ) {
825+ modalBody . innerHTML = '' ;
826+ modalBody . style . transform = '' ;
827+ modalBody . style . transformOrigin = '' ;
828+ modalBody . style . width = '' ;
829+ modalBody . style . height = '' ;
830+ }
730831 if ( lastFocusedElement && typeof lastFocusedElement . focus === 'function' ) {
731- lastFocusedElement . focus ( ) ;
832+ lastFocusedElement . focus ( { preventScroll : true } ) ;
732833 }
733834 lastFocusedElement = null ;
734835 }
0 commit comments