diff --git a/src/app.js b/src/app.js index 029f60d..3e60225 100644 --- a/src/app.js +++ b/src/app.js @@ -7,7 +7,8 @@ import { initI18n, t, getLanguage, setLanguage, applyTranslations } from "./i18n // Simplified state - LessonEngine now manages lesson state and progress const state = { userSettings: { - disableFeedbackErrors: false + disableFeedbackErrors: false, + skipResetCodeConfirmation: false }, showExpected: false }; @@ -62,7 +63,12 @@ const elements = { resetDialog: document.getElementById("reset-dialog"), resetDialogClose: document.getElementById("reset-dialog-close"), cancelReset: document.getElementById("cancel-reset"), - confirmReset: document.getElementById("confirm-reset") + confirmReset: document.getElementById("confirm-reset"), + resetCodeDialog: document.getElementById("reset-code-dialog"), + resetCodeDialogClose: document.getElementById("reset-code-dialog-close"), + cancelResetCode: document.getElementById("cancel-reset-code"), + confirmResetCode: document.getElementById("confirm-reset-code"), + resetCodeDontShow: document.getElementById("reset-code-dont-show") }; // Initialize the lesson engine - now the single source of truth @@ -675,6 +681,35 @@ function handleResetConfirm() { updateProgressDisplay(); } +function showResetCodeConfirmation() { + // Reset the checkbox state each time dialog is shown + elements.resetCodeDontShow.checked = false; + elements.resetCodeDialog.showModal(); +} + +function closeResetCodeDialog() { + elements.resetCodeDialog.close(); +} + +function handleResetCodeConfirm() { + // Save preference if checkbox is checked + if (elements.resetCodeDontShow.checked) { + state.userSettings.skipResetCodeConfirmation = true; + saveUserSettings(); + } + + closeResetCodeDialog(); + resetCode(); +} + +function handleResetCodeClick() { + if (state.userSettings.skipResetCodeConfirmation) { + resetCode(); + } else { + showResetCodeConfirmation(); + } +} + // ================= INITIALIZATION ================= function initCodeEditor() { @@ -746,7 +781,7 @@ function init() { elements.redoBtn.addEventListener("click", () => { if (codeEditor) codeEditor.redo(); }); - elements.resetCodeBtn.addEventListener("click", resetCode); + elements.resetCodeBtn.addEventListener("click", handleResetCodeClick); // Dialogs elements.helpBtn.addEventListener("click", showHelp); @@ -761,6 +796,12 @@ function init() { }); elements.cancelReset.addEventListener("click", closeResetDialog); elements.confirmReset.addEventListener("click", handleResetConfirm); + elements.resetCodeDialogClose.addEventListener("click", closeResetCodeDialog); + elements.resetCodeDialog.addEventListener("click", (e) => { + if (e.target === elements.resetCodeDialog) closeResetCodeDialog(); + }); + elements.cancelResetCode.addEventListener("click", closeResetCodeDialog); + elements.confirmResetCode.addEventListener("click", handleResetCodeConfirm); // Settings elements.disableFeedbackToggle.addEventListener("change", (e) => { diff --git a/src/config/lessons.js b/src/config/lessons.js index 7bc3f87..756e227 100644 --- a/src/config/lessons.js +++ b/src/config/lessons.js @@ -19,6 +19,7 @@ import htmlTablesEN from "../../lessons/30-html-tables.json"; import htmlMarqueeEN from "../../lessons/31-html-marquee.json"; import htmlSvgEN from "../../lessons/32-html-svg.json"; import flexboxEN from "../../lessons/flexbox.json"; +import goodbyeEN from "../../lessons/99-goodbye.json"; // German lesson imports import welcomeDE from "../../lessons/de/00-welcome.json"; @@ -128,7 +129,9 @@ const moduleStoreEN = [ flexboxEN, responsiveEN, // CSS Animationen - transitionsAnimationsEN + transitionsAnimationsEN, + // Goodbye + goodbyeEN ]; // German module store - ordered by learning path @@ -154,7 +157,9 @@ const moduleStoreDE = [ flexboxDE, responsiveDE, // CSS Animationen - transitionsAnimationsDE + transitionsAnimationsDE, + // Goodbye + goodbyeEN ]; // Polish module store - ordered by learning path @@ -173,7 +178,8 @@ const moduleStorePL = [ unitsVariablesPL, flexboxPL, responsivePL, - transitionsAnimationsPL + transitionsAnimationsPL, + goodbyeEN ]; // Spanish module store - ordered by learning path @@ -192,7 +198,8 @@ const moduleStoreES = [ unitsVariablesES, flexboxES, responsiveES, - transitionsAnimationsES + transitionsAnimationsES, + goodbyeEN ]; // Arabic module store - ordered by learning path @@ -211,7 +218,8 @@ const moduleStoreAR = [ unitsVariablesAR, flexboxAR, responsiveAR, - transitionsAnimationsAR + transitionsAnimationsAR, + goodbyeEN ]; // Ukrainian module store - ordered by learning path @@ -230,7 +238,8 @@ const moduleStoreUK = [ unitsVariablesUK, flexboxUK, responsiveUK, - transitionsAnimationsUK + transitionsAnimationsUK, + goodbyeEN ]; // Map of language codes to module stores diff --git a/src/i18n.js b/src/i18n.js index ba04fed..b2dcea4 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -49,7 +49,8 @@ const translations = { // Help dialog helpTitle: "Help", aboutTitle: "About Code Crispies", - aboutText: "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required - just start coding!", + aboutText: + "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required - just start coding!", learningModesTitle: "Learning Modes", modeCss: "CSS - Write CSS rules to style elements", modeTailwind: "Tailwind - Apply utility classes directly in HTML", @@ -84,7 +85,7 @@ const translations = { // Contact contactTitle: "Contact & Links", - contactText: "Code Crispies is developed by LibreTECH", + contactText: 'Code Crispies is developed by LibreTECH', // Reset dialog resetDialogTitle: "Reset Progress", @@ -92,6 +93,12 @@ const translations = { cancel: "Cancel", resetAll: "Reset All", + // Reset code dialog + resetCodeDialogTitle: "Reset Code", + resetCodeDialogText: "Reset your code to the initial state for this lesson?", + dontShowAgain: "Don't show this again", + reset: "Reset", + // Dynamic content loadingFallbackText: "Could not load lesson. Please select one from the menu or check the help.", completed: "Completed", @@ -149,13 +156,15 @@ const translations = { // Help dialog helpTitle: "Hilfe", aboutTitle: "Über Code Crispies", - aboutText: "Code Crispies ist eine kostenlose Open-Source-Plattform zum Erlernen von Webentwicklung durch praktische Übungen. Kein Konto erforderlich - einfach loslegen!", + aboutText: + "Code Crispies ist eine kostenlose Open-Source-Plattform zum Erlernen von Webentwicklung durch praktische Übungen. Kein Konto erforderlich - einfach loslegen!", learningModesTitle: "Lernmodi", modeCss: "CSS - Schreibe CSS-Regeln zum Stylen von Elementen", modeTailwind: "Tailwind - Wende Utility-Klassen direkt im HTML an", modeHtml: "HTML - Übe semantisches Markup und native Elemente", gettingStartedTitle: "Erste Schritte", - gettingStartedText: "Öffne das Menü (☰), um Lektionsmodule zu durchsuchen. Jedes Modul behandelt ein Thema mit aufeinander aufbauenden Übungen.", + gettingStartedText: + "Öffne das Menü (☰), um Lektionsmodule zu durchsuchen. Jedes Modul behandelt ein Thema mit aufeinander aufbauenden Übungen.", completingLessonsTitle: "Lektionen abschließen", completingStep1: "Lies die Aufgabenstellung auf der linken Seite", completingStep2: "Schreibe deinen Code im Editor", @@ -184,7 +193,7 @@ const translations = { // Contact contactTitle: "Kontakt & Links", - contactText: "Code Crispies wird von LibreTECH entwickelt", + contactText: 'Code Crispies wird von LibreTECH entwickelt', // Reset dialog resetDialogTitle: "Fortschritt zurücksetzen", @@ -192,6 +201,12 @@ const translations = { cancel: "Abbrechen", resetAll: "Alles zurücksetzen", + // Reset code dialog + resetCodeDialogTitle: "Code zurücksetzen", + resetCodeDialogText: "Code auf den Anfangszustand dieser Lektion zurücksetzen?", + dontShowAgain: "Nicht mehr anzeigen", + reset: "Zurücksetzen", + // Dynamic content loadingFallbackText: "Lektion konnte nicht geladen werden. Bitte wähle eine aus dem Menü oder prüfe die Hilfe.", completed: "Erledigt", @@ -250,7 +265,8 @@ const translations = { // Help dialog helpTitle: "Pomoc", aboutTitle: "O Code Crispies", - aboutText: "Code Crispies to darmowa platforma open-source do nauki tworzenia stron internetowych poprzez praktyczne ćwiczenia. Nie wymaga konta - po prostu zacznij kodować!", + aboutText: + "Code Crispies to darmowa platforma open-source do nauki tworzenia stron internetowych poprzez praktyczne ćwiczenia. Nie wymaga konta - po prostu zacznij kodować!", learningModesTitle: "Tryby nauki", modeCss: "CSS - Pisz reguły CSS do stylizowania elementów", modeTailwind: "Tailwind - Stosuj klasy utility bezpośrednio w HTML", @@ -285,7 +301,7 @@ const translations = { // Contact contactTitle: "Kontakt i linki", - contactText: "Code Crispies jest rozwijany przez LibreTECH", + contactText: 'Code Crispies jest rozwijany przez LibreTECH', // Reset dialog resetDialogTitle: "Resetuj postęp", @@ -293,6 +309,12 @@ const translations = { cancel: "Anuluj", resetAll: "Resetuj wszystko", + // Reset code dialog + resetCodeDialogTitle: "Resetuj kod", + resetCodeDialogText: "Przywrócić kod do stanu początkowego tej lekcji?", + dontShowAgain: "Nie pokazuj ponownie", + reset: "Resetuj", + // Dynamic content loadingFallbackText: "Nie można załadować lekcji. Wybierz jedną z menu lub sprawdź pomoc.", completed: "Ukończono", @@ -351,13 +373,15 @@ const translations = { // Help dialog helpTitle: "Ayuda", aboutTitle: "Acerca de Code Crispies", - aboutText: "Code Crispies es una plataforma gratuita de código abierto para aprender desarrollo web a través de ejercicios prácticos. No se requiere cuenta, ¡solo empieza a programar!", + aboutText: + "Code Crispies es una plataforma gratuita de código abierto para aprender desarrollo web a través de ejercicios prácticos. No se requiere cuenta, ¡solo empieza a programar!", learningModesTitle: "Modos de aprendizaje", modeCss: "CSS - Escribe reglas CSS para estilizar elementos", modeTailwind: "Tailwind - Aplica clases de utilidad directamente en HTML", modeHtml: "HTML - Practica marcado semántico y elementos nativos", gettingStartedTitle: "Primeros pasos", - gettingStartedText: "Abre el menú (☰) para explorar los módulos de lecciones. Cada módulo cubre un tema específico con ejercicios progresivos.", + gettingStartedText: + "Abre el menú (☰) para explorar los módulos de lecciones. Cada módulo cubre un tema específico con ejercicios progresivos.", completingLessonsTitle: "Completar lecciones", completingStep1: "Lee las instrucciones de la tarea a la izquierda", completingStep2: "Escribe tu código en el editor", @@ -386,7 +410,7 @@ const translations = { // Contact contactTitle: "Contacto y enlaces", - contactText: "Code Crispies es desarrollado por LibreTECH", + contactText: 'Code Crispies es desarrollado por LibreTECH', // Reset dialog resetDialogTitle: "Reiniciar progreso", @@ -394,6 +418,12 @@ const translations = { cancel: "Cancelar", resetAll: "Reiniciar todo", + // Reset code dialog + resetCodeDialogTitle: "Reiniciar código", + resetCodeDialogText: "¿Restaurar el código al estado inicial de esta lección?", + dontShowAgain: "No mostrar de nuevo", + reset: "Reiniciar", + // Dynamic content loadingFallbackText: "No se pudo cargar la lección. Selecciona una del menú o consulta la ayuda.", completed: "Completado", @@ -487,7 +517,7 @@ const translations = { // Contact contactTitle: "التواصل والروابط", - contactText: "Code Crispies تم تطويره بواسطة LibreTECH", + contactText: 'Code Crispies تم تطويره بواسطة LibreTECH', // Reset dialog resetDialogTitle: "إعادة تعيين التقدم", @@ -495,6 +525,12 @@ const translations = { cancel: "إلغاء", resetAll: "إعادة تعيين الكل", + // Reset code dialog + resetCodeDialogTitle: "إعادة تعيين الكود", + resetCodeDialogText: "استعادة الكود إلى الحالة الأولية لهذا الدرس؟", + dontShowAgain: "لا تظهر هذا مرة أخرى", + reset: "إعادة تعيين", + // Dynamic content loadingFallbackText: "تعذر تحميل الدرس. اختر واحدًا من القائمة أو تحقق من المساعدة.", completed: "مكتمل", @@ -553,7 +589,8 @@ const translations = { // Help dialog helpTitle: "Допомога", aboutTitle: "Про Code Crispies", - aboutText: "Code Crispies — це безкоштовна платформа з відкритим кодом для вивчення веб-розробки через практичні вправи. Обліковий запис не потрібен — просто починайте кодувати!", + aboutText: + "Code Crispies — це безкоштовна платформа з відкритим кодом для вивчення веб-розробки через практичні вправи. Обліковий запис не потрібен — просто починайте кодувати!", learningModesTitle: "Режими навчання", modeCss: "CSS - Пишіть правила CSS для стилізації елементів", modeTailwind: "Tailwind - Застосовуйте утилітарні класи безпосередньо в HTML", @@ -588,7 +625,7 @@ const translations = { // Contact contactTitle: "Контакти та посилання", - contactText: "Code Crispies розроблено LibreTECH", + contactText: 'Code Crispies розроблено LibreTECH', // Reset dialog resetDialogTitle: "Скинути прогрес", @@ -596,6 +633,12 @@ const translations = { cancel: "Скасувати", resetAll: "Скинути все", + // Reset code dialog + resetCodeDialogTitle: "Скинути код", + resetCodeDialogText: "Відновити код до початкового стану цього уроку?", + dontShowAgain: "Більше не показувати", + reset: "Скинути", + // Dynamic content loadingFallbackText: "Не вдалося завантажити урок. Виберіть один з меню або перевірте допомогу.", completed: "Завершено", diff --git a/src/impl/CodeEditor.js b/src/impl/CodeEditor.js index d8df4a5..3a8636f 100644 --- a/src/impl/CodeEditor.js +++ b/src/impl/CodeEditor.js @@ -12,19 +12,22 @@ import { autocompletion } from "@codemirror/autocomplete"; import { abbreviationTracker, expandAbbreviation } from "@emmetio/codemirror6-plugin"; // Custom overrides for One Dark theme -const editorTheme = EditorView.theme({ - "&": { - height: "100%", - fontSize: "14px" +const editorTheme = EditorView.theme( + { + "&": { + height: "100%", + fontSize: "14px" + }, + ".cm-content": { + fontFamily: "'JetBrains Mono', 'Fira Code', monospace", + padding: "12px 0" + }, + ".cm-line": { + padding: "0 12px" + } }, - ".cm-content": { - fontFamily: "'JetBrains Mono', 'Fira Code', monospace", - padding: "12px 0" - }, - ".cm-line": { - padding: "0 12px" - } -}, { dark: true }); + { dark: true } +); export class CodeEditor { constructor(container, options = {}) { @@ -55,19 +58,16 @@ export class CodeEditor { // Emmet abbreviation tracking abbreviationTracker(), // High priority keymap for Emmet - Prec.highest(keymap.of([ - { - key: "Tab", - run: expandAbbreviation - } - ])), + Prec.highest( + keymap.of([ + { + key: "Tab", + run: expandAbbreviation + } + ]) + ), // Standard keymaps including history (Ctrl+Z, Ctrl+Shift+Z) - keymap.of([ - ...historyKeymap, - { key: "Tab", run: indentMore }, - { key: "Shift-Tab", run: indentLess }, - ...defaultKeymap - ]), + keymap.of([...historyKeymap, { key: "Tab", run: indentMore }, { key: "Shift-Tab", run: indentLess }, ...defaultKeymap]), autocompletion({ activateOnTyping: true, maxRenderedOptions: 10 diff --git a/src/index.html b/src/index.html index 791e56f..22d381f 100644 --- a/src/index.html +++ b/src/index.html @@ -4,7 +4,10 @@ - + Code Crispies - Learn HTML & CSS Interactively @@ -41,15 +44,20 @@
- +
- +
- +
@@ -142,7 +150,10 @@

