/* ================================================================
   Board Sea Technology — 03-header-hero.css
   S0 header / nav overlay ＋ S1 hero ＋ 導覽 RWD。

   ⚠️ header 與 hero **刻意沒有拆開**。原始碼裡兩者是交錯的：.hero 與
      .site-header 共用同一條規則、focus-visible 那組選擇器同時涵蓋
      .site-header / .nav-overlay，硬拆要改寫選擇器 —— 那已經不是搬檔案
      而是改設計，風險不成比例。內頁的新樣式請進 pages/，本檔不會再長大。
   ⚠️ .site-header 本體永久禁用 backdrop-filter / filter / transform /
      will-change（DESIGN.md 硬約束）：會讓 fixed 定位的 nav overlay
      定位基準塌回 72px 高的導覽列裡。

   ── 本檔是 styles.css（4,118 行）拆檔的第 3/5 段 ──
   拆檔只切不搬：五個檔案**依序串接回去逐位元組等於原檔**，層疊結果不變。
   由 includes/_head.php 依 01→05 的順序載入，**順序不可調換**。
   來源 banner 邊界見 git 歷史（2026-08-20 拆檔 commit）。
   ================================================================ */

/* ================================================================
   COMPONENT LAYER — Unit 2 of 2 (S0 header/nav + S1 hero)
   單一真相源：/Users/dawa/博洋興業有限公司/site-b/DESIGN.md
   對應：component.header / component.nav_item / component.logo /
         component.hero_title / component.hero_subtitle（chevron 依 D16
         不渲染，見 index.html 內註解）/ breakpoints._cascade（漢堡觸發
         沿用 tablet 斷點 max-width:1023px，同 --header-height 覆寫點）

   本層以下只引用既有 token（:root 與四個 media block，Unit 1 建立），
   不 hardcode 色值／間距／字級／圓角／動畫時長。唯一新增的一個 token
   （--letter-spacing-label-wide）已於上方 :root 區塊尾端補入，非本區塊
   新建 :root（維持 DESIGN.md 交接契約硬約束⑨「單一 :root」）。
   ================================================================ */

/* ── Skip link（DESIGN.md 交接契約 2；z 用 --z-skip-link） ── */
.skip-link {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  z-index: var(--z-skip-link);
  padding: var(--space-3) var(--space-5);
  background-color: var(--surface-inverse);
  color: var(--text-on-inverse);
  font-family: var(--type-scale-caption-family);
  font-size: var(--type-scale-caption-size);
  font-weight: var(--font-weight-bold);
  text-decoration: none;
  border-radius: var(--radius-alias-control);
  transform: translateY(-200%);
  transition: transform var(--motion-duration-hover) var(--motion-easing-standard);
}

.skip-link:focus {
  transform: translateY(0);
}

/* ── 共用 utility：視覺隱藏但螢幕閱讀器可讀（無設計值，純 a11y 手法，
     不受 token 化規則約束） ── */
.u-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── 共用 focus-visible（極端對比規則 #5：3px ring + 2px offset，
     outline:none 無替代即驗收失敗）。本輪新增元件全部在淺底（header /
     nav-overlay 皆 surface_page 或 surface_subtle），一律用 on_light；
     hero 本輪無可聚焦元素（chevron 依 D16 不渲染），故不需 on_dark。 ── */
.skip-link:focus-visible,
.site-header a:focus-visible,
.site-header button:focus-visible,
.nav-overlay a:focus-visible,
.nav-overlay button:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-on-light);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-alias-control);
}

/* 2026-08-18 QA 修正（qaB a11y FAIL，最低 1.12:1）：skip-link 本身背景是
   navy（--surface-inverse），但 focus ring 因 outline-offset:2px 外擴，
   實際落在 ring 外緣的是頁面白底，不是 skip-link 自己的深色背景——
   on-dark 色（#CAE5FF）對白僅 1.12:1，未達 3:1。改用 on-light（與上方
   共用規則其餘選擇器一致，rgb(0,115,231) 對 #FFFFFF 實測 4.56:1，見本檔
   .card__cta:focus-visible 同色值案例）。移除這條規則其實也會自動吃到
   上面共用規則的 on-light，這裡保留一條顯式覆寫＋說明，是不讓「skip-link
   曾經需要 on-dark」的假設被未來維護者無意間復原。 */
.skip-link:focus-visible {
  outline-color: var(--focus-ring-on-light);
}

/* 捲動精簡態 focus ring（2026-08-19 D32）：70% 白玻璃疊深底時 accent.500 最壞僅
   2.16:1，改用 accent.700（見 :root --focus-ring-on-glass 旁註解），最壞 3.95:1。
   只在 .site-header.is-stuck 內部覆寫，.nav-overlay 內的 focus ring 不受影響
   （面板是 --surface-subtle 實心底，既有值仍達標）。 */
.site-header.is-stuck a:focus-visible,
.site-header.is-stuck button:focus-visible {
  outline-color: var(--focus-ring-on-glass);
}

/* iOS Safari pitfalls #9：關掉預設的灰色點擊高亮——本輪自己畫了
   :active 態（translateY(1px)），兩層疊加會顯得雜亂。實際讓 :active
   在 iOS 上真的觸發，還需要 site.js 掛一個空的 passive touchstart
   listener，見該檔。 */
.site-header a,
.site-header button,
.nav-overlay a,
.nav-overlay button {
  -webkit-tap-highlight-color: transparent;
}


/* ── Header（component.header.resting + component.header.stuck，2026-08-19 派工單 D30）。
   硬阻擋：.nav-overlay 是 .site-header 的 position:fixed 子元素（index.html:90-116）。
   CSS 規範下任何帶 filter/backdrop-filter/transform 的元素會成為其 fixed 後代的
   containing block（iOS Safari pitfalls skill rendering-compositing.md 條目 1 同一機制、
   全引擎皆然，非 iOS 專屬）——在 .site-header 本體掛 backdrop-filter 會讓全螢幕遮罩塌進
   72px 的 header 盒子，選單整組崩壞。玻璃層因此改掛虛擬元素（2026-08-19 B1 第 2 段已由
   .site-header::before 搬遷為 .site-header__bar::before，見下方搬遷註解）：
   虛擬元素無後代、也不是 .nav-overlay 的祖先，無此效應。硬約束：.site-header 本體永久
   禁止 backdrop-filter / filter / transform / will-change。
   position 由 sticky 改 fixed：sticky 仍佔 flow 空間，stuck 態改高度會連帶縮小其 flow
   box、推移全站後續內容（真實 CLS）。fixed + body 常數 padding-top（見 body 規則）使
   CLS 恆為 0，且靜止態版面逐 px 不變。 ── */
.site-header {
  position: fixed;
  top: 0;
  inset-inline: 0;
  z-index: var(--z-header);
  padding-block-start: 0; /* 2026-08-19 B1 浮島第 3 段：靜止態顯式 0，與下方 .is-stuck
     覆寫成對，detach 距離的完整故事收在同一組規則裡。選 padding 而非 margin：
     .site-header 是 position:fixed 且僅設 top:0（bottom auto），margin-block-start
     在此情形下雖能等效位移，但需靠 top/margin/bottom 三方 over-constrained 公式求解；
     padding 是直接的內部空間，行為更可預測，對 iOS Safari 這類對 fixed 定位常有例外
     行為的引擎更穩健（不依賴該套測試覆蓋較少的路徑）。 */
}

/* 2026-08-19 用戶當面裁決撤回浮島（派工單「Header 去藥丸化」）：B1 浮島形變
   （detach 8px／border-radius／窄寬 .site-header__bar::before 玻璃層）與 D46
   滿版托盤層（rgba(255,255,255,0.35)）全數撤回。用戶原話：「網頁往下滾 就置頂
   寬度滿版就好 你想太多了 我沒有要藥丸狀」。截圖裡標題文字背後的灰色色塊即為
   D46 托盤層（stuck 態 35% 白疊在窄寬浮島玻璃層之後所致），成因已定位、隨浮島
   一併撤除，不再有雙層落差。
   本規則現為 header 唯一的玻璃背景層，滿版（inset:0，containing block = fixed
   的 .site-header 本體），方正、貼齊視口上緣，無 border-radius、無 detach。
   靜止態不透明白；stuck 態見下方 .is-stuck::before 規則——原掛在
   .site-header__bar::before 的 70% 白 + blur(20px) saturate(1.8) 玻璃參數
   原樣搬回本層，數值不變（D31 不得改動）。 */
.site-header::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: var(--surface-page);
}

