Fix VO2 arrow: tip lands at exact value on arc centre-line, base points outward
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 5s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 9s

Previously the tip was sitting just outside the outer edge of the track,
making it hard to see exactly where it pointed. Now tipR=r (centre of the
coloured band) so the tip is precisely at the value's position, with a
narrow 5° spread for better precision.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 01:20:53 +01:00
parent bb9e8c59f4
commit 2ea691085f
+6 -7
View File
@@ -89,13 +89,13 @@ function Vo2MaxGauge({ value, birthYear, biologicalSex }) {
const cat = value != null ? getVo2Category(value, age, biologicalSex) : null const cat = value != null ? getVo2Category(value, age, biologicalSex) : null
// Small white triangle: base outside the arc, tip touching the outer edge — points inward // White arrow: tip lands exactly at the arc centre-line at the value's angle;
// base extends outside the track — unambiguously marks the precise position.
const arrowPts = value != null ? (() => { const arrowPts = value != null ? (() => {
const a = toAngle(Math.max(MIN, Math.min(MAX, value))) const a = toAngle(Math.max(MIN, Math.min(MAX, value)))
const outerEdge = r + sw / 2 // outer surface of the track const tipR = r // tip at centre of the coloured track
const tipR = outerEdge + 1 // tip just outside the track surface const baseR = r + sw / 2 + 9 // base well outside the outer edge
const baseR = outerEdge + 12 // base further out const s = 0.09 // half-spread ≈ 5° — narrow for precision
const s = 0.11 // half-spread ≈ 6°
const tipX = cx + tipR * Math.cos(a), tipY = cy - tipR * Math.sin(a) const tipX = cx + tipR * Math.cos(a), tipY = cy - tipR * Math.sin(a)
const b1x = cx + baseR * Math.cos(a + s), b1y = cy - baseR * Math.sin(a + s) const b1x = cx + baseR * Math.cos(a + s), b1y = cy - baseR * Math.sin(a + s)
const b2x = cx + baseR * Math.cos(a - s), b2y = cy - baseR * Math.sin(a - s) const b2x = cx + baseR * Math.cos(a - s), b2y = cy - baseR * Math.sin(a - s)
@@ -104,7 +104,6 @@ function Vo2MaxGauge({ value, birthYear, biologicalSex }) {
return ( return (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
{/* Extra top padding so the arrow doesn't clip at the top of the card */}
<svg width="140" height="92" viewBox="0 0 140 92"> <svg width="140" height="92" viewBox="0 0 140 92">
{/* Dark background track, slightly wider than the colour bands */} {/* Dark background track, slightly wider than the colour bands */}
<path d={arc(MIN, MAX)} stroke="#1f2937" strokeWidth={sw + 4} fill="none" strokeLinecap="butt" /> <path d={arc(MIN, MAX)} stroke="#1f2937" strokeWidth={sw + 4} fill="none" strokeLinecap="butt" />
@@ -120,7 +119,7 @@ function Vo2MaxGauge({ value, birthYear, biologicalSex }) {
) )
})} })}
{/* White arrow pointing inward at the value's position */} {/* White arrow: tip at exact value position on arc, base pointing outward */}
{arrowPts && <polygon points={arrowPts} fill="white" />} {arrowPts && <polygon points={arrowPts} fill="white" />}
{/* VO2 number, coloured by category */} {/* VO2 number, coloured by category */}