Skip to content

perf: optimize web font loading — preload Inter, async non-critical fonts, add CLS fallback metrics#706

Merged
pethers merged 8 commits intomainfrom
copilot/optimize-font-loading-strategy
Mar 1, 2026
Merged

perf: optimize web font loading — preload Inter, async non-critical fonts, add CLS fallback metrics#706
pethers merged 8 commits intomainfrom
copilot/optimize-font-loading-strategy

Conversation

Copy link
Contributor

Copilot AI commented Mar 1, 2026

Three font families loaded as a single blocking stylesheet caused render-blocking requests and CLS on font-swap. Orbitron was loaded with 4 weights despite only being used at 500700 for headings.

CSS (styles.css)

  • Added @font-face fallback metric overrides using distinct family names 'Inter Fallback' and 'Orbitron Fallback'size-adjust, ascent-override, descent-override, line-gap-override approximate each web font's metrics, eliminating layout shift during the swap without overriding the real webfont declarations
  • Inter Fallback sources: local('Arial'), local('Liberation Sans'), local('Helvetica Neue'), local('Helvetica') for cross-platform coverage (Windows, Linux, macOS, Android)
  • Orbitron Fallback sources: local('Arial Black'), local('Impact'), local('Century Gothic'), local('Trebuchet MS') for cross-platform coverage
  • Both fallback family names inserted into all relevant font-family stacks (--font-main, --font-accent, dark-mode heading rule, .news-page h1, chart state headings, var(--font-heading, ...) fallbacks)

HTML — all 29 pages (14 root index*.html, 14 dashboard/index*.html, politician-dashboard.html)

Split one blocking <link> into targeted tags:

<!-- Critical: preload the Inter 400 woff2 directly + blocking Inter stylesheet -->
<link rel="preload" href="https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7.woff2"
      as="font" type="font/woff2" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

<!-- Non-critical: Orbitron (500;600;700) + Share Tech Mono loaded async via print-swap trick -->
<link rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&display=swap"
      media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="...same URL..."></noscript>

Orbitron loads wght@500;600;700 to match h1/h2 (font-weight: 600) and h3 (font-weight: 500) heading rules, avoiding synthetic weight rendering.

CJK/RTL dashboard pages — language-specific Noto font loaded synchronously

For the five dashboard pages whose primary content font is a Noto family, that family is now loaded as a blocking stylesheet (alongside Inter) to prevent a late font swap and CLS for those users. The remaining Noto families stay in the async bundle:

Page Blocking (critical) Async (non-critical)
dashboard/index_zh.html Noto Sans SC Orbitron, Share Tech Mono, Noto JP/KR/Arabic/Hebrew
dashboard/index_ja.html Noto Sans JP Orbitron, Share Tech Mono, Noto KR/SC/Arabic/Hebrew
dashboard/index_ko.html Noto Sans KR Orbitron, Share Tech Mono, Noto JP/SC/Arabic/Hebrew
dashboard/index_ar.html Noto Sans Arabic Orbitron, Share Tech Mono, Noto JP/KR/SC/Hebrew
dashboard/index_he.html Noto Sans Hebrew Orbitron, Share Tech Mono, Noto JP/KR/SC/Arabic

Templates (scripts/article-template/template.ts, scripts/generate-news-indexes/template.ts)

Applied the same pattern so newly generated articles inherit the optimized loading strategy.

Original prompt

This section details on the original issue you should resolve

<issue_title>Optimize web font loading strategy: preload critical fonts, async non-critical</issue_title>
<issue_description>## 📋 Issue Type
Performance / SEO

🎯 Objective

Optimize web font loading strategy to reduce Cumulative Layout Shift (CLS) and improve First Contentful Paint (FCP) by implementing font-display swap and optional font preloading.

📊 Current State

In index.html and all page templates:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Orbitron:wght@400;500;600;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">

