feat: add new project and single page layouts

- Created a new layout for projects with sections for hero, projects, roadmap, selection criteria, and a call to action.
- Added a single page layout for individual project details, including a hero section and content area.
- Introduced a brand highlight partial to emphasize "TheAfriForge" in text.
- Implemented an icon partial for reusable SVG icons across the site.
- Developed a site header and footer with navigation and legal links, adapting for language variations.
- Enhanced the index layout with sections for hero, stats, pillars, projects, and members.
- Added a development script to load environment variables and run Hugo with optional arguments.
This commit is contained in:
Cheick Oumar 2026-05-17 17:11:54 +02:00
commit 3dbacc94d3
35 changed files with 3046 additions and 0 deletions

View file

@ -0,0 +1,16 @@
{{ if eq .Level 2 }}
<div class="mt-16 mb-6">
<div class="flex items-start gap-4">
<div class="mt-1.5 h-6 w-1.5 flex-shrink-0 rounded-full bg-green-500"></div>
<h2 id="{{ .Anchor }}" class="text-2xl font-black text-slate-900 leading-snug">{{ .Text | safeHTML }}</h2>
</div>
<div class="mt-4 ml-5 h-px bg-slate-100"></div>
</div>
{{ else if eq .Level 3 }}
<h3 id="{{ .Anchor }}" class="mt-8 mb-3 flex items-center gap-2 text-base font-bold uppercase tracking-widest text-green-700">
<span class="inline-block h-1.5 w-1.5 rounded-full bg-green-500"></span>
{{ .Text | safeHTML }}
</h3>
{{ else }}
<h{{ .Level }} id="{{ .Anchor }}">{{ .Text | safeHTML }}</h{{ .Level }}>
{{ end }}

View file

@ -0,0 +1,37 @@
<!doctype html>
<html lang="{{ .Site.Language.Lang }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} | {{ .Site.Title }}{{ end }}</title>
<meta name="description" content="{{ i18n "siteDescription" }}">
<script>
tailwind = {
config: {
theme: {
extend: {
fontFamily: {
sans: ["Space Grotesk", "ui-sans-serif", "system-ui", "sans-serif"]
}
}
}
}
}
</script>
<script src="https://cdn.tailwindcss.com"></script>
<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=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body class="min-h-screen bg-white text-slate-900 antialiased">
{{ partial "site-header.html" . }}
<main>
{{ block "main" . }}{{ end }}
</main>
{{ partial "site-footer.html" . }}
</body>
</html>

View file

