|
1016 | 1016 | setActive(matching.length ? matching : [firstLinkForHash(hash)]); |
1017 | 1017 | }; |
1018 | 1018 |
|
1019 | | - const homeSection = document.querySelector("#hero"); |
| 1019 | + const homeSection = document.querySelector("#hero"); |
| 1020 | + const getDocumentBottom = () => { |
| 1021 | + const scrollEl = document.scrollingElement || document.documentElement || document.body; |
| 1022 | + return Math.max(0, (scrollEl?.scrollHeight || document.documentElement.scrollHeight || 0) - window.innerHeight); |
| 1023 | + }; |
1020 | 1024 | let lockedHash = ""; |
1021 | 1025 | let lockTimer = 0; |
1022 | 1026 |
|
|
1047 | 1051 | return; |
1048 | 1052 | } |
1049 | 1053 |
|
1050 | | - const syncFromViewport = () => { |
1051 | | - if (lockedHash) { |
1052 | | - setActiveByHash(lockedHash); |
1053 | | - return; |
1054 | | - } |
| 1054 | + const syncFromViewport = () => { |
| 1055 | + if (lockedHash) { |
| 1056 | + setActiveByHash(lockedHash); |
| 1057 | + return; |
| 1058 | + } |
1055 | 1059 |
|
1056 | | - const focusLine = Math.max( |
1057 | | - getNavOffset() + 18, |
1058 | | - Math.round(window.innerHeight * 0.34) |
1059 | | - ); |
| 1060 | + const scrollTop = getScrollTop(); |
| 1061 | + const documentBottom = getDocumentBottom(); |
| 1062 | + const lastSection = sectionsInOrder[sectionsInOrder.length - 1] || null; |
1060 | 1063 |
|
1061 | | - let currentSection = homeSection || sectionsInOrder[0] || null; |
| 1064 | + if (lastSection && scrollTop >= Math.max(0, documentBottom - 8)) { |
| 1065 | + const lastHash = lastSection.id ? `#${lastSection.id}` : ""; |
| 1066 | + setActiveByHash(lastHash); |
| 1067 | + return; |
| 1068 | + } |
1062 | 1069 |
|
1063 | | - sectionsInOrder.forEach((section) => { |
1064 | | - const rect = section.getBoundingClientRect(); |
1065 | | - if (rect.top <= focusLine) { |
1066 | | - currentSection = section; |
1067 | | - } |
1068 | | - }); |
| 1070 | + const focusLine = Math.max( |
| 1071 | + getNavOffset() + 18, |
| 1072 | + Math.round(window.innerHeight * 0.34) |
| 1073 | + ); |
1069 | 1074 |
|
1070 | | - if (!currentSection) { |
1071 | | - return; |
1072 | | - } |
| 1075 | + let currentSection = homeSection || sectionsInOrder[0] || null; |
1073 | 1076 |
|
1074 | | - const currentHash = currentSection.id ? `#${currentSection.id}` : ""; |
1075 | | - setActiveByHash(currentHash); |
1076 | | - }; |
| 1077 | + sectionsInOrder.forEach((section) => { |
| 1078 | + const rect = section.getBoundingClientRect(); |
| 1079 | + if (rect.top <= focusLine) { |
| 1080 | + currentSection = section; |
| 1081 | + } |
| 1082 | + }); |
| 1083 | + |
| 1084 | + if (!currentSection) { |
| 1085 | + return; |
| 1086 | + } |
| 1087 | + |
| 1088 | + const currentHash = currentSection.id ? `#${currentSection.id}` : ""; |
| 1089 | + setActiveByHash(currentHash); |
| 1090 | + }; |
1077 | 1091 |
|
1078 | 1092 | const observer = new IntersectionObserver( |
1079 | 1093 | () => { |
|
1297 | 1311 | root.style.setProperty("--home-next-layer-shift", "0px"); |
1298 | 1312 | }; |
1299 | 1313 |
|
1300 | | - const sync = () => { |
1301 | | - rafId = 0; |
1302 | | - |
1303 | | - const scrollTop = getScrollTop(); |
1304 | | - const progress = clamp(scrollTop / transitionDistance, 0, 1); |
1305 | | - const uiOpacity = 1 - range(progress, 0.06, 0.56, linear); |
1306 | | - const imageScale = lerp(1, 1.125, range(progress, 0, 0.72, easeOutCubic)); |
1307 | | - const baseFade = 1 - range(progress, 0.08, 0.52, linear); |
1308 | | - const baseBrightness = lerp(1, 0.68, range(progress, 0.1, 0.48, easeInOutCubic)); |
1309 | | - const baseContrast = lerp(1, 0.92, range(progress, 0.12, 0.48, easeInOutCubic)); |
1310 | | - const silhouetteAppear = range(progress, 0.2, 0.28, easeInOutCubic); |
1311 | | - const silhouetteFade = 1 - range(progress, 0.28, 0.48, easeInOutCubic); |
1312 | | - const silhouetteOpacity = 0.74 * silhouetteAppear * silhouetteFade; |
1313 | | - const atmosphereProgress = range(progress, 0.48, 0.88, easeInOutCubic); |
1314 | | - const nextLayerMotionProgress = range(progress, 0.48, 0.86, easeInOutCubic); |
1315 | | - const nextLayerOpacityProgress = range(progress, 0.48, 0.72, easeInOutCubic); |
1316 | | - const backdropSuppression = progress < 0.88 ? "1" : "0"; |
1317 | | - |
1318 | | - root.style.setProperty("--home-ui-opacity", Math.max(0, uiOpacity).toFixed(4)); |
1319 | | - root.style.setProperty("--home-image-scroll-scale", imageScale.toFixed(4)); |
1320 | | - root.style.setProperty("--home-image-base-layer-opacity", Math.max(0, baseFade).toFixed(4)); |
1321 | | - root.style.setProperty("--home-image-base-brightness", baseBrightness.toFixed(4)); |
1322 | | - root.style.setProperty("--home-image-base-contrast", baseContrast.toFixed(4)); |
1323 | | - root.style.setProperty("--home-image-silhouette-layer-opacity", Math.max(0, silhouetteOpacity).toFixed(4)); |
1324 | | - setAtmosphere(atmosphereProgress); |
1325 | | - setLowerLayer(nextLayerOpacityProgress); |
1326 | | - |
1327 | | - if (backdropSuppression !== lastBackdropSuppression) { |
1328 | | - body.dataset.homeBackdropSuppressed = backdropSuppression; |
1329 | | - lastBackdropSuppression = backdropSuppression; |
1330 | | - window.dispatchEvent(new Event("home-transition-sync")); |
1331 | | - } |
1332 | | - }; |
| 1314 | + const sync = () => { |
| 1315 | + rafId = 0; |
| 1316 | + |
| 1317 | + const scrollTop = getScrollTop(); |
| 1318 | + const progress = clamp(scrollTop / transitionDistance, 0, 1); |
| 1319 | + const uiOpacity = 1 - range(progress, 0.06, 0.56, linear); |
| 1320 | + const imageScale = lerp(1, 1.125, range(progress, 0, 0.72, easeOutCubic)); |
| 1321 | + const baseFade = 1 - range(progress, 0.08, 0.52, linear); |
| 1322 | + const baseBrightness = lerp(1, 0.68, range(progress, 0.1, 0.48, easeInOutCubic)); |
| 1323 | + const baseContrast = lerp(1, 0.92, range(progress, 0.12, 0.48, easeInOutCubic)); |
| 1324 | + const silhouetteAppear = range(progress, 0.2, 0.28, easeInOutCubic); |
| 1325 | + const silhouetteFade = 1 - range(progress, 0.28, 0.48, easeInOutCubic); |
| 1326 | + const silhouetteOpacity = 0.74 * silhouetteAppear * silhouetteFade; |
| 1327 | + const atmosphereProgress = range(progress, 0.48, 0.88, easeInOutCubic); |
| 1328 | + const nextLayerMotionProgress = range(progress, 0.48, 0.86, easeInOutCubic); |
| 1329 | + const nextLayerOpacityProgress = range(progress, 0.48, 0.72, easeInOutCubic); |
| 1330 | + const backdropSuppression = progress < 0.88 ? "1" : "0"; |
| 1331 | + |
| 1332 | + root.style.setProperty("--home-ui-opacity", Math.max(0, uiOpacity).toFixed(4)); |
| 1333 | + root.style.setProperty("--home-image-scroll-scale", imageScale.toFixed(4)); |
| 1334 | + root.style.setProperty("--home-image-base-layer-opacity", Math.max(0, baseFade).toFixed(4)); |
| 1335 | + root.style.setProperty("--home-image-base-brightness", baseBrightness.toFixed(4)); |
| 1336 | + root.style.setProperty("--home-image-base-contrast", baseContrast.toFixed(4)); |
| 1337 | + root.style.setProperty("--home-image-silhouette-layer-opacity", Math.max(0, silhouetteOpacity).toFixed(4)); |
| 1338 | + setAtmosphere(atmosphereProgress); |
| 1339 | + setLowerLayer(nextLayerOpacityProgress); |
| 1340 | + |
| 1341 | + if (backdropSuppression !== lastBackdropSuppression) { |
| 1342 | + body.dataset.homeBackdropSuppressed = backdropSuppression; |
| 1343 | + lastBackdropSuppression = backdropSuppression; |
| 1344 | + window.dispatchEvent(new Event("home-transition-sync")); |
| 1345 | + } |
| 1346 | + }; |
1333 | 1347 |
|
1334 | 1348 | const requestSync = () => { |
1335 | 1349 | if (rafId) { |
|
0 commit comments