.stockbot{
--sb-height: 300px;
--sb-bg:#F4F6F9; --sb-floor:#E4EAF2; --sb-line:#DCE3EC;
--sb-shelf:#B7C2D2; --sb-shelf-2:#9AA8BC;
--sb-teal:#0387D1; --sb-teal-soft:#7FBEE4;
--sb-gold:#157F3C; --sb-gold-deep:#157F3C;
--sb-ink:#0A1B33; --sb-ink-2:#4A5A72; --sb-ink-3:#7C8AA0;
--sb-surface:#FFFFFF;
--sb-i1:#C3CCDA; --sb-i2:#AEBACB; --sb-i3:#D5DCE7; --sb-i4:#9FB0C6; --sb-i5:#BCC7D6;
--sb-font:"IBM Plex Sans","Segoe UI",Helvetica,Arial,sans-serif;
--sb-mono:"Fira Mono",ui-monospace,Consolas,monospace;
}
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]) .stockbot{
--sb-bg:#061225; --sb-floor:#0B1B33; --sb-line:#1D3050;
--sb-shelf:#26405F; --sb-shelf-2:#33567C;
--sb-teal:#4FB3EE; --sb-teal-soft:#2A6B96;
--sb-gold:#4ADE80; --sb-gold-deep:#4ADE80;
--sb-ink:#E8EEF7; --sb-ink-2:#9EAEC6; --sb-ink-3:#71829C;
--sb-surface:#0D1D35;
--sb-i1:#33465F; --sb-i2:#3D5271; --sb-i3:#2A3A50; --sb-i4:#465873; --sb-i5:#38495F;
}
}
:root[data-theme="dark"] .stockbot{
--sb-bg:#061225; --sb-floor:#0B1B33; --sb-line:#1D3050;
--sb-shelf:#26405F; --sb-shelf-2:#33567C;
--sb-teal:#4FB3EE; --sb-teal-soft:#2A6B96;
--sb-gold:#4ADE80; --sb-gold-deep:#4ADE80;
--sb-ink:#E8EEF7; --sb-ink-2:#9EAEC6; --sb-ink-3:#71829C;
--sb-surface:#0D1D35;
--sb-i1:#33465F; --sb-i2:#3D5271; --sb-i3:#2A3A50; --sb-i4:#465873; --sb-i5:#38495F;
}
.stockbot,.stockbot *{box-sizing:border-box}
.stockbot{width:100%;font-family:var(--sb-font)}
.stockbot__frame{border:1px solid var(--sb-line);border-radius:13px;background:var(--sb-bg);overflow:hidden}
.stockbot__canvas{display:block;width:100%;height:var(--sb-height);max-width:100%}
.stockbot__bar{display:flex;flex-wrap:wrap;align-items:center;border-top:1px solid var(--sb-line);background:var(--sb-surface)}
.stockbot__stat{display:flex;align-items:baseline;gap:9px;padding:11px 20px;border-right:1px solid var(--sb-line);font-family:var(--sb-mono);font-size:11px;line-height:1.4;letter-spacing:.11em;text-transform:uppercase;color:var(--sb-ink-3)}
.stockbot__stat b{font-size:14px;font-weight:700;letter-spacing:.02em;color:var(--sb-ink);font-variant-numeric:tabular-nums}
.stockbot__stat--live b{color:var(--sb-gold-deep)}
.stockbot__note{margin-left:auto;padding:11px 20px;font-family:var(--sb-mono);font-size:10px;line-height:1.4;letter-spacing:.08em;text-transform:uppercase;color:var(--sb-ink-3)}
@media (max-width:820px){
.stockbot__note{margin-left:0;width:100%;border-top:1px solid var(--sb-line)}
.stockbot__stat{padding:10px 14px}
}
Items read0
Wrong position0
Suspected missing0
StatusScanning
Illustration of scanning behaviour — not live data
(function () {
'use strict';
function initAll(){
var n = document.querySelectorAll('.stockbot');
for (var i = 0; i < n.length; i++) init(n[i]);
}
function init(root){
if (root.getAttribute('data-sb-ready') === '1') return;
root.setAttribute('data-sb-ready', '1');
var cv = root.querySelector('.stockbot__canvas');
if (!cv || !cv.getContext) return;
var ctx = cv.getContext('2d');
var elScanned = root.querySelector('[data-sb="scanned"]');
var elFlagged = root.querySelector('[data-sb="flagged"]');
var elMissing = root.querySelector('[data-sb="missing"]');
var elStatus = root.querySelector('[data-sb="status"]');
var motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
var W=0,H=0,dpr=1,floorY=0,shelfTop=0,rows=3,rowH=0;
var items=[],uprights=[],bot=null;
var scanned=0,flagged=0,missing=0,rafId=null,last=0,running=false,C={},MODE='vision';
function readTokens(){
var s=getComputedStyle(root);
function g(n){return s.getPropertyValue(n).trim();}
C={bg:g('--sb-bg'),floor:g('--sb-floor'),line:g('--sb-line'),shelf:g('--sb-shelf'),shelf2:g('--sb-shelf-2'),
teal:g('--sb-teal'),tealSoft:g('--sb-teal-soft'),gold:g('--sb-gold'),goldDeep:g('--sb-gold-deep'),
ink:g('--sb-ink'),ink2:g('--sb-ink-2'),ink3:g('--sb-ink-3'),surface:g('--sb-surface'),
items:[g('--sb-i1'),g('--sb-i2'),g('--sb-i3'),g('--sb-i4'),g('--sb-i5')],mono:g('--sb-mono')||'monospace'};
MODE=(root.getAttribute('data-mode')||'vision').toLowerCase();
if(MODE!=='rfid') MODE='vision';
}
function rr(x,y,w,h,r){r=Math.min(r,Math.abs(w)/2,Math.abs(h)/2);ctx.beginPath();ctx.moveTo(x+r,y);
ctx.arcTo(x+w,y,x+w,y+h,r);ctx.arcTo(x+w,y+h,x,y+h,r);ctx.arcTo(x,y+h,x,y,r);ctx.arcTo(x,y,x+w,y,r);ctx.closePath();}
function build(){
floorY=H-Math.max(20,H*0.08); shelfTop=Math.max(14,H*0.06);
rows=H<240?3:4; rowH=(floorY-shelfTop)/rows;
uprights=[];
var step=Math.max(150,W/Math.max(2,Math.round(W/190)));
for(var ux=0;ux<=W+step;ux+=step) uprights.push(ux);
items=[];
for(var r=0;r<rows;r++){
var boardY=shelfTop+(r+1)*rowH, x=8;
while(xW-8) break;
var gap=2+Math.random()*3;
var empty=Math.random()<0.07;
var h=rowH*(0.52+Math.random()*0.34);
items.push({x:x,w:w,h:h,row:r,top:boardY-3-h,col:C.items[(Math.random()*C.items.length)|0],
empty:empty,misplaced:(!empty&&Math.random()<0.06),state:'pending',t:0});
x+=w+gap;
}
}
bot={x:-60,speed:52+Math.random()*10,alpha:0,h:(floorY-shelfTop)*0.92};
}
function reset(){build(); if(elStatus) elStatus.textContent='Scanning';}
function scanX(){return bot.x+26;}
function update(dt){
bot.x+=bot.speed*dt; bot.alpha=Math.min(1,bot.alpha+dt*2);
var sx=scanX();
for(var i=0;i=it.x+it.w*0.5){
it.state='scan'; it.t=0;
if(it.empty){missing++;} else {scanned++; if(it.misplaced) flagged++;}
} else if(it.state==='scan'){
it.t+=dt/0.55; if(it.t>=1){it.t=1;it.state='done';}
}
}
if(bot.x>W+70){scanned=0;flagged=0;missing=0;reset();}
}
function drawShelving(){
ctx.fillStyle=C.floor; ctx.fillRect(0,floorY,W,H-floorY);
ctx.strokeStyle=C.shelf2; ctx.globalAlpha=.5;
ctx.beginPath();ctx.moveTo(0,floorY+.5);ctx.lineTo(W,floorY+.5);ctx.stroke();
ctx.globalAlpha=1;
ctx.fillStyle=C.shelf;
for(var r=0;r<=rows;r++){ctx.fillRect(0,shelfTop+r*rowH,W,3);}
ctx.fillStyle=C.shelf2;
for(var i=0;i<uprights.length;i++){ctx.fillRect(uprights[i],shelfTop,4,floorY-shelfTop);}
ctx.fillStyle=C.ink3; ctx.font='500 8px '+C.mono; ctx.textAlign='left'; ctx.textBaseline='top';
for(var j=0;j<uprights.length-1;j++){ctx.fillText('A'+(j+1),uprights[j]+8,shelfTop+5);}
}
function drawItems(){
for(var i=0;i<items.length;i++){
var it=items[i];
if(!it.empty){
ctx.fillStyle=it.col; rr(it.x,it.top,it.w,it.h,1.5); ctx.fill();
ctx.fillStyle=C.surface; ctx.globalAlpha=.22;
ctx.fillRect(it.x+1.5,it.top+2,Math.max(1,it.w*0.22),it.h-4); ctx.globalAlpha=1;
}
if(it.state==='scan'){
var e=1-Math.pow(1-it.t,2);
ctx.globalAlpha=0.35+0.65*(1-it.t);
ctx.strokeStyle=it.empty?C.ink3:C.teal; ctx.lineWidth=1.4;
if(it.empty) ctx.setLineDash([3,3]);
var pad=2.5*(1-e)+1.5;
rr(it.x-pad,it.top-pad,it.w+pad*2,it.h+pad*2,2); ctx.stroke();
ctx.setLineDash([]); ctx.globalAlpha=1;
}
if(it.state==='done'){
if(it.empty){
ctx.strokeStyle=C.ink3; ctx.globalAlpha=.55; ctx.lineWidth=1; ctx.setLineDash([3,3]);
rr(it.x,it.top,it.w,it.h,2); ctx.stroke(); ctx.setLineDash([]); ctx.globalAlpha=1;
} else if(it.misplaced){
ctx.strokeStyle=C.teal; ctx.lineWidth=1.3;
rr(it.x-1.5,it.top-1.5,it.w+3,it.h+3,2); ctx.stroke();
ctx.fillStyle=C.teal; ctx.beginPath(); ctx.arc(it.x+it.w/2,it.top-5,2,0,Math.PI*2); ctx.fill();
} else {
ctx.fillStyle=C.gold; ctx.globalAlpha=.85;
ctx.fillRect(it.x,it.top+it.h+1.5,it.w,1.4); ctx.globalAlpha=1;
}
}
}
}
function drawScan(){
var sx=scanX(); if(sxW+30) return;
var bandW=MODE==='rfid'?96:62, top=shelfTop, bot2=floorY;
var grad=ctx.createLinearGradient(sx-bandW/2,0,sx+bandW/2,0);
grad.addColorStop(0,'rgba(0,0,0,0)'); grad.addColorStop(0.5,C.teal); grad.addColorStop(1,'rgba(0,0,0,0)');
ctx.globalAlpha=MODE==='rfid'?.14:.18; ctx.fillStyle=grad;
ctx.fillRect(sx-bandW/2,top,bandW,bot2-top); ctx.globalAlpha=1;
ctx.strokeStyle=C.teal; ctx.globalAlpha=.75; ctx.lineWidth=1.2;
ctx.beginPath();ctx.moveTo(sx,top);ctx.lineTo(sx,bot2);ctx.stroke(); ctx.globalAlpha=1;
}
function drawBot(){
var baseW=34,baseH=13,x=bot.x,y=floorY,topY=y-bot.h;
ctx.save(); ctx.globalAlpha=bot.alpha;
ctx.fillStyle=C.teal; ctx.globalAlpha=bot.alpha*.12;
ctx.beginPath();ctx.ellipse(x,y+2,baseW*0.85,5,0,0,Math.PI*2);ctx.fill();
ctx.globalAlpha=bot.alpha;
var headY=topY+10;
ctx.strokeStyle=C.teal; ctx.globalAlpha=bot.alpha*.45; ctx.lineWidth=1;
if(MODE==='rfid'){
for(var k=1;k<=3;k++){
ctx.beginPath();
ctx.arc(x+8,y-bot.h*0.5,12+k*11,-Math.PI*0.42,Math.PI*0.42);
ctx.stroke();
}
} else {
ctx.beginPath();
ctx.moveTo(x+6,headY);ctx.lineTo(scanX()+22,shelfTop+6);
ctx.moveTo(x+6,headY);ctx.lineTo(scanX()+22,floorY-6);
ctx.stroke();
}
ctx.globalAlpha=bot.alpha;
ctx.strokeStyle=C.ink2; ctx.lineWidth=4; ctx.lineCap='round';
ctx.beginPath();ctx.moveTo(x,topY+26);ctx.lineTo(x,y-baseH-26);ctx.stroke();
ctx.fillStyle=C.ink; rr(x-5,topY,10,18,2.5); ctx.fill();
ctx.fillStyle=C.teal; ctx.beginPath();ctx.arc(x,topY+9,2.4,0,Math.PI*2);ctx.fill();
ctx.fillStyle=C.surface; ctx.strokeStyle=C.teal; ctx.lineWidth=2;
ctx.beginPath();ctx.ellipse(x,topY+34,15,8,0,0,Math.PI*2);ctx.fill();ctx.stroke();
var bodyTop=topY+40,bodyBot=y-baseH-1;
ctx.beginPath();
ctx.moveTo(x-8,bodyTop);
ctx.quadraticCurveTo(x-10,(bodyTop+bodyBot)/2,x-19,bodyBot);
ctx.lineTo(x+19,bodyBot);
ctx.quadraticCurveTo(x+10,(bodyTop+bodyBot)/2,x+8,bodyTop);
ctx.closePath();
ctx.fillStyle=C.surface; ctx.fill(); ctx.strokeStyle=C.shelf2; ctx.lineWidth=1; ctx.stroke();
ctx.fillStyle=C.surface; rr(x-baseW/2,y-baseH,baseW,baseH,5); ctx.fill();
ctx.strokeStyle=C.teal; ctx.lineWidth=2; rr(x-baseW/2,y-baseH,baseW,baseH,5); ctx.stroke();
ctx.fillStyle=C.ink2; ctx.fillRect(x-7,y-baseH/2-1,14,2);
ctx.restore();
}
function draw(){
ctx.clearRect(0,0,W,H);
ctx.fillStyle=C.bg; ctx.fillRect(0,0,W,H);
drawShelving(); drawItems(); drawScan(); drawBot();
}
function syncStats(){
if(elScanned&&elScanned.textContent!==String(scanned)) elScanned.textContent=scanned.toLocaleString('en-US');
if(elFlagged&&elFlagged.textContent!==String(flagged)) elFlagged.textContent=String(flagged);
if(elMissing&&elMissing.textContent!==String(missing)) elMissing.textContent=String(missing);
}
function frame(now){
if(!running) return;
var dt=Math.min(0.05,(now-last)/1000||0); last=now;
update(dt); draw(); syncStats();
rafId=requestAnimationFrame(frame);
}
function start(){ if(running||motionQuery.matches||!W) return; running=true; last=performance.now(); rafId=requestAnimationFrame(frame); }
function stop(){ running=false; if(rafId) cancelAnimationFrame(rafId); rafId=null; }
function staticFrame(){
bot.x=W*0.46; bot.alpha=1;
var sx=scanX(); scanned=0;flagged=0;missing=0;
for(var i=0;i<items.length;i++){
var it=items[i];
if(it.x+it.w*0.5<sx){ it.state='done'; it.t=1;
if(it.empty) missing++; else { scanned++; if(it.misplaced) flagged++; } }
}
draw(); syncStats();
if(elStatus) elStatus.textContent='Scanning';
}
function resize(){
var r=cv.getBoundingClientRect(); if(!r.width) return;
dpr=Math.min(window.devicePixelRatio||1,2);
cv.width=Math.round(r.width*dpr); cv.height=Math.round(r.height*dpr);
ctx.setTransform(dpr,0,0,dpr,0,0);
W=r.width; H=r.height;
readTokens(); scanned=0;flagged=0;missing=0; build();
if(motionQuery.matches) staticFrame(); else draw();
}
var rt;
window.addEventListener('resize',function(){clearTimeout(rt);rt=setTimeout(resize,140);});
var darkQuery=window.matchMedia('(prefers-color-scheme: dark)');
function repaint(){readTokens(); if(!running) draw();}
if(darkQuery.addEventListener) darkQuery.addEventListener('change',repaint);
if(window.MutationObserver){
new MutationObserver(repaint).observe(root,{attributes:true,attributeFilter:['data-scheme','data-mode']});
new MutationObserver(repaint).observe(document.documentElement,{attributes:true,attributeFilter:['data-theme']});
}
if(motionQuery.addEventListener){
motionQuery.addEventListener('change',function(){ if(motionQuery.matches){stop();staticFrame();} else {start();} });
}
document.addEventListener('visibilitychange',function(){ if(document.hidden) stop(); else start(); });
if('IntersectionObserver' in window){
new IntersectionObserver(function(e){ if(e[0].isIntersecting) start(); else stop(); },{threshold:0.02}).observe(cv);
}
function boot(){ resize(); if(!motionQuery.matches) start(); }
if(document.fonts&&document.fonts.ready) document.fonts.ready.then(boot).catch(boot); else boot();
}
if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',initAll);
else initAll();
})();