Cities with Internet and the phone legend speak the page's language

Cities with Internet was English end to end: the legend title, the four bands,
the World Bank caveat, the new people-group row and the whole country sheet. Its
chrome now comes from a connectivity catalog area in all eleven locales, following
gospel-resources: i18n is optional, English from the shipped catalog is the
fallback, and carrier and platform names stay as data. The legend and the sheets
are anchored with logical insets, so in Arabic the legend sits opposite the control
rail instead of under the zoom buttons.

LegendMobile hard-coded its column labels ("Prayer Status", "Engagement Status",
…) and titles ("Legend", "Languages", …) in English. They now read
legend.colLabel.* and legend.titleBar.*, which every catalog already translates.
English is byte-identical, so other consumers are unchanged until they rebuild.

Measured in the Research Gallery tab on a served built tree, at 1280 and 390, in
en/ar/zh/ru/hi: Cities shows no English UI string (only the Mapbox and
geoBoundaries attribution); ar is right-to-left with 0 clipped and the legend
clear of the control rail.
This commit is contained in:
doxa-maps-88655c70 2026-09-25 06:05:46 +00:00
parent e8bbe77822
commit 502a4186dc
24 changed files with 393 additions and 105 deletions

View file

@ -469,18 +469,17 @@ const tabsForMobile = computed(() => {
const treeTitleForMobile = computed(() => {
// A profile-supplied title names the MAP; the taxonomy names only the tab.
if (props.title) return props.title
if (_isFlatTabMobile.value) return 'Legend'
if (props.legendType === 'affinity-block') return 'People Groups'
if (props.legendType === 'doxa-regions') return 'WAGF Regions'
return 'Languages'
if (_isFlatTabMobile.value) return t('legend.titleBar.legend')
if (props.legendType === 'affinity-block') return t('legend.titleBar.peopleGroups')
if (props.legendType === 'doxa-regions') return t('legend.titleBar.wagfRegions')
return t('legend.titleBar.languages')
})
const treeColumnLabelForMobile = computed(() => {
if (props.columnLabel !== null) return props.columnLabel
if (props.legendType === 'religion') return 'UUPG Religions'
if (props.legendType === 'prayer') return 'Prayer Status'
if (props.legendType === 'engagement') return 'Engagement Status'
if (props.legendType === 'adoption') return 'Adoption Status'
if (_isFlatTabMobile.value) return 'Status'
// From the catalog (legend.colLabel.*), translated in all 11 locales; these were English
// literals, so a phone in ar/zh/ru/hi showed "Prayer Status" over translated rows.
if (['religion', 'prayer', 'engagement', 'adoption'].includes(props.legendType)) return t('legend.colLabel.' + props.legendType)
if (_isFlatTabMobile.value) return t('legend.colLabel.status')
return ''
})

View file

@ -0,0 +1,25 @@
{
"title": "الوصول إلى الإنترنت حيث تعيش الشعوب غير المنخرطة",
"bands": {
"yes": "متصل 60%+",
"partial": "جزئي 25–59%",
"no": "منخفض، أقل من 25%",
"unknown": "غير مُقاس"
},
"peopleGroup": "شعب",
"caveat": "أرقام وطنية (البنك الدولي). بيانات الاتصال لكل مدينة غير متاحة للعامة — لا تستنتج حال مدينة من بيانات بلدها.",
"sheet": {
"close": "إغلاق",
"ai": "عُثر على هذه البيانات عبر بحث معمّق بالذكاء الاصطناعي — وقد تتضمن معلومات خاطئة.",
"dismiss": "إغلاق التنبيه",
"internetUsers": "مستخدمو الإنترنت",
"measured": "سنة القياس",
"mobilePer100": "اشتراكات الجوال لكل 100",
"peopleGroups": "الشعوب",
"topPlatform": "المنصة الأولى",
"unknown": "غير معروف",
"carriers": "شركات الاتصالات ومزودو الإنترنت ({n})",
"socialPlatforms": "منصات التواصل الاجتماعي",
"note": "أرقام وطنية وليست قياسات للمدن. أسماء شركات الاتصالات هي أسماء أصحاب سجلات ASN — وتشمل البنوك والجامعات إلى جانب مزودي الإنترنت، لذا فإن تصنيفها كشركات اتصالات تقدير مبني على الاسم وليس تصنيفًا مُتحقَّقًا منه."
}
}

View file

@ -0,0 +1,25 @@
{
"title": "Internetzugang dort, wo Volksgruppen ohne Engagement leben",
"bands": {
"yes": "verbunden 60%+",
"partial": "teilweise 25–59%",
"no": "gering, unter 25%",
"unknown": "nicht gemessen"
},
"peopleGroup": "Volksgruppe",
"caveat": "LANDESWEITE Zahlen (Weltbank). Konnektivität je Stadt ist nicht öffentlich verfügbar — schließen Sie nicht vom Land auf eine Stadt.",
"sheet": {
"close": "Schließen",
"ai": "Diese Daten wurden durch KI-gestützte Recherche gefunden — sie können falsche Angaben enthalten.",
"dismiss": "Ausblenden",
"internetUsers": "Internetnutzer",
"measured": "Gemessen",
"mobilePer100": "Mobilfunk pro 100",
"peopleGroups": "Volksgruppen",
"topPlatform": "Wichtigste Plattform",
"unknown": "unbekannt",
"carriers": "Netzbetreiber & ISPs ({n})",
"socialPlatforms": "Soziale Plattformen",
"note": "Landesweite Zahlen, keine Messungen je Stadt. Betreibernamen sind ASN-Registerinhaber — darunter neben ISPs auch Banken und Universitäten; die Einstufung als Netzbetreiber ist daher eine Namensheuristik, keine geprüfte Klassifizierung."
}
}

View file

@ -0,0 +1,25 @@
{
"title": "Internet access where unengaged people groups live",
"bands": {
"yes": "connected 60%+",
"partial": "partial 25–59%",
"no": "low, under 25%",
"unknown": "not measured"
},
"peopleGroup": "people group",
"caveat": "NATIONAL figures (World Bank). Per-city connectivity is not publicly available — do not read a city off a country.",
"sheet": {
"close": "Close",
"ai": "Data was found by AI deep research — it is subject to false information.",
"dismiss": "Dismiss",
"internetUsers": "Internet users",
"measured": "Measured",
"mobilePer100": "Mobile per 100",
"peopleGroups": "People groups",
"topPlatform": "Top platform",
"unknown": "unknown",
"carriers": "Carriers & ISPs ({n})",
"socialPlatforms": "Social platforms",
"note": "National figures, not city measurements. Carrier names are ASN registry holders — that set includes banks and universities as well as ISPs, so the carrier flag is a name heuristic, not a verified classification."
}
}

View file

@ -0,0 +1,25 @@
{
"title": "Acceso a internet donde viven pueblos sin implicación",
"bands": {
"yes": "conectado 60%+",
"partial": "parcial 25–59%",
"no": "bajo, menos del 25%",
"unknown": "sin medir"
},
"peopleGroup": "pueblo",
"caveat": "Cifras NACIONALES (Banco Mundial). La conectividad por ciudad no está disponible públicamente: no deduzca una ciudad a partir de su país.",
"sheet": {
"close": "Cerrar",
"ai": "Estos datos se obtuvieron mediante investigación con IA y pueden contener información falsa.",
"dismiss": "Descartar",
"internetUsers": "Usuarios de internet",
"measured": "Medido",
"mobilePer100": "Móviles por cada 100",
"peopleGroups": "Pueblos",
"topPlatform": "Plataforma principal",
"unknown": "desconocido",
"carriers": "Operadores y proveedores de internet ({n})",
"socialPlatforms": "Redes sociales",
"note": "Cifras nacionales, no mediciones por ciudad. Los nombres de operadores son titulares de registros ASN (incluyen bancos y universidades además de proveedores), así que la marca de operador es una heurística por nombre, no una clasificación verificada."
}
}

View file

@ -0,0 +1,25 @@
{
"title": "Accès à Internet là où vivent les peuples sans engagement",
"bands": {
"yes": "connecté 60 %+",
"partial": "partiel 25–59 %",
"no": "faible, moins de 25 %",
"unknown": "non mesuré"
},
"peopleGroup": "peuple",
"caveat": "Chiffres NATIONAUX (Banque mondiale). La connectivité par ville n'est pas publique — ne déduisez pas une ville à partir de son pays.",
"sheet": {
"close": "Fermer",
"ai": "Ces données proviennent d'une recherche approfondie par IA — elles peuvent contenir des informations erronées.",
"dismiss": "Ignorer",
"internetUsers": "Internautes",
"measured": "Mesuré",
"mobilePer100": "Mobiles pour 100",
"peopleGroups": "Peuples",
"topPlatform": "Plateforme principale",
"unknown": "inconnu",
"carriers": "Opérateurs et FAI ({n})",
"socialPlatforms": "Réseaux sociaux",
"note": "Chiffres nationaux, pas des mesures par ville. Les noms d'opérateurs sont les titulaires de registres ASN (on y trouve aussi des banques et des universités à côté des FAI), donc l'étiquette « opérateur » est une heuristique fondée sur le nom, non une classification vérifiée."
}
}

View file

@ -0,0 +1,25 @@
{
"title": "इंटरनेट पहुँच जहाँ बिना सहभागिता वाले जनसमूह रहते हैं",
"bands": {
"yes": "जुड़ा हुआ 60%+",
"partial": "आंशिक 25–59%",
"no": "कम, 25% से नीचे",
"unknown": "मापा नहीं गया"
},
"peopleGroup": "जनसमूह",
"caveat": "राष्ट्रीय आँकड़े (विश्व बैंक)। शहर-स्तर की कनेक्टिविटी सार्वजनिक रूप से उपलब्ध नहीं है — देश के आँकड़ों से किसी शहर का अनुमान न लगाएँ।",
"sheet": {
"close": "बंद करें",
"ai": "यह डेटा एआई गहन शोध से मिला है — इसमें गलत जानकारी हो सकती है।",
"dismiss": "हटाएँ",
"internetUsers": "इंटरनेट उपयोगकर्ता",
"measured": "मापा गया",
"mobilePer100": "प्रति 100 मोबाइल",
"peopleGroups": "जनसमूह",
"topPlatform": "शीर्ष प्लेटफ़ॉर्म",
"unknown": "अज्ञात",
"carriers": "कैरियर और आईएसपी ({n})",
"socialPlatforms": "सोशल प्लेटफ़ॉर्म",
"note": "ये राष्ट्रीय आँकड़े हैं, शहरों के माप नहीं। कैरियर के नाम ASN रजिस्ट्री धारकों के हैं — उनमें आईएसपी के साथ बैंक और विश्वविद्यालय भी शामिल हैं, इसलिए कैरियर का चिह्न नाम पर आधारित अनुमान है, सत्यापित वर्गीकरण नहीं।"
}
}

View file

@ -0,0 +1,25 @@
{
"title": "Accesso a internet dove vivono i popoli senza impegno",
"bands": {
"yes": "connesso 60%+",
"partial": "parziale 25–59%",
"no": "basso, sotto il 25%",
"unknown": "non misurato"
},
"peopleGroup": "popolo",
"caveat": "Dati NAZIONALI (Banca Mondiale). La connettività per città non è disponibile pubblicamente: non dedurre una città dal suo paese.",
"sheet": {
"close": "Chiudi",
"ai": "Questi dati provengono da una ricerca approfondita con IA e possono contenere informazioni errate.",
"dismiss": "Ignora",
"internetUsers": "Utenti internet",
"measured": "Misurato",
"mobilePer100": "Cellulari ogni 100",
"peopleGroups": "Popoli",
"topPlatform": "Piattaforma principale",
"unknown": "sconosciuto",
"carriers": "Operatori e ISP ({n})",
"socialPlatforms": "Piattaforme social",
"note": "Dati nazionali, non misure per città. I nomi degli operatori sono titolari di registri ASN (includono banche e università oltre agli ISP), quindi l'etichetta di operatore è un'euristica basata sul nome, non una classificazione verificata."
}
}

View file

@ -0,0 +1,25 @@
{
"title": "Acesso à internet onde vivem os povos sem envolvimento",
"bands": {
"yes": "conectado 60%+",
"partial": "parcial 25–59%",
"no": "baixo, abaixo de 25%",
"unknown": "não medido"
},
"peopleGroup": "povo",
"caveat": "Números NACIONAIS (Banco Mundial). A conectividade por cidade não está disponível publicamente — não deduza uma cidade a partir do país.",
"sheet": {
"close": "Fechar",
"ai": "Estes dados foram encontrados por pesquisa aprofundada com IA — podem conter informações falsas.",
"dismiss": "Dispensar",
"internetUsers": "Usuários de internet",
"measured": "Medido",
"mobilePer100": "Celulares por 100",
"peopleGroups": "Povos",
"topPlatform": "Plataforma principal",
"unknown": "desconhecido",
"carriers": "Operadoras e provedores ({n})",
"socialPlatforms": "Redes sociais",
"note": "Números nacionais, não medições por cidade. Os nomes das operadoras são titulares de registros ASN (incluem bancos e universidades além de provedores), então a marcação de operadora é uma heurística pelo nome, não uma classificação verificada."
}
}

View file

@ -0,0 +1,25 @@
{
"title": "Acces la internet unde trăiesc popoarele fără implicare",
"bands": {
"yes": "conectat 60%+",
"partial": "parțial 25–59%",
"no": "scăzut, sub 25%",
"unknown": "nemăsurat"
},
"peopleGroup": "popor",
"caveat": "Cifre NAȚIONALE (Banca Mondială). Conectivitatea pe oraș nu este disponibilă public — nu deduceți un oraș din datele țării.",
"sheet": {
"close": "Închide",
"ai": "Aceste date au fost găsite prin cercetare aprofundată cu IA — pot conține informații false.",
"dismiss": "Închide notificarea",
"internetUsers": "Utilizatori de internet",
"measured": "Măsurat",
"mobilePer100": "Mobile la 100",
"peopleGroups": "Popoare",
"topPlatform": "Platforma principală",
"unknown": "necunoscut",
"carriers": "Operatori și ISP-uri ({n})",
"socialPlatforms": "Platforme sociale",
"note": "Cifre naționale, nu măsurători pe oraș. Numele operatorilor sunt titulari de înregistrări ASN (printre ei se află și bănci și universități, nu doar ISP-uri), deci eticheta de operator este o euristică după nume, nu o clasificare verificată."
}
}

View file

@ -0,0 +1,25 @@
{
"title": "Доступ к интернету там, где живут народы без вовлечённости",
"bands": {
"yes": "подключено 60%+",
"partial": "частично 25–59%",
"no": "низкий, менее 25%",
"unknown": "не измерено"
},
"peopleGroup": "народ",
"caveat": "НАЦИОНАЛЬНЫЕ данные (Всемирный банк). Данных о подключении по городам в открытом доступе нет — не судите о городе по стране.",
"sheet": {
"close": "Закрыть",
"ai": "Эти данные найдены с помощью углублённого поиска ИИ и могут содержать ошибки.",
"dismiss": "Скрыть",
"internetUsers": "Пользователи интернета",
"measured": "Год измерения",
"mobilePer100": "Мобильных на 100",
"peopleGroups": "Народы",
"topPlatform": "Главная платформа",
"unknown": "неизвестно",
"carriers": "Операторы и провайдеры ({n})",
"socialPlatforms": "Социальные платформы",
"note": "Национальные данные, а не измерения по городам. Названия операторов — это владельцы записей в реестре ASN; среди них есть банки и университеты, а не только провайдеры, поэтому пометка «оператор» — эвристика по названию, а не проверенная классификация."
}
}

View file

@ -0,0 +1,25 @@
{
"title": "未参与族群所在地的互联网接入",
"bands": {
"yes": "已联网 60%+",
"partial": "部分 25–59%",
"no": "低,低于 25%",
"unknown": "未测量"
},
"peopleGroup": "族群",
"caveat": "全国数据(世界银行)。各城市的联网数据未公开——请勿用国家数据推断某个城市。",
"sheet": {
"close": "关闭",
"ai": "这些数据来自人工智能深度检索,可能包含错误信息。",
"dismiss": "忽略",
"internetUsers": "互联网用户",
"measured": "测量年份",
"mobilePer100": "每百人移动电话",
"peopleGroups": "族群",
"topPlatform": "主要平台",
"unknown": "未知",
"carriers": "运营商和互联网服务商({n})",
"socialPlatforms": "社交平台",
"note": "这是全国数据,不是城市测量值。运营商名称来自 ASN 注册持有者——其中除互联网服务商外还包括银行和大学,因此“运营商”标记是基于名称的推断,并非经过核实的分类。"
}
}

View file

@ -33,6 +33,8 @@
* carrier flag is a NAME-PATTERN HEURISTIC, not a verified classification.
*/
import { computed, inject, ref, shallowRef, markRaw, onMounted, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import enConnectivity from '@map/i18n/locales/en/connectivity.json'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { addMapAttribution } from '@map/composables/useMapAttribution'
@ -151,12 +153,25 @@ function paintGroups(map) {
}
}
// ── UI text: library/i18n/locales/<lc>/connectivity.json, in all eleven locales ─────────
// i18n is OPTIONAL (the gospel-resources pattern): useI18n() throws on a host that never
// installed it, so English from the shipped catalog is the fallback, never a blank or a key.
// Carrier and platform names are data and are not translated.
const enLookup = (key) =>
String(key).replace(/^connectivity\./, '').split('.').reduce((o, k) => (o == null ? o : o[k]), enConnectivity)
const fill = (str, params) => (params ? String(str).replace(/\{(\w+)\}/g, (m, k) => (k in params ? params[k] : m)) : str)
let t = (key, params) => fill(enLookup(key) ?? String(key), params)
try {
const i18n = useI18n()
t = (key, params) => { const v = i18n.t(key, params || {}); return v === key ? fill(enLookup(key) ?? key, params) : v }
} catch (_) { /* no i18n on this host: the English catalog above is the fallback */ }
// Bands, not a continuous ramp: a reader can hold four categories, not a gradient.
const BANDS = [
{ id: 'yes', label: 'connected 60%+', color: '#10b981' },
{ id: 'partial', label: 'partial 25–59%', color: '#e8c15a' },
{ id: 'no', label: 'low, under 25%', color: '#b42318' },
{ id: 'unknown', label: 'not measured', color: '#9aa2ad' },
{ id: 'yes', color: '#10b981' },
{ id: 'partial', color: '#e8c15a' },
{ id: 'no', color: '#b42318' },
{ id: 'unknown', color: '#9aa2ad' },
]
const bandColor = (b) => (BANDS.find((x) => x.id === b) || {}).color || '#9aa2ad'
@ -293,13 +308,15 @@ useShadowStyles(
// correctly: `left:8px; width:var(--map-legend-width,380px)`. Bounded and
// left-anchored cannot reach the right rail, so the two can never overlap
// again — a geometric guarantee, not a tuned z-index.
'.gm-legend{position:absolute;top:8px;left:8px;width:var(--map-legend-width,380px);' +
// LOGICAL, not physical: the control rail sits at inset-inline-end, so in Arabic it moves to the
// LEFT; a legend pinned left:8px then sat under the zoom buttons (measured in the ar gallery tab).
'.gm-legend{position:absolute;top:8px;inset-inline-start:8px;width:var(--map-legend-width,380px);' +
'max-width:calc(100% - 16px);box-sizing:border-box;z-index:4;padding:8px 10px;' +
'border-radius:10px;background:rgba(255,255,255,.94);color:#0f1419;' +
"font:12px/1.35 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;box-shadow:0 2px 10px rgba(0,0,0,.16)}" +
// Narrow screens: the rail is still on the right, so the legend must stop short
// of it rather than run under it. 46px = 32px button + 3px inset + clearance.
'@media(max-width:480px){.gm-legend{width:auto;right:46px}}' +
'@media(max-width:480px){.gm-legend{width:auto;inset-inline-end:46px}}' +
'.is-dark .gm-legend{background:rgba(19,32,25,.95);color:#e5e7eb}' +
'.gm-title{font-weight:700;font-size:12.5px;margin-bottom:5px}' +
'.gm-keys{display:flex;flex-wrap:wrap;gap:5px 12px}' +
@ -310,7 +327,7 @@ useShadowStyles(
'.gm-sheet{position:absolute;left:0;right:0;bottom:0;z-index:6;max-height:62%;overflow-y:auto;' +
'background:#fff;color:#0f1419;border-radius:14px 14px 0 0;padding:14px 16px 22px;' +
"font:13px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;box-shadow:0 -6px 26px rgba(0,0,0,.28)}" +
'@media(min-width:700px){.gm-sheet{left:auto;right:14px;bottom:14px;width:330px;border-radius:12px}}' +
'@media(min-width:700px){.gm-sheet{inset-inline-start:auto;inset-inline-end:14px;bottom:14px;width:330px;border-radius:12px}}' +
'.is-dark .gm-sheet{background:#132019;color:#e5e7eb}' +
'.gm-sh-head{display:flex;align-items:center;gap:10px;margin-bottom:8px}' +
'.gm-sh-name{font-weight:800;font-size:15px}' +
@ -353,17 +370,17 @@ useShadowStyles(
</MapToolbar>
<div v-show="!groupOpen" class="gm-legend">
<div class="gm-title">Internet access where unengaged people groups live</div>
<div class="gm-title">{{ t('connectivity.title') }}</div>
<div class="gm-keys">
<span v-for="b in BANDS" :key="b.id" class="gm-key">
<i class="gm-sw" :style="{ background: b.color }"></i>{{ b.label }}
<i class="gm-sw" :style="{ background: b.color }"></i>{{ t('connectivity.bands.' + b.id) }}
</span>
<span v-if="groups.length" class="gm-key">
<i class="gm-dot" :style="{ background: PG_DOT }"></i>people group ({{ fmt(groups.length) }})
<i class="gm-dot" :style="{ background: PG_DOT }"></i>{{ t('connectivity.peopleGroup') }} ({{ fmt(groups.length) }})
</span>
</div>
<div class="gm-caveat">
NATIONAL figures (World Bank). Per-city connectivity is not publicly available — do not read a city off a country.
{{ t('connectivity.caveat') }}
</div>
</div>
@ -381,29 +398,26 @@ useShadowStyles(
<div v-if="selected" class="gm-sheet">
<div class="gm-sh-head">
<span class="gm-sh-name">{{ selected.country }}</span>
<button type="button" class="gm-x" @click="selected = null">Close</button>
<button type="button" class="gm-x" @click="selected = null">{{ t('connectivity.sheet.close') }}</button>
</div>
<div v-if="aiNoticeOpen" class="gm-ai">
<span>Data was found by AI deep research — it is subject to false information.</span>
<button type="button" class="gm-ai-x" @click="aiNoticeOpen = false" aria-label="Dismiss">×</button>
<span>{{ t('connectivity.sheet.ai') }}</span>
<button type="button" class="gm-ai-x" @click="aiNoticeOpen = false" :aria-label="t('connectivity.sheet.dismiss')">×</button>
</div>
<div class="gm-stat"><span>Internet users</span><b>{{ selected.internet_pct != null ? selected.internet_pct + '%' : 'unknown' }}</b></div>
<div class="gm-stat"><span>Measured</span><b>{{ selected.internet_year || '—' }}</b></div>
<div class="gm-stat"><span>Mobile per 100</span><b>{{ selected.mobile_per_100 ?? '—' }}</b></div>
<div class="gm-stat"><span>People groups</span><b>{{ fmt(selected.people_groups) }}</b></div>
<div class="gm-stat"><span>Top platform</span><b>{{ selected.top_platform || 'unknown' }}<template v-if="selected.top_platform_pct"> · {{ selected.top_platform_pct }}%</template></b></div>
<div class="gm-stat"><span>{{ t('connectivity.sheet.internetUsers') }}</span><b>{{ selected.internet_pct != null ? selected.internet_pct + '%' : t('connectivity.sheet.unknown') }}</b></div>
<div class="gm-stat"><span>{{ t('connectivity.sheet.measured') }}</span><b>{{ selected.internet_year || '—' }}</b></div>
<div class="gm-stat"><span>{{ t('connectivity.sheet.mobilePer100') }}</span><b>{{ selected.mobile_per_100 ?? '—' }}</b></div>
<div class="gm-stat"><span>{{ t('connectivity.sheet.peopleGroups') }}</span><b>{{ fmt(selected.people_groups) }}</b></div>
<div class="gm-stat"><span>{{ t('connectivity.sheet.topPlatform') }}</span><b>{{ selected.top_platform || t('connectivity.sheet.unknown') }}<template v-if="selected.top_platform_pct"> · {{ selected.top_platform_pct }}%</template></b></div>
<div v-if="selected.carriers && selected.carriers.length" class="gm-list">
<div class="gm-list-h">Carriers &amp; ISPs ({{ selected.carrier_count }})</div>
<div class="gm-list-h">{{ t('connectivity.sheet.carriers', { n: selected.carrier_count }) }}</div>
<ul><li v-for="(c, i) in selected.carriers" :key="i">{{ c }}</li></ul>
</div>
<div v-if="selected.platforms && selected.platforms.length" class="gm-list">
<div class="gm-list-h">Social platforms</div>
<div class="gm-list-h">{{ t('connectivity.sheet.socialPlatforms') }}</div>
<ul><li v-for="(p, i) in selected.platforms" :key="i">{{ p.name }} · {{ p.pct }}%</li></ul>
</div>
<p class="gm-note">
National figures, not city measurements. Carrier names are ASN registry holders — that set includes
banks and universities as well as ISPs, so the carrier flag is a name heuristic, not a verified classification.
</p>
<p class="gm-note">{{ t('connectivity.sheet.note') }}</p>
</div>
</div>
</template>

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
<script src="../cities-internet.js" integrity="sha384-rvaIXZcg4jjrxN6XHCvdI+DHoyZA0t2kiYyAThKirIGfpKM2qiFvvXKkEct/FLa3" crossorigin="anonymous"></script>
<script src="../cities-internet.js" integrity="sha384-DAdglvXNlk5pszrj+32A6kSxWJeFOxYHnP2TCsxmxp8oNAaIpFiFaNHJr6pFT9hX" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
<script src="../cities-internet.js" integrity="sha384-rvaIXZcg4jjrxN6XHCvdI+DHoyZA0t2kiYyAThKirIGfpKM2qiFvvXKkEct/FLa3" crossorigin="anonymous"></script>
<script src="../cities-internet.js" integrity="sha384-DAdglvXNlk5pszrj+32A6kSxWJeFOxYHnP2TCsxmxp8oNAaIpFiFaNHJr6pFT9hX" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <cities-internet-map> custom element. -->
<script src="../cities-internet.js" integrity="sha384-rvaIXZcg4jjrxN6XHCvdI+DHoyZA0t2kiYyAThKirIGfpKM2qiFvvXKkEct/FLa3" crossorigin="anonymous"></script>
<script src="../cities-internet.js" integrity="sha384-DAdglvXNlk5pszrj+32A6kSxWJeFOxYHnP2TCsxmxp8oNAaIpFiFaNHJr6pFT9hX" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
<script src="../../doxa-simple-map.js" integrity="sha384-OMLEhQZDOxDlsePrZj3SUJD9kGm2HXF8B/C+nmKtgB+cc4KGKZtYkFMxAXf/hyum" crossorigin="anonymous"></script>
<script src="../../doxa-simple-map.js" integrity="sha384-UNTsuIGRBdH3u7q8NOdFm7YZCgyfNseZ35e5Z9sipJQo7m6cnmz3vEU19jehgtcc" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
<script src="../../doxa-simple-map.js" integrity="sha384-OMLEhQZDOxDlsePrZj3SUJD9kGm2HXF8B/C+nmKtgB+cc4KGKZtYkFMxAXf/hyum" crossorigin="anonymous"></script>
<script src="../../doxa-simple-map.js" integrity="sha384-UNTsuIGRBdH3u7q8NOdFm7YZCgyfNseZ35e5Z9sipJQo7m6cnmz3vEU19jehgtcc" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
<script src="../doxa-simple-map.js" integrity="sha384-OMLEhQZDOxDlsePrZj3SUJD9kGm2HXF8B/C+nmKtgB+cc4KGKZtYkFMxAXf/hyum" crossorigin="anonymous"></script>
<script src="../doxa-simple-map.js" integrity="sha384-UNTsuIGRBdH3u7q8NOdFm7YZCgyfNseZ35e5Z9sipJQo7m6cnmz3vEU19jehgtcc" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <doxa-map> custom element. -->
<script src="../../doxa-simple-map.js" integrity="sha384-OMLEhQZDOxDlsePrZj3SUJD9kGm2HXF8B/C+nmKtgB+cc4KGKZtYkFMxAXf/hyum" crossorigin="anonymous"></script>
<script src="../../doxa-simple-map.js" integrity="sha384-UNTsuIGRBdH3u7q8NOdFm7YZCgyfNseZ35e5Z9sipJQo7m6cnmz3vEU19jehgtcc" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -105,7 +105,7 @@
"bundle": "cities-internet",
"scriptUrl": "./cities-internet/cities-internet.js",
"output": "doxa-maps/cities-internet/cities-internet.js",
"integrity": "sha384-rvaIXZcg4jjrxN6XHCvdI+DHoyZA0t2kiYyAThKirIGfpKM2qiFvvXKkEct/FLa3",
"integrity": "sha384-DAdglvXNlk5pszrj+32A6kSxWJeFOxYHnP2TCsxmxp8oNAaIpFiFaNHJr6pFT9hX",
"elements": [
"cities-internet-map"
],
@ -121,7 +121,7 @@
"scriptUrl": "./cities-internet/cities-internet.js",
"embed": {
"iframe": "<iframe src=\"./cities-internet/connectivity-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-rvaIXZcg4jjrxN6XHCvdI+DHoyZA0t2kiYyAThKirIGfpKM2qiFvvXKkEct/FLa3\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-DAdglvXNlk5pszrj+32A6kSxWJeFOxYHnP2TCsxmxp8oNAaIpFiFaNHJr6pFT9hX\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
}
},
{
@ -135,7 +135,7 @@
"scriptUrl": "./cities-internet/cities-internet.js",
"embed": {
"iframe": "<iframe src=\"./cities-internet/connectivity-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-rvaIXZcg4jjrxN6XHCvdI+DHoyZA0t2kiYyAThKirIGfpKM2qiFvvXKkEct/FLa3\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-DAdglvXNlk5pszrj+32A6kSxWJeFOxYHnP2TCsxmxp8oNAaIpFiFaNHJr6pFT9hX\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"connectivity-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
}
},
{
@ -149,7 +149,7 @@
"scriptUrl": "./cities-internet/cities-internet.js",
"embed": {
"iframe": "<iframe src=\"./cities-internet/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-rvaIXZcg4jjrxN6XHCvdI+DHoyZA0t2kiYyAThKirIGfpKM2qiFvvXKkEct/FLa3\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./cities-internet/cities-internet.js\" integrity=\"sha384-DAdglvXNlk5pszrj+32A6kSxWJeFOxYHnP2TCsxmxp8oNAaIpFiFaNHJr6pFT9hX\" crossorigin=\"anonymous\"></script>\n<cities-internet-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></cities-internet-map>"
}
}
]
@ -396,7 +396,7 @@
"bundle": "doxa-simple-map",
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
"output": "doxa-maps/doxa-simple-map/doxa-simple-map.js",
"integrity": "sha384-OMLEhQZDOxDlsePrZj3SUJD9kGm2HXF8B/C+nmKtgB+cc4KGKZtYkFMxAXf/hyum",
"integrity": "sha384-UNTsuIGRBdH3u7q8NOdFm7YZCgyfNseZ35e5Z9sipJQo7m6cnmz3vEU19jehgtcc",
"elements": [
"doxa-map",
"doxa-simple-map"
@ -413,7 +413,7 @@
"scriptUrl": "./doxa-simple-map/doxa-simple-map.js",
"embed": {
"iframe": "<iframe src=\"./doxa-simple-map/doxa-simple-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-OMLEhQZDOxDlsePrZj3SUJD9kGm2HXF8B/C+nmKtgB+cc4KGKZtYkFMxAXf/hyum\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-simple-map/doxa-simple-map.js\" integrity=\"sha384-UNTsuIGRBdH3u7q8NOdFm7YZCgyfNseZ35e5Z9sipJQo7m6cnmz3vEU19jehgtcc\" crossorigin=\"anonymous\"></script>\n<doxa-map profile-config='{\"profile\":\"doxa-simple-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-map>"
},
"instances": [
{