# e+ 等公務園 加盟機關：復刻筆記

## 來源
- 原始來源：使用者上傳的 `e_加盟機關_離線版-2.zip`，內含真原始碼（3 個 HTML、`styles/`、`assets/`）
- 唯讀基準：`_src/original/`（解壓後原樣保留，未做任何修改）
- 授權：使用者自有專案，無第三方授權問題

## 技術棧（原版）
- 單檔靜態 HTML + 原生 JS；頁內路由使用 hash（`#franchise` / `#explore` / `#feat` / `#ai-*`）
- 版面使用 **Tailwind Play CDN 3.4.17**，在執行期依 `tailwind.config` 產生 CSS
- 字型：Google Fonts（Noto Sans TC 400/500/700/900、Noto Sans、Inter 400–800、Material Icons Outlined、Material Symbols Outlined）
- 圖示：Font Awesome 6.5.1（cdnjs）
- 共用 design system：`styles/tokens.css`、`themes.css`、`components.css`

## 復刻前預判
- 複雜度：**L2**（靜態多畫面、hash 路由、無框架／無 WebGL）
- 模式：**忠實復刻**。既然手上有真原始碼，原始碼本身就是正確答案：HTML、JS、自訂 CSS、圖片一律逐字節保留
- 唯一缺口：原版需要連網（CDN）。`開啟說明.txt` 也寫明「完全無網路時版面與圖示會退化」

## 改了什麼（相對原版）
| 項目 | 原版 | 復刻版 |
|---|---|---|
| Tailwind | `<script src="cdn.tailwindcss.com">` 在執行期產生 | 用各頁原本的 `tailwind.config` 以 **3.4.17**（與 CDN 同版本）預先編譯成 `vendor/tailwind/{portal,agency-kaohsiung,agency-hrd}.css` |
| Tailwind 層疊順序 | CDN 把 `<style>` 附加在 `<head>` 最末 | 編譯後的 `<link>` 也放在 `</head>` 前，保持相同的層疊順序（已實測確認 CDN 的注入位置） |
| Google Fonts | 外部 CSS + gstatic | `vendor/fonts/google-fonts.css` + 122 個 woff2（6.1 MB，包含 Noto Sans TC 所有 unicode-range 切片，動態文字也能完整顯示） |
| Font Awesome | cdnjs | `vendor/fontawesome/`（css + webfonts） |
| 經濟部標準檢驗局 logo | 外連 `bsmi.elearn.hrd.gov.tw/.../logo.png` | 下載為 `assets/agency-logos/10088.png` |
| `tailwind.config` script | 執行期設定 | 移除（CDN 已不存在，保留會出現 `tailwind is not defined` 錯誤），改為註解說明；原設定保留在 `_src/original` |

其他內容（DOM、文案、JS、自訂 `<style>`、`styles/*.css`、所有圖片）**完全未修改**。

## 驗證（真實瀏覽器，原版 CDN vs 復刻版）
1. **逐元素計算樣式比對**：3 頁 × 10 個畫面 × 1440/768/390，共 30 組。比對每個元素的 rect 和 26 個計算樣式屬性（顏色、字型、字級、行高、間距、陰影、grid、transform 等），結果 **30/30 為 0 差異**，元素數與頁高完全一致。證據：`RECON/style-parity.json`
2. **全頁像素比對**：20 組（10 個畫面 × 1440/390），**全部 changedPixels = 0**（visualScore 5/5）。截圖在 `RECON/screenshots/`，差異圖在 `RECON/visual-diff/`
3. **執行期 class 覆蓋率**：在原版切換所有內頁、打開客服面板與 header 下拉、捲動全頁後，擷取 CDN 實際產生的所有選擇器（Portal 622 條、兩個機關頁各約 485 條）逐條比對編譯版，覆蓋率 **100%**（4 條 preflight 規則只是寫法不同，已人工確認存在）
4. **Console**：原版 0 錯誤；復刻版沒有外部請求
5. `audit-clone --strict`：保真度硬傷 0

### 已知且可接受的差異
- 原版在 Tailwind CDN 產生樣式前有一瞬間的無樣式閃爍（FOUC），期間會多下載 Inter 800 與 Noto Sans TC 900 字重；復刻版樣式一開始就在，沒有這個暫態。最終畫面像素相同
- 客服機器人的 AI 對話屬於前端 demo 腳本（原版就是如此），不是真實後端

