/* ==========================================
   字體
   ------------------------------------------
   🔴 這裡刻意沒有 @import。字體改由 partials/head-common.html 的 <link> 載入。
      @import 要等 style.css 下載並解析完才開始請求字體，形成串連等待，
      是首屏字體跳動最嚴重的載入方式。

   內文用系統字體堆疊、不依賴 web font：中文字體檔很大，
   內文全靠 web font 在慢速網路下必定看到整頁文字替換跳動一次。
   自訂字體只留給標題（h1-h4），數量少、影響範圍小。

   ⚠️ 若要改回內文也用 Noto Sans TC，就會把字體跳動一起帶回來，
      不要只改這裡而忘了那個後果。
   ========================================== */

/* ==========================================
   Design Tokens（自 design/首頁.jpg 取樣統整，待逐頁確認）
   ⚠️ `design/` 已於 2026-08-18 移除（commit dc907e3），這行留著是**設計依據的出處記錄**，
      不是還在的路徑。要看原圖：`git show 4cb9a8f:design/首頁.jpg > /tmp/首頁.jpg`
   ========================================== */
:root {
  /* ★ 品牌色（2026-08-11 客戶指定）——要換品牌色只改這三行
     --brand-purple-dark 是由主色加深算出來的 hover 色，沒有客戶指定值

     🔴 --brand-purple 已於 2026-08-24（Q4）由 #451470 改成 #6B2FA8：
        公會窗口反映「主色深紫太暗，要明亮一點」，PM 定案採用 #6B2FA8。
        被否決的候選（都算過白字對比，全部過 AA）：
          #5C2091（10.14）— 幅度太小，很可能改了等於沒改、還要再來一次
          #714C8E（6.72）— 柔和偏灰，是既有的 --purple-600，會與色階混淆
          **#6B2FA8（8.09）← 採用**，一眼看得出變亮
     🔴 --brand-purple-soft 已於 2026-08-25（R21）由 #9B82BE 改成 **#704C8C**（客戶指定）。
        ⚠️ **它仍然是「客戶指定色」，不要為了配色微調**——換值是客戶自己要求的。
        ✅ **最實際的好處：多了一個可以配白字的紫**（白字對比 3.32 → **6.77**）。
           舊值 #9B82BE 太淺，`downloads.css` 當年為此留了一則「不要用它當底色配白字」的
           警語——那則註解在 R21 之後已經不成立，**已同步改掉**
           （留著會主動說服下一個人一件已經不是事實的事）。
        ⚠️ **站上看得到的變化不多**：唯一整片拿它當底色的是 quicklink 淺色卡，
           而那個位置 Q2 之後已經改成金色，目前一張都沒有。其餘消費端是
           **邊框 5 處、捲軸 2 處、12〜30% 的 tint 4 處、圖示色 1 處**。
        🔴 **tint 會跟著變深，上面有文字的要重驗**（實測 2026-08-25，全部仍過門檻）：
           行事曆今天 18% ＋ 主色日期 6.76 → **6.19**；chip 30% ＋ 主色 5.95 → **5.08**；
           行事曆高亮 22% ＋ 內文 13.95 → **12.54**；會員專區 22% 圓底 ＋ 主色圖示 6.48 → **5.83**；
           報名選中 12% ＋ muted 5.33 → **5.06**。**每一項都比舊值低，但都還在門檻之上。**
           ⚠️ 再加深這個色之前要重跑這張表——**它們是一路往下走的**。

     🔴 --brand-purple-dark 必須跟著主色重算，否則會變成「主色很亮、hover 很暗」的突兀落差。
        算法：**維持同色相同飽和度，亮度乘 0.666**（那正是舊的 #451470 → #2E0D4B 的關係，
        HSL 亮度 0.259 → 0.1725）。#6B2FA8 的 HSL 亮度 0.4216 × 0.666 = 0.2808 → **#471F70**。
        ⚠️ 換主色時不要只改上面那行就收工——這一行沒跟著改不會報錯，只會看起來很怪。

     ⚠️ Q4 的連帶影響（實測值，2026-08-24）：全站 hover 的金色對比從 **7.36 降到 4.50**
        （--gold-from #E0BD70 對主色）。仍遠高於當初的問題狀態（紫→更深紫只有 1.24），
        但比舊主色時代低。**要再提亮主色之前先想到這件事**：底色越亮，金色 hover 越不明顯。 */
  --brand-purple: #6B2FA8;
  --brand-purple-dark: #471F70;
  --brand-purple-soft: #704C8C;

  /* Colors — Primitive
     ⚠️ 以下是接手時的舊色階。主色與淺紫已改用上面的品牌色，
        但這些舊值還有消費端（.btn-light、兩處漸層、tag 底色），尚未統一，
        清單見 CLAUDE.md「色票」那節。不要以為改了品牌色就全站都換完了。 */
  --purple-700: #5A3B72;
  --purple-600: #714C8E;
  /* 🔴 T5（PM 2026-08-28）：會員專區歡迎區漸層的**亮端**，只有那一處消費端。
     為什麼要新開一階，而不是沿用既有的：
       Q4 把主色（漸層暗端）改亮成 #6B2FA8，但亮端 --purple-600 沒跟著動
       → 兩端只差 **1.20**（舊主色時是 1.97），客戶當初嫌的「一塊實心深紫」回來了。
       🔴 **不可以直接沿用 --purple-500 #8A5FA8**：兩端差距能拉到 1.65，
          但 .member-welcome-sub 的 **85% 白副標**在它上面只剩 **4.04，掉出 AA 4.5**。
     ⚠️ 這一階的上限是「85% 白仍過 AA」算出來的，不是挑一個好看的顏色：
        85% 白對背景的對比隨背景變亮而下降，4.5 那條線落在亮度 47% 附近。
        本值刻意停在 4.69 而不是壓線的 4.52，留一點餘裕給日後微調。
     實測（2026-08-28，全部對 #7E549B）：
       兩端差距（對 --brand-purple #6B2FA8）  1.40  ← 原本 1.20
       白字（.member-welcome-greeting）      5.79  ✓ 過 AA
       85% 白（.member-welcome-sub 與 S17 的隱私提示）  4.69  ✓ 過 AA
     🔴 要再亮就必須先改副標的透明度，那是另一個決定，**不要單獨把這個值往上調**。 */
  --purple-550: #7E549B;
  /* 500 是 2026-08-12 為頭像佔位圓補的一階（客戶說 600 太深）。
     ⚠️ 它是「白字還能過 AA 的最淺紫」：白字在它上面 4.90:1，
        再往 400（#A376C7）走就掉到 3.52，白字讀不清楚。
        要更淺就必須把首字改成深色，但那時圓圈對白卡只剩 2.x，
        「看不出頭像範圍」的問題會回來（見 about.css 該處註解）。 */
  --purple-500: #8A5FA8;
  --purple-400: #A376C7;
  --purple-200: #C6A8DE;
  --purple-300: #9B7EB0;
  --purple-950: #33223E;
  --gray-900: #1A1A1A;
  --gray-600: #636363;
  --gray-100: #ECECEC;
  --gray-50: #F7F5F9;
  --gray-border: #E6E1EA;

  /* 金色漸層（2026-08-11 客戶指定，用於會員登入按鈕）
     --color-on-gold 是金底上的文字與圖示色，客戶一併指定。

     🔴 --gold-from 已於 2026-08-12 由 #D5AD59 調亮成 #E0BD70，為的是讓
        客戶指定的文字色 #654729 在漸層兩端都過 AA。歷程與現況：

        | 底色                        | #654729 的對比 | 判讀 |
        |----------------------------|---------------|------|
        | 舊 --gold-from #D5AD59     | 4.00:1        | ✗ 低於 AA 4.5:1（漸層偏暗那側不合格）|
        | **現 --gold-from #E0BD70** | **4.70:1**    | ✓ 過 AA |
        | --gold-to #EED593（未動）   | 5.86:1        | ✓ 過 AA |

     🔴 為什麼調底色而不是調文字色：
        #654729 是客戶指定的，而且他特地更正過一次色碼（#392F25 → #654729），
        **文字色不准動**。改漸層暗端是唯一能補足對比、又不影響他認得的視覺的做法。
     ⚠️ --gold-to（#EED593）刻意不動：它那一端本來就過 AA，
        兩端一起調亮會讓整個金色變得太淡、失去「金」的辨識度。

     ⚠️ 順帶確認「金色 vs 紫底的變化幅度」沒有變差——那是當初把 hover 從
        「紫色再加深」改成金色的依據（客戶原話：長輩幾乎看不出差異）：
          舊紫底 #451470 → 舊金 #D5AD59 ＝ 6.27
          舊紫底 #451470 → **新金 #E0BD70 ＝ 7.36**（本機瀏覽器實測，2026-08-12）
        調亮之後幅度更大，不是變差。

     🔴 2026-08-24（Q4）主色改成 #6B2FA8 之後這組數字變了：
          **新紫底 #6B2FA8 → 金 #E0BD70 ＝ 4.50**（漸層另一端 #EED593 ＝ 5.61）
        仍遠高於當初的問題狀態（紫 → 更深紫只有 1.24），hover 依然一眼看得出來，
        但幅度已從 7.36 掉到 4.50。**主色再往亮的方向調，這個數字會繼續掉**——
        ⚠️ 金色是客戶指定色，不可以為了拉回對比去改它（要改的是主色，且要 PM 定案）。

     🔴 金底上仍然絕對不可以用白字，而且調亮後更糟：
          白字 on 舊金 #D5AD59 ＝ 2.11 ／ 白字 on **新金 #E0BD70 ＝ 1.80**
        底色越亮，白字越讀不到。要在金底上放文字只有 --color-on-gold 一個選項。

     🔴 全站金底上的文字只能用 --color-on-gold，不要在各處寫死色值——
        寫死的話下次改色會有一半改不到，而且不會報錯。
        同理 --gold-from／--gold-to 也只在這裡定義：本輪 grep 過全站，
        除了本註解與這兩行之外沒有任何寫死的金色色值。 */
  --gold-from: #E0BD70;
  --gold-to: #EED593;
  --color-on-gold: #654729;

  /* 社群品牌原色（2026-08-12 客戶第 38 項：右下浮動鈕不要用淺紫，要用兩家的原色）
     🔴 這兩個值是**別人的品牌色**，不是我們的設計 token——不要為了配色微調它們，
        也不要拿去用在非社群的元件上。hover 用各自的官方深色態。

     ⚠️ 白色圖示在這兩個底色上的實測對比（本機瀏覽器算的，非估值）：
          白 on #1877F2（Facebook）→ 4.23:1   過 WCAG 非文字 3:1，甚至接近文字 4.5:1
          白 on #06C755（LINE）    → 2.26:1   ✗ 未達 3:1
        LINE 的綠色先天亮度太高（相對亮度 0.415），白色圖示不可能達標；
        「一步深」的 #06A550 才是 3.23:1。目前**維持官方 #06C755**，理由是
        WCAG 1.4.11 對商標／標誌有明文豁免，且客戶認得的是官方色。
        若日後決定要達標，改這一行為 #06A550（連同 hover）即可，不要改成白底綠字
        ——那是同一組色對，對比一樣是 2.26。 */
  --brand-facebook: #1877F2;
  --brand-facebook-hover: #0C63D4;
  --brand-line: #06C755;
  --brand-line-hover: #05A948;

  /* ══════════════════════════════════════════
     客戶 logo 的四個顏色（R17，2026-08-25）
     ------------------------------------------
     這是 **primitive 層**：全站唯一宣告這四個色碼的地方。
     🔴 **同一個色碼在 `:root` 只能出現一次。** Q3 的課程狀態色其實就是取自
        這四色裡的兩個（#FA5B12／#0F723F），當時直接寫死在 `--status-*` 上；
        R17 要用同一組色，若再宣告一次就會變成**兩份**——客戶哪天說「橘色再深一點」
        只會改到一半，**而且不會報錯**。所以改成狀態色去指向這裡。
     ⚠️ 命名刻意用編號（`--logo-1`〜`4`）而不是顏色名：客戶日後換 logo 配色時，
        「橘」可能就不是橘了，而消費端不必跟著改名。
     ⚠️ 這四個是 primitive，**消費端不要直接用**——語意層是 `--status-*`
        與 `.news-badge.is-tone-N`。

     實測對比（標籤是 1rem＝18px 常規字重，門檻 AA 4.5）：
       | 色 | 配白字 | 配深字 #3D1400 | **實際採用** |
       |---|---|---|---|
       | `--logo-1` #EF9D35 | **2.20** ✗ | 7.35 ✓ | 🔴 **白字（PM 2026-08-25 定案）** |
       | `--logo-2` #FA5B12 | **3.20** ✗ | 5.06 ✓ | 🔴 **白字（PM 2026-08-25 定案）** |
       | `--logo-3` #080A6E | **16.42** ✓ | — | 白字 |
       | `--logo-4` #0F723F | **6.00** ✓ | — | 白字 |

     🔴 **四個標籤一律白字，不要「修正」回深字——這是已知且刻意接受的取捨。**

     兩個暖色是分兩次定案的，方向一致：
     ・`--logo-2`（稅務訊息）先改：橘底配深字**視覺上反而更難讀**
       （深棕字在飽和橘底上會糊成一團）。
     ・`--logo-1`（最新消息／防治洗錢）當天稍後也改：PM 看過實際畫面後認為
       **底色夠深，白字仍看得清楚**，並要求與其餘三色統一。

     🔴 **數字上 `--logo-1` 配白字只有 2.20、比 LINE 綠（2.26）還低，PM 已知情。**
        判斷依據與全站 LINE 綠底白字同一類：**數字不是唯一的可讀性判準**，
        而這是品牌色底上的短標籤、不是內文；四顆標籤同時出現在同一畫面，
        **一深三白的不一致本身也是可讀性問題**。
        ⚠️ 一併適用於課程狀態的「即將額滿」（同樣是 `--logo-2` 底色）。
     ✅ 全部遵守同一條原則：**保留客戶指定的底色、只調文字色**。

     ⚠️ **`--logo-warm-text` 已於此時移除**：`--logo-1` 改白字之後它一個消費端都不剩，
        留著就是一個沒人用、卻看起來像「暖色底該用這個」的假規範。
        要復原深字的話值是 `#3D1400`（上表那一欄）。
     ══════════════════════════════════════════ */
  --logo-1: #EF9D35;
  --logo-2: #FA5B12;
  --logo-3: #080A6E;
  --logo-4: #0F723F;
  /* 課程狀態標籤色（Q3，客戶 2026-08-24：「三種狀態要有顏色分別」）
     🔴 底色 #FA5B12／#0F723F 是**客戶指定的**，不要為了配色微調。
     🔴 R17（2026-08-25）之後這兩個值**改成指向上面的 logo primitive**——
        它們本來就是同一組色（客戶的 logo 色），先前是各寫一份。
     實測對比（標籤是 0.875rem ＝ 15.75px 粗體，**不算大字，門檻是 AA 4.5 不是 3**）：
       | 配色                                | 對比 | |
       |------------------------------------|------|---|
       | #0F723F 綠底 ＋ 白字                | 6.00 | ✓ |
       | **#FA5B12 橘底 ＋ 白字**            | 3.20 | 🔴 ← **PM 2026-08-25 改採這個** |
       | #FA5B12 橘底 ＋ 深字 #3D1400        | 5.06 | ✓（Q3 原本的做法，2026-08-25 已推翻） |
       | 加深成 #C2410C ＋ 白字              | 5.05 | ✓（但改掉了客戶指定的色）|
     🔴 選「保留客戶的底色、改文字色」而不是「改底色」，與全站金底的處理方式一致
        （金底一律 --color-on-gold 深字、不用白字）——**客戶認得的是底色**。
     ⚠️ 「已額滿」的灰**不在這組裡也不要改**：它用 --color-muted，
        理由是「已額滿是資訊不是錯誤」（見下方 .course-status 的說明）。 */
  --status-nearfull: var(--logo-2);
  --status-nearfull-text: #FFFFFF;
  --status-open: var(--logo-4);
  --status-open-text: #FFFFFF;

  /* Colors — Semantic */
  --color-primary: var(--brand-purple);
  --color-primary-hover: var(--brand-purple-dark);
  --color-primary-soft: var(--brand-purple-soft);
  --color-primary-light: var(--purple-400);
  --color-heading: var(--purple-950);
  --color-text: var(--gray-900);
  --color-muted: var(--gray-600);
  --color-bg: #FFFFFF;
  --color-bg-alt: var(--gray-100);
  --color-surface: #FFFFFF;
  --color-border: var(--gray-border);
  /* 🔴 U4（PM 2026-08-31）：時效提醒用的紅。全站在此之前**沒有任何 danger／error 色**。
     目前唯一消費端是課程卡片／內頁的「報名至 …」在剩餘 ≤3 天時轉紅（`.is-urgent`）。
     ⚠️ **不可以挪用 `--file-pdf: #C62828`**：那是「PDF 這種檔案格式」的顏色，
        色碼相同純屬巧合——把它拿來當警示色，日後客戶要換 PDF 徽章顏色就會
        連帶把截止提醒一起換掉，**而且不會報錯**。同一個色碼、兩個不同的意思，
        就該是兩顆 token。
     ⚠️ 白底對比 **5.9:1** ✓ 過 AA。要換色值請確認白底仍過 4.5。
     🔴 元件裡一律用這顆 token，不要寫死色碼。 */
  --color-urgent: #C62828;
  /* 🔴 分隔線專用的「看得見版」邊框色（客戶 2026-08-25：本會會務每筆訊息的分隔線太淺）。
     `--color-border` #E6E1EA 對白底只有 **1.29**，而消息清單底下還疊著一張背景圖
     （`.news`／`.courses-band` 的 55% 白遮罩），淺色線在上面幾乎消失。
     這個值對白底 **1.72**，客戶要的是「加深一點點」，不是加深到像格線。
     ⚠️ **不要直接把 `--gray-border` 調深來解**：那顆 token 全站到處在用
        （表格、卡片、輸入框、頁尾…），調深會讓整站的框線一起變重，
        而客戶只反映了消息清單這一處。
     ⚠️ 目前唯一消費端是 `.news-item`。其他 7 條 `dashed var(--color-border)`
        （`register` 分隔、`course-detail`、`downloads`、`member-center`、
        `about`、`calendar`、`style.css:3907`）**維持原色**——它們都在純白卡片上，
        客戶沒有反映看不清楚。要一起加深請先確認，不要順手改掉。 */
  --color-border-strong: #CCC2D4;
  --color-tag-info-bg: var(--purple-200);
  --color-tag-gov-bg: var(--purple-300);

  /* 檔案格式色（下載專區的格式徽章）
     2026-08-12 客戶第三批第 7 項：「希望標籤有不同顏色去分辨，例如 excel 是綠色、
     word 檔是藍色之類的」。用大家在別的軟體裡已經認得的色相，不自創一套：
     紅＝PDF、藍＝Word、綠＝Excel。

     🔴 這三個值都是「配白字」用的底色，全部量過 WCAG 對比（本機算的，非估值）：
        | token          | 底色      | 白字對比 | AA 4.5:1（徽章字級 0.75rem 屬小字） |
        |----------------|-----------|---------|-----------------------------------|
        | --file-pdf     | #C62828   | 5.62:1  | ✓ |
        | --file-word    | #185ABD   | 6.49:1  | ✓ |
        | --file-excel   | #107C41   | 5.27:1  | ✓ |
        外部連結徽章不在這組裡：它是**外框式**（透明底＋主色紫字，13.23:1），
        用形狀而不是顏色和「可下載的檔案」區分開來。

     🔴 顏色只是輔助，不是唯一線索：徽章上一律保留格式文字（PDF／DOCX／XLSX）。
        紅與綠對紅綠色弱者是最難分的一組，而且黑白列印會全部變灰
        ——文字沒了就真的分不出來。**不要為了視覺乾淨把文字換成純圖示。**

     ⚠️ 新增格式（PPTX／ZIP／ODF…）時在這裡加一個 token、
        在 downloads.css 加一條 .is-<格式> 規則，並把新底色的白字對比補進上表。
        沒配到色的格式會落到 .dl-item-badge 的預設主色紫，不會沒有樣式。 */
  --file-pdf: #C62828;
  --file-word: #185ABD;
  --file-excel: #107C41;

  /* Typography
     --font-heading 用 web font（h1-h4，需要 600 與 700 兩個字重）
     --font-body    純系統字體，不發任何字體請求
     ⚠️ 原本兩者都是 'Noto Sans TC', 'Inter'。'Inter' 其實永遠不會被用到——
        Noto Sans TC 本身就含完整拉丁字母，排在它後面的 Inter 沒有機會遞補，
        等於白載三個字重，已移除。 */
  --font-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang TC', '蘋方-繁',
    'Microsoft JhengHei', '微軟正黑體', 'Noto Sans TC', sans-serif;
  --font-heading: 'Noto Sans TC', var(--font-system);
  --font-body: var(--font-system);

  /* Radius */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;

  /* Layout */
  --container-max: 1440px;
  --header-container-max: 1600px;

  /* Shadow（帶品牌色調的柔和陰影，避免死黑） */
  --shadow-sm: 0 1px 3px rgba(113, 76, 142, 0.10);
  --shadow-md: 0 6px 20px rgba(113, 76, 142, 0.12);
  --shadow-lg: 0 12px 32px rgba(113, 76, 142, 0.16);

  /* Spacing */
  --spacing-section: 96px;
  --spacing-section-sm: 56px;
  --spacing-gap: 24px;

  /* 🔴 --page-gutter ＝ 內容與視窗左右邊緣的距離，全站唯一來源。
     2026-08-12 客戶回報：「當裝置縮小時，很多地方沒有做間距，導致文字和視窗緊貼」
     並明講「其他我沒有截圖和提出的地方都要全站檢查一次」。
     實測根因不是「值太小」，而是**有幾個滿版區塊的左右內距是 0**
     （.hero-inner 的 padding 寫成 92px 0px 132px、.quicklinks 完全沒有內距），
     其餘區塊本來就是 24px。所以修法是「把 0 補上」而不是「把 24 改小」。

     ⚠️ 值刻意維持 24px、不隨斷點改變：
        24px 已經是全站既有的實際值，改成隨斷點變動只會多一個會寫錯的斷點，
        而客戶要求的安全下限是 16px——24 已經高於下限，沒有再往下調的必要。
     🔴 新增任何「貼著視窗邊緣的區塊」時，左右內距一律用這個變數，
        不要再寫 24px 字面值，也不要寫 0。寫 0 就是這次客戶回報的那個 bug。

     ⚠️ 元素本身就是 max-width 容器時（.hero-inner、.quicklinks 這種
        「自己 margin: 0 auto」的寫法），加上內距會讓它比別的區塊窄 48px、
        在寬螢幕上與其他區塊對不齊。這時 max-width 要一起改成
        calc(var(--container-max) + var(--page-gutter) * 2)，
        內容框才會回到 1440px、與 24px 內距那一派的區塊左右對齊（已實測一致）。 */
  --page-gutter: 24px;

  /* Header — 🔴 這裡有三列，不是一列。改 header 結構時務必回來更新這組 token。
     ┌ --header-top-height     工具列（回首頁／網站導覽／相關連結／下載專區／文字大小）
     ├ --header-height         導覽那一列（logo／主導覽／會員登入）
     └ --announcement-height   公告跑馬燈
     --header-stack ＝ 三者相加，實測 .site-header 總高 163px（2026-08-12 客戶要求縮矮後）。

     🔴 要把內容推到 header 底下（sticky 元素的 top、錨點的 scroll-margin-top）
        一定要用 --header-stack，不要用 --header-height。
        用錯的後果是內容被 header 蓋住，而且不會報錯——只是「標題剛好看不到」。

     🔴 這個坑已經發生兩次，兩次都是「header 多長一列、卻沒更新 token」：
        1. 2026-08-11 加公告列後少算 48px（掃同類修了 6 處）
        2. 2026-08-12 加 header-top 工具列後少算 52px
        ⚠️ 所以：**新增任何一列 header 都要在這裡加一個 token 並併進 --header-stack**，
           不要在各頁的 CSS 補魔術數字——下一次又長高時，那些數字全部要重找一遍。

     🔴 而且「改高度」和「加一列」一樣會踩到：2026-08-12 縮矮 header 時
        padding 與字級都改了，這三個值全部要重量一次。
        因為漏改不會報錯（只是內容被蓋住一點），script.js 加了一道執行期檢查：
        實測值與 token 差超過 2px 就在 console 印警告，並就地修正這三個變數。
        ⚠️ 那道檢查是「JS 跑了才更準」的加分項，不是正確性的依賴——
           所以下面的靜態值本身就必須是對的（寧可寫大一點，不要寫小）。

     ⚠️ 工具列在放大字級或窄螢幕會換行變高，寫死的數字不會永遠正確。
        因此**只要 sticky 的捲動容器不是視窗本身**（自己帶 overflow 的元素內部），
        就直接用 top: 0，完全不要依賴這組 token——那個容器的頂端與 header 無關。
        反過來說，黏在視窗上的 sticky 才用 top: var(--header-stack)。
        ⚠️ 這裡原本舉「會員名錄表頭」當例子，已經不成立：
           about.css 的 .about-table-wrap 在 1024px 以上改成 overflow: visible，
           那個表頭現在是黏在視窗上、用 top: var(--header-stack)。
           要舉例前先確認該處到底是不是元素捲動容器，否則會照著改錯。 */
  /* 🔴 這一列的高度**會隨字級變**（文字是 0.875rem，rem 跟著 html font-size 走）。
     2026-08-14 實測三種字級（375／1024／1440 三個寬度量出來都一樣）：

       預設 18px → 40.39px ｜ 中 20px → 43.00px ｜ 大 22px → 45.59px

     🔴 所以這裡取的是**最大值的向上取整（46px），不是中位數**。
        判準是「寧可多讓空隙，不要少讓」：寫大只是多留一點空白，
        寫小會讓 JS 沒跑時的 sticky 定位與錨點 scroll-margin-top 少推，內容被 header 蓋住。
     ⚠️ 原本寫 43px（＝中字級的值），在預設與大字級都比實測小或差太多，
        `script.js` 的守門檢查因此長期在 console 叫。
        那道檢查已於 2026-08-14 一併改成「只在寫得太小時警告」——
        **兩處要一起看**，只改其中一邊會讓警告回來或讓警告失去意義。
     ⚠️ 日後改工具列的字級、padding 或項目數，要重量這三個值再取最大。 */
  --header-top-height: 46px;
  /* 導覽那一列的高度 ＝ logo 高 ＋ 上下呼吸空間。歷次調整：
     80px → 72px（2026-08-12 客戶「縮小一點點」）→ 桌機維持 72、手機另外壓到 60（見下）。
     ⚠️ 現在桌機是 logo 64 ＋ 上下各 4px，**已經沒有再放大的空間**（見 .brand-logo-img）。 */
  --header-height: 72px;
  --announcement-height: 48px;
  /* 後台管理列（只有後台登入中才有，見 partials/admin-bar.php）。
     平常是 0px，所以下面的 calc 不必分兩套寫法；html.has-admin-bar 會把它改成實際高度。
     🔴 一併算進 --header-stack：管理列是 fixed 疊在最上面，sticky 的 header 與
        錨點的 scroll-margin-top 少推這一段，內容就會被壓在管理列底下——
        而且只有管理員看得到，客戶端永遠不會回報這個問題。 */
  --admin-bar-height: 0px;
  --header-stack: calc(var(--header-top-height) + var(--header-height) + var(--announcement-height) + var(--admin-bar-height));
}

