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 390cb1bfd2
commit 114f4abe7c
3 changed files with 285 additions and 70 deletions

View File

@@ -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);
}
/**

View File

@@ -4,11 +4,55 @@
<meta charset="UTF-8" />
<link rel="icon" href="./favicon.ico" type="image/x-icon" />
<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
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" />
<script defer src="https://librete.ch/u/script.js" data-website-id="2189049f-80c1-4ca0-b0ff-b5cc49276b5f"></script>
</head>
@@ -42,78 +86,76 @@
<div class="landing-content">
<section class="hero">
<img src="./bowl.png" width="120" alt="" class="hero-logo" />
<h1>Learn Web Development<br /><span class="hero-highlight">Interactively</span></h1>
<p class="hero-subtitle">Master HTML, CSS, and Tailwind through hands-on exercises. Free and open source.</p>
<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 with instant feedback. Free and open source.
</p>
<a href="#welcome/0" class="cta-button cta-primary">Start Learning Free</a>
</section>
<section class="section-cards" id="section-cards">
<a href="#css" class="section-card" data-section="css">
<div class="section-card-icon" style="background: #264de4">CSS</div>
<h2>CSS</h2>
<p>Styling, layout, and animations</p>
<span class="section-card-progress" id="css-progress"></span>
</a>
<a href="#html" class="section-card" data-section="html">
<div class="section-card-icon" style="background: #e34c26">HTML</div>
<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>
<section class="why-it-works">
<h2>Why Code Crispies Works</h2>
<div class="benefits-grid">
<article class="benefit-card">
<svg class="benefit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="16 18 22 12 16 6"></polyline>
<polyline points="8 6 2 12 8 18"></polyline>
</svg>
<h3>Learn by Doing</h3>
<p>
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.
</p>
</div>
<div class="about-item">
<span class="about-icon">2</span>
<h3>See Results</h3>
<p>Watch your changes appear instantly in the live preview. Understand how each property affects the output.</p>
</div>
<div class="about-item">
<span class="about-icon">3</span>
<h3>Build Skills</h3>
<p>Complete focused exercises that reinforce concepts. Track your progress and return anytime to continue.</p>
</div>
</article>
<article class="benefit-card">
<svg class="benefit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 20V10M18 20V4M6 20v-4"></path>
</svg>
<h3>Practice at Your Pace</h3>
<p>Start with basics, fill gaps in your understanding, then accelerate. Pick up where you left off anytime.</p>
</article>
<article class="benefit-card">
<svg class="benefit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<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>
</section>
<section class="landing-features">
<div class="feature-row">
<div class="feature-text">
<h3>Beginner Friendly</h3>
<p>
No prior experience required. Lessons start with fundamentals and introduce concepts gradually, with clear
explanations for each step.
</p>
</div>
<div class="feature-text">
<h3>Practical Focus</h3>
<p>
Every exercise teaches skills you'll use in real projects—<a href="#reference/flexbox">flexbox layouts</a>,
<a href="#html-forms-basic/0">form styling</a>, <a href="#responsive-design/0">responsive design</a>, and modern
CSS techniques.
</p>
</div>
<section class="learning-paths">
<h2>Explore Learning Paths</h2>
<div class="section-cards" id="section-cards">
<a href="#css" class="section-card" data-section="css">
<div class="section-card-icon" style="background: #264de4">CSS</div>
<h3>CSS</h3>
<p>Styling, layout, and animations</p>
<span class="section-card-progress" id="css-progress"></span>
</a>
<a href="#html" class="section-card" data-section="html">
<div class="section-card-icon" style="background: #e34c26">HTML</div>
<h3>HTML</h3>
<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>
<h3>Tailwind CSS</h3>
<p>Utility-first CSS framework</p>
<span class="section-card-progress" id="tailwind-progress"></span>
</a>
</div>
</section>
<section class="landing-cta">
<a href="#welcome/0" class="cta-button">Start Learning</a>
<p class="cta-sub">Free and open source. No account required.</p>
<h2>Start Learning Today</h2>
<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>
</div>
</div>
@@ -196,9 +238,7 @@
>
</button>
<button id="random-template-btn" class="btn btn-icon hidden" title="Load random template">
🎲
</button>
<button id="random-template-btn" class="btn btn-icon hidden" title="Load random template">🎲</button>
</div>
<button id="run-btn" class="btn btn-run"><img src="./gear.svg" alt="" /><span data-i18n="run">Run</span></button>
</div>

View File

@@ -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 {