.site-header.is-stuck::before {
  background-color: var(--header-stuck-bg);
  /* WebKit bug 289800 直寫字面值例外（DESIGN.md D36）：字面值須逐字等於
     --header-stuck-backdrop 的展開值。token 仍存於 :root 作單一真相源，異動
     --elevation-backdrop-blur-header 或 --header-stuck-backdrop 時須同步手動
     更新下方兩行字面值。玻璃疊純黑時對比地板：黑字 9.96:1／on-glass 兩項
     3.95:1，全站最低值，數學下界不隨本輪撤回浮島而改變。真機 iOS 渲染驗證
     待 quality（見驗收 G2）。 */
  backdrop-filter: blur(20px) saturate(1.8); /* token: --header-stuck-backdrop，WebKit bug 289800，須與 :root 展開值同步（DESIGN.md D36） */
  -webkit-backdrop-filter: blur(20px) saturate(1.8); /* token: --header-stuck-backdrop，WebKit bug 289800，須與 :root 展開值同步（DESIGN.md D36） */
  box-shadow: var(--elevation-level3);
}

/* 無毛玻璃支援時退回不透明白 bar——沒有模糊的 70% 白會讓底下內容高頻細節透出、
   「字疊字」可讀性壞掉，誠實降級為一片實心白。 */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .site-header.is-stuck::before {
    background-color: var(--surface-page);
  }
}

/* 漸層模糊邊緣層（原 .site-header__bar::after，A2 第 4 段／D40）已於 2026-08-19 用戶
   裁決簡化批整層移除——用戶原話「60% 或 70% 白 + 背景模糊，我可以接受」，玻璃收斂為
   單層（見上方 .site-header.is-stuck .site-header__bar::before）。本區塊原本自帶的
   退場方案（「若 quality 量到掉幀，整段刪除」）在此提前執行，觸發原因是用戶裁決而非
   效能問題。專屬 token（--header-glass-blur-edge／--header-glass-edge-mask）已於
   :root 一併移除，DESIGN.md 對應條目待清理（見任務報告）。 */

/* IntersectionObserver 雙哨兵（site.js HEADER STUCK 模組消費）。無定位祖先 →
   containing block = ICB，隨文件捲動；純量測用，aria-hidden + pointer-events:none。 */
[data-header-sentinel] {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  opacity: 0;
  pointer-events: none;
}

[data-header-sentinel="enter"] {
  height: var(--header-height);
}

[data-header-sentinel="exit"] {
  height: calc(var(--header-height) - var(--space-5));
}

/* html.is-header-ready 由 site.js 於首次 IO callback 後的下一個 rAF 加上，避免瀏覽器
   捲動位置還原（reload 時 scrollY 非 0）造成的首次套用播放變形動畫。delay 全部為 0、
   完全同步起跑——是同一個手勢的多個面向，錯開起跑點會讀成四件事各自發生（先例：
   component.menu_toggle 與面板刻意共用同一 duration）。

   D45（2026-08-19）：材質與幾何兩組現已統一為單一 duration（280ms）+ 單一 easing
   （--motion-duration-menu + --motion-easing-glass），delay 全 0，終點零時間差。此前
   （B1 第 3 段）曾拆成材質 240ms/standard、幾何 280ms/glass-overshoot 兩組，40ms 終點差
   是「不順暢」的獨立來源之一（材質淡入到一半、bar 已收完，玻璃鎖定後 bar 還要再晃
   40ms）；用戶實測回報故障感後，本次一併恢復完全同步。
   幾何維持 280ms（非退回 240ms）的可算理由：B1 第 3 段把行程從 30px（102→72）加大到
   38px（102→64），cubic-bezier(0.4,0,0.2,1) 峰值斜率 2.5，改動前峰值速度
   2.5×30/240=0.313px/ms；若行程加大而沿用 240ms 會變 2.5×38/240=0.396px/ms（快 27%，
   是生硬感的物理來源）；280ms 下為 2.5×38/280=0.339px/ms，貼近改動前水準。
   硬約束：往後任何加進 header 精簡態的 transition（含 .site-header__bar::after）必須
   引用同一組 token，不得自選 duration/easing、不得引入 stagger。

   reduced-motion：--motion-duration-menu 已在 reduce 區塊歸零至 0.01ms（不重寫
   media query，硬約束），瞬間跳位——reduced-motion 針對移動過程，不是終點。 */
/* .site-header__bar::before 已隨浮島撤回一併刪除（2026-08-19 用戶裁決），原本掛在此
   選擇器的材質 transition（background-color/backdrop-filter/box-shadow）併入下方
   .site-header::before 規則。.site-header__bar::after 的材質 transition（原 A2 第 4
   段第 9 項，D40）已隨 ::after 整層於用戶裁決簡化批一併移除，見上方 :root 與
   ::before 規則的說明。 */

html.is-header-ready .site-header {
  transition: padding-block-start var(--motion-duration-menu) var(--motion-easing-glass); /* 浮島撤回後 padding-block-start 恆為 0，本條為 no-op，保留以存決策脈絡（成本為零） */
}

html.is-header-ready .site-header::before {
  transition:
    background-color var(--motion-duration-menu) var(--motion-easing-glass),
    backdrop-filter var(--motion-duration-menu) var(--motion-easing-glass),
    -webkit-backdrop-filter var(--motion-duration-menu) var(--motion-easing-glass),
    box-shadow var(--motion-duration-menu) var(--motion-easing-glass); /* 浮島撤回後，玻璃
     全部參數（含 blur/shadow）搬回本層一併過場，原分散在 .site-header__bar::before 的
     transition 規則已刪除、併入本條。虛擬元素需自己的 transition，不可併入本體的
     padding-block-start 那條。 */
}

html.is-header-ready .site-header__bar {
  transition:
    min-height var(--motion-duration-menu) var(--motion-easing-glass),
    padding-block var(--motion-duration-menu) var(--motion-easing-glass); /* border-radius
     已隨浮島撤回移除（2026-08-19 用戶裁決），stuck 態恆為方正、無需過場 */
}

html.is-header-ready .site-header .logo__mark {
  transition: transform var(--motion-duration-menu) var(--motion-easing-glass);
}

/* 選單開啟時 body 被 site.js 設為 position:fixed（iOS Safari pitfall #6）。
   此時 sticky 的 containing block / scrollport 關係改變，跨瀏覽器行為不保證一致 →
   顯式改為 fixed，讓 toggle 的視口座標在開/關兩態恆等（驗收 A1）。
   `html.is-menu-open` 這個 class 由 site.js 負責加（另一批工作），此規則在該 class
   出現前不會生效，屬預期中間態，非本次缺陷。
   2026-08-19 D30 追加註解：新架構下 .site-header 本來就恆為 fixed，本規則現為
   no-op，保留以存決策脈絡，成本為零（uiux 交接契約硬約束，不刪除）。 */
html.is-menu-open .site-header {
  position: fixed;
  top: 0;
  inset-inline: 0;
}

/* nav 錨點捲動偏移（2026-08-18 派工單 Unit 2）：header 為 position:sticky，
   不加此規則錨點跳轉後區塊標題會被吃進 header 底下。只影響錨點跳轉後的
   停靠位置，不影響一般文件流版面（不違反「不動版面」限制）。涵蓋所有
   可能的錨點目標（hero/section/footer），值沿用既有 --header-height
   token（含其 tablet/mobile 覆寫）。 */
.hero,
.section,
.site-footer {
  scroll-margin-top: var(--header-height);
}

/* ── header bar 左緣公式與 .container（C2，styles.css:2000）結構同源
   （width: min(100% - 2*inset, cap); margin-inline:auto），但 cap 值
   自 2026-08-19 起改吃專屬的 header 上限 token（定義見 :root LAYOUT
   區），不再直接引用 --container-max-width——理由見下方方向 E。

   沿革：原固定 padding-inline 與 .container 封頂公式行為分歧，>1440
   時落差隨視口線性擴大（1920 下達 240px），使 DESIGN.md 驗收點 9 在
   ≥1441 靜默失敗。2026-08-18 方向 A 改成完全複用 container_formula，
   ≤1440 落差鎖 0，但也讓 header 寬度跟內容容器一起封頂在 1200，用戶
   看過實裝後裁示 NAV 太窄。

   2026-08-19 方向 E：header 拆出獨立上限 token（:root
   預設 = var(--container-max-width) = 1200，≤1440 逐 px 沿用方向 A
   的零落差行為；@media (min-width:1441px) 覆寫為 1680px）。NAV 因此
   能比內容容器寬，但落差不再無上限線性擴大——公式改變後上限鎖死在
   (headerCap−1200)/2，1920 與 2560 兩個視口的落差須完全相同即為證據。
   --container-max-width／--inset-x／.container 本身不受影響。
   DESIGN.md layout.header_max_width／header_bar_grid 已同步。

   2026-08-19 B1 浮島追記（第 2 段，玻璃層搬遷）：本輪與後續 B1 形變（detach + radius +
   高度，第 3 段執行）皆不改動本段寬度公式，D1（唯一 120px 左緣）未撤回。

   2026-08-20 追記（container 封頂值後續變更，上方歷史敘事原樣保留不刪）：
   --container-max-width 本身已由 1200 改為 1680（用戶顯性推翻 D28 對方向
   D 的否決），與 --header-max-width 現值相等。上面「方向 E」段落所述的
   header／container 分流（header 1680 vs container 1200，落差鎖死在
   (headerCap−1200)/2）與本段 D1「唯一 120px 左緣」，皆為此次變更前的
   歷史狀態；現況是兩者共用同一個 cap、同一條公式，四斷點左緣逐 px 相等，
   落差恆為 0，--inset-x 現為 --space-9(60px)，非 120px。 ── */
