From 5ef799928321d9a27b4290634eb8bddbb4aa4f6a Mon Sep 17 00:00:00 2001 From: aionyx Date: Tue, 1 Sep 2026 09:37:07 +0800 Subject: [PATCH 1/3] feat(Mermaid): color the metro route diagram by line - an edge label naming a Taipei metro line paints that segment and its label in the line's color - add the --mrt-* tokens; blue, green, orange and brown reuse the category colors, red is calibrated to the same lightness - move diagram styling into themeCSS: paper stations, rounded corners, line-strong outlines and markers - mark the destination station with class dest, drawn as a solid ink pill - reserve the block height before mermaid renders so the source no longer flashes - center wide diagrams with an auto margin; justify-content clipped the left edge out of scroll reach - name the platform direction in the transfer tabs - record both diagram conventions in the syntax spec --- docs/spec/SPEC.md | 3 ++ src/content/articles/transportation.md | 11 +++--- src/scripts/content.ts | 51 +++++++++++++++++++++++++- src/styles/markdown.css | 18 ++++++++- src/styles/tokens.css | 7 ++++ 5 files changed, 82 insertions(+), 8 deletions(-) diff --git a/docs/spec/SPEC.md b/docs/spec/SPEC.md index f3cb6a4..ef6f0ef 100644 --- a/docs/spec/SPEC.md +++ b/docs/spec/SPEC.md @@ -235,6 +235,9 @@ graph LR; - `showLineNumbers` 顯示行號(預設隱藏):`​```python title="x.py" showLineNumbers {3}`。 - mermaid 於 **client 端動態載入渲染**(動態 import,只有含 mermaid 的頁面才載入; build 時轉 SVG 為長期方向,內容大量撰寫前評估)。 +- 圖表配色一律由 `src/scripts/content.ts` 依 tokens 套用,圖內不寫顏色。兩條約定: + 邊標籤寫**捷運線名**(板南線、淡水信義線、松山新店線、中和新蘆線、文湖線)該段自動上該線色; + `class 站名 dest` 把該站標成終點(深色實心)。 --- diff --git a/src/content/articles/transportation.md b/src/content/articles/transportation.md index 5da2787..942604d 100644 --- a/src/content/articles/transportation.md +++ b/src/content/articles/transportation.md @@ -5,7 +5,7 @@ category: life tags: [行, 生活] description: 從臺北車站到臺科怎麼走、YouBike 與共享運具怎麼用、機車停車位怎麼申請 order: 3 -updated: 2026-07-27 +updated: 2026-09-01 --- ## 第一次來臺科 @@ -22,17 +22,18 @@ updated: 2026-07-27 ::::tabs :::tab[路線一:走西門] -**板南線** 臺北車站 → 西門,轉 **松山新店線** → 公館 +**板南線**(往頂埔方向)臺北車站 → 西門,站內轉 **松山新店線**(往新店方向)→ 公館 ::: :::tab[路線二:走中正紀念堂] -**淡水信義線** 臺北車站 → 中正紀念堂,轉 **松山新店線** → 公館 +**淡水信義線**(往象山方向)臺北車站 → 中正紀念堂,站內轉 **松山新店線**(往新店方向)→ 公館 ::: :::: ```mermaid graph LR; -臺北車站--板南線---西門--松山新店線---公館 -臺北車站--淡水信義線---中正紀念堂--松山新店線---公館 +臺北車站([臺北車站])--板南線---西門([西門])--松山新店線---公館([公館]) +臺北車站--淡水信義線---中正紀念堂([中正紀念堂])--松山新店線---公館 +class 公館 dest; ``` 兩條路線時間差不多,看你在臺北車站站在哪個月台比較近就選哪條。 diff --git a/src/scripts/content.ts b/src/scripts/content.ts index 08798b9..3763187 100644 --- a/src/scripts/content.ts +++ b/src/scripts/content.ts @@ -61,13 +61,62 @@ export function initYt() { } } +// Taipei metro line colors live in tokens.css; the edge label text is the line name +const MRT_LINES: Record = { + 板南線: '--mrt-bl', + 淡水信義線: '--mrt-r', + 松山新店線: '--mrt-g', + 中和新蘆線: '--mrt-o', + 文湖線: '--mrt-br', +}; + +// mermaid rules are scoped by svg id and outrank any stylesheet, so the palette rides in +// through themeCSS, which lands inside that scope; themeVariables would reject oklch() tokens +const MERMAID_CSS = ` + .node rect, .node path, .node polygon, .node circle { fill: var(--paper); stroke: var(--line-strong); stroke-width: 2px; } + .node rect { rx: 8px; ry: 8px; } + .node .nodeLabel { color: var(--ink); font-weight: 650; } + .flowchart-link { stroke: var(--line-strong); } + .marker { fill: var(--line-strong); stroke: var(--line-strong); } + .edgeLabel, .edgeLabel p, .labelBkg { background: var(--surface); } + .edgeLabel p { font-size: var(--text-tiny); font-weight: 700; letter-spacing: 0.03em; } + .dest rect, .dest path { fill: var(--ink); stroke: var(--ink); } + .dest .nodeLabel { color: var(--paper); font-weight: 750; } +`; + export async function initMermaid() { if (!document.querySelector('pre.mermaid')) return; try { const { default: mermaid } = await import('mermaid'); - mermaid.initialize({ startOnLoad: false, theme: 'neutral', fontFamily: 'inherit' }); + mermaid.initialize({ + startOnLoad: false, + theme: 'neutral', + fontFamily: 'inherit', + themeCSS: MERMAID_CSS, + flowchart: { curve: 'basis', nodeSpacing: 34, rankSpacing: 92, padding: 12 }, + }); await mermaid.run({ querySelector: 'pre.mermaid', suppressErrors: false }); + paintMrtLines(); } catch (err) { console.error('[mermaid] render failed:', err); } } + +function paintMrtLines() { + for (const svg of document.querySelectorAll('pre.mermaid svg')) { + // one label group per edge in edge order, empty ones included, so index pairs the two; + // a count mismatch means mermaid changed that and the pairing would color the wrong edge + const paths = svg.querySelectorAll('.edgePaths > path'); + const labels = svg.querySelectorAll('.edgeLabels > g'); + if (paths.length !== labels.length) continue; + paths.forEach((path, i) => { + const varName = MRT_LINES[labels[i].textContent?.trim() ?? '']; + if (!varName) return; + const color = `var(${varName})`; + path.style.stroke = color; // inline: the color varies per edge, themeCSS above is static + path.style.strokeWidth = '5px'; + const chip = labels[i].querySelector('p'); + if (chip) chip.style.color = color; + }); + } +} diff --git a/src/styles/markdown.css b/src/styles/markdown.css index 74848de..02855ba 100644 --- a/src/styles/markdown.css +++ b/src/styles/markdown.css @@ -818,14 +818,28 @@ html:not([data-dept='csie']) [data-dept-only='csie'] { /* ---- Mermaid ---- */ pre.mermaid { display: flex; - justify-content: center; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); - padding: 1.2rem; + padding: 1.5rem 1.2rem; overflow-x: auto; } +/* 窄螢幕寧可橫向捲動,等比縮到手機寬度會讓站名小到看不清; + 置中用 auto margin,justify-content 會把溢出的左半邊裁掉且捲不回來 */ +pre.mermaid svg { + min-width: 26rem; + margin-inline: auto; +} + +/* 渲染前先佔位,不閃出原始碼;無 JS 的瀏覽器仍看得到原始碼 */ +@media (scripting: enabled) { + pre.mermaid:not([data-processed]) { + min-height: 10rem; + color: transparent; + } +} + /* ---- 註腳 ---- */ .md sup a { text-decoration: none; diff --git a/src/styles/tokens.css b/src/styles/tokens.css index 19cd9f2..5462f9d 100644 --- a/src/styles/tokens.css +++ b/src/styles/tokens.css @@ -19,6 +19,13 @@ --c-info: oklch(54.5% 0.125 55); --c-misc: oklch(50% 0.055 60); + /* 北捷路線色(文內路線圖):四條與分類色同源,紅線另行校準到相同明度 */ + --mrt-bl: var(--c-life); + --mrt-r: oklch(52% 0.18 27); + --mrt-g: var(--c-course); + --mrt-o: var(--c-info); + --mrt-br: var(--c-misc); + /* 語意色(callout) */ --clr-tip: var(--c-course); --clr-info: var(--c-life); From 5ff622a9afee70207f97821501525c77429d33c5 Mon Sep 17 00:00:00 2001 From: aionyx Date: Tue, 1 Sep 2026 09:42:40 +0800 Subject: [PATCH 2/3] fix(Mermaid): show the diagram source when rendering fails - the placeholder hid the block until mermaid set data-processed, so a failed import or render left an empty box behind - flag the blocks mermaid never processed and let their source through again --- src/scripts/content.ts | 5 +++++ src/styles/markdown.css | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/scripts/content.ts b/src/scripts/content.ts index 3763187..097cd34 100644 --- a/src/scripts/content.ts +++ b/src/scripts/content.ts @@ -99,6 +99,11 @@ export async function initMermaid() { paintMrtLines(); } catch (err) { console.error('[mermaid] render failed:', err); + // the placeholder hides the source until mermaid swaps in the svg, so hand it back + // here: without this the block stays an empty box whenever rendering falls over + for (const pre of document.querySelectorAll('pre.mermaid:not([data-processed])')) { + pre.setAttribute('data-mermaid-failed', ''); + } } } diff --git a/src/styles/markdown.css b/src/styles/markdown.css index 02855ba..63bb577 100644 --- a/src/styles/markdown.css +++ b/src/styles/markdown.css @@ -832,9 +832,10 @@ pre.mermaid svg { margin-inline: auto; } -/* 渲染前先佔位,不閃出原始碼;無 JS 的瀏覽器仍看得到原始碼 */ +/* 渲染前先佔位,不閃出原始碼。無 JS 不套用;渲染失敗時 content.ts 補上 data-mermaid-failed, + 兩種情況都讓原始碼留著可讀,不會只剩一個空框 */ @media (scripting: enabled) { - pre.mermaid:not([data-processed]) { + pre.mermaid:not([data-processed]):not([data-mermaid-failed]) { min-height: 10rem; color: transparent; } From fcbc7b16dd617bee1e5ce082431a4b6c4ee337c8 Mon Sep 17 00:00:00 2001 From: aionyx Date: Tue, 1 Sep 2026 15:54:19 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat(Mermaid):=20add=20color=20for=20?= =?UTF-8?q?=E7=92=B0=E7=8B=80=E7=B7=9A=20in=20metro=20route=20diagram?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/spec/SPEC.md | 2 +- src/scripts/content.ts | 1 + src/styles/tokens.css | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/spec/SPEC.md b/docs/spec/SPEC.md index ef6f0ef..9e9ec18 100644 --- a/docs/spec/SPEC.md +++ b/docs/spec/SPEC.md @@ -236,7 +236,7 @@ graph LR; - mermaid 於 **client 端動態載入渲染**(動態 import,只有含 mermaid 的頁面才載入; build 時轉 SVG 為長期方向,內容大量撰寫前評估)。 - 圖表配色一律由 `src/scripts/content.ts` 依 tokens 套用,圖內不寫顏色。兩條約定: - 邊標籤寫**捷運線名**(板南線、淡水信義線、松山新店線、中和新蘆線、文湖線)該段自動上該線色; + 邊標籤寫**捷運線名**(板南線、淡水信義線、松山新店線、中和新蘆線、文湖線、環狀線)該段自動上該線色; `class 站名 dest` 把該站標成終點(深色實心)。 --- diff --git a/src/scripts/content.ts b/src/scripts/content.ts index 097cd34..beac207 100644 --- a/src/scripts/content.ts +++ b/src/scripts/content.ts @@ -68,6 +68,7 @@ const MRT_LINES: Record = { 松山新店線: '--mrt-g', 中和新蘆線: '--mrt-o', 文湖線: '--mrt-br', + 環狀線: '--mrt-y', }; // mermaid rules are scoped by svg id and outrank any stylesheet, so the palette rides in diff --git a/src/styles/tokens.css b/src/styles/tokens.css index 5462f9d..056e717 100644 --- a/src/styles/tokens.css +++ b/src/styles/tokens.css @@ -19,12 +19,14 @@ --c-info: oklch(54.5% 0.125 55); --c-misc: oklch(50% 0.055 60); - /* 北捷路線色(文內路線圖):四條與分類色同源,紅線另行校準到相同明度 */ + /* 北捷路線色(文內路線圖):四條與分類色同源,紅、黃另行校準到相同明度。 + 環狀線原色過亮,壓到 4.5:1 後偏橄欖金,可讀性優先於還原度 */ --mrt-bl: var(--c-life); --mrt-r: oklch(52% 0.18 27); --mrt-g: var(--c-course); --mrt-o: var(--c-info); --mrt-br: var(--c-misc); + --mrt-y: oklch(52.5% 0.11 108); /* 語意色(callout) */ --clr-tip: var(--c-course);