/* 🔴 手機版單獨壓低導覽列（客戶 2026-08-25：「logo 上下的間距可以縮短一點點，
   不然客戶會覺得好像還有空間可以放大」）。
   手機的 logo 是 44px，在 72px 的列裡上下各留 14px——那個空隙大到會被讀成
   「這裡還空著」。壓到 60px 之後上下各 8px，與 640–1023 那一階一致。
   ⚠️ **只改 `--header-height` 就夠了**：`--header-stack` 是 `calc()` 引用它，
      會自動跟著變小，sticky 與錨點的偏移不必另外調。
   ⚠️ 漢堡鈕是 40px，60px 的列裝得下。
   🔴 不要用這個值去壓桌機：桌機 logo 64，60px 的列會讓它上下溢出。 */
@media (max-width: 639px) {
  :root {
    --header-height: 60px;
  }
}

/* ==========================================
   Base Reset
   ========================================== */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* 🔴 全站根治：`hidden` 屬性必須真的隱藏元素。
   ------------------------------------------
   HTML 的 `hidden` 是靠瀏覽器預設樣式 `[hidden] { display: none }` 生效，
   而**作者樣式永遠贏過瀏覽器預設樣式**。所以任何元件只要自己宣告了 display
   （`.courses-grid { display: grid }`、`.gallery-grid`、`.dl-panel`…），
   `el.hidden = true` 就**完全沒有效果**。

   🔴 這種 bug 驗屬性驗不出來：`el.hidden` 讀起來是 `true`、程式碼看起來也對，
      只有實際看畫面（或讀 `getComputedStyle().display`）才會發現。
      2026-08-13 courses.html 三種檢視疊在一起就是這樣——使用者看到的是「點了沒反應」。

   **為什麼是兩個 `[hidden]` 而不是一個**：寫一次是 (0,1,0)，與 `.courses-grid`
   同特異性，後定義的那條會贏（而頁面 CSS 都排在 style.css 之後，一定是它贏）。
   重複一次把特異性抬到 (0,2,0) 就穩定勝出。不用 `!important`（本專案禁用）。

   ⚠️ **它的上限是 (0,2,0)**：若日後有 ≥2 個 class 的規則對「可能被 hidden 的元素」
      設非 none 的 display，這條還是會輸。已掃過全站 314 條 display 宣告，
      目前只有 4 組面板類規則達到那個特異性
      （`.about-panel.is-active`／`.member-panel.is-active`／`.register-panel.is-active`／
      `.course-tab-panel.is-active`），而它們**一律用 `.is-active` class 切換、不用 `hidden`**，
      所以不衝突。
   🔴 **判準：面板類（用 `.is-active` 顯示的）不要改用 `hidden` 切換**——
      混用就會踩回這個 bug，而且會是「明明加了 hidden 卻還看得見」。

   全站有 50 處 `.hidden =` 賦值（`script.js` 27、`downloads` 12、`about` 6、
   `gallery` 4、`lightbox` 1），逐處補 CSS 一定會漏，所以在這裡一次解決。 */
[hidden][hidden] {
  display: none;
}

/* ==========================================
   文字大小切換（預設／中／大）
   ------------------------------------------
   使用者多為長輩，右下角提供文字放大切換。原理是改 <html> 的 font-size，
   全站字級都是 rem，所以會一起縮放。

   🔴 因此全站 CSS 的 font-size 一律用 rem，不可以用 px。
      寫 px 的那一行不會跟著放大，而且不會報錯——只會有一小塊字沒變大，
      使用者只覺得「怪」，不會知道原因。
      2026-08-11 已把既有的 108 處 px 字級全部轉成 rem。

   ⚠️ 間距（padding／margin／gap）刻意維持 px，不隨字級縮放：
      若間距也放大，版面會整體變胖、手機更容易溢出，等於變成整頁縮放。
      代價是放大模式下按鈕與卡片的留白相對變窄——所以中／大模式一定要
      實測各斷點有沒有裁切或溢出（見 CLAUDE.md 驗收方式）。

   class 由 partials/head-common.html 在 <head> 就掛上（避免開頁後字級跳動一次）。
   ========================================== */
/* 🔴 預設字級是 112.5%（18px），不是瀏覽器預設的 16px。
   客戶指出「網站文字偏小，政府機關網站的文字都蠻大」，而使用者以長輩為主。
   實地比對過連結區那幾個政府網站，內文多半落在 17–18px。
   ⚠️ 改這個數字會等比放大全站所有 rem 尺寸，包含導覽列——
      導覽放不下就會橫向溢出（本專案已經因為導覽塞不下改過一次斷點）。
      調整後一定要重測 1280／1440 的 header，不要只看內文。 */
html {
  font-size: 112.5%;
  /* 18px */
  scroll-behavior: smooth;
}

html.fs-md {
  font-size: 125%;
  /* 20px */
}

html.fs-lg {
  font-size: 137.5%;
  /* 22px */
}

body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--color-text);
  background-color: var(--color-bg);
  line-height: 1.6;
}

/* 後台登入中才成立：管理列是 fixed，要空出它的高度，否則頁首第一列被蓋住。
   ⚠️ 管理列在手機上可能因為關站警示換行而變高，所以高度分兩階（見下方 @media）。 */
html.has-admin-bar {
  --admin-bar-height: 40px;
}

html.has-admin-bar body {
  padding-top: var(--admin-bar-height);
}

@media (max-width: 639px) {
  html.has-admin-bar {
    --admin-bar-height: 36px;
  }
}

img {
  max-width: 100%;
  display: block;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font-family: inherit;
  cursor: pointer;
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-heading);
  font-weight: 600;
  margin: 0;
}

/* ==========================================
   共用元件：按鈕
   ========================================== */
.btn {
  display: inline-flex;
  align-items: center;
  /* 🔴 `justify-content` 不可省（客戶 2026-08-19：「變成滿版寬度的按鈕我覺得很 ok，
     但文字和箭頭要左右置中。」）。
     本來就沒設過，算出值是 `normal` ＝ 靠左——**但一直看不出來**，
     因為 `inline-flex` 的按鈕寬度貼合內容，左右本來就沒有多餘空間。
     P3（2026-08-18）把首頁四個區塊的 CTA 改成滿版之後才現形：
     實測 375px 的「所有課程」內容左緣距 29px、右緣距 194px。

     🔴 **判準：flex 容器沒設 `justify-content` 時的「看起來正常」，
        可能只是因為容器寬度剛好貼合內容。**一旦寬度被撐開
        （被 flex 拉伸、`flex: 1`、grid 欄位）就會現形。
        同一句話也適用於 `align-items`——只是那一條這裡本來就有設。
     ⚠️ 改的是共用元件，2026-08-19 掃過全站：**只有那 4 顆是被撐開且偏一邊的**，
        其餘按鈕（含課程卡的「我要報名」）左右間距本來就相等，補這行不會動到它們。 */
  justify-content: center;
  gap: 6px;
  font-size: 0.9375rem;
  font-weight: 500;
  padding: 15px 28px;
  min-height: 44px;
  border: 1px solid transparent;
  transition: background-color 0.2s ease-out, color 0.2s ease-out, border-color 0.2s ease-out, transform 0.2s ease-out;
  white-space: nowrap;
}

.btn svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

/* 全站按鈕圓角固定 4px */
.btn,
.btn-pill,
.btn-block {
  border-radius: var(--radius-sm);
}

/* K2（2026-08-17）：全站原本沒有任何 .btn 的 disabled 樣式（只有 .pagination-btn 有），
   加了 disabled 屬性的結果是「**外觀完全沒變、只是按不動**」——那比沒鎖更糟，
   使用者只會覺得按鈕壞了。
   🔴 這裡用 `disabled` 是對的，**不要套用 J1 那條「唯讀改用 readonly」**：
      J1 講的是 input（會員要能選取／複製自己的帳號或會員編號，所以不能 disabled）。
      按鈕的語意本來就是「不可用」，而且我們**不希望**它能被點擊或鍵盤觸發。
   🔴 `background-image: none` 不可省：全站 hover 是金色漸層（七之二節），
      不清掉的話滑過去還是會變金色，看起來就像可以按。
   ⚠️ 這條在 .btn-primary／.btn-outline／.btn-light **之前**宣告也贏得了——
      它的特異性是 (0,2,0)，那三條是 (0,1,0)。 */
.btn:disabled,
.btn:disabled:hover,
.btn:disabled:focus-visible {
  background-color: var(--gray-100);
  background-image: none;
  border-color: var(--color-border);
  color: var(--color-muted);
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

/* 🔴 hover 一律用「金色漸層 ＋ 深字」，不是把紫色再加深。
   客戶反映原本的 hover 長輩看不出來。實測原因：
     深紫 #451470 → 更深紫 #2E0D4B 的對比只有 1.24 倍（1 ＝ 完全沒變化）
     金色漸層 vs 紫底的變化幅度是 6.27 倍
   ⚠️ 上面兩個數字是**舊主色時代**量的（2026-08-12）。Q4（2026-08-24）主色改亮成
      #6B2FA8 之後：主色 → hover 深紫 #471F70 是 1.53，金色 #E0BD70 vs 主色是 **4.50**。
      幅度縮小了，但金色仍遠勝「紫再加深」——結論不變，數字要用新的。
   金底配 --color-on-gold 的對比是 4.00〜5.86（依漸層位置），見 :root 的說明。
   ⚠️ 金底上絕對不要用白字：白字在金底上只有 2.x:1，會讀不清楚。
   ⚠️ 這也同時滿足客戶「想多一點金色點綴、紫色太多看起來膩」的要求——
      平常是紫色（品牌色），互動時才出現金色，金色不會過量。 */
.btn-primary {
  background-color: var(--color-primary);
  color: #FFFFFF;
}

.btn-primary:hover,
.btn-primary:focus-visible {
  background-image: linear-gradient(135deg, var(--gold-from), var(--gold-to));
  color: var(--color-on-gold);
}

.btn-primary:active {
  transform: translateY(1px);
}

.btn-outline {
  background-color: transparent;
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.btn-outline:hover,
.btn-outline:focus-visible {
  background-image: linear-gradient(135deg, var(--gold-from), var(--gold-to));
  border-color: var(--gold-from);
  color: var(--color-on-gold);
}

/* 金色按鈕（會員登入）本身已是金色，hover 不能再變金色，改用提亮 */
.btn-light {
  background-color: var(--purple-400);
  color: #FFFFFF;
}

.btn-light:hover,
.btn-light:focus-visible {
  background-image: linear-gradient(135deg, var(--gold-from), var(--gold-to));
  color: var(--color-on-gold);
}

/* 金色漸層按鈕（會員登入專用，客戶指定）
   ⚠️ 文字與圖示一定要用 --color-on-gold，不要寫死色值也不要用白色：
      金色底很亮，白字在上面只有 2.11:1，完全讀不清楚。
   ⚠️ 圖示是 currentColor，所以只要 color 設對，箭頭會自動跟著變色。 */
.btn-gold {
  background-image: linear-gradient(135deg, var(--gold-from), var(--gold-to));
  border-color: transparent;
  color: var(--color-on-gold);
}

.btn-gold:hover {
  /* 漸層沒辦法用 background-color 做 hover，改用 filter 提亮 */
  filter: brightness(1.06);
}

/* ==========================================
   圖示載入失敗時的文字退路
   ------------------------------------------
   🔴 為什麼需要這個：全站圖示改用 lucide（unpkg CDN）之後，
      CDN 掛掉或被內網擋住時 <i data-lucide> 不會變成 <svg>，
      而「只有圖示的按鈕」就會變成一個空方塊——
      漢堡選單、輪播箭頭、關閉鈕、回頂端、搜尋鈕全都認不出來。
      旁邊有文字的裝飾性圖示沒這個問題（少一個圖示不影響理解），
      所以只有 icon-only 的控制項需要退路。

   做法：用 :has() 判斷按鈕裡有沒有真的 <svg>。
     lucide 有跑 → <i> 被換成 <svg> → 退路文字隱藏
     lucide 沒跑 → 按鈕裡沒有 svg    → 退路文字顯示
   ⚠️ :has() 各家瀏覽器 2023 起都支援；真的不支援的舊瀏覽器會「一直顯示文字」，
      那是安全的失敗方向（看得懂但不好看），不是壞掉。
   ========================================== */
.icon-fallback {
  display: none;
  /* 退路多半是單一字元（‹ › ↑ ⋯ ⌕ ✕），要夠大才看得清；
     文字型退路（例如漢堡鍵的「選單」）會被下面那條調回小字 */
  font-size: 1.25rem;
  line-height: 1;
  white-space: nowrap;
}

/* 兩個字以上的文字退路不需要那麼大 */
.menu-toggle>.icon-fallback {
  font-size: 0.8125rem;
}

:is(button, a):not(:has(svg))>.icon-fallback {
  display: inline;
}

/* ==========================================
   Header Top（工具列）
   ------------------------------------------
   放全站工具型連結：回首頁／網站導覽／相關連結／下載專區／文字大小。
   與主導覽（內容分區）分開，主導覽才不會被工具性項目稀釋。
   ⚠️ 客戶把「相關連結」「下載專區」也移到這裡，理由是字級調大後主導覽會太擠。
   ⚠️ 文字大小切換原本在右下角浮動鈕，移來這裡是為了發現性——
      長輩不必先找到右下角那顆「字」才知道可以放大。
   ========================================== */
.header-top {
  background-color: var(--color-bg-alt);
  border-bottom: 1px solid var(--color-border);
}

.header-top-inner {
  max-width: var(--header-container-max);
  margin: 0 auto;
  /* 客戶反映這一列太高、字太大。padding 由 6px→4px、gap 由 18px→8px，
     搭配下面的字級縮小，整列高度從 52px 降到約 40px。
     ⚠️ 改完一定要回頭校正 :root 的 --header-top-height，
        那個值是 sticky 定位偏移用的，對不上會蓋住錨點目標。 */
  padding: 4px var(--page-gutter);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 2px 8px;
}

.header-top-link {
  display: inline-flex;
  align-items: center;
  /* 客戶指定「更小，例如 14px」。0.8125rem 在預設 18px 根字級下 ＝ 14.6px，
     而且落在本檔既有的字級級距上，不另開 0.7778rem 這種只為湊 14px 的碎值。
     ⚠️ 仍然用 rem：這一列也要跟著文字放大切換一起長大。
     padding 留著是為了點擊範圍——雖然已經沒有底色，看不出來，但手指與滑鼠需要。 */
  padding: 4px 6px;
  border-radius: var(--radius-sm);
  font-size: 0.8125rem;
  color: var(--color-text);
  transition: color 0.2s ease-out, text-decoration-color 0.2s ease-out;
}

/* 分隔線「｜」：客戶指定用直線分隔，不用圖示。
   ⚠️ 是獨立元素而不是連結的 ::before——分隔線要能獨立控制顏色，
      包在連結裡會跟著連結的 hover 一起變色。
   顏色用 --color-muted 而不是 --color-border：
   實測 --color-border（#E6E1EA）在這一列的底色（#ECECEC）上只有 1.09:1，
   等於看不見（客戶回報「幾乎看不到」）。--color-muted 是 5.09:1。
   全形「｜」本身左右就帶空白，所以這裡不再加 margin。 */
.header-top-sep {
  color: var(--color-muted);
  font-size: 0.8125rem;
  line-height: 1;
  user-select: none;
}

/* 🔴 這一列的 hover 是全站「金色漸層」標準的刻意例外（客戶 2026-08-12 指定）：
   「文字樣式懸停即可，不需要像按鈕一樣背景整塊金色漸層」。
   理由站得住腳——整塊金色會讓工具列看起來比主導覽還重要。

   ⚠️ 但不能只換文字顏色：實測 --color-text（#1A1A1A）→ --color-primary
      的亮度變化只有 1.32 倍（舊主色 #451470；Q4 改成 #6B2FA8 後是 2.15，
      仍遠低於金色標準的水準），這正是當初把 hover 改成金色要解決的問題
      （客戶原話：長輩看不出來）。
   → 所以同時加底線。底線是「形狀」變化而不是對比變化，
     不依賴使用者分辨得出兩個深色的差別，弱視與色弱都看得到。
   ⚠️ 不要用 border-bottom 代替 text-decoration：
      這一列的連結是 inline-flex，border 會貼在整個 padding 框的底部、
      離文字很遠，看起來像一條沒關聯的線。 */
/* 🔴 底線 2px → 1px（客戶 2026-08-12 第三批第 8 項，截圖指的就是這一列）。
   與 .nav-link::after 同步細化，全站底線只有一種粗細。
   ⚠️ text-underline-offset 維持 3px：線變細之後若又貼近文字，
      中文字的下緣筆畫會和底線黏成一團，反而更難辨識。 */
.header-top-link:hover,
.header-top-link:focus-visible {
  color: var(--color-primary);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.header-fontsize {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding-left: 2px;
}

.header-fontsize-label {
  font-size: 0.8125rem;
  color: var(--color-muted);
}

/* 🔴 這三顆的尺寸刻意用 px，是本專案「字級一律 rem」的唯一例外。
   理由：它們是「大小示意」（小／中／大），按下「大」之後如果三顆一起放大，
   就看不出彼此的相對大小了——放大功能本身不該被自己放大。
   高度也一起寫死，否則字級固定、框卻在變，三顆會忽大忽小。 */
.header-fs-btn {
  min-width: 30px;
  height: 26px;
  padding: 0 6px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  color: var(--color-text);
  line-height: 1;
  transition: background-color 0.2s ease-out, color 0.2s ease-out, border-color 0.2s ease-out;
}

.header-fs-btn[data-scale="default"] {
  font-size: 12px;
}

.header-fs-btn[data-scale="md"] {
  font-size: 14px;
}

.header-fs-btn[data-scale="lg"] {
  font-size: 17px;
}

.header-fs-btn:hover,
.header-fs-btn:focus-visible {
  background-image: linear-gradient(135deg, var(--gold-from), var(--gold-to));
  border-color: var(--gold-from);
  color: var(--color-on-gold);
}

.header-fs-btn.is-active {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
  color: #FFFFFF;
}

/* 🔴 目前選中的那一顆，hover 時也要變成金色。

   為什麼需要這一條：`.header-fs-btn:hover` 與 `.header-fs-btn.is-active`
   的 specificity 都是 (0,2,0)，同分時比先後順序——`.is-active` 寫在後面，
   所以它的 background-color（紫）與 color（白）會蓋回 hover 的設定。
   ⚠️ 但 hover 設的是 background-**image**，而 is-active 設的是 background-**color**，
      兩者不是同一個屬性、不會互相覆蓋。結果是「金色漸層疊在紫底上、
      文字卻還是白色、邊框變成金色」——客戶截圖看到的那個怪狀態。
      這種「一半生效」比完全沒生效更難看出原因。
   → 這一條把三個屬性一次講齊，狀態就一致了。
   ⚠️ background-color 要設成 transparent：不設的話紫底仍在漸層下層。 */
.header-fs-btn.is-active:hover,
.header-fs-btn.is-active:focus-visible {
  background-color: transparent;
  background-image: linear-gradient(135deg, var(--gold-from), var(--gold-to));
  border-color: var(--gold-from);
  color: var(--color-on-gold);
}

@media (max-width: 767px) {

  /* 手機把工具列收成單行可捲動，不要換行佔掉兩三排高度 */
  .header-top-inner {
    justify-content: flex-start;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    gap: 8px;
  }

  .header-top-inner::-webkit-scrollbar {
    display: none;
  }

  .header-top-link,
  .header-top-sep,
  .header-fontsize {
    flex-shrink: 0;
  }
}

/* ==========================================
   後台管理列（admin bar）
   ------------------------------------------
   後台登入中瀏覽前台時，頂端固定一條黑色列（WordPress admin bar 的做法）。
   HTML 只有管理員拿得到：layout.php 在沒有 $adminBar 時根本不輸出這段。

   🔴 顏色刻意不吃 --color-* tokens：這條列不屬於網站配色，是工具列。
      跟著網站主題變色會讓人一時分不清它是網站的一部分還是工具——
      WordPress 也是固定深色，換佈景不會變。

   🔴 fixed 而不是 sticky：管理員捲到哪都要看得到「現在是登入狀態／網站已關閉」。
      因此 body 要補一段 padding-top，否則頁首第一列會被它蓋住。
   ========================================== */
.admin-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* 999 是手機選單、1200 是操作導覽、2000 是燈箱：管理列要壓過手機選單，
     但不該擋住導覽與燈箱那種「使用者正在專心看」的全螢幕內容。 */
  z-index: 1000;
  height: var(--admin-bar-height);
  padding: 0 var(--page-gutter);
  background-color: #1d2327;
  color: #e6e8ea;
  font-size: 0.8125rem;
  line-height: 1.4;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.admin-bar__group {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

/* 「回到後台」「登出」：看起來一樣的兩顆，一個是連結一個是按鈕，所以按鈕要洗掉預設樣式 */
.admin-bar__item {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 0;
  border-radius: 999px;
  background-color: rgba(255, 255, 255, 0.1);
  color: #e6e8ea;
  font: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.admin-bar__item:hover,
.admin-bar__item:focus-visible {
  background-color: var(--gold-from);
  color: var(--color-on-gold);
}

.admin-bar__item--logout:disabled {
  opacity: 0.6;
  cursor: default;
}

.admin-bar__ico {
  display: inline-flex;
  flex: none;
}

/* 圖示的 svg 是 24px 的原尺寸，這條列只有 40px 高，要縮到與文字相稱 */
.admin-bar__ico svg {
  width: 16px;
  height: 16px;
}

/* 網站開放狀態。🔴 這顆是整條列存在的主要理由之一：關站時管理員看得到完整前台，
   很容易忘記訪客看到的是維護頁，所以關閉狀態用警示色、字也寫明「僅管理員可見」。 */
.admin-bar__status {
  flex: 0 1 auto;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.admin-bar__dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: currentColor;
}

.admin-bar__status--live {
  color: #6fd08c;
}

.admin-bar__status--closed {
  color: #ffc773;
  font-weight: 700;
}

.admin-bar__user {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  color: #b9c0c6;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 手機：欄寬不夠時先讓掉使用者名稱與「開放中」，
   保留「回到後台」「登出」與關站警示——那幾項才是需要當下行動的資訊。 */
@media (max-width: 639px) {
  .admin-bar {
    font-size: 0.75rem;
    gap: 8px;
  }

  .admin-bar__group {
    gap: 8px;
  }

  .admin-bar__user,
  .admin-bar__status--live {
    display: none;
  }

  /* 🔴 不要讓這條列裡的文字換行：管理列是固定高度（body 的 padding-top 吃同一個變數），
     換行會被切掉一半，而且 body 不會跟著讓位。窄螢幕就單行省略。 */
}

/* ==========================================
   Header
   ========================================== */
.site-header {
  position: sticky;
  /* 🔴 不是 0：有後台管理列時要黏在它底下，否則捲動時頁首會滑到管理列後面被切掉一截。
     沒有管理列時 --admin-bar-height 是 0px，行為與原本完全相同。 */
  top: var(--admin-bar-height);
  z-index: 50;
  background-color: var(--color-bg);
}

.header-inner {
  max-width: var(--header-container-max);
  margin: 0 auto;
  height: var(--header-height);
  padding: 0 var(--page-gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  position: relative;
  z-index: 2;
}

/* 頁首內部物件（Logo／導覽／右側按鈕）的進場動畫改由 partials/header.php 的 data-aos 負責（2026-09-18）。
   🔴 原本這裡的 header-item-fade-in 關鍵影格已移除，不要加回來：它用 fill-mode: both，
      跑完後終值停在動畫層級、蓋過一般規則，AOS 的 fade-down 被吃掉（使用者看到的就是「頁首沒有動畫」），
      主導覽也會永遠帶著 transform，下拉選單被困在它的堆疊環境裡。 */

/* 🔴 原本這裡的 main-fade-in（<main> 底下每個大區塊整塊淡入＋上移 12px）已移除（2026-09-19）。
   全站每個區塊都掛了 data-aos 之後，同一塊內容會「整塊淡入一次、裡面的小區塊又各自淡入一次」，
   看起來拖泥帶水（使用者：「AOS 動畫蠻鳥的不好看」）。進場動畫統一交給 AOS，不要加回來。
   ⚠️ 它另外還有一個副作用（fill-mode both 讓 transform 永遠不是 none，.join 的 background-attachment: fixed
      視差因此失效，當時是用 :not(.join) 繞開的），拿掉之後這個限制也一起消失。 */

/* ==========================================
   共用元件：品牌標誌（切版版本）— T9，2026-08-28 客戶
   ------------------------------------------
   header 與 footer **共用同一段 HTML 結構**（partials/header.html／footer.html），
   只差一個 .brand--on-dark 修飾 class 換文字顏色 → **名稱只有一份來源**。

   為什麼是切版而不是放圖：客戶要新的排版（icon ＋ 中文全名 ＋ 金色分隔線 ＋ 英文全名）
   而我們沒有那張圖的高清原檔；切版之後裝置縮小時文字可以依斷點自由調整，
   對 SEO 與無障礙也是改善（真文字取代 img alt）。

   🔴 **文字一律用 px，不可以用 rem。**
      「裝置縮小時可以調整文字大小」（依斷點）與「全站三段字級切換」（html.fs-md／fs-lg）
      是兩件不同的事，切版之後很容易被混為一談。
      舊的圖片版 logo 高度只由媒體查詢決定，**完全不隨使用者的字級切換而變**；
      用 rem 就會開始跟著變，而導覽與右側 CTA 本來就會跟著字級放大——
      等於在「最窄的桌機斷點 × 最大字級」那一格再加一份壓力，那正是 S20 那個 bug 出現的地方。
      ✅ 用 px 完全等同於圖片版的行為，不是退步；字級切換的存在理由是「長輩讀內文」，
         logo 是品牌標誌不是內文。
      **驗收方式：三種字級下 .brand 的 getBoundingClientRect().height 必須完全相同。**

   🔴 **brand 總高不可超過 --header-height（桌機 72px／手機 60px）**：
      .header-inner 是固定高度，超過不會把那一列撐高，**只會上下溢出到公告列與 header top
      上——看起來像「沒對齊」而不是「太大了」**。目標是完全不動 --header-height
      （動了要連帶重算 --header-stack，6 處消費端）。

   ⚠️ 中文字重用 700：--font-heading 目前只載 Noto Sans TC 600／700
      （partials/head-common.html 的 <link>）。🔴 要更粗必須先把字重加進那個 wght@，
      否則是瀏覽器合成的**假粗體**。
   ========================================== */
.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}

.brand-icon {
  height: 40px;
  width: auto;
  flex-shrink: 0;
}

.brand-text {
  display: block;
  min-width: 0;
}

/* 🔴 px 不是 rem，理由見本節開頭 */
.brand-zh {
  display: block;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 17px;
  line-height: 1.24;
  letter-spacing: 0.01em;
  color: var(--color-heading);
  white-space: nowrap;
}

/* 🔴 **`<1024` 兩行、`≥1024` 一行**，靠切 block／inline 達成。
   不用 `<br>`（桌機關不掉），也不靠自動斷行（會斷在「代理／人公會」這種地方）。
   斷行位置固定是「社團法人新北市」＋「記帳及報稅代理人公會」（7＋11 字，斷在語意處）。

   🔴 **這一條改過兩次，兩次的歷史都要留著，否則下一個人會再改成兩行一次：**

   | 輪次 | 做法 | 為什麼 |
   |---|---|---|
   | **AB1**（2026-09-04 上午） | 兩行的範圍由 `<640` 擴大到 **`<1024`** | 768 的 footer 第一欄只有 280px，一行中文要 292px，`nowrap` 放不下也不會折 → **壓到「聯絡我們」13.1px** |
   | **AB3**（2026-09-04 下午，**現行**） | **還原回 `≥640` 一行** | footer 的兩欄斷點改成 1024，`<1024` 變單欄 ⇒ **第一欄變成整個寬度，擠壓的前提消失** |

   🔴 **AB3 不是把 AB1 當成 bug 修掉，是換一個更好的手段**：
      AB1 治的是症狀（讓中文變窄），AB3 治的是原因（那一欄本來就太窄），
      而且**AB1 解不掉英文**——英文一行要 305px，比中文還寬，
      AB1 之後它仍然壓著「聯絡我們」26.2px（見總待辦 AB 軌）。**改單欄一次解掉兩者。**
   ⚠️ **`nowrap` 是刻意的、不要拿掉**（拿掉會斷在「代理／人公會」這種地方）——
      要解的是「那一欄有多寬」，不是「放不下時讓它自己斷」。
   ⚠️ **也不要改成加寬 footer 第一欄**：那會壓縮右邊的聯絡資訊。 */
.brand-zh > span {
  display: block;
}

/* 金色分隔線：🔴 用既有的 --gold-from／--gold-to，全站金色只有這一組來源，另寫就是分叉。
   預設 `<640` 隱藏；**AB4／AB5 之後 footer 在整個 `<640` 都顯示**（見下方那幾段 media）。
   🔴 顯示範圍必須與 `.brand-en` **完全一致**：只留分隔線會變成一條沒有下文的金線。 */
.brand-rule {
  display: none;
  height: 1px;
  margin: 5px 0;
  background-image: linear-gradient(90deg, var(--gold-from), var(--gold-to));
}

/* 英文全名。**預設 `<640` 隱藏**；🔴 **AB4／AB5 之後 footer 在整個 `<640` 都顯示
   （`≤374` 允許折行）、header 維持隱藏**——兩處規則不同是刻意的，
   理由見本檔下方那幾段 media。

   ── 這一條的來歷（不要只看現值，會誤判成有人違反客戶指定）──
   **客戶原本指定「手機只留 icon ＋ 中文兩行」**，所以 `<640` 兩者都隱藏。
   **2026-09-04（AB4）改為：footer 補回英文與分隔線（8.5px）、header 因可用寬不足維持隱藏。**
   **2026-09-07（AB5）再補：`≤374` 也顯示（英文折行），且 `≥375` 的中文改排一行。**
   理由是 PM 認為手機版 logo 應與桌機一致，而 footer 的寬度**實測是夠的**。
   🔴 U5（PM 2026-08-31）：由 `--logo-4` 綠（白底 7.26）改成一般本文色
      `--color-text` #1A1A1A（白底 **17.4:1**）。
   🔴 **只改這一份（淺色底）。深色底那份在下面，維持白字，不要一起改成黑字**
      ——頁尾是深紫底，黑字在那裡讀不到。

   ── 🔴 AB2（2026-09-04）試過「手機補回英文」，**被實測擋下、已還原** ──
   **不要照著規格再做一次，數字是錯的。** 交辦的可用寬 375→271px／425→321px
   是「視窗寬 − 左右 gutter 48 − icon − gap 10」算出來的，
   🔴 **漏算了右側漢堡鈕（`.header-actions` 40px）與 `.header-inner` 的 flex gap（24px）**，
   實際少 76px。實測給文字的可用寬只有 **375 → 195px、425 → 245px**，
   對應一行放得下的最大字級是 **6px 與 8px**
   ——而 AB2 自己的理由正是「7px 是一條看得到卻讀不出寫什麼的灰線，比沒有更糟」。
   ⚠️ 照規格的 8.5px／10px 實作之後實測：**375 橫向溢出 16px、425 溢出 12px**，
      而且 `.brand-text` 高 63.8／65.7px **撐破了 `<640` 固定 60px 的 header 列**
      （`.header-inner` 是固定高度，超過只會上下溢出到公告列，看起來像沒對齊）。
   🔴 **要在 header 做到只有兩條路，都要 PM 先定案**：允許英文折行（header 會變高 ⇒ 要重算
      `--header-height` 與七之三節那個四角關係），或接受更小的字級（違反可見文字下限）。
   ✅ **AB4（2026-09-04）改成只在 footer 做，已完成**——footer 沒有漢堡鈕、可用寬多 133px。
      **header 仍然是上面那兩條路，沒有第三條。** */
.brand-en {
  display: none;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 10px;
  line-height: 1.25;
  letter-spacing: 0.03em;
  color: var(--color-text);
  white-space: nowrap;
}

/* 深色底（頁尾）只換文字顏色，結構與尺寸一律沿用上面那份。
   白字對 --color-primary #6B2FA8 實測 8.09 ✓ */
.brand--on-dark .brand-zh,
.brand--on-dark .brand-en {
  color: #FFFFFF;
}

/* 🔴 AB4（PM 2026-09-04）：**手機版只有 footer 補回英文與分隔線，header 維持隱藏。**

   ── 為什麼兩處規則不同（🔴 這不是漏做，不要「順手」幫 header 補上去）──
   兩者的可用寬差 **約 133px**，因為 **header 那一列還有漢堡鈕 40px ＋ `.header-inner` 的 flex gap 24px**，
   而 footer 沒有。實測 `.brand-text` 的可用寬：

   | 視窗 | **footer 給文字的可用寬** | （對照）**header** |
   |---|---|---|
   | 425 | **333px** | 171.7px |
   | 375 | **283px** | 171.7px |
   | **320** | 🔴 **228px** | 171.7px |

   🔴 **320 那一格交辦時寫成 270.9px，是錯的——那是「格子寬 272」，沒扣掉 icon 34 ＋ gap 10。**
      算式：`footer-inner 320 − 左右內距 48 = 格子 272`，再 `− icon 34 − gap 10 = 228`。
      **實測就是 228**（祖先鏈逐層量出來的，不是公式推的）。

   🔴 **在 header 補上去會直接橫向溢出**——AB2 就是這樣被實測擋下的（375 溢出 16px、425 溢出 12px），
      詳見上方 `.brand-en` 註解裡 AB2 那段。

   ── 為什麼是 8.5px、而且不分斷點 ──
   英文各字級實測需要的寬：**10px → 305 ／ 9px → 274 ／ 8.5px → 259**。

   | 視窗 | 10px | 9px | **8.5px（採用）** |
   |---|---|---|---|
   | 425 | 餘 28 | 餘 59 | 餘 **74** |
   | 375 | 🔴 放不下 | 餘 9 | 餘 **24** |
   | **320** | 🔴 放不下 | 🔴 放不下 | 🔴 **短少 31**（實測戳出 31px、頁面溢出 7px） |

   🔴 **AB4（09-04）當時的結論是「`≤374` 隱藏」**，因為 320 一行放不下、
      而降到 7px 違反「可見文字要有下限」。
      ✅ **AB5（09-07）改用第三條路：讓英文折行**，320 因此也能顯示、且維持 8.5px
      ——見下方 `@media (max-width: 374px)` 那段。**這一段的歷史要留著，
      否則下一個人會以為 320 顯示英文是誤加。**
   ⚠️ **不要分三段字級**：8.5px 在 375 與 425 都成立，**規則只有一條、不會錯**。
   ⚠️ **不要改用 10px**：375 直接放不下。
   ✅ `.brand-en` 用 **px 不是 rem**，所以三段字級切換不會改變它的寬度——這一項不必驗字級維度。
   🔴 但**寬度維度要驗**：實測 `<640` 全開 8.5px 時，320 就戳出 31px、頁面溢出 7px
      ——**字級與斷點選錯真的會出事，不能只驗 375**。

   ⚠️ 分隔線必須跟著一起顯示，否則會變成一條沒有下文的金線（見 `.brand-rule`）。
   ⚠️ **AB5（09-07）之後 `≤374` 也顯示了**（英文改成可折行），見下一段。 */
@media (max-width: 639px) {
  .site-footer .brand-en,
  .site-footer .brand-rule {
    display: block;
  }

  .site-footer .brand-en {
    font-size: 8.5px;
  }
}

/* 🔴 AB5（PM 2026-09-07）：**320 也要有金線與英文**（原本 `≤374` 是隱藏的）。
   一行放不下就**讓英文折行**，不要縮字級——
   320 給文字的可用寬只有 **228px**，而英文 8.5px 需要 **259px**（短 31px）；
   要一行就得降到 **7px**，而本專案已寫明「7px 不是小字，是一條看得到卻讀不出寫什麼的灰線」。
   ✅ **折成兩行可以維持 8.5px 的可讀性**，實測戳出 0、頁面溢出 0。
   ✅ **footer 沒有固定高度也不 sticky**，多一行不會有 header 那種撐破列高的問題
      （header 至今仍維持隱藏，理由見上方那段）。
   ⚠️ 只在 `≤374` 放寬 `white-space`：`375` 以上一行放得下，維持 `nowrap` 比較整齊。 */
@media (max-width: 374px) {
  .site-footer .brand-en {
    white-space: normal;
  }
}

/* 🔴 AB5（PM 2026-09-07）：**footer 的中文在 `≥375` 排成一行**
   （PM：「425／375 的中文不要換行，右邊還有足夠的空間」）。
   ⚠️ 這是**只給 footer 的覆寫**——header 的可用寬只有 171.7px，一行中文要 292px，**差得遠**，
      所以 header 在 `<640` 仍然是兩行（基礎規則）。**兩處不同是刻意的。**
   ⚠️ `320` 不套用：可用寬 228px < 292px，維持兩行（PM 明示「320 中文可以換成兩行」）。 */
@media (min-width: 375px) and (max-width: 639px) {
  .site-footer .brand-zh > span {
    display: inline;
  }
}

/* 🔴 AB5：`375–424` 的中文要降到 **16px** 才排得下一行。
   實測：一行中文在 17px 需要 **292px**，而 375 的可用寬是 **283px**——**17px 會戳出 9px**。
   16px 需要 275px，**戳出 0**。
   ⚠️ `425` 起可用寬 333px，17px 綽綽有餘，**不要把 16px 一路套到 639**
      ——那會在空間充足的地方無故縮小字。 */
@media (min-width: 375px) and (max-width: 424px) {
  .site-footer .brand-zh {
    font-size: 16px;
  }
}

@media (min-width: 640px) {
  .brand-icon {
    height: 46px;
  }

  .brand-zh {
    font-size: 17px;
  }

  .brand-zh > span {
    display: inline;
  }

  .brand-rule,
  .brand-en {
    display: block;
  }
}

@media (min-width: 1024px) {
  .brand-icon {
    height: 50px;
  }

  .brand-zh {
    font-size: 18px;
  }

}

@media (min-width: 1280px) {
  .brand-icon {
    height: 54px;
  }

  .brand-zh {
    font-size: 20px;
  }

  .brand-en {
    font-size: 11px;
  }
}

.main-nav {
  display: none;
}

.main-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  gap: 32px;
}

.nav-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 1rem;
  font-weight: 500;
  color: var(--color-text);
  padding: 8px 0;
  transition: color 0.2s ease-out;
}

.nav-link:hover,
.nav-link:focus-visible {
  color: var(--color-primary);
}

/* 導覽底線：客戶指定改成主色紫、線條再細一點（原本是 3px 金色漸層，太搶眼）。
   ⚠️ 這是全站「hover 用金色」標準的刻意例外——見七之二節的說明：
      金色是留給「可按的區塊」（按鈕、卡片整塊）的 hover 訊號；
      導覽這裡同時要表達「當前所在頁」（.is-active，非 hover 狀態也會亮），
      用金色會和按鈕的 hover 語意打架，所以改用品牌主色。
   ⚠️ 底線細化後不能只靠底線傳達 hover——文字同時轉主色紫（上面那條規則），
      兩個訊號一起變，長輩才看得出來。

   🔴 粗細 2px → 1px（客戶 2026-08-12 第三批第 8 項，附截圖指定「線條再細一點」）。
      同一組值也用在 sitemap.css 的 .sitemap-block-title a::after 與
      about.css 的區塊標題底線——**底線的視覺語言全站只有一種，三處要一起改**。
      ⚠️ border-radius 跟著改成 0.5px：留 1px 會讓一條 1px 的線兩端各被削掉一半，
         看起來比實際更短更淡。
      ⚠️ 1px 在本站預設 18px 根字級下仍然看得見（2026-08-12 截圖確認過）；
         能這樣細化的前提是 hover 有兩個訊號（文字轉紫 ＋ 底線），
         若日後拿掉文字變色，就不能只留 1px 底線當唯一訊號。 */
.nav-link::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  border-radius: 0.5px;
  background-color: var(--color-primary);
  opacity: 0;
  transition: opacity 0.2s ease-out;
}

.nav-link:hover::after,
.nav-link:focus-visible::after,
.nav-link.is-active::after {
  opacity: 1;
}

.nav-link svg {
  width: 14px;
  height: 14px;
  transition: transform 0.2s ease-out;
}

.nav-item {
  position: relative;
  isolation: isolate;
  z-index: 999;
}

.nav-item:hover .nav-link svg {
  transform: rotate(180deg);
}

.nav-dropdown {
  position: absolute;
  top: 100%;
  left: 50%;
  z-index: 999;
  transform: translateX(-50%) translateY(4px);
  min-width: 176px;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: 8px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.08s ease-out, transform 0.08s ease-out, visibility 0.08s;
}

.nav-item:hover .nav-dropdown,
.nav-item:focus-within .nav-dropdown {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
}

.nav-dropdown a {
  display: block;
  font-size: 1rem;
  color: var(--color-text);
  padding: 9px 12px;
  border-radius: var(--radius-sm);
}

.nav-dropdown a:hover {
  background-color: rgba(163, 118, 199, 0.12);
  color: var(--color-primary);
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}

/* 🔴 只縮 header 裡的按鈕，不要改全站 .btn。
   客戶要的是「降低 header 高度」，.btn 的 padding: 15px 28px / min-height: 44px
   是全站按鈕的共同尺寸，動它會連帶改掉每一頁的表單與 CTA。
   ⚠️ 仍然全部用 rem：header 也要跟著文字放大切換一起長。 */
.header-actions .btn {
  padding: 0.5rem 1rem;
  min-height: 2.25rem;
  font-size: 0.875rem;
  gap: 0.375rem;
}

.header-actions .btn svg {
  width: 0.875rem;
  height: 0.875rem;
}

.header-actions .btn,
.header-actions .social-icon {
  display: none;
}

.social-icon {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background-color: var(--color-primary);
  color: #FFFFFF;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background-color 0.2s ease-out;
}

.social-icon:hover {
  background-color: var(--color-primary-hover);
}

.social-icon svg {
  width: 24px;
  height: 24px;
}

.menu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: none;
  border: none;
  color: var(--color-text);
  flex-shrink: 0;
}

.menu-toggle svg {
  width: 24px;
  height: 24px;
}

/* 手機版導覽選單 */
.mobile-nav {
  display: none;
  flex-direction: column;
  background-color: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}

.mobile-nav.is-open {
  display: flex;
}

.mobile-nav-list {
  list-style: none;
  margin: 0;
  padding: 8px var(--page-gutter) 16px;
}

.mobile-nav-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: none;
  border: none;
  padding: 14px 0;
  font-size: 1rem;
  font-weight: 500;
  color: var(--color-text);
  border-bottom: 1px solid var(--color-border);
}

.mobile-nav-toggle svg {
  width: 16px;
  height: 16px;
  transition: transform 0.2s ease-out;
}

.mobile-nav-item.is-open .mobile-nav-toggle svg {
  transform: rotate(180deg);
}

.mobile-nav-panel {
  display: none;
  flex-direction: column;
  padding: 4px 0 8px 12px;
}

.mobile-nav-item.is-open .mobile-nav-panel {
  display: flex;
}

.mobile-nav-panel a {
  padding: 10px 0;
  font-size: 0.90625rem;
  color: var(--color-muted);
}

.mobile-nav-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* W3（2026-09-01 客戶）：移除 border-top。
     🔴 padding 保留——那是按鈕與清單的距離，不是框線的附屬品。
     ⚠️「活動花絮」下方那條線來自 .mobile-nav-toggle 自己的 border-bottom（每個選單項都有），
     與這裡無關，不要一起刪。 */
  padding: 16px var(--page-gutter) 20px;
}

