/*
 * 一天聽一點 — 前台樣式（SSOT）
 *
 * ════════════════════════════════════════════════════════════════════
 * 本檔是前台唯一樣式來源。原 wireframe.css 已於此併入並刪除。
 * 設計 token 定義在 tokens.css，本檔一律引用不自訂數值。
 * ════════════════════════════════════════════════════════════════════
 *
 * 【紀律】（中央廚房 03-frontend.md 準則 5 / 出站檢查 FE-03）
 *   1. 版面、格線、元件優先用 Bootstrap 原生 class，不夠用才寫自訂 class
 *   2. 不寫 inline style 裝飾 — view 裡的 style="" 一律搬進來
 *   3. 顏色一律 hsl(var(--x))，禁止 hex
 *   4. 共用 UI 元件全站一份（SSOT），重複出現的抽成 class 或 partial
 *
 * 【命名】
 *   .site-*  正式元件，會上線
 *   .wf-*    wireframe 溝通用標註（.wf-label / .wf-note / .wf-tag / .wf-bp /
 *            .wf-dummy / .wf-tline），**上線前整批移除**，故不做 token 化
 *
 * 【驗收】清完 inline style 後移除 .env 的 app.CSPEnabled = false。
 *   CSP 開著頁面正常 ＝ 沒有 inline style 殘留、資源都本地化，等同正式站條件。
 */

/* ═══════════════════════════════════════════════════════════════════
   Base — 全域基礎
   ═══════════════════════════════════════════════════════════════════ */

* { box-sizing: border-box; }

body {
    margin: 0;
    background: hsl(var(--background));
    color: hsl(var(--foreground));
    font-family: var(--font-body);
    font-weight: var(--weight-normal);
    line-height: var(--leading-normal);
    overflow-x: hidden;   /* 配合下方 .site-bleed 的滿版技法 */
}

a {
    color: inherit;
    transition: color var(--transition);
}

/* 無障礙：焦點必須看得見。用 aerial-600（對白底 4.59）確保足夠對比 */
:focus-visible {
    outline: 2px solid hsl(var(--ring));
    outline-offset: 2px;
}

/* ═══════════════════════════════════════════════════════════════════
   Layout — 版面
   ═══════════════════════════════════════════════════════════════════ */

.site-frame {
    background: hsl(var(--background));
}

.site-inner {
    max-width: var(--container-max);
    margin-inline: auto;
}

.site-section {
    padding: var(--section-y) var(--gutter-x);
}

/* 相鄰 section 之間不重複上下留白 */
.site-section + .site-section {
    padding-top: var(--space-3);
}

/*
 * 滿版出血
 *
 * 內容容器 .site-inner 有 max-width:1440px，若直接給 section 上底色，
 * 在寬螢幕會變成一條停在 1440px 的色帶、兩側露白，看起來像壞掉。
 * 這裡把元素撐到視窗寬再用 padding 把內容推回原本的對齊位置。
 *
 * ⚠️ 50vw 含捲軸寬，可能溢出 1 個捲軸寬度 → body 已設 overflow-x:hidden 兜底。
 *    FE-02 會逐頁檢查無橫向捲軸，改動此處後務必重驗。
 */
/*
 * ⚠️ 算式裡的 `var(--sbw, 0px) / 2` 不能省（2026-07-31 加）。
 *    `vw` **含捲軸寬，版面可用寬度不含**，所以只寫 `50% - 50vw` 會讓滿版區
 *    比可視範圍寬出一個捲軸；又因為兩側共用同一個負 margin，整條還會往左偏。
 *    實測（Chrome、捲軸 15px）：左緣 -15、右緣切齊，而且橫向真的捲得動 8px
 *    —— body 的 overflow-x:hidden 擋不住，捲動容器是 html。
 *    --sbw 由 assets/front/js/viewport-vars.js 量測後寫入；沒載入時取 0，
 *    退回原本的行為，不會破版。**不要改成 html{overflow-x:hidden}**，
 *    那個實測會弄壞 sticky（理由見那支 JS 的檔頭）。
 */
.site-bleed {
    margin-inline: calc(50% - 50vw + var(--sbw, 0px) / 2);
    padding-inline: calc(50vw - 50% - var(--sbw, 0px) / 2 + var(--gutter-x));
}

/* 區塊底色（區塊分色，見 .agent/spec.md 設計系統章節）
 *
 * ⚠️ 首頁不使用這兩個 class：品牌規格第 3 節「將設計的部分降低至 15%」，
 *    色帶是純裝飾性的區塊分色。首頁的區塊分隔由手繪筆觸（.site-rule）承擔。
 *    定義與 token 保留供內頁備用。 */
.site-bg-warm { background: hsl(var(--surface-warm)); }   /* 服務專區（首頁不用） */
.site-bg-cool { background: hsl(var(--surface-cool)); }   /* 知識專區（首頁不用） */

/* 2026-07-31 新增。看起來像沒作用（就是頁面底色），但它是**必要的**：
   主視覺是 sticky（.site-hero-sticky），凡是要蓋過它的 section 都必須自己
   有不透明底色，否則主視覺會從底下透出來 —— 實測時知識專區就整片透出了
   壓暗模糊的主視覺照片。沒有色帶、又排在 sticky 主視覺之後的 section，
   一律要掛這個。 */
.site-bg-base { background: hsl(var(--background)); }

/* 首頁的逐階加深（2026-07-31）。第一階就是 .site-bg-warm，不另立 class。
   色階與「不要再加深」的理由見 tokens.css 的 --surface-warm-2 / -3。 */
.site-bg-warm-2 { background: hsl(var(--surface-warm-2)); }
.site-bg-warm-3 { background: hsl(var(--surface-warm-3)); }

/*
 * ─── 紙紋顆粒（2026-07-31，使用者指定）──────────────────────────
 *
 * 要求是「過渡的質感，但不要漸層」，而且**只要三個色帶的背景有**，
 * 卡片、文字、按鈕都不要。
 *
 * 【為什麼掛在區塊的 ::before，而不是浮在整頁最上層】
 * 第一版做成 body::after（position: fixed、蓋在所有東西之上），使用者指出
 * 那樣連卡片與文字都罩到了。改成各區塊自己的 ::before，用 z-index: -1
 * 壓在**區塊底色之上、所有內容之下** —— 負 z-index 的子元素在繪製順序上
 * 排在父層背景之後、區塊內容之前，剛好就是要的位置。
 * ⚠️ 這需要區塊是定位元素且自成堆疊脈絡。三區都帶 .site-layer / .site-slab
 *    （position: relative + z-index），成立；下面仍補一條 position: relative
 *    當保險，日後單獨使用這些 class 時才不會掉出去。
 *
 * 【background-attachment: fixed 保留捲動錯動感】
 * 紋理錨定在視窗、內容照常捲，於是滾動時有「隔著一層紙在看」的細微位移。
 * 零 JS，不必 scroll-timeline（Firefox 還在旗標後面），也沒有常駐動畫
 * （brand-spec 第 3 節：避免過度粗、用力）。
 * ⚠️ iOS Safari 會忽略 fixed 而當成 scroll —— 降級是「顆粒跟著捲」，
 *    質感still在，可接受，不必為它另寫一套。
 *
 * 【顆粒為什麼要粗】
 * 第一版用 baseFrequency='.85' + 3 個 octave，顆粒細到在 dpr 2 的螢幕上
 * 每顆約一個實體像素，視覺上直接被平均掉 —— 使用者得把透明度拉到 50%
 * 才「看得出來」，但那時看到的是整片變灰，不是顆粒。
 * 降到 .55 / 2 octave 讓顆粒真的有面積，低透明度就讀得到。
 *
 * ⚠️ 調強弱請改下面的 --grain，**不要回頭拉 baseFrequency** ——
 *    那會變成又細又重，也就是「螢幕髒髒的」那種失敗樣貌。
 */
.site-bg-warm,
.site-bg-warm-2,
.site-bg-warm-3 {
    --grain: .5;
    position: relative;
}

.site-bg-warm::before,
.site-bg-warm-2::before,
.site-bg-warm-3::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    /*
     * ⚠️ 這一行不能省。絕對定位的子元素**不會**被父層的 border-radius 裁切
     * （只有父層自己的背景會被裁），所以區塊底色是圓角、顆粒層卻是方角，
     * 會在 .site-slab 的上緣圓角處露出一塊方形缺口 —— 實測服務專區左上角就是。
     * inherit 讓它跟宿主拿同一組圓角；沒有圓角的區塊（.site-layer）繼承到 0，
     * 不會有副作用。
     * 用 inherit 而不是在區塊上加 overflow:hidden：後者會連帶裁掉區塊內
     * 任何想溢出的東西，範圍比需要的大。
     */
    border-radius: inherit;
    /* feTurbulence 的 stitchTiles='stitch' 讓圖樣無縫重複，才能用小圖平鋪；
       少了它每一塊 200px 的接縫都看得出來。
       feColorMatrix saturate=0 把彩色雜訊去飽和 —— 原始 feTurbulence 是彩色的，
       不去飽和會在淺底上透出彩色斑點。
       走 img-src 的 data: URI（CSP 已允許，見 ContentSecurityPolicy.php）。 */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
    background-repeat: repeat;
    background-size: 200px 200px;
    background-attachment: fixed;
    opacity: var(--grain);
}

/* 減少動態：顆粒本身不會動，但「內容捲、紋理不動」的錯動對前庭敏感的
   使用者仍可能不適。改成跟著頁面一起捲 —— 質感留著，錯動消失。 */
@media (prefers-reduced-motion: reduce) {
    .site-bg-warm::before,
    .site-bg-warm-2::before,
    .site-bg-warm-3::before { background-attachment: scroll; }
}

/* 有底色的 section 需要完整上下留白，否則色帶會貼著內容；
   其後的 section 同理，不套用上面的相鄰收斂規則。 */
/* 2026-07-31：深色區（.site-dark）也算「有底色的 section」，一併納入。
   少了它，深色區與其後的知識專區會掉回 --space-3 的收斂留白、擠成一團。

   ⚠️ 前三條**必須寫成 `.site-section.site-bg-*`（0,2,0）**，只寫 `.site-bg-warm`
      （0,1,0）會輸給上面的 `.site-section + .site-section`（0,2,0）。
      這條以前不會爆，是因為色帶 section 前面一定夾著一個 .site-rule 的 div，
      相鄰選擇器根本不成立；2026-07-31 手繪線移除、section 真的相鄰了才露出來。
      實測症狀：服務專區的 padding-top 只有 16px 而不是 48px。 */
.site-section.site-bg-warm,
.site-section.site-bg-warm-2,
.site-section.site-bg-warm-3,
.site-section.site-bg-cool,
.site-section.site-bg-base,
.site-section.site-dark,
.site-bg-warm + .site-section,
.site-bg-warm-2 + .site-section,
.site-bg-warm-3 + .site-section,
.site-bg-cool + .site-section,
.site-bg-base + .site-section,
.site-dark + .site-section { padding-top: var(--section-y); }

/*
 * ─── 深色區塊（.site-dark）─────────────────────────────────────
 *
 * 語意層整組反轉。原本只有 .site-footer 在做這件事（檔案裡自稱「Layer 3 的
 * 唯一實例」），2026-07-31 抽出來共用 —— 首頁的實體課程區也要用同一套，
 * 兩邊各寫一份遲早會走鐘。
 *
 * 【用法】掛在區塊上，裡面的子元素自動吃到反轉後的顏色，不必逐一改寫。
 *
 * ⚠️ **`--primary` 在這裡是白的**（實心按鈕要反白），所以區塊自己的底色
 *    不能再寫 `hsl(var(--primary))` —— 那會變成白底。底色一律走 --background。
 *    .site-footer 原本正是那樣寫的，改用本 class 時已一併移除。
 *
 * ⚠️ 卡片刻意跟著變深（--card: slate-800）而不是維持白卡。白卡的話卡內文字
 *    得再反轉回深色，等於在深色區裡挖一塊淺色區、每個子元件都要處理兩種情境。
 *    整組同調最單純，而且暖色調的照片壓在深卡上反而更跳。
 */
.site-dark {
    --background:          var(--black);
    --foreground:          var(--white);
    --muted:               var(--slate-800);
    --muted-foreground:    var(--slate-300);
    --card:                var(--slate-800);
    --card-foreground:     var(--white);
    --card-soft:           var(--slate-800);
    /* slate-700 在純黑上只有 2.73，過不了 WCAG 1.4.11 對非文字元件的 3:1
       ——「進入」圓鈕（.site-go）的描邊就是靠它撐出可視邊界。改用 slate-600
       （黑底上 3.76）。卡片邊框跟著變亮一階，在 slate-800 的卡上仍然很含蓄。 */
    --border:              var(--slate-600);
    --input:               var(--slate-600);
    --primary:             var(--white);
    --primary-foreground:  var(--slate-900);
    --ring:                var(--white);
    --link:                var(--aerial-300);
    --link-hover:          var(--aerial-200);

    /*
     * ⚠️ Bootstrap 對接的那組 --bs-* **必須在這裡重新宣告一次**。
     *    tokens.css 是在 :root 寫 `--bs-secondary-color: hsl(var(--muted-foreground))`，
     *    那是「在 :root 這個元素上」完成代換的 —— 算出來的 rgb 值往下繼承，
     *    子孫再怎麼改 --muted-foreground 都不會讓它重算。
     *    少了這幾行，深底上的 .text-muted 會維持淺底用的 slate-600
     *    （實測黑底上只有 3.76，不到 4.5）。這是反轉語意層的第二類必踩坑，
     *    第一類是下面那兩顆 hover 態寫死色階的按鈕。
     */
    --bs-body-bg:          hsl(var(--background));
    --bs-body-color:       hsl(var(--foreground));
    --bs-secondary-color:  hsl(var(--muted-foreground));
    --bs-border-color:     hsl(var(--border));
    --bs-emphasis-color:   hsl(var(--foreground));
    --bs-link-color:       hsl(var(--link));
    --bs-link-hover-color: hsl(var(--link-hover));

    background: hsl(var(--background));
    color: hsl(var(--muted-foreground));
}

/*
 * 兩顆按鈕的 hover 底色是**寫死的色階**（--nude-100 / --slate-800），
 * 不是語意 token，所以上面那組反轉救不到它們。少了這兩條會變成：
 *   .site-btn:hover        淺暖白底 ＋ 白字 → 整顆消失
 *   .site-btn-solid:hover  深灰底 ＋ 深灰字 → 整顆消失
 * 這不是理論問題，是反轉語意層時一定會踩到的一類 bug：
 * **凡是 hover 態直接寫色階而非 token 的元件，都要在這裡補一條。**
 *
 * ── 2026-08-03：按鈕改成玻璃鈕之後，這裡也從「覆寫 .site-btn 的屬性」改成
 *    「覆寫 --glass-* 這組 token」，與 .site-header.is-dark 同一個做法。
 *    上面那段警告仍然成立、而且更好處理了：hover 態的色階現在也是 token，
 *    只要在這裡補一份深底配方，所有玻璃鈕一起翻。
 *    ⚠️ 描邊用 .38 而不是淺底那份的 .55：白/.38 疊在 --black 上是 97 灰，
 *       對純黑 3.9:1，與改版前 --slate-400 的重量相當（那條原註解說明了
 *       為什麼深底不能沿用 slate-500）。
 */
.site-dark {
    --glass-fill:       hsl(var(--white) / .08);
    --glass-line:       hsl(var(--white) / .38);
    --glass-fill-hover: hsl(var(--white) / .16);
    --glass-line-hover: hsl(var(--white));
}
/*
 * ⚠️ 選擇器**必須是 `.site-dark .site-btn.site-btn-solid`（0,3,0）**，
 *    只寫 `.site-dark .site-btn-solid`（0,2,0）會被 footer 後面的
 *    `.site-footer a { color: hsl(var(--muted-foreground)) }`（同為 0,2,0、
 *    但在檔案更後面）蓋掉字色 —— 實測結果是白底配 slate-300，對比 **1.75**，
 *    整顆訂閱鈕幾乎看不見。
 *    盤過全站，.site-btn-solid 一律與 .site-btn 同時出現（<a>／<span>／<button>
 *    都有），所以疊兩個 class 拉權重是安全的做法。
 */
.site-dark .site-btn.site-btn-solid {
    border-color: hsl(var(--primary));
    color: hsl(var(--primary-foreground));
}
.site-dark .site-btn.site-btn-solid:hover {
    background: hsl(var(--slate-200));
    border-color: hsl(var(--slate-200));
    color: hsl(var(--primary-foreground));
}

/*
 * ⚠️ 這裡曾經有 .site-dark-warm（實體課程區的暖棕底色），2026-07-31 移除。
 *
 * 首頁區塊銜接的完整過程，避免有人再走一次同樣的路：
 *   1. 一開始實體課程區用 .site-dark（純黑）。與鄰區的落差是 19.9 與 21.0，
 *      是能達到的最大反差，使用者回報捲動時太刺眼。
 *   2. 試過在上下邊界加漸層淡入淡出 —— 使用者否決，讀起來像一層霧不像過場。
 *   3. 改成暖棕（nude-800），落差降到 8.0 —— 使用者判定**仍然太深**。
 *   4. 定案：整條都留在淺色帶，三區只做微幅遞進（1.06 / 1.06 / 1.12），
 *      見 tokens.css 的 --surface-warm-2 / -3 與 .site-bg-warm-2 / -3。
 *
 * 也就是說，「用一塊深色區當視覺重心」這個方向已經整個否決掉了，
 * 不是換個深色就能過。.site-dark 現在只剩 footer 在用。
 */

/*
 * ─── 滿版區塊與圓角疊合 ────────────────────────────────────────
 *
 * 手法取自 lenusehealth.com（使用者指定的參考站）。它的「精緻感」幾乎不靠 JS，
 * 核心就是這兩行：上緣圓角 ＋ 等量的負 margin，於是這一區會**疊在上一區之上**，
 * 圓角把上一區的下緣咬掉一口，看起來像一張卡片滑上來。
 *
 * ⚠️ 負 margin 必須**等於**圓角半徑。小於的話上一區會從圓角缺口露出來，
 *    大於的話會吃掉上一區的內容。兩個值共用 --radius-xl，不要各寫一個數字。
 * ⚠️ 需要 position + z-index：不建立堆疊順序的話，上一區的背景會蓋在圓角上，
 *    弧線就看不見了。
 *
 * 與 .site-bleed 搭配使用（滿版才有意義；容器寬度下圓角會浮在半空中）。
 */

/*
 * .site-layer —— 只建立堆疊順序，不做圓角疊合。
 *
 * 主視覺是 sticky，**每一個排在它後面的 section 都必須有這個**，否則會被
 * 主視覺蓋住（sticky 是 positioned 元素，會畫在 static 元素之上）。
 * 需要圓角疊合的那幾區改用下面的 .site-slab（它已包含這一層的效果）。
 */
.site-layer {
    position: relative;
    z-index: var(--z-base);
}

/*
 * .site-slab —— .site-layer ＋ 上緣圓角疊合。
 *
 * ⚠️ 2026-07-31 起**只用在兩個地方**（使用者指定）：
 *      服務專區   ＝ 主視覺下方那一道圓角
 *      footer     ＝ 頁尾上方那一道圓角
 *    中間的實體課程與知識專區改用 .site-layer，區塊之間是平的。
 *    參考站也是這個用法 —— 圓角是「進入內容」與「收束頁面」兩個節點的標記，
 *    每個區塊都給就變成純裝飾了。
 */
.site-slab {
    position: relative;
    z-index: var(--z-base);
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    margin-top: calc(-1 * var(--radius-xl));
}

/*
 * ─── Hero 視差（.site-hero-sticky）────────────────────────────────
 *
 * 主視覺釘在原地，下一區（滿版、不透明）直接蓋上來。同樣取自參考站，
 * 同樣是純 CSS —— 它那支 `.hero-slide_sticky` 就只有 `position: sticky; top: 0`。
 *
 * ⚠️ z-index 必須低於後面的 .site-slab（--z-base = 1），否則會反過來蓋住它們。
 *    這裡用 0 而不是 auto：auto 不建立堆疊順序，之後如果 hero 內部出現任何
 *    positioned 元素，疊法會變得難以預測。
 * ⚠️ 站上的 header 是 `position: sticky; top: 0; z-index: 30`，所以釘住之後
 *    hero 的上緣會滑進 header 底下。這是刻意接受的 —— 那正是「主視覺往後退」
 *    的觀感，而且此時下一區已經從下方蓋上來，可見範圍本來就在縮小。
 *    若之後覺得被切太多，解法是給 top 一個等於 header 高度的值，
 *    但 header 高度沒有 token（--header-y 只是內距），要改得先立一個。
 *
 * 觸控與減少動態都不必特別處理：sticky 不是動畫，沒有播放與否的問題。
 *   ── 2026-08-03 更正：減少動態確實不必處理，但**觸控必須處理**，見下。
 *
 * 【⚠️ 鐵則：sticky 只能開在「主視覺保證等於一屏」的條件下】（2026-08-03）
 *
 * `top: 0` 的 sticky 元素只要比視窗高，釘住之後**下緣就永遠捲不出來** ——
 * 不是被裁掉而已，是根本到不了：它會一路釘在頂端，可見範圍只會從下方被
 * 下一區（.site-slab）愈蓋愈少。實測：
 *   桌機 1440×760   主視覺 1041px  超出 281px（下排格子看不到）← 使用者回報的災情
 *   手機  390×780   主視覺 1735px  超出 955px（六格只看得到前兩格）
 *
 * 所以 sticky 與「一屏」是一組，開啟條件必須**逐字等於**下面兩條
 * `min-height: var(--hero-fit)` 生效的媒體查詢聯集 —— 只有在那些條件下，
 * 主視覺的高度才由 --hero-fit 保證等於一屏。改一邊就要改另一邊：
 *
 *   ≤575.98px                                   手機：大圖 ＋ 橫滑入口列
 *   (hover:hover)and(pointer:fine)and(≥768px)   桌機：3×2 揭示格
 *
 * 中間那段（576–767，以及 768 以上的觸控裝置）是平板的 2 欄 3 排，高度由
 * 內容決定、不保證一屏（實測 767×1024 是 1.03 屏，剛好而已），所以**不開**。
 *
 * 平板因此走 relative：下一區的圓角與 -24px 負 margin 照樣會疊上來，
 * 少掉的只有「釘住不動」這件事，沒有內容看不到的問題。
 * ⚠️ 平板要不要也收進一屏是還沒決定的題目（使用者 2026-08-03：「我是感覺
 *    平板還好」）。在那之前不要把它加進下面的條件。
 */
/*
 * ⚠️ 不釘住的時候是 **relative 而不是 static**，兩個理由缺一不可：
 *   1. 背景光線 .site-rays 與光暈 .site-heroglow 是 `absolute; inset: 0`，
 *      沒有定位祖先就會跑去對齊整頁（.site-section 本身沒有 position）。
 *   2. 那兩層是 z-index: -1，要靠這裡的 z-index: 0 建立堆疊環境才會畫在
 *      主視覺黑底**之上**、內容之下；沒有堆疊環境會整個沉到黑底底下看不見。
 * relative 且不給任何 offset，視覺上與 static 完全相同，沒有副作用。
 */
.site-hero-sticky {
    position: relative;
    z-index: 0;
}

/* ⚠️ 這條必須排在上面那條**之後**：兩者權重相同（0,1,0），媒體查詢不加權重，
      靠順序決勝。搬到前面去 sticky 就會被 relative 蓋掉、視差整個失效。
   ⚠️ 逗號＝聯集，兩個條件各自獨立（不是 and）。手機那條是純寬度、不判斷指標，
      因為桌機視窗拉到 500px 時吃到的也是手機版型（大圖＋橫滑列），
      同樣保證一屏，該一起釘。 */
@media (max-width: 575.98px),
       (hover: hover) and (pointer: fine) and (min-width: 768px) {
    .site-hero-sticky {
        position: sticky;
        top: 0;
    }
}

/*
 * ─── 主視覺深色底（.site-hero-dark，2026-07-31）────────────────────
 *
 * 【為什麼主視覺變深色】
 * 為了讓背景光線（side-rays.js）成立。那支 shader 的 alpha 直接取自亮度、
 * 而且是 source-over 疊上去，**光要讀得出來的唯一條件是周圍比它暗** ——
 * 在原本的白底上沒有任何一組參數能讓它看起來像光。完整論證見那支 JS 的檔頭。
 *
 * 順帶救到的兩件事（不是附帶好處，是選深色的另外兩個理由）：
 *   1. 游標聚光（.site-cardgrid-chroma / -veil）在白底上「亮的地方」與
 *      「沒亮的地方」差很少，深底一換才真的會亮起來。
 *   2. .site-slab 的圓角疊合本來是「淺色面板滑過淺色主視覺」，看不太出來；
 *      換成從深色主視覺上滑過去，那道圓角才是一個過場。
 *
 * ⚠️ **刻意不套 .site-dark。** 那組是整層語意反轉，套上去會把
 *    `--background` 變黑 —— 而 .site-go（全站唯一的進入語彙，實心白圓片）
 *    的底色正是 `hsl(var(--background))`，六格右下的圓鈕會整批翻成黑色，
 *    破壞「全站六處進入鈕一致」那條規定（見 .site-go 的註解）。
 *    主視覺裡真正需要因為換底而調整的只有線框註記一項，值不得動整層 token。
 *
 * ⚠️ 這與 .site-dark-warm 當初被否決不是同一件事，不要拿那段歷史否掉這裡。
 *    那次是「兩個淺色區之間插一塊深色」，捲動時的落差刺眼；這裡是頁首封面，
 *    上面是 header、下面是圓角面板蓋上來，本來就該有分量。
 *
 * ⚠️ 必須與 .site-bleed 同時掛。主視覺原本是容器寬（1440），只給底色的話
 *    寬螢幕會變成一條停在 1440 的深色帶、兩側露白。
 */
/*
 * 底色用純黑（2026-07-31 下午，使用者指定：「反正 footer 也是黑底」）。
 * 一開始給的是暖棕 --nude-900 #402B12，改成 --black 有兩個好處：
 *   - 與 footer 同色，一頁的頭尾是同一個深色，不是兩種深色
 *   - 光線的動態範圍變大（暖棕本身已經佔掉一段明度），暖色光更跳
 * ⚠️ 這裡直接寫 --black 而不是套 .site-dark，理由見下面那段警告。
 */
