+
+ @if (description(); as descriptionText) {
+
{{ descriptionText }}
+ }
+
+
diff --git a/src/app/shared/motion/metric-bar/metric-bar.scss b/src/app/shared/motion/metric-bar/metric-bar.scss
new file mode 100644
index 0000000..4d86e91
--- /dev/null
+++ b/src/app/shared/motion/metric-bar/metric-bar.scss
@@ -0,0 +1,44 @@
+@use 'app/shared/motion/reveal' as reveal;
+
+:host {
+ display: block;
+}
+
+.metric-bar {
+ display: grid;
+ gap: var(--space-2);
+ @include reveal.reveal-target;
+}
+
+.metric-bar-header {
+ display: flex;
+ justify-content: space-between;
+ gap: var(--space-3);
+ font-size: var(--text-sm);
+}
+
+.metric-bar-description {
+ margin: 0;
+ color: var(--color-text-muted);
+ font-size: var(--text-sm);
+}
+
+.metric-bar-track {
+ height: 0.5rem;
+ overflow: hidden;
+ border-radius: var(--radius-pill);
+ background: var(--color-surface-muted);
+}
+
+.metric-bar-fill {
+ height: 100%;
+ border-radius: inherit;
+ background: var(--color-accent);
+ transition: width var(--duration-base) var(--ease-standard);
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .metric-bar-fill {
+ transition: none;
+ }
+}
diff --git a/src/app/shared/motion/metric-bar/metric-bar.spec.ts b/src/app/shared/motion/metric-bar/metric-bar.spec.ts
new file mode 100644
index 0000000..2708436
--- /dev/null
+++ b/src/app/shared/motion/metric-bar/metric-bar.spec.ts
@@ -0,0 +1,95 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { MetricBar } from './metric-bar';
+
+describe('MetricBar', () => {
+ async function createFixture(inputs: {
+ label: string;
+ value: number;
+ max?: number;
+ valueText?: string;
+ description?: string;
+ }): Promise