.mobile-nav-actions .btn {
  width: 100%;
  justify-content: center;
}

.mobile-nav-socials {
  display: flex;
  gap: 12px;
  padding: 0 var(--page-gutter) 20px;
}

/* 桌機導覽斷點的歷程（改這個數字前務必讀完）
     原本 1024 → 實測溢出：導覽 7 項時 brand 298 ＋ nav 394 ＋ actions 270 ＋ 96 = 1058 > 1024，
                 且導覽文字被壓到每行 1–2 字（「關於本會」只剩 34px 寬）
     改成 1280 → 不溢出，但 1024–1279 的平板橫向要用漢堡選單
     2026-08-12 客戶把「首頁／相關連結／下載專區」移到 header top，主導覽只剩 4 項；
                 同時預設字級調大到 18px。重測 10 個寬度 × 3 字級
                 （含 1023／1024／1279／1280 斷點兩側）全部 0 溢出，
                 所以降回 1024，平板橫向也能用桌機導覽。
     2026-08-27（S20）→ **又改回 1280**。成因是 logo 放大（29a3c95／aa2f0ea 每一階 +8px）
                 → 1024 × 大字級下四個導覽項各自折成兩行（「關於本／會」「本會會／務」…），
                 `[header]` 守門叫 `--header-height 設定 72px，但最高的內容是 82.00px`。
     2026-08-28（T9）→ logo 改成切版之後**重量一次**，1280 仍然成立：
                 fs-lg 下 main-nav 到 **1211 還是折兩行（82px）、1220 起才回到一行（49px）**，
                 門檻約 1216 → 1280 留了約 64px 餘裕。
                 （切版後 brand 在 1024–1279 是 362px、≥1280 是 400px，
                 都比圖片版的 252px 寬，所以這一格只會更擠、不會更鬆。）
   🔴 這個數字與「主導覽項目數」「預設字級」「logo 尺寸」是綁在一起的。
      任何一項變動都必須重跑那個掃描，不能只改數字——會溢出，而且不會報錯。
   🔴 **只改 header 這個斷點，不要全站把 1024 換成 1280**：`.section-header` 的 CTA 橫排、
      課程卡三欄、列表檢視、報名流程（S8）都各自停在 1024，**那是刻意的**
      （每個區塊照它自己的內容決定斷點）。
   ⚠️ 代價與 2026-08-11 那次相同、客戶已接受過一次：1024–1279 的平板橫向與小筆電看到漢堡選單。 */
@media (min-width: 1280px) {
  .main-nav {
    display: block;
  }

  .menu-toggle {
    display: none;
  }

  .mobile-nav {
    display: none !important;
  }

  .header-actions .btn,
  .header-actions .social-icon {
    display: inline-flex;
  }
}

/* 1280–1439 之間空間吃緊：縮小導覽間距，避免導覽文字換行。
   1440 以上才回到原本的寬鬆間距。
   ⚠️ S20（2026-08-27）隨主導覽斷點由 1024–1439 改成 1280–1439：
      1024–1279 現在是漢堡選單，`.main-nav` 根本不顯示，那段區間套這條沒有意義。 */
@media (min-width: 1280px) and (max-width: 1439px) {
  .main-nav ul {
    gap: 20px;
  }

  .nav-link {
    font-size: 0.9375rem;
  }

  /* 🔴 T9（2026-08-28）：這裡原本有一條 `.brand-logo-img { max-height: 54px }`
     ——「1024–1439 導覽與右側 CTA 都在，logo 要讓一點寬度出來」。
     切版之後已經沒有那張圖，而且 brand 的尺寸改由本檔「品牌標誌（切版版本）」那節
     逐階宣告（≥1280 才放到最大），這個區間本來就吃 ≥1024 那一階的較小值，
     **不需要再壓一次**。留這行註解是避免下一個人「發現少了一條」又補回去。 */
}

/* ==========================================
   公告列
   ========================================== */
.announcement-bar {
  background-color: var(--color-primary);
  color: #FFFFFF;
}

.announcement-inner {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 14px var(--page-gutter);
  display: flex;
  justify-content: center;
  font-size: 0.875rem;
}

.announcement-ticker {
  position: relative;
  overflow: hidden;
  height: 20px;
  line-height: 20px;
  width: min(640px, 84vw);
}

.announcement-list {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
  width: 100%;
  height: 100%;
}

/* 用 top 而不是 transform 做滑動效果——transform 會讓這個項目自成一個堆疊層級，
   在 position:sticky 的頁首裡，這會導致瀏覽器在跟其他定位元素（例如導覽下拉選單）比較時，
   即使下拉選單的 z-index 更高，仍可能被這個 transform 圖層蓋過去（瀏覽器合成層級的問題，不是 z-index 數字本身的問題） */
.announcement-list li {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  opacity: 0;
  transition: top 0.5s ease, opacity 0.5s ease;
}

.announcement-list li.is-active {
  opacity: 1;
  top: 0;
}

.announcement-list li.is-leaving {
  opacity: 0;
  top: -100%;
}

.announcement-list .announcement-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* P2（客戶 2026-08-18 第 1 項）：放不下時橫向捲動，不要截斷。
   結構是兩層：`.announcement-text` 是**視窗**（overflow: hidden），
   `.announcement-text-inner` 是被 transform 推著跑的**內容**。
   🔴 不要把 transform 直接下在 `.announcement-text` 上——那樣連可點範圍都會被推出去。
   ⚠️ 這裡**刻意不再寫 `text-overflow: ellipsis`**：截斷正是客戶回報的問題本身。
      放不下時由 JS 加 `.is-scrolling` 捲動；`prefers-reduced-motion` 時由下方的
      規則改成換行顯示。兩條路都不會讓文字讀不到。 */
.announcement-list .announcement-text {
  font-size: 1rem;
  flex: 0 1 auto;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  transition: opacity 0.2s ease-out;
}

.announcement-list .announcement-text:hover {
  opacity: 0.8;
}

.announcement-list .announcement-text-inner {
  display: inline-block;
  white-space: nowrap;
  will-change: transform;
}

/* 捲動距離與時間都是 JS 依實測寬度算出來寫進 inline style 的
   （`--announcement-scroll-x` 與 `animation-duration`）——它們是 runtime 才知道的值，
   屬於全域守則允許的那種 inline style。 */
.announcement-text.is-scrolling .announcement-text-inner {
  animation-name: announcement-scroll;
  animation-timing-function: linear;
  animation-iteration-count: 1;
  animation-fill-mode: forwards;
}

/* 頭尾各停一下（0 → 12%、88% → 100%），不然最後一個字剛出現就被換掉了。
   ⚠️ 這兩個百分比與 script.js 的 EDGE_PAUSE 是同一件事的兩半：
      EDGE_PAUSE 把總時間加長，這裡決定那段時間花在頭尾不動上。 */
@keyframes announcement-scroll {
  0%, 12% { transform: translateX(0); }
  88%, 100% { transform: translateX(var(--announcement-scroll-x, 0)); }
}

/* 滑鼠移上去／鍵盤 focus 進來要暫停，否則使用者永遠讀不完一句話。
   ⚠️ 停留計時（放得下的那些公告）不在 CSS 管得到的範圍，由 script.js 一起處理。 */
.announcement-ticker.is-paused .announcement-text-inner {
  animation-play-state: paused;
}

.announcement-list .announcement-link {
  font-size: 1rem;
  flex-shrink: 0;
  font-weight: 400;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 8px 12px;
  margin: -8px -12px;
  border-radius: var(--radius-sm);
  transition: transform 0.2s ease-out;
}

.announcement-list .announcement-link:hover {
  transform: translateX(4px);
}

.announcement-list .announcement-link svg {
  width: 14px;
  height: 14px;
}

@media (max-width: 639px) {
  .announcement-inner {
    font-size: 0.8125rem;
  }

  /* P2：客戶指定手機版跑馬燈文字約 14px。
     🔴 用 rem 不寫死 px——全站字級開關（小／中／大）要能把它一起放大。
     ⚠️ 選 `0.8125rem`：預設 html 是 18px，所以是 **14.625px（比 14px 大 0.625px）**。
        沒有取更接近的 `0.7778rem`（＝14.0px）的兩個理由：
        ① 全站字級都走 1/16rem 的級距（0.875／0.9375／0.8125…），0.7778 是唯一的例外值；
        ② 另一個級距 `0.75rem` ＝ 13.5px **比 14 小**，而這個站的字級方針是
           「客戶指出文字偏小、使用者以長輩為主」——往小的方向湊整不合理。
     🔴 原本這兩個選擇器各自寫死 `font-size: 1rem`，所以 `.announcement-inner`
        的 0.8125rem **從來沒有生效過**，實測手機上是 18px。改的是這兩條，不是那一條。 */
  .announcement-list .announcement-text,
  .announcement-list .announcement-link {
    font-size: 0.8125rem;
  }

  .announcement-ticker {
    width: 88vw;
  }

  .announcement-list .announcement-link span {
    display: none;
  }
}

/* 🔴 `prefers-reduced-motion: reduce`：不捲動，但**也不可以截斷**。
   「不動」不能變成「讀不到」——所以改成讓文字換行、公告列自己長高。
   ⚠️ 高度變高不影響 `--announcement-height` 那個靜態 token：
      JS 沒跑時公告列是空的（項目由 script.js 產生），而 JS 跑得動時
      `calibrateHeaderTokens()` 會依實測值修正。
   ⚠️ 項目仍會輪播（instant，無過場動畫），否則第二則之後的公告使用者永遠看不到。 */
@media (prefers-reduced-motion: reduce) {
  .announcement-ticker {
    height: auto;
    min-height: 20px;
  }

  .announcement-list {
    height: auto;
  }

  /* 🔴 li 必須從 `absolute` 改回文件流，否則換行的第二行撐不起 `.announcement-ticker`，
     而 ticker 是 `overflow: hidden`——結果就是「不捲動、但第二行被裁掉」，
     等於把原本的 bug 換一種樣子留著。
     ⚠️ 只有 `.is-active` 那一則在流裡，其餘 `display: none`，不然四則會疊成一長條。 */
  .announcement-list li {
    display: none;
    position: relative;
    top: auto;
    opacity: 1;
    transition: none;
  }

  .announcement-list li.is-active {
    display: flex;
    top: auto;
  }

  .announcement-list li.is-leaving {
    display: none;
  }

  .announcement-list .announcement-text {
    overflow: visible;
  }

  .announcement-list .announcement-text-inner {
    white-space: normal;
  }

  /* 🔴 要壓過 `.announcement-text.is-scrolling .announcement-text-inner`（0,3,0）
     就必須用**同樣的選擇器**再寫一次——(0,2,0) 的寫法完全壓不過去，
     那會變成「規則看起來把動畫關掉了，實際上沒有」。
     ⚠️ JS 偵測到 reduce 時本來就不會加 `.is-scrolling`（見 script.js），
        這條是第二道防線；兩道都要成立，因為使用者可能在頁面開著時才改系統設定。 */
  .announcement-text.is-scrolling .announcement-text-inner {
    animation: none;
    transform: none;
  }
}

/* ==========================================
   共用元件：Section 標頭（eyebrow + title + desc + CTA）
   ========================================== */
.section-header {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 20px;
  margin-bottom: 32px;
}

.section-eyebrow {
  display: block;
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-primary);
  margin-bottom: 8px;
}