Issues:

  1. Three font families loaded from Google Fonts (Inter, Orbitron, Share Tech Mono) with 4 weights each
  2. display=swap is set which is good, but font files are render-blocking
  3. No font-display: optional option for non-critical fonts
  4. No subset loading for CJK languages (Japanese, Korean, Chinese) which need much larger font files
  5. Share Tech Mono may only be used for monospace code elements
  6. The <link> tags are not using rel="preload" for critical fonts

🚀 Desired State

  • Critical font (Inter) preloaded with <link rel="preload">
  • Non-critical fonts (Orbitron, Share Tech Mono) loaded asynchronously
  • CJK language pages load appropriate font subsets
  • CLS reduced by reserving font space with fallback font metrics
  • Optional: Self-host critical fonts for offline capability

🔧 Implementation Approach

  1. Preload critical Inter font weights:
<link rel="preload" href="https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7.woff2" as="font" type="font/woff2" crossorigin>
  1. Load non-critical fonts asynchronously:
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@700&family=Share+Tech+Mono&display=swap" media="print" onload="this.media='all'">
  1. Add font fallback metrics to prevent CLS:
@font-face {
  font-family: 'Inter';
  font-display: swap;
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
  src: local('Arial');
}
  1. Reduce Orbitron weights to only 700 (headings only)
  2. Consider subsetting for CJK languages

🤖 Recommended Agent

performance-engineer — Font loading optimization

✅ Acceptance Criteria

  • Critical font (Inter 400) preloaded
  • Non-critical fonts load asynchronously
  • CLS ≤ 0.1 (current target)
  • FCP improvement measurable in Lighthouse
  • No visible FOUT (Flash of Unstyled Text)
  • CJK pages have appropriate font handling
  • All 14 language variants optimized

📚 References

  • Current: All index*.html files (font loading in <head>)
  • Styles: styles.css (font-family declarations)
  • Lighthouse: .github/workflows/lighthouse-ci.yml
  • Budget: budget.json
  • Web Vitals: https://web.dev/cls/

🏷️ Labels

type:performance, priority:medium, component:ui-ux</issue_description>

<agent_instructions>Optimize font loading. Preload critical Inter font. Load Orbitron and Share Tech Mono asynchronously. Reduce Orbitron weights to 700 only. Add font fallback metrics to prevent CLS. Apply to all index*.html, dashboard/index*.html templates.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

…TechMono, CLS fallback metrics

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copilot AI changed the title [WIP] Optimize web font loading strategy for performance perf: optimize web font loading — preload Inter, async non-critical fonts, add CLS fallback metrics Mar 1, 2026
@github-actions github-actions bot added dashboard Interactive dashboards (Chart.js/D3.js) html-css HTML/CSS changes i18n Internationalization/localization translation Translation updates rtl RTL language support (Arabic, Hebrew) refactor Code refactoring size-m Medium change (50-250 lines) labels Mar 1, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes font loading across the static site to reduce render-blocking requests and mitigate CLS during webfont swaps by preloading critical Inter and loading non-critical fonts asynchronously; also updates generation templates so newly generated pages inherit the same strategy.

Changes:

  • Added CSS @font-face metric overrides intended to stabilize layout during font swap.
  • Split Google Fonts loading into: Inter (blocking + preload) and non-critical fonts (async via print/onload + noscript fallback).
  • Updated page templates so generated news indexes and articles use the new loading pattern.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
