feat: add landing footer with donation support, change license to Unlicense

- Add extended landing footer with module links grouped by section
- Integrate Liberapay donation widget with Umami tracking
- Add support section to help dialog and goodbye lesson
- Change license from MIT to Unlicense (public domain)
- Disable Tailwind section (not yet activated)
- Update German CTA copy
- Update all 6 language translations for license text

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
2026-01-16 03:33:41 +01:00
parent f1496e7232
commit 1368f1c079
7 changed files with 323 additions and 14 deletions

View File

@@ -155,6 +155,7 @@ const elements = {
sidebarBackdrop: document.getElementById("sidebar-backdrop"),
closeSidebar: document.getElementById("close-sidebar"),
moduleList: document.getElementById("module-list"),
footerLessonLinks: document.getElementById("footer-lesson-links"),
progressFill: document.getElementById("progress-fill"),
progressText: document.getElementById("progress-text"),
resetBtn: document.getElementById("reset-btn"),
@@ -1991,6 +1992,40 @@ function showLandingPage() {
// Update section progress on landing page
updateLandingProgress();
// Render footer lesson links
renderFooterLessonLinks();
}
/**
* Render module links in the landing page footer, grouped by section
*/
function renderFooterLessonLinks() {
if (!elements.footerLessonLinks) return;
const modules = lessonEngine.modules || [];
const sectionGroups = { css: [], html: [] };
modules.forEach((module) => {
if (module.excludeFromProgress) return;
const sectionId = getModuleSection(module);
if (sectionId && sectionGroups[sectionId]) {
sectionGroups[sectionId].push(module);
}
});
let html = "";
Object.entries(sectionGroups).forEach(([sectionId, sectionModules]) => {
if (sectionModules.length === 0) return;
const sectionName = sectionId.toUpperCase();
html += `<div class="footer-section-group"><strong><a href="#${sectionId}">${sectionName}</a></strong>`;
sectionModules.forEach((module) => {
html += `<a href="#${module.id}/0">${module.title}</a>`;
});
html += "</div>";
});
elements.footerLessonLinks.innerHTML = html;
}
/**
@@ -2387,6 +2422,8 @@ function init() {
track("landing_cta", { href: target.getAttribute("href") });
} else if (target.classList.contains("section-card")) {
track("landing_section", { section: target.dataset.section });
} else if (target.closest(".footer-support")) {
track("support_click", { location: "landing" });
}
});
}

View File

@@ -134,7 +134,20 @@ const translations = {
landingTailwindDesc: "Utility-first CSS framework",
landingCtaTitle: "Start Learning Today",
landingCtaSub: "Free and open source. No account required. Progress saved locally.",
landingCtaButton: "Begin Your Journey"
landingCtaButton: "Begin Your Journey",
// Footer
footerModules: "Modules",
footerResources: "Resources",
footerPlayground: "Playground",
footerAbout: "About",
footerSupport: "Support",
footerSupportText: "Help keep Code Crispies free and open source.",
footerLicense: "Released into the public domain.",
// Help Dialog Support
supportTitle: "Support the Project",
supportText: "Help keep Code Crispies free and open source."
},
de: {
@@ -271,7 +284,20 @@ const translations = {
landingTailwindDesc: "Utility-first CSS-Framework",
landingCtaTitle: "Heute noch anfangen",
landingCtaSub: "Kostenlos und Open Source. Kein Konto erforderlich. Fortschritt wird lokal gespeichert.",
landingCtaButton: "Starte deine Reise"
landingCtaButton: "Jetzt erste Schritte machen",
// Footer
footerModules: "Module",
footerResources: "Ressourcen",
footerPlayground: "Playground",
footerAbout: "Über uns",
footerSupport: "Unterstützen",
footerSupportText: "Hilf mit, Code Crispies kostenlos und Open Source zu halten.",
footerLicense: "Gemeinfrei (Public Domain).",
// Help Dialog Support
supportTitle: "Projekt unterstützen",
supportText: "Hilf mit, Code Crispies kostenlos und Open Source zu halten."
},
// Polish
@@ -408,7 +434,20 @@ const translations = {
landingTailwindDesc: "Framework CSS oparty na klasach utility",
landingCtaTitle: "Zacznij naukę już dziś",
landingCtaSub: "Darmowe i open source. Bez konta. Postęp zapisywany lokalnie.",
landingCtaButton: "Rozpocznij swoją podróż"
landingCtaButton: "Rozpocznij swoją podróż",
// Footer
footerModules: "Moduły",
footerResources: "Zasoby",
footerPlayground: "Plac zabaw",
footerAbout: "O nas",
footerSupport: "Wsparcie",
footerSupportText: "Pomóż utrzymać Code Crispies darmowym i open source.",
footerLicense: "Udostępnione jako domena publiczna.",
// Help Dialog Support
supportTitle: "Wesprzyj projekt",
supportText: "Pomóż utrzymać Code Crispies darmowym i open source."
},
// Spanish
@@ -547,7 +586,20 @@ const translations = {
landingTailwindDesc: "Framework CSS basado en utilidades",
landingCtaTitle: "Empieza a aprender hoy",
landingCtaSub: "Gratis y de código abierto. Sin cuenta requerida. Progreso guardado localmente.",
landingCtaButton: "Comienza tu viaje"
landingCtaButton: "Comienza tu viaje",
// Footer
footerModules: "Módulos",
footerResources: "Recursos",
footerPlayground: "Zona de pruebas",
footerAbout: "Acerca de",
footerSupport: "Apoyar",
footerSupportText: "Ayuda a mantener Code Crispies gratis y de código abierto.",
footerLicense: "Liberado al dominio público.",
// Help Dialog Support
supportTitle: "Apoyar el proyecto",
supportText: "Ayuda a mantener Code Crispies gratis y de código abierto."
},
// Arabic
@@ -681,7 +733,20 @@ const translations = {
landingTailwindDesc: "إطار CSS قائم على الأدوات",
landingCtaTitle: "ابدأ التعلم اليوم",
landingCtaSub: "مجاني ومفتوح المصدر. لا حاجة لحساب. يُحفظ التقدم محليًا.",
landingCtaButton: "ابدأ رحلتك"
landingCtaButton: "ابدأ رحلتك",
// Footer
footerModules: "الوحدات",
footerResources: "الموارد",
footerPlayground: "ساحة التجربة",
footerAbout: "حول",
footerSupport: "الدعم",
footerSupportText: "ساعد في إبقاء Code Crispies مجانيًا ومفتوح المصدر.",
footerLicense: "مُطلق للملكية العامة.",
// Help Dialog Support
supportTitle: "ادعم المشروع",
supportText: "ساعد في إبقاء Code Crispies مجانيًا ومفتوح المصدر."
},
// Ukrainian
@@ -817,7 +882,20 @@ const translations = {
landingTailwindDesc: "CSS-фреймворк на основі утиліт",
landingCtaTitle: "Почни вчитися сьогодні",
landingCtaSub: "Безкоштовно та з відкритим кодом. Без реєстрації. Прогрес зберігається локально.",
landingCtaButton: "Розпочни свою подорож"
landingCtaButton: "Розпочни свою подорож",
// Footer
footerModules: "Модулі",
footerResources: "Ресурси",
footerPlayground: "Пісочниця",
footerAbout: "Про нас",
footerSupport: "Підтримка",
footerSupportText: "Допоможи зберегти Code Crispies безкоштовним та з відкритим кодом.",
footerLicense: "Передано у суспільне надбання.",
// Help Dialog Support
supportTitle: "Підтримати проєкт",
supportText: "Допоможи зберегти Code Crispies безкоштовним та з відкритим кодом."
}
};

View File

@@ -74,7 +74,8 @@
<nav class="main-nav" id="main-nav" aria-label="Main sections">
<a href="#css" class="nav-link" data-section="css">CSS</a>
<a href="#html" class="nav-link" data-section="html">HTML</a>
<a href="#tailwind" class="nav-link" data-section="tailwind">Tailwind</a>
<!-- Tailwind disabled until lessons are ready -->
<!-- <a href="#tailwind" class="nav-link" data-section="tailwind">Tailwind</a> -->
<a href="#reference/css" class="nav-link nav-link-ref" data-section="reference">Reference</a>
</nav>
<button id="help-btn" class="help-toggle" data-i18n-aria-label="help" aria-label="Help">?</button>
@@ -150,12 +151,15 @@
<p data-i18n="landingHtmlDesc">Semantic markup and native elements</p>
<span class="section-card-progress" id="html-progress"></span>
</a>
<!-- Tailwind disabled until lessons are ready -->
<!--
<a href="#tailwind" class="section-card" data-section="tailwind">
<div class="section-card-icon" style="background: #06b6d4">TW</div>
<h3>Tailwind CSS</h3>
<p data-i18n="landingTailwindDesc">Utility-first CSS framework</p>
<span class="section-card-progress" id="tailwind-progress"></span>
</a>
-->
</div>
</section>
@@ -164,6 +168,39 @@
<a href="#welcome/0" class="cta-button" data-i18n="landingCtaButton">Begin Your Journey</a>
<p class="cta-sub" data-i18n="landingCtaSub">Free and open source. No account required. Progress saved locally.</p>
</section>
<footer class="landing-footer">
<div class="footer-grid">
<section class="footer-section footer-modules">
<div id="footer-lesson-links" class="footer-links"></div>
</section>
<section class="footer-section">
<h4 data-i18n="footerResources">Resources</h4>
<ul class="footer-links">
<li><a href="#reference/css">CSS Reference</a></li>
<li><a href="#reference/html">HTML Reference</a></li>
<li><a href="#playground/0" data-i18n="footerPlayground">Playground</a></li>
</ul>
</section>
<section class="footer-section">
<h4 data-i18n="footerAbout">About</h4>
<ul class="footer-links">
<li><a href="https://librete.ch" target="_blank">LibreTECH</a></li>
<li><a href="https://git.librete.ch/libretech/code-crispies" target="_blank">Source Code</a></li>
<li><a href="https://github.com/nextlevelshit/code-crispies" target="_blank">GitHub</a></li>
</ul>
</section>
<section class="footer-section footer-support">
<h4 data-i18n="footerSupport">Support</h4>
<p data-i18n="footerSupportText">Help keep Code Crispies free and open source.</p>
<script src="https://liberapay.com/libretech/widgets/button.js"></script>
<noscript><a href="https://liberapay.com/libretech/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript>
</section>
</div>
<div class="footer-bottom">
<p>&copy; 2025 <a href="https://librete.ch">LibreTECH</a>. <span data-i18n="footerLicense">Open source under MIT License.</span></p>
</div>
</footer>
</div>
</div>
@@ -356,7 +393,7 @@
<li data-i18n-html="modeHtml"><strong>HTML</strong> - Practice semantic markup and native elements</li>
</ul>
<p class="help-nav-links">
Jump to: <a href="#css">CSS</a> | <a href="#html">HTML</a> | <a href="#tailwind">Tailwind</a> |
Jump to: <a href="#css">CSS</a> | <a href="#html">HTML</a> |
<a href="#reference/css">Reference</a>
</p>
@@ -415,6 +452,13 @@
<li><a href="https://github.com/nextlevelshit/code-crispies" target="_blank">GitHub</a> Public mirror</li>
<li><a href="https://www.linkedin.com/in/michael-werner-czechowski" target="_blank">LinkedIn</a> Michael Czechowski</li>
</ul>
<h4 data-i18n="supportTitle">Support the Project</h4>
<p data-i18n="supportText">Help keep Code Crispies free and open source.</p>
<div class="help-support" onclick="typeof umami !== 'undefined' && umami.track('support_click', {location: 'help'})">
<script src="https://liberapay.com/libretech/widgets/button.js"></script>
<noscript><a href="https://liberapay.com/libretech/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript>
</div>
</div>
</dialog>

View File

@@ -1865,6 +1865,132 @@ input:checked + .toggle-slider::before {
margin-top: 1rem;
}
/* ================= LANDING FOOTER ================= */
.landing-footer {
margin-top: 3rem;
padding-top: 2rem;
border-top: 1px solid var(--border-color);
}
.footer-grid {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.footer-section h4 {
font-size: 0.875rem;
font-weight: 600;
color: var(--text-color);
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.footer-links {
list-style: none;
padding: 0;
margin: 0;
}
.footer-links li {
margin-bottom: 0.5rem;
}
.footer-links a {
color: var(--light-text);
text-decoration: none;
font-size: 0.875rem;
transition: color 0.2s;
}
.footer-links a:hover {
color: var(--primary-color);
}
#footer-lesson-links {
display: flex;
gap: 2rem;
}
.footer-section-group {
display: flex;
flex-direction: column;
gap: 0.25rem;
min-width: 0;
}
.footer-section-group strong {
margin-bottom: 0.5rem;
}
.footer-section-group strong a {
color: var(--text-color);
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.footer-section-group a {
display: inline-block;
color: #888;
text-decoration: none;
font-size: 0.7rem;
font-weight: 500;
background: rgba(0, 0, 0, 0.03);
padding: 4px 10px;
border-radius: 12px;
max-width: 140px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.footer-section-group a:hover {
color: var(--primary-color);
background: rgba(0, 0, 0, 0.06);
}
.footer-support p {
color: var(--light-text);
font-size: 0.875rem;
margin-bottom: 1rem;
}
.footer-bottom {
text-align: center;
padding-top: 1.5rem;
border-top: 1px solid var(--border-color);
}
.footer-bottom p {
color: var(--light-text);
font-size: 0.8rem;
margin: 0;
}
.footer-bottom a {
color: var(--light-text);
}
.footer-bottom a:hover {
color: var(--primary-color);
}
@media (max-width: 768px) {
.footer-grid {
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
}
@media (max-width: 480px) {
.footer-grid {
grid-template-columns: 1fr;
}
}
/* ================= SECTION PAGE ================= */
.section-page {
flex: 1;