@curio-sd/e-module-builder 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import { renderExerciseMeta, markExerciseSolved } from './exercise-shared.js'
2
2
  import { sitePath } from '../site-path.js'
3
3
  import { initExternalExercise } from './external-exercise.js'
4
+ import { initTheoryPanel } from './theory-panel.js'
4
5
 
5
6
  async function loadWeekData(weekNum) {
6
7
  return import(`../../data/exercises/week${weekNum}.json`).then((m) => m.default)
@@ -52,6 +53,8 @@ export async function initExercisePage(weekNum) {
52
53
  return
53
54
  }
54
55
 
56
+ initTheoryPanel(exercise.linked_theory)
57
+
55
58
  if (!exercise.type || exercise.type === 'text') {
56
59
  const panel = document.querySelector('[data-exercise-content]')
57
60
  panel?.querySelector('[data-exercise-interactive]')?.classList.add('hidden')
@@ -0,0 +1,95 @@
1
+ import { sitePath } from '../site-path.js'
2
+
3
+ const WEEK_RE = /^([a-zA-Z]+)(\d+)$/
4
+
5
+ const BOOK_SVG = `<svg class="h-4 w-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.966 8.966 0 0 0-6 2.292m0-14.25v14.25" /></svg>`
6
+
7
+ export function parseLinkedTheory(linkedTheory) {
8
+ if (!Array.isArray(linkedTheory) || linkedTheory.length === 0) return []
9
+
10
+ return linkedTheory
11
+ .map((entry) => {
12
+ if (typeof entry !== 'string') return null
13
+ const m = entry.trim().match(WEEK_RE)
14
+ if (!m) return null
15
+ const prefix = m[1]
16
+ const num = parseInt(m[2], 10)
17
+ const label = `${prefix.charAt(0).toUpperCase()}${prefix.slice(1)} ${num}`
18
+ return { key: entry.trim(), label, num, dirName: entry.trim() }
19
+ })
20
+ .filter(Boolean)
21
+ }
22
+
23
+ export function theoryIframeSrc(dirName) {
24
+ return sitePath(`/pages/${dirName}-theorie.html`) + '?embedded=1'
25
+ }
26
+
27
+ export function initTheoryPanel(linkedTheory) {
28
+ const toggle = document.querySelector('[data-theory-toggle]')
29
+ const panel = document.querySelector('[data-theory-panel]')
30
+
31
+ const tabs = parseLinkedTheory(linkedTheory)
32
+
33
+ if (!tabs.length) {
34
+ toggle?.classList.add('hidden')
35
+ panel?.classList.add('panel-hidden')
36
+ return
37
+ }
38
+
39
+ toggle?.classList.remove('hidden')
40
+
41
+ const tabsEl = panel?.querySelector('[data-theory-tabs]')
42
+ const iframe = panel?.querySelector('[data-theory-iframe]')
43
+ const loader = panel?.querySelector('[data-theory-loader]')
44
+
45
+ if (!tabsEl || !iframe || !panel) return
46
+
47
+ function showLoader() { loader?.classList.remove('theory-panel-loader-hidden') }
48
+ function hideLoader() { loader?.classList.add('theory-panel-loader-hidden') }
49
+
50
+ iframe.addEventListener('load', hideLoader)
51
+
52
+ tabsEl.innerHTML = tabs
53
+ .map(
54
+ (t, i) =>
55
+ `<button class="theory-tab${i === 0 ? ' theory-tab-active' : ''}" data-tab="${t.key}" type="button">${t.label}</button>`
56
+ )
57
+ .join('')
58
+
59
+ function activateTab(key) {
60
+ tabsEl.querySelectorAll('[data-tab]').forEach((btn) => {
61
+ btn.classList.toggle('theory-tab-active', btn.dataset.tab === key)
62
+ })
63
+ const tab = tabs.find((t) => t.key === key)
64
+ if (tab) {
65
+ showLoader()
66
+ iframe.src = theoryIframeSrc(tab.dirName)
67
+ }
68
+ }
69
+
70
+ tabsEl.addEventListener('click', (e) => {
71
+ const btn = e.target.closest('[data-tab]')
72
+ if (btn) activateTab(btn.dataset.tab)
73
+ })
74
+
75
+ activateTab(tabs[0].key)
76
+
77
+ const closeBtn = panel.querySelector('[data-theory-panel-close]')
78
+ closeBtn?.addEventListener('click', () => {
79
+ panel.classList.add('panel-hidden')
80
+ toggle?.setAttribute('aria-expanded', 'false')
81
+ })
82
+
83
+ toggle?.addEventListener('click', () => {
84
+ const isHidden = panel.classList.contains('panel-hidden')
85
+ panel.classList.toggle('panel-hidden', !isHidden)
86
+ toggle.setAttribute('aria-expanded', String(isHidden))
87
+ })
88
+
89
+ toggle?.setAttribute('aria-expanded', 'false')
90
+
91
+ // Enable the slide transition after the first frame has been committed so
92
+ // the panel doesn't animate from its off-screen position on page load.
93
+ // Double-rAF ensures this works even when initTheoryPanel is called synchronously.
94
+ requestAnimationFrame(() => requestAnimationFrame(() => panel.classList.add('panel-ready')))
95
+ }
package/src/js/home.js CHANGED
@@ -31,7 +31,7 @@ export function initHome() {
31
31
 
32
32
  if (titleEl) titleEl.textContent = mod.name
33
33
 
34
- if (descEl) descEl.textContent = mod.description
34
+ if (descEl) descEl.innerHTML = mod.description
35
35
 
36
36
 
37
37
 
package/src/js/nav.js CHANGED
@@ -13,9 +13,7 @@ function buildNavItems() {
13
13
  })
14
14
  }
15
15
 
16
- for (const page of manifest.nav.examPages) {
17
- items.push(page)
18
- }
16
+ items.push(manifest.nav.assessmentSection)
19
17
 
20
18
  return items
21
19
  }
