HTML
<div
class="pb-gauge"
role="progressbar"
aria-valuenow="78"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Performance score"
style="--pb-gauge-value: 78"
>
<svg class="pb-gauge-svg" viewBox="0 0 200 170" aria-hidden="true">
<defs>
<linearGradient id="pb-gauge-grad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#f43f5e" />
<stop offset="50%" stop-color="#fb923c" />
<stop offset="100%" stop-color="#34d399" />
</linearGradient>
</defs>
<!-- track -->
<path
class="pb-gauge-track"
d="M 25 105 A 75 75 0 0 1 175 105"
fill="none"
stroke="rgba(255,255,255,0.06)"
stroke-width="12"
stroke-linecap="round"
/>
<!-- value arc -->
<path
class="pb-gauge-value"
d="M 25 105 A 75 75 0 0 1 175 105"
fill="none"
stroke="url(#pb-gauge-grad)"
stroke-width="12"
stroke-linecap="round"
pathLength="100"
/>
<!-- ticks -->
<text class="pb-gauge-tick" x="25" y="125" text-anchor="middle">0</text>
<text class="pb-gauge-tick" x="100" y="22" text-anchor="middle">50</text>
<text class="pb-gauge-tick" x="175" y="125" text-anchor="middle">100</text>
<!-- needle -->
<g class="pb-gauge-needle">
<line
x1="100"
y1="105"
x2="100"
y2="44"
stroke="#f0eeff"
stroke-width="3"
stroke-linecap="round"
/>
<circle cx="100" cy="105" r="7" fill="#fff" stroke="#15151d" stroke-width="2" />
</g>
<!-- score readout -->
<text class="pb-gauge-num" x="100" y="148" text-anchor="middle">78</text>
<text class="pb-gauge-label" x="100" y="164" text-anchor="middle">PERFORMANCE</text>
</svg>
</div> CSS
.pb-gauge {
width: 220px;
height: 170px;
font-family: system-ui, sans-serif;
}
.pb-gauge-svg {
width: 100%;
height: 100%;
display: block;
overflow: visible;
}
.pb-gauge-value {
stroke-dasharray: 100;
stroke-dashoffset: 100;
animation: pbGaugeArc 1.4s cubic-bezier(0.5, 0, 0.3, 1.2) forwards;
}
.pb-gauge-needle {
transform-origin: 100px 105px;
animation: pbGaugeSweep 1.4s cubic-bezier(0.5, 0, 0.3, 1.2) forwards;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.4));
}
.pb-gauge-tick {
font-family: "JetBrains Mono", monospace;
font-size: 10px;
font-weight: 700;
fill: #64748b;
letter-spacing: 0.04em;
}
.pb-gauge-num {
font-family: system-ui, sans-serif;
font-size: 30px;
font-weight: 700;
fill: #34d399;
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
}
.pb-gauge-label {
font-family: system-ui, sans-serif;
font-size: 10px;
font-weight: 600;
fill: #94a3b8;
letter-spacing: 0.16em;
}
@media (prefers-reduced-motion: reduce) {
.pb-gauge-value {
animation: none;
stroke-dashoffset: calc(100 - var(--pb-gauge-value, 0));
}
.pb-gauge-needle {
animation: none;
transform: rotate(calc(var(--pb-gauge-value, 0) * 1.8deg - 90deg));
}
}
@keyframes pbGaugeArc {
to { stroke-dashoffset: calc(100 - var(--pb-gauge-value, 0)); }
}
@keyframes pbGaugeSweep {
from { transform: rotate(-90deg); }
to { transform: rotate(calc(var(--pb-gauge-value, 0) * 1.8deg - 90deg)); }
}