.section-title {
  font-size: 1.875rem;
  font-weight: 700;
  color: var(--color-heading);
  line-height: 1.3;
  margin-bottom: 12px;
}

/* H5（客戶 2026-08-13）：720px 時右側大片留白。
   客戶未來要在後台自己決定哪裡換行，所以版面要留更多空間 → 900px（約 50 字／行）。
   ⚠️ 50 字是中文可讀性的實務上限，**不要再往上加**：
      行長超過這個範圍，眼睛從行尾回到下一行行首會開始找不到位置。
   ⚠️ 同樣是治標：真正讓客戶「自己決定換行」要靠後台編輯器支援手動換行，
      指望容器寬度剛好讓文字排成一行，在不同螢幕寬度下都會落空。 */
.section-desc {
  font-size: 1rem;
  line-height: 1.7;
  /* 🔴 X3（PM 2026-09-02）：由 `--color-muted` 改成 `--color-text`。
     PM 覺得淺灰的層級感太弱（**不是可讀性問題**——`--color-muted #636363`
     白底 5.74 本來就過 AA）。
     🔴 只有「內容型文字」改深；**狀態／未選取／分隔／次要標示那 9 類一律不動**
        （`.course-signup-deadline`／`.course-tab`／`.pagination-btn`／
        `.pagination-ellipsis`／`.course-status.is-full`／麵包屑／header top 的「｜」與
        「文字大小」／`.filter-date-sep`／`.about-figure-caption`）——
        那裡的 `--color-muted` **不是「比較淺的字」，是語意**，改深會讓主次層級消失。 */
  color: var(--color-text);
  max-width: 900px;
}

.section-header-cta {
  flex-shrink: 0;
  white-space: nowrap;
}

@media (min-width: 768px) {
  /* ⚠️ M3 只把 `.section-header` 的橫排搬到 1024，**字級留在 768**：
     客戶要的是「按鈕移到內容下方」，沒有要改標題大小。
     兩件事原本寫在同一個 media query 裡，整塊搬過去會順帶改掉字級。 */
  .section-title {
    font-size: 2.75rem;
  }
}

/* 🔴 M3（客戶 2026-08-17）：平板以下把區塊 CTA 移到內容下方。
   「標題與按鈕橫排」的斷點從 **768 → 1024**，於是 768–1023 會回到預設的
   `flex-direction: column`（標題在上、CTA 在下），正是客戶截圖那個寬度。

   ⚠️ **為什麼是 1024 不是 1280**：M3 的真正理由是**客戶說「平板要把 CTA 移到內容下方」，
      由內容決定**——1024 起版面已經寬到可以標題與 CTA 橫排。
      🔴 這裡原本寫的是「1024 是本專案桌機導覽開始顯示的斷點」，
         **S20（2026-08-27）把主導覽斷點改成 1280 之後那句已不成立**，
         但 `.section-header` 這條**刻意留在 1024 不動**（它與導覽放不放得下無關）。
         留這段是避免下一個人照舊理由把它一起改成 1280。
   🔴 **M5 用的是 1280，兩項用不同數字是刻意的**（PM 2026-08-17 明確指示
      不要訂一個統一的「桌機／平板」界線，每個區塊照它自己的內容判斷）。
      **不要為了一致把這兩個數字統一。**
   ⚠️ 改共用這一條就好，**不要逐區塊各寫一份**：客戶點名的最新課程／本會會務／
      成為專屬夥伴／相關連結全部都用這個元件，改共用才不會漏掉他沒點名的那幾個。 */
@media (min-width: 1024px) {
  .section-header {
    flex-direction: row;
    align-items: flex-end;
    justify-content: space-between;
    gap: 24px;
  }
}

/* ══════════════════════════════════════════
   🔴 P3（客戶 2026-08-18 第 2、4、5、6 項）：平板以下，區塊 CTA 移到「內容下方」
   ------------------------------------------
   客戶原話：「所有課程按鈕應該放到課程輪播下方，就是**區塊主要的內容下方**」
   ——另外三處是本會會務、成為專屬夥伴、相關連結，四項是同一件事。
   ⚠️ M3（2026-08-17）已經讓 CTA 在 <1024 從「標題右側」變成「標題下方」，
      但客戶要的是移到**整個區塊內容的最下面**，不是標題下面。

   🔴 **整段包在 `max-width: 1023px` 裡，桌機一條新規則都沒有**——
      客戶明講「桌機版不更動」，而「一個像素都不變」最可靠的保證方式
      不是改完再比對，是**讓桌機根本沒有規則可以套用**。
      （仍然有比對：1024／1280／1440 三個寬度的 CTA 位置改前改後逐項相同。）

   🔴 **作法是 `display: contents` ＋ `order`，不動 DOM、也不用 JS 搬節點。**
      為什麼不動 DOM：CTA 目前在 `.section-header` 裡，而四個區塊的內容各自在不同層級
      （courses／news 的內容與標題同層；join／home-links 的內容是 `-inner` 的兄弟）。
      DOM 搬家之後桌機要用 `position: absolute` 把它定位回標題列，
      **那需要逐區塊的魔術數字，而且會隨標題行數改變而跑掉**。
      `display: contents` 把包裝層「拆掉」，讓 CTA 與區塊內容變成同一個 flex 容器的兄弟，
      `order` 就能把它排到最後——桌機不套用這些規則，自然完全維持原狀。
   ⚠️ **JS 搬節點是明確被否決的**：JS 沒跑時按鈕會留在原位，而且會看到跳動。
   ⚠️ **HTML 出現兩份更不行**：日後改一份就會不一致。

   ⚠️ `display: contents` 會讓包裝層自己的樣式失效，這裡逐一補回來：
      `.section-header` 的 `gap: 20px` / `margin-bottom: 32px` → 補在 `.section-header-text` 上
      `-inner` 的 `max-width: 1440px` → **<1024 本來就吃不到**（容器最寬 1023px），不必補
      `.join-inner` 的 `position: relative; z-index: 1` → 補在被提升的子項上
   ⚠️ `display: contents` 用在 `<div>` 包裝層上沒有語意問題（div 本來就沒有語意）。
   ══════════════════════════════════════════ */
@media (max-width: 1023px) {
  /* courses／news：內容與標題本來就在同一個 -inner 裡，拆掉 .section-header 就夠 */
  .courses-inner,
  .news-inner {
    display: flex;
    flex-direction: column;
  }

  /* join／home-links：內容是 -inner 的**兄弟**，所以 -inner 也要一起拆掉，
     CTA 才有機會排到內容後面 */
  .join,
  .home-links {
    display: flex;
    flex-direction: column;
  }

  .join > .join-inner,
  .home-links > .home-links-inner {
    display: contents;
  }

  .section-header {
    display: contents;
  }

  /* 補回 .section-header 原本提供的下方間距（gap 20 ＋ margin-bottom 32）。
     ⚠️ 標題文字本身是一個 div，內部的 eyebrow／title／desc 間距沒有受影響。 */
  .section-header-text {
    margin-bottom: 32px;
  }

  /* 補回 .join-inner 的堆疊層級：.join 有背景圖與 overflow: hidden，
     原本靠 -inner 的 position/z-index 蓋在背景之上。 */
  .join > .section-header-text,
  .join > .section-header-cta,
  .join > .gallery-dots {
    position: relative;
    z-index: 1;
  }

  /* 🔴 這一行才是這一項的重點：把 CTA 排到區塊內容的最後面。
     ⚠️ 用一個夠大的 order 而不是逐區塊算——區塊內容的子項數量不一樣，
        寫死名次會在新增內容時安靜地跑錯位置。 */
  .section-header-cta {
    order: 99;
    margin-top: 32px;
  }
}

/* ══════════════════════════════════════════
   共用元件：輪播控制項（箭頭 ＋ dots ＋ 兩者同排的控制列）
   ------------------------------------------
   客戶 2026-08-19：「首頁課程輪播的左右切換鍵，和課程內頁相關課程的切換鍵看起來一樣，
   但其實有些不同，我希望兩邊樣式統一，若還有其他地方也有用到這樣的切換按鈕也是要一致。」

   🔴 **`.carousel-arrow` 是全站唯一的箭頭外觀定義**，五處都掛這個 class：
      首頁 hero／首頁最新課程／首頁相簿／首頁相關連結／課程內頁相關課程。
      各頁的 CSS 只留「定位」與 hero 的配色覆寫，**不要在別處重新宣告尺寸或配色**。

   ⚠️ **這一版才是真的共用**。P4（2026-08-18）只做到「把數值調成一樣」——
      🔴 而那其實沒有真的一致：**`≥1024` 的放大規則寫在 `index.css`，
         課程內頁不載入那支**，所以 1280 實測首頁是 48×48／圖示 20px、
         相關課程仍是 40×40／圖示 18px。客戶說的「其實有些不同」就是這個。
      **判準：兩處數值相同不等於共用——要問「改一邊另一邊會不會跟著變」。**

   ⚠️ hero 的箭頭**共用尺寸與形狀，但配色獨立**（半透明白＋白邊框＋模糊）：
      它疊在活動照片上，深紫實心會過重、也可能與照片撞色。覆寫寫在 `index.css`。
      🔴 統一之後 hero 在 `≥1024` 從 44px 變成 48px——**這是刻意的**，不是改壞。
   ══════════════════════════════════════════ */
.carousel-arrow {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background-color: var(--color-primary);
  color: #FFFFFF;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  box-shadow: var(--shadow-md);
  transition: background-color 0.2s ease-out, border-color 0.2s ease-out;
}

.carousel-arrow:hover,
.carousel-arrow:focus-visible {
  background-color: var(--color-primary-hover);
}

.carousel-arrow svg {
  width: 18px;
  height: 18px;
}

/* 🔴 `disabled` 的箭頭要看得出不能按（首頁的 scroll-snap 輪播會在頭尾設 disabled）。
   ⚠️ 相關課程與 hero 是無限循環的，不會 disabled——但規則共用不會有副作用。 */
.carousel-arrow:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* 🔴 放大規則**必須放在這裡**（style.css），不可以留在 `index.css`——
   課程內頁不載入 `index.css`，那正是先前 1280 下兩邊差 8px 的原因。 */
@media (min-width: 1024px) {
  .carousel-arrow {
    width: 48px;
    height: 48px;
  }

  .carousel-arrow svg {
    width: 20px;
    height: 20px;
  }
}

/* dots。
   🔴 這組原本在 `index.css`，P4 移到這裡——課程內頁的相關課程輪播也要用，
      而那些頁面**不載入 index.css**。留在原處的話 dots 會退化成沒有樣式的原生按鈕
      （實測 16×6 灰色方塊、沒有圓角），而首頁看起來完全正常，很難發現。 */
.gallery-dots {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  margin-top: 40px;
}

.gallery-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: none;
  border-radius: 4px;
  background-color: var(--color-border);
  transition: background-color 0.2s ease-out, width 0.2s ease-out;
}

.gallery-dot.is-active {
  width: 20px;
  background-color: var(--color-primary);
}

/* 「左箭頭 ─ dots ─ 右箭頭」同一排。
   ⚠️ 兩處的觸發條件不同：首頁只在 <1024 變成這樣（客戶明講其他尺寸不要動），
      相關課程則是**所有尺寸**（客戶：「不管哪個版本都會是輪播的」）。
      所以這裡只定義「排法」，什麼時候套用由各自的頁面 CSS 決定。 */
.carousel-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  margin-top: 40px;
}

.carousel-controls .gallery-dots {
  margin-top: 0;
}

/* ==========================================
   Footer（共用元件，各頁面皆需引入）
   ========================================== */
.site-footer {
  background-color: var(--color-primary);
  color: #FFFFFF;
}

/* 🔴 max-width 要加上兩倍 --page-gutter（理由與 .hero-inner／.register-inner 相同：
   這個元素自己既吃內距又當 1440px 容器，只寫 1440 內容框會變 1392px）。
   2026-08-12 實測（1600px 視窗）：修正前頁尾內容左緣 104px、頁面內容 80px
   ——**全站 15 頁的頁尾都比正文往內縮 24px**，捲到頁尾時左緣會突然內移一階。
   修正後兩者同為 80px。 */
.footer-inner {
  max-width: calc(var(--container-max) + var(--page-gutter) * 2);
  margin: 0 auto;
  padding: 56px var(--page-gutter) 84px;
  display: grid;
  grid-template-columns: 1fr;
  gap: 40px;
}

.footer-brand {
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.footer-logo {
  display: flex;
  align-items: center;
}

/* 🔴 T9（2026-08-28）：頁尾 logo 也切版了，尺寸統一由 .brand-* 那一節決定
   （原本這裡是 `.footer-logo-img` 的 max-height: 80px ＋ 兩個 auto，
   為的是避開 base reset 的 `img { max-width: 100% }` 把整張圖壓扁變形——
   切版之後 icon 是獨立的小圖、寬度由 height + width:auto 決定，那個問題不存在了）。
   ⚠️ .footer-logo 只留頁尾自己的排版身分（.brand 已經是 flex + align-items:center），
      **不要在這裡重新宣告字級或顏色**——顏色走 .brand--on-dark 那一條，只有一份來源。 */

.footer-socials {
  display: flex;
  gap: 12px;
}

/* 客戶 2026-08-12 第 39 項：頁尾社群鈕「顏色不變、尺寸放大」。
   40px → 48px、圖示 24px → 28px。⚠️ 底色刻意仍是白底紫圖示（不套品牌原色）：
   頁尾整面是主色紫，兩顆品牌色圓鈕放在紫底上會變成兩個搶眼的色塊，
   而右下浮動鈕在白色內容區上才需要品牌色來被認出來。客戶也只要求「尺寸」。 */
.footer-social-icon {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background-color: #FFFFFF;
  color: var(--color-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background-color 0.2s ease-out, color 0.2s ease-out, transform 0.2s ease-out;
}

/* hover 改成與聯絡我們頁的社群按鈕完全一致（客戶 2026-08-12 指定「我喜歡聯絡我們的
   社群按鈕的懸停效果，我希望 footer 的社群按鈕也可以按照這樣」）：
   金色漸層 ＋ --color-on-gold 深字 ＋ 上浮 2px。

   ⚠️ 原本是 background-color: var(--purple-200)——白底換成淺紫，
      實測對比只有 1.15:1（#FFFFFF → #C6A8DE 之間的亮度差），
      是「幾乎看不出變化」那一類，與客戶反覆反映過的問題同源。
   🔴 background-color 要一起設成 transparent：
      background-image 疊在 background-color 上面，只設 image 的話
      白底仍在下層，圓鈕邊緣會透出一圈白。
   ⚠️ 預設狀態刻意仍是白底紫圖示（客戶第 39 項只要求放大尺寸）：
      頁尾整面是主色紫，兩顆品牌色圓鈕放上去會變成搶眼的色塊。 */
.footer-social-icon:hover,
.footer-social-icon:focus-visible {
  background-color: transparent;
  background-image: linear-gradient(135deg, var(--gold-from), var(--gold-to));
  color: var(--color-on-gold);
  transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
  .footer-social-icon {
    transition: background-color 0.2s ease-out, color 0.2s ease-out;
  }

  /* 只拿掉位移，保留顏色變化——拿掉顏色就等於沒有 hover 了 */
  .footer-social-icon:hover,
  .footer-social-icon:focus-visible {
    transform: none;
  }
}

.footer-social-icon svg {
  width: 28px;
  height: 28px;
}

.footer-heading {
  font-size: 1.125rem;
  font-weight: 600;
  color: #FFFFFF;
  margin-bottom: 20px;
}

.footer-contact-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.footer-contact-list li {
  display: flex;
  /* 地址與服務時間會換行，圖示要對齊第一行而不是垂直居中 */
  align-items: flex-start;
  gap: 10px;
  font-size: 1rem;
  /* 🔴 min-width: 0 ＋ overflow-wrap 兩個都要：
     flex 項目的預設 min-width 是 auto，等於「不可小於內容的 min-content」，
     而 tpacc2005@gmail.com 是一串不可斷字，min-content 就是它的完整寬度。
     聯絡我們改成兩欄之後每欄變窄，768px 配放大字級時這串會把頁尾撐出
     橫向捲動（實測 +13px／fs-md、+31px／fs-lg，且 index／contact／courses 都一樣）。
     min-width: 0 讓欄位可以窄於內容，overflow-wrap 允許在字中間斷行。 */
  min-width: 0;
}

.footer-contact-list li>a,
.footer-contact-list li>span {
  min-width: 0;
  overflow-wrap: anywhere;
}

.footer-contact-list svg {
  /* 對齊第一行文字的視覺中線（line-height 1.6 × 16px 的一半附近） */
  margin-top: 4px;
}

.footer-contact-list svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: rgba(255, 255, 255, 0.75);
}

.footer-contact-list a {
  transition: color 0.2s ease-out;
}

.footer-contact-list a:hover {
  color: #FFFFFF;
}

/* ==========================================
   Footer 網站導覽（sitemap）
   ------------------------------------------
   客戶需求：這類單位網站資訊量大，希望每個頁面都能從頁尾快速找到入口，
   所以頁尾刻意做大。排法是三層：
     第一層 品牌＋聯絡資訊（含服務時間）
     第二層 六欄有子分類的主選單
     第三層 沒有子分類的單頁（活動花絮／聯絡我們／隱私權政策）橫排

   ⚠️ 為什麼活動花絮與聯絡我們不各自成欄：它們底下沒有子項，
      給一個標題配一個連結會出現空洞欄位，六欄的高度落差也會更突兀。
   ========================================== */
.footer-nav-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 28px 24px;
}

.footer-nav-col {
  min-width: 0;
}

.footer-nav-list {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* 🔴 hover 不能只寫 color: #FFFFFF。
   .site-footer 的文字本來就是純白（color: #FFFFFF），
   所以「hover 變白」等於完全沒有變化——客戶反映「不知道能不能點」就是這個原因。
   正解是讓「平常」略暗、hover 才回到純白，並加上底線與微幅右移，三個訊號一起給。 */
.footer-nav-list a,
.footer-single-links a,
.footer-contact-list a,
.footer-heading a {
  color: rgba(255, 255, 255, 0.76);
  transition: color 0.2s ease-out, transform 0.2s ease-out;
}

.footer-nav-list a,
.footer-single-links a {
  font-size: 0.9375rem;
  line-height: 1.5;
  display: inline-block;
}

.footer-nav-list a:hover,
.footer-single-links a:hover,
.footer-contact-list a:hover,
.footer-heading a:hover {
  color: #FFFFFF;
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* 只有清單型連結做位移；聯絡資訊那幾行與欄位標題不位移，
   避免圖示與文字之間的距離跟著跳動 */
.footer-nav-list a:hover,
.footer-single-links a:hover {
  transform: translateX(3px);
}

/* 鍵盤操作也要看得見（只靠 hover 的話用鍵盤的人完全沒有回饋） */
.footer-nav-list a:focus-visible,
.footer-single-links a:focus-visible,
.footer-contact-list a:focus-visible,
.footer-heading a:focus-visible {
  color: #FFFFFF;
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: 3px;
  border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {

  .footer-nav-list a,
  .footer-single-links a {
    transition: color 0.2s ease-out;
  }

  .footer-nav-list a:hover,
  .footer-single-links a:hover {
    transform: none;
  }
}

/* 第 8 項：聯絡我們拆兩欄（左：地址／服務時間，右：電話／傳真／信箱）
   全部擠一欄會讓這一塊變得很高，與旁邊的品牌區高度差太多 */
.footer-contact-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px 32px;
}

/* 斷點刻意用 768 而非 640：640–767 之間每欄只剩約 140px，
   兩欄反而比一欄更擠（地址每行只放得下 4–5 個字）。 */
@media (min-width: 768px) {
  .footer-contact-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* 頁尾的「安全登出」（客戶 2026-08-19）。
   ⚠️ 它是 <button>，所以要自己對齊頁尾連結的外觀（其餘項目都是 <a>）：
      同樣的字級、行高與 hover 色，不然它在那一欄裡會像個異物。
   🔴 hover 用紅色系而不是全站金色——與側欄的 `.member-sidebar-logout` 一致：
      登出是「離開」的動作，不該長得跟一般導覽連結一樣。 */
.footer-logout-btn {
  padding: 0;
  border: none;
  background: none;
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  color: rgba(255, 255, 255, 0.75);
  transition: color 0.2s ease-out;
}

.footer-logout-btn:hover,
.footer-logout-btn:focus-visible {
  color: #FFC9C9;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.footer-single-links {
  list-style: none;
  margin: 32px 0 0;
  padding: 20px 0 0;
  border-top: 1px solid rgba(255, 255, 255, 0.15);
  display: flex;
  flex-wrap: wrap;
  /* gap 從 28px 收到 16px：加了「｜」分隔線之後，
     全形豎線本身左右就帶空白，原本的 28px 會讓整列看起來鬆散。
     🔴 H8（2026-08-13）欄間距再收到 8px，與 `li+li::before` 的 `margin-right` 相同——
        **兩者相等是分隔線左右等距的充要條件**（見下方 ::before 的說明）。
        改其中一個就要改另一個，否則又會歪掉。 */
  gap: 10px 8px;
}

/* 「｜」分隔線（客戶 2026-08-12 指定，與 header top 那一列同一種呈現）。
   🔴 這裡用 li + li::before，不像 header top 那樣放獨立的 <span>——
      兩處的做法不同是有原因的，不是不一致：
        header top 的連結 hover 時整顆是金色底，分隔線若寫在連結內會被金底蓋掉，
                   所以必須是獨立元素（也因此需要「數量 = 項目數 − 1」的檢查）。
        footer 的連結 hover 只換文字顏色與底線，分隔線寫成 ::before 不會被影響，
                   而且**新增或移除連結時不必記得調整分隔線數量**。
   ⚠️ 分隔線放在 li 上而不是 a 上：a 在 hover 時會 translateX(3px)，
      寫在 a 裡的分隔線會跟著移動，看起來像線在抖。
   ⚠️ aria-hidden 沒辦法下在 ::before 上，但 CSS 產生的內容本來就多數不會被
      讀螢幕軟體念出來，且這裡是純裝飾，不影響可理解性。 */
.footer-single-links li+li::before {
  /* 用 Unicode 轉義而不是直接寫「｜」：CSS 的 content 若含非 ASCII 字元，
     在伺服器沒宣告 charset 時會變亂碼，而那只會在正式主機上出現。 */
  content: '\FF5C';
  /* 🔴 這個 alpha 是**跟著頁尾底色（主色）算出來的**，不是隨手挑的——
     主色一改就要重算，否則會安靜地掉到門檻以下。
       舊主色 #451470：alpha 0.40 → 3.24:1 ✓
       新主色 #6B2FA8（Q4，2026-08-24）：alpha 0.40 只剩 **2.57**，🔴 已低於 3:1
       → 改成 **alpha 0.50 → 3.21:1**，回到與舊值相當的位置
     過非文字的 3:1 門檻；同時明顯比連結文字（白字 8.09:1）暗，不會搶注意力。
     ⚠️ 不要再調淡：0.25 在新主色上只有 1.9，就是客戶抱怨 header 那條「幾乎看不到」的程度。 */
  color: rgba(255, 255, 255, 0.50);
  /* 🔴 H8（2026-08-13）：原本是 `margin-right: -0.25em`，
     旁邊的註解寫著「讓分隔線與兩側連結的距離看起來對稱」——**與實際效果相反**。
     實測：分隔線左側到前一項文字 ≈ 24.5px、右側到後一項文字 ≈ 4px，
     負邊距是**把它更往右推**、加劇不對稱，正是客戶說「靠右貼著後面的連結」的原因。
     （錯的註解比沒有註解更糟：下一個人會照它推論而不去量。）

     **為什麼 `margin-right` 要等於容器的 column-gap**：
     `｜`（U+FF5C）是全形字，豎線的墨跡**水平置中**在它的 em 方框裡，
     所以左右各自帶著約 8.5px 的內建空白。分隔線左側的視覺距離
     ＝ column-gap ＋ 內建空白，右側 ＝ 內建空白 ＋ margin-right。
     內建空白兩邊相等，因此 **column-gap == margin-right ⇔ 左右等距**，
     與字級無關（放大字級時兩邊會一起等比變化）。 */
  margin-right: 8px;
  user-select: none;
}

@media (min-width: 640px) {
  .footer-nav-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 1024px) {
  .footer-nav-grid {
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 24px 20px;
  }
}

.footer-bottom {
  border-top: 1px solid rgba(255, 255, 255, 0.15);
}

/* max-width 加兩倍內距的理由同 .footer-inner——這一行是置中文字，
   左緣看不出差別，但不跟著改，版權列的可用寬度會比上面那區窄 48px。 */
.footer-bottom p {
  max-width: calc(var(--container-max) + var(--page-gutter) * 2);
  margin: 0 auto;
  padding: 20px var(--page-gutter);
  font-size: 0.8125rem;
  text-align: center;
  color: #FFFFFF;
}

/* 🔴 **AB3（PM 2026-09-04）：兩欄的斷點由 `768` 改成 `1024`，`<1024` 一律單欄。**
   原因：768 的第一欄只有 **280px**，而 logo 的中文一行要 292px、英文一行要 305px
   ——兩者都放不下，`white-space: nowrap` 又不會折，於是**直接壓到右邊的「聯絡我們」**
   （實測中文壓 13.1px、英文壓 26.2px）。
   ✅ 改單欄之後第一欄變成整個寬度，**擠壓的前提消失**，logo 可以維持一排（見 `.brand-zh > span`）。
   ⚠️ 代價：768–1023 的 footer 變成上下堆疊、比較長。**這是 PM 定案的取捨**——
      版面長一點沒有壞掉，字壓在一起才是壞掉。 */
@media (min-width: 1024px) {
  /* 第一排：品牌 ／ 聯絡資訊。第二排：網站導覽獨占整寬。
     網站導覽有六欄，跟 logo 和聯絡資訊擠在同一排會壓得很窄、六欄全部斷行。 */
  .footer-inner {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
    gap: 40px 48px;
  }

  .footer-col-nav {
    grid-column: 1 / -1;
    /* 8px 太貼，「網站導覽」這行幾乎黏在分隔線上（客戶反映）。
       拉到 32px 讓分隔線真的像分隔線。 */
    padding-top: 32px;
    border-top: 1px solid rgba(255, 255, 255, 0.15);
  }
}

/* ==========================================
   內頁標頭（共用元件：報名課程／聯絡我們等內頁皆可套用）
   ========================================== */
.page-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: 220px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  background-size: cover;
  background-position: center;
  background-color: var(--purple-950);
}

/* M6（PM 2026-08-17 提供素材）：內頁標頭的背景圖。
   `.page-hero` 本身只提供深紫底色與遮罩，背景圖由各頁自己指定。

   🔴 **這兩條刻意放在 style.css，不是放在頁面 CSS**：
      `news-hero.jpg` 由 `news.html` 與 `article.html` 共用，
      而那兩頁載入的頁面 CSS 不同支（`news.css` / `article.css`）
      ——寫在頁面 CSS 就得寫兩份，換圖時改一份不夠而且不會報錯。
   ⚠️ 只寫 `background-image`：`background-size: cover` 與置中由 `.page-hero` 提供，
      🔴 **不要為了某一張圖寫死 `background-position: 68%` 這種偏移值**
         ——客戶日後換一張主體在別處的圖就會歪掉，而且不會報錯，只是看起來很怪。
         構圖靠上傳前裁好（全域守則）。
   ⚠️ 深紫遮罩（`.page-hero-overlay` 的 0.6）**不可拿掉**：hero 上是白色標題，
      沒有遮罩在亮色照片上會讀不清楚。
   ⚠️ 全站 11 個 `.page-hero` 的背景圖**一律寫在 CSS，沒有一頁用 inline style**
      （2026-08-18 把 contact／gallery／links／member-center／sitemap 五頁收進各頁 CSS）。
      🔴 盤點「哪一頁有背景圖」時**只 grep CSS 檔會漏掉 inline style**，
         那正是 M 軌踩過的坑（見 CLAUDE.md 六節）——現在沒有 inline style 了，
         但新增頁面時仍要守住這條，不要為了省事寫回 `style="background-image:…"`。
   ⚠️ 專屬照片目前只有 news／courses 兩張（PM 確認過用途）；
      about 用 hero-1.jpg、gallery 用 gallery-hero.jpg（都寫在各自的頁面 CSS）；
      其餘 **6 頁**（contact／downloads／links／member-center／privacy／sitemap）
      共用通用的 page-hero.jpg（2026-08-26 更正：舊註解寫「4 頁」漏了 downloads 與 privacy）。
      🔴 換那張通用圖的做法是**直接覆蓋 images/hero/page-hero.jpg**，六頁一次到位
         （S10，客戶 2026-08-26）——不要另存新檔只給部分頁面用，那是分叉。
      換圖時改該頁 CSS 那一條即可。 */
.news-hero {
  background-image: url('../images/hero/news-hero.jpg');
}

.courses-hero {
  background-image: url('../images/hero/courses-hero.jpg');
}

.page-hero-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: rgba(26, 17, 33, 0.6);
  pointer-events: none;
}

.page-hero-content {
  position: relative;
  z-index: 2;
  padding: 24px;
}

.page-hero-eyebrow {
  display: block;
  font-size: 1rem;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.85);
}