styles.css Adds font fallback metric overrides intended to reduce CLS.
scripts/generate-news-indexes/template.ts Updates generated news index pages to preload Inter + async Orbitron.
scripts/article-template/template.ts Updates generated articles to preload Inter + async Orbitron/Share Tech Mono.
politician-dashboard.html Applies preload/async font loading split to the politician dashboard page.
index.html Applies preload/async font loading split to the EN landing page.
index_ar.html Applies preload/async font loading split to the AR landing page.
index_da.html Applies preload/async font loading split to the DA landing page.
index_de.html Applies preload/async font loading split to the DE landing page.
index_es.html Applies preload/async font loading split to the ES landing page.
index_fi.html Applies preload/async font loading split to the FI landing page.
index_fr.html Applies preload/async font loading split to the FR landing page.
index_he.html Applies preload/async font loading split to the HE landing page.
index_ja.html Applies preload/async font loading split to the JA landing page.
index_ko.html Applies preload/async font loading split to the KO landing page.
index_nl.html Applies preload/async font loading split to the NL landing page.
index_no.html Applies preload/async font loading split to the NO landing page.
index_sv.html Applies preload/async font loading split to the SV landing page.
index_zh.html Applies preload/async font loading split to the ZH landing page.
dashboard/index.html Applies preload/async font loading split (incl. async Noto families) to dashboard EN.
dashboard/index_ar.html Applies preload/async font loading split (incl. async Noto families) to dashboard AR.
dashboard/index_da.html Applies preload/async font loading split (incl. async Noto families) to dashboard DA.
dashboard/index_de.html Applies preload/async font loading split (incl. async Noto families) to dashboard DE.
dashboard/index_es.html Applies preload/async font loading split (incl. async Noto families) to dashboard ES.
dashboard/index_fi.html Applies preload/async font loading split (incl. async Noto families) to dashboard FI.
dashboard/index_fr.html Applies preload/async font loading split (incl. async Noto families) to dashboard FR.
dashboard/index_he.html Applies preload/async font loading split (incl. async Noto families) to dashboard HE.
dashboard/index_ja.html Applies preload/async font loading split (incl. async Noto families) to dashboard JA.
dashboard/index_ko.html Applies preload/async font loading split (incl. async Noto families) to dashboard KO.
dashboard/index_nl.html Applies preload/async font loading split (incl. async Noto families) to dashboard NL.
dashboard/index_no.html Applies preload/async font loading split (incl. async Noto families) to dashboard NO.
dashboard/index_sv.html Applies preload/async font loading split (incl. async Noto families) to dashboard SV.
dashboard/index_zh.html Applies preload/async font loading split (incl. async Noto families) to dashboard ZH.

Comment on lines 7 to 11
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 100 900;
font-display: swap;
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new @font-face rule defines font-family "Inter" with only local('Arial') sources. Because styles.css is loaded after the Google Fonts stylesheet, this rule can override the webfont @font-face definitions for Inter (same family/style/weight range), causing the site to render in Arial and never use the downloaded Inter files. Use a distinct fallback family name (e.g., "Inter Fallback") and reference it in the font-family stack, or otherwise ensure these fallback-metric faces don’t override the real webfont faces.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1e8716a. Renamed the fallback @font-face family from 'Inter' to 'Inter Fallback' so it no longer conflicts with the Google Fonts webfont declaration. Added "Inter Fallback" as the second entry in all Inter-based font-family stacks (--font-main, --font-accent, Noto stacks, and direct references).

