diff --git a/explorer/app.js b/explorer/app.js index c4ae0e6..bbd05f6 100644 --- a/explorer/app.js +++ b/explorer/app.js @@ -1,7 +1,7 @@ import { parseRoadmap, renderInline, TYPES } from './parser.js'; import { linkDiagram } from './diagram.js'; import { createMapView } from './mapview.js'; -import { CREDITS, IMPORTANCE, areaDots, areaLabel, h, plural } from './dom.js'; +import { CREDITS, IMPORTANCE, TITLE, areaDots, areaLabel, h, plural } from './dom.js'; const SOURCES = [ 'README.md', @@ -97,7 +97,7 @@ function navigate(node, { push = true, resource = null, reveal = false } = {}) { syncOutline(); if (resource) revealResource(resource); const root = state.data.root; - document.title = node === root ? 'Embedded Systems Engineering Roadmap · Explorer' : `${node.title} · Embedded Systems Engineering Roadmap`; + document.title = node === root ? `${TITLE} · Explorer` : `${node.title} · ${TITLE}`; if (push) { const hash = node === root ? '#/' : `#/${node.slug}`; if (location.hash !== hash) history.pushState(null, '', hash); @@ -344,7 +344,7 @@ function renderPanel(node) { crumbs(node), h('h2', { class: 'panel-title' }, node.icon ? h('span', { class: 'title-icon', 'aria-hidden': 'true' }, node.icon) : node === root ? null : swatch(node), - node === root ? 'Embedded Systems Engineering Roadmap' : node.title), + node === root ? 'About this roadmap' : node.title), node === root ? null : h('div', { class: 'meta' }, node.areas && node.areas.length ? h('span', { class: 'pill area-pill' }, areaDots(node.areas), areaLabel(node.areas)) : null, node.importance ? h('span', { class: `pill imp-pill ${node.importance}` }, IMPORTANCE[node.importance]) : null, @@ -589,7 +589,7 @@ async function loadMarkdown() { const response = await fetch(source, { cache: 'no-cache' }); if (!response.ok) continue; const text = await response.text(); - if (text.includes('# Embedded Systems Engineering Roadmap')) return text; + if (text.includes(`# ${TITLE}`)) return text; } catch { // Try the next source. } @@ -674,6 +674,7 @@ function wireGlobal() { } async function init() { + document.getElementById('brand-title').textContent = TITLE; applyTheme(store('roadmap-theme') || (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark')); let markdown; const diagramRequest = loadMap(); diff --git a/explorer/dom.js b/explorer/dom.js index 86ce686..91544fd 100644 --- a/explorer/dom.js +++ b/explorer/dom.js @@ -2,6 +2,9 @@ export const SVG_NS = 'http://www.w3.org/2000/svg'; +// The roadmap's name; also the README's top heading. +export const TITLE = 'Embedded Systems Engineering Roadmap'; + export const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`; export const CREDITS = { diff --git a/explorer/index.html b/explorer/index.html index a0dad26..1661c80 100644 --- a/explorer/index.html +++ b/explorer/index.html @@ -17,7 +17,7 @@ - Embedded Systems Engineering Roadmap + Interactive explorer diff --git a/explorer/map.css b/explorer/map.css index 7a98191..1487f6d 100644 --- a/explorer/map.css +++ b/explorer/map.css @@ -228,6 +228,12 @@ letter-spacing: -0.01em; } +@media not print { + .page-title { + display: none; + } +} + .legend-text { fill: var(--text); font-size: 13px; diff --git a/explorer/mapview.js b/explorer/mapview.js index 7993eab..c8f7e32 100644 --- a/explorer/mapview.js +++ b/explorer/mapview.js @@ -1,6 +1,6 @@ // Interactive roadmap diagram: Software / Hardware / Soft skills areas with their cross-sections, // importance colors, filters and connections between topics. Laid out by layout.js to fit A4. -import { AREAS, CREDITS, GITHUB_MARK, IMPORTANCE, areaDots, areaLabel, h, plural, svg } from './dom.js'; +import { AREAS, CREDITS, GITHUB_MARK, IMPORTANCE, TITLE, areaDots, areaLabel, h, plural, svg } from './dom.js'; import { SPACE, buildLayout } from './layout.js'; const MIN_ZOOM = 0.6; @@ -151,8 +151,11 @@ export function createMapView({ root, plan, data, build, onSelect }) { rect({ x: 0, y: 0, w: layout.width, h: layout.height }, { class: 'page-bg' }, layers.page); // Title and legend, so the diagram also stands on its own when printed. + // On screen the header already names the roadmap, so the title only appears in print, PDF and PNG. + const legendY = layout.titleY + 22; + const screenTop = legendY - 24; const title = svg('text', { x: 24, y: layout.titleY, class: 'page-title' }, layers.labels); - title.textContent = 'Embedded Systems Engineering Roadmap'; + title.textContent = TITLE; const legend = svg('g', { class: 'page-legend' }, layers.labels); const legendItems = [ ...Object.entries(IMPORTANCE).map(([level, label]) => ({ kind: 'imp', cls: level, label })), @@ -162,7 +165,7 @@ export function createMapView({ root, plan, data, build, onSelect }) { { kind: 'area', cls: 'ss', label: 'Soft skills' }, ]; for (const item of legendItems) { - const y = layout.titleY + 22; + const y = legendY; const cls = item.kind === 'imp' ? `legend-imp ${item.cls}` : item.kind === 'area' ? `legend-area ${item.cls}` : 'legend-hatch'; const key = rect({ x: 0, y, w: 22, h: 13 }, { rx: item.kind === 'imp' ? 3 : 4, class: cls }, legend); const t = svg('text', { x: 0, y: y + 11, class: 'legend-text' }, legend); @@ -456,7 +459,7 @@ export function createMapView({ root, plan, data, build, onSelect }) { const slackX = Math.max(0, page.w - v.w); const slackY = Math.max(0, page.h - v.h); v.x = slackX ? Math.min(Math.max(v.x, -40), slackX + 40) : (page.w - v.w) / 2; - v.y = slackY ? Math.min(Math.max(v.y, -40), slackY + 40) : (page.h - v.h) / 2; + v.y = slackY ? Math.min(Math.max(v.y, screenTop - 40), slackY + 40) : (page.h - v.h) / 2; state.view = v; chart.setAttribute('viewBox', `${v.x.toFixed(1)} ${v.y.toFixed(1)} ${v.w.toFixed(1)} ${v.h.toFixed(1)}`); if (size) zoomLevel.textContent = `${Math.round((size.w / v.w) * 100)}%`; @@ -466,7 +469,7 @@ export function createMapView({ root, plan, data, build, onSelect }) { const size = canvasSize(); const width = size ? Math.max(page.w + 40, size.w / MAX_SCALE) : page.w; fitMode = 'width'; - setView({ x: (page.w - width) / 2, y: -20, w: width }); + setView({ x: (page.w - width) / 2, y: screenTop - 20, w: width }); fitButton.title = 'Show the whole page'; } diff --git a/explorer/parser.js b/explorer/parser.js index 77b7ad0..735fa47 100644 --- a/explorer/parser.js +++ b/explorer/parser.js @@ -1,5 +1,6 @@ // Turns the roadmap README into a topic tree with descriptions, resources and cross-links. // Pure module: no DOM access, so it runs in the browser and under `node --test`. +import { TITLE } from './dom.js'; export const TYPES = { book: { icon: '📘', label: 'Book' }, @@ -87,7 +88,7 @@ function makeNode(fields) { export function parseRoadmap(markdown) { const lines = markdown.replace(/\r\n?/g, '\n').split('\n'); - const root = makeNode({ id: 'root', slug: '', title: 'Embedded Systems Engineering Roadmap' }); + const root = makeNode({ id: 'root', slug: '', title: TITLE }); const groups = []; const stack = []; let target = root;