## 保真度評分
| 面向 | 分數 | 依據 |
|---|---|---|
| 結構 | 5/5 | DOM 逐字節相同 |
| 視覺 | 5/5 | 20 組像素差異皆為 0 |
| 互動 | 5/5 | JS 原樣保留；動態 class 覆蓋 100% |
| 響應式 | 5/5 | 1440/768/390 計算樣式皆為 0 差異 |
| 離線能力 | 5/5 | 三個頁面都沒有外部 URL |

## 跑起來
直接雙擊任一 HTML 即可；或在專案根目錄執行：
```bash
python3 -m http.server 8123   # 開 http://127.0.0.1:8123/
```
入口頁：`index.html`（10 個畫面的總覽）

## 部署前須知
- 內容為使用者自有品牌（e+ 等公務園／各加盟機關），審計工具列出的「品牌殘留」屬於預期內容
- 若之後要改 Tailwind class，需重新編譯 `vendor/tailwind/*.css`（Tailwind CLI 3.4.17，content 指向對應 HTML，config 取自 `_src/original` 各頁的 `tailwind.config`，input 為 `@tailwind base; @tailwind components; @tailwind utilities;`）

---

## 新增：我的課程（個人專區，2026-10-01）
- 依據：Figma `sMwEZpjDITo6esSjSjyoyQ` 節點 12140:125916「#2012：前台 - 個人專區 (我的課程)」，共 11 個畫框（截圖存於 `RECON/figma-my-courses/`）
- 新頁面：`Portal我的課程.html`，由 `tools/build-my-courses.py` 產生。head、header、footer、共用腳本逐字取自 `Portal主首頁.html`，只替換 `<main>`；**Portal 改版後重新執行此腳本即可同步**
- 新檔案：`styles/my-courses.css`、`js/my-courses.js`、`assets/my-courses/`（Figma 匯出的 7 個 SVG：狀態卡圖示、即將下架圖示、tooltip 三角）
- 流程：Portal 點「登入」→ 頭像下拉選單「我的課程」→ 數位課程／組裝課程／電子書三個分頁（`#digital`／`#assemble`／`#ebook`），以及無權限彈窗（`#no-permission`）

### 對 Portal主首頁.html 的修改（不再與原始碼逐字相同，僅以下兩處）
1. 頭像下拉選單「我的課程」：`href="#"` → `href="Portal我的課程.html"`
2. `setAuth()`：登入狀態寫入 `localStorage['eplus-authed']`，載入時還原；在我的課程頁登出會回到首頁
- 修改前的版本保留在 `_src/original/`

### 已知事項
- Portal 共用 header 在 390 寬度原本就超出 22px；登入後多了頭像與語系按鈕，超出 128px。我的課程主內容本身沒有溢出。這是首頁 header 既有問題，未修改
- 新頁面放了一組隱藏的空節點（`#myc-home-stubs`），讓 Portal 共用腳本中操作首頁元素的程式不會出錯；否則同一個 script 區塊裡的登入／頭像選單程式不會執行

### 我的課程：既有元件一律沿用現有 HTML（2026-10-01 修正）
- 頁面外框、篩選卡外框、收合鈕、「清除選擇」、課程類型 tab、終身課程主類別／子類別、加盟機關、認證時數、認證時數類別、關鍵字＋搜尋、「共 N 筆課程」＋排序、分頁列、結果卡外框、toast：**全部由建置腳本從 `Portal主首頁.html` 探索頁原始 HTML 擷取**（id 前綴 `ex`→`mc`），行為交給 Portal 既有的 `window.__mkFilters('mc')`，不另寫樣式
- 學習狀態：沿用「報名狀態（已登入）」同一組 HTML 與互動，僅選項改為 未通過／已通過（id 前綴 `mcl-`，避免被 mkFilters 當成報名狀態重複綁定）
- 新內容（Figma 新增、Portal 沒有的）才使用 `styles/my-courses.css`：課程表格、進度步驟、學習狀態卡、操作選單、MOOCs 提示框、日期區間、組裝課程清單彈窗、無權限彈窗、退選確認