@ -0,0 +1,128 @@
{{ define "main" }}
{{ $p := .Params }}
<!-- HERO -->
<section class="relative overflow-hidden bg-green-950 px-6 py-28 lg:px-8">
<div class="absolute inset-0 opacity-10" style="background-image:radial-gradient(circle at 1px 1px,white 1px,transparent 0);background-size:34px 34px"></div>
<div class="relative mx-auto max-w-6xl">
<span class="inline-flex items-center gap-2 rounded-full border border-green-700 bg-green-900 px-4 py-1.5 text-xs font-bold uppercase tracking-widest text-green-400 mb-7">
<span class="h-1.5 w-1.5 rounded-full bg-green-400"></span>
TheAfriForge
</span>
<h1 class="max-w-4xl text-5xl md:text-7xl font-black leading-tight text-white">{{ .Title }}</h1>
{{ with $p.description }}
<p class="mt-6 max-w-3xl text-lg leading-relaxed text-green-200">{{ . }}</p>
{{ end }}
<div class="mt-10 grid max-w-3xl gap-4 sm:grid-cols-3">
{{ range $p.hero.metrics }}
<div class="rounded-xl border border-green-900 bg-green-900/55 px-4 py-4">
<p class="text-2xl font-black text-green-300">{{ .value }}</p>
<p class="text-xs uppercase tracking-wider text-green-500">{{ .label }}</p>
</div>
{{ end }}
</div>
</div>
</section>
<!-- PRINCIPLES -->
<section class="bg-white px-6 py-20 lg:px-8">
<div class="mx-auto max-w-6xl">
<h2 class="text-3xl md:text-4xl font-black text-slate-900">{{ $p.principles.title }}</h2>
<p class="mt-4 max-w-3xl text-slate-600">{{ $p.principles.intro }}</p>
<div class="mt-10 grid gap-6 md:grid-cols-2 lg:grid-cols-4">
{{ range $p.principles.items }}
<article class="rounded-2xl border border-slate-200 bg-slate-50 p-6">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-green-900 text-green-300">
{{ partial "icon.html" .icon }}
</span>
<h3 class="mt-4 text-lg font-black text-slate-900">{{ .title }}</h3>
<p class="mt-2 text-sm leading-relaxed text-slate-600">{{ .text }}</p>
</article>
{{ end }}
</div>
</div>
</section>
<!-- BODIES -->
<section class="bg-slate-100 px-6 py-20 lg:px-8">
<div class="mx-auto max-w-6xl">
<h2 class="text-3xl md:text-4xl font-black text-slate-900">{{ $p.bodies.title }}</h2>
<p class="mt-4 max-w-3xl text-slate-600">{{ $p.bodies.intro }}</p>
<div class="mt-10 grid gap-6 md:grid-cols-3">
{{ range $p.bodies.items }}
<article class="rounded-2xl border border-slate-200 bg-white p-7 shadow-sm">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-slate-900 text-green-300">
{{ partial "icon.html" .icon }}
</span>
<h3 class="text-xl font-black text-slate-900">{{ .title }}</h3>
</div>
<p class="mt-4 text-slate-600">{{ .text }}</p>
<ul class="mt-5 space-y-2 text-sm text-slate-600">
{{ range .missions }}
<li class="flex items-start gap-2">
<span class="mt-1 inline-flex h-1.5 w-1.5 rounded-full bg-green-600"></span>
<span>{{ . }}</span>
</li>
{{ end }}
</ul>
</article>
{{ end }}
</div>
</div>
</section>
<!-- DECISION LOOP -->
<section class="bg-white px-6 py-20 lg:px-8">
<div class="mx-auto max-w-6xl">
<h2 class="text-3xl md:text-4xl font-black text-slate-900">{{ $p.decisions.title }}</h2>
<p class="mt-4 max-w-3xl text-slate-600">{{ $p.decisions.intro }}</p>
<div class="mt-10 space-y-4">
{{ range $index, $item := $p.decisions.steps }}
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-6">
<div class="flex items-start gap-4">
<span class="inline-flex h-9 min-w-9 items-center justify-center rounded-full bg-green-900 px-3 text-sm font-black text-green-300">{{ add $index 1 }}</span>
<div>
<h3 class="text-lg font-black text-slate-900">{{ .title }}</h3>
<p class="mt-2 text-slate-600">{{ .text }}</p>
</div>
</div>
</div>
{{ end }}
</div>
</div>
</section>
<!-- TRANSPARENCY -->
<section class="bg-green-950 px-6 py-20 lg:px-8">
<div class="mx-auto max-w-6xl">
<h2 class="text-3xl md:text-4xl font-black text-white">{{ $p.transparency.title }}</h2>
<p class="mt-4 max-w-3xl text-green-200">{{ $p.transparency.intro }}</p>
<div class="mt-10 grid gap-5 md:grid-cols-2">
{{ range $p.transparency.items }}
<div class="rounded-2xl border border-green-900 bg-green-900/45 p-6">
<h3 class="text-lg font-black text-white">{{ .title }}</h3>
<p class="mt-2 text-green-200">{{ .text }}</p>
</div>
{{ end }}
</div>
</div>
</section>
<!-- CTA -->
<section class="bg-white px-6 py-20 lg:px-8">
<div class="mx-auto max-w-4xl text-center">
<h2 class="text-4xl font-black text-slate-900">{{ $p.call.title }}</h2>
<p class="mt-5 text-lg leading-relaxed text-slate-600">{{ $p.call.text }}</p>
<a href="{{ $p.call.link | relLangURL }}" class="mt-10 inline-flex items-center justify-center rounded-xl bg-green-600 px-8 py-4 text-sm font-black uppercase tracking-wide text-white transition hover:bg-green-500">
{{ $p.call.cta }}
</a>
</div>
</section>
{{ end }}

312
layouts/_default/join.html Normal file
View file

