Skip to content

Commit 5db5d8e

Browse files
gmoonclaude
andcommitted
Add scroll-triggered animations, drift badge, and feedback arc to homepage
How It Works section now animates on scroll: trace cards stagger in with slide-up transitions, drift badge shows a concrete version-drift example using real node IDs, and feedback arc pills show upstream edge types (challenges, validates, reveals_gap_in). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c3eec63 commit 5db5d8e

1 file changed

Lines changed: 144 additions & 8 deletions

File tree

src/pages/HomePage.tsx

Lines changed: 144 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useState, useEffect, useRef } from 'react'
12
import { colors, fonts, shadows, radius } from '../tokens'
23
import { Header } from '../components/Header'
34
import { Hero } from '../components/Hero'
@@ -6,6 +7,25 @@ import { Footer } from '../components/Footer'
67
import { projects } from '../data/projects'
78
import { blogPosts } from '../data/blog-posts'
89

10+
// --- Intersection Observer Hook ---
11+
12+
function useInView() {
13+
const ref = useRef<HTMLDivElement>(null)
14+
const [inView, setInView] = useState(false)
15+
useEffect(() => {
16+
if (!ref.current) return
17+
const observer = new IntersectionObserver(
18+
([entry]) => {
19+
if (entry.isIntersecting) setInView(true)
20+
},
21+
{ threshold: 0.15 },
22+
)
23+
observer.observe(ref.current)
24+
return () => observer.disconnect()
25+
}, [])
26+
return { ref, inView }
27+
}
28+
929
// --- Value Props ---
1030