/*
 * 【往上鑽到 header 底下】（2026-07-31 下午，配合 header 的半透明深色態）
 * 主視覺原本從 header 下緣才開始，於是毛玻璃 header 透到的是頁面白底 ——
 * 深色態實測是一條**灰帶**，磨砂質感等於沒有。把主視覺往上拉一個 header 高度，
 * header 才是「浮在主視覺上」，透上來的是背景光線與照片。
 *
 * ⚠️ 負 margin 與補償的 padding **必須是同一個值**，否則格子會整個位移。
 *    兩者共用 --header-h（由 viewport-vars.js 量測後寫入）。
 * ⚠️ 預設值 0px 是刻意的：JS 沒載入時不做位移，header 維持原本的白底實心
 *    （header-theme.js 同時也沒跑），兩邊一致、不會出現半透明 header 壓在
 *    白底上導覽文字消失的情況。
 * ⚠️ padding-top 寫在這裡會蓋掉 .site-section 的 `padding: var(--section-y) ...`
 *    簡寫（同權重、本規則在後）。--section-y 那一份要自己加回來，別漏。
 */
/*
 * 【--hero-y / --hero-gap：主視覺的垂直用量清單】（2026-08-03）
 *
 * 主視覺現在是「剛好一屏」（見下方 .site-cardgrid 的 min-height），
 * 而「一屏減掉多少才是格子的高度」＝ 這個 section 除了格子以外的所有垂直用量：
 *
 *     header 高   var(--header-h)     被負 margin 拉上去、再由 padding-top 補回
 *     上留白      var(--hero-y)
 *     下留白      var(--hero-y)
 *     格子下方    var(--hero-gap)     .site-cardgrid 的 margin-bottom
 *
 * ⚠️ **這四項就是全部，不要在主視覺裡再加任何有高度的東西**（多一行說明文字、
 *    多一個按鈕都算）。加了就必須同步進下面的 min-height 算式，否則主視覺會
 *    比一屏高 —— 而 sticky 元素一比視窗高，釘住之後下緣就永遠捲不出來
 *    （2026-08-03 使用者回報的「下面被切掉、往下捲也卡住」正是這個）。
 * ⚠️ 之所以要把留白抽成變數而不是直接寫 --section-y：算式要跟真正生效的
 *    padding 是**同一個來源**（SSOT）。兩邊各寫各的，改一邊就會悄悄失準。
 * ⚠️ 線框註記（.wf-label / .wf-note）在首頁是被 .wf-quiet 收掉的（高度 0），
 *    所以沒進這份清單。若之後把註記打開，主視覺就會超過一屏、sticky 陷阱回來
 *    —— 那是溝通用的臨時狀態，不為它調版型。
 *
 * 【為什麼是 min(…, 8svh) 而不是一個 max-height 斷點】
 * 總高等於一屏，所以留白吃掉多少、格子就少多少 —— 640px 高的筆電上，
 * 固定 381px 的用量會讓六格只剩 259px（兩排各一百多 px，照片等於沒了）。
 * 留白必須跟著矮視窗收。
 *
 * 8svh 這個數字不是拍腦袋：--section-y 是 6rem＝96px，96/1200＝8%，
 * 所以**視窗高 1200px 以上時 min() 取的一律是 --section-y（＝今天的樣子）**，
 * 低於 1200px 才開始等比例收，是連續的、沒有跳動。
 *
 * ⚠️ 一開始寫的是 `@media (max-height: 800px)` 收一半，當天就換掉：
 *    斷點兩側會差 96px，出現「801px 高的視窗，格子反而比 800px 的小」
 *    這種說不通的結果。**不要再改回斷點寫法。**
 * ⚠️ 只收主視覺自己的留白，不動 --section-y —— 那是全站節奏，
 *    其他區塊沒有「必須塞進一屏」的限制，跟著縮只會讓整頁變擠。
 */
.site-hero-dark {
    --hero-y:   min(var(--section-y), 8svh);
    --hero-gap: min(var(--space-7),  8svh);

    /*
     * 【--hero-fit：格子拿得到的高度＝一屏扣掉上面那份清單】（2026-08-03）
     * 桌機版與手機版各有一條 min-height 要用到它，算式只寫這一次。
     * ⚠️ 不要在任何一邊把算式再抄一遍 —— 抄了就會有兩份要同步的真相，
     *    而「主視覺等於一屏」正是 sticky 能不能開的唯一前提。
     */
    --hero-fit: calc(100svh
                     - var(--header-h, 0px)
                     - var(--hero-y) * 2
                     - var(--hero-gap));

    background: hsl(var(--black));
    margin-top: calc(-1 * var(--header-h, 0px));
    padding-top: calc(var(--hero-y) + var(--header-h, 0px));
    padding-bottom: var(--hero-y);
}


/*
 * 格子的描邊在深色底上會變成一圈亮邊（--border 是淺灰），使用者指定拿掉。
 *
 * ⚠️ **不能只寫 `border-color: transparent`。** 照片（.site-cardgrid-media）是
 *    `position:absolute; inset:0`，蓋的是**內距框**，蓋不到那 1px 的外框環；
 *    而格子的 `background` 預設畫到外框框，只把描邊透明化會露出一圈白底。
 *    兩個都要透明，那一圈才會直接看到主視覺背景（含光線與光暈），等於不存在。
 * ⚠️ 用透明而不是把寬度歸零：寬度歸零會讓六格各長 2px，grid 的 fr 分配、
 *    展開補間、focus outline 的 offset 全部要跟著重算。
 *
 * 順帶解掉一件事：格子原本的白底會在照片載入前閃一下白色方塊，
 * 在黑底上非常明顯。透明之後那一瞬間看到的是背景本身。
 */
.site-hero-dark .site-cardgrid-cell {
    border-color: transparent;
    background: transparent;
}

/*
 * ─── 背景光暈（.site-heroglow）────────────────────────────────────
 *
 * 讓格子的游標聚光**延伸到格子外的背景**（2026-07-31 下午，使用者指定）。
 * 光停在格線邊緣會變成「一盞只照得到卡片的燈」，那不是燈的行為。
 *
 * 【SSOT】用的是格子聚光**同一組**變數 --cg-x / --cg-y / --cg-r，
 * 由 card-grid.js 每幀一起寫（主視覺 section 掛 [data-cg-spot] 就會多寫一份，
 * 座標相對於 section 自己）。不要另外開一套變數 —— 兩套會各自 lerp，
 * 游標快速移動時兩個光心會分家。
 *
 * 背景需要比卡片更寬更淡的暈開，所以在這裡把半徑放大（calc），
 * **不是**在 JS 再算一個半徑出來。
 *
 * 色階的量是配著純黑底調的：底色 0 時峰值 .26 的 --nude-200 疊出來是
 * rgb(63,59,54) —— 看得出是一窪暖光，又還在暗色的範圍內。
 * ⚠️ 主視覺底色若之後又改動，這四階要一起重看：底色愈亮，同樣的 alpha
 *    看起來愈弱（第一版配暖棕 #402B12 時是 .14，換純黑後才提到 .26）。
 *
 * ⚠️ --cg-r 的預設值 0px 是整套退場機制的支點（游標離開／JS 沒跑／減少動態／
 *    觸控裝置，四條退路共用）。半徑為 0 的 radial-gradient 是退化漸層，
 *    CSS Images 3 §4.2 規定整片畫成**最後一個色階** —— 所以最後一階
 *    必須是全透明，這一層才會在那四種情況下完全消失。
 */
.site-heroglow {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: radial-gradient(circle calc(var(--cg-r, 0px) * 1.7)
                at var(--cg-x, 50%) var(--cg-y, 50%),
                hsl(var(--nude-200) / .26)  0%,
                hsl(var(--nude-200) / .155) 38%,
                hsl(var(--nude-200) / .065) 66%,
                hsl(var(--nude-200) / 0)   100%);
}

/*
 * 線框註記在深底上的還原。做法與 `.site-footer .wf-label` 同一套 ——
 * .wf-* 是寫死色碼的線框層（正式版整批移除），不吃語意 token，
 * 所以每出現一個深色區就要補一條。
 * 這組暖灰在 #402B12 上約 4.8:1，比它在白底上的 4.00 還好一點。
 */
.site-hero-dark .wf-label {
    color: #a99a92;
    border-left-color: #6a564a;
}

/*
 * ─── 主視覺背景光線（.site-rays）──────────────────────────────────
 * 內容 canvas 由 assets/front/js/side-rays.js 建立並每幀重畫。
 *
 * ⚠️ z-index:-1 —— 負值的子元素會畫在**父層背景之上、父層內容之下**，
 *    剛好是「鋪在深色底上、被格子蓋住」這個位置。成立的前提是父層本身是
 *    堆疊環境，.site-hero-sticky 的 z-index:0 已經建立了一個（那行的註解
 *    正好說明了為什麼是 0 而不是 auto —— 這裡就是它預留的用途）。
 * ⚠️ overflow:hidden 不能省：canvas 的尺寸來自 host.clientWidth，
 *    resize 與實際重畫之間有一幀的落差，沒有裁切時那一幀會溢出去。
 * ⚠️ JS 沒載入／不支援 WebGL 時這一層是空的 div，完全不影響版面。
 */
.site-rays {
    position: absolute;
    inset: 0;
    z-index: -1;
    overflow: hidden;
    pointer-events: none;
}

/* ─── 手繪分隔筆觸 ──────────────────────────────────────────────
   元件：_rule_stroke.php｜進場：draw-in.js
   接手上面色帶的區塊分隔職責。它同時是留白節奏的關鍵：插在兩個 section
   之間會讓 `.site-section + .site-section` 的相鄰收斂失效，
   每個 section 因此取回完整的 --section-y。 */

.site-rule {
    padding-inline: var(--gutter-x);   /* 與 .site-section 的水平留白對齊 */
}

/*
 * 接縫版（2026-07-31）：跨在兩個色帶的交界上，不佔版面高度。
 *
 * 首頁的三個色帶是直接相接的，中間沒有空隙可以放東西。絕對定位讓這一筆
 * 疊在接縫上，插進 section 也不會把內容往下推、不會動到留白節奏。
 *
 * top 取 SVG 高度的一半（20 / 2 = 10）往上提，線才會**騎在**接縫上而不是
 * 貼在下方區塊的頂端。
 *
 * ⚠️ 它會超出宿主 section 的上緣，靠的是 section 沒有 overflow:hidden。
 *    哪天有人為了別的理由給 section 加上 overflow:hidden，這一筆會被切一半。
 * ⚠️ 宿主必須是定位元素。首頁三區都有 .site-layer / .site-slab，成立。
 */
.site-rule-seam {
    position: absolute;
    top: -10px;
    left: 0;
    right: 0;
    pointer-events: none;
    /*
     * 頭尾各留 5%（使用者指定）。
     *
     * ⚠️ 刻意用**百分比**而不是 --gutter-x 或固定 px：留白要跟著螢幕寬度縮放，
     *    線才會在任何寬度下都是「兩端各留一點」的同一種比例。--gutter-x 是
     *    固定值（3.5rem），在寬螢幕上會顯得幾乎貼邊、在窄螢幕上又切太多。
     *
     * ⚠️ 也刻意**不改 $paths 的路徑資料**（那裡是 6～1194 / 1200，等於各留 0.5%）。
     *    三組筆畫是 seam 版與獨立版共用的 SSOT，改它會連帶動到另一種用法；
     *    留白是「這個用法的版面決定」，屬於 CSS 不屬於路徑。
     *
     * 實際落點會是 5.5% ～ 94.5%（5% 的 padding ＋ 路徑自帶的 0.5%）。
     */
    padding-inline: 5%;
}

/*
 * 「畫出來」的揭示改用 clip-path（2026-07-31）。
 *
 * ⚠️ 原本是 stroke-dasharray/dashoffset ＋ path 上的 pathLength="1"，
 *    **那個組合在這裡是壞的**，症狀是「線的右邊少一截，視窗越寬少越多」。
 *    成因：preserveAspectRatio="none" 讓路徑被**非等比**拉伸（實測視窗 1585 時
 *    橫向 1.189 倍、縱向 1 倍），而 vector-effect: non-scaling-stroke 讓虛線
 *    在螢幕座標下描繪 —— 但 dasharray 的「一整條」是用未拉伸的座標算的。
 *    實測路徑在使用者座標長 1188、螢幕上卻佔 1412，所以只覆蓋得到 84%。
 *    視窗越寬橫向倍率越大，覆蓋率就越低。
 *
 *    clip-path 是螢幕座標的裁切，完全不受縮放影響，而且揭示方向一樣是由左至右。
 *    **不要改回 dasharray**，除非同時拿掉 preserveAspectRatio="none"
 *    或 non-scaling-stroke —— 但那兩個各自都有理由存在。
 */
.site-rule-svg {
    display: block;
    width: 100%;
    height: 20px;
    clip-path: inset(0 100% 0 0);
    transition: clip-path var(--duration-draw) var(--ease);
}

/* .is-drawn 由 draw-in.js 在捲入視窗時加在外層 .site-rule 上。
   減少動態時 --duration-draw 為 0ms（tokens.css），直接以完成態出現。 */
.site-rule.is-drawn .site-rule-svg {
    clip-path: inset(0 0 0 0);
}

/*
 * ─── 背景手繪小插圖（.site-doodle，2026-07-31 下午）──────────────
 * 元件：_doodle.php｜素材來源與處理：assets/front/images/README.md
 *
 * 【濃度是這一整組的關鍵】原素材是很粗的黑色麥克筆。用 alpha 遮罩 ＋
 * `background-color` 把它壓到與鉛筆線同一個灰、極低不透明度，才不會在
 * 紙質感的底上變成四塊黑斑，也才守得住品牌規格第 3 節「設計降到 15%」。
 * ⚠️ 要調整存在感請只動 --dd-alpha，不要改素材檔。
 *
 * 【為什麼位置寫死在 CSS 而不是 inline style】本站有 CSP，行內樣式會被擋。
 * 六個位置都是**手放的**，挑的是版面確定空著的地方（區塊的上下內距帶、
 * 寬螢幕才存在的左右外緣），不是演算法算的 —— 換版面時要重看一遍。
 */
.site-doodle {
    --dd-alpha: .13;
    position: absolute;
    z-index: -1;
    pointer-events: none;
    background-color: hsl(var(--foreground) / var(--dd-alpha));
    /* 遮罩尺寸用 contain：四張的長寬比不同，寫死比例會壓扁 */
    -webkit-mask: var(--dd-src) no-repeat center / contain;
            mask: var(--dd-src) no-repeat center / contain;
}

/* 素材路徑相對於本 CSS 檔（assets/front/css/），不是網站根目錄 —— 同 signature。
   ⚠️ 本檔有 ?v= 內容 hash，但 mask 引用的 PNG 沒有：換素材時要一併改檔名。 */
.site-doodle-1 { --dd-src: url("../images/doodle/doodle-01.png"); }  /* 蹲著種盆栽 */
.site-doodle-2 { --dd-src: url("../images/doodle/doodle-02.png"); }  /* 站著剪枝 */
.site-doodle-3 { --dd-src: url("../images/doodle/doodle-03.png"); }  /* 澆水 */
.site-doodle-4 { --dd-src: url("../images/doodle/doodle-04.png"); }  /* 捧著小盆栽 */
.site-doodle-5 { --dd-src: url("../images/doodle/doodle-05.png"); }  /* 高盆栽（無人物） */
.site-doodle-6 { --dd-src: url("../images/doodle/doodle-06.png"); }  /* 石頭（橫長，當小填充） */

/*
 * a–c：放在區塊**下內距**那條 144px 的空帶裡，任何寬度都存在，所以永遠顯示。
 * 高度刻意小於 144，才不會頂到上面的內容或下面的接縫筆觸。
 */
.site-doodle-a { left: 4%;   bottom: 18px; width: 84px;  height: 112px; rotate: -4deg; }
.site-doodle-b { right: 5%;  bottom: 16px; width: 90px;  height: 120px; rotate: 3deg; }
.site-doodle-c { left: 8%;   bottom: 20px; width: 78px;  height: 104px; rotate: 5deg; }
.site-doodle-g { right: 12%; bottom: 26px; width: 40px;  height: 100px; rotate: -6deg; }  /* 高盆栽，細長 */
.site-doodle-h { left: 18%;  bottom: 22px; width: 74px;  height: 50px;  rotate: 2deg; }   /* 石頭，橫長 */

/*
 * d–f、i：放在容器兩側的外緣。
 * ⚠️ 這一段空間**只有寬螢幕才存在** —— 容器是 1440、左右內距 3.5rem，
 *    1440px 的視窗左右各只剩 56px，塞不下。所以整組用媒體查詢關掉，
 *    低於 1700px 直接不顯示，而不是縮小塞進去（縮到看不清就沒有意義了）。
 */
.site-doodle-d,
.site-doodle-e,
.site-doodle-f,
.site-doodle-i { display: none; }

@media (min-width: 1700px) {
    .site-doodle-d,
    .site-doodle-e,
    .site-doodle-f,
    .site-doodle-i { display: block; }

    .site-doodle-d { left: 2.5%;  top: 34%; width: 100px; height: 136px; rotate: -7deg; }
    .site-doodle-e { right: 2.5%; top: 46%; width: 92px;  height: 124px; rotate: 6deg; }
    .site-doodle-f { right: 3%;   top: 28%; width: 86px;  height: 118px; rotate: -3deg; }
    .site-doodle-i { left: 3%;    top: 62%; width: 46px;  height: 118px; rotate: 4deg; }  /* 高盆栽 */
}

/*
 * ─── 鉛筆塗鴉層（.site-pencil，2026-07-31 下午）────────────────────
 * 行為與效能取捨見 assets/front/js/pencil-pad.js。
 *
 * ⚠️ z-index:-1 與紙紋顆粒（.site-bg-*::before）同層，靠 DOM 順序決勝：
 *    ::before 在樹序上排在所有子節點之前，所以畫布一定疊在顆粒之上、
 *    內容之下 —— 使用者只畫得到「內容以外的紙面」，畫不到卡片與文字上。
 *    這正是想要的：卡片不會被塗鴉蓋住。
 * ⚠️ 畫布由 JS 動態建立，這裡只描述容器；沒有 JS 時整層不存在。
 */
/*
 * 紙面上的游標換成一支鉛筆（2026-07-31 下午，使用者指定）。
 *
 * 【為什麼用 SVG data URI 而不是另存一個檔】游標圖跟著 CSS 走最單純，
 * 少一個 HTTP 請求、也不會有 ?v= 快取失效的問題（.site-doodle 的 PNG 就有那個坑）。
 * ⚠️ `#` 必須寫成 `%23`，否則會被當成 URL 的片段識別字元，整條宣告失效。
 * ⚠️ 熱點座標 `3 23` 對準的是筆尖。寫錯的話「線從哪裡出來」會跟游標對不上，
 *    改圖形時務必一起改。
 * ⚠️ 尾巴的 `crosshair` 是退路：舊瀏覽器或 SVG 游標被停用時至少不是預設箭頭
 *    （箭頭會讓人以為這裡沒有互動）。
 *
 * 筆身填白是刻意的：紙面上會有小插圖與已經畫過的筆跡，純外框的筆在那上面
 * 會斷斷續續看不清楚。
 */
[data-pencil] {
    cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='26' height='26' viewBox='0 0 26 26'%3E%3Cg stroke='%232b2724' stroke-width='1.4' stroke-linejoin='round'%3E%3Cpath fill='%23fff' d='M3.4 22.6 5 17.6 17.6 5a2 2 0 0 1 2.8 0l.6.6a2 2 0 0 1 0 2.8L8.4 21Z'/%3E%3Cpath fill='%232b2724' d='M3.4 22.6 5 17.6l3.4 3.4z'/%3E%3Cpath fill='none' d='M15.8 6.8l3.4 3.4'/%3E%3C/g%3E%3C/svg%3E") 3 23, crosshair;
}

/*
 * 內容不是紙。直接子元素一律還原成一般游標 —— 卡片、面板、標題上按下去
 * 不會畫線（JS 用 `e.target === host` 擋掉了），游標就不該長得像可以畫。
 * ⚠️ 用直接子選擇器一次涵蓋，不要逐一列元件名 —— 那份清單一定會過期。
 *    連結與按鈕自己的 `cursor: pointer` 權重更高，不受這條影響。
 * ⚠️ .site-pencil 與 .site-doodle 也會吃到 auto，但它們是 pointer-events:none、
 *    永遠不會被命中，所以不必排除。
 */
[data-pencil] > * { cursor: auto; }

/* 拖曳畫線時關掉文字選取：不關的話游標會一路變成 I 型、還會把卡片上的字反白 */
[data-pencil].is-drawing { user-select: none; }

.site-pencil {
    position: absolute;
    inset: 0;
    z-index: -1;
    overflow: hidden;
    pointer-events: none;
    border-radius: inherit;   /* 圓角區塊（.site-slab）不能讓筆跡溢出圓角外 */
    opacity: 1;
    transition: opacity var(--duration-slow) var(--ease);
}

/* 游標離開該區時整層淡出，再由 JS 清空 —— 筆跡不會永久留在頁面上 */
.site-pencil.is-fading { opacity: 0; }

.site-pencil canvas {
    display: block;
    width: 100%;
    height: 100%;
}

/*
 * ─── 區塊標題 ＋ 左右手繪筆翼（.site-sechead，2026-07-31 下午）──────
 * 元件：_section_head.php｜筆畫資料仍在 _rule_stroke.php（SSOT）
 *
 * 取代「交界一道線 ＋ 標題兩側各一道直線」那組重複的記號，見元件檔頭。
 * ⚠️ .site-h2.rule（直線版）保留不動 —— 內頁四處還在用。
 */
.site-sechead {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-5);   /* 與 .site-h2 原本的下方留白一致 */
}

/*
 * 留在文件流裡的那一個（首頁＝服務專區）也要滿版，否則兩翼會比 seam 版短一截
 * （實測 611px vs 1092px）——「同一個記號」就不成立了。
 *
 * seam 版是靠 `position:absolute; left:0; right:0` 撐到宿主的**內距框**才滿版的，
 * 在文件流裡沒有這條路：它的寬度是宿主的**內容框**，而宿主 .site-bleed 的
 * padding-inline 已經把內容推回容器寬。
 *
 * 這裡用的是 .site-bleed 那條負 margin 的通式：任何一個「寬度等於包含塊」的
 * 區塊元素，兩側各給 `50% - 50vw + --sbw/2`，總寬就會變成 vw - 捲軸寬。
 * ⚠️ 但**不能直接掛 .site-bleed**：它的 padding-inline 會把內容再推回容器寬，
 *    而這裡要的是 5%（與 seam 版同一個值、同一個理由）。所以只借負 margin。
 * ⚠️ --sbw 那一項不能省，理由與 .site-bleed 完全相同（vw 含捲軸、版面寬不含）。
 */
.site-sechead:not(.site-sechead-seam) {
    margin-inline: calc(50% - 50vw + var(--sbw, 0px) / 2);
    /*
     * 上方留白補到與「課程／知識區塊下方的留白」相同（使用者指定）。
     * 那個值是 144px ＝ --section-y(96) ＋ --space-5(48)，全站已經在用的節奏
     * （見 home.php 的 .site-more 註解）。這裡的 section 已經有 96 的上內距，
     * 所以補一個 --space-5 就到 144。
     * ⚠️ 兩種版本的「上方留白」量到的東西本來就不同：seam 版量的是「上一區
     *    最後的內容到線」，這裡量的是「色帶上緣到標題」。線的中心會比 144
     *    再低半個標題高（約 17px），那是字高造成的，不寫死補償 ——
     *    補償值會隨字級變動，寫死等於埋一個之後改字級才會發現的坑。
     */
    margin-top: var(--space-5);

    /*
     * 【標題到內容的距離：三區必須一致】（2026-08-03，使用者回報）
     *
     * 改版前實測：服務專區 48px、實體課程與知識專區 **81.6px**，差 33.6px。
     * （第一次量到的 58 是被 Bootstrap `.row` 的 `margin-top: -24px` 騙了，
     *   那兩區的內容是 .row 包著卡片，要往裡再取一層才是看得到的第一個東西。）
     *
     * 【為什麼是服務去對齊另外兩區，不是反過來】
     * 81.6 不是挑出來的數字，是**結構算出來的**：
     *     seam 版的標題騎在色帶交界上（translateY(-50%)），標題下緣落在
     *     section 上緣 ＋ 半個標題高；內容則由 section 自己的 padding-top 決定。
     *     所以 距離 ＝ --section-y − 標題半高 ＝ 96 − 14.4 ＝ 81.6
     * 服務那邊的 48（--space-5）才是外加的。對齊到有結構來源的那個值，
     * 兩個斷點（--section-y 在 991.98 / 575.98 以下會變）也會自動跟著對齊。
     *
     * ⚠️ **0.6 ＝ line-height 1.2 的一半，而那個 1.2 來自 Bootstrap 給 h2 的
     *    預設值，不是本專案的 token**（--leading-tight 是 1.3，.site-h2 沒有用它）。
     *    這是全式子裡唯一一個外部依賴：Bootstrap 換版或 .site-h2 自己開始指定
     *    line-height 時，這個 0.6 要跟著重算，否則三區又會不齊。
     * ⚠️ 標題是 white-space: nowrap（見 .site-sechead .site-h2），永遠一行，
     *    所以「半高」只需要算一行。拿掉 nowrap 的話這條式子就不成立了。
     */
    /* ⚠️ 這裡的字級 token 必須與 .site-sechead .site-h2 的 font-size 完全一致
           （2026-08-03 一起由 --text-xl 改成 --text-2xl）。對不上就三區不齊。 */
    margin-bottom: calc(var(--section-y) - var(--text-2xl) * 0.6);
}

