feat(phase3): implement scroll animations with Intersection Observer
SUMMARY: Implemented Phase 3 Task 3.1: Scroll Animations system using Intersection Observer API for smooth, performant scroll-triggered animations on homepage. CHANGES: 1. Created scroll-animations.js (new): - Intersection Observer-based animation system - Supports 7 animation types (fade-in, slide-up/down/left/right, scale-in, rotate-in) - Staggered delays via data-stagger attribute - Respects prefers-reduced-motion - Auto-initializes, can be disabled globally - Custom 'scroll-animated' event for other components 2. Enhanced tractatus-theme.css: - Added 100+ lines of scroll animation CSS - Smooth transitions using CSS transforms (GPU-accelerated) - Data-attribute based animation selection - Default animation (slide-up) if none specified - Accessibility: respects prefers-reduced-motion 3. Updated index.html (homepage): - Added scroll-animations.js script - Value proposition: slide-up animation - Three audience cards: scale-in with 100/200/300ms stagger - Capabilities heading: fade-in - Six capability cards: slide-up with 100-600ms stagger - All animations trigger on scroll (not page load) ANIMATION TYPES: - fade-in: Opacity 0 → 1 - slide-up: Translates from bottom (+2rem) → 0 - slide-down: Translates from top (-2rem) → 0 - slide-left: Translates from right (+2rem) → 0 - slide-right: Translates from left (-2rem) → 0 - scale-in: Scales from 0.95 → 1 with opacity - rotate-in: Rotates from 12deg → 0 with scale USAGE EXAMPLE: <div class="animate-on-scroll" data-animation="slide-up" data-stagger="200"> Content here </div> ACCESSIBILITY: ✓ Respects prefers-reduced-motion (disables all animations) ✓ GPU-accelerated transforms (60fps on modern devices) ✓ Progressive enhancement (graceful degradation) ✓ Zero CSP violations maintained PERFORMANCE: - Intersection Observer (better than scroll listeners) - Unobserves elements after animation (memory efficient) - Supports data-animate-repeat for repeatable animations - CSS transitions (GPU-accelerated) UI_TRANSFORMATION_PROJECT_PLAN.md: ✓ Phase 3 Task 3.1.1: Implemented Intersection Observer ✓ Phase 3 Task 3.1.2: Added scroll animations to homepage NEXT STEPS: - Phase 3 Task 3.1.3: Apply to all pages site-wide - Phase 3 Task 3.2: Interactive architecture diagram 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1ccaf14c5b
commit
fa01644c17
3 changed files with 255 additions and 11 deletions
|
|
@ -823,6 +823,112 @@ h3 { letter-spacing: -0.015em; }
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
* SCROLL ANIMATIONS
|
||||
* Intersection Observer-based scroll animations
|
||||
* ======================================== */
|
||||
|
||||
/* Scroll animation initial states (before visible) */
|
||||
.animate-on-scroll {
|
||||
transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Fade in animation */
|
||||
.animate-on-scroll[data-animation="fade-in"] {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.animate-on-scroll[data-animation="fade-in"].is-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Slide up animation */
|
||||
.animate-on-scroll[data-animation="slide-up"] {
|
||||
opacity: 0;
|
||||
transform: translateY(2rem);
|
||||
}
|
||||
|
||||
.animate-on-scroll[data-animation="slide-up"].is-visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Slide down animation */
|
||||
.animate-on-scroll[data-animation="slide-down"] {
|
||||
opacity: 0;
|
||||
transform: translateY(-2rem);
|
||||
}
|
||||
|
||||
.animate-on-scroll[data-animation="slide-down"].is-visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Slide left animation */
|
||||
.animate-on-scroll[data-animation="slide-left"] {
|
||||
opacity: 0;
|
||||
transform: translateX(2rem);
|
||||
}
|
||||
|
||||
.animate-on-scroll[data-animation="slide-left"].is-visible {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* Slide right animation */
|
||||
.animate-on-scroll[data-animation="slide-right"] {
|
||||
opacity: 0;
|
||||
transform: translateX(-2rem);
|
||||
}
|
||||
|
||||
.animate-on-scroll[data-animation="slide-right"].is-visible {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* Scale in animation */
|
||||
.animate-on-scroll[data-animation="scale-in"] {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.animate-on-scroll[data-animation="scale-in"].is-visible {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
/* Rotate in animation */
|
||||
.animate-on-scroll[data-animation="rotate-in"] {
|
||||
opacity: 0;
|
||||
transform: rotate(12deg) scale(0.95);
|
||||
}
|
||||
|
||||
.animate-on-scroll[data-animation="rotate-in"].is-visible {
|
||||
opacity: 1;
|
||||
transform: rotate(0deg) scale(1);
|
||||
}
|
||||
|
||||
/* Default animation if no data-animation specified */
|
||||
.animate-on-scroll:not([data-animation]) {
|
||||
opacity: 0;
|
||||
transform: translateY(2rem);
|
||||
}
|
||||
|
||||
.animate-on-scroll:not([data-animation]).is-visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Respect user's motion preferences for scroll animations */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.animate-on-scroll {
|
||||
opacity: 1 !important;
|
||||
transform: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
* DARK MODE SUPPORT (Future)
|
||||
* Placeholder for dark mode implementation
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@
|
|||
|
||||
<!-- Value Proposition -->
|
||||
<section class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16" aria-labelledby="core-insight">
|
||||
<div class="bg-amber-50 border-l-4 border-amber-500 p-6 rounded-r-lg">
|
||||
<div class="bg-amber-50 border-l-4 border-amber-500 p-6 rounded-r-lg animate-on-scroll" data-animation="slide-up">
|
||||
<h2 id="core-insight" class="text-2xl font-bold text-amber-900 mb-3" data-i18n="value_prop.heading">A Starting Point</h2>
|
||||
<p class="text-amber-800 text-lg" data-i18n-html="value_prop.text">
|
||||
Instead of hoping AI systems <em>"behave correctly,"</em> we propose <strong>structural constraints</strong>
|
||||
|
|
@ -106,7 +106,7 @@ We recognize this is one small step in addressing AI safety challenges. Explore
|
|||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
|
||||
<!-- Researcher Path → CrossReferenceValidator (Purple) -->
|
||||
<a href="/researcher.html" class="block bg-white rounded-xl shadow-lg border-l-4 overflow-visible hover:shadow-2xl transition-all duration-300 hover:-translate-y-1 relative group animate-fade-in-scale animate-delay-100" class="border-l-service-validator">
|
||||
<a href="/researcher.html" class="block bg-white rounded-xl shadow-lg border-l-4 overflow-visible hover:shadow-2xl transition-all duration-300 hover:-translate-y-1 relative group animate-on-scroll border-l-service-validator" data-animation="scale-in" data-stagger="100">
|
||||
<!-- Tooltip -->
|
||||
<div class="absolute bottom-full mb-4 left-1/2 transform -translate-x-1/2 bg-gray-900 text-white text-sm rounded-lg px-4 py-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-10 w-64 text-center shadow-xl">
|
||||
<span data-i18n="paths.researcher.tooltip">For AI safety researchers, academics, and scientists investigating LLM failure modes and governance architectures</span>
|
||||
|
|
@ -154,7 +154,7 @@ Explore the theoretical foundations, architectural constraints, and scholarly co
|
|||
</a>
|
||||
|
||||
<!-- Implementer Path → InstructionPersistenceClassifier (Indigo) -->
|
||||
<a href="/implementer.html" class="block bg-white rounded-xl shadow-lg border-l-4 overflow-visible hover:shadow-2xl transition-all duration-300 hover:-translate-y-1 relative group animate-fade-in-scale animate-delay-200" class="border-l-service-instruction">
|
||||
<a href="/implementer.html" class="block bg-white rounded-xl shadow-lg border-l-4 overflow-visible hover:shadow-2xl transition-all duration-300 hover:-translate-y-1 relative group animate-on-scroll border-l-service-instruction" data-animation="scale-in" data-stagger="200">
|
||||
<!-- Tooltip -->
|
||||
<div class="absolute bottom-full mb-4 left-1/2 transform -translate-x-1/2 bg-gray-900 text-white text-sm rounded-lg px-4 py-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-10 w-64 text-center shadow-xl">
|
||||
<span data-i18n="paths.implementer.tooltip">For software engineers, ML engineers, and technical teams building production AI systems</span>
|
||||
|
|
@ -202,7 +202,7 @@ Get hands-on with implementation guides, API documentation, and reference code e
|
|||
</a>
|
||||
|
||||
<!-- Leader Path → PluralisticDeliberationOrchestrator (Teal) -->
|
||||
<a href="/leader.html" class="block bg-white rounded-xl shadow-lg border-l-4 overflow-visible hover:shadow-2xl transition-all duration-300 hover:-translate-y-1 relative group animate-fade-in-scale animate-delay-300" class="border-l-service-deliberation">
|
||||
<a href="/leader.html" class="block bg-white rounded-xl shadow-lg border-l-4 overflow-visible hover:shadow-2xl transition-all duration-300 hover:-translate-y-1 relative group animate-on-scroll border-l-service-deliberation" data-animation="scale-in" data-stagger="300">
|
||||
<!-- Tooltip -->
|
||||
<div class="absolute bottom-full mb-4 left-1/2 transform -translate-x-1/2 bg-gray-900 text-white text-sm rounded-lg px-4 py-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-10 w-64 text-center shadow-xl">
|
||||
<span data-i18n="paths.leader.tooltip">For AI executives, research directors, startup founders, and strategic decision makers setting AI safety policy</span>
|
||||
|
|
@ -257,11 +257,11 @@ Navigate the business case, compliance requirements, and competitive advantages
|
|||
<!-- Key Capabilities -->
|
||||
<section class="bg-white py-16">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h2 class="text-3xl font-bold text-center text-gray-900 mb-12" data-i18n="capabilities.heading">Framework Capabilities</h2>
|
||||
<h2 class="text-3xl font-bold text-center text-gray-900 mb-12 animate-on-scroll" data-i18n="capabilities.heading" data-animation="fade-in">Framework Capabilities</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
|
||||
<div class="card-base card-interactive card-service instruction">
|
||||
<div class="card-base card-interactive card-service instruction animate-on-scroll" data-animation="slide-up" data-stagger="100">
|
||||
<div class="w-14 h-14 rounded-xl flex items-center justify-center mb-4" class="bg-gradient-service-instruction">
|
||||
<svg aria-hidden="true" class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
|
||||
|
|
@ -273,7 +273,7 @@ Quadrant-based classification (STR/OPS/TAC/SYS/STO) with time-persistence metada
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-base card-interactive card-service validator">
|
||||
<div class="card-base card-interactive card-service validator animate-on-scroll" data-animation="slide-up" data-stagger="200">
|
||||
<div class="w-14 h-14 rounded-xl flex items-center justify-center mb-4" class="bg-gradient-service-validator">
|
||||
<svg aria-hidden="true" class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
|
|
@ -285,7 +285,7 @@ Validates AI actions against explicit user instructions to prevent pattern-based
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-base card-interactive card-service boundary">
|
||||
<div class="card-base card-interactive card-service boundary animate-on-scroll" data-animation="slide-up" data-stagger="300">
|
||||
<div class="w-14 h-14 rounded-xl flex items-center justify-center mb-4" class="bg-gradient-service-boundary">
|
||||
<svg aria-hidden="true" class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
||||
|
|
@ -297,7 +297,7 @@ Implements Tractatus 12.1-12.7 boundaries - values decisions architecturally req
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-base card-interactive card-service pressure">
|
||||
<div class="card-base card-interactive card-service pressure animate-on-scroll" data-animation="slide-up" data-stagger="400">
|
||||
<div class="w-14 h-14 rounded-xl flex items-center justify-center mb-4" class="bg-gradient-service-pressure">
|
||||
<svg aria-hidden="true" class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
|
|
@ -309,7 +309,7 @@ Detects degraded operating conditions (token pressure, errors, complexity) and a
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-base card-interactive card-service metacognitive">
|
||||
<div class="card-base card-interactive card-service metacognitive animate-on-scroll" data-animation="slide-up" data-stagger="500">
|
||||
<div class="w-14 h-14 rounded-xl flex items-center justify-center mb-4" class="bg-gradient-service-metacognitive">
|
||||
<svg aria-hidden="true" class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||||
|
|
@ -321,7 +321,7 @@ AI self-checks alignment, coherence, safety before execution - structural pause-
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-base card-interactive card-service deliberation">
|
||||
<div class="card-base card-interactive card-service deliberation animate-on-scroll" data-animation="slide-up" data-stagger="600">
|
||||
<div class="w-14 h-14 rounded-xl flex items-center justify-center mb-4" class="bg-gradient-service-deliberation">
|
||||
<svg aria-hidden="true" class="w-7 h-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
|
||||
|
|
@ -395,6 +395,9 @@ Additional case studies and research findings documented in technical papers
|
|||
<script src="/js/i18n-simple.js?v=1760818106"></script>
|
||||
<script src="/js/components/language-selector.js?v=1760818106"></script>
|
||||
|
||||
<!-- Scroll Animations (Phase 3) -->
|
||||
<script src="/js/scroll-animations.js"></script>
|
||||
|
||||
<!-- Footer Component -->
|
||||
<script src="/js/components/footer.js"></script>
|
||||
|
||||
|
|
|
|||
135
public/js/scroll-animations.js
Normal file
135
public/js/scroll-animations.js
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/**
|
||||
* Scroll Animations using Intersection Observer
|
||||
* Tractatus Framework - Phase 3: Engagement & Interactive Features
|
||||
*
|
||||
* Provides smooth scroll-triggered animations for elements marked with .animate-on-scroll
|
||||
* Supports multiple animation types via data-animation attribute
|
||||
*/
|
||||
|
||||
class ScrollAnimations {
|
||||
constructor(options = {}) {
|
||||
this.observerOptions = {
|
||||
threshold: options.threshold || 0.1,
|
||||
rootMargin: options.rootMargin || '0px 0px -100px 0px'
|
||||
};
|
||||
|
||||
this.animations = {
|
||||
'fade-in': 'opacity-0 animate-fade-in',
|
||||
'slide-up': 'opacity-0 translate-y-8 animate-slide-up',
|
||||
'slide-down': 'opacity-0 -translate-y-8 animate-slide-down',
|
||||
'slide-left': 'opacity-0 translate-x-8 animate-slide-left',
|
||||
'slide-right': 'opacity-0 -translate-x-8 animate-slide-right',
|
||||
'scale-in': 'opacity-0 scale-95 animate-scale-in',
|
||||
'rotate-in': 'opacity-0 rotate-12 animate-rotate-in'
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Wait for DOM to be ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => this.observe());
|
||||
} else {
|
||||
this.observe();
|
||||
}
|
||||
|
||||
console.log('[ScrollAnimations] Initialized');
|
||||
}
|
||||
|
||||
observe() {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
this.animateElement(entry.target);
|
||||
|
||||
// Optional: unobserve after animation to improve performance
|
||||
// Only do this for elements without data-animate-repeat attribute
|
||||
if (!entry.target.hasAttribute('data-animate-repeat')) {
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
} else if (entry.target.hasAttribute('data-animate-repeat')) {
|
||||
// Reset animation for repeatable elements
|
||||
this.resetElement(entry.target);
|
||||
}
|
||||
});
|
||||
}, this.observerOptions);
|
||||
|
||||
// Find all elements with .animate-on-scroll class
|
||||
const elements = document.querySelectorAll('.animate-on-scroll');
|
||||
console.log(`[ScrollAnimations] Observing ${elements.length} elements`);
|
||||
|
||||
elements.forEach((el, index) => {
|
||||
// Add stagger delay if data-stagger attribute is present
|
||||
if (el.hasAttribute('data-stagger')) {
|
||||
const delay = parseInt(el.getAttribute('data-stagger')) || (index * 100);
|
||||
el.style.animationDelay = `${delay}ms`;
|
||||
}
|
||||
|
||||
// Apply initial animation classes based on data-animation attribute
|
||||
const animationType = el.getAttribute('data-animation') || 'fade-in';
|
||||
if (this.animations[animationType]) {
|
||||
// Remove any existing animation classes
|
||||
Object.values(this.animations).forEach(classes => {
|
||||
classes.split(' ').forEach(cls => el.classList.remove(cls));
|
||||
});
|
||||
|
||||
// Add initial state classes (will be removed when visible)
|
||||
const initialClasses = this.getInitialClasses(animationType);
|
||||
initialClasses.forEach(cls => el.classList.add(cls));
|
||||
}
|
||||
|
||||
observer.observe(el);
|
||||
});
|
||||
}
|
||||
|
||||
getInitialClasses(animationType) {
|
||||
// Return classes that represent the "before animation" state
|
||||
const map = {
|
||||
'fade-in': ['opacity-0'],
|
||||
'slide-up': ['opacity-0', 'translate-y-8'],
|
||||
'slide-down': ['opacity-0', '-translate-y-8'],
|
||||
'slide-left': ['opacity-0', 'translate-x-8'],
|
||||
'slide-right': ['opacity-0', '-translate-x-8'],
|
||||
'scale-in': ['opacity-0', 'scale-95'],
|
||||
'rotate-in': ['opacity-0', 'rotate-12']
|
||||
};
|
||||
|
||||
return map[animationType] || ['opacity-0'];
|
||||
}
|
||||
|
||||
animateElement(element) {
|
||||
// Remove initial state classes
|
||||
element.classList.remove('opacity-0', 'translate-y-8', '-translate-y-8', 'translate-x-8', '-translate-x-8', 'scale-95', 'rotate-12');
|
||||
|
||||
// Add visible state
|
||||
element.classList.add('is-visible');
|
||||
|
||||
// Trigger custom event for other components to listen to
|
||||
element.dispatchEvent(new CustomEvent('scroll-animated', {
|
||||
bubbles: true,
|
||||
detail: { element }
|
||||
}));
|
||||
}
|
||||
|
||||
resetElement(element) {
|
||||
// Remove visible state
|
||||
element.classList.remove('is-visible');
|
||||
|
||||
// Re-apply initial animation classes
|
||||
const animationType = element.getAttribute('data-animation') || 'fade-in';
|
||||
const initialClasses = this.getInitialClasses(animationType);
|
||||
initialClasses.forEach(cls => element.classList.add(cls));
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-initialize when script loads
|
||||
// Can be disabled by setting window.DISABLE_AUTO_SCROLL_ANIMATIONS = true before loading this script
|
||||
if (typeof window !== 'undefined' && !window.DISABLE_AUTO_SCROLL_ANIMATIONS) {
|
||||
window.scrollAnimations = new ScrollAnimations();
|
||||
}
|
||||
|
||||
// Export for module usage
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = ScrollAnimations;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue