component
Sunon MF40200V1-1000U-G99 · 5 V tachometer fan
Public Made by Adomby adom
Tachometer fan with factory flying leads, an explicit Adom connector harness and gentle GLB state demonstration.
main
John Lauer
Bundle fan preview data for authenticated wiki rendering without companion fetches
e73bd0e
12d ago
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Adom · Fan component states</title><link rel="icon" href="data:,"><link rel="stylesheet" href="https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/style.css"><script type="module" src="https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/adom-3d-viewer-babylon9.esm.js"></script><style>body{margin:0;background:#10161d;color:#e6edf3;font:15px system-ui}header{height:44px;box-sizing:border-box;padding:10px 18px;border-bottom:1px solid #35424c}#view{position:absolute;top:44px;bottom:0;left:320px;right:0}aside{width:280px;padding:20px}h1{font-size:23px}p{color:#aabac6;line-height:1.5}button,input,select{font:inherit}button{background:#253847;color:#fff;border:1px solid #618092;border-radius:6px;padding:9px 20px;margin-right:6px;cursor:pointer}button.active{background:#087c76}input[type=range]{width:100%}strong{font-size:30px;color:#5ee4ce}label{display:block;margin:18px 0}#badge{color:#ffd17b}#status{font-size:12px;white-space:pre-wrap}small{color:#aabac6}</style></head><body><header>Adom / Component states / Sunon fan</header><aside><h1>MF40200V1-1000U-G99</h1><p>Fan state and speed preview</p><div id="badge">SIMULATED · no hardware connection</div><p><strong id="rpm">0</strong> RPM</p><button id="off" class="active">Off</button><button id="on">On</button><label>Simulated rotor speed <input id="speed" type="range" min="0" max="10000" step="100" value="7000"><output id="requested">7000 RPM</output></label><label>Playback<select id="scale"><option value="0.02">Slow motion · 0.02×</option><option value="1">Real time · 1×</option></select></label><p id="rate">Rotor stopped</p><small>Nominal envelope. Blade and support geometry are illustrative. Animation demonstrates state control; it does not prove airflow or cooling.</small><p><small>At high RPM, screen sampling can make blades appear slower or reversed.</small></p><div id="status">Loading canonical Adom viewer…</div></aside><div id="view"></div><script type="module">
import {bindFanMotion} from '../states/controller.js';
const $=id=>document.getElementById(id);let state='off';
try{
const V=window.Adom3DViewerBabylon9,B=window.BABYLON;const v=V.init($('view'),{zUp:true,showViewCube:true,showGround:false});const scene=v.getScene();window.fanViewer=v;scene.lights.filter(l=>l.name==='light').forEach(l=>l.intensity=1.4);
const imported=await B.SceneLoader.ImportMeshAsync('','../states/','fan-animated.glb',scene);imported.meshes[0].scaling.scaleInPlace(1000);const manifest=await(await fetch('../states.json')).json();const motion=bindFanMotion(imported,manifest);window.fanMotion=motion;window.fanImported=imported;window.fanScene=scene;
const c=v.getCamera();c.setTarget(new B.Vector3(0,0,10));c.radius=105;c.alpha=-1.15;c.beta=.70;
function apply(){const speed=state==='off'?0:Number($('speed').value),timeScale=Number($('scale').value);const s=motion.apply({state,speed,timeScale,source:'simulated'});$('rpm').textContent=s.speed;$('requested').textContent=$('speed').value+' RPM';$('rate').textContent=state==='off'?'Rotor stopped':`${timeScale}× playback · ${(speed*timeScale).toFixed(0)} displayed RPM`;$('off').classList.toggle('active',state==='off');$('on').classList.toggle('active',state==='on');$('status').textContent='Rotor: Fan.Rotor\nAnimation: Fan.Rotor.Revolution';return s;}
$('off').onclick=()=>{state='off';apply()};$('on').onclick=()=>{state='on';apply()};$('speed').oninput=apply;$('scale').onchange=apply;
window.fanPreview={set(s,rpm,scale=.02){state=s;$('speed').value=rpm;$('scale').value=scale;return apply()},status:()=>motion.status()};
addEventListener('pagehide',()=>motion.dispose());document.addEventListener('visibilitychange',()=>{if(document.hidden)motion.pause();else apply()});
apply();await new Promise(r=>scene.executeWhenReady(r));v.toggleAxes('world',false);scene.lights.filter(l=>l.name==='light').forEach(l=>l.intensity=1.4);scene.getEngine().hideLoadingUI();window.fanReady=true;
}catch(e){$('status').textContent=e.stack;console.error(e)}
</script></body></html>