Comment on lines 19 to 23
@font-face {
font-family: 'Orbitron';
font-style: normal;
font-weight: 700;
font-display: swap;
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above for Orbitron: this local-only @font-face for font-family "Orbitron" can override the Google Fonts Orbitron faces since it’s declared in styles.css. That would prevent the actual Orbitron webfont from ever being used. Consider moving these metric overrides to separate fallback family names and using them in the font-family fallback list.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1e8716a. Renamed the fallback @font-face family from 'Orbitron' to 'Orbitron Fallback' and added "Orbitron Fallback" to every Orbitron-based font-family declaration in styles.css (--font-accent, dark-mode heading rule, .news-page h1, .chart-empty-state h3, .chart-error-state h3, and all var(--font-heading, ...) fallbacks).

Comment on lines 120 to 121
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@700&family=Share+Tech+Mono&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@700&family=Share+Tech+Mono&display=swap"></noscript>
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orbitron is now requested with only wght@700, but the CSS applies Orbitron to headings that still request other weights (e.g., base h1/h2 use 600 and h3 uses 500; news-page h1 inherits 600). This will force synthetic weights or fallback, which can undermine the intended CLS/typography improvements. Either load the additional Orbitron weights actually used, or update the relevant heading rules to use font-weight: 700 when Orbitron is active.

Suggested change
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@700&family=Share+Tech+Mono&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@700&family=Share+Tech+Mono&display=swap"></noscript>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&display=swap"></noscript>

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1e8716a. Orbitron now loads wght@500;600;700 across all 29 HTML pages and both templates, matching the h1/h2 (font-weight: 600) and h3 (font-weight: 500) heading rules in styles.css.

Comment on lines 178 to 180
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@700&display=swap"></noscript>
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template now loads Orbitron with only wght@700, but styles.css applies Orbitron to the news page H1 without setting font-weight, so it inherits the global h1 font-weight (600). With only 700 available, browsers will synthesize or fallback. Consider aligning the CSS heading weights with 700 or loading the weights that are actually used on news pages.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1e8716a. generate-news-indexes/template.ts now loads Orbitron:wght@500;600;700, covering the inherited font-weight: 600 on news-page h1.

@pethers
Copy link
Member

pethers commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

… load Orbitron wght@500;600;700

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

All four review comments addressed in 1e8716a:

  • @font-face families renamed to 'Inter Fallback' / 'Orbitron Fallback' to prevent overriding the Google Fonts webfont definitions
  • Fallback names added to all font-family stacks in styles.css
  • Orbitron loads wght@500;600;700 to match the 600/500 weights used by h1–h3 headings

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 7 comments.

<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Orbitron:wght@400;500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="preload" href="https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa2JL7.woff2" as="font" type="font/woff2" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This page’s primary font for :lang(zh) content is Noto Sans SC (per styles.css), but Noto is now loaded via an async media="print" stylesheet. That can cause an initial render in Inter/system fonts followed by a late swap to Noto, increasing CLS for CJK users. Consider keeping the required Noto family as a blocking stylesheet (or preload its critical subset) and only async-load non-critical fonts like Orbitron/Share Tech Mono.

Suggested change
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in eb8fa2a. dashboard/index_zh.html now loads Noto Sans SC as a blocking stylesheet alongside Inter; the remaining 4 Noto families stay in the async bundle.

Comment on lines 65 to 66
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap"></noscript>
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This page’s primary font for :lang(ja) content is Noto Sans JP (per styles.css), but it’s currently included in the async media="print" Google Fonts stylesheet. That can cause an initial render in fallback fonts and a late swap to Noto, increasing CLS. Consider loading the needed Noto family synchronously (or preloading a critical subset) while keeping Orbitron/Share Tech Mono async.

Suggested change
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap"></noscript>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap"></noscript>

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in eb8fa2a. dashboard/index_ja.html now loads Noto Sans JP as a blocking stylesheet; the other Noto families remain async.

Comment on lines 65 to 66
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap"></noscript>
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This page’s primary font for :lang(ko) content is Noto Sans KR (per styles.css), but Noto is now loaded via an async media="print" stylesheet. That can increase CLS when text swaps to Noto after initial paint. Consider loading the required Noto family as a blocking stylesheet (or preloading its critical subset) and only async-loading non-critical fonts.

Suggested change
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap"></noscript>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap"></noscript>

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in eb8fa2a. dashboard/index_ko.html now loads Noto Sans KR as a blocking stylesheet; the other Noto families remain async.

Comment on lines 65 to 66
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap"></noscript>
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This page’s primary font for :lang(ar) content is Noto Sans Arabic (per styles.css), but it’s included in the async media="print" Google Fonts stylesheet. That can cause late font swapping and added CLS for RTL users. Consider loading the required Noto family synchronously (or preloading a critical subset) while keeping Orbitron/Share Tech Mono async.

Suggested change
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap"></noscript>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap"></noscript>

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in eb8fa2a. dashboard/index_ar.html now loads Noto Sans Arabic as a blocking stylesheet; the other Noto families remain async.

Comment on lines 65 to 66
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap"></noscript>
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This page’s primary font for :lang(he) content is Noto Sans Hebrew (per styles.css), but it’s currently loaded via an async media="print" stylesheet. That can cause a late swap to Noto and increase CLS for RTL users. Consider loading the required Noto family synchronously (or preloading a critical subset) and only async-loading non-critical fonts.

Suggested change
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&family=Noto+Sans+Hebrew:wght@400;500;700&display=swap"></noscript>
<!-- Critical font for Hebrew content: load Noto Sans Hebrew synchronously to avoid layout shifts -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+Hebrew:wght@400;500;700&display=swap">
<!-- Non-critical fonts loaded asynchronously -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&display=swap" media="print" onload="this.media='all'">
<noscript>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+Hebrew:wght@400;500;700&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;600;700&family=Share+Tech+Mono&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans+Arabic:wght@400;500;700&display=swap">
</noscript>

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in eb8fa2a. dashboard/index_he.html now loads Noto Sans Hebrew as a blocking stylesheet; the other Noto families remain async.

Comment on lines 7 to 17
@font-face {
font-family: 'Inter Fallback';
font-style: normal;
font-weight: 100 900;
font-display: swap;
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
src: local('Arial');
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Inter fallback-metrics face only sources local('Arial'). On platforms without Arial (common on Linux/Android variants), the entire @font-face will be ignored, so size-adjust/override metrics won’t apply and CLS improvements won’t benefit those users. Consider adding additional widely-available local fallbacks (e.g., Liberation Sans/Helvetica/Noto Sans) or otherwise ensuring the fallback face resolves cross-platform.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in eb8fa2a. Added local('Liberation Sans'), local('Helvetica Neue'), local('Helvetica') to the Inter Fallback src list, covering Linux (Liberation Sans) and macOS (Helvetica Neue/Helvetica).

Comment on lines 19 to 29
@font-face {
font-family: 'Orbitron Fallback';
font-style: normal;
font-weight: 500 700;
font-display: swap;
size-adjust: 94%;
ascent-override: 85%;
descent-override: 15%;
line-gap-override: 0%;
src: local('Arial Black'), local('Impact');
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same portability issue for the Orbitron fallback-metrics face: local('Arial Black'), local('Impact') are not reliably present across OSes, so the metric-adjusted fallback may not resolve and CLS may remain. Consider expanding the src: local(...) list with common cross-platform alternatives (or provide separate metric faces per OS family).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in eb8fa2a. Added local('Century Gothic') (Windows) and local('Trebuchet MS') (widely available) to the Orbitron Fallback src list alongside the existing Arial Black and Impact entries.

@pethers
Copy link
Member

pethers commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

…s; broaden fallback font sources

Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 1, 2026

@copilot apply changes based on the comments in this thread

All seven comments addressed in eb8fa2a:

  • CJK/RTL dashboard pages (zh, ja, ko, ar, he) now load their primary Noto font synchronously to prevent CLS; other Noto families stay async
  • Inter Fallback extended with Liberation Sans, Helvetica Neue, Helvetica for Linux/macOS coverage
  • Orbitron Fallback extended with Century Gothic, Trebuchet MS for broader OS coverage

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

🔍 Lighthouse Performance Audit

Category Score Status
Performance 85/100 🟡
Accessibility 95/100 🟢
Best Practices 90/100 🟢
SEO 95/100 🟢

📥 Download full Lighthouse report

Budget Compliance: Performance budgets enforced via budget.json

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated no new comments.

@pethers pethers marked this pull request as ready for review March 1, 2026 12:26
@pethers pethers merged commit 3d1998f into main Mar 1, 2026
30 checks passed
@pethers pethers deleted the copilot/optimize-font-loading-strategy branch March 1, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dashboard Interactive dashboards (Chart.js/D3.js) html-css HTML/CSS changes i18n Internationalization/localization refactor Code refactoring rtl RTL language support (Arabic, Hebrew) size-m Medium change (50-250 lines) translation Translation updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimize web font loading strategy: preload critical fonts, async non-critical

3 participants