// Voxel office — isometric CSS-3D scene with department zones and walking avatars.
// Built with pure transforms; modular avatars made of 4 cubes (legs/torso/head + accessory).
const VOX_STYLE = `
.vox-wrap{ position:absolute; inset:0; perspective: 1400px; }
.vox-scene{
position:absolute; left:50%; top:50%;
transform-style: preserve-3d;
transform: translate(-50%, -50%) rotateX(54deg) rotateZ(45deg);
width: 0; height: 0;
}
.vox-cube, .vox-tile{
position:absolute; transform-style: preserve-3d;
width: var(--s, 28px); height: var(--s, 28px);
}
/* Standard cube — top is the visible face, left/right are sides for height */
.vox-cube .top, .vox-cube .left, .vox-cube .right{
position:absolute; left:0; top:0;
width: var(--s, 28px); height: var(--s, 28px);
background: var(--c, #fff);
}
.vox-cube .top { transform: translateZ(var(--h, 28px)); filter: brightness(1.08); }
.vox-cube .left {
transform-origin: left top;
transform: rotateY(-90deg) translateX(0) translateZ(0);
width: var(--h, 28px);
filter: brightness(.78);
}
.vox-cube .right {
transform-origin: left top;
transform: rotateX(90deg) translateY(0) translateZ(0);
height: var(--h, 28px);
filter: brightness(.9);
}
/* Flat floor tile */
.vox-tile .face{
position:absolute; inset:0; background: var(--c, #DCE6F2);
border: 1px solid rgba(0,0,0,.04);
}
.vox-label{
position:absolute; transform: rotateZ(-45deg) rotateX(-54deg);
transform-origin: 0 0;
font: 600 10px/1 'Inter', sans-serif;
color: rgba(15,30,60,.7);
letter-spacing:.04em; text-transform: uppercase;
white-space:nowrap; pointer-events: none;
text-shadow: 0 1px 0 rgba(255,255,255,.6);
}
/* Floating bubble (chat preview) above a worker */
.vox-bubble{
position:absolute;
transform: rotateZ(-45deg) rotateX(-54deg);
transform-origin: center;
background: #fff;
border-radius: 10px;
padding: 5px 9px;
font: 600 10px/1.2 'Inter', sans-serif;
color: #1F2937;
box-shadow: 0 6px 14px rgba(8,20,40,.25);
white-space: nowrap;
pointer-events:none;
}
.vox-bubble .who{ color: var(--brand-secondary); font-weight:700; font-size: 9px; text-transform: uppercase; letter-spacing: .05em }
.vox-bubble .dot{ width:6px;height:6px;border-radius:999px;background:#16A34A;display:inline-block;margin-right:4px;vertical-align:middle}
@keyframes voxBob { 0%,100%{ transform: translateZ(0) } 50%{ transform: translateZ(4px) } }
@keyframes voxWalk { from{ transform: translateZ(0) } 50%{ transform: translateZ(3px) } to{ transform: translateZ(0) } }
.vox-worker .bob{ animation: voxBob 1.4s ease-in-out infinite; transform-style: preserve-3d; position:absolute; inset:0; }
`;
// One voxel cube
function Cube({ x = 0, y = 0, z = 0, w = 28, h = 28, depth, color = '#cccccc', style }) {
const s = w; // assume square footprint
const tall = h;
return (
);
}
function Tile({ x = 0, y = 0, w = 28, color = '#E5EDF7', children, style }) {
return (
{children}
);
}
// Voxel worker — modular: legs (small), torso (color), head (skin)
function Worker({ x, y, color = '#1E6BB0', skin = '#F3D1A2', anim = true, message, role }) {
return (
{/* Legs (dark) */}
{/* Torso (brand color) */}
{/* Head (skin) */}
{/* Hair patch */}
{message && (
{role &&
{role}
}
{message}
)}
);
}
// The full office scene
function VoxelOffice({ brandPrimary = '#1B3A6B', brandSecondary = '#1E6BB0', brandAccent = '#E8620A' }) {
const [hoverDept, setHoverDept] = React.useState(null);
// Workers animate by cycling positions through a small loop
const [tick, setTick] = React.useState(0);
React.useEffect(() => {
const i = setInterval(() => setTick(t => t + 1), 2200);
return () => clearInterval(i);
}, []);
// ──────────────────────────────────────────────────────────
// GRID — using "voxel coords" where one unit = 28px tile.
// We compose tile positions and building blocks below.
// Center is (0,0); coords flow outward.
// ──────────────────────────────────────────────────────────
const U = 28; // unit
// Floor tiles per department (zone color)
const zones = [
{ id: 'recepcion', name: 'Recepción', tiles: [[ -1, 1],[-1,2],[0,1],[0,2]], color: '#FFE6CF', border: brandAccent },
{ id: 'marketing', name: 'Marketing', tiles: [[-3,-2],[-3,-1],[-2,-2],[-2,-1],[-3,-3],[-2,-3]], color: '#E4D7F7' },
{ id: 'ventas', name: 'Ventas / Soporte', tiles: [[1,-2],[1,-1],[2,-2],[2,-1],[1,-3],[2,-3]], color: '#CFE8E0' },
{ id: 'almacen', name: 'Almacén', tiles: [[-3,2],[-3,3],[-2,2],[-2,3]], color: '#F6E1B8' },
{ id: 'estudio', name: 'Estudio Creativo', tiles: [[1,2],[1,3],[2,2],[2,3]], color: '#D5E7FF' },
{ id: 'ti', name: 'Oficina TI', tiles: [[-1,-2],[-1,-1],[0,-2],[0,-1]], color: '#DDE3EB' },
];
// Building elements (walls / planters / dividers — short cubes)
const blocks = [
// outer walls — perimeter at half-height
...Array.from({length: 8}, (_,i)=> ({ x:i-3.5, y:-3.5, h: 6, color:'#F4F8FC' })),
...Array.from({length: 8}, (_,i)=> ({ x:i-3.5, y: 3.5, h: 6, color:'#F4F8FC' })),
...Array.from({length: 7}, (_,i)=> ({ x:-3.5, y:i-2.5, h: 6, color:'#F4F8FC' })),
...Array.from({length: 7}, (_,i)=> ({ x: 3.5, y:i-2.5, h: 6, color:'#F4F8FC' })),
// dividers between rooms
{ x: 0.5, y:-2.5, h: 14, color:'#E5EDF7' },
{ x: 0.5, y:-1.5, h: 14, color:'#E5EDF7' },
{ x: 0.5, y: 2.5, h: 14, color:'#E5EDF7' },
{ x:-2.5, y: 0.5, h: 14, color:'#E5EDF7' },
{ x: 2.5, y: 0.5, h: 14, color:'#E5EDF7' },
// Reception desk (accent)
{ x:-0.5, y: 1.5, h: 10, color: brandAccent },
// plants — short green cubes
{ x:-3, y: 0, h: 12, color:'#7CB37A' },
{ x: 3, y: 0, h: 12, color:'#7CB37A' },
];
// Workers — one per zone, plus a director near recepción
const seats = [
{ id:'mkt', zone:'marketing', role:'Marketing', color:'#7C3AED', x:-2.6, y:-2.0, msg:'Redactando post…' },
{ id:'ana', zone:'marketing', role:'Analítica', color:'#0EA5E9', x:-2.0, y:-2.7, msg:'Calculando ROAS' },
{ id:'sop', zone:'ventas', role:'Atención', color:'#14B8A6', x: 1.6, y:-2.0, msg:'CSAT 4.9' },
{ id:'inv', zone:'almacen', role:'Inventario', color:'#A16207', x:-2.6, y: 2.6, msg:'2 SKUs sin SEO' },
{ id:'log', zone:'almacen', role:'Logística', color: brandAccent, x:-2.0, y: 2.0, msg:'Entrega #142' },
{ id:'dis', zone:'estudio', role:'Diseño Web', color:'#DB2777', x: 1.6, y: 2.6, msg:'Editando landing' },
{ id:'sec', zone:'ti', role:'Seguridad', color:'#475569', x:-0.7, y:-1.6, msg:'Monitor activo' },
{ id:'dir', zone:'recepcion', role:'Director', color: brandPrimary, x:-0.7, y: 1.6, msg:'@dueño aprobación', tag: true },
];
// Pulse the active worker (rotates each tick)
const activeIdx = tick % seats.length;
return (
{/* Base platform — slight raised slab */}
{/* Zone floor tiles */}
{zones.map(z => z.tiles.map(([gx, gy], i) => (
)))}
{/* Walls / planters / desks */}
{blocks.map((b, i) => (
))}
{/* Workers */}
{seats.map((w, i) => (
))}
{/* Zone labels positioned at zone centroid */}
{zones.map(z => {
const cx = z.tiles.reduce((a,t)=>a+t[0],0)/z.tiles.length;
const cy = z.tiles.reduce((a,t)=>a+t[1],0)/z.tiles.length;
return (
{z.name}
);
})}
{/* Overlay legend */}
Vista isométrica — Tu oficina IA
· 8 colaboradores activos
{['Aérea','Planta'].map((v,i)=>(
))}
);
}
Object.assign(window, { VoxelOffice });