[object Object]

← back to Ticket System

office: professional polish pass (design-agent driven) — Inter/mono type, solid command bar with stat pills, spark-bar workload legend, 3-row activity log, grouped controls, restrained bloom + warm/cool lighting + lane floor stripes; robust boot-curtain timer

64312788eb25ff356658b641b40856de20e4475c · 2026-07-27 07:54:34 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 64312788eb25ff356658b641b40856de20e4475c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jul 27 07:54:34 2026 -0700

    office: professional polish pass (design-agent driven) — Inter/mono type, solid command bar with stat pills, spark-bar workload legend, 3-row activity log, grouped controls, restrained bloom + warm/cool lighting + lane floor stripes; robust boot-curtain timer
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 office.html | 219 +++++++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 144 insertions(+), 75 deletions(-)

diff --git a/office.html b/office.html
index 9455c4f..aa1314e 100644
--- a/office.html
+++ b/office.html
@@ -4,77 +4,124 @@
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width,initial-scale=1">
 <title>Fleet Office — live ticket floor</title>
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@500;600;700&display=swap" rel="stylesheet">
 <style>
-  :root{ --bg:#07080d; --panel:#10131dcc; --line:#242a3a; --dim:#8a93a5; --blue:#6db3f2; --red:#ff6b7a; --green:#3ee6a0; --purple:#c9a4f2; --amber:#ffcf6b; }
+  :root{ --bg:#06070c; --panel:rgba(11,13,20,0.82); --solid:rgba(9,11,17,0.92); --line:rgba(255,255,255,0.09);
+         --ink:#eef1f7; --dim:#7f8798; --faint:#586074;
+         --blue:#5aa9f0; --red:#ff5f6d; --open:#aab4c6; --green:#34d9a0; --purple:#c9a4f2; --amber:#f5b74a; }
   *{box-sizing:border-box}
-  html,body{margin:0;height:100%;background:#07080d;color:#eef1f7;font:13px -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;overflow:hidden}
+  html,body{margin:0;height:100%;background:#06070c;color:var(--ink);
+    font:13px/1.4 'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
+    -webkit-font-smoothing:antialiased;overflow:hidden}
+  .mono{font-family:'JetBrains Mono',ui-monospace,monospace;font-variant-numeric:tabular-nums}
   #c{position:fixed;inset:0;display:block}
   .hud{position:fixed;pointer-events:none;z-index:10}
-  #top{top:0;left:0;right:0;padding:14px 18px;display:flex;gap:20px;align-items:center;
-       background:linear-gradient(#07080df2,#07080d00);letter-spacing:.05em}
-  #top b{color:#fff;font-size:15px;letter-spacing:.14em;text-shadow:0 0 18px #6db3f2aa}
-  #top .stat{color:var(--dim);font-size:12px}
-  #top .stat span{color:#fff;font-weight:700}
   .glass{background:var(--panel);border:1px solid var(--line);border-radius:12px;
-         backdrop-filter:blur(9px);-webkit-backdrop-filter:blur(9px);box-shadow:0 8px 30px #000a}
-  .legend{position:fixed;top:56px;left:18px;z-index:10;padding:11px 13px;line-height:2;pointer-events:auto}
-  .legend .row{display:flex;align-items:center;gap:9px}
-  .dot{width:11px;height:11px;border-radius:3px;display:inline-block;box-shadow:0 0 10px currentColor}
-  .legend small{color:var(--dim)}
-  #ticker{position:fixed;bottom:0;left:0;right:0;z-index:10;background:linear-gradient(#07080d00,#07080df2);
-          padding:16px 18px 14px;pointer-events:none}
-  #ticker .lbl{color:var(--purple);letter-spacing:.16em;font-size:10px;text-transform:uppercase;text-shadow:0 0 12px #c9a4f288}
-  #ticker .msg{color:#dfe5f0;margin-top:4px;max-width:74vw;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:13px}
-  #ticker .msg b{color:#fff}
-  .ctl{position:fixed;top:56px;right:18px;z-index:10;display:flex;flex-direction:column;gap:7px;pointer-events:auto}
-  .ctl button{background:var(--panel);border:1px solid var(--line);color:#cdd4e2;border-radius:10px;
-              padding:8px 12px;cursor:pointer;font:inherit;letter-spacing:.03em;backdrop-filter:blur(9px);transition:.15s}
-  .ctl button:hover{border-color:var(--blue);color:#fff;box-shadow:0 0 16px #6db3f244}
-  .ctl button.on{border-color:var(--blue);color:var(--blue);box-shadow:0 0 14px #6db3f233}
-  #b-ping{border-color:#c9a4f2;color:#c9a4f2}
-  #tip{position:fixed;z-index:11;max-width:360px;pointer-events:none;display:none;padding:10px 12px}
-  #tip .h{color:var(--blue);font-weight:700;margin-bottom:3px;letter-spacing:.04em}
-  #tip .s{color:var(--dim);font-size:11px;margin-top:2px}
-  #tip .t{color:#dfe5f0;margin-top:5px;font-size:12px;line-height:1.5}
-  #err{position:fixed;bottom:56px;left:18px;z-index:12;color:var(--red);display:none;text-shadow:0 0 10px #ff6b7a66}
-  #boot{position:fixed;inset:0;z-index:20;background:#07080d;display:flex;align-items:center;justify-content:center;
-        flex-direction:column;gap:14px;transition:opacity .8s;letter-spacing:.3em;color:#6db3f2}
-  #boot .r{width:120px;height:2px;background:#1a2030;overflow:hidden;border-radius:2px}
-  #boot .r i{display:block;height:100%;width:40%;background:linear-gradient(90deg,#6db3f2,#c9a4f2);
-             animation:sweep 1s infinite;box-shadow:0 0 12px #6db3f2}
-  @keyframes sweep{0%{transform:translateX(-100%)}100%{transform:translateX(320%)}}
+         backdrop-filter:blur(14px) saturate(1.2);-webkit-backdrop-filter:blur(14px) saturate(1.2);
+         box-shadow:0 10px 40px #000b, inset 0 1px 0 rgba(255,255,255,0.05)}
+  /* ── top command bar ── */
+  #top{top:0;left:0;right:0;height:46px;padding:0 18px;display:flex;gap:14px;align-items:center;
+       background:var(--solid);border-bottom:1px solid var(--line);backdrop-filter:blur(14px);pointer-events:auto}
+  #top .brand{color:#fff;font-size:13px;font-weight:800;letter-spacing:.22em}
+  #top .brand i{color:var(--blue);font-style:normal;margin-right:7px;text-shadow:0 0 14px var(--blue)}
+  .pills{display:flex;gap:7px;align-items:center;margin-left:6px}
+  .pill{display:flex;align-items:center;gap:6px;padding:4px 10px;border-radius:7px;
+        background:rgba(255,255,255,0.04);border:1px solid var(--line);font-size:11px;color:var(--dim);
+        text-transform:uppercase;letter-spacing:.08em}
+  .pill i.d{width:7px;height:7px;border-radius:2px;box-shadow:0 0 8px currentColor}
+  .pill b{font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:700;color:#fff;letter-spacing:0}
+  #top .age{margin-left:auto;font-size:10px;color:var(--faint);letter-spacing:.14em;text-transform:uppercase}
+  #top .age b{font-family:'JetBrains Mono',monospace;color:var(--dim);font-weight:600}
+  /* ── legend / workload ── */
+  .legend{position:fixed;top:60px;left:18px;z-index:10;padding:12px 14px;width:214px;pointer-events:auto}
+  .lhd{font-size:10px;letter-spacing:.18em;color:var(--faint);text-transform:uppercase;margin-bottom:9px}
+  .lrow{display:grid;grid-template-columns:9px 1fr auto;align-items:center;gap:9px;margin:7px 0}
+  .lrow .lc{width:9px;height:9px;border-radius:2px;background:var(--c);box-shadow:0 0 9px var(--c)}
+  .lrow .ln{font-size:12px;color:var(--ink);text-transform:capitalize}
+  .lrow .lv{font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:700;color:#fff}
+  .lbar{grid-column:1/4;height:3px;border-radius:2px;background:rgba(255,255,255,0.06);overflow:hidden;margin-top:-2px}
+  .lbar i{display:block;height:100%;width:0;background:var(--c);box-shadow:0 0 8px var(--c);transition:width .6s cubic-bezier(.4,0,.2,1)}
+  /* ── controls ── */
+  .ctl{position:fixed;top:60px;right:18px;z-index:10;display:flex;flex-direction:column;gap:6px;
+       width:150px;padding:11px;pointer-events:auto}
+  .csec{font-size:10px;letter-spacing:.18em;color:var(--faint);text-transform:uppercase;margin:4px 2px 2px}
+  .csec:first-child{margin-top:0}
+  .ctl button{display:flex;align-items:center;gap:8px;background:rgba(255,255,255,0.03);border:1px solid var(--line);
+    color:#cdd4e2;border-radius:8px;padding:8px 11px;cursor:pointer;font:inherit;font-size:12px;
+    letter-spacing:.02em;transition:.14s;text-align:left}
+  .ctl button::before{content:'';width:6px;height:6px;border-radius:50%;background:var(--faint);flex:0 0 auto;transition:.14s}
+  .ctl button:hover{border-color:rgba(255,255,255,0.22);color:#fff;background:rgba(255,255,255,0.06)}
+  .ctl button.on{color:#fff;border-color:var(--blue);background:rgba(90,169,240,0.12)}
+  .ctl button.on::before{background:var(--blue);box-shadow:0 0 9px var(--blue)}
+  #b-ping{color:var(--purple);border-color:rgba(201,164,242,0.4)}
+  #b-ping::before{background:var(--purple);box-shadow:0 0 9px var(--purple)}
+  #b-reset::before,#b-ping::before{display:none}
+  #b-reset,#b-ping{justify-content:center;margin-top:3px}
+  /* ── activity log ── */
+  #ticker{bottom:0;left:0;right:0;padding:14px 18px 15px;
+          background:linear-gradient(#06070c00,#06070cf5 60%);pointer-events:none}
+  #ticker .lbl{color:var(--faint);letter-spacing:.2em;font-size:10px;text-transform:uppercase;margin-bottom:7px}
+  #log{display:flex;flex-direction:column;gap:3px;max-width:820px}
+  .lgrow{display:grid;grid-template-columns:44px 190px 78px 1fr;gap:12px;align-items:baseline;
+         font-size:12px;white-space:nowrap;overflow:hidden}
+  .lgrow .t{font-family:'JetBrains Mono',monospace;color:var(--faint);font-size:11px}
+  .lgrow .ag{color:var(--blue);font-weight:600;overflow:hidden;text-overflow:ellipsis}
+  .lgrow .tk{font-family:'JetBrains Mono',monospace;color:var(--purple);font-size:11px}
+  .lgrow .mx{color:#c2c9d6;overflow:hidden;text-overflow:ellipsis}
+  .lgrow.muted{color:var(--faint);grid-template-columns:1fr}
+  .lgrow.fresh .mx{color:#fff}
+  /* ── tooltip ── */
+  #tip{position:fixed;z-index:11;max-width:360px;pointer-events:none;display:none;padding:11px 13px;border-left:3px solid var(--blue)}
+  #tip .h{color:var(--blue);font-weight:700;margin-bottom:3px;letter-spacing:.04em;font-family:'JetBrains Mono',monospace;font-size:12px}
+  #tip .s{color:var(--dim);font-size:11px;margin-top:3px}
+  #tip .t{color:#dfe5f0;margin-top:6px;font-size:12px;line-height:1.5}
+  #err{position:fixed;bottom:78px;left:18px;z-index:12;color:var(--red);display:none;font-size:12px}
+  /* ── boot curtain ── */
+  #boot{position:fixed;inset:0;z-index:20;background:#06070c;display:flex;align-items:center;justify-content:center;
+        flex-direction:column;gap:16px;transition:opacity .8s;letter-spacing:.34em;color:var(--blue);font-size:12px;font-weight:600}
+  #boot .r{width:140px;height:2px;background:rgba(255,255,255,0.08);overflow:hidden;border-radius:2px}
+  #boot .r i{display:block;height:100%;width:38%;background:linear-gradient(90deg,var(--blue),var(--purple));
+             animation:sweep 1s infinite;box-shadow:0 0 14px var(--blue)}
+  @keyframes sweep{0%{transform:translateX(-100%)}100%{transform:translateX(360%)}}
 </style>
 </head>
 <body>
 <canvas id="c"></canvas>
-<div id="boot"><div style="font-size:13px">ENTERING FLEET OFFICE</div><div class="r"><i></i></div></div>
+<div id="boot"><div>ENTERING FLEET OFFICE</div><div class="r"><i></i></div></div>
 <div class="hud" id="top">
-  <b>◆ FLEET OFFICE</b>
-  <span class="stat">rooms <span id="s-rooms">0</span></span>
-  <span class="stat">agents <span id="s-agents">0</span></span>
-  <span class="stat">doing <span id="s-doing" style="color:var(--blue)">0</span></span>
-  <span class="stat">blocked <span id="s-blocked" style="color:var(--red)">0</span></span>
-  <span class="stat">DMs <span id="s-dms" style="color:var(--purple)">0</span></span>
-  <span class="stat" style="margin-left:auto">↻ <span id="s-age">–</span></span>
+  <span class="brand"><i>◆</i>FLEET OFFICE</span>
+  <div class="pills">
+    <span class="pill"><i class="d" style="background:var(--open);color:var(--open)"></i>rooms <b id="s-rooms">0</b></span>
+    <span class="pill"><i class="d" style="background:var(--blue);color:var(--blue)"></i>doing <b id="s-doing">0</b></span>
+    <span class="pill"><i class="d" style="background:var(--red);color:var(--red)"></i>blocked <b id="s-blocked">0</b></span>
+    <span class="pill"><i class="d" style="background:var(--open);color:var(--open)"></i>open <b id="s-open">0</b></span>
+    <span class="pill"><i class="d" style="background:var(--green);color:var(--green)"></i>done <b id="s-done">0</b></span>
+    <span class="pill"><i class="d" style="background:var(--purple);color:var(--purple)"></i>agents <b id="s-agents">0</b></span>
+    <span class="pill"><i class="d" style="background:var(--purple);color:var(--purple)"></i>DMs <b id="s-dms">0</b></span>
+  </div>
+  <span class="age">SYNC <b id="s-age">–</b></span>
 </div>
 <div class="legend glass">
-  <div class="row" style="color:#6db3f2"><span class="dot"></span> <span style="color:#eef1f7">doing</span> <small>active work</small></div>
-  <div class="row" style="color:#ff6b7a"><span class="dot"></span> <span style="color:#eef1f7">blocked</span></div>
-  <div class="row" style="color:#8a93a5"><span class="dot"></span> <span style="color:#eef1f7">open</span></div>
-  <div class="row" style="color:#3ee6a0"><span class="dot"></span> <span style="color:#eef1f7">done</span> <small>(recent)</small></div>
-  <div class="row" style="color:#c9a4f2"><span class="dot"></span> <span style="color:#eef1f7">DM in flight</span></div>
+  <div class="lhd">Workload</div>
+  <div class="lrow" style="--c:var(--blue)"><span class="lc"></span><span class="ln">doing</span><span class="lv" id="lg-doing">0</span><span class="lbar"><i id="bar-doing"></i></span></div>
+  <div class="lrow" style="--c:var(--red)"><span class="lc"></span><span class="ln">blocked</span><span class="lv" id="lg-blocked">0</span><span class="lbar"><i id="bar-blocked"></i></span></div>
+  <div class="lrow" style="--c:var(--open)"><span class="lc"></span><span class="ln">open</span><span class="lv" id="lg-open">0</span><span class="lbar"><i id="bar-open"></i></span></div>
+  <div class="lrow" style="--c:var(--green)"><span class="lc"></span><span class="ln">done</span><span class="lv" id="lg-done">0</span><span class="lbar"><i id="bar-done"></i></span></div>
 </div>
-<div class="ctl">
-  <button id="b-orbit" class="on">Orbit: on</button>
-  <button id="b-labels" class="on">Labels: on</button>
-  <button id="b-done" class="on">Done rooms: on</button>
-  <button id="b-follow">Follow DMs: off</button>
+<div class="ctl glass">
+  <div class="csec">View</div>
+  <button id="b-orbit" class="on">Orbit</button>
+  <button id="b-follow">Follow DMs</button>
+  <div class="csec">Scene</div>
+  <button id="b-labels" class="on">Labels</button>
+  <button id="b-done" class="on">Done rooms</button>
   <button id="b-ping" title="Preview the DM packet animation (visual only — writes nothing)">Ping ✦</button>
   <button id="b-reset">Reset camera</button>
 </div>
 <div id="tip" class="glass"></div>
 <div id="err"></div>
-<div class="hud" id="ticker"><div class="lbl">latest activity</div><div class="msg" id="ticker-msg">connecting to ticket board…</div></div>
+<div class="hud" id="ticker"><div class="lbl">Activity stream</div><div id="log"><div class="lgrow muted">connecting to ticket board…</div></div></div>
 
 <script type="importmap">
 { "imports": {
@@ -156,17 +203,17 @@ const grid=new THREE.GridHelper(1600,160,0x2a3550,0x141a28);
 grid.material.transparent=true; grid.material.opacity=0.35; grid.position.y=0.02; scene.add(grid);
 
 // ───────────────────────── lights ─────────────────────────
-scene.add(new THREE.HemisphereLight(0x9fb8ff,0x05060a,0.7));
-const key=new THREE.DirectionalLight(0xffffff,1.15); key.position.set(70,170,60);
-key.castShadow=true; key.shadow.mapSize.set(2048,2048);
+scene.add(new THREE.HemisphereLight(0x9fb8ff,0x05060a,0.55));
+const key=new THREE.DirectionalLight(0xfff2e0,1.05); key.position.set(70,170,60);  // warm key
+key.castShadow=true; key.shadow.mapSize.set(2048,2048); key.shadow.bias=-0.0004;
 Object.assign(key.shadow.camera,{ left:-300,right:300,top:300,bottom:-300,near:20,far:600 });
 scene.add(key);
-scene.add(new THREE.DirectionalLight(0x5b7bff,0.25).translateX(-80));
+const fill=new THREE.DirectionalLight(0x4d6bff,0.5); fill.position.set(-90,60,-40); scene.add(fill); // cool fill
 
 // ───────────────────────── postprocessing ─────────────────────────
 const composer=new EffectComposer(renderer);
 composer.addPass(new RenderPass(scene,camera));
-const bloom=new UnrealBloomPass(new THREE.Vector2(innerWidth,innerHeight), 0.85, 0.7, 0.15);
+const bloom=new UnrealBloomPass(new THREE.Vector2(innerWidth,innerHeight), 0.55, 0.6, 0.22);
 composer.addPass(bloom);
 composer.addPass(new OutputPass());
 
@@ -199,7 +246,7 @@ function buildRoom(t){
   // floor slab
   const floor=new THREE.Mesh(new THREE.BoxGeometry(ROOM,0.5,ROOM),
     new THREE.MeshStandardMaterial({ color:col, roughness:0.5, metalness:0.4,
-      emissive:col, emissiveIntensity:t.status==='doing'?0.6:(t.status==='blocked'?0.5:0.18) }));
+      emissive:col, emissiveIntensity:t.status==='doing'?0.42:(t.status==='blocked'?0.34:0.12) }));
   floor.position.y=0.25; floor.receiveShadow=true; grp.add(floor);
   // glowing perimeter rim (additive line loop on the top edge)
   const rimPts=[[-ROOM/2,-ROOM/2],[ROOM/2,-ROOM/2],[ROOM/2,ROOM/2],[-ROOM/2,ROOM/2],[-ROOM/2,-ROOM/2]]
@@ -311,14 +358,25 @@ function layout(tk){
   });
   for(const [id,room] of roomByTicket) if(!want.has(id)) room.visible=false;
   if(!laneSigns){ laneSigns=new THREE.Group(); scene.add(laneSigns);
-    LANES.forEach((lane,li)=>{ const s=makeLabel([{t:lane.toUpperCase()}],{ w:260, scale:1.5, glow:hex(COLOR[lane]) });
-      s.position.set(li*LANE_W-LANE_W*1.5+30,12,-PITCH); laneSigns.add(s); }); }
+    LANES.forEach((lane,li)=>{ const x=li*LANE_W-LANE_W*1.5+30;
+      const s=makeLabel([{t:lane.toUpperCase()}],{ w:260, scale:1.5, glow:hex(COLOR[lane]) });
+      s.position.set(x,12,-PITCH); laneSigns.add(s);
+      // continuous colored floor stripe = lane identity readable from any angle
+      const stripe=new THREE.Mesh(new THREE.PlaneGeometry(ROOM+4, PITCH*15),
+        new THREE.MeshBasicMaterial({ color:COLOR[lane], transparent:true, opacity:0.07,
+          blending:THREE.AdditiveBlending, depthWrite:false }));
+      stripe.rotation.x=-Math.PI/2; stripe.position.set(x,0.04,PITCH*6.2); scene.add(stripe);
+      const edge=new THREE.Mesh(new THREE.PlaneGeometry(0.3, PITCH*15),
+        new THREE.MeshBasicMaterial({ color:COLOR[lane], transparent:true, opacity:0.5,
+          blending:THREE.AdditiveBlending, depthWrite:false }));
+      edge.rotation.x=-Math.PI/2; edge.position.set(x-(ROOM+4)/2,0.05,PITCH*6.2); scene.add(edge);
+    }); }
 }
 function updateRoom(room,t){ const u=room.userData, prev=u.ticket; u.ticket=t;
   const col=COLOR[t.status]??COLOR.open; u.col=col;
   u.floor.material.color.setHex(col); u.floor.material.emissive.setHex(col);
   u.rim.material.color.setHex(col); u.trim.material.color.setHex(col); u.mon.material.color.setHex(col); u.mon.material.emissive.setHex(col);
-  u.baseEmis=t.status==='doing'?0.6:(t.status==='blocked'?0.5:0.18);
+  u.baseEmis=t.status==='doing'?0.42:(t.status==='blocked'?0.34:0.12);
   if(prev.status!==t.status||prev.assignee!==t.assignee){ room.remove(u.label);
     u.label.material.map.dispose(); u.label.material.dispose();
     u.label=roomLabel(t); u.label.position.set(0,6.6,0); room.add(u.label); }
@@ -360,18 +418,29 @@ function apply(tk,msgs){
     if(ts>=+new Date((latest?.ts)||0)) latest={agent:a.agent,ts:a.ts,text:a.text,id:t.id};
     if(ts>maxTs) maxTs=ts; }
   lastActionTs=maxTs;
-  if(latest) document.getElementById('ticker-msg').innerHTML=
-    `<b>@${esc(latest.agent)}</b> · ${esc(latest.id.replace(/^(TK-\d+).*/,'$1'))} — ${esc((latest.text||'').slice(0,190))}`;
+  // activity log — 3 most-recent actions across all tickets, newest first
+  const evs=[];
+  for(const t of tk) for(const a of t.actions||[]) evs.push({ ts:+new Date(a.ts), agent:a.agent, id:t.id, text:a.text });
+  evs.sort((x,y)=>y.ts-x.ts);
+  const hhmm=ms=>{ const d=new Date(ms); return String(d.getHours()).padStart(2,'0')+':'+String(d.getMinutes()).padStart(2,'0'); };
+  const logEl=document.getElementById('log');
+  logEl.innerHTML=evs.length ? evs.slice(0,3).map((e,i)=>
+    `<div class="lgrow${i===0?' fresh':''}"><span class="t">${hhmm(e.ts)}</span><span class="ag">@${esc(e.agent||'—')}</span>`+
+    `<span class="tk">${esc(e.id.replace(/^(TK-\d+).*/,'$1'))}</span><span class="mx">${esc((e.text||'').slice(0,200))}</span></div>`).join('')
+    : '<div class="lgrow muted">no activity yet</div>';
   if(seenMsgMids===null) seenMsgMids=new Set(msgs.map(m=>m.mid));
   else for(const m of msgs){ if(seenMsgMids.has(m.mid)) continue; seenMsgMids.add(m.mid);
     const from=avatarByName.get(m.from); if(!from) continue;
     if(m.to==='all'||m.to==='*'||m.to==='everyone'){ for(const [nm,av] of avatarByName) if(nm!==m.from) spawnPacket(worldPos(from),worldPos(av),true); }
     else { const to=avatarByName.get(m.to); if(to) spawnPacket(worldPos(from),worldPos(to),false); } }
   const cnt={doing:0,blocked:0,open:0,done:0}; for(const t of tk) if(cnt[t.status]!=null) cnt[t.status]++;
-  document.getElementById('s-rooms').textContent=(cnt.doing+cnt.blocked+cnt.open+(showDone?Math.min(cnt.done,DONE_CAP):0));
-  document.getElementById('s-agents').textContent=avatarByName.size;
-  document.getElementById('s-doing').textContent=cnt.doing; document.getElementById('s-blocked').textContent=cnt.blocked;
-  document.getElementById('s-dms').textContent=msgs.length;
+  const totalActive=(cnt.doing+cnt.blocked+cnt.open+cnt.done)||1;
+  const set=(id,v)=>{ const el=document.getElementById(id); if(el) el.textContent=v; };
+  set('s-rooms', cnt.doing+cnt.blocked+cnt.open+(showDone?Math.min(cnt.done,DONE_CAP):0));
+  set('s-agents', avatarByName.size); set('s-doing',cnt.doing); set('s-blocked',cnt.blocked);
+  set('s-open',cnt.open); set('s-done',cnt.done); set('s-dms',msgs.length);
+  for(const k of ['doing','blocked','open','done']){ set('lg-'+k,cnt[k]);
+    const bar=document.getElementById('bar-'+k); if(bar) bar.style.width=Math.round(cnt[k]/totalActive*100)+'%'; }
 }
 
 // ───────────────────────── hover tooltip ─────────────────────────
@@ -389,10 +458,10 @@ function updateTip(){ const tip=document.getElementById('tip'); if(!hover){ tip.
 
 // ───────────────────────── buttons ─────────────────────────
 const B=id=>document.getElementById(id);
-B('b-orbit').onclick=e=>{ controls.autoRotate=!controls.autoRotate; e.target.className=controls.autoRotate?'on':''; e.target.textContent='Orbit: '+(controls.autoRotate?'on':'off'); };
-B('b-labels').onclick=e=>{ showLabels=!showLabels; e.target.className=showLabels?'on':''; e.target.textContent='Labels: '+(showLabels?'on':'off'); };
-B('b-done').onclick=e=>{ showDone=!showDone; e.target.className=showDone?'on':''; e.target.textContent='Done rooms: '+(showDone?'on':'off'); poll(); };
-B('b-follow').onclick=e=>{ followDMs=!followDMs; e.target.className=followDMs?'on':''; e.target.textContent='Follow DMs: '+(followDMs?'on':'off'); };
+B('b-orbit').onclick=e=>{ controls.autoRotate=!controls.autoRotate; e.target.classList.toggle('on',controls.autoRotate); };
+B('b-labels').onclick=e=>{ showLabels=!showLabels; e.target.classList.toggle('on',showLabels); };
+B('b-done').onclick=e=>{ showDone=!showDone; e.target.classList.toggle('on',showDone); poll(); };
+B('b-follow').onclick=e=>{ followDMs=!followDMs; e.target.classList.toggle('on',followDMs); };
 B('b-reset').onclick=()=>{ boot.t=1; camera.position.copy(HOME.pos); controls.target.copy(HOME.tgt); hideBoot(); };
 let bootHidden=false;
 function hideBoot(){ if(bootHidden) return; bootHidden=true; const b=document.getElementById('boot');
@@ -427,9 +496,9 @@ function tick(){
   // rooms
   for(const room of roomByTicket.values()){ if(!room.visible) continue; const u=room.userData;
     let em=u.baseEmis;
-    if(u.ticket.status==='doing') em=0.5+0.28*Math.sin(el*3+room.position.z*0.2);
-    else if(u.ticket.status==='blocked') em=0.4+0.25*Math.sin(el*6);
-    if(u.flash>0){ em+=u.flash*1.3; u.flash-=dt*1.1; }
+    if(u.ticket.status==='doing') em=0.4+0.16*Math.sin(el*2.4+room.position.z*0.2);
+    else if(u.ticket.status==='blocked') em=0.32+0.14*Math.sin(el*5);
+    if(u.flash>0){ em+=u.flash*1.0; u.flash-=dt*1.1; }
     u.floor.material.emissiveIntensity=em; u.rim.material.opacity=0.7+0.3*Math.sin(el*2+room.position.x);
     u.label.visible=showLabels; }
   if(laneSigns) laneSigns.children.forEach((s,i)=>{ s.visible=showLabels; s.position.y=12+Math.sin(el*1.2+i)*0.5; });

← c4b15ed auto-save: 2026-07-27T07:48:56 (1 files) — office.html  ·  back to Ticket System  ·  marketing: Four Horsemen pass — branded Fleet Office hero ca 8db70dc →