<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Team HQ</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #06060c; overflow: hidden; font-family: 'Segoe UI', system-ui, sans-serif; }
canvas { display: block; }
#tooltip {
position: fixed; pointer-events: none; display: none;
background: rgba(6,6,14,0.97); border: 1px solid rgba(255,255,255,0.1);
border-radius: 12px; padding: 14px 18px; color: #fff;
font-size: 13px; line-height: 1.6; backdrop-filter: blur(16px);
max-width: 250px; z-index: 100;
box-shadow: 0 12px 40px rgba(0,0,0,0.6);
}
#tooltip .tname { font-weight: 700; font-size: 16px; margin-bottom: 3px; }
#tooltip .trole { opacity: 0.5; font-size: 11px; margin-bottom: 8px; }
#tooltip .tdesc { font-size: 12px; opacity: 0.8; }
#hint { position:fixed; bottom:14px; left:50%; transform:translateX(-50%);
color: rgba(255,255,255,0.2); font-size:11px; pointer-events:none; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="tooltip"></div>
<div id="hint">hover to inspect rooms</div>
<script>
const TEAM = [
{ name:'Arlo', role:'Analyst', color:'#70C8FF', icon:'📊',
desc:'Market & tech research, cited briefings', floor:0, col:0 },
{ name:'Vera', role:'QA & Test Automation', color:'#FF8C6B', icon:'🔍',
desc:'Test suites, quality gates, regression', floor:0, col:1 },
{ name:'Luna', role:'Frontend & UI/UX', color:'#FF85B3', icon:'🎨',
desc:'React/Next.js, design system, responsive',floor:1, col:0 },
{ name:'Stellar', role:'DevOps / SRE', color:'#FFCF5C', icon:'🚀',
desc:'Docker, Dokploy, CI/CD, monitoring', floor:1, col:1 },
{ name:'Yuma', role:'Tech Lead / Architect', color:'#7C6FF7', icon:'🏗️',
desc:'System design, schema DB, code review', floor:2, col:0 },
{ name:'Flynn', role:'Backend Engineer', color:'#00D2A0', icon:'⚙️',
desc:'Endpoints, PostgreSQL, auth, workers', floor:2, col:1 },
];
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
let W, H, CX, CY, SC;
function resize() {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight;
CX = W / 2;
CY = H * 0.68;
SC = Math.min(W / 900, H / 700, 1.3);
}
resize();
window.addEventListener('resize', resize);
function iso(x, y, z) {
const sx = (x - z) * 50 * SC;
const sy = -(y * 48 * SC) + (x + z) * 25 * SC;
return { x: CX + sx, y: CY + sy };
}
function hexRgba(hex, a) {
const r = parseInt(hex.slice(1,3),16);
const g = parseInt(hex.slice(3,5),16);
const b = parseInt(hex.slice(5,7),16);
return `rgba(${r},${g},${b},${a})`;
}
function poly(pts, fill, stroke, lw) {
ctx.beginPath();
ctx.moveTo(pts[0].x, pts[0].y);
for (let i = 1; i < pts.length; i++) ctx.lineTo(pts[i].x, pts[i].y);
ctx.closePath();
ctx.fillStyle = fill; ctx.fill();
if (stroke) { ctx.strokeStyle = stroke; ctx.lineWidth = lw || 0.5; ctx.stroke(); }
}
function box(bx, by, bz, w, h, d, tc, lc, rc, ec) {
ec = ec || 'rgba(255,255,255,0.04)';
// right face (front-right)
poly(
[iso(bx+w,by+h,bz+d), iso(bx,by+h,bz+d), iso(bx,by,bz+d), iso(bx+w,by,bz+d)],
rc, ec
);
// left face (front-left)
poly(
[iso(bx,by+h,bz), iso(bx,by+h,bz+d), iso(bx,by,bz+d), iso(bx,by,bz)],
lc, ec
);
// top face
poly(
[iso(bx,by+h,bz), iso(bx+w,by+h,bz), iso(bx+w,by+h,bz+d), iso(bx,by+h,bz+d)],
tc, ec
);
}
function desk(bx, by, bz, color) {
// table
box(bx-0.9,by+0.85,bz-0.4, 1.8,0.07,0.9, '#2a2a40','#1e1e30','#181828');
// legs
[[-0.8,-0.35],[0.75,-0.35],[-0.8,0.4],[0.75,0.4]].forEach(([ox,oz]) =>
box(bx+ox,by,bz+oz, 0.07,0.85,0.07, '#333348','#222238','#1e1e30'));
// monitor
box(bx-0.45,by+1.25,bz-0.35, 0.9,0.6,0.05, '#0d0d1a','#080814','#060610');
// screen glow
box(bx-0.4,by+1.28,bz-0.33, 0.8,0.52,0.01,
hexRgba(color,0.85), hexRgba(color,0.25), hexRgba(color,0.2), 'transparent');
// stand
box(bx-0.04,by+0.92,bz-0.32, 0.08,0.33,0.08, '#1a1a2a','#111120','#0e0e1c');
// chair seat
box(bx-0.25,by+0.4,bz+0.45, 0.55,0.06,0.55, '#222234','#1a1a2a','#161626');
// chair back
box(bx-0.25,by+0.46,bz+0.9, 0.55,0.5,0.06, '#1e1e30','#161626','#131322');
}
function serverRack(bx, by, bz, t) {
box(bx,by,bz, 0.6,1.8,0.5, '#111122','#0a0a18','#0c0c1a');
for (let i = 0; i < 6; i++) {
const bright = (Math.sin(t * 3 + i * 1.2) + 1) * 0.5;
const c = i % 2 === 0 ? '#00ff88' : '#00aaff';
box(bx+0.05,by+0.15+i*0.24,bz+0.01, 0.45,0.08,0.02,
hexRgba(c, 0.5 + bright * 0.5),
hexRgba(c, 0.2 + bright * 0.2),
hexRgba(c, 0.15 + bright * 0.15), 'transparent');
}
}
function whiteboard(bx, by, bz) {
box(bx,by+0.8,bz, 2.0,1.1,0.04, '#e0e0ea','#c0c0d0','#b8b8c8','rgba(0,0,0,0.1)');
box(bx-0.05,by+1.85,bz-0.01, 2.1,0.06,0.06, '#404058','#303046','#282840');
box(bx-0.05,by+0.78,bz-0.01, 2.1,0.06,0.06, '#404058','#303046','#282840');
}
function plant(bx, by, bz) {
box(bx,by,bz, 0.28,0.3,0.28, '#7a6840','#5a4c30','#4a3e26');
const p = iso(bx+0.14, by+0.42, bz+0.14);
[[0,0,11,'#2ecc71'],[-7,4,8,'#27ae60'],[6,3,9,'#1e9e50'],[-3,-5,7,'#34d67a']].forEach(([ox,oy,r,c]) => {
ctx.beginPath(); ctx.arc(p.x+ox, p.y+oy, r*SC, 0, Math.PI*2);
ctx.fillStyle = c; ctx.fill();
});
}
function books(bx, by, bz) {
['#3498db','#e74c3c','#f39c12','#2ecc71'].forEach((c,i) => {
box(bx,by+i*0.06,bz, 0.35,0.05,0.45,
hexRgba(c,0.9), hexRgba(c,0.6), hexRgba(c,0.5));
});
}
function testScreens(bx, by, bz, color, t) {
for (let i = 0; i < 3; i++) {
const bright = (Math.sin(t*2+i*1.5)+1)*0.3 + 0.4;
box(bx+i*0.75, by+1.0, bz, 0.55,0.4,0.03,
hexRgba(color, bright),
hexRgba(color, bright*0.4),
hexRgba(color, bright*0.3), 'transparent');
}
}
const BW = 10, BD = 7, FH = 3.2;
const hitZones = [];
let tick = 0;
function draw() {
tick += 0.016;
ctx.clearRect(0,0,W,H);
// BG
const bg = ctx.createRadialGradient(CX,CY*0.8,0, CX,CY*0.8,W*0.7);
bg.addColorStop(0,'#0e0e1e'); bg.addColorStop(1,'#040408');
ctx.fillStyle = bg; ctx.fillRect(0,0,W,H);
// Subtle ground glow
const gg = ctx.createRadialGradient(CX,CY+60*SC,0, CX,CY+60*SC,220*SC);
gg.addColorStop(0,'rgba(60,30,120,0.25)'); gg.addColorStop(1,'rgba(0,0,0,0)');
ctx.fillStyle = gg; ctx.fillRect(0,0,W,H);
hitZones.length = 0;
// Draw floors bottom to top, back to front within each
for (let f = 0; f < 3; f++) {
const fy = f * FH;
// Floor slab
box(0,fy,0, BW,0.1,BD, 'rgba(18,22,45,0.8)','rgba(10,14,30,0.7)','rgba(8,12,28,0.7)');
// Columns
for (const cx2 of [0, BW/2, BW]) {
for (const cz2 of [0, BD]) {
box(cx2-0.06,fy,cz2-0.06, 0.12,FH,0.12,
'rgba(55,65,95,0.9)','rgba(30,40,70,0.9)','rgba(25,35,60,0.9)',
'rgba(255,255,255,0.08)');
}
}
// Center divider
box(BW/2-0.04,fy+0.1,0.2, 0.08,FH-0.2,BD-0.4,
'rgba(35,42,70,0.5)','rgba(20,26,50,0.6)','rgba(18,22,45,0.5)');
// Two rooms
const floorMembers = TEAM.filter(m => m.floor === f);
// Render col=0 (left/back) first, then col=1 (right/front) for correct overlap
const sorted = [...floorMembers].sort((a,b) => a.col - b.col);
sorted.forEach(member => {
const rx = member.col * (BW/2);
const rW = BW/2, rD = BD;
// Room floor tint
box(rx+0.15,fy+0.1,0.15, rW-0.3,0.02,rD-0.3,
hexRgba(member.color,0.07), hexRgba(member.color,0.04), hexRgba(member.color,0.03), 'transparent');
// Glass back wall
box(rx+0.1,fy+0.1,0.02, rW-0.2,FH-0.3,0.03,
hexRgba(member.color,0.03), hexRgba(member.color,0.06), hexRgba(member.color,0.04),
hexRgba(member.color,0.1));
// Glass side wall (left edge of room)
box(rx+0.02,fy+0.1,0.1, 0.03,FH-0.3,rD-0.2,
hexRgba(member.color,0.02), hexRgba(member.color,0.05), hexRgba(member.color,0.03),
hexRgba(member.color,0.08));
// Ambient glow in room
const center = iso(rx+rW/2, fy+FH/2, rD/2);
const glow = ctx.createRadialGradient(center.x,center.y,0, center.x,center.y,70*SC);
glow.addColorStop(0,hexRgba(member.color,0.12)); glow.addColorStop(1,'rgba(0,0,0,0)');
ctx.fillStyle = glow;
ctx.fillRect(center.x-100*SC, center.y-80*SC, 200*SC, 160*SC);
// Furniture
const dx = rx + rW/2 - 0.3;
const dz = rD/2 - 0.3;
desk(dx, fy+0.1, dz, member.color);
// Per-member props
if (member.name === 'Stellar') {
serverRack(rx+rW-1.0, fy+0.1, 1.0, tick);
serverRack(rx+rW-1.0, fy+0.1, 2.2, tick);
}
if (member.name === 'Yuma') whiteboard(rx+0.5, fy+0.1, rD-0.5);
if (member.name === 'Arlo') books(rx+rW-1.2, fy+0.1+0.87, dz+0.3);
if (member.name === 'Vera') testScreens(rx+0.3, fy+0.1, 0.3, member.color, tick);
if (member.name === 'Luna') {
// second monitor
box(dx-1.1,fy+0.1+1.25,dz-0.35, 0.85,0.55,0.05, '#0d0d1a','#080814','#060610');
box(dx-1.05,fy+0.1+1.28,dz-0.33, 0.75,0.48,0.01,
hexRgba(member.color,0.7), hexRgba(member.color,0.2), hexRgba(member.color,0.15), 'transparent');
}
if (member.name === 'Flynn') {
// terminal screens
box(rx+rW-1.6,fy+0.1+1.0,0.4, 0.5,0.35,0.03,
hexRgba(member.color,0.6), hexRgba(member.color,0.2), hexRgba(member.color,0.15), 'transparent');
}
plant(rx+0.3, fy+0.1, rD-1.0);
// Ceiling light strip
const lp = iso(rx+rW/2, fy+FH-0.05, rD/2);
const lg = ctx.createRadialGradient(lp.x,lp.y,0, lp.x,lp.y,25*SC);
lg.addColorStop(0,hexRgba(member.color,0.5)); lg.addColorStop(1,'rgba(0,0,0,0)');
ctx.fillStyle = lg;
ctx.fillRect(lp.x-35*SC,lp.y-15*SC,70*SC,30*SC);
// Hit zone
const c1 = iso(rx,fy,0), c2 = iso(rx+rW,fy+FH,rD);
hitZones.push({
minX: Math.min(c1.x,c2.x)-20, maxX: Math.max(c1.x,c2.x)+20,
minY: Math.min(c1.y,c2.y)-20, maxY: Math.max(c1.y,c2.y)+20,
member
});
});
// Ceiling slab
box(0,fy+FH,0, BW,0.08,BD,
'rgba(16,20,40,0.65)','rgba(10,14,30,0.55)','rgba(8,12,28,0.55)');
}
// Labels AFTER all geometry so they're always on top
for (const member of TEAM) {
const rx = member.col * (BW/2);
const rW = BW/2, rD = BD;
const fy = member.floor * FH;
const lp = iso(rx+rW/2, fy+FH+0.25, rD/2);
ctx.save();
ctx.font = `bold ${14*SC}px Segoe UI, system-ui, sans-serif`;
ctx.textAlign = 'center';
ctx.fillStyle = member.color;
ctx.shadowColor = member.color; ctx.shadowBlur = 14*SC;
ctx.fillText(member.icon+' '+member.name, lp.x, lp.y);
ctx.shadowBlur = 0;
ctx.font = `${10*SC}px Segoe UI, system-ui, sans-serif`;
ctx.fillStyle = 'rgba(255,255,255,0.35)';
ctx.fillText(member.role, lp.x, lp.y + 15*SC);
ctx.restore();
}
// Title
ctx.save();
ctx.font = `bold ${20*SC}px Segoe UI, system-ui, sans-serif`;
ctx.textAlign = 'center';
ctx.fillStyle = 'rgba(255,255,255,0.6)';
ctx.fillText('T E A M H Q', CX, 32*SC);
ctx.font = `${11*SC}px Segoe UI, system-ui, sans-serif`;
ctx.fillStyle = 'rgba(255,255,255,0.2)';
ctx.fillText("Vania's Crew · 6 engineers · 3 floors", CX, 50*SC);
ctx.restore();
// Ambient particles
for (let i = 0; i < 35; i++) {
const px = (Math.sin(i*12.4+tick*0.25)*0.5+0.5)*W;
const py = (Math.cos(i*8.7+tick*0.15)*0.5+0.5)*H;
const pa = (Math.sin(i*3.1+tick*0.6)*0.5+0.5)*0.25;
ctx.beginPath(); ctx.arc(px,py,1.2*SC,0,Math.PI*2);
ctx.fillStyle = `rgba(100,100,255,${pa})`; ctx.fill();
}
requestAnimationFrame(draw);
}
draw();
// Tooltip
const tooltip = document.getElementById('tooltip');
canvas.addEventListener('mousemove', e => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left, my = e.clientY - rect.top;
let hit = null;
// check from top floor down (last drawn = first priority)
for (let i = hitZones.length-1; i >= 0; i--) {
const z = hitZones[i];
if (mx >= z.minX && mx <= z.maxX && my >= z.minY && my <= z.maxY) { hit = z.member; break; }
}
if (hit) {
tooltip.innerHTML = `<div class="tname" style="color:${hit.color}">${hit.icon} ${hit.name}</div>
<div class="trole">${hit.role}</div><div class="tdesc">${hit.desc}</div>`;
tooltip.style.display = 'block';
tooltip.style.left = Math.min(e.clientX+16,W-270)+'px';
tooltip.style.top = (e.clientY-10)+'px';
canvas.style.cursor = 'crosshair';
} else { tooltip.style.display='none'; canvas.style.cursor='default'; }
});
canvas.addEventListener('mouseleave', () => { tooltip.style.display='none'; });
</script>
</body>
</html>
Scan with iPhone Camera or Android QR scanner to preview natively on your device.