/*
 * 兩翼的頭尾留白：兩種版本必須用**同一個算式**，不能都寫 `5%`。
 *
 * ⚠️ padding 的百分比是相對**包含塊**解析的，而兩者的包含塊不同：
 *      seam 版      包含塊是 section 的內距框（＝滿版，2545）→ 5% = 127
 *      文件流版      包含塊是 section 的內容框（＝容器寬，1343）→ 5% = 67
 *    實測兩翼因此差了 61px（1142 vs 1081），線一長一短。
 *    改成直接對視窗算，兩邊就一定相同。--sbw 要扣掉，理由同 .site-bleed。
 */
.site-sechead {
    padding-inline: calc((100vw - var(--sbw, 0px)) * .05);
}

/*
 * 標題在這裡是 flex item：外距交還給容器，而且不能斷行 ——
 * 斷行會讓兩翼的垂直中心跑掉，線就不在同一條水平線上了。
 *
 * ⚠️ 選擇器**必須是 `.site-sechead .site-h2`（0,2,0）**。只寫 `.site-sechead-t`
 *    是 (0,1,0)，與 `.site-h2` 同權重，而 `.site-h2` 在本檔更後面 → 它勝，
 *    `margin: 0 0 var(--space-5)` 照樣生效。
 *    症狀不是「下面多一段空白」而是**標題比兩翼高 24px**：flex 的 align-items
 *    對齊的是**外距框**，48px 的下外距等於把邊框框往上推一半。
 *    實測就是 24px，與 --space-5 的一半吻合。
 */
.site-sechead .site-h2 {
    margin: 0;
    white-space: nowrap;
    /*
     * 比 .site-h2 的 --text-lg 大一級（使用者指定）。
     * ⚠️ 刻意只加在這個元件上，**不動 .site-h2 本身** —— 那一個內頁四處還在用
     *    （courses_list 的「課程一覽」、search 的分組標題、member_center、
     *    knowledge_detail 的「相關文章」），那些是頁內小標，與首頁的區塊名
     *    本來就不是同一個層級。要一起放大請先看過那四頁。
     * 手機自動收回 1.15rem（--text-xl 在 tokens.css 的斷點裡會降級），
     * 也就是與原本的 --text-lg 同大小，窄螢幕不會被標題吃掉整條線。
     *
     * 【2026-08-03 再放大一級：--text-xl → --text-2xl（使用者指定）】
     * ⚠️ 上面 .site-sechead:not(.site-sechead-seam) 的 margin-bottom 是
     *    `calc(--section-y − 字級 × 0.6)`，**那條式子必須跟著改成同一個 token**，
     *    否則服務專區的「標題到內容」會比另外兩區多 15px（三區對齊那件事
     *    2026-08-03 才剛修好，改字級是最容易把它弄壞的一步）。
     * 手機同樣自動降一級（--text-2xl 在 tokens.css 斷點裡是 1.5rem）。
     */
    font-size: var(--text-2xl);
}

.site-sechead-wing {
    flex: 1;
    min-width: 0;      /* 沒有這行，SVG 的內在寬度會撐爆 flex 容器 */
    pointer-events: none;
}

/* _rule_stroke 自帶與 .site-section 對齊用的水平留白，在兩翼裡不需要
   （留白由 .site-sechead 的 gap 與 seam 版的 padding-inline 決定）。 */
.site-sechead-wing .site-rule {
    padding-inline: 0;
}

/*
 * 左翼的揭示方向反過來（由右往左），兩翼於是從標題往外長出去。
 * 都由左往右的話，左翼會是「朝著標題畫過來」，看起來像兩筆分開的動作。
 * ⚠️ 這條必須排在 .site-rule-svg 之後才蓋得掉它的 clip-path 起始值。
 */
.site-sechead-wing-l .site-rule-svg {
    clip-path: inset(0 0 0 100%);
}

/*
 * 騎在色帶交界上。作法與 .site-rule-seam 相同（那一段有完整的前提說明），
 * 差別只在這裡連標題一起帶上去，所以要用 translateY 而不是固定的 top 位移 ——
 * 位移量必須是「自己高度的一半」，而高度會隨字級與斷點變動。
 *
 * ⚠️ margin-bottom 要歸零：絕對定位之後它不再佔版面，留著會在計算上
 *    產生一個看不見但存在的偏移量，日後很難查。
 * ⚠️ 頭尾留白（5%）寫在上面的 .site-sechead，兩種版本共用同一個算式 ——
 *    這裡不要再寫一次 padding-inline，理由見那一段。
 */
.site-sechead-seam {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    margin-bottom: 0;
    transform: translateY(-50%);
}

.site-rule-path {
    fill: none;
    /* 比一般分隔線（--border）深一階是刻意的：這是條曲線，抗鋸齒會把 1.5px
       的墨色攤到二三個像素列上，同色時看起來會比 .site-h2.rule 那種
       水平直線淡一截。深一階才與全站其他分隔線視覺等重。 */
    stroke: hsl(var(--slate-300));
    /* 2026-07-31 下午由 1.5 加粗到 2.2（使用者指定）。同一批標題字級升到
       --text-xl，線也要跟著加重，否則兩翼在放大的標題旁邊會顯得細。
       ⚠️ 這支目前只由 _section_head 使用，所以直接改這裡而不是在元件層覆寫
          （同一個值只留一處）。日後 _rule_stroke 若又獨立使用，
          再視情況拆成兩個值。 */
    stroke-width: 2.2;
    stroke-linecap: round;
    /* 線隨容器伸展（preserveAspectRatio="none"），但線寬不跟著被拉粗。
       ⚠️ 這一行也是上面那組 dasharray 會壞掉的原因之一，但它必須留著 ——
          少了它，線在橫向拉伸下會變成不等粗的一條。揭示改用 clip-path
          就不受影響了，理由見 .site-rule-svg 那段。 */
    vector-effect: non-scaling-stroke;
}

/* ═══════════════════════════════════════════════════════════════════
   Typography
   ═══════════════════════════════════════════════════════════════════ */

/*
 * 標題一律明體（--font-display），內文維持黑體（--font-body）。
 * 依客戶方品牌規格：「避免使用過度粗、用力的字體，接近無印良品風格」，
 * 該提案第二版封面的大標與副標即為明體。理由與取捨見 tokens.css 字體區塊。
 */
/* 字重 500 而非 700：明體在 700 會顯得「粗、用力」，正是品牌規格要避免的。
   500 仍足以與內文拉開層級，但保住無印風的克制感。（客戶已確認） */
.site-h1 {
    margin: 0 0 var(--space-1);
    font-family: var(--font-display);
    font-size: var(--text-2xl);
    font-weight: var(--weight-medium);
    overflow-wrap: break-word;
}

/* 細體大標：用於 banner 主標與搜尋頁，是本站優雅感的來源 */
.site-h1-light {
    font-weight: var(--weight-light);
    letter-spacing: var(--tracking);
}

/* 2026-07-31：標題下方由 --space-4（24px）拉到 --space-5（48px）。
   section 外圍留白在同一輪拉到 96px 之後，標題到內容只有 24px 會變成
   「內部比外部還擠」—— 使用者回報的擁擠感有一半來自這裡，不是只有區塊之間。
   48px 大約是外圍的一半，是標題與內文之間常見的比例。 */
.site-h2 {
    margin: 0 0 var(--space-5);
    font-family: var(--font-display);
    font-size: var(--text-lg);
    font-weight: var(--weight-medium);
    /* ⚠️ 2026-07-31 明確指定 --foreground。原本沒寫，於是在深色區塊裡繼承了
          section 的 --muted-foreground（.site-dark 有設 color）—— 標題變成次要色。
          在純黑上是 12:1 所以看不出問題，換成暖棕底之後只剩 4.85，貼著門檻。
          淺色區塊本來就繼承 --foreground，所以這一行對它們沒有任何改變。 */
    color: hsl(var(--foreground));
    text-align: center;
}

/* 兩側細線的 section 標題 */
.site-h2.rule {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.site-h2.rule::before,
.site-h2.rule::after {
    content: "";
    flex: 1;
    height: var(--border-width);
    background: hsl(var(--border));
}

@media (max-width: 991.98px) {
    .site-h2.rule::before,
    .site-h2.rule::after { display: none; }
}

/* 內文段落 */
.site-prose {
    color: hsl(var(--muted-foreground));
    line-height: var(--leading-relaxed);
}

/* 引言區塊（原為 knowledge_detail 的 7 處 inline style） */
.site-quote {
    margin: 0;
    padding-left: var(--space-3);
    border-left: var(--border-width-thick) solid hsl(var(--border));
    color: hsl(var(--muted-foreground));
}

/* ═══════════════════════════════════════════════════════════════════
   Components — 共用元件（SSOT）
   ═══════════════════════════════════════════════════════════════════ */

/* ─── Header ─────────────────────────────────────────────────────── */

.site-header {
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);
    background: hsl(var(--background));
    border-bottom: var(--border-width) solid hsl(var(--border));
}

/*
 * ─── Header 深色態（.is-dark，2026-07-31）─────────────────────────
 * 掛卸由 assets/front/js/header-theme.js 負責：主視覺還在 header 底下時深色，
 * 捲到掛了 [data-header-light-from] 的區塊（首頁＝服務專區）就淡回白底。
 *
 * 【為什麼是半透明 ＋ 毛玻璃，不是實心深色】
 * 實心深色等於在黑色主視覺上再貼一條黑色橫帶，接縫只是換個位置。
 * 半透明會讓底下的光線與照片透上來、跟著捲動變化，header 才像是「浮在
 * 主視覺上」而不是「壓在主視覺上」。毛玻璃負責把透上來的東西糊掉，
 * 導覽文字才有穩定的底。
 *
 * ⚠️ **底色的不透明度是可及性參數，不是純美觀參數。** 主視覺的照片會捲到
 *    header 底下，最亮的部分接近純白（banner 六張裡有好幾張就是窗光），
 *    scrim 太淡的話導覽文字（slate-300）會直接消失。
 *
 *    .72 是**算出來的下限**，取法是不去賭照片有多亮，直接假設最壞情況：
 *      背景純白 255 → 疊 .72 黑之後 = 0.28 × 255 ≈ 71 → 相對亮度 .113
 *      slate-300 #C3C4C6 → 相對亮度 .551
 *      對比 = (.551 + .05) / (.113 + .05) = **5.32**，過 AA 的 4.5
 *    也就是說底下捲過什麼都成立，不必逐張照片重驗。
 *    ⚠️ 往下調就會破這個保證 —— 第一版 .62 在純白底下只有 4.01，不合格。
 *    真的要更透，就得同時把導覽文字往白色調（但那會吃掉 hover／active 的層級差）。
 * ⚠️ backdrop-filter 一直開著（不是只有深色態才開）：只在深色態開的話，
 *    切換的那一瞬間模糊會硬生生消失，而 backdrop-filter 沒辦法平滑補間。
 *    淺色態時它作用在白底上是無變化，等於免費。
 *
 * ⚠️ 導覽文字的 --slate-700 是**寫死的色階不是 token**，語意層反轉救不到，
 *    所以下面要單獨寫一條。這與 .site-dark 那邊 .site-btn:hover 的坑同一類：
 *    凡是直接寫色階的元件，深色態都要補一條。
 */
/* 毛玻璃只在「會切換深淺的頁面」掛上（class 由 header-theme.js 在初始化時加）。
   其他頁面的 header 是不透明白底，掛 backdrop-filter 只是白白讓它獨立成一層
   合成、每幀多算 2545×93 個像素。 */
.site-header.is-glass {
    backdrop-filter: blur(16px) saturate(1.4);
    -webkit-backdrop-filter: blur(16px) saturate(1.4);
}

/* 過場只在 .is-theming 之後才開啟 —— JS 定完初始狀態才加這個 class，
   否則載入當下會看到白色 header 淡成深色的那一下。 */
.site-header.is-theming {
    transition: background-color var(--duration-slow) var(--ease),
                border-color var(--duration-slow) var(--ease),
                color var(--duration-slow) var(--ease);
}

.site-header.is-dark {
    --foreground: var(--white);          /* .site-logo 的 mask 吃 currentColor，一行就反白 */
    background: hsl(var(--black) / .72);
    border-bottom-color: hsl(var(--white) / .14);
}

.site-header.is-dark .site-nav a { color: hsl(var(--slate-300)); }

.site-header.is-dark .site-nav a:hover,
.site-header.is-dark .site-nav a.active { color: hsl(var(--white)); }

/* 行動版的展開鈕（☰）。.site-btn 的底色與描邊都是淺色 token，
   深色態下會變成一顆亮片黏在毛玻璃上。
   ── 2026-08-03：全站按鈕改成玻璃鈕之後，這裡從「覆寫 .site-btn 的三個屬性」
      改成「覆寫 --glass-* 這組 token」。.site-btn 那條規則因此一個字都不用動，
      而且同一份深色玻璃配方會自動套到之後任何進到深色 header 的鈕上。
      **這四個值就是使用者說好看的那顆漢堡鈕的原始配方**，實測白/.28 疊在
      純黑 header 上是 3.0:1（剛好過 1.4.11 的 3:1），不要再調淡。 */
.site-header.is-dark {
    --glass-fill:       hsl(var(--white) / .10);
    --glass-line:       hsl(var(--white) / .28);
    --glass-fill-hover: hsl(var(--white) / .18);
    --glass-line-hover: hsl(var(--white));
}

.site-header.is-dark .site-btn {
    color: hsl(var(--white));
}

.site-header .inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--header-y) var(--gutter-x);
}

.site-logo {
    font-size: var(--text-xl);
    font-weight: var(--weight-light);
    letter-spacing: var(--tracking-widest);
    text-decoration: none;
    color: hsl(var(--foreground));
}

/* ─── Signature 品牌印記 ─────────────────────────────────────────
 *
 * 素材來源、暫代狀態與換檔方式見 app/Views/Front/wireframe/_signature.php。
 *
 * 顏色吃 currentColor：header 是墨黑，疊在深色圖上只要把父層 color 設成白，
 * 同一個素材檔就能兩用，不必準備反白版。
 *
 * 素材路徑是相對於本 CSS 檔（assets/front/css/），不是網站根目錄。
 * ⚠️ 本檔有 ?v= 內容 hash，但 mask 引用的 PNG 沒有 —— 日後換成向量檔時
 *    要一併改檔名（或加 ?v=），否則瀏覽器會續用舊快取。
 */
.site-signature {
    display: inline-block;
    line-height: var(--leading-none);
}

/* 預設顯示文字。不支援 mask 的瀏覽器看到的是「一天聽一點」四個字，
   而不是一塊實心色塊 —— 退化後仍可讀，這是預設值放文字的理由。 */
.site-signature-mark { display: none; }

@supports (mask-image: none) or (-webkit-mask-image: none) {

    /* 等同 Bootstrap .visually-hidden：字仍留在 DOM 供螢幕閱讀器與搜尋引擎讀取。
       不用 display:none，那會讓輔助技術也讀不到品牌名。 */
    .site-signature-text {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
        border: 0;
    }

    .site-signature-mark {
        display: block;
        aspect-ratio: var(--signature-ratio);
        background-color: currentColor;
        -webkit-mask: url("../images/brand/signature.png") no-repeat center / contain;
                mask: url("../images/brand/signature.png") no-repeat center / contain;
    }
}

.site-signature-sm .site-signature-mark { width: var(--signature-w-sm); }
.site-signature-md .site-signature-mark { width: var(--signature-w-md); }
.site-signature-lg .site-signature-mark { width: var(--signature-w-lg); }

/* ─── 主導覽 ─────────────────────────────────────────────────────── */

.site-nav {
    display: flex;
    align-items: center;
    gap: var(--space-4);
}

/*
 * 導覽項目一律 inline-flex：
 * Bootstrap 的 .dropdown-toggle::after 是 inline-block 且有實際高度（border .3em），
 * 若走 line box + vertical-align，帶箭頭的 <a> 會比其他 <a> 高，align-items:center
 * 對齊盒子中心後文字就錯位。改 inline-flex 後箭頭成為 flex item 自動垂直置中，
 * 不再參與 line box 計算，各項目高度一致。line-height:1 消除字級差造成的高度浮動。
 */
.site-nav a {
    display: inline-flex;
    align-items: center;
    line-height: var(--leading-none);
    /* 2026-08-03 下午：--text-sm → --text-md（使用者指定）。
       ⚠️ 導覽是 uppercase ＋ --tracking-wider（0.18em），字級一提整條列會明顯
          變長 —— 991.98px 以上才是橫排，窄一點的筆電要確認不會擠到 LOGO。 */
    font-size: var(--text-md);
    letter-spacing: var(--tracking-wider);
    text-transform: uppercase;
    text-decoration: none;
    color: hsl(var(--slate-700));
}

.site-nav a:hover { color: hsl(var(--foreground)); }

.site-nav a.active {
    font-weight: var(--weight-bold);
    color: hsl(var(--foreground));
}

/* .dropdown 是 block，其 line box 會受繼承字級的隱形 strut 撐高；
   改為 flex 容器即無 strut，高度由內部 <a> 決定，與其他項目一致。 */
.site-nav .dropdown {
    display: flex;
    align-items: center;
}

/* letter-spacing 在最後一個字之後也留白，使各項右緣多出 .18em、flex gap 視覺不均。
   補償要下在「flex item」層級（純 <a> 與 .dropdown 皆是），只加在 <a> 上會漏掉
   帶下拉的項目，造成兩種項目間距差 2.25px。 */
.site-nav > a,
.site-nav > .dropdown { margin-right: calc(var(--tracking-wider) * -1); }

/* 文字尾端已有字距，箭頭只需再補一點點
   ── 2026-08-03：Bootstrap 那個 border 疊出來的實心小三角整個關掉，改用
      _icon.php 的線性 chevron（見 .site-nav-caret）。原本這條的 margin 改由
      caret 自己帶。**留著 display:none 而不是刪掉這條規則**，是因為
      Bootstrap 隨時可能因為版本或其他 utility 把 ::after 叫回來。 */
.site-nav .dropdown-toggle::after { display: none; }

/*
 * 下拉指示（線性 chevron）。
 * ⚠️ 尺寸用 em 而不是固定 px：它要跟著導覽字級走，不然斷點一換就會比例失調。
 *    .8em 是配 --text-sm 調的，看起來與文字同一個重量。
 * 展開時轉 180° —— 這是 CSS transition 不是 keyframe，
 * --duration 在 prefers-reduced-motion 下已歸零，不必另外寫守衛。
 */
.site-nav-caret {
    display: inline-flex;
    margin-left: .35em;
    font-size: .8em;
    transition: rotate var(--transition);
}

/* Bootstrap 展開時會把 .show 加在 toggle 上 */
.site-nav .dropdown-toggle.show .site-nav-caret { rotate: 180deg; }

/* 放大鏡。導覽文字是 --text-sm，圖示照那個字級會偏小（線性圖示的視覺重量
   本來就比文字輕），拉到 --text-lg 才與旁邊的字看起來一樣份量。
   ⚠️ 這個 class 只給**單獨一顆圖示、沒有文字**的情形用（導覽列那顆）。
      圖示與文字並排時不要套它 —— 見下方 .site-btn .site-icon。 */
.site-nav-search {
    display: inline-flex;
    font-size: var(--text-lg);
}

/* 圖示與文字並排在按鈕裡（搜尋頁的送出鈕）。
   .site-btn 是 inline-block，SVG 走基線對齊會偏低半個字，這裡補回來。
   尺寸維持 1em ＝ 與按鈕文字同級，不必像導覽列那顆放大。 */
.site-btn .site-icon { vertical-align: -.15em; }

/*
 * ─── 下拉選單（2026-08-03 改版，使用者指定「要跟現在首頁的風格匹配」）──
 *
 * 走與全站按鈕同一套玻璃構造：半透明 ＋ 細描邊 ＋ 背景模糊 ＋ 大一階的圓角，
 * 而且**沒有陰影**（2026-07-30 下午全站按鈕與卡片的陰影已一律移除）。
 *
 * ⚠️ 底色**不能直接用 --glass-fill**。那組是為「鈕」調的（深色態只有 10% 白），
 *    面板底下會有整頁的內容在跑，10% 透明度會讓選單文字與背後的照片糊在一起。
 *    面板需要的是能讀字的遮罩，所以這裡自己給 .88／.82 的高不透明度 ——
 *    構造相同、濃度不同，這是刻意的分工，不要「統一」回 --glass-fill。
 *    對比：淺色態 white/.88 疊在任何底上 ≥ 246，深色態 black/.82 疊在最亮的
 *    照片(238)上是 43，白字 15:1，兩邊都遠超 4.5:1。
 * ⚠️ 描邊沿用 --glass-line（會跟著 header 深淺態自動翻），只有底色自己給。
 */
.site-nav .dropdown-menu {
    min-width: 10rem;
    padding: var(--space-1);
    border: var(--border-width) solid var(--glass-line);
    border-radius: var(--radius-lg);
    background: hsl(var(--background) / .88);
    -webkit-backdrop-filter: var(--glass-blur);
            backdrop-filter: var(--glass-blur);
    box-shadow: none;
    margin-top: var(--space-2);
}

.site-nav .dropdown-item {
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius);
    font-size: var(--text-sm);
    letter-spacing: var(--tracking);
    color: hsl(var(--slate-700));
    transition: background-color var(--transition), color var(--transition);
}

.site-nav .dropdown-item:hover,
.site-nav .dropdown-item:focus {
    background: var(--glass-fill-hover);
    color: hsl(var(--foreground));
}

/*
 * 深色態：跟著 header 一起翻，並且**一起漸變**（使用者指定
 * 「一開始的子層顏色也要跟著改，捲動到服務專區後也是漸變成淺色」）。
 *
 * ⚠️ `.is-theming` 那條 transition 只掛在 .site-header 自己身上，
 *    子元素不會繼承轉場 —— 選單與項目要各自寫一份，否則 header 在漸變、
 *    下拉選單卻是瞬間跳色。
 * ⚠️ 深色態的項目文字用 --slate-300 而不是 --muted-foreground：
 *    與 .site-header.is-dark .site-nav a 同一階，兩層的次要文字才會同色。
 */
.site-header.is-theming .dropdown-menu,
.site-header.is-theming .dropdown-item {
    transition: background-color var(--duration-slow) var(--ease),
                border-color     var(--duration-slow) var(--ease),
                color            var(--duration-slow) var(--ease);
}

.site-header.is-dark .dropdown-menu {
    background: hsl(var(--black) / .82);
}

.site-header.is-dark .dropdown-item { color: hsl(var(--slate-300)); }

.site-header.is-dark .dropdown-item:hover,
.site-header.is-dark .dropdown-item:focus {
    background: hsl(var(--white) / .12);
    color: hsl(var(--white));
}

/* ─── 麵包屑 ─────────────────────────────────────────────────────── */

.site-crumb {
    padding: var(--space-3) var(--gutter-x) 0;
    font-size: var(--text-xs);
    letter-spacing: var(--tracking);
    color: hsl(var(--muted-foreground));
}

.site-crumb a {
    color: hsl(var(--muted-foreground));
    text-decoration: none;
}

.site-crumb a:hover { color: hsl(var(--foreground)); }

/* ─── 按鈕 ───────────────────────────────────────────────────────── */

/* 2026-07-30：加圓角（pill）。原本是直角細框，屬「印刷」的語彙；
   全站的進入鈕已統一成圓形，文字鈕跟著轉圓才是同一套。
   ⚠️ 刻意**不做成實心黑**：它承擔的是「看更多」這類次要動作，實心黑會比
      它旁邊的內容還響。要改成實心加 .site-btn-solid 就好，那個 class 還在。 */
/*
 * ─── 2026-08-03：改成玻璃鈕（使用者指定，全站按鈕統一）────────────────
 *
 * 構造 ＝ 半透明填色 ＋ 1px 細描邊 ＋ 背景模糊，顏色全部走 tokens.css 的
 * --glass-* 那一組（那裡寫了每個 alpha 是怎麼回推出來的，改值前先讀）。
 *
 * ⚠️ **模糊不是裝飾，它是這裡的必要條件。** 2026-07-31 的原註解記著：
 *    「透明底讓紙紋顆粒整片從鈕裡透上來，看起來像鈕沒有實體」，當時因此把底
 *    從 transparent 改成實心的 --card-soft。半透明本身沒解掉那個問題 ——
 *    **是 backdrop-filter 解掉的**：顆粒進到鈕裡會先被糊成一片均勻的霧，
 *    才會讀成「一塊磨砂玻璃」而不是「破了一個洞」。
 *    所以 **不要為了省效能把 backdrop-filter 拿掉只留半透明**，那會直接回到
 *    2026-07-31 修掉的那個 bug。要省請整顆改回實心。
 *
 * ⚠️ 深色區塊（.site-dark／header 深色態）不必在這裡處理 —— 那些選擇器覆寫的是
 *    --glass-* 本身（見下方），這條規則一個字都不用改就會跟著翻。
 */
.site-btn {
    display: inline-block;
    padding: var(--space-2) var(--space-4);
    border: var(--border-width) solid var(--glass-line);
    border-radius: var(--radius-pill);
    /*
     * 【改版前的原註解，保留備查】
     * 2026-07-31 由 transparent 改為 --card-soft。首頁的色帶加上紙紋顆粒之後，
     * 透明底讓顆粒整片從鈕裡透上來，看起來像鈕沒有實體。
     * 取 --card-soft 與卡片、服務專區面板同一個白 —— 全站的「可點的表面」
     * 是同一色，而且它比三個色帶都淺一階，鈕自然浮得出來。
     * ⚠️ 深色區塊裡 --card-soft 會被 .site-dark 反轉成深灰，不必另外處理。
     * hover 仍是 --nude-100（深一階），rest → hover 的方向沒有變。
     */
    background: var(--glass-fill);
    -webkit-backdrop-filter: var(--glass-blur);
            backdrop-filter: var(--glass-blur);
    color: hsl(var(--foreground));
    font-size: var(--text-sm);
    letter-spacing: var(--tracking);
    text-decoration: none;
    transition: background-color var(--transition), border-color var(--transition);
}

/* hover：填色壓深一階、描邊到底 —— 與改版前（card-soft → nude-100）同方向。
   ⚠️ **文字鈕的 hover 刻意不用 ember。** 深橘是全站唯一的高飽和色，用途嚴格
      限定在「進入語彙的圓鈕」（brand-spec §7b 的行為契約）。47 顆文字鈕一起
      會橘，那個顏色就不再是訊號了。圓鈕的 ember hover 見 .site-go。 */
