feat: add Markdown learning module with 8 beginner lessons
- Add markdown-basics module with lessons for headings, text formatting, lists, links, and inline code - Integrate markdown section with blue color theme (#5b8dd9) - Add markdown mode support in CodeEditor and LessonEngine - Add markdown preview rendering using marked library - Add section overview page with educational content - Add markdown reference page with syntax guide - Add i18n translations for 6 languages (EN, DE, PL, ES, AR, UK) - Update router to recognize #markdown as section route - Add all section-specific CSS styles for markdown theme
This commit is contained in:
197
lessons/40-markdown-basics.json
Normal file
197
lessons/40-markdown-basics.json
Normal file
@@ -0,0 +1,197 @@
|
||||
{
|
||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||
"id": "markdown-basics",
|
||||
"title": "Markdown Basics",
|
||||
"description": "Learn to format text documents with Markdown, a simple and readable markup language used everywhere from GitHub to note-taking apps.",
|
||||
"mode": "markdown",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "md-headings",
|
||||
"title": "Headings",
|
||||
"description": "Markdown uses hash symbols <kbd>#</kbd> to create headings. One <kbd>#</kbd> creates the largest heading (h1), two <kbd>##</kbd> creates a smaller heading (h2), and so on up to six levels.<br><br><pre># Main Title\n## Section\n### Subsection</pre>",
|
||||
"task": "Create a main heading by typing <kbd># Hello</kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "# Hello",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "^#\\s+.+",
|
||||
"message": "Start with <kbd>#</kbd> followed by a space and your heading text"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "Hello",
|
||||
"message": "Your heading should contain <kbd>Hello</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "md-heading-levels",
|
||||
"title": "Heading Levels",
|
||||
"description": "Use more <kbd>#</kbd> symbols for smaller headings. <kbd>##</kbd> creates an h2, <kbd>###</kbd> an h3. This creates a clear document structure with visual hierarchy.",
|
||||
"task": "Create an h2 heading with <kbd>## About</kbd> followed by an h3 heading with <kbd>### Details</kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "## About\n\n### Details",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "^##\\s+About",
|
||||
"message": "Start with <kbd>## About</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "###\\s+Details",
|
||||
"message": "Add <kbd>### Details</kbd> for the h3 heading"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "md-bold",
|
||||
"title": "Bold Text",
|
||||
"description": "Wrap text in double asterisks <kbd>**</kbd> or double underscores <kbd>__</kbd> to make it <strong>bold</strong>. This emphasizes important words or phrases.",
|
||||
"task": "Make the word <kbd>important</kbd> bold by wrapping it with <kbd>**</kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "This is important text.",
|
||||
"solution": "This is **important** text.",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "\\*\\*important\\*\\*",
|
||||
"message": "Wrap <kbd>important</kbd> with double asterisks: <kbd>**important**</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "md-italic",
|
||||
"title": "Italic Text",
|
||||
"description": "Wrap text in single asterisks <kbd>*</kbd> or single underscores <kbd>_</kbd> to make it <em>italic</em>. Use this for subtle emphasis or titles of works.",
|
||||
"task": "Make the word <kbd>elegant</kbd> italic by wrapping it with <kbd>*</kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "A simple and elegant solution.",
|
||||
"solution": "A simple and *elegant* solution.",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "\\*elegant\\*",
|
||||
"message": "Wrap <kbd>elegant</kbd> with single asterisks: <kbd>*elegant*</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "not_contains",
|
||||
"value": "**elegant**",
|
||||
"message": "Use single asterisks for italic, not double"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "md-unordered-list",
|
||||
"title": "Bullet Lists",
|
||||
"description": "Create bullet lists using <kbd>-</kbd>, <kbd>*</kbd>, or <kbd>+</kbd> at the start of each line. Each item goes on its own line.",
|
||||
"task": "Create a bullet list with three items: <kbd>Apple</kbd>, <kbd>Banana</kbd>, <kbd>Cherry</kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "- Apple\n- Banana\n- Cherry",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "^[-*+]\\s+Apple",
|
||||
"message": "Start with a dash, asterisk, or plus followed by <kbd>Apple</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "[-*+]\\s+Banana",
|
||||
"message": "Add <kbd>Banana</kbd> as a list item"
|
||||
},
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "[-*+]\\s+Cherry",
|
||||
"message": "Add <kbd>Cherry</kbd> as a list item"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "md-ordered-list",
|
||||
"title": "Numbered Lists",
|
||||
"description": "Create numbered lists by starting lines with <kbd>1.</kbd>, <kbd>2.</kbd>, etc. Markdown automatically numbers them in sequence.",
|
||||
"task": "Create a numbered list: <kbd>Wake up</kbd>, <kbd>Eat breakfast</kbd>, <kbd>Start coding</kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "1. Wake up\n2. Eat breakfast\n3. Start coding",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "\\d+\\.\\s+Wake up",
|
||||
"message": "Start with a number and period: <kbd>1. Wake up</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "\\d+\\.\\s+Eat breakfast",
|
||||
"message": "Add <kbd>Eat breakfast</kbd> as a numbered item"
|
||||
},
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "\\d+\\.\\s+Start coding",
|
||||
"message": "Add <kbd>Start coding</kbd> as a numbered item"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "md-links",
|
||||
"title": "Links",
|
||||
"description": "Create links with <kbd>[text](url)</kbd>. The text in brackets is what readers see; the URL in parentheses is where they go when clicked.",
|
||||
"task": "Create a link that shows <kbd>Google</kbd> and goes to <kbd>https://google.com</kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "[Google](https://google.com)",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "\\[Google\\]\\(https?://google\\.com\\)",
|
||||
"message": "Use the format <kbd>[Google](https://google.com)</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "md-inline-code",
|
||||
"title": "Inline Code",
|
||||
"description": "Wrap text in backticks <kbd>`</kbd> to format it as code. This is useful for variable names, commands, or short code snippets in your text.",
|
||||
"task": "Format <kbd>npm install</kbd> as inline code using backticks",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "Run npm install to install dependencies.",
|
||||
"solution": "Run `npm install` to install dependencies.",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "`npm install`",
|
||||
"message": "Wrap <kbd>npm install</kbd> with backticks: <kbd>`npm install`</kbd>"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
58
package-lock.json
generated
58
package-lock.json
generated
@@ -13,12 +13,14 @@
|
||||
"@codemirror/commands": "^6.10.1",
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
"@codemirror/lang-html": "^6.4.11",
|
||||
"@codemirror/lang-markdown": "^6.5.0",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/theme-one-dark": "^6.1.3",
|
||||
"@codemirror/view": "^6.39.4",
|
||||
"@emmetio/codemirror6-plugin": "^0.4.0",
|
||||
"@supabase/supabase-js": "^2.90.1",
|
||||
"codemirror": "^6.0.2",
|
||||
"marked": "^17.0.1",
|
||||
"whatwg-fetch": "^3.6.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -156,7 +158,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz",
|
||||
"integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@@ -169,7 +170,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.1.tgz",
|
||||
"integrity": "sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
@@ -182,7 +182,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@@ -196,7 +195,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
|
||||
"integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/lang-css": "^6.0.0",
|
||||
@@ -224,12 +222,26 @@
|
||||
"@lezer/javascript": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-markdown": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz",
|
||||
"integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.7.1",
|
||||
"@codemirror/lang-html": "^6.0.0",
|
||||
"@codemirror/language": "^6.3.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.2.1",
|
||||
"@lezer/markdown": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
@@ -266,7 +278,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
@@ -288,7 +299,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.4.tgz",
|
||||
"integrity": "sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"crelt": "^1.0.6",
|
||||
@@ -384,7 +394,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -408,7 +417,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
@@ -974,9 +982,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz",
|
||||
"integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.0.tgz",
|
||||
"integrity": "sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/css": {
|
||||
@@ -1030,6 +1038,16 @@
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/markdown": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.3.tgz",
|
||||
"integrity": "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.5.0",
|
||||
"@lezer/highlight": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
@@ -2336,7 +2354,6 @@
|
||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
@@ -2433,6 +2450,18 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "17.0.1",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz",
|
||||
"integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/min-indent": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||
@@ -2579,7 +2608,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -3123,7 +3151,6 @@
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@@ -3222,7 +3249,6 @@
|
||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.4",
|
||||
|
||||
@@ -37,12 +37,14 @@
|
||||
"@codemirror/commands": "^6.10.1",
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
"@codemirror/lang-html": "^6.4.11",
|
||||
"@codemirror/lang-markdown": "^6.5.0",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/theme-one-dark": "^6.1.3",
|
||||
"@codemirror/view": "^6.39.4",
|
||||
"@emmetio/codemirror6-plugin": "^0.4.0",
|
||||
"@supabase/supabase-js": "^2.90.1",
|
||||
"codemirror": "^6.0.2",
|
||||
"marked": "^17.0.1",
|
||||
"whatwg-fetch": "^3.6.20"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": ["css", "tailwind", "html"],
|
||||
"description": "Whether this module teaches CSS, Tailwind, or HTML"
|
||||
"enum": ["css", "tailwind", "html", "markdown"],
|
||||
"description": "Whether this module teaches CSS, Tailwind, HTML, or Markdown"
|
||||
},
|
||||
"difficulty": {
|
||||
"type": "string",
|
||||
@@ -60,7 +60,7 @@
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": ["css", "tailwind", "html"],
|
||||
"enum": ["css", "tailwind", "html", "markdown"],
|
||||
"description": "Override module mode for individual lessons"
|
||||
},
|
||||
"tailwindConfig": {
|
||||
|
||||
192
src/app.js
192
src/app.js
@@ -573,6 +573,11 @@ function updateEditorForMode(mode) {
|
||||
label: "CSS Editor",
|
||||
cmMode: "css"
|
||||
},
|
||||
markdown: {
|
||||
placeholder: "# Heading\n\nWrite your **Markdown** here...",
|
||||
label: "Markdown Editor",
|
||||
cmMode: "markdown"
|
||||
},
|
||||
playground: {
|
||||
placeholder: "<style>\n /* CSS here */\n</style>\n\n<!-- HTML here -->",
|
||||
label: "HTML & CSS",
|
||||
@@ -1409,6 +1414,85 @@ const sectionContent = {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
markdown: `
|
||||
<div class="section-overview">
|
||||
<p><strong>Markdown</strong> is a lightweight markup language created by John Gruber in 2004. It lets you write formatted text using plain text syntax that's easy to read and write. Markdown is used everywhere—from GitHub READMEs to documentation, note-taking apps, and content management systems.</p>
|
||||
<p>The beauty of Markdown is its simplicity: <code># Heading</code> creates a heading, <code>**bold**</code> makes text bold, and <code>[link](url)</code> creates a link. No complex HTML tags needed. Markdown files can be converted to HTML, PDF, or many other formats.</p>
|
||||
</div>
|
||||
|
||||
<div class="topic-row">
|
||||
<div class="topic-text">
|
||||
<h2>Headings & Structure</h2>
|
||||
<p>Create document structure with headings using <code>#</code> symbols. One <code>#</code> for h1, two <code>##</code> for h2, up to six levels. This creates a clear hierarchy in your documents.</p>
|
||||
<p>
|
||||
<a href="#markdown-basics/0" class="topic-link">Practice headings →</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="topic-code">
|
||||
<div class="code-block">
|
||||
<pre><code># Main Title
|
||||
## Section
|
||||
### Subsection
|
||||
#### Detail</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="topic-row">
|
||||
<div class="topic-text">
|
||||
<h2>Text Formatting</h2>
|
||||
<p>Emphasize text with <code>**bold**</code> or <code>*italic*</code>. Combine them with <code>***bold italic***</code>. Use backticks for <code>\`inline code\`</code> to highlight commands or code snippets in your text.</p>
|
||||
<p>
|
||||
<a href="#markdown-basics/2" class="topic-link">Practice formatting →</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="topic-code">
|
||||
<div class="code-block">
|
||||
<pre><code>This is **bold** text.
|
||||
This is *italic* text.
|
||||
This is \`inline code\`.</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="topic-row">
|
||||
<div class="topic-text">
|
||||
<h2>Lists</h2>
|
||||
<p>Create bullet lists with <code>-</code>, <code>*</code>, or <code>+</code>. Numbered lists use <code>1.</code>, <code>2.</code>, etc. Indent items with spaces to create nested lists for complex outlines.</p>
|
||||
<p>
|
||||
<a href="#markdown-basics/4" class="topic-link">Practice lists →</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="topic-code">
|
||||
<div class="code-block">
|
||||
<pre><code>- First item
|
||||
- Second item
|
||||
- Nested item
|
||||
|
||||
1. Step one
|
||||
2. Step two
|
||||
3. Step three</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="topic-row">
|
||||
<div class="topic-text">
|
||||
<h2>Links & Images</h2>
|
||||
<p>Create links with <code>[text](url)</code> syntax. Images use the same format with an exclamation mark: <code></code>. The alt text describes the image for accessibility.</p>
|
||||
<p>
|
||||
<a href="#markdown-basics/6" class="topic-link">Practice links →</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="topic-code">
|
||||
<div class="code-block">
|
||||
<pre><code>[Visit Google](https://google.com)
|
||||
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
|
||||
@@ -1925,6 +2009,105 @@ const referenceContent = {
|
||||
</section>
|
||||
|
||||
<p class="ref-see-also">Learn: <a href="#html">HTML Section</a> | Style with: <a href="#reference/css">CSS Properties</a></p>
|
||||
`,
|
||||
|
||||
markdown: `
|
||||
<h1>Markdown Syntax Reference</h1>
|
||||
<p class="ref-intro">A quick guide to Markdown syntax for formatting text documents. Markdown is used in GitHub, documentation, and note-taking apps.</p>
|
||||
|
||||
<section class="ref-section">
|
||||
<h2>Text Formatting</h2>
|
||||
<table class="ref-table">
|
||||
<thead><tr><th>Syntax</th><th>Result</th><th>Notes</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>**bold**</code></td><td><strong>bold</strong></td><td>Or use __bold__</td></tr>
|
||||
<tr><td><code>*italic*</code></td><td><em>italic</em></td><td>Or use _italic_</td></tr>
|
||||
<tr><td><code>***bold italic***</code></td><td><strong><em>bold italic</em></strong></td><td>Combine both</td></tr>
|
||||
<tr><td><code>~~strikethrough~~</code></td><td><s>strikethrough</s></td><td>GFM extension</td></tr>
|
||||
<tr><td><code>\`inline code\`</code></td><td><code>inline code</code></td><td>Monospace font</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="ref-section">
|
||||
<h2>Headings</h2>
|
||||
<table class="ref-table">
|
||||
<thead><tr><th>Syntax</th><th>Level</th><th>Usage</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code># Heading 1</code></td><td>h1</td><td>Document title</td></tr>
|
||||
<tr><td><code>## Heading 2</code></td><td>h2</td><td>Main sections</td></tr>
|
||||
<tr><td><code>### Heading 3</code></td><td>h3</td><td>Subsections</td></tr>
|
||||
<tr><td><code>#### Heading 4</code></td><td>h4</td><td>Minor sections</td></tr>
|
||||
<tr><td><code>##### Heading 5</code></td><td>h5</td><td>Rarely used</td></tr>
|
||||
<tr><td><code>###### Heading 6</code></td><td>h6</td><td>Smallest heading</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="ref-section">
|
||||
<h2>Lists</h2>
|
||||
<table class="ref-table">
|
||||
<thead><tr><th>Syntax</th><th>Type</th><th>Notes</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>- Item</code></td><td>Unordered</td><td>Or use * or +</td></tr>
|
||||
<tr><td><code>1. Item</code></td><td>Ordered</td><td>Numbers auto-increment</td></tr>
|
||||
<tr><td><code> - Nested</code></td><td>Nested list</td><td>2-space indent</td></tr>
|
||||
<tr><td><code>- [x] Task</code></td><td>Task list</td><td>GFM extension</td></tr>
|
||||
<tr><td><code>- [ ] Task</code></td><td>Unchecked task</td><td>Interactive checkboxes</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="ref-section">
|
||||
<h2>Links & Images</h2>
|
||||
<table class="ref-table">
|
||||
<thead><tr><th>Syntax</th><th>Purpose</th><th>Example</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>[text](url)</code></td><td>Inline link</td><td>[Google](https://google.com)</td></tr>
|
||||
<tr><td><code>[text](url "title")</code></td><td>Link with tooltip</td><td>Hover text</td></tr>
|
||||
<tr><td><code></code></td><td>Image</td><td>Alt text for accessibility</td></tr>
|
||||
<tr><td><code><url></code></td><td>Auto-link</td><td>URLs become clickable</td></tr>
|
||||
<tr><td><code>[ref]: url</code></td><td>Reference link</td><td>Define at doc bottom</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="ref-section">
|
||||
<h2>Code Blocks</h2>
|
||||
<table class="ref-table">
|
||||
<thead><tr><th>Syntax</th><th>Purpose</th><th>Notes</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>\`\`\`</code></td><td>Fenced code</td><td>3 backticks or tildes</td></tr>
|
||||
<tr><td><code>\`\`\`js</code></td><td>Syntax highlight</td><td>Add language identifier</td></tr>
|
||||
<tr><td><code> code</code></td><td>Indented code</td><td>4-space indent</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="ref-section">
|
||||
<h2>Block Elements</h2>
|
||||
<table class="ref-table">
|
||||
<thead><tr><th>Syntax</th><th>Element</th><th>Notes</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>> Quote</code></td><td>Blockquote</td><td>Nest with >></td></tr>
|
||||
<tr><td><code>---</code></td><td>Horizontal rule</td><td>Or *** or ___</td></tr>
|
||||
<tr><td><code>| A | B |</code></td><td>Table</td><td>GFM extension</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="ref-section">
|
||||
<h2>Tables (GFM)</h2>
|
||||
<div class="ref-example">
|
||||
<pre><code>| Header 1 | Header 2 |
|
||||
|----------|----------|
|
||||
| Cell 1 | Cell 2 |
|
||||
| Cell 3 | Cell 4 |</code></pre>
|
||||
</div>
|
||||
<p>Use colons for alignment: <code>:---</code> (left), <code>:---:</code> (center), <code>---:</code> (right)</p>
|
||||
</section>
|
||||
|
||||
<p class="ref-see-also">Learn: <a href="#markdown">Markdown Section</a> | Also try: <a href="#html">HTML Elements</a></p>
|
||||
`
|
||||
};
|
||||
|
||||
@@ -1970,7 +2153,7 @@ function updatePageMeta(route) {
|
||||
break;
|
||||
|
||||
case RouteType.SECTION: {
|
||||
const sectionNames = { css: "CSS", html: "HTML", tailwind: "Tailwind CSS" };
|
||||
const sectionNames = { css: "CSS", html: "HTML", tailwind: "Tailwind CSS", markdown: "Markdown" };
|
||||
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.`;
|
||||
@@ -1994,7 +2177,8 @@ function updatePageMeta(route) {
|
||||
selectors: "CSS Selectors",
|
||||
flexbox: "Flexbox",
|
||||
grid: "CSS Grid",
|
||||
html: "HTML Elements"
|
||||
html: "HTML Elements",
|
||||
markdown: "Markdown Syntax"
|
||||
};
|
||||
const refName = refNames[route.refId] || "Reference";
|
||||
title = `${refName} Reference - CODE CRISPIES`;
|
||||
@@ -2163,7 +2347,7 @@ function renderFooterLessonLinks() {
|
||||
* Update progress indicators on landing page
|
||||
*/
|
||||
function updateLandingProgress() {
|
||||
["css", "html", "tailwind"].forEach((sectionId) => {
|
||||
["css", "html", "tailwind", "markdown"].forEach((sectionId) => {
|
||||
const progressEl = document.getElementById(`${sectionId}-progress`);
|
||||
if (progressEl) {
|
||||
const sectionModules = getModulesBySection(lessonEngine.modules, sectionId);
|
||||
@@ -2249,7 +2433,7 @@ function showReferencePage(refId) {
|
||||
const activeRef = refId || "css";
|
||||
|
||||
// Map reference to section for color coding
|
||||
const refToSection = { css: "css", selectors: "css", flexbox: "css", grid: "css", html: "html" };
|
||||
const refToSection = { css: "css", selectors: "css", flexbox: "css", grid: "css", html: "html", markdown: "markdown" };
|
||||
updateSectionColor(refToSection[activeRef] || "css");
|
||||
|
||||
// Track reference page view
|
||||
|
||||
@@ -30,6 +30,7 @@ import gradientsEN from "../../lessons/09-gradients.json";
|
||||
import filtersEN from "../../lessons/11-filters.json";
|
||||
import positioningEN from "../../lessons/12-positioning.json";
|
||||
import pseudoElementsEN from "../../lessons/13-pseudo-elements.json";
|
||||
import markdownBasicsEN from "../../lessons/40-markdown-basics.json";
|
||||
import playgroundEN from "../../lessons/98-playground.json";
|
||||
import goodbyeEN from "../../lessons/99-goodbye.json";
|
||||
|
||||
@@ -162,6 +163,8 @@ const moduleStoreEN = [
|
||||
htmlFieldsetEN,
|
||||
htmlDatalistEN,
|
||||
htmlTablesEN,
|
||||
// Markdown
|
||||
markdownBasicsEN,
|
||||
// Outro
|
||||
goodbyeEN,
|
||||
playgroundEN
|
||||
@@ -201,6 +204,8 @@ const moduleStoreDE = [
|
||||
htmlFieldsetDE,
|
||||
htmlDatalistDE,
|
||||
htmlTablesDE,
|
||||
// Markdown
|
||||
markdownBasicsEN, // Using EN fallback until translated
|
||||
// Outro
|
||||
goodbyeEN,
|
||||
playgroundEN
|
||||
@@ -240,6 +245,8 @@ const moduleStorePL = [
|
||||
htmlFieldsetPL,
|
||||
htmlDatalistPL,
|
||||
htmlTablesPL,
|
||||
// Markdown
|
||||
markdownBasicsEN, // Using EN fallback until translated
|
||||
// Outro
|
||||
goodbyeEN,
|
||||
playgroundEN
|
||||
@@ -279,6 +286,8 @@ const moduleStoreES = [
|
||||
htmlFieldsetES,
|
||||
htmlDatalistES,
|
||||
htmlTablesES,
|
||||
// Markdown
|
||||
markdownBasicsEN, // Using EN fallback until translated
|
||||
// Outro
|
||||
goodbyeEN,
|
||||
playgroundEN
|
||||
@@ -318,6 +327,8 @@ const moduleStoreAR = [
|
||||
htmlFieldsetAR,
|
||||
htmlDatalistAR,
|
||||
htmlTablesAR,
|
||||
// Markdown
|
||||
markdownBasicsEN, // Using EN fallback until translated
|
||||
// Outro
|
||||
goodbyeEN,
|
||||
playgroundEN
|
||||
@@ -357,6 +368,8 @@ const moduleStoreUK = [
|
||||
htmlFieldsetUK,
|
||||
htmlDatalistUK,
|
||||
htmlTablesUK,
|
||||
// Markdown
|
||||
markdownBasicsEN, // Using EN fallback until translated
|
||||
// Outro
|
||||
goodbyeEN,
|
||||
playgroundEN
|
||||
|
||||
@@ -24,6 +24,13 @@ export const sections = {
|
||||
description: "Utility-first CSS framework",
|
||||
color: "#26a69a",
|
||||
order: 3
|
||||
},
|
||||
markdown: {
|
||||
id: "markdown",
|
||||
title: "Markdown",
|
||||
description: "Lightweight markup language for formatting text",
|
||||
color: "#5b8dd9",
|
||||
order: 4
|
||||
}
|
||||
};
|
||||
|
||||
@@ -57,6 +64,7 @@ export function getModuleSection(module) {
|
||||
const mode = module.mode || "css";
|
||||
if (mode === "html") return "html";
|
||||
if (mode === "tailwind") return "tailwind";
|
||||
if (mode === "markdown") return "markdown";
|
||||
return "css";
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* - #css -> CSS section landing
|
||||
* - #html -> HTML section landing
|
||||
* - #tailwind -> Tailwind section landing
|
||||
* - #markdown -> Markdown section landing
|
||||
* - #reference/css -> CSS cheatsheet
|
||||
* - #module/index -> Lesson (e.g., #flexbox/2)
|
||||
*/
|
||||
@@ -26,7 +27,7 @@ export const RouteType = {
|
||||
/**
|
||||
* Valid section IDs
|
||||
*/
|
||||
const SECTIONS = ["css", "html", "tailwind"];
|
||||
const SECTIONS = ["css", "html", "tailwind", "markdown"];
|
||||
|
||||
/**
|
||||
* Valid language codes for URL-based switching
|
||||
|
||||
@@ -143,6 +143,7 @@ const translations = {
|
||||
landingCssDesc: "Styling, layout, and animations",
|
||||
landingHtmlDesc: "Semantic markup and native elements",
|
||||
landingTailwindDesc: "Utility-first CSS framework",
|
||||
landingMarkdownDesc: "Format text with simple syntax",
|
||||
comingSoon: "Coming Soon",
|
||||
landingCtaTitle: "Start Learning Today",
|
||||
landingCtaSub: "Free and open source. No account required. Progress saved locally.",
|
||||
@@ -376,6 +377,7 @@ const translations = {
|
||||
landingCssDesc: "Styling, Layout und Animationen",
|
||||
landingHtmlDesc: "Semantisches Markup und native Elemente",
|
||||
landingTailwindDesc: "Utility-first CSS-Framework",
|
||||
landingMarkdownDesc: "Text mit einfacher Syntax formatieren",
|
||||
comingSoon: "Bald verfügbar",
|
||||
landingCtaTitle: "Jetzt gleich anfangen",
|
||||
landingCtaSub: "Kostenlos und Open Source. Kein Konto erforderlich. Fortschritt wird lokal gespeichert.",
|
||||
@@ -605,6 +607,7 @@ const translations = {
|
||||
landingCssDesc: "Stylowanie, układy i animacje",
|
||||
landingHtmlDesc: "Semantyczne znaczniki i natywne elementy",
|
||||
landingTailwindDesc: "Framework CSS oparty na klasach utility",
|
||||
landingMarkdownDesc: "Formatuj tekst prostą składnią",
|
||||
comingSoon: "Wkrótce",
|
||||
landingCtaTitle: "Zacznij naukę już dziś",
|
||||
landingCtaSub: "Darmowe i open source. Bez konta. Postęp zapisywany lokalnie.",
|
||||
@@ -836,6 +839,7 @@ const translations = {
|
||||
landingCssDesc: "Estilos, diseño y animaciones",
|
||||
landingHtmlDesc: "Marcado semántico y elementos nativos",
|
||||
landingTailwindDesc: "Framework CSS basado en utilidades",
|
||||
landingMarkdownDesc: "Formatea texto con sintaxis simple",
|
||||
comingSoon: "Próximamente",
|
||||
landingCtaTitle: "Empieza a aprender hoy",
|
||||
landingCtaSub: "Gratis y de código abierto. Sin cuenta requerida. Progreso guardado localmente.",
|
||||
@@ -1062,6 +1066,7 @@ const translations = {
|
||||
landingCssDesc: "التنسيق والتخطيط والرسوم المتحركة",
|
||||
landingHtmlDesc: "الترميز الدلالي والعناصر الأصلية",
|
||||
landingTailwindDesc: "إطار CSS قائم على الأدوات",
|
||||
landingMarkdownDesc: "تنسيق النص بصيغة بسيطة",
|
||||
comingSoon: "قريباً",
|
||||
landingCtaTitle: "ابدأ التعلم اليوم",
|
||||
landingCtaSub: "مجاني ومفتوح المصدر. لا حاجة لحساب. يُحفظ التقدم محليًا.",
|
||||
@@ -1290,6 +1295,7 @@ const translations = {
|
||||
landingCssDesc: "Стилізація, макети та анімації",
|
||||
landingHtmlDesc: "Семантична розмітка та нативні елементи",
|
||||
landingTailwindDesc: "CSS-фреймворк на основі утиліт",
|
||||
landingMarkdownDesc: "Форматуй текст простим синтаксисом",
|
||||
comingSoon: "Незабаром",
|
||||
landingCtaTitle: "Почни вчитися сьогодні",
|
||||
landingCtaSub: "Безкоштовно та з відкритим кодом. Без реєстрації. Прогрес зберігається локально.",
|
||||
|
||||
@@ -7,6 +7,7 @@ import { defaultKeymap, historyKeymap, indentMore, indentLess, undo, redo } from
|
||||
import { history } from "@codemirror/commands";
|
||||
import { html } from "@codemirror/lang-html";
|
||||
import { css } from "@codemirror/lang-css";
|
||||
import { markdown } from "@codemirror/lang-markdown";
|
||||
import { autocompletion } from "@codemirror/autocomplete";
|
||||
import { abbreviationTracker, expandAbbreviation } from "@emmetio/codemirror6-plugin";
|
||||
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
|
||||
@@ -180,7 +181,7 @@ export class CodeEditor {
|
||||
const fullDoc = prefix + initialValue + suffix;
|
||||
|
||||
// Get language extension based on mode
|
||||
const langExtension = this.mode === "html" ? html() : css();
|
||||
const langExtension = this.mode === "html" ? html() : this.mode === "markdown" ? markdown() : css();
|
||||
|
||||
// Create read-only zones decorations
|
||||
const readOnlyMark = Decoration.mark({ class: "cm-readonly-zone" });
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Single source of truth for lesson state and progress
|
||||
*/
|
||||
import { validateUserCode } from "../helpers/validator.js";
|
||||
import { marked } from "marked";
|
||||
|
||||
// Auth sync - lazy loaded to avoid circular dependencies
|
||||
let authModule = null;
|
||||
@@ -255,6 +256,38 @@ export class LessonEngine {
|
||||
${htmlWithClasses}
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
} else if (mode === "markdown") {
|
||||
// For Markdown mode, parse user code to HTML
|
||||
const renderedHtml = marked.parse(this.userCode || "");
|
||||
iframeDoc.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>html, body { min-height: 100%; margin: 0; }</style>
|
||||
<style>${previewBaseCSS || ""}</style>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; line-height: 1.6; padding: 1rem; }
|
||||
h1, h2, h3, h4, h5, h6 { margin: 1em 0 0.5em; line-height: 1.3; }
|
||||
h1 { font-size: 2em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
|
||||
h2 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
|
||||
p { margin: 0.5em 0; }
|
||||
ul, ol { margin: 0.5em 0; padding-left: 2em; }
|
||||
li { margin: 0.25em 0; }
|
||||
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-family: monospace; }
|
||||
pre { background: #f4f4f4; padding: 1em; overflow-x: auto; border-radius: 4px; }
|
||||
pre code { background: none; padding: 0; }
|
||||
blockquote { margin: 0.5em 0; padding-left: 1em; border-left: 4px solid #ddd; color: #666; }
|
||||
a { color: #0366d6; }
|
||||
hr { border: none; border-top: 1px solid #eee; margin: 1em 0; }
|
||||
img { max-width: 100%; }
|
||||
</style>
|
||||
<style>${sandboxCSS || ""}</style>
|
||||
</head>
|
||||
<body>
|
||||
${renderedHtml}
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
} else {
|
||||
// Original CSS mode
|
||||
@@ -349,6 +382,38 @@ export class LessonEngine {
|
||||
${htmlWithClasses}
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
} else if (mode === "markdown") {
|
||||
// For Markdown mode, parse solution to HTML
|
||||
const renderedHtml = marked.parse(solutionCode || "");
|
||||
iframeDoc.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>html, body { min-height: 100%; margin: 0; }</style>
|
||||
<style>${previewBaseCSS || ""}</style>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; line-height: 1.6; padding: 1rem; }
|
||||
h1, h2, h3, h4, h5, h6 { margin: 1em 0 0.5em; line-height: 1.3; }
|
||||
h1 { font-size: 2em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
|
||||
h2 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
|
||||
p { margin: 0.5em 0; }
|
||||
ul, ol { margin: 0.5em 0; padding-left: 2em; }
|
||||
li { margin: 0.25em 0; }
|
||||
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-family: monospace; }
|
||||
pre { background: #f4f4f4; padding: 1em; overflow-x: auto; border-radius: 4px; }
|
||||
pre code { background: none; padding: 0; }
|
||||
blockquote { margin: 0.5em 0; padding-left: 1em; border-left: 4px solid #ddd; color: #666; }
|
||||
a { color: #0366d6; }
|
||||
hr { border: none; border-top: 1px solid #eee; margin: 1em 0; }
|
||||
img { max-width: 100%; }
|
||||
</style>
|
||||
<style>${sandboxCSS || ""}</style>
|
||||
</head>
|
||||
<body>
|
||||
${renderedHtml}
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
} else {
|
||||
// CSS mode - wrap solution with prefix/suffix
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
<a href="#css" class="nav-link" data-section="css">CSS</a>
|
||||
<a href="#html" class="nav-link" data-section="html">HTML</a>
|
||||
<a href="#tailwind" class="nav-link" data-section="tailwind">Tailwind</a>
|
||||
<a href="#markdown" class="nav-link" data-section="markdown">Markdown</a>
|
||||
<a href="#reference/css" class="nav-link nav-link-ref" data-section="reference">Reference</a>
|
||||
</nav>
|
||||
<button id="auth-trigger-header" class="btn btn-outline btn-sm" data-i18n="authLogin">Log In</button>
|
||||
@@ -168,6 +169,12 @@
|
||||
<p data-i18n="landingTailwindDesc">Utility-first CSS framework</p>
|
||||
<span class="section-card-progress" id="tailwind-progress"></span>
|
||||
</a>
|
||||
<a href="#markdown" class="section-card" data-section="markdown">
|
||||
<div class="section-card-icon" style="background: #5b8dd9">MD</div>
|
||||
<h3>Markdown</h3>
|
||||
<p data-i18n="landingMarkdownDesc">Lightweight markup for formatting text</p>
|
||||
<span class="section-card-progress" id="markdown-progress"></span>
|
||||
</a>
|
||||
</div>
|
||||
<p class="device-notice" data-i18n-html="deviceNotice">
|
||||
<strong>Best on desktop or tablet (landscape).</strong> Mobile works, but larger screens make coding easier.
|
||||
@@ -328,6 +335,7 @@
|
||||
<a href="#reference/flexbox" class="ref-nav-link" data-ref="flexbox">Flexbox</a>
|
||||
<a href="#reference/grid" class="ref-nav-link" data-ref="grid">Grid</a>
|
||||
<a href="#reference/html" class="ref-nav-link" data-ref="html">HTML Elements</a>
|
||||
<a href="#reference/markdown" class="ref-nav-link" data-ref="markdown">Markdown</a>
|
||||
</nav>
|
||||
<div class="reference-body" id="reference-body">
|
||||
<!-- Reference content injected by JS -->
|
||||
|
||||
90
src/main.css
90
src/main.css
@@ -283,6 +283,14 @@ kbd {
|
||||
background: #1aafb8;
|
||||
}
|
||||
|
||||
[data-section="markdown"] .logo h1 .code-text {
|
||||
color: #5b8dd9;
|
||||
}
|
||||
|
||||
[data-section="markdown"] .logo h1 .crispies-text {
|
||||
background: #5b8dd9;
|
||||
}
|
||||
|
||||
.help-toggle {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
@@ -3602,6 +3610,14 @@ input:checked + .toggle-slider::before {
|
||||
--section-color-rgb: 26, 175, 184;
|
||||
}
|
||||
|
||||
/* Markdown Section - Blue */
|
||||
[data-section="markdown"] {
|
||||
--section-color: #5b8dd9;
|
||||
--section-color-light: #7ba3e5;
|
||||
--section-color-dark: #4070b8;
|
||||
--section-color-rgb: 91, 141, 217;
|
||||
}
|
||||
|
||||
/* Apply section colors to nav links */
|
||||
.nav-link[data-section="css"] {
|
||||
color: #9163b8;
|
||||
@@ -3615,6 +3631,10 @@ input:checked + .toggle-slider::before {
|
||||
color: #1aafb8;
|
||||
}
|
||||
|
||||
.nav-link[data-section="markdown"] {
|
||||
color: #5b8dd9;
|
||||
}
|
||||
|
||||
.nav-link[data-section="css"]:hover,
|
||||
.nav-link[data-section="css"].active {
|
||||
background: rgba(145, 99, 184, 0.1);
|
||||
@@ -3633,6 +3653,12 @@ input:checked + .toggle-slider::before {
|
||||
color: #0d8f96;
|
||||
}
|
||||
|
||||
.nav-link[data-section="markdown"]:hover,
|
||||
.nav-link[data-section="markdown"].active {
|
||||
background: rgba(91, 141, 217, 0.1);
|
||||
color: #4070b8;
|
||||
}
|
||||
|
||||
/* Hint section colors */
|
||||
body[data-section="css"] .hint {
|
||||
background: rgba(145, 99, 184, 0.3);
|
||||
@@ -3661,6 +3687,15 @@ body[data-section="tailwind"] .hint-progress {
|
||||
background: #1aafb8;
|
||||
}
|
||||
|
||||
body[data-section="markdown"] .hint {
|
||||
background: rgba(91, 141, 217, 0.3);
|
||||
border-left-color: #7ba3e5;
|
||||
}
|
||||
|
||||
body[data-section="markdown"] .hint-progress {
|
||||
background: #5b8dd9;
|
||||
}
|
||||
|
||||
/* RTL hint border */
|
||||
[dir="rtl"] body[data-section="css"] .hint {
|
||||
border-right-color: #a98cd6;
|
||||
@@ -3674,6 +3709,10 @@ body[data-section="tailwind"] .hint-progress {
|
||||
border-right-color: #4db6ac;
|
||||
}
|
||||
|
||||
[dir="rtl"] body[data-section="markdown"] .hint {
|
||||
border-right-color: #7ba3e5;
|
||||
}
|
||||
|
||||
/* Reference nav link colors */
|
||||
.ref-nav-link[data-ref="css"],
|
||||
.ref-nav-link[data-ref="selectors"],
|
||||
@@ -3759,6 +3798,24 @@ body[data-section="tailwind"] .cm-editor .cm-activeLine {
|
||||
background-color: rgba(26, 175, 184, 0.08) !important;
|
||||
}
|
||||
|
||||
body[data-section="markdown"] .cm-editor .cm-content {
|
||||
caret-color: #5b8dd9 !important;
|
||||
}
|
||||
|
||||
body[data-section="markdown"] .cm-editor .cm-cursor,
|
||||
body[data-section="markdown"] .cm-editor .cm-dropCursor {
|
||||
border-left-color: #5b8dd9 !important;
|
||||
}
|
||||
|
||||
body[data-section="markdown"] .cm-editor .cm-selectionBackground,
|
||||
body[data-section="markdown"] .cm-editor .cm-content ::selection {
|
||||
background-color: rgba(91, 141, 217, 0.25) !important;
|
||||
}
|
||||
|
||||
body[data-section="markdown"] .cm-editor .cm-activeLine {
|
||||
background-color: rgba(91, 141, 217, 0.08) !important;
|
||||
}
|
||||
|
||||
/* Module pill section colors */
|
||||
body[data-section="css"] .module-pill {
|
||||
background: rgba(145, 99, 184, 0.1);
|
||||
@@ -3787,6 +3844,15 @@ body[data-section="tailwind"] .module-pill .level-indicator {
|
||||
color: #0d8f96;
|
||||
}
|
||||
|
||||
body[data-section="markdown"] .module-pill {
|
||||
background: rgba(91, 141, 217, 0.1);
|
||||
color: #5b8dd9;
|
||||
}
|
||||
|
||||
body[data-section="markdown"] .module-pill .level-indicator {
|
||||
color: #4070b8;
|
||||
}
|
||||
|
||||
/* Code block border section colors */
|
||||
body[data-section="css"] .code-block {
|
||||
border-color: rgba(145, 99, 184, 0.4);
|
||||
@@ -3800,6 +3866,10 @@ body[data-section="tailwind"] .code-block {
|
||||
border-color: rgba(26, 175, 184, 0.4);
|
||||
}
|
||||
|
||||
body[data-section="markdown"] .code-block {
|
||||
border-color: rgba(91, 141, 217, 0.4);
|
||||
}
|
||||
|
||||
/* Section code block CodeMirror syntax highlighting overrides */
|
||||
body[data-section="css"] .code-block .cm-editor .cm-line {
|
||||
color: #c9c0e0;
|
||||
@@ -3813,6 +3883,10 @@ body[data-section="tailwind"] .code-block .cm-editor .cm-line {
|
||||
color: #c0e0e8;
|
||||
}
|
||||
|
||||
body[data-section="markdown"] .code-block .cm-editor .cm-line {
|
||||
color: #c0d0e8;
|
||||
}
|
||||
|
||||
/* Task instruction bubble section colors */
|
||||
[data-section="css"] .task-instruction {
|
||||
background: rgba(145, 99, 184, 0.92);
|
||||
@@ -3826,6 +3900,10 @@ body[data-section="tailwind"] .code-block .cm-editor .cm-line {
|
||||
background: rgba(26, 175, 184, 0.92);
|
||||
}
|
||||
|
||||
[data-section="markdown"] .task-instruction {
|
||||
background: rgba(91, 141, 217, 0.92);
|
||||
}
|
||||
|
||||
/* Section page progress bar colors */
|
||||
body[data-section="css"] .section-progress-bar .progress-fill {
|
||||
background: #9163b8;
|
||||
@@ -3839,6 +3917,10 @@ body[data-section="tailwind"] .section-progress-bar .progress-fill {
|
||||
background: #1aafb8;
|
||||
}
|
||||
|
||||
body[data-section="markdown"] .section-progress-bar .progress-fill {
|
||||
background: #5b8dd9;
|
||||
}
|
||||
|
||||
/* Section page header colors */
|
||||
[data-section="css"] .section-hero h1 {
|
||||
color: #9163b8;
|
||||
@@ -3852,6 +3934,10 @@ body[data-section="tailwind"] .section-progress-bar .progress-fill {
|
||||
color: #1aafb8;
|
||||
}
|
||||
|
||||
[data-section="markdown"] .section-hero h1 {
|
||||
color: #5b8dd9;
|
||||
}
|
||||
|
||||
/* Lesson title h2 section colors */
|
||||
body[data-section="css"] #lesson-title {
|
||||
color: #9163b8;
|
||||
@@ -3865,6 +3951,10 @@ body[data-section="tailwind"] #lesson-title {
|
||||
color: #1aafb8;
|
||||
}
|
||||
|
||||
body[data-section="markdown"] #lesson-title {
|
||||
color: #5b8dd9;
|
||||
}
|
||||
|
||||
/* Section and Reference footer - override landing-footer styles */
|
||||
.section-footer.landing-footer,
|
||||
.reference-footer.landing-footer {
|
||||
|
||||
@@ -27,7 +27,7 @@ describe("Lessons Config Module", () => {
|
||||
modules.forEach((module) => {
|
||||
module.lessons.forEach((lesson) => {
|
||||
expect(lesson.mode).toBeDefined();
|
||||
expect(["html", "css", "tailwind", "playground"]).toContain(lesson.mode);
|
||||
expect(["html", "css", "tailwind", "markdown", "playground"]).toContain(lesson.mode);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user