.page-hero-title {
  font-size: 2rem;
  font-weight: 600;
  color: #FFFFFF;
}

@media (min-width: 768px) {
  .page-hero {
    min-height: 280px;
  }

  .page-hero-title {
    font-size: 2.5rem;
  }
}

/* ==========================================
   導覽列：當前頁面標示
   ========================================== */
.nav-link.is-active {
  color: var(--color-primary);
}

/* ==========================================
   共用元件：課程卡片（首頁 home-02 與報名課程頁共用）
   ========================================== */
/* 🔴 grid 軌道一定要用 minmax(0, 1fr)，不可以只寫 1fr。
      1fr 等於 minmax(auto, 1fr)，而 auto 的下限是「項目的 min-content」——
      卡片是 180px 縮圖 ＋ 20px gap ＋ 內文，min-content 剛好 312px，
      容器只有 257px（320px 螢幕減去左右 padding 與捲軸）時軌道仍佔 312px，
      整頁就橫向溢出 16px。文字放大模式下更嚴重（大：36px）。
      minmax(0, …) 把下限拿掉，軌道就不會超過容器。 */
.courses-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 32px 40px;
}

/* 🔴 圖片在上、文字在下（客戶第 22 項指定）。
   原本是 display: flex 的左右排（縮圖 180px 在左、文字在右）。
   改成直式之後：
     - 縮圖變成滿版，圖片看得清楚（原本 180px 寬的課程照片幾乎看不出內容）
     - 卡片變窄變高，一列可以放更多張
   ⚠️ 因此 style.css 裡原本針對「左右排擺不下」寫的那些規則都不需要了
      （359px 以下改堆疊、html.fs-lg 下延伸到 374px），已一併移除——
      直式版面本來就不會有「兩欄擺不下」的問題。 */
/* 🔴 I1（客戶 2026-08-13「同一排卡片有高有低」）：**等高要靠版面撐，不是靠欄位對齊。**
   統一欄位（8 個一律輸出）只解決「meta 行數不同」，但課程名稱長度不同
   （`D班（板橋農會）` vs `114年度營所稅申報解析（板橋 1 班）`）在窄欄位下折行數就不同，
   價格與按鈕還是會錯開。
   做法：卡片本身 `height: 100%` 撐滿 grid 軌道（grid 項目預設 stretch，
   但卡片內容比軌道矮時仍需要它才會拉高），內容用 flex column，
   價格＋按鈕那一區 `margin-top: auto` 推到底。
   ⚠️ **`align-items` 不要改成 start／center**：那會讓卡片縮成內容高度，等高立刻失效。
   驗收：量同一排卡片按鈕的 `getBoundingClientRect().top`，差距應為 0。 */
.course-card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: 100%;
}

.course-thumb {
  width: 100%;
  min-width: 0;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: linear-gradient(135deg, var(--purple-600), var(--purple-400));
  color: #FFFFFF;
  display: flex;
  align-items: center;
  justify-content: center;
}

.course-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.course-thumb svg {
  width: 36px;
  height: 36px;
  opacity: 0.85;
}

/* H2：內文與縮圖之間要有間距。
   ⚠️ 只加上內距，左右刻意維持 0——卡片是「圖上文下」，
      文字左緣要與縮圖左緣對齊，補水平內距會讓文字內縮而看起來沒對齊。 */
.course-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding-top: 16px;
}

.course-title {
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--color-text);
  line-height: 1.5;
  margin-bottom: 16px;
}

/* ==========================================
   共用元件：課程狀態標籤 .course-status
   ------------------------------------------
   三個消費端：課程卡片（首頁／報名課程）、列表檢視的表格列、課程內頁 hero。

   🔴 為什麼要抽出來：原本是**三份**——`.course-row-status`（courses.css）、
      `.course-detail-status`（course-detail.css）視覺幾乎一樣，
      而 E0 新增卡片時用了第三個 class `.course-card-status`
      **卻沒有寫任何 CSS**。後果不是「樣式不一致」而是**功能失效**：
      「報名中」與「已額滿」都變成同色同粗細的本文，使用者分不出狀態，
      而且不會報錯（畫面上就是一行普通文字）。
   ⚠️ 這正是「新增前先查元件表」的反例——查了就會發現已經有兩份可用。

   `.is-full` 用灰色（`--color-muted`）而不是紅色：已額滿是「資訊」不是「錯誤」，
   紅色會讓人以為自己操作出錯。實測灰底白字 4.83:1。

   🔴 Q3（客戶 2026-08-24）：從兩種變成**四種**（預設紫／即將額滿橘／報名中綠／已額滿灰）。
      色值與對比實測見 :root 的 --status-* 那組。
   🔴 **class 由 `window.courseStatusClass(status)` 統一決定，三個消費端共用一支**
      （script.js 的 card-html 區段最前面）。Q3 之前三處各自寫 `status === '已額滿'`，
      新增第三種狀態要改三個地方，**漏一處是那個畫面沒顏色而且不會報錯**。
      ⚠️ 新增狀態值時只改那支函式 ＋ 這裡加一條 class，不要回到消費端補 if。
   ⚠️ 預設（紫底白字，實測 8.09）留給「對應不到的狀態值」當安全預設，不是死碼。
   ========================================== */
.course-status {
  display: inline-block;
  align-self: flex-start;
  padding: 0.125rem 0.625rem;
  border-radius: var(--radius-sm);
  background-color: var(--color-primary);
  color: #FFFFFF;
  font-size: 0.875rem;
  font-weight: 600;
}

.course-status.is-full {
  background-color: var(--color-muted);
}

/* 即將額滿——底色與文字色都在 `:root` 的 `--status-nearfull*`，理由寫在那裡。
   ⚠️ **這裡原本有一則「文字一定要用深色」的警語，2026-08-25 窗口指示改成白字之後
      已經不成立，所以刪掉了**（當時的理由是白字只有 3.20、深字 5.06）。
      🔴 **留著錯的警語比沒有警語更糟**——它會主動說服下一個人把白字「修」回深字。
      現在的定案與理由見 `:root` 的 `--status-nearfull-text`。 */
.course-status.is-nearfull {
  background-color: var(--status-nearfull);
  color: var(--status-nearfull-text);
}

/* Q3：報名中——客戶指定的綠，白字 6.00 過 AA。 */
.course-status.is-open {
  background-color: var(--status-open);
  color: var(--status-open-text);
}

/* 🔴 S19-1（客戶 2026-08-27）：已截止——與 .is-full 同一族（都是「不能報名」的**資訊**，
   不是錯誤，🔴 不要用紅色）。兩者同色但**文字不同**（「已額滿」vs「已截止」），
   這是可接受的：使用者要分辨的是「為什麼不能報」，字已經講清楚了。
   ⚠️ 判斷邏輯在 script.js 的 courseStatusClass()，**四個**消費端共用一支
      （卡片／內頁 hero／列表列／tools/gen-course-list.js）——不要回到消費端補 if。 */
.course-status.is-closed {
  background-color: var(--color-muted);
}

/* 課程內頁 hero 用的放大版（第 27 項：字級與內距一起放大，
   否則字變大而底色框沒跟著長，看起來會擠）。 */
.course-status-lg {
  font-size: 0.9375rem;
  padding: 6px 14px;
}

/* ==========================================
   S19-2／S19-3：狀態標籤那一列（標籤在左、報名截止日在右）
   ------------------------------------------
   原本 .course-status 是 .course-body（flex column）的直接子項、`align-self: flex-start`，
   自己獨佔一列、右側整片空白。客戶要把報名截止日放在它右邊。
   ✅ **高度零增加**——所以先前討論的「縮短課程照片」前提消失，
      `.course-thumb` 維持 `aspect-ratio: 4/3` 不動。

   🔴 `.course-status` 的 `align-self: flex-start` 在這個 flex row 裡會變成「靠上對齊」，
      與右邊的日期文字對不齊 → 這裡改寫成 `center`。
      ⚠️ 不能直接把那條從 .course-status 拿掉：列表檢視的表格列沒有包這一層，
         那裡仍要靠它避免標籤被拉成整格寬。

   🔴 `flex-wrap: wrap` 是給「最擠的組合」用的（實測 360 × fs-lg：
      「即將額滿」＋「報名至 115/12/14」放不下）→ **日期換行到標籤下方**，
      只在極端組合下多一列。
      🔴 **不要用 `overflow: hidden` 裁掉日期**——那會讓資訊安靜地消失。
   ========================================== */
.course-status-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 4px 10px;
  /* 🔴 U3（PM 2026-08-31）：這一列與下面課程名稱之間的間距。
     ------------------------------------------
     這**不是新規則**，是把一條既有規則搬到對的位置上。原本寫成
     `.course-body>.course-status { margin-bottom: 10px }`（間距由容器給、不寫進元件本身），
     而 S19-2 把狀態標籤包進 `.course-status-row` 之後，`.course-status` 就不再是
     `.course-body` 的直接子項——**那條子選擇器從此再也沒命中過，而且完全不會報錯**，
     畫面上只是「有點擠」，直到 PM 目視發現。
     🔴 判準：**包一層容器會讓 `>` 子選擇器安靜失效**——改結構時要回頭掃一次
        有沒有規則是靠「原本的父子關係」寫的。
     ⚠️ 不要改成加在 `.course-title` 的 `margin-top`：卡片以外還有列表檢視與內頁 hero
        兩個消費端，標題自帶上邊距會把間距帶到不需要的位置去。
     ⚠️ 內頁 hero 用 `.course-status-row-lg`，它的下邊距在 course-detail.css 另有一份
        （`.course-detail-hero .course-status-row`，特異性 (0,2,0) 會蓋過這裡的 (0,1,0)）
        ——那是「這個位置才需要的間距」，不要動。 */
  margin-bottom: 10px;
}

.course-status-row .course-status {
  align-self: center;
}

/* 🔴 日期**不要做成第二顆標籤**（沒有底色、沒有邊框）
   ——兩個色塊並排會讓人以為那也是一種狀態。這一條在 U4 之後仍然成立。

   🔴 U4（PM 2026-08-31）：字級由 `0.875rem` 改成 **`1rem`，與 `.course-meta-item` 一致**。
      S19-3 刻意做小的理由是「不要做成第二顆標籤」——那個理由由「無底色」達成，
      不必靠縮小字級，而 PM 反映這行太不顯眼。顏色維持 `--color-muted`（meta 本來就是這個色）。
   ⚠️ `white-space: nowrap` 與 `.course-status-row` 的 `flex-wrap: wrap` **要一起保留**：
      字變大之後「即將額滿＋報名至 115/12/14」在窄螢幕更容易放不下，靠換行處理。
      🔴 絕對不要改用 `overflow: hidden` 裁掉——資訊會安靜地消失。 */
.course-signup-deadline {
  font-size: 1rem;
  color: var(--color-muted);
  white-space: nowrap;
}

/* 🔴 U4：剩餘天數 ≤3 天（含當天）轉紅。
   ------------------------------------------
   🔴 **這個 class 只由執行期的 `markUrgentDeadlines()` 加上，產生器一律不輸出它**。
      「剩幾天」是**滾動窗口狀態、每天都在變**，而 85 個內頁與 courses.html 的靜態第一頁
      是建置當下產生的靜態 HTML——寫死進去的話隔幾天就是錯的紅字，
      **而且不會報錯、畫面看起來完全正常**。做法與 S19-1 的「已截止」同一套：
      產生器只寫事實（`data-signup-end="YYYY-MM-DD"`），紅色由執行期算。
   ✅ JS 沒跑時＝沒有紅色，但日期本身照常顯示（漸進增強，不會變空白也不會是錯的紅）。
   ⚠️ 已截止（負數天）**不套紅**：那時標籤已經是「已截止」，再加紅是重複，
      而且會把「資訊」變成「錯誤」的語意（灰＝資訊，見 courseStatusClass 的註解）。
   🔴 **加粗不是裝飾**：只靠顏色傳達狀態，色覺障礙者等於收不到這個訊號
      （全域守則七之二節：單一訊號常常等於沒有訊號）。要拿掉粗體請 PM 定案。 */
.course-signup-deadline.is-urgent {
  color: var(--color-urgent);
  font-weight: 700;
}

/* 課程內頁 hero：hero 空間比卡片寬，標籤與日期靠左並排就好，不必推到兩端。
   ⚠️ 那一列的下邊距在 course-detail.css（`.course-detail-hero .course-status-row`）
      ——它是「這個位置才需要的間距」，不屬於元件本身，只有一份來源。 */
.course-status-row-lg {
  justify-content: flex-start;
  gap: 6px 14px;
}

/* 🔴 頁尾等深色底不會用到這個元件；若日後有深色底的卡片，
   --color-muted 對深底的對比要重量，不要直接沿用。 */

/* I1：價格＋按鈕成一組推到卡片底部，這樣不論上方課名折幾行，按鈕都對齊。
   ⚠️ 兩者要包在同一個容器裡，不能只對按鈕下 margin-top: auto——
      那樣價格會被留在上面、與按鈕之間拉開一段隨課名長度變化的空白。 */
.course-card-foot {
  margin-top: auto;
}

/* ==========================================
   共用元件：清單空狀態 .course-list-empty
   ------------------------------------------
   四個消費端：`courses.html`（找不到符合條件的課程）、`index.html`（分類無課／無消息）、
   `links.html`、`news.html`。

   🔴 原本這條只寫在 `courses.css` 裡，而那支只有報名課程頁會載入——
      其他三頁的空狀態**完全沒有樣式**（本文色、左對齊、0 內距），
      看起來像漏了一段文字而不是「這裡目前沒有內容」。
      ⚠️ class 名字取得像是專屬於課程頁，是它被放錯檔案的原因之一；
         沿用原名不改，是為了不動 4 頁的 HTML 與 2 支 JS。

   🔴 `grid-column: 1 / -1` 是 H4 的真正修法。
      首頁的空狀態是 `.courses-grid`（三欄）的 grid 項目，
      不跨欄的話它只佔 **1 欄 379px**（容器 1217px），42 個字被擠成兩行——
      客戶看到的「文字太窄」是這個原因，不是 max-width 設太小。
      ⚠️ 對非 grid 容器裡的空狀態（news／links）這行會被忽略，不必分開寫。
   ========================================== */
.course-list-empty {
  grid-column: 1 / -1;
  text-align: center;
  color: var(--color-muted);
  font-size: 0.9375rem;
  padding: 64px 24px;
}

/* ==========================================
   課程卡片的 hover 互動（客戶反映「太靜態」）
   ------------------------------------------
   四個訊號一起給，因為單一訊號對長輩來說常常察覺不到：
     1. 縮圖放大（scale）＋ 亮度略降，讓圖看起來「被選中」
     2. 標題轉主色 ＋ 金色底線
     3. 整張卡片微微上浮 ＋ 陰影
     4. 縮圖上浮出一個「查看課程」提示
   ⚠️ 縮圖放大用 transform 而不是改 width／height：後者會觸發 reflow，
      同一列其他卡片會跟著位移。
   ⚠️ overflow: hidden 已在 .course-thumb 上，放大不會超出圓角框。
   ⚠️ 卡片上浮用 translateY 而非 margin，否則會推動下方內容。
   ========================================== */
.course-card {
  transition: transform 0.25s ease-out;
}

.course-card:hover {
  transform: translateY(-4px);
}

/* 卡片掛了 data-aos：AOS 動畫結束的規則（權重 0,3,0）會蓋掉上面那條 hover 上浮，用更高的權重補回來 */
.course-card[data-aos].aos-animate:hover {
  transform: translateY(-4px);
}

.course-thumb {
  position: relative;
}

.course-thumb img {
  transition: transform 0.35s ease-out, filter 0.25s ease-out;
}

.course-card:hover .course-thumb img {
  transform: scale(1.06);
  filter: brightness(0.85);
}

/* 縮圖上的提示：平常完全透明，hover 才浮出。
   🔴 這裡原本是 .course-thumb::after { content: '查看課程' }，改成真實元素有兩個理由：
     ① CSS 的 content 放中文，在伺服器沒宣告 charset 時會變亂碼
        （本機 python http.server 回的是 `text/css` 不帶 charset）——
        這是那種只會在正式主機上出現的 bug，本機永遠測不到。
     ② content 裡放不了圖示，而客戶第 21 項要求「圖示在文字左邊」。
   ⚠️ 全站其他地方若還要在 content 裡放非 ASCII 字元，請用 Unicode 轉義
      （例如 '\FF5C' 代表「｜」），那種寫法不受 charset 影響。 */
.course-thumb-hint {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) scale(0.9);
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.5rem 1rem;
  border-radius: var(--radius-sm);
  background-image: linear-gradient(135deg, var(--gold-from), var(--gold-to));
  color: var(--color-on-gold);
  font-size: 0.875rem;
  font-weight: 600;
  white-space: nowrap;
  opacity: 0;
  /* pointer-events: none 讓滑鼠事件穿透到底下的 <a>，
     否則游標移到提示上會被它接住，hover 狀態反而閃爍 */
  pointer-events: none;
  transition: opacity 0.25s ease-out, transform 0.25s ease-out;
}

.course-thumb-hint svg {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

.course-card:hover .course-thumb-hint,
.course-thumb:focus-visible .course-thumb-hint {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

.course-card:hover .course-title,
.course-card:hover .course-title a {
  color: var(--color-primary);
}

.course-title a {
  position: relative;
  transition: color 0.2s ease-out;
}

/* 標題 hover 底線：1px ＋ 主色紫（2026-08-12 客戶指定，與主導覽、
   header top、網站導覽、關於本會標題全部統一成同一種線）。
   ⚠️ 原本是 2px 的金色漸層——金色在本站的語意是「整塊可點擊區域的 hover」
      （見七之二節）。課程卡整塊本來就是可點的，卡片自己已經有 hover 效果，
      標題再來一條金線就變成同一個訊號講兩次；改成紫色細線當「這是連結」的提示。
   🔴 border-radius 刻意不設：1px 高的方塊配 1px 圓角會兩端各被削掉一半，
      看起來更短更淡（.nav-link::after 因此用 0.5px，這裡直接不設更乾脆）。
   ⚠️ 改粗細要連同 .nav-link::after 與 sitemap 的標題底線一起改——
      全站只有一種底線是刻意的保證，只改一處會讓那句話變成假的。 */
.course-title a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1px;
  background-color: var(--color-primary);
  opacity: 0;
  transition: opacity 0.2s ease-out;
}

.course-card:hover .course-title a::after,
.course-title a:focus-visible::after {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {

  .course-card,
  .course-thumb img,
  .course-thumb::after {
    transition: none;
  }

  .course-card:hover,
  .course-card[data-aos].aos-animate:hover {
    transform: none;
  }

  .course-card:hover .course-thumb img {
    transform: none;
  }
}

.course-meta {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 16px;
}

.course-meta-item {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 1rem;
  /* 🔴 X3（PM 2026-09-02）：由 `--color-muted` 改成 `--color-text`。
     PM 覺得淺灰的層級感太弱（**不是可讀性問題**——`--color-muted #636363`
     白底 5.74 本來就過 AA）。
     🔴 只有「內容型文字」改深；**狀態／未選取／分隔／次要標示那 9 類一律不動**
        （`.course-signup-deadline`／`.course-tab`／`.pagination-btn`／
        `.pagination-ellipsis`／`.course-status.is-full`／麵包屑／header top 的「｜」與
        「文字大小」／`.filter-date-sep`／`.about-figure-caption`）——
        那裡的 `--color-muted` **不是「比較淺的字」，是語意**，改深會讓主次層級消失。 */
  color: var(--color-text);
}

.course-meta-item svg {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  color: var(--purple-300);
}

.course-price {
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--color-primary);
  margin: 0 0 16px;
  /* 費用有兩段（會員／非會員），窄卡片時要能換行，
     不換行會把卡片撐寬造成橫向溢出 */
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 0.25rem;
}

/* 🔴 會員與非會員用不同顏色（客戶第 21 項），但都留在紫色系。
   實測（白底上的對比 / 與會員色的差異倍數）：
     會員   --color-primary #6B2FA8 →  8.09:1（Q4 前是 #451470／13.23:1）
     非會員 --purple-500    #8A5FA8 →  4.90:1，與會員色差 **1.65 倍**
   🔴 **這個差異倍數在 Q4（2026-08-24）主色改亮之後從 2.70 掉到 1.65**，
      已經低於當初「因為分不出來而被否決」的 --purple-600（1.97）。
      ⚠️ 已回報 PM，維持現值等 PM 定案——**不要自己換非會員色**：
      色階裡沒有一個值能同時（a）與新主色差得開（b）在白底上還過 AA 4.5
      （#A376C7 差 2.30 但白底只有 3.52）。
   ⚠️ 為什麼選 500 不選 600：--purple-600（#714C8E）與會員色只差 1.97 倍（舊主色時），
      兩個深紫並排時分不出來——「不同顏色」的目的就落空了。
   ⚠️ 為什麼不再往淺（--purple-400 #A376C7 差 3.76 倍更明顯）：
      它對白底只有 3.52:1，一般文字門檻是 4.5，會讀不清楚。
   ⚠️ **不要只靠顏色區分**：兩段各自都寫著「會員」「非會員」的字樣，
      顏色只是輔助。色弱使用者與黑白列印都還讀得懂。 */
.course-price-member {
  color: var(--color-primary);
}

.course-price-guest {
  color: var(--purple-500);
}

.course-price-sep {
  color: var(--color-border);
  font-weight: 400;
}

/* 🔴 max-width: 100% ＋ 解除 nowrap 是防溢出的必要組合，不是保險條款。
   360px ＋ 放大字級「大」實測：卡片文字欄只有 112px，而這顆按鈕的 max-content
   （「我要報名」19.25px × 4 ＋ 圖示 16 ＋ gap 6 ＋ 左右內距 40）是 141px；
   align-self: flex-start 讓它用 max-content 當寬度，.course-body 又是 min-width: 0
   不會頂回去 → 整頁橫向溢出 8px（index 與 courses 兩頁的課程卡都會）。
   ⚠️ 360px 是下面那條 max-width: 359px 斷點的**外側第一格**，也就是「還並排的最窄情形」。
      過去的掃描用 320／375／430／768／1280 剛好跳過 360，所以一直沒被抓到
      ——這與 CLAUDE.md 記的 1024px 那次是同一種錯（沒有測在斷點邊界上）。
   ⚠️ 只在這一顆按鈕解除 .btn 的 white-space: nowrap，不要去改 .btn 本體：
      其他按鈕不在這種被夾死的窄欄位裡，全站解除會讓長標籤到處斷行。 */
.course-cta {
  align-self: flex-start;
  padding: 10px 20px;
  font-size: 0.875rem;
  min-height: auto;
  max-width: 100%;
  white-space: normal;
}

/* 🔴 上面三段規則已於 2026-08-12 移除，這裡記下為什麼、避免有人「補回來」：
     - @media (max-width: 359px) 課程卡改堆疊
     - @media (max-width: 374px) 在 fs-lg 下延伸堆疊
     - @media (min-width: 640px) .course-thumb { flex-basis: 240px }
   它們全部是為了「縮圖 180px 在左、文字在右」那個**左右排**版面而存在的：
   窄螢幕下兩欄擺不下才要堆疊、寬螢幕下才要把縮圖加寬。
   客戶第 22 項把卡片改成「圖片在上、文字在下」之後，卡片本來就是單欄，
   不存在「兩欄擺不下」也不需要指定縮圖寬度（已是 width: 100%）。
   ⚠️ 若日後又改回左右排，這三段要一起回來——單獨改 flex-direction
      會在 320px 與 fs-lg 下橫向溢出（當時實測分別是擺不下與溢出 8px）。 */


/* 🔴 欄數配合「圖上文下」的直式卡片改過（客戶第 22 項）。
   左右排的卡片很寬，2 欄就已經佔滿；直式卡片窄而高，2 欄會讓每張寬到 676px
   （1440 容器實測），縮圖被拉成一條橫幅、看起來很怪。
   改成 640px 起 2 欄、1024px 起 3 欄：1440 容器下每張約 437px，
   4:3 縮圖高約 328px，是課程照片看得清楚的尺寸。
   ⚠️ 全部用 minmax(0, 1fr) 不用 1fr——理由見上面 .courses-grid 那段註解
      （1fr 的下限是 min-content，窄螢幕會撐出橫向溢出）。 */
@media (min-width: 640px) {
  .courses-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1024px) {
  .courses-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* ==========================================
   共用元件：麵包屑（內頁通用）
   ------------------------------------------
   全站 7 頁都用這個 class（about／article／course-detail／downloads／
   gallery／privacy／sitemap），各頁的專屬 CSS 只加 margin 與斷行，
   🔴 不要在頁面 CSS 覆寫字級或顏色，否則就不是單一來源了。

   字級：客戶反映 16px 以下對長輩太小（原本 0.875rem ＝ 15.75px）。
   改用 0.9375rem ＝ 16.875px——直接落在本檔既有的字級級距上，
   不另開一個 0.8889rem 這種只為湊 16px 的碎值。
   ⚠️ 一定要用 rem 不能寫 16px：右上角有文字放大切換（改 html 的 font-size），
      寫死 px 的地方按「大」不會跟著放大。
   ========================================== */
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 0.9375rem;
  color: var(--color-muted);
}

.breadcrumb a {
  transition: color 0.2s ease-out;
}

.breadcrumb a:hover {
  color: var(--color-primary);
}

.breadcrumb .breadcrumb-current {
  color: var(--color-primary);
  font-weight: 600;
}

/* ==========================================
   共用元件：表單控制項（input / textarea / select）
   ========================================== */
.form-label {
  display: block;
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-text);
  margin-bottom: 8px;
}