1131
const valuePropsStyles: Record<string, React.CSSProperties> = {
@@ -251,11 +271,77 @@ const inlineCode: React.CSSProperties = {
251271
fontFamily: fonts.mono,
252272
}
253273

254-
function VerticalTrace() {
274+
const driftStyles: Record<string, React.CSSProperties> = {
275+
container: {
276+
display: 'flex',
277+
alignItems: 'flex-start',
278+
gap: '0.6rem',
279+
maxWidth: '520px',
280+
margin: '1rem auto 0',
281+
background: `${colors.accentRed}0a`,
282+
border: `1px solid ${colors.accentRed}30`,
283+
borderRadius: radius,
284+
padding: '0.75rem 1rem',
285+
fontFamily: fonts.mono,
286+
fontSize: '0.78rem',
287+
color: colors.accentRed,
288+
lineHeight: 1.5,
289+
},
290+
icon: {
291+
flexShrink: 0,
292+
fontSize: '0.9rem',
293+
},
294+
}
295+
296+
const feedbackStyles: Record<string, React.CSSProperties> = {
297+
container: {
298+
display: 'flex',
299+
flexDirection: 'column' as const,
300+
alignItems: 'center',
301+
gap: '0.5rem',
302+
margin: '1.5rem 0',
303+
},
304+
label: {
305+
fontSize: '0.85rem',
306+
fontFamily: fonts.system,
307+
color: colors.textMuted,
308+
fontWeight: 500,
309+
},
310+
pills: {
311+
display: 'flex',
312+
gap: '0.5rem',
313+
flexWrap: 'wrap' as const,
314+
justifyContent: 'center',
315+
},
316+
pill: {
317+
fontFamily: fonts.mono,
318+
fontSize: '0.72rem',
319+
padding: '0.25rem 0.65rem',
320+
borderRadius: '100px',
321+
border: `1px solid ${colors.borderColor}`,
322+
background: 'rgba(0,0,0,0.02)',
323+
color: colors.textMuted,
324+
},
325+
arrow: {
326+
fontSize: '0.65rem',
327+
marginLeft: '0.2rem',
328+
},
329+
}
330+
331+
function VerticalTrace({ inView }: { inView: boolean }) {
255332
return (
256333
<div style={traceStyles.wrapper}>
257334
{traceNodes.flatMap((node, i) => [
258-
<div key={node.id} style={{ ...traceStyles.card, borderLeft: `4px solid ${node.color}` }}>
335+
<div
336+
key={node.id}
337+
style={{
338+
...traceStyles.card,
339+
borderLeft: `4px solid ${node.color}`,
340+
opacity: inView ? 1 : 0,
341+
transform: inView ? 'none' : 'translateY(12px)',
342+
transition: `opacity 0.5s ease ${i * 0.2}s, transform 0.5s ease ${i * 0.2}s`,
343+
}}
344+
>
259345
<div style={traceStyles.cardHeader}>
260346
<span style={{ ...traceStyles.typePill, color: node.color, background: `${node.color}14` }}>
261347
{node.type}
@@ -267,7 +353,14 @@ function VerticalTrace() {
267353
</div>,
268354
...(i < traceEdges.length
269355
? [
270-
<div key={`edge-${i}`} style={traceStyles.connector}>
356+
<div
357+
key={`edge-${i}`}
358+
style={{
359+
...traceStyles.connector,
360+
opacity: inView ? 1 : 0,
361+
transition: `opacity 0.3s ease ${i * 0.2 + 0.1}s`,
362+
}}
363+
>
271364
<div style={traceStyles.connectorLine} />
272365
<span style={traceStyles.connectorLabel}>{traceEdges[i]}</span>
273366
<div style={traceStyles.connectorLine} />
@@ -280,21 +373,64 @@ function VerticalTrace() {
280373
)
281374
}
282375

376+
function DriftBadge({ inView }: { inView: boolean }) {
377+
return (
378+
<div
379+
style={{
380+
...driftStyles.container,
381+
opacity: inView ? 1 : 0,
382+
transform: inView ? 'none' : 'translateY(8px)',
383+
transition: 'opacity 0.5s ease 0.9s, transform 0.5s ease 0.9s',
384+
}}
385+
>
386+
<span style={driftStyles.icon}>{'\u26A0'}</span>
387+
<span>
388+
drift detected &mdash; <strong>THX-VERSION-AWARE</strong> changed v1.0.0 &rarr; v1.1.0 &mdash;{' '}
389+
<strong>REQ-CORE-005</strong> needs review
390+
</span>
391+
</div>
392+
)
393+
}
394+
395+
function FeedbackArc({ inView }: { inView: boolean }) {
396+
return (
397+
<div
398+
style={{
399+
...feedbackStyles.container,
400+
opacity: inView ? 1 : 0,
401+
transition: 'opacity 0.5s ease 1.1s',
402+
}}
403+
>
404+
<span style={feedbackStyles.label}>Knowledge flows upstream too</span>
405+
<div style={feedbackStyles.pills}>
406+
{['challenges', 'validates', 'reveals_gap_in'].map((edge) => (
407+
<span key={edge} style={feedbackStyles.pill}>
408+
{edge}
409+
<span style={feedbackStyles.arrow}>{'\u2191'}</span>
410+
</span>
411+
))}
412+
</div>
413+
</div>
414+
)
415+
}
416+
283417
function HowItWorks() {
418+
const { ref, inView } = useInView()
284419
return (
285420
<section style={howStyles.section}>
286-
<div style={howStyles.container}>
421+
<div ref={ref} style={howStyles.container}>
287422
<h2 style={howStyles.sectionTitle}>How it works</h2>
288423
<p style={howStyles.intro}>
289424
Lattice organizes knowledge into four layers &mdash; sources, theses, requirements, and implementations
290425
&mdash; connected by version-bound edges. Here&rsquo;s a real trace from Lattice&rsquo;s own knowledge graph:
291426
</p>
292-
<VerticalTrace />
427+
<VerticalTrace inView={inView} />
293428
<p style={howStyles.body}>
294-
Every edge records the version it was bound to. When a source is updated or a thesis is revised,{' '}
295-
<code style={inlineCode}>lattice drift</code> tells you exactly which downstream requirements and
296-
implementations need review.
429+
Every edge records the version it was bound to. When something changes,{' '}
430+
<code style={inlineCode}>lattice drift</code> tells you what needs review:
297431
</p>
432+
<DriftBadge inView={inView} />
433+
<FeedbackArc inView={inView} />
298434
<pre style={howStyles.codeBlock}>
299435
<code>{`.lattice/
300436
├── config.yaml

0 commit comments

Comments
 (0)