// ARGUS Maturity Pyramid — true 3D pyramid with 4 SVG faces // Each face is one cohesive SVG (full stack) so faces never "explode" while rotating. // 6 levels, 4 dimensions (People / Process / Policy / Technology) — one per face. const { useState, useEffect, useRef } = React; const PYRAMID_LEVELS = [ { lvl: 'LEVEL 0', name: 'Unmanaged', desc: 'No visibility on identities or AI agents. No policies in place. The organisation operates fully reactively, with no structured approach to access or governance.' }, { lvl: 'LEVEL 1', name: 'Fragmented', desc: 'Siloed systems with no central oversight. Access management is ad-hoc and person-dependent. AI tools are adopted without registration or ownership. Controls exist in isolation.' }, { lvl: 'LEVEL 2', name: 'Identity Aware', desc: 'Initial visibility on identities is established. Basic controls are in place, centralised authentication, some access policies, first steps toward understanding the AI landscape. The foundation is forming.' }, { lvl: 'LEVEL 3', name: 'Governed', desc: 'Central governance is established and enforced. Policies are documented. Compliance is demonstrable. AI usage is registered with clear ownership. Access reviews happen periodically. The organisation can explain who has access to what, and why.' }, { lvl: 'LEVEL 4', name: 'AI-Assisted', desc: 'AI actively supports governance and operations. Automated access reviews, anomaly detection, intelligent lifecycle management. Governance processes are enhanced, not replaced, by AI. Human oversight remains central.' }, { lvl: 'LEVEL 5', name: 'Autonomous', desc: 'Self-learning governance. AI-driven decision-making within clearly defined boundaries. Identity, security, and AI governance are fully connected in a continuous, adaptive cycle. The organisation doesn\u2019t just respond to change \u2014 it anticipates it.' } ]; // Faces in order: front (0°), right (90°), back (180°), left (270°) const DIMENSIONS = [ { id: 'people', name: 'People' }, { id: 'process', name: 'Process' }, { id: 'policy', name: 'Policy' }, { id: 'technology', name: 'Technology' } ]; // ---------- Pyramid face: full SVG (front, with text + active highlight) ---------- const PyramidFace = ({ active, onSelectLevel, isFront }) => { // SVG viewBox dimensions const VB_W = 560; const VB_H = 460; const layerH = 52; const gap = 4; const baseW = 520; const topW = 150; const totalLayers = PYRAMID_LEVELS.length; const stackBottomY = VB_H - 30; const cx = VB_W / 2; const widthFor = (i) => { const t = i / (totalLayers - 1); return baseW + (topW - baseW) * t; }; return ( {PYRAMID_LEVELS.map((layer, i) => { const wBot = widthFor(i); const wTop = widthFor(i + 1 < totalLayers ? i + 1 : i + 0.6); const yBot = stackBottomY - i * (layerH + gap); const yTop = yBot - layerH; const isActive = i === active; const points = [ [cx - wTop / 2, yTop], [cx + wTop / 2, yTop], [cx + wBot / 2, yBot], [cx - wBot / 2, yBot] ].map(p => p.join(',')).join(' '); const fill = isActive ? '#C8A961' : '#0E1A3A'; const stroke = isActive ? '#C8A961' : 'rgba(200,169,97,0.42)'; const textColor = isActive ? '#0E1A3A' : '#F7F3EC'; const lvlColor = isActive ? '#0E1A3A' : '#D9BE7C'; const yMid = (yTop + yBot) / 2; const wMid = (wTop + wBot) / 2; // Top 2 levels: stack LEVEL label above name to fit narrow treads const stackLabels = wMid < 240; return ( { e.stopPropagation(); if (isFront) onSelectLevel(i); }} style={{ cursor: isFront ? 'pointer' : 'default' }} className={'pyr-tread' + (isActive ? ' active' : '')} > {stackLabels ? ( <> {layer.lvl} {layer.name} ) : ( <> {layer.lvl} {layer.name} )} ); })} ); }; // ---------- Main pyramid component ---------- const Pyramid = () => { const [active, setActive] = useState(3); const [rotY, setRotY] = useState(0); // start facing the viewer; drag reveals 3D const [isDragging, setIsDragging] = useState(false); const dragState = useRef({ dragging: false, x: 0, ry: 0, moved: 0 }); const stageRef = useRef(null); // Determine which face is most front-facing based on rotY. // rotY = 0 → front (idx 0). rotY = -90 → right (idx 1). etc. // Normalize rotY into [0, 360), pick nearest 90° increment. const normY = ((-rotY) % 360 + 360) % 360; const activeDim = Math.round(normY / 90) % 4; // 3D depth: smaller value pulls side faces closer to center → reads more like a single solid shape. // Square base would be FACE_W/2 = 280, but that pushes them too far. ~32% of width feels right. const FACE_W = 560; const HALF_DEPTH = FACE_W * 0.32; const onPointerDown = (e) => { e.preventDefault(); const p = e.touches ? e.touches[0] : e; dragState.current = { dragging: true, x: p.clientX, ry: rotY, moved: 0 }; setIsDragging(true); }; const onPointerMove = (e) => { if (!dragState.current.dragging) return; const p = e.touches ? e.touches[0] : e; const dx = p.clientX - dragState.current.x; dragState.current.moved = Math.max(dragState.current.moved, Math.abs(dx)); setRotY(dragState.current.ry + dx * 0.5); }; const onPointerUp = () => { if (!dragState.current.dragging) return; dragState.current.dragging = false; setIsDragging(false); // Snap to nearest 90° face for clarity setRotY(prevY => Math.round(prevY / 90) * 90); }; useEffect(() => { window.addEventListener('mousemove', onPointerMove); window.addEventListener('mouseup', onPointerUp); window.addEventListener('touchmove', onPointerMove, { passive: false }); window.addEventListener('touchend', onPointerUp); return () => { window.removeEventListener('mousemove', onPointerMove); window.removeEventListener('mouseup', onPointerUp); window.removeEventListener('touchmove', onPointerMove); window.removeEventListener('touchend', onPointerUp); }; }, []); // Snap to a specific dimension face — pick equivalent angle closest to current rotY const snapTo = (idx) => { const targetY = -idx * 90; setRotY(prev => { const delta = ((targetY - prev) % 360 + 540) % 360 - 180; return prev + delta; }); }; const detail = PYRAMID_LEVELS[active]; const dim = DIMENSIONS[activeDim]; // Face transforms: front, right, back, left around square base const faceTransforms = [ `rotateY(0deg) translateZ(${HALF_DEPTH}px)`, `rotateY(90deg) translateZ(${HALF_DEPTH}px)`, `rotateY(180deg) translateZ(${HALF_DEPTH}px)`, `rotateY(-90deg) translateZ(${HALF_DEPTH}px)` ]; return (
{/* Pyramid stage (3D) */}
{DIMENSIONS.map((d, i) => (
))}
{/* base shadow */}
{/* Dimension label below pyramid */}
DIMENSION
{dim.name}
{/* Right column */}
{DIMENSIONS.map((d, i) => ( ))}
{detail.lvl} · {dim.name}

{detail.name}

{detail.desc}

Drag to rotate
{PYRAMID_LEVELS.map((_, i) => (
); }; window.Pyramid = Pyramid; window.PYRAMID_LEVELS = PYRAMID_LEVELS; window.PYRAMID_DIMENSIONS = DIMENSIONS;