Eget flashcard-sett (category: "fasttrack") som tester huskereglene, skillene og eksamensfellene fra de fem Fast-track-modulene — kort og konsist, uten ukesoverlappen. - flashcards.json: 30 nye kort (7/5/7/7/4 per modul), alle med kort front - flashcards.js: «Fast-track»-filter, categoryLabel, og fcInit(initialFilter) - app.js: ruter #/flashcards/<filter> for dyplenke - index.html: «Start flashcards →»-CTA på Fast-track-landingssiden - style.css: stil for test-CTA Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
194 lines
7.0 KiB
JavaScript
194 lines
7.0 KiB
JavaScript
// =====================================================
|
|
// App — router, sidebar, theme toggle
|
|
// =====================================================
|
|
|
|
const view = document.getElementById('view');
|
|
const crumbCurrent = document.getElementById('crumbCurrent');
|
|
|
|
function parseRoute() {
|
|
const hash = location.hash.replace(/^#\/?/, '');
|
|
if (!hash || hash === '/') return { name: 'home' };
|
|
const parts = hash.split('/').filter(Boolean);
|
|
if (parts[0] === 'uke' && parts[1]) return { name: 'uke', weekId: parseInt(parts[1], 10) };
|
|
if (parts[0] === 'tema' && parts[1]) return { name: 'tema', temaId: parts[1] };
|
|
if (parts[0] === 'fast-track') {
|
|
return parts[1] ? { name: 'fasttrack-module', moduleId: parseInt(parts[1], 10) } : { name: 'fasttrack' };
|
|
}
|
|
if (parts[0] === 'tldr') return { name: 'tldr' };
|
|
if (parts[0] === 'flashcards') return { name: 'flashcards', filter: parts[1] || null };
|
|
if (parts[0] === 'quiz') return { name: 'quiz' };
|
|
if (parts[0] === 'eksamen') return { name: 'eksamen' };
|
|
return { name: 'home' };
|
|
}
|
|
|
|
async function route() {
|
|
const r = parseRoute();
|
|
view.innerHTML = '';
|
|
let content;
|
|
let crumb = 'Oversikt';
|
|
|
|
switch (r.name) {
|
|
case 'home':
|
|
content = SMF.renderHome();
|
|
crumb = 'Oversikt';
|
|
break;
|
|
case 'uke':
|
|
content = await SMF.renderLesson(r.weekId);
|
|
const w = SMF.WEEKS.find(x => x.id === r.weekId);
|
|
crumb = w ? `Uke ${w.id} · ${w.title}` : 'Ukjent uke';
|
|
break;
|
|
case 'tema':
|
|
content = await SMF.renderTema(r.temaId);
|
|
const t = SMF.getTheme(r.temaId);
|
|
crumb = t ? t.label : 'Tema';
|
|
break;
|
|
case 'fasttrack':
|
|
content = await SMF.renderFastTrackHome();
|
|
crumb = 'Fast-track';
|
|
break;
|
|
case 'fasttrack-module':
|
|
content = await SMF.renderFastTrackModule(r.moduleId);
|
|
const m = SMF.getFastTrack(r.moduleId);
|
|
crumb = m ? `Fast-track · ${m.title}` : 'Fast-track';
|
|
break;
|
|
case 'tldr':
|
|
content = await SMF.renderTldr();
|
|
crumb = 'tl;dr · i farta';
|
|
break;
|
|
case 'flashcards':
|
|
content = document.getElementById('t-flashcards').content.cloneNode(true);
|
|
crumb = 'Flashcards';
|
|
break;
|
|
case 'quiz':
|
|
content = document.getElementById('t-quiz').content.cloneNode(true);
|
|
crumb = 'Selvtest';
|
|
break;
|
|
case 'eksamen':
|
|
content = document.getElementById('t-eksamen').content.cloneNode(true);
|
|
crumb = 'Eksamenstrener';
|
|
break;
|
|
}
|
|
|
|
view.appendChild(content);
|
|
crumbCurrent.textContent = crumb;
|
|
|
|
// After DOM is in place, init relevant mode
|
|
if (r.name === 'flashcards') await SMF.fcInit(r.filter);
|
|
else if (r.name === 'quiz') await SMF.quizInit();
|
|
else if (r.name === 'eksamen') await SMF.examInit();
|
|
|
|
updateActiveLinks(r);
|
|
closeSidebar();
|
|
window.scrollTo(0, 0);
|
|
}
|
|
|
|
function updateActiveLinks(r) {
|
|
document.querySelectorAll('.sidebar__link').forEach(a => {
|
|
a.classList.remove('sidebar__link--active');
|
|
});
|
|
if (r.name === 'home') {
|
|
document.querySelector('.sidebar__link[data-route="home"]')?.classList.add('sidebar__link--active');
|
|
} else if (r.name === 'tldr') {
|
|
document.querySelector('.sidebar__link[data-route="tldr"]')?.classList.add('sidebar__link--active');
|
|
} else if (r.name === 'fasttrack' || r.name === 'fasttrack-module') {
|
|
document.querySelector('.sidebar__link[data-route="fasttrack"]')?.classList.add('sidebar__link--active');
|
|
} else if (r.name === 'flashcards') {
|
|
document.querySelector('.sidebar__link[data-route="flashcards"]')?.classList.add('sidebar__link--active');
|
|
} else if (r.name === 'quiz') {
|
|
document.querySelector('.sidebar__link[data-route="quiz"]')?.classList.add('sidebar__link--active');
|
|
} else if (r.name === 'eksamen') {
|
|
document.querySelector('.sidebar__link[data-route="eksamen"]')?.classList.add('sidebar__link--active');
|
|
} else if (r.name === 'uke') {
|
|
document.querySelector(`.sidebar__link[data-week="${r.weekId}"]`)?.classList.add('sidebar__link--active');
|
|
} else if (r.name === 'tema') {
|
|
// No specific active link for tema (could be added)
|
|
}
|
|
}
|
|
|
|
function renderWeekNav() {
|
|
const nav = document.getElementById('weekNav');
|
|
if (!nav) return;
|
|
nav.innerHTML = SMF.WEEKS.map(w => {
|
|
return `
|
|
<a href="#/uke/${w.id}" data-week="${w.id}" class="sidebar__link">
|
|
<span class="sidebar__link-num">${String(w.id).padStart(2, '0')}</span>
|
|
<span>${w.title}</span>
|
|
</a>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
// ============= Sidebar (mobile) =============
|
|
function openSidebar() {
|
|
document.getElementById('sidebar').classList.add('sidebar--open');
|
|
document.getElementById('sidebarScrim').classList.add('sidebar-scrim--open');
|
|
}
|
|
|
|
function closeSidebar() {
|
|
document.getElementById('sidebar').classList.remove('sidebar--open');
|
|
document.getElementById('sidebarScrim').classList.remove('sidebar-scrim--open');
|
|
}
|
|
|
|
document.getElementById('sidebarToggle')?.addEventListener('click', () => {
|
|
if (document.getElementById('sidebar').classList.contains('sidebar--open')) closeSidebar();
|
|
else openSidebar();
|
|
});
|
|
document.getElementById('sidebarScrim')?.addEventListener('click', closeSidebar);
|
|
|
|
// ============= Theme toggle =============
|
|
function applyTheme(theme) {
|
|
document.documentElement.dataset.theme = theme;
|
|
localStorage.setItem('smf-theme', theme);
|
|
// Update icon
|
|
const icon = document.getElementById('themeIcon');
|
|
if (theme === 'dark') {
|
|
icon.innerHTML = `<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>`;
|
|
} else {
|
|
icon.innerHTML = `<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>`;
|
|
}
|
|
}
|
|
|
|
function initTheme() {
|
|
// dark default; only switch to light if explicitly saved
|
|
const saved = localStorage.getItem('smf-theme');
|
|
applyTheme(saved === 'light' ? 'light' : 'dark');
|
|
}
|
|
|
|
document.getElementById('themeBtn').addEventListener('click', () => {
|
|
const current = document.documentElement.dataset.theme;
|
|
applyTheme(current === 'dark' ? 'light' : 'dark');
|
|
});
|
|
|
|
// ============= Countdown =============
|
|
function updateCountdown() {
|
|
const el = document.getElementById('examCountdown');
|
|
if (!el) return;
|
|
const c = SMF.daysToExam();
|
|
el.textContent = c.label;
|
|
}
|
|
|
|
// ============= Init =============
|
|
async function init() {
|
|
initTheme();
|
|
renderWeekNav();
|
|
updateCountdown();
|
|
SMF.attachSearch();
|
|
|
|
// Sidebar brand link
|
|
document.querySelectorAll('[data-route="home"]').forEach(a => {
|
|
a.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
location.hash = '';
|
|
});
|
|
});
|
|
|
|
// Initial route
|
|
await route();
|
|
// Build search index in background
|
|
setTimeout(() => SMF.buildSearchIndex().catch(console.warn), 500);
|
|
}
|
|
|
|
window.addEventListener('hashchange', route);
|
|
window.addEventListener('DOMContentLoaded', init);
|
|
if (document.readyState !== 'loading') init();
|