- 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.
312 lines
14 KiB
HTML
312 lines
14 KiB
HTML
{{ 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 }}
|