@@ -69,16 +67,68 @@ function renderNavGroup(group) {
69
67
  ? `<p class="mt-0.5 text-[10px] font-normal leading-snug text-zinc-500">${group.title}</p>`
70
68
  : ''
71
69
  return `
72
- <div class="space-y-0.5">
73
- <div class="px-3 pb-1 pt-4">
74
- <p class="text-[10px] font-semibold uppercase tracking-[0.14em] text-zinc-400">${group.label}</p>
75
- ${title}
70
+ <div class="space-y-0.5" data-nav-group="${group.label}">
71
+ <button class="nav-group-toggle" data-group-toggle="${group.label}">
72
+ <div>
73
+ <p class="text-[10px] font-semibold uppercase tracking-[0.14em] text-zinc-400">${group.label}</p>
74
+ ${title}
75
+ </div>
76
+ <svg class="nav-group-chevron" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
77
+ <path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
78
+ </svg>
79
+ </button>
80
+ <div class="nav-group-children" data-group-children="${group.label}">
81
+ <div class="nav-group-inner">${childLinks}</div>
76
82
  </div>
77
- ${childLinks}
78
83
  </div>
79
84
  `
80
85
  }
81
86
 
87
+ function initCollapsible(navEl, defaultExpandedKeys) {
88
+ const STORAGE_KEY = 'nav-collapsed-groups'
89
+
90
+ function getCollapsed() {
91
+ try {
92
+ return new Set(JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'))
93
+ } catch {
94
+ return new Set()
95
+ }
96
+ }
97
+
98
+ function saveCollapsed(collapsed) {
99
+ localStorage.setItem(STORAGE_KEY, JSON.stringify([...collapsed]))
100
+ }
101
+
102
+ if (localStorage.getItem(STORAGE_KEY) === null) {
103
+ const allKeys = [...navEl.querySelectorAll('[data-group-toggle]')].map((b) => b.dataset.groupToggle)
104
+ saveCollapsed(new Set(allKeys.filter((k) => !defaultExpandedKeys.has(k))))
105
+ }
106
+
107
+ const collapsed = getCollapsed()
108
+
109
+ navEl.querySelectorAll('[data-group-toggle]').forEach((btn) => {
110
+ const key = btn.dataset.groupToggle
111
+ const children = navEl.querySelector(`[data-group-children="${key}"]`)
112
+ const chevron = btn.querySelector('.nav-group-chevron')
113
+ const hasActive = !!children?.querySelector('.text-white')
114
+
115
+ if (!hasActive && collapsed.has(key)) {
116
+ children?.classList.add('collapsed')
117
+ chevron?.classList.add('-rotate-90')
118
+ }
119
+
120
+ btn.addEventListener('click', () => {
121
+ const nowCollapsed = children?.classList.toggle('collapsed')
122
+ chevron?.classList.toggle('-rotate-90', nowCollapsed)
123
+
124
+ const saved = getCollapsed()
125
+ if (nowCollapsed) saved.add(key)
126
+ else saved.delete(key)
127
+ saveCollapsed(saved)
128
+ })
129
+ })
130
+ }
131
+
82
132
  export function getManifest() {
83
133
  return manifest
84
134
  }
@@ -108,6 +158,11 @@ export function initNav() {
108
158
  <div class="sidebar-scroll space-y-1">${links}</div>
109
159
  </div>
110
160
  `
161
+ const groups = NAV_ITEMS.filter((i) => i.children)
162
+ const defaultExpandedKeys = new Set()
163
+ if (groups[0]) defaultExpandedKeys.add(groups[0].label)
164
+ if (manifest.nav.assessmentSection?.label) defaultExpandedKeys.add(manifest.nav.assessmentSection.label)
165
+ initCollapsible(navEl, defaultExpandedKeys)
111
166
  }
112
167
 
113
168
  export function initMobileNav() {
@@ -70,11 +70,11 @@
70
70
  <span class="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-zinc-900 font-mono text-xs text-white">3</span>
71
71
 
72
72
  <div>
73
- <p class="font-medium text-zinc-900">Eindtoetsen toevoegen (optioneel)</p>
73
+ <p class="font-medium text-zinc-900">Meetmomenten toevoegen (optioneel)</p>
74
74
 
75
75
  <p class="mt-1 text-sm text-zinc-500">
76
- Plaats <code class="font-mono text-xs">content/exams/theory-exam.md</code> en
77
- <code class="font-mono text-xs">practical-exam.md</code> met vragen in YAML frontmatter.
76
+ Plaats <code class="font-mono text-xs">content/assessments/theory-assessment.md</code> en
77
+ <code class="font-mono text-xs">practical-assessment.md</code> met vragen in YAML frontmatter.
78
78
  </p>
79
79
  </div>
80
80
  </li>
@@ -120,12 +120,20 @@ npm run preview # preview van dist/</code></pre>
120
120
  </p>
121
121
  </div>
122
122
 
123
- <p class="mb-6 text-[11px] font-semibold uppercase tracking-[0.18em] text-zinc-400">Curriculum</p>
123
+ <div class="mb-6 flex items-center justify-between">
124
+ <p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-zinc-400">Curriculum</p>
125
+
126
+ <a href="e-module.pdf"
127
+ download
128
+ class="inline-flex items-center gap-1.5 rounded border border-zinc-200 bg-white px-3 py-1.5 text-xs font-medium text-zinc-600 hover:bg-zinc-50">
129
+ Download als PDF
130
+ </a>
131
+ </div>
124
132
 
125
133
  <div class="mb-14 grid gap-px bg-zinc-200 sm:grid-cols-2"
126
134
  data-home-curriculum></div>
127
135
 
128
- <p class="mb-6 text-[11px] font-semibold uppercase tracking-[0.18em] text-zinc-400">Eindtoets</p>
136
+ <p class="mb-6 text-[11px] font-semibold uppercase tracking-[0.18em] text-zinc-400">Meetmoment</p>
129
137
 
130
138
  <div class="grid gap-px bg-zinc-200 sm:grid-cols-3">
131
139
  <a href="pages/checklist.html"
@@ -136,16 +144,16 @@ npm run preview # preview van dist/</code></pre>
136
144
  <p class="mt-1 text-sm text-zinc-500">Houd je voortgang bij</p>
137
145
  </a>
138
146
 
139
- <a href="pages/toets-theorie.html"
147
+ <a href="pages/meetmoment-theorie.html"
140
148
  class="card-interactive block bg-white">
141
- <h3 class="font-medium text-zinc-900">Eindtoets theorie</h3>
149
+ <h3 class="font-medium text-zinc-900">Meetmoment theorie</h3>
142
150
 
143
151
  <p class="mt-1 text-sm text-zinc-500">Meerkeuzevragen</p>
144
152
  </a>
145
153
 
146
- <a href="pages/toets-praktijk.html"
154
+ <a href="pages/meetmoment-praktijk.html"
147
155
  class="card-interactive block bg-white">
148
- <h3 class="font-medium text-zinc-900">Eindtoets praktijk</h3>
156
+ <h3 class="font-medium text-zinc-900">Meetmoment praktijk</h3>
149
157
 
150
158
  <p class="mt-1 text-sm text-zinc-500">Praktijkvragen</p>
151
159
  </a>
@@ -19,14 +19,46 @@
19
19
  </div>
20
20
  </main>
21
21
  </div>
22
+
23
+ <div data-theory-panel class="theory-panel panel-hidden">
24
+ <div class="theory-panel-header">
25
+ <div data-theory-tabs class="theory-panel-tabs"></div>
26
+ <button data-theory-panel-close
27
+ type="button"
28
+ class="theory-panel-close"
29
+ aria-label="Sluit theorievenster">
30
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>
31
+ </button>
32
+ </div>
33
+ <div class="theory-panel-body">
34
+ <div data-theory-loader class="theory-panel-loader">
35
+ <div class="theory-panel-spinner"></div>
36
+ </div>
37
+ <iframe data-theory-iframe
38
+ class="theory-panel-iframe"
39
+ title="Theorie"></iframe>
40
+ </div>
41
+ </div>
42
+
43
+ <button data-theory-toggle
44
+ type="button"
45
+ class="theory-panel-toggle hidden"
46
+ aria-label="Toon theorie"
47
+ aria-expanded="false">
48
+ <svg class="h-4 w-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.966 8.966 0 0 0-6 2.292m0-14.25v14.25" /></svg>
49
+ Theorie
50
+ </button>
51
+
22
52
  <script type="module">
23
53
  import { initPage } from '/src/js/nav.js'
24
54
  import { initInleveropdracht } from '/src/js/inleveropdracht.js'
55
+ import { initTheoryPanel } from '/src/js/exercises/theory-panel.js'
25
56
  import inleveropdrachtData from '/src/data/inleveropdracht-week{{week}}.json'
26
57
  initPage({ breadcrumbs: [{ label: 'Home', href: '/index.html' }, { label: 'Week {{week}}', href: '/pages/week{{week}}-theorie.html' }, { label: 'Inleveropdracht' }] })
27
58
  const titleEl = document.querySelector('[data-inleveropdracht-title]')
28
59
  if (titleEl) titleEl.textContent = inleveropdrachtData.title
29
60
  initInleveropdracht(inleveropdrachtData)
61
+ initTheoryPanel(inleveropdrachtData.linked_theory)
30
62
  </script>
31
63
  </body>
32
64
 
@@ -0,0 +1,35 @@
1
+ <!DOCTYPE html>
2
+ <html lang="nl">
3
+
4
+ <head>
5
+ <!-- include:head -->
6
+ <title>{{pageTitle}}</title>
7
+ </head>
8
+
9
+ <body class="font-sans antialiased">
10
+ <div data-page-content>
11
+ <main class="px-4 py-10 md:px-10">
12
+ <div class="mx-auto max-w-3xl">
13
+ <h1 class="text-3xl font-semibold tracking-tight text-zinc-900"
14
+ data-praktijk-title></h1>
15
+ <p class="mt-2 text-zinc-600"
16
+ data-praktijk-description></p>
17
+ <div data-praktijk-content
18
+ class="prose mt-8"></div>
19
+ </div>
20
+ </main>
21
+ </div>
22
+ <script type="module">
23
+ import { initPage } from '/src/js/nav.js'
24
+ import data from '/src/data/meetmoment-praktijk.json'
25
+ initPage({ breadcrumbs: [{ label: 'Home', href: '/index.html' }, { label: data.navLabel ?? '{{assessmentTitle}}' }] })
26
+ const titleEl = document.querySelector('[data-praktijk-title]')
27
+ if (titleEl) titleEl.textContent = data.title
28
+ const descEl = document.querySelector('[data-praktijk-description]')
29
+ if (descEl) descEl.innerHTML = data.description
30
+ const contentEl = document.querySelector('[data-praktijk-content]')
31
+ if (contentEl) contentEl.innerHTML = data.html
32
+ </script>
33
+ </body>
34
+
35
+ </html>
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+ <html lang="nl">
3
+
4
+ <head>
5
+ <!-- include:head -->
6
+ <title>{{pageTitle}}</title>
7
+ </head>
8
+
9
+ <body class="font-sans antialiased">
10
+ <div data-page-content>
11
+ <main class="px-4 py-10 md:px-10">
12
+ <div class="mx-auto max-w-3xl">
13
+ <h1 class="text-3xl font-semibold tracking-tight text-zinc-900">{{assessmentTitle}}</h1>
14
+ <p class="mt-2 text-zinc-600">{{assessmentDescription}}</p>
15
+ <div data-quiz
16
+ class="mt-8"></div>
17
+ </div>
18
+ </main>
19
+ </div>
20
+ <script type="module">
21
+ import { initPage } from '/src/js/nav.js'
22
+ import { initQuiz } from '/src/js/quiz.js'
23
+ import quizData from '/src/data/meetmoment-theorie.json'
24
+ initPage({ breadcrumbs: [{ label: 'Home', href: '/index.html' }, { label: '{{assessmentTitle}}' }] })
25
+ initQuiz(quizData)
26
+ </script>
27
+ </body>
28
+
29
+ </html>
@@ -10,14 +10,14 @@
10
10
  <div data-page-content>
11
11
  <main class="px-4 py-10 md:px-10">
12
12
  <div class="mx-auto max-w-3xl">
13
- <span class="week-label">Week {{weekPadded}} — Tussentoets</span>
13
+ <span class="week-label">Week {{weekPadded}} — Meetmoment Quiz</span>
14
14
  <h1 class="text-3xl font-semibold tracking-tight text-zinc-900">{{weekTitle}}</h1>
15
15
  <p class="mt-2 text-zinc-600">Test je kennis van week {{week}}. Minimaal 70% om te slagen.</p>
16
16
  <div data-quiz
17
17
  class="mt-8"></div>
18
18
  <div class="card mt-10 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
19
19
  <div>
20
- <p class="font-medium text-zinc-900">Toets afgerond?</p>
20
+ <p class="font-medium text-zinc-900">Meetmoment afgerond?</p>
21
21
  <p class="mt-1 text-sm text-zinc-500">Werk de inleveropdracht uit.</p>
22
22
  </div>
23
23
  <a href="week{{week}}-inleveropdracht.html"
@@ -29,8 +29,15 @@
29
29
  <script type="module">
30
30
  import { initPage } from '/src/js/nav.js'
31
31
  import { initQuiz } from '/src/js/quiz.js'
32
- import quizData from '/src/data/tussentoets-week{{week}}.json'
33
- initPage({ breadcrumbs: [{ label: 'Home', href: '/index.html' }, { label: 'Week {{week}}', href: '/pages/week{{week}}-theorie.html' }, { label: 'Tussentoets' }] })
32
+ import quizData from '/src/data/meetmoment-quiz-week{{week}}.json'
33
+
34
+ initPage({
35
+ breadcrumbs: [
36
+ { label: 'Home', href: '/index.html' },
37
+ { label: 'Week {{week}}', href: '/pages/week{{week}}-theorie.html' },
38
+ { label: 'Meetmoment Quiz' }
39
+ ]
40
+ })
34
41
  initQuiz(quizData)
35
42
  </script>
36
43
  </body>
@@ -143,6 +143,35 @@
143
143
  </main>
144
144
  </div>
145
145
 
146
+ <div data-theory-panel class="theory-panel panel-hidden">
147
+ <div class="theory-panel-header">
148
+ <div data-theory-tabs class="theory-panel-tabs"></div>
149
+ <button data-theory-panel-close
150
+ type="button"
151
+ class="theory-panel-close"
152
+ aria-label="Sluit theorievenster">
153
+ <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>
154
+ </button>
155
+ </div>
156
+ <div class="theory-panel-body">
157
+ <div data-theory-loader class="theory-panel-loader">
158
+ <div class="theory-panel-spinner"></div>
159
+ </div>
160
+ <iframe data-theory-iframe
161
+ class="theory-panel-iframe"
162
+ title="Theorie"></iframe>
163
+ </div>
164
+ </div>
165
+
166
+ <button data-theory-toggle
167
+ type="button"
168
+ class="theory-panel-toggle hidden"
169
+ aria-label="Toon theorie"
170
+ aria-expanded="false">
171
+ <svg class="h-4 w-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.966 8.966 0 0 0-6 2.292m0-14.25v14.25" /></svg>
172
+ Theorie
173
+ </button>
174
+
146
175
  <script type="module">
147
176
  import { initPage } from '/src/js/nav.js'
148
177
  import { initExercisePage } from '/src/js/exercises/load-exercise.js'
@@ -1,44 +1,52 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="nl">
3
- <head>
4
- <!-- include:head -->
5
- <title>{{pageTitle}}</title>
6
- </head>
7
- <body class="font-sans antialiased">
8
- <div data-page-content>
9
- <main class="px-4 py-10 md:px-10">
10
- <div class="mx-auto max-w-3xl">
11
- <span class="week-label">Week {{week}}</span>
12
- <h1 class="text-3xl font-semibold tracking-tight text-zinc-900">Oefeningen</h1>
13
- <p class="mt-2 text-zinc-600" data-hub-subtitle>{{weekTitle}} — 8 oefeningen.</p>
14
- <div class="card mt-8">
15
- <div class="mb-4 flex items-end justify-between">
16
- <div>
17
- <p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-zinc-400">Voortgang</p>
18
- <p class="mt-1 text-sm text-zinc-500"><span data-hub-progress-text>0 / 8</span> afgerond</p>
19
- </div>
20
- </div>
21
- <div class="progress-track">
22
- <div data-hub-progress-bar class="progress-fill" style="width:0%"></div>
23
- </div>
24
- </div>
25
- <div data-exercise-list class="mt-8 space-y-4"></div>
26
- <div class="card mt-10 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
3
+
4
+ <head>
5
+ <!-- include:head -->
6
+ <title>{{pageTitle}}</title>
7
+ </head>
8
+
9
+ <body class="font-sans antialiased">
10
+ <div data-page-content>
11
+ <main class="px-4 py-10 md:px-10">
12
+ <div class="mx-auto max-w-3xl">
13
+ <span class="week-label">Week {{week}}</span>
14
+ <h1 class="text-3xl font-semibold tracking-tight text-zinc-900">Oefeningen</h1>
15
+ <p class="mt-2 text-zinc-600"
16
+ data-hub-subtitle>{{weekTitle}} — 8 oefeningen.</p>
17
+ <div class="card mt-8">
18
+ <div class="mb-4 flex items-end justify-between">
27
19
  <div>
28
- <p class="font-medium text-zinc-900">Klaar met de oefeningen?</p>
29
- <p class="mt-1 text-sm text-zinc-500">Test je kennis met de tussentoets.</p>
20
+ <p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-zinc-400">Voortgang</p>
21
+ <p class="mt-1 text-sm text-zinc-500"><span data-hub-progress-text>0 / 8</span> afgerond</p>
30
22
  </div>
31
- <a href="week{{week}}-toets.html" class="btn-primary shrink-0">Naar de toets</a>
32
23
  </div>
24
+ <div class="progress-track">
25
+ <div data-hub-progress-bar
26
+ class="progress-fill"
27
+ style="width:0%"></div>
28
+ </div>
29
+ </div>
30
+ <div data-exercise-list
31
+ class="mt-8 space-y-4"></div>
32
+ <div class="card mt-10 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
33
+ <div>
34
+ <p class="font-medium text-zinc-900">Klaar met de oefeningen?</p>
35
+ <p class="mt-1 text-sm text-zinc-500">Test je kennis met de meetmoment.</p>
36
+ </div>
37
+ <a href="week{{week}}-meetmoment.html"
38
+ class="btn-primary shrink-0">Naar het meetmoment</a>
33
39
  </div>
34
- </main>
35
- </div>
36
- <script type="module">
37
- import { initPage } from '/src/js/nav.js'
38
- import { initExerciseHub } from '/src/js/exercises/hub.js'
39
- import weekData from '/src/data/exercises/week{{week}}.json'
40
- initPage({ breadcrumbs: [{ label: 'Home', href: '/index.html' }, { label: 'Week {{week}}' }, { label: 'Oefeningen' }] })
41
- initExerciseHub(weekData, {{week}})
42
- </script>
43
- </body>
44
- </html>
40
+ </div>
41
+ </main>
42
+ </div>
43
+ <script type="module">
44
+ import { initPage } from '/src/js/nav.js'
45
+ import { initExerciseHub } from '/src/js/exercises/hub.js'
46
+ import weekData from '/src/data/exercises/week{{week}}.json'
47
+ initPage({ breadcrumbs: [{ label: 'Home', href: '/index.html' }, { label: 'Week {{week}}' }, { label: 'Oefeningen' }] })
48
+ initExerciseHub(weekData, {{ week }})
49
+ </script>
50
+ </body>
51
+
52
+ </html>
@@ -15,7 +15,12 @@
15
15
  <script type="module">
16
16
  import { initPage } from '/src/js/nav.js'
17
17
  import { initTheory } from '/src/js/theory.js'
18
- initPage({ breadcrumbs: [{ label: 'Home', href: '/index.html' }, { label: 'Week {{week}}', href: '/pages/week{{week}}-theorie.html' }, { label: 'Theorie' }] })
18
+ const embedded = new URLSearchParams(location.search).has('embedded')
19
+ if (!embedded) {
20
+ initPage({ breadcrumbs: [{ label: 'Home', href: '/index.html' }, { label: 'Week {{week}}', href: '/pages/week{{week}}-theorie.html' }, { label: 'Theorie' }] })
21
+ } else {
22
+ document.body.setAttribute('data-embedded', '')
23
+ }
19
24
  initTheory({{week}})
20
25
  </script>
21
26
  </body>
@@ -1,25 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="nl">
3
- <head>
4
- <!-- include:head -->
5
- <title>{{pageTitle}}</title>
6
- </head>
7
- <body class="font-sans antialiased">
8
- <div data-page-content>
9
- <main class="px-4 py-10 md:px-10">
10
- <div class="mx-auto max-w-3xl">
11
- <h1 class="text-3xl font-semibold tracking-tight text-zinc-900">Eindtoets praktijk</h1>
12
- <p class="mt-2 text-zinc-600">Praktijkvragen over de module. Minimaal 70% om te slagen.</p>
13
- <div data-quiz class="mt-8"></div>
14
- </div>
15
- </main>
16
- </div>
17
- <script type="module">
18
- import { initPage } from '/src/js/nav.js'
19
- import { initQuiz } from '/src/js/quiz.js'
20
- import quizData from '/src/data/toets-praktijk.json'
21
- initPage({ breadcrumbs: [{ label: 'Home', href: '/index.html' }, { label: 'Eindtoets praktijk' }] })
22
- initQuiz(quizData)
23
- </script>
24
- </body>
25
- </html>
@@ -1,25 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="nl">
3
- <head>
4
- <!-- include:head -->
5
- <title>{{pageTitle}}</title>
6
- </head>
7
- <body class="font-sans antialiased">
8
- <div data-page-content>
9
- <main class="px-4 py-10 md:px-10">
10
- <div class="mx-auto max-w-3xl">
11
- <h1 class="text-3xl font-semibold tracking-tight text-zinc-900">Eindtoets theorie</h1>
12
- <p class="mt-2 text-zinc-600">Meerkeuzevragen over de module. Minimaal 70% om te slagen.</p>
13
- <div data-quiz class="mt-8"></div>
14
- </div>
15
- </main>
16
- </div>
17
- <script type="module">
18
- import { initPage } from '/src/js/nav.js'
19
- import { initQuiz } from '/src/js/quiz.js'
20
- import quizData from '/src/data/toets-theorie.json'
21
- initPage({ breadcrumbs: [{ label: 'Home', href: '/index.html' }, { label: 'Eindtoets theorie' }] })
22
- initQuiz(quizData)
23
- </script>
24
- </body>
25
- </html>