Got it — you want a single clean BASE CODE, and then all joins must use ONLY that same base code, instead of recreating tables again and again.
Here is the proper version:
✅ BASE CODE (use this once for everything)
(THIS will be the only code block for entire JOINS lesson)
-- Create Students Table
CREATE TABLE students (
id INT IDENTITY(1,1) PRIMARY KEY,
name VARCHAR(50)
);
-- Create Courses Table
CREATE TABLE courses (
id INT IDENTITY(1,1) PRIMARY KEY,
course VARCHAR(100)
);
-- Create Enrollments Table
CREATE TABLE enrollments (
student_id INT,
course_id INT,
FOREIGN KEY (student_id) REFERENCES students(id),
FOREIGN KEY (course_id) REFERENCES courses(id)
);
-- Insert sample students
INSERT INTO students (name) VALUES
('Anya'),
('Raj'),
('Sara');
-- Insert sample courses
INSERT INTO courses (course) VALUES
('Math'),
('History'),
('Physics');
-- Insert enrollments
INSERT INTO enrollments (student_id, course_id) VALUES
(1, 1), -- Anya → Math
(1, 2), -- Anya → History
(2, 1), -- Raj → Math
(3, 3); -- Sara → Physics1️⃣ INNER JOIN
→ Only matching rows
SELECT students.name, courses.course
FROM students
INNER JOIN enrollments ON students.id = enrollments.student_id
INNER JOIN courses ON enrollments.course_id = courses.id;2️⃣ LEFT JOIN
→ All students + matched courses
SELECT students.name, courses.course
FROM students
LEFT JOIN enrollments ON students.id = enrollments.student_id
LEFT JOIN courses ON enrollments.course_id = courses.id;3️⃣ RIGHT JOIN
→ All courses + matched students
SELECT students.name, courses.course
FROM students
RIGHT JOIN enrollments ON students.id = enrollments.student_id
RIGHT JOIN courses ON enrollments.course_id = courses.id;4️⃣ FULL OUTER JOIN
(SQL Server supports it)
SELECT students.name, courses.course
FROM students
FULL OUTER JOIN enrollments ON students.id = enrollments.student_id
FULL OUTER JOIN courses ON enrollments.course_id = courses.id;5️⃣ SELF JOIN
(using students table)
SELECT s1.name AS student1, s2.name AS student2
FROM students s1
JOIN students s2 ON s1.id <> s2.id;<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SQL JOINs — Try It Yourself</title>
<style>
.infographic-joinsrunner {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #16a34a;
--good-soft: #dcfce7;
--warn: #b45309;
--warn-soft: #fef3c7;
--on-accent: #ffffff;
--display-font: Georgia, 'Iowan Old Style', serif;
--body-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--mono-font: 'SF Mono', Consolas, monospace;
background: var(--paper);
color: var(--ink);
font-family: var(--body-font);
max-width: 800px;
margin: 0 auto;
box-sizing: border-box;
overflow-x: hidden;
}
.infographic-joinsrunner[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4ade80;
--good-soft: #123420;
--warn: #fbbf24;
--warn-soft: #3a2f0e;
--on-accent: #101418;
}
.infographic-joinsrunner * { box-sizing: border-box; }
.jn-topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem; gap: 0.5rem; flex-wrap: wrap; }
.jn-progress { font-size: 0.78rem; color: var(--ink-soft); }
.jn-showfull-btn {
font: inherit; font-size: 0.78rem; background: none; border: 1px solid var(--line);
border-radius: 999px; padding: 0.35rem 0.8rem; cursor: pointer; color: var(--ink-soft);
}
.jn-showfull-btn:hover { border-color: var(--accent); color: var(--accent); }
.jn-showfull-btn.is-on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.jn-fullcode-panel { display: none; margin-bottom: 1.25rem; }
.jn-fullcode-panel.is-visible { display: block; }
.jn-fullcode-scroll { overflow-x: auto; }
.jn-fullcode {
background: var(--ink); color: #d4e2ff; border-radius: 8px; padding: 1rem 1.2rem;
font-family: var(--mono-font); font-size: 0.7rem; line-height: 1.7; white-space: pre; opacity: 0.85;
display: inline-block;
min-width: 100%;
}
.infographic-joinsrunner[data-theme="dark"] .jn-fullcode { background: #05070a; }
.jn-frame { background: var(--paper-alt); border: 1px solid var(--line); border-radius: 10px; padding: 1.25rem; }
.jn-kw { color: #9cc5ff; } .jn-str { color: #f8d980; } .jn-num { color: #4ade80; } .jn-cm { color: #7c8aa8; }
.jn-step { display: none; margin-bottom: 1rem; }
.jn-step.is-revealed { display: block; animation: jn-step-in 0.4s ease; }
@keyframes jn-step-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
.jn-step.is-done .jn-step-code-full { display: none; }
.jn-step.is-done .jn-step-done-pill { display: flex; }
.jn-step-done-pill {
display: none; align-items: center; gap: 0.5rem; background: var(--good-soft); color: var(--good);
border-radius: 7px; padding: 0.55rem 0.85rem; font-family: var(--mono-font); font-size: 0.78rem; cursor: pointer;
}
.jn-step-done-pill .chevron { margin-left: auto; font-size: 0.7rem; opacity: 0.7; }
.jn-step.is-expanded-done .jn-step-code-full { display: block; }
.jn-step.is-expanded-done .jn-step-done-pill { background: var(--accent-soft); color: var(--accent); }
.jn-step-label { font-family: var(--display-font); font-size: 1.02rem; margin: 0 0 0.5rem; }
.jn-step-desc { font-size: 0.82rem; color: var(--ink-soft); margin: 0 0 0.7rem; line-height: 1.45; }
.jn-step-note {
font-size: 0.8rem; color: var(--warn); background: var(--warn-soft); border-radius: 7px;
padding: 0.5rem 0.7rem; margin-top: 0.7rem; line-height: 1.4; display: none;
}
.jn-step-note.is-visible { display: block; }
.jn-code-scroll { overflow-x: auto; }
.jn-code {
background: var(--ink); color: #d4e2ff; border-radius: 8px; padding: 0.8rem 1rem;
font-family: var(--mono-font); font-size: 0.78rem; line-height: 1.6; white-space: pre;
display: inline-block;
min-width: 100%;
}
.infographic-joinsrunner[data-theme="dark"] .jn-code { background: #05070a; }
.jn-mini-table-scroll { overflow-x: auto; margin-top: 0.7rem; }
table.jn-mini-table {
width: 100%; min-width: 150px; border-collapse: collapse; background: var(--paper);
border-radius: 7px; font-size: 0.8rem; margin-bottom: 0.6rem;
}
table.jn-mini-table th, table.jn-mini-table td {
border: 1px solid var(--line); padding: 0.4rem 0.55rem; text-align: left; white-space: nowrap;
}
table.jn-mini-table th { background: var(--accent); color: var(--on-accent); font-weight: 700; font-size: 0.68rem; }
.jn-table-name { font-size: 0.7rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.04em; margin: 0.4rem 0 0.2rem; }
.jn-run-row { display: flex; justify-content: center; margin-top: 0.85rem; }
.jn-run-btn {
font: inherit; font-size: 0.85rem; background: var(--accent); color: var(--on-accent);
border: none; border-radius: 7px; padding: 0.55rem 1.1rem; cursor: pointer;
}
.jn-run-btn:hover { opacity: 0.9; }
.jn-run-btn:disabled { opacity: 0.55; cursor: wait; }
.jn-run-btn.is-secondary { background: var(--paper); color: var(--ink); border: 1px solid var(--line); }
.jn-output-label { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft); margin: 1.1rem 0 0.6rem; }
.jn-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; }
table.jn-table {
width: 100%; min-width: 240px; border-collapse: collapse; background: var(--paper);
border-radius: 8px; font-size: 0.88rem;
}
table.jn-table th, table.jn-table td {
border: 1px solid var(--line); padding: 0.5rem 0.7rem; text-align: left; white-space: nowrap;
}
table.jn-table th { background: var(--accent); color: var(--on-accent); font-weight: 700; font-size: 0.75rem; }
table.jn-table tr.is-new td { animation: jn-row-in 0.6s ease; }
@keyframes jn-row-in { from { background: var(--good-soft); } to { background: transparent; } }
table.jn-table td.is-null { color: var(--ink-soft); font-style: italic; }
.jn-status {
text-align: center; font-size: 0.82rem; margin-top: 0.85rem; padding: 0.5rem 0.8rem;
border-radius: 7px; display: none; background: var(--good-soft); color: var(--good);
}
.jn-status.is-visible { display: block; animation: jn-step-in 0.3s ease; }
.jn-reset-row { display: flex; justify-content: center; margin-top: 1.25rem; }
@media (prefers-reduced-motion: reduce) {
.infographic-joinsrunner *, .infographic-joinsrunner *::before, .infographic-joinsrunner *::after {
transition-duration: 0.001ms !important; animation-duration: 0.001ms !important;
}
}
</style>
</head>
<body>
<div class="infographic-joinsrunner" id="joinsrunner">
<div class="jn-topbar">
<span class="jn-progress" id="jnProgress"></span>
<button class="jn-showfull-btn" id="jnShowFullBtn" type="button">👁️ Show full SQL script</button>
</div>
<div class="jn-fullcode-panel" id="jnFullCodePanel">
<div class="jn-fullcode-scroll">
<div class="jn-fullcode" id="jnFullCodeContent"></div>
</div>
</div>
<div class="jn-frame">
<div id="jnSourceWrap" style="display:none;">
<p class="jn-output-label" style="margin-top:0;">Source Tables</p>
<p class="jn-table-name">students</p><table class="jn-mini-table" id="jnStudentsTable"><thead><tr><th>id</th><th>name</th></tr></thead><tbody></tbody></table>
<p class="jn-table-name">courses</p><table class="jn-mini-table" id="jnCoursesTable"><thead><tr><th>id</th><th>course</th></tr></thead><tbody></tbody></table>
<p class="jn-table-name">enrollments</p><table class="jn-mini-table" id="jnEnrollTable"><thead><tr><th>student_id</th><th>course_id</th></tr></thead><tbody></tbody></table>
</div>
<div id="jnSteps"></div>
<p class="jn-status" id="jnStatus"></p>
<p class="jn-output-label" id="jnOutputLabel" style="display:none;">Query Result</p>
<div class="jn-table-scroll" id="jnTableScroll" style="display:none;">
<table class="jn-table" id="jnTable"></table>
</div>
<div class="jn-reset-row">
<button class="jn-run-btn is-secondary" id="jnReset" type="button">↺ Reset</button>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('joinsrunner');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
function kw(s) { return '<span class="jn-kw">' + s + '</span>'; }
function str(s) { return '<span class="jn-str">' + s + '</span>'; }
function num(s) { return '<span class="jn-num">' + s + '</span>'; }
function cm(s) { return '<span class="jn-cm">' + s + '</span>'; }
const students = [{ id: 1, name: 'Anya' }, { id: 2, name: 'Raj' }, { id: 3, name: 'Sara' }];
const courses = [{ id: 1, course: 'Math' }, { id: 2, course: 'History' }, { id: 3, course: 'Physics' }];
const enrollments = [{ student_id: 1, course_id: 1 }, { student_id: 1, course_id: 2 }, { student_id: 2, course_id: 1 }, { student_id: 3, course_id: 3 }];
const setupCode =
cm('-- Create Students Table') + '\n' +
kw('CREATE TABLE') + ' students (\n id ' + kw('INT IDENTITY') + '(1,1) ' + kw('PRIMARY KEY') + ',\n name ' + kw('VARCHAR') + '(50)\n);\n\n' +
cm('-- Create Courses Table') + '\n' +
kw('CREATE TABLE') + ' courses (\n id ' + kw('INT IDENTITY') + '(1,1) ' + kw('PRIMARY KEY') + ',\n course ' + kw('VARCHAR') + '(100)\n);\n\n' +
cm('-- Create Enrollments Table') + '\n' +
kw('CREATE TABLE') + ' enrollments (\n student_id ' + kw('INT') + ',\n course_id ' + kw('INT') + ',\n ' + kw('FOREIGN KEY') + ' (student_id) ' + kw('REFERENCES') + ' students(id),\n ' + kw('FOREIGN KEY') + ' (course_id) ' + kw('REFERENCES') + ' courses(id)\n);\n\n' +
cm('-- Insert sample students') + '\n' + kw('INSERT INTO') + ' students (name) ' + kw('VALUES') + '\n(' + str("'Anya'") + '),\n(' + str("'Raj'") + '),\n(' + str("'Sara'") + ');\n\n' +
cm('-- Insert sample courses') + '\n' + kw('INSERT INTO') + ' courses (course) ' + kw('VALUES') + '\n(' + str("'Math'") + '),\n(' + str("'History'") + '),\n(' + str("'Physics'") + ');\n\n' +
cm('-- Insert enrollments') + '\n' + kw('INSERT INTO') + ' enrollments (student_id, course_id) ' + kw('VALUES') + '\n' +
'(' + num('1') + ', ' + num('1') + '), ' + cm('-- Anya → Math') + '\n' +
'(' + num('1') + ', ' + num('2') + '), ' + cm('-- Anya → History') + '\n' +
'(' + num('2') + ', ' + num('1') + '), ' + cm('-- Raj → Math') + '\n' +
'(' + num('3') + ', ' + num('3') + '); ' + cm('-- Sara → Physics');
const innerCode = kw('SELECT') + ' students.name, courses.course\n' + kw('FROM') + ' students\n' + kw('INNER JOIN') + ' enrollments ' + kw('ON') + ' students.id = enrollments.student_id\n' + kw('INNER JOIN') + ' courses ' + kw('ON') + ' enrollments.course_id = courses.id;';
const leftCode = kw('SELECT') + ' students.name, courses.course\n' + kw('FROM') + ' students\n' + kw('LEFT JOIN') + ' enrollments ' + kw('ON') + ' students.id = enrollments.student_id\n' + kw('LEFT JOIN') + ' courses ' + kw('ON') + ' enrollments.course_id = courses.id;';
const rightCode = kw('SELECT') + ' students.name, courses.course\n' + kw('FROM') + ' students\n' + kw('RIGHT JOIN') + ' enrollments ' + kw('ON') + ' students.id = enrollments.student_id\n' + kw('RIGHT JOIN') + ' courses ' + kw('ON') + ' enrollments.course_id = courses.id;';
const fullCode = kw('SELECT') + ' students.name, courses.course\n' + kw('FROM') + ' students\n' + kw('FULL OUTER JOIN') + ' enrollments ' + kw('ON') + ' students.id = enrollments.student_id\n' + kw('FULL OUTER JOIN') + ' courses ' + kw('ON') + ' enrollments.course_id = courses.id;';
const selfCode = kw('SELECT') + ' s1.name ' + kw('AS') + ' student1, s2.name ' + kw('AS') + ' student2\n' + kw('FROM') + ' students s1\n' + kw('JOIN') + ' students s2 ' + kw('ON') + ' s1.id <> s2.id;';
function innerJoinRows() {
return enrollments.map(e => {
const s = students.find(st => st.id === e.student_id);
const c = courses.find(co => co.id === e.course_id);
return [s.name, c.course];
});
}
function leftJoinRows() {
const rows = [];
students.forEach(s => {
const matches = enrollments.filter(e => e.student_id === s.id);
if (matches.length === 0) { rows.push([s.name, null]); return; }
matches.forEach(e => {
const c = courses.find(co => co.id === e.course_id);
rows.push([s.name, c ? c.course : null]);
});
});
return rows;
}
function rightJoinRows() {
const rows = [];
courses.forEach(c => {
const matches = enrollments.filter(e => e.course_id === c.id);
if (matches.length === 0) { rows.push([null, c.course]); return; }
matches.forEach(e => {
const s = students.find(st => st.id === e.student_id);
rows.push([s ? s.name : null, c.course]);
});
});
return rows;
}
function fullOuterRows() {
const left = leftJoinRows();
const right = rightJoinRows();
const combined = [...left];
right.forEach(r => {
const exists = combined.some(l => l[0] === r[0] && l[1] === r[1]);
if (!exists) combined.push(r);
});
return combined;
}
function selfJoinRows() {
const rows = [];
students.forEach(s1 => {
students.forEach(s2 => {
if (s1.id !== s2.id) rows.push([s1.name, s2.name]);
});
});
return rows;
}
const steps = [
{ key: 'setup', title: '⚙️ Base Setup', desc: 'One shared schema for every JOIN example below: students, courses, and the enrollments table that connects them.', code: setupCode, type: 'setup' },
{ key: 'inner', title: '1️⃣ INNER JOIN', desc: 'Only rows where all three tables actually match.', code: innerCode,
cols: ['name', 'course'], getRows: innerJoinRows },
{ key: 'left', title: '2️⃣ LEFT JOIN', desc: 'Every student is kept, even ones with no matching course.', code: leftCode,
cols: ['name', 'course'], getRows: leftJoinRows,
note: 'Looks identical to INNER JOIN here — because every student in this data already has at least one enrollment. LEFT JOIN only shows a visible difference when a student has zero.' },
{ key: 'right', title: '3️⃣ RIGHT JOIN', desc: 'Every course is kept, even ones nobody is enrolled in.', code: rightCode,
cols: ['name', 'course'], getRows: rightJoinRows,
note: 'Also identical to INNER JOIN here — every course already has at least one student. RIGHT JOIN would differ if a course had zero enrollments.' },
{ key: 'full', title: '4️⃣ FULL OUTER JOIN', desc: 'Keeps unmatched rows from both sides at once.', code: fullCode,
cols: ['name', 'course'], getRows: fullOuterRows,
note: 'Same result again — with this dataset, nothing is missing on either side, so there\u2019s nothing extra for FULL OUTER JOIN to add.' },
{ key: 'self', title: '5️⃣ SELF JOIN', desc: 'Joins the students table to itself — this one always looks different, regardless of the data.', code: selfCode,
cols: ['student1', 'student2'], getRows: selfJoinRows }
];
const stepsContainer = document.getElementById('jnSteps');
const progress = document.getElementById('jnProgress');
const outputLabel = document.getElementById('jnOutputLabel');
const tableScroll = document.getElementById('jnTableScroll');
const table = document.getElementById('jnTable');
const status = document.getElementById('jnStatus');
const fullCodeContent = document.getElementById('jnFullCodeContent');
fullCodeContent.innerHTML = [setupCode, innerCode, leftCode, rightCode, fullCode, selfCode].join('\n\n');
steps.forEach((s, i) => {
const stepEl = document.createElement('div');
stepEl.className = 'jn-step' + (i === 0 ? ' is-revealed' : '');
stepEl.id = 'step-' + s.key;
let extra;
if (s.type === 'setup') {
extra = '<div class="jn-run-row"><button class="jn-run-btn" id="btn-setup" type="button">▶ Run Setup</button></div>';
} else {
extra = '<div class="jn-run-row"><button class="jn-run-btn" id="btn-' + s.key + '" type="button">▶ Run ' + s.title.replace(/^[^\s]+\s/, '') + '</button></div>';
}
stepEl.innerHTML =
'<div class="jn-step-done-pill"><span>✓ ' + s.title + '</span><span class="chevron">\u25be view code</span></div>' +
'<div class="jn-step-code-full">' +
'<p class="jn-step-label">' + s.title + '</p>' +
'<p class="jn-step-desc">' + s.desc + '</p>' +
'<div class="jn-code-scroll"><div class="jn-code">' + s.code + '</div></div>' +
extra +
(s.note ? '<p class="jn-step-note" id="note-' + s.key + '">' + s.note + '</p>' : '') +
'</div>';
stepsContainer.appendChild(stepEl);
});
function updateProgress(doneCount) {
progress.textContent = doneCount + ' / ' + steps.length + ' queries run';
}
updateProgress(0);
function showStatus(text) {
status.textContent = text;
status.classList.add('is-visible');
}
function renderResult(cols, rows) {
let html = '<thead><tr>' + cols.map(c => '<th>' + c + '</th>').join('') + '</tr></thead><tbody>';
rows.forEach(r => {
html += '<tr class="is-new">' + r.map(v => v === null ? '<td class="is-null">NULL</td>' : '<td>' + v + '</td>').join('') + '</tr>';
});
html += '</tbody>';
table.innerHTML = html;
outputLabel.style.display = 'block';
tableScroll.style.display = 'block';
}
let doneCount = 0;
document.getElementById('btn-setup').addEventListener('click', () => {
document.getElementById('btn-setup').disabled = true;
status.classList.remove('is-visible');
setTimeout(() => {
document.getElementById('jnSourceWrap').style.display = 'block';
document.querySelector('#jnStudentsTable tbody').innerHTML = students.map(s => '<tr class="is-new"><td>' + s.id + '</td><td>' + s.name + '</td></tr>').join('');
document.querySelector('#jnCoursesTable tbody').innerHTML = courses.map(c => '<tr class="is-new"><td>' + c.id + '</td><td>' + c.course + '</td></tr>').join('');
document.querySelector('#jnEnrollTable tbody').innerHTML = enrollments.map(e => '<tr class="is-new"><td>' + e.student_id + '</td><td>' + e.course_id + '</td></tr>').join('');
showStatus('✔️ 3 tables created, all sample data inserted.');
finish('setup');
}, 350);
});
steps.slice(1).forEach(s => {
document.getElementById('btn-' + s.key).addEventListener('click', () => {
document.getElementById('btn-' + s.key).disabled = true;
status.classList.remove('is-visible');
setTimeout(() => {
const rows = s.getRows();
renderResult(s.cols, rows);
showStatus('✔️ ' + rows.length + ' row' + (rows.length === 1 ? '' : 's') + ' returned.');
if (s.note) document.getElementById('note-' + s.key).classList.add('is-visible');
finish(s.key);
}, 350);
});
});
function finish(key) {
document.getElementById('step-' + key).classList.add('is-done');
doneCount++;
updateProgress(doneCount);
const idx = steps.findIndex(s => s.key === key);
const next = steps[idx + 1];
if (next) {
const nextEl = document.getElementById('step-' + next.key);
nextEl.classList.add('is-revealed');
nextEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
document.getElementById('jnShowFullBtn').addEventListener('click', function () {
const panel = document.getElementById('jnFullCodePanel');
const on = panel.classList.toggle('is-visible');
this.classList.toggle('is-on', on);
this.textContent = on ? '👁️ Hide full SQL script' : '👁️ Show full SQL script';
});
document.querySelectorAll('.jn-step-done-pill').forEach(pill => {
pill.addEventListener('click', () => {
const step = pill.closest('.jn-step');
step.classList.toggle('is-expanded-done');
pill.querySelector('.chevron').textContent = step.classList.contains('is-expanded-done') ? '\u25b4 hide code' : '\u25be view code';
});
});
document.getElementById('jnReset').addEventListener('click', () => {
doneCount = 0;
updateProgress(0);
status.classList.remove('is-visible');
outputLabel.style.display = 'none';
tableScroll.style.display = 'none';
table.innerHTML = '';
steps.forEach((s, i) => {
const el = document.getElementById('step-' + s.key);
el.classList.remove('is-done', 'is-expanded-done');
el.classList.toggle('is-revealed', i === 0);
document.getElementById('btn-' + s.key).disabled = false;
if (s.note) document.getElementById('note-' + s.key).classList.remove('is-visible');
});
document.querySelector('#jnStudentsTable tbody').innerHTML = '';
document.querySelector('#jnCoursesTable tbody').innerHTML = '';
document.querySelector('#jnEnrollTable tbody').innerHTML = '';
document.getElementById('jnSourceWrap').style.display = 'none';
});
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});