From 1368f1c079466e397e5365bbd54b7302c39359ea Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Fri, 16 Jan 2026 03:33:41 +0100 Subject: [PATCH] feat: add landing footer with donation support, change license to Unlicense MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- LICENSE | 24 ++++++++ README.md | 10 ++-- lessons/99-goodbye.json | 2 +- src/app.js | 37 ++++++++++++ src/i18n.js | 90 ++++++++++++++++++++++++++-- src/index.html | 48 ++++++++++++++- src/main.css | 126 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 323 insertions(+), 14 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md index bafbd1c..95535dd 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ ![Code Crispies Logo](./public/android-chrome-192x192.png) # Code Crispies -An interactive platform for learning CSS and Tailwind CSS through practical challenges. +An interactive platform for learning HTML, CSS, and Tailwind CSS through practical challenges. ## 📚 Overview -Code Crispies is a web-based learning platform designed to help users master CSS and Tailwind CSS through hands-on exercises. The application presents a series of progressive challenges organized into themed modules, allowing learners to build their skills step by step while receiving immediate feedback. +Code Crispies is a web-based learning platform designed to help users master HTML, CSS, and Tailwind CSS through hands-on exercises. The application presents a series of progressive challenges organized into themed modules, allowing learners to build their skills step by step while receiving immediate feedback. ### Key Features -- **Interactive Lessons**: Learn CSS and Tailwind concepts through practical, hands-on challenges -- **Dual Mode Support**: Switch between CSS and Tailwind CSS learning modes +- **Interactive Lessons**: Learn HTML, CSS, and Tailwind concepts through practical, hands-on challenges +- **Multiple Learning Paths**: CSS fundamentals, HTML semantics, and Tailwind CSS utilities - **Progressive Difficulty**: Modules are structured to build skills gradually from basic to advanced - **Real-Time Feedback**: Get immediate validation on your code solutions with comprehensive feedback - **Progress Tracking**: Your learning progress is automatically saved in the browser @@ -256,4 +256,4 @@ When adding new lessons: ## 📄 License -Copyright (c) 2026 Michael Czechowski. Licensed under the [./LICENSE](WTFPL). +This project is released into the public domain under the [Unlicense](./LICENSE). You are free to copy, modify, publish, use, compile, sell, or distribute this software for any purpose. diff --git a/lessons/99-goodbye.json b/lessons/99-goodbye.json index 56cda1b..4d5d410 100644 --- a/lessons/99-goodbye.json +++ b/lessons/99-goodbye.json @@ -10,7 +10,7 @@ { "id": "next-steps", "title": "Keep Going!", - "description": "Great progress! You're building real web development skills.

Continue learning:
MDN Web Docs - The definitive reference
CSS-Tricks - Practical techniques

Practice ideas:
• Build your portfolio site
• Recreate a website you like
• Try the Playground to experiment freely

Contribute: Code Crispies is open source. Add lessons, fix bugs, or translate!", + "description": "Great progress! You're building real web development skills.

Continue learning:
MDN Web Docs - The definitive reference
CSS-Tricks - Practical techniques

Practice ideas:
• Build your portfolio site
• Recreate a website you like
• Try the Playground to experiment freely

Contribute: Code Crispies is open source. Add lessons, fix bugs, or translate!

Support: Help keep it free and open source!
\"Donate", "task": "Type Thank you!", "previewHTML": "", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 1.5rem; color: #6366f1; }", diff --git a/src/app.js b/src/app.js index cda038e..83eb191 100644 --- a/src/app.js +++ b/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 += `"; + }); + + 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" }); } }); } diff --git a/src/i18n.js b/src/i18n.js index a8164cb..a1d9fe3 100644 --- a/src/i18n.js +++ b/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 безкоштовним та з відкритим кодом." } }; diff --git a/src/index.html b/src/index.html index 146342a..d42885b 100644 --- a/src/index.html +++ b/src/index.html @@ -74,7 +74,8 @@ @@ -150,12 +151,15 @@

Semantic markup and native elements

+ + @@ -164,6 +168,39 @@ Begin Your Journey

Free and open source. No account required. Progress saved locally.

+ + @@ -356,7 +393,7 @@
  • HTML - Practice semantic markup and native elements
  • @@ -415,6 +452,13 @@
  • GitHub – Public mirror
  • LinkedIn – Michael Czechowski
  • + +

    Support the Project

    +

    Help keep Code Crispies free and open source.

    +
    + + +
    diff --git a/src/main.css b/src/main.css index 8db786f..38267f2 100644 --- a/src/main.css +++ b/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;