<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>VERSEVA Design · Example: Pulse analytics</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo:wght@500..700&family=Manrope:wght@400..700&family=JetBrains+Mono:wght@400..600&display=swap">
<link rel="stylesheet" href="../../tokens.css?v=1p3">
<link rel="stylesheet" href="../../components.css?v=1p3">
<style>
* { margin: 0; box-sizing: border-box; }
body { background: var(--bg); color: var(--text-1); font-family: var(--font-sans); font-size: 15px; line-height: 1.5; }
.crumb { position: sticky; top: 0; z-index: 10; background: var(--bg); display: flex; align-items: center; gap: var(--s-3); padding: var(--s-3) var(--s-5); border-bottom: 1px solid var(--hairline); font-size: 12.5px; color: var(--text-3); }
.crumb a { color: var(--text-2); text-decoration: none; font-family: var(--font-display); font-weight: var(--display-weight); }
.wrap { max-width: 72rem; margin: 0 auto; padding: var(--s-5) var(--s-5) var(--s-8); display: grid; gap: var(--s-5); }
.headrow { display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap; }
.headrow h1 { font-family: var(--font-display); font-weight: var(--heading-weight); font-size: 20px; letter-spacing: -0.015em; flex: 1; }
.grid2 { display: grid; grid-template-columns: 1.4fr 1fr; gap: var(--s-4); align-items: start; }
.grid3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--s-4); align-items: start; }
.panel { background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-md); padding: var(--s-4); box-shadow: var(--e-1); }
.panel h4 { font-size: 14px; font-weight: 600; }
.panel .sub { font-size: 11.5px; color: var(--text-3); }
.ringsrow { display: flex; gap: var(--s-5); flex-wrap: wrap; align-items: center; }
.stackcards { display: grid; gap: var(--s-4); }
footer { max-width: 72rem; margin: 0 auto; padding: 0 var(--s-5) var(--s-6); display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--s-3); color: var(--text-3); font-size: 12.5px; }
.badge-line { font-family: var(--font-display); font-weight: var(--display-weight); font-size: 13px; color: var(--text-2); }
@media (max-width: 860px) { .grid2 { grid-template-columns: 1fr; } }
.crumb a { display: inline-flex; align-items: center; gap: 8px; }
.crumb .vmark { width: 16px; height: 16px; flex: none; }
.crumb .codelink { margin-left: auto; }
</style>
</head>
<body>
<div class="crumb"><a href="../index.html"><svg class="vmark" viewBox="0 0 768 775.92" aria-hidden="true"><defs><clipPath id="vmk-a"><path d="M400 277h358v396.4H400z"/></clipPath><clipPath id="vmk-b"><path d="M10 97h550v576.4H10z"/></clipPath><clipPath id="vmk-c"><path d="M10 94.5h358V491H10z"/></clipPath><clipPath id="vmk-d"><path d="M208 94.5h550V671H208z"/></clipPath></defs><g clip-path="url(#vmk-a)"><path fill="currentColor" opacity=".55" d="M 558.605469 278.128906 C 555.410156 277.984375 552.164062 277.90625 548.96875 277.90625 C 466.261719 277.90625 407.433594 327.675781 401.222656 333.144531 L 400.800781 333.59375 C 400.800781 333.59375 587.386719 406.570312 718.046875 673.386719 C 718.046875 673.386719 767.757812 550.011719 756.207031 509.574219 C 740.042969 453 663.917969 362.328125 584.683594 279.359375 L 558.605469 278.128906"/></g><g clip-path="url(#vmk-b)"><path fill="currentColor" d="M 49.953125 673.386719 C 49.953125 673.386719 262.128906 253.59375 559.761719 253.59375 C 497.894531 190.457031 437.765625 134.679688 406.402344 106.136719 C 393.699219 94.574219 374.308594 94.574219 361.601562 106.136719 C 285.859375 175.0625 42.257812 402.917969 11.796875 509.574219 C 0.242188 550.011719 49.953125 673.386719 49.953125 673.386719"/></g><g clip-path="url(#vmk-c)"><path fill="currentColor" opacity=".55" d="M 209.394531 489.785156 C 212.589844 489.929688 215.835938 490.007812 219.03125 490.007812 C 301.738281 490.007812 360.566406 440.238281 366.777344 434.765625 L 367.199219 434.316406 C 367.199219 434.316406 180.613281 361.34375 49.953125 94.523438 C 49.953125 94.523438 0.242188 217.902344 11.792969 258.335938 C 27.957031 314.914062 104.082031 405.585938 183.316406 488.554688 L 209.394531 489.785156"/></g><g clip-path="url(#vmk-d)"><path fill="currentColor" d="M 718.046875 94.523438 C 718.046875 94.523438 505.871094 514.316406 208.238281 514.316406 C 270.105469 577.453125 330.234375 633.230469 361.597656 661.773438 C 374.300781 673.335938 393.691406 673.335938 406.398438 661.773438 C 482.140625 592.847656 725.742188 364.992188 756.203125 258.335938 C 767.757812 217.902344 718.046875 94.523438 718.046875 94.523438"/></g></svg><span>VERSEVA Design</span></a><span>examples</span><span>Pulse analytics</span><a class="btn btn-secondary sm codelink" href="../code.html?f=examples/pulse.html">Code</a></div>
<main class="wrap">
<div class="headrow">
<h1>Pulse</h1>
<div class="seg" id="range"><button type="button" aria-selected="true">12 weeks</button><button type="button" aria-selected="false">30 days</button></div>
<span class="badge ok">All gates green</span>
</div>
<div class="statrow">
<div class="stat">
<div class="num">45<i>%</i></div>
<div class="lbl">Win rate · 14 of 31 decided</div>
<div class="microbar"><i style="width:45%"></i></div>
</div>
<div class="stat">
<div class="num">112<i>/wk</i></div>
<div class="lbl">Quotes priced</div>
<div class="delta"><span class="trend good">↑ +8</span></div>
</div>
<div class="stat">
<div class="num">97<i>%</i></div>
<div class="lbl">Provenance · 46 of 47 lines</div>
<div class="microbar"><i style="width:97%"></i></div>
</div>
<div class="stat">
<div class="num">6<i>%</i></div>
<div class="lbl">Verify kill rate · 30 d</div>
<div class="delta"><span class="trend bad">↑ +2%</span></div>
</div>
</div>
<div class="grid2">
<div class="panel">
<h4>Priced vs won</h4>
<div class="sub mb-3">The demand line</div>
<div class="vizplot" id="vseries" style="height:170px"
data-labels-a="W1,W2,W3,W4,W5,W6,W7,W8,W9,W10,W11,W12" data-a1="62,70,66,74,82,78,88,95,90,98,104,112" data-a2="41,44,40,52,58,55,61,72,66,74,79,84" data-refa="76|AVG · 12 WK"
data-labels-b="D1,D5,D10,D15,D20,D25,D30" data-b1="88,95,90,98,104,112,118" data-b2="61,72,66,74,79,84,90" data-refb="94|AVG · 30 D"
data-names="Priced,Won">
<svg preserveAspectRatio="none" role="img" aria-label="Priced vs won"></svg>
</div>
<div class="xlabels mt-2" id="vxlab"></div>
<div class="vizlegend mt-2"><span><i></i> Priced</span><span><i class="alt"></i> Won</span></div>
<div class="takeaway">Won tracks priced with a one-week lag; the gap narrowed after rate card v18.</div>
</div>
<div class="stackcards">
<div class="panel">
<h4>Lanes this quarter</h4>
<div class="hbars mt-3">
<div class="hbar"><span class="hlabel">Bottling</span><span class="htrack"><i style="width:100%"></i></span><span class="hcount">48</span></div>
<div class="hbar"><span class="hlabel">Labeling</span><span class="htrack"><i style="width:65%"></i></span><span class="hcount">31</span></div>
<div class="hbar"><span class="hlabel">Kitting</span><span class="htrack"><i style="width:38%"></i></span><span class="hcount">18</span></div>
<div class="hbar"><span class="hlabel">Sourcing</span><span class="htrack"><i style="width:23%"></i></span><span class="hcount">11</span></div>
</div>
</div>
<div class="panel">
<h4>Desk activity</h4>
<div class="sub mb-3">12 weeks · today ringed</div>
<span class="actstrip" style="grid-auto-columns:1fr;width:100%">
<i data-l="1"></i><i data-l="2"></i><i></i><i data-l="1"></i><i data-l="3"></i><i data-l="2"></i><i data-l="1"></i><i data-l="2"></i><i data-l="3"></i><i data-l="2"></i><i data-l="3"></i><i data-l="3" class="today"></i>
</span>
</div>
</div>
</div>
<div class="grid3">
<div class="panel">
<h4>Readiness</h4>
<div class="sub mb-3">The actionable number leads</div>
<div class="ringsrow">
<span class="ring" role="img" aria-label="Capacity 72 percent ready">
<span class="rdial"><svg width="74" height="74" viewBox="0 0 74 74"><circle class="ringtrack" cx="37" cy="37" r="30"/><circle class="ringfill" cx="37" cy="37" r="30" stroke-dasharray="188.5" stroke-dashoffset="52.8"/></svg><span class="rv"><b>72</b></span></span>
<small class="rlbl">READY</small>
</span>
<span class="ring" role="img" aria-label="18 quote slots left this week">
<span class="rdial"><svg width="74" height="74" viewBox="0 0 74 74"><circle class="ringtrack" cx="37" cy="37" r="30"/><circle class="ringfill" cx="37" cy="37" r="30" stroke-dasharray="188.5" stroke-dashoffset="113.1"/></svg><span class="rv"><b>18</b></span></span>
<small class="rlbl">SLOTS LEFT</small>
</span>
</div>
</div>
<div class="panel">
<h4>In the gates</h4>
<div class="lrows mt-3">
<div class="lrow"><span class="tx"><b>Q-2081 · Northline</b><span class="metaline">verifying</span></span><span class="markstrip" title="6 of 9 cleared, now verifying" aria-label="6 of 9 gates cleared, current stage verifying"><i class="cleared"></i><i class="cleared"></i><i class="cleared"></i><i class="cleared"></i><i class="cleared"></i><i class="cleared"></i><i class="current"></i><i></i><i></i></span></div>
<div class="lrow"><span class="tx"><b>Q-2080 · Harbor</b><span class="metaline">sourcing blocked</span></span><span class="markstrip" title="2 cleared, attention on sourcing" aria-label="2 of 9 gates cleared, attention on sourcing"><i class="cleared"></i><i class="cleared"></i><i class="attention"></i><i></i><i></i><i></i><i></i><i></i><i></i></span></div>
<div class="lrow"><span class="tx"><b>Q-2079 · Crestpoint</b><span class="metaline">issued</span></span><span class="markstrip" title="9 of 9 cleared" aria-label="all 9 gates cleared"><i class="cleared"></i><i class="cleared"></i><i class="cleared"></i><i class="cleared"></i><i class="cleared"></i><i class="cleared"></i><i class="cleared"></i><i class="cleared"></i><i class="cleared"></i></span></div>
</div>
</div>
<div class="panel">
<h4>Records</h4>
<div class="lrows mt-3">
<div class="lrow"><span class="tx"><b>Fastest close</b><span class="metaline">was 3.1 d</span></span><span class="trend good">↓ −0.8 d</span></div>
<div class="lrow"><span class="tx"><b>Largest quote</b><span class="metaline">was 86,410</span></span><span class="trend good">↑ +12,300</span></div>
<div class="lrow"><span class="tx"><b>Rework rate</b><span class="metaline">was 4%</span></span><span class="trend bad">↑ +2%</span></div>
</div>
</div>
</div>
</main>
<footer>
<span>Pulse is fictional; this page is the VERSEVA Design data-viz kit assembled.</span>
<span class="badge-line">Built on VERSEVA Design</span>
</footer>
<script>
const lpts = (vals, W, H, pad, top) => vals.map((v, i) => [
pad + (W - pad * 2) * (vals.length === 1 ? 0.5 : i / (vals.length - 1)),
pad + (H - pad * 2) * (1 - v / top),
]);
const lp = (p) => p.map(([x, y], i) => `${i ? 'L' : 'M'}${x.toFixed(1)} ${y.toFixed(1)}`).join(' ');
const ap = (p, H, pad) => `M${p[0][0].toFixed(1)} ${H - pad} ${lp(p).slice(1)} L${p[p.length - 1][0].toFixed(1)} ${H - pad} Z`;
const plot = document.getElementById('vseries');
const plotSvg = plot.querySelector('svg');
const xlab = document.getElementById('vxlab');
const W = 320, PH = 170, PAD = 6;
let state = 'a';
let cur = null;
function renderPlot() {
const d = plot.dataset;
const labels = d[`labels${state.toUpperCase()}`].split(',');
const s1 = d[`${state}1`].split(',').map(Number);
const s2 = d[`${state}2`].split(',').map(Number);
const [refV, refLabel] = d[state === 'a' ? 'refa' : 'refb'].split('|');
const top = Math.max(...s1, ...s2, +refV);
const p1 = lpts(s1, W, PH, PAD, top);
const p2 = lpts(s2, W, PH, PAD, top);
const refY = PAD + (PH - PAD * 2) * (1 - refV / top);
cur = { labels, s1, s2, p1, p2 };
plotSvg.setAttribute('viewBox', `0 0 ${W} ${PH}`);
plotSvg.innerHTML = `<defs><linearGradient id="vg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="var(--data-3)" stop-opacity=".22"/><stop offset="1" stop-color="var(--data-3)" stop-opacity="0"/></linearGradient></defs>
<line class="refline" x1="${PAD}" y1="${refY.toFixed(1)}" x2="${W - PAD}" y2="${refY.toFixed(1)}"/>
<path d="${ap(p1, PH, PAD)}" fill="url(#vg)"/>
<path d="${lp(p1)}" fill="none" stroke="var(--data-3)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="${lp(p2)}" fill="none" stroke="var(--data-alt)" stroke-width="2" stroke-dasharray="5 4" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="${p1[p1.length - 1][0].toFixed(1)}" cy="${p1[p1.length - 1][1].toFixed(1)}" r="3" fill="var(--data-3)"/>
<circle cx="${p2[p2.length - 1][0].toFixed(1)}" cy="${p2[p2.length - 1][1].toFixed(1)}" r="3" fill="var(--data-alt)"/>
<g id="vcross" hidden></g>`;
let tag = plot.querySelector('.reftag');
if (!tag) { tag = document.createElement('span'); tag.className = 'reftag'; plot.append(tag); }
tag.textContent = refLabel;
tag.style.top = `${(refY / PH) * 100}%`;
xlab.innerHTML = `<span>${labels[0]}</span><span>${labels[labels.length - 1]}</span>`;
const tip = plot.querySelector('.viztip');
if (tip) tip.remove();
}
plotSvg.addEventListener('pointermove', (e) => {
if (!cur) return;
const box = plotSvg.getBoundingClientRect();
const x = ((e.clientX - box.left) / box.width) * W;
const i = Math.max(0, Math.min(cur.labels.length - 1, Math.round(((x - PAD) / (W - PAD * 2)) * (cur.labels.length - 1))));
const cross = plotSvg.querySelector('#vcross');
cross.hidden = false;
cross.innerHTML = `<line x1="${cur.p1[i][0]}" y1="${PAD}" x2="${cur.p1[i][0]}" y2="${PH - PAD}" stroke="var(--text-3)" stroke-width="1" opacity=".5"/>
<circle cx="${cur.p1[i][0]}" cy="${cur.p1[i][1]}" r="4" fill="var(--data-3)" stroke="var(--surface)" stroke-width="2"/>
<circle cx="${cur.p2[i][0]}" cy="${cur.p2[i][1]}" r="4" fill="var(--data-alt)" stroke="var(--surface)" stroke-width="2"/>`;
let tip = plot.querySelector('.viztip');
if (!tip) { tip = document.createElement('div'); tip.className = 'viztip'; plot.append(tip); }
const names = plot.dataset.names.split(',');
tip.innerHTML = `<b>${cur.labels[i]}</b><span>${names[0]} ${cur.s1[i]}</span><span>${names[1]} ${cur.s2[i]}</span>`;
});
plotSvg.addEventListener('pointerleave', () => {
plotSvg.querySelector('#vcross').hidden = true;
const tip = plot.querySelector('.viztip');
if (tip) tip.remove();
});
document.querySelectorAll('#range button').forEach((b, i) => b.addEventListener('click', () => {
document.querySelectorAll('#range button').forEach((x) => x.setAttribute('aria-selected', String(x === b)));
state = i === 0 ? 'a' : 'b';
renderPlot();
}));
renderPlot();
</script>
</body>
</html>