@ -0,0 +1,312 @@
{{ define "main" }}
{{ $p := .Params }}
{{ $privacyPath := cond (eq .Site.Language.Lang "fr") "politique-confidentialite" "privacy-policy" }}
{{ $formAction := or (getenv "HUGO_MEMBERSHIP_FORM_ACTION") .Site.Params.membershipFormAction $p.form.action }}
{{ $defaultSuccessPath := cond (eq .Site.Language.Lang "fr") "devenir-membre" "become-member" }}
{{ $successPath := or (getenv "HUGO_MEMBERSHIP_FORM_SUCCESS_PATH") .Site.Params.membershipFormSuccessPath $defaultSuccessPath }}
{{ $formbricksHost := getenv "HUGO_FORMBRICKS_API_HOST" }}
{{ $formbricksEnv := getenv "HUGO_FORMBRICKS_ENV_ID" }}
{{ $formbricksEvent := or (getenv "HUGO_FORMBRICKS_EVENT") "membership_form_submitted" }}
{{ $formbricksSdkURL := or (getenv "HUGO_FORMBRICKS_SDK_URL") "https://form.theafriforge.com/js/formbricks.umd.cjs" }}
<section class="relative overflow-hidden bg-green-950 px-6 py-28 lg:px-8">
<div class="absolute inset-0 opacity-10" style="background-image:radial-gradient(circle at 1px 1px,white 1px,transparent 0);background-size:34px 34px"></div>
<div class="relative mx-auto max-w-6xl">
<span class="inline-flex items-center gap-2 rounded-full border border-green-700 bg-green-900 px-4 py-1.5 text-xs font-bold uppercase tracking-widest text-green-400 mb-7">
<span class="h-1.5 w-1.5 rounded-full bg-green-400"></span>
TheAfriForge
</span>
<h1 class="max-w-4xl text-5xl md:text-7xl font-black leading-tight text-white">{{ .Title }}</h1>
{{ with $p.description }}
<p class="mt-6 max-w-3xl text-lg leading-relaxed text-green-200">{{ . }}</p>
{{ end }}
</div>
</section>
<section class="bg-white px-6 py-20 lg:px-8">
<div class="mx-auto max-w-6xl">
<h2 class="text-3xl md:text-4xl font-black text-slate-900">{{ $p.value.title }}</h2>
<p class="mt-4 max-w-3xl text-slate-600">{{ $p.value.intro }}</p>
<div class="mt-10 grid gap-6 md:grid-cols-3">
{{ range $p.value.items }}
<article class="rounded-2xl border border-slate-200 bg-slate-50 p-6">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-green-900 text-green-300">
{{ partial "icon.html" .icon }}
</span>
<h3 class="mt-4 text-lg font-black text-slate-900">{{ .title }}</h3>
<p class="mt-2 text-sm text-slate-600 leading-relaxed">{{ .text }}</p>
</article>
{{ end }}
</div>
</div>
</section>
<section class="bg-slate-100 px-6 py-20 lg:px-8">
<div class="mx-auto max-w-6xl">
<h2 class="text-3xl md:text-4xl font-black text-slate-900">{{ $p.membership.title }}</h2>
<p class="mt-4 max-w-3xl text-slate-600">{{ $p.membership.intro }}</p>
<div class="mt-10 grid gap-6 md:grid-cols-3">
{{ range $p.membership.items }}
<article class="rounded-2xl border border-slate-200 bg-white p-7 shadow-sm">
<h3 class="text-xl font-black text-slate-900">{{ .title }}</h3>
<p class="mt-3 text-slate-600">{{ .text }}</p>
<ul class="mt-5 space-y-2 text-sm text-slate-600">
{{ range .expectations }}
<li class="flex items-start gap-2">
<span class="mt-1 inline-flex h-1.5 w-1.5 rounded-full bg-green-600"></span>
<span>{{ . }}</span>
</li>
{{ end }}
</ul>
</article>
{{ end }}
</div>
</div>
</section>
<section class="bg-white px-6 py-20 lg:px-8">
<div class="mx-auto max-w-6xl">
<h2 class="text-3xl md:text-4xl font-black text-slate-900">{{ $p.process.title }}</h2>
<div class="mt-10 space-y-4">
{{ range $index, $item := $p.process.steps }}
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-6">
<div class="flex items-start gap-4">
<span class="inline-flex h-9 min-w-9 items-center justify-center rounded-full bg-green-900 px-3 text-sm font-black text-green-300">{{ add $index 1 }}</span>
<div>
<h3 class="text-lg font-black text-slate-900">{{ .title }}</h3>
<p class="mt-2 text-slate-600">{{ .text }}</p>
{{ with .delay }}
<p class="mt-2 text-xs font-bold uppercase tracking-wider text-green-700">{{ . }}</p>
{{ end }}
</div>
</div>
</div>
{{ end }}
</div>
</div>
</section>
<section class="bg-green-950 px-6 py-20 lg:px-8">
<div class="mx-auto max-w-5xl">
<h2 class="text-3xl md:text-4xl font-black text-white">{{ $p.form.title }}</h2>
<p class="mt-4 max-w-3xl text-green-200">{{ $p.form.intro }}</p>
<div id="join-success" class="mt-10 hidden rounded-2xl border border-green-500 bg-green-900/60 p-10 text-center">
<svg class="mx-auto mb-5 h-14 w-14 text-green-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></svg>
<h3 class="text-2xl font-black text-white">{{ $p.form.successTitle }}</h3>
<p class="mt-3 text-green-200">{{ $p.form.successText }}</p>
</div>
<p id="join-error" class="mt-4 hidden rounded-xl border border-red-700 bg-red-900/40 px-5 py-3 text-sm text-red-300">{{ $p.form.errorText }}</p>
<form id="join-form" data-action="{{ $formAction }}" data-sending="{{ $p.form.sending }}" data-submit="{{ $p.form.submit }}" data-formbricks-host="{{ $formbricksHost }}" data-formbricks-env="{{ $formbricksEnv }}" data-formbricks-event="{{ $formbricksEvent }}" data-formbricks-sdk="{{ $formbricksSdkURL }}" novalidate class="mt-10 grid gap-5 rounded-2xl border border-green-900 bg-green-900/40 p-8 md:grid-cols-2">
<input type="text" name="_honey" class="hidden" tabindex="-1" autocomplete="off">
<label class="block">
<span class="mb-2 block text-sm font-bold text-green-100">{{ $p.form.labels.organization }}</span>
<input name="organization" class="w-full rounded-lg border border-green-800 bg-green-950/60 px-4 py-3 text-sm text-white placeholder:text-green-400 focus:border-green-500 focus:outline-none" placeholder="{{ $p.form.placeholders.organization }}">
</label>
<label class="block">
<span class="mb-2 block text-sm font-bold text-green-100">{{ $p.form.labels.actorType }}</span>
<select name="actor_type" required class="w-full rounded-lg border border-green-800 bg-green-950/60 px-4 py-3 text-sm text-white focus:border-green-500 focus:outline-none">
<option value="" disabled selected>{{ $p.form.placeholders.actorType }}</option>
{{ range $p.form.actorTypes }}
<option value="{{ . }}">{{ . }}</option>
{{ end }}
</select>
</label>
<label class="block">
<span class="mb-2 block text-sm font-bold text-green-100">{{ $p.form.labels.firstName }}</span>
<input name="first_name" required class="w-full rounded-lg border border-green-800 bg-green-950/60 px-4 py-3 text-sm text-white placeholder:text-green-400 focus:border-green-500 focus:outline-none" placeholder="{{ $p.form.placeholders.firstName }}">
</label>
<label class="block">
<span class="mb-2 block text-sm font-bold text-green-100">{{ $p.form.labels.lastName }}</span>
<input name="last_name" required class="w-full rounded-lg border border-green-800 bg-green-950/60 px-4 py-3 text-sm text-white placeholder:text-green-400 focus:border-green-500 focus:outline-none" placeholder="{{ $p.form.placeholders.lastName }}">
</label>
<label class="block">
<span class="mb-2 block text-sm font-bold text-green-100">{{ $p.form.labels.profession }}</span>
<input name="profession" required class="w-full rounded-lg border border-green-800 bg-green-950/60 px-4 py-3 text-sm text-white placeholder:text-green-400 focus:border-green-500 focus:outline-none" placeholder="{{ $p.form.placeholders.profession }}">
</label>
<label class="block md:col-span-2">
<span class="mb-2 block text-sm font-bold text-green-100">{{ $p.form.labels.country }}</span>
<select name="country" required class="w-full rounded-lg border border-green-800 bg-green-950/60 px-4 py-3 text-sm text-white focus:border-green-500 focus:outline-none">
<option value="" disabled selected>{{ $p.form.placeholders.country }}</option>
{{ range $p.form.countries }}
<option value="{{ . }}">{{ . }}</option>
{{ end }}
</select>
</label>
<label class="block">
<span class="mb-2 block text-sm font-bold text-green-100">{{ $p.form.labels.email }}</span>
<input type="email" name="email" required class="w-full rounded-lg border border-green-800 bg-green-950/60 px-4 py-3 text-sm text-white placeholder:text-green-400 focus:border-green-500 focus:outline-none" placeholder="{{ $p.form.placeholders.email }}">
</label>
<label class="block">
<span class="mb-2 block text-sm font-bold text-green-100">{{ $p.form.labels.phone }}</span>
<input name="phone" required class="w-full rounded-lg border border-green-800 bg-green-950/60 px-4 py-3 text-sm text-white placeholder:text-green-400 focus:border-green-500 focus:outline-none" placeholder="{{ $p.form.placeholders.phone }}">
</label>
<label class="block md:col-span-2">
<span class="mb-2 block text-sm font-bold text-green-100">{{ $p.form.labels.contribution }}</span>
<textarea name="contribution" rows="4" required class="w-full rounded-lg border border-green-800 bg-green-950/60 px-4 py-3 text-sm text-white placeholder:text-green-400 focus:border-green-500 focus:outline-none" placeholder="{{ $p.form.placeholders.contribution }}"></textarea>
</label>
<label class="flex items-start gap-3 md:col-span-2">
<input type="checkbox" name="consent" required class="mt-1 h-4 w-4 rounded border-green-700 bg-green-950 text-green-500">
<span class="text-sm text-green-200">{{ $p.form.consentText }} <a href="{{ $privacyPath | relLangURL }}" class="font-bold text-green-300 hover:text-green-200">{{ $p.form.privacyLinkLabel }}</a>.</span>
</label>
<div class="md:col-span-2">
<button id="join-submit" type="submit" class="inline-flex items-center justify-center rounded-xl bg-green-500 px-8 py-4 text-sm font-black uppercase tracking-wide text-white transition hover:bg-green-400 disabled:opacity-60 disabled:cursor-not-allowed">
{{ $p.form.submit }}
</button>
</div>
</form>
</div>
</section>
<script>
(function () {
var form = document.getElementById('join-form');
var btn = document.getElementById('join-submit');
var successEl = document.getElementById('join-success');
var errorEl = document.getElementById('join-error');
if (!form) return;
function showSuccess() {
form.classList.add('hidden');
successEl.classList.remove('hidden');
successEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
function showError() {
btn.disabled = false;
btn.textContent = form.dataset.submit;
errorEl.classList.remove('hidden');
}
function toFields(raw) {
var out = {};
raw.forEach(function (val, key) {
if (key !== '_honey') out[key] = val;
});
return out;
}
function initFormbricks(host, envId, sdkUrl) {
return new Promise(function (resolve, reject) {
if (!host || !envId) {
resolve(false);
return;
}
function boot() {
if (!window.formbricks || typeof window.formbricks.init !== 'function') {
reject(new Error('formbricks not ready'));
return;
}
window.formbricks.init({ environmentId: envId, apiHost: host });
resolve(true);
}
if (window.formbricks && typeof window.formbricks.init === 'function') {
boot();
return;
}
var src = (sdkUrl || '').trim();
if (!src && host) {
src = host.replace(/\/$/, '') + '/js/formbricks.umd.cjs';
}
if (!src) {
reject(new Error('sdk url missing'));
return;
}
var existing = document.querySelector('script[data-formbricks-sdk="1"]');
if (existing) {
existing.addEventListener('load', boot, { once: true });
existing.addEventListener('error', function () { reject(new Error('sdk load failed')); }, { once: true });
return;
}
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = src;
script.dataset.formbricksSdk = '1';
script.addEventListener('load', boot, { once: true });
script.addEventListener('error', function () { reject(new Error('sdk load failed')); }, { once: true });
document.head.appendChild(script);
});
}
form.addEventListener('submit', function (e) {
e.preventDefault();
if (form.querySelector('[name="_honey"]').value) return;
var action = form.dataset.action;
if (!action) { errorEl.classList.remove('hidden'); return; }
btn.disabled = true;
btn.textContent = form.dataset.sending;
errorEl.classList.add('hidden');
var raw = new FormData(form);
var fields = toFields(raw);
var fbHost = (form.dataset.formbricksHost || '').trim();
var fbEnv = (form.dataset.formbricksEnv || '').trim();
var fbEvent = (form.dataset.formbricksEvent || 'membership_form_submitted').trim();
var fbSdk = (form.dataset.formbricksSdk || '').trim();
var isDebug = window.location.search.indexOf('formbricksDebug=true') !== -1;
if (isDebug) {
console.info('[membership] Formbricks config', {
apiHost: fbHost,
environmentId: fbEnv,
event: fbEvent,
sdkUrl: fbSdk
});
}
console.log('[membership] Submit with env=' + fbEnv + ' event=' + fbEvent + ' host=' + fbHost);
if (fbHost && fbEnv) {
initFormbricks(fbHost, fbEnv, fbSdk)
.then(function (ready) {
if (!ready || !window.formbricks || typeof window.formbricks.track !== 'function') {
throw new Error('formbricks track unavailable');
}
window.formbricks.track(fbEvent, fields);
console.log('[membership] Event tracked: ' + fbEvent);
showSuccess();
})
.catch(function (err) {
console.warn('[membership] Formbricks error (non-blocking):', err);
showSuccess();
});
return;
}
var payload = JSON.stringify({ data: fields });
fetch(action, {
method: 'POST',
body: payload,
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }
})
.then(function (res) {
if (!res.ok) throw new Error('server error');
showSuccess();
})
.catch(showError);
});
})();
</script>
{{ end }}

