feat: add reset code confirmation dialog with skip option

- Add dialog to confirm resetting code to initial state
- Allow users to skip confirmation with "don't show again" checkbox
- Save preference to user settings
- Improve i18n and CodeEditor components
This commit is contained in:
2026-01-07 14:16:45 +01:00
parent 52abfb37db
commit fbe0f20ef7
6 changed files with 191 additions and 59 deletions

View File

@@ -7,7 +7,8 @@ import { initI18n, t, getLanguage, setLanguage, applyTranslations } from "./i18n
// Simplified state - LessonEngine now manages lesson state and progress // Simplified state - LessonEngine now manages lesson state and progress
const state = { const state = {
userSettings: { userSettings: {
disableFeedbackErrors: false disableFeedbackErrors: false,
skipResetCodeConfirmation: false
}, },
showExpected: false showExpected: false
}; };
@@ -62,7 +63,12 @@ const elements = {
resetDialog: document.getElementById("reset-dialog"), resetDialog: document.getElementById("reset-dialog"),
resetDialogClose: document.getElementById("reset-dialog-close"), resetDialogClose: document.getElementById("reset-dialog-close"),
cancelReset: document.getElementById("cancel-reset"), 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 // Initialize the lesson engine - now the single source of truth
@@ -675,6 +681,35 @@ function handleResetConfirm() {
updateProgressDisplay(); 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 ================= // ================= INITIALIZATION =================
function initCodeEditor() { function initCodeEditor() {
@@ -746,7 +781,7 @@ function init() {
elements.redoBtn.addEventListener("click", () => { elements.redoBtn.addEventListener("click", () => {
if (codeEditor) codeEditor.redo(); if (codeEditor) codeEditor.redo();
}); });
elements.resetCodeBtn.addEventListener("click", resetCode); elements.resetCodeBtn.addEventListener("click", handleResetCodeClick);
// Dialogs // Dialogs
elements.helpBtn.addEventListener("click", showHelp); elements.helpBtn.addEventListener("click", showHelp);
@@ -761,6 +796,12 @@ function init() {
}); });
elements.cancelReset.addEventListener("click", closeResetDialog); elements.cancelReset.addEventListener("click", closeResetDialog);
elements.confirmReset.addEventListener("click", handleResetConfirm); 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 // Settings
elements.disableFeedbackToggle.addEventListener("change", (e) => { elements.disableFeedbackToggle.addEventListener("change", (e) => {

View File

@@ -19,6 +19,7 @@ import htmlTablesEN from "../../lessons/30-html-tables.json";
import htmlMarqueeEN from "../../lessons/31-html-marquee.json"; import htmlMarqueeEN from "../../lessons/31-html-marquee.json";
import htmlSvgEN from "../../lessons/32-html-svg.json"; import htmlSvgEN from "../../lessons/32-html-svg.json";
import flexboxEN from "../../lessons/flexbox.json"; import flexboxEN from "../../lessons/flexbox.json";
import goodbyeEN from "../../lessons/99-goodbye.json";
// German lesson imports // German lesson imports
import welcomeDE from "../../lessons/de/00-welcome.json"; import welcomeDE from "../../lessons/de/00-welcome.json";
@@ -128,7 +129,9 @@ const moduleStoreEN = [
flexboxEN, flexboxEN,
responsiveEN, responsiveEN,
// CSS Animationen // CSS Animationen
transitionsAnimationsEN transitionsAnimationsEN,
// Goodbye
goodbyeEN
]; ];
// German module store - ordered by learning path // German module store - ordered by learning path
@@ -154,7 +157,9 @@ const moduleStoreDE = [
flexboxDE, flexboxDE,
responsiveDE, responsiveDE,
// CSS Animationen // CSS Animationen
transitionsAnimationsDE transitionsAnimationsDE,
// Goodbye
goodbyeEN
]; ];
// Polish module store - ordered by learning path // Polish module store - ordered by learning path
@@ -173,7 +178,8 @@ const moduleStorePL = [
unitsVariablesPL, unitsVariablesPL,
flexboxPL, flexboxPL,
responsivePL, responsivePL,
transitionsAnimationsPL transitionsAnimationsPL,
goodbyeEN
]; ];
// Spanish module store - ordered by learning path // Spanish module store - ordered by learning path
@@ -192,7 +198,8 @@ const moduleStoreES = [
unitsVariablesES, unitsVariablesES,
flexboxES, flexboxES,
responsiveES, responsiveES,
transitionsAnimationsES transitionsAnimationsES,
goodbyeEN
]; ];
// Arabic module store - ordered by learning path // Arabic module store - ordered by learning path
@@ -211,7 +218,8 @@ const moduleStoreAR = [
unitsVariablesAR, unitsVariablesAR,
flexboxAR, flexboxAR,
responsiveAR, responsiveAR,
transitionsAnimationsAR transitionsAnimationsAR,
goodbyeEN
]; ];
// Ukrainian module store - ordered by learning path // Ukrainian module store - ordered by learning path
@@ -230,7 +238,8 @@ const moduleStoreUK = [
unitsVariablesUK, unitsVariablesUK,
flexboxUK, flexboxUK,
responsiveUK, responsiveUK,
transitionsAnimationsUK transitionsAnimationsUK,
goodbyeEN
]; ];
// Map of language codes to module stores // Map of language codes to module stores