.site-header__bar {
  /* 2026-08-19 用戶當面裁決撤回浮島：position:relative 已移除。原本存在的唯一理由是
     給已刪除的 .site-header__bar::before 玻璃層當 containing block（讓 border-radius:
     inherit 的圓角落在浮島邊界上）；玻璃層與 border-radius 皆已撤回，此宣告不再有
     消費者，屬死代碼一併清除。z-index 解析不受影響（本規則 z-index 原為 auto，未
     建立 stacking context，移除 position 對此無影響）。 */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-7);
  min-height: var(--header-height);
  width: min(100% - 2 * var(--inset-x), var(--header-max-width));
  margin-inline: auto;
  padding-block: var(--space-4);
}

/* 捲動精簡態（2026-08-19 D30）：bar 高度 min-height 由 --header-height-compact 承擔
   （2026-08-19 第 3 段起 base/wide 64px、tablet/mobile 64px 四斷點統一，見 :root 三處
   token）。bar 寬度公式必須不變——若精簡態換窄上限，logo 與 toggle 會在變形時橫向滑動，
   讀起來像版面故障。
   2026-08-19 B1 浮島第 3 段（形變落地）：padding-block base/wide 由 space.3(12)→space.2(8)，
   與 --header-height-compact 72→64 同步（48+2×8=64 的數學閉環，見該 token 註解）；新增
   border-radius 使精簡態讀成浮島。本段仍不動寬度公式，D1（唯一 120px 左緣）未撤回
   （2026-08-20 註：D1 本身已於後續批次撤回，詳見上方「header bar 左緣公式」
   段落 2026-08-20 追記），形變來源明確限定於 detach + radius + 高度三項。 */
.site-header.is-stuck .site-header__bar {
  min-height: var(--header-height-compact);
  padding-block: var(--space-2); /* 2026-08-19 用戶當面裁決撤回浮島：border-radius
     （--radius-alias-panel）已移除，stuck 態方正、無圓角。高度／padding 精簡行為
     保留（用戶否決的是形狀，不是精簡本身）。--radius-alias-panel 這個 token 本身
     不刪除——它是既有一般用途 alias（原始用途「mobile menu 展開面板」），只是不再
     被本規則消費，見 styles.css :root 該 token 定義旁的既有註解。 */
}

@media (max-width: 1023px) {
  /* 2026-08-19 B1 浮島第 3 段：base/wide 的預設值（上方規則）現也已是 space.2(8)，與此處
     數值相同——本覆寫技術上已成冗餘，但依專案「四斷點各自完整覆寫、不靠隱式繼承」慣例
     （同 --header-height-compact 三處 token、radius 三階梯的既有做法）保留顯式宣告，避免
     未來只改上方值卻誤以為 tablet/mobile 會跟著變動。 */
  .site-header.is-stuck .site-header__bar {
    padding-block: var(--space-2);
  }
}

/* ── Logo（component.logo；wordmark/subline 為區域值，不入 type_role，
     直接引用 type_scale.logo_wordmark / logo_subline，見報告說明） ── */
.logo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  text-decoration: none;
  flex-shrink: 0;
}

.logo__mark {
  flex-shrink: 0;
}

/* 捲動精簡態 logo 縮放（2026-08-19 D35）：transform 而非改 width——改 width 每幀觸發
   reflow，且 flex 版面下 logo 變窄會讓 justify-content:space-between 重算，nav 五項
   會跟著橫向滑動。transform-origin:left center 保左緣一個 px 都不動（D1 唯一容器左緣）。
   選擇器必須含 .site-header，避免誤傷 .nav-overlay__logo 與 footer 的同名元素。 */
.site-header .logo__mark {
  transform-origin: left center;
}

.site-header.is-stuck .logo__mark {
  transform: scale(var(--header-logo-scale-compact));
}

/* .logo__text / .logo__wordmark / .logo__subline 已於 2026-08-18 移除：
   logo 改用整顆 assets/icons/logo-full.svg（見上方 .logo__mark 與
   index.html header/nav-overlay 兩處），文字改由 SVG 內建路徑呈現，
   不再需要獨立的 HTML span + CSS 排版；移除前已 grep 確認全站（僅
   index.html 一份）零殘留引用。--type-scale-logo-wordmark-* /
   logo-subline-* token 本身保留在 :root（DESIGN.md 治理範圍，非本次
   改動授權範圍），只移除消費端的三個 class。 */

/* ── Primary nav（component.nav_item）。Hover／當前頁底線 2026-08-18
   重新裝回，推翻同日稍早的移除（見下方兩支獨立 QA 逐像素量測結論仍然
   成立：原稿 desktop-1448-fullpage.png 五個項目底下、含當前頁 HOME，
   確實一條底線都沒有——但靜止截圖本來就不可能拍到 :hover 態，「截圖
   沒畫」從來不能證明「沒打算做 hover 效果」。使用者看過兩項事實後裁示：
   底線從文字下方展開，hover 觸發；當前頁另外常駐一條。這是需求裁決，
   不是量測結果——見 2026-08-18 任務派工單。

   原始使用者回報「底線寬度與位置對不上文字」的根因：舊 ::after 用
   `transform: translateX(-100%)` 做靜止態，但沒有搭配 `overflow:hidden`
   ——線本身的完整寬度仍在，只是位置往左位移了一個自身寬度，結果整條線
   仍完整可見，只是畫到了「前一個項目」的下方。這次改用 `scaleX(0)`
   取代 translateX：scale 到 0 時線的實際繪製寬度就是 0，不管位置在哪都
   不會畫出東西，不需要額外靠 overflow:hidden 兜底整條線徹底消失。

   水平貼合（「貼合文字，不是貼合 `<a>` 的盒子」）：已在渲染頁 @1440 對
   全部 5 項實測（上游 header-nav-progress.md §2 指定的決定性測法）——
   同時取 a.getBoundingClientRect() 與
   Range.selectNodeContents(文字節點).getBoundingClientRect()。
   結果：五項全部 lsb = rsb = 0px（兩個矩形逐 px 相同）。這不是測失敗，
   是預期結果——`.primary-nav__list a` 本來就沒有 padding-inline，
   `<a>` 的 content box 本身就是文字排版盒本身，left/right 根本沒有
   padding 可以扣。（這不代表推翻原稿 PNG 像素掃描估出的每側 4~5px：
   那是對攤平點陣圖做「真墨水反鋸齒像素」偵測，量的是字型 glyph 本身的
   side bearing，屬於比任何 DOM／Range API 都更精細一層的量測，DOM 排版
   盒天生看不到那個層級；要看到需要 canvas 級逐 px 描邊偵測，對一條
   裝飾用 hover 底線不成比例。）所以下方直接 `left:0; right:0`，不內縮
   ——DOM 能量到的精度下，這就是最貼合文字的寫法。

   觸控地板：兩個最窄項目（HOME 39.47px、NEWS 38.70px，@1440 實測）的
   盒寬本身低於 44px 地板，即使高度（padding-block 撐出的 46px）已達標。
   沒有用 padding-inline 撐寬——那會連帶撐大 `<a>` 盒本身，把兩支獨立 QA
   已驗證貼合原稿的 ink-to-ink 間距（約 49px 節奏）撐開；改成只放大
   「可點區」本身：用完全透明、負 inset 的絕對定位 ::before。落在祖先
   border box 之外的子元素，點擊事件目標仍在該子元素上、仍算「點在」
   `<a>` 內（同一個可互動元素內部），因此可在零視覺/版面副作用下拉到
   觸控地板——下方 menu-toggle 的可點區擴大用同一招。 ── */
.primary-nav__list {
  display: flex;
  align-items: center;
  gap: var(--space-7);
  list-style: none;
}

.primary-nav__list a {
  position: relative;
  display: inline-block;
  padding-block: var(--space-3);
  font-family: var(--type-role-nav-item-family);
  font-size: var(--type-role-nav-item-size);
  line-height: var(--type-role-nav-item-line-height);
  letter-spacing: var(--type-role-nav-item-letter-spacing);
  font-weight: var(--type-role-nav-item-weight);
  text-transform: uppercase;
  color: var(--text-primary);
  text-decoration: none;
}

