From 85205f836e47cf2b620fa7b67708ba2893020663 Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Thu, 15 Jan 2026 17:26:02 +0100 Subject: [PATCH] feat: redesign landing page with SEO enhancements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add comprehensive SEO meta tags (Open Graph, Twitter Cards, JSON-LD) - Restructure landing page with Khan Academy-style "Why It Works" section - Replace "How It Works" + "Features" with 3 benefit cards: - Learn by Doing (code icon) - Practice at Your Pace (progress icon) - Master Real Skills (tools icon) - Add "Explore Learning Paths" section with learning tracks - Implement dynamic meta tag updates for route-based SEO - Update page title and descriptions based on current route - Add new CSS styles for benefit cards, learning paths, and enhanced CTA 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- src/app.js | 85 ++++++++++++++++++++++++ src/index.html | 172 ++++++++++++++++++++++++++++++------------------- src/main.css | 98 ++++++++++++++++++++++++++-- 3 files changed, 285 insertions(+), 70 deletions(-) diff --git a/src/app.js b/src/app.js index b9cf9cb..cb41462 100644 --- a/src/app.js +++ b/src/app.js @@ -1827,6 +1827,90 @@ function handlePopState() { handleRoute(false); } +/** + * Strip HTML tags from a string for meta descriptions + */ +function stripHtml(html) { + const tmp = document.createElement("div"); + tmp.innerHTML = html || ""; + return tmp.textContent || tmp.innerText || ""; +} + +/** + * Update page meta tags based on current route for SEO + */ +function updatePageMeta(route) { + const defaultTitle = "Code Crispies - Learn HTML & CSS Interactively | Free Coding Practice"; + const defaultDesc = + "Master HTML, CSS, and Tailwind through hands-on coding exercises. Free, open-source learning platform with instant feedback. No account required."; + + let title = defaultTitle; + let description = defaultDesc; + + if (!route) { + document.title = title; + return; + } + + switch (route.type) { + case RouteType.HOME: + // Use defaults + break; + + case RouteType.SECTION: { + const sectionNames = { css: "CSS", html: "HTML", tailwind: "Tailwind CSS" }; + const sectionName = sectionNames[route.sectionId] || route.sectionId; + title = `${sectionName} Lessons - Code Crispies | Learn ${sectionName}`; + description = `Learn ${sectionName} through interactive coding exercises. Hands-on practice with instant feedback.`; + break; + } + + case RouteType.LESSON: { + const module = lessonEngine.modules.find((m) => m.id === route.moduleId); + const lesson = module?.lessons[route.lessonIndex]; + if (module && lesson) { + title = `${lesson.title} - ${module.title} | Code Crispies`; + const lessonDesc = stripHtml(lesson.description || lesson.task); + description = lessonDesc.length > 155 ? lessonDesc.slice(0, 152) + "..." : lessonDesc || defaultDesc; + } + break; + } + + case RouteType.REFERENCE: { + const refNames = { + css: "CSS Properties", + selectors: "CSS Selectors", + flexbox: "Flexbox", + grid: "CSS Grid", + html: "HTML Elements" + }; + const refName = refNames[route.refId] || "Reference"; + title = `${refName} Reference - Code Crispies`; + description = `Quick reference guide for ${refName}. Syntax, examples, and common patterns for web development.`; + break; + } + } + + // Update document title + document.title = title; + + // Update meta description + const metaDesc = document.querySelector('meta[name="description"]'); + if (metaDesc) metaDesc.setAttribute("content", description); + + // Update Open Graph tags + const ogTitle = document.querySelector('meta[property="og:title"]'); + const ogDesc = document.querySelector('meta[property="og:description"]'); + if (ogTitle) ogTitle.setAttribute("content", title.replace(" | Code Crispies", "").replace(" - Code Crispies", "")); + if (ogDesc) ogDesc.setAttribute("content", description); + + // Update Twitter tags + const twitterTitle = document.querySelector('meta[name="twitter:title"]'); + const twitterDesc = document.querySelector('meta[name="twitter:description"]'); + if (twitterTitle) twitterTitle.setAttribute("content", title.replace(" | Code Crispies", "").replace(" - Code Crispies", "")); + if (twitterDesc) twitterDesc.setAttribute("content", description); +} + /** * Main route handler - switches between page types */ @@ -1858,6 +1942,7 @@ function handleRoute(shouldUpdateUrl = true) { } updateNavHighlight(route); + updatePageMeta(route); } /** diff --git a/src/index.html b/src/index.html index 0e895af..6231b8b 100644 --- a/src/index.html +++ b/src/index.html @@ -4,11 +4,55 @@ + + + Code Crispies - Learn HTML & CSS Interactively | Free Coding Practice - Code Crispies - Learn HTML & CSS Interactively + + + + + + + + + + + + + + + + + + + + + + @@ -42,78 +86,76 @@
-

Learn Web Development
Interactively

-

Master HTML, CSS, and Tailwind through hands-on exercises. Free and open source.

+

Learn Web Development
By Writing Real Code

+

+ Master HTML, CSS, and Tailwind through hands-on exercises with instant feedback. Free and open source. +

+ Start Learning Free
-
- -
CSS
-

CSS

-

Styling, layout, and animations

- -
- -
HTML
-

HTML

-

Semantic markup and native elements

- -
- -
TW
-

Tailwind CSS

-

Utility-first CSS framework

- -
-
- -
-

How It Works

-
-
- 1 -

Write Code

+
+

Why Code Crispies Works

+
+
+ + + + +

Learn by Doing

- Type CSS, HTML, or Tailwind directly in the browser editor. No installation, no configuration—start coding - immediately. + Write real code from lesson one. No videos to watch—just you, an editor, and instant feedback on every keystroke.

-
-
- 2 -

See Results

-

Watch your changes appear instantly in the live preview. Understand how each property affects the output.

-
-
- 3 -

Build Skills

-

Complete focused exercises that reinforce concepts. Track your progress and return anytime to continue.

-
+ +
+ + + +

Practice at Your Pace

+

Start with basics, fill gaps in your understanding, then accelerate. Pick up where you left off anytime.

+
+
+ + + +

Master Real Skills

+

+ Learn CSS, HTML, and Tailwind the way professionals use them—through hands-on exercises and + reference guides. +

+
-
-
-
-

Beginner Friendly

-

- No prior experience required. Lessons start with fundamentals and introduce concepts gradually, with clear - explanations for each step. -

-
-
-

Practical Focus

-

- Every exercise teaches skills you'll use in real projects—flexbox layouts, - form styling, responsive design, and modern - CSS techniques. -

-
+
+

Explore Learning Paths

+
- Start Learning -

Free and open source. No account required.

+

Start Learning Today

+

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

+ Begin Your Journey
@@ -196,9 +238,7 @@ > ⟲ - + diff --git a/src/main.css b/src/main.css index e483c7f..4684dbe 100644 --- a/src/main.css +++ b/src/main.css @@ -1591,7 +1591,85 @@ input:checked + .toggle-slider::before { font-size: 1.1rem; color: var(--light-text); max-width: 500px; - margin: 0 auto; + margin: 0 auto 2rem; +} + +.hero .cta-button { + margin-top: 1rem; +} + +/* Why It Works Section */ +.why-it-works { + padding: 3rem 1rem; + text-align: center; +} + +.why-it-works h2 { + font-size: 1.75rem; + color: var(--primary-dark); + margin-bottom: 2rem; +} + +.benefits-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: var(--spacing-lg); + text-align: left; +} + +.benefit-card { + background: var(--panel-bg); + padding: var(--spacing-lg); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow); + transition: + transform 0.2s, + box-shadow 0.2s; +} + +.benefit-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); +} + +.benefit-icon { + width: 48px; + height: 48px; + color: var(--primary-color); + margin-bottom: var(--spacing-md); +} + +.benefit-card h3 { + font-size: 1.1rem; + color: var(--primary-dark); + margin-bottom: var(--spacing-sm); +} + +.benefit-card p { + color: var(--light-text); + line-height: 1.6; + font-size: 0.95rem; +} + +.benefit-card a { + color: var(--primary-color); + text-decoration: none; +} + +.benefit-card a:hover { + text-decoration: underline; +} + +/* Learning Paths Section */ +.learning-paths { + padding: 2rem 1rem 3rem; + text-align: center; +} + +.learning-paths h2 { + font-size: 1.75rem; + color: var(--primary-dark); + margin-bottom: 0; } /* Section Cards */ @@ -1635,7 +1713,8 @@ input:checked + .toggle-slider::before { margin-bottom: var(--spacing-md); } -.section-card h2 { +.section-card h2, +.section-card h3 { font-size: 1.25rem; margin-bottom: var(--spacing-xs); } @@ -1736,7 +1815,16 @@ input:checked + .toggle-slider::before { /* Landing CTA */ .landing-cta { text-align: center; - padding: 2rem 1rem 3rem; + padding: 3rem 1rem; + background: var(--panel-bg); + border-radius: var(--border-radius-lg); + margin-top: var(--spacing-lg); +} + +.landing-cta h2 { + font-size: 1.5rem; + color: var(--primary-dark); + margin-bottom: var(--spacing-sm); } .cta-button { @@ -2089,7 +2177,9 @@ input:checked + .toggle-slider::before { color: var(--text-color); font-size: 0.9rem; font-weight: 500; - transition: background 0.2s, color 0.2s; + transition: + background 0.2s, + color 0.2s; } .ref-nav-link:hover {