View file

@ -0,0 +1,191 @@
{{ define "main" }}
{{ $p := .Params }}
{{ $joinPath := cond (eq .Site.Language.Lang "fr") "devenir-membre" "become-member" }}
<!-- ─── HERO ─────────────────────────────────────────────────── -->
<section class="relative overflow-hidden bg-green-950 px-6 py-28 lg:px-8">
<div class="absolute inset-0 opacity-5" style="background-image:radial-gradient(circle at 1px 1px,white 1px,transparent 0);background-size:40px 40px"></div>
<div class="relative mx-auto max-w-5xl">
<span class="inline-flex items-center gap-2 rounded-full border border-green-700 bg-green-900 px-4 py-1.5 text-xs font-bold uppercase tracking-widest text-green-400 mb-8">
<span class="h-1.5 w-1.5 rounded-full bg-green-400"></span>
TheAfriForge
</span>
<h1 class="text-5xl md:text-7xl font-black leading-none text-white mb-6">{{ .Title }}</h1>
{{ with .Description }}
<p class="text-lg text-green-200 max-w-2xl leading-relaxed">{{ . }}</p>
{{ end }}
</div>
</section>
<!-- ─── PRÉAMBULE ─────────────────────────────────────────────── -->
<section class="bg-white px-6 py-20 lg:px-8">
<div class="mx-auto max-w-5xl">
<blockquote class="relative mb-12 rounded-2xl bg-green-950 px-10 py-10">
<svg class="absolute top-6 left-6 h-8 w-8 text-green-700" fill="currentColor" viewBox="0 0 24 24"><path d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.638-3.995 5.849h4v10h-9.983zm-14.017 0v-7.391c0-5.704 3.748-9.57 9-10.609l.996 2.151c-2.433.917-3.996 3.638-3.996 5.849h3.983v10h-9.983z"/></svg>
<p class="text-2xl md:text-3xl font-black leading-snug text-white pl-6">{{ $p.preamble.quote }}</p>
</blockquote>
<div class="grid md:grid-cols-2 gap-8 text-slate-600 text-lg leading-relaxed">
<p>{{ $p.preamble.text }}</p>
<div class="rounded-xl border-l-4 border-green-500 bg-green-50 p-6">
<p class="font-semibold text-green-900">{{ partial "brand-highlight.html" $p.preamble.conviction }}</p>
</div>
</div>
</div>
</section>
<!-- ─── CONVICTION ───────────────────────────────────────────── -->
<section class="bg-slate-50 px-6 py-20 lg:px-8">
<div class="mx-auto max-w-5xl">
<div class="mb-12">
<h2 class="text-3xl font-black text-slate-900 mb-3">{{ $p.conviction.title }}</h2>
<p class="text-slate-500 max-w-xl">{{ $p.conviction.intro }}</p>
</div>
<div class="grid sm:grid-cols-2 gap-4">
{{ range $p.conviction.items }}
<div class="flex items-start gap-4 rounded-xl bg-white border border-slate-100 p-6 shadow-sm hover:shadow-md transition">
<div class="flex-shrink-0 flex h-11 w-11 items-center justify-center rounded-lg bg-green-100 text-green-700">
{{ partial "icon.html" .icon }}
</div>
<p class="text-slate-700 font-medium leading-relaxed">{{ .text }}</p>
</div>
{{ end }}
</div>
</div>
</section>
<!-- ─── PILIERS ───────────────────────────────────────────────── -->
<section class="bg-white px-6 py-20 lg:px-8">
<div class="mx-auto max-w-5xl">
<div class="mb-12">
<h2 class="text-3xl font-black text-slate-900 mb-3">{{ $p.pillars.title }}</h2>
<p class="text-slate-500 max-w-xl">{{ $p.pillars.intro }}</p>
</div>
<div class="grid md:grid-cols-3 gap-6">
{{ range $p.pillars.items }}
<div class="group relative rounded-2xl border border-slate-200 bg-white p-8 shadow-sm hover:border-green-400 hover:shadow-lg transition">
<div class="mb-6 flex items-center justify-between">
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-green-950 text-green-400">
{{ partial "icon.html" .icon }}
</div>
<span class="text-4xl font-black text-slate-100 group-hover:text-green-100 transition">{{ .number }}</span>
</div>
<h3 class="text-xl font-black text-slate-900 mb-3">{{ .title }}</h3>
<p class="text-slate-600 leading-relaxed text-sm">{{ .text }}</p>
</div>
{{ end }}
</div>
</div>
</section>
<!-- ─── PRINCIPES ─────────────────────────────────────────────── -->
<section class="bg-green-950 px-6 py-20 lg:px-8">
<div class="mx-auto max-w-5xl">
<div class="mb-12">
<h2 class="text-3xl font-black text-white mb-3">{{ $p.principles.title }}</h2>
</div>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
{{ range $p.principles.items }}
<div class="rounded-xl border border-green-800 bg-green-900 p-6 hover:border-green-600 hover:bg-green-800 transition">
<div class="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-green-700 text-green-200">
{{ partial "icon.html" .icon }}
</div>
<h3 class="text-base font-black text-white mb-2">{{ .title }}</h3>
<p class="text-green-300 text-sm leading-relaxed">{{ .text }}</p>
</div>
{{ end }}
</div>
</div>
</section>
<!-- ─── ENGAGEMENTS ───────────────────────────────────────────── -->
<section class="bg-white px-6 py-20 lg:px-8">
<div class="mx-auto max-w-5xl">
<div class="grid md:grid-cols-2 gap-12 items-start">
<div>
<h2 class="text-3xl font-black text-slate-900 mb-3">{{ $p.commitments.title }}</h2>
<p class="text-slate-500 mb-8">{{ $p.commitments.intro }}</p>
<ul class="space-y-4">
{{ range $p.commitments.items }}
<li class="flex items-start gap-3">
<span class="mt-1 flex-shrink-0 flex h-5 w-5 items-center justify-center rounded-full bg-green-500">
<svg class="h-3 w-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path></svg>
</span>
<span class="text-slate-700">{{ . }}</span>
</li>
{{ end }}
</ul>
</div>
<div class="sticky top-24">
<div class="rounded-2xl bg-green-950 p-10 text-center">
<svg class="mx-auto mb-4 h-10 w-10 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>
<p class="text-xl font-black text-white leading-snug">{{ $p.commitments.quote }}</p>
</div>
</div>
</div>
</div>
</section>
<!-- ─── VISION ────────────────────────────────────────────────── -->
<section class="bg-slate-50 px-6 py-20 lg:px-8">
<div class="mx-auto max-w-5xl">
<div class="mb-4">
<h2 class="text-3xl font-black text-slate-900 mb-3">{{ $p.vision.title }}</h2>
<p class="text-slate-500 max-w-2xl mb-2">{{ partial "brand-highlight.html" $p.vision.intro }}</p>
<p class="font-bold text-green-800">→ {{ $p.vision.objective }}</p>
</div>
<div class="mt-10 grid sm:grid-cols-2 gap-4">
{{ range $p.vision.items }}
<div class="flex gap-5 rounded-xl bg-white border border-slate-100 p-6 shadow-sm hover:shadow-md transition">
<div class="flex-shrink-0 flex h-12 w-12 items-center justify-center rounded-xl bg-slate-900 text-green-400">
{{ partial "icon.html" .icon }}
</div>
<div>
<h3 class="font-black text-slate-900 mb-1">{{ .title }}</h3>
<p class="text-slate-500 text-sm leading-relaxed">{{ .text }}</p>
</div>
</div>
{{ end }}
</div>
</div>
</section>
<!-- ─── GOUVERNANCE ───────────────────────────────────────────── -->
<section class="bg-white px-6 py-20 lg:px-8">
<div class="mx-auto max-w-5xl">
<div class="grid md:grid-cols-2 gap-12 items-center">
<div>
<h2 class="text-3xl font-black text-slate-900 mb-8">{{ $p.governance.title }}</h2>
<ul class="space-y-4">
{{ range $p.governance.items }}
<li class="flex items-start gap-3">
<span class="mt-0.5 flex-shrink-0 h-5 w-5 text-green-600">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</span>
<span class="text-slate-700">{{ . }}</span>
</li>
{{ end }}
</ul>
</div>
<div class="rounded-2xl border-l-4 border-green-500 bg-green-50 p-8">
<p class="text-xl font-black text-green-900 leading-snug italic">« {{ $p.governance.quote }} »</p>
</div>
</div>
</div>
</section>
<!-- ─── COALITION ─────────────────────────────────────────────── -->
<section class="bg-green-950 px-6 py-24 lg:px-8">
<div class="mx-auto max-w-3xl text-center">
<h2 class="text-4xl md:text-5xl font-black text-white mb-6">{{ $p.coalition.title }}</h2>
<p class="text-green-200 text-lg leading-relaxed mb-6">{{ $p.coalition.text }}</p>
<p class="text-white font-bold text-xl mb-10">{{ partial "brand-highlight.html" $p.coalition.statement }}</p>
<a href="{{ $joinPath | relLangURL }}" class="inline-flex items-center gap-2 rounded-xl bg-green-500 px-8 py-4 text-base font-black text-white hover:bg-green-400 transition">
{{ $p.coalition.cta }}
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path></svg>
</a>
<p class="mt-10 text-sm text-green-500 italic">{{ $p.coalition.closing }}</p>
</div>
</section>
{{ end }}