.site-btn:hover {
    background: var(--glass-fill-hover);
    border-color: var(--glass-line-hover);
}

/* 主要動作：黑底白字，對比 21:1
   ⚠️ **這一顆刻意留在實心，不做成玻璃**（2026-08-03）。玻璃是「次要動作」的
      語彙 —— 全站鈕都變成同一種半透明之後，主要動作若也跟著透，頁面上就沒有
      任何一顆說得出「這才是要按的那顆」。實心黑正是玻璃的對照組。
   ⚠️ 底是不透明的，所以要把繼承自 .site-btn 的模糊關掉：留著不會有視覺效果，
      只是白白多一層合成。 */
.site-btn-solid {
    background: hsl(var(--primary));
    border-color: hsl(var(--primary));
    color: hsl(var(--primary-foreground));
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
}

.site-btn-solid:hover {
    background: hsl(var(--slate-800));
    border-color: hsl(var(--slate-800));
    color: hsl(var(--primary-foreground));
}

.site-btn-block {
    display: block;
    width: 100%;
    text-align: center;
}

/*
 * 「看更多」按鈕列（首頁的實體課程與知識專區，2026-07-31 新增）。
 *
 * 上下都用 --space-5，是為了讓兩段距離對齊既有的節奏：
 *   上 48px  ＝ .site-h2 的 margin-bottom（區塊標題到內容的距離）
 *   下 48px  ＋ section 的 96px 內距 ＝ 144px
 *            ＝ 服務專區面板底到 section 底的距離（.site-vtabs 也是 --space-5）
 * 三處綁在同一個 token 上，改一次就一起動 —— 原本用的是 Bootstrap 的 mt-4
 * （24px），跟誰都對不上。
 *
 * ⚠️ 只給「區塊底部的次要動作」用。內頁（course_detail、content_generic）
 *    那幾個 .text-center.mt-4 是版型內的按鈕，不是區塊收尾，沒有一併改。
 */
.site-more {
    margin-top: var(--space-5);
    margin-bottom: var(--space-5);
}

/* ─── 標籤 ───────────────────────────────────────────────────────── */

.site-pill {
    display: inline-block;
    margin: var(--space-1) var(--space-1) 0 0;
    padding: 0.12rem var(--space-2);
    border: var(--border-width) solid transparent;
    border-radius: var(--radius-pill);
    background: hsl(var(--pill));
    color: hsl(var(--pill-foreground));
    font-size: var(--text-2xs);
    text-decoration: none;
}

/* 選中態：反白為黑底 */
.site-pill-dark {
    background: hsl(var(--primary));
    border-color: hsl(var(--primary));
    color: hsl(var(--primary-foreground));
}

/* 標籤（#tag）走冷色，與分類 pill 區分 */
.site-tag {
    background: hsl(var(--tag));
    color: hsl(var(--tag-foreground));
}

/* ─── 卡片與分隔線 ───────────────────────────────────────────────── */

.site-card {
    background: hsl(var(--card));
    border: var(--border-width) solid hsl(var(--border));
}

.site-divider-b { border-bottom: var(--border-width) solid hsl(var(--border)); }
.site-divider-t { border-top: var(--border-width) solid hsl(var(--border)); }

/* ─── 表單 ───────────────────────────────────────────────────────── */

.site-input {
    width: 100%;
    padding: var(--space-2) var(--space-2);
    border: var(--border-width) solid hsl(var(--input));
    border-radius: var(--radius-sm);
    background: hsl(var(--background));
    font-size: var(--text-base);
    color: hsl(var(--foreground));
    transition: border-color var(--transition);
}

.site-input:focus {
    border-color: hsl(var(--ring));
    outline: none;
    box-shadow: 0 0 0 3px hsl(var(--ring) / 0.18);
}

/* ─── 資訊區塊與表格 ─────────────────────────────────────────────
   原本在 content_generic.php 與 course_detail.php 各寫一份幾乎逐字相同的
   inline style（共 16 條宣告）。 */

.site-infobox {
    padding: var(--space-4);
    border: 2px solid hsl(var(--slate-500));
    background: hsl(var(--muted));
}

.site-infotable {
    width: 100%;
    border-collapse: collapse;
}

.site-infotable th,
.site-infotable td {
    padding: var(--space-2);
    border-bottom: var(--border-width) solid hsl(var(--border));
    text-align: left;
    vertical-align: top;
}

.site-infotable th {
    width: 8rem;
    font-size: var(--text-base);
    font-weight: var(--weight-medium);
    color: hsl(var(--foreground));
    white-space: nowrap;
}

.site-infotable td {
    font-size: var(--text-base);
    color: hsl(var(--muted-foreground));
}

@media (max-width: 575.98px) {
    /* 窄幅改上下堆疊，避免固定 8rem 的 th 把內容擠成一直排 */
    .site-infotable th,
    .site-infotable td { display: block; width: auto; }
    .site-infotable th { border-bottom: 0; padding-bottom: 0; }
}

/* 主要 CTA：比一般按鈕更寬鬆 */
.site-btn-lg { padding: var(--space-2) var(--space-5); }

/* ─── 小型工具 class ─────────────────────────────────────────────
   字距在本站是視覺識別的一部分，出現頻率高到值得做成 utility，
   避免各處又寫回 inline style。 */

.site-tracking       { letter-spacing: var(--tracking); }
.site-tracking-wide  { letter-spacing: var(--tracking-wide); }
.site-tracking-wider { letter-spacing: var(--tracking-wider); }

/* 完成頁的大勾號 */
.site-check-icon {
    font-size: 2.4rem;
    line-height: var(--leading-none);
}

/* 條款捲動區（會員同意聲明） */
.site-scrollbox {
    max-height: 340px;
    overflow-y: auto;
    padding-right: var(--space-2);
}

/* 行內表單（Email + 送出），限制寬度避免在寬螢幕拉太長 */
.site-form-inline { max-width: 480px; }

/* 手機版分類篩選的下拉選單（JS 以 display 切換） */
.site-filter-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: var(--z-dropdown);
    max-height: 260px;
    overflow-y: auto;
    border: var(--border-width) solid hsl(var(--border));
    border-radius: var(--radius);
    background: hsl(var(--background));
    box-shadow: var(--shadow-md);
}

/* ─── 圖片（搭配 _img.php 元件）──────────────────────────────────
   比例容器優先用 Bootstrap 原生 .ratio .ratio-16x9 / .ratio-4x3 / .ratio-1x1，
   這裡只補框架沒有的：填滿裁切、banner 專用比例。 */

/* <picture> 只是「挑格式」的容器，本身不該影響版面，撐滿外層比例容器即可。
   Bootstrap 的 .ratio > * 只會作用在第一個子元素（也就是 picture），
   內層 <img> 的尺寸得自己給，否則 .site-banner 這種非 .ratio 容器會塌掉。 */
.site-img-wrap {
    display: block;
    width: 100%;
    height: 100%;
}

.site-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;   /* 素材原始比例不一，一律置中裁切填滿，避免變形或留白 */
}

/* 搜尋結果縮圖：固定寬度、不隨 flex 壓縮 */
.site-search-thumb {
    width: 240px;
    max-width: 100%;
    flex-shrink: 0;
}

/* 內文插圖：略窄於文字欄寬，與段落拉出層次 */
.site-content-img { max-width: 80%; }

/* ─── 輪播 ───────────────────────────────────────────────────────── */

/*
 * 內嵌輪播（知識內頁的相關文章）：箭頭與指示點疊在照片上，不外移。
 */
.site-carousel-inset .carousel-control-prev,
.site-carousel-inset .carousel-control-next {
    width: 4%;
    opacity: .7;
}

.site-carousel-inset .carousel-control-prev-icon,
.site-carousel-inset .carousel-control-next-icon { filter: invert(1); }

.site-carousel-inset .carousel-indicators { margin-bottom: var(--space-2); }

.site-carousel-inset .carousel-indicators [data-bs-target] {
    background: hsl(var(--slate-400));
    opacity: 1;
}

.site-carousel-inset .carousel-indicators .active { background: hsl(var(--foreground)); }

/* ─── 右下浮動社群選單 ───────────────────────────────────────────── */

