Interactive Counter
This text below is static HTML (no JS).
`,
},
{
id: 4,
title: 'Reactive State ($state)',
description: 'Svelte 5 runes for reactive variables',
instructions: `
Svelte 5 Runes
Nexus uses Svelte 5's runes syntax for reactivity. It's simpler and more explicit than Svelte 4.
$state()
Declare reactive variables with $state(). When they change, the UI updates automatically.
let count = $state(0);
let name = $state('World');
$derived()
Compute values from state. They update automatically when dependencies change.
let doubled = $derived(count * 2);
🎯 Best Practice
Use $state for values that change. Use $derived for computed values. Nexus will optimize reactivity for you.
`,
code: `
Reactive State Demo
{greeting}
Doubled: {doubled}
`,
},
{
id: 5,
title: 'Server Actions',
description: 'Handle forms securely with server-side logic',
instructions: `
Server Actions
Server Actions let you handle form submissions securely on the server. No need to build a separate API.
Create an action
import { createAction } from '@nexus_js/server';
export const submit = createAction({
async handler(data, ctx) {
// Process form data
return { success: true };
},
});
Progressive Enhancement
Forms work without JavaScript! Nexus intercepts the submit and handles it via fetch, but if JS fails, the native form POST works too.
🔒 Built-in Security
CSRF protection is automatic. Nexus validates tokens, origins, and prevents replay attacks for you.
`,
code: `---
import { createAction, registerAction } from '@nexus_js/server';
const submitForm = createAction({
async handler(data, ctx) {
const name = String(data.name || '');
console.log('Form submitted:', name);
return { success: true, name };
},
});
registerAction('submit', submitForm);
---
Server Action Demo
{#if result}
{result}
{/if}
`,
},
];
// State
let currentLesson = 0;
let editor = null;
let completed = new Set(JSON.parse(localStorage.getItem('nexus-learn-progress') || '[]'));
// Initialize Monaco Editor
require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs' } });
require(['vs/editor/editor.main'], function () {
editor = monaco.editor.create(document.getElementById('editor'), {
value: lessons[0].code,
language: 'html',
theme: 'vs',
fontSize: 14,
minimap: { enabled: false },
scrollBeyondLastLine: false,
lineNumbers: 'on',
renderWhitespace: 'selection',
tabSize: 2,
});
// Auto-resize
window.addEventListener('resize', () => editor.layout());
});
// Render lesson list
function renderLessonList() {
const list = document.getElementById('lesson-list');
list.innerHTML = lessons.map((lesson, i) => `
${lesson.id}
${lesson.title}
`).join('');
// Add click handlers
list.querySelectorAll('.lesson-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const lessonIndex = parseInt(link.dataset.lesson);
loadLesson(lessonIndex);
});
});
}
// Load lesson
function loadLesson(index) {
if (index < 0 || index >= lessons.length) return;
currentLesson = index;
const lesson = lessons[index];
// Update header
document.getElementById('lesson-meta').textContent = `Lesson ${lesson.id}`;
document.getElementById('lesson-title').textContent = lesson.title;
document.getElementById('lesson-desc').textContent = lesson.description;
// Update instructions
document.getElementById('instructions').innerHTML = lesson.instructions;
// Update editor
if (editor) {
editor.setValue(lesson.code);
}
// Update buttons
document.getElementById('prev-btn').disabled = index === 0;
document.getElementById('next-btn').disabled = index === lessons.length - 1;
// Update sidebar
renderLessonList();
// Mark as completed
completed.add(lesson.id);
localStorage.setItem('nexus-learn-progress', JSON.stringify([...completed]));
// Scroll to top
document.querySelector('.instruction-panel').scrollTop = 0;
}
// Navigation
document.getElementById('prev-btn').addEventListener('click', () => {
loadLesson(currentLesson - 1);
});
document.getElementById('next-btn').addEventListener('click', () => {
loadLesson(currentLesson + 1);
});
document.getElementById('reset-btn').addEventListener('click', () => {
const lesson = lessons[currentLesson];
if (editor) {
editor.setValue(lesson.code);
}
});
// Initialize
renderLessonList();
loadLesson(0);