/* 隱形可點區擴大——理由見上方大註解。--space-1（4px）每側，讓最窄的
   NEWS（38.70px）也能到 46.70px ≥ 44px。 */
.primary-nav__list a::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline: calc(-1 * var(--space-1));
}

/* Hover／當前頁底線——left/right 為何不內縮、為何用 scaleX 而非
   translateX，見上方大註解。粗細 2px 沿用 .nav-overlay__list
   a[aria-current="page"] 既有的底線粗細（本檔下方），桌機/手機一致；
   顏色用 --text-link（藍），同一個既有色階選擇，非新發明。

   2026-08-18 全站微動態規格 P3 核對：套用矩陣要求 .primary-nav a 也套用
   .u-underline-wipe，但 HTML 未掛此 class 在這裡——查證後刻意排除，理由
   成立：這條 ::after 規則本身已是等價（甚至更完整，含 current-page 常駐態）
   的底線 hover 指示，若再疊加 .u-underline-wipe 的 ::before 底線會在同一
   位置（bottom:0, height:2px）疊出雙層規則同時動畫，視覺上會是同一條線
   被畫兩次、無新增效果，純屬多餘且增加維護面。維持現況，不補規則。 */
.primary-nav__list a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background-color: var(--text-link);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--motion-duration-hover) var(--motion-easing-standard);
}

.primary-nav__list a[aria-current="page"]::after {
  transform: scaleX(1);
}

/* 捲動精簡態 current-page／hover 底線（2026-08-19 D32）：accent.600 疊玻璃於
   #000000 上實測 2.85:1，未達 WCAG 1.4.11 狀態指示 3:1（疊 navy 3.06:1 邊緣通過，
   但需以最壞情況把關）。改用 accent.700，最壞 3.95:1。 */
.site-header.is-stuck .primary-nav__list a::after {
  background-color: var(--text-link-on-glass);
}

/* iOS Safari pitfalls #8：hover 專屬樣式收在 hover-capable 裝置內
   （沿用本檔既有慣例，見 .nav-overlay__services-list a:hover）。 */
@media (hover: hover) and (pointer: fine) {
  .primary-nav__list a:hover::after {
    transform: scaleX(1);
  }
}

/* 2026-08-18 全站微動態規格 P2：hardcode 1px 改 var(--motion-press-offset)，
   使 reduced-motion 安全網（該 token 於 :root reduce 區塊歸零為 0）能正確
   套用——字面值不受 token 覆寫影響，是既有的 reduce 漏洞。 */
.primary-nav__list a:active {
  transform: translateY(var(--motion-press-offset));
}

/* ── Menu toggle（漢堡按鈕，D7：48×40 圖示置中於 48×48 觸控框——這顆
   48×48 的觸控框本來就已經 ≥44×44，這次桌機顯示不需要另外撐可點區）。
   2026-08-18 起改為所有寬度恆定顯示（原本只有 `display:none` 這一條，
   靠下方 `@media (max-width:1023px)` 覆寫成 `inline-flex`，故只在
   mobile/tablet 出現）——原稿桌機截圖本來就與 nav 五項並存畫了這顆
   icon，故直接顯示、不再限寬度；nav-overlay 本身「顯示」的觸發規則
   同步不再限寬度，見下方 .nav-overlay 註解。 ── */
.menu-toggle {
  position: relative;   /* + z-index：讓 toggle 疊在 .nav-overlay 之上，開啟態仍可見可點。
     有效前提：.nav-overlay 是 .site-header 的子元素，.site-header 建立 stacking context，
     .nav-overlay 本身無 z-index → z-index:2 的 toggle 疊在其上。scrim 不會蓋住 toggle，
     這是預期行為（Nav toggle 規格 E5 註解）。 */
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-8);    /* 48px — 原字面值，改走 token */
  height: var(--space-8);
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  flex-shrink: 0;
}

.menu-toggle:active {
  transform: translateY(var(--motion-press-offset));
}

/* ☰ ↔ ✕ 形變 —— docs/spec-nav-toggle.md §3
   幾何：兩條線繞共同光學中心 (24, 19.5) 各旋轉 ∓17.354deg（= atan(7.5/24)，
   與 assets/icons/menu-close.svg 的 matrix 反解斜率完全相同）。
   scaleX 1.0477 = 50.2892/48，讓旋轉後的水平投影仍佔滿 48（同 menu-close.svg 的 x2）。
   三個數值不得取整——取整即偏離 Figma 的 ✕ 斜率。
   狀態選擇器一律用 [aria-expanded]，不新增 class：site.js 已在切換這個屬性，狀態已經
   在 DOM 上；再加一個 .is-open class 就有兩個真相源，會漂移。
   duration 用 --motion-duration-menu（280ms，非 --motion-duration-slide 240ms）——
   Dispatcher 裁決：圖示與面板是同時發生、位置相鄰的同一個手勢，共用 duration 才會一起
   收束，差 40ms 會看到 ✕ 先完成、面板還在跑。 */
.menu-toggle__icon {
  display: block;
}

.menu-toggle__bar {
  stroke: var(--icon-hamburger-stroke);
  stroke-width: 1;
  transform-box: view-box;
  transition: transform var(--motion-duration-menu) var(--motion-easing-standard),
              stroke var(--motion-duration-hover) var(--motion-easing-standard);
}

.menu-toggle__bar--top    { transform-origin: 24px 13.5px; }
.menu-toggle__bar--bottom { transform-origin: 24px 25.5px; }

.menu-toggle[aria-expanded="true"] .menu-toggle__bar--top {
  transform: translateY(var(--menu-toggle-bar-shift))
             rotate(var(--menu-toggle-bar-angle))
             scaleX(var(--menu-toggle-bar-stretch));
}

.menu-toggle[aria-expanded="true"] .menu-toggle__bar--bottom {
  transform: translateY(calc(-1 * var(--menu-toggle-bar-shift)))
             rotate(calc(-1 * var(--menu-toggle-bar-angle)))
             scaleX(var(--menu-toggle-bar-stretch));
}

@media (hover: hover) and (pointer: fine) {
  .menu-toggle:hover .menu-toggle__bar {
    stroke: var(--text-primary);
  }
}


/* ── Nav overlay（mobile/tablet 全螢幕選單面板。elevation.level3 仍
     UNVERIFIED_PENDING_PNG，暫用值＝level1 hairline + radius_alias.panel
     依修正清單 D-h；本輪做全出血滿版面板，四角無可見邊緣，
     radius_alias.panel 因此零使用者——DESIGN.md「uiux 本輪發現但未處理」
     第4點已預先接受此結果，非本輪缺陷。視覺依據：docs/figma-ref/
     nav-overlay.png（使用者提供的參考截圖，confidence 比照
     figma-measured-from-image 對待）。

     [hidden] 屬性陷阱（見團隊記憶 hidden-attr-display-trap）：預設
     display:none 由本規則明寫，不依賴 UA 預設；「顯示」的觸發規則見下方
     `:not([hidden])` / `.is-visible` 兩條——它們能蓋過上面這條 base
     `display:none`，靠的是選擇器本身比 `.nav-overlay` 更高的特異度
     （多了一個屬性/class 選擇器），不靠寬度篩選，也不需要 !important。

     2026-08-18 起這兩條「顯示」規則從原本收在
     `@media (max-width:1023px)` 內（結構上保證 ≥1024px 不會被打開）
     改成不分寬度恆定生效：menu-toggle 現在所有寬度都顯示（見上方
     .menu-toggle 註解），桌機也要能點開這個面板，原本的寬度限制是
     「桌機不支援 overlay」年代的設計，隨本次改動一併移除——`.primary-nav`
     隱藏（下方 `@media (max-width:1023px)` 區塊）仍然只在
     mobile/tablet 生效，未受影響：桌機是 nav 五項 + menu-toggle 並存，
     overlay 只在被點開時才蓋在最上層。

     2026-08-18 第二次改動（本次派工單）：使用者回報「點開後是彈滿版，
     不是右邊」——原稿量測報告證實右緣＝視口右緣、滿高、無圓角/邊框/陰影
     （報告 §1/§2），故本層（.nav-overlay，外層）與 .nav-overlay__panel
     （內層，DOM 已存在、非新增元素）職責拆開：
       - .nav-overlay（本規則）retained 為滿版 fixed 容器，但不再是
         「可見面板」本身——拿掉 background-color / box-shadow，改當
         「面板 + 遮罩」的外殼／捲動容器。box-shadow 原本是 hairline ring
         （--elevation-level1），滿版時貼在視口邊緣看不出來，改右側面板後
         左緣會現形，原稿量測確認無邊框，故整條移除（非改用別的值）。
       - .nav-overlay__panel（下一條規則）改為 372px 定寬、貼右對齊，
         背景色從本規則搬過去，成為唯一的「可見面板」。
     backdrop（面板外的半透明遮罩）是 Dispatcher 裁決、非原稿量測值
     （原稿整張圖框內 100% 是面板內部，見報告 §3，無法量測 backdrop 有無/
     顏色/透明度）。裁決要加一層半透明深色遮罩，但逐條核對 :root 全部
     顏色 token 後，當時找不到任何「半透明深色」token 可用（僅有 3 個
     rgba(255,255,255,...) 方向相反的 token，語意分別是 hero chevron 底、
     hairline-on-inverse、card-index 浮水印，顏色/用途皆不符），故當時
     只做了遮罩的「結構」——.nav-overlay 本身滿版覆蓋、可攔截點擊（見
     site.js 新增的 backdrop click-to-close），暫不上色，列為缺口待補。

     2026-08-18 缺口已補：新增語意 token --surface-scrim（rgba(0,0,0,0.5)，
     Dispatcher 2026-08-18 裁決值，同樣非原稿量測值——原稿無法量測見上段），
     下方 .nav-overlay 規則已改用 background-color: var(--surface-scrim)
     消費此 token（見該條規則），遮罩現在「已上色」，不再是透明結構。
     此段文字保留缺口曾經存在的來龍去脈，供後續維護者理解決策脈絡，不再
     視為待辦事項。 ── */
