feat: drive blog hub from json
This commit is contained in:
@@ -0,0 +1,44 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"area": "Grant compliance",
|
||||||
|
"title": "Grant-ready NGO ERP checklist",
|
||||||
|
"teaser": "<p>Map every grant condition to automated workflows and never miss a reporting milestone again.</p>",
|
||||||
|
"content": "<p>Map every grant condition to automated workflows and never miss a reporting milestone again. Start by linking each donor requirement to a workflow stage, add automated reminders for supporting documents, and use a shared KPI dashboard so finance and program leads see the same data.</p>",
|
||||||
|
"date": "2026-03-20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"area": "Field operations",
|
||||||
|
"title": "Digitize volunteer onboarding",
|
||||||
|
"teaser": "<p>Standardize approvals, ID checks, and training with low-code flows tied directly to your ERP workspace.</p>",
|
||||||
|
"content": "<p>Standardize approvals, ID checks, and training with low-code flows tied directly to your ERP workspace. Issue dynamic checklists to each volunteer, collect signatures on mobile devices, and sync completion data back to HR and program rosters instantly.</p>",
|
||||||
|
"date": "2026-03-18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"area": "Impact storytelling",
|
||||||
|
"title": "From KPI to donor story",
|
||||||
|
"teaser": "<p>Turn ERP data into visual narratives that inspire board members and keep major donors engaged.</p>",
|
||||||
|
"content": "<p>Turn ERP data into visual narratives that inspire board members and keep major donors engaged. Pair outcome metrics with qualitative anecdotes, embed automated charts directly into newsletters, and reuse the same narrative blocks across social, email, and board decks.</p>",
|
||||||
|
"date": "2026-03-15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"area": "Finance automation",
|
||||||
|
"title": "Close monthly books in 48 hours",
|
||||||
|
"teaser": "<p>Use consolidated ledgers, smart reconciliations, and approval routing to shrink month-end crunch time.</p>",
|
||||||
|
"content": "<p>Use consolidated ledgers, smart reconciliations, and approval routing to shrink month-end crunch time. Automate multi-currency FX adjustments, surface outlier transactions for manual review, and share a read-only dashboard with leadership the moment the books are closed.</p>",
|
||||||
|
"date": "2026-03-12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"area": "Localisation",
|
||||||
|
"title": "Deliver parity across EN/ES/DE",
|
||||||
|
"teaser": "<p>Keep every landing page, email sequence, and CTA synchronized across languages without copy drift.</p>",
|
||||||
|
"content": "<p>Keep every landing page, email sequence, and CTA synchronized across languages without copy drift. Store shared messaging pillars in your ERP knowledge base, tag assets by language, and schedule automated QA passes before campaigns launch.</p>",
|
||||||
|
"date": "2026-03-10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"area": "Data strategy",
|
||||||
|
"title": "Build an NGO impact warehouse",
|
||||||
|
"teaser": "<p>Consolidate donor, program, and finance data so every report starts from a trustworthy baseline.</p>",
|
||||||
|
"content": "<p>Consolidate donor, program, and finance data so every report starts from a trustworthy baseline. Use ELT pipelines into an affordable warehouse, enforce a shared taxonomy for indicators, and expose curated models to analysts and program teams alike.</p>",
|
||||||
|
"date": "2026-03-08"
|
||||||
|
}
|
||||||
|
]
|
||||||
+151
-66
@@ -144,28 +144,10 @@
|
|||||||
<h2 class="text-3xl font-semibold" data-i18n="blog-title"></h2>
|
<h2 class="text-3xl font-semibold" data-i18n="blog-title"></h2>
|
||||||
<p class="text-brand-ink/80" data-i18n="blog-subtitle"></p>
|
<p class="text-brand-ink/80" data-i18n="blog-subtitle"></p>
|
||||||
</div>
|
</div>
|
||||||
<a href="#" class="text-brand-accent font-semibold" data-i18n="blog-link"></a>
|
<button id="blog-toggle" type="button" class="text-brand-accent font-semibold hover:underline underline-offset-4">Browse all articles →</button>
|
||||||
</div>
|
|
||||||
<div class="grid gap-6 md:grid-cols-3">
|
|
||||||
<article class="border border-brand-smoke rounded-3xl p-6 h-full flex flex-col">
|
|
||||||
<p class="text-xs uppercase tracking-widest text-brand-primary mb-3" data-i18n="blog1-tag"></p>
|
|
||||||
<h3 class="text-xl font-semibold mb-3" data-i18n="blog1-title"></h3>
|
|
||||||
<p class="text-sm text-brand-ink/80 mb-6" data-i18n="blog1-desc"></p>
|
|
||||||
<a href="#" class="mt-auto text-sm font-semibold text-brand-accent" data-i18n="blog1-link" data-i18n-aria="blog1-aria"></a>
|
|
||||||
</article>
|
|
||||||
<article class="border border-brand-smoke rounded-3xl p-6 h-full flex flex-col">
|
|
||||||
<p class="text-xs uppercase tracking-widest text-brand-primary mb-3" data-i18n="blog2-tag"></p>
|
|
||||||
<h3 class="text-xl font-semibold mb-3" data-i18n="blog2-title"></h3>
|
|
||||||
<p class="text-sm text-brand-ink/80 mb-6" data-i18n="blog2-desc"></p>
|
|
||||||
<a href="#" class="mt-auto text-sm font-semibold text-brand-accent" data-i18n="blog2-link" data-i18n-aria="blog2-aria"></a>
|
|
||||||
</article>
|
|
||||||
<article class="border border-brand-smoke rounded-3xl p-6 h-full flex flex-col">
|
|
||||||
<p class="text-xs uppercase tracking-widest text-brand-primary mb-3" data-i18n="blog3-tag"></p>
|
|
||||||
<h3 class="text-xl font-semibold mb-3" data-i18n="blog3-title"></h3>
|
|
||||||
<p class="text-sm text-brand-ink/80 mb-6" data-i18n="blog3-desc"></p>
|
|
||||||
<a href="#" class="mt-auto text-sm font-semibold text-brand-accent" data-i18n="blog3-link" data-i18n-aria="blog3-aria"></a>
|
|
||||||
</article>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="blog-grid" class="grid gap-6 md:grid-cols-3"></div>
|
||||||
|
<p id="blog-error" class="text-sm text-brand-ink/70 mt-6 hidden"></p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -264,21 +246,10 @@
|
|||||||
'blog-title': 'Blog & resource hub',
|
'blog-title': 'Blog & resource hub',
|
||||||
'blog-subtitle': 'Stay ahead with tutorials, case studies, and playbooks for NGO ERP rollouts.',
|
'blog-subtitle': 'Stay ahead with tutorials, case studies, and playbooks for NGO ERP rollouts.',
|
||||||
'blog-link': 'Browse all articles →',
|
'blog-link': 'Browse all articles →',
|
||||||
'blog1-tag': 'Grant compliance',
|
'blog-all-open': 'Showing all articles',
|
||||||
'blog1-title': 'Grant-ready NGO ERP checklist',
|
'blog-read-more': 'Read more',
|
||||||
'blog1-desc': 'Map every grant condition to automated workflows and never miss a reporting milestone again.',
|
'blog-show-less': 'Show less',
|
||||||
'blog1-link': 'Read the guide',
|
'blog-error': 'Unable to load articles right now.',
|
||||||
'blog1-aria': 'Read the grant-ready NGO ERP checklist',
|
|
||||||
'blog2-tag': 'Field operations',
|
|
||||||
'blog2-title': 'Digitize volunteer onboarding',
|
|
||||||
'blog2-desc': 'Standardize approvals, ID checks, and training with low-code workflows tied to your ERP.',
|
|
||||||
'blog2-link': 'View playbook',
|
|
||||||
'blog2-aria': 'Read the volunteer onboarding playbook',
|
|
||||||
'blog3-tag': 'Impact storytelling',
|
|
||||||
'blog3-title': 'From KPI to donor story',
|
|
||||||
'blog3-desc': 'Turn ERP data into visual narratives that inspire donors and board members.',
|
|
||||||
'blog3-link': 'See examples',
|
|
||||||
'blog3-aria': 'See donor storytelling examples',
|
|
||||||
'trial-tagline': 'Sign up free trial',
|
'trial-tagline': 'Sign up free trial',
|
||||||
'trial-heading': 'Launch your NGO ERP sandbox in under 5 minutes',
|
'trial-heading': 'Launch your NGO ERP sandbox in under 5 minutes',
|
||||||
'trial-body': 'Experience localized NGO templates, donor CRM, and automation recipes. No credit card required—just choose your focus area and invite teammates.',
|
'trial-body': 'Experience localized NGO templates, donor CRM, and automation recipes. No credit card required—just choose your focus area and invite teammates.',
|
||||||
@@ -335,21 +306,10 @@
|
|||||||
'blog-title': 'Blog y centro de recursos',
|
'blog-title': 'Blog y centro de recursos',
|
||||||
'blog-subtitle': 'Aprende con tutoriales, casos prácticos y playbooks para implementar un ERP en tu ONG.',
|
'blog-subtitle': 'Aprende con tutoriales, casos prácticos y playbooks para implementar un ERP en tu ONG.',
|
||||||
'blog-link': 'Ver todos los artículos →',
|
'blog-link': 'Ver todos los artículos →',
|
||||||
'blog1-tag': 'Cumplimiento',
|
'blog-all-open': 'Mostrando todos los artículos',
|
||||||
'blog1-title': 'Checklist ERP listo para subvenciones',
|
'blog-read-more': 'Leer más',
|
||||||
'blog1-desc': 'Relaciona cada requisito de la subvención con flujos automatizados y evita retrasos en los reportes.',
|
'blog-show-less': 'Mostrar menos',
|
||||||
'blog1-link': 'Leer la guía',
|
'blog-error': 'No pudimos cargar los artículos en este momento.',
|
||||||
'blog1-aria': 'Leer la guía ERP para subvenciones',
|
|
||||||
'blog2-tag': 'Operaciones de campo',
|
|
||||||
'blog2-title': 'Digitaliza la incorporación de voluntarios',
|
|
||||||
'blog2-desc': 'Estandariza aprobaciones, verificaciones e inducciones con flujos low-code conectados a tu ERP.',
|
|
||||||
'blog2-link': 'Ver playbook',
|
|
||||||
'blog2-aria': 'Ver el playbook de voluntariado',
|
|
||||||
'blog3-tag': 'Narrativas de impacto',
|
|
||||||
'blog3-title': 'Del KPI a la historia del donante',
|
|
||||||
'blog3-desc': 'Convierte datos del ERP en historias visuales que inspiran a donantes y juntas directivas.',
|
|
||||||
'blog3-link': 'Ver ejemplos',
|
|
||||||
'blog3-aria': 'Ver ejemplos de historias para donantes',
|
|
||||||
'trial-tagline': 'Activa tu prueba',
|
'trial-tagline': 'Activa tu prueba',
|
||||||
'trial-heading': 'Lanza tu sandbox ERP para ONG en menos de 5 minutos',
|
'trial-heading': 'Lanza tu sandbox ERP para ONG en menos de 5 minutos',
|
||||||
'trial-body': 'Explora plantillas localizadas, CRM de donantes y recetas de automatización. Sin tarjeta; solo elige tu prioridad e invita al equipo.',
|
'trial-body': 'Explora plantillas localizadas, CRM de donantes y recetas de automatización. Sin tarjeta; solo elige tu prioridad e invita al equipo.',
|
||||||
@@ -406,21 +366,10 @@
|
|||||||
'blog-title': 'Blog & Wissenshub',
|
'blog-title': 'Blog & Wissenshub',
|
||||||
'blog-subtitle': 'Tutorials, Fallstudien und Playbooks für erfolgreiche NGO-ERP-Rollouts.',
|
'blog-subtitle': 'Tutorials, Fallstudien und Playbooks für erfolgreiche NGO-ERP-Rollouts.',
|
||||||
'blog-link': 'Alle Artikel ansehen →',
|
'blog-link': 'Alle Artikel ansehen →',
|
||||||
'blog1-tag': 'Förder-Compliance',
|
'blog-all-open': 'Alle Artikel werden angezeigt',
|
||||||
'blog1-title': 'ERP-Checkliste für Fördermittel',
|
'blog-read-more': 'Mehr lesen',
|
||||||
'blog1-desc': 'Verknüpfe jede Auflage mit automatisierten Workflows und vermeide verspätete Berichte.',
|
'blog-show-less': 'Weniger anzeigen',
|
||||||
'blog1-link': 'Zur Anleitung',
|
'blog-error': 'Artikel konnten gerade nicht geladen werden.',
|
||||||
'blog1-aria': 'ERP-Checkliste für Fördermittel lesen',
|
|
||||||
'blog2-tag': 'Feldeinsätze',
|
|
||||||
'blog2-title': 'Ehrenamtliche digital onboarden',
|
|
||||||
'blog2-desc': 'Standardisiere Freigaben, ID-Prüfungen und Schulungen mit Low-Code-Flows im ERP.',
|
|
||||||
'blog2-link': 'Playbook ansehen',
|
|
||||||
'blog2-aria': 'Playbook zur Ehrenamtlichen-Onboarding ansehen',
|
|
||||||
'blog3-tag': 'Impact Storytelling',
|
|
||||||
'blog3-title': 'Vom KPI zur Spenderstory',
|
|
||||||
'blog3-desc': 'Mach aus ERP-Daten visuelle Geschichten, die Spender und Boards überzeugen.',
|
|
||||||
'blog3-link': 'Beispiele ansehen',
|
|
||||||
'blog3-aria': 'Beispiele für Spenderstorys ansehen',
|
|
||||||
'trial-tagline': 'Kostenlos testen',
|
'trial-tagline': 'Kostenlos testen',
|
||||||
'trial-heading': 'Starte dein NGO-ERP-Sandbox in unter 5 Minuten',
|
'trial-heading': 'Starte dein NGO-ERP-Sandbox in unter 5 Minuten',
|
||||||
'trial-body': 'Teste lokalisierte Templates, Donor-CRM und Automatisierungen. Keine Kreditkarte – wähle den Fokus und lade dein Team ein.',
|
'trial-body': 'Teste lokalisierte Templates, Donor-CRM und Automatisierungen. Keine Kreditkarte – wähle den Fokus und lade dein Team ein.',
|
||||||
@@ -455,6 +404,140 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const BLOG_LIMIT = 3;
|
||||||
|
const blogGrid = document.getElementById('blog-grid');
|
||||||
|
const blogToggle = document.getElementById('blog-toggle');
|
||||||
|
const blogError = document.getElementById('blog-error');
|
||||||
|
let blogPosts = [];
|
||||||
|
let blogExpanded = false;
|
||||||
|
|
||||||
|
if (blogGrid) {
|
||||||
|
fetch('blog.json')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
blogPosts = Array.isArray(data) ? data : [];
|
||||||
|
renderBlogCards();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (blogError) {
|
||||||
|
blogError.classList.remove('hidden');
|
||||||
|
blogError.textContent = getTranslation(document.documentElement.lang, 'blog-error', 'Unable to load articles right now.');
|
||||||
|
}
|
||||||
|
console.error('blog.json load failed', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blogToggle) {
|
||||||
|
blogToggle.addEventListener('click', () => {
|
||||||
|
if (blogExpanded || blogPosts.length <= BLOG_LIMIT) return;
|
||||||
|
blogExpanded = true;
|
||||||
|
renderBlogCards();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTranslation(locale, key, fallback) {
|
||||||
|
return (translations[locale] && translations[locale][key]) ?? translations.en[key] ?? fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBlogCards() {
|
||||||
|
if (!blogGrid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!blogPosts.length) {
|
||||||
|
blogGrid.innerHTML = '';
|
||||||
|
updateBlogToggleLabel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const limit = blogExpanded ? blogPosts.length : Math.min(BLOG_LIMIT, blogPosts.length);
|
||||||
|
blogGrid.innerHTML = '';
|
||||||
|
blogPosts.slice(0, limit).forEach(post => {
|
||||||
|
blogGrid.appendChild(createBlogCard(post));
|
||||||
|
});
|
||||||
|
updateBlogToggleLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBlogCard(post) {
|
||||||
|
const card = document.createElement('article');
|
||||||
|
card.className = 'border border-brand-smoke rounded-3xl p-6 h-full flex flex-col';
|
||||||
|
|
||||||
|
const meta = document.createElement('div');
|
||||||
|
meta.className = 'flex items-center justify-between mb-3';
|
||||||
|
const area = document.createElement('p');
|
||||||
|
area.className = 'text-xs uppercase tracking-widest text-brand-primary';
|
||||||
|
area.textContent = post.area || '';
|
||||||
|
const dateEl = document.createElement('time');
|
||||||
|
dateEl.className = 'text-xs text-brand-ink/60';
|
||||||
|
if (post.date) {
|
||||||
|
dateEl.dateTime = post.date;
|
||||||
|
}
|
||||||
|
dateEl.textContent = formatBlogDate(post.date);
|
||||||
|
meta.append(area, dateEl);
|
||||||
|
card.appendChild(meta);
|
||||||
|
|
||||||
|
const title = document.createElement('h3');
|
||||||
|
title.className = 'text-xl font-semibold mb-3';
|
||||||
|
title.textContent = post.title || '';
|
||||||
|
card.appendChild(title);
|
||||||
|
|
||||||
|
const teaser = document.createElement('div');
|
||||||
|
teaser.className = 'text-sm text-brand-ink/80 space-y-3 mb-4';
|
||||||
|
teaser.innerHTML = post.teaser || '';
|
||||||
|
card.appendChild(teaser);
|
||||||
|
|
||||||
|
let contentBlock = null;
|
||||||
|
const hasMore = post.content && post.content.trim() !== (post.teaser || '').trim();
|
||||||
|
if (post.content) {
|
||||||
|
contentBlock = document.createElement('div');
|
||||||
|
contentBlock.className = 'text-sm text-brand-ink/80 space-y-3 mb-4 hidden';
|
||||||
|
contentBlock.innerHTML = post.content;
|
||||||
|
card.appendChild(contentBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMore && contentBlock) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 'text-sm font-semibold text-brand-accent underline-offset-4 hover:underline self-start';
|
||||||
|
btn.textContent = getTranslation(document.documentElement.lang, 'blog-read-more', 'Read more');
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const isHidden = contentBlock.classList.contains('hidden');
|
||||||
|
if (isHidden) {
|
||||||
|
contentBlock.classList.remove('hidden');
|
||||||
|
btn.textContent = getTranslation(document.documentElement.lang, 'blog-show-less', 'Show less');
|
||||||
|
} else {
|
||||||
|
contentBlock.classList.add('hidden');
|
||||||
|
btn.textContent = getTranslation(document.documentElement.lang, 'blog-read-more', 'Read more');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
card.appendChild(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatBlogDate(dateString) {
|
||||||
|
if (!dateString) return '';
|
||||||
|
try {
|
||||||
|
const locale = document.documentElement.lang || 'en';
|
||||||
|
return new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'short', day: 'numeric' }).format(new Date(dateString));
|
||||||
|
} catch (error) {
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBlogToggleLabel() {
|
||||||
|
if (!blogToggle) return;
|
||||||
|
if (blogPosts.length <= BLOG_LIMIT) {
|
||||||
|
blogToggle.classList.add('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
blogToggle.classList.remove('hidden');
|
||||||
|
blogToggle.disabled = blogExpanded;
|
||||||
|
const locale = document.documentElement.lang || 'en';
|
||||||
|
blogToggle.textContent = blogExpanded
|
||||||
|
? getTranslation(locale, 'blog-all-open', 'Showing all articles')
|
||||||
|
: getTranslation(locale, 'blog-link', 'Browse all articles →');
|
||||||
|
}
|
||||||
|
|
||||||
function setLanguage(lang) {
|
function setLanguage(lang) {
|
||||||
const locale = translations[lang] ? lang : 'en';
|
const locale = translations[lang] ? lang : 'en';
|
||||||
document.documentElement.lang = locale;
|
document.documentElement.lang = locale;
|
||||||
@@ -478,6 +561,8 @@
|
|||||||
const value = translations[locale][key] ?? translations.en[key] ?? '';
|
const value = translations[locale][key] ?? translations.en[key] ?? '';
|
||||||
if (value !== undefined) el.setAttribute('aria-label', value);
|
if (value !== undefined) el.setAttribute('aria-label', value);
|
||||||
});
|
});
|
||||||
|
renderBlogCards();
|
||||||
|
updateBlogToggleLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('lang-selector').addEventListener('change', (event) => {
|
document.getElementById('lang-selector').addEventListener('change', (event) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user