SPB Git

spb/countryatlas

Public

Web polish: line-chart y-domain rule, currency prefixes, collapsible subtopics, home filter notes, headline debt indicator

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Sep 11, 2026) parent 0ff8724 Browse files

588 changed files +1,371 −99

588 changed files
modified apps/web/README.md +15 −4
@@ -33,6 +33,8 @@ node qa/screens.mjs [BASE_URL] # screenshots 320/360/375/390/414/430/1280/1440
33 33 # overflow (scrollWidth>clientWidth), tap targets <44px, tab-bar/footer overlap, CLS, console errors
34 34 node qa/segments.mjs [BASE_URL] # viewport-sized segments (top + scroll offsets) → qa/screens/seg/ for visual review
35 35 node qa/favicons.mjs # re-rasterise src/app/icon.svg → icon.png (512) + apple-icon.png (180)
36 +node qa/polish-sweep.mjs [BASE] [--quick] # 25 routes × 320…1440 × light/dark → qa/screens/polish/ (+ report.json):
37 + # overflow, tap targets, console errors, CLS after settle, clipped text, "undefined/NaN/null", footer credits
36 38 ```
37 39
38 40 Playwright comes from `~/Desktop/uqo-eval/node_modules` (symlinked into `node_modules/playwright[-core]`; ESM ignores
@@ -109,9 +111,14 @@ components/
109 111 ```
110 112 `isNotBuilt` covers 503 ("Data not built yet") **and** an unreachable API — the site renders a calm state, never crashes.
111 113 * **Strings:** everything visible goes through `t('key')` in `src/i18n/en.ts`. Dynamic keys → `tOpt`.
112 −* **Numbers/dates:** `lib/format.ts` only, fixed `en-US` locale (Intl hydration bug prevention). Prefer the API's
113 − `formatted` string when present (`m.formatted ?? formatValue(m.value, m)`) so server and client agree.
114 − `tnum` utility on columns of numbers, `pnum` on hero figures.
114 +* **Numbers/dates:** `lib/format.ts` only, fixed `en-US` locale (Intl hydration bug prevention). Display an API
115 + value with `displayValue(m.value, m, m.formatted)`: currencies ALWAYS go through the client formatter (unit
116 + prefix guaranteed — "US$55.7k", "intl $66.7k"), other formats prefer the API's `formatted` (it carries the
117 + indicator precision that `MetricValue` lacks). Never render `m.formatted` alone. `tnum` utility on columns of
118 + numbers, `pnum` on hero figures.
119 +* **Freshness honesty:** a value whose year is ≥ 2 years older than the reference year of its list (ranking rows,
120 + home lists, member rankings, top-3 previews, compact subtopic tables) shows its year next to the value —
121 + `staleYear(year, reference)` in `charts/ranked-bars.tsx`; `Metric`/`IndicatorRow`/snapshot cells always show it.
115 122 * **No cards.** `Section` (rule above, editorial heading, subtitle, right-side actions) + `divide-y divide-rule` lists.
116 123 Grid children that contain long text need `min-w-0` (Section already has it).
117 124 * **Never invent numbers.** Missing → `EmptyState` / "No data" (`t('common.noData')`).
@@ -160,7 +167,7 @@ hairline solid grid. Points type: `SeriesPoint {period, year, value, is_forecast
160 167
161 168 | Component | Kind | Notes |
162 169 |---|---|---|
163 −| `LineChart` / `AreaChart` / `StackedArea` | client | multi-series, dashed `is_forecast`, `log`, crosshair + one tooltip for all series, end labels ≤ 4 series, auto left margin |
170 +| `LineChart` / `AreaChart` / `StackedArea` | client | multi-series, dashed `is_forecast`, `log`, crosshair + one tooltip for all series, end labels ≤ 4 series, auto left margin. **Y domain** (`scales.ts::lineDomain`): line/area use the padded (≈ 6 %) nice extent of the data; zero is included only if min ≤ 0, min < 0.3·max, or a percent/index share spanning > ½ of [0, max]. Stacked keeps the zero baseline. |
164 171 | `Sparkline` | server | tiny, no axes, end dot coloured by `direction` |
165 172 | `RankedBars` (+`rankedRowFromCountry`) | server, HTML | horizontal bars with flags, `highlightId`, values at the tip |
166 173 | `Scatter` | client | bubble `size`, labels for highlighted + extremes only, 24 px hit radius |
@@ -180,6 +187,10 @@ hairline solid grid. Points type: `SeriesPoint {period, year, value, is_forecast
180 187 * `BottomSheet` (native `<dialog>`): `side="drawer"` (right drawer on md+), `"center"`, `"full"`; drag-down to close.
181 188 * `DataTable<T>` → definition list under `sm` (no horizontal overflow). `TopicNav` sticky under the header (52/56 px).
182 189 * `ChangeList` (feed with kind icon + severity chip) and `Timeline` (events by year) both take `ChangeItem[]`.
190 +* `CollapsibleGroup` (+ `SubtopicJumpNav`, `lib/anchors.ts::subtopicAnchor`) — subtopic block for long chart pages:
191 + collapsed → `summary` (compact latest-values table: `compare/SnapshotCompact`, `country/TopicCompact`), expanded →
192 + charts (still lazy on scroll); a `#anchor` hash opens it. Used by compare topic tabs (> 8 charts, first block open)
193 + and `/countries/[slug]/[topic]` (> 12 indicators, first two open).
183 194 * `SiteFooter` renders `<FooterCredits/>` ("Made by Simon-Pierre Boucher · contact@spboucher.ai · Hosted on MacLustr")
184 195 — reuse `FooterCredits` on `/sources` and `/api`.
185 196
modified apps/web/qa/mock_api.py +1 −1
@@ -69,7 +69,7 @@ def series(c: dict, ind: dict) -> list[dict]:
69 69 v = v * (1 + trend + noise) if fmt not in ("percent", "years", "celsius") else v + trend + noise
70 70 v = max(0.001, v) if fmt != "percent" or "balance" not in ind["slug"] else v
71 71 out.append({"period": f"{y}-01-01", "year": y, "frequency": "A", "value": round(v, 3), "is_forecast": False, "is_estimate": y >= 2023, "status": "verified", "source_id": "worldbank", "provenance": prov(ind, c)})
72 − if ind["slug"] in ("gdp", "gdp-per-capita", "gdp-growth", "inflation", "unemployment-rate", "government-debt-pct-gdp"):
72 + if ind["slug"] in ("gdp", "gdp-per-capita", "gdp-growth", "inflation", "unemployment-rate", "general-government-gross-debt-pct-gdp"):
73 73 for y in range(end + 1, end + 4):
74 74 v = v * (1 + trend) if fmt not in ("percent",) else v + trend
75 75 out.append({"period": f"{y}-01-01", "year": y, "frequency": "A", "value": round(v, 3), "is_forecast": True, "is_estimate": False, "status": "imported", "source_id": "imf", "provenance": prov(ind, c, "imf")})
added apps/web/qa/polish-segments.mjs +66 −0
@@ -0,0 +1,66 @@
1 +/**
2 + * Viewport-sized segments for visual review (the full-page PNGs of polish-sweep are too tall to read):
3 + * top + evenly spaced offsets per route × width × theme → qa/screens/polish/seg/<route>-<width>-<theme>-<i>.png
4 + * node qa/polish-segments.mjs [BASE_URL] [routes…] (defaults: key routes, widths 320 + 390 + 1440, light + dark at 390)
5 + */
6 +import { chromium } from '/Users/simon-pierreboucher/Desktop/uqo-eval/node_modules/playwright/index.mjs';
7 +import { mkdirSync } from 'node:fs';
8 +
9 +const args = process.argv.slice(2);
10 +const BASE = args.find((a) => a.startsWith('http')) ?? 'http://localhost:8290';
11 +const custom = args.filter((a) => a.startsWith('/'));
12 +const OUT = new URL('./screens/polish/seg/', import.meta.url).pathname;
13 +mkdirSync(OUT, { recursive: true });
14 +const ROUTES = custom.length ? custom : ['/', '/countries', '/countries/canada', '/countries/canada/economy', '/compare/canada/united-states/france', '/compare/canada/united-states/france?tab=economy', '/rankings', '/rankings/gdp-per-capita', '/indicators/life-expectancy', '/regions/oecd', '/explore', '/changes', '/sources', '/methodology', '/api', '/data', '/indicators', '/regions', '/compare'];
15 +const ALL_COMBOS = [
16 + [320, 'light'],
17 + [390, 'light'],
18 + [390, 'dark'],
19 + [1440, 'light'],
20 + [1440, 'dark'],
21 +];
22 +// --desktop → 1440 only; --phone → 320/390 only
23 +const COMBOS = args.includes('--desktop') ? ALL_COMBOS.filter(([w]) => w >= 1024) : args.includes('--phone') ? ALL_COMBOS.filter(([w]) => w < 1024) : ALL_COMBOS;
24 +const slug = (p) => (p === '/' ? 'home' : p.slice(1).replace(/[/?=&]+/g, '_'));
25 +const browser = await chromium.launch();
26 +for (const [width, theme] of COMBOS) {
27 + const mobile = width < 768;
28 + const height = mobile ? 844 : 900;
29 + const ctx = await browser.newContext({ viewport: { width, height }, deviceScaleFactor: 1, isMobile: mobile, hasTouch: mobile, colorScheme: theme });
30 + const page = await ctx.newPage();
31 + for (const path of ROUTES) {
32 + let docH = 0;
33 + for (let attempt = 0; attempt < 3; attempt++) {
34 + try {
35 + await page.goto(BASE + path, { waitUntil: 'networkidle', timeout: 60_000 });
36 + await page.evaluate(() => document.fonts.ready);
37 + docH = await page.evaluate(async () => {
38 + const h = document.documentElement.scrollHeight;
39 + for (let y = 0; y < h; y += 700) {
40 + window.scrollTo(0, y);
41 + await new Promise((r) => setTimeout(r, 50));
42 + }
43 + window.scrollTo(0, 0);
44 + return document.documentElement.scrollHeight;
45 + });
46 + break;
47 + } catch (e) {
48 + if (attempt === 2) console.log(`FAILED ${path}: ${String(e).slice(0, 120)}`);
49 + await page.waitForTimeout(1500);
50 + }
51 + }
52 + if (!docH) continue;
53 + await page.waitForLoadState('networkidle').catch(() => {});
54 + await page.waitForTimeout(400);
55 + const n = Math.min(mobile ? 6 : 4, Math.max(1, Math.ceil(docH / height)));
56 + for (let i = 0; i < n; i++) {
57 + const y = n === 1 ? 0 : Math.round((i * (docH - height)) / (n - 1));
58 + await page.evaluate((yy) => window.scrollTo(0, yy), y);
59 + await page.waitForTimeout(250);
60 + await page.screenshot({ path: `${OUT}${slug(path)}-${width}-${theme}-${i}.png`, fullPage: false });
61 + }
62 + console.log(`${width} ${theme} ${path} h=${docH} segments=${n}`);
63 + }
64 + await ctx.close();
65 +}
66 +await browser.close();
added apps/web/qa/polish-sweep.mjs +211 −0
@@ -0,0 +1,211 @@
1 +/**
2 + * Polish sweep (real API): every public route × 320/375/390/430/1280/1440 × light/dark.
3 + * Asserts: no horizontal overflow, tap targets ≥ 44 px on phones, no console errors, no layout shift after
4 + * fonts/charts settle (PerformanceObserver layout-shift, CLS > 0.02 flagged), no mid-word truncation at 320
5 + * (`.truncate` elements that actually clip), no "undefined"/"NaN"/"null" in the visible text, footer credits
6 + * present. Screenshots → qa/screens/polish/<route>-<width>-<theme>.png, report → qa/screens/polish/report.json.
7 + *
8 + * node qa/polish-sweep.mjs [BASE_URL] [--quick] (--quick: 390 + 1440 light only, 10 routes)
9 + */
10 +import { chromium } from '/Users/simon-pierreboucher/Desktop/uqo-eval/node_modules/playwright/index.mjs';
11 +import { mkdirSync, writeFileSync } from 'node:fs';
12 +
13 +const args = process.argv.slice(2);
14 +const quick = args.includes('--quick');
15 +const BASE = args.find((a) => a.startsWith('http')) ?? process.env.BASE_URL ?? 'http://localhost:8290';
16 +const OUT = new URL('./screens/polish/', import.meta.url).pathname;
17 +mkdirSync(OUT, { recursive: true });
18 +
19 +const ROUTES_FULL = [
20 + '/', '/countries', '/countries/canada', '/countries/canada/economy', '/countries/nigeria', '/countries/japan/energy',
21 + '/compare', '/compare/canada/united-states/france', '/compare/canada/united-states/france?tab=economy',
22 + '/rankings', '/rankings/gdp-per-capita', '/rankings/life-expectancy?group=oecd',
23 + '/indicators', '/indicators/life-expectancy', '/indicators/inflation',
24 + '/regions', '/regions/oecd', '/explore', '/changes', '/data', '/sources', '/sources/worldbank', '/methodology', '/api', '/?search=can',
25 +];
26 +const ROUTES_QUICK = ['/', '/countries', '/countries/canada', '/countries/canada/economy', '/compare/canada/united-states/france?tab=economy', '/rankings/gdp-per-capita', '/indicators/life-expectancy', '/regions/oecd', '/changes', '/api'];
27 +const ROUTES = quick ? ROUTES_QUICK : ROUTES_FULL;
28 +const WIDTHS = quick ? [390, 1440] : [320, 375, 390, 430, 1280, 1440];
29 +const THEMES = quick ? ['light'] : ['light', 'dark'];
30 +
31 +const slug = (p) => (p === '/' ? 'home' : p.slice(1).replace(/[/?=&]+/g, '_'));
32 +const report = [];
33 +const browser = await chromium.launch();
34 +
35 +for (const theme of THEMES) {
36 + for (const width of WIDTHS) {
37 + const mobile = width < 768;
38 + const ctx = await browser.newContext({ viewport: { width, height: mobile ? 844 : 900 }, deviceScaleFactor: 1, isMobile: mobile, hasTouch: mobile, colorScheme: theme, reducedMotion: 'no-preference' });
39 + await ctx.addInitScript(() => {
40 + window.__cls = 0;
41 + window.__clsAfterSettle = 0;
42 + window.__settled = false;
43 + try {
44 + new PerformanceObserver((l) => {
45 + for (const e of l.getEntries()) {
46 + if (e.hadRecentInput) continue;
47 + window.__cls += e.value;
48 + if (window.__settled) window.__clsAfterSettle += e.value;
49 + }
50 + }).observe({ type: 'layout-shift', buffered: true });
51 + } catch {}
52 + });
53 + const page = await ctx.newPage();
54 + const errors = [];
55 + page.on('pageerror', (e) => errors.push(`pageerror: ${String(e).slice(0, 200)}`));
56 + page.on('console', (m) => {
57 + if (m.type() === 'error') errors.push(m.text().slice(0, 200));
58 + });
59 + for (const path of ROUTES) {
60 + let status = 0;
61 + try {
62 + const resp = await page.goto(BASE + path, { waitUntil: 'networkidle', timeout: 60_000 });
63 + status = resp?.status() ?? 0;
64 + } catch (e) {
65 + report.push({ path, width, theme, status: 'ERR', error: String(e).slice(0, 200) });
66 + continue;
67 + }
68 + await page.evaluate(() => document.fonts.ready);
69 + await page.waitForTimeout(700);
70 + // "/?search=<q>" is not a route: open the search dialog from the header and type the query.
71 + const searchQ = /[?&]search=([^&]+)/.exec(path)?.[1];
72 + if (searchQ) {
73 + try {
74 + // The header renders two triggers (icon on phones, field on desktop): click the visible one.
75 + const trigger = page.locator('header button[aria-label="Open search"]').locator('visible=true').first();
76 + await trigger.click({ timeout: 10_000 });
77 + const input = page.locator('dialog input').first();
78 + await input.waitFor({ timeout: 10_000 });
79 + await input.fill(decodeURIComponent(searchQ));
80 + await page.waitForLoadState('networkidle').catch(() => {});
81 + await page.waitForTimeout(800);
82 + } catch (e) {
83 + errors.push(`search dialog: ${String(e).slice(0, 120)}`);
84 + }
85 + }
86 + // Scroll through the page so lazy charts mount, then back to top; measure shift after settle.
87 + if (!searchQ)
88 + await page.evaluate(async () => {
89 + const h = document.documentElement.scrollHeight;
90 + for (let y = 0; y < h; y += 700) {
91 + window.scrollTo(0, y);
92 + await new Promise((r) => setTimeout(r, 60));
93 + }
94 + window.scrollTo(0, 0);
95 + });
96 + await page.waitForLoadState('networkidle').catch(() => {});
97 + await page.waitForTimeout(500);
98 + await page.evaluate(() => {
99 + window.__settled = true;
100 + });
101 + await page.waitForTimeout(600);
102 + let m;
103 + try {
104 + m = await page.evaluate(
105 + ({ mobile, width }) => {
106 + const de = document.documentElement;
107 + const overflow = de.scrollWidth - de.clientWidth;
108 + const vis = (el) => {
109 + const r = el.getBoundingClientRect();
110 + if (r.width === 0 || r.height === 0) return false;
111 + const cs = getComputedStyle(el);
112 + return cs.visibility !== 'hidden' && cs.display !== 'none';
113 + };
114 + const wide = [...document.querySelectorAll('body *')]
115 + .filter((el) => {
116 + const r = el.getBoundingClientRect();
117 + return r.right > de.clientWidth + 1 && r.width > 0;
118 + })
119 + .slice(0, 6)
120 + .map((el) => `${el.tagName.toLowerCase()}.${String(el.className).split(' ').slice(0, 3).join('.')} right=${Math.round(el.getBoundingClientRect().right)}`);
121 + const targets = [...document.querySelectorAll('a,button,[role=button],input,select,summary,[role=radio],[role=tab]')].filter(vis);
122 + const small = mobile
123 + ? targets
124 + .filter((el) => {
125 + const r = el.getBoundingClientRect();
126 + return r.height < 44 && r.width < 44;
127 + })
128 + .map((el) => `${el.tagName.toLowerCase()} "${(el.getAttribute('aria-label') || el.textContent || '').trim().slice(0, 30)}" ${Math.round(el.getBoundingClientRect().width)}x${Math.round(el.getBoundingClientRect().height)}`)
129 + : [];
130 + const shortH = mobile
131 + ? targets
132 + .filter((el) => el.getBoundingClientRect().height < 44 && !el.closest('svg'))
133 + .map((el) => `${el.tagName.toLowerCase()} "${(el.getAttribute('aria-label') || el.textContent || '').trim().slice(0, 28)}" h=${Math.round(el.getBoundingClientRect().height)}`)
134 + : [];
135 + // Mid-word truncation: elements with text-overflow: ellipsis whose content is clipped.
136 + const clipped = [...document.querySelectorAll('body *')]
137 + .filter((el) => {
138 + const cs = getComputedStyle(el);
139 + if (cs.textOverflow !== 'ellipsis' || cs.overflow === 'visible') return false;
140 + return el.scrollWidth > el.clientWidth + 1 && el.textContent && el.textContent.trim().length > 0;
141 + })
142 + .map((el) => `${el.tagName.toLowerCase()}: "${el.textContent.trim().slice(0, 40)}" ${el.clientWidth}/${el.scrollWidth}`);
143 + const text = document.body.innerText || '';
144 + const bad = [];
145 + for (const re of [/\bundefined\b/g, /\bNaN\b/g, /(?<![\w"':])null(?![\w"':])/g]) {
146 + let mm;
147 + let n = 0;
148 + while ((mm = re.exec(text)) && n < 3) {
149 + bad.push(`${mm[0]} @ "${text.slice(Math.max(0, mm.index - 40), mm.index + 30).replace(/\s+/g, ' ')}"`);
150 + n++;
151 + }
152 + }
153 + const footer = document.querySelector('footer');
154 + const ft = footer ? footer.innerText : '';
155 + const credits = /Made by\s+Simon-Pierre Boucher/.test(ft) && /contact@spboucher\.ai/.test(ft) && /Hosted on\s+MacLustr/.test(ft);
156 + const mail = !!footer?.querySelector('a[href="mailto:contact@spboucher.ai"]');
157 + const mac = !!footer?.querySelector('a[href="https://www.maclustr.io"]');
158 + const overlapping = [];
159 + // Chart tick labels overlapping each other (same axis group)
160 + for (const g of document.querySelectorAll('svg .axis')) {
161 + const ts = [...g.querySelectorAll('text')].map((tx) => tx.getBoundingClientRect());
162 + for (let i = 0; i < ts.length; i++)
163 + for (let j = i + 1; j < ts.length; j++) {
164 + const a = ts[i];
165 + const b = ts[j];
166 + if (a.width && b.width && a.left < b.right - 1 && b.left < a.right - 1 && a.top < b.bottom - 1 && b.top < a.bottom - 1) overlapping.push(`${Math.round(a.left)},${Math.round(a.top)}`);
167 + }
168 + }
169 + return { overflow, wide, small: small.slice(0, 10), nSmall: small.length, shortH: shortH.slice(0, 8), nShortH: shortH.length, clipped: clipped.slice(0, 10), nClipped: clipped.length, bad, credits, mail, mac, cls: window.__cls, clsAfter: window.__clsAfterSettle, docH: de.scrollHeight, title: document.title, overlapping: overlapping.slice(0, 5), width };
170 + },
171 + { mobile, width },
172 + );
173 + } catch (e) {
174 + report.push({ path, width, theme, status: 'ERR', error: String(e).slice(0, 200), errors: errors.splice(0) });
175 + continue;
176 + }
177 + const file = `${slug(path)}-${width}-${theme}.png`;
178 + await page.screenshot({ path: OUT + file, fullPage: !searchQ }).catch(() => {});
179 + report.push({ path, width, theme, status, ...m, errors: errors.splice(0), file });
180 + writeFileSync(OUT + 'report.json', JSON.stringify(report, null, 2));
181 + }
182 + await ctx.close();
183 + }
184 +}
185 +await browser.close();
186 +writeFileSync(OUT + 'report.json', JSON.stringify(report, null, 2));
187 +
188 +let fails = 0;
189 +for (const r of report) {
190 + const flags = [];
191 + if (r.status !== 200) flags.push(`HTTP ${r.status}`);
192 + if (r.overflow > 0) flags.push(`OVERFLOW +${r.overflow}px`);
193 + if (r.nSmall) flags.push(`${r.nSmall} small targets`);
194 + if (r.nShortH) flags.push(`${r.nShortH} targets <44h`);
195 + if (r.nClipped) flags.push(`${r.nClipped} clipped`);
196 + if (r.bad?.length) flags.push(`BAD TEXT ${r.bad.length}`);
197 + if (r.errors?.length) flags.push(`${r.errors.length} console errors`);
198 + if (r.clsAfter > 0.02) flags.push(`CLS-after ${r.clsAfter.toFixed(3)}`);
199 + if (r.cls > 0.1) flags.push(`CLS ${r.cls.toFixed(3)}`);
200 + if (r.credits === false || r.mail === false || r.mac === false) flags.push('NO CREDITS');
201 + if (r.overlapping?.length) flags.push(`${r.overlapping.length} tick overlaps`);
202 + if (flags.length) fails++;
203 + console.log(`${String(r.width).padStart(4)} ${r.theme.padEnd(5)} ${r.path.padEnd(52)} ${flags.join(' · ') || 'ok'} (h=${r.docH})`);
204 + if (r.wide?.length) console.log(' wide:', r.wide.join(' | '));
205 + if (r.small?.length) console.log(' small:', r.small.slice(0, 5).join(' | '));
206 + if (r.shortH?.length) console.log(' <44h:', r.shortH.slice(0, 5).join(' | '));
207 + if (r.clipped?.length) console.log(' clipped:', r.clipped.slice(0, 5).join(' | '));
208 + if (r.bad?.length) console.log(' bad:', r.bad.join(' | '));
209 + if (r.errors?.length) console.log(' errors:', r.errors.slice(0, 3).join(' | '));
210 +}
211 +console.log(`\n${report.length} renders, ${fails} with flags`);
added apps/web/qa/screens/polish/api-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/api-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/brand/_apple_icon_png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/brand/_countries_canada_opengraph_image +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/brand/_icon_png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/brand/_icon_svg +1 −0
@@ -0,0 +1 @@
1 +<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 32 32" fill="none" stroke="#1c5cab" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect width="32" height="32" rx="7" fill="#fbfaf7" stroke="none"/><circle cx="16" cy="16" r="12"/><path d="M5 19.3h22"/><path d="M10.1 26.6 16 7.6l5.9 19"/><path d="M16 7.6c-3 2.9-4.3 7.1-4.3 11.7" opacity="0.55"/><path d="M16 7.6c3 2.9 4.3 7.1 4.3 11.7" opacity="0.55"/></svg>
added apps/web/qa/screens/polish/brand/_indicators_life_expectancy_opengraph_image +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/brand/_manifest_webmanifest +1 −0
@@ -0,0 +1 @@
1 +{"name":"CountryAtlas","short_name":"CountryAtlas","description":"Understand the world, one country at a time.","start_url":"/","display":"standalone","background_color":"#fbfaf7","theme_color":"#1c5cab","icons":[{"src":"/icon.svg","type":"image/svg+xml","sizes":"any"},{"src":"/icon.png","type":"image/png","sizes":"512x512"},{"src":"/apple-icon.png","type":"image/png","sizes":"180x180"}]}
\ No newline at end of file
added apps/web/qa/screens/polish/brand/_opengraph_image +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/brand/_rankings_gdp_per_capita_opengraph_image +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/brand/_twitter_image +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/brand/compare_og.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/changes-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/changes-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/changes-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/compare-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/compare-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/compare-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/compare_canada_united-states_france-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/compare_canada_united-states_france-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/compare_canada_united-states_france-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/compare_canada_united-states_france_tab_economy-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/compare_canada_united-states_france_tab_economy-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/compare_canada_united-states_france_tab_economy-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries_canada-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries_canada-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries_canada-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries_canada_economy-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries_canada_economy-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries_canada_economy-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries_japan_energy-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries_japan_energy-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries_japan_energy-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries_nigeria-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries_nigeria-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/countries_nigeria-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/data-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/data-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/data-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/explore-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/explore-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/explore-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/home-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/home-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/home-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/indicators-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/indicators-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/indicators-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/indicators_inflation-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/indicators_inflation-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/indicators_inflation-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/indicators_life-expectancy-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/indicators_life-expectancy-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/indicators_life-expectancy-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/methodology-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/methodology-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/rankings-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/rankings-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/rankings-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/rankings_gdp-per-capita-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/rankings_gdp-per-capita-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/rankings_gdp-per-capita-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/rankings_life-expectancy_group_oecd-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/rankings_life-expectancy_group_oecd-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/rankings_life-expectancy_group_oecd-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/regions-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/regions-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/regions-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/regions_oecd-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/regions_oecd-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/regions_oecd-390-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/report.json +510 −0
@@ -0,0 +1,510 @@
1 +[
2 + {
3 + "path": "/",
4 + "width": 320,
5 + "theme": "light",
6 + "status": 200,
7 + "overflow": 0,
8 + "wide": [
9 + "li. right=357",
10 + "a.inline-flex.h-11.items-center right=357",
11 + "li. right=501",
12 + "a.inline-flex.h-11.items-center right=501",
13 + "li. right=585",
14 + "a.inline-flex.h-11.items-center right=585"
15 + ],
16 + "small": [
17 + "a \"Skip to content\" 1x1"
18 + ],
19 + "nSmall": 1,
20 + "shortH": [
21 + "a \"Skip to content\" h=1",
22 + "a \"Build your own comparison →\" h=40",
23 + "a \"See all →\" h=21",
24 + "a \"🇺🇸United States\" h=19",
25 + "a \"🇨🇳China\" h=19",
26 + "a \"🇩🇪Germany\" h=19",
27 + "a \"🇯🇵Japan\" h=19",
28 + "a \"🇬🇧United Kingdom\" h=19"
29 + ],
30 + "nShortH": 60,
31 + "clipped": [
32 + "span: \"Search countries, indicators, topics…\" 224/281",
33 + "span: \"CO₂ emissions (fossil fuels and industry\" 192/266",
34 + "span: \"Individuals using the Internet (% of pop\" 192/310",
35 + "span: \"GDP per capita (current US$)\" 192/194",
36 + "span: \"GDP per capita, PPP (current internation\" 192/295",
37 + "span: \"At least upper secondary attainment (age\" 192/332",
38 + "span: \"Education · % of population ages 25–64\" 192/226",
39 + "span: \"Employment rate (ages 15–64)\" 197/200",
40 + "span: \"Labor · % of population ages 15–64\" 197/200",
41 + "span: \"Long-term unemployment (% of unemployed)\" 197/303"
42 + ],
43 + "nClipped": 16,
44 + "bad": [],
45 + "credits": true,
46 + "mail": true,
47 + "mac": true,
48 + "cls": 0,
49 + "clsAfter": 0,
50 + "docH": 10409,
51 + "title": "CountryAtlas — Understand the world, one country at a time.",
52 + "overlapping": [],
53 + "errors": [],
54 + "file": "home-320-light.png"
55 + },
56 + {
57 + "path": "/countries",
58 + "width": 320,
59 + "theme": "light",
60 + "status": 200,
61 + "overflow": 0,
62 + "wide": [],
63 + "small": [
64 + "a \"Skip to content\" 1x1"
65 + ],
66 + "nSmall": 1,
67 + "shortH": [
68 + "a \"Skip to content\" h=1",
69 + "input \"Filter by name…\" h=40",
70 + "select \"NamePopulationGDP per capita\" h=19",
71 + "button \"Filters\" h=40",
72 + "a \"contact@spboucher.ai\" h=15",
73 + "a \"MacLustr\" h=15"
74 + ],
75 + "nShortH": 6,
76 + "clipped": [
77 + "span: \"Northern Mariana Islands\" 164/169",
78 + "span: \"Saint Vincent and the Grenadines\" 164/223",
79 + "span: \"Turks and Caicos Islands\" 164/167"
80 + ],
81 + "nClipped": 3,
82 + "bad": [],
83 + "credits": true,
84 + "mail": true,
85 + "mac": true,
86 + "cls": 0,
87 + "clsAfter": 0,
88 + "docH": 14283,
89 + "title": "Countries — CountryAtlas",
90 + "overlapping": [],
91 + "errors": [],
92 + "file": "countries-320-light.png"
93 + },
94 + {
95 + "path": "/countries/canada",
96 + "width": 320,
97 + "theme": "light",
98 + "status": 200,
99 + "overflow": 0,
100 + "wide": [
101 + "li.snap-start right=342",
102 + "a.inline-flex.h-11.items-center right=342",
103 + "span.tnum.text-2xs.text-ink-3 right=330",
104 + "li.snap-start right=460",
105 + "a.inline-flex.h-11.items-center right=460",
106 + "span.tnum.text-2xs.text-ink-3 right=448"
107 + ],
108 + "small": [
109 + "a \"Skip to content\" 1x1"
110 + ],
111 + "nSmall": 1,
112 + "shortH": [
113 + "a \"Skip to content\" h=1",
114 + "a \"Compare Canada with another \" h=40",
115 + "a \"Download\" h=40",
116 + "button \"Share\" h=40",
117 + "a \"GDP reached a record high of\" h=39",
118 + "a \"🇺🇸 United States\" h=17",
119 + "a \"Sharp dropPopulation growth \" h=36",
120 + "a \"Sharp dropTrade balance fell\" h=36"
121 + ],
122 + "nShortH": 12,
123 + "clipped": [],
124 + "nClipped": 0,
125 + "bad": [],
126 + "credits": true,
127 + "mail": true,
128 + "mac": true,
129 + "cls": 0,
130 + "clsAfter": 0,
131 + "docH": 10266,
132 + "title": "Canada Data & Statistics — CountryAtlas",
133 + "overlapping": [],
134 + "errors": [],
135 + "file": "countries_canada-320-light.png"
136 + },
137 + {
138 + "path": "/countries/canada/economy",
139 + "width": 320,
140 + "theme": "light",
141 + "status": 200,
142 + "overflow": 0,
143 + "wide": [
144 + "li.snap-start right=399",
145 + "a.inline-flex.h-11.items-center right=399",
146 + "li.snap-start right=465",
147 + "a.inline-flex.h-11.items-center right=465",
148 + "li.snap-start right=542",
149 + "a.inline-flex.h-11.items-center right=542"
150 + ],
151 + "small": [
152 + "a \"Skip to content\" 1x1"
153 + ],
154 + "nSmall": 1,
155 + "shortH": [
156 + "a \"Skip to content\" h=1",
157 + "a \"Countries\" h=15",
158 + "a \"🇨🇦 Canada\" h=15",
159 + "button \"Show source and provenance\" h=32",
160 + "button \"View data as table\" h=32",
161 + "button \"Show source and provenance\" h=32",
162 + "button \"View data as table\" h=32",
163 + "button \"Show source and provenance\" h=32"
164 + ],
165 + "nShortH": 22,
166 + "clipped": [
167 + "span: \"Source: World Bank — WDI · NY.GDP.MKTP.C\" 137/366",
168 + "span: \"Source: World Bank — WDI · NY.GDP.MKTP.P\" 137/383",
169 + "span: \"Source: World Bank — WDI · NY.GDP.PCAP.C\" 137/364",
170 + "span: \"Source: World Bank — WDI · NY.GDP.PCAP.P\" 137/381",
171 + "span: \"Source: World Bank — WDI · NY.GDP.MKTP.K\" 137/366",
172 + "span: \"Source: OECD — OECD.SDD.STES,DSD_KEI@DF_\" 137/537",
173 + "span: \"Source: World Bank — WDI · NY.GDP.MKTP.K\" 137/383",
174 + "span: \"Source: World Bank — WDI · NY.GDP.PCAP.K\" 137/381",
175 + "span: \"Household consumption (% of GDP)\" 193/240"
176 + ],
177 + "nClipped": 9,
178 + "bad": [],
179 + "credits": true,
180 + "mail": true,
181 + "mac": true,
182 + "cls": 0,
183 + "clsAfter": 0,
184 + "docH": 7564,
185 + "title": "Canada — Economy — CountryAtlas",
186 + "overlapping": [],
187 + "errors": [],
188 + "file": "countries_canada_economy-320-light.png"
189 + },
190 + {
191 + "path": "/countries/nigeria",
192 + "width": 320,
193 + "theme": "light",
194 + "status": 200,
195 + "overflow": 0,
196 + "wide": [
197 + "li.snap-start right=335",
198 + "a.inline-flex.h-11.items-center right=335",
199 + "span.tnum.text-2xs.text-ink-3 right=323",
200 + "li.snap-start right=453",
201 + "a.inline-flex.h-11.items-center right=453",
202 + "span.tnum.text-2xs.text-ink-3 right=441"
203 + ],
204 + "small": [
205 + "a \"Skip to content\" 1x1"
206 + ],
207 + "nSmall": 1,
208 + "shortH": [
209 + "a \"Skip to content\" h=1",
210 + "a \"Compare Nigeria with another\" h=40",
211 + "a \"Download\" h=40",
212 + "button \"Share\" h=40",
213 + "a \"🇧🇯 Benin\" h=17",
214 + "a \"🇨🇲 Cameroon\" h=17",
215 + "a \"🇹🇩 Chad\" h=17",
216 + "a \"🇳🇪 Niger\" h=17"
217 + ],
218 + "nShortH": 18,
219 + "clipped": [
220 + "span: \"Republic of the Congo\" 126/147"
221 + ],
222 + "nClipped": 1,
223 + "bad": [],
224 + "credits": true,
225 + "mail": true,
226 + "mac": true,
227 + "cls": 0,
228 + "clsAfter": 0,
229 + "docH": 10282,
230 + "title": "Nigeria Data & Statistics — CountryAtlas",
231 + "overlapping": [],
232 + "errors": [],
233 + "file": "countries_nigeria-320-light.png"
234 + },
235 + {
236 + "path": "/countries/japan/energy",
237 + "width": 320,
238 + "theme": "light",
239 + "status": 200,
240 + "overflow": 0,
241 + "wide": [
242 + "li.snap-start right=384",
243 + "a.inline-flex.h-11.items-center right=384",
244 + "li.snap-start right=502",
245 + "a.inline-flex.h-11.items-center right=502",
246 + "li.snap-start right=571",
247 + "a.inline-flex.h-11.items-center right=571"
248 + ],
249 + "small": [
250 + "a \"Skip to content\" 1x1"
251 + ],
252 + "nSmall": 1,
253 + "shortH": [
254 + "a \"Skip to content\" h=1",
255 + "a \"Countries\" h=15",
256 + "a \"🇯🇵 Japan\" h=15",
257 + "button \"Show source and provenance\" h=32",
258 + "button \"View data as table\" h=32",
259 + "button \"Show source and provenance\" h=32",
260 + "button \"View data as table\" h=32",
261 + "button \"Show source and provenance\" h=32"
262 + ],
263 + "nShortH": 23,
264 + "clipped": [
265 + "span: \"Source: Our World in Data — energy · ele\" 137/430",
266 + "span: \"Source: Our World in Data — energy · per\" 137/429",
267 + "span: \"Source: Our World in Data — energy · sol\" 137/401",
268 + "span: \"Source: Our World in Data — energy · win\" 137/400",
269 + "span: \"Source: Our World in Data — energy · hyd\" 137/405",
270 + "span: \"Source: Our World in Data — energy · nuc\" 137/414",
271 + "span: \"Source: Our World in Data — energy · pri\" 137/471",
272 + "span: \"Source: Our World in Data — energy · ene\" 137/414",
273 + "span: \"Source: World Bank — WDI · EG.FEC.RNEW.Z\" 137/363",
274 + "span: \"Net electricity imports (% of demand)\" 202/249"
275 + ],
276 + "nClipped": 13,
277 + "bad": [],
278 + "credits": true,
279 + "mail": true,
280 + "mac": true,
281 + "cls": 0,
282 + "clsAfter": 0,
283 + "docH": 7934,
284 + "title": "Japan — Energy — CountryAtlas",
285 + "overlapping": [],
286 + "errors": [],
287 + "file": "countries_japan_energy-320-light.png"
288 + },
289 + {
290 + "path": "/compare",
291 + "width": 320,
292 + "theme": "light",
293 + "status": 200,
294 + "overflow": 0,
295 + "wide": [],
296 + "small": [
297 + "a \"Skip to content\" 1x1"
298 + ],
299 + "nSmall": 1,
300 + "shortH": [
301 + "a \"Skip to content\" h=1",
302 + "a \"Nordics5 countries🇩🇰🇫🇮🇮\" h=43",
303 + "a \"BRICS5 countries🇧🇷🇷🇺🇮🇳\" h=43",
304 + "a \"USMCA3 countries🇨🇦🇲🇽🇺🇸\" h=43",
305 + "a \"Anglosphere5 countries🇦🇺🇨\" h=43",
306 + "a \"Asian Tigers4 countries🇭🇰\ud83c\" h=43",
307 + "a \"contact@spboucher.ai\" h=15",
308 + "a \"MacLustr\" h=15"
309 + ],
310 + "nShortH": 8,
311 + "clipped": [],
312 + "nClipped": 0,
313 + "bad": [],
314 + "credits": true,
315 + "mail": true,
316 + "mac": true,
317 + "cls": 0,
318 + "clsAfter": 0,
319 + "docH": 2569,
320 + "title": "Compare countries side by side — CountryAtlas",
321 + "overlapping": [],
322 + "errors": [],
323 + "file": "compare-320-light.png"
324 + },
325 + {
326 + "path": "/compare/canada/united-states/france",
327 + "width": 320,
328 + "theme": "light",
329 + "status": 200,
330 + "overflow": 0,
331 + "wide": [
332 + "li.group/chip.shrink-0 right=329",
333 + "span.inline-flex.h-11.items-center right=329",
334 + "button.grid.h-11.w-9 right=328",
335 + "li.group/chip.shrink-0 right=465",
336 + "span.inline-flex.h-11.items-center right=465",
337 + "span.h-2.w-2.shrink-0 right=352"
338 + ],
339 + "small": [
340 + "a \"Skip to content\" 1x1"
341 + ],
342 + "nSmall": 1,
343 + "shortH": [
344 + "a \"Skip to content\" h=1",
345 + "a \"contact@spboucher.ai\" h=15",
346 + "a \"MacLustr\" h=15"
347 + ],
348 + "nShortH": 3,
349 + "clipped": [],
350 + "nClipped": 0,
351 + "bad": [],
352 + "credits": true,
353 + "mail": true,
354 + "mac": true,
355 + "cls": 0,
356 + "clsAfter": 0,
357 + "docH": 4083,
358 + "title": "Canada vs United States vs France — Compare countries — CountryAtlas",
359 + "overlapping": [],
360 + "errors": [],
361 + "file": "compare_canada_united-states_france-320-light.png"
362 + },
363 + {
364 + "path": "/compare/canada/united-states/france?tab=economy",
365 + "width": 320,
366 + "theme": "light",
367 + "status": 200,
368 + "overflow": 0,
369 + "wide": [
370 + "li.group/chip.shrink-0 right=329",
371 + "span.inline-flex.h-11.items-center right=329",
372 + "button.grid.h-11.w-9 right=328",
373 + "li.group/chip.shrink-0 right=465",
374 + "span.inline-flex.h-11.items-center right=465",
375 + "span.h-2.w-2.shrink-0 right=352"
376 + ],
377 + "small": [
378 + "a \"Skip to content\" 1x1"
379 + ],
380 + "nSmall": 1,
381 + "shortH": [
382 + "a \"Skip to content\" h=1",
383 + "a \"GDP (current US$)\" h=17",
384 + "a \"Ranking\" h=32",
385 + "button \"View data as table\" h=32",
386 + "button \"Canada: US$2.32T. Show sourc\" h=32",
387 + "button \"United States: US$30.8T. Sho\" h=32",
388 + "button \"France: US$3.37T. Show sourc\" h=32",
389 + "button \"Source for Canada, United St\" h=28"
390 + ],
391 + "nShortH": 125,
392 + "clipped": [
393 + "span: \"Foreign direct investment, net inflows (\" 272/327",
394 + "span: \"Personal remittances received (% of GDP)\" 272/279",
395 + "span: \"Agriculture, forestry and fishing, value\" 272/378",
396 + "span: \"Industry (incl. construction), value add\" 272/350"
397 + ],
398 + "nClipped": 4,
399 + "bad": [],
400 + "credits": true,
401 + "mail": true,
402 + "mac": true,
403 + "cls": 0,
404 + "clsAfter": 0,
405 + "docH": 13228,
406 + "title": "Canada vs United States vs France — Economy — Compare countries — CountryAtlas",
407 + "overlapping": [],
408 + "errors": [],
409 + "file": "compare_canada_united-states_france_tab_economy-320-light.png"
410 + },
411 + {
412 + "path": "/rankings",
413 + "width": 320,
414 + "theme": "light",
415 + "status": 200,
416 + "overflow": 0,
417 + "wide": [],
418 + "small": [
419 + "a \"Skip to content\" 1x1"
420 + ],
421 + "nSmall": 1,
422 + "shortH": [
423 + "a \"Skip to content\" h=1",
424 + "a \"contact@spboucher.ai\" h=15",
425 + "a \"MacLustr\" h=15"
426 + ],
427 + "nShortH": 3,
428 + "clipped": [],
429 + "nClipped": 0,
430 + "bad": [],
431 + "credits": true,
432 + "mail": true,
433 + "mac": true,
434 + "cls": 0.06249728007569462,
435 + "clsAfter": 0,
436 + "docH": 26715,
437 + "title": "Country rankings — CountryAtlas",
438 + "overlapping": [],
439 + "errors": [
440 + "Failed to load resource: the server responded with a status of 429 (Too Many Requests)"
441 + ],
442 + "file": "rankings-320-light.png"
443 + },
444 + {
445 + "path": "/rankings/gdp-per-capita",
446 + "width": 320,
447 + "theme": "light",
448 + "status": 200,
449 + "overflow": 0,
450 + "wide": [],
451 + "small": [
452 + "a \"Skip to content\" 1x1"
453 + ],
454 + "nSmall": 1,
455 + "shortH": [
456 + "a \"Skip to content\" h=1",
457 + "select \"Year\" h=19",
458 + "button \"View data as table\" h=32",
459 + "a \"contact@spboucher.ai\" h=15",
460 + "a \"MacLustr\" h=15"
461 + ],
462 + "nShortH": 5,
463 + "clipped": [],
464 + "nClipped": 0,
465 + "bad": [],
466 + "credits": true,
467 + "mail": true,
468 + "mac": true,
469 + "cls": 0,
470 + "clsAfter": 0,
471 + "docH": 4739,
472 + "title": "GDP per capita by country (2025) — CountryAtlas",
473 + "overlapping": [],
474 + "errors": [],
475 + "file": "rankings_gdp-per-capita-320-light.png"
476 + },
477 + {
478 + "path": "/rankings/life-expectancy?group=oecd",
479 + "width": 320,
480 + "theme": "light",
481 + "status": 200,
482 + "overflow": 0,
483 + "wide": [],
484 + "small": [
485 + "a \"Skip to content\" 1x1"
486 + ],
487 + "nSmall": 1,
488 + "shortH": [
489 + "a \"Skip to content\" h=1",
490 + "select \"Year\" h=19",
491 + "button \"View data as table\" h=32",
492 + "a \"contact@spboucher.ai\" h=15",
493 + "a \"MacLustr\" h=15"
494 + ],
495 + "nShortH": 5,
496 + "clipped": [],
497 + "nClipped": 0,
498 + "bad": [],
499 + "credits": true,
500 + "mail": true,
501 + "mac": true,
502 + "cls": 0,
503 + "clsAfter": 0,
504 + "docH": 4689,
505 + "title": "Life expectancy in OECD (2024) — CountryAtlas",
506 + "overlapping": [],
507 + "errors": [],
508 + "file": "rankings_life-expectancy_group_oecd-320-light.png"
509 + }
510 +]
\ No newline at end of file
added apps/web/qa/screens/polish/search_q_can-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/search_q_can-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg-desktop.log +38 −0
@@ -0,0 +1,38 @@
1 +1440 light / h=5223 segments=4
2 +1440 light /countries h=13393 segments=4
3 +1440 light /countries/canada h=4473 segments=4
4 +1440 light /countries/canada/economy h=5049 segments=4
5 +1440 light /compare/canada/united-states/france h=1759 segments=2
6 +1440 light /compare/canada/united-states/france?tab=economy h=5592 segments=4
7 +1440 light /rankings h=10106 segments=4
8 +1440 light /rankings/gdp-per-capita h=2667 segments=3
9 +1440 light /indicators/life-expectancy h=4678 segments=4
10 +1440 light /regions/oecd h=4762 segments=4
11 +1440 light /explore h=5594 segments=4
12 +1440 light /changes h=11502 segments=4
13 +1440 light /sources h=2075 segments=3
14 +1440 light /methodology h=7624 segments=4
15 +1440 light /api h=6827 segments=4
16 +1440 light /data h=2789 segments=4
17 +1440 light /indicators h=22031 segments=4
18 +1440 light /regions h=2346 segments=3
19 +1440 light /compare h=1359 segments=2
20 +1440 dark / h=5223 segments=4
21 +1440 dark /countries h=13393 segments=4
22 +1440 dark /countries/canada h=4473 segments=4
23 +1440 dark /countries/canada/economy h=5049 segments=4
24 +1440 dark /compare/canada/united-states/france h=1759 segments=2
25 +1440 dark /compare/canada/united-states/france?tab=economy h=5592 segments=4
26 +1440 dark /rankings h=10106 segments=4
27 +1440 dark /rankings/gdp-per-capita h=2667 segments=3
28 +1440 dark /indicators/life-expectancy h=4678 segments=4
29 +1440 dark /regions/oecd h=4762 segments=4
30 +1440 dark /explore h=5525 segments=4
31 +1440 dark /changes h=11502 segments=4
32 +1440 dark /sources h=2075 segments=3
33 +1440 dark /methodology h=7624 segments=4
34 +1440 dark /api h=6827 segments=4
35 +1440 dark /data h=2789 segments=4
36 +1440 dark /indicators h=22031 segments=4
37 +1440 dark /regions h=2346 segments=3
38 +1440 dark /compare h=1359 segments=2
added apps/web/qa/screens/polish/seg.log +161 −0
@@ -0,0 +1,161 @@
1 +320 light / h=10409 segments=6
2 +320 light /countries h=14283 segments=6
3 +320 light /countries/canada h=10266 segments=6
4 +320 light /countries/canada/economy h=7564 segments=6
5 +320 light /compare/canada/united-states/france h=4083 segments=5
6 +320 light /compare/canada/united-states/france?tab=economy h=13228 segments=6
7 +320 light /rankings h=26767 segments=6
8 +320 light /rankings/gdp-per-capita h=4739 segments=6
9 +320 light /indicators/life-expectancy h=6868 segments=6
10 +320 light /regions/oecd h=9637 segments=6
11 +320 light /explore h=9365 segments=6
12 +320 light /changes h=15227 segments=6
13 +320 light /sources h=3846 segments=5
14 +320 light /methodology h=13226 segments=6
15 +320 light /api h=9658 segments=6
16 +320 light /data h=4366 segments=6
17 +320 light /indicators h=53477 segments=6
18 +320 light /regions h=4569 segments=6
19 +320 light /compare h=2569 segments=4
20 +390 light / h=9854 segments=6
21 +390 light /countries h=14199 segments=6
22 +390 light /countries/canada h=8620 segments=6
23 +390 light /countries/canada/economy h=7460 segments=6
24 +390 light /compare/canada/united-states/france h=4045 segments=5
25 +390 light /compare/canada/united-states/france?tab=economy h=12773 segments=6
26 +390 light /rankings h=24430 segments=6
27 +390 light /rankings/gdp-per-capita h=4603 segments=6
28 +390 light /indicators/life-expectancy h=6494 segments=6
29 +390 light /regions/oecd h=9300 segments=6
30 +390 light /explore h=8606 segments=6
31 +390 light /changes h=13813 segments=6
32 +390 light /sources h=3556 segments=5
33 +390 light /methodology h=11263 segments=6
34 +390 light /api h=9020 segments=6
35 +390 light /data h=4065 segments=5
36 +390 light /indicators h=48272 segments=6
37 +390 light /regions h=4489 segments=6
38 +390 light /compare h=2491 segments=3
39 +390 dark / h=9854 segments=6
40 +390 dark /countries h=14199 segments=6
41 +390 dark /countries/canada h=8620 segments=6
42 +390 dark /countries/canada/economy h=7460 segments=6
43 +390 dark /compare/canada/united-states/france h=4045 segments=5
44 +390 dark /compare/canada/united-states/france?tab=economy h=12773 segments=6
45 +390 dark /rankings h=24430 segments=6
46 +390 dark /rankings/gdp-per-capita h=4603 segments=6
47 +390 dark /indicators/life-expectancy h=6494 segments=6
48 +390 dark /regions/oecd h=9300 segments=6
49 +390 dark /explore h=8606 segments=6
50 +390 dark /changes h=13813 segments=6
51 +390 dark /sources h=3556 segments=5
52 +390 dark /methodology h=11263 segments=6
53 +390 dark /api h=9020 segments=6
54 +390 dark /data h=4065 segments=5
55 +390 dark /indicators h=48272 segments=6
56 +390 dark /regions h=4489 segments=6
57 +390 dark /compare h=2491 segments=3
58 +1440 light / h=5223 segments=4
59 +1440 light /countries h=13393 segments=4
60 +1440 light /countries/canada h=4473 segments=4
61 +1440 light /countries/canada/economy h=5049 segments=4
62 +FAILED /compare/canada/united-states/france: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/compare/canada/united-states/france
63 +Call log:
64 +[2
65 +FAILED /compare/canada/united-states/france?tab=economy: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/compare/canada/united-states/france?tab=economy
66 +C
67 +FAILED /rankings: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/rankings
68 +Call log:
69 + - navigating to "http://
70 +FAILED /rankings/gdp-per-capita: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/rankings/gdp-per-capita
71 +Call log:
72 + - navigat
73 +FAILED /indicators/life-expectancy: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/indicators/life-expectancy
74 +Call log:
75 + - navi
76 +FAILED /regions/oecd: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/regions/oecd
77 +Call log:
78 + - navigating to "htt
79 +FAILED /explore: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/explore
80 +Call log:
81 + - navigating to "http://l
82 +FAILED /changes: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/changes
83 +Call log:
84 + - navigating to "http://l
85 +FAILED /sources: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/sources
86 +Call log:
87 + - navigating to "http://l
88 +FAILED /methodology: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/methodology
89 +Call log:
90 + - navigating to "http
91 +FAILED /api: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/api
92 +Call log:
93 + - navigating to "http://local
94 +FAILED /data: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/data
95 +Call log:
96 + - navigating to "http://loca
97 +FAILED /indicators: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/indicators
98 +Call log:
99 + - navigating to "http:
100 +FAILED /regions: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/regions
101 +Call log:
102 + - navigating to "http://l
103 +FAILED /compare: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/compare
104 +Call log:
105 + - navigating to "http://l
106 +FAILED /: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/
107 +Call log:
108 + - navigating to "http://localhos
109 +FAILED /countries: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/countries
110 +Call log:
111 + - navigating to "http:/
112 +FAILED /countries/canada: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/countries/canada
113 +Call log:
114 + - navigating to
115 +FAILED /countries/canada/economy: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/countries/canada/economy
116 +Call log:
117 + - naviga
118 +FAILED /compare/canada/united-states/france: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/compare/canada/united-states/france
119 +Call log:
120 +[2
121 +FAILED /compare/canada/united-states/france?tab=economy: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/compare/canada/united-states/france?tab=economy
122 +C
123 +FAILED /rankings: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/rankings
124 +Call log:
125 + - navigating to "http://
126 +FAILED /rankings/gdp-per-capita: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/rankings/gdp-per-capita
127 +Call log:
128 + - navigat
129 +FAILED /indicators/life-expectancy: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/indicators/life-expectancy
130 +Call log:
131 + - navi
132 +FAILED /regions/oecd: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/regions/oecd
133 +Call log:
134 + - navigating to "htt
135 +FAILED /explore: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/explore
136 +Call log:
137 + - navigating to "http://l
138 +FAILED /changes: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/changes
139 +Call log:
140 + - navigating to "http://l
141 +FAILED /sources: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/sources
142 +Call log:
143 + - navigating to "http://l
144 +FAILED /methodology: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/methodology
145 +Call log:
146 + - navigating to "http
147 +FAILED /api: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/api
148 +Call log:
149 + - navigating to "http://local
150 +FAILED /data: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/data
151 +Call log:
152 + - navigating to "http://loca
153 +FAILED /indicators: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/indicators
154 +Call log:
155 + - navigating to "http:
156 +FAILED /regions: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/regions
157 +Call log:
158 + - navigating to "http://l
159 +FAILED /compare: Error: page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:8290/compare
160 +Call log:
161 + - navigating to "http://l
added apps/web/qa/screens/polish/seg/api-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/api-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/changes-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/compare_canada_united-states_france_tab_economy-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/countries_canada_economy-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/data-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/explore-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/home-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/indicators_life-expectancy-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/methodology-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/rankings_gdp-per-capita-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-1440-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-1440-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-320-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-390-dark-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/regions_oecd-390-light-5.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-1440-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-1440-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-1440-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-1440-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-1440-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-1440-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-320-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-320-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-320-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-320-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-320-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-390-dark-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-390-dark-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-390-dark-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-390-dark-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-390-dark-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-390-light-0.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-390-light-1.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-390-light-2.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-390-light-3.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/seg/sources-390-light-4.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/sources-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/sources-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/sources_worldbank-320-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/sources_worldbank-375-light.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/sweep1.log +10 −0
@@ -0,0 +1,10 @@
1 +node:internal/modules/run_main:107
2 + triggerUncaughtException(
3 + ^
4 +
5 +page.waitForTimeout: Target page, context or browser has been closed
6 + at /Users/simon-pierreboucher/Desktop/Projets/apps-web/countryatlas/apps/web/qa/polish-sweep.mjs:69:18 {
7 + log: []
8 +}
9 +
10 +Node.js v25.9.0
added apps/web/qa/screens/polish/verify/can-eco-m.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/verify/can-health-d.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/verify/can-health-m.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/verify/cmp-eco-m.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/verify/cmp-health-d.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/verify/cmp-health-m.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/verify/home-d.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/verify/le-ind-d.png +0 −0

Binary file not shown.

added apps/web/qa/screens/polish/verify/le-ind-m.png +0 −0

Binary file not shown.

modified apps/web/src/app/(home)/page.tsx +9 −5
@@ -18,7 +18,8 @@ import { TopicsGrid } from '@/components/home/topics-grid';
18 18 export const metadata: Metadata = { alternates: { canonical: '/' } };
19 19 export const revalidate = 900;
20 20
21 −const LIST_KEYS = ['largest_economies', 'fastest_population_growth', 'highest_life_expectancy', 'energy_transition_leaders'] as const;
21 +/** Home lists in display order; a key missing from the API payload is simply skipped (e.g. fastest_gdp_growth). */
22 +const LIST_KEYS = ['largest_economies', 'fastest_gdp_growth', 'highest_gdp_per_capita_ppp', 'highest_life_expectancy', 'fastest_population_growth', 'energy_transition_leaders'] as const;
22 23
23 24 export default async function HomePage() {
24 25 let home: Awaited<ReturnType<typeof api.home>> | null = null;
@@ -58,14 +59,17 @@ export default async function HomePage() {
58 59 <div className="grid gap-x-10 gap-y-8 lg:grid-cols-2">
59 60 {LIST_KEYS.map((key) => {
60 61 const list = home.lists[key];
61 − if (!list) return null;
62 + if (!list || !list.rows?.length) return null;
63 + // Freshness honesty: a row ≥ 2 years older than the newest row of the list shows its year.
64 + const latestYear = Math.max(0, ...list.rows.map((r) => r.year ?? 0));
62 65 return (
63 − <div key={key}>
64 − <div className="mb-2 flex items-baseline justify-between gap-3">
66 + <div key={key} className="min-w-0">
67 + <div className="mb-2 flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5">
65 68 <h3 className="text-base font-semibold text-ink">{list.title}</h3>
66 69 <span className="text-xs text-ink-3">{list.description}</span>
67 70 </div>
68 − <RankedBars rows={list.rows.map((r) => rankedRowFromCountry(r.country, r.value, r.rank))} spec={list.indicator} provenance={list.rows[0]?.provenance ?? null} />
71 + {list.filter_note ? <p className="-mt-1 mb-2 text-xs text-ink-3">{list.filter_note}</p> : null}
72 + <RankedBars rows={list.rows.map((r) => rankedRowFromCountry(r.country, r.value, r.rank, null, r.year != null && latestYear - r.year >= 2 ? r.year : null))} spec={list.indicator} provenance={list.rows[0]?.provenance ?? null} />
69 73 <Link href={routes.ranking(list.indicator.slug)} className="mt-2 inline-flex min-h-[36px] items-center text-sm text-accent hover:underline">
70 74 {t('common.seeFullRanking')} →
71 75 </Link>
modified apps/web/src/app/compare/[...slugs]/page.tsx +2 −11
@@ -9,6 +9,7 @@ import { topicById } from '@/lib/topics';
9 9 import type { CountrySummary, IndicatorCard } from '@/lib/types';
10 10 import { toCountryLite, type CompareSeries, type CompareSnapshotRow, type CountryLite } from '@/lib/types-compare';
11 11 import { CompareChart } from '@/components/compare/compare-chart';
12 +import { ChartGrid, ChartSections } from '@/components/compare/chart-sections';
12 13 import { CompareControls } from '@/components/compare/compare-controls';
13 14 import { CustomPanel } from '@/components/compare/custom-panel';
14 15 import { SnapshotTable } from '@/components/compare/snapshot-table';
@@ -164,7 +165,7 @@ export default async function CompareViewPage({ params, searchParams }: { params
164 165 {gridRows.length === 0 ? (
165 166 <p className="py-6 text-sm text-ink-3">{t('compare.charts.none', { topic: (topicDef?.short ?? topic).toLowerCase() })}</p>
166 167 ) : (
167 − <ChartGrid rows={gridRows} countries={countries} state={state} eager={eager} maxYear={maxYear} />
168 + <ChartSections rows={gridRows} countries={countries} slugs={slugs} state={state} eager={eager} maxYear={maxYear} anchorPrefix={`sub-${topic}`} />
168 169 )}
169 170 </Section>
170 171 ) : null}
@@ -185,13 +186,3 @@ export default async function CompareViewPage({ params, searchParams }: { params
185 186 </>
186 187 );
187 188 }
188 −
189 −function ChartGrid({ rows, countries, state, eager, maxYear }: { rows: CompareSnapshotRow[]; countries: CountryLite[]; state: CompareState; eager: Map<string, CompareSeries[]>; maxYear: number }) {
190 − return (
191 − <div className="grid gap-x-10 gap-y-8 lg:grid-cols-2">
192 − {rows.map((row) => (
193 − <CompareChart key={row.indicator.slug} indicator={row.indicator} countries={countries} state={state} initial={eager.get(row.indicator.slug)} snapshot={row.values} maxYear={maxYear} />
194 − ))}
195 − </div>
196 − );
197 −}
modified apps/web/src/app/countries/[slug]/[topic]/page.tsx +32 −10
@@ -8,6 +8,9 @@ import { regionShort } from '@/lib/regions';
8 8 import { TOPICS, isTopicId, topicById } from '@/lib/topics';
9 9 import type { CountryTopicResponse, MetricValue, SeriesResponse } from '@/lib/types';
10 10 import { IndicatorRow } from '@/components/country/indicator-row';
11 +import { TopicCompact } from '@/components/country/topic-compact';
12 +import { subtopicAnchor } from '@/lib/anchors';
13 +import { CollapsibleGroup, SubtopicJumpNav } from '@/components/data/collapsible-group';
11 14 import { NotBuiltState } from '@/components/data/empty-state';
12 15 import { Section } from '@/components/data/section';
13 16 import { TopicNav } from '@/components/data/topic-nav';
@@ -16,6 +19,8 @@ export const revalidate = 900;
16 19
17 20 type Params = { slug: string; topic: string };
18 21 const EAGER = 4; // charts rendered with server-fetched full history; the rest mount on scroll
22 +const SECTION_THRESHOLD = 12; // above this many indicators with data → collapsible subtopic blocks
23 +const OPEN_BLOCKS = 2; // blocks expanded by default on a sectioned topic page
19 24
20 25 async function load(slug: string, topic: string): Promise<CountryTopicResponse | 'not-built' | null> {
21 26 if (!isTopicId(topic)) return null;
@@ -59,7 +64,14 @@ export default async function CountryTopicPage({ params }: { params: Promise<Par
59 64 const all = data.subtopics.flatMap((b) => b.indicators);
60 65 const withData = all.filter((m) => m.has_data);
61 66 const noData = all.filter((m) => !m.has_data);
62 − const eagerIds = withData.slice(0, EAGER).map((m) => m.indicator);
67 + const blocks = data.subtopics.map((b) => ({ ...b, rows: b.indicators.filter((m) => m.has_data) })).filter((b) => b.rows.length > 0);
68 + // Long topics (> 12 indicators with data, ≥ 2 subtopics): collapsible subtopic blocks, first two expanded.
69 + const sectioned = withData.length > SECTION_THRESHOLD && blocks.length >= 2;
70 + const openBlocks = sectioned ? blocks.slice(0, OPEN_BLOCKS) : blocks;
71 + const eagerIds = openBlocks
72 + .flatMap((b) => b.rows)
73 + .slice(0, EAGER)
74 + .map((m) => m.indicator);
63 75 const eagerSeries = await Promise.all(eagerIds.map((iid) => safe(api.countrySeries(c.id, iid))));
64 76 const seriesById = new Map<string, SeriesResponse | null>(eagerIds.map((iid, i) => [iid, eagerSeries[i] ?? null]));
65 77 const def = topicById(topic);
@@ -88,16 +100,26 @@ export default async function CountryTopicPage({ params }: { params: Promise<Par
88 100 </header>
89 101 <TopicNav slug={countryRef.slug} />
90 102
91 − {data.subtopics.map((block) => {
92 − const rows = block.indicators.filter((m) => m.has_data);
93 − if (rows.length === 0) return null;
103 + {sectioned ? <SubtopicJumpNav items={blocks.map((b) => ({ id: subtopicAnchor('sub', b.subtopic), label: b.subtopic, count: b.rows.length }))} label={t('topic.jump')} className="mt-3" /> : null}
104 +
105 + {blocks.map((block, i) => {
106 + const id = subtopicAnchor('sub', block.subtopic);
107 + const rowsEl = (
108 + <div>
109 + {block.rows.map((m: MetricValue) => (
110 + <IndicatorRow key={m.indicator} metric={m} country={countryRef} regionName={regionShort(c.region) ?? c.region_name} series={seriesById.get(m.indicator)} eager={seriesById.has(m.indicator)} />
111 + ))}
112 + </div>
113 + );
114 + if (sectioned)
115 + return (
116 + <CollapsibleGroup key={block.subtopic} id={id} title={block.subtopic} count={block.rows.length} defaultOpen={i < OPEN_BLOCKS} summary={<TopicCompact rows={block.rows} country={countryRef} regionName={regionShort(c.region) ?? c.region_name} />}>
117 + {rowsEl}
118 + </CollapsibleGroup>
119 + );
94 120 return (
95 − <Section key={block.subtopic} id={`sub-${block.subtopic.toLowerCase().replace(/\W+/g, '-')}`} title={block.subtopic} level={3} tight className="pb-2 pt-6">
96 − <div>
97 − {rows.map((m: MetricValue) => (
98 − <IndicatorRow key={m.indicator} metric={m} country={countryRef} regionName={regionShort(c.region) ?? c.region_name} series={seriesById.get(m.indicator)} eager={seriesById.has(m.indicator)} />
99 − ))}
100 − </div>
121 + <Section key={block.subtopic} id={id} title={block.subtopic} level={3} tight className="pb-2 pt-6">
122 + {rowsEl}
101 123 </Section>
102 124 );
103 125 })}
modified apps/web/src/app/countries/[slug]/opengraph-image.tsx +2 −1
@@ -1,6 +1,7 @@
1 1 import { ImageResponse } from 'next/og';
2 2 import { t } from '@/i18n';
3 3 import { api, safe } from '@/lib/api';
4 +import { displayValue } from '@/lib/format';
4 5 import { OG_INK2, OG_INK3, OG_RULE, OG_SIZE_H, OG_SIZE_W, OgFrame, OgWordmark } from '../../og-shared';
5 6
6 7 export const alt = 'Country statistics on CountryAtlas';
@@ -35,7 +36,7 @@ export default async function CountryOgImage({ params }: { params: Promise<{ slu
35 36 {facts.map((m) => (
36 37 <div key={m.indicator} style={{ display: 'flex', flexDirection: 'column' }}>
37 38 <div style={{ fontSize: 18, color: OG_INK3, textTransform: 'uppercase', letterSpacing: 1.5 }}>{m.indicator_name ?? m.indicator}</div>
38 − <div style={{ fontSize: 44, fontWeight: 600, marginTop: 6 }}>{m.formatted ?? String(m.value ?? '')}</div>
39 + <div style={{ fontSize: 44, fontWeight: 600, marginTop: 6 }}>{displayValue(m.value, m, m.formatted)}</div>
39 40 <div style={{ fontSize: 18, color: OG_INK3, marginTop: 2 }}>{String(m.year ?? '')}</div>
40 41 </div>
41 42 ))}
modified apps/web/src/app/rankings/[indicator]/opengraph-image.tsx +2 −1
@@ -2,6 +2,7 @@ import { ImageResponse } from 'next/og';
2 2 import { t } from '@/i18n';
3 3 import { safe } from '@/lib/api';
4 4 import { apiCompare } from '@/lib/api-compare';
5 +import { displayValue } from '@/lib/format';
5 6 import { OG_INK2, OG_INK3, OG_RULE, OG_SIZE_H, OG_SIZE_W, OgFrame, OgWordmark } from '../../og-shared';
6 7
7 8 export const alt = 'Country ranking on CountryAtlas';
@@ -31,7 +32,7 @@ export default async function RankingOgImage({ params }: { params: Promise<{ ind
31 32 <div style={{ fontSize: 44, lineHeight: 1, display: 'flex' }}>{r.country.flag ?? ''}</div>
32 33 <div style={{ display: 'flex', flexDirection: 'column' }}>
33 34 <div style={{ fontSize: 20, color: OG_INK2, display: 'flex' }}>{r.country.name ?? r.country.id}</div>
34 − <div style={{ fontSize: 30, fontWeight: 600, display: 'flex' }}>{r.formatted ?? String(r.value ?? '')}</div>
35 + <div style={{ fontSize: 30, fontWeight: 600, display: 'flex' }}>{displayValue(r.value, data!.indicator, r.formatted)}</div>
35 36 </div>
36 37 </div>
37 38 ))}
modified apps/web/src/app/regions/[slug]/page.tsx +1 −1
@@ -41,7 +41,7 @@ export async function generateMetadata({ params }: { params: Promise<Params> }):
41 41 }
42 42
43 43 /** Extra indicators offered in the member ranking picker besides the headline ones. */
44 −const EXTRA_RANKING = ['gdp-per-capita-ppp', 'population-growth', 'fertility-rate', 'renewable-electricity-share', 'internet-users', 'co2-per-capita', 'government-debt-pct-gdp', 'median-age'];
44 +const EXTRA_RANKING = ['gdp-per-capita-ppp', 'population-growth', 'fertility-rate', 'renewable-electricity-share', 'internet-users', 'co2-per-capita', 'general-government-gross-debt-pct-gdp', 'median-age'];
45 45
46 46 export default async function RegionPage({ params }: { params: Promise<Params> }) {
47 47 const { slug } = await params;
modified apps/web/src/app/regions/page.tsx +3 −3
@@ -64,19 +64,19 @@ export default async function RegionsPage() {
64 64 </span>
65 65 <dl className="tnum mt-1 grid grid-cols-3 gap-x-3 text-xs">
66 66 <div className="min-w-0">
67 − <dt className="truncate text-2xs text-ink-3">
67 + <dt className="text-2xs leading-tight text-ink-3">
68 68 {t('regions.population')} <span className="text-ink-3/80">({t('regions.sum')})</span>
69 69 </dt>
70 70 <dd className="truncate text-ink">{isNum(g.population_latest) ? compact(g.population_latest) : t('common.na')}</dd>
71 71 </div>
72 72 <div className="min-w-0">
73 − <dt className="truncate text-2xs text-ink-3">
73 + <dt className="text-2xs leading-tight text-ink-3">
74 74 {t('regions.gdp')} <span className="text-ink-3/80">({t('regions.sum')})</span>
75 75 </dt>
76 76 <dd className="truncate text-ink">{isNum(g.gdp_latest) ? `US$${compact(g.gdp_latest)}` : t('common.na')}</dd>
77 77 </div>
78 78 <div className="min-w-0">
79 − <dt className="truncate text-2xs text-ink-3">
79 + <dt className="text-2xs leading-tight text-ink-3">
80 80 {le ? `${t('regions.lifeExpectancy')} (${t('regions.median')})` : gdppc ? `${t('common.gdpPerCapita')} (${t('regions.weighted_mean')})` : t('regions.lifeExpectancy')}
81 81 </dt>
82 82 <dd className="truncate text-ink">{le ? formatValue(le.value, le.indicator) : gdppc ? formatValue(gdppc.value, gdppc.indicator) : t('common.na')}</dd>
modified apps/web/src/components/charts/line-chart.tsx +10 −5
@@ -7,7 +7,7 @@ import type { FormatSpec as Spec, Provenance } from '@/lib/types';
7 7 import type { ProvenancePayload } from '@/components/data/provenance-context';
8 8 import { ChartFrame, Legend, type TableData } from './chart-frame';
9 9 import { CHART, MARK, seriesVar } from './palette';
10 −import { DEFAULT_MARGIN, extent, periodToX, splitForecast, xYearScale, yScale, yearTicks, type Margin, type SeriesPoint } from './scales';
10 +import { DEFAULT_MARGIN, extent, lineDomain, periodToX, splitForecast, xYearScale, yScale, yearTicks, type Margin, type SeriesPoint } from './scales';
11 11 import { summarizeMulti, summarizeSeries } from './summary';
12 12 import { ChartTooltip, TooltipRow } from './tooltip';
13 13 import { useMeasure } from './use-measure';
@@ -76,9 +76,11 @@ export function LineChart({
76 76 const margin: Margin = { ...DEFAULT_MARGIN, ...marginIn };
77 77 if (endLabels && series.length > 1 && series.length <= 4) margin.right = Math.max(margin.right, 72);
78 78 // Left margin fits the longest y tick label (≈ 6.4 px per character at 11 px) so labels are never clipped.
79 + // Uses the same domain rule as the chart itself (lineDomain) so the probe ticks are the real ticks.
79 80 if (marginIn?.left == null) {
80 81 const probe = extent(series.flatMap((s) => s.points.map((p) => p.value))) ?? [0, 1];
81 − const longest = Math.max(...yScale(probe, [0, 1], { log }).ticks(4).map((tk) => formatTick(tk, spec).length), 3);
82 + const dom = variant === 'stacked' ? ([0, probe[1]] as [number, number]) : log && probe[0] > 0 ? probe : lineDomain(probe, spec);
83 + const longest = Math.max(...yScale(dom, [0, 1], { log, includeZero: variant === 'stacked' }).ticks(4).map((tk) => formatTick(tk, spec).length), 3);
82 84 margin.left = Math.min(84, Math.max(36, Math.round(longest * 6.4) + 12));
83 85 }
84 86 const [hover, setHover] = useState<number | null>(null); // x-value (fractional year)
@@ -110,9 +112,11 @@ export function LineChart({
110 112 const top = Math.max(0, ...stacks.flat().map((d) => d[2]));
111 113 yDom = [0, top];
112 114 } else {
113 − yDom = extent(clean.flatMap((s) => s.points.map((p) => p.value))) ?? [0, 1];
115 + // Line / area: padded nice extent; zero only when the rule says so (see lineDomain). Log keeps the raw extent.
116 + const raw = extent(clean.flatMap((s) => s.points.map((p) => p.value))) ?? [0, 1];
117 + yDom = log && raw[0] > 0 ? raw : lineDomain(raw, spec);
114 118 }
115 − const y = yScale(yDom, [innerH, 0], { log, includeZero: variant !== 'line' || !log });
119 + const y = yScale(yDom, [innerH, 0], { log, includeZero: variant === 'stacked' });
116 120 const xTicks = yearTicks(xDom, Math.max(2, Math.floor(innerW / 90)));
117 121 const yTicks = y.ticks(4);
118 122 const pathFor = d3Line<XY>()
@@ -146,7 +150,8 @@ export function LineChart({
146 150 });
147 151
148 152 return { clean, x, y, innerW, innerH, xTicks, yTicks, pathFor, areaFor, layers, stackPaths, xDom };
149 − }, [series, width, height, margin.left, margin.right, margin.top, margin.bottom, variant, log]);
153 + // eslint-disable-next-line react-hooks/exhaustive-deps -- only spec.format drives the domain rule
154 + }, [series, width, height, margin.left, margin.right, margin.top, margin.bottom, variant, log, spec.format]);
150 155
151 156 const onMove = useCallback(
152 157 (e: PointerEvent<SVGRectElement>) => {
modified apps/web/src/components/charts/ranked-bars.tsx +25 −15
@@ -13,8 +13,10 @@ export interface RankedBarRow {
13 13 href?: string | null;
14 14 value: number | null;
15 15 rank?: number | null;
16 − /** Secondary text shown right of the value (e.g. change). */
16 + /** Secondary text shown right of the value (e.g. change); hidden on phones. */
17 17 hint?: string | null;
18 + /** Observation year — pass it only when it is older than the list's reference year (freshness honesty); always visible. */
19 + year?: number | null;
18 20 }
19 21
20 22 /**
@@ -52,37 +54,40 @@ export function RankedBars({
52 54 {rows.map((r, i) => {
53 55 const pct = isNum(r.value) && max > 0 ? Math.max(0, (Math.abs(r.value) / max) * 100) : 0;
54 56 const hl = highlightId && r.id === highlightId;
57 + // Names wrap on phones (never clipped mid-word); they truncate only in the fixed 13 rem column on sm+.
55 58 const label = (
56 − <span className={cn('flex min-w-0 items-center gap-1.5 truncate text-sm', hl ? 'font-semibold text-ink' : 'text-ink')}>
59 + <span className={cn('flex min-w-0 items-center gap-1.5 text-sm leading-snug', hl ? 'font-semibold text-ink' : 'text-ink')}>
57 60 {r.flag ? (
58 61 <span aria-hidden className="text-base leading-none">
59 62 {r.flag}
60 63 </span>
61 64 ) : null}
62 − <span className="truncate">{r.label}</span>
65 + <span className="min-w-0 sm:truncate">{r.label}</span>
63 66 </span>
64 67 );
68 + // Phones: [rank + name] [value] on line 1, bar on line 2. sm+: [rank + name] [bar] [value hint] on one line.
65 69 return (
66 − <li key={r.id} className="grid grid-cols-[minmax(0,46%)_1fr] items-center gap-x-3 py-1.5 sm:grid-cols-[minmax(0,13rem)_1fr]">
70 + <li key={r.id} className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 gap-y-1 py-1.5 sm:grid-cols-[minmax(0,13rem)_1fr_auto] sm:gap-y-0">
67 71 <div className="flex min-w-0 items-center gap-2">
68 72 {showRank ? <span className="tnum w-6 shrink-0 text-right text-xs text-ink-3">{r.rank ?? i + 1}</span> : null}
69 73 {linkRows && r.href ? (
70 − <Link href={r.href} className="link-quiet min-w-0 truncate">
74 + <Link href={r.href} className="link-quiet min-w-0 sm:truncate">
71 75 {label}
72 76 </Link>
73 77 ) : (
74 78 label
75 79 )}
76 80 </div>
77 − <div className="flex min-w-0 items-center gap-2">
78 − <div className="h-4 min-w-0 flex-1" style={{ maxHeight: MARK.barMax }}>
79 − <div
80 − className={cn('h-full rounded-r-sm', hl ? 'bg-accent' : 'bg-series-1')}
81 − style={{ width: `${pct}%`, minWidth: isNum(r.value) ? 2 : 0, borderRadius: `0 ${MARK.barRadius}px ${MARK.barRadius}px 0`, opacity: hl ? 1 : 0.85 }}
82 − />
83 − </div>
84 − <span className={cn('tnum shrink-0 text-right text-sm', hl ? 'font-semibold text-ink' : 'text-ink')} style={{ minWidth: '4.5rem' }}>
81 + <div className="col-span-2 row-start-2 h-3 min-w-0 sm:col-span-1 sm:col-start-2 sm:row-start-1 sm:h-4" style={{ maxHeight: MARK.barMax }}>
82 + <div
83 + className={cn('h-full rounded-r-sm', hl ? 'bg-accent' : 'bg-series-1')}
84 + style={{ width: `${pct}%`, minWidth: isNum(r.value) ? 2 : 0, borderRadius: `0 ${MARK.barRadius}px ${MARK.barRadius}px 0`, opacity: hl ? 1 : 0.85 }}
85 + />
86 + </div>
87 + <div className="col-start-2 row-start-1 flex shrink-0 items-center justify-end gap-2 sm:col-start-3">
88 + <span className={cn('tnum whitespace-nowrap text-right text-sm', hl ? 'font-semibold text-ink' : 'text-ink')} style={{ minWidth: '4.5rem' }}>
85 89 {formatValue(r.value, spec)}
90 + {r.year != null ? <span className="tnum ml-1 rounded-xs bg-surface-2 px-1 text-2xs font-normal text-ink-2">{r.year}</span> : null}
86 91 </span>
87 92 {r.hint ? <span className="tnum hidden shrink-0 text-xs text-ink-3 sm:inline">{r.hint}</span> : null}
88 93 </div>
@@ -101,8 +106,13 @@ export function RankedBars({
101 106 }
102 107
103 108 /** Convenience: a country row for RankedBars from a ranking row. */
104 −export function rankedRowFromCountry(c: { id: string; slug: string | null; name: string | null; flag: string | null }, value: number | null, rank?: number | null, hint?: string | null): RankedBarRow {
105 − return { id: c.id, label: c.name ?? c.id, flag: c.flag, href: c.slug ? routes.country(c.slug) : null, value, rank, hint };
109 +export function rankedRowFromCountry(c: { id: string; slug: string | null; name: string | null; flag: string | null }, value: number | null, rank?: number | null, hint?: string | null, year?: number | null): RankedBarRow {
110 + return { id: c.id, label: c.name ?? c.id, flag: c.flag, href: c.slug ? routes.country(c.slug) : null, value, rank, hint, year };
111 +}
112 +
113 +/** Year to display for a row (freshness honesty): only when ≥ 2 years older than the reference year. */
114 +export function staleYear(year: number | null | undefined, reference: number | null | undefined): number | null {
115 + return year != null && reference != null && reference - year >= 2 ? year : null;
106 116 }
107 117
108 118 export { ordinal };
modified apps/web/src/components/charts/scales.ts +33 −4
@@ -49,6 +49,28 @@ export function extent(values: Array<number | null | undefined>): [number, numbe
49 49 return [lo, hi];
50 50 }
51 51
52 +/**
53 + * Y domain for LINE / AREA charts (dataviz rule: a line need not start at zero — a tight series such as
54 + * life expectancy 60→83 must not render flat). Zero is included only when
55 + * - the data crosses or touches zero (min ≤ 0), or
56 + * - the minimum is within 30 % of the maximum (min < 0.3·max: the zero baseline costs little), or
57 + * - the indicator is a percent share / index whose floor is 0 AND the data spans more than half of [0, max].
58 + * Otherwise the extent is padded by ≈ 6 % on each side (then `.nice()`d by the scale). Bars and stacked
59 + * areas keep the zero baseline (they do not use this).
60 + */
61 +export function lineDomain(dom: [number, number], spec?: { format?: string | null } | null): [number, number] {
62 + const [lo, hi] = dom;
63 + if (lo === hi) {
64 + const pad = Math.abs(lo) * 0.1 || 1;
65 + return [lo - pad, hi + pad];
66 + }
67 + const isShare = spec?.format === 'percent' || spec?.format === 'index';
68 + const includeZero = lo <= 0 || lo < 0.3 * hi || (isShare && hi - lo > 0.5 * hi);
69 + if (includeZero) return [Math.min(0, lo), Math.max(0, hi)];
70 + const pad = (hi - lo) * 0.06;
71 + return [lo - pad, hi + pad];
72 +}
73 +
52 74 /** Y scale: linear (zero-anchored when the data allows) or log for strictly positive values. */
53 75 export function yScale(
54 76 domain: [number, number],
@@ -81,10 +103,17 @@ export function yearTicks(domain: [number, number], n = 5): number[] {
81 103 const [lo, hi] = domain;
82 104 const span = Math.max(1, hi - lo);
83 105 const steps = [1, 2, 5, 10, 20, 25, 50, 100];
84 − const step = steps.find((s) => span / s <= n) ?? 100;
85 − const start = Math.ceil(lo / step) * step;
86 − const out: number[] = [];
87 − for (let y = start; y <= hi; y += step) out.push(y);
106 + let idx = steps.findIndex((s) => span / s <= n);
107 + if (idx < 0) idx = steps.length - 1;
108 + const build = (step: number) => {
109 + const start = Math.ceil(lo / step) * step;
110 + const out: number[] = [];
111 + for (let y = start; y <= hi; y += step) out.push(y);
112 + return out;
113 + };
114 + // Always at least two labelled years (a lone "2000" on a 1960–2025 axis reads as nothing): step down if needed.
115 + let out = build(steps[idx]!);
116 + while (out.length < 2 && idx > 0) out = build(steps[--idx]!);
88 117 if (out.length === 0) out.push(lo, hi);
89 118 return out;
90 119 }
added apps/web/src/components/compare/chart-sections.tsx +53 −0
@@ -0,0 +1,53 @@
1 +import { t } from '@/i18n';
2 +import type { CompareState } from '@/lib/compare-state';
3 +import type { CompareSeries, CompareSnapshotRow, CountryLite } from '@/lib/types-compare';
4 +import { subtopicAnchor } from '@/lib/anchors';
5 +import { CollapsibleGroup, SubtopicJumpNav } from '@/components/data/collapsible-group';
6 +import { CompareChart } from './compare-chart';
7 +import { SnapshotCompact } from './snapshot-table';
8 +
9 +/** Above this many charts a topic tab is split into collapsible subtopic blocks (first one expanded). */
10 +const SECTION_THRESHOLD = 8;
11 +
12 +export function ChartGrid({ rows, countries, state, eager, maxYear }: { rows: CompareSnapshotRow[]; countries: CountryLite[]; state: CompareState; eager: Map<string, CompareSeries[]>; maxYear: number }) {
13 + return (
14 + <div className="grid gap-x-10 gap-y-8 lg:grid-cols-2">
15 + {rows.map((row) => (
16 + <CompareChart key={row.indicator.slug} indicator={row.indicator} countries={countries} state={state} initial={eager.get(row.indicator.slug)} snapshot={row.values} maxYear={maxYear} />
17 + ))}
18 + </div>
19 + );
20 +}
21 +
22 +/** Group rows by `indicator.subtopic`, preserving first-seen order. */
23 +export function groupBySubtopic<T extends { indicator: { subtopic: string | null } }>(rows: T[]): Array<{ subtopic: string; rows: T[] }> {
24 + const m = new Map<string, T[]>();
25 + for (const r of rows) {
26 + const k = r.indicator.subtopic ?? t('common.other');
27 + const cur = m.get(k);
28 + if (cur) cur.push(r);
29 + else m.set(k, [r]);
30 + }
31 + return Array.from(m, ([subtopic, rs]) => ({ subtopic, rows: rs }));
32 +}
33 +
34 +/**
35 + * Charts of a compare topic tab. Short tabs → plain grid. Long tabs (> 8 charts, ≥ 2 subtopics) → a jump nav +
36 + * one CollapsibleGroup per subtopic: the first expanded (charts), the others collapsed showing the latest
37 + * values inline (SnapshotCompact) so nothing is hidden, just deferred. Charts still lazy-mount on scroll.
38 + */
39 +export function ChartSections({ rows, countries, slugs, state, eager, maxYear, anchorPrefix }: { rows: CompareSnapshotRow[]; countries: CountryLite[]; slugs: string[]; state: CompareState; eager: Map<string, CompareSeries[]>; maxYear: number; anchorPrefix: string }) {
40 + const groups = groupBySubtopic(rows);
41 + if (rows.length <= SECTION_THRESHOLD || groups.length < 2) return <ChartGrid rows={rows} countries={countries} state={state} eager={eager} maxYear={maxYear} />;
42 + const items = groups.map((g) => ({ id: subtopicAnchor(anchorPrefix, g.subtopic), label: g.subtopic, count: g.rows.length }));
43 + return (
44 + <div className="min-w-0">
45 + <SubtopicJumpNav items={items} label={t('topic.jump')} className="mb-2" />
46 + {groups.map((g, i) => (
47 + <CollapsibleGroup key={g.subtopic} id={items[i]!.id} title={g.subtopic} count={g.rows.length} defaultOpen={i === 0} summary={<SnapshotCompact rows={g.rows} countries={countries} slugs={slugs} state={state} />}>
48 + <ChartGrid rows={g.rows} countries={countries} state={state} eager={eager} maxYear={maxYear} />
49 + </CollapsibleGroup>
50 + ))}
51 + </div>
52 + );
53 +}
modified apps/web/src/components/compare/compare-chart.tsx +2 −2
@@ -7,7 +7,7 @@ import { t } from '@/i18n';
7 7 import { clientCompare } from '@/lib/client-api-compare';
8 8 import { cn } from '@/lib/cn';
9 9 import { compareQuery, type CompareState } from '@/lib/compare-state';
10 −import { formatDate, formatPeriod, formatValue } from '@/lib/format';
10 +import { displayValue, formatDate, formatPeriod, formatValue } from '@/lib/format';
11 11 import { routes } from '@/lib/site';
12 12 import type { FormatSpec, IndicatorCard, MetricValue } from '@/lib/types';
13 13 import type { CompareSeries, CountryLite } from '@/lib/types-compare';
@@ -186,7 +186,7 @@ export function CompareChart({ indicator, countries, state, initial, snapshot, h
186 186 <ul className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-xs" aria-label={t('compare.charts.latest')}>
187 187 {latest.map((l) => {
188 188 const has = !!l.last || !!l.snap;
189 − const text = l.last ? (state.mode === 'absolute' && l.snap?.formatted && l.snap.year === l.last.year ? l.snap.formatted : formatValue(l.last.value, spec)) : l.snap ? l.snap.formatted ?? formatValue(l.snap.value, l.snap) : t('common.noData');
189 + const text = l.last ? (state.mode === 'absolute' && l.snap?.formatted && l.snap.year === l.last.year ? displayValue(l.last.value, spec, l.snap.formatted) : formatValue(l.last.value, spec)) : l.snap ? displayValue(l.snap.value, l.snap, l.snap.formatted) : t('common.noData');
190 190 const when = l.last ? formatPeriod(l.last.period, indicator.frequency) : l.snap?.year;
191 191 return (
192 192 <li key={l.c.id}>
modified apps/web/src/components/compare/snapshot-table.tsx +49 −4
@@ -4,7 +4,7 @@ import Link from 'next/link';
4 4 import { t } from '@/i18n';
5 5 import { cn } from '@/lib/cn';
6 6 import { COMPARE_TOPIC_TABS, compareQuery, type CompareState, type CompareTab } from '@/lib/compare-state';
7 −import { formatValue, isNum, ordinal } from '@/lib/format';
7 +import { displayValue, isNum, ordinal } from '@/lib/format';
8 8 import { routes } from '@/lib/site';
9 9 import type { CompareSnapshotRow, CountryLite } from '@/lib/types-compare';
10 10 import type { MetricValue } from '@/lib/types';
@@ -19,6 +19,51 @@ export function chartHrefFor(slugs: string[], state: CompareState, indicator: {
19 19 return `${routes.compare(...slugs)}${compareQuery({ ...state, tab, indicator: indicator.slug, indicators })}`;
20 20 }
21 21
22 +/**
23 + * Compact latest-values table used inside a collapsed subtopic block (no charts): one row per indicator,
24 + * one cell per country (value · year, best highlighted). Indicator name → its chart (hero). Any cell →
25 + * provenance. Phones: value cells wrap under the indicator name.
26 + */
27 +export function SnapshotCompact({ rows, countries, slugs, state }: { rows: CompareSnapshotRow[]; countries: CountryLite[]; slugs: string[]; state: CompareState }) {
28 + const { open } = useProvenance();
29 + const openCell = (m: MetricValue, c: CountryLite, name: string) => open(payloadFor(m, { id: c.id, slug: c.slug, name: c.name, flag: c.flag }, { name }));
30 + if (rows.length === 0) return null;
31 + const latestYear = Math.max(0, ...rows.flatMap((r) => Object.values(r.values).map((v) => v.year ?? 0)));
32 + return (
33 + <ul className="divide-y divide-rule border-b border-rule text-sm" aria-label={t('group.latestCol')}>
34 + {rows.map((r) => {
35 + const name = r.indicator.short_name ?? r.indicator.name ?? r.indicator.slug;
36 + return (
37 + <li key={r.indicator.slug} className="grid gap-x-4 gap-y-1 py-1.5 md:grid-cols-[minmax(0,14rem)_1fr] lg:grid-cols-[minmax(0,18rem)_1fr]">
38 + <Link href={chartHrefFor(slugs, state, r.indicator)} className="group/i inline-flex min-h-[32px] min-w-0 items-center gap-1 text-ink hover:text-accent" aria-label={t('compare.snapshot.openChart', { name })}>
39 + <span className="truncate">{r.indicator.name ?? name}</span>
40 + <ChevronRight size={12} aria-hidden className="shrink-0 text-ink-3 group-hover/i:text-accent" />
41 + </Link>
42 + <ul className="flex flex-wrap gap-x-4 gap-y-0.5">
43 + {countries.map((c, i) => {
44 + const m = r.values[c.id];
45 + const has = !!m && m.has_data && isNum(m.value);
46 + const best = r.best === c.id;
47 + const stale = has && m.year != null && latestYear - m.year >= 2;
48 + return (
49 + <li key={c.id}>
50 + <button type="button" disabled={!has} onClick={() => has && openCell(m, c, r.indicator.name ?? name)} className="inline-flex min-h-[32px] items-center gap-1.5 rounded-sm px-1 hover:bg-surface-2 disabled:hover:bg-transparent" aria-label={has ? `${c.name}: ${displayValue(m.value, m, m.formatted)} (${m.year ?? ''}). ${t('common.openProvenance')}` : `${c.name}: ${t('common.noData')}`}>
51 + <span aria-hidden className="h-2 w-2 shrink-0 rounded-full" style={{ background: seriesVar(i) }} />
52 + <span aria-hidden>{c.flag}</span>
53 + <span className={cn('tnum', has ? (best ? 'font-semibold text-ink' : 'text-ink') : 'text-ink-3')}>{has ? displayValue(m.value, m, m.formatted) : t('common.noData')}</span>
54 + {has && m.year != null ? <span className={cn('tnum text-2xs', stale ? 'rounded-xs bg-surface-2 px-1 text-ink-2' : 'text-ink-3')}>{m.year}</span> : null}
55 + </button>
56 + </li>
57 + );
58 + })}
59 + </ul>
60 + </li>
61 + );
62 + })}
63 + </ul>
64 + );
65 +}
66 +
22 67 /**
23 68 * Snapshot comparison: countries as columns on md+ (value · year · rank chip per cell, best value highlighted);
24 69 * on phones each indicator is a block with one horizontal mini bar per country — no horizontal overflow.
@@ -80,7 +125,7 @@ export function SnapshotTable({ rows, countries, slugs, state }: { rows: Compare
80 125 return (
81 126 <td key={c.id} className={cn('py-1 pr-1 text-right align-top', best && 'bg-accent-soft/60')}>
82 127 <button type="button" onClick={() => openCell(m, c, r.indicator.name ?? name)} className="group/c -my-0 flex min-h-[44px] w-full flex-col items-end rounded-sm px-2 py-1 text-right hover:bg-surface-2" aria-label={t('common.openProvenance')}>
83 − <span className={cn('tnum text-base leading-tight', best ? 'font-semibold text-ink' : 'text-ink')}>{m.formatted ?? formatValue(m.value, m)}</span>
128 + <span className={cn('tnum text-base leading-tight', best ? 'font-semibold text-ink' : 'text-ink')}>{displayValue(m.value, m, m.formatted)}</span>
84 129 <span className="tnum mt-0.5 flex items-center gap-1.5 text-2xs text-ink-3">
85 130 <span>{m.year ?? ''}</span>
86 131 {isNum(m.rank_world) && isNum(m.n_world) ? (
@@ -130,7 +175,7 @@ export function SnapshotTable({ rows, countries, slugs, state }: { rows: Compare
130 175 disabled={!has}
131 176 onClick={() => has && openCell(m, c, r.indicator.name ?? name)}
132 177 className="grid min-h-[44px] w-full grid-cols-[1.5rem_minmax(0,1fr)_auto] items-center gap-x-2 rounded-sm text-left hover:bg-surface-2 disabled:hover:bg-transparent"
133 − aria-label={has ? `${c.name}: ${m.formatted ?? formatValue(m.value, m)} (${m.year ?? ''}). ${t('common.openProvenance')}` : `${c.name}: ${t('common.noData')}`}
178 + aria-label={has ? `${c.name}: ${displayValue(m.value, m, m.formatted)} (${m.year ?? ''}). ${t('common.openProvenance')}` : `${c.name}: ${t('common.noData')}`}
134 179 >
135 180 <span aria-hidden className="text-base leading-none">
136 181 {c.flag}
@@ -141,7 +186,7 @@ export function SnapshotTable({ rows, countries, slugs, state }: { rows: Compare
141 186 </span>
142 187 </span>
143 188 <span className="tnum flex items-baseline gap-1.5 text-right">
144 − <span className={cn('text-sm', best ? 'font-semibold text-ink' : 'text-ink')}>{has ? m.formatted ?? formatValue(m.value, m) : t('common.noData')}</span>
189 + <span className={cn('text-sm', best ? 'font-semibold text-ink' : 'text-ink')}>{has ? displayValue(m.value, m, m.formatted) : t('common.noData')}</span>
145 190 <span className="text-2xs text-ink-3">{has ? m.year : ''}</span>
146 191 </span>
147 192 </button>
modified apps/web/src/components/countries/directory.tsx +2 −1
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/components/country/indicator-row.tsx +2 −2
Large diff (16 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/components/country/topic-compact.tsx +47 −0
Large diff (47 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/components/explore/copy-button.tsx +7 −4
Large diff (21 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/components/indicators/directory.tsx +2 −1
Large diff (9 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/components/rankings/ranking-view.tsx +12 −7
Large diff (63 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/components/rankings/top3-preview.tsx +14 −9
Large diff (36 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/components/regions/member-ranking.tsx +5 −3
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/components/regions/members-table.tsx +6 −1
Large diff (19 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/i18n/en.ts +11 −0
Large diff (23 lines) is not loaded by default to keep the page fast.Load diff
added apps/web/src/lib/anchors.ts +4 −0
Large diff (4 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/lib/format.ts +17 −1
Large diff (31 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/lib/topics.ts +2 −2
Large diff (16 lines) is not loaded by default to keep the page fast.Load diff
modified apps/web/src/lib/types.ts +3 −1
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
Menu

Theme