@@ -33,129 +33,11 @@ function Blog() {
3333 } ) ;
3434 } , [ ] ) ;
3535
36- // Ensure page-level horizontal overflow is hidden while this component is mounted
37- useEffect ( ( ) => {
38- const prevHtmlOverflowX = document . documentElement . style . overflowX ;
39- const prevBodyOverflowX = document . body . style . overflowX ;
40- const prevBodyMarginRight = document . body . style . marginRight ;
41- document . documentElement . style . overflowX = 'hidden' ;
42- document . body . style . overflowX = 'hidden' ;
43- // ensure no body margin that could shift scrollbar
44- document . body . style . marginRight = '0' ;
45- return ( ) => {
46- document . documentElement . style . overflowX = prevHtmlOverflowX ;
47- document . body . style . overflowX = prevBodyOverflowX ;
48- document . body . style . marginRight = prevBodyMarginRight ;
49- } ;
50- } , [ ] ) ;
51-
52- // Compute header/footer heights at runtime and set container offsets so content isn't overlapped
53- const [ offsets , setOffsets ] = React . useState ( { top : null , bottom : null } ) ;
54-
55- useEffect ( ( ) => {
56- if ( typeof window === 'undefined' ) return ;
57-
58- const headerSelectors = [ 'header' , '[data-app-header]' , '.app-header' , '.MuiAppBar-root' , '.MuiPaper-root' ] ;
59- const footerSelectors = [ 'footer' , '[data-app-footer]' , '.app-footer' , '.app-footer-root' ] ;
60-
61- function findEl ( selectors , type = 'footer' ) {
62- // try provided selectors first
63- for ( const s of selectors ) {
64- const el = document . querySelector ( s ) ;
65- if ( el ) return el ;
66- }
67- // try by class name containing 'footer' or 'header'
68- const keyword = type === 'header' ? 'header' : 'footer' ;
69- const byClass = Array . from ( document . querySelectorAll ( '[class]' ) ) . find ( el => {
70- try {
71- return el . className && String ( el . className ) . toLowerCase ( ) . includes ( keyword ) ;
72- } catch ( e ) {
73- return false ;
74- }
75- } ) ;
76- if ( byClass ) return byClass ;
77- // try role attributes
78- const role = type === 'header' ? '[role="banner"]' : '[role="contentinfo"]' ;
79- const byRole = document . querySelector ( role ) ;
80- if ( byRole ) return byRole ;
81-
82- // final fallback: find an element tucked at the bottom of the viewport
83- const candidates = Array . from ( document . querySelectorAll ( 'body *' ) ) . filter ( el => {
84- const r = el . getBoundingClientRect ( ) ;
85- // visible and non-zero size
86- return r . width > 10 && r . height > 10 && r . bottom >= ( window . innerHeight - 1 ) ;
87- } ) ;
88- if ( candidates . length ) {
89- // pick the one with the greatest top (closest to bottom)
90- candidates . sort ( ( a , b ) => b . getBoundingClientRect ( ) . top - a . getBoundingClientRect ( ) . top ) ;
91- return candidates [ 0 ] ;
92- }
93- return null ;
94- }
95-
96- function compute ( ) {
97- const headerEl = findEl ( headerSelectors , 'header' ) ;
98- const footerEl = findEl ( footerSelectors , 'footer' ) ;
99- const top = headerEl ? Math . ceil ( headerEl . getBoundingClientRect ( ) . height ) : null ;
100- let bottom = null ;
101- if ( footerEl ) {
102- // use the footer element's height so our container sits above it
103- const fr = footerEl . getBoundingClientRect ( ) ;
104- bottom = Math . ceil ( fr . height ) ;
105- // if footer is positioned above bottom (e.g., floating), ensure bottom includes distance from viewport bottom
106- const distanceFromBottom = Math . max ( 0 , Math . ceil ( window . innerHeight - fr . bottom ) ) ;
107- bottom = bottom + distanceFromBottom ;
108- }
109- // enforce a small minimum so container never touches the footer exactly
110- const MIN_BOTTOM = 4 ;
111- if ( bottom != null ) bottom = Math . max ( bottom , MIN_BOTTOM ) ;
112-
113- // account for iOS safe-area inset-bottom if present
114- const safeAreaInset = parseInt ( getComputedStyle ( document . documentElement ) . getPropertyValue ( '--safe-area-inset-bottom' ) || '0' , 10 ) || 0 ;
115- if ( safeAreaInset > 0 && bottom != null ) bottom += safeAreaInset ;
116-
117- setOffsets ( { top, bottom } ) ;
118- }
119-
120- compute ( ) ;
121- const ro = new ResizeObserver ( ( ) => compute ( ) ) ;
122- // observe header/footer if present
123- const headerEl = findEl ( headerSelectors ) ;
124- const footerEl = findEl ( footerSelectors ) ;
125- if ( headerEl ) ro . observe ( headerEl ) ;
126- if ( footerEl ) ro . observe ( footerEl ) ;
127-
128- // MutationObserver to detect layout changes inside footer (class changes, child changes)
129- const mo = new MutationObserver ( ( ) => compute ( ) ) ;
130- if ( footerEl ) mo . observe ( footerEl , { attributes : true , childList : true , subtree : true } ) ;
131-
132- // Debounced resize handler (run compute immediately and once after resize stops)
133- let resizeTimer = null ;
134- function onResize ( ) {
135- compute ( ) ;
136- if ( resizeTimer ) clearTimeout ( resizeTimer ) ;
137- resizeTimer = setTimeout ( ( ) => {
138- compute ( ) ;
139- resizeTimer = null ;
140- } , 200 ) ;
141- }
142-
143- window . addEventListener ( 'resize' , onResize ) ;
144-
145- return ( ) => {
146- window . removeEventListener ( 'resize' , onResize ) ;
147- try { ro . disconnect ( ) ; } catch ( e ) { }
148- try { mo . disconnect ( ) ; } catch ( e ) { }
149- if ( resizeTimer ) clearTimeout ( resizeTimer ) ;
150- } ;
151- } , [ ] ) ;
152-
15336 if ( loading ) {
15437 return (
15538 < Box
15639 sx = { {
157- width : 'auto' ,
158- height : 'calc(100vh - var(--app-header-height, 75px) - var(--app-footer-height, 150px))' ,
40+ minHeight : '100vh' ,
15941 display : 'flex' ,
16042 alignItems : 'center' ,
16143 justifyContent : 'center' ,
@@ -171,8 +53,7 @@ function Blog() {
17153 return (
17254 < Box
17355 sx = { {
174- width : 'auto' ,
175- height : 'calc(100vh - var(--app-header-height, 75px) - var(--app-footer-height, 150px))' ,
56+ minHeight : '100vh' ,
17657 display : 'flex' ,
17758 alignItems : 'center' ,
17859 justifyContent : 'center' ,
@@ -189,38 +70,15 @@ function Blog() {
18970 return (
19071 < Box
19172 sx = { {
192- position : 'fixed' ,
193- top : offsets . top != null ? `${ offsets . top } px` : 'var(--app-header-height, 75px)' ,
194- bottom : offsets . bottom != null ? `${ offsets . bottom } px` : 'var(--app-footer-height, 10px)' ,
195- left : 0 ,
196- right : 0 ,
197- boxSizing : 'border-box' ,
73+ minHeight : '100vh' ,
19874 backgroundColor : '#f9f9f9' ,
199- overflowX : 'hidden' ,
200- // keep layout stacking context
201- zIndex : 1 ,
202- ...( process . env . NODE_ENV === 'development' ? { outline : '2px dashed rgba(0,0,0,0.08)' } : { } ) ,
75+ paddingLeft : '2rem' ,
76+ paddingRight : '2rem' ,
77+ paddingTop : '1rem' ,
78+ paddingBottom : '1rem' ,
79+ boxSizing : 'border-box' ,
20380 } }
20481 >
205- < Box
206- sx = { {
207- position : 'absolute' ,
208- inset : 0 ,
209- boxSizing : 'border-box' ,
210- paddingLeft : '2rem' ,
211- paddingRight : '2rem' ,
212- paddingTop : '1rem' ,
213- paddingBottom : '1rem' ,
214- overflowY : 'auto' ,
215- overflowX : 'hidden' ,
216- WebkitOverflowScrolling : 'touch' ,
217- '&::-webkit-scrollbar' : { width : '12px' } ,
218- '&::-webkit-scrollbar-track' : { background : 'transparent' } ,
219- '&::-webkit-scrollbar-thumb' : { backgroundColor : '#c1c1c1' , borderRadius : '6px' } ,
220- scrollbarWidth : 'auto' ,
221- scrollbarColor : '#c1c1c1 transparent' ,
222- } }
223- >
22482 < ReactMarkdown
22583 remarkPlugins = { [ remarkGfm ] }
22684 rehypePlugins = { [ rehypeRaw ] }
@@ -442,7 +300,6 @@ function Blog() {
442300 >
443301 { readmeContent }
444302 </ ReactMarkdown >
445- </ Box >
446303 </ Box >
447304 ) ;
448305}
0 commit comments