.site-float {
    position: fixed;
    right: var(--space-3);
    bottom: var(--space-3);
    z-index: var(--z-float);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.site-float a {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    /* 2026-08-03：跟著全站改成玻璃鈕。
       ⚠️ 這兩顆是 `position: fixed`，**底下是什麼都有可能** —— 純黑主視覺、
          紙面色帶、照片、深色 footer 全都會經過。所以用會跟著底翻的
          --glass-*（淺色區走亮玻璃、進到 .site-dark 區時那組 token 會被覆寫），
          而不是 .site-go 那種固定深色的 --glass-media-*：後者在深色 footer 上
          會變成「深色鈕貼在深色底」。
       ⚠️ 也因此它的 hover **不用 ember** —— 它連到的是外部平台，不是站內的
          進入語彙，ember 只屬於 .site-go（brand-spec §7b）。 */
    border: var(--border-width) solid var(--glass-line);
    border-radius: var(--radius-full);
    background: var(--glass-fill);
    -webkit-backdrop-filter: var(--glass-blur);
            backdrop-filter: var(--glass-blur);
    color: hsl(var(--foreground));
    /* 2026-07-31：內容由 emoji 換成 _icon.php 的圖示（1em 見方）之後字級跟著上調。
       emoji 自帶的字身比線稿圖示大一截，沿用 --text-md 會讓圖示在 46px 的圓裡
       顯得空。--text-lg 與 .site-go 同一階，兩種圓鈕的圖示大小才對得上。 */
    font-size: var(--text-lg);
    text-decoration: none;
    /* 陰影於 2026-07-30 下午拿掉（全站按鈕一律無陰影）。
       它浮在內容之上，靠 --slate-500 的描邊與純白底分界，不靠陰影。 */
    transition: background-color var(--transition);
}

.site-float a:hover {
    background: var(--glass-fill-hover);
    border-color: var(--glass-line-hover);
}

/* ─── 步驟條（會員流程，原 6 個檔案逐字複製的 inline style）───────── */

.site-steps {
    display: flex;
    align-items: center;
    justify-content: center;
}

.site-step {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.site-step-no {
    display: flex;
    align-items: center;
    justify-content: center;
    /* 原為 26px；略放大到 28px 以接近 WCAG 2.2 的最小點擊目標建議 */
    width: 1.75rem;
    height: 1.75rem;
    border: var(--border-width) solid hsl(var(--slate-400));
    border-radius: var(--radius-full);
    background: hsl(var(--background));
    color: hsl(var(--muted-foreground));
    font-size: var(--text-sm);
}

.site-step-label {
    font-size: var(--text-sm);
    color: hsl(var(--muted-foreground));
}

/* 進行中／已完成 */
.site-step.is-on .site-step-no {
    background: hsl(var(--primary));
    border-color: hsl(var(--primary));
    color: hsl(var(--primary-foreground));
}

.site-step.is-on .site-step-label {
    color: hsl(var(--foreground));
    font-weight: var(--weight-medium);
}

/* 步驟之間的連接線 */
.site-step-line {
    width: 3rem;
    height: var(--border-width);
    margin: 0 var(--space-2);
    background: hsl(var(--slate-300));
}

@media (max-width: 575.98px) {
    .site-step-line { width: 1.5rem; }
    .site-step-label { display: none; }
}

/* ─── Footer ─────────────────────────────────────────────────────── */

/*
 * 【2026-07-31：語意反轉與底色搬到共用的 .site-dark】
 * 這裡原本自己覆寫 --foreground / --muted-foreground / --border 並寫
 * `background: hsl(var(--primary))`，是當時全站唯一的深色面。首頁的實體課程區
 * 也要深色之後，兩邊各寫一份遲早走鐘，所以抽成 .site-dark（見檔案上方）。
 * 標記改成 `<footer class="site-footer site-dark site-slab">`。
 *
 * ⚠️ 底色那一行**不能留著**：.site-dark 把 --primary 反轉成白（實心按鈕要反白），
 *    `hsl(var(--primary))` 在這裡會變成白底。底色一律由 .site-dark 的
 *    --background 提供。
 *
 * 上緣圓角與負 margin 由 .site-slab 提供，全站每一頁的 footer 因此一致。
 */
.site-footer {
    padding: var(--section-y) var(--gutter-x) var(--space-4);
}

/* 品牌敘述（取代原本的 .wf-tline 佔位條）。比一般內文亮一階 ——
   它是 footer 裡唯一一句品牌語言，用 --muted-foreground 會太退。 */
.site-footer-lede {
    max-width: 22em;
    color: hsl(var(--foreground));
    font-size: var(--text-base);
    line-height: var(--leading-normal);
}

.site-footer-social {
    display: flex;
    gap: var(--space-2);
    margin-bottom: var(--space-2);
}

.site-footer a {
    color: hsl(var(--muted-foreground));
    font-size: var(--text-sm);
    text-decoration: none;
}

.site-footer a:hover { color: hsl(var(--foreground)); }

.site-footer hr { border-color: hsl(var(--border)); }

/* Footer 的 LOGO 比 header 小一號、字距也收窄
   （原本三處各寫一次 .12em/.14em/.32em，現統一由 token 控制） */
.site-footer .site-logo {
    font-size: var(--text-lg);
    letter-spacing: var(--tracking-wide);
}

/* ─── 電子報訂閱框（虛線） ───────────────────────────────────────── */

.site-subscribe {
    border: var(--border-width) dashed hsl(var(--slate-500));
    background: hsl(var(--muted));
}

/* ═══════════════════════════════════════════════════════════════════
   Wireframe 標註 — 上線前整批移除
   這些不是正式元件，刻意不做 token 化，避免與正式設計混淆。
   ═══════════════════════════════════════════════════════════════════ */

/*
 * 安靜模式 — 隱藏溝通標註，讓版面能以「接近成品」的樣子檢視
 *
 * body.wf-quiet 由 main.php 掛上，目前預設只在首頁生效（見該檔說明），
 * 可用 ?wf=on / ?wf=off 即時切換，做法與 ?theme= 同一套。
 *
 * ⚠️ 只隱藏「標註」，不動「假內容佔位」：
 *   隱藏  .wf-label（區塊說明）/ .wf-note（補充）/ .wf-tag（模組標籤）/ .wf-bp（斷點指示）
 *   保留  .wf-dummy（示意文字）/ .wf-tline（灰色佔位條）
 *   後兩者是卡片與 footer 的內容本體，藏掉會變成空殼、撐不出正確的版面高度，
 *   反而看不出成品的樣子 —— 與本模式的目的相反。
 *
 * 這是「隱藏」不是「移除」：標註仍在 DOM 裡，上線前整批移除的工作照舊要做。
 */
.wf-quiet .wf-label,
.wf-quiet .wf-note,
.wf-quiet .wf-tag {
    display: none;
}

/* 斷點指示的四個 <span> 各自帶著 Bootstrap 的 .d-block / .d-none.d-lg-block 等
   顯示 utility，而 Bootstrap 的 display utility 一律是 !important，
   普通宣告蓋不掉 —— 這裡必須同樣用 !important 才會真的消失。 */
.wf-quiet .wf-bp {
    display: none !important;
}

.wf-label {
    display: inline-block;
    max-width: 100%;
    margin: 0 0 1rem;
    padding-left: .55rem;
    border-left: 2px solid #ffdbe6;
    /* 2026-07-31 由 #b1949e 調深一階。首頁三個區塊改成逐階加深的暖色帶之後，
       這個粉灰在最深的那一階（nude-200）只剩 2.19，比原本在純白上的 2.76 更差。
       #9a7583 在四種底色上分別是 4.00 / 3.79 / 3.57 / 3.18，處處都不比原本差。
       ⚠️ 仍然刻意低於 4.5 —— 它是線框註記，正式版會整批移除，不該與內容
          搶注意力。要提高可讀性請往 #8e6877 之後走，但先想清楚是否該搶。 */
    color: #9a7583;
    font-size: .72rem;
    font-weight: 500;
    letter-spacing: .02em;
    word-break: break-word;
}

.site-footer .wf-label { color: #9a9a9a; border-left-color: #6a6a6a; }

.wf-note {
    display: inline-block;
    margin-top: .5rem;
    padding: .3rem .6rem;
    border: 1px dashed #d9c85a;
    border-radius: 4px;
    background: #fffceb;
    color: #7a6f2a;
    font-size: .7rem;
}

.wf-tag {
    display: inline-block;
    padding: .5rem .8rem;
    border: 1px solid #eee;
    background: #fff;
    box-shadow: 0 2px 8px rgb(0 0 0 / .12);
    color: #666;
    font-size: .72rem;
}

.wf-dummy { color: #c9c9c9; font-size: .82rem; line-height: 1.9; word-break: break-word; }

.wf-tline { height: .7rem; margin: .4rem 0; border-radius: 2px; background: #ededed; }
.site-footer .wf-tline { background: #565656; }   /* 深底上的佔位條 */
.wf-tline.w-80 { width: 80%; }
.wf-tline.w-60 { width: 60%; }
.wf-tline.w-40 { width: 40%; }

.wf-bp {
    position: fixed;
    right: 8px;
    bottom: 8px;
    z-index: 50;
    padding: .2rem .5rem;
    border-radius: 3px;
    background: #000;
    color: #fff;
    font-size: .68rem;
    opacity: .5;
}

/* ═══════════════════════════════════════════════════════════════════
   響應式
   斷點一律用 Bootstrap 的 .98 寫法（先前 991px 與 991.98px 並存，
   中間有 0.98px 兩邊規則都不套用的縫隙）。
   間距類由 tokens.css 的 --gutter-x / --section-y 自動收斂，此處只放
   真的需要改變結構的規則。
   ═══════════════════════════════════════════════════════════════════ */

@media (max-width: 991.98px) {

    .site-header .inner { flex-wrap: wrap; }

    .site-logo { letter-spacing: var(--tracking-wide); }

    /* 導覽改直向堆疊 */
    .site-nav {
        flex-direction: column;
        align-items: flex-start;
        gap: var(--space-2);
        width: 100%;
        margin-top: var(--space-2);
        padding-top: var(--space-2);
        border-top: var(--border-width) solid hsl(var(--border));
    }

    /* 展開時浮層覆蓋在內容上方（不推擠下方內容） */
    .site-nav.show,
    .site-nav.collapsing {
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        z-index: var(--z-dropdown);
        max-height: calc(100vh - 68px);
        margin-top: 0;
        padding: var(--space-3) var(--gutter-x);
        overflow-y: auto;
        background: hsl(var(--background));
        border-top: var(--border-width) solid hsl(var(--border));
        box-shadow: var(--shadow-lg);
    }

    .site-nav a {
        width: 100%;
        padding: var(--space-1) 0;
        border-radius: var(--radius);
    }

    .site-nav a:hover {
        padding-left: var(--space-2);
        background: hsl(var(--muted));
    }

    /* 手機版子選單為靜態展開，需還原 block 排列，
       否則 menu 會成為 flex item 與 toggle 並排 */
    .site-nav .dropdown { display: block; width: 100%; }

    .site-nav .dropdown-menu {
        position: static !important;
        display: block;
        float: none;
        margin: 0;
        padding: .1rem 0 .3rem var(--space-3);
        border: 0;
        background: transparent;
        box-shadow: none;
        transform: none !important;
    }

    .site-nav .dropdown-menu a {
        padding: var(--space-1) var(--space-2);
        font-size: var(--text-base);
        color: hsl(var(--muted-foreground));
        border-radius: var(--radius);
    }

    .site-nav .dropdown-menu a:hover {
        background: hsl(var(--muted));
        color: hsl(var(--foreground));
    }

    .site-nav .dropdown-toggle::after { display: none; }

    /* 2026-08-03：線性 chevron 在窄幅也要收掉，理由與上面那條相同 ——
       手機版的子選單是**常駐展開**的（見上方 .dropdown-menu 的 position:static），
       一個永遠指著下、永遠不會轉的指示符號等於在說謊。 */
    .site-nav-caret { display: none; }

    /*
     * ═══ 手機展開選單的深色態（2026-08-03 下午，使用者回報）═══════════
     *
     * 【壞掉的樣子】壓在黑色主視覺上打開漢堡時：面板是**白底**，但連結吃到
     * `.site-header.is-dark .site-nav a` 的 --slate-300（淺灰），白底淺灰字
     * 幾乎讀不到；而子選單又被桌機那組浮層樣式套成**黑底圓角毛玻璃塊**，
     * 於是白面板上浮著幾塊黑磚。
     *
     * 【成因：specificity，不是漏寫】
     *   .site-header.is-dark .dropdown-menu   (0,3,0)  ← 三個 class
     *   .site-nav .dropdown-menu              (0,2,0)  ← 這個媒體查詢裡的重設
     * 雖然重設寫在後面，但少一個 class 就是輸。所以下面的規則**都必須寫到
     * (0,3,0) 以上**，不能只靠「寫在後面」。這是這一區最容易再犯的錯。
     *
     * 【要的行為】在主視覺上＝整片深色，捲到服務專區後＝跟著 header 變回淺色。
     * 只要跟著 .is-dark 走就自動成立，`.is-theming` 的漸變也一起繼承。
     */
    .site-header.is-dark .site-nav.show,
    .site-header.is-dark .site-nav.collapsing {
        /* .92 不是 1：header 本身是毛玻璃，面板全不透明會在交界處露出一條
           明顯的色差。留一點透光，兩層才像同一塊玻璃。 */
        background: hsl(var(--black) / .92);
        -webkit-backdrop-filter: var(--glass-blur);
                backdrop-filter: var(--glass-blur);
        border-top-color: hsl(var(--white) / .18);
    }

    .site-header.is-dark .site-nav a:hover {
        background: hsl(var(--white) / .12);
        color: hsl(var(--white));
    }

    /* 子選單在手機上是**常駐展開的清單**，不是浮層 —— 桌機那組面板樣式
       （黑底、圓角、毛玻璃、描邊）整組還原成沒有。
       ⚠️ 這條要壓過 (0,3,0) 的 .site-header.is-dark .dropdown-menu，
          所以寫成 .site-header.is-dark .site-nav .dropdown-menu (0,4,0)。 */
    .site-header.is-dark .site-nav .dropdown-menu {
        border: 0;
        border-radius: 0;
        background: transparent;
        -webkit-backdrop-filter: none;
                backdrop-filter: none;
    }

    /* 子項目比上一層再淡一階，讀得出「這是第二層」。
       (0,4,1) 壓得過 .site-header.is-dark .dropdown-item 的 (0,3,0)。 */
    .site-header.is-dark .site-nav .dropdown-menu a {
        color: hsl(var(--slate-400));
    }

    .site-header.is-dark .site-nav .dropdown-menu a:hover,
    .site-header.is-dark .site-nav .dropdown-menu a:focus {
        background: hsl(var(--white) / .12);
        color: hsl(var(--white));
    }

    /* 淺色態（捲過服務專區之後）也要把桌機的浮層樣式收掉 —— 在此之前
       子選單在淺色態同樣頂著圓角＋毛玻璃＋描邊，只是白底上看不太出來。
       這條沒有 .is-dark，用 .site-header 補到 (0,3,0) 打平。 */
    .site-header .site-nav .dropdown-menu {
        border-radius: 0;
        -webkit-backdrop-filter: none;
                backdrop-filter: none;
    }

    /* 窄幅時 section 左右留白放不下箭頭，改疊在內容上 */
    .site-carousel .carousel-control-prev { left: 0; }
    .site-carousel .carousel-control-next { right: 0; }

    .wf-label { display: block; }
}

@media (max-width: 767.98px) {
    .site-content-img { max-width: 100%; }
}

@media (max-width: 575.98px) {
    .site-search-thumb { width: 100%; }
}

/* ─── 向右箭頭圖示（site-arrow）────────────────────────────────────
 * 標記與來源／授權見 app/Views/Front/wireframe/_arrow.php。
 * 首頁主視覺與服務專區共用同一支，樣式也只在這裡定義一次。
 */

/*
 * 【2026-07-30 下午：靜止斜 45°、hover 轉正】（使用者指定，附參考圖）
 * 圖示本體是水平向右的 →（見 _icon.php 的 arrow-right 幾何，那支是共用的，
 * **不要去改它的路徑**）。斜角一律由這裡給：
 *   靜止  rotate: -45deg → 指向右上 ↗（羅盤 45°）
 *   hover rotate: 0deg   → 轉正成水平 →（羅盤 90°）
 *
 * ⚠️ 用的是**獨立變換屬性** `rotate:`，不是 `transform: rotate()`。這不是風格
 *    偏好，是為了讓轉向與「頂一下」（site-arrow-nudge，走 `translate:`）能並存。
 *    兩者都寫進同一個 transform 值的話，keyframe 那一份會整個蓋掉這裡的角度，
 *    播動畫時箭頭會瞬間彈平再轉回去 —— 這是實際踩過的坑，先前一度只好把
 *    「頂一下」整個拿掉。拆成兩個屬性之後各自補間，互不干擾。
 *    合成順序是 translate → rotate，所以位移走的是**未旋轉的水平軸**（往右頂），
 *    不會因為靜止時是斜的就斜著頂。
 *
 * ⚠️ 別把 `rotate:` 改回 `transform:`。瀏覽器支援沒問題（Chrome 104 / Safari
 *    14.1 / Firefox 72 起），真正的代價是上面那個坑會立刻回來。
 */
.site-arrow {
    display: block;
    /* 圖示本體是 1em；字級由使用它的地方決定 */
    flex-shrink: 0;
    rotate: -45deg;
    /* --duration-slow 已被 prefers-reduced-motion 那組歸零，
       減少動態時轉向直接發生、不補間，不必另外寫守衛。
       opacity 這一項只有服務專區用得到（＋與箭頭疊著淡入淡出，
       見 .site-vtabs-more），其他五處的箭頭 opacity 從頭到尾都是 1。 */
    transition: rotate var(--duration-slow) var(--ease),
                opacity var(--transition);
}

/*
 * hover 圓鈕時往右頂一下再回來。原 registry item 唯一的動畫
 * （motion 的 `x: [0, 4, 0]`），用 keyframe 一比一還原，不引進函式庫。
 *
 * ⚠️ 動的是 `translate:` 這個獨立屬性，**不可以寫成 `transform: translateX()`**
 *    —— 那會連帶把 .site-arrow 的 rotate 一起歸零（理由見上）。
 *
 * 觸發條件是**滑到圓鈕本身**，與「變色」同一組選擇器、和「轉向」不同：
 * 轉向是「這一塊進得去」（滑到整張卡就給），頂一下與變色是「你正要按這一顆」。
 * 三者的分工寫在 brand-spec 第 7b 節的行為契約表。
 *
 * 時長用 --duration-slow：那組 token 已被 prefers-reduced-motion 歸零，
 * 減少動態時整個動畫自然消失，不必另外寫守衛。
 */
@keyframes site-arrow-nudge {
    0%   { translate: 0; }
    50%  { translate: 4px; }
    100% { translate: 0; }
}

.site-go:hover .site-arrow,
.site-go:focus-visible .site-arrow {
    animation: site-arrow-nudge var(--duration-slow) var(--ease);
}

/*
 * 觸發：滑到圓鈕本身，或滑到「整個操作單位」（整格／整張卡／整個項目）。
 * 六處的觸發器集中寫在這裡，是因為它們是同一個互動語彙 ——
 * 分散到各自的區塊裡，改一邊忘另一邊就會不一致。
 *
 * ⚠️ **這組（轉向）的觸發器比上面那組（頂一下）寬，是刻意的，不要拉齊。**
 *    轉向是 transition：只要 hover 還在，終點狀態就一直維持著，所以掛在外層
 *    也成立 —— 主視覺格子與服務專區的箭頭是隨項目展開才現形的，什麼時候現形
 *    都看得到已經轉正的樣子。
 *    「頂一下」是 keyframe 動畫，掛在外層會**箭頭還沒淡入就播完**（實測過），
 *    補 delay 又會變成滑進去不重播，所以只能掛在圓鈕自己身上。
 *    語意上也剛好對得起來：轉向說「這一塊進得去」，頂一下說「你正要按這一顆」。
 *
 * 圓鈕自己被滑到／聚焦 —— 一顆選擇器管全部六處（見 .site-go）。
 * 服務專區的上一個／下一個裡面是 Bootstrap 的圖示、知識卡下方兩顆是
 * radio／youtube，都沒有 .site-arrow，所以不會誤觸發。
 */
.site-go:hover .site-arrow,
.site-go:focus-visible .site-arrow,
.site-cardgrid-cell:hover .site-arrow,
.site-cardgrid-cell:focus-visible .site-arrow,
.site-coursecard:hover .site-arrow,
.site-coursecard:focus-visible .site-arrow,
.site-kncard:hover .site-arrow,
.site-kncard:focus-within .site-arrow,
.site-vtabs-item:hover .site-arrow,
.site-vtabs-item:focus-within .site-arrow,
.site-svc-hero:hover .site-arrow,
.site-svc-hero:focus-visible .site-arrow,
.site-svc-chip:hover .site-arrow,
.site-svc-chip:focus-visible .site-arrow {
    rotate: 0deg;
}

/* ─── 圖示的 hover 動畫（site-icon-*）──────────────────────────────
 * 移植自 itshover registry 各圖示的 motion 行為，標記見 _icon.php。
 * 原版用 useAnimate() 逐個 element 補間；這裡全部改成 CSS keyframes。
 *
 * ⚠️ 時長一律寫成 token 的 calc 倍數，**不可寫死秒數**。
 *    prefers-reduced-motion 是靠 tokens.css 把 --duration-* 歸零達成的
 *    （單一開關、SSOT）。寫死 0.6s 會繞過那個開關 —— 使用者關掉動態之後
 *    圖示照樣在跳，而且不會有人想到要來這裡找。
 *
 * ⚠️ SVG 子元素的 transform-origin 要搭 transform-box: view-box 才會以
 *    viewBox 座標為基準（12px 12px = 畫布正中心）。少了它，瀏覽器會用
 *    各個 path 自己的 bounding box，弧線會繞著自己的端點亂轉。
 */

.site-icon-core,
.site-icon-wave-i,
.site-icon-wave-o,
.site-icon-frame,
.site-icon-play {
    transform-box: view-box;
    transform-origin: 12px 12px;
}

/* radio（podcast）：中心點脈動，內外兩層弧線依序往外擴散並淡出。
   三層錯開的 delay 是「正在播送」的關鍵 —— 同時動會變成整枚圖示在抖。 */
@keyframes site-icon-pulse {
    0%, 100% { transform: none; }
    50%      { transform: scale(1.3); }
}
@keyframes site-icon-wave-i {
    0%, 100% { transform: none;        opacity: 1; }
    50%      { transform: scale(1.15); opacity: .6; }
}
@keyframes site-icon-wave-o {
    0%, 100% { transform: none;       opacity: 1; }
    50%      { transform: scale(1.2); opacity: .4; }
}

.site-kncard-act:hover .site-icon-core {
    animation: site-icon-pulse calc(var(--duration-slow) * 1.6) var(--ease);
}
.site-kncard-act:hover .site-icon-wave-i {
    animation: site-icon-wave-i calc(var(--duration-slow) * 1.9) var(--ease)
               calc(var(--duration-fast) * 0.8);
}
.site-kncard-act:hover .site-icon-wave-o {
    animation: site-icon-wave-o calc(var(--duration-slow) * 2.2) var(--ease)
               calc(var(--duration-fast) * 1.6);
}

/* youtube：三角形先被「描出來」再縮一下。
   描繪靠 pathLength="1" + dashoffset 1→0（同 _rule_stroke.php 的手法，
   標記已在 _icon.php 帶好 pathLength，這裡不必量路徑長度）。
   兩段串接：scale 的 delay 等於描繪的時長。 */
@keyframes site-icon-draw {
    from { stroke-dashoffset: 1; }
    to   { stroke-dashoffset: 0; }
}
@keyframes site-icon-press {
    0%   { transform: none; }
    50%  { transform: scale(.8); }
    100% { transform: none; }
}

.site-kncard-act:hover .site-icon-play {
    stroke-dasharray: 1;
    animation: site-icon-draw var(--duration-slow) var(--ease),
               site-icon-press var(--duration-slow) var(--ease) var(--duration-slow);
}

/* hashtag / bookmark：原版就只是 hover 持續態（不是一次性補間），
   所以用 transition 而不是 keyframes —— 滑上去變、移開還原。 */
.site-icon-h,
.site-icon-v,
.site-icon-body {
    transform-box: view-box;
    transform-origin: center;
    transition: transform var(--transition), opacity var(--transition);
}

.site-kncard-tag:hover .site-icon-h { transform: scaleX(1.1); opacity: .7; }
.site-kncard-tag:hover .site-icon-v { transform: scaleY(1.1); opacity: .7; }
/* bookmark 往下壓一點點，做出「被收藏」的按下感 */
.site-kncard-tag:hover .site-icon-body { transform: scaleY(.9) translateY(2px); }

/* ─── 服務專區：垂直分頁（site-vtabs）──────────────────────────────
 * 移植自 21st.dev「@0xUrvish/vertical-tabs」，行為見 assets/front/js/vtabs.js。
 * 原版用 motion/react 做展開與滑入，這裡全部由 CSS 負責：
 *   展開 → grid-template-rows: 0fr → 1fr（不必量高度，也不必等 JS）
 *   滑入 → opacity + translateY
 *   進度 → @keyframes，時長由 JS 以 CSSOM 對齊 data-vt-interval
 */

/*
 * 2026-07-30：整區包成一張面板。
 *
 * 它接手的是**原本那五條分隔線的分區職責** —— 六個項目之間各畫一條 1px 線
 * 是「紙」的語彙，改成「一整塊面 ＋ 當前項目一塊白底」是同一件事的介面版。
 * 面板同時讓服務專區在全白頁面上自成一區，section 之間不必再靠線。
 *
 * ⚠️ 它是**容器內的面板**，不是滿版色帶（.site-bg-warm + .site-bleed）。
 *    色帶已於 brand-spec §6c 拿掉過一次（純裝飾、違反「設計降低至 15%」）；
 *    面板是元件自己的表面，寬度收在內容容器內，性質不同。要改成滿版色帶
 *    請先看那一節。
 */
/*
 * 2026-07-30 下午：整片底色拿掉，改用**有顏色的描邊**圈出這一區（使用者指定）。
 * 面板的職責沒變（把六個服務框成一區、接手原本那五條分隔線），只是分界從
 * 「一塊面」換成「一條有色的線」—— 底色一拿掉，頁面回到全白，卡片與這張面板
 * 就不會再有兩塊相近的暖白在打架。
 *
 * 描邊用 --border 的中性灰。曾短暫改用 --border-accent（暖橘），同日下午
 * 使用者要求拿掉顏色 —— 橘色留給互動狀態（底部進度線、展開項目的圓鈕），
 * 一條常駐的橘框會跟那兩個搶「這裡在動」的訊號。
 */
.site-vtabs {
    /* 進度線的高度在兩個地方要對得上（線自己、以及上一個／下一個那兩顆要
       讓開的距離），所以拉成元件層級的變數，不要各寫一次數字。 */
    --vt-rail-h: 2px;
    /* 上下鈕在大圖右下角佔掉的寬度：兩顆 .site-go（40px）＋ 中間的 gap ＋ 靠右的內距。
       摘要要靠它讓位，所以兩邊共用同一個定義（SSOT）—— 只改一邊就會撞在一起。
       ⚠️ .site-go 的 40px 是寫死在該元件裡的，這裡跟著它；改圓鈕尺寸要一起改。 */
    --vt-nav-w: calc(40px * 2 + var(--space-2) + var(--space-3));
    padding: var(--space-4);
    border: var(--border-width) solid hsl(var(--border));
    border-radius: var(--radius-lg);
    margin: 0 0 var(--space-5);
    /*
     * 2026-07-31 補上底色。在此之前這張面板是**透明**的（只有描邊），
     * 區塊加上紙紋顆粒之後，顆粒整片從面板底下透上來，看起來像面板髒了。
     *
     * 取 --card-soft，與知識卡、課程卡同一個白 —— 全站的「卡片表面」是同一色。
     * ⚠️ 它與所在區塊的底色（--surface-warm）**剛好同值**，所以面板不是靠
     *    顏色跟區塊分開，而是靠「有沒有顆粒」＋描邊。這是刻意的：乾淨的一張紙
     *    疊在有紋理的紙上。若哪天把顆粒拿掉，這張面板就會只剩描邊在分界，
     *    到時要重新決定它該不該換色。
     */
    background: hsl(var(--card-soft));
}

.site-vtabs-grid {
    display: grid;
    grid-template-columns: 5fr 7fr;
    gap: var(--space-5);
    align-items: start;
}

/* 六個服務全部列出（客戶決定），高度超出就在左欄內部捲動 ——
   不收合成「只顯示前三個」，也不讓左欄把右圖拉得老長。 */
.site-vtabs-list {
    max-height: 27rem;
    overflow-y: auto;
    /* 2026-07-30：原本要 padding-left 讓出貼在 left:0 的進度軌。軌道已收進
       項目內側（見 .site-vtabs-rail），這裡不必再讓 —— 面板自己已有 space-4
       內距，兩層加起來會縮掉 48px。 */
}

/* 2026-07-30：項目之間的 1px 分隔線拿掉了。分區改由「當前項目一塊白底」
   承擔 —— 六條線是紙的語彙，一塊高亮的面是介面的語彙，兩者做的是同一件事
   （告訴你「現在在這一項」＋「這些是一組」），但不重複疊在一起。
   ⚠️ 別把線加回來：加回來會變成又有線又有底色，那才是真的不統一。 */
.site-vtabs-item {
    position: relative;
    border-radius: var(--radius);
    transition: background var(--transition);
}

/*
 * 2026-07-30 下午：面板底色拿掉之後，這兩個狀態的用色整組重排。
 * 舊版是「暖白面板 ＋ 當前項目一塊純白」；面板現在是全白的，純白高亮等於
 * 沒有高亮，所以改成：
 * 展開中的項目底色**與卡片同一個值**（--card-soft 暖白）。中間短暫用過
 * --accent-soft 淡橘，同日稍晚使用者反映太深 —— 橘色現在只留給進度線與
 * 圓鈕 hover，那兩個是「正在動」的訊號，一塊常駐的底色不該跟它們同色系。
 * 全站因此只有一種「浮起來的面」。
 *
 * ⚠️ hover 與 is-on 給同一個值是刻意的，不是漏寫。vtabs.js 的 mouseenter
 *    直接就 choose(i)（見該檔 178 行），滑進去的瞬間項目就變 is-on，兩者
 *    本來就分不出先後。留著 :hover 是給 JS 還沒載入／整支拋錯時當回饋，
 *    否則那會是一份完全沒有反應的清單。
 */
/* 2026-07-31：面板本身補上 --card-soft 之後，這裡不能再用同一個值，
   否則被選中的項目跟面板一樣白、完全看不出來。改用深一階的 --surface-selected
   （落差 1.06，與首頁色帶的階距同尺度）。 */
.site-vtabs-item:hover,
.site-vtabs-item.is-on {
    background: hsl(var(--surface-selected));
}

/* 展開中的項目整塊可點（vtabs.js 的 item click → go()）。游標必須跟著變，
   否則那是一塊「點得到但看不出點得到」的區域。
   ⚠️ 只給 is-on。未展開的項目點下去是**展開**不是前往，那由標題鈕自己的
      cursor:pointer 表示，整塊給 pointer 會承諾一件它不做的事。 */
.site-vtabs-item.is-on {
    cursor: pointer;
}

/*
 * 輪播進度線。位置沿革（同一天內兩次）：
 *   項目左側的直軌（灰底＋深色填色，每項一條）
 *   → 項目底部的橫線（無底軌、橘色填色）
 *   → **右側大圖正下方，整個元件只留一條**（2026-07-30 下午定案，使用者指定）
 *
 * 【為什麼最後放在圖片下方】
 * 它量的是「還有多久換頁」—— 那是**元件層級**的狀態，不是某一項的屬性。
 * 掛在項目上時，六條裡永遠只有一條看得見，另外五條是同一個東西的閒置副本；
 * 而且它會隨著展開項目上下跳，讀起來像是那一項自己的裝飾。
 * 移到大圖下方之後位置固定，而大圖正是「換頁」真正換掉的東西。
 *
 * 【沒有底軌】空的軌道本身不帶資訊，只是一條常駐的線。因此沒在自動換頁時
 * （使用者已介入、減少動態、捲出畫面）這裡就是一片空白 —— 這是對的：
 * 不會換頁了，就沒有「還有多久」可以顯示。
 *
 * ⚠️ 量的維度是**寬度**。要改回直軌的話，下面的 @keyframes site-vtabs-fill
 *    得一起改，只改這裡會變成動畫跑了但看不出來。
 * overflow:hidden 是為了讓裡面的填色也吃到圓端；少了它填到一半是方角。
 */
.site-vtabs-rail {
    display: block;
    height: var(--vt-rail-h);
    margin-top: var(--space-2);
    overflow: hidden;
    border-radius: var(--radius-pill);
}

.site-vtabs-progress {
    display: block;
    width: 0;
    height: 100%;
    border-radius: var(--radius-pill);
    background: hsl(var(--accent-strong));
}

/* 自動換頁進行中才跑動畫。animation-duration 由 JS 寫入。 */
.site-vtabs-progress.is-running {
    animation: site-vtabs-fill linear forwards;
}

@keyframes site-vtabs-fill {
    from { width: 0; }
    to   { width: 100%; }
}

/* padding-left 是拿掉編號欄之後補的：進度軌貼在項目左緣，原本靠編號欄把
   標題擋開，現在文字會直接黏上那條線。不能改用 list 的 padding —— 軌道
   在項目內部，list 加 padding 是連軌道一起往右推。 */
.site-vtabs-tab {
    display: block;
    width: 100%;
    padding: var(--space-4) 0 var(--space-2) var(--space-3);
    border: 0;
    background: none;
    text-align: left;
    color: hsl(var(--muted-foreground));
    cursor: pointer;
    transition: color var(--transition);
}

.site-vtabs-tab:hover,
.site-vtabs-item.is-on .site-vtabs-tab {
    color: hsl(var(--foreground));
}

.site-vtabs-title {
    font-family: var(--font-display);
    font-size: var(--text-lg);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
}

/*
 * 展開／收合。grid-template-rows 從 0fr 補間到 1fr 是唯一不需要知道
 * 內容高度的純 CSS 做法（max-height 那套要猜一個夠大的值，收合會有延遲感）。
 * 內層必須 min-height:0，否則 grid 項目不會被壓到 0。
 */
.site-vtabs-body {
    display: grid;
    grid-template-rows: 0fr;
    /* 與標題切齊（同上，讓開進度軌） */
    padding-left: var(--space-3);
    transition: grid-template-rows var(--duration-slow) var(--ease);
}

.site-vtabs-item.is-on .site-vtabs-body {
    grid-template-rows: 1fr;
}

/*
 * 收合狀態下裡面的「了解更多」連結若仍可聚焦，鍵盤使用者會 tab 到一個
 * 看不見的連結。這件事**由 JS 的 inert 屬性處理，不在這裡用 CSS 做**：
 *
 * 原本寫的是 visibility:hidden + `transition: visibility 0s linear <delay>`
 * （延後隱藏，讓收合動畫跑完），實測收合後 computed visibility 仍停在
 * visible，連結照樣可聚焦 —— 帶延遲的 0s visibility transition 在收合方向
 * 不可靠。改用 inert：它移出 tab 序列與無障礙樹但完全不影響繪製，
 * 收合動畫維持原樣，且與 CSS 的補間時序無關。
 * 內容一直留在原始碼中（不是 display:none），六個連結對爬蟲永遠可見。
 */
.site-vtabs-body-inner {
    overflow: hidden;
    min-height: 0;
}

/*
 * 內文那一列。（箭頭已於 2026-07-30 下午移出這一列，見 .site-vtabs-more；
 * 這裡現在只剩說明文字，但 flex 與 padding-bottom 仍有用，別順手拆掉。）
 *
 * ⚠️ flex 要下在這一層，不能直接下在 .site-vtabs-body-inner 上：
 *    那一層在收合時高度會被壓到 0，若它同時是 align-items:center 的 flex
 *    容器，內容會在動畫過程中往上飄出去（置中是相對於正在縮小的框）。
 *    分成兩層後，這一列維持自然高度，由外層單純從底部裁掉。
 */
.site-vtabs-row {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding-bottom: var(--space-4);
}

.site-vtabs-sub {
    flex: 1;
    margin: 0;
    /* 右側要讓開絕對定位的 ＋／箭頭鈕（40px 寬 ＋ 兩側各 space-3），
       否則長一點的說明會跑到鈕底下。 */
    padding-right: calc(40px + var(--space-4));
    color: hsl(var(--muted-foreground));
    line-height: var(--leading-relaxed);
}

/*
 * ═══ 項目右側的「＋／箭頭」（.site-vtabs-more）═══════════════════════
 * 2026-07-30 下午改版（使用者指定）。同一顆 <a> 有兩種長相：
 *   未展開 → 裸的「＋」，無框無底，意思是「這裡還有東西」
 *   已展開 → 描邊圓鈕＋箭頭（.site-go 的原樣），是真正的入口
 *
 * 【為什麼是絕對定位】
 * 它原本坐在說明文字那一列裡，所以垂直位置跟著內文走、右側也沒有留白。
 * 要求是「垂直對齊項目正中、與容器留出間距」，那就必須脫離文件流 ——
 * 而且**只能相對於 .site-vtabs-item**（它是 position:relative）：
 * .site-vtabs-body-inner 是 overflow:hidden 的收合容器，掛在裡面會被裁掉。
 * 標記也因此一起搬出 body，見 _vtabs.php 的說明。
 *
 * 【為什麼用 grid 而不是 .site-go 的 flex】
 * ＋ 與箭頭要疊在同一格互相淡入淡出。grid-area:1/1 是最省的疊法，
 * 不必再包一層 position:relative 的殼。
 *
 * ⚠️ 選擇器**必須寫成 .site-vtabs-more.site-go**（0,2,0）。只寫
 *    .site-vtabs-more 的話與 .site-go 同為 0,1,0，而 .site-go 在本檔更後面，
 *    display:flex 會贏 —— 實測結果是兩個圖示變成**左右並排**塞在 40px 裡，
 *    ＋ 偏左、箭頭偏右，切換時會看到符號橫向跳動。
 */
.site-vtabs-more.site-go {
    position: absolute;
    top: 50%;
    right: var(--space-3);
    /* 覆寫 .site-go 的 flex —— 兩個圖示要疊在同一格 */
    display: grid;
    place-items: center;
    transform: translateY(-50%);
}

.site-vtabs-more > svg {
    grid-area: 1 / 1;
}

/* 未展開：褪成一個裸的加號。框與底都收掉，但**尺寸不變** ——
   40×40 的命中框留著，觸控裝置點得到，展開時也不會有位移。
   ⚠️ 2026-08-03 起 .site-go 帶 backdrop-filter，**這裡一定要跟著關掉** ——
      只把 border 與 background 透明化的話，會留下一個看不見的 40px 圓形
      模糊區浮在文字上（實測：未展開項目的說明文字會被糊掉一塊）。 */
.site-vtabs-item:not(.is-on) .site-vtabs-more {
    border-color: transparent;
    background: transparent;
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
    color: hsl(var(--muted-foreground));
}

.site-vtabs-item:not(.is-on) .site-arrow { opacity: 0; }
.site-vtabs-item.is-on .site-vtabs-plus { opacity: 0; }

.site-vtabs-plus {
    transition: opacity var(--transition);
}

/* 右側圖片面板 */
.site-vtabs-panel {
    position: relative;
}

.site-vtabs-figure {
    position: relative;
    display: block;
    aspect-ratio: 16 / 11;
    overflow: hidden;
    /* 降一階：它現在在 .site-vtabs 這張面板裡面（面板是 --radius-lg）。
       載入中的底色也從 --muted 的冷灰改成白 —— 冷灰壓在暖白面板上會發灰。 */
    border-radius: var(--radius);
    background: hsl(var(--card));
}

.site-vtabs-figure:focus-visible {
    outline: 2px solid hsl(var(--ring));
    outline-offset: 3px;
}

.site-vtabs-shot {
    position: absolute;
    inset: 0;
    opacity: 0;
    /* 進場方向：預設由下往上遞進；往回切換時 JS 加 .is-rev 反向 */
    transform: translateY(8%);
    transition: opacity var(--duration-slow) var(--ease),
                transform var(--duration-slow) var(--ease);
}

.site-vtabs-figure.is-rev .site-vtabs-shot {
    transform: translateY(-8%);
}

.site-vtabs-shot.is-on {
    opacity: 1;
    transform: none;
}

/* 進場前把起點瞬間套上去用的（vtabs.js 只掛一個重排的時間），
   不關掉過場的話它會用 --duration-slow 慢慢滑到起點，等於沒有起點。 */
.site-vtabs-shot.is-priming {
    transition: none;
}

/*
 * ⚠️ **這一條不能拿掉**（2026-07-30 修）。
 *
 * 上面那兩條是「進場方向」與「已就定位」，本來是互斥的狀態，但權重打架：
 *     .site-vtabs-figure.is-rev .site-vtabs-shot   (0,3,0)  ← 贏
 *     .site-vtabs-shot.is-on                       (0,2,0)
 * 往回切換時 JS 會在 figure 上掛 .is-rev，結果連**已經就定位的那張**也被按在
 * translateY(-8%)，圖整個往上推，figure 底部露出 8% 的白底（＝ --card 的底色）。
 * 看起來很像「圖片沒載入完、下面被切掉」，實際上圖是完整的，只是位置錯了。
 * 往前切換不會發生（那時沒有 .is-rev），所以只有往回切才重現得出來。
 *
 * 這裡用 (0,4,0) 把「就定位」拉回最高，是最小改動。方向類別掛在 figure 上
 * 本來就會涵蓋到不該管的那張，要根治得把方向改掛在 shot 自己身上（JS 也要動）。
 */
.site-vtabs-figure.is-rev .site-vtabs-shot.is-on {
    transform: none;
}

.site-vtabs-shot .site-img,
.site-vtabs-shot img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/*
 * 大圖上的摘要（2026-07-30 新增，使用者指定，作法比照知識卡）。
 *
 * ⚠️ 壓黑漸層**比知識卡的強也高**，不是隨手調的：那邊只壓一行日期與標籤
 *    （貼著底緣），這裡是一整段最多三行，字的頂端會爬到圖高的三成左右。
 *    六張服務圖又全是高明度暖調（沙灘、白牆、逆光窗景），弱漸層在那個高度
 *    已經壓不住。實測用的判準是**白字對背景 4.5:1**（WCAG 1.4.3 一般文字），
 *    量的是「字實際落點」那一段的合成亮度，不是漸層最底部那個最深的值。
 *    改字級、改行高、改 bottom 內距都會讓字往上跑 —— 動任何一項都要重量一次。
 *
 * 【實測（2026-07-31，六張圖 × 標題與摘要各自取範圍內最差的一點）】
 *   視窗 1600（圖 718×493，摘要兩行，字塊頂端距圖底  89px） 最差  9.02
 *   視窗 1010（圖 458×315，摘要三行，字塊頂端距圖底 111px） 最差  7.68
 *   視窗  500（圖 393×270，摘要四行，字塊頂端距圖底 134px） 最差  5.96
 * 最差的一律是**標題**（它在字塊最上面，爬得最高）而不是摘要，
 * 圖則多半是 service-corporate（白牆）或 service-ai（沙灘逆光）。
 *
 * 【這組色階是兩次實測改出來的，不要憑感覺調回去】
 *   百分比版本 → 手機版 service-ai 只有 4.1，所以改成 px（見上面那段）。
 *   px 第一版（.74@60 / .60@120 / .28@190 / 0@270）→ 加了標題之後字塊從 90px
 *   長到 110px，手機版最差掉到 4.55，貼著門檻等於沒有餘裕。
 *   現在這組把 120～190px 那一段補厚，最差拉到 5.96。
 *   那一段在桌機是照片的 24～39% 高度，字根本到不了，所以桌機看不出變化。
 */
.site-vtabs-scrim {
    position: absolute;
    inset: 0;
    /* 不吃滑鼠事件。這裡整層都在 figure 的 <a> 內，其實不影響點擊，
       但和知識卡保持同一種寫法，日後若把它搬出 <a> 也不會踩到。 */
    pointer-events: none;
    /*
     * ⚠️ 色階用的是**絕對像素，不是百分比**，這是刻意的。
     *    文字區塊的高度是固定的（行數 × 行高 ＋ 內距），但百分比漸層會跟著
     *    圖高縮放 —— 實測：桌機圖高 493px 時字落在圖高 13%，手機圖高 270px 時
     *    同一段字落在 34%，那裡百分比漸層已經淡掉了（service-ai 只剩 4.1:1，
     *    不到 4.5）。改成 px 之後，不管圖多高蓋住的都是同一條帶。
     *    270px 這個終點 ＝ 三行字（約 90px）＋ 內距，再加一段餘裕。
     */
    background: linear-gradient(to top,
                hsl(var(--black) / .80)   0,
                hsl(var(--black) / .75)  60px,
                hsl(var(--black) / .66) 120px,
                hsl(var(--black) / .40) 190px,
                hsl(var(--black) / 0)   280px);
}

.site-vtabs-cap {
    position: absolute;
    left: var(--space-4);
    /*
     * ⚠️ 右邊要把上下鈕讓出來，不能只寫 --space-4。
     *    兩者都貼在大圖底部，圖一窄就會疊在一起 —— 實測 393px 寬的手機版，
     *    第二、三行整段跑到兩顆鈕底下，字讀不到、鈕也點不準。
     *    這不是只有手機會發生：兩欄版在 992～1100px 之間大圖只有 480 上下，
     *    一樣會撞。所以用讓位而不是加中斷點，任何寬度都不會疊。
     */
    right: calc(var(--space-4) + var(--vt-nav-w));
    bottom: var(--space-4);
    /* 上限比容器窄：大圖寬約 700px，滿寬一行會拉到四十幾個字，
       眼睛換行時容易掉行。收在 30em 讓它維持在兩到三行。 */
    max-width: 30em;
}

/*
 * 服務名。**這是標籤，不是大標。**
 *
 * ⚠️ 刻意不用 --font-display，也刻意比底下的摘要小。
 *    主視覺格子的 .site-cardgrid-cap 也是「左下白字壓黑」，照抄字級與字體
 *    會變成同一個版型在同一頁出現兩次。兩者的分工其實相反：
 *      hero    主體是標題（--text-2xl 明體），副標是註腳（--text-xs）
 *      這裡    主體是摘要（--text-base），標題只說明「這是六個裡的哪一個」
 *    把層級倒過來，即使同樣是左下白字壓黑，一眼就分得出是兩種東西。
 *    要改大請先想清楚會不會跟 hero 撞版。
 *
 * ⚠️ 字色**必須是純白，不要為了「別跟摘要搶」而降透明度**。
 *    第一版設 .78，實測手機版六張裡四張只有 3.48～4.25，過不了 4.5 ——
 *    它是整塊 caption 最上面那一行，爬到距圖底 134px，壓黑漸層在那裡只剩約
 *    .55，再乘上 .78 就撐不住（桌機兩行時只到 89px，所以桌機看不出問題）。
 *    層級要靠**字級與字距**做，那兩項不吃對比度預算。
 */
.site-vtabs-cap-t {
    display: block;
    margin-bottom: var(--space-2);
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    line-height: var(--leading-none);
    letter-spacing: var(--tracking-wider);
    color: hsl(var(--white));
}

.site-vtabs-excerpt {
    margin: 0;
    font-size: var(--text-base);
    line-height: var(--leading-normal);
    color: hsl(var(--white));
}

/* 上一個／下一個：疊在大圖右下角。
   ⚠️ bottom 要把進度線那一條讓出來。它是相對於 .site-vtabs-panel 的內緣定位，
      而進度線在 panel 的最底下（圖片下方）—— 只寫 space-3 的話這兩顆會壓在
      線上。「線高 ＋ 線與圖之間的間距」要一起加回來。 */
.site-vtabs-nav {
    position: absolute;
    right: var(--space-3);
    bottom: calc(var(--space-3) + var(--space-2) + var(--vt-rail-h));
    display: flex;
    gap: var(--space-2);
}

/* 上一個／下一個：外觀完全走 .site-go（元件上兩個 class 一起帶），
   這裡只剩它自己的事。**它原本就是圓框鈕**，全站的進入鈕就是照它統一的。 */

/* Bootstrap 的箭頭圖示預設是白色，淺底看不見（同 .site-carousel 的處理）
   ── 2026-08-03：圓鈕改成深色玻璃之後，**白色才是對的**，所以 invert 收掉
      （改成 none 而不是刪掉那一行，是為了讓下面 hover 那條的存在理由讀得懂）。
      這兩顆是 Bootstrap 的**背景圖**、吃不到 color，所以其他五處靠
      `stroke="currentColor"` 自動變白這件事在這裡不成立，只能靠 filter 控制。 */
.site-vtabs-arrow .carousel-control-prev-icon,
.site-vtabs-arrow .carousel-control-next-icon {
    width: 1rem;
    height: 1rem;
    filter: none;
    transition: filter var(--transition);
}

/* hover 時 .site-go 整顆翻成深橘實心，其他五處的圖示是 stroke="currentColor"
   會自動跟著變白；這兩顆是 Bootstrap 內建的**背景圖**，吃不到 color，
   得把上面那道 invert 收回來才會變回白色。少了這條，深橘底上會留一對黑箭頭。 */
.site-vtabs-arrow:hover .carousel-control-prev-icon,
.site-vtabs-arrow:hover .carousel-control-next-icon,
.site-vtabs-arrow:focus-visible .carousel-control-prev-icon,
.site-vtabs-arrow:focus-visible .carousel-control-next-icon {
    filter: none;
}

/*
 * ═══ 可點標題的 hover 回饋：底線淡入 ═════════════════════════════════
 *
 * 2026-07-31 新增。在此之前只有知識卡的標題有 `text-decoration: underline`
 * 的 hover 效果，而且是瞬間出現；使用者要求課程卡、服務專區、主視覺格子
 * 也要有對應的回饋，並且加上淡入。
 *
 * 【2026-07-31 由「淡入」改為「由左至右滑入」（使用者指定）】
 * 第一版用 text-decoration-color 從 transparent 過渡到 currentColor，
 * 那只能做淡入 —— 顏色是整條一起變的，做不出方向感。
 *
 * 【為什麼用 background 漸層，不用 ::before 或 ::after】
 * ⚠️ **::after 被佔用**：`.site-kncard-t::after { inset: 0 }` 是整張卡可點的
 *    覆蓋層，拿它當底線會弄掉整張卡的命中區。
 * ⚠️ **::before 看似可用，其實是陷阱**：絕對定位的底線需要在標題上加
 *    `position: relative`，而那會讓標題變成上面那個 ::after 的包含塊 ——
 *    覆蓋層會從「整張卡」縮成「只有標題」，整張卡就點不動了。
 * 背景漸層兩個問題都沒有：不需要偽元素、也不需要定位。
 *
 * 作法是畫一條 1px 高的背景線，寬度從 0 過渡到 100%：
 *   background-size: 0 1px  →  100% 1px
 * 只有背景在變，沒有位移、不會撐開版面。
 * 用 currentColor 讓它自動吃到各處的字色 —— 深色區的白字、照片上的白字、
 * 白卡上的深字都不必分別寫。
 *
 * ⚠️ `width: fit-content` 不能省：這幾個標題多半是 flex 欄裡的 block，
 *    預設會被拉成整欄寬。實測課程卡標題元素寬 395、文字只有 164 ——
 *    不收寬度的話會滑出一條超過文字兩倍長的線。
 *    服務專區的標題本來就是 inline（元素寬＝文字寬），fit-content 對它無作用，
 *    留著無妨。
 * ⚠️ text-decoration: none 要留著：知識卡的標題是 <a>，少了它會出現
 *    瀏覽器預設的底線，跟這條滑入的線疊在一起。
 *
 * ⚠️ **觸發條件各處不同，這是刻意的**，對應各元件的可點範圍：
 *      知識卡    標題自己 :hover（它的 ::after 覆蓋整張卡，等於卡片 hover）
 *      課程卡    整張卡是 <a>，所以由卡片 hover 觸發
 *      服務專區   由項目 hover 觸發（滑過即展開，與既有行為一致）
 *      主視覺     由格子 hover 觸發（標題本來就是展開後才出現）
 *    完整的可點範圍差異見 .site-go 那段的「用在哪」與各元件檔頭。
 *
 * ⚠️ 減少動態時 --transition 為 0ms（tokens.css），底線會直接出現，
 *    功能不受影響。
 */
.site-kncard-t,
.site-coursecard-t,
.site-vtabs-title,
.site-svc-hero-t,
.site-svc-chip-t,
.site-cardgrid-t {
    width: fit-content;
    text-decoration: none;
    /* 一條 1px 的線，貼著文字區塊的底緣，靠左、不重複 */
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-position: 0 100%;
    background-size: 0 1px;
    transition: background-size var(--transition);
}

.site-kncard-t:hover,
.site-kncard-t:focus-visible,
.site-coursecard:hover .site-coursecard-t,
.site-coursecard:focus-visible .site-coursecard-t,
.site-vtabs-item:hover .site-vtabs-title,
.site-vtabs-item:focus-within .site-vtabs-title,
.site-svc-hero:hover .site-svc-hero-t,
.site-svc-hero:focus-visible .site-svc-hero-t,
.site-svc-chip:hover .site-svc-chip-t,
.site-svc-chip:focus-visible .site-svc-chip-t,
.site-cardgrid-cell:hover .site-cardgrid-t,
.site-cardgrid-cell:focus-visible .site-cardgrid-t {
    background-size: 100% 1px;
}

/*
 * ═══ 進入鈕（.site-go）═══════════════════════════════════════════════
 * 全站唯一的「進入」語彙：40×40 圓形、細描邊、半透明白底、深色箭頭。
 * 2026-07-30 統一。在此之前同一件事在首頁有 7 種長相（無框裸箭頭 ×3、
 * 圓框鈕 ×1、實心白圓鈕 ×2、文字＋內嵌箭頭 ×1），那是「風格不統一」的
 * 實際來源。**要改外觀改這一處。**
 *
 * 【為什麼一種樣式通吃兩種底】
 * 它會落在壓暗的照片上（主視覺、知識卡、課程卡）也會落在淺色面板上
 * （服務專區內文）。半透明白底在照片上是一枚白圓片、在白面板上幾乎等於白，
 * 兩邊都成立，不必分深淺兩版。
 *
 * 【描邊不能省】
 * 照片的亮部會把無框的白圓片吃掉；白面板上無框則整顆消失。
 * 更硬的理由是觸控裝置沒有 hover ——這顆圓鈕是「這裡可以點」的唯一提示，
 * 課程卡的「課程詳情 / 報名」文字拿掉之後尤其如此。
 *
 * 【尺寸】40×40 遠高於 WCAG 2.5.8 的目標尺寸下限 24×24。
 * 原本無框那兩顆是 48×48 —— 框看不見時必須靠大命中框補；現在框看得見，
 * 40 就夠，且與服務專區原有的上一個／下一個對齊。
 *
 * 【用在哪】
 *   .site-cardgrid-go    主視覺格子右下（<span>，整格才是連結）
 *   .site-kncard-go      知識卡圖片右上（<a>，卡上有 5 個目的地不能整張包）
 *   .site-kncard-act     知識卡下方 podcast／影片（<a>，外部平台）
 *   .site-coursecard-go  課程卡圖片右上（<span>，整張卡是連結）
 *   .site-vtabs-more     服務專區內文右側（<a>）
 *   .site-vtabs-arrow    服務專區大圖右下的上一個／下一個（<button>）
 *
 * ⚠️ **焦點行為各處刻意不同**，不要看到不一致就順手統一：
 *      整張卡／整格是連結的（主視覺、課程卡）→ 圓鈕是 <span> aria-hidden
 *      標題本身是連結的（知識卡）→ 圓鈕 tabindex="-1" aria-hidden
 *      標題是 <button> 的（服務專區）→ 圓鈕是唯一入口，必須可聚焦＋aria-label
 *    理由完整寫在 _knowledge_card.php 與 _card_grid.php 檔頭。
 */
.site-go {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    padding: 0;
    border: var(--border-width) solid hsl(var(--border));
    border-radius: var(--radius-full);
    /* 2026-07-30 下午：半透明（.88）改成**實心白**，照參考圖。
       這同時補掉了同批拿掉陰影留下的坑 —— 陰影原本是圓鈕壓在高明度照片上的
       保命符（描邊是 --border 淺灰，在奶油色牆面／白桌上會整圈消失），
       實心白至少讓圓片本體有形狀。描邊仍保留一圈：參考圖是無描邊的，但那張
       底圖是中明度的暖棕，我們的素材有一半是接近純白的（品牌規格第 6 節的
       選圖標準就是「柔和色調、留白足夠」），無描邊會整顆不見。
       ⚠️ 若之後仍回報「照片上看不到圓鈕」，**不要把陰影加回來**，
          改把描邊色調深（例如 --foreground / .28）。 */
    /*
     * ─── 2026-08-03：實心白 → 深色玻璃（使用者指定）────────────────────
     *
     * 【為什麼是深色玻璃而不是白玻璃】六處圓鈕**全部壓在照片上**，而選圖標準
     * 是「柔和色調、留白足夠」（品牌規格第 6 節）—— 近白照片是常態不是例外。
     * 實測知識照片最亮處 (238,239,238)，白玻璃疊上去 240，等於沒有鈕。
     * 深色玻璃反過來：照片愈亮，鈕與底的分界愈明顯；照片暗的地方靠白描邊撐。
     * 這跟 iOS 在淺色桌布上用深色模糊鈕是同一個道理。
     *
     * ⚠️ 這推翻了 2026-07-30 下午「改成實心白」那次決定（照參考圖定的），
     *    原註解保留在上面備查。當時擔心的「淺色照片上圓鈕會消失」在這一版是
     *    **反向解決**的：不是把鈕做得更白，是做得更暗。
     * ⚠️ alpha 是回推的下限，不要調低，理由見 tokens.css 的 --glass-media-fill。
     */
    border: var(--border-width) solid var(--glass-media-line);
    background: var(--glass-media-fill);
    -webkit-backdrop-filter: var(--glass-blur);
            backdrop-filter: var(--glass-blur);
    color: var(--glass-media-fg);
    font-size: var(--text-lg);
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition: background var(--transition),
                border-color var(--transition),
                color var(--transition);
}

/* hover／focus：整顆翻成深橘實心（2026-07-30 下午新增，使用者指定）。
   --accent-strong 是全站唯一的高飽和色，用途嚴格限定在互動狀態，理由見
   tokens.css 的 ember 軸註解。白箭頭配深橘 4.55:1，過 AA（圖示只要 3:1）。
   箭頭同時由 ↗ 轉正成 →，那組規則在 .site-arrow 那一區。

   ── 2026-08-03：改成「ember 玻璃」（使用者指定）。填色是 --accent-strong / .85
      而不是實色，描邊是實色。**只有 alpha 在走，構造從靜止到 hover 都是同一種**
      （半透明填色 ＋ 細描邊 ＋ 模糊），不會硬切成一顆實心圓片。
      ⚠️ 對比不受影響：.85 的深橘疊在任何底上都已經接近實色，白箭頭最差的情況
         （疊在近白照片上）是 4.2:1，仍過 AA 的 3:1（圖示門檻）。
      ⚠️ 描邊刻意留實色：hover 的重點是「這一顆被選中了」，邊界要斬釘截鐵。 */
.site-go:hover,
.site-go:focus-visible {
    background: hsl(var(--accent-strong) / .85);
    border-color: hsl(var(--accent-strong));
    color: hsl(var(--accent-strong-foreground));
}

/*
 * ⚠️ 這裡曾經有一條「滑到整張卡／整格，右上那顆圓鈕一起變橘」的規則，
 *    2026-07-30 下午依使用者指示拿掉。**不要憑直覺加回來。**
 *    定案的分工是：
 *      滑到 item（卡／格／項目）→ 只有箭頭轉向（45° → 水平）
 *      滑到圓鈕本身            → 才變橘
 *    理由是變色的意思是「你正要按下這一顆」，罩在整張卡上是誇大 ——
 *    尤其知識卡上有五個目的地，整張卡一亮就分不出你按到的是哪一個。
 *    轉向留在 item 層是對的：它只說「這一塊是可以進去的」，不指定哪一顆。
 */

.site-go:focus-visible {
    outline: 2px solid hsl(var(--ring));
    outline-offset: 2px;
}

/*
 * ⚠️ 這裡曾經有一條 `.site-vtabs-item.is-on .site-vtabs-more { border-color:
 *    hsl(var(--foreground)) }`（展開項目的圓鈕描邊轉深色），2026-07-30 下午
 *    依使用者指示拿掉 —— 全站六處進入鈕的描邊必須一致。
 *
 *    當初加它的理由是「展開的那項是現在在看的，入口不該跟其他五個一樣淡」，
 *    但那是**其他五顆也還是圓框鈕**時的事。現在未展開的項目整顆褪成一個裸的
 *    「＋」（見 .site-vtabs-more），已經沒有東西需要靠描邊去區分了。
 */

@media (max-width: 991.98px) {
    .site-vtabs-grid {
        grid-template-columns: 1fr;
        gap: var(--space-4);
    }

    /* 單欄時圖在上、清單在下：先看到內容，再看到選單 */
    .site-vtabs-panel { order: -1; }

    /* 已經是單欄長版面，左欄不必再自己捲動 */
    .site-vtabs-list {
        max-height: none;
        overflow-y: visible;
    }
}

/* ─── 服務專區：主次不對稱（site-svc）──────────────────────────────
 * 2026-08-03 起首頁服務專區的版型。標記與設計理由見 _services.php。
 * 取代上面那整段 .site-vtabs-*（**保留不刪**，只是首頁不再引用，
 * 處置方式同 _rule_stroke.php 與 .site-bg-*）。
 *
 * 【階層寫在版型裡】主推服務（清單第一筆＝顧問案）佔一張整幅大卡，
 * 其餘降級成一條卡片列。舊版六個項目是等權的，客戶最想展示的顧問案
 * 只能靠「排第一」暗示 —— 那太弱，而且 N=2 時整個元件會失衡（見元件檔頭）。
 *
 * 【N 的自適應交給 flex-basis，不數 N】
 * 服務列是 `flex: 1 0 20rem`：N≤3 撐滿一列不捲，N≥4 自動溢出橫捲。
 * 沒有任何一條規則需要知道現在有幾筆，PHP 那邊也不必分支。
 *
 * ⚠️ **這一區的底色 --surface-warm 與卡片的 --card-soft 剛好同值**
 *    （都是 --nude-50 #FCF8F3）。所以卡片不是靠底色跟色帶分開，而是靠
 *    **描邊 ＋ 有沒有紙紋顆粒** —— 與舊版 .site-vtabs 那張面板完全同一招，
 *    理由也寫在那裡。要換 token 前務必回頭確認這件事還成立，否則整排卡片
 *    會在色帶上消失。知識卡與課程卡沒有這個問題：它們在色帶 2／3 上，
 *    那兩階比 --card-soft 深。
 */

/* 下緣留白沿用舊版 .site-vtabs 的 --space-5，section 內的節奏不變 */
.site-svc {
    margin: 0 0 var(--space-5);
}

/* 等權模式（目前首頁用這個，見 _services.php 檔頭）：每一筆都是整幅大卡，
   上下等距排。間距用 --space-4 而不是 --space-5 —— 兩張是同一組東西，
   卡與卡的距離要明顯小於「這一組」到 section 下緣的距離。 */
.site-svc-equal {
    display: grid;
    gap: var(--space-4);
}

/* ═══ 主推服務：整幅大卡 ═════════════════════════════════════════════
 * 桌機左圖右文（7fr / 5fr），手機圖上文下 —— 同一份 DOM，差異全在下面的
 * 媒體查詢。構造沿用課程卡與知識卡那一套：卡片自己吃 padding、圖片內嵌並
 * 自己裁一階小的圓角（規則見 tokens.css 圓角區塊）。
 */
.site-svc-hero {
    display: grid;
    grid-template-columns: 7fr 5fr;
    gap: var(--space-4);
    align-items: center;
    padding: var(--space-3);
    /* 覆寫 .site-card 的白底細框（刻意，不是漏帶 class）—— 理由同
       .site-coursecard 那段：元件仍需要 .site-card 這個 hook，但表面走
       全站卡片的暖白。描邊這裡**要留著**，見本區塊開頭的同值警告。 */
    border: var(--border-width) solid hsl(var(--border));
    border-radius: var(--radius-lg);
    background: hsl(var(--card-soft));
    color: hsl(var(--foreground));
    text-decoration: none;
    transition: transform var(--duration-slow) var(--ease),
                opacity var(--duration-slow) var(--ease);
}

.site-svc-hero-media {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius);
}

/* 進入圓鈕壓在照片右上，位置與課程卡對齊（同一條上緣、同一個內距） */
.site-svc-hero-go {
    position: absolute;
    top: var(--space-3);
    right: var(--space-3);
    z-index: 1;
}

.site-svc-hero-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding-right: var(--space-2);
}

/* 比其他卡片的標題大兩級（--text-md → --text-xl）。這是**刻意**的，不是
   隨手放大：全站卡片標題統一在 --text-md，唯有這一張要在同一個畫面上比
   下面那排小卡明顯重，階層才讀得出來 —— 那正是這次改版的目的。
   ⚠️ 不要再往上跳到 --text-2xl（頁面主標）：這是卡片標題不是頁面標題，
      而且會壓過區塊標題「服務專區」。 */
.site-svc-hero-t {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: var(--weight-bold);
    line-height: var(--leading-tight);
}

.site-svc-hero-sub {
    font-size: var(--text-sm);
    line-height: var(--leading-normal);
    color: hsl(var(--muted-foreground));
}

/* excerpt 是這張卡的主體，所以用 --foreground 而不是副標的 muted ——
   兩者同色的話會讀成同一段。舊版是把它壓在照片上（需要一整條壓黑漸層
   才讀得到），這一版在白底上，那條漸層連同它的對比度問題一起不需要了。 */
.site-svc-hero-x {
    margin: 0;
    /* --text-md（內文級）而不是舊版壓在圖上時用的 --text-base（輔助說明級）
       —— 它現在是這張卡的主體，不是照片上的一行註腳。 */
    font-size: var(--text-md);
    line-height: var(--leading-relaxed);
    color: hsl(var(--foreground));
}

/* ═══ 其餘服務：卡片列 ═══════════════════════════════════════════════
 * ⚠️ overflow-x:auto 會讓**縱向**也變成捲動容器（規範：一軸非 visible 時，
 *    另一軸的 visible 計算成 auto）。卡片 hover 會抬起 3px、focus 還有
 *    2px 外框＋2px offset，不留上下內距就會被裁掉。space-2（8px）夠用。
 *    也因為這件事，下面小卡的進場**只淡入、不上浮**（見 .has-draw 那段）。
 */
.site-svc-strip {
    display: flex;
    gap: var(--space-4);
    margin-top: var(--space-4);
    padding-block: var(--space-2);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
}

.site-svc-strip::-webkit-scrollbar { display: none; }

/* flex: 1 0 20rem —— 這一行就是「N 的自適應」的全部：
     N≤3  三張加 gap 塞得進 1140 的容器 → 一起長大撐滿，不出現捲動
     N≥4  塞不進 → 溢出橫捲，第四張露出邊緣當可捲提示
   max-width 是為了 N=2（正式站現況）：只有一張小卡時 flex-grow 會把它拉成
   整列寬，一張又寬又矮的卡讀起來像壞掉的版面，收在 26rem 比較像「還有一個」。 */
.site-svc-chip {
    position: relative;
    display: flex;
    flex: 1 0 20rem;
    max-width: 26rem;
    flex-direction: column;
    gap: var(--space-2);
    scroll-snap-align: start;
    padding: var(--space-3);
    border: var(--border-width) solid hsl(var(--border));
    border-radius: var(--radius-lg);
    background: hsl(var(--card-soft));
    color: hsl(var(--foreground));
    text-decoration: none;
    transition: transform var(--duration-slow) var(--ease),
                opacity var(--duration-slow) var(--ease);
}

.site-svc-chip-media {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius);
}

.site-svc-chip-go {
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
    z-index: 1;
}

.site-svc-chip-t {
    font-family: var(--font-display);
    font-size: var(--text-md);
    font-weight: var(--weight-bold);
    line-height: var(--leading-tight);
}

.site-svc-chip-sub {
    font-size: var(--text-sm);
    line-height: var(--leading-normal);
    color: hsl(var(--muted-foreground));
}

/*
 * 進場：與課程卡、知識卡同一套機制（draw-in.js 掛 .is-drawn）。
 * 起始的 opacity:0 一定要掛在 .has-draw 之下 —— 服務是**內容**不是裝飾，
 * JS 沒跑（404／被 CSP 擋／舊瀏覽器拋錯）時必須以完整態呈現。理由詳見
 * .site-coursecard 那段。
 *
 * ⚠️ **這裡刻意只淡入、不上浮**，與課程卡／知識卡的 translateY 不同：
 *    小卡在 overflow-x:auto 的容器裡，起始位移 16px 會被縱向裁掉一半，
 *    進場看起來像從一條縫裡冒出來。大卡跟著一起只淡入，兩者才是同一組。
 */
.has-draw .site-svc-hero,
.has-draw .site-svc-chip { opacity: 0; }
.has-draw .site-svc-hero.is-drawn,
.has-draw .site-svc-chip.is-drawn { opacity: 1; }

/* hover 抬起。沿用課程卡的位移量與理由（不用 scale —— 卡內文字會重新
   光柵化而發虛）。這裡不必像課程卡那樣加 .is-drawn 提高 specificity：
   上面那組沒有寫 transform:none，沒有東西要壓過。 */
.site-svc-hero:hover,
.site-svc-chip:hover {
    transform: translateY(-3px);
}

.site-svc-hero:focus-visible,
.site-svc-chip:focus-visible {
    outline: 2px solid hsl(var(--ring));
    outline-offset: 2px;
}

/* 平板起大卡轉單欄（圖上文下）。7fr/5fr 在 991 以下會把右欄壓到
   放不下一整段 excerpt，斷字會變得很難看。 */
@media (max-width: 991.98px) {
    .site-svc-hero {
        grid-template-columns: 1fr;
    }
}

/*
 * ═══ 手機：小卡轉文字膠囊 ═══════════════════════════════════════════
 *
 * 【為什麼手機不沿用圖卡】
 * 手機主視覺就在正上方，而它是「滿版壓字大圖 ＋ 底部 46% 寬的橫滑圖卡列」。
 * 這一區若也是大圖＋圖卡列，兩區會撞臉。分工改成：
 *   hero    字壓在圖上、卡列是有高度的圖卡
 *   服務專區 字在圖外面、卡列是一行高的文字膠囊
 * 使用者 2026-08-03 指定文字膠囊這一版。
 *
 * 【膠囊的表面走玻璃鈕那一套，不是卡片那一套】
 * 它在這個尺寸下的角色已經比較接近按鈕而不是卡片，所以用 --glass-* ——
 * 與全站按鈕同一組值（SSOT 在 tokens.css）。順帶解掉對比問題：卡片用的
 * --border（slate-200）壓在 --nude-50 上遠不到 3:1，那在有圖有標題的
 * 卡片上還撐得住，但膠囊只剩一條框在表示「這是一顆可以按的東西」。
 *
 * ⚠️ **沒有任何內容被拿掉**：
 *      -media  display:none —— 標記與 <img> 都還在 DOM 裡
 *      -sub    轉 visually-hidden（**不是** display:none）—— 它仍是可及
 *              名稱的一部分，讀屏會唸到「AI 分身 凱宇老師 AI 分身應用服務」。
 *              配方與主視覺滑動列的 .site-cardgrid-cap 同一份。
 * ⚠️ 膠囊上**沒有 .site-go 圓鈕**（-go 在 -media 裡，一起不繪製）。這是對
 *    「全站六處進入語彙一致」的刻意偏離，先例是主視覺滑動列的小卡：
 *    一行高的膠囊塞一顆 40px 圓鈕會整個變形，而膠囊本身就是 affordance。
 *    要還原的話把 -go 移出 -media 即可，一行的事。
 */
@media (max-width: 575.98px) {
    .site-svc-strip {
        gap: var(--space-2);
    }

    .site-svc-chip {
        flex: 0 0 auto;
        max-width: none;
        padding: var(--space-2) var(--space-3);
        border-color: var(--glass-line);
        border-radius: var(--radius-pill);
        background: var(--glass-fill);
        -webkit-backdrop-filter: var(--glass-blur);
                backdrop-filter: var(--glass-blur);
        white-space: nowrap;
    }

    .site-svc-chip-media { display: none; }

    /* 膠囊上的標題降成按鈕字級：明體大標是主視覺的招牌，這裡只是入口的名字 */
    .site-svc-chip-t {
        font-family: inherit;
        font-size: var(--text-sm);
        font-weight: var(--weight-medium);
        letter-spacing: var(--tracking);
    }

    /* visually-hidden：看不見但讀得到（配方同 .site-cardgrid-cap）。
       ⚠️ 不可改成 display:none —— 那會把副標從無障礙樹裡整個拿掉。 */
    .site-svc-chip-sub {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
        border: 0;
    }
}

/* ─── 首頁主視覺：格子版（site-cardgrid）───────────────────────────
 * 移植自 21st.dev「DynamicFrameLayout」，行為見 assets/front/js/card-grid.js。
 *
 * 展開動畫就是 grid-template-columns / -rows 的補間（原版也是 —— 它的
 * motion.div 沒帶任何動畫 prop）。JS 只負責算 fr 分配並以 CSSOM 寫入。
 *
 * 【只有兩個狀態】
 *   未 hover  照片壓暗＋淡淡模糊 ＋ 置中的手寫英文標籤（白字）
 *   hover 中  照片清晰 ＋ scrim ＋ 底部的中文標題與副標（白字）
 *
 * 滑鼠移開不回復 —— 最後停留的那格維持展開。
 * 英文標籤與中文 caption 是兩組不同內容，所以交叉淡入而非位移。
 *
 * ⚠️ 本區塊是「觸控優先」寫法：下面的預設值是觸控裝置看到的樣子
 *    （兩欄、六格全部清晰、標題在底部），揭示互動整組放在下方的
 *    @media 裡。順序反過來會讓手機看到六張模糊照片。
 */

/* 格子之間留間距。用既有的 --space-1（0.25rem）—— 間距一律取自 token，
   不寫死 px，全站的節奏才會跟著 tokens.css 一起變。

   ⚠️ gap 與「外框圓角放哪一層」是綁在一起的：
      無 gap 時六格拼成一整塊，外框圓角只能由容器負責（每格各自帶 border
      會在相鄰處疊成 2px 的線，圓角交會處還會露出背景色的小三角）；
      有 gap 時每格是獨立的方塊，就必須各自帶圓角，否則會變成「方角方塊
      裝在圓角框裡」。容器這時反而不該有框，不然多一層無意義的外圈。
      改 gap 就要一起檢查這兩層，不要只動一個值。 */
.site-cardgrid {
    display: grid;
    grid-template-columns: 1fr 1fr;   /* 觸控預設：兩欄 */
    gap: var(--space-1);
    /*
     * 格子與下一區之間的呼吸空間（2026-07-31，使用者指定用 margin 解）。
     *
     * 【為什麼不是靠 section 的內距就好】主視覺是 sticky，下一區是 .site-slab
     * （上緣圓角 ＋ -24px 負 margin）會直接蓋上來，所以 section 的下內距有一段
     * 被疊掉。要讓格子底緣與色帶頂緣之間看得出留白，就得在格子自己身上加。
     *
     * ⚠️ **調主視覺的鬆緊請動這個值**，不要去改上面的 min-height: 660px
     *    （那會讓格子高度隨螢幕變動，已被否決一次）。
     *    ── 2026-08-03 更新：min-height 已改成跟著視窗算（見下方那段），
     *       上面這句的前半仍然成立（鬆緊調這裡），後半的理由已被推翻。
     *
     * ⚠️ 值改由 --hero-gap 提供（定義在 .site-hero-dark，往下繼承）。
     *    它同時是「一屏減掉多少」算式裡的一項，兩邊必須是同一個來源，
     *    不要改回直接寫 --space-7。fallback 是給主視覺以外的用途留的。
     */
    margin-bottom: var(--hero-gap, var(--space-7));
}

/*
 * ─── 游標聚光的三個變數（2026-07-30，移植自 react-bits ChromaGrid）──────
 *
 * card-grid.js 每幀把它們寫到**每一格**上（不是寫在容器），因為每格需要的是
 * 「游標在該格內的本地座標」—— 六格各自不同，圓弧才接得起來、跨得過格線。
 *
 *   --cg-x / --cg-y  游標圓心，相對於該格左上角。可以是負值或大於格寬
 *                    （游標在別格時就是這樣），CSS 的 radial-gradient 吃得下。
 *   --cg-r           聚光半徑。
 *
 * ⚠️ **`--cg-r: 0` 必須等於「今天的樣子」**，整套退場機制都掛在這上面：
 *      游標離開   → rAF 把 r 收回 0，自然淡回全灰
 *                   （原版要疊 .chroma-fade 第二層才做得到，我們一層就夠）
 *      JS 沒跑    → r 恆為 0
 *      減少動態   → 不 bind，r 恆為 0
 *      觸控裝置   → 不 bind，而且下面的揭示樣式整組不生效
 *    半徑為 0 的 radial-gradient 是**退化漸層**，CSS Images 3 §4.2 規定要整片
 *    畫成最後一個色階 —— 剛好就是原本的純色 veil 與全不透明的 mask。
 *    改這裡的預設值會同時弄壞上面四條退路。
 */
.site-cardgrid-cell {
    --cg-x: 50%;
    --cg-y: 50%;
    --cg-r: 0px;
}

.site-cardgrid-cell {
    position: relative;
    display: block;
    aspect-ratio: 4 / 3;              /* 觸控版靠比例撐高；指標版由 grid 決定 */
    /* overflow:hidden 有兩個作用：把照片裁到圓角內，以及擋住未 hover 時
       照片的 scale(1.05) —— 不裁的話會溢出去蓋到隔壁格。 */
    overflow: hidden;
    border: var(--border-width) solid hsl(var(--border));
    border-radius: var(--radius-lg);
    background: hsl(var(--background));
    color: hsl(var(--foreground));
    text-decoration: none;
}

/* offset 0：gap 是 4px（--space-1），每側只剩 2px，外框 2px 寬貼著格子邊緣
   畫剛好填滿那 2px、不會越過中線碰到隔壁格。
   ⚠️ 這個值跟上面的 gap 是連動的，改 gap 要一起重算：
      可用位移 = gap / 2 - 外框線寬。gap 若改回 --space-2（8px）就能給 2px。 */
.site-cardgrid-cell:focus-visible {
    outline: 2px solid hsl(var(--ring));
    outline-offset: 0;
}

.site-cardgrid-media {
    position: absolute;
    inset: 0;
    transition: filter var(--duration-slow) var(--ease),
                transform var(--duration-slow) var(--ease);
}

.site-cardgrid-media .site-img,
.site-cardgrid-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* 未 hover 時壓暗照片，讓白色手寫標籤有足夠對比。hover 時淡出。
   用壓暗而不是原本的白紗：白紗會把低飽和的照片洗成一片灰，
   壓暗則保留照片的層次，標籤浮在上面也更像同一張圖的一部分。 */
/*
 * 游標聚光層（2026-07-30，移植自 react-bits ChromaGrid）。
 *
 * 【為什麼一格一層，而不是整個 grid 一層】
 * 整組六層 backdrop-filter 顯然比一層貴，仍然這樣做的原因是 `blur()` 會跨過
 * 元素邊界去採樣：一層蓋住整個 grid 的話，它會把格線之間 4px 的白色 gap
 * 吸進來，六格每一邊都浮出一圈亮邊。放進格子裡才吃得到 .site-cardgrid-cell
 * 的 overflow:hidden 裁切，圓角也自動對齊。
 *
 * 【它濾誰】
 * backdrop-filter 濾的是「畫在它底下的東西」。整個元件沒有任何 z-index，
 * 繪製順序＝DOM 順序，所以它只吃到上一個兄弟 .site-cardgrid-media（照片），
 * veil／scrim／label／cap／go 都畫在它之後，完全不受影響。
 * ⚠️ 在 _card_grid.php 裡搬動這一行的位置會直接糊掉標題文字。
 *
 * 【觸控裝置整層不存在】display:none 是預設值，只有下面的揭示媒體查詢會打開。
 *    觸控版六格本來就全部清晰，掛一層 backdrop-filter 在那裡純粹是白付合成成本。
 */
.site-cardgrid-chroma {
    display: none;
    position: absolute;
    inset: 0;
    pointer-events: none;
    /* 模糊與去飽和合在同一條 filter 鏈，只有一次 backdrop 取樣。
       blur 的 4px 是從 .site-cardgrid-media 搬過來的原值，不是新調的。
       saturate 是原版的主軸，在這批低飽和照片上只是附加 —— 理由見 _card_grid.php。 */
    backdrop-filter: blur(4px) saturate(.35);
    -webkit-backdrop-filter: blur(4px) saturate(.35);
    /* 圓心透明（＝不濾，照片原樣）、外圍不透明（＝全濾）。
       中段多給幾個色階是為了讓衰減看起來是光，不是一個有邊的圓。 */
    mask-image: radial-gradient(circle var(--cg-r) at var(--cg-x) var(--cg-y),
                rgb(0 0 0 / 0)    0%,
                rgb(0 0 0 / .10) 45%,
                rgb(0 0 0 / .38) 72%,
                rgb(0 0 0 / .72) 90%,
                rgb(0 0 0 / 1)  100%);
    -webkit-mask-image: radial-gradient(circle var(--cg-r) at var(--cg-x) var(--cg-y),
                rgb(0 0 0 / 0)    0%,
                rgb(0 0 0 / .10) 45%,
                rgb(0 0 0 / .38) 72%,
                rgb(0 0 0 / .72) 90%,
                rgb(0 0 0 / 1)  100%);
    transition: opacity var(--duration-slow) var(--ease);
}

/*
 * 【亮度這條通道刻意**不**併進 chroma 的 backdrop-filter】
 * 那一層若加 brightness()，會連格線的 4px gap 與圓角外的白底一起壓暗，
 * 六格之間浮出看得見的暗線。blur 與 saturate 作用在純白上是無變化，
 * 所以只有那兩條能放進 backdrop-filter，亮度必須留在這裡自己畫。
 * 這也順便省一層 —— 它是普通 background，不吃合成成本。
 */
.site-cardgrid-veil {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;                       /* 觸控預設：照片是清晰的，不壓暗 */
    background: radial-gradient(circle var(--cg-r) at var(--cg-x) var(--cg-y),
                hsl(var(--black) / 0)   0%,
                hsl(var(--black) / .10) 45%,
                hsl(var(--black) / .28) 72%,
                hsl(var(--black) / .40) 90%,
                hsl(var(--black) / .46) 100%);
    transition: opacity var(--duration-slow) var(--ease);
}

/* 底部漸層：讓白字壓在任何照片上都讀得到 */
.site-cardgrid-scrim {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(to top,
                hsl(var(--black) / .66) 0%,
                hsl(var(--black) / .26) 36%,
                hsl(var(--black) / 0) 64%);
    transition: opacity var(--duration-slow) var(--ease);
}

/* 靜止狀態的英文標籤。觸控版不需要（圖與中文字都直接看得到）。 */
.site-cardgrid-label {
    display: none;
    position: absolute;
    inset: 0;
    align-items: center;
    justify-content: center;
    padding: var(--space-3);
    /* 手寫體，與 signature 毛筆體同一種筆意。
       ⚠️ 刻意不加 text-transform:uppercase 與寬字距 —— 手寫體被拆成大寫、
          字距又拉開的話，連筆會斷掉，看起來像把手寫字硬排成印刷字。
       字級比一般小標大兩階：手寫體的 x-height 偏小，用 --text-sm 會讀不清。 */
    font-family: var(--font-script);
    font-size: var(--text-xl);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
    text-align: center;
    color: hsl(var(--white) / .94);   /* 壓暗的照片上，白字 */
    transition: opacity var(--duration-slow) var(--ease);
}

/*
 * ⚠️ 這裡曾經有一條 .site-cardgrid-label-zh（英文底下那行中文，系統楷體），
 *    2026-08-03 下午依使用者指示整組拿掉 —— 「只留英文就好了，中文加上去
 *    不太好看」。標記那邊的說明見 _card_grid.php。
 *    上面 .site-cardgrid-label 的 flex-direction / gap 也一起還原成單行置中。
 */

.site-cardgrid-cap {
    position: absolute;
    inset: auto 0 0 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    /* space-3 → space-4（2026-07-30 下午）：圓鈕往內縮之後，左下的標題還維持
       原本的內距就顯得比圓鈕更貼邊，同一格裡兩種留白。 */
    padding: var(--space-4);
    /* 2026-07-30：原本右側要讓開 --space-6 給右下角的箭頭。箭頭移到右上之後
       這個讓位不必要了，長標題拿回整行寬度。**箭頭若搬回右下要記得補回來**，
       否則長標題會壓到圓鈕上。 */
    transition: opacity var(--duration-slow) var(--ease),
                transform var(--duration-slow) var(--ease);
}

.site-cardgrid-t {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--text-md);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
    letter-spacing: var(--tracking-tight);
    color: hsl(var(--white));
    /* ⚠️ background-size 這一項不能漏：上方「可點標題的 hover 回饋」有設它的
          過渡，但這裡的 transition 是同權重且在檔案更後面，會整條蓋掉 ——
          漏掉的話主視覺的底線會是瞬間出現，四處裡只有它不會滑入。 */
    transition: color var(--duration-slow) var(--ease),
                font-size var(--duration-slow) var(--ease),
                background-size var(--transition);
}

.site-cardgrid-s {
    font-size: var(--text-xs);
    line-height: var(--leading-tight);
    color: hsl(var(--white) / .82);
    transition: opacity var(--duration-slow) var(--ease);
}

/*
 * 「前往」圓鈕，2026-07-30 由右下移到**右上**（使用者指定），外觀走 .site-go。
 * 與知識卡、課程卡的圓鈕同位置同尺寸，三者並排時對得上。
 *
 * 【它不是連結，但要吃得到滑鼠】
 * 整格才是 <a>（HTML 不允許巢狀 <a>，理由見 _card_grid.php）。這裡不設
 * pointer-events:none —— 位移動畫要靠它自己的 :hover 觸發。
 * 讓它吃滑鼠事件沒有副作用：它是整格的子元素，游標移上來時整格仍在
 * :hover 狀態（不會抖動），click 也照樣冒泡到整格的連結上。
 *
 * 【為什麼不再需要自己撐命中框】
 * 前一版是無框裸箭頭，得靠 padding 把命中框撐到 48px；.site-go 本身就是
 * 40×40 的實體圓鈕，命中範圍看得見也摸得到。
 *
 * transition 要保留 opacity —— 它跟著格子展開才淡入（見下方媒體查詢）。
 */
/* 2026-07-30 下午由 space-2 拉到 space-3：貼太近卡片邊緣。
   與課程卡、知識卡的圓鈕同一個值 —— 三處並置時距離才對得上。 */
.site-cardgrid-go {
    position: absolute;
    top: var(--space-3);
    right: var(--space-3);
    transition: opacity var(--duration-slow) var(--ease),
                transform var(--duration-slow) var(--ease),
                background var(--transition),
                border-color var(--transition);
}

/* ── 指標裝置且夠寬：靜止模糊＋置中標題，hover／focus 才揭示 ────────
   ⚠️ 這組條件必須與 card-grid.js 的 REVEAL_MQ 逐字一致。JS 會用 CSSOM 寫
      grid-template-*，而 inline style 贏過媒體查詢 —— 兩邊不同步的話，
      CSS 想切回單欄也切不動。改一邊就要改另一邊。
   min-width 不能省：桌機視窗拉到 500px 時仍然是精準指標，只判斷 hover/pointer
      會讓六格擠成一百多 px 寬。 */
@media (hover: hover) and (pointer: fine) and (min-width: 768px) {

    .site-cardgrid {
        /* JS 會用 CSSOM 覆寫這兩個值；這裡是 JS 尚未執行時的樣子 */
        grid-template-columns: 6fr 3fr 3fr;
        grid-template-rows: 7fr 5fr;
        /*
         * ── 這裡的歷史（兩次相反的決定，都留著，不要只看其中一次）──────
         *
         * 【2026-07-31】曾為了「讓格子塞得進一屏」改成 svh 算式，使用者否決，
         *   當時寫的警告是：「這是固定高度，不要改成跟著視窗算（例如 100svh
         *   減 header）。高度會隨螢幕大小變動，大螢幕變得很高、小螢幕又縮得
         *   很矮，格子的比例關係（6fr 3fr 3fr / 7fr 5fr）看起來就不是同一個
         *   設計。主視覺的分量應該是固定的，要多一點呼吸空間請調
         *   .site-cardgrid 的 margin-bottom，不要動這個值。」
         *
         * 【2026-08-03】使用者回報 660px 固定高的實際後果，並改變決定：
         *   主視覺是 sticky，總高 1041px（189 上留白＋660 格子＋96＋96 下留白），
         *   視窗只要不到 1041px 高，**釘住之後下緣就永遠捲不出來** ——
         *   不是被裁掉而已，是根本到不了（1366×768 筆電可視高約 640、
         *   MacBook Air 13" 約 760，全部中招）。使用者裁示「其實應該照著視窗算
         *   比較好」，所以改回 svh 算式，上面那段警告作廢。
         *
         * ⚠️ **刻意沒有上限（沒有 min(660px, …)）**。加了上限就等於「高螢幕
         *    維持 660、矮螢幕才縮」，那是 2026-08-03 提過但**未被採用**的方案；
         *    使用者要的是單純跟著視窗。想加回上限請先問過。
         * ⚠️ 算式必須與 .site-hero-dark 的 --hero-y / --hero-gap 同步 ——
         *    那邊的註解列了完整的四項用量清單，改任何一項都要回去看。
         * ⚠️ 用 svh 不是 vh：手機網址列收合時 vh 會比實際可視區大，
         *    雖然這條規則只在指標裝置生效，svh 是不會踩雷的那個。
         * ⚠️ 這是 min-height 不是 height：格子若因為某種原因內容撐得更高，
         *    寧可高出去（看得到），也不要裁掉。
         * ⚠️ 算式本身在 .site-hero-dark 的 --hero-fit（往下繼承）。fallback 0px
         *    是給主視覺以外的用途留的 —— 那時本來就不該有一屏的限制。
         */
        min-height: var(--hero-fit, 0px);
        transition: grid-template-columns var(--duration-slow) var(--ease),
                    grid-template-rows    var(--duration-slow) var(--ease);
    }

    /* 高度交給 grid，不再用比例 */
    .site-cardgrid-cell { aspect-ratio: auto; }

    /* 未 hover：照片壓暗＋淡淡模糊。模糊只是讓它退到背景，不是要糊掉，
       所以模糊半徑要小 —— 太糊會看不出是什麼照片。
       scale 是為了把模糊在邊緣造成的透明羽化推出視野外，
       不放大的話四邊會出現一圈淡淡的白邊。

       ⚠️ 2026-07-30：`filter: blur(4px)` 已搬到 .site-cardgrid-chroma 的
          backdrop-filter，模糊才能隨游標距離變化。**不要搬回來** —— 兩邊
          都寫的話會疊成 8px，遠處的格子會糊到看不出是什麼照片。
          scale 留著：不論模糊由哪一層負責，邊緣羽化都要推出視野外。
          （沒有 backdrop-filter 的舊瀏覽器由下方的 @supports 補回原本的寫法） */
    .site-cardgrid-media {
        transform: scale(1.05);
    }

    /* 聚光層只在指標裝置存在 */
    .site-cardgrid-chroma { display: block; }

    .site-cardgrid-veil  { opacity: 1; }
    .site-cardgrid-scrim { opacity: 0; }

    /* 靜止：顯示英文標籤，收掉中文 caption */
    .site-cardgrid-label { display: flex; }

    .site-cardgrid-cap,
    .site-cardgrid-go {
        opacity: 0;
        transform: translateY(var(--space-2));
    }

    /* ── hover 中 ── */

    .site-cardgrid-cell.is-open .site-cardgrid-media {
        filter: none;
        transform: none;
    }

    /*
     * 展開那格把聚光層整個關掉，不靠 mask 剛好落在它身上。
     * 游標可以停在一個 700px 寬的格子邊緣，這時圓心離對側很遠，
     * 只靠 mask 的話主視覺會自己糊掉一半。**這格永遠全清晰**是硬規則。
     */
    .site-cardgrid-cell.is-open .site-cardgrid-chroma { opacity: 0; }

    /*
     * ⚠️ 這裡曾經有一條 `.site-cardgrid-cell.is-open { border-color:
     *    hsl(var(--accent-strong)) }`（展開那格的框變 ember 深橘），
     *    2026-07-31 依使用者指示拿掉。
     *
     *    當初加它的理由是接上「hover 到互動元素就變 ember」的語彙。但主視覺
     *    的 is-open **不是 hover 態** —— 它初次載入就有一格是開的，滑鼠移開
     *    也不回復，所以那顆橘框幾乎是常駐的，反而稀釋了 ember「你正在動它」
     *    的訊號（tokens.css 的 ember 軸註解就是在講這件事）。
     *    **不要再加回來。** 要標示當前那格，展開本身＋照片轉清晰已經夠了。
     */
    .site-cardgrid-cell.is-open .site-cardgrid-veil  { opacity: 0; }
    .site-cardgrid-cell.is-open .site-cardgrid-label { opacity: 0; }
    .site-cardgrid-cell.is-open .site-cardgrid-scrim { opacity: 1; }

    .site-cardgrid-cell.is-open .site-cardgrid-cap,
    .site-cardgrid-cell.is-open .site-cardgrid-go {
        opacity: 1;
        transform: none;
    }

    /* 展開的那格字級放大一階：它是主視覺，不該和縮著的格子同級 */
    .site-cardgrid-cell.is-open .site-cardgrid-t {
        font-size: var(--text-2xl);
        font-weight: var(--weight-light);
        letter-spacing: var(--tracking);
    }

    .site-cardgrid-cell.is-open .site-cardgrid-s { font-size: var(--text-sm); }
}

/*
 * 沒有 backdrop-filter 的瀏覽器：把模糊放回照片上，退成 2026-07-30 之前的
 * 「一視同仁壓暗＋模糊」。**不能只是少了聚光** —— 模糊已經從 media 搬到
 * 聚光層，不補這一段的話那些瀏覽器會完全沒有模糊，靜止狀態的手寫標籤
 * 壓在清晰照片上會讀不到（壓暗仍在，但層次是靠模糊拉開的）。
 * 亮度那條通道不必補：veil 的 radial-gradient 在 --cg-r: 0 時就是原本的純色。
 *
 * 條件必須與上面那組揭示媒體查詢逐字一致 —— 只有指標裝置才有模糊可退。
 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    @media (hover: hover) and (pointer: fine) and (min-width: 768px) {
        .site-cardgrid-media { filter: blur(4px); }
        .site-cardgrid-cell.is-open .site-cardgrid-media { filter: none; }
    }
}

/* ─── 手機版主視覺：一張大圖 ＋ 底部橫滑入口列（2026-08-03）──────────
 *
 * 【為什麼改】原本手機是單欄六排（`grid-template-columns: 1fr`），六張 4:3
 * 卡疊起來 1735px（390×780 實測），使用者要捲 **2.2 屏**才看得到服務專區 ——
 * 而服務專區是客戶除了主視覺以外最想展示的區塊。360×640 更慘，2.44 屏。
 * 改成第一格當大圖、其餘收進一條橫滑列之後，主視覺剛好一屏，捲一下就到。
 *
 * 【平板刻意不動】實測 767×1024 是 2 欄 3 排、服務專區落在 1.03 屏，本來就
 * 剛好一屏，沒有問題要解（使用者 2026-08-03：「我是感覺平板還好」）。
 * 所以這一整段只掛在 ≤575.98px，576–767 與觸控 768+ 維持 2 欄 3 排。
 *
 * 【滑動列不是第二份標記】多出來的只有 .site-cardgrid-strip 一層 div，
 * 桌機／平板用 display:contents 讓它消失，五格回到 grid 的直屬項目。
 * 標記與 card-grid.js 都不必分裝置，見 _card_grid.php 的說明。
 */

/*
 * ⚠️ display: contents 不能省，也不能改成 display: block／unset。
 *    這層若真的存在，六格就變成「一個大圖 ＋ 一個包了五格的盒子」兩個 grid
 *    項目，桌機的 3×2 版型（含 card-grid.js 寫的 fr 分配）會整個垮掉。
 *    Chrome 65+／Safari 11.1+／Firefox 37+ 都支援；這層是沒有語意的 div，
 *    早期「display:contents 會從無障礙樹消失」的瑕疵對它沒有影響。
 */
.site-cardgrid-strip { display: contents; }

@media (max-width: 575.98px) {

    /*
     * ─── 服務專區：換頁箭頭移到圖片外面（2026-08-03，使用者回報）────────
     *
     * 手機上兩顆 40px 的圓鈕橫跨圖片寬的 **29.5%**（實測 390×780：圖 298×215、
     * 箭頭列 88×40），而且正好壓在左下的摘要文字那一角。
     *
     * 【為什麼是移出去，不是改小】使用者說「位置蠻好按的，但佔了很大的圖片空間」
     * —— 要解的是佔位、不是位置。改小會同時傷到可按性（40px 已經接近觸控目標的
     * 下限了），移到圖片正下方則是：圖片 100% 還回來、拇指行程幾乎不變、
     * 尺寸與其他五處進入圓鈕仍然一致（brand-spec §7b 的契約不用開例外）。
     *
     * 代價是版面高 48px（40 ＋ --space-2）。進度線本來就在圖片與箭頭之間，
     * 移出來之後剛好變成一條「進度線 ＋ 換頁鈕」的控制列，讀起來更像一組。
     *
     * ⚠️ 只在 ≤575.98px 生效。576–991 是單欄但圖還很寬（575px 視窗時箭頭只佔
     *    17%、768px 時 12.6%），把箭頭推出去反而是白白多 48px 高度。
     */
    .site-vtabs-nav {
        position: static;
        justify-content: flex-end;
        margin-top: var(--space-2);
    }

    .site-cardgrid {
        /* 上排：大圖（1fr，把剩下的高度全吃掉）／下排：滑動列（自己的高度） */
        grid-template-columns: 1fr;
        grid-template-rows: 1fr auto;
        /* 主視覺收進一屏。算式在 .site-hero-dark 的 --hero-fit，
           與桌機那條共用同一份，不要在這裡重抄。 */
        min-height: var(--hero-fit, 0px);
    }

    /* 大圖不吃 4:3 —— 高度由上面的 1fr 決定，剛好把一屏填滿。
       ⚠️ 用子選擇器（>）而不是 .site-cardgrid-cell：滑動列裡那五格仍然要 4:3。 */
    .site-cardgrid > .site-cardgrid-cell { aspect-ratio: auto; }

    .site-cardgrid-strip {
        display: flex;
        gap: var(--space-1);            /* 與格子之間的 gap 同一個值 */
        overflow-x: auto;
        overscroll-behavior-x: contain; /* 滑到底不要把整頁往旁邊帶 */
        scroll-snap-type: x proximity;  /* proximity 不是 mandatory：手指想停在
                                           兩張中間也該讓它停，不要硬拉 */
        /* 捲軸藏起來。內容本身會露出下一張的邊，那才是滑得動的提示。 */
        scrollbar-width: none;
    }

    .site-cardgrid-strip::-webkit-scrollbar { display: none; }

    /*
     * 46%：348px 的容器（390 螢幕扣掉兩側 --gutter-x）放得下兩張 160px 的卡，
     * 第三張露出 24px。**這個露邊是刻意的**，它是「還有更多、可以滑」的唯一
     * 提示（沒有箭頭、沒有圓點）。改寬度要重算，別讓第三張剛好切齊右緣。
     */
    .site-cardgrid-strip .site-cardgrid-cell {
        flex: 0 0 46%;
        scroll-snap-align: start;
    }

    /*
     * 小卡上顯示的是**英文標籤**，不是中文標題 —— 與桌機的靜止態同一套語彙。
     * 160px 寬的卡放不下「人際回應力課程 · 台中場開放報名」這種長度的中文，
     * 而英文標籤（Courses／AI Persona…）本來就是各自目的地的名稱。
     *
     * ⚠️ 中文標題**不可以用 display:none 收掉**。它是這個 <a> 的可及名稱
     *    （英文標籤是 aria-hidden，見 _card_grid.php），拿掉的話讀屏使用者
     *    會聽到五個沒有名字的連結。所以走 .visually-hidden 那套（字仍在 DOM）。
     */
    .site-cardgrid-strip .site-cardgrid-label {
        display: flex;
        /* --text-xl 是為 300px 以上的格子調的，放進 160px 會撞邊。
           2026-08-03：由 --text-base 提到 --text-md（使用者指定「手機版字體
           大一級」）。--text-md 是內文級，160px 的卡放得下「AI Persona」
           這種長度；再大一階（--text-lg）實測會撞邊。 */
        font-size: var(--text-md);
        padding: var(--space-2);
    }

    .site-cardgrid-strip .site-cardgrid-cap {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
        border: 0;
    }

    /*
     * 壓暗照片，白色手寫標籤才讀得到（與桌機靜止態相同的分工：
     * veil 負責壓暗、scrim 是給底部中文字的，這裡沒有底部中文字）。
     * --cg-r 在觸控裝置恆為 0，radial-gradient 退化成最後一階的純色 ——
     * 這是設計好的退化，不是巧合（見 .site-cardgrid-cell 的 --cg-r 註解）。
     *
     * ⚠️ **這裡把 .46 改寫成 .58，不要「統一」回去。** 桌機靜止態的 .46 夠用，
     *    是因為那邊還疊了 .site-cardgrid-chroma 的 `backdrop-filter: blur(4px)`
     *    把照片的亮斑抹平；手機沒有那一層（chroma 只在指標裝置 display:block）。
     *    實測（六張照片各取文字帶內最亮的 6×6 區塊，套上壓暗再與 94% 白字算）：
     *      .46  Corporate Training **3.66**、Coaching 4.37 → 不合格
     *      .58  最低 5.44（Corporate Training），五張全部 ≥ 4.5
     *    手寫體 16px 屬一般文字，門檻是 4.5:1，不是大字的 3:1。
     */
    .site-cardgrid-strip .site-cardgrid-veil {
        opacity: 1;
        background: hsl(var(--black) / .58);
    }

    .site-cardgrid-strip .site-cardgrid-scrim { opacity: 0; }

    /*
     * ⚠️ 小卡不放進入圓鈕，這是對「進入語彙行為契約」（brand-spec §7b）的
     *    **一處例外**，理由：40×40 的圓鈕放在 160×120 的縮圖上會變成整張卡
     *    最重的元素，直接撞「總量守恆／設計降到 15%」那條。滑動列本身就是
     *    一排有名字的入口，可點性由「一排縮圖」這個形態承擔，不需要再標。
     *    大圖那一格的圓鈕照舊，契約在那裡完整成立。
     */
    .site-cardgrid-strip .site-cardgrid-go { display: none; }

    /*
     * 大小卡對調的過場（2026-08-03 下午，使用者：「動態好像太誇張了」）。
     *
     * 只有交叉淡入，**沒有位移也沒有縮放**。第一版是給兩張卡各掛一個
     * view-transition-name，瀏覽器會做幾何形變 —— 小卡一路放大飛到大圖位置，
     * 太用力。拿掉名字之後只剩 root 這一層的淡入淡出（見 card-grid.js 的
     * promote()），這裡把預設的 250ms 收到全站的 --duration 並換上 --ease。
     *
     * ⚠️ 減少動態不必在這裡處理：tokens.css 在 prefers-reduced-motion 下把
     *    --duration 設成 0ms，而且 card-grid.js 那邊本來就直接跳過轉場，
     *    兩道防線都在。
     * ⚠️ 全站目前只有這一處用 View Transition，所以規則寫在手機版這段裡；
     *    日後別處要用，請自己另外指定，不要把這條搬到外層當通則。
     */
    ::view-transition-old(root),
    ::view-transition-new(root) {
        animation-duration: var(--duration);
        animation-timing-function: var(--ease);
    }
}

/* ─── 實體課程卡（site-coursecard）─────────────────────────────────
 * 首頁與課程列表頁共用，標記與設計理由見 _course_card.php。
 * 移植自 21st.dev 的 PlaceCard，framer-motion 的兩組動畫改寫為：
 *   whileInView 進場 → [data-draw] + .is-drawn（沿用 draw-in.js）
 *   whileHover 彈簧  → translateY，用 --ease 不用彈簧
 *                      （原本還帶 box-shadow，2026-07-30 下午拿掉）
 *
 * 元件上仍帶著 .site-card（白底細框），但這裡把那兩項都覆寫掉 ——
 * 見下方 .site-coursecard 的 border/background 註解。
 */

.site-coursecard {
    position: relative;
    display: flex;
    flex-direction: column;
    /* 同列等高：Bootstrap 的 .h-100 由這裡取代，呼叫端不必再帶 utility */
    height: 100%;
    /* 2026-07-30（下午）：改成與知識卡同一種構造 —— 卡片自己吃 padding、
       圖片內嵌。圖不再切齊卡緣，所以這一層不需要 overflow:hidden，
       裁切改由 .site-coursecard-media 自己做（同 .site-kncard-media）。 */
    padding: var(--space-3);
    border-radius: var(--radius-lg);
    /*
     * 2026-07-30：細框換成暖白底，與知識卡統一。
     * **覆寫 .site-card 的 border 與 background 是刻意的**，不是漏帶 class：
     * 元件仍需要 .site-card 那個 hook（內頁其他地方靠它套通用卡片行為），
     * 但這張卡的分界改走「比頁面底亮一階」，線與框都不留。
     * 不直接改 .site-card 是因為它還服務會員表單那些卡，那些不該一起變。
     *
     * ⚠️ 陰影已於 2026-07-30（下午）依使用者指示全面移除（卡片與按鈕）。
     *    分界因此**只剩底色差**（--card-soft vs --background）—— 換底色 token
     *    時務必確認這兩個值還有差，否則整張卡會在頁面上消失。
     */
    border: 0;
    background: hsl(var(--card-soft));
    color: hsl(var(--foreground));
    text-decoration: none;
    transition: transform var(--duration-slow) var(--ease),
                opacity var(--duration-slow) var(--ease);
}

/*
 * 進場：捲進視窗才淡入上浮，由 draw-in.js 加 .is-drawn。
 *
 * ⚠️ 起始的 opacity:0 掛在 `.has-draw` 之下，不能直接寫在 .site-coursecard 上。
 *    課程卡是**內容**，不是裝飾 —— 若 draw-in.js 沒載入（404、被擋、舊瀏覽器
 *    整支拋錯），直接寫死 opacity:0 會讓八張課程卡永久看不見。
 *    .has-draw 由 draw-in.js 自己掛在 <html> 上：JS 沒跑就沒有這個 class，
 *    卡片以完整態呈現，只是少了進場動畫。分隔筆觸可以接受消失，課程不行。
 *
 * 同列錯開用 :nth-child 的 transition-delay，不由 JS 算 index —— CSP 擋
 * inline style，JS 寫不進去（詳見 card-grid.js 檔頭的實測）。
 * 延遲掛在 .col 上而不是卡上：卡是 .col 的子層，選 nth-child 要選有兄弟的那一層。
 */
.has-draw .site-coursecard { opacity: 0; transform: translateY(var(--space-3)); }
.has-draw .site-coursecard.is-drawn { opacity: 1; transform: none; }

.site-coursecard-col:nth-child(4n + 2) .site-coursecard { transition-delay: 60ms; }
.site-coursecard-col:nth-child(4n + 3) .site-coursecard { transition-delay: 120ms; }
.site-coursecard-col:nth-child(4n + 4) .site-coursecard { transition-delay: 180ms; }

/* hover 抬起。specificity 要壓過 .has-draw ... .is-drawn（0,2,0 → 這裡 0,3,0），
   否則 transform:none 會贏，卡片不會浮起來。
   用 translateY 而非原版的 scale：scale 會讓卡內文字重新光柵化而發虛。
   陰影拿掉後（2026-07-30 下午）hover 只剩位移，這是刻意的。 */
.site-coursecard.is-drawn:hover,
.site-coursecard:hover {
    transform: translateY(-3px);
}

.site-coursecard:focus-visible {
    outline: 2px solid hsl(var(--ring));
    outline-offset: 2px;
}

/* 圖片內嵌（2026-07-30 下午改）：與 .site-kncard-media 同一套 ——
   卡片給 padding，圖自己裁圓角，圖與卡緣之間因此有一圈留白。
   內圓角比卡片降一階，理由與知識卡相同（規則見 tokens.css 圓角區塊）。 */
.site-coursecard-media {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius);
}

/* 狀態 pill 疊在圖片左上（原版 badge 的位置）。
   沿用 .site-pill-dark 的實心深底＋白字，而不是原版的半透明淺底 ——
   六張課程插畫都是高明度的奶油色，半透明淺底壓上去會讀不出來；
   實心深底則不論底圖是什麼都保證對比。margin 要歸零，.site-pill 預設帶了。 */
/* 位置跟著 .site-coursecard-go 一起由 space-2 拉到 space-3（2026-07-30 下午）
   —— 兩者是同一條上緣，只改一邊會看出來歪掉。 */
.site-coursecard-status {
    position: absolute;
    top: var(--space-3);
    left: var(--space-3);
    z-index: 1;
    margin: 0;
}

/* 右上角的「進入」圓鈕。外觀走 .site-go，這裡只管定位。
   與知識卡的 .site-kncard-go 同一個位置，兩張卡並排時對得上。
   2026-07-30 下午由 space-2 拉到 space-3，照參考圖 —— 圖片本身已經內縮
   一圈 space-3，圓鈕再貼著圖角 8px 會顯得被擠在角落。 */
.site-coursecard-go {
    position: absolute;
    top: var(--space-3);
    right: var(--space-3);
    z-index: 1;
}

/* 左右與下方的留白由卡片自己的 padding 提供，這裡只補圖與文字之間的距離
   —— 與 .site-kncard-body 逐字相同的理由：卡片有 padding 之後若這裡再留
   側邊 padding，文字會比圖片多縮一圈、左緣對不齊。 */
.site-coursecard-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    /* flex-grow 讓卡身吃掉剩餘高度，CTA 才能靠 margin-top:auto 貼底 */
    flex-grow: 1;
    padding: var(--space-3) 0 0;
}

