feat: blog modal with animation, replace trial form with product/location/webhook widget

This commit is contained in:
Oliver
2026-03-27 16:56:52 -03:00
parent 5bd3c7b398
commit d111b55de0
3 changed files with 188 additions and 68 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+157 -67
View File
@@ -140,28 +140,46 @@
<li class="flex gap-3"><span class="text-brand-emerald" aria-hidden="true"></span><span data-i18n="trial-bullet3"></span></li>
</ul>
</div>
<form class="bg-white text-brand-ink rounded-3xl p-8 shadow-brand space-y-5" data-event="form:trial">
<label class="block text-sm font-medium">
<span data-i18n="trial-label-email"></span>
<input type="email" required class="mt-2 w-full rounded-2xl border border-brand-smoke px-4 py-3 focus:outline-none focus:ring-2 focus:ring-brand-accent" data-i18n-placeholder="trial-placeholder-email" />
</label>
<label class="block text-sm font-medium">
<span data-i18n="trial-label-priority"></span>
<select class="mt-2 w-full rounded-2xl border border-brand-smoke px-4 py-3 focus:outline-none focus:ring-2 focus:ring-brand-accent">
<option data-i18n-option="priority-1"></option>
<option data-i18n-option="priority-2"></option>
<option data-i18n-option="priority-3"></option>
<option data-i18n-option="priority-4"></option>
</select>
</label>
<label class="block text-sm font-medium">
<span data-i18n="trial-label-region"></span>
<input type="text" class="mt-2 w-full rounded-2xl border border-brand-smoke px-4 py-3 focus:outline-none focus:ring-2 focus:ring-brand-accent" data-i18n-placeholder="trial-placeholder-region" />
</label>
<p class="text-xs text-brand-ink/70" data-i18n="trial-microcopy"></p>
<button type="submit" class="w-full py-3 rounded-2xl bg-brand-accent text-brand-ink font-semibold" data-i18n="cta-trial"></button>
<p class="text-xs text-brand-ink/60 text-center" data-i18n="trial-help"></p>
</form>
<div id="trial-form-wrapper">
<form id="trial-form" class="bg-white text-brand-ink rounded-3xl p-8 shadow-brand space-y-5">
<fieldset class="space-y-2">
<legend class="block text-sm font-medium mb-2" data-i18n="trial-label-product">Product</legend>
<div class="flex gap-3">
<label class="flex items-center gap-2 flex-1 border border-brand-smoke rounded-2xl px-4 py-3 cursor-pointer hover:border-brand-accent transition">
<input type="radio" name="product" value="odoo_19" checked class="accent-brand-accent">
<span class="text-sm font-medium">ODOO</span>
</label>
<label class="flex items-center gap-2 flex-1 border border-brand-smoke rounded-2xl px-4 py-3 cursor-pointer hover:border-brand-accent transition">
<input type="radio" name="product" value="N8N" class="accent-brand-accent">
<span class="text-sm font-medium">N8N</span>
</label>
</div>
</fieldset>
<label class="block text-sm font-medium">
<span data-i18n="trial-label-location">Server region</span>
<select name="location" required class="mt-2 w-full rounded-2xl border border-brand-smoke px-4 py-3 focus:outline-none focus:ring-2 focus:ring-brand-accent bg-white">
<option value="" disabled selected data-i18n-option="trial-location-placeholder">Select a region</option>
<option value="manchester">🇬🇧 UK, Manchester</option>
<option value="boston">🇺🇸 US, Boston</option>
<option value="mumbai">🇮🇳 IN, Mumbai</option>
<option value="saopaulo">🇧🇷 BR, São Paulo</option>
<option value="Meppel">🇳🇱 NL, Meppel</option>
</select>
</label>
<label class="block text-sm font-medium">
<span data-i18n="trial-label-email"></span>
<input type="email" name="email" required class="mt-2 w-full rounded-2xl border border-brand-smoke px-4 py-3 focus:outline-none focus:ring-2 focus:ring-brand-accent" data-i18n-placeholder="trial-placeholder-email" />
</label>
<input type="hidden" id="trial-affiliate" name="affiliate_code" value="NGO">
<p class="text-xs text-brand-ink/70" data-i18n="trial-microcopy"></p>
<button type="submit" class="w-full py-3 rounded-2xl bg-brand-accent hover:bg-brand-sunrise text-brand-ink font-semibold transition plausible-event-name=Trial" data-i18n="cta-trial"></button>
<p class="text-xs text-brand-ink/60 text-center" data-i18n="trial-help"></p>
</form>
<div id="trial-status" class="hidden bg-white text-brand-ink rounded-3xl p-8 shadow-brand text-center space-y-4">
<p class="text-brand-emerald text-4xl" aria-hidden="true"></p>
<p id="trial-status-msg" class="text-sm text-brand-ink/80"></p>
</div>
</div>
</div>
</section>
@@ -197,6 +215,27 @@
</div>
</footer>
<!-- Blog post modal -->
<div id="blog-modal" role="dialog" aria-modal="true" aria-labelledby="blog-modal-title" style="display:none" class="fixed inset-0 z-50 items-center justify-center p-4">
<div id="blog-modal-backdrop" class="absolute inset-0 bg-brand-ink/80 backdrop-blur cursor-pointer"></div>
<article id="blog-modal-box" class="relative bg-white rounded-3xl shadow-brand w-full max-w-2xl max-h-[90vh] overflow-y-auto flex flex-col">
<div class="sticky top-0 bg-white rounded-t-3xl px-8 pt-6 pb-4 flex items-start justify-between gap-4 border-b border-brand-smoke z-10">
<div class="flex-1 min-w-0">
<p id="blog-modal-area" class="text-xs uppercase tracking-widest text-brand-primary mb-1"></p>
<h2 id="blog-modal-title" class="text-2xl font-semibold leading-tight text-brand-ink pr-4"></h2>
</div>
<button id="blog-modal-close-x" type="button" aria-label="Close" class="flex-shrink-0 w-12 h-12 rounded-full bg-brand-smoke flex items-center justify-center text-2xl font-bold text-brand-ink hover:bg-brand-accent hover:text-white transition"></button>
</div>
<div class="px-8 py-6 flex-1">
<time id="blog-modal-date" class="text-xs text-brand-ink/60 block mb-4"></time>
<div id="blog-modal-content" class="text-sm text-brand-ink/80 space-y-3"></div>
</div>
<div class="px-8 pb-8 flex justify-center">
<button id="blog-modal-close-bottom" type="button" class="px-10 py-3 rounded-full bg-brand-ink text-white font-semibold hover:bg-brand-primary transition">Close</button>
</div>
</article>
</div>
<script>
const translations = {
en: {
@@ -235,13 +274,10 @@
'trial-bullet3': 'Guided checklists for go-live within 30 days',
'trial-label-email': 'Work email',
'trial-placeholder-email': 'impact@yourngo.org',
'trial-label-priority': 'Primary priority',
'priority-1': 'Grant compliance reporting',
'priority-2': 'Volunteer management',
'priority-3': 'Donor CRM & fundraising',
'priority-4': 'Finance & procurement',
'trial-label-region': 'Country / region',
'trial-placeholder-region': 'e.g., Paraguay, LATAM',
'trial-label-product': 'Product',
'trial-label-location': 'Server region',
'trial-location-placeholder': 'Select a region',
'modal-close': 'Close',
'trial-microcopy': 'No credit card required. EU hosting & GDPR-ready.',
'trial-help': 'Need help? Use the Schedule a Meeting button in the header to book a guided call.',
'meeting-tagline': 'Have a meeting',
@@ -295,13 +331,10 @@
'trial-bullet3': 'Checklists guiados para salir a producción en 30 días',
'trial-label-email': 'Correo laboral',
'trial-placeholder-email': 'impacto@tuong.org',
'trial-label-priority': 'Prioridad principal',
'priority-1': 'Reportes de subvenciones',
'priority-2': 'Gestión de voluntarios',
'priority-3': 'CRM y recaudación',
'priority-4': 'Finanzas y compras',
'trial-label-region': 'País / región',
'trial-placeholder-region': 'ej. Paraguay, LATAM',
'trial-label-product': 'Producto',
'trial-label-location': 'Región de servidor',
'trial-location-placeholder': 'Selecciona una región',
'modal-close': 'Cerrar',
'trial-microcopy': 'Sin tarjeta. Hosting en la UE y cumplimiento GDPR.',
'trial-help': '¿Dudas? Usa el botón “Agendar una reunión” en el encabezado para reservar una llamada guiada.',
'meeting-tagline': 'Agenda una reunión',
@@ -355,13 +388,10 @@
'trial-bullet3': 'Geführte Checklisten für Go-Live in 30 Tagen',
'trial-label-email': 'Arbeits-E-Mail',
'trial-placeholder-email': 'impact@deinengo.org',
'trial-label-priority': 'Höchste Priorität',
'priority-1': 'Förder-Reporting',
'priority-2': 'Ehrenamt-Management',
'priority-3': 'Spender-CRM & Fundraising',
'priority-4': 'Finanzen & Beschaffung',
'trial-label-region': 'Land / Region',
'trial-placeholder-region': 'z.B. Paraguay, LATAM',
'trial-label-product': 'Produkt',
'trial-label-location': 'Serverstandort',
'trial-location-placeholder': 'Region auswählen',
'modal-close': 'Schließen',
'trial-microcopy': 'Keine Kreditkarte. EU-Hosting & DSGVO-konform.',
'trial-help': 'Brauchen Sie Hilfe? Nutzen Sie den Button „Meeting planen“ im Header für einen geführten Call.',
'meeting-tagline': 'Meeting vereinbaren',
@@ -459,9 +489,7 @@
area.textContent = post.area || '';
const dateEl = document.createElement('time');
dateEl.className = 'text-xs text-brand-ink/60';
if (post.date) {
dateEl.dateTime = post.date;
}
if (post.date) dateEl.dateTime = post.date;
dateEl.textContent = formatBlogDate(post.date);
meta.append(area, dateEl);
card.appendChild(meta);
@@ -472,40 +500,57 @@
card.appendChild(title);
const teaser = document.createElement('div');
teaser.className = 'text-sm text-brand-ink/80 space-y-3 mb-4';
teaser.className = 'text-sm text-brand-ink/80 space-y-3 mb-4 flex-1';
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.className = 'text-sm font-semibold text-brand-accent underline-offset-4 hover:underline self-start mt-auto';
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');
}
});
btn.addEventListener('click', function () { openBlogModal(post); });
card.appendChild(btn);
}
return card;
}
function openBlogModal(post) {
var modal = document.getElementById('blog-modal');
var modalBox = document.getElementById('blog-modal-box');
var backdrop = document.getElementById('blog-modal-backdrop');
var locale = document.documentElement.lang || 'en';
document.getElementById('blog-modal-area').textContent = post.area || '';
var modalDateEl = document.getElementById('blog-modal-date');
modalDateEl.textContent = formatBlogDate(post.date);
if (post.date) modalDateEl.dateTime = post.date;
document.getElementById('blog-modal-title').textContent = post.title || '';
document.getElementById('blog-modal-content').innerHTML = post.content || post.teaser || '';
document.getElementById('blog-modal-close-bottom').textContent = getTranslation(locale, 'modal-close', 'Close');
modalBox.classList.remove('modal-animate-box');
backdrop.classList.remove('modal-animate-backdrop');
modal.style.display = 'flex';
document.body.style.overflow = 'hidden';
requestAnimationFrame(function () {
modalBox.classList.add('modal-animate-box');
backdrop.classList.add('modal-animate-backdrop');
});
}
function closeBlogModal() {
var modal = document.getElementById('blog-modal');
modal.style.display = 'none';
document.body.style.overflow = '';
document.getElementById('blog-modal-box').classList.remove('modal-animate-box');
document.getElementById('blog-modal-backdrop').classList.remove('modal-animate-backdrop');
}
function formatBlogDate(dateString) {
if (!dateString) return '';
try {
@@ -563,6 +608,51 @@
setLanguage('en');
document.getElementById('year').textContent = new Date().getFullYear();
// Blog modal event listeners
var blogModal = document.getElementById('blog-modal');
if (blogModal) {
document.getElementById('blog-modal-close-x').addEventListener('click', closeBlogModal);
document.getElementById('blog-modal-close-bottom').addEventListener('click', closeBlogModal);
document.getElementById('blog-modal-backdrop').addEventListener('click', closeBlogModal);
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && blogModal.style.display !== 'none') closeBlogModal();
});
}
// Trial form — affiliate code + webhook submission
var affiliateCode = (new URLSearchParams(window.location.search)).get('affiliate') || 'NGO';
var trialAffiliateInput = document.getElementById('trial-affiliate');
if (trialAffiliateInput) trialAffiliateInput.value = affiliateCode;
var trialForm = document.getElementById('trial-form');
var trialStatusEl = document.getElementById('trial-status');
var trialStatusMsg = document.getElementById('trial-status-msg');
if (trialForm) {
trialForm.addEventListener('submit', async function (e) {
e.preventDefault();
var btn = trialForm.querySelector('button[type="submit"]');
var origText = btn.textContent;
btn.disabled = true;
btn.textContent = '…';
try {
var res = await fetch('https://002-001-5dd6e535-4d1c-46bc-9bd9-42ad4bc5f082.odoo4projects.com/webhook/c25169c6-4234-4b47-8e74-612b9539da0a', {
method: 'POST',
body: new FormData(trialForm)
});
var text = await res.text();
trialForm.style.display = 'none';
trialStatusEl.classList.remove('hidden');
trialStatusMsg.textContent = text;
} catch (err) {
btn.disabled = false;
btn.textContent = origText;
trialStatusEl.classList.remove('hidden');
trialStatusMsg.textContent = '❌ Submission failed. Please try again.';
}
});
}
</script>
</body>
</html>
+30
View File
@@ -15,3 +15,33 @@ body {
color: #201824;
background-color: #ffffff;
}
/* ── Blog post modal animations ───────────────────────── */
@keyframes modal-slide-in {
from {
opacity: 0;
transform: scale(0.95) translateY(20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes modal-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.modal-animate-box {
animation: modal-slide-in 0.3s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.modal-animate-backdrop {
animation: modal-fade-in 0.2s ease both;
}