### 字型角色規則（2026-10-01，已寫入 Design System §3.1）
- 繁體中文（預設）Noto Sans TC 優先｜數字、英／數 Inter 優先（`.num`）｜英文 Arial 優先（`.en`、`lang="en"`）｜多語系 Noto Sans（其他 `lang`）
- `design-system/font-roles.js`：在 `[data-font-roles]` 容器內自動把數字段標 `.num`、英文字詞標 `.en`（略過圖示字型、輸入框、code）
- 已套用：Design System 全部規格頁與 UI Kit、`Portal我的課程.html`（body 加 `data-font-roles`；Portal 頁內 `body` 的 Inter 優先由 `styles/my-courses.css` 覆寫）
- 未套用：`Portal主首頁.html`、兩個加盟機關頁（仍為原始 Inter 優先）
- 日期區間（最近上課時間／課程下架時間）：改用 Tailwind 風格日曆彈窗（`js/my-courses.js` 的 DP 區段、`styles/my-courses.css` 的 `.myc-cal`），開始／結束連動成區間，支援鍵盤（方向鍵、PageUp/Down、Enter、Esc）、「今天」「清除」；「清除選擇」會一併清空日期
- 課程類型 tab 選中外框：Portal 原本的 1.5px 漸層環（`::before` 放大 2 倍再 `scale(.5)`）在 zoom .83 下，左緣和下緣會露出 0.5–1px 白線，Portal 探索頁也一樣。我的課程頁在 `styles/my-courses.css` 改成真正的 1.5px 漸層 border，外框尺寸不變；Portal主首頁.html 未修改
- Tooltip（MOOCs、通過條件的 ic-info，以及「展開組裝課程清單」）：比照 Figma UI Library `sys / tooltip`（`FUSZ9w9dJWB2DJUTUEiGVW` 103:4837）的 8 種變體。預設顯示在元素上方；上方空間不足時改到下方；置中會超出視窗時，三角改成靠左或靠右（距邊 12px）；上下都放不下時改到側邊。三角永遠對準元素中心
- 下拉選單：比照 Figma `sys / dropdown_select_list`（`FUSZ9w9dJWB2DJUTUEiGVW` 53:1928）。面板改為圓角 4、Elevation/00 陰影、無內距；選項內距 10/12、16px 行高 1.48、hover 疊 4% 黑。單選用純文字款、複選用左側勾選框款（check_box／check_box_outline_blank 20px）、排序用右側勾選款。只在 `styles/my-courses.css` 以 `#mc-form`／`#mc-sortbox` 範圍覆寫外觀，Portal 探索頁未修改
- 輸入框聚焦內框（2026-10-01，使用者回報）：Portal 全域 `:focus-visible{outline:3px…}` 與瀏覽器預設外框，會在外層已有框線的 input 內多畫一圈。已在 `Portal主首頁.html`、兩個加盟機關頁的第一個 `<style>` 末端加一條規則：文字輸入框、textarea、每頁筆數 select 聚焦時不畫 outline，改由外層框線換成主題色表示（`:where(...)[class][class]`）；`Portal我的課程.html` 已重新產生。這是對 Portal 原始碼的第 3 處修改
- 表格文字一致性（2026-10-01）：欄位標題統一 18px Bold（移除 16px 的 .sm）；組裝課程列表的課程名稱改為與其他表格相同的 20px Bold、on-surface #24364D（Figma 該處為 Regular 黑字，依使用者「同位階一致」要求調整）；空值「-」統一用 Noto Sans TC（`.myc-na`）
- e+ 智慧客服「2-1. 排隊時 → 進入學習環境須提醒」（Figma `sMwEZpjDITo6esSjSjyoyQ` 12057:195553，UX 稿）：Portal 客服共用程式新增 `window.csIsQueuing()`（AI 候位或專人客服排隊中）與 `window.csEndQueue()`（第 4 處 Portal 修改）。我的課程頁點課程名稱或「查看」時若排隊中，先跳 `#myc-qleave` 提醒；「確定，我要進入後台」會終止排隊再開課（已對齊 Figma 12704:102831 最新版：540×333、error_outline 72px 圓底、「如進入後臺，將會**終止排隊**。確定要進入後臺嗎？」，與設計稿逐像素比對誤差 ≤1px），「取消」則繼續排隊。示意：`Portal我的課程.html#queue-learn`
- 篩選條件收合摘要：沿用 Portal 既有 flt-sum（收合時在「篩選條件」列顯示已選條件 pill，每組最多 2 值＋N，點擊展開）。建置腳本把 `['mc-collapse','mc-filters']` 加入 PAIRS，並補「課程類型」與日期區間（開始～結束）的讀取；Portal主首頁.html 本身未改

