feat: redesign landing page with SEO enhancements

- 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)
This commit is contained in:
2026-01-15 17:26:02 +01:00
parent 6639a70070
commit 85205f836e
3 changed files with 285 additions and 70 deletions

View File

@@ -1827,6 +1827,90 @@ function handlePopState() {
handleRoute(false); 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 * Main route handler - switches between page types
*/ */
@@ -1858,6 +1942,7 @@ function handleRoute(shouldUpdateUrl = true) {
} }
updateNavHighlight(route); updateNavHighlight(route);
updatePageMeta(route);
} }
/** /**

View File

@@ -4,11 +4,55 @@
<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" />
<!-- Primary Meta Tags -->
<title>Code Crispies - Learn HTML & CSS Interactively | Free Coding Practice</title>
<meta <meta
name="description" 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." content="Master HTML, CSS, and Tailwind through hands-on coding exercises. Free, open-source learning platform with instant feedback. No account required."
/> />
<title>Code Crispies - Learn HTML & CSS Interactively</title> <meta name="keywords" content="learn CSS, HTML tutorial, Tailwind CSS, web development, coding practice, free" />
<meta name="robots" content="index, follow" />
<meta name="theme-color" content="#6366f1" />
<link rel="canonical" href="https://codecrispi.es/" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://codecrispi.es/" />
<meta property="og:title" content="Code Crispies - Learn HTML & CSS Interactively" />
<meta property="og:description" content="Master HTML, CSS, and Tailwind through hands-on coding exercises. Free and open source." />
<meta property="og:image" content="https://codecrispi.es/og-image.png" />
<meta property="og:site_name" content="Code Crispies" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Code Crispies - Learn HTML & CSS Interactively" />
<meta name="twitter:description" content="Master HTML, CSS, and Tailwind through hands-on coding exercises." />
<meta name="twitter:image" content="https://codecrispi.es/og-image.png" />
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "Code Crispies",
"description": "Interactive platform for learning HTML, CSS, and Tailwind through hands-on coding exercises",
"url": "https://codecrispi.es/",
"applicationCategory": "EducationalApplication",
"operatingSystem": "Web Browser",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
},
"author": {
"@type": "Organization",
"name": "LibreTECH",
"url": "https://librete.ch"
}
}
</script>
<link rel="stylesheet" href="main.css" /> <link rel="stylesheet" href="main.css" />
<script defer src="https://librete.ch/u/script.js" data-website-id="2189049f-80c1-4ca0-b0ff-b5cc49276b5f"></script> <script defer src="https://librete.ch/u/script.js" data-website-id="2189049f-80c1-4ca0-b0ff-b5cc49276b5f"></script>
</head> </head>
@@ -42,78 +86,76 @@
<div class="landing-content"> <div class="landing-content">
<section class="hero"> <section class="hero">
<img src="./bowl.png" width="120" alt="" class="hero-logo" /> <img src="./bowl.png" width="120" alt="" class="hero-logo" />
<h1>Learn Web Development<br /><span class="hero-highlight">Interactively</span></h1> <h1>Learn Web Development<br /><span class="hero-highlight">By Writing Real Code</span></h1>
<p class="hero-subtitle">Master HTML, CSS, and Tailwind through hands-on exercises. Free and open source.</p> <p class="hero-subtitle">
Master HTML, CSS, and Tailwind through hands-on exercises with instant feedback. Free and open source.
</p>
<a href="#welcome/0" class="cta-button cta-primary">Start Learning Free</a>
</section> </section>
<section class="section-cards" id="section-cards"> <section class="why-it-works">
<a href="#css" class="section-card" data-section="css"> <h2>Why Code Crispies Works</h2>
<div class="section-card-icon" style="background: #264de4">CSS</div> <div class="benefits-grid">
<h2>CSS</h2> <article class="benefit-card">
<p>Styling, layout, and animations</p> <svg class="benefit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<span class="section-card-progress" id="css-progress"></span> <polyline points="16 18 22 12 16 6"></polyline>
</a> <polyline points="8 6 2 12 8 18"></polyline>
<a href="#html" class="section-card" data-section="html"> </svg>
<div class="section-card-icon" style="background: #e34c26">HTML</div> <h3>Learn by Doing</h3>
<h2>HTML</h2>
<p>Semantic markup and native elements</p>
<span class="section-card-progress" id="html-progress"></span>
</a>
<a href="#tailwind" class="section-card" data-section="tailwind">
<div class="section-card-icon" style="background: #06b6d4">TW</div>
<h2>Tailwind CSS</h2>
<p>Utility-first CSS framework</p>
<span class="section-card-progress" id="tailwind-progress"></span>
</a>
</section>
<section class="landing-about">
<h2>How It Works</h2>
<div class="about-grid">
<div class="about-item">
<span class="about-icon">1</span>
<h3>Write Code</h3>
<p> <p>
Type CSS, HTML, or Tailwind directly in the browser editor. No installation, no configuration—start coding Write real code from lesson one. No videos to watch—just you, an editor, and instant feedback on every keystroke.
immediately.
</p> </p>
</div> </article>
<div class="about-item"> <article class="benefit-card">
<span class="about-icon">2</span> <svg class="benefit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<h3>See Results</h3> <path d="M12 20V10M18 20V4M6 20v-4"></path>
<p>Watch your changes appear instantly in the live preview. Understand how each property affects the output.</p> </svg>
</div> <h3>Practice at Your Pace</h3>
<div class="about-item"> <p>Start with basics, fill gaps in your understanding, then accelerate. Pick up where you left off anytime.</p>
<span class="about-icon">3</span> </article>
<h3>Build Skills</h3> <article class="benefit-card">
<p>Complete focused exercises that reinforce concepts. Track your progress and return anytime to continue.</p> <svg class="benefit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
</div> <path
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
></path>
</svg>
<h3>Master Real Skills</h3>
<p>
Learn CSS, HTML, and Tailwind the way professionals use them—through hands-on exercises and
<a href="#reference/css">reference guides</a>.
</p>
</article>
</div> </div>
</section> </section>
<section class="landing-features"> <section class="learning-paths">
<div class="feature-row"> <h2>Explore Learning Paths</h2>
<div class="feature-text"> <div class="section-cards" id="section-cards">
<h3>Beginner Friendly</h3> <a href="#css" class="section-card" data-section="css">
<p> <div class="section-card-icon" style="background: #264de4">CSS</div>
No prior experience required. Lessons start with fundamentals and introduce concepts gradually, with clear <h3>CSS</h3>
explanations for each step. <p>Styling, layout, and animations</p>
</p> <span class="section-card-progress" id="css-progress"></span>
</div> </a>
<div class="feature-text"> <a href="#html" class="section-card" data-section="html">
<h3>Practical Focus</h3> <div class="section-card-icon" style="background: #e34c26">HTML</div>
<p> <h3>HTML</h3>
Every exercise teaches skills you'll use in real projects—<a href="#reference/flexbox">flexbox layouts</a>, <p>Semantic markup and native elements</p>
<a href="#html-forms-basic/0">form styling</a>, <a href="#responsive-design/0">responsive design</a>, and modern <span class="section-card-progress" id="html-progress"></span>
CSS techniques. </a>
</p> <a href="#tailwind" class="section-card" data-section="tailwind">
</div> <div class="section-card-icon" style="background: #06b6d4">TW</div>
<h3>Tailwind CSS</h3>
<p>Utility-first CSS framework</p>
<span class="section-card-progress" id="tailwind-progress"></span>
</a>
</div> </div>
</section> </section>
<section class="landing-cta"> <section class="landing-cta">
<a href="#welcome/0" class="cta-button">Start Learning</a> <h2>Start Learning Today</h2>
<p class="cta-sub">Free and open source. No account required.</p> <p class="cta-sub">Free and open source. No account required. Progress saved locally.</p>
<a href="#welcome/0" class="cta-button">Begin Your Journey</a>
</section> </section>
</div> </div>
</div> </div>
@@ -196,9 +238,7 @@
> >
</button> </button>
<button id="random-template-btn" class="btn btn-icon hidden" title="Load random template"> <button id="random-template-btn" class="btn btn-icon hidden" title="Load random template">🎲</button>
🎲
</button>
</div> </div>
<button id="run-btn" class="btn btn-run"><img src="./gear.svg" alt="" /><span data-i18n="run">Run</span></button> <button id="run-btn" class="btn btn-run"><img src="./gear.svg" alt="" /><span data-i18n="run">Run</span></button>
</div> </div>

View File

@@ -1591,7 +1591,85 @@ input:checked + .toggle-slider::before {
font-size: 1.1rem; font-size: 1.1rem;
color: var(--light-text); color: var(--light-text);
max-width: 500px; 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 */ /* Section Cards */
@@ -1635,7 +1713,8 @@ input:checked + .toggle-slider::before {
margin-bottom: var(--spacing-md); margin-bottom: var(--spacing-md);
} }
.section-card h2 { .section-card h2,
.section-card h3 {
font-size: 1.25rem; font-size: 1.25rem;
margin-bottom: var(--spacing-xs); margin-bottom: var(--spacing-xs);
} }
@@ -1736,7 +1815,16 @@ input:checked + .toggle-slider::before {
/* Landing CTA */ /* Landing CTA */
.landing-cta { .landing-cta {
text-align: center; 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 { .cta-button {
@@ -2089,7 +2177,9 @@ input:checked + .toggle-slider::before {
color: var(--text-color); color: var(--text-color);
font-size: 0.9rem; font-size: 0.9rem;
font-weight: 500; font-weight: 500;
transition: background 0.2s, color 0.2s; transition:
background 0.2s,
color 0.2s;
} }
.ref-nav-link:hover { .ref-nav-link:hover {