|
3 | 3 |
|
4 | 4 | const body = document.body; |
5 | 5 | const base = (body?.getAttribute('data-base') || '.').trim(); |
6 | | - const assetVersion = '20260410g'; |
| 6 | + const assetVersion = '20260410i'; |
7 | 7 | const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; |
8 | 8 | const SETTLE_PASS_DELAYS = [0, 140, 320, 560]; |
9 | 9 | const simpleIcon = (name) => `https://cdn.jsdelivr.net/npm/simple-icons@v11/icons/${name}.svg`; |
|
51 | 51 | "Diving into the core of operating systems and device environments. My work involves Custom ROM development and low-level system exploration, studying how device architectures function from the inside out to build highly optimized environments.", |
52 | 52 | arsenalKind: "engineering", |
53 | 53 | arsenal: [ |
54 | | - { iconSvg: iconSvg('<path d="M6.1 7.2h7.8"/><path d="M7.3 5.1v2.1M12.7 5.1v2.1"/><path d="M6.8 7.2 5.7 12h8.6l-1.1-4.8"/><path d="M7.6 12v2.2M12.4 12v2.2"/><path d="M6.2 14.2h1.7M12.1 14.2h1.7"/>'), label: "Custom ROM Building" }, |
| 54 | + { iconSvg: iconSvg('<path d="M6.3 7.2h7.4a1.8 1.8 0 0 1 1.8 1.8v3a1.8 1.8 0 0 1-1.8 1.8H6.3A1.8 1.8 0 0 1 4.5 12V9a1.8 1.8 0 0 1 1.8-1.8Z"/><path d="m7.4 6.1-1-1.4"/><path d="m12.6 6.1 1-1.4"/><circle cx="8.8" cy="10" r=".44" fill="currentColor" stroke="none"/><circle cx="11.2" cy="10" r=".44" fill="currentColor" stroke="none"/><path d="M7.6 13.8v1.5M12.4 13.8v1.5"/><path d="M4.5 9.5H3.3M16.7 9.5h-1.2"/>'), label: "Custom ROM Building" }, |
55 | 55 | { iconSvg: iconSvg('<path d="M10 4.2 14 5.7v3.8c0 2.6-1.6 4.8-4 5.9-2.4-1.1-4-3.3-4-5.9V5.7L10 4.2Z"/><path d="m12.7 12.7 2.6 2.6"/><circle cx="12.1" cy="12.1" r="2.3"/>'), label: "iOS Security Analysis" }, |
56 | 56 | { iconSvg: iconSvg('<path d="m6.4 6.2-3.1 3.8 3.1 3.8"/><path d="m13.6 6.2 3.1 3.8-3.1 3.8"/><path d="m11 4.8-2 10.4"/>'), label: "Reverse Engineering" }, |
57 | 57 | { iconSvg: iconSvg('<rect x="4.1" y="4.5" width="11.8" height="8.2" rx="1.8"/><path d="M6.5 15.5h7"/><path d="M8 12.7v2.8M12 12.7v2.8"/>'), label: "System Virtualization" } |
|
1607 | 1607 | const steps = portraitQuery.matches |
1608 | 1608 | ? [ |
1609 | 1609 | { x: 0, scale: 1, rotate: 0 }, |
1610 | | - { x: cardWidth * 0.21, scale: 0.892, rotate: 4.9 }, |
1611 | | - { x: cardWidth * 0.334, scale: 0.786, rotate: 7.8 }, |
1612 | | - { x: cardWidth * 0.418, scale: 0.692, rotate: 10.2 }, |
1613 | | - { x: cardWidth * 0.474, scale: 0.614, rotate: 12.2 } |
| 1610 | + { x: cardWidth * 0.228, scale: 0.872, rotate: 5.1 }, |
| 1611 | + { x: cardWidth * 0.366, scale: 0.744, rotate: 8.4 }, |
| 1612 | + { x: cardWidth * 0.462, scale: 0.63, rotate: 11.1 }, |
| 1613 | + { x: cardWidth * 0.528, scale: 0.538, rotate: 13.5 } |
1614 | 1614 | ] |
1615 | 1615 | : [ |
1616 | 1616 | { x: 0, scale: 1, rotate: 0 }, |
1617 | | - { x: cardWidth * 0.224, scale: 0.904, rotate: 4.4 }, |
1618 | | - { x: cardWidth * 0.356, scale: 0.8, rotate: 6.8 }, |
1619 | | - { x: cardWidth * 0.446, scale: 0.706, rotate: 8.9 }, |
1620 | | - { x: cardWidth * 0.506, scale: 0.626, rotate: 10.5 } |
| 1617 | + { x: cardWidth * 0.238, scale: 0.886, rotate: 4.6 }, |
| 1618 | + { x: cardWidth * 0.386, scale: 0.762, rotate: 7.2 }, |
| 1619 | + { x: cardWidth * 0.486, scale: 0.654, rotate: 9.8 }, |
| 1620 | + { x: cardWidth * 0.55, scale: 0.566, rotate: 11.8 } |
1621 | 1621 | ]; |
1622 | 1622 |
|
1623 | 1623 | return steps.reduce((layouts, step, depth) => { |
|
1719 | 1719 | const active = disciplines[activeIndex]; |
1720 | 1720 | stack.setAttribute("aria-label", `Core disciplines cards. ${active.title} is in focus.`); |
1721 | 1721 | stack.dataset.swipeEnabled = portraitQuery.matches ? "true" : "false"; |
1722 | | - if (portraitQuery.matches) { |
1723 | | - stack.removeAttribute("tabindex"); |
1724 | | - } else { |
1725 | | - stack.tabIndex = 0; |
1726 | | - } |
| 1722 | + stack.removeAttribute("tabindex"); |
1727 | 1723 | }; |
1728 | 1724 |
|
1729 | 1725 | const applyState = ({ dragProgress = 0 } = {}) => { |
1730 | 1726 | const isDragging = portraitQuery.matches && Math.abs(dragProgress) > 0.001; |
1731 | 1727 | const dragSign = dragProgress === 0 ? 0 : Math.sign(dragProgress); |
1732 | 1728 | const currentMetrics = getMetrics(); |
1733 | 1729 | const dragMagnitude = Math.abs(dragProgress); |
| 1730 | + const inwardSide = dragSign === 0 ? 0 : -dragSign; |
| 1731 | + const hasTarget = |
| 1732 | + dragSign === 0 || |
| 1733 | + (dragSign < 0 ? activeIndex < total - 1 : activeIndex > 0); |
1734 | 1734 |
|
1735 | 1735 | stack.classList.toggle("is-dragging", isDragging); |
1736 | 1736 |
|
1737 | 1737 | cards.forEach((card, index) => { |
1738 | 1738 | const offset = index - activeIndex; |
1739 | 1739 | let visual = getLayoutForOffset(offset); |
| 1740 | + let zIndex = getZIndex(offset); |
1740 | 1741 |
|
1741 | | - if (isDragging && offset === 0 && dragSign !== 0) { |
| 1742 | + if (isDragging && hasTarget && offset === 0 && dragSign !== 0) { |
1742 | 1743 | visual = createLayout( |
1743 | | - currentMetrics.cardWidth * 0.66 * dragMagnitude * dragSign, |
| 1744 | + currentMetrics.cardWidth * 0.58 * dragMagnitude * dragSign, |
1744 | 1745 | 0, |
1745 | | - 1 - dragMagnitude * 0.028, |
1746 | | - dragSign * 10.5 * dragMagnitude |
| 1746 | + 1 - dragMagnitude * 0.024, |
| 1747 | + dragSign * 9.1 * dragMagnitude |
1747 | 1748 | ); |
| 1749 | + } else if (isDragging && hasTarget && offset !== 0) { |
| 1750 | + const side = Math.sign(offset); |
| 1751 | + const depth = Math.min(Math.abs(offset), total - 1); |
| 1752 | + const inwardScaleLift = [0, 0.032, 0.026, 0.02, 0.016][depth] || 0.016; |
| 1753 | + const outwardScaleDrop = [0, 0.022, 0.028, 0.032, 0.036][depth] || 0.036; |
| 1754 | + const inwardXPull = [0, 0.18, 0.15, 0.13, 0.11][depth] || 0.11; |
| 1755 | + const outwardXPush = [0, 0.14, 0.18, 0.22, 0.26][depth] || 0.26; |
| 1756 | + const inwardRotateEase = [0, 0.18, 0.14, 0.12, 0.1][depth] || 0.1; |
| 1757 | + const outwardRotateBoost = [0, 0.08, 0.1, 0.12, 0.14][depth] || 0.14; |
| 1758 | + |
| 1759 | + if (side === inwardSide) { |
| 1760 | + visual = createLayout( |
| 1761 | + visual.x * (1 - inwardXPull * dragMagnitude), |
| 1762 | + 0, |
| 1763 | + visual.scale + inwardScaleLift * dragMagnitude, |
| 1764 | + visual.rotate * (1 - inwardRotateEase * dragMagnitude) |
| 1765 | + ); |
| 1766 | + zIndex += 8 - depth; |
| 1767 | + } else if (side === dragSign) { |
| 1768 | + visual = createLayout( |
| 1769 | + visual.x * (1 + outwardXPush * dragMagnitude), |
| 1770 | + 0, |
| 1771 | + visual.scale - outwardScaleDrop * dragMagnitude, |
| 1772 | + visual.rotate * (1 + outwardRotateBoost * dragMagnitude) |
| 1773 | + ); |
| 1774 | + zIndex -= 5 + depth; |
| 1775 | + } |
1748 | 1776 | } |
1749 | 1777 |
|
1750 | 1778 | const appearance = getDepthAppearance(offset); |
|
1756 | 1784 | card.classList.toggle("is-active", offset === 0); |
1757 | 1785 | card.classList.toggle("is-neighbor", isNeighbor); |
1758 | 1786 | card.setAttribute("aria-hidden", offset === 0 ? "false" : "true"); |
1759 | | - card.style.zIndex = String(getZIndex(offset)); |
| 1787 | + card.style.zIndex = String(zIndex); |
1760 | 1788 | card.style.transform = formatTransform(visual); |
1761 | 1789 | card.style.setProperty("--discipline-depth-dim", appearance.dim.toFixed(3)); |
1762 | 1790 | card.style.setProperty("--discipline-surface-lift", appearance.lift.toFixed(3)); |
|
1895 | 1923 |
|
1896 | 1924 | event.preventDefault(); |
1897 | 1925 | const width = Math.max(stack.clientWidth, 1); |
1898 | | - const raw = deltaX / (width * 0.2); |
| 1926 | + const raw = deltaX / (width * 0.46); |
1899 | 1927 | const direction = raw === 0 ? 0 : raw > 0 ? -1 : 1; |
1900 | 1928 | const outOfBounds = (direction < 0 && activeIndex === 0) || (direction > 0 && activeIndex === total - 1); |
1901 | | - const limit = outOfBounds ? 0.2 : 0.92; |
1902 | | - const resistance = outOfBounds ? 2.05 : 0.88; |
| 1929 | + const limit = outOfBounds ? 0.18 : 0.94; |
| 1930 | + const resistance = outOfBounds ? 1.8 : 0.84; |
1903 | 1931 | const progress = clamp(Math.sign(raw || 0) * limit * (1 - Math.exp(-Math.abs(raw) * resistance)), -limit, limit); |
1904 | 1932 | pointerState.progress = progress; |
1905 | 1933 | applyState({ dragProgress: progress }); |
|
1955 | 1983 | stack.addEventListener("pointerup", onPointerUp); |
1956 | 1984 | stack.addEventListener("pointercancel", (event) => clearPointer(event, { snap: true })); |
1957 | 1985 | stack.addEventListener("pointerleave", (event) => clearPointer(event, { snap: true })); |
1958 | | - stack.addEventListener("keydown", (event) => { |
1959 | | - if (portraitQuery.matches) { |
1960 | | - return; |
1961 | | - } |
1962 | | - |
1963 | | - if (event.key === "ArrowLeft") { |
1964 | | - event.preventDefault(); |
1965 | | - rotate(-1); |
1966 | | - } else if (event.key === "ArrowRight") { |
1967 | | - event.preventDefault(); |
1968 | | - rotate(1); |
1969 | | - } |
1970 | | - }); |
1971 | | - |
1972 | 1986 | metrics = measureMetrics(); |
1973 | 1987 | stack.classList.add("discipline-stack-viewport--static"); |
1974 | 1988 | applyState(); |
|
0 commit comments