|
46 | 46 | .index{position:absolute;right:clamp(18px,3vw,42px);top:19%;z-index:20;text-align:right}.index .num{font:clamp(3.5rem,9vw,8.6rem)/.75 Arial,sans-serif;letter-spacing:-.08em;color:rgba(21,27,27,.09)}.index small{display:block;font:9px/1.4 "Courier New",monospace;letter-spacing:.18em;text-transform:uppercase;margin-top:11px} |
47 | 47 | .panel{position:absolute;z-index:30;width:min(265px,25vw);border-top:1px solid var(--ink);padding-top:10px;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);background:linear-gradient(to bottom,var(--panel),rgba(234,233,226,.15));opacity:0;transform:translateY(18px);will-change:transform,opacity}.panel-left{left:clamp(18px,3vw,44px);top:18%}.panel-right{right:clamp(18px,3vw,44px);bottom:16%}.panel-head{display:flex;justify-content:space-between;font:8px/1.2 "Courier New",monospace;letter-spacing:.16em;text-transform:uppercase}.blink{display:inline-flex;align-items:center;gap:7px}.blink:before{content:"";width:5px;height:5px;border-radius:50%;background:var(--cyan);box-shadow:0 0 10px var(--cyan)}.panel h2{font-size:clamp(17px,1.8vw,25px);font-weight:500;line-height:1;margin:24px 0 7px}.latin{font:italic 11px/1.3 Georgia,serif;color:var(--muted)}.metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin-top:20px}.metric{border-top:1px solid var(--line);padding-top:7px}.metric b{font:18px/1 Arial,sans-serif}.metric span{display:block;font:7px/1.2 "Courier New",monospace;letter-spacing:.12em;margin-top:5px;color:var(--muted)} |
48 | 48 | .specimen-list{display:grid;gap:0;margin-top:18px;border-top:1px solid var(--line)}.specimen-btn{border:0;border-bottom:1px solid var(--line);background:transparent;padding:9px 0;display:flex;justify-content:space-between;text-align:left;cursor:pointer;font:8px/1.2 "Courier New",monospace;letter-spacing:.09em;text-transform:uppercase}.specimen-btn.active{color:#007e73}.specimen-btn.active:after{content:"●";color:var(--cyan)} |
49 | | -.macro{height:125px;margin-top:16px;border:1px solid var(--line-hi);overflow:hidden;position:relative;background:#aeb1a9}.macro img{width:100%;height:100%;object-fit:cover;transform:scale(var(--mz,2.7));transform-origin:var(--mx,44%) var(--my,52%);filter:saturate(1.12) contrast(1.08);transition:transform .9s var(--ease),transform-origin .9s var(--ease)}.macro:before,.macro:after{content:"";position:absolute;z-index:2;pointer-events:none}.macro:before{inset:9px;border:1px solid rgba(0,230,210,.76)}.macro:after{left:50%;top:50%;width:14px;height:14px;border:1px solid var(--cyan);border-radius:50%;transform:translate(-50%,-50%)}.macro-meta{display:flex;justify-content:space-between;margin-top:7px;font:7px/1 "Courier New",monospace;letter-spacing:.1em;text-transform:uppercase} |
| 49 | +.macro{height:125px;margin-top:16px;border:1px solid var(--line-hi);overflow:hidden;position:relative;background:#aeb1a9}.macro video{display:block;width:100%;height:100%;object-fit:cover;transform:scale(var(--mz,2.7));transform-origin:var(--mx,44%) var(--my,52%);filter:saturate(1.12) contrast(1.08);transition:transform .9s var(--ease),transform-origin .9s var(--ease);will-change:transform}.macro:before,.macro:after{content:"";position:absolute;z-index:2;pointer-events:none}.macro:before{inset:9px;border:1px solid rgba(0,230,210,.76)}.macro:after{left:50%;top:50%;width:14px;height:14px;border:1px solid var(--cyan);border-radius:50%;transform:translate(-50%,-50%)}.macro-meta{display:flex;justify-content:space-between;margin-top:7px;font:7px/1 "Courier New",monospace;letter-spacing:.1em;text-transform:uppercase} |
50 | 50 | .scroll-cue{position:absolute;right:clamp(18px,3vw,42px);bottom:clamp(22px,4.5vh,54px);z-index:20;display:flex;align-items:center;gap:10px;font:8px/1 "Courier New",monospace;letter-spacing:.17em;text-transform:uppercase}.scroll-cue i{width:42px;height:1px;background:var(--ink);display:block;transform-origin:left;animation:pulse 2.4s var(--ease-scan) infinite}@keyframes pulse{50%{transform:scaleX(.25);opacity:.4}} |
51 | 51 | .section{padding:clamp(80px,13vw,170px) clamp(18px,5vw,72px);border-top:1px solid var(--line);position:relative}.section-tag{font:9px/1 "Courier New",monospace;letter-spacing:.22em;text-transform:uppercase;color:var(--muted)}.section h2{font-size:clamp(2.7rem,7vw,7rem);font-weight:500;line-height:.86;letter-spacing:-.065em;text-transform:uppercase;margin:.25em 0}.section-lede{font-size:clamp(1rem,1.7vw,1.4rem);line-height:1.45;max-width:46ch;color:#424b49}.catalogue{background:#171d1d;color:#e9e9e1}.catalogue .section-tag,.catalogue .section-lede{color:#929b98}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:rgba(255,255,255,.13);margin-top:clamp(44px,7vw,90px);border:1px solid rgba(255,255,255,.13)}.card{background:#171d1d;padding:clamp(20px,3vw,36px);min-height:310px;position:relative;overflow:hidden}.card-num{font:9px/1 "Courier New",monospace;letter-spacing:.18em;color:var(--cyan)}.card h3{font:500 clamp(1.5rem,3vw,3rem)/.9 Arial,sans-serif;letter-spacing:-.05em;text-transform:uppercase;margin:90px 0 13px}.card p{font-size:12px;line-height:1.5;color:#909997;max-width:28ch}.card-orbit{position:absolute;width:150px;height:150px;border:1px solid rgba(0,230,210,.17);border-radius:50%;right:-55px;top:-55px}.card-orbit:after{content:"";position:absolute;inset:28px;border:1px dashed rgba(0,230,210,.35);border-radius:50%}.card-orbit{transition:transform 1.2s var(--ease)} |
52 | 52 | .method-grid{display:grid;grid-template-columns:.8fr 1.2fr;gap:clamp(40px,9vw,140px);align-items:start}.steps{border-top:1px solid var(--ink)}.step{display:grid;grid-template-columns:46px 1fr;gap:20px;border-bottom:1px solid var(--line);padding:23px 0}.step b{font:10px/1 "Courier New",monospace;color:#008b7e}.step h3{font-size:clamp(1.1rem,2vw,1.7rem);margin:0 0 7px;font-weight:500}.step p{font-size:12px;line-height:1.5;color:var(--muted);margin:0;max-width:46ch}.final{min-height:90svh;display:grid;place-items:center;text-align:center;background:var(--paper-hi)}.final h2{max-width:12ch}.final a{display:inline-flex;margin-top:25px;border:1px solid var(--ink);padding:15px 22px;text-decoration:none;font:9px/1 "Courier New",monospace;letter-spacing:.16em;text-transform:uppercase;transition:background .3s var(--ease),color .3s var(--ease)} |
|
68 | 68 | <div class="index" aria-hidden="true"><div class="num">01</div><small>Tolerance ±0.002 μm<br/>Cycle 11.667 s</small></div> |
69 | 69 | <div class="specimen-wrap" aria-hidden="true"><div class="specimen-stage"><video class="specimen base" src="./assets/kern-mono.mp4" poster="./assets/kern-calibration-unit.png" muted loop playsinline webkit-playsinline autoplay preload="auto" disablepictureinpicture></video><video class="specimen scan" src="./assets/kern-color.mp4" poster="./assets/kern-calibration-unit.png" muted loop playsinline webkit-playsinline autoplay preload="auto" disablepictureinpicture></video><div class="scan-plane"></div><div class="bloom-ring"></div><div class="bloom-ring"></div><div class="bloom-ring"></div><div class="bloom-ring"></div><div class="bloom-ring"></div><div class="target"><i></i></div><div class="connector"></div></div></div> |
70 | 70 | <aside class="panel panel-left" aria-label="Calibration data"><div class="panel-head"><span>System index</span><span class="blink">Scan active</span></div><h2 id="speciesName">Copper coil</h2><div class="latin" id="speciesLatin">Electromagnetic impulse stage</div><div class="metrics"><div class="metric"><b id="metricVital">96</b><span>Stability</span></div><div class="metric"><b id="metricSignal">91</b><span>Torque</span></div><div class="metric"><b id="metricRisk">04</b><span>Drift</span></div></div><div class="specimen-list" role="list"><button class="specimen-btn active" data-index="0" type="button">01 Copper coil</button><button class="specimen-btn" data-index="1" type="button">02 Outer gimbal</button><button class="specimen-btn" data-index="2" type="button">03 Escapement</button><button class="specimen-btn" data-index="3" type="button">04 Ruby bearing</button><button class="specimen-btn" data-index="4" type="button">05 Cyan emitter</button></div></aside> |
71 | | - <aside class="panel panel-right" aria-label="Macro optical view"><div class="panel-head"><span>Macro optics</span><span id="plateLabel">Plate 01/A</span></div><div class="macro"><img src="./assets/kern-calibration-unit.png" alt="Magnified precision mechanism" /></div><div class="macro-meta"><span id="magnification">MAG 4.8×</span><span>Tracking locked</span></div></aside> |
| 71 | + <aside class="panel panel-right" aria-label="Macro optical view"><div class="panel-head"><span>Macro optics</span><span id="plateLabel">Plate 01/A</span></div><div class="macro"><video class="macro-video" src="./assets/kern-color.mp4" poster="./assets/kern-calibration-unit.png" muted loop playsinline webkit-playsinline autoplay preload="auto" disablepictureinpicture aria-label="Live magnified precision mechanism"></video></div><div class="macro-meta"><span id="magnification">MAG 4.8×</span><span>Live / phase locked</span></div></aside> |
72 | 72 | <div class="scroll-cue" aria-hidden="true"><i></i>Scroll to calibrate</div> |
73 | 73 | </div> |
74 | 74 | </section> |
|
81 | 81 | const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches; |
82 | 82 | const track = document.querySelector('.hero-track'), scan = document.querySelector('.specimen.scan'); |
83 | 83 | const monoVideo = document.querySelector('video.specimen.base'), colorVideo = document.querySelector('video.specimen.scan'); |
| 84 | + const macroVideo = document.querySelector('video.macro-video'); |
84 | 85 | const plane = document.querySelector('.scan-plane'), stage = document.querySelector('.specimen-stage'); |
85 | 86 | const target = document.querySelector('.target'), connector = document.querySelector('.connector'); |
86 | 87 | const heroTitle = document.querySelector('.hero-title'); |
|
139 | 140 | } |
140 | 141 | function ping(freq=310){if(!audioCtx)return;const o=audioCtx.createOscillator(),g=audioCtx.createGain();o.frequency.value=freq;o.type='sine';g.gain.setValueAtTime(.025,audioCtx.currentTime);g.gain.exponentialRampToValueAtTime(.0001,audioCtx.currentTime+.22);o.connect(g).connect(audioCtx.destination);o.start();o.stop(audioCtx.currentTime+.23)} |
141 | 142 | document.querySelectorAll('.specimen-btn').forEach(b=>b.addEventListener('click',()=>setSpecimen(+b.dataset.index,true))); |
142 | | - function syncVideos(){ |
143 | | - if(monoVideo.readyState<2||colorVideo.readyState<2||!monoVideo.duration||!colorVideo.duration)return; |
144 | | - const duration=Math.min(monoVideo.duration,colorVideo.duration); |
145 | | - const raw=monoVideo.currentTime-colorVideo.currentTime; |
| 143 | + function syncFollower(video,duration){ |
| 144 | + if(video.readyState<2||!video.duration)return; |
| 145 | + const raw=monoVideo.currentTime-video.currentTime; |
146 | 146 | const delta=((raw+duration/2)%duration+duration)%duration-duration/2; |
147 | | - colorVideo.playbackRate=monoVideo.playbackRate+Math.max(-.2,Math.min(.2,delta*1.6)); |
148 | | - if(monoVideo.paused!==colorVideo.paused){monoVideo.paused?colorVideo.pause():colorVideo.play().catch(()=>{});} |
| 147 | + video.playbackRate=monoVideo.playbackRate+Math.max(-.2,Math.min(.2,delta*1.6)); |
| 148 | + if(monoVideo.paused!==video.paused){monoVideo.paused?video.pause():video.play().catch(()=>{});} |
| 149 | + } |
| 150 | + function syncVideos(){ |
| 151 | + if(monoVideo.readyState<2||colorVideo.readyState<2||macroVideo.readyState<2||!monoVideo.duration)return; |
| 152 | + const duration=Math.min(monoVideo.duration,colorVideo.duration,macroVideo.duration); |
| 153 | + syncFollower(colorVideo,duration); |
| 154 | + syncFollower(macroVideo,duration); |
149 | 155 | } |
150 | 156 | const whenReady=(video)=>video.readyState>=2?Promise.resolve():new Promise(resolve=>video.addEventListener('loadeddata',resolve,{once:true})); |
151 | 157 | let startPromise=null; |
152 | 158 | function startVideos(){ |
153 | | - if(reduce){monoVideo.pause();colorVideo.pause();try{monoVideo.currentTime=0;colorVideo.currentTime=0}catch(_){}return Promise.resolve();} |
154 | | - if(!monoVideo.paused&&!colorVideo.paused&&monoVideo.readyState>=2&&colorVideo.readyState>=2)return Promise.resolve(); |
| 159 | + if(reduce){[monoVideo,colorVideo,macroVideo].forEach(video=>{video.pause();try{video.currentTime=0}catch(_){}});return Promise.resolve();} |
| 160 | + if([monoVideo,colorVideo,macroVideo].every(video=>!video.paused&&video.readyState>=2))return Promise.resolve(); |
155 | 161 | if(startPromise)return startPromise; |
156 | 162 | startPromise=(async()=>{ |
157 | | - monoVideo.defaultMuted=colorVideo.defaultMuted=true; |
158 | | - await Promise.all([whenReady(monoVideo),whenReady(colorVideo)]); |
159 | | - if(Math.abs(monoVideo.currentTime-colorVideo.currentTime)>.08)colorVideo.currentTime=monoVideo.currentTime; |
160 | | - await Promise.all([monoVideo.play(),colorVideo.play()]); |
| 163 | + [monoVideo,colorVideo,macroVideo].forEach(video=>video.defaultMuted=true); |
| 164 | + await Promise.all([monoVideo,colorVideo,macroVideo].map(whenReady)); |
| 165 | + [colorVideo,macroVideo].forEach(video=>{if(Math.abs(monoVideo.currentTime-video.currentTime)>.08)video.currentTime=monoVideo.currentTime}); |
| 166 | + await Promise.all([monoVideo,colorVideo,macroVideo].map(video=>video.play())); |
161 | 167 | syncVideos(); |
162 | 168 | })().catch(()=>{/* posters remain visible until browser permits playback */}).finally(()=>{startPromise=null}); |
163 | 169 | return startPromise; |
|
177 | 183 | document.addEventListener('visibilitychange',()=>{if(!document.hidden&&heroVisible)startVideos()}); |
178 | 184 | addEventListener('pageshow',startVideos,{once:true}); |
179 | 185 | // Persistent recovery: any interaction restarts a stalled loop while the hero is on screen. |
180 | | - ['pointerdown','touchstart','wheel','scroll'].forEach(type=>addEventListener(type,()=>{if(heroVisible&&!reduce&&(monoVideo.paused||colorVideo.paused))startVideos()},{passive:true})); |
181 | | - // Decode budget: pause both loops when the hero leaves the viewport, resume in sync on return. |
| 186 | + ['pointerdown','touchstart','wheel','scroll'].forEach(type=>addEventListener(type,()=>{if(heroVisible&&!reduce&&[monoVideo,colorVideo,macroVideo].some(video=>video.paused))startVideos()},{passive:true})); |
| 187 | + // Decode budget: pause all synchronized loops when the hero leaves the viewport, resume in sync on return. |
182 | 188 | new IntersectionObserver(es=>{heroVisible=es[0].isIntersecting; |
183 | 189 | if(reduce)return; |
184 | | - if(heroVisible)startVideos();else{monoVideo.pause();colorVideo.pause();} |
| 190 | + if(heroVisible)startVideos();else [monoVideo,colorVideo,macroVideo].forEach(video=>video.pause()); |
185 | 191 | },{rootMargin:'15%'}).observe(stage); |
186 | 192 | startVideos(); |
187 | 193 | const smooth=t=>t<=0?0:t>=1?1:t*t*(3-2*t); |
|
0 commit comments