feat: add newsletter signup with email field and Umami tracking

- Add email input field to newsletter signup form
- Add disclaimer about max frequency and unsubscribe option
- Add newsletter translations for all 6 languages (en, de, pl, es, ar, uk)
- Update hero highlight to "Crispy Code"
- Update CTA button to "Let's get crispy!"
- Add Umami tracking for newsletter submissions
- Style newsletter form without white background
This commit is contained in:
2026-01-16 11:06:42 +01:00
parent d167541b19
commit 1d59d18870
4 changed files with 161 additions and 19 deletions

View File

@@ -2560,6 +2560,19 @@ function init() {
track("support_click", { location: "landing" });
}
});
// Newsletter form submission
const newsletterForm = document.getElementById("newsletter-form");
const newsletterThanks = document.getElementById("newsletter-thanks");
newsletterForm?.addEventListener("submit", (e) => {
e.preventDefault();
const email = document.getElementById("newsletter-email")?.value;
if (email) {
track("newsletter_signup", { email });
newsletterForm.classList.add("hidden");
newsletterThanks?.classList.remove("hidden");
}
});
}
// Start the application

View File

@@ -118,7 +118,7 @@ const translations = {
// Landing page
landingHeroTitle: "Learn Web Development",
landingHeroHighlight: "By Writing Real Code",
landingHeroHighlight: "Crispy Code",
landingHeroSubtitle: "Master HTML, CSS, and Tailwind through hands-on exercises with instant feedback. Free and open source.",
landingCtaStart: "Start Learning NOW",
landingWhyTitle: "Why CODE CRISPIES Works",
@@ -137,7 +137,7 @@ const translations = {
comingSoon: "Coming Soon",
landingCtaTitle: "Start Learning Today",
landingCtaSub: "Free and open source. No account required. Progress saved locally.",
landingCtaButton: "Begin Your Journey",
landingCtaButton: "Let's get crispy!",
// Coming Soon
landingComingSoonTitle: "Coming Soon",
@@ -150,6 +150,13 @@ const translations = {
comingSoonFrameworksTitle: "Frameworks",
comingSoonFrameworksText: "React, Vue, and Svelte basics. Build real components step by step.",
// Newsletter
newsletterText: "Want to know when new features launch?",
newsletterPlaceholder: "your@email.com",
newsletterButton: "Notify Me",
newsletterThanks: "Thanks! We'll keep you posted.",
newsletterDisclaimer: "Max once a week. Unsubscribe anytime via mail@codecrispi.es",
// Device Notice
deviceNotice: "<strong>Best on desktop or tablet (landscape).</strong> Mobile works, but larger screens make coding easier.",
@@ -283,7 +290,7 @@ const translations = {
// Landing page
landingHeroTitle: "Web Programmierung",
landingHeroHighlight: "Selbstständig lernen",
landingHeroHighlight: "Crispy Code",
landingHeroSubtitle: "Meistere HTML, CSS und Tailwind durch praktische Übungen mit sofortigem Feedback. Kostenlos und Open Source.",
landingCtaStart: "Jetzt starten",
landingWhyTitle: "Warum CODE CRISPIES funktioniert",
@@ -304,7 +311,7 @@ const translations = {
comingSoon: "Bald verfügbar",
landingCtaTitle: "Heute noch anfangen",
landingCtaSub: "Kostenlos und Open Source. Kein Konto erforderlich. Fortschritt wird lokal gespeichert.",
landingCtaButton: "Jetzt erste Schritte machen",
landingCtaButton: "Let's get crispy!",
// Coming Soon
landingComingSoonTitle: "Demnächst",
@@ -317,6 +324,13 @@ const translations = {
comingSoonFrameworksTitle: "Frameworks",
comingSoonFrameworksText: "React, Vue und Svelte Grundlagen. Baue echte Komponenten Schritt für Schritt.",
// Newsletter
newsletterText: "Möchtest du erfahren, wenn neue Funktionen erscheinen?",
newsletterPlaceholder: "deine@email.de",
newsletterButton: "Benachrichtigen",
newsletterThanks: "Danke! Wir halten dich auf dem Laufenden.",
newsletterDisclaimer: "Max. einmal pro Woche. Jederzeit abmelden über mail@codecrispi.es",
// Device Notice
deviceNotice: "<strong>Am besten auf Desktop oder Tablet (Querformat).</strong> Mobil funktioniert, aber größere Bildschirme machen das Coden einfacher.",
@@ -450,7 +464,7 @@ const translations = {
// Landing page
landingHeroTitle: "Naucz się tworzenia stron",
landingHeroHighlight: "Pisząc prawdziwy kod",
landingHeroHighlight: "Crispy Code",
landingHeroSubtitle: "Opanuj HTML, CSS i Tailwind poprzez praktyczne ćwiczenia z natychmiastową informacją zwrotną. Darmowe i open source.",
landingCtaStart: "Zacznij TERAZ",
landingWhyTitle: "Dlaczego CODE CRISPIES działa",
@@ -471,7 +485,7 @@ const translations = {
comingSoon: "Wkrótce",
landingCtaTitle: "Zacznij naukę już dziś",
landingCtaSub: "Darmowe i open source. Bez konta. Postęp zapisywany lokalnie.",
landingCtaButton: "Rozpocznij swoją podróż",
landingCtaButton: "Let's get crispy!",
// Coming Soon
landingComingSoonTitle: "Wkrótce",
@@ -484,6 +498,13 @@ const translations = {
comingSoonFrameworksTitle: "Frameworki",
comingSoonFrameworksText: "Podstawy React, Vue i Svelte. Buduj prawdziwe komponenty krok po kroku.",
// Newsletter
newsletterText: "Chcesz wiedzieć, kiedy pojawią się nowe funkcje?",
newsletterPlaceholder: "twoj@email.pl",
newsletterButton: "Powiadom mnie",
newsletterThanks: "Dzięki! Będziemy informować.",
newsletterDisclaimer: "Maks. raz w tygodniu. Wypisz się w dowolnym momencie przez mail@codecrispi.es",
// Device Notice
deviceNotice: "<strong>Najlepiej na komputerze lub tablecie (poziomo).</strong> Na telefonie też działa, ale większy ekran ułatwia kodowanie.",
@@ -618,7 +639,7 @@ const translations = {
// Landing page
landingHeroTitle: "Aprende desarrollo web",
landingHeroHighlight: "Escribiendo código real",
landingHeroHighlight: "Crispy Code",
landingHeroSubtitle:
"Domina HTML, CSS y Tailwind a través de ejercicios prácticos con retroalimentación instantánea. Gratis y de código abierto.",
landingCtaStart: "Empieza AHORA",
@@ -640,7 +661,7 @@ const translations = {
comingSoon: "Próximamente",
landingCtaTitle: "Empieza a aprender hoy",
landingCtaSub: "Gratis y de código abierto. Sin cuenta requerida. Progreso guardado localmente.",
landingCtaButton: "Comienza tu viaje",
landingCtaButton: "Let's get crispy!",
// Coming Soon
landingComingSoonTitle: "Próximamente",
@@ -653,6 +674,13 @@ const translations = {
comingSoonFrameworksTitle: "Frameworks",
comingSoonFrameworksText: "Fundamentos de React, Vue y Svelte. Construye componentes reales paso a paso.",
// Newsletter
newsletterText: "¿Quieres saber cuando se lancen nuevas funciones?",
newsletterPlaceholder: "tu@email.com",
newsletterButton: "Notificarme",
newsletterThanks: "¡Gracias! Te mantendremos informado.",
newsletterDisclaimer: "Máximo una vez por semana. Cancela cuando quieras vía mail@codecrispi.es",
// Device Notice
deviceNotice: "<strong>Mejor en escritorio o tablet (horizontal).</strong> Funciona en móvil, pero pantallas más grandes facilitan la programación.",
@@ -785,7 +813,7 @@ const translations = {
// Landing page
landingHeroTitle: "تعلم تطوير الويب",
landingHeroHighlight: "بكتابة كود حقيقي",
landingHeroHighlight: "Crispy Code",
landingHeroSubtitle: "أتقن HTML و CSS و Tailwind من خلال تمارين عملية مع ملاحظات فورية. مجاني ومفتوح المصدر.",
landingCtaStart: "ابدأ الآن",
landingWhyTitle: "لماذا CODE CRISPIES فعال",
@@ -804,7 +832,7 @@ const translations = {
comingSoon: "قريباً",
landingCtaTitle: "ابدأ التعلم اليوم",
landingCtaSub: "مجاني ومفتوح المصدر. لا حاجة لحساب. يُحفظ التقدم محليًا.",
landingCtaButton: "ابدأ رحلتك",
landingCtaButton: "Let's get crispy!",
// Coming Soon
landingComingSoonTitle: "قريباً",
@@ -817,6 +845,13 @@ const translations = {
comingSoonFrameworksTitle: "أطر العمل",
comingSoonFrameworksText: "أساسيات React وVue وSvelte. ابنِ مكونات حقيقية خطوة بخطوة.",
// Newsletter
newsletterText: "هل تريد معرفة متى تُطلق ميزات جديدة؟",
newsletterPlaceholder: "بريدك@email.com",
newsletterButton: "أبلغني",
newsletterThanks: "شكراً! سنبقيك على اطلاع.",
newsletterDisclaimer: "مرة واحدة أسبوعياً كحد أقصى. إلغاء الاشتراك في أي وقت عبر mail@codecrispi.es",
// Device Notice
deviceNotice: "<strong>أفضل على الكمبيوتر أو الجهاز اللوحي (أفقي).</strong> يعمل على الجوال، لكن الشاشات الأكبر تسهّل البرمجة.",
@@ -950,7 +985,7 @@ const translations = {
// Landing page
landingHeroTitle: "Вивчай веб-розробку",
landingHeroHighlight: "Пишучи справжній код",
landingHeroHighlight: "Crispy Code",
landingHeroSubtitle: "Опануй HTML, CSS та Tailwind через практичні вправи з миттєвим зворотним зв'язком. Безкоштовно та з відкритим кодом.",
landingCtaStart: "Почни ЗАРАЗ",
landingWhyTitle: "Чому CODE CRISPIES працює",
@@ -970,7 +1005,7 @@ const translations = {
comingSoon: "Незабаром",
landingCtaTitle: "Почни вчитися сьогодні",
landingCtaSub: "Безкоштовно та з відкритим кодом. Без реєстрації. Прогрес зберігається локально.",
landingCtaButton: "Розпочни свою подорож",
landingCtaButton: "Let's get crispy!",
// Coming Soon
landingComingSoonTitle: "Незабаром",
@@ -983,6 +1018,13 @@ const translations = {
comingSoonFrameworksTitle: "Фреймворки",
comingSoonFrameworksText: "Основи React, Vue та Svelte. Створюй справжні компоненти крок за кроком.",
// Newsletter
newsletterText: "Хочете дізнатися, коли з'являться нові функції?",
newsletterPlaceholder: "ваш@email.com",
newsletterButton: "Повідомити мене",
newsletterThanks: "Дякуємо! Ми будемо тримати вас в курсі.",
newsletterDisclaimer: "Максимум раз на тиждень. Відписатися можна будь-коли через mail@codecrispi.es",
// Device Notice
deviceNotice: "<strong>Найкраще на комп'ютері або планшеті (горизонтально).</strong> На телефоні теж працює, але більший екран полегшує програмування.",

View File

@@ -173,37 +173,54 @@
<h2 data-i18n="landingComingSoonTitle">Coming Soon</h2>
<div class="coming-soon-grid">
<article class="coming-soon-card">
<span class="coming-soon-icon">🔄</span>
<span class="coming-soon-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 0 1-9 9m9-9a9 9 0 0 0-9-9m9 9H3m9 9a9 9 0 0 1-9-9m9 9c1.66 0 3-4.03 3-9s-1.34-9-3-9m0 18c-1.66 0-3-4.03-3-9s1.34-9 3-9m-9 9a9 9 0 0 1 9-9"/></svg>
</span>
<h3 data-i18n="comingSoonSyncTitle">Cloud Sync</h3>
<p data-i18n="comingSoonSyncText">Sync your progress across all devices. Start on desktop, continue on tablet.</p>
</article>
<article class="coming-soon-card">
<span class="coming-soon-icon">🏆</span>
<span class="coming-soon-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="6"/><path d="M15.477 12.89 17 22l-5-3-5 3 1.523-9.11"/></svg>
</span>
<h3 data-i18n="comingSoonAchievementsTitle">Achievements</h3>
<p data-i18n="comingSoonAchievementsText">Earn badges as you master new skills. Track your learning milestones.</p>
</article>
<article class="coming-soon-card">
<span class="coming-soon-icon"></span>
<span class="coming-soon-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/></svg>
</span>
<h3 data-i18n="comingSoonJsTitle">JavaScript</h3>
<p data-i18n="comingSoonJsText">Interactive JavaScript lessons with live code execution and DOM manipulation.</p>
</article>
<article class="coming-soon-card">
<span class="coming-soon-icon">🧩</span>
<span class="coming-soon-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
</span>
<h3 data-i18n="comingSoonFrameworksTitle">Frameworks</h3>
<p data-i18n="comingSoonFrameworksText">React, Vue, and Svelte basics. Build real components step by step.</p>
</article>
</div>
<div class="newsletter-signup">
<p data-i18n="newsletterText">Want to know when new features launch?</p>
<form id="newsletter-form" class="newsletter-form">
<input type="email" id="newsletter-email" required data-i18n-placeholder="newsletterPlaceholder" placeholder="your@email.com">
<button type="submit" class="btn btn-outline" data-i18n="newsletterButton">Notify Me</button>
</form>
<p class="newsletter-disclaimer" data-i18n="newsletterDisclaimer">Max once a week. Unsubscribe anytime via mail@codecrispi.es</p>
<p id="newsletter-thanks" class="newsletter-thanks hidden" data-i18n="newsletterThanks">Thanks! We'll keep you posted.</p>
</div>
</section>
<section class="device-notice">
<p data-i18n="deviceNotice">
<p data-i18n-html="deviceNotice">
<strong>Best on desktop or tablet (landscape).</strong> Mobile works, but larger screens make coding easier.
</p>
</section>
<section class="landing-cta">
<h2 data-i18n="landingCtaTitle">Start Learning Today</h2>
<a href="#welcome/0" class="cta-button" data-i18n="landingCtaButton">Begin Your Journey</a>
<a href="#welcome/0" class="cta-button" data-i18n="landingCtaButton">Let's get crispy!</a>
<p class="cta-sub" data-i18n="landingCtaSub">Free and open source. No account required. Progress saved locally.</p>
</section>

View File

@@ -1924,11 +1924,16 @@ input:checked + .toggle-slider::before {
}
.coming-soon-icon {
font-size: 2rem;
display: block;
margin-bottom: 0.75rem;
}
.coming-soon-icon svg {
width: 2rem;
height: 2rem;
stroke: var(--section-color);
}
.coming-soon-card h3 {
font-size: 1rem;
margin-bottom: 0.5rem;
@@ -1954,6 +1959,71 @@ input:checked + .toggle-slider::before {
}
}
/* Newsletter Signup */
.newsletter-signup {
margin-top: var(--spacing-lg);
padding: 1.5rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
}
.newsletter-signup p {
margin: 0;
color: var(--light-text);
}
.newsletter-form {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
justify-content: center;
}
.newsletter-form input[type="email"] {
padding: 0.5rem 1rem;
border: 2px solid var(--border-color);
border-radius: var(--border-radius-sm);
background: var(--panel-bg);
color: var(--text);
font-size: 1rem;
min-width: 200px;
}
.newsletter-form input[type="email"]:focus {
outline: none;
border-color: var(--section-color);
}
.newsletter-signup .btn-outline {
border: 2px solid var(--section-color);
color: var(--section-color);
background: transparent;
padding: 0.5rem 1.5rem;
font-weight: 500;
transition: all 0.2s;
}
.newsletter-signup .btn-outline:hover {
background: var(--section-color);
color: white;
}
.newsletter-disclaimer {
font-size: 0.8rem;
opacity: 0.7;
}
.newsletter-thanks {
color: var(--success);
font-weight: 500;
}
.newsletter-thanks.hidden {
display: none;
}
/* Device Notice */
.device-notice {
margin-top: var(--spacing-lg);