View file

@ -0,0 +1,122 @@
{{ define "main" }}
{{ $p := .Params }}
<!-- HERO -->
<section class="relative overflow-hidden bg-green-950 px-6 py-28 lg:px-8">
<div class="absolute inset-0 opacity-20" style="background:radial-gradient(circle at 20% 20%,#166534 0%,transparent 40%),radial-gradient(circle at 80% 0%,#15803d 0%,transparent 35%)"></div>
<div class="relative mx-auto max-w-6xl">
<span class="inline-flex items-center gap-2 rounded-full border border-green-800 bg-green-900/40 px-4 py-1.5 text-xs font-bold uppercase tracking-widest text-green-300 mb-7">
<span class="h-1.5 w-1.5 rounded-full bg-green-400"></span>
TheAfriForge
</span>
<h1 class="max-w-4xl text-5xl md:text-7xl font-black leading-tight text-white">{{ .Title }}</h1>
{{ with $p.description }}
<p class="mt-6 max-w-3xl text-lg leading-relaxed text-green-200">{{ . }}</p>
{{ end }}
<div class="mt-10 grid gap-4 sm:grid-cols-3 max-w-3xl">
{{ range $p.hero.metrics }}
<div class="rounded-xl border border-green-900 bg-green-900/60 px-4 py-4">
<p class="text-2xl font-black text-green-300">{{ .value }}</p>
<p class="text-xs uppercase tracking-wider text-green-500">{{ .label }}</p>
</div>
{{ end }}
</div>
</div>
</section>
<!-- PROJECTS -->
<section class="bg-white px-6 py-20 lg:px-8">
<div class="mx-auto max-w-6xl">
<div class="mb-12 flex flex-wrap items-end justify-between gap-4">
<div>
<p class="text-xs font-bold uppercase tracking-[0.2em] text-green-700">{{ $p.portfolio.tag }}</p>
<h2 class="mt-3 text-3xl md:text-4xl font-black text-slate-900">{{ $p.portfolio.title }}</h2>
</div>
<p class="max-w-2xl text-slate-600">{{ $p.portfolio.intro }}</p>
</div>
<div class="grid gap-6 md:grid-cols-3">
{{ range $p.portfolio.items }}
{{ $status := lower .status }}
{{ $statusClass := "bg-slate-100 text-slate-700" }}
{{ if eq $status "production" }}
{{ $statusClass = "bg-green-100 text-green-800" }}
{{ else if eq $status "incubation" }}
{{ $statusClass = "bg-amber-100 text-amber-800" }}
{{ else if eq $status "research" }}
{{ $statusClass = "bg-sky-100 text-sky-800" }}
{{ else if eq $status "recherche" }}
{{ $statusClass = "bg-sky-100 text-sky-800" }}
{{ end }}
<article class="group rounded-2xl border border-slate-200 bg-slate-50 p-7 shadow-sm transition hover:-translate-y-1 hover:shadow-lg">
<div class="mb-5 flex items-center justify-between gap-3">
<span class="inline-flex items-center rounded-full px-3 py-1 text-xs font-bold uppercase tracking-wide {{ $statusClass }}">{{ .status }}</span>
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-slate-900 text-green-300">
{{ partial "icon.html" .icon }}
</span>
</div>
<h3 class="text-2xl font-black text-slate-900">{{ .name }}</h3>
<p class="mt-4 text-slate-600 leading-relaxed">{{ .text }}</p>
<p class="mt-5 text-sm text-slate-500"><span class="font-bold text-slate-700">{{ $p.portfolio.impactLabel }}:</span> {{ .impact }}</p>
<p class="mt-2 text-sm text-slate-500"><span class="font-bold text-slate-700">{{ $p.portfolio.stackLabel }}:</span> {{ .stack }}</p>
<a href="{{ .link }}" class="mt-7 inline-flex items-center gap-2 text-sm font-bold text-green-800 transition group-hover:text-green-600">
{{ .cta }}
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path></svg>
</a>
</article>
{{ end }}
</div>
</div>
</section>
<!-- ROADMAP -->
<section class="bg-slate-100 px-6 py-20 lg:px-8">
<div class="mx-auto max-w-6xl">
<h2 class="text-3xl md:text-4xl font-black text-slate-900">{{ $p.roadmap.title }}</h2>
<p class="mt-4 max-w-3xl text-slate-600">{{ $p.roadmap.intro }}</p>
<div class="mt-10 grid gap-5 md:grid-cols-3">
{{ range $p.roadmap.items }}
<div class="rounded-2xl border border-slate-200 bg-white p-6">
<p class="text-xs font-black uppercase tracking-[0.2em] text-green-700">{{ .period }}</p>
<h3 class="mt-3 text-xl font-black text-slate-900">{{ .title }}</h3>
<p class="mt-3 text-slate-600">{{ .text }}</p>
</div>
{{ end }}
</div>
</div>
</section>
<!-- SELECTION CRITERIA -->
<section class="bg-white px-6 py-20 lg:px-8">
<div class="mx-auto max-w-6xl">
<h2 class="text-3xl md:text-4xl font-black text-slate-900">{{ $p.selection.title }}</h2>
<p class="mt-4 max-w-3xl text-slate-600">{{ $p.selection.intro }}</p>
<div class="mt-10 grid gap-6 md:grid-cols-2 lg:grid-cols-4">
{{ range $p.selection.items }}
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-6">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-green-900 text-green-300">
{{ partial "icon.html" .icon }}
</span>
<h3 class="mt-4 text-lg font-black text-slate-900">{{ .title }}</h3>
<p class="mt-2 text-sm leading-relaxed text-slate-600">{{ .text }}</p>
</div>
{{ end }}
</div>
</div>
</section>
<!-- CTA -->
<section class="bg-green-950 px-6 py-20 lg:px-8">
<div class="mx-auto max-w-4xl text-center">
<h2 class="text-4xl font-black text-white">{{ $p.call.title }}</h2>
<p class="mt-5 text-lg leading-relaxed text-green-200">{{ $p.call.text }}</p>
<a href="{{ $p.call.link | relLangURL }}" class="mt-10 inline-flex items-center justify-center rounded-xl bg-green-500 px-8 py-4 text-sm font-black uppercase tracking-wide text-white transition hover:bg-green-400">
{{ $p.call.cta }}
</a>
</div>
</section>
{{ end }}

