517 lines
14 KiB
Plaintext
517 lines
14 KiB
Plaintext
{{define "main-page"}}
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Interactive HTML Tutorial</title>
|
|
<script type="module" src="/datastar.js"></script>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
line-height: 1.6;
|
|
color: #333;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
.container {
|
|
max-width: 900px;
|
|
margin: 0 auto;
|
|
background: white;
|
|
border-radius: 20px;
|
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
overflow: hidden;
|
|
}
|
|
.header {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
padding: 30px;
|
|
text-align: center;
|
|
}
|
|
.header h1 {
|
|
font-size: 2.5em;
|
|
margin-bottom: 10px;
|
|
}
|
|
.progress-bar {
|
|
background: rgba(255,255,255,0.2);
|
|
height: 8px;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
margin-top: 20px;
|
|
}
|
|
.progress-fill {
|
|
background: #4ade80;
|
|
height: 100%;
|
|
transition: width 0.5s ease;
|
|
}
|
|
#content {
|
|
padding: 40px;
|
|
min-height: 400px;
|
|
}
|
|
h2 {
|
|
color: #667eea;
|
|
margin-bottom: 20px;
|
|
font-size: 2em;
|
|
}
|
|
h3 {
|
|
color: #764ba2;
|
|
margin: 20px 0 10px 0;
|
|
}
|
|
p {
|
|
margin-bottom: 15px;
|
|
font-size: 1.1em;
|
|
}
|
|
button {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 30px;
|
|
border-radius: 8px;
|
|
font-size: 1em;
|
|
cursor: pointer;
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
margin: 10px 10px 10px 0;
|
|
}
|
|
button:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
}
|
|
.btn-secondary {
|
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
}
|
|
.quiz-option {
|
|
display: block;
|
|
width: 100%;
|
|
text-align: left;
|
|
margin: 10px 0;
|
|
background: #f8f9fa;
|
|
color: #333;
|
|
}
|
|
.quiz-option:hover {
|
|
background: #e9ecef;
|
|
}
|
|
.feedback {
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
margin: 20px 0;
|
|
}
|
|
.feedback.success {
|
|
background: #d1fae5;
|
|
border-left: 4px solid #10b981;
|
|
}
|
|
.feedback.error {
|
|
background: #fee2e2;
|
|
border-left: 4px solid #ef4444;
|
|
}
|
|
.hint {
|
|
background: #fef3c7;
|
|
border-left: 4px solid #f59e0b;
|
|
padding: 15px;
|
|
margin: 15px 0;
|
|
border-radius: 5px;
|
|
}
|
|
textarea {
|
|
width: 100%;
|
|
min-height: 200px;
|
|
padding: 15px;
|
|
border: 2px solid #e5e7eb;
|
|
border-radius: 8px;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 14px;
|
|
margin: 15px 0;
|
|
resize: vertical;
|
|
}
|
|
textarea:focus {
|
|
outline: none;
|
|
border-color: #667eea;
|
|
}
|
|
.code-output {
|
|
background: #1f2937;
|
|
color: #10b981;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
font-family: 'Courier New', monospace;
|
|
margin: 15px 0;
|
|
}
|
|
pre {
|
|
white-space: pre-wrap;
|
|
}
|
|
.intro-box {
|
|
background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
|
|
padding: 30px;
|
|
border-radius: 15px;
|
|
margin: 20px 0;
|
|
}
|
|
.stats {
|
|
display: flex;
|
|
justify-content: space-around;
|
|
padding: 20px;
|
|
background: #f8f9fa;
|
|
border-radius: 10px;
|
|
margin: 20px 0;
|
|
}
|
|
.stat-item {
|
|
text-align: center;
|
|
}
|
|
.stat-value {
|
|
font-size: 2em;
|
|
font-weight: bold;
|
|
color: #667eea;
|
|
}
|
|
.stat-label {
|
|
color: #6b7280;
|
|
font-size: 0.9em;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="content" class="container" data-signals='{"progress": 0, "mastery": 0}'>
|
|
{{template "intro" .}}
|
|
</div>
|
|
|
|
<script>
|
|
// Lightweight click handler to support data-on-click / data-on:click attributes if datastar.js doesn't wire them.
|
|
(function(){
|
|
function parseSpec(spec) {
|
|
spec = (spec||"").trim();
|
|
var m = spec.match(/^(?:@|\$+)?([A-Za-z]+)\((['"])(.+?)\2\)$/);
|
|
if (!m) return null;
|
|
var verb = m[1].toUpperCase();
|
|
var path = m[3];
|
|
var v2 = path.match(/^([A-Z]+)\s+(\/.*)$/);
|
|
if (v2) { verb = v2[1]; path = v2[2]; }
|
|
return { method: verb, path: path };
|
|
}
|
|
|
|
document.addEventListener('click', function(e){
|
|
// support either attribute name (old and rc6): data-on-click and data-on:click
|
|
var el = e.target.closest && e.target.closest('[data-on-click], [data-on\\:click]');
|
|
if (!el) return;
|
|
var spec = el.getAttribute('data-on-click') || el.getAttribute('data-on:click') || '';
|
|
var parsed = parseSpec(spec);
|
|
if (!parsed) return;
|
|
e.preventDefault();
|
|
|
|
if (parsed.method === 'GET') {
|
|
fetch(parsed.path, { method: 'GET', headers: { Accept: 'text/html' }, credentials: 'same-origin' })
|
|
.then(function(res){ return res.text(); })
|
|
.then(function(html){
|
|
var container = document.getElementById('content');
|
|
if (container) {
|
|
container.innerHTML = html;
|
|
container.querySelectorAll('script').forEach(function(s){
|
|
var ns = document.createElement('script');
|
|
if (s.src) ns.src = s.src;
|
|
ns.text = s.textContent;
|
|
document.head.appendChild(ns);
|
|
document.head.removeChild(ns);
|
|
});
|
|
}
|
|
})
|
|
.catch(function(err){ console.error('nav fetch failed', err); });
|
|
return;
|
|
}
|
|
|
|
console.warn('Unhandled method in data-on-click/ data-on:click:', parsed.method);
|
|
}, false);
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|
|
{{end}}
|
|
|
|
{{define "intro"}}
|
|
<div class="intro-box">
|
|
<h2>Welcome to Your HTML Journey! 👋</h2>
|
|
<p>This interactive tutorial will teach you HTML from the ground up. You'll learn by doing, with:</p>
|
|
<ul style="margin: 20px 0 20px 30px; font-size: 1.1em;">
|
|
</ul>
|
|
<p>The tutorial adapts to your pace - if you're doing great, we'll move faster. If you need more practice, we'll provide it!</p>
|
|
<button data-on:click="@get('/node/what-is-html')">
|
|
Start Learning
|
|
</button>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{define "what-is-html"}}
|
|
<div>
|
|
<h2>What is HTML? 🤔</h2>
|
|
<p><strong>HTML</strong> stands for <strong>HyperText Markup Language</strong>. It's the standard language used to create web pages.</p>
|
|
<button data-on:click="@get('/node/quiz-what-is-html')">
|
|
Next
|
|
</button>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{define "correct-answer"}}
|
|
<div class="feedback success">
|
|
<h3>✅ Correct! Great job!</h3>
|
|
<p>{{.Explanation}}</p>
|
|
<button data-on:click="@get('/node/{{.NextNode}}')">
|
|
Continue
|
|
</button>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{define "wrong-answer"}}
|
|
<div class="feedback error">
|
|
<h3>❌ Not quite right</h3>
|
|
{{if .ShowHint}}
|
|
<div class="hint">
|
|
</div>
|
|
{{end}}
|
|
<button data-on:click="@get('/node/quiz-what-is-html')">
|
|
Try Again
|
|
</button>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{define "html-structure"}}
|
|
<div>
|
|
<h2>HTML Structure 🏗️</h2>
|
|
<p>Every HTML document follows a basic structure. Think of it like a house - it needs a foundation, walls, and a roof!</p>
|
|
|
|
<div class="code-output">
|
|
<pre><!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Page Title</title>
|
|
</head>
|
|
<body>
|
|
<h1>My First Heading</h1>
|
|
<p>My first paragraph.</p>
|
|
</body>
|
|
</html></pre>
|
|
</div>
|
|
|
|
<h3>Let's break this down:</h3>
|
|
<ul style="margin: 15px 0 15px 30px; line-height: 2;">
|
|
<li><code><!DOCTYPE html></code> - Tells the browser this is HTML5</li>
|
|
<li><code><html></code> - The root element, contains everything</li>
|
|
<li><code><head></code> - Contains metadata (title, links to CSS, etc.)</li>
|
|
<li><code><body></code> - Contains the visible content</li>s
|
|
</ul>
|
|
|
|
<p>Most HTML elements have an <strong>opening tag</strong> and a <strong>closing tag</strong> (with a /). The content goes between them!</p>
|
|
|
|
<button data-on-click="@get('/node/html-tags')">
|
|
Learn About HTML Tags →
|
|
</button>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{define "html-tags"}}
|
|
<div>
|
|
<h2>HTML Tags 🏷️</h2>
|
|
<p>HTML uses <strong>tags</strong> to mark up content. Tags tell the browser how to display the content.</p>
|
|
|
|
<h3>Common HTML Tags:</h3>
|
|
<div style="background: #f8f9fa; padding: 20px; border-radius: 10px; margin: 20px 0;">
|
|
<p><code><h1></code> to <code><h6></code> - Headings (h1 is largest)</p>
|
|
<p><code><p></code> - Paragraph</p>
|
|
<p><code><a></code> - Link</p>
|
|
<p><code><img></code> - Image</p>
|
|
<p><code><div></code> - Division/container</p>
|
|
<p><code><span></code> - Inline container</p>
|
|
</div>
|
|
|
|
<h3>Tag Anatomy:</h3>
|
|
<div class="code-output">
|
|
<pre><p>This is a paragraph</p>
|
|
↑ ↑ ↑
|
|
Opening Content Closing
|
|
Tag Tag</pre>
|
|
</div>
|
|
|
|
<p>Most tags come in pairs, but some are <strong>self-closing</strong> like <code><img></code> and <code><br></code>.</p>
|
|
|
|
<button data-on-click="@get('/node/quiz-html-tags')">
|
|
Take the Quiz →
|
|
</button>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{define "quiz-html-tags"}}
|
|
<div>
|
|
<h2>Quiz: HTML Tags 📝</h2>
|
|
<p>Let's test your knowledge of HTML tags:</p>
|
|
|
|
<div style="margin: 30px 0;">
|
|
<h3>Which tag is used to create a paragraph?</h3>
|
|
{{range .QuizOptions}}
|
|
<button class="quiz-option" data-on-click="@post('/answer', {quiz: 'quiz-html-tags', answer: '{{.ID}}'})">
|
|
{{.Text}}
|
|
</button>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div id="quiz-feedback"></div>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{/* Alias for older/alternate node name used by buttons/routes */}}
|
|
{{define "quiz-what-is-html"}}{{template "quiz-html-tags" .}}{{end}}
|
|
|
|
{{define "exercise-first-tag"}}
|
|
<div data-signals='{"code": "{{.StartingCode}}"}'>
|
|
<h2>{{.Title}} 💻</h2>
|
|
<p>{{.Instructions}}</p>
|
|
|
|
<p>Remember: HTML tags have an opening tag <code><h1></code> and a closing tag <code></h1></code> with the content in between!</p>
|
|
|
|
<textarea
|
|
id="code-editor"
|
|
data-model="code"
|
|
placeholder="Type your HTML here..."
|
|
>{{.StartingCode}}</textarea>
|
|
|
|
<button data-on-click="@post('/code/check', {exerciseId: '{{.ExerciseID}}', code: $code})">
|
|
Check My Code ✓
|
|
</button>
|
|
|
|
<div id="exercise-feedback"></div>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{define "html-attributes"}}
|
|
<div>
|
|
<h2>HTML Attributes 🎯</h2>
|
|
<p>Attributes provide additional information about HTML elements. They're always specified in the opening tag.</p>
|
|
|
|
<div class="code-output">
|
|
<pre><a href="https://example.com">Click me</a>
|
|
↑
|
|
Attribute
|
|
(name="value")</pre>
|
|
</div>
|
|
|
|
<h3>Common Attributes:</h3>
|
|
<div style="background: #f8f9fa; padding: 20px; border-radius: 10px; margin: 20px 0;">
|
|
<p><code>href</code> - Link destination (for <code><a></code> tags)</p>
|
|
<p><code>src</code> - Image source (for <code><img></code> tags)</p>
|
|
<p><code>alt</code> - Alternative text for images</p>
|
|
<p><code>id</code> - Unique identifier for an element</p>
|
|
<p><code>class</code> - Class name for styling</p>
|
|
</div>
|
|
|
|
<h3>Example with Multiple Attributes:</h3>
|
|
<div class="code-output">
|
|
<pre><img src="logo.png" alt="Company Logo" width="200"></pre>
|
|
</div>
|
|
|
|
<button data-on-click="@get('/node/exercise-paragraph')">
|
|
Practice with Paragraphs →
|
|
</button>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{define "exercise-paragraph"}}
|
|
<div data-signals='{"code": "{{.StartingCode}}"}'>
|
|
<h2>{{.Title}} 💻</h2>
|
|
<p>{{.Instructions}}</p>
|
|
|
|
<p><strong>Tip:</strong> A paragraph tag looks like this: <code><p>Your text here</p></code></p>
|
|
|
|
<textarea
|
|
id="code-editor"
|
|
data-model="code"
|
|
placeholder="Type your HTML here..."
|
|
>{{.StartingCode}}</textarea>
|
|
|
|
<button data-on-click="@post('/code/check', {exerciseId: '{{.ExerciseID}}', code: $code})">
|
|
Check My Code ✓
|
|
</button>
|
|
|
|
<div id="exercise-feedback"></div>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{define "advanced-tags"}}
|
|
<div>
|
|
<h2>Advanced HTML Tags 🚀</h2>
|
|
<p>Great job so far! You're ready for more advanced tags. Let's look at lists and semantic HTML.</p>
|
|
|
|
<h3>Lists:</h3>
|
|
<div class="code-output">
|
|
<pre><!-- Unordered List -->
|
|
<ul>
|
|
<li>Item 1</li>
|
|
<li>Item 2</li>
|
|
</ul>
|
|
|
|
<!-- Ordered List -->
|
|
<ol>
|
|
<li>First</li>
|
|
<li>Second</li>
|
|
</ol></pre>
|
|
</div>
|
|
|
|
<h3>Semantic HTML5 Tags:</h3>
|
|
<div style="background: #f8f9fa; padding: 20px; border-radius: 10px; margin: 20px 0;">
|
|
<p><code><header></code> - Page or section header</p>
|
|
<p><code><nav></code> - Navigation links</p>
|
|
<p><code><main></code> - Main content</p>
|
|
<p><code><article></code> - Self-contained content</p>
|
|
<p><code><section></code> - Thematic grouping</p>
|
|
<p><code><footer></code> - Page or section footer</p>
|
|
</div>
|
|
|
|
<p>These semantic tags make your HTML more meaningful and accessible!</p>
|
|
|
|
<button data-on-click="@get('/node/congratulations')">
|
|
Complete Tutorial →
|
|
</button>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{define "congratulations"}}
|
|
<div>
|
|
<h2>🎉 Congratulations!</h2>
|
|
<p>You've completed the HTML Basics tutorial! You now know:</p>
|
|
|
|
<ul style="margin: 20px 0 20px 30px; font-size: 1.1em; line-height: 2;">
|
|
<li>✅ What HTML is and why it's important</li>
|
|
<li>✅ Basic HTML structure</li>
|
|
<li>✅ Common HTML tags</li>
|
|
<li>✅ How to write HTML code</li>
|
|
<li>✅ HTML attributes</li>
|
|
{{if ge .MasteryScore 80}}
|
|
<li>✅ Advanced HTML concepts</li>
|
|
{{end}}
|
|
</ul>
|
|
|
|
<div class="stats">
|
|
<div class="stat-item">
|
|
<div class="stat-value">{{.NodesCompleted}}</div>
|
|
<div class="stat-label">Lessons Completed</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value">{{.MasteryScore}}%</div>
|
|
<div class="stat-label">Mastery Score</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value">{{len .CompletedExercises}}</div>
|
|
<div class="stat-label">Exercises Done</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="intro-box">
|
|
<h3>What's Next? 🚀</h3>
|
|
<p>Continue your web development journey with:</p>
|
|
<ul style="margin: 15px 0 15px 30px;">
|
|
<li>CSS Tutorial - Style your HTML pages</li>
|
|
<li>JavaScript Tutorial - Add interactivity</li>
|
|
<li>Build Your First Website - Put it all together</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<button data-on-click="@get('/node/intro')">
|
|
Start Over
|
|
</button>
|
|
<button class="btn-secondary" onclick="alert('More tutorials coming soon!')">
|
|
Next Tutorial →
|
|
</button>
|
|
</div>
|
|
{{end}} |