32 lines
6.8 KiB
HTML
32 lines
6.8 KiB
HTML
<!doctype html>
|
|
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>One system, three responsibilities</title>
|
|
<style>
|
|
/* FONT_CSS */
|
|
*{box-sizing:border-box}body{margin:0;background:#101e25;color:#f4f3ed;font-family:'Work Sans',sans-serif}main{max-width:1120px;margin:auto;padding:40px 36px}.eyebrow{color:#75d5c6;letter-spacing:.15em;font-size:13px;text-transform:uppercase}h1{font-size:44px;letter-spacing:-.04em;margin:12px 0}p{line-height:1.5;color:#c9d2d5}.intro{max-width:750px}.controls{display:flex;gap:10px;flex-wrap:wrap;margin:24px 0}button{font:inherit;color:inherit;border:1px solid #74868e;background:transparent;padding:11px 16px;cursor:pointer}button[aria-pressed=true]{background:#75d5c6;color:#101e25;border-color:#75d5c6}button:focus-visible,[role=button]:focus-visible{outline:3px solid #ffd17a;outline-offset:5px}svg{display:block;width:100%;height:auto;overflow:visible;background:#152831;border:1px solid #425861}.part{cursor:pointer;outline:none}.part .shell{fill:#26434e;stroke:#9ac0cb;stroke-width:2}.part[aria-pressed=true] .shell{fill:#34675f;stroke:#75d5c6;stroke-width:3}.part:hover .shell{stroke:#fff}svg text{fill:#f4f3ed;font-family:inherit;text-anchor:middle;font-size:20px}svg .hint{fill:#aec3cb;font-size:14px}.detail{border-left:3px solid #75d5c6;padding:12px 18px;min-height:83px;margin:20px 0}.detail strong{display:block;margin-bottom:5px}.foot{font-size:12px;color:#aec3cb}@media(max-width:600px){main{padding:24px 16px}h1{font-size:32px}}@media print{body{background:white;color:#101e25}.controls{display:none}main{padding:0}.detail,p,.foot{color:#101e25}}
|
|
</style></head><body><main>
|
|
<div class="eyebrow">Explanatory motion · original SVG example</div><h1>One system, three responsibilities.</h1><p class="intro">Keep the same parts in view. Separate them to explain their roles, inspect a component, then recombine the system.</p>
|
|
<div class="controls" aria-label="Assembly controls"><button id="assemble" aria-pressed="true">1 · Assemble</button><button id="explode" aria-pressed="false">2 · Explode</button><button id="inspect" aria-pressed="false">3 · Inspect</button><button id="reassemble" aria-pressed="false">4 · Reassemble</button></div>
|
|
<svg viewBox="0 0 1040 370" role="group" aria-label="Three-part service assembly">
|
|
<path d="M135 185H905" stroke="#58727c" stroke-dasharray="5 8"/><text x="520" y="40" class="hint" id="diagram-state">Assembled · a single system</text>
|
|
<g class="part" data-index="2" role="button" tabindex="0" aria-label="Inspect storage" aria-pressed="false" transform="translate(580 185)"><rect class="shell" x="-105" y="-100" width="210" height="200" rx="8"/><path d="M-65 -20H65M-65 0H65M-65 20H65" stroke="#75d5c6" stroke-width="4"/><text y="-55">Storage</text><text y="65" class="hint">Retain the outcome</text></g>
|
|
<g class="part" data-index="1" role="button" tabindex="0" aria-label="Inspect processing" aria-pressed="false" transform="translate(520 185)"><rect class="shell" x="-105" y="-100" width="210" height="200" rx="8"/><path d="M-45 0L0 -30L45 0L0 30Z" fill="none" stroke="#75d5c6" stroke-width="4"/><text y="-55">Processing</text><text y="65" class="hint">Apply the rules</text></g>
|
|
<g class="part" data-index="0" role="button" tabindex="0" aria-label="Inspect interface" aria-pressed="false" transform="translate(460 185)"><rect class="shell" x="-105" y="-100" width="210" height="200" rx="8"/><circle r="27" fill="none" stroke="#75d5c6" stroke-width="4"/><text y="-55">Interface</text><text y="65" class="hint">Receive the intent</text></g>
|
|
<text x="520" y="338" class="hint">Explode to reveal all labels · select a part for its responsibility</text></svg>
|
|
<div class="detail" aria-live="polite"><strong id="detail-title">Assembled system</strong><span id="detail-body">Three stable components share one purpose. Explode the assembly to reveal each responsibility.</span></div>
|
|
<p class="foot">Original abstract 2D assembly, not a reproduction of the Anime.js mechanism. No third-party artwork. Motion respects reduced-motion preferences. Keyboard: Tab, Enter and Space.</p>
|
|
</main><script>/* ANIME_JS */</script><script>
|
|
'use strict';
|
|
const parts=[...document.querySelectorAll('.part')].sort((a,b)=>a.dataset.index-b.dataset.index),buttons=[...document.querySelectorAll('.controls button')],mq=matchMedia('(prefers-reduced-motion: reduce)');
|
|
const info=[['Interface','Receives intent and presents the result. It does not own persistence.'],['Processing','Applies rules and coordinates the operation. It does not own the user interface.'],['Storage','Retains the outcome for later retrieval. It does not decide the business rules.']];
|
|
const model=[{x:460},{x:520},{x:580}];let animation=null,expanded=false,selected=-1;
|
|
function draw(){parts.forEach((p,i)=>p.setAttribute('transform',`translate(${model[i].x} 185)`));}
|
|
function settle(){if(animation){animation.complete();animation=null;}}
|
|
function move(open,immediate=false){settle();expanded=open;parts.forEach((p,i)=>p.querySelectorAll('text').forEach(t=>t.style.opacity=open||i===0?'1':'0'));const target=open?[200,520,840]:[460,520,580];if(mq.matches||immediate){model.forEach((m,i)=>m.x=target[i]);draw();}else{animation=anime.animate(model,{x:(_,i)=>target[i],duration:650,ease:'inOutCubic',onUpdate:draw,onComplete:()=>{animation=null;}});}document.querySelector('#diagram-state').textContent=open?'Exploded · inspect each responsibility':'Assembled · a single system';}
|
|
function activate(id){buttons.forEach(b=>b.setAttribute('aria-pressed',String(b.id===id)));}
|
|
function detail(i){selected=i;parts.forEach((p,j)=>p.setAttribute('aria-pressed',String(i===j)));document.querySelector('#detail-title').textContent=i<0?'Assembled system':info[i][0];document.querySelector('#detail-body').textContent=i<0?'Three stable components share one purpose. Explode the assembly to reveal each responsibility.':info[i][1];}
|
|
function inspect(i){activate('inspect');move(true);detail(i);}
|
|
buttons.forEach(b=>b.addEventListener('click',()=>{activate(b.id);if(b.id==='inspect'){inspect(selected<0?1:selected);}else{move(b.id==='explode');detail(-1);if(b.id==='explode'){document.querySelector('#detail-title').textContent='Exploded system';document.querySelector('#detail-body').textContent='The same components, separated without changing identity. Select any component to inspect its role.';}}}));
|
|
parts.forEach((p,i)=>{p.addEventListener('click',()=>inspect(i));p.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();e.stopPropagation();inspect(i);}});});
|
|
mq.addEventListener('change',settle);document.addEventListener('visibilitychange',()=>{if(document.hidden)settle();});window.addEventListener('pagehide',settle);window.addEventListener('beforeprint',()=>{move(true,true);});move(false,true);window.mechanismQA={settle,state:()=>({expanded,selected,active:!!animation,positions:model.map(m=>m.x)})};
|
|
</script><!-- LICENSES --></body></html>
|