diff --git a/library/composables/useDocumentFont.js b/library/composables/useDocumentFont.js
index 86c3e07d2..916ee0963 100644
--- a/library/composables/useDocumentFont.js
+++ b/library/composables/useDocumentFont.js
@@ -101,3 +101,28 @@ export const POPPINS_FACES = [
{ weight: '700', src: 'fonts/poppins/poppins-700-latin.woff2', unicodeRange: POPPINS_LATIN },
{ weight: '700', src: 'fonts/poppins/poppins-700-latin-ext.woff2', unicodeRange: POPPINS_LATIN_EXT },
]
+
+// Oswald, as DX-life serves it for CYRILLIC HEADINGS. base/_globals.scss:35-44 switches every
+// h1/h2/h3 to --font-family-cyrillic under [lang="ru-RU"], because Bebas Neue has no Cyrillic
+// glyphs at all — without the switch a Russian heading leaves the family entirely. Google's own
+// unicode-range values (css2 v57), carried verbatim so only the needed subset is fetched; latin and
+// latin-ext are included because a Russian heading routinely contains Latin characters and a
+// Cyrillic-only face would drop those back to the fallback mid-word.
+export const OSWALD_FACES = [
+ {
+ src: 'fonts/oswald/oswald-400-cyrillic.woff2',
+ unicodeRange: 'U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116',
+ },
+ {
+ src: 'fonts/oswald/oswald-400-cyrillic-ext.woff2',
+ unicodeRange: 'U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F',
+ },
+ {
+ src: 'fonts/oswald/oswald-400-latin.woff2',
+ unicodeRange: 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD',
+ },
+ {
+ src: 'fonts/oswald/oswald-400-latin-ext.woff2',
+ unicodeRange: 'U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF',
+ },
+]
diff --git a/plugin-designs/regions-dashboard/region-dashboards.vue b/plugin-designs/regions-dashboard/region-dashboards.vue
index 06105fce0..3f18b3998 100644
--- a/plugin-designs/regions-dashboard/region-dashboards.vue
+++ b/plugin-designs/regions-dashboard/region-dashboards.vue
@@ -28,7 +28,7 @@ import { useShadowStyles } from '@map/composables/useShadowStyles.js'
// house pattern (doxa-simple-map.vue:18, :246, :1346) — an EMBEDDED element must not reach for
// document.documentElement.dir, which belongs to the host page.
import { RTL_LOCALES } from '@map/i18n/index.js'
-import { useDocumentFont, BEBAS_NEUE_FACES, POPPINS_FACES, FIGTREE_FACES } from '@map/composables/useDocumentFont.js'
+import { useDocumentFont, BEBAS_NEUE_FACES, POPPINS_FACES, FIGTREE_FACES, OSWALD_FACES } from '@map/composables/useDocumentFont.js'
import FilterDropdown from '@map/components/search/FilterDropdown.vue'
import UupgsList from './src/UupgsList.vue'
// Their strings, via their own English keys (see src/useRegionsI18n.js). The goal sentences,
@@ -105,6 +105,12 @@ useDocumentFont('Poppins', POPPINS_FACES, assetsBase.value)
// giveaway (Driver, 2026-09-24). @font-face is DOCUMENT-scoped, so it registers here beside
// the other two rather than inside the shadow root that uses it.
useDocumentFont('Figtree', FIGTREE_FACES, assetsBase.value)
+// Oswald, for CYRILLIC HEADINGS ONLY. base/_globals.scss:35-44 switches every h1/h2/h3 to
+// --font-family-cyrillic under [lang="ru-RU"], because BEBAS NEUE HAS NO CYRILLIC GLYPHS — their
+// heading face cannot render Russian at all, so without the switch the heading leaves the family.
+// We ship a `ru` locale and had neither the rule nor the font, so every Russian heading here was
+// rendering in whatever condensed system face the browser picked.
+useDocumentFont('Oswald', OSWALD_FACES, assetsBase.value)
// ── state ───────────────────────────────────────────────────────────────────
const { t, tf , locale } = useRegionsI18n()
@@ -824,6 +830,19 @@ useShadowStyles(
' font-family:"Bebas Neue","Arial Narrow",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;' +
' font-weight:400; }' +
'.rd-h2--start{ text-align:start; }' +
+ // ★ CYRILLIC HEADINGS SWITCH FACE, as theirs do. base/_globals.scss:35-44:
+ // h1,h2,h3 { [lang="ru-RU"] & { font-family: var(--font-family-cyrillic); letter-spacing: -0.02em } }
+ // and --font-family-cyrillic is Oswald. The switch is not decoration: BEBAS NEUE HAS NO CYRILLIC
+ // GLYPHS, so a Russian heading in it leaves the family entirely and lands on whatever condensed
+ // system face the browser picks. We ship a `ru` locale and had neither the rule nor the font.
+ // ONLY the headings, exactly as theirs — their .region-stat__value is a
and keeps Bebas even in
+ // Russian, which is their behaviour and so is ours.
+ // BOTH TAG FORMS, deliberately: their site tags the document ru-RU while our i18n locale is `ru`,
+ // and a host page embedding this element may set either. A selector that matched only one would
+ // work on their site and fail on ours, or the reverse, depending on who set the attribute.
+ '[lang="ru"] .rd-title, [lang="ru-RU"] .rd-title,' +
+ '[lang="ru"] .rd-h2, [lang="ru-RU"] .rd-h2{ font-family:"Oswald","Arial Narrow",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;' +
+ ' letter-spacing:-0.02em; }' +
// .font-size-4xl .font-weight-medium / .font-size-lg
// Their .font-size-4xl (var(--font-size-4xl)) verified live. Same substitution consequence as the h2:
// in our wider stand-in face 'Adoption Progress' 21 / 2,107 breaks after the slash inside a
@@ -912,7 +931,11 @@ useShadowStyles(
-
+
+
diff --git a/plugin-designs/regions-dashboard/regions-list.vue b/plugin-designs/regions-dashboard/regions-list.vue
index 98d7df291..53d19b396 100644
--- a/plugin-designs/regions-dashboard/regions-list.vue
+++ b/plugin-designs/regions-dashboard/regions-list.vue
@@ -38,7 +38,7 @@ import { useRegionsI18n } from './src/useRegionsI18n.js'
// document.documentElement.dir, which belongs to the host page.
import { RTL_LOCALES } from '@map/i18n/index.js'
import { useI18n } from 'vue-i18n'
-import { useDocumentFont, BEBAS_NEUE_FACES, POPPINS_FACES, FIGTREE_FACES } from '@map/composables/useDocumentFont.js'
+import { useDocumentFont, BEBAS_NEUE_FACES, POPPINS_FACES, FIGTREE_FACES, OSWALD_FACES } from '@map/composables/useDocumentFont.js'
import { summarizeRegions, PEOPLE_GROUP_LIST_FIELDS } from '@map/utils/countries-meta.js'
// DX-life's spacing/type scales under THEIR token names. Prepended to this
// component's shadow CSS so every var() below resolves inside the shadow root.
@@ -123,6 +123,12 @@ useDocumentFont('Poppins', POPPINS_FACES, assetsBase.value)
// giveaway (Driver, 2026-09-24). @font-face is DOCUMENT-scoped, so it registers here beside
// the other two rather than inside the shadow root that uses it.
useDocumentFont('Figtree', FIGTREE_FACES, assetsBase.value)
+// Oswald, for CYRILLIC HEADINGS ONLY. base/_globals.scss:35-44 switches every h1/h2/h3 to
+// --font-family-cyrillic under [lang="ru-RU"], because BEBAS NEUE HAS NO CYRILLIC GLYPHS — their
+// heading face cannot render Russian at all, so without the switch the heading leaves the family.
+// We ship a `ru` locale and had neither the rule nor the font, so every Russian heading here was
+// rendering in whatever condensed system face the browser picked.
+useDocumentFont('Oswald', OSWALD_FACES, assetsBase.value)
// ── state ───────────────────────────────────────────────────────────────────
const regions = ref([])
@@ -427,6 +433,19 @@ useShadowStyles(
' margin-inline-start:10px; }' +
// .region-card__summary: --font-size-md (var(--font-size-md)), 0.85 opacity, on the header band.
'.region-card__summary{ font-size:var(--font-size-md); opacity:0.85; margin:0; }' +
+ // ★ CYRILLIC HEADINGS SWITCH FACE, as theirs do. base/_globals.scss:35-44:
+ // h1,h2,h3 { [lang="ru-RU"] & { font-family: var(--font-family-cyrillic); letter-spacing: -0.02em } }
+ // and --font-family-cyrillic is Oswald. The switch is not decoration: BEBAS NEUE HAS NO CYRILLIC
+ // GLYPHS, so a Russian heading in it leaves the family entirely and lands on whatever condensed
+ // system face the browser picks. We ship a `ru` locale and had neither the rule nor the font.
+ // ONLY the headings, exactly as theirs — their .region-stat__value is a and keeps Bebas even in
+ // Russian, which is their behaviour and so is ours.
+ // BOTH TAG FORMS, deliberately: their site tags the document ru-RU while our i18n locale is `ru`,
+ // and a host page embedding this element may set either. A selector that matched only one would
+ // work on their site and fail on ours, or the reverse, depending on who set the attribute.
+ '[lang="ru"] .rl-title, [lang="ru-RU"] .rl-title,' +
+ '[lang="ru"] .region-card__title, [lang="ru-RU"] .region-card__title{ font-family:"Oswald","Arial Narrow",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;' +
+ ' letter-spacing:-0.02em; }' +
// .region-card__body: padding --spacing-lg / clamp(...) / --spacing-xl -> var(--spacing-lg) / clamp / var(--spacing-xl)
'.region-card__body{ padding:var(--spacing-lg) clamp(var(--spacing-md), 4cqw, var(--spacing-2xl)) var(--spacing-xl); }' +
// .countries-grid: DX-life's track is minmax(220px,1fr). Gap --spacing-xs / --spacing-xl
@@ -505,7 +524,11 @@ useShadowStyles(
-