.nav-overlay {
  display: none;
  position: fixed;
  inset: 0;
  overflow-y: auto;
  overscroll-behavior: contain; /* P1-6 修復批（2026-08-19）：site.js 的 lockBodyScroll()
     已用 position:fixed + top 補償鎖住 body（iOS Safari pitfalls #6 正確手法，見該檔
     註解），此屬性補第二道防線——防止本層內部捲動（overflow-y:auto）觸底/觸頂後的
     scroll chaining 溢出到背後視口。純捲動行為屬性，不影響 layout/box model，零 CLS
     風險；不支援的舊引擎會靜默忽略，退回既有 body 鎖定機制，漸進增強。 */
  background-color: var(--surface-scrim);
  opacity: 0;
  transition: opacity var(--motion-duration-menu) var(--motion-easing-standard);
}

.nav-overlay:not([hidden]) {
  display: block;
}

.nav-overlay.is-visible {
  opacity: 1;
}

/* .nav-overlay__panel 現為唯一的「可見面板」——372px 定寬、margin-left:auto
   貼右（父層 .nav-overlay 已滿版，auto 左邊界是標準的定寬區塊靠右手法，
   不需要把父層改成 flex）。背景色從 .nav-overlay 搬過來；無 box-shadow/
   border/radius（原稿 ring 掃描確認框內全無，見報告 §2）。

   2026-08-18 第三次改動（本次派工單）：使用者裁決點開時面板應「從右邊
   滑進來」，QA-C2 量到目前入場只有 .nav-overlay 的 opacity transition，
   .nav-overlay__panel 的 transform 全程 computed none（從未宣告過）。
   本規則新增 transform: translateX(100%) 作為關閉態（面板停在自身寬度
   外的視口右側），下方 .nav-overlay.is-visible .nav-overlay__panel 覆寫
   為 translateX(0)。duration/easing 直接複用 .nav-overlay 本身 opacity
   transition 已在用的 --motion-duration-menu／--motion-easing-standard
   （非新發明 token）——兩者用同一組 token 讓遮罩淡入與面板滑入同步收尾，
   是 drawer 的標準組合。reduced-motion：--motion-duration-menu 已在上方
   @media (prefers-reduced-motion: reduce) 區塊歸零至 0.01ms（見該區塊），
   本規則複用同一 token 故自動繼承，不在此另寫 media query（硬約束，
   見上方 1478 行註解）。overflow：.nav-overlay 本身已宣告
   overflow-y: auto，未宣告 overflow-x 的情況下瀏覽器會將其 computed
   value 由預設 visible 轉為 auto（CSS Overflow 規格：一軸非 visible、
   另一軸未顯式宣告時的強制轉換），使 .nav-overlay 對兩軸都是捲動容器，
   翻譯出視口外的 panel 因此被 .nav-overlay 自身的捲動區域吸收，不會
   往上傳播成 document.scrollWidth 溢出——已用 Playwright 8 寬度實測
   （含關閉態）驗證，見任務報告。 */
.nav-overlay__panel {
  min-height: 100%;
  width: var(--nav-overlay-panel-width);
  margin-left: auto;
  display: flex;
  flex-direction: column;
  padding: calc(var(--header-height) + var(--space-6)) var(--nav-panel-inset-x) var(--space-10);
  background-color: var(--surface-subtle);
  transform: translateX(100%);
  transition: transform var(--motion-duration-menu) var(--motion-easing-standard);
}

.nav-overlay.is-visible .nav-overlay__panel {
  transform: translateX(0);
}

/* stuck 態開選單時的面板頂緣補償（2026-08-19 D30 配套）：.nav-overlay__panel 的
   padding-top 寫死跟著靜止態 --header-height；stuck 態 bar 只有精簡高度，若不覆寫
   會在面板頂端留一段莫名空白（102-72=30px 或 80-64=16px）。用後代選擇器即可
   （overlay 就在 header 裡面），不需要 :has()。 */
.site-header.is-stuck .nav-overlay__panel {
  padding-top: calc(var(--header-height-compact) + var(--space-6));
}

/* ≤768：372px 會比視口本身還寬，維持改動前的滿版行為（派工單允許「維持
   現況滿版」或「自訂合理值」兩選一，選前者：風險最低、不需另外裁決一個
   新的「留一小條」數字）。 */
@media (max-width: 768px) {
  .nav-overlay__panel {
    width: 100%;
    margin-left: 0;
  }
}

.nav-overlay__nav {
  margin-bottom: var(--space-10);
}

.nav-overlay__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  list-style: none;
}

/* 2026-08-18：改用 --type-scale-body-* 直接引用（不再走 --type-role-nav-item-*
   共用 role token）。原因：這組 role token 同時被 .primary-nav__list a
   （桌機 header）共用，但本次派工單要求兩者分道——header 字重改 400
   （Unit 3），面板導覽項字重／字級要對稿到原稿量測值（粗體維持不變、
   字級 ≈16 CSS，見報告 §5.2/§7 row 8/9），若繼續共用同一組 token 會兩邊
   一起變。既有 type-scale 階裡沒有剛好 16 的一階（h6=13 太小／h5=18
   太大），body=15 是最接近的既有階，且 line-height(24)+list gap(32,
   --space-6 不變) = 56，與原稿量測的節距目標（報告 §5.4：56 CSS）整數
   精確命中——不是巧合挑中，是驗證後才確定用這組。字重維持粗體，故
   font-weight 直接寫 --font-weight-bold（不透過已拆離的 role token）。
   letter-spacing/family 與原 h6 階同值（皆 0.01em／Albert Sans），對視覺
   無額外影響。同一手法（scale token 直接引用、不透過 role）在本檔
   .nav-overlay__eyebrow／.nav-overlay__services-list a／.skip-link 已有
   先例，非本次新發明的模式。 */
.nav-overlay__list a {
  position: relative; /* 2026-08-18 P3：.u-underline-wipe::before 定位錨點，見下方 */
  display: inline-block;
  font-family: var(--type-scale-body-family);
  font-size: var(--type-scale-body-size);
  line-height: var(--type-scale-body-line-height);
  letter-spacing: var(--type-scale-body-letter-spacing);
  font-weight: var(--font-weight-bold);
  text-transform: uppercase;
  color: var(--text-primary);
  text-decoration: none;
}

.nav-overlay__list a[aria-current="page"] {
  text-decoration: underline;
  text-decoration-color: var(--text-link);
  text-decoration-thickness: 2px;
  text-underline-offset: var(--space-1);
}

/* 2026-08-18 全站微動態規格 P3 核對：套用矩陣要求 .nav-overlay__list a 也套
   .u-underline-wipe，HTML 已掛 class。查證後判定與 .primary-nav__list a
   不同——這裡除了 [aria-current="page"] 的常駐底線外，非當前頁項目完全沒有
   任何 hover 指示（全檔 grep 零筆 .nav-overlay__list a:hover），排除理由不
   成立，故補上規則，手法比照既有 3 組 X.u-underline-wipe compound selector
   （.link-list a / .news-row__title:has() / .card__title，色值/厚度/duration
   皆沿用同一組既定值，非新發明）。
   `:not([aria-current="page"])` 排除當前頁項目觸發 hover/focus wipe——當前
   頁已有永久底線（text-underline-offset:var(--space-1) 定位），與這裡
   bottom:0 絕對定位的 wipe 底線不保證重合，疊加有雙線風險；且當前頁本身已
   有明確指示，不需要再疊一層互動態。此為技術保守判斷，非新配色/新元件
   決策，供覆核。 */
