feat(design): Phase 5 — cost dashboard refresh (#199)

* feat(design): Phase 5 — cost dashboard refresh Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(design): prevent cost metrics wrapping Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Juan Manuel Servera committed May 25, 2026 at 22:50 UTC b0bd6b5f929227a9aa5ab2a1f492cebd235f25b1
14 files changed +145 -99
.squad/agents/amy/history.md
+2
@@ -18,3 +18,5 @@
18 - 2026-05-25 Home visual verification runs through `node scripts/design/verify-visual.mjs --base http://localhost:1313/SquadScope/` after starting `/tmp/hugo server -D --bind 0.0.0.0 --port 1313 --baseURL http://localhost:1313/SquadScope/`.
19 - 2026-05-25 Phase 4 article layout: keep analyzer Markdown clean and derive article components from Hugo partials/render hooks (`article-content`, `repo-card`, `render-link`) instead of raw HTML in content.
20 - 2026-05-25 Weekly press context uses canonical `## Where Industry Meets Code`; wrapping it in a Hugo partial preserves clean heading flow while giving it complementary landmark styling.
21 +- 2026-05-25 Phase 5 cost transparency lives in the About page via `{{< cost-dashboard >}}`; keep the shortcode as a thin wrapper over `layouts/partials/cost-dashboard.html` so direct `/dashboard/` links can reuse it.
22 +- 2026-05-25 PR #199 follow-up: cost dashboard metric lists live in `assets/css/common/cost-dashboard.css`; strengthen `.post-content .cost-dashboard__metrics` specificity so global `.md-content dl/dt/dd` flex rules cannot override dashboard grid layout in article content.
.squad/decisions.md
+6
@@ -51,6 +51,12 @@
51 - OQ4: Star threshold (50 proposed)
52 - OQ8: Copilot usage limits in automation
53
54 +## Cost Transparency Placement (2026-05-25)
55 +
56 +**Decision:** The AI pipeline cost dashboard is part of `/about/` under a Pipeline transparency section, with the existing `/dashboard/` page retained as a direct audit link that reuses the same shortcode.
57 +
58 +**Rationale:** Cost reporting is operational transparency, not a primary navigation destination or product dashboard. Keeping it on About matches the editorial-restrained redesign while preserving the old URL for references.
59 +
60 ## Copilot CLI in GitHub Actions (2026-05-18)
61
62 **Requestor:** jmservera
assets/css/common/cost-dashboard.css new
+46
@@ -0,0 +1,46 @@
1 +.cost-dashboard { margin: var(--space-6) 0; padding: var(--space-5); color: var(--color-text); background: var(--color-surface); border: var(--size-border-thin) solid var(--color-border); border-radius: var(--radius-lg); }
2 +.cost-dashboard__header { max-width: var(--prose-measure); margin-bottom: var(--space-5); }
3 +.cost-dashboard__header h2 { margin: var(--space-1) 0 var(--space-2); font-size: var(--text-2xl); line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); }
4 +.cost-dashboard__header p:last-child, .cost-dashboard__total span, .cost-dashboard__trend p, .cost-dashboard__metrics dt, .cost-dashboard__table caption { color: var(--color-text-muted); }
5 +.cost-dashboard__summary { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: var(--space-4); align-items: stretch; margin-bottom: var(--space-5); }
6 +.cost-dashboard__total, .cost-dashboard__metrics { padding: var(--space-4); border: var(--size-border-thin) solid var(--color-border); border-radius: var(--radius-md); background: var(--color-bg); }
7 +.cost-dashboard__total { display: grid; gap: var(--space-2); }
8 +.cost-dashboard__label, .cost-dashboard__metrics dt, .cost-dashboard__table caption, .cost-dashboard__details summary, .cost-dashboard__trend figcaption { font-size: var(--text-xs); font-weight: 600; line-height: var(--leading-normal); letter-spacing: var(--tracking-nav); text-transform: uppercase; }
9 +.cost-dashboard__total strong { color: var(--color-accent); font-size: var(--text-4xl); line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); }
10 +.cost-dashboard__budget-meter { display: block; overflow: hidden; block-size: var(--space-2); margin-top: var(--space-2); background: var(--color-accent-muted); border-radius: var(--radius-full); }
11 +.cost-dashboard__budget-meter span { display: block; block-size: 100%; background: var(--color-accent); border-radius: inherit; }
12 +.cost-dashboard__metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-3); margin: 0; }
13 +.post-content .cost-dashboard__metrics { display: grid; flex-wrap: initial; }
14 +.cost-dashboard__metrics div { display: grid; gap: var(--space-1); }
15 +.cost-dashboard__metrics dd { margin: 0; font-family: var(--font-mono); font-size: var(--text-lg); font-weight: 600; }
16 +.post-content .cost-dashboard__metrics dt,
17 +.post-content .cost-dashboard__metrics dd { width: auto; padding-inline-start: 0; overflow-wrap: normal; word-break: normal; }
18 +.post-content .cost-dashboard__metrics dd { white-space: nowrap; }
19 +.cost-dashboard__trend { display: grid; gap: var(--space-3); margin: 0 0 var(--space-5); padding: var(--space-4); border-inline-start: var(--space-1) solid var(--color-accent); background: var(--color-accent-muted); border-radius: var(--radius-md); }
20 +.cost-dashboard__trend svg { inline-size: 100%; block-size: var(--space-7); color: var(--color-accent); }
21 +.cost-dashboard__trend polyline { fill: none; stroke: currentColor; stroke-width: var(--size-border-thin); stroke-linecap: round; stroke-linejoin: round; }
22 +.cost-dashboard__trend p { margin: 0; }
23 +.cost-dashboard__table-wrap { overflow-x: auto; border: var(--size-border-thin) solid var(--color-border); border-radius: var(--radius-md); background: var(--color-surface); -webkit-overflow-scrolling: touch; }
24 +.cost-dashboard__table-wrap:focus-visible { outline: var(--size-focus-ring) solid var(--color-accent); outline-offset: var(--space-1); }
25 +.cost-dashboard__table { inline-size: 100%; min-inline-size: 42rem; margin: 0; border-collapse: collapse; font-size: var(--text-sm); }
26 +.cost-dashboard__table caption { padding: var(--space-3) var(--space-4); text-align: left; background: var(--color-bg); border-bottom: var(--size-border-thin) solid var(--color-border); }
27 +.cost-dashboard__table th, .cost-dashboard__table td { padding: var(--space-3) var(--space-4); text-align: left; border-bottom: var(--size-border-thin) solid var(--color-border); }
28 +.cost-dashboard__table tbody tr:last-child th, .cost-dashboard__table tbody tr:last-child td { border-bottom: 0; }
29 +.cost-dashboard__table th[scope="row"] { font-weight: 600; }
30 +.cost-dashboard__table td:not(:last-child) { font-family: var(--font-mono); }
31 +.cost-dashboard__details { margin-top: var(--space-4); border: var(--size-border-thin) solid var(--color-border); border-radius: var(--radius-md); background: var(--color-surface); }
32 +.cost-dashboard__details summary { padding: var(--space-3) var(--space-4); color: var(--color-text); }
33 +.cost-dashboard__details .cost-dashboard__table { border-top: var(--size-border-thin) solid var(--color-border); }
34 +.cost-dashboard__table--compact { min-inline-size: 32rem; }
35 +@media (max-width: 700px) { .cost-dashboard { padding: var(--space-4); } .cost-dashboard__summary, .cost-dashboard__metrics { grid-template-columns: 1fr; } .cost-dashboard__total strong { font-size: var(--text-3xl); } }
36 +
37 +.cost-dashboard__metrics dt,
38 +.cost-dashboard__metrics dd {
39 + width: auto;
40 + padding-inline-start: 0;
41 +}
42 +
43 +.cost-dashboard__metrics dt {
44 + overflow-wrap: normal;
45 + word-break: normal;
46 +}
content/about/_index.md
+6
@@ -9,3 +9,9 @@ description = 'Learn what SquadScope publishes and how to follow its GitHub tren
9 SquadScope turns GitHub activity into readable weekly, monthly, and yearly trend reports for busy developers and technical leaders.
10
11 The project favors editorial signal over dashboard noise: what mattered this week, what is merely loud, and what is missing from the conversation.
12 +
13 +## Pipeline transparency
14 +
15 +SquadScope uses an automated AI analysis pipeline to turn crawl data into editorial briefs. The cost report below publishes the estimated token spend behind that pipeline so readers can see the operational footprint alongside the analysis.
16 +
17 +{{< cost-dashboard >}}
content/dashboard.md
+5 -1
@@ -1,5 +1,9 @@
1 ---
2 -title: "Cost Dashboard"
2 +title: "AI Pipeline Cost"
3 layout: "single"
4 +summary: "Estimated token-usage cost for SquadScope's AI analysis pipeline."
5 ---
6 +
7 +This page mirrors the transparency section on [About](/SquadScope/about/) for direct links and audits.
8 +
9 {{< cost-dashboard >}}
data/metrics/cost-summary.json
+5 -4
@@ -1,9 +1,10 @@
1 {
2 "weeks": [
3 - {"week": "2026-W19", "cost": 0.32, "tokens_in": 82000, "tokens_out": 3500, "model": "claude-sonnet-4"},
4 - {"week": "2026-W20", "cost": 0.28, "tokens_in": 75000, "tokens_out": 3200, "model": "claude-sonnet-4"},
5 - {"week": "2026-W21", "cost": 0.35, "tokens_in": 86000, "tokens_out": 4000, "model": "claude-sonnet-4"}
3 + {"week": "2026-W19", "cost": 0.32, "tokens_in": 82000, "tokens_out": 3500, "model": "claude-sonnet-4", "stages": [{"stage": "analysis", "cost": 0.32, "tokens_in": 82000, "tokens_out": 3500, "model": "claude-sonnet-4"}]},
4 + {"week": "2026-W20", "cost": 0.28, "tokens_in": 75000, "tokens_out": 3200, "model": "claude-sonnet-4", "stages": [{"stage": "analysis", "cost": 0.28, "tokens_in": 75000, "tokens_out": 3200, "model": "claude-sonnet-4"}]},
5 + {"week": "2026-W21", "cost": 0.35, "tokens_in": 86000, "tokens_out": 4000, "model": "claude-sonnet-4", "stages": [{"stage": "analysis", "cost": 0.35, "tokens_in": 86000, "tokens_out": 4000, "model": "claude-sonnet-4"}]},
6 + {"week": "2026-W22", "cost": 0.317232, "tokens_in": 88769, "tokens_out": 3395, "model": "copilot-default", "stages": [{"stage": "analysis", "cost": 0.317232, "tokens_in": 88769, "tokens_out": 3395, "model": "copilot-default", "estimated": true}]}
7 ],
7 - "cumulative_cost": 0.95,
8 + "cumulative_cost": 1.267232,
9 "budget_limit": 10.00
10 }
layouts/partials/cost-dashboard.html new
+63
@@ -0,0 +1,63 @@
1 +{{- $data := index hugo.Data "metrics" "cost-summary" -}}
2 +{{- if not $data -}}
3 +<div class="cost-dashboard" aria-labelledby="cost-dashboard-title">
4 + <p>No cost data available yet.</p>
5 +</div>
6 +{{- else -}}
7 +{{- $weeks := $data.weeks | default (slice) -}}
8 +{{- $weekCount := len $weeks -}}
9 +{{- $cumulative := float ($data.cumulative_cost | default 0) -}}
10 +{{- $budget := float ($data.budget_limit | default 1.0) -}}
11 +{{- $pctUsed := 0.0 -}}
12 +{{- if gt $budget 0.0 -}}{{- $pctUsed = mul (div $cumulative $budget) 100 -}}{{- end -}}
13 +{{- $budgetWidth := $pctUsed -}}
14 +{{- if gt $budgetWidth 100.0 -}}{{- $budgetWidth = 100.0 -}}{{- end -}}
15 +{{- $maxCost := 0.0 -}}{{- $minCost := 0.0 -}}{{- $totalInput := 0 -}}{{- $totalOutput := 0 -}}
16 +{{- $firstCost := 0.0 -}}{{- $lastCost := 0.0 -}}{{- $firstWeek := "" -}}{{- $lastWeek := "" -}}
17 +{{- if gt $weekCount 0 -}}{{- $first := index $weeks 0 -}}{{- $last := index $weeks (sub $weekCount 1) -}}{{- $firstCost = float ($first.cost | default 0) -}}{{- $lastCost = float ($last.cost | default 0) -}}{{- $firstWeek = $first.week -}}{{- $lastWeek = $last.week -}}{{- $minCost = $firstCost -}}{{- end -}}
18 +{{- range $weeks -}}{{- $cost := float (.cost | default 0) -}}{{- if gt $cost $maxCost -}}{{- $maxCost = $cost -}}{{- end -}}{{- if lt $cost $minCost -}}{{- $minCost = $cost -}}{{- end -}}{{- $totalInput = add $totalInput (int (.tokens_in | default 0)) -}}{{- $totalOutput = add $totalOutput (int (.tokens_out | default 0)) -}}{{- end -}}
19 +{{- $delta := sub $lastCost $firstCost -}}{{- $trendState := "stable" -}}{{- if gt $delta 0.01 -}}{{- $trendState = "rising" -}}{{- else if lt $delta -0.01 -}}{{- $trendState = "falling" -}}{{- end -}}
20 +{{- $deltaAbs := $delta -}}{{- if lt $deltaAbs 0.0 -}}{{- $deltaAbs = mul $deltaAbs -1 -}}{{- end -}}
21 +{{- $trendPct := 0.0 -}}{{- if gt $firstCost 0.0 -}}{{- $trendPct = mul (div $deltaAbs $firstCost) 100 -}}{{- end -}}
22 +{{- $points := "" -}}{{- if gt $weekCount 1 -}}{{- $rangeCost := sub $maxCost $minCost -}}{{- $xStep := div 100.0 (float (sub $weekCount 1)) -}}{{- range $index, $week := $weeks -}}{{- $normalized := 0.5 -}}{{- if gt $rangeCost 0.0 -}}{{- $normalized = div (sub (float ($week.cost | default 0)) $minCost) $rangeCost -}}{{- end -}}{{- $x := mul (float $index) $xStep -}}{{- $y := sub 88.0 (mul $normalized 76.0) -}}{{- $points = printf "%s %.2f,%.2f" $points $x $y -}}{{- end -}}{{- end -}}
23 +<section class="cost-dashboard" aria-labelledby="cost-dashboard-title">
24 + <div class="cost-dashboard__header">
25 + <p class="section-topline">Pipeline transparency</p>
26 + <h2 id="cost-dashboard-title">AI pipeline cost</h2>
27 + <p>Estimated token-usage cost for the automated analysis pipeline. The numbers are here for accountability, not performance theater.</p>
28 + </div>
29 + <div class="cost-dashboard__summary" aria-label="Cost summary">
30 + <div class="cost-dashboard__total">
31 + <span class="cost-dashboard__label">Total estimated spend</span>
32 + <strong>${{ printf "%.2f" $cumulative }}</strong>
33 + <span>{{ printf "%.1f" $pctUsed }}% of the ${{ printf "%.2f" $budget }} transparency budget</span>
34 + <span class="cost-dashboard__budget-meter" aria-hidden="true"><span style="inline-size: {{ printf "%.2f" $budgetWidth }}%;"></span></span>
35 + </div>
36 + <dl class="cost-dashboard__metrics">
37 + <div><dt>Weeks tracked</dt><dd>{{ $weekCount }}</dd></div>
38 + <div><dt>Input tokens</dt><dd>{{ $totalInput | lang.FormatNumber 0 }}</dd></div>
39 + <div><dt>Output tokens</dt><dd>{{ $totalOutput | lang.FormatNumber 0 }}</dd></div>
40 + </dl>
41 + </div>
42 + {{- if gt $weekCount 1 }}
43 + <figure class="cost-dashboard__trend" aria-labelledby="cost-trend-title" aria-describedby="cost-trend-summary">
44 + <figcaption id="cost-trend-title">Recent weekly direction</figcaption>
45 + <svg viewBox="0 0 100 100" role="img" aria-labelledby="cost-trend-svg-title cost-trend-svg-desc" preserveAspectRatio="none">
46 + <title id="cost-trend-svg-title">Weekly cost trend from {{ $firstWeek }} to {{ $lastWeek }}</title>
47 + <desc id="cost-trend-svg-desc">Costs are {{ $trendState }} by ${{ printf "%.2f" $deltaAbs }} across {{ $weekCount }} tracked weeks.</desc>
48 + <polyline points="{{ strings.TrimSpace $points }}" vector-effect="non-scaling-stroke" />
49 + </svg>
50 + <p id="cost-trend-summary">Costs are <strong>{{ $trendState }}</strong>: ${{ printf "%.2f" $firstCost }} in {{ $firstWeek }} to ${{ printf "%.2f" $lastCost }} in {{ $lastWeek }}{{ if gt $trendPct 0.0 }} ({{ printf "%.1f" $trendPct }}% change){{ end }}.</p>
51 + </figure>
52 + {{- end }}
53 + <div class="cost-dashboard__table-wrap" tabindex="0">
54 + <table class="cost-dashboard__table">
55 + <caption>Cost by week and pipeline stage</caption>
56 + <thead><tr><th scope="col">Week / stage</th><th scope="col">Cost</th><th scope="col">Input tokens</th><th scope="col">Output tokens</th><th scope="col">Model</th></tr></thead>
57 + <tbody>{{- range $weeks }}{{- $week := . -}}{{- if .stages }}{{- range .stages }}<tr><th scope="row">{{ $week.week }} · {{ .stage | default "analysis" }}</th><td>${{ printf "%.2f" (float (.cost | default 0)) }}</td><td>{{ int (.tokens_in | default 0) | lang.FormatNumber 0 }}</td><td>{{ int (.tokens_out | default 0) | lang.FormatNumber 0 }}</td><td>{{ .model | default $week.model }}</td></tr>{{- end }}{{- else }}<tr><th scope="row">{{ .week }} · analysis</th><td>${{ printf "%.2f" (float (.cost | default 0)) }}</td><td>{{ int (.tokens_in | default 0) | lang.FormatNumber 0 }}</td><td>{{ int (.tokens_out | default 0) | lang.FormatNumber 0 }}</td><td>{{ .model }}</td></tr>{{- end }}{{- end }}</tbody>
58 + </table>
59 + </div>
60 + {{- $models := dict -}}{{- $modelCosts := dict -}}{{- range $weeks -}}{{- $model := .model | default "unknown" -}}{{- $models = merge $models (dict $model (add (index $models $model | default 0) 1)) -}}{{- $modelCosts = merge $modelCosts (dict $model (add (float (index $modelCosts $model | default 0)) (float (.cost | default 0)))) -}}{{- end -}}
61 + {{- if gt (len $models) 0 }}<details class="cost-dashboard__details"><summary>Model breakdown</summary><table class="cost-dashboard__table cost-dashboard__table--compact"><caption>Model usage across tracked weeks</caption><thead><tr><th scope="col">Model</th><th scope="col">Weeks used</th><th scope="col">Estimated cost</th><th scope="col">Share</th></tr></thead><tbody>{{- range $model, $count := $models }}<tr><th scope="row">{{ $model }}</th><td>{{ $count }}</td><td>${{ printf "%.2f" (float (index $modelCosts $model | default 0)) }}</td><td>{{ printf "%.0f" (mul (div (float $count) (float $weekCount)) 100) }}%</td></tr>{{- end }}</tbody></table></details>{{- end }}
62 +</section>
63 +{{- end -}}
layouts/shortcodes/cost-dashboard.html
+1 -94
@@ -1,94 +1 @@
1 -{{- $data := site.Data.metrics.cost_summary -}}
2 -{{- if not $data -}}
3 -<div class="cost-dashboard"><p>No cost data available yet.</p></div>
4 -{{- else -}}
5 -{{- $weeks := $data.weeks -}}
6 -{{- $cumulative := $data.cumulative_cost -}}
7 -{{- $budget := $data.budget_limit | default 1.0 -}}
8 -{{- $pctUsed := mul (div $cumulative $budget) 100 -}}
9 -
10 -<div class="cost-dashboard">
11 - <h2>💰 Cost Dashboard</h2>
12 -
13 - <!-- Cumulative Spend -->
14 - <section class="cumulative-spend">
15 - <h3>Cumulative Spend</h3>
16 - <p><strong>${{ printf "%.2f" $cumulative }}</strong> of ${{ printf "%.2f" $budget }} budget ({{ printf "%.1f" $pctUsed }}% used)</p>
17 - <svg width="100%" height="30" viewBox="0 0 400 30" xmlns="http://www.w3.org/2000/svg">
18 - <rect x="0" y="5" width="400" height="20" rx="4" fill="#e0e0e0"/>
19 - <rect x="0" y="5" width="{{ mul 4 $pctUsed }}" height="20" rx="4" fill="{{ if lt $pctUsed 50.0 }}#4caf50{{ else if lt $pctUsed 80.0 }}#ff9800{{ else }}#f44336{{ end }}"/>
20 - </svg>
21 - </section>
22 -
23 - <!-- Weekly Cost Trend -->
24 - <section class="weekly-trend">
25 - <h3>Weekly Cost Trend</h3>
26 - <table>
27 - <thead>
28 - <tr>
29 - <th>Week</th>
30 - <th>Cost</th>
31 - <th>Tokens In</th>
32 - <th>Tokens Out</th>
33 - <th>Model</th>
34 - <th>Bar</th>
35 - </tr>
36 - </thead>
37 - <tbody>
38 - {{- $maxCost := 0.0 -}}
39 - {{- range $weeks -}}
40 - {{- if gt .cost $maxCost }}{{ $maxCost = .cost }}{{ end -}}
41 - {{- end -}}
42 - {{- range $weeks }}
43 - <tr>
44 - <td>{{ .week }}</td>
45 - <td>${{ printf "%.2f" .cost }}</td>
46 - <td>{{ .tokens_in | lang.FormatNumber 0 }}</td>
47 - <td>{{ .tokens_out | lang.FormatNumber 0 }}</td>
48 - <td>{{ .model }}</td>
49 - <td>
50 - {{- $barWidth := mul (div .cost $maxCost) 100 -}}
51 - <svg width="120" height="16" xmlns="http://www.w3.org/2000/svg">
52 - <rect x="0" y="2" width="{{ $barWidth }}%" height="12" rx="2" fill="#2196f3"/>
53 - </svg>
54 - </td>
55 - </tr>
56 - {{- end }}
57 - </tbody>
58 - </table>
59 - </section>
60 -
61 - <!-- Model Usage Distribution -->
62 - <section class="model-distribution">
63 - <h3>Model Usage Distribution</h3>
64 - {{- $models := dict -}}
65 - {{- range $weeks -}}
66 - {{- $prev := index $models .model | default 0 -}}
67 - {{- $models = merge $models (dict .model (add $prev 1)) -}}
68 - {{- end -}}
69 - <table>
70 - <thead>
71 - <tr><th>Model</th><th>Weeks Used</th><th>Share</th></tr>
72 - </thead>
73 - <tbody>
74 - {{- $total := len $weeks -}}
75 - {{- range $model, $count := $models }}
76 - <tr>
77 - <td>{{ $model }}</td>
78 - <td>{{ $count }}</td>
79 - <td>{{ printf "%.0f" (mul (div (float $count) (float $total)) 100) }}%</td>
80 - </tr>
81 - {{- end }}
82 - </tbody>
83 - </table>
84 - </section>
85 -</div>
86 -
87 -<style>
88 -.cost-dashboard { font-family: system-ui, sans-serif; max-width: 800px; }
89 -.cost-dashboard section { margin: 1.5rem 0; }
90 -.cost-dashboard table { border-collapse: collapse; width: 100%; }
91 -.cost-dashboard th, .cost-dashboard td { border: 1px solid #ddd; padding: 0.5rem; text-align: left; }
92 -.cost-dashboard th { background: #f5f5f5; }
93 -</style>
94 -{{- end -}}
1 +{{ partial "cost-dashboard.html" . }}
screenshots/design-verification/2026-05-25/phase5/about-desktop-light.png
Binary files /dev/null and b/screenshots/design-verification/2026-05-25/phase5/about-desktop-light.png differ
screenshots/design-verification/2026-05-25/phase5/about-mobile-dark.png
Binary files /dev/null and b/screenshots/design-verification/2026-05-25/phase5/about-mobile-dark.png differ
screenshots/design-verification/2026-05-25/phase5/about-mobile-light.png
Binary files /dev/null and b/screenshots/design-verification/2026-05-25/phase5/about-mobile-light.png differ
screenshots/design-verification/2026-05-25/phase5/about-wide-dark.png
Binary files /dev/null and b/screenshots/design-verification/2026-05-25/phase5/about-wide-dark.png differ
scripts/design/verify-visual.mjs
+1
@@ -46,6 +46,7 @@ const THEMES = ['light', 'dark'];
46
47 const PAGES = [
48 { key: 'home', path: '/' },
49 + { key: 'about', path: '/about/' },
50 { key: 'weekly-w22', path: '/weekly/2026/w22/' },
51 { key: 'monthly-may', path: '/monthly/2026/05/' },
52 { key: 'yearly-2026', path: '/yearly/2026/' },
tests/visual/visual.spec.mjs
+10
@@ -33,6 +33,16 @@ test('home — full page', async ({ page }) => {
33 await expect(page).toHaveScreenshot('home.png', { fullPage: true });
34 });
35
36 +// ---------------------------------------------------------------------------
37 +// About / cost transparency
38 +// ---------------------------------------------------------------------------
39 +
40 +test('about — full page', async ({ page }) => {
41 + await page.goto('/about/');
42 + await settle(page);
43 + await expect(page).toHaveScreenshot('about.png', { fullPage: true });
44 +});
45 +
46 // ---------------------------------------------------------------------------
47 // Latest weekly
48 // ---------------------------------------------------------------------------