import { LessonEngine } from "./impl/LessonEngine.js";
import { CodeEditor } from "./impl/CodeEditor.js";
import { renderLesson, renderModuleList, renderLevelIndicator, updateActiveLessonInSidebar } from "./helpers/renderer.js";
import { loadModules } from "./config/lessons.js";
import { initI18n, t, getLanguage, setLanguage, applyTranslations } from "./i18n.js";
import { parseHash, updateHash, replaceHash, getShareableUrl, RouteType, navigateTo } from "./helpers/router.js";
import { sections, getSection, getModuleSection, getModulesBySection } from "./config/sections.js";
import { getRandomTemplate } from "./config/playground-templates.js";
// CodeMirror imports for syntax highlighting
import { EditorState } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import { oneDark } from "@codemirror/theme-one-dark";
import { html } from "@codemirror/lang-html";
import { css } from "@codemirror/lang-css";
// Simplified state - LessonEngine now manages lesson state and progress
const state = {
userSettings: {
disableFeedbackErrors: false,
skipResetCodeConfirmation: false
},
showExpected: false,
animationTimeout: null
};
// Track CodeMirror views for cleanup
let sectionCodeViews = [];
// Read-only CodeMirror theme for code examples
const readOnlyTheme = EditorView.theme(
{
"&": {
fontSize: "13px"
},
".cm-content": {
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
padding: "12px 0"
},
".cm-line": {
padding: "0 12px"
},
".cm-gutters": {
display: "none"
}
},
{ dark: true }
);
/**
* Highlight all code blocks in the section page using CodeMirror
*/
function highlightSectionCodeBlocks() {
// Clean up previous views
sectionCodeViews.forEach((view) => view.destroy());
sectionCodeViews = [];
// Find all code blocks in section page
const codeBlocks = elements.sectionIntro?.querySelectorAll(".code-block") || [];
codeBlocks.forEach((block) => {
const pre = block.querySelector("pre");
const code = block.querySelector("code");
if (!pre || !code) return;
const content = code.textContent || "";
// Detect language from content
const isHTML = content.includes("<") && content.includes(">");
const langExtension = isHTML ? html() : css();
// Create read-only CodeMirror view
const state = EditorState.create({
doc: content,
extensions: [langExtension, oneDark, readOnlyTheme, EditorState.readOnly.of(true), EditorView.lineWrapping]
});
const view = new EditorView({
state,
parent: block
});
// Remove original pre/code
pre.remove();
sectionCodeViews.push(view);
});
}
// DOM elements - updated for new layout
const elements = {
// Header
menuBtn: document.getElementById("menu-btn"),
logoLink: document.getElementById("logo-link"),
langSelect: document.getElementById("lang-select"),
helpBtn: document.getElementById("help-btn"),
mainNav: document.getElementById("main-nav"),
// Page containers
landingPage: document.getElementById("landing-page"),
sectionPage: document.getElementById("section-page"),
gameLayout: document.getElementById("main-content"),
// Section page elements
sectionTitle: document.getElementById("section-title"),
sectionDescription: document.getElementById("section-description"),
sectionProgressFill: document.getElementById("section-progress-fill"),
sectionProgressText: document.getElementById("section-progress-text"),
sectionIntro: document.getElementById("section-intro"),
// Left panel
instructionsSection: document.querySelector(".instructions"),
editorSection: document.querySelector(".editor-section"),
modulePill: document.getElementById("module-pill"),
moduleName: document.querySelector(".module-name"),
lessonTitle: document.getElementById("lesson-title"),
lessonTitleRow: document.querySelector(".lesson-title-row"),
lessonDescription: document.getElementById("lesson-description"),
taskInstruction: document.getElementById("task-instruction"),
codeInput: document.getElementById("code-input"),
runBtn: document.getElementById("run-btn"),
undoBtn: document.getElementById("undo-btn"),
redoBtn: document.getElementById("redo-btn"),
resetCodeBtn: document.getElementById("reset-code-btn"),
randomTemplateBtn: document.getElementById("random-template-btn"),
hintArea: document.getElementById("hint-area"),
editorContent: document.querySelector(".editor-content"),
codeEditor: document.querySelector(".code-editor"),
// Right panel
previewArea: document.getElementById("preview-area"),
showExpectedBtn: document.getElementById("show-expected-btn"),
expectedOverlay: document.getElementById("expected-overlay"),
previewWrapper: document.querySelector(".preview-wrapper"),
previewSection: document.querySelector(".preview-section"),
prevBtn: document.getElementById("prev-btn"),
nextBtn: document.getElementById("next-btn"),
levelIndicator: document.getElementById("level-indicator"),
headerLevelPill: document.getElementById("header-level-pill"),
// Sidebar
sidebarDrawer: document.getElementById("sidebar-drawer"),
sidebarBackdrop: document.getElementById("sidebar-backdrop"),
closeSidebar: document.getElementById("close-sidebar"),
moduleList: document.getElementById("module-list"),
progressFill: document.getElementById("progress-fill"),
progressText: document.getElementById("progress-text"),
resetBtn: document.getElementById("reset-btn"),
disableFeedbackToggle: document.getElementById("disable-feedback-toggle"),
// Dialogs
helpDialog: document.getElementById("help-dialog"),
helpDialogClose: document.getElementById("help-dialog-close"),
resetDialog: document.getElementById("reset-dialog"),
resetDialogClose: document.getElementById("reset-dialog-close"),
cancelReset: document.getElementById("cancel-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"),
// Share dialog
shareBtn: document.getElementById("share-btn"),
shareDialog: document.getElementById("share-dialog"),
shareDialogClose: document.getElementById("share-dialog-close"),
shareUrlInput: document.getElementById("share-url-input"),
copyUrlBtn: document.getElementById("copy-url-btn"),
copyFeedback: document.getElementById("copy-feedback")
};
// Initialize the lesson engine - now the single source of truth
const lessonEngine = new LessonEngine();
// Code editor instance (initialized later)
let codeEditor = null;
let currentMode = "css";
// ================= SIDEBAR FUNCTIONS =================
// Track element that opened sidebar for focus return
let sidebarTrigger = null;
function openSidebar() {
// Store trigger element for focus return
sidebarTrigger = document.activeElement;
elements.sidebarDrawer.classList.add("open");
elements.sidebarBackdrop.classList.add("visible");
// Move focus to close button for keyboard users
elements.closeSidebar.focus();
}
function closeSidebar() {
elements.sidebarDrawer.classList.remove("open");
elements.sidebarBackdrop.classList.remove("visible");
// Return focus to trigger element
if (sidebarTrigger && typeof sidebarTrigger.focus === "function") {
sidebarTrigger.focus();
sidebarTrigger = null;
}
}
// ================= EXPECTED RESULT TOGGLE =================
function toggleExpectedResult() {
state.showExpected = !state.showExpected;
if (state.showExpected) {
elements.expectedOverlay.classList.add("visible");
elements.showExpectedBtn.textContent = t("hideExpected");
elements.showExpectedBtn.classList.add("btn-primary");
} else {
elements.expectedOverlay.classList.remove("visible");
elements.showExpectedBtn.textContent = t("showExpected");
elements.showExpectedBtn.classList.remove("btn-primary");
}
}
// ================= LANGUAGE TOGGLE =================
function changeLanguage(newLang) {
// Add transition class before any updates
elements.editorSection?.classList.add("transitioning");
setLanguage(newLang);
applyTranslations();
// Reload lessons in new language
const engineState = lessonEngine.getCurrentState();
const currentModuleId = engineState.module?.id;
const currentLessonIndex = engineState.lessonIndex;
const modules = loadModules(newLang);
lessonEngine.setModules(modules);
renderModuleList(elements.moduleList, modules, selectModule, selectLesson);
// Restore position in current module/lesson
if (currentModuleId) {
lessonEngine.setModuleById(currentModuleId);
lessonEngine.setLessonByIndex(currentLessonIndex);
loadCurrentLesson();
}
updateProgressDisplay();
// Remove transition class after all updates
requestAnimationFrame(() => {
elements.editorSection?.classList.remove("transitioning");
});
}
// ================= HINT SYSTEM =================
function showHint(message, step, total, isSuccess = false) {
const hintClass = isSuccess ? "hint hint-success" : "hint";
elements.hintArea.innerHTML = `
${step}/${total}${message}
`;
}
function clearHint() {
elements.hintArea.innerHTML = "";
}
function showSuccessHint(message) {
elements.hintArea.innerHTML = `
✓${message}
`;
}
// ================= PROGRESS DISPLAY =================
function updateProgressDisplay() {
const stats = lessonEngine.getProgressStats();
elements.progressFill.style.width = `${stats.percentComplete}%`;
elements.progressText.textContent = t("progressText", {
percent: stats.percentComplete,
completed: stats.totalCompleted,
total: stats.totalLessons
});
}
// ================= USER SETTINGS =================
function loadUserSettings() {
const savedSettings = localStorage.getItem("codeCrispies.settings");
if (savedSettings) {
try {
const settings = JSON.parse(savedSettings);
state.userSettings = { ...state.userSettings, ...settings };
elements.disableFeedbackToggle.checked = !state.userSettings.disableFeedbackErrors;
} catch (e) {
console.error("Error loading user settings:", e);
}
}
}
function saveUserSettings() {
localStorage.setItem("codeCrispies.settings", JSON.stringify(state.userSettings));
}
// ================= LESSON CACHE =================
let cachedUserCode = null;
function restoreLessonCache() {
try {
const cached = localStorage.getItem("codeCrispies.lessonCache");
if (cached) {
const data = JSON.parse(cached);
if (data.moduleTitle && elements.moduleName) {
elements.moduleName.textContent = data.moduleTitle;
// Remove data-i18n so applyTranslations won't overwrite
elements.moduleName.removeAttribute("data-i18n");
}
if (data.lessonTitle && elements.lessonTitle) {
elements.lessonTitle.textContent = data.lessonTitle;
elements.lessonTitle.removeAttribute("data-i18n");
}
if (data.lessonDescription && elements.lessonDescription) {
elements.lessonDescription.innerHTML = data.lessonDescription;
}
if (data.taskInstruction && elements.taskInstruction) {
elements.taskInstruction.innerHTML = data.taskInstruction;
}
if (data.levelIndicator && elements.levelIndicator) {
elements.levelIndicator.innerHTML = data.levelIndicator;
}
// Store userCode to apply after editor init
if (data.userCode) {
cachedUserCode = data.userCode;
}
}
} catch (e) {
// Ignore cache errors
}
}
// ================= MODULE INITIALIZATION =================
let loadingTimeout = null;
function showLoadingFallback() {
// Only show if no lesson is loaded yet
if (!elements.lessonTitle.textContent) {
elements.lessonDescription.innerHTML = `
${t("loadingFallbackText")}
`;
}
}
function clearLoadingTimeout() {
if (loadingTimeout) {
clearTimeout(loadingTimeout);
loadingTimeout = null;
}
}
function initializeModules() {
try {
const modules = loadModules(getLanguage());
lessonEngine.setModules(modules);
// Use the new renderModuleList function with both callbacks
renderModuleList(elements.moduleList, modules, selectModule, selectLesson);
// Handle route (home, section, or lesson)
handleRoute(false);
updateProgressDisplay();
clearLoadingTimeout();
} catch (error) {
console.error("Failed to load modules:", error);
showLoadingFallback();
}
}
// ================= MODULE/LESSON SELECTION =================
function selectModule(moduleId) {
const success = lessonEngine.setModuleById(moduleId);
if (!success) return;
// Show lesson UI
showLessonUI();
// Update URL
const engineState = lessonEngine.getCurrentState();
updateHash(moduleId, engineState.lessonIndex);
// Update module list UI to highlight the active module
const moduleItems = elements.moduleList.querySelectorAll(".module-header");
moduleItems.forEach((item) => {
item.classList.remove("active");
if (item.dataset.moduleId === moduleId) {
item.classList.add("active");
}
});
loadCurrentLesson();
resetSuccessIndicators();
// Close sidebar after selection on mobile
if (window.innerWidth <= 768) {
closeSidebar();
}
}
function selectLesson(moduleId, lessonIndex) {
const currentState = lessonEngine.getCurrentState();
if (!currentState.module || currentState.module.id !== moduleId) {
lessonEngine.setModuleById(moduleId);
}
lessonEngine.setLessonByIndex(lessonIndex);
// Show lesson UI
showLessonUI();
// Update URL
updateHash(moduleId, lessonIndex);
loadCurrentLesson();
// Close sidebar after selection on mobile
if (window.innerWidth <= 768) {
closeSidebar();
}
}
// ================= LESSON LOADING =================
function resetSuccessIndicators() {
// Clear any pending animation timeout
if (state.animationTimeout) {
clearTimeout(state.animationTimeout);
state.animationTimeout = null;
}
elements.codeEditor.classList.remove("success-highlight");
elements.lessonTitle.classList.remove("success-text");
elements.nextBtn.classList.remove("success");
elements.taskInstruction.classList.remove("success-instruction");
elements.runBtn.classList.remove("success");
elements.previewWrapper?.classList.remove("matched");
elements.previewWrapper?.classList.remove("completed-glow");
elements.previewSection?.classList.remove("matched");
}
function updateEditorForMode(mode) {
const editorLabel = document.querySelector(".editor-label");
const modeConfig = {
html: {
placeholder: "Type HTML here... Try: nav>ul>li*3 then press Tab",
label: "HTML Editor",
cmMode: "html"
},
tailwind: {
placeholder: t("tailwindPlaceholder"),
label: "Tailwind Classes",
cmMode: "css"
},
css: {
placeholder: "Enter your CSS code here...",
label: "CSS Editor",
cmMode: "css"
},
playground: {
placeholder: "\n\n",
label: "HTML & CSS",
cmMode: "html"
}
};
const config = modeConfig[mode] || modeConfig.css;
if (editorLabel) editorLabel.textContent = config.label;
// Update CodeMirror mode if needed
if (codeEditor && currentMode !== config.cmMode) {
currentMode = config.cmMode;
codeEditor.setMode(config.cmMode);
}
}
function loadCurrentLesson() {
const engineState = lessonEngine.getCurrentState();
if (!engineState.module || !engineState.lesson) {
return;
}
const lesson = engineState.lesson;
const mode = lesson.mode || engineState.module?.mode || "css";
const isPlayground = lesson.mode === "playground";
// Handle playground mode - hide instructions, full height editor, show random button
if (isPlayground) {
elements.instructionsSection?.classList.add("hidden");
elements.editorSection?.classList.add("playground-mode");
elements.randomTemplateBtn?.classList.remove("hidden");
} else {
elements.instructionsSection?.classList.remove("hidden");
elements.editorSection?.classList.remove("playground-mode");
elements.randomTemplateBtn?.classList.add("hidden");
}
// Add transition class for smooth content swap
elements.editorSection?.classList.add("transitioning");
// Update UI based on mode
updateEditorForMode(mode);
// Update module name in pill
if (elements.moduleName && engineState.module) {
elements.moduleName.textContent = engineState.module.title;
}
// Reset any success indicators
resetSuccessIndicators();
// Clear hints
clearHint();
// Hide expected overlay
state.showExpected = false;
elements.expectedOverlay.classList.remove("visible");
elements.showExpectedBtn.textContent = t("showExpected");
elements.showExpectedBtn.classList.remove("btn-primary");
// Update UI
renderLesson(
elements.lessonTitle,
elements.lessonDescription,
elements.taskInstruction,
elements.previewArea,
null, // editorPrefix no longer used
null, // codeInput no longer used (using CodeMirror)
null, // editorSuffix no longer used
lesson
);
// Set user code in CodeMirror
if (codeEditor) {
codeEditor.setValue(engineState.userCode);
}
// Update Run button text based on completion status
if (engineState.isCompleted) {
elements.runBtn.querySelector("span").textContent = t("rerun");
// Add completion badge if not present
if (!document.querySelector(".completion-badge")) {
const badge = document.createElement("span");
badge.className = "completion-badge";
badge.textContent = t("completed");
elements.lessonTitleRow.appendChild(badge);
}
// Show gradient border for completed lessons
elements.previewWrapper?.classList.add("completed-glow");
} else {
elements.runBtn.querySelector("span").textContent = t("run");
// Remove completion badge and border if exists
const badge = document.querySelector(".completion-badge");
if (badge) badge.remove();
elements.previewWrapper?.classList.remove("completed-glow");
}
// Update level indicator
renderLevelIndicator(elements.levelIndicator, engineState.lessonIndex + 1, engineState.totalLessons);
// Header pill shows module name + level (clickable link to return to lesson)
if (elements.headerLevelPill && engineState.module) {
const label = t("lessonLabel");
elements.headerLevelPill.innerHTML = `${engineState.module.title}${label} ${engineState.lessonIndex + 1} / ${engineState.totalLessons}`;
elements.headerLevelPill.href = `#${engineState.module.id}/${engineState.lessonIndex}`;
}
// Update active lesson in sidebar
updateActiveLessonInSidebar(engineState.module.id, engineState.lessonIndex);
// Update navigation buttons
updateNavigationButtons();
// Update progress display
updateProgressDisplay();
// Focus on the code editor
if (codeEditor) {
codeEditor.focus();
}
// Render the expected/solution preview
lessonEngine.renderExpectedPreview();
// Remove transition class after content is updated
requestAnimationFrame(() => {
elements.editorSection?.classList.remove("transitioning");
});
// Cache lesson display data for instant restore on reload
try {
localStorage.setItem(
"codeCrispies.lessonCache",
JSON.stringify({
moduleTitle: engineState.module?.title,
lessonTitle: lesson.title,
lessonDescription: lesson.description,
taskInstruction: lesson.task,
levelIndicator: elements.levelIndicator?.innerHTML,
userCode: engineState.userCode,
mode: mode
})
);
} catch (e) {
// Ignore storage errors
}
}
// ================= LIVE PREVIEW =================
let previewTimer = null;
function handleEditorChange(code) {
if (previewTimer) {
clearTimeout(previewTimer);
}
previewTimer = setTimeout(() => {
runCode();
}, 800);
}
// ================= NAVIGATION =================
function updateNavigationButtons() {
const engineState = lessonEngine.getCurrentState();
elements.prevBtn.disabled = !engineState.canGoPrev;
elements.nextBtn.disabled = !engineState.canGoNext;
elements.prevBtn.classList.toggle("btn-disabled", !engineState.canGoPrev);
elements.nextBtn.classList.toggle("btn-disabled", !engineState.canGoNext);
}
function nextLesson() {
const prevModuleId = lessonEngine.getCurrentState().module?.id;
const success = lessonEngine.nextLesson();
if (success) {
const newState = lessonEngine.getCurrentState();
// Update URL
updateHash(newState.module.id, newState.lessonIndex);
if (newState.module.id !== prevModuleId) {
updateModuleHighlight(newState.module.id);
}
loadCurrentLesson();
}
}
function prevLesson() {
const prevModuleId = lessonEngine.getCurrentState().module?.id;
const success = lessonEngine.previousLesson();
if (success) {
const newState = lessonEngine.getCurrentState();
// Update URL
updateHash(newState.module.id, newState.lessonIndex);
if (newState.module.id !== prevModuleId) {
updateModuleHighlight(newState.module.id);
}
loadCurrentLesson();
}
}
function updateModuleHighlight(moduleId) {
const moduleItems = elements.moduleList.querySelectorAll(".module-header");
moduleItems.forEach((item) => {
item.classList.remove("active");
if (item.dataset.moduleId === moduleId) {
item.classList.add("active");
}
});
}
// ================= CODE EXECUTION =================
function resetCode() {
// Reset editor to initial code for current lesson
lessonEngine.reset();
const engineState = lessonEngine.getCurrentState();
if (codeEditor && engineState.lesson) {
codeEditor.setValue(engineState.lesson.initialCode || "");
}
// Clear hints and success indicators
clearHint();
resetSuccessIndicators();
}
function loadRandomTemplate() {
const template = getRandomTemplate();
if (codeEditor && template) {
codeEditor.setValue(template.code);
// Apply the code to the preview
lessonEngine.applyUserCode(template.code, true);
}
}
function runCode() {
const userCode = codeEditor ? codeEditor.getValue() : "";
const engineState = lessonEngine.getCurrentState();
const isPlayground = engineState.lesson?.mode === "playground";
// Rotate the Run button icon
const runButtonImg = document.querySelector("#run-btn img");
if (runButtonImg) {
const currentRotation = parseInt(runButtonImg.style.transform?.match(/\d+/)?.[0] || "0");
runButtonImg.style.transform = `rotate(${currentRotation + 180}deg)`;
}
// Apply the code to the preview via LessonEngine
lessonEngine.applyUserCode(userCode, true);
// Skip validation for playground mode
if (isPlayground) {
return;
}
// Validate code using LessonEngine
const validationResult = lessonEngine.validateCode();
if (validationResult.isValid) {
// Show success hint
showSuccessHint(validationResult.message || t("successMessage"));
// Update Run button
elements.runBtn.querySelector("span").textContent = t("rerun");
elements.runBtn.classList.add("success");
// Add completion badge
if (!document.querySelector(".completion-badge")) {
const badge = document.createElement("span");
badge.className = "completion-badge";
badge.textContent = t("completed");
elements.lessonTitleRow.appendChild(badge);
}
// Add success visual indicators
elements.codeEditor.classList.add("success-highlight");
elements.lessonTitle.classList.add("success-text");
elements.nextBtn.classList.add("success");
elements.taskInstruction.classList.add("success-instruction");
// Show match animation (rotating gradient glow)
const crispyQuotes = [
"Crissssssssspy!",
"You did it!",
"Good job!",
"Nailed it!",
"Perfect!",
"Well done!",
"Awesome!",
"Nice work!",
//"0x2B 0x31",
"+1"
];
const randomQuote = crispyQuotes[Math.floor(Math.random() * crispyQuotes.length)];
elements.previewWrapper?.style.setProperty("--crispy-quote", `"${randomQuote}"`);
elements.previewWrapper?.classList.add("matched");
elements.previewSection?.classList.add("matched");
state.animationTimeout = setTimeout(() => {
elements.previewWrapper?.classList.remove("matched");
elements.previewSection?.classList.remove("matched");
// Keep the gradient border visible after animation
elements.previewWrapper?.classList.add("completed-glow");
state.animationTimeout = null;
}, 3500);
updateNavigationButtons();
updateProgressDisplay();
} else {
// Reset success indicators
resetSuccessIndicators();
// Show hint with step progress
const step = validationResult.validCases + 1;
const total = validationResult.totalCases;
// Only show hints if enabled
if (!state.userSettings.disableFeedbackErrors) {
showHint(validationResult.message || t("keepTrying"), step, total);
}
}
}
// ================= DIALOGS =================
function showHelp() {
elements.helpDialog.showModal();
}
function closeHelpDialog() {
elements.helpDialog.close();
}
function showResetConfirmation() {
elements.resetDialog.showModal();
}
function closeResetDialog() {
elements.resetDialog.close();
}
function handleResetConfirm() {
lessonEngine.clearProgress();
closeResetDialog();
closeSidebar();
// Reload first module
const modules = lessonEngine.modules;
if (modules.length > 0) {
selectModule(modules[0].id);
}
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();
}
}
// ================= SHARE DIALOG =================
function showShareDialog() {
const engineState = lessonEngine.getCurrentState();
if (engineState.module && engineState.lesson !== null) {
const shareUrl = getShareableUrl(engineState.module.id, engineState.lessonIndex);
elements.shareUrlInput.value = shareUrl;
elements.copyFeedback.hidden = true;
}
elements.shareDialog.showModal();
}
function closeShareDialog() {
elements.shareDialog.close();
}
async function copyShareUrl() {
try {
await navigator.clipboard.writeText(elements.shareUrlInput.value);
elements.copyFeedback.hidden = false;
setTimeout(() => {
elements.copyFeedback.hidden = true;
}, 2000);
} catch (err) {
// Fallback for older browsers
elements.shareUrlInput.select();
document.execCommand("copy");
elements.copyFeedback.hidden = false;
setTimeout(() => {
elements.copyFeedback.hidden = true;
}, 2000);
}
}
// ================= SECTION EDUCATIONAL CONTENT =================
const sectionContent = {
css: `
CSS (Cascading Style Sheets) is a stylesheet language that controls the visual presentation of HTML documents. While HTML defines the structure and content, CSS handles colors, typography, spacing, and layout. The "cascading" in CSS means rules can override each other based on specificity—allowing you to set defaults and then refine them for specific elements.
Introduced in 1996 to separate content from presentation, CSS enables one stylesheet to style multiple HTML pages, keeping design consistent and maintainable. Modern CSS includes powerful layout systems like Flexbox and Grid, custom properties (variables), and animations—all without JavaScript.
Selectors & Properties
CSS uses selectors to target HTML elements and apply styles. The most common selector is the class selector (.classname), which targets elements with a specific class attribute. You can also use element selectors (p, div), ID selectors (#id), and combinators to select nested elements.
Properties define what aspect of the element to style. Common properties include color for text color, background for backgrounds, padding for internal spacing, and margin for external spacing. Each property accepts specific value types like colors, lengths, or keywords.
Every HTML element is rendered as a rectangular box with four distinct layers. The content area holds your text or images. padding creates space inside the element between the content and border. The border wraps around the padding. Finally, margin creates space outside the element, separating it from neighbors.
By default, width only sets the content width. Adding padding and border increases the total size. Use box-sizing: border-box to include padding and border in the declared width, making layouts much more predictable.
Flexbox is a one-dimensional layout system for arranging items in rows or columns. Apply display: flex to a container to enable it. Child elements become flex items that can grow, shrink, and align automatically.
Control alignment with justify-content (main axis: flex-start, center, space-between) and align-items (cross axis: stretch, center, flex-end). The gap property adds consistent spacing between items without margins.
CSS Grid is a two-dimensional layout system for creating complex row and column layouts. Enable it with display: grid, then define columns using grid-template-columns. The repeat() function creates multiple tracks, and fr units distribute available space proportionally.
Grid excels at page layouts and card grids. Use grid-template-columns: repeat(3, 1fr) for three equal columns, or repeat(auto-fill, minmax(250px, 1fr)) for responsive columns that wrap automatically.
CSS supports multiple unit types for different use cases. Use px for fixed sizes, rem for scalable typography (relative to root font size), % for parent-relative sizing, and vh/vw for viewport-relative dimensions. Prefer rem for accessibility—it respects user font preferences.
CSS custom properties (variables) store reusable values. Define them with --name: value in :root for global access, then use them anywhere with var(--name). This makes themes and consistent design systems easy to maintain.
HTML (HyperText Markup Language) is not a programming language—it's a markup language that describes the structure and content of web documents. Invented by Tim Berners-Lee in 1989, HTML uses tags like <p>, <h1>, and <a> to define paragraphs, headings, and links. The browser reads this markup and renders it as a visual page.
HTML documents form a hierarchical tree called the DOM (Document Object Model). Elements have parent-child relationships: a <ul> contains <li> children, a <form> contains <input> elements. Modern HTML5 includes native interactive elements like <dialog>, <details>, and form validation—features that previously required JavaScript.
Semantic Structure
HTML5 introduced semantic elements that convey meaning about content structure. Use <header> for introductory content, <nav> for navigation links, <main> for primary content, <article> for self-contained compositions, <section> for thematic groupings, and <footer> for closing content.
Semantic markup improves accessibility—screen readers announce element roles. It also helps SEO as search engines better understand your content hierarchy. Replace generic <div> containers with appropriate semantic elements whenever possible.
<article>
<header>
<h1>Article Title</h1>
<time datetime="2024-01-15">
January 15, 2024
</time>
</header>
<p>Article content...</p>
<footer>
<p>Written by Author</p>
</footer>
</article>
Forms & Validation
HTML forms collect user input with elements like <input>, <select>, <textarea>, and <button>. Always pair inputs with <label> elements using matching for and id attributes—this is crucial for accessibility and usability.
Native validation attributes eliminate JavaScript for common cases: required prevents empty submissions, type="email" validates email format, minlength/maxlength control text length, and pattern accepts custom regex patterns. The browser handles error messages automatically.
Modern HTML includes powerful interactive components that work without JavaScript. The <details> element creates native accordions—click <summary> to toggle visibility. Add the open attribute to start expanded. Multiple details elements create FAQ-style interfaces instantly.
The <dialog> element creates accessible modal dialogs. Call .showModal() in JavaScript to open it with backdrop and focus trapping built-in. Use <datalist> with inputs to provide autocomplete suggestions from a predefined list.
<details>
<summary>What is HTML?</summary>
<p>HTML is the standard markup
language for web pages.</p>
</details>
<dialog id="confirm">
<h2>Confirm Action</h2>
<p>Are you sure?</p>
<button onclick="this.closest('dialog').close()">
Close
</button>
</dialog>
Tables & Lists
Use <table> exclusively for tabular data, never for page layout. Structure tables with <thead> for header rows, <tbody> for data rows, and optionally <tfoot> for summaries. Mark header cells with <th> (not <td>) and add scope="col" or scope="row" for accessibility.
Lists come in three flavors: <ul> for unordered bullet lists, <ol> for numbered sequences, and <dl> for definition lists (term/description pairs). Nest lists for hierarchical content like navigation menus or category trees.
Tailwind CSS is a utility-first CSS framework that takes a radically different approach to styling. Instead of writing custom CSS classes like .card or .button, you compose designs using small, single-purpose utility classes directly in your HTML: class="p-4 bg-white rounded shadow".
This approach solves common CSS problems: no more specificity battles, no unused styles, no inventing class names. Tailwind's consistent spacing scale (p-1 through p-12), color palette (blue-500, gray-100), and responsive prefixes (md:, lg:) make building consistent, responsive interfaces fast and predictable.
Utility-First Basics
Tailwind CSS uses small, single-purpose utility classes applied directly in HTML. Instead of writing .btn { background: blue; padding: 1rem; } in a stylesheet, you write class="bg-blue-500 p-4" on the element. Each class does exactly one thing, making styles predictable and composable.
This approach eliminates context-switching between HTML and CSS files. Common utilities include text-lg for font size, font-bold for weight, rounded for border radius, and shadow for box shadows. Hover states use the hover: prefix like hover:bg-blue-600.
Tailwind's spacing scale is consistent and memorable. The pattern is simple: p-4 means padding of 1rem (16px), p-2 is 0.5rem, p-8 is 2rem. The same numbers work for margin (m-4), gap (gap-4), and space utilities. Use directional variants like px-4 (horizontal) or pt-2 (top only).
Width and height follow patterns too: w-full for 100%, w-1/2 for 50%, w-64 for fixed 16rem, h-screen for viewport height. Combine max-w-xl with mx-auto for centered containers with maximum widths.
Tailwind's layout utilities map directly to CSS flexbox and grid. Enable flex with flex, then control direction (flex-row, flex-col), alignment (items-center, justify-between), and wrapping (flex-wrap). The gap-4 utility adds consistent spacing between items.
For grid layouts, use grid with column definitions like grid-cols-3 for three equal columns or grid-cols-[200px_1fr] for custom track sizes. The col-span-2 utility makes items span multiple columns.
Tailwind uses mobile-first responsive prefixes. Unprefixed utilities apply to all screen sizes. Add sm: (640px+), md: (768px+), lg: (1024px+), or xl: (1280px+) prefixes to apply styles at specific breakpoints and above.
Build responsive layouts by starting with mobile styles, then adding larger-screen overrides. For example, flex-col md:flex-row stacks items vertically on mobile and horizontally on medium screens. Use hidden md:block to show/hide elements at different sizes.