.nav-overlay__list a.u-underline-wipe::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background-color: var(--border-interactive);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--motion-duration-slide) var(--motion-easing-standard);
}

.nav-overlay__list a.u-underline-wipe:not([aria-current="page"]):focus-visible::before {
  transform: scaleX(1);
}

@media (hover: hover) and (pointer: fine) {
  .nav-overlay__list a.u-underline-wipe:not([aria-current="page"]):hover::before {
    transform: scaleX(1);
  }
}

/* ── Services 子清單（引用 component.footer.column_heading /
     component.footer.nav_item 的既有裁決——與 footer 的 SERVICES
     欄內容/語意相同，故沿用同一組樣式決定，非另外發明）。

     2026-08-18 B6（footer）派工單 C4／D 段指定的合併：原本此處是
     `.nav-overlay__eyebrow` 一份獨立規則，與 atom 層 `.eyebrow.
     eyebrow--label`（規格 §A-2 C4）指紋逐一比對後完全相同——
     font-family/size/line-height 皆透過 --type-role-eyebrow-*
     間接指向同一組 --type-scale-caption-* 值，letter-spacing 與
     text-transform 由 `.eyebrow--label` 覆寫為同一個
     --letter-spacing-label-wide／uppercase，color 的
     --text-eyebrow 與這裡原本引用的 --text-link 是同一個
     --color-accent-600（逐 token 核對，非目視判斷）。component-audit
     指紋聚類會把兩者判為同一 unique variant 卻掛兩個不同 class，
     即一級一致性缺陷，故依規格指示合併：HTML（index.html 的
     `.nav-overlay__services` 內）已改用 `.eyebrow.eyebrow--label`，
     本規則整塊移除。

     唯一遺失的是 margin-bottom:16px（原規則的 spacing，`.eyebrow`/
     `.eyebrow--label` 兩個 atom 都不帶 margin）——不改 atom 本身
     （atom 層 C1-C14 只有 B1 可編輯），改在這裡補一條情境限定的
     margin-bottom，數值沿用同一個 --space-4 token（與 footer 自己
     的 .site-footer__column 使用的 --section-head-gap 剛好同值
     16px，兩處視覺節奏一致）。 ── */
.nav-overlay__services .eyebrow--label {
  margin-bottom: var(--space-4);
}

.nav-overlay__services {
  margin-bottom: var(--space-9);
}

/* gap 2026-08-18 改 --space-4（16，原 --space-3=12）：原稿量測子項節距
   ≈36 CSS（報告 §5.4），現況 line-height(22)+gap 只有兩個既有 token 可選、
   對 36 等距（12→34／16→38，皆差 2），無精確命中；選較寬方向，與導覽
   項目節距 54→56 同樣「加寬」的整體傾向一致，非任意挑邊——判斷已於任務
   回報揭露，非隱性帶過。字級維持 caption(13px)：原稿反推 ≈11.9-12，
   本階已是最接近的既有值（12px 只存在於 tablet/mobile 覆寫範圍內，base
   階沒有對應階可換，維持不動）。 */
.nav-overlay__services-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  list-style: none;
}

/* font-weight/color 2026-08-18 對稿：原稿子項實測為粗體＋純黑核心（報告
   §5.3「明顯偏粗 600–700」／§4 逐字掃描 1159 個純黑核心像素），現況原是
   regular/secondary(#616161)——改用既有 --font-weight-bold(700，落在
   600–700 區間內)與 --text-primary(#000000，逐 token 對應原稿量到的
   #000000)，皆為既有 token，非新值。 */
.nav-overlay__services-list a {
  font-family: var(--type-scale-caption-family);
  font-size: var(--type-scale-caption-size);
  line-height: var(--type-scale-caption-line-height);
  font-weight: var(--font-weight-bold);
  color: var(--text-primary);
  text-decoration: none;
}

.nav-overlay__services-list a:focus-visible {
  color: var(--text-primary);
  text-decoration: underline;
}

/* iOS Safari pitfalls #8：同上，hover 專屬樣式收在 hover-capable 裝置內。 */
@media (hover: hover) and (pointer: fine) {
  .nav-overlay__services-list a:hover {
    color: var(--text-primary);
    text-decoration: underline;
  }
}

.nav-overlay__logo {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* E-logo 防禦（Nav toggle 規格）：本檔上方 `.logo__mark { flex-shrink: 0; }` 是全域規則，
   在寬度受限的面板內是溢出風險來源，此處收斂回可縮放。（不寫死行號：行號會隨本檔後續
   編輯漂移，寫死曾在本次編輯過程中出現一次落差，見任務報告自我修正記錄。） */
.nav-overlay__logo .logo__mark {
  max-width: 100%;
  height: auto;
}


/* ── Hero（component.hero_title / hero_subtitle；HERO 文字疊照片的可
     量測地板見 DESIGN.md「極端對比規則」節，遮罩 gradient 為 spec 給定
     的字面 rgba 數值，非本檔發明；HERO 上半 55% 不得放文字——本輪唯一
     文字塊靠 justify-content:flex-end 貼齊底部，天然滿足） ── */
.hero {
  position: relative;
  min-height: var(--hero-height);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: var(--hero-text-padding);
  overflow: clip;
}

.hero__media,
.hero__scrim {
  position: absolute;
  inset: 0;
}

/* Unit 2 hero carousel：.hero__media 現疊放 N 個 <picture>（一 slide 一張），
   靠 opacity 交叉淡入，因此每個 picture 都需要自己的盒模型可以定位/淡入淡出
   ——原本「單一 picture」年代用的 display:contents 手法（讓 picture 不佔
   盒模型、img 直接吃 .hero__media 的高度）在多 picture 疊放下不再適用
   （display:contents 元素沒有自己的盒可以 position/opacity）。改為每個
   picture 自己 position:absolute + inset:0（base reset 已給 display:block，
   不需重複宣告），img 對「已定位好的 picture」用 width/height:100% 填滿，
   不再依賴繼承 .hero__media 的高度。不影響 a11y——.hero__media 本身已
   aria-hidden。 */
.hero__slide-media {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity var(--motion-duration-carousel) var(--motion-easing-standard);
}

.hero__slide-media.is-active {
  opacity: 1;
}

.hero__bg {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--hero-bg-position-base);
  transform-origin: 50% 50%; /* 顯式宣告、不隨 object-position 偏移——兩者作用階段不同
     （object-fit 先決定取樣區，transform 再對已取樣畫面均勻縮放），origin 跟著
     object-position 走會讓 wide 斷點的取樣區在 Ken Burns 過程中非線性漂移
     （spec-hero-motion-2026-08-19 §2.11.4，硬約束）。 */
  animation-duration: var(--motion-duration-kenburns);
  animation-timing-function: var(--motion-easing-linear); /* linear 不是 ease：9 秒尺度上要讀成
     「持續勻速漂移」而非「有始有終」，ease-in-out 的可辨識加減速會讓效果性質整個變掉（spec §2.11.2） */
  animation-iteration-count: 1;
  animation-fill-mode: both;
  animation-play-state: paused; /* 預設 paused，只在下方 .is-active 規則時 running */
}

/* Unit 1 (2026-08-19 hero motion pass)：Ken Burns 方向交替，spec-hero-motion
   -2026-08-19 §2.11.1。odd(1st/3rd child = slide 1/3) 用 zoom-in keyframe，
   even(2nd child = slide 2) 用 zoom-out——用戶原話「Cut1 微微放大 cut2 微微
   縮小」。.hero__media 底下只有這 3 個 <picture> 是直接子元素，nth-child
   天然對應 data-slide，N 張皆成立不寫死 3。 */
.hero__slide-media:nth-child(odd) .hero__bg {
  animation-name: hero-kenburns-in;
}

.hero__slide-media:nth-child(even) .hero__bg {
  animation-name: hero-kenburns-out;
}

/* 「離場即凍結」完全靠這條規則失配達成，不需要 JS 介入——移除 is-active 後
   animation-play-state 掉回上方 .hero__bg 基準規則的 paused，CSS 動畫在
   play-state 從 running 轉 paused 的瞬間就地凍結在當下 scale。「進場重置
   currentTime=0」CSS 做不到，見 site.js render()。 */
.hero__slide-media.is-active .hero__bg {
  animation-play-state: running;
}

.hero__scrim {
  z-index: var(--z-hero-scrim);
  background: var(--hero-scrim-gradient);
  pointer-events: none;
}

.hero__content {
  position: relative;
  z-index: var(--z-hero-text);
  display: flex;
  flex-direction: column;
  gap: var(--hero-title-gap); /* F1：標題↔副標，見 :root token 註解 */
}