.site-coursecard-t {
    font-family: var(--font-display);
    font-size: var(--text-md);
    font-weight: var(--weight-bold);
    line-height: var(--leading-tight);
}

.site-coursecard-meta {
    font-size: var(--text-xs);
    line-height: var(--leading-tight);
    color: hsl(var(--muted-foreground));
}

.site-coursecard-sub {
    font-size: var(--text-sm);
    line-height: var(--leading-normal);
    color: hsl(var(--muted-foreground));
}

/* .site-coursecard-cta 已移除（2026-07-30）：卡底的「課程詳情 / 報名」文字
   換成圖片右上的 .site-coursecard-go 圓鈕，理由見 _course_card.php 檔頭。
   卡底不再需要對齊基線，.site-coursecard-body 的 flex-grow 就夠了。 */

/* ─── 知識文章卡（site-kncard）──────────────────────────────────────
 * 四處共用（首頁／知識列表／知識內頁相關文章的桌機與手機兩版），
 * 標記與無障礙決策見 _knowledge_card.php。
 *
 * ⚠️ **刻意不帶 border**，與 .site-card 家族（課程卡）的白底細框不同。
 *    參考圖是「淺底卡＋內嵌圖」的做法，靠底色差異與圓角分界，不靠線。
 *    這是設計上的選擇，不是漏寫 —— 要改成有框請一併問過設計。
 */

