@@ -0,0 +1,546 @@
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Secure Paved Road Architecture< / title >
< link href = "https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel = "stylesheet" >
< style >
* , * :: before , * :: after { box-sizing : border-box ; }
: root {
--paper : #f5f5f5 ;
--paper-2 : #ececec ;
--ink : #2d3142 ;
--muted : #4f5d75 ;
--soft : #7a8399 ;
--rule : rgba ( 45 , 49 , 66 , 0.12 ) ;
--rule-strong : rgba ( 45 , 49 , 66 , 0.30 ) ;
--accent : #eb6c36 ;
--accent-tint : rgba ( 235 , 108 , 54 , 0.08 ) ;
--sans : 'Geist' , system-ui , sans-serif ;
--serif : 'Instrument Serif' , serif ;
--mono : 'Geist Mono' , ui-monospace , monospace ;
--motion-fast : 160 ms ;
--motion-step : 480 ms ;
--motion-hold : 720 ms ;
--motion-total : 3600 ms ;
--motion-ease : cubic-bezier ( .2 , .8 , .2 , 1 ) ;
}
body {
margin : 0 ;
min-height : 100 vh ;
padding : 32 px ;
color : var ( - - ink ) ;
background : var ( - - paper ) ;
font-family : var ( - - sans ) ;
}
[ data-motion-root ] {
width : min ( 100 % , 1280 px ) ;
margin : 0 auto ;
}
. page-header {
display : flex ;
align-items : end ;
justify-content : space-between ;
gap : 32 px ;
margin-bottom : 16 px ;
}
. eyebrow {
margin : 0 0 8 px ;
color : var ( - - muted ) ;
font : 500 10 px / 1.4 var ( - - mono ) ;
letter-spacing : .18 em ;
text-transform : uppercase ;
}
h1 {
margin : 0 ;
font : 400 clamp ( 28 px , 3 vw , 40 px ) / 1.08 var ( - - serif ) ;
letter-spacing : -.02 em ;
}
. lede {
max-width : 540 px ;
margin : 0 0 4 px ;
color : var ( - - muted ) ;
font-size : 13 px ;
line-height : 1.6 ;
}
. diagram-wrap {
overflow-x : auto ;
border-top : 1 px solid var ( - - rule ) ;
border-bottom : 1 px solid var ( - - rule ) ;
}
svg { display : block ; width : 100 % ; min-width : 920 px ; }
/* Motion item states */
. motion-ready [ data-motion-item ] {
opacity : .12 ;
transform : translateY ( 8 px ) ;
transform-box : fill-box ;
transform-origin : center ;
transition : opacity var ( - - motion - step ) var ( - - motion - ease ) ,
transform var ( - - motion - step ) var ( - - motion - ease ) ;
}
. motion-ready [ data-motion-item ] . is-visible ,
. motion-ready [ data-frame = "end" ] [ data-motion-item ] ,
. motion-ready [ data-frame = "static" ] [ data-motion-item ] {
opacity : 1 ;
transform : none ;
}
html [ data-motion = "static" ] [ data-motion-controls ] { display : none !important ; }
html [ data-motion = "static" ] [ data-motion-item ] {
opacity : 1 !important ;
transform : none !important ;
transition : none !important ;
}
html [ data-motion = "step" ] [ data-motion-controls ] ,
html [ data-motion = "step" ] [ data-motion-decorative ] { display : none !important ; }
html [ data-motion = "step" ] [ data-motion-item ] {
animation : none !important ;
transition : none !important ;
}
@ media ( prefers-reduced-motion : reduce ) {
* , * :: before , * :: after {
animation-duration : .001 ms !important ;
animation-iteration-count : 1 !important ;
scroll-behavior : auto !important ;
transition-duration : .001 ms !important ;
}
[ data-motion-item ] { opacity : 1 !important ; transform : none !important ; }
[ data-motion-decorative ] { display : none !important ; }
[ data-motion-controls ] { display : none !important ; }
}
@ media print {
body { padding : 0 ; }
[ data-motion-controls ] , [ data-motion-decorative ] { display : none !important ; }
[ data-motion-item ] { opacity : 1 !important ; transform : none !important ; }
}
[ data-motion-controls ] {
display : none ;
align-items : center ;
gap : 8 px ;
min-height : 60 px ;
padding : 8 px 0 ;
}
. motion-ready [ data-motion-controls ] { display : flex ; }
[ data-motion-controls ] [ hidden ] { display : none !important ; }
[ data-motion-controls ] button {
min-width : 44 px ;
min-height : 44 px ;
padding : 8 px 12 px ;
border : 1 px solid var ( - - muted ) ;
border-radius : 4 px ;
color : var ( - - ink ) ;
background : var ( - - paper ) ;
font : 600 12 px / 1 var ( - - sans ) ;
cursor : pointer ;
}
[ data-motion-controls ] button : hover { border-color : var ( - - accent ) ; }
[ data-motion-controls ] button : focus-visible ,
[ data-motion-root ] : focus-visible {
outline : 3 px solid var ( - - accent ) ;
outline-offset : 2 px ;
}
[ data-motion-controls ] button : disabled { cursor : not-allowed ; opacity : .45 ; }
. keyboard-help {
margin-left : 8 px ;
color : var ( - - soft ) ;
font : 500 9 px / 1.4 var ( - - mono ) ;
}
[ data-motion-status ] {
display : block ;
min-width : 272 px ;
min-height : 16 px ;
margin : 8 px 0 0 ;
color : var ( - - muted ) ;
font : 500 10 px / 1.4 var ( - - mono ) ;
text-align : right ;
}
noscript p {
margin : 12 px 0 0 ;
color : var ( - - muted ) ;
font : 500 10 px / 1.4 var ( - - mono ) ;
}
. sr-only {
position : absolute ;
width : 1 px ;
height : 1 px ;
padding : 0 ;
margin : -1 px ;
overflow : hidden ;
clip : rect ( 0 , 0 , 0 , 0 ) ;
white-space : nowrap ;
border : 0 ;
}
< / style >
< / head >
< body >
< main data-motion-root data-motion-mode = "step" data-step-count = "5" data-step-current = "5" data-frame = "static" data-static-frame = "complete" tabindex = "-1" >
< header class = "page-header" >
< div >
< p class = "eyebrow" > Semantic pattern · Secure paved road< / p >
< h1 > Secure Paved Road Architecture< / h1 >
< / div >
< p class = "lede" >
Authorized deployments follow a paved road with verified identity gates, while unauthorized bypass attempts are blocked at the trust boundary.
< / p >
< / header >
< div class = "diagram-wrap" >
< svg role = "img" aria-labelledby = "paved-road-animated-title paved-road-animated-desc" viewBox = "0 0 1160 620" xmlns = "http://www.w3.org/2000/svg" data-motion-primitive = "secure-paved-road" >
< title id = "paved-road-animated-title" > Secure Paved Road Architecture< / title >
< desc id = "paved-road-animated-desc" > Interactive step-by-step demonstration of authorized paved-road deployment versus blocked unauthorized bypass attempts at trust boundaries.< / desc >
< rect width = "1160" height = "620" fill = "#f5f5f5" / >
< defs >
< marker id = "arrow-ink" viewBox = "0 0 10 10" refX = "8" refY = "5" markerWidth = "6" markerHeight = "6" orient = "auto-start-reverse" >
< path d = "M 0 1 L 10 5 L 0 9 z" fill = "#2d3142" / >
< / marker >
< marker id = "arrow-accent" viewBox = "0 0 10 10" refX = "8" refY = "5" markerWidth = "6" markerHeight = "6" orient = "auto-start-reverse" >
< path d = "M 0 1 L 10 5 L 0 9 z" fill = "#eb6c36" / >
< / marker >
< / defs >
<!-- Base Layout: Trust Zone 1 - Public Ingress -->
< rect x = "40" y = "40" width = "240" height = "490" rx = "8" fill = "#ececec" stroke = "rgba(45,49,66,0.12)" stroke-width = "1" / >
< text x = "60" y = "72" fill = "#4f5d75" font-family = "'Geist Mono', monospace" font-size = "11" letter-spacing = "1.6" > PUBLIC INGRESS ZONE< / text >
<!-- Node: Developer Client -->
< rect x = "60" y = "96" width = "200" height = "72" rx = "6" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1.5" / >
< text x = "76" y = "128" fill = "#2d3142" font-family = "'Geist', sans-serif" font-size = "14" font-weight = "600" > Developer Workstation< / text >
< text x = "76" y = "148" fill = "#4f5d75" font-family = "'Geist Mono', monospace" font-size = "11" > Identity: dev_alice (mTLS)< / text >
<!-- Node: Unauthorized Attacker -->
< rect x = "60" y = "325" width = "200" height = "72" rx = "6" fill = "#ffffff" stroke = "#eb6c36" stroke-width = "1.5" / >
< text x = "76" y = "357" fill = "#eb6c36" font-family = "'Geist', sans-serif" font-size = "14" font-weight = "600" > Unauthorized Attacker< / text >
< text x = "76" y = "377" fill = "#eb6c36" font-family = "'Geist Mono', monospace" font-size = "11" > Bypass Attempt (No Key)< / text >
<!-- Base Layout: Trust Zone 2 - Corporate CI/CD & Governance -->
< rect x = "330" y = "40" width = "380" height = "490" rx = "8" fill = "#ececec" stroke = "rgba(45,49,66,0.12)" stroke-width = "1" / >
< text x = "350" y = "72" fill = "#4f5d75" font-family = "'Geist Mono', monospace" font-size = "11" letter-spacing = "1.6" > CORPORATE GOVERNANCE ZONE< / text >
<!-- Node: CI/CD Pipeline -->
< rect x = "350" y = "96" width = "340" height = "72" rx = "6" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1.5" / >
< text x = "370" y = "128" fill = "#2d3142" font-family = "'Geist', sans-serif" font-size = "14" font-weight = "600" > CI/CD Paved Road Pipeline< / text >
< text x = "370" y = "148" fill = "#4f5d75" font-family = "'Geist Mono', monospace" font-size = "11" > Build: Signed Artifact & Policy Check< / text >
<!-- Node: Privileged Security Gate -->
< rect x = "350" y = "210" width = "340" height = "76" rx = "6" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1.5" / >
< text x = "370" y = "238" fill = "#2d3142" font-family = "'Geist', sans-serif" font-size = "14" font-weight = "600" > Privileged Security Gate< / text >
< text x = "370" y = "258" fill = "#4f5d75" font-family = "'Geist Mono', monospace" font-size = "11" > Policy: OPA Signature & Vulnerability Scan< / text >
<!-- Node: Boundary Firewall & WAF (Left side x=350..560) -->
< rect x = "350" y = "325" width = "210" height = "72" rx = "6" fill = "rgba(235,108,54,0.08)" stroke = "#eb6c36" stroke-width = "1.5" / >
< text x = "366" y = "355" fill = "#eb6c36" font-family = "'Geist', sans-serif" font-size = "13" font-weight = "600" > Boundary Firewall & WAF< / text >
< text x = "366" y = "375" fill = "#eb6c36" font-family = "'Geist Mono', monospace" font-size = "10.5" font-weight = "600" > Status: × BLOCKED< / text >
<!-- Node: Audit Log Service -->
< rect x = "350" y = "435" width = "340" height = "72" rx = "6" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1.5" / >
< text x = "370" y = "465" fill = "#2d3142" font-family = "'Geist', sans-serif" font-size = "14" font-weight = "600" > Centralized Audit Log Service< / text >
< text x = "370" y = "485" fill = "#4f5d75" font-family = "'Geist Mono', monospace" font-size = "11" > Storage: Immutable SIEM Audit Trail< / text >
<!-- Base Layout: Trust Zone 3 - Isolated Production Runtime -->
< rect x = "760" y = "40" width = "360" height = "490" rx = "8" fill = "#ececec" stroke = "rgba(45,49,66,0.12)" stroke-width = "1" / >
< text x = "780" y = "72" fill = "#4f5d75" font-family = "'Geist Mono', monospace" font-size = "11" letter-spacing = "1.6" > ISOLATED PRODUCTION RUNTIME< / text >
<!-- Node: Production API Gateway -->
< rect x = "780" y = "96" width = "320" height = "72" rx = "6" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1.5" / >
< text x = "800" y = "128" fill = "#2d3142" font-family = "'Geist', sans-serif" font-size = "14" font-weight = "600" > Production API Gateway< / text >
< text x = "800" y = "148" fill = "#4f5d75" font-family = "'Geist Mono', monospace" font-size = "11" > Status: ADMITTED [TLS 1.3 Strict]< / text >
<!-- Node: Production Database -->
< rect x = "780" y = "210" width = "320" height = "76" rx = "6" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1.5" / >
< text x = "800" y = "238" fill = "#2d3142" font-family = "'Geist', sans-serif" font-size = "14" font-weight = "600" > Isolated Core Database< / text >
< text x = "800" y = "258" fill = "#4f5d75" font-family = "'Geist Mono', monospace" font-size = "11" > Access: Paved Road Only (KMS Encrypted)< / text >
<!-- Node: Security Operations Center -->
< rect x = "780" y = "435" width = "320" height = "72" rx = "6" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1.5" / >
< text x = "800" y = "465" fill = "#2d3142" font-family = "'Geist', sans-serif" font-size = "14" font-weight = "600" > Security Operations Center< / text >
< text x = "800" y = "485" fill = "#4f5d75" font-family = "'Geist Mono', monospace" font-size = "11" > Monitoring: Real-time Alerting Active< / text >
<!-- STEP 1: Authorized Developer Code Ingress -->
< g data-motion-item data-step = "1" aria-label = "Step 1: Developer workstation pushes signed code commit into CI/CD Paved Road pipeline" >
< path d = "M 260 132 L 350 132" fill = "none" stroke = "#2d3142" stroke-width = "2" marker-end = "url(#arrow-ink)" / >
< rect x = "275" y = "104" width = "60" height = "18" rx = "3" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1" / >
< text x = "305" y = "117" text-anchor = "middle" fill = "#2d3142" font-family = "'Geist Mono', monospace" font-size = "9" font-weight = "600" > GIT PUSH< / text >
< / g >
<!-- STEP 2: CI/CD Build & Cryptographic Signing -->
< g data-motion-item data-step = "2" aria-label = "Step 2: CI/CD pipeline builds binary and generates cryptographic provenance signature" >
< path d = "M 520 168 L 520 210" fill = "none" stroke = "#2d3142" stroke-width = "2" marker-end = "url(#arrow-ink)" / >
< rect x = "440" y = "178" width = "160" height = "20" rx = "4" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1" / >
< text x = "520" y = "192" text-anchor = "middle" fill = "#2d3142" font-family = "'Geist Mono', monospace" font-size = "9" font-weight = "600" > PROVENANCE [SIGNED]< / text >
< / g >
<!-- STEP 3: Security Gate Approval → Production Gateway → Database Deployment -->
< g data-motion-item data-step = "3" aria-label = "Step 3: Security gate verifies OPA policy, approves artifact, and deploys via Production API Gateway to Isolated Core Database" >
<!-- Gate ✓ PASS → Production API Gateway (diagonal: gate right edge to gateway left edge) -->
< path d = "M 690 230 L 780 132" fill = "none" stroke = "#2d3142" stroke-width = "2" marker-end = "url(#arrow-ink)" / >
< rect x = "700" y = "166" width = "65" height = "18" rx = "3" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1" / >
< text x = "732" y = "179" text-anchor = "middle" fill = "#2d3142" font-family = "'Geist Mono', monospace" font-size = "9" font-weight = "600" > ✓ PASS< / text >
<!-- Production API Gateway → Isolated Core Database (vertical: gateway bottom to db top) -->
< path d = "M 940 168 L 940 210" fill = "none" stroke = "#2d3142" stroke-width = "2" marker-end = "url(#arrow-ink)" / >
< / g >
<!-- STEP 4: Unauthorized Bypass Attempt Blocked at Boundary Firewall -->
< g data-motion-item data-step = "4" aria-label = "Step 4: Unauthorized attacker connection attempt is blocked at trust boundary firewall" >
< path d = "M 260 361 L 350 361" fill = "none" stroke = "#eb6c36" stroke-width = "2" stroke-dasharray = "4 4" marker-end = "url(#arrow-accent)" / >
< rect x = "272" y = "345" width = "65" height = "18" rx = "3" fill = "#ffffff" stroke = "#eb6c36" stroke-width = "1" / >
< text x = "304" y = "358" text-anchor = "middle" fill = "#eb6c36" font-family = "'Geist Mono', monospace" font-size = "8.5" font-weight = "600" > BYPASS< / text >
< / g >
<!-- STEP 5: Audit Log & SIEM Security Monitoring Record -->
< g data-motion-item data-step = "5" aria-label = "Step 5: Immutable SIEM audit log records both authorized deployment and blocked attack attempt" >
<!-- Gate Audit Trail (Runs cleanly down the right side x=620, clear of Firewall node x=350..560!) -->
< path d = "M 620 286 L 620 435" fill = "none" stroke = "#2d3142" stroke-width = "2" marker-end = "url(#arrow-ink)" / >
<!-- WAF Audit Trail (Runs directly down x=455 into Audit Log) -->
< path d = "M 455 397 L 455 435" fill = "none" stroke = "#eb6c36" stroke-width = "2" stroke-dasharray = "4 4" marker-end = "url(#arrow-accent)" / >
<!-- SOC Monitoring Link (Data flows FROM Audit Log TO SOC for real - time monitoring) -->
< path d = "M 690 471 L 780 471" fill = "none" stroke = "#2d3142" stroke-width = "2" marker-end = "url(#arrow-ink)" / >
<!-- Audit Badge Chip -->
< rect x = "570" y = "350" width = "100" height = "20" rx = "3" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1" / >
< text x = "620" y = "364" text-anchor = "middle" fill = "#2d3142" font-family = "'Geist Mono', monospace" font-size = "9" font-weight = "600" > AUDIT LOGGED< / text >
< / g >
<!-- Precision SVG Legend Footer (Exact visual mini - arrows and mini - rects) -->
< g data-motion-decorative aria-hidden = "true" >
<!-- Flow Line 1: Authorized Paved Road -->
< line x1 = "40" y1 = "584" x2 = "65" y2 = "584" stroke = "#2d3142" stroke-width = "2" marker-end = "url(#arrow-ink)" / >
< text x = "73" y = "588" fill = "#2d3142" font-family = "'Geist Mono', monospace" font-size = "9.5" > Authorized Paved Road< / text >
<!-- Flow Line 2: Blocked Bypass Attempt -->
< line x1 = "225" y1 = "584" x2 = "250" y2 = "584" stroke = "#eb6c36" stroke-width = "2" stroke-dasharray = "3 3" marker-end = "url(#arrow-accent)" / >
< text x = "258" y = "588" fill = "#2d3142" font-family = "'Geist Mono', monospace" font-size = "9.5" > Blocked Bypass Attempt< / text >
<!-- Box 1: Paved Road Node -->
< rect x = "425" y = "577" width = "16" height = "12" rx = "2" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1.5" / >
< text x = "447" y = "588" fill = "#2d3142" font-family = "'Geist Mono', monospace" font-size = "9.5" > Paved Road Node< / text >
<!-- Box 2: Attacker Node (White Fill + Orange Border) -->
< rect x = "565" y = "577" width = "16" height = "12" rx = "2" fill = "#ffffff" stroke = "#eb6c36" stroke-width = "1.5" / >
< text x = "587" y = "588" fill = "#2d3142" font-family = "'Geist Mono', monospace" font-size = "9.5" > Attacker Node< / text >
<!-- Box 3: Firewall Blocked Node (Orange Tint Fill + Orange Border) -->
< rect x = "690" y = "577" width = "16" height = "12" rx = "2" fill = "rgba(235,108,54,0.08)" stroke = "#eb6c36" stroke-width = "1.5" / >
< text x = "712" y = "588" fill = "#2d3142" font-family = "'Geist Mono', monospace" font-size = "9.5" > Firewall Blocked< / text >
<!-- Box 4: Action / Status Chip (White Chip + Solid Border) -->
< rect x = "830" y = "577" width = "16" height = "12" rx = "2" fill = "#ffffff" stroke = "#2d3142" stroke-width = "1" / >
< text x = "852" y = "588" fill = "#2d3142" font-family = "'Geist Mono', monospace" font-size = "9.5" > Action / Status Chip< / text >
< / g >
< / svg >
< / div >
<!-- Pinned Motion Controls -->
< div data-motion-controls role = "group" aria-label = "Diagram playback controls" >
< button type = "button" data-motion-action = "prev" > Previous< / button >
< button type = "button" data-motion-action = "play" aria-pressed = "false" > Play< / button >
< button type = "button" data-motion-action = "pause" aria-pressed = "true" > Pause< / button >
< button type = "button" data-motion-action = "next" > Next< / button >
< button type = "button" data-motion-action = "replay" > Replay< / button >
< span class = "keyboard-help" > ←/→ step · Space play/pause · R replay · Home/End jump< / span >
< span data-motion-status-visible aria-hidden = "true" > Step < span data-motion-step-label > 5< / span > of 5< / span >
< / div >
< p class = "sr-only" data-motion-status role = "status" aria-live = "polite" aria-atomic = "true" > < / p >
< noscript > < p > Animation controls require JavaScript. The complete final diagram is shown above.< / p > < / noscript >
< / main >
< script data-diagram-controls >
( ( ) => {
const params = new URLSearchParams ( location . search ) ;
const staticOverride = params . get ( 'motion' ) === 'static' || document . documentElement . dataset . motion === 'static' ;
const requestedStep = params . get ( 'step' ) ;
const reduce = matchMedia ( '(prefers-reduced-motion: reduce)' ) ;
document . fonts . ready . then ( ( ) => { document . documentElement . dataset . fontsReady = 'true' ; } ) ;
document . querySelectorAll ( '[data-motion-root]' ) . forEach ( ( root ) => {
const count = Number ( root . dataset . stepCount ) ;
const hold = parseFloat ( getComputedStyle ( root ) . getPropertyValue ( '--motion-hold' ) ) || 720 ;
const parsedStep = requestedStep !== null && /^\d+$/ . test ( requestedStep ) ? Number ( requestedStep ) : null ;
const exactStep = params . get ( 'motion' ) === 'step' && Number . isSafeInteger ( parsedStep ) && parsedStep >= 0 && parsedStep <= count ? parsedStep : null ;
const items = [ ... root . querySelectorAll ( '[data-motion-item]' ) ] ;
const labels = Array . from ( { length : count + 1 } , ( _ , index ) => index === 0
? 'Ready'
: items . filter ( ( item ) => ! item . hasAttribute ( 'data-motion-decorative' ) && Number ( item . dataset . step ) === index )
. map ( ( item ) => item . getAttribute ( 'aria-label' ) ) . join ( '; ' ) ) ;
const controls = root . querySelector ( '[data-motion-controls]' ) ;
const status = root . querySelector ( '[data-motion-status]' ) ;
const visibleStep = root . querySelector ( '[data-motion-step-label]' ) ;
let step = count ;
let timer = 0 ;
let playing = false ;
let stepBeforeReduce = 0 ;
let resumeAfterReduce = root . dataset . motionMode === 'reveal' ;
const buttons = ( action ) => controls . querySelector ( ` [data-motion-action=" ${ action } "] ` ) ;
const clearClock = ( ) => { window . clearTimeout ( timer ) ; timer = 0 ; } ;
const announce = ( ) => { status . textContent = ` Step ${ step } of ${ count } : ${ labels [ step ] || 'Complete' } ` ; } ;
const setControlsAvailable = ( available ) => {
controls . hidden = ! available ;
controls . setAttribute ( 'aria-hidden' , String ( ! available ) ) ;
controls . dataset . playbackDisabled = String ( ! available ) ;
controls . querySelectorAll ( 'button' ) . forEach ( ( control ) => { control . disabled = ! available ; } ) ;
} ;
function render ( next , userInitiated = false ) {
step = Math . max ( 0 , Math . min ( count , next ) ) ;
root . dataset . stepCurrent = String ( step ) ;
root . dataset . frame = step === count ? 'end' : step === 0 ? 'start' : 'step' ;
items . forEach ( ( item ) => {
const itemStep = Number ( item . dataset . step ) ;
item . classList . toggle ( 'is-visible' , itemStep <= step ) ;
item . classList . toggle ( 'is-current' , itemStep === step ) ;
} ) ;
if ( visibleStep ) visibleStep . textContent = String ( step ) ;
const unavailable = controls . dataset . playbackDisabled === 'true' ;
buttons ( 'prev' ) . disabled = unavailable || step === 0 ;
buttons ( 'next' ) . disabled = unavailable || step === count ;
if ( userInitiated ) announce ( ) ;
}
function pause ( userInitiated = false ) {
clearClock ( ) ;
playing = false ;
root . classList . remove ( 'is-playing' ) ;
buttons ( 'play' ) . setAttribute ( 'aria-pressed' , 'false' ) ;
buttons ( 'pause' ) . setAttribute ( 'aria-pressed' , 'true' ) ;
if ( userInitiated ) status . textContent = ` Paused at step ${ step } of ${ count } ` ;
}
function finish ( ) {
pause ( false ) ;
root . dataset . frame = 'end' ;
status . textContent = ` Complete · step ${ count } of ${ count } ` ;
}
function tick ( ) {
if ( ! playing ) return ;
if ( step >= count ) { finish ( ) ; return ; }
render ( step + 1 , false ) ;
if ( step >= count ) { finish ( ) ; return ; }
timer = window . setTimeout ( tick , hold ) ;
}
function play ( fromStart = false , userInitiated = false ) {
clearClock ( ) ;
if ( fromStart || step >= count ) render ( 0 , userInitiated ) ;
playing = true ;
root . classList . add ( 'is-playing' ) ;
buttons ( 'play' ) . setAttribute ( 'aria-pressed' , 'true' ) ;
buttons ( 'pause' ) . setAttribute ( 'aria-pressed' , 'false' ) ;
timer = window . setTimeout ( tick , hold ) ;
}
controls . addEventListener ( 'click' , ( event ) => {
const button = event . target . closest ( '[data-motion-action]' ) ;
if ( ! button ) return ;
const action = button . dataset . motionAction ;
if ( action === 'play' ) play ( false , true ) ;
if ( action === 'pause' ) pause ( true ) ;
if ( action === 'replay' ) play ( true , true ) ;
if ( action === 'prev' ) { pause ( false ) ; render ( step - 1 , true ) ; }
if ( action === 'next' ) { pause ( false ) ; render ( step + 1 , true ) ; }
} ) ;
root . addEventListener ( 'keydown' , ( event ) => {
if ( event . target . matches ( 'input, textarea, select, a[href]' ) ) return ;
const keys = { ArrowLeft : step - 1 , ArrowRight : step + 1 , Home : 0 , End : count } ;
if ( event . code === 'Space' && ! event . target . closest ( 'button' ) ) {
event . preventDefault ( ) ;
playing ? pause ( true ) : play ( false , true ) ;
return ;
}
if ( ! event . ctrlKey && ! event . metaKey && ! event . altKey && event . key . toLowerCase ( ) === 'r' ) {
event . preventDefault ( ) ;
play ( true , true ) ;
return ;
}
if ( ! ( event . key in keys ) ) return ;
event . preventDefault ( ) ;
pause ( false ) ;
render ( keys [ event . key ] , true ) ;
} ) ;
document . addEventListener ( 'visibilitychange' , ( ) => {
if ( document . visibilityState === 'hidden' && playing ) pause ( false ) ;
} ) ;
reduce . addEventListener ( 'change' , ( event ) => {
if ( event . matches && root . dataset . motionState !== 'reduced' ) {
stepBeforeReduce = step ;
resumeAfterReduce = playing ;
}
pause ( false ) ;
if ( event . matches ) {
setControlsAvailable ( false ) ;
render ( count , false ) ;
root . dataset . frame = 'static' ;
root . dataset . motionState = 'reduced' ;
status . textContent = 'Reduced motion · complete static frame · playback controls unavailable' ;
} else if ( staticOverride || root . dataset . motionMode === 'none' ) {
setControlsAvailable ( false ) ;
render ( count , false ) ;
root . dataset . frame = 'static' ;
root . dataset . motionState = 'static' ;
status . textContent = 'Static frame · complete diagram · playback controls unavailable' ;
} else {
delete root . dataset . motionState ;
if ( exactStep !== null ) {
setControlsAvailable ( false ) ;
render ( exactStep , false ) ;
root . dataset . frame = 'step' ;
root . dataset . motionState = 'test' ;
status . textContent = ` Test frame · step ${ step } of ${ count } · playback controls unavailable ` ;
} else {
setControlsAvailable ( true ) ;
render ( stepBeforeReduce , false ) ;
status . textContent = ` Ready · step ${ step } of ${ count } ` ;
if ( resumeAfterReduce && root . dataset . motionMode === 'reveal' ) {
play ( false , false ) ;
}
}
resumeAfterReduce = false ;
}
} ) ;
if ( staticOverride ) {
document . documentElement . dataset . motion = 'static' ;
pause ( false ) ;
setControlsAvailable ( false ) ;
render ( count , false ) ;
root . dataset . frame = 'static' ;
root . dataset . motionState = 'static' ;
status . textContent = 'Static frame · complete diagram · playback controls unavailable' ;
} else if ( reduce . matches || root . dataset . motionMode === 'none' ) {
pause ( false ) ;
setControlsAvailable ( false ) ;
render ( count , false ) ;
root . dataset . frame = 'static' ;
root . dataset . motionState = reduce . matches ? 'reduced' : 'static' ;
status . textContent = reduce . matches
? 'Reduced motion · complete static frame · playback controls unavailable'
: 'Static frame · complete diagram · playback controls unavailable' ;
} else if ( exactStep !== null ) {
document . documentElement . dataset . motion = 'step' ;
pause ( false ) ;
setControlsAvailable ( false ) ;
render ( exactStep , false ) ;
root . dataset . frame = 'step' ;
root . dataset . motionState = 'test' ;
status . textContent = ` Test frame · step ${ step } of ${ count } · playback controls unavailable ` ;
} else if ( root . dataset . motionMode === 'reveal' ) {
setControlsAvailable ( true ) ;
render ( 0 , false ) ;
play ( false , false ) ;
} else {
pause ( false ) ;
setControlsAvailable ( true ) ;
render ( 0 , false ) ;
status . textContent = ` Ready · step 0 of ${ count } ` ;
}
root . classList . add ( 'motion-ready' ) ;
} ) ;
} ) ( ) ;
< / script >
< / body >
< / html >