@@ -1247,37 +1247,37 @@ <h3>Persistent Configuration Layer</h3>
12471247 < div class ="container ">
12481248 < div class ="metrics-grid ">
12491249 < div class ="metric-item ">
1250- < div class ="metric-value "> 94 %</ div >
1250+ < div class ="metric-value " data-target =" 94 " data-suffix =" % " > 0 %</ div >
12511251 < div class ="metric-label "> Reduction in Setup Overhead</ div >
12521252 </ div >
12531253 < div class ="metric-item ">
1254- < div class ="metric-value "> 73 %</ div >
1254+ < div class ="metric-value " data-target =" 73 " data-suffix =" % " > 0 %</ div >
12551255 < div class ="metric-label "> Less Context Switching</ div >
12561256 </ div >
12571257 < div class ="metric-item ">
1258- < div class ="metric-value "> 2.4x </ div >
1258+ < div class ="metric-value " data-target =" 2.4 " data-suffix =" x " data-decimals =" 1 " > 0x </ div >
12591259 < div class ="metric-label "> Sprint Velocity Multiplier</ div >
12601260 </ div >
12611261 < div class ="metric-item ">
1262- < div class ="metric-value "> 37 %</ div >
1262+ < div class ="metric-value " data-target =" 37 " data-suffix =" % " > 0 %</ div >
12631263 < div class ="metric-label "> Faster Headcount Scaling</ div >
12641264 </ div >
12651265 </ div >
12661266 < div class ="metrics-grid " style ="margin-top: 2rem; padding-top: 2rem; border-top: 1px solid var(--border-subtle); ">
12671267 < div class ="metric-item ">
1268- < div class ="metric-value "> 147</ div >
1268+ < div class ="metric-value " data-target =" 147 " data-suffix ="" > 0 </ div >
12691269 < div class ="metric-label "> Eng Hours Saved/Quarter</ div >
12701270 </ div >
12711271 < div class ="metric-item ">
1272- < div class ="metric-value "> 89 %</ div >
1272+ < div class ="metric-value " data-target =" 89 " data-suffix =" % " > 0 %</ div >
12731273 < div class ="metric-label "> Fewer Config Incidents</ div >
12741274 </ div >
12751275 < div class ="metric-item ">
1276- < div class ="metric-value "> $2.3M </ div >
1276+ < div class ="metric-value " data-target =" 2.3 " data-prefix =" $ " data-suffix =" M " data-decimals =" 1 " > $0M </ div >
12771277 < div class ="metric-label "> Annual Productivity Gains*</ div >
12781278 </ div >
12791279 < div class ="metric-item ">
1280- < div class ="metric-value "> 4.9</ div >
1280+ < div class ="metric-value " data-target =" 4.9 " data-suffix ="" data-decimals =" 1 " > 0 </ div >
12811281 < div class ="metric-label "> Engineer Satisfaction (NPS)</ div >
12821282 </ div >
12831283 </ div >
@@ -1641,6 +1641,148 @@ <h3>Stakeholder Communications</h3>
16411641 } ;
16421642 document . addEventListener ( 'mousemove' , window . __crabMouseMoveHandler ) ;
16431643 }
1644+
1645+ // ANIMATED METRIC COUNTERS - count up when scrolled into view
1646+ const metricObserver = new IntersectionObserver ( ( entries ) => {
1647+ entries . forEach ( entry => {
1648+ if ( entry . isIntersecting && ! entry . target . dataset . animated ) {
1649+ entry . target . dataset . animated = 'true' ;
1650+ const target = parseFloat ( entry . target . dataset . target ) ;
1651+ const decimals = parseInt ( entry . target . dataset . decimals ) || 0 ;
1652+ const prefix = entry . target . dataset . prefix || '' ;
1653+ const suffix = entry . target . dataset . suffix || '' ;
1654+ const duration = 1500 ;
1655+ const startTime = performance . now ( ) ;
1656+
1657+ const animate = ( currentTime ) => {
1658+ const elapsed = currentTime - startTime ;
1659+ const progress = Math . min ( elapsed / duration , 1 ) ;
1660+ // Ease out cubic
1661+ const eased = 1 - Math . pow ( 1 - progress , 3 ) ;
1662+ const current = target * eased ;
1663+ entry . target . textContent = prefix + current . toFixed ( decimals ) + suffix ;
1664+ if ( progress < 1 ) {
1665+ requestAnimationFrame ( animate ) ;
1666+ }
1667+ } ;
1668+ requestAnimationFrame ( animate ) ;
1669+ }
1670+ } ) ;
1671+ } , { threshold : 0.5 } ) ;
1672+
1673+ document . querySelectorAll ( '.metric-value[data-target]' ) . forEach ( el => {
1674+ metricObserver . observe ( el ) ;
1675+ } ) ;
1676+
1677+ // BUBBLE POP - click floating bubbles to pop them
1678+ let bubbleScore = 0 ;
1679+ document . addEventListener ( 'click' , ( e ) => {
1680+ if ( e . target . classList . contains ( 'bubble' ) ) {
1681+ e . target . style . animation = 'none' ;
1682+ e . target . style . transform = 'scale(1.5)' ;
1683+ e . target . style . opacity = '0' ;
1684+ e . target . style . transition = 'all 0.2s ease-out' ;
1685+ bubbleScore ++ ;
1686+
1687+ // Show score popup
1688+ const popup = document . createElement ( 'div' ) ;
1689+ popup . textContent = '+1 🦀' ;
1690+ popup . style . cssText = `
1691+ position: fixed;
1692+ left: ${ e . clientX } px;
1693+ top: ${ e . clientY } px;
1694+ color: var(--crab-coral);
1695+ font-size: 1rem;
1696+ font-weight: bold;
1697+ pointer-events: none;
1698+ z-index: 9999;
1699+ animation: pop-score 0.8s ease-out forwards;
1700+ ` ;
1701+ document . body . appendChild ( popup ) ;
1702+ setTimeout ( ( ) => popup . remove ( ) , 800 ) ;
1703+
1704+ // Achievement at 10
1705+ if ( bubbleScore === 10 ) {
1706+ const achievement = document . createElement ( 'div' ) ;
1707+ achievement . innerHTML = '🦀 Bubble Popper Unlocked! 🦀' ;
1708+ achievement . style . cssText = `
1709+ position: fixed;
1710+ top: 20%;
1711+ left: 50%;
1712+ transform: translateX(-50%);
1713+ background: var(--crab-shell);
1714+ color: white;
1715+ padding: 1rem 2rem;
1716+ border-radius: 8px;
1717+ font-weight: bold;
1718+ z-index: 10000;
1719+ animation: achievement-pop 2s ease-out forwards;
1720+ ` ;
1721+ document . body . appendChild ( achievement ) ;
1722+ setTimeout ( ( ) => achievement . remove ( ) , 2000 ) ;
1723+ }
1724+
1725+ setTimeout ( ( ) => e . target . remove ( ) , 200 ) ;
1726+ }
1727+ } ) ;
1728+
1729+ // Make bubbles clickable
1730+ const style = document . createElement ( 'style' ) ;
1731+ style . textContent = `
1732+ .bubble { pointer-events: auto !important; cursor: pointer; }
1733+ .bubble:hover { transform: scale(1.2); }
1734+ @keyframes pop-score {
1735+ 0% { transform: translateY(0) scale(1); opacity: 1; }
1736+ 100% { transform: translateY(-50px) scale(1.5); opacity: 0; }
1737+ }
1738+ @keyframes achievement-pop {
1739+ 0% { transform: translateX(-50%) scale(0); opacity: 0; }
1740+ 20% { transform: translateX(-50%) scale(1.1); opacity: 1; }
1741+ 30% { transform: translateX(-50%) scale(1); }
1742+ 80% { transform: translateX(-50%) scale(1); opacity: 1; }
1743+ 100% { transform: translateX(-50%) scale(0.8); opacity: 0; }
1744+ }
1745+ @keyframes scurry {
1746+ 0% { left: -30px; transform: scaleX(1); }
1747+ 100% { left: calc(100vw + 30px); transform: scaleX(1); }
1748+ }
1749+ .scurry-crab {
1750+ position: fixed;
1751+ font-size: 1rem;
1752+ z-index: 999;
1753+ pointer-events: none;
1754+ animation: scurry 1.5s linear forwards;
1755+ }
1756+ ` ;
1757+ document . head . appendChild ( style ) ;
1758+
1759+ // SECTION TRANSITION CRABS - crabs scurry across when entering new sections
1760+ let lastSection = null ;
1761+ const sectionObserver = new IntersectionObserver ( ( entries ) => {
1762+ entries . forEach ( entry => {
1763+ if ( entry . isIntersecting && entry . target !== lastSection ) {
1764+ lastSection = entry . target ;
1765+ // Spawn 3-5 scurrying crabs
1766+ const numCrabs = Math . floor ( Math . random ( ) * 3 ) + 3 ;
1767+ for ( let i = 0 ; i < numCrabs ; i ++ ) {
1768+ setTimeout ( ( ) => {
1769+ const crab = document . createElement ( 'div' ) ;
1770+ crab . className = 'scurry-crab' ;
1771+ crab . textContent = '🦀' ;
1772+ crab . style . top = ( entry . target . offsetTop - 20 + Math . random ( ) * 40 ) + 'px' ;
1773+ crab . style . animationDuration = ( 1 + Math . random ( ) * 0.5 ) + 's' ;
1774+ document . body . appendChild ( crab ) ;
1775+ setTimeout ( ( ) => crab . remove ( ) , 2000 ) ;
1776+ } , i * 150 ) ;
1777+ }
1778+ }
1779+ } ) ;
1780+ } , { threshold : 0.3 } ) ;
1781+
1782+ // Observe major sections
1783+ document . querySelectorAll ( '.bento-section, .metrics-section, .layout-section, .manifesto, .commands-section' ) . forEach ( section => {
1784+ sectionObserver . observe ( section ) ;
1785+ } ) ;
16441786 </ script >
16451787</ body >
16461788</ html >
0 commit comments