diff --git a/site/src/scripts/techapi.js b/site/src/scripts/techapi.js index f080ad5cdb1..258a167db23 100644 --- a/site/src/scripts/techapi.js +++ b/site/src/scripts/techapi.js @@ -289,7 +289,6 @@ function countUp(node, target, opts = {}) { const ys = (t) => VH - PAD - ((t - minTotal) / range) * (VH - 2 * PAD); const line = points.map((p, i) => `${i ? "L" : "M"}${xs(i).toFixed(1)} ${ys(p.total).toFixed(1)}`).join(" "); const area = `${line} L${xs(points.length - 1).toFixed(1)} ${VH} L${xs(0).toFixed(1)} ${VH} Z`; - const last = points[points.length - 1]; chartEl.innerHTML = ` @@ -297,9 +296,7 @@ function countUp(node, target, opts = {}) { - - ${esc(points[0].when)} · ${minTotal.toLocaleString()} - ${esc(last.when)} · ${last.total.toLocaleString()}`; + `; // Show every sync (newest first), growth-first. The list scrolls (CSS // max-height) so the full history stays reachable without a giant section. @@ -318,6 +315,47 @@ function countUp(node, target, opts = {}) { ${esc(changes)}${tag ? ` · ${esc(tag)}` : ""} `; }).join(""); + + // Hover the curve: snap to the nearest sync and show its date + total + delta. + const hover = document.createElement("div"); + hover.className = "history-hover"; + hover.hidden = true; + hover.innerHTML = `
`; + chartEl.appendChild(hover); + const hLine = hover.querySelector(".hh-line"); + const hDot = hover.querySelector(".hh-dot"); + const hTip = hover.querySelector(".hh-tip"); + + function moveHover(clientX) { + const rect = chartEl.getBoundingClientRect(); + if (!rect.width) return; + const relX = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width)); + const i = Math.round(relX * (points.length - 1)); + const p = points[i]; + const px = (xs(i) / VW) * rect.width; + const py = (ys(p.total) / VH) * rect.height; + hover.hidden = false; + hLine.style.left = px + "px"; + hDot.style.left = px + "px"; + hDot.style.top = py + "px"; + const chg = p.changes && p.changes.length + ? p.changes.map((row) => `${shortLabel[row.key]} ${formatDelta(row.delta)}`).join(", ") + : ""; + hTip.innerHTML = `${esc(p.when)}` + + `${p.total.toLocaleString()} records` + + `${p.baseline ? "baseline" : esc(formatDelta(p.delta))}` + + (chg ? `${esc(chg)}` : ""); + const tipW = hTip.offsetWidth || 150; + let tx = px + 14; + if (tx + tipW > rect.width) tx = px - tipW - 14; + hTip.style.left = Math.max(4, tx) + "px"; + hTip.style.top = Math.min(rect.height - (hTip.offsetHeight || 70) - 4, Math.max(4, py - 24)) + "px"; + } + chartEl.onmousemove = (e) => moveHover(e.clientX); + chartEl.onmouseleave = () => { hover.hidden = true; }; + chartEl.ontouchstart = chartEl.ontouchmove = (e) => { + if (e.touches[0]) moveHover(e.touches[0].clientX); + }; } const fmtWhen = (date) => date diff --git a/site/src/styles/techapi.css b/site/src/styles/techapi.css index 22f68ba1436..20a1920a9ad 100644 --- a/site/src/styles/techapi.css +++ b/site/src/styles/techapi.css @@ -326,18 +326,29 @@ code, .mono { font-family: var(--mono); } var(--surface-2); } .history-svg { display: block; width: 100%; height: 100%; } -.history-cap { - position: absolute; - bottom: 8px; - font-family: var(--mono); - font-size: 10.5px; - color: var(--muted); - background: color-mix(in srgb, var(--surface-2) 72%, transparent); - padding: 1px 6px; - border-radius: 3px; -} -.history-cap-lo { left: 9px; } -.history-cap-hi { right: 9px; color: var(--accent-text); } +.history-hover { position: absolute; inset: 0; pointer-events: none; z-index: 2; } +.hh-line { + position: absolute; top: 0; bottom: 0; width: 1px; + background: color-mix(in srgb, var(--accent) 55%, transparent); + transform: translateX(-0.5px); +} +.hh-dot { + position: absolute; width: 10px; height: 10px; border-radius: 50%; + background: var(--accent); border: 2px solid var(--surface-2); + transform: translate(-50%, -50%); + box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 18%, transparent); +} +.hh-tip { + position: absolute; min-width: 130px; display: grid; gap: 2px; + padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: 6px; + background: var(--surface); box-shadow: var(--code-shadow); + font-family: var(--mono); font-size: 11.5px; white-space: nowrap; line-height: 1.35; +} +.hh-tip b { font-size: 12.5px; color: var(--fg); } +.hh-tip span { color: var(--muted); } +.hh-tip .hh-delta { color: var(--accent-text); font-weight: 600; } +.hh-tip .hh-delta.is-negative { color: var(--err); } +.hh-tip .hh-chg { color: var(--faint); font-size: 10.5px; } .history-empty { position: absolute; inset: 0;