.site-kncard {
    position: relative;                 /* 整張卡可點的覆蓋層靠它定位，見 .site-kncard-t::after */
    display: flex;
    flex-direction: column;
    height: 100%;                       /* 同列等高 */
    padding: var(--space-3);            /* 做出參考圖的內嵌感 */
    border-radius: var(--radius-lg);
    /* 用 --card-soft 而不是 --card：後者與 --background 同為純白，
       這張卡是靠底色分界（沒有 border），同色就完全看不見輪廓。 */
    background: hsl(var(--card-soft));
    /* 陰影史：2026-07-30 上午由「只在 hover 有」改成靜止就帶（往數位化走）；
       同日下午使用者指示全站卡片與按鈕**一律不要陰影**，整個拿掉。
       ⚠️ 這張卡本來就沒有 border（見上面），陰影拿掉後分界**只剩底色差**
          （--card-soft vs --background）—— 動這兩個 token 前先確認還有差。 */
    transition: transform var(--duration-slow) var(--ease),
                opacity var(--duration-slow) var(--ease);
}

/*
 * 進場：與課程卡同一套機制。起始的 opacity:0 掛在 .has-draw 之下 ——
 * 文章卡是**內容**，draw-in.js 沒載入時必須以完整態呈現，不能永久看不見。
 * 錯開延遲用 :nth-child（CSP 擋 inline style，JS 寫不進去）。
 * 延遲掛在 .col 上而不是卡上：卡是 .col 的子層，nth-child 要選有兄弟的那一層。
 */
