diff --git a/site/app.js b/site/app.js index 4092ae744..d2f1a8e14 100644 --- a/site/app.js +++ b/site/app.js @@ -124,11 +124,12 @@ var statusClass = p.status.replace(/ /g, '-'); var roman = toRoman(p.id); var num = String(p.id).padStart(2, '0'); - html += '
'; - html += '' + roman + '.'; - html += '
' + escapeHtml(p.name) + '
'; - html += '' + done + ' / ' + total + ''; - html += '' + num + ''; + var label = 'Phase ' + num + ': ' + p.name + ', ' + done + ' of ' + total + ' lessons complete'; + html += '
'; + html += ''; + html += '
' + escapeHtml(p.name) + '
'; + html += ''; + html += ''; html += '
'; } grid.innerHTML = html; @@ -179,16 +180,34 @@ var row = e.target.closest('.toc-row, .phase-card'); if (row) { var idx = parseInt(row.getAttribute('data-phase'), 10); - if (!isNaN(idx)) openModal(idx); + if (!isNaN(idx)) openModal(idx, row); } }); + // Keyboard activation for the role="button" phase rows. + document.addEventListener('keydown', function (e) { + if (e.key !== 'Enter' && e.key !== ' ') return; + var row = e.target.closest && e.target.closest('.toc-row, .phase-card'); + if (!row || !row.hasAttribute('data-phase')) return; + e.preventDefault(); + var idx = parseInt(row.getAttribute('data-phase'), 10); + if (!isNaN(idx)) openModal(idx, row); + }); + closeBtn.addEventListener('click', closeModal); overlay.addEventListener('click', function (e) { if (e.target === overlay) closeModal(); }); document.addEventListener('keydown', function (e) { - if (e.key === 'Escape') closeModal(); + if (!overlay.classList.contains('open')) return; + if (e.key === 'Escape') { + e.preventDefault(); + closeModal(); + return; + } + if (e.key === 'Tab') { + trapTab(e, overlay); + } }); var resetBtn = document.getElementById('modalReset'); @@ -202,12 +221,37 @@ } } + function getFocusables(root) { + // getClientRects().length is more reliable than offsetParent for visibility: + // offsetParent returns null for position: fixed elements even when visible. + return Array.prototype.slice.call(root.querySelectorAll( + 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])' + )).filter(function (el) { return el.getClientRects().length > 0; }); + } + + function trapTab(e, container) { + var f = getFocusables(container); + if (!f.length) return; + var first = f[0]; + var last = f[f.length - 1]; + var active = document.activeElement; + if (e.shiftKey && active === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && active === last) { + e.preventDefault(); + first.focus(); + } + } + var currentPhaseIdx = -1; + var lastModalTrigger = null; - function openModal(idx) { + function openModal(idx, triggerEl) { var p = PHASES[idx]; if (!p) return; currentPhaseIdx = idx; + lastModalTrigger = triggerEl || document.activeElement; document.getElementById('modalPhaseNum').textContent = 'PHASE ' + String(p.id).padStart(2, '0'); document.getElementById('modalTitle').textContent = p.name; @@ -215,8 +259,14 @@ renderModalLessons(p); - document.getElementById('modalOverlay').classList.add('open'); + var overlay = document.getElementById('modalOverlay'); + overlay.classList.add('open'); + overlay.setAttribute('aria-hidden', 'false'); document.body.style.overflow = 'hidden'; + + // Move focus into the dialog so screen readers and keyboards land there. + var closeBtn = document.getElementById('modalClose'); + if (closeBtn) closeBtn.focus(); } function renderModalLessons(p) { @@ -238,11 +288,13 @@ if (userComplete) statusClass = 'complete'; html += ' @@ -257,26 +265,29 @@

Lesson Catalog

Every lesson across all 20 phases. Search, filter, sort.

- - + + -
-
+
- - - - - + + + + + @@ -448,7 +459,8 @@

Lesson Catalog

statusSelect.addEventListener('change', render); document.querySelectorAll('.catalog-table th[data-sort]').forEach(function (th) { - th.addEventListener('click', function () { + var btn = th.querySelector('.sort-btn'); + var handler = function () { var col = th.getAttribute('data-sort'); if (sortCol === col) { sortDir = sortDir * -1; @@ -456,12 +468,18 @@

Lesson Catalog

sortCol = col; sortDir = 1; } - document.querySelectorAll('.catalog-table th .sort-arrow').forEach(function (a) { - a.textContent = ''; + document.querySelectorAll('.catalog-table th[data-sort]').forEach(function (otherTh) { + otherTh.setAttribute('aria-sort', 'none'); + var arrow = otherTh.querySelector('.sort-arrow'); + if (arrow) arrow.textContent = ''; }); - th.querySelector('.sort-arrow').textContent = sortDir === 1 ? ' \u25B2' : ' \u25BC'; + th.setAttribute('aria-sort', sortDir === 1 ? 'ascending' : 'descending'); + var arrow = th.querySelector('.sort-arrow'); + if (arrow) arrow.textContent = sortDir === 1 ? ' \u25B2' : ' \u25BC'; render(); - }); + }; + if (btn) btn.addEventListener('click', handler); + else th.addEventListener('click', handler); }); render(); diff --git a/site/glossary.html b/site/glossary.html index ddd949f95..ad5bca607 100644 --- a/site/glossary.html +++ b/site/glossary.html @@ -180,10 +180,10 @@ CatalogRoadmapGlossary - + - + @@ -208,9 +208,10 @@

AI Glossary

What people say vs what things actually mean

- + +
-
+
diff --git a/site/index.html b/site/index.html index 142afcf72..f944a47f7 100644 --- a/site/index.html +++ b/site/index.html @@ -580,9 +580,9 @@ CatalogRoadmapGlossary - + - + @@ -630,29 +630,29 @@

AI Engineering
from Scratch

Finished Lessons - + 0 / 0
Phases - + 0 / 0
Languages - + 4
Glossary Terms - + ···
-
-
Curriculum · 20 phases · 435 lessons
+
+

Curriculum · 20 phases · 435 lessons

Tap a phase to expand its lessons. Each one ships when its math, code, and test are all written.
@@ -662,9 +662,9 @@

AI Engineering
from Scratch

-
diff --git a/site/lesson.html b/site/lesson.html index 7258e12e1..910791e64 100644 --- a/site/lesson.html +++ b/site/lesson.html @@ -1577,10 +1577,10 @@ CatalogRoadmapGlossary - + - + - +
Phase Lesson Type Language Status