View File

@@ -49,7 +49,8 @@ const translations = {
// Help dialog // Help dialog
helpTitle: "Help", helpTitle: "Help",
aboutTitle: "About Code Crispies", 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", learningModesTitle: "Learning Modes",
modeCss: "<strong>CSS</strong> - Write CSS rules to style elements", modeCss: "<strong>CSS</strong> - Write CSS rules to style elements",
modeTailwind: "<strong>Tailwind</strong> - Apply utility classes directly in HTML", modeTailwind: "<strong>Tailwind</strong> - Apply utility classes directly in HTML",
@@ -84,7 +85,7 @@ const translations = {
// Contact // Contact
contactTitle: "Contact & Links", contactTitle: "Contact & Links",
contactText: "Code Crispies is developed by <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>", contactText: 'Code Crispies is developed by <a href="https://librete.ch" target="_blank">LibreTECH</a>',
// Reset dialog // Reset dialog
resetDialogTitle: "Reset Progress", resetDialogTitle: "Reset Progress",
@@ -92,6 +93,12 @@ const translations = {
cancel: "Cancel", cancel: "Cancel",
resetAll: "Reset All", 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 // Dynamic content
loadingFallbackText: "Could not load lesson. Please select one from the menu or check the help.", loadingFallbackText: "Could not load lesson. Please select one from the menu or check the help.",
completed: "Completed", completed: "Completed",
@@ -149,13 +156,15 @@ const translations = {
// Help dialog // Help dialog
helpTitle: "Hilfe", helpTitle: "Hilfe",
aboutTitle: "Über Code Crispies", 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", learningModesTitle: "Lernmodi",
modeCss: "<strong>CSS</strong> - Schreibe CSS-Regeln zum Stylen von Elementen", modeCss: "<strong>CSS</strong> - Schreibe CSS-Regeln zum Stylen von Elementen",
modeTailwind: "<strong>Tailwind</strong> - Wende Utility-Klassen direkt im HTML an", modeTailwind: "<strong>Tailwind</strong> - Wende Utility-Klassen direkt im HTML an",
modeHtml: "<strong>HTML</strong> - Übe semantisches Markup und native Elemente", modeHtml: "<strong>HTML</strong> - Übe semantisches Markup und native Elemente",
gettingStartedTitle: "Erste Schritte", 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", completingLessonsTitle: "Lektionen abschließen",
completingStep1: "Lies die Aufgabenstellung auf der linken Seite", completingStep1: "Lies die Aufgabenstellung auf der linken Seite",
completingStep2: "Schreibe deinen Code im Editor", completingStep2: "Schreibe deinen Code im Editor",
@@ -184,7 +193,7 @@ const translations = {
// Contact // Contact
contactTitle: "Kontakt & Links", contactTitle: "Kontakt & Links",
contactText: "Code Crispies wird von <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a> entwickelt", contactText: 'Code Crispies wird von <a href="https://librete.ch" target="_blank">LibreTECH</a> entwickelt',
// Reset dialog // Reset dialog
resetDialogTitle: "Fortschritt zurücksetzen", resetDialogTitle: "Fortschritt zurücksetzen",
@@ -192,6 +201,12 @@ const translations = {
cancel: "Abbrechen", cancel: "Abbrechen",
resetAll: "Alles zurücksetzen", 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 // Dynamic content
loadingFallbackText: "Lektion konnte nicht geladen werden. Bitte wähle eine aus dem Menü oder prüfe die Hilfe.", loadingFallbackText: "Lektion konnte nicht geladen werden. Bitte wähle eine aus dem Menü oder prüfe die Hilfe.",
completed: "Erledigt", completed: "Erledigt",
@@ -250,7 +265,8 @@ const translations = {
// Help dialog // Help dialog
helpTitle: "Pomoc", helpTitle: "Pomoc",
aboutTitle: "O Code Crispies", 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", learningModesTitle: "Tryby nauki",
modeCss: "<strong>CSS</strong> - Pisz reguły CSS do stylizowania elementów", modeCss: "<strong>CSS</strong> - Pisz reguły CSS do stylizowania elementów",
modeTailwind: "<strong>Tailwind</strong> - Stosuj klasy utility bezpośrednio w HTML", modeTailwind: "<strong>Tailwind</strong> - Stosuj klasy utility bezpośrednio w HTML",
@@ -285,7 +301,7 @@ const translations = {
// Contact // Contact
contactTitle: "Kontakt i linki", contactTitle: "Kontakt i linki",
contactText: "Code Crispies jest rozwijany przez <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>", contactText: 'Code Crispies jest rozwijany przez <a href="https://librete.ch" target="_blank">LibreTECH</a>',
// Reset dialog // Reset dialog
resetDialogTitle: "Resetuj postęp", resetDialogTitle: "Resetuj postęp",
@@ -293,6 +309,12 @@ const translations = {
cancel: "Anuluj", cancel: "Anuluj",
resetAll: "Resetuj wszystko", 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 // Dynamic content
loadingFallbackText: "Nie można załadować lekcji. Wybierz jedną z menu lub sprawdź pomoc.", loadingFallbackText: "Nie można załadować lekcji. Wybierz jedną z menu lub sprawdź pomoc.",
completed: "Ukończono", completed: "Ukończono",
@@ -351,13 +373,15 @@ const translations = {
// Help dialog // Help dialog
helpTitle: "Ayuda", helpTitle: "Ayuda",
aboutTitle: "Acerca de Code Crispies", 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", learningModesTitle: "Modos de aprendizaje",
modeCss: "<strong>CSS</strong> - Escribe reglas CSS para estilizar elementos", modeCss: "<strong>CSS</strong> - Escribe reglas CSS para estilizar elementos",
modeTailwind: "<strong>Tailwind</strong> - Aplica clases de utilidad directamente en HTML", modeTailwind: "<strong>Tailwind</strong> - Aplica clases de utilidad directamente en HTML",
modeHtml: "<strong>HTML</strong> - Practica marcado semántico y elementos nativos", modeHtml: "<strong>HTML</strong> - Practica marcado semántico y elementos nativos",
gettingStartedTitle: "Primeros pasos", 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", completingLessonsTitle: "Completar lecciones",
completingStep1: "Lee las instrucciones de la tarea a la izquierda", completingStep1: "Lee las instrucciones de la tarea a la izquierda",
completingStep2: "Escribe tu código en el editor", completingStep2: "Escribe tu código en el editor",
@@ -386,7 +410,7 @@ const translations = {
// Contact // Contact
contactTitle: "Contacto y enlaces", contactTitle: "Contacto y enlaces",
contactText: "Code Crispies es desarrollado por <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>", contactText: 'Code Crispies es desarrollado por <a href="https://librete.ch" target="_blank">LibreTECH</a>',
// Reset dialog // Reset dialog
resetDialogTitle: "Reiniciar progreso", resetDialogTitle: "Reiniciar progreso",
@@ -394,6 +418,12 @@ const translations = {
cancel: "Cancelar", cancel: "Cancelar",
resetAll: "Reiniciar todo", 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 // Dynamic content
loadingFallbackText: "No se pudo cargar la lección. Selecciona una del menú o consulta la ayuda.", loadingFallbackText: "No se pudo cargar la lección. Selecciona una del menú o consulta la ayuda.",
completed: "Completado", completed: "Completado",
@@ -487,7 +517,7 @@ const translations = {
// Contact // Contact
contactTitle: "التواصل والروابط", contactTitle: "التواصل والروابط",
contactText: "Code Crispies تم تطويره بواسطة <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>", contactText: 'Code Crispies تم تطويره بواسطة <a href="https://librete.ch" target="_blank">LibreTECH</a>',
// Reset dialog // Reset dialog
resetDialogTitle: "إعادة تعيين التقدم", resetDialogTitle: "إعادة تعيين التقدم",
@@ -495,6 +525,12 @@ const translations = {
cancel: "إلغاء", cancel: "إلغاء",
resetAll: "إعادة تعيين الكل", resetAll: "إعادة تعيين الكل",
// Reset code dialog
resetCodeDialogTitle: "إعادة تعيين الكود",
resetCodeDialogText: "استعادة الكود إلى الحالة الأولية لهذا الدرس؟",
dontShowAgain: "لا تظهر هذا مرة أخرى",
reset: "إعادة تعيين",
// Dynamic content // Dynamic content
loadingFallbackText: "تعذر تحميل الدرس. اختر واحدًا من القائمة أو تحقق من المساعدة.", loadingFallbackText: "تعذر تحميل الدرس. اختر واحدًا من القائمة أو تحقق من المساعدة.",
completed: "مكتمل", completed: "مكتمل",
@@ -553,7 +589,8 @@ const translations = {
// Help dialog // Help dialog
helpTitle: "Допомога", helpTitle: "Допомога",
aboutTitle: "Про Code Crispies", aboutTitle: "Про Code Crispies",
aboutText: "Code Crispies — це безкоштовна платформа з відкритим кодом для вивчення веб-розробки через практичні вправи. Обліковий запис не потрібен — просто починайте кодувати!", aboutText:
"Code Crispies — це безкоштовна платформа з відкритим кодом для вивчення веб-розробки через практичні вправи. Обліковий запис не потрібен — просто починайте кодувати!",
learningModesTitle: "Режими навчання", learningModesTitle: "Режими навчання",
modeCss: "<strong>CSS</strong> - Пишіть правила CSS для стилізації елементів", modeCss: "<strong>CSS</strong> - Пишіть правила CSS для стилізації елементів",
modeTailwind: "<strong>Tailwind</strong> - Застосовуйте утилітарні класи безпосередньо в HTML", modeTailwind: "<strong>Tailwind</strong> - Застосовуйте утилітарні класи безпосередньо в HTML",
@@ -588,7 +625,7 @@ const translations = {
// Contact // Contact
contactTitle: "Контакти та посилання", contactTitle: "Контакти та посилання",
contactText: "Code Crispies розроблено <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>", contactText: 'Code Crispies розроблено <a href="https://librete.ch" target="_blank">LibreTECH</a>',
// Reset dialog // Reset dialog
resetDialogTitle: "Скинути прогрес", resetDialogTitle: "Скинути прогрес",
@@ -596,6 +633,12 @@ const translations = {
cancel: "Скасувати", cancel: "Скасувати",
resetAll: "Скинути все", resetAll: "Скинути все",
// Reset code dialog
resetCodeDialogTitle: "Скинути код",
resetCodeDialogText: "Відновити код до початкового стану цього уроку?",
dontShowAgain: "Більше не показувати",
reset: "Скинути",
// Dynamic content // Dynamic content
loadingFallbackText: "Не вдалося завантажити урок. Виберіть один з меню або перевірте допомогу.", loadingFallbackText: "Не вдалося завантажити урок. Виберіть один з меню або перевірте допомогу.",
completed: "Завершено", completed: "Завершено",

View File

@@ -12,7 +12,8 @@ import { autocompletion } from "@codemirror/autocomplete";
import { abbreviationTracker, expandAbbreviation } from "@emmetio/codemirror6-plugin"; import { abbreviationTracker, expandAbbreviation } from "@emmetio/codemirror6-plugin";
// Custom overrides for One Dark theme // Custom overrides for One Dark theme
const editorTheme = EditorView.theme({ const editorTheme = EditorView.theme(
{
"&": { "&": {
height: "100%", height: "100%",
fontSize: "14px" fontSize: "14px"
@@ -24,7 +25,9 @@ const editorTheme = EditorView.theme({
".cm-line": { ".cm-line": {
padding: "0 12px" padding: "0 12px"
} }
}, { dark: true }); },
{ dark: true }
);
export class CodeEditor { export class CodeEditor {
constructor(container, options = {}) { constructor(container, options = {}) {
@@ -55,19 +58,16 @@ export class CodeEditor {
// Emmet abbreviation tracking // Emmet abbreviation tracking
abbreviationTracker(), abbreviationTracker(),
// High priority keymap for Emmet // High priority keymap for Emmet
Prec.highest(keymap.of([ Prec.highest(
keymap.of([
{ {
key: "Tab", key: "Tab",
run: expandAbbreviation run: expandAbbreviation
} }
])), ])
),
// Standard keymaps including history (Ctrl+Z, Ctrl+Shift+Z) // Standard keymaps including history (Ctrl+Z, Ctrl+Shift+Z)
keymap.of([ keymap.of([...historyKeymap, { key: "Tab", run: indentMore }, { key: "Shift-Tab", run: indentLess }, ...defaultKeymap]),
...historyKeymap,
{ key: "Tab", run: indentMore },
{ key: "Shift-Tab", run: indentLess },
...defaultKeymap
]),
autocompletion({ autocompletion({
activateOnTyping: true, activateOnTyping: true,
maxRenderedOptions: 10 maxRenderedOptions: 10

View File

@@ -4,7 +4,10 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="./favicon.ico" type="image/x-icon" /> <link rel="icon" href="./favicon.ico" type="image/x-icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Code Crispies - Free, open-source platform for learning HTML and CSS through hands-on exercises. Master semantic markup, selectors, flexbox, animations and more." /> <meta
name="description"
content="Code Crispies - Free, open-source platform for learning HTML and CSS through hands-on exercises. Master semantic markup, selectors, flexbox, animations and more."
/>
<title>Code Crispies - Learn HTML & CSS Interactively</title> <title>Code Crispies - Learn HTML & CSS Interactively</title>
<link rel="stylesheet" href="main.css" /> <link rel="stylesheet" href="main.css" />
</head> </head>
@@ -41,15 +44,20 @@
<div class="editor-tools"> <div class="editor-tools">
<button id="undo-btn" class="btn btn-icon" data-i18n-title="undoTitle" title="Undo (Ctrl+Z)"></button> <button id="undo-btn" class="btn btn-icon" data-i18n-title="undoTitle" title="Undo (Ctrl+Z)"></button>
<button id="redo-btn" class="btn btn-icon" data-i18n-title="redoTitle" title="Redo (Ctrl+Shift+Z)"></button> <button id="redo-btn" class="btn btn-icon" data-i18n-title="redoTitle" title="Redo (Ctrl+Shift+Z)"></button>
<button id="reset-code-btn" class="btn btn-icon" data-i18n-title="resetCodeTitle" title="Reset to initial code"></button> <button
</div> id="reset-code-btn"
<button id="run-btn" class="btn btn-run"> class="btn btn-icon"
<img src="./gear.svg" alt="" /><span data-i18n="run">Run</span> data-i18n-title="resetCodeTitle"
title="Reset to initial code"
>
</button> </button>
</div> </div>
<button id="run-btn" class="btn btn-run"><img src="./gear.svg" alt="" /><span data-i18n="run">Run</span></button>
</div>
</div> </div>
<div class="editor-content"> <div class="editor-content">
<textarea id="code-input" class="code-input" spellcheck="false" autocomplete="off" style="display: none;"></textarea> <textarea id="code-input" class="code-input" spellcheck="false" autocomplete="off" style="display: none"></textarea>
</div> </div>
</div> </div>
<div class="hint-area" id="hint-area"></div> <div class="hint-area" id="hint-area"></div>
@@ -142,7 +150,10 @@
</div> </div>
<div class="dialog-content"> <div class="dialog-content">
<h4 data-i18n="aboutTitle">About Code Crispies</h4> <h4 data-i18n="aboutTitle">About Code Crispies</h4>
<p data-i18n="aboutText">Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required - just start coding!</p> <p data-i18n="aboutText">
Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required -
just start coding!
</p>
<h4 data-i18n="learningModesTitle">Learning Modes</h4> <h4 data-i18n="learningModesTitle">Learning Modes</h4>
<ul> <ul>
@@ -152,7 +163,9 @@
</ul> </ul>
<h4 data-i18n="gettingStartedTitle">Getting Started</h4> <h4 data-i18n="gettingStartedTitle">Getting Started</h4>
<p data-i18n="gettingStartedText">Open the menu (☰) to browse lesson modules. Each module covers a specific topic with progressive exercises.</p> <p data-i18n="gettingStartedText">
Open the menu (☰) to browse lesson modules. Each module covers a specific topic with progressive exercises.
</p>
<h4 data-i18n="completingLessonsTitle">Completing Lessons</h4> <h4 data-i18n="completingLessonsTitle">Completing Lessons</h4>
<ol> <ol>
@@ -207,6 +220,26 @@
</div> </div>
</dialog> </dialog>
<!-- Reset Code Confirmation Dialog -->
<dialog id="reset-code-dialog" class="dialog">
<div class="dialog-header">
<h3 data-i18n="resetCodeDialogTitle">Reset Code</h3>
<button id="reset-code-dialog-close" class="dialog-close" aria-label="Close">&times;</button>
</div>
<div class="dialog-content">
<p data-i18n="resetCodeDialogText">Reset your code to the initial state for this lesson?</p>
<label class="toggle-switch" style="margin: 1rem 0">
<input type="checkbox" id="reset-code-dont-show" />
<span class="toggle-slider"></span>
<span class="toggle-label" data-i18n="dontShowAgain">Don't show this again</span>
</label>
<div class="dialog-actions">
<button id="cancel-reset-code" class="btn" data-i18n="cancel">Cancel</button>
<button id="confirm-reset-code" class="btn btn-ghost" data-i18n="reset">Reset</button>
</div>
</div>
</dialog>
<!-- Reset Confirmation Dialog --> <!-- Reset Confirmation Dialog -->
<dialog id="reset-dialog" class="dialog"> <dialog id="reset-dialog" class="dialog">
<div class="dialog-header"> <div class="dialog-header">

View File

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