coworking-agents 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/achievements.png +0 -0
- package/docs/building.png +0 -0
- package/docs/camera.png +0 -0
- package/docs/christmas.png +0 -0
- package/docs/customise.png +0 -0
- package/docs/game-room.png +0 -0
- package/docs/halloween.png +0 -0
- package/docs/house-wall.png +0 -0
- package/docs/office-day.png +0 -0
- package/docs/office-night.png +0 -0
- package/docs/panel.png +0 -0
- package/docs/paper.png +0 -0
- package/docs/servers.png +0 -0
- package/docs/talk.png +0 -0
- package/docs/top-bar.png +0 -0
- package/docs/trophy-room.png +0 -0
- package/docs/usage.png +0 -0
- package/docs/work-site.png +0 -0
- package/package.json +1 -1
- package/public/js/fun2.js +21 -12
- package/public/js/views.js +109 -58
package/docs/achievements.png
CHANGED
|
Binary file
|
package/docs/building.png
CHANGED
|
Binary file
|
package/docs/camera.png
CHANGED
|
Binary file
|
package/docs/christmas.png
CHANGED
|
Binary file
|
package/docs/customise.png
CHANGED
|
Binary file
|
package/docs/game-room.png
CHANGED
|
Binary file
|
package/docs/halloween.png
CHANGED
|
Binary file
|
package/docs/house-wall.png
CHANGED
|
Binary file
|
package/docs/office-day.png
CHANGED
|
Binary file
|
package/docs/office-night.png
CHANGED
|
Binary file
|
package/docs/panel.png
CHANGED
|
Binary file
|
package/docs/paper.png
CHANGED
|
Binary file
|
package/docs/servers.png
CHANGED
|
Binary file
|
package/docs/talk.png
CHANGED
|
Binary file
|
package/docs/top-bar.png
CHANGED
|
Binary file
|
package/docs/trophy-room.png
CHANGED
|
Binary file
|
package/docs/usage.png
CHANGED
|
Binary file
|
package/docs/work-site.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coworking-agents",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A pixel-art coworking office for your AI coding agents (Claude Code, Codex, …): see who's working, who needs you, and who's editing the same checkout.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"coworking-agents": "bin/coworking-agents.js"
|
package/public/js/fun2.js
CHANGED
|
@@ -91,21 +91,30 @@ function openArcade() {
|
|
|
91
91
|
function drawArcadeTitle() {
|
|
92
92
|
const c = arcadeEl.querySelector('.arcade-cv'); if (!c) return;
|
|
93
93
|
const g = c.getContext('2d'); arcadeBg(g, performance.now());
|
|
94
|
-
g
|
|
95
|
-
g
|
|
94
|
+
gText(g, AW / 2, 46, 'BUG CATCHER', '#ffd84d', 3, true);
|
|
95
|
+
gText(g, AW / 2, 78, lang === 'pt' ? 'ESPACO OU ENTER' : 'SPACE OR ENTER', '#c0cbdc', 1, true);
|
|
96
96
|
drawBug(g, AW / 2 - 30, 100, false, 0); drawBug(g, AW / 2 - 4, 104, true, 1); drawBug(g, AW / 2 + 22, 100, false, 0);
|
|
97
97
|
}
|
|
98
|
+
// crisp text: the office's pixel font, scaled by whole pixels
|
|
99
|
+
function gText(g, x, y, str, c, k = 1, center = false) {
|
|
100
|
+
const txt = String(str).toUpperCase(); if (center) x -= Math.round(txt.length * 4 * k / 2);
|
|
101
|
+
g.fillStyle = c; let cx = Math.round(x);
|
|
102
|
+
for (const ch of txt) { const gl = PIXFONT[ch]; if (gl) for (let i = 0; i < 15; i++) if (gl[i] === '1') g.fillRect(cx + (i % 3) * k, Math.round(y) + ((i / 3) | 0) * k, k, k); cx += 4 * k; }
|
|
103
|
+
}
|
|
98
104
|
function arcadeBg(g, now) {
|
|
99
105
|
g.fillStyle = '#141826'; g.fillRect(0, 0, AW, AH);
|
|
100
|
-
g.fillStyle = '#1c2236'; for (let y = 0; y < AH; y += 8) { const sh = (y * 13) % 70; g.fillRect(6 + sh % 20, (y + now / 60) % AH, 30 + sh, 2); } // code scrolling behind
|
|
106
|
+
g.fillStyle = '#1c2236'; for (let y = 0; y < AH; y += 8) { const sh = (y * 13) % 70; g.fillRect(6 + sh % 20, Math.round((y + now / 60) % AH), 30 + sh, 2); } // code scrolling behind
|
|
101
107
|
g.fillStyle = '#2a2f45'; g.fillRect(0, AH - 8, AW, 8);
|
|
102
108
|
}
|
|
103
109
|
function drawBug(g, x, y, gold, f) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
g.fillStyle =
|
|
107
|
-
g.fillStyle = '#
|
|
108
|
-
|
|
110
|
+
x = Math.round(x); y = Math.round(y);
|
|
111
|
+
const body = gold ? '#ffd84d' : '#63c74d', dark = gold ? '#b07d2a' : '#2f7a3b', leg = '#c0cbdc';
|
|
112
|
+
g.fillStyle = leg; for (const [dx, dy] of [[-3, 3], [-3, 6], [11, 3], [11, 6]]) g.fillRect(x + dx + (f ? (dx < 0 ? -1 : 1) : 0), y + dy + (f ? 1 : 0), 4, 1); // legs wiggle
|
|
113
|
+
g.fillStyle = '#0b0a0e'; g.fillRect(x - 1, y - 1, 13, 11);
|
|
114
|
+
g.fillStyle = dark; g.fillRect(x, y, 11, 9); g.fillStyle = body; g.fillRect(x + 1, y + 1, 4, 7); g.fillRect(x + 6, y + 1, 4, 7); // shell, split down the middle
|
|
115
|
+
g.fillStyle = '#ffffff'; g.fillRect(x + 2, y + 2, 1, 2); g.fillRect(x + 7, y + 2, 1, 2);
|
|
116
|
+
g.fillStyle = '#3a4058'; g.fillRect(x + 2, y - 4, 7, 4); g.fillStyle = '#ffffff'; g.fillRect(x + 3, y - 3, 1, 1); g.fillRect(x + 7, y - 3, 1, 1); // head and eyes
|
|
117
|
+
g.fillStyle = leg; g.fillRect(x + 2, y - 7, 1, 3); g.fillRect(x + 8, y - 7, 1, 3); // antennae
|
|
109
118
|
}
|
|
110
119
|
function startArcade() {
|
|
111
120
|
const cv2 = arcadeEl.querySelector('.arcade-cv'); if (!cv2) return;
|
|
@@ -129,7 +138,7 @@ function tickArcade(now) {
|
|
|
129
138
|
} else if (b.y > AH - 8) { b.miss = true; G.lives--; G.combo = 0; }
|
|
130
139
|
}
|
|
131
140
|
G.bugs = G.bugs.filter(b => !b.hit && !b.miss);
|
|
132
|
-
for (const s of G.sparks) { s.x += s.vx * dt; s.y += s.vy * dt; s.vy += 80 * dt; s.life -= dt;
|
|
141
|
+
for (const s of G.sparks) { s.x += s.vx * dt; s.y += s.vy * dt; s.vy += 80 * dt; s.life -= dt; if (s.txt) gText(g, s.x, s.y - 6, s.txt, s.c); else { g.fillStyle = s.c; g.fillRect(Math.round(s.x), Math.round(s.y), 2, 2); } }
|
|
133
142
|
G.sparks = G.sparks.filter(s => s.life > 0);
|
|
134
143
|
// the laptop paddle
|
|
135
144
|
const px = Math.round(G.x), py = AH - 18;
|
|
@@ -137,14 +146,14 @@ function tickArcade(now) {
|
|
|
137
146
|
g.fillStyle = '#8b9bb4'; g.fillRect(px, py + 2, 28, 4); g.fillStyle = '#d97757'; g.fillRect(px + 11, py - 4, 6, 2);
|
|
138
147
|
// HUD: hearts, score, combo
|
|
139
148
|
for (let i = 0; i < 3; i++) { g.fillStyle = i < G.lives ? '#e43b44' : '#3a3448'; g.fillRect(6 + i * 10, 6, 3, 2); g.fillRect(10 + i * 10, 6, 3, 2); g.fillRect(6 + i * 10, 8, 7, 2); g.fillRect(7 + i * 10, 10, 5, 1); g.fillRect(8 + i * 10, 11, 3, 1); }
|
|
140
|
-
g
|
|
141
|
-
if (G.combo >= 5)
|
|
149
|
+
gText(g, AW - 6 - String(G.score).length * 8, 5, G.score, '#ffffff', 2);
|
|
150
|
+
if (G.combo >= 5) gText(g, AW / 2, 6, 'COMBO X2', '#ff6ec7', 1, true);
|
|
142
151
|
const sc = arcadeEl.querySelector('#arcade-score'); if (sc) sc.textContent = `${T.arcade.score}: ${G.score}`;
|
|
143
152
|
if (G.lives <= 0) {
|
|
144
153
|
const best = Number(store.get('arcadeBest', 0)) || 0;
|
|
145
154
|
if (G.score > best) { store.set('arcadeBest', G.score); toast(`<b>${esc(T.arcade.record)}</b>${G.score}`, 'ok'); const be = arcadeEl.querySelector('#arcade-best'); if (be) be.textContent = G.score; }
|
|
146
155
|
achRecord.arcadeBest = Math.max(achRecord.arcadeBest || 0, G.score); achBump('arcadeGames');
|
|
147
|
-
g.fillStyle = 'rgba(0,0,0,.
|
|
156
|
+
g.fillStyle = 'rgba(0,0,0,.7)'; g.fillRect(0, 56, AW, 46); gText(g, AW / 2, 64, 'GAME OVER', '#ffd84d', 2, true); gText(g, AW / 2, 84, `${G.score}`, '#ffffff', 2, true);
|
|
148
157
|
game2 = null; return;
|
|
149
158
|
}
|
|
150
159
|
requestAnimationFrame(tickArcade);
|
package/public/js/views.js
CHANGED
|
@@ -2,71 +2,122 @@
|
|
|
2
2
|
// ---------------- more places: the terrace, the server room, the elevator panel; weather indoors; photo frames ----------------
|
|
3
3
|
let viewHits = [];
|
|
4
4
|
|
|
5
|
-
// ---- the
|
|
5
|
+
// ---- the rooftop: a garden terrace over the city; agents whose turn ended relax here ----
|
|
6
|
+
function skyStops(phase) {
|
|
7
|
+
return phase === 'night' ? ['#0b0f24', '#18204a', '#2a3466'] : phase === 'dawn' ? ['#3a3a7a', '#f77622', '#ffd27a'] : phase === 'dusk' ? ['#2a2a6a', '#b55088', '#f7a23a'] : ['#3a9ad9', '#7fc8f0', '#c8ecff'];
|
|
8
|
+
}
|
|
6
9
|
function drawTerrace(t) {
|
|
7
|
-
const Wt = TROPHY_W, Ht = TROPHY_H, hits = [];
|
|
8
|
-
const hr = new Date().getHours() + new Date().getMinutes() / 60, sky = Art.skyFor(styleHour(qs.get('hour') ? Number(qs.get('hour')) : hr));
|
|
9
|
-
// sky gradient
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
for (let
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
10
|
+
const Wt = TROPHY_W, Ht = TROPHY_H, hits = [], HZ = 170;
|
|
11
|
+
const hr = new Date().getHours() + new Date().getMinutes() / 60, sky = Art.skyFor(styleHour(qs.get('hour') ? Number(qs.get('hour')) : hr)), night = sky.phase === 'night', day = sky.phase === 'day';
|
|
12
|
+
// sky: three-stop gradient, sun or moon, stars, clouds, a plane, birds
|
|
13
|
+
const st = skyStops(sky.phase);
|
|
14
|
+
for (let y = 0; y < HZ; y++) { const k = y / HZ, c = k < .55 ? st[0] : k < .85 ? st[1] : st[2]; r(0, y, Wt, 1, c); }
|
|
15
|
+
for (let y = 0; y < HZ; y += 2) { const k = y / HZ; ctx.fillStyle = `rgba(255,255,255,${(k * .06).toFixed(3)})`; ctx.fillRect(0, y, Wt, 2); }
|
|
16
|
+
if (night) { for (let i = 0; i < 90; i++) { const h = hash('ts' + i); r(h % Wt, (h >>> 9) % 130, 1, 1, ((t / 700 + i) | 0) % 6 ? '#ffffff' : '#6b7a9c'); }
|
|
17
|
+
const mx = Wt - 110, my = 34; ctx.save(); ctx.globalCompositeOperation = 'lighter'; const g = ctx.createRadialGradient(mx, my, 0, mx, my, 40); g.addColorStop(0, 'rgba(255,250,220,.25)'); g.addColorStop(1, 'rgba(255,250,220,0)'); ctx.fillStyle = g; ctx.fillRect(mx - 40, my - 40, 80, 80); ctx.restore();
|
|
18
|
+
r(mx - 8, my - 8, 16, 16, '#f4ecd8'); r(mx - 10, my - 5, 20, 10, '#f4ecd8'); r(mx - 3, my - 4, 3, 3, '#d8cfb8'); r(mx + 3, my + 2, 2, 2, '#d8cfb8'); }
|
|
19
|
+
else if (day) { const sx = Wt - 120, sy = 40; ctx.save(); ctx.globalCompositeOperation = 'lighter'; const g = ctx.createRadialGradient(sx, sy, 0, sx, sy, 60); g.addColorStop(0, 'rgba(255,240,180,.5)'); g.addColorStop(1, 'rgba(255,240,180,0)'); ctx.fillStyle = g; ctx.fillRect(sx - 60, sy - 60, 120, 120); ctx.restore(); r(sx - 10, sy - 10, 20, 20, '#fff2b0'); r(sx - 12, sy - 6, 24, 12, '#fff2b0'); }
|
|
20
|
+
else { const sx = sky.phase === 'dawn' ? 120 : Wt - 140; r(sx - 18, HZ - 26, 36, 16, '#ffd27a'); r(sx - 14, HZ - 32, 28, 6, '#ffd27a'); r(sx - 8, HZ - 36, 16, 4, '#ffd27a'); }
|
|
21
|
+
if (!night) for (let i = 0; i < 4; i++) { const cx = ((t / (90 + i * 30) + i * 180) % (Wt + 120)) - 60, cy = 20 + i * 22, c = day ? '#ffffff' : '#f4c0d0'; r(cx, cy, 40, 8, c); r(cx + 8, cy - 5, 22, 6, c); r(cx + 26, cy - 3, 12, 4, c); ctx.fillStyle = 'rgba(0,0,0,.06)'; ctx.fillRect(cx, cy + 6, 40, 2); }
|
|
22
|
+
{ const px = ((t / 25) % (Wt + 80)) - 40, py = 52; r(px, py, 10, 2, night ? '#8b9bb4' : '#ffffff'); r(px + 3, py - 2, 3, 6, night ? '#8b9bb4' : '#ffffff'); if (((t / 400) | 0) % 2) r(px + 10, py, 1, 1, '#e43b44'); }
|
|
23
|
+
if (day) for (let i = 0; i < 3; i++) { const bx = ((t / 18 + i * 40) % (Wt + 40)) - 20, by = 70 + i * 6 + Math.round(Math.sin(t / 300 + i) * 2), w = ((t / 150 + i) | 0) % 2; r(bx, by, 1, 1, '#2a2a3a'); r(bx - 2, by - w, 2, 1, '#2a2a3a'); r(bx + 1, by - w, 2, 1, '#2a2a3a'); }
|
|
24
|
+
// the city in three layers: far (faded), middle, near (detailed: windows, water tanks, antennas)
|
|
25
|
+
const layer = (seed, base, minH, varH, color, win, step, detail) => {
|
|
26
|
+
for (let x = -6; x < Wt; x += step) {
|
|
27
|
+
const h = minH + hash(seed + x) % varH, w = step - 1 - hash(seed + 'w' + x) % 3, top = base - h;
|
|
28
|
+
r(x, top, w, h, color);
|
|
29
|
+
if (detail) { if (hash(seed + 'a' + x) % 4 === 0) { r(x + w / 2, top - 10, 1, 10, color); if (((t / 600 + x) | 0) % 2) r(x + w / 2, top - 11, 1, 1, '#ff4d57'); } if (hash(seed + 't' + x) % 5 === 0) { r(x + 3, top - 6, 7, 6, color); r(x + 4, top - 8, 5, 2, color); } }
|
|
30
|
+
if (win) for (let yy = top + 3; yy < base - 2; yy += 4) for (let xx = 2; xx < w - 2; xx += 3) { const lit = night ? hash(seed + x + ':' + yy + ':' + xx + ((t / 12000) | 0)) % 3 === 0 : day ? hash(seed + x + ':' + yy + xx) % 9 === 0 : hash(seed + x + yy + xx) % 4 === 0; if (lit) r(x + xx, yy, 1, 2, night ? '#feae34' : day ? '#bfe0ff' : '#ffd27a'); }
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
layer('far', HZ, 30, 60, night ? '#1a2045' : day ? '#a9c4dc' : '#7a4a6a', false, 11, false);
|
|
34
|
+
layer('mid', HZ, 20, 70, night ? '#141a38' : day ? '#8aa4bf' : '#5a3a5a', true, 15, true);
|
|
35
|
+
// the parapet and a glass railing
|
|
36
|
+
r(0, HZ, Wt, 10, '#6b5a4a'); r(0, HZ, Wt, 2, '#8a7a6a'); ctx.fillStyle = 'rgba(200,230,255,.18)'; ctx.fillRect(0, HZ - 18, Wt, 18); for (let x = 0; x < Wt; x += 40) r(x, HZ - 18, 2, 18, '#c0cbdc'); r(0, HZ - 19, Wt, 2, '#c0cbdc');
|
|
37
|
+
// the deck: planks with depth, a stone path
|
|
38
|
+
for (let y = HZ + 10; y < Ht; y += 7) { const k = (y - HZ) / (Ht - HZ); r(0, y, Wt, 7, (y / 7 | 0) % 2 ? '#a6754c' : '#b07f54'); r(0, y + 6, Wt, 1, '#8a5e3c'); for (let x = (y * 11) % 50; x < Wt; x += Math.round(50 + k * 20)) r(x, y, 1, 6, '#8a5e3c'); }
|
|
39
|
+
for (let i = 0; i < 10; i++) { const y = HZ + 24 + i * 22, w = 22 + i * 2; r(Wt / 2 - w / 2, y, w, 10, '#c9c0ae'); r(Wt / 2 - w / 2, y, w, 2, '#e0d8c6'); }
|
|
40
|
+
// a pergola with crossing string lights
|
|
41
|
+
for (const px of [40, Wt - 44]) { r(px, 30, 5, HZ + 70, '#6b4a33'); r(px + 1, 30, 1, HZ + 70, '#8a6a4a'); }
|
|
42
|
+
r(40, 28, Wt - 80, 5, '#6b4a33'); r(40, 28, Wt - 80, 1, '#8a6a4a');
|
|
43
|
+
for (const [x0, x1, sag] of [[44, Wt - 44, 30], [44, Wt / 2, 18], [Wt / 2, Wt - 44, 18]]) for (let x = x0, i = 0; x < x1; x += 2, i++) { const k = (x - x0) / (x1 - x0), y = 33 + Math.round(Math.sin(k * Math.PI) * sag); r(x, y, 1, 1, '#2a1d16'); if (i % 7 === 0) { const c = ['#ffd84d', '#ff6ec7', '#2ce8f5', '#ffffff'][(i / 7) % 4 | 0], on = ((t / 500 + i) | 0) % 5; r(x - 1, y + 1, 3, 3, on ? c : '#5a4a3a'); if (on && night) { ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.fillStyle = c + '30'; ctx.fillRect(x - 4, y - 2, 9, 9); ctx.restore(); } } }
|
|
44
|
+
// a plunge pool with shimmering water and loungers
|
|
45
|
+
const poolX = 70, poolY = HZ + 40;
|
|
46
|
+
r(poolX - 3, poolY - 3, 126, 66, '#e8e2d6'); r(poolX, poolY, 120, 60, '#2f8fd0'); r(poolX, poolY, 120, 4, '#1e6fa8');
|
|
47
|
+
for (let i = 0; i < 14; i++) { const h = hash('pw' + i), x = poolX + 4 + h % 112, y = poolY + 6 + (h >>> 8) % 50; if (((t / 300 + i) | 0) % 3) r(x, y, 6, 1, '#9fd3ff'); }
|
|
48
|
+
r(poolX + 110, poolY - 8, 2, 12, '#c0cbdc'); r(poolX + 104, poolY - 8, 8, 2, '#c0cbdc');
|
|
49
|
+
// the bar on the right: counter, bottles, stools, a neon sign
|
|
50
|
+
const bx = Wt - 200, by = HZ + 30;
|
|
51
|
+
r(bx, by + 18, 140, 24, '#5a3a28'); r(bx, by + 16, 140, 4, '#8a6a4a'); r(bx, by + 18, 140, 2, '#3a2418');
|
|
52
|
+
r(bx + 4, by - 14, 132, 26, '#3a2418'); for (let i = 0; i < 14; i++) r(bx + 8 + i * 9, by - 10 + (i % 2) * 2, 4, 10 - (i % 2) * 2, ['#63c74d', '#ffd84d', '#e43b44', '#2ce8f5', '#b55088'][i % 5]);
|
|
53
|
+
r(bx + 36, by - 32, 68, 14, '#1b1d2e'); const on = ((t / 140) | 0) % 37 !== 0; pixText(bx + 42, by - 28, 'ROOF BAR', on ? '#ff6ec7' : '#5a2a48'); if (on) { ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.fillStyle = 'rgba(255,110,199,.12)'; ctx.fillRect(bx + 30, by - 36, 80, 22); ctx.restore(); }
|
|
54
|
+
const stools = [0, 1, 2, 3, 4].map(i => ({ x: bx + 10 + i * 26, y: by + 44 }));
|
|
55
|
+
for (const s2 of stools) { r(s2.x + 2, s2.y + 8, 2, 10, PAL.ink); r(s2.x - 2, s2.y + 6, 10, 3, '#e43b44'); }
|
|
56
|
+
// umbrella tables in the middle
|
|
57
|
+
const tables = [{ x: 220, y: HZ + 110 }, { x: Wt - 290, y: HZ + 110 }, { x: 250, y: HZ + 180 }];
|
|
58
|
+
for (const tb of tables) { r(tb.x, tb.y + 16, 24, 4, '#e8e2d6'); r(tb.x + 11, tb.y - 30, 2, 46, '#c0cbdc'); r(tb.x - 14, tb.y - 36, 52, 8, '#e43b44'); r(tb.x - 8, tb.y - 40, 40, 4, '#e43b44'); for (let k = 0; k < 4; k++) r(tb.x - 14 + k * 13, tb.y - 36, 6, 8, '#ffffff'); }
|
|
59
|
+
// a fire pit (glows at night), potted palms, lanterns
|
|
60
|
+
const fx = Wt / 2 + 80, fy = HZ + 150; r(fx - 14, fy, 28, 10, '#6b6a66'); r(fx - 12, fy + 2, 24, 6, '#3a3a40');
|
|
61
|
+
for (let i = 0; i < 5; i++) { const fl = ((t / 90 + i) | 0) % 3; r(fx - 8 + i * 4, fy - 4 - fl * 2, 3, 6 + fl * 2, i % 2 ? '#feae34' : '#e43b44'); }
|
|
62
|
+
if (!day) { ctx.save(); ctx.globalCompositeOperation = 'lighter'; const g = ctx.createRadialGradient(fx, fy, 0, fx, fy, 50); g.addColorStop(0, 'rgba(255,150,60,.35)'); g.addColorStop(1, 'rgba(255,150,60,0)'); ctx.fillStyle = g; ctx.fillRect(fx - 50, fy - 50, 100, 100); ctx.restore(); }
|
|
63
|
+
for (const [px, py] of [[16, HZ + 20], [Wt - 30, HZ + 20], [16, Ht - 60], [Wt - 30, Ht - 60]]) { r(px, py + 20, 14, 12, '#c96f4a'); for (let k = 0; k < 5; k++) r(px + 6 - k * 3, py + 6 - k, 2, 16 + k, '#3a8f46'), r(px + 6 + k * 3, py + 6 - k, 2, 16 + k, '#3a8f46'); r(px + 6, py, 2, 22, '#6b4a33'); }
|
|
64
|
+
// the people: agents whose turn ended (measured), at the bar, the tables and the pool
|
|
65
|
+
const relaxing = (data ? data.people : []).filter(p => !p.leaving && p.state === 'idle').slice(0, 11);
|
|
66
|
+
const seats = [...stools.map(s2 => ({ x: s2.x - 4, y: s2.y - 14, pose: 'back' })), ...tables.flatMap(tb => [{ x: tb.x - 14, y: tb.y - 4, pose: 'front' }, { x: tb.x + 22, y: tb.y - 4, pose: 'front' }]), { x: poolX + 20, y: poolY + 66, pose: 'front' }, { x: poolX + 70, y: poolY + 66, pose: 'front' }];
|
|
32
67
|
relaxing.forEach((p, i) => {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
r(x + 18, y + 8, 6, 6, PAL.ink); r(x + 19, y + 9, 4, 4, '#ff6ec7'); r(x + 20, y + 6, 1, 3, '#63c74d'); // a drink
|
|
37
|
-
hits.push({ x: x - 6, y: y - 4, w: 30, h: 34, id: p.id, title: `${p.name} · ${p.title || ''}` });
|
|
68
|
+
const s2 = seats[i]; if (!s2) return;
|
|
69
|
+
if (s2.pose === 'back') Art.drawSeatedBack(s2.x, s2.y, look(p.id), t, {}); else Art.drawFront(s2.x, s2.y, look(p.id), t, { legs: Art.LEGS_SIT, legsKey: 'sit' });
|
|
70
|
+
hits.push({ x: s2.x - 2, y: s2.y - 4, w: 20, h: 32, id: p.id, title: `${p.name} · ${p.title || ''}` });
|
|
38
71
|
});
|
|
39
|
-
if (!relaxing.length) { r(Wt / 2 -
|
|
40
|
-
|
|
72
|
+
if (!relaxing.length) { r(Wt / 2 - 66, Ht - 40, 132, 14, '#00000055'); pixText(Wt / 2 - 58, Ht - 36, lang === 'pt' ? 'NINGUEM DE FOLGA' : 'NOBODY ON BREAK', '#f4ecd8'); }
|
|
73
|
+
if (!day) { ctx.fillStyle = 'rgba(10,12,40,.25)'; ctx.fillRect(0, HZ, Wt, Ht - HZ); } // evening light on the deck
|
|
41
74
|
viewHits = hits;
|
|
42
75
|
}
|
|
43
76
|
|
|
44
|
-
// ---- the server room:
|
|
77
|
+
// ---- the server room: two rows of glass racks, a NOC wall with live numbers, cold aisle, cable trays ----
|
|
45
78
|
function drawServers(t) {
|
|
46
|
-
const Wt = TROPHY_W, Ht = TROPHY_H, hits = [];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
for (let x =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
r(x +
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
79
|
+
const Wt = TROPHY_W, Ht = TROPHY_H, hits = [], people = (data ? data.people : []).filter(p => !p.leaving);
|
|
80
|
+
const ledOf = s => s === 'needs_you' ? '#ff4d57' : s === 'waiting' ? '#feae34' : s === 'asleep' ? '#2a3050' : s === 'idle' ? '#3b8bff' : '#62ff7a';
|
|
81
|
+
// room: dark walls with acoustic panels, a raised floor with vents, blue strip lights
|
|
82
|
+
r(0, 0, Wt, 140, '#151a28'); for (let x = 0; x < Wt; x += 32) { r(x + 2, 8, 28, 120, '#1a2032'); r(x + 2, 8, 28, 1, '#232a40'); }
|
|
83
|
+
r(0, 138, Wt, 3, '#2ce8f5'); ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.fillStyle = 'rgba(44,232,245,.08)'; ctx.fillRect(0, 128, Wt, 24); ctx.restore();
|
|
84
|
+
for (let y = 141; y < Ht; y += 18) for (let x = 0; x < Wt; x += 18) { r(x, y, 18, 18, (x + y) % 36 ? '#1c2132' : '#20263a'); r(x, y, 18, 1, '#2a3148'); r(x, y, 1, 18, '#2a3148'); }
|
|
85
|
+
for (let x = 96; x < Wt - 96; x += 36) { r(x, 268, 18, 18, '#11141f'); for (let k = 0; k < 4; k++) r(x + 2, 270 + k * 4, 14, 1, '#2a3148'); } // cold-aisle vents
|
|
86
|
+
for (let i = 0; i < 12; i++) { const h = hash('mist' + i), k = ((t / 2200 + i / 12) % 1); r(100 + h % (Wt - 200), 280 - Math.round(k * 40), 2, 1, `rgba(180,230,255,${(.35 - k * .35).toFixed(2)})`); }
|
|
87
|
+
// cable trays overhead with cables dropping to the racks
|
|
88
|
+
r(0, 22, Wt, 4, '#3a4058'); r(0, 22, Wt, 1, '#5a6280');
|
|
89
|
+
// racks: a back row (smaller) and a front row (bigger); each rack is a session
|
|
90
|
+
const rack = (p, x, y, w, h, i) => {
|
|
91
|
+
const busy = !['idle', 'asleep'].includes(p.state), speed = p.state === 'asleep' ? 0 : busy ? 80 : 600, c = ledOf(p.state);
|
|
92
|
+
r(x - 2, y - 2, w + 4, h + 4, '#0b0a0e'); r(x, y, w, h, '#232a40'); r(x, y, w, 3, agentOf(p).color);
|
|
93
|
+
const units = Math.floor((h - 10) / 7);
|
|
94
|
+
for (let u = 0; u < units; u++) { const uy = y + 6 + u * 7; r(x + 3, uy, w - 6, 5, '#141826'); r(x + 3, uy, w - 6, 1, '#2a3148'); for (let l = 0; l < Math.floor((w - 16) / 3); l++) { const lit = speed && hash(p.id + u + ':' + l + ':' + ((t / speed) | 0)) % 3 !== 0; r(x + 5 + l * 3, uy + 2, 2, 2, lit ? c : '#1e2236'); } }
|
|
95
|
+
ctx.fillStyle = 'rgba(200,230,255,.06)'; ctx.fillRect(x, y, w, h); ctx.fillStyle = 'rgba(255,255,255,.08)'; ctx.beginPath(); ctx.moveTo(x + w * .2, y); ctx.lineTo(x + w * .35, y); ctx.lineTo(x + w * .15, y + h); ctx.lineTo(x, y + h); ctx.fill(); // glass door
|
|
96
|
+
const blink = p.state === 'needs_you' ? ((t / 300) | 0) % 2 : 1; r(x + w / 2 - 2, y - 6, 4, 4, blink ? c : '#2a3050'); if (blink && p.state !== 'asleep') { ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.fillStyle = c + '33'; ctx.fillRect(x + w / 2 - 8, y - 12, 16, 16); ctx.restore(); }
|
|
97
|
+
r(x + w / 2, 26, 1, y - 32, ['#e43b44', '#ffd84d', '#3b5dc9', '#63c74d'][i % 4]); // cable from the tray
|
|
98
|
+
const nm = String(p.name).toUpperCase().replace(/[^A-Z0-9\-]/g, '').slice(0, Math.floor(w / 4));
|
|
99
|
+
r(x, y + h + 3, w, 8, '#11141f'); pixText(x + w / 2 - nm.length * 2, y + h + 5, nm, '#c0cbdc');
|
|
100
|
+
hits.push({ x: x - 2, y: y - 8, w: w + 4, h: h + 20, id: p.id, title: `${p.name} · ${T.states[p.state] || p.state} · ${fmtK((p.today && p.today.outTokens) || 0)} ${T.usage.outShort}` });
|
|
101
|
+
};
|
|
102
|
+
const back = people.slice(0, 8), front = people.slice(8, 14);
|
|
103
|
+
back.forEach((p, i) => rack(p, 40 + i * 72, 118, 44, 80, i));
|
|
104
|
+
front.forEach((p, i) => rack(p, 30 + i * 100, 238, 60, 110, i + 8));
|
|
105
|
+
// the NOC wall: three big screens with live numbers
|
|
106
|
+
const working = people.filter(p => !['idle', 'asleep', 'needs_you', 'waiting'].includes(p.state)).length, need = people.filter(p => p.state === 'needs_you' || p.state === 'waiting');
|
|
107
|
+
const scr = (x, w) => { r(x - 3, 34, w + 6, 66, '#0b0a0e'); r(x, 37, w, 60, '#071018'); ctx.fillStyle = 'rgba(44,232,245,.06)'; for (let y = 37; y < 97; y += 2) ctx.fillRect(x, y, w, 1); };
|
|
108
|
+
scr(170, 90); scr(275, 90); scr(380, 90);
|
|
109
|
+
pixText(176, 41, 'SESSIONS', '#2ce8f5'); gText(ctx, 176, 52, people.length, '#62ff7a', 4); pixText(176, 86, `${working} WORKING`, '#62ff7a');
|
|
110
|
+
pixText(281, 41, 'OUTPUT TODAY', '#2ce8f5');
|
|
111
|
+
{ const top = people.slice().sort((a, b) => ((b.today && b.today.outTokens) || 0) - ((a.today && a.today.outTokens) || 0)).slice(0, 8), max = Math.max(1, ...top.map(p => (p.today && p.today.outTokens) || 0));
|
|
112
|
+
top.forEach((p, i) => { const h = Math.max(1, Math.round(((p.today && p.today.outTokens) || 0) / max * 38)); r(281 + i * 10, 92 - h, 7, h, agentOf(p).color); }); }
|
|
113
|
+
pixText(386, 41, 'ALERTS', need.length ? '#ff4d57' : '#2ce8f5');
|
|
114
|
+
if (need.length) need.slice(0, 5).forEach((p, i) => { if (((t / 500) | 0) % 2 || i) r(386, 52 + i * 8, 3, 3, '#ff4d57'); pixText(392, 52 + i * 8, String(p.name).toUpperCase().replace(/[^A-Z0-9\-]/g, '').slice(0, 13), '#ffb0b0'); });
|
|
115
|
+
else { pixText(386, 58, 'ALL SYSTEMS', '#62ff7a'); pixText(386, 66, 'NOMINAL', '#62ff7a'); }
|
|
116
|
+
// a sysadmin desk, a fire extinguisher, a warning stripe
|
|
117
|
+
r(Wt - 110, 300, 80, 6, '#3a4058'); r(Wt - 104, 306, 3, 30, '#2a3148'); r(Wt - 40, 306, 3, 30, '#2a3148'); r(Wt - 96, 286, 24, 14, '#0b0a0e'); r(Wt - 94, 288, 20, 10, '#2ce8f5'); r(Wt - 64, 294, 6, 6, '#e8e2d6');
|
|
118
|
+
r(14, 160, 8, 18, '#e43b44'); r(15, 156, 6, 4, '#2a2a30'); r(16, 164, 4, 6, '#ffffff');
|
|
119
|
+
for (let x = 0; x < Wt; x += 16) r(x, Ht - 6, 8, 6, '#ffd84d'), r(x + 8, Ht - 6, 8, 6, '#1b1622');
|
|
120
|
+
if (!people.length) pixText(Wt / 2 - 30, 200, 'NO SESSIONS', '#c0cbdc');
|
|
70
121
|
viewHits = hits;
|
|
71
122
|
}
|
|
72
123
|
|