/* .hero__row = 副標(輪播文字區) + 控制項(計數器/chevron) 同一橫列，
   底部對齊、右對齊（原稿目視）；--hero-text-gap 歸位後在此消費
   （文字區↔控制區間距，40px）。 */
.hero__row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--hero-text-gap);
}

/* D22（≤768）：控制列改左對齊、移到文字下方獨立一列——D1 全站單一左緣是
   最強結構約束，行動版若只有控制列右對齊，唯一對齊錨即被打破（可翻：
   改 justify-content 一個值）。align-items 改 stretch（非 flex-start）
   是刻意的：column 方向下 cross-axis 決定「寬度」，.hero__slides 需要
   stretch 撐滿整列寬度副標才會在正確的可用寬度折行；只有 .hero__controls
   要「左對齊、寬度自適應內容」，故改用 align-self 單獨覆寫那一個子項，
   不動整列的 align-items（那會連 .hero__slides 一起壓縮成 shrink-to-fit，
   讓副標折行寬度算錯）。 */
@media (max-width: 768px) {
  .hero__row {
    flex-direction: column;
    align-items: stretch;
  }

  .hero__controls {
    align-self: flex-start;
  }
}

/* .hero__slides：3 個 .hero__slide（role=group）疊放於此，靠 min-height
   預留固定高度做零抖動保證（C7 容差 0px，spec §2.9(a)）。min-height 用
   --type-role-paragraph-line-height 而非規格算式所依據的 h5——現行
   .hero__subtitle 已改用 body 字級（commit 7773e66，非本次改動範圍），
   此處以現況字級為準，用 min-height 吸收三句在現況字級下可能不同的
   行數，不回頭改字級（任務派工單明講）。flex:1 讓文字區吃滿與控制項
   之間可用寬度；min-width:0 防止 flex 子項因長文字內容撐爆容器。 */
.hero__slides {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  min-height: calc(1 * var(--type-role-paragraph-line-height));
}

@media (max-width: 1023px) {
  .hero__slides {
    min-height: calc(2 * var(--type-role-paragraph-line-height));
  }
}

/* .hero__slide：交叉淡入的兩層意圖疊在同一條 transition 宣告上——
   「非 active」狀態（此規則）只轉場 opacity（160ms exit-easing，副標
   離場依規格只淡出、不位移），transform 不列入 transition-property，
   故從 translateY(0) 回到 var(--motion-transform-enter) 是「瞬間重置」
   （opacity 同時淡到 0，肉眼不可見這個瞬間跳動）。「active」狀態
   （下方 .is-active 規則）才轉場 opacity+transform（400ms standard +
   120ms delay，進場：淡入 + translateY(12px)→0）。CSS 在屬性變動當下
   讀「目的地」規則的 transition 設定，兩個方向天生不對稱，不需要 JS
   另外分辨「正在離場」與「正在進場」。reduced-motion：token 層已把
   duration 全部收斂到 0.01ms、--motion-transform-enter 收斂到 none，
   本層只消費 token，不重寫 @media (prefers-reduced-motion)（硬約束）。 */
.hero__slide {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end; /* 文字底部對齊，與 .hero__row 的 controls 同一條基線
     ——min-height 保留的空間若比實際行數高（如 tablet 兩行預留、實際只
     一行），文字仍貼底對齊，不會浮在保留空間頂端與控制項錯位 */
  opacity: 0;
  transform: var(--motion-transform-enter);
  transition: opacity var(--motion-duration-hover) var(--motion-easing-exit);
}

.hero__slide.is-active {
  opacity: 1;
  transform: translateY(0);
  transition:
    opacity var(--motion-duration-enter) var(--motion-easing-standard) calc(var(--motion-stagger) * 2),
    transform var(--motion-duration-enter) var(--motion-easing-standard) calc(var(--motion-stagger) * 2);
}

/* [hidden] 屬性陷阱（同本檔 .nav-overlay 既有處理手法）：author 的
   `display:flex` 規則會以 origin 優先權蓋過 UA 的 [hidden]{display:none}
   —— 不能只寫 .hero__controls{display:flex}，必須顯式用 :not([hidden])
   把「顯示」的觸發規則獨立出來，base 規則維持 display:none。 */
.hero__controls {
  display: none;
  align-items: center;
  gap: var(--hero-controls-gap);
  flex-shrink: 0;
  animation: hero-load-in var(--motion-duration-enter) var(--motion-easing-standard) backwards;
  animation-delay: calc(var(--motion-stagger) * 2);
}

.hero__controls:not([hidden]) {
  display: flex;
}

.hero__counter {
  display: inline-block; /* min-width 對 inline 元素無效，需 inline-block */
  min-width: var(--hero-counter-width);
  font-family: var(--type-scale-caption-family);
  font-size: var(--type-scale-caption-size);
  line-height: var(--type-scale-caption-line-height);
  letter-spacing: var(--type-scale-caption-letter-spacing);
  font-weight: var(--font-weight-regular);
  color: var(--text-on-inverse);
}

/* 當前/總數用字重差（700 vs 400）區分，不用透明度——60% 白字疊在合成底
   L=0.15 上實算 3.55:1 不及格（規格 §2.3 推導）。 */
.hero__counter b {
  font-weight: var(--font-weight-bold);
}

.hero__chevrons {
  display: flex;
  align-items: center;
  gap: var(--space-3); /* 兩顆間距，figma-exact */
}

/* D18：72×44 膠囊（非 72×72 正圓），見 :root token 註解。5 態全部沿用
   既有語意色 token，svg 用 stroke:currentColor 故 hover/focus 變色不需
   換資產（規格禁 <img> 載入 chevron 正是為此）。 */
.hero__chevron {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--hero-chevron-width);
  height: var(--hero-chevron-height);
  padding: 0;
  border-radius: var(--radius-alias-control);
  border: var(--border-width-hairline) solid var(--text-on-inverse);
  background-color: var(--surface-on-dark-subtle);
  color: var(--text-on-inverse);
  cursor: pointer;
  transition:
    background-color var(--motion-duration-hover) var(--motion-easing-standard),
    transform var(--motion-duration-press) var(--motion-easing-standard);
}

.hero__chevron svg {
  width: var(--hero-chevron-icon-size);
  height: var(--hero-chevron-icon-size);
}

/* prev = next 的 icon 轉 180 度，不另備資產（規格明文）。 */
.hero__chevron--prev svg {
  transform: rotate(180deg);
}

@media (hover: hover) and (pointer: fine) {
  .hero__chevron:hover {
    background-color: var(--surface-on-dark-subtle-hover);
  }
}

/* 2026-08-18 全站微動態規格衝突①裁決：撤銷 scale(0.97)，統一為
   translateY(var(--motion-press-offset))，同全站其餘 5 個 active 元件。
   理由：(a) 用戶拍板「不縮放」；(b) chevron 有 1px solid 邊框，scale 0.97
   讓邊框渲染為非整數 0.97px，在 hairline 語言的站上是可見的不一致。 */
.hero__chevron:active {
  background-color: var(--surface-on-dark-subtle-active);
  transform: translateY(var(--motion-press-offset));
}

.hero__chevron:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-on-dark);
  outline-offset: var(--focus-ring-offset);
}

/* 本案循環輪播，disabled 態永不觸發（連點禁止用 disabled 節流，見
   site.js 註解）——token/樣式仍保留，供日後改非循環時使用（規格明文
   「token 保留，供日後改非循環時使用」，非死碼）。 */
.hero__chevron:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* P7 載入進場共用 keyframes（.hero__title / slide1 .hero__subtitle /
   .hero__controls 三處共用，視覺值同 P6，觸發機制換成
   @keyframes+animation-fill-mode:backwards，不依賴 JS——hero 在首屏不需
   observer）。衝突排查（硬約束「先確認不會跟既有輪播切換 transition
   打架」）：.hero__title 為 .hero__content 直接子元素，不在 .hero__slide
   底下，父層 .hero__content／.hero__row 均無 opacity/transform 宣告，零
   干擾。 */
/* Unit 1 (2026-08-19 hero motion pass)：Ken Burns 振幅走 --hero-kenburns-
   {from,to} token（reduce 下於 :root 歸零為 1/1）。兩個方向各自一個
   keyframes 而非共用一個+animation-direction:alternate，是因為 alternate
   會在下一輪反向播放，但本設計每次進場都重置 currentTime=0 從頭開始
   （iteration-count:1，不循環），沒有「下一輪」可供 alternate 反向。 */
@keyframes hero-kenburns-in {
  from {
    transform: scale(var(--hero-kenburns-from));
  }
  to {
    transform: scale(var(--hero-kenburns-to));
  }
}

@keyframes hero-kenburns-out {
  from {
    transform: scale(var(--hero-kenburns-to));
  }
  to {
    transform: scale(var(--hero-kenburns-from));
  }
}