## 新增：個人專區＿學習紀錄（2026-10-01）
- 依據：Figma `sMwEZpjDITo6esSjSjyoyQ` 節點 12340:89905「個人專區_學習紀錄_1列表」（截圖與統計卡圖示存於 `RECON/figma-learning-record/`）
- 新頁面：`Portal學習紀錄.html`，由 `tools/build-learning-record.py` 產生；共用片段（head／header／footer／Portal 腳本、探索頁篩選欄位、收合摘要）直接取自 `tools/build-my-courses.py`，表格、浮層、彈窗逐字沿用 `Portal我的課程.html`。**Portal 或我的課程改版後，兩支腳本都重新執行**
- 互動程式共用 `js/my-courses.js`（`body[data-page="learning-record"]` 時走 `LR` 分支）；新增元件樣式在 `styles/learning-record.css`
- 新內容：年度單選下拉（HTML 沿用探索頁「認證時數」下拉，id `mcy-`）、通過期間（同我的課程日期區間）、終身學習入口網站鈕（gradient-e+）、分頁籤（學習紀錄 & 時數上傳／統計圖表）、提示（secondary #E26414）、列印學習證明、3 張統計卡（點選＝依學習狀態篩選表格）
- 待補：統計圖表分頁（此 Figma 畫框未提供，暫為空狀態）、提示彈窗文案（暫擬）、列印學習證明（原型以提示訊息代替）、終身子類別欄（同我的課程，沿用探索頁靜態欄位）
- 連帶：重新產生 `Portal我的課程.html` 時會帶入 Portal 新增的 `#ex-tab-ring` 樣式；`styles/my-courses.css` 末端已停用該 `::before` 環，避免與本頁 SVG 框線重疊

## 篩選條件全站統一（以「我的課程」為準，2026-10-01）
- 項目卡：Portal 探索頁、兩個加盟機關探索頁（`#ex-tab-ring`）＋ Portal 主打課程專區的專區 tab 卡 `.ftab`／英語力廠商卡 `.fven`（`#ex-tab-ring-zone`）：未選 0.5px #d2dff3、選中 2px 漸層；加盟機關「探索」與課程類別 3 種結果頁的項目卡顏色也改成與我的課程相同（#74b509→#0b7dbc 框、#f2fbf9 底、同色勾勾），不再隨 theme 換色，::before＋SVG 遮罩描邊
- 下拉選單（`#ex-filter-dropdown`）：探索頁 `#ex-filters`、主打課程專區 `#feat-filters`、所有排序選單 `.exo-menu`（含加盟頁主推課程專區）改為 Figma sys/dropdown（圓角 4、無內距、16px 選項、check_box 勾選框，已選色走 --primary）；篩選卡外框陰影同我的課程
- 學習紀錄「年度」選項 `.mcy-opt` 同 only_text 樣式
- 以上樣式皆注入在三個頁面 `</body>` 前；Portal主首頁.html 改版後，`tools/build-my-courses.py`、`tools/build-learning-record.py` 需重新執行
- 未納入：加盟機關「主推課程專區」的分類膠囊鈕（公務人員必讀／環境教育…）為不同元件（pill tab），非項目卡，維持原樣
- 項目卡「選中」外框（2026-10-01 修正下緣變細）：遮罩描邊在卡片高度換算成半像素時下／右緣被稀釋 → 改為真正的漸層 border（2.29／2.41px＝2 畫面 px，padding 扣回、尺寸不變），選中／hover 上浮改 translateY(-2.29／-2.41px)＝2 畫面 px 整數位移；Portal 探索、兩個加盟機關探索（`#ex-tab-border`）與我的課程（`styles/my-courses.css` 檔尾）一致
- 主打課程專區 `.ftab`／`.fven` 選中外框同樣改為真正的 2 畫面 px 漸層 border（`Portal主首頁.html` 的 `#ftab-border`），hover 上浮改整數畫面像素；修正下緣白線