/* ==========================================
   共用元件：表單的說明文字
   ------------------------------------------
   .member-panel-desc  區塊標題下方的一句說明
   .form-hint          單一欄位下方的補充說明

   🔴 2026-08-13 從 member-center.css 搬過來：聯絡我們頁的表單也在用，
      而那一頁不載入 member-center.css，所以樣式根本沒生效
      （實測 .member-panel-desc 的算出值與裸 <p> 完全相同）。
   ⚠️ 判準：**class 名字是通用的就不要放在頁面 CSS**。
      放錯的失敗模式是「第二個頁面用了沒有樣式」——不報錯、也不明顯。
   消費端：member-center.html、contact.html
   ========================================== */
.member-panel-desc {
  font-size: 0.875rem;
  color: var(--color-muted);
  margin: -8px 0 20px;
}

.form-hint {
  margin: 6px 0 0;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--color-muted);
}

.form-label span {
  color: #E53935;
}

.form-group {
  margin-bottom: 0;
}

.input {
  width: 100%;
  appearance: none;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 12px 16px;
  font-size: 1rem;
  font-family: inherit;
  color: var(--color-text);
  transition: border-color 0.2s ease-out;
}

.input::placeholder {
  color: var(--color-muted);
}

.input:focus {
  outline: none;
  border-color: var(--color-primary);
}

textarea.input {
  resize: vertical;
  min-height: 96px;
}

/* 唯讀欄位：資料是公會登錄或註冊時驗證過的，會員只能看（J1，客戶 2026-08-14）。
   ------------------------------------------
   客戶：「不可變更的欄位視覺上和其他欄位長得一樣，會分不出來，
   通常這種欄位懸停之後滑鼠會有禁止符號之類的嗎？或是欄位顏色也會和別的欄位稍微不同。」

   🔴 **兩個都要做，而且靜態的視覺差異比 hover 更重要**——
      hover 要滑過去才知道，**手機根本沒有 hover**。

   🔴 **用 `readonly` 不要用 `disabled`**：

     |          | `disabled` | `readonly` |
     |----------|-----------|-----------|
     | 選取／複製 | ❌ 不能    | ✅ 可以    |
     | 鍵盤 focus | ❌ 跳過    | ✅ 可以    |
     | 螢幕閱讀器 | 唸「不可用」| 唸「唯讀」 |

     會員很可能想**複製自己的帳號或會員編號**，`disabled` 會讓他複製不了。
     語意上這些欄位也不是「不可用」，是「不給改」。
   ⚠️ 因此這條選擇器只認 `[readonly]`——還掛著 `disabled` 的欄位不會套到樣式，
      這是刻意的：看起來沒生效就會有人回來把它改成 `readonly`。 */
.input[readonly] {
  background-color: var(--color-bg-alt);
  color: var(--color-muted);
  cursor: not-allowed;
}

/* 唯讀欄位 focus 時不要變主色框：那個框的意思是「可以開始輸入了」。 */
.input[readonly]:focus {
  border-color: var(--color-border);
}

/* ==========================================
   共用元件：內容方框（白底卡片，報名課程／會員中心／報名明細等頁面共用）
   ========================================== */
.register-box {
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 24px;
}

.register-box-title {
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--color-heading);
  margin-bottom: 20px;
}

@media (min-width: 768px) {
  .register-box {
    padding: 32px;
  }
}

.register-form-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  margin-bottom: 8px;
}

.form-group-full {
  grid-column: 1 / -1;
}

.register-error {
  color: #C0392B;
  font-size: 0.8125rem;
  margin: 4px 0 16px;
}

@media (min-width: 640px) {
  .register-form-grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* 標籤在左、值緊接在右的資料列表（確認資料／報名明細等） */
.register-confirm-list {
  margin: 0 0 12px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.register-confirm-list div {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  font-size: 0.9375rem;
}

.register-confirm-list dt {
  flex-shrink: 0;
  width: 110px;
  color: var(--color-muted);
  font-size: 1rem;
}

.register-confirm-list dd {
  margin: 0;
  flex: 1;
  min-width: 0;
  color: var(--color-text);
  font-weight: 600;
  font-size: 1rem;
  /* 🔴 Z1（PM 2026-09-02）：`min-width: 0` 只讓「框」縮得下去，
     **不會讓裡面那串不可斷字的文字換行**——電子信箱 `demo@example.com` 與
     訂單編號 `R20260115230` 都是連續 ASCII，320 寬實測分別溢出 71／25px，
     一路往上撐到 `.member-main`（327px）＝ `registration-detail.html` 整頁橫向溢出的主因。
     ⚠️ 兩條要一起在：只有 `min-width: 0` 會看到「框縮了、字還是戳出去」。
     ⚠️ 用 `anywhere` 不用 `break-word`：`anywhere` 會一併縮小 min-content 貢獻，
        父層才真的縮得下來（與 `.footer-contact-list` 的信箱是同一個坑、同一種修法）。 */
  overflow-wrap: anywhere;
}

.register-actions {
  padding-top: 20px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.register-actions.register-actions-split {
  padding-top: 24px;
  justify-content: left;
}

.register-actions.register-actions-start {
  justify-content: flex-start;
  padding-top: 24px;
}

/* 匯款帳號放大顯示的方框 */
/* 付款項目（2026-09-17）：報名明細把一張單拆成多筆付款時，逐筆一段。
   結構與多組匯款帳號那一段一致，只是多了標題列（名稱｜金額｜狀態）。 */
.register-payment-item + .register-payment-item {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--color-border, #E5E1D8);
}

.register-payment-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}

.register-payment-title {
  font-weight: 600;
}

.register-payment-amount {
  margin-left: auto;
  font-weight: 700;
}

.register-payment-meta {
  margin-top: 6px;
  color: var(--color-text-2, #5C5C70);
  font-size: 0.9375rem;
}

.register-payment-bank {
  margin-top: 12px;
}

.register-payment-total {
  margin: 20px 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--color-border, #E5E1D8);
  text-align: right;
  font-size: 1.0625rem;
}

/* 多組匯款帳號（2026-09-17）：一組一段，第二組起用上方分隔線隔開，
   只有一組時不會有 .is-more，畫面與單一組時完全一樣。 */
.register-bank-account.is-more {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--color-border, #E5E1D8);
}

.register-bank-account-title {
  margin: 0 0 8px;
  font-weight: 600;
}

.register-account-box {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 20px 24px;
  margin: 4px 0 16px;
}

.register-account-number {
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: 0.4em;
  color: var(--color-primary);
  /* 帳號是一串數字，中間被折成兩行會看錯位；寧可整串縮小也不要換行 */
  white-space: nowrap;
}

/* 手機：1.5rem + 0.4em 字距的帳號放不進畫面寬度，縮字並讓帳號與按鈕各佔一整行。
   字距在最後一碼後面也會加空隙，靠 margin-right 補回來，數字才不會看起來偏左。 */
@media (max-width: 575.98px) {
  .register-account-box {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    padding: 16px;
  }

  .register-account-number {
    font-size: 1.125rem;
    letter-spacing: 0.18em;
    margin-right: -0.18em;
    text-align: center;
  }

  .register-account-box .btn {
    width: 100%;
    justify-content: center;
  }
}

.register-bank-hint {
  font-size: 0.8125rem;
  color: #E53935;
}

.register-reconcile-status {
  font-size: 0.9375rem;
  color: var(--color-text);
  margin-bottom: 12px;
}

.register-reconcile-status strong {
  color: var(--color-muted);
  font-weight: 600;
}

/* ==========================================
   共用元件：會員側欄版面（會員中心／報名明細等頁面共用）
   ========================================== */
/* 🔴 左右內距原本寫成 0px，會員中心與報名明細兩頁的內容在 ≤1440px
   直接貼齊視窗邊緣——正是客戶回報的「文字和視窗緊貼」那一類問題。
   ⚠️ max-width 要加兩倍 gutter：max-width 量到 padding 外緣，
      只補 padding 的話內容框會縮成 1392px，在寬螢幕上比其他區塊窄 24px 而對不齊。
      （register、footer、course-detail 都踩過同一個坑，改法一致。） */
.member-center-inner {
  max-width: calc(var(--container-max) + var(--page-gutter) * 2);
  margin: 0 auto;
  padding: 60px var(--page-gutter) 96px;
}

.member-center-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
  align-items: start;
}

@media (min-width: 900px) {
  .member-center-layout {
    grid-template-columns: 280px minmax(0, 1fr);
  }
}

.member-sidebar {
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 24px;
  position: sticky;
  /* 用 --header-stack（含公告列），不是 --header-height */
  top: calc(var(--header-stack) + 24px);
}

/* 🔴 Z3（PM 2026-09-02）：`<900` 取消 sticky，改回 `static`。
   PM 回報「手機版會員專區沒辦法正常看」——768 實測側欄高 459.4px，
   單欄版面下它是「壓在內容上方的一整塊」，sticky 讓它一路黏在畫面上、蓋掉將近半個視窗。

   🔴 判準：`position: sticky` 的前提是「它旁邊還有東西可以捲」。
      版面一旦塌成單欄，同一條 sticky 的語意就從「側欄跟著讀者」
      變成「一塊擋在內容前面的浮層」——**規則沒變，但版面變了，它的意思就變了**。

   🔴 斷點必須是 899，對齊上面 `.member-center-layout` 塌成單欄的 900——
      兩者必須同時翻轉，另取一個數字會出現「已經單欄了但還黏著」的中間地帶。
   🔴 `≥900` 的 sticky 不可以一起改掉：兩欄版面側欄本來就該黏住，那是刻意的。
   ⚠️ `registration-detail.html` 共用同一個側欄（`partials/member-sidebar.html`），
      改這裡兩頁都會吃到，驗收要兩頁都看。 */
@media (max-width: 899px) {
  .member-sidebar {
    position: static;
  }
}

.member-sidebar-profile {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 8px;
}

.member-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  border-radius: 50%;
  background-color: var(--gray-100);
  color: var(--color-muted);
}

.member-avatar svg {
  width: 26px;
  height: 26px;
}

/* 上傳了大頭照時，script.js 會把裡面的圖示換成 <img>。
   ⚠️ overflow: hidden 要下在 .member-avatar 上（圓形裁切），
      但那條規則沒有 overflow——圖片會是方的、蓋出圓圈外。
      這裡補上，並用 object-fit: cover ＋ 預設置中
      （專案規範：構圖在上傳前裁好，CSS 不寫死放大與位移）。 */
.member-avatar {
  overflow: hidden;
}

.member-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.member-sidebar-name {
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--color-heading);
}

.member-sidebar-line-status {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 4px;
  font-size: 0.8125rem;
  color: var(--color-muted);
}

.member-sidebar-line-status.is-linked {
  color: #1F9D6C;
}

.member-sidebar-line-status svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}

.member-sidebar-divider {
  border: none;
  border-top: 1px solid var(--color-border);
  margin: 16px 0;
}

.member-sidebar-nav {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.member-sidebar-link {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  padding: 12px 10px;
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--color-text);
  transition: background-color 0.2s ease-out, color 0.2s ease-out;
  text-decoration: none;
  cursor: pointer;
}

.member-sidebar-link svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

.member-sidebar-link:hover {
  background-color: var(--gray-50);
}

.member-sidebar-link.is-active {
  background-color: var(--gray-50);
  color: var(--color-primary);
}

.member-sidebar-logout {
  color: var(--color-muted);
  font-weight: 600;
}

.member-sidebar-logout:hover {
  color: #D64545;
  background-color: #FDECEC;
}

.member-main {
  display: flex;
  flex-direction: column;
  gap: 24px;
  /* 🔴 Z1（PM 2026-09-02）：`min-width: 0` 必須寫在這裡，不可以留在 `member-center.css`。
     grid 子項的預設 `min-width: auto` ＝ 不可小於自己的 min-content，
     而 `.member-center-layout` 在 <900 是 `grid-template-columns: 1fr`
     （＝`minmax(auto, 1fr)`，那個 auto 就是 min-content 下限）
     → 內容一寬就把整欄撐出視窗，320 寬實測橫向溢出。
     ⚠️ `≥900` 那一段早就是 `minmax(0, 1fr)`，所以桌機一直是好的——只有窄螢幕會壞。

     🔴 為什麼要放在 `style.css` 而不是頁面 CSS：
        `.member-main` 是 `member-center.html` 與 `registration-detail.html` **兩頁共用**的
        （左欄 partial 也共用）。X 軌原本寫在 `member-center.css`，同特異性 (0,1,0)
        靠**來源順序**贏——那等於同一個屬性有兩個來源，而且
        **只對載入那支 CSS 的那一頁生效**：`registration-detail.html` 沒有載入它，
        仍是 `min-width: auto`，320 寬實測溢出 31／48／65px（預設／中／大字級）。
        寫在元件本體才是兩頁共用的單一來源。 */
  min-width: 0;
}

.member-back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-muted);
  margin-bottom: 4px;
  transition: color 0.2s ease-out;
}

.member-back-link svg {
  width: 16px;
  height: 16px;
}

.member-back-link:hover {
  color: var(--color-primary);
}

/* ==========================================
   共用元件：文章／消息清單（本會會務列表、首頁 home-03 共用）
   ========================================== */
.news-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.news-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  padding: 18px 4px;
  /* 🔴 用 `--color-border-strong` 不是 `--color-border`（客戶 2026-08-25 反映太淺）。
     這一列同時服務首頁 home-03 與 `news.html`，兩處都疊在背景圖上。理由見該 token。 */
  border-bottom: 1px dashed var(--color-border-strong);
  transition: opacity 0.2s ease-out;
}

.news-list li:last-child .news-item {
  border-bottom: none;
}

.news-item:hover {
  opacity: 0.75;
}

.news-date {
  flex-shrink: 0;
  font-weight: 600;
  font-size: 1rem;
  color: var(--color-primary);
}

.news-badge {
  flex-shrink: 0;
  padding: 4px 12px;
  border-radius: var(--radius-sm);
  font-size: 1rem;
  font-weight: 400;
  background-color: var(--color-primary);
  color: #FFFFFF;
}

/* M8（客戶 2026-08-17：「手機版時文章分類標籤文字可以改小一點 16px」）。
   🔴 **一定要用 rem 不可以寫死 16px**（PM 2026-08-17 確認）：
      全站字級可切小／中／大（18／20／22px），寫死 px 的話長輩把字調大時
      **只有這個標籤不會跟著變大**，而且不會有人發現。
      0.875rem 在三種字級下分別是 15.75／17.5／19.25px，都在可讀範圍，
      15.75 也就是客戶說的「16px 左右」。
   ⚠️ 桌機維持 1rem（客戶只說手機版）。
   ⚠️ 這是**共用元件**：首頁最新消息區與 news.html 兩處都會變，兩頁都要看。
   ⚠️ 旁邊的 `.news-date` 也是 1rem，**刻意沒有一起縮**：
      實測 375px 下日期與標籤同高、視覺沒有不協調，而客戶只指名標籤。
      要一起縮要先問 PM（見總待辦回報）。 */
@media (max-width: 767px) {
  .news-badge {
    font-size: 0.875rem;
  }
}

/* ══════════════════════════════════════════
   R17（PM 2026-08-25）：分類標籤改用客戶 logo 的四個顏色，**分類多於四個就循環**
   ------------------------------------------
   🔴 **是「循環」不是「對照表」。** class 名用 `is-tone-0`〜`3`（位置），
      不是 `tag-tax`／`tag-aml`（分類名）——PM 明講「之後有更多分類就重複循環」，
      所以 tone 是**算出來的**（`NEWS_CATEGORIES.indexOf(cat) % 4`），
      新增分類完全不必回來改 CSS。
   ⚠️ 這也正是 2026-08-17 移除舊 `NEWS_CATEGORY_TAG_CLASS` 的理由的延續：
      那份對照表把 6 個分類對到 6 個**背景色完全一樣**的 class，白做工。
      現在顏色真的不同了，但仍然**不要把對照表加回來**。
   🔴 `.news-badge` 上仍然保留 `--color-primary` 底色當**預設**：
      `indexOf` 回 -1（分類不在清單裡）時不輸出 tone class，
      標籤退回主色紫底白字——**不會變成一顆沒有顏色的標籤**。
   ⚠️ **順序即顏色**：調整 `NEWS_CATEGORIES` 的排列會讓所有文章換顏色。
      這是循環制的必然，可接受；但改完**一定要重跑 `node tools/gen-detail-pages.js`**，
      否則 72 篇靜態頁的顏色與清單頁對不上（`--check` 會擋）。

   ⚠️ **顏色的語意會被稀釋，PM 已知情**：`--logo-2` 同時是「即將額滿」與「稅務訊息」、
      `--logo-4` 同時是「報名中」與「相關法規」，首頁同一畫面就會同時出現。
      判斷可以接受：兩者形狀、位置、文字都不同（狀態在課程卡片內，分類在消息列日期旁），
      標籤上的文字本來就講明了自己是什麼。
      **若日後覺得混淆，乾淨的解法是把分類標籤改成「淺底＋深色字」的弱化版**
      （分類是資訊、狀態是行動訊號，本來就該有強弱差別），**不是換掉客戶的 logo 色**。

   🔴 **只套用在 `.news-badge`（每則消息前面的標籤），不做頁籤。**
      `news.html` 的分類頁籤與首頁左側的直式分類清單要表達的是
      「**現在選中哪一個**」，再疊一組分類色會把選中訊號洗掉。 */
