From 4d27cb5dc13628a45ac0ab305511ce001784929d Mon Sep 17 00:00:00 2001 From: A Charlwood Date: Mon, 9 Feb 2026 13:26:44 +0000 Subject: [PATCH] Task 9: Implement scroll animations and responsive design - Add IntersectionObserver-based scroll reveal for all sections - Sections start at opacity:0/translateY(24px), animate to visible on scroll - Hero section immediately visible (above fold, no animation) - Staggered child card/item animations with 60ms delay per item - Animations fire once only (observer unobserved after reveal) - Fix hover transform specificity for cards, education, projects - Fix vital-card hover at hero-level specificity Co-Authored-By: Claude Opus 4.6 --- 4-vitals-monitor.html | 108 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/4-vitals-monitor.html b/4-vitals-monitor.html index 19182e3..2f6b487 100644 --- a/4-vitals-monitor.html +++ b/4-vitals-monitor.html @@ -755,6 +755,72 @@ margin-top: 2px; } + /* ========================================= + SCROLL REVEAL ANIMATIONS + ========================================= */ + + .cv-main section { + opacity: 0; + transform: translateY(24px); + transition: opacity 0.6s ease, transform 0.6s ease; + } + + .cv-main section.visible { + opacity: 1; + transform: translateY(0); + } + + /* Hero section should be immediately visible (above fold) */ + .cv-main section.hero { + opacity: 1; + transform: none; + transition: none; + } + + /* Staggered child card/item animations */ + .cv-main section .vital-card, + .cv-main section .skill-item, + .cv-main section .timeline-entry, + .cv-main section .education-card, + .cv-main section .project-card, + .cv-main section .contact-item { + opacity: 0; + transform: translateY(16px); + transition: opacity 0.5s ease, transform 0.5s ease; + } + + .cv-main section.visible .vital-card, + .cv-main section.visible .skill-item, + .cv-main section.visible .timeline-entry, + .cv-main section.visible .education-card, + .cv-main section.visible .project-card, + .cv-main section.visible .contact-item { + opacity: 1; + transform: translateY(0); + } + + /* Re-enable hover transforms at matching specificity */ + .cv-main section.visible .education-card:hover, + .cv-main section.visible .project-card:hover { + transform: translateY(-2px); + } + + .cv-main section.visible .timeline-entry .timeline-card:hover { + transform: scale(1.01); + } + + /* Hero vital cards get their own reveal since hero is always visible */ + .cv-main .hero .vital-card { + opacity: 1; + transform: none; + transition: box-shadow 0.3s ease, transform 0.3s ease; + } + + .cv-main .hero .vital-card:hover { + box-shadow: var(--shadow-md); + transform: translateY(-2px); + } + /* ========================================= FOOTER ========================================= */ @@ -1686,9 +1752,10 @@ var cvContent = document.getElementById('cv-content'); cvContent.classList.add('revealed'); - // Initialize nav tracking, smooth scroll, and skill gauges after reveal + // Initialize nav tracking, smooth scroll, skill gauges, and scroll reveal after reveal initNavTracking(); initSkillGauges(); + initScrollReveal(); }, 500); } @@ -1777,6 +1844,45 @@ gaugeObserver.observe(skillsSection); } + /* ========================================= + SCROLL REVEAL: Sections & staggered children + ========================================= */ + + function initScrollReveal() { + var sections = document.querySelectorAll('.cv-main section'); + + var revealObserver = new IntersectionObserver(function(entries) { + entries.forEach(function(entry) { + if (entry.isIntersecting) { + var section = entry.target; + section.classList.add('visible'); + + // Stagger child cards/items + var children = section.querySelectorAll( + '.vital-card, .skill-item, .timeline-entry, .education-card, .project-card, .contact-item' + ); + children.forEach(function(child, i) { + child.style.transitionDelay = (i * 60) + 'ms'; + }); + + // Only animate once — stop observing after reveal + revealObserver.unobserve(section); + } + }); + }, { + threshold: 0.15 + }); + + sections.forEach(function(section) { + // Hero is always visible (above the fold), skip it + if (section.classList.contains('hero')) { + section.classList.add('visible'); + return; + } + revealObserver.observe(section); + }); + } + })();