Skip to content

Commit 64e976e

Browse files
committed
Animate the realistic solar swarm automatically with pause and resume
1 parent 901771f commit 64e976e

3 files changed

Lines changed: 12 additions & 3 deletions

File tree

‎app/globals.css‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2250,3 +2250,5 @@ body {
22502250
.interactive-concept{touch-action:none;cursor:grab;outline:none}.interactive-concept:active{cursor:grabbing}.interactive-concept:focus-visible{box-shadow:inset 0 0 0 2px #86bfd7}.interactive-concept img{pointer-events:none;will-change:transform}.interactive-concept:after,.interactive-concept .cinematic-title{pointer-events:none}.concept-zoom{position:absolute;right:22px;top:55px;z-index:5;display:flex;align-items:center;gap:8px;background:#050b13dc;border:1px solid #405060;border-radius:4px;padding:5px 8px;font-size:12px;color:#e7edf2}.concept-zoom button{padding:5px 8px;cursor:pointer}.concept-zoom button:hover{background:#233340}.concept-zoom span{font:11px var(--font-code)}
22512251

22522252
.concept-pan-surface{position:absolute;inset:0;width:100%;height:100%;text-align:left;border:0;padding:0;background:transparent;cursor:grab;touch-action:none}.concept-pan-surface:active{cursor:grabbing}.concept-pan-surface:focus-visible{outline:2px solid #9bd7ef;outline-offset:-3px}.concept-pan-surface .cinematic-title{z-index:2}
2253+
2254+
.animated-swarm{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:1;transform-origin:center}.moving-collector{animation-name:collector-orbit;animation-timing-function:linear;animation-iteration-count:infinite;transform-origin:0 0;filter:drop-shadow(0 0 3px #ffbc5d)}@keyframes collector-orbit{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.moving-collector{animation-duration:150s!important}}

‎components/interactive-concept.tsx‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export default function InteractiveConcept() {
55
const host=useRef<HTMLButtonElement>(null);
66
const drag=useRef<{x:number;y:number}|null>(null);
77
const [view,setView]=useState({x:0,y:0,z:1.15});
8+
const [animated,setAnimated]=useState(true);
89
const bound=(v:{x:number;y:number;z:number})=>{const r=host.current?.getBoundingClientRect();const z=Math.max(1.05,Math.min(3,v.z));const mx=(r?.width??1000)*(z-1)/2,my=(r?.height??550)*(z-1)/2;return{x:Math.max(-mx,Math.min(mx,v.x)),y:Math.max(-my,Math.min(my,v.y)),z};};
910
useEffect(()=>{const el=host.current;if(!el)return;const wheel=(e:WheelEvent)=>{e.preventDefault();setView(v=>bound({...v,z:v.z*Math.exp(-e.deltaY*.001)}));};el.addEventListener('wheel',wheel,{passive:false});return()=>el.removeEventListener('wheel',wheel);},[]);
1011
return <div className="swarm-cinematic interactive-concept"><button type="button" className="concept-pan-surface" ref={host} aria-label="Interactive Dyson swarm concept. Drag to pan, scroll to zoom. Arrow keys pan, plus and minus zoom, Home resets."
@@ -13,7 +14,13 @@ export default function InteractiveConcept() {
1314
onPointerUp={()=>{drag.current=null;}} onPointerCancel={()=>{drag.current=null;}}
1415
onKeyDown={e=>{if(['ArrowLeft','ArrowRight','ArrowUp','ArrowDown','+','-','=','Home'].includes(e.key))e.preventDefault();setView(v=>bound(e.key==='Home'?{x:0,y:0,z:1.15}:{...v,x:v.x+(e.key==='ArrowRight'?25:e.key==='ArrowLeft'?-25:0),y:v.y+(e.key==='ArrowDown'?25:e.key==='ArrowUp'?-25:0),z:v.z*(e.key==='+'||e.key==='='?1.15:e.key==='-'?1/1.15:1)}));}}>
1516
<Image src="/assets/dyson-swarm-hero.png" alt="Solar collectors surrounding the Sun" fill unoptimized priority sizes="100vw" draggable={false} style={{transform:`translate(${view.x}px, ${view.y}px) scale(${view.z})`}} />
16-
<div className="cinematic-title"><span>THE STARBOUND INITIATIVE</span><h2>A star.<br/>A million possibilities.</h2><p>Drag to explore the swarm. Scroll to move closer.</p></div>
17-
</button><div className="concept-zoom" onPointerDown={e=>e.stopPropagation()}><button aria-label="Zoom out" onClick={()=>setView(v=>bound({...v,z:v.z/1.2}))}>−</button><span>{Math.round(view.z*100)}%</span><button aria-label="Zoom in" onClick={()=>setView(v=>bound({...v,z:v.z*1.2}))}>+</button><button onClick={()=>setView({x:0,y:0,z:1.15})}>Reset view</button></div>
17+
<svg className="animated-swarm" viewBox="0 0 1672 941" preserveAspectRatio="xMidYMid slice" aria-label="Animated representative solar collectors orbiting the Sun" style={{transform:`translate(${view.x}px, ${view.y}px) scale(${view.z})`}}>
18+
{[0,1,2].map(ring=><g key={ring} transform={`translate(1085 370) rotate(${ring*24-28}) scale(1 ${.34+ring*.1})`}>
19+
<circle r={310+ring*115} fill="none" stroke="#f4c279" strokeOpacity=".14" strokeWidth="1" />
20+
{Array.from({length:22},(_,i)=><g key={i} transform={`rotate(${i*360/22+ring*11})`}><g className="moving-collector" style={{animationDuration:`${32+ring*18}s`,animationPlayState:animated?'running':'paused'}}><rect x={305+ring*115} y="-5" width="15" height="10" rx="1" fill={i%3===0?'#fee1a3':'#b0b8bd'} stroke="#fff0bf" strokeWidth="1"/><path d={`M${312+ring*115} -5v10`} stroke="#3e4856" strokeWidth="1"/></g></g>)}
21+
</g>)}
22+
</svg>
23+
<div className="cinematic-title"><span>THE STARBOUND INITIATIVE</span><h2>A star.<br/>A million possibilities.</h2><p>Watch the swarm orbit. Drag to explore. Scroll to move closer.</p></div>
24+
</button><div className="concept-zoom" onPointerDown={e=>e.stopPropagation()}><button onClick={()=>setAnimated(a=>!a)}>{animated?'Pause animation':'Play animation'}</button><button aria-label="Zoom out" onClick={()=>setView(v=>bound({...v,z:v.z/1.2}))}>−</button><span>{Math.round(view.z*100)}%</span><button aria-label="Zoom in" onClick={()=>setView(v=>bound({...v,z:v.z*1.2}))}>+</button><button onClick={()=>setView({x:0,y:0,z:1.15})}>Reset view</button></div>
1825
</div>;
1926
}

‎components/orbital-scene.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ export default function OrbitalScene({
351351
</Button>}
352352
</div>
353353
<div className="scene-scale">
354-
{mode === 'concept' ? 'Interactive concept artwork · live calculations in telemetry above' : `${error ? 'Interactive projected view' : 'Drag to orbit · scroll to zoom'} · representative collectors · sizes enlarged`}
354+
{mode === 'concept' ? 'Animated concept · illustrative orbital motion · live telemetry above' : `${error ? 'Interactive projected view' : 'Drag to orbit · scroll to zoom'} · representative collectors · sizes enlarged`}
355355
</div>
356356
</>
357357
);

0 commit comments

Comments
 (0)