.news-badge.is-tone-0 { background-color: var(--logo-1); color: #FFFFFF; }
.news-badge.is-tone-1 { background-color: var(--logo-2); color: #FFFFFF; }
.news-badge.is-tone-2 { background-color: var(--logo-3); color: #FFFFFF; }
.news-badge.is-tone-3 { background-color: var(--logo-4); color: #FFFFFF; }

/* ⚠️ 以下是 R17 之前的說明，保留當背景：
   原本這裡列了 tag-member／tag-gov／tag-record／tag-file／tag-tax／tag-aml
   六個 class，但它們的宣告完全一樣。既然分類會隨客戶需求增減，
   每次都要回來加一個 class 名卻不改任何樣式是白做工，
   已改成直接掛在 .news-badge 上，script.js 那份對應表也一併移除。 */

.news-title {
  flex: 1 1 100%;
  min-width: 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-text);
  order: 4;
}

.news-arrow {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  color: var(--purple-300);
  margin-left: auto;
  transition: transform 0.2s ease-out;
}

.news-item:hover .news-arrow {
  transform: translateX(4px);
}

@media (min-width: 640px) {
  .news-title {
    flex: 1;
    order: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* ==========================================
   共用元件：連結卡片
   ------------------------------------------
   原本只在 links.css（相關連結頁）。首頁新增「相關連結」輪播區後兩處都要用，
   而首頁不會載入 links.css，所以 2026-08-11 搬到這裡。
   🔴 是「搬移」不是「複製」——links.css 裡那份已刪掉。
   ⚠️ .link-grid（格狀排列）留在 links.css：那是相關連結頁專屬的版型，
      首頁用的是輪播軌道，兩者不共用。卡片本身才是共用的。
   ========================================== */
.link-card {
  display: block;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background-color: var(--color-surface);
  transition: border-color 0.2s ease-out;
}

.link-card:hover {
  border-color: var(--color-primary);
}

.link-card-cover {
  aspect-ratio: 16 / 10;
  background: linear-gradient(135deg, var(--purple-600), var(--purple-400));
  display: flex;
  align-items: center;
  justify-content: center;
  color: #FFFFFF;
  overflow: hidden;
}

.link-card-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.3s ease-out;
}

.link-card:hover .link-card-cover img {
  transform: scale(1.05);
}

.link-card-cover svg {
  width: 40px;
  height: 40px;
  opacity: 0.85;
}

.link-card-body {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 20px;
}

.link-card-name {
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--color-text);
  line-height: 1.5;
}

.link-card-arrow {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  color: var(--purple-300);
  transition: transform 0.2s ease-out;
}

.link-card:hover .link-card-arrow {
  transform: translate(2px, -2px);
}

/* ==========================================
   共用元件：篩選列（下拉 ＋ 關鍵字搜尋）
   ------------------------------------------
   原本只有 courses.html 有這組（叫 .course-filter*，寫在 courses.css）。
   會員名錄、本會會務、相關連結、下載專區都要同樣的篩選與搜尋，
   所以 2026-08-11 搬到這裡並改成中性名稱，courses.html 一併改用新 class。
   🔴 是「搬移」不是「複製」——courses.css 裡那份已經刪掉，不要讓它變成兩份實作。

   用法（下拉與搜尋框數量可自由增減）：
     <div class="filter-bar"><div class="filter-bar-inner">
       <div class="filter-select-wrap"><select class="filter-select">…</select><svg…箭頭/></div>
       <div class="filter-search"><input …><button aria-label="搜尋"><svg…放大鏡/></button></div>
     </div></div>
   ⚠️ 下拉的箭頭與搜尋的放大鏡是用絕對定位疊在框上，所以 wrap 的 position: relative 不能拿掉。
   ⚠️ .filter-select 有 appearance: none，箭頭一定要自己畫，否則下拉會沒有任何指示。
   ========================================== */
.filter-bar {
  padding: 32px var(--page-gutter) 0;
}

.filter-bar-inner {
  max-width: var(--container-max);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.filter-select-wrap {
  position: relative;
}

.filter-select {
  width: 100%;
  appearance: none;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 12px 40px 12px 16px;
  font-size: 0.9375rem;
  font-family: inherit;
  color: var(--color-text);
}

.filter-select-wrap svg {
  position: absolute;
  top: 50%;
  right: 14px;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  color: var(--color-muted);
  pointer-events: none;
}

.filter-search {
  position: relative;
  flex: 1;
}

.filter-search input {
  width: 100%;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 12px 48px 12px 16px;
  font-size: 0.9375rem;
  font-family: inherit;
  color: var(--color-text);
}

.filter-search input::placeholder {
  color: var(--color-muted);
}

.filter-search button {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  border: none;
  background: none;
  color: var(--color-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color 0.2s ease-out;
}

.filter-search button:hover {
  color: var(--color-primary);
}

.filter-search svg {
  width: 18px;
  height: 18px;
}

@media (min-width: 768px) {
  .filter-bar-inner {
    flex-direction: row;
    align-items: center;
    /* 🔴 M11（客戶 2026-08-17：「768px 時報名課程的搜尋框被擠壓得很小，
       應該要換到下一行才對」）。
       根因不是 courses.css，而是這裡：下拉是固定 `flex: 0 0 200px`，
       報名課程頁有 4 個下拉（狀態／分類／第二層／排序）＝ 800px ＋ 間隙，
       已經超過 768px 扣掉左右間距後的 705px，於是可伸縮的搜尋框被壓到只剩 69px
       （實測值，只放得下「搜」一個字）。

       修法是**讓這一列可以換行**、並給搜尋框一個最小寬度，
       擺不下時它自己掉到下一行。
       🔴 **刻意改共用這條，不是只改 courses.css**：擠壓是「下拉數量 × 固定寬度」
          造成的，任何頁面日後多加一個下拉都會再撞一次。只補 courses.css
          等於把地雷留給下一個人，而且下一個人不會知道要來這裡看。
       ⚠️ 已實測其餘 5 個消費端（news／links／downloads／about 名錄／會員專區課程管理）
          在 768 都只有 0–2 個下拉，這兩行對它們**不產生任何變化**（沒有東西需要換行）。 */
    flex-wrap: wrap;
  }

  .filter-select-wrap {
    flex: 0 0 200px;
  }

  /* 240px 是搜尋框「還能正常輸入」的下限（放得下 placeholder 與右側放大鏡鈕）。
     擺不下就整條換到下一行，而不是被壓縮。 */
  .filter-search {
    flex: 1 1 240px;
  }
}


/* 列表頁用的搜尋列：.filter-bar 原本是報名課程頁的整段篩選區（有自己的上下留白），
   放進已經有分類頁籤的列表頁時上方留白過多，這裡收掉。
   ⚠️ 是在共用元件之外「加一個修飾 class」，不是改共用元件本身——
      courses.html 仍然需要原本的留白。 */
.list-filter-bar {
  padding: 0 0 20px;
}


/* ==========================================
   共用元件：日期區間篩選（.filter-date）
   ------------------------------------------
   起日 ─ 迄日 ─ 清除鈕，放在 .filter-bar 內。消費端：`news.html`、`gallery.html`。

   🔴 **AA1（2026-09-03）由 `news.css` 搬到這裡。** 原本它只服務消息頁，
      活動花絮要用時，施工方為了不重複而暫時在 `gallery.html` 多載了一支 `news.css`
      ——那是權宜之計（載進整頁的 CSS 只為了一組元件），本輪收工時搬成正式共用元件。
      ⚠️ **不要把它複製一份到頁面 CSS**：下面那幾個值是實測 Chrome／Safari
         三種字級對齊出來的，複製後的失敗模式是「有人修好其中一頁的日期欄高度，
         另一頁悄悄歪掉」。

   三段式版面（只用全站認可的 min-width 斷點，沒有 max-width 範圍查詢）：
     ～767   搜尋框與日期組各自一列；日期組再換行成「起一列、迄一列、清除鈕一列」
     768～   日期組收成一列（起 ─ 迄 ─ 清除鈕），仍然自己占一整列
     1024～  日期組回到搜尋框右邊，同一列

   🔴 `.filter-date-input` 的 `border` / `border-radius` / `padding` / `font-size`
      必須與上面 `.filter-search input` 完全一致，否則兩個框在同一列會高度不齊
      （`type="date"` 的原生外觀各瀏覽器差很多，高度只能靠這幾個值對齊，
      **不要另外寫死 `height`**——寫死了在「文字大小 中／大」就會歪）。

   ⚠️ 共用元件 `.filter-bar-inner` 在 768 起是 `flex-direction: row` 且沒有 `flex-wrap`，
      日期組會被硬塞進搜尋框旁邊。**消費端各自在自己的容器上補 `flex-wrap: wrap`**
      （`news.css` 的 `.news-list-inner`、`gallery.css` 的 `.gallery-inner`）
      ——🔴 不要改共用那條，報名課程頁的兩個下拉仍要維持不換行。
   ========================================== */
.filter-date {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* 8px 而不是共用元件的 12px：這一組是「一個控制項」，內部要比外部間距緊 */
  gap: 8px;
  min-width: 0;
  /* 1023px 以下自己占一整列（1024px 起改回 flex: 0 0 auto，見檔尾斷點） */
  flex: 1 1 100%;
}

/* label ＋ input 的配對容器：它自己不換行，所以標籤永遠跟著它的欄位走。
   手機一組占一整列（欄位吃滿寬度好點選），768px 起收成內容寬度。 */
.filter-date-field {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 8px;
  min-width: 0;
  flex: 1 1 100%;
}

.filter-date-label {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--color-text);
  white-space: nowrap;
}

/* 只給輔助科技讀的補字（畫面上不顯示，見 news.html 該處註解）。
   ⚠️ 不要改成 display: none 或 visibility: hidden——那樣輔助科技也讀不到。 */
.filter-date-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.filter-date-input {
  /* flex-basis 用 rem 而不是 px：字級切到中／大時日期文字會變寬，
     用 px 會把 yyyy/mm/dd 擠成 yyy…／裁掉右邊的日曆圖示。 */
  flex: 1 1 9rem;
  min-width: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 12px 12px;
  background-color: var(--color-surface);
  font-family: inherit;
  font-size: 0.9375rem;
  color: var(--color-text);
}

/* 🔴 line-height 必須明寫成 1.1875，不可以留 normal、更不可以寫 1.6（Chrome 實測 2026-08-12）。
      三個控制項的 padding／border／font-size 完全一樣，高度卻不一樣，原因是 line-height 的
      `normal` 依「實際用到的字型」計算，而三者用到的字型不同：

        .filter-search input   內容高 20px → 46px   單行文字框的內在高度只看拉丁主字型（≈1.1875em）
        input[type=date]       內容高 26px → 52px   內部欄位是「年/月/日」，吃到中文字型的較大 metrics
        button（清除區間）      內容高 24px → 50px   line box 由「清除區間」這幾個中文字決定

      1.1875em 就是搜尋框那個 20px 的來源，明寫出來三者才會一致（誤差 ≤0.5px，三種字級都驗過）。
      ⚠️ 不要改成「用 align-items: stretch 讓它們一起變高」——那會把搜尋框從 46 撐到 52，
         報名課程頁與相關連結頁的同一個搜尋框仍是 46，反而變成頁與頁之間不一致。 */
.filter-date-input,
.filter-date-clear {
  line-height: 1.1875;
}

/* 兩件事一起處理，不要分開寫成兩條規則（同一個選擇器分兩處＝下次有人只改一處）：
   1. Chrome 的內層 ::-webkit-datetime-edit 自帶 1px 上下 padding，
      會讓日期欄比搜尋框高 2px。歸零才真的對齊。
   2. Safari 的 date 欄位預設用自己的 intrinsic 寬度、不吃外層 width，
      在 320px 會把整列撐出橫向捲動；min-width: 0 把它拉回一般表單控制項的行為。 */
.filter-date-input::-webkit-datetime-edit,
.filter-date-input::-webkit-datetime-edit-fields-wrapper {
  padding: 0;
  min-width: 0;
}

.filter-date-input::-webkit-date-and-time-value {
  text-align: left;
  min-width: 0;
}

/* ==========================================
   flatpickr（全站日期／時間選擇器，見 site.js 的 window.initDatePicker）
   ------------------------------------------
   使用者 2026-09-12 指定：全站不可用 <input type="date">（Safari 會壞），一律 flatpickr。
   上面 ::-webkit-datetime-edit 那幾條是原生欄位時代留下的，對 flatpickr 生的文字框不起作用、也不衝突。
   這裡只把套件預設的藍色換成本站主色，其餘照套件預設。
   ========================================== */
.flatpickr-input[readonly] {
  cursor: pointer;
  background-color: var(--color-surface);
}

/* 🔴 日期欄位是「可以點的」，不可以長得像唯讀欄位（使用者 2026-09-16：灰底會讓人以為不能按）。
   flatpickr 顯示用的文字框一定帶 `readonly`（allowInput: false 的必然），
   會吃到上面 `.input[readonly]` 的灰底＋灰字＋禁止游標——那組是給「真的不能改」的會員資料欄位用的。
   記號 class 由 site.js 的 initDatePicker 以 altInputClass 掛上。
   ⚠️ 上面那條 `.flatpickr-input[readonly]` 管的是**被藏起來的原始 input**（存 ISO 值），不是這一個。 */
.input.js-datepicker-input[readonly] {
  background-color: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
}

.input.js-datepicker-input[readonly]:focus {
  border-color: var(--color-primary);
}

.flatpickr-calendar {
  font-family: inherit;
  border-radius: var(--radius-md);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
  color: #fff;
}

.flatpickr-day.today {
  border-color: var(--color-primary-soft);
}

.flatpickr-day.today:hover,
.flatpickr-day.today:focus {
  background-color: var(--color-primary-soft);
  border-color: var(--color-primary-soft);
  color: var(--color-heading);
}

.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
  fill: var(--color-primary);
}

/* SweetAlert 彈窗內用 static: true，月曆掛在欄位下方：要占版面而不是浮在上面蓋住下一個欄位 */
.swal-form-body .flatpickr-wrapper {
  display: block;
  width: 100%;
}

.swal-form-body .flatpickr-calendar.static.open {
  position: relative;
  top: 4px;
  margin-bottom: 8px;
}

/* 日曆圖示：原生的灰階圖示在紫色系版面裡偏灰，hover 時給主色回饋。
   🔴 margin／padding 歸零 ＋ height: 1em 也是對齊高度的一部分（Chrome 實測）：
      原生圖示的固定高度比一行文字高，會單獨把日期欄撐高 1px 上下。
      用 1em 而不是固定 px，字級切到中／大時圖示才會跟著放大。 */
.filter-date-input::-webkit-calendar-picker-indicator {
  margin: 0;
  padding: 0;
  height: 1em;
  cursor: pointer;
  opacity: 0.65;
}

.filter-date-input:hover::-webkit-calendar-picker-indicator {
  opacity: 1;
}

/* 起訖之間的破折號：手機兩組上下堆疊時，一條橫線單獨占一行只會讓人困惑，
   所以 767 以下不顯示（它是 aria-hidden 的裝飾，藏掉不影響語意，起／迄兩個標籤仍在）。
   768px 起兩組並排時才出現。 */
.filter-date-sep {
  display: none;
  color: var(--color-muted);
}

/* 清除區間：行為由 script.js 接（清空兩個日期欄位並重新篩選、回第 1 頁）。
   刻意用「有邊框的按鈕」而不是純文字連結——客戶的使用者以長輩為主，
   純文字在一列控制項裡不容易被認出是可以按的。 */
.filter-date-clear {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  padding: 12px 16px;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--color-text);
  white-space: nowrap;
  transition: background-color 0.2s ease-out, color 0.2s ease-out, border-color 0.2s ease-out;
}

/* 全站 hover 標準：金色漸層 ＋ --color-on-gold（🔴 絕對不要白字，
   金底配白字對比只有 2.x）。見 CLAUDE.md 七之二節。 */
.filter-date-clear:hover,
.filter-date-clear:focus-visible {
  border-color: transparent;
  background-image: linear-gradient(135deg, var(--gold-from), var(--gold-to));
  color: var(--color-on-gold);
}

/* ⚠️ 原本這裡有一條 `.filter-date-clear[hidden] { display: none }`，2026-08-13 刪除：
   已由 `style.css` Base Reset 的全站 `[hidden][hidden]` 涵蓋（見 CLAUDE.md 五節 2-2）。 */

@media (min-width: 768px) {
  /* 日期組收成一列（起 ─ 迄 ─ 清除鈕），但仍然自己占一整列
     （flex-basis 還是 100%，1024px 才改） */
  .filter-date {
    flex-wrap: nowrap;
  }

  .filter-date-sep {
    display: block;
  }

  .filter-date-field {
    flex: 0 1 auto;
  }

  /* 🔴 flex-shrink 為 0：日期欄不像文字會斷行，被壓縮就是把「2026/01/31」
        連同日曆圖示裁掉一半，而且不會報錯。9.5rem 是實測填入完整日期後仍不裁切的寬度。 */
  .filter-date-input {
    flex: 0 0 9.5rem;
  }
}

@media (min-width: 1024px) {
  /* 回到搜尋框右邊、同一列。搜尋框（.filter-search 有 flex: 1）吃剩餘寬度。 */
  .filter-date {
    flex: 0 0 auto;
  }
}

/* ==========================================
   共用元件：橫向可捲動的頁籤列（.scroll-tabs）
   ------------------------------------------
   🔴 客戶回報：裝置縮小時「捲軸會壓到切換按鈕」。

   實測原因（不是猜的）：
     這台 Chrome 用的是**佔空間的傳統捲軸**，水平捲軸吃掉 15px。
     而各處頁籤列自己寫的 padding-bottom 只留 4〜6px
     （about.css 甚至寫了註解「捲軸不要壓到按鈕文字」，但值不夠）。
     15 − 6 ＝ 9px，正好就是截圖裡壓住按鈕的那一截。

   🔴 為什麼不是「把 padding 加大一點」就好：
     捲軸厚度是使用者的系統設定，macOS 預設是 overlay（0px）、
     開了「總是顯示捲軸」或在 Windows 上是 15px 甚至更厚。
     **只要厚度未知，任何 padding 值都只是在某一台機器上剛好對。**
   → 所以先用 ::-webkit-scrollbar 把厚度講死成 8px（一旦寫了尺寸，
     Chrome 就不再用 overlay 捲軸，改成佔空間且高度確定的那種），
     再留 8px ＋ 4px 間隙。這樣兩種系統設定下的結果都一樣。

   🔴 為什麼不乾脆把捲軸藏起來（scrollbar-width: none）：
     這個站的使用者多為長輩。捲軸是「這一列還有東西，可以往右滑」的唯一提示，
     藏掉之後看不見的分類就等於不存在。
     ⚠️ header-top 工具列是刻意的例外——那一列項目短、四個字一組，
        而且它同時被 flex-wrap 處理過，藏捲軸不會讓人漏掉內容。

   用法：容器加 .scroll-tabs，子項不必再寫 flex-shrink: 0。
   ========================================== */
.scroll-tabs {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  /* overflow-y: hidden 是必要的：只設 x 的話 y 會變成 auto，
     頁籤的 focus 外框會多出一條垂直捲軸 */
  overflow-y: hidden;
  /* min-width: 0 不能省：這一列常常是 grid／flex 的子項，
     預設 min-width: auto 會被內容撐開，變成整頁橫向溢出而不是內部捲動 */
  min-width: 0;
  /* proximity 而不是 mandatory：mandatory 會在使用者想停在兩顆之間時硬拉回去 */
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 12px;
}

.scroll-tabs>* {
  flex: 0 0 auto;
  scroll-snap-align: start;
}

/* 把捲軸厚度講死成 8px。⚠️ 這一組規則的作用不只是「變好看」，
   而是讓上面的 padding-bottom 有一個確定的對象可以算。 */
.scroll-tabs::-webkit-scrollbar {
  height: 8px;
}

.scroll-tabs::-webkit-scrollbar-track {
  background-color: transparent;
}

.scroll-tabs::-webkit-scrollbar-thumb {
  background-color: var(--brand-purple-soft);
  border-radius: 999px;
}

/* Firefox 不支援 ::-webkit-scrollbar，用它自己的兩個屬性。
   thin 實測佔 11px，比 8px 多 3px，落在 12px 的 padding 內，不會壓到按鈕。 */
@supports not selector(::-webkit-scrollbar) {
  .scroll-tabs {
    scrollbar-width: thin;
    scrollbar-color: var(--brand-purple-soft) transparent;
  }
}

/* ==========================================
   共用元件：分頁（本會會務、活動相簿等列表頁共用）
   ========================================== */
/* flex-wrap 是必要的，不是保險：分頁按鈕每顆至少 36px ＋ 8px 間距，
   頁數一多（7 顆就要 300px）在 320px 螢幕上一行放不下，會讓整頁橫向溢出。
   換行後寬螢幕的外觀完全不變，只有窄螢幕會折成第二行。 */
.pagination {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 8px;
}

/* 🔴 上下頁鈕與數字鈕必須是同一個尺寸。
   客戶指出兩者大小不一致，原因是上下頁鈕裡面放的是 lucide SVG（預設 24×24），
   數字鈕放的是文字——沒有固定尺寸與置中時，兩種內容會撐出不同的框。
   所以這裡把 .pagination-btn 定義成「固定 40×40 的置中盒子」，
   不論裡面是數字還是圖示都一樣大，並限制 svg 尺寸。 */
.pagination-btn {
  /* 🔴 尺寸一律 rem，不可以寫 px。
     客戶指出「上下頁按鈕尺寸不對」，原因是箭頭鈕裡放 24px 的 lucide SVG、
     數字鈕放文字，兩者自然算出不同大小——所以這裡把框的尺寸講死，
     再用 inline-flex 置中，箭頭與數字就一定同大小。

     ⚠️ 但「講死」不能用 px：右上角的文字放大切換是改 <html> 的 font-size，
        寫 height: 40px 的話按「大」時字變大、框不變，字會被框壓住。
        （我第一版就是寫 40px，實測 fs-lg 下字 20.6px 塞在 40px 的框裡才發現。）
        2.25rem 在預設 18px 根字級下 ＝ 40.5px，按「大」時會自己長到 49.5px。

     min-width ＋ padding 的分工：頁數變成兩位數（10、11）時靠 padding 變寬，
     高度不受影響，所以整排按鈕的高度永遠齊平。 */
  min-width: 2.25rem;
  min-height: 2.25rem;
  padding: 0 0.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--color-muted);
  transition: background-color 0.2s ease-out, color 0.2s ease-out, border-color 0.2s ease-out;
}

/* 上下頁鈕裡是 lucide SVG，不限尺寸的話預設 24px 會把框撐大。
   同樣用 rem：不然按「大」時數字變大、箭頭不變，兩種鈕又會看起來不一樣。 */
.pagination-btn svg {
  width: 1rem;
  height: 1rem;
}

/* M9（2026-08-17）：頁數多時的省略號。
   🔴 它是 `<span>` 不是 `<button>`——做成按鈕會讓人以為點得下去，而它沒有目標頁。
      所以這裡刻意**不給**邊框、底色與 hover，讓它一眼就跟數字鈕不一樣。
   ⚠️ 高度用與 .pagination-btn 相同的 min-height，否則它會比數字鈕矮，
      整排按鈕的中線就歪掉（.pagination 是 align-items: center，矮的那個會浮在中間）。 */
.pagination-ellipsis {
  min-width: 1.5rem;
  min-height: 2.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--color-muted);
  user-select: none;
}

/* 連結版分頁（活動花絮）：<a> 不可 disabled，首／末頁上下頁鈕是 <span class="is-disabled">，外觀與 :disabled 相同 */
a.pagination-btn {
  text-decoration: none;
}

.pagination-btn:disabled,
.pagination-btn.is-disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* hover 沿用全站金色標準（見七之二節）：只變文字顏色長輩看不出來。
   disabled 的按鈕不要有 hover 反應，否則會以為點得動。 */
.pagination-btn:hover:not(:disabled):not(.is-disabled),
.pagination-btn:focus-visible:not(:disabled):not(.is-disabled) {
  background-image: linear-gradient(135deg, var(--gold-from), var(--gold-to));
  border-color: var(--gold-from);
  color: var(--color-on-gold);
}

.pagination-btn.is-active {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
  color: #FFFFFF;
}

/* ==========================================
   共用元件：分類頁籤（藥丸狀，本會會務列表頁使用）
   ========================================== */
.pill-tabs {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
}

.pill-tab {
  border: 1px solid var(--color-border);
  background-color: var(--color-surface);
  color: var(--color-text);
  border-radius: var(--radius-sm);
  padding: 10px 24px;
  font-size: 0.9375rem;
  font-weight: 600;
  transition: background-color 0.2s ease-out, color 0.2s ease-out, border-color 0.2s ease-out;
}

.pill-tab:hover {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.pill-tab.is-active {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
  color: #FFFFFF;
}

/* ==========================================
   共用元件：狀態標籤（會員中心課程管理／訂單狀態）
   ========================================== */
.status-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 12px;
  border-radius: var(--radius-sm);
  font-size: 0.8125rem;
  font-weight: 600;
}

.status-badge.is-paid {
  background-color: var(--color-primary);
  color: #FFFFFF;
}

.status-badge.is-unpaid {
  background-color: #D64545;
  color: #FFFFFF;
}

.status-badge.is-pending {
  background-color: var(--purple-400);
  color: #FFFFFF;
}

/* 待確認金額（price_pending，2026-09-14 新增）：費用別金額未定，還沒到「未付款」的階段
   （不知道要付多少，談不上未付），沿用課程卡片「即將額滿」同一組警示色，語意都是「需要注意」。 */
.status-badge.is-tbd {
  background-color: var(--status-nearfull);
  color: var(--status-nearfull-text);
}

/* 取消（J3，2026-08-14 新增的第 5 個狀態）。
   ⚠️ 刻意用灰色而不是紅色：紅色在這組標籤裡已經是「未付款」＝**要你去處理**，
      而「取消」是已經結束、不需要動作的狀態。兩個都紅會讓人分不出哪個要處理。
   ⚠️ 規格原本寫「在 member-center.css 補對應色」——實際上這組標籤是
      style.css 的共用元件（報名明細頁也在用），補在這裡才是單一來源。 */
.status-badge.is-cancelled {
  background-color: var(--gray-600);
  color: #FFFFFF;
}

/* ==========================================
   會員登入彈窗（SweetAlert2 彈窗內容，全站共用，依 design/彈出登入.png）
   ⚠️ `design/` 已於 2026-08-18 移除；取回原圖：`git show 4cb9a8f:design/彈出登入.png`
   ========================================== */
.swal-form-popup {
  border-radius: 20px;
  padding: 24px 24px 32px;
}

.swal-form-title.swal2-title {
  color: var(--color-heading);
}

.swal-form-desc {
  margin: 0 0 24px;
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--color-muted);
  text-align: center;
}

/* 登入／註冊彈窗：欄位、checkbox、LINE 按鈕、送出按鈕全部包在同一個直向 flex 容器裡，
   靠 align-items: stretch 讓每一個子元素自動對齊到同樣寬度，不必各自猜測 100% 的基準是誰 */
.swal-form-body {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: 100%;
}

.swal-form-field {
  text-align: left;
  margin-bottom: 16px;
}

.swal-form-label {
  display: block;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-text);
  margin-bottom: 8px;
}

/* 必填星號。與一般表單的 .form-label span（:1830）用同一個色，
   彈窗與頁面表單的必填標記才會一致。
   ⚠️ #E53935 目前是寫死的色值、不是 token，所以這裡是**第二份**——
      要換必填色的話兩處都要改（:1830 與這裡）。 */
.swal-form-label span {
  color: #E53935;
}

/* 彈窗內的行內提示（例如「已重新發送驗證碼」）。
   🔴 為什麼不用 notify()：notify() 也是 SweetAlert2，而 SweetAlert2 同時只允許
      一個實例——在彈窗開著時呼叫 notify() 會把那個彈窗關掉。
      2026-08-12 忘記密碼流程就是這樣踩到的（第二步整步消失）。 */
.swal-form-note {
  margin: 8px 0 0;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--color-muted);
  text-align: center;
}

/* 登入／註冊彈窗的欄位直接沿用全站 .input 樣式（見上方），不使用 SweetAlert2 內建的 .swal2-input，
   避免和 SweetAlert2 CDN 動態載入的樣式表打架 */

.swal-form-input.swal2-input {
  width: 100%;
  height: auto;
  margin: 0;
  padding: 12px 16px;
  font-size: 0.9375rem;
  border-radius: var(--radius-sm);
  box-shadow: none;
  border: 1px solid var(--color-border);
}

.swal-form-input.swal2-input:focus {
  border-color: var(--color-primary);
  box-shadow: none;
}

.login-modal-remember {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
  color: var(--color-text);
  margin: 4px 0 20px;
  cursor: pointer;
}

.login-modal-remember input {
  width: 16px;
  height: 16px;
  accent-color: var(--color-primary);
}

/* 共用元件：登入方式分隔線（AA3／AA4，客戶 2026-09-03）
   「一般登入／註冊」與「LINE 登入／註冊」之間的帶字分隔線。
   🔴 **AA3（登入彈窗）與 AA4（註冊彈窗）共用這一份**——兩處各寫一份就是分叉，
      日後改分隔文字（現在是「或」）只會改到一邊。
   ⚠️ 文字刻意只有一個「或」，不用「其他登入方式」這類長句：
      客戶提出這一項的理由是長輩分不出要按哪一顆，要的是快速分辨、不是說明。
   ⚠️ --color-muted 在這裡是**正確用法**（次要標示，見 X3 的 B 類判準），
      不要因為「全站在減少 --color-muted」就把它改成內文色。
   ⚠️ 兩側的線用 ::before／::after 各佔一半剩餘寬度，所以文字永遠置中、
      兩端自動對齊容器（.swal-form-body 是 align-items: stretch）。 */
.auth-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 20px 0 12px;
  font-size: 0.8125rem;
  color: var(--color-muted);
}

.auth-divider::before,
.auth-divider::after {
  content: '';
  flex: 1 1 0;
  min-width: 0;
  height: 1px;
  background-color: var(--color-border);
}

/* 驗證碼畫面的兩顆按鈕（AA5，客戶 2026-09-03）：原本緊貼在一起。
   🔴 **間距由容器提供**（本專案規矩：間距只有一個來源、由容器給）——
      不要對那兩顆按鈕下 margin，它們在別處也有消費端
      （.swal-form-submit-btn 在登入／註冊／忘記密碼的每一步都在用）。
   ⚠️ 兩個消費端：註冊第二步（完成註冊＋重新寄送）與忘記密碼第二步（驗證＋重新寄送）。
      只修一處等於沒修完。 */
.swal-form-actions-stack {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* 彈窗內的次要按鈕（AA6，客戶 2026-09-03）：兩顆「重新寄送」。
   🔴 它們原本掛的是 .login-modal-line-btn ＝ **LINE 綠色按鈕的樣式**，
      但重新寄送與 LINE 完全無關。AA3／AA4 的用意正是「用顏色區分一般方式與 LINE 方式」，
      留著一顆綠色的「重新寄送」會讓長輩以為那也是 LINE 功能，與客戶的原意直接矛盾。
   🔴 直接沿用全站的 .btn ＋ .btn-outline，不要另寫一套次要按鈕——
      這樣 .btn:disabled（含不可省的 background-image: none）自動生效，
      倒數中滑過去才不會變成金色、看起來像可以按。
   ⚠️ 這裡只做兩件事：撐滿寬度、讓總高與隔壁的 .swal-form-submit-btn 一致（48px）。
      🔴 內距是 **11px 不是 12px**：.btn 帶 1px 邊框而 .swal-form-submit-btn 是 border: none，
         照抄 12px 的話兩顆疊在一起會差 2px（實測 48 vs 50），一高一矮。 */
.swal-form-secondary-btn.btn {
  width: 100%;
  padding: 11px 24px;
}

/* 🔴 .login-modal-line-btn 現在**只服務真正的 LINE 按鈕**（登入彈窗 1 顆、註冊彈窗 1 顆）。
   AA6 之前那兩顆「重新寄送」也掛著它——不要再把非 LINE 的按鈕掛上來。
   ⚠️ 全站 LINE 綠底一律白字（對比 2.26，客戶已知並接受，見 CLAUDE.md 元件表）。 */
.login-modal-line-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 44px;
  /* AA3／AA4 之後這顆一律是 .swal-form-body 的最後一個元素，上方的間距由
     .auth-divider 的 margin 提供，所以自己不再帶 margin（間距由容器／分隔線給一份就好）。 */
  margin: 0;
  padding: 10px 24px;
  border: none;
  border-radius: var(--radius-sm);
  background-color: #06C755;
  color: #FFFFFF;
  font-size: 0.9375rem;
  font-weight: 600;
  font-family: inherit;
  transition: background-color 0.2s ease-out;
}

.login-modal-line-btn:hover {
  background-color: #05B34C;
}

.login-modal-line-btn svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* .swal-form-actions / .swal-form-confirm-btn：回報匯款、更改密碼等雙按鈕彈窗仍沿用 SweetAlert2 內建按鈕 */
.swal-form-actions {
  margin-top: 4px;
}

.swal-form-confirm-btn.swal2-confirm {
  margin: 0;
  border-radius: var(--radius-sm);
  padding: 12px 24px;
}

/* 登入／註冊彈窗：不使用 SweetAlert2 內建的按鈕與 footer，改用自訂元素，避免樣式互相干擾 */
.swal-form-submit-btn {
  display: block;
  width: 100%;
  min-height: 44px;
  margin: 10px 0 0;
  padding: 12px 24px;
  border: none;
  border-radius: var(--radius-sm);
  background-color: var(--color-primary);
  color: #FFFFFF;
  font-size: 0.9375rem;
  font-weight: 600;
  font-family: inherit;
  text-align: center;
  transition: background-color 0.2s ease-out;
}

.swal-form-submit-btn:hover {
  background-color: var(--color-primary-hover);
}

/* 更改密碼彈窗沒有下方連結區塊（.swal-form-links），單獨多留一點按鈕到彈窗底部的空間 */
#pw-submit-btn {
  margin-bottom: 12px;
}

