Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
271 changes: 203 additions & 68 deletions src/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
}

header h1 {
Expand All @@ -46,6 +48,40 @@

header h1 span { color: var(--accent); }

.header-right {
display: flex;
align-items: center;
gap: 1rem;
}

.lang-toggle {
display: flex;
gap: 2px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 3px;
padding: 2px;
}

.lang-btn {
background: none;
border: none;
color: var(--text-dim);
font-size: 0.68rem;
font-family: var(--font-mono);
padding: 0.2rem 0.5rem;
border-radius: 2px;
cursor: pointer;
letter-spacing: 0.05em;
}

.lang-btn.active {
background: var(--accent);
color: #fff;
}

.lang-btn:hover:not(.active) { color: var(--text); }

header a {
font-size: 0.75rem;
font-family: var(--font-mono);
Expand Down Expand Up @@ -146,10 +182,7 @@
color: var(--text-dim);
}

.meta-bar span b {
color: #94a3b8;
font-weight: 500;
}
.meta-bar span b { color: #94a3b8; font-weight: 500; }

.grid {
display: grid;
Expand Down Expand Up @@ -288,84 +321,192 @@
@media (max-width: 640px) {
.stats-bar { grid-template-columns: repeat(2, 1fr); }
.stat { border-right: none; border-bottom: 1px solid var(--border); }
header { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
header { flex-direction: column; align-items: flex-start; }
}
</style>
</head>
<body>

<header>
<h1><span>GLAPAGOS</span> / regional ai governance dashboard</h1>
<a href="https://github.com/castroquiles/glapagos" target="_blank">github</a>
<h1><span>GLAPAGOS</span> / <span id="h-subtitle">regional ai governance dashboard</span></h1>
<div class="header-right">
<div class="lang-toggle">
<button class="lang-btn active" onclick="setLang('en')">EN</button>
<button class="lang-btn" onclick="setLang('es')">ES</button>
<button class="lang-btn" onclick="setLang('pt')">PT</button>
</div>
<a href="https://github.com/castroquiles/glapagos" target="_blank">github</a>
</div>
</header>

<div class="stats-bar">
<div class="stat">
<div class="stat-value" id="stat-total">--</div>
<div class="stat-label">Initiatives</div>
<div class="stat-label" id="label-initiatives">Initiatives</div>
</div>
<div class="stat">
<div class="stat-value" id="stat-countries">--</div>
<div class="stat-label">Countries Covered</div>
<div class="stat-label" id="label-countries">Countries Covered</div>
</div>
<div class="stat">
<div class="stat-value" id="stat-domains">--</div>
<div class="stat-label">Domains</div>
<div class="stat-label" id="label-domains">Domains</div>
</div>
<div class="stat">
<div class="stat-value" id="stat-types">--</div>
<div class="stat-label">Initiative Types</div>
<div class="stat-label" id="label-types">Initiative Types</div>
</div>
</div>

<div class="controls">
<label>Domain</label>
<label id="label-domain">Domain</label>
<select id="filter-domain">
<option value="">All</option>
<option value="" id="opt-all-domain">All</option>
<option value="governance">Governance</option>
<option value="policy">Policy</option>
<option value="foundation-models">Foundation Models</option>
<option value="infrastructure">Infrastructure</option>
<option value="safety">Safety</option>
</select>

<label>Type</label>
<label id="label-type">Type</label>
<select id="filter-type">
<option value="">All</option>
<option value="multilateral">Multilateral</option>
<option value="national">National</option>
<option value="research">Research</option>
<option value="legislative">Legislative</option>
<option value="" id="opt-all-type">All</option>
<option value="multilateral" id="opt-multilateral">Multilateral</option>
<option value="national" id="opt-national">National</option>
<option value="research" id="opt-research">Research</option>
<option value="legislative" id="opt-legislative">Legislative</option>
</select>

<label>Status</label>
<label id="label-status">Status</label>
<select id="filter-status">
<option value="">All</option>
<option value="active">Active</option>
<option value="passed">Passed</option>
<option value="" id="opt-all-status">All</option>
<option value="active" id="opt-active">Active</option>
<option value="passed" id="opt-passed">Passed</option>
</select>

<button class="reset-btn" id="reset-btn">reset</button>
<span class="count-label" id="count-label"></span>
</div>

<div class="meta-bar" id="meta-bar"></div>

<div class="grid" id="grid"></div>

<footer>
<span>glapagos v0.1.0 -- open multilateral ai platform for the americas</span>
<span id="footer-text">glapagos v0.1.0 -- open multilateral ai platform for the americas</span>
<a href="https://www.glapagos.com" target="_blank">glapagos.com</a>
</footer>

<script>
// Data is injected at build time by scripts/build_dashboard.py.
// The placeholder __INITIATIVES_DATA__ is replaced with the full
// contents of data/initiatives.json before deployment.
const DATASET = __INITIATIVES_DATA__;

const I18N = {
en: {
subtitle: "regional ai governance dashboard",
initiatives: "Initiatives",
countries: "Countries Covered",
domains: "Domains",
types: "Initiative Types",
domain: "Domain",
type: "Type",
status: "Status",
all: "All",
multilateral: "Multilateral",
national: "National",
research: "Research",
legislative: "Legislative",
active: "Active",
passed: "Passed",
reset: "reset",
results: function(n) { return n + " result" + (n !== 1 ? "s" : ""); },
empty: "no initiatives match the current filters.",
version: "version",
updated: "updated",
source: "source",
footer: "glapagos v0.1.0 -- open multilateral ai platform for the americas",
},
es: {
subtitle: "panel de gobernanza de ia regional",
initiatives: "Iniciativas",
countries: "Paises Cubiertos",
domains: "Dominios",
types: "Tipos de Iniciativa",
domain: "Dominio",
type: "Tipo",
status: "Estado",
all: "Todos",
multilateral: "Multilateral",
national: "Nacional",
research: "Investigacion",
legislative: "Legislativo",
active: "Activo",
passed: "Aprobado",
reset: "reiniciar",
results: function(n) { return n + " resultado" + (n !== 1 ? "s" : ""); },
empty: "ninguna iniciativa coincide con los filtros actuales.",
version: "version",
updated: "actualizado",
source: "fuente",
footer: "glapagos v0.1.0 -- plataforma multilateral abierta de ia para las americas",
},
pt: {
subtitle: "painel de governanca de ia regional",
initiatives: "Iniciativas",
countries: "Paises Cobertos",
domains: "Dominios",
types: "Tipos de Iniciativa",
domain: "Dominio",
type: "Tipo",
status: "Status",
all: "Todos",
multilateral: "Multilateral",
national: "Nacional",
research: "Pesquisa",
legislative: "Legislativo",
active: "Ativo",
passed: "Aprovado",
reset: "redefinir",
results: function(n) { return n + " resultado" + (n !== 1 ? "s" : ""); },
empty: "nenhuma iniciativa corresponde aos filtros atuais.",
version: "versao",
updated: "atualizado",
source: "fonte",
footer: "glapagos v0.1.0 -- plataforma multilateral aberta de ia para as americas",
}
};

var currentLang = "en";

function setLang(lang) {
currentLang = lang;
var t = I18N[lang];
document.querySelectorAll(".lang-btn").forEach(function(btn) {
btn.classList.toggle("active", btn.textContent === lang.toUpperCase());
});
document.getElementById("h-subtitle").textContent = t.subtitle;
document.getElementById("label-initiatives").textContent = t.initiatives;
document.getElementById("label-countries").textContent = t.countries;
document.getElementById("label-domains").textContent = t.domains;
document.getElementById("label-types").textContent = t.types;
document.getElementById("label-domain").textContent = t.domain;
document.getElementById("label-type").textContent = t.type;
document.getElementById("label-status").textContent = t.status;
document.getElementById("opt-all-domain").textContent = t.all;
document.getElementById("opt-all-type").textContent = t.all;
document.getElementById("opt-all-status").textContent = t.all;
document.getElementById("opt-multilateral").textContent = t.multilateral;
document.getElementById("opt-national").textContent = t.national;
document.getElementById("opt-research").textContent = t.research;
document.getElementById("opt-legislative").textContent = t.legislative;
document.getElementById("opt-active").textContent = t.active;
document.getElementById("opt-passed").textContent = t.passed;
document.getElementById("reset-btn").textContent = t.reset;
document.getElementById("footer-text").textContent = t.footer;
render();
}

function domainClass(domain) {
const map = {
var map = {
"governance": "badge-governance",
"policy": "badge-policy",
"foundation-models": "badge-foundation-models",
Expand All @@ -376,39 +517,35 @@ <h1><span>GLAPAGOS</span> / regional ai governance dashboard</h1>
}

function computeStats(initiatives) {
const domains = {};
const types = {};
const countries = new Set();
var domains = {}, types = {}, countries = new Set();
initiatives.forEach(function(item) {
domains[item.domain] = (domains[item.domain] || 0) + 1;
types[item.type] = (types[item.type] || 0) + 1;
types[item.type] = (types[item.type] || 0) + 1;
item.countries.forEach(function(c) { countries.add(c); });
});
return { domains, types, countries };
return { domains: domains, types: types, countries: countries };
}

function renderCard(item) {
const countries = item.countries.length > 8
var countries = item.countries.length > 8
? item.countries.slice(0, 8).join(" ") + " +" + (item.countries.length - 8) + " more"
: item.countries.join(" ");
return `
<div class="card" onclick="window.open('${item.url}', '_blank')">
<div class="card-header">
<div class="card-title">${item.name}</div>
<span class="badge ${domainClass(item.domain)}">${item.domain}</span>
</div>
<div class="card-institution">${item.institution}</div>
<div class="card-description">${item.description}</div>
<div class="card-footer">
<span class="type-tag">
<span class="status-dot ${item.status !== "active" ? item.status : ""}"></span>
${item.type} / ${item.status}
</span>
<span class="year-tag">${item.year}</span>
</div>
<div class="countries">${countries}</div>
</div>
`;
return '<div class="card" onclick="window.open(\'' + item.url + '\', \'_blank\')">'
+ '<div class="card-header">'
+ '<div class="card-title">' + item.name + '</div>'
+ '<span class="badge ' + domainClass(item.domain) + '">' + item.domain + '</span>'
+ '</div>'
+ '<div class="card-institution">' + item.institution + '</div>'
+ '<div class="card-description">' + item.description + '</div>'
+ '<div class="card-footer">'
+ '<span class="type-tag">'
+ '<span class="status-dot ' + (item.status !== "active" ? item.status : "") + '"></span>'
+ item.type + ' / ' + item.status
+ '</span>'
+ '<span class="year-tag">' + item.year + '</span>'
+ '</div>'
+ '<div class="countries">' + countries + '</div>'
+ '</div>';
}

function getFilters() {
Expand All @@ -429,33 +566,31 @@ <h1><span>GLAPAGOS</span> / regional ai governance dashboard</h1>
}

function render() {
const filters = getFilters();
const results = applyFilters(DATASET.initiatives, filters);
const grid = document.getElementById("grid");
const label = document.getElementById("count-label");

var t = I18N[currentLang];
var filters = getFilters();
var results = applyFilters(DATASET.initiatives, filters);
var grid = document.getElementById("grid");
var label = document.getElementById("count-label");
if (results.length === 0) {
grid.innerHTML = '<div class="empty">no initiatives match the current filters.</div>';
label.textContent = "0 results";
grid.innerHTML = '<div class="empty">' + t.empty + '</div>';
label.textContent = t.results(0);
return;
}

grid.innerHTML = results.map(renderCard).join("");
label.textContent = results.length + " result" + (results.length !== 1 ? "s" : "");
label.textContent = t.results(results.length);
}

function init() {
const stats = computeStats(DATASET.initiatives);
var t = I18N[currentLang];
var stats = computeStats(DATASET.initiatives);
document.getElementById("stat-total").textContent = DATASET.initiatives.length;
document.getElementById("stat-countries").textContent = stats.countries.size;
document.getElementById("stat-domains").textContent = Object.keys(stats.domains).length;
document.getElementById("stat-types").textContent = Object.keys(stats.types).length;

document.getElementById("meta-bar").innerHTML =
'<span>version <b>' + DATASET.version + '</b></span>' +
'<span>updated <b>' + DATASET.updated + '</b></span>' +
'<span>source <b>data/initiatives.json</b></span>';

'<span>' + t.version + ' <b>' + DATASET.version + '</b></span>' +
'<span>' + t.updated + ' <b>' + DATASET.updated + '</b></span>' +
'<span>' + t.source + ' <b>data/initiatives.json</b></span>';
render();
}

Expand Down
Loading