/* Shared figures. Every one is drawn from window.SX_DATA — nothing is typed twice. Chrome (title, legend, note) comes from ChartFrame wherever a figure is a chart. */ const { Tag: FTag, Card: FCard, Button: FButton, ChartFrame, Plot, DataTable: FTable, GlobalDelivery: FGlobe } = window.SoftVerxDesignSystem_6fd95b; const D = window.SX_DATA; /* ── the six stages, as a chain ──────────────────────────────────── Circular nodes on one rail with the flow drawn between them. Each stage owns a hue and lights on hover; the icons are hairline schematics, not pictograms. */ const STAGE_ICON = { Sense: (<>), Stream: (<>), Store: (<>), Learn: (<>), Predict: (<>), Act: (<>), }; function StageIcon({ title, size }) { return {STAGE_ICON[title]}; } const STAGE_HUE = ["var(--sx-accent)", "color-mix(in oklab, var(--sx-accent) 55%, var(--sx-field))", "var(--sx-field)", "var(--sx-signal)", "var(--sx-amber)", "var(--sx-accent)"]; /* The chain is drawn on an arc rather than a rule: six nodes sampled off one cubic, solid arrowheads set on the tangent, labels on a common baseline. */ const bez = (t, p0, c1, c2, p3) => { const u = 1 - t; return [u * u * u * p0[0] + 3 * u * u * t * c1[0] + 3 * u * t * t * c2[0] + t * t * t * p3[0], u * u * u * p0[1] + 3 * u * u * t * c1[1] + 3 * u * t * t * c2[1] + t * t * t * p3[1]]; }; function PipelineFigure({ mobile }) { const s = D.pipeline; const wrap = React.useRef(null); const [w, setW] = React.useState(1120); React.useEffect(() => { const el = wrap.current; if (!el) return; const ro = new ResizeObserver(() => setW(el.clientWidth || 1120)); ro.observe(el); setW(el.clientWidth || 1120); return () => ro.disconnect(); }, []); if (mobile) { return (
{s.map((st, i) => ( {i > 0 && }
{st.n}

{st.title}

{st.detail}

))}
); } const R = 50, pad = R + 34, H = 232; const P0 = [pad, H - 46], C1 = [w * 0.28, 14], C2 = [w * 0.72, 14], P3 = [w - pad, H - 46]; const pts = s.map((_, i) => bez(i / (s.length - 1), P0, C1, C2, P3)); const mids = pts.slice(0, -1).map((p, i) => { const t = (i + 0.5) / (s.length - 1); const a = bez(t - 0.012, P0, C1, C2, P3), b = bez(t + 0.012, P0, C1, C2, P3); return { m: bez(t, P0, C1, C2, P3), deg: (Math.atan2(b[1] - a[1], b[0] - a[0]) * 180) / Math.PI }; }); const boxH = Math.max(...pts.map((p) => p[1])) + R + 148; /* The label can never be wider than its own slot on the arc, or apex neighbours collide. */ const slot = Math.max(96, (w - pad * 2) / (s.length - 1) - 14); return (
{STAGE_HUE.map((c, i) => )} {mids.map((a, i) => ( ))} {pts.map((p, i) => (
{s[i].n}

{s[i].title}

{s[i].detail}

))}
); } /* ── capability accumulation, 1999 → 2026 ───────────────────────────────── */ const T0 = 1999, T1 = 2026; const PRACTICE_LAYER_ = { "ERP & billing": "core", "Data platforms": "data", "Cloud & DevOps": "apps", "Industrial IoT & edge": "edge", "Applied ML": "ml", "Computer vision": "ml", "AI agents": "agents", "Document intelligence": "agents", "Energy-tech": "edge", "Agri-tech": "edge" }; const yearOf = (p) => +String(p.year || "").slice(0, 4) || null; function CapabilityTimeline({ mobile }) { const w = 1000, rowH = mobile ? 15 : 19, gap = 3; const layers = D.layers; const h = layers.length * (rowH + gap); const x = (y) => ((y - T0) / (T1 - T0)) * w; /* Each layer opens in the year its earliest delivered programme falls in — read from the record, never authored twice. */ const start = {}; layers.forEach((l) => { const years = D.projects.filter((p) => PRACTICE_LAYER_[p.practice] === l.id).map(yearOf).filter(Boolean); start[l.id] = years.length ? Math.min(...years) : T1; }); const anchors = D.projects.filter((p) => p.tier === "flagship" && yearOf(p)).map((p) => ({ year: yearOf(p), label: p.short, slug: p.slug })).sort((a, b) => a.year - b.year) .filter((a, i, arr) => i === 0 || a.year - arr[i - 1].year >= (mobile ? 6 : 3)); const [hover, setHover] = React.useState(null); return (
{[2000, 2005, 2010, 2015, 2020, 2025].map((y) => ( {y} ))} {layers.map((l, i) => { const y = i * (rowH + gap); const lit = !hover || hover === l.id; return ( setHover(l.id)} onPointerLeave={() => setHover(null)} style={{ cursor: "default" }}> {l.code} {l.label.toLowerCase()} {!mobile && {l.note.toLowerCase()}} ); })} {D.eras.map((e) => { const [a, b] = e.label.split("–").map(Number); return ( {!mobile && {e.title.toLowerCase()}} ); })} {D.projects.filter(yearOf).map((p) => ( ))} {anchors.map((a) => ( {a.label.toLowerCase()} ))}
); } /* ── sector × capability layer ──────────────────────────────────────────── */ const PRACTICE_LAYER = { "ERP & billing": "core", "Data platforms": "data", "Cloud & DevOps": "apps", "Industrial IoT & edge": "edge", "Applied ML": "ml", "Computer vision": "ml", "AI agents": "agents", "Document intelligence": "agents", "Energy-tech": "edge", "Agri-tech": "edge" }; function SectorLayerHeatmap({ mobile, wide }) { const sectors = D.sectors.map((s) => s.label); const layers = D.layers; const counts = {}; D.projects.forEach((p) => { const l = PRACTICE_LAYER[p.practice]; counts[p.sector + "|" + l] = (counts[p.sector + "|" + l] || 0) + 1; }); const max = Math.max(...Object.values(counts)); const [tip, setTip] = React.useState(null); const cell = (s, l, h) => { const n = counts[s + "|" + l.id] || 0; return (
setTip(`${s.toLowerCase()} · ${l.label.toLowerCase()} · ${n}`)} onPointerLeave={() => setTip(null)} style={{ height: h, display: "grid", placeItems: "center", border: "1px solid var(--border-default)", borderRadius: 4, background: n ? `color-mix(in oklab, var(--sx-accent) ${18 + (n / max) * 62}%, var(--surface-card))` : "transparent" }}> {n || "·"}
); }; if (wide && !mobile) { /* Fourteen sector names cannot sit horizontally at this column width, so the heads run at 45° — full names, ending at the column they label. */ return (
))} {layers.map((l) => ( {sectors.map((s) => )} ))}
{sectors.map((s) => (
{s.toLowerCase()}
{l.code}{l.label.toLowerCase()} {cell(s, l, 32)}

{tip || ""}

); } return (
))} {sectors.map((s) => ( {layers.map((l) => { const n = counts[s + "|" + l.id] || 0; return ( ); })} ))}
{layers.map((l) => ( {l.code}
{s}
setTip(`${s} · ${l.label.toLowerCase()} · ${n}`)} onPointerLeave={() => setTip(null)} style={{ height: mobile ? 30 : 38, display: "grid", placeItems: "center", border: "1px solid var(--border-default)", borderRadius: 4, background: n ? `color-mix(in oklab, var(--sx-accent) ${18 + (n / max) * 62}%, var(--surface-card))` : "transparent" }}> {n || "·"}
{D.layers.map((l) => {l.code} {l.label.toLowerCase()})}

{tip || ""}

); } /* ── solution reuse ─────────────────────────────────────────────────────── */ function SolutionReuse({ mobile, limit }) { const proven = D.solutions.map((s) => ({ ...s, n: (s.proof || []).length })).filter((s) => s.n > 1).sort((a, b) => b.n - a.n); const rows = proven.slice(0, limit || 99); const max = Math.max(...proven.map((r) => r.n)); return (
    {rows.map((r) => (
  • {r.name} {Array.from({ length: max }).map((_, i) => ( ))} {r.n}
  • ))}
{rows.length < proven.length && (

{proven.length - rows.length} further solutions carry more than one deployment — the full list is on the solutions page.

)}
); } /* ── honest comparison ──────────────────────────────────────────────────── */ function ComparisonMatrix({ mobile }) { const { columns, rows } = D.comparison; if (mobile) { return (
{rows.map((r) => (

{r.criterion}

{r.detail}

{columns.map((c, i) => (
{c.toLowerCase()}
{r.cells[i]}
))}
))}
); } const col = "minmax(0,1.5fr) repeat(4, minmax(0,0.85fr))"; return (
what you are weighing
{columns.map((c, i) => (
{c.toLowerCase()}
))}
{rows.map((r, ri) => (

{r.criterion}

{r.detail}

{r.cells.map((cell, i) => (
{cell}
))}
))}
); } /* ── engagement ladder ──────────────────────────────────────────────────── */ function EngagementLadder({ mobile }) { const [picked, setPicked] = React.useState(1); return (
{D.engagements.map((e, i) => ( ))}
); } /* ── four tiers we build, as a table view companion ─────────────────────── */ /* A pipeline is a sequence, so the colour is a journey, not a set of labels: accent ("detected") resolving into field ("measured, good") step by step. */ const PROC_STEPS = 6; const PROC_HUE = Array.from({ length: PROC_STEPS }, (_, i) => `color-mix(in oklab, var(--sx-accent) ${100 - Math.round((i * 100) / (PROC_STEPS - 1))}%, var(--sx-field))`); /* The engagement as a track, not a grid: six nodes on one rail, a pulse running the length of it, and each stage hanging off its own node. */ function ProcessSteps({ mobile }) { const steps = D.process; if (mobile) { return (
    {steps.map((p, i) => (
  1. {p.step}

    {p.title}

    {p.description}

  2. ))}
); } return (
    {steps.map((p, i) => (
  1. {p.step}

    {p.title}

    {p.description}

  2. ))}
); } Object.assign(window, { PipelineFigure, CapabilityTimeline, SectorLayerHeatmap, SolutionReuse, ComparisonMatrix, EngagementLadder, ProcessSteps, PRACTICE_LAYER }); /* ── the globe, without its ranked bar list ─────────────────────────────── */ function GlobeOnly({ mobile, height }) { const ref = React.useRef(null); React.useEffect(() => { const root = ref.current?.firstElementChild; if (!root) return; const hide = () => { const ul = root.querySelector("ul"); if (!ul) return; let el = ul; while (el.parentElement && el.parentElement !== root) el = el.parentElement; el.style.display = "none"; }; hide(); const t = setTimeout(hide, 500); return () => clearTimeout(t); }, []); return (
    {D.countries.map((c) => (
  • {c.flag && } {c.name}{c.total}
  • ))}
); } Object.assign(window, { GlobeOnly }); /* ── the stack as a core sample ─────────────────────────────────────────── */ const LAYER_TECH = { agents: ["LangChain", "LangGraph", "LangSmith", "LlamaIndex", "vLLM", "Ollama", "Model Context Protocol", "Claude API", "OpenAI API", "Tesseract", "PaddleOCR", "Azure Document Intelligence", "pgvector"], ml: ["PyTorch", "TensorFlow", "scikit-learn", "Hugging Face Transformers", "Ultralytics YOLO", "OpenCV", "ONNX Runtime", "OpenVINO", "XGBoost", "pandas", "NumPy"], edge: ["ESP32", "STM32", "Raspberry Pi (through Pi 5)", "ESP-IDF", "FreeRTOS", "MQTT", "Modbus RTU/TCP", "BLE", "LoRa", "ThingsBoard", "AWS IoT Core"], apps: ["React", "Next.js", "Tailwind CSS", "TypeScript", "TanStack Query", "Recharts", "React Native", "Flutter", "Expo"], data: ["PostgreSQL", "Oracle", "SQL Server", "MySQL", "MongoDB", "DynamoDB", "Redis", "Kafka", "RabbitMQ", "Celery"], core: ["Python", "Java", "C#", "C", "C++", "FastAPI", "Django", "Spring Boot", "ASP.NET Core", "Node.js", "REST", "gRPC"], }; /* Layers inherit the three family hues, so a layer is the same colour here as it is in the practice dome and the solutions register. */ const LAYER_HUE = { core: "var(--sx-layer-core)", data: "var(--sx-layer-core)", apps: "var(--sx-layer-connected)", edge: "var(--sx-layer-connected)", ml: "var(--sx-layer-intelligence)", agents: "var(--sx-layer-intelligence)" }; const LAYER_ORDER = ["core", "data", "apps", "edge", "ml", "agents"]; /* Six layers as a numbered chain, oldest first: each one is built on the one before it, which is the argument the figure has to make. The technologies belong to the open step — six lists at once is a wall, not a diagram. */ function StrataFigure({ mobile }) { const byId = {}; D.layers.forEach((l) => { byId[l.id] = l; }); const steps = LAYER_ORDER.map((id) => byId[id]).filter(Boolean); const [open, setOpen] = React.useState(steps[steps.length - 1].id); const g = steps.find((s) => s.id === open) || steps[0]; const hue = LAYER_HUE[g.id]; return (
{steps.map((l, i) => { const on = open === l.id; const h = LAYER_HUE[l.id]; return ( {!mobile && i < steps.length - 1 && ( )} ); })}

{g.label}

    {(LAYER_TECH[g.id] || []).map((it) => (
  • {it}
  • ))}

Read left to right: 01 is the layer everything else writes to, 06 is the newest work. Pick a step to see what we run in it.

); } Object.assign(window, { StrataFigure, LAYER_TECH });