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:
37
src/app.js
37
src/app.js
@@ -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" });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
90
src/i18n.js
90
src/i18n.js
@@ -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 безкоштовним та з відкритим кодом."
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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>© 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>
|
||||
|
||||
|
||||
126
src/main.css
126
src/main.css
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user