View file

@ -0,0 +1,31 @@
{{ define "main" }}
<!-- ─── HERO ───────────────────────────────────── -->
<section class="bg-green-950 px-6 py-24 lg:px-8">
<div class="mx-auto max-w-4xl">
<p class="text-xs font-bold uppercase tracking-widest text-green-400 mb-5">TheAfriForge — {{ .Site.Language.LanguageName }}</p>
<h1 class="text-5xl md:text-6xl font-black leading-tight text-white mb-6">{{ .Title }}</h1>
{{ with .Description }}
<p class="text-lg text-green-200 max-w-2xl leading-relaxed">{{ . }}</p>
{{ end }}
</div>
</section>
<!-- ─── CONTENT ─────────────────────────────────── -->
<section class="bg-white px-6 py-16 lg:px-8">
<div class="mx-auto max-w-4xl">
<div class="prose prose-slate prose-lg max-w-none
prose-p:text-slate-600 prose-p:leading-relaxed
prose-li:text-slate-600 prose-li:leading-relaxed
prose-strong:text-slate-900 prose-strong:font-bold
prose-a:text-green-700 prose-a:font-medium
prose-blockquote:not-italic prose-blockquote:border-l-4 prose-blockquote:border-green-500
prose-blockquote:bg-green-50 prose-blockquote:rounded-r-xl prose-blockquote:px-6 prose-blockquote:py-4
prose-blockquote:text-green-900 prose-blockquote:font-medium
prose-hr:border-slate-200">
{{ partial "brand-highlight.html" (printf "%s" .Content) }}
</div>
</div>
</section>
{{ end }}