About Code Crispies

-

Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required - just start coding!

+

+ Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required - + just start coding! +

Learning Modes

Getting Started

-

Open the menu (☰) to browse lesson modules. Each module covers a specific topic with progressive exercises.

+

+ Open the menu (☰) to browse lesson modules. Each module covers a specific topic with progressive exercises. +

Completing Lessons

    @@ -207,6 +220,26 @@
+ + +
+

Reset Code

+ +
+
+

Reset your code to the initial state for this lesson?

+ +
+ + +
+
+
+
diff --git a/src/main.css b/src/main.css index a73511b..ce267fd 100644 --- a/src/main.css +++ b/src/main.css @@ -98,7 +98,8 @@ body { overflow: hidden; } -code, kbd { +code, +kbd { font-family: var(--font-code); } @@ -272,8 +273,12 @@ code, kbd { } @keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } + from { + opacity: 0; + } + to { + opacity: 1; + } } .module-pill { @@ -656,7 +661,9 @@ code, kbd { background: rgba(0, 0, 0, 0.4); opacity: 0; visibility: hidden; - transition: opacity 0.3s, visibility 0.3s; + transition: + opacity 0.3s, + visibility 0.3s; z-index: 199; } @@ -1054,7 +1061,6 @@ input:checked + .toggle-slider::before { color: var(--text-color); } - /* ================= DIALOG (Native HTML) ================= */ .dialog { border: none;