@keyframes hero-load-in {
  from {
    opacity: 0;
    transform: var(--motion-transform-enter);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.hero__title {
  margin: 0;
  font-family: var(--type-role-hero-title-family);
  font-size: var(--type-role-hero-title-size);
  line-height: var(--type-role-hero-title-line-height);
  letter-spacing: var(--type-role-hero-title-letter-spacing);
  font-weight: var(--type-role-hero-title-weight);
  color: var(--text-on-inverse);
  text-shadow: var(--elevation-text-shadow-hero);
  overflow-wrap: anywhere; /* breakpoints.floor_guard.rule 第二層保險 */
  animation: hero-load-in var(--motion-duration-enter) var(--motion-easing-standard) backwards;
  transition: opacity var(--motion-duration-enter) var(--motion-easing-standard); /* 淡入態（destination
     規則讀取慣例同 .hero__slide/.hero__slide.is-active，見該區塊註解），淡出態見下方 .is-swapping */
}

/* Unit 4 (2026-08-19，Dispatcher 現場裁決推翻 DESIGN.md:667 + spec-hero-
   carousel.md 硬約束13「主標三張共用」——理由與回寫清單見 frontend 報告)：
   主標逐張切換，單一 <h1> 淡出→換字→淡入，不是像 .hero__subtitle 那樣疊
   三個節點交叉淡入（內容本身要換，不能疊兩份）。換字動作見 site.js
   updateHeadline()/transitionend handler。reduce 下 --motion-duration-
   {enter,hover} 已於 :root token 歸零，本層不重寫 media query（既有硬
   約束，styles.css:2046 一線）。 */
.hero__title.is-swapping {
  opacity: 0;
  transition: opacity var(--motion-duration-hover) var(--motion-easing-exit);
}

.hero__subtitle {
  margin: 0;
  max-width: 58.5em; /* 2026-08-18 改用 body 字體後重量：用戶裁示「用body字體就好，
     字型、大小都跟內文一樣」，5 個排版屬性全改引用 --type-role-paragraph-*
     （與 .prose 同一組 role token），字級從 h5/700（18px）降為 body/400（15px
     base），字也變窄（400 比 700 窄）。Playwright 實測整句在 .prose 排版下的
     單行墨水寬度（Range/getBoundingClientRect，非目視）＝865.7px@15px。
     58.5em(=877.5px@15px) 留約 12px 餘裕，仍遠小於父容器 .hero__content 可用
     寬度 1200px。wide 斷點(16px)：58.5em=936px，同比例換算單行寬需求約 923px，
     餘裕約 13px，同樣不折行。em 隨斷點字級（body-size 14/14/15/16px）自動縮放，
     非 white-space:nowrap 硬鎖——tablet/mobile 可用寬度遠小於此上限，會自然先
     觸底折行（768/375 折行為正確行為，非缺陷，見任務硬約束）。 */
  font-family: var(--type-role-paragraph-family);
  font-size: var(--type-role-paragraph-size);
  line-height: var(--type-role-paragraph-line-height);
  letter-spacing: var(--type-role-paragraph-letter-spacing);
  font-weight: var(--type-role-paragraph-weight);
  color: var(--text-on-inverse);
  text-shadow: var(--elevation-text-shadow-hero);
  overflow-wrap: anywhere;
}

/* P7 載入進場只套 slide 1（首屏可見那個）。用 [data-slide="1"]（靜態屬性，
   JS 輪播只切換 is-active/inert/aria-hidden，不動 data-slide）而非
   .is-active 掛鉤——is-active 會隨輪播移到 slide 2/3，若掛在 is-active 上，
   切換時會被「重新觸發」一次進場動畫，跟輪播本身 .hero__slide.is-active 的
   crossfade transition（opacity+transform，見上方 .hero__slide 區塊，
   delay 同為 calc(var(--motion-stagger) * 2) 但作用在父層、與本規則作用在
   子層各自獨立疊加，非同一條宣告）打架。data-slide 保證這個動畫在該元素
   一生只播一次（頁面載入時）。父層 .hero__slide.is-active 在靜態 HTML
   已帶 is-active class，初始渲染直接吃 .is-active 規則（opacity:1/
   translateY(0)），CSS transition 只在屬性「變動」時觸發、初始渲染不算
   變動，故頁面載入當下父層無 transition 播放，只有本規則的 @keyframes
   在子層播放，時間軸不重疊。 */
.hero__slide[data-slide="1"] .hero__subtitle {
  animation: hero-load-in var(--motion-duration-enter) var(--motion-easing-standard) backwards;
  animation-delay: calc(var(--motion-stagger) * 1);
}

/* ── HERO 底圖 focal position 響應（source: docs/hero-bg-spec.md RWD 行為表）。
   斷點沿用既有 Unit 1 token-layer 覆寫點 max-width:1023 / max-width:768 /
   min-width:1441，未新增斷點（breakpoints._cascade _no_new）。這是獨立於
   Unit 1 四個 :root token media block 的新增規則，只動 .hero__bg 選擇器，
   不觸碰、不插入那四塊既有內容。floor_guard（≤374）沿用 mobile 這階，
   不另寫規則（C3：floor_guard 沿用 mobile 組資產，不另裁）。 ── */
@media (max-width: 1023px) {
  .hero__bg,
  .hero__video {
    object-position: var(--hero-bg-position-tablet);
  }
}

@media (max-width: 768px) {
  .hero__bg {
    object-position: var(--hero-bg-position-mobile);
  }
  /* .hero__video 不列入本斷點：<=768 影片節點根本不進 DOM（spec §2.5），
     沒有東西可以套這條 object-position。 */
}

@media (min-width: 1441px) {
  .hero__bg,
  .hero__video {
    object-position: var(--hero-bg-position-wide);
  }
}

/* ── Hero 影片本體（自動播放，2026-08-20 用戶推翻「點一下才播」後的新裁決；
   舊規格 docs/spec-hero-video-2026-08-20.md 已在檔頭標記 retracted，細節見該
   檔）。播放鍵／.hero__actions 版位已整組移除，hero 版面回到播放鍵加入之前的
   比例。零新視覺 token：影片本體幾何延用既有 --hero-bg-position-*／
   --motion-duration-carousel。唯一保留的行為參數 token 是 :root 的
   --hero-video-load-timeout（載入逾時判斷，非視覺，自動播放模式下仍需要，理由
   見該 token 定義處註解）。 ── */

/* Hero 影片本體（<video>，由 site.js 動態建立並插入 .hero__media 作為
   第 4 個子元素，附加在 3 個既有 .hero__slide-media <picture> 之後——
   不影響 :nth-child(odd/even) 的 Ken Burns 選擇器，那是複合選擇器，只有
   帶 .hero__slide-media class 的既有 3 個 <picture> 才會匹配，新增的
   <video> 不帶該 class）。硬約束 1（spec §2.9）：全站 reset 把 video 與
   img,picture,svg 綁在同一組 height:auto（styles.css 上方 reset 區塊），
   會讓影片塌高——此處顯式覆寫 height:100% 是必須的，非裝飾性宣告。
   poster 用既有 <picture>，不用 <video poster>，故本元件淡入淡出時底下
   一路是既有 slide 2 的 <picture>，不需額外機制。 */
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--hero-bg-position-base);
  opacity: 0;
  transition: opacity var(--motion-duration-carousel) var(--motion-easing-standard);
}

.hero__video.is-visible {
  opacity: 1;
}

/* spec §2.7 point 4：播放中暫停 Ken Burns（poster 被影片完全蓋住，縮放
   沒有人看得到，純耗 GPU）。.is-film-playing 只在 playing 態為真，
   paused/ended 後移除、Ken Burns 恢復 running——不額外判斷 paused 態是
   否也該暫停，逐字對應 spec 給的單一 class 規則。 */
.hero.is-film-playing .hero__bg {
  animation-play-state: paused;
}


/* ================================================================
   RESPONSIVE — primary-nav 隱藏。斷點沿用 Unit 1 已建立的 tablet
   覆寫點 max-width:1023px（與 --header-height 收窄到 72px、導覽收進
   漢堡為同一個觸發寬度，見 DESIGN.md rwd_behavior.tablet「導覽收進
   漢堡」與修正清單 F 組 <=960→<=1023）。

   2026-08-18 起本區塊只剩 .primary-nav 隱藏這一條：menu-toggle 的顯示
   與 nav-overlay 的顯示/開啟觸發規則已移到各自元件的本體規則旁、改為
   不分寬度恆定生效（見 .menu-toggle 與 .nav-overlay 兩處註解）——桌機
   現在是 nav 五項 + menu-toggle 並存，只有 mobile/tablet 才把五項收進
   漢堡（.primary-nav 消失、只剩 menu-toggle 可觸發同一個 overlay）。
   ================================================================ */
@media (max-width: 1023px) {
  .primary-nav {
    display: none;
  }
}

