|
1254 | 1254 | const lerp = (start, end, amount) => start + (end - start) * amount; |
1255 | 1255 | const linear = (value) => value; |
1256 | 1256 | const easeOutCubic = (value) => 1 - Math.pow(1 - value, 3); |
| 1257 | + const easeInOutSine = (value) => 0.5 - Math.cos(Math.PI * value) / 2; |
1257 | 1258 | const easeInOutCubic = (value) => |
1258 | 1259 | value < 0.5 |
1259 | 1260 | ? 4 * value * value * value |
|
1291 | 1292 | stageReference * 1.78, |
1292 | 1293 | viewportHeight * 1.42 |
1293 | 1294 | ); |
1294 | | - lowerLayerStartShift = Math.min(Math.max(viewportHeight * 0.014, 6), 12); |
| 1295 | + lowerLayerStartShift = Math.min(Math.max(viewportHeight * 0.015, 8), 14); |
1295 | 1296 | const lowerLayerOverlap = Math.max( |
1296 | | - Math.min(viewportHeight * 1.02, stageReference * 1.26), |
1297 | | - viewportHeight * 0.86 |
| 1297 | + Math.min(transitionDistance + viewportHeight * 0.18, viewportHeight * 1.58), |
| 1298 | + viewportHeight * 1.36 |
1298 | 1299 | ); |
1299 | 1300 | hero.style.setProperty("--home-transition-distance", `${transitionDistance.toFixed(2)}px`); |
1300 | 1301 | root.style.setProperty("--home-next-layer-overlap", `${lowerLayerOverlap.toFixed(2)}px`); |
1301 | 1302 | }; |
1302 | 1303 |
|
1303 | 1304 | const setAtmosphere = (amount) => { |
1304 | | - body.style.setProperty("--site-atmosphere-opacity", amount.toFixed(4)); |
| 1305 | + root.style.setProperty("--site-atmosphere-opacity", amount.toFixed(4)); |
1305 | 1306 | }; |
1306 | 1307 |
|
1307 | | - const setLowerLayer = (amount) => { |
1308 | | - const shift = lerp(lowerLayerStartShift, 0, amount); |
1309 | | - root.style.setProperty("--home-next-layer-opacity", amount.toFixed(4)); |
| 1308 | + const setLowerLayer = (opacityAmount, motionAmount) => { |
| 1309 | + const shift = lerp(lowerLayerStartShift, 0, motionAmount); |
| 1310 | + root.style.setProperty("--home-next-layer-opacity", opacityAmount.toFixed(4)); |
1310 | 1311 | root.style.setProperty("--home-next-layer-shift", `${shift.toFixed(2)}px`); |
1311 | 1312 | }; |
1312 | 1313 |
|
|
1323 | 1324 | const silhouetteFade = 1 - range(progress, 0.34, 0.58, easeInOutCubic); |
1324 | 1325 | const silhouetteOpacity = 0.74 * silhouetteAppear * silhouetteFade; |
1325 | 1326 | const heroUnitOpacity = Math.max(baseFade, silhouetteOpacity); |
1326 | | - const atmosphereProgress = range(progress, 0.64, 0.96, easeInOutCubic); |
1327 | | - const nextLayerProgress = range(progress, 0.64, 0.9, easeInOutCubic); |
| 1327 | + const nextLayerMotionProgress = range(progress, 0.38, 0.88, easeInOutCubic); |
| 1328 | + const nextLayerOpacityProgress = range(progress, 0.63, 0.94, easeInOutSine); |
| 1329 | + const atmosphereProgress = range(progress, 0.63, 0.985, easeInOutSine); |
1328 | 1330 | const backdropSuppression = progress < 0.88 ? "1" : "0"; |
1329 | 1331 |
|
1330 | 1332 | root.style.setProperty("--home-ui-opacity", Math.max(0, uiOpacity).toFixed(4)); |
|
1335 | 1337 | root.style.setProperty("--home-image-base-contrast", baseContrast.toFixed(4)); |
1336 | 1338 | root.style.setProperty("--home-image-silhouette-layer-opacity", Math.max(0, silhouetteOpacity).toFixed(4)); |
1337 | 1339 | setAtmosphere(atmosphereProgress); |
1338 | | - setLowerLayer(nextLayerProgress); |
| 1340 | + setLowerLayer(nextLayerOpacityProgress, nextLayerMotionProgress); |
1339 | 1341 |
|
1340 | 1342 | if (backdropSuppression !== lastBackdropSuppression) { |
1341 | 1343 | body.dataset.homeBackdropSuppressed = backdropSuppression; |
|
1370 | 1372 | root.style.setProperty("--home-image-base-contrast", "1"); |
1371 | 1373 | root.style.setProperty("--home-image-silhouette-layer-opacity", "0"); |
1372 | 1374 | setAtmosphere(1); |
1373 | | - setLowerLayer(1); |
| 1375 | + setLowerLayer(1, 1); |
1374 | 1376 | body.dataset.homeBackdropSuppressed = "0"; |
1375 | 1377 | lastBackdropSuppression = "0"; |
1376 | 1378 | window.dispatchEvent(new Event("home-transition-sync")); |
|
1784 | 1786 | let lastPortraitState = portraitQuery.matches; |
1785 | 1787 | let loadingFadeScheduled = false; |
1786 | 1788 | let loadingFinished = false; |
| 1789 | + let revealTimer = 0; |
1787 | 1790 |
|
1788 | 1791 | const clamp = (value, min, max) => Math.min(max, Math.max(min, value)); |
1789 | 1792 | const createLayout = (x, y, scale, rotate) => ({ x, y, scale, rotate }); |
1790 | 1793 | const getSide = (offset) => (offset === 0 ? "front" : offset < 0 ? "left" : "right"); |
1791 | 1794 |
|
1792 | | - const setStackLoading = (isLoading) => { |
1793 | | - shell?.setAttribute("data-stack-loading", isLoading ? "true" : "false"); |
1794 | | - stack.setAttribute("aria-busy", isLoading ? "true" : "false"); |
| 1795 | + const setStackLoadingState = (state) => { |
| 1796 | + shell?.setAttribute("data-stack-loading", state); |
| 1797 | + stack.setAttribute("aria-busy", state === "false" ? "false" : "true"); |
1795 | 1798 | }; |
1796 | 1799 |
|
1797 | 1800 | const finishStackLoading = () => { |
|
1800 | 1803 | } |
1801 | 1804 |
|
1802 | 1805 | loadingFadeScheduled = true; |
1803 | | - requestAnimationFrame(() => { |
1804 | | - requestAnimationFrame(() => { |
1805 | | - loadingFadeScheduled = false; |
1806 | | - loadingFinished = true; |
| 1806 | + setStackLoadingState("settling"); |
| 1807 | + window.setTimeout(() => { |
| 1808 | + loadingFadeScheduled = false; |
| 1809 | + loadingFinished = true; |
| 1810 | + stack.dataset.stackReady = "revealing"; |
| 1811 | + setStackLoadingState("false"); |
| 1812 | + window.clearTimeout(revealTimer); |
| 1813 | + revealTimer = window.setTimeout(() => { |
1807 | 1814 | stack.dataset.stackReady = "true"; |
1808 | | - setStackLoading(false); |
1809 | | - }); |
1810 | | - }); |
| 1815 | + }, 760 + Math.max(0, total - 1) * 88); |
| 1816 | + }, 280); |
1811 | 1817 | }; |
1812 | 1818 |
|
1813 | 1819 | const buildLayouts = (cardWidth) => { |
1814 | 1820 | const steps = portraitQuery.matches |
1815 | 1821 | ? [ |
1816 | 1822 | { x: 0, scale: 1, rotate: 0 }, |
1817 | | - { x: cardWidth * 0.228, scale: 0.872, rotate: 5.1 }, |
1818 | | - { x: cardWidth * 0.366, scale: 0.744, rotate: 8.4 }, |
1819 | | - { x: cardWidth * 0.462, scale: 0.63, rotate: 11.1 }, |
1820 | | - { x: cardWidth * 0.528, scale: 0.538, rotate: 13.5 }, |
1821 | | - { x: cardWidth * 0.586, scale: 0.476, rotate: 15.3 } |
| 1823 | + { x: cardWidth * 0.228, scale: 0.864, rotate: 5.1 }, |
| 1824 | + { x: cardWidth * 0.366, scale: 0.73, rotate: 8.4 }, |
| 1825 | + { x: cardWidth * 0.462, scale: 0.612, rotate: 11.1 }, |
| 1826 | + { x: cardWidth * 0.528, scale: 0.514, rotate: 13.5 }, |
| 1827 | + { x: cardWidth * 0.586, scale: 0.446, rotate: 15.3 } |
1822 | 1828 | ] |
1823 | 1829 | : [ |
1824 | 1830 | { x: 0, scale: 1, rotate: 0 }, |
1825 | | - { x: cardWidth * 0.238, scale: 0.886, rotate: 4.6 }, |
1826 | | - { x: cardWidth * 0.386, scale: 0.762, rotate: 7.2 }, |
1827 | | - { x: cardWidth * 0.486, scale: 0.654, rotate: 9.8 }, |
1828 | | - { x: cardWidth * 0.55, scale: 0.566, rotate: 11.8 }, |
1829 | | - { x: cardWidth * 0.604, scale: 0.502, rotate: 13.6 } |
| 1831 | + { x: cardWidth * 0.238, scale: 0.878, rotate: 4.6 }, |
| 1832 | + { x: cardWidth * 0.386, scale: 0.748, rotate: 7.2 }, |
| 1833 | + { x: cardWidth * 0.486, scale: 0.638, rotate: 9.8 }, |
| 1834 | + { x: cardWidth * 0.55, scale: 0.544, rotate: 11.8 }, |
| 1835 | + { x: cardWidth * 0.604, scale: 0.482, rotate: 13.6 } |
1830 | 1836 | ]; |
1831 | 1837 |
|
1832 | 1838 | return steps.reduce((layouts, step, depth) => { |
|
1940 | 1946 |
|
1941 | 1947 | stack.classList.toggle("is-dragging", isDragging); |
1942 | 1948 |
|
1943 | | - cards.forEach((card, index) => { |
| 1949 | + cards.forEach((card, index) => { |
| 1950 | + card.style.setProperty("--stack-reveal-order", String(index)); |
1944 | 1951 | const offset = index - activeIndex; |
1945 | 1952 | let visual = getLayoutForOffset(offset); |
1946 | 1953 | let zIndex = getZIndex(offset); |
|
2235 | 2242 | stack.addEventListener("pointerleave", (event) => clearPointer(event, { snap: true })); |
2236 | 2243 |
|
2237 | 2244 | metrics = measureMetrics(); |
2238 | | - setStackLoading(true); |
| 2245 | + setStackLoadingState("true"); |
2239 | 2246 | stack.dataset.stackReady = "false"; |
2240 | 2247 | stack.classList.add("discipline-stack-viewport--static"); |
2241 | 2248 | applyState(); |
|
0 commit comments