## 頁面互連（2026-10-01）
- Portal 頁首「我的學習」→ `Portal我的課程.html`；頭像選單 我的課程／學習紀錄 → 兩個個人專區頁
- Portal（含兩個個人專區頁）頁首「加盟機關」清單與加盟機關專區卡片：公務人力發展學院 → `加盟機關首頁_公務人力發展學院.html`，其餘示意機關 → `加盟機關首頁_港都e學院.html`；改為同分頁開啟（原為另開新分頁）
- 個人專區頁：Logo、麵包屑「首頁」→ Portal 首頁；探索選單「查看全部課程」→ `Portal主首頁.html#explore`（`js/my-courses.js`）
- 加盟機關頁會員選單（登入後）新增「我的課程」「學習紀錄 (證書)」；原有「返回e+公務園學習平台」→ Portal 首頁
- 仍為示意空連結（無對應頁面）：關於平臺／資安政策／隱私權、網站導覽／下載專區／常見問題、個人資訊／學習地圖／後台管理、最新消息全文、相關網站
- 加盟機關獨立頁（兩頁）探索篩選「加盟機關」：固定為本機關、不可複選（`#xea-locked`：移除下拉箭頭／清除鈕／+N／選單，欄位只顯示本機關 chip；鍵盤與點擊皆不開選單）；「清除選擇」不再把本機關算成已選條件
- 所有下拉選單（`#dropdown-radius`，三個首頁檔尾，個人專區頁重新產生後帶入）：面板圓角實際 12px、選項文字實際 17px（zoom 換算 13.71／14.46px、19.43／20.48px）；說明文字、分類標題、帳號資訊維持原字級

## 加盟機關「FEATURED 主推課程專區」原檔核對（2026-10-02）
- 附件 `e_加盟機關_離線版-2-1.zip` 解開後 3 支 HTML、`開啟說明.txt` 與 `_src/original/` 逐位元組相同（cmp 皆 SAME），assets／styles 亦同 → 復刻基準未變
- 兩個加盟頁首頁皆保留原檔「FEATURED 主推課程專區」區塊：公務人力 11 張、港都 4 張分類卡（`a.feat-card` → `#feat=<分類>`）
- 結果頁 `#pf-view`：原檔 vs 現行，1440／1280／390 三種寬度點擊前 3 張卡皆進入結果頁、分類膠囊正確選中、15 張課程卡、0 JS 錯誤；1440 截圖逐像素比對僅 DEMO 面板／歡迎泡泡差異
- index.html：兩加盟頁的卡片標題由「主打課程專區」更正為「主推課程專區（FEATURED）」（Portal 端仍為主打課程專區，對應原檔用詞）

## 加盟機關獨立頁：各檢視拆成獨立頁面檔（2026-10-06）
- 附件 `styles.zip`、3 支 `-1.html` 與 `_src/original/` 逐位元組相同 → 樣式與 HTML 基準未變
- 原檔把「主推課程專區」「探索」「課程類別 3 頁」做成機關首頁內的 hash 檢視，專案內看不到獨立頁面、hash 連結在預覽環境也可能失效 → 以 `tools/build-agency-pages.py` 每機關產生 5 個實體檔：
  `加盟機關_<機關>_主推課程專區.html`、`_探索課程.html`、`_數位課程.html`、`_組裝課程.html`、`_電子書.html`
- 子頁＝機關首頁逐字複製，只加 `<head>` 末端 `#agency-router` 與 `<title>`；首頁也注入同一段路由。首頁 FEATURED 分類卡、Header／手機選單「課程類別」連結改指向實體檔（保留 `#feat=分類`／`#explore=類型`）；舊的 `首頁.html#feat`、`#explore=…` 深連結自動轉到對應檔
- 機關首頁有改動時需重跑 `python3 tools/build-agency-pages.py`
- 驗證（兩機關、1440）：5 個子頁直接開啟皆顯示正確檢視；首頁分類卡→主推課程專區（分類正確選中）→麵包屑首頁→上一頁；課程類別 3 頁互跳；探索；`#hot` 回首頁；舊深連結轉址；0 JS 錯誤
- index.html：加盟機關兩組各新增 3 張課程類別入口卡，主推課程專區／探索課程改連實體檔
- 主推課程專區：篩選條件收合時，摘要列顯示目前選取的「主推分類」（沿用既有 flt-sum 摘要膠囊，排在關鍵字之前；點摘要即展開）。改在兩支機關首頁的摘要程式 groups()，已重跑 build-agency-pages.py 同步 10 支子頁（2026-10-06）
- 主推課程專區收合摘要改為與「篩選條件」左右同列（`#pf-sum-inline`：篩選卡 flex-wrap，右側放不下才換行；≤640 沿用既有規則換到下一行）；展開狀態版面不變。已重跑 build-agency-pages.py（2026-10-06）