.has-draw .site-kncard { opacity: 0; transform: translateY(var(--space-3)); }
.has-draw .site-kncard.is-drawn { opacity: 1; transform: none; }

.site-kncard-col:nth-child(4n + 2) .site-kncard { transition-delay: 60ms; }
.site-kncard-col:nth-child(4n + 3) .site-kncard { transition-delay: 120ms; }
.site-kncard-col:nth-child(4n + 4) .site-kncard { transition-delay: 180ms; }

/* hover 抬起。specificity 要壓過 .has-draw … .is-drawn 的 transform:none
   陰影拿掉後（2026-07-30 下午）hover 只剩位移，這是刻意的。 */
.site-kncard.is-drawn:hover,
.site-kncard:hover {
    transform: translateY(-3px);
}

/* ── 圖片區 ── */

.site-kncard-media {
    position: relative;
    /* 裁圓角，也擋住圖片可能的溢出 */
    overflow: hidden;
    /* 內圓角比卡片降一階：卡片內距 16px、外圓角 16px，內層再給 16px 會鼓出來。
       規則寫在 tokens.css 的圓角區塊。 */
    border-radius: var(--radius);
}

.site-kncard-fig {
    display: block;
}

/* 底部漸層：左下的白字要在任何一張照片上都讀得到。
   知識素材明暗差很多（奶油色桌面、白牆、深色木桌），不壓暗的話
   淺色那幾張上面的白字會整段消失。做法同主視覺的 .site-cardgrid-scrim。

   ⚠️ 2026-07-30 壓高了：左下改成日期＋標籤**兩行**，原本 58% 的漸層頂端
      正好切在日期那一行上，淺色圖上日期會半透明地消失。 */
.site-kncard-scrim {
    position: absolute;
    inset: 0;
    /* 不吃滑鼠事件，否則會蓋掉底下圖連結的命中區 */
    pointer-events: none;
    background: linear-gradient(to top,
                hsl(var(--black) / .66) 0%,
                hsl(var(--black) / .34) 26%,
                hsl(var(--black) / .10) 48%,
                hsl(var(--black) / 0) 70%);
}

/* 右上：進入內頁的圓鈕。外觀走 .site-go，這裡只管定位。
   2026-07-30 下午由 space-2 拉到 space-3，與課程卡同步（照參考圖）。
   z-index 要壓過 .site-kncard-t::after 那片覆蓋層（z-index:1），
   否則滑鼠根本碰不到它，hover 變橘那組規則會像壞掉一樣完全不生效。 */
.site-kncard-go {
    position: absolute;
    top: var(--space-3);
    right: var(--space-3);
    z-index: 2;
}

/* 圖片下方最底一排：podcast／影片。外觀走 .site-go。
   margin-top:auto 把它推到卡底，同列各卡的圓鈕因此排在同一條線上，
   不受標題折行與說明長短影響。 */
.site-kncard-actions {
    display: flex;
    gap: var(--space-2);
    margin-top: auto;
    padding-top: var(--space-3);
}

/* z-index 理由同 .site-kncard-go。這兩顆尤其不能被吃掉 ——
   它們通往**外部平台**，被覆蓋層接管會變成點了卻進到文章內頁。 */
.site-kncard-act {
    position: relative;
    z-index: 2;
}

/* 左下：日期一行 ＋ 分類與標籤一行（使用者指定的版型）。 */
.site-kncard-meta {
    position: absolute;
    left: var(--space-3);
    right: var(--space-3);
    bottom: var(--space-3);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

/* 分類與標籤。純文字＋圖示、無底色（靠上面的 scrim 讀）。 */
.site-kncard-tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
}

/* z-index 理由同 .site-kncard-go：要浮在整張卡的覆蓋層之上，
   否則點分類／標籤會變成進文章內頁，篩選功能等於消失。 */
.site-kncard-tag {
    position: relative;
    z-index: 2;
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    line-height: var(--leading-none);
    color: hsl(var(--white) / .94);
    text-decoration: none;
    transition: color var(--transition);
}

.site-kncard-tag:hover,
.site-kncard-tag:focus-visible {
    color: hsl(var(--white));
}

/* 正在篩選中的那顆。列表頁原本靠 .site-pill-dark 反白，改成壓在照片上之後
   沒有底色可以反白，改用底線 —— 這是「你正在看這個分類」的唯一提示，
   不能只靠顏色（那對色弱使用者不成立，也違反 WCAG 1.4.1 不單以顏色傳達）。 */
.site-kncard-tag.is-on {
    color: hsl(var(--white));
    text-decoration: underline;
    text-underline-offset: 3px;
}

.site-kncard-tag:focus-visible {
    outline: 2px solid hsl(var(--white));
    outline-offset: 3px;
    border-radius: var(--radius-sm);
}

/* ── 圖片下方 ── */

/* 左右與下方的留白由卡片自己的 padding 提供，這裡只補圖與文字之間的距離。
   卡片 padding 提到 space-3 之後若再留側邊 padding，文字會比圖片多縮 8px、
   左緣對不齊。 */
.site-kncard-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    flex-grow: 1;
    padding: var(--space-3) 0 0;
}

/* 日期移到圖片左下之後改白字（2026-07-30）。原本在圖片下方、用次要文字色，
   壓在照片上必須反白，靠 .site-kncard-scrim 撐對比。

   ⚠️ **不要為了讓它比標籤退一階而降成 /.88**（試過，退回來了）。
      它是 --text-2xs（約 11px）的小字，要求 4.5:1。以最惡劣情況試算
      （照片該處是純白、漸層在該高度約 57% 黑）：純白字 5.2:1 過，
      /.88 只有 4.46:1，差一點就不合格。層級差異靠字級與字距做，不靠透明度。 */
.site-kncard-date {
    font-size: var(--text-2xs);
    line-height: var(--leading-none);
    letter-spacing: var(--tracking-wider);
    color: hsl(var(--white));
}

.site-kncard-t {
    display: block;
    font-family: var(--font-display);
    font-size: var(--text-md);
    font-weight: var(--weight-bold);
    line-height: var(--leading-tight);
    color: hsl(var(--foreground));
    /* text-decoration: none 由上方「可點標題的 hover 回饋」統一設定（它是 <a>，
       要拿掉瀏覽器預設底線）。滑入的那條線是背景漸層畫的，兩者不衝突。 */
}

/*
 * 整張卡可點（2026-07-30 下午，使用者指定「點 item 或按鈕都要能前往」）。
 *
 * 四塊裡只有知識卡點空白處沒反應 —— 主視覺格子與課程卡整個外層就是 <a>，
 * 這張不行：卡上有五個目的地（標題／圖／右上圓鈕／分類／標籤／podcast／影片），
 * HTML 不允許巢狀 <a>。做法改成讓**標題那個連結**長出一片覆蓋整張卡的偽元素，
 * 這樣連結數量沒有增加，可及名稱仍是標題，讀屏聽到的東西一個字都沒變。
 *
 * ⚠️ 卡上其他目的地必須浮到覆蓋層之上（見下面那條 z-index:2），否則會被它
 *    整片吃掉 —— 分類、標籤、podcast、影片會全部變成「進文章內頁」。
 * ⚠️ 副作用：卡內文字不能再用滑鼠選取（覆蓋層擋住）。這是 stretched-link
 *    這個手法的固有代價，卡片型內容可以接受；若之後有人回報「複製不了標題」，
 *    原因在這裡。
 */
.site-kncard-t::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
}

/* 滑到卡上任何一處，標題就跟著出現底線 —— 覆蓋層讓 :hover 一路涵蓋整張卡，
   這正好變成「整張卡是可以點的」的提示，不必另外做。 */
/* 2026-07-31：原本這裡是 `.site-kncard-t:hover { text-decoration: underline }`
   —— 瞬間出現、而且只有知識卡有。已改為全站四處共用的「底線淡入」，
   規則在上方「可點標題的 hover 回饋」那一段，這裡不再單獨定義。 */

.site-kncard-sub {
    margin: 0;
    font-size: var(--text-sm);
    line-height: var(--leading-normal);
    color: hsl(var(--muted-foreground));
}

/* $ratio='fill'：尺寸由父層決定（卡片堆疊的卡片由 JS 設寬高） */
.site-img-fill {
    position: absolute;
    inset: 0;
}