.swal-form-links {
  margin: 24px 0 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.swal-form-links-text {
  color: var(--color-muted);
  font-size: 0.8125rem;
}

.swal-form-links-text a {
  color: var(--color-primary);
  font-weight: 600;
  margin-left: 2px;
}

.swal-form-links a {
  color: var(--color-muted);
  font-size: 0.8125rem;
  transition: color 0.2s ease-out;
}

.swal-form-links-text a:hover,
.swal-form-links a:hover {
  color: var(--color-primary-hover);
}

/* 舊的共用燈箱樣式（.lightbox-popup／.lightbox-img／.lightbox-nav）已於
   2026-08-12 全部移除：燈箱改成 js/lightbox.js ＋ css/lightbox.css 的獨立元件，
   同時支援輪播與縮放（原本是兩套：這裡只能輪播、about.js 那套只能縮放）。
   ⚠️ 那些 class 在全站已無消費端，留著就是隱形分叉——
      下一個人會以為燈箱樣式在 style.css 裡，改了卻沒有任何效果。
   🔴 舊版有一個 !important 疊在 SweetAlert2 的 popup 上（因為要拆掉它的白底卡片），
      新版自己蓋 overlay，不必再和別人的樣式打架，所以 !important 一併消失。 */

/* ==========================================
   AOS 進場動畫補強（全站每個區塊、含頁首頁尾都掛 data-aos，使用者 2026-09-18）
   ------------------------------------------
   🔴 動畫結束停在 transform: none，不用 aos.css 預設的 translateZ(0)。
      translateZ(0) 會讓元素**永久**成為堆疊環境與 position: fixed 的包含區塊：
      頁首主導覽的下拉選單會被後面的公告列蓋住、區塊裡的 fixed 浮層會跟著區塊跑，而且不會報錯。
      權重與 aos.css 的 .aos-animate 規則同為 0,3,0，靠本檔排在 aos.css 之後勝出。
   ⚠️ 副作用：自己有 hover 位移的元素（例如 .course-card）掛了 data-aos 之後 hover 會被蓋掉，
      要在該元件補一條 [data-aos].aos-animate:hover 的規則。新掛 data-aos 時優先掛在外層容器，
      不要掛在按鈕、卡片這種本身有 hover transform 的元素上。
   ========================================== */
[data-aos][data-aos].aos-animate {
  transform: none;
}

/* 🔴 延遲補齊（2026-09-19 抓到）：aos.css 只定義 50 的倍數（50、100、150…3000），
   data-aos-delay="60"／"80"／"120"／"280" 這種值**沒有對應規則＝延遲 0**，而且不報錯——
   全站「依序出現」的卡片、清單原本幾乎都是同時出現，看不出時間差。
   這裡補上 10～1500ms 每 10ms 一條（50 的倍數 aos.css 已經有，跳過）；權重與 aos.css 相同，只作用在 .aos-animate（播放時）。 */
[data-aos][data-aos][data-aos-delay="10"].aos-animate { transition-delay: 10ms; }
[data-aos][data-aos][data-aos-delay="20"].aos-animate { transition-delay: 20ms; }
[data-aos][data-aos][data-aos-delay="30"].aos-animate { transition-delay: 30ms; }
[data-aos][data-aos][data-aos-delay="40"].aos-animate { transition-delay: 40ms; }
[data-aos][data-aos][data-aos-delay="60"].aos-animate { transition-delay: 60ms; }
[data-aos][data-aos][data-aos-delay="70"].aos-animate { transition-delay: 70ms; }
[data-aos][data-aos][data-aos-delay="80"].aos-animate { transition-delay: 80ms; }
[data-aos][data-aos][data-aos-delay="90"].aos-animate { transition-delay: 90ms; }
[data-aos][data-aos][data-aos-delay="110"].aos-animate { transition-delay: 110ms; }
[data-aos][data-aos][data-aos-delay="120"].aos-animate { transition-delay: 120ms; }
[data-aos][data-aos][data-aos-delay="130"].aos-animate { transition-delay: 130ms; }
[data-aos][data-aos][data-aos-delay="140"].aos-animate { transition-delay: 140ms; }
[data-aos][data-aos][data-aos-delay="160"].aos-animate { transition-delay: 160ms; }
[data-aos][data-aos][data-aos-delay="170"].aos-animate { transition-delay: 170ms; }
[data-aos][data-aos][data-aos-delay="180"].aos-animate { transition-delay: 180ms; }
[data-aos][data-aos][data-aos-delay="190"].aos-animate { transition-delay: 190ms; }
[data-aos][data-aos][data-aos-delay="210"].aos-animate { transition-delay: 210ms; }
[data-aos][data-aos][data-aos-delay="220"].aos-animate { transition-delay: 220ms; }
[data-aos][data-aos][data-aos-delay="230"].aos-animate { transition-delay: 230ms; }
[data-aos][data-aos][data-aos-delay="240"].aos-animate { transition-delay: 240ms; }
[data-aos][data-aos][data-aos-delay="260"].aos-animate { transition-delay: 260ms; }
[data-aos][data-aos][data-aos-delay="270"].aos-animate { transition-delay: 270ms; }
[data-aos][data-aos][data-aos-delay="280"].aos-animate { transition-delay: 280ms; }
[data-aos][data-aos][data-aos-delay="290"].aos-animate { transition-delay: 290ms; }
[data-aos][data-aos][data-aos-delay="310"].aos-animate { transition-delay: 310ms; }
[data-aos][data-aos][data-aos-delay="320"].aos-animate { transition-delay: 320ms; }
[data-aos][data-aos][data-aos-delay="330"].aos-animate { transition-delay: 330ms; }
[data-aos][data-aos][data-aos-delay="340"].aos-animate { transition-delay: 340ms; }
[data-aos][data-aos][data-aos-delay="360"].aos-animate { transition-delay: 360ms; }
[data-aos][data-aos][data-aos-delay="370"].aos-animate { transition-delay: 370ms; }
[data-aos][data-aos][data-aos-delay="380"].aos-animate { transition-delay: 380ms; }
[data-aos][data-aos][data-aos-delay="390"].aos-animate { transition-delay: 390ms; }
[data-aos][data-aos][data-aos-delay="410"].aos-animate { transition-delay: 410ms; }
[data-aos][data-aos][data-aos-delay="420"].aos-animate { transition-delay: 420ms; }
[data-aos][data-aos][data-aos-delay="430"].aos-animate { transition-delay: 430ms; }
[data-aos][data-aos][data-aos-delay="440"].aos-animate { transition-delay: 440ms; }
[data-aos][data-aos][data-aos-delay="460"].aos-animate { transition-delay: 460ms; }
[data-aos][data-aos][data-aos-delay="470"].aos-animate { transition-delay: 470ms; }
[data-aos][data-aos][data-aos-delay="480"].aos-animate { transition-delay: 480ms; }
[data-aos][data-aos][data-aos-delay="490"].aos-animate { transition-delay: 490ms; }
[data-aos][data-aos][data-aos-delay="510"].aos-animate { transition-delay: 510ms; }
[data-aos][data-aos][data-aos-delay="520"].aos-animate { transition-delay: 520ms; }
[data-aos][data-aos][data-aos-delay="530"].aos-animate { transition-delay: 530ms; }
[data-aos][data-aos][data-aos-delay="540"].aos-animate { transition-delay: 540ms; }
[data-aos][data-aos][data-aos-delay="560"].aos-animate { transition-delay: 560ms; }
[data-aos][data-aos][data-aos-delay="570"].aos-animate { transition-delay: 570ms; }
[data-aos][data-aos][data-aos-delay="580"].aos-animate { transition-delay: 580ms; }
[data-aos][data-aos][data-aos-delay="590"].aos-animate { transition-delay: 590ms; }
[data-aos][data-aos][data-aos-delay="610"].aos-animate { transition-delay: 610ms; }
[data-aos][data-aos][data-aos-delay="620"].aos-animate { transition-delay: 620ms; }
[data-aos][data-aos][data-aos-delay="630"].aos-animate { transition-delay: 630ms; }
[data-aos][data-aos][data-aos-delay="640"].aos-animate { transition-delay: 640ms; }
[data-aos][data-aos][data-aos-delay="660"].aos-animate { transition-delay: 660ms; }
[data-aos][data-aos][data-aos-delay="670"].aos-animate { transition-delay: 670ms; }
[data-aos][data-aos][data-aos-delay="680"].aos-animate { transition-delay: 680ms; }
[data-aos][data-aos][data-aos-delay="690"].aos-animate { transition-delay: 690ms; }
[data-aos][data-aos][data-aos-delay="710"].aos-animate { transition-delay: 710ms; }
[data-aos][data-aos][data-aos-delay="720"].aos-animate { transition-delay: 720ms; }
[data-aos][data-aos][data-aos-delay="730"].aos-animate { transition-delay: 730ms; }
[data-aos][data-aos][data-aos-delay="740"].aos-animate { transition-delay: 740ms; }
[data-aos][data-aos][data-aos-delay="760"].aos-animate { transition-delay: 760ms; }
[data-aos][data-aos][data-aos-delay="770"].aos-animate { transition-delay: 770ms; }
[data-aos][data-aos][data-aos-delay="780"].aos-animate { transition-delay: 780ms; }
[data-aos][data-aos][data-aos-delay="790"].aos-animate { transition-delay: 790ms; }
[data-aos][data-aos][data-aos-delay="810"].aos-animate { transition-delay: 810ms; }
[data-aos][data-aos][data-aos-delay="820"].aos-animate { transition-delay: 820ms; }
[data-aos][data-aos][data-aos-delay="830"].aos-animate { transition-delay: 830ms; }
[data-aos][data-aos][data-aos-delay="840"].aos-animate { transition-delay: 840ms; }
[data-aos][data-aos][data-aos-delay="860"].aos-animate { transition-delay: 860ms; }
[data-aos][data-aos][data-aos-delay="870"].aos-animate { transition-delay: 870ms; }
[data-aos][data-aos][data-aos-delay="880"].aos-animate { transition-delay: 880ms; }
[data-aos][data-aos][data-aos-delay="890"].aos-animate { transition-delay: 890ms; }
[data-aos][data-aos][data-aos-delay="910"].aos-animate { transition-delay: 910ms; }
[data-aos][data-aos][data-aos-delay="920"].aos-animate { transition-delay: 920ms; }
[data-aos][data-aos][data-aos-delay="930"].aos-animate { transition-delay: 930ms; }
[data-aos][data-aos][data-aos-delay="940"].aos-animate { transition-delay: 940ms; }
[data-aos][data-aos][data-aos-delay="960"].aos-animate { transition-delay: 960ms; }
[data-aos][data-aos][data-aos-delay="970"].aos-animate { transition-delay: 970ms; }
[data-aos][data-aos][data-aos-delay="980"].aos-animate { transition-delay: 980ms; }
[data-aos][data-aos][data-aos-delay="990"].aos-animate { transition-delay: 990ms; }
[data-aos][data-aos][data-aos-delay="1010"].aos-animate { transition-delay: 1010ms; }
[data-aos][data-aos][data-aos-delay="1020"].aos-animate { transition-delay: 1020ms; }
[data-aos][data-aos][data-aos-delay="1030"].aos-animate { transition-delay: 1030ms; }
[data-aos][data-aos][data-aos-delay="1040"].aos-animate { transition-delay: 1040ms; }
[data-aos][data-aos][data-aos-delay="1060"].aos-animate { transition-delay: 1060ms; }
[data-aos][data-aos][data-aos-delay="1070"].aos-animate { transition-delay: 1070ms; }
[data-aos][data-aos][data-aos-delay="1080"].aos-animate { transition-delay: 1080ms; }
[data-aos][data-aos][data-aos-delay="1090"].aos-animate { transition-delay: 1090ms; }
[data-aos][data-aos][data-aos-delay="1110"].aos-animate { transition-delay: 1110ms; }
[data-aos][data-aos][data-aos-delay="1120"].aos-animate { transition-delay: 1120ms; }
[data-aos][data-aos][data-aos-delay="1130"].aos-animate { transition-delay: 1130ms; }
[data-aos][data-aos][data-aos-delay="1140"].aos-animate { transition-delay: 1140ms; }
[data-aos][data-aos][data-aos-delay="1160"].aos-animate { transition-delay: 1160ms; }
[data-aos][data-aos][data-aos-delay="1170"].aos-animate { transition-delay: 1170ms; }
[data-aos][data-aos][data-aos-delay="1180"].aos-animate { transition-delay: 1180ms; }
[data-aos][data-aos][data-aos-delay="1190"].aos-animate { transition-delay: 1190ms; }
[data-aos][data-aos][data-aos-delay="1210"].aos-animate { transition-delay: 1210ms; }
[data-aos][data-aos][data-aos-delay="1220"].aos-animate { transition-delay: 1220ms; }
[data-aos][data-aos][data-aos-delay="1230"].aos-animate { transition-delay: 1230ms; }
[data-aos][data-aos][data-aos-delay="1240"].aos-animate { transition-delay: 1240ms; }
[data-aos][data-aos][data-aos-delay="1260"].aos-animate { transition-delay: 1260ms; }
[data-aos][data-aos][data-aos-delay="1270"].aos-animate { transition-delay: 1270ms; }
[data-aos][data-aos][data-aos-delay="1280"].aos-animate { transition-delay: 1280ms; }
[data-aos][data-aos][data-aos-delay="1290"].aos-animate { transition-delay: 1290ms; }
[data-aos][data-aos][data-aos-delay="1310"].aos-animate { transition-delay: 1310ms; }
[data-aos][data-aos][data-aos-delay="1320"].aos-animate { transition-delay: 1320ms; }
[data-aos][data-aos][data-aos-delay="1330"].aos-animate { transition-delay: 1330ms; }
[data-aos][data-aos][data-aos-delay="1340"].aos-animate { transition-delay: 1340ms; }
[data-aos][data-aos][data-aos-delay="1360"].aos-animate { transition-delay: 1360ms; }
[data-aos][data-aos][data-aos-delay="1370"].aos-animate { transition-delay: 1370ms; }
[data-aos][data-aos][data-aos-delay="1380"].aos-animate { transition-delay: 1380ms; }
[data-aos][data-aos][data-aos-delay="1390"].aos-animate { transition-delay: 1390ms; }
[data-aos][data-aos][data-aos-delay="1410"].aos-animate { transition-delay: 1410ms; }
[data-aos][data-aos][data-aos-delay="1420"].aos-animate { transition-delay: 1420ms; }
[data-aos][data-aos][data-aos-delay="1430"].aos-animate { transition-delay: 1430ms; }
[data-aos][data-aos][data-aos-delay="1440"].aos-animate { transition-delay: 1440ms; }
[data-aos][data-aos][data-aos-delay="1460"].aos-animate { transition-delay: 1460ms; }
[data-aos][data-aos][data-aos-delay="1470"].aos-animate { transition-delay: 1470ms; }
[data-aos][data-aos][data-aos-delay="1480"].aos-animate { transition-delay: 1480ms; }
[data-aos][data-aos][data-aos-delay="1490"].aos-animate { transition-delay: 1490ms; }

/* 緩動曲線：aos.css 2.3.4 的 ease-out-quart 對應到的其實是 quad 的曲線（cubic-bezier(.25,.46,.45,.94)），
   尾段不夠長、看起來比較硬。這裡換成真正尾段拉長的減速曲線（權重 0,2,1 與 aos.css 的 body 規則相同，靠排在後面勝出；
   個別元素用 data-aos-easing 指定的（例如 ease-out-back 彈性）權重 0,3,0，仍然以它為準）。 */
body [data-aos][data-aos] {
  transition-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
}

/* 位移距離：aos.css 預設 100px，每個區塊都往上飄 100px 很晃、很廉價（使用者 2026-09-19），全站縮成 28px。
   權重 0,1,1 壓過 aos.css 的 0,1,0，但低於下面頁首／頁尾的 0,2,0 與 .aos-animate 的 0,3,0。 */
html [data-aos="fade-up"] {
  transform: translate3d(0, 28px, 0);
}

html [data-aos="fade-down"] {
  transform: translate3d(0, -28px, 0);
}

/* 頁首（使用者 2026-09-19：要華麗、要有時間差）：各項目依序落下／滑入，時間軸見 partials/header.php。
   頁首列只有 40～90px 高，落下 18px、LOGO 從左 32px 滑入；按鈕用 AOS 預設的 zoom-in（0.6 倍）搭 ease-out-back 彈出。
   ⚠️ LOGO 從左邊進場（負位移）不會撐出橫向捲軸；不要改成從右邊進場。 */
.site-header [data-aos="fade-down"] {
  transform: translate3d(0, -18px, 0);
}

.site-header [data-aos="fade-right"] {
  transform: translate3d(-32px, 0, 0);
}

/* 頁尾：位移縮成 20px，並把頁尾裁切起來。
   動畫開始前元素停在往下位移的狀態，版權列又貼著頁面最底端，位移會把整頁的可捲動高度撐長——
   捲到底會先看到一段空白、動畫跑完再縮回去，畫面跳一下。
   ⚠️ 用 clip 不用 hidden：hidden 會讓頁尾變成捲動容器；不支援 clip 的舊瀏覽器只是回到會多 20px 的狀態。 */
.site-footer {
  overflow: clip;
}

.site-footer [data-aos="fade-up"] {
  transform: translate3d(0, 20px, 0);
}

/* ==========================================
   AOS 沒載到時的保命規則
   ------------------------------------------
   AOS 的 CDN CSS 對 [data-aos^=fade] 設了無條件 opacity: 0（權重 0,2,0），
   aos.css 載到但 aos.js 沒載到（CDN 被擋／內網／離線）、或 site.js 沒跑到時，
   全站掛了 data-aos 的區塊會全部消失。
   site.js 檔頭與 layout.php 的保險絲會在 <html> 掛上 aos-unavailable，由下面這條解除隱藏。
   選擇器權重 0,2,1 > AOS 的 0,2,0，所以不需要 !important。
   ========================================== */
/* 首頁「公會理念」的進場：純 CSS 關鍵影格（animation-fill-mode: both，跑完就停在可見狀態），
   刻意不用 data-aos——這一區是首頁唯一的 h1，可見性不能依賴 aos.js 有沒有載到（見 home.php 的說明）。
   prefers-reduced-motion 時不動。 */
/* 2026-09-19 加上模糊→清晰（使用者：要華麗一點），標語像鏡頭對焦一樣浮現 */
@keyframes creed-rise {
  from { opacity: 0; transform: translate3d(0, 24px, 0) scale(0.96); filter: blur(10px); }
  to   { opacity: 1; transform: none; filter: none; }
}
.creed .creed-title,
.creed .creed-desc {
  animation: creed-rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.creed .creed-desc {
  animation-delay: 0.15s;
}
@media (prefers-reduced-motion: reduce) {
  .creed .creed-title,
  .creed .creed-desc {
    animation: none;
  }
}

html.aos-unavailable [data-aos] {
  opacity: 1;
  transform: none;
}

/* ==========================================
   共用元件：附件下載按鈕（課程內頁／文章內頁 共用）
   ========================================== */
.course-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* ⚠️ 這個 class 同時用在 <a>（真實檔案）與 <button>（示範項目，沒有網址）上。
   🔴 因此一定要寫 `background-color: transparent` 與 `font: inherit`——
      <button> 的瀏覽器預設是灰底 ＋ 系統字體，不寫的話示範項目會變成一顆灰方塊，
      而且**只有在示範項目上看得出來**，很容易漏檢。
   ⚠️ 用 transparent 而不是填一個白色：這個元件同時出現在白底與米色底的區塊上，
      填死任一色都會在另一邊出錯。 */
.course-attachment {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 20px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: transparent;
  font: inherit;
  font-size: 0.875rem;
  color: var(--color-text);
  text-align: left;
  transition: border-color 0.2s ease-out, color 0.2s ease-out;
}

.course-attachment:hover,
.course-attachment:focus-visible {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

/* I5：示範內容的免責提示（課程介紹與附件下載兩區共用）。
   ⚠️ 不用 emoji、不用驚嘆號——這是狀態說明不是警告。 */
.course-demo-note {
  margin: 16px 0 0;
  padding-top: 12px;
  border-top: 1px dashed var(--color-border);
  font-size: 0.875rem;
  line-height: 1.7;
  /* 🔴 X3（PM 2026-09-02）：由 `--color-muted` 改成 `--color-text`。
     PM 覺得淺灰的層級感太弱（**不是可讀性問題**——`--color-muted #636363`
     白底 5.74 本來就過 AA）。
     🔴 只有「內容型文字」改深；**狀態／未選取／分隔／次要標示那 9 類一律不動**
        （`.course-signup-deadline`／`.course-tab`／`.pagination-btn`／
        `.pagination-ellipsis`／`.course-status.is-full`／麵包屑／header top 的「｜」與
        「文字大小」／`.filter-date-sep`／`.about-figure-caption`）——
        那裡的 `--color-muted` **不是「比較淺的字」，是語意**，改深會讓主次層級消失。 */
  /* ⚠️ 這一項 PM 另外確認過：它是「這是展示資料」的**告知**，原本刻意用中性灰。
     改深之後會和正文一樣重，但**上線後客戶填了真內容這行就不顯示**，影響有限。 */
  color: var(--color-text);
}

.course-attachment svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
/* ==========================================
   右下角浮動操作鈕（可展開）
   ------------------------------------------
   客戶反映（2026-08-11）：整合成一張卡片之後仍然是「右下一大塊」，容易遮住畫面。
   改成收合時只有三顆圓鈕（字／分享／回頂端），點擊才向上展開選項。

   🔴 展開用「點擊」不用 hover：手機沒有 hover，長輩也常不知道要把游標停在上面。
   🔴 同時只允許一個展開，否則又會變成一長條。
   ⚠️ 文字大小被收進按鈕後，發現性變差——但這是客戶明確要求的取捨。
      為了補償，「字」那顆按鈕用主色實心、和其他兩顆有明顯區別，
      aria-label 也寫成「調整文字大小」而不是只有一個「字」。
   ========================================== */
.float-actions {
  position: fixed;
  right: 12px;
  bottom: 12px;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.float-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

/* 圓形主鈕 */
.float-fab {
  width: 46px;
  height: 46px;
  flex-shrink: 0;
  border: none;
  border-radius: 50%;
  background-color: var(--color-primary);
  color: #FFFFFF;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 🔴 白色外環是必要的，不是裝飾：
     .float-fab 用 --color-primary 填色，而 footer 的底色也是 --color-primary，
     捲到頁尾時按鈕會整顆融進背景、只看得到圖示（實測確認過）。
     外環讓它在白底內容區與紫底頁尾上都有邊界。 */
  box-shadow: 0 0 0 2px #FFFFFF, var(--shadow-md);
  transition: background-color 0.2s ease-out, transform 0.2s ease-out;
}

.float-fab:hover {
  background-color: var(--color-primary-hover);
  transform: translateY(-2px);
}

.float-fab:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 3px;
}

.float-fab svg {
  width: 20px;
  height: 20px;
}

/* 「字」那顆用文字而不是圖示：長輩看得懂「字」，看不懂 A↕ 這類符號 */
.float-fab-label {
  font-size: 18px;
  font-weight: 600;
  line-height: 1;
}

/* 展開出來的社群子項（Facebook／LINE）。
   客戶 2026-08-12 第 38 項：「顏色改成兩家的品牌原色、按鈕比較小」。
   ⚠️ 原本是 40px ＋ 淺紫，比主鈕小一號來表達「附屬」。現在改成：
      尺寸 48px（比主鈕 46px 還大一點，這兩顆才是真正要按的目標，
      48px 也高於 44px 的觸控目標下限——使用者以長輩為主），
      「附屬」關係改由**位置**（展開才出現、在主鈕上方）表達，不再靠尺寸。
   🔴 選擇器綁在 aria-label 上，因為 HTML 在 partials/footer.html（單一來源、不逐頁改），
      那裡的兩個 <a> 除了 aria-label 之外沒有可區分的 class。
      ⚠️ 改 partials/footer.html 的 aria-label 會讓這兩條規則靜默失效
         （按鈕會退回 .float-fab 的紫色、不報錯）——要改就兩邊一起改。 */
.float-fab-sub {
  width: 48px;
  height: 48px;
}

.float-fab-sub svg {
  width: 24px;
  height: 24px;
}

.float-fab-sub[aria-label="Facebook"] {
  background-color: var(--brand-facebook);
}

.float-fab-sub[aria-label="Facebook"]:hover {
  background-color: var(--brand-facebook-hover);
}

.float-fab-sub[aria-label="LINE"] {
  background-color: var(--brand-line);
}

.float-fab-sub[aria-label="LINE"]:hover {
  background-color: var(--brand-line-hover);
}

/* 展開容器：hidden 屬性負責可近用性，這裡只做動畫 */
.float-popout {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  animation: float-popout-in 0.18s ease-out both;
}

/* ⚠️ 原本這裡有一條 `.float-popout[hidden] { display: none }`，2026-08-13 刪除：
   已由 `style.css` Base Reset 的全站 `[hidden][hidden]` 涵蓋（見 CLAUDE.md 五節 2-2）。 */

@keyframes float-popout-in {
  from {
    opacity: 0;
    transform: translateY(6px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* 文字大小的三顆藥丸 */
.float-pill {
  width: 40px;
  height: 34px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  color: var(--color-text);
  line-height: 1;
  /* 🔴 白色外環：藥丸被選中時填的是主色，而 footer 底色也是主色，
     捲到頁尾時「當前選中」那一顆會整個融進背景（實測「小」變成一個沒有框的字）。
     外環讓三顆在任何底色上都是三個可辨識的方塊。 */
  box-shadow: 0 0 0 2px #FFFFFF, 0 1px 4px rgba(0, 0, 0, 0.18);
  transition: background-color 0.2s ease-out, color 0.2s ease-out, border-color 0.2s ease-out;
}

/* 三顆的字級刻意寫死 px：它們是「大小示意」，不能隨切換一起放大，
   否則按下「大」之後三顆同時變大，就看不出彼此的相對大小了 */
.float-pill[data-scale="default"] {
  font-size: 13px;
}

.float-pill[data-scale="md"] {
  font-size: 15px;
}

.float-pill[data-scale="lg"] {
  font-size: 18px;
}

.float-pill:hover {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

.float-pill.is-active {
  background-color: var(--color-primary);
  border-color: var(--color-primary);
  color: #FFFFFF;
}

/* 回到頂端：捲動一段距離後才出現。
   ⚠️ 純操作按鈕、沒有內容價值，所以 JS 沒跑、永遠不出現也不會少任何內容。 */
.float-top {
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity 0.25s ease-out, transform 0.25s ease-out, visibility 0.25s;
}

.float-top.is-shown {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.float-top.is-shown:hover {
  transform: translateY(-2px);
}

/* ==========================================
   LINE LIFF 模式：隱藏頁首、頁尾、右下浮動鈕、麵包屑與會員側欄的安全登出
   ------------------------------------------
   html.is-liff：/liff/* 頁面由 layout.php 直接輸出；其他頁面由 <head> inline script 依 sessionStorage.liffMode 掛上
   （pages/liff.js 在 LINE App 內開啟時設定），同一個 LIFF 視窗內之後瀏覽的頁面（課程、報名、會員專區）都延續；
   從 LINE 聊天室點一般連結會開新的視窗，sessionStorage 是空的，不受影響。
   ⚠️ 用 display:none 而不是不輸出 HTML：site.js 會綁 header／footer 裡的元素，留著 DOM 比較不會出錯。
   ⚠️ --header-stack 歸零：會員專區側欄等 sticky 元素用它預留頁首高度，頁首隱藏後不歸零會多一段空白。 */
html.is-liff {
  --header-stack: 0px;
  /* 🔴 也要歸零：管理列是 fixed，body 的 padding-top 吃這個變數。
     不歸零的話，管理員在 LINE 裡開 LIFF 頁會看到頂端多出一段空白。 */
  --admin-bar-height: 0px;
}

/* LIFF（LINE App 內）不顯示後台管理列：那是在 LINE 的網頁視窗裡，
   點「進入後台」會在 LINE 內開一個沒有網址列的後台，等於把人困住。 */
html.is-liff .admin-bar,
html.is-liff .site-header,
html.is-liff .site-footer,
html.is-liff .breadcrumb,
html.is-liff .member-sidebar-logout,
html.is-liff .member-sidebar-nav + .member-sidebar-divider {
  display: none;
}

/* 🔴 LIFF 的右下角浮動鈕：**只留「操作導覽」那一顆**，社群與回到頂端照舊隱藏。
   原本整個 `.float-actions` 都 display:none，結果使用者把操作導覽關掉之後，
   課程列表／課程內頁／報名／會員專區都沒有任何入口可以再打開（使用者 2026-09-17）。
   ⚠️ 導覽鈕是 tour.js 動態插進 `#float-actions` 的第一個子節點（class `.float-tour`），
      所以這裡用「除了 .float-tour 以外的子節點都隱藏」，不需要動 JS。
   ⚠️ 沒有導覽的頁面 tour.js 不會建那顆鈕，容器裡的子節點全被這條隱藏 ＝ 看不到任何東西
      （容器本身沒有背景與尺寸，不會留下一塊空白）。 */
html.is-liff .float-actions > :not(.float-tour) {
  display: none;
}

/* LIFF 會員側欄（會員專區、報名明細頁共用）：安全登出與它上方的分隔線不顯示（上一條）；
   選單改成橫向四格並排，圖示在上、文字在下（使用者 2026-09-15） */
html.is-liff .member-sidebar-nav {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
}

html.is-liff .member-sidebar-nav .member-sidebar-link {
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  padding: 10px 4px;
  text-align: center;
  font-size: 0.8125rem;
}

html.is-liff .member-sidebar-nav .member-sidebar-link svg {
  width: 22px;
  height: 22px;
}

/* ==========================================
   共用元件：佔位頁（_placeholder／404／403）
   ------------------------------------------
   消費端：pages/_placeholder.php、pages/404.php、pages/403.php。
   `/404`、`/403` 是 routes.php 註冊的真實路由（含 app.js 在 API 回 404 時導過去），
   不是開發用的暫時頁，因此需要基本可讀的樣式，不能貼齊視窗邊緣。
   只做「置中容器＋上下留白＋標題與內文層級」，沿用全站既有的容器與間距變數，
   不另外設計視覺元件。 */
.placeholder-page {
  max-width: calc(var(--container-max) + var(--page-gutter) * 2);
  margin: 0 auto;
  padding: var(--spacing-section) var(--page-gutter);
  text-align: center;
}

.placeholder-page h1 {
  font-size: 2rem;
  color: var(--color-heading);
  margin-bottom: 16px;
}

.placeholder-page p {
  margin: 0 0 16px;
}

.placeholder-page code {
  display: inline-block;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  background-color: var(--color-bg-alt);
  color: var(--color-muted);
  font-size: 0.875rem;
}

@media (min-width: 768px) {
  .float-actions {
    right: 20px;
    bottom: 20px;
  }
}

@media (prefers-reduced-motion: reduce) {

  .float-fab,
  .float-top {
    transition: opacity 0.2s ease-out, visibility 0.2s;
  }

  .float-fab:hover,
  .float-top.is-shown:hover {
    transform: none;
  }

  .float-popout {
    animation: none;
  }
}
