Skip to content

Commit 1cdfee1

Browse files
DexterStoreyclaude
andauthored
fix scroll to animation (#31)
* Add Contract Engineering blog post Interactive figures for contract lifecycle, scenario matrix, and event log. Updates announcement banner to feature the new post. Adapted from site-rebuild branch with paths updated to main's directory structure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * more * touches * Update contract engineering copy with final edits - Add TDD comparison paragraph with Wikipedia link - Link PRDs to Wikipedia on first mention - Add Ambiguity limitation, Acknowledgments section - Fix tense ("nobody had defined"), revert Limitations opener - Simplify Sharpen the Spec examples back to concise form - Update links: rubriclab, full Unblocking Agents URL Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Restore fleshed-out contract examples and experimental Limitations tone Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix animations to autoplay on scroll, not on first paint Both EmailAppLifecycleFigure and ContractEventLog now use IntersectionObserver to start playback when scrolled into view. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ce6c3a9 commit 1cdfee1

File tree

2 files changed

+42
-6
lines changed

2 files changed

+42
-6
lines changed

src/ui/blog/contract-engineering/contract-event-log.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useCallback, useEffect, useState } from 'react'
3+
import { useCallback, useEffect, useRef, useState } from 'react'
44
import { cn } from '~/lib/utils/cn'
55
import { Button } from '~/ui/button'
66
import { Figure } from '~/ui/figure'
@@ -157,7 +157,25 @@ const SpinnerIcon = () => (
157157

158158
export const ContractEventLog = () => {
159159
const [tick, setTick] = useState(0)
160-
const [isPlaying, setIsPlaying] = useState(true)
160+
const [isPlaying, setIsPlaying] = useState(false)
161+
const hasAutoPlayed = useRef(false)
162+
const containerRef = useRef<HTMLDivElement>(null)
163+
164+
useEffect(() => {
165+
const el = containerRef.current
166+
if (!el) return undefined
167+
const observer = new IntersectionObserver(
168+
([entry]) => {
169+
if (entry?.isIntersecting && !hasAutoPlayed.current) {
170+
hasAutoPlayed.current = true
171+
setIsPlaying(true)
172+
}
173+
},
174+
{ threshold: 0.3 }
175+
)
176+
observer.observe(el)
177+
return () => observer.disconnect()
178+
}, [])
161179

162180
const arrivedCount = Math.min(tick, EVENTS.length)
163181
const evaluatedCount = Math.min(Math.max(tick - EVAL_DELAY, 0), EVENTS.length)
@@ -188,7 +206,7 @@ export const ContractEventLog = () => {
188206
}, [isComplete])
189207

190208
return (
191-
<div className="w-full rounded-xl border border-subtle bg-subtle/10 px-4 pt-4 pb-3">
209+
<div ref={containerRef} className="w-full rounded-xl border border-subtle bg-subtle/10 px-4 pt-4 pb-3">
192210
{/* Header */}
193211
<div className="mb-3 flex items-center justify-between">
194212
<span className="font-mono text-[9px] text-secondary/50 uppercase tracking-wide">

src/ui/blog/contract-engineering/email-app-lifecycle-figure.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useCallback, useEffect, useState } from 'react'
3+
import { useCallback, useEffect, useRef, useState } from 'react'
44
import { cn } from '~/lib/utils/cn'
55
import { Button } from '~/ui/button'
66
import { Figure } from '~/ui/figure'
@@ -385,7 +385,25 @@ const Field = ({
385385

386386
export const EmailAppLifecycleFigure = () => {
387387
const [currentStep, setCurrentStep] = useState(0)
388-
const [isPlaying, setIsPlaying] = useState(true)
388+
const [isPlaying, setIsPlaying] = useState(false)
389+
const hasAutoPlayed = useRef(false)
390+
const containerRef = useRef<HTMLDivElement>(null)
391+
392+
useEffect(() => {
393+
const el = containerRef.current
394+
if (!el) return undefined
395+
const observer = new IntersectionObserver(
396+
([entry]) => {
397+
if (entry?.isIntersecting && !hasAutoPlayed.current) {
398+
hasAutoPlayed.current = true
399+
setIsPlaying(true)
400+
}
401+
},
402+
{ threshold: 0.3 }
403+
)
404+
observer.observe(el)
405+
return () => observer.disconnect()
406+
}, [])
389407

390408
const state = STATES[currentStep] ?? STATES[0]
391409
const isComplete = currentStep >= STATES.length - 1
@@ -418,7 +436,7 @@ export const EmailAppLifecycleFigure = () => {
418436
}, [isComplete])
419437

420438
return (
421-
<div className="w-full rounded-xl border border-subtle bg-subtle/10 px-4 pt-4 pb-3">
439+
<div ref={containerRef} className="w-full rounded-xl border border-subtle bg-subtle/10 px-4 pt-4 pb-3">
422440
<div className="flex flex-col gap-3">
423441
{/* App mockup */}
424442
<div className="overflow-hidden rounded-lg